UNPKG

@geoext/geoext

Version:

GeoExt is Open Source and enables building desktop-like GIS applications through the web. It is a JavaScript framework that combines the GIS functionality of OpenLayers with the user interface savvy of the ExtJS library provided by Sencha.

613 lines (558 loc) 16.3 kB
/* Copyright (c) 2015-present The Open Source Geospatial Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * A component that renders an `ol.Map` and that can be used in any ExtJS * layout. * * An example: A map component rendered inside of a panel: * * @example preview * var mapComponent = Ext.create('GeoExt.component.Map', { * map: new ol.Map({ * layers: [ * new ol.layer.Tile({ * source: new ol.source.OSM() * }) * ], * view: new ol.View({ * center: ol.proj.fromLonLat([-8.751278, 40.611368]), * zoom: 12 * }) * }) * }); * var mapPanel = Ext.create('Ext.panel.Panel', { * title: 'GeoExt.component.Map Example', * height: 200, * items: [mapComponent], * renderTo: Ext.getBody() * }); * * @class GeoExt.component.Map */ Ext.define('GeoExt.component.Map', { extend: 'Ext.Component', alias: ['widget.gx_map', 'widget.gx_component_map'], requires: ['GeoExt.data.store.Layers', 'GeoExt.util.Version'], mixins: ['GeoExt.mixin.SymbolCheck'], // <debug> symbols: [ 'ol.layer.Base', 'ol.Map', 'ol.Map#addLayer', 'ol.Map#getLayers', 'ol.Map#getSize', 'ol.Map#getView', 'ol.Map#removeLayer', 'ol.Map#setTarget', 'ol.Map#setView', 'ol.Map#updateSize', 'ol.View', 'ol.View#calculateExtent', 'ol.View#fit', 'ol.View#getCenter', 'ol.View#setCenter', ], // </debug> /** * @event pointerrest * * Fires if the user has left the pointer for an amount * of #pointerRestInterval milliseconds at the *same location*. Use the * configuration #pointerRestPixelTolerance to configure how long a pixel is * considered to be on the *same location*. * * Please note that this event will only fire if the map has #pointerRest * configured with `true`. * * @param {ol.MapBrowserEvent} olEvt The original and most recent * MapBrowserEvent event. * @param {ol.Pixel} lastPixel The originally captured pixel, which defined * the center of the tolerance bounds (itself configurable with the the * configuration #pointerRestPixelTolerance). If this is null, a * completely *new* pointerrest event just happened. */ /** * @event pointerrestout * * Fires if the user first was resting his pointer on the map element, but * then moved the pointer out of the map completely. * * Please note that this event will only fire if the map has #pointerRest * configured with `true`. * * @param {ol.MapBrowserEvent} olEvt The MapBrowserEvent event. */ /** * @event aftermapmove * * Triggered when the 'moveend' event of the underlying OpenLayers map is * fired. * * @param {GeoExt.component.Map} this * @param {ol.Map} olMap The OpenLayers map firing the original 'moveend' * event * @param {ol.MapEvent} olEvt The original OpenLayers event */ stateEvents: ['aftermapmove'], config: { /** * A configured map or a configuration object for the map constructor. * * @cfg {ol.Map} map */ map: null, /** * A boolean flag to control whether the map component will fire the * events #pointerrest and #pointerrestout. If this is set to `false` * (the default), no such events will be fired. * * @cfg {boolean} pointerRest Whether the component shall provide the * `pointerrest` and `pointerrestout` events. */ pointerRest: false, /** * The amount of milliseconds after which we will consider a rested * pointer as `pointerrest`. Only relevant if #pointerRest is `true`. * * @cfg {number} pointerRestInterval The interval in milliseconds. */ pointerRestInterval: 1000, /** * The amount of pixels that a pointer may move in both vertical and * horizontal direction, and still be considered to be a #pointerrest. * Only relevant if #pointerRest is `true`. * * @cfg {number} pointerRestPixelTolerance The tolerance in pixels. */ pointerRestPixelTolerance: 3, /** * List of css selectors for the element(s) on which neither * the pointerrest event, nor the pointerrestout event * should be fired. * * @cfg {Array<string>} ignorePointerRestSelectors The css selectors * on which no `pointerrest` and `pointerrestout` events * should be fired. */ ignorePointerRestSelectors: [], }, /** * Whether we already rendered an ol.Map in this component. Will be * updated in #onResize, after the first rendering happened. * * @property {boolean} mapRendered Map already rendered flag * @private */ mapRendered: false, /** * @property {GeoExt.data.store.Layers} layerStore The map's layer store * @private */ layerStore: null, /** * The location of the last mousemove which we track to be able to fire * the #pointerrest event. Only usable if #pointerRest is `true`. * * @property {ol.Pixel} lastPointerPixel Location of last mousemove * @private */ lastPointerPixel: null, /** * Whether the pointer is currently over the map component. Only usable if * the configuration #pointerRest is `true`. * * @property {boolean} isMouseOverMapEl Pointer is over map component flag * @private */ isMouseOverMapEl: null, /** * @inheritdoc */ constructor: function (config) { const me = this; me.callParent([config]); if (!(me.getMap() instanceof ol.Map)) { const olMap = new ol.Map({ view: new ol.View({ center: [0, 0], zoom: 2, }), }); me.setMap(olMap); } me.layerStore = Ext.create('GeoExt.data.store.Layers', { storeId: me.getId() + '-store', map: me.getMap(), }); me.bindStateOlEvents(); me.on('resize', me.onResize, me); }, /** * (Re-)render the map when size changes. */ onResize: function () { // Get the corresponding view of the controller (the mapComponent). const me = this; if (!me.mapRendered) { const el = me.getTargetEl ? me.getTargetEl() : me.element; me.getMap().setTarget(el.dom); me.mapRendered = true; } else { me.getMap().updateSize(); } }, /** * Will contain a buffered version of #unbufferedPointerMove, but only if * the configuration #pointerRest is true. * * @private */ bufferedPointerMove: Ext.emptyFn, /** * Bound as a eventlistener for pointermove on the OpenLayers map, but only * if the configuration #pointerRest is true. Will eventually fire the * special events #pointerrest or #pointerrestout. * * @param {ol.MapBrowserEvent} olEvt The MapBrowserEvent event. * @private */ unbufferedPointerMove: function (olEvt) { const me = this; const tolerance = me.getPointerRestPixelTolerance(); const pixel = olEvt.pixel; if (me.isMouseOverIgnoreEl(olEvt)) { return; } if (!me.isMouseOverMapEl) { me.fireEvent('pointerrestout', olEvt); return; } if (me.lastPointerPixel) { const deltaX = Math.abs(me.lastPointerPixel[0] - pixel[0]); const deltaY = Math.abs(me.lastPointerPixel[1] - pixel[1]); if (deltaX > tolerance || deltaY > tolerance) { me.lastPointerPixel = pixel; } else { // fire pointerrest, and include the original pointer pixel me.fireEvent('pointerrest', olEvt, me.lastPointerPixel); return; } } else { me.lastPointerPixel = pixel; } // a new pointerrest event, the second argument (the 'original' pointer // pixel) must be null, as we start from a totally new position me.fireEvent('pointerrest', olEvt, null); }, /** * Checks if the mouse is positioned over * an ignore element. * @return {boolean} Whether the mouse is positioned over an ignore element. */ isMouseOverIgnoreEl: function () { const me = this; const selectors = me.getIgnorePointerRestSelectors(); if (selectors === undefined || selectors.length === 0) { return false; } const hoverEls = Ext.query(':hover'); return hoverEls.some(function (el) { return selectors.some(function (sel) { return el.matches(sel); }); }); }, /** * Creates #bufferedPointerMove from #unbufferedPointerMove and binds it * to `pointermove` on the OpenLayers map. * * @private */ registerPointerRestEvents: function () { const me = this; const map = me.getMap(); if (me.bufferedPointerMove === Ext.emptyFn) { me.bufferedPointerMove = Ext.Function.createBuffered( me.unbufferedPointerMove, me.getPointerRestInterval(), me, ); } // Check if we have to fire any pointer* events map.on('pointermove', me.bufferedPointerMove); if (!me.rendered) { // make sure we do not fire any if the pointer left the component me.on('afterrender', me.bindOverOutListeners, me); } else { me.bindOverOutListeners(); } }, /** * Registers listeners that'll take care of setting #isMouseOverMapEl to * correct values. * * @private */ bindOverOutListeners: function () { const me = this; const mapEl = me.getTargetEl ? me.getTargetEl() : me.element; if (mapEl) { mapEl.on({ mouseover: me.onMouseOver, mouseout: me.onMouseOut, scope: me, }); } }, /** * Unregisters listeners that'll take care of setting #isMouseOverMapEl to * correct values. * * @private */ unbindOverOutListeners: function () { const me = this; const mapEl = me.getTargetEl ? me.getTargetEl() : me.element; if (mapEl) { mapEl.un({ mouseover: me.onMouseOver, mouseout: me.onMouseOut, scope: me, }); } }, /** * Sets isMouseOverMapEl to true, see #pointerRest. * * @private */ onMouseOver: function () { this.isMouseOverMapEl = true; }, /** * Sets isMouseOverMapEl to false, see #pointerRest. * * @private */ onMouseOut: function () { this.isMouseOverMapEl = false; }, /** * Unregisters the #bufferedPointerMove event listener and unbinds the * over- and out-listeners. */ unregisterPointerRestEvents: function () { const me = this; const map = me.getMap(); me.unbindOverOutListeners(); if (map) { map.un('pointermove', me.bufferedPointerMove); } me.bufferedPointerMove = Ext.emptyFn; }, /** * Whenever the value of #pointerRest is changed, this method will take * care of registering or unregistering internal event listeners. * * @param {boolean} val The new value that someone set for `pointerRest`. * @return {boolean} The passed new value for `pointerRest` unchanged. */ applyPointerRest: function (val) { if (val) { this.registerPointerRestEvents(); } else { this.unregisterPointerRestEvents(); } return val; }, /** * Whenever the value of #pointerRestInterval is changed, this method will * take to reinitialize the #bufferedPointerMove method and handlers to * actually trigger the event. * * @param {boolean} val The new value that someone set for * `pointerRestInterval`. * @return {boolean} The passed new value for `pointerRestInterval` * unchanged. */ applyPointerRestInterval: function (val) { const me = this; const isEnabled = me.getPointerRest(); if (isEnabled) { // Toggle to rebuild the buffered pointer move. me.setPointerRest(false); me.setPointerRest(isEnabled); } return val; }, /** * Returns the center coordinate of the view. * * @return {ol.Coordinate} The center of the map view as `ol.Coordinate`. */ getCenter: function () { return this.getMap().getView().getCenter(); }, /** * Set the center of the view. * * @param {ol.Coordinate} center The new center as `ol.Coordinate`. */ setCenter: function (center) { this.getMap().getView().setCenter(center); }, /** * Returns the extent of the current view. * * @return {ol.Extent} The extent of the map view as `ol.Extent`. */ getExtent: function () { return this.getView().calculateExtent(this.getMap().getSize()); }, /** * Set the extent of the view. * * @param {ol.Extent} extent The extent as `ol.Extent`. */ setExtent: function (extent) { // Check for backwards compatibility if (GeoExt.util.Version.isOl3()) { this.getView().fit(extent, this.getMap().getSize()); } else { this.getView().fit(extent); } }, /** * Returns the layers of the map. * * @return {ol.Collection} The layer collection. */ getLayers: function () { return this.getMap().getLayers(); }, /** * Add a layer to the map. * * @param {ol.layer.Base} layer The layer to add. */ addLayer: function (layer) { if (layer instanceof ol.layer.Base) { this.getMap().addLayer(layer); } else { Ext.Error.raise( 'Can not add layer ' + layer + ' as it is not ' + 'an instance of ol.layer.Base', ); } }, /** * Remove a layer from the map. * * @param {ol.layer.Base} layer The layer to remove. */ removeLayer: function (layer) { if (layer instanceof ol.layer.Base) { if (Ext.Array.contains(this.getLayers().getArray(), layer)) { this.getMap().removeLayer(layer); } } else { Ext.Error.raise( 'Can not remove layer ' + layer + ' as it is not ' + 'an instance of ol.layer.Base', ); } }, /** * Returns the `GeoExt.data.store.Layers`. * * @return {GeoExt.data.store.Layers} The layer store. */ getStore: function () { return this.layerStore; }, /** * Returns the view of the map. * * @return {ol.View} The `ol.View` of the map. */ getView: function () { return this.getMap().getView(); }, /** * Set the view of the map. * * @param {ol.View} view The `ol.View` to use for the map. */ setView: function (view) { this.getMap().setView(view); }, /** * Forwards the OpenLayers events so they become usable in the #statedEvents * array and a possible `GeoExt.state.PermalinkProvider` can change the * state when one of the events gets fired. */ bindStateOlEvents: function () { const me = this; const olMap = me.getMap(); olMap.on('moveend', function (evt) { me.fireEvent('aftermapmove', me, olMap, evt); }); }, /** * Returns the state of the map as keyed object. The following keys will be * available: * * * `center` * * `zoom` * * `rotation` * * @return {Object} The state object * @private */ getState: function () { const me = this; const view = me.getMap().getView(); return { zoom: view.getZoom(), center: view.getCenter(), rotation: view.getRotation(), }; }, /** * Apply the provided map state object. The following keys are interpreted: * * * `center` * * `zoom` * * `rotation` * * @param {Object} mapState The state object */ applyState: function (mapState) { // exit if no map state is provided if (!Ext.isObject(mapState)) { return; } const me = this; const view = me.getMap().getView(); view.setCenter(mapState.center); view.setZoom(mapState.zoom); view.setRotation(mapState.rotation); }, });