UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

1,288 lines (1,111 loc) 46.9 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = exports.VIEW_EVENTS = void 0; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var THREE = _interopRequireWildcard(require("three")); var _Camera = _interopRequireDefault(require("../Renderer/Camera")); var _MainLoop = _interopRequireWildcard(require("./MainLoop")); var _ColorLayersOrdering = require("../Renderer/ColorLayersOrdering"); var _c3DEngine = _interopRequireDefault(require("../Renderer/c3DEngine")); var _RenderMode = _interopRequireDefault(require("../Renderer/RenderMode")); var _Crs = _interopRequireDefault(require("./Geographic/Crs")); var _Coordinates = _interopRequireDefault(require("./Geographic/Coordinates")); var _FeaturesUtils = _interopRequireDefault(require("../Utils/FeaturesUtils")); var _LayeredMaterial = require("../Renderer/LayeredMaterial"); var _Scheduler = _interopRequireDefault(require("./Scheduler/Scheduler")); var _Picking = _interopRequireDefault(require("./Picking")); // TEMPORY fix, on waiting THREE v110. THREE.ShaderChunk.logdepthbuf_fragment = THREE.ShaderChunk.logdepthbuf_fragment.replace('== 1.0', '> 0.5'); var VIEW_EVENTS = { /** * Fires when all the layers of the view are considered initialized. * Initialized in this context means: all layers are ready to be * displayed (no pending network access, no visual improvement to be * expected, ...). * If you add new layers, the event will be fired again when all * layers are ready. * @event View#layers-initialized * @property type {string} layers-initialized */ LAYERS_INITIALIZED: 'layers-initialized', LAYER_REMOVED: 'layer-removed', LAYER_ADDED: 'layer-added', INITIALIZED: 'initialized', COLOR_LAYERS_ORDER_CHANGED: _ColorLayersOrdering.COLOR_LAYERS_ORDER_CHANGED }; exports.VIEW_EVENTS = VIEW_EVENTS; var _syncGeometryLayerVisibility = function (layer, view) { if (layer.object3d) { layer.object3d.visible = layer.visible; } if (layer.threejsLayer) { if (layer.visible) { view.camera.camera3D.layers.enable(layer.threejsLayer); } else { view.camera.camera3D.layers.disable(layer.threejsLayer); } } }; function _preprocessLayer(view, layer, provider, parentLayer) { var source = layer.source; if (parentLayer && !layer.extent) { layer.extent = parentLayer.extent; if (source && !source.extent) { source.extent = parentLayer.extent; } } if (layer.isGeometryLayer) { if (parentLayer) { // layer.threejsLayer *must* be assigned before preprocessing, // because TileProvider.preprocessDataLayer function uses it. layer.threejsLayer = view.mainLoop.gfxEngine.getUniqueThreejsLayer(); } layer.defineLayerProperty('visible', true, function () { return _syncGeometryLayerVisibility(layer, view); }); _syncGeometryLayerVisibility(layer, view); // Find projection layer, this is projection destination layer.projection = view.referenceCrs; } else if (parentLayer.tileMatrixSets.includes(_Crs["default"].formatToTms(source.projection))) { layer.projection = source.projection; } else { layer.projection = parentLayer.extent.crs; } if (!layer.whenReady) { if (provider && provider.preprocessDataLayer) { layer.whenReady = provider.preprocessDataLayer(layer, view, view.mainLoop.scheduler, parentLayer); } else if (source && source.whenReady) { layer.whenReady = source.whenReady; } else { layer.whenReady = Promise.resolve(); } } layer.whenReady = layer.whenReady.then(function () { layer.ready = true; return layer; }); return layer; } var _eventCoords = new THREE.Vector2(); var matrix = new THREE.Matrix4(); var screen = new THREE.Vector2(); var ray = new THREE.Ray(); var direction = new THREE.Vector3(); var positionVector = new THREE.Vector3(); var coordinates = new _Coordinates["default"]('EPSG:4326'); var View = /*#__PURE__*/ function (_THREE$EventDispatche) { (0, _inherits2["default"])(View, _THREE$EventDispatche); /** * Constructs an Itowns View instance * * @param {string} crs - The default CRS of Three.js coordinates. Should be a cartesian CRS. * @param {HTMLElement} viewerDiv - Where to instanciate the Three.js scene in the DOM * @param {Object=} options - Optional properties. * @param {?MainLoop} options.mainLoop - {@link MainLoop} instance to use, otherwise a default one will be constructed * @param {?(WebGLRenderer|object)} options.renderer - {@link WebGLRenderer} instance to use, otherwise * a default one will be constructed. In this case, if options.renderer is an object, it will be used to * configure the renderer (see {@link c3DEngine}. If not present, a new &lt;canvas> will be created and * added to viewerDiv (mutually exclusive with mainLoop) * @param {?Scene} options.scene3D - {@link Scene} instance to use, otherwise a default one will be constructed * @constructor */ function View(crs, viewerDiv) { var _this; var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; (0, _classCallCheck2["default"])(this, View); if (!viewerDiv) { throw new Error('Invalid viewerDiv parameter (must non be null/undefined)'); } _this = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(View).call(this)); _this.referenceCrs = crs; coordinates.crs = crs; var engine; // options.renderer can be 2 separate things: // - an actual renderer (in this case we don't use viewerDiv) // - options for the renderer to be created if (options.renderer && options.renderer.domElement) { engine = new _c3DEngine["default"](options.renderer); } else { engine = new _c3DEngine["default"](viewerDiv, options.renderer); } _this.mainLoop = options.mainLoop || new _MainLoop["default"](new _Scheduler["default"](), engine); _this.scene = options.scene3D || new THREE.Scene(); if (!options.scene3D) { _this.scene.autoUpdate = false; } _this.camera = new _Camera["default"](_this.referenceCrs, _this.mainLoop.gfxEngine.getWindowSize().x, _this.mainLoop.gfxEngine.getWindowSize().y, options); _this._frameRequesters = {}; _this._layers = []; window.addEventListener('resize', function () { // If the user gave us a container (<div>) then itowns' size is // the container's size. Otherwise we use window' size. _this.mainLoop.gfxEngine.onWindowResize(viewerDiv.clientWidth, viewerDiv.clientHeight); _this.camera.resize(viewerDiv.clientWidth, viewerDiv.clientHeight); _this.notifyChange(_this.camera.camera3D); }, false); _this._changeSources = new Set(); _this._delayedFrameRequesterRemoval = []; _this._allLayersAreReadyCallback = function () { // all layers must be ready var allReady = _this.getLayers().every(function (layer) { return layer.ready; }); if (allReady && _this.mainLoop.scheduler.commandsWaitingExecutionCount() == 0 && _this.mainLoop.renderingState == _MainLoop.RENDERING_PAUSED) { _this.dispatchEvent({ type: VIEW_EVENTS.LAYERS_INITIALIZED }); _this.removeFrameRequester(_MainLoop.MAIN_LOOP_EVENTS.UPDATE_END, _this._allLayersAreReadyCallback); } }; _this.camera.resize(viewerDiv.clientWidth, viewerDiv.clientHeight); var fn = function () { _this.removeEventListener(VIEW_EVENTS.LAYERS_INITIALIZED, fn); _this.dispatchEvent({ type: VIEW_EVENTS.INITIALIZED }); }; _this.addEventListener(VIEW_EVENTS.LAYERS_INITIALIZED, fn); _this._fullSizeDepthBuffer = null; _this.addFrameRequester(_MainLoop.MAIN_LOOP_EVENTS.BEFORE_RENDER, function () { if (_this._fullSizeDepthBuffer != null && _this._fullSizeDepthBuffer.needsUpdate) { // clean depth buffer _this._fullSizeDepthBuffer = null; } }); // Focus needed to capture some key events. viewerDiv.focus(); return _this; } /** * Add layer in viewer. * The layer id must be unique. * * This function calls `preprocessDataLayer` of the relevant provider with this * layer and set `layer.whenReady` to a promise that resolves when * the preprocessing operation is done. This promise is also returned by * `addLayer` allowing to chain call. * * @param {LayerOptions|Layer|GeometryLayer} layer * @param {Layer=} parentLayer * @return {Promise} a promise resolved with the new layer object when it is fully initialized or rejected if any error occurred. */ (0, _createClass2["default"])(View, [{ key: "addLayer", value: function addLayer(layer, parentLayer) { var _this2 = this; return new Promise(function (resolve, reject) { if (!layer) { reject(new Error('layer is undefined')); return; } var duplicate = _this2.getLayerById(layer.id); if (duplicate) { reject(new Error("Invalid id '".concat(layer.id, "': id already used"))); return; } var protocol = layer.source ? layer.source.protocol : layer.protocol; var provider = _this2.mainLoop.scheduler.getProtocolProvider(protocol); if (layer.protocol && !provider) { reject(new Error("".concat(layer.protocol, " is not a recognized protocol name."))); return; } layer = _preprocessLayer(_this2, layer, provider, parentLayer); if (parentLayer) { if (layer.isColorLayer) { var layerColors = _this2.getLayers(function (l) { return l.isColorLayer; }); var sumColorLayers = parentLayer.countColorLayersTextures.apply(parentLayer, (0, _toConsumableArray2["default"])(layerColors).concat([layer])); if (sumColorLayers <= (0, _LayeredMaterial.getMaxColorSamplerUnitsCount)()) { parentLayer.attach(layer); } else { reject(new Error("Cant add color layer ".concat(layer.id, ": the maximum layer is reached"))); return; } } else { parentLayer.attach(layer); } } else { if (typeof layer.update !== 'function') { reject(new Error('Cant add GeometryLayer: missing a update function')); return; } if (typeof layer.preUpdate !== 'function') { reject(new Error('Cant add GeometryLayer: missing a preUpdate function')); return; } _this2._layers.push(layer); } if (layer.object3d && !layer.object3d.parent && layer.object3d !== _this2.scene) { _this2.scene.add(layer.object3d); } layer.whenReady.then(function (layer) { _this2.notifyChange(parentLayer || layer, false); if (!_this2._frameRequesters[_MainLoop.MAIN_LOOP_EVENTS.UPDATE_END] || !_this2._frameRequesters[_MainLoop.MAIN_LOOP_EVENTS.UPDATE_END].includes(_this2._allLayersAreReadyCallback)) { _this2.addFrameRequester(_MainLoop.MAIN_LOOP_EVENTS.UPDATE_END, _this2._allLayersAreReadyCallback); } resolve(layer); }); _this2.dispatchEvent({ type: VIEW_EVENTS.LAYER_ADDED, layerId: layer.id }); }); } /** * Removes a specific imagery layer from the current layer list. This removes layers inserted with attach(). * @example * view.removeLayer('layerId'); * @param {string} layerId The identifier * @return {boolean} */ }, { key: "removeLayer", value: function removeLayer(layerId) { var layer = this.getLayerById(layerId); if (layer) { var parentLayer = layer.parent; // Remove and dispose all nodes layer["delete"](); // Detach layer if it's attached if (parentLayer && !parentLayer.detach(layer)) { throw new Error("Error to detach ".concat(layerId, " from ").concat(parentLayer.id)); } else if (parentLayer == undefined) { // Remove layer from viewer this._layers.splice(this._layers.findIndex(function (l) { return l.id == layerId; }), 1); } if (layer.isColorLayer) { // Update color layers sequence var imageryLayers = this.getLayers(function (l) { return l.isColorLayer; }); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = imageryLayers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var color = _step.value; if (color.sequence > layer.sequence) { color.sequence--; } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator["return"] != null) { _iterator["return"](); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } this.notifyChange(this.camera); this.dispatchEvent({ type: VIEW_EVENTS.LAYER_REMOVED, layerId: layerId }); return true; } else { throw new Error("".concat(layerId, " doesn't exist")); } } /** * Notifies the scene it needs to be updated due to changes exterior to the * scene itself (e.g. camera movement). * non-interactive events (e.g: texture loaded) * @param {*} changeSource * @param {boolean} needsRedraw - indicates if notified change requires a full scene redraw. */ }, { key: "notifyChange", value: function notifyChange() { var changeSource = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; var needsRedraw = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (changeSource) { this._changeSources.add(changeSource); if ((changeSource.isTileMesh || changeSource.isCamera) && this._fullSizeDepthBuffer) { this._fullSizeDepthBuffer.needsUpdate = true; } } this.mainLoop.scheduleViewUpdate(this, needsRedraw); } /** * Get all layers, with an optionnal filter applied. * The filter method will be called with 2 args: * - 1st: current layer * - 2nd: (optional) the geometry layer to which the current layer is attached * @example * // get all layers * view.getLayers(); * // get all color layers * view.getLayers(layer => layer.isColorLayer); * // get all elevation layers * view.getLayers(layer => layer.isElevationLayer); * // get all geometry layers * view.getLayers(layer => layer.isGeometryLayer); * // get one layer with id * view.getLayers(layer => layer.id === 'itt'); * @param {function(Layer):boolean} filter * @returns {Array<Layer>} */ }, { key: "getLayers", value: function getLayers(filter) { var result = []; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = this._layers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var layer = _step2.value; if (!filter || filter(layer)) { result.push(layer); } var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = layer.attachedLayers[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var attached = _step3.value; if (!filter || filter(attached, layer)) { result.push(attached); } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3["return"] != null) { _iterator3["return"](); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) { _iterator2["return"](); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return result; } /** * Gets the layer by identifier. * * @param {String} layerId The layer identifier * @return {Layer} The layer by identifier. */ }, { key: "getLayerById", value: function getLayerById(layerId) { var layers = this.getLayers(function (l) { return l.id === layerId; }); if (layers.length) { return layers[0]; } } /** * @param {Layer} layer * @returns {GeometryLayer} the parent layer of the given layer or undefined. */ }, { key: "getParentLayer", value: function getParentLayer(layer) { var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = this._layers[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var geometryLayer = _step4.value; var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = geometryLayer.attachedLayers[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { var attached = _step5.value; if (attached === layer) { return geometryLayer; } } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5["return"] != null) { _iterator5["return"](); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4["return"] != null) { _iterator4["return"](); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } } /** * @name FrameRequester * @function * * @description * Method that will be called each time the `MainLoop` updates. This function * will be given as parameter the delta (in ms) between this update and the * previous one, and whether or not we just started to render again. This update * is considered as the "next" update if `view.notifyChange` was called during a * precedent update. If `view.notifyChange` has been called by something else * (other micro/macrotask, UI events etc...), then this update is considered as * being the "first". It can also receive optional arguments, depending on the * attach point of this function. Currently only `BEFORE_LAYER_UPDATE / * AFTER_LAYER_UPDATE` attach points provide an additional argument: the layer * being updated. * <br><br> * * This means that if a `frameRequester` function wants to animate something, it * should keep on calling `view.notifyChange` until its task is done. * <br><br> * * Implementors of `frameRequester` should keep in mind that this function will * be potentially called at each frame, thus care should be given about * performance. * <br><br> * * Typical frameRequesters are controls, module wanting to animate moves or UI * elements etc... Basically anything that would want to call * requestAnimationFrame. * * @param {number} dt * @param {boolean} updateLoopRestarted * @param {...*} args */ /** * Add a frame requester to this view. * * FrameRequesters can activate the MainLoop update by calling view.notifyChange. * * @param {String} when - decide when the frameRequester should be called during * the update cycle. Can be any of {@link MAIN_LOOP_EVENTS}. * @param {FrameRequester} frameRequester - this function will be called at each * MainLoop update with the time delta between last update, or 0 if the MainLoop * has just been relaunched. */ }, { key: "addFrameRequester", value: function addFrameRequester(when, frameRequester) { if (typeof frameRequester !== 'function') { throw new Error('frameRequester must be a function'); } if (!this._frameRequesters[when]) { this._frameRequesters[when] = [frameRequester]; } else { this._frameRequesters[when].push(frameRequester); } } /** * Remove a frameRequester. * The effective removal will happen either later; at worst it'll be at * the beginning of the next frame. * * @param {String} when - attach point of this requester. Can be any of * {@link MAIN_LOOP_EVENTS}. * @param {FrameRequester} frameRequester */ }, { key: "removeFrameRequester", value: function removeFrameRequester(when, frameRequester) { if (this._frameRequesters[when].includes(frameRequester)) { this._delayedFrameRequesterRemoval.push({ when: when, frameRequester: frameRequester }); } else { console.error('Invalid call to removeFrameRequester: frameRequester isn\'t registered'); } } }, { key: "_executeFrameRequestersRemovals", value: function _executeFrameRequestersRemovals() { var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = this._delayedFrameRequesterRemoval[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { var toDelete = _step6.value; var index = this._frameRequesters[toDelete.when].indexOf(toDelete.frameRequester); if (index >= 0) { this._frameRequesters[toDelete.when].splice(index, 1); } else { console.warn('FrameReq has already been removed'); } } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6["return"] != null) { _iterator6["return"](); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } this._delayedFrameRequesterRemoval.length = 0; } /** * Execute a frameRequester. * * @param {String} when - attach point of this (these) requester(s). Can be any * of {@link MAIN_LOOP_EVENTS}. * @param {Number} dt - delta between this update and the previous one * @param {boolean} updateLoopRestarted * @param {...*} args - optional arguments */ }, { key: "execFrameRequesters", value: function execFrameRequesters(when, dt, updateLoopRestarted) { if (!this._frameRequesters[when]) { return; } if (this._delayedFrameRequesterRemoval.length > 0) { this._executeFrameRequestersRemovals(); } for (var _len = arguments.length, args = new Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) { args[_key - 3] = arguments[_key]; } var _iteratorNormalCompletion7 = true; var _didIteratorError7 = false; var _iteratorError7 = undefined; try { for (var _iterator7 = this._frameRequesters[when][Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { var frameRequester = _step7.value; if (frameRequester.update) { frameRequester.update(dt, updateLoopRestarted, args); } else { frameRequester(dt, updateLoopRestarted, args); } } } catch (err) { _didIteratorError7 = true; _iteratorError7 = err; } finally { try { if (!_iteratorNormalCompletion7 && _iterator7["return"] != null) { _iterator7["return"](); } } finally { if (_didIteratorError7) { throw _iteratorError7; } } } } /** * Extract view coordinates from a mouse-event / touch-event * @param {event} event - event can be a MouseEvent or a TouchEvent * @param {THREE.Vector2} target - the target to set the view coords in * @param {number} [touchIdx=0] - finger index when using a TouchEvent * @return {THREE.Vector2} - view coordinates (in pixels, 0-0 = top-left of the View) */ }, { key: "eventToViewCoords", value: function eventToViewCoords(event) { var target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _eventCoords; var touchIdx = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; if (event.touches === undefined || !event.touches.length) { return target.set(event.offsetX, event.offsetY); } else { var br = this.mainLoop.gfxEngine.renderer.domElement.getBoundingClientRect(); return target.set(event.touches[touchIdx].clientX - br.x, event.touches[touchIdx].clientY - br.y); } } /** * Extract normalized coordinates (NDC) from a mouse-event / touch-event * @param {event} event - event can be a MouseEvent or a TouchEvent * @param {number} touchIdx - finger index when using a TouchEvent (default: 0) * @return {THREE.Vector2} - NDC coordinates (x and y are [-1, 1]) */ }, { key: "eventToNormalizedCoords", value: function eventToNormalizedCoords(event) { var touchIdx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; return this.viewToNormalizedCoords(this.eventToViewCoords(event, _eventCoords, touchIdx)); } /** * Convert view coordinates to normalized coordinates (NDC) * @param {THREE.Vector2} viewCoords (in pixels, 0-0 = top-left of the View) * @return {THREE.Vector2} - NDC coordinates (x and y are [-1, 1]) */ }, { key: "viewToNormalizedCoords", value: function viewToNormalizedCoords(viewCoords) { _eventCoords.x = 2 * (viewCoords.x / this.camera.width) - 1; _eventCoords.y = -2 * (viewCoords.y / this.camera.height) + 1; return _eventCoords; } /** * Convert NDC coordinates to view coordinates * @param {THREE.Vector2} ndcCoords * @return {THREE.Vector2} - view coordinates (in pixels, 0-0 = top-left of the View) */ }, { key: "normalizedToViewCoords", value: function normalizedToViewCoords(ndcCoords) { _eventCoords.x = (ndcCoords.x + 1) * 0.5 * this.camera.width; _eventCoords.y = (ndcCoords.y - 1) * -0.5 * this.camera.height; return _eventCoords; } /** * Return objects from some layers/objects3d under the mouse in this view. * * @param {Object} mouseOrEvt - mouse position in window coordinates (0, 0 = top-left) * or MouseEvent or TouchEvent * @param {number} radius - picking will happen in a circle centered on mouseOrEvt. Radius * is the radius of this circle, in pixels * @param {...*} where - where to look for objects. Can be either: empty (= look * in all layers with type == 'geometry'), layer ids or layers or a mix of all * the above. * @return {Array} - an array of objects. Each element contains at least an object * property which is the Object3D under the cursor. Then depending on the queried * layer/source, there may be additionnal properties (coming from THREE.Raycaster * for instance). * * @example * view.pickObjectsAt({ x, y }) * view.pickObjectsAt({ x, y }, 1, 'wfsBuilding') * view.pickObjectsAt({ x, y }, 3, 'wfsBuilding', myLayer) */ }, { key: "pickObjectsAt", value: function pickObjectsAt(mouseOrEvt) { var radius = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var results = []; for (var _len2 = arguments.length, where = new Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) { where[_key2 - 2] = arguments[_key2]; } var sources = where.length == 0 ? this.getLayers(function (l) { return l.isGeometryLayer; }) : [].concat(where); var mouse = mouseOrEvt instanceof Event ? this.eventToViewCoords(mouseOrEvt) : mouseOrEvt; var _iteratorNormalCompletion8 = true; var _didIteratorError8 = false; var _iteratorError8 = undefined; try { for (var _iterator8 = sources[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { var source = _step8.value; if (source.isLayer || typeof source === 'string') { var layer = typeof source === 'string' ? this.getLayerById(source) : source; if (!layer || !layer.ready) { console.warn('view.pickObjectAt : layer is not ready : ', source); continue; } var parentLayer = this.getParentLayer(layer); if (!parentLayer) { var sp = layer.pickObjectsAt(this, mouse, radius); // warning: sp might be very large, so we can't use '...sp' (we'll hit // 'javascript maximum call stack size exceeded' error) nor // Array.prototype.push.apply(result, sp) for (var i = 0; i < sp.length; i++) { results.push(sp[i]); } } else { // raycast using parent layer object3d var obj = _Picking["default"].pickObjectsAt(this, mouse, radius, parentLayer.object3d); // then filter the results var _iteratorNormalCompletion9 = true; var _didIteratorError9 = false; var _iteratorError9 = undefined; try { for (var _iterator9 = obj[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) { var o = _step9.value; if (o.layer === layer) { results.push(o); } } } catch (err) { _didIteratorError9 = true; _iteratorError9 = err; } finally { try { if (!_iteratorNormalCompletion9 && _iterator9["return"] != null) { _iterator9["return"](); } } finally { if (_didIteratorError9) { throw _iteratorError9; } } } } } else if (source.isObject3D) { _Picking["default"].pickObjectsAt(this, mouse, radius, source, results); } else { throw new Error("Invalid where arg (value = ".concat(where, "). Expected layers, layer ids or Object3Ds")); } } } catch (err) { _didIteratorError8 = true; _iteratorError8 = err; } finally { try { if (!_iteratorNormalCompletion8 && _iterator8["return"] != null) { _iterator8["return"](); } } finally { if (_didIteratorError8) { throw _iteratorError8; } } } return results; } /** * Return the current zoom scale at the central point of the view. This * function compute the scale of a map. * * @param {number} pitch - Screen pitch, in millimeters ; 0.28 by default * * @return {number} The zoom scale. */ }, { key: "getScale", value: function getScale() { var pitch = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.28; return this.getScaleFromDistance(pitch, this.getDistanceFromCamera()); } }, { key: "getScaleFromDistance", value: function getScaleFromDistance() { var pitch = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.28; var distance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; pitch /= 1000; var fov = THREE.Math.degToRad(this.camera.camera3D.fov); var unit = this.camera.height / (2 * distance * Math.tan(fov * 0.5)); return pitch * unit; } /** * Given a screen coordinates, get the distance between the projected * coordinates and the camera associated to this view. * * @param {THREE.Vector2} [screenCoord] - The screen coordinate to get the * distance at. By default this is the middle of the screen. * * @return {number} The distance in meters. */ }, { key: "getDistanceFromCamera", value: function getDistanceFromCamera(screenCoord) { this.getPickingPositionFromDepth(screenCoord, positionVector); return this.camera.camera3D.position.distanceTo(positionVector); } /** * Get, for a specific screen coordinate, the projected distance on the * surface of the main layer of the view. * * @param {number} [pixels=1] - The size, in pixels, to get in meters. * @param {THREE.Vector2} [screenCoord] - The screen coordinate to get the * projected distance at. By default, this is the middle of the screen. * * @return {number} The projected distance in meters. */ }, { key: "getPixelsToMeters", value: function getPixelsToMeters() { var pixels = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; var screenCoord = arguments.length > 1 ? arguments[1] : undefined; return this.getPixelsToMetersFromDistance(pixels, this.getDistanceFromCamera(screenCoord)); } }, { key: "getPixelsToMetersFromDistance", value: function getPixelsToMetersFromDistance() { var pixels = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; var distance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; return pixels * distance / this.camera._preSSE; } /** * Get, for a specific screen coordinate, this size in pixels of a projected * distance on the surface of the main layer of the view. * * @param {number} [meters=1] - The size, in meters, to get in pixels. * @param {THREE.Vector2} [screenCoord] - The screen coordinate to get the * projected distance at. By default, this is the middle of the screen. * * @return {number} The projected distance in meters. */ }, { key: "getMetersToPixels", value: function getMetersToPixels() { var meters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; var screenCoord = arguments.length > 1 ? arguments[1] : undefined; return this.getMetersToPixelsFromDistance(meters, this.getDistanceFromCamera(screenCoord)); } }, { key: "getMetersToPixelsFromDistance", value: function getMetersToPixelsFromDistance() { var meters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; var distance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; return this.camera._preSSE * meters / distance; } /** * Returns features under the mouse, for a set of specified layers. * * @param {MouseEvent|Object} mouseOrEvt - A MouseEvent, or a screen * coordinates. * @param {number} [radius=3] - The precision of the picking, in pixels. * @param {Layer[]} [where] - The layers to look into. If not specified, all * `GeometryLayer` and `ColorLayer` layers of this view will be looked in. * * @return {Object} - An object, with a property per layer. For example, * looking for features on layers `wfsBuilding` and `wfsRoads` will give an * object like `{ wfsBuilding: [...], wfsRoads: [] }`. Each property is made * of an array, that can be empty or filled with found features. * * @example * view.pickFeaturesAt({ x, y }); * view.pickFeaturesAt({ x, y }, 1, ['wfsBuilding']); * view.pickFeaturesAt({ x, y }, 3, ['wfsBuilding', myLayer]); */ }, { key: "pickFeaturesAt", value: function pickFeaturesAt(mouseOrEvt) { var radius = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3; var where = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; var result = {}; var layers = where.length == 0 ? this.getLayers(function (l) { return l.isGeometryLayer || l.isColorLayer; }) : where; layers = layers.map(function (l) { var id = l.isLayer ? l.id : l; result[id] = []; return id; }); // Get the mouse coordinates to the correct system var mouse = mouseOrEvt instanceof Event ? this.eventToViewCoords(mouseOrEvt, _eventCoords) : mouseOrEvt; this.getPickingPositionFromDepth(mouse, positionVector); var distance = this.camera.camera3D.position.distanceTo(positionVector); coordinates.setFromVector3(positionVector); // Get the correct precision; the position variable will be set in this // function. var precision; var precisions = { M: this.getPixelsToMetersFromDistance(radius, distance), D: 0.001 * radius }; if (this.isPlanarView) { precisions.D = precisions.M; } else if (this.getPixelsToDegrees) { precisions.D = this.getMetersToDegrees(precisions.M); } // Get the tile corresponding to where the cursor is var tiles = _Picking["default"].pickTilesAt(this, mouse, radius, this.tileLayer); var _iteratorNormalCompletion10 = true; var _didIteratorError10 = false; var _iteratorError10 = undefined; try { for (var _iterator10 = tiles[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) { var tile = _step10.value; if (!tile.object.material) { continue; } var _iteratorNormalCompletion11 = true; var _didIteratorError11 = false; var _iteratorError11 = undefined; try { for (var _iterator11 = tile.object.material.getLayers(layers)[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { var materialLayer = _step11.value; var _iteratorNormalCompletion12 = true; var _didIteratorError12 = false; var _iteratorError12 = undefined; try { for (var _iterator12 = materialLayer.textures[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) { var texture = _step12.value; if (!texture.parsedData) { continue; } precision = _Crs["default"].isMetricUnit(texture.parsedData.crs) ? precisions.M : precisions.D; result[materialLayer.id] = result[materialLayer.id].concat(_FeaturesUtils["default"].filterFeaturesUnderCoordinate(coordinates, texture.parsedData, precision)); } } catch (err) { _didIteratorError12 = true; _iteratorError12 = err; } finally { try { if (!_iteratorNormalCompletion12 && _iterator12["return"] != null) { _iterator12["return"](); } } finally { if (_didIteratorError12) { throw _iteratorError12; } } } } } catch (err) { _didIteratorError11 = true; _iteratorError11 = err; } finally { try { if (!_iteratorNormalCompletion11 && _iterator11["return"] != null) { _iterator11["return"](); } } finally { if (_didIteratorError11) { throw _iteratorError11; } } } } } catch (err) { _didIteratorError10 = true; _iteratorError10 = err; } finally { try { if (!_iteratorNormalCompletion10 && _iterator10["return"] != null) { _iterator10["return"](); } } finally { if (_didIteratorError10) { throw _iteratorError10; } } } return result; } }, { key: "readDepthBuffer", value: function readDepthBuffer(x, y, width, height) { var g = this.mainLoop.gfxEngine; var currentWireframe = this.tileLayer.wireframe; var currentOpacity = this.tileLayer.opacity; var currentVisibility = this.tileLayer.visible; if (currentWireframe) { this.tileLayer.wireframe = false; } if (currentOpacity < 1.0) { this.tileLayer.opacity = 1.0; } if (!currentVisibility) { this.tileLayer.visible = true; } var restore = this.tileLayer.level0Nodes.map(function (n) { return _RenderMode["default"].push(n, _RenderMode["default"].MODES.DEPTH); }); var buffer = g.renderViewToBuffer({ camera: this.camera, scene: this.tileLayer.object3d }, { x: x, y: y, width: width, height: height }); restore.forEach(function (r) { return r(); }); if (this.tileLayer.wireframe !== currentWireframe) { this.tileLayer.wireframe = currentWireframe; } if (this.tileLayer.opacity !== currentOpacity) { this.tileLayer.opacity = currentOpacity; } if (this.tileLayer.visible !== currentVisibility) { this.tileLayer.visible = currentVisibility; } return buffer; } /** * Returns the world position (view's crs: referenceCrs) under view coordinates. * This position is computed with depth buffer. * * @param {THREE.Vector2} mouse position in view coordinates (in pixel), if it's null so it's view's center. * @param {THREE.Vector3} [target=THREE.Vector3()] target. the result will be copied into this Vector3. If not present a new one will be created. * @return {THREE.Vector3} the world position in view's crs: referenceCrs. */ }, { key: "getPickingPositionFromDepth", value: function getPickingPositionFromDepth(mouse) { var target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new THREE.Vector3(); if (!this.tileLayer || this.tileLayer.level0Nodes.length == 0 || !this.tileLayer.level0Nodes[0]) { target = undefined; return; } var l = this.mainLoop; var viewPaused = l.scheduler.commandsWaitingExecutionCount() == 0 && l.renderingState == _MainLoop.RENDERING_PAUSED; var g = l.gfxEngine; var dim = g.getWindowSize(); var camera = this.camera.camera3D; mouse = mouse || dim.clone().multiplyScalar(0.5); mouse.x = Math.floor(mouse.x); mouse.y = Math.floor(mouse.y); // Prepare state var prev = camera.layers.mask; camera.layers.mask = 1 << this.tileLayer.threejsLayer; // Render/Read to buffer var buffer; if (viewPaused) { this._fullSizeDepthBuffer = this._fullSizeDepthBuffer || this.readDepthBuffer(0, 0, dim.x, dim.y); var id = ((dim.y - mouse.y - 1) * dim.x + mouse.x) * 4; buffer = this._fullSizeDepthBuffer.slice(id, id + 4); } else { buffer = this.readDepthBuffer(mouse.x, mouse.y, 1, 1); } screen.x = mouse.x / dim.x * 2 - 1; screen.y = -(mouse.y / dim.y) * 2 + 1; // Origin ray.origin.copy(camera.position); // Direction ray.direction.set(screen.x, screen.y, 0.5); // Unproject matrix.multiplyMatrices(camera.matrixWorld, matrix.getInverse(camera.projectionMatrix)); ray.direction.applyMatrix4(matrix); ray.direction.sub(ray.origin); direction.set(0, 0, 1.0); direction.applyMatrix4(matrix); direction.sub(ray.origin); var angle = direction.angleTo(ray.direction); var orthoZ = g.depthBufferRGBAValueToOrthoZ(buffer, camera); var length = orthoZ / Math.cos(angle); target.addVectors(camera.position, ray.direction.setLength(length)); camera.layers.mask = prev; if (target.length() > 10000000) { return undefined; } return target; } }]); return View; }(THREE.EventDispatcher); var _default = View; exports["default"] = _default;