ol-ext
Version:
A set of cool extensions for OpenLayers (ol) in node modules structure
1,603 lines (1,590 loc) • 1.2 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 v3.1.19
* @author Jean-Marc Viglino
* @see https://github.com/Viglino/ol-ext#,
* @license BSD-3-Clause
*/
/** @namespace ol.ext
*/
/*global ol*/
if (window.ol && !ol.ext) {
ol.ext = {};
}
/** 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 = function(options) {
options = options || {};
ol.Object.call(this);
this._auth = options.auth;
this.set('dataType', options.dataType || 'JSON');
};
ol.ext.inherits(ol.ext.Ajax, ol.Object);
/** 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
*/
ol.ext.Ajax.get = function(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
*/
ol.ext.Ajax.getCORS = function(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
*/
ol.ext.Ajax.prototype.send = function (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 = function(options) {
options = options || {};
ol.Object.call(this);
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 );
};
ol.ext.inherits(ol.ext.SVGFilter, ol.Object);
ol.ext.SVGFilter.prototype.NS = "http://www.w3.org/2000/svg";
ol.ext.SVGFilter.prototype.svg = null;
ol.ext.SVGFilter.prototype._id = 0;
/** Get filter ID
* @return {string}
*/
ol.ext.SVGFilter.prototype.getId = function() {
return this._id;
};
/** Remove from DOM
*/
ol.ext.SVGFilter.prototype.remove = function() {
this.element.remove();
};
/** Add a new operation
* @param {ol.ext.SVGOperation} operation
*/
ol.ext.SVGFilter.prototype.addOperation = function(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
*/
ol.ext.SVGFilter.prototype.grayscale = function(value) {
this.addOperation({
feoperation: 'feColorMatrix',
type: 'saturate',
values: value || 0
});
};
/** Add a luminanceToAlpha operation
* @param {*} options
* @param {number} options.gamma enhance gamma, default 0
*/
ol.ext.SVGFilter.prototype.luminanceToAlpha = function(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
}]
});
}
};
ol.ext.SVGFilter.prototype.applyTo = function(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;
};
/** SVG filter
* @param {string | *} attributes a list of attributes or fe operation
* @param {string} attributes.feoperation filter primitive tag name
*/
ol.ext.SVGOperation = function(attributes) {
if (typeof(attributes)==='string') attributes = { feoperation: attributes };
if (!attributes || !attributes.feoperation) {
console.error('[SVGOperation]: no operation defined.')
return;
}
ol.Object.call(this);
this._name = attributes.feoperation;
this.element = document.createElementNS( this.NS, this._name );
this.setProperties(attributes);
if (attributes.operations instanceof Array) this.appendChild(attributes.operations);
};
ol.ext.inherits(ol.ext.SVGOperation, ol.Object);
ol.ext.SVGOperation.prototype.NS = "http://www.w3.org/2000/svg";
/** Get filter name
* @return {string}
*/
ol.ext.SVGOperation.prototype.getName = function() {
return this._name;
};
/** Set Filter attribute
* @param {*} attributes
*/
ol.ext.SVGOperation.prototype.set = function(k, val) {
if (!/^feoperation$|^operations$/.test(k)) {
ol.Object.prototype.set.call(this, k, val);
this.element.setAttribute( k, val );
}
};
/** Set Filter attributes
* @param {*} attributes
*/
ol.ext.SVGOperation.prototype.setProperties = function(attributes) {
attributes = attributes || {};
for (var k in attributes) {
this.set(k, attributes[k])
}
};
/** Get SVG element
* @return {Element}
*/
ol.ext.SVGOperation.prototype.geElement = function() {
return this.element;
};
/** Append a new operation
* @param {ol.ext.SVGOperation} operation
*/
ol.ext.SVGOperation.prototype.appendChild = function(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() );
}
};
/** 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/
*/
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
* @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 '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 'style': {
this.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;
};
/** 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) {
if (typeof eventType === 'string') eventType = eventType.split(' ');
eventType.forEach(function(e) {
element.addEventListener(e, fn);
});
};
/**
* 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.widtth || (rect.right - rect.left)
}
};
/** Make a div scrollable without scrollbar.
* On touch devices the default behavior is preserved
* @param {DOMElement} elt
* @param {function} onmove a function that takes a boolean indicating that the div is scrolling
*/
ol.ext.element.scrollDiv = function(elt, 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 scroll = options.vertical ? 'scrollTop' : 'scrollLeft';
var moving = false;
// Prevent image dragging
elt.querySelectorAll('img').forEach(function(i) {
i.ondragstart = function(){ return false; };
});
// Start scrolling
ol.ext.element.addListener(elt, ['mousedown'], function(e) {
moving = false;
pos = e[page];
dt = new Date();
elt.classList.add('ol-move');
});
// Register scroll
ol.ext.element.addListener(window, ['mousemove'], function(e) {
moving = true;
if (pos !== false) {
var delta = pos - e[page];
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 {
// Not moving yet
onmove(false);
}
});
// Stop scrolling
ol.ext.element.addListener(window, ['mouseup'], function(e) {
if (moving) setTimeout (function() { elt.classList.remove('ol-move'); });
else elt.classList.remove('ol-move');
moving = false;
dt = new Date() - dt;
if (dt>100) {
// User stop: no speed
speed = 0;
} else if (dt>0) {
// Calculate new speed
speed = ((speed||0) + (pos - e[page]) / dt) / 2;
}
elt[scroll] += speed*100;
pos = false;
speed = 0;
dt = 0;
});
// Handle mousewheel
if (options.mousewheel && !elt.classList.contains('ol-touch')) {
ol.ext.element.addListener(elt,
['mousewheel', 'DOMMouseScroll', '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;
}
);
}
};
/** 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;
};
/* 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<Array<number>>}
*/
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<number>}
*/
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<Array<number>>}
*/
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<Array<number>>}
*/
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<Array<number>>} 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<Array<number>>} mx1
* @param {Array<Array<number>>} mx2
* @return {Array<Array<number>>}
*/
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<Array<number>>} tx transform matrix
* @param {Array<Array<number>>} origin transform origin
* @return {Array<Array<number>>}
*/
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<Array<number>>} 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<number>} vert
* @return {Array<number>}
*/
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<Array<number>>} m matrix to transform
* @return {Array<Array<number>>}
*/
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 = function(options) {
options = options || {};
ol.ext.SVGFilter.call(this, { 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 });
};
ol.ext.inherits(ol.ext.SVGFilter.Laplacian, ol.ext.SVGFilter);
/* 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 = function(options) {
options = options || {};
ol.ext.SVGFilter.call(this, { 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();
};
ol.ext.inherits(ol.ext.SVGFilter.Prewitt, ol.ext.SVGFilter);
/* 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 = function(options) {
options = options || {};
ol.ext.SVGFilter.call(this, { 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();
};
ol.ext.inherits(ol.ext.SVGFilter.Roberts, ol.ext.SVGFilter);
/* 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 = function(options) {
options = options || {};
ol.ext.SVGFilter.call(this, { 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 });
};
ol.ext.inherits(ol.ext.SVGFilter.Sobel, ol.ext.SVGFilter);
/**
* @classdesc
* Attribution Control integrated in the canvas (for jpeg/png
* @see http://www.kreidefossilien.de/webgis/dokumentation/beispiele/export-map-to-png-with-scale
*
* @constructor
* @extends {ol.control.Control}
* @param {Object=} options extend the ol.control options.
* @param {ol.style.Style} options.style style used to draw the title.
*/
ol.control.CanvasBase = function(options) {
if (!options) options = {};
// Define a style to draw on the canvas
this.setStyle(options.style);
ol.control.Control.call(this, options);
}
ol.ext.inherits(ol.control.CanvasBase, ol.control.Control);
/**
* Remove the control from its current map and attach it to the new map.
* Subclasses may set up event handlers to get notified about changes to
* the map here.
* @param {import('ol/Map')} map Map.
* @api stable
*/
ol.control.CanvasBase.prototype.setMap = function (map) {
this.getCanvas(map);
var oldmap = this.getMap();
if (this._listener) {
ol.Observable.unByKey(this._listener);
this._listener = null;
}
ol.control.Control.prototype.setMap.call(this, map);
if (oldmap) oldmap.renderSync();
if (map) {
this._listener = map.on('postcompose', this._draw.bind(this));
// Get a canvas layer on top of the map
}
};
/** Get canvas overlay
*/
ol.control.CanvasBase.prototype.getCanvas = function(map) {
return ol.ext.getMapCanvas(map);
};
/** Get map Canvas
* @private
*/
ol.control.CanvasBase.prototype.getContext = function(e) {
var ctx = e.context;
if (!ctx && this.getMap()) {
var c = this.getMap().getViewport().getElementsByClassName('ol-fixedoverlay')[0];
ctx = c ? c.getContext('2d') : null;
}
return ctx;
};
/** Set Style
* @api
*/
ol.control.CanvasBase.prototype.setStyle = function(style) {
this._style = style || new ol.style.Style ({});
};
/** Get style
* @api
*/
ol.control.CanvasBase.prototype.getStyle = function() {
return this._style;
};
/** Get stroke
* @api
*/
ol.control.CanvasBase.prototype.getStroke = function() {
var t = this._style.getStroke();
if (!t) this._style.setStroke(new ol.style.Stroke ({ color:'#000', width:1.25 }));
return this._style.getStroke();
};
/** Get fill
* @api
*/
ol.control.CanvasBase.prototype.getFill = function() {
var t = this._style.getFill();
if (!t) this._style.setFill(new ol.style.Fill ({ color:'#fff' }));
return this._style.getFill();
};
/** Get stroke
* @api
*/
ol.control.CanvasBase.prototype.getTextStroke = function() {
var t = this._style.getText();
if (!t) t = new ol.style.Text({});
if (!t.getStroke()) t.setStroke(new ol.style.Stroke ({ color:'#fff', width:3 }));
return t.getStroke();
};
/** Get text fill
* @api
*/
ol.control.CanvasBase.prototype.getTextFill = function() {
var t = this._style.getText();
if (!t) t = new ol.style.Text({});
if (!t.getFill()) t.setFill(new ol.style.Fill ({ color:'#fff' }));
return t.getFill();
};
/** Get text font
* @api
*/
ol.control.CanvasBase.prototype.getTextFont = function() {
var t = this._style.getText();
if (!t) t = new ol.style.Text({});
if (!t.getFont()) t.setFont('12px sans-serif');
return t.getFont();
};
/** Draw the control on canvas
* @protected
*/
ol.control.CanvasBase.prototype._draw = function(/* e */) {
console.warn('[CanvasBase] draw function not implemented.');
};
/* Copyright (c) 2019 Jean-Marc VIGLINO,
released under the CeCILL-B license (French BSD license)
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
/**
* This is the base class for Select controls on attributes values.
* Abstract base class;
* normally only used for creating subclasses and not instantiated in apps.
*
* @constructor
* @extends {ol.control.Control}
* @fires select
* @param {Object=} options
* @param {string} options.className control class name
* @param {Element | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport.
* @param {ol.Collection<ol.Feature>} options.features a collection of feature to search in, the collection will be kept in date while selection
* @param {ol.source.Vector | Array<ol.source.Vector>} options.source the source to search in if no features set
*/
ol.control.SelectBase = function(options) {
if (!options) options = {};
this._features = this.setFeatures(options.features);
var element;
if (options.target) {
element = document.createElement("div");
} else {
element = document.createElement("div");
element.className = 'ol-select ol-unselectable ol-control ol-collapsed';
ol.ext.element.create('BUTTON', {
type: 'button',
on: {
'click touchstart': function(e) {
element.classList.toggle('ol-collapsed');
e.preventDefault();
}
},
parent: element
});
}
if (options.className) element.classList.add(options.className);
element.appendChild(options.content);
// OK button
ol.ext.element.create('BUTTON', {
html: options.btInfo || 'OK',
className: 'ol-ok',
on: { 'click touchstart': this.doSelect.bind(this) },
parent: options.content
});
ol.control.Control.call(this, {
element: element,
target: options.target
});
this.setSources(options.source);
};
ol.ext.inherits(ol.control.SelectBase, ol.control.Control);
/** Set the current sources
* @param {ol.source.Vector|Array<ol.source.Vector>|undefined} source
*/
ol.control.SelectBase.prototype.setSources = function (source) {
if (source) {
this.set ('source', (source instanceof Array) ? source : [source]);
} else {
this.unset('source');
}
};
/** Set feature collection to search in
* @param {ol.Collection<ol.Feature>} features
*/
ol.control.SelectBase.prototype.setFeatures = function (features) {
if (features instanceof ol.Collection) this._features = features;
else this._features = null;
};
/** Get feature collection to search in
* @return {ol.Collection<ol.Feature>}
*/
ol.control.SelectBase.prototype.getFeatures = function () {
return this._features;
};
/** List of operators / translation
* @api
*/
ol.control.SelectBase.prototype.operationsList = {
'=': '=',
'!=': '≠',
'<': '<',
'<=': '≤',
'>=': '≥',
'>': '>',
'contain': '⊂', // ∈
'!contain': '⊄', // ∉
'regexp': '≃',
'!regexp': '≄'
};
/** Escape string for regexp
* @param {string} search
* @return {string}
*/
ol.control.SelectBase.prototype._escape = function (s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
};
/**
* Test if a feature check aconditino
* @param {ol.Feature} f the feature to check condition
* @param {Object} condition an object to use for test
* @param {string} condition.attr attribute name
* @param {string} condition.op operator
* @param {any} condition.val value to test
* @param {boolean} usecase use case or not when testing strings
* @return {boolean}
* @private
*/
ol.control.SelectBase.prototype._checkCondition = function (f, condition, usecase) {
if (!condition.attr) return true;
var val = f.get(condition.attr);
var rex;
switch (condition.op) {
case '=':
rex = new RegExp('^'+this._escape(condition.val)+'$', usecase ? '' : 'i');
return rex.test(val);
case '!=':
rex = new RegExp('^'+this._escape(condition.val)+'$', usecase ? '' : 'i');
return !rex.test(val);
case '<':
return val < condition.val;
case '<=':
return val <= condition.val;
case '>':
return val > condition.val;
case '>=':
return val >= condition.val;
case 'contain':
rex = new RegExp(this._escape(condition.val), usecase ? '' : 'i');
return rex.test(val);
case '!contain':
rex = new RegExp(this._escape(condition.val), usecase ? '' : 'i');
return !rex.test(val);
case 'regexp':
rex = new RegExp(condition.val, usecase ? '' : 'i');
return rex.test(val);
case '!regexp':
rex = new RegExp(condition.val, usecase ? '' : 'i');
return !rex.test(val);
default:
return false;
}
};
/** Selection features in a list of features
* @param {Array<ol.Feature>} result the current list of features
* @param {Array<ol.Feature>} features to test in
* @param {Object} condition
* @param {string} condition.attr attribute name
* @param {string} condition.op operator
* @param {any} condition.val value to test
* @param {boolean} all all conditions must be valid
* @param {boolean} usecase use case or not when testing strings
*/
ol.control.SelectBase.prototype._selectFeatures = function (result, features, conditions, all, usecase) {
conditions = conditions || [];
var f;
for (var i=features.length-1; f=features[i]; i--) {
var isok = all;
for (var k=0, c; c=conditions[k]; k++) {
if (c.attr) {
if (all) {
isok = isok && this._checkCondition(f,c,usecase);
}
else {
isok = isok || this._checkCondition(f,c,usecase);
}
}
}
if (isok) {
result.push(f);
} else if (this._features) {
this._features.removeAt(i);
}
}
return result;
};
/** Get vector source
* @return {Array<ol.source.Vector>}
*/
ol.control.SelectBase.prototype.getSources = function () {
if (this.get('source')) return this.get('source');
var sources = [];
function getSources(layers) {
layers.forEach(function(l){
if (l.getLayers) {
getSources(l.getLayers());
} else if (l.getSource && l.getSource() instanceof ol.source.Vector) {
sources.push(l.getSource());
}
});
}
if (this.getMap()) {
getSources(this.getMap().getLayers());
}
return sources;
};
/** Select features by attributes
* @param {*} options
* @param {Array<ol.source.Vector>|undefined} options.sources source to apply rules, default the select sources
* @param {bool} options.useCase case sensitive, default false
* @param {bool} options.matchAll match all conditions, default false
* @param {Array<conditions>} options.conditions array of conditions
* @return {Array<ol.Feature>}
* @fires select
*/
ol.control.SelectBase.prototype.doSelect = function (options) {
options = options || {};
var features = [];
if (options.features) {
this._selectFeatures(features, options.features, options.conditions, options.matchAll, options.useCase);
} else if (this._features) {
this._selectFeatures(features, this._features.getArray(), options.conditions, options.matchAll, options.useCase);
} else {
var sources = options.sources || this.getSources();
sources.forEach(function(s) {
this._selectFeatures(features, s.getFeatures(), options.conditions, options.matchAll, options.useCase);
}.bind(this));
}
this.dispatchEvent({ type:"select", features: features });
return features;
};
/* 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 push button control
* @constructor
* @extends {ol.control.Control}
* @param {Object=} options Control options.
* @param {String} options.className class of the control
* @param {String} options.title title of the control
* @param {String} options.name an optional name, default none
* @param {String} options.html html to insert in the control
* @param {function} options.handleClick callback when control is clicked (or use change:active event)
*/
ol.control.Button = function(options){
options = options || {};
var element = document.createElement("div");
element.className = (options.className || '') + " ol-button ol-unselectable ol-control";
var self = this;
var bt = this.button_ = document.createElement(/ol-text-button/.test(options.className) ? "div": "button");
bt.type = "button";
if (options.title) bt.title = options.title;
if (options.html instanceof Element) bt.appendChild(options.html)
else bt.innerHTML = options.html || "";
var evtFunction = function(e) {
if (e && e.preventDefault) {
e.preventDefault();
e.stopPropagation();
}
if (options.handleClick) {
options.handleClick.call(self, e);
}
};
bt.addEventListener("click", evtFunction);
bt.addEventListener("touchstart", evtFunction);
element.appendChild(bt);
// Try to get a title in the button content
if (!options.title && bt.firstElementChild) {
bt.title = bt.firstElementChild.title;
}
ol.control.Control.call(this, {
element: element,
target: options.target
});
if (options.title) {
this.set("title", options.title);
}
if (options.title) this.set("title", options.title);
if (options.name) this.set("name", options.name);
};
ol.ext.inherits(ol.control.Button, ol.control.Control);
/** Set the control visibility
* @param {boolean} b
*/
ol.control.Button.prototype.setVisible = function (val) {
if (val) ol.ext.element.show(this.element);
else ol.ext.element.hide(this.element);
};
/**
* Set the button title
* @param {string} title
*/
ol.control.Button.prototype.setTitle = function(title) {
this.button_.setAttribute('title', title);
};
/**
* Set the button html
* @param {string} html
*/
ol.control.Bu