/* Copyright (c) 2019 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ import ol_Map from 'ol/Map.js' import ol_ext_element from '../util/element.js'; import ol_Overlay from 'ol/Overlay.js' import { inAndOut as ol_easing_inAndOut } from 'ol/easing.js' import ol_matrix3D from '../util/matrix3D.js' import { altKeyOnly as ol_events_condition_altKeyOnly } from 'ol/events/condition.js' /** A map with a perspective * @constructor * @extends {ol.Map} * @fires change:perspective * @param {olx.MapOptions=} options * @param {ol.events.condition} tiltCondition , default altKeyOnly */ var ol_PerspectiveMap = class olPerspectiveMap extends ol_Map { constructor(options) { // Map div var divMap = options.target instanceof Element ? options.target : document.getElementById(options.target); if (window.getComputedStyle(divMap).position !== 'absolute') { divMap.style.position = 'relative'; } divMap.style.overflow = 'hidden'; // Create map inside var map = ol_ext_element.create('DIV', { className: 'ol-perspective-map', parent: divMap }); var opts = {}; Object.assign(opts, options); opts.target = map; // enhance pixel ratio //opts.pixelRatio = 2; super(opts); this._tiltCondition = options.tiltCondition || ol_events_condition_altKeyOnly; } /** Get pixel ratio for the map */ getPixelRatio() { return window.devicePixelRatio; } /** Set perspective angle * @param {number} angle the perspective angle 0 (vertical) - 30 (max), default 0 * @param {*} options * @param {number} options.duration The duration of the animation in milliseconds, default 500 * @param {function} options.easing The easing function used during the animation, defaults to ol.easing.inAndOut). */ setPerspective(angle, options) { options = options || {}; // max angle if (angle > 30) angle = 30; else if (angle < 0) angle = 0; var fromAngle = this._angle || 0; var toAngle = Math.round(angle * 10) / 10; var style = this.getTarget().querySelector('.ol-layers').style; cancelAnimationFrame(this._animatedPerspective); requestAnimationFrame(function (t) { this._animatePerpective(t, t, style, fromAngle, toAngle, options.duration, options.easing || ol_easing_inAndOut); }.bind(this)); } /** Animate the perspective * @param {number} t0 starting timestamp * @param {number} t current timestamp * @param {CSSStyleDeclaration} style style to modify * @param {number} fromAngle starting angle * @param {number} toAngle ending angle * @param {number} duration The duration of the animation in milliseconds, default 500 * @param {function} easing The easing function used during the animation, defaults to ol.easing.inAndOut). * @private */ _animatePerpective(t0, t, style, fromAngle, toAngle, duration, easing) { var dt, end; if (duration === 0) { dt = 1; end = true; } else { dt = (t - t0) / (duration || 500); end = (dt >= 1); } dt = easing(dt); var angle; if (end) { angle = this._angle = toAngle; } else { angle = this._angle = fromAngle + (toAngle - fromAngle) * dt; } var fac = angle / 30; // apply transform to the style style.transform = 'translateY(-' + (17 * fac) + '%) perspective(200px) rotateX(' + angle + 'deg) scaleY(' + (1 - fac / 2) + ')'; this.getMatrix3D(true); this.render(); if (!end) { requestAnimationFrame(function (t) { this._animatePerpective(t0, t, style, fromAngle, toAngle, duration || 500, easing || ol_easing_inAndOut); }.bind(this)); } // Dispatch event this.dispatchEvent({ type: 'change:perspective', angle: angle, animating: !end }); } /** Convert to pixel coord according to the perspective * @param {MapBrowserEvent} mapBrowserEvent The event to handle. */ handleMapBrowserEvent(e) { e.pixel = [ e.originalEvent.offsetX / this.getPixelRatio(), e.originalEvent.offsetY / this.getPixelRatio() ]; e.coordinate = this.getCoordinateFromPixel(e.pixel); ol_Map.prototype.handleMapBrowserEvent.call(this, e); // Change perspective on tilt condition if (this._tiltCondition(e)) { switch (e.type) { case 'pointerdown': { this._dragging = e.originalEvent.offsetY; break; } case 'pointerup': { this._dragging = false; break; } case 'pointerdrag': { if (this._dragging !== false) { var angle = e.originalEvent.offsetY > this._dragging ? .5 : -.5; if (angle) { this.setPerspective((this._angle || 0) + angle, { duration: 0 }); } this._dragging = e.originalEvent.offsetY; } break; } } } else { this._dragging = false; } } /** Get map full teansform matrix3D * @return {Array>} */ getMatrix3D(compute) { if (compute) { var ele = this.getTarget().querySelector('.ol-layers'); // Get transform matrix3D from CSS var tx = ol_matrix3D.getTransform(ele); // Get the CSS transform origin from the transformed parent - default is '50% 50%' var txOrigin = ol_matrix3D.getTransformOrigin(ele); // Compute the full transform that is applied to the transformed parent (-origin * tx * origin) this._matrixTransform = ol_matrix3D.computeTransformMatrix(tx, txOrigin); } if (!this._matrixTransform) this._matrixTransform = ol_matrix3D.identity(); return this._matrixTransform; } /** Get pixel at screen from coordinate. * The default getPixelFromCoordinate get pixel in the perspective. * @param {ol.coordinate} coord * @param {ol.pixel} */ getPixelScreenFromCoordinate(coord) { // Get pixel in the transform system var px = this.getPixelFromCoordinate(coord); // Get transform matrix3D from CSS var fullTx = this.getMatrix3D(); // Transform the point using full transform var pixel = ol_matrix3D.transformVertex(fullTx, px); // Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component). pixel = ol_matrix3D.projectVertex(pixel); return [pixel[0], pixel[1]]; } /** Not working... * */ getPixelFromPixelScreen(px) { // Get transform matrix3D from CSS var fullTx = ol_matrix3D.inverse(this.getMatrix3D()); // Transform the point using full transform var pixel = ol_matrix3D.transformVertex(fullTx, px); // Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component). pixel = ol_matrix3D.projectVertex(pixel); return [pixel[0], pixel[1]]; } } /* HACK: Overwrited Overlay function to handle overlay positing in a perspective map */ ;(function() { var _updatePixelPosition = ol_Overlay.prototype.updatePixelPosition; /** Update pixel projection in a perspective map (apply projection to the position) * @private */ ol_Overlay.prototype.updatePixelPosition = function () { var map = this.getMap(); if (map && map instanceof ol_PerspectiveMap) { var position = this.getPosition(); if (!map || !map.isRendered() || !position) { this.setVisible(false); return; } // Get pixel at screen var pixel = map.getPixelScreenFromCoordinate(position); var mapSize = map.getSize(); pixel[0] -= mapSize[0]/4 pixel[1] -= mapSize[1]/4 /* for ol v6.2.x // Offset according positioning var pos = this.getPositioning(); if (/bottom/.test(pos)) { pixel[1] += mapSize[1]/4 } else { pixel[1] -= mapSize[1]/4 } if (/right/.test(pos)) { pixel[0] += mapSize[0]/4 } else { pixel[0] -= mapSize[0]/4 } */ // Update this.updateRenderedPosition(pixel , mapSize); } else { _updatePixelPosition.call(this); } }; /**/ })(); export default ol_PerspectiveMap