/**
* ol-ext - A set of cool extensions for OpenLayers (ol) in node modules structure
* @description ol3,openlayers,popup,menu,symbol,renderer,filter,canvas,interaction,split,statistic,charts,pie,LayerSwitcher,toolbar,animation
* @version v4.0.9
* @author Jean-Marc Viglino
* @see https://github.com/Viglino/ol-ext#,
* @license BSD-3-Clause
*/
/*global ol*/
if (window.ol) {
/** @namespace ol.ext */
if (!ol.ext) ol.ext = {};
/** @namespace ol.legend */
if (!ol.legend) ol.legend = {};
/** @namespace ol.particule */
if (!ol.particule) ol.particule = {};
/** @namespace ol.ext.imageLoader */
if (!ol.ext.imageLoader) ol.ext.imageLoader = {};
/** @namespace ol.ext.input */
if (!ol.ext.input) ol.ext.input = {};
/* Version */
if (!ol.util) {
ol.util = {
VERSION: ol.VERSION || '5.3.0'
};
} else if (!ol.util.VERSION) {
ol.util.VERSION = ol.VERSION || '6.1.0'
}
}
/** Inherit the prototype methods from one constructor into another.
* replace deprecated ol method
*
* @param {!Function} childCtor Child constructor.
* @param {!Function} parentCtor Parent constructor.
* @function module:ol.inherits
* @api
*/
ol.ext.inherits = function(child,parent) {
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
};
// Compatibilty with ol > 5 to be removed when v6 is out
if (window.ol) {
if (!ol.inherits) ol.inherits = ol.ext.inherits;
}
/* IE Polyfill */
// NodeList.forEach
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
// Element.remove
if (window.Element && !Element.prototype.remove) {
Element.prototype.remove = function() {
if (this.parentNode) this.parentNode.removeChild(this);
}
}
/* End Polyfill */
/** Ajax request
* @fires success
* @fires error
* @param {*} options
* @param {string} options.auth Authorisation as btoa("username:password");
* @param {string} options.dataType The type of data that you're expecting back from the server, default JSON
*/
ol.ext.Ajax = class olextAjax extends ol.Object {
constructor(options) {
options = options || {};
super();
this._auth = options.auth;
this.set('dataType', options.dataType || 'JSON');
}
/** Helper for get
* @param {*} options
* @param {string} options.url
* @param {string} options.auth Authorisation as btoa("username:password");
* @param {string} options.dataType The type of data that you're expecting back from the server, default JSON
* @param {string} options.success
* @param {string} options.error
* @param {*} options.options get options
*/
static get(options) {
var ajax = new ol.ext.Ajax(options);
if (options.success)
ajax.on('success', function (e) { options.success(e.response, e); });
if (options.error)
ajax.on('error', function (e) { options.error(e); });
ajax.send(options.url, options.data, options.options);
}
/** Helper to get cors header
* @param {string} url
* @param {string} callback
*/
static getCORS(url, callback) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.send();
request.onreadystatechange = function () {
if (this.readyState == this.HEADERS_RECEIVED) {
callback(request.getResponseHeader('Access-Control-Allow-Origin'));
}
};
}
/** Send an ajax request (GET)
* @fires success
* @fires error
* @param {string} url
* @param {*} data Data to send to the server as key / value
* @param {*} options a set of options that are returned in the
* @param {boolean} options.abort false to prevent aborting the current request, default true
*/
send(url, data, options) {
options = options || {};
var self = this;
// Url
var encode = (options.encode !== false);
if (encode)
url = encodeURI(url);
// Parameters
var parameters = '';
for (var index in data) {
if (data.hasOwnProperty(index) && data[index] !== undefined) {
parameters += (parameters ? '&' : '?') + index + '=' + (encode ? encodeURIComponent(data[index]) : data[index]);
}
}
// Abort previous request
if (this._request && options.abort !== false) {
this._request.abort();
}
// New request
var ajax = this._request = new XMLHttpRequest();
ajax.open('GET', url + parameters, true);
if (options.timeout)
ajax.timeout = options.timeout;
if (this._auth) {
ajax.setRequestHeader("Authorization", "Basic " + this._auth);
}
// Load complete
this.dispatchEvent({ type: 'loadstart' });
ajax.onload = function () {
self._request = null;
self.dispatchEvent({ type: 'loadend' });
if (this.status >= 200 && this.status < 400) {
var response;
// Decode response
try {
switch (self.get('dataType')) {
case 'JSON': {
response = JSON.parse(this.response);
break;
}
default: {
response = this.response;
}
}
} catch (e) {
// Error
self.dispatchEvent({
type: 'error',
status: 0,
statusText: 'parsererror',
error: e,
options: options,
jqXHR: this
});
return;
}
// Success
//console.log('response',response)
self.dispatchEvent({
type: 'success',
response: response,
status: this.status,
statusText: this.statusText,
options: options,
jqXHR: this
});
} else {
self.dispatchEvent({
type: 'error',
status: this.status,
statusText: this.statusText,
options: options,
jqXHR: this
});
}
};
// Oops
ajax.ontimeout = function () {
self._request = null;
self.dispatchEvent({ type: 'loadend' });
self.dispatchEvent({
type: 'error',
status: this.status,
statusText: 'Timeout',
options: options,
jqXHR: this
});
};
ajax.onerror = function () {
self._request = null;
self.dispatchEvent({ type: 'loadend' });
self.dispatchEvent({
type: 'error',
status: this.status,
statusText: this.statusText,
options: options,
jqXHR: this
});
};
// GO!
ajax.send();
}
}
/** SVG filter
* @param {*} options
* @param {ol.ext.SVGOperation} option.operation
* @param {string} option.id filter id, only to use if you want to adress the filter directly or var the lib create one, if none create a unique id
* @param {string} option.color color interpolation filters, linear or sRGB
*/
ol.ext.SVGFilter = class olextSVGFilter extends ol.Object {
constructor(options) {
options = options || {};
super();
if (!ol.ext.SVGFilter.prototype.svg) {
ol.ext.SVGFilter.prototype.svg = document.createElementNS(this.NS, 'svg');
ol.ext.SVGFilter.prototype.svg.setAttribute('version', '1.1');
ol.ext.SVGFilter.prototype.svg.setAttribute('width', 0);
ol.ext.SVGFilter.prototype.svg.setAttribute('height', 0);
ol.ext.SVGFilter.prototype.svg.style.position = 'absolute';
/* Firefox doesn't process hidden svg
ol.ext.SVGFilter.prototype.svg.style.display = 'none';
*/
document.body.appendChild(ol.ext.SVGFilter.prototype.svg);
}
this.element = document.createElementNS(this.NS, 'filter');
this._id = options.id || '_ol_SVGFilter_' + (ol.ext.SVGFilter.prototype._id++);
this.element.setAttribute('id', this._id);
if (options.color)
this.element.setAttribute('color-interpolation-filters', options.color);
if (options.operation)
this.addOperation(options.operation);
ol.ext.SVGFilter.prototype.svg.appendChild(this.element);
}
/** Get filter ID
* @return {string}
*/
getId() {
return this._id;
}
/** Remove from DOM
*/
remove() {
this.element.remove();
}
/** Add a new operation
* @param {ol.ext.SVGOperation} operation
*/
addOperation(operation) {
if (operation instanceof Array) {
operation.forEach(function (o) { this.addOperation(o); }.bind(this));
} else {
if (!(operation instanceof ol.ext.SVGOperation))
operation = new ol.ext.SVGOperation(operation);
this.element.appendChild(operation.geElement());
}
}
/** Add a grayscale operation
* @param {number} value
*/
grayscale(value) {
this.addOperation({
feoperation: 'feColorMatrix',
type: 'saturate',
values: value || 0
});
}
/** Add a luminanceToAlpha operation
* @param {*} options
* @param {number} options.gamma enhance gamma, default 0
*/
luminanceToAlpha(options) {
options = options || {};
this.addOperation({
feoperation: 'feColorMatrix',
type: 'luminanceToAlpha'
});
if (options.gamma) {
this.addOperation({
feoperation: 'feComponentTransfer',
operations: [{
feoperation: 'feFuncA',
type: 'gamma',
amplitude: options.gamma,
exponent: 1,
offset: 0
}]
});
}
}
applyTo(img) {
var canvas = document.createElement('CANVAS');
canvas.width = img.naturalWidth || img.width;
canvas.height = img.naturalHeight || img.height;
canvas.getContext('2d').filter = 'url(#' + this.getId() + ')';
canvas.getContext('2d').drawImage(img, 0, 0);
return canvas;
}
}
ol.ext.SVGFilter.prototype.NS = 'http://www.w3.org/2000/svg';
ol.ext.SVGFilter.prototype.svg = null;
ol.ext.SVGFilter.prototype._id = 0;
/**
* @typedef {Object} svgOperation
* @property {string} attributes.feoperation filter primitive tag name
* @property {Array
} attributes.operations a list of operations
*/
/** SVG filter
* @param {string | svgOperation} attributes the fe operation or a list of operations
*/
ol.ext.SVGOperation = class olextSVGOperation extends ol.Object {
constructor(attributes) {
if (typeof (attributes) === 'string') attributes = { feoperation: attributes };
super();
if (!attributes || !attributes.feoperation) {
console.error('[SVGOperation]: no operation defined.');
return;
}
this._name = attributes.feoperation;
this.element = document.createElementNS(ol.ext.SVGOperation.NS || 'http://www.w3.org/2000/svg', this._name);
this.setProperties(attributes);
if (attributes.operations instanceof Array) {
this.appendChild(attributes.operations);
}
}
/** Get filter name
* @return {string}
*/
getName() {
return this._name;
}
/** Set Filter attribute
* @param {*} attributes
*/
set(k, val) {
if (!/^feoperation$|^operations$/.test(k)) {
super.set(k, val);
this.element.setAttribute(k, val);
}
}
/** Set Filter attributes
* @param {*} attributes
*/
setProperties(attributes) {
attributes = attributes || {};
for (var k in attributes) {
this.set(k, attributes[k]);
}
}
/** Get SVG element
* @return {Element}
*/
geElement() {
return this.element;
}
/** Append a new operation
* @param {ol.ext.SVGOperation} operation
*/
appendChild(operation) {
if (operation instanceof Array) {
operation.forEach(function (o) { this.appendChild(o); }.bind(this));
} else {
if (!(operation instanceof ol.ext.SVGOperation)) {
operation = new ol.ext.SVGOperation(operation);
}
this.element.appendChild(operation.geElement());
}
}
}
/** Text file reader (chunk by chunk, line by line).
* Large files are read in chunks and returned line by line
* to handle read progress and prevent memory leaks.
* @param {Object} options
* @param {File} [options.file]
* @param {number} [options.chunkSize=1E6]
*/
ol.ext.TextStreamReader = function(options) {
options = options || {};
this.setChunkSize(options.chunkSize);
this.setFile(options.file);
this.reader_ = new FileReader();
};
/** Set file to read
* @param {File} file
*/
ol.ext.TextStreamReader.prototype.setFile = function(file) {
this.file_ = file;
this.fileSize_ = (this.file_.size - 1);
this.rewind();
}
/** Sets the file position indicator to the beginning of the file stream.
*/
ol.ext.TextStreamReader.prototype.rewind = function() {
this.chunk_ = 0;
this.residue_ = '';
};
/** Set reader chunk size
* @param {number} [chunkSize=1E6]
*/
ol.ext.TextStreamReader.prototype.setChunkSize = function(s) {
this.chunkSize_ = s || 1E6;
};
/** Get progress
* @return {number} progress [0,1]
*/
ol.ext.TextStreamReader.prototype.getProgress = function() {
return this.chunk_ / this.fileSize_;
};
/** Read a text file line by line from the start
* @param {function} getLine a function that gets the current line as argument. Return false to stop reading
* @param {function} [progress] a function that gets the progress on each chunk (beetween 0,1) and a boolean set to true on end
*/
ol.ext.TextStreamReader.prototype.readLines = function(getLine, progress) {
this.rewind();
this.readChunk(function(lines) {
// getLine by line
for (var i=0; i.
* @param {function} [progress] a function that gets the progress (beetween 0,1) and a boolean set to true on end of file
*/
ol.ext.TextStreamReader.prototype.readChunk = function(getLines) {
// Parse chunk line by line
this.reader_.onload = function(e) {
// Get lines
var lines = e.target.result.replace(/\r/g,'').split('\n')
lines[0] = this.residue_ + lines[0] || '';
// next
this.chunk_ += this.chunkSize_;
// more to read?
if (this.chunk_ < this.fileSize_) {
this.residue_ = lines.pop();
} else {
this.residue_ = '';
}
// Get lines
getLines(lines);
}.bind(this)
// Read next chunk
this.nexChunk_();
};
/** Read next chunk
* @private
*/
ol.ext.TextStreamReader.prototype.nexChunk_ = function() {
if (this.chunk_ < this.fileSize_) {
var blob = this.file_.slice(this.chunk_, this.chunk_ + this.chunkSize_);
this.reader_.readAsText(blob);
return true;
}
return false;
};
// Prevent overwrite
if (ol.View.prototype.flyTo) {
console.warn('[OL-EXT] ol/View~View.flyTo redefinition')
}
/** Destination
* @typedef {Object} viewTourDestinations
* @property {string} [type=flyto] animation type (flyTo, moveTo), default flyTo
* @property {number} [duration=2000] animation duration
* @property {ol.coordinate} [center=] destination coordinate, default current center
* @property {number} [zoom] destination zoom, default current zoom
* @property {number} [zoomAt=-2] zoom to fly to, default min (current zoom, zoom) -2
* @property {function} [easing] easing function used during the animation, defaults ol/easing~inAndOut
* @property {number} [rotation] The rotation of the view at the end of the animation
* @property {anchor} [anchor] Optional anchor to remain fixed during a rotation or resolution animation.
*/
/** FlyTo animation
* @param {viewTourDestinations} options
* @param {function} done callback function called at the end of an animation, called with true if the animation completed
*/
ol.View.prototype.flyTo = function(options, done) {
options = options || {};
// Start new anim
this.cancelAnimations();
var callback = (typeof(done) === 'function' ? done : function(){});
// Fly to destination
var duration = options.duration || 2000;
var zoomAt = options.zoomAt || (Math.min(options.zoom||100, this.getZoom())-2);
var zoomTo = options.zoom || this.getZoom();
var coord = options.center || this.getCenter();
// Move to
this.animate ({
center: coord,
duration: duration,
easing: options.easing,
anchor: options.anchor,
rotation: options.rotation
});
// Zoom to
this.animate ({
zoom: zoomAt,
duration: duration/2,
easing: options.easing,
anchor: options.anchor
},{
zoom: zoomTo,
duration: duration/2,
easing: options.easing,
anchor: options.anchor
},
callback);
};
/** Start a tour on the map
* @param {Array|Array} destinations an array of destinations or an array of [x,y,zoom,destinationType]
* @param {Object} options
* @param {number} [options.delay=750] delay between 2 destination
* @param {string} [options.type] animation type (flyTo, moveTo) to use if not defined in destinations
* @param {function} [options.easing] easing function used during the animation if not defined in destinations
* @param {function} [options.done] callback function called at the end of an animation, called with true if the tour completed
* @param {function} [options.step] callback function called when a destination is reached with the step index as param
*/
ol.View.prototype.takeTour = function(destinations, options) {
options = options || {};
var index = -1;
var next = function(more) {
if (more) {
var dest = destinations[++index];
if (typeof(options.step) === 'function') options.step(index, destinations);
if (dest) {
if (dest instanceof Array) dest = { center: [dest[0],dest[1]], zoom: dest[2], type: dest[3] };
var delay = index === 0 ? 0 : (options.delay || 750);
if (!dest.easing) dest.easing = options.easing;
if (!dest.type) dest.type = options.type;
setTimeout(function () {
switch(dest.type) {
case 'moveTo': {
this.animate(dest, next);
break;
}
case 'flightTo':
default: {
this.flyTo(dest, next);
break;
}
}
}.bind(this), delay);
} else {
if (typeof(options.done)==='function') options.done(true);
}
} else {
if (typeof(options.done)==='function') options.done(false);
}
}.bind(this)
next(true);
};
/** Worker helper to create a worker from code
* @constructor
* @param {function} mainFn main worker function
* @param {object} options
* @param {function} [options.onMessage] a callback function to get worker result
*/
ol.ext.Worker = class olextWorker {
constructor(mainFn, options) {
// Convert to function
var mainStr = mainFn.toString().replace(/^.*\(/, 'function(');
var lib = '';
for (var i in options.lib) {
lib += '\nvar ' + i + ' = ' + options.lib[i].toString().replace(/^.*\(/, 'function(') + ';';
}
// Code
var lines = ['var mainFn = ' + mainStr + lib + `
self.addEventListener("message", function(event) {
var result = mainFn(event);
self.postMessage(result);
});`];
// console.log(lines[0])
this.code_ = URL.createObjectURL(new Blob(lines, { type: 'text/javascript' }));
this.onMessage_ = options.onMessage;
this.start();
}
/** Terminate current worker and start a new one
*/
start() {
if (this.worker)
this.worker.terminate();
this.worker = new Worker(this.code_);
this.worker.addEventListener('message', function (e) {
this.onMessage_(e.data);
}.bind(this));
}
/** Terminate a worker */
terminate() {
this.worker.terminate();
}
/** Post a new message to the worker
* @param {object} message
* @param {boolean} [restart=false] stop the worker and restart a new one
*/
postMessage(message, restart) {
if (restart)
this.start();
this.worker.postMessage(message);
}
/** Set onMessage callback
* @param {function} fn a callback function to get worker result
*/
onMessage(fn) {
this.onMessage_ = fn;
}
}
/** Converts an RGB color value to HSL.
* returns hsl as array h:[0,360], s:[0,100], l:[0,100]
* @param {ol/color~Color|string} rgb
* @param {number} [round=100]
* @returns {Array} hsl as h:[0,360], s:[0,100], l:[0,100]
*/
ol.color.toHSL = function(rgb, round) {
if (round===undefined) round = 100;
if (!Array.isArray(rgb)) rgb = ol.color.asArray(rgb);
var r = rgb[0] / 255;
var g = rgb[1] / 255;
var b = rgb[2] / 255;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
}
var hsl = [
Math.round(h*60*round)/round,
Math.round(s*100*round)/round,
Math.round(l*100*round)/round
];
if (rgb.length>3) hsl[3] = rgb[3];
return hsl;
}
/** Converts an HSL color value to RGB.
* @param {Array} hsl as h:[0,360], s:[0,100], l:[0,100]
* @param {number} [round=1000]
* @returns {Array} rgb
*/
ol.color.fromHSL = function(hsl, round) {
if (round===undefined) round = 1000
var h = hsl[0] / 360;
var s = hsl[1] / 100;
var l = hsl[2] / 100;
var r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
var hue2rgb = function(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
var rgb = [
Math.round(r * 255*round) / round,
Math.round(g * 255*round) / round,
Math.round(b * 255*round) / round
];
if (hsl.length>3) rgb[3] = hsl[3];
return rgb;
}
/** Converts an HSL color value to RGB.
* @param {ol/color~Color|string} rgb
* @param {number} [round=100]
* @returns {Array} hsl as h:[0,360], s:[0,100], l:[0,100]
*/
ol.color.toHSV = function(rgb, round) {
if (round===undefined) round = 100;
if (!Array.isArray(rgb)) rgb = ol.color.asArray(rgb);
var r = rgb[0] / 255;
var g = rgb[1] / 255;
var b = rgb[2] / 255;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, v = max;
var d = max - min;
s = max == 0 ? 0 : d / max;
if (max == min) {
h = 0; // achromatic
} else {
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
}
var hsv = [
Math.round(h*60*round)/round,
Math.round(s*100*round)/round,
Math.round(v*100*round)/round
];
if (rgb.length>3) hsv[3] = rgb[3];
return hsv;
}
/** Converts an HSV color value to RGB.
* @param {Array} hsl as h:[0,360], s:[0,100], l:[0,100]
* @param {number} [round=1000]
* @returns {Array} rgb
*/
ol.color.fromHSV = function(hsv, round) {
if (round===undefined) round = 1000
var h = hsv[0] / 360;
var s = hsv[1] / 100;
var v = hsv[2] / 100;
var r, g, b;
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
var rgb = [
Math.round(r * 255*round) / round,
Math.round(g * 255*round) / round,
Math.round(b * 255*round) / round
];
if (hsv.length>3) rgb[3] = hsv[3];
return rgb;
}
/** Converts an HSL color value to RGB.
* @param {ol/color~Color|string} rgb
* @returns {string}
*/
ol.color.toHexa = function(rgb) {
return '#' + ((1 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).slice(1);
}
/** Vanilla JS helper to manipulate DOM without jQuery
* @see https://github.com/nefe/You-Dont-Need-jQuery
* @see https://plainjs.com/javascript/
* @see http://youmightnotneedjquery.com/
*/
/** @namespace ol.ext.element */
ol.ext.element = {};
/**
* Create an element
* @param {string} tagName The element tag, use 'TEXT' to create a text node
* @param {*} options
* @param {string} options.className className The element class name
* @param {Element} options.parent Parent to append the element as child
* @param {Element|string} [options.html] Content of the element (if text is not set)
* @param {string} [options.text] Text content (if html is not set)
* @param {Element|string} [options.options] when tagName = SELECT a list of options as key:value to add to the select
* @param {string} options.* Any other attribut to add to the element
*/
ol.ext.element.create = function (tagName, options) {
options = options || {};
var elt;
// Create text node
if (tagName === 'TEXT') {
elt = document.createTextNode(options.html||'');
if (options.parent) options.parent.appendChild(elt);
} else {
// Other element
elt = document.createElement(tagName);
if (/button/i.test(tagName)) elt.setAttribute('type', 'button');
for (var attr in options) {
switch (attr) {
case 'className': {
if (options.className && options.className.trim) elt.setAttribute('class', options.className.trim());
break;
}
case 'text': {
elt.innerText = options.text;
break;
}
case 'html': {
if (options.html instanceof Element) elt.appendChild(options.html)
else if (options.html!==undefined) elt.innerHTML = options.html;
break;
}
case 'parent': {
if (options.parent) options.parent.appendChild(elt);
break;
}
case 'options': {
if (/select/i.test(tagName)) {
for (var i in options.options) {
ol.ext.element.create('OPTION', {
html: i,
value: options.options[i],
parent: elt
})
}
}
break;
}
case 'style': {
ol.ext.element.setStyle(elt, options.style);
break;
}
case 'change':
case 'click': {
ol.ext.element.addListener(elt, attr, options[attr]);
break;
}
case 'on': {
for (var e in options.on) {
ol.ext.element.addListener(elt, e, options.on[e]);
}
break;
}
case 'checked': {
elt.checked = !!options.checked;
break;
}
default: {
elt.setAttribute(attr, options[attr]);
break;
}
}
}
}
return elt;
};
/** Create a toggle switch input
* @param {*} options
* @param {string|Element} options.html
* @param {string|Element} options.after
* @param {boolean} options.checked
* @param {*} [options.on] a list of actions
* @param {function} [options.click]
* @param {function} [options.change]
* @param {Element} options.parent
*/
ol.ext.element.createSwitch = function (options) {
var input = ol.ext.element.create('INPUT', {
type: 'checkbox',
on: options.on,
click: options.click,
change: options.change,
parent: options.parent
});
var opt = Object.assign ({ input: input }, options || {});
new ol.ext.input.Switch(opt);
return input;
};
/** Create a toggle switch input
* @param {*} options
* @param {string|Element} options.html
* @param {string|Element} options.after
* @param {string} [options.name] input name
* @param {string} [options.type=checkbox] input type: radio or checkbox
* @param {string} options.value input value
* @param {*} [options.on] a list of actions
* @param {function} [options.click]
* @param {function} [options.change]
* @param {Element} options.parent
*/
ol.ext.element.createCheck = function (options) {
var input = ol.ext.element.create('INPUT', {
name: options.name,
type: (options.type==='radio' ? 'radio' : 'checkbox'),
on: options.on,
parent: options.parent
});
var opt = Object.assign ({ input: input }, options || {});
if (options.type === 'radio') {
new ol.ext.input.Radio(opt);
} else {
new ol.ext.input.Checkbox(opt);
}
return input;
};
/** Set inner html or append a child element to an element
* @param {Element} element
* @param {Element|string} html Content of the element
*/
ol.ext.element.setHTML = function(element, html) {
if (html instanceof Element) element.appendChild(html)
else if (html!==undefined) element.innerHTML = html;
};
/** Append text into an elemnt
* @param {Element} element
* @param {string} text text content
*/
ol.ext.element.appendText = function(element, text) {
element.appendChild(document.createTextNode(text||''));
};
/**
* Add a set of event listener to an element
* @param {Element} element
* @param {string|Array} eventType
* @param {function} fn
*/
ol.ext.element.addListener = function (element, eventType, fn, useCapture ) {
if (typeof eventType === 'string') eventType = eventType.split(' ');
eventType.forEach(function(e) {
element.addEventListener(e, fn, useCapture);
});
};
/**
* Add a set of event listener to an element
* @param {Element} element
* @param {string|Array} eventType
* @param {function} fn
*/
ol.ext.element.removeListener = function (element, eventType, fn) {
if (typeof eventType === 'string') eventType = eventType.split(' ');
eventType.forEach(function(e) {
element.removeEventListener(e, fn);
});
};
/**
* Show an element
* @param {Element} element
*/
ol.ext.element.show = function (element) {
element.style.display = '';
};
/**
* Hide an element
* @param {Element} element
*/
ol.ext.element.hide = function (element) {
element.style.display = 'none';
};
/**
* Test if an element is hihdden
* @param {Element} element
* @return {boolean}
*/
ol.ext.element.hidden = function (element) {
return ol.ext.element.getStyle(element, 'display') === 'none';
};
/**
* Toggle an element
* @param {Element} element
*/
ol.ext.element.toggle = function (element) {
element.style.display = (element.style.display==='none' ? '' : 'none');
};
/** Set style of an element
* @param {DOMElement} el the element
* @param {*} st list of style
*/
ol.ext.element.setStyle = function(el, st) {
for (var s in st) {
switch (s) {
case 'top':
case 'left':
case 'bottom':
case 'right':
case 'minWidth':
case 'maxWidth':
case 'width':
case 'height': {
if (typeof(st[s]) === 'number') {
el.style[s] = st[s]+'px';
} else {
el.style[s] = st[s];
}
break;
}
default: {
el.style[s] = st[s];
}
}
}
};
/**
* Get style propertie of an element
* @param {DOMElement} el the element
* @param {string} styleProp Propertie name
* @return {*} style value
*/
ol.ext.element.getStyle = function(el, styleProp) {
var value, defaultView = (el.ownerDocument || document).defaultView;
// W3C standard way:
if (defaultView && defaultView.getComputedStyle) {
// sanitize property name to css notation
// (hypen separated words eg. font-Size)
styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase();
value = defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
} else if (el.currentStyle) { // IE
// sanitize property name to camelCase
styleProp = styleProp.replace(/-(\w)/g, function(str, letter) {
return letter.toUpperCase();
});
value = el.currentStyle[styleProp];
// convert other units to pixels on IE
if (/^\d+(em|pt|%|ex)?$/i.test(value)) {
return (function(value) {
var oldLeft = el.style.left, oldRsLeft = el.runtimeStyle.left;
el.runtimeStyle.left = el.currentStyle.left;
el.style.left = value || 0;
value = el.style.pixelLeft + "px";
el.style.left = oldLeft;
el.runtimeStyle.left = oldRsLeft;
return value;
})(value);
}
}
if (/px$/.test(value)) return parseInt(value);
return value;
};
/** Get outerHeight of an elemen
* @param {DOMElement} elt
* @return {number}
*/
ol.ext.element.outerHeight = function(elt) {
return elt.offsetHeight + ol.ext.element.getStyle(elt, 'marginBottom')
};
/** Get outerWidth of an elemen
* @param {DOMElement} elt
* @return {number}
*/
ol.ext.element.outerWidth = function(elt) {
return elt.offsetWidth + ol.ext.element.getStyle(elt, 'marginLeft')
};
/** Get element offset rect
* @param {DOMElement} elt
* @return {*}
*/
ol.ext.element.offsetRect = function(elt) {
var rect = elt.getBoundingClientRect();
return {
top: rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0),
left: rect.left + (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0),
height: rect.height || (rect.bottom - rect.top),
width: rect.width || (rect.right - rect.left)
}
};
/** Get element offset
* @param {ELement} elt
* @returns {Object} top/left offset
*/
ol.ext.element.getFixedOffset = function(elt) {
var offset = {
left:0,
top:0
};
var getOffset = function(parent) {
if (!parent) return offset;
// Check position when transform
if (ol.ext.element.getStyle(parent, 'position') === 'absolute'
&& ol.ext.element.getStyle(parent, 'transform') !== "none") {
var r = parent.getBoundingClientRect();
offset.left += r.left;
offset.top += r.top;
return offset;
}
return getOffset(parent.offsetParent)
}
return getOffset(elt.offsetParent)
};
/** Get element offset rect
* @param {DOMElement} elt
* @param {boolean} fixed get fixed position
* @return {Object}
*/
ol.ext.element.positionRect = function(elt, fixed) {
var gleft = 0;
var gtop = 0;
var getRect = function( parent ) {
if (parent) {
gleft += parent.offsetLeft;
gtop += parent.offsetTop;
return getRect(parent.offsetParent);
} else {
var r = {
top: elt.offsetTop + gtop,
left: elt.offsetLeft + gleft
};
if (fixed) {
r.top -= (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0);
r.left -= (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0);
}
r.bottom = r.top + elt.offsetHeight;
r.right = r.top + elt.offsetWidth;
return r;
}
};
return getRect(elt.offsetParent);
}
/** Make a div scrollable without scrollbar.
* On touch devices the default behavior is preserved
* @param {DOMElement} elt
* @param {*} options
* @param {function} [options.onmove] a function that takes a boolean indicating that the div is scrolling
* @param {boolean} [options.vertical=false]
* @param {boolean} [options.animate=true] add kinetic to scroll
* @param {boolean} [options.mousewheel=false] enable mousewheel to scroll
* @param {boolean} [options.minibar=false] add a mini scrollbar to the parent element (only vertical scrolling)
* @returns {Object} an object with a refresh function
*/
ol.ext.element.scrollDiv = function(elt, options) {
options = options || {};
var pos = false;
var speed = 0;
var d, dt = 0;
var onmove = (typeof(options.onmove) === 'function' ? options.onmove : function(){});
//var page = options.vertical ? 'pageY' : 'pageX';
var page = options.vertical ? 'screenY' : 'screenX';
var scroll = options.vertical ? 'scrollTop' : 'scrollLeft';
var moving = false;
// Factor scale content / container
var scale, isbar;
// Update the minibar
var updateCounter = 0;
var updateMinibar = function() {
if (scrollbar) {
updateCounter++;
setTimeout(updateMinibarDelay);
}
}
var updateMinibarDelay = function() {
if (scrollbar) {
updateCounter--;
// Prevent multi call
if (updateCounter) return;
// Container height
var pheight = elt.clientHeight;
// Content height
var height = elt.scrollHeight;
// Set scrollbar value
scale = pheight / height;
scrollbar.style.height = scale * 100 + '%';
scrollbar.style.top = (elt.scrollTop / height * 100) + '%';
scrollContainer.style.height = pheight + 'px';
// No scroll
if (pheight > height - .5) scrollContainer.classList.add('ol-100pc');
else scrollContainer.classList.remove('ol-100pc');
}
}
// Handle pointer down
var onPointerDown = function(e) {
// Prevent scroll
if (e.target.classList.contains('ol-noscroll')) return;
// Start scrolling
moving = false;
pos = e[page];
dt = new Date();
elt.classList.add('ol-move');
// Prevent elt dragging
e.preventDefault();
// Listen scroll
window.addEventListener('pointermove', onPointerMove);
ol.ext.element.addListener(window, ['pointerup','pointercancel'], onPointerUp);
}
// Register scroll
var onPointerMove = function(e) {
if (pos !== false) {
var delta = (isbar ? -1/scale : 1) * (pos - e[page]);
moving = moving || Math.round(delta)
elt[scroll] += delta;
d = new Date();
if (d-dt) {
speed = (speed + delta / (d - dt))/2;
}
pos = e[page];
dt = d;
// Tell we are moving
if (delta) onmove(true);
} else {
moving = true;
}
};
// Animate scroll
var animate = function(to) {
var step = (to>0) ? Math.min(100, to/2) : Math.max(-100, to/2);
to -= step;
elt[scroll] += step;
if (-1 < to && to < 1) {
if (moving) setTimeout(function() { elt.classList.remove('ol-move'); });
else elt.classList.remove('ol-move');
moving = false;
onmove(false);
} else {
setTimeout(function() {
animate(to);
}, 40);
}
}
// Initialize scroll container for minibar
var scrollContainer, scrollbar;
if (options.vertical && options.minibar) {
var init = function(b) {
// only once
elt.removeEventListener('pointermove', init);
elt.parentNode.classList.add('ol-miniscroll');
scrollbar = ol.ext.element.create('DIV');
scrollContainer = ol.ext.element.create('DIV', {
className: 'ol-scroll',
html: scrollbar
});
elt.parentNode.insertBefore(scrollContainer, elt);
// Move scrollbar
scrollbar.addEventListener('pointerdown', function(e) {
isbar = true;
onPointerDown(e)
});
// Handle mousewheel
if (options.mousewheel) {
ol.ext.element.addListener(scrollContainer,
['mousewheel', 'DOMMouseScroll', 'onmousewheel'],
function(e) { onMouseWheel(e) }
);
ol.ext.element.addListener(scrollbar,
['mousewheel', 'DOMMouseScroll', 'onmousewheel'],
function(e) { onMouseWheel(e) }
);
}
// Update on enter
elt.parentNode.addEventListener('pointerenter', updateMinibar);
// Update on resize
window.addEventListener('resize', updateMinibar);
// Update
if (b!==false) updateMinibar();
};
// Allready inserted in the DOM
if (elt.parentNode) init(false);
// or wait when ready
else elt.addEventListener('pointermove', init);
// Update on scroll
elt.addEventListener('scroll', function() {
updateMinibar();
});
}
// Enable scroll
elt.style['touch-action'] = 'none';
elt.style['overflow'] = 'hidden';
elt.classList.add('ol-scrolldiv');
// Start scrolling
ol.ext.element.addListener(elt, ['pointerdown'], function(e) {
isbar = false;
onPointerDown(e)
});
// Prevet click when moving...
elt.addEventListener('click', function(e) {
if (elt.classList.contains('ol-move')) {
e.preventDefault();
e.stopPropagation();
}
}, true);
// Stop scrolling
var onPointerUp = function(e) {
dt = new Date() - dt;
if (dt>100 || isbar) {
// User stop: no speed
speed = 0;
} else if (dt>0) {
// Calculate new speed
speed = ((speed||0) + (pos - e[page]) / dt) / 2;
}
animate(options.animate===false ? 0 : speed*200);
pos = false;
speed = 0;
dt = 0;
// Add class to handle click (on iframe / double-click)
if (!elt.classList.contains('ol-move')) {
elt.classList.add('ol-hasClick')
setTimeout(function() { elt.classList.remove('ol-hasClick'); }, 500);
} else {
elt.classList.remove('ol-hasClick');
}
isbar = false;
window.removeEventListener('pointermove', onPointerMove)
ol.ext.element.removeListener(window, ['pointerup','pointercancel'], onPointerUp);
};
// Handle mousewheel
var onMouseWheel = function(e) {
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
elt.classList.add('ol-move');
elt[scroll] -= delta*30;
elt.classList.remove('ol-move');
return false;
}
if (options.mousewheel) { // && !elt.classList.contains('ol-touch')) {
ol.ext.element.addListener(elt,
['mousewheel', 'DOMMouseScroll', 'onmousewheel'],
onMouseWheel
);
}
return {
refresh: updateMinibar
}
};
/** Dispatch an event to an Element
* @param {string} eventName
* @param {Element} element
*/
ol.ext.element.dispatchEvent = function (eventName, element) {
var event;
try {
event = new CustomEvent(eventName);
} catch(e) {
// Try customevent on IE
event = document.createEvent("CustomEvent");
event.initCustomEvent(eventName, true, true, {});
}
element.dispatchEvent(event);
};
/** Get a canvas overlay for a map (non rotated, on top of the map)
* @param {ol.Map} map
* @return {canvas}
*/
ol.ext.getMapCanvas = function(map) {
if (!map) return null;
var canvas = map.getViewport().getElementsByClassName('ol-fixedoverlay')[0];
if (!canvas) {
if (map.getViewport().querySelector('.ol-layers')) {
// Add a fixed canvas layer on top of the map
canvas = document.createElement('canvas');
canvas.className = 'ol-fixedoverlay';
map.getViewport().querySelector('.ol-layers').after(canvas);
// Clear before new compose
map.on('precompose', function (e){
canvas.width = map.getSize()[0] * e.frameState.pixelRatio;
canvas.height = map.getSize()[1] * e.frameState.pixelRatio;
});
} else {
canvas = map.getViewport().querySelector('canvas');
}
}
return canvas;
};
ol.ext.olVersion = ol.util.VERSION.split('.');
ol.ext.olVersion = parseInt(ol.ext.olVersion[0])*100 + parseInt(ol.ext.olVersion[1]);
/** Get style to use in a VectorContext
* @param {} e
* @param {ol.style.Style} s
* @return {ol.style.Style}
*/
ol.ext.getVectorContextStyle = function(e, s) {
var ratio = e.frameState.pixelRatio;
// Bug with Icon images
if (ol.ext.olVersion > 605 && ratio !== 1 && (s.getImage() instanceof ol.style.Icon)) {
s = s.clone();
var img = s.getImage();
img.setScale(img.getScale()*ratio);
/* BUG anchor don't use ratio */
var anchor = img.getAnchor();
if (img.setDisplacement) {
var disp = img.getDisplacement();
if (disp) {
disp[0] -= anchor[0]/ratio;
disp[1] += anchor[1]/ratio;
img.setAnchor([0,0]);
}
} else {
if (anchor) {
anchor[0] /= ratio;
anchor[1] /= ratio;
}
}
/**/
}
return s;
}
/** Helper for loading BIL-32 (Band Interleaved by Line) image
* @param {string} src
* @param {function} onload a function that takes a Float32Array and a ol.size.Size (array size)
* @param {function} onerror
* @private
*/
ol.ext.imageLoader.loadBILImage = function(src, onload, onerror) {
var size = [
parseInt(src.replace(/.*WIDTH=(\d*).*/i,'$1')),
parseInt(src.replace(/.*HEIGHT=(\d*).*/i,'$1'))
];
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.addEventListener('loadend', function () {
var resp = this.response;
if (resp !== undefined) {
var reader = new FileReader();
// Get as array
reader.addEventListener('loadend', (e) => {
var data = new Float32Array(e.target.result);
onload(data, size);
});
// Start reading the blob
reader.readAsArrayBuffer(resp);
// tile.getImage().src = URL.createObjectURL(blob);
} else {
onerror();
}
});
xhr.addEventListener('error', function () {
onerror();
});
xhr.open('GET', src);
xhr.send();
};
/** Helper for loading image
* @param {string} src
* @param {function} onload a function that takes a an image and a ol.size.Size
* @param {function} onerror
* @private
*/
ol.ext.imageLoader.loadImage = function(src, onload, onerror) {
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.addEventListener('loadend', function () {
var resp = this.response;
if (resp !== undefined) {
var img = new Image();
img.onload = function() {
onload(img, [img.naturalWidth, img.naturalHeight]);
}
img.src = URL.createObjectURL(resp);
} else {
onerror();
}
});
xhr.addEventListener('error', function () {
onerror();
});
xhr.open('GET', src);
xhr.send();
};
/** Get a TileLoadFunction to transform tiles images
* @param {function} setPixel a function that takes a Uint8ClampedArray and the pixel position to transform
* @returns {function} an ol/Tile~LoadFunction
*/
ol.ext.imageLoader.pixelTransform = function(setPixel) {
return function(tile, src) {
ol.ext.imageLoader.loadImage(
src,
function(img, size) {
var canvas = document.createElement('canvas');
canvas.width = size[0];
canvas.height = size[1];
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, size[0], size[1]);
var pixels = imgData.data;
for (var i = 0; i < pixels.length; i += 4) {
setPixel(pixels, i, size);
}
ctx.putImageData(imgData, 0, 0);
tile.setImage(canvas);
},
function() {
tile.setState(3);
}
);
}
};
/** Get a TileLoadFunction to transform tiles into grayscale images
* @returns {function} an ol/Tile~LoadFunction
*/
ol.ext.imageLoader.grayscale = function() {
return ol.ext.imageLoader.pixelTransform(function(pixels, i) {
pixels[i] = pixels[i + 1] = pixels[i + 2] = parseInt(3*pixels[i] + 4*pixels[i + 1] + pixels[i + 2] >>> 3);
})
};
/** Get a TileLoadFunction to turn color or a color range transparent
* @param {ol.color.Color|Array} colors color or color range to turn transparent
* @returns {function} an ol/Tile~LoadFunction
*/
ol.ext.imageLoader.transparent = function(colors) {
var color1, color2;
if (colors instanceof Array) {
color1 = colors[0];
color2 = colors[1];
}
var color = color1 = ol.color.asArray(color1);
if (!color2) {
return ol.ext.imageLoader.pixelTransform(function(pixels, i) {
if (pixels[i]===color[0] && pixels[i+1]===color[1] && pixels[i+2]===color[2]) {
pixels[i+3] = 0;
}
})
} else {
color2 = ol.color.asArray(color2);
color = [Math.min(color1[0], color2[0]), Math.min(color1[1], color2[1]), Math.min(color1[2], color2[2])];
color2 = [Math.max(color1[0], color2[0]), Math.max(color1[1], color2[1]), Math.max(color1[2], color2[2])];
return ol.ext.imageLoader.pixelTransform(function(pixels, i) {
if (pixels[i]>=color1[0] && pixels[i]<=color2[0]
&& pixels[i+1]>=color[1] && pixels[i+1]<=color2[1]
&& pixels[i+2]>=color[2] && pixels[i+2]<=color2[2]) {
pixels[i+3] = 0;
}
})
}
};
/** Returns an Imageloader function to load an x-bil-32 image as sea level map
* to use as a ol/Tile~LoadFunction or ol/Image~LoadFunction
* @param { number } level
* @param {*} options
* @param { ol.Color } [options.color] fill color
* @param { boolean } [options.opacity=true] smooth color on border
* @param { number } [options.minValue=-Infinity] minimum level value
* @returns {function} an ol/Tile~LoadFunction
*/
ol.ext.imageLoader.seaLevelMap = function(level, options) {
options = options || {};
var h0 = Math.max(level + .01, .01);
var c = options.color ? ol.color.asArray(options.color) : [135,203,249];
var min = typeof(options.minValue) === 'number' ? options.minValue : -Infinity;
var opacity = options.opacity!==false;
return ol.ext.imageLoader.elevationMap(function(h) {
if (h < h0 && h > min) {
return [c[0], c[1], c[2], opacity ? 255 * (h0-h) / h0 : 255];
} else {
return [0,0,0,0];
}
})
};
/** Shaded relief ? not/bad working yet...
* @returns {function} an ol/Tile~LoadFunction
* @private
*/
ol.ext.imageLoader.shadedRelief = function() {
var sunElev = Math.PI / 4;
var sunAzimuth = 2*Math.PI - Math.PI / 4;
return function (tile, src) {
ol.ext.imageLoader.loadBILImage(
src,
function(data, size) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var width = canvas.width = size[0];
var height = canvas.height = size[1];
var imgData = ctx.getImageData(0, 0, width, height);
var pixels = imgData.data;
function getIndexForCoordinates(x, y) {
return x + y*width;
}
for (var x=0; x 0 ) {
if ( sly > 0 ) azimuth = phi + 1.5*Math.PI;
else if ( sly < 0 ) azimuth = 1.5*Math.PI - phi;
else phi = 1.5*Math.PI;
} else if ( slx < 0 ){
if ( sly < 0 ) azimuth = phi + .5*Math.PI;
else if ( sly > 0 ) azimuth = .5*Math.PI - phi;
else azimuth = .5*Math.PI;
} else {
if ( sly < 0 ) azimuth = Math.PI;
else if ( sly > 0 ) azimuth = 0;
}
// get luminance
var lum = Math.cos( azimuth - sunAzimuth )*Math.cos( Math.PI*.5 - Math.atan(sl0) )*Math.cos( sunElev ) + Math.sin( Math.PI*.5 - Math.atan(sl0) )*Math.sin( sunElev );
if (lum<0) lum = 0;
lum = Math.sqrt(lum*.8 + .5);
var p = getIndexForCoordinates(x,y) * 4;
pixels[p] = pixels[p+1] = pixels[p+2] = 0;
pixels[p+3] = 255 - lum*255;
}
ctx.putImageData(imgData, 0, 0);
tile.setImage(canvas);
},
function () {
tile.setState(3);
}
)
};
};
/** Get a TileLoadFunction to load an x-bil-32 image as elevation map (ie. pixels colors codes elevations as terrain-RGB)
* If getPixelColor is not define pixel store elevation as rgb, use {@link ol.ext.getElevationFromPixel} to get elevation from pixel
* @param {function} [getPixelColor] a function that taket an elevation and return a color array [r,g,b,a], default store elevation as terrain-RGB
* @returns {function} an ol/Tile~LoadFunction
*/
ol.ext.imageLoader.elevationMap = function(getPixelColor) {
if (typeof(getPixelColor) !== 'function') getPixelColor = ol.ext.getPixelFromElevation;
return function (tile, src) {
ol.ext.imageLoader.loadBILImage(
src,
function(data, size) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = size[0];
canvas.height = size[1];
var imgData = ctx.getImageData(0, 0, size[0], size[1]);
var pixels = imgData.data;
for (var i=0; i -12000 m
* - 2 digits (0.01 m)
* @param {number} height elevation
* @returns {Array} pixel value
*/
ol.ext.getPixelFromElevation = function(height) {
var h = Math.round(height*100 + 1200000);
var pixel = [
h >> 16,
(h % 65536) >> 8,
h % 256,
255
];
return pixel;
};
/** Convert pixel (terrain-RGB) to elevation
* @see ol.ext.getPixelFromElevation
* @param {Array} pixel the pixel value
* @returns {number} elevation
*/
ol.ext.getElevationFromPixel = function(pixel) {
// return -10000 + (pixel[0] * 65536 + pixel[1] * 256 + pixel[2]) * 0.01;
return -12000 + ((pixel[0] << 16) + (pixel[1] << 8) + pixel[2]) * 0.01;
};
/* See
https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web
https://evanw.github.io/lightgl.js/docs/matrix.html
https://github.com/jlmakes/rematrix
https://jsfiddle.net/2znLxda2/
*/
/** Matrix3D; a set of functions to handle matrix3D
*/
ol.matrix3D = {};
/** Get transform matrix3D of an element
* @param {Element} ele
* @return {Array>}
*/
ol.matrix3D.getTransform = function(ele) {
var style = window.getComputedStyle(ele, null);
var tr = style.getPropertyValue("-webkit-transform")
|| style.getPropertyValue("-moz-transform")
|| style.getPropertyValue("-ms-transform")
|| style.getPropertyValue("-o-transform")
|| style.getPropertyValue("transform");
var values = tr.split('(')[1].split(')')[0].split(',');
var mx = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ];
var i, j;
if (values.length === 16) {
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
mx[j][i] = +values[i * 4 + j];
}
}
} else {
for (i = 0; i < 3; ++i) {
for (j = 0; j < 2; ++j) {
mx[j][i] = +values[i * 2 + j];
}
}
}
return mx;
};
/** Get transform matrix3D of an element
* @param {Element} ele
* @return {Array}
*/
ol.matrix3D.getTransformOrigin = function (ele) {
var style = window.getComputedStyle(ele, null);
var tr = style.getPropertyValue("-webkit-transform-origin")
|| style.getPropertyValue("-moz-transform-origin")
|| style.getPropertyValue("-ms-transform-origin")
|| style.getPropertyValue("-o-transform-origin")
|| style.getPropertyValue("transform-origin");
var values = tr.split(' ');
var mx = [ 0, 0, 0, 1 ];
for (var i = 0; i < values.length; ++i) {
mx[i] = parseInt(values[i]);
}
return mx;
};
/** Compute translate matrix
* @param {number} x
* @param {number} y
* @param {number} z
* @return {Array>}
*/
ol.matrix3D.translateMatrix = function(x, y, z) {
return [
[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
];
};
/** Identity matrix
* @return {Array>}
*/
ol.matrix3D.identity = function() {
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
};
/** Round matrix
* @param {Array>} mx
* @param {number} round Rounding value, default 1E-10
*/
ol.matrix3D.roundTo = function(mx, round) {
if (!round) round = 1E-10;
var m = [[],[],[],[]];
for (var i=0; i<4; i++) {
for (var j=0; j<4; j++) {
m[i][j] = Math.round(mx[i][j] / round) * round;
}
}
return m;
};
/** Multiply matrix3D
* @param {Array>} mx1
* @param {Array>} mx2
* @return {Array>}
*/
ol.matrix3D.multiply = function (mx1, mx2) {
var mx = [ [], [], [], [] ];
for (var i = 0; i < 4; ++i) {
for (var j = 0; j < 4; ++j) {
var sum = 0;
for (var k = 0; k < 4; ++k) {
sum += (mx1[k][i] * mx2[j][k]);
}
mx[j][i] = sum;
}
}
return mx;
};
/** Compute the full transform that is applied to the transformed parent: -origin o tx o origin
* @param {Array>} tx transform matrix
* @param {Array>} origin transform origin
* @return {Array>}
*/
ol.matrix3D.computeTransformMatrix = function(tx, origin) {
var preTx = ol.matrix3D.translateMatrix(-origin[0], -origin[1], -origin[2]);
var postTx = ol.matrix3D.translateMatrix(origin[0], origin[1], origin[2]);
var temp1 = ol.matrix3D.multiply(preTx, tx);
return ol.matrix3D.multiply(temp1, postTx);
};
/** Apply transform to a coordinate
* @param {Array>} tx
* @param {ol.pixel} px
*/
ol.matrix3D.transformVertex = function(tx, px) {
var vert = [px[0], px[1], 0, 1]
var mx = [ ];
for (var i = 0; i < 4; ++i) {
mx[i] = 0;
for (var j = 0; j < 4; ++j) {
mx[i] += +tx[i][j] * vert[j];
}
}
return mx;
}
/** Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component).
* @param {Array} vert
* @return {Array}
*/
ol.matrix3D.projectVertex = function(vert) {
var out = [ ];
for (var i = 0; i < 4; ++i) {
out[i] = vert[i] / vert[3];
}
return out;
};
/** Inverse a matrix3D
* @return {Array>} m matrix to transform
* @return {Array>}
*/
ol.matrix3D.inverse = function(m) {
var s0 = m[0][0] * m[1][1] - m[1][0] * m[0][1]
var s1 = m[0][0] * m[1][2] - m[1][0] * m[0][2]
var s2 = m[0][0] * m[1][3] - m[1][0] * m[0][3]
var s3 = m[0][1] * m[1][2] - m[1][1] * m[0][2]
var s4 = m[0][1] * m[1][3] - m[1][1] * m[0][3]
var s5 = m[0][2] * m[1][3] - m[1][2] * m[0][3]
var c5 = m[2][2] * m[3][3] - m[3][2] * m[2][3]
var c4 = m[2][1] * m[3][3] - m[3][1] * m[2][3]
var c3 = m[2][1] * m[3][2] - m[3][1] * m[2][2]
var c2 = m[2][0] * m[3][3] - m[3][0] * m[2][3]
var c1 = m[2][0] * m[3][2] - m[3][0] * m[2][2]
var c0 = m[2][0] * m[3][1] - m[3][0] * m[2][1]
var determinant = 1 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0)
if (isNaN(determinant) || determinant === Infinity) {
throw new Error('Inverse determinant attempted to divide by zero.')
}
return [
[
(m[1][1] * c5 - m[1][2] * c4 + m[1][3] * c3) * determinant,
(-m[0][1] * c5 + m[0][2] * c4 - m[0][3] * c3) * determinant,
(m[3][1] * s5 - m[3][2] * s4 + m[3][3] * s3) * determinant,
(-m[2][1] * s5 + m[2][2] * s4 - m[2][3] * s3) * determinant
],[
(-m[1][0] * c5 + m[1][2] * c2 - m[1][3] * c1) * determinant,
(m[0][0] * c5 - m[0][2] * c2 + m[0][3] * c1) * determinant,
(-m[3][0] * s5 + m[3][2] * s2 - m[3][3] * s1) * determinant,
(m[2][0] * s5 - m[2][2] * s2 + m[2][3] * s1) * determinant
],[
(m[1][0] * c4 - m[1][1] * c2 + m[1][3] * c0) * determinant,
(-m[0][0] * c4 + m[0][1] * c2 - m[0][3] * c0) * determinant,
(m[3][0] * s4 - m[3][1] * s2 + m[3][3] * s0) * determinant,
(-m[2][0] * s4 + m[2][1] * s2 - m[2][3] * s0) * determinant
],[
(-m[1][0] * c3 + m[1][1] * c1 - m[1][2] * c0) * determinant,
(m[0][0] * c3 - m[0][1] * c1 + m[0][2] * c0) * determinant,
(-m[3][0] * s3 + m[3][1] * s1 - m[3][2] * s0) * determinant,
(m[2][0] * s3 - m[2][1] * s1 + m[2][2] * s0) * determinant
]
]
};
/* global ol */
/* Create ol.sphere for backward compatibility with ol < 5.0
* To use with Openlayers package
*/
if (window.ol && !ol.sphere) {
ol.sphere = {};
ol.sphere.getDistance = function (c1, c2, radius) {
var sphere = new ol.Sphere(radius || 6371008.8);
return sphere.haversineDistance(c1, c2);
}
ol.sphere.getArea = ol.Sphere.getArea;
ol.sphere.getLength = ol.Sphere.getLength;
}
/* Copyright (c) 2016 Jean-Marc VIGLINO,
released under the CeCILL-B license (French BSD license)
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
/** A simple filter to detect edges on images
* @constructor
* @requires ol.filter
* @extends {ol.ext.SVGFilter}
* @param {*} options
* @param {number} options.neighbours nb of neighbour (4 or 8), default 8
* @param {boolean} options.grayscale get grayscale image, default false,
* @param {boolean} options.alpha get alpha channel, default false
*/
ol.ext.SVGFilter.Laplacian = class olextSVGFilterLaplacian extends ol.ext.SVGFilter {
constructor(options) {
options = options || {};
super({ id: options.id });
var operation = {
feoperation: 'feConvolveMatrix',
in: 'SourceGraphic',
preserveAlpha: true,
result: 'C1'
};
if (options.neighbours===4) {
operation.kernelMatrix = [
0, -1, 0,
-1, 4, -1,
0, -1, 0
];
} else {
operation.kernelMatrix = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
];
}
this.addOperation(operation);
if (options.grayscale) this.grayscale();
else if (options.alpha) this.luminanceToAlpha({ gamma: options.gamma });
}
};
/* Copyright (c) 2016 Jean-Marc VIGLINO,
released under the CeCILL-B license (French BSD license)
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
/** Apply a sobel filter on an image
* @constructor
* @requires ol.filter
* @extends {ol.ext.SVGFilter}
* @param {object} options
* @param {string} [options.id]
* @param {number} [options.scale=1]
* @param {number} [options.ligth=50] light option. 0: darker, 100: lighter
*/
ol.ext.SVGFilter.Paper = class olextSVGFilterPaper extends ol.ext.SVGFilter {
constructor(options) {
options = options || {};
super({
id: options.id
});
this.addOperation({
feoperation: 'feTurbulence',
numOctaves: 4,
seed: 0,
type: 'fractalNoise',
baseFrequency: 0.2 / (options.scale || 1)
});
this.addOperation({
feoperation: 'feDiffuseLighting',
'lighting-color': 'rgb(255,255,255)',
surfaceScale: 1.5,
kernelUnitLength: 0.01,
diffuseConstant: 1.1000000000000001,
result: 'paper',
operations: [{
feoperation: 'feDistantLight',
elevation: options.light || 50,
azimuth: 75
}]
});
this.addOperation({
feoperation: 'feBlend',
in: 'SourceGraphic',
in2: 'paper',
mode: 'multiply'
});
}
/** Set filter light
* @param {number} light light option. 0: darker, 100: lighter
*/
setLight(light) {
this.element.querySelector('feDistantLight').setAttribute('elevation', light);
}
}
/* Copyright (c) 2016 Jean-Marc VIGLINO,
released under the CeCILL-B license (French BSD license)
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
/** Apply a Prewitt filter on an image
* @constructor
* @requires ol.filter
* @extends {ol.ext.SVGFilter}
* @param {*} options
* @param {boolean} options.grayscale get grayscale image, default false,
* @param {boolean} options.alpha get alpha channel, default false
*/
ol.ext.SVGFilter.Prewitt = class olextSVGFilterPrewitt extends ol.ext.SVGFilter {
constructor(options) {
options = options || {};
super({ id: options.id, color: 'sRGB' });
var operation = {
feoperation: 'feConvolveMatrix',
in: 'SourceGraphic',
preserveAlpha: true,
order: 3
};
// Vertical
operation.kernelMatrix = [
-1, -1, -1,
0, 0, 0,
1, 1, 1
];
operation.result = 'V1';
this.addOperation(operation);
operation.kernelMatrix = [
1, 1, 1,
0, 0, 0,
-1, -1, -1
];
operation.result = 'V2';
this.addOperation(operation);
// Horizontal
operation.kernelMatrix = [
-1, 0, 1,
-1, 0, 1,
-1, 0, 1
];
operation.result = 'H1';
this.addOperation(operation);
operation.kernelMatrix = [
1, -0, -1,
1, 0, -1,
1, 0, -1
];
operation.result = 'H2';
this.addOperation(operation);
// Compose V
this.addOperation({
feoperation: 'feComposite',
operator: 'arithmetic',
in: 'V1',
in2: 'V2',
k2: 1,
k3: 1,
result: 'V'
});
// Compose H
this.addOperation({
feoperation: 'feComposite',
operator: 'arithmetic',
in: 'H1',
in2: 'H2',
k2: 1,
k3: 1,
result: 'H'
});
// Merge
this.addOperation({
feoperation: 'feBlend',
mode: 'lighten',
in: 'H',
in2: 'V'
});
if (options.grayscale) this.grayscale();
else if (options.alpha) this.luminanceToAlpha();
}
};
/* Copyright (c) 2016 Jean-Marc VIGLINO,
released under the CeCILL-B license (French BSD license)
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
/** Apply a Roberts filter on an image
* @constructor
* @requires ol.filter
* @extends {ol.ext.SVGFilter}
* @param {*} options
* @param {boolean} options.grayscale get grayscale image, default false,
* @param {boolean} options.alpha get alpha channel, default false
*/
ol.ext.SVGFilter.Roberts = class olextSVGFilterRoberts extends ol.ext.SVGFilter {
constructor(options) {
options = options || {};
super({ id: options.id, color: 'sRGB' });
var operation = {
feoperation: 'feConvolveMatrix',
in: 'SourceGraphic',
preserveAlpha: true,
order: 3
};
// Vertical
operation.kernelMatrix = [
-1, 0, 0,
0, 0, 0,
0, 0, 1
];
operation.result = 'V1';
this.addOperation(operation);
operation.kernelMatrix = [
1, 0, 0,
0, 0, 0,
0, 0, -1
];
operation.result = 'V2';
this.addOperation(operation);
// Horizontal
operation.kernelMatrix = [
0, 0, 1,
0, 0, 0,
-1, 0, 0
];
operation.result = 'H1';
this.addOperation(operation);
operation.kernelMatrix = [
0, -0, -1,
0, 0, 0,
1, 0, 0
];
operation.result = 'H2';
this.addOperation(operation);
// Compose V
this.addOperation({
feoperation: 'feComposite',
operator: 'arithmetic',
in: 'V1',
in2: 'V2',
k2: 1,
k3: 1,
result: 'V'
});
// Compose H
this.addOperation({
feoperation: 'feComposite',
operator: 'arithmetic',
in: 'H1',
in2: 'H2',
k2: 1,
k3: 1,
result: 'H'
});
// Merge
this.addOperation({
feoperation: 'feBlend',
mode: 'lighten',
in: 'H',
in2: 'V'
});
if (options.grayscale) this.grayscale();
else if (options.alpha) this.luminanceToAlpha();
}
};
/* Copyright (c) 2016 Jean-Marc VIGLINO,
released under the CeCILL-B license (French BSD license)
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
/** Apply a sobel filter on an image
* @constructor
* @requires ol.filter
* @extends {ol.ext.SVGFilter}
* @param {*} options
* @param {boolean} options.grayscale get grayscale image, default false,
* @param {boolean} options.alpha get alpha channel, default false
*/
ol.ext.SVGFilter.Sobel = class olextSVGFilterSobel extends ol.ext.SVGFilter {
constructor(options) {
options = options || {};
super({ id: options.id, color: 'sRGB' });
var operation = {
feoperation: 'feConvolveMatrix',
in: 'SourceGraphic',
preserveAlpha: true,
order: 3
};
// Vertical
operation.kernelMatrix = [
-1, -2, -1,
0, 0, 0,
1, 2, 1
];
operation.result = 'V1';
this.addOperation(operation);
operation.kernelMatrix = [
1, 2, 1,
0, 0, 0,
-1, -2, -1
];
operation.result = 'V2';
this.addOperation(operation);
// Horizontal
operation.kernelMatrix = [
-1, 0, 1,
-2, 0, 2,
-1, 0, 1
];
operation.result = 'H1';
this.addOperation(operation);
operation.kernelMatrix = [
1, -0, -1,
2, 0, -2,
1, 0, -1
];
operation.result = 'H2';
this.addOperation(operation);
// Compose V
this.addOperation({
feoperation: 'feComposite',
operator: 'arithmetic',
in: 'V1',
in2: 'V2',
k2: 1,
k3: 1,
result: 'V'
});
// Compose H
this.addOperation({
feoperation: 'feComposite',
operator: 'arithmetic',
in: 'H1',
in2: 'H2',
k2: 1,
k3: 1,
result: 'H'
});
// Merge
this.addOperation({
feoperation: 'feBlend',
mode: 'lighten',
in: 'H',
in2: 'V'
});
if (options.grayscale) this.grayscale();
else if (options.alpha) this.luminanceToAlpha({ gamma: options.gamma });
}
};
/** Vanilla JS geographic inputs
* color, size, width, font, symboles, dash, arrow, pattern
*/
/** Abstract base class; normally only used for creating subclasses and not instantiated in apps.
* @constructor
* @extends {ol.Object}
* @param {*} options
* @param {Element} [options.input] input element, if none create one
* @param {string} [options.type] input type, if no input
* @param {number} [options.min] input min, if no input
* @param {number} [options.max] input max, if no input
* @param {number} [options.step] input step, if no input
* @param {string|number} [options.val] input value
* @param {boolean} [options.checked] check input
* @param {boolean} [options.hidden] the input is display:none
* @param {boolean} [options.disabled] disable input
* @param {Element} [options.parent] parent element, if no input
*/
ol.ext.input.Base = class olextinputBase extends ol.Object {
constructor(options) {
options = options || {};
super();
var input = this.input = options.input;
if (!input) {
input = this.input = document.createElement('INPUT');
if (options.type)
input.setAttribute('type', options.type);
if (options.min !== undefined)
input.setAttribute('min', options.min);
if (options.max !== undefined)
input.setAttribute('max', options.max);
if (options.step !== undefined)
input.setAttribute('step', options.step);
if (options.parent)
options.parent.appendChild(input);
}
if (options.disabled)
input.disabled = true;
if (options.checked !== undefined)
input.checked = !!options.checked;
if (options.val !== undefined)
input.value = options.val;
if (options.hidden)
input.style.display = 'none';
input.addEventListener('focus', function () {
if (this.element)
this.element.classList.add('ol-focus');
}.bind(this));
var tout;
input.addEventListener('focusout', function () {
if (this.element) {
if (tout)
clearTimeout(tout);
tout = setTimeout(function () {
this.element.classList.remove('ol-focus');
}.bind(this), 0);
}
}.bind(this));
}
/** Listen to drag event
* @param {Element} elt
* @param {function} cback when draggin on the element
* @private
*/
_listenDrag(elt, cback) {
var handle = function (e) {
this.moving = true;
this.element.classList.add('ol-moving');
var listen = function (e) {
if (e.type === 'pointerup') {
document.removeEventListener('pointermove', listen);
document.removeEventListener('pointerup', listen);
document.removeEventListener('pointercancel', listen);
setTimeout(function () {
this.moving = false;
this.element.classList.remove('ol-moving');
}.bind(this));
}
if (e.target === elt)
cback(e);
e.stopPropagation();
e.preventDefault();
}.bind(this);
document.addEventListener('pointermove', listen, false);
document.addEventListener('pointerup', listen, false);
document.addEventListener('pointercancel', listen, false);
e.stopPropagation();
e.preventDefault();
}.bind(this);
elt.addEventListener('mousedown', handle, false);
elt.addEventListener('touchstart', handle, false);
}
/** Set the current value
*/
setValue(v) {
if (v !== undefined)
this.input.value = v;
this.input.dispatchEvent(new Event('change'));
}
/** Get the current getValue
* @returns {string}
*/
getValue() {
return this.input.value;
}
/** Get the input element
* @returns {Element}
*/
getInputElement() {
return this.input;
}
}
/** Checkbox input
* @constructor
* @extends {ol.ext.input.Base}
* @param {*} options
* @param {string} [options.className]
* @param {Element} [options.input] input element, if non create one (use parent to tell where)
* @param {Element} [options.parent] element to use as parent if no input option
* @param {booelan} [options.hover=true] show popup on hover
* @param {string} [options.align=left] align popup left/right
* @param {string} [options.type] a slide type as 'size'
* @param {number} [options.min] min value, default use input min
* @param {number} [options.max] max value, default use input max
* @param {number} [options.step] step value, default use input step
* @param {boolean} [options.overflow=false] enable values over min/max
* @param {string|Element} [options.before] an element to add before the slider
* @param {string|Element} [options.after] an element to add after the slider
* @param {boolean} [options.fixed=false] no pupop
*/
ol.ext.input.Slider = class olextinputSlider extends ol.ext.input.Base {
constructor(options) {
options = options || {};
super(options);
this.set('overflow', !!options.overflow);
this.element = ol.ext.element.create('DIV', {
className: 'ol-input-slider'
+ (options.hover !== false ? ' ol-hover' : '')
+ (options.type ? ' ol-' + options.type : '')
+ (options.className ? ' ' + options.className : '')
});
if (options.fixed)
this.element.classList.add('ol-fixed');
var input = this.input;
if (input.parentNode)
input.parentNode.insertBefore(this.element, input);
this.element.appendChild(input);
if (options.align === 'right')
this.element.classList.add('ol-right');
var popup = ol.ext.element.create('DIV', {
className: 'ol-popup',
parent: this.element
});
// Before element
if (options.before) {
ol.ext.element.create('DIV', {
className: 'ol-before',
html: options.before,
parent: popup
});
}
// Slider
var slider = this.slider = ol.ext.element.create('DIV', {
className: 'ol-slider',
parent: popup
});
ol.ext.element.create('DIV', {
className: 'ol-back',
parent: this.slider
});
// Cursor
var cursor = ol.ext.element.create('DIV', {
className: 'ol-cursor',
parent: slider
});
// After element
if (options.after) {
ol.ext.element.create('DIV', {
className: 'ol-after',
html: options.after,
parent: popup
});
}
var min = (options.min !== undefined) ? options.min : parseFloat(input.min) || 0;
var max = (options.max !== undefined) ? options.max : parseFloat(input.max) || 1;
var step = (options.step !== undefined) ? options.step : parseFloat(input.step) || 1;
var dstep = 1 / step;
// Handle popup drag
this._listenDrag(slider, function (e) {
var tx = Math.max(0, Math.min(e.offsetX / slider.clientWidth, 1));
cursor.style.left = Math.max(0, Math.min(100, Math.round(tx * 100))) + '%';
var v = input.value = Math.round((tx * (max - min) + min) * dstep) / dstep;
this.dispatchEvent({ type: 'change:value', value: v });
}.bind(this));
// Set value
var setValue = function () {
var v = parseFloat(input.value) || 0;
if (!this.get('overflow'))
v = Math.max(min, Math.min(max, v));
if (v != input.value)
input.value = v;
var tx = (v - min) / (max - min);
cursor.style.left = Math.max(0, Math.min(100, Math.round(tx * 100))) + '%';
this.dispatchEvent({ type: 'change:value', value: v });
}.bind(this);
input.addEventListener('change', setValue);
setValue();
}
}
/** Base class for input popup
* @constructor
* @extends {ol.ext.input.Base}
* @fires change:color
* @fires color
* @param {*} options
* @param {string} [options.className]
* @param {ol.colorLike} [options.color] default color
* @param {Element} [options.input] input element, if non create one
* @param {Element} [options.parent] parent element, if create an input
* @param {string} [options.position='popup'] fixed | static | popup | inline (no popup)
* @param {boolean} [options.autoClose=true] close when click on color
* @param {boolean} [options.hidden=false] display the input
*/
ol.ext.input.PopupBase = class olextinputPopupBase extends ol.ext.input.Base {
constructor(options) {
options = options || {};
options.hidden = options.hidden !== false;
super(options);
this.set('autoClose', options.autoClose !== false);
this.element = ol.ext.element.create('DIV', {
className: ('ol-ext-popup-input ' + (options.className || '')).trim(),
});
switch (options.position) {
case 'inline': break;
case 'static':
case 'fixed': {
this.element.classList.add('ol-popup');
this.element.classList.add('ol-popup-fixed');
this._fixed = (options.position === 'fixed');
break;
}
default: {
this.element.classList.add('ol-popup');
break;
}
}
var input = this.input;
if (input.parentNode)
input.parentNode.insertBefore(this.element, input);
// Show on element click
this.element.addEventListener('click', function () {
if (this.isCollapsed())
setTimeout(function () { this.collapse(false); }.bind(this));
}.bind(this));
this._elt = {};
// Popup container
this._elt.popup = ol.ext.element.create('DIV', { className: 'ol-popup', parent: this.element });
this._elt.popup.addEventListener('click', function (e) { e.stopPropagation(); });
// Hide on click outside
var down = false;
this._elt.popup.addEventListener('pointerdown', function () {
down = true;
});
this._elt.popup.addEventListener('click', function () {
down = false;
});
document.addEventListener('click', function () {
if (!this.moving && !down)
this.collapse(true);
down = false;
}.bind(this));
// Hide on window resize
window.addEventListener('resize', function () {
this.collapse(true);
}.bind(this));
}
/** show/hide color picker
* @param {boolean} [b=false]
*/
collapse(b) {
if (b != this.isCollapsed()) {
this.dispatchEvent({
type: 'change:visible',
visible: !this.isCollapsed()
});
}
this.dispatchEvent({
type: 'collapse',
visible: !b
});
if (b) {
this._elt.popup.classList.remove('ol-visible');
} else {
this._elt.popup.classList.add('ol-visible');
if (this._fixed) {
// Get fixed position
var pos = this.element.getBoundingClientRect();
var offset = ol.ext.element.getFixedOffset(this.element);
pos = {
bottom: pos.bottom - offset.top,
left: pos.left - offset.left
};
// Test window overflow + recenter
var dh = pos.bottom + this._elt.popup.offsetHeight + offset.top;
if (dh > document.documentElement.clientHeight) {
this._elt.popup.style.top = Math.max(document.documentElement.clientHeight - this._elt.popup.offsetHeight - offset.top, 0) + 'px';
} else {
this._elt.popup.style.top = pos.bottom + 'px';
}
var dw = pos.left + this._elt.popup.offsetWidth + offset.left;
if (dw > document.documentElement.clientWidth) {
this._elt.popup.style.left = Math.max(document.documentElement.clientWidth - this._elt.popup.offsetWidth - offset.left, 0) + 'px';
} else {
this._elt.popup.style.left = pos.left + 'px';
}
}
}
}
/** Is the popup collapsed ?
* @returns {boolean}
*/
isCollapsed() {
return !this._elt.popup.classList.contains('ol-visible');
}
/** Toggle the popup
*/
toggle() {
this.collapse(!this.isCollapsed());
}
}
/** Checkbox input
* @constructor
* @extends {ol.ext.input.Base}
* @fires check
* @param {*} options
* @param {string} [options.className]
* @param {Element|string} [options.html] label content
* @param {string} [options.after] label garnish (placed after)
* @param {Element} [options.input] input element, if non create one
* @param {Element} [options.parent] parent element, if create an input
* @param {boolean} [options.autoClose=true]
* @param {boolean} [options.visible=false] display the input
*/
ol.ext.input.Checkbox = class olextinputCheckbox extends ol.ext.input.Base {
constructor(options) {
options = options || {};
super(options);
var label = this.element = document.createElement('LABEL');
if (options.html instanceof Element)
label.appendChild(options.html);
else if (options.html !== undefined)
label.innerHTML = options.html;
label.className = ('ol-ext-check ol-ext-checkbox ' + (options.className || '')).trim();
if (this.input.parentNode)
this.input.parentNode.insertBefore(label, this.input);
label.appendChild(this.input);
label.appendChild(document.createElement('SPAN'));
if (options.after) {
label.appendChild(document.createTextNode(options.after));
}
// Handle change
this.input.addEventListener('change', function () {
this.dispatchEvent({ type: 'check', checked: this.input.checked, value: this.input.value });
}.bind(this));
}
isChecked() {
return this.input.checked;
}
}
/** A list element synchronize with a Collection.
* Element in the list can be reordered interactively and the associated Collection is kept up to date.
* @constructor
* @fires item:select
* @fires item:dblclick
* @fires item:order
* @extends {ol.Object}
* @param {*} options
* @param {Element} [options.target]
* @param {Collection} [options.collection] the collection to display in the list
* @param {function} [options.getTitle] a function that takes a collection item and returns an Element or a string
*/
ol.ext.input.Collection = class olextinputCollection extends ol.Object {
constructor(options) {
super();
this.element = ol.ext.element.create('UL', {
className: ('ol-collection-list ' + (options.className || '')).trim(),
parent: options.target
});
this._title = (typeof (options.getTitle) === 'function' ? options.getTitle : function (elt) { return elt.title; });
this.setCollection(options.collection);
}
/** Remove current collection (listeners)
* /!\ remove collection when input list is removed from the DOM
*/
removeCollection() {
if (this.collection) {
this.collection.un('change:length', this._update);
this.collection = null;
}
}
/** Set the collection
* @param {ol.Collection} collection
*/
setCollection(collection) {
this.removeCollection();
this.collection = collection;
this.refresh();
if (this.collection) {
this._update = function () {
if (!this._reorder) {
this.refresh();
var pos = this.getSelectPosition();
if (pos < 0) {
this.dispatchEvent({ type: 'item:select', position: -1, item: null });
} else {
this.dispatchEvent({ type: 'item:order', position: pos, item: this._currentItem });
}
}
}.bind(this);
this.collection.on('change:length', this._update);
}
}
/** Select an item
* @param {*} item
*/
select(item) {
if (item === this._currentItem)
return;
var pos = -1;
this._listElt.forEach(function (l, i) {
if (l.item !== item) {
l.li.classList.remove('ol-select');
} else {
l.li.classList.add('ol-select');
pos = i;
}
});
this._currentItem = (pos >= 0 ? item : null);
this.dispatchEvent({ type: 'item:select', position: pos, item: this._currentItem });
}
/** Select an item at
* @param {number} n
*/
selectAt(n) {
this.select(this.collection.item(n));
}
/** Get current selection
* @returns {*}
*/
getSelect() {
return this._currentItem;
}
/** Get current selection
* @returns {number}
*/
getSelectPosition() {
if (!this.collection)
return -1;
return this.collection.getArray().indexOf(this._currentItem);
}
/** Redraw the list
*/
refresh() {
this.element.innerHTML = '';
this._listElt = [];
if (!this.collection)
return;
this.collection.forEach((item, pos) => {
var li = ol.ext.element.create('LI', {
html: this._title(item),
className: this._currentItem === item ? 'ol-select' : '',
'data-position': pos,
on: {
click: function () {
this.select(item);
}.bind(this),
dblclick: function () {
this.dispatchEvent({ type: 'item:dblclick', position: pos, item: item });
}.bind(this),
},
parent: this.element
});
this._listElt.push({ li: li, item: item });
var order = ol.ext.element.create('DIV', {
className: 'ol-noscroll ol-order',
parent: li
});
var current = pos;
var move = function (e) {
// Get target
var target = e.pointerType === 'touch' ? document.elementFromPoint(e.clientX, e.clientY) : e.target;
while (target && target.parentNode !== this.element) {
target = target.parentNode;
}
if (target && target !== li) {
var over = parseInt(target.getAttribute('data-position'));
if (target.getAttribute('data-position') < current) {
target.insertAdjacentElement('beforebegin', li);
current = over;
} else {
target.insertAdjacentElement('afterend', li);
current = over + 1;
}
}
}.bind(this);
var stop = function () {
document.removeEventListener('pointermove', move);
document.removeEventListener('pointerup', stop);
document.removeEventListener('pointercancel', stop);
if (current !== pos) {
this._reorder = true;
this.collection.removeAt(pos);
this.collection.insertAt(current > pos ? current - 1 : current, item);
this._reorder = false;
this.dispatchEvent({ type: 'item:order', position: current > pos ? current - 1 : current, oldPosition: pos, item: item });
this.refresh();
}
}.bind(this);
order.addEventListener('pointerdown', function () {
this.select(item);
document.addEventListener('pointermove', move);
document.addEventListener('pointerup', stop);
document.addEventListener('pointercancel', stop);
}.bind(this));
});
}
}
/** Color picker
* @constructor
* @extends {ol.ext.input.PopupBase}
* @fires color
* @param {*} options
* @param {string} [options.className]
* @param {ol.colorLike} [options.color] default color
* @param {Element} [options.input] input element, if non create one
* @param {Element} [options.parent] parent element, if create an input
* @param {boolean} [options.hastab=false] use tabs for palette / picker
* @param {string} [options.paletteLabel="palette"] label for the palette tab
* @param {string} [options.pickerLabel="picker"] label for the picker tab
* @param {string} [options.position='popup'] fixed | static | popup | inline (no popup)
* @param {boolean} [options.opacity=true] enable opacity
* @param {boolean} [options.autoClose=true] close when click on color
* @param {boolean} [options.hidden=true] display the input
*/
ol.ext.input.Color = class olextinputColor extends ol.ext.input.PopupBase {
constructor(options) {
options = options || {};
options.hidden = options.hidden !== false;
options.className = ('ol-ext-colorpicker ' + (options.hastab ? 'ol-tab ' : '') + (options.className || '')).trim();
super(options);
if (options.opacity === false) {
this.element.classList.add('ol-nopacity');
}
this._cursor = {};
var hsv = this._hsv = {};
// Vignet
this._elt.vignet = ol.ext.element.create('DIV', { className: 'ol-vignet', parent: this.element });
// Bar
var bar = ol.ext.element.create('DIV', { className: 'ol-tabbar', parent: this._elt.popup });
ol.ext.element.create('DIV', {
className: 'ol-tab',
html: options.paletteLabel || 'palette',
click: function () {
this.element.classList.remove('ol-picker-tab');
}.bind(this),
parent: bar
});
ol.ext.element.create('DIV', {
className: 'ol-tab',
html: options.pickerLabel || 'picker',
click: function () {
this.element.classList.add('ol-picker-tab');
}.bind(this),
parent: bar
});
// Popup container
var container = ol.ext.element.create('DIV', { className: 'ol-container', parent: this._elt.popup });
// Color picker
var picker = this._elt.picker = ol.ext.element.create('DIV', { className: 'ol-picker', parent: container });
var pickerCursor = this._cursor.picker = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: picker });
this._listenDrag(picker, function (e) {
var tx = Math.max(0, Math.min(e.offsetX / picker.clientWidth, 1));
var ty = Math.max(0, Math.min(e.offsetY / picker.clientHeight, 1));
pickerCursor.style.left = Math.round(tx * 100) + '%';
pickerCursor.style.top = Math.round(ty * 100) + '%';
hsv.s = tx * 100;
hsv.v = 100 - ty * 100;
this.setColor();
}.bind(this));
// Opacity cursor
var slider = ol.ext.element.create('DIV', { className: 'ol-slider', parent: container });
this._elt.slider = ol.ext.element.create('DIV', { parent: slider });
var sliderCursor = this._cursor.slide = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: slider });
this._listenDrag(slider, function (e) {
var t = Math.max(0, Math.min(e.offsetX / slider.clientWidth, 1));
hsv.a = t * 100;
sliderCursor.style.left = Math.round(t * 100) + '%';
this.setColor();
}.bind(this));
// Tint cursor
var tint = ol.ext.element.create('DIV', { className: 'ol-tint', parent: container });
var tintCursor = this._cursor.tint = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: tint });
this._listenDrag(tint, function (e) {
var t = Math.max(0, Math.min(e.offsetY / tint.clientHeight, 1));
hsv.h = t * 360;
tintCursor.style.top = Math.round(t * 100) + '%';
this.setColor();
}.bind(this));
// Clear button
ol.ext.element.create('DIV', {
className: 'ol-clear',
click: function () {
this.setColor([0, 0, 0, 0]);
}.bind(this),
parent: container
});
// RVB input
var rgb = ol.ext.element.create('DIV', {
className: 'ol-rgb',
parent: container
});
var changergb = function () {
var r = Math.max(0, Math.min(255, parseInt(this._elt.r.value)));
var g = Math.max(0, Math.min(255, parseInt(this._elt.g.value)));
var b = Math.max(0, Math.min(255, parseInt(this._elt.b.value)));
var a = Math.max(0, Math.min(1, parseFloat(this._elt.a.value)));
this.setColor([r, g, b, a]);
}.bind(this);
this._elt.r = ol.ext.element.create('INPUT', { type: 'number', lang: 'en-GB', change: changergb, min: 0, max: 255, parent: rgb });
this._elt.g = ol.ext.element.create('INPUT', { type: 'number', lang: 'en-GB', change: changergb, min: 0, max: 255, parent: rgb });
this._elt.b = ol.ext.element.create('INPUT', { type: 'number', lang: 'en-GB', change: changergb, min: 0, max: 255, parent: rgb });
this._elt.a = ol.ext.element.create('INPUT', { type: 'number', lang: 'en-GB', change: changergb, min: 0, max: 1, step: .1, parent: rgb });
// Text color input
this._elt.txtColor = ol.ext.element.create('INPUT', {
type: 'text',
className: 'ol-txt-color',
change: function () {
var color;
this._elt.txtColor.classList.remove('ol-error');
try {
color = ol.color.asArray(this._elt.txtColor.value);
} catch (e) {
this._elt.txtColor.classList.add('ol-error');
}
if (color)
this.setColor(color);
}.bind(this),
parent: container
});
ol.ext.element.create('BUTTON', {
html: 'OK',
click: function () {
this._addCustomColor(this.getColor());
this.collapse(true);
}.bind(this),
parent: container
});
var i;
// Color palette
this._paletteColor = {};
this._elt.palette = ol.ext.element.create('DIV', {
className: 'ol-palette',
parent: this._elt.popup
});
for (i = 0; i < 8; i++) {
var c = Math.round(255 - 255 * i / 7);
this.addPaletteColor([c, c, c], c); //ol.color.toHexa([c,c,c]));
}
var colors = ['#f00', '#f90', '#ff0', '#0f0', '#0ff', '#48e', '#00f', '#f0f'];
colors.forEach(function (c) {
this.addPaletteColor(c, ol.color.toHexa(ol.color.asArray(c)));
}.bind(this));
for (i = 0; i < 5; i++) {
colors.forEach(function (c) {
c = ol.color.toHSV(ol.color.asArray(c));
c = [c[0], i / 4 * 80 + 20, 100 - i / 4 * 60];
c = ol.color.fromHSV(c, 1);
this.addPaletteColor(c, ol.color.toHexa(c));
}.bind(this));
}
// Custom colors
ol.ext.element.create('HR', { parent: this._elt.palette });
// Create custom color list
if (!ol.ext.input.Color.customColorList) {
ol.ext.input.Color.customColorList = new ol.Collection();
var ccolor = JSON.parse(localStorage.getItem('ol-ext@colorpicker') || '[]');
ccolor.forEach(function (c) {
ol.ext.input.Color.customColorList.push(c);
});
ol.ext.input.Color.customColorList.on(['add', 'remove'], function () {
localStorage.setItem('ol-ext@colorpicker', JSON.stringify(ol.ext.input.Color.customColorList.getArray()));
});
}
// Handle custom color
ol.ext.input.Color.customColorList.on('add', function (e) {
this.addPaletteColor(this.getColorFromID(e.element));
}.bind(this));
ol.ext.input.Color.customColorList.on('remove', function (e) {
if (this._paletteColor[e.element])
this._paletteColor[e.element].element.remove();
delete this._paletteColor[e.element];
}.bind(this));
// Add new one
ol.ext.input.Color.customColorList.forEach(function (c) {
this._addCustomColor(this.getColorFromID(c));
}.bind(this));
// Current color
this.setColor(options.color || [0, 0, 0, 0]);
this._currentColor = this.getColorID(this.getColor());
// Add new palette color
this.on('color', function () {
this._addCustomColor(this.getColor());
this._currentColor = this.getColorID(this.getColor());
this.setColor();
}.bind(this));
// Update color on hide
this.on('collapse', function (e) {
if (!e.visible) {
var c = this.getColor();
if (this._currentColor !== this.getColorID(c)) {
this.dispatchEvent({ type: 'color', color: c });
}
} else {
this._currentColor = this.getColorID(this.getColor());
}
}.bind(this));
}
/** Add color to palette
* @param {ol.colorLike} color
* @param {string} title
* @param {boolean} select
*/
addPaletteColor(color, title, select) {
// Get color id
try {
color = ol.color.asArray(color);
} catch (e) {
return;
}
var id = this.getColorID(color);
// Add new one
if (!this._paletteColor[id] && color[3]) {
this._paletteColor[id] = {
color: color,
element: ol.ext.element.create('DIV', {
title: title || '',
className: (color[3] < 1 ? 'ol-alpha' : ''),
style: {
color: 'rgb(' + (color.join(',')) + ')'
},
click: function () {
this.setColor(color);
if (this.get('autoClose'))
this.collapse(true);
}.bind(this),
parent: this._elt.palette
})
};
}
if (select) {
this._selectPalette(color);
}
}
/** Show palette or picker tab
* @param {string} what palette or picker
*/
showTab(what) {
if (what === 'palette')
this.element.classList.remove('ol-picker-tab');
else
this.element.classList.add('ol-picker-tab');
}
/** Show palette or picker tab
* @returns {string} palette or picker
*/
getTab() {
return this.element.classList.contains('ol-picker-tab') ? 'picker' : 'palette';
}
/** Select a color in the palette
* @private
*/
_selectPalette(color) {
var id = this.getColorID(color);
Object.keys(this._paletteColor).forEach(function (c) {
this._paletteColor[c].element.classList.remove('ol-select');
}.bind(this));
if (this._paletteColor[id]) {
this._paletteColor[id].element.classList.add('ol-select');
}
}
/** Set Color
* @param { Array }
*/
setColor(color) {
var hsv = this._hsv;
if (color) {
color = ol.color.asArray(color);
var hsv2 = ol.color.toHSV(color);
hsv.h = hsv2[0];
hsv.s = hsv2[1];
hsv.v = hsv2[2];
if (hsv2.length > 3)
hsv.a = hsv2[3] * 100;
else
hsv.a = 100;
this._cursor.picker.style.left = hsv.s + '%';
this._cursor.picker.style.top = (100 - hsv.v) + '%';
this._cursor.tint.style.top = (hsv.h / 360 * 100) + '%';
this._cursor.slide.style.left = hsv.a + '%';
if (this.isCollapsed()) {
this.dispatchEvent({ type: 'color', color: color });
}
} else {
/*
hsv.h = Math.round(hsv.h) % 360;
hsv.s = Math.round(hsv.s);
hsv.v = Math.round(hsv.v);
*/
hsv.a = Math.round(hsv.a);
color = this.getColor();
}
var val = 'rgba(' + color.join(', ') + ')';
// Show color
this._elt.picker.style.color = 'hsl(' + hsv.h + ', 100%, 50%)';
this._elt.slider.style.backgroundImage = 'linear-gradient(45deg, transparent, rgba(' + this.getColor(false).join(',') + '))';
this._elt.vignet.style.color = val;
// RGB
this._elt.r.value = color[0];
this._elt.g.value = color[1];
this._elt.b.value = color[2];
this._elt.a.value = color[3];
// Txt color
this._elt.txtColor.classList.remove('ol-error');
if (color[3] === 1) {
this._elt.txtColor.value = ol.color.toHexa(color);
} else {
this._elt.txtColor.value = val;
}
this._selectPalette(color);
// Set input value
if (this.input.value !== val) {
this.input.value = val;
this.input.dispatchEvent(new Event('change'));
}
}
/** Get current color
* @param {boolean} [opacity=true]
* @return {Array}
*/
getColor(opacity) {
return ol.color.fromHSV([this._hsv.h, this._hsv.s, this._hsv.v, (opacity !== false) ? this._hsv.a / 100 : 1], 1);
}
/**
* @private
*/
_addCustomColor(color) {
var id = this.getColorID(color);
if (this._paletteColor[id])
return;
if (!color[3])
return;
if (ol.ext.input.Color.customColorList.getArray().indexOf(id) < 0) {
ol.ext.input.Color.customColorList.push(id);
if (ol.ext.input.Color.customColorList.getLength() > 24) {
ol.ext.input.Color.customColorList.removeAt(0);
}
}
this.addPaletteColor(color);
}
clearCustomColor() {
ol.ext.input.Color.customColorList.clear();
}
/** Convert color to id
* @param {ol.colorLike} Color
* @returns {number}
*/
getColorID(color) {
color = ol.color.asArray(color);
if (color[3] === undefined)
color[3] = 1;
return color.join('-');
}
/** Convert color to id
* @param {number} id
* @returns {Array} Color
*/
getColorFromID(id) {
var c = id.split('-');
return ([parseFloat(c[0]), parseFloat(c[1]), parseFloat(c[2]), parseFloat(c[3])]);
}
}
/** Custom color list
* @private
*/
ol.ext.input.Color.customColorList = null;
/** Checkbox input
* @constructor
* @extends {ol.ext.input.Base}
* @param {*} options
* @param {string} [options.className]
* @param {Array