UNPKG

@mapbox/react-map-gl

Version:

A React wrapper for MapboxGL-js and overlay API.

605 lines (509 loc) 18.3 kB
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; import _createClass from "@babel/runtime/helpers/esm/createClass"; import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; // Copyright (c) 2015 Uber Technologies, Inc. // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import MapState from './map-state'; import { LinearInterpolator } from './transition'; import TransitionManager, { TRANSITION_EVENTS } from './transition-manager'; var NO_TRANSITION_PROPS = { transitionDuration: 0 }; export var LINEAR_TRANSITION_PROPS = { transitionDuration: 300, transitionEasing: function transitionEasing(t) { return t; }, transitionInterpolator: new LinearInterpolator(), transitionInterruption: TRANSITION_EVENTS.BREAK }; // EVENT HANDLING PARAMETERS var PITCH_MOUSE_THRESHOLD = 5; var PITCH_ACCEL = 1.2; var ZOOM_ACCEL = 0.01; var EVENT_TYPES = { WHEEL: ['wheel'], PAN: ['panstart', 'panmove', 'panend'], PINCH: ['pinchstart', 'pinchmove', 'pinchend'], DOUBLE_TAP: ['doubletap'], KEYBOARD: ['keydown'] }; /** * @classdesc * A class that handles events and updates mercator style viewport parameters */ var MapController = /*#__PURE__*/ function () { function MapController() { var _this = this; _classCallCheck(this, MapController); _defineProperty(this, "events", []); _defineProperty(this, "mapState", void 0); _defineProperty(this, "onViewportChange", void 0); _defineProperty(this, "onStateChange", void 0); _defineProperty(this, "mapStateProps", void 0); _defineProperty(this, "eventManager", void 0); _defineProperty(this, "scrollZoom", true); _defineProperty(this, "dragPan", true); _defineProperty(this, "dragRotate", true); _defineProperty(this, "doubleClickZoom", true); _defineProperty(this, "touchZoom", true); _defineProperty(this, "touchRotate", false); _defineProperty(this, "keyboard", true); _defineProperty(this, "_state", { isDragging: false }); _defineProperty(this, "_events", {}); _defineProperty(this, "_transitionManager", new TransitionManager()); _defineProperty(this, "setState", function (newState) { Object.assign(_this._state, newState); if (_this.onStateChange) { _this.onStateChange(_this._state); } }); this.handleEvent = this.handleEvent.bind(this); } /** * Callback for events * @param {hammer.Event} event */ _createClass(MapController, [{ key: "handleEvent", value: function handleEvent(event) { this.mapState = this.getMapState(); switch (event.type) { case 'panstart': return this._onPanStart(event); case 'panmove': return this._onPan(event); case 'panend': return this._onPanEnd(event); case 'pinchstart': return this._onPinchStart(event); case 'pinchmove': return this._onPinch(event); case 'pinchend': return this._onPinchEnd(event); case 'doubletap': return this._onDoubleTap(event); case 'wheel': return this._onWheel(event); case 'keydown': return this._onKeyDown(event); default: return false; } } /* Event utils */ // Event object: http://hammerjs.github.io/api/#event-object }, { key: "getCenter", value: function getCenter(event) { var _event$offsetCenter = event.offsetCenter, x = _event$offsetCenter.x, y = _event$offsetCenter.y; return [x, y]; } }, { key: "isFunctionKeyPressed", value: function isFunctionKeyPressed(event) { var srcEvent = event.srcEvent; return Boolean(srcEvent.metaKey || srcEvent.altKey || srcEvent.ctrlKey || srcEvent.shiftKey); } }, { key: "updateViewport", /* Callback util */ // formats map state and invokes callback function value: function updateViewport(newMapState) { var extraProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var extraState = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var oldViewport = this.mapState ? this.mapState.getViewportProps() : {}; var newViewport = Object.assign({}, newMapState.getViewportProps(), extraProps); var viewStateChanged = Object.keys(newViewport).some(function (key) { return oldViewport[key] !== newViewport[key]; }); // viewState has changed if (viewStateChanged) { this.onViewportChange(newViewport, extraState, oldViewport); } this.setState(Object.assign({}, newMapState.getInteractiveState(), extraState)); } }, { key: "getMapState", value: function getMapState(overrides) { return new MapState(Object.assign({}, this.mapStateProps, this._state, overrides)); } /** * Extract interactivity options */ }, { key: "setOptions", value: function setOptions(options) { var onViewportChange = options.onViewportChange, onStateChange = options.onStateChange, _options$eventManager = options.eventManager, eventManager = _options$eventManager === void 0 ? this.eventManager : _options$eventManager, _options$isInteractiv = options.isInteractive, isInteractive = _options$isInteractiv === void 0 ? true : _options$isInteractiv, _options$scrollZoom = options.scrollZoom, scrollZoom = _options$scrollZoom === void 0 ? this.scrollZoom : _options$scrollZoom, _options$dragPan = options.dragPan, dragPan = _options$dragPan === void 0 ? this.dragPan : _options$dragPan, _options$dragRotate = options.dragRotate, dragRotate = _options$dragRotate === void 0 ? this.dragRotate : _options$dragRotate, _options$doubleClickZ = options.doubleClickZoom, doubleClickZoom = _options$doubleClickZ === void 0 ? this.doubleClickZoom : _options$doubleClickZ, _options$touchZoom = options.touchZoom, touchZoom = _options$touchZoom === void 0 ? this.touchZoom : _options$touchZoom, _options$touchRotate = options.touchRotate, touchRotate = _options$touchRotate === void 0 ? this.touchRotate : _options$touchRotate, _options$keyboard = options.keyboard, keyboard = _options$keyboard === void 0 ? this.keyboard : _options$keyboard; this.onViewportChange = onViewportChange; this.onStateChange = onStateChange; if (!this.mapStateProps || this.mapStateProps.height !== options.height) { // Dimensions changed, normalize the props this.updateViewport(new MapState(options)); } this.mapStateProps = options; // Update transition this._transitionManager.processViewportChange(Object.assign({}, options, { onStateChange: this.setState })); if (this.eventManager !== eventManager) { // EventManager has changed this.eventManager = eventManager; this._events = {}; this.toggleEvents(this.events, true); } // Register/unregister events this.toggleEvents(EVENT_TYPES.WHEEL, isInteractive && scrollZoom); this.toggleEvents(EVENT_TYPES.PAN, isInteractive && (dragPan || dragRotate)); this.toggleEvents(EVENT_TYPES.PINCH, isInteractive && (touchZoom || touchRotate)); this.toggleEvents(EVENT_TYPES.DOUBLE_TAP, isInteractive && doubleClickZoom); this.toggleEvents(EVENT_TYPES.KEYBOARD, isInteractive && keyboard); // Interaction toggles this.scrollZoom = scrollZoom; this.dragPan = dragPan; this.dragRotate = dragRotate; this.doubleClickZoom = doubleClickZoom; this.touchZoom = touchZoom; this.touchRotate = touchRotate; this.keyboard = keyboard; } }, { key: "toggleEvents", value: function toggleEvents(eventNames, enabled) { var _this2 = this; if (this.eventManager) { eventNames.forEach(function (eventName) { if (_this2._events[eventName] !== enabled) { _this2._events[eventName] = enabled; if (enabled) { _this2.eventManager.on(eventName, _this2.handleEvent); } else { _this2.eventManager.off(eventName, _this2.handleEvent); } } }); } } /* Event handlers */ // Default handler for the `panstart` event. }, { key: "_onPanStart", value: function _onPanStart(event) { var pos = this.getCenter(event); var newMapState = this.mapState.panStart({ pos: pos }).rotateStart({ pos: pos }); this.updateViewport(newMapState, NO_TRANSITION_PROPS, { isDragging: true }); return true; } // Default handler for the `panmove` event. }, { key: "_onPan", value: function _onPan(event) { return this.isFunctionKeyPressed(event) || event.rightButton ? this._onPanRotate(event) : this._onPanMove(event); } // Default handler for the `panend` event. }, { key: "_onPanEnd", value: function _onPanEnd(event) { var newMapState = this.mapState.panEnd().rotateEnd(); this.updateViewport(newMapState, null, { isDragging: false, isPanning: false, isRotating: false }); return true; } // Default handler for panning to move. // Called by `_onPan` when panning without function key pressed. }, { key: "_onPanMove", value: function _onPanMove(event) { if (!this.dragPan) { return false; } var pos = this.getCenter(event); var newMapState = this.mapState.pan({ pos: pos }); this.updateViewport(newMapState, NO_TRANSITION_PROPS, { isPanning: true }); return true; } // Default handler for panning to rotate. // Called by `_onPan` when panning with function key pressed. }, { key: "_onPanRotate", value: function _onPanRotate(event) { if (!this.dragRotate) { return false; } var deltaX = event.deltaX, deltaY = event.deltaY; var _this$getCenter = this.getCenter(event), _this$getCenter2 = _slicedToArray(_this$getCenter, 2), centerY = _this$getCenter2[1]; var startY = centerY - deltaY; var _this$mapState$getVie = this.mapState.getViewportProps(), width = _this$mapState$getVie.width, height = _this$mapState$getVie.height; var deltaScaleX = deltaX / width; var deltaScaleY = 0; if (deltaY > 0) { if (Math.abs(height - startY) > PITCH_MOUSE_THRESHOLD) { // Move from 0 to -1 as we drag upwards deltaScaleY = deltaY / (startY - height) * PITCH_ACCEL; } } else if (deltaY < 0) { if (startY > PITCH_MOUSE_THRESHOLD) { // Move from 0 to 1 as we drag upwards deltaScaleY = 1 - centerY / startY; } } deltaScaleY = Math.min(1, Math.max(-1, deltaScaleY)); var newMapState = this.mapState.rotate({ deltaScaleX: deltaScaleX, deltaScaleY: deltaScaleY }); this.updateViewport(newMapState, NO_TRANSITION_PROPS, { isRotating: true }); return true; } // Default handler for the `wheel` event. }, { key: "_onWheel", value: function _onWheel(event) { if (!this.scrollZoom) { return false; } event.preventDefault(); var pos = this.getCenter(event); var delta = event.delta; // Map wheel delta to relative scale var scale = 2 / (1 + Math.exp(-Math.abs(delta * ZOOM_ACCEL))); if (delta < 0 && scale !== 0) { scale = 1 / scale; } var newMapState = this.mapState.zoom({ pos: pos, scale: scale }); this.updateViewport(newMapState, NO_TRANSITION_PROPS, { isZooming: true }); // This is a one-off event, state should not persist this.setState({ isZooming: false }); return true; } // Default handler for the `pinchstart` event. }, { key: "_onPinchStart", value: function _onPinchStart(event) { var pos = this.getCenter(event); var newMapState = this.mapState.zoomStart({ pos: pos }).rotateStart({ pos: pos }); // hack - hammer's `rotation` field doesn't seem to produce the correct angle this._state.startPinchRotation = event.rotation; this.updateViewport(newMapState, NO_TRANSITION_PROPS, { isDragging: true }); return true; } // Default handler for the `pinch` event. }, { key: "_onPinch", value: function _onPinch(event) { if (!this.touchZoom && !this.touchRotate) { return false; } var newMapState = this.mapState; if (this.touchZoom) { var scale = event.scale; var pos = this.getCenter(event); newMapState = newMapState.zoom({ pos: pos, scale: scale }); } if (this.touchRotate) { var rotation = event.rotation; var startPinchRotation = this._state.startPinchRotation; newMapState = newMapState.rotate({ deltaScaleX: -(rotation - startPinchRotation) / 180 }); } this.updateViewport(newMapState, NO_TRANSITION_PROPS, { isDragging: true, isPanning: this.touchZoom, isZooming: this.touchZoom, isRotating: this.touchRotate }); return true; } // Default handler for the `pinchend` event. }, { key: "_onPinchEnd", value: function _onPinchEnd(event) { var newMapState = this.mapState.zoomEnd().rotateEnd(); this._state.startPinchRotation = 0; this.updateViewport(newMapState, null, { isDragging: false, isPanning: false, isZooming: false, isRotating: false }); return true; } // Default handler for the `doubletap` event. }, { key: "_onDoubleTap", value: function _onDoubleTap(event) { if (!this.doubleClickZoom) { return false; } var pos = this.getCenter(event); var isZoomOut = this.isFunctionKeyPressed(event); var newMapState = this.mapState.zoom({ pos: pos, scale: isZoomOut ? 0.5 : 2 }); this.updateViewport(newMapState, Object.assign({}, LINEAR_TRANSITION_PROPS, { transitionInterpolator: new LinearInterpolator({ around: pos }) })); return true; } /* eslint-disable complexity */ // Default handler for the `keydown` event }, { key: "_onKeyDown", value: function _onKeyDown(event) { if (!this.keyboard) { return false; } var funcKey = this.isFunctionKeyPressed(event); var mapStateProps = this.mapStateProps; var newMapState; switch (event.srcEvent.keyCode) { case 189: // - if (funcKey) { newMapState = this.getMapState({ zoom: mapStateProps.zoom - 2 }); } else { newMapState = this.getMapState({ zoom: mapStateProps.zoom - 1 }); } break; case 187: // + if (funcKey) { newMapState = this.getMapState({ zoom: mapStateProps.zoom + 2 }); } else { newMapState = this.getMapState({ zoom: mapStateProps.zoom + 1 }); } break; case 37: // left if (funcKey) { newMapState = this.getMapState({ bearing: mapStateProps.bearing - 15 }); } else { newMapState = this.mapState.pan({ pos: [100, 0], startPos: [0, 0] }); } break; case 39: // right if (funcKey) { newMapState = this.getMapState({ bearing: mapStateProps.bearing + 15 }); } else { newMapState = this.mapState.pan({ pos: [-100, 0], startPos: [0, 0] }); } break; case 38: // up if (funcKey) { newMapState = this.getMapState({ pitch: mapStateProps.pitch + 10 }); } else { newMapState = this.mapState.pan({ pos: [0, 100], startPos: [0, 0] }); } break; case 40: // down if (funcKey) { newMapState = this.getMapState({ pitch: mapStateProps.pitch - 10 }); } else { newMapState = this.mapState.pan({ pos: [0, -100], startPos: [0, 0] }); } break; default: return false; } return this.updateViewport(newMapState, LINEAR_TRANSITION_PROPS); } /* eslint-enable complexity */ }]); return MapController; }(); export { MapController as default }; //# sourceMappingURL=map-controller.js.map