ol-ext
Version:
A set of cool extensions for OpenLayers (ol) in node modules structure
1,538 lines (1,527 loc) • 1.61 MB
JavaScript
/**
* 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.34
* @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<ol.ext.SVGOperation>} 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<lines.length; i++) {
if (getLine(lines[i]) === false) {
// Stop condition
if (progress) progress(this.chunk_ / this.fileSize_, true);
return;
}
}
if (progress) progress(this.chunk_ / this.fileSize_, false);
// Red next chunk
if (!this.nexChunk_() && progress) {
// EOF
progress(1, true);
}
}.bind(this), progress);
};
/** Read a set of line chunk from the stream
* @param {function} getLines a function that gets lines read as an Array<String>.
* @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<viewTourDestinations>|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<number>} 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<number>} hsl as h:[0,360], s:[0,100], l:[0,100]
* @param {number} [round=1000]
* @returns {Array<number>} 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<number>} 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<number>} hsl as h:[0,360], s:[0,100], l:[0,100]
* @param {number} [round=1000]
* @returns {Array<number>} 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.toLowerCase());
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<string>} 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<string>} 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);
};
/** Set cursor
* @param {Element|ol/Map} elt
* @param {string} cursor
*/
ol.ext.element.setCursor = function(elt, cursor) {
if (elt instanceof ol.Map) elt = elt.getTargetElement()
// prevent flashing on mobile device
if (!('ontouchstart' in window) && elt instanceof Element) {
elt.style.cursor = cursor;
}
}
/** 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
&& ol.ext.olVersion < 700
&& 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 (anchor && 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<ol.color.Color>} 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]) {
pixel