import {unByKey as ol_Observable_unByKey} from 'ol/Observable.js' import ol_control_Control from 'ol/control/Control.js' import ol_ext_element from '../util/element.js' import ol_ext_getMapCanvas from '../util/getMapCanvas.js' /** Image line control * * @constructor * @extends {ol.control.Control} * @fires select * @fires collapse * @param {Object=} options Control options. * @param {String} options.className class of the control * @param {Array|ol.source.Vector} options.source vector sources that contains the images * @param {Array} options.layers A list of layers to display images. If no source and no layers, all visible layers will be considered. * @param {function} options.getImage a function that gets a feature and return the image url or false if no image to Show, default return the img propertie * @param {function} options.getTitle a function that gets a feature and return the title, default return an empty string * @param {boolean} options.collapsed the line is collapse, default false * @param {boolean} options.collapsible the line is collapsible, default false * @param {number} options.maxFeatures the maximum image element in the line, default 100 * @param {number} options.useExtent only show feature in the current extent * @param {boolean} options.hover select image on hover, default false * @param {string|boolean} options.linkColor link color or false if no link, default false */ var ol_control_Imageline = class olcontrolImageline extends ol_control_Control { constructor(options) { var element = ol_ext_element.create('DIV', { className: (options.className || '') + ' ol-imageline' + (options.target ? '' : ' ol-unselectable ol-control') + (options.collapsed && options.collapsible ? 'ol-collapsed' : '') }); // Initialize super({ element: element, target: options.target }); if (!options.target && options.collapsible) { ol_ext_element.create('BUTTON', { type: 'button', click: function () { this.toggle(); }.bind(this), parent: element }); } // Source if (options.source) this._sources = options.source.forEach ? options.source : [options.source]; if (options.layers) { this.setLayers(options.layers); } // Scroll imageline this._setScrolling(); this._scrolldiv.addEventListener("scroll", function () { if (this.getMap()) this.getMap().render(); }.bind(this)); // Parameters if (typeof (options.getImage) === 'function') this._getImage = options.getImage; if (typeof (options.getTitle) === 'function') this._getTitle = options.getTitle; this.set('maxFeatures', options.maxFeatures || 100); this.set('linkColor', options.linkColor || false); this.set('hover', options.hover || false); this.set('useExtent', options.useExtent || false); this.refresh(); } /** * Remove the control from its current map and attach it to the new map. * @param {ol.Map} map Map. * @api stable */ setMap(map) { if (this._listener) { this._listener.forEach(function (l) { ol_Observable_unByKey(l); }.bind(this)); } this._listener = null; super.setMap(map); if (map) { this._listener = [ map.on('postcompose', this._drawLink.bind(this)), map.on('moveend', function () { if (this.get('useExtent')) this.refresh(); }.bind(this)) ]; this.refresh(); } } /** Set layers to use in the control * @param {Array} layers */ setLayers(layers) { this._sources = this._getSources(layers); } /** Get source from a set of layers * @param {Array} layers * @returns {Array} * @private */ _getSources(layers) { var sources = []; layers.forEach(function (l) { if (l.getVisible()) { if (l.getSource() && l.getSource().getFeatures) sources.push(l.getSource()); else if (l.getLayers) this._getSources(l.getLayers()); } }.bind(this)); return sources; } /** Set useExtent param and refresh the line * @param {boolean} b */ useExtent(b) { this.set('useExtent', b); this.refresh(); } /** Is the line collapsed * @return {boolean} */ isCollapsed() { return this.element.classList.contains('ol-collapsed'); } /** Collapse the line * @param {boolean} b */ collapse(b) { if (b) this.element.classList.add('ol-collapsed'); else this.element.classList.remove('ol-collapsed'); if (this.getMap()) { setTimeout(function () { this.getMap().render(); }.bind(this), this.isCollapsed() ? 0 : 250); } this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() }); } /** Collapse the line */ toggle() { this.element.classList.toggle('ol-collapsed'); if (this.getMap()) { setTimeout(function () { this.getMap().render(); }.bind(this), this.isCollapsed() ? 0 : 250); } this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() }); } /** Default function to get an image of a feature * @param {ol.Feature} f * @private */ _getImage(f) { return f.get('img'); } /** Default function to get an image title * @param {ol.Feature} f * @private */ _getTitle( /* f */) { return ''; } /** * Get features * @return {Array} */ getFeatures() { var map = this.getMap(); if (!map) return []; var features = []; var sources = this._sources || this._getSources(map.getLayers()); sources.forEach(function (s) { if (features.length < this.get('maxFeatures')) { if (!this.get('useExtent') || !map) { features.push(s.getFeatures()); } else { var extent = map.getView().calculateExtent(map.getSize()); features.push(s.getFeaturesInExtent(extent)); } } }.bind(this)); return features; } /** Set element scrolling with a acceleration effect on desktop * (on mobile it uses the scroll of the browser) * @private */ _setScrolling() { var elt = this._scrolldiv = ol_ext_element.create('DIV', { parent: this.element }); ol_ext_element.scrollDiv(elt, { // Prevent selection when moving onmove: function (b) { this._moving = b; }.bind(this) }); elt.addEventListener('scroll', this._updateScrollBounds.bind(this)); this._updateScrollBounds(); } /** Set element scrolling with a acceleration effect on desktop * (on mobile it uses the scroll of the browser) * @private */ _updateScrollBounds() { var elt = this._scrolldiv; if (elt.scrollLeft < 5) { this.element.classList.add('ol-scroll0'); } else { this.element.classList.remove('ol-scroll0'); } if (elt.scrollWidth - elt.scrollLeft - elt.offsetWidth < 5) { this.element.classList.add('ol-scroll1'); } else { this.element.classList.remove('ol-scroll1'); } } /** * Refresh the imageline with new data */ refresh() { this._scrolldiv.innerHTML = ''; var allFeatures = this.getFeatures(); var current = this._select ? this._select.feature : null; if (this._select) this._select.elt = null; this._iline = []; if (this.getMap()) this.getMap().render(); // Add a new image var addImage = function (f) { if (this._getImage(f)) { var img = ol_ext_element.create('DIV', { className: 'ol-image', parent: this._scrolldiv }); ol_ext_element.create('IMG', { src: this._getImage(f), parent: img }).addEventListener('load', function () { this.classList.add('ol-loaded'); }); ol_ext_element.create('SPAN', { html: this._getTitle(f), parent: img }); // Current image var sel = { elt: img, feature: f }; // On click > dispatch event img.addEventListener('click', function () { if (!this._moving) { this._scrolldiv.scrollLeft = img.offsetLeft + ol_ext_element.getStyle(img, 'width') / 2 - ol_ext_element.getStyle(this.element, 'width') / 2; if (this._select) this._select.elt.classList.remove('select'); this._select = sel; if (this._select) this._select.elt.classList.add('select'); this.dispatchEvent({ type: 'select', feature: f }); } }.bind(this)); // Show link img.addEventListener('mouseover', function (e) { if (this.get('hover')) { if (this._select) this._select.elt.classList.remove('select'); this._select = sel; this._select.elt.classList.add('select'); this.getMap().render(); e.stopPropagation(); } }.bind(this)); // Remove link img.addEventListener('mouseout', function (e) { if (this.get('hover')) { if (this._select) this._select.elt.classList.remove('select'); this._select = false; this.getMap().render(); e.stopPropagation(); } }.bind(this)); // Prevent image dragging img.ondragstart = function () { return false; }; // Add image this._iline.push(sel); if (current === f) { this._select = sel; sel.elt.classList.add('select'); } } }.bind(this); // Add images var nb = this.get('maxFeatures'); allFeatures.forEach(function (features) { for (var i = 0, f; f = features[i]; i++) { if (nb-- < 0) break; addImage(f); } }.bind(this)); // Add the selected one if (this._select && this._select.feature && !this._select.elt) { addImage(this._select.feature); } this._updateScrollBounds(); } /** Center image line on a feature * @param {ol.feature} feature * @param {boolean} scroll scroll the line to center on the image, default true * @api */ select(feature, scroll) { this._select = false; // Find the image this._iline.forEach(function (f) { if (f.feature === feature) { f.elt.classList.add('select'); this._select = f; if (scroll !== false) { this._scrolldiv.scrollLeft = f.elt.offsetLeft + ol_ext_element.getStyle(f.elt, 'width') / 2 - ol_ext_element.getStyle(this.element, 'width') / 2; } } else { f.elt.classList.remove('select'); } }.bind(this)); } /** Draw link on the map * @private */ _drawLink(e) { if (!this.get('linkColor') | this.isCollapsed()) return; var map = this.getMap(); if (map && this._select && this._select.elt) { var ctx = e.context || ol_ext_getMapCanvas(this.getMap()).getContext('2d'); var ratio = e.frameState.pixelRatio; var pt = [ this._select.elt.offsetLeft - this._scrolldiv.scrollLeft + ol_ext_element.getStyle(this._select.elt, 'width') / 2, parseFloat(ol_ext_element.getStyle(this.element, 'top')) || this.getMap().getSize()[1] ]; var geom = this._select.feature.getGeometry().getFirstCoordinate(); geom = this.getMap().getPixelFromCoordinate(geom); ctx.save(); ctx.fillStyle = this.get('linkColor'); ctx.beginPath(); if (geom[0] > pt[0]) { ctx.moveTo((pt[0] - 5) * ratio, pt[1] * ratio); ctx.lineTo((pt[0] + 5) * ratio, (pt[1] + 5) * ratio); } else { ctx.moveTo((pt[0] - 5) * ratio, (pt[1] + 5) * ratio); ctx.lineTo((pt[0] + 5) * ratio, pt[1] * ratio); } ctx.lineTo(geom[0] * ratio, geom[1] * ratio); ctx.fill(); ctx.restore(); } } } export default ol_control_Imageline