UNPKG

ol-ext

Version:

A set of cool extensions for OpenLayers (ol) in node modules structure

1,603 lines (1,590 loc) 1.2 MB
/** * 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