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