/** * 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.2.22 * @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() ); } }; // Prevent overwrite if (ol.View.prototype.flyTo) { console.warn('[OL-EXT] ol/View~View.flyTo redefinition') } /** Destination * @typedef {Object} viewTourDestinations * @property {string} [type=flyto] animation type (flyTo, moveTo), default flyTo * @property {number} [duration=2000] animation duration * @property {ol.coordinate} [center=] destination coordinate, default current center * @property {number} [zoom] destination zoom, default current zoom * @property {number} [zoomAt=-2] zoom to fly to, default min (current zoom, zoom) -2 * @property {function} [easing] easing function used during the animation, defaults ol/easing~inAndOut * @property {number} [rotation] The rotation of the view at the end of the animation * @property {anchor} [anchor] Optional anchor to remain fixed during a rotation or resolution animation. */ /** FlyTo animation * @param {viewTourDestinations} options * @param {function} done callback function called at the end of an animation, called with true if the animation completed */ ol.View.prototype.flyTo = function(options, done) { options = options || {}; // Start new anim this.cancelAnimations(); var callback = (typeof(done) === 'function' ? done : function(){}); // Fly to destination var duration = options.duration || 2000; var zoomAt = options.zoomAt || (Math.min(options.zoom||100, this.getZoom())-2); var zoomTo = options.zoom || this.getZoom(); var coord = options.center || this.getCenter(); // Move to this.animate ({ center: coord, duration: duration, easing: options.easing, anchor: options.anchor, rotation: options.rotation }); // Zoom to this.animate ({ zoom: zoomAt, duration: duration/2, easing: options.easing, anchor: options.anchor },{ zoom: zoomTo, duration: duration/2, easing: options.easing, anchor: options.anchor }, callback); }; /** Start a tour on the map * @param {Array|Array} destinations an array of destinations or an array of [x,y,zoom,destinationType] * @param {Object} options * @param {number} [options.delay=750] delay between 2 destination * @param {string} [options.type] animation type (flyTo, moveTo) to use if not defined in destinations * @param {function} [options.easing] easing function used during the animation if not defined in destinations * @param {function} [options.done] callback function called at the end of an animation, called with true if the tour completed * @param {function} [options.step] callback function called when a destination is reached with the step index as param */ ol.View.prototype.takeTour = function(destinations, options) { options = options || {}; var index = -1; var next = function(more) { if (more) { var dest = destinations[++index]; if (typeof(options.step) === 'function') options.step(index, destinations); if (dest) { if (dest instanceof Array) dest = { center: [dest[0],dest[1]], zoom: dest[2], type: dest[3] }; var delay = index === 0 ? 0 : (options.delay || 750); if (!dest.easing) dest.easing = options.easing; if (!dest.type) dest.type = options.type; setTimeout(function () { switch(dest.type) { case 'moveTo': { this.animate(dest, next); break; } case 'flightTo': default: { this.flyTo(dest, next); break; } } }.bind(this), delay); } else { if (typeof(options.done)==='function') options.done(true); } } else { if (typeof(options.done)==='function') options.done(false); } }.bind(this) next(true); }; /** Converts an RGB color value to HSL. * returns hsl as array h:[0,360], s:[0,100], l:[0,100] * @param {ol/color~Color|string} rgb * @param {number} [round=100] * @returns {Array} hsl as h:[0,360], s:[0,100], l:[0,100] */ ol.color.toHSL = function(rgb, round) { if (round===undefined) round = 100; if (!Array.isArray(rgb)) rgb = ol.color.asArray(rgb); var r = rgb[0] / 255; var g = rgb[1] / 255; var b = rgb[2] / 255; var max = Math.max(r, g, b); var min = Math.min(r, g, b); var h, s, l = (max + min) / 2; if (max === min) { h = s = 0; // achromatic } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } } var hsl = [ Math.round(h*60*round)/round, Math.round(s*100*round)/round, Math.round(l*100*round)/round ]; if (rgb.length>3) hsl[3] = rgb[3]; return hsl; } /** Converts an HSL color value to RGB. * @param {Array} hsl as h:[0,360], s:[0,100], l:[0,100] * @param {number} [round=1000] * @returns {Array} rgb */ ol.color.fromHSL = function(hsl, round) { if (round===undefined) round = 1000 var h = hsl[0] / 360; var s = hsl[1] / 100; var l = hsl[2] / 100; var r, g, b; if (s == 0) { r = g = b = l; // achromatic } else { var hue2rgb = function(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } var rgb = [ Math.round(r * 255*round) / round, Math.round(g * 255*round) / round, Math.round(b * 255*round) / round ]; if (hsl.length>3) rgb[3] = hsl[3]; return rgb; } /** Converts an HSL color value to RGB. * @param {ol/color~Color|string} rgb * @param {number} [round=100] * @returns {Array} hsl as h:[0,360], s:[0,100], l:[0,100] */ ol.color.toHSV = function(rgb, round) { if (round===undefined) round = 100; if (!Array.isArray(rgb)) rgb = ol.color.asArray(rgb); var r = rgb[0] / 255; var g = rgb[1] / 255; var b = rgb[2] / 255; var max = Math.max(r, g, b); var min = Math.min(r, g, b); var h, s, v = max; var d = max - min; s = max == 0 ? 0 : d / max; if (max == min) { h = 0; // achromatic } else { switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } } var hsv = [ Math.round(h*60*round)/round, Math.round(s*100*round)/round, Math.round(v*100*round)/round ]; if (rgb.length>3) hsv[3] = rgb[3]; return hsv; } /** Converts an HSV color value to RGB. * @param {Array} hsl as h:[0,360], s:[0,100], l:[0,100] * @param {number} [round=1000] * @returns {Array} rgb */ ol.color.fromHSV = function(hsv, round) { if (round===undefined) round = 1000 var h = hsv[0] / 360; var s = hsv[1] / 100; var v = hsv[2] / 100; var r, g, b; var i = Math.floor(h * 6); var f = h * 6 - i; var p = v * (1 - s); var q = v * (1 - f * s); var t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; } var rgb = [ Math.round(r * 255*round) / round, Math.round(g * 255*round) / round, Math.round(b * 255*round) / round ]; if (hsv.length>3) rgb[3] = hsv[3]; return rgb; } /** Converts an HSL color value to RGB. * @param {ol/color~Color|string} rgb * @returns {string} */ ol.color.toHexa = function(rgb) { return '#' + ((1 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).slice(1); } /** Vanilla JS helper to manipulate DOM without jQuery * @see https://github.com/nefe/You-Dont-Need-jQuery * @see https://plainjs.com/javascript/ * @see http://youmightnotneedjquery.com/ */ /** @namespace ol.ext.element */ ol.ext.element = {}; /** * Create an element * @param {string} tagName The element tag, use 'TEXT' to create a text node * @param {*} options * @param {string} options.className className The element class name * @param {Element} options.parent Parent to append the element as child * @param {Element|string} options.html Content of the element * @param {Element|string} [options.options] when tagName = SELECT a list of options as key:value to add to the select * @param {string} options.* Any other attribut to add to the element */ ol.ext.element.create = function (tagName, options) { options = options || {}; var elt; // Create text node if (tagName === 'TEXT') { elt = document.createTextNode(options.html||''); if (options.parent) options.parent.appendChild(elt); } else { // Other element elt = document.createElement(tagName); if (/button/i.test(tagName)) elt.setAttribute('type', 'button'); for (var attr in options) { switch (attr) { case 'className': { if (options.className && options.className.trim) elt.setAttribute('class', options.className.trim()); break; } case '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': { 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; }; /** 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 }); console.log(input) var opt = Object.assign ({ input: input }, options || {}); if (options.type === 'radio') { new ol.ext.input.Radio(opt); } else { new ol.ext.input.Checkbox(opt); } return input; }; /** Set inner html or append a child element to an element * @param {Element} element * @param {Element|string} html Content of the element */ ol.ext.element.setHTML = function(element, html) { if (html instanceof Element) element.appendChild(html) else if (html!==undefined) element.innerHTML = html; }; /** Append text into an elemnt * @param {Element} element * @param {string} text text content */ ol.ext.element.appendText = function(element, text) { element.appendChild(document.createTextNode(text||'')); }; /** * Add a set of event listener to an element * @param {Element} element * @param {string|Array} eventType * @param {function} fn */ ol.ext.element.addListener = function (element, eventType, fn, useCapture ) { if (typeof eventType === 'string') eventType = eventType.split(' '); eventType.forEach(function(e) { element.addEventListener(e, fn, useCapture); }); }; /** * Add a set of event listener to an element * @param {Element} element * @param {string|Array} eventType * @param {function} fn */ ol.ext.element.removeListener = function (element, eventType, fn) { if (typeof eventType === 'string') eventType = eventType.split(' '); eventType.forEach(function(e) { element.removeEventListener(e, fn); }); }; /** * Show an element * @param {Element} element */ ol.ext.element.show = function (element) { element.style.display = ''; }; /** * Hide an element * @param {Element} element */ ol.ext.element.hide = function (element) { element.style.display = 'none'; }; /** * Test if an element is hihdden * @param {Element} element * @return {boolean} */ ol.ext.element.hidden = function (element) { return ol.ext.element.getStyle(element, 'display') === 'none'; }; /** * Toggle an element * @param {Element} element */ ol.ext.element.toggle = function (element) { element.style.display = (element.style.display==='none' ? '' : 'none'); }; /** Set style of an element * @param {DOMElement} el the element * @param {*} st list of style */ ol.ext.element.setStyle = function(el, st) { for (var s in st) { switch (s) { case 'top': case 'left': case 'bottom': case 'right': case 'minWidth': case 'maxWidth': case 'width': case 'height': { if (typeof(st[s]) === 'number') { el.style[s] = st[s]+'px'; } else { el.style[s] = st[s]; } break; } default: { el.style[s] = st[s]; } } } }; /** * Get style propertie of an element * @param {DOMElement} el the element * @param {string} styleProp Propertie name * @return {*} style value */ ol.ext.element.getStyle = function(el, styleProp) { var value, defaultView = (el.ownerDocument || document).defaultView; // W3C standard way: if (defaultView && defaultView.getComputedStyle) { // sanitize property name to css notation // (hypen separated words eg. font-Size) styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase(); value = defaultView.getComputedStyle(el, null).getPropertyValue(styleProp); } else if (el.currentStyle) { // IE // sanitize property name to camelCase styleProp = styleProp.replace(/-(\w)/g, function(str, letter) { return letter.toUpperCase(); }); value = el.currentStyle[styleProp]; // convert other units to pixels on IE if (/^\d+(em|pt|%|ex)?$/i.test(value)) { return (function(value) { var oldLeft = el.style.left, oldRsLeft = el.runtimeStyle.left; el.runtimeStyle.left = el.currentStyle.left; el.style.left = value || 0; value = el.style.pixelLeft + "px"; el.style.left = oldLeft; el.runtimeStyle.left = oldRsLeft; return value; })(value); } } if (/px$/.test(value)) return parseInt(value); return value; }; /** Get outerHeight of an elemen * @param {DOMElement} elt * @return {number} */ ol.ext.element.outerHeight = function(elt) { return elt.offsetHeight + ol.ext.element.getStyle(elt, 'marginBottom') }; /** Get outerWidth of an elemen * @param {DOMElement} elt * @return {number} */ ol.ext.element.outerWidth = function(elt) { return elt.offsetWidth + ol.ext.element.getStyle(elt, 'marginLeft') }; /** Get element offset rect * @param {DOMElement} elt * @return {*} */ ol.ext.element.offsetRect = function(elt) { var rect = elt.getBoundingClientRect(); return { top: rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0), left: rect.left + (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0), height: rect.height || (rect.bottom - rect.top), width: rect.width || (rect.right - rect.left) } }; /** Get element offset * @param {ELement} elt * @returns {Object} top/left offset */ ol.ext.element.getFixedOffset = function(elt) { var offset = { left:0, top:0 }; var getOffset = function(parent) { if (!parent) return offset; // Check position when transform if (ol.ext.element.getStyle(parent, 'position') === 'absolute' && ol.ext.element.getStyle(parent, 'transform') !== "none") { var r = parent.getBoundingClientRect(); offset.left += r.left; offset.top += r.top; return offset; } console.log(parent, 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) { moving = true; if (pos !== false) { var delta = (isbar ? -1/scale : 1) * (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); } }; // Animate scroll var animate = function(to) { var step = (to>0) ? Math.min(100, to/2) : Math.max(-100, to/2); to -= step; elt[scroll] += step; if (-1 < to && to < 1) { if (moving) setTimeout(function() { elt.classList.remove('ol-move'); }); else elt.classList.remove('ol-move'); moving = false; onmove(false); } else { setTimeout(function() { animate(to); }, 40); } } // Initialize scroll container for minibar var scrollContainer, scrollbar; if (options.vertical && options.minibar) { var init = function(b) { // only once elt.removeEventListener('pointermove', init); elt.parentNode.classList.add('ol-miniscroll'); scrollbar = ol.ext.element.create('DIV'); scrollContainer = ol.ext.element.create('DIV', { className: 'ol-scroll', html: scrollbar }); elt.parentNode.insertBefore(scrollContainer, elt); // Move scrollbar scrollbar.addEventListener('pointerdown', function(e) { isbar = true; onPointerDown(e) }); // Handle mousewheel if (options.mousewheel) { ol.ext.element.addListener(scrollContainer, ['mousewheel', 'DOMMouseScroll', 'onmousewheel'], function(e) { onMouseWheel(e) } ); ol.ext.element.addListener(scrollbar, ['mousewheel', 'DOMMouseScroll', 'onmousewheel'], function(e) { onMouseWheel(e) } ); } // Update on enter elt.parentNode.addEventListener('pointerenter', updateMinibar); // Update on resize window.addEventListener('resize', updateMinibar); // Update if (b!==false) updateMinibar(); }; // Allready inserted in the DOM if (elt.parentNode) init(false); // or wait when ready else elt.addEventListener('pointermove', init); // Update on scroll elt.addEventListener('scroll', function() { updateMinibar(); }); } // Enable scroll elt.style['touch-action'] = 'none'; elt.style['overflow'] = 'hidden'; elt.classList.add('ol-scrolldiv'); // Start scrolling ol.ext.element.addListener(elt, ['pointerdown'], function(e) { isbar = false; onPointerDown(e) }); // Prevet click when moving... elt.addEventListener('click', function(e) { if (elt.classList.contains('ol-move')) { e.preventDefault(); e.stopPropagation(); } }, true); // Stop scrolling var onPointerUp = function(e) { dt = new Date() - dt; if (dt>100 || isbar) { // User stop: no speed speed = 0; } else if (dt>0) { // Calculate new speed speed = ((speed||0) + (pos - e[page]) / dt) / 2; } animate(options.animate===false ? 0 : speed*200); pos = false; speed = 0; dt = 0; // Add class to handle click (on iframe / double-click) if (!elt.classList.contains('ol-move')) { elt.classList.add('ol-hasClick') setTimeout(function() { elt.classList.remove('ol-hasClick'); }, 500); } else { elt.classList.remove('ol-hasClick'); } isbar = false; window.removeEventListener('pointermove', onPointerMove) ol.ext.element.removeListener(window, ['pointerup','pointercancel'], onPointerUp); }; // Handle mousewheel var onMouseWheel = function(e) { var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))); elt.classList.add('ol-move'); elt[scroll] -= delta*30; elt.classList.remove('ol-move'); return false; } if (options.mousewheel) { // && !elt.classList.contains('ol-touch')) { ol.ext.element.addListener(elt, ['mousewheel', 'DOMMouseScroll', 'onmousewheel'], onMouseWheel ); } return { refresh: updateMinibar } }; /** Dispatch an event to an Element * @param {string} eventName * @param {Element} element */ ol.ext.element.dispatchEvent = function (eventName, element) { var event; try { event = new CustomEvent(eventName); } catch(e) { // Try customevent on IE event = document.createEvent("CustomEvent"); event.initCustomEvent(eventName, true, true, {}); } element.dispatchEvent(event); }; /** Get a canvas overlay for a map (non rotated, on top of the map) * @param {ol.Map} map * @return {canvas} */ ol.ext.getMapCanvas = function(map) { if (!map) return null; var canvas = map.getViewport().getElementsByClassName('ol-fixedoverlay')[0]; if (!canvas) { if (map.getViewport().querySelector('.ol-layers')) { // Add a fixed canvas layer on top of the map canvas = document.createElement('canvas'); canvas.className = 'ol-fixedoverlay'; map.getViewport().querySelector('.ol-layers').after(canvas); // Clear before new compose map.on('precompose', function (e){ canvas.width = map.getSize()[0] * e.frameState.pixelRatio; canvas.height = map.getSize()[1] * e.frameState.pixelRatio; }); } else { canvas = map.getViewport().querySelector('canvas'); } } return canvas; }; /** @namespace ol.ext.imageLoader */ if (window.ol) window.ol.ext.imageLoader = {}; /** Helper for loading BIL-32 (Band Interleaved by Line) image * @param {string} src * @param {function} onload a function that takes a Float32Array and a ol.size.Size (array size) * @param {function} onerror * @private */ ol.ext.imageLoader.loadBILImage = function(src, onload, onerror) { var size = [ parseInt(src.replace(/.*WIDTH=(\d*).*/i,'$1')), parseInt(src.replace(/.*HEIGHT=(\d*).*/i,'$1')) ]; var xhr = new XMLHttpRequest(); xhr.responseType = 'blob'; xhr.addEventListener('loadend', function () { var resp = this.response; if (resp !== undefined) { var reader = new FileReader(); // Get as array reader.addEventListener('loadend', (e) => { var data = new Float32Array(e.target.result); onload(data, size); }); // Start reading the blob reader.readAsArrayBuffer(resp); // tile.getImage().src = URL.createObjectURL(blob); } else { onerror(); } }); xhr.addEventListener('error', function () { onerror(); }); xhr.open('GET', src); xhr.send(); }; /** Helper for loading image * @param {string} src * @param {function} onload a function that takes a an image and a ol.size.Size * @param {function} onerror * @private */ ol.ext.imageLoader.loadImage = function(src, onload, onerror) { var xhr = new XMLHttpRequest(); xhr.responseType = 'blob'; xhr.addEventListener('loadend', function () { var resp = this.response; if (resp !== undefined) { var img = new Image(); img.onload = function() { onload(img, [img.naturalWidth, img.naturalHeight]); } img.src = URL.createObjectURL(resp); } else { onerror(); } }); xhr.addEventListener('error', function () { onerror(); }); xhr.open('GET', src); xhr.send(); }; /** Get a TileLoadFunction to transform tiles images * @param {function} setPixel a function that takes a Uint8ClampedArray and the pixel position to transform * @returns {function} an ol/Tile~LoadFunction */ ol.ext.imageLoader.pixelTransform = function(setPixel) { return function(tile, src) { ol.ext.imageLoader.loadImage( src, function(img, size) { var canvas = document.createElement('canvas'); canvas.width = size[0]; canvas.height = size[1]; var ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); var imgData = ctx.getImageData(0, 0, size[0], size[1]); var pixels = imgData.data; for (var i = 0; i < pixels.length; i += 4) { setPixel(pixels, i, size); } ctx.putImageData(imgData, 0, 0); tile.setImage(canvas); }, function() { tile.setState(3); } ); } }; /** Get a TileLoadFunction to transform tiles into grayscale images * @returns {function} an ol/Tile~LoadFunction */ ol.ext.imageLoader.grayscale = function() { return ol.ext.imageLoader.pixelTransform(function(pixels, i) { pixels[i] = pixels[i + 1] = pixels[i + 2] = parseInt(3*pixels[i] + 4*pixels[i + 1] + pixels[i + 2] >>> 3); }) }; /** Get a TileLoadFunction to turn color or a color range transparent * @param {ol.color.Color|Array} colors color or color range to turn transparent * @returns {function} an ol/Tile~LoadFunction */ ol.ext.imageLoader.transparent = function(colors) { var color1, color2; if (colors instanceof Array) { color1 = colors[0]; color2 = colors[1]; } var color = color1 = ol.color.asArray(color1); if (!color2) { return ol.ext.imageLoader.pixelTransform(function(pixels, i) { if (pixels[i]===color[0] && pixels[i+1]===color[1] && pixels[i+2]===color[2]) { pixels[i+3] = 0; } }) } else { color2 = ol.color.asArray(color2); color = [Math.min(color1[0], color2[0]), Math.min(color1[1], color2[1]), Math.min(color1[2], color2[2])]; color2 = [Math.max(color1[0], color2[0]), Math.max(color1[1], color2[1]), Math.max(color1[2], color2[2])]; return ol.ext.imageLoader.pixelTransform(function(pixels, i) { if (pixels[i]>=color1[0] && pixels[i]<=color2[0] && pixels[i+1]>=color[1] && pixels[i+1]<=color2[1] && pixels[i+2]>=color[2] && pixels[i+2]<=color2[2]) { pixels[i+3] = 0; } }) } }; /** Returns an Imageloader function to load an x-bil-32 image as sea level map * to use as a ol/Tile~LoadFunction or ol/Image~LoadFunction * @param { number } level * @param {*} options * @param { ol.Color } [options.color] fill color * @param { boolean } [options.opacity=true] smooth color on border * @param { number } [options.minValue=-Infinity] minimum level value * @returns {function} an ol/Tile~LoadFunction */ ol.ext.imageLoader.seaLevelMap = function(level, options) { options = options || {}; var h0 = Math.max(level + .01, .01); var c = options.color ? ol.color.asArray(options.color) : [135,203,249]; var min = typeof(options.minValue) === 'number' ? options.minValue : -Infinity; var opacity = options.opacity!==false; return ol.ext.imageLoader.elevationMap(function(h) { if (h < h0 && h > min) { return [c[0], c[1], c[2], opacity ? 255 * (h0-h) / h0 : 255]; } else { return [0,0,0,0]; } }) }; /** Shaded relief ? not/bad working yet... * @returns {function} an ol/Tile~LoadFunction * @private */ ol.ext.imageLoader.shadedRelief = function() { var sunElev = Math.PI / 4; var sunAzimuth = 2*Math.PI - Math.PI / 4; return function (tile, src) { ol.ext.imageLoader.loadBILImage( src, function(data, size) { var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); var width = canvas.width = size[0]; var height = canvas.height = size[1]; var imgData = ctx.getImageData(0, 0, width, height); var pixels = imgData.data; function getIndexForCoordinates(x, y) { return x + y*width; } for (var x=0; x 0 ) { if ( sly > 0 ) azimuth = phi + 1.5*Math.PI; else if ( sly < 0 ) azimuth = 1.5*Math.PI - phi; else phi = 1.5*Math.PI; } else if ( slx < 0 ){ if ( sly < 0 ) azimuth = phi + .5*Math.PI; else if ( sly > 0 ) azimuth = .5*Math.PI - phi; else azimuth = .5*Math.PI; } else { if ( sly < 0 ) azimuth = Math.PI; else if ( sly > 0 ) azimuth = 0; } // get luminance var lum = Math.cos( azimuth - sunAzimuth )*Math.cos( Math.PI*.5 - Math.atan(sl0) )*Math.cos( sunElev ) + Math.sin( Math.PI*.5 - Math.atan(sl0) )*Math.sin( sunElev ); if (lum<0) lum = 0; lum = Math.sqrt(lum*.8 + .5); var p = getIndexForCoordinates(x,y) * 4; pixels[p] = pixels[p+1] = pixels[p+2] = 0; pixels[p+3] = 255 - lum*255; } ctx.putImageData(imgData, 0, 0); tile.setImage(canvas); }, function () { tile.setState(3); } ) }; }; /** Get a TileLoadFunction to load an x-bil-32 image as elevation map (ie. pixels colors codes elevations as terrain-RGB) * If getPixelColor is not define pixel store elevation as rgb, use {@link ol.ext.getElevationFromPixel} to get elevation from pixel * @param {function} [getPixelColor] a function that taket an elevation and return a color array [r,g,b,a], default store elevation as terrain-RGB * @returns {function} an ol/Tile~LoadFunction */ ol.ext.imageLoader.elevationMap = function(getPixelColor) { if (typeof(getPixelColor) !== 'function') getPixelColor = ol.ext.getPixelFromElevation; return function (tile, src) { ol.ext.imageLoader.loadBILImage( src, function(data, size) { var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); canvas.width = size[0]; canvas.height = size[1]; var imgData = ctx.getImageData(0, 0, size[0], size[1]); var pixels = imgData.data; for (var i=0; i -12000 m * - 2 digits (0.01 m) * @param {number} height elevation * @returns {Array} pixel value */ ol.ext.getPixelFromElevation = function(height) { var h = Math.round(height*100 + 1200000); var pixel = [ h >> 16, (h % 65536) >> 8, h % 256, 255 ]; return pixel; }; /** Convert pixel (terrain-RGB) to elevation * @see ol.ext.getPixelFromElevation * @param {Array} pixel the pixel value * @returns {number} elevation */ ol.ext.getElevationFromPixel = function(pixel) { // return -10000 + (pixel[0] * 65536 + pixel[1] * 256 + pixel[2]) * 0.01; return -12000 + ((pixel[0] << 16) + (pixel[1] << 8) + pixel[2]) * 0.01; }; /* See https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web https://evanw.github.io/lightgl.js/docs/matrix.html https://github.com/jlmakes/rematrix https://jsfiddle.net/2znLxda2/ */ /** Matrix3D; a set of functions to handle matrix3D */ ol.matrix3D = {}; /** Get transform matrix3D of an element * @param {Element} ele * @return {Array>} */ ol.matrix3D.getTransform = function(ele) { var style = window.getComputedStyle(ele, null); var tr = style.getPropertyValue("-webkit-transform") || style.getPropertyValue("-moz-transform") || style.getPropertyValue("-ms-transform") || style.getPropertyValue("-o-transform") || style.getPropertyValue("transform"); var values = tr.split('(')[1].split(')')[0].split(','); var mx = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ]; var i, j; if (values.length === 16) { for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { mx[j][i] = +values[i * 4 + j]; } } } else { for (i = 0; i < 3; ++i) { for (j = 0; j < 2; ++j) { mx[j][i] = +values[i * 2 + j]; } } } return mx; }; /** Get transform matrix3D of an element * @param {Element} ele * @return {Array} */ ol.matrix3D.getTransformOrigin = function (ele) { var style = window.getComputedStyle(ele, null); var tr = style.getPropertyValue("-webkit-transform-origin") || style.getPropertyValue("-moz-transform-origin") || style.getPropertyValue("-ms-transform-origin") || style.getPropertyValue("-o-transform-origin") || style.getPropertyValue("transform-origin"); var values = tr.split(' '); var mx = [ 0, 0, 0, 1 ]; for (var i = 0; i < values.length; ++i) { mx[i] = parseInt(values[i]); } return mx; }; /** Compute translate matrix * @param {number} x * @param {number} y * @param {number} z * @return {Array>} */ ol.matrix3D.translateMatrix = function(x, y, z) { return [ [1, 0, 0, x], [0, 1, 0, y], [0, 0, 1, z], [0, 0, 0, 1] ]; }; /** Identity matrix * @return {Array>} */ ol.matrix3D.identity = function() { return [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ]; }; /** Round matrix * @param {Array>} mx * @param {number} round Rounding value, default 1E-10 */ ol.matrix3D.roundTo = function(mx, round) { if (!round) round = 1E-10; var m = [[],[],[],[]]; for (var i=0; i<4; i++) { for (var j=0; j<4; j++) { m[i][j] = Math.round(mx[i][j] / round) * round; } } return m; }; /** Multiply matrix3D * @param {Array>} mx1 * @param {Array>} mx2 * @return {Array>} */ ol.matrix3D.multiply = function (mx1, mx2) { var mx = [ [], [], [], [] ]; for (var i = 0; i < 4; ++i) { for (var j = 0; j < 4; ++j) { var sum = 0; for (var k = 0; k < 4; ++k) { sum += (mx1[k][i] * mx2[j][k]); } mx[j][i] = sum; } } return mx; }; /** Compute the full transform that is applied to the transformed parent: -origin o tx o origin * @param {Array>} tx transform matrix * @param {Array>} origin transform origin * @return {Array>} */ ol.matrix3D.computeTransformMatrix = function(tx, origin) { var preTx = ol.matrix3D.translateMatrix(-origin[0], -origin[1], -origin[2]); var postTx = ol.matrix3D.translateMatrix(origin[0], origin[1], origin[2]); var temp1 = ol.matrix3D.multiply(preTx, tx); return ol.matrix3D.multiply(temp1, postTx); }; /** Apply transform to a coordinate * @param {Array>} tx * @param {ol.pixel} px */ ol.matrix3D.transformVertex = function(tx, px) { var vert = [px[0], px[1], 0, 1] var mx = [ ]; for (var i = 0; i < 4; ++i) { mx[i] = 0; for (var j = 0; j < 4; ++j) { mx[i] += +tx[i][j] * vert[j]; } } return mx; } /** Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component). * @param {Array} vert * @return {Array} */ ol.matrix3D.projectVertex = function(vert) { var out = [ ]; for (var i = 0; i < 4; ++i) { out[i] = vert[i] / vert[3]; } return out; }; /** Inverse a matrix3D * @return {Array>} m matrix to transform * @return {Array>} */ ol.matrix3D.inverse = function(m) { var s0 = m[0][0] * m[1][1] - m[1][0] * m[0][1] var s1 = m[0][0] * m[1][2] - m[1][0] * m[0][2] var s2 = m[0][0] * m[1][3] - m[1][0] * m[0][3] var s3 = m[0][1] * m[1][2] - m[1][1] * m[0][2] var s4 = m[0][1] * m[1][3] - m[1][1] * m[0][3] var s5 = m[0][2] * m[1][3] - m[1][2] * m[0][3] var c5 = m[2][2] * m[3][3] - m[3][2] * m[2][3] var c4 = m[2][1] * m[3][3] - m[3][1] * m[2][3] var c3 = m[2][1] * m[3][2] - m[3][1] * m[2][2] var c2 = m[2][0] * m[3][3] - m[3][0] * m[2][3] var c1 = m[2][0] * m[3][2] - m[3][0] * m[2][2] var c0 = m[2][0] * m[3][1] - m[3][0] * m[2][1] var determinant = 1 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0) if (isNaN(determinant) || determinant === Infinity) { throw new Error('Inverse determinant attempted to divide by zero.') } return [ [ (m[1][1] * c5 - m[1][2] * c4 + m[1][3] * c3) * determinant, (-m[0][1] * c5 + m[0][2] * c4 - m[0][3] * c3) * determinant, (m[3][1] * s5 - m[3][2] * s4 + m[3][3] * s3) * determinant, (-m[2][1] * s5 + m[2][2] * s4 - m[2][3] * s3) * determinant ],[ (-m[1][0] * c5 + m[1][2] * c2 - m[1][3] * c1) * determinant, (m[0][0] * c5 - m[0][2] * c2 + m[0][3] * c1) * determinant, (-m[3][0] * s5 + m[3][2] * s2 - m[3][3] * s1) * determinant, (m[2][0] * s5 - m[2][2] * s2 + m[2][3] * s1) * determinant ],[ (m[1][0] * c4 - m[1][1] * c2 + m[1][3] * c0) * determinant, (-m[0][0] * c4 + m[0][1] * c2 - m[0][3] * c0) * determinant, (m[3][0] * s4 - m[3][1] * s2 + m[3][3] * s0) * determinant, (-m[2][0] * s4 + m[2][1] * s2 - m[2][3] * s0) * determinant ],[ (-m[1][0] * c3 + m[1][1] * c1 - m[1][2] * c0) * determinant, (m[0][0] * c3 - m[0][1] * c1 + m[0][2] * c0) * determinant, (-m[3][0] * s3 + m[3][1] * s1 - m[3][2] * s0) * determinant, (m[2][0] * s3 - m[2][1] * s1 + m[2][2] * s0) * determinant ] ] }; /* global ol */ /* Create ol.sphere for backward compatibility with ol < 5.0 * To use with Openlayers package */ if (window.ol && !ol.sphere) { ol.sphere = {}; ol.sphere.getDistance = function (c1, c2, radius) { var sphere = new ol.Sphere(radius || 6371008.8); return sphere.haversineDistance(c1, c2); } ol.sphere.getArea = ol.Sphere.getArea; ol.sphere.getLength = ol.Sphere.getLength; } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** A simple filter to detect edges on images * @constructor * @requires ol.filter * @extends {ol.ext.SVGFilter} * @param {*} options * @param {number} options.neighbours nb of neighbour (4 or 8), default 8 * @param {boolean} options.grayscale get grayscale image, default false, * @param {boolean} options.alpha get alpha channel, default false */ ol.ext.SVGFilter.Laplacian = 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); /** Vanilla JS geographic inputs * color, size, width, font, symboles, dash, arrow, pattern */ /** @namespace ol.ext.input */ /*global ol*/ if (window.ol) { ol.ext.input = {}; } /** Abstract base class; normally only used for creating subclasses and not instantiated in apps. * @constructor * @extends {ol.Object} * @param {*} options * @param {Element} [options.input] input element, if non create one * @param {string} [options.type] input type, if no input * @param {number} [options.min] input min, if no input * @param {number} [options.max] input max, if no input * @param {number} [options.step] input step, if no input * @param {string|number} [options.val] input value * @param {boolean} [options.checked] check input * @param {boolean} [options.hidden] the input is display:none * @param {boolean} [options.disabled] disable input * @param {Element} [options.parent] parent element, if no input */ ol.ext.input.Base = function(options) { options = options || {}; ol.Object.call(this); var input = this.input = options.input; if (!input) { input = this.input = document.createElement('INPUT'); if (options.type) input.setAttribute('type', options.type); if (options.min !== undefined) input.setAttribute('min', options.min); if (options.max !== undefined) input.setAttribute('max', options.max); if (options.step !== undefined) input.setAttribute('step', options.step); if (options.parent) options.parent.appendChild(input); } if (options.disabled) input.disabled = true; if (options.checked !== undefined) input.checked = !!options.checked; if (options.val !== undefined) input.value = options.val; if (options.hidden) input.style.display = 'none'; }; ol.ext.inherits(ol.ext.input.Base, ol.Object); /** Listen to drag event * @param {Element} elt * @param {function} cback when draggin on the element * @private */ ol.ext.input.Base.prototype._listenDrag = function(elt, cback) { var handle = function(e) { this.moving = true; var listen = function(e) { if (e.type==='pointerup') { document.removeEventListener('pointermove', listen); document.removeEventListener('pointerup', listen); document.removeEventListener('pointercancel', listen); setTimeout(function() { this.moving = false; }.bind(this)); } if (e.target === elt) cback(e); e.stopPropagation(); e.preventDefault(); }.bind(this); document.addEventListener('pointermove', listen, false); document.addEventListener('pointerup', listen, false); document.addEventListener('pointercancel', listen, false); e.stopPropagation(); e.preventDefault(); }.bind(this) elt.addEventListener('mousedown', handle, false); elt.addEventListener('touchstart', handle, false); }; /** Set the current value */ ol.ext.input.Base.prototype.setValue = function(v) { if (v !== undefined) this.input.value = v; this.input.dispatchEvent(new Event('change')); }; /** Get the current getValue * @returns {string} */ ol.ext.input.Base.prototype.getValue = function() { return this.input.value; }; /** Get the input element * @returns {Element} */ ol.ext.input.Base.prototype.getInputElement = function() { return this.input; }; /** Checkbox input * @constructor * @extends {ol.ext.input.Base} * @param {*} options * @param {string} [options.className] * @param {Element} [options.input] input element, if non create one * @param {Element} [options.parent] parent element, if create an input * @param {string} [options.align=left] align popup left/right * @param {string} [options.type] a slide type as 'size' * @param {number} [options.min] min value, default use input min * @param {number} [options.max] max value, default use input max * @param {number} [options.step] step value, default use input step * @param {boolean} [options.overflow=false] enable values over min/max * @param {string|Element} [options.before] an element to add before the slider * @param {string|Element} [options.after] an element to add after the slider * @param {boolean} [options.fixed=false] no pupop */ ol.ext.input.Slider = function(options) { options = options || {}; ol.ext.input.Base.call(this, options); this.set('overflow', !!options.overflow); this.element = ol.ext.element.create('DIV', { className: 'ol-input-slider' + (options.type ? ' ol-' + options.type : '') + (options.className ? ' ' + options.className : '') }); if (options.fixed) this.element.classList.add('ol-fixed'); var input = this.input; if (input.parentNode) input.parentNode.insertBefore(this.element, input); this.element.appendChild(input); if (options.align==='right') this.element.classList.add('ol-right'); var popup = ol.ext.element.create('DIV', { className: 'ol-popup', parent: this.element }) // Before element if (options.before) { ol.ext.element.create('DIV', { className: 'ol-before', html: options.before, parent: popup }); } // Slider var slider = this.slider = ol.ext.element.create('DIV', { className: 'ol-slider', parent: popup }); ol.ext.element.create('DIV', { className: 'ol-back', parent: this.slider }) // Cursor var cursor = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: slider }) // After element if (options.after) { ol.ext.element.create('DIV', { className: 'ol-after', html: options.after, parent: popup }); } var min = (options.min !== undefined) ? options.min : parseFloat(input.min) || 0; var max = (options.max !== undefined) ? options.max : parseFloat(input.max) || 1; var step = (options.step !== undefined) ? options.step : parseFloat(input.step) || 1; // Handle popup drag this._listenDrag(slider, function(e) { var tx = Math.max(0, Math.min(e.offsetX / slider.clientWidth, 1)); cursor.style.left = Math.max(0, Math.min(100, Math.round(tx*100) )) + '%'; var v = input.value = Math.round((tx * (max - min) + min) / step) * step; this.dispatchEvent({ type: 'change:value', value: v }); }.bind(this)); // Set value var setValue = function() { var v = parseFloat(input.value) || 0; if (!this.get('overflow')) v = Math.max(min, Math.min(max, v)); if (v != input.value) input.value = v; var tx = (v - min) / (max - min); cursor.style.left = Math.max(0, Math.min(100, Math.round(tx*100) )) + '%'; this.dispatchEvent({ type: 'change:value', value: v }); }.bind(this); input.addEventListener('change', setValue); setValue(); }; ol.ext.inherits(ol.ext.input.Slider, ol.ext.input.Base); /** Base class for input popup * @constructor * @extends {ol.ext.input.Base} * @fires change:color * @fires color * @param {*} options * @param {string} [options.className] * @param {ol.colorLike} [options.color] default color * @param {Element} [options.input] input element, if non create one * @param {Element} [options.parent] parent element, if create an input * @param {string} [options.position='popup'] fixed | static | popup | inline (no popup) * @param {boolean} [options.autoClose=true] close when click on color * @param {boolean} [options.hidden=false] display the input */ ol.ext.input.PopupBase = function(options) { options = options || {}; options.hidden = options.hidden!==false; ol.ext.input.Base.call(this, options); this.set('autoClose', options.autoClose !== false); this.element = ol.ext.element.create('DIV', { className: ('ol-ext-popup-input ' + (options.className || '')).trim(), }); switch (options.position) { case 'inline': break; case 'static': case 'fixed': { this.element.classList.add('ol-popup'); this.element.classList.add('ol-popup-fixed'); this._fixed = (options.position === 'fixed'); break; } default: { this.element.classList.add('ol-popup'); break; } } var input = this.input; if (input.parentNode) input.parentNode.insertBefore(this.element, input); // Show on element click this.element.addEventListener('click', function() { if (this.isCollapsed()) setTimeout( function() { this.collapse(false); }.bind(this) ); }.bind(this)); // Hide on click outside document.addEventListener('click', function() { if (!this.moving) this.collapse(true); }.bind(this)); // Hide on window resize window.addEventListener('resize', function() { this.collapse(true); }.bind(this)); this._elt = {}; // Popup container this._elt.popup = ol.ext.element.create('DIV', { className: 'ol-popup', parent: this.element }); this._elt.popup.addEventListener('click', function(e) { e.stopPropagation(); }); }; ol.ext.inherits(ol.ext.input.PopupBase, ol.ext.input.Base); /** show/hide color picker * @param {boolean} [b=false] */ ol.ext.input.PopupBase.prototype.collapse = function(b) { if (b != this.isCollapsed()) { this.dispatchEvent({ type: 'change:visible', visible: !this.isCollapsed() }); } this.dispatchEvent({ type: 'collapse', visible: !b }); if (b) { this._elt.popup.classList.remove('ol-visible'); } else { this._elt.popup.classList.add('ol-visible'); if (this._fixed) { // Get fixed position var pos = this.element.getBoundingClientRect(); var offset = ol.ext.element.getFixedOffset(this.element); pos = { bottom: pos.bottom - offset.top, left: pos.left - offset.left } // Test window overflow + recenter var dh = pos.bottom + this._elt.popup.offsetHeight + offset.top; if (dh > document.documentElement.clientHeight) { this._elt.popup.style.top = Math.max(document.documentElement.clientHeight - this._elt.popup.offsetHeight - offset.top, 0) + 'px'; } else { this._elt.popup.style.top = pos.bottom + 'px'; } var dw = pos.left + this._elt.popup.offsetWidth + offset.left; if (dw > document.documentElement.clientWidth) { this._elt.popup.style.left = Math.max(document.documentElement.clientWidth - this._elt.popup.offsetWidth - offset.left, 0) + 'px'; } else { this._elt.popup.style.left = pos.left + 'px'; } } } }; /** Is the popup collapsed ? * @returns {boolean} */ ol.ext.input.PopupBase.prototype.isCollapsed = function() { return !this._elt.popup.classList.contains('ol-visible'); }; /** Toggle the popup */ ol.ext.input.PopupBase.prototype.toggle = function() { this.collapse(!this.isCollapsed()); }; /** Checkbox input * @constructor * @extends {ol.ext.input.Base} * @fires check * @param {*} options * @param {string} [options.className] * @param {Element|string} [options.html] label content * @param {string} [options.after] label garnish (placed after) * @param {Element} [options.input] input element, if non create one * @param {Element} [options.parent] parent element, if create an input * @param {boolean} [options.autoClose=true] * @param {boolean} [options.visible=false] display the input */ ol.ext.input.Checkbox = function(options) { options = options || {}; ol.ext.input.Base.call(this, options); var label = this.element = document.createElement('LABEL'); if (options.html instanceof Element) label.appendChild(options.html) else if (options.html !== undefined) label.innerHTML = options.html; label.className = ('ol-ext-check ol-ext-checkbox ' + (options.className || '')).trim(); if (this.input.parentNode) this.input.parentNode.insertBefore(label, this.input); label.appendChild(this.input); label.appendChild(document.createElement('SPAN')); if (options.after) { label.appendChild(document.createTextNode(options.after)); } // Handle change this.input.addEventListener('change', function() { this.dispatchEvent({ type: 'check', checked: this.input.checked, value: this.input.value }); }.bind(this)); }; ol.ext.inherits(ol.ext.input.Checkbox, ol.ext.input.Base); ol.ext.input.Checkbox.prototype.isChecked = function () { return this.input.checked; }; /** A list element synchronize with a Collection. * Element in the list can be reordered interactively and the associated Collection is kept up to date. * @constructor * @fires item:select * @fires item:dblclick * @fires item:order * @extends {ol.Object} * @param {*} options * @param {Element} [options.target] * @param {Collection} [options.collection] the collection to display in the list * @param {function} [options.getTitle] a function that takes a collection item and returns an Element or a string */ ol.ext.input.Collection = function(options) { ol.Object.call(this); this.element = ol.ext.element.create('UL', { className: ('ol-collection-list '+(options.className||'')).trim(), parent: options.target }) this.collection = options.collection; this._title = (typeof(options.getTitle) === 'function' ? options.getTitle : function(elt) { return elt.title }); this.refresh(); this.collection.on('change:length', function() { if (!this._reorder) { this.refresh(); var pos = this.getSelectPosition(); if (pos < 0) { this.dispatchEvent({ type: 'item:select', position: -1, item: null }); } else { this.dispatchEvent({ type: 'item:order', position: pos, item: this._currentItem }); } } }.bind(this)); }; ol.ext.inherits(ol.ext.input.Collection, ol.Object); /** Select an item * @param {*} item */ ol.ext.input.Collection.prototype.select = function(item) { if (item === this._currentItem) return; var pos = -1; this._listElt.forEach(function (l, i) { if (l.item !== item) { l.li.classList.remove('ol-select'); } else { l.li.classList.add('ol-select'); pos = i; } }) this._currentItem = (pos >= 0 ? item : null); this.dispatchEvent({ type: 'item:select', position: pos, item: this._currentItem }); }; /** Select an item at * @param {number} n */ ol.ext.input.Collection.prototype.selectAt = function(n) { this.select(this.collection.item(n)); }; /** Get current selection * @returns {*} */ ol.ext.input.Collection.prototype.getSelect = function() { return this._currentItem; }; /** Get current selection * @returns {number} */ ol.ext.input.Collection.prototype.getSelectPosition = function() { return this.collection.getArray().indexOf(this._currentItem); }; /** Redraw the list */ ol.ext.input.Collection.prototype.refresh = function() { this.element.innerHTML = ''; this._listElt = []; this.collection.forEach((item, pos) => { var li = ol.ext.element.create('LI', { html: this._title(item), className: this._currentItem === item ? 'ol-select' : '', 'data-position': pos, on: { click: function() { this.select(item); }.bind(this), dblclick: function() { this.dispatchEvent({ type: 'item:dblclick', position: pos, item: item }); }.bind(this), }, parent: this.element }); this._listElt.push({ li: li, item: item }); var order = ol.ext.element.create('DIV', { className: 'ol-noscroll ol-order', parent: li }); var current = pos; var move = function(e) { // Get target var target = e.pointerType==='touch' ? document.elementFromPoint(e.clientX, e.clientY) : e.target; while (target && target.parentNode !== this.element) { target = target.parentNode; } if (target && target !== li) { var over = parseInt(target.getAttribute('data-position')); if (target.getAttribute('data-position') < current) { target.insertAdjacentElement('beforebegin', li); current = over; } else { target.insertAdjacentElement('afterend', li); current = over+1; } } }.bind(this); var stop = function() { document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', stop); document.removeEventListener('pointercancel', stop); if (current !== pos) { this._reorder = true; this.collection.removeAt(pos); this.collection.insertAt(current>pos ? current-1 : current, item); this._reorder = false; this.dispatchEvent({ type: 'item:order', position: current>pos ? current-1 : current, oldPosition: pos, item: item }) this.refresh(); } }.bind(this); order.addEventListener('pointerdown', function() { this.select(item) document.addEventListener('pointermove', move); document.addEventListener('pointerup', stop) document.addEventListener('pointercancel', stop) }.bind(this)); }); } /** Color picker * @constructor * @extends {ol.ext.input.PopupBase} * @fires change:color * @fires color * @param {*} options * @param {string} [options.className] * @param {ol.colorLike} [options.color] default color * @param {Element} [options.input] input element, if non create one * @param {Element} [options.parent] parent element, if create an input * @param {boolean} [options.hastab=false] use tabs for palette / picker * @param {string} [options.paletteLabel="palette"] label for the palette tab * @param {string} [options.pickerLabel="picker"] label for the picker tab * @param {string} [options.position='popup'] fixed | static | popup | inline (no popup) * @param {boolean} [options.opacity=true] enable opacity * @param {boolean} [options.autoClose=true] close when click on color * @param {boolean} [options.hidden=true] display the input */ ol.ext.input.Color = function(options) { options = options || {}; options.hidden = options.hidden!==false; options.className = ('ol-ext-colorpicker ' + (options.hastab ? 'ol-tab ' : '') + (options.className || '')).trim(); ol.ext.input.PopupBase.call(this, options); if (options.opacity===false) { this.element.classList.add('ol-nopacity'); } this._cursor = {}; var hsv = this._hsv = {}; // Vignet this._elt.vignet = ol.ext.element.create('DIV', { className: 'ol-vignet', parent: this.element }); // Bar var bar = ol.ext.element.create('DIV', { className: 'ol-tabbar', parent: this._elt.popup }); ol.ext.element.create('DIV', { className: 'ol-tab', html: options.paletteLabel || 'palette', click: function() { this.element.classList.remove('ol-picker-tab'); }.bind(this), parent: bar }); ol.ext.element.create('DIV', { className: 'ol-tab', html: options.pickerLabel || 'picker', click: function() { this.element.classList.add('ol-picker-tab'); }.bind(this), parent: bar }); // Popup container var container = ol.ext.element.create('DIV', { className: 'ol-container', parent: this._elt.popup }); // Color picker var picker = this._elt.picker = ol.ext.element.create('DIV', { className: 'ol-picker', parent: container }); var pickerCursor = this._cursor.picker = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: picker }); this._listenDrag(picker, function(e) { var tx = Math.max(0, Math.min(e.offsetX / picker.clientWidth, 1)); var ty = Math.max(0, Math.min(e.offsetY / picker.clientHeight, 1)); pickerCursor.style.left = Math.round(tx*100) + '%'; pickerCursor.style.top = Math.round(ty*100) + '%'; hsv.s = tx * 100; hsv.v = 100 - ty * 100; this.setColor(); }.bind(this)); // Opacity cursor var slider = ol.ext.element.create('DIV', { className: 'ol-slider', parent: container }); this._elt.slider = ol.ext.element.create('DIV', { parent: slider }); var sliderCursor = this._cursor.slide = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: slider }); this._listenDrag(slider, function(e) { var t = Math.max(0, Math.min(e.offsetX / slider.clientWidth, 1)); hsv.a = t*100; sliderCursor.style.left = Math.round(t*100) + '%'; this.setColor(); }.bind(this)); // Tint cursor var tint = ol.ext.element.create('DIV', { className: 'ol-tint', parent: container }); var tintCursor = this._cursor.tint = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: tint }); this._listenDrag(tint, function(e) { var t = Math.max(0, Math.min(e.offsetY / tint.clientHeight, 1)); hsv.h = t*360; tintCursor.style.top = Math.round(t*100) + '%'; this.setColor(); }.bind(this)); // Clear button ol.ext.element.create('DIV', { className: 'ol-clear', click: function() { this.setColor([0,0,0,0]); }.bind(this), parent: container }); // RVB input var rgb = ol.ext.element.create('DIV', { className: 'ol-rgb', parent: container }); var changergb = function() { var r = Math.max(0, Math.min(255, parseInt(this._elt.r.value))); var g = Math.max(0, Math.min(255, parseInt(this._elt.g.value))); var b = Math.max(0, Math.min(255, parseInt(this._elt.b.value))); var a = Math.max(0, Math.min(1, parseFloat(this._elt.a.value))); this.setColor([r, g, b, a]); }.bind(this); this._elt.r = ol.ext.element.create('INPUT', { type: 'number', lang:'en-GB', change: changergb, min:0, max:255, parent: rgb }); this._elt.g = ol.ext.element.create('INPUT', { type: 'number', lang:'en-GB', change: changergb, min:0, max:255, parent: rgb }); this._elt.b = ol.ext.element.create('INPUT', { type: 'number', lang:'en-GB', change: changergb, min:0, max:255, parent: rgb }); this._elt.a = ol.ext.element.create('INPUT', { type: 'number', lang:'en-GB', change: changergb, min:0, max:1, step:.1, parent: rgb }); // Text color input this._elt.txtColor = ol.ext.element.create('INPUT', { type: 'text', className: 'ol-txt-color', change: function(){ var color; this._elt.txtColor.classList.remove('ol-error') try { color = ol.color.asArray(this._elt.txtColor.value); } catch(e) { this._elt.txtColor.classList.add('ol-error'); } if (color) this.setColor(color) }.bind(this), parent: container }); ol.ext.element.create('BUTTON', { html: 'OK', click: function() { this._addCustomColor(this.getColor()); this.collapse(true); }.bind(this), parent: container }); var i; // Color palette this._paletteColor = {}; this._elt.palette = ol.ext.element.create('DIV', { className: 'ol-palette', parent: this._elt.popup }) for (i=0; i<8; i++) { var c = Math.round(255 - 255*i/7); this.addPaletteColor([c,c,c], c);//ol.color.toHexa([c,c,c])); } var colors = ['#f00', '#f90', '#ff0', '#0f0', '#0ff', '#48e', '#00f', '#f0f'] colors.forEach(function(c){ this.addPaletteColor(c, ol.color.toHexa(ol.color.asArray(c))); }.bind(this)); for (i=0; i<5; i++) { colors.forEach(function(c){ c = ol.color.toHSV(ol.color.asArray(c)); c = [c[0], i/4*80+20, 100 - i/4*60]; c = ol.color.fromHSV(c,1) this.addPaletteColor(c, ol.color.toHexa(c)); }.bind(this)); } // Custom colors ol.ext.element.create('HR', { parent: this._elt.palette }); // Create custom color list if (!ol.ext.input.Color.customColorList) { ol.ext.input.Color.customColorList = new ol.Collection(); var ccolor = JSON.parse(localStorage.getItem('ol-ext@colorpicker') || '[]'); ccolor.forEach(function(c) { ol.ext.input.Color.customColorList.push(c); }) ol.ext.input.Color.customColorList.on(['add','remove'], function(){ localStorage.setItem('ol-ext@colorpicker', JSON.stringify(ol.ext.input.Color.customColorList.getArray())); }); } // Handle custom color ol.ext.input.Color.customColorList.on('add', function(e) { this.addPaletteColor(this.getColorFromID(e.element)); }.bind(this)); ol.ext.input.Color.customColorList.on('remove', function(e) { if (this._paletteColor[e.element]) this._paletteColor[e.element].element.remove(); delete this._paletteColor[e.element]; }.bind(this)); // Add new one ol.ext.input.Color.customColorList.forEach(function(c) { this._addCustomColor(this.getColorFromID(c)); }.bind(this)); // Current color this.setColor(options.color || [0,0,0,0]); this._currentColor = this.getColorID(this.getColor()); // Add new palette color this.on('color', function() { this._addCustomColor(this.getColor()); this._currentColor = this.getColorID(this.getColor()); this.setColor(); }.bind(this)); // Update color on hide this.on('collapse', function(e) { if (!e.visible) { var c = this.getColor(); if (this._currentColor !== this.getColorID(c)) { this.dispatchEvent({ type: 'color', color: c }); } } else { this._currentColor = this.getColorID(this.getColor()); } }.bind(this)); }; ol.ext.inherits(ol.ext.input.Color, ol.ext.input.PopupBase); /** Custom color list * @private */ ol.ext.input.Color.customColorList = null; /** Add color to palette * @param {ol.colorLike} color * @param {string} title * @param {boolean} select */ ol.ext.input.Color.prototype.addPaletteColor = function(color, title, select) { // Get color id try { color = ol.color.asArray(color); } catch(e) { return; } var id = this.getColorID(color); // Add new one if (!this._paletteColor[id] && color[3]) { this._paletteColor[id] = { color: color, element: ol.ext.element.create('DIV', { title: title || '', className: (color[3]<1 ? 'ol-alpha' : ''), style: { color: 'rgb('+(color.join(','))+')' }, click: function() { this.setColor(color); if (this.get('autoClose')) this.collapse(true); }.bind(this), parent: this._elt.palette }) } } if (select) { this._selectPalette(color); } }; /** Show palette or picker tab * @param {string} what palette or picker */ ol.ext.input.Color.prototype.showTab = function(what) { if (what==='palette') this.element.classList.remove('ol-picker-tab'); else this.element.classList.add('ol-picker-tab'); }; /** Show palette or picker tab * @returns {string} palette or picker */ ol.ext.input.Color.prototype.getTab = function() { return this.element.classList.contains('ol-picker-tab') ? 'picker' : 'palette'; }; /** Select a color in the palette * @private */ ol.ext.input.Color.prototype._selectPalette = function(color) { var id = this.getColorID(color); Object.keys(this._paletteColor).forEach(function(c) { this._paletteColor[c].element.classList.remove('ol-select') }.bind(this)) if (this._paletteColor[id]) { this._paletteColor[id].element.classList.add('ol-select'); } } /** Set Color * @param { Array } */ ol.ext.input.Color.prototype.setColor = function(color) { var hsv = this._hsv; if (color) { color = ol.color.asArray(color); var hsv2 = ol.color.toHSV(color); hsv.h = hsv2[0]; hsv.s = hsv2[1]; hsv.v = hsv2[2]; if (hsv2.length > 3) hsv.a = hsv2[3]*100; else hsv.a = 100; this._cursor.picker.style.left = hsv.s + '%'; this._cursor.picker.style.top = (100-hsv.v) + '%'; this._cursor.tint.style.top = (hsv.h / 360 * 100) + '%'; this._cursor.slide.style.left = hsv.a + '%'; if (this.isCollapsed()) { this.dispatchEvent({ type: 'color', color: color }); } } else { /* hsv.h = Math.round(hsv.h) % 360; hsv.s = Math.round(hsv.s); hsv.v = Math.round(hsv.v); */ hsv.a = Math.round(hsv.a); color = this.getColor(); } var val = 'rgba('+color.join(', ')+')'; // Show color this._elt.picker.style.color = 'hsl(' + hsv.h + ', 100%, 50%)'; this._elt.slider.style.backgroundImage = 'linear-gradient(45deg, transparent, rgba('+this.getColor(false).join(',')+'))'; this._elt.vignet.style.color = val; // RGB this._elt.r.value = color[0]; this._elt.g.value = color[1]; this._elt.b.value = color[2]; this._elt.a.value = color[3]; // Txt color this._elt.txtColor.classList.remove('ol-error') if (color[3]===1) { this._elt.txtColor.value = ol.color.toHexa(color); } else { this._elt.txtColor.value = val; } this._selectPalette(color); // Set input value if (this.input.value !== val) { this.input.value = val; this.input.dispatchEvent(new Event('change')); } }; /** Get current color * @param {boolean} [opacity=true] * @return {Array} */ ol.ext.input.Color.prototype.getColor = function(opacity) { return ol.color.fromHSV([this._hsv.h, this._hsv.s, this._hsv.v, (opacity !== false) ? this._hsv.a/100 : 1], 1); } /** * @private */ ol.ext.input.Color.prototype._addCustomColor = function(color) { var id = this.getColorID(color); if (this._paletteColor[id]) return; if (!color[3]) return; if (ol.ext.input.Color.customColorList.getArray().indexOf(id) < 0) { ol.ext.input.Color.customColorList.push(id); if (ol.ext.input.Color.customColorList.getLength() > 24) { ol.ext.input.Color.customColorList.removeAt(0) } } this.addPaletteColor(color); }; ol.ext.input.Color.prototype.clearCustomColor = function() { ol.ext.input.Color.customColorList.clear(); }; /** Convert color to id * @param {ol.colorLike} Color * @returns {number} */ ol.ext.input.Color.prototype.getColorID = function(color) { color = ol.color.asArray(color); if (color[3]===undefined) color[3] = 1; return color.join('-'); }; /** Convert color to id * @param {number} id * @returns {Array} Color */ ol.ext.input.Color.prototype.getColorFromID = function(id) { var c = id.split('-'); return ([parseFloat(c[0]), parseFloat(c[1]), parseFloat(c[2]), parseFloat(c[3])]); }; /** Checkbox input * @constructor * @extends {ol.ext.input.Base} * @param {*} options * @param {string} [options.className] * @param {Array} options.options an array of options to place in the popup { html:, title:, value: } * @param {Element} [options.input] input element, if non create one * @param {Element} [options.parent] parent element, if create an input * @param {boolean} [options.fixed=false] don't use a popup, default use a popup * @param {string} [options.align=left] align popup left/right/middle * @param {boolean} [options.fixed=false] no popup */ ol.ext.input.List = function(options) { options = options || {}; ol.ext.input.Base.call(this, options); this._content = ol.ext.element.create('DIV'); this.element = ol.ext.element.create('DIV', { html: this._content, className: 'ol-input-popup' }); this.set('hideOnClick', options.hideOnClick !== false); if (options.className) this.element.classList.add(options.className); if (options.fixed) { this.element.classList.add('ol-fixed'); this.set('hideOnClick', false); } switch (options.align) { case 'middle': this.set('hideOnClick', false); // fall through case 'rigth': this.element.classList.add('ol-' + options.align); break; default: break; } var input = this.input; if (input.parentNode) input.parentNode.insertBefore(this.element, input); this.element.appendChild(input); var popup = this.popup = ol.ext.element.create('UL', { className: 'ol-popup', parent: this.element }); var opts = []; options.options.forEach(option => { opts.push({ value: option.value, element: ol.ext.element.create('LI', { html: option.html, title: option.title || option.value, className: 'ol-option', click: function() { this.setValue(option.value); if (this.get('hideOnClick')) { popup.style.display = 'none'; setTimeout(function() { popup.style.display = ''; }, 200); } }.bind(this), parent: this.popup }) }) }); this.input.addEventListener('change', function() { var v = this.input.value; var val; opts.forEach(function(o) { if (o.value == v) { o.element.classList.add('ol-selected'); val = o.element; } else { o.element.classList.remove('ol-selected'); } }); this.dispatchEvent({ type: 'change:value', value: this.getValue() }); this._content.innerHTML = val ? val.innerHTML : ''; }.bind(this)); // Initial value var event = new Event('change'); setTimeout(function() { this.input.dispatchEvent(event); }.bind(this)); }; ol.ext.inherits(ol.ext.input.List, ol.ext.input.Base); /** Switch input * @constructor * @extends {ol.ext.input.Checkbox} * @fires check * @param {*} options * @param {string} [options.className] * @param {Element} [options.input] input element, if non create one * @param {Element} [options.parent] parent element, if create an input */ ol.ext.input.Radio = function(options) { options = options || {}; ol.ext.input.Checkbox.call(this, options); this.element.className = ('ol-ext-check ol-ext-radio ' + (options.className || '')).trim(); }; ol.ext.inherits(ol.ext.input.Radio, ol.ext.input.Checkbox); /** Checkbox input * @constructor * @extends {ol.ext.input.Slider} * @param {*} options * @param {string} [options.className] * @param {Element} [options.input] input element, if non create one * @param {Element} [options.parent] parent element, if create an input * @param {Array} [options.size] a list of size (default 0,2,3,5,8,13,21,34,55) */ ol.ext.input.Size = function(options) { options = options || {}; options.options = []; (options.size || [0,2,3,5,8,13,21,34,55]).forEach(function(i) { options.options.push({ value: i, html: ol.ext.element.create('DIV', { className: 'ol-option-'+i, style: { fontSize: i ? i+'px' : undefined } }) }) }) ol.ext.input.List.call(this, options); this._content.remove(); this.element.classList.add('ol-size'); }; ol.ext.inherits(ol.ext.input.Size, ol.ext.input.List); /** Get the current value * @returns {number} */ ol.ext.input.Size.prototype.getValue = function() { return parseFloat(ol.ext.input.List.prototype.getValue.call(this)); }; /** Switch input * @constructor * @extends {ol.ext.input.Checkbox} * @fires check * @param {*} options * @param {string} [options.className] * @param {Element} [options.input] input element, if non create one * @param {Element} [options.parent] parent element, if create an input */ ol.ext.input.Switch = function(options) { options = options || {}; ol.ext.input.Checkbox.call(this, options); this.element.className = ('ol-ext-toggle-switch ' + (options.className || '')).trim(); }; ol.ext.inherits(ol.ext.input.Switch, ol.ext.input.Checkbox); /** Checkbox input * @constructor * @extends {ol.ext.input.Slider} * @param {*} options * @param {string} [options.className] * @param {Element} [options.input] input element, if non create one * @param {Element} [options.parent] parent element, if create an input * @param {Array} [options.size] a list of size (default 0,1,2,3,5,10,15,20) */ ol.ext.input.Width = function(options) { options = options || {}; options.options = []; (options.size || [0,1,2,3,5,10,15,20]).forEach(function(i) { options.options.push({ value: i, html: ol.ext.element.create('DIV', { className: 'ol-option-'+i, style: { height: i || undefined } }) }) }); ol.ext.input.List.call(this, options); this._content.remove(); this.element.classList.add('ol-width'); }; ol.ext.inherits(ol.ext.input.Width, ol.ext.input.List); /** Get the current value * @returns {number} */ ol.ext.input.Width.prototype.getValue = function() { return parseFloat(ol.ext.input.List.prototype.getValue.call(this)); } /** @namespace ol.legend */ /*global ol*/ if (window.ol && !ol.legend) { ol.legend = {}; } /** Legend class to draw features in a legend element * @constructor * @fires select * @fires refresh * @param {*} options * @param {String} options.title Legend title * @param {ol.size | undefined} options.size Size of the symboles in the legend, default [40, 25] * @param {number | undefined} options.margin Size of the symbole's margin, default 10 * @param { ol.style.Text | undefined } options.textStyle a text style for the legend, default 16px sans-serif * @param { ol.style.Text | undefined } options.titleStyle a text style for the legend title, default textStyle + bold * @param { ol.style.Style | Array | ol.StyleFunction | undefined } options.style a style or a style function to use with features */ ol.legend.Legend = function(options) { options = options || {}; ol.Object.call(this); this._items = new ol.Collection(); var listeners = []; var tout; this._items.on('add', function(e) { listeners.push({ item: e.element, on: e.element.on('change', function() { this.refresh(); }.bind(this)) }); if (tout) { clearTimeout(tout); tout = null; } tout = setTimeout(function() { this.refresh(); }.bind(this), 0); }.bind(this)); this._items.on('remove', function(e) { for (var i=0; i | ol.StyleFunction | undefined } style a style or a style function to use with features */ ol.legend.Legend.prototype.setStyle = function(style) { this._style = style; this.refresh(); }; /** Add a new item to the legend * @param {olLegendItemOptions|ol.legend.Item} item */ ol.legend.Legend.prototype.addItem = function(item) { if (item instanceof ol.legend.Item) { this._items.push(item); } else { this._items.push(new ol.legend.Item(item)); } }; /** Get item collection * @param {ol.Collection} */ ol.legend.Legend.prototype.getItems = function() { return this._items; }; /** Draw legend text * @private */ ol.legend.Legend.prototype._drawText = function(ctx, text, x, y) { ctx.save(); ctx.scale(ol.has.DEVICE_PIXEL_RATIO, ol.has.DEVICE_PIXEL_RATIO); text = text || ''; var txt = text.split('\n'); if (txt.length===1) { ctx.fillText(text, x, y); } else { ctx.textBaseline = 'bottom'; ctx.fillText(txt[0], x, y); ctx.textBaseline = 'top'; ctx.fillText(txt[1], x, y); } ctx.restore(); }; /** Draw legend text * @private */ ol.legend.Legend.prototype._measureText = function(ctx, text) { var txt = (text || '').split('\n'); if (txt.length===1) { return ctx.measureText(text); } else { var m1 = ctx.measureText(txt[0]); var m2 = ctx.measureText(txt[1]); return { width: Math.max(m1.width, m2.width), height: m1.height + m2.height } } }; /** Refresh the legend */ ol.legend.Legend.prototype.refresh = function() { var table = this._listElement; table.innerHTML = ''; var margin = this.get('margin'); var width = this.get('size')[0] + 2 * margin; var height = this.get('lineHeight') || this.get('size')[1] + 2 * margin; var canvas = this.getCanvas(); var ctx = canvas.getContext('2d'); ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; var ratio = ol.has.DEVICE_PIXEL_RATIO; // Calculate width ctx.font = this._titleStyle.getFont(); var textWidth = this._measureText(ctx, this.getTitle('title')).width; this._items.forEach(function(r) { if (r.get('feature') || r.get('typeGeom') ) { ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._textStyle.getFont(); textWidth = Math.max(textWidth, this._measureText(ctx, r.get('title')).width + width); } else { ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._titleStyle.getFont(); textWidth = Math.max(textWidth, this._measureText(ctx, r.get('title')).width); } }.bind(this)); canvas.width = (textWidth + 2*margin) * ratio; canvas.height = (this._items.getLength()+1) * height * ratio; canvas.style.height = ((this._items.getLength()+1) * height) + 'px'; ctx.textBaseline = 'middle'; ctx.fillStyle = ol.color.asString(this._textStyle.getFill().getColor()); // Add Title if (this.getTitle()) { table.appendChild(this._title.getElement([width, height], function(b) { this.dispatchEvent({ type: 'select', index: -1, symbol: b, item: this._title }); }.bind(this))); ctx.font = this._titleStyle.getFont(); ctx.textAlign = 'center'; this._drawText(ctx, this.getTitle(), canvas.width/ratio/2, height/2); } // Add items this._items.forEach(function(r,i) { var index = i + (this.getTitle() ? 1 : 0); table.appendChild(r.getElement([width, height], function(b) { this.dispatchEvent({ type: 'select', index: i, symbol: b, item: r }); }.bind(this))); var item = r.getProperties(); ctx.textAlign = 'left'; if (item.feature || item.typeGeom) { canvas = this.getLegendImage(item, canvas, index); ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._textStyle.getFont(); this._drawText(ctx, r.get('title'), width + margin, (i+1.5)*height); } else { ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._titleStyle.getFont(); if (/\bcenter\b/.test(item.className)) { ctx.textAlign = 'center'; this._drawText(ctx, r.get('title'), canvas.width/ratio/2, (i+1.5)*height); } else { this._drawText(ctx, r.get('title'), margin, (i+1.5)*height); } } }.bind(this)); // Done this.dispatchEvent({ type: 'refresh', width: width, height: (this._items.length+1)*height }); }; /** Get the image for a style * @param {olLegendItemOptions} item * @param {Canvas|undefined} canvas a canvas to draw in, if none creat one * @param {int|undefined} row row number to draw in canvas, default 0 * @return {CanvasElement} */ ol.legend.Legend.prototype.getLegendImage = function(options, canvas, row) { options = options || {}; return ol.legend.Legend.getLegendImage({ className: options.className, feature: options.feature, typeGeom: options.typeGeom, style: options.style || this._style, properties: options.properties, margin: options.margin || this.get('margin'), size: options.size || this.get('size'), lineHeight: options.lineHeight || this.get('lineHeight'), onload: function() { // Force refresh this.refresh(); }.bind(this) }, canvas, row); }; /** Get a symbol image for a given legend item * @param {olLegendItemOptions} item * @param {Canvas|undefined} canvas a canvas to draw in, if none creat one * @param {int|undefined} row row number to draw in canvas, default 0 */ ol.legend.Legend.getLegendImage = function(item, canvas, row) { item = item || {}; if (typeof(item.margin) === 'undefined') item.margin = 10; var size = item.size || [40,25]; item.onload = item.onload || function() { setTimeout(function() { ol.legend.Legend.getLegendImage(item, canvas, row); }, 100); }; var width = size[0] + 2 * item.margin; var height = item.lineHeight || (size[1] + 2 * item.margin); var ratio = ol.has.DEVICE_PIXEL_RATIO; if (!canvas) { row = 0; canvas = document.createElement('canvas'); canvas.width = width * ratio; canvas.height = height * ratio; } var ctx = canvas.getContext('2d'); ctx.save(); var vectorContext = ol.render.toContext(ctx, { pixelRatio: ratio }); var typeGeom = item.typeGeom; var style; var feature = item.feature; if (!feature && typeGeom) { if (/Point/.test(typeGeom)) feature = new ol.Feature(new ol.geom.Point([0,0])); else if (/LineString/.test(typeGeom)) feature = new ol.Feature(new ol.geom.LineString([0,0])); else feature = new ol.Feature(new ol.geom.Polygon([[0,0]])); if (item.properties) feature.setProperties(item.properties); } if (feature) { style = feature.getStyle(); if (typeof(style)==='function') style = style(feature); if (!style) { style = typeof(item.style) === 'function' ? item.style(feature) : item.style || []; } typeGeom = feature.getGeometry().getType(); } else { style = []; } if (!(style instanceof Array)) style = [style]; var cx = width/2; var cy = height/2; var sx = size[0]/2; var sy = size[1]/2; var i, s; // Get point offset if (typeGeom === 'Point') { var extent = null; for (i=0; s= style[i]; i++) { var img = s.getImage(); // Refresh legend on image load if (img) { var imgElt = img.getImage(); // Check image is loaded if (imgElt && imgElt.complete && !imgElt.naturalWidth) { if (typeof(item.onload) === 'function') { imgElt.addEventListener('load', function() { setTimeout(function() { item.onload() }, 100); }); } img.load(); } // Check anchor to center the image if (img.getAnchor) { var anchor = img.getAnchor(); if (anchor) { var si = img.getSize(); var dx = anchor[0] - si[0]; var dy = anchor[1] - si[1]; if (!extent) { extent = [dx, dy, dx+si[0], dy+si[1]]; } else { ol.extent.extend(extent, [dx, dy, dx+si[0], dy+si[1]]); } } } } } if (extent) { cx = cx + (extent[2] + extent[0])/2; cy = cy + (extent[3] + extent[1])/2; } } // Draw image cy += (row*height) || 0; for (i=0; s= style[i]; i++) { vectorContext.setStyle(s); switch (typeGeom) { case ol.geom.Point: case 'Point': case 'MultiPoint': vectorContext.drawGeometry(new ol.geom.Point([cx, cy])); break; case ol.geom.LineString: case 'LineString': case 'MultiLineString': ctx.save(); ctx.rect(item.margin * ratio, 0, size[0] * ratio, canvas.height); ctx.clip(); vectorContext.drawGeometry(new ol.geom.LineString([[cx-sx, cy], [cx+sx, cy]])); ctx.restore(); break; case ol.geom.Polygon: case 'Polygon': case 'MultiPolygon': vectorContext.drawGeometry(new ol.geom.Polygon([[[cx-sx, cy-sy], [cx+sx, cy-sy], [cx+sx, cy+sy], [cx-sx, cy+sy], [cx-sx, cy-sy]]])); break; } } ctx.restore(); return canvas; }; /** ol/legend/Item options * @typedef {Object} olLegendItemOptions * @property {string} title row title * @property {className} className * @property {ol.Feature} feature a feature to draw on the legend * @property {string} typeGeom type geom to draw with the style or the properties if no feature is provided * @property {Object} properties a set of properties to use with a style function * @property {ol.style.Style.styleLike} style a style or a style function to use to draw the legend symbol * @property {ol.style.Text} textStyle a text style to draw the item title in the legend * @property {ol.size|undefined} size * @property {number|undefined} margin */ /** A class for legend items * @constructor * @fires select * @param {olLegendItemOptions} options */ ol.legend.Item = function(options) { options = options || {}; ol.Object.call(this, options); if (options.feature) this.set('feature', options.feature.clone()); }; ol.ext.inherits(ol.legend.Item, ol.Object); /** Set the legend title * @param {string} title */ ol.legend.Item.prototype.setTitle = function(title) { this.set('title', title || ''); this.changed(); }; /** Get element * @param {ol.size} size symbol size */ ol.legend.Item.prototype.getElement = function(size, onclick) { var element = ol.ext.element.create('LI', { className : this.get('className'), click: function(e) { onclick(false); e.stopPropagation(); }, style: { height: size[1] + 'px' }, 'aria-label': this.get('title') }); ol.ext.element.create ('DIV', { click: function(e) { onclick(true); e.stopPropagation(); }, style: { width: size[0] + 'px', height: size[1] + 'px' }, parent: element }); return element; }; /** * @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 {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) { try { oldmap.renderSync(); } catch(e) { /* ok */ } } 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} options.features a collection of feature to search in, the collection will be kept in date while selection * @param {ol.source.Vector | Array} 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|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} 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.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); // Try to test numeric values var isNumber = (Number(val) == val && Number(condition.val) == condition.val); if (isNumber) val = Number(val); // Check var rex; switch (condition.op) { case '=': if (isNumber) { return val == condition.val; } else { rex = new RegExp('^'+this._escape(condition.val)+'$', usecase ? '' : 'i'); return rex.test(val); } case '!=': if (isNumber) { return val != condition.val; } else { 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} result the current list of features * @param {Array} 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.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|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} options.conditions array of conditions * @return {Array} * @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.name) bt.name = options.name; 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.Button.prototype.setHtml = function(html) { ol.ext.element.setHTML (this.button_, html); }; /** * Get the button element * @returns {Element} */ ol.control.Button.prototype.getButtonElement = function() { return this.button_; }; /* 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 toggle control * The control can be created with an interaction to control its activation. * * @constructor * @extends {ol.control.Button} * @fires change:active, change:disable * @param {Object=} options Control options. * @param {String} options.className class of the control * @param {String} options.title title of the control * @param {String} options.html html to insert in the control * @param {ol.interaction} options.interaction interaction associated with the control * @param {bool} options.active the control is created active, default false * @param {bool} options.disable the control is created disabled, default false * @param {ol.control.Bar} options.bar a subbar associated with the control (drawn when active if control is nested in a ol.control.Bar) * @param {bool} options.autoActive the control will activate when shown in an ol.control.Bar, default false * @param {function} options.onToggle callback when control is clicked (or use change:active event) */ ol.control.Toggle = function(options) { options = options || {}; var self = this; this.interaction_ = options.interaction; if (this.interaction_) { this.interaction_.setActive(options.active); this.interaction_.on("change:active", function() { self.setActive(self.interaction_.getActive()); }); } if (options.toggleFn) options.onToggle = options.toggleFn; // compat old version options.handleClick = function() { self.toggle(); if (options.onToggle) options.onToggle.call(self, self.getActive()); }; options.className = (options.className||"") + " ol-toggle"; ol.control.Button.call(this, options); this.set("title", options.title); this.set ("autoActivate", options.autoActivate); if (options.bar) this.setSubBar(options.bar); this.setActive (options.active); this.setDisable (options.disable); }; ol.ext.inherits(ol.control.Toggle, ol.control.Button); /** * Set the map instance the control is associated with * and add interaction attached to it to this map. * @param {_ol_Map_} map The map instance. */ ol.control.Toggle.prototype.setMap = function(map) { if (!map && this.getMap()) { if (this.interaction_) { this.getMap().removeInteraction (this.interaction_); } if (this.subbar_) this.getMap().removeControl (this.subbar_); } ol.control.Button.prototype.setMap.call(this, map); if (map) { if (this.interaction_) map.addInteraction (this.interaction_); if (this.subbar_) map.addControl (this.subbar_); } }; /** Get the subbar associated with a control * @return {ol.control.Bar} */ ol.control.Toggle.prototype.getSubBar = function () { return this.subbar_; }; /** Set the subbar associated with a control * @param {ol.control.Bar} [bar] a subbar if none remove the current subbar */ ol.control.Toggle.prototype.setSubBar = function (bar) { var map = this.getMap(); if (map && this.subbar_) map.removeControl (this.subbar_); this.subbar_ = bar; if (bar) { this.subbar_.setTarget(this.element); this.subbar_.element.classList.add("ol-option-bar"); if (map) map.addControl (this.subbar_); } }; /** * Test if the control is disabled. * @return {bool}. * @api stable */ ol.control.Toggle.prototype.getDisable = function() { var button = this.element.querySelector("button"); return button && button.disabled; }; /** Disable the control. If disable, the control will be deactivated too. * @param {bool} b disable (or enable) the control, default false (enable) */ ol.control.Toggle.prototype.setDisable = function(b) { if (this.getDisable()==b) return; this.element.querySelector("button").disabled = b; if (b && this.getActive()) this.setActive(false); this.dispatchEvent({ type:'change:disable', key:'disable', oldValue:!b, disable:b }); }; /** * Test if the control is active. * @return {bool}. * @api stable */ ol.control.Toggle.prototype.getActive = function() { return this.element.classList.contains("ol-active"); }; /** Toggle control state active/deactive */ ol.control.Toggle.prototype.toggle = function() { if (this.getActive()) this.setActive(false); else this.setActive(true); }; /** Change control state * @param {bool} b activate or deactivate the control, default false */ ol.control.Toggle.prototype.setActive = function(b) { if (this.interaction_) this.interaction_.setActive (b); if (this.subbar_) this.subbar_.setActive(b); if (this.getActive()===b) return; if (b) this.element.classList.add("ol-active"); else this.element.classList.remove("ol-active"); this.dispatchEvent({ type:'change:active', key:'active', oldValue:!b, active:b }); }; /** Set the control interaction * @param {_ol_interaction_} i interaction to associate with the control */ ol.control.Toggle.prototype.setInteraction = function(i) { this.interaction_ = i; }; /** Get the control interaction * @return {_ol_interaction_} interaction associated with the control */ ol.control.Toggle.prototype.getInteraction = function() { return this.interaction_; }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Search Control. * This is the base class for search controls. You can use it for simple custom search or as base to new class. * @see ol.control.SearchFeature * @see ol.control.SearchPhoton * * @constructor * @extends {ol.control.Control} * @fires select * @fires change:input * @param {Object=} options * @param {string} options.className control class name * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.title Title to use for the search button tooltip, default "Search" * @param {string | undefined} options.reverseTitle Title to use for the reverse geocoding button tooltip, default "Click on the map..." * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {boolean | undefined} options.reverse enable reverse geocoding, default false * @param {string | undefined} options.inputLabel label for the input, default none * @param {string | undefined} options.collapsed search is collapsed on start, default true * @param {string | undefined} options.noCollapse prevent collapsing on input blur, default false * @param {number | undefined} options.typing a delay on each typing to start searching (ms) use -1 to prevent autocompletion, default 300. * @param {integer | undefined} options.minLength minimum length to start searching, default 1 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * @param {integer | undefined} options.maxHistory maximum number of items to display in history. Set -1 if you don't want history, default maxItems * @param {function} options.getTitle a function that takes a feature and return the name to display in the index. * @param {function} options.autocomplete a function that take a search string and callback function to send an array * @param {function} options.onselect a function called when a search is selected * @param {boolean} options.centerOnSelect center map on search, default false * @param {number|boolean} options.zoomOnSelect center map on search and zoom to value if zoom < value, default false */ ol.control.Search = function(options) { var self = this; if (!options) options = {}; if (options.typing == undefined) options.typing = 300; // Class name for history this._classname = options.className || 'search'; var classNames = (options.className||'')+ ' ol-search' + (options.target ? '' : ' ol-unselectable ol-control'); var element = ol.ext.element.create('DIV',{ className: classNames }) if (options.collapsed!==false) element.classList.add('ol-collapsed'); if (!options.target) { this.button = document.createElement('BUTTON'); this.button.setAttribute('type', 'button'); this.button.setAttribute('title', options.title || options.label || 'Search'); this.button.addEventListener('click', function() { element.classList.toggle('ol-collapsed'); if (!element.classList.contains('ol-collapsed')) { element.querySelector('input.search').focus(); var listElements = element.querySelectorAll('li'); for (var i = 0; i < listElements.length; i++) { listElements[i].classList.remove('select'); } // Display history if (!input.value) { self.drawList_(); } } }); element.appendChild(this.button); } // Input label if (options.inputLabel) { var label = document.createElement("LABEL"); label.innerText = options.inputLabel; element.appendChild(label); } // Search input var tout, cur=""; var input = this._input = document.createElement("INPUT"); input.setAttribute("type", "search"); input.setAttribute("class", "search"); input.setAttribute("autocomplete", "off"); input.setAttribute("placeholder", options.placeholder||"Search..."); input.addEventListener("change", function(e) { self.dispatchEvent({ type:"change:input", input:e, value:input.value }); }); var doSearch = function(e) { // console.log(e.type+" "+e.key)' var li = element.querySelector("ul.autocomplete li.select"); var val = input.value; // move up/down if (e.key=='ArrowDown' || e.key=='ArrowUp' || e.key=='Down' || e.key=='Up') { if (li) { li.classList.remove("select"); li = (/Down/.test(e.key)) ? li.nextElementSibling : li.previousElementSibling; if (li) li.classList.add("select"); } else element.querySelector("ul.autocomplete li").classList.add("select"); } // Clear input else if (e.type=='input' && !val) { setTimeout(function(){ self.drawList_(); }, 200); } // Select in the list else if (li && (e.type=="search" || e.key =="Enter")) { if (element.classList.contains("ol-control")) input.blur(); li.classList.remove("select"); cur = val; self._handleSelect(self._list[li.getAttribute("data-search")]); } // Search / autocomplete else if ( (e.type=="search" || e.key =='Enter') || (cur!=val && options.typing>=0)) { // current search cur = val; if (cur) { // prevent searching on each typing if (tout) clearTimeout(tout); var minLength = self.get("minLength"); tout = setTimeout(function() { if (cur.length >= minLength) { var s = self.autocomplete (cur, function(auto) { self.drawList_(auto); }); if (s) self.drawList_(s); } else self.drawList_(); }, options.typing); } else self.drawList_(); } // Clear list selection else { li = element.querySelector("ul.autocomplete li"); if (li) li.classList.remove('select'); } }; input.addEventListener("keyup", doSearch); input.addEventListener("search", doSearch); input.addEventListener("cut", doSearch); input.addEventListener("paste", doSearch); input.addEventListener("input", doSearch); if (!options.noCollapse) { input.addEventListener('blur', function() { setTimeout(function(){ if (input !== document.activeElement) { element.classList.add('ol-collapsed'); this.set('reverse', false); element.classList.remove('ol-revers'); } }.bind(this), 200); }.bind(this)); input.addEventListener('focus', function() { if (!this.get('reverse')) { element.classList.remove('ol-collapsed'); element.classList.remove('ol-revers'); } }.bind(this)); } element.appendChild(input); // Reverse geocode if (options.reverse) { var reverse = ol.ext.element.create('BUTTON', { type: 'button', class: 'ol-revers', title: options.reverseTitle || 'click on the map', click: function() { if (!this.get('reverse')) { this.set('reverse', !this.get('reverse')); input.focus(); element.classList.add('ol-revers'); } else { this.set('reverse', false); } }.bind(this) }); element.appendChild(reverse); } // Autocomplete list var ul = document.createElement('UL'); ul.classList.add('autocomplete'); element.appendChild(ul); ol.control.Control.call(this, { element: element, target: options.target }); if (typeof (options.getTitle)=='function') this.getTitle = options.getTitle; if (typeof (options.autocomplete)=='function') this.autocomplete = options.autocomplete; // Options this.set('copy', options.copy); this.set('minLength', options.minLength || 1); this.set('maxItems', options.maxItems || 10); this.set('maxHistory', options.maxHistory || options.maxItems || 10); // Select if (options.onselect) this.on('select', options.onselect); // Center on select if (options.centerOnSelect) this.on('select', function(e) { var map = this.getMap(); if (map) { map.getView().setCenter(e.coordinate); } }.bind(this)); // Zoom on select if (options.zoomOnSelect) this.on('select', function(e) { var map = this.getMap(); if (map) { map.getView().setCenter(e.coordinate); if (map.getView().getZoom() < options.zoomOnSelect) map.getView().setZoom(options.zoomOnSelect); } }.bind(this)); // History this.restoreHistory(); this.drawList_(); }; ol.ext.inherits(ol.control.Search, 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 {ol.Map} map Map. * @api stable */ ol.control.Search.prototype.setMap = function (map) { if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; ol.control.Control.prototype.setMap.call(this, map); if (map) { this._listener = map.on('click', this._handleClick.bind(this)); } }; /** Collapse the search * @param {boolean} [b=true] * @api */ ol.control.Search.prototype.collapse = function (b) { if (b===false) this.element.classList.remove('ol-collapsed') else this.element.classList.add('ol-collapsed') }; /** Get the input field * @return {Element} * @api */ ol.control.Search.prototype.getInputField = function () { return this._input; }; /** Returns the text to be displayed in the menu * @param {any} f feature to be displayed * @return {string} the text to be displayed in the index, default f.name * @api */ ol.control.Search.prototype.getTitle = function (f) { return f.name || "No title"; }; /** Returns title as text * @param {any} f feature to be displayed * @return {string} * @api */ ol.control.Search.prototype._getTitleTxt = function (f) { return ol.ext.element.create('DIV', { html: this.getTitle(f) }).innerText; }; /** Force search to refresh */ ol.control.Search.prototype.search = function () { var search = this.element.querySelector("input.search"); this._triggerCustomEvent('search', search); }; /** Reverse geocode * @param {Object} event * @param {ol.coordinate} event.coordinate * @private */ ol.control.Search.prototype._handleClick = function (e) { if (this.get('reverse')) { document.activeElement.blur(); this.reverseGeocode(e.coordinate); } }; /** Reverse geocode * @param {ol.coordinate} coord * @param {function | undefined} cback a callback function, default trigger a select event * @api */ ol.control.Search.prototype.reverseGeocode = function (/*coord, cback*/) { // this._handleSelect(f); }; /** Trigger custom event on elemebt * @param {*} eventName * @param {*} element * @private */ ol.control.Search.prototype._triggerCustomEvent = function (eventName, element) { ol.ext.element.dispatchEvent(eventName, element); }; /** Set the input value in the form (for initialisation purpose) * @param {string} value * @param {boolean} search to start a search * @api */ ol.control.Search.prototype.setInput = function (value, search) { var input = this.element.querySelector("input.search"); input.value = value; if (search) this._triggerCustomEvent("keyup", input); }; /** A line has been clicked in the menu > dispatch event * @param {any} f the feature, as passed in the autocomplete * @param {boolean} reverse true if reverse geocode * @param {ol.coordinate} coord * @param {*} options options passed to the event * @api */ ol.control.Search.prototype.select = function (f, reverse, coord, options) { var event = { type:"select", search:f, reverse: !!reverse, coordinate: coord }; if (options) { for (var i in options) { event[i] = options[i]; } } this.dispatchEvent(event); }; /** * Save history and select * @param {*} f * @param {boolean} reverse true if reverse geocode * @param {*} options options send in the event * @private */ ol.control.Search.prototype._handleSelect = function (f, reverse, options) { if (!f) return; // Save input in history var hist = this.get('history'); // Prevent error on stringify var i; try { var fstr = JSON.stringify(f); for (i=hist.length-1; i>=0; i--) { if (!hist[i] || JSON.stringify(hist[i]) === fstr) { hist.splice(i,1); } } } catch (e) { for (i=hist.length-1; i>=0; i--) { if (hist[i] === f) { hist.splice(i,1); } } } hist.unshift(f); var size = Math.max(0, this.get('maxHistory')||10) || 0; while (hist.length > size) { hist.pop(); } this.saveHistory(); // Select feature this.select(f, reverse, null, options); if (reverse) { this.setInput(this._getTitleTxt(f)); this.drawList_(); setTimeout(function() { this.collapse(false); }.bind(this), 300); } }; /** Current history */ ol.control.Search.prototype._history = {}; /** Save history (in the localstorage) */ ol.control.Search.prototype.saveHistory = function () { try { if (this.get('maxHistory')>=0) { localStorage["ol@search-"+this._classname] = JSON.stringify(this.get('history')); } else { localStorage.removeItem("ol@search-"+this._classname); } } catch(e) { console.warn('Failed to access localStorage...'); } }; /** Restore history (from the localstorage) */ ol.control.Search.prototype.restoreHistory = function () { if (this._history[this._classname]) { this.set('history', this._history[this._classname]); } else { try { this._history[this._classname] = JSON.parse(localStorage["ol@search-"+this._classname]); this.set('history', this._history[this._classname]); } catch(e) { this.set('history', []); } } }; /** * Remove previous history */ ol.control.Search.prototype.clearHistory = function () { this.set('history', []); this.saveHistory(); this.drawList_(); }; /** * Get history table */ ol.control.Search.prototype.getHistory = function () { return this.get('history'); }; /** Autocomplete function * @param {string} s search string * @param {function} cback a callback function that takes an array to display in the autocomplete field (for asynchronous search) * @return {Array|false} an array of search solutions or false if the array is send with the cback argument (asnchronous) * @api */ ol.control.Search.prototype.autocomplete = function (s, cback) { cback ([]); return false; // or just return []; }; /** Draw the list * @param {Array} auto an array of search result * @private */ ol.control.Search.prototype.drawList_ = function (auto) { var self = this; var ul = this.element.querySelector("ul.autocomplete"); ul.innerHTML = ''; this._list = []; if (!auto) { var input = this.element.querySelector("input.search"); var value = input.value; if (!value) { auto = this.get('history'); } else { return; } ul.setAttribute('class', 'autocomplete history'); } else { ul.setAttribute('class', 'autocomplete'); } var li, max = Math.min (self.get("maxItems"),auto.length); for (var i=0; i= 200 && resp.status < 400) { if (typeof(this._callback) === 'function') this._callback(resp.response); } else { if (typeof(this._callback) === 'function') this._callback(false, 'error'); console.log('AJAX ERROR', arguments); } }.bind(this)); this._ajax.on('error', function() { if (typeof(this._callback) === 'function') this._callback(false, 'error'); console.log('AJAX ERROR', arguments); }.bind(this)); // Handle searchin this._ajax.on('loadstart', function() { this.element.classList.add('searching'); }.bind(this)); this._ajax.on('loadend', function() { this.element.classList.remove('searching'); }.bind(this)); // Overwrite handleResponse if (typeof(options.handleResponse)==='function') this.handleResponse = options.handleResponse; }; ol.ext.inherits(ol.control.SearchJSON, ol.control.Search); /** Send ajax request * @param {string} url * @param {*} data * @param {function} cback a callback function that takes an array of {name, feature} to display in the autocomplete field */ ol.control.SearchJSON.prototype.ajax = function (url, data, cback, options) { options = options || {}; this._callback = cback; this._ajax.set('dataType', options.dataType || 'JSON'); this._ajax.send(url, data, options); }; /** Autocomplete function (ajax request to the server) * @param {string} s search string * @param {function} cback a callback function that takes an array of {name, feature} to display in the autocomplete field */ ol.control.SearchJSON.prototype.autocomplete = function (s, cback) { var data = this.requestData(s); var url = encodeURI(this.get('url')); this.ajax(url, data, function(resp) { if (typeof(cback) === 'function') cback(this.handleResponse(resp)); }); }; /** * @param {string} s the search string * @return {Object} request data (as key:value) * @api */ ol.control.SearchJSON.prototype.requestData = function (s){ return { q: s }; }; /** * Handle server response to pass the features array to the display list * @param {any} response server response * @return {Array} an array of feature * @api */ ol.control.SearchJSON.prototype.handleResponse = function (response) { return response; }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Search places using the photon API. * * @constructor * @extends {ol.control.SearchJSON} * @fires select * @param {Object=} Control options. * @param {string} options.className control class name * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.title Title to use for the search button tooltip, default "Search" * @param {string | undefined} options.reverseTitle Title to use for the reverse geocoding button tooltip, default "Click on the map..." * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 1000. * @param {integer | undefined} options.minLength minimum length to start searching, default 3 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * @param {function | undefined} options.handleResponse Handle server response to pass the features array to the list * * @param {string|undefined} options.url Url to photon api, default "http://photon.komoot.de/api/" * @param {string|undefined} options.lang Force preferred language, default none * @param {boolean} options.position Search, with priority to geo position, default false * @param {function} options.getTitle a function that takes a feature and return the name to display in the index, default return street + name + contry */ ol.control.SearchPhoton = function(options) { options = options || {}; options.className = options.className || 'photon'; options.url = options.url || 'https://photon.komoot.io/api/'; options.copy = options.copy || '© OpenStreetMap contributors'; ol.control.SearchJSON.call(this, options); this.set('lang', options.lang); this.set('position', options.position); }; ol.ext.inherits(ol.control.SearchPhoton, ol.control.SearchJSON); /** Returns the text to be displayed in the menu * @param {ol.Feature} f the feature * @return {string} the text to be displayed in the index * @api */ ol.control.SearchPhoton.prototype.getTitle = function (f) { var p = f.properties; return (p.housenumber||"") + " "+(p.street || p.name || "") + "" + " "+(p.postcode||"") + " "+(p.city||"") + " ("+p.country + ")"; }; /** * @param {string} s the search string * @return {Object} request data (as key:value) * @api */ ol.control.SearchPhoton.prototype.requestData = function (s) { var data = { q: s, lang: this.get('lang'), limit: this.get('maxItems') } // Handle position proirity if (this.get('position')) { var view = this.getMap().getView(); var pt = new ol.geom.Point(view.getCenter()); pt = (pt.transform (view.getProjection(), "EPSG:4326")).getCoordinates(); data.lon = pt[0]; data.lat = pt[1]; } return data; }; /** * Handle server response to pass the features array to the list * @param {any} response server response * @return {Array} an array of feature */ ol.control.SearchPhoton.prototype.handleResponse = function (response) { return response.features; }; /** Prevent same feature to be drawn twice: test equality * @param {} f1 First feature to compare * @param {} f2 Second feature to compare * @return {boolean} * @api */ ol.control.SearchPhoton.prototype.equalFeatures = function (f1, f2) { return (this.getTitle(f1) === this.getTitle(f2) && f1.geometry.coordinates[0] === f2.geometry.coordinates[0] && f1.geometry.coordinates[1] === f2.geometry.coordinates[1]); }; /** A ligne has been clicked in the menu > dispatch event * @param {any} f the feature, as passed in the autocomplete * @api */ ol.control.SearchPhoton.prototype.select = function (f) { var c = f.geometry.coordinates; // Add coordinate to the event try { c = ol.proj.transform (f.geometry.coordinates, 'EPSG:4326', this.getMap().getView().getProjection()); } catch(e) { /* ok */ } this.dispatchEvent({ type:"select", search:f, coordinate: c }); }; /** Get data for reverse geocode * @param {ol.coordinate} coord */ ol.control.SearchPhoton.prototype.reverseData = function (coord) { var lonlat = ol.proj.transform (coord, this.getMap().getView().getProjection(), 'EPSG:4326'); return { lon: lonlat[0], lat: lonlat[1] }; }; /** Reverse geocode * @param {ol.coordinate} coord * @api */ ol.control.SearchPhoton.prototype.reverseGeocode = function (coord, cback) { this.ajax( this.get('url').replace('/api/', '/reverse/').replace('/search/', '/reverse/'), this.reverseData(coord), function(resp) { if (resp.features) resp = resp.features; if (!(resp instanceof Array)) resp = [resp]; if (cback) { cback.call(this, resp); } else { this._handleSelect(resp[0], true); // this.setInput('', true); } }.bind(this) ); }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Search places using the French National Base Address (BAN) API. * * @constructor * @extends {ol.control.SearchJSON} * @fires select * @param {any} options extend ol.control.SearchJSON options * @param {string} options.className control class name * @param {string | undefined} [options.apiKey] the service api key. * @param {string | undefined} options.authentication: basic authentication for the service API as btoa("login:pwd") * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {boolean | undefined} options.reverse enable reverse geocoding, default false * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500. * @param {integer | undefined} options.minLength minimum length to start searching, default 3 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * * @param {StreetAddress|PositionOfInterest|CadastralParcel|Commune} options.type type of search. Using Commune will return the INSEE code, default StreetAddress,PositionOfInterest * @see {@link https://geoservices.ign.fr/documentation/geoservices/geocodage.html} */ ol.control.SearchGeoportail = function(options) { options = options || {}; options.className = options.className || 'IGNF'; options.typing = options.typing || 500; options.url = 'https://wxs.ign.fr/' + (options.apiKey || 'essentiels') + '/ols/apis/completion'; options.copy = '© IGN-Géoportail'; ol.control.SearchJSON.call(this, options); this.set('type', options.type || 'StreetAddress,PositionOfInterest'); this.set('timeout', options.timeout || 2000); // Authentication // this._auth = options.authentication; }; ol.ext.inherits(ol.control.SearchGeoportail, ol.control.SearchJSON); /** Reverse geocode * @param {ol.coordinate} coord * @param {function|*} options callback function called when revers located or options passed to the select event * @api */ ol.control.SearchGeoportail.prototype.reverseGeocode = function (coord, options) { var lonlat = ol.proj.transform(coord, this.getMap().getView().getProjection(), 'EPSG:4326'); this._handleSelect({ x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) }, true, options); // Search type var type = this.get('type')==='Commune' ? 'PositionOfInterest' : this.get('type') || 'StreetAddress'; if (/,/.test(type)) type = 'StreetAddress'; // request var request = '' +'' +' ' +' ' +' '+type+'' +' ' +' '+lonlat[1]+' '+lonlat[0]+'' +' ' +' ' +' ' +''; this.ajax (this.get('url').replace('ols/apis/completion','geoportail/ols'), { xls: request }, function(xml) { var f = {}; if (!xml) { f = { x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) } } else { xml = xml.replace(/\n|\r/g,''); var p = (xml.replace(/.*(.*)<\/gml:pos>.*/, "$1")).split(' '); if (!Number(p[1]) && !Number(p[0])) { f = { x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) } } else { f.x = lonlat[0]; f.y = lonlat[1]; f.city = (xml.replace(/.*([^<]*)<\/Place>.*/, "$1")); f.insee = (xml.replace(/.*([^<]*)<\/Place>.*/, "$1")); f.zipcode = (xml.replace(/.*([^<]*)<\/PostalCode>.*/, "$1")); if (//.test(xml)) { f.kind = ''; f.country = 'StreetAddress'; f.street = (xml.replace(/.*([^<]*)<\/Street>.*/, "$1")); var number = (xml.replace(/.*([^<]*)<\/Place>.*/, "$1")); f.country = 'PositionOfInterest'; f.street = ''; f.fulltext = f.zipcode+' '+f.city; } } } if (typeof(options)==='function') { options.call(this, [f]); } else { this.getHistory().shift(); this._handleSelect(f, true, options); // this.setInput('', true); // this.drawList_(); } }.bind(this), { timeout: this.get('timeout'), dataType: 'XML' } ); }; /** Returns the text to be displayed in the menu * @param {ol.Feature} f the feature * @return {string} the text to be displayed in the index * @api */ ol.control.SearchGeoportail.prototype.getTitle = function (f) { return (f.fulltext); }; /** * @param {string} s the search string * @return {Object} request data (as key:value) * @api */ ol.control.SearchGeoportail.prototype.requestData = function (s) { return { text: s, type: this.get('type')==='Commune' ? 'PositionOfInterest' : this.get('type') || 'StreetAddress,PositionOfInterest', maximumResponses: this.get('maxItems') }; }; /** * Handle server response to pass the features array to the display list * @param {any} response server response * @return {Array} an array of feature * @api */ ol.control.SearchGeoportail.prototype.handleResponse = function (response) { var features = response.results; if (this.get('type') === 'Commune') { for (var i=features.length-1; i>=0; i--) { if ( features[i].kind && (features[i].classification>5 || features[i].kind=="Département") ) { features.splice(i,1); } } } return features; }; /** A ligne has been clicked in the menu > dispatch event * @param {any} f the feature, as passed in the autocomplete * @param {boolean} reverse true if reverse geocode * @param {ol.coordinate} coord * @param {*} options options passed to the event * @api */ ol.control.SearchGeoportail.prototype.select = function (f, reverse, coord, options){ if (f.x || f.y) { var c = [Number(f.x), Number(f.y)]; // Add coordinate to the event try { c = ol.proj.transform (c, 'EPSG:4326', this.getMap().getView().getProjection()); } catch(e) { /* ok */} // Get insee commune ? if (this.get('type')==='Commune') { this.searchCommune(f, function () { ol.control.Search.prototype.select.call(this, f, reverse, c, options); //this.dispatchEvent({ type:"select", search:f, coordinate: c, revers: reverse, options: options }); }); } else { ol.control.Search.prototype.select.call(this, f, reverse, c, options); //this.dispatchEvent({ type:"select", search:f, coordinate: c, revers: reverse, options: options }); } } else { this.searchCommune(f); } }; /** Search if no position and get the INSEE code * @param {string} s le nom de la commune */ ol.control.SearchGeoportail.prototype.searchCommune = function (f, cback) { var request = '' +'' +'' +'' +'' +'
' +''+f.zipcode+' '+f.city+'+' +'
' +'
' +'
' +'
' // Search this.ajax (this.get('url').replace('ols/apis/completion','geoportail/ols'), { 'xls': request }, function(xml) { if (xml) { // XML to JSON var parser = new DOMParser(); var xmlDoc = parser.parseFromString(xml,"text/xml"); var com = xmlDoc.getElementsByTagName('GeocodedAddress')[0]; var coord = com.getElementsByTagName('gml:Point')[0].textContent.trim().split(' '); f.x = Number(coord[1]); f.y = Number(coord[0]); var place = com.getElementsByTagName('Place'); for (var i=0; i h-dh) { // Bug IE: need to have an height defined ol.ext.element.setStyle(this.element, { height: '100%' }); var li = this.panel_.querySelectorAll('li.visible .li-content')[0]; var lh = li ? 2 * ol.ext.element.getStyle(li, 'height') : 0; switch (dir) { case 1: top += lh; break; case -1: top -= lh; break; case "+50%": top += Math.round(h/2); break; case "-50%": top -= Math.round(h/2); break; default: break; } // Scroll div if (top+hp <= h-3*dh/2) { top = h-3*dh/2-hp; ol.ext.element.hide(this.botv); } else { ol.ext.element.show(this.botv); } if (top >= 0) { top = 0; ol.ext.element.hide(this.topv); } else { ol.ext.element.show(this.topv); } // Scroll ? ol.ext.element.setStyle(this.panel_, { top: top+"px" }); return true; } else { ol.ext.element.setStyle(this.element, { height: "auto" }); ol.ext.element.setStyle(this.panel_, { top: 0 }); ol.ext.element.hide(this.botv); ol.ext.element.hide(this.topv); return false; } } else return false; }; /** Set the layer associated with a li * @param {Element} li * @param {ol.layer} layer * @private */ ol.control.LayerSwitcher.prototype._setLayerForLI = function(li, layer) { var listeners = []; if (layer.getLayers) { listeners.push(layer.getLayers().on('change:length', this.drawPanel.bind(this))); } if (li) { // Handle opacity change listeners.push(layer.on('change:opacity', (function() { this.setLayerOpacity(layer, li); }).bind(this))); // Handle visibility chage listeners.push(layer.on('change:visible', (function() { this.setLayerVisibility(layer, li); }).bind(this))); } // Other properties listeners.push(layer.on('propertychange', (function(e) { if (e.key === 'displayInLayerSwitcher' || e.key === 'openInLayerSwitcher') { this.drawPanel(e); } }).bind(this))); this._layers.push({ li:li, layer:layer, listeners: listeners }); }; /** Set opacity for a layer * @param {ol.layer.Layer} layer * @param {Element} li the list element * @private */ ol.control.LayerSwitcher.prototype.setLayerOpacity = function(layer, li) { var i = li.querySelector('.layerswitcher-opacity-cursor') if (i) i.style.left = (layer.getOpacity()*100)+"%" this.dispatchEvent({ type: 'layer:opacity', layer: layer }); }; /** Set visibility for a layer * @param {ol.layer.Layer} layer * @param {Element} li the list element * @api */ ol.control.LayerSwitcher.prototype.setLayerVisibility = function(layer, li) { var i = li.querySelector('.ol-visibility'); if (i) i.checked = layer.getVisible(); if (layer.getVisible()) li.classList.add('ol-visible'); else li.classList.remove('ol-visible'); this.dispatchEvent({ type: 'layer:visible', layer: layer }); }; /** Clear layers associated with li * @private */ ol.control.LayerSwitcher.prototype._clearLayerForLI = function() { this._layers.forEach(function (li) { li.listeners.forEach(function(l) { ol.Observable.unByKey(l); }); }) this._layers = []; }; /** Get the layer associated with a li * @param {Element} li * @return {ol.layer} * @private */ ol.control.LayerSwitcher.prototype._getLayerForLI = function(li) { for (var i=0, l; l=this._layers[i]; i++) { if (l.li===li) return l.layer; } return null; }; /** * On view change hide layer depending on resolution / extent * @private */ ol.control.LayerSwitcher.prototype.viewChange = function() { this.panel_.querySelectorAll('li').forEach( function(li) { var l = this._getLayerForLI(li); if (l) { if (this.testLayerVisibility(l)) li.classList.remove('ol-layer-hidden'); else li.classList.add('ol-layer-hidden'); } }.bind(this)); }; /** Get control panel * @api */ ol.control.LayerSwitcher.prototype.getPanel = function() { return this.panelContainer_; }; /** Draw the panel control (prevent multiple draw due to layers manipulation on the map with a delay function) * @api */ ol.control.LayerSwitcher.prototype.drawPanel = function() { if (!this.getMap()) return; var self = this; // Multiple event simultaneously / draw once => put drawing in the event queue this.dcount++; setTimeout (function(){ self.drawPanel_(); }, this.get('drawDelay') || 0); }; /** Delayed draw panel control * @private */ ol.control.LayerSwitcher.prototype.drawPanel_ = function() { if (--this.dcount || this.dragging_) return; var scrollTop = this.panelContainer_.scrollTop; // Remove existing layers this._clearLayerForLI(); this.panel_.querySelectorAll('li').forEach (function(li) { if (!li.classList.contains('ol-header')) li.remove(); }.bind(this)); // Draw list if (this._layerGroup) this.drawList (this.panel_, this._layerGroup.getLayers()); else if (this.getMap()) this.drawList (this.panel_, this.getMap().getLayers()); // Reset scrolltop this.panelContainer_.scrollTop = scrollTop; }; /** Change layer visibility according to the baselayer option * @param {ol.layer} * @param {Array} related layers * @private */ ol.control.LayerSwitcher.prototype.switchLayerVisibility = function(l, layers) { if (!l.get('baseLayer')) { l.setVisible(!l.getVisible()); } else { if (!l.getVisible()) l.setVisible(true); layers.forEach(function(li) { if (l!==li && li.get('baseLayer') && li.getVisible()) li.setVisible(false); }); } }; /** Check if layer is on the map (depending on resolution / zoom and extent) * @param {ol.layer} * @return {boolean} * @private */ ol.control.LayerSwitcher.prototype.testLayerVisibility = function(layer) { if (!this.getMap()) return true; var res = this.getMap().getView().getResolution(); var zoom = this.getMap().getView().getZoom(); // Calculate visibility on resolution or zoom if (layer.getMaxResolution()<=res || layer.getMinResolution()>=res) { return false; } else if (layer.getMinZoom && (layer.getMinZoom()>=zoom || layer.getMaxZoom()j) collection.insertAt (j,drop); else collection.insertAt (j+1,drop); break; } } } if (isSelected) self.selectLayer(drop); self.dispatchEvent({ type: "reorder-end", layer: drop, group: group }); } elt.parentNode.querySelectorAll('li').forEach(function(li){ li.classList.remove('dropover'); li.classList.remove('dropover-after'); li.classList.remove('dropover-before'); }); elt.classList.remove("drag"); elt.parentNode.classList.remove("drag"); self.element.classList.remove('drag'); if (dragElt) dragElt.remove(); ol.ext.element.removeListener(document, 'mousemove touchmove', move); ol.ext.element.removeListener(document, 'mouseup touchend touchcancel', stop); } // Ordering function move(e) { // First drag (more than 2 px) => show drag element (ghost) pageY = e.pageY || (e.touches && e.touches.length && e.touches[0].pageY) || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY); if (start && Math.abs(pageY0 - pageY) > 2) { start = false; elt.classList.add("drag"); layer = self._getLayerForLI(elt); target = false; group = self._getLayerForLI(elt.parentNode.parentNode); // Ghost div dragElt = ol.ext.element.create('LI', { className: 'ol-dragover', html: elt.innerHTML, style: { position: "absolute", "z-index": 10000, left: elt.offsetLeft, opacity: 0.5, width: ol.ext.element.outerWidth(elt), height: ol.ext.element.getStyle(elt,'height'), }, parent: panel }); self.element.classList.add('drag'); self.dispatchEvent({ type: "reorder-start", layer: layer, group: group }); } // Start a new drag sequence if (!start) { e.preventDefault(); e.stopPropagation(); // Ghost div ol.ext.element.setStyle(dragElt, { top: pageY - ol.ext.element.offsetRect(panel).top + panel.scrollTop +5 }); var li; if (!e.touches) { li = e.target; } else { li = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY); } if (li.classList.contains("ol-switcherbottomdiv")) { self.overflow(-1); } else if (li.classList.contains("ol-switchertopdiv")) { self.overflow(1); } // Get associated li while (li && li.tagName!=='LI') { li = li.parentNode; } if (!li || !li.classList.contains('dropover')) { elt.parentNode.querySelectorAll('li').forEach(function(li) { li.classList.remove('dropover'); li.classList.remove('dropover-after'); li.classList.remove('dropover-before'); }); } if (li && li.parentNode.classList.contains('drag') && li !== elt) { target = self._getLayerForLI(li); // Don't mix layer level if (target && !target.get('allwaysOnTop') == !layer.get('allwaysOnTop')) { li.classList.add("dropover"); li.classList.add((elt.offsetTop < li.offsetTop) ? 'dropover-after' : 'dropover-before'); } else { target = false; } ol.ext.element.show(dragElt); } else { target = false; if (li===elt) ol.ext.element.hide(dragElt); else ol.ext.element.show(dragElt); } if (!target) dragElt.classList.add("forbidden"); else dragElt.classList.remove("forbidden"); } } // Start ordering ol.ext.element.addListener(document, 'mousemove touchmove', move); ol.ext.element.addListener(document, 'mouseup touchend touchcancel', stop); }; /** Change opacity on drag * @param {event} e drag event * @private */ ol.control.LayerSwitcher.prototype.dragOpacity_ = function(e) { e.stopPropagation(); e.preventDefault(); var self = this // Register start params var elt = e.target; var layer = this._getLayerForLI(elt.parentNode.parentNode.parentNode); if (!layer) return; var x = e.pageX || (e.touches && e.touches.length && e.touches[0].pageX) || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); var start = ol.ext.element.getStyle (elt, 'left') - x; self.dragging_ = true; // stop dragging function stop() { ol.ext.element.removeListener(document, "mouseup touchend touchcancel", stop); ol.ext.element.removeListener(document, "mousemove touchmove", move); self.dragging_ = false; } // On draggin function move(e) { var x = e.pageX || (e.touches && e.touches.length && e.touches[0].pageX) || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); var delta = (start + x) / ol.ext.element.getStyle(elt.parentNode, 'width'); var opacity = Math.max (0, Math.min(1, delta)); ol.ext.element.setStyle (elt, { left: (opacity*100) + "%" }); elt.parentNode.nextElementSibling.innerHTML = Math.round(opacity*100); layer.setOpacity(opacity); } // Register move ol.ext.element.addListener(document, "mouseup touchend touchcancel", stop); ol.ext.element.addListener(document, "mousemove touchmove", move); }; /** Render a list of layer * @param {Elemen} element to render * @layers {Array{ol.layer}} list of layer to show * @api stable * @private */ ol.control.LayerSwitcher.prototype.drawList = function(ul, collection) { var self = this; var layers = collection.getArray(); // Change layer visibility var setVisibility = function(e) { e.stopPropagation(); e.preventDefault(); var l = self._getLayerForLI(this.parentNode.parentNode); self.switchLayerVisibility(l, collection); if (self.get('selection') && l.getVisible()) { self.selectLayer(l); } if (self.onchangeCheck) { self.onchangeCheck(l); } }; // Info button click function onInfo(e) { e.stopPropagation(); e.preventDefault(); var l = self._getLayerForLI(this.parentNode.parentNode); self.oninfo(l); self.dispatchEvent({ type: "info", layer: l }); } // Zoom to extent button function zoomExtent(e) { e.stopPropagation(); e.preventDefault(); var l = self._getLayerForLI(this.parentNode.parentNode); if (self.onextent) self.onextent(l); else self.getMap().getView().fit (l.getExtent(), self.getMap().getSize()); self.dispatchEvent({ type: "extent", layer: l }); } // Remove a layer on trash click function removeLayer(e) { e.stopPropagation(); e.preventDefault(); var li = this.parentNode.parentNode.parentNode.parentNode; var layer, group = self._getLayerForLI(li); // Remove the layer from a group or from a map if (group) { layer = self._getLayerForLI(this.parentNode.parentNode); group.getLayers().remove(layer); if (group.getLayers().getLength()==0 && !group.get('noSwitcherDelete')) { removeLayer.call(li.querySelectorAll('.layerTrash')[0], e); } } else { li = this.parentNode.parentNode; self.getMap().removeLayer(self._getLayerForLI(li)); } } // Create a list for a layer function createLi(layer) { if (!this.displayInLayerSwitcher(layer)) { this._setLayerForLI(null, layer); return; } var li = ol.ext.element.create('LI', { className: (layer.getVisible()?"visible ":" ")+(layer.get('baseLayer')?"baselayer":""), parent: ul }); this._setLayerForLI(li, layer); if (this._selectedLayer === layer) { li.classList.add('ol-layer-select'); } var layer_buttons = ol.ext.element.create('DIV', { className: 'ol-layerswitcher-buttons', parent: li }); // Content div var d = ol.ext.element.create('DIV', { className: 'li-content',// + (this.testLayerVisibility(layer) ? '' : ' ol-layer-hidden'), parent: li }); // Visibility ol.ext.element.create('INPUT', { type: layer.get('baseLayer') ? 'radio' : 'checkbox', className: 'ol-visibility', checked: layer.getVisible(), click: setVisibility, parent: d }); // Label var label = ol.ext.element.create('LABEL', { title: layer.get('title') || layer.get('name'), click: setVisibility, unselectable: 'on', style: { userSelect: 'none' }, parent: d }); label.addEventListener('selectstart', function(){ return false; }); ol.ext.element.create('SPAN', { html: layer.get('title') || layer.get('name'), click: function(e) { if (this.get('selection')) { e.stopPropagation(); this.selectLayer(layer); } }.bind(this), parent: label }); // up/down if (this.reordering) { if ( (i0 && (!layer.get("allwaysOnTop") || layers[i-1].get("allwaysOnTop")) ) ) { ol.ext.element.create('DIV', { className: 'layerup ol-noscroll', title: this.tip.up, on: { 'mousedown touchstart': function(e) { self.dragOrdering_(e) } }, parent: layer_buttons }); } } // Show/hide sub layers if (layer.getLayers) { var nb = 0; layer.getLayers().forEach(function(l) { if (self.displayInLayerSwitcher(l)) nb++; }); if (nb) { ol.ext.element.create('DIV', { className: layer.get("openInLayerSwitcher") ? "collapse-layers" : "expend-layers", title: this.tip.plus, click: function() { var l = self._getLayerForLI(this.parentNode.parentNode); l.set("openInLayerSwitcher", !l.get("openInLayerSwitcher") ) }, parent: layer_buttons }); } } // Info button if (this.oninfo) { ol.ext.element.create('DIV', { className: 'layerInfo', title: this.tip.info, click: onInfo, parent: layer_buttons }); } // Layer remove if (this.hastrash && !layer.get("noSwitcherDelete")) { ol.ext.element.create('DIV', { className: 'layerTrash', title: this.tip.trash, click: removeLayer, parent: layer_buttons }); } // Layer extent if (this.hasextent && layers[i].getExtent()) { var ex = layers[i].getExtent(); if (ex.length==4 && ex[0]=0; i--) { createLi.call(this, layers[i]); } this.viewChange(); if (ul === this.panel_) this.overflow(); }; /** Select a layer * @param {ol.layer.Layer} layer * @returns {string} the layer classname * @api */ ol.control.LayerSwitcher.prototype.getLayerClass = function(layer) { if (!layer) return 'none'; if (layer.getLayers) return 'ol-layer-group'; if (layer instanceof ol.layer.Vector) return 'ol-layer-vector'; if (layer instanceof ol.layer.VectorTile) return 'ol-layer-vectortile'; if (layer instanceof ol.layer.Tile) return 'ol-layer-tile'; if (layer instanceof ol.layer.Image) return 'ol-layer-image'; if (layer instanceof ol.layer.Heatmap) return 'ol-layer-heatmap'; /* ol < 6 compatibility VectorImage is not defined */ // if (layer instanceof ol.layer.VectorImage) return 'ol-layer-vectorimage'; if (layer.getFeatures) return 'ol-layer-vectorimage'; /* */ return 'unknown'; }; /** Select a layer * @param {ol.layer.Layer} layer * @api */ ol.control.LayerSwitcher.prototype.selectLayer = function(layer, silent) { if (!layer) { if (!this.getMap()) return; layer = this.getMap().getLayers().item(this.getMap().getLayers().getLength()-1) } this._selectedLayer = layer; this.drawPanel(); if (!silent) this.dispatchEvent({ type: 'select', layer: layer }); }; /** Get selected layer * @returns {ol.layer.Layer} */ ol.control.LayerSwitcher.prototype.getSelection = function() { return this._selectedLayer; }; /** Handle progress bar for a layer * @private */ ol.control.LayerSwitcher.prototype.setprogress_ = function(layer) { if (!layer.layerswitcher_progress) { var loaded = 0; var loading = 0; var draw = function() { if (loading === loaded) { loading = loaded = 0; ol.ext.element.setStyle(layer.layerswitcher_progress, { width: 0 });// layer.layerswitcher_progress.width(0); } else { ol.ext.element.setStyle(layer.layerswitcher_progress, { width: (loaded / loading * 100).toFixed(1) + '%' });// layer.layerswitcher_progress.css('width', (loaded / loading * 100).toFixed(1) + '%'); } } layer.getSource().on('tileloadstart', function() { loading++; draw(); }); layer.getSource().on('tileloadend', function() { loaded++; draw(); }); layer.getSource().on('tileloaderror', function() { loaded++; draw(); }); } }; /* 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). */ /** Control bar for OL3 * The control bar is a container for other controls. It can be used to create toolbars. * Control bars can be nested and combined with ol.control.Toggle to handle activate/deactivate. * @class * @constructor * @extends ol.control.Control * @param {Object=} options Control options. * @param {String} options.className class of the control * @param {boolean} options.group is a group, default false * @param {boolean} options.toggleOne only one toggle control is active at a time, default false * @param {boolean} options.autoDeactivate used with subbar to deactivate all control when top level control deactivate, default false * @param {Array } options.controls a list of control to add to the bar */ ol.control.Bar = function(options) { if (!options) options={}; var element = document.createElement("div"); element.classList.add('ol-unselectable', 'ol-control', 'ol-bar'); if (options.className) { var classes = options.className.split(' ').filter(function(className) { return className.length > 0; }); element.classList.add.apply(element.classList, classes) } if (options.group) element.classList.add('ol-group'); ol.control.Control.call(this, { element: element, target: options.target }); this.set('toggleOne', options.toggleOne); this.set('autoDeactivate', options.autoDeactivate); this.controls_ = []; if (options.controls instanceof Array) { for (var i=0; i} */ ol.control.Bar.prototype.getControls = function () { return this.controls_; }; /** Set tool bar position * @param {string} pos a combinaison of top|left|bottom|right */ ol.control.Bar.prototype.setPosition = function (pos) { this.element.classList.remove('ol-left', 'ol-top', 'ol-bottom', 'ol-right'); pos = pos.split ('-'); for (var i=0; i} */ ol.control.Bar.prototype.getActiveControls = function () { var active = []; for (var i=0, c; c=this.controls_[i]; i++) { if (c.getActive && c.getActive()) active.push(c); } return active; } /** Auto activate/deactivate controls in the bar * @param {boolean} b activate/deactivate */ ol.control.Bar.prototype.setActive = function (b) { if (!b && this.get("autoDeactivate")) { this.deactivateControls(); } if (b) { var ctrls = this.getControls(); for (var i=0, sb; (sb = ctrls[i]); i++) { if (sb.get("autoActivate")) sb.setActive(true); } } } /** Post-process an activated/deactivated control * @param {ol.event} e :an object with a target {_ol_control_} and active flag {bool} */ ol.control.Bar.prototype.onActivateControl_ = function (e, ctrl) { if (this.get('toggleOne')) { if (e.active) { var n; //var ctrl = e.target; for (n=0; n test auto activate if (!this.getActiveControls().length) { for (var i=0, c; c=this.controls_[i]; i++) { if (c.get("autoActivate")) { c.setActive(true); break; } } } } } }; /** * @param {string} name of the control to search * @return {ol.control.Control} */ ol.control.Bar.prototype.getControlsByName = function(name) { var controls = this.getControls(); return controls.filter( function(control) { return (control.get('name') === name); } ); }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * @classdesc * OpenLayers 3 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.Attribution * @param {Object=} options extend the ol.control.Attribution options. * @param {ol.style.Style} options.style option is usesd to draw the text. * @paream {boolean} [options.canvas=false] draw on canvas */ ol.control.CanvasAttribution = function(options) { if (!options) options = {}; ol.control.Attribution.call(this, options); this.element.classList.add('ol-canvas-control'); // Draw in canvas this.setCanvas(!!options.canvas); // Get style options if (!options) options={}; if (!options.style) options.style = new ol.style.Style(); this.setStyle (options.style); } ol.ext.inherits(ol.control.CanvasAttribution, ol.control.Attribution); /** * Draw attribution on canvas * @param {boolean} b draw the attribution on canvas. */ ol.control.CanvasAttribution.prototype.setCanvas = function (b) { this.isCanvas_ = b; if (b) this.setCollapsed(false); this.element.style.visibility = b ? "hidden":"visible"; if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } }; /** Get map Canvas * @private */ ol.control.CanvasAttribution.prototype.getContext = ol.control.CanvasBase.prototype.getContext; /** * Change the control style * @param {ol.style.Style} style */ ol.control.CanvasAttribution.prototype.setStyle = function (style) { var text = style.getText(); this.font_ = text ? text.getFont() : "10px sans-serif"; var stroke = text ? text.getStroke() : null; var fill = text ? text.getFill() : null; this.fontStrokeStyle_ = stroke ? ol.color.asString(stroke.getColor()) : "#fff"; this.fontFillStyle_ = fill ? ol.color.asString(fill.getColor()) : "#000"; this.fontStrokeWidth_ = stroke ? stroke.getWidth() : 3; if (this.getMap()) this.getMap().render(); }; /** * 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 {ol.Map} map Map. * @api stable */ ol.control.CanvasAttribution.prototype.setMap = function (map) { ol.control.CanvasBase.prototype.getCanvas.call(this, map); var oldmap = this.getMap(); if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; ol.control.Attribution.prototype.setMap.call(this, map); if (oldmap) { try { oldmap.renderSync(); } catch(e) { /* ok */ } } // Get change (new layer added or removed) if (map) { this._listener = map.on('postcompose', this.drawAttribution_.bind(this)); } this.setCanvas (this.isCanvas_); }; /** * Draw attribution in the final canvas * @private */ ol.control.CanvasAttribution.prototype.drawAttribution_ = function(e) { if (!this.isCanvas_) return; var ctx = this.getContext(e); if (!ctx) return; var text = ""; Array.prototype.slice.call(this.element.querySelectorAll('li')) .filter(function(el) { return el.style.display !== "none"; }) .map(function(el) { text += (text ? " - ":"") + el.textContent; }); // Retina device var ratio = e.frameState.pixelRatio; ctx.save(); ctx.scale(ratio,ratio); // Position var eltRect = this.element.getBoundingClientRect(); var mapRect = this.getMap().getViewport().getBoundingClientRect(); var sc = this.getMap().getSize()[0] / mapRect.width; ctx.translate((eltRect.left-mapRect.left)*sc, (eltRect.top-mapRect.top)*sc); var h = this.element.clientHeight; var w = this.element.clientWidth; var textAlign = ol.ext.element.getStyle(this.element, 'textAlign') || 'center'; var left; switch(textAlign) { case 'left': { left = 0; break; } case 'right': { left = w; break; } default: { left = w/2; break; } } // Draw scale text ctx.beginPath(); ctx.strokeStyle = this.fontStrokeStyle_; ctx.fillStyle = this.fontFillStyle_; ctx.lineWidth = this.fontStrokeWidth_; ctx.textAlign = textAlign; ctx.textBaseline = 'middle'; ctx.font = this.font_; ctx.strokeText(text, left, h/2); ctx.fillText(text, left, h/2); ctx.closePath(); ctx.restore(); }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * @classdesc * OpenLayers Scale Line 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.ScaleLine} * @param {Object=} options extend the ol.control.ScaleLine options. * @param {ol.style.Style} options.style used to draw the scale line (default is black/white, 10px Arial). */ ol.control.CanvasScaleLine = function(options) { ol.control.ScaleLine.call(this, options); this.element.classList.add('ol-canvas-control'); this.scaleHeight_ = 6; // Get style options if (!options) options={}; if (!options.style) options.style = new ol.style.Style(); this.setStyle(options.style); } ol.ext.inherits(ol.control.CanvasScaleLine, ol.control.ScaleLine); /** Get map Canvas * @private */ ol.control.CanvasScaleLine.prototype.getContext = ol.control.CanvasBase.prototype.getContext; /** * 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 {ol.Map} map Map. * @api stable */ ol.control.CanvasScaleLine.prototype.setMap = function (map) { ol.control.CanvasBase.prototype.getCanvas.call(this, map); var oldmap = this.getMap(); if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; ol.control.ScaleLine.prototype.setMap.call(this, map); if (oldmap) { try { oldmap.renderSync(); } catch(e) { /* ok */ } } // Add postcompose on the map if (map) { this._listener = map.on('postcompose', this.drawScale_.bind(this)); } // Hide the default DOM element this.element.style.visibility = 'hidden'; this.olscale = this.element.querySelector(".ol-scale-line-inner"); } /** * Change the control style * @param {ol.style.Style} style */ ol.control.CanvasScaleLine.prototype.setStyle = function (style) { var stroke = style.getStroke(); this.strokeStyle_ = stroke ? ol.color.asString(stroke.getColor()) : "#000"; this.strokeWidth_ = stroke ? stroke.getWidth() : 2; var fill = style.getFill(); this.fillStyle_ = fill ? ol.color.asString(fill.getColor()) : "#fff"; var text = style.getText(); this.font_ = text ? text.getFont() : "10px Arial"; stroke = text ? text.getStroke() : null; fill = text ? text.getFill() : null; this.fontStrokeStyle_ = stroke ? ol.color.asString(stroke.getColor()) : this.fillStyle_; this.fontStrokeWidth_ = stroke ? stroke.getWidth() : 3; this.fontFillStyle_ = fill ? ol.color.asString(fill.getColor()) : this.strokeStyle_; // refresh if (this.getMap()) this.getMap().render(); } /** * Draw attribution in the final canvas * @param {ol.render.Event} e * @private */ ol.control.CanvasScaleLine.prototype.drawScale_ = function(e) { if ( this.element.style.visibility !== 'hidden' || ol.ext.element.getStyle(this.element, 'display') === 'none' ) return; var ctx = this.getContext(e); if (!ctx) return; // Get size of the scale div var scalewidth = parseInt(this.olscale.style.width); if (!scalewidth) return; var text = this.olscale.textContent; var position = {left: this.element.offsetLeft, top: this.element.offsetTop}; // Retina device var ratio = e.frameState.pixelRatio; ctx.save(); ctx.scale(ratio,ratio); // On top position.top += this.element.clientHeight - this.scaleHeight_; // Draw scale text ctx.beginPath(); ctx.strokeStyle = this.fontStrokeStyle_; ctx.fillStyle = this.fontFillStyle_; ctx.lineWidth = this.fontStrokeWidth_; ctx.textAlign = "center"; ctx.textBaseline ="bottom"; ctx.font = this.font_; ctx.strokeText(text, position.left+scalewidth/2, position.top); ctx.fillText(text, position.left+scalewidth/2, position.top); ctx.closePath(); // Draw scale bar position.top += 2; ctx.lineWidth = this.strokeWidth_; ctx.strokeStyle = this.strokeStyle_; var max = 4; var n = parseInt(text); while (n%10 === 0) n/=10; if (n%5 === 0) max = 5; for (var i=0; i 0) { this.set('max', Number(max)); } else { max = this.get('max'); } if (!max) { ol.ext.element.setStyle(this._progress, { display: 'none' }) } else { var p = Math.round(val / max * 100); ol.ext.element.setStyle(this._progress, { display: '' }) this._progressbar.className = p ? '' : 'notransition'; ol.ext.element.setStyle(this._progressbar, { width: p+'%' }) } this._progressMessage.innerHTML = ''; ol.ext.element.setHTML(this._progressMessage, message || ''); }; /** Returns a function to do something on button click * @param {strnig} button button id * @param {function} callback * @returns {function} * @private */ ol.control.Dialog.prototype._onButton = function(button, callback) { // Dispatch a button event var fn = function(e) { e.preventDefault(); if (button!=='submit' || this.get('closeOnSubmit')!==false) this.hide(); var inputs = {}; this.element.querySelectorAll('form input').forEach (function(input) { if (input.className) inputs[input.className] = input; }); this.dispatchEvent ({ type: 'button', button: button, inputs: inputs }); if (typeof(callback) === 'function') callback(button, inputs); }.bind(this); return fn; }; /** Close the dialog */ ol.control.Dialog.prototype.hide = function() { this.element.classList.remove('ol-visible'); this.dispatchEvent ({ type: 'hide' }); }; /** Close the dialog * @method Dialog.close * @return {bool} true if a dialog is closed */ ol.control.Dialog.prototype.close = ol.control.Dialog.prototype.hide; /** The dialog is shown * @return {bool} true if a dialog is open */ ol.control.Dialog.prototype.isOpen = function() { return (this.element.classList.contains('ol-visible')); }; /** A simple control to disable all actions on the map. * The control will create an invisible div over the map. * @constructor * @extends {ol.control.Control} * @param {Object=} options Control options. * @param {String} options.class class of the control * @param {String} options.html html code to insert in the control * @param {bool} options.on the control is on * @param {function} options.toggleFn callback when control is clicked */ ol.control.Disable = function(options) { options = options||{}; var element = document.createElement("div"); element.className = (options.className||""+' ol-disable ol-unselectable ol-control').trim(); var stylesOptions = { top:"0px", left:"0px", right:"0px", bottom:"0px", "zIndex":10000, background:"none", display:"none" }; Object.keys(stylesOptions).forEach(function(styleKey) { element.style[styleKey] = stylesOptions[styleKey]; }); ol.control.Control.call(this, { element: element }); } ol.ext.inherits(ol.control.Disable, ol.control.Control); /** Test if the control is on * @return {bool} * @api stable */ ol.control.Disable.prototype.isOn = function() { return this.element.classList.contains("ol-disable"); } /** Disable all action on the map * @param {bool} b, default false * @api stable */ ol.control.Disable.prototype.disableMap = function(b) { if (b) { this.element.classList.add("ol-enable").show(); } else { this.element.classList.remove("ol-enable").hide(); } } /** Control bar for editing in a layer * @constructor * @extends {ol.control.Bar} * @fires info * @param {Object=} options Control options. * @param {String} options.className class of the control * @param {String} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {boolean} options.edition false to remove the edition tools, default true * @param {Object} options.interactions List of interactions to add to the bar * ie. Select, Delete, Info, DrawPoint, DrawLine, DrawPolygon * Each interaction can be an interaction or true (to get the default one) or false to remove it from bar * @param {ol.source.Vector} options.source Source for the drawn features. */ ol.control.EditBar = function(options) { options = options || {}; options.interactions = options.interactions || {}; // New bar ol.control.Bar.call(this, { className: (options.className ? options.className+' ': '') + 'ol-editbar', toggleOne: true, target: options.target }); this._source = options.source; // Add buttons / interaction this._interactions = {}; this._setSelectInteraction(options); if (options.edition!==false) this._setEditInteraction(options); this._setModifyInteraction(options); }; ol.ext.inherits(ol.control.EditBar, ol.control.Bar); /** * Set the map instance the control is associated with * and add its controls associated to this map. * @param {_ol_Map_} map The map instance. */ ol.control.EditBar.prototype.setMap = function (map) { if (this.getMap()) { if (this._interactions.Delete) this.getMap().removeInteraction(this._interactions.Delete); if (this._interactions.ModifySelect) this.getMap().removeInteraction(this._interactions.ModifySelect); } ol.control.Bar.prototype.setMap.call(this, map); if (this.getMap()) { if (this._interactions.Delete) this.getMap().addInteraction(this._interactions.Delete); if (this._interactions.ModifySelect) this.getMap().addInteraction(this._interactions.ModifySelect); } }; /** Get an interaction associated with the bar * @param {string} name */ ol.control.EditBar.prototype.getInteraction = function (name) { return this._interactions[name]; }; /** Get the option title */ ol.control.EditBar.prototype._getTitle = function (option) { if (option) { if (option.get) return option.get('title'); else if (typeof(option) === 'string') return option; else return option.title; } }; /** Add selection tool: * 1. a toggle control with a select interaction * 2. an option bar to delete / get information on the selected feature * @private */ ol.control.EditBar.prototype._setSelectInteraction = function (options) { var self = this; // Sub bar var sbar = new ol.control.Bar(); var selectCtrl; // Delete button if (options.interactions.Delete !== false) { if (options.interactions.Delete instanceof ol.interaction.Delete) { this._interactions.Delete = options.interactions.Delete; } else { this._interactions.Delete = new ol.interaction.Delete(); } var del = this._interactions.Delete; del.setActive(false); if (this.getMap()) this.getMap().addInteraction(del); sbar.addControl (new ol.control.Button({ className: 'ol-delete', title: this._getTitle(options.interactions.Delete) || "Delete", name: 'Delete', handleClick: function(e) { // Delete selection del.delete(selectCtrl.getInteraction().getFeatures()); var evt = { type: 'select', selected: [], deselected: selectCtrl.getInteraction().getFeatures().getArray().slice(), mapBrowserEvent: e.mapBrowserEvent }; selectCtrl.getInteraction().getFeatures().clear(); selectCtrl.getInteraction().dispatchEvent(evt); } })); } // Info button if (options.interactions.Info !== false) { sbar.addControl (new ol.control.Button({ className: 'ol-info', name: 'Info', title: this._getTitle(options.interactions.Info) || "Show informations", handleClick: function() { self.dispatchEvent({ type: 'info', features: selectCtrl.getInteraction().getFeatures() }); } })); } // Select button if (options.interactions.Select !== false) { if (options.interactions.Select instanceof ol.interaction.Select) { this._interactions.Select = options.interactions.Select } else { this._interactions.Select = new ol.interaction.Select({ condition: ol.events.condition.click }); } var sel = this._interactions.Select; selectCtrl = new ol.control.Toggle({ className: 'ol-selection', name: 'Select', title: this._getTitle(options.interactions.Select) || "Select", interaction: sel, bar: sbar.getControls().length ? sbar : undefined, autoActivate:true, active:true }); this.addControl(selectCtrl); sel.on('change:active', function() { sel.getFeatures().clear(); }); } }; /** Add editing tools * @private */ ol.control.EditBar.prototype._setEditInteraction = function (options) { if (options.interactions.DrawPoint !== false) { if (options.interactions.DrawPoint instanceof ol.interaction.Draw) { this._interactions.DrawPoint = options.interactions.DrawPoint; } else { this._interactions.DrawPoint = new ol.interaction.Draw({ type: 'Point', source: this._source }); } var pedit = new ol.control.Toggle({ className: 'ol-drawpoint', name: 'DrawPoint', title: this._getTitle(options.interactions.DrawPoint) || 'Point', interaction: this._interactions.DrawPoint }); this.addControl ( pedit ); } if (options.interactions.DrawLine !== false) { if (options.interactions.DrawLine instanceof ol.interaction.Draw) { this._interactions.DrawLine = options.interactions.DrawLine } else { this._interactions.DrawLine = new ol.interaction.Draw ({ type: 'LineString', source: this._source, // Count inserted points geometryFunction: function(coordinates, geometry) { if (geometry) geometry.setCoordinates(coordinates); else geometry = new ol.geom.LineString(coordinates); this.nbpts = geometry.getCoordinates().length; return geometry; } }); } var ledit = new ol.control.Toggle({ className: 'ol-drawline', title: this._getTitle(options.interactions.DrawLine) || 'LineString', name: 'DrawLine', interaction: this._interactions.DrawLine, // Options bar associated with the control bar: new ol.control.Bar ({ controls:[ new ol.control.TextButton({ html: this._getTitle(options.interactions.UndoDraw) || 'undo', title: this._getTitle(options.interactions.UndoDraw) || "delete last point", handleClick: function() { if (ledit.getInteraction().nbpts>1) ledit.getInteraction().removeLastPoint(); } }), new ol.control.TextButton ({ html: this._getTitle(options.interactions.FinishDraw) || 'finish', title: this._getTitle(options.interactions.FinishDraw) || "finish", handleClick: function() { // Prevent null objects on finishDrawing if (ledit.getInteraction().nbpts>2) ledit.getInteraction().finishDrawing(); } }) ] }) }); this.addControl ( ledit ); } if (options.interactions.DrawPolygon !== false) { if (options.interactions.DrawPolygon instanceof ol.interaction.Draw){ this._interactions.DrawPolygon = options.interactions.DrawPolygon } else { this._interactions.DrawPolygon = new ol.interaction.Draw ({ type: 'Polygon', source: this._source, // Count inserted points geometryFunction: function(coordinates, geometry) { this.nbpts = coordinates[0].length; if (geometry) geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])]); else geometry = new ol.geom.Polygon(coordinates); return geometry; } }); } this._setDrawPolygon( 'ol-drawpolygon', this._interactions.DrawPolygon, this._getTitle(options.interactions.DrawPolygon) || 'Polygon', 'DrawPolygon', options ); } // Draw hole if (options.interactions.DrawHole !== false) { if (options.interactions.DrawHole instanceof ol.interaction.DrawHole){ this._interactions.DrawHole = options.interactions.DrawHole; } else { this._interactions.DrawHole = new ol.interaction.DrawHole (); } this._setDrawPolygon( 'ol-drawhole', this._interactions.DrawHole, this._getTitle(options.interactions.DrawHole) || 'Hole', 'DrawHole', options ); } // Draw regular if (options.interactions.DrawRegular !== false) { var label = { pts: 'pts', circle: 'circle' }; if (options.interactions.DrawRegular instanceof ol.interaction.DrawRegular) { this._interactions.DrawRegular = options.interactions.DrawRegular; label.pts = this._interactions.DrawRegular.get('ptsLabel') || label.pts; label.circle = this._interactions.DrawRegular.get('circleLabel') || label.circle; } else { this._interactions.DrawRegular = new ol.interaction.DrawRegular ({ source: this._source, sides: 4 }); if (options.interactions.DrawRegular) { label.pts = options.interactions.DrawRegular.ptsLabel || label.pts; label.circle = options.interactions.DrawRegular.circleLabel || label.circle; } } var regular = this._interactions.DrawRegular; var div = document.createElement('DIV'); var down = ol.ext.element.create('DIV', { parent: div }); ol.ext.element.addListener(down, ['click', 'touchstart'], function() { var sides = regular.getSides() -1; if (sides < 2) sides = 2; regular.setSides (sides); text.textContent = sides>2 ? sides+' '+label.pts : label.circle; }.bind(this)); var text = ol.ext.element.create('TEXT', { html:'4 '+label.pts, parent: div }); var up = ol.ext.element.create('DIV', { parent: div }); ol.ext.element.addListener(up, ['click', 'touchstart'], function() { var sides = regular.getSides() +1; if (sides<3) sides=3; regular.setSides(sides); text.textContent = sides+' '+label.pts; }.bind(this)); var ctrl = new ol.control.Toggle({ className: 'ol-drawregular', title: this._getTitle(options.interactions.DrawRegular) || 'Regular', name: 'DrawRegular', interaction: this._interactions.DrawRegular, // Options bar associated with the control bar: new ol.control.Bar ({ controls:[ new ol.control.TextButton({ html: div }) ] }) }); this.addControl (ctrl); } }; /** * @private */ ol.control.EditBar.prototype._setDrawPolygon = function (className, interaction, title, name, options) { var fedit = new ol.control.Toggle ({ className: className, name: name, title: title, interaction: interaction, // Options bar associated with the control bar: new ol.control.Bar({ controls:[ new ol.control.TextButton ({ html: this._getTitle(options.interactions.UndoDraw) || 'undo', title: this._getTitle(options.interactions.UndoDraw) || 'undo last point', handleClick: function(){ if (fedit.getInteraction().nbpts>1) fedit.getInteraction().removeLastPoint(); } }), new ol.control.TextButton({ html: this._getTitle(options.interactions.FinishDraw) || 'finish', title: this._getTitle(options.interactions.FinishDraw) || 'finish', handleClick: function() { // Prevent null objects on finishDrawing if (fedit.getInteraction().nbpts>3) fedit.getInteraction().finishDrawing(); } }) ] }) }); this.addControl (fedit); return fedit; }; /** Add modify tools * @private */ ol.control.EditBar.prototype._setModifyInteraction = function (options) { // Modify on selected features if (options.interactions.ModifySelect !== false && options.interactions.Select !== false) { if (options.interactions.ModifySelect instanceof ol.interaction.ModifyFeature) { this._interactions.ModifySelect = options.interactions.ModifySelect; } else { this._interactions.ModifySelect = new ol.interaction.ModifyFeature({ features: this.getInteraction('Select').getFeatures() }); } if (this.getMap()) this.getMap().addInteraction(this._interactions.ModifySelect); // Activate with select this._interactions.ModifySelect.setActive(this._interactions.Select.getActive()); this._interactions.Select.on('change:active', function() { this._interactions.ModifySelect.setActive(this._interactions.Select.getActive()); }.bind(this)); } if (options.interactions.Transform !== false) { if (options.interactions.Transform instanceof ol.interaction.Transform) { this._interactions.Transform = options.interactions.Transform; } else { this._interactions.Transform = new ol.interaction.Transform ({ addCondition: ol.events.condition.shiftKeyOnly }); } var transform = new ol.control.Toggle ({ html: '', className: 'ol-transform', title: this._getTitle(options.interactions.Transform) || 'Transform', name: 'Transform', interaction: this._interactions.Transform }); this.addControl (transform); } if (options.interactions.Split !== false) { if (options.interactions.Split instanceof ol.interaction.Split) { this._interactions.Split = options.interactions.Split; } else { this._interactions.Split = new ol.interaction.Split ({ sources: this._source }); } var split = new ol.control.Toggle ({ className: 'ol-split', title: this._getTitle(options.interactions.Split) || 'Split', name: 'Split', interaction: this._interactions.Split }); this.addControl (split); } if (options.interactions.Offset !== false) { if (options.interactions.Offset instanceof ol.interaction.Offset) { this._interactions.Offset = options.interactions.Offset; } else { this._interactions.Offset = new ol.interaction.Offset ({ source: this._source }); } var offset = new ol.control.Toggle ({ html: '', className: 'ol-offset', title: this._getTitle(options.interactions.Offset) || 'Offset', name: 'Offset', interaction: this._interactions.Offset }); this.addControl (offset); } }; /* Copyright (c) 2017 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 gauge control to display level information on the map. * * @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 {number} options.max maximum value, default 100; * @param {number} options.val the value, default 0 */ ol.control.Gauge = function(options) { options = options || {}; var element = ol.ext.element.create('DIV', { className: ((options.className||"") + ' ol-gauge ol-unselectable ol-control').trim() }); this.title_ = ol.ext.element.create('SPAN', { parent: element }); var div = ol.ext.element.create('DIV', { parent: element }); this.gauge_ = ol.ext.element.create('BUTTON', { type: 'button', style: { width: '0px' }, parent: div }); /* var element = document.createElement("div"); element.className = ((options.className||"") + ' ol-gauge ol-unselectable ol-control').trim(); this.title_ = document.createElement("span"); element.appendChild(this.title_); this.gauge_ = document.createElement("button"); this.gauge_.setAttribute('type','button'); element.appendChild(document.createElement("div").appendChild(this.gauge_)) this.gauge_.style.width = '0px'; */ ol.control.Control.call(this, { element: element, target: options.target }); this.setTitle(options.title); this.set("max", options.max||100); this.val(options.val); }; ol.ext.inherits(ol.control.Gauge, ol.control.Control); /** Set the control title * @param {string} title */ ol.control.Gauge.prototype.setTitle = function(title) { this.title_.innerHTML = title||""; if (!title) this.title_.display = 'none'; else this.title_.display = ''; }; /** Set/get the gauge value * @param {number|undefined} v the value or undefined to get it * @return {number} the value */ ol.control.Gauge.prototype.val = function(v) { if (v!==undefined) { this.val_ = v; this.gauge_.style.width = (v/this.get('max')*100)+"%"; } return this.val_; }; /** Bookmark positions on ol maps. * * @constructor * @extends {ol.control.Control} * @fires add * @fires remove * @fires select * @param {} options Geobookmark's options * @param {string} options.className default ol-bookmark * @param {string | undefined} options.title Title to use for the button tooltip, default "Geobookmarks" * @param {string} options.placeholder input placeholder, default Add a new geomark... * @param {string} [options.deleteTitle='Suppr.'] title for delete buttons * @param {bool} options.editable enable modification, default true * @param {string} options.namespace a namespace to save the boolmark (if more than one on a page), default ol * @param {Array} options.marks a list of default bookmarks: * @see [Geobookmark example](../../examples/control/map.control.geobookmark.html) * @example var bm = new GeoBookmark ({ marks: { "Paris": {pos:ol.proj.transform([2.351828, 48.856578], 'EPSG:4326', 'EPSG:3857'), zoom:11, permanent: true }, "London": {pos:ol.proj.transform([-0.1275,51.507222], 'EPSG:4326', 'EPSG:3857'), zoom:12} } }); */ ol.control.GeoBookmark = function(options) { options = options || {}; var self = this; var element = document.createElement('div'); if (options.target) { element.className = options.className || "ol-bookmark"; } else { element.className = (options.className || "ol-bookmark") + " ol-unselectable ol-control ol-collapsed"; element.addEventListener("mouseleave", function() { if (input !== document.activeElement) { menu.style.display = 'none'; } }); // Show bookmarks on click this.button = ol.ext.element.create('BUTTON', { type: 'button', title: options.title || 'Geobookmarks', click: function() { var show = (menu.style.display === '' || menu.style.display === 'none'); menu.style.display = (show ? 'block': 'none'); if (show) this.setBookmarks(); }.bind(this) }); element.appendChild(this.button); } // The menu var menu = document.createElement('div'); element.appendChild(menu); var ul = document.createElement('ul'); menu.appendChild(ul); var input = document.createElement('input'); input.setAttribute("placeholder", options.placeholder || "Add a new geomark...") input.addEventListener("keydown", function(e) { e.stopPropagation(); if (e.keyCode === 13) { e.preventDefault(); var title = this.value; if (title) { self.addBookmark(title); this.value = ''; self.dispatchEvent({ type: "add", name: title }); } menu.style.display = 'none'; } }); input.addEventListener("blur", function() { menu.style.display = 'none'; }); menu.appendChild(input); // Init ol.control.Control.call(this, { element: element, target: options.target }); this.on("propertychange", function(e) { if (e.key==='editable') { element.className = element.className.replace(" ol-editable",""); if (this.get('editable')) { element.className += " ol-editable"; } } // console.log(e); }.bind(this)); this.set("namespace", options.namespace || 'ol'); this.set("editable", options.editable !== false); this.set('deleteTitle', options.deleteTitle || 'Suppr.'); // Set default bmark var bmark = {}; try { if (localStorage[this.get('namespace')+"@bookmark"]) { bmark = JSON.parse(localStorage[this.get('namespace')+"@bookmark"]); } } catch(e) { console.warn('Failed to access localStorage...'); } if (options.marks) { for (var i in options.marks) { bmark[i] = options.marks[i]; } } this.setBookmarks(bmark); }; ol.ext.inherits(ol.control.GeoBookmark, ol.control.Control); /** Set bookmarks * @param {} bmark a list of bookmarks, default retreave in the localstorage * @example bm.setBookmarks({ "Paris": {pos:_ol_proj_.transform([2.351828, 48.856578], 'EPSG:4326', 'EPSG:3857'), zoom:11, permanent: true }, "London": {pos:_ol_proj_.transform([-0.1275,51.507222], 'EPSG:4326', 'EPSG:3857'), zoom:12} }); */ ol.control.GeoBookmark.prototype.setBookmarks = function(bmark) { if (!bmark) { bmark = {}; try { bmark = JSON.parse(localStorage[this.get('namespace')+"@bookmark"] || "{}"); } catch(e) { console.warn('Failed to access localStorage...'); } } var modify = this.get("editable"); var ul = this.element.querySelector("ul"); var menu = this.element.querySelector("div"); var self = this; ul.innerHTML = ''; for (var b in bmark) { var li = document.createElement('li'); li.textContent = b; li.setAttribute('data-bookmark', JSON.stringify(bmark[b])); li.setAttribute('data-name', b); li.addEventListener('click', function() { var bm = JSON.parse(this.getAttribute("data-bookmark")); self.getMap().getView().setCenter(bm.pos); self.getMap().getView().setZoom(bm.zoom); self.getMap().getView().setRotation(bm.rot || 0); menu.style.display = 'none'; self.dispatchEvent({ type: 'select', name: this.getAttribute("data-name"), bookmark: bm }); }); ul.appendChild(li); if (modify && !bmark[b].permanent) { var button = document.createElement('button'); button.setAttribute('data-name', b); button.setAttribute('type', 'button'); button.setAttribute('title', this.get('deleteTitle') ||'Suppr.'); button.addEventListener('click', function(e) { self.removeBookmark(this.getAttribute("data-name")); self.dispatchEvent({ type: "remove", name: this.getAttribute("data-name") }); e.stopPropagation(); }); li.appendChild(button); } } try { localStorage[this.get('namespace')+"@bookmark"] = JSON.stringify(bmark); } catch(e) { console.warn('Failed to access localStorage...'); } }; /** Get Geo bookmarks * @return {any} a list of bookmarks : { BM1:{pos:ol.coordinates, zoom: integer}, BM2:{pos:ol.coordinates, zoom: integer} } */ ol.control.GeoBookmark.prototype.getBookmarks = function() { var bm = {}; try { bm = JSON.parse(localStorage[this.get('namespace')+"@bookmark"] || "{}"); } catch(e) { console.warn('Failed to access localStorage...'); } return bm; }; /** Remove a Geo bookmark * @param {string} name */ ol.control.GeoBookmark.prototype.removeBookmark = function(name) { if (!name) { return; } var bmark = this.getBookmarks(); delete bmark[name]; this.setBookmarks(bmark); }; /** Add a new Geo bookmark (replace existing one if any) * @param {string} name name of the bookmark (display in the menu) * @param {*} options * @param {ol.coordinate} position default current position * @param {number} zoom default current map zoom * @param {number} rotation default current map rotation * @param {bool} permanent prevent from deletion, default false */ ol.control.GeoBookmark.prototype.addBookmark = function(name, position, zoom, permanent) { if (!name) return; var options = position; var rot; if (options && options.position) { zoom = options.zoom; permanent = options.permanent; rot = options.rotation ; position = options.position; } else { rot = this.getMap().getView().getRotation(); } var bmark = this.getBookmarks(); // Don't override permanent bookmark if (bmark[name] && bmark[name].permanent) return; // Create or override bmark[name] = { pos: position || this.getMap().getView().getCenter(), zoom: zoom || this.getMap().getView().getZoom(), permanent: !!permanent }; if (rot) { bmark[name].rot = rot; } this.setBookmarks(bmark); }; /* 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). */ /** Geolocation bar * The control bar is a container for other controls. It can be used to create toolbars. * Control bars can be nested and combined with ol.control.Toggle to handle activate/deactivate. * * @constructor * @extends {ol.control.Bar} * @param {Object=} options Control bar options. * @param {String} options.className class of the control * @param {String} options.centerLabel label for center button, default center * @param {String} options.position position of the control, default bottom-right */ ol.control.GeolocationBar = function(options) { if (!options) options = {}; options.className = options.className || 'ol-geobar'; ol.control.Bar.call(this, options); this.setPosition(options.position || 'bottom-right'); var element = this.element; // Geolocation draw interaction var interaction = new ol.interaction.GeolocationDraw({ source: options.source, zoom: options.zoom, minZoom: options.minZoom, tolerance: options.tolerance, followTrack: options.followTrack, minAccuracy: options.minAccuracy || 10000 }); this._geolocBt = new ol.control.Toggle ({ className: 'geolocBt', interaction: interaction, onToggle: function() { interaction.pause(true); interaction.setFollowTrack(options.followTrack); element.classList.remove('pauseTrack'); } }); this.addControl(this._geolocBt); this._geolocBt.setActive(false); // Buttons var bar = new ol.control.Bar(); this.addControl(bar); var centerBt = new ol.control.TextButton ({ className: 'centerBt', html: options.centerLabel ||'center', handleClick: function() { interaction.setFollowTrack('auto'); } }); bar.addControl(centerBt); var startBt = new ol.control.Button ({ className: 'startBt', handleClick: function(){ interaction.pause(false); interaction.setFollowTrack('auto'); element.classList.add('pauseTrack'); } }); bar.addControl(startBt); var pauseBt = new ol.control.Button ({ className: 'pauseBt', handleClick: function(){ interaction.pause(true); interaction.setFollowTrack('auto'); element.classList.remove('pauseTrack'); } }); bar.addControl(pauseBt); interaction.on('follow', function(e) { if (e.following) { element.classList.remove('centerTrack'); } else { element.classList.add('centerTrack'); } }); // Activate this._geolocBt.on('change:active', function(e) { if (e.active) { element.classList.add('ol-active'); } else { element.classList.remove('ol-active'); } }); }; ol.ext.inherits(ol.control.GeolocationBar, ol.control.Bar); /** * 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 {ol.Map} map Map. * @api stable */ ol.control.GeolocationBar.prototype.setMap = function (map) { if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; ol.control.Bar.prototype.setMap.call(this, map); // Get change (new layer added or removed) if (map) { this._listener = map.on('moveend', function() { var geo = this.getInteraction(); if (geo.getActive() && geo.get('followTrack') === 'auto' && geo.path_.length) { if (geo.path_[geo.path_.length-1][0] !== map.getView().getCenter()[0]) { this.element.classList.add('centerTrack'); } } }.bind(this)); } }; /** Get the ol.interaction.GeolocationDraw associatedwith the bar * @return {ol.interaction.GeolocationDraw} */ ol.control.GeolocationBar.prototype.getInteraction = function () { return this._geolocBt.getInteraction(); }; /* 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). */ /** Geolocation bar * The control bar is a container for other controls. It can be used to create toolbars. * Control bars can be nested and combined with ol.control.Toggle to handle activate/deactivate. * * @constructor * @fires tracking * @extends {ol.control.Toggle} * @param {Object=} options ol.interaction.GeolocationDraw option. * @param {String} options.className class of the control * @param {String} options.title title of the control to display as tooltip, default Geolocation * @param {number} options.delay delay before removing the location in ms, delfaut 3000 (3s) */ ol.control.GeolocationButton = function(options) { if (!options) options = {}; // Geolocation draw interaction options.followTrack = options.followTrack || 'auto'; options.zoom = options.zoom || 16; //options.minZoom = options.minZoom || 16; var interaction = new ol.interaction.GeolocationDraw(options); ol.control.Toggle.call (this, { className: options.className = ((options.className || '') + ' ol-geobt').trim(), interaction: interaction, title: options.title || 'Geolocation', onToggle: function() { interaction.pause(true); interaction.setFollowTrack(options.followTrack || 'auto'); } }); this.setActive(false); interaction.on('tracking', function(e) { this.dispatchEvent({ type: 'position', coordinate: e.geolocation.getPosition() }); }.bind(this)); // Timeout delay var tout; interaction.on('change:active', function() { this.dispatchEvent({ type:'position' }); if (tout) { clearTimeout(tout); tout = null; } if (interaction.getActive()) { tout = setTimeout(function() { interaction.setActive(false); tout = null; }.bind(this), options.delay || 3000); } }.bind(this)); }; ol.ext.inherits(ol.control.GeolocationButton, ol.control.Toggle); /* 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). */ /** * OpenLayers 3 lobe Overview Control. * The globe can rotate with map (follow.) * * @constructor * @extends {ol.control.Control} * @param {Object=} options Control options. * @param {boolean} follow follow the map when center change, default false * @param {top|bottom-left|right} align position as top-left, etc. * @param {Array} layers list of layers to display on the globe * @param {ol.style.Style | Array. | undefined} style style to draw the position on the map , default a marker */ ol.control.Globe = function(opt_options) { var options = opt_options || {}; var self = this; // API var element; if (options.target) { element = document.createElement("div"); this.panel_ = options.target; } else { element = document.createElement("div"); element.classList.add('ol-globe', 'ol-unselectable', 'ol-control'); if (/top/.test(options.align)) element.classList.add('ol-control-top'); if (/right/.test(options.align)) element.classList.add('ol-control-right'); this.panel_ = document.createElement("div"); this.panel_.classList.add("panel") element.appendChild(this.panel_); this.pointer_ = document.createElement("div"); this.pointer_.classList.add("ol-pointer"); element.appendChild(this.pointer_); } ol.control.Control.call(this, { element: element, target: options.target }); // http://openlayers.org/en/latest/examples/sphere-mollweide.html ??? // Create a globe map this.ovmap_ = new ol.Map( { controls: new ol.Collection(), interactions: new ol.Collection(), target: this.panel_, view: new ol.View ({ zoom: 0, center: [0,0] }), layers: options.layers }); setTimeout (function() { self.ovmap_.updateSize(); }, 0); this.set('follow', options.follow || false); // Cache extent this.extentLayer = new ol.layer.Vector( { name: 'Cache extent', source: new ol.source.Vector(), style: options.style || [new ol.style.Style( { image: new ol.style.Circle( { fill: new ol.style.Fill({ color: 'rgba(255,0,0, 1)' }), stroke: new ol.style.Stroke( { width: 7, color: 'rgba(255,0,0, 0.8)' }), radius: 5 }) } )] }) this.ovmap_.addLayer(this.extentLayer); }; ol.ext.inherits(ol.control.Globe, ol.control.Control); /** * Set the map instance the control associated with. * @param {ol.Map} map The map instance. */ ol.control.Globe.prototype.setMap = function(map) { if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; ol.control.Control.prototype.setMap.call(this, map); // Get change (new layer added or removed) if (map) { this._listener = map.getView().on('propertychange', this.setView.bind(this)); this.setView(); } }; /** Set the globe center with the map center */ ol.control.Globe.prototype.setView = function() { if (this.getMap() && this.get('follow')) { this.setCenter(this.getMap().getView().getCenter()); } } /** Get globe map * @return {ol.Map} */ ol.control.Globe.prototype.getGlobe = function() { return this.ovmap_; } /** Show/hide the globe */ ol.control.Globe.prototype.show = function(b) { if (b!==false) this.element.classList.remove("ol-collapsed"); else this.element.classList.add("ol-collapsed"); this.ovmap_.updateSize(); } /** Set position on the map * @param {top|bottom-left|right} align */ ol.control.Globe.prototype.setPosition = function(align) { if (/top/.test(align)) this.element.classList.add("ol-control-top"); else this.element.classList.remove("ol-control-top"); if (/right/.test(align)) this.element.classList.add("ol-control-right"); else this.element.classList.remove("ol-control-right"); } /** Set the globe center * @param {_ol_coordinate_} center the point to center to * @param {boolean} show show a pointer on the map, defaylt true */ ol.control.Globe.prototype.setCenter = function (center, show) { var self = this; this.pointer_.classList.add("hidden"); if (center) { var map = this.ovmap_; var p = map.getPixelFromCoordinate(center); if (p) { if (show!==false) { var h = this.element.clientHeight; setTimeout(function() { self.pointer_.style.top = String(Math.min(Math.max(p[1],0),h)) + 'px'; self.pointer_.style.left = "50%"; self.pointer_.classList.remove("hidden"); }, 800); } map.getView().animate({ center: [center[0],0] }); } } }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Draw a graticule on the map. * * @constructor * @extends {ol.control.CanvasBase} * @param {Object=} _ol_control_ options. * @param {ol.projectionLike} options.projection projection to use for the graticule, default EPSG:4326 * @param {number} options.maxResolution max resolution to display the graticule * @param {ol.style.Style} options.style Style to use for drawing the graticule, default black. * @param {number} options.step step beetween lines (in proj units), default 1 * @param {number} options.stepCoord show a coord every stepCoord, default 1 * @param {number} options.spacing spacing beetween lines (in px), default 40px * @param {number} options.borderWidth width of the border (in px), default 5px * @param {number} options.margin margin of the border (in px), default 0px * @param {number} options.formatCoord a function that takes a coordinate and a position and return the formated coordinate */ ol.control.Graticule = function(options) { if (!options) options = {}; // Initialize parent var elt = document.createElement("div"); elt.className = "ol-graticule ol-unselectable ol-hidden"; ol.control.CanvasBase.call(this, { element: elt }); this.set('projection', options.projection || 'EPSG:4326'); // Use to limit calculation var p = new ol.proj.Projection({code:this.get('projection')}); var m = p.getMetersPerUnit(); this.fac = 1; while (m/this.fac>10) { this.fac *= 10; } this.fac = 10000/this.fac; this.set('maxResolution', options.maxResolution || Infinity); this.set('step', options.step || 0.1); this.set('stepCoord', options.stepCoord || 1); this.set('spacing', options.spacing || 40); this.set('margin', options.margin || 0); this.set('borderWidth', options.borderWidth || 5); this.set('stroke', options.stroke!==false); this.formatCoord = options.formatCoord || function(c){return c;}; if (options.style instanceof ol.style.Style) { this.setStyle(options.style); } else { this.setStyle(new ol.style.Style({ stroke: new ol.style.Stroke({ color:"#000", width:1 }), fill: new ol.style.Fill({ color: "#fff" }), text: new ol.style.Text({ stroke: new ol.style.Stroke({ color:"#fff", width:2 }), fill: new ol.style.Fill({ color:"#000" }), }) })); } }; ol.ext.inherits(ol.control.Graticule, ol.control.CanvasBase); ol.control.Graticule.prototype.setStyle = function (style) { this._style = style; }; ol.control.Graticule.prototype._draw = function (e) { if (this.get('maxResolution')w) { var dt = Math.round((xmax-xmin)/w*spacing /step); step *= dt; if (step>this.fac) step = Math.round(step/this.fac)*this.fac; } xmin = (Math.floor(xmin/step))*step -step; ymin = (Math.floor(ymin/step))*step -step; xmax = (Math.floor(xmax/step))*step +2*step; ymax = (Math.floor(ymax/step))*step +2*step; var extent = ol.proj.get(proj).getExtent(); if (extent) { if (xmin < extent[0]) xmin = extent[0]; if (ymin < extent[1]) ymin = extent[1]; if (xmax > extent[2]) xmax = extent[2]+step; if (ymax > extent[3]) ymax = extent[3]+step; } var hasLines = this.getStyle().getStroke() && this.get("stroke"); var hasText = this.getStyle().getText(); var hasBorder = this.getStyle().getFill(); ctx.save(); ctx.scale(ratio,ratio); ctx.beginPath(); ctx.rect(margin, margin, w-2*margin, h-2*margin); ctx.clip(); ctx.beginPath(); var txt = {top:[],left:[],bottom:[], right:[]}; var x, y, p, p0, p1; for (x=xmin; x0 && p1[1]<0) txt.top.push([x, p]); if (p[1]>h && p1[1]0) txt.left.push([y,p]); if (p[0]w) txt.right.push([y,p]); p = p1; } } if (hasLines) { ctx.strokeStyle = this.getStyle().getStroke().getColor(); ctx.lineWidth = this.getStyle().getStroke().getWidth(); ctx.stroke(); } // Draw text if (hasText) { ctx.fillStyle = this.getStyle().getText().getFill().getColor(); ctx.strokeStyle = this.getStyle().getText().getStroke().getColor(); ctx.lineWidth = this.getStyle().getText().getStroke().getWidth(); ctx.font = this.getStyle().getText().getFont(); ctx.textAlign = 'center'; ctx.textBaseline = 'hanging'; var t, tf; var offset = (hasBorder ? borderWidth : 0) + margin + 2; for (i=0; t = txt.top[i]; i++) if (!(Math.round(t[0]/this.get('step'))%step2)) { tf = this.formatCoord(t[0], 'top'); ctx.strokeText(tf, t[1][0], offset); ctx.fillText(tf, t[1][0], offset); } ctx.textBaseline = 'alphabetic'; for (i=0; t = txt.bottom[i]; i++) if (!(Math.round(t[0]/this.get('step'))%step2)) { tf = this.formatCoord(t[0], 'bottom'); ctx.strokeText(tf, t[1][0], h-offset); ctx.fillText(tf, t[1][0], h-offset); } ctx.textBaseline = 'middle'; ctx.textAlign = 'left'; for (i=0; t = txt.left[i]; i++) if (!(Math.round(t[0]/this.get('step'))%step2)) { tf = this.formatCoord(t[0], 'left'); ctx.strokeText(tf, offset, t[1][1]); ctx.fillText(tf, offset, t[1][1]); } ctx.textAlign = 'right'; for (i=0; t = txt.right[i]; i++) if (!(Math.round(t[0]/this.get('step'))%step2)) { tf = this.formatCoord(t[0], 'right'); ctx.strokeText(tf, w-offset, t[1][1]); ctx.fillText(tf, w-offset, t[1][1]); } } // Draw border if (hasBorder) { var fillColor = this.getStyle().getFill().getColor(); var color, stroke; if (stroke = this.getStyle().getStroke()) { color = this.getStyle().getStroke().getColor(); } else { color = fillColor; fillColor = "#fff"; } ctx.strokeStyle = color; ctx.lineWidth = stroke ? stroke.getWidth() : 1; // for (i=1; ib * @api */ ol.control.GridReference.prototype.sortFeatures = function (a,b) { return (this.getFeatureName(a) == this.getFeatureName(b)) ? 0 : (this.getFeatureName(a) < this.getFeatureName(b)) ? -1 : 1; }; /** Get the feature title * @param {ol.Feature} f * @return the first letter of the eature name (getFeatureName) * @api */ ol.control.GridReference.prototype.indexTitle = function (f) { return this.getFeatureName(f).charAt(0); }; /** Display features in the index * @param { Array | ol.Collection } features */ ol.control.GridReference.prototype.setIndex = function (features) { if (!this.getMap()) return; var self = this; if (features.getArray) features = features.getArray(); features.sort ( function(a,b) { return self.sortFeatures(a,b); } ); this.element.innerHTML = ""; var elt = this.element; var search = document.createElement("input"); search.setAttribute('type', 'search'); search.setAttribute('placeholder', this.get('filterLabel') || 'filter'); var searchKeyupFunction = function() { var v = this.value.replace(/^\*/,''); // console.log(v) var r = new RegExp (v, 'i'); var list = ul.querySelectorAll('li'); Array.prototype.forEach.call(list, function(li) { if (li.classList.contains('ol-title')) { li.style.display = ''; } else { if (r.test(li.querySelector('.ol-name').textContent)) li.style.display = ''; else li.style.display = 'none'; } }); Array.prototype.forEach.call(ul.querySelectorAll("li.ol-title"), function(li) { var nextVisible; var start = false; for (var i=0; i=size[0]) return ""; var dy = Math.floor ( (extent[3] - coords[1]) / (extent[3]- extent[1]) * size[1] ); if (dy<0 || dy>=size[1]) return ""; return this.getHIndex(dx)+this.getVIndex(dy); }; /** Get vertical index (0,1,2,3...) * @param {number} index * @returns {string} * @api */ ol.control.GridReference.prototype.getVIndex = function (index) { return index; }; /** Get horizontal index (A,B,C...) * @param {number} index * @returns {string} * @api */ ol.control.GridReference.prototype.getHIndex = function (index) { return String.fromCharCode(65 + index); }; /** Draw the grid * @param {ol.event} e postcompose event * @private */ ol.control.GridReference.prototype._draw = function (e) { if (this.get('maxResolution')h) { y = h-spacing; ctx.textBaseline = 'alphabetic'; } else ctx.textBaseline = 'hanging'; ctx.strokeText(letter, x, y); ctx.fillText(letter, x, y); } ctx.textBaseline = 'middle'; for (i=0; iw) { x = w-spacing; ctx.textAlign = 'right'; } else ctx.textAlign = 'left'; ctx.strokeText(letter, x, y); ctx.fillText(letter, x, y); } ctx.restore(); }; /** Image line control * * @constructor * @extends {ol.control.Control} * @fires select * @fires collapse * @param {Object=} options Control options. * @param {String} options.className class of the control * @param {Array|ol.source.Vector} options.source vector sources that contains the images * @param {Array} options.layers A list of layers to display images. If no source and no layers, all visible layers will be considered. * @param {function} options.getImage a function that gets a feature and return the image url or false if no image to Show, default return the img propertie * @param {function} options.getTitle a function that gets a feature and return the title, default return an empty string * @param {boolean} options.collapsed the line is collapse, default false * @param {boolean} options.collapsible the line is collapsible, default false * @param {number} options.maxFeatures the maximum image element in the line, default 100 * @param {number} options.useExtent only show feature in the current extent * @param {boolean} options.hover select image on hover, default false * @param {string|boolean} options.linkColor link color or false if no link, default false */ ol.control.Imageline = function(options) { var element = ol.ext.element.create('DIV', { className: (options.className || '') + ' ol-imageline' + (options.target ? '': ' ol-unselectable ol-control') + (options.collapsed && options.collapsible ? 'ol-collapsed' : '') }); if (!options.target && options.collapsible) { ol.ext.element.create('BUTTON', { type: 'button', click: function() { this.toggle(); }.bind(this), parent: element }); } // Source if (options.source) this._sources = options.source.forEach ? options.source : [options.source]; if (options.layers) { this.setLayers(options.layers); } // Initialize ol.control.Control.call(this, { element: element, target: options.target }); // Scroll imageline this._setScrolling(); this._scrolldiv.addEventListener("scroll", function() { if (this.getMap()) this.getMap().render(); }.bind(this)); // Parameters if (typeof(options.getImage)==='function') this._getImage = options.getImage; if (typeof(options.getTitle)==='function') this._getTitle = options.getTitle; this.set('maxFeatures', options.maxFeatures || 100); this.set('linkColor', options.linkColor || false); this.set('hover', options.hover || false); this.set('useExtent', options.useExtent || false); this.refresh(); }; ol.ext.inherits(ol.control.Imageline, ol.control.Control); /** * Remove the control from its current map and attach it to the new map. * @param {ol.Map} map Map. * @api stable */ ol.control.Imageline.prototype.setMap = function (map) { if (this._listener) { this._listener.forEach(function(l) { ol.Observable.unByKey(l); }.bind(this)); } this._listener = null; ol.control.Control.prototype.setMap.call(this, map); if (map) { this._listener = [ map.on('postcompose', this._drawLink.bind(this)), map.on('moveend', function() { if (this.get('useExtent')) this.refresh(); }.bind(this)) ] this.refresh(); } }; /** Set layers to use in the control * @param {Array} */ ol.control.Imageline.prototype.getFeatures = function() { var map = this.getMap(); if (!map) return []; var features = []; var sources = this._sources || this._getSources(map.getLayers()); sources.forEach(function(s) { if (features.length < this.get('maxFeatures')) { if (!this.get('useExtent') || !map) { features.push(s.getFeatures()); } else { var extent = map.getView().calculateExtent(map.getSize()); features.push(s.getFeaturesInExtent(extent)); } } }.bind(this)) return features; }; /** Set element scrolling with a acceleration effect on desktop * (on mobile it uses the scroll of the browser) * @private */ ol.control.Imageline.prototype._setScrolling = function() { var elt = this._scrolldiv = ol.ext.element.create('DIV', { parent: this.element }); ol.ext.element.scrollDiv(elt, { // Prevent selection when moving onmove: function(b) { this._moving=b; }.bind(this) }); }; /** * Refresh the imageline with new data */ ol.control.Imageline.prototype.refresh = function() { this._scrolldiv.innerHTML = ''; var allFeatures = this.getFeatures(); var current = this._select ? this._select.feature : null; if (this._select) this._select.elt = null; this._iline = []; if (this.getMap()) this.getMap().render(); // Add a new image var addImage = function(f) { if (this._getImage(f)) { var img = ol.ext.element.create('DIV', { className: 'ol-image', parent: this._scrolldiv }); ol.ext.element.create('IMG', { src: this._getImage(f), parent: img }).addEventListener('load', function(){ this.classList.add('ol-loaded'); }); ol.ext.element.create('SPAN', { html: this._getTitle(f), parent: img }); // Current image var sel = { elt: img, feature: f }; // On click > dispatch event img.addEventListener('click', function(){ if (!this._moving) { this.dispatchEvent({type: 'select', feature: f }); this._scrolldiv.scrollLeft = img.offsetLeft + ol.ext.element.getStyle(img, 'width')/2 - ol.ext.element.getStyle(this.element, 'width')/2; if (this._select) this._select.elt.classList.remove('select'); this._select = sel; this._select.elt.classList.add('select'); } }.bind(this)); // Show link img.addEventListener('mouseover', function(e) { if (this.get('hover')) { if (this._select) this._select.elt.classList.remove('select'); this._select = sel; this._select.elt.classList.add('select'); this.getMap().render(); e.stopPropagation(); } }.bind(this)); // Remove link img.addEventListener('mouseout', function(e) { if (this.get('hover')) { if (this._select) this._select.elt.classList.remove('select'); this._select = false; this.getMap().render(); e.stopPropagation(); } }.bind(this)); // Prevent image dragging img.ondragstart = function(){ return false; }; // Add image this._iline.push(sel); if (current===f) { this._select = sel; sel.elt.classList.add('select'); } } }.bind(this); // Add images var nb = this.get('maxFeatures'); allFeatures.forEach(function(features) { for (var i=0, f; f=features[i]; i++) { if (nb-- < 0) break; addImage(f); } }.bind(this)); // Add the selected one if (this._select && this._select.feature && !this._select.elt) { addImage(this._select.feature); } }; /** Center image line on a feature * @param {ol.feature} feature * @param {boolean} scroll scroll the line to center on the image, default true * @api */ ol.control.Imageline.prototype.select = function(feature, scroll) { this._select = false; // Find the image this._iline.forEach(function (f) { if (f.feature === feature) { f.elt.classList.add('select'); this._select = f; if (scroll!==false) { this._scrolldiv.scrollLeft = f.elt.offsetLeft + ol.ext.element.getStyle(f.elt, 'width')/2 - ol.ext.element.getStyle(this.element, 'width')/2; } } else { f.elt.classList.remove('select'); } }.bind(this)); }; /** Draw link on the map * @private */ ol.control.Imageline.prototype._drawLink = function(e) { if (!this.get('linkColor') | this.isCollapsed()) return; var map = this.getMap(); if (map && this._select && this._select.elt) { var ctx = e.context || ol.ext.getMapCanvas(this.getMap()).getContext('2d'); var ratio = e.frameState.pixelRatio; var pt = [ this._select.elt.offsetLeft - this._scrolldiv.scrollLeft + ol.ext.element.getStyle(this._select.elt, 'width')/2, parseFloat(ol.ext.element.getStyle(this.element, 'top')) || this.getMap().getSize()[1] ]; var geom = this._select.feature.getGeometry().getFirstCoordinate(); geom = this.getMap().getPixelFromCoordinate(geom); ctx.save(); ctx.fillStyle = this.get('linkColor'); ctx.beginPath(); if (geom[0]>pt[0]) { ctx.moveTo((pt[0]-5)*ratio, pt[1]*ratio); ctx.lineTo((pt[0]+5)*ratio, (pt[1]+5)*ratio); } else { ctx.moveTo((pt[0]-5)*ratio, (pt[1]+5)*ratio); ctx.lineTo((pt[0]+5)*ratio, pt[1]*ratio); } ctx.lineTo(geom[0]*ratio, geom[1]*ratio); ctx.fill(); ctx.restore(); } }; /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Geoportail isochrone Control. * @see https://geoservices.ign.fr/documentation/geoservices/isochrones.html * @constructor * @extends {ol.control.Control} * @fires isochrone * @fires error * @param {Object=} options * @param {string} options.className control class name * @param {string} [options.apiKey] Geoportail apo key * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {string | undefined} options.inputLabel label for the input, default none * @param {string | undefined} options.noCollapse prevent collapsing on input blur, default false * @param {number | undefined} options.typing a delay on each typing to start searching (ms) use -1 to prevent autocompletion, default 300. * @param {integer | undefined} options.minLength minimum length to start searching, default 1 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * @param {integer | undefined} options.maxHistory maximum number of items to display in history. Set -1 if you don't want history, default maxItems * @param {function} options.getTitle a function that takes a feature and return the name to display in the index. * @param {function} options.autocomplete a function that take a search string and callback function to send an array * * @param {string} options.exclusions Exclusion list separate with a comma 'Toll,Tunnel,Bridge' */ ol.control.IsochroneGeoportail = function(options) { var self = this; if (!options) options = {}; if (options.typing == undefined) options.typing = 300; var classNames = (options.className ? options.className : '')+ ' ol-isochrone ol-routing'; if (!options.target) classNames += ' ol-unselectable ol-control'; var element = ol.ext.element.create('DIV', { className: classNames }) if (!options.target) { var bt = ol.ext.element.create('BUTTON', { parent: element }) bt.addEventListener('click', function(){ element.classList.toggle('ol-collapsed'); }); } // Inherits ol.control.Control.call(this, { element: element, target: options.target }); this.set('iter', 1); var content = ol.ext.element.create('DIV', { className: 'content', parent: element } ) // Search control this._addSearchCtrl(content, options); // Method buttons ol.ext.element.create('BUTTON', { className: 'ol-button ol-method-time selected', title:'isochrone', parent: content }) .addEventListener('click', function(){ this.setMethod('time'); }.bind(this)); ol.ext.element.create('I', { className: 'ol-button ol-method-distance', title:'isodistance', parent: content }) .addEventListener('click', function(){ this.setMethod('distance'); }.bind(this)); // Mode buttons ol.ext.element.create('I', { className: 'ol-button ol-car selected', title:'by car', parent: content }) .addEventListener('click', function(){ this.setMode('car'); }.bind(this)); ol.ext.element.create('I', { className: 'ol-button ol-pedestrian', title:'by foot', parent: content }) .addEventListener('click', function(){ this.setMode('pedestrian'); }.bind(this)); // Direction buttons ol.ext.element.create('I', { className: 'ol-button ol-direction-direct selected', title:'direct', parent: content }) .addEventListener('click', function(){ this.setDirection('direct'); }.bind(this)); ol.ext.element.create('I', { className: 'ol-button ol-direction-reverse', title:'reverse', parent: content }) .addEventListener('click', function(){ this.setDirection('reverse'); }.bind(this)); // Input var div = ol.ext.element.create('DIV', { className: 'ol-time', parent: content }) ol.ext.element.create('DIV', { html:'isochrone:', parent: div }); ol.ext.element.create('INPUT', { type: 'number', parent: div, min: 0 }) .addEventListener('change', function(){ self.set('hour', Number(this.value)); }); ol.ext.element.create('TEXT', { parent: div, html: 'h' }); ol.ext.element.create('INPUT', { type: 'number', parent: div, min: 0 }) .addEventListener('change', function(){ self.set('minute', Number(this.value)); }); ol.ext.element.create('TEXT', { parent: div, html: 'mn' }); div = ol.ext.element.create('DIV', { className: 'ol-distance', parent: content }); ol.ext.element.create('DIV', { html:'isodistance:', parent: div }); ol.ext.element.create('INPUT', { type: 'number', step: 'any', parent: div, min: 0 }) .addEventListener('change', function(){ self.set('distance', Number(this.value)); }); ol.ext.element.create('TEXT', { parent: div, html: 'km' }); div = ol.ext.element.create('DIV', { className: 'ol-iter', parent: content }) ol.ext.element.create('DIV', { html:'Iteration:', parent: div }); ol.ext.element.create('INPUT', { type: 'number', parent: div, value: 1, min: 1 }) .addEventListener('change', function(){ self.set('iter', Number(this.value)); }); // OK button ol.ext.element.create('I', { className:'ol-ok', html:'ok', parent: content }) .addEventListener('click', function() { var val = 0; switch (this.get('method')) { case 'distance': { val = this.get('distance')*1000; break; } default: { val = (this.get('hour')||0)*3600 + (this.get('minute')||0)*60; break; } } if (val && this.get('coordinate')) { this.search(this.get('coordinate'), val); } }.bind(this)); this.set('url', 'https://wxs.ign.fr/'+(options.apiKey || 'essentiels')+'/isochrone/isochrone.json'); this._ajax = new ol.ext.Ajax({ dataType: 'JSON', auth: options.auth }); this._ajax.on('success', this._success.bind(this)); this._ajax.on('error', this._error.bind(this)); // searching this._ajax.on('loadstart', function() { this.element.classList.add('ol-searching'); }.bind(this)); this._ajax.on('loadend', function() { this.element.classList.remove('ol-searching'); }.bind(this)); this.setMethod(options.method); }; ol.ext.inherits(ol.control.IsochroneGeoportail, ol.control.Control); /** * Set the map instance the control is associated with * and add its controls associated to this map. * @param {_ol_Map_} map The map instance. */ ol.control.IsochroneGeoportail.prototype.setMap = function (map) { ol.control.Control.prototype.setMap.call(this, map); this._search.setMap(map); }; /** Add a new search input * @private */ ol.control.IsochroneGeoportail.prototype._addSearchCtrl = function (element, options) { var div = ol.ext.element.create("DIV", { parent: element }); var search = this._search = new ol.control.SearchGeoportail({ className: 'IGNF ol-collapsed', apiKey: options.apiKey, target: div }); search.on('select', function(e){ search.setInput(e.search.fulltext); this.set('coordinate', e.coordinate); }.bind(this)); search.on('change:input', function(){ this.set('coordinate', false); }.bind(this)); }; /** Set the travel method * @param [string] method The method (time or distance) */ ol.control.IsochroneGeoportail.prototype.setMethod = function(method) {7 method = (/distance/.test(method) ? 'distance' : 'time'); this.element.querySelector(".ol-method-time").classList.remove("selected"); this.element.querySelector(".ol-method-distance").classList.remove("selected"); this.element.querySelector(".ol-method-"+method).classList.add("selected"); this.element.querySelector("div.ol-time").classList.remove("selected"); this.element.querySelector("div.ol-distance").classList.remove("selected"); this.element.querySelector("div.ol-"+method).classList.add("selected"); this.set('method', method); }; /** Set mode * @param {string} mode The mode: 'car' or 'pedestrian', default 'car' */ ol.control.IsochroneGeoportail.prototype.setMode = function (mode) { this.set('mode', mode); this.element.querySelector(".ol-car").classList.remove("selected"); this.element.querySelector(".ol-pedestrian").classList.remove("selected"); this.element.querySelector(".ol-"+mode).classList.add("selected"); }; /** Set direction * @param {string} direction The direction: 'direct' or 'reverse', default direct */ ol.control.IsochroneGeoportail.prototype.setDirection = function (direction) { this.set('direction', direction); this.element.querySelector(".ol-direction-direct").classList.remove("selected"); this.element.querySelector(".ol-direction-reverse").classList.remove("selected"); this.element.querySelector(".ol-direction-"+direction).classList.add("selected"); }; /** Calculate an isochrone * @param {ol.coordinate} coord * @param {number|string} option A number as time (in second) or distance (in meter), depend on method propertie * or a string with a unit (s, mn, h for time or km, m) */ ol.control.IsochroneGeoportail.prototype.search = function(coord, option, iter) { var proj = this.getMap() ? this.getMap().getView().getProjection() : 'EPSG:3857'; var method = /distance/.test(this.get('method')) ? 'distance' : 'time'; if (typeof(option)==='string') { var unit = option.replace(/([0-9|.]*)([a-z]*)$/,'$2'); method = 'time'; option = parseFloat(option); // convert unit switch (unit) { case 'mn': { option = option*60; break; } case 'h': { option = option*3600; break; } case 'm': { method = 'distance'; break; } case 'km': { method = 'distance'; option = option*1000; break; } } } var dt = Math.round(option * (this.get('iter')-(iter||0)) / this.get('iter')); if (typeof option === 'number') { // Send data var data = { 'gp-access-lib': '2.1.0', location: ol.proj.toLonLat(coord, proj), graphName: (this.get('mode')==='pedestrian' ? 'Pieton' : 'Voiture'), exclusions: this.get('exclusions') || undefined, method: method, time: method==='time' ? dt : undefined, distance: method==='distance' ? dt : undefined, reverse: (this.get('direction') === 'reverse'), smoothing: this.get('smoothing') || true, holes: this.get('holes') || false }; this._ajax.send(this.get('url'), data, { coord: coord, option: option, data: data, iteration: (iter||0)+1 }); } }; /** Trigger result * @private */ ol.control.IsochroneGeoportail.prototype._success = function(e) { var proj = this.getMap() ? this.getMap().getView().getProjection() : 'EPSG:3857'; // Convert to features var format = new ol.format.WKT(); var evt = e.response; evt.feature = format.readFeature(evt.wktGeometry, { dataProjection: 'EPSG:4326', featureProjection: proj }); evt.feature.set('iteration', e.options.iteration); evt.feature.set('method', e.options.data.method); evt.feature.set(e.options.data.method, e.options.data[e.options.data.method]); delete evt.wktGeometry; evt.type = 'isochrone'; evt.iteration = e.options.iteration-1; this.dispatchEvent (evt); if (e.options.iteration < this.get('iter')) { this.search(e.options.coord, e.options.option, e.options.iteration); } }; /** Trigger error * @private */ ol.control.IsochroneGeoportail.prototype._error = function() { this.dispatchEvent ({ type:'error' }); }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * OpenLayers Layer Switcher Control. * * @constructor * @extends {ol.control.LayerSwitcher} * @param {Object=} options Control options. */ ol.control.LayerPopup = function(options) { options = options || {}; options.switcherClass="ol-layerswitcher-popup"; if (options.mouseover!==false) options.mouseover=true; ol.control.LayerSwitcher.call(this, options); }; ol.ext.inherits(ol.control.LayerPopup, ol.control.LayerSwitcher); /** Disable overflow */ ol.control.LayerPopup.prototype.overflow = function(){}; /** Render a list of layer * @param {elt} element to render * @layers {Array{ol.layer}} list of layer to show * @api stable */ ol.control.LayerPopup.prototype.drawList = function(ul, layers) { var self=this; var setVisibility = function(e) { e.preventDefault(); var l = self._getLayerForLI(this); self.switchLayerVisibility(l,layers); if (e.type=="touchstart") self.element.classList.add("ol-collapsed"); }; layers.forEach(function(layer) { if (self.displayInLayerSwitcher(layer)) { var d = ol.ext.element.create('LI', { html: layer.get("title") || layer.get("name"), on: { 'click touchstart': setVisibility }, parent: ul }); self._setLayerForLI(d, layer); if (self.testLayerVisibility(layer)) d.classList.add("ol-layer-hidden"); if (layer.getVisible()) d.classList.add('ol-visible'); } }); }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** LayerShop a layer switcher with special controls to handle operation on layers. * @fires select * @fires drawlist * @fires toggle * @fires reorder-start * @fires reorder-end * @fires layer:visible * @fires layer:opacity * * @constructor * @extends {ol.control.LayerSwitcher} * @param {Object=} options * @param {boolean} options.selection enable layer selection when click on the title * @param {function} options.displayInLayerSwitcher function that takes a layer and return a boolean if the layer is displayed in the switcher, default test the displayInLayerSwitcher layer attribute * @param {boolean} options.show_progress show a progress bar on tile layers, default false * @param {boolean} options.mouseover show the panel on mouseover, default false * @param {boolean} options.reordering allow layer reordering, default true * @param {boolean} options.trash add a trash button to delete the layer, default false * @param {function} options.oninfo callback on click on info button, if none no info button is shown DEPRECATED: use on(info) instead * @param {boolean} options.extent add an extent button to zoom to the extent of the layer * @param {function} options.onextent callback when click on extent, default fits view to extent * @param {number} options.drawDelay delay in ms to redraw the layer (usefull to prevent flickering when manipulating the layers) * @param {boolean} options.collapsed collapse the layerswitcher at beginning, default true * @param {ol.layer.Group} options.layerGroup a layer group to display in the switcher, default display all layers of the map * @param {boolean} options.noScroll prevent handle scrolling, default false * * Layers attributes that control the switcher * - allwaysOnTop {boolean} true to force layer stay on top of the others while reordering, default false * - displayInLayerSwitcher {boolean} display the layer in switcher, default true * - noSwitcherDelete {boolean} to prevent layer deletion (w. trash option = true), default false */ ol.control.LayerShop = function(options) { options = options || {}; options.selection = true; options.noScroll = true; ol.control.LayerSwitcher.call (this, options); this.element.classList.add('ol-layer-shop'); // Control title (selected layer) var title = this.element.insertBefore(ol.ext.element.create('DIV', { className: 'ol-title-bar' }), this.getPanel()); this.on('select', function(e) { title.innerText = e.layer ? e.layer.get('title') : ''; this.element.setAttribute('data-layerClass', this.getLayerClass(e.layer)); }.bind(this)); // Top/bottom bar this._topbar = this.element.insertBefore(ol.ext.element.create('DIV', { className: 'ol-bar ol-top-bar' }), this.getPanel()); this._bottombar = ol.ext.element.create('DIV', { className: 'ol-bar ol-bottom-bar', parent: this.element }); this._controls = []; }; ol.ext.inherits(ol.control.LayerShop, ol.control.LayerSwitcher); /** Set the map instance the control is associated with. * @param {_ol_Map_} map The map instance. */ ol.control.LayerShop.prototype.setMap = function(map) { if (this.getMap()) { // Remove map controls this._controls.forEach(function(c) { this.getMap().removeControl(c) }.bind(this)); } ol.control.LayerSwitcher.prototype.setMap.call(this, map); if (map) { // Select first layer this.selectLayer(); // Remove a layer this._listener.removeLayer = map.getLayers().on('remove', function(e) { // Select first layer if (e.element === this.getSelection()) { this.selectLayer(); } }.bind(this)); // Add controls this._controls.forEach(function(c) { this.getMap().addControl(c) }.bind(this)); } }; /** Get the bar element (to add new element in it) * @param {string} [position='top'] bar position bottom or top, default top * @returns {Element} */ ol.control.LayerShop.prototype.getBarElement = function(position) { return position==='bottom' ? this._bottombar : this._topbar; }; /** Add a control to the panel * @param {ol.control.Control} control * @param {string} [position='top'] bar position bottom or top, default top */ ol.control.LayerShop.prototype.addControl = function(control, position) { this._controls.push(control); control.setTarget(position==='bottom' ? this._bottombar : this._topbar); if (this.getMap()) { this.getMap().addControl(control); } }; /* 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). */ /** * @classdesc OpenLayers Layer Switcher Control. * @require layer.getPreview * * @constructor * @extends {ol.control.LayerSwitcher} * @param {Object=} options Control options. */ ol.control.LayerSwitcherImage = function(options) { options = options || {}; options.switcherClass = "ol-layerswitcher-image"; if (options.mouseover!==false) options.mouseover=true; ol.control.LayerSwitcher.call(this, options); }; ol.ext.inherits(ol.control.LayerSwitcherImage, ol.control.LayerSwitcher); /** Render a list of layer * @param {elt} element to render * @layers {Array{ol.layer}} list of layer to show * @api stable */ ol.control.LayerSwitcherImage.prototype.drawList = function(ul, layers) { var self = this; var setVisibility = function(e) { e.preventDefault(); var l = self._getLayerForLI(this); self.switchLayerVisibility(l,layers); if (e.type=="touchstart") self.element.classList.add("ol-collapsed"); }; ol.ext.element.setStyle(ul, { height: 'auto' }); layers.forEach(function(layer) { if (self.displayInLayerSwitcher(layer)) { var preview = layer.getPreview ? layer.getPreview() : ["none"]; var d = ol.ext.element.create('LI', { className: 'ol-imgcontainer' + (layer.getVisible() ? ' ol-visible':''), on: { 'touchstart click': setVisibility }, parent: ul }); self._setLayerForLI(d, layer); preview.forEach(function(img){ ol.ext.element.create('IMG', { src: img, parent: d }) }); ol.ext.element.create('p', { html: layer.get("title") || layer.get("name"), parent: d }); if (self.testLayerVisibility(layer)) d.classList.add('ol-layer-hidden'); } }); }; /** Disable overflow */ ol.control.LayerSwitcherImage.prototype.overflow = function(){}; // eslint-disable-next-line no-unused-vars /** Create a legend for styles * @constructor * @extends {ol.control.CanvasBase} * @fires select * @param {*} options * @param {String} options.className class of the control * @param {ol.legend.Legend} options.legend * @param {boolean | undefined} options.collapsed Specify if legend should be collapsed at startup. Default is true. * @param {boolean | undefined} options.collapsible Specify if legend can be collapsed, default true. * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. */ ol.control.Legend = function(options) { options = options || {}; var element = document.createElement('div'); if (options.target) { element.className = options.className || 'ol-legend'; } else { element.className = (options.className || 'ol-legend') +' ol-unselectable ol-control' +(options.collapsible===false ? ' ol-uncollapsible': ' ol-collapsed'); // Show on click var button = document.createElement('button'); button.setAttribute('type', 'button'); button.addEventListener('click', function() { this.toggle(); }.bind(this)); element.appendChild(button); // Hide on click button = document.createElement('button'); button.setAttribute('type', 'button'); button.className = 'ol-closebox'; button.addEventListener('click', function() { this.toggle(); }.bind(this)); element.appendChild(button); } ol.control.CanvasBase.call(this, { element: element, target: options.target }); // The legend this._legend = options.legend; this._legend.getCanvas().className = 'ol-legendImg'; element.appendChild(this._legend.getCanvas()); element.appendChild(this._legend.getListElement()); if (options.collapsible!==false && options.collapsed===false) this.show(); this._legend.on('select', function(e) { this.dispatchEvent(e); }.bind(this)); this._legend.on('refresh', function() { if (this._onCanvas && this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } }.bind(this)); }; ol.ext.inherits(ol.control.Legend, ol.control.CanvasBase); /** Get the legend associated with the control * @returns {ol.legend.Legend} */ ol.control.Legend.prototype.getLegend = function () { return this._legend; }; /** Draw control on canvas * @param {boolean} b draw on canvas. */ ol.control.Legend.prototype.setCanvas = function (b) { this._onCanvas = b; this.element.style.visibility = b ? "hidden":"visible"; if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } }; /** Is control on canvas * @returns {boolean} */ ol.control.Legend.prototype.onCanvas = function () { return !!this._onCanvas; }; /** Draw legend on canvas * @private */ ol.control.Legend.prototype._draw = function (e) { if (this._onCanvas && !this.element.classList.contains('ol-collapsed')) { var canvas = this._legend.getCanvas(); var ctx = this.getContext(e); var h = ctx.canvas.height - canvas.height; ctx.save(); ctx.rect(0, h, canvas.width, canvas.height); var col = '#fff'; if (this._legend.getTextStyle().getBackgroundFill()) { col = ol.color.asString(this._legend.getTextStyle().getBackgroundFill().getColor()); } ctx.fillStyle = ctx.strokeStyle = col; ctx.lineWidth = 10; ctx.lineJoin = 'round'; ctx.stroke(); ctx.clearRect(0, h, canvas.width, canvas.height); ctx.fill(); ctx.drawImage(canvas, 0, h); ctx.restore(); } }; /** Show control */ ol.control.Legend.prototype.show = function() { if (this.element.classList.contains('ol-collapsed')) { this.element.classList.remove('ol-collapsed'); this.dispatchEvent({ type:'change:collapse', collapsed: false }); if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } } }; /** Hide control */ ol.control.Legend.prototype.hide = function() { if (!this.element.classList.contains('ol-collapsed')) { this.element.classList.add('ol-collapsed'); this.dispatchEvent({ type:'change:collapse', collapsed: true }); if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } } }; /** Show/hide control * @returns {boolean} */ ol.control.Legend.prototype.collapse = function(b) { if (b===false) this.show(); else this.hide(); }; /** Is control collapsed * @returns {boolean} */ ol.control.Legend.prototype.isCollapsed = function() { return (this.element.classList.contains('ol-collapsed')); }; /** Toggle control */ ol.control.Legend.prototype.toggle = function() { this.element.classList.toggle('ol-collapsed'); this.dispatchEvent({ type:'change:collapse', collapsed: this.element.classList.contains('ol-collapsed') }); if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } }; /* 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). */ // /** A control to jump from one zone to another. * @constructor * @fires select * @extends {ol.control.Control} * @param {Object=} options Control options. * @param {string} options.className class name * @param {Array} options.zone an array of zone: { name, extent (in EPSG:4326) } * @param {ol.layer.Layer|function} options.layer layer to display in the control or a function that takes a zone and returns a layer to add to the control * @param {ol.ProjectionLike} options.projection projection of the control, Default is EPSG:3857 (Spherical Mercator). * @param {bolean} options.centerOnClick center on click when a zone is clicked (or listen to 'select' event to do something), default true */ ol.control.MapZone = function(options) { if (!options) options={}; var element = document.createElement("div"); if (options.target) { element = ol.ext.element.create('DIV', { className: options.className || "ol-mapzone" }); } else { element = ol.ext.element.create('DIV', { className: (options.className || "ol-mapzone") +' ol-unselectable ol-control ol-collapsed' }); var bt = ol.ext.element.create('BUTTON', { type: 'button', on: { 'click': function() { element.classList.toggle("ol-collapsed"); maps.forEach(function (m) { m.updateSize(); }); }.bind(this) }, parent: element }); ol.ext.element.create('I', { parent: bt }); } // Parent control ol.control.Control.call(this, { element: element, target: options.target }); this.set('centerOnClick', options.centerOnClick); // Create maps var maps = this._maps = []; this._projection = options.projection; this._layer = options.layer; options.zones.forEach(this.addZone.bind(this)); // Refresh the maps setTimeout(function() { maps.forEach(function (m) { m.updateSize(); }); }); }; ol.ext.inherits(ol.control.MapZone, ol.control.Control); /** Collapse the control * @param {boolean} b */ ol.control.MapZone.prototype.setCollapsed = function (b) { if (b) { this.element.classList.remove('ol-collapsed'); // Force map rendering this.getMaps().forEach(function (m) { m.updateSize(); }); } else { this.element.classList.add('ol-collapsed'); } }; /** Get control collapsed * @return {boolean} */ ol.control.MapZone.prototype.getCollapsed = function () { return this.element.classList.contains('ol-collapsed'); }; /** Set the control visibility (collapsed) * @param {boolean} b * @deprecated use setCollapsed instead */ ol.control.MapZone.prototype.setVisible = ol.control.MapZone.prototype.setCollapsed; /** Get associated maps * @return {ol.Map} */ ol.control.MapZone.prototype.getMaps = function () { return this._maps; }; /** Get nb zone */ ol.control.MapZone.prototype.getLength = function () { return this._maps.length; }; /** Add a new zone to the control * @param {Object} z * @param {string} title * @param {ol.extent} extent if map is not defined * @param {ol.Map} map if map is defined use the map extent * @param {ol.layer.Layer} [layer] layer of the zone, default use default control layer */ ol.control.MapZone.prototype.addZone = function (z) { var view = new ol.View({ zoom: 6, center: [0,0], projection: this._projection }); var extent; if (z.map) { extent = ol.proj.transformExtent(z.map.getView().calculateExtent(), z.map.getView().getProjection(), view.getProjection()) ; } else { extent = ol.proj.transformExtent(z.extent, 'EPSG:4326', view.getProjection()); } // console.log(extent, z.extent) var div = ol.ext.element.create('DIV', { className: 'ol-mapzonezone', parent: this.element, click : function() { // Get index var index = -1; this._maps.forEach(function(m, i) { if (m.get('zone') === z) { index = i; } }) this.dispatchEvent({ type: 'select', zone: z, index: index, coordinate: ol.extent.getCenter(extent), extent: extent }); if (this.get('centerOnClick') !== false) { this.getMap().getView().fit(extent); } this.setVisible(false); }.bind(this) }); var layer; if (z.layer) { layer = z.layer; } else if (typeof(this._layer) === 'function') { layer = this._layer(z); } else { // Try to clone the layer layer = new this._layer.constructor({ source: this._layer.getSource() }); } var map = new ol.Map({ target: div, view: view, controls: [], interactions:[], layers: [layer] }); map.set('zone', z); this._maps.push(map); view.fit(extent); // Name ol.ext.element.create('P', { html: z.title, parent: div }); }; /** Remove a zone from the control * @param {number} index */ ol.control.MapZone.prototype.removeZone = function (index) { var z = this.element.querySelectorAll('.ol-mapzonezone')[index]; if (z) { z.remove(); this._maps.splice(index, 1); } }; /** Pre-defined zones */ ol.control.MapZone.zones = {}; /** French overseas departments */ ol.control.MapZone.zones.DOM = [{ "title": "Guadeloupe", "extent": [ -61.898594315312444, 15.75623038647845, -60.957887532935324, 16.575317670979473 ] },{ "title": "Guyane", "extent": [ -54.72525931072715, 2.1603763430019, -51.528236062921344, 5.7984307809552575 ] },{ "title": "Martinique", "extent": [ -61.257556528564756, 14.387506317407514, -60.76934912110432, 14.895067461729951 ] },{ "title": "Mayotte", "extent": [ 44.959844536967815, -13.01674138212816, 45.35328866510648, -12.65521942207829 ] },{ "title": "La réunion", "extent": [ 55.17059012967656, -21.407680069231688, 55.88195702001797, -20.85560221637526 ] }]; /** French overseas territories */ ol.control.MapZone.zones.TOM = [{ "title": "Polynésie Française", "extent": [ 206.23664226630862, -22.189040615809787, 221.85920743981987, -10.835039595040698 ] },{ "title": "Nouvelle Calédonie", "extent": [ 163.76420580160925, -22.581641092751838, 167.66984709498706, -19.816411635668445 ] },{ "title": "St-Pierre et Miquelon", "extent": [ -56.453698765748676, 46.74449858188555, -56.0980198121544, 47.14669874229787 ] },{ "title": "Wallis et Futuna", "extent": [ 181.7588623143665, -14.7341169873267, 183.95612353301715, -13.134720799175085 ] },{ "title": "St-Martin St-Barthélemy", "extent": [ -63.1726389501678, 17.806097291313506, -62.7606535945649, 18.13267688837938 ] }]; /** French overseas departments and territories */ ol.control.MapZone.zones.DOMTOM = [{ title: 'Métropole', extent: [ -5.318421740712579, 41.16082274292913, 9.73284186155716, 51.21957336557702 ] }].concat(ol.control.MapZone.zones.DOM, ol.control.MapZone.zones.TOM); /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Control overlay for OL3 * The overlay control is a control that display an overlay over the map * * @constructor * @extends {ol.control.Control} * @fire change:visible * @param {Object=} options Control options. * @param {string} className class of the control * @param {boolean} options.closeBox add a close button * @param {boolean} options.hideOnClick close dialog when click */ ol.control.Notification = function(options) { options = options || {}; var element = document.createElement('DIV'); this.contentElement = ol.ext.element.create('DIV', { click: function() { if (this.get('hideOnClick')) this.hide(); }.bind(this), parent: element }); var classNames = (options.className||"")+ " ol-notification"; if (!options.target) { classNames += " ol-unselectable ol-control ol-collapsed"; } element.setAttribute('class', classNames); ol.control.Control.call(this, { element: element, target: options.target }); this.set('closeBox', options.closeBox); this.set('hideOnClick', options.hideOnClick); }; ol.ext.inherits(ol.control.Notification, ol.control.Control); /** * Display a notification on the map * @param {string|node|undefined} what the notification to show, default get the last one * @param {number} [duration=3000] duration in ms, if -1 never hide */ ol.control.Notification.prototype.show = function(what, duration) { var self = this; var elt = this.element; if (what) { if (what instanceof Node) { this.contentElement.innerHTML = ''; this.contentElement.appendChild(what); } else { this.contentElement.innerHTML = what; } if (this.get('closeBox')) { this.contentElement.classList.add('ol-close') ol.ext.element.create('SPAN', { className: 'closeBox', click: function() { this.hide(); }.bind(this), parent: this.contentElement }) } else { this.contentElement.classList.remove('ol-close') } } if (this._listener) { clearTimeout(this._listener); this._listener = null; } elt.classList.add('ol-collapsed'); this._listener = setTimeout(function() { elt.classList.remove('ol-collapsed'); if (!duration || duration >= 0) { self._listener = setTimeout(function() { elt.classList.add('ol-collapsed'); self._listener = null; }, duration || 3000); } else { self._listener = null; } }, 100); }; /** * Remove a notification on the map */ ol.control.Notification.prototype.hide = function() { if (this._listener) { clearTimeout(this._listener); this._listener = null; } this.element.classList.add('ol-collapsed'); }; /** * Toggle a notification on the map * @param {number} [duration=3000] duration in ms */ ol.control.Notification.prototype.toggle = function(duration) { if (this.element.classList.contains('ol-collapsed')) { this.show(null, duration); } else { this.hide(); } }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Control overlay for OL3 * The overlay control is a control that display an overlay over the map * * @constructor * @extends {ol.control.Control} * @fire change:visible * @param {Object=} options Control options. * @param {String} options.className class of the control * @param {String|Element} options.content * @param {bool} options.hideOnClick hide the control on click, default false * @param {bool} options.closeBox add a closeBox to the control, default false */ ol.control.Overlay = function(options) { if (!options) options={}; /* var element = document.createElement("div"); element.classList.add('ol-unselectable', 'ol-overlay'); //if (options.className) element.classList.add(options.className); */ var element = ol.ext.element.create('DIV', { className: 'ol-unselectable ol-overlay '+(options.className||''), html: options.content }); ol.control.Control.call(this, { element: element, target: options.target }); var self = this; if (options.hideOnClick) element.addEventListener("click", function(){self.hide();}); this.set("closeBox", options.closeBox); this._timeout = false; this.setContent (options.content); }; ol.ext.inherits(ol.control.Overlay, ol.control.Control); /** Set the content of the overlay * @param {string|Element} html the html to display in the control */ ol.control.Overlay.prototype.setContent = function (html) { var self = this; if (html) { var elt = this.element; if (html instanceof Element) { elt.innerHTML=''; elt.appendChild(html) } else if (html!==undefined) elt.innerHTML = html; if (this.get("closeBox")) { var cb = document.createElement("div"); cb.classList.add("ol-closebox"); cb.addEventListener("click", function(){self.hide();}); elt.insertBefore(cb, elt.firstChild); } } }; /** Set the control visibility * @param {string|Element} html the html to display in the control * @param {ol.coordinate} coord coordinate of the top left corner of the control to start from */ ol.control.Overlay.prototype.show = function (html, coord) { var self = this; var elt = this.element; elt.style.display = 'block'; if (coord) { this.center_ = this.getMap().getPixelFromCoordinate(coord); elt.style.top = this.center_[1]+'px'; elt.style.left = this.center_[0]+'px'; } else { //TODO: Do fix from hkollmann pull request this.center_ = false; elt.style.top = ""; elt.style.left = ""; } if (html) this.setContent(html); if (this._timeout) clearTimeout(this._timeout); this._timeout = setTimeout(function() { elt.classList.add("ol-visible") elt.style.top = ""; elt.style.left = ""; self.dispatchEvent({ type:'change:visible', visible:true, element: self.element }); }, 10); }; /** Show an image * @param {string} src image url * @param {*} options * @param {string} options.title * @param {ol.coordinate} coordinate */ ol.control.Overlay.prototype.showImage = function (src, options) { options = options || {}; var content = ol.ext.element.create('DIV', { className: 'ol-fullscreen-image' }); ol.ext.element.create('IMG', { src: src, parent: content }); if (options.title) { content.classList.add('ol-has-title'); ol.ext.element.create('P', { html: options.title, parent: content }); } this.show(content, options.coordinate); }; /** Set the control visibility hidden */ ol.control.Overlay.prototype.hide = function () { var elt = this.element; this.element.classList.remove("ol-visible"); if (this.center_) { elt.style.top = this.center_[1]+'px'; elt.style.left = this.center_[0]+'px'; this.center_ = false; } if (this._timeout) clearTimeout(this._timeout); this._timeout = setTimeout(function(){ elt.style.display = 'none'; }, 500); this.dispatchEvent({ type:'change:visible', visible:false, element: this.element }); }; /** Toggle control visibility */ ol.control.Overlay.prototype.toggle = function () { if (this.getVisible()) this.hide(); else this.show(); } /** Get the control visibility * @return {boolean} b */ ol.control.Overlay.prototype.getVisible = function () { return ol.ext.element.getStyle(this.element, 'display') !== 'none'; }; /** Change class name * @param {String} className a class name or a list of class names separated by a space */ ol.control.Overlay.prototype.setClass = function (className) { var vis = this.element.classList.contains('ol-visible'); this.element.className = ('ol-unselectable ol-overlay '+(vis ? 'ol-visible ' : '')+className).trim(); }; /* 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). */ /** * OpenLayers 3 Layer Overview Control. * The overview can rotate with map. * Zoom levels are configurable. * Click on the overview will center the map. * Change width/height of the overview trough css. * * @constructor * @extends {ol.control.Control} * @param {Object=} options Control options. * @param {ol.ProjectionLike} options.projection The projection. Default is EPSG:3857 (Spherical Mercator). * @param {Number} options.minZoom default 0 * @param {Number} options.maxZoom default 18 * @param {boolean} options.rotation enable rotation, default false * @param {top|bottom-left|right} options.align position * @param {Array} options.layers list of layers * @param {ol.style.Style | Array. | undefined} options.style style to draw the map extent on the overveiw * @param {bool|elastic} options.panAnimation use animation to center map on click, default true */ ol.control.Overview = function(options) { options = options || {}; var self = this; // API this.minZoom = options.minZoom || 0; this.maxZoom = options.maxZoom || 18; this.rotation = options.rotation; var element; if (options.target) { element = document.createElement("div"); this.panel_ = options.target; } else { element = document.createElement("div"); element.classList.add('ol-overview', 'ol-unselectable', 'ol-control', 'ol-collapsed'); if (/top/.test(options.align)) element.classList.add('ol-control-top'); if (/right/.test(options.align)) element.classList.add('ol-control-right'); var button = document.createElement("button"); button.setAttribute('type','button'); button.addEventListener("touchstart", function(e){ self.toggleMap(); e.preventDefault(); }); button.addEventListener("click", function(){self.toggleMap()}); element.appendChild(button); this.panel_ = document.createElement("div"); this.panel_.classList.add("panel"); element.appendChild(this.panel_); } ol.control.Control.call(this, { element: element, target: options.target }); // Create a overview map this.ovmap_ = new ol.Map({ controls: new ol.Collection(), interactions: new ol.Collection(), target: this.panel_, view: new ol.View ({ zoom: 2, center: [0,0], projection: options.projection }), layers: options.layers }); this.oview_ = this.ovmap_.getView(); // Cache extent this.extentLayer = new ol.layer.Vector({ name: 'Cache extent', source: new ol.source.Vector(), style: options.style || [new ol.style.Style({ image: new ol.style.Circle({ fill: new ol.style.Fill({ color: 'rgba(255,0,0, 1)' }), stroke: new ol.style.Stroke({ width: 7, color: 'rgba(255,0,0, 0.8)' }), radius: 5 }), stroke: new ol.style.Stroke({ width: 5, color: "rgba(255,0,0,0.8)" }) } )] }) this.ovmap_.addLayer(this.extentLayer); /** Elastic bounce * @param {Int} bounce number of bounce * @param {Number} amplitude amplitude of the bounce [0,1] * @return {Number} * / var bounceFn = function (bounce, amplitude){ var a = (2*bounce+1) * Math.PI/2; var b = amplitude>0 ? -1/amplitude : -100; var c = - Math.cos(a) * Math.pow(2, b); return function(t) { t = 1-Math.cos(t*Math.PI/2); return 1 + Math.abs( Math.cos(a*t) ) * Math.pow(2, b*t) + c*t; } } /** Elastic bounce * @param {Int} bounce number of bounce * @param {Number} amplitude amplitude of the bounce [0,1] * @return {Number} */ var elasticFn = function (bounce, amplitude) { var a = 3*bounce * Math.PI/2; var b = amplitude>0 ? -1/amplitude : -100; var c = Math.cos(a) * Math.pow(2, b); return function(t){ t = 1-Math.cos(t*Math.PI/2); return 1 - Math.cos(a*t) * Math.pow(2, b*t) + c*t; } } // Click on the preview center the map this.ovmap_.addInteraction (new ol.interaction.Pointer({ handleDownEvent: function(evt) { if (options.panAnimation !==false) { if (options.panAnimation=="elastic" || options.elasticPan) { self.getMap().getView().animate({ center: evt.coordinate, easing: elasticFn(2,0.3), duration: 1000 }); } else { self.getMap().getView().animate({ center: evt.coordinate, duration: 300 }); } } else self.getMap().getView().setCenter(evt.coordinate); return false; } })); }; ol.ext.inherits(ol.control.Overview, ol.control.Control); /** Get overview map * @return {ol.Map} */ ol.control.Overview.prototype.getOverviewMap = function(){ return this.ovmap_; }; /** Toggle overview map */ ol.control.Overview.prototype.toggleMap = function(){ this.element.classList.toggle("ol-collapsed"); this.ovmap_.updateSize(); this.setView(); }; /** Set overview map position * @param {top|bottom-left|right} */ ol.control.Overview.prototype.setPosition = function(align){ if (/top/.test(align)) this.element.classList.add("ol-control-top"); else this.element.classList.remove("ol-control-top"); if (/right/.test(align)) this.element.classList.add("ol-control-right"); else this.element.classList.remove("ol-control-right"); }; /** * Set the map instance the control associated with. * @param {ol.Map} map The map instance. */ ol.control.Overview.prototype.setMap = function(map) { if (this._listener) { for (var i in this._listener) { ol.Observable.unByKey(this._listener[i]); } } this._listener = {}; ol.control.Control.prototype.setMap.call(this, map); if (map) { this._listener.map = map.on('change:view', function() { if (this._listener.view) ol.Observable.unByKey(this._listener.view); if (map.getView()) { this._listener.view = map.getView().on('propertychange', this.setView.bind(this)); this.setView(); } }.bind(this)); this._listener.view = map.getView().on('propertychange', this.setView.bind(this)); this.setView(); } }; /** Calculate the extent of the map and draw it on the overview */ ol.control.Overview.prototype.calcExtent_ = function(extent){ var map = this.getMap(); if (!map) return; var source = this.extentLayer.getSource(); source.clear(); var f = new ol.Feature(); var size = map.getSize(); var resolution = map.getView().getResolution(); var rotation = map.getView().getRotation(); var center = map.getView().getCenter(); if (!resolution) return; var dx = resolution * size[0] / 2; var dy = resolution * size[1] / 2; var res2 = this.oview_.getResolution(); if (dx/res2>5 || dy/res2>5) { var cos = Math.cos(rotation); var sin = Math.sin(rotation); var i, x, y; extent=[[-dx,-dy],[-dx,dy],[dx,dy],[dx,-dy]]; for (i = 0; i < 4; ++i) { x = extent[i][0]; y = extent[i][1]; extent[i][0] = center[0] + x * cos - y * sin; extent[i][1] = center[1] + x * sin + y * cos; } f.setGeometry (new ol.geom.Polygon( [ extent ])); } else { f.setGeometry (new ol.geom.Point( center )); } source.addFeature(f); }; /** * @private */ ol.control.Overview.prototype.setView = function(e){ if (!e) { // refresh all this.setView({key:'rotation'}); this.setView({key:'resolution'}); this.setView({key:'center'}); return; } // Set the view params switch (e.key){ case 'rotation': { if (this.rotation) this.oview_.setRotation(this.getMap().getView().getRotation()); else if (this.oview_.getRotation()) this.oview_.setRotation(0); break; } case 'center': { var mapExtent = this.getMap().getView().calculateExtent(this.getMap().getSize()); var extent = this.oview_.calculateExtent(this.ovmap_.getSize()); if (mapExtent[0]extent[2] || mapExtent[3]>extent[3]){ this.oview_.setCenter(this.getMap().getView().getCenter()); } break; } case 'resolution': { //var z = Math.round(this.getMap().getView().getZoom()/2)*2-4; var z = Math.round(this.oview_.getZoomForResolution(this.getMap().getView().getResolution())/2)*2-4; z = Math.min ( this.maxZoom, Math.max(this.minZoom, z) ); this.oview_.setZoom(z); break; } default: break; } this.calcExtent_(); }; /* Copyright (c) 2015-2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Set an hyperlink that will return the user to the current map view. * Just add a `permalink`property to layers to be handled by the control (and added in the url). * The layer's permalink property is used to name the layer in the url. * The control must be added after all layer are inserted in the map to take them into acount. * * @constructor * @extends {ol.control.Control} * @param {Object=} options * @param {boolean} options.urlReplace replace url or not, default true * @param {boolean|string} [options.localStorage=false] save current map view in localStorage, if 'position' only store map position * @param {boolean} options.geohash use geohash instead of lonlat, default false * @param {integer} options.fixed number of digit in coords, default 6 * @param {boolean} options.anchor use "#" instead of "?" in href * @param {boolean} options.visible hide the button on the map, default true * @param {boolean} options.hidden hide the button on the map, default false DEPRECATED: use visible instead * @param {function} options.onclick a function called when control is clicked */ ol.control.Permalink = function(opt_options) { var options = opt_options || {}; var self = this; var button = document.createElement('button'); this.replaceState_ = (options.urlReplace!==false); this.fixed_ = options.fixed || 6; this.hash_ = options.anchor ? "#" : "?"; this._localStorage = options.localStorage; if (!this._localStorage) { try { localStorage.removeItem('ol@permalink'); } catch(e) { console.warn('Failed to access localStorage...'); } } function linkto() { if (typeof(options.onclick) == 'function') options.onclick(self.getLink()); else self.setUrlReplace(!self.replaceState_); } button.addEventListener('click', linkto, false); button.addEventListener('touchstart', linkto, false); var element = document.createElement('div'); element.className = (options.className || "ol-permalink") + " ol-unselectable ol-control"; element.appendChild(button); if (options.hidden || options.visible===false) ol.ext.element.hide(element); ol.control.Control.call(this, { element: element, target: options.target }); this.set('geohash', options.geohash); this.set('initial', false); this.on ('change', this.viewChange_.bind(this)); // Save search params this.search_ = {}; var init = {}; var hash = document.location.hash || document.location.search || ''; // console.log('hash', hash) if (this.replaceState_ && !hash && this._localStorage) { try { hash = localStorage['ol@permalink']; } catch(e) { console.warn('Failed to access localStorage...'); } } if (hash) { hash = hash.replace(/(^#|^\?)/,"").split("&"); for (var i=0; i|undefined} an array of layer to search in * @return {ol.layer|false} */ ol.control.Permalink.prototype.getLayerByLink = function (id, layers) { if (!layers && this.getMap()) layers = this.getMap().getLayers().getArray(); for (var i=0; i canvas.height) ? 'landscape' : 'portrait'; } if (orient === 'landscape') size = [size[1],size[0]]; var sc = Math.min ((size[0]-2*margin)/canvas.width,(size[1]-2*margin)/canvas.height); w = sc * canvas.width; h = sc * canvas.height; // Image position position = [(size[0] - w)/2, (size[1] - h)/2]; } // get the canvas image var image; try { image = canvas ? canvas.toDataURL(imageType, quality) : null; } catch(e) { // Fire error event this.dispatchEvent({ type: 'error', canvas: canvas }); return; } // Fire print event var e = Object.assign({ type: 'print', print: { format: format, orientation: orient, unit: 'mm', size: size, position: position, imageWidth: w, imageHeight: h }, image: image, imageType: imageType, quality: quality, canvas: canvas }, options); this.dispatchEvent(e); }.bind(this)); this.getMap().render(); } }; /* 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). */ /** Print control to get an image of the map * @constructor * @fire show * @fire print * @fire error * @fire printing * @extends {ol.control.Control} * @param {Object=} options Control options. * @param {string} options.className class of the control * @param {String} options.title button title * @param {string} [options.lang=en] control language, default en * @param {string} options.imageType A string indicating the image format, default image/jpeg * @param {number} options.quality Number between 0 and 1 indicating the image quality to use for image formats that use lossy compression such as image/jpeg and image/webp * @param {string} options.orientation Page orientation (landscape/portrait), default guest the best one * @param {boolean} options.immediate force print even if render is not complete, default false * @param {boolean} [options.openWindow=false] open the file in a new window on print * @param {boolean} [options.copy=true] add a copy select option * @param {boolean} [options.print=true] add a print select option * @param {boolean} [options.pdf=true] add a pdf select option * @param {function} [options.saveAs] a function to save the image as blob * @param {*} [options.jsPDF] jsPDF object to save map as pdf */ ol.control.PrintDialog = function(options) { if (!options) options = {}; this._lang = options.lang; var element = ol.ext.element.create('DIV', { className: (options.className || 'ol-print') + ' ol-unselectable ol-control' }); ol.ext.element.create('BUTTON', { type: 'button', title: options.title || 'Print', click: function() { this.print(); }.bind(this), parent: element }); ol.control.Control.call(this, { element: element }); // Open in a new window if (options.openWindow) { this.on('print', function(e) { // Print success if (e.canvas) { window.open().document.write(''); } }); } // Print control options.target = ol.ext.element.create('DIV'); var printCtrl = this._printCtrl = new ol.control.Print(options); printCtrl.on(['print','error','printing'], function(e) { content.setAttribute('data-status', e.type); if (!e.clipboard) { this.dispatchEvent(e); } }.bind(this)); // North arrow this._compass = new ol.control.Compass({ src: options.northImage || 'compact', visible: false, className: 'olext-print-compass', style: new ol.style.Stroke({ color: '#333', width: 0 }) }); // Print dialog var printDialog = this._printDialog = new ol.control.Dialog({ target: document.body, closeBox: true, className: 'ol-ext-print-dialog' }); var content = printDialog.getContentElement(); this._input = {}; var param = ol.ext.element.create('DIV',{ className: 'ol-print-param', parent: content }); this._pages = [ ol.ext.element.create('DIV', { className: 'ol-page' })]; var printMap = ol.ext.element.create('DIV', { className: 'ol-map', parent: this._pages[0] }); ol.ext.element.create('DIV', { html: this._pages[0], className: 'ol-print-map', parent: content }); ol.ext.element.create('H2',{ html: this.i18n('title'), parent: param }); var ul = ol.ext.element.create('UL',{ parent: param }); // Orientation var li = ol.ext.element.create('LI', { /* html: ol.ext.element.create('LABEL', { html: this.18n('orientation') }), */ className: 'ol-orientation', parent: ul }); this._input.orientation = { list: li }; var label = ol.ext.element.create('LABEL', { className: 'portrait', parent: li }); this._input.orientation.portrait = ol.ext.element.create('INPUT', { type: 'radio', name: 'ol-print-orientation', value: 'portrait', checked: true, on: { change: function(e) { this.setOrientation(e.target.value); }.bind(this) }, parent: label }); ol.ext.element.create('SPAN', { html: this.i18n('portrait'), parent: label }); label = ol.ext.element.create('LABEL', { className: 'landscape', parent: li }); this._input.orientation.landscape = ol.ext.element.create('INPUT',{ type: 'radio', name: 'ol-print-orientation', value: 'landscape', on: { change: function(e) { this.setOrientation(e.target.value); }.bind(this) }, parent: label }); ol.ext.element.create('SPAN', { html: this.i18n('landscape'), parent: label }); // Page size var s; li = ol.ext.element.create('LI',{ html: ol.ext.element.create('LABEL', { html: this.i18n('size'), }), className: 'ol-size', parent: ul }); var size = this._input.size = ol.ext.element.create('SELECT', { on: { change: function(){ this.setSize(size.value || originalSize); }.bind(this) }, parent: li }); for (s in this.paperSize) { ol.ext.element.create('OPTION', { html: s + (this.paperSize[s] ? ' - '+this.paperSize[s][0]+'x'+this.paperSize[s][1]+' mm' : this.i18n('custom')), value: s, parent: size }); } // Margin li = ol.ext.element.create('LI',{ html: ol.ext.element.create('LABEL', { html: this.i18n('margin'), }), className: 'ol-margin', parent: ul }); var margin = this._input.margin = ol.ext.element.create('SELECT', { on: { change: function(){ this.setMargin(margin.value); }.bind(this) }, parent: li }); for (s in this.marginSize) { ol.ext.element.create('OPTION', { html: s + ' - ' + this.marginSize[s] + ' mm', value: this.marginSize[s], parent: margin }); } // Scale li = ol.ext.element.create('LI',{ html: ol.ext.element.create('LABEL', { html: this.i18n('scale'), }), className: 'ol-scale', parent: ul }); var scale = this._input.scale = ol.ext.element.create('SELECT', { on: { change: function() { this.setScale(parseInt(scale.value)) }.bind(this) }, parent: li }); Object.keys(this.scales).forEach(function(s) { ol.ext.element.create('OPTION', { html: this.scales[s], value: s, parent: scale }); }.bind(this)); // Legend li = ol.ext.element.create('LI',{ className: 'ol-legend', parent: ul }); var legend = ol.ext.element.createSwitch({ html: (this.i18n('legend')), checked: false, on: { change: function() { extraCtrl.legend.control.setCanvas(legend.checked); }.bind(this) }, parent: li }); // North li = ol.ext.element.create('LI',{ className: 'ol-print-north', parent: ul }); var north = this._input.north = ol.ext.element.createSwitch({ html: this.i18n('north'), checked: 'checked', on: { change: function() { if (north.checked) this._compass.element.classList.add('ol-print-compass'); else this._compass.element.classList.remove('ol-print-compass'); this.getMap().render(); }.bind(this)}, parent: li }); // Title li = ol.ext.element.create('LI',{ className: 'ol-print-title', parent: ul }); var title = ol.ext.element.createSwitch({ html: this.i18n('mapTitle'), checked: false, on: { change: function(e) { extraCtrl.title.control.setVisible(e.target.checked); }.bind(this) }, parent: li }); var titleText = ol.ext.element.create('INPUT', { type: 'text', placeholder: this.i18n('mapTitle'), on: { keydown: function(e) { if (e.keyCode === 13) e.preventDefault(); }, keyup: function() { extraCtrl.title.control.setTitle(titleText.value); }, change: function() { extraCtrl.title.control.setTitle(titleText.value); }.bind(this) }, parent: li }); // User div element var userElt = ol.ext.element.create('DIV', { className: 'ol-user-param', parent: param }); // Save as li = ol.ext.element.create('LI',{ className: 'ol-saveas', parent: ul }); var copied = ol.ext.element.create('DIV', { html: this.i18n('copied'), className: 'ol-clipboard-copy', parent: li }); var save = ol.ext.element.create('SELECT', { on: { change: function() { // Copy to clipboard if (this.formats[save.value].clipboard) { printCtrl.copyMap(this.formats[save.value], function(isok) { if (isok) { copied.classList.add('visible'); setTimeout(function() { copied.classList.remove('visible'); }, 1000); } }); } else { // Print to file var format = (typeof(this.getSize())==='string' ? this.getSize() : null); var opt = Object.assign({ format: format, size: format ? this.paperSize[format] : null, orient: this.getOrientation(), margin: this.getMargin(), }, this.formats[save.value]); printCtrl.print(opt); } save.value = ''; }.bind(this) }, parent: li }); ol.ext.element.create('OPTION', { html: this.i18n('saveas'), style: { display: 'none' }, value: '', parent: save }); this.formats.forEach(function(format, i) { if (format.pdf) { if (options.pdf === false) return; } else if (format.clipboard) { if (options.copy === false) return; } else if (options.save === false) { return; } ol.ext.element.create('OPTION', { html: format.title, value: i, parent: save }); }); // Save Legend li = ol.ext.element.create('LI',{ className: 'ol-savelegend', parent: ul }); var copylegend = ol.ext.element.create('DIV', { html: this.i18n('copied'), className: 'ol-clipboard-copy', parent: li }); var saveLegend = ol.ext.element.create('SELECT', { on: { change: function() { // Print canvas (with white background) var clegend = extraCtrl.legend.control.getLegend().getCanvas(); var canvas = document.createElement('CANVAS'); canvas.width = clegend.width; canvas.height = clegend.height; var ctx = canvas.getContext('2d'); ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(clegend, 0, 0); // Copy to clipboard if (this.formats[saveLegend.value].clipboard) { canvas.toBlob(function(blob) { try { navigator.clipboard.write([ new window.ClipboardItem( Object.defineProperty({}, blob.type, { value: blob, enumerable: true }) ) ]) copylegend.classList.add('visible'); setTimeout(function() { copylegend.classList.remove('visible'); }, 1000); } catch (err) { /* errror */ } }, 'image/png'); } else { var image; try { image = canvas.toDataURL(this.formats[saveLegend.value].imageType, this.formats[saveLegend.value].quality); var format = (typeof(this.getSize())==='string' ? this.getSize() : 'A4'); var w = canvas.width / 96 * 25.4; var h = canvas.height / 96 * 25.4; var size = this.paperSize[format]; if (this.getOrientation()==='landscape') size = [size[1], size[0]]; var position = [ (size[0] - w) /2, (size[1] - h) /2 ]; this.dispatchEvent({ type: 'print', print: { legend: true, format: format, orientation: this.getOrientation(), unit: 'mm', size: this.paperSize[format], position: position, imageWidth: w, imageHeight: h }, image: image, imageType: this.formats[saveLegend.value].imageType, pdf: this.formats[saveLegend.value].pdf, quality: this.formats[saveLegend.value].quality, canvas: canvas }) } catch(err) { /* error */ } } saveLegend.value = ''; }.bind(this) }, parent: li }); ol.ext.element.create('OPTION', { html: this.i18n('saveLegend'), style: { display: 'none' }, value: '', parent: saveLegend }); this.formats.forEach(function(format, i) { ol.ext.element.create('OPTION', { html: format.title, value: i, parent: saveLegend }); }); // Print var prButtons = ol.ext.element.create('DIV', { className: 'ol-ext-buttons', parent: param }); ol.ext.element.create('BUTTON', { html: this.i18n('printBt'), type: 'submit', click: function(e) { e.preventDefault(); window.print(); }, parent: prButtons }); ol.ext.element.create('BUTTON', { html: this.i18n('cancel'), type: 'button', click: function() { printDialog.hide(); }, parent: prButtons }); ol.ext.element.create('DIV', { html: this.i18n('errorMsg'), className: 'ol-error', parent: param }); // Handle dialog show/hide var originalTarget; var originalSize; var scalelistener; var extraCtrl = {}; printDialog.on('show', function() { // Dialog is showing this.dispatchEvent({ type: 'show', userElement: userElt, dialog: this._printDialog, page: this.getPage() }); // var map = this.getMap(); if (!map) return; // Print document document.body.classList.add('ol-print-document'); originalTarget = map.getTargetElement(); originalSize = map.getSize(); if (typeof(this.getSize()) === 'string') this.setSize(this.getSize()); else this.setSize(originalSize); map.setTarget(printMap); // Refresh on move end if (scalelistener) ol.Observable.unByKey(scalelistener); scalelistener = map.on('moveend', function() { this.setScale(ol.sphere.getMapScale(map)); }.bind(this)); this.setScale(ol.sphere.getMapScale(map)); // Get extra controls extraCtrl = {}; this.getMap().getControls().forEach(function(c) { if (c instanceof ol.control.Legend) { extraCtrl.legend = { control: c }; } if (c instanceof ol.control.CanvasTitle) { extraCtrl.title = { control: c }; } if (c instanceof ol.control.Compass) { if (extraCtrl.compass) { c.element.classList.remove('ol-print-compass') } else { if (this._input.north.checked) c.element.classList.add('ol-print-compass') else c.element.classList.remove('ol-print-compass') this._compass = c; extraCtrl.compass = { control: c }; } } }.bind(this)); // Show hide title if (extraCtrl.title) { title.checked = extraCtrl.title.isVisible = extraCtrl.title.control.getVisible(); titleText.value = extraCtrl.title.control.getTitle(); title.parentNode.parentNode.classList.remove('hidden'); } else { title.parentNode.parentNode.classList.add('hidden'); } // Show hide legend if (extraCtrl.legend) { extraCtrl.legend.ison = extraCtrl.legend.control.onCanvas(); extraCtrl.legend.collapsed = extraCtrl.legend.control.isCollapsed(); extraCtrl.legend.control.collapse(false); saveLegend.parentNode.classList.remove('hidden'); legend.parentNode.parentNode.classList.remove('hidden'); legend.checked = !extraCtrl.legend.collapsed; extraCtrl.legend.control.setCanvas(!extraCtrl.legend.collapsed); } else { saveLegend.parentNode.classList.add('hidden'); legend.parentNode.parentNode.classList.add('hidden'); } }.bind(this)); printDialog.on('hide', function() { // No print document.body.classList.remove('ol-print-document'); if (!originalTarget) return; this.getMap().setTarget(originalTarget); originalTarget = null; if (scalelistener) ol.Observable.unByKey(scalelistener); // restore if (extraCtrl.title) { extraCtrl.title.control.setVisible(extraCtrl.title.isVisible); } if (extraCtrl.legend) { extraCtrl.legend.control.setCanvas(extraCtrl.legend.ison); extraCtrl.legend.control.collapse(extraCtrl.legend.collapsed); } this.dispatchEvent({ type: 'hide' }); }.bind(this)); // Update preview on resize window.addEventListener('resize', function() { this.setSize(); }.bind(this)); // Save or print if (options.saveAs) { this.on('print', function(e) { if (!e.pdf) { // Save image as file e.canvas.toBlob(function(blob) { var name = (e.print.legend ? 'legend.' : 'map.')+e.imageType.replace('image/',''); options.saveAs(blob, name); }, e.imageType, e.quality); } }); } // Save or print if (options.jsPDF) { this.on('print', function(e) { if (e.pdf) { // Export pdf using the print info var pdf = new options.jsPDF({ orientation: e.print.orientation, unit: e.print.unit, format: e.print.size }); pdf.addImage(e.image, 'JPEG', e.print.position[0], e.print.position[0], e.print.imageWidth, e.print.imageHeight); pdf.save(e.print.legend ? 'legend.pdf' : 'map.pdf'); } }); } }; ol.ext.inherits(ol.control.PrintDialog, ol.control.Control); /** Check if the dialog is oprn * @return {boolean} */ ol.control.PrintDialog.prototype.isOpen = function() { return this._printDialog.isOpen(); }; /** Add a new language * @param {string} lang lang id * @param {Objetct} labels */ ol.control.PrintDialog.addLang = function(lang, labels) { ol.control.PrintDialog.prototype._labels[lang] = labels; }; /** Translate * @param {string} what * @returns {string} */ ol.control.PrintDialog.prototype.i18n = function(what) { var rep = this._labels.en[what] || 'bad param'; if (this._labels[this._lang] && this._labels[this._lang][what]) { rep = this._labels[this._lang][what]; } return rep; }; /** Print dialog labels (for customisation) */ ol.control.PrintDialog.prototype._labels = { en: { title: 'Print', orientation: 'Orientation', portrait: 'Portrait', landscape: 'Landscape', size: 'Page size', custom: 'screen size', margin: 'Margin', scale: 'Scale', legend: 'Legend', north: 'North arrow', mapTitle: 'Map title', saveas: 'Save as...', saveLegend: 'Save legend...', copied: '✔ Copied to clipboard', errorMsg: 'Can\'t save map canvas...', printBt: 'Print...', cancel: 'cancel' }, fr: { title: 'Imprimer', orientation: 'Orientation', portrait: 'Portrait', landscape: 'Paysage', size: 'Taille du papier', custom: 'taille écran', margin: 'Marges', scale: 'Echelle', legend: 'Légende', north: 'Flèche du nord', mapTitle: 'Titre de la carte', saveas: 'Enregistrer sous...', saveLegend: 'Enregistrer la légende...', copied: '✔ Carte copiée', errorMsg: 'Impossible d\'enregistrer la carte', printBt: 'Imprimer', cancel: 'annuler' }, zh:{ title: '打印', orientation: '方向', portrait: '纵向', landscape: '横向', size: '页面大小', custom: '屏幕大小', margin: '外边距', scale: '尺度', legend: '图例', north: '指北针', mapTitle: '地图名字', saveas: '保存为...', saveLegend: '保存图例为...', copied: '✔ 已复制到剪贴板', errorMsg: '无法保存地图...', printBt: '打印...', cancel: '取消' } }; /** List of paper size */ ol.control.PrintDialog.prototype.paperSize = { '': null, 'A0': [841,1189], 'A1': [594,841], 'A2': [420,594], 'A3': [297,420], 'A4': [210,297], 'A5': [148,210], 'B4': [257,364], 'B5': [182,257] }; /** List of margin size */ ol.control.PrintDialog.prototype.marginSize = { none: 0, small: 5, large: 10 }; /** List of legeng options * / ol.control.PrintDialog.prototype.legendOptions = { off: 'Hide legend', on: 'Show legend' }; /** List of print image file formats */ ol.control.PrintDialog.prototype.formats = [{ title: 'copy to clipboard', imageType: 'image/png', clipboard: true }, { title: 'save as jpeg', imageType: 'image/jpeg', quality: .8 }, { title: 'save as png', imageType: 'image/png', quality: .8 }, { title: 'save as pdf', imageType: 'image/jpeg', pdf: true } ]; /** List of print scale */ ol.control.PrintDialog.prototype.scales = { ' 5000': '1/5.000', ' 10000': '1/10.000', ' 25000': '1/25.000', ' 50000': '1/50.000', ' 100000': '1/100.000', ' 250000': '1/250.000', ' 1000000': '1/1.000.000' }; /** Get print orientation * @returns {string} */ ol.control.PrintDialog.prototype.getOrientation = function () { return this._orientation || 'portrait'; }; /** Set print orientation * @param {string} ori landscape or portrait */ ol.control.PrintDialog.prototype.setOrientation = function (ori) { this._orientation = (ori==='landscape' ? 'landscape' : 'portrait'); this._input.orientation[this._orientation].checked = true; this.setSize(); }; /** Get print margin * @returns {number} */ ol.control.PrintDialog.prototype.getMargin = function () { return this._margin || 0; }; /** Set print margin * @param {number} */ ol.control.PrintDialog.prototype.setMargin = function (margin) { this._margin = margin; this._input.margin.value = margin; this.setSize(); }; /** Get print size * @returns {ol.size} */ ol.control.PrintDialog.prototype.getSize = function () { return this._size; }; /** Set map print size * @param {ol/size|string} size map size as ol/size or A4, etc. */ ol.control.PrintDialog.prototype.setSize = function (size) { // reset status this._printDialog.getContentElement().setAttribute('data-status',''); if (size) this._size = size; else size = this._size; if (!size) return; if (typeof(size) === 'string') { size = size.toLocaleUpperCase(); if (!this.paperSize[size]) size = this._size = 'A4'; this._input.size.value = size; size = [ Math.trunc(this.paperSize[size][0]* 96/25.4), Math.trunc(this.paperSize[size][1]* 96/25.4) ] if (this.getOrientation() === 'landscape') { size = [size[1], size[0]]; } this.getPage().classList.remove('margin'); } else { this._input.size.value = ''; this.getPage().classList.add('margin'); } var printElement = this.getPage(); var s = printElement.parentNode.getBoundingClientRect(); var scx = (s.width - 40) / size[0]; var scy = (s.height - 40) / size[1]; var sc = Math.min(scx, scy, 1); printElement.style.width = size[0]+'px'; printElement.style.height = size[1]+'px'; printElement.style['-webkit-transform'] = printElement.style.transform = 'translate(-50%,-50%) scale('+sc+')'; var px = Math.round(5/sc); printElement.style['-webkit-box-shadow'] = printElement.style['box-shadow'] = px+'px '+px+'px '+px+'px rgba(0,0,0,.6)'; printElement.style['padding'] = (this.getMargin() * 96/25.4)+'px'; if (this.getMap()) { this.getMap().updateSize(); } this.dispatchEvent({ type: 'dialog:refresh' }); }; /** Get dialog content element * @return {Element} */ ol.control.PrintDialog.prototype.getContentElement = function () { return this._printDialog.getContentElement(); }; /** Get dialog user element * @return {Element} */ ol.control.PrintDialog.prototype.getUserElement = function () { return this._printDialog.getContentElement().querySelector('.ol-user-param'); }; /** Get page element * @return {Element} */ ol.control.PrintDialog.prototype.getPage = function () { return this._pages[0] }; /** * 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 {ol.Map} map Map. * @api stable */ ol.control.PrintDialog.prototype.setMap = function (map) { if (this.getMap()) { this.getMap().removeControl(this._compass); this.getMap().removeControl(this._printCtrl); this.getMap().removeControl(this._printDialog); } ol.control.Control.prototype.setMap.call(this, map); if (this.getMap()) { this.getMap().addControl(this._compass); this.getMap().addControl(this._printCtrl); this.getMap().addControl(this._printDialog); } }; /** Set the current scale (will change the scale of the map) * @param {number|string} value the scale factor or a scale string as 1/xxx */ ol.control.PrintDialog.prototype.setScale = function (value) { ol.sphere.setMapScale(this.getMap(), value); this._input.scale.value = ' '+(Math.round(value/100) * 100); }; /** Get the current map scale factor * @return {number} */ ol.control.PrintDialog.prototype.getScale = function () { return ol.sphere.getMapScale(this.getMap()); }; /** Show print dialog * @param {*} * @param {ol/size|string} options.size map size as ol/size or A4, etc. * @param {number|string} options.value the scale factor or a scale string as 1/xxx * @param {string} options.orientation landscape or portrait * @param {number} options.margin */ ol.control.PrintDialog.prototype.print = function(options) { options = options || {}; if (options.size) this.setSize(options.size); if (options.scale) this.setScale(options.scale); if (options.orientation) this.setOrientation(options.orientation); if (options.margin) this.setMargin(options.margin); this._printDialog.show(); }; /** Get print control * @returns {ol.control.Print} */ ol.control.PrintDialog.prototype.getrintControl = function() { return this._printCtrl; } /* 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). */ /*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ /** * @classdesc OpenLayers 3 Profil Control. * Draw a profile of a feature (with a 3D geometry) * * @constructor * @extends {ol.control.Control} * @fires over * @fires out * @fires show * @fires dragstart * @fires dragging * @fires dragend * @fires dragcancel * @param {Object=} options * @param {string} options.className * @param {String} options.title button title * @param {ol.style.Style} [options.style] style to draw the profil, default darkblue * @param {ol.style.Style} [options.selectStyle] style for selection, default darkblue fill * @param {*} options.info keys/values for i19n * @param {number} options.width * @param {number} options.height * @param {ol.Feature} options.feature the feature to draw profil * @param {boolean} options.selectable enable selection on the profil, default false * @param {boolean} options.zoomable can zoom in the profil */ ol.control.Profil = function(options) { options = options || {}; this.info = options.info || ol.control.Profil.prototype.info; var self = this; var element; if (options.target) { element = document.createElement("div"); element.classList.add(options.className || "ol-profil"); } else { element = document.createElement("div"); element.className = ((options.className || 'ol-profil') +' ol-unselectable ol-control ol-collapsed').trim(); this.button = document.createElement("button"); this.button.title = options.title || 'Profile', this.button.setAttribute('type','button'); var click_touchstart_function = function(e) { self.toggle(); e.preventDefault(); }; this.button.addEventListener("click", click_touchstart_function); this.button.addEventListener("touchstart", click_touchstart_function); element.appendChild(this.button); } // Drawing style if (options.style instanceof ol.style.Style) { this._style = options.style; } else { this._style = new ol.style.Style({ text: new ol.style.Text(), stroke: new ol.style.Stroke({ width: 1.5, color: '#369' }) }); } if (!this._style.getText()) this._style.setText(new ol.style.Text()); // Selection style if (options.selectStyle instanceof ol.style.Style) { this._selectStyle = options.selectStyle; } else { this._selectStyle = new ol.style.Style({ fill: new ol.style.Fill({ color: '#369' }) }); } var div_inner = document.createElement("div"); div_inner.classList.add("ol-inner"); element.appendChild(div_inner); var div = document.createElement("div"); div.style.position = "relative"; div_inner.appendChild(div); var ratio = this.ratio = 2; this.canvas_ = document.createElement('canvas'); this.canvas_.width = (options.width || 300)*ratio; this.canvas_.height = (options.height || 150)*ratio; var styles = { "msTransform":"scale(0.5,0.5)", "msTransformOrigin":"0 0", "webkitTransform":"scale(0.5,0.5)", "webkitTransformOrigin":"0 0", "mozTransform":"scale(0.5,0.5)", "mozTransformOrigin":"0 0", "transform":"scale(0.5,0.5)", "transformOrigin":"0 0" }; Object.keys(styles).forEach(function(style) { if (style in self.canvas_.style) { self.canvas_.style[style] = styles[style]; } }); var div_to_canvas = document.createElement("div"); div.appendChild(div_to_canvas); div_to_canvas.style.width = this.canvas_.width/ratio + "px"; div_to_canvas.style.height = this.canvas_.height/ratio + "px"; div_to_canvas.appendChild(this.canvas_); div_to_canvas.addEventListener('pointerdown', this.onMove.bind(this)); document.addEventListener('pointerup', this.onMove.bind(this)); div_to_canvas.addEventListener('mousemove', this.onMove.bind(this)); div_to_canvas.addEventListener('touchmove', this.onMove.bind(this)); ol.control.Control.call(this, { element: element, target: options.target }); this.set('selectable', options.selectable); // Offset in px this.margin_ = { top:10*ratio, left:40*ratio, bottom:30*ratio, right:10*ratio }; if (!this.info.ytitle) this.margin_.left -= 20*ratio; if (!this.info.xtitle) this.margin_.bottom -= 20*ratio; // Cursor this.bar_ = document.createElement("div"); this.bar_.classList.add("ol-profilbar"); this.bar_.style.top = (this.margin_.top/ratio)+"px"; this.bar_.style.height = (this.canvas_.height-this.margin_.top-this.margin_.bottom)/ratio+"px"; div.appendChild(this.bar_); this.cursor_ = document.createElement("div"); this.cursor_.classList.add("ol-profilcursor"); div.appendChild(this.cursor_); this.popup_ = document.createElement("div"); this.popup_.classList.add("ol-profilpopup"); this.cursor_.appendChild(this.popup_); // Track information var t = document.createElement("table"); t.cellPadding = '0'; t.cellSpacing = '0'; t.style.clientWidth = this.canvas_.width/ratio + "px"; div.appendChild(t); var firstTr = document.createElement("tr"); firstTr.classList.add("track-info"); t.appendChild(firstTr); var div_zmin = document.createElement("td"); div_zmin.innerHTML = (this.info.zmin||"Zmin")+': '; firstTr.appendChild(div_zmin); var div_zmax = document.createElement("td"); div_zmax.innerHTML = (this.info.zmax||"Zmax")+': '; firstTr.appendChild(div_zmax); var div_distance = document.createElement("td"); div_distance.innerHTML = (this.info.distance||"Distance")+': '; firstTr.appendChild(div_distance); var div_time = document.createElement("td"); div_time.innerHTML = (this.info.time||"Time")+': '; firstTr.appendChild(div_time); var secondTr = document.createElement("tr"); secondTr.classList.add("point-info") t.appendChild(secondTr); var div_altitude = document.createElement("td"); div_altitude.innerHTML = (this.info.altitude||"Altitude")+': '; secondTr.appendChild(div_altitude); var div_distance2 = document.createElement("td"); div_distance2.innerHTML = (this.info.distance||"Distance")+': '; secondTr.appendChild(div_distance2); var div_time2 = document.createElement("td"); div_time2.innerHTML = (this.info.time||"Time")+': '; secondTr.appendChild(div_time2); // Array of data this.tab_ = []; // Show feature if (options.feature) { this.setGeometry (options.feature); } // Zoom on profile if (options.zoomable) { this.set('selectable', true); var start, geom; this.on('change:geometry', function() { geom = null; }); this.on('dragstart', function(e) { start = e.index; }) this.on('dragend', function(e) { if (Math.abs(start - e.index) > 10) { if (!geom) { var bt = ol.ext.element.create('BUTTON', { parent: element, className: 'ol-zoom-out', click: function(e) { e.stopPropagation(); e.preventDefault(); if (geom) { this.dispatchEvent({ type:'zoom' }); this.setGeometry(geom, this._geometry[1]); } element.removeChild(bt); }.bind(this) }) } var saved = geom || this._geometry[0]; var g = new ol.geom.LineString(this.getSelection(start, e.index)); this.setGeometry(g, this._geometry[1]); geom = saved; this.dispatchEvent({ type:'zoom', geometry: g, start: start, end: e.index }); } }.bind(this)); } }; ol.ext.inherits(ol.control.Profil, ol.control.Control); /** Custom infos list * @api stable */ ol.control.Profil.prototype.info = { "zmin": "Zmin", "zmax": "Zmax", "ytitle": "Altitude (m)", "xtitle": "Distance (km)", "time": "Time", "altitude": "Altitude", "distance": "Distance", "altitudeUnits": "m", "distanceUnitsM": "m", "distanceUnitsKM": "km", }; /** Show popup info * @param {string} info to display as a popup * @api stable */ ol.control.Profil.prototype.popup = function(info) { this.popup_.innerHTML = info; }; /** Show point on profil * @param {*} p * @param {number} dx * @private */ ol.control.Profil.prototype._drawAt = function(p, dx) { if (p) { this.cursor_.style.left = dx+"px"; this.cursor_.style.top = (this.canvas_.height-this.margin_.bottom+p[1]*this.scale_[1]+this.dy_)/this.ratio+"px"; this.cursor_.style.display = "block"; this.bar_.parentElement.classList.add("over"); this.bar_.style.left = dx+"px"; this.bar_.style.display = "block"; this.element.querySelector(".point-info .z").textContent = p[1]+this.info.altitudeUnits; this.element.querySelector(".point-info .dist").textContent = (p[0]/1000).toFixed(1)+this.info.distanceUnitsKM; this.element.querySelector(".point-info .time").textContent = p[2]; if (dx>this.canvas_.width/this.ratio/2) this.popup_.classList.add('ol-left'); else this.popup_.classList.remove('ol-left'); } else { this.cursor_.style.display = "none"; this.bar_.style.display = 'none'; this.cursor_.style.display = 'none'; this.bar_.parentElement.classList.remove("over"); } }; /** Show point at coordinate or a distance on the profil * @param { ol.coordinates|number } where a coordinate or a distance from begining, if none it will hide the point * @return { ol.coordinates } current point */ ol.control.Profil.prototype.showAt = function(where) { var i, p, p0, d0 = Infinity; if (typeof(where) === 'undefined') { if (this.bar_.parentElement.classList.contains("over")) { // Remove it this._drawAt(); } } else if (where.length) { // Look for closest the point for (i=1; p=this.tab_[i]; i++) { var d = ol.coordinate.dist2d(p[3], where); if (d= where) { break; } } } if (p0) { var dx = (p0[0] * this.scale_[0] + this.margin_.left) / this.ratio; this._drawAt(p0, dx); return p0[3]; } return null; }; /** Show point at a time on the profil * @param { Date|number } time a Date or a DateTime (in s) to show the profile on, if none it will hide the point * @param { booelan } delta true if time is a delta from the start, default false * @return { ol.coordinates } current point */ ol.control.Profil.prototype.showAtTime = function(time, delta) { var i, p, p0; if (time instanceof Date) { time = time.getTime()/1000; } else if (delta) { time += this.tab_[0][3][3]; } if (typeof(time) === 'undefined') { if (this.bar_.parentElement.classList.contains("over")) { // Remove it this._drawAt(); } } else { for (i=0; p=this.tab_[i]; i++) { p0 = p; if (p[3][3] >= time) { break; } } } if (p0) { var dx = (p0[0] * this.scale_[0] + this.margin_.left) / this.ratio; this._drawAt(p0, dx); return p0[3]; } return null; }; /** Get the point at a given time on the profil * @param { number } time time at which to show the point * @return { ol.coordinates } current point */ ol.control.Profil.prototype.pointAtTime = function(time) { var i, p; // Look for closest the point for (i=1; p=this.tab_[i]; i++) { var t = p[3][3]; if (t >= time) { // Previous one ? var pt = this.tab_[i-1][3]; if ((pt[3]+t)/2 < time) return pt; else return p; } } return this.tab_[this.tab_.length-1][3]; }; /** Mouse move over canvas */ ol.control.Profil.prototype.onMove = function(e) { if (!this.tab_.length) return; var box_canvas = this.canvas_.getBoundingClientRect(); var pos = { top: box_canvas.top + window.pageYOffset - document.documentElement.clientTop, left: box_canvas.left + window.pageXOffset - document.documentElement.clientLeft }; var pageX = e.pageX || (e.touches && e.touches.length && e.touches[0].pageX) || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); var pageY = e.pageY || (e.touches && e.touches.length && e.touches[0].pageY) || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY); var dx = pageX -pos.left; var dy = pageY -pos.top; var ratio = this.ratio; if (dx>this.margin_.left/ratio && dx<(this.canvas_.width-this.margin_.right)/ratio && dy>this.margin_.top/ratio && dy<(this.canvas_.height-this.margin_.bottom)/ratio) { var d = (dx*ratio-this.margin_.left)/this.scale_[0]; var p0 = this.tab_[0]; var index, p; for (index=1; p=this.tab_[index]; index++) { if (p[0]>=d) { if (d < (p[0]+p0[0])/2) { index = 0; p = p0; } break; } } this._drawAt(p, dx); this.dispatchEvent({ type:'over', click:e.type==='click', index: index, coord: p[3], time: p[2], distance: p[0] }); // Handle drag / click switch (e.type) { case 'pointerdown': { this._dragging = { event: { type:'dragstart', index: index, coord: p[3], time: p[2], distance: p[0] }, pageX: pageX, pageY: pageY } break; } case 'pointerup': { if (this._dragging && this._dragging.pageX) { if (Math.abs(this._dragging.pageX - pageX)<3 && Math.abs(this._dragging.pageY - pageY) < 3) { this.dispatchEvent({ type:'click', index: index, coord: p[3], time: p[2], distance: p[0] }); this.refresh(); } } else { this.dispatchEvent({ type:'dragend', index: index, coord: p[3], time: p[2], distance: p[0] }); } this._dragging = false; break; } default: { if (this._dragging) { if (this._dragging.pageX) { if (Math.abs(this._dragging.pageX - pageX)>3 || Math.abs(this._dragging.pageY - pageY) > 3) { this._dragging.pageX = this._dragging.pageY = false; this.dispatchEvent(this._dragging.event); } } else { this.dispatchEvent({ type:'dragging', index: index, coord: p[3], time: p[2], distance: p[0] }); var min = Math.min(this._dragging.event.index, index); var max = Math.max(this._dragging.event.index, index); this.refresh(); if (this.get('selectable')) this._drawGraph(this.tab_.slice(min, max), this._selectStyle); } } break; } } } else { if (this.bar_.parentElement.classList.contains('over')) { this._drawAt(); this.dispatchEvent({ type:'out' }); } if (e.type === 'pointerup' && this._dragging) { this.dispatchEvent({ type:'dragcancel' }); this._dragging = false; } } }; /** Show panel * @api stable */ ol.control.Profil.prototype.show = function() { this.element.classList.remove("ol-collapsed"); this.dispatchEvent({ type:'show', show: true }); }; /** Hide panel * @api stable */ ol.control.Profil.prototype.hide = function() { this.element.classList.add("ol-collapsed"); this.dispatchEvent({ type:'show', show: false }); }; /** Toggle panel * @api stable */ ol.control.Profil.prototype.toggle = function() { this.element.classList.toggle("ol-collapsed"); var b = this.element.classList.contains("ol-collapsed"); this.dispatchEvent({ type:'show', show: !b }); } /** Is panel visible */ ol.control.Profil.prototype.isShown = function() { return (!this.element.classList.contains("ol-collapsed")); }; /** Get selection * @param {number} starting point * @param {number} ending point * @return {Array} */ ol.control.Profil.prototype.getSelection = function(start, end) { var sel = []; var min = Math.max(Math.min(start, end), 0); var max = Math.min(Math.max(start, end), this.tab_.length-1); for (var i=min; i <= max; i++) { sel.push(this.tab_[i][3]) } return sel; }; /** Draw the graph * @private */ ol.control.Profil.prototype._drawGraph = function(t, style) { if (!t.length) return; var ctx = this.canvas_.getContext('2d'); var scx = this.scale_[0]; var scy = this.scale_[1]; var dy = this.dy_; var ratio = this.ratio; var i, p; // Draw Path ctx.beginPath(); for (i=0; p=t[i]; i++) { if (i==0) ctx.moveTo(p[0]*scx,p[1]*scy+dy); else ctx.lineTo(p[0]*scx,p[1]*scy+dy); } if (style.getStroke()) { ctx.strokeStyle = style.getStroke().getColor() || '#000'; ctx.lineWidth = style.getStroke().getWidth() * ratio; ctx.setLineDash([]); ctx.stroke(); } // Fill path if (style.getFill()) { ctx.fillStyle = style.getFill().getColor() || '#000'; ctx.Style = style.getFill().getColor() || '#000'; ctx.lineTo(t[t.length-1][0]*scx, 0); ctx.lineTo(t[0][0]*scx, 0); ctx.fill(); } }; /** * Set the geometry to draw the profil. * @param {ol.Feature|ol.geom.Geometry} f the feature. * @param {Object=} options * @param {ol.ProjectionLike} options.projection feature projection, default projection of the map * @param {string} options.zunit 'm' or 'km', default m * @param {string} options.unit 'm' or 'km', default km * @param {Number|undefined} options.zmin default 0 * @param {Number|undefined} options.zmax default max Z of the feature * @param {integer|undefined} options.zDigits number of digits for z graduation, default 0 * @param {Number|undefined} options.graduation z graduation default 100 * @param {integer|undefined} options.amplitude amplitude of the altitude, default zmax-zmin * @api stable */ ol.control.Profil.prototype.setGeometry = function(g, options) { if (!options) options = {}; if (g instanceof ol.Feature) g = g.getGeometry(); this._geometry = [g, options]; // No Z if (!/Z/.test(g.getLayout())) return; // No time if(/M/.test(g.getLayout())) this.element.querySelector(".time").parentElement.style.display = 'block'; else this.element.querySelector(".time").parentElement.style.display = 'none'; // Coords var c = g.getCoordinates(); switch (g.getType()) { case "LineString": break; case "MultiLineString": c = c[0]; break; default: return; } // Distance beetween 2 coords var proj = options.projection || this.getMap().getView().getProjection(); function dist2d(p1,p2) { return ol.sphere.getDistance( ol.proj.transform(p1, proj, 'EPSG:4326'), ol.proj.transform(p2, proj, 'EPSG:4326') ); } function getTime(t0, t1) { if (!t0 || !t1) return "-" var dt = (t1-t0) / 60; // mn var ti = Math.trunc(dt/60); var mn = Math.trunc(dt-ti*60); return ti+"h"+(mn<10?"0":"")+mn+"mn"; } // Calculate [distance, altitude, time, point] for each points var zmin=Infinity, zmax=-Infinity; var i, p, d, z, ti, t = this.tab_ = []; for (i=0, p; p=c[i]; i++) { z = p[2]; if (zzmax) zmax=z; if (i==0) d = 0; else d += dist2d(c[i-1], p); ti = getTime(c[0][3],p[3]); t.push ([d, z, ti, p]); } this._z = [zmin,zmax]; this.set('graduation', options.graduation || 100); this.set('zmin', options.zmin); this.set('zmax', options.zmax); this.set('amplitude', options.amplitude); this.set('unit', options.unit); this.set('zunit', options.zunit); this.set('zDigits', options.zDigits); this.dispatchEvent({ type: 'change:geometry', geometry: g }) this.refresh(); }; /** Refresh the profil */ ol.control.Profil.prototype.refresh = function() { var canvas = this.canvas_; var ctx = canvas.getContext('2d'); var w = canvas.width; var h = canvas.height; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0,0, w, h); var zmin = this._z[0]; var zmax = this._z[1]; var t = this.tab_; var d = t[t.length-1][0]; var ti = t[t.length-1][2]; var i; if (!d) { console.error('[ol/control/Profil] no data...', t); return; } // Margin ctx.setTransform(1, 0, 0, 1, this.margin_.left, h-this.margin_.bottom); var ratio = this.ratio; w -= this.margin_.right + this.margin_.left; h -= this.margin_.top + this.margin_.bottom; // Draw axes ctx.strokeStyle = this._style.getText().getFill().getColor() || '#000'; ctx.lineWidth = 0.5*ratio; ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(0,-h); ctx.moveTo(0,0); ctx.lineTo(w, 0); ctx.stroke(); // Info this.element.querySelector(".track-info .zmin").textContent = zmin.toFixed(2)+this.info.altitudeUnits; this.element.querySelector(".track-info .zmax").textContent = zmax.toFixed(2)+this.info.altitudeUnits; if (d>1000) { this.element.querySelector(".track-info .dist").textContent = (d/1000).toFixed(1)+this.info.distanceUnitsKM; } else { this.element.querySelector(".track-info .dist").textContent= (d).toFixed(1)+this.info.distanceUnitsM; } this.element.querySelector(".track-info .time").textContent = ti; // Set graduation var grad = this.get('graduation'); while (true) { zmax = Math.ceil(zmax/grad)*grad; zmin = Math.floor(zmin/grad)*grad; var nbgrad = (zmax-zmin)/grad; if (h/nbgrad < 15*ratio) { grad *= 2; } else break; } // Set amplitude if (typeof(this.get('zmin'))=='number' && zmin > this.get('zmin')) zmin = this.get('zmin'); if (typeof(this.get('zmax'))=='number' && zmax < this.get('zmax')) zmax = this.get('zmax'); var amplitude = this.get('amplitude'); if (amplitude) { zmax = Math.max (zmin + amplitude, zmax); } // Scales lines var scx = w/d; var scy = -h/(zmax-zmin); var dy = this.dy_ = -zmin*scy; this.scale_ = [scx,scy]; this._drawGraph(t, this._style); // Draw ctx.font = (10*ratio)+'px arial'; ctx.textAlign = 'right'; ctx.textBaseline = 'middle'; ctx.fillStyle = this._style.getText().getFill().getColor() || '#000'; // Scale Z ctx.beginPath(); var fix = this.get('zDigits') || 0; for (i=zmin; i<=zmax; i+=grad) { if (this.get('zunit') != 'km') ctx.fillText(i.toFixed(fix), -4*ratio, i*scy+dy); else ctx.fillText((i/1000).toFixed(1), -4*ratio, i*scy+dy); ctx.moveTo (-2*ratio, i*scy+dy); if (i!=0) ctx.lineTo (d*scx, i*scy+dy); else ctx.lineTo (0, i*scy+dy); } // Scale X ctx.textAlign = "center"; ctx.textBaseline = "top"; ctx.setLineDash([ratio,3*ratio]); var unit = this.get('unit') ||"km"; var step; if (d>1000) { step = Math.round(d/1000)*100; if (step > 1000) step = Math.ceil(step/1000)*1000; } else { unit = "m"; if (d>100) step = Math.round(d/100)*10; else if (d>10) step = Math.round(d/10); else if (d>1) step = Math.round(d)/10; else step = d; } for (i=0; i<=d; i+=step) { var txt = (unit=="m") ? i : (i/1000); //if (i+step>d) txt += " "+ (options.zunits || "km"); ctx.fillText(Math.round(txt*10)/10, i*scx, 4*ratio); ctx.moveTo (i*scx, 2*ratio); ctx.lineTo (i*scx, 0); } ctx.font = (12*ratio)+"px arial"; ctx.fillText(this.info.xtitle.replace("(km)","("+unit+")"), w/2, 18*ratio); ctx.save(); ctx.rotate(-Math.PI/2); ctx.fillText(this.info.ytitle, h/2, -this.margin_.left); ctx.restore(); ctx.stroke(); }; /** Get profil image * @param {string|undefined} type image format or 'canvas' to get the canvas image, default image/png. * @param {Number|undefined} encoderOptions between 0 and 1 indicating image quality image/jpeg or image/webp, default 0.92. * @return {string} requested data uri * @api stable */ ol.control.Profil.prototype.getImage = function(type, encoderOptions) { if (type==="canvas") return this.canvas_; return this.canvas_.toDataURL(type, encoderOptions); }; /* 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.label] waiting label * @param {ol.layer.Layer} [options.layers] a tile layer with tileload events */ ol.control.ProgressBar = function(options) { options = options || {}; var element = ol.ext.element.create('DIV', { className: ((options.className || '') + ' ol-progress-bar ol-unselectable ol-control').trim() }); this._waiting = ol.ext.element.create('DIV', { html: options.label || '', className: 'ol-waiting', parent: element }); this._bar = ol.ext.element.create('DIV', { className: 'ol-bar', parent: element }); ol.control.Control.call(this, { element: element, target: options.target }); this._layerlistener = []; this.setLayers(options.layers); }; ol.ext.inherits(ol.control.ProgressBar, ol.control.Control); /** Set the control visibility * @param {Number} [n] progress percentage, a number beetween 0,1, default hide progress bar */ ol.control.ProgressBar.prototype.setPercent = function (n) { this._bar.style.width = ((Number(n) || 0) * 100)+'%'; if (n===undefined) { ol.ext.element.hide(this.element); } else { ol.ext.element.show(this.element); } }; /** Set waiting text * @param {string} label */ ol.control.ProgressBar.prototype.setLabel = function (label) { this._waiting.innerHTML = label; }; /** Use a list of tile layer to shown tile load * @param {ol.layer.Layer|Array} layers a layer or a list of layer */ ol.control.ProgressBar.prototype.setLayers = function (layers) { // reset this._layerlistener.forEach(function (l) { ol.Observable.unByKey(l); }); this._layerlistener = []; this.setPercent(); var loading=0, loaded=0; if (layers instanceof ol.layer.Layer) layers = [layers]; if (!layers || !layers.forEach) return; var tout; // Listeners layers.forEach(function(layer) { if (layer instanceof ol.layer.Layer) { this._layerlistener.push(layer.getSource().on('tileloadstart', function () { loading++; this.setPercent(loaded/loading); clearTimeout(tout); }.bind(this))); this._layerlistener.push(layer.getSource().on(['tileloadend', 'tileloaderror'], function () { loaded++; if (loaded === loading) { loading = loaded = 0; this.setPercent(1); tout = setTimeout(this.setPercent.bind(this), 300); } else { this.setPercent(loaded/loading); } }.bind(this))); } }.bind(this)); } /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Geoportail routing Control. * @constructor * @extends {ol.control.Control} * @fires select * @fires change:input * @param {Object=} options * @param {string} options.className control class name * @param {string | undefined} [options.apiKey] the service api key. * @param {string | undefined} options.authentication: basic authentication for the service API as btoa("login:pwd") * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {string | undefined} options.inputLabel label for the input, default none * @param {string | undefined} options.noCollapse prevent collapsing on input blur, default false * @param {number | undefined} options.typing a delay on each typing to start searching (ms) use -1 to prevent autocompletion, default 300. * @param {integer | undefined} options.minLength minimum length to start searching, default 1 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * @param {integer | undefined} options.maxHistory maximum number of items to display in history. Set -1 if you don't want history, default maxItems * @param {function} options.getTitle a function that takes a feature and return the name to display in the index. * @param {function} options.autocomplete a function that take a search string and callback function to send an array * @param {number} options.timeout default 10s */ ol.control.RoutingGeoportail = function(options) { var self = this; if (!options) options = {}; if (options.typing == undefined) options.typing = 300; options.apiKey = options.apiKey || 'essentiels'; // Class name for history this._classname = options.className || 'search'; this._source = new ol.source.Vector(); // Authentication this._auth = options.authentication; var element = document.createElement("DIV"); var classNames = (options.className||"")+ " ol-routing"; if (!options.target) { classNames += " ol-unselectable ol-control"; } element.setAttribute('class', classNames); if (!options.target) { var bt = ol.ext.element.create('BUTTON', { parent: element }) bt.addEventListener('click', function(){ element.classList.toggle('ol-collapsed'); }); } ol.control.Control.call(this, { element: element, target: options.target }); this.set('url', 'https://wxs.ign.fr/'+options.apiKey+'/itineraire/rest/route.json'); var content = ol.ext.element.create('DIV', { className: 'content', parent: element } ) var listElt = ol.ext.element.create('DIV', { className: 'search-input', parent: content }); this._search = []; this.addSearch(listElt, options); this.addSearch(listElt, options); ol.ext.element.create('I', { className: 'ol-car', title: options.carlabel||'by car', parent: content }) .addEventListener("click", function() { self.setMode('car'); }); ol.ext.element.create('I', { className: 'ol-pedestrian', title: options.pedlabel||'pedestrian', parent: content }) .addEventListener("click", function() { self.setMode('pedestrian'); }); ol.ext.element.create('I', { className: 'ol-ok', title: options.runlabel||'search', html:'OK', parent: content }) .addEventListener("click", function() { self.calculate(); }); ol.ext.element.create('I', { className: 'ol-cancel', html:'cancel', parent: content }) .addEventListener("click", function() { this.resultElement.innerHTML = ''; }.bind(this)); this.resultElement = document.createElement("DIV"); this.resultElement.setAttribute('class', 'ol-result'); element.appendChild(this.resultElement); this.setMode(options.mode || 'car'); this.set('timeout', options.timeout || 10000); }; ol.ext.inherits(ol.control.RoutingGeoportail, ol.control.Control); ol.control.RoutingGeoportail.prototype.setMode = function (mode, silent) { this.set('mode', mode); this.element.querySelector(".ol-car").classList.remove("selected"); this.element.querySelector(".ol-pedestrian").classList.remove("selected"); this.element.querySelector(".ol-"+mode).classList.add("selected"); if (!silent) this.calculate(); }; ol.control.RoutingGeoportail.prototype.setMethod = function (method, silent) { this.set('method', method); if (!silent) this.calculate(); }; ol.control.RoutingGeoportail.prototype.addButton = function (className, title, info) { var bt = document.createElement("I"); bt.setAttribute("class", className); bt.setAttribute("type", "button"); bt.setAttribute("title", title); bt.innerHTML = info||''; this.element.appendChild(bt); return bt; }; /** Get point source * @return {ol.source.Vector } */ ol.control.RoutingGeoportail.prototype.getSource = function () { return this._source; }; ol.control.RoutingGeoportail.prototype._resetArray = function (element) { this._search = []; var q = element.parentNode.querySelectorAll('.search-input > div') q.forEach(function(d) { if (d.olsearch) { if (d.olsearch.get('feature')) { d.olsearch.get('feature').set('step', this._search.length); if (this._search.length===0) d.olsearch.get('feature').set('pos', 'start'); else if (this._search.length === q.length-1) d.olsearch.get('feature').set('pos', 'end'); else d.olsearch.get('feature').set('pos', ''); } this._search.push(d.olsearch); } }.bind(this)); }; /** Remove a new search input * @private */ ol.control.RoutingGeoportail.prototype.removeSearch = function (element, options, after) { element.removeChild(after); if (after.olsearch.get('feature')) this._source.removeFeature(after.olsearch.get('feature')); if (this.getMap()) this.getMap().removeControl(after.olsearch); this._resetArray(element); }; /** Add a new search input * @private */ ol.control.RoutingGeoportail.prototype.addSearch = function (element, options, after) { var self = this; var div = ol.ext.element.create('DIV'); if (after) element.insertBefore(div, after.nextSibling); else element.appendChild(div); ol.ext.element.create ('BUTTON', { title: options.startlabel||"add/remove", parent: div}) .addEventListener('click', function(e) { if (e.ctrlKey) { if (this._search.length>2) this.removeSearch(element, options, div); } else if (e.shiftKey) { this.addSearch(element, options, div); } }.bind(this)); var search = div.olsearch = new ol.control.SearchGeoportail({ className: 'IGNF ol-collapsed', apiKey: options.apiKey, authentication: options.authentication, target: div, reverse: true }); search._changeCounter = 0; this._resetArray(element); search.on('select', function(e){ search.setInput(e.search.fulltext); var f = search.get('feature'); if (!f) { f = new ol.Feature(new ol.geom.Point(e.coordinate)); search.set('feature', f); this._source.addFeature(f); // Check geometry change search.checkgeom = true; f.getGeometry().on('change', function() { if (search.checkgeom) this.onGeometryChange(search, f); }.bind(this)); } else { search.checkgeom = false; if (!e.silent) f.getGeometry().setCoordinates(e.coordinate); search.checkgeom = true; } f.set('name', search.getTitle(e.search)); f.set('step', this._search.indexOf(search)); if (f.get('step') === 0) f.set('pos','start'); else if (f.get('step') === this._search.length-1) f.set('pos','end'); search.set('selection', e.search); }.bind(this)); search.element.querySelector('input').addEventListener('change', function(){ search.set('selection', null); self.resultElement.innerHTML = ''; }); if (this.getMap()) this.getMap().addControl(search); }; /** Geometry has changed * @private */ ol.control.RoutingGeoportail.prototype.onGeometryChange = function (search, f, delay) { // Set current geom var lonlat = ol.proj.transform(f.getGeometry().getCoordinates(), this.getMap().getView().getProjection(), 'EPSG:4326'); search._handleSelect({ x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) }, true, { silent: true }); // Try to revers geocode if (delay) { search._changeCounter--; if (!search._changeCounter) { search.reverseGeocode(f.getGeometry().getCoordinates(), { silent: true }); return; } } else { search._changeCounter++; setTimeout(function() { this.onGeometryChange(search, f, true); }.bind(this), 1000); } } /** * Set the map instance the control is associated with * and add its controls associated to this map. * @param {_ol_Map_} map The map instance. */ ol.control.RoutingGeoportail.prototype.setMap = function (map) { ol.control.Control.prototype.setMap.call(this, map); for (var i=0; i' + d + (t ? ' - ' + t : '') +'' ul.appendChild(li); } }; /** Handle routing response * @private */ ol.control.RoutingGeoportail.prototype.handleResponse = function (data, start, end) { if (data.status === 'ERROR') { this.dispatchEvent({ type: 'errror', status: '200', statusText: data.message }) return; } var routing = { type:'routing' }; routing.features = []; var distance = 0; var duration = 0; var f, route = []; for (var i=0, l; l=data.legs[i]; i++) { for (var j=0, s; s=l.steps[j]; j++) { var geom = []; for (var k=0, p; p=s.points[k]; k++){ p = p.split(','); geom.push([parseFloat(p[0]),parseFloat(p[1])]); if (i===0 || k!==0) route.push(geom[k]); } geom = new ol.geom.LineString(geom); var options = { geometry: geom.transform('EPSG:4326',this.getMap().getView().getProjection()), name: s.name, instruction: s.navInstruction, distance: parseFloat(s.distanceMeters), duration: parseFloat(s.durationSeconds) } //console.log(duration, options.duration, s) distance += options.distance; duration += options.duration; options.distanceT = distance; options.durationT = duration; f = new ol.Feature(options); routing.features.push(f); } } routing.distance = parseFloat(data.distanceMeters); routing.duration = parseFloat(data.durationSeconds); // Full route route = new ol.geom.LineString(route); routing.feature = new ol.Feature ({ geometry: route.transform('EPSG:4326',this.getMap().getView().getProjection()), start: this._search[0].getTitle(start), end: this._search[0].getTitle(end), distance: routing.distance, duration: routing.duration }); // console.log(data, routing); this.dispatchEvent(routing); this.path = routing; console.log(routing) return routing; }; /** Calculate route * @param {Array|undefined} steps an array of steps in EPSG:4326, default use control input values * @return {boolean} true is a new request is send (more than 2 points to calculate) */ ol.control.RoutingGeoportail.prototype.calculate = function (steps) { this.resultElement.innerHTML = ''; if (steps) { var convert = []; steps.forEach(function(s) { convert.push({ x: s[0], y: s[1] }); }); steps = convert; } else { steps = [] for (var i=0; i100) d = Math.round(d/100) * 100; else d = Math.round(d); return '1 / '+ d.toLocaleString(); }; /** Set the current scale (will change the scale of the map) * @param {Number} value the scale factor */ ol.control.Scale.prototype.setScale = function (value) { var map = this.getMap(); if (map && value) { if (value.target) value = value.target.value; ol.sphere.setMapScale(map, value, this.get('ppi')); } this.getScale(); }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Search places using the French National Base Address (BAN) API. * * @constructor * @extends {ol.control.Search} * @fires select * @param {Object=} Control options. * @param {string} options.className control class name * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.reverseTitle Title to use for the reverse geocoding button tooltip, default "Click on the map..." * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500. * @param {integer | undefined} options.minLength minimum length to start searching, default 3 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * * @param {string|undefined} options.url Url to BAN api, default "https://api-adresse.data.gouv.fr/search/" * @param {boolean} options.position Search, with priority to geo position, default false * @param {function} options.getTitle a function that takes a feature and return the text to display in the menu, default return label attribute * @see {@link https://adresse.data.gouv.fr/api/} */ ol.control.SearchBAN = function(options) { options = options || {}; options.typing = options.typing || 500; options.url = options.url || 'https://api-adresse.data.gouv.fr/search/'; options.className = options.className || 'BAN'; options.copy = '© BAN-data.gouv.fr'; ol.control.SearchPhoton.call(this, options); }; ol.ext.inherits(ol.control.SearchBAN, ol.control.SearchPhoton); /** Returns the text to be displayed in the menu * @param {ol.Feature} f the feature * @return {string} the text to be displayed in the index * @api */ ol.control.SearchBAN.prototype.getTitle = function (f) { var p = f.properties; return (p.label); }; /** A ligne has been clicked in the menu > dispatch event * @param {any} f the feature, as passed in the autocomplete * @api */ ol.control.SearchBAN.prototype.select = function (f){ var c = f.geometry.coordinates; // Add coordinate to the event try { c = ol.proj.transform (f.geometry.coordinates, 'EPSG:4326', this.getMap().getView().getProjection()); } catch(e) { /* ok */ } this.dispatchEvent({ type:"select", search:f, coordinate: c }); }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Search on DFCI grid. * * @constructor * @extends {ol.control.Search} * @fires select * @param {Object=} Control options. * @param {string} options.className control class name * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 300. * @param {integer | undefined} options.minLength minimum length to start searching, default 1 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * * @param {string | undefined} options.property a property to display in the index, default 'name'. * @param {function} options.getTitle a function that takes a feature and return the name to display in the index, default return the property * @param {function | undefined} options.getSearchString a function that take a feature and return a text to be used as search string, default geTitle() is used as search string */ ol.control.SearchDFCI = function(options) { if (!options) options = {}; options.className = options.className || 'dfci'; options.placeholder = options.placeholder || 'Code DFCI'; ol.control.Search.call(this, options); }; ol.ext.inherits(ol.control.SearchDFCI, ol.control.Search); /** Autocomplete function * @param {string} s search string * @return {Array|false} an array of search solutions or false if the array is send with the cback argument (asnchronous) * @api */ ol.control.SearchDFCI.prototype.autocomplete = function (s) { s = s.toUpperCase(); s = s.replace(/[^0-9,^A-H,^K-N]/g,''); if (s.length<2) { this.setInput(s); return []; } var i; var proj = this.getMap().getView().getProjection(); var result = []; var c = ol.coordinate.fromDFCI(s, proj); var level = Math.floor(s.length/2)-1; var dfci = ol.coordinate.toDFCI(c, level, proj); dfci = dfci.replace(/[^0-9,^A-H,^K-N]/g,''); // Valid DFCI ? if (!/NaN/.test(dfci) && dfci) { console.log('ok', dfci) this.setInput(dfci + s.substring(dfci.length, s.length)); result.push({ coordinate: ol.coordinate.fromDFCI(dfci, proj), name: dfci }); if (s.length===5) { c = ol.coordinate.fromDFCI(s+0, proj); dfci = (ol.coordinate.toDFCI(c, level+1, proj)).substring(0,5); for (i=0; i<10; i++) { result.push({ coordinate: ol.coordinate.fromDFCI(dfci+i, proj), name: dfci+i }); } } if (level === 2) { for (i=0; i<6; i++) { result.push({ coordinate: ol.coordinate.fromDFCI(dfci+'.'+i, proj), name: dfci+'.'+i }); } } } return result; }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Search features. * * @constructor * @extends {ol.control.Search} * @fires select * @param {Object=} Control options. * @param {string} options.className control class name * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 300. * @param {integer | undefined} options.minLength minimum length to start searching, default 1 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * * @param {string | undefined} options.property a property to display in the index, default 'name'. * @param {function} options.getTitle a function that takes a feature and return the name to display in the index, default return the property * @param {function | undefined} options.getSearchString a function that take a feature and return a text to be used as search string, default geTitle() is used as search string */ ol.control.SearchFeature = function(options) { if (!options) options = {}; options.className = options.className || 'feature'; ol.control.Search.call(this, options); if (typeof(options.getSearchString)=="function") this.getSearchString = options.getSearchString; this.set('property', options.property || 'name'); this.source_ = options.source; }; ol.ext.inherits(ol.control.SearchFeature, ol.control.Search); /** No history avaliable on features */ ol.control.SearchFeature.prototype.restoreHistory = function () { this.set('history', []); }; /** No history avaliable on features */ ol.control.SearchFeature.prototype.saveHistory = function () { try { localStorage.removeItem("ol@search-"+this._classname); } catch(e) { console.warn('Failed to access localStorage...'); } } /** Returns the text to be displayed in the menu * @param {ol.Feature} f the feature * @return {string} the text to be displayed in the index * @api */ ol.control.SearchFeature.prototype.getTitle = function (f) { return f.get(this.get('property')||'name'); }; /** Return the string to search in * @param {ol.Feature} f the feature * @return {string} the text to be used as search string * @api */ ol.control.SearchFeature.prototype.getSearchString = function (f) { return this.getTitle(f); }; /** Get the source * @return {ol.source.Vector} * @api */ ol.control.SearchFeature.prototype.getSource = function () { return this.source_; } /** Get the source * @param {ol.source.Vector} source * @api */ ol.control.SearchFeature.prototype.setSource = function (source) { this.source_ = source; }; /** Autocomplete function * @param {string} s search string * @param {int} max max * @param {function} cback a callback function that takes an array to display in the autocomplete field (for asynchronous search) * @return {Array|false} an array of search solutions or false if the array is send with the cback argument (asnchronous) * @api */ ol.control.SearchFeature.prototype.autocomplete = function (s) { var result = []; if (this.source_) { // regexp s = s.replace(/^\*/,''); var rex = new RegExp(s, 'i'); // The source var features = this.source_.getFeatures(); var max = this.get('maxItems') for (var i=0, f; f=features[i]; i++) { var att = this.getSearchString(f); if (att !== undefined && rex.test(att)) { result.push(f); if ((--max)<=0) break; } } } return result; }; /* 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). */ /** * Search on GPS coordinate. * * @constructor * @extends {ol.control.Search} * @fires select * @param {Object=} Control options. * @param {string} options.className control class name * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 300. * @param {integer | undefined} options.minLength minimum length to start searching, default 1 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 */ ol.control.SearchGPS = function(options) { if (!options) options = {}; options.className = (options.className || '') + ' ol-searchgps'; options.placeholder = options.placeholder || 'lon,lat'; ol.control.Search.call(this, options); // Geolocation this.geolocation = new ol.Geolocation({ projection: "EPSG:4326", trackingOptions: { maximumAge: 10000, enableHighAccuracy: true, timeout: 600000 } }); ol.ext.element.create ('BUTTON', { className: 'ol-geoloc', title: 'Locate with GPS', parent: this.element, click: function(){ this.geolocation.setTracking(true); }.bind(this) }) // DMS switcher ol.ext.element.createSwitch({ html: 'decimal', after: 'DMS', change: function(e) { if (e.target.checked) this.element.classList.add('ol-dms'); else this.element.classList.remove('ol-dms'); }.bind(this), parent: this.element }); this._createForm(); // Move list to the end var ul = this.element.querySelector("ul.autocomplete"); this.element.appendChild(ul); }; ol.ext.inherits(ol.control.SearchGPS, ol.control.Search); /** Create input form * @private */ ol.control.SearchGPS.prototype._createForm = function () { // Value has change var onchange = function(e) { if (e.target.classList.contains('ol-dms')) { lon.value = (lond.value<0 ? -1:1) * Number(lond.value) + Number(lonm.value)/60 + Number(lons.value)/3600; lon.value = (lond.value<0 ? -1:1) * Math.round(lon.value*10000000)/10000000; lat.value = (latd.value<0 ? -1:1) * Number(latd.value) + Number(latm.value)/60 + Number(lats.value)/3600; lat.value = (latd.value<0 ? -1:1) * Math.round(lat.value*10000000)/10000000; } if (lon.value||lat.value) { this._input.value = lon.value+','+lat.value; } else { this._input.value = ''; } if (!e.target.classList.contains('ol-dms')) { var s = ol.coordinate.toStringHDMS([Number(lon.value), Number(lat.value)]); var c = s.replace(/(N|S|E|W)/g,'').split('″'); c[1] = c[1].trim().split(' '); lond.value = (/W/.test(s) ? -1 : 1) * parseInt(c[1][0]); lonm.value = parseInt(c[1][1]); lons.value = parseInt(c[1][2]); c[0] = c[0].trim().split(' '); latd.value = (/W/.test(s) ? -1 : 1) * parseInt(c[0][0]); latm.value = parseInt(c[0][1]); lats.value = parseInt(c[0][2]); } this.search(); }.bind(this); function createInput(className, unit) { var input = ol.ext.element.create('INPUT', { className: className, type:'number', step:'any', lang: 'en', parent: div, on: { 'change keyup': onchange } }); if (unit) { ol.ext.element.create('SPAN', { className: 'ol-dms', html: unit, parent: div, }); } return input; } // Longitude var div = ol.ext.element.create('DIV', { className: 'ol-longitude', parent: this.element }); ol.ext.element.create('LABEL', { html: 'Longitude', parent: div }); var lon = createInput('ol-decimal'); var lond = createInput('ol-dms','°'); var lonm = createInput('ol-dms','\''); var lons = createInput('ol-dms','"'); // Latitude div = ol.ext.element.create('DIV', { className: 'ol-latitude', parent: this.element }) ol.ext.element.create('LABEL', { html: 'Latitude', parent: div }); var lat = createInput('ol-decimal'); var latd = createInput('ol-dms','°'); var latm = createInput('ol-dms','\''); var lats = createInput('ol-dms','"'); // Focus on open if (this.button) { this.button.addEventListener("click", function() { lon.focus(); }); } // Change value on click this.on('select', function(e){ lon.value = e.search.gps[0]; lat.value = e.search.gps[1]; }.bind(this)); // Change value on geolocation this.geolocation.on('change', function(){ this.geolocation.setTracking(false); var coord = this.geolocation.getPosition(); lon.value = coord[0]; lat.value = coord[1]; this._triggerCustomEvent('keyup', lon); }.bind(this)); }; /** Autocomplete function * @param {string} s search string * @return {Array|false} an array of search solutions * @api */ ol.control.SearchGPS.prototype.autocomplete = function (s) { var result = []; var c = s.split(','); c[0] = Number(c[0]); c[1] = Number(c[1]); // Name s = ol.coordinate.toStringHDMS(c) if (s) s= s.replace(/(°|′|″) /g,'$1'); // var coord = ol.proj.transform ([c[0], c[1]], 'EPSG:4326', this.getMap().getView().getProjection()); result.push({ gps: c, coordinate: coord, name: s }); return result; }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Search places using the French National Base Address (BAN) API. * * @constructor * @extends {ol.control.SearchJSON} * @fires select * @param {any} options extend ol.control.SearchJSON options * @param {string} options.className control class name * @param {boolean | undefined} [options.apiKey] the service api key. * @param {string | undefined} options.authentication: basic authentication for the service API as btoa("login:pwd") * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500. * @param {integer | undefined} options.minLength minimum length to start searching, default 3 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * * @param {Number} options.pageSize item per page for parcelle list paging, use -1 for no paging, default 5 * @see {@link https://geoservices.ign.fr/documentation/geoservices/geocodage.html} */ ol.control.SearchGeoportailParcelle = function(options) { var self = this; options.type = "Commune"; options.className = (options.className ? options.className:"")+" IGNF-parcelle ol-collapsed-list ol-collapsed-num"; options.inputLabel = "Commune"; options.noCollapse = true; options.placeholder = options.placeholder || "Choisissez une commune..."; ol.control.SearchGeoportail.call(this, options); this.set('copy', null); var element = this.element; // Add parcel form var div = document.createElement("DIV"); element.appendChild(div); var label = document.createElement("LABEL"); label.innerText = 'Préfixe' div.appendChild(label); label = document.createElement("LABEL"); label.innerText = 'Section' div.appendChild(label); label = document.createElement("LABEL"); label.innerText = 'Numéro' div.appendChild(label); div.appendChild(document.createElement("BR")); // Input this._inputParcelle = { prefix: document.createElement("INPUT"), section: document.createElement("INPUT"), numero: document.createElement("INPUT") }; this._inputParcelle.prefix.setAttribute('maxlength',3); this._inputParcelle.section.setAttribute('maxlength',2); this._inputParcelle.numero.setAttribute('maxlength',4); // Delay search var tout; var doSearch = function() { if (tout) clearTimeout(tout); tout = setTimeout(function() { self.autocompleteParcelle(); }, options.typing || 0); } // Add inputs for (var i in this._inputParcelle) { div.appendChild(this._inputParcelle[i]); this._inputParcelle[i].addEventListener("keyup", doSearch); this._inputParcelle[i].addEventListener('blur', function() { tout = setTimeout(function(){ element.classList.add('ol-collapsed-num'); }, 200); }); this._inputParcelle[i].addEventListener('focus', function() { clearTimeout(tout); element.classList.remove('ol-collapsed-num'); }); } this.activateParcelle(false); // Autocomplete list var auto = document.createElement('DIV'); auto.className = 'autocomplete-parcelle'; element.appendChild(auto); var ul = document.createElement('UL'); ul.classList.add('autocomplete-parcelle'); auto.appendChild(ul); ul = document.createElement('UL'); ul.classList.add('autocomplete-page'); auto.appendChild(ul); // Show/hide list on fcus/blur this._input.addEventListener('blur', function() { setTimeout(function(){ element.classList.add('ol-collapsed-list') }, 200); }); this._input.addEventListener('focus', function() { element.classList.remove('ol-collapsed-list'); self._listParcelle([]); if (self._commune) { self._commune = null; self._input.value = ''; self.drawList_(); } self.activateParcelle(false); }); this.on('select', this.selectCommune.bind(this)); this.set('pageSize', options.pageSize || 5); }; ol.ext.inherits(ol.control.SearchGeoportailParcelle, ol.control.SearchGeoportail); /** Select a commune => start searching parcelle * @param {any} e * @private */ ol.control.SearchGeoportailParcelle.prototype.selectCommune = function(e) { this._commune = e.search.insee; this._input.value = e.search.insee + ' - ' + e.search.fulltext; this.activateParcelle(true); this._inputParcelle.numero.focus(); this.autocompleteParcelle(); }; /** Set the input parcelle * @param {*} p parcel * @param {string} p.Commune * @param {string} p.CommuneAbsorbee * @param {string} p.Section * @param {string} p.Numero * @param {boolean} search start a search */ ol.control.SearchGeoportailParcelle.prototype.setParcelle = function(p, search) { this._inputParcelle.prefix.value = (p.Commune||'') + (p.CommuneAbsorbee||''); this._inputParcelle.section.value = p.Section||''; this._inputParcelle.numero.value = p.Numero||''; if (search) this._triggerCustomEvent("keyup", this._inputParcelle.prefix); }; /** Activate parcelle inputs * @param {bolean} b */ ol.control.SearchGeoportailParcelle.prototype.activateParcelle = function(b) { for (var i in this._inputParcelle) { this._inputParcelle[i].readOnly = !b; } if (b) { this._inputParcelle.section.parentElement.classList.add('ol-active'); } else { this._inputParcelle.section.parentElement.classList.remove('ol-active'); } }; /** Send search request for the parcelle * @private */ ol.control.SearchGeoportailParcelle.prototype.autocompleteParcelle = function() { // Add 0 to fit the format function complete (s, n, c) { if (!s) return s; c = c || "0"; while (s.length < n) s = c+s; return s.replace(/\*/g,'_'); } // The selected commune var commune = this._commune; var prefix = complete (this._inputParcelle.prefix.value, 3); if (prefix === '000') { prefix = '___'; } // Get parcelle number var section = complete (this._inputParcelle.section.value, 2); var numero = complete (this._inputParcelle.numero.value, 4, "0"); var search = commune + (prefix||'___') + (section||"__") + (numero ? numero : section ? "____":"0001"); this.searchParcelle(search, function(jsonResp) { this._listParcelle(jsonResp); }.bind(this), function() { console.log('oops') }) }; /** Send search request for a parcelle number * @param {string} search search parcelle number * @param {function} success callback function called on success * @param {function} error callback function called on error */ ol.control.SearchGeoportailParcelle.prototype.searchParcelle = function(search, success /*, error */) { // Request var request = '' +'' +'' +'' +'' +'
' +''+search+'+' +'
' +'
' +'
' +'
' // Geocode this.ajax( this.get('url').replace('ols/apis/completion','geoportail/ols'), { xls: request }, function(xml) { // XML to JSON var parser = new DOMParser(); var xmlDoc = parser.parseFromString(xml,"text/xml"); var parcelles = xmlDoc.getElementsByTagName('GeocodedAddress'); var jsonResp = [] for (var i=0, parc; parc= parcelles[i]; i++) { var node = parc.getElementsByTagName('gml:pos')[0] || parc.getElementsByTagName('pos')[0]; var p = node.childNodes[0].nodeValue.split(' '); var att = parc.getElementsByTagName('Place'); var json = { lon: Number(p[1]), lat: Number(p[0]) }; for (var k=0, a; a=att[k]; k++) { json[a.attributes.type.value] = a.childNodes[0].nodeValue; } jsonResp.push(json); } success(jsonResp); }, { dataType: 'XML' } ); }; /** * Draw the autocomplete list * @param {*} resp * @private */ ol.control.SearchGeoportailParcelle.prototype._listParcelle = function(resp) { var self = this; var ul = this.element.querySelector("ul.autocomplete-parcelle"); ul.innerHTML=''; var page = this.element.querySelector("ul.autocomplete-page"); page.innerHTML=''; this._listParc = []; // Show page i function showPage(i) { var l = ul.children; var visible = "ol-list-"+i; var k; for (k=0; k1 ? '' : 'none'; } // Sort table resp.sort(function(a,b) { var na = a.INSEE+a.CommuneAbsorbee+a.Section+a.Numero; var nb = b.INSEE+b.CommuneAbsorbee+b.Section+b.Numero; return na===nb ? 0 : na0) li.classList.add("ol-list-"+Math.floor(i/n)); this._listParc.push(r); li.addEventListener("click", function(e) { self._handleParcelle(self._listParc[e.currentTarget.getAttribute("data-search")]); }); li.innerHTML = r.INSEE+r.CommuneAbsorbee+r.Section+r.Numero; ul.appendChild(li); // if (n>0 && !(i%n)) { li = document.createElement("LI"); li.innerText = Math.floor(i/n); li.addEventListener("click", function(e) { showPage(e.currentTarget.innerText); }); page.appendChild(li); } } if (n>0) showPage(0); }; /** * Handle parcelle section * @param {*} parc * @private */ ol.control.SearchGeoportailParcelle.prototype._handleParcelle = function(parc) { this.dispatchEvent({ type:"parcelle", search: parc, coordinate: ol.proj.fromLonLat([parc.lon, parc.lat], this.getMap().getView().getProjection()) }); }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Search places using the Nominatim geocoder from the OpenStreetmap project. * * @constructor * @extends {ol.control.Search} * @fires select * @param {Object=} Control options. * @param {string} options.className control class name * @param {boolean | undefined} options.polygon To get output geometry of results (in geojson format), default false. * @param {Array | undefined} options.viewbox The preferred area to find search results. Any two corner points of the box are accepted in any order as long as they span a real box, default none. * @param {boolean | undefined} options.bounded Restrict the results to only items contained with the bounding box. Restricting the results to the bounding box also enables searching by amenity only. default false * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.title Title to use for the search button tooltip, default "Search" * @param {string | undefined} options.reverseTitle Title to use for the reverse geocoding button tooltip, default "Click on the map..." * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500. * @param {integer | undefined} options.minLength minimum length to start searching, default 3 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * * @param {string|undefined} options.url URL to Nominatim API, default "https://nominatim.openstreetmap.org/search" * @see {@link https://wiki.openstreetmap.org/wiki/Nominatim} */ ol.control.SearchNominatim = function(options) { options = options || {}; options.className = options.className || 'nominatim'; options.typing = options.typing || 500; options.url = options.url || 'https://nominatim.openstreetmap.org/search'; options.copy = '© OpenStreetMap contributors'; ol.control.SearchJSON.call(this, options); this.set('polygon', options.polygon); this.set('viewbox', options.viewbox); this.set('bounded', options.bounded); }; ol.ext.inherits(ol.control.SearchNominatim, ol.control.SearchJSON); /** Returns the text to be displayed in the menu * @param {ol.Feature} f the feature * @return {string} the text to be displayed in the index * @api */ ol.control.SearchNominatim.prototype.getTitle = function (f) { var info = []; if (f.class) info.push(f.class); if (f.type) info.push(f.type); var title = f.display_name+(info.length ? ""+info.join(' - ')+"" : ''); if (f.icon) title = "" + title; return (title); }; /** * @param {string} s the search string * @return {Object} request data (as key:value) * @api */ ol.control.SearchNominatim.prototype.requestData = function (s) { var data = { format: "json", addressdetails: 1, q: s, polygon_geojson: this.get('polygon') ? 1:0, bounded: this.get('bounded') ? 1:0, limit: this.get('maxItems') }; if (this.get('viewbox')) data.viewbox = this.get('viewbox'); return data; }; /** A ligne has been clicked in the menu > dispatch event * @param {any} f the feature, as passed in the autocomplete * @api */ ol.control.SearchNominatim.prototype.select = function (f){ var c = [Number(f.lon), Number(f.lat)]; // Add coordinate to the event try { c = ol.proj.transform (c, 'EPSG:4326', this.getMap().getView().getProjection()); } catch(e) { /* ok */} this.dispatchEvent({ type:"select", search:f, coordinate: c }); }; /** * Handle server response to pass the features array to the display list * @param {any} response server response * @return {Array} an array of feature * @api */ ol.control.SearchJSON.prototype.handleResponse = function (response) { return response.results || response; }; /** Reverse geocode * @param {ol.coordinate} coord * @api */ ol.control.SearchNominatim.prototype.reverseGeocode = function (coord, cback) { var lonlat = ol.proj.transform (coord, this.getMap().getView().getProjection(), 'EPSG:4326'); this.ajax( this.get('url').replace('search', 'reverse'), { lon: lonlat[0], lat: lonlat[1], format: 'json' }, function(resp) { if (cback) { cback.call(this, [resp]); } else { if (resp && !resp.error) { this._handleSelect(resp, true); } //this.setInput('', true); } }.bind(this) ); }; /* 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). */ /** * Search places using the MediaWiki API. * @see https://www.mediawiki.org/wiki/API:Main_page * * @constructor * @extends {ol.control.SearchJSON} * @fires select * @param {Object=} Control options. * @param {string} options.className control class name * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 1000. * @param {integer | undefined} options.minLength minimum length to start searching, default 3 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * @param {function | undefined} options.handleResponse Handle server response to pass the features array to the list * * @param {string|undefined} options.lang API language, default none */ ol.control.SearchWikipedia = function(options){ options = options || {}; options.lang = options.lang||'en'; options.className = options.className || 'ol-search-wikipedia'; options.url = 'https://'+options.lang+'.wikipedia.org/w/api.php'; options.placeholder = options.placeholder || 'search string, File:filename'; options.copy = 'Wikipedia® - CC-By-SA'; ol.control.SearchJSON.call(this, options); this.set('lang', options.lang); }; ol.ext.inherits(ol.control.SearchWikipedia, ol.control.SearchJSON); /** Returns the text to be displayed in the menu * @param {ol.Feature} f the feature * @return {string} the text to be displayed in the index * @api */ ol.control.SearchWikipedia.prototype.getTitle = function (f){ return ol.ext.element.create('DIV', { html: f.title, title: f.desc }); //return f.desc; }; /** Set the current language * @param {string} lang the current language as ISO string (en, fr, de, es, it, ja, ...) */ ol.control.SearchWikipedia.prototype.setLang = function (lang){ this.set('lang', lang) this.set('url', 'https://'+lang+'.wikipedia.org/w/api.php'); }; /** * @param {string} s the search string * @return {Object} request data (as key:value) * @api */ ol.control.SearchWikipedia.prototype.requestData = function (s) { var data = { action: 'opensearch', search: s, lang: this.get('lang'), format: 'json', origin: '*', limit: this.get('maxItems') } return data; }; /** * Handle server response to pass the features array to the list * @param {any} response server response * @return {Array} an array of feature */ ol.control.SearchWikipedia.prototype.handleResponse = function (response) { var features = []; for (var i=0; i} options.source the source to search in * @param {string} [options.selectLabel=select] select button label * @param {string} [options.addLabel=add] add button label * @param {string} [options.caseLabel=case sensitive] case checkbox label * @param {string} [options.allLabel=match all] match all checkbox label * @param {string} [options.attrPlaceHolder=attribute] * @param {string} [options.valuePlaceHolder=value] */ ol.control.Select = function(options) { var self = this; if (!options) options = {}; // Container var div = options.content = document.createElement("div"); // Autocompletion list this._ul = ol.ext.element.create('UL', { parent: div }); // All conditions this._all = ol.ext.element.create('INPUT', { type: 'checkbox', checked: true }); var label_match_all = ol.ext.element.create('LABEL',{ html: this._all, parent: div }); ol.ext.element.appendText(label_match_all, options.allLabel || 'match all'); // Use case this._useCase = ol.ext.element.create('INPUT', { type: 'checkbox' }); var label_case_sensitive = ol.ext.element.create('LABEL',{ html: this._useCase, parent: div }); ol.ext.element.appendText(label_case_sensitive, options.caseLabel || 'case sensitive'); ol.control.SelectBase.call(this, options); // Add button ol.ext.element.create('BUTTON', { className: 'ol-append', html: options.addLabel || 'add rule', click: function(){ self.addCondition(); }, parent: div }); this._conditions = []; this.set('attrPlaceHolder', options.attrPlaceHolder || 'attribute'); this.set('valuePlaceHolder', options.valuePlaceHolder || 'value'); this.addCondition(); }; ol.ext.inherits(ol.control.Select, ol.control.SelectBase); /** Add a new condition * @param {*} options * @param {string} options.attr attribute name * @param {string} options.op operator * @param {string} options.val attribute value */ ol.control.Select.prototype.addCondition = function (options) { options = options || {}; this._conditions.push({ attr: options.attr || '', op: options.op || '=', val: options.val || '' }); this._drawlist(); }; /** Get the condition list */ ol.control.Select.prototype.getConditions = function () { return { usecase: this._useCase.checked, all: this._all.checked, conditions: this._conditions } }; /** Set the condition list */ ol.control.Select.prototype.setConditions = function (cond) { this._useCase.checked = cond.usecase; this._all.checked = cond.all; this._conditions = cond.conditions; this._drawlist(); }; /** Get the conditions as string */ ol.control.Select.prototype.getConditionsString = function (cond) { var st = ''; for (var i=0,c; c=cond.conditions[i]; i++) { if (c.attr) { st += (st ? (cond.all ? ' AND ' : ' OR ') : '') + c.attr + this.operationsList[c.op] + c.val; } } return st }; /** Draw the liste * @private */ ol.control.Select.prototype._drawlist = function () { this._ul.innerHTML = ''; for (var i=0; i < this._conditions.length; i++) { this._ul.appendChild(this._getLiCondition(i)); } }; /** Get a line * @return {*} * @private */ ol.control.Select.prototype._autocomplete = function (val, ul) { ul.classList.remove('ol-hidden'); ul.innerHTML = ''; var attributes = {}; var sources = this.get('source'); for (var i=0, s; s=sources[i]; i++) { var features = s.getFeatures(); for (var j=0, f; f=features[j]; j++) { Object.assign(attributes, f.getProperties()); if (j>100) break; } } var rex = new RegExp(val, 'i'); for (var a in attributes) { if (a==='geometry') continue; if (rex.test(a)) { var li = document.createElement('li'); li.textContent = a; li.addEventListener("click", function() { ul.previousElementSibling.value = this.textContent; var event = document.createEvent('HTMLEvents'); event.initEvent('change', true, false); ul.previousElementSibling.dispatchEvent(event); ul.classList.add('ol-hidden'); }); ul.appendChild(li); } } }; /** Get a line * @return {*} * @private */ ol.control.Select.prototype._getLiCondition = function (i) { var self = this; var li = document.createElement('li'); // Attribut var autocomplete = document.createElement('div'); autocomplete.classList.add('ol-autocomplete'); autocomplete.addEventListener("mouseleave", function() { this.querySelector('ul'). classList.add('ol-hidden'); }); li.appendChild(autocomplete); var input_attr = document.createElement('input'); input_attr.classList.add('ol-attr'); input_attr.setAttribute('type', 'search'); input_attr.setAttribute('placeholder', this.get('attrPlaceHolder')); input_attr.addEventListener('keyup', function () { self._autocomplete( this.value, this.nextElementSibling ); }) input_attr.addEventListener('focusout', function() { setTimeout(function() { autocomplete.querySelector('ul'). classList.add('ol-hidden'); }, 300); }); input_attr.addEventListener('click', function(){ setTimeout(function() { self._autocomplete( this.value, this.nextElementSibling ); this.nextElementSibling.classList.remove('ol-hidden'); }.bind(this)); }) input_attr.addEventListener('change', function() { self._conditions[i].attr = this.value; }) input_attr.value = self._conditions[i].attr; autocomplete.appendChild(input_attr); // Autocomplete list var ul_autocomplete = document.createElement('ul'); ul_autocomplete.classList.add('ol-hidden') autocomplete.appendChild(ul_autocomplete); // Operation var select = document.createElement('select'); li.appendChild(select); for (var k in this.operationsList) { var option = document.createElement('option'); option.value = k; option.textContent = this.operationsList[k]; select.appendChild(option); } select.value = self._conditions[i].op; select.addEventListener('change', function() { self._conditions[i].op = this.value; }); // Value var input_value = document.createElement('input'); input_value.setAttribute('type', 'text'); input_value.setAttribute('placeholder', this.get('valuePlaceHolder')); input_value.addEventListener('change', function() { self._conditions[i].val = this.value; }) input_value.value = self._conditions[i].val; li.appendChild(input_value); if (this._conditions.length > 1) { var div_delete = document.createElement('div'); div_delete.classList.add('ol-delete'); div_delete.addEventListener("click", function(){ self.removeCondition(i); }) li.appendChild(div_delete); } // return li; }; /** Remove the ith condition * @param {int} i condition index */ ol.control.Select.prototype.removeCondition = function (i) { this._conditions.splice(i,1); this._drawlist(); }; /** Select features by attributes * @param {*} options * @param {Array|undefined} options.sources source to apply rules, default the select sources * @param {bool} options.useCase case sensitive, default checkbox state * @param {bool} options.matchAll match all conditions, , default checkbox state * @param {Array} options.conditions array of conditions * @fires select */ ol.control.Select.prototype.doSelect = function (options) { options = options || {}; options.useCase = options.useCase || this._useCase.checked; options.matchAll = options.matchAll || this._all.checked; options.conditions = options.conditions || this._conditions return ol.control.SelectBase.prototype.doSelect.call(this, options); }; /* 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). */ /** * Select features by property using a popup * * @constructor * @extends {ol.control.SelectBase} * @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/source/Vector | Array
    } options.source the source to search in * @param {string} options.property property to select on * @param {string} options.label control label * @param {number} options.max max feature to test to get the values, default 10000 * @param {number} options.selectAll select all features if no option selected * @param {string} options.type check type: checkbox or radio, default checkbox * @param {number} options.defaultLabel label for the default radio button * @param {function|undefined} options.onchoice function triggered when an option is clicked, default doSelect */ ol.control.SelectCheck = function(options) { if (!options) options = {}; // Container var div = options.content = ol.ext.element.create('DIV'); if (options.label) { ol.ext.element.create('LABEL', { html: options.label, parent: div }); } // Input div this._input = ol.ext.element.create('DIV', { parent: div }); options.className = options.className || 'ol-select-check'; ol.control.SelectBase.call(this, options); this.set('property', options.property || 'name'); this.set('max', options.max || 10000); this.set('defaultLabel', options.defaultLabel); this.set('type', options.type); this._selectAll = options.selectAll; this._onchoice = options.onchoice; // Set select options if (options.values) { this.setValues({ values: options.values, sort: true }); } else { this.setValues(); } }; ol.ext.inherits(ol.control.SelectCheck, ol.control.SelectBase); /** * Set the map instance the control associated with. * @param {o.Map} map The map instance. */ ol.control.SelectCheck.prototype.setMap = function(map) { ol.control.SelectBase.prototype.setMap.call(this, map); this.setValues(); }; /** Select features by attributes */ ol.control.SelectCheck.prototype.doSelect = function(options) { console.log('select') options = options || {}; var conditions = []; this._checks.forEach(function(c) { if (c.checked) { if (c.value) { conditions.push({ attr: this.get('property'), op: '=', val: c.value }); } } }.bind(this)); if (!conditions.length) { return ol.control.SelectBase.prototype.doSelect.call(this, { features: options.features, matchAll: this._selectAll }); } else { return ol.control.SelectBase.prototype.doSelect.call(this, { features: options.features, conditions: conditions }) } }; /** Set the popup values * @param {Object} options * @param {Object} options.values a key/value list with key = property value, value = title shown in the popup, default search values in the sources * @param {boolean} options.sort sort values */ ol.control.SelectCheck.prototype.setValues = function(options) { options = options || {}; var values, vals; if (options.values) { if (options.values instanceof Array) { vals = {}; options.values.forEach(function(v) { vals[v] = v; }); } else { vals = options.values; } } else { vals = {}; var prop = this.get('property'); this.getSources().forEach(function(s){ var features = s.getFeatures(); var max = Math.min(features.length, this.get('max')) for (var i=0; i} options.source the source to search in * @param {string} options.label control label, default 'condition' * @param {number} options.selectAll select all features if no option selected * @param {condition|Array} options.condition conditions * @param {function|undefined} options.onchoice function triggered when an option is clicked, default doSelect */ ol.control.SelectCondition = function(options) { if (!options) options = {}; // Container var div = options.content = ol.ext.element.create('DIV'); this._check = ol.ext.element.createSwitch({ after: options.label || 'condition', change: function () { if (this._onchoice) this._onchoice() else this.doSelect(); }.bind(this), parent: div }) // Input div this._input = ol.ext.element.create('DIV', { parent: div }); options.className = options.className || 'ol-select-condition'; ol.control.SelectBase.call(this, options); this.setCondition(options.condition); this._selectAll = options.selectAll; this._onchoice = options.onchoice; }; ol.ext.inherits(ol.control.SelectCondition, ol.control.SelectBase); /** Set condition to select on * @param {condition | Array} condition * @param {string} attr property to select on * @param {string} op operator (=, !=, <; <=, >, >=, contain, !contain, regecp) * @param {*} val value to select on */ ol.control.SelectCondition.prototype.setCondition = function(condition) { if (!condition) this._conditions = []; else this._conditions = (condition instanceof Array ? condition : [condition]); }; /** Add a condition to select on * @param {condition} condition * @param {string} attr property to select on * @param {string} op operator (=, !=, <; <=, >, >=, contain, !contain, regecp) * @param {*} val value to select on */ ol.control.SelectCondition.prototype.addCondition = function(condition) { this._conditions.push(condition); }; /** Select features by condition */ ol.control.SelectCondition.prototype.doSelect = function(options) { options = options || {}; var conditions = this._conditions; if (!this._check.checked) { return ol.control.SelectBase.prototype.doSelect.call(this, { features: options.features, matchAll: this._selectAll }); } else { return ol.control.SelectBase.prototype.doSelect.call(this, { features: options.features, conditions: conditions }) } }; /* 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). */ /** * Select features by property using a simple text input * * @constructor * @extends {ol.control.SelectBase} * @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/source/Vector | Array
      } options.source the source to search in * @param {string} options.property property to select on * @param {function|undefined} options.onchoice function triggered the text change, default nothing */ ol.control.SelectFulltext = function(options) { if (!options) options = {}; // Container var div = options.content =ol.ext.element.create('DIV'); if (options.label) { ol.ext.element.create('LABEL', { html: options.label, parent: div }); } this._input = ol.ext.element.create('INPUT', { placeHolder: options.placeHolder || 'search...', change: function() { if (this._onchoice) this._onchoice(); }.bind(this), parent: div }); ol.control.SelectBase.call(this, options); this._onchoice = options.onchoice; this.set('property', options.property || 'name'); }; ol.ext.inherits(ol.control.SelectFulltext, ol.control.SelectBase); /** Select features by condition */ ol.control.SelectFulltext.prototype.doSelect= function(options) { options = options || {}; return ol.control.SelectBase.prototype.doSelect.call(this, { features: options.features, useCase: false, conditions: [{ attr: this.get('property'), op: 'contain', val: this._input.value }] }); } /* 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). */ /** * A multiselect control. * A container that manage other control Select * * @constructor * @extends {ol.control.SelectBase} * @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/source/Vector | Array
        } options.source the source to search in * @param {Array} options.controls an array of controls */ ol.control.SelectMulti = function(options) { if (!options) options = {}; // Container options.content = ol.ext.element.create('DIV'); this._container = ol.ext.element.create('UL', { parent: options.content }); options.className = options.className || 'ol-select-multi'; ol.control.SelectBase.call(this, options); this._controls = []; options.controls.forEach(this.addControl.bind(this)); }; ol.ext.inherits(ol.control.SelectMulti, ol.control.SelectBase); /** * Set the map instance the control associated with. * @param {o.Map} map The map instance. */ ol.control.SelectMulti.prototype.setMap = function(map) { if (this.getMap()) { this._controls.forEach(function(c) { this.getMap().remveControl(c); }.bind(this)); } ol.control.SelectBase.prototype.setMap.call(this, map); if (this.getMap()) { this._controls.forEach(function(c) { this.getMap().addControl(c); }.bind(this)); } }; /** Add a new control * @param {ol.control.SelectBase} c */ ol.control.SelectMulti.prototype.addControl = function(c) { if (c instanceof ol.control.SelectBase) { this._controls.push(c); c.setTarget(ol.ext.element.create('LI', { parent: this._container })); c._selectAll = true; c._onchoice = this.doSelect.bind(this); if (this.getMap()) { this.getMap().addControl(c); } } }; /** Get select controls * @return {Aray} */ ol.control.SelectMulti.prototype.getControls = function() { return this._controls; }; /** Select features by condition */ ol.control.SelectMulti.prototype.doSelect = function() { var features = []; this.getSources().forEach(function(s) { features = features.concat(s.getFeatures()); }); this._controls.forEach(function(c) { features = c.doSelect({ features: features }); }); this.dispatchEvent({ type:"select", features: features }); return features; }; /* 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). */ /** * Select features by property using a popup * * @constructor * @extends {ol.control.SelectBase} * @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/source/Vector | Array
          } options.source the source to search in * @param {string} options.property property to select on * @param {number} options.max max feature to test to get the values, default 10000 * @param {number} options.selectAll select all features if no option selected * @param {string} options.defaultLabel label for the default selection * @param {function|undefined} options.onchoice function triggered when an option is clicked, default doSelect */ ol.control.SelectPopup = function(options) { if (!options) options = {}; // Container var div = options.content = ol.ext.element.create('DIV'); if (options.label) { ol.ext.element.create('LABEL', { html: options.label, parent: div }); } this._input = ol.ext.element.create('SELECT', { on: { change: function () { if (this._onchoice) this._onchoice(); else this.doSelect(); }.bind(this) }, parent: div }); options.className = options.className || 'ol-select-popup'; ol.control.SelectBase.call(this, options); this.set('property', options.property || 'name'); this.set('max', options.max || 10000); this.set('defaultLabel', options.defaultLabel); this._selectAll = options.selectAll; this._onchoice = options.onchoice; // Set select options this.setValues(); }; ol.ext.inherits(ol.control.SelectPopup, ol.control.SelectBase); /** * Set the map instance the control associated with. * @param {o.Map} map The map instance. */ ol.control.SelectPopup.prototype.setMap = function(map) { ol.control.SelectBase.prototype.setMap.call(this, map); this.setValues(); }; /** Select features by attributes */ ol.control.SelectPopup.prototype.doSelect = function(options) { options = options || {}; if (!this._input.value) { return ol.control.SelectBase.prototype.doSelect.call(this, { features: options.features, matchAll: this._selectAll }); } else { return ol.control.SelectBase.prototype.doSelect.call(this, { features: options.features, conditions: [{ attr: this.get('property'), op: '=', val: this._input.value }] }) } }; /** Set the popup values * @param {Object} values a key/value list with key = property value, value = title shown in the popup, default search values in the sources */ ol.control.SelectPopup.prototype.setValues = function(options) { options = options || {}; var values, vals; if (options.values) { if (options.values instanceof Array) { vals = {}; options.values.forEach(function(v) { vals[v] = v; }); } else { vals = options.values; } } else { vals = {}; var prop = this.get('property'); this.getSources().forEach(function(s){ var features = s.getFeatures(); var max = Math.min(features.length, this.get('max')) for (var i=0; i '+html[i]+'
          '; } } ol.ext.element.setHTML(this.element, s); } else { ol.ext.element.hide(this.element); } }; /** Set status position * @param {string} position position of the status 'top', 'left', 'bottom' or 'right', default top */ ol.control.Status.prototype.setPosition = function(position) { this.element.classList.remove('ol-left'); this.element.classList.remove('ol-right'); this.element.classList.remove('ol-bottom'); this.element.classList.remove('ol-center'); if (/^left$|^right$|^bottom$|^center$/.test(position)) { this.element.classList.add('ol-'+position); } }; /** Show the status * @param {boolean} show show or hide the control, default true */ ol.control.Status.prototype.show = function(show) { if (show===false) ol.ext.element.hide(this.element); else ol.ext.element.show(this.element); }; /** Hide the status */ ol.control.Status.prototype.hide = function() { ol.ext.element.hide(this.element); }; /** Toggle the status */ ol.control.Status.prototype.toggle = function() { ol.ext.element.toggle(this.element); }; /** Is status visible */ ol.control.Status.prototype.isShown = function() { return this.element.style.display==='none'; }; // Add flyTo /** A control with scroll-driven navigation to create narrative maps * * @constructor * @extends {ol.control.Control} * @fires scrollto * @fires clickimage * @param {Object=} options Control options. * @param {String} options.className class of the control * @param {Element | string | undefined} [options.html] The storymap content * @param {Element | string | undefined} [options.target] The target element to place the story. If no html is provided the content of the target will be used. * @param {boolean} [options.minibar=false] add a mini scroll bar */ ol.control.Storymap = function(options) { // Remove or get target content if (options.target) { if (!options.html) { options.html = options.target.innerHTML; } else if (options.html instanceof Element) { options.html = options.html.innerHTML; } options.target.innerHTML = ''; } // New element var element = ol.ext.element.create('DIV', { className: (options.className || '') + ' ol-storymap' + (options.target ? '': ' ol-unselectable ol-control'), }); this.content = ol.ext.element.create('DIV', { parent: element }); // Initialize ol.control.Control.call(this, { element: element, target: options.target }); // Make a scroll div ol.ext.element.scrollDiv (this.content, { vertical: true, mousewheel: true, minibar: options.minibar }); this.setStory(options.html); }; ol.ext.inherits(ol.control.Storymap, ol.control.Control); /** Scroll to a chapter * @param {string} name Name of the chapter to scroll to */ ol.control.Storymap.prototype.setChapter = function (name) { var chapter = this.content.querySelectorAll('.chapter'); for (var i=0, s; s=chapter[i]; i++) { if (s.getAttribute('name')===name) { this.content.scrollTop = s.offsetTop - 30; } } }; /** Scroll to a chapter * @param {string} name Name of the chapter to scroll to */ ol.control.Storymap.prototype.setStory = function (html) { if (html instanceof Element) { this.content.innerHTML = ''; this.content.appendChild(html); } else { this.content.innerHTML = html; } this.content.querySelectorAll('.chapter').forEach(function(c) { c.addEventListener('click', function(e) { if (!c.classList.contains('ol-select')) { this.content.scrollTop = c.offsetTop - 30; e.preventDefault(); } else { if (e.target.tagName==='IMG' && e.target.dataset.title) { this.dispatchEvent({ coordinate: this.getMap() ? this.getMap().getCoordinateFromPixel([e.layerX,e.layerY]) : null, type: 'clickimage', img: e.target, title: e.target.dataset.title, element: c, name: c.getAttribute('name'), originalEvent: e }); } } }.bind(this)); }.bind(this)); // Scroll to the next chapter var sc = this.content.querySelectorAll('.ol-scroll-next'); sc.forEach(function(s) { s.addEventListener('click', function(e) { if (s.parentElement.classList.contains('ol-select')) { var chapter = this.content.querySelectorAll('.chapter'); var scrollto = s.offsetTop; for (var i=0, c; c=chapter[i]; i++) { if (c.offsetTop > scrollto) { scrollto = c.offsetTop; break; } } this.content.scrollTop = scrollto - 30; e.stopPropagation(); e.preventDefault(); } }.bind(this)); }.bind(this)); // Scroll top sc = this.content.querySelectorAll('.ol-scroll-top'); sc.forEach(function(i) { i.addEventListener('click', function(e){ this.content.scrollTop = 0; e.stopPropagation(); e.preventDefault(); }.bind(this)); }.bind(this)); var getEvent = function(currentDiv) { var lonlat = [ parseFloat(currentDiv.getAttribute('data-lon')), parseFloat(currentDiv.getAttribute('data-lat'))]; var coord = ol.proj.fromLonLat(lonlat, this.getMap().getView().getProjection()); var zoom = parseFloat(currentDiv.getAttribute('data-zoom')); return { type: 'scrollto', element: currentDiv, name: currentDiv.getAttribute('name'), coordinate: coord, lon: lonlat, zoom: zoom }; }.bind(this); // Handle scrolling var currentDiv = this.content.querySelectorAll('.chapter')[0]; setTimeout (function (){ currentDiv.classList.add('ol-select'); this.dispatchEvent(getEvent(currentDiv)); }.bind(this)); // Trigger change event on scroll this.content.addEventListener('scroll', function() { var current, chapter = this.content.querySelectorAll('.chapter'); var height = ol.ext.element.getStyle(this.content, 'height'); if (!this.content.scrollTop) { current = chapter[0]; } else { for (var i=0, s; s=chapter[i]; i++) { var p = s.offsetTop - this.content.scrollTop; if (p > height/3) break; current = s; } } if (current && current!==currentDiv) { if (currentDiv) currentDiv.classList.remove('ol-select'); currentDiv = current; currentDiv.classList.add('ol-select'); var e = getEvent(currentDiv); var view = this.getMap().getView(); view.cancelAnimations(); switch (currentDiv.getAttribute('data-animation')) { case 'flyto': { view.flyTo({ center: e.coordinate, zoomAt: Math.min(view.getZoom(), e.zoom)-1, zoom: e.zoom }); break; } default: break; } this.dispatchEvent(e); } }.bind(this)); }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * @classdesc Swipe Control. * @fires moving * @constructor * @extends {ol.control.Control} * @param {Object=} Control options. * @param {ol.layer} options.layers layer to swipe * @param {ol.layer} options.rightLayer layer to swipe on right side * @param {string} options.className control class name * @param {number} options.position position propertie of the swipe [0,1], default 0.5 * @param {string} options.orientation orientation propertie (vertical|horizontal), default vertical */ ol.control.Swipe = function(options) { options = options || {}; var button = document.createElement('button'); var element = document.createElement('div'); element.className = (options.className || "ol-swipe") + " ol-unselectable ol-control"; element.appendChild(button); element.addEventListener("mousedown", this.move.bind(this)); element.addEventListener("touchstart", this.move.bind(this)); ol.control.Control.call(this, { element: element }); // An array of listener on layer postcompose this.precomposeRight_ = this.precomposeRight.bind(this); this.precomposeLeft_ = this.precomposeLeft.bind(this); this.postcompose_ = this.postcompose.bind(this); this.layers = []; if (options.layers) this.addLayer(options.layers, false); if (options.rightLayers) this.addLayer(options.rightLayers, true); this.on('propertychange', function(e) { if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } if (this.get('orientation') === "horizontal") { this.element.style.top = this.get('position')*100+"%"; this.element.style.left = ""; } else { if (this.get('orientation') !== "vertical") this.set('orientation', "vertical"); this.element.style.left = this.get('position')*100+"%"; this.element.style.top = ""; } if (e.key==='orientation') { this.element.classList.remove("horizontal", "vertical"); this.element.classList.add(this.get('orientation')); } // Force VectorImage to refresh if (!this.isMoving) { this.layers.forEach(function(l) { if (l.layer.getImageRatio) l.layer.changed(); }) } }.bind(this)); this.set('position', options.position || 0.5); this.set('orientation', options.orientation || 'vertical'); }; ol.ext.inherits(ol.control.Swipe, ol.control.Control); /** * Set the map instance the control associated with. * @param {_ol_Map_} map The map instance. */ ol.control.Swipe.prototype.setMap = function(map) { var i; var l; if (this.getMap()) { for (i=0; i} layer to clip * @param {bool} add layer in the right part of the map, default left. */ ol.control.Swipe.prototype.addLayer = function(layers, right) { if (!(layers instanceof Array)) layers = [layers]; for (var i=0; i} layer to clip */ ol.control.Swipe.prototype.removeLayer = function(layers) { if (!(layers instanceof Array)) layers = [layers]; for (var i=0; i=0 && this.getMap()) { if (this.layers[k].right) layers[i].un(['precompose','prerender'], this.precomposeRight_); else layers[i].un(['precompose','prerender'], this.precomposeLeft_); layers[i].un(['postcompose','postrender'], this.postcompose_); this.layers.splice(k,1); } } if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } }; /** Get visible rectangle * @returns {ol.extent} */ ol.control.Swipe.prototype.getRectangle = function() { var s; if (this.get('orientation') === 'vertical') { s = this.getMap().getSize(); return [ 0, 0, s[0]*this.get('position'), s[1]]; } else { s = this.getMap().getSize(); return [ 0, 0, s[0], s[1]*this.get('position')]; } }; /** @private */ ol.control.Swipe.prototype.move = function(e) { var self = this; var l; if (!this._movefn) this._movefn = this.move.bind(this); switch (e.type) { case 'touchcancel': case 'touchend': case 'mouseup': { self.isMoving = false; ["mouseup", "mousemove", "touchend", "touchcancel", "touchmove"] .forEach(function(eventName) { document.removeEventListener(eventName, self._movefn); }); // Force VectorImage to refresh this.layers.forEach(function(l) { if (l.layer.getImageRatio) l.layer.changed(); }) break; } case 'mousedown': case 'touchstart': { self.isMoving = true; ["mouseup", "mousemove", "touchend", "touchcancel", "touchmove"] .forEach(function(eventName) { document.addEventListener(eventName, self._movefn); }); } // fallthrough case 'mousemove': case 'touchmove': { if (self.isMoving) { if (self.get('orientation') === 'vertical') { var pageX = e.pageX || (e.touches && e.touches.length && e.touches[0].pageX) || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); if (!pageX) break; pageX -= self.getMap().getTargetElement().getBoundingClientRect().left + window.pageXOffset - document.documentElement.clientLeft; l = self.getMap().getSize()[0]; var w = l - Math.min(Math.max(0, l-pageX), l); l = w/l; self.set('position', l); self.dispatchEvent({ type: 'moving', size: [w, self.getMap().getSize()[1]], position: [l,0] }); } else { var pageY = e.pageY || (e.touches && e.touches.length && e.touches[0].pageY) || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY); if (!pageY) break; pageY -= self.getMap().getTargetElement().getBoundingClientRect().top + window.pageYOffset - document.documentElement.clientTop; l = self.getMap().getSize()[1]; var h = l - Math.min(Math.max(0, l-pageY), l); l = h/l; self.set('position', l); self.dispatchEvent({ type: 'moving', size: [self.getMap().getSize()[0],h], position: [0,l] }); } } break; } default: break; } }; /** @private */ ol.control.Swipe.prototype._transformPt = function(e, pt) { var tr = e.inversePixelTransform; var x = pt[0]; var y = pt[1]; pt[0] = tr[0] * x + tr[2] * y + tr[4]; pt[1] = tr[1] * x + tr[3] * y + tr[5]; return pt; }; /** @private */ ol.control.Swipe.prototype._drawRect = function(e, pts) { var tr = e.inversePixelTransform; if (tr) { var r = [ [pts[0][0], pts[0][1]], [pts[0][0], pts[1][1]], [pts[1][0], pts[1][1]], [pts[1][0], pts[0][1]], [pts[0][0], pts[0][1]] ]; e.context.save() // Rotate VectorImages if (e.target.getImageRatio) { var rot = -Math.atan2(e.frameState.pixelToCoordinateTransform[1], e.frameState.pixelToCoordinateTransform[0]); e.context.translate(e.frameState.size[0]/2, e.frameState.size[1]/2) e.context.rotate(rot) e.context.translate(-e.frameState.size[0]/2, -e.frameState.size[1]/2) } r.forEach(function (pt, i) { pt = [ (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) ]; if (!i) { e.context.moveTo(pt[0], pt[1]); } else { e.context.lineTo(pt[0], pt[1]); } }); e.context.restore(); } else { var ratio = e.frameState.pixelRatio; e.context.rect(pts[0][0]*ratio,pts[0][1]*ratio,pts[1][0]*ratio,pts[1][1]*ratio); } }; /** @private */ ol.control.Swipe.prototype.precomposeLeft = function(e) { var ctx = e.context; if (ctx instanceof WebGLRenderingContext) { if (e.type === 'prerender') { // Clear ctx.clearColor(0, 0, 0, 0); ctx.clear(ctx.COLOR_BUFFER_BIT); // Clip ctx.enable(ctx.SCISSOR_TEST); var mapSize = this.getMap().getSize(); // [width, height] in CSS pixels // get render coordinates and dimensions given CSS coordinates var bottomLeft = this._transformPt(e, [0, mapSize[1]]); var topRight = this._transformPt(e, [mapSize[0], 0]); var width = topRight[0] - bottomLeft[0]; var height = topRight[1] - bottomLeft[1]; if (this.get('orientation') === "vertical") { width = Math.round(width * this.get('position')); } else { height = Math.round(height * this.get('position')); bottomLeft[1] += mapSize[1] - height; } ctx.scissor(bottomLeft[0], bottomLeft[1], width, height); } } else { var size = e.frameState.size; ctx.save(); ctx.beginPath(); var pts = [[0,0],[size[0],size[1]]]; if (this.get('orientation') === "vertical") { pts[1] = [ size[0]*.5 + this.getMap().getSize()[0] * (this.get('position') - .5), size[1] ]; } else { pts[1] = [ size[0], size[1]*.5 + this.getMap().getSize()[1] * (this.get('position') - .5) ]; } this._drawRect(e, pts); ctx.clip(); } }; /** @private */ ol.control.Swipe.prototype.precomposeRight = function(e) { var ctx = e.context; if (ctx instanceof WebGLRenderingContext) { if (e.type === 'prerender') { // Clear ctx.clearColor(0, 0, 0, 0); ctx.clear(ctx.COLOR_BUFFER_BIT); // Clip ctx.enable(ctx.SCISSOR_TEST); var mapSize = this.getMap().getSize(); // [width, height] in CSS pixels // get render coordinates and dimensions given CSS coordinates var bottomLeft = this._transformPt(e, [0, mapSize[1]]); var topRight = this._transformPt(e, [mapSize[0], 0]); var width = topRight[0] - bottomLeft[0]; var height = topRight[1] - bottomLeft[1]; if (this.get('orientation') === "vertical") { width = Math.round(width * (1-this.get('position'))); bottomLeft[0] += mapSize[0] - width; } else { height = Math.round(height * (1-this.get('position'))); } ctx.scissor(bottomLeft[0], bottomLeft[1], width, height); } } else { var size = e.frameState.size; ctx.save(); ctx.beginPath(); var pts = [[0,0],[size[0],size[1]]]; if (this.get('orientation') === "vertical") { pts[0] = [ size[0]*.5 + this.getMap().getSize()[0] * (this.get('position') - .5), 0 ]; } else { pts[0] = [ 0, size[1]*.5 + this.getMap().getSize()[1] * (this.get('position') - .5) ] } this._drawRect(e, pts); ctx.clip(); } }; /** @private */ ol.control.Swipe.prototype.postcompose = function(e) { if (e.context instanceof WebGLRenderingContext) { if (e.type === 'postrender') { var gl = e.context; gl.disable(gl.SCISSOR_TEST); } } else { // restore context when decluttering is done (ol>=6) // https://github.com/openlayers/openlayers/issues/10096 if (e.target.getClassName && e.target.getClassName()!=='ol-layer' && e.target.get('declutter')) { setTimeout(function () { e.context.restore(); }, 0); } else { e.context.restore(); } } }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** A control that use a CSS clip rect to swipe the map * @classdesc Swipe Control. * @fires moving * @constructor * @extends {ol.control.Control} * @param {Object=} Control options. * @param {ol.layer} options.layers layer to swipe * @param {ol.layer} options.rightLayer layer to swipe on right side * @param {string} options.className control class name * @param {number} options.position position propertie of the swipe [0,1], default 0.5 * @param {string} options.orientation orientation propertie (vertical|horizontal), default vertical */ ol.control.SwipeMap = function(options) { options = options || {}; var button = document.createElement('button'); var element = document.createElement('div'); element.className = (options.className || "ol-swipe") + " ol-unselectable ol-control"; element.appendChild(button); element.addEventListener("mousedown", this.move.bind(this)); element.addEventListener("touchstart", this.move.bind(this)); ol.control.Control.call(this, { element: element }); this.on('propertychange', function(e) { if (this.get('orientation') === "horizontal") { this.element.style.top = this.get('position')*100+"%"; this.element.style.left = ""; } else { if (this.get('orientation') !== "vertical") this.set('orientation', "vertical"); this.element.style.left = this.get('position')*100+"%"; this.element.style.top = ""; } if (e.key === 'orientation') { this.element.classList.remove("horizontal", "vertical"); this.element.classList.add(this.get('orientation')); } this._clip(); }.bind(this)); this.on('change:active', this._clip.bind(this)); this.set('position', options.position || 0.5); this.set('orientation', options.orientation || 'vertical'); this.set('right', options.right); }; ol.ext.inherits(ol.control.SwipeMap, ol.control.Control); /** Set the map instance the control associated with. * @param {ol.Map} map The map instance. */ ol.control.SwipeMap.prototype.setMap = function(map) { if (this.getMap()) { if (this._listener) ol.Observable.unByKey(this._listener); var layerDiv = this.getMap().getViewport().querySelector('.ol-layers'); layerDiv.style.clip = ''; } ol.control.Control.prototype.setMap.call(this, map); if (map) { this._listener = map.on('change:size', this._clip.bind(this)); } }; /** Clip * @private */ ol.control.SwipeMap.prototype._clip = function() { if (this.getMap()) { var layerDiv = this.getMap().getViewport().querySelector('.ol-layers'); var rect = this.getRectangle(); layerDiv.style.clip = 'rect(' + rect[1]+'px,' // top + rect[2]+'px,' // right + rect[3]+'px,' // bottom + rect[0]+'px' //left + ')'; } }; /** Get visible rectangle * @returns {ol.extent} */ ol.control.SwipeMap.prototype.getRectangle = function() { var s = this.getMap().getSize(); if (this.get('orientation') === 'vertical') { if (this.get('right')) { return [ s[0]*this.get('position'), 0, s[0], s[1]]; } else { return [ 0, 0, s[0]*this.get('position'), s[1]]; } } else { if (this.get('right')) { return [ 0, s[1]*this.get('position'), s[0], s[1]]; } else { return [ 0, 0, s[0], s[1]*this.get('position')]; } } }; /** @private */ ol.control.SwipeMap.prototype.move = function(e) { var self = this; var l; if (!this._movefn) this._movefn = this.move.bind(this); switch (e.type) { case 'touchcancel': case 'touchend': case 'mouseup': { self.isMoving = false; ["mouseup", "mousemove", "touchend", "touchcancel", "touchmove"] .forEach(function(eventName) { document.removeEventListener(eventName, self._movefn); }); break; } case 'mousedown': case 'touchstart': { self.isMoving = true; ["mouseup", "mousemove", "touchend", "touchcancel", "touchmove"] .forEach(function(eventName) { document.addEventListener(eventName, self._movefn); }); } // fallthrough case 'mousemove': case 'touchmove': { if (self.isMoving) { if (self.get('orientation') === 'vertical') { var pageX = e.pageX || (e.touches && e.touches.length && e.touches[0].pageX) || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); if (!pageX) break; pageX -= self.getMap().getTargetElement().getBoundingClientRect().left + window.pageXOffset - document.documentElement.clientLeft; l = self.getMap().getSize()[0]; var w = l - Math.min(Math.max(0, l-pageX), l); l = w/l; self.set('position', l); self.dispatchEvent({ type: 'moving', size: [w, self.getMap().getSize()[1]], position: [l,0] }); } else { var pageY = e.pageY || (e.touches && e.touches.length && e.touches[0].pageY) || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY); if (!pageY) break; pageY -= self.getMap().getTargetElement().getBoundingClientRect().top + window.pageYOffset - document.documentElement.clientTop; l = self.getMap().getSize()[1]; var h = l - Math.min(Math.max(0, l-pageY), l); l = h/l; self.set('position', l); self.dispatchEvent({ type: 'moving', size: [self.getMap().getSize()[0],h], position: [0,l] }); } } break; } default: break; } }; /* 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). */ /** ol.control.Target draw a target at the center of the map. * @constructor * @extends {ol.control.CanvasBase} * @param {Object} options * @param {ol.style.Style|Array} options.style * @param {string} options.composite composite operation = difference|multiply|xor|screen|overlay|darken|lighter|lighten|... */ ol.control.Target = function(options) { options = options || {}; this.style = options.style || [ new ol.style.Style({ image: new ol.style.RegularShape ({ points: 4, radius: 11, radius1: 0, radius2: 0, snapToPixel:true, stroke: new ol.style.Stroke({ color: "#fff", width:3 }) }) }), new ol.style.Style({ image: new ol.style.RegularShape ({ points: 4, radius: 11, radius1: 0, radius2: 0, snapToPixel:true, stroke: new ol.style.Stroke({ color: "#000", width:1 }) }) }) ]; if (!(this.style instanceof Array)) this.style = [this.style]; this.composite = options.composite || ''; var div = document.createElement('div'); div.className = "ol-target ol-unselectable ol-control"; ol.control.CanvasBase.call(this, { element: div, target: options.target }); this.setVisible(options.visible!==false); }; ol.ext.inherits(ol.control.Target, ol.control.CanvasBase); /** Set the control visibility * @paraam {boolean} b */ ol.control.Target.prototype.setVisible = function (b) { this.set("visible",b); if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } }; /** Get the control visibility * @return {boolean} b */ ol.control.Target.prototype.getVisible = function () { return this.get("visible"); }; /** Draw the target * @private */ ol.control.Target.prototype._draw = function (e) { var ctx = this.getContext(e); if (!ctx || !this.getMap() || !this.getVisible()) return; var ratio = e.frameState.pixelRatio; ctx.save(); ctx.scale(ratio,ratio); var cx = ctx.canvas.width/(2*ratio); var cy = ctx.canvas.height/(2*ratio); var geom = new ol.geom.Point (this.getMap().getCoordinateFromPixel([cx,cy])); if (this.composite) ctx.globalCompositeOperation = this.composite; for (var i=0; i} options.features Features to show in the timeline * @param {ol.SourceImageOptions.vector} options.source class of the control * @param {Number} options.interval time interval length in ms or a text with a format d, h, mn, s (31 days = '31d'), default none * @param {String} options.maxWidth width of the time line in px, default 2000px * @param {String} options.minDate minimum date * @param {String} options.maxDate maximum date * @param {Number} options.minZoom Minimum zoom for the line, default .2 * @param {Number} options.maxZoom Maximum zoom for the line, default 4 * @param {boolean} options.zoomButton Are zoom buttons avaliable, default false * @param {function} options.getHTML a function that takes a feature and returns the html to display * @param {function} options.getFeatureDate a function that takes a feature and returns its date, default the date propertie * @param {function} options.endFeatureDate a function that takes a feature and returns its end date, default no end date * @param {String} options.graduation day|month to show month or day graduation, default show only years * @param {String} options.scrollTimeout Time in milliseconds to get a scroll event, default 15ms */ ol.control.Timeline = function(options) { var element = ol.ext.element.create('DIV', { className: (options.className || '') + ' ol-timeline' + (options.target ? '': ' ol-unselectable ol-control') + (options.zoomButton ? ' ol-hasbutton':'') }); // Initialize ol.control.Control.call(this, { element: element, target: options.target }); // Scroll div this._scrollDiv = ol.ext.element.create('DIV', { className: 'ol-scroll', parent: this.element }); // Add a button bar this._buttons = ol.ext.element.create('DIV', { className: 'ol-buttons', parent: this.element }); // Zoom buttons if (options.zoomButton) { // Zoom in this.addButton({ className: 'ol-zoom-in', handleClick: function(){ var zoom = this.get('zoom'); if (zoom>=1) { zoom++; } else { zoom = Math.min(1, zoom + 0.1); } zoom = Math.round(zoom*100)/100; this.refresh(zoom); }.bind(this) }); // Zoom out this.addButton({ className: 'ol-zoom-out', handleClick: function(){ var zoom = this.get('zoom'); if (zoom>1) { zoom--; } else { zoom -= 0.1; } zoom = Math.round(zoom*100)/100; this.refresh(zoom); }.bind(this) }); } // Draw center date this._intervalDiv = ol.ext.element.create('DIV', { className: 'ol-center-date', parent: this.element }); // Remove selection this.element.addEventListener('mouseover', function(){ if (this._select) this._select.elt.classList.remove('ol-select'); }.bind(this)); // Trigger scroll event var scrollListener = null; this._scrollDiv.addEventListener('scroll', function() { this._setScrollLeft(); if (scrollListener) { clearTimeout(scrollListener); scrollListener = null; } scrollListener = setTimeout(function() { this.dispatchEvent({ type: 'scroll', date: this.getDate(), dateStart: this.getDate('start'), dateEnd: this.getDate('end') }); }.bind(this), options.scrollTimeout || 15); }.bind(this)); // Magic to give "live" scroll events on touch devices // this._scrollDiv.addEventListener('gesturechange', function() {}); // Scroll timeline ol.ext.element.scrollDiv(this._scrollDiv, { onmove: function(b) { // Prevent selection on moving this._moving = b; }.bind(this) }); this._tline = []; // Parameters this._scrollLeft = 0; this.set('maxWidth', options.maxWidth || 2000); this.set('minDate', options.minDate || Infinity); this.set('maxDate', options.maxDate || -Infinity); this.set('graduation', options.graduation); this.set('minZoom', options.minZoom || .2); this.set('maxZoom', options.maxZoom || 4); this.setInterval(options.interval); if (options.getHTML) this._getHTML = options.getHTML; if (options.getFeatureDate) this._getFeatureDate = options.getFeatureDate; if (options.endFeatureDate) this._endFeatureDate = options.endFeatureDate; // Feature source this.setFeatures(options.features || options.source, options.zoom); }; ol.ext.inherits(ol.control.Timeline, ol.control.Control); /** * Set the map instance the control is associated with * and add interaction attached to it to this map. * @param {_ol_Map_} map The map instance. */ ol.control.Timeline.prototype.setMap = function(map) { ol.control.Control.prototype.setMap.call(this, map); this.refresh(this.get('zoom')||1, true); }; /** Add a button on the timeline * @param {*} button * @param {string} button.className * @param {title} button.className * @param {Element|string} button.html Content of the element * @param {function} button.click a function called when the button is clicked */ ol.control.Timeline.prototype.addButton = function(button) { this.element.classList.add('ol-hasbutton'); ol.ext.element.create('BUTTON', { className: button.className || undefined, title: button.title, html : button.html, click: button.handleClick, parent: this._buttons }) }; /** Set an interval * @param {number|string} length the interval length in ms or a farmatted text ie. end with y, 1d, h, mn, s (31 days = '31d'), default none */ ol.control.Timeline.prototype.setInterval = function(length) { if (typeof(length)==='string') { if (/s$/.test(length)) { length = parseFloat(length) * 1000; } else if (/mn$/.test(length)) { length = parseFloat(length) * 1000 * 60; } else if (/h$/.test(length)) { length = parseFloat(length) * 1000 * 3600; } else if (/d$/.test(length)) { length = parseFloat(length) * 1000 * 3600 * 24; } else if (/y$/.test(length)) { length = parseFloat(length) * 1000 * 3600 * 24 * 365; } else { length = 0; } } this.set('interval', length || 0); if (length) this.element.classList.add('ol-interval'); else this.element.classList.remove('ol-interval'); this.refresh(this.get('zoom')); } /** Default html to show in the line * @param {ol.Feature} feature * @return {DOMElement|string} * @private */ ol.control.Timeline.prototype._getHTML = function(feature) { return feature.get('name') || ''; }; /** Default function to get the date of a feature, returns the date attribute * @param {ol.Feature} feature * @return {Data|string} * @private */ ol.control.Timeline.prototype._getFeatureDate = function(feature) { return (feature && feature.get) ? feature.get('date') : null; }; /** Default function to get the end date of a feature, return undefined * @param {ol.Feature} feature * @return {Data|string} * @private */ ol.control.Timeline.prototype._endFeatureDate = function(/* feature */) { return undefined; }; /** Is the line collapsed * @return {boolean} */ ol.control.Timeline.prototype.isCollapsed = function() { return this.element.classList.contains('ol-collapsed'); }; /** Collapse the line * @param {boolean} b */ ol.control.Timeline.prototype.collapse = function(b) { if (b) this.element.classList.add('ol-collapsed'); else this.element.classList.remove('ol-collapsed'); this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() }); }; /** Collapse the line */ ol.control.Timeline.prototype.toggle = function() { this.element.classList.toggle('ol-collapsed'); this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() }); }; /** Set the features to display in the timeline * @param {Array|ol.source.Vector} features An array of features or a vector source * @param {number} zoom zoom to draw the line default 1 */ ol.control.Timeline.prototype.setFeatures = function(features, zoom) { this._features = this._source = null; if (features instanceof ol.source.Vector) this._source = features; else if (features instanceof Array) this._features = features; else this._features = []; this.refresh(zoom); }; /** * Get features * @return {Array} */ ol.control.Timeline.prototype.getFeatures = function() { return this._features || this._source.getFeatures(); } /** * Refresh the timeline with new data * @param {Number} zoom Zoom factor from 0.25 to 10, default 1 */ ol.control.Timeline.prototype.refresh = function(zoom, first) { if (!this.getMap()) return; if (!zoom) zoom = this.get('zoom'); zoom = Math.min(this.get('maxZoom'), Math.max(this.get('minZoom'), zoom || 1)); this.set('zoom', zoom); this._scrollDiv.innerHTML = ''; var features = this.getFeatures(); var d, d2; // Get features sorted by date var tline = this._tline = []; features.forEach(function(f) { if (d = this._getFeatureDate(f)) { if (!(d instanceof Date)) { d = new Date(d) } if (this._endFeatureDate) { d2 = this._endFeatureDate(f); if (!(d2 instanceof Date)) { d2 = new Date(d2) } } if (!isNaN(d)) { tline.push({ date: d, end: isNaN(d2) ? null : d2, feature: f }); } } }.bind(this)); tline.sort(function(a,b) { return (a.date < b.date ? -1 : (a.date===b.date ? 0: 1)) }); // Draw var div = ol.ext.element.create('DIV', { parent: this._scrollDiv }); // Calculate width var min = this._minDate = Math.min(this.get('minDate'), tline.length ? tline[0].date : Infinity); var max = this._maxDate = Math.max(this.get('maxDate'), tline.length ? tline[tline.length-1].date : -Infinity); if (!isFinite(min)) this._minDate = min = new Date(); if (!isFinite(max)) this._maxDate = max = new Date(); var delta = (max-min); var maxWidth = this.get('maxWidth'); var scale = this._scale = (delta > maxWidth ? maxWidth/delta : 1) * zoom; // Leave 10px on right min = this._minDate = this._minDate - 10/scale; delta = (max-min) * scale; ol.ext.element.setStyle(div, { width: delta, maxWidth: 'unset' }); // Draw time's bar this._drawTime(div, min, max, scale); // Set interval if (this.get('interval')) { ol.ext.element.setStyle (this._intervalDiv, { width: this.get('interval') * scale }); } else { ol.ext.element.setStyle (this._intervalDiv, { width: '' }); } // Draw features var line = []; var lineHeight = ol.ext.element.getStyle(this._scrollDiv, 'lineHeight'); // Wrapper var fdiv = ol.ext.element.create('DIV', { className: 'ol-features', parent: div }); // Add features on the line tline.forEach(function(f) { var d = f.date; var t = f.elt = ol.ext.element.create('DIV', { className: 'ol-feature', style: { left: Math.round((d-min)*scale), }, html: this._getHTML(f.feature), parent: fdiv }); // Prevent image dragging var img = t.querySelectorAll('img'); for (var i=0; i l) { break; } } line[pos] = left + ol.ext.element.getStyle(t, 'width'); ol.ext.element.setStyle(t, { top: pos*lineHeight }); }.bind(this)); this._nbline = line.length; if (first) this.setDate(this._minDate, { anim: false, position: 'start' }); // Dispatch scroll event this.dispatchEvent({ type: 'scroll', date: this.getDate(), dateStart: this.getDate('start'), dateEnd: this.getDate('end') }); }; /** Get offset given a date * @param {Date} date * @return {number} * @private */ ol.control.Timeline.prototype._getOffsetFromDate = function(date) { return (date - this._minDate) * this._scale; }; /** Get date given an offset * @param {Date} date * @return {number} * @private */ ol.control.Timeline.prototype._getDateFromOffset = function(offset) { return offset / this._scale + this._minDate; }; /** Set the current position * @param {number} scrollLeft current position (undefined when scrolling) * @returns {number} * @private */ ol.control.Timeline.prototype._setScrollLeft = function(scrollLeft) { this._scrollLeft = scrollLeft; if (scrollLeft !== undefined) { this._scrollDiv.scrollLeft = scrollLeft; } }; /** Get the current position * @returns {number} * @private */ ol.control.Timeline.prototype._getScrollLeft = function() { // Unset when scrolling if (this._scrollLeft === undefined) { return this._scrollDiv.scrollLeft; } else { // St by user return this._scrollLeft; } }; /** * Draw dates on line * @private */ ol.control.Timeline.prototype._drawTime = function(div, min, max, scale) { // Times div var tdiv = ol.ext.element.create('DIV', { className: 'ol-times', parent: div }); var d, dt, month, dmonth; var dx = ol.ext.element.getStyle(tdiv, 'left'); var heigth = ol.ext.element.getStyle(tdiv, 'height'); // Year var year = (new Date(this._minDate)).getFullYear(); dt = ((new Date(0)).setFullYear(String(year)) - new Date(0).setFullYear(String(year-1))) * scale; var dyear = Math.round(2*heigth/dt)+1; while(true) { d = new Date(0).setFullYear(year); if (d > this._maxDate) break; ol.ext.element.create('DIV', { className: 'ol-time ol-year', style: { left: this._getOffsetFromDate(d) - dx }, html: year, parent: tdiv }); year += dyear; } // Month if (/day|month/.test(this.get('graduation'))) { dt = ((new Date(0,0,1)).setFullYear(String(year)) - new Date(0,0,1).setFullYear(String(year-1))) * scale; dmonth = Math.max(1, Math.round(12 / Math.round(dt/heigth/2))); if (dmonth < 12) { year = (new Date(this._minDate)).getFullYear(); month = dmonth+1; while(true) { d = new Date(0,0,1); d.setFullYear(year); d.setMonth(month-1); if (d > this._maxDate) break; ol.ext.element.create('DIV', { className: 'ol-time ol-month', style: { left: this._getOffsetFromDate(d) - dx }, html: d.toLocaleDateString(undefined, { month: 'short'}), parent: tdiv }); month += dmonth; if (month > 12) { year++; month = dmonth+1; } } } } // Day if (this.get('graduation')==='day') { dt = (new Date(0,1,1) - new Date(0,0,1)) * scale; var dday = Math.max(1, Math.round(31 / Math.round(dt/heigth/2))); if (dday < 31) { year = (new Date(this._minDate)).getFullYear(); month = 0; var day = dday; while(true) { d = new Date(0,0,1); d.setFullYear(year); d.setMonth(month); d.setDate(day); if (isNaN(d)) { month++; if (month>12) { month = 1; year++; } day = dday; } else { if (d > this._maxDate) break; if (day>1) { var offdate = this._getOffsetFromDate(d); if (this._getOffsetFromDate(new Date(year, month+1, 1)) - offdate > heigth) { ol.ext.element.create('DIV', { className: 'ol-time ol-day', style: { left: offdate - dx }, html: day, parent: tdiv }); } } year = d.getFullYear(); month = d.getMonth(); day = d.getDate() + dday; if (day > new Date(year, month+1, 0).getDate()) { month++; day = dday; } } } } } }; /** Center timeline on a date * @param {Date|String|ol.feature} feature a date or a feature with a date * @param {Object} options * @param {boolean} options.anim animate scroll * @param {string} options.position start, end or middle, default middle */ ol.control.Timeline.prototype.setDate = function(feature, options) { var date; options = options || {}; // It's a date if (feature instanceof Date) { date = feature; } else { // Get date from Feature if (this.getFeatures().indexOf(feature) >= 0) { date = this._getFeatureDate(feature); } if (date && !(date instanceof Date)) { date = new Date(date); } if (!date || isNaN(date)) { date = new Date(String(feature)); } } if (!isNaN(date)) { if (options.anim === false) this._scrollDiv.classList.add('ol-move'); var scrollLeft = this._getOffsetFromDate(date); if (options.position==='start') { scrollLeft += ol.ext.element.outerWidth(this._scrollDiv)/2 - ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; } else if (options.position==='end') { scrollLeft -= ol.ext.element.outerWidth(this._scrollDiv)/2 - ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; } this._setScrollLeft(scrollLeft); if (options.anim === false) this._scrollDiv.classList.remove('ol-move'); if (feature) { for (var i=0, f; f = this._tline[i]; i++) { if (f.feature === feature) { f.elt.classList.add('ol-select'); this._select = f; } else { f.elt.classList.remove('ol-select'); } } } } }; /** Get round date (sticked to mn, hour day or month) * @param {Date} d * @param {string} stick sticking option to stick date to: 'mn', 'hour', 'day', 'month', default no stick * @return {Date} */ ol.control.Timeline.prototype.roundDate = function(d, stick) { switch (stick) { case 'mn': { return new Date(this._roundTo(d, 60*1000)); } case 'hour': { return new Date(this._roundTo(d, 60*60*1000)); } case 'day': { return new Date(this._roundTo(d, 24*60*60*1000)); } case 'month': { d = new Date(this._roundTo(d, 24*60*60*1000)); if (d.getDate() > 15) { d = new Date(d.setMonth(d.getMonth() + 1)); } d = d.setDate(1); return new Date(d); } default: return new Date(d); } }; /** Get the date of the center * @param {string} position position to get 'start', 'end' or 'middle', default middle * @param {string} stick sticking option to stick date to: 'mn', 'hour', 'day', 'month', default no stick * @return {Date} */ ol.control.Timeline.prototype.getDate = function(position, stick) { var pos; if (!stick) stick = position; switch (position) { case 'start': { if (this.get('interval')) { pos = - ol.ext.element.getStyle(this._intervalDiv, 'width')/2 + ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; } else { pos = - ol.ext.element.outerWidth(this._scrollDiv)/2 + ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; } break; } case 'end': { if (this.get('interval')) { pos = ol.ext.element.getStyle(this._intervalDiv, 'width')/2 - ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; } else { pos = ol.ext.element.outerWidth(this._scrollDiv)/2 - ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; } break; } default: { pos = 0; break; } } var d = this._getDateFromOffset(this._getScrollLeft() + pos); d = this.roundDate(d, stick); return new Date(d); }; /** Round number to * @param {number} d * @param {number} r * @return {number} * @private */ ol.control.Timeline.prototype._roundTo = function(d, r) { return Math.round(d/r) * r; }; /** Get the start date of the control * @return {Date} */ ol.control.Timeline.prototype.getStartDate = function() { return new Date(this.get('minDate')); } /** Get the end date of the control * @return {Date} */ ol.control.Timeline.prototype.getEndDate = function() { return new Date(this.get('maxDate')); } /* 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). */ /** Record map canvas as video * @constructor * @fire start * @fire error * @fire stop * @fire pause * @fire resume * @extends {ol.control.Control} * @param {Object=} options Control options. * @param {String} options.className class of the control * @param {number} [options.framerate=30] framerate for the video * @param {number} [options.videoBitsPerSecond=5000000] bitrate for the video * @param {DOMElement|string} [options.videoTarget] video element or the container to add the video when finished or 'DIALOG' to show it in a dialog, default none */ ol.control.VideoRecorder = function(options) { if (!options) options = {}; var element = ol.ext.element.create('DIV', { className: (options.className || 'ol-videorec') + ' ol-unselectable ol-control' }); // buttons ol.ext.element.create('BUTTON', { type: 'button', className: 'ol-start', title: 'start', click: function() { this.start(); }.bind(this), parent: element }); ol.ext.element.create('BUTTON', { type: 'button', className: 'ol-stop', title: 'stop', click: function() { this.stop(); }.bind(this), parent: element }); ol.ext.element.create('BUTTON', { type: 'button', className: 'ol-pause', title: 'pause', click: function() { this.pause(); }.bind(this), parent: element }); ol.ext.element.create('BUTTON', { type: 'button', className: 'ol-resume', title: 'resume', click: function() { this.resume(); }.bind(this), parent: element }); // Start ol.control.Control.call(this, { element: element, target: options.target }); this.set('framerate', 30); this.set('videoBitsPerSecond', 5000000); if (options.videoTarget === 'DIALOG') { this._dialog = new ol.control.Dialog({ className: 'ol-fullscreen-dialog', target: document.body, closeBox: true }); this._videoTarget = this._dialog.getContentElement(); } else { this._videoTarget = options.videoTarget; } // Print control this._printCtrl = new ol.control.Print({ target: ol.ext.element.create('DIV') }); } ol.ext.inherits(ol.control.VideoRecorder, 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 {ol.Map} map Map. * @api stable */ ol.control.VideoRecorder.prototype.setMap = function (map) { if (this.getMap()) { this.getMap().removeControl(this._printCtrl); if (this._dialog) this.getMap().removeControl(this._dialog); } ol.control.Control.prototype.setMap.call(this, map); if (this.getMap()) { this.getMap().addControl(this._printCtrl); if (this._dialog) this.getMap().addControl(this._dialog); } }; /** Start recording */ ol.control.VideoRecorder.prototype.start = function () { var print = this._printCtrl; var stop = false; function capture(canvas) { if (stop) return; print.fastPrint({ canvas: canvas }, capture); } print.fastPrint({}, function(canvas) { var videoStream; try { videoStream = canvas.captureStream(this.get('framerate') || 30); } catch(e) { this.dispatchEvent({ type: 'error', error: e }); // console.warn(e); return; } this._mediaRecorder = new MediaRecorder(videoStream, { videoBitsPerSecond : this.get('videoBitsPerSecond') || 5000000 }); var chunks = []; this._mediaRecorder.ondataavailable = function(e) { chunks.push(e.data); }; this._mediaRecorder.onstop = function() { stop = true; var blob = new Blob(chunks, { 'type' : 'video/mp4' }); // other types are available such as 'video/webm' for instance, see the doc for more info chunks = []; if (this._videoTarget instanceof Element) { var video; if (this._videoTarget.tagName === 'VIDEO') { video = this._videoTarget; } else { video = this._videoTarget.querySelector('video'); if (!video) { video = ol.ext.element.create('VIDEO', { controls: '', parent: this._videoTarget }); } } if (this._dialog) this._dialog.show(); video.src = URL.createObjectURL(blob); this.dispatchEvent({ type: 'stop', videoURL: video.src }); } else { this.dispatchEvent({ type: 'stop', videoURL: URL.createObjectURL(blob) }); } }.bind(this); this._mediaRecorder.onpause = function() { stop = true; this.dispatchEvent({ type: 'pause' }); }.bind(this); this._mediaRecorder.onresume = function() { stop = false; capture(canvas); this.dispatchEvent({ type: 'resume' }); }.bind(this); this._mediaRecorder.onerror = function(e) { this.dispatchEvent({ type: 'error', error: e }); }.bind(this); stop = false; capture(canvas); this._mediaRecorder.start(); this.dispatchEvent({ type: 'start', canvas: canvas }); this.element.setAttribute('data-state', 'rec'); }.bind(this)) }; /** Stop recording */ ol.control.VideoRecorder.prototype.stop = function () { if (this._mediaRecorder) { this._mediaRecorder.stop(); this._mediaRecorder = null; this.element.setAttribute('data-state', 'inactive'); } }; /** Pause recording */ ol.control.VideoRecorder.prototype.pause = function () { if (this._mediaRecorder) { this._mediaRecorder.pause(); this.element.setAttribute('data-state', 'pause'); } }; /** Resume recording after pause */ ol.control.VideoRecorder.prototype.resume = function () { if (this._mediaRecorder) { this._mediaRecorder.resume(); this.element.setAttribute('data-state', 'rec'); } }; /* Using WMS Layer with EPSG:4326 projection. The tiles will be reprojected to map pojection (EPSG:3857). NB: reduce tileSize to minimize deformations on small scales. */ /** WMSCapabilities * @constructor * @fires load * @fires capabilities * @extends {ol.control.Button} * @param {*} options * @param {string|Element} options.target the target to set the dialog, use document.body to have fullwindow dialog * @param {string} options.proxy proxy to use when requesting Getcapabilites, default none (suppose the service use CORS) * @param {string} options.placeholder input placeholder, default 'service url...' * @param {string} options.title dialog title, default 'WMS' * @param {string} options.searchLabel Label for search button, default 'search' * @param {string} options.loadLabel Label for load button, default 'load' * @param {boolean} options.popupLayer Use a popup for the layers, default false * @param {*} options.services a key/url object of services for quick access in a menu * @param {Array} options.srs an array of supported srs, default map projection code or 'EPSG:3857' * @param {number} options.timeout Timeout for getCapabilities request, default 1000 * @param {boolean} options.cors Use CORS, default false * @param {boolean} options.trace Log layer info, default false * @param {function} [options.onselect] callback function that takes a layer and layer options on select layer */ ol.control.WMSCapabilities = function (options) { options = options || {}; var buttonOptions = Object.assign({}, options || {}); this._proxy = options.proxy; if (buttonOptions.target===document.body) delete buttonOptions.target; if (buttonOptions.target) { buttonOptions.className = ((buttonOptions.className||'') + ' ol-wmscapabilities ol-hidden').trim(); delete buttonOptions.target; } else { buttonOptions.className = ((buttonOptions.className||'') + ' ol-wmscapabilities').trim(); buttonOptions.handleClick = function () { this.showDialog(); }.bind(this) } ol.control.Button.call(this, buttonOptions); // WMS options this.set('srs', options.srs || []); this.set('cors', options.cors); this.set('trace', options.trace); this.set('title', options.title); this.set('loadLabel', options.loadLabel); // Dialog this.createDialog(options); // Default version this._elements.formVersion.value = '1.0.0'; // Ajax request var parser = this._getParser(); this._ajax = new ol.ext.Ajax({ dataType:'text', auth: options.authentication }); this._ajax.on('success', function (evt) { var caps; try { caps = parser.read(evt.response); } catch (e) { this.showError({ type: 'load', error: e }); } if (caps) { if (!caps.Capability.Layer.Layer) { this.showError({ type: 'noLayer' }); } else { this.showCapabilities(caps); } } this.dispatchEvent({ type: 'capabilities', capabilities: caps }); if (typeof(evt.options.callback) === 'function') evt.options.callback(caps); }.bind(this)); this._ajax.on('error', function(evt) { this.showError({ type: 'load', error: evt }); this.dispatchEvent({ type: 'capabilities' }); if (typeof(evt.options.callback) === 'function') false; }.bind(this)); // Handle waiting this._ajax.on('loadstart', function() { this._elements.element.classList.add('ol-searching'); }.bind(this)); this._ajax.on('loadend', function() { this._elements.element.classList.remove('ol-searching'); }.bind(this)); // Load a layer if (options.onselect) { this.on('load', function(e) { options.onselect(e.layer, e.options); }); } }; ol.ext.inherits(ol.control.WMSCapabilities, ol.control.Button); /** Get service parser */ ol.control.WMSCapabilities.prototype._getParser = function() { return new ol.format.WMSCapabilities(); }; /** Error list: a key/value list of error to display in the dialog * Overwrite it to handle internationalization */ ol.control.WMSCapabilities.prototype.error = { load: 'Can\'t retrieve service capabilities, try to add it manually...', badUrl: 'The input value is not a valid url...', TileMatrix: 'No TileMatrixSet supported...', noLayer: 'No layer available for this service...', srs: 'The service projection looks different from that of your map, it may not display correctly...' }; /** Form labels: a key/value list of form labels to display in the dialog * Overwrite it to handle internationalization */ ol.control.WMSCapabilities.prototype.labels = { formTitle: 'Title:', formLayer: 'Layers:', formMap: 'Map:', formStyle: 'Style:', formFormat: 'Format:', formMinZoom: 'Min zoom level:', formMaxZoom: 'Max zoom level:', formExtent: 'Extent:', mapExtent: 'use map extent...', formProjection: 'Projection:', formCrossOrigin: 'CrossOrigin:', formVersion: 'Version:', formAttribution: 'Attribution:' }; /** Create dialog * @private */ ol.control.WMSCapabilities.prototype.createDialog = function (options) { var target = options.target; if (!target || target===document.body) { this._dialog = new ol.control.Dialog({ className: 'ol-wmscapabilities', closeBox: true, closeOnSubmit: false, target: options.target }); this._dialog.on('button', function(e) { if (e.button==='submit') { this.getCapabilities(e.inputs.url.value); } }.bind(this)); target = null; } var element = ol.ext.element.create('DIV', { className: ('ol-wmscapabilities '+(options.className||'')).trim(), parent: target }); this._elements = { element: target || element }; var inputdiv = ol.ext.element.create('DIV', { className: 'ol-url', parent: element }); var input = this._elements.input = ol.ext.element.create('INPUT', { className: 'url', type: 'text', tabIndex: 1, placeholder: options.placeholder || 'service url...', autocorrect: 'off', autocapitalize: 'off', parent: inputdiv }); input.addEventListener('keyup', function(e) { if (e.keyCode===13) { this.getCapabilities(input.value, options); } }.bind(this)); if (options.services) { var qaccess = ol.ext.element.create('SELECT', { className: 'url', on: { change: function(e) { var url = e.target.options[e.target.selectedIndex].value; this.getCapabilities(url, options); e.target.selectedIndex = 0; }.bind(this) }, parent: inputdiv }); ol.ext.element.create('OPTION', { html: ' ', parent: qaccess }); for (var k in options.services) { ol.ext.element.create('OPTION', { html: k, value: options.services[k], parent: qaccess }); } } ol.ext.element.create('BUTTON', { click: function() { this.getCapabilities(input.value, options); }.bind(this), html: options.searchLabel || 'search', parent: inputdiv }); // Errors this._elements.error = ol.ext.element.create('DIV', { className: 'ol-error', parent: inputdiv }); // Result div var rdiv = this._elements.result = ol.ext.element.create('DIV', { className: 'ol-result', parent: element }); // Preview var preview = ol.ext.element.create('DIV', { className: 'ol-preview', html: options.previewLabel || 'preview', parent: rdiv }); this._elements.preview = ol.ext.element.create('IMG', { parent: preview }); // Check tainted canvas this._img = new Image; this._img.crossOrigin = 'Anonymous'; this._img.addEventListener('error', function() { preview.className = 'ol-preview tainted'; this._elements.formCrossOrigin.checked = false; }.bind(this)); this._img.addEventListener('load', function() { preview.className = 'ol-preview ok'; this._elements.formCrossOrigin.checked = true; }.bind(this)); // Select list this._elements.select = ol.ext.element.create('DIV', { className: 'ol-select-list', tabIndex: 2, parent: rdiv }); // Info data this._elements.data = ol.ext.element.create('DIV', { className: 'ol-data', parent: rdiv }); this._elements.buttons = ol.ext.element.create('DIV', { className: 'ol-buttons', parent: rdiv }); this._elements.legend = ol.ext.element.create('IMG', { className: 'ol-legend', parent: rdiv }); // WMS form var form = this._elements.form = ol.ext.element.create('UL', { className: 'ol-wmsform', parent: element }); var addLine = function(label, val, pholder) { var li = ol.ext.element.create('LI', { parent: form }); ol.ext.element.create('LABEL', { html: this.labels[label], parent: li }); if (typeof(val) === 'boolean') { this._elements[label] = ol.ext.element.create('INPUT', { type: 'checkbox', checked: val, parent: li }); } else if (val instanceof Array) { var sel = this._elements[label] = ol.ext.element.create('SELECT', { parent: li }); val.forEach(function(v) { ol.ext.element.create('OPTION', { html: v, value: v, parent: sel }); }.bind(this)); } else { this._elements[label] = ol.ext.element.create('INPUT', { value: (val===undefined ? '' : val), placeholder: pholder || '', type: typeof(val), parent: li }); } return li; }.bind(this); addLine('formTitle'); addLine('formLayer', '', 'layer1,layer2,...'); var li = addLine('formMap'); li.setAttribute('data-param', 'map'); li = addLine('formStyle'); li.setAttribute('data-param', 'style'); addLine('formFormat', ['image/png', 'image/jpeg']); addLine('formMinZoom', 0); addLine('formMaxZoom', 20); li = addLine('formExtent', '', 'xmin,ymin,xmax,ymax'); li.setAttribute('data-param', 'extent'); var extent = li.querySelector('input'); ol.ext.element.create('BUTTON', { title: this.labels.mapExtent, click: function() { extent.value = this.getMap().getView().calculateExtent(this.getMap().getSize()).join(','); }.bind(this), parent: li }); li = addLine('formProjection', ''); li.setAttribute('data-param', 'proj'); addLine('formCrossOrigin', false); li = addLine('formVersion', '1.3.0'); li.setAttribute('data-param', 'version'); addLine('formAttribution', ''); ol.ext.element.create('BUTTON', { html: this.get('loadLabel') || 'Load', click: function() { var opt = this._getFormOptions(); var layer = this.getLayerFromOptions(opt); this.dispatchEvent({ type: 'load', layer: layer, options: opt }); this._dialog.hide(); }.bind(this), parent: form }); return element; }; /** Create a new layer using options received by getOptionsFromCap method * @param {*} options */ ol.control.WMSCapabilities.prototype.getLayerFromOptions = function (options) { options.layer.source = new ol.source.TileWMS(options.source); var layer = new ol.layer.Tile(options.layer); delete options.layer.source; return layer; }; /** * Set the map instance the control is associated with * and add its controls associated to this map. * @param {_ol_Map_} map The map instance. */ ol.control.WMSCapabilities.prototype.setMap = function (map) { ol.control.Button.prototype.setMap.call(this, map); if (this._dialog) this._dialog.setMap(map); }; /** Get the dialog * @returns {ol.control.Dialog} */ ol.control.WMSCapabilities.prototype.getDialog = function() { return this._dialog; }; /** Show dialog for url * @param {string} [url] service url, default ask for an url * @param {*} options capabilities options * @param {string} options.map WMS map or get map in url?map=xxx * @param {string} options.version WMS version (yet only 1.3.0 is implemented), default 1.3.0 * @param {number} options.timeout timout to get the capabilities, default 10000 */ ol.control.WMSCapabilities.prototype.showDialog = function(url, options) { this.showError(); if (!this._elements.formProjection.value) { this._elements.formProjection.value = this.getMap().getView().getProjection().getCode(); } if (this._dialog) { this._dialog.show({ title: this.get('title')===undefined ? 'WMS' : this.get('title'), content: this._elements.element }); } this.getCapabilities(url, options); // Center on selection var sel = this._elements.select.querySelector('.selected'); if (sel) { this._elements.select.scrollTop = sel.offsetTop - 20; } }; /** Test url and return true if it is a valid url string * @param {string} url * @return {bolean} * @api */ ol.control.WMSCapabilities.prototype.testUrl = function(url) { // var pattern = /(https?:\/\/)([\da-z.-]+)\.([a-z]{2,6})([/\w.-]*)*\/?/ var pattern = new RegExp( // protocol '^(https?:\\/\\/)'+ // domain name '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // OR ip (v4) address '((\\d{1,3}\\.){3}\\d{1,3}))'+ // port and path '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // query string '(\\?[;&a-z\\d%_.~+=\\/-]*)?'+ // fragment locator '(\\#[-a-z\\d_]*)?$','i'); return !!pattern.test(url); }; /** Get Capabilities request parameters * @param {*} options */ ol.control.WMSCapabilities.prototype.getRequestParam = function(options) { return { SERVICE: 'WMS', REQUEST: 'GetCapabilities', VERSION: options.version || '1.3.0' } }; /** Get WMS capabilities for a server * @fire load * @param {string} url service url * @param {*} options * @param {string} options.map WMS map or get map in url?map=xxx * @param {string} [options.version=1.3.0] WMS version (yet only 1.3.0 is implemented), default 1.3.0 * @param {number} [options.timeout=10000] timout to get the capabilities, default 10000 * @param {function} [options.onload] callback function */ ol.control.WMSCapabilities.prototype.getCapabilities = function(url, options) { if (!url) return; if (!this.testUrl(url)) { this.showError({ type: 'badUrl' }) return; } options = options || {}; // Extract map attributes url = url.split('?'); var search = url[1]; url = url[0]; // reset this._elements.formMap.value = ''; this._elements.formLayer.value = ''; this._elements.formStyle.value = ''; this._elements.formTitle.value = ''; this._elements.formProjection.value = this.getMap().getView().getProjection().getCode(); this._elements.formFormat.selectedIndex = 0; var map = options.map || ''; if (search) { search = search.replace(/^\?/,'').split('&'); search.forEach(function(s) { s = s.split('='); s[1] = decodeURIComponent(s[1] || ''); if (/^map$/i.test(s[0])) { map = s[1]; this._elements.formMap.value = map; } if (/^layers$/i.test(s[0])) { this._elements.formLayer.value = s[1]; this._elements.formTitle.value = s[1].split(',')[0]; } if (/^style$/i.test(s[0])) { this._elements.formStyle.value = s[1]; } if (/^crs$/i.test(s[0])) { this._elements.formProjection.value = s[1]; } if (/^format$/i.test(s[0])) { for (var o,i=0; o=this._elements.formFormat.options[i]; i++) { if (o.value===s[1]) { this._elements.formFormat.selectedIndex = i; break; } } } }.bind(this)) } // Fill form this._elements.input.value = (url || '') + (map ? '?map='+map : ''); this.clearForm(); var request = this.getRequestParam(options); if (map) request.MAP = map; if (this._proxy) { var q = ''; for (var r in request) q += (q?'&':'')+r+'='+request[r]; this._ajax.send(this._proxy, { url: q }, { timeout: options.timeout || 10000, callback: options.onload, abort: false }); } else { this._ajax.send(url, request, { timeout: options.timeout || 10000, callback: options.onload, abort: false }); } }; /** Display error * @param {*} error event */ ol.control.WMSCapabilities.prototype.showError = function(e) { if (!e) this._elements.error.innerHTML = ''; else this._elements.error.innerHTML = this.error[e.type] || ('ERROR ('+e.type+')'); if (e && e.type === 'load') { this._elements.form.classList.add('visible'); } else { this._elements.form.classList.remove('visible'); } }; /** Clear form */ ol.control.WMSCapabilities.prototype.clearForm = function() { this._elements.result.classList.remove('ol-visible') this.showError(); this._elements.select.innerHTML = ''; this._elements.data.innerHTML = ''; this._elements.preview.src = ''; this._elements.legend.src = ''; this._elements.legend.classList.remove('visible'); }; /** Display capabilities in the dialog * @param {*} caps JSON capabilities */ ol.control.WMSCapabilities.prototype.showCapabilities = function(caps) { console.log(caps) this._elements.result.classList.add('ol-visible') // console.log(caps) var list = []; var addLayers = function(parent, level) { level = level || 0; parent.Layer.forEach(function(l) { if (!l.Attribution) l.Attribution = parent.Attribution; if (!l.EX_GeographicBoundingBox) l.EX_GeographicBoundingBox = parent.EX_GeographicBoundingBox; var li = ol.ext.element.create('DIV', { className: (l.Layer ? 'ol-title ' : '') + 'level-'+level, html: l.Name || l.Title, click: function() { // Reset this._elements.buttons.innerHTML = ''; this._elements.data.innerHTML = ''; this._elements.legend.src = this._elements.preview.src = ''; this._elements.element.classList.remove('ol-form'); this.showError(); // Load layer var options = this.getOptionsFromCap(l, caps); var layer = this.getLayerFromOptions(options); this._currentOptions = options; // list.forEach(function(i) { i.classList.remove('selected'); }) li.classList.add('selected'); // Fill form if (layer) { ol.ext.element.create('BUTTON', { html: this.get('loadLabel') || 'Load', className: 'ol-load', click: function() { this.dispatchEvent({type: 'load', layer: layer, options: options }); if (this._dialog) this._dialog.hide(); }.bind(this), parent: this._elements.buttons }); ol.ext.element.create('BUTTON', { className: 'ol-wmsform', click: function() { this._elements.element.classList.toggle('ol-form'); }.bind(this), parent: this._elements.buttons }); // Show preview var reso = this.getMap().getView().getResolution(); var center = this.getMap().getView().getCenter(); this._elements.preview.src = layer.getPreview(center, reso, this.getMap().getView().getProjection()); this._img.src = this._elements.preview.src; // ShowInfo ol.ext.element.create('p', { className: 'ol-title', html: options.data.title, parent: this._elements.data }); ol.ext.element.create('p', { html: options.data.abstract, parent: this._elements.data }); if (options.data.legend.length) { this._elements.legend.src = options.data.legend[0]; this._elements.legend.classList.add('visible'); } else { this._elements.legend.src = ''; this._elements.legend.classList.remove('visible'); } } }.bind(this), parent: this._elements.select }); list.push(li); if (l.Layer) { addLayers(l, level+1); } }.bind(this)); }.bind(this); // Show layers this._elements.select.innerHTML = ''; addLayers(caps.Capability.Layer); }; /** Get resolution for a layer * @param {string} 'min' or 'max' * @param {*} layer * @param {number} val * @return {number} * @private */ ol.control.WMSCapabilities.prototype.getLayerResolution = function(m, layer, val) { var att = m==='min' ? 'MinScaleDenominator' : 'MaxScaleDenominator'; if (layer[att] !== undefined) return layer[att]/(72/2.54*100); if (!layer.Layer) return (m==='min' ? 0 : 156543.03392804097); // Get min / max of contained layers val = (m==='min' ? 156543.03392804097 : 0); for (var i=0; i=0) { crs = true; } else if (caps.CRS.indexOf('EPSG:4326')>=0) { // try to set EPSG:4326 instead srs = 'EPSG:4326'; crs = true; } else { this.get('srs').forEach(function(s) { if (caps.CRS.indexOf(s)>=0) { srs = s; crs = true; } }) } if (!crs) { this.showError({ type:'srs' }); if (this.get('trace')) console.log('BAD srs: ', caps.CRS); } var bbox = caps.EX_GeographicBoundingBox; //bbox = ol.proj.transformExtent(bbox, 'EPSG:4326', srs); if (bbox) bbox = ol.proj.transformExtent(bbox, 'EPSG:4326', this.getMap().getView().getProjection()); var attributions = []; if (caps.Attribution) { attributions.push('© '+caps.Attribution.Title.replace(/'); } var layer_opt = { title: caps.Title, extent: bbox, queryable: caps.queryable, abstract: caps.Abstract, minResolution: this.getLayerResolution('min', caps), maxResolution: this.getLayerResolution('max', caps) || 156543.03392804097 }; var source_opt = { url: parent.Capability.Request.GetMap.DCPType[0].HTTP.Get.OnlineResource, //parent.Service.OnlineResource, projection: srs, attributions: attributions, crossOrigin: this.get('cors') ? 'anonymous' : null, params: { 'LAYERS': caps.Name, 'FORMAT': format, 'VERSION': parent.version || '1.3.0' } } // Resolution to zoom var view = new ol.View({ projection: this.getMap().getView().getProjection() }) view.setResolution(layer_opt.minResolution); var maxZoom = Math.round(view.getZoom()); view.setResolution(layer_opt.maxResolution); var minZoom = Math.round(view.getZoom()); // Fill form this._fillForm({ title: layer_opt.title, layers: source_opt.params.LAYERS, format: source_opt.params.FORMAT, minZoom: minZoom, maxZoom: maxZoom, extent: bbox ? bbox.join(',') : '', projection: source_opt.projection, attribution: source_opt.attributions[0] || '', version: source_opt.params.VERSION }); // Trace if (this.get('trace')) { var tso = JSON.stringify([ source_opt ], null, "\t").replace(/\\"/g,'"'); layer_opt.source = "SOURCE"; var t = "new ol.layer.Tile (" +JSON.stringify(layer_opt, null, "\t")+ ")" t = t.replace(/\\"/g,'"') .replace('"SOURCE"', "new ol.source.TileWMS("+tso+")") .replace(/\\t/g,"\t").replace(/\\n/g,"\n") .replace("([\n\t","(") .replace("}\n])","})"); console.log(t); delete layer_opt.source; } // Legend ? var legend = []; if (caps.Style) { caps.Style.forEach(function(s) { if (s.LegendURL) { legend.push(s.LegendURL[0].OnlineResource); } }); } return ({ layer: layer_opt, source: source_opt, data: { title: caps.Title, abstract: caps.Abstract, logo: caps.Attribution && caps.Attribution.LogoURL ? caps.Attribution.LogoURL.OnlineResource : undefined, keyword: caps.KeywordList, legend: legend, opaque: caps.opaque, queryable: caps.queryable } }); }; /** Get WMS options from control form * @return {*} options * @private */ ol.control.WMSCapabilities.prototype._getFormOptions = function() { var minZoom = parseInt(this._elements.formMinZoom.value); var maxZoom = parseInt(this._elements.formMaxZoom.value); var view = new ol.View({ projection: this.getMap().getView().getProjection() }) view.setZoom(minZoom); var maxResolution = view.getResolution(); view.setZoom(maxZoom); var minResolution = view.getResolution(); var ext = []; if (this._elements.formExtent.value) { this._elements.formExtent.value.split(',').forEach(function(b) { ext.push(parseFloat(b)); }) } if (ext.length !== 4) ext = undefined; var attributions = [] if (this._elements.formAttribution.value) attributions.push(this._elements.formAttribution.value); var options = { layer: { title: this._elements.formTitle.value, extent: ext, maxResolution: maxResolution, minResolution: minResolution }, source: { url: this._elements.input.value, crossOrigin: this._elements.formCrossOrigin.checked ? 'anonymous' : null, projection: this._elements.formProjection.value, attributions: attributions, params: { FORMAT: this._elements.formFormat.options[this._elements.formFormat.selectedIndex].value, LAYERS: this._elements.formLayer.value, VERSION: this._elements.formVersion.value } }, data: { title: this._elements.formTitle.value } } if (this._elements.formMap.value) options.source.params.MAP = this._elements.formMap.value; return options; }; /** Fill dialog form * @private */ ol.control.WMSCapabilities.prototype._fillForm = function(opt) { this._elements.formTitle.value = opt.title; this._elements.formLayer.value = opt.layers; this._elements.formStyle.value = opt.style; var o, i; for (i=0; o=this._elements.formFormat.options[i]; i++) { if (o.value === opt.format) { this._elements.formFormat.selectedIndex = i; break; } } this._elements.formExtent.value = opt.extent || ''; this._elements.formMaxZoom.value = opt.maxZoom; this._elements.formMinZoom.value = opt.minZoom; this._elements.formProjection.value = opt.projection; this._elements.formAttribution.value = opt.attribution; this._elements.formVersion.value = opt.version; }; /** Load a layer using service * @param {string} url service url * @param {string} layername * @param {function} [onload] callback function (or listen to 'load' event) */ ol.control.WMSCapabilities.prototype.loadLayer = function(url, layerName, onload) { this.getCapabilities(url, { onload: function(cap) { if (cap) { cap.Capability.Layer.Layer.forEach(function(l) { if (l.Name===layerName || l.Identifier===layerName) { var options = this.getOptionsFromCap(l, cap); var layer = this.getLayerFromOptions(options); this.dispatchEvent({ type: 'load', layer: layer, options: options }); if (typeof(onload) === 'function') onload({ layer: layer, options: options }); } }.bind(this)) } else { this.dispatchEvent({ type: 'load', error: true }); } }.bind(this) }); }; /** WMTSCapabilities * @constructor * @fires load * @fires capabilities * @extends {ol.control.WMSCapabilities} * @param {*} options * @param {string|Element} options.target the target to set the dialog, use document.body to have fullwindow dialog * @param {string} options.proxy proxy to use when requesting Getcapabilites, default none (suppose the service use CORS) * @param {string} options.placeholder input placeholder, default 'service url...' * @param {string} options.title dialog title, default 'WMS' * @param {string} options.searchLabel Label for search button, default 'search' * @param {string} options.loadLabel Label for load button, default 'load' * @param {boolean} options.popupLayer Use a popup for the layers, default false * @param {*} options.services a key/url object of services for quick access in a menu * @param {Array} options.srs an array of supported srs, default map projection code or 'EPSG:3857' * @param {number} options.timeout Timeout for getCapabilities request, default 1000 * @param {boolean} options.cors Use CORS, default false * @param {boolean} options.trace Log layer info, default false */ ol.control.WMTSCapabilities = function (options) { options = options || {}; options.title = options.title || 'WMTS'; ol.control.WMSCapabilities.call(this, options); this.getDialog().element.classList.add('ol-wmtscapabilities'); }; ol.ext.inherits(ol.control.WMTSCapabilities, ol.control.WMSCapabilities); /** Get service parser * @private */ ol.control.WMTSCapabilities.prototype._getParser = function() { var pars = new ol.format.WMTSCapabilities(); return { read: function(data) { var resp = pars.read(data); resp.Capability = { Layer: resp.Contents, } // Generic attribution for layers resp.Capability.Layer.Attribution = { Title: resp.ServiceProvider.ProviderName } // Remove non image format var layers = []; resp.Contents.Layer.forEach(function(l) { if (l.Format && /jpeg|png/.test(l.Format[0])) { layers.push(l); } }) resp.Contents.Layer = layers; return resp; }.bind(this) } }; /** Get Capabilities request parameters * @param {*} options */ ol.control.WMTSCapabilities.prototype.getRequestParam = function(options) { return { SERVICE: 'WMTS', REQUEST: 'GetCapabilities', VERSION: options.version || '1.0.0' } }; /** Get tile grid options only for EPSG:3857 projection * @returns {*} * @private */ ol.control.WMTSCapabilities.prototype._getTG = function(tileMatrixSet, minZoom, maxZoom) { var matrixIds = new Array(); var resolutions = new Array(); var size = ol.extent.getWidth(ol.proj.get('EPSG:3857').getExtent()) / 256; for (var z=0; z <= (maxZoom ? maxZoom : 20) ; z++) { var id = tileMatrixSet !== 'PM' ? tileMatrixSet+':'+z : z; matrixIds[z] = id ; resolutions[z] = size / Math.pow(2, z); } return { origin: [-20037508, 20037508], resolutions: resolutions, matrixIds: matrixIds, minZoom: (minZoom ? minZoom : 0) } }; /** Get WMTS tile grid (only EPSG:3857) * @param {sting} tileMatrixSet * @param {number} minZoom * @param {number} maxZoom * @returns {ol.tilegrid.WMTS} * @private */ ol.control.WMTSCapabilities.prototype.getTileGrid = function(tileMatrixSet, minZoom, maxZoom) { return new ol.tilegrid.WMTS(this._getTG(tileMatrixSet, minZoom, maxZoom)); }; /** Return a WMTS options for the given capabilities * @param {*} caps layer capabilities (read from the capabilities) * @param {*} parent capabilities * @return {*} options */ ol.control.WMTSCapabilities.prototype.getOptionsFromCap = function(caps, parent) { var bbox = caps.WGS84BoundingBox; if (bbox) bbox = ol.proj.transformExtent(bbox, 'EPSG:4326', this.getMap().getView().getProjection()); // Tilematrix zoom var minZoom = Infinity, maxZoom = -Infinity; var tmatrix; caps.TileMatrixSetLink.forEach(function(tm) { if (tm.TileMatrixSet === 'PM' || tm.TileMatrixSet === 'EPSG:3857') { tmatrix = tm; caps.TileMatrixSet = tm.TileMatrixSet; } }); if (!tmatrix) { this.showError({ type: 'TileMatrix' }); return; } tmatrix.TileMatrixSetLimits.forEach(function(tm) { var zoom = tm.TileMatrix.split(':').pop(); minZoom = Math.min(minZoom, parseInt(zoom)); maxZoom = Math.max(maxZoom, parseInt(zoom)); }); var view = new ol.View(); view.setZoom(minZoom); var layer_opt = { title: caps.Title, extent: bbox, abstract: caps.Abstract, maxResolution: view.getResolution() }; var source_opt = { url: parent.OperationsMetadata.GetTile.DCP.HTTP.Get[0].href, layer: caps.Identifier, matrixSet: caps.TileMatrixSet, format: caps.Format[0] || 'image/jpeg', projection: 'EPSG:3857', //tileGrid: tg, minZoom: minZoom, maxZoom: maxZoom, style: caps.Style ? caps.Style[0].Identifier : 'normal', attributions: caps.Attribution.Title, crossOrigin: this.get('cors') ? 'anonymous' : null, wrapX: (this.get('wrapX') !== false), }; // Fill form this._fillForm({ title: layer_opt.title, layers: source_opt.layer, style: source_opt.style, format: source_opt.format, minZoom: minZoom, maxZoom: maxZoom, extent: bbox ? bbox.join(',') : '', projection: source_opt.projection, attribution: source_opt.attributions || '', version: '1.0.0' }); // Trace if (this.get('trace')) { // Source source_opt.tileGrid = 'TILEGRID'; var tso = JSON.stringify([ source_opt ], null, "\t").replace(/\\"/g,'"'); tso = tso.replace('"TILEGRID"', 'new ol.tilegrid.WMTS(' + JSON.stringify(this._getTG(source_opt.matrixSet, source_opt.minZoom, source_opt.maxZoom), null, '\t').replace(/\n/g, '\n\t\t') + ')' ); delete source_opt.tileGrid; // Layer layer_opt.source = "SOURCE"; var t = "new ol.layer.Tile (" +JSON.stringify(layer_opt, null, "\t")+ ")" t = t.replace(/\\"/g,'"') .replace('"SOURCE"', "new ol.source.WMTS("+tso+")") .replace(/\\t/g,"\t").replace(/\\n/g,"\n") .replace(/"tileGrid": {/g, '"tileGrid": new ol.tilegrid.WMTS({') .replace(/},\n(\t*)"style"/g, '}),\n$1"style"') .replace("([\n\t","(") .replace("}\n])","})"); console.log(t); delete layer_opt.source; } return ({ layer: layer_opt, source: source_opt, data: { title: caps.Title, abstract: caps.Abstract, legend: caps.Style ? [ caps.Style[0].LegendURL[0].href ] : undefined, } }); }; /** Get WMS options from control form * @return {*} original original options * @return {*} options * @private */ ol.control.WMTSCapabilities.prototype._getFormOptions = function() { var options = this._currentOptions || {}; if (!options.layer) options.layer = {}; if (!options.source) options.source = {}; if (!options.data) options.data = {}; var minZoom = parseInt(this._elements.formMinZoom.value) || 0; var maxZoom = parseInt(this._elements.formMaxZoom.value) || 20; var ext = []; if (this._elements.formExtent.value) { this._elements.formExtent.value.split(',').forEach(function(b) { ext.push(parseFloat(b)); }) } if (ext.length !== 4) ext = undefined; var attributions = [] if (this._elements.formAttribution.value) attributions.push(this._elements.formAttribution.value); var view = new ol.View({ projection: this.getMap().getView().getProjection() }) view.setZoom(minZoom); var layer_opt = { title: this._elements.formTitle.value, extent: ext, abstract: options.layer.abstract || '', maxResolution: view.getResolution() } var source_opt = { url: this._elements.input.value, layer: this._elements.formLayer.value, matrixSet: options.source.matrixSet || 'PM', format: this._elements.formFormat.options[this._elements.formFormat.selectedIndex].value, projection: 'EPSG:3857', minZoom: minZoom, maxZoom: maxZoom, // tileGrid: this._getTG(options.source.matrixSet || 'PM', minZoom, maxZoom), style: this._elements.formStyle.value || 'normal', attributions: attributions, crossOrigin: this._elements.formCrossOrigin.checked ? 'anonymous' : null, wrapX: (this.get('wrapX') !== false), } return ({ layer: layer_opt, source: source_opt, data: { title: this._elements.formTitle.value, abstract: options.data.abstract, legend: options.data.legend, } }); }; /** Create a new layer using options received by getOptionsFromCap method * @param {*} options */ ol.control.WMTSCapabilities.prototype.getLayerFromOptions = function (options) { if (!options) return; options.source.tileGrid = this.getTileGrid(options.source.matrixSet, options.source.minZoom, options.source.maxZoom); options.layer.source = new ol.source.WMTS(options.source); var layer = new ol.layer.Tile(options.layer); // Restore options delete options.layer.source; delete options.source.tileGrid; return layer; }; /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Feature animation base class * Use the {@link ol.Map#animateFeature} or {@link ol.layer.Vector#animateFeature} to animate a feature * on postcompose in a map or a layer * @constructor * @fires animationstart * @fires animating * @fires animationend * @param {ol.featureAnimationOptions} options * @param {Number} options.duration duration of the animation in ms, default 1000 * @param {bool} options.revers revers the animation direction * @param {Number} options.repeat number of time to repeat the animation, default 0 * @param {ol.style.Style} options.hiddenStyle a style to display the feature when playing the animation * to be used to make the feature selectable when playing animation * (@see {@link ../examples/map.featureanimation.select.html}), default the feature * will be hidden when playing (and not selectable) * @param {ol.easing.Function} options.fade an easing function used to fade in the feature, default none * @param {ol.easing.Function} options.easing an easing function for the animation, default ol.easing.linear */ ol.featureAnimation = function(options) { options = options || {}; this.duration_ = typeof (options.duration)=='number' ? (options.duration>=0 ? options.duration : 0) : 1000; this.fade_ = typeof(options.fade) == 'function' ? options.fade : null; this.repeat_ = Number(options.repeat); var easing = typeof(options.easing) =='function' ? options.easing : ol.easing.linear; if (options.revers) this.easing_ = function(t) { return (1 - easing(t)); }; else this.easing_ = easing; this.hiddenStyle = options.hiddenStyle; ol.Object.call(this); }; ol.ext.inherits(ol.featureAnimation, ol.Object); /** Hidden style: a transparent style */ ol.featureAnimation.hiddenStyle = new ol.style.Style({ image: new ol.style.Circle({}), stroke: new ol.style.Stroke({ color: 'transparent' }) }); /** Draw a geometry * @param {olx.animateFeatureEvent} e * @param {ol.geom} geom geometry for shadow * @param {ol.geom} shadow geometry for shadow (ie. style with zIndex = -1) * @private */ ol.featureAnimation.prototype.drawGeom_ = function (e, geom, shadow) { if (this.fade_) { e.context.globalAlpha = this.fade_(1-e.elapsed); } var style = e.style; for (var i=0; i} fanim the animation to play * @return {animationControler} an object to control animation with start, stop and isPlaying function */ ol.Map.prototype.animateFeature = function(feature, fanim) { // Get or create an animation layer associated with the map var layer = this._featureAnimationLayer; if (!layer) { layer = this._featureAnimationLayer = new ol.layer.Vector({ source: new ol.source.Vector() }); layer.setMap(this); } // Animate feature on this layer layer.getSource().addFeature(feature); var listener = fanim.on('animationend', function(e) { if (e.feature===feature) { // Remove feature on end layer.getSource().removeFeature(feature); ol.Observable.unByKey(listener); } }); layer.animateFeature(feature, fanim); }; /** Animate feature on a vector layer * @fires animationstart, animationend * @param {ol.Feature} feature Feature to animate * @param {ol.featureAnimation|Array} fanim the animation to play * @param {boolean} useFilter use the filters of the layer * @return {animationControler} an object to control animation with start, stop and isPlaying function */ ol.layer.Base.prototype.animateFeature = function(feature, fanim, useFilter) { var self = this; var listenerKey; // Save style var style = feature.getStyle(); var flashStyle = style || (this.getStyleFunction ? this.getStyleFunction()(feature) : null); if (!flashStyle) flashStyle=[]; if (!(flashStyle instanceof Array)) flashStyle = [flashStyle]; // Structure pass for animating var event = { // Frame context vectorContext: null, frameState: null, start: 0, time: 0, elapsed: 0, extent: false, // Feature information feature: feature, geom: feature.getGeometry(), typeGeom: feature.getGeometry().getType(), bbox: feature.getGeometry().getExtent(), coord: ol.extent.getCenter(feature.getGeometry().getExtent()), style: flashStyle }; if (!(fanim instanceof Array)) fanim = [fanim]; // Remove null animations for (var i=fanim.length-1; i>=0; i--) { if (fanim[i].duration_===0) fanim.splice(i,1); } var nb=0, step = 0; // Filter availiable on the layer var filters = (useFilter && this.getFilters) ? this.getFilters() : []; function animate(e) { event.type = e.type; try { event.vectorContext = e.vectorContext || ol.render.getVectorContext(e); } catch(e) { /* nothing todo */ } event.frameState = e.frameState; event.inversePixelTransform = e.inversePixelTransform; if (!event.extent) { event.extent = e.frameState.extent; event.start = e.frameState.time; event.context = e.context; } event.time = e.frameState.time - event.start; event.elapsed = event.time / fanim[step].duration_; if (event.elapsed > 1) event.elapsed = 1; // Filter e.context.save(); filters.forEach(function(f) { if (f.get('active')) f.precompose(e); }); if (this.getOpacity) { e.context.globalAlpha = this.getOpacity(); } // Stop animation? if (!fanim[step].animate(event)) { nb++; // Repeat animation if (nb < fanim[step].repeat_) { event.extent = false; } else if (step < fanim.length-1) { // newt step fanim[step].dispatchEvent({ type:'animationend', feature: feature }); step++; nb=0; event.extent = false; } else { // the end stop(); } } else { var animEvent = { type: 'animating', step: step, start: event.start, time: event.time, elapsed: event.elapsed, rotation: event.rotation||0, geom: event.geom, coordinate: event.coord, feature: feature }; fanim[step].dispatchEvent(animEvent); self.dispatchEvent(animEvent); } filters.forEach(function(f) { if (f.get('active')) f.postcompose(e); }); e.context.restore(); // tell OL3 to continue postcompose animation e.frameState.animate = true; } // Stop animation function stop(options) { ol.Observable.unByKey(listenerKey); listenerKey = null; feature.setStyle(style); // Send event var event = { type:'animationend', feature: feature }; if (options) { for (var i in options) if (options.hasOwnProperty(i)) { event[i] = options[i]; } } fanim[step].dispatchEvent(event); self.dispatchEvent(event); } // Launch animation function start(options) { if (fanim.length && !listenerKey) { listenerKey = self.on(['postcompose','postrender'], animate.bind(self)); // map or layer? if (self.renderSync) { try { self.renderSync(); } catch(e) { /* ok */ } } else { self.changed(); } // Hide feature while animating feature.setStyle(fanim[step].hiddenStyle || ol.featureAnimation.hiddenStyle); // Send event var event = { type:'animationstart', feature: feature }; if (options) { for (var i in options) if (options.hasOwnProperty(i)) { event[i] = options[i]; } } fanim[step].dispatchEvent(event); self.dispatchEvent(event); } } start(); // Return animation controler return { start: start, stop: stop, isPlaying: function() { return (!!listenerKey); } }; }; /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Blink a feature * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationOptions} options * @param {Number} options.nb number of blink, default 10 */ ol.featureAnimation.Blink = function(options) { ol.featureAnimation.call(this, options); this.set('nb', options.nb || 10) } ol.ext.inherits(ol.featureAnimation.Blink, ol.featureAnimation); /** Animate: Show or hide feature depending on the laptimes * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Blink.prototype.animate = function (e) { if (!(Math.round(this.easing_(e.elapsed)*this.get('nb'))%2)) { this.drawGeom_(e, e.geom); } return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Bounce animation: * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationBounceOptions} options * @param {Integer} options.bounce number of bounce, default 3 * @param {Integer} options.amplitude bounce amplitude,default 40 * @param {ol.easing} options.easing easing used for decaying amplitude, use function(){return 0} for no decay, default ol.easing.linear * @param {Integer} options.duration duration in ms, default 1000 */ ol.featureAnimation.Bounce = function(options) { options = options || {}; ol.featureAnimation.call(this, options); this.amplitude_ = options.amplitude || 40; this.bounce_ = -Math.PI*(options.bounce || 3); } ol.ext.inherits(ol.featureAnimation.Bounce, ol.featureAnimation); /** Animate * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Bounce.prototype.animate = function (e) { // Animate var flashGeom = e.geom.clone(); /* var t = this.easing_(e.elapsed) t = Math.abs(Math.sin(this.bounce_*t)) * this.amplitude_ * (1-t) * e.frameState.viewState.resolution; */ var t = Math.abs(Math.sin(this.bounce_*e.elapsed)) * this.amplitude_ * (1-this.easing_(e.elapsed)) * e.frameState.viewState.resolution; flashGeom.translate(0, t); this.drawGeom_(e, flashGeom, e.geom); return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Drop animation: drop a feature on the map * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationDropOptions} options * @param {Number} options.speed speed of the feature if 0 the duration parameter will be used instead, default 0 * @param {Number} options.side top or bottom, default top */ ol.featureAnimation.Drop = function(options) { options = options || {}; this.speed_ = options.speed || 0; ol.featureAnimation.call(this, options); this.side_ = options.side || 'top'; } ol.ext.inherits(ol.featureAnimation.Drop, ol.featureAnimation); /** Animate * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Drop.prototype.animate = function (e) { // First time > calculate duration / speed if (!e.time) { var angle = e.frameState.viewState.rotation; var s = e.frameState.size[1] * e.frameState.viewState.resolution; if (this.side_!='top') s *= -1; this.dx = -Math.sin(angle)*s; this.dy = Math.cos(angle)*s; if (this.speed_) { this.duration_ = s/this.speed_/e.frameState.viewState.resolution; } } // Animate var flashGeom = e.geom.clone(); flashGeom.translate( this.dx*(1-this.easing_(e.elapsed)), this.dy*(1-this.easing_(e.elapsed)) ); this.drawGeom_(e, flashGeom, e.geom); return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Fade animation: feature fade in * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationOptions} options */ ol.featureAnimation.Fade = function(options) { options = options || {}; this.speed_ = options.speed || 0; ol.featureAnimation.call(this, options); } ol.ext.inherits(ol.featureAnimation.Fade, ol.featureAnimation); /** Animate * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Fade.prototype.animate = function (e) { e.context.globalAlpha = this.easing_(e.elapsed); this.drawGeom_(e, e.geom); return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Do nothing for a given duration * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationShowOptions} options * */ ol.featureAnimation.None = function(options) { ol.featureAnimation.call(this, options); }; ol.ext.inherits(ol.featureAnimation.None, ol.featureAnimation); /** Animate: do nothing during the laps time * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.None.prototype.animate = function (e) { return (e.time <= this.duration_); }; /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Do nothing * @constructor * @extends {ol.featureAnimation} */ ol.featureAnimation.Null = function() { ol.featureAnimation.call(this, { duration:0 }); }; ol.ext.inherits(ol.featureAnimation.Null, ol.featureAnimation); /* Copyright (c) 2016-2018 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Path animation: feature follow a path * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationPathOptions} options extend ol.featureAnimation options * @param {Number} options.speed speed of the feature, if 0 the duration parameter will be used instead, default 0 * @param {Number|boolean} options.rotate rotate the symbol when following the path, true or the initial rotation, default false * @param {ol.geom.LineString|ol.Feature} options.path the path to follow * @param {Number} options.duration duration of the animation in ms */ ol.featureAnimation.Path = function(options){ options = options || {}; ol.featureAnimation.call(this, options); this.speed_ = options.speed || 0; this.path_ = options.path; switch (options.rotate) { case true: case 0: this.rotate_ = 0; break; default: this.rotate_ = options.rotate || false; break; } if (this.path_ && this.path_.getGeometry) this.path_ = this.path_.getGeometry(); if (this.path_ && this.path_.getLineString) this.path_ = this.path_.getLineString(); if (this.path_.getLength) { this.dist_ = this.path_.getLength() if (this.path_ && this.path_.getCoordinates) this.path_ = this.path_.getCoordinates(); } else { this.dist_ = 0; } if (this.speed_>0) this.duration_ = this.dist_/this.speed_; } ol.ext.inherits(ol.featureAnimation.Path, ol.featureAnimation); /** Animate * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Path.prototype.animate = function (e) { // First time if (!e.time) { if (!this.dist_) return false; } var dmax = this.dist_*this.easing_(e.elapsed); var p0, p, s, dx,dy, dl, d = 0; p = this.path_[0]; // Linear interpol for (var i = 1; i=dmax) { s = (dmax-d)/dl; p = [ p0[0] + (p[0]-p0[0])*s, p0[1] + (p[1]-p0[1])*s]; break; } d += dl; } // Rotate symbols var style = e.style; e.rotation = Math.PI/2 + Math.atan2(p0[1] - p[1], p0[0] - p[0]); if (this.rotate_!==false) { var st = [] var angle = this.rotate_ - e.rotation + e.frameState.viewState.rotation; e.rotation = Math.PI/2 + Math.atan2(p0[1] - p[1], p0[0] - p[0]); for (var k=0; s=e.style[k]; k++) { if (s.getImage()) { //s = s.clone(); s.getImage().setRotation(angle); } st.push(s); } // Rotated style e.style = st; } e.geom.setCoordinates(p); // Animate this.drawGeom_(e, e.geom); // restore style (if modify by rotation) e.style = style; return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Shakee animation: * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationShakeOptions} options * @param {Integer} options.bounce number o bounds, default 6 * @param {Integer} options.amplitude amplitude of the animation, default 40 * @param {bool} options.horizontal shake horizontally default false (vertical) */ ol.featureAnimation.Shake = function(options) { options = options || {}; ol.featureAnimation.call(this, options); // this.easing_ = options.easing_ || function(t){return (0.5+t)*t -0.5*t ;}; this.amplitude_ = options.amplitude || 40; this.bounce_ = -Math.PI*(options.bounce || 6); this.horizontal_ = options.horizontal; } ol.ext.inherits(ol.featureAnimation.Shake, ol.featureAnimation); /** Animate * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Shake.prototype.animate = function (e) { // Animate var flashGeom = e.geom.clone(); var shadow = e.geom.clone(); var t = this.easing_(e.elapsed) t = Math.sin(this.bounce_*t) * this.amplitude_ * (1-t) * e.frameState.viewState.resolution; if (this.horizontal_) { flashGeom.translate(t, 0); shadow.translate(t, 0); } else flashGeom.translate(0, t); this.drawGeom_(e, flashGeom, shadow); return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Show an object for a given duration * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationOptions} options */ ol.featureAnimation.Show = function(options) { ol.featureAnimation.call(this, options); } ol.ext.inherits(ol.featureAnimation.Show, ol.featureAnimation); /** Animate: just show the object during the laps time * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Show.prototype.animate = function (e) { this.drawGeom_(e, e.geom); return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Slice animation: feature enter from left * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationSlideOptions} options * @param {Number} options.speed speed of the animation, if 0 the duration parameter will be used instead, default 0 */ ol.featureAnimation.Slide = function(options) { options = options || {}; this.speed_ = options.speed || 0; ol.featureAnimation.call(this, options); this.side_ = options.side || 'left'; } ol.ext.inherits(ol.featureAnimation.Slide, ol.featureAnimation); /** Animate * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Slide.prototype.animate = function (e) { // First time > calculate duration / speed if (!e.time) { if (this.side_=='left') this.dx = (e.extent[0]-e.bbox[2]) else this.dx = (e.extent[2]-e.bbox[0]) if (this.speed_) this.duration_ = Math.abs(this.dx)/this.speed_/e.frameState.viewState.resolution; } // Animate var flashGeom = e.geom.clone(); flashGeom.translate(this.dx*(1-this.easing_(e.elapsed)), 0); this.drawGeom_(e, flashGeom); return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Teleport a feature at a given place * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationOptions} options */ ol.featureAnimation.Teleport = function(options) { ol.featureAnimation.call(this, options); } ol.ext.inherits(ol.featureAnimation.Teleport, ol.featureAnimation); /** Animate * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Teleport.prototype.animate = function (e) { var sc = this.easing_(e.elapsed); if (sc) { e.context.save() var ratio = e.frameState.pixelRatio; e.context.globalAlpha = sc; e.context.scale(sc,1/sc); var m = e.frameState.coordinateToPixelTransform; var dx = (1/sc-1) * ratio * (m[0]*e.coord[0] + m[1]*e.coord[1] +m[4]); var dy = (sc-1) * ratio * (m[2]*e.coord[0] + m[3]*e.coord[1] +m[5]); e.context.translate(dx,dy); this.drawGeom_(e, e.geom); e.context.restore() } return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Slice animation: feature enter from left * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationThrowOptions} options * @param {left|right} options.side side of the animation, default left */ ol.featureAnimation.Throw = function(options) { options = options || {}; ol.featureAnimation.call(this, options); this.speed_ = options.speed || 0; this.side_ = options.side || 'left'; } ol.ext.inherits(ol.featureAnimation.Throw, ol.featureAnimation); /** Animate * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Throw.prototype.animate = function (e) { // First time > calculate duration / speed if (!e.time && this.speed_) { var dx, dy; if (this.side_=='left') { dx = this.dx = e.extent[0]-e.bbox[2]; dy = this.dy = e.extent[3]-e.bbox[1]; } else { dx = this.dx = e.extent[2]-e.bbox[0]; dy = this.dy = e.extent[3]-e.bbox[1]; } this.duration_ = Math.sqrt(dx*dx+dy*dy)/this.speed_/e.frameState.viewState.resolution; } // Animate var flashGeom = e.geom.clone(); var shadow = e.geom.clone(); flashGeom.translate(this.dx*(1-this.easing_(e.elapsed)), this.dy*Math.cos(Math.PI/2*this.easing_(e.elapsed))); shadow.translate(this.dx*(1-this.easing_(e.elapsed)), 0); this.drawGeom_(e, flashGeom, shadow); return (e.time <= this.duration_); } /* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ /** Zoom animation: feature zoom in (for points) * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationZoomOptions} options * @param {bool} options.zoomOut to zoom out */ ol.featureAnimation.Zoom = function(options){ options = options || {}; ol.featureAnimation.call(this, options); this.set('zoomout', options.zoomOut); } ol.ext.inherits(ol.featureAnimation.Zoom, ol.featureAnimation); /** Zoom animation: feature zoom out (for points) * @constructor * @extends {ol.featureAnimation} * @param {ol.featureAnimationZoomOptions} options */ ol.featureAnimation.ZoomOut = function(options) { options = options || {}; options.zoomOut = true; ol.featureAnimation.Zoom.call(this, options); } ol.ext.inherits(ol.featureAnimation.ZoomOut, ol.featureAnimation.Zoom); /** Animate * @param {ol.featureAnimationEvent} e */ ol.featureAnimation.Zoom.prototype.animate = function (e) { var fac = this.easing_(e.elapsed); if (fac) { if (this.get('zoomout')) fac = 1/fac; var style = e.style; var i, imgs, sc=[] for (i=0; i= v6 if (e.type==='postrender') imgs.setScale(sc[i]*fac/e.frameState.pixelRatio); else imgs.setScale(sc[i]*fac); } } this.drawGeom_(e, e.geom); for (i=0; i=0; i--) { if (this.filters_[i]===filter) this.filters_.splice(i,1); } for (i=filter._listener.length-1; i>=0; i--) { // Remove listener on this object if (filter._listener[i].target === this) { if (filter.removeFromLayer) filter.removeFromLayer(this); ol.Observable.unByKey(filter._listener[i].listener); filter._listener.splice(i,1); } } filterRedraw_.call (this); } /** Add a filter to an ol.Map * @param {ol.filter} */ ol.Map.prototype.addFilter = function (filter) { console.warn('[OL-EXT] addFilter deprecated on map.') addFilter_.call (this, filter); }; /** Remove a filter to an ol.Map * @param {ol.filter} */ ol.Map.prototype.removeFilter = function (filter) { removeFilter_.call (this, filter); }; /** Get filters associated with an ol.Map * @return {Array} */ ol.Map.prototype.getFilters = function () { return this.filters_ || []; }; /** Add a filter to an ol.Layer * @param {ol.filter} */ ol.layer.Base.prototype.addFilter = function (filter) { addFilter_.call (this, filter); }; /** Remove a filter to an ol.Layer * @param {ol.filter} */ ol.layer.Base.prototype.removeFilter = function (filter) { removeFilter_.call (this, filter); }; /** Get filters associated with an ol.Map * @return {Array} */ ol.layer.Base.prototype.getFilters = function () { return this.filters_ || []; }; })(); /* 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). */ /** Mask drawing using an ol.Feature * @constructor * @requires ol.filter * @extends {ol.filter.Base} * @param {Object} [options] * @param {ol.Feature} [options.feature] feature to mask with * @param {ol.style.Fill} [options.fill] style to fill with * @param {boolean} [options.inner] mask inner, default false */ ol.filter.Mask = function(options) { options = options || {}; ol.filter.Base.call(this, options); if (options.feature) { switch (options.feature.getGeometry().getType()) { case 'Polygon': case 'MultiPolygon': this.feature_ = options.feature; break; default: break; } } this.set('inner', options.inner); this.fillColor_ = options.fill ? ol.color.asString(options.fill.getColor()) || "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.2)"; } ol.ext.inherits(ol.filter.Mask, ol.filter.Base); /** Draw the feature into canvas */ ol.filter.Mask.prototype.drawFeaturePath_ = function(e, out) { var ctx = e.context; var canvas = ctx.canvas; var ratio = e.frameState.pixelRatio; // Transform var tr; if (e.frameState.coordinateToPixelTransform) { var m = e.frameState.coordinateToPixelTransform; // ol > 6 if (e.inversePixelTransform) { var ipt = e.inversePixelTransform; tr = function(pt) { pt = [ (pt[0]*m[0]+pt[1]*m[1]+m[4]), (pt[0]*m[2]+pt[1]*m[3]+m[5]) ]; return [ (pt[0]*ipt[0] - pt[1]*ipt[1] + ipt[4]), (-pt[0]*ipt[2] + pt[1]*ipt[3] + ipt[5]) ] } } else { // ol 5 tr = function(pt) { return [ (pt[0]*m[0]+pt[1]*m[1]+m[4])*ratio, (pt[0]*m[2]+pt[1]*m[3]+m[5])*ratio ]; } } } else { // Older version m = e.frameState.coordinateToPixelMatrix; tr = function(pt) { return [ (pt[0]*m[0]+pt[1]*m[1]+m[12])*ratio, (pt[0]*m[4]+pt[1]*m[5]+m[13])*ratio ]; } } // Geometry var ll = this.feature_.getGeometry().getCoordinates(); if (this.feature_.getGeometry().getType()=="Polygon") ll = [ll]; ctx.beginPath(); if (out) { ctx.moveTo (0,0); ctx.lineTo (canvas.width, 0); ctx.lineTo (canvas.width, canvas.height); ctx.lineTo (0, canvas.height); ctx.lineTo (0, 0); } for (var l=0; l=0) { layer.once('postrender', function(e) { e.context.canvas.parentNode.style['mix-blend-mode'] = ''; e.context.canvas.parentNode.style['filter'] = ''; e.context.canvas.parentNode.style['display'] = ''; }.bind(this)); layer.changed(); this._layers.splice(pos, 1); } }; /* 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). */ /** @typedef {Object} CanvasFilterOptions * @property {url} url Takes an IRI pointing to an SVG filter element * @property {number} blur Gaussian blur value in px * @property {number} brightness linear multiplier to the drawing, under 100: darkens the image, over 100 brightens it * @property {number} contrast Adjusts the contrast, under 0: black, 100 no change * @property {ol.pixel} shadow Applies a drop shadow effect, pixel offset * @property {number} shadowBlur Blur radius * @property {number} shadowColor * @property {number} grayscale 0: unchanged, 100: completely grayscale * @property {number} hueRotate Hue rotation angle in deg * @property {number} invert Inverts the drawing, 0: unchanged, 100: invert * @property {number} saturate Saturates the drawing, 0: unsaturated, 100: unchanged * @property {number} sepia Converts the drawing to sepia, 0: sepia, 100: unchanged */ /** Add a canvas Context2D filter to a layer * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter * @constructor * @requires ol.filter * @extends {ol.filter.Base} * @param {CanvasFilterOptions} options */ ol.filter.CanvasFilter = function(options) { ol.filter.Base.call(this, options); this._svg = {}; }; ol.ext.inherits(ol.filter.CanvasFilter, ol.filter.Base); /** Add a new svg filter * @param {string|ol.ext.SVGFilter} url IRI pointing to an SVG filter element */ ol.filter.CanvasFilter.prototype.addSVGFilter = function(url) { if (url.getId) url = '#'+url.getId(); this._svg[url] = 1; this.dispatchEvent({ type: 'propertychange', key: 'svg', oldValue: this._svg }); }; /** Remove a svg filter * @param {string|ol.ext.SVGFilter} url IRI pointing to an SVG filter element */ ol.filter.CanvasFilter.prototype.removeSVGFilter = function(url) { if (url.getId) url = '#'+url.getId(); delete this._svg[url] this.dispatchEvent({ type: 'propertychange', key: 'svg', oldValue: this._svg }); }; /** * @private */ ol.filter.CanvasFilter.prototype.precompose = function() { }; /** * @private */ ol.filter.CanvasFilter.prototype.postcompose = function(e) { var filter = [] // Set filters if (this.get('url')!==undefined) filter.push('url('+this.get('url')+')'); for (var f in this._svg) { filter.push('url('+f+')'); } if (this.get('blur')!==undefined) filter.push('blur('+this.get('blur')+'px)'); if (this.get('brightness')!==undefined) filter.push('brightness('+this.get('brightness')+'%)'); if (this.get('contrast')!==undefined) filter.push('contrast('+this.get('contrast')+'%)'); if (this.get('shadow')!==undefined) { filter.push('drop-shadow(' + this.get('shadow')[0]+'px ' + this.get('shadow')[1]+'px ' + (this.get('shadowBlur')||0)+'px ' + this.get('shadowColor')+')'); } if (this.get('grayscale')!==undefined) filter.push('grayscale('+this.get('grayscale')+'%)'); if (this.get('hueRotate')!==undefined) filter.push('hue-rotate('+this.get('hueRotate')+'deg)'); if (this.get('invert')!==undefined) filter.push('invert('+this.get('invert')+'%)'); if (this.get('saturate')!==undefined) filter.push('saturate('+this.get('saturate')+'%)'); if (this.get('sepia')!==undefined) filter.push('sepia('+this.get('sepia')+'%)'); filter = filter.join(' '); // Apply filter if (filter) { e.context.save(); e.context.filter = filter; e.context.drawImage(e.context.canvas, 0,0); e.context.restore(); } }; /* 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). */ /** Clip layer or map * @constructor * @requires ol.filter * @extends {ol.filter.Base} * @param {Object} [options] * @param {Array} [options.coords] * @param {ol.Extent} [options.extent] * @param {string} [options.units] coords units percent (%) or pixel (px) * @param {boolean} [options.keepAspectRatio] keep aspect ratio * @param {string} [options.color] backgroundcolor */ ol.filter.Clip = function(options) { options = options || {}; ol.filter.Base.call(this, options); this.set("coords", options.coords); this.set("units", options.units); this.set("keepAspectRatio", options.keepAspectRatio); this.set("extent", options.extent || [0,0,1,1]); this.set("color", options.color); if (!options.extent && options.units!="%" && options.coords) { var xmin = Infinity; var ymin = Infinity; var xmax = -Infinity; var ymax = -Infinity; for (var i=0, p; p=options.coords[i]; i++) { if (xmin > p[0]) xmin = p[0]; if (xmax < p[0]) xmax = p[0]; if (ymin > p[1]) ymin = p[1]; if (ymax < p[1]) ymax = p[1]; } options.extent = [xmin,ymin,xmax,ymax]; } } ol.ext.inherits(ol.filter.Clip, ol.filter.Base); ol.filter.Clip.prototype.clipPath_ = function(e) { var ctx = e.context; var size = e.frameState.size; var coords = this.get("coords"); if (!coords) return; var ex = this.get('extent'); var scx = 1, scy = 1; if (this.get("units")=="%") { scx = size[0]/(ex[2]-ex[0]); scy = size[1]/(ex[3]-ex[1]); } if (this.get("keepAspectRatio")) { scx = scy = Math.min (scx, scy); } var pos = this.get('position'); var dx=0, dy=0; if (/left/.test(pos)) { dx = -ex[0]*scx; } else if (/center/.test(pos)) { dx = size[0]/2 - (ex[2]-ex[0])*scx/2; } else if (/right/.test(pos)) { dx = size[0] - (ex[2]-ex[0])*scx; } var fx = function(x) { return x*scx + dx }; if (/top/.test(pos)) { dy = -ex[1]*scy; } else if (/middle/.test(pos)) { dy = size[1]/2 - (ex[3]-ex[1])*scy/2; } else if (/bottom/.test(pos)) { dy = size[1] - (ex[3]-ex[1])*scy; } var fy = function(y) { return y*scy + dy; }; var pt = [ fx(coords[0][0]), fy(coords[0][1]) ]; var tr = e.inversePixelTransform; if (tr) { pt = [ (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) ]; } ctx.moveTo ( pt[0], pt[1] ); for (var i=1, p; p=coords[i]; i++) { pt = [ fx(p[0]), fy(p[1]) ]; if (tr) { pt = [ (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) ]; } ctx.lineTo ( pt[0], pt[1] ); } pt = [ fx(coords[0][0]), fy(coords[0][1]) ]; if (tr) { pt = [ (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) ]; } ctx.moveTo ( pt[0], pt[1] ); }; /** * @private */ ol.filter.Clip.prototype.precompose = function(e) { if (!this.get("color")){ e.context.save(); e.context.beginPath(); this.clipPath_(e); e.context.clip(); } }; /** * @private */ ol.filter.Clip.prototype.postcompose = function(e) { if (this.get("color")) { var ctx = e.context; var canvas = e.context.canvas; ctx.save(); ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(0,canvas.height); ctx.lineTo(canvas.width, canvas.height); ctx.lineTo(canvas.width, canvas.height); ctx.lineTo(canvas.width, 0); ctx.lineTo(0, 0); this.clipPath_(e); ctx.fillStyle = this.get("color"); ctx.fill("evenodd"); } e.context.restore(); }; /* 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). */ /** @typedef {Object} FilterColorizeOptions * @property {ol.Color} color style to fill with * @property {string} operation 'enhance' or a CanvasRenderingContext2D.globalCompositeOperation * @property {number} value a value to modify the effect value [0-1] * @property {boolean} inner mask inner, default false * @property {boolean} preserveAlpha preserve alpha channel, default false */ /** Colorize map or layer * @constructor * @requires ol.filter * @extends {ol.filter.Base} * @author Thomas Tilak https://github.com/thhomas * @author Jean-Marc Viglino https://github.com/viglino * @param {FilterColorizeOptions} options */ ol.filter.Colorize = function(options) { ol.filter.Base.call(this, options); this.setFilter(options); } ol.ext.inherits(ol.filter.Colorize, ol.filter.Base); /** Set options to the filter * @param {FilterColorizeOptions} [options] */ ol.filter.Colorize.prototype.setFilter = function(options) { options = options || {}; switch (options) { case "grayscale": options = { operation:'hue', color: [0,0,0], value:1 }; break; case "invert": options = { operation:'difference', color: [255,255,255], value:1 }; break; case "sepia": options = { operation:'color', color: [153,102,51], value:0.6 }; break; default: break; } var color = options.color ? ol.color.asArray(options.color) : [ options.red, options.green, options.blue, options.value]; this.set('color', ol.color.asString(color)) this.set ('value', options.value||1); this.set ('preserveAlpha', options.preserveAlpha); var v; switch (options.operation){ case 'hue': case 'difference': case 'color-dodge': case 'enhance': { this.set ('operation', options.operation); break; } case 'saturation': { v = 255*(options.value || 0); this.set('color', ol.color.asString([0,0,v,v||1])); this.set ('operation', options.operation); break; } case 'luminosity': { v = 255*(options.value || 0); this.set('color', ol.color.asString([v,v,v,255])); //this.set ('operation', 'luminosity') this.set ('operation', 'hard-light'); break; } case 'contrast': { v = 255*(options.value || 0); this.set('color', ol.color.asString([v,v,v,255])); this.set('operation', 'soft-light'); break; } default: { this.set ('operation', 'color'); this.setValue(options.value||1); break; } } } /** Set the filter value * @param {ol.Color} options.color style to fill with */ ol.filter.Colorize.prototype.setValue = function(v) { this.set ('value', v); var c = ol.color.asArray(this.get("color")); c[3] = v; this.set("color", ol.color.asString(c)); } /** Set the color value * @param {number} options.value a [0-1] value to modify the effect value */ ol.filter.Colorize.prototype.setColor = function(c) { c = ol.color.asArray(c); if (c) { c[3] = this.get("value"); this.set("color", ol.color.asString(c)); } } /** @private */ ol.filter.Colorize.prototype.precompose = function(/* e */) { } /** @private */ ol.filter.Colorize.prototype.postcompose = function(e) { // Set back color hue var c2, ctx2; var ctx = e.context; var canvas = ctx.canvas; ctx.save(); if (this.get('operation')=='enhance') { var v = this.get('value'); if (v) { var w = canvas.width; var h = canvas.height; if (this.get('preserveAlpha')) { c2 = document.createElement('CANVAS'); c2.width = canvas.width; c2.height = canvas.height; ctx2 = c2.getContext('2d'); ctx2.drawImage (canvas, 0, 0, w, h); ctx2.globalCompositeOperation = 'color-burn'; console.log(v) ctx2.globalAlpha = v; ctx2.drawImage (c2, 0, 0, w, h); ctx2.drawImage (c2, 0, 0, w, h); ctx2.drawImage (c2, 0, 0, w, h); ctx.globalCompositeOperation = 'source-in'; ctx.drawImage(c2, 0,0); } else { ctx.globalCompositeOperation = 'color-burn' ctx.globalAlpha = v; ctx.drawImage (canvas, 0, 0, w, h); ctx.drawImage (canvas, 0, 0, w, h); ctx.drawImage (canvas, 0, 0, w, h); } } } else { if (this.get('preserveAlpha')) { c2 = document.createElement('CANVAS'); c2.width = canvas.width; c2.height = canvas.height; ctx2 = c2.getContext('2d'); ctx2.drawImage(canvas, 0,0); ctx2.globalCompositeOperation = this.get('operation'); ctx2.fillStyle = this.get('color'); ctx2.fillRect(0,0,canvas.width,canvas.height); ctx.globalCompositeOperation = 'source-in'; ctx.drawImage(c2, 0,0); } else { ctx.globalCompositeOperation = this.get('operation'); ctx.fillStyle = this.get('color'); ctx.fillRect(0,0,canvas.width,canvas.height); } } ctx.restore(); } /* 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). */ /** Add a composite filter on a layer. * With ol6+ you'd better use {@link ol.filter.CSS} instead. * Use {@link ol.layer.Base#addFilter}, {@link ol.layer.Base#removeFilter} or {@link ol.layer.Base#getFilters} * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation * @constructor * @requires ol.filter * @extends {ol.filter.Base} * @param {Object} options * @param {string} options.operation composite operation */ ol.filter.Composite = function(options) { ol.filter.Base.call(this, options); this.set("operation", options.operation || "source-over"); } ol.ext.inherits(ol.filter.Composite, ol.filter.Base); /** Change the current operation * @param {string} operation composite function */ ol.filter.Composite.prototype.setOperation = function(operation) { this.set('operation', operation || "source-over"); } ol.filter.Composite.prototype.precompose = function(e) { var ctx = e.context; ctx.save(); ctx.globalCompositeOperation = this.get('operation'); } ol.filter.Composite.prototype.postcompose = function(e) { e.context.restore(); } /* 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). */ /** Crop drawing using an ol.Feature * @constructor * @requires ol.filter * @requires ol.filter.Mask * @extends {ol.filter.Mask} * @param {Object} [options] * @param {ol.Feature} [options.feature] feature to crop with * @param {boolean} [options.inner=false] mask inner, default false */ ol.filter.Crop = function(options) { options = options || {}; ol.filter.Mask.call(this, options); } ol.ext.inherits(ol.filter.Crop, ol.filter.Mask); ol.filter.Crop.prototype.precompose = function(e) { if (this.feature_) { var ctx = e.context; ctx.save(); this.drawFeaturePath_(e, this.get("inner")); ctx.clip("evenodd"); } } ol.filter.Crop.prototype.postcompose = function(e) { if (this.feature_) e.context.restore(); } /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Fold filer map * @constructor * @requires ol.filter * @extends {ol.filter.Base} * @param {Object} [options] * @param {Array} [options.fold] number of fold (horizontal and vertical) * @param {number} [options.margin] margin in px, default 8 * @param {number} [options.padding] padding in px, default 8 * @param {number|number[]} [options.fsize] fold size in px, default 8,10 * @param {boolean} [options.fill] true to fill the background, default false * @param {boolean} [options.shadow] true to display shadow, default true */ ol.filter.Fold = function(options) { options = options || {}; ol.filter.Base.call(this, options); this.set('fold', options.fold || [8,4]); this.set('margin', options.margin || 8); this.set('padding', options.padding || 8); if (typeof options.fsize == 'number') options.fsize = [options.fsize,options.fsize]; this.set('fsize', options.fsize || [8,10]); this.set('fill', options.fill); this.set('shadow', options.shadow!==false); }; ol.ext.inherits(ol.filter.Fold, ol.filter.Base); ol.filter.Fold.prototype.drawLine_ = function(ctx, d, m) { var canvas = ctx.canvas; var fold = this.get("fold"); var w = canvas.width; var h = canvas.height; var x, y, i; ctx.beginPath(); ctx.moveTo ( m, m ); for (i=1; i<=fold[0]; i++) { x = i*w/fold[0] - (i==fold[0] ? m : 0); y = d[1]*(i%2) +m; ctx.lineTo ( x, y ); } for (i=1; i<=fold[1]; i++) { x = w - d[0]*(i%2) - m; y = i*h/fold[1] - (i==fold[1] ? d[0]*(fold[0]%2) + m : 0); ctx.lineTo ( x, y ); } for (i=fold[0]; i>0; i--) { x = i*w/fold[0] - (i==fold[0] ? d[0]*(fold[1]%2) + m : 0); y = h - d[1]*(i%2) -m; ctx.lineTo ( x, y ); } for (i=fold[1]; i>0; i--) { x = d[0]*(i%2) + m; y = i*h/fold[1] - (i==fold[1] ? m : 0); ctx.lineTo ( x, y ); } ctx.closePath(); }; ol.filter.Fold.prototype.precompose = function(e) { var ctx = e.context; ctx.save(); ctx.shadowColor = "rgba(0,0,0,0.3)"; ctx.shadowBlur = 8; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 3; this.drawLine_(ctx, this.get("fsize"), this.get("margin")); ctx.fillStyle="#fff"; if (this.get('fill')) ctx.fill(); ctx.strokeStyle = "rgba(0,0,0,0.1)"; ctx.stroke(); ctx.restore(); ctx.save(); this.drawLine_(ctx, this.get("fsize"), this.get("margin") + this.get("padding")); ctx.clip(); }; ol.filter.Fold.prototype.postcompose = function(e) { var ctx = e.context; var canvas = ctx.canvas; ctx.restore(); ctx.save(); this.drawLine_(ctx, this.get("fsize"), this.get("margin")); ctx.clip(); if (this.get('shadow')) { var fold = this.get("fold"); var w = canvas.width/fold[0]; var h = canvas.height/fold[1]; var grd = ctx.createRadialGradient(5*w/8,5*w/8,w/4,w/2,w/2,w); grd.addColorStop(0,"transparent"); grd.addColorStop(1,"rgba(0,0,0,0.2)"); ctx.fillStyle = grd; ctx.scale (1,h/w); for (var i=0; i=0 && y this.pixels.length) { while (this.pixels.length < nb) { this.pixels.push([Math.random(), Math.random(), Math.random()*4+2]); } } return nb; }; /** @private */ ol.filter.Pointillism.prototype.precompose = function(/* e */) { }; /** @private */ ol.filter.Pointillism.prototype.postcompose = function(e) { // var ratio = e.frameState.pixelRatio; // Set back color hue var ctx = e.context; var canvas = ctx.canvas; var w = canvas.width; var h = canvas.height; // Grayscale image var img = document.createElement('canvas'); img.width = w; img.height = h; var ictx = img.getContext('2d'); ictx.filter = 'saturate('+Math.round(2*this.get('saturate')*100)+'%)'; ictx.drawImage(canvas, 0,0); ctx.save(); // Saturate and blur ctx.filter = 'blur(3px) saturate('+(this.get('saturate')*100)+'%)'; ctx.drawImage(canvas, 0,0); // ctx.clearRect(0,0,w,h); // debug // Draw points ctx.filter = 'none'; ctx.opacity = .5; var max = this._getPixels(w*h/50); for (var i=0; i} filters */ ol.filter.SVGFilter = function(filters) { ol.filter.Base.call(this); this._svg = {}; if (filters) { if (!(filters instanceof Array)) filters = [filters]; filters.forEach(function(f) { this.addSVGFilter(f); }.bind(this)); } }; ol.ext.inherits(ol.filter.SVGFilter, ol.filter.Base); /** Add a new svg filter * @param {ol.ext.SVGFilter} filter */ ol.filter.SVGFilter.prototype.addSVGFilter = function(filter) { var url = '#'+filter.getId(); this._svg[url] = 1; this.dispatchEvent({ type: 'propertychange', key: 'svg', oldValue: this._svg }); }; /** Remove a svg filter * @param {ol.ext.SVGFilter} filter */ ol.filter.SVGFilter.prototype.removeSVGFilter = function(filter) { var url = '#'+filter.getId(); delete this._svg[url] this.dispatchEvent({ type: 'propertychange', key: 'svg', oldValue: this._svg }); }; /** * @private */ ol.filter.SVGFilter.prototype.precompose = function() { }; /** * @private */ ol.filter.SVGFilter.prototype.postcompose = function(e) { var filter = [] // Set filters for (var f in this._svg) { filter.push('url('+f+')'); } filter = filter.join(' '); var canvas = document.createElement('canvas'); canvas.width = e.context.canvas.width; canvas.height = e.context.canvas.height; canvas.getContext('2d').drawImage(e.context.canvas,0,0); // Apply filter if (filter) { e.context.save(); e.context.clearRect(0,0,canvas.width, canvas.height); e.context.filter = filter; e.context.drawImage(canvas, 0,0); e.context.restore(); } }; /* 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). */ /** @typedef {Object} FilterTextureOptions * @property {Image | undefined} img Image object for the texture * @property {string} src Image source URI * @property {number} scale scale to draw the image. Default 1. * @property {number} [opacity] * @property {boolean} rotate Whether to rotate the texture with the view (may cause animation lags on mobile or slow devices). Default is true. * @property {null | string | undefined} crossOrigin The crossOrigin attribute for loaded images. */ /** Add texture effects on maps or layers * @constructor * @requires ol.filter * @extends {ol.filter.Base} * @param {FilterTextureOptions} options */ ol.filter.Texture = function(options) { ol.filter.Base.call(this, options); this.setFilter(options); } ol.ext.inherits(ol.filter.Texture, ol.filter.Base); /** Set texture * @param {FilterTextureOptions} [options] */ ol.filter.Texture.prototype.setFilter = function(options) { var img; options = options || {}; if (options.img) img = options.img; else { img = new Image(); if (options.src) { // Look for a texture stored in ol.filter.Texture.Image if (ol.filter.Texture.Image && ol.filter.Texture.Image[options.src]) { img.src = ol.filter.Texture.Image[options.src]; } // default source else { if (!img.src) img.src = options.src; } } img.crossOrigin = options.crossOrigin || null; } this.set('rotateWithView', options.rotateWithView !== false); this.set('opacity', typeof(options.opacity)=='number' ? options.opacity : 1); this.set('ready', false); var self = this; function setPattern(img) { self.pattern = {}; self.pattern.scale = options.scale || 1; self.pattern.canvas = document.createElement('canvas'); self.pattern.canvas.width = img.width * self.pattern.scale; self.pattern.canvas.height = img.height * self.pattern.scale; self.pattern.canvas.width = img.width;// * self.pattern.scale; self.pattern.canvas.height = img.height;// * self.pattern.scale; self.pattern.ctx = self.pattern.canvas.getContext("2d"); self.pattern.ctx.fillStyle = self.pattern.ctx.createPattern(img, 'repeat'); // Force refresh self.set('ready', true); } if (img.width) { setPattern(img); } else { img.onload = function() { setPattern(img); } } } /** Get translated pattern * @param {number} offsetX x offset * @param {number} offsetY y offset */ ol.filter.Texture.prototype.getPattern = function (offsetX, offsetY) { var c = this.pattern.canvas; var ctx = this.pattern.ctx; ctx.save(); /* offsetX /= this.pattern.scale; offsetY /= this.pattern.scale; ctx.scale(this.pattern.scale,this.pattern.scale); */ ctx.translate(-offsetX, offsetY); ctx.beginPath(); ctx.rect(offsetX, -offsetY, c.width, c.height); ctx.fill(); ctx.restore(); return ctx.createPattern(c, 'repeat'); } /** Draw pattern over the map on postcompose */ ol.filter.Texture.prototype.postcompose = function(e) { // not ready if (!this.pattern) return; // Set back color hue var ctx = e.context; var canvas = ctx.canvas; var m = 1.5 * Math.max(canvas.width, canvas.height); var mt = e.frameState.pixelToCoordinateTransform; // Old version (matrix) if (!mt) { mt = e.frameState.pixelToCoordinateMatrix, mt[2] = mt[4]; mt[3] = mt[5]; mt[4] = mt[12]; mt[5] = mt[13]; } var ratio = e.frameState.pixelRatio; var res = e.frameState.viewState.resolution; var w = canvas.width/2, h = canvas.height/2; ctx.save(); ctx.globalCompositeOperation = "multiply"; //ctx.globalCompositeOperation = "overlay"; //ctx.globalCompositeOperation = "color"; ctx.globalAlpha = this.get('opacity'); ctx.scale(ratio*this.pattern.scale,ratio*this.pattern.scale); if (this.get('rotateWithView')) { // Translate pattern res *= this.pattern.scale ctx.fillStyle = this.getPattern ((w*mt[0] + h*mt[1] + mt[4])/res, (w*mt[2] + h*mt[3] + mt[5])/res); // Rotate on canvas center and fill ctx.translate(w/this.pattern.scale, h/this.pattern.scale); ctx.rotate(e.frameState.viewState.rotation); ctx.beginPath(); ctx.rect(-w-m, -h-m, 2*m, 2*m); ctx.fill(); } else { /**/ var dx = -(w*mt[0] + h*mt[1] + mt[4])/res; var dy = (w*mt[2] + h*mt[3] + mt[5])/res; var cos = Math.cos(e.frameState.viewState.rotation); var sin = Math.sin(e.frameState.viewState.rotation); var offsetX = (dx*cos - dy*sin) / this.pattern.scale; var offsetY = (dx*sin + dy*cos) / this.pattern.scale; ctx.translate(offsetX, offsetY); ctx.beginPath(); ctx.fillStyle = this.pattern.ctx.fillStyle; ctx.rect(-offsetX -m , -offsetY -m, 2*m, 2*m); ctx.fill(); /* //old version without centered rotation var offsetX = -(e.frameState.extent[0]/res) % this.pattern.canvas.width; var offsetY = (e.frameState.extent[1]/res) % this.pattern.canvas.height; ctx.rotate(e.frameState.viewState.rotation); ctx.translate(offsetX, offsetY); ctx.beginPath(); ctx.fillStyle = this.pattern.ctx.fillStyle ctx.rect(-offsetX -m , -offsetY -m, 2*m, 2*m); ctx.fill(); */ } ctx.restore(); } /*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ /** Feature format for reading and writing data in the GeoJSONX format. * @constructor * @extends {ol.format.GeoJSON} * @param {*} options options. * @param {number} options.decimals number of decimals to save, default 7 for EPSG:4326, 2 for other projections * @param {boolean|Array<*>} options.deleteNullProperties An array of property values to remove, if false, keep all properties, default [null,undefined,""] * @param {boolean|Array<*>} options.extended Decode/encode extended GeoJSON with foreign members (id, bbox, title, etc.), default false * @param {Array|function} options.whiteList A list of properties to keep on features when encoding or a function that takes a property name and retrun true if the property is whitelisted * @param {Array|function} options.blackList A list of properties to remove from features when encoding or a function that takes a property name and retrun true if the property is blacklisted * @param {string} [options.layout='XY'] layout layout (XY or XYZ or XYZM) * @param {ol.ProjectionLike} options.dataProjection Projection of the data we are reading. If not provided `EPSG:4326` * @param {ol.ProjectionLike} options.featureProjection Projection of the feature geometries created by the format reader. If not provided, features will be returned in the dataProjection. */ ol.format.GeoJSONX = function(options) { options = options || {}; ol.format.GeoJSON.call (this, options); this._hash = {}; this._count = 0; this._extended = options.extended; if (typeof(options.whiteList)==='function') { this._whiteList = options.whiteList; } else if (options.whiteList && options.whiteList.indexOf) { this._whiteList = function (k) { return options.whiteList.indexOf(k) > -1 }; } else { this._whiteList = function() { return true }; } if (typeof(options.blackList)==='function') { this._blackList = options.blackList; } else if (options.blackList && options.blackList.indexOf) { this._blackList = function (k) { return options.blackList.indexOf(k) > -1 }; } else { this._blackList = function() { return false }; } this._deleteNull = options.deleteNullProperties===false ? false : [null,undefined,""]; var decimals = 2; if (!options.dataProjection || options.dataProjection === 'EPSG:4326') decimals = 7; if (!isNaN(parseInt(options.decimals))) decimals = parseInt(options.decimals); this._decimals = decimals; this.setLayout(options.layout || 'XY'); }; ol.ext.inherits(ol.format.GeoJSONX, ol.format.GeoJSON); /** Radix */ ol.format.GeoJSONX.prototype._radix = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ !#$%&\'()*-.:<=>?@[]^_`{|}~'; /** Radix size */ ol.format.GeoJSONX.prototype._size = ol.format.GeoJSONX.prototype._radix.length; /** GeoSJON types */ ol.format.GeoJSONX.prototype._type = { "Point": 0, "LineString": 1, "Polygon": 2, "MultiPoint": 3, "MultiLineString": 4, "MultiPolygon": 5, "GeometryCollection": null // Not supported }; /** GeoSJONX types */ ol.format.GeoJSONX.prototype._toType = [ "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" ]; /** Set geometry layout * @param {string} layout the geometry layout (XY or XYZ or XYZM) */ ol.format.GeoJSONX.prototype.setLayout = function(layout) { switch(layout) { case 'XYZ': case 'XYZM': { this._layout = layout; break; } default: { this._layout = 'XY'; break; } } }; /** Get geometry layout * @return {string} layout */ ol.format.GeoJSONX.prototype.getLayout = function() { return this._layout; }; /** Encode a number * @param {number} number Number to encode * @param {number} decimals Number of decimals * @param {string} */ ol.format.GeoJSONX.prototype.encodeNumber = function(number, decimals) { if (isNaN(Number(number)) || number === null || !isFinite(number)) { number = 0; } if (!decimals && decimals!==0) decimals = this._decimals; // Round number number = Math.round(number * Math.pow(10, decimals)); // Zigzag encoding (get positive number) if (number<0) number = -2*number - 1; else number = 2*number; // Encode var result = ''; var modulo, residual = number; while (true) { modulo = residual % this._size result = this._radix.charAt(modulo) + result; residual = Math.floor(residual / this._size); if (residual == 0) break; } return result; }; /** Decode a number * @param {string} s * @param {number} decimals Number of decimals * @return {number} */ ol.format.GeoJSONX.prototype.decodeNumber = function(s, decimals) { if (!decimals && decimals!==0) decimals = this._decimals; var decode = 0; s.split('').forEach(function (c) { decode = (decode * this._size) + this._radix.indexOf(c); }.bind(this)); // Zigzag encoding var result = Math.floor(decode/2) if (result !== decode/2) result = -1-result; return result / Math.pow(10, decimals); }; /** Encode coordinates * @param {ol.coordinate|Array} v * @param {number} decimal * @return {string|Array} * @api */ ol.format.GeoJSONX.prototype.encodeCoordinates = function(v, decimal) { var i, p, tp; if (typeof(v[0]) === 'number') { p = this.encodeNumber(v[0], decimal) +','+ this.encodeNumber(v[1], decimal); if (this._layout[2]=='Z' && v.length > 2) p += ',' + this.encodeNumber(v[i][2], 2); if (this._layout[3]=='M' && v.length > 3) p += ',' + this.encodeNumber(v[i][3], 0); return p; } else if (v.length && v[0]) { if (typeof(v[0][0]) === 'number') { var dxy = [0,0,0,0]; var xy = []; var hasZ = (this._layout[2]=='Z' && v[0].length > 2); var hasM = (this._layout[3]=='M' && v[0].length > 3); for (i=0; i} * @param {number} decimal Number of decimals * @return {ol.coordinate|Array} v * @api */ ol.format.GeoJSONX.prototype.decodeCoordinates = function(v, decimals) { var i, p; if (typeof(v) === 'string') { v = v.split(';'); if (v.length>1) { var pow = Math.pow(10,decimals); var dxy = [0,0,0,0]; v.forEach(function(vi, i) { v[i] = vi.split(','); v[i][0] = Math.round((this.decodeNumber(v[i][0], decimals) + dxy[0]) * pow) / pow; v[i][1] = Math.round((this.decodeNumber(v[i][1], decimals) + dxy[1]) * pow) / pow; if (v[i].length > 2) v[i][2] = Math.round((this.decodeNumber(v[i][2], 2) + dxy[2]) * pow) / pow; if (v[i].length > 3) v[i][3] = Math.round((this.decodeNumber(v[i][3], 0) + dxy[3]) * pow) / pow; dxy = v[i]; }.bind(this)); return v; } else { v = v[0].split(','); p = [ this.decodeNumber(v[0], decimals), this.decodeNumber(v[1], decimals) ]; if (v.length > 2) p[2] = this.decodeNumber(v[2], 2); if (v.length > 3) p[3] = this.decodeNumber(v[3], 0); return p; } } else if (v.length) { var r = []; for (i=0; i} features Features. * @param {*} options Write options. * @return {*} GeoJSONX Object. * @override * @api */ ol.format.GeoJSONX.prototype.writeFeaturesObject = function (features, options) { options = options || {}; this._count = 0; this._hash = {}; var geojson = ol.format.GeoJSON.prototype.writeFeaturesObject.call(this, features, options); geojson.decimals = this._decimals; geojson.hashProperties = []; Object.keys(this._hash).forEach(function(k) { geojson.hashProperties.push(k); }.bind(this)); this._count = 0; this._hash = {}; // Push features at the end of the object var temp = geojson.features; delete geojson.features; geojson.features = temp; return geojson; }; /** Encode a set of features as a GeoJSONX object. * @param {ol.Feature} feature Feature * @param {*} options Write options. * @return {*} GeoJSONX Object. * @override * @api */ ol.format.GeoJSONX.prototype.writeFeatureObject = function(source, options) { var f0 = ol.format.GeoJSON.prototype.writeFeatureObject.call(this, source, options); // Only features supported yet if (f0.type !== 'Feature') throw 'GeoJSONX doesn\'t support '+f0.type+'.'; var f = []; // Encode geometry if (f0.geometry.type==='Point') { f.push(this.encodeCoordinates(f0.geometry.coordinates, this._decimals)); } else if (f0.geometry.type==='MultiPoint') { var pts = []; f0.geometry.coordinates.forEach(function(p) { pts.push(this.encodeCoordinates(p, this._decimals)); }.bind(this)); f.push ([ this._type[f0.geometry.type], pts.join(';') ]); } else { if (!this._type[f0.geometry.type]) { throw 'GeoJSONX doesn\'t support '+f0.geometry.type+'.'; } f.push ([ this._type[f0.geometry.type], this.encodeCoordinates(f0.geometry.coordinates, this._decimals) ]); } // Encode properties var k; var prop = []; for (k in f0.properties) { if (!this._whiteList(k) || this._blackList(k)) continue; if (!this._hash.hasOwnProperty(k)) { this._hash[k] = this._count; this._count++; } if (!this._deleteNull || this._deleteNull.indexOf(f0.properties[k])<0) { prop.push (this._hash[k], f0.properties[k]); } } // Create prop table if (prop.length || this._extended) { f.push(prop); } // Other properties (id, title, bbox, centerline... if (this._extended) { var found = false; prop = {}; for (k in f0) { if (!/^type$|^geometry$|^properties$/.test(k)) { prop[k] = f0[k]; found = true; } } if (found) f.push(prop); } return f; }; /** Encode a geometry as a GeoJSONX object. * @param {ol.geom.Geometry} geometry Geometry. * @param {*} options Write options. * @return {*} Object. * @override * @api */ ol.format.GeoJSONX.prototype.writeGeometryObject = function(source, options) { var g = ol.format.GeoJSON.prototype.writeGeometryObject.call(this, source, options); // Encode geometry if (g.type==='Point') { return this.encodeCoordinates(g.coordinates, this._decimals) } else { return [ this._type[g.type], this.encodeCoordinates(g.coordinates, this._decimals) ]; } }; /** Decode a GeoJSONX object. * @param {*} object GeoJSONX * @param {*} options Read options. * @return {Array} * @override * @api */ ol.format.GeoJSONX.prototype.readFeaturesFromObject = function (object, options) { this._hashProperties = object.hashProperties || []; options = options || {}; options.decimals = parseInt(object.decimals); if (!options.decimals && options.decimals!==0) throw 'Bad file format...'; var features = ol.format.GeoJSON.prototype.readFeaturesFromObject.call(this, object, options); return features; }; /** Decode GeoJSONX Feature object. * @param {*} object GeoJSONX * @param {*} options Read options. * @return {ol.Feature} */ ol.format.GeoJSONX.prototype.readFeatureFromObject = function (f0, options) { var f = { type: 'Feature' } if (typeof(f0[0]) === 'string') { f.geometry = { type: 'Point', coordinates: this.decodeCoordinates(f0[0], typeof(options.decimals) === 'number' ? options.decimals : this.decimals) } } else { f.geometry = { type: this._toType[f0[0][0]] } if (f.geometry.type === 'MultiPoint') { var g = f.geometry.coordinates = []; var coords = f0[0][1].split(';'); coords.forEach(function(c) { c = c.split(','); g.push([this.decodeNumber(c[0], options.decimals), this.decodeNumber(c[1], options.decimals)]) }.bind(this)); } else { f.geometry.coordinates = this.decodeCoordinates(f0[0][1], typeof(options.decimals) === 'number' ? options.decimals : this.decimals); } } if (this._hashProperties && f0[1]) { f.properties = {}; var t = f0[1]; for (var i=0; i} v * @return {string|Array} * @api */ ol.format.GeoJSONP.prototype.encodeCoordinates = function(v) { var g; if (typeof(v[0]) === 'number') { g = new ol.geom.Point(v); return this._lineFormat.writeGeometry(g); } else if (v.length && v[0]) { var tab = (typeof(v[0][0]) === 'number'); if (tab) { g = new ol.geom.LineString(v); return this._lineFormat.writeGeometry(g); } else { var r = []; for (var i=0; i} * @return {ol.coordinate|Array} v * @api */ ol.format.GeoJSONP.prototype.decodeCoordinates = function(v) { var i, g; if (typeof(v) === 'string') { g = this._lineFormat.readGeometry(v); return g.getCoordinates()[0]; } else if (v.length) { var tab = (typeof(v[0]) === 'string'); var r = []; if (tab) { for (i=0; i} Features. * @api */ ol.format.GeoRSS.prototype.readFeatures = function(source, options) { var items; if (typeof(source)==='string') { var parser = new DOMParser(); var xmlDoc = parser.parseFromString(source,"text/xml"); items = xmlDoc.getElementsByTagName(this.getDocumentItemsTagName(xmlDoc)); } else if (source instanceof Document) { items = source.getElementsByTagName(this.getDocumentItemsTagName(source)); } else if (source instanceof Node) { items = source; } else { return []; } var features = [] for (var i=0, item; item = items[i]; i++) { var f = this.readFeature(item, options); if (f) features.push(f); } return features; }; /** * Get the tag name for the items in the XML Document depending if we are * dealing with an atom base document or not. * @param {Document} xmlDoc document to extract the tag name for the items * @return {string} tag name * @private */ ol.format.GeoRSS.prototype.getDocumentItemsTagName = function(xmlDoc) { switch (xmlDoc.documentElement.tagName) { case 'feed': return 'entry'; default: return 'item'; } } /** Clip interaction to clip layers in a circle * @constructor * @extends {ol.interaction.Pointer} * @param {ol.interaction.Clip.options} options flashlight param * @param {number} options.radius radius of the clip, default 100 * @param {ol.layer|Array} options.layers layers to clip */ ol.interaction.Clip = function(options) { this.layers_ = []; ol.interaction.Pointer.call(this, { handleDownEvent: this._setPosition, handleMoveEvent: this._setPosition }); this.precomposeBind_ = this.precompose_.bind(this); this.postcomposeBind_ = this.postcompose_.bind(this); // Default options options = options || {}; this.pos = false; this.radius = (options.radius||100); if (options.layers) this.addLayer(options.layers); }; ol.ext.inherits(ol.interaction.Clip, ol.interaction.Pointer); /** Set the map > start postcompose */ ol.interaction.Clip.prototype.setMap = function(map) { var i; if (this.getMap()) { for (i=0; i} layer to clip */ ol.interaction.Clip.prototype.addLayer = function(layers) { if (!(layers instanceof Array)) layers = [layers]; for (var i=0; i} layer to clip */ ol.interaction.Clip.prototype.removeLayer = function(layers) { if (!(layers instanceof Array)) layers = [layers]; for (var i=0; i} options.layers layers to clip * @param {number} [options.stiffness=20] spring stiffness coef, default 20 * @param {number} [options.damping=7] spring damping coef * @param {number} [options.mass=1] blob mass * @param {number} [options.points=10] number of points for the blob polygon * @param {number} [options.tension=.5] blob polygon spline tension * @param {number} [options.fuss] bob fussing factor * @param {number} [options.amplitude=1] blob deformation amplitude factor */ ol.interaction.Blob = function(options) { ol.interaction.Clip.call(this, options); }; ol.ext.inherits(ol.interaction.Blob, ol.interaction.Clip); /** Animate the blob * @private */ ol.interaction.Blob.prototype.precompose_ = function(e) { if (!this.getActive()) return; var ctx = e.context; var ratio = e.frameState.pixelRatio; ctx.save(); if (!this.pos) { ctx.beginPath(); ctx.moveTo (0,0); ctx.clip(); return; } var pt = [ this.pos[0], this.pos[1] ]; var tr = e.inversePixelTransform; if (tr) { pt = [ (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) ]; } else { pt[0] *= ratio; pt[1] *= ratio; } // Time laps if (!this.frame) this.frame = e.frameState.time; var dt = e.frameState.time - this.frame; this.frame = e.frameState.time; // Blob position pt = this._getCenter(pt, dt); // Blob geom var blob = this._calculate(dt); // Draw var p = blob[0]; ctx.beginPath(); ctx.moveTo (pt[0] + p[0], pt[1] + p[1]); for (var i=1; p=blob[i]; i++) { ctx.lineTo (pt[0] + p[0], pt[1] + p[1]); } ctx.clip(); e.frameState.animate = true; }; /** Get blob center with kinetic * @param {number} dt0 time laps * @private */ ol.interaction.Blob.prototype._getCenter = function(pt, dt0) { if (!this._center) { this._center = pt; this._velocity = [0, 0]; } else { var k = this.get('stiffness') || 20; // stiffness var d = -1* (this.get('damping') || 7); // damping var mass = Math.max(this.get('mass') || 1, .1); var dt = Math.min(dt0/1000, 1/30); var fSpring = [ k * (pt[0] - this._center[0]), k * (pt[1] - this._center[1]) ]; var fDamping = [ d * this._velocity[0], d * this._velocity[1] ]; var accel = [ (fSpring[0] + fDamping[0]) / mass, (fSpring[1] + fDamping[1]) / mass ]; this._velocity[0] += accel[0] * dt; this._velocity[1] += accel[1] * dt; this._center[0] += this._velocity[0] * dt; this._center[1] += this._velocity[1] * dt; } return this._center; }; /** Calculate the blob geom * @param {number} dt time laps * @returns {Array} * @private */ ol.interaction.Blob.prototype._calculate = function(dt) { var i, nb = this.get('points') || 10; if (!this._waves || this._waves.length !== nb) { this._waves = []; for (i = 0; i < nb; i++) { this._waves.push({ angle: Math.random()*Math.PI, noise: Math.random() }); } } var blob = []; var speed = (this._velocity[0]*this._velocity[0] + this._velocity[1]*this._velocity[1]) / 500; this._rotation = (this._rotation||0) + (this._velocity[0]>0 ? 1 : -1) * Math.min(.015, speed/70000*dt); for (i = 0; i < nb; i++) { var angle = i * 2 * Math.PI / nb + this._rotation; var radius = this.radius + Math.min(this.radius, speed); var delta = Math.cos(this._waves[i].angle) * radius/4 * this._waves[i].noise * (this.get('amplitude') || 1); blob.push([ (this.radius + delta) * Math.cos(angle), (this.radius + delta) * Math.sin(angle) ]); // Add noise this._waves[i].angle += (Math.PI + Math.random() + speed/200)/350 * dt * (this.get('fuss') || 1); this._waves[i].noise = Math.min(1, Math.max(0, this._waves[i].noise + (Math.random() - .5) *.1 *(this.get('fuss') || 1))); } blob.push(blob[0]); return ol.coordinate.cspline(blob, { tension: this.get('tension') }); }; /* 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). */ /** Handles coordinates on the center of the viewport. * It can be used as abstract base class used for creating subclasses. * The CenterTouch interaction modifies map browser event coordinate and pixel properties to force pointer on the viewport center to any interaction that them. * Only pointermove pointerup are concerned with it. * @constructor * @extends {ol.interaction.Interaction} * @param {olx.interaction.InteractionOptions} options Options * @param {ol.style.Style|Array} options.targetStyle a style to draw the target point, default cross style * @param {string} options.composite composite operation for the target : difference|multiply|xor|screen|overlay|darken|lighter|lighten|... */ ol.interaction.CenterTouch = function(options) { options = options || {}; // LIst of listerner on the object this._listener = {}; // Filter event var rex = /^pointermove$|^pointerup$/; // Interaction to defer center on top of the interaction // this is done to enable other coordinates manipulation inserted after the interaction (snapping) this.ctouch = new ol.interaction.Interaction({ handleEvent: function(e) { if (rex.test(e.type) && this.getMap()) { e.coordinate = this.getMap().getView().getCenter(); e.pixel = this.getMap().getSize(); e.pixel = [ e.pixel[0]/2, e.pixel[1]/2 ]; } return true; } }); // Target on map center this._target = new ol.control.Target({ style: options.targetStyle, composite: options.composite }); ol.interaction.Interaction.call(this, { handleEvent: function(e) { if (rex.test(e.type)) this.pos_ = e.coordinate; if (options.handleEvent) return options.handleEvent.call (this,e); return true; } }); }; ol.ext.inherits(ol.interaction.CenterTouch, ol.interaction.Interaction); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {_ol_Map_} map Map. * @api stable */ ol.interaction.CenterTouch.prototype.setMap = function(map) { if (this.getMap()) { this.getMap().removeInteraction(this.ctouch); this.getMap().removeInteraction(this._target); } ol.interaction.Interaction.prototype.setMap.call (this, map); if (this.getMap()) { if (this.getActive()) { this.getMap().addInteraction(this.ctouch); this.getMap().addControl(this._target); } } }; /** * Activate or deactivate the interaction. * @param {boolean} active Active. * @observable * @api */ ol.interaction.CenterTouch.prototype.setActive = function(b) { ol.interaction.Interaction.prototype.setActive.call (this, b); this.pos_ = null; if (this.getMap()) { if (this.getActive()) { this.getMap().addInteraction(this.ctouch); this.getMap().addControl(this._target); } else { this.getMap().removeInteraction(this.ctouch); this.getMap().removeControl(this._target); } } }; /** Get the position of the target * @return {ol.coordinate} */ ol.interaction.CenterTouch.prototype.getPosition = function () { if (!this.pos_) { var px =this.getMap().getSize(); px = [ px[0]/2, px[1]/2 ]; this.pos_ = this.getMap().getCoordinateFromPixel(px); } return this.pos_; }; /** An interaction to copy/paste features on a map. * It will fire a 'focus' event on the map when map is focused (use mapCondition option to handle the condition when the map is focused). * @constructor * @fires focus * @fires copy * @fires paste * @extends {ol.interaction.Interaction} * @param {Object} options Options * @param {function} options.condition a function that takes a mapBrowserEvent and return the action to perform: 'copy', 'cut' or 'paste', default Ctrl+C / Ctrl+V * @param {function} options.mapCondition a function that takes a mapBrowserEvent and return true if the map is the active map, default always returns true * @param {ol.Collection} options.features list of features to copy * @param {ol.source.Vector | Array} options.sources the source to copy from (used for cut), if not defined, it will use the destination * @param {ol.source.Vector} options.destination the source to copy to */ ol.interaction.CopyPaste = function(options) { options = options || {}; // Features to copy this.features = []; this._cloneFeature = true; var condition = options.condition; if (typeof (condition) !== 'function') { condition = function (e) { if (e.originalEvent.ctrlKey) { if (/^c$/i.test(e.originalEvent.key)) return 'copy'; if (/^x$/i.test(e.originalEvent.key)) return 'cut'; if (/^v$/i.test(e.originalEvent.key)) return 'paste'; } return false; } } this._featuresSource = options.features || new ol.Collection(); this.setSources(options.sources); this.setDestination(options.destination); // Create intreaction ol.interaction.CurrentMap.call(this, { condition: options.mapCondition, onKeyDown: function (e) { switch (condition(e)) { case 'copy': { this.copy({ silent: false }); break; } case 'cut': { this.copy({ cut: true, silent: false }); break; } case 'paste': { this.paste({ silent: false }); break; } default: break; } }.bind(this) }); }; ol.ext.inherits(ol.interaction.CopyPaste, ol.interaction.CurrentMap); /** Sources to cut feature from * @param { ol.source.Vector | Array } sources */ ol.interaction.CopyPaste.prototype.setSources = function (sources) { if (sources) { this._source = []; this._source = sources instanceof Array ? sources : [sources]; } else { this._source = null; } }; /** Get sources to cut feature from * @return { Array } */ ol.interaction.CopyPaste.prototype.getSources = function () { return this._source; }; /** Source to paste features * @param { ol.source.Vector } source */ ol.interaction.CopyPaste.prototype.setDestination = function (destination) { this._destination = destination; }; /** Get source to paste features * @param { ol.source.Vector } */ ol.interaction.CopyPaste.prototype.getDestination = function () { return this._destination; }; /** Get current feature to copy * @return {Array} */ ol.interaction.CopyPaste.prototype.getFeatures = function() { return this.features; }; /** Set current feature to copy * @param {Object} options * @param {Array | ol.Collection} options.features feature to copy, default get in the provided collection * @param {boolean} options.cut try to cut feature from the sources, default false * @param {boolean} options.silent true to send an event, default true */ ol.interaction.CopyPaste.prototype.copy = function (options) { options = options || {}; var features = options.features || this._featuresSource.getArray(); // Try to remove feature from sources if (options.cut) { var sources = this._source || [this._destination]; // Remove feature from sources features.forEach(function(f) { sources.forEach(function(source) { try { source.removeFeature(f); } catch(e) {/*ok*/} }); }); } if (this._cloneFeature) { this.features = []; features.forEach(function(f) { this.features.push(f.clone()); }.bind(this)); } else { this.features = features; } // Send an event if (options.silent===false) this.dispatchEvent({ type: options.cut ? 'cut' : 'copy', time: (new Date).getTime() }); }; /** Paste features * @param {Object} options * @param {Array | ol.Collection} features feature to copy, default get current features * @param {ol.source.Vector} options.destination Source to paste to, default the current source * @param {boolean} options.silent true to send an event, default true */ ol.interaction.CopyPaste.prototype.paste = function(options) { options = options || {}; var features = options.features || this.features; if (features) { var destination = options.destination || this._destination; if (destination) { destination.addFeatures(this.features); if (this._cloneFeature) this.copy({ features: this.features }); } } // Send an event if (options.silent===false) this.dispatchEvent({ type:'paste', features: features, time: (new Date).getTime() }); }; /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** A Select interaction to delete features on click. * @constructor * @extends {ol.interaction.Interaction} * @fires deletestart * @fires deleteend * @param {*} options ol.interaction.Select options */ ol.interaction.Delete = function(options) { ol.interaction.Select.call(this, options); this.on('select', function(e) { this.getFeatures().clear(); this.delete(e.selected); }.bind(this)); }; ol.ext.inherits(ol.interaction.Delete, ol.interaction.Select); /** Get vector source of the map * @return {Array} */ ol.interaction.Delete.prototype._getSources = function(layers) { if (!this.getMap()) return []; if (!layers) layers = this.getMap().getLayers(); var sources = []; layers.forEach(function (l) { // LayerGroup if (l.getLayers) { sources = sources.concat(this._getSources(l.getLayers())); } else { if (l.getSource && l.getSource() instanceof ol.source.Vector) { sources.push(l.getSource()); } } }.bind(this)); return sources; }; /** Delete features: remove the features from the map (from all layers in the map) * @param {ol.Collection|Array} features The features to delete * @api */ ol.interaction.Delete.prototype.delete = function(features) { if (features && (features.length || features.getLength())) { this.dispatchEvent({ type: 'deletestart', features: features }); var delFeatures = []; // Get the sources concerned this._getSources().forEach(function (source) { try { // Try to delete features in the source features.forEach(function(f) { source.removeFeature(f); delFeatures.push(f); }); } catch(e) { /* ok */ } }) this.dispatchEvent({ type: 'deleteend', features: delFeatures }); } }; /** Drag an overlay on the map * @constructor * @extends {ol.interaction.Pointer} * @fires dragstart * @fires dragging * @fires dragend * @param {any} options * @param {ol.Overlay|Array} options.overlays the overlays to drag * @param {ol.Size} options.offset overlay offset, default [0,0] */ ol.interaction.DragOverlay = function(options) { if (!options) options = {}; var offset = options.offset || [0,0]; // Extend pointer ol.interaction.Pointer.call(this, { // start draging on an overlay handleDownEvent: function(evt) { var res = evt.frameState.viewState.resolution var coordinate = [evt.coordinate[0] + offset[0]*res, evt.coordinate[1] - offset[1]*res]; // Click on a button (closeBox) or on a link: don't drag! if (/^(BUTTON|A)$/.test(evt.originalEvent.target.tagName)) { this._dragging = false; return true; } // Start dragging if (this._dragging) { if (options.centerOnClick !== false) { this._dragging.setPosition(coordinate, true); } else { coordinate = this._dragging.getPosition(); } this.dispatchEvent({ type: 'dragstart', overlay: this._dragging, originalEvent: evt.originalEvent, frameState: evt.frameState, coordinate: coordinate }); return true; } return false; }, // Drag handleDragEvent: function(evt) { var res = evt.frameState.viewState.resolution var coordinate = [evt.coordinate[0] + offset[0]*res, evt.coordinate[1] - offset[1]*res]; if (this._dragging) { this._dragging.setPosition(coordinate, true); this.dispatchEvent({ type: 'dragging', overlay: this._dragging, originalEvent: evt.originalEvent, frameState: evt.frameState, coordinate: coordinate }); } }, // Stop dragging handleUpEvent: function(evt) { var res = evt.frameState.viewState.resolution var coordinate = [evt.coordinate[0] + offset[0]*res, evt.coordinate[1] - offset[1]*res]; if (this._dragging) { this.dispatchEvent({ type: 'dragend', overlay: this._dragging, originalEvent: evt.originalEvent, frameState: evt.frameState, coordinate: coordinate }); this._dragging = false; return true; } return false; } }); // List of overlays / listeners this._overlays = []; if (!(options.overlays instanceof Array)) options.overlays = [options.overlays]; options.overlays.forEach(this.addOverlay.bind(this)); }; ol.ext.inherits(ol.interaction.DragOverlay, ol.interaction.Pointer); /** Add an overlay to the interacton * @param {ol.Overlay} ov */ ol.interaction.DragOverlay.prototype.addOverlay = function (ov) { for (var i=0, o; o=this._overlays[i]; i++) { if (o===ov) return; } // Stop event overlay if (ov.element.parentElement && ov.element.parentElement.classList.contains('ol-overlaycontainer-stopevent')) { console.warn('[DragOverlay.addOverlay] overlay must be created with stopEvent set to false!'); return; } // Add listener on overlay of the same map var handler = function() { if (this.getMap()===ov.getMap()) this._dragging = ov; }.bind(this); this._overlays.push({ overlay: ov, listener: handler }); ov.element.addEventListener('pointerdown', handler); }; /** Remove an overlay from the interacton * @param {ol.Overlay} ov */ ol.interaction.DragOverlay.prototype.removeOverlay = function (ov) { for (var i=0, o; o=this._overlays[i]; i++) { if (o.overlay===ov) { var l = this._overlays.splice(i,1)[0]; ov.element.removeEventListener('pointerdown', l.listener); break; } } }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Interaction to draw holes in a polygon. * It fires a drawstart, drawend event when drawing the hole * and a modifystart, modifyend event before and after inserting the hole in the feature geometry. * @constructor * @extends {ol.interaction.Interaction} * @fires drawstart * @fires drawend * @fires modifystart * @fires modifyend * @param {olx.interaction.DrawHoleOptions} options extend olx.interaction.DrawOptions * @param {Array | function | undefined} options.layers A list of layers from which polygons should be selected. Alternatively, a filter function can be provided. default: all visible layers * @param {Array | ol.Collection | function | undefined} options.features An array or a collection of features the interaction applies on or a function that takes a feature and a layer and returns true if the feature is a candidate * @param { ol.style.Style | Array | StyleFunction | undefined } Style for the selected features, default: default edit style */ ol.interaction.DrawHole = function(options) { if (!options) options = {}; var self = this; // Select interaction for the current feature this._select = new ol.interaction.Select({ style: options.style }); this._select.setActive(false); // Geometry function that test points inside the current var geometryFn, geomFn = options.geometryFunction; if (geomFn) { geometryFn = function(c,g) { g = self._geometryFn (c, g); return geomFn (c,g); } } else { geometryFn = function(c,g) { return self._geometryFn (c, g); } } // Create draw interaction options.type = "Polygon"; options.geometryFunction = geometryFn; ol.interaction.Draw.call(this, options); // Layer filter function if (options.layers) { if (typeof (options.layers) === 'function') { this.layers_ = options.layers; } else if (options.layers.indexOf) { this.layers_ = function(l) { return (options.layers.indexOf(l) >= 0); }; } } // Features to apply on if (typeof(options.features) === 'function') { this._features = options.features; } else if (options.features) { var features = options.features; this._features = function(f) { if (features.indexOf) { return !!features[features.indexOf(f)]; } else { return !!features.item(features.getArray().indexOf(f)); } } } else { this._features = function() { return true } } // Start drawing if inside a feature this.on('drawstart', this._startDrawing.bind(this)); // End drawing add the hole to the current Polygon this.on('drawend', this._finishDrawing.bind(this)); }; ol.ext.inherits(ol.interaction.DrawHole, ol.interaction.Draw); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.DrawHole.prototype.setMap = function(map) { if (this.getMap()) this.getMap().removeInteraction(this._select); if (map) map.addInteraction(this._select); ol.interaction.Draw.prototype.setMap.call (this, map); }; /** * Activate/deactivate the interaction * @param {boolean} * @api stable */ ol.interaction.DrawHole.prototype.setActive = function(b) { this._select.getFeatures().clear(); ol.interaction.Draw.prototype.setActive.call (this, b); }; /** * Remove last point of the feature currently being drawn * (test if points to remove before). */ ol.interaction.DrawHole.prototype.removeLastPoint = function() { if (this._feature && this._feature.getGeometry().getCoordinates()[0].length>2) { ol.interaction.Draw.prototype.removeLastPoint.call(this); } }; /** * Get the current polygon to hole * @return {ol.Feature} */ ol.interaction.DrawHole.prototype.getPolygon = function() { return this._polygon; // return this._select.getFeatures().item(0).getGeometry(); }; /** * Get current feature to add a hole and start drawing * @param {ol.interaction.Draw.Event} e * @private */ ol.interaction.DrawHole.prototype._startDrawing = function(e) { var map = this.getMap(); this._feature = e.feature; var coord = e.feature.getGeometry().getCoordinates()[0][0]; this._current = null; // Check object under the pointer map.forEachFeatureAtPixel( map.getPixelFromCoordinate(coord), function(feature, layer) { if (this._features(feature, layer)) { var poly = feature.getGeometry(); if (poly.getType() === "Polygon" && poly.intersectsCoordinate(coord)) { this._polygonIndex = false; this._polygon = poly; this._current = feature; } else if (poly.getType() === "MultiPolygon" && poly.intersectsCoordinate(coord)) { for (var i=0, p; p=poly.getPolygon(i); i++) { if (p.intersectsCoordinate(coord)) { this._polygonIndex = i; this._polygon = p; this._current = feature; break; } } } } }.bind(this), { layerFilter: this.layers_ } ); this._select.getFeatures().clear(); if (!this._current) { this.setActive(false); this.setActive(true); } else { this._select.getFeatures().push(this._current); } }; /** * Stop drawing and add the sketch feature to the target feature. * @param {ol.interaction.Draw.Event} e * @private */ ol.interaction.DrawHole.prototype._finishDrawing = function(e) { // The feature is the hole e.hole = e.feature; // Get the current feature e.feature = this._select.getFeatures().item(0); this.dispatchEvent({ type: 'modifystart', features: [ this._current ] }); // Create the hole var c = e.hole.getGeometry().getCoordinates()[0]; if (c.length > 3) { if (this._polygonIndex!==false) { var geom = e.feature.getGeometry(); var newGeom = new ol.geom.MultiPolygon([]); for (var i=0, pi; pi=geom.getPolygon(i); i++) { if (i===this._polygonIndex) { pi.appendLinearRing(new ol.geom.LinearRing(c)); newGeom.appendPolygon(pi); } else { newGeom.appendPolygon(pi); } } e.feature.setGeometry(newGeom); } else { this.getPolygon().appendLinearRing(new ol.geom.LinearRing(c)); } } this.dispatchEvent({ type: 'modifyend', features: [ this._current ] }); // reset this._feature = null; this._select.getFeatures().clear(); }; /** * Function that is called when a geometry's coordinates are updated. * @param {Array} coordinates * @param {ol.geom.Polygon} geometry * @return {ol.geom.Polygon} * @private */ ol.interaction.DrawHole.prototype._geometryFn = function(coordinates, geometry) { var coord = coordinates[0].pop(); if (!this.getPolygon() || this.getPolygon().intersectsCoordinate(coord)) { this.lastOKCoord = [coord[0],coord[1]]; } coordinates[0].push([this.lastOKCoord[0],this.lastOKCoord[1]]); if (geometry) { geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])]); } else { geometry = new ol.geom.Polygon(coordinates); } return geometry; }; /* 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). */ /** Interaction rotate * @constructor * @extends {ol.interaction.Interaction} * @fires drawstart, drawing, drawend, drawcancel * @param {olx.interaction.TransformOptions} options * @param {Array} options.source Destination source for the drawn features * @param {ol.Collection} options.features Destination collection for the drawn features * @param {ol.style.Style | Array. | ol.style.StyleFunction | undefined} options.style style for the sketch * @param {integer} options.sides number of sides, default 0 = circle * @param { ol.events.ConditionType | undefined } options.condition A function that takes an ol.MapBrowserEvent and returns a boolean that event should be handled. By default module:ol/events/condition.always. * @param { ol.events.ConditionType | undefined } options.squareCondition A function that takes an ol.MapBrowserEvent and returns a boolean to draw square features. Default test shift key * @param { ol.events.ConditionType | undefined } options.centerCondition A function that takes an ol.MapBrowserEvent and returns a boolean to draw centered features. Default check Ctrl key * @param { bool } options.canRotate Allow rotation when centered + square, default: true * @param { string } [options.geometryName=geometry] * @param { number } options.clickTolerance click tolerance on touch devices, default: 6 * @param { number } options.maxCircleCoordinates Maximum number of point on a circle, default: 100 */ ol.interaction.DrawRegular = function(options) { if (!options) options={}; this.squaredClickTolerance_ = options.clickTolerance ? options.clickTolerance * options.clickTolerance : 36; this.maxCircleCoordinates_ = options.maxCircleCoordinates || 100; // Collection of feature to transform this.features_ = options.features; // List of layers to transform this.source_ = options.source; // Square condition this.conditionFn_ = options.condition; // Square condition this.squareFn_ = options.squareCondition; // Centered condition this.centeredFn_ = options.centerCondition; // Allow rotation when centered + square this.canRotate_ = (options.canRotate !== false); // Specify custom geometry name this.geometryName_ = options.geometryName; // Number of sides (default=0: circle) this.setSides(options.sides); // Style var defaultStyle = ol.style.Style.defaultStyle(true); // Create a new overlay layer for the sketch this.sketch_ = new ol.Collection(); this.overlayLayer_ = new ol.layer.Vector({ source: new ol.source.Vector({ features: this.sketch_, useSpatialIndex: false }), name:'DrawRegular overlay', displayInLayerSwitcher: false, style: options.style || defaultStyle }); ol.interaction.Interaction.call(this, { /* handleDownEvent: this.handleDownEvent_, handleMoveEvent: this.handleMoveEvent_, handleUpEvent: this.handleUpEvent_, */ handleEvent: this.handleEvent_ }); }; ol.ext.inherits(ol.interaction.DrawRegular, ol.interaction.Interaction); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.DrawRegular.prototype.setMap = function(map) { if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_); ol.interaction.Interaction.prototype.setMap.call (this, map); this.overlayLayer_.setMap(map); }; /** * Activate/deactivate the interaction * @param {boolean} * @api stable */ ol.interaction.DrawRegular.prototype.setActive = function(b) { this.reset(); ol.interaction.Interaction.prototype.setActive.call (this, b); } /** * Reset the interaction * @api stable */ ol.interaction.DrawRegular.prototype.reset = function() { this.overlayLayer_.getSource().clear(); this.started_ = false; } /** * Set the number of sides. * @param {int} number of sides. * @api stable */ ol.interaction.DrawRegular.prototype.setSides = function (nb) { nb = parseInt(nb); this.sides_ = nb>2 ? nb : 0; } /** * Allow rotation when centered + square * @param {bool} * @api stable */ ol.interaction.DrawRegular.prototype.canRotate = function (b) { if (b===true || b===false) this.canRotate_ = b; return this.canRotate_; } /** * Get the number of sides. * @return {int} number of sides. * @api stable */ ol.interaction.DrawRegular.prototype.getSides = function () { return this.sides_; } /** Default start angle array for each sides */ ol.interaction.DrawRegular.prototype.startAngle = { 'default':Math.PI/2, 3: -Math.PI/2, 4: Math.PI/4 }; /** Get geom of the current drawing * @return {ol.geom.Polygon | ol.geom.Point} */ ol.interaction.DrawRegular.prototype.getGeom_ = function () { this.overlayLayer_.getSource().clear(); if (!this.center_) return false; var g; if (this.coord_) { var center = this.center_; var coord = this.coord_; // Specific case: circle var d, dmax, r, circle, centerPx; if (!this.sides_ && this.square_ && !this.centered_) { center = [(coord[0] + center[0])/2, (coord[1] + center[1])/2]; d = [coord[0] - center[0], coord[1] - center[1]]; r = Math.sqrt(d[0]*d[0]+d[1]*d[1]); circle = new ol.geom.Circle(center, r, 'XY'); // Optimize points on the circle centerPx = this.getMap().getPixelFromCoordinate(center); dmax = Math.max (100, Math.abs(centerPx[0]-this.coordPx_[0]), Math.abs(centerPx[1]-this.coordPx_[1])); dmax = Math.min ( this.maxCircleCoordinates_, Math.round(dmax / 3 )); return ol.geom.Polygon.fromCircle (circle, dmax, 0); } else { var hasrotation = this.canRotate_ && this.centered_ && this.square_; d = [coord[0] - center[0], coord[1] - center[1]]; if (this.square_ && !hasrotation) { //var d = [coord[0] - center[0], coord[1] - center[1]]; var dm = Math.max (Math.abs(d[0]), Math.abs(d[1])); coord = [ center[0] + (d[0]>0 ? dm:-dm), center[1] + (d[1]>0 ? dm:-dm) ]; } r = Math.sqrt(d[0]*d[0]+d[1]*d[1]); if (r>0) { circle = new ol.geom.Circle(center, r, 'XY'); var a; if (hasrotation) a = Math.atan2(d[1], d[0]); else a = this.startAngle[this.sides_] || this.startAngle['default']; if (this.sides_) { g = ol.geom.Polygon.fromCircle (circle, this.sides_, a); } else { // Optimize points on the circle centerPx = this.getMap().getPixelFromCoordinate(this.center_); dmax = Math.max (100, Math.abs(centerPx[0]-this.coordPx_[0]), Math.abs(centerPx[1]-this.coordPx_[1])); dmax = Math.min ( this.maxCircleCoordinates_, Math.round(dmax / (this.centered_ ? 3:5) )); g = ol.geom.Polygon.fromCircle (circle, dmax, 0); } if (hasrotation) return g; // Scale polygon to fit extent var ext = g.getExtent(); if (!this.centered_) center = this.center_; else center = [ 2*this.center_[0]-this.coord_[0], 2*this.center_[1]-this.coord_[1] ]; var scx = (center[0] - coord[0]) / (ext[0] - ext[2]); var scy = (center[1] - coord[1]) / (ext[1] - ext[3]); if (this.square_) { var sc = Math.min(Math.abs(scx),Math.abs(scy)); scx = Math.sign(scx)*sc; scy = Math.sign(scy)*sc; } var t = [ center[0] - ext[0]*scx, center[1] - ext[1]*scy ]; g.applyTransform(function(g1, g2, dim) { for (var i=0; i return a point return new ol.geom.Point(this.center_); }; /** Draw sketch * @return {ol.Feature} The feature being drawn. */ ol.interaction.DrawRegular.prototype.drawSketch_ = function(evt) { this.overlayLayer_.getSource().clear(); if (evt) { this.square_ = this.squareFn_ ? this.squareFn_(evt) : evt.originalEvent.shiftKey; this.centered_ = this.centeredFn_ ? this.centeredFn_(evt) : evt.originalEvent.metaKey || evt.originalEvent.ctrlKey; var g = this.getGeom_(); if (g) { var f = this.feature_; //f.setGeometry (g); if (g.getType()==='Polygon') f.getGeometry().setCoordinates(g.getCoordinates()); this.overlayLayer_.getSource().addFeature(f); if (this.coord_ && this.square_ && ((this.canRotate_ && this.centered_ && this.coord_) || (!this.sides_ && !this.centered_))) { this.overlayLayer_.getSource().addFeature(new ol.Feature(new ol.geom.LineString([this.center_,this.coord_]))); } return f; } } }; /** Draw sketch (Point) */ ol.interaction.DrawRegular.prototype.drawPoint_ = function(pt, noclear) { if (!noclear) this.overlayLayer_.getSource().clear(); this.overlayLayer_.getSource().addFeature(new ol.Feature(new ol.geom.Point(pt))); }; /** * @param {ol.MapBrowserEvent} evt Map browser event. */ ol.interaction.DrawRegular.prototype.handleEvent_ = function(evt) { var dx, dy; // Event date time this._eventTime = new Date(); switch (evt.type) { case "pointerdown": { if (this.conditionFn_ && !this.conditionFn_(evt)) break; this.downPx_ = evt.pixel; this.start_(evt); // Test long touch var dt = 500; this._longTouch = false; setTimeout(function() { this._longTouch = (new Date() - this._eventTime > .9*dt); if (this._longTouch) this.handleMoveEvent_(evt); }.bind(this), dt); break; } case "pointerup": { // Started and fisrt move if (this.started_ && this.coord_) { dx = this.downPx_[0] - evt.pixel[0]; dy = this.downPx_[1] - evt.pixel[1]; if (dx*dx + dy*dy <= this.squaredClickTolerance_) { // The pointer has moved if ( this.lastEvent == "pointermove" || this.lastEvent == "keydown" ) { this.end_(evt); } // On touch device there is no move event : terminate = click on the same point else { dx = this.upPx_[0] - evt.pixel[0]; dy = this.upPx_[1] - evt.pixel[1]; if ( dx*dx + dy*dy <= this.squaredClickTolerance_) { this.end_(evt); } else { this.handleMoveEvent_(evt); this.drawPoint_(evt.coordinate,true); } } } } this.upPx_ = evt.pixel; break; } case "pointerdrag": { if (this.started_) { var centerPx = this.getMap().getPixelFromCoordinate(this.center_); dx = centerPx[0] - evt.pixel[0]; dy = centerPx[1] - evt.pixel[1]; if (dx*dx + dy*dy <= this.squaredClickTolerance_) { this.reset(); } } return !this._longTouch; // break; } case "pointermove": { if (this.started_) { dx = this.downPx_[0] - evt.pixel[0]; dy = this.downPx_[1] - evt.pixel[1]; if (dx*dx + dy*dy > this.squaredClickTolerance_) { this.handleMoveEvent_(evt); this.lastEvent = evt.type; } } break; } default: { this.lastEvent = evt.type; // Prevent zoom in on dblclick if (this.started_ && evt.type==='dblclick') { //evt.stopPropagation(); return false; } break; } } return true; } /** Stop drawing. */ ol.interaction.DrawRegular.prototype.finishDrawing = function() { if (this.started_ && this.coord_) { this.end_({ pixel: this.upPx_, coordinate: this.coord_}); } }; /** * @param {ol.MapBrowserEvent} evt Event. */ ol.interaction.DrawRegular.prototype.handleMoveEvent_ = function(evt) { if (this.started_) { this.coord_ = evt.coordinate; this.coordPx_ = evt.pixel; var f = this.drawSketch_(evt); this.dispatchEvent({ type:'drawing', feature: f, pixel: evt.pixel, startCoordinate: this.center_, coordinate: evt.coordinate, square: this.square_, centered: this.centered_ }); } else { this.drawPoint_(evt.coordinate); } }; /** Start an new draw * @param {ol.MapBrowserEvent} evt Map browser event. * @return {boolean} `false` to stop the drag sequence. */ ol.interaction.DrawRegular.prototype.start_ = function(evt) { if (!this.started_) { this.started_ = true; this.center_ = evt.coordinate; this.coord_ = null; var f = this.feature_ = new ol.Feature({}); f.setGeometryName(this.geometryName_); f.setGeometry(new ol.geom.Polygon([[evt.coordinate,evt.coordinate,evt.coordinate]])); this.drawSketch_(evt); this.dispatchEvent({ type:'drawstart', feature: f, pixel: evt.pixel, coordinate: evt.coordinate }); } else { this.coord_ = evt.coordinate; } }; /** End drawing * @param {ol.MapBrowserEvent} evt Map browser event. * @return {boolean} `false` to stop the drag sequence. */ ol.interaction.DrawRegular.prototype.end_ = function(evt) { this.coord_ = evt.coordinate; this.started_ = false; if (this.coord_ && (this.center_[0]!==this.coord_[0] || this.center_[1]!==this.coord_[1])) { var f = this.feature_; f.setGeometry(this.getGeom_()); if (this.source_) this.source_.addFeature(f); else if (this.features_) this.features_.push(f); this.dispatchEvent({ type:'drawend', feature: f, pixel: evt.pixel, coordinate: evt.coordinate, square: this.square_, centered: this.centered_ }); } else { this.dispatchEvent({ type:'drawcancel', feature: null, pixel: evt.pixel, coordinate: evt.coordinate, square: this.square_, centered: this.centered_ }); } this.center_ = this.coord_ = null; this.drawSketch_(); }; /* 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). */ /** Interaction DrawTouch : pointer is deferred to the center of the viewport and a target is drawn to materialize this point * The interaction modifies map browser event coordinate and pixel properties to force pointer on the viewport center to any interaction that them. * @constructor * @fires drawstart * @fires drawend * @fires drawabort * @extends {ol.interaction.CenterTouch} * @param {olx.interaction.DrawOptions} options * @param {ol.source.Vector | undefined} options.source Destination source for the drawn features. * @param {ol.geom.GeometryType} options.type Drawing type ('Point', 'LineString', 'Polygon') not ('MultiPoint', 'MultiLineString', 'MultiPolygon' or 'Circle'). Required. * @param {boolean} [options.tap=true] enable point insertion on tap, default true * @param {ol.style.Style|Array} [options.style] Drawing style * @param {ol.style.Style|Array} [options.sketchStyle] Sketch style * @param {ol.style.Style|Array} [options.targetStyle] a style to draw the target point, default cross style * @param {string} [options.composite] composite operation : difference|multiply|xor|screen|overlay|darken|lighter|lighten|... */ ol.interaction.DrawTouch = function(options) { options = options||{}; options.handleEvent = function(e) { if (this.get('tap')) { this.sketch.setPosition(this.getPosition()); switch (e.type) { case 'singleclick': { this.addPoint(); break; } case 'dblclick': { this.addPoint(); this.finishDrawing(); return false; //break; } default: break; } } return true; } if (!options.sketchStyle) { options.sketchStyle = ol.style.Style.defaultStyle(); } var sketch = this.sketch = new ol.layer.SketchOverlay(options); sketch.on(['drawstart', 'drawabort'], function(e) { this.dispatchEvent(e); }.bind(this)); sketch.on(['drawend'], function(e) { if (e.feature && e.valid && options.source) options.source.addFeature(e.feature); this.dispatchEvent(e); }.bind(this)); ol.interaction.CenterTouch.call(this, options); this._source = options.source; this.set('tap', options.tap!==false); }; ol.ext.inherits(ol.interaction.DrawTouch, ol.interaction.CenterTouch); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.DrawTouch.prototype.setMap = function(map) { if (this._listener) { for(var l in this._listener) ol.Observable.unByKey(l); } this._listener = {}; ol.interaction.CenterTouch.prototype.setMap.call (this, map); this.sketch.setMap(map); if (map){ this._listener.center = map.on('postcompose', function() { if (!ol.coordinate.equal(this.getPosition(), this.sketch.getPosition() || [])) { this.sketch.setPosition(this.getPosition()); } }.bind(this)); } }; /** Set geometry type * @param {ol.geom.GeometryType} type */ ol.interaction.DrawTouch.prototype.setGeometryType = function(type) { return this.sketch.setGeometryType(type); }; /** Get geometry type * @return {ol.geom.GeometryType} */ ol.interaction.DrawTouch.prototype.getGeometryType = function() { return this.sketch.getGeometryType(); }; /** Start drawing and add the sketch feature to the target layer. * The ol.interaction.Draw.EventType.DRAWEND event is dispatched before inserting the feature. */ ol.interaction.DrawTouch.prototype.finishDrawing = function() { this.sketch.finishDrawing(true); }; /** Add a new Point to the drawing */ ol.interaction.DrawTouch.prototype.addPoint = function() { this.sketch.addPoint(this.getPosition()); }; /** Remove last point of the feature currently being drawn. */ ol.interaction.DrawTouch.prototype.removeLastPoint = function() { this.sketch.removeLastPoint(); }; /** * Activate or deactivate the interaction. * @param {boolean} active Active. * @observable * @api */ ol.interaction.DrawTouch.prototype.setActive = function(b) { ol.interaction.CenterTouch.prototype.setActive.call (this, b); this.sketch.abortDrawing(); this.sketch.setVisible(b); }; /** Extend DragAndDrop choose drop zone + fires loadstart, loadend * @constructor * @extends {ol.interaction.DragAndDrop} * @fires loadstart, loadend, addfeatures * @param {*} options * @param {string} options.zone selector for the drop zone, default document * @param{ol.projection} options.projection default projection of the map * @param {Array|undefined} options.formatConstructors Format constructors, default [ ol.format.GPX, ol.format.GeoJSONX, ol.format.GeoJSONP, ol.format.GeoJSON, ol.format.IGC, ol.format.KML, ol.format.TopoJSON ] * @param {Array|undefined} options.accept list of accepted format, default ["gpx","json","geojsonx","geojsonp","geojson","igc","kml","topojson"] */ ol.interaction.DropFile = function(options) { options = options||{}; ol.interaction.DragAndDrop.call(this, {}); var zone = options.zone || document; zone.addEventListener('dragenter', this.onstop ); zone.addEventListener('dragover', this.onstop ); zone.addEventListener('dragleave', this.onstop ); // Options this.formatConstructors_ = options.formatConstructors || [ ol.format.GPX, ol.format.GeoJSONX, ol.format.GeoJSONP, ol.format.GeoJSON, ol.format.IGC, ol.format.KML, ol.format.TopoJSON ]; this.projection_ = options.projection; this.accept_ = options.accept || ["gpx","json","geojsonx","geojsonp","geojson","igc","kml","topojson"]; var self = this; zone.addEventListener('drop', function(e){ return self.ondrop(e);}); }; ol.ext.inherits(ol.interaction.DropFile, ol.interaction.DragAndDrop); /** Set the map */ ol.interaction.DropFile.prototype.setMap = function(map) { ol.interaction.Interaction.prototype.setMap.call(this, map); }; /** Do something when over */ ol.interaction.DropFile.prototype.onstop = function(e) { e.preventDefault(); e.stopPropagation(); return false; } /** Do something when over */ ol.interaction.DropFile.prototype.ondrop = function(e) { e.preventDefault(); if (e.dataTransfer && e.dataTransfer.files.length) { var self = this; var projection = this.projection_ || (this.getMap() ? this.getMap().getView().getProjection() : null); // fetch FileList object var files = e.dataTransfer.files; // e.originalEvent.target.files ? // process all File objects var file; var pat = /\.([0-9a-z]+)(?=[?#])|(\.)(?:[\w]+)$/; for (var i=0; file=files[i]; i++) { var ex = file.name.match(pat)[0]; var isok = (this.accept_.indexOf(ex.toLocaleLowerCase()) >= 0); self.dispatchEvent({ type:'loadstart', file: file, filesize: file.size, filetype: file.type, fileextension: ex, projection: projection, isok: isok }); // Don't load file if (!this.formatConstructors_.length) continue; // Load file var reader = new FileReader(); var formatConstructors = this.formatConstructors_ var theFile = file; reader.onload = function(e) { var result = e.target.result; var features = []; var i, ii; for (i = 0, ii = formatConstructors.length; i < ii; ++i) { var formatConstructor = formatConstructors[i]; try { var format = new formatConstructor(); features = format.readFeatures(result, { featureProjection: projection }); if (features && features.length > 0) { self.dispatchEvent({ type:'addfeatures', features: features, file: theFile, projection: projection }); self.dispatchEvent({ type:'loadend', features: features, file: theFile, projection: projection }); return; } } catch(e) { /* ok */ } } // Nothing match, try to load by yourself self.dispatchEvent({ type:'loadend', file: theFile, result: result }); }; // Start loading reader.readAsText(file); } } return false; }; /** A Select interaction to fill feature's properties on click. * @constructor * @extends {ol.interaction.Interaction} * @fires setattributestart * @fires setattributeend * @param {*} options extentol.interaction.Select options * @param {boolean} options.active activate the interaction on start, default true * @param {string=} options.name * @param {boolean|string} options.cursor interaction cursor if false use default, default use a paint bucket cursor * @param {*} properties The properties as key/value */ ol.interaction.FillAttribute = function(options, properties) { options = options || {}; if (!options.condition) options.condition = ol.events.condition.click; ol.interaction.Select.call(this, options); this.setActive(options.active!==false) this.set('name', options.name); this._attributes = properties; this.on('select', function(e) { this.getFeatures().clear(); this.fill(e.selected, this._attributes); }.bind(this)); if (options.cursor === undefined) { var canvas = document.createElement('CANVAS'); canvas.width = canvas.height = 32; var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(9,3); ctx.lineTo(2,9); ctx.lineTo(10,17); ctx.lineTo(17,11); ctx.closePath(); ctx.fillStyle = "#fff"; ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(6,4); ctx.lineTo(0,8); ctx.lineTo(0,13); ctx.lineTo(3,17); ctx.lineTo(3,8); ctx.closePath(); ctx.fillStyle = "#000"; ctx.fill(); ctx.stroke(); ctx.moveTo(8,8); ctx.lineTo(10,0); ctx.lineTo(11,0); ctx.lineTo(13,3); ctx.lineTo(13,7); ctx.stroke(); this._cursor = 'url('+canvas.toDataURL()+') 0 13, auto'; } if (options.cursor) { this._cursor = options.cursor; } }; ol.ext.inherits(ol.interaction.FillAttribute, ol.interaction.Select); /** Define the interaction cursor * @param {string} cursor CSS cursor */ ol.interaction.FillAttribute.prototype.setCursor = function(cursor) { this._cursor = cursor; }; /** Get the interaction cursor * @return {string} cursor */ ol.interaction.FillAttribute.prototype.getCursor = function() { return this._cursor; }; /** Activate the interaction * @param {boolean} active */ ol.interaction.FillAttribute.prototype.setActive = function(active) { if(active === this.getActive()) return; ol.interaction.Select.prototype.setActive.call(this, active); if (this.getMap() && this._cursor) { if (active) { this._previousCursor = this.getMap().getTargetElement().style.cursor; this.getMap().getTargetElement().style.cursor = this._cursor; // console.log('setCursor',this._cursor) } else { this.getMap().getTargetElement().style.cursor = this._previousCursor; this._previousCursor = undefined; } } }; /** Set attributes * @param {*} properties The properties as key/value */ ol.interaction.FillAttribute.prototype.setAttributes = function(properties) { this._attributes = properties; }; /** Set an attribute * @param {string} key * @param {*} val */ ol.interaction.FillAttribute.prototype.setAttribute = function(key, val) { this._attributes[key] = val; }; /** get attributes * @return {*} The properties as key/value */ ol.interaction.FillAttribute.prototype.getAttributes = function() { return this._attributes; }; /** Get an attribute * @param {string} key * @return {*} val */ ol.interaction.FillAttribute.prototype.getAttribute = function(key) { return this._attributes[key]; }; /** Fill feature attributes * @param {Array} features The features to modify * @param {*} properties The properties as key/value */ ol.interaction.FillAttribute.prototype.fill = function(features, properties) { if (features.length && properties) { // Test changes var changes = false; for (var i=0, f; f = features[i]; i++) { for (var p in properties) { if (f.get(p) !== properties[p]) changes = true; } if (changes) break; } // Set Attributes if (changes) { this.dispatchEvent({ type: 'setattributestart', features: features, properties: properties }); features.forEach(function(f) { for (var p in properties) { f.set(p, properties[p]); } }); this.dispatchEvent({ type: 'setattributeend', features: features, properties: properties }); } } }; /** * @constructor * @extends {ol.interaction.Pointer} * @param {ol.flashlight.options} flashlight options param * @param {ol.Color} options.color light color, default transparent * @param {ol.Color} options.fill fill color, default rgba(0,0,0,0.8) * @param {number} options.radius radius of the flash */ ol.interaction.Flashlight = function(options) { ol.interaction.Pointer.call(this, { handleDownEvent: this.setPosition, handleMoveEvent: this.setPosition }); // Default options options = options||{}; this.pos = false; this.radius = (options.radius||100); this.setColor(options); }; ol.ext.inherits(ol.interaction.Flashlight, ol.interaction.Pointer); /** Set the map > start postcompose */ ol.interaction.Flashlight.prototype.setMap = function(map) { if (this.getMap()) { this.getMap().render(); } if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; ol.interaction.Pointer.prototype.setMap.call(this, map); if (map) { this._listener = map.on('postcompose', this.postcompose_.bind(this)); } } /** Set flashlight radius * @param {integer} radius */ ol.interaction.Flashlight.prototype.setRadius = function(radius) { this.radius = radius if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } } /** Set flashlight color * @param {ol.flashlight.options} flashlight options param * - color {ol.Color} light color, default transparent * - fill {ol.Color} fill color, default rgba(0,0,0,0.8) */ ol.interaction.Flashlight.prototype.setColor = function(options) { // Backcolor var color = (options.fill ? options.fill : [0,0,0,0.8]); var c = ol.color.asArray(color); this.startColor = ol.color.asString(c); // Halo color if (options.color) { c = this.endColor = ol.color.asString(ol.color.asArray(options.color)||options.color); } else { c[3] = 0 this.endColor = ol.color.asString(c); } c[3] = 0.1; this.midColor = ol.color.asString(c); if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } } /** Set position of the flashlight * @param {ol.Pixel|ol.MapBrowserEvent} */ ol.interaction.Flashlight.prototype.setPosition = function(e) { if (e.pixel) this.pos = e.pixel; else this.pos = e; if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } } /** Postcompose function */ ol.interaction.Flashlight.prototype.postcompose_ = function(e) { var ctx = ol.ext.getMapCanvas(this.getMap()).getContext('2d'); var ratio = e.frameState.pixelRatio; var w = ctx.canvas.width; var h = ctx.canvas.height; ctx.save(); ctx.scale(ratio,ratio); if (!this.pos) { ctx.fillStyle = this.startColor; ctx.fillRect( 0,0,w,h ); } else { var d = Math.max(w, h); // reveal wherever we drag var radGrd = ctx.createRadialGradient( this.pos[0], this.pos[1], w*this.radius/d, this.pos[0], this.pos[1], h*this.radius/d ); radGrd.addColorStop( 0, this.startColor ); radGrd.addColorStop( 0.8, this.midColor ); radGrd.addColorStop( 1, this.endColor ); ctx.fillStyle = radGrd; ctx.fillRect( this.pos[0] - d, this.pos[1] - d, 2*d, 2*d ); } ctx.restore(); }; /** An interaction to focus on the map on click. Usefull when using keyboard event on the map. * @constructor * @fires focus * @extends {ol.interaction.Interaction} */ ol.interaction.FocusMap = function() { // ol.interaction.Interaction.call(this, {}); // Focus (hidden) button to focus on the map when click on it this.focusBt = ol.ext.element.create('BUTTON', { on: { focus: function() { this.dispatchEvent({ type:'focus' }); }.bind(this) }, style: { position: 'absolute', zIndex: -1, top: 0, opacity: 0 } }); }; ol.ext.inherits(ol.interaction.FocusMap, ol.interaction.Interaction); /** Set the map > add the focus button and focus on the map when pointerdown to enable keyboard events. */ ol.interaction.FocusMap.prototype.setMap = function(map) { if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; if (this.getMap()) { this.getMap().getViewport().removeChild(this.focusBt); } ol.interaction.Interaction.prototype.setMap.call (this, map); if (this.getMap()) { // Force focus on the clicked map this._listener = this.getMap().on('pointerdown', function() { if (this.getActive()) this.focusBt.focus(); }.bind(this)); this.getMap().getViewport().appendChild(this.focusBt); } }; // /** Interaction to draw on the current geolocation * It combines a draw with a ol.Geolocation * @constructor * @extends {ol.interaction.Interaction} * @fires drawstart, drawend, drawing, tracking, follow * @param {any} options * @param { ol.Collection. | undefined } option.features Destination collection for the drawn features. * @param { ol.source.Vector | undefined } options.source Destination source for the drawn features. * @param {ol.geom.GeometryType} options.type Drawing type ('Point', 'LineString', 'Polygon'), default LineString. * @param {Number | undefined} options.minAccuracy minimum accuracy underneath a new point will be register (if no condition), default 20 * @param {function | undefined} options.condition a function that take a ol.Geolocation object and return a boolean to indicate whether location should be handled or not, default return true if accuracy < minAccuracy * @param {Object} options.attributes a list of attributes to register as Point properties: {accuracy:true,accuracyGeometry:true,heading:true,speed:true}, default none. * @param {Number} options.tolerance tolerance to add a new point (in meter), default 5 * @param {Number} options.zoom zoom for tracking, default 16 * @param {Number} options.minZoom min zoom for tracking, if zoom is less it will zoom to it, default use zoom option * @param {boolean|auto|position|visible} options.followTrack true if you want the interaction to follow the track on the map, default true * @param { ol.style.Style | Array. | ol.StyleFunction | undefined } options.style Style for sketch features. */ ol.interaction.GeolocationDraw = function(options) { if (!options) options={}; // Geolocation this.geolocation = new ol.Geolocation(({ projection: "EPSG:4326", trackingOptions: { maximumAge: 10000, enableHighAccuracy: true, timeout: 600000 } })); this.geolocation.on('change', this.draw_.bind(this)); // Current path this.path_ = []; this.lastPosition_ = false; // Default style var white = [255, 255, 255, 1]; var blue = [0, 153, 255, 1]; var width = 3; var circle = new ol.style.Circle({ radius: width * 2, fill: new ol.style.Fill({ color: blue }), stroke: new ol.style.Stroke({ color: white, width: width / 2 }) }); var style = [ new ol.style.Style({ stroke: new ol.style.Stroke({ color: white, width: width + 2 }) }), new ol.style.Style({ stroke: new ol.style.Stroke({ color: blue, width: width }), fill: new ol.style.Fill({ color: [255, 255, 255, 0.5] }) }) ]; var triangle = new ol.style.RegularShape({ radius: width * 3.5, points: 3, rotation: 0, fill: new ol.style.Fill({ color: blue }), stroke: new ol.style.Stroke({ color: white, width: width / 2 }) }); // stretch the symbol var c = triangle.getImage(); var ctx = c.getContext("2d"); var c2 = document.createElement('canvas'); c2.width = c2.height = c.width; c2.getContext("2d").drawImage(c, 0,0); ctx.clearRect(0,0,c.width,c.height); ctx.drawImage(c2, 0,0, c.width, c.height, width, 0, c.width-2*width, c.height); var defaultStyle = function(f) { if (f.get('heading')===undefined) { style[1].setImage(circle); } else { style[1].setImage(triangle); triangle.setRotation( f.get('heading') || 0); } return style; } // Style for the accuracy geometry this.locStyle = { error: new ol.style.Style({ fill: new ol.style.Fill({ color: [255, 0, 0, 0.2] }) }), warn: new ol.style.Style({ fill: new ol.style.Fill({ color: [255, 192, 0, 0.2] }) }), ok: new ol.style.Style({ fill: new ol.style.Fill({ color: [0, 255, 0, 0.2] }) }), }; // Create a new overlay layer for the sketch this.overlayLayer_ = new ol.layer.Vector({ source: new ol.source.Vector(), name:'GeolocationDraw overlay', style: options.style || defaultStyle }); this.sketch_ = [new ol.Feature(), new ol.Feature(), new ol.Feature()]; this.overlayLayer_.getSource().addFeatures(this.sketch_); this.features_ = options.features; this.source_ = options.source; this.condition_ = options.condition || function(loc) { return loc.getAccuracy() < this.get("minAccuracy") }; // Prevent interaction when tracking ol.interaction.Interaction.call(this, { handleEvent: function() { return (!this.get('followTrack') || this.get('followTrack')=='auto');// || !geoloc.getTracking()); } }); this.set('type', options.type||"LineString"); this.set('attributes', options.attributes||{}); this.set('minAccuracy', options.minAccuracy||20); this.set('tolerance', options.tolerance||5); this.set('zoom', options.zoom); this.set('minZoom', options.minZoom); this.setFollowTrack (options.followTrack===undefined ? true : options.followTrack); this.setActive(false); }; ol.ext.inherits(ol.interaction.GeolocationDraw, ol.interaction.Interaction); /** Simplify 3D geometry * @param {ol.geom.Geometry} geo * @param {number} tolerance */ ol.interaction.GeolocationDraw.prototype.simplify3D = function(geo, tolerance) { var geom = geo.getCoordinates(); var proj = this.getMap().getView().getProjection(); if (this.get("type")==='Polygon') { geom = geom[0]; } var simply = [geom[0]]; var pi, p = ol.proj.transform(geom[0], proj, 'EPSG:4326') for (var i=1; i tolerance) { simply.push(geom[i]); p = pi; } } if (simply[simply.length-1] !== geom[geom.length-1]) { simply.push(geom[geom.length-1]); } /* var simply = geo.simplify(tolerance).getCoordinates(); if (this.get("type")==='Polygon') { simply = simply[0]; } var step=0; simply.forEach(function(p) { for (; step|boolean} track a list of point or false to stop * @param {*} options * @param {number} delay delay in ms, default 1000 (1s) * @param {number} accuracy gps accuracy, default 10 * @param {boolean} repeat repeat track, default true */ ol.interaction.GeolocationDraw.prototype.simulate = function(track, options) { if (this._track) { clearTimeout(this._track.timeout); } if (!track) { this._track = false; return; } options = options || {}; var delay = options.delay || 1000; function handleTrack() { if (this._track.pos >= this._track.track.length) { this._track = false; return; } var coord = this._track.track[this._track.pos]; coord[2] = coord[3] || 0; coord[3] = (new Date()).getTime(); this._track.pos++; if (options.repeat !== false) { this._track.pos = this._track.pos % this._track.track.length; } if (this.getActive()) this.draw_(true, coord, options.accuracy); this._track.timeout = setTimeout(handleTrack.bind(this), delay); } this._track = { track: track, pos: 0, timeout: setTimeout(handleTrack.bind(this), 0) } }; /** Is simulation on ? * @returns {boolean} */ ol.interaction.GeolocationDraw.prototype.simulating = function() { return !!this._track; }; /** Reset drawing */ ol.interaction.GeolocationDraw.prototype.reset = function() { this.sketch_[1].setGeometry(); this.path_ = []; this.lastPosition_ = false; }; /** Start tracking = setActive(true) */ ol.interaction.GeolocationDraw.prototype.start = function() { this.setActive(true); }; /** Stop tracking = setActive(false) */ ol.interaction.GeolocationDraw.prototype.stop = function() { this.setActive(false); }; /** Pause drawing * @param {boolean} b */ ol.interaction.GeolocationDraw.prototype.pause = function(b) { this.pause_ = (b!==false); }; /** Is paused * @return {boolean} b */ ol.interaction.GeolocationDraw.prototype.isPaused = function() { return this.pause_; }; /** Enable following the track on the map * @param {boolean|auto|position|visible} follow, * false: don't follow, * true: follow (position+zoom), * 'position': follow only position, * 'auto': start following until user move the map, * 'visible': center when position gets out of the visible extent */ ol.interaction.GeolocationDraw.prototype.setFollowTrack = function(follow) { this.set('followTrack', follow); var map = this.getMap(); // Center if wanted if (this.getActive() && map) { var zoom; if (follow !== 'position') { if (this.get('minZoom')) { zoom = Math.max(this.get('minZoom'), map.getView().getZoom()); } else { zoom = this.get('zoom'); } } if (follow !== false && !this.lastPosition_) { var pos = this.path_[this.path_.length-1]; if (pos) { map.getView().animate({ center: pos, zoom: zoom }); } } else if (follow==='auto' && this.lastPosition_) { map.getView().animate({ center: this.lastPosition_, zoom: zoom }); } } this.lastPosition_ = false; this.dispatchEvent({ type:'follow', following: follow!==false }); }; /** Add a new point to the current path * @private */ ol.interaction.GeolocationDraw.prototype.draw_ = function(simulate, coord, accuracy) { var map = this.getMap(); if (!map) return; var accu, pos, p, loc, heading; // Simulation mode if (this._track) { if (simulate!==true) return; pos = coord; accu = accuracy || 10; if (this.path_ && this.path_.length) { var pt = this.path_[this.path_.length-1]; heading = Math.atan2(coord[0]-pt[0],coord[1]-pt[1]) } var circle = new ol.geom.Circle(pos, map.getView().getResolution()*accu); p = ol.geom.Polygon.fromCircle(circle); } else { // Current location loc = this.geolocation; accu = loc.getAccuracy(); pos = this.getPosition(loc); p = loc.getAccuracyGeometry(); heading = loc.getHeading(); } // Center on point // console.log(this.get('followTrack')) switch (this.get('followTrack')) { // Follow center + zoom case true: { // modify zoom if (this.get('followTrack') == true) { if (this.get('minZoom')) { if (this.get('minZoom') > map.getView().getZoom()) { map.getView().setZoom(this.get('minZoom')); } } else { map.getView().setZoom( this.get('zoom') || 16 ); } if (!ol.extent.containsExtent(map.getView().calculateExtent(map.getSize()), p.getExtent())) { map.getView().fit(p.getExtent()); } } map.getView().setCenter( pos ); break; } // Follow position case 'position': { // modify center map.getView().setCenter( pos ); break; } // Keep on following case 'auto': { if (this.lastPosition_) { var center = map.getView().getCenter(); // console.log(center,this.lastPosition_) if (center[0]!=this.lastPosition_[0] || center[1]!=this.lastPosition_[1]) { //this.dispatchEvent({ type:'follow', following: false }); this.setFollowTrack (false); } else { map.getView().setCenter( pos ); this.lastPosition_ = pos; } } else { map.getView().setCenter( pos ); if (this.get('minZoom')) { if (this.get('minZoom') > map.getView().getZoom()) { map.getView().setZoom(this.get('minZoom')); } } else if (this.get('zoom')) { map.getView().setZoom( this.get('zoom')); } this.lastPosition_ = pos; } break; } // Force to stay on the map case 'visible': { if (!ol.extent.containsCoordinate(map.getView().calculateExtent(map.getSize()), pos)) { map.getView().setCenter (pos); } break; } // Don't follow default: break; } // Draw occuracy var f = this.sketch_[0]; f.setGeometry(p); if (accu < this.get("minAccuracy")/2) f.setStyle(this.locStyle.ok); else if (accu < this.get("minAccuracy")) f.setStyle(this.locStyle.warn); else f.setStyle(this.locStyle.error); var geo; if (this.pause_) { this.lastPosition_ = pos; } if (!this.pause_ && (!loc || this.condition_.call(this, loc))) { f = this.sketch_[1]; this.path_.push(pos); switch (this.get("type")) { case "Point": this.path_ = [pos]; f.setGeometry(new ol.geom.Point(pos, 'XYZM')); var attr = this.get('attributes'); if (attr.heading) f.set("heading",loc.getHeading()); if (attr.accuracy) f.set("accuracy",loc.getAccuracy()); if (attr.altitudeAccuracy) f.set("altitudeAccuracy", loc.getAltitudeAccuracy()); if (attr.speed) f.set("speed",loc.getSpeed()); break; case "LineString": if (this.path_.length>1) { geo = new ol.geom.LineString(this.path_, 'XYZM'); if (this.get("tolerance")) geo = this.simplify3D (geo, this.get("tolerance")); f.setGeometry(geo); } else { f.setGeometry(); } break; case "Polygon": if (this.path_.length>2) { geo = new ol.geom.Polygon([this.path_], 'XYZM'); if (this.get("tolerance")) geo = this.simplify3D (geo, this.get("tolerance")); f.setGeometry(geo); } else f.setGeometry(); break; } this.dispatchEvent({ type:'drawing', feature: this.sketch_[1], geolocation: loc }); } this.sketch_[2].setGeometry(new ol.geom.Point(pos)); this.sketch_[2].set("heading", heading); // Drawing this.dispatchEvent({ type:'tracking', feature: this.sketch_[1], geolocation: loc }); }; /** Get a position according to the geolocation * @param {Geolocation} loc * @returns {Array} an array of measure X,Y,Z,T * @api */ ol.interaction.GeolocationDraw.prototype.getPosition = function (loc) { var pos = loc.getPosition(); pos.push (Math.round((loc.getAltitude()||0)*100)/100); pos.push (Math.round((new Date()).getTime()/1000)); return pos; } /** Interaction hover do to something when hovering a feature * @constructor * @extends {ol.interaction.Interaction} * @fires hover, enter, leave * @param {olx.interaction.HoverOptions} * @param { string | undefined } options.cursor css cursor propertie or a function that gets a feature, default: none * @param {function | undefined} options.featureFilter filter a function with two arguments, the feature and the layer of the feature. Return true to select the feature * @param {function | undefined} options.layerFilter filter a function with one argument, the layer to test. Return true to test the layer * @param {Array | undefined} options.layers a set of layers to test * @param {number | undefined} options.hitTolerance Hit-detection tolerance in pixels. * @param { function | undefined } options.handleEvent Method called by the map to notify the interaction that a browser event was dispatched to the map. The function may return false to prevent the propagation of the event to other interactions in the map's interactions chain. */ ol.interaction.Hover = function(options) { if (!options) options={}; var self = this; var dragging = false; ol.interaction.Interaction.call(this, { handleEvent: function(e) { if (!self.getActive()) return true; switch(e.type) { case 'pointerdrag': { dragging = true; break; } case 'pointerup': { dragging = false; break; } case 'pointermove': { if (!dragging) { self.handleMove_(e); } break; } } if (options.handleEvent) return options.handleEvent(e); return true; } }); this.setLayerFilter (options.layerFilter); if (options.layers && options.layers.length) { this.setLayerFilter(function(l) { return (options.layers.indexOf(l) >= 0); }) } this.setFeatureFilter (options.featureFilter); this.set('hitTolerance', options.hitTolerance) this.setCursor (options.cursor); }; ol.ext.inherits(ol.interaction.Hover, ol.interaction.Interaction); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.Hover.prototype.setMap = function(map) { if (this.previousCursor_!==undefined && this.getMap()) { this.getMap().getTargetElement().style.cursor = this.previousCursor_; this.previousCursor_ = undefined; } ol.interaction.Interaction.prototype.setMap.call (this, map); }; /** Activate / deactivate interaction * @param {boolean} b */ ol.interaction.Hover.prototype.setActive = function(b) { ol.interaction.Interaction.prototype.setActive.call (this, b); if (this.cursor_ && this.getMap() && this.getMap().getTargetElement()) { var style = this.getMap().getTargetElement().style; if (this.previousCursor_ !== undefined) { style.cursor = this.previousCursor_; this.previousCursor_ = undefined; } } }; /** * Set cursor on hover * @param { string } cursor css cursor propertie or a function that gets a feature, default: none * @api stable */ ol.interaction.Hover.prototype.setCursor = function(cursor) { if (!cursor && this.previousCursor_!==undefined && this.getMap()) { this.getMap().getTargetElement().style.cursor = this.previousCursor_; this.previousCursor_ = undefined; } this.cursor_ = cursor; }; /** Feature filter to get only one feature * @param {function} filter a function with two arguments, the feature and the layer of the feature. Return true to select the feature */ ol.interaction.Hover.prototype.setFeatureFilter = function(filter) { if (typeof (filter) == 'function') this.featureFilter_ = filter; else this.featureFilter_ = function(){ return true; }; }; /** Feature filter to get only one feature * @param {function} filter a function with one argument, the layer to test. Return true to test the layer */ ol.interaction.Hover.prototype.setLayerFilter = function(filter) { if (typeof (filter) == 'function') this.layerFilter_ = filter; else this.layerFilter_ = function(){ return true; }; }; /** Get features whenmove * @param {ol.event} e "move" event */ ol.interaction.Hover.prototype.handleMove_ = function(e) { var map = this.getMap(); if (map) { //var b = map.hasFeatureAtPixel(e.pixel); var feature, layer; var self = this; var b = map.forEachFeatureAtPixel( e.pixel, function(f, l) { if (self.featureFilter_.call(null,f,l)) { feature = f; layer = l; return true; } else { feature = layer = null; return false; } },{ hitTolerance: this.get('hitTolerance'), layerFilter: self.layerFilter_ } ); if (b) this.dispatchEvent({ type: 'hover', feature: feature, layer: layer, coordinate: e.coordinate, pixel: e.pixel, map: e.map, originalEvent: e.originalEvent, dragging: e.dragging }); if (this.feature_===feature && this.layer_===layer){ /* ok */ } else { this.feature_ = feature; this.layer_ = layer; if (feature) { this.dispatchEvent({ type: 'enter', feature: feature, layer: layer, coordinate: e.coordinate, pixel: e.pixel, map: e.map, originalEvent: e.originalEvent, dragging: e.dragging }); } else { this.dispatchEvent({ type: 'leave', coordinate: e.coordinate, pixel: e.pixel, map: e.map, originalEvent: e.originalEvent, dragging: e.dragging }); } } if (this.cursor_) { var style = map.getTargetElement().style; if (b) { if (style.cursor != this.cursor_) { this.previousCursor_ = style.cursor; style.cursor = this.cursor_; } } else if (this.previousCursor_ !== undefined) { style.cursor = this.previousCursor_; this.previousCursor_ = undefined; } } } }; /* 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). */ /** Interaction to handle longtouch events * @constructor * @extends {ol.interaction.Interaction} * @param {olx.interaction.LongTouchOptions} * @param {function | undefined} options.handleLongTouchEvent Function handling 'longtouch' events, it will receive a mapBrowserEvent. Or listen to the map 'longtouch' event. * @param {integer | undefined} [options.pixelTolerance=0] pixel tolerance before drag, default 0 * @param {integer | undefined} [options.delay=1000] The delay for a long touch in ms, default is 1000 */ ol.interaction.LongTouch = function(options) { if (!options) options = {}; this.delay_ = options.delay || 1000; var ltouch = options.handleLongTouchEvent || function(){}; var _timeout = null; var position, event; var tol = options.pixelTolerance || 0; ol.interaction.Interaction.call(this, { handleEvent: function(e) { if (this.getActive()) { switch (e.type) { case 'pointerdown': { if (_timeout) clearTimeout(_timeout); position = e.pixel; event = { type: 'longtouch', originalEvent: e.originalEvent, frameState: e.frameState, pixel: e.pixel, coordinate: e.coordinate, map: this.getMap() } _timeout = setTimeout (function() { ltouch(event); event.map.dispatchEvent(event); }, this.delay_); break; } case 'pointerdrag': { // Check if dragging over tolerance if (_timeout && (Math.abs(e.pixel[0] - position[0]) > tol || Math.abs(e.pixel[1] - position[1]) > tol)) { clearTimeout(_timeout); _timeout = null; } break; } case 'pointerup': { if (_timeout) { clearTimeout(_timeout); _timeout = null; } break; } default: break; } } else { if (_timeout) { clearTimeout(_timeout); _timeout = null; } } return true; } }); }; ol.ext.inherits(ol.interaction.LongTouch, ol.interaction.Interaction); // Use ol.getUid for Openlayers < v6 /** Extent the ol/interaction/Modify with a getModifyFeatures * Get the features modified by the interaction * @return {Array} the modified features * @deprecated */ ol.interaction.Modify.prototype.getModifiedFeatures = function() { var featuresById = {}; this.dragSegments_.forEach( function(s) { var feature = s[0].feature; // Openlayers > v.6 if (window.ol && window.ol.util) featuresById[ol.util.getUid(feature)] = feature; // old version of Openlayers (< v.6) or ol all versions else featuresById[ol.getUid(feature)] = feature; }); var features = []; for (var i in featuresById) features.push(featuresById[i]); 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). */ /** Interaction for modifying feature geometries. Similar to the core ol/interaction/Modify. * The interaction is more suitable to use to handle feature modification: only features concerned * by the modification are passed to the events (instead of all feature with ol/interaction/Modify) * - the modifystart event is fired before the feature is modified (no points still inserted) * - the modifyend event is fired after the modification * - it fires a modifying event * @constructor * @extends {ol.interaction.Pointer} * @fires modifystart * @fires modifying * @fires modifyend * @fires select * @param {*} options * @param {ol.source.Vector} options.source a source to modify (configured with useSpatialIndex set to true) * @param {ol.source.Vector|Array} options.sources a list of source to modify (configured with useSpatialIndex set to true) * @param {ol.Collection.} options.features collection of feature to modify * @param {integer} options.pixelTolerance Pixel tolerance for considering the pointer close enough to a segment or vertex for editing. Default is 10. * @param {function|undefined} options.filter a filter that takes a feature and return true if it can be modified, default always true. * @param {ol.style.Style | Array | undefined} options.style Style for the sketch features. * @param {ol.EventsConditionType | undefined} options.condition A function that takes an ol.MapBrowserEvent and returns a boolean to indicate whether that event will be considered to add or move a vertex to the sketch. Default is ol.events.condition.primaryAction. * @param {ol.EventsConditionType | undefined} options.deleteCondition A function that takes an ol.MapBrowserEvent and returns a boolean to indicate whether that event should be handled. By default, ol.events.condition.singleClick with ol.events.condition.altKeyOnly results in a vertex deletion. * @param {ol.EventsConditionType | undefined} options.insertVertexCondition A function that takes an ol.MapBrowserEvent and returns a boolean to indicate whether a new vertex can be added to the sketch features. Default is ol.events.condition.always * @param {boolean} options.wrapX Wrap the world horizontally on the sketch overlay, default false */ ol.interaction.ModifyFeature = function(options){ if (!options) options = {}; var dragging, modifying; ol.interaction.Pointer.call(this,{ /* handleDownEvent: this.handleDownEvent, handleDragEvent: this.handleDragEvent, handleMoveEvent: this.handleMoveEvent, handleUpEvent: this.handleUpEvent, */ handleEvent: function(e) { switch(e.type) { case 'pointerdown': { dragging = this.handleDownEvent(e); modifying = dragging || this._deleteCondition(e); return !dragging; } case 'pointerup': { dragging = false; return this.handleUpEvent(e); } case 'pointerdrag': { if (dragging) return this.handleDragEvent(e); else return true; } case 'pointermove': { if (!dragging) return this.handleMoveEvent(e); else return true; } case 'singleclick': case 'click': { // Prevent click when modifying return !modifying; } default: return true; } } }); // Snap distance (in px) this.snapDistance_ = options.pixelTolerance || 10; // Split tolerance between the calculated intersection and the geometry this.tolerance_ = 1e-10; // Cursor this.cursor_ = options.cursor; // List of source to split this.sources_ = options.sources ? (options.sources instanceof Array) ? options.sources:[options.sources] : []; if (options.source) this.sources_.push (options.source); if (options.features) this.sources_.push (new ol.source.Vector({ features: options.features })); // Get all features candidate this.filterSplit_ = options.filter || function(){ return true; }; this._condition = options.condition || ol.events.condition.primaryAction; this._deleteCondition = options.deleteCondition || ol.events.condition.altKeyOnly; this._insertVertexCondition = options.insertVertexCondition || ol.events.condition.always; // Default style var sketchStyle = function() { return [ new ol.style.Style({ image: new ol.style.Circle({ radius: 6, fill: new ol.style.Fill({ color: [0, 153, 255, 1] }), stroke: new ol.style.Stroke({ color: '#FFF', width: 1.25 }) }) }) ]; } // Custom style if (options.style) { if (typeof(options.style) === 'function') { sketchStyle = options.style } else { sketchStyle = function() { return options.style; } } } // Create a new overlay for the sketch this.overlayLayer_ = new ol.layer.Vector({ source: new ol.source.Vector({ useSpatialIndex: false }), name:'Modify overlay', displayInLayerSwitcher: false, style: sketchStyle, wrapX: options.wrapX }); }; ol.ext.inherits(ol.interaction.ModifyFeature, ol.interaction.Pointer); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.ModifyFeature.prototype.setMap = function(map) { if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_); ol.interaction.Interaction.prototype.setMap.call (this, map); this.overlayLayer_.setMap(map); }; /** * Activate or deactivate the interaction + remove the sketch. * @param {boolean} active. * @api stable */ ol.interaction.ModifyFeature.prototype.setActive = function(active) { ol.interaction.Interaction.prototype.setActive.call (this, active); if (this.overlayLayer_) this.overlayLayer_.getSource().clear(); }; /** Change the filter function * @param {function|undefined} options.filter a filter that takes a feature and return true if it can be modified, default always true. */ ol.interaction.ModifyFeature.prototype.setFilter = function(filter) { if (typeof(filter) === 'function') this.filterSplit_ = filter; else if (filter === undefined) this.filterSplit_ = function(){ return true; }; }; /** Get closest feature at pixel * @param {ol.Pixel} * @return {*} * @private */ ol.interaction.ModifyFeature.prototype.getClosestFeature = function(e) { var f, c, d = this.snapDistance_+1; for (var i=0; i this.snapDistance_) { if (this.currentFeature) this.dispatchEvent({ type: 'select', selected: [], deselected: [this.currentFeature] }) this.currentFeature = null; return false; } else { // Snap to node var coord = this.getNearestCoord (c, f.getGeometry()); if (coord) { coord = coord.coord; var p = this.getMap().getPixelFromCoordinate(coord); if (ol.coordinate.dist2d(e.pixel, p) < this.snapDistance_) { c = coord; } // if (this.currentFeature !== f) this.dispatchEvent({ type: 'select', selected: [f], deselected: [this.currentFeature] }) this.currentFeature = f; return { source:source, feature:f, coord: c }; } } } /** Get nearest coordinate in a list * @param {ol.coordinate} pt the point to find nearest * @param {ol.geom} coords list of coordinates * @return {*} the nearest point with a coord (projected point), dist (distance to the geom), ring (if Polygon) */ ol.interaction.ModifyFeature.prototype.getNearestCoord = function(pt, geom) { var i, l, p, p0, dm; switch (geom.getType()) { case 'Point': { return { coord: geom.getCoordinates(), dist: ol.coordinate.dist2d(geom.getCoordinates(), pt) }; } case 'MultiPoint': { return this.getNearestCoord (pt, new ol.geom.LineString(geom.getCoordinates())); } case 'LineString': case 'LinearRing': { var d; dm = Number.MAX_VALUE; var coords = geom.getCoordinates(); for (i=0; i < coords.length; i++) { d = ol.coordinate.dist2d (pt, coords[i]); if (d < dm) { dm = d; p0 = coords[i]; } } return { coord: p0, dist: dm }; } case 'MultiLineString': { var lstring = geom.getLineStrings(); p0 = false, dm = Number.MAX_VALUE; for (i=0; l=lstring[i]; i++) { p = this.getNearestCoord(pt, l); if (p && p.dist2) { coords = split[1].getCoordinates(); for (i=2; s=split[i]; i++) { var c = s.getCoordinates(); c.shift(); coords = coords.concat(c); } split = [ split[0], new ol.geom.LineString(coords) ]; } // Split in two if (split.length === 2) { var c0 = split[0].getCoordinates(); var c1 = split[1].getCoordinates(); var nbpt = c0.length + c1.length -1; c0.pop(); c1.shift(); arcs = { geom: geom, type: geom.getType(), coord1: c0, coord2: c1, node: (geom.getCoordinates().length === nbpt), closed: false } } else if (split.length === 1) { s = split[0].getCoordinates(); var start = ol.coordinate.equal(s[0], coord); var end = ol.coordinate.equal(s[s.length-1], coord); // Move first point if (start) { s.shift(); if (end) s.pop(); arcs = { geom: geom, type: geom.getType(), coord1: [], coord2: s, node: true, closed: end } } else if (end) { // Move last point s.pop() arcs = { geom: geom, type: geom.getType(), coord1: s, coord2: [], node: true, closed: false } } } } break; } case 'MultiLineString': { var lstring = geom.getLineStrings(); for (i=0; l=lstring[i]; i++) { arcs = this.getArcs(l, coord); if (arcs) { arcs.geom = geom; arcs.type = geom.getType(); arcs.lstring = i; break; } } break; } case 'Polygon': { var lring = geom.getLinearRings(); for (i=0; l=lring[i]; i++) { arcs = this.getArcs(l, coord); if (arcs) { arcs.geom = geom; arcs.type = geom.getType(); arcs.index = i; break; } } break; } case 'MultiPolygon': { var poly = geom.getPolygons(); for (i=0; l=poly[i]; i++) { arcs = this.getArcs(l, coord); if (arcs) { arcs.geom = geom; arcs.type = geom.getType(); arcs.poly = i; break; } } break; } case 'GeometryCollection': { g = geom.getGeometries(); for (i=0; l=g[i]; i++) { arcs = this.getArcs(l, coord); if (arcs) { arcs.geom = geom; arcs.g = i; arcs.typeg = arcs.type; arcs.type = geom.getType(); break; } } break; } default: { console.error('ol/interaction/ModifyFeature '+geom.getType()+' not supported!'); break; } } return arcs; }; /** * @param {ol.MapBrowserEvent} evt Map browser event. * @return {boolean} `true` to start the drag sequence. */ ol.interaction.ModifyFeature.prototype.handleDownEvent = function(evt) { if (!this.getActive()) return false; // Something to move ? var current = this.getClosestFeature(evt); if (current && (this._condition(evt) || this._deleteCondition(evt))) { var features = []; this.arcs = []; // Get features concerned this.sources_.forEach(function(s) { var extent = ol.extent.buffer (ol.extent.boundingExtent([current.coord]), this.tolerance_); features = features.concat(features, s.getFeaturesInExtent(extent)); }.bind(this)); // Get arcs concerned this._modifiedFeatures = []; features.forEach(function(f) { var a = this.getArcs(f.getGeometry(), current.coord); if (a) { if (this._insertVertexCondition(evt) || a.node) { a.feature = f; this._modifiedFeatures.push(f); this.arcs.push(a); } } }.bind(this)); if (this._modifiedFeatures.length) { if (this._deleteCondition(evt)) { return !this._removePoint(current, evt); } else { this.dispatchEvent({ type:'modifystart', coordinate: current.coord, originalEvent: evt.originalEvent, features: this._modifiedFeatures }); this.handleDragEvent({ coordinate: current.coord, originalEvent: evt.originalEvent }) return true; } } else { return true; } } else { return false; } }; /** Get modified features * @return {Array} list of modified features */ ol.interaction.ModifyFeature.prototype.getModifiedFeatures = function() { return this._modifiedFeatures || []; }; /** Removes the vertex currently being pointed. */ ol.interaction.ModifyFeature.prototype.removePoint = function() { this._removePoint({},{}); }; /** * @private */ ol.interaction.ModifyFeature.prototype._getModification = function(a) { var coords = a.coord1.concat(a.coord2); switch (a.type) { case 'LineString': { if (a.closed) coords.push(coords[0]); if (coords.length>1) { if (a.geom.getCoordinates().length != coords.length) { a.coords = coords; return true; } } break; } case 'MultiLineString': { if (a.closed) coords.push(coords[0]); if (coords.length>1) { var c = a.geom.getCoordinates(); if (c[a.lstring].length != coords.length) { c[a.lstring] = coords; a.coords = c; return true; } } break; } case 'Polygon': { if (a.closed) coords.push(coords[0]); if (coords.length>3) { c = a.geom.getCoordinates(); if (c[a.index].length != coords.length) { c[a.index] = coords; a.coords = c; return true; } } break; } case 'MultiPolygon': { if (a.closed) coords.push(coords[0]); if (coords.length>3) { c = a.geom.getCoordinates(); if (c[a.poly][a.index].length != coords.length) { c[a.poly][a.index] = coords; a.coords = c; return true; } } break; } case 'GeometryCollection': { a.type = a.typeg; var geom = a.geom; var geoms = geom.getGeometries(); a.geom = geoms[a.g]; var found = this._getModification(a); // Restore current arc geom.setGeometries(geoms); a.geom = geom; a.type = 'GeometryCollection'; return found; } default: { //console.error('ol/interaction/ModifyFeature '+a.type+' not supported!'); break; } } return false; }; /** Removes the vertex currently being pointed. * @private */ ol.interaction.ModifyFeature.prototype._removePoint = function(current, evt) { if (!this.arcs) return false; this.overlayLayer_.getSource().clear(); var found = false; // Get all modifications this.arcs.forEach(function(a) { found = found || this._getModification(a); }.bind(this)); // Almost one point is removed if (found) { this.dispatchEvent({ type:'modifystart', coordinate: current.coord, originalEvent: evt.originalEvent, features: this._modifiedFeatures }); this.arcs.forEach(function(a) { if (a.geom.getType() === 'GeometryCollection') { if (a.coords) { var geoms = a.geom.getGeometries(); geoms[a.g].setCoordinates(a.coords); a.geom.setGeometries(geoms); } } else { if (a.coords) a.geom.setCoordinates(a.coords); } }.bind(this)); this.dispatchEvent({ type:'modifyend', coordinate: current.coord, originalEvent: evt.originalEvent, features: this._modifiedFeatures }); } this.arcs = []; return found; }; /** * @private */ ol.interaction.ModifyFeature.prototype.handleUpEvent = function(e) { if (!this.getActive()) return false; if (!this.arcs || !this.arcs.length) return true; this.overlayLayer_.getSource().clear(); this.dispatchEvent({ type:'modifyend', coordinate: e.coordinate, originalEvent: e.originalEvent, features: this._modifiedFeatures }); this.arcs = []; return true; }; /** * @private */ ol.interaction.ModifyFeature.prototype.setArcCoordinates = function(a, coords) { var c; switch (a.type) { case 'Point': { a.geom.setCoordinates(coords[0]); break; } case 'MultiPoint': { c = a.geom.getCoordinates(); c[a.index] = coords[0]; a.geom.setCoordinates(c); break; } case 'LineString': { a.geom.setCoordinates(coords); break; } case 'MultiLineString': { c = a.geom.getCoordinates(); c[a.lstring] = coords; a.geom.setCoordinates(c); break; } case 'Polygon': { c = a.geom.getCoordinates(); c[a.index] = coords; a.geom.setCoordinates(c); break; } case 'MultiPolygon': { c = a.geom.getCoordinates(); c[a.poly][a.index] = coords; a.geom.setCoordinates(c); break; } case 'GeometryCollection': { a.type = a.typeg; var geom = a.geom; var geoms = geom.getGeometries(); a.geom = geoms[a.g]; this.setArcCoordinates(a, coords); geom.setGeometries(geoms); a.geom = geom; a.type = 'GeometryCollection'; break; } } }; /** * @private */ ol.interaction.ModifyFeature.prototype.handleDragEvent = function(e) { if (!this.getActive()) return false; if (!this.arcs) return true; // Show sketch this.overlayLayer_.getSource().clear(); var p = new ol.Feature(new ol.geom.Point(e.coordinate)); this.overlayLayer_.getSource().addFeature(p); // Nothing to do if (!this.arcs.length) return true; // Move arcs this.arcs.forEach(function(a) { var coords = a.coord1.concat([e.coordinate], a.coord2); if (a.closed) coords.push(e.coordinate); this.setArcCoordinates(a, coords); }.bind(this)); this.dispatchEvent({ type:'modifying', coordinate: e.coordinate, originalEvent: e.originalEvent, features: this._modifiedFeatures }); return true; }; /** * @param {ol.MapBrowserEvent} evt Event. * @private */ ol.interaction.ModifyFeature.prototype.handleMoveEvent = function(e) { if (!this.getActive()) return false; this.overlayLayer_.getSource().clear(); var current = this.getClosestFeature(e); // Draw sketch if (current) { var p = new ol.Feature(new ol.geom.Point(current.coord)); this.overlayLayer_.getSource().addFeature(p); } // Show cursor var element = e.map.getTargetElement(); if (this.cursor_) { if (current) { if (element.style.cursor != this.cursor_) { this.previousCursor_ = element.style.cursor; element.style.cursor = this.cursor_; } } else if (this.previousCursor_ !== undefined) { element.style.cursor = this.previousCursor_; this.previousCursor_ = undefined; } } }; /** Get the current feature to modify * @return {ol.Feature} */ ol.interaction.ModifyFeature.prototype.getCurrentFeature = function() { return this.currentFeature; }; /* 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). */ /** Modify interaction with a popup to delet a point on touch device * @constructor * @fires showpopup * @fires hidepopup * @extends {ol.interaction.Modify} * @param {olx.interaction.ModifyOptions} options * @param {String|undefined} options.title title to display, default "remove point" * @param {String|undefined} options.className CSS class name for the popup * @param {String|undefined} options.positioning positioning for the popup * @param {Number|Array|undefined} options.offsetBox offset box for the popup * @param {Boolean|undefined} options.usePopup use a popup, default true */ ol.interaction.ModifyTouch = function(options) { var self = this; if (!options) options = {}; this._popup = new ol.Overlay.Popup ({ popupClass: options.className || 'modifytouch', positioning: options.positioning || 'bottom-rigth', offsetBox: options.offsetBox || 10 }); this._source = options.source; this._features = options.features; // popup content var a = document.createElement('a'); a.appendChild(document.createTextNode(options.title || "remove point")); a.onclick = function() { self.removePoint(); }; this.setPopupContent(a); var pixelTolerance = options.pixelTolerance || 0; var searchDist = pixelTolerance +5; // Check if there is a feature to select options.condition = function(e) { var features = this.getMap().getFeaturesAtPixel(e.pixel,{ hitTolerance: searchDist }); var p0, p1, found = false; if (features) { var search = this._features; if (!search) { p0 = [e.pixel[0] - searchDist, e.pixel[1] - searchDist] p1 = [e.pixel[0] + searchDist, e.pixel[1] + searchDist] p0 = this.getMap().getCoordinateFromPixel(p0); p1 = this.getMap().getCoordinateFromPixel(p1); var ext = ol.extent.boundingExtent([p0,p1]); search = this._source.getFeaturesInExtent(ext); } if (search.getArray) search = search.getArray(); for (var i=0, f; f=features[i]; i++) { if (search.indexOf(f) >= 0) break; } if (f) { p0 = e.pixel; p1 = f.getGeometry().getClosestPoint(e.coordinate); p1 = this.getMap().getPixelFromCoordinate(p1); var dx = p0[0] - p1[0]; var dy = p0[1] - p1[1]; found = (Math.sqrt(dx*dx+dy*dy) < searchDist); } } // Show popup if any this.showDeleteBt(found ? { type:'show', feature:f, coordinate: e.coordinate } : { type:'hide' }); return true; }; // Hide popup on insert options.insertVertexCondition = function() { this.showDeleteBt({ type:'hide' }); return true; } ol.interaction.Modify.call(this, options); this.on(['modifystart','modifyend'], function(){ this.showDeleteBt({ type:'hide', modifying: true }); }); // Use a popup ? this.set('usePopup', options.usePopup !== false); }; ol.ext.inherits(ol.interaction.ModifyTouch, ol.interaction.Modify); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.ModifyTouch.prototype.setMap = function(map) { if (this.getMap()) { this.getMap().removeOverlay(this._popup); } ol.interaction.Modify.prototype.setMap.call (this, map); if (this.getMap()) { this.getMap().addOverlay(this._popup); } }; /** Activate the interaction and remove popup * @param {Boolean} b */ ol.interaction.ModifyTouch.prototype.setActive = function(b) { ol.interaction.Modify.prototype.setActive.call (this, b); this.showDeleteBt({ type:'hide' }); }; /** * Remove the current point */ ol.interaction.ModifyTouch.prototype.removePoint = function() { // Prevent touch + click on popup if (new Date() - this._timeout < 200) return; // Remove point ol.interaction.Modify.prototype.removePoint.call (this); this.showDeleteBt({ type:'hide' }); } /** * Show the delete button (menu) * @param {Event} e * @api stable */ ol.interaction.ModifyTouch.prototype.showDeleteBt = function(e) { if (this.get('usePopup') && e.type==='show') { this._popup.show(e.coordinate, this._menu); } else { this._popup.hide(); } e.type += 'popup'; this.dispatchEvent(e); // Date if popup start a timeout to prevent touch + click on the popup this._timeout = new Date(); }; /** * Change the popup content * @param {DOMElement} html */ ol.interaction.ModifyTouch.prototype.setPopupContent = function(html) { this._menu = html; } /** * Get the popup content * @return {DOMElement} */ ol.interaction.ModifyTouch.prototype.getPopupContent = function() { return this._menu; } /* 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). */ /** Offset interaction for offseting feature geometry * @constructor * @extends {ol.interaction.Pointer} * @fires offsetstart * @fires offsetting * @fires offsetend * @param {any} options * @param {ol.layer.Vector | Array} options.layers list of feature to transform * @param {ol.Collection.} options.features collection of feature to transform * @param {ol.source.Vector | undefined} options.source source to duplicate feature when ctrl key is down * @param {boolean} options.duplicate force feature to duplicate (source must be set) * @param {ol.style.Style | Array. | ol.style.StyleFunction | undefined} style style for the sketch */ ol.interaction.Offset = function(options) { if (!options) options = {}; // Extend pointer ol.interaction.Pointer.call(this, { handleDownEvent: this.handleDownEvent_, handleDragEvent: this.handleDragEvent_, handleMoveEvent: this.handleMoveEvent_, handleUpEvent: this.handleUpEvent_ }); // Collection of feature to transform this.features_ = options.features; // List of layers to transform this.layers_ = options.layers ? (options.layers instanceof Array) ? options.layers:[options.layers] : null; // duplicate this.set('duplicate', options.duplicate); this.source_ = options.source; // Style this._style = (typeof (options.style) === 'function') ? options.style : function () { if (options.style) return options.style; else return ol.style.Style.defaultStyle(true); }; // init this.previousCursor_ = false; }; ol.ext.inherits(ol.interaction.Offset, ol.interaction.Pointer); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.Offset.prototype.setMap = function(map) { ol.interaction.Pointer.prototype.setMap.call (this, map); }; /** Get Feature at pixel * @param {ol.MapBrowserEvent} evt Map browser event. * @return {any} a feature and the hit point * @private */ ol.interaction.Offset.prototype.getFeatureAtPixel_ = function(e) { var self = this; return this.getMap().forEachFeatureAtPixel(e.pixel, function(feature, layer) { var current; // feature belong to a layer if (self.layers_) { for (var i=0; i1) return false; // test distance var p = current.getGeometry().getClosestPoint(e.coordinate); var dx = p[0]-e.coordinate[0]; var dy = p[1]-e.coordinate[1]; var d = Math.sqrt(dx*dx+dy*dy) / e.frameState.viewState.resolution; if (d<5) { return { feature: current, hit: p, coordinates: current.getGeometry().getCoordinates(), geom: current.getGeometry().clone(), geomType: typeGeom } } else { return false; } } else { return false; } }, { hitTolerance: 5 }); }; /** * @param {ol.MapBrowserEvent} e Map browser event. * @return {boolean} `true` to start the drag sequence. * @private */ ol.interaction.Offset.prototype.handleDownEvent_ = function(e) { this.current_ = this.getFeatureAtPixel_(e); if (this.current_) { this.currentStyle_ = this.current_.feature.getStyle(); if (this.source_ && (this.get('duplicate') || e.originalEvent.ctrlKey)) { this.current_.feature = this.current_.feature.clone(); this.current_.feature.setStyle(this._style(this.current_.feature)); this.source_.addFeature(this.current_.feature); } else { // Modify the current feature this.current_.feature.setStyle(this._style(this.current_.feature)); this._modifystart = true; } this.dispatchEvent({ type:'offsetstart', feature: this.current_.feature, offset: 0 }); return true; } else { return false; } }; /** * @param {ol.MapBrowserEvent} e Map browser event. * @private */ ol.interaction.Offset.prototype.handleDragEvent_ = function(e) { if (this._modifystart) { this.dispatchEvent({ type:'modifystart', features: [ this.current_.feature ] }); this._modifystart = false; } var p = this.current_.geom.getClosestPoint(e.coordinate); var d = ol.coordinate.dist2d(p, e.coordinate); var seg, v1, v2, offset; switch (this.current_.geomType) { case 'Polygon': { seg = ol.coordinate.findSegment(p, this.current_.coordinates[0]).segment; if (seg) { v1 = [ seg[1][0]-seg[0][0], seg[1][1]-seg[0][1] ]; v2 = [ e.coordinate[0]-p[0], e.coordinate[1]-p[1] ]; if (v1[0]*v2[1] - v1[1]*v2[0] > 0) { d = -d; } offset = []; for (var i=0; i 0) { d = -d; } offset = ol.coordinate.offsetCoords(this.current_.coordinates, d); this.current_.feature.setGeometry(new ol.geom.LineString(offset)); } break; } default: { break; } } this.dispatchEvent({ type:'offsetting', feature: this.current_.feature, offset: d, segment: [p, e.coordinate], coordinate: e.coordinate }); }; /** * @param {ol.MapBrowserEvent} e Map browser event. * @private */ ol.interaction.Offset.prototype.handleUpEvent_ = function(e) { if (!this._modifystart) { this.dispatchEvent({ type:'offsetend', feature: this.current_.feature, coordinate: e.coordinate }); } this.current_.feature.setStyle(this.currentStyle_); this.current_ = false; }; /** * @param {ol.MapBrowserEvent} e Event. * @private */ ol.interaction.Offset.prototype.handleMoveEvent_ = function(e) { var f = this.getFeatureAtPixel_(e); if (f) { if (this.previousCursor_ === false) { this.previousCursor_ = e.map.getTargetElement().style.cursor; } e.map.getTargetElement().style.cursor = 'pointer'; } else { e.map.getTargetElement().style.cursor = this.previousCursor_; this.previousCursor_ = false; } }; /* Water ripple effect. Original code (Java) by Neil Wallis @link http://www.neilwallis.com/java/water.html Original code (JS) by Sergey Chikuyonok (serge.che@gmail.com) @link http://chikuyonok.ru @link http://media.chikuyonok.ru/ripple/ Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). @link https://github.com/Viglino */ /** * @constructor * @extends {ol.interaction.Pointer} * @param {*} options * @param {ol/layer/Layer} options.layer layer to animate * @param {number} options.radius raindrop radius * @param {number} options.interval raindrop interval (in ms), default 1000 */ ol.interaction.Ripple = function(options) { ol.interaction.Pointer.call(this, { handleDownEvent: this.rainDrop, handleMoveEvent: this.rainDrop }); // Default options options = options||{}; this.riprad = options.radius || 3; this.ripplemap = []; this.last_map = []; // Generate random ripples this.rains (this.interval); options.layer.on(['postcompose', 'postrender'], this.postcompose_.bind(this)); }; ol.ext.inherits(ol.interaction.Ripple, ol.interaction.Pointer); /** Generate random rain drop * @param {integer} interval */ ol.interaction.Ripple.prototype.rains = function(interval) { if (this.onrain) clearTimeout (this.onrain); var self = this; var vdelay = (typeof(interval)=="number" ? interval : 1000)/2; var delay = 3*vdelay/2; var rnd = Math.random; function rain() { if (self.width) self.rainDrop([rnd() * self.width, rnd() * self.height]); self.onrain = setTimeout (rain, rnd()*vdelay + delay); } // Start raining if (delay) rain(); } /** Disturb water at specified point * @param {ol.Pixel|ol.MapBrowserEvent} */ ol.interaction.Ripple.prototype.rainDrop = function(e) { if (!this.width) return; var dx,dy; if (e.pixel) { dx = e.pixel[0]*this.ratio; dy = e.pixel[1]*this.ratio; } else { dx = e[0]*this.ratio; dy = e[1]*this.ratio; } dx <<= 0; dy <<= 0; for (var j = dy - this.riprad*this.ratio; j < dy + this.riprad*this.ratio; j++) { for (var k = dx - this.riprad*this.ratio; k < dx + this.riprad*this.ratio; k++) { this.ripplemap[this.oldind + (j * this.width) + k] += 128; } } } /** Postcompose function */ ol.interaction.Ripple.prototype.postcompose_ = function(e) { var ctx = e.context; var canvas = ctx.canvas; // Initialize when canvas is ready / modified if (this.width != canvas.width || this.height != canvas.height) { this.width = canvas.width; this.height = canvas.height; this.ratio = e.frameState.pixelRatio; this.half_width = this.width >> 1; this.half_height = this.height >> 1; this.size = this.width * (this.height + 2) * 2; this.oldind = this.width; this.newind = this.width * (this.height + 3); for (var i = 0; i < this.size; i++) { this.last_map[i] = this.ripplemap[i] = 0; } } this.texture = ctx.getImageData(0, 0, this.width, this.height); this.ripple = ctx.getImageData(0, 0, this.width, this.height); // Run animation var a, b, data, cur_pixel, new_pixel; var t = this.oldind; this.oldind = this.newind; this.newind = t; i = 0; var _rd = this.ripple.data, _td = this.texture.data; for (var y = 0; y < this.height; y++) { for (var x = 0; x < this.width; x++) { var _newind = this.newind + i, _mapind = this.oldind + i; data = ( this.ripplemap[_mapind - this.width] + this.ripplemap[_mapind + this.width] + this.ripplemap[_mapind - 1] + this.ripplemap[_mapind + 1]) >> 1; data -= this.ripplemap[_newind]; data -= data >> 5; this.ripplemap[_newind] = data; //where data=0 then still, where data>0 then wave data = 1024 - data; if (this.last_map[i] != data) { this.last_map[i] = data; //offsets a = (((x - this.half_width) * data / 1024) << 0) + this.half_width; b = (((y - this.half_height) * data / 1024) << 0) + this.half_height; //bounds check if (a >= this.width) a = this.width - 1; if (a < 0) a = 0; if (b >= this.height) b = this.height - 1; if (b < 0) b = 0; new_pixel = (a + (b * this.width)) * 4; cur_pixel = i * 4; /**/ _rd[cur_pixel] = _td[new_pixel]; _rd[cur_pixel + 1] = _td[new_pixel + 1]; _rd[cur_pixel + 2] = _td[new_pixel + 2]; /*/ // only in blue pixels if (_td[new_pixel + 2]>_td[new_pixel + 1] && _td[new_pixel + 2]>_td[new_pixel]) { _rd[cur_pixel] = _td[new_pixel]; _rd[cur_pixel + 1] = _td[new_pixel + 1]; _rd[cur_pixel + 2] = _td[new_pixel + 2]; } else this.ripplemap[_newind] = 0; /**/ } ++i; } } ctx.putImageData(this.ripple, 0, 0); // tell OL3 to continue postcompose animation this.getMap().render(); }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (http://www.cecill.info/). ol/interaction/SelectCluster is an interaction for selecting vector features in a cluster. */ /** * @classdesc * Interaction for selecting vector features in a cluster. * It can be used as an ol.interaction.Select. * When clicking on a cluster, it springs apart to reveal the features in the cluster. * Revealed features are selectable and you can pick the one you meant. * Revealed features are themselves a cluster with an attribute features that contain the original feature. * * @constructor * @extends {ol.interaction.Select} * @param {olx.interaction.SelectOptions=} options SelectOptions. * @param {ol.style} options.featureStyle used to style the revealed features as options.style is used by the Select interaction. * @param {boolean} options.selectCluster false if you don't want to get cluster selected * @param {Number} options.pointRadius to calculate distance between the features * @param {bool} options.spiral means you want the feature to be placed on a spiral (or a circle) * @param {Number} options.circleMaxObjects number of object that can be place on a circle * @param {Number} options.maxObjects number of object that can be drawn, other are hidden * @param {bool} options.animate if the cluster will animate when features spread out, default is false * @param {Number} options.animationDuration animation duration in ms, default is 500ms * @param {boolean} options.autoClose if selecting a cluster should close previously selected clusters. False to get toggle feature. Default is true * @fires ol.interaction.SelectEvent * @api stable */ ol.interaction.SelectCluster = function(options) { options = options || {}; this.pointRadius = options.pointRadius || 12; this.circleMaxObjects = options.circleMaxObjects || 10; this.maxObjects = options.maxObjects || 60; this.spiral = (options.spiral !== false); this.animate = options.animate; this.animationDuration = options.animationDuration || 500; this.selectCluster_ = (options.selectCluster !== false); this._autoClose = (options.autoClose !== false) // Create a new overlay layer for var overlay = this.overlayLayer_ = new ol.layer.Vector({ source: new ol.source.Vector({ features: new ol.Collection(), wrapX: options.wrapX, useSpatialIndex: true }), name:'Cluster overlay', updateWhileAnimating: true, updateWhileInteracting: true, displayInLayerSwitcher: false, style: options.featureStyle }); // Add the overlay to selection if (options.layers) { if (typeof(options.layers) == "function") { var fnLayers = options.layers; options.layers = function(layer) { return (layer===overlay || fnLayers(layer)); }; } else if (options.layers.push) { options.layers.push(this.overlayLayer_); } } // Don't select links if (options.filter) { var fnFilter = options.filter; options.filter = function(f,l){ //if (l===overlay && f.get("selectclusterlink")) return false; if (!l && f.get("selectclusterlink")) return false; else return fnFilter(f,l); }; } else options.filter = function(f,l) { //if (l===overlay && f.get("selectclusterlink")) return false; if (!l && f.get("selectclusterlink")) return false; else return true; }; this.filter_ = options.filter; if (!this._autoClose && !options.toggleCondition) { options.toggleCondition = ol.events.condition.singleClick; } ol.interaction.Select.call(this, options); this.on("select", this.selectCluster.bind(this)); }; ol.ext.inherits(ol.interaction.SelectCluster, ol.interaction.Select); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.SelectCluster.prototype.setMap = function(map) { if (this.getMap()) { this.getMap().removeLayer(this.overlayLayer_); } if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; ol.interaction.Select.prototype.setMap.call (this, map); this.overlayLayer_.setMap(map); // map.addLayer(this.overlayLayer_); if (map && map.getView()) { this._listener = map.getView().on('change:resolution', this.clear.bind(this)); } }; /** * Clear the selection, close the cluster and remove revealed features * @api stable */ ol.interaction.SelectCluster.prototype.clear = function() { this.getFeatures().clear(); this.overlayLayer_.getSource().clear(); }; /** * Get the layer for the revealed features * @api stable */ ol.interaction.SelectCluster.prototype.getLayer = function() { return this.overlayLayer_; }; /** * Select a cluster * @param {ol.SelectEvent | ol.Feature} a cluster feature ie. a feature with a 'features' attribute. * @api stable */ ol.interaction.SelectCluster.prototype.selectCluster = function (e) { // It's a feature => convert to SelectEvent if (e instanceof ol.Feature) { e = { selected: [e] }; } // Nothing selected if (!e.selected.length) { if (this._autoClose) { this.clear(); } else { var deselectedFeatures = e.deselected; deselectedFeatures.forEach(deselectedFeature => { var selectClusterFeatures = deselectedFeature.get('selectcluserfeatures'); selectClusterFeatures.forEach(selectClusterFeature => { this.overlayLayer_.getSource().removeFeature(selectClusterFeature); }); }); } return; } // Get selection var feature = e.selected[0]; // It's one of ours if (feature.get('selectclusterfeature')) return; // Clic out of the cluster => close it var source = this.overlayLayer_.getSource(); if (this._autoClose) { source.clear(); } var cluster = feature.get('features'); // Not a cluster (or just one feature) if (!cluster || cluster.length==1) return; // Remove cluster from selection if (!this.selectCluster_) this.getFeatures().clear(); var center = feature.getGeometry().getCoordinates(); // Pixel size in map unit var pix = this.getMap().getView().getResolution(); var r, a, i, max; var p, cf, lk; // The features var features = []; // Draw on a circle if (!this.spiral || cluster.length <= this.circleMaxObjects) { max = Math.min(cluster.length, this.circleMaxObjects); r = pix * this.pointRadius * (0.5 + max / 4); for (i=0; i increase d in one turn r = d/2 + d*a/(2*Math.PI); // Angle a = a + (d+0.1)/r; var dx = pix*r*Math.sin(a) var dy = pix*r*Math.cos(a) p = [ center[0]+dx, center[1]+dy ]; cf = new ol.Feature({ 'selectclusterfeature':true, 'features':[cluster[i]], geometry: new ol.geom.Point(p) }); cf.setStyle(cluster[i].getStyle()); features.push(cf); lk = new ol.Feature({ 'selectclusterlink':true, geometry: new ol.geom.LineString([center,p]) }); features.push(lk); } } feature.set('selectcluserfeatures', features); if (this.animate) { this.animateCluster_(center, features); } else { source.addFeatures(features); } }; /** * Animate the cluster and spread out the features * @param {ol.Coordinates} the center of the cluster */ ol.interaction.SelectCluster.prototype.animateCluster_ = function(center, features) { // Stop animation (if one is running) if (this.listenerKey_) { ol.Observable.unByKey(this.listenerKey_); } // Features to animate // var features = this.overlayLayer_.getSource().getFeatures(); if (!features.length) return; var style = this.overlayLayer_.getStyle(); var stylefn = (typeof(style) == 'function') ? style : style.length ? function(){ return style; } : function(){ return [style]; } ; var duration = this.animationDuration || 500; var start = new Date().getTime(); // Animate function function animate(event) { var vectorContext = event.vectorContext || ol.render.getVectorContext(event); // Retina device var ratio = event.frameState.pixelRatio; var res = this.getMap().getView().getResolution(); var e = ol.easing.easeOut((event.frameState.time - start) / duration); for (var i=0, feature; feature = features[i]; i++) if (feature.get('features')) { var pt = feature.getGeometry().getCoordinates(); pt[0] = center[0] + e * (pt[0]-center[0]); pt[1] = center[1] + e * (pt[1]-center[1]); var geo = new ol.geom.Point(pt); // Image style var st = stylefn(feature, res); for (var s=0; s v3.14 if (vectorContext.setStyle) { vectorContext.setStyle(st[s]); vectorContext.drawGeometry(geo); } // older version else { vectorContext.setImageStyle(imgs); vectorContext.drawPointGeometry(geo); } if (imgs) imgs.setScale(sc); } } // Stop animation and restore cluster visibility if (e > 1.0) { ol.Observable.unByKey(this.listenerKey_); this.overlayLayer_.getSource().addFeatures(features); this.overlayLayer_.changed(); return; } // tell OL3 to continue postcompose animation event.frameState.animate = true; } // Start a new postcompose animation this.listenerKey_ = this.overlayLayer_.on(['postcompose','postrender'], animate.bind(this)); // Start animation with a ghost feature var feature = new ol.Feature(new ol.geom.Point(this.getMap().getView().getCenter())); feature.setStyle(new ol.style.Style({ image: new ol.style.Circle({}) })); this.overlayLayer_.getSource().addFeature(feature); }; /** Helper function to get the extent of a cluster * @param {ol.feature} feature * @return {ol.extent|null} the extent or null if extent is empty (no cluster or superimposed points) */ ol.interaction.SelectCluster.prototype.getClusterExtent = function(feature) { if (!feature.get('features')) return null; var extent = ol.extent.createEmpty(); feature.get('features').forEach(function(f) { extent = ol.extent.extend(extent, f.getGeometry().getExtent()); }); if (extent[0]===extent[2] && extent[1]===extent[3]) return null; return extent; }; /* 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). */ /** Interaction to snap to guidelines * @constructor * @extends {ol.interaction.Interaction} * @param {*} options * @param {number | undefined} options.pixelTolerance distance (in px) to snap to a guideline, default 10 px * @param {bool | undefined} options.enableInitialGuides whether to draw initial guidelines based on the maps orientation, default false. * @param {ol.style.Style | Array | undefined} options.style Style for the sektch features. * @param {*} options.vectorClass a vector layer class to create the guides with ol6, use ol/layer/VectorImage using ol6 */ ol.interaction.SnapGuides = function(options) { if (!options) options = {}; // Intersect 2 guides function getIntersectionPoint (d1, d2) { var d1x = d1[1][0] - d1[0][0]; var d1y = d1[1][1] - d1[0][1]; var d2x = d2[1][0] - d2[0][0]; var d2y = d2[1][1] - d2[0][1]; var det = d1x * d2y - d1y * d2x; if (det != 0) { var k = (d1x * d1[0][1] - d1x * d2[0][1] - d1y * d1[0][0] + d1y * d2[0][0]) / det; return [d2[0][0] + k*d2x, d2[0][1] + k*d2y]; } else return false; } function dist2D (p1,p2) { var dx = p1[0]-p2[0]; var dy = p1[1]-p2[1]; return Math.sqrt(dx*dx+dy*dy); } // Snap distance (in px) this.snapDistance_ = options.pixelTolerance || 10; this.enableInitialGuides_ = options.enableInitialGuides || false; // Default style var sketchStyle = [ new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#ffcc33', lineDash: [8,5], width: 1.25 }) }) ]; // Custom style if (options.style) sketchStyle = options.style instanceof Array ? options.style : [options.style]; // Create a new overlay for the sketch this.overlaySource_ = new ol.source.Vector({ features: new ol.Collection(), useSpatialIndex: false }); // Use ol/layer/VectorImage to render the snap guides as an image to improve performance on rerenderers var vectorClass = options.vectorClass || ol.layer.Vector; this.overlayLayer_ = new vectorClass({ // render the snap guides as an image to improve performance on rerenderers renderMode: 'image', source: this.overlaySource_, style: function() { return sketchStyle; }, name:'Snap overlay', displayInLayerSwitcher: false }); // Use snap interaction ol.interaction.Interaction.call(this, { handleEvent: function(e) { if (this.getActive()) { var features = this.overlaySource_.getFeatures(); var prev = null; var p = null; var res = e.frameState.viewState.resolution; for (var i=0, f; f = features[i]; i++) { var c = f.getGeometry().getClosestPoint(e.coordinate); if ( dist2D(c, e.coordinate) / res < this.snapDistance_) { // Intersection on 2 lines if (prev) { var c2 = getIntersectionPoint(prev.getGeometry().getCoordinates(), f.getGeometry().getCoordinates()); if (c2) { if (dist2D(c2, e.coordinate) / res < this.snapDistance_) { p = c2; } } } else { p = c; } prev = f; } } if (p) e.coordinate = p; } return true; } }); }; ol.ext.inherits(ol.interaction.SnapGuides, ol.interaction.Interaction); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.SnapGuides.prototype.setMap = function(map) { if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_); ol.interaction.Interaction.prototype.setMap.call (this, map); this.overlayLayer_.setMap(map); if (map) this.projExtent_ = map.getView().getProjection().getExtent(); }; /** Activate or deactivate the interaction. * @param {boolean} active */ ol.interaction.SnapGuides.prototype.setActive = function(active) { this.overlayLayer_.setVisible(active); ol.interaction.Interaction.prototype.setActive.call (this, active); } /** Clear previous added guidelines * @param {Array | undefined} features a list of feature to remove, default remove all feature */ ol.interaction.SnapGuides.prototype.clearGuides = function(features) { if (!features) { this.overlaySource_.clear(); } else { for (var i=0, f; f=features[i]; i++) { try { this.overlaySource_.removeFeature(f); } catch(e) {/* nothing to to */} } } }; /** Get guidelines * @return {ol.Collection} guidelines features */ ol.interaction.SnapGuides.prototype.getGuides = function() { return this.overlaySource_.getFeaturesCollection(); } /** Add a new guide to snap to * @param {Array} v the direction vector * @return {ol.Feature} feature guide */ ol.interaction.SnapGuides.prototype.addGuide = function(v, ortho) { if (v) { var map = this.getMap(); // Limit extent var extent = map.getView().calculateExtent(map.getSize()); var guideLength = Math.max( this.projExtent_[2] - this.projExtent_[0], this.projExtent_[3] - this.projExtent_[1] ); extent = ol.extent.buffer(extent, guideLength * 1.5); //extent = ol.extent.boundingExtent(extent, this.projExtent_); if (extent[0]this.projExtent_[2]) extent[2]=this.projExtent_[2]; if (extent[3]>this.projExtent_[3]) extent[3]=this.projExtent_[3]; var dx = v[0][0] - v[1][0]; var dy = v[0][1] - v[1][1]; var d = 1 / Math.sqrt(dx*dx+dy*dy); var generateLine = function(loopDir) { var p, g = []; var loopCond = guideLength*loopDir*2; for (var i=0; loopDir > 0 ? i < loopCond : i > loopCond; i+=(guideLength * loopDir) / 100) { if (ortho) p = [ v[0][0] + dy*d*i, v[0][1] - dx*d*i]; else p = [ v[0][0] + dx*d*i, v[0][1] + dy*d*i]; if (ol.extent.containsCoordinate(extent, p)) g.push(p); else break; } return new ol.Feature(new ol.geom.LineString([g[0], g[g.length-1]])); } var f0 = generateLine(1); var f1 = generateLine(-1); this.overlaySource_.addFeature(f0); this.overlaySource_.addFeature(f1); return [f0, f1]; } }; /** Add a new orthogonal guide to snap to * @param {Array} v the direction vector * @return {ol.Feature} feature guide */ ol.interaction.SnapGuides.prototype.addOrthoGuide = function(v) { return this.addGuide(v, true); }; /** Listen to draw event to add orthogonal guidelines on the first and last point. * @param {_ol_interaction_Draw_} drawi a draw interaction to listen to * @api */ ol.interaction.SnapGuides.prototype.setDrawInteraction = function(drawi) { var self = this; // Number of points currently drawing var nb = 0; // Current guidelines var features = []; function setGuides(e) { var coord = e.target.getCoordinates(); var s = 2; switch (e.target.getType()) { case 'Point': return; case 'Polygon': coord = coord[0].slice(0, -1); break; default: break; } var l = coord.length; if (l === s && self.enableInitialGuides_) { var x = coord[0][0]; var y = coord[0][1]; coord = [[x, y], [x, y - 1]]; } if (l != nb && (self.enableInitialGuides_ ? l >= s : l > s)) { self.clearGuides(features); // use try catch to remove a bug on freehand draw... try { var p1 = coord[l - s], p2 = coord[l - s - 1]; if (l > s && !(p1[0] === p2[0] && p1[1] === p2[1])) { features = self.addOrthoGuide([coord[l - s], coord[l - s - 1]]); } features = features.concat(self.addGuide([coord[0], coord[1]])); features = features.concat(self.addOrthoGuide([coord[0], coord[1]])); nb = l; } catch (e) { /* ok*/ } } } // New drawing drawi.on ("drawstart", function(e) { // When geom is changing add a new orthogonal direction e.feature.getGeometry().on("change", setGuides); }); // end drawing / deactivate => clear directions drawi.on (["drawend", "change:active"], function(e) { self.clearGuides(features); if (e.feature) e.feature.getGeometry().un("change", setGuides); nb = 0; features = []; }); }; /** Listen to modify event to add orthogonal guidelines relative to the currently dragged point * @param {_ol_interaction_Modify_} modifyi a modify interaction to listen to * @api */ ol.interaction.SnapGuides.prototype.setModifyInteraction = function (modifyi) { function mod(d, n) { return ((d % n) + n) % n; } var self = this; // Current guidelines var features = []; function computeGuides(e) { var selectedVertex = e.target.vertexFeature_ if (!selectedVertex) return; var f = e.target.getModifiedFeatures()[0]; var geom = f.getGeometry(); var coord = geom.getCoordinates(); switch (geom.getType()) { case 'Point': return; case 'Polygon': coord = coord[0].slice(0, -1); break; default: break; } var modifyVertex = selectedVertex.getGeometry().getCoordinates(); var idx = coord.findIndex(function(c) { return c[0] === modifyVertex[0] && c[1] === modifyVertex[1] }); var l = coord.length; self.clearGuides(features); features = self.addOrthoGuide([coord[mod(idx - 1, l)], coord[mod(idx - 2, l)]]); features = features.concat(self.addGuide([coord[mod(idx - 1, l)], coord[mod(idx - 2, l)]])); features = features.concat(self.addGuide([coord[mod(idx + 1, l)], coord[mod(idx + 2, l)]])); features = features.concat(self.addOrthoGuide([coord[mod(idx + 1, l)], coord[mod(idx + 2, l)]])); } function setGuides(e) { // This callback is called before ol adds the vertex to the feature, so // defer a moment for openlayers to add the new vertex setTimeout(computeGuides, 0, e); } function drawEnd() { self.clearGuides(features); features = []; } // New drawing modifyi.on("modifystart", setGuides); // end drawing, clear directions modifyi.on("modifyend", drawEnd); }; /* 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). */ /** An interaction to snap on pixel on a layer * The CenterTouch interaction modifies map browser event coordinate and pixel properties to force pointer on the viewport center to any interaction that them. * @constructor * @extends {ol.interaction.Interaction} * @param {olx.interaction.InteractionOptions} options Options * @param {ol.layer.Layer} options.layer layer to snap on */ ol.interaction.SnapLayerPixel = function(options) { options = options || {}; // Get layer canevas context this._layer = options.layer; this._layer.on(['postcompose', 'postrender'], function(e) { this._ctx = e.context; }.bind(this)); var radius = options.radius || 8; var size = 2*radius; ol.interaction.Interaction.call(this, { handleEvent: function(e) { if (this._layer.getVisible() && this._layer.getOpacity() && ol.events.condition.altKeyOnly(e) && this.getMap()) { var x0 = e.pixel[0] - radius; var y0 = e.pixel[1] - radius; var imgd = this._ctx.getImageData(x0, y0, size, size); var pix = imgd.data; // Loop over each pixel and invert the color. var x, y, xm, ym, max=-1; var t = []; for (x=0; x < size; x++) { t.push([]) for (y=0; y < size; y++) { var l = pix[3+ 4 * (x + y*size)]; t[x].push(l>10 ? l : 0) } } for (x=1; x < size-1; x++) { for (y=1; y < size-1; y++) { var m = t[x][y+1] + t[x][y] + t[x][y+1] + t[x-1][y-1] + t[x-1][y] + t[x-1][y+1] + t[x+1][y-1] + t[x+1][y] + t[x+1][y+1]; if (m > max) { max = m; xm = x; ym = y; } } } e.pixel = [x0+xm, y0+ym]; e.coordinate = this.getMap().getCoordinateFromPixel(e.pixel); /* e.coordinate = this.getMap().getView().getCenter(); e.pixel = this.getMap().getSize(); e.pixel = [ e.pixel[0]/2, e.pixel[1]/2 ]; */ } return true; } }); }; ol.ext.inherits(ol.interaction.SnapLayerPixel, ol.interaction.Interaction); /* 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). */ /** Interaction split interaction for splitting feature geometry * @constructor * @extends {ol.interaction.Interaction} * @fires beforesplit, aftersplit, pointermove * @param {*} * @param {ol.source.Vector|Array} options.source a list of source to split (configured with useSpatialIndex set to true) * @param {ol.Collection.} options.features collection of feature to split * @param {integer} options.snapDistance distance (in px) to snap to an object, default 25px * @param {string|undefined} options.cursor cursor name to display when hovering an objet * @param {function|undefined} opttion.filter a filter that takes a feature and return true if it can be clipped, default always split. * @param ol.style.Style | Array | false | undefined} options.featureStyle Style for the selected features, choose false if you don't want feature selection. By default the default edit style is used. * @param {ol.style.Style | Array | undefined} options.sketchStyle Style for the sektch features. * @param {function|undefined} options.tolerance Distance between the calculated intersection and a vertex on the source geometry below which the existing vertex will be used for the split. Default is 1e-10. */ ol.interaction.Split = function(options) { if (!options) options = {}; ol.interaction.Interaction.call(this, { handleEvent: function(e) { switch (e.type) { case "singleclick": return this.handleDownEvent(e); case "pointermove": return this.handleMoveEvent(e); default: return true; } //return true; } }); // Snap distance (in px) this.snapDistance_ = options.snapDistance || 25; // Split tolerance between the calculated intersection and the geometry this.tolerance_ = options.tolerance || 1e-10; // Cursor this.cursor_ = options.cursor; // List of source to split this.sources_ = options.sources ? (options.sources instanceof Array) ? options.sources:[options.sources] : []; if (options.features) { this.sources_.push (new ol.source.Vector({ features: options.features })); } // Get all features candidate this.filterSplit_ = options.filter || function(){ return true; }; // Default style var white = [255, 255, 255, 1]; var blue = [0, 153, 255, 1]; var width = 3; var fill = new ol.style.Fill({ color: 'rgba(255,255,255,0.4)' }); var stroke = new ol.style.Stroke({ color: '#3399CC', width: 1.25 }); var sketchStyle = [ new ol.style.Style({ image: new ol.style.Circle({ fill: fill, stroke: stroke, radius: 5 }), fill: fill, stroke: stroke }) ]; var featureStyle = [ new ol.style.Style({ stroke: new ol.style.Stroke({ color: white, width: width + 2 }) }), new ol.style.Style({ image: new ol.style.Circle({ radius: 2*width, fill: new ol.style.Fill({ color: blue }), stroke: new ol.style.Stroke({ color: white, width: width/2 }) }), stroke: new ol.style.Stroke({ color: blue, width: width }) }), ]; // Custom style if (options.sketchStyle) sketchStyle = options.sketchStyle instanceof Array ? options.sketchStyle : [options.sketchStyle]; if (options.featureStyle) featureStyle = options.featureStyle instanceof Array ? options.featureStyle : [options.featureStyle]; // Create a new overlay for the sketch this.overlayLayer_ = new ol.layer.Vector({ source: new ol.source.Vector({ useSpatialIndex: false }), name:'Split overlay', displayInLayerSwitcher: false, style: function(f) { if (f._sketch_) return sketchStyle; else return featureStyle; } }); }; ol.ext.inherits(ol.interaction.Split, ol.interaction.Interaction); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.Split.prototype.setMap = function(map) { if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_); ol.interaction.Interaction.prototype.setMap.call (this, map); this.overlayLayer_.setMap(map); }; /** Get closest feature at pixel * @param {ol.Pixel} * @return {ol.feature} * @private */ ol.interaction.Split.prototype.getClosestFeature = function(e) { var source, f, c, g, d = this.snapDistance_+1; // Look for closest point in the sources this.sources_.forEach(function(si) { var fi = si.getClosestFeatureToCoordinate(e.coordinate); if (fi && fi.getGeometry().splitAt) { var ci = fi.getGeometry().getClosestPoint(e.coordinate); var gi = new ol.geom.LineString([e.coordinate,ci]); var di = gi.getLength() / e.frameState.viewState.resolution; if (di < d) { source = si; d = di; f = fi; g = gi; c = ci; } } }); // Snap ? if (d > this.snapDistance_) { return false; } else { // Snap to node var coord = this.getNearestCoord (c, f.getGeometry().getCoordinates()); var p = this.getMap().getPixelFromCoordinate(coord); if (ol.coordinate.dist2d(e.pixel, p) < this.snapDistance_) { c = coord; } // return { source:source, feature:f, coord: c, link: g }; } } /** Get nearest coordinate in a list * @param {ol.coordinate} pt the point to find nearest * @param {Array} coords list of coordinates * @return {ol.coordinate} the nearest coordinate in the list */ ol.interaction.Split.prototype.getNearestCoord = function(pt, coords) { var d, dm=Number.MAX_VALUE, p0; for (var i=0; i < coords.length; i++) { d = ol.coordinate.dist2d (pt, coords[i]); if (d < dm) { dm = d; p0 = coords[i]; } } return p0; }; /** * @param {ol.MapBrowserEvent} evt Map browser event. * @return {boolean} `true` to start the drag sequence. */ ol.interaction.Split.prototype.handleDownEvent = function(evt) { // Something to split ? var current = this.getClosestFeature(evt); if (current) { var self = this; self.overlayLayer_.getSource().clear(); var split = current.feature.getGeometry().splitAt(current.coord, this.tolerance_); var i; if (split.length > 1) { var tosplit = []; for (i=0; i} A collection of feature to be split (replace source target). * - triggerFeatures {ol.Collection.} Any newly created or modified features from this collection will be used to split features on the target source (replace triggerSource). * - filter {function|undefined} a filter that takes a feature and return true if the feature is eligible for splitting, default always split. * - tolerance {function|undefined} Distance between the calculated intersection and a vertex on the source geometry below which the existing vertex will be used for the split. Default is 1e-10. * @todo verify auto intersection on features that split. */ ol.interaction.Splitter = function(options) { if (!options) options = {}; ol.interaction.Interaction.call(this, { handleEvent: function(e) { // Hack to get only one changeFeature when draging with ol.interaction.Modify on. if (e.type != "pointermove" && e.type != "pointerdrag") { if (this.lastEvent_) { this.splitSource(this.lastEvent_.feature); this.lastEvent_ = null; } this.moving_ = false; } else this.moving_ = true; return true; }, }); // Features added / remove this.added_ = []; this.removed_ = []; // Source to split if (options.features) { this.source_ = new ol.source.Vector({ features: options.features }); } else { this.source_ = options.source ? options.source : new ol.source.Vector({ features: new ol.Collection() }); } var trigger = this.triggerSource; if (options.triggerFeatures) { trigger = new ol.source.Vector({ features: options.triggerFeatures }); } if (trigger) { trigger.on("addfeature", this.onAddFeature.bind(this)); trigger.on("changefeature", this.onChangeFeature.bind(this)); trigger.on("removefeature", this.onRemoveFeature.bind(this)); } else { this.source_.on("addfeature", this.onAddFeature.bind(this)); this.source_.on("changefeature", this.onChangeFeature.bind(this)); this.source_.on("removefeature", this.onRemoveFeature.bind(this)); } // Split tolerance between the calculated intersection and the geometry this.tolerance_ = options.tolerance || 1e-10; // Get all features candidate this.filterSplit_ = options.filter || function(){ return true; }; }; ol.ext.inherits(ol.interaction.Splitter, ol.interaction.Interaction); /** Calculate intersection on 2 segs * @param {Array<_ol_coordinate_>} s1 first seg to intersect (2 points) * @param {Array<_ol_coordinate_>} s2 second seg to intersect (2 points) * @return { boolean | _ol_coordinate_ } intersection point or false no intersection */ ol.interaction.Splitter.prototype.intersectSegs = function(s1,s2) { var tol = this.tolerance_; // Solve var x12 = s1[0][0] - s1[1][0]; var x34 = s2[0][0] - s2[1][0]; var y12 = s1[0][1] - s1[1][1]; var y34 = s2[0][1] - s2[1][1]; var det = x12 * y34 - y12 * x34; // No intersection if (Math.abs(det) < tol) { return false; } else { // Outside segement var r1 = ((s1[0][0] - s2[1][0])*y34 - (s1[0][1] - s2[1][1])*x34) / det; if (Math.abs(r1)1) return false; var r2 = ((s1[0][1] - s2[1][1])*x12 - (s1[0][0] - s2[1][0])*y12) / det; if (Math.abs(r2)1) return false; // Intersection var a = s1[0][0] * s1[1][1] - s1[0][1] * s1[1][0]; var b = s2[0][0] * s2[1][1] - s2[0][1] * s2[1][0]; var p = [(a * x34 - b * x12) / det, (a * y34 - b * y12) / det]; // Test start / end /* console.log("r1: "+r1) console.log("r2: "+r2) console.log ("s10: "+(_ol_coordinate_.dist2d(p,s1[0])1) { found = f; return true; } } } } return false; } // Split existing features for (i=0; i1) { for (k=0; k wait end of other interactions setTimeout (function() { if (splitOriginal) self.source_.removeFeature(feature); self.source_.dispatchEvent({ type:'aftersplit', featureAdded: self.added_, featureRemoved: self.removed_, source: this.source_ }); // Finish self.splitting = false; },0); }; /** New feature source is added */ ol.interaction.Splitter.prototype.onAddFeature = function(e) { this.splitSource(e.feature); if (this.splitting) { this.added_.push(e.feature); } /* if (this.splitting) return; var self = this; setTimeout (function() { self.splitSource(e.feature); }, 0); */ }; /** Feature source is removed > count features added/removed */ ol.interaction.Splitter.prototype.onRemoveFeature = function(e) { if (this.splitting) { var n = this.added_.indexOf(e.feature); if (n==-1) { this.removed_.push(e.feature); } else { this.added_.splice(n,1); } } }; /** Feature source is changing */ ol.interaction.Splitter.prototype.onChangeFeature = function(e) { if (this.moving_) { this.lastEvent_ = e; } else this.splitSource(e.feature); }; /* 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). */ /** Interaction synchronize * @constructor * @extends {ol.interaction.Interaction} * @param {*} options * @param {Array} options maps An array of maps to synchronize with the map of the interaction */ ol.interaction.Synchronize = function(options) { if (!options) options={}; var self = this; ol.interaction.Interaction.call(this, { handleEvent: function(e) { if (e.type=="pointermove") { self.handleMove_(e); } return true; } }); this.maps = options.maps || []; if (options.active === false) this.setActive(false); }; ol.ext.inherits(ol.interaction.Synchronize, ol.interaction.Interaction); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.Synchronize.prototype.setMap = function(map) { if (this._listener) { ol.Observable.unByKey(this._listener.center); ol.Observable.unByKey(this._listener.rotation); ol.Observable.unByKey(this._listener.resolution); this.getMap().getTargetElement().removeEventListener('mouseout', this._listener.mouseout); } this._listener = null; ol.interaction.Interaction.prototype.setMap.call (this, map); if (map) { this._listener = {}; this._listener.center = this.getMap().getView().on('change:center', this.syncMaps.bind(this)); this._listener.rotation = this.getMap().getView().on('change:rotation', this.syncMaps.bind(this)); this._listener.resolution = this.getMap().getView().on('change:resolution', this.syncMaps.bind(this)); this._listener.mouseout = this.handleMouseOut_.bind(this); if (this.getMap().getTargetElement()) { this.getMap().getTargetElement().addEventListener('mouseout', this._listener.mouseout); } this.syncMaps(); } }; /** Auto activate/deactivate controls in the bar * @param {boolean} b activate/deactivate */ ol.interaction.Synchronize.prototype.setActive = function (b) { ol.interaction.Interaction.prototype.setActive.call(this, b); this.syncMaps(); }; /** Synchronize the maps */ ol.interaction.Synchronize.prototype.syncMaps = function() { if (!this.getActive()) return; var map = this.getMap(); if (map) { if (map.get('lockView')) return; for (var i=0; i tells other maps to show the cursor * @param {ol.event} e "move" event */ ol.interaction.Synchronize.prototype.handleMove_ = function(e) { for (var i=0; i tells other maps to hide the cursor * @param {event} e "mouseOut" event */ ol.interaction.Synchronize.prototype.handleMouseOut_ = function(/*e*/) { for (var i=0; i start postcompose */ ol.interaction.TinkerBell.prototype.setMap = function(map) { if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; if (this.getMap()) { map.getViewport().removeEventListener('mouseout', this.out_, false); this.getMap().render(); } ol.interaction.Pointer.prototype.setMap.call(this, map); if (map) { this._listener = map.on('postcompose', this.postcompose_.bind(this)); map.getViewport().addEventListener('mouseout', this.out_, false); } }; ol.interaction.TinkerBell.prototype.onMove = function(e) { this.sparkle = e.pixel; this.isout_ = false; this.getMap().render(); }; /** Postcompose function */ ol.interaction.TinkerBell.prototype.postcompose_ = function(e) { var delta = 15; var ctx = e.context || ol.ext.getMapCanvas(this.getMap()).getContext('2d'); var dt = e.frameState.time - this.time; this.time = e.frameState.time; if (e.frameState.time-this.lastSparkle > 30 && !this.isout_) { this.lastSparkle = e.frameState.time; this.sparkles.push({ p:[this.sparkle[0]+Math.random()*delta-delta/2, this.sparkle[1]+Math.random()*delta], o:1 }); } ctx.save(); ctx.scale(e.frameState.pixelRatio,e.frameState.pixelRatio); ctx.fillStyle = this.get("color"); for (var i=this.sparkles.length-1, p; p=this.sparkles[i]; i--) { if (p.o < 0.2) { this.sparkles.splice(0,i+1); break; } ctx.globalAlpha = p.o; ctx.beginPath(); ctx.arc (p.p[0], p.p[1], 2.2, 0, 2 * Math.PI, false); ctx.fill(); p.o *= 0.98; p.p[0] += (Math.random()-0.5); p.p[1] += dt*(1+Math.random())/30; } ctx.restore(); // continue postcompose animation if (this.sparkles.length) this.getMap().render(); }; /* 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). */ /** Interaction splitter: acts as a split feature agent while editing vector features (LineString). * @constructor * @extends {ol.interaction.Pointer} * @param {olx.interaction.TouchCompass} * - onDrag {function|undefined} Function handling "drag" events. It provides a dpixel and a traction (in projection) vector form the center of the compas * - size {Number} size of the compass in px, default 80 * - alpha {Number} opacity of the compass, default 0.5 */ ol.interaction.TouchCompass = function(options) { options = options||{}; var opt = {}; // Click on the compass opt.handleDownEvent = function(e) { var s = this.getCenter_(); var dx = e.pixel[0]-s[0]; var dy = e.pixel[1]-s[1]; this.start = e; return (Math.sqrt(dx*dx+dy*dy) < this.size/2); }; // Pn drag opt.handleDragEvent = function(e) { if (!this.pos) { this.pos = this.start; try { this.getMap().renderSync(); } catch(e) { /* ok */ } } this.pos = e; }; // Stop drag opt.handleUpEvent = function() { this.pos = false; return true; }; ol.interaction.Pointer.call(this, opt); this.ondrag_ = options.onDrag; this.size = options.size || 80; this.alpha = options.alpha || 0.5; if (!ol.interaction.TouchCompass.prototype.compass) { var canvas = ol.interaction.TouchCompass.prototype.compass = document.createElement('canvas'); var ctx = canvas.getContext("2d"); var s = canvas.width = canvas.height = this.size; var w = s/10; var r = s/2; var r2 = 0.22*r; ctx.translate(r,r); ctx.fillStyle = "#999"; ctx.strokeStyle = "#ccc"; ctx.lineWidth = w; ctx.beginPath(); ctx.arc (0,0, s*0.42, 0, 2*Math.PI); ctx.fill(); ctx.stroke(); ctx.fillStyle = "#99f"; ctx.beginPath(); ctx.moveTo (0,0); ctx.lineTo (r,0); ctx.lineTo (r2,r2); ctx.moveTo (0,0); ctx.lineTo (-r,0); ctx.lineTo (-r2,-r2); ctx.moveTo (0,0); ctx.lineTo (0,r); ctx.lineTo (-r2,r2); ctx.moveTo (0,0); ctx.lineTo (0,-r); ctx.lineTo (r2,-r2); ctx.moveTo (0,0); ctx.fill(); ctx.fillStyle = "#eee"; ctx.beginPath(); ctx.moveTo (0,0); ctx.lineTo (r,0); ctx.lineTo (r2,-r2); ctx.moveTo (0,0); ctx.lineTo (-r,0); ctx.lineTo (-r2,r2); ctx.moveTo (0,0); ctx.lineTo (0,r); ctx.lineTo (r2,r2); ctx.moveTo (0,0); ctx.lineTo (0,-r); ctx.lineTo (-r2,-r2); ctx.moveTo (0,0); ctx.fill(); } }; ol.ext.inherits(ol.interaction.TouchCompass, ol.interaction.Pointer); /** Compass Image as a JS Image object * @api */ ol.interaction.TouchCompass.prototype.compass = null; /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {_ol_Map_} map Map. * @api stable */ ol.interaction.TouchCompass.prototype.setMap = function(map) { if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; ol.interaction.Pointer.prototype.setMap.call (this, map); if (map) { this._listener = map.on('postcompose', this.drawCompass_.bind(this)); ol.ext.getMapCanvas(map); } }; /** * Activate or deactivate the interaction. * @param {boolean} active Active. * @observable * @api */ ol.interaction.TouchCompass.prototype.setActive = function(b) { ol.interaction.Pointer.prototype.setActive.call (this, b); if (this.getMap()) { try { this.getMap().renderSync(); } catch(e) { /* ok */ } } } /** * Get the center of the compass * @param {_ol_coordinate_} * @private */ ol.interaction.TouchCompass.prototype.getCenter_ = function() { var margin = 10; var s = this.size; var c = this.getMap().getSize(); return [c[0]/2, c[1]-margin-s/2]; } /** * Draw the compass on post compose * @private */ ol.interaction.TouchCompass.prototype.drawCompass_ = function(e) { if (!this.getActive()) return; var ctx = e.context || ol.ext.getMapCanvas(this.getMap()).getContext('2d'); var ratio = e.frameState.pixelRatio; ctx.save(); ctx.scale(ratio,ratio); ctx.globalAlpha = this.alpha; ctx.strokeStyle = "#fff"; ctx.lineWidth = 5; var s = this.size; var c = this.getCenter_(); ctx.drawImage(this.compass, 0,0,this.compass.width,this.compass.height, c[0]-s/2, c[1]-s/2, s,s); if (this.pos) { var dx = this.pos.pixel[0]-this.start.pixel[0]; var dy = this.pos.pixel[1]-this.start.pixel[1]; for (var i=1; i<=4; i++) { ctx.beginPath(); ctx.arc (c[0] +dx/4*i, c[1] +dy/4*i, s/2*(0.6+0.4*i/4), 0, 2*Math.PI); ctx.stroke(); } } ctx.restore(); if (this.pos) { // Get delta if (this.ondrag_) { var r = this.getMap().getView().getResolution(); var delta = { dpixel: [ this.pos.pixel[0] - this.start.pixel[0], this.pos.pixel[1] - this.start.pixel[1] ] } delta.traction = [ delta.dpixel[0]*r, -delta.dpixel[1]*r]; this.ondrag_(delta, this.pos); } // Continue animation e.frameState.animate = true; } }; /* 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). */ /** Handle a touch cursor to defer event position on overlay position * It can be used as abstract base class used for creating subclasses. * The TouchCursor interaction modifies map browser event coordinate and pixel properties to force pointer on the graphic cursor on the screen to any interaction that them. * @constructor * @extends {ol.interaction.DragOverlay} * @param {olx.interaction.InteractionOptions} options Options * @param {string} options.className cursor class name * @param {ol.coordinate} options.coordinate position of the cursor * @param {Array<*>} options.buttons an array of buttons * @param {number} options.maxButtons maximum number of buttons (default 5) */ ol.interaction.TouchCursor = function(options) { options = options || {}; // List of listerner on the object this._listeners = {}; // Interaction to defer position on top of the interaction // this is done to enable other coordinates manipulation inserted after the interaction (snapping) var offset = [-35,-35]; this.ctouch = new ol.interaction.Interaction({ handleEvent: function(e) { if (!/drag/.test(e.type) && this.getMap()) { e.coordinate = this.overlay.getPosition(); e.pixel = this.getMap().getPixelFromCoordinate(e.coordinate); this._lastEvent = e; } else { var res = e.frameState.viewState.resolution var cosa = Math.cos(e.frameState.viewState.rotation); var sina = Math.sin(e.frameState.viewState.rotation); e.coordinate = [ e.coordinate[0] + cosa*offset[0]*res + sina*offset[1]*res, e.coordinate[1] + sina*offset[0]*res - cosa*offset[1]*res ]; e.pixel = this.getMap().getPixelFromCoordinate(e.coordinate); } return true; }.bind(this) }); // Force interaction on top this.ctouch.set('onTop', true); // Add Overlay this.overlay = new ol.Overlay.Fixed({ className: ('ol-touch-cursor '+(options.className||'')).trim(), positioning: 'top-left', element: ol.ext.element.create('DIV', {}), stopEvent: false, }); ol.interaction.DragOverlay.call(this, { centerOnClick: false, //offset: [-20,-20], overlays: this.overlay }); this.setPosition(options.coordinate, true); this.set('maxButtons', options.maxButtons || 5); if (options.buttons) { if (options.buttons.length > this.get('maxButtons')) this.set('maxButtons', options.buttons.length); var elt = this.overlay.element; var begin = options.buttons.length > 4 ? 0 : 1; options.buttons.forEach((function (b, i) { if (i<5) { ol.ext.element.create('DIV', { className: ((b.className||'')+' ol-button ol-button-' + (i+begin)).trim(), html: ol.ext.element.create('DIV', { html: b.html }), click: b.click, on: b.on, parent: elt }) } })) } // Replace events to handle click var dragging = false; var start = false; this.on('dragstart', function (e) { this._pixel = this.getMap().getPixelFromCoordinate(this.overlay.getPosition()) start = e; return !e.overlay; }) this.on('dragend', function (e) { this._pixel = this.getMap().getPixelFromCoordinate(this.overlay.getPosition()) if (!e.overlay) return true; if (dragging) { this.dispatchEvent({ type: 'dragend', dragging: dragging, originalEvent: e.originalEvent, frameState: e.frameState, pixel: this._pixel, coordinate: this.overlay.getPosition() }); dragging = false; } else { if (e.originalEvent.target === this.overlay.element) { this.dispatchEvent({ type: 'click', dragging: dragging, originalEvent: e.originalEvent, frameState: e.frameState, pixel: this._pixel, coordinate: this.overlay.getPosition() }); } } return false; }.bind(this)) this.on('dragging', function (e) { this._pixel = this.getMap().getPixelFromCoordinate(this.overlay.getPosition()); if (!e.overlay) return true; dragging = true; if (start) { this.dispatchEvent({ type: 'dragstart', dragging: dragging, originalEvent: start.originalEvent, frameState: e.frameState, pixel: this._pixel, coordinate: start.coordinate }); start = false; } this.dispatchEvent({ type: 'dragging', dragging: dragging, originalEvent: e.originalEvent, frameState: e.frameState, pixel: this._pixel, coordinate: this.overlay.getPosition() }); return false; }.bind(this)) }; ol.ext.inherits(ol.interaction.TouchCursor, ol.interaction.DragOverlay); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {_ol_Map_} map Map. * @api stable */ ol.interaction.TouchCursor.prototype.setMap = function(map) { // Reset if (this.getMap()) { this.getMap().removeInteraction(this.ctouch); if (this.getActive()) this.getMap().removeOverlay(this.overlay); } for (var l in this._listeners) { ol.Observable.unByKey(this._listeners[l]); } this._listeners = {}; ol.interaction.DragOverlay.prototype.setMap.call (this, map); // Set listeners if (map) { if (this.getActive()) { map.addOverlay(this.overlay); setTimeout( function() { this.setPosition(this.getPosition() || map.getView().getCenter()); }.bind(this)); } map.addInteraction(this.ctouch); this._listeners.addInteraction = map.getInteractions().on('add', function(e) { // Move on top if (!e.element.get('onTop')) { map.removeInteraction(this.ctouch); map.addInteraction(this.ctouch); } }.bind(this)); } }; /** * Activate or deactivate the interaction. * @param {boolean} active Active. * @param {ol.coordinate|null} position position of the cursor (when activating), default viewport center. * @observable * @api */ ol.interaction.TouchCursor.prototype.setActive = function(b, position) { if (b!==this.getActive()) { this.ctouch.setActive(b); if (!b) { this.setPosition(); this.overlay.element.classList.remove('active'); if (this._activate) clearTimeout(this._activate); if (this.getMap()) this.getMap().removeOverlay(this.overlay); } else { if (this.getMap()) { this.getMap().addOverlay(this.overlay); } if (position) { this.setPosition(position); } else if (this.getMap()) { this.setPosition(this.getMap().getView().getCenter()); } this._activate = setTimeout(function() { this.overlay.element.classList.add('active'); }.bind(this), 100); } ol.interaction.DragOverlay.prototype.setActive.call (this, b); } else if (position) { this.setPosition(position); } else if (this.getMap()) { this.setPosition(this.getMap().getView().getCenter()); } }; /** Set the position of the target * @param {ol.coordinate} coord */ ol.interaction.TouchCursor.prototype.setPosition = function (coord) { this.overlay.setPosition(coord, true); if (this.getMap() && coord) { this._pixel = this.getMap().getPixelFromCoordinate(coord); } }; /** Offset the target position * @param {ol.coordinate} coord */ ol.interaction.TouchCursor.prototype.offsetPosition = function (coord) { var pos = this.overlay.getPosition(); if (pos) this.overlay.setPosition([pos[0]+coord[0], pos[1]+coord[1]]); }; /** Get the position of the target * @return {ol.coordinate} */ ol.interaction.TouchCursor.prototype.getPosition = function () { return this.overlay.getPosition(); }; /** Get pixel position * @return {ol.pixel} */ ol.interaction.TouchCursor.prototype.getPixel = function () { if (this.getMap()) return this.getMap().getPixelFromCoordinate(this.getPosition()); }; /** Get cursor overlay * @return {ol.Overlay} */ ol.interaction.TouchCursor.prototype.getOverlay = function () { return this.overlay; }; /** Get cursor overlay element * @return {Element} */ ol.interaction.TouchCursor.prototype.getOverlayElement = function () { return this.overlay.element; }; /** Get cursor button element * @param {string|number} button the button className or the button index * @return {Element} */ ol.interaction.TouchCursor.prototype.getButtonElement = function (button) { if (typeof(button) === 'number') return this.getOverlayElement().getElementsByClassName('ol-button')[button]; return this.getOverlayElement().getElementsByClassName(button)[0]; }; /** Remove a button element * @param {string|number|undefined} button the button className or the button index, if undefined remove all buttons, default remove all * @return {Element} */ ol.interaction.TouchCursor.prototype.removeButton = function (button) { if (button===undefined) { var buttons = this.getOverlayElement().getElementsByClassName('ol-button'); for (var i=buttons.length-1; i>=0; i--) { this.getOverlayElement().removeChild(buttons[i]); } } else { var elt = this.getButtonElement(button); if (elt) this.getOverlayElement().removeChild(elt); } }; /** Add a button element * @param {*} button * @param {string} options.className button class name * @param {DOMElement|string} options.html button content * @param {function} options.click onclick function * @param {*} options.on an object with * @param {boolean} options.before */ ol.interaction.TouchCursor.prototype.addButton = function (b) { var buttons = this.getOverlayElement().getElementsByClassName('ol-button'); var max = (this.get('maxButtons') || 5); if (buttons.length >= max) { console.error('[ol/interaction/TouchCursor~addButton] too many button on the cursor (max='+max+')...') return; } var button = ol.ext.element.create('DIV', { className: ((b.className||'')+' ol-button').trim(), html: ol.ext.element.create('DIV', { html: b.html }), click: b.click, on: b.on }); if (!b.before || buttons.length===0) this.getOverlayElement().appendChild(button); else this.getOverlayElement().insertBefore(button, buttons[0]); // Reorder buttons var start = buttons.length >= max ? 0 : 1; for (var i=0; i} options.types geometry types avaliable, default none * @param {ol.source.Vector} options.source Destination source for the drawn features * @param {ol.Collection} options.features Destination collection for the drawn features * @param {number} options.clickTolerance The maximum distance in pixels for "click" event to add a point/vertex to the geometry being drawn. default 6 * @param {number} options.snapTolerance Pixel distance for snapping to the drawing finish, default 12 * @param {number} options.maxPoints The number of points that can be drawn before a polygon ring or line string is finished. By default there is no restriction. * @param {number} options.minPoints The number of points that must be drawn before a polygon ring or line string can be finished. Default is 3 for polygon rings and 2 for line strings. * @param {ol.style.Style} options.style Style for sketch features. * @param {function} options.geometryFunction Function that is called when a geometry's coordinates are updated. * @param {string} options.geometryName Geometry name to use for features created by the draw interaction. * @param {boolean} options.wrapX Wrap the world horizontally on the sketch overlay, default false */ ol.interaction.TouchCursorDraw = function(options) { options = options || {}; // Draw var sketch = this.sketch = new ol.layer.SketchOverlay({ type: options.type }); sketch.on('drawend', function(e) { if (e.valid && options.source) options.source.addFeature(e.feature); this.getOverlayElement().classList.add('nodrawing'); this.dispatchEvent(e); }.bind(this)); sketch.on('drawstart', function(e) { this.getOverlayElement().classList.remove('nodrawing'); this.dispatchEvent(e); }.bind(this)); sketch.on('drawabort', function(e) { this.getOverlayElement().classList.add('nodrawing'); this.dispatchEvent(e); }.bind(this)); // Create cursor ol.interaction.TouchCursor.call(this, { className: options.className, coordinate: options.coordinate, }); this.getOverlayElement().classList.add('nodrawing'); this.set('types', options.types); this.setType(options.type); this.on('click', function() { this.sketch.addPoint(this.getPosition()); }.bind(this)) this.on('dragging', function() { this.sketch.setPosition(this.getPosition()); }.bind(this)) }; ol.ext.inherits(ol.interaction.TouchCursorDraw, ol.interaction.TouchCursor); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {_ol_Map_} map Map. * @api stable */ ol.interaction.TouchCursorDraw.prototype.setMap = function(map) { ol.interaction.TouchCursor.prototype.setMap.call (this, map); this.sketch.setMap(map); if (map) { this._listeners.movend = map.on('moveend', function() { this.sketch.setPosition(this.getPosition()) }.bind(this)) } }; /** * Activate or deactivate the interaction. * @param {boolean} active Active. * @param {ol.coordinate|null} position position of the cursor (when activating), default viewport center. * @observable * @api */ ol.interaction.TouchCursorDraw.prototype.setActive = function(b, position) { ol.interaction.TouchCursor.prototype.setActive.call (this, b, position); this.sketch.abortDrawing(); this.sketch.setPosition(position); this.sketch.setVisible(b); }; /** * Set Geometry type * @param {string} type Geometry type */ ol.interaction.TouchCursorDraw.prototype.setType = function(type) { this.removeButton(); var sketch = this.sketch; this.getOverlayElement().classList.remove(sketch.getGeometryType()); // Set type var oldValue = sketch.setGeometryType(); type = sketch.setGeometryType(type); this.getOverlayElement().classList.add(type); this.dispatchEvent({ type: 'change:type', oldValue: oldValue }); // Next type var types = this.get('types'); if (types && types.length) { var next = types[(types.indexOf(type) + 1) % types.length]; this.addButton({ className: 'ol-button-type '+next, click: function() { this.setType(next) }.bind(this) }); } // Add buttons if (type !== 'Point') { // Cancel drawing this.addButton({ className: 'ol-button-x', click: function() { sketch.abortDrawing(); } }); if (type !== 'Circle') { // Add a new point (nothing to do, just click) this.addButton({ className: 'ol-button-check', click: function() { sketch.finishDrawing(true); } }); // Remove last point this.addButton({ className: 'ol-button-remove', click: function() { sketch.removeLastPoint(); } }); } } }; /** Get geometry type */ ol.interaction.TouchCursorDraw.prototype.getType = function() { return this.sketch.getGeometryType(); }; /* 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). */ /** TouchCursor interaction + ModifyFeature * @constructor * @extends {ol.interaction.TouchCursor} * @param {olx.interaction.InteractionOptions} options Options * @param {string} options.className cursor class name * @param {ol.coordinate} options.coordinate cursor position * @param {ol.source.Vector} options.source a source to modify (configured with useSpatialIndex set to true) * @param {ol.source.Vector|Array} options.sources a list of source to modify (configured with useSpatialIndex set to true) * @param {ol.Collection.} options.features collection of feature to modify * @param {function|undefined} options.filter a filter that takes a feature and return true if it can be modified, default always true. * @param {number} pixelTolerance Pixel tolerance for considering the pointer close enough to a segment or vertex for editing, default 10 * @param {ol.style.Style | Array | undefined} options.style Style for the sketch features. * @param {boolean} options.wrapX Wrap the world horizontally on the sketch overlay, default false */ ol.interaction.TouchCursorModify = function(options) { options = options || {}; var drag = false; // enable drag var dragging = false; // dragging a point var del = false; // deleting a point // Modify interaction var mod = this._modify = new ol.interaction.ModifyFeature ({ source: options.source, sources: options.sources, features: options.features, pixelTolerance: options.pixelTolerance, filter: options.filter, style: options.style || [ new ol.style.Style({ image: new ol.style.RegularShape ({ points: 4, radius: 10, radius2: 0, stroke: new ol.style.Stroke({ color: [255,255,255, .5], width: 3 }) }) }), new ol.style.Style({ image: new ol.style.RegularShape ({ points: 4, radius: 10, radius2: 0, stroke: new ol.style.Stroke({ color: [0, 153, 255, 1], width: 1.25 }) }) }) ], wrapX: options.wrapX, condition: function(e) { return e.dragging || dragging; }, deleteCondition: function() { return del; } }); ol.interaction.TouchCursor.call(this, { className: ('disable '+options.className).trim(), coordinate: options.coordinate, buttons: [{ // Dragging button className: 'ol-button-move', on: { pointerdown: function() { drag = true; }, pointerup: function() { drag = false; } } }, { // Add a new point to a line className: 'ol-button-add', click: function() { dragging = true; mod.handleDownEvent(this._lastEvent); mod.handleUpEvent(this._lastEvent); dragging = false; }.bind(this) }, { // Remove a point className: 'ol-button-remove', click: function() { del = true; mod.handleDownEvent(this._lastEvent); del = false; }.bind(this) } ] }); // Show when modification is active mod.on('select', function(e) { if (e.selected.length) { this.getOverlayElement().classList.remove('disable') } else { this.getOverlayElement().classList.add('disable') } }.bind(this)); // Handle dragging, prevent drag outside the control this.on('dragstart', function() { if (drag) { mod.handleDownEvent(this._lastEvent); } }.bind(this)); this.on('dragging', function(e) { if (drag) mod.handleDragEvent(e); }); this.on('dragend', function(e) { mod.handleUpEvent(e); drag = false; }); }; ol.ext.inherits(ol.interaction.TouchCursorModify, ol.interaction.TouchCursor); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {_ol_Map_} map Map. * @api stable */ ol.interaction.TouchCursorModify.prototype.setMap = function(map) { if (this.getMap()) { this.getMap().removeInteraction(this._modify); } if (map) { map.addInteraction(this._modify); } ol.interaction.TouchCursor.prototype.setMap.call (this, map); }; /** * Activate or deactivate the interaction. * @param {boolean} active Active. * @param {ol.coordinate|null} position position of the cursor (when activating), default viewport center. * @observable * @api */ ol.interaction.TouchCursorModify.prototype.setActive = function(b, position) { ol.interaction.TouchCursor.prototype.setActive.call (this, b, position); this._modify.setActive(b); }; /** * Get the modify interaction. * @retunr {ol.interaction.ModifyFeature} * @observable * @api */ ol.interaction.TouchCursorModify.prototype.getInteraction = function() { return this._modify; }; /* 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 TouchCursor to select objects on hovering the cursor * @constructor * @extends {ol.interaction.DragOverlay} * @param {olx.interaction.InteractionOptions} options Options * @param {string} options.className cursor class name * @param {ol.coordinate} options.coordinate position of the cursor */ ol.interaction.TouchCursorSelect = function(options) { options = options || {}; ol.interaction.TouchCursor.call(this, { className: 'ol-select '+(options.className || ''), coordinate: options.coordinate }); this._selection = null; this._layerFilter = options.layerFilter; this._filter = options.filter; this._style = options.style || ol.style.Style.defaultStyle(true); this.set('hitTolerance', options.hitTolerance || 0); this.on(['change:active', 'dragging'], function() { this.select() }); }; ol.ext.inherits(ol.interaction.TouchCursorSelect, ol.interaction.TouchCursor); /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {_ol_Map_} map Map. * @api stable */ ol.interaction.TouchCursorSelect.prototype.setMap = function(map) { ol.interaction.TouchCursor.prototype.setMap.call (this, map); if (map) { // Select on move end this._listeners.movend = map.on('moveend', function() { this.select() }.bind(this)) } }; /** Get current selection * @return {ol.Feature|null} */ ol.interaction.TouchCursorSelect.prototype.getSelection = function() { return this._selection ? this._selection.feature : null; }; /** Set position * @param {ol.coordinate} coord */ ol.interaction.TouchCursorSelect.prototype.setPosition = function(coord) { ol.interaction.TouchCursor.prototype.setPosition.call (this, coord); this.select(); }; /** Select feature * @param {ol.Feature|undefined} f a feature to select or select at the cursor position */ ol.interaction.TouchCursorSelect.prototype.select = function(f) { var current = this._selection; if (this.getActive() && this.getPosition()) { if (!f) { var sel = this.getMap().getFeaturesAtPixel(this.getPixel(), { layerFilter: this._layerFilter, filter: this._filter, hitTolerance: this.get('hitTolerance') }); f = sel ? sel[0] : null; } if (f) { if (current && f === current.feature) { current = null; } else { this._selection = { feature: f, style: f.getStyle() } f.setStyle(this._style); this.dispatchEvent({ type:'select', selected: [f], deselected: current ? [current.feature] : [] }); } } else { this._selection = null; this.dispatchEvent({ type:'select', selected: [], deselected: current ? [current.feature] : [] }); } } else { this._selection = null; this.dispatchEvent({ type:'select', selected: [], deselected: current ? [current.feature] : [] }); } // Restore current style if (current) { current.feature.setStyle(current.style); } // if (this._selection) this.getOverlayElement().classList.remove('disable'); else this.getOverlayElement().classList.add('disable'); }; /** Interaction rotate * @constructor * @extends {ol.interaction.Pointer} * @fires select | rotatestart | rotating | rotateend | translatestart | translating | translateend | scalestart | scaling | scaleend * @param {any} options * @param {function} options.filter A function that takes a Feature and a Layer and returns true if the feature may be transformed or false otherwise. * @param {Array} options.layers array of layers to transform, * @param {ol.Collection} options.features collection of feature to transform, * @param {ol.EventsConditionType|undefined} options.condition A function that takes an ol.MapBrowserEvent and a feature collection and returns a boolean to indicate whether that event should be handled. default: ol.events.condition.always. * @param {ol.EventsConditionType|undefined} options.addCondition A function that takes an ol.MapBrowserEvent and returns a boolean to indicate whether that event should be handled ie. the feature will be added to the transforms features. default: ol.events.condition.never. * @param {number | undefined} options.hitTolerance Tolerance to select feature in pixel, default 0 * @param {bool} options.translateFeature Translate when click on feature * @param {bool} options.translate Can translate the feature * @param {bool} options.translateBBox Enable translate when the user drags inside the bounding box * @param {bool} options.stretch can stretch the feature * @param {bool} options.scale can scale the feature * @param {bool} options.rotate can rotate the feature * @param {bool} options.noFlip prevent the feature geometry to flip, default false * @param {bool} options.selection the intraction handle selection/deselection, if not use the select prototype to add features to transform, default true * @param {ol.events.ConditionType | undefined} options.keepAspectRatio A function that takes an ol.MapBrowserEvent and returns a boolean to keep aspect ratio, default ol.events.condition.shiftKeyOnly. * @param {ol.events.ConditionType | undefined} options.modifyCenter A function that takes an ol.MapBrowserEvent and returns a boolean to apply scale & strech from the center, default ol.events.condition.metaKey or ol.events.condition.ctrlKey. * @param {boolean} options.enableRotatedTransform Enable transform when map is rotated * @param {boolean} [options.keepRectangle=false] keep rectangle when possible * @param {} options.style list of ol.style for handles * */ ol.interaction.Transform = function(options) { if (!options) options = {}; var self = this; this.selection_ = new ol.Collection(); // Create a new overlay layer for the sketch this.handles_ = new ol.Collection(); this.overlayLayer_ = new ol.layer.Vector({ source: new ol.source.Vector({ features: this.handles_, useSpatialIndex: false, wrapX: false // For vector editing across the -180° and 180° meridians to work properly, this should be set to false }), name:'Transform overlay', displayInLayerSwitcher: false, // Return the style according to the handle type style: function (feature) { return (self.style[(feature.get('handle')||'default')+(feature.get('constraint')||'')+(feature.get('option')||'')]); }, }); // Extend pointer ol.interaction.Pointer.call(this, { handleDownEvent: this.handleDownEvent_, handleDragEvent: this.handleDragEvent_, handleMoveEvent: this.handleMoveEvent_, handleUpEvent: this.handleUpEvent_ }); // Collection of feature to transform this.features_ = options.features; // Filter or list of layers to transform if (typeof(options.filter)==='function') this._filter = options.filter; this.layers_ = options.layers ? (options.layers instanceof Array) ? options.layers:[options.layers] : null; this._handleEvent = options.condition || function() { return true; }; this.addFn_ = options.addCondition || function() { return false; }; /* Translate when click on feature */ this.set('translateFeature', (options.translateFeature!==false)); /* Can translate the feature */ this.set('translate', (options.translate!==false)); /* Translate when click on the bounding box */ this.set('translateBBox', (options.translateBBox===true)); /* Can stretch the feature */ this.set('stretch', (options.stretch!==false)); /* Can scale the feature */ this.set('scale', (options.scale!==false)); /* Can rotate the feature */ this.set('rotate', (options.rotate!==false)); /* Keep aspect ratio */ this.set('keepAspectRatio', (options.keepAspectRatio || function(e){ return e.originalEvent.shiftKey })); /* Modify center */ this.set('modifyCenter', (options.modifyCenter || function(e){ return e.originalEvent.metaKey || e.originalEvent.ctrlKey })); /* Prevent flip */ this.set('noFlip', (options.noFlip || false)); /* Handle selection */ this.set('selection', (options.selection !== false)); /* */ this.set('hitTolerance', (options.hitTolerance || 0)); /* Enable view rotated transforms */ this.set('enableRotatedTransform', (options.enableRotatedTransform || false)); /* Keep rectangle angles 90 degrees */ this.set('keepRectangle', (options.keepRectangle || false)); // Force redraw when changed this.on ('propertychange', function() { this.drawSketch_(); }); // setstyle this.setDefaultStyle(); }; ol.ext.inherits(ol.interaction.Transform, ol.interaction.Pointer); /** Cursors for transform */ ol.interaction.Transform.prototype.Cursors = { 'default': 'auto', 'select': 'pointer', 'translate': 'move', 'rotate': 'move', 'rotate0': 'move', 'scale': 'nesw-resize', 'scale1': 'nwse-resize', 'scale2': 'nesw-resize', 'scale3': 'nwse-resize', 'scalev': 'ew-resize', 'scaleh1': 'ns-resize', 'scalev2': 'ew-resize', 'scaleh3': 'ns-resize' }; /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.Transform.prototype.setMap = function(map) { var oldMap = this.getMap(); if (oldMap) { var targetElement = oldMap.getTargetElement(); oldMap.removeLayer(this.overlayLayer_); if (this.previousCursor_ && targetElement) { targetElement.style.cursor = this.previousCursor_; } this.previousCursor_ = undefined; } ol.interaction.Pointer.prototype.setMap.call (this, map); this.overlayLayer_.setMap(map); if (map === null) { this.select(null); } if (map !== null) { this.isTouch = /touch/.test(map.getViewport().className); this.setDefaultStyle(); } }; /** * Activate/deactivate interaction * @param {bool} * @api stable */ ol.interaction.Transform.prototype.setActive = function(b) { this.select(null); this.overlayLayer_.setVisible(b); ol.interaction.Pointer.prototype.setActive.call (this, b); }; /** Set default sketch style * @param {Object|undefined} options * @param {ol.style.Stroke} stroke stroke style for selection rectangle * @param {ol.style.Fill} fill fill style for selection rectangle * @param {ol.style.Stroke} pointStroke stroke style for handles * @param {ol.style.Fill} pointFill fill style for handles */ ol.interaction.Transform.prototype.setDefaultStyle = function(options) { options = options || {} // Style var stroke = options.pointStroke || new ol.style.Stroke({ color: [255,0,0,1], width: 1 }); var strokedash = options.stroke || new ol.style.Stroke({ color: [255,0,0,1], width: 1, lineDash:[4,4] }); var fill0 = options.fill || new ol.style.Fill({ color:[255,0,0,0.01] }); var fill = options.pointFill || new ol.style.Fill({ color:[255,255,255,0.8] }); var circle = new ol.style.RegularShape({ fill: fill, stroke: stroke, radius: this.isTouch ? 12 : 6, points: 15 }); circle.getAnchor()[0] = this.isTouch ? -10 : -5; var bigpt = new ol.style.RegularShape({ fill: fill, stroke: stroke, radius: this.isTouch ? 16 : 8, points: 4, angle: Math.PI/4 }); var smallpt = new ol.style.RegularShape({ fill: fill, stroke: stroke, radius: this.isTouch ? 12 : 6, points: 4, angle: Math.PI/4 }); function createStyle (img, stroke, fill) { return [ new ol.style.Style({image:img, stroke:stroke, fill:fill}) ]; } /** Style for handles */ this.style = { 'default': createStyle (bigpt, strokedash, fill0), 'translate': createStyle (bigpt, stroke, fill), 'rotate': createStyle (circle, stroke, fill), 'rotate0': createStyle (bigpt, stroke, fill), 'scale': createStyle (bigpt, stroke, fill), 'scale1': createStyle (bigpt, stroke, fill), 'scale2': createStyle (bigpt, stroke, fill), 'scale3': createStyle (bigpt, stroke, fill), 'scalev': createStyle (smallpt, stroke, fill), 'scaleh1': createStyle (smallpt, stroke, fill), 'scalev2': createStyle (smallpt, stroke, fill), 'scaleh3': createStyle (smallpt, stroke, fill), }; this.drawSketch_(); } /** * Set sketch style. * @param {style} style Style name: 'default','translate','rotate','rotate0','scale','scale1','scale2','scale3','scalev','scaleh1','scalev2','scaleh3' * @param {ol.style.Style|Array} olstyle * @api stable */ ol.interaction.Transform.prototype.setStyle = function(style, olstyle) { if (!olstyle) return; if (olstyle instanceof Array) this.style[style] = olstyle; else this.style[style] = [ olstyle ]; for (var i=0; i} features the features to transform */ ol.interaction.Transform.prototype.setSelection = function(features) { this.selection_.clear(); features.forEach(function(feature) { this.selection_.push(feature); }.bind(this)); this.ispt_ = (this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Point") : false); this.iscircle_ = (this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Circle") : false); this.drawSketch_(); this.watchFeatures_(); // select event this.dispatchEvent({ type:'select', features: this.selection_ }); }; /** Watch selected features * @private */ ol.interaction.Transform.prototype.watchFeatures_ = function() { // Listen to feature modification if (this._featureListeners) { this._featureListeners.forEach(function (l) { ol.Observable.unByKey(l) }); } this._featureListeners = []; this.selection_.forEach(function(f) { this._featureListeners.push( f.on('change', function() { if (!this.isUpdating_) { this.drawSketch_(); } }.bind(this)) ); }.bind(this)); }; /** * @param {ol.MapBrowserEvent} evt Map browser event. * @return {boolean} `true` to start the drag sequence. * @private */ ol.interaction.Transform.prototype.handleDownEvent_ = function(evt) { if (!this._handleEvent(evt, this.selection_)) return; var sel = this.getFeatureAtPixel_(evt.pixel); var feature = sel.feature; if (this.selection_.getLength() && this.selection_.getArray().indexOf(feature) >= 0 && ((this.ispt_ && this.get('translate')) || this.get('translateFeature')) ){ sel.handle = 'translate'; } if (sel.handle) { this.mode_ = sel.handle; this.opt_ = sel.option; this.constraint_ = sel.constraint; // Save info var viewRotation = this.getMap().getView().getRotation(); this.coordinate_ = evt.coordinate; this.pixel_ = evt.pixel; this.geoms_ = []; this.rotatedGeoms_ = []; var extent = ol.extent.createEmpty(); var rotExtent = ol.extent.createEmpty(); for (var i=0, f; f=this.selection_.item(i); i++) { this.geoms_.push(f.getGeometry().clone()); extent = ol.extent.extend(extent, f.getGeometry().getExtent()); if (this.get('enableRotatedTransform') && viewRotation !== 0) { var rotGeom = this.getGeometryRotateToZero_(f, true); this.rotatedGeoms_.push(rotGeom); rotExtent = ol.extent.extend(rotExtent, rotGeom.getExtent()); } } this.extent_ = (ol.geom.Polygon.fromExtent(extent)).getCoordinates()[0]; if (this.get('enableRotatedTransform') && viewRotation !== 0) { this.rotatedExtent_ = (ol.geom.Polygon.fromExtent(rotExtent)).getCoordinates()[0]; } if (this.mode_==='rotate') { this.center_ = this.getCenter() || ol.extent.getCenter(extent); // we are now rotating (cursor down on rotate mode), so apply the grabbing cursor var element = evt.map.getTargetElement(); element.style.cursor = this.Cursors.rotate0; this.previousCursor_ = element.style.cursor; } else { this.center_ = ol.extent.getCenter(extent); } this.angle_ = Math.atan2(this.center_[1]-evt.coordinate[1], this.center_[0]-evt.coordinate[0]); this.dispatchEvent({ type: this.mode_+'start', feature: this.selection_.item(0), // backward compatibility features: this.selection_, pixel: evt.pixel, coordinate: evt.coordinate }); return true; } else if (this.get('selection')) { if (feature){ if (!this.addFn_(evt)) this.selection_.clear(); var index = this.selection_.getArray().indexOf(feature); if (index < 0) this.selection_.push(feature); else this.selection_.removeAt(index); } else { this.selection_.clear(); } this.ispt_ = this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Point") : false; this.iscircle_ = (this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Circle") : false); this.drawSketch_(); this.watchFeatures_(); this.dispatchEvent({ type:'select', feature: feature, features: this.selection_, pixel: evt.pixel, coordinate: evt.coordinate }); return false; } }; /** * Get features to transform * @return {ol.Collection} */ ol.interaction.Transform.prototype.getFeatures = function() { return this.selection_; }; /** * Get the rotation center * @return {ol.coordinates|undefined} */ ol.interaction.Transform.prototype.getCenter = function() { return this.get('center'); }; /** * Set the rotation center * @param {ol.coordinates|undefined} c the center point, default center on the objet */ ol.interaction.Transform.prototype.setCenter = function(c) { return this.set('center', c); } function projectVectorOnVector(displacement_vector, base) { var k = (displacement_vector[0] * base[0] + displacement_vector[1] * base[1]) / (base[0] * base[0] + base[1] * base[1]); return [base[0] * k, base[1] * k]; } function countVector(start, end) { return [end[0] - start[0], end[1] - start[1]]; } function movePoint(point, displacementVector) { return [point[0]+displacementVector[0], point[1]+displacementVector[1]]; } /** * @param {ol.MapBrowserEvent} evt Map browser event. * @private */ ol.interaction.Transform.prototype.handleDragEvent_ = function(evt) { if (!this._handleEvent(evt, this.features_)) return; var viewRotation = this.getMap().getView().getRotation(); var i, j, f, geometry; var pt0 = [this.coordinate_[0], this.coordinate_[1]]; var pt = [evt.coordinate[0], evt.coordinate[1]]; this.isUpdating_ = true; switch (this.mode_) { case 'rotate': { var a = Math.atan2(this.center_[1]-pt[1], this.center_[0]-pt[0]); if (!this.ispt) { // var geometry = this.geom_.clone(); // geometry.rotate(a-this.angle_, this.center_); // this.feature_.setGeometry(geometry); for (i=0, f; f=this.selection_.item(i); i++) { geometry = this.geoms_[i].clone(); geometry.rotate(a - this.angle_, this.center_); // bug: ol, bad calculation circle geom extent if (geometry.getType() == 'Circle') geometry.setCenterAndRadius(geometry.getCenter(), geometry.getRadius()); f.setGeometry(geometry); } } this.drawSketch_(true); this.dispatchEvent({ type:'rotating', feature: this.selection_.item(0), features: this.selection_, angle: a-this.angle_, pixel: evt.pixel, coordinate: evt.coordinate }); break; } case 'translate': { var deltaX = pt[0] - pt0[0]; var deltaY = pt[1] - pt0[1]; //this.feature_.getGeometry().translate(deltaX, deltaY); for (i=0, f; f=this.selection_.item(i); i++) { f.getGeometry().translate(deltaX, deltaY); } this.handles_.forEach(function(f) { f.getGeometry().translate(deltaX, deltaY); }); this.coordinate_ = evt.coordinate; this.dispatchEvent({ type:'translating', feature: this.selection_.item(0), features: this.selection_, delta:[deltaX,deltaY], pixel: evt.pixel, coordinate: evt.coordinate }); break; } case 'scale': { var center = this.center_; if (this.get('modifyCenter')(evt)) { var extentCoordinates = this.extent_; if (this.get('enableRotatedTransform') && viewRotation !== 0) { extentCoordinates = this.rotatedExtent_; } center = extentCoordinates[(Number(this.opt_)+2)%4]; } var keepRectangle = (this.geoms_.length == 1 && this._isRectangle(this.geoms_[0])); var stretch = this.constraint_; var opt = this.opt_; var downCoordinate = this.coordinate_; var dragCoordinate = evt.coordinate; if (this.get('enableRotatedTransform') && viewRotation !== 0) { var downPoint = new ol.geom.Point(this.coordinate_); downPoint.rotate(viewRotation * -1, center); downCoordinate = downPoint.getCoordinates(); var dragPoint = new ol.geom.Point(evt.coordinate); dragPoint.rotate(viewRotation * -1, center); dragCoordinate = dragPoint.getCoordinates(); } var scx = ((dragCoordinate)[0] - (center)[0]) / (downCoordinate[0] - (center)[0]); var scy = ((dragCoordinate)[1] - (center)[1]) / (downCoordinate[1] - (center)[1]); var displacementVector = [dragCoordinate[0] - downCoordinate[0], (dragCoordinate)[1] - downCoordinate[1]]; if (this.get('enableRotatedTransform') && viewRotation !== 0) { var centerPoint = new ol.geom.Point(center); centerPoint.rotate(viewRotation * -1, this.getMap().getView().getCenter()); center = centerPoint.getCoordinates(); } if (this.get('noFlip')) { if (scx<0) scx=-scx; if (scy<0) scy=-scy; } if (this.constraint_) { if (this.constraint_=="h") scx=1; else scy=1; } else { if (this.get('keepAspectRatio')(evt)) { scx = scy = Math.min(scx,scy); } } for (i=0, f; f=this.selection_.item(i); i++) { geometry = (viewRotation === 0 || !this.get('enableRotatedTransform')) ? this.geoms_[i].clone() : this.rotatedGeoms_[i].clone(); geometry.applyTransform(function(g1, g2, dim) { if (dim<2) return g2; if (!keepRectangle) { for (j=0; j} options.layers array of layers to undo/redo */ ol.interaction.UndoRedo = function(options) { if (!options) options = {}; ol.interaction.Interaction.call(this, { handleEvent: function() { return true; } }); //array of layers to undo/redo this._layers = options.layers this._undoStack = new ol.Collection(); this._redoStack = new ol.Collection(); // Zero level stack this._undo = []; this._redo = []; this._undoStack.on('add', function(e) { if (e.element.level === undefined) { e.element.level = this._level; if (!e.element.level) { e.element.view = { center: this.getMap().getView().getCenter(), zoom: this.getMap().getView().getZoom() }; this._undo.push(e.element); } } else { if (!e.element.level) this._undo.push(this._redo.shift()); } if (!e.element.level) { this.dispatchEvent({ type: 'stack:add', action: e.element }); } this._reduce(); }.bind(this)); this._undoStack.on('remove', function(e) { if (!e.element.level) { if (this._doShift) { this._undo.shift(); } else { if (this._undo.length) this._redo.push(this._undo.pop()); } if (!this._doClear) { this.dispatchEvent({ type: 'stack:remove', action: e.element, shift: this._doShift }); } } }.bind(this)); // Block counter this._block = 0; this._level = 0; // Shift an undo action ? this._doShift = false; // Start recording this._record = true; // Custom definitions this._defs = {}; }; ol.ext.inherits(ol.interaction.UndoRedo, ol.interaction.Interaction); /** Add a custom undo/redo * @param {string} action the action key name * @param {function} undoFn function called when undoing * @param {function} redoFn function called when redoing * @api */ ol.interaction.UndoRedo.prototype.define = function(action, undoFn, redoFn) { this._defs[action] = { undo: undoFn, redo: redoFn }; }; /** Get first level undo / redo length * @param {string} [type] get redo stack length, default get undo * @return {number} */ ol.interaction.UndoRedo.prototype.length = function(type) { return (type==='redo') ? this._redo.length : this._undo.length; }; /** Set undo stack max length * @param {number} length */ ol.interaction.UndoRedo.prototype.setMaxLength = function(length) { length = parseInt(length); if (length && length<0) length = 0; this.set('maxLength', length); this._reduce(); }; /** Get undo / redo size (includes all block levels) * @param {string} [type] get redo stack length, default get undo * @return {number} */ ol.interaction.UndoRedo.prototype.size = function(type) { return (type==='redo') ? this._redoStack.getLength() : this._undoStack.getLength(); }; /** Set undo stack max size * @param {number} size */ ol.interaction.UndoRedo.prototype.setMaxSize = function(size) { size = parseInt(size); if (size && size<0) size = 0; this.set('maxSize', size); this._reduce(); }; /** Reduce stack: shift undo to set size * @private */ ol.interaction.UndoRedo.prototype._reduce = function() { if (this.get('maxLength')) { while (this.length() > this.get('maxLength')) { this.shift(); } } if (this.get('maxSize')) { while (this.length() > 1 && this.size() > this.get('maxSize')) { this.shift(); } } }; /** Get first level undo / redo first level stack * @param {string} [type] get redo stack, default get undo * @return {Array<*>} */ ol.interaction.UndoRedo.prototype.getStack = function(type) { return (type==='redo') ? this._redo : this._undo; }; /** Add a new custom undo/redo * @param {string} action the action key name * @param {any} prop an object that will be passed in the undo/redo functions of the action * @param {string} name action name * @return {boolean} true if the action is defined */ ol.interaction.UndoRedo.prototype.push = function(action, prop, name) { if (this._defs[action]) { this._undoStack.push({ type: action, name: name, custom: true, prop: prop }); return true; } else { console.warn('[UndoRedoInteraction]: "'+action+'" is not defined.'); return false; } }; /** Remove undo action from the beginning of the stack. * The action is not returned. */ ol.interaction.UndoRedo.prototype.shift = function() { this._doShift = true; var a = this._undoStack.removeAt(0); this._doShift = false; // Remove all block if (a.type==='blockstart') { a = this._undoStack.item(0); while (this._undoStack.getLength() && a.level>0) { this._undoStack.removeAt(0); a = this._undoStack.item(0); } } }; /** Activate or deactivate the interaction, ie. records or not events on the map. * @param {boolean} active * @api stable */ ol.interaction.UndoRedo.prototype.setActive = function(active) { ol.interaction.Interaction.prototype.setActive.call (this, active); this._record = active; }; /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. * @param {ol.Map} map Map. * @api stable */ ol.interaction.UndoRedo.prototype.setMap = function(map) { if (this._mapListener) { this._mapListener.forEach(function(l) { ol.Observable.unByKey(l); }) } this._mapListener = []; ol.interaction.Interaction.prototype.setMap.call (this, map); // Watch blocks if (map) { this._mapListener.push(map.on('undoblockstart', this.blockStart.bind(this))); this._mapListener.push(map.on('undoblockend', this.blockEnd.bind(this))); } // Watch sources this._watchSources(); this._watchInteractions(); }; /** Watch for changes in the map sources * @private */ ol.interaction.UndoRedo.prototype._watchSources = function() { var map = this.getMap(); // Clear listeners if (this._sourceListener) { this._sourceListener.forEach(function(l) { ol.Observable.unByKey(l); }) } this._sourceListener = []; var self = this; // Ges vector layers function getVectorLayers(layers, init) { if (!init) init = []; layers.forEach(function(l) { if (l instanceof ol.layer.Vector) { if (!self._layers || self._layers.indexOf(l) >= 0) { init.push(l); } } else if (l.getLayers) { getVectorLayers(l.getLayers(), init); } }); return init; } if (map) { // Watch the vector sources in the map var vectors = getVectorLayers(map.getLayers()); vectors.forEach((function(l) { var s = l.getSource(); this._sourceListener.push( s.on(['addfeature', 'removefeature'], this._onAddRemove.bind(this)) ); this._sourceListener.push( s.on('clearstart', function() { this.blockStart('clear') }.bind(this))); this._sourceListener.push( s.on('clearend', this.blockEnd.bind(this)) ); }).bind(this)); // Watch new inserted/removed this._sourceListener.push( map.getLayers().on(['add', 'remove'], this._watchSources.bind(this) ) ); } }; /** Watch for interactions * @private */ ol.interaction.UndoRedo.prototype._watchInteractions = function() { var map = this.getMap(); // Clear listeners if (this._interactionListener) { this._interactionListener.forEach(function(l) { ol.Observable.unByKey(l); }) } this._interactionListener = []; if (map) { // Watch the interactions in the map map.getInteractions().forEach((function(i) { this._interactionListener.push(i.on( ['setattributestart', 'modifystart', 'rotatestart', 'translatestart', 'scalestart', 'deletestart', 'deleteend', 'beforesplit', 'aftersplit'], this._onInteraction.bind(this) )); }).bind(this)); // Watch new inserted / unwatch removed this._interactionListener.push( map.getInteractions().on( ['add', 'remove'], this._watchInteractions.bind(this) )); } }; /** A feature is added / removed */ ol.interaction.UndoRedo.prototype._onAddRemove = function(e) { if (this._record) { this._redoStack.clear(); this._redo.length = 0; this._undoStack.push({ type: e.type, source: e.target, feature: e.feature }); } }; /** Perform an interaction * @private */ ol.interaction.UndoRedo.prototype._onInteraction = function(e) { var fn = this._onInteraction[e.type]; if (fn) fn.call(this,e); }; /** Set attribute * @private */ ol.interaction.UndoRedo.prototype._onInteraction.setattributestart = function(e) { this.blockStart(e.target.get('name') || 'setattribute'); var newp = Object.assign({}, e.properties); e.features.forEach(function(f) { var oldp = {}; for (var p in newp) { oldp[p] = f.get(p); } this._undoStack.push({ type: 'changeattribute', feature: f, newProperties: newp, oldProperties: oldp }); }.bind(this)); this.blockEnd(); }; ol.interaction.UndoRedo.prototype._onInteraction.rotatestart = ol.interaction.UndoRedo.prototype._onInteraction.translatestart = ol.interaction.UndoRedo.prototype._onInteraction.scalestart = ol.interaction.UndoRedo.prototype._onInteraction.modifystart = function (e) { this.blockStart(e.type.replace(/start$/,'')); e.features.forEach(function(m) { this._undoStack.push({ type: 'changegeometry', feature: m, oldGeom: m.getGeometry().clone() }); }.bind(this)); this.blockEnd(); }; /** Start an undo block * @param {string} [name] name f the action * @api */ ol.interaction.UndoRedo.prototype.blockStart = function (name) { this._redoStack.clear(); this._redo.length = 0; this._undoStack.push({ type: 'blockstart', name: name }); this._level++; }; /** @private */ ol.interaction.UndoRedo.prototype._onInteraction.beforesplit = function() { this.blockStart('split'); }; ol.interaction.UndoRedo.prototype._onInteraction.deletestart = function() { this.blockStart('delete'); } /** End an undo block * @api */ ol.interaction.UndoRedo.prototype.blockEnd = function () { this._undoStack.push({ type: 'blockend' }); this._level--; }; /** @private */ ol.interaction.UndoRedo.prototype._onInteraction.aftersplit = ol.interaction.UndoRedo.prototype._onInteraction.deleteend = ol.interaction.UndoRedo.prototype.blockEnd; /** handle undo/redo * @private */ ol.interaction.UndoRedo.prototype._handleDo = function(e, undo) { // Not active if (!this.getActive()) return; // Stop recording while undoing this._record = false; if (e.custom) { if (this._defs[e.type]) { if (undo) this._defs[e.type].undo(e.prop); else this._defs[e.type].redo(e.prop); } else { console.warn('[UndoRedoInteraction]: "'+e.type+'" is not defined.'); } } else { switch (e.type) { case 'addfeature': { if (undo) e.source.removeFeature(e.feature); else e.source.addFeature(e.feature); break; } case 'removefeature': { if (undo) e.source.addFeature(e.feature); else e.source.removeFeature(e.feature); break; } case 'changegeometry': { var geom = e.feature.getGeometry(); e.feature.setGeometry(e.oldGeom); e.oldGeom = geom; break; } case 'changeattribute': { var newp = e.newProperties; var oldp = e.oldProperties; for (var p in oldp) { if (oldp === undefined) e.feature.unset(p); else e.feature.set(p, oldp[p]); } e.oldProperties = newp; e.newProperties = oldp; break; } case 'blockstart': { this._block += undo ? -1 : 1; break; } case 'blockend': { this._block += undo ? 1 : -1; break; } default: { console.warn('[UndoRedoInteraction]: "'+e.type+'" is not defined.'); } } } // Handle block if (this._block<0) this._block = 0; if (this._block) { if (undo) this.undo(); else this.redo(); } this._record = true; // Dispatch event this.dispatchEvent( { type: undo ? 'undo' : 'redo', action: e }); }; /** Undo last operation * @api */ ol.interaction.UndoRedo.prototype.undo = function() { var e = this._undoStack.item(this._undoStack.getLength() - 1); if (!e) return; this._redoStack.push(e); this._undoStack.pop(); this._handleDo(e, true); }; /** Redo last operation * @api */ ol.interaction.UndoRedo.prototype.redo = function() { var e = this._redoStack.item(this._redoStack.getLength() - 1); if (!e) return; this._undoStack.push(e); this._redoStack.pop(); this._handleDo(e, false); }; /** Clear undo stack * @api */ ol.interaction.UndoRedo.prototype.clear = function() { this._doClear = true; this._undo.length = this._redo.length = 0; this._undoStack.clear(); this._redoStack.clear(); this._doClear = false; this.dispatchEvent({ type: 'stack:clear' }); }; /** Check if undo is avaliable * @return {number} the number of undo * @api */ ol.interaction.UndoRedo.prototype.hasUndo = function() { return this._undoStack.getLength(); }; /** Check if redo is avaliable * @return {number} the number of redo * @api */ ol.interaction.UndoRedo.prototype.hasRedo = function() { return this._redoStack.getLength(); }; /* 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). */ /** Abstract base class; normally only used for creating subclasses. Bin collector for data * @constructor * @extends {ol.source.Vector} * @param {Object} options ol.source.VectorOptions + grid option * @param {ol.source.Vector} options.source Source * @param {boolean} options.listenChange listen changes (move) on source features to recalculate the bin, default true * @param {fucntion} [options.geometryFunction] Function that takes an ol.Feature as argument and returns an ol.geom.Point as feature's center. * @param {function} [options.flatAttributes] Function takes a bin and the features it contains and aggragate the features in the bin attributes when saving */ ol.source.BinBase = function (options) { options = options || {}; this._bindModify = this._onModifyFeature.bind(this); this._watch = true; ol.source.Vector.call(this, options); this._origin = options.source; this._listen = (options.listenChange !== false); // Geometry function this._geomFn = options.geometryFunction || ol.coordinate.getFeatureCenter || function (f) { return f.getGeometry().getFirstCoordinate(); }; // Existing features this.reset(); // Future features this._origin.on('addfeature', this._onAddFeature.bind(this)); this._origin.on('removefeature', this._onRemoveFeature.bind(this)); this._origin.on('clearstart', this._onClearFeature.bind(this)); this._origin.on('clearend', this._onClearFeature.bind(this)); if (typeof (options.flatAttributes) === 'function') this._flatAttributes = options.flatAttributes; }; ol.ext.inherits(ol.source.BinBase, ol.source.Vector); /** * On add feature * @param {ol.events.Event} e * @param {ol.Feature} bin * @private */ ol.source.BinBase.prototype._onAddFeature = function (e, bin, listen) { var f = e.feature || e.target; bin = bin || this.getBinAt(this._geomFn(f), true); if (bin) bin.get('features').push(f); if (this._listen && listen!==false) f.on('change', this._bindModify); }; /** * On remove feature * @param {ol.events.Event} e * @param {ol.Feature} bin * @private */ ol.source.BinBase.prototype._onRemoveFeature = function (e, bin, listen) { if (!this._watch) return; var f = e.feature || e.target; bin = bin || this.getBinAt(this._geomFn(f)); if (bin) { // Remove feature from bin var features = bin.get('features'); for (var i=0, fi; fi=features[i]; i++) { if (fi===f) { features.splice(i, 1); break; } } // Remove bin if no features if (!features.length) { this.removeFeature(bin); } } else { // console.log("[ERROR:Bin] remove feature: feature doesn't exists anymore."); } if (this._listen && listen!==false) f.un('change', this._bindModify); }; /** When clearing features remove the listener * @private */ ol.source.BinBase.prototype._onClearFeature = function (e) { if (e.type==='clearstart') { if (this._listen) { this._origin.getFeatures().forEach(function (f) { f.un('change', this._bindModify); }.bind(this)); } this.clear(); this._watch = false; } else { this._watch = true; } }; /** * Get the bin that contains a feature * @param {ol.Feature} f the feature * @return {ol.Feature} the bin or null it doesn't exit */ ol.source.BinBase.prototype.getBin = function (feature) { var bins = this.getFeatures(); for (var i=0, b; b = bins[i]; i++) { var features = b.get('features'); for (var j=0, f; f=features[j]; j++) { if (f===feature) return b; } } return null; } /** Get the grid geometry at the coord * @param {ol.Coordinate} coord * @param {Object} attributes add key/value to this object to add properties to the grid feature * @returns {ol.geom.Polygon} * @api */ ol.source.BinBase.prototype.getGridGeomAt = function (coord /*, attributes */) { return new ol.geom.Polygon([coord]); }; /** Get the bean at a coord * @param {ol.Coordinate} coord * @param {boolean} create true to create if doesn't exit * @return {ol.Feature} the bin or null it doesn't exit */ ol.source.BinBase.prototype.getBinAt = function (coord, create) { var attributes = {}; var g = this.getGridGeomAt(coord, attributes); if (!g) return null; var center = g.getInteriorPoint ? g.getInteriorPoint().getCoordinates() : g.getInteriorPoints().getCoordinates()[0];// ol.extent.getCenter(g.getExtent()); var features = this.getFeaturesAtCoordinate( center ); var bin = features[0]; if (!bin && create) { attributes.geometry = g; attributes.features = []; attributes.center = center; bin = new ol.Feature(attributes); this.addFeature(bin); } return bin || null; }; /** * A feature has been modified * @param {ol.events.Event} e * @private */ ol.source.BinBase.prototype._onModifyFeature = function (e) { var bin = this.getBin(e.target); var bin2 = this.getBinAt(this._geomFn(e.target), 'create'); if (bin !== bin2) { // remove from the bin if (bin) { this._onRemoveFeature(e, bin, false); } // insert in the new bin if (bin2) { this._onAddFeature(e, bin2, false); } } this.changed(); }; /** Clear all bins and generate a new one. */ ol.source.BinBase.prototype.reset = function () { this.clear(); var features = this._origin.getFeatures(); for (var i = 0, f; f = features[i]; i++) { this._onAddFeature({ feature: f }); } this.changed(); }; /** * Get features without circular dependencies (vs. getFeatures) * @return {Array} */ ol.source.BinBase.prototype.getGridFeatures = function () { var features = []; this.getFeatures().forEach(function (f) { var bin = new ol.Feature(f.getGeometry().clone()); for (var i in f.getProperties()) { if (i!=='features' && i!=='geometry') { bin.set(i, f.get(i)); } } bin.set('nb', f.get('features').length); this._flatAttributes(bin, f.get('features')); features.push(bin); }.bind(this)); return features; }; /** Create bin attributes using the features it contains when exporting * @param {ol.Feature} bin the bin to export * @param {Array} features the features it contains */ ol.source.BinBase.prototype._flatAttributes = function(/*bin, features*/) { }; /** Set the flatAttribute function * @param {function} fn Function that takes a bin and the features it contains and aggragate the features in the bin attributes when saving */ ol.source.BinBase.prototype.setFlatAttributesFn = function(fn) { if (typeof(fn)==='function') this._flatAttributes = fn; }; /** * Get the orginal source * @return {ol.source.Vector} */ ol.source.BinBase.prototype.getSource = function () { return this._origin; }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). @classdesc ol.source.DBPedia is a DBPedia layer source that load DBPedia located content in a vector layer. olx.source.DBPedia: olx.source.Vector { url: {string} Url for DBPedia SPARQL } Inherits from: */ /** * @constructor ol.source.DBPedia * @extends {ol.source.Vector} * @param {olx.source.DBPedia=} opt_options */ ol.source.DBPedia = function(opt_options) { var options = opt_options || {}; options.loader = this._loaderFn; /** Url for DBPedia SPARQL */ this._url = options.url || "http://fr.dbpedia.org/sparql"; /** Max resolution to load features */ this._maxResolution = options.maxResolution || 100; /** Result language */ this._lang = options.lang || "fr"; /** Query limit */ this._limit = options.limit || 1000; /** Default attribution */ if (!options.attributions) options.attributions = [ "© DBpedia CC-by-SA" ]; // Bbox strategy : reload at each move if (!options.strategy) options.strategy = ol.loadingstrategy.bbox; ol.source.Vector.call (this, options); }; ol.ext.inherits(ol.source.DBPedia, ol.source.Vector); /** Decode RDF attributes and choose to add feature to the layer * @param {feature} the feature * @param {attributes} RDF attributes * @param {lastfeature} last feature added (null if none) * @return {boolean} true: add the feature to the layer * @API stable */ ol.source.DBPedia.prototype.readFeature = function (feature, attributes, lastfeature) { // Copy RDF attributes values for (var i in attributes) { if (attributes[i].type==='uri') attributes[i].value = encodeURI(attributes[i].value); feature.set (i, attributes[i].value); } // Prevent same feature with different type duplication if (lastfeature && lastfeature.get("subject") == attributes.subject.value) { // Kepp dbpedia.org type ? // if (bindings[i].type.match ("dbpedia.org") lastfeature.get("type") = bindings[i].type.value; // Concat types lastfeature.set("type", lastfeature.get("type") +"\n"+ attributes.type.value); return false; } else { return true; } }; /** Set RDF query subject, default: select label, thumbnail, abstract and type * @API stable */ ol.source.DBPedia.prototype.querySubject = function () { return "?subject rdfs:label ?label. " + "OPTIONAL {?subject dbpedia-owl:thumbnail ?thumbnail}." + "OPTIONAL {?subject dbpedia-owl:abstract ?abstract} . " + "OPTIONAL {?subject rdf:type ?type}"; } /** Set RDF query filter, default: select language * @API stable */ ol.source.DBPedia.prototype.queryFilter = function () { return "lang(?label) = '"+this._lang+"' " + "&& lang(?abstract) = '"+this._lang+"'" // Filter on type //+ "&& regex (?type, 'Monument|Sculpture|Museum', 'i')" } /** Loader function used to load features. * @private */ ol.source.DBPedia.prototype._loaderFn = function(extent, resolution, projection) { if (resolution > this._maxResolution) return; var self = this; var bbox = ol.proj.transformExtent(extent, projection, "EPSG:4326"); // SPARQL request: for more info @see http://fr.dbpedia.org/ var query = "PREFIX geo: " + "SELECT DISTINCT * WHERE { " + "?subject geo:lat ?lat . " + "?subject geo:long ?long . " + this.querySubject()+" . " + "FILTER("+this.queryFilter()+") . " // Filter bbox + "FILTER(xsd:float(?lat) <= " + bbox[3] + " && " + bbox[1] + " <= xsd:float(?lat) " + "&& xsd:float(?long) <= " + bbox[2] + " && " + bbox[0] + " <= xsd:float(?long) " + ") . " + "} LIMIT "+this._limit; // Ajax request to get the tile ol.ext.Ajax.get({ url: this._url, data: { query: query, format:"json" }, success: function(data) { var bindings = data.results.bindings; var features = []; var att, pt, feature, lastfeature = null; for ( var i in bindings ) { att = bindings[i]; pt = [Number(bindings[i].long.value), Number(bindings[i].lat.value)]; feature = new ol.Feature(new ol.geom.Point(ol.proj.transform (pt,"EPSG:4326",projection))); if (self.readFeature(feature, att, lastfeature)) { features.push(feature); lastfeature = feature; } } self.addFeatures(features); }}); }; ol.style.clearDBPediaStyleCache; ol.style.dbPediaStyleFunction; (function(){ // Style cache var styleCache = {}; /** Reset the cache (when fonts are loaded) */ ol.style.clearDBPediaStyleCache = function() { styleCache = {}; } /** Get a default style function for dbpedia * @param {} options * @param {string|function|undefined} options.glyph a glyph name or a function that takes a feature and return a glyph * @param {number} options.radius radius of the symbol, default 8 * @param {ol.style.Fill} options.fill style for fill, default navy * @param {ol.style.stroke} options.stroke style for stroke, default 2px white * @param {string} options.prefix a prefix if many style used for the same type * * @require ol.style.FontSymbol and FontAwesome defs are required for dbPediaStyleFunction() */ ol.style.dbPediaStyleFunction = function(options) { if (!options) options={}; // Get font function using dbPedia type var getFont; switch (typeof(options.glyph)) { case "function": getFont = options.glyph; break; case "string": getFont = function(){ return options.glyph; }; break; default: { getFont = function (f) { var type = f.get("type"); if (type) { if (type.match("/Museum")) return "fa-camera"; else if (type.match("/Monument")) return "fa-building"; else if (type.match("/Sculpture")) return "fa-android"; else if (type.match("/Religious")) return "fa-institution"; else if (type.match("/Castle")) return "fa-key"; else if (type.match("Water")) return "fa-tint"; else if (type.match("Island")) return "fa-leaf"; else if (type.match("/Event")) return "fa-heart"; else if (type.match("/Artwork")) return "fa-asterisk"; else if (type.match("/Stadium")) return "fa-futbol-o"; else if (type.match("/Place")) return "fa-street-view"; } return "fa-star"; } break; } } // Default values var radius = options.radius || 8; var fill = options.fill || new ol.style.Fill({ color:"navy"}); var stroke = options.stroke || new ol.style.Stroke({ color: "#fff", width: 2 }); var prefix = options.prefix ? options.prefix+"_" : ""; // Vector style function return function (feature) { var glyph = getFont(feature); var k = prefix + glyph; var style = styleCache[k]; if (!style) { styleCache[k] = style = new ol.style.Style ({ image: new ol.style.FontSymbol({ glyph: glyph, radius: radius, fill: fill, stroke: stroke }) }); } return [style]; } }; })(); /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** DFCI source: a source to display the French DFCI grid on a map * @see http://ccffpeynier.free.fr/Files/dfci.pdf * @constructor ol.source.DFCI * @extends {ol/source/Vector} * @param {any} options Vector source options * @param {Array} resolutions a list of resolution to change the drawing level, default [1000,100,20] */ ol.source.DFCI = function(options) { options = options || {}; options.loader = this._calcGrid; options.strategy = function(extent, resolution) { if (this.resolution && this.resolution != resolution){ this.clear(); this.refresh(); } return [extent]; } this._bbox = [[0,1600000],[11*100000, 1600000+10*100000]]; ol.source.Vector.call (this, options); this.set('resolutions', options.resolutions || [1000,100,20]); // Add Lambert IIe proj if (!proj4.defs["EPSG:27572"]) proj4.defs("EPSG:27572","+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs"); ol.proj.proj4.register(proj4); }; ol.ext.inherits(ol.source.DFCI, ol.source.Vector); /** Cacluate grid according extent/resolution */ ol.source.DFCI.prototype._calcGrid = function (extent, resolution, projection) { // Show step 0 var f, ext, res = this.get('resolutions'); if (resolution > (res[0] || 1000)) { if (this.resolution != resolution) { if (!this._features0) { ext = [this._bbox[0][0], this._bbox[0][1],this._bbox[1][0], this._bbox[1][1]]; this._features0 = this._getFeatures(0, ext, projection); } this.addFeatures(this._features0); } } else if (resolution > (res[1] || 100)) { this.clear(); ext = ol.proj.transformExtent(extent, projection, 'EPSG:27572'); f = this._getFeatures(1, ext, projection) this.addFeatures(f); } else if (resolution > (res[2] || 0)) { this.clear(); ext = ol.proj.transformExtent(extent, projection, 'EPSG:27572'); f = this._getFeatures(2, ext, projection) this.addFeatures(f); } else { this.clear(); ext = ol.proj.transformExtent(extent, projection, 'EPSG:27572'); f = this._getFeatures(3, ext, projection) this.addFeatures(f); } // reset load this.resolution = resolution; }; /** * Get middle point * @private */ ol.source.DFCI.prototype._midPt = function(p1,p2) { return [(p1[0]+p2[0])/2, (p1[1]+p2[1])/2]; }; /** * Get feature with geom * @private */ ol.source.DFCI.prototype._trFeature = function(geom, id, level, projection) { var g = new ol.geom.Polygon([geom]); var f = new ol.Feature(g.transform('EPSG:27572', projection)); f.set('id', id); f.set('level', level); return f; }; /** Get features * */ ol.source.DFCI.prototype._getFeatures = function (level, extent, projection) { var features = []; var i; var step = 100000; if (level>0) step /= 5; if (level>1) step /= 10; var p0 = [ Math.max(this._bbox[0][0], Math.floor(extent[0]/step)*step), Math.max(this._bbox[0][1], Math.floor(extent[1]/step)*step) ]; var p1 = [ Math.min(this._bbox[1][0]+99999, Math.floor(extent[2]/step)*step), Math.min(this._bbox[1][1]+99999, Math.floor(extent[3]/step)*step) ]; for (var x=p0[0]; x<=p1[0]; x += step) { for (var y=p0[1]; y<=p1[1]; y += step) { var p, geom = [ [x,y], [x+step, y], [x+step, y+step], [x , y+step], [x,y]]; if (level>2) { var m = this._midPt(geom[0],geom[2]); // .5 var g = []; for (i=0; i .4 for (i=0; i<4; i++) { g = []; g.push(geom[i]); g.push(this._midPt(geom[i],geom[(i+1)%4])); g.push(this._midPt(m,g[1])); g.push(this._midPt(m,geom[i])); p = this._midPt(geom[i],geom[(i+3)%4]); g.push(this._midPt(m,p)); g.push(p); g.push(geom[i]); features.push(this._trFeature(g, ol.coordinate.toDFCI([x,y], 2)+'.'+(4-i), level, projection)); } } else { features.push(this._trFeature(geom, ol.coordinate.toDFCI([x,y], level), level, projection)); } } } return features }; /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** DayNight source: a source to display day/night on a map * @constructor * @extends {ol.source.Vector} * @param {any} options Vector source options * @param {string|Date} time source date time * @param {number} step step in degree for coordinate precision */ ol.source.DayNight = function(options) { options = options || {}; options.loader = this._loader options.strategy = ol.loadingstrategy.all; ol.source.Vector.call (this, options); this.set('time', options.time || new Date()); this.set('step', options.step || 1); }; ol.ext.inherits(ol.source.DayNight, ol.source.Vector); (function(){ /** Loader * @private */ ol.source.DayNight.prototype._loader = function(extent, resolution, projection) { var lonlat = this.getCoordinates(this.get('time')); var geom = new ol.geom.Polygon([lonlat]); geom.transform('EPSG:4326', projection); this.addFeature(new ol.Feature(geom)); }; /** Set source date time * @param {string|Date} time source date time */ ol.source.DayNight.prototype.setTime = function(time) { this.set('time', time); this.refresh(); }; /** Compute the position of the Sun in ecliptic coordinates at julianDay. * @see http://en.wikipedia.org/wiki/Position_of_the_Sun * @param {number} julianDay * @private */ function _sunEclipticPosition(julianDay) { var deg2rad = Math.PI / 180; // Days since start of J2000.0 var n = julianDay - 2451545.0; // mean longitude of the Sun var L = 280.460 + 0.9856474 * n; L %= 360; // mean anomaly of the Sun var g = 357.528 + 0.9856003 * n; g %= 360; // ecliptic longitude of Sun var lambda = L + 1.915 * Math.sin(g * deg2rad) + 0.02 * Math.sin(2 * g * deg2rad); // distance from Sun in AU var R = 1.00014 - 0.01671 * Math.cos(g * deg2rad) - 0.0014 * Math.cos(2 * g * deg2rad); return { lambda: lambda, R: R }; } /** * @see http://en.wikipedia.org/wiki/Axial_tilt#Obliquity_of_the_ecliptic_.28Earth.27s_axial_tilt.29 * @param {number} julianDay * @private */ function _eclipticObliquity(julianDay) { var n = julianDay - 2451545.0; // Julian centuries since J2000.0 var T = n / 36525; var epsilon = 23.43929111 - T * (46.836769 / 3600 - T * (0.0001831 / 3600 + T * (0.00200340 / 3600 - T * (0.576e-6 / 3600 - T * 4.34e-8 / 3600)))); return epsilon; } /* Compute the Sun's equatorial position from its ecliptic position. * @param {number} sunEclLng sun lon in degrees * @param {number} eclObliq secliptic position in degrees * @return {number} position in degrees * @private */ function _sunEquatorialPosition(sunEclLon, eclObliq) { var rad2deg = 180 / Math.PI; var deg2rad = Math.PI / 180; var alpha = Math.atan(Math.cos(eclObliq * deg2rad) * Math.tan(sunEclLon * deg2rad)) * rad2deg; var delta = Math.asin(Math.sin(eclObliq * deg2rad) * Math.sin(sunEclLon * deg2rad)) * rad2deg; var lQuadrant = Math.floor(sunEclLon / 90) * 90; var raQuadrant = Math.floor(alpha / 90) * 90; alpha = alpha + (lQuadrant - raQuadrant); return {alpha: alpha, delta: delta}; } /** Get night-day separation line * @param {string} time DateTime string, default yet * @param {string} options use 'line' to get the separation line, 'day' to get the day polygon, 'night' to get the night polygon or 'daynight' to get both polygon, default 'night' * @return {Array|Array>} */ ol.source.DayNight.prototype.getCoordinates = function (time, options) { var rad2deg = 180 / Math.PI; var deg2rad = Math.PI / 180; var date = time ? new Date(time) : new Date(); // Calculate the present UTC Julian Date. // Function is valid after the beginning of the UNIX epoch 1970-01-01 and ignores leap seconds. var julianDay = (date / 86400000) + 2440587.5; // Calculate Greenwich Mean Sidereal Time (low precision equation). // http://aa.usno.navy.mil/faq/docs/GAST.php var gst = (18.697374558 + 24.06570982441908 * (julianDay - 2451545.0)) % 24; var lonlat = []; var sunEclPos = _sunEclipticPosition(julianDay); var eclObliq = _eclipticObliquity(julianDay); var sunEqPos = _sunEquatorialPosition(sunEclPos.lambda, eclObliq); var step = this.get('step') || 1; for (var i = -180; i <= 180; i += step) { var lon = i; // Hour angle (indegrees) of the sun for a longitude on Earth. var ha = (gst * 15 + lon) - sunEqPos.alpha; // Latitude var lat = Math.atan(-Math.cos(ha * deg2rad) / Math.tan(sunEqPos.delta * deg2rad)) * rad2deg; // New point lonlat.push([lon, lat]); } switch (options) { case 'line': break; case 'day': sunEqPos.delta *= -1; // fallthrough default: { // Close polygon lat = (sunEqPos.delta < 0) ? 90 : -90; for(var tlon=180; tlon>=-180; tlon-=step){ lonlat.push([tlon,lat]); } lonlat.push(lonlat[0]) break; } } // Return night + day polygon if (options === 'daynight') { var day = []; lonlat.forEach(function (t) { day.push(t.slice()); }); day[0][1] = -day[0][1]; day[day.length-1][1] = -day[0][1]; day[day.length-1][1] = -day[0][1]; lonlat = [ lonlat, day ]; } // Return polygon return lonlat; }; })(); /*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ /** Delaunay source * Calculate a delaunay triangulation from points in a source * @param {*} options extend ol/source/Vector options * @param {ol/source/Vector} options.source the source that contains the points */ ol.source.Delaunay = function(options) { options = options || {}; this._nodes = options.source; delete options.source; ol.source.Vector.call (this, options); // Convex hull this.hull = []; // A new node is added to the source node: calculate the new triangulation this._nodes.on('addfeature', this._onAddNode.bind(this)); // A new node is removed from the source node: calculate the new triangulation this._nodes.on('removefeature', this._onRemoveNode.bind(this)); this.set ('epsilon', options.epsilon || .0001) }; ol.ext.inherits(ol.source.Delaunay, ol.source.Vector); /** Clear source (and points) * @param {boolean} opt_fast */ ol.source.Delaunay.prototype.clear = function(opt_fast) { ol.source.Vector.prototype.clear.call(this, opt_fast); this.getNodeSource().clear(opt_fast); }; /** Add a new triangle in the source * @param {Array
            } pts */ ol.source.Delaunay.prototype._addTriangle = function(pts) { pts.push(pts[0]); var triangle = new ol.Feature(new ol.geom.Polygon([pts])); this.addFeature(triangle); this.flip.push(triangle); return triangle; }; /** Get nodes */ ol.source.Delaunay.prototype.getNodes = function () { return this._nodes.getFeatures(); }; /** Get nodes source */ ol.source.Delaunay.prototype.getNodeSource = function () { return this._nodes; }; /** * A point has been removed * @param {ol/source/Vector.Event} evt */ ol.source.Delaunay.prototype._onRemoveNode = function(evt) { // console.log(evt) var pt = evt.feature.getGeometry().getCoordinates(); if (!pt) return; // Still there (when removing duplicated points) if (this.getNodesAt(pt).length) return; // console.log('removenode', evt.feature) // Get associated triangles var triangles = this.getTrianglesAt(pt); this.flip=[]; // Get hole var i; var edges = []; while (triangles.length) { var tr = triangles.pop() this.removeFeature(tr); tr = tr.getGeometry().getCoordinates()[0]; var pts = []; for (i=0; i<3; i++) { p = tr[i]; if (!ol.coordinate.equal(p,pt)) { pts.push(p); } } edges.push(pts); } pts = edges.pop(); /* DEBUG var se = ''; edges.forEach(function(e){ se += ' - '+this.listpt(e); }.bind(this)); console.log('EDGES', se); */ i = 0; function testEdge(p0, p1, index) { if (ol.coordinate.equal(p0, pts[index])) { if (index) pts.push(p1); else pts.unshift(p1); return true } return false; } while (true) { var e = edges[i]; if ( testEdge(e[0], e[1], 0) || testEdge(e[1], e[0], 0) || testEdge(e[0], e[1], pts.length-1) || testEdge(e[1], e[0], pts.length-1) ) { edges.splice(i,1); i = 0; } else { i++ } if (!edges.length) break; if (i>=edges.length) { // console.log(this.listpt(pts), this.listpt(edges)); throw '[DELAUNAY:removePoint] No edge found'; } } // Closed = interior // console.log('PTS', this.listpt(pts)) var closed = ol.coordinate.equal(pts[0], pts[pts.length-1]); if (closed) pts.pop(); // Update convex hull: remove pt + add new ones var p; for (i; p=this.hull[i]; i++) { if (ol.coordinate.equal(pt,p)) { this.hull.splice(i,1); break; } } this.hull = ol.coordinate.convexHull(this.hull.concat(pts)); // select.getFeatures().clear(); // var clockwise = function (t) { var i1, s = 0; for (var i=0; i=0 ? 1:-1) }; // Add ears // interior point : ear area and object area have the same sign // extrior point : add a new point and close var clock; var enveloppe = pts.slice(); if (closed) { clock = clockwise(pts); } else { // console.log('ouvert', pts, pts.slice().push(pt)) enveloppe.push(pt); clock = clockwise(enveloppe); } // console.log('S=',clock,'CLOSED',closed) // console.log('E=',this.listpt(enveloppe)) for (i=0; i<=pts.length+1; i++) { if (pts.length<3) break; var t = [ pts[i % pts.length], pts[(i+1) % pts.length], pts[(i+2) % pts.length] ]; if (clockwise(t)===clock) { var ok = true; for (var k=i+3; k3) console.log('oops'); console.log('LEAV',this.listpt(pts)); var ul = $('ul.triangles').html(''); $('
          1. ') .text('E:'+this.listpt(enveloppe)+' - '+clock+' - '+closed) .data('triangle', new ol.Feature(new ol.geom.Polygon([enveloppe]))) .click(function(){ var t = $(this).data('triangle'); select.getFeatures().clear(); select.getFeatures().push(t); }) .appendTo(ul); for (var i=0; i') .text(this.listpt(this.flip[i].getGeometry().getCoordinates()[0]) +' - ' + clockwise(this.flip[i].getGeometry().getCoordinates()[0])) .data('triangle', this.flip[i]) .click(function(){ var t = $(this).data('triangle'); select.getFeatures().clear(); select.getFeatures().push(t); }) .appendTo(ul); } /**/ // Flip? this.flipTriangles(); }; /** * A new point has been added * @param {ol/source/VectorEvent} e */ ol.source.Delaunay.prototype._onAddNode = function(e) { var finserted = e.feature; var i, p; // Not a point! if (finserted.getGeometry().getType() !== 'Point') { this._nodes.removeFeature(finserted); return; } // Reset flip table this.flip = []; var nodes = this.getNodes(); // The point var pt = finserted.getGeometry().getCoordinates(); // Test existing point if (this.getNodesAt(pt).length > 1) { // console.log('remove duplicated points') this._nodes.removeFeature(finserted); return; } // Triangle needs at least 3 points if (nodes.length <= 3) { if (nodes.length===3) { var pts = []; for (i=0; i<3; i++) pts.push(nodes[i].getGeometry().getCoordinates()); this._addTriangle(pts); this.hull = ol.coordinate.convexHull(pts); } return; } // Get the triangle var t = this.getFeaturesAtCoordinate(pt)[0]; if (t) { this.removeFeature(t); t.set('del', true); var c = t.getGeometry().getCoordinates()[0]; for (i=0; i<3; i++) { this._addTriangle([ pt, c[i], c[(i+1)%3]]); } } else { // Calculate new convex hull var hull2 = this.hull.slice(); hull2.push(pt); hull2 = ol.coordinate.convexHull(hull2); // Search for points for (i=0; p=hull2[i]; i++) { if (ol.coordinate.equal(p,pt)) break; } i = (i!==0 ? i-1 : hull2.length-1); var p0 = hull2[i]; var stop = hull2[(i+2) % hull2.length]; for (i=0; p=this.hull[i]; i++) { if (ol.coordinate.equal(p,p0)) break; } // Connect to the hull while (true) { // DEBUG: prevent infinit loop if (i>1000) { console.error('[DELAUNAY:addPoint] Too many iterations') break; } i++; p = this.hull[i % this.hull.length]; this._addTriangle([pt, p, p0]); p0 = p; if (p[0] === stop[0] && p[1] === stop[1]) break; } this.hull = hull2; } this.flipTriangles(); }; /** Flipping algorithme: test new inserted triangle and flip */ ol.source.Delaunay.prototype.flipTriangles = function () { var count = 1000; // Count to prevent too many iterations var pi; while (this.flip.length) { // DEBUG: prevent infinite loop if (count--<0) { console.error('[DELAUNAY:flipTriangles] Too many iterations') break; } var tri = this.flip.pop(); if (tri.get('del')) continue; var ti = tri.getGeometry().getCoordinates()[0]; for (var k=0; k<3; k++) { // Get facing triangles var mid = [(ti[(k+1)%3][0]+ti[k][0])/2, (ti[(k+1)%3][1]+ti[k][1])/2]; var triangles = this.getTrianglesAt(mid); var pt1 = null; // Get opposite point if (triangles.length>1) { var t0 = triangles[0].getGeometry().getCoordinates()[0]; var t1 = triangles[1].getGeometry().getCoordinates()[0]; for (pi=0; pi} d1 * @param {Array} d2 * @return {bbolean} */ ol.source.Delaunay.prototype.intersectSegs = function (d1, d2) { var d1x = d1[1][0] - d1[0][0]; var d1y = d1[1][1] - d1[0][1]; var d2x = d2[1][0] - d2[0][0]; var d2y = d2[1][1] - d2[0][1]; var det = d1x * d2y - d1y * d2x; if (det != 0) { var k = (d1x * d1[0][1] - d1x * d2[0][1] - d1y * d1[0][0] + d1y * d2[0][0]) / det; // Intersection: return [d2[0][0] + k*d2x, d2[0][1] + k*d2y]; return (0} triangle * @return {boolean} */ ol.source.Delaunay.prototype._ptInTriangle = function(pt, triangle) { for (var i=0, p; p=triangle[i]; i++) { if (ol.coordinate.equal(pt,p)) return true; } return false; }; /** List points in a triangle (assume points get an id) for debug purposes * @param {Array} pts * @return {String} ids list */ ol.source.Delaunay.prototype.listpt = function (pts) { var s = ''; for (var i=0, p; p = pts[i]; i++) { var c = this._nodes.getClosestFeatureToCoordinate(p); if (!ol.coordinate.equal(c.getGeometry().getCoordinates(), p)) c=null; s += (s?', ':'') + (c ? c.get('id') : '?'); } return s; }; /** Test if coord is within triangle's circumcircle * @param {ol.coordinate} coord * @param {Array} triangle * @return {boolean} */ ol.source.Delaunay.prototype.inCircle = function (coord, triangle) { var c = this.getCircumCircle(triangle); return ol.coordinate.dist2d(coord, c.center) < c.radius; } /** Calculate the circumcircle of a triangle * @param {Array} triangle * @return {*} */ ol.source.Delaunay.prototype.getCircumCircle = function (triangle) { var x1 = triangle[0][0]; var y1 = triangle[0][1]; var x2 = triangle[1][0]; var y2 = triangle[1][1]; var x3 = triangle[2][0]; var y3 = triangle[2][1]; var m1 = (x1-x2)/(y2-y1); var m2 = (x1-x3)/(y3-y1); var b1 = ((y1+y2)/2) - m1*(x1+x2)/2; var b2 = ((y1+y3)/2) - m2*(x1+x3)/2; var cx = (b2-b1)/(m1-m2); var cy = m1*cx + b1; var center = [cx, cy]; return { center: center, radius: ol.coordinate.dist2d(center,triangle[0]) }; }; /** Get triangles at a point */ ol.source.Delaunay.prototype.getTrianglesAt = function(coord) { var extent = ol.extent.buffer (ol.extent.boundingExtent([coord]), this.get('epsilon')); var result = []; this.forEachFeatureIntersectingExtent(extent, function(f){ result.push(f); }); return result; }; /** Get nodes at a point */ ol.source.Delaunay.prototype.getNodesAt = function(coord) { var extent = ol.extent.buffer (ol.extent.boundingExtent([coord]), this.get('epsilon')); return this._nodes.getFeaturesInExtent(extent); }; /** Get Voronoi * @param {boolean} border include border, default false * @return { Array< ol.geom.Polygon > } */ ol.source.Delaunay.prototype.calculateVoronoi = function(border) { var voronoi = []; this.getNodes().forEach(function(f) { var pt = f.getGeometry().getCoordinates(); var isborder = false; if (border !== true) { for (var i=0; i} options.features the features, ignored if binSource is provided, default none * @param {number} [options.size] size of the grid in meter, default 200m * @param {function} [options.geometryFunction] Function that takes an ol.Feature as argument and returns an ol.geom.Point as feature's center. * @param {function} [options.flatAttributes] Function takes a bin and the features it contains and aggragate the features in the bin attributes when saving */ ol.source.FeatureBin = function (options) { options = options || {}; if (options.binSource) { this._sourceFeature = options.binSource; // When features change recalculate the bin... var timout; this._sourceFeature.on(['addfeature','changefeature','removefeature'], function() { if (timout) { // Do it only one time clearTimeout(timout); } timout = setTimeout(function () { this.reset(); }.bind(this)); }.bind(this)); } else { this._sourceFeature = new ol.source.Vector ({ features: options.features || [] }); } ol.source.BinBase.call(this, options); }; ol.ext.inherits(ol.source.FeatureBin, ol.source.BinBase); /** Set features to use as bin collector * @param {ol.Feature} features */ ol.source.FeatureBin.prototype.setFeatures = function (features) { this._sourceFeature.clear(); this._sourceFeature.addFeatures(features || []); this.reset(); }; /** Get the grid geometry at the coord * @param {ol.Coordinate} coord * @returns {ol.geom.Polygon} * @api */ ol.source.FeatureBin.prototype.getGridGeomAt = function (coord, attributes) { var f = this._sourceFeature.getFeaturesAtCoordinate(coord)[0]; if (!f) return null; var a = f.getProperties(); for (var i in a) { if (i!=='geometry') attributes[i] = a[i]; } return f.getGeometry(); }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). ol.source.GeoImage is a layer source with georeferencement to place it on a map. */ /** @typedef {Object} GeoImageOptions * @property {url} url url of the static image * @property {image} image the static image, if not provided, use url to load an image * @property {ol.Coordinate} imageCenter coordinate of the center of the image * @property {ol.Size|number} imageScale [scalex, scaley] of the image * @property {number} imageRotate angle of the image in radian, default 0 * @property {ol.Extent} imageCrop of the image to be show (in the image) default: [0,0,imageWidth,imageHeight] * @property {Array.} imageMask linestring to mask the image on the map */ /** Layer source with georeferencement to place it on a map * @constructor * @extends {ol.source.ImageCanvas} * @param {GeoImageOptions} options */ ol.source.GeoImage = function(opt_options) { var options = { attributions: opt_options.attributions, logo: opt_options.logo, projection: opt_options.projection }; // options.projection = opt_options.projection; // Load Image this._image = (opt_options.image ? opt_options.image : new Image ); this._image.crossOrigin = opt_options.crossOrigin; // 'anonymous'; // Show image on load var self = this; this._image.onload = function() { self.setCrop (self.crop); self.changed(); } if (!opt_options.image) this._image.src = opt_options.url; // Draw image on canvas options.canvasFunction = this.calculateImage; ol.source.ImageCanvas.call (this, options); // Coordinate of the image center this.center = opt_options.imageCenter; // Image scale this.setScale(opt_options.imageScale); // Rotation of the image this.rotate = opt_options.imageRotate ? opt_options.imageRotate : 0; // Crop of the image this.crop = opt_options.imageCrop; // Mask of the image this.mask = opt_options.imageMask; // Crop this.setCrop (this.crop); // Calculate extent on change this.on('change', function() { this.set('extent', this.calculateExtent()); }.bind(this)); }; ol.ext.inherits(ol.source.GeoImage, ol.source.ImageCanvas); /** calculate image at extent / resolution * @param {ol/extent/Extent} extent * @param {number} resolution * @param {number} pixelRatio * @param {ol/size/Size} size * @return {HTMLCanvasElement} */ ol.source.GeoImage.prototype.calculateImage = function(extent, resolution, pixelRatio, size) { if (!this.center) return; var canvas = document.createElement('canvas'); canvas.width = size[0]; canvas.height = size[1]; var ctx = canvas.getContext('2d'); if (!this._imageSize) return canvas; // transform coords to pixel function tr(xy) { return [ (xy[0]-extent[0])/(extent[2]-extent[0]) * size[0], (xy[1]-extent[3])/(extent[1]-extent[3]) * size[1] ]; } // Clipping mask if (this.mask) { ctx.beginPath(); var p = tr(this.mask[0]); ctx.moveTo(p[0],p[1]); for (var i=1; i get it latter if (!this._image.naturalWidth) { this.crop = crop; return; } if (crop) { switch (typeof(crop)) { case 'number': crop = [crop,crop,this._image.naturalWidth-crop,this._image.naturalHeight-crop]; break; case 'object': if (crop.length != 4) return; break; default: return; } crop = ol.extent.boundingExtent([ [crop[0],crop[1]], [crop[2],crop[3]] ]); this.crop = [ Math.max(0,crop[0]), Math.max(0,crop[1]), Math.min(this._image.naturalWidth,crop[2]), Math.min(this._image.naturalHeight,crop[3]) ]; } else this.crop = [0,0, this._image.naturalWidth,this._image.naturalHeight]; if (this.crop[2]<=this.crop[0]) this.crop[2] = this.crop[0]+1; if (this.crop[3]<=this.crop[1]) this.crop[3] = this.crop[1]+1; this._imageSize = [ this.crop[2]-this.crop[0], this.crop[3]-this.crop[1] ]; this.changed(); }; /** Get the extent of the source. * @param {module:ol/extent~Extent} extent If provided, no new extent will be created. Instead, that extent's coordinates will be overwritten. * @return {ol.extent} */ ol.source.GeoImage.prototype.getExtent = function(opt_extent) { var ext = this.get('extent'); if (!ext) ext = this.calculateExtent(); if (opt_extent) { for (var i=0; i} */ ol.source.HexBin.prototype.getHexFeatures = function () { return ol.source.BinBase.prototype.getGridFeatures.call(this); }; /* Copyright (c) 2021 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) */ /** Inverse distance weighting interpolated source - Shepard's method * @see https://en.wikipedia.org/wiki/Inverse_distance_weighting * @constructor * @extends {ol.source.ImageCanvas} * @param {*} [options] * @param {ol.source.vector} options.source a source to interpolate * @param {number} [options.scale=4] scale factor, use large factor to enhance performances (but minor accuracy) * @param {string|function} options.weight The feature attribute to use for the weight or a function that returns a weight from a feature. Weight values should range from 0 to 100. Default use the weight attribute of the feature. */ ol.source.IDW = function(options) { options = options || {}; // Draw image on canvas options.canvasFunction = this.calculateImage; this._source = options.source; this._canvas = document.createElement('CANVAS'); this._source.on(['addfeature','removefeature','clear','removefeature'], function() { this.changed(); }.bind(this)); ol.source.ImageCanvas.call (this, options); this.set('scale', options.scale || 4); this._weight = typeof(options.weight) === 'function' ? options.weight : function(f) { return f.get(options.weight||'weight'); } }; ol.ext.inherits(ol.source.IDW, ol.source.ImageCanvas); /** Get the source */ ol.source.IDW.prototype.getSource = function() { return this._source; }; /** Convert hue to rgb factor * @param {number} h * @return {number} * @private */ ol.source.IDW.prototype.hue2rgb = function(h) { h = (h + 6) % 6; if (h < 1) return Math.round(h * 255); if (h < 3) return 255; if (h < 4) return Math.round((4 - h) * 255); return 0; }; /** Get color for a value. Return an array of RGBA values. * @param {number} v value * @returns {Array} * @api */ ol.source.IDW.prototype.getColor = function(v) { // Get hue var h = 4 - (0.04 * v); // Convert to RGB return [ this.hue2rgb(h + 2), this.hue2rgb(h), this.hue2rgb(h - 2), 255 ] }; /** Apply the value to the map RGB. Overwrite this function to set your own colors. * @param {number} v value * @param {Uint8ClampedArray} data RGBA array * @param {number} i index in the RGBA array * @api */ ol.source.IDW.prototype.setData = function(v, data, i) { // Get color var color = this.getColor(v) // Convert to RGB data[i] = color[0]; data[i+1] = color[1]; data[i+2] = color[2]; data[i+3] = color[3]; }; /** Get image value at coord (RGBA) * @param {l.coordinate} coord * @return {Uint8ClampedArray} */ ol.source.IDW.prototype.getValue = function(coord) { if (!this._canvas) return null var pt = this.transform(coord); var v = this._canvas.getContext('2d').getImageData(Math.round(pt[0]), Math.round(pt[1]), 1, 1).data; return (v); }; /** Calculate IDW at extent / resolution * @param {ol/extent/Extent} extent * @param {number} resolution * @param {number} pixelRatio * @param {ol/size/Size} size * @return {HTMLCanvasElement} * @private */ ol.source.IDW.prototype.calculateImage = function(extent, resolution, pixelRatio, size) { if (!this._source) return this._canvas; // Calculation canvas at small resolution var canvas = document.createElement('CANVAS'); var width = canvas.width = Math.round(size[0] / (this.get('scale')*pixelRatio)); var height = canvas.height = Math.round(size[1] / (this.get('scale')*pixelRatio)); var ctx = canvas.getContext('2d'); var imageData = ctx.getImageData(0, 0, width, height); // Transform coords to pixel / value var pts = []; var dw = width / (extent[2]-extent[0]); var dh = height / (extent[1]-extent[3]); var tr = this.transform = function(xy, v) { return [ (xy[0]-extent[0]) * dw, (xy[1]-extent[3]) * dh, v ]; } // Get features / weight this._source.getFeatures().forEach(function(f) { pts.push(tr(f.getGeometry().getFirstCoordinate(), this._weight(f))); }.bind(this)); // Compute image var x, y; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { var t = 0, b = 0; for(var i = 0; i < pts.length; ++i) { var dx = x - pts[i][0]; var dy = y - pts[i][1]; var d = dx*dx + dy*dy; // Inverse distance weighting - Shepard's method if (d === 0) { b = 1; t = pts[i][2]; break; } var inv = 1 / (d*d); t += inv * pts[i][2]; b += inv; } this.setData(t/b, imageData.data, (y*width + x)*4); } } ctx.putImageData(imageData, 0, 0); /* DEBUG : Draw points * / pts.forEach(function(p) { ctx.fillRect(p[0], p[1], 1, 1); }); /**/ // Draw full resolution canvas this._canvas.width = Math.round(size[0]); this._canvas.height = Math.round(size[1]); this._canvas.getContext('2d').drawImage(canvas, 0, 0, size[0], size[1]); return this._canvas; } /* 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). */ /** A source for INSEE grid * @constructor * @extends {ol.source.Vector} * @param {Object} options ol.source.VectorOptions + grid option * @param {ol.source.Vector} options.source Source * @param {number} [options.size] size of the grid in meter, default 200m * @param {function} [options.geometryFunction] Function that takes an ol.Feature as argument and returns an ol.geom.Point as feature's center. * @param {function} [options.flatAttributes] Function takes a bin and the features it contains and aggragate the features in the bin attributes when saving */ ol.source.InseeBin = function (options) { options = options || {}; this._grid = new ol.InseeGrid({ size: options.size }); ol.source.BinBase.call(this, options); }; ol.ext.inherits(ol.source.InseeBin, ol.source.BinBase); /** Set grid size * @param {number} size */ ol.source.InseeBin.prototype.setSize = function (size) { if (this.getSize() !== size) { this._grid.set('size', size); this.reset(); } }; /** Get grid size * @return {number} size */ ol.source.InseeBin.prototype.getSize = function () { return this._grid.get('size'); }; /** Get the grid geometry at the coord * @param {ol.Coordinate} coord * @returns {ol.geom.Polygon} * @api */ ol.source.InseeBin.prototype.getGridGeomAt = function (coord) { return this._grid.getGridAtCoordinate(coord, this.getProjection()); }; /** Get grid extent * @param {ol.ProjectionLike} proj * @return {ol.Extent} */ ol.source.InseeBin.prototype.getGridExtent = function (proj) { return this._grid.getExtent(proj); }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). @classdesc ol.source.Mapillary is a source that load Mapillary's geotagged photos in a vector layer. Inherits from: */ /** * @constructor ol.source.Mapillary * @extends {ol.source.Vector} * @param {olx.source.Mapillary=} options */ ol.source.Mapillary = function(opt_options) { var options = opt_options || {}; options.loader = this._loaderFn; /** Max resolution to load features */ this._maxResolution = options.maxResolution || 100; /** Query limit */ this._limit = options.limit || 100; /** Default attribution */ if (!options.attributions) options.attributions = [ "© Mapillary" ]; // Bbox strategy : reload at each move if (!options.strategy) options.strategy = ol.loadingstrategy.bbox; // Init parent ol.source.Vector.call (this, options); // Client ID // this.set("clientId", options.clientId); }; ol.ext.inherits(ol.source.Mapillary, ol.source.Vector); /** Decode wiki attributes and choose to add feature to the layer * @param {feature} the feature * @param {attributes} wiki attributes * @return {boolean} true: add the feature to the layer * @API stable */ ol.source.Mapillary.prototype.readFeature = function (/*feature, attributes*/) { // Allways read feature (no filter) return true; }; /** Loader function used to load features. * @private */ ol.source.Mapillary.prototype._loaderFn = function(extent, resolution, projection) { if (resolution > this._maxResolution) return; var bbox = ol.proj.transformExtent(extent, projection, "EPSG:4326"); // Commons API: for more info @see https://www.mapillary.com/developer var date = Date.now() - 6 * 30 * 24 * 60 * 60 * 1000; var url = "https://a.mapillary.com/v2/search/im?client_id=" + this.get('clientId') + "&max_lat=" + bbox[3] + "&max_lon=" + bbox[2] + "&min_lat=" + bbox[1] + "&min_lon=" + bbox[0] + "&limit="+(this._limit-1) + "&start_time=" + date; // Ajax request to get the tile ol.ext.Ajax.get( { url: url, dataType: 'jsonp', success: function(data) { console.log(data); /* var features = []; var att, pt, feature, lastfeature = null; if (data.query && data.query.pages) return; for ( var i in data.query.pages) { att = data.query.pages[i]; if (att.coordinates && att.coordinates.length ) { pt = [att.coordinates[0].lon, att.coordinates[0].lat]; } else { var meta = att.imageinfo[0].metadata; if (!meta) { //console.log(att); continue; } pt = []; for (var k=0; k} sources Input sources or layers. For vector data, use an VectorImage layer. * @param {number} radius default 4 * @param {number} intensity default 25 */ ol.source.OilPainting = function (options) { options.operation = this._operation; options.operationType = 'image'; ol.source.Raster.call(this, options); this.set('radius', options.radius || 4); this.set('intensity', options.intensity || 25); this.on('beforeoperations', function (event) { var w = Math.round((event.extent[2]-event.extent[0])/event.resolution); var h = Math.round((event.extent[3]-event.extent[1])/event.resolution); event.data.image = new ImageData(w,h); event.data.radius = Number(this.get('radius')) || 1; event.data.intensity = Number(this.get('intensity')); }.bind(this)); }; ol.ext.inherits(ol.source.OilPainting, ol.source.Raster); /** Set value and force change */ ol.source.OilPainting.prototype.set = function(key, val) { if (val) { switch (key) { case 'intensity': case 'radius': { val = Number(val); if (val<1) val = 1; this.changed(); break; } } } return ol.source.Raster.prototype.set.call(this, key, val); }; /** * @private */ ol.source.OilPainting.prototype._operation = function(pixels, data) { var width = pixels[0].width, height = pixels[0].height, imgData = pixels[0], pixData = imgData.data, pixelIntensityCount = []; var destImageData = data.image, destPixData = destImageData.data, intensityLUT = [], rgbLUT = []; for (var y = 0; y < height; y++) { intensityLUT[y] = []; rgbLUT[y] = []; for (var x = 0; x < width; x++) { var idx = (y * width + x) * 4, r = pixData[idx], g = pixData[idx + 1], b = pixData[idx + 2], avg = (r + g + b) / 3; intensityLUT[y][x] = Math.round((avg * data.intensity) / 255); rgbLUT[y][x] = { r: r, g: g, b: b }; } } var radius = data.radius; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { pixelIntensityCount = []; // Find intensities of nearest pixels within radius. for (var yy = -radius; yy <= radius; yy++) { for (var xx = -radius; xx <= radius; xx++) { if (y + yy > 0 && y + yy < height && x + xx > 0 && x + xx < width) { var intensityVal = intensityLUT[y + yy][x + xx]; if (!pixelIntensityCount[intensityVal]) { pixelIntensityCount[intensityVal] = { val: 1, r: rgbLUT[y + yy][x + xx].r, g: rgbLUT[y + yy][x + xx].g, b: rgbLUT[y + yy][x + xx].b } } else { pixelIntensityCount[intensityVal].val++; pixelIntensityCount[intensityVal].r += rgbLUT[y + yy][x + xx].r; pixelIntensityCount[intensityVal].g += rgbLUT[y + yy][x + xx].g; pixelIntensityCount[intensityVal].b += rgbLUT[y + yy][x + xx].b; } } } } pixelIntensityCount.sort(function (a, b) { return b.val - a.val; }); var curMax = pixelIntensityCount[0].val, dIdx = (y * width + x) * 4; destPixData[dIdx] = ~~ (pixelIntensityCount[0].r / curMax); destPixData[dIdx + 1] = ~~ (pixelIntensityCount[0].g / curMax); destPixData[dIdx + 2] = ~~ (pixelIntensityCount[0].b / curMax); destPixData[dIdx + 3] = 255; } } return destImageData; }; /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * OSM layer using the Ovepass API * @constructor ol.source.Overpass * @extends {ol.source.Vector} * @param {any} options * @param {string} options.url service url, default: https://overpass-api.de/api/interpreter * @param {Array} options.filter an array of tag filters, ie. ["key", "key=value", "key~value", ...] * @param {boolean} options.node get nodes, default: true * @param {boolean} options.way get ways, default: true * @param {boolean} options.rel get relations, default: false * @param {number} options.maxResolution maximum resolution to load features * @param {string|ol.Attribution|Array} options.attributions source attribution, default OSM attribution * @param {ol.loadingstrategy} options.strategy loading strategy, default ol.loadingstrategy.bbox */ ol.source.Overpass = function(options) { options = options || {}; options.loader = this._loaderFn; /** Ovepass API Url */ this._url = options.url || 'https://overpass-api.de/api/interpreter'; /** Max resolution to load features */ this._maxResolution = options.maxResolution || 100; /** Default attribution */ if (!options.attributions) { options.attributions = ol.source.OSM.ATTRIBUTION; } // Bbox strategy : reload at each move if (!options.strategy) options.strategy = ol.loadingstrategy.bbox; ol.source.Vector.call (this, options); this._types = { node: options.node!==false, way: options.way!==false, rel: options.rel===true }; this._filter = options.filter; }; ol.ext.inherits(ol.source.Overpass, ol.source.Vector); /** Loader function used to load features. * @private */ ol.source.Overpass.prototype._loaderFn = function(extent, resolution, projection) { if (resolution > this._maxResolution) return; var self = this; var bbox = ol.proj.transformExtent(extent, projection, "EPSG:4326"); bbox = bbox[1] + ',' + bbox[0] + ',' + bbox[3] + ',' + bbox[2]; // Overpass QL var query = '[bbox:'+bbox+'][out:xml][timeout:25];'; query += '('; // Search attributes for (var t in this._types) { if (this._types[t]) { query += t; for (var n=0, filter; filter = this._filter[n]; n++) { query += '['+filter+']'; } query += ';' } } query +=');out;>;out skel qt;' var ajax = new XMLHttpRequest(); ajax.open('POST', this._url, true); ajax.onload = function () { var features = new ol.format.OSMXML().readFeatures(this.responseText,{featureProjection: projection}); var result = []; // Remove duplicated features for (var i=0, f; f=features[i]; i++) { if (!self.hasFeature(f)) result.push(f); } self.addFeatures(result); }; ajax.onerror = function () { console.log(arguments); }; ajax.send('data='+query); }; /** * Search if feature is allready loaded * @param {ol.Feature} feature * @return {boolean} * @private */ ol.source.Overpass.prototype.hasFeature = function(feature) { var p = feature.getGeometry().getFirstCoordinate(); var id = feature.getId(); var existing = this.getFeaturesInExtent([p[0]-0.1, p[1]-0.1, p[0]+0.1, p[1]+0.1]); for (var i=0, f; f=existing[i]; i++) { if (id===f.getId()) { return true; } } return false; }; /* 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). */ /** A vector source to load WFS at a tile zoom level * @constructor * @fires tileloadstart * @fires tileloadend * @fires tileloaderror * @fires overload * @extends {ol.source.Vector} * @param {Object} options * @param {string} [options.version=1.1.0] WFS version to use. Can be either 1.0.0, 1.1.0 or 2.0.0. * @param {string} options.typeName WFS type name parameter * @param {number} options.tileZoom zoom to load the tiles * @param {number} options.maxFeatures maximum features returned in the WFS * @param {number} options.featureLimit maximum features in the source before refresh, default Infinity * @param {boolean} [options.pagination] experimental enable pagination, default no pagination */ ol.source.TileWFS = function (options) { options = options || {}; if (!options.featureLimit) options.featureLimit = Infinity; // Tile loading strategy var zoom = options.tileZoom || 14; var sourceOpt = { strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ minZoom: zoom, maxZoom: zoom, tileSize:512 })) }; // Loading params var format = new ol.format.GeoJSON(); var url = options.url + '?service=WFS' + '&request=GetFeature' + '&version=' + (options.version || '1.1.0') + '&typename=' + (options.typeName || '') + '&outputFormat=application/json'; if (options.maxFeatures) { url += '&maxFeatures=' + options.maxFeatures + '&count=' + options.maxFeatures; } var loader = { loading: 0, loaded: 0 }; // Loading fn sourceOpt.loader = function(extent, resolution, projection) { if (loader.loading === loader.loaded) { loader.loading = loader.loaded = 0; if (this.getFeatures().length > options.maxFeatures) { this.clear(); this.refresh(); } } loader.loading++; this.dispatchEvent({ type: 'tileloadstart', loading: loader.loading, loaded: loader.loaded }); this._loadTile(url, extent, projection, format, loader); } ol.source.Vector.call(this, sourceOpt); this.set('pagination', options.pagination); }; ol.ext.inherits(ol.source.TileWFS, ol.source.Vector); /** * */ ol.source.TileWFS.prototype._loadTile = function(url, extent, projection, format, loader) { var req = url + '&srsname=' + projection.getCode() + '&bbox=' + extent.join(',') + ',' + projection.getCode(); if (this.get('pagination') && !/&startIndex/.test(url)) { req += '&startIndex=0'; } ol.ext.Ajax.get({ url: req, success: function(response) { loader.loaded++; if (response.error) { this.dispatchEvent({ type: 'tileloaderror', error: response, loading: loader.loading, loaded: loader.loaded }); } else { var pos = response.numberReturned || 0; if (/&startIndex/.test(url)) { pos += parseInt(url.replace(/.*&startIndex=(\d*).*/, '$1')); url = url.replace(/&startIndex=(\d*)/, ''); } // Still something to load ? if (pos < response.totalFeatures) { if (!this.get('pagination')) { this.dispatchEvent({ type: 'overload', total: response.totalFeatures, returned: response.numberReturned }); this.dispatchEvent({ type: 'tileloadend', loading: loader.loading, loaded: loader.loaded }); } else { url += '&startIndex='+pos; loader.loaded--; this._loadTile(url, extent, projection, format, loader); } } else { this.dispatchEvent({ type: 'tileloadend', loading: loader.loading, loaded: loader.loaded }); } var features = format.readFeatures(response, { featureProjection: projection }); if (features.length > 0) { this.addFeatures(features); } } }.bind(this), error: function(e) { loader.loaded++; this.dispatchEvent({ type: 'tileloaderror', error: e, loading: loader.loading, loaded: loader.loaded }); }.bind(this) }) }; (function () { var clear = ol.source.Vector.prototype.clear; /** Overwrite ol/source/Vector clear to fire clearstart / clearend event */ ol.source.Vector.prototype.clear = function(opt_fast) { this.dispatchEvent({ type: 'clearstart' }); clear.call(this, opt_fast) this.dispatchEvent({ type: 'clearend' }); }; })(); /** * @classdesc 3D vector layer rendering * @constructor * @extends {pl.layer.Image} * @param {Object} options * @param {ol.layer.Vector} options.source the source to display in 3D * @param {ol.style.Style} options.styler drawing style * @param {number} options.maxResolution max resolution to render 3D * @param {number} options.defaultHeight default height if none is return by a propertie * @param {function|string|Number} options.height a height function (returns height giving a feature) or a popertie name for the height or a fixed value * @param {Array} options.center center of the view, default [.5,1] */ ol.layer.Vector3D = function (options) { options = options || {}; this._source = options.source; this.height_ = this.getHfn (options.height); var canvas = document.createElement('canvas'); ol.layer.Image.call (this, { source: new ol.source.ImageCanvas({ canvasFunction: function(extent, resolution, pixelRatio, size /*, projection*/ ) { canvas.width = size[0]; canvas.height = size[1]; return canvas; } }), center: options.center || [.5,1], defaultHeight: options.defaultHeight || 0, maxResolution: options.maxResolution || Infinity }); this.setStyle(options.style); this.on (['postcompose', 'postrender'], this.onPostcompose_.bind(this)); } ol.ext.inherits(ol.layer.Vector3D, ol.layer.Image); /** * Set the height function for the layer * @param {function|string|Number} height a height function (returns height giving a feature) or a popertie name or a fixed value */ ol.layer.Vector3D.prototype.setHeight = function(height) { this.height_ = this.getHfn (height); this.changed(); }; /** * Set style associated with the layer * @param {ol.style.Style} s */ ol.layer.Vector3D.prototype.setStyle = function(s) { if (s instanceof ol.style.Style) this._style = s; else this._style = new ol.style.Style(); if (!this._style.getStroke()) { this._style.setStroke(new ol.style.Stroke({ width: 1, color: 'red' })); } if (!this._style.getFill()) { this._style.setFill( new ol.style.Fill({ color: 'rgba(0,0,255,0.5)'}) ); } if (!this._style.getText()) { this._style.setText( new ol.style.Fill({ color: 'red'}) ); } // Get the geometry if (s && s.getGeometry()) { var geom = s.getGeometry(); if (typeof(geom)==='function') { this.set('geometry', geom); } else { this.set('geometry', function() { return geom; }); } } else { this.set('geometry', function(f) { return f.getGeometry(); }); } }; /** * Get style associated with the layer * @return {ol.style.Style} */ ol.layer.Vector3D.prototype.getStyle = function() { return this._style; }; /** Calculate 3D at potcompose * @private */ ol.layer.Vector3D.prototype.onPostcompose_ = function(e) { var res = e.frameState.viewState.resolution; if (res > this.get('maxResolution')) return; this.res_ = res*400; if (this.animate_) { var elapsed = e.frameState.time - this.animate_; if (elapsed < this.animateDuration_) { this.elapsedRatio_ = this.easing_(elapsed / this.animateDuration_); // tell OL3 to continue postcompose animation e.frameState.animate = true; } else { this.animate_ = false; this.height_ = this.toHeight_ } } var ratio = e.frameState.pixelRatio; var ctx = e.context; var m = this.matrix_ = e.frameState.coordinateToPixelTransform; // Old version (matrix) if (!m) { m = e.frameState.coordinateToPixelMatrix, m[2] = m[4]; m[3] = m[5]; m[4] = m[12]; m[5] = m[13]; } this.center_ = [ ctx.canvas.width*this.get('center')[0]/ratio, ctx.canvas.height*this.get('center')[1]/ratio ]; var f = this._source.getFeaturesInExtent(e.frameState.extent); ctx.save(); ctx.scale(ratio,ratio); var s = this.getStyle(); ctx.lineWidth = s.getStroke().getWidth(); ctx.lineCap = s.getStroke().getLineCap(); ctx.strokeStyle = ol.color.asString(s.getStroke().getColor()); ctx.fillStyle = ol.color.asString(s.getFill().getColor()); var builds = []; for (var i=0; i this.animateDuration_) { this.animate_ = false; } return !!this.animate_; } /** Get height for a feature * @param {ol.Feature} f * @return {number} * @private */ ol.layer.Vector3D.prototype._getFeatureHeight = function (f) { if (this.animate_) { var h1 = this.height_(f); var h2 = this.toHeight_(f); return (h1*(1-this.elapsedRatio_)+this.elapsedRatio_*h2); } else return this.height_(f); }; /** Get hvector for a point * @private */ ol.layer.Vector3D.prototype.hvector_ = function (pt, h) { var p0 = [ pt[0]*this.matrix_[0] + pt[1]*this.matrix_[1] + this.matrix_[4], pt[0]*this.matrix_[2] + pt[1]*this.matrix_[3] + this.matrix_[5] ]; return { p0: p0, p1: [ p0[0] + h/this.res_ * (p0[0]-this.center_[0]), p0[1] + h/this.res_ * (p0[1]-this.center_[1]) ] }; }; /** Get a vector 3D for a feature * @private */ ol.layer.Vector3D.prototype.getFeature3D_ = function (f, h) { var geom = this.get('geometry')(f); var c = geom.getCoordinates(); switch (geom.getType()) { case "Polygon": c = [c]; // fallthrough case "MultiPolygon": var build = []; for (var i=0; i=0; k--) { ctx.lineTo(b[k].p1[0], b[k].p1[1]); } } ctx.closePath(); } ctx.fill("evenodd"); ctx.stroke(); break; } case "Point": { b = build[i]; var t = b.feature.get('label'); if (t) { var p = b.geom.p1; var m = ctx.measureText(t); var h = Number (ctx.font.match(/\d+(\.\d+)?/g).join([])); ctx.fillRect (p[0]-m.width/2 -5, p[1]-h -5, m.width +10, h +10) ctx.strokeRect (p[0]-m.width/2 -5, p[1]-h -5, m.width +10, h +10) ctx.save() ctx.fillStyle = ol.color.asString(this._style.getText().getFill().getColor()); ctx.textAlign = 'center'; ctx.textBaseline = 'bottom'; ctx.fillText ( t, p[0], p[1] ); ctx.restore(); } break; } default: break; } } }; /* 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). @classdesc ol.source.WikiCommons is a source that load Wikimedia Commons content in a vector layer. Inherits from: */ /** * @constructor ol.source.WikiCommons * @extends {ol.source.Vector} * @param {olx.source.WikiCommons=} options */ ol.source.WikiCommons = function(opt_options) { var options = opt_options || {}; options.loader = this._loaderFn; /** Max resolution to load features */ this._maxResolution = options.maxResolution || 100; /** Result language */ this._lang = options.lang || "fr"; /** Query limit */ this._limit = options.limit || 100; /** Default attribution */ if (!options.attributions) options.attributions = [ "© Wikimedia Commons" ]; // Bbox strategy : reload at each move if (!options.strategy) options.strategy = ol.loadingstrategy.bbox; ol.source.Vector.call (this, options); }; ol.ext.inherits(ol.source.WikiCommons, ol.source.Vector); /** Decode wiki attributes and choose to add feature to the layer * @param {feature} the feature * @param {attributes} wiki attributes * @return {boolean} true: add the feature to the layer * @API stable */ ol.source.WikiCommons.prototype.readFeature = function (feature, attributes){ feature.set("descriptionurl", attributes.descriptionurl); feature.set("url", attributes.url); feature.set("title", attributes.title.replace(/^file:|.jpg$/ig,"")); feature.set("thumbnail", attributes.url.replace(/^(.+wikipedia\/commons)\/([a-zA-Z0-9]\/[a-zA-Z0-9]{2})\/(.+)$/,"$1/thumb/$2/$3/200px-$3")); feature.set("user", attributes.user); if (attributes.extmetadata && attributes.extmetadata.LicenseShortName) feature.set("copy", attributes.extmetadata.LicenseShortName.value); return true; }; /** Loader function used to load features. * @private */ ol.source.WikiCommons.prototype._loaderFn = function(extent, resolution, projection){ if (resolution > this._maxResolution) return; var self = this; var bbox = ol.proj.transformExtent(extent, projection, "EPSG:4326"); // Commons API: for more info @see https://commons.wikimedia.org/wiki/Commons:API/MediaWiki var url = "https://commons.wikimedia.org/w/api.php?action=query&format=json&origin=*&prop=coordinates|imageinfo" + "&generator=geosearch&iiprop=timestamp|user|url|extmetadata|metadata|size&iiextmetadatafilter=LicenseShortName" + "&ggsbbox=" + bbox[3] + "|" + bbox[0] + "|" + bbox[1] + "|" + bbox[2] + "&ggslimit="+this._limit + "&iilimit="+(this._limit-1) + "&ggsnamespace=6"; // Ajax request to get the tile ol.ext.Ajax.get({ url: url, success: function(data) { //console.log(data); var features = []; var att, pt, feature; if (!data.query || !data.query.pages) return; for ( var i in data.query.pages){ att = data.query.pages[i]; if (att.coordinates && att.coordinates.length ) { pt = [att.coordinates[0].lon, att.coordinates[0].lat]; } else { var meta = att.imageinfo[0].metadata; if (!meta) { //console.log(att); continue; } pt = []; var found=0; for (var k=0; k1000) { this.stopAnimation(); return; } // Start animation from now time = a.start = (new Date()).getTime(); } // Run animation if (a.start) { var vectorContext = e.vectorContext || ol.render.getVectorContext(e); var d = (time - a.start) / duration; // Animation ends if (d > 1.0) { this.stopAnimation(); d = 1; } d = this.get('animationMethod')(d); // Animate var style = this.getStyle(); var stylefn = (typeof(style) == 'function') ? style : style.length ? function(){ return style; } : function(){ return [style]; } ; // Layer opacity e.context.save(); e.context.globalAlpha = this.getOpacity(); for (i=0, c; c=a.clusters[i]; i++) { var pt = c.f.getGeometry().getCoordinates(); var dx = pt[0]-c.pt[0]; var dy = pt[1]-c.pt[1]; if (a.revers) { pt[0] = c.pt[0] + d * dx; pt[1] = c.pt[1] + d * dy; } else { pt[0] = pt[0] - d * dx; pt[1] = pt[1] - d * dy; } // Draw feature var st = stylefn(c.f, resolution, true); if (!st.length) st = [st]; // If one feature: draw the feature if (c.f.get("features").length===1 && !dx && !dy) { f = c.f.get("features")[0]; } // else draw a point else { var geo = new ol.geom.Point(pt); f = new ol.Feature(geo); } for (var k=0, s; s=st[k]; k++) { // Multi-line text if (s.getText() && /\n/.test(s.getText().getText())) { var offsetX = s.getText().getOffsetX(); var offsetY = s.getText().getOffsetY(); var rot = s.getText().getRotation() || 0; var fontSize = Number((s.getText().getFont() || '10px').match(/\d+/)) * 1.2; var str = s.getText().getText().split('\n') var dl, nb = str.length-1; var s2 = s.clone(); // Draw each lines str.forEach(function(t, i) { if (i==1) { // Allready drawn s2.setImage(); s2.setFill(); s2.setStroke(); } switch (s.getText().getTextBaseline()) { case 'alphabetic': case 'ideographic': case 'bottom': { dl = nb; break; } case 'hanging': case 'top': { dl = 0; break; } default : { dl = nb/2; break; } } s2.getText().setOffsetX(offsetX - Math.sin(rot)*fontSize*(i - dl)); s2.getText().setOffsetY(offsetY + Math.cos(rot)*fontSize*(i - dl)); s2.getText().setText(t); vectorContext.drawFeature(f, s2); }); } else { vectorContext.drawFeature(f, s); } /* OLD VERSION OL < 4.3 // Retina device var ratio = e.frameState.pixelRatio; var sc; // OL < v4.3 : setImageStyle doesn't check retina var imgs = ol.Map.prototype.getFeaturesAtPixel ? false : s.getImage(); if (imgs) { sc = imgs.getScale(); imgs.setScale(sc*ratio); } // OL3 > v3.14 if (vectorContext.setStyle) { // If one feature: draw the feature if (c.f.get("features").length===1 && !dx && !dy) { vectorContext.drawFeature(c.f.get("features")[0], s); } // else draw a point else { vectorContext.setStyle(s); vectorContext.drawGeometry(geo); } } // older version else { vectorContext.setImageStyle(imgs); vectorContext.setTextStyle(s.getText()); vectorContext.drawPointGeometry(geo); } if (imgs) imgs.setScale(sc); */ } } e.context.restore(); // tell ol to continue postcompose animation e.frameState.animate = true; // Prevent layer drawing (clip with null rect) e.context.save(); e.context.beginPath(); e.context.rect(0,0,0,0); e.context.clip(); this.clip_ = true; } return; }; /** * remove clipping after the layer is drawn * @private */ ol.layer.AnimatedCluster.prototype.postanimate = function(e) { if (this.clip_) { e.context.restore(); this.clip_ = false; } }; /* 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). */ /** * @classdesc * Image layer to use with a GeoImage source and return the extent calcaulted with this source. * @extends {ol.layer.Image} * @param {Object=} options Layer Image options. * @api */ ol.layer.GeoImage = function(options) { ol.layer.Image.call(this, options); } ol.ext.inherits (ol.layer.GeoImage, ol.layer.Image); /** * Return the {@link module:ol/extent~Extent extent} of the source associated with the layer. * @return {ol.Extent} The layer extent. * @observable * @api */ ol.layer.GeoImage.prototype.getExtent = function() { return this.getSource().getExtent(); } /* 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). */ // /** IGN's Geoportail WMTS layer definition * @constructor * @extends {ol.layer.Tile} * @param {olx.layer.WMTSOptions=} options WMTS options if not defined default are used * @param {string} options.layer Geoportail layer name * @param {string} options.gppKey Geoportail API key, default use layer registered key * @param {ol.projectionLike} [options.projection=EPSG:3857] projection for the extent, default EPSG:3857 * @param {olx.source.WMTSOptions=} tileoptions WMTS options if not defined default are used */ ol.layer.Geoportail = function(layer, options, tileoptions) { options = options || {}; tileoptions = tileoptions || {}; // use function(options, tileoption) when layer is set in options if (typeof(layer)!=='string') { tileoptions = options || {}; options = layer; layer = options.layer; } var maxZoom = options.maxZoom; // A source is defined if (options.source) { layer = options.source.getLayer(); options.gppKey = options.source.getGPPKey(); } var capabilities = window.geoportailConfig ? window.geoportailConfig.capabilities[options.gppKey || options.key] || window.geoportailConfig.capabilities["default"] || ol.layer.Geoportail.capabilities : ol.layer.Geoportail.capabilities; capabilities = capabilities[layer]; if (!capabilities) capabilities = ol.layer.Geoportail.capabilities[layer]; if (!capabilities) { capabilities = { title: layer, originators: [] }; console.error("ol.layer.Geoportail: no layer definition for \""+layer+"\"\nTry to use ol/layer/Geoportail~loadCapabilities() to get it."); // throw new Error("ol.layer.Geoportail: no layer definition for \""+layer+"\""); } // tile options & default params for (var i in capabilities) if (typeof tileoptions[i]== "undefined") tileoptions[i] = capabilities[i]; this._originators = capabilities.originators; if (!tileoptions.gppKey) tileoptions.gppKey = options.gppKey || options.key; if (!options.source) options.source = new ol.source.Geoportail(layer, tileoptions); if (!options.title) options.title = capabilities.title; if (!options.name) options.name = layer; options.layer = layer; if (!options.queryable) options.queryable = capabilities.queryable; if (!options.desc) options.desc = capabilities.desc; if (!options.extent && capabilities.bbox) { if (capabilities.bbox[0]>-170 && capabilities.bbox[2]<170) { options.extent = ol.proj.transformExtent(capabilities.bbox, 'EPSG:4326', options.projection || 'EPSG:3857'); } } options.maxZoom = maxZoom; // calculate layer max resolution if (!options.maxResolution && tileoptions.minZoom) { options.source.getTileGrid().minZoom -= (tileoptions.minZoom>1 ? 2 : 1); options.maxResolution = options.source.getTileGrid().getResolution(options.source.getTileGrid().minZoom) options.source.getTileGrid().minZoom = tileoptions.minZoom; } ol.layer.Tile.call (this, options); // BUG GPP: Attributions constraints are not set properly :( /** / // Set attribution according to the originators var counter = 0; // Get default attribution var getAttrib = function(title, o) { if (this.get('attributionMode')==='logo') { if (!title) return ol.source.Geoportail.prototype.attribution; else return ''; } else { if (!title) return ol.source.Geoportail.prototype.attribution; else return '© '+title+'' } }.bind(this); var currentZ, currentCenter = []; var setAttribution = function(e) { var a, o, i; counter--; if (!counter) { var z = e.frameState.viewState.zoom; console.log(e) if (z===currentZ && e.frameState.viewState.center[0]===currentCenter[0] && e.frameState.viewState.center[1]===currentCenter[1]){ return; } currentZ = z; currentCenter = e.frameState.viewState.center; var ex = e.frameState.extent; ex = ol.proj.transformExtent (ex, e.frameState.viewState.projection, 'EPSG:4326'); if (this._originators) { var attrib = this.getSource().getAttributions(); // ol v5 if (typeof(attrib)==='function') attrib = attrib(); attrib.splice(0, attrib.length); var maxZoom = 0; for (a in this._originators) { o = this._originators[a]; for (i=0; i maxZoom && ol.extent.intersects(ex, o.constraint[i].bbox)) { maxZoom = o.constraint[i].maxZoom; } } } if (maxZoom < z) z = maxZoom; if (this.getSource().getTileGrid() && z < this.getSource().getTileGrid().getMinZoom()) { z = this.getSource().getTileGrid().getMinZoom(); } for (a in this._originators) { o = this._originators[a]; if (!o.constraint.length) { attrib.push (getAttrib(a, o)); } else { for (i=0; i= o.constraint[i].minZoom && ol.extent.intersects(ex, o.constraint[i].bbox)) { attrib.push (getAttrib(a, o)); break; } } } } if (!attrib.length) attrib.push ( getAttrib() ); this.getSource().setAttributions(attrib); } } }.bind(this); this.on('precompose', function(e) { counter++; setTimeout(function () { setAttribution(e) }, 500); }); /**/ }; ol.ext.inherits (ol.layer.Geoportail, ol.layer.Tile); /** Default capabilities for main layers */ ol.layer.Geoportail.capabilities = { // choisirgeoportail "GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2": { "key":"cartes", "server":"https://wxs.ign.fr/geoportail/wmts","layer":"GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2","title":"Plan IGN v2","format":"image/png","style":"normal","queryable":false,"tilematrix":"PM","minZoom":0,"maxZoom":19,"bbox":[-175,-85,175,85],"desc":"Cartographie multi-échelles sur le territoire national, issue des bases de données vecteur de l’IGN, mis à jour régulièrement et réalisée selon un processus entièrement automatisé. Version actuellement en beta test","originators":{"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":0,"maxZoom":19,"constraint":[{"minZoom":0,"maxZoom":19,"bbox":[-175,-85,175,85]}]}}}, "CADASTRALPARCELS.PARCELLAIRE_EXPRESS": { "key":"parcellaire", "server":"https://wxs.ign.fr/geoportail/wmts","layer":"CADASTRALPARCELS.PARCELLAIRE_EXPRESS","title":"PCI vecteur","format":"image/png","style":"PCI vecteur","queryable":false,"tilematrix":"PM","minZoom":0,"maxZoom":19,"bbox":[-63.37252,-21.475586,55.925865,51.31212],"desc":"Plan cadastral informatisé vecteur de la DGFIP.","originators":{"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":0,"maxZoom":19,"constraint":[{"minZoom":0,"maxZoom":19,"bbox":[-63.37252,-21.475586,55.925865,51.31212]}]}}}, "ORTHOIMAGERY.ORTHOPHOTOS": { "key":"ortho", "server":"https://wxs.ign.fr/geoportail/wmts","layer":"ORTHOIMAGERY.ORTHOPHOTOS","title":"Photographies aériennes","format":"image/jpeg","style":"normal","queryable":true,"tilematrix":"PM","minZoom":0,"bbox":[-178.18713,-22.767689,167.94624,51.11242],"desc":"Photographies aériennes","originators":{"CRCORSE":{"href":"http://www.corse.fr//","attribution":"CRCORSE","logo":"https://wxs.ign.fr/static/logos/CRCORSE/CRCORSE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[8.428783,41.338627,9.688606,43.08541]}]},"SIGLR":{"href":"http://www.siglr.org//","attribution":"SIGLR","logo":"https://wxs.ign.fr/static/logos/SIGLR/SIGLR.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[1.6784439,42.316307,4.8729386,44.978218]}]},"BOURGOGNE-FRANCHE-COMTE":{"href":"https://www.bourgognefranchecomte.fr/","attribution":"Auvergne","logo":"https://wxs.ign.fr/static/logos/BOURGOGNE-FRANCHE-COMTE/BOURGOGNE-FRANCHE-COMTE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[2.837849,46.131435,7.1713247,48.408287]}]},"FEDER_AUVERGNE":{"href":"http://www.europe-en-auvergne.eu/","attribution":"Auvergne","logo":"https://wxs.ign.fr/static/logos/FEDER_AUVERGNE/FEDER_AUVERGNE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[2.0398402,44.60505,3.38408,45.49146]}]},"FEDER_PAYSDELALOIRE":{"href":"https://www.europe.paysdelaloire.fr/","attribution":"Pays-de-la-Loire","logo":"https://wxs.ign.fr/static/logos/FEDER_PAYSDELALOIRE/FEDER_PAYSDELALOIRE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[-2.457367,46.19304,0.951426,48.57609]}]},"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":13,"maxZoom":20,"constraint":[{"minZoom":19,"maxZoom":19,"bbox":[-63.160706,-21.401262,55.84643,51.11242]},{"bbox":[0.035491213,43.221077,6.0235267,49.696926]},{"minZoom":20,"maxZoom":20,"bbox":[0.035491213,43.221077,6.0235267,49.696926]},{"minZoom":13,"maxZoom":18,"bbox":[-178.18713,-21.401329,55.85611,51.11242]}]},"E-MEGALIS":{"href":"http://www.e-megalisbretagne.org//","attribution":"Syndicat mixte de coopération territoriale (e-Megalis)","logo":"https://wxs.ign.fr/static/logos/E-MEGALIS/E-MEGALIS.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[-3.7059498,47.971947,-1.8486879,48.99035]}]},"FEDER2":{"href":"http://www.europe-en-france.gouv.fr/","attribution":"Fonds européen de développement économique et régional","logo":"https://wxs.ign.fr/static/logos/FEDER2/FEDER2.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[1.3577043,48.824635,4.269964,50.37648]}]},"PREFECTURE_GUADELOUPE":{"href":"www.guadeloupe.pref.gouv.fr/","attribution":"guadeloupe","logo":"https://wxs.ign.fr/static/logos/PREFECTURE_GUADELOUPE/PREFECTURE_GUADELOUPE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[-61.82342,14.371942,-60.787838,16.521578]}]},"OCCITANIE":{"href":"https://www.laregion.fr/","attribution":"La Région Occitanie; Pyrénées - Méditerranée","logo":"https://wxs.ign.fr/static/logos/OCCITANIE/OCCITANIE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[2.2086434,48.805965,2.4859917,48.915382]}]},"RGD_SAVOIE":{"href":"http://www.rgd.fr","attribution":"Régie de Gestion de Données des Pays de Savoie (RGD 73-74)","logo":"https://wxs.ign.fr/static/logos/RGD_SAVOIE/RGD_SAVOIE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":19,"maxZoom":19,"bbox":[5.7759595,45.65335,7.0887337,46.438328]},{"minZoom":13,"maxZoom":18,"bbox":[5.5923314,45.017353,7.2323394,46.438328]}]},"CG45":{"href":"http://www.loiret.com","attribution":"Le conseil général du Loiret","logo":"https://wxs.ign.fr/static/logos/CG45/CG45.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[1.4883244,47.471867,3.1349874,48.354233]}]},"CRAIG":{"href":"http://www.craig.fr","attribution":"Centre Régional Auvergnat de l'Information Géographique (CRAIG)","logo":"https://wxs.ign.fr/static/logos/CRAIG/CRAIG.gif","minZoom":13,"maxZoom":20,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[2.0398402,44.60505,6.4295278,46.8038]},{"minZoom":20,"maxZoom":20,"bbox":[2.2243388,44.76621,2.7314367,45.11295]}]},"e-Megalis":{"href":"http://www.e-megalisbretagne.org//","attribution":"Syndicat mixte de coopération territoriale (e-Megalis)","logo":"https://wxs.ign.fr/static/logos/e-Megalis/e-Megalis.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[-5.1937118,47.23789,-0.98568505,48.980812]}]},"PPIGE":{"href":"http://www.ppige-npdc.fr/","attribution":"PPIGE","logo":"https://wxs.ign.fr/static/logos/PPIGE/PPIGE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[1.5212119,49.957302,4.2673664,51.090965]}]},"CG06":{"href":"http://www.cg06.fr","attribution":"Département Alpes Maritimes (06) en partenariat avec : Groupement Orthophoto 06 (NCA, Ville de Cannes, CARF, CASA,CG06, CA de Grasse) ","logo":"https://wxs.ign.fr/static/logos/CG06/CG06.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[6.6093955,43.44647,7.7436337,44.377018]}]},"MEGALIS-BRETAGNE":{"href":"https://www.megalisbretagne.org/","attribution":"Syndicat mixte Mégalis Bretagne","logo":"https://wxs.ign.fr/static/logos/MEGALIS-BRETAGNE/MEGALIS-BRETAGNE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[-5.2086344,47.591938,-3.3396015,48.808697]}]},"FEDER":{"href":"http://www.europe-en-france.gouv.fr/","attribution":"Fonds européen de développement économique et régional","logo":"https://wxs.ign.fr/static/logos/FEDER/FEDER.gif","minZoom":0,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[-1.9662633,42.316307,8.25674,50.18387]},{"minZoom":0,"maxZoom":12,"bbox":[-2.400665,41.333557,9.560094,50.366302]}]},"LANGUEDOC-ROUSSILLON":{"href":"https://www.laregion.fr/","attribution":"Région Occitanie","logo":"https://wxs.ign.fr/static/logos/LANGUEDOC-ROUSSILLON/LANGUEDOC-ROUSSILLON.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[1.6784439,42.63972,4.208843,43.979004]}]},"GRAND_EST":{"href":"https://www.grandest.fr/","attribution":"Hauts-de-France","logo":"https://wxs.ign.fr/static/logos/GRAND_EST/GRAND_EST.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[5.362788,47.390827,7.6924667,49.58011]}]},"CNES_AUVERGNE":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES_AUVERGNE/CNES_AUVERGNE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[2.2656832,45.279934,4.0227704,46.8038]}]},"HAUTS_DE_FRANCE":{"href":"https://www.hautsdefrance.fr/","attribution":"Hauts-de-France","logo":"https://wxs.ign.fr/static/logos/HAUTS_DE_FRANCE/HAUTS_DE_FRANCE.gif","minZoom":13,"maxZoom":19,"constraint":[{"minZoom":13,"maxZoom":19,"bbox":[2.0740242,48.81521,4.3390365,51.11242]}]},"MPM":{"href":"http://www.marseille-provence.com/","attribution":"Marseille Provence Métropole","logo":"https://wxs.ign.fr/static/logos/MPM/MPM.gif","minZoom":20,"maxZoom":20,"constraint":[{"minZoom":20,"maxZoom":20,"bbox":[5.076959,43.153347,5.7168245,43.454994]}]},"DITTT":{"href":"http://www.dittt.gouv.nc/portal/page/portal/dittt/","attribution":"Direction des Infrastructures, de la Topographie et des Transports Terrestres","logo":"https://wxs.ign.fr/static/logos/DITTT/DITTT.gif","minZoom":13,"maxZoom":18,"constraint":[{"minZoom":13,"maxZoom":18,"bbox":[163.47784,-22.767689,167.94624,-19.434975]}]},"CNES_978":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES_978/CNES_978.gif","minZoom":13,"maxZoom":18,"constraint":[{"minZoom":13,"maxZoom":18,"bbox":[-63.160706,18.04345,-62.962185,18.133898]}]},"CNES_ALSACE":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES_ALSACE/CNES_ALSACE.gif","minZoom":13,"maxZoom":18,"constraint":[{"minZoom":13,"maxZoom":18,"bbox":[6.8086324,47.39981,7.668318,48.32695]}]},"CNES_974":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES_974/CNES_974.gif","minZoom":13,"maxZoom":18,"constraint":[{"minZoom":13,"maxZoom":18,"bbox":[55.205757,-21.401262,55.84643,-20.862825]}]},"CNES_975":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES_975/CNES_975.gif","minZoom":13,"maxZoom":18,"constraint":[{"minZoom":13,"maxZoom":18,"bbox":[-56.410988,46.734093,-56.10308,47.149963]}]},"CNES_976":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES_976/CNES_976.gif","minZoom":13,"maxZoom":18,"constraint":[{"minZoom":13,"maxZoom":18,"bbox":[44.916977,-13.089187,45.30442,-12.564543]}]},"CNES_977":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES_977/CNES_977.gif","minZoom":13,"maxZoom":18,"constraint":[{"minZoom":13,"maxZoom":18,"bbox":[-62.952805,17.862621,-62.78276,17.98024]}]},"CNES":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES/CNES.gif","minZoom":13,"maxZoom":16,"constraint":[{"minZoom":13,"maxZoom":16,"bbox":[-55.01953,1.845384,-50.88867,6.053161]}]},"ASTRIUM":{"href":"http://www.geo-airbusds.com/","attribution":"Airbus Defence and Space","logo":"https://wxs.ign.fr/static/logos/ASTRIUM/ASTRIUM.gif","minZoom":13,"maxZoom":16,"constraint":[{"minZoom":13,"maxZoom":16,"bbox":[-55.01953,1.845384,-50.88867,6.053161]}]},"CNES_971":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES_971/CNES_971.gif","minZoom":13,"maxZoom":18,"constraint":[{"minZoom":13,"maxZoom":18,"bbox":[-61.82342,15.819616,-60.99497,16.521578]}]},"CNES_972":{"href":"http://www.cnes.fr/","attribution":"Centre national d'études spatiales (CNES)","logo":"https://wxs.ign.fr/static/logos/CNES_972/CNES_972.gif","minZoom":13,"maxZoom":18,"constraint":[{"minZoom":13,"maxZoom":18,"bbox":[-61.247208,14.371855,-60.778458,14.899901]}]}}}, // Deprecated "GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN-EXPRESS.STANDARD": {"server":"https://wxs.ign.fr/geoportail/wmts","layer":"GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN-EXPRESS.STANDARD","title":"Carte IGN","format":"image/jpeg","style":"normal","queryable":false,"tilematrix":"PM","minZoom":0,"maxZoom":18,"bbox":[-179.62723,-84.5047,179.74588,85.47958],"desc":"Cartographie topographique multi-échelles du territoire français issue des bases de données vecteur de l’IGN - emprise nationale, visible du 1/200 au 1/130000000","originators":{"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":0,"maxZoom":18,"constraint":[{"minZoom":5,"maxZoom":5,"bbox":[-179.57285,-83.84196,178.4975,85.36646]},{"minZoom":0,"maxZoom":2,"bbox":[-175.99709,-84.42859,175.99709,84.2865]},{"minZoom":3,"maxZoom":3,"bbox":[-176.23093,-84.5047,179.08267,84.89126]},{"minZoom":4,"maxZoom":4,"bbox":[-179.62723,-84.0159,-179.21112,85.47958]},{"minZoom":6,"maxZoom":8,"bbox":[-179.49689,-84.02368,179.74588,85.30035]},{"minZoom":15,"maxZoom":18,"bbox":[-5.6663494,41.209736,10.819784,51.175068]},{"minZoom":14,"maxZoom":14,"bbox":[-5.713191,40.852314,11.429714,51.44377]},{"minZoom":13,"maxZoom":13,"bbox":[-63.37252,13.428586,11.429714,51.44377]},{"minZoom":11,"maxZoom":12,"bbox":[-63.37252,13.428586,11.496459,51.444122]},{"minZoom":9,"maxZoom":9,"bbox":[-64.81273,13.428586,11.496459,51.444016]},{"minZoom":10,"maxZoom":10,"bbox":[-63.37252,13.428586,11.496459,51.444016]}]}}}, // Need API key "GEOGRAPHICALGRIDSYSTEMS.MAPS": {"server":"https://wxs.ign.fr/geoportail/wmts","layer":"GEOGRAPHICALGRIDSYSTEMS.MAPS","title":"Cartes IGN","format":"image/jpeg","style":"normal","queryable":true,"tilematrix":"PM","minZoom":0,"maxZoom":18,"bbox":[-180,-75,180,80],"desc":"Cartes IGN","originators":{"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":0,"maxZoom":18,"constraint":[{"minZoom":7,"maxZoom":7,"bbox":[-178.20573,-68.138855,144.84375,51.909786]},{"minZoom":8,"maxZoom":8,"bbox":[-178.20573,-68.138855,168.24327,51.909786]},{"minZoom":13,"maxZoom":13,"bbox":[-178.20573,-67.101425,168.24327,51.44377]},{"minZoom":14,"maxZoom":14,"bbox":[-178.20573,-67.101425,168.23909,51.44377]},{"minZoom":11,"maxZoom":12,"bbox":[-178.20573,-67.101425,168.24327,51.444122]},{"minZoom":9,"maxZoom":10,"bbox":[-178.20573,-68.138855,168.24327,51.444016]},{"minZoom":15,"maxZoom":15,"bbox":[-178.20573,-46.502903,168.23909,51.175068]},{"minZoom":16,"maxZoom":16,"bbox":[-178.20573,-46.502903,168.29811,51.175068]},{"minZoom":0,"maxZoom":6,"bbox":[-180,-60,180,80]},{"minZoom":18,"maxZoom":18,"bbox":[-5.6663494,41.209736,10.819784,51.175068]},{"minZoom":17,"maxZoom":17,"bbox":[-179.5,-75,179.5,75]}]},"DITTT":{"href":"http://www.dittt.gouv.nc/portal/page/portal/dittt/","attribution":"Direction des Infrastructures, de la Topographie et des Transports Terrestres","logo":"https://wxs.ign.fr/static/logos/DITTT/DITTT.gif","minZoom":8,"maxZoom":16,"constraint":[{"minZoom":8,"maxZoom":10,"bbox":[163.47784,-22.972307,168.24327,-19.402702]},{"minZoom":11,"maxZoom":13,"bbox":[163.47784,-22.972307,168.24327,-19.494438]},{"minZoom":14,"maxZoom":15,"bbox":[163.47784,-22.764496,168.23909,-19.493542]},{"minZoom":16,"maxZoom":16,"bbox":[163.47784,-22.809465,168.29811,-19.403923]}]}}}, // Other layers "GEOGRAPHICALGRIDSYSTEMS.SLOPES.MOUNTAIN": {"key":"altimetrie","server":"https://wxs.ign.fr/geoportail/wmts","layer":"GEOGRAPHICALGRIDSYSTEMS.SLOPES.MOUNTAIN","title":"Carte des pentes","format":"image/png","style":"normal","queryable":false,"tilematrix":"PM","minZoom":0,"maxZoom":17,"bbox":[-63.161392,-21.544624,56.001812,51.099052],"desc":"Carte des zones ayant une valeur de pente supérieure à 30°-35°-40°-45° d'après la BD ALTI au pas de 5m","originators":{"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":0,"maxZoom":17,"constraint":[{"minZoom":0,"maxZoom":17,"bbox":[-5.1504726,41.32521,9.570543,51.099052]}]}}}, "ADMINEXPRESS_COG_2018": {"server":"https://wxs.ign.fr/geoportail/wmts","layer":"ADMINEXPRESS_COG_2018","title":"ADMINEXPRESS_COG (2018)","format":"image/png","style":"normal","queryable":true,"tilematrix":"PM","minZoom":6,"maxZoom":16,"bbox":[-63.37252,-21.475586,55.925865,51.31212],"desc":"Limites administratives COG mises à jour en continu. État en Mai 2018.","originators":{"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":6,"maxZoom":16,"constraint":[{"minZoom":6,"maxZoom":16,"bbox":[-63.37252,-21.475586,55.925865,51.31212]}]}}}, "ELEVATION.SLOPES": {"key":"altimetrie","server":"https://wxs.ign.fr/geoportail/wmts","layer":"ELEVATION.SLOPES","title":"Altitude","format":"image/jpeg","style":"normal","queryable":true,"tilematrix":"PM","minZoom":6,"maxZoom":14,"bbox":[-178.20589,-22.595179,167.43176,50.93085],"desc":"La couche altitude se compose d'un MNT (Modèle Numérique de Terrain) affiché en teintes hypsométriques et issu de la BD ALTI®.","originators":{"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":6,"maxZoom":14,"constraint":[{"minZoom":6,"maxZoom":14,"bbox":[55.205746,-21.392344,55.846554,-20.86271]}]}}}, "GEOGRAPHICALGRIDSYSTEMS.MAPS.BDUNI.J1": { "key":"cartes", "server":"https://wxs.ign.fr/geoportail/wmts","layer":"GEOGRAPHICALGRIDSYSTEMS.MAPS.BDUNI.J1","title":"Plan IGN j+1","format":"image/png","style":"normal","queryable":false,"tilematrix":"PM","minZoom":0,"maxZoom":18,"bbox":[-179.5,-75,179.5,75],"desc":"Plan IGN j+1","originators":{"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":0,"maxZoom":18,"constraint":[{"minZoom":0,"maxZoom":18,"bbox":[-179,-80,179,80]}]}}}, "TRANSPORTNETWORKS.ROADS": {"server":"https://wxs.ign.fr/geoportail/wmts","layer":"TRANSPORTNETWORKS.ROADS","title":"Routes","format":"image/png","style":"normal","queryable":false,"tilematrix":"PM","minZoom":6,"maxZoom":18,"bbox":[-63.969162,-21.49687,55.964417,71.584076],"desc":"Affichage du réseau routier français et européen.","originators":{"IGN":{"href":"http://www.ign.fr","attribution":"Institut national de l'information géographique et forestière","logo":"https://wxs.ign.fr/static/logos/IGN/IGN.gif","minZoom":6,"maxZoom":18,"constraint":[{"minZoom":15,"maxZoom":18,"bbox":[-63.37252,-21.475586,55.925865,51.31212]},{"minZoom":6,"maxZoom":14,"bbox":[-63.969162,-21.49687,55.964417,71.584076]}]}}}, }; /** Register new layer capability * @param {string} layer layer name * @param {*} capability */ ol.layer.Geoportail.register = function(layer, capability) { ol.layer.Geoportail.capabilities[layer] = capability; }; /** Check if a layer registered with a key? * @param {string} layer layer name * @returns {boolean} */ ol.layer.Geoportail.isRegistered = function(layer) { return ol.layer.Geoportail.capabilities[layer] && ol.layer.Geoportail.capabilities[layer].key; }; /** Load capabilities from the service * @param {string} gppKey the API key to get capabilities for * @return {*} Promise-like response */ ol.layer.Geoportail.loadCapabilities = function(gppKey, all) { var onSuccess = function() {} var onError = function() {} var onFinally = function() {}; this.getCapabilities(gppKey,all).then(function(c) { ol.layer.Geoportail.capabilities = c; onSuccess(c); }).catch(function(e) { onError(e); }).finally(function(c) { onFinally(c); }); var response = { then: function (callback) { if (typeof(callback)==='function') onSuccess = callback; return response; }, catch: function (callback) { if (typeof(callback)==='function') onError = callback; return response; }, finally: function (callback) { if (typeof(callback)==='function') onFinally = callback; return response; } } return response; }; /** Get Key capabilities * @param {string} gppKey the API key to get capabilities for * @return {*} Promise-like response */ ol.layer.Geoportail.getCapabilities = function(gppKey) { var capabilities = {}; var onSuccess = function() {} var onError = function() {} var onFinally = function() {} var geopresolutions = [156543.03390625,78271.516953125,39135.7584765625,19567.87923828125,9783.939619140625,4891.9698095703125,2445.9849047851562,1222.9924523925781,611.4962261962891,305.74811309814453,152.87405654907226,76.43702827453613,38.218514137268066,19.109257068634033,9.554628534317017,4.777314267158508,2.388657133579254,1.194328566789627,0.5971642833948135,0.29858214169740677,0.14929107084870338]; // Transform resolution to zoom function getZoom(res) { res = Number(res) * 0.000281; for (var r=0; rgeopresolutions[r]) return r; } // Merge constraints function mergeConstraints(ori) { for (var i=ori.constraint.length-1; i>0; i--) { for (var j=0; j$/, '$1') }; service.originators = {}; var origin = l.getElementsByTagName('gpp:Originator'); for (var k=0, o; o=origin[k]; k++) { var ori = service.originators[o.attributes['name'].value] = { href: o.getElementsByTagName('gpp:URL')[0].innerHTML, attribution: o.getElementsByTagName('gpp:Attribution')[0].innerHTML, logo: o.getElementsByTagName('gpp:Logo')[0].innerHTML, minZoom: 20, maxZoom: 0, constraint: [] }; // Scale contraints var constraint = o.getElementsByTagName('gpp:Constraint'); for (var j=0, c; c=constraint[j]; j++) { var zmax = getZoom(c.getElementsByTagName('sld:MinScaleDenominator')[0].innerHTML); var zmin = getZoom(c.getElementsByTagName('sld:MaxScaleDenominator')[0].innerHTML); if (zmin > ori.maxZoom) ori.maxZoom = zmin; if (zmin < ori.minZoom) ori.minZoom = zmin; if (zmax>ori.maxZoom) ori.maxZoom = zmax; if (zmax} list of preview url * @api */ ol.layer.Base.prototype.getPreview = function(lonlat, resolution, projection) { if (this.get("preview")) return [ this.get("preview") ]; if (!resolution) resolution = 150; // Get middle resolution if (resolution < this.getMinResolution() || resolution > this.getMaxResolution()) { var rmin = this.getMinResolution(), rmax = this.getMaxResolution(); if (rmax>100000) rmax = 156543; // min zoom : world if (rmin<0.15) rmin = 0.15; // max zoom resolution = rmax; while (rmax>rmin) { rmin *= 2; rmax /= 2; resolution = rmin; } } var e = this.getExtent(); if (!lonlat) lonlat = [21020, 6355964]; // Default lonlat if (e && !ol.extent.containsCoordinate(e,lonlat)) lonlat = [ (e[0]+e[2])/2, (e[1]+e[3])/2 ]; if (projection) lonlat = ol.proj.transform (lonlat, projection, this.getSource().getProjection()); if (this.getSource && this.getSource()) { return [ this.getSource().getPreview(lonlat, resolution) ]; } return []; }; /** * Return a preview for the layer. * @param {_ol_coordinate_|undefined} lonlat The center of the preview. * @param {number} resolution of the preview. * @return {Array} list of preview url * @api */ ol.layer.Group.prototype.getPreview = function(lonlat, resolution) { if (this.get("preview")) return [ this.get("preview") ]; var t = []; if (this.getLayers) { var l = this.getLayers().getArray(); for (var i=0; i this.get('maxResolution')) return; this.res_ = res*400; if (this.animate_) { var elapsed = e.frameState.time - this.animate_; if (elapsed < this.animateDuration_) { this.elapsedRatio_ = this.easing_(elapsed / this.animateDuration_); // tell OL3 to continue postcompose animation e.frameState.animate = true; } else { this.animate_ = false; this.height_ = this.toHeight_ } } var ratio = e.frameState.pixelRatio; var ctx = e.context; var m = this.matrix_ = e.frameState.coordinateToPixelTransform; // Old version (matrix) if (!m) { m = e.frameState.coordinateToPixelMatrix, m[2] = m[4]; m[3] = m[5]; m[4] = m[12]; m[5] = m[13]; } this.center_ = [ ctx.canvas.width/2/ratio, ctx.canvas.height/ratio ]; var f = this.layer_.getSource().getFeaturesInExtent(e.frameState.extent); ctx.save(); ctx.scale(ratio,ratio); var s = this.getStyle(); ctx.lineWidth = s.getStroke().getWidth(); ctx.strokeStyle = ol.color.asString(s.getStroke().getColor()); ctx.fillStyle = ol.color.asString(s.getFill().getColor()); var builds = []; for (var i=0; i this.animateDuration_) { this.animate_ = false; } return !!this.animate_; } /** Get feature height * @param {ol.Feature} f */ ol.render3D.prototype.getFeatureHeight = function (f) { if (this.animate_) { var h1 = this.height_(f); var h2 = this.toHeight_(f); return (h1*(1-this.elapsedRatio_)+this.elapsedRatio_*h2); } else return this.height_(f); }; /** Get elevation line * @private */ ol.render3D.prototype.hvector_ = function (pt, h) { var p0 = [ pt[0]*this.matrix_[0] + pt[1]*this.matrix_[1] + this.matrix_[4], pt[0]*this.matrix_[2] + pt[1]*this.matrix_[3] + this.matrix_[5] ]; return { p0: p0, p1: [ p0[0] + h/this.res_ * (p0[0]-this.center_[0]), p0[1] + h/this.res_ * (p0[1]-this.center_[1]) ] }; }; /** Get drawing * @private */ ol.render3D.prototype.getFeature3D_ = function (f, h) { var geom = this.get('geometry')(f); var c = geom.getCoordinates(); switch (geom.getType()) { case "Polygon": c = [c]; // fallthrough case "MultiPolygon": var build = []; for (var i=0; i=0; k--) { ctx.lineTo(b[k].p1[0], b[k].p1[1]); } } ctx.closePath(); } ctx.fill("evenodd"); ctx.stroke(); break; } case "Point": { b = build[i]; var t = b.feature.get('label'); if (t) { var p = b.geom.p1; var f = ctx.fillStyle; ctx.fillStyle = ctx.strokeStyle; ctx.textAlign = 'center'; ctx.textBaseline = 'bottom'; ctx.fillText ( t, p[0], p[1] ); var m = ctx.measureText(t); var h = Number (ctx.font.match(/\d+(\.\d+)?/g).join([])); ctx.fillStyle = "rgba(255,255,255,0.5)"; ctx.fillRect (p[0]-m.width/2 -5, p[1]-h -5, m.width +10, h +10) ctx.strokeRect (p[0]-m.width/2 -5, p[1]-h -5, m.width +10, h +10) ctx.fillStyle = f; //console.log(build[i].feature.getProperties()) } break; } default: break; } } }; /** * @private */ ol.render3D.prototype.drawGhost3D_ = function(ctx, build) { var i,j, b, k; // Construct for (i=0; i} options.style Drawing style * @param {ol.style.Style|Array} options.sketchStyle Sketch style */ ol.layer.SketchOverlay = function(options) { options = options || {}; var style = options.style || ol.style.Style.defaultStyle(true); var sketchStyle = options.sketchStyle; if (!sketchStyle) { sketchStyle = ol.style.Style.defaultStyle(); sketchStyle = [ new ol.style.Style({ image: new ol.style.RegularShape ({ points: 4, radius: 10, radius2: 0, stroke: new ol.style.Stroke({ color: [255,255,255, .5], width: 3 }) }) }), sketchStyle[0].clone() ]; sketchStyle[1].setImage(new ol.style.RegularShape ({ points: 4, radius: 10, radius2: 0, stroke: new ol.style.Stroke({ color: [0, 153, 255, 1], width: 1.25 }) })); } this._geom = []; ol.layer.Vector.call (this, { name: 'sketch', source: new ol.source.Vector({ useSpatialIndex: false }), style: function(f) { return (f.get('sketch') ? sketchStyle : style); }, updateWhileAnimating: true, updateWhileInteracting: true }); // Sketch features this.getSource().addFeatures([ new ol.Feature({ sketch: true, geometry: new ol.geom.Point([]) }), new ol.Feature({ sketch: true, geometry: new ol.geom.LineString([]) }), new ol.Feature(), new ol.Feature(new ol.geom.Point([])) ]); this.setGeometryType(options.type); }; ol.ext.inherits (ol.layer.SketchOverlay, ol.layer.Vector); /** Set geometry type * @param {string} type Geometry type * @return {string} the current type */ ol.layer.SketchOverlay.prototype.setGeometryType = function(type) { var t = /^Point$|^LineString$|^Polygon$|^Circle$/.test(type) ? type : 'LineString'; if (t !== this._type) { this.abortDrawing(); this._type = t; } return this._type; }; /** Get geometry type * @return {string} Geometry type */ ol.layer.SketchOverlay.prototype.getGeometryType = function() { return this._type; }; /** Add a new Point to the sketch * @param {ol.coordinate} coord * @return {boolean} true if point has been added, false if same coord */ ol.layer.SketchOverlay.prototype.addPoint = function(coord) { if (this._lastCoord !== this._position) { if (!this._geom.length) { this.startDrawing(); } this._geom.push(coord); this._lastCoord = coord; this._position = coord; this.drawSketch(); if (this.getGeometryType() === 'Point') { this.finishDrawing(); } if (this.getGeometryType() === 'Circle' && this._geom.length>=2) { this.finishDrawing(); } return true; } return false; }; /** Remove the last Point from the sketch */ ol.layer.SketchOverlay.prototype.removeLastPoint = function() { this._geom.pop(); this._lastCoord = this._geom[this._geom.length-1]; this.drawSketch(); }; /** Strat a new drawing * @param {*} options * @param {string} type Geometry type, default the current type * @param {Array} coordinates a list of coordinates to extend * @param {ol.Feature} feature a feature to extend (LineString or Polygon only) * @param {boolean} atstart extent coordinates or feature at start, default false (extend at end) */ ol.layer.SketchOverlay.prototype.startDrawing = function(options) { options = options || {}; this._geom = []; if (options.type) this.setGeometryType(options.type); this.drawSketch(); if (!this._drawing) { this.dispatchEvent({ type: 'drawstart', feature: this.getFeature() }); } this._drawing = true; }; /** Finish drawing * @return {ol.Feature} the drawed feature */ ol.layer.SketchOverlay.prototype.finishDrawing = function(valid) { var f = this.getSource().getFeatures()[2].clone(); var isvalid = !!f; switch (this.getGeometryType()) { case 'Circle': case 'LineString': { isvalid = this._geom.length > 1; break; } case 'Polygon': { isvalid = this._geom.length > 2; break; } } if (valid && !isvalid) return false; this._geom = []; this._lastCoord = null; this.drawSketch(); if (this._drawing) { this.dispatchEvent({ type: 'drawend', valid: isvalid, feature: f }); } this._drawing = false return f; }; /** Abort drawing */ ol.layer.SketchOverlay.prototype.abortDrawing = function() { if (this._drawing) { this.dispatchEvent({ type: 'drawabort', feature: this.getFeature() }); } this._drawing = false; this._geom = []; this._lastCoord = null; this.drawSketch(); }; /** Set current position * @param {ol.coordinate} coord */ ol.layer.SketchOverlay.prototype.setPosition = function(coord) { this._position = coord; this.drawLink(); }; /** Get current position * @return {ol.coordinate} */ ol.layer.SketchOverlay.prototype.getPosition = function() { return this._position; }; /** Draw/refresh link */ ol.layer.SketchOverlay.prototype.drawLink = function() { var features = this.getSource().getFeatures(); if (this._position) { if (this._lastCoord && this._lastCoord === this._position) { features[0].getGeometry().setCoordinates([]); } else { features[0].getGeometry().setCoordinates(this._position); } if (this._geom.length) { if (this.getGeometryType()==='Circle') { features[1].setGeometry(new ol.geom.Circle(this._geom[0], ol.coordinate.dist2d(this._geom[0], this._position))); } else if (this.getGeometryType()==='Polygon') { features[1].setGeometry(new ol.geom.LineString([ this._lastCoord, this._position, this._geom[0] ])); } else { features[1].setGeometry(new ol.geom.LineString([ this._lastCoord, this._position ])); } } else { features[1].setGeometry(new ol.geom.LineString([])); } } else { features[0].getGeometry().setCoordinates([]); features[1].setGeometry(new ol.geom.LineString([])); } }; /** Get current feature */ ol.layer.SketchOverlay.prototype.getFeature = function() { return this.getSource().getFeatures()[2]; }; /** Draw/refresh sketch */ ol.layer.SketchOverlay.prototype.drawSketch = function() { this.drawLink(); var features = this.getSource().getFeatures(); if (!this._geom.length) { features[2].setGeometry(null); features[3].setGeometry(new ol.geom.Point([])); } else { if (!this._lastCoord) this._lastCoord = this._geom[this._geom.length-1]; features[3].getGeometry().setCoordinates(this._lastCoord); switch (this._type) { case 'Point': { features[2].setGeometry(new ol.geom.Point(this._lastCoord)); break; } case 'Circle': { if (!features[2].getGeometry()) { features[2].setGeometry(new ol.geom.Circle(this._geom[0], ol.coordinate.dist2d(this._geom[0], this._geom[this._geom.length-1]))); } else { features[2].getGeometry().setRadius(ol.coordinate.dist2d(this._geom[0], this._geom[this._geom.length-1])); } break; } case 'LineString': { if (!features[2].getGeometry()) { features[2].setGeometry(new ol.geom.LineString(this._geom)); } else { features[2].getGeometry().setCoordinates(this._geom); } break; } case 'Polygon': { this._geom.push(this._geom[0]); if (!features[2].getGeometry()) { features[2].setGeometry(new ol.geom.Polygon([this._geom])); } else { features[2].getGeometry().setCoordinates([this._geom]); } this._geom.pop(); break; } default: { console.error('[ol/layer/SketchOverlay~drawSketch] geometry type not supported ('+this._type+')'); break; } } } }; /* 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). */ /** A map with a perspective * @constructor * @extends {ol.Map} * @fires change:perspective * @param {olx.MapOptions=} options * @param {ol.events.condition} tiltCondition , default altKeyOnly */ ol.PerspectiveMap = function(options) { // Map div var divMap = options.target instanceof Element ? options.target : document.getElementById(options.target); if (window.getComputedStyle(divMap).position !== 'absolute') { divMap.style.position = 'relative'; } divMap.style.overflow = 'hidden'; // Create map inside var map = ol.ext.element.create('DIV', { className: 'ol-perspective-map', parent: divMap }); var opts = {}; Object.assign(opts, options); opts.target = map; // enhance pixel ratio //opts.pixelRatio = 2; ol.Map.call (this, opts); this._tiltCondition = options.tiltCondition || ol.events.condition.altKeyOnly; }; ol.ext.inherits (ol.PerspectiveMap, ol.Map); /** Get pixel ratio for the map */ ol.PerspectiveMap.prototype.getPixelRatio = function(){ return window.devicePixelRatio; }; /** Set perspective angle * @param {number} angle the perspective angle 0 (vertical) - 30 (max), default 0 * @param {*} options * @param {number} options.duration The duration of the animation in milliseconds, default 500 * @param {function} options.easing The easing function used during the animation, defaults to ol.easing.inAndOut). */ ol.PerspectiveMap.prototype.setPerspective = function(angle, options) { options = options || {}; // max angle if (angle > 30) angle = 30; else if (angle<0) angle = 0; var fromAngle = this._angle || 0; var toAngle = Math.round(angle*10)/10; var style = this.getTarget().querySelector('.ol-layers').style; cancelAnimationFrame(this._animatedPerspective) requestAnimationFrame(function(t) { this._animatePerpective(t, t, style, fromAngle, toAngle, options.duration, options.easing||ol.easing.inAndOut); }.bind(this)) }; /** Animate the perspective * @param {number} t0 starting timestamp * @param {number} t current timestamp * @param {CSSStyleDeclaration} style style to modify * @param {number} fromAngle starting angle * @param {number} toAngle ending angle * @param {number} duration The duration of the animation in milliseconds, default 500 * @param {function} easing The easing function used during the animation, defaults to ol.easing.inAndOut). * @private */ ol.PerspectiveMap.prototype._animatePerpective = function(t0, t, style, fromAngle, toAngle, duration, easing ) { var dt, end; if (duration === 0) { dt = 1; end = true; } else { dt = (t-t0)/(duration||500); end = (dt>=1); } dt = easing(dt); var angle; if (end) { angle = this._angle = toAngle; } else { angle = this._angle = fromAngle + (toAngle-fromAngle)*dt; } var fac = angle/30; // apply transform to the style style.transform = 'translateY(-'+(17*fac)+'%) perspective(200px) rotateX('+angle+'deg) scaleY('+(1-fac/2)+')'; this.getMatrix3D(true); this.render(); if (!end) { requestAnimationFrame(function(t) { this._animatePerpective(t0, t, style, fromAngle, toAngle, duration||500, easing||ol.easing.inAndOut); }.bind(this)) } // Dispatch event this.dispatchEvent({ type: 'change:perspective', angle: angle, animating: !end }); }; /** Convert to pixel coord according to the perspective * @param {MapBrowserEvent} mapBrowserEvent The event to handle. */ ol.PerspectiveMap.prototype.handleMapBrowserEvent = function(e) { e.pixel = [ e.originalEvent.offsetX / this.getPixelRatio(), e.originalEvent.offsetY / this.getPixelRatio() ]; e.coordinate = this.getCoordinateFromPixel(e.pixel); ol.Map.prototype.handleMapBrowserEvent.call (this, e); // Change perspective on tilt condition if (this._tiltCondition(e)) { switch(e.type) { case 'pointerdown': { this._dragging = e.originalEvent.offsetY; break; } case 'pointerup': { this._dragging = false; break; } case 'pointerdrag': { if (this._dragging !== false) { var angle = e.originalEvent.offsetY > this._dragging ? .5 : -.5; if (angle) { this.setPerspective((this._angle||0) + angle, { duration: 0 }); } this._dragging = e.originalEvent.offsetY; } break; } } } else { this._dragging = false; } }; /** Get map full teansform matrix3D * @return {Array>} */ ol.PerspectiveMap.prototype.getMatrix3D = function (compute) { if (compute) { var ele = this.getTarget().querySelector('.ol-layers'); // Get transform matrix3D from CSS var tx = ol.matrix3D.getTransform(ele); // Get the CSS transform origin from the transformed parent - default is '50% 50%' var txOrigin = ol.matrix3D.getTransformOrigin(ele); // Compute the full transform that is applied to the transformed parent (-origin * tx * origin) this._matrixTransform = ol.matrix3D.computeTransformMatrix(tx, txOrigin); } if (!this._matrixTransform) this._matrixTransform = ol.matrix3D.identity(); return this._matrixTransform; }; /** Get pixel at screen from coordinate. * The default getPixelFromCoordinate get pixel in the perspective. * @param {ol.coordinate} coord * @param {ol.pixel} */ ol.PerspectiveMap.prototype.getPixelScreenFromCoordinate = function (coord) { // Get pixel in the transform system var px = this.getPixelFromCoordinate(coord); // Get transform matrix3D from CSS var fullTx = this.getMatrix3D(); // Transform the point using full transform var pixel = ol.matrix3D.transformVertex(fullTx, px); // Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component). pixel = ol.matrix3D.projectVertex(pixel); return [pixel[0], pixel[1]]; }; /** Not working... * */ ol.PerspectiveMap.prototype.getPixelFromPixelScreen = function (px) { // Get transform matrix3D from CSS var fullTx = ol.matrix3D.inverse(this.getMatrix3D()); // Transform the point using full transform var pixel = ol.matrix3D.transformVertex(fullTx, px); // Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component). pixel = ol.matrix3D.projectVertex(pixel); return [pixel[0], pixel[1]]; }; /* Overwrited Overlay function to handle overlay positing in a perspective map */ (function() { var _updatePixelPosition = ol.Overlay.prototype.updatePixelPosition; /** Update pixel projection in a perspective map (apply projection to the position) * @private */ ol.Overlay.prototype.updatePixelPosition = function () { var map = this.getMap(); if (map && map instanceof ol.PerspectiveMap) { var position = this.getPosition(); if (!map || !map.isRendered() || !position) { this.setVisible(false); return; } // Get pixel at screen var pixel = map.getPixelScreenFromCoordinate(position); var mapSize = map.getSize(); pixel[0] -= mapSize[0]/4 pixel[1] -= mapSize[1]/4 /* for ol v6.2.x // Offset according positioning var pos = this.getPositioning(); if (/bottom/.test(pos)) { pixel[1] += mapSize[1]/4 } else { pixel[1] -= mapSize[1]/4 } if (/right/.test(pos)) { pixel[0] += mapSize[0]/4 } else { pixel[0] -= mapSize[0]/4 } */ // Update this.updateRenderedPosition(pixel , mapSize); } else { _updatePixelPosition.call(this); } }; /**/ })(); /* Copyright (c) 2020 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /*global ol*/ if (window.ol && !ol.particule) { ol.particule = {}; } /** Abstract base class; normally only used for creating subclasses. * An object with coordinates, draw and update * @constructor * @extends {ol.Object} * @param {*} options * @param {ol.Overlay} options.overlay * @param {ol.pixel} coordinate the position of the particule */ ol.particule.Base = function(options) { if (!options) options = {}; ol.Object.call(this); this.setOverlay(options.overlay) ; this.coordinate = options.coordinate || [0,0]; }; ol.ext.inherits(ol.particule.Base, ol.Object); /** Set the particule overlay * @param {ol.Overlay} overl */ ol.particule.Base.prototype.setOverlay = function(overlay) { this._overlay = overlay; }; /** Get the particule overlay * @return {ol.Overlay} */ ol.particule.Base.prototype.getOverlay = function() { return this._overlay; }; /** Draw the particule * @param { CanvasRenderingContext2D } ctx */ ol.particule.Base.prototype.draw = function(/* ctx */) { }; /** Update the particule * @param {number} dt timelapes since last call */ ol.particule.Base.prototype.update = function(/* dt */) { }; /** Update the particule * @param {number} dt timelapes since last call */ ol.particule.Base.prototype.getRandomCoord = function(dt) { if (this.getOverlay().randomCoord) return this.getOverlay().randomCoord(); else return [dt,0]; }; /* Copyright (c) 2020 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** A cloud particule to display clouds over the map * @constructor * @extends {ol.particule.Base} * @param {*} options * @param {ol.Overlay} options.overlay * @param {ol.pixel} coordinate the position of the particule */ ol.particule.Bird = function(options) { if (!options) options = {}; ol.particule.Base.call(this, options); this.bird = new Image(); this.bird.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAABDCAQAAAD+S8VaAAAAAnNCSVQICFXsRgQAAAAJcEhZcwAAAvMAAALzAdLpCioAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAG90lEQVR42uWaaVRTRxTHJ4AUUIoIiqISOArIDiIhBBfccCMoR0vVUpXjTqun4Fr1tO5i3YuodaFqRMECKm4VUHEhUvWgBYuilgpiRVZpCARI3r8fWA4hYY9AXu77+ObNzO/O8u787xDSyeYSTzSICpu+DjV0ogrze84PBneByuJv3JpbBlx6MEBbJfG/8S2RAACFXXtU0gERN1BjCc9UEN/e7I2w1gFPinv3UDkHbFiGOqOwJVjlHMALRT3LLJ7trGIOuHwFUsY7q2IOuJ0u7YB//pswWFn6/vnUcbOCAn7ctfnUrsijl85dv5pw786fd9OTsvg5/JykN3fTb6ZcTDgVvefIkqXmVvKr0NN/IUQDO7C1qwJrOwyftIZ7cmIiN21eZlB+SOUtFKNl9kF0hb9ujmyVM73FMmWv3m+2J4zxw74NDN5/5vT1qzeT7j3n5/Bz7mcmPk24cy32Ai8i9Pj2nwIX+jo4kc8UMMqeXr5bfC6N/2tUHrdsCQ4gAR/QNhNRJ8+6GklXH7xStlxW+ViLxrpjqBswJ/z4rYyCFrQnwJPCxGe/x53i+fO+XOth2xpsvQm+PkfGP3YuYIo1oInTyIJiLDFtoZfUP+AXeaW2rZHXKZ8xJ35NeU+1odVSbIIBbEQeb70Tffd6ckmj0QbDy9/zOufdILE6SN0TBkVafnn0ka/NatrrditDXpmYKw36pREwPyr+Y0V72n0CsxoedTDFrMJJyRMDZJYIx8+yYICQKbDJtcjtL9IGAcEMKN7efIy+snnTYv/tR8Ry3+eWRUYFzavRB9SWL7icXKWAVrPRr96wEqjBTjg5bop03GGi77XF85FdqVZNIQ1konOsEvx35yOCN1xMFimszjNSDqh+ektGfVG3xjyTzaqkX3uDTiaCdh0ZA/qSgWXWWfb7CYMQQsiUUANK1j8hoJf1lSFUg0u+z1xCiFuMUYWsAy7QCj9ZzhIgIDCkpi4nhBCGsafNGx2peXCQRvhlcGrEAQSOhYQQQtyTG74YCglN8CswrVF8goEVhBBCrMzdozi33OOHJmvUvQqghQtKMEUu+GDB0Cj2Q/vsUdJn0JH8+oXG4rWS46djSD0ePcr2lUuafbZlIbN0UAnngpyA0I3FumeZxxQYVlZ/ooWleKm0+FHQbTDuWnAp5F6cbNfskcDtcg9J9aMGNUxDIiglgy+CPxhypj4Ddu/cfFpxOrIqrv7QAsH4V2nwYxoEvwQEOpRlAeeG07hWnopH7FMHgTr6VmhAA1xEQNjF4bMxQwpcj2I9duVZLiVtTb7YT7T2I30JccyqrrA7ZuESRF0SvhQ/QKfByDu/VZAs5O6rXS9U6onZ+A2CLgQvwWn0l5n4TAFnjOKksR5En6i73q6/q3IRhvwugB8LBylwi6IhixxX9Wd/CoWQwTrJTuaEOSwzENcKDR7Yj4xOg4+Hq3SEXzX8fIfcObAZPizV+bGxqLZhMyxBWgdP+xi4ScGbCNnhhrodqxnrso65pLidNxMQENihqoPgS3AY5rU7krh35eCPbon2c4hap2nnxob2GQQE+zpAM4qFb53EoUWxE3t93jXyBwyXcG1KD+8/IXwBAmFYg26Vx37oHjnIlnQlGzbJvMCX+lQrPgT6dat9yAcT/S6aSOIs2rjjxLaQ9SsX83gv8uShiNuAn4mR9fZ5dizpphRpREvj1YvOhiU84OdmoghFyKH47y/GHohtLf45ITvVuLyfyKLI5RlntyJSXx2+P+gaejt5O7FNCSEkcFHTuAmPom6/qqxJqFRee33wHGc6rVLjXtym8C8nTTcnDNMh/n5BfnN8mFY18jWdbPlceeBViEsPi16xxFSL7ncjukVelTvxUzsxjOlAUzsULv8/GfdEJa7G7D7YWLCcUzbNkfb42zaXNaG2h4XTHH/n9x+bjIHKqeAdNMZf55fbrKBYLNq+lqb433lkFrUk5hNKdu6mIf5XA1KetzibR+09TLcfonrMtVYlNKk9h2gV//FCW3tCFmMXT0nOe83bxpklbdDJqrD+BC1mwUzTtOw2Sl/UFjpsh8ci2pHirFgxV8nxV/oJxO2RwR6+HNFbmfkZ15PaqwQe/VmJ+R18Aql37XTAsQ9EefUBW6NeEk34IaWN8HkIQk+Jva0SzwGXP6p1XDeEoqB1qx/L0B3dKY+VSr0JDurDFNaK2ZoYg5142sx1m3LEYxUsq+Vv8ejVSv8bdJ/UXySds9eDB4JwEnFIRS6KUIi/8RJxCEEARte74GBR6DycFpGgtZNFPkHrHgOx61miSaPDEOtEn8qWwvepZMc5Mel3ItZmHbbM12wSXV/snMHZQ6eRlzEzI9d9rnftskwERhXVNxF7ik1Krd87pbLCbWYR9Y7v0f/htaJHbsoDhwAAAABJRU5ErkJggg=="; this.set('size', [this.bird.width || 50, this.bird.height || 50]); }; ol.ext.inherits(ol.particule.Bird, ol.particule.Base); /** Draw the particule * @param {CanvasRenderingContext2D } ctx */ ol.particule.Bird.prototype.draw = function(ctx) { //ctx.drawImage(this.bird, this.coordinate[0], this.coordinate[1]); var angle = this.getOverlay().get('angle'); ctx.save(); ctx.translate (this.coordinate[0], this.coordinate[1]); ctx.rotate(angle+Math.PI/2); ctx.scale(.5,.5); ctx.drawImage( this.bird, -this.bird.width/2, -this.bird.height/2 ); ctx.restore(); }; /** Update the particule * @param {number} dt timelapes since last call */ ol.particule.Bird.prototype.update = function(dt) { var speed = this.getOverlay().get('speed') * dt / this.getOverlay()._fps; var angle = this.getOverlay().get('angle'); this.coordinate[0] += speed * Math.cos(angle); this.coordinate[1] += speed * Math.sin(angle); }; /* Copyright (c) 2020 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** A cloud particule to display clouds over the map * @constructor * @extends {ol.particule.Base} * @param {*} options * @param {ol.Overlay} options.overlay * @param {ol.pixel} coordinate the position of the particule */ ol.particule.Cloud = function(options) { if (!options) options = {}; ol.particule.Base.call(this, options); this.set('size', [100,100]) var canvas = document.createElement('CANVAS'); canvas.width = 200; canvas.height = 200; var ctx = canvas.getContext('2d'); var grd = this.gradient = ctx.createRadialGradient(50,50, 0, 50,50, 50); grd.addColorStop(0, 'rgba(255,255,255,.2'); grd.addColorStop(1, 'rgba(255,255,255,0'); // Create cloud image this.image = canvas; for (var k=0; k<7; k++) { ctx.save(); var x = Math.random()*100; var y = Math.random()*100; ctx.translate (x,y); ctx.fillStyle = grd; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.restore(); } }; ol.ext.inherits(ol.particule.Cloud, ol.particule.Base); /** Draw the particule * @param {CanvasRenderingContext2D } ctx */ ol.particule.Cloud.prototype.draw = function(ctx) { ctx.save(); ctx.translate (this.coordinate[0], this.coordinate[1]); ctx.drawImage(this.image, -this.image.width/2, -this.image.width/2); ctx.restore(); }; /** Update the particule * @param {number} dt timelapes since last call */ ol.particule.Cloud.prototype.update = function(dt) { var speed = this.getOverlay().get('speed') * dt / this.getOverlay()._fps; var angle = this.getOverlay().get('angle'); this.coordinate[0] += speed * Math.cos(angle); this.coordinate[1] += speed * Math.sin(angle); }; /* Copyright (c) 2020 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Rain particules to display clouds over the map * @constructor * @extends {ol.particule.Base} * @param {*} options * @param {ol.Overlay} options.overlay * @param {ol.pixel} coordinate the position of the particule */ ol.particule.Rain = function(options) { if (!options) options = {}; ol.particule.Base.call(this, options); this.z = Math.floor(Math.random()*5) + 1; var canvas = document.createElement('CANVAS'); canvas.width = 50; canvas.height = 50; var ctx = canvas.getContext('2d'); this.gradient = ctx.createRadialGradient(0,0, 0, 0,0, 25); this.gradient.addColorStop(0, 'rgba(0,0,80,0)'); this.gradient.addColorStop(1, 'rgba(0,0,80,.3)'); }; ol.ext.inherits(ol.particule.Rain, ol.particule.Base); /** Draw the particule * @param {CanvasRenderingContext2D } ctx */ ol.particule.Rain.prototype.draw = function(ctx) { ctx.save(); var angle = this.getOverlay().get('angle'); ctx.beginPath(); var x1 = Math.cos(angle) * 10 * (1+this.z/2); var y1 = Math.sin(angle) * 10 * (1+this.z/2); ctx.lineWidth = Math.round(this.z/2); ctx.strokeStyle = this.gradient; ctx.translate (this.coordinate[0], this.coordinate[1]); ctx.moveTo(0,0); ctx.lineTo(x1, y1); ctx.stroke(); ctx.restore(); }; /** Update the particule * @param {number} dt timelapes since last call */ ol.particule.Rain.prototype.update = function(dt) { var dl = this.getOverlay().get('speed') * dt / this.getOverlay()._fps * this.z; var angle = this.getOverlay().get('angle'); this.coordinate[0] += dl * Math.cos(angle); this.coordinate[1] += dl * Math.sin(angle); }; /* Copyright (c) 2020 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Raindrop particules to display clouds over the map * @constructor * @extends {ol.particule.Base} * @param {*} options * @param {ol.Overlay} options.overlay * @param {ol.pixel} coordinate the position of the particule */ ol.particule.RainDrop = function(options) { if (!options) options = {}; ol.particule.Base.call(this, options); this.size = 0; // Drops var canvas = document.createElement('CANVAS'); canvas.width = 100; canvas.height = 100; var ctx = canvas.getContext('2d'); var grd = ctx.createRadialGradient(50,50, 0, 50,50, 50); grd.addColorStop(0, 'rgba(128,128,192,.8'); grd.addColorStop(1, 'rgba(128,128,192,0'); this.image = canvas; ctx.fillStyle = grd; ctx.fillRect(0,0,canvas.width,canvas.height); }; ol.ext.inherits(ol.particule.RainDrop, ol.particule.Base); /** Draw the particule * @param {CanvasRenderingContext2D } ctx */ ol.particule.RainDrop.prototype.draw = function(ctx) { if (this.size>0) { ctx.save(); ctx.translate (this.coordinate[0], this.coordinate[1]); ctx.globalAlpha = this.size/50; ctx.scale(1-this.size/50,1-this.size/50); ctx.drawImage(this.image, -50,-50); ctx.restore(); } }; /** Update the particule * @param {number} dt timelapes since last call */ ol.particule.RainDrop.prototype.update = function(dt) { if (this.size>0 || Math.random() < .01) { if (this.size<=0) { this.size = 50; this.coordinates = this.getRandomCoord(); } this.size = this.size - Math.round(dt/20); } }; /* Copyright (c) 2020 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Rain particules to display clouds over the map * @constructor * @extends {ol.particule.Base} * @param {*} options * @param {ol.Overlay} options.overlay * @param {ol.pixel} coordinate the position of the particule */ ol.particule.Snow = function(options) { if (!options) options = {}; ol.particule.Base.call(this, options); this.z = (Math.floor(Math.random()*5) + 1) / 5; this.angle = Math.random() * Math.PI; // Snow fakes var canvas = document.createElement('CANVAS'); canvas.width = 20; canvas.height = 20; var ctx = canvas.getContext('2d'); var grd = ctx.createRadialGradient(10,10, 0, 10,10, 10); grd.addColorStop(0, "rgba(255, 255, 255,1)"); // white grd.addColorStop(.8, "rgba(210, 236, 242,.8)"); // bluish grd.addColorStop(1, "rgba(237, 247, 249,0)"); // lighter bluish this.image = canvas; ctx.fillStyle = grd; ctx.fillRect(0,0,canvas.width,canvas.height); }; ol.ext.inherits(ol.particule.Snow, ol.particule.Base); /** Draw the particule * @param {CanvasRenderingContext2D } ctx */ ol.particule.Snow.prototype.draw = function(ctx) { ctx.save(); ctx.translate (this.coordinate[0], this.coordinate[1]); ctx.globalAlpha = .4 + this.z/2; ctx.scale(this.z,this.z); ctx.drawImage(this.image, -10,-10); ctx.restore(); }; /** Update the particule * @param {number} dt timelapes since last call */ ol.particule.Snow.prototype.update = function(dt) { var speed = this.getOverlay().get('speed') * dt / this.getOverlay()._fps * this.z * 5; var angle = this.getOverlay().get('angle'); this.angle = this.angle + dt / this.getOverlay()._fps / 100; this.coordinate[0] += Math.sin(this.angle + this.z) * 2 + speed * Math.cos(angle); this.coordinate[1] += Math.cos(this.angle) + speed * Math.sin(angle); }; /* 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). */ /** * @classdesc * A popup element to be displayed over the map and attached to a single map * location. The popup are customized using CSS. * * @example var popup = new ol.Overlay.Popup(); map.addOverlay(popup); popup.show(coordinate, "Hello!"); popup.hide(); * * @constructor * @extends {ol.Overlay} * @fires show * @fires hide * @param {} options Extend Overlay options * @param {String} [options.popupClass] the a class of the overlay to style the popup. * @param {boolean} [options.anim Animate=false] the popup the popup, default false. * @param {bool} [options.closeBox=false] popup has a close box, default false. * @param {function|undefined} [options.onclose] callback function when popup is closed * @param {function|undefined} [options.onshow] callback function when popup is shown * @param {Number|Array} [options.offsetBox] an offset box * @param {ol.OverlayPositioning | string | undefined} options.positioning * the 'auto' positioning var the popup choose its positioning to stay on the map. * @param {boolean} [options.minibar=false] add a mini vertical bar * @api stable */ ol.Overlay.Popup = function (options) { options = options || {}; if (typeof(options.offsetBox)==='number') this.offsetBox = [options.offsetBox,options.offsetBox,options.offsetBox,options.offsetBox]; else this.offsetBox = options.offsetBox; // Popup div var element = document.createElement("div"); //element.classList.add('ol-overlaycontainer-stopevent'); options.element = element; // Closebox this.closeBox = options.closeBox; this.onclose = options.onclose; this.onshow = options.onshow; ol.ext.element.create('BUTTON', { className: 'closeBox' + (options.closeBox ? ' hasclosebox':''), type: 'button', click: function() { this.hide(); }.bind(this), parent: element }); // Anchor div if (options.anchor!==false) { ol.ext.element.create('DIV', { className: 'anchor', parent: element }) } // Content this.content = ol.ext.element.create('DIV', { html: options.html || '', className: "ol-popup-content", parent: element }); if (options.minibar) { ol.ext.element.scrollDiv(this.content, { vertical: true, mousewheel: true, minibar: true }); } // Stop event if (options.stopEvent) { element.addEventListener("mousedown", function(e){ e.stopPropagation(); }); element.addEventListener("touchstart", function(e){ e.stopPropagation(); }); } ol.Overlay.call(this, options); this._elt = element; // call setPositioning first in constructor so getClassPositioning is called only once this.setPositioning(options.positioning || 'auto'); this.setPopupClass(options.popupClass || options.className || 'default'); if (options.anim) this.addPopupClass('anim'); // Show popup on timeout (for animation purposes) if (options.position) { setTimeout(function(){ this.show(options.position); }.bind(this)); } }; ol.ext.inherits(ol.Overlay.Popup, ol.Overlay); /** * Get CSS class of the popup according to its positioning. * @private */ ol.Overlay.Popup.prototype.getClassPositioning = function () { var c = ""; var pos = this.getPositioning(); if (/bottom/.test(pos)) c += "ol-popup-bottom "; if (/top/.test(pos)) c += "ol-popup-top "; if (/left/.test(pos)) c += "ol-popup-left "; if (/right/.test(pos)) c += "ol-popup-right "; if (/^center/.test(pos)) c += "ol-popup-middle "; if (/center$/.test(pos)) c += "ol-popup-center "; return c; }; /** * Set a close box to the popup. * @param {bool} b * @api stable */ ol.Overlay.Popup.prototype.setClosebox = function (b) { this.closeBox = b; if (b) this.element.classList.add("hasclosebox"); else this.element.classList.remove("hasclosebox"); }; /** * Set the CSS class of the popup. * @param {string} c class name. * @api stable */ ol.Overlay.Popup.prototype.setPopupClass = function (c) { var classes = ["ol-popup"]; if (this.getVisible()) classes.push('visible'); this.element.className = ""; var classesPositioning = this.getClassPositioning().split(' ') .filter(function(className) { return className.length > 0; }); if (c) { c.split(' ').filter(function(className) { return className.length > 0; }) .forEach(function(className) { classes.push(className); }); } else { classes.push("default"); } classesPositioning.forEach(function(className) { classes.push(className); }); if (this.closeBox) { classes.push("hasclosebox"); } this.element.classList.add.apply(this.element.classList, classes); }; /** * Add a CSS class to the popup. * @param {string} c class name. * @api stable */ ol.Overlay.Popup.prototype.addPopupClass = function (c) { this.element.classList.add(c); }; /** * Remove a CSS class to the popup. * @param {string} c class name. * @api stable */ ol.Overlay.Popup.prototype.removePopupClass = function (c) { this.element.classList.remove(c); }; /** * Set positionning of the popup * @param {ol.OverlayPositioning | string | undefined} pos an ol.OverlayPositioning * or 'auto' to var the popup choose the best position * @api stable */ ol.Overlay.Popup.prototype.setPositioning = function (pos) { if (pos === undefined) return; if (/auto/.test(pos)) { this.autoPositioning = pos.split('-'); if (this.autoPositioning.length==1) this.autoPositioning[1]="auto"; } else { this.autoPositioning = false; } pos = pos.replace(/auto/g,"center"); if (pos=="center") pos = "bottom-center"; this.setPositioning_(pos); }; /** @private * @param {ol.OverlayPositioning | string | undefined} pos */ ol.Overlay.Popup.prototype.setPositioning_ = function (pos) { if (this.element) { ol.Overlay.prototype.setPositioning.call(this, pos); this.element.classList.remove("ol-popup-top", "ol-popup-bottom", "ol-popup-left", "ol-popup-right", "ol-popup-center", "ol-popup-middle"); var classes = this.getClassPositioning().split(' ') .filter(function(className) { return className.length > 0; }); this.element.classList.add.apply(this.element.classList, classes); } }; /** Check if popup is visible * @return {boolean} */ ol.Overlay.Popup.prototype.getVisible = function () { return this.element.classList.contains("visible"); }; /** * Set the position and the content of the popup. * @param {ol.Coordinate|string} coordinate the coordinate of the popup or the HTML content. * @param {string|undefined} html the HTML content (undefined = previous content). * @example var popup = new ol.Overlay.Popup(); // Show popup popup.show([166000, 5992000], "Hello world!"); // Move popup at coord with the same info popup.show([167000, 5990000]); // set new info popup.show("New informations"); * @api stable */ ol.Overlay.Popup.prototype.show = function (coordinate, html) { if (!html && typeof(coordinate)=='string') { html = coordinate; coordinate = null; } if (coordinate===true) { coordinate = this.getPosition(); } var self = this; var map = this.getMap(); if (!map) return; if (html && html !== this.prevHTML) { // Prevent flickering effect this.prevHTML = html; this.content.innerHTML = ''; if (html instanceof Element) { this.content.appendChild(html); } else { ol.ext.element.create('DIV', { html: html, parent: this.content }) } // Refresh when loaded (img) Array.prototype.slice.call(this.content.querySelectorAll('img')) .forEach(function(image) { image.addEventListener('load', function() { try { map.renderSync(); } catch(e) { /* ok */ } self.content.dispatchEvent(new Event('scroll')); }); }); } if (coordinate) { // Auto positionning if (this.autoPositioning) { var p = map.getPixelFromCoordinate(coordinate); var s = map.getSize(); var pos=[]; if (this.autoPositioning[0]=='auto') { pos[0] = (p[1] this._particules.length) { for (var i=this._particules.length; i this._canvas.width +size[0]) { p.coordinate[0] = -size[0]; p.coordinate[1] = Math.random() * (this._canvas.height+size[1]) - size[1]/2; } else if (p.coordinate[1] < -size[1]) { p.coordinate[0] = Math.random() * (this._canvas.width+size[0]) - size[0]/2; p.coordinate[1] = this._canvas.height + size[1]; } else if (p.coordinate[1] > this._canvas.height +size[1]) { p.coordinate[0] = Math.random() * (this._canvas.width+size[0]) - size[0]/2; p.coordinate[1] = -size[1]; } }; /** Clear canvas */ ol.Overlay.AnimatedCanvas.prototype.clear = function() { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); }; /** Get overlay canvas * @return {CanvasElement} */ ol.Overlay.AnimatedCanvas.prototype.getCanvas = function() { return this._canvas; }; /** Set canvas animation * @param {boolean} anim, default true * @api */ ol.Overlay.AnimatedCanvas.prototype.setAnimation = function(anim) { anim = (anim!==false); this.set('animation', anim); if (anim) { this._pause = true; requestAnimationFrame(this._animate.bind(this)); } else { this.dispatchEvent({ type:'animation:stop', time: this._time }); } }; /** * @private */ ol.Overlay.AnimatedCanvas.prototype._animate = function(time) { if (this.getVisible() && this.get('animation')) { if (this._pause) { // reset time requestAnimationFrame(function(time){ this._time = time; requestAnimationFrame(this._animate.bind(this)); }.bind(this)); } else { // Test fps if (time - this._time > this._fps) { this.draw(time - this._time); this._time = time; } requestAnimationFrame(this._animate.bind(this)); } } this._pause = false; }; /* 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). */ /** * @classdesc * An overlay fixed on the map. * Use setPosition(coord, true) to force the overlay position otherwise the position will be ignored. * * @example var popup = new ol.Overlay.Fixed(); map.addOverlay(popup); popup.setposition(position, true); * * @constructor * @extends {ol.Overlay} * @param {} options Extend Overlay options * @api stable */ ol.Overlay.Fixed = function (options) { ol.Overlay.call(this, options); }; ol.ext.inherits(ol.Overlay.Fixed, ol.Overlay); /** Prevent modifying position and use a force argument to force positionning. * @param {ol.coordinate} position * @param {boolean} force true to change the position, default false */ ol.Overlay.Fixed.prototype.setPosition = function(position, force) { if (this.getMap() && position) { this._pixel = this.getMap().getPixelFromCoordinate(position); } ol.Overlay.prototype.setPosition.call(this, position) if (force) { ol.Overlay.prototype.updatePixelPosition.call(this); } }; /** Update position according the pixel position */ ol.Overlay.Fixed.prototype.updatePixelPosition = function() { if (this.getMap() && this._pixel && this.getPosition()) { var pixel = this.getMap().getPixelFromCoordinate(this.getPosition()) if (Math.round(pixel[0]*1000) !== Math.round(this._pixel[0]*1000) || Math.round(pixel[0]*1000) !== Math.round(this._pixel[0]*1000) ) { this.setPosition(this.getMap().getCoordinateFromPixel(this._pixel)); } } }; /* 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 popup element to be displayed over the map and attached to a single map * location. The popup are customized using CSS. * * @constructor * @extends {ol.Overlay.Popup} * @fires show * @fires hide * @param {} options Extend Overlay options * @param {String} options.popupClass the a class of the overlay to style the popup. * @param {ol.style.Style} options.style a style to style the link on the map. * @param {number} options.minScale min scale for the popup, default .5 * @param {number} options.maxScale max scale for the popup, default 2 * @param {bool} options.closeBox popup has a close box, default false. * @param {function|undefined} options.onclose: callback function when popup is closed * @param {function|undefined} options.onshow callback function when popup is shown * @param {Number|Array} options.offsetBox an offset box * @param {ol.OverlayPositioning | string | undefined} options.positioning * the 'auto' positioning var the popup choose its positioning to stay on the map. * @api stable */ ol.Overlay.FixedPopup = function (options) { options.anchor = false; options.positioning = options.positioning || 'center-center'; options.className = (options.className || '') + ' ol-fixPopup'; ol.Overlay.Popup.call(this, options); this.set('minScale', options.minScale || .5); this.set('maxScale', options.maxScale || 2); var canvas = document.createElement('canvas'); this._overlay = new ol.layer.Image({ source: new ol.source.ImageCanvas({ canvasFunction: function(extent, res, ratio, size) { canvas.width = size[0]; canvas.height = size[1]; return canvas } }) }); this._style = options.style || new ol.style.Style({ fill: new ol.style.Fill({ color: [102,153,255] }) }); this._overlay.on(['postcompose','postrender'], function(e) { if (this.getVisible() && this._pixel) { var map = this.getMap(); var position = this.getPosition(); var pixel = map.getPixelFromCoordinate(position); var r1 = this.element.getBoundingClientRect() var r2 = this.getMap().getTargetElement().getBoundingClientRect(); var pixel2 = [r1.left-r2.left+r1.width/2, r1.top-r2.top+r1.height/2] e.context.save(); var tr = e.inversePixelTransform; if (tr) { e.context.transform(tr[0],tr[1],tr[2],tr[3],tr[4],tr[5]); } else { // ol ~ v5.3.0 e.context.scale(e.frameState.pixelRatio,e.frameState.pixelRatio) } e.context.beginPath(); e.context.moveTo(pixel[0], pixel[1]); if (Math.abs(pixel2[0]-pixel[0]) > Math.abs(pixel2[1]-pixel[1])) { e.context.lineTo(pixel2[0],pixel2[1]-8); e.context.lineTo(pixel2[0],pixel2[1]+8); } else { e.context.lineTo(pixel2[0]-8,pixel2[1]); e.context.lineTo(pixel2[0]+8,pixel2[1]); } e.context.moveTo(pixel[0], pixel[1]); if (this._style.getFill()) { e.context.fillStyle = ol.color.asString(this._style.getFill().getColor()); e.context.fill(); } if (this._style.getStroke()) { e.context.strokeStyle = ol.color.asString(this._style.getStroke().getColor()); e.context.lineWidth = this._style.getStroke().width(); e.context.stroke(); } e.context.restore(); } }.bind(this)); var update = function() { this.setPixelPosition(); }.bind(this); this.on(['hide', 'show'], function() { setTimeout(update) }.bind(this)) // Get events centroid function centroid(pevents) { var clientX = 0; var clientY = 0; var length = 0; for (var i in pevents) { clientX += pevents[i].clientX; clientY += pevents[i].clientY; length++; } return [clientX / length, clientY / length]; } // Get events angle function angle() { var p1,p2, v = Object.keys(pointerEvents); if (v.length<2) return false; p1 = pointerEvents[v[0]]; p2 = pointerEvents[v[1]]; var v1 = [p2.clientX - p1.clientX, p2.clientY - p1.clientY]; p1 = pointerEvents2[v[0]]; p2 = pointerEvents2[v[1]]; var v2 = [p2.clientX - p1.clientX, p2.clientY - p1.clientY]; var d1 = Math.sqrt(v1[0]*v1[0]+v1[1]*v1[1]); var d2 = Math.sqrt(v2[0]*v2[0]+v2[1]*v2[1]); var a = Math.acos((v1[0]*v2[0]+v1[1]*v2[1]) / (d1*d2)) * 360 / Math.PI; if (v1[0]*v2[1]-v1[1]*v2[0] < 0) return -a; else return a; } // Get distance beetween events function distance(pevents) { var v = Object.keys(pevents); if (v.length<2) return false; return ol.coordinate.dist2d([pevents[v[0]].clientX, pevents[v[0]].clientY], [pevents[v[1]].clientX, pevents[v[1]].clientY]); } // Handle popup move var pointerEvents = {}; var pointerEvents2 = {}; var pixelPosition = []; var distIni, rotIni, scaleIni, move; // down this.element.addEventListener('pointerdown', function(e) { e.preventDefault(); e.stopPropagation(); // Reset events to this position for (var i in pointerEvents) { if (pointerEvents2[i]) { pointerEvents[i] = pointerEvents2[i]; } } pointerEvents[e.pointerId] = e; pixelPosition = this._pixel; rotIni = this.get('rotation') || 0; scaleIni = this.get('scale') || 1; distIni = distance(pointerEvents); move = false; }.bind(this)); // Prevent click when move this.element.addEventListener('click', function(e) { if (move) { e.preventDefault(); e.stopPropagation(); } }, true); // up / cancel var removePointer = function(e) { if (pointerEvents[e.pointerId]) { delete pointerEvents[e.pointerId]; e.preventDefault(); } if (pointerEvents2[e.pointerId]) { delete pointerEvents2[e.pointerId]; } /* Simulate a second touch pointer * / if (e.metaKey || e.ctrlKey) { pointerEvents['touch'] = e; pointerEvents2['touch'] = e; } else { delete pointerEvents['touch']; delete pointerEvents2['touch']; } /**/ }.bind(this); document.addEventListener('pointerup', removePointer); document.addEventListener('pointercancel', removePointer); // move document.addEventListener('pointermove', function(e) { if (pointerEvents[e.pointerId]) { e.preventDefault(); pointerEvents2[e.pointerId] = e; var c1 = centroid(pointerEvents); var c2 = centroid(pointerEvents2); var dx = c2[0] - c1[0]; var dy = c2[1] - c1[1]; move = move || Math.abs(dx) > 3 || Math.abs(dy) > 3; var a = angle(); if (a) { this.setRotation(rotIni + a*1.5, false); } var d = distance(pointerEvents2); if (d!==false && distIni) { this.setScale(scaleIni * d / distIni, false); distIni = scaleIni * d / this.get('scale'); } this.setPixelPosition([pixelPosition[0]+dx, pixelPosition[1]+dy]); } }.bind(this)); }; ol.ext.inherits(ol.Overlay.FixedPopup, ol.Overlay.Popup); /** * Set the map instance the control is associated with * and add its controls associated to this map. * @param {_ol_Map_} map The map instance. */ ol.Overlay.FixedPopup.prototype.setMap = function (map) { ol.Overlay.Popup.prototype.setMap.call(this, map); this._overlay.setMap(this.getMap()); if (this._listener) { ol.Observable.unByKey(this._listener); } if (map) { // Force popup inside the viewport this._listener = map.on('change:size', function() { this.setPixelPosition(); }.bind(this)) } }; /** Update pixel position * @return {boolean} * @private */ ol.Overlay.FixedPopup.prototype.updatePixelPosition = function () { var map = this.getMap(); var position = this.getPosition(); if (!map || !map.isRendered() || !position) { this.setVisible(false); return; } if (!this._pixel) { this._pixel = map.getPixelFromCoordinate(position); var mapSize = map.getSize(); this.updateRenderedPosition(this._pixel, mapSize); } else { this.setVisible(true); } }; /** updateRenderedPosition * @private */ ol.Overlay.FixedPopup.prototype.updateRenderedPosition = function (pixel, mapsize) { ol.Overlay.Popup.prototype.updateRenderedPosition.call(this, pixel, mapsize); this.setRotation(); this.setScale() }; /** Set pixel position * @param {ol.pixel} pix * @param {string} position top/bottom/middle-left/right/center */ ol.Overlay.FixedPopup.prototype.setPixelPosition = function (pix, position) { var r, map = this.getMap(); var mapSize = map ? map.getSize() : [0,0]; if (position) { this.setPositioning(position); r = ol.ext.element.offsetRect(this.element); r.width = r.height = 0; if (/top/.test(position)) pix[1] += r.height/2; else if (/bottom/.test(position)) pix[1] = mapSize[1] - r.height/2 - pix[1]; else pix[1] = mapSize[1]/2 + pix[1]; if (/left/.test(position)) pix[0] += r.width/2; else if (/right/.test(position)) pix[0] = mapSize[0] - r.width/2 - pix[0]; else pix[0] = mapSize[0]/2 + pix[0]; } if (pix) this._pixel = pix; if (map && this._pixel) { this.updateRenderedPosition(this._pixel, mapSize); // Prevent outside var outside = false; r = ol.ext.element.offsetRect(this.element); var rmap = ol.ext.element.offsetRect(map.getTargetElement()); if (r.left < rmap.left) { this._pixel[0] = this._pixel[0] + rmap.left - r.left; outside = true; } else if (r.left + r.width > rmap.left + rmap.width) { this._pixel[0] = this._pixel[0] + rmap.left - r.left + rmap.width - r.width; outside = true; } if (r.top < rmap.top) { this._pixel[1] = this._pixel[1] + rmap.top - r.top; outside = true; } else if (r.top + r.height > rmap.top + rmap.height) { this._pixel[1] = this._pixel[1] + rmap.top - r.top + rmap.height - r.height; outside = true; } if (outside) this.updateRenderedPosition(this._pixel, mapSize); this._overlay.changed(); } }; /** Set pixel position * @returns {ol.pixel} */ ol.Overlay.FixedPopup.prototype.getPixelPosition = function () { return this._pixel; }; /** * Set the CSS class of the popup. * @param {string} c class name. * @api stable */ ol.Overlay.FixedPopup.prototype.setPopupClass = function (c) { ol.Overlay.Popup.prototype.setPopupClass.call(this, c); this.addPopupClass('ol-fixPopup'); }; /** Set poppup rotation * @param {number} angle * @param {booelan} update update popup, default true * @api */ ol.Overlay.FixedPopup.prototype.setRotation = function (angle, update) { if (typeof(angle) === 'number') this.set('rotation', angle); if (update!==false) { if (/rotate/.test(this.element.style.transform)) { this.element.style.transform = this.element.style.transform.replace(/rotate\((-?[\d,.]+)deg\)/,'rotate('+(this.get('rotation')||0)+'deg)') } else { this.element.style.transform = this.element.style.transform + ' rotate('+(this.get('rotation')||0)+'deg)'; } } }; /** Set poppup scale * @param {number} scale * @param {booelan} update update popup, default true * @api */ ol.Overlay.FixedPopup.prototype.setScale = function (scale, update) { if (typeof(scale) === 'number') this.set('scale', scale); scale = Math.min(Math.max(this.get('minScale')||0, this.get('scale')||1 ), this.get('maxScale')||2); this.set('scale', scale); if (update!==false) { if (/scale/.test(this.element.style.transform)) { this.element.style.transform = this.element.style.transform.replace(/scale\(([\d,.]+)\)/,'scale('+(scale)+')') } else { this.element.style.transform = this.element.style.transform + ' scale('+(scale)+')'; } } }; /** Set link style * @param {ol.style.Style} style */ ol.Overlay.FixedPopup.prototype.setLinkStyle = function (style) { this._style = style; this._overlay.changed(); }; /** Get link style * @return {ol.style.Style} style */ ol.Overlay.FixedPopup.prototype.getLinkStyle = function () { return this._style; }; /* 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). */ /** * @classdesc * The Magnify overlay add a "magnifying glass" effect to an OL3 map that displays * a portion of the map in a different zoom (and actually display different content). * * @constructor * @extends {ol.Overlay} * @param {olx.OverlayOptions} options Overlay options * @api stable */ ol.Overlay.Magnify = function (options) { var elt = document.createElement("div"); elt.className = "ol-magnify"; this._elt = elt; ol.Overlay.call(this, { positioning: options.positioning || "center-center", element: this._elt, stopEvent: false }); // Create magnify map this.mgmap_ = new ol.Map({ controls: new ol.Collection(), interactions: new ol.Collection(), target: options.target || this._elt, view: new ol.View({ projection: options.projection }), layers: options.layers }); this.mgview_ = this.mgmap_.getView(); this.external_ = options.target?true:false; this.set("zoomOffset", options.zoomOffset||1); this.set("active", true); this.on("propertychange", this.setView_.bind(this)); }; ol.ext.inherits(ol.Overlay.Magnify, ol.Overlay); /** * Set the map instance the overlay is associated with. * @param {ol.Map} map The map instance. */ ol.Overlay.Magnify.prototype.setMap = function(map) { if (this.getMap()) { this.getMap().getViewport().removeEventListener("mousemove", this.onMouseMove_); } if (this._listener) ol.Observable.unByKey(this._listener); this._listener = null; ol.Overlay.prototype.setMap.call(this, map); map.getViewport().addEventListener("mousemove", this.onMouseMove_.bind(this)); this._listener = map.getView().on('propertychange', this.setView_.bind(this)); this.setView_(); }; /** Get the magnifier map * @return {_ol_Map_} */ ol.Overlay.Magnify.prototype.getMagMap = function() { return this.mgmap_; }; /** Magnify is active * @return {boolean} */ ol.Overlay.Magnify.prototype.getActive = function() { return this.get("active"); }; /** Activate or deactivate * @param {boolean} active */ ol.Overlay.Magnify.prototype.setActive = function(active) { return this.set("active", active); }; /** Mouse move * @private */ ol.Overlay.Magnify.prototype.onMouseMove_ = function(e) { var self = this; if (!self.get("active")) { self.setPosition(); } else { var px = self.getMap().getEventCoordinate(e); if (!self.external_) self.setPosition(px); self.mgview_.setCenter(px); if (!self._elt.querySelector('canvas') || self._elt.querySelector('canvas').style.display =="none") self.mgmap_.updateSize(); } }; /** View has changed * @private */ ol.Overlay.Magnify.prototype.setView_ = function(e) { if (!this.get("active")) { this.setPosition(); return; } if (!e) { // refresh all this.setView_({key:'rotation'}); this.setView_({key:'resolution'}); return; } // Set the view params switch (e.key) { case 'rotation': this.mgview_.setRotation(this.getMap().getView().getRotation()); break; case 'zoomOffset': case 'resolution': { var z = Math.max(0,this.getMap().getView().getZoom()+Number(this.get("zoomOffset"))); this.mgview_.setZoom(z); break; } default: break; } }; /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * @classdesc * A placemark element to be displayed over the map and attached to a single map * location. The placemarks are customized using CSS. * * @example var popup = new ol.Overlay.Placemark(); map.addOverlay(popup); popup.show(coordinate); popup.hide(); * * @constructor * @extends {ol.Overlay} * @param {} options Extend ol/Overlay/Popup options * @param {String} options.color placemark color * @param {String} options.backgroundColor placemark color * @param {String} options.contentColor placemark color * @param {Number} options.radius placemark radius in pixel * @param {String} options.popupClass the a class of the overlay to style the popup. * @param {function|undefined} options.onclose: callback function when popup is closed * @param {function|undefined} options.onshow callback function when popup is shown * @api stable */ ol.Overlay.Placemark = function (options) { options = options || {}; options.popupClass = (options.popupClass || '') + ' placemark anim' options.positioning = 'bottom-center', ol.Overlay.Popup.call(this, options); this.setPositioning = function(){}; if (options.color) this.element.style.color = options.color; if (options.backgroundColor ) this.element.style.backgroundColor = options.backgroundColor ; if (options.contentColor ) this.setContentColor(options.contentColor); if (options.size) this.setRadius(options.size); }; ol.ext.inherits(ol.Overlay.Placemark, ol.Overlay.Popup); /** * Set the position and the content of the placemark (hide it before to enable animation). * @param {ol.Coordinate|string} coordinate the coordinate of the popup or the HTML content. * @param {string|undefined} html the HTML content (undefined = previous content). */ ol.Overlay.Placemark.prototype.show = function(coordinate, html) { if (coordinate===true) { coordinate = this.getPosition(); } this.hide(); ol.Overlay.Popup.prototype.show.apply(this, [coordinate, html]); }; /** * Set the placemark color. * @param {string} color */ ol.Overlay.Placemark.prototype.setColor = function(color) { this.element.style.color = color; }; /** * Set the placemark background color. * @param {string} color */ ol.Overlay.Placemark.prototype.setBackgroundColor = function(color) { this._elt.style.backgroundColor = color; }; /** * Set the placemark content color. * @param {string} color */ ol.Overlay.Placemark.prototype.setContentColor = function(color) { var c = this.element.getElementsByClassName('ol-popup-content')[0]; if (c) c.style.color = color; }; /** * Set the placemark class. * @param {string} name */ ol.Overlay.Placemark.prototype.setClassName = function(name) { var oldclass = this.element.className; this.element.className = 'ol-popup placemark ol-popup-bottom ol-popup-center ' + (/visible/.test(oldclass) ? 'visible ' : '') + (/anim/.test(oldclass) ? 'anim ' : '') + name; }; /** * Set the placemark radius. * @param {number} size size in pixel */ ol.Overlay.Placemark.prototype.setRadius = function(size) { this.element.style.fontSize = size + 'px'; }; /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Template attributes for popup * @typedef {Object} TemplateAttributes * @property {string} title * @property {function} format a function that takes an attribute and a feature and returns the formated attribute * @property {string} before string to instert before the attribute (prefix) * @property {string} after string to instert after the attribute (sudfix) * @property {boolean|function} visible boolean or a function (feature, value) that decides the visibility of a attribute entry */ /** Template * @typedef {Object} Template * @property {string|function} title title of the popup, attribute name or a function that takes a feature and returns the title * @property {Object.} attributes a list of template attributes */ /** * A popup element to be displayed on a feature. * * @constructor * @extends {ol.Overlay.Popup} * @fires show * @fires hide * @fires select * @param {} options Extend Popup options * @param {String} options.popupClass the a class of the overlay to style the popup. * @param {bool} options.closeBox popup has a close box, default false. * @param {function|undefined} options.onclose: callback function when popup is closed * @param {function|undefined} options.onshow callback function when popup is shown * @param {Number|Array} options.offsetBox an offset box * @param {ol.OverlayPositioning | string | undefined} options.positionning * the 'auto' positioning var the popup choose its positioning to stay on the map. * @param {Template|function} options.template A template with a list of properties to use in the popup or a function that takes a feature and returns a Template * @param {ol.interaction.Select} options.select a select interaction to get features from * @param {boolean} options.keepSelection keep original selection, otherwise set selection to the current popup feature and add a counter to change current feature, default false * @param {boolean} options.canFix Enable popup to be fixed, default false * @param {boolean} options.showImage display image url as image, default false * @param {boolean} options.maxChar max char to display in a cell, default 200 * @api stable */ ol.Overlay.PopupFeature = function (options) { options = options || {}; ol.Overlay.Popup.call(this, options); this.setTemplate(options.template); this.set('canFix', options.canFix) this.set('showImage', options.showImage) this.set('maxChar', options.maxChar||200) this.set('keepSelection', options.keepSelection) // Bind with a select interaction if (options.select && (typeof options.select.on ==='function')) { this._select = options.select; options.select.on('select', function(e){ if (!this._noselect) { if (e.selected[0]) { this.show(e.mapBrowserEvent.coordinate, options.select.getFeatures().getArray(), e.selected[0]); } else { this.hide(); } } }.bind(this)); } }; ol.ext.inherits(ol.Overlay.PopupFeature, ol.Overlay.Popup); /** Set the template * @param {Template} template A template with a list of properties to use in the popup */ ol.Overlay.PopupFeature.prototype.setTemplate = function(template) { this._template = template; this._attributeObject(this._template); } /** * @private */ ol.Overlay.PopupFeature.prototype._attributeObject = function (temp) { if (temp && temp.attributes instanceof Array) { var att = {}; temp.attributes.forEach(function (a) { att[a] = true; }); temp.attributes = att; } return temp.attributes; }; /** Show the popup on the map * @param {ol.coordinate|undefined} coordinate Position of the popup * @param {ol.Feature|Array} features The features on the popup * @param {ol.Feature} current The current feature if keepSelection = true, otherwise get the first feature */ ol.Overlay.PopupFeature.prototype.show = function(coordinate, features, current) { if (coordinate instanceof ol.Feature || (coordinate instanceof Array && coordinate[0] instanceof ol.Feature)) { features = coordinate; coordinate = null; } if (!(features instanceof Array)) features = [features]; this._features = features.slice(); if (!this._count) this._count = 1; // Calculate html upon feaures attributes this._count = 1; var f = this.get('keepSelection') ? current || features[0] : features[0]; var html = this._getHtml(f); if (html) { if (!this.element.classList.contains('ol-fixed')) this.hide(); if (!coordinate || features[0].getGeometry().getType()==='Point') { coordinate = features[0].getGeometry().getFirstCoordinate(); } ol.Overlay.Popup.prototype.show.call(this, coordinate, html); } else { this.hide(); } }; /** * @private */ ol.Overlay.PopupFeature.prototype._getHtml = function(feature) { if (!feature) return ''; var html = ol.ext.element.create('DIV', { className: 'ol-popupfeature' }); if (this.get('canFix')) { ol.ext.element.create('I', { className:'ol-fix', parent: html }) .addEventListener('click', function(){ this.element.classList.toggle('ol-fixed'); }.bind(this)); } var template = this._template; // calculate template if (typeof(template) === 'function') { template = template(feature, this._count, this._features.length); } else if (!template || !template.attributes) { template = template || {}; template. attributes = {}; for (var i in feature.getProperties()) if (i!='geometry') { template.attributes[i] = i; } } // Display title if (template.title) { var title; if (typeof template.title === 'function') { title = template.title(feature); } else { title = feature.get(template.title); } ol.ext.element.create('H1', { html:title, parent: html }); } // Display properties in a table if (template.attributes) { var tr, table = ol.ext.element.create('TABLE', { parent: html }); var atts = this._attributeObject(template); var featureAtts = feature.getProperties(); for (var att in atts) { if (featureAtts.hasOwnProperty(att)) { var a = atts[att]; var content, val = featureAtts[att]; // Get calculated value if (typeof(a.format)==='function') { val = a.format(val, feature); } // Is entry visible? var visible = true; if (typeof(a.visible)==='boolean') { visible = a.visible; } else if (typeof(a.visible)==='function') { visible = a.visible(feature, val); } if (visible) { tr = ol.ext.element.create('TR', { parent: table }); ol.ext.element.create('TD', { html: a.title || att, parent: tr }); // Show image or content if (this.get('showImage') && /(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png)/.test(val)) { content = ol.ext.element.create('IMG',{ src: val }); } else { content = (a.before||'') + val + (a.after||''); var maxc = this.get('maxChar') || 200; if (typeof(content) === 'string' && content.length>maxc) content = content.substr(0,maxc)+'[...]'; } // Add value ol.ext.element.create('TD', { html: content, parent: tr }); } } } } // Zoom button ol.ext.element.create('BUTTON', { className: 'ol-zoombt', parent: html }) .addEventListener('click', function() { if (feature.getGeometry().getType()==='Point') { this.getMap().getView().animate({ center: feature.getGeometry().getFirstCoordinate(), zoom: Math.max(this.getMap().getView().getZoom(), 18) }); } else { var ext = feature.getGeometry().getExtent(); this.getMap().getView().fit(ext, { duration:1000 }); } }.bind(this)); // Counter if (!this.get('keepSelection') && this._features.length > 1) { var div = ol.ext.element.create('DIV', { className: 'ol-count', parent: html }); ol.ext.element.create('DIV', { className: 'ol-prev', parent: div, click: function() { this._count--; if (this._count<1) this._count = this._features.length; html = this._getHtml(this._features[this._count-1]); setTimeout(function() { ol.Overlay.Popup.prototype.show.call(this, this.getPosition(), html); }.bind(this), 350 ); }.bind(this) }); ol.ext.element.create('TEXT', { html:this._count+'/'+this._features.length, parent: div }); ol.ext.element.create('DIV', { className: 'ol-next', parent: div, click: function() { this._count++; if (this._count>this._features.length) this._count = 1; html = this._getHtml(this._features[this._count-1]); setTimeout(function() { ol.Overlay.Popup.prototype.show.call(this, this.getPosition(), html); }.bind(this), 350 ); }.bind(this) }); } // Use select interaction if (this._select && !this.get('keepSelection')) { this._noselect = true; this._select.getFeatures().clear(); this._select.getFeatures().push(feature); this._noselect = false; } this.dispatchEvent({ type: 'select', feature: feature, index: this._count }) return html; }; /** Fix the popup * @param {boolean} fix */ ol.Overlay.PopupFeature.prototype.setFix = function (fix) { if (fix) this.element.classList.add('ol-fixed'); else this.element.classList.remove('ol-fixed'); }; /** Is a popup fixed * @return {boolean} */ ol.Overlay.PopupFeature.prototype.getFix = function () { return this.element.classList.contains('ol-fixed'); }; /** Get a function to use as format to get local string for an attribute * if the attribute is a number: Number.toLocaleString() * if the attribute is a date: Date.toLocaleString() * otherwise the attibute itself * @param {string} locales string with a BCP 47 language tag, or an array of such strings * @param {*} options Number or Date toLocaleString options * @return {function} a function that takes an attribute and return the formated attribute */ ol.Overlay.PopupFeature.localString = function (locales , options) { return function (a) { if (a && a.toLocaleString) { return a.toLocaleString(locales , options); } else { // Try to get a date from a string var date = new Date(a); if (isNaN(date)) return a; else return date.toLocaleString(locales , options); } }; }; /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** A tooltip element to be displayed over the map and attached on the cursor position. * @constructor * @extends {ol.Overlay.Popup} * @param {} options Extend Popup options * @param {String} options.popupClass the a class of the overlay to style the popup. * @param {number} options.maximumFractionDigits maximum digits to display on measure, default 2 * @param {function} options.formatLength a function that takes a number and returns the formated value, default length in meter * @param {function} options.formatArea a function that takes a number and returns the formated value, default length in square-meter * @param {function} options.getHTML a function that takes a feature and the info string and return a formated info to display in the tooltip, default display feature measure & info * @param {Number|Array} options.offsetBox an offset box * @param {ol.OverlayPositioning | string | undefined} options.positionning * the 'auto' positioning var the popup choose its positioning to stay on the map. * @api stable */ ol.Overlay.Tooltip = function (options) { options = options || {}; options.popupClass = options.popupClass || options.className || 'tooltips black'; options.positioning = options.positioning || 'center-left'; options.stopEvent = !!(options.stopEvent); ol.Overlay.Popup.call(this, options); this.set('maximumFractionDigits', options.maximumFractionDigits||2); if (typeof(options.formatLength)==='function') this.formatLength = options.formatLength; if (typeof(options.formatArea)==='function') this.formatArea = options.formatArea; if (typeof(options.getHTML)==='function') this.getHTML = options.getHTML; this._interaction = new ol.interaction.Interaction({ handleEvent: function(e){ if (e.type==='pointermove' || e.type==='click') { var info = this.getHTML(this._feature, this.get('info')); if (info) { this.show(e.coordinate, info); } else this.hide(); this._coord = e.coordinate; } return true; }.bind(this) }); }; ol.ext.inherits(ol.Overlay.Tooltip, ol.Overlay.Popup); /** * Set the map instance the control is associated with * and add its controls associated to this map. * @param {_ol_Map_} map The map instance. */ ol.Overlay.Tooltip.prototype.setMap = function (map) { if (this.getMap()) this.getMap().removeInteraction(this._interaction); ol.Overlay.Popup.prototype.setMap.call(this, map); if (this.getMap()) this.getMap().addInteraction(this._interaction); }; /** Get the information to show in the tooltip * The area/length will be added if a feature is attached. * @param {ol.Feature|undefined} feature the feature * @param {string} info the info string * @api */ ol.Overlay.Tooltip.prototype.getHTML = function(feature, info) { if (this.get('measure')) return this.get('measure') + (info ? '
            '+ info : ''); else return info || ''; }; /** Set the Tooltip info * If information is not null it will be set with a delay, * thus watever the information is inserted, the significant information will be set. * ie. ttip.setInformation('ok'); ttip.setInformation(null); will set 'ok' * ttip.set('info','ok'); ttip.set('info', null); will set null * @param {string} what The information to display in the tooltip, default remove information */ ol.Overlay.Tooltip.prototype.setInfo = function(what) { if (!what) { this.set('info',''); this.hide(); } else setTimeout(function() { this.set('info', what); this.show(this._coord, this.get('info')); }.bind(this)); }; /** Remove the current featue attached to the tip * Similar to setFeature() with no argument */ ol.Overlay.Tooltip.prototype.removeFeature = function() { this.setFeature(); }; /** Format area to display in the popup. * Can be overwritten to display measure in a different unit (default: square-metter). * @param {number} area area in m2 * @return {string} the formated area * @api */ ol.Overlay.Tooltip.prototype.formatArea = function(area) { if (area > Math.pow(10,-1*this.get('maximumFractionDigits'))) { if (area>10000) { return (area/1000000).toLocaleString(undefined, {maximumFractionDigits: this.get('maximumFractionDigits)')}) + ' km²'; } else { return area.toLocaleString(undefined, {maximumFractionDigits: this.get('maximumFractionDigits')}) + ' m²'; } } else { return ''; } }; /** Format area to display in the popup * Can be overwritten to display measure in different unit (default: meter). * @param {number} length length in m * @return {string} the formated length * @api */ ol.Overlay.Tooltip.prototype.formatLength = function(length) { if (length > Math.pow(10,-1*this.get('maximumFractionDigits'))) { if (length>100) { return (length/1000).toLocaleString(undefined, {maximumFractionDigits: this.get('maximumFractionDigits')}) + ' km'; } else { return length.toLocaleString(undefined, {maximumFractionDigits: this.get('maximumFractionDigits')}) + ' m'; } } else { return ''; } }; /** Set a feature associated with the tooltips, measure info on the feature will be added in the tooltip * @param {ol.Feature|ol.Event} feature an ol.Feature or an event (object) with a feature property */ ol.Overlay.Tooltip.prototype.setFeature = function(feature) { // Handle event with a feature as property. if (feature && feature.feature) feature = feature.feature; // The feature this._feature = feature; if (this._listener) { this._listener.forEach(function(l) { ol.Observable.unByKey(l); }); } this._listener = []; this.set('measure', ''); if (feature) { this._listener.push(feature.getGeometry().on('change', function(e){ var geom = e.target; var measure; if (geom.getArea) { measure = this.formatArea(ol.sphere.getArea(geom, { projection: this.getMap().getView().getProjection() })); } else if (geom.getLength) { measure = this.formatLength(ol.sphere.getLength(geom, { projection: this.getMap().getView().getProjection() })); } this.set('measure', measure); }.bind(this))); } }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (http://www.cecill.info/). ol.coordinate.convexHull compute a convex hull using Andrew's Monotone Chain Algorithm. @see https://en.wikipedia.org/wiki/Convex_hull_algorithms */ ol.coordinate.convexHull; (function(){ /** Tests if a point is left or right of line (a,b). * @param {ol.coordinate} a point on the line * @param {ol.coordinate} b point on the line * @param {ol.coordinate} o * @return {bool} true if (a,b,o) turns clockwise */ var clockwise = function (a, b, o) { return ((a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) <= 0); }; /** Compute a convex hull using Andrew's Monotone Chain Algorithm * @param {Array} points an array of 2D points * @return {Array} the convex hull vertices */ ol.coordinate.convexHull = function (points) { // Sort by increasing x and then y coordinate var i; points.sort(function(a, b) { return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]; }); // Compute the lower hull var lower = []; for (i = 0; i < points.length; i++) { while (lower.length >= 2 && clockwise(lower[lower.length - 2], lower[lower.length - 1], points[i])) { lower.pop(); } lower.push(points[i]); } // Compute the upper hull var upper = []; for (i = points.length - 1; i >= 0; i--) { while (upper.length >= 2 && clockwise(upper[upper.length - 2], upper[upper.length - 1], points[i])) { upper.pop(); } upper.push(points[i]); } upper.pop(); lower.pop(); return lower.concat(upper); }; /* Get coordinates of a geometry */ var getCoordinates = function (geom) { var i, p var h = []; switch (geom.getType()) { case "Point":h.push(geom.getCoordinates()); break; case "LineString": case "LinearRing": case "MultiPoint":h = geom.getCoordinates(); break; case "MultiLineString": p = geom.getLineStrings(); for (i = 0; i < p.length; i++) h.concat(getCoordinates(p[i])); break; case "Polygon": h = getCoordinates(geom.getLinearRing(0)); break; case "MultiPolygon": p = geom.getPolygons(); for (i = 0; i < p.length; i++) h.concat(getCoordinates(p[i])); break; case "GeometryCollection": p = geom.getGeometries(); for (i = 0; i < p.length; i++) h.concat(getCoordinates(p[i])); break; default:break; } return h; }; /** Compute a convex hull on a geometry using Andrew's Monotone Chain Algorithm * @return {Array} the convex hull vertices */ ol.geom.Geometry.prototype.convexHull = function() { return ol.coordinate.convexHull(getCoordinates(this)); }; })(); /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (http://www.cecill.info/). */ /** Convert coordinate to French DFCI grid * @param {ol/coordinate} coord * @param {number} level [0-3] * @param {ol/proj/Projection} projection of the coord, default EPSG:27572 * @return {String} the DFCI index */ ol.coordinate.toDFCI = function (coord, level, projection) { if (!level && level !==0) level = 3; if (projection) { if (!ol.proj.get('EPSG:27572')) { // Add Lambert IIe proj if (!proj4.defs["EPSG:27572"]) proj4.defs("EPSG:27572","+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs"); ol.proj.proj4.register(proj4); } coord = ol.proj.transform(coord, projection, 'EPSG:27572'); } var x = coord[0]; var y = coord[1]; var s = ''; // Level 0 var step = 100000; s += String.fromCharCode(65 + Math.floor((x<800000?x:x+200000)/step)) + String.fromCharCode(65 + Math.floor((y<2300000?y:y+200000)/step) - 1500000/step); if (level === 0) return s; // Level 1 var step1 = 100000/5; s += 2*Math.floor((x%step)/step1); s += 2*Math.floor((y%step)/step1); if (level === 1) return s; // Level 2 var step2 = step1 / 10; var x0 = Math.floor((x%step1)/step2); s += String.fromCharCode(65 + (x0<8 ? x0 : x0+2)); s += Math.floor((y%step1)/step2); if (level === 2) return s; // Level 3 var x3 = Math.floor((x%step2)/500); var y3 = Math.floor((y%step2)/500); if (x3<1) { if (y3>1) s += '.1'; else s += '.4'; } else if (x3>2) { if (y3>1) s += '.2'; else s += '.3'; } else if (y3>2) { if (x3<2) s += '.1'; else s += '.2'; } else if (y3<1) { if (x3<2) s += '.4'; else s += '.3'; } else { s += '.5'; } return s; }; /** Get coordinate from French DFCI index * @param {String} index the DFCI index * @param {ol/proj/Projection} projection result projection, default EPSG:27572 * @return {ol/coordinate} coord */ ol.coordinate.fromDFCI = function (index, projection) { var coord; // Level 0 var step = 100000; var x = index.charCodeAt(0) - 65; x = (x<8 ? x : x-2)*step; var y = index.charCodeAt(1) - 65; y = (y<8 ? y : y-2)*step + 1500000; if (index.length===2) { coord = [x+step/2, y+step/2]; } else { // Level 1 step /= 5; x += Number(index.charAt(2))/2*step; y += Number(index.charAt(3))/2*step; if (index.length===4) { coord = [x+step/2, y+step/2]; } else { // Level 2 step /= 10; var x0 = index.charCodeAt(4) - 65; x += (x0<8 ? x0 : x0-2)*step; y += Number(index.charAt(5))*step; if (index.length === 6) { coord = [x+step/2, y+step/2]; } else { // Level 3 switch (index.charAt(7)) { case '1': coord = [x+step/4, y+3*step/4]; break; case '2': coord = [x+3*step/4, y+3*step/4]; break; case '3': coord = [x+3*step/4, y+step/4]; break; case '4': coord = [x+step/4, y+step/4]; break; default: coord = [x+step/2, y+step/2]; break; } } } } // Convert ? if (projection) { if (!ol.proj.get('EPSG:27572')) { // Add Lambert IIe proj if (!proj4.defs["EPSG:27572"]) proj4.defs("EPSG:27572","+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs"); ol.proj.proj4.register(proj4); } coord = ol.proj.transform(coord, 'EPSG:27572', projection); } return coord; }; /** The string is a valid DFCI index * @param {string} index DFCI index * @return {boolean} */ ol.coordinate.validDFCI = function (index) { if (index.length<2 || index.length>8) return false; if (/[^A-H|^K-N]/.test(index.substr(0,1))) return false; if (/[^B-H|^K-N]/.test(index.substr(1,1))) return false; if (index.length>2) { if (index.length<4) return false; if (/[^0,^2,^4,^6,^8]/.test(index.substr(2,1))) return false; if (/[^0,^2,^4,^6,^8]/.test(index.substr(3,1))) return false; } if (index.length>4) { if (index.length<6) return false; if (/[^A-H|^K-L]/.test(index.substr(4,1))) return false; if (/[^0-9]/.test(index.substr(5,1))) return false; } if (index.length>6) { if (index.length<8) return false; if (index.substr(6,1)!=='.') return false; if (/[^1-5]/.test(index.substr(7,1))) return false; } return true; } /** Coordinate is valid for DFCI * @param {ol/coordinate} coord * @param {ol/proj/Projection} projection result projection, default EPSG:27572 * @return {boolean} */ ol.coordinate.validDFCICoord = function (coord, projection) { if (projection) { if (!ol.proj.get('EPSG:27572')) { // Add Lambert IIe proj if (!proj4.defs["EPSG:27572"]) proj4.defs("EPSG:27572","+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs"); ol.proj.proj4.register(proj4); } coord = ol.proj.transform(coord, projection, 'EPSG:27572'); } // Test extent if (0 > coord[0] || coord[0] > 1200000 ) return false; if (1600000 > coord[1] || coord[1] > 2700000 ) return false; return true; }; /* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (http://www.cecill.info/). */ /* Define namespace */ ol.graph = {}; /** * @classdesc * Compute the shortest paths between nodes in a graph source * The source must only contains LinesString. * * It uses a A* optimisation. * You can overwrite methods to customize the result. * @see https://en.wikipedia.org/wiki/Dijkstras_algorithm * @constructor * @fires calculating * @fires start * @fires finish * @fires pause * @param {any} options * @param {ol/source/Vector} options.source the source for the edges * @param {integer} [options.maxIteration=20000] maximum iterations before a pause event is fired, default 20000 * @param {integer} [options.stepIteration=2000] number of iterations before a calculating event is fired, default 2000 * @param {number} [options.epsilon=1E-6] geometric precision (min distance beetween 2 points), default 1E-6 */ ol.graph.Dijskra = function (options) { options = options || {}; this.source = options.source; this.nodes = new ol.source.Vector(); // Maximum iterations this.maxIteration = options.maxIteration || 20000; this.stepIteration = options.stepIteration || 2000; // A* optimisation this.astar = true; this.candidat = []; ol.Object.call (this); this.set ('epsilon', options.epsilon || 1E-6); }; ol.ext.inherits(ol.graph.Dijskra, ol.Object); /** Get the weighting of the edge, for example a speed factor * The function returns a value beetween ]0,1] * - 1 = no weighting * - 0.5 = goes twice more faster on this road * * If no feature is provided you must return the lower weighting you're using * @param {ol/Feature} feature * @return {number} a number beetween 0-1 * @api */ ol.graph.Dijskra.prototype.weight = function(/* feature */) { return 1; }; /** Get the edge direction * - 0 : the road is blocked * - 1 : direct way * - -1 : revers way * - 2 : both way * @param {ol/Feature} feature * @return {Number} 0: blocked, 1: direct way, -1: revers way, 2:both way * @api */ ol.graph.Dijskra.prototype.direction = function(/* feature */) { return 2; }; /** Calculate the length of an edge * @param {ol/Feature|ol/geom/LineString} geom * @return {number} * @api */ ol.graph.Dijskra.prototype.getLength = function(geom) { if (geom.getGeometry) geom = geom.getGeometry(); return geom.getLength(); }; /** Get the nodes source concerned in the calculation * @return {ol/source/Vector} */ ol.graph.Dijskra.prototype.getNodeSource = function() { return this.nodes; }; /** Get all features at a coordinate * @param {ol/coordinate} coord * @return {Array
              } */ ol.graph.Dijskra.prototype.getEdges = function(coord) { var extent = ol.extent.buffer (ol.extent.boundingExtent([coord]), this.get('epsilon')); var result = []; this.source.forEachFeatureIntersectingExtent(extent, function(f){ result.push(f); }); return result; }; /** Get a node at a coordinate * @param {ol/coordinate} coord * @return {ol/Feature} the node */ ol.graph.Dijskra.prototype.getNode = function(coord) { var extent = ol.extent.buffer (ol.extent.boundingExtent([coord]), this.get('epsilon')); var result = []; this.nodes.forEachFeatureIntersectingExtent(extent, function(f){ result.push(f); }); return result[0]; }; /** Add a node * @param {ol/coorindate} p * @param {number} wdist the distance to reach this node * @param {ol/Feature} from the feature used to come to this node * @param {ol/Feature} prev the previous node * @return {ol/Feature} the node * @private */ ol.graph.Dijskra.prototype.addNode = function(p, wdist, dist, from, prev) { // Final condition if (this.wdist && wdist > this.wdist) return false; // Look for existing point var node = this.getNode(p); // Optimisation ? var dtotal = wdist + this.getLength(new ol.geom.LineString([this.end, p])) * this.weight(); if (this.astar && this.wdist && dtotal > this.wdist) return false; if (node) { // Allready there if (node!==this.arrival && node.get('wdist') <= wdist) return node; // New candidat node.set('dist', dist); node.set('wdist', wdist); node.set('dtotal', dtotal); node.set('from', from); node.set('prev', prev); if (node===this.arrival) { this.wdist = wdist; } this.candidat.push (node); } else { // New candidat node = new ol.Feature({ geometry: new ol.geom.Point(p), from: from, prev: prev, dist: dist || 0, wdist: wdist, dtotal: dtotal, }); if (wdist<0) { node.set('wdist', false); } else this.candidat.push (node); // Add it in the node source this.nodes.addFeature(node); } return node; }; /** Get the closest coordinate of a node in the graph source (an edge extremity) * @param {ol/coordinate} p * @return {ol/coordinate} * @private */ ol.graph.Dijskra.prototype.closestCoordinate = function(p) { var e = this.source.getClosestFeatureToCoordinate(p); var p0 = e.getGeometry().getFirstCoordinate(); var p1 = e.getGeometry().getLastCoordinate(); if (ol.coordinate.dist2d(p, p0) < ol.coordinate.dist2d(p, p1)) return p0; else return p1; }; /** Calculate a path beetween 2 points * @param {ol/coordinate} start * @param {ol/coordinate} end * @return {boolean|Array
                } false if don't start (still running) or start and end nodes */ ol.graph.Dijskra.prototype.path = function(start, end) { if (this.running) return false; // Starting nodes start = this.closestCoordinate(start); this.end = this.closestCoordinate(end); if (start[0]===this.end[0] && start[1]===this.end[1]) { this.dispatchEvent({ type: 'finish', route: [], wDistance: -1, distance: this.wdist }); return false; } // Initialize var self = this; this.nodes.clear(); this.candidat = []; this.wdist = 0; this.running = true; // Starting point this.addNode(start, 0); // Arrival this.arrival = this.addNode(this.end, -1); // Start this.nb = 0; this.dispatchEvent({ type: 'start' }); setTimeout(function() { self._resume(); }); return [start, this.end]; }; /** Restart after pause */ ol.graph.Dijskra.prototype.resume = function() { if (this.running) return; if (this.candidat.length) { this.running = true; this.nb = 0; this._resume(); } }; /** Pause */ ol.graph.Dijskra.prototype.pause = function() { if (!this.running) return; this.nb = -1; }; /** Get the current 'best way'. * This may be used to animate while calculating. * @return {Array
                  } */ ol.graph.Dijskra.prototype.getBestWay = function() { var node, max = -1; for (var i=0, n; n = this.candidat[i]; i++) { if (n.get('wdist') > max) { node = n; max = n.get('wdist'); } } // Calculate route to this node return this.getRoute(node); }; /** Go on searching new candidats * @private */ ol.graph.Dijskra.prototype._resume = function() { if (!this.running) return; while (this.candidat.length) { // Sort by wdist this.candidat.sort (function(a,b) { return (a.get('dtotal') < b.get('dtotal') ? 1 : a.get('dtotal')===b.get('dtotal') ? 0 : -1); }); // First candidate var node = this.candidat.pop(); var p = node.getGeometry().getCoordinates(); // Find connected edges var edges = this.getEdges(p); for (var i=0, e; e=edges[i]; i++) { if (node.get('from')!==e) { var dist = this.getLength (e); if (dist < 0) { console.log ('distance < 0!'); // continue; } var wdist = node.get('wdist') + dist * this.weight(e); dist = node.get('dist') + dist; var pt1 = e.getGeometry().getFirstCoordinate(); var pt2 = e.getGeometry().getLastCoordinate(); var sens = this.direction(e); if (sens!==0) { if (p[0]===pt1[0] && p[1]===pt1[1] && sens!==-1) { this.addNode(pt2, wdist, dist, e, node); } if (p[0]===pt2[0] && p[0]===pt2[0] && sens!==1) { this.addNode(pt1, wdist, dist, e, node); } } } // Test overflow or pause if (this.nb === -1 || this.nb++ > this.maxIteration) { this.running = false; this.dispatchEvent({ type: 'pause', overflow: (this.nb !== -1) }); return; } // Take time to do something if (!(this.nb % this.stepIteration)){ var self = this; window.setTimeout(function() { self._resume() }, 5); this.dispatchEvent({ type: 'calculating' }); return; } } } // Finish! this.nodes.clear(); this.running = false; this.dispatchEvent({ type: 'finish', route: this.getRoute(this.arrival), wDistance: this.wdist, distance: this.arrival.get('dist') }); }; /** Get the route to a node * @param {ol/Feature} node * @return {Array
                    } * @private */ ol.graph.Dijskra.prototype.getRoute = function(node) { var route = []; while (node) { route.unshift(node.get('from')); node = node.get('prev'); } route.shift(); return route; }; /** French Geoportail alti coding * @param {ol.geom.Geometry} geom * @param {Object} options * @param {ol/proj~ProjectionLike} [options.projection='EPSG:3857'] geometry projection, default 'EPSG:3857' * @param {string} [options.apiKey='essentiels'] Geoportail API key * @param {number} [options.sampling=0] number of resulting point, max 5000, if none keep input points or use samplingDist * @param {number} [options.samplingDist=0] distance for sampling the line or use sampling if lesser * @param {string} options.success a function that takes the resulting XYZ geometry * @param {string} options.error */ ol.geom.GPAltiCode = function(geom, options) { options = options || {}; var typeGeom = geom.getType(); if (typeGeom !== 'Point' && typeGeom !== 'LineString') { console.warn('[GPAltiCode] '+typeGeom+' not supported...') return; } var proj = options.projection || 'EPSG:3857'; var sampling = options.sampling || 0; if (options.samplingDist) { var d = geom.getLength(); sampling = Math.max(sampling, Math.round(d / options.samplingDist)); } if (sampling > 5000) sampling = 5000; if (sampling < 2) sampling = 0; geom = geom.clone().transform(proj, 'EPSG:4326'); var g, lon = [], lat = []; switch (typeGeom) { case 'Point': { g = [geom.getCoordinates()]; break; } case 'LineString': { g = geom.getCoordinates(); break; } default: return; } if (sampling <= g.length) sampling = 0; g.forEach(function(p) { lon.push(Math.round(p[0]*1000000)/1000000); lat.push(Math.round(p[1]*1000000)/1000000); }); // Get elevation var param = 'lon='+lon.join('|')+'&lat='+lat.join('|'); if (sampling) param += '&sampling='+sampling; ol.ext.Ajax.get({ url: 'https://wxs.ign.fr/'+(options.apiKey || 'essentiels')+'/alti/rest/'+(lon.length>1 ? 'elevationLine' : 'elevation')+'.json?'+param, success: function(res) { var pts = []; res.elevations.forEach(function(e, i) { if (sampling) { pts.push([e.lon, e.lat, e.z]); } else { pts.push([g[i][0], g[i][1], e.z]); } }); if (typeGeom==='Point') pts = pts[0]; var result = ol.geom.createFromType(typeGeom, pts); result.transform('EPSG:4326', proj); if (typeof(options.success) === 'function') options.success(result); }, error: function(e) { if (typeof(options.error) === 'function') options.error(e); } }); } /** Calculate elevation on coordinates or on a set of coordinates * @param {ol.coordinate|Array} coord coordinate or an array of coordinates * @param {Object} options * @param {ol/proj~ProjectionLike} [options.projection='EPSG:3857'] geometry projection, default 'EPSG:3857' * @param {string} [options.apiKey='essentiels'] Geoportail API key * @param {number} [options.sampling=0] number of resulting point, max 5000, if none keep input points or use samplingDist * @param {number} [options.samplingDist=0] distance for sampling the line or use sampling if lesser * @param {string} options.success a function that takes the resulting XYZ coordinates * @param {string} options.error */ ol.coordinate.GPAltiCode = function(coord, options) { options = options || {}; var unique = !coord[0].length; var g = unique ? new ol.geom.Point(coord) : new ol.geom.LineString(coord); ol.geom.GPAltiCode(g, { projection: options.projection, apiKey: options.apiKey, sampling: options.sampling, samplingDist: options.samplingDist, success: function(g) { if (typeof(options.success) === 'function') { options.success(g.getCoordinates()) } }, error: options.error }) } /* 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). Usefull function to handle geometric operations */ /** Distance beetween 2 points * Usefull geometric functions * @param {ol.Coordinate} p1 first point * @param {ol.Coordinate} p2 second point * @return {number} distance */ ol.coordinate.dist2d = function(p1, p2) { var dx = p1[0]-p2[0]; var dy = p1[1]-p2[1]; return Math.sqrt(dx*dx+dy*dy); } /** 2 points are equal * Usefull geometric functions * @param {ol.Coordinate} p1 first point * @param {ol.Coordinate} p2 second point * @return {boolean} */ ol.coordinate.equal = function(p1, p2) { return (p1[0]==p2[0] && p1[1]==p2[1]); } /** Get center coordinate of a feature * @param {ol.Feature} f * @return {ol.coordinate} the center */ ol.coordinate.getFeatureCenter = function(f) { return ol.coordinate.getGeomCenter (f.getGeometry()); }; /** Get center coordinate of a geometry * @param {ol.geom.Geometry} geom * @return {ol.Coordinate} the center */ ol.coordinate.getGeomCenter = function(geom) { switch (geom.getType()) { case 'Point': return geom.getCoordinates(); case "MultiPolygon": geom = geom.getPolygon(0); // fallthrough case "Polygon": return geom.getInteriorPoint().getCoordinates(); default: return geom.getClosestPoint(ol.extent.getCenter(geom.getExtent())); } }; /** Offset a polyline * @param {Array} coords * @param {number} offset * @return {Array} resulting coord * @see http://stackoverflow.com/a/11970006/796832 * @see https://drive.google.com/viewerng/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxqa2dhZGdldHN0b3JlfGd4OjQ4MzI5M2Y0MjNmNzI2MjY */ ol.coordinate.offsetCoords = function (coords, offset) { var path = []; var N = coords.length-1; var max = N; var mi, mi1, li, li1, ri, ri1, si, si1, Xi1, Yi1; var p0, p1, p2; var isClosed = ol.coordinate.equal(coords[0],coords[N]); if (!isClosed) { p0 = coords[0]; p1 = coords[1]; p2 = [ p0[0] + (p1[1] - p0[1]) / ol.coordinate.dist2d(p0,p1) *offset, p0[1] - (p1[0] - p0[0]) / ol.coordinate.dist2d(p0,p1) *offset ]; path.push(p2); coords.push(coords[N]) N++; max--; } for (var i = 0; i < max; i++) { p0 = coords[i]; p1 = coords[(i+1) % N]; p2 = coords[(i+2) % N]; mi = (p1[1] - p0[1])/(p1[0] - p0[0]); mi1 = (p2[1] - p1[1])/(p2[0] - p1[0]); // Prevent alignements if (Math.abs(mi-mi1) > 1e-10) { li = Math.sqrt((p1[0] - p0[0])*(p1[0] - p0[0])+(p1[1] - p0[1])*(p1[1] - p0[1])); li1 = Math.sqrt((p2[0] - p1[0])*(p2[0] - p1[0])+(p2[1] - p1[1])*(p2[1] - p1[1])); ri = p0[0] + offset*(p1[1] - p0[1])/li; ri1 = p1[0] + offset*(p2[1] - p1[1])/li1; si = p0[1] - offset*(p1[0] - p0[0])/li; si1 = p1[1] - offset*(p2[0] - p1[0])/li1; Xi1 = (mi1*ri1-mi*ri+si-si1) / (mi1-mi); Yi1 = (mi*mi1*(ri1-ri)+mi1*si-mi*si1) / (mi1-mi); // Correction for vertical lines if(p1[0] - p0[0] == 0) { Xi1 = p1[0] + offset*(p1[1] - p0[1])/Math.abs(p1[1] - p0[1]); Yi1 = mi1*Xi1 - mi1*ri1 + si1; } if (p2[0] - p1[0] == 0 ) { Xi1 = p2[0] + offset*(p2[1] - p1[1])/Math.abs(p2[1] - p1[1]); Yi1 = mi*Xi1 - mi*ri + si; } path.push([Xi1, Yi1]); } } if (isClosed) { path.push(path[0]); } else { coords.pop(); p0 = coords[coords.length-1]; p1 = coords[coords.length-2]; p2 = [ p0[0] - (p1[1] - p0[1]) / ol.coordinate.dist2d(p0,p1) *offset, p0[1] + (p1[0] - p0[0]) / ol.coordinate.dist2d(p0,p1) *offset ]; path.push(p2); } return path; } /** Find the segment a point belongs to * @param {ol.Coordinate} pt * @param {Array} coords * @return {} the index (-1 if not found) and the segment */ ol.coordinate.findSegment = function (pt, coords) { for (var i=0; i} geom * @param {number} y the y to split * @param {number} n contour index * @return {Array>} */ ol.coordinate.splitH = function (geom, y, n) { var x, abs; var list = []; for (var i=0; iy || geom[i][1]>=y && geom[i+1][1]} d1 * @param {Arrar} d2 */ ol.coordinate.getIntersectionPoint = function (d1, d2) { var d1x = d1[1][0] - d1[0][0]; var d1y = d1[1][1] - d1[0][1]; var d2x = d2[1][0] - d2[0][0]; var d2y = d2[1][1] - d2[0][1]; var det = d1x * d2y - d1y * d2x; if (det != 0) { var k = (d1x * d1[0][1] - d1x * d2[0][1] - d1y * d1[0][0] + d1y * d2[0][0]) / det; return [d2[0][0] + k*d2x, d2[0][1] + k*d2y]; } else { return false; } }; ol.extent.intersection; (function() { // Split at x function splitX(pts, x) { var pt; for (var i=pts.length-1; i>0; i--) { if ((pts[i][0]>x && pts[i-1][0]x)) { pt = [ x, (x - pts[i][0]) / (pts[i-1][0]-pts[i][0]) * (pts[i-1][1]-pts[i][1]) + pts[i][1]]; pts.splice(i, 0, pt); } } } // Split at y function splitY(pts, y) { var pt; for (var i=pts.length-1; i>0; i--) { if ((pts[i][1]>y && pts[i-1][1]y)) { pt = [ (y - pts[i][1]) / (pts[i-1][1]-pts[i][1]) * (pts[i-1][0]-pts[i][0]) + pts[i][0], y]; pts.splice(i, 0, pt); } } } /** Fast polygon intersection with an extent (used for area calculation) * @param {ol.extent.Extent} extent * @param {ol.geom.Polygon|ol.geom.MultiPolygon} polygon * @returns {ol.geom.Polygon|ol.geom.MultiPolygon|null} return null if not a polygon geometry */ ol.extent.intersection = function(extent, polygon) { var poly = (polygon.getType() === 'Polygon'); if (!poly && polygon.getType() !== 'MultiPolygon') return null; var geom = polygon.getCoordinates(); if (poly) geom = [geom]; geom.forEach(function(g) { g.forEach(function(c) { splitX(c, extent[0]); splitX(c, extent[2]); splitY(c, extent[1]); splitY(c, extent[3]); }); }) // Snap geom to the extent geom.forEach(function(g) { g.forEach(function(c) { c.forEach(function(p) { if (p[0]extent[2]) p[0] = extent[2]; if (p[1]extent[3]) p[1] = extent[3]; }) }) }) if (poly) { return new ol.geom.Polygon(geom[0]); } else { return new ol.geom.MultiPolygon(geom); } }; })(); /** Add points along a segment * @param {ol.Coordinate} p1 * @param {ol.Coordinate} p2 * @param {number} d * @param {boolean} start include starting point, default true * @returns {Array} */ ol.coordinate.sampleAt = function(p1, p2, d, start) { var pts = []; if (start!==false) pts.push(p1); var dl = ol.coordinate.dist2d(p1,p2); if (dl) { var nb = Math.round(dl/d); if (nb>1) { var dx = (p2[0]-p1[0]) / nb; var dy = (p2[1]-p1[1]) / nb; for (var i=1; i r) { hasout = true; l.push([ c[0] + r / d * (p[0]-c[0]), c[1] + r / d * (p[1]-c[1]) ]); } else { // hasin = true; l.push(p); } }); }) }); if (!hasout) return geom; if (geom.getType() === 'Polygon') { return new ol.geom.Polygon(result[0]); } else { return new ol.geom.MultiPolygon(result); } } } } else { console.warn('[ol/geom/Circle~intersection] Unsupported geometry type: '+geom.getType()); } return geom; }; /** Split a lineString by a point or a list of points * NB: points must be on the line, use getClosestPoint() to get one * @param {ol.Coordinate | Array} pt points to split the line * @param {Number} tol distance tolerance for 2 points to be equal */ ol.geom.LineString.prototype.splitAt = function(pt, tol) { var i; if (!pt) return [this]; if (!tol) tol = 1e-10; // Test if list of points if (pt.length && pt[0].length) { var result = [this]; for (i=0; i split if (split) { ci.push(pt); c.push (new ol.geom.LineString(ci)); ci = [pt]; } } ci.push(c0[i+1]); } if (ci.length>1) c.push (new ol.geom.LineString(ci)); if (c.length) return c; else return [this]; } // import('ol-ext/geom/LineStringSplitAt') /* 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). Usefull function to handle geometric operations */ /*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ /** * Calculate a MultiPolyline to fill a Polygon with a scribble effect that appears hand-made * @param {} options * @param {Number} options.interval interval beetween lines * @param {Number} options.angle hatch angle in radian, default PI/2 * @return {ol.geom.MultiLineString|null} the resulting MultiLineString geometry or null if none */ ol.geom.MultiPolygon.prototype.scribbleFill = function (options) { var scribbles = []; var poly = this.getPolygons(); var i, p, s; for (i=0; p=poly[i]; i++) { var mls = p.scribbleFill(options); if (mls) scribbles.push(mls); } if (!scribbles.length) return null; // Merge scribbles var scribble = scribbles[0]; var ls; for (i = 0; s = scribbles[i]; i++) { ls = s.getLineStrings(); for (var k = 0; k < ls.length; k++) { scribble.appendLineString(ls[k]); } } return scribble; }; /** * Calculate a MultiPolyline to fill a Polygon with a scribble effect that appears hand-made * @param {} options * @param {Number} options.interval interval beetween lines * @param {Number} options.angle hatch angle in radian, default PI/2 * @return {ol.geom.MultiLineString|null} the resulting MultiLineString geometry or null if none */ ol.geom.Polygon.prototype.scribbleFill = function (options) { var step = options.interval; var angle = options.angle || Math.PI/2; var i, k,l; // Geometry + rotate var geom = this.clone(); geom.rotate(angle, [0,0]); var coords = geom.getCoordinates(); // Merge holes var coord = coords[0]; for (i=1; i nexty) break; if (lines[k][0].pt[1] === nexty) { var d = Math.min( (lines[k][0].index - l[0].index + mod) % mod, (l[0].index - lines[k][0].index + mod) % mod ); var d2 = Math.min( (l[1].index - l[0].index + mod) % mod, (l[0].index - l[1].index + mod) % mod ); if (d 12) precision = 12; var idx = 0; // index into base32 map var bit = 0; // each char holds 5 bits var evenBit = true; var geohash = ''; var latMin = -90, latMax = 90; var lonMin = -180, lonMax = 180; while (geohash.length < precision) { if (evenBit) { // bisect E-W longitude var lonMid = (lonMin + lonMax) / 2; if (lon >= lonMid) { idx = idx*2 + 1; lonMin = lonMid; } else { idx = idx*2; lonMax = lonMid; } } else { // bisect N-S latitude var latMid = (latMin + latMax) / 2; if (lat >= latMid) { idx = idx*2 + 1; latMin = latMid; } else { idx = idx*2; latMax = latMid; } } evenBit = !evenBit; if (++bit == 5) { // 5 bits gives us a character: append it and start over geohash += ol.geohash.base32.charAt(idx); bit = 0; idx = 0; } } return geohash; }; /** Decode geohash to latitude/longitude * (location is approximate centre of geohash cell, to reasonable precision). * @param {string} geohash - Geohash string to be converted to latitude/longitude. * @returns {ol.coordinate} */ ol.geohash.toLonLat = function(geohash) { var extent = ol.geohash.getExtent(geohash); // <-- the hard work // now just determine the centre of the cell... var latMin = extent[1], lonMin = extent[0]; var latMax = extent[3], lonMax = extent[2]; // cell centre var lat = (latMin + latMax)/2; var lon = (lonMin + lonMax)/2; // round to close to centre without excessive precision: ⌊2-log10(Δ°)⌋ decimal places lat = lat.toFixed(Math.floor(2-Math.log(latMax-latMin)/Math.LN10)); lon = lon.toFixed(Math.floor(2-Math.log(lonMax-lonMin)/Math.LN10)); return [Number(lon), Number(lat)]; }; /** Returns SW/NE latitude/longitude bounds of specified geohash. * @param {string} geohash Cell that bounds are required of. * @returns {ol.extent | false} */ ol.geohash.getExtent = function(geohash) { if (!geohash) return false; geohash = geohash.toLowerCase(); var evenBit = true; var latMin = -90, latMax = 90; var lonMin = -180, lonMax = 180; for (var i=0; i=0; n--) { var bitN = idx >> n & 1; if (evenBit) { // longitude var lonMid = (lonMin+lonMax) / 2; if (bitN == 1) { lonMin = lonMid; } else { lonMax = lonMid; } } else { // latitude var latMid = (latMin+latMax) / 2; if (bitN == 1) { latMin = latMid; } else { latMax = latMid; } } evenBit = !evenBit; } } return [lonMin, latMin, lonMax, latMax]; }; /** Determines adjacent cell in given direction. * @param {string} geohash Geohash cel * @param {string} direction direction as char : N/S/E/W. * @returns {string|false} */ ol.geohash.getAdjacent = function (geohash, direction) { // based on github.com/davetroy/geohash-js geohash = geohash.toLowerCase(); direction = direction.toLowerCase(); if (!geohash) return false; if ('nsew'.indexOf(direction) == -1) return false; var neighbour = { n: [ 'p0r21436x8zb9dcf5h7kjnmqesgutwvy', 'bc01fg45238967deuvhjyznpkmstqrwx' ], s: [ '14365h7k9dcfesgujnmqp0r2twvyx8zb', '238967debc01fg45kmstqrwxuvhjyznp' ], e: [ 'bc01fg45238967deuvhjyznpkmstqrwx', 'p0r21436x8zb9dcf5h7kjnmqesgutwvy' ], w: [ '238967debc01fg45kmstqrwxuvhjyznp', '14365h7k9dcfesgujnmqp0r2twvyx8zb' ], }; var border = { n: [ 'prxz', 'bcfguvyz' ], s: [ '028b', '0145hjnp' ], e: [ 'bcfguvyz', 'prxz' ], w: [ '0145hjnp', '028b' ], }; var lastCh = geohash.slice(-1); // last character of hash var parent = geohash.slice(0, -1); // hash without last character var type = geohash.length % 2; // check for edge-cases which don't share common prefix if (border[direction][type].indexOf(lastCh) != -1 && parent != '') { parent = ol.geohash.getAdjacent(parent, direction); } // append letter for direction to parent return parent + ol.geohash.base32.charAt(neighbour[direction][type].indexOf(lastCh)); } /** Returns all 8 adjacent cells to specified geohash. * @param {string} geohash Geohash neighbours are required of. * @returns {{n,ne,e,se,s,sw,w,nw: string}} */ ol.geohash.getNeighbours = function(geohash) { return { 'n': ol.geohash.getAdjacent(geohash, 'n'), 'ne': ol.geohash.getAdjacent(ol.geohash.getAdjacent(geohash, 'n'), 'e'), 'e': ol.geohash.getAdjacent(geohash, 'e'), 'se': ol.geohash.getAdjacent(ol.geohash.getAdjacent(geohash, 's'), 'e'), 's': ol.geohash.getAdjacent(geohash, 's'), 'sw': ol.geohash.getAdjacent(ol.geohash.getAdjacent(geohash, 's'), 'w'), 'w': ol.geohash.getAdjacent(geohash, 'w'), 'nw': ol.geohash.getAdjacent(ol.geohash.getAdjacent(geohash, 'n'), 'w'), }; } /** Compute great circle bearing of two points. * @See http://www.movable-type.co.uk/scripts/latlong.html for the original code * @param {ol.coordinate} origin origin in lonlat * @param {ol.coordinate} destination destination in lonlat * @return {number} bearing angle in radian */ ol.sphere.greatCircleBearing = function(origin, destination) { var toRad = Math.PI/180; var ori = [ origin[0]*toRad, origin[1]*toRad ]; var dest = [ destination[0]*toRad, destination[1]*toRad ]; var bearing = Math.atan2( Math.sin(dest[0] - ori[0]) * Math.cos(dest[1]), Math.cos(ori[1]) * Math.sin(dest[1]) - Math.sin(ori[1]) * Math.cos(dest[1]) * Math.cos(dest[0] - ori[0]) ); return bearing; }; /** * Computes the destination point given an initial point, a distance and a bearing * @See http://www.movable-type.co.uk/scripts/latlong.html for the original code * @param {ol.coordinate} origin stating point in lonlat coords * @param {number} distance * @param {number} bearing bearing angle in radian * @param {*} options * @param {booelan} normalize normalize longitude beetween -180/180, deafulet true * @param {number|undefined} options.radius sphere radius, default 6371008.8 */ ol.sphere.computeDestinationPoint = function(origin, distance, bearing, options) { options = options || {}; var toRad = Math.PI/180; var radius = options.radius || 6371008.8; var phi1 = origin[1] * toRad; var lambda1 = origin[0] * toRad; var delta = distance / radius; var phi2 = Math.asin( Math.sin(phi1) * Math.cos(delta) + Math.cos(phi1) * Math.sin(delta) * Math.cos(bearing) ); var lambda2 = lambda1 + Math.atan2( Math.sin(bearing) * Math.sin(delta) * Math.cos(phi1), Math.cos(delta) - Math.sin(phi1) * Math.sin(phi2) ); var lon = lambda2 / toRad; // normalise to >=-180 and <=180° if (options.normalize!==false && (lon < -180 || lon > 180)) { lon = ((lon * 540) % 360) - 180; } return [ lon, phi2 / toRad ]; }; /** Calculate a track along the great circle given an origin and a destination * @param {ol.coordinate} origin origin in lonlat * @param {ol.coordinate} destination destination in lonlat * @param {number} distance distance between point along the track in meter, default 1km (1000) * @param {number|undefined} radius sphere radius, default 6371008.8 * @return {Array} */ ol.sphere.greatCircleTrack = function(origin, destination, options) { options = options || {}; var bearing = ol.sphere.greatCircleBearing(origin, destination); var dist = ol.sphere.getDistance(origin, destination, options.radius); var distance = options.distance || 1000; var d = distance; var geom = [origin]; while (d < dist) { geom.push(ol.sphere.computeDestinationPoint(origin, d, bearing, { radius: options.radius, normalize: false })); d += distance; } var pt = ol.sphere.computeDestinationPoint(origin, dist, bearing, { radius: options.radius, normalize: false }); if (Math.abs(pt[0]-destination[0]) > 1) { if (pt[0] > destination[0]) destination[0] += 360; else destination[0] -= 360; } geom.push(destination); return geom; }; /** Get map scale factor * @param {ol.Map} map * @param {number} [dpi=96] dpi, default 96 * @return {number} */ ol.sphere.getMapScale = function (map, dpi) { var view = map.getView(); var proj = view.getProjection(); var center = view.getCenter(); var px = map.getPixelFromCoordinate(center); px[1] += 1; var coord = map.getCoordinateFromPixel(px); var d = ol.sphere.getDistance( ol.proj.transform(center, proj, 'EPSG:4326'), ol.proj.transform(coord, proj, 'EPSG:4326')); d *= (dpi||96) /.0254 return d; }; /** Set map scale factor * @param {ol.Map} map * @param {number|string} scale the scale factor or a scale string as 1/xxx * @param {number} [dpi=96] dpi, default 96 * @return {number} scale factor */ ol.sphere.setMapScale = function (map, scale, dpi) { if (map && scale) { var fac = scale; if (typeof(scale)==='string') { fac = scale.split('/')[1]; if (!fac) fac = scale; fac = fac.replace(/[^\d]/g,''); fac = parseInt(fac); } if (!fac) return; // Calculate new resolution var view = map.getView(); var proj = view.getProjection(); var center = view.getCenter(); var px = map.getPixelFromCoordinate(center); px[1] += 1; var coord = map.getCoordinateFromPixel(px); var d = ol.sphere.getDistance( ol.proj.transform(center, proj, 'EPSG:4326'), ol.proj.transform(coord, proj, 'EPSG:4326')); d *= (dpi || 96) /.0254 view.setResolution(view.getResolution()*fac/d); return fac; } }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Pulse an extent on postcompose * @param {ol.coordinates} point to pulse * @param {ol.pulse.options} options pulse options param * @param {ol.projectionLike|undefined} options.projection projection of coords, default no transform * @param {Number} options.duration animation duration in ms, default 2000 * @param {ol.easing} options.easing easing function, default ol.easing.upAndDown * @param {ol.style.Stroke} options.style stroke style, default 2px red */ ol.Map.prototype.animExtent = function(extent, options) { var listenerKey; options = options || {}; // Change to map's projection if (options.projection) { extent = ol.proj.transformExtent (extent, options.projection, this.getView().getProjection()); } // options var start = new Date().getTime(); var duration = options.duration || 1000; var easing = options.easing || ol.easing.upAndDown; var width = options.style ? options.style.getWidth() || 2 : 2; var color = options.style ? options.style.getColr() || 'red' : 'red'; // Animate function function animate(event) { var frameState = event.frameState; var ratio = frameState.pixelRatio; var elapsed = frameState.time - start; if (elapsed > duration) ol.Observable.unByKey(listenerKey); else { var elapsedRatio = elapsed / duration; var p0 = this.getPixelFromCoordinate([extent[0],extent[1]]); var p1 = this.getPixelFromCoordinate([extent[2],extent[3]]); var context = event.context; context.save(); context.scale(ratio,ratio); context.beginPath(); // var e = easing(elapsedRatio) context.globalAlpha = easing(1 - elapsedRatio); context.lineWidth = width; context.strokeStyle = color; context.rect(p0[0], p0[1], p1[0]-p0[0], p1[1]-p0[1]); context.stroke(); context.restore(); // tell OL3 to continue postcompose animation frameState.animate = true; } } // Launch animation listenerKey = this.on('postcompose', animate.bind(this)); try { this.renderSync(); } catch(e) { /* ok */ } } /** Create a cardinal spline version of this geometry. * Original https://github.com/epistemex/cardinal-spline-js * @see https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline * * @param {} options * @param {Number} options.tension a [0,1] number / can be interpreted as the "length" of the tangent, default 0.5 * @param {Number} options.resolution size of segment to split * @param {Integer} options.pointsPerSeg number of points per segment to add if no resolution is provided, default add 10 points per segment */ /** Cache cspline calculation on a geometry * @param {} options * @param {Number} options.tension a [0,1] number / can be interpreted as the "length" of the tangent, default 0.5 * @param {Number} options.resolution size of segment to split * @param {Integer} options.pointsPerSeg number of points per segment to add if no resolution is provided, default add 10 points per segment * @return {ol.geom.Geometry} */ ol.geom.Geometry.prototype.cspline = function(options){ // Calculate cspline if (this.calcCSpline_){ if (this.csplineGeometryRevision != this.getRevision() || this.csplineOption != JSON.stringify(options)) { this.csplineGeometry_ = this.calcCSpline_(options) this.csplineGeometryRevision = this.getRevision(); this.csplineOption = JSON.stringify(options); } return this.csplineGeometry_; } else { // Default do nothing return this; } }; ol.geom.GeometryCollection.prototype.calcCSpline_ = function(options) { var g=[], g0=this.getGeometries(); for (var i=0; i} line * @param {} options * @param {Number} options.tension a [0,1] number / can be interpreted as the "length" of the tangent, default 0.5 * @param {Number} options.resolution size of segment to split * @param {Integer} options.pointsPerSeg number of points per segment to add if no resolution is provided, default add 10 points per segment * @return {Array} */ ol.coordinate.cspline = function(line, options) { if (!options) options={}; var tension = typeof options.tension === "number" ? options.tension : 0.5; var length = 0; var p0 = line[0]; line.forEach(function(p) { length += ol.coordinate.dist2d(p0, p); p0 = p; }) var resolution = options.resolution || (length / line.length / (options.pointsPerSeg || 10)); var pts, res = [], // clone array x, y, // our x,y coords t1x, t2x, t1y, t2y, // tension vectors c1, c2, c3, c4, // cardinal points st, t, i; // steps based on num. of segments // clone array so we don't change the original // pts = line.slice(0); // The algorithm require a previous and next point to the actual point array. // Check if we will draw closed or open curve. // If closed, copy end points to beginning and first points to end // If open, duplicate first points to beginning, end points to end if (line.length>2 && line[0][0]==line[line.length-1][0] && line[0][1]==line[line.length-1][1]) { pts.unshift(line[line.length-2]); pts.push(line[1]); } else { pts.unshift(line[0]); pts.push(line[line.length-1]); } // ok, lets start.. function dist2d(x1, y1, x2, y2) { var dx = x2-x1; var dy = y2-y1; return Math.sqrt(dx*dx+dy*dy); } // 1. loop goes through point array // 2. loop goes through each segment between the 2 pts + 1e point before and after for (i=1; i < (pts.length - 2); i++) { var d1 = dist2d (pts[i][0], pts[i][1], pts[i+1][0], pts[i+1][1]); var numOfSegments = Math.round(d1/resolution); var d=1; if (options.normalize) { d1 = dist2d (pts[i+1][0], pts[i+1][1], pts[i-1][0], pts[i-1][1]); var d2 = dist2d (pts[i+2][0], pts[i+2][1], pts[i][0], pts[i][1]); if (d1 y_diff && x_diff > z_diff) rx = -ry-rz else if (y_diff > z_diff) ry = -rx-rz else rz = -rx-ry return [rx, ry, rz]; }; /** Round axial coords * @param {ol.Coordinate} h axial coordinate * @return {ol.Coordinate} rounded axial coordinate */ ol.HexGrid.prototype.hex_round = function(h) { return this.cube2hex( this.cube_round( this.hex2cube(h )) ); }; /** Get hexagon corners */ ol.HexGrid.prototype.hex_corner = function(center, size, i) { return [ center[0] + size * this.layout_[8+(2*(i%6))], center[1] + size * this.layout_[9+(2*(i%6))]]; }; /** Get hexagon coordinates at a coordinate * @param {ol.Coordinate} coord * @return {Arrary} */ ol.HexGrid.prototype.getHexagonAtCoord = function (coord) { return (this.getHexagon(this.coord2hex(coord))); }; /** Get hexagon coordinates at hex * @param {ol.Coordinate} hex * @return {Arrary} */ ol.HexGrid.prototype.getHexagon = function (hex) { var p = []; var c = this.hex2coord(hex); for (var i=0; i<=7; i++) { p.push(this.hex_corner(c, this.size_, i, this.layout_[8])); } return p; }; /** Convert hex to coord * @param {ol.hex} hex * @return {ol.Coordinate} */ ol.HexGrid.prototype.hex2coord = function (hex) { return [ this.origin_[0] + this.size_ * (this.layout_[0] * hex[0] + this.layout_[1] * hex[1]), this.origin_[1] + this.size_ * (this.layout_[2] * hex[0] + this.layout_[3] * hex[1]) ]; }; /** Convert coord to hex * @param {ol.Coordinate} coord * @return {ol.hex} */ ol.HexGrid.prototype.coord2hex = function (coord) { var c = [ (coord[0]-this.origin_[0]) / this.size_, (coord[1]-this.origin_[1]) / this.size_ ]; var q = this.layout_[4] * c[0] + this.layout_[5] * c[1]; var r = this.layout_[6] * c[0] + this.layout_[7] * c[1]; return this.hex_round([q, r]); }; /** Calculate distance between to hexagon (number of cube) * @param {ol.Coordinate} a first cube coord * @param {ol.Coordinate} a second cube coord * @return {number} distance */ ol.HexGrid.prototype.cube_distance = function (a, b) { //return ( (Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2])) / 2 ); return ( Math.max (Math.abs(a[0] - b[0]), Math.abs(a[1] - b[1]), Math.abs(a[2] - b[2])) ); }; (function(){ /** Line interpolation */ function lerp(a, b, t) { // for floats return a + (b - a) * t; } function cube_lerp(a, b, t) { // for hexes return [ lerp (a[0]+1e-6, b[0], t), lerp (a[1]+1e-6, b[1], t), lerp (a[2]+1e-6, b[2], t) ]; } /** Calculate line between to hexagon * @param {ol.Coordinate} a first cube coord * @param {ol.Coordinate} b second cube coord * @return {Array} array of cube coordinates */ ol.HexGrid.prototype.cube_line = function (a, b) { var d = this.cube_distance(a, b); if (!d) return [a]; var results = [] for (var i=0; i<=d; i++) { results.push ( this.cube_round ( cube_lerp(a, b, i/d) ) ); } return results; }; })(); ol.HexGrid.prototype.neighbors = { 'cube': [ [+1, -1, 0], [+1, 0, -1], [0, +1, -1], [-1, +1, 0], [-1, 0, +1], [0, -1, +1] ], 'hex': [ [+1, 0], [+1, -1], [0, -1], [-1, 0], [-1, +1], [0, +1] ] }; /** Get the neighbors for an hexagon * @param {ol.Coordinate} h axial coord * @param {number} direction * @return { ol.Coordinate | Array } neighbor || array of neighbors */ ol.HexGrid.prototype.hex_neighbors = function (h, d) { if (d!==undefined) { return [ h[0] + this.neighbors.hex[d%6][0], h[1] + this.neighbors.hex[d%6][1] ]; } else { var n = []; for (d=0; d<6; d++) { n.push ([ h[0] + this.neighbors.hex[d][0], h[1] + this.neighbors.hex[d][1] ]); } return n; } }; /** Get the neighbors for an hexagon * @param {ol.Coordinate} c cube coord * @param {number} direction * @return { ol.Coordinate | Array } neighbor || array of neighbors */ ol.HexGrid.prototype.cube_neighbors = function (c, d) { if (d!==undefined) { return [ c[0] + this.neighbors.cube[d%6][0], c[1] + this.neighbors.cube[d%6][1], c[2] + this.neighbors.cube[d%6][2] ]; } else { var n = []; for (d=0; d<6; d++) { n.push ([ c[0] + this.neighbors.cube[d][0], c[1] + this.neighbors.cube[d][1], c[2] + this.neighbors.cube[d][2] ]); } for (d=0; d<6; d++) n[d] = this.cube2hex(n[d]) return n; } }; /* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * French INSEE grids * @classdesc a class to compute French INSEE grids, ie. fix area (200x200m) square grid, * based appon EPSG:3035 * * @requires proj4 * @constructor * @extends {ol.Object} * @param {Object} [options] * @param {number} [options.size] size grid size in meter, default 200 (200x200m) */ ol.InseeGrid = function (options) { options = options || {}; // Define EPSG:3035 if none if (!proj4.defs["EPSG:3035"]) { proj4.defs("EPSG:3035","+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs"); ol.proj.proj4.register(proj4); } ol.Object.call (this, options); // Options var size = Math.max(200, Math.round((options.size||0)/200) * 200); this.set('size', size); }; ol.ext.inherits (ol.InseeGrid, ol.Object); /** Grid extent (in EPSG:3035) */ ol.InseeGrid.extent = [3200000,2000000,4300000,3140000]; /** Get the grid extent * @param {ol.proj.ProjLike} [proj='EPSG:3857'] */ ol.InseeGrid.prototype.getExtent = function (proj) { return ol.proj.transformExtent(ol.InseeGrid.extent, proj||'EPSG:3035', 'EPSG:3857') }; /** Get grid geom at coord * @param {ol.Coordinate} coord * @param {ol.proj.ProjLike} [proj='EPSG:3857'] */ ol.InseeGrid.prototype.getGridAtCoordinate = function (coord, proj) { var c = ol.proj.transform(coord, proj||'EPSG:3857', 'EPSG:3035') var s = this.get('size'); var x = Math.floor(c[0]/s) * s; var y = Math.floor(c[1]/s) * s; var geom = new ol.geom.Polygon([[[x,y],[x+s,y],[x+s,y+s],[x,y+s],[x,y]]]); geom.transform('EPSG:3035', proj||'EPSG:3857'); return geom; }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Show a markup a point on postcompose * @deprecated use map.animateFeature instead * @param {ol.coordinates} point to pulse * @param {ol.markup.options} pulse options param * - projection {ol.projection|String|undefined} projection of coords, default none * - delay {Number} delay before mark fadeout * - maxZoom {Number} zoom when mark fadeout * - style {ol.style.Image|ol.style.Style|Array} Image to draw as markup, default red circle * @return Unique key for the listener with a stop function to stop animation */ ol.Map.prototype.markup = function(coords, options) { var listenerKey; var self = this; options = options || {}; // Change to map's projection if (options.projection) { coords = ol.proj.transform(coords, options.projection, this.getView().getProjection()); } // options var start = new Date().getTime(); var delay = options.delay || 3000; var duration = 1000; var maxZoom = options.maxZoom || 100; var easing = ol.easing.easeOut; var style = options.style; if (!style) style = new ol.style.Circle({ radius:10, stroke:new ol.style.Stroke({color:'red', width:2 }) }); if (style instanceof ol.style.Image) style = new ol.style.Style({ image: style }); if (!(style instanceof Array)) style = [style]; // Animate function function animate(event) { var frameState = event.frameState; var elapsed = frameState.time - start; if (elapsed > delay+duration) { ol.Observable.unByKey(listenerKey); listenerKey = null; } else { if (delay>elapsed && this.getView().getZoom()>maxZoom) delay = elapsed; var ratio = frameState.pixelRatio; var elapsedRatio = 0; if (elapsed > delay) elapsedRatio = (elapsed-delay) / duration; var context = event.context; context.save(); context.beginPath(); context.globalAlpha = easing(1 - elapsedRatio); for (var i=0; i= delay) frameState.animate = true; } } setTimeout (function() { if (listenerKey) { try { self.renderSync(); } catch(e) { /* ok */ } } }, delay); // Launch animation listenerKey = this.on('postcompose', animate.bind(this)); try { this.renderSync(); } catch(e) { /* ok */ } listenerKey.stop = function() { delay = duration = 0; try { this.target.renderSync(); } catch(e) { /* ok */ } }; return listenerKey; } /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Ordering function for ol.layer.Vector renderOrder parameter * ol.ordering.fn (options) * It will return an ordering function (f0,f1) * @namespace */ ol.ordering = {}; /** y-Ordering * @return ordering function (f0,f1) */ ol.ordering.yOrdering = function() { return function(f0,f1) { return f1.getGeometry().getExtent()[1] - f0.getGeometry().getExtent()[1] ; }; }; /** Order with a feature attribute * @param options * @param {string} options.attribute ordering attribute, default zIndex * @param {function} options.equalFn ordering function for equal values * @return ordering function (f0,f1) */ ol.ordering.zIndex = function(options) { if (!options) options = {}; var attr = options.attribute || 'zIndex'; if (options.equalFn) { return function(f0,f1) { if (f0.get(attr) == f1.get(attr)) return options.equalFn(f0,f1); else return f0.get(attr) < f1.get(attr) ? 1:-1; }; } else { return function(f0,f1) { if (f0.get(attr) == f1.get(attr)) return 0; else return f0.get(attr) < f1.get(attr) ? 1:-1; }; } }; /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** Pulse a point on postcompose * @deprecated use map.animateFeature instead * @param {ol.coordinates} point to pulse * @param {ol.pulse.options} pulse options param * - projection {ol.projection||String} projection of coords * - duration {Number} animation duration in ms, default 3000 * - amplitude {Number} movement amplitude 0: none - 0.5: start at 0.5*radius of the image - 1: max, default 1 * - easing {ol.easing} easing function, default ol.easing.easeOut * - style {ol.style.Image|ol.style.Style|Array} Image to draw as markup, default red circle */ ol.Map.prototype.pulse = function(coords, options) { var listenerKey; options = options || {}; // Change to map's projection if (options.projection) { coords = ol.proj.transform(coords, options.projection, this.getView().getProjection()); } // options var start = new Date().getTime(); var duration = options.duration || 3000; var easing = options.easing || ol.easing.easeOut; var style = options.style; if (!style) style = new ol.style.Circle({ radius:30, stroke:new ol.style.Stroke({color:'red', width:2 }) }); if (style instanceof ol.style.Image) style = new ol.style.Style({ image: style }); if (!(style instanceof Array)) style = [style]; var amplitude = options.amplitude || 1; if (amplitude<0) amplitude=0; var maxRadius = options.radius || 15; if (maxRadius<0) maxRadius = 5; /* var minRadius = maxRadius - (options.amplitude || maxRadius); //options.minRadius || 0; var width = options.lineWidth || 2; var color = options.color || 'red'; console.log("pulse") */ // Animate function function animate(event) { var frameState = event.frameState; var ratio = frameState.pixelRatio; var elapsed = frameState.time - start; if (elapsed > duration) ol.Observable.unByKey(listenerKey); else { var elapsedRatio = elapsed / duration; var context = event.context; context.save(); context.beginPath(); var e = easing(elapsedRatio) context.globalAlpha = easing(1 - elapsedRatio); console.log("anim") for (var i=0; i} options.colors predefined color set "classic","dark","pale","pastel","neon" / array of color string, default classic * @param {number} options.offsetX X offset in px * @param {number} options.offsetY Y offset in px * @param {number} options.animation step in an animation sequence [0,1] * @param {number} options.max maximum value for bar chart * @see [Statistic charts example](../../examples/style/map.style.chart.html) * @extends {ol.style.RegularShape} * @implements {ol.structs.IHasChecksum} * @api */ ol.style.Chart = function(opt_options) { var options = opt_options || {}; var strokeWidth = 0; if (opt_options.stroke) strokeWidth = opt_options.stroke.getWidth(); ol.style.RegularShape.call (this, { radius: options.radius + strokeWidth, fill: new ol.style.Fill({color: [0,0,0]}), rotation: options.rotation, snapToPixel: options.snapToPixel }); if (options.scale) this.setScale(options.scale); this._stroke = options.stroke; this._radius = options.radius || 20; this._donutratio = options.donutRatio || 0.5; this._type = options.type; this._offset = [options.offsetX ? options.offsetX : 0, options.offsetY ? options.offsetY : 0]; this._animation = (typeof(options.animation) == 'number') ? { animate:true, step:options.animation } : this._animation = { animate:false, step:1 }; this._max = options.max; this._data = options.data; if (options.colors instanceof Array) { this._colors = options.colors; } else { this._colors = ol.style.Chart.colors[options.colors]; if(!this._colors) this._colors = ol.style.Chart.colors.classic; } this.renderChart_(); }; ol.ext.inherits(ol.style.Chart, ol.style.RegularShape); /** Default color set: classic, dark, pale, pastel, neon */ ol.style.Chart.colors = { "classic": ["#ffa500","blue","red","green","cyan","magenta","yellow","#0f0"], "dark": ["#960","#003","#900","#060","#099","#909","#990","#090"], "pale": ["#fd0","#369","#f64","#3b7","#880","#b5d","#666"], "pastel": ["#fb4","#79c","#f66","#7d7","#acc","#fdd","#ff9","#b9b"], "neon": ["#ff0","#0ff","#0f0","#f0f","#f00","#00f"] } /** * Clones the style. * @return {ol.style.Chart} */ ol.style.Chart.prototype.clone = function() { var s = new ol.style.Chart({ type: this._type, radius: this._radius, rotation: this.getRotation(), scale: this.getScale(), data: this.getData(), snapToPixel: this.getSnapToPixel ? this.getSnapToPixel() : false, stroke: this._stroke, colors: this._colors, offsetX: this._offset[0], offsetY: this._offset[1], animation: this._animation }); s.setScale(this.getScale()); s.setOpacity(this.getOpacity()); return s; }; /** Get data associatied with the chart */ ol.style.Chart.prototype.getData = function() { return this._data; } /** Set data associatied with the chart * @param {Array} */ ol.style.Chart.prototype.setData = function(data) { this._data = data; this.renderChart_(); } /** Get symbol radius */ ol.style.Chart.prototype.getRadius = function() { return this._radius; } /** Set symbol radius * @param {number} symbol radius * @param {number} donut ratio */ ol.style.Chart.prototype.setRadius = function(radius, ratio) { this._radius = radius; this.donuratio_ = ratio || this.donuratio_; this.renderChart_(); } /** Set animation step * @param {false|number} false to stop animation or the step of the animation [0,1] */ ol.style.Chart.prototype.setAnimation = function(step) { if (step===false) { if (this._animation.animate == false) return; this._animation.animate = false; } else { if (this._animation.step == step) return; this._animation.animate = true; this._animation.step = step; } this.renderChart_(); } /** @private */ ol.style.Chart.prototype.renderChart_ = function(pixelratio) { if (!pixelratio) { if (this.getPixelRatio) { pixelratio = window.devicePixelRatio; this.renderChart_(pixelratio); if (this.getPixelRatio && pixelratio!==1) this.renderChart_(1); } else { this.renderChart_(1); } return; } var strokeStyle; var strokeWidth = 0; if (this._stroke) { strokeStyle = ol.color.asString(this._stroke.getColor()); strokeWidth = this._stroke.getWidth(); } // no atlas manager is used, create a new canvas var canvas = this.getImage(pixelratio); // draw the circle on the canvas var context = (canvas.getContext('2d')); context.save(); // reset transform context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0); context.clearRect(0, 0, canvas.width, canvas.height); context.lineJoin = 'round'; var sum=0; var i, c; for (i=0; i0 ? Number(options.scale) : 1; var ratio = scale*ol.has.DEVICE_PIXEL_RATIO || ol.has.DEVICE_PIXEL_RATIO; var ctx = canvas.getContext('2d'); if (options.image) { options.image.load(); var i; var img = options.image.getImage(); if (img.width) { canvas.width = Math.round(img.width *ratio); canvas.height = Math.round(img.height *ratio); ctx.globalAlpha = typeof(options.opacity) == 'number' ? options.opacity:1; ctx.drawImage(img, 0,0, img.width, img.height, 0, 0, canvas.width, canvas.height); pattern = ctx.createPattern(canvas, 'repeat'); } else { var self = this; pattern = [0,0,0,0]; img.onload = function () { canvas.width = Math.round(img.width *ratio); canvas.height = Math.round(img.height *ratio); ctx.globalAlpha = typeof(options.opacity) == 'number' ? options.opacity:1; ctx.drawImage(img, 0,0, img.width, img.height, 0, 0, canvas.width, canvas.height); pattern = ctx.createPattern(canvas, 'repeat'); self.setColor(pattern); } } } else { var pat = this.getPattern_(options); canvas.width = Math.round(pat.width *ratio); canvas.height = Math.round(pat.height *ratio); ctx.beginPath(); if (options.fill) { ctx.fillStyle = ol.color.asString(options.fill.getColor()); ctx.fillRect(0,0, canvas.width, canvas.height); } ctx.scale(ratio,ratio); ctx.lineCap = "round"; ctx.lineWidth = pat.stroke || 1; ctx.fillStyle = ol.color.asString(options.color||"#000"); ctx.strokeStyle = ol.color.asString(options.color||"#000"); if (pat.circles) for (i=0; i180) a -= 360; a *= Math.PI/180; var cos = Math.cos(a); var sin = Math.sin(a); if (Math.abs(sin)<0.0001) { pat.width = pat.height = d; pat.lines = [ [ 0,0.5, d, 0.5 ] ]; pat.repeat = [ [0,0], [0,d] ]; } else if (Math.abs(cos)<0.0001) { pat.width = pat.height = d; pat.lines = [ [ 0.5,0, 0.5, d] ]; pat.repeat = [ [0,0], [d,0] ]; if (options.pattern=='cross') { pat.lines.push ([ 0,0.5, d, 0.5 ]); pat.repeat.push([0,d]); } } else { var w = pat.width = Math.round(Math.abs(d/sin)) || 1; var h = pat.height = Math.round(Math.abs(d/cos)) || 1; if (options.pattern=='cross') { pat.lines = [ [-w,-h, 2*w,2*h], [2*w,-h, -w,2*h] ]; pat.repeat = [ [0,0] ]; } else if (cos*sin>0) { pat.lines = [ [-w,-h, 2*w,2*h] ]; pat.repeat = [ [0,0], [w,0], [0,h] ]; } else { pat.lines = [ [2*w,-h, -w,2*h] ]; pat.repeat = [ [0,0], [-w,0], [0,h] ]; } } pat.stroke = options.size===0 ? 0 : options.size||4; break; } default: break; } return pat } /** Static fuction to add char patterns * @param {title} * @param {*} options * @param {integer} [options.size=10] default 10 * @param {integer} [options. width=10] default 10 * @param {integer} [options.height=10] default 10 * @param {Array} [options.circles] * @param {Array} [options.lines] * @param {integer} [options.stroke] * @param {bool} [options.fill] * @param {char} [option.char] * @param {string} [font="10px Arial"] */ ol.style.FillPattern.addPattern = function (title, options) { if (!options) options={}; ol.style.FillPattern.prototype.patterns[title || options.char] = { width: options.width || options.size || 10, height: options.height || options.size || 10, font: options.font, char: options.char, circles: options.circles, lines: options.lines, repeat: options.repeat, stroke: options.stroke, angle: options.angle, fill: options.fill } } /** Patterns definitions * @see pattern generator http://www.imagico.de/map/jsdotpattern.php */ ol.style.FillPattern.prototype.patterns = { "hatch": { width:5, height:5, lines:[[0,2.5,5,2.5]], stroke:1 }, "cross": { width:7, height:7, lines:[[0,3,10,3],[3,0,3,10]], stroke:1 }, "dot": { width:8, height:8, circles:[[5,5,2]], stroke:false, fill:true, }, "circle": { width:10, height:10, circles:[[5,5,2]], stroke:1, fill:false, }, "square": { width:10, height:10, lines:[[3,3, 3,8, 8,8, 8,3, 3,3]], stroke:1, fill:false, }, "tile": { width:10, height:10, lines:[[3,3, 3,8, 8,8, 8,3, 3,3]], fill:true, }, "woven": { width: 12, height: 12, lines: [[ 3,3, 9,9 ],[0,12, 3,9], [9,3, 12,0], [-1,1,1,-1], [13,11,11,13]], stroke: 1 }, "crosses": { width: 8, height: 8, lines: [[ 2,2, 6,6 ],[2,6,6,2]], stroke: 1 }, "caps": { width: 8, height: 8, lines: [[ 2,6, 4,2, 6,6 ]], stroke: 1 }, "nylon": { width: 20, height: 20, // lines: [[ 0,5, 0,0, 5,0 ],[ 5,10, 10,10, 10,5 ], [ 10,15, 10,20, 15,20 ],[ 15,10, 20,10, 20,15 ]], // repeat: [[0,0], [20,0], [0,20], [-20,0], [0,-20], [-20,-20]], lines: [[ 1,6, 1,1, 6,1 ],[ 6,11, 11,11, 11,6 ], [ 11,16, 11,21, 16,21 ],[ 16,11, 21,11, 21,16 ]], repeat: [[0,0], [-20,0], [0,-20] ], stroke: 1 }, "hexagon": { width: 20, height: 12, lines: [[ 0,10, 4,4, 10,4, 14,10, 10,16, 4,16, 0,10 ]], stroke:1, repeat:[[0,0],[10,6],[10,-6],[-10,-6]] }, "cemetry": { width:15, height:19, lines:[[0,3.5,7,3.5],[3.5,0,3.5,10], //[7,12.5,14,12.5],[10.5,9,10.5,19] ], stroke:1, repeat:[[0,0],[7,9]] }, "sand": { width: 20, height: 20, circles:[ [1,2,1],[9,3,1],[2,16,1], [7,8,1],[6,14,1],[4,19,1], [14,2,1],[12,10,1],[14,18,1], [18,8,1],[18,14,1] ], fill:1 }, "conglomerate": { width: 60, height: 40, circles:[[2,4,1],[17,3,1],[26,18,1],[12,17,1],[5,17,2],[28,11,2]], lines:[[7,5, 6,7, 9,9, 11,8, 11,6, 9,5, 7,5], [16,10, 15,13, 16,14, 19,15, 21,13, 22,9, 20,8, 19,8, 16,10], [24,6, 26,7, 27,5, 26,4, 24,4, 24,6]], repeat: [[30,0], [-15,20], [15,20], [45,20]], stroke:1 }, "conglomerate2": { width:60, height:40, circles:[[2,4,1],[17,3,1],[26,18,1],[12,17,1],[5,17,2],[28,11,2]], lines:[ [7,5, 6,7, 9,9, 11,8, 11,6, 9,5, 7,5], [16,10, 15,13, 16,14, 19,15, 21,13, 22,9, 20,8, 19,8, 16,10], [24,6, 26,7, 27,5, 26,4, 24,4, 24,6] ], repeat: [[30,0], [-15,20], [15,20], [45,20]], fill:1 }, "gravel": { width:15, height:10, circles:[[4,2,1],[5,9,1],[1,7,1]],//[9,9,1],,[15,2,1]], lines:[[7,5, 6,6, 7,7, 8,7, 9,7, 10,5, 9,4, 7,5], [11,2, 14,4, 14,1, 12,1, 11,2]], stroke:1 }, "brick": { width:18, height:16, lines:[ [0,1,18,1],[0,10,18,10], [6,1,6,10],[12,10,12,18],[12,0,12,1]], stroke:1 }, "dolomite": { width:20, height:16, lines:[[0,1,20,1],[0,9,20,9],[1,9,6,1],[11,9,14,16],[14,0,14.4,1]], stroke:1 }, "coal": { width:20, height:16, lines:[[1,5, 7,1, 7,7], [11,10, 12,5, 18,9], [5,10, 2,15, 9,15,], [15,16, 15,13, 20,16], [15,0, 15,2, 20,0]], fill:1 }, "breccia": { width:20, height:16, lines:[[1,5, 7,1, 7,7, 1,5], [11,10, 12,5, 18,9, 11,10], [5,10, 2,15, 9,15, 5,10], [15,16, 15,13, 22,18], [15,0, 15,2, 20,0] ], stroke:1, }, "clay": { width:20, height:20, lines:[[0,0, 3,11, 0,20], [11,0, 10,3, 13,13, 11,20], [0,0, 10,3, 20,0], [0,12, 3,11, 13,13, 20,12]], stroke:1 }, "flooded": { width:15, height:10, lines:[ [0,1,10,1],[0,6,5,6], [10,6,15,6]], stroke:1 }, "chaos": { width:40, height:40, lines:[[40,2, 40,0, 38,0, 40,2], [4,0, 3,2, 2,5, 0,0, 0,3, 2,7, 5,6, 7,7, 8,10, 9,12, 9,13, 9,14, 8,14, 6,15, 2,15, 0,20, 0,22, 2,20, 5,19, 8,15, 10,14, 11,12.25, 10,12, 10,10, 12,9, 13,7, 12,6, 13,4, 16,7, 17,4, 20,0, 18,0, 15,3, 14,2, 14,0, 12,1, 11,0, 10,1, 11,4, 10,7, 9,8, 8,5, 6,4, 5,3, 5,1, 5,0, 4,0], [7,1, 7,3, 8,3, 8,2, 7,1], [4,3, 5,5, 4,5, 4,3], [34,5, 33,7, 38,10, 38,8, 36,5, 34,5], [ 27,0, 23,2, 21,8, 30,0, 27,0], [25,8, 26,12, 26,16, 22.71875,15.375, 20,13, 18,15, 17,18, 13,22, 17,21, 19,22, 21,20, 19,18, 22,17, 30,25, 26,26, 24,28, 21.75,33.34375, 20,36, 18,40, 20,40, 24,37, 25,32, 27,31, 26,38, 27,37, 30,32, 32,35, 36,37, 38,40, 38,39, 40,40, 37,36, 34,32, 37,31, 36,29, 33,27, 34,24, 39,21, 40,21, 40,16, 37,20, 31,22, 32,25, 27,20, 29,15, 30,20, 32,20, 34,18, 33,12, 31,11, 29,14, 26,9, 25,8], [39,24, 37,26, 40,28, 39,24], [13,15, 9,19, 14,18, 13,15], [18,23, 14,27, 16,27, 17,25, 20,26, 18,23], [6,24, 2,26, 1,28, 2,30, 5,28, 12,30, 16,32, 18,30, 15,30, 12,28, 9,25, 7,27, 6,24], [29,27, 32,28, 33,31, 30,29, 27,28, 29,27], [5,35, 1,33, 3,36, 13,38, 15,35, 10,36, 5,35]], fill:1, }, "grass": { width:27, height:22, lines: [[0,10.5,13,10.5], [2.5,10,1.5,7], [4.5,10, 4.5,5, 3.5,4 ], [7,10, 7.5,6, 8.5,3], [10,10,11,6]], repeat: [[0,0],[14,10]], stroke:1 }, "swamp": { width:24, height:23, lines:[ [0,10.5,9.5,10.5], [2.5,10,2.5,7], [4.5,10,4.5,4], [6.5,10,6.5,6], [3,12.5,7,12.5] ], repeat: [[0,0],[14,10]], stroke:1 }, "reed": { width:26, height:23, lines:[ [2.5,10,2,7], [4.5,10,4.2,4], [6.5,10,6.8,4], [8.5,10,9,6], [3.7,4,3.7,2.5], [4.7,4,4.7,2.5], [6.3,4,6.3,2.5], [7.3,4,7.3,2.5] ], circles:[ [4.2,2.5,.5], [18.2,12.5,.5], [6.8,2.5,.5], [20.8,12.5,.5], [9,6,.5], [23,16,.5] ], repeat: [[0,0],[14,10]], stroke:1 }, "wave": { width:10, height:8, lines:[ [0,0, 5,4, 10,0] ], stroke:1 }, "vine": { width:13, height:13, lines:[[3,0,3,6],[9,7,9,13]], stroke:1.0 }, "forest": { width:55, height:30, circles:[[7,7,3.5],[20,20,1.5],[42,22,3.5],[35,5,1.5]], stroke:1 }, "forest2": { width:55, height:30, circles:[[7,7,3.5],[20,20,1.5],[42,22,3.5],[35,5,1.5]], fill:1, stroke:1 }, "scrub": { width:26, height:20, lines:[ [1,4, 4,8, 6,4] ], circles:[[20,13,1.5]], stroke:1, }, "tree": { width:30, height:30, lines:[[7.78,10.61,4.95,10.61,4.95,7.78,3.54,7.78,2.12,6.36,0.71,6.36,0,4.24,0.71,2.12,4.24,0,7.78,0.71,9.19,3.54,7.78,4.95,7.07,7.07,4.95,7.78]], repeat: [[3,1],[18,16]], stroke:1 }, "tree2": { width:30, height:30, lines:[[7.78,10.61,4.95,10.61,4.95,7.78,3.54,7.78,2.12,6.36,0.71,6.36,0,4.24,0.71,2.12,4.24,0,7.78,0.71,9.19,3.54,7.78,4.95,7.07,7.07,4.95,7.78,4.95,10.61,7.78,10.61]], repeat: [[3,1],[18,16]], fill:1, stroke:1 }, "pine": { width:30, height:30, lines:[[5.66,11.31,2.83,11.31,2.83,8.49,0,8.49,2.83,0,5.66,8.49,2.83,8.49]], repeat:[[3,1],[18,16]], stroke:1 }, "pine2": { width:30, height:30, lines:[[5.66,11.31,2.83,11.31,2.83,8.49,0,8.49,2.83,0,5.66,8.49,2.83,8.49,2.83,11.31,5.66,11.31]], repeat:[[3,1],[18,16]], fill:1, stroke:1 }, "mixtree": { width:30, height:30, lines:[ [7.78,10.61,4.95,10.61,4.95,7.78,3.54,7.78,2.12,6.36,0.71,6.36,0,4.24,0.71,2.12,4.24,0,7.78,0.71,9.19,3.54,7.78,4.95,7.07,7.07,4.95,7.78,4.95,10.61,7.78,10.61], [23.66, 27.31, 20.83, 27.31, 20.83, 24.49, 18, 24.49, 20.83, 16, 23.66, 24.49, 20.83, 24.49, 20.83, 27.31, 23.66, 27.31] ], repeat: [[3,1]], stroke:1 }, "mixtree2": { width:30, height:30, lines:[ [7.78,10.61,4.95,10.61,4.95,7.78,3.54,7.78,2.12,6.36,0.71,6.36,0,4.24,0.71,2.12,4.24,0,7.78,0.71,9.19,3.54,7.78,4.95,7.07,7.07,4.95,7.78,4.95,10.61,7.78,10.61], [23.66, 27.31, 20.83, 27.31, 20.83, 24.49, 18, 24.49, 20.83, 16, 23.66, 24.49, 20.83, 24.49, 20.83, 27.31, 23.66, 27.31] ], repeat: [[3,1]], fill:1, stroke:1 }, "pines": { width:22, height:20, lines:[[1,4,3.5,1,6,4],[1,8,3.5,5,6,8],[3.5,1,3.5,11],[12,14.5,14.5,14,17,14.5],[12,18,17,18],[14.5,12,14.5,18]], repeat: [[2,1]], stroke:1 }, "rock": { width:20, height:20, lines:[ [1,0,1,9],[4,0,4,9],[7,0,7,9], [10,1,19,1],[10,4,19,4],[10,7,19,7], [0,11,9,11],[0,14,9,14],[0,17,9,17], [12,10,12,19],[15,10,15,19],[18,10,18,19] ], repeat:[[0.5,0.5]], stroke:1 }, "rocks": { width:20, height:20, lines:[ [5,0, 3,0, 5,4, 4,6, 0,3, 0,5, 3,6, 5,9, 3.75,10, 2.5,10, 0,9, 0,10, 4,11, 5,14, 4,15, 0,13, 0,13, 0,13, 0,14, 0,14, 5,16, 5,18, 3,19, 0,19, -0.25,19.9375, 5,20, 10,19, 10,20, 11,20, 12,19, 14,20, 15,20, 17,19, 20,20, 20,19, 19,16, 20,15, 20,11, 20,10, 19,8, 20,5, 20,0, 19,0, 20,2, 19,4, 17,4, 16,3, 15,0, 14,0, 15,4, 11,5, 10,4, 11,0, 10,0, 9,4, 6,5, 5,0,], [18,5, 19,6, 18,10, 16,10, 14,9, 16,5, 18,5], [5,6, 9,5, 10,6, 10,9, 6,10, 5,6], [14,5, 14,8, 13,9, 12,9, 11,7, 12,5, 14,5], [ 5,11, 8,10, 9,11, 10,14, 6,15, 6,15, 5,11], [13,10, 14,11, 15,14, 15,14, 15,14, 11,15, 10,11, 11,10, 13,10], [15,12, 16,11, 19,11, 19,15, 16,14, 16,14, 15,12], [6,16, 9,15, 10,18, 5,19, 6,16], [10,16, 14,16, 14,18, 13,19, 11,18, 10,16], [15,15, 18,16, 18,18, 16,19, 15,18, 15,15]], stroke:1 } }; /* 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). */ /** Flow line style * Draw LineString with a variable color / width * NB: the FlowLine style doesn't impress the hit-detection. * If you want your lines to be sectionable you have to add your own style to handle this. * (with transparent line: stroke color opacity to .1 or zero width) * @constructor * @extends {ol.style.Style} * @param {Object} options * @param {boolean} options.visible draw only the visible part of the line, default true * @param {number|function} options.width Stroke width or a function that gets a feature and the position (beetween [0,1]) and returns current width * @param {number} options.width2 Final stroke width (if width is not a function) * @param {number} options.arrow Arrow at start (-1), at end (1), at both (2), none (0), default geta * @param {ol.colorLike|function} options.color Stroke color or a function that gets a feature and the position (beetween [0,1]) and returns current color * @param {ol.colorLike} options.color2 Final sroke color if color is nor a function * @param {ol.colorLike} options.arrowColor Color of arrows, if not defined used color or color2 * @param {string} options.lineCap CanvasRenderingContext2D.lineCap 'butt' | 'round' | 'square', default 'butt' * @param {number|ol.size} options.arrowSize height and width of the arrow, default 16 * @param {boolean} [options.noOverlap=false] prevent segments overlaping * @param {number} options.offset0 offset at line start * @param {number} options.offset1 offset at line end */ ol.style.FlowLine = function(options) { if (!options) options = {}; ol.style.Style.call (this, { renderer: this._render.bind(this), stroke: options.stroke, text: options.text, zIndex: options.zIndex, geometry: options.geometry }); // Draw only visible this._visible = (options.visible !== false); // Width if (typeof options.width === 'function') { this._widthFn = options.width; } else { this.setWidth(options.width); } this.setWidth2(options.width2); // Color if (typeof options.color === 'function') { this._colorFn = options.color; } else { this.setColor(options.color); } this.setColor2(options.color2); // LineCap this.setLineCap(options.lineCap); // Arrow this.setArrow(options.arrow); this.setArrowSize(options.arrowSize); this.setArrowColor(options.arrowColor); // Offset this._offset = [0,0]; this.setOffset(options.offset0, 0); this.setOffset(options.offset1, 1); // Overlap this._noOverlap = options.noOverlap; }; ol.ext.inherits(ol.style.FlowLine, ol.style.Style); /** Set the initial width * @param {number} width width, default 0 */ ol.style.FlowLine.prototype.setWidth = function(width) { this._width = width || 0; }; /** Set the final width * @param {number} width width, default 0 */ ol.style.FlowLine.prototype.setWidth2 = function(width) { this._width2 = width; }; /** Get offset at start or end * @param {number} where 0=start, 1=end * @return {number} width */ ol.style.FlowLine.prototype.getOffset = function(where) { return this._offset[where]; }; /** Add an offset at start or end * @param {number} width * @param {number} where 0=start, 1=end */ ol.style.FlowLine.prototype.setOffset = function(width, where) { width = Math.max(0, parseFloat(width)); switch(where) { case 0: { this._offset[0] = width; break; } case 1: { this._offset[1] = width; break; } } }; /** Set the LineCap * @param {steing} cap LineCap (round or butt), default butt */ ol.style.FlowLine.prototype.setLineCap = function(cap) { this._lineCap = (cap==='round' ? 'round' : 'butt'); }; /** Get the current width at step * @param {ol.feature} feature * @param {number} step current drawing step beetween [0,1] * @return {number} */ ol.style.FlowLine.prototype.getWidth = function(feature, step) { if (this._widthFn) return this._widthFn(feature, step); var w2 = (typeof(this._width2) === 'number') ? this._width2 : this._width; return this._width + (w2-this._width) * step; }; /** Set the initial color * @param {ol.colorLike} color */ ol.style.FlowLine.prototype.setColor = function(color) { try{ this._color = ol.color.asArray(color); } catch(e) { this._color = [0,0,0,1]; } }; /** Set the final color * @param {ol.colorLike} color */ ol.style.FlowLine.prototype.setColor2 = function(color) { try { this._color2 = ol.color.asArray(color); } catch(e) { this._color2 = null; } }; /** Set the arrow color * @param {ol.colorLike} color */ ol.style.FlowLine.prototype.setArrowColor = function(color) { try { this._acolor = ol.color.asString(color); } catch(e) { this._acolor = null; } }; /** Get the current color at step * @param {ol.feature} feature * @param {number} step current drawing step beetween [0,1] * @return {string} */ ol.style.FlowLine.prototype.getColor = function(feature, step) { if (this._colorFn) return ol.color.asString(this._colorFn(feature, step)); var color = this._color; var color2 = this._color2 || this._color return 'rgba('+ + Math.round(color[0] + (color2[0]-color[0]) * step) +',' + Math.round(color[1] + (color2[1]-color[1]) * step) +',' + Math.round(color[2] + (color2[2]-color[2]) * step) +',' + (color[3] + (color2[3]-color[3]) * step) +')'; }; /** Get arrow */ ol.style.FlowLine.prototype.getArrow = function() { return this._arrow; }; /** Set arrow * @param {number} n -1 | 0 | 1 | 2, default: 0 */ ol.style.FlowLine.prototype.setArrow = function(n) { this._arrow = parseInt(n); if (this._arrow < -1 || this._arrow > 2) this._arrow = 0; } /** getArrowSize * @return {ol.size} */ ol.style.FlowLine.prototype.getArrowSize = function() { return this._arrowSize || [16,16]; }; /** setArrowSize * @param {number|ol.size} size */ ol.style.FlowLine.prototype.setArrowSize = function(size) { if (Array.isArray(size)) this._arrowSize = size; else if (typeof(size) === 'number') this._arrowSize = [size,size]; }; /** drawArrow * @param {CanvasRenderingContext2D} ctx * @param {ol.coordinate} p0 * @param ol.coordinate} p1 * @param {number} width * @param {number} ratio pixelratio * @private */ ol.style.FlowLine.prototype.drawArrow = function (ctx, p0, p1, width, ratio) { var asize = this.getArrowSize()[0] * ratio; var l = ol.coordinate.dist2d(p0, p1); var dx = (p0[0]-p1[0])/l; var dy = (p0[1]-p1[1])/l; width = Math.max(this.getArrowSize()[1]/2, width/2) * ratio; ctx.beginPath(); ctx.moveTo(p0[0],p0[1]); ctx.lineTo(p0[0]-asize*dx+width*dy, p0[1]-asize*dy-width*dx); ctx.lineTo(p0[0]-asize*dx-width*dy, p0[1]-asize*dy+width*dx); ctx.lineTo(p0[0],p0[1]); ctx.fill(); }; /** Renderer function * @param {Array} geom The pixel coordinates of the geometry in GeoJSON notation * @param {ol.render.State} e The olx.render.State of the layer renderer */ ol.style.FlowLine.prototype._render = function(geom, e) { if (e.geometry.getType()==='LineString') { var i, g, p, ctx = e.context; // Get geometry used at drawing if (!this._visible) { var a = e.pixelRatio / e.resolution; var cos = Math.cos(e.rotation) var sin = Math.sin(e.rotation) g = e.geometry.getCoordinates(); var dx = geom[0][0] - g[0][0] * a *cos - g[0][1] * a *sin ; var dy = geom[0][1] - g[0][0] * a * sin + g[0][1] * a * cos; geom = []; for (i=0; p=g[i]; i++) { geom[i] = [ dx + p[0] * a * cos + p[1] * a * sin, dy + p[0] * a * sin - p[1] * a * cos, p[2] ]; } } var asize = this.getArrowSize()[0] * e.pixelRatio; ctx.save(); // Offsets if (this.getOffset(0)) this._splitAsize(geom, this.getOffset(0) * e.pixelRatio) if (this.getOffset(1)) this._splitAsize(geom, this.getOffset(1) * e.pixelRatio, true) // Arrow 1 if (geom.length>1 && (this.getArrow()===-1 || this.getArrow()===2)) { p = this._splitAsize(geom, asize); if (this._acolor) ctx.fillStyle = this._acolor; else ctx.fillStyle = this.getColor(e.feature, 0); this.drawArrow(ctx, p[0], p[1], this.getWidth(e.feature, 0), e.pixelRatio); } // Arrow 2 if (geom.length>1 && this.getArrow()>0) { p = this._splitAsize(geom, asize, true); if (this._acolor) ctx.fillStyle = this._acolor; else ctx.fillStyle = this.getColor(e.feature, 1); this.drawArrow(ctx, p[0], p[1], this.getWidth(e.feature, 1), e.pixelRatio); } // Split into var geoms = this._splitInto(geom, 255, 2); var k = 0; var nb = geoms.length; // Draw ctx.lineJoin = 'round'; ctx.lineCap = this._lineCap || 'butt'; if (geoms.length > 1) { for (k=0; k asize) { p = [p[0]+(p1[0]-p[0])*(asize-d)/dl, p[1]+(p1[1]-p[1])*(asize-d)/dl]; dl = ol.coordinate.dist2d(p,p0); if (end) { geom.push(p1); geom.push(p); geom.push([p[0]+(p0[0]-p[0])/dl, p[1]+(p0[1]-p[1])/dl]); } else { geom.unshift(p1); geom.unshift(p); geom.unshift([p[0]+(p0[0]-p[0])/dl, p[1]+(p0[1]-p[1])/dl]); } break; } d += dl; p = p1; } return [p0,p]; }; /** Split line geometry into equal length geometries * @param {Array} geom * @param {number} nb number of resulting geometries, default 255 * @param {number} nim minimum length of the resulting geometries, default 1 */ ol.style.FlowLine.prototype._splitInto = function(geom, nb, min) { var i, p; var dt = this._noOverlap ? 1 : .9; // Split geom into equal length geoms var geoms = []; var dl, l = 0; for (i=1; p=geom[i]; i++) { l += ol.coordinate.dist2d(geom[i-1], p); } var length = Math.max (min||2, l/(nb||255)); var p0 = geom[0]; l = 0; var g = [p0]; i = 1; p = geom[1]; while (i < geom.length) { var dx = p[0]-p0[0]; var dy = p[1]-p0[1]; dl = Math.sqrt(dx*dx + dy*dy); if (l+dl > length) { var d = (length-l) / dl; g.push([ p0[0] + dx * d, p0[1] + dy * d ]); geoms.push(g); p0 =[ p0[0] + dx * d*dt, p0[1] + dy * d*dt ]; g = [p0]; l = 0; } else { l += dl; p0 = p; g.push(p0); i++; p = geom[i]; } } geoms.push(g); return geoms; } /* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * @classdesc * A marker style to use with font symbols. * * @constructor * @param {} options Options. * @param {string} [options.color] default #000 * @param {string} options.glyph the glyph name or a char to display as symbol. * The name must be added using the {@link ol.style.FontSymbol.addDefs} function. * @param {string} [options.text] a text to display as a glyph * @param {string} [options.font] font to use with the text option * @param {string} options.form * none|circle|poi|bubble|marker|coma|shield|blazon|bookmark|hexagon|diamond|triangle|sign|ban|lozenge|square * a form that will enclose the glyph, default none * @param {number} options.radius * @param {number} options.rotation * @param {boolean} options.rotateWithView * @param {number} [options.opacity=1] * @param {number} [options.fontSize=1] default 1 * @param {string} [options.fontStyle] the font style (bold, italic, bold italic, etc), default none * @param {boolean} options.gradient true to display a gradient on the symbol * @param {number} [options.offsetX=0] default 0 * @param {number} [options.offsetY=0] default 0 * @param {_ol_style_Fill_} options.fill * @param {_ol_style_Stroke_} options.stroke * @extends {ol.style.RegularShape} * @implements {ol.structs.IHasChecksum} * @api */ ol.style.FontSymbol = function(options) { options = options || {}; var strokeWidth = 0; if (options.stroke) { strokeWidth = options.stroke.getWidth(); } ol.style.RegularShape.call (this, { radius: options.radius, fill:options.fill, rotation:options.rotation, rotateWithView: options.rotateWithView }); if (typeof(options.opacity)=="number") this.setOpacity(options.opacity); this.color_ = options.color; this.fontSize_ = options.fontSize || 1; this.fontStyle_ = options.fontStyle || ''; this.stroke_ = options.stroke; this.fill_ = options.fill; this.radius_ = options.radius -strokeWidth; this.form_ = options.form || "none"; this.gradient_ = options.gradient; this.offset_ = [options.offsetX ? options.offsetX :0, options.offsetY ? options.offsetY :0]; if (options.glyph) this.glyph_ = this.getGlyph(options.glyph); else this.glyph_ = this.getTextGlyph(options.text||'', options.font); this.renderMarker_(); }; ol.ext.inherits(ol.style.FontSymbol, ol.style.RegularShape); /** Cool stuff to get the image symbol for a style */ ol.style.Image.prototype.getImagePNG = function() { var canvas = this.getImage(); if (canvas) { try { return canvas.toDataURL("image/png"); } catch(e) { return false; } } else { return false; } } /** Font defs */ ol.style.FontSymbol.prototype.defs = { 'fonts':{}, 'glyphs':{} }; /** Static function : add new font defs * @param {String|Object} font the font desciption * @param {} glyphs a key / value list of glyph definitions. * Each key is the name of the glyph, * the value is an object that code the font, the caracter code, * the name and a search string for the glyph. */ ol.style.FontSymbol.addDefs = function(font, glyphs) { var thefont = font; if (typeof(font) == 'string') thefont = { font:font, name:font, copyright:'' }; if (!thefont.font || typeof(thefont.font) !== 'string') { console.log('bad font def'); return; } var fontname = thefont.font; ol.style.FontSymbol.prototype.defs.fonts[fontname] = thefont; for (var i in glyphs) { var g = glyphs[i]; if (typeof(g) === 'string' && g.length==1) g = { char: g }; ol.style.FontSymbol.prototype.defs.glyphs[i] = { font: thefont.font, char: g.char || ''+String.fromCharCode(g.code) || '', theme: g.theme || thefont.name, name: g.name || i, search: g.search || '' }; } }; /** Clones the style. * @return {ol.style.FontSymbol} */ ol.style.FontSymbol.prototype.clone = function() { var g = new ol.style.FontSymbol({ glyph: '', color: this.color_, fontSize: this.fontSize_, fontStyle: this.fontStyle_, stroke: this.stroke_, fill: this.fill_, radius: this.radius_ + (this.stroke_ ? this.stroke_.getWidth():0), form: this.form_, gradient: this.gradient_, offsetX: this.offset_[0], offsetY: this.offset_[1], opacity: this.getOpacity(), rotation: this.getRotation(), rotateWithView: this.getRotateWithView() }); g.setScale(this.getScale()); g.glyph_ = this.glyph_; g.renderMarker_(); return g; }; /** Get the fill style for the symbol. * @return {ol.style.Fill} Fill style. * @api */ ol.style.FontSymbol.prototype.getFill = function() { return this.fill_; }; /** Get the stroke style for the symbol. * @return {_ol_style_Stroke_} Stroke style. * @api */ ol.style.FontSymbol.prototype.getStroke = function() { return this.stroke_; }; /** Get the glyph definition for the symbol. * @param {string|undefined} name a glyph name to get the definition, default return the glyph definition for the style. * @return {*} * @api */ ol.style.FontSymbol.prototype.getGlyph = function(name) { if (name) return ol.style.FontSymbol.prototype.defs.glyphs[name] || { font:'sans-serif', char:name.charAt(0), theme:'none', name:'none', search:'' }; else return this.glyph_; }; /** Get glyph definition given a text and a font * @param {string|undefined} text * @param {string} [font] the font for the text * @return {*} * @api */ ol.style.FontSymbol.prototype.getTextGlyph = function(text, font) { return { font: font || 'sans-serif', char:String(text), theme:'none', name:'none', search:'' }; }; /** * Get the glyph name. * @return {string} the name * @api */ ol.style.FontSymbol.prototype.getGlyphName = function() { for (var i in ol.style.FontSymbol.prototype.defs.glyphs) { if (ol.style.FontSymbol.prototype.defs.glyphs[i] === this.glyph_) return i; } return ''; }; /** * Get the stroke style for the symbol. * @return {_ol_style_Stroke_} Stroke style. * @api */ ol.style.FontSymbol.prototype.getFontInfo = function(glyph) { return ol.style.FontSymbol.prototype.defs.fonts[glyph.font]; } /** @private */ ol.style.FontSymbol.prototype.renderMarker_ = function(pixelratio) { if (!pixelratio) { if (this.getPixelRatio) { pixelratio = window.devicePixelRatio; this.renderMarker_(pixelratio); if (this.getPixelRatio && pixelratio!==1) this.renderMarker_(1); } else { this.renderMarker_(1); } return; } var strokeStyle; var strokeWidth = 0; if (this.stroke_) { strokeStyle = ol.color.asString(this.stroke_.getColor()); strokeWidth = this.stroke_.getWidth(); } // get canvas var canvas = this.getImage(pixelratio); //console.log(this.getImage().width+" / "+(2 * (this.radius_ + strokeWidth) + 1)); /** @type {ol.style.FontSymbol.RenderOptions} */ var renderOptions = { strokeStyle: strokeStyle, strokeWidth: strokeWidth, size: canvas.width/pixelratio, }; // draw the circle on the canvas var context = (canvas.getContext('2d')); context.clearRect(0, 0, canvas.width, canvas.height); this.drawMarker_(renderOptions, context, 0, 0, pixelratio); // Set anchor / displacement if (this.setDisplacement) { this.setDisplacement([this.offset_[0], -this.offset_[1]]) } else { var a = this.getAnchor(); a[0] = canvas.width / 2 - this.offset_[0]; a[1] = canvas.width / 2 - this.offset_[1]; } }; /** * @private * @param {ol.style.FontSymbol.RenderOptions} renderOptions * @param {CanvasRenderingContext2D} context */ ol.style.FontSymbol.prototype.drawPath_ = function(renderOptions, context) { var s = 2*this.radius_+renderOptions.strokeWidth+1; var w = renderOptions.strokeWidth/2; var c = renderOptions.size / 2; // Transfo to place the glyph at the right place var transfo = { fac:1, posX:renderOptions.size / 2, posY:renderOptions.size / 2 }; context.lineJoin = 'round'; context.beginPath(); // Draw the path with the form switch (this.form_) { case "none": { transfo.fac=1; break; } case "circle": case "ban": { context.arc ( c, c, s/2, 0, 2 * Math.PI, true); break; } case "poi": { context.arc ( c, c -0.4*this.radius_, 0.6*this.radius_, 0.15*Math.PI, 0.85*Math.PI, true); context.lineTo ( c-0.89*0.05*s, (0.95+0.45*0.05)*s+w); context.arc ( c, 0.95*s+w, 0.05*s, 0.85*Math.PI, 0.15*Math.PI, true); transfo = { fac:0.45, posX:c, posY:c -0.35*this.radius_ }; break; } case "bubble": { context.arc ( c, c -0.2*this.radius_, 0.8*this.radius_, 0.4*Math.PI, 0.6*Math.PI, true); context.lineTo ( 0.5*s+w, s+w); transfo = { fac:0.7, posX:c, posY:c -0.2*this.radius_ }; break; } case "marker": { context.arc ( c, c -0.2*this.radius_, 0.8*this.radius_, 0.25*Math.PI, 0.75*Math.PI, true); context.lineTo ( 0.5*s+w, s+w); transfo = { fac:0.7, posX: c, posY: c -0.2*this.radius_ }; break; } case "coma": { context.moveTo ( c + 0.8*this.radius_, c -0.2*this.radius_); context.quadraticCurveTo ( 0.95*s+w, 0.75*s+w, 0.5*s+w, s+w); context.arc ( c, c -0.2*this.radius_, 0.8*this.radius_, 0.45*Math.PI, 0, false); transfo = { fac:0.7, posX: c, posY: c -0.2*this.radius_ }; break; } default: { var pts; switch (this.form_) { case "shield": { pts = [ 0.05,0, 0.95,0, 0.95,0.8, 0.5,1, 0.05,0.8, 0.05,0 ]; transfo.posY = 0.45*s+w ; break; } case "blazon": { pts = [ 0.1,0, 0.9,0, 0.9,0.8, 0.6,0.8, 0.5,1, 0.4,0.8, 0.1,0.8, 0.1,0 ]; transfo.fac = 0.8; transfo.posY = 0.4*s+w ; break; } case "bookmark": { pts = [ 0.05,0, 0.95,0, 0.95,1, 0.5,0.8, 0.05,1, 0.05,0 ]; transfo.fac = 0.9; transfo.posY = 0.4*s+w ; break; } case "hexagon": { pts = [ 0.05,0.2, 0.5,0, 0.95,0.2, 0.95,0.8, 0.5,1, 0.05,0.8, 0.05,0.2 ]; transfo.fac = 0.9; transfo.posY = 0.5*s+w ; break; } case "diamond": { pts = [ 0.25,0, 0.75,0, 1,0.2, 1,0.4, 0.5,1, 0,0.4, 0,0.2, 0.25,0 ]; transfo.fac = 0.75 ; transfo.posY = 0.35*s+w ; break; } case "triangle": { pts = [ 0,0, 1,0, 0.5,1, 0,0 ]; transfo.fac = 0.6 ; transfo.posY = 0.3*s+w ; break; } case "sign": { pts = [ 0.5,0.05, 1,0.95, 0,0.95, 0.5,0.05 ]; transfo.fac = 0.7 ; transfo.posY = 0.65*s+w ; break; } case "lozenge": { pts = [ 0.5,0, 1,0.5, 0.5,1, 0,0.5, 0.5,0 ]; transfo.fac = 0.7; break; } case "square": default: { pts = [ 0,0, 1,0, 1,1, 0,1, 0,0 ]; break; } } for (var i=0; i} geom The pixel coordinates of the geometry in GeoJSON notation * @param {ol.render.State} e The olx.render.State of the layer renderer */ ol.style.Profile.prototype._render = function(geom, e) { if (!/Z/.test(e.feature.getGeometry().getLayout())) return; var g = e.geometry.getCoordinates(); switch (e.geometry.getType()) { case 'LineString': { this._renderLine(geom, g, e.feature.getGeometry(), e); break; } case 'MultiLineString': { e.feature.getGeometry().getLineStrings().forEach(function(l, i) { this._renderLine(geom[i], g[i], l, e); }.bind(this)); break; } case 'Point': { break; } } }; /** @private */ ol.style.Profile.prototype._renderLine = function(geom, g, l, e) { var i, p, ctx = e.context; var cos = Math.cos(e.rotation) var sin = Math.sin(e.rotation) // var a = e.pixelRatio / e.resolution; var a = ol.coordinate.dist2d(geom[0],geom[1]) / ol.coordinate.dist2d(g[0],g[1]) var dx = geom[0][0] - g[0][0] * a *cos - g[0][1] * a *sin ; var dy = geom[0][1] - g[0][0] * a * sin + g[0][1] * a * cos; geom = l.getCoordinates(); var dz = Infinity; for (i=0; p=geom[i]; i++) { var x = dx + p[0] * a * cos + p[1] * a * sin; var y = dy + p[0] * a * sin - p[1] * a * cos; dz = Math.min(dz, p[2]); geom[i] = [x, y, p[2]]; } ctx.save(); ctx.fillStyle = ol.color.asString(this.getFill().getColor()); ctx.strokeStyle = ol.color.asString(this.getStroke().getColor()); ctx.strokeWidth = this.getStroke().getWidth(); var p0 = geom[0]; var ez = this.getScale() * e.pixelRatio; for (i=1; p=geom[i]; i++) { ctx.beginPath(); ctx.moveTo(p0[0],p0[1]); ctx.lineTo(p[0],p[1]); ctx.lineTo(p[0],p[1]-(p[2]-dz)*ez); ctx.lineTo(p0[0],p0[1]-(p0[2]-dz)*ez); ctx.lineTo(p0[0],p0[1]); ctx.fill(); p0 = p; } p0 = geom[0]; ctx.beginPath(); ctx.moveTo(p0[0],p0[1]-(p0[2]-dz)*ez); for (i=1; p=geom[i]; i++) { ctx.lineTo(p[0],p[1]-(p[2]-dz)*ez); } ctx.stroke(); ctx.restore(); }; /** Add a setTextPath style to draw text along linestrings @toto letterpadding/spacing, wordpadding/spacing */ (function() { /** Internal drawing function called on postcompose * @param {ol.eventPoscompose} e postcompose event */ function drawTextPath (e) { // Prent drawing at large resolution if (e.frameState.viewState.resolution > this.textPathMaxResolution_) return; var extent = e.frameState.extent; var c2p = e.frameState.coordinateToPixelTransform; // Get pixel path with coordinates var k; function getPath(c, readable) { var path1 = []; for (k=0; kpath1[path1.length-2]) { var path2 = []; for (k=path1.length-2; k>=0; k-=2) { path2.push(path1[k]); path2.push(path1[k+1]); } return path2; } else return path1; } var ctx = e.context; ctx.save(); ctx.scale(e.frameState.pixelRatio,e.frameState.pixelRatio); var features = this.getSource().getFeaturesInExtent(extent); for (var i=0, f; f=features[i]; i++) { { var style = this.textPathStyle_(f,e.frameState.viewState.resolution); for (var s,j=0; s=style[j]; j++) { var g = s.getGeometry() || f.getGeometry(); var c; switch (g.getType()) { case "LineString": c = g.getCoordinates(); break; case "MultiLineString": c = g.getLineString(0).getCoordinates(); break; default: continue; } var st = s.getText(); var path = getPath(c, st.getRotateWithView() ); ctx.font = st.getFont(); ctx.textBaseline = st.getTextBaseline(); ctx.textAlign = st.getTextAlign(); ctx.lineWidth = st.getStroke() ? (st.getStroke().getWidth()||0) : 0; ctx.strokeStyle = st.getStroke() ? (st.getStroke().getColor()||"#fff") : "#fff"; ctx.fillStyle = st.getFill() ? st.getFill().getColor()||"#000" : "#000"; // New params ctx.textJustify = st.getTextAlign()=="justify"; ctx.textOverflow = st.getTextOverflow ? st.getTextOverflow():""; ctx.minWidth = st.getMinWidth ? st.getMinWidth():0; // Draw textpath ctx.textPath(st.getText()||f.get("name"), path); } } } ctx.restore(); } /** Set the style for features. * This can be a single style object, an array of styles, or a function that takes a feature and resolution and * returns an array of styles. If it is undefined the default style is used. * If it is null the layer has no style (a null style). * See ol.style for information on the default style. * @param {ol.style.Style|Array.|ol.StyleFunction} style * @param {Number} maxResolution to display text, default: 0 */ ol.layer.Vector.prototype.setTextPathStyle = function(style, maxResolution) { // Remove existing style if (style===null) { if (this.textPath_) this.unByKey(this.textPath_); this.textPath_ = null; this.changed(); return; } // New postcompose if (!this.textPath_) { this.textPath_ = this.on(['postcompose','postrender'], drawTextPath.bind(this)); } // Set textPathStyle if (style===undefined) { style = [ new ol.style.Style({ text: new ol.style.Text()}) ]; } if (typeof(style) == "function") this.textPathStyle_ = style; else this.textPathStyle_ = function() { return style; }; this.textPathMaxResolution_ = Number(maxResolution) || Number.MAX_VALUE; // Force redraw this.changed(); } /** Add new properties to ol.style.Text * to use with ol.layer.Vector.prototype.setTextPathStyle * @constructor * @param {} options * @param {visible|ellipsis|string} textOverflow * @param {number} minWidth minimum width (px) to draw text, default 0 */ ol.style.TextPath = function(options) { if (!options) options={}; ol.style.Text.call (this, options); this.textOverflow_ = typeof(options.textOverflow)!="undefined" ? options.textOverflow : "visible"; this.minWidth_ = options.minWidth || 0; } ol.ext.inherits(ol.style.TextPath, ol.style.Text); ol.style.TextPath.prototype.getTextOverflow = function() { return this.textOverflow_; }; ol.style.TextPath.prototype.getMinWidth = function() { return this.minWidth_; }; /**/ })(); /** CanvasRenderingContext2D: draw text along path * @param {string} text * @param {Array} path */ CanvasRenderingContext2D.prototype.textPath = function (text, path) { var ctx = this; function dist2D(x1,y1,x2,y2) { var dx = x2-x1; var dy = y2-y1; return Math.sqrt(dx*dx+dy*dy); } var di, dpos=0; var pos=2; function getPoint(path, dl) { if (!di || dpos+didl) break; pos += 2; if (pos>=path.length) break; dpos += di; } } var x, y, a, dt = dl-dpos; if (pos>=path.length) { pos = path.length-2; } if (!dt) { x = path[pos-2]; y = path[pos-1]; a = Math.atan2(path[pos+1]-path[pos-1], path[pos]-path[pos-2]); } else { x = path[pos-2]+ (path[pos]-path[pos-2])*dt/di; y = path[pos-1]+(path[pos+1]-path[pos-1])*dt/di; a = Math.atan2(path[pos+1]-path[pos-1], path[pos]-path[pos-2]); } return [x,y,a]; } var letterPadding = ctx.measureText(" ").width *0.25; var start = 0; var d = 0; for (var i=2; i0 ? Number(options.scale) : 1; var ratio = scale*ol.has.DEVICE_PIXEL_RATIO || ol.has.DEVICE_PIXEL_RATIO; var ctx = canvas.getContext('2d'); if (options.image) { options.image.load(); var img = options.image.getImage(); if (img.width) { canvas.width = Math.round(img.width *ratio); canvas.height = Math.round(img.height *ratio); ctx.globalAlpha = typeof(options.opacity) == 'number' ? options.opacity:1; ctx.drawImage(img, 0,0, img.width, img.height, 0, 0, canvas.width, canvas.height); pattern = ctx.createPattern(canvas, 'repeat'); } else { var self = this; pattern = [0,0,0,0]; img.onload = function () { canvas.width = Math.round(img.width *ratio); canvas.height = Math.round(img.height *ratio); ctx.globalAlpha = typeof(options.opacity) == 'number' ? options.opacity:1; ctx.drawImage(img, 0,0, img.width, img.height, 0, 0, canvas.width, canvas.height); pattern = ctx.createPattern(canvas, 'repeat'); self.setColor(pattern); } } } else { var pat = this.getPattern_(options); canvas.width = Math.round(pat.width *ratio); canvas.height = Math.round(pat.height *ratio); ctx.beginPath(); if (options.fill) { ctx.fillStyle = ol.color.asString(options.fill.getColor()); ctx.fillRect(0,0, canvas.width, canvas.height); } ctx.scale(ratio,ratio); ctx.lineCap = "round"; ctx.lineWidth = pat.stroke || 1; ctx.fillStyle = ol.color.asString(options.color||"#000"); ctx.strokeStyle = ol.color.asString(options.color||"#000"); if (pat.circles) for (i=0; i180) a -= 360; a *= Math.PI/180; var cos = Math.cos(a); var sin = Math.sin(a); if (Math.abs(sin)<0.0001) { pat.width = pat.height = d; pat.lines = [ [ 0,0.5, d, 0.5 ] ]; pat.repeat = [ [0,0], [0,d] ]; } else if (Math.abs(cos)<0.0001) { pat.width = pat.height = d; pat.lines = [ [ 0.5,0, 0.5, d] ]; pat.repeat = [ [0,0], [d,0] ]; if (options.pattern=='cross') { pat.lines.push ([ 0,0.5, d, 0.5 ]); pat.repeat.push([0,d]); } } else { var w = pat.width = Math.round(Math.abs(d/sin)) || 1; var h = pat.height = Math.round(Math.abs(d/cos)) || 1; if (options.pattern=='cross') { pat.lines = [ [-w,-h, 2*w,2*h], [2*w,-h, -w,2*h] ]; pat.repeat = [ [0,0] ]; } else if (cos*sin>0) { pat.lines = [ [-w,-h, 2*w,2*h] ]; pat.repeat = [ [0,0], [w,0], [0,h] ]; } else { pat.lines = [ [2*w,-h, -w,2*h] ]; pat.repeat = [ [0,0], [-w,0], [0,h] ]; } } pat.stroke = options.size===0 ? 0 : options.size||4; break; } default: { break; } } return pat }; ol.style.Style.defaultStyle; (function() { // Style var white = [255, 255, 255, 1]; var blue = [0, 153, 255, 1]; var width = 3; var defaultEditStyle = [ new ol.style.Style({ stroke: new ol.style.Stroke({ color: white, width: width + 2 }) }), new ol.style.Style({ image: new ol.style.Circle({ radius: width * 2, fill: new ol.style.Fill({ color: blue }), stroke: new ol.style.Stroke({ color: white, width: width / 2 }) }), stroke: new ol.style.Stroke({ color: blue, width: width }), fill: new ol.style.Fill({ color: [255, 255, 255, 0.5] }) }) ]; /** * Get the default style * @param {boolean|*} [edit] true to get editing style or a { color, fillColor } object, default get default blue style * @return {Array} */ ol.style.Style.defaultStyle = function(edit) { if (edit===true) { return defaultEditStyle; } else { edit = edit || {}; var fill = new ol.style.Fill({ color: edit.fillColor || 'rgba(255,255,255,0.4)' }); var stroke = new ol.style.Stroke({ color: edit.color || '#3399CC', width: 1.25 }); var style = new ol.style.Style({ image: new ol.style.Circle({ fill: fill, stroke: stroke, radius: 5 }), fill: fill, stroke: stroke }); return [ style ]; } }; })(); /* * Copyright (c) 2015 Jean-Marc VIGLINO, * released under the CeCILL-B license (French BSD license) * (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ /** * Get a style for Geoportail WFS features * * @param {String} options.typeName * @param {any} options * @param {boolean|number} options.sens true show flow direction or a max resolution to show it, default false * @param {boolean} options.vert 'vert' road section (troncon_de_route) style, default false * @param {boolean} options.symbol show symbol on buildings (batiment), default false * @return {Array} */ ol.style.geoportailStyle; (function(){ var cache = {}; var styleCount = 0; // Troncon de route function troncon_de_route(options) { // Get color according to road properties var getColor = function (feature) { if (options.vert && feature.get('itineraire_vert')) { if (feature.get('position_par_rapport_au_sol') < 0) return [0, 128, 0, .7]; else if (feature.get('position_par_rapport_au_sol') > 0) return [0, 100, 0, 1]; else return [0, 128, 0, 1]; } if (!feature.get('importance')) return "magenta"; if (feature.get('nature') === 'Piste cyclable') { return [27,177,27,.5] } if (feature.get('position_par_rapport_au_sol') != "0") { var col; switch(feature.get('importance')) { case "1": col = [177, 27, 177, 1]; break; case "2": col = [177, 27, 27, 1]; break; case "3": col = [217, 119, 0, 1]; break; case "4": col = [255, 225, 0, 1]; break; case "5": col = [204, 204, 204, 1]; break; default: col = [211, 211, 211, 1]; break; } if (feature.get('position_par_rapport_au_sol') < 0) col[3] = .7; return col; } else { switch(feature.get('importance')) { case "1": return [255,0,255,1]; case "2": return [255,0,0,1]; case "3": return [255, 165, 0, 1]; case "4": return [255,255,0,1]; case "5": return [255,255,255,1]; default: return [211, 211, 211, 1]; } } // return "#808080"; } // Get Width var getWidth = function (feature) { return Math.max ( feature.get('largeur_de_chaussee')||2 , 2 ); } // Zindex var getZindex = function (feature) { if (!feature.get('position_par_rapport_au_sol')) return 100; var pos = Number(feature.get('position_par_rapport_au_sol')); if (pos>0) return 10 + pos*10 - (Number(feature.get('importance')) || 10); else if (pos<0) return Math.max(4 + pos, 0); else return 10 - (Number(feature.get('importance')) || 10); // return 0; } // Get rotation on the center of the line var lrot = function (geom) { //if (sens != options.direct && sens != options.inverse) return 0; var geo = geom.getCoordinates(); var x, y, dl=0, l = geom.getLength(); for (var i=0; i=l/2) break; } return -Math.atan2(y,x); } // Sens circulation var getSens = function (feature) { if (options.sens && !/double|sans/i.test(feature.get('sens_de_circulation'))) { return new ol.style.Text({ text: (feature.get('sens_de_circulation') == 'Sens direct' ? '→' : '←'), font: 'bold 12px sans-serif', placement: 'point', textAlign: 'center', fill: new ol.style.Fill({ color: [0,0,0,.3] }), stroke: new ol.style.Stroke({ color: [0,0,0,.3], width: 1.5 }), rotateWithView: true }) } return null; } var getDash = function(feature) { switch (feature.get('nature')) { case 'Escalier': { return [1,4] } case 'Sentier': { return [8,10] } } } var styleId = 'ROUT-'+(styleCount++)+'-' return function (feature, res) { var useSens = (options.sens === true || res < options.sens); var id = styleId + feature.get('nature') + '-' + feature.get('position_par_rapport_au_sol') + '-' + (useSens ? feature.get('sens_de_circulation') : 'Sans objet') + '-' + feature.get('position_par_rapport_au_sol') + '-' + feature.get('importance') + '-' + feature.get('largeur_de_chaussee') + '-' + feature.get('itineraire_vert'); var style = cache[id]; if (!style) { style = cache[id] = [ new ol.style.Style ({ text: useSens ? getSens(feature) : null, stroke: new ol.style.Stroke({ color: getColor(feature), width: getWidth(feature), lineDash: getDash(feature) }), zIndex: getZindex(feature)-100 }) ]; } // Rotation if (style[0].getText()) style[0].getText().setRotation(lrot(feature.getGeometry())); return style; }; } /** Style for batiments */ function batiment(options) { var getBatiColor = function (feature) { switch (feature.get('nature')) { case "Industriel, agricole ou commercial": return [51, 102, 153,1]; case "Remarquable": return [0,192,0,1]; default: switch ( feature.get('usage_1') ) { case 'Résidentiel': case 'Indifférencié': return [128,128,128,1]; case 'Industriel': case 'Commercial et services': return [51, 102, 153,1]; case "Sportif": return [51,153,102,1]; case "Religieux": return [153,102,51,1]; default: return [153,51,51,1]; } } } var getSymbol = function (feature) { switch ( feature.get('usage_1') ) { case "Commercial et services": return "\uf217"; case "Sportif": return "\uf1e3"; default: return null; } } var styleId = 'BATI-'+(styleCount++)+'-' return function (feature) { if (feature.get('detruit')) return []; var id = styleId + feature.get('usage_1') + '-' + feature.get('nature') + '-' + feature.get('etat_de_l_objet'); var style = cache[id]; if (!style) { var col = getBatiColor(feature); var colfill = [col[0], col[1], col[1], .5] var projet = !/en service/i.test(feature.get('etat_de_l_objet')); if (projet) colfill[3] = .1; var symbol = (options.symbol ? getSymbol(feature): null); return [ new ol.style.Style({ text: symbol ? new ol.style.Text({ text: symbol, font: '12px FontAwesome', fill: new ol.style.Fill({ color: [0,0,0, .6] //col }) }) : null, fill: new ol.style.Fill({ color: colfill }), stroke: new ol.style.Stroke ({ color: col, width: 1.5, lineDash: projet ? [5,5] : null }) }) ] } return style } } // Parcelle / cadastre function parcelle(options) { var style = new ol.style.Style({ text: new ol.style.Text({ text: '0000', font: 'bold 12px sans-serif', fill: new ol.style.Fill({ color: [100, 0, 255, 1] }), stroke: new ol.style.Stroke ({ color: [255,255,255, .8], width: 3 }) }), stroke: new ol.style.Stroke ({ color: [255, 165, 0, 1], width: 1.5 }), fill: new ol.style.Fill({ color: [100, 0, 255, .1] }) }) return function(feature, resolution) { if (resolution < .8) style.getText().setFont('bold 12px sans-serif'); else style.getText().setFont('bold 10px sans-serif'); if (options.section) { style.getText().setText(feature.get('section') +'-'+ (feature.get('numero')||'').replace(/^0*/,'')); } else { style.getText().setText((feature.get('numero')||'').replace(/^0*/,'')); } return style; } } // Corine Land Cover Style var clcColors = { 111: { color: [230,0,77,255], title: 'Continuous urban fabric'}, 112: { color: [255,0,0,255], title: 'Discontinuous urban fabric'}, 121: { color: [204,77,242,255], title: 'Industrial or commercial units'}, 122: { color: [204,0,0,255], title: 'Road and rail networks and associated land'}, 123: { color: [230,204,204,255], title: 'Port areas'}, 124: { color: [230,204,230,255], title: 'Airports'}, 131: { color: [166,0,204,255], title: 'Mineral extraction sites'}, 132: { color: [166,77,0,255], title: 'Dump sites'}, 133: { color: [255,77,255,255], title: 'Construction sites'}, 141: { color: [255,166,255,255], title: 'Green urban areas'}, 142: { color: [255,230,255,255], title: 'Sport and leisure facilities'}, 211: { color: [255,255,168,255], title: 'Non-irrigated arable land'}, 212: { color: [255,255,0,255], title: 'Permanently irrigated land'}, 213: { color: [230,230,0,255], title: 'Rice fields'}, 221: { color: [230,128,0,255], title: 'Vineyards'}, 222: { color: [242,166,77,255], title: 'Fruit trees and berry plantations'}, 223: { color: [230,166,0,255], title: 'Olive groves'}, 231: { color: [230,230,77,255], title: 'Pastures'}, 241: { color: [255,230,166,255], title: 'Annual crops associated with permanent crops'}, 242: { color: [255,230,77,255], title: 'Complex cultivation patterns'}, 243: { color: [230,204,77,255], title: 'Land principally occupied by agriculture with significant areas of natural vegetation'}, 244: { color: [242,204,166,255], title: 'Agro-forestry areas'}, 311: { color: [128,255,0,255], title: 'Broad-leaved forest'}, 312: { color: [0,166,0,255], title: 'Coniferous forest'}, 313: { color: [77,255,0,255], title: 'Mixed forest'}, 321: { color: [204,242,77,255], title: 'Natural grasslands'}, 322: { color: [166,255,128,255], title: 'Moors and heathland'}, 323: { color: [166,230,77,255], title: 'Sclerophyllous vegetation'}, 324: { color: [166,242,0,255], title: 'Transitional woodland-shrub'}, 331: { color: [230,230,230,255], title: 'Beaches dunes sands'}, 332: { color: [204,204,204,255], title: 'Bare rocks'}, 333: { color: [204,255,204,255], title: 'Sparsely vegetated areas'}, 334: { color: [0,0,0,255], title: 'Burnt areas'}, 335: { color: [166,230,204,255], title: 'Glaciers and perpetual snow'}, 411: { color: [166,166,255,255], title: 'Inland marshes'}, 412: { color: [77,77,255,255], title: 'Peat bogs'}, 421: { color: [204,204,255,255], title: 'Salt marshes'}, 422: { color: [230,230,255,255], title: 'Salines'}, 423: { color: [166,166,230,255], title: 'Intertidal flats'}, 511: { color: [0,204,242,255], title: 'Water courses'}, 512: { color: [128,242,230,255], title: 'Water bodies'}, 521: { color: [0,255,166,255], title: 'Coastal lagoons'}, 522: { color: [166,255,230,255], title: 'Estuaries'}, 523: { color: [230,242,255,255], title: 'Sea and ocean'}, }; function corineLandCover (options) { return function(feature) { var code = feature.get('code_'+options.date); var style = cache['CLC-'+code]; if (!style) { var color = clcColors[code].color.slice(); color[3] = options.opacity || 1; style = cache['CLC-'+code] = new ol.style.Style({ fill: new ol.style.Fill({ color: color || [255,255,255,.5] }) }) } return style; } } /** Get ol style for an IGN WFS layer * @param {string} typeName * @param {Object} options */ ol.style.geoportailStyle = function(typeName, options) { options = options || {}; switch (typeName) { // Troncons de route case 'BDTOPO_V3:troncon_de_route': return troncon_de_route(options); // Bati case 'BDTOPO_V3:batiment': return batiment(options); // Parcelles case 'CADASTRALPARCELS.PARCELLAIRE_EXPRESS:parcelle': return parcelle(options); default: { // CLC if (/LANDCOVER/.test(typeName)) { options.date = typeName.replace(/[^\d]*(\d*).*/,'$1'); return (corineLandCover(options)); } else { // Default style console.warn('[ol/style/geoportailStyle] no style defined for type: ' + typeName) return ol.style.Style.defaultStyle(); } } } }; /** List of clc colors */ ol.style.geoportailStyle.clcColors = JSON.parse(JSON.stringify(clcColors)); })();