1/** 2 * ol-ext - A set of cool extensions for OpenLayers (ol) in node modules structure 3 * @description ol3,openlayers,popup,menu,symbol,renderer,filter,canvas,interaction,split,statistic,charts,pie,LayerSwitcher,toolbar,animation 4 * @version v3.2.26 5 * @author 6 * @see https://github.com/Viglino/ol-ext#, 7 * @license BSD-3-Clause 8 */ 9/** @namespace ol.ext 10 */ 11/*global ol*/ 12if (window.ol && !ol.ext) { 13 ol.ext = {}; 14} 15/** Inherit the prototype methods from one constructor into another. 16 * replace deprecated ol method 17 * 18 * @param {!Function} childCtor Child constructor. 19 * @param {!Function} parentCtor Parent constructor. 20 * @function module:ol.inherits 21 * @api 22 */ 23ol.ext.inherits = function(child,parent) { 24 child.prototype = Object.create(parent.prototype); 25 child.prototype.constructor = child; 26}; 27// Compatibilty with ol > 5 to be removed when v6 is out 28if (window.ol) { 29 if (!ol.inherits) ol.inherits = ol.ext.inherits; 30} 31/* IE Polyfill */ 32// NodeList.forEach 33if (window.NodeList && !NodeList.prototype.forEach) { 34 NodeList.prototype.forEach = Array.prototype.forEach; 35} 36// Element.remove 37if (window.Element && !Element.prototype.remove) { 38 Element.prototype.remove = function() { 39 if (this.parentNode) this.parentNode.removeChild(this); 40 } 41} 42/* End Polyfill */ 43 44/** Ajax request 45 * @fires success 46 * @fires error 47 * @param {*} options 48 * @param {string} options.auth Authorisation as btoa("username:password"); 49 * @param {string} options.dataType The type of data that you're expecting back from the server, default JSON 50 */ 51ol.ext.Ajax = function(options) { 52 options = options || {}; 53 ol.Object.call(this); 54 this._auth = options.auth; 55 this.set('dataType', options.dataType || 'JSON'); 56}; 57ol.ext.inherits(ol.ext.Ajax, ol.Object); 58/** Helper for get 59 * @param {*} options 60 * @param {string} options.url 61 * @param {string} options.auth Authorisation as btoa("username:password"); 62 * @param {string} options.dataType The type of data that you're expecting back from the server, default JSON 63 * @param {string} options.success 64 * @param {string} options.error 65 * @param {*} options.options get options 66 */ 67ol.ext.Ajax.get = function(options) { 68 var ajax = new ol.ext.Ajax(options); 69 if (options.success) ajax.on('success', function(e) { options.success(e.response, e); } ); 70 if (options.error) ajax.on('error', function(e) { options.error(e); } ); 71 ajax.send(options.url, options.data, options.options); 72}; 73/** Helper to get cors header 74 * @param {string} url 75 * @param {string} callback 76 */ 77ol.ext.Ajax.getCORS = function(url, callback) { 78 var request = new XMLHttpRequest(); 79 request.open('GET', url, true); 80 request.send(); 81 request.onreadystatechange = function() { 82 if (this.readyState == this.HEADERS_RECEIVED) { 83 callback(request.getResponseHeader('Access-Control-Allow-Origin')); 84 } 85 } 86}; 87/** Send an ajax request (GET) 88 * @fires success 89 * @fires error 90 * @param {string} url 91 * @param {*} data Data to send to the server as key / value 92 * @param {*} options a set of options that are returned in the 93 * @param {boolean} options.abort false to prevent aborting the current request, default true 94 */ 95ol.ext.Ajax.prototype.send = function (url, data, options){ 96 options = options || {}; 97 var self = this; 98 // Url 99 var encode = (options.encode !== false) 100 if (encode) url = encodeURI(url); 101 // Parameters 102 var parameters = ''; 103 for (var index in data) { 104 if (data.hasOwnProperty(index) && data[index]!==undefined) { 105 parameters += (parameters ? '&' : '?') + index + '=' + (encode ? encodeURIComponent(data[index]) : data[index]); 106 } 107 } 108 // Abort previous request 109 if (this._request && options.abort!==false) { 110 this._request.abort(); 111 } 112 // New request 113 var ajax = this._request = new XMLHttpRequest(); 114 ajax.open('GET', url + parameters, true); 115 if (options.timeout) ajax.timeout = options.timeout; 116 if (this._auth) { 117 ajax.setRequestHeader("Authorization", "Basic " + this._auth); 118 } 119 // Load complete 120 this.dispatchEvent ({ type: 'loadstart' }); 121 ajax.onload = function() { 122 self._request = null;
123 self.dispatchEvent ({ type: 'loadend' }); 124 if (this.status >= 200 && this.status < 400) { 125 var response; 126 // Decode response 127 try { 128 switch (self.get('dataType')) { 129 case 'JSON': { 130 response = JSON.parse(this.response); 131 break; 132 } 133 default: { 134 response = this.response; 135 } 136 } 137 } catch(e) { 138 // Error 139 self.dispatchEvent ({ 140 type: 'error', 141 status: 0, 142 statusText: 'parsererror', 143 error: e, 144 options: options, 145 jqXHR: this 146 }); 147 return; 148 } 149 // Success 150 //console.log('response',response) 151 self.dispatchEvent ({ 152 type: 'success', 153 response: response, 154 status: this.status, 155 statusText: this.statusText, 156 options: options, 157 jqXHR: this 158 }); 159 } else { 160 self.dispatchEvent ({ 161 type: 'error', 162 status: this.status, 163 statusText: this.statusText, 164 options: options, 165 jqXHR: this 166 }); 167 } 168 }; 169 // Oops 170 ajax.ontimeout = function() { 171 self._request = null; 172 self.dispatchEvent ({ type: 'loadend' }); 173 self.dispatchEvent ({ 174 type: 'error', 175 status: this.status, 176 statusText: 'Timeout', 177 options: options, 178 jqXHR: this 179 }); 180 }; 181 ajax.onerror = function() { 182 self._request = null; 183 self.dispatchEvent ({ type: 'loadend' }); 184 self.dispatchEvent ({ 185 type: 'error', 186 status: this.status, 187 statusText: this.statusText, 188 options: options, 189 jqXHR: this 190 }); 191 }; 192 // GO! 193 ajax.send(); 194}; 195 196/** SVG filter 197 * @param {*} options 198 * @param {ol.ext.SVGOperation} option.operation 199 * @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 200 * @param {string} option.color color interpolation filters, linear or sRGB 201 */ 202ol.ext.SVGFilter = function(options) { 203 options = options || {}; 204 ol.Object.call(this); 205 if (!ol.ext.SVGFilter.prototype.svg) { 206 ol.ext.SVGFilter.prototype.svg = document.createElementNS( this.NS, 'svg' ); 207 ol.ext.SVGFilter.prototype.svg.setAttribute('version','1.1'); 208 ol.ext.SVGFilter.prototype.svg.setAttribute('width',0); 209 ol.ext.SVGFilter.prototype.svg.setAttribute('height',0); 210 ol.ext.SVGFilter.prototype.svg.style.position = 'absolute'; 211 /* Firefox doesn't process hidden svg 212 ol.ext.SVGFilter.prototype.svg.style.display = 'none'; 213 */ 214 document.body.appendChild( ol.ext.SVGFilter.prototype.svg ); 215 } 216 this.element = document.createElementNS( this.NS, 'filter' ); 217 this._id = options.id || '_ol_SVGFilter_' + (ol.ext.SVGFilter.prototype._id++); 218 this.element.setAttribute( 'id', this._id ); 219 if (options.color) this.element.setAttribute( 'color-interpolation-filters', options.color ); 220 if (options.operation) this.addOperation(options.operation); 221 ol.ext.SVGFilter.prototype.svg.appendChild( this.element ); 222}; 223ol.ext.inherits(ol.ext.SVGFilter, ol.Object); 224ol.ext.SVGFilter.prototype.NS = "http://www.w3.org/2000/svg"; 225ol.ext.SVGFilter.prototype.svg = null; 226ol.ext.SVGFilter.prototype._id = 0; 227/** Get filter ID 228 * @return {string} 229 */ 230ol.ext.SVGFilter.prototype.getId = function() { 231 return this._id; 232}; 233/** Remove from DOM 234 */ 235ol.ext.SVGFilter.prototype.remove = function() { 236 this.element.remove(); 237}; 238/** Add a new operation 239 * @param {ol.ext.SVGOperation} operation 240 */ 241ol.ext.SVGFilter.prototype.addOperation = function(operation) { 242 if (operation instanceof Array) { 243 operation.forEach(function(o) { this.addOperation(o) }.bind(this)); 244 } else { 245 if (!(operation instanceof ol.ext.SVGOperation)) operation = new ol.ext.SVGOperation(operation); 246 this.element.appendChild( operation.geElement() ); 247 } 248}; 249/** Add a grayscale operation 250 * @param {number} value 251 */ 252ol.ext.SVGFilter.prototype.grayscale = function(value) { 253 this.addOperation({ 254 feoperation: 'feColorMatrix', 255 type: 'saturate', 256 values: value || 0 257 }); 258}; 259/** Add a luminanceToAlpha operation 260 * @param {*} options 261 * @param {number} options.gamma enhance gamma, default 0 262 */ 263ol.ext.SVGFilter.prototype.luminanceToAlpha = function(options) { 264 options = options || {}; 265 this.addOperation({ 266 feoperation: 'feColorMatrix', 267 type: 'luminanceToAlpha' 268 }); 269 if (options.gamma) { 270 this.addOperation({ 271 feoperation: 'feComponentTransfer', 272 operations: [{ 273 feoperation: 'feFuncA', 274 type: 'gamma', 275 amplitude: options.gamma, 276 exponent: 1, 277 offset: 0 278 }] 279 }); 280 } 281}; 282ol.ext.SVGFilter.prototype.applyTo = function(img) { 283 var canvas = document.createElement('CANVAS');
284 canvas.width = img.naturalWidth || img.width; 285 canvas.height = img.naturalHeight || img.height; 286 canvas.getContext('2d').filter = 'url(#'+this.getId()+')'; 287 canvas.getContext('2d').drawImage(img, 0, 0); 288 return canvas; 289}; 290 291/** SVG filter 292 * @param {string | *} attributes a list of attributes or fe operation 293 * @param {string} attributes.feoperation filter primitive tag name 294 */ 295ol.ext.SVGOperation = function(attributes) { 296 if (typeof(attributes)==='string') attributes = { feoperation: attributes }; 297 if (!attributes || !attributes.feoperation) { 298 console.error('[SVGOperation]: no operation defined.') 299 return; 300 } 301 ol.Object.call(this); 302 this._name = attributes.feoperation; 303 this.element = document.createElementNS( this.NS, this._name ); 304 this.setProperties(attributes); 305 if (attributes.operations instanceof Array) this.appendChild(attributes.operations); 306}; 307ol.ext.inherits(ol.ext.SVGOperation, ol.Object); 308ol.ext.SVGOperation.prototype.NS = "http://www.w3.org/2000/svg"; 309/** Get filter name 310 * @return {string} 311 */ 312ol.ext.SVGOperation.prototype.getName = function() { 313 return this._name; 314}; 315/** Set Filter attribute 316 * @param {*} attributes 317 */ 318ol.ext.SVGOperation.prototype.set = function(k, val) { 319 if (!/^feoperation$|^operations$/.test(k)) { 320 ol.Object.prototype.set.call(this, k, val); 321 this.element.setAttribute( k, val ); 322 } 323}; 324/** Set Filter attributes 325 * @param {*} attributes 326 */ 327ol.ext.SVGOperation.prototype.setProperties = function(attributes) { 328 attributes = attributes || {}; 329 for (var k in attributes) { 330 this.set(k, attributes[k]) 331 } 332}; 333/** Get SVG element 334 * @return {Element} 335 */ 336ol.ext.SVGOperation.prototype.geElement = function() { 337 return this.element; 338}; 339/** Append a new operation 340 * @param {ol.ext.SVGOperation} operation 341 */ 342ol.ext.SVGOperation.prototype.appendChild = function(operation) { 343 if (operation instanceof Array) { 344 operation.forEach(function(o) { this.appendChild(o) }.bind(this)); 345 } else { 346 if (!(operation instanceof ol.ext.SVGOperation)) operation = new ol.ext.SVGOperation(operation); 347 this.element.appendChild( operation.geElement() ); 348 } 349}; 350 351// Prevent overwrite 352if (ol.View.prototype.flyTo) { 353 console.warn('[OL-EXT] ol/View~View.flyTo redefinition') 354} 355/** Destination 356 * @typedef {Object} viewTourDestinations 357 * @property {string} [type=flyto] animation type (flyTo, moveTo), default flyTo 358 * @property {number} [duration=2000] animation duration 359 * @property {ol.coordinate} [center=] destination coordinate, default current center 360 * @property {number} [zoom] destination zoom, default current zoom 361 * @property {number} [zoomAt=-2] zoom to fly to, default min (current zoom, zoom) -2 362 * @property {function} [easing] easing function used during the animation, defaults ol/easing~inAndOut 363 * @property {number} [rotation] The rotation of the view at the end of the animation 364 * @property {anchor} [anchor] Optional anchor to remain fixed during a rotation or resolution animation. 365 */ 366/** FlyTo animation 367 * @param {viewTourDestinations} options 368 * @param {function} done callback function called at the end of an animation, called with true if the animation completed 369 */ 370ol.View.prototype.flyTo = function(options, done) { 371 options = options || {}; 372 // Start new anim 373 this.cancelAnimations(); 374 var callback = (typeof(done) === 'function' ? done : function(){}); 375 // Fly to destination 376 var duration = options.duration || 2000; 377 var zoomAt = options.zoomAt || (Math.min(options.zoom||100, this.getZoom())-2); 378 var zoomTo = options.zoom || this.getZoom(); 379 var coord = options.center || this.getCenter(); 380 // Move to 381 this.animate ({ 382 center: coord, 383 duration: duration, 384 easing: options.easing, 385 anchor: options.anchor, 386 rotation: options.rotation 387 }); 388 // Zoom to 389 this.animate ({ 390 zoom: zoomAt, 391 duration: duration/2, 392 easing: options.easing, 393 anchor: options.anchor 394 },{ 395 zoom: zoomTo, 396 duration: duration/2, 397 easing: options.easing, 398 anchor: options.anchor 399 }, 400 callback); 401}; 402/** Start a tour on the map 403 * @param {Array<viewTourDestinations>|Array<Array>} destinations an array of destinations or an array of [x,y,zoom,destinationType] 404 * @param {Object} options 405 * @param {number} [options.delay=750] delay between 2 destination 406 * @param {string} [options.type] animation type (flyTo, moveTo) to use if not defined in destinations 407 * @param {function} [options.easing] easing function used during the animation if not defined in destinations 408 * @param {function} [options.done] callback function called at the end of an animation, called with true if the tour completed 409 * @param {function}
409 [options.step] callback function called when a destination is reached with the step index as param 410 */ 411ol.View.prototype.takeTour = function(destinations, options) { 412 options = options || {}; 413 var index = -1; 414 var next = function(more) { 415 if (more) { 416 var dest = destinations[++index]; 417 if (typeof(options.step) === 'function') options.step(index, destinations); 418 if (dest) { 419 if (dest instanceof Array) dest = { center: [dest[0],dest[1]], zoom: dest[2], type: dest[3] }; 420 var delay = index === 0 ? 0 : (options.delay || 750); 421 if (!dest.easing) dest.easing = options.easing; 422 if (!dest.type) dest.type = options.type; 423 setTimeout(function () { 424 switch(dest.type) { 425 case 'moveTo': { 426 this.animate(dest, next); 427 break; 428 } 429 case 'flightTo': 430 default: { 431 this.flyTo(dest, next); 432 break; 433 } 434 } 435 }.bind(this), delay); 436 } else { 437 if (typeof(options.done)==='function') options.done(true); 438 } 439 } else { 440 if (typeof(options.done)==='function') options.done(false); 441 } 442 }.bind(this) 443 next(true); 444}; 445 446/** Converts an RGB color value to HSL. 447 * returns hsl as array h:[0,360], s:[0,100], l:[0,100] 448 * @param {ol/color~Color|string} rgb 449 * @param {number} [round=100] 450 * @returns {Array<number>} hsl as h:[0,360], s:[0,100], l:[0,100] 451 */ 452ol.color.toHSL = function(rgb, round) { 453 if (round===undefined) round = 100; 454 if (!Array.isArray(rgb)) rgb = ol.color.asArray(rgb); 455 var r = rgb[0] / 255; 456 var g = rgb[1] / 255; 457 var b = rgb[2] / 255; 458 var max = Math.max(r, g, b); 459 var min = Math.min(r, g, b); 460 var h, s, l = (max + min) / 2; 461 if (max === min) { 462 h = s = 0; // achromatic 463 } else { 464 var d = max - min; 465 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 466 switch (max) { 467 case r: h = (g - b) / d + (g < b ? 6 : 0); break; 468 case g: h = (b - r) / d + 2; break; 469 case b: h = (r - g) / d + 4; break; 470 } 471 } 472 var hsl = [ 473 Math.round(h*60*round)/round, 474 Math.round(s*100*round)/round, 475 Math.round(l*100*round)/round 476 ]; 477 if (rgb.length>3) hsl[3] = rgb[3]; 478 return hsl; 479} 480/** Converts an HSL color value to RGB. 481 * @param {Array<number>} hsl as h:[0,360], s:[0,100], l:[0,100] 482 * @param {number} [round=1000] 483 * @returns {Array<number>} rgb 484 */ 485ol.color.fromHSL = function(hsl, round) { 486 if (round===undefined) round = 1000 487 var h = hsl[0] / 360; 488 var s = hsl[1] / 100; 489 var l = hsl[2] / 100; 490 var r, g, b; 491 if (s == 0) { 492 r = g = b = l; // achromatic 493 } else { 494 var hue2rgb = function(p, q, t) { 495 if (t < 0) t += 1; 496 if (t > 1) t -= 1; 497 if (t < 1/6) return p + (q - p) * 6 * t; 498 if (t < 1/2) return q; 499 if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; 500 return p; 501 } 502 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 503 var p = 2 * l - q; 504 r = hue2rgb(p, q, h + 1/3); 505 g = hue2rgb(p, q, h); 506 b = hue2rgb(p, q, h - 1/3); 507 } 508 var rgb = [ 509 Math.round(r * 255*round) / round, 510 Math.round(g * 255*round) / round, 511 Math.round(b * 255*round) / round 512 ]; 513 if (hsl.length>3) rgb[3] = hsl[3]; 514 return rgb; 515} 516/** Converts an HSL color value to RGB. 517 * @param {ol/color~Color|string} rgb 518 * @param {number} [round=100] 519 * @returns {Array<number>} hsl as h:[0,360], s:[0,100], l:[0,100] 520 */ 521ol.color.toHSV = function(rgb, round) { 522 if (round===undefined) round = 100; 523 if (!Array.isArray(rgb)) rgb = ol.color.asArray(rgb); 524 var r = rgb[0] / 255; 525 var g = rgb[1] / 255; 526 var b = rgb[2] / 255; 527 var max = Math.max(r, g, b); 528 var min = Math.min(r, g, b); 529 var h, s, v = max; 530 var d = max - min; 531 s = max == 0 ? 0 : d / max; 532 if (max == min) { 533 h = 0; // achromatic 534 } else { 535 switch (max) { 536 case r: h = (g - b) / d + (g < b ? 6 : 0); break; 537 case g: h = (b - r) / d + 2; break; 538 case b: h = (r - g) / d + 4; break; 539 } 540 } 541 var hsv = [ 542 Math.round(h*60*round)/round, 543 Math.round(s*100*round)/round, 544 Math.round(v*100*round)/round 545 ]; 546 if (rgb.length>3) hsv[3] = rgb[3]; 547 return hsv; 548} 549/** Converts an HSV color value to RGB. 550 * @param {Array<number>} hsl as h:[0,360], s:[0,100], l:[0,100] 551 * @param {number} [round=1000] 552 * @returns {Array<number>} rgb 553 */ 554ol.color.fromHSV = function(hsv, round) {
555 if (round===undefined) round = 1000 556 var h = hsv[0] / 360; 557 var s = hsv[1] / 100; 558 var v = hsv[2] / 100; 559 var r, g, b; 560 var i = Math.floor(h * 6); 561 var f = h * 6 - i; 562 var p = v * (1 - s); 563 var q = v * (1 - f * s); 564 var t = v * (1 - (1 - f) * s); 565 switch (i % 6) { 566 case 0: r = v, g = t, b = p; break; 567 case 1: r = q, g = v, b = p; break; 568 case 2: r = p, g = v, b = t; break; 569 case 3: r = p, g = q, b = v; break; 570 case 4: r = t, g = p, b = v; break; 571 case 5: r = v, g = p, b = q; break; 572 } 573 var rgb = [ 574 Math.round(r * 255*round) / round, 575 Math.round(g * 255*round) / round, 576 Math.round(b * 255*round) / round 577 ]; 578 if (hsv.length>3) rgb[3] = hsv[3]; 579 return rgb; 580} 581/** Converts an HSL color value to RGB. 582 * @param {ol/color~Color|string} rgb 583 * @returns {string} 584 */ 585ol.color.toHexa = function(rgb) { 586 return '#' + ((1 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).slice(1); 587} 588 589/** Vanilla JS helper to manipulate DOM without jQuery 590 * @see https://github.com/nefe/You-Dont-Need-jQuery 591 * @see https://plainjs.com/javascript/ 592 * @see http://youmightnotneedjquery.com/ 593 */ 594/** @namespace ol.ext.element */ 595ol.ext.element = {}; 596/** 597 * Create an element 598 * @param {string} tagName The element tag, use 'TEXT' to create a text node 599 * @param {*} options 600 * @param {string} options.className className The element class name 601 * @param {Element} options.parent Parent to append the element as child 602 * @param {Element|string} [options.html] Content of the element (if text is not set) 603 * @param {string} [options.text] Text content (if html is not set) 604 * @param {Element|string} [options.options] when tagName = SELECT a list of options as key:value to add to the select 605 * @param {string} options.* Any other attribut to add to the element 606 */ 607ol.ext.element.create = function (tagName, options) { 608 options = options || {}; 609 var elt; 610 // Create text node 611 if (tagName === 'TEXT') { 612 elt = document.createTextNode(options.html||''); 613 if (options.parent) options.parent.appendChild(elt); 614 } else { 615 // Other element 616 elt = document.createElement(tagName); 617 if (/button/i.test(tagName)) elt.setAttribute('type', 'button'); 618 for (var attr in options) { 619 switch (attr) { 620 case 'className': { 621 if (options.className && options.className.trim) elt.setAttribute('class', options.className.trim()); 622 break; 623 } 624 case 'text': { 625 elt.innerText = options.text; 626 break; 627 } 628 case 'html': { 629 if (options.html instanceof Element) elt.appendChild(options.html) 630 else if (options.html!==undefined) elt.innerHTML = options.html; 631 break; 632 } 633 case 'parent': { 634 if (options.parent) options.parent.appendChild(elt); 635 break; 636 } 637 case 'options': { 638 if (/select/i.test(tagName)) { 639 for (var i in options.options) { 640 ol.ext.element.create('OPTION', { 641 html: i, 642 value: options.options[i], 643 parent: elt 644 }) 645 } 646 } 647 break; 648 } 649 case 'style': { 650 ol.ext.element.setStyle(elt, options.style); 651 break; 652 } 653 case 'change': 654 case 'click': { 655 ol.ext.element.addListener(elt, attr, options[attr]); 656 break; 657 } 658 case 'on': { 659 for (var e in options.on) { 660 ol.ext.element.addListener(elt, e, options.on[e]); 661 } 662 break; 663 } 664 case 'checked': { 665 elt.checked = !!options.checked; 666 break; 667 } 668 default: { 669 elt.setAttribute(attr, options[attr]); 670 break; 671 } 672 } 673 } 674 } 675 return elt; 676}; 677/** Create a toggle switch input 678 * @param {*} options 679 * @param {string|Element} options.html 680 * @param {string|Element} options.after 681 * @param {boolean} options.checked 682 * @param {*} [options.on] a list of actions 683 * @param {function} [options.click] 684 * @param {function} [options.change] 685 * @param {Element} options.parent 686 */ 687ol.ext.element.createSwitch = function (options) { 688 var input = ol.ext.element.create('INPUT', { 689 type: 'checkbox', 690 on: options.on, 691 click: options.click, 692 change: options.change, 693 parent: options.parent 694 }); 695 var opt = Object.assign ({ input: input }, options || {}); 696 new ol.ext.input.Switch(opt); 697 return input; 698}; 699/** Create a toggle switch input 700 * @param {*} options 701 * @param {string|Element} options.html 702 * @param {string|Element} options.after 703 * @param {string} [options.name] input name 704 * @param {string} [options.type=checkbox] input type: radio or checkbox 705 * @param {string} options.value input value 706 * @param {*} [options.on] a list of actions 707 * @param {function} [options.click] 708 * @param {function} [options.change] 709 * @param {Element} options.parent 710 */ 711ol.ext.element.createCheck = function (options) { 712 var input = ol.ext.element.create('INPUT', { 713 name: options.name, 714 type: (options.type==='radio' ? 'radio' : 'checkbox'), 715 on: options.on, 716 parent: options.parent 717 }); 718 var opt = Object.assign ({ input: input }, options || {}); 719 if (options.type === 'radio') { 720 new ol.ext.input.Radio(opt); 721 } else { 722 new ol.ext.input.Checkbox(opt); 723 } 724 return input; 725}; 726/** Set inner html or append a child element to an element 727 * @param {Element} element 728 * @param {Element|string} html Content of the element 729 */ 730ol.ext.element.setHTML = function(element, html) { 731 if (html instanceof Element) element.appendChild(html) 732 else if (html!==undefined) element.innerHTML = html; 733}; 734/** Append text into an elemnt 735 * @param {Element} element 736 * @param {string} text text content 737 */ 738ol.ext.element.appendText = function(element, text) { 739 element.appendChild(document.createTextNode(text||'')); 740}; 741/** 742 * Add a set of event listener to an element 743 * @param {Element} element 744 * @param {string|Array<string>} eventType 745 * @param {function} fn 746 */ 747ol.ext.element.addListener = function (element, eventType, fn, useCapture ) { 748 if (typeof eventType === 'string') eventType = eventType.split(' '); 749 eventType.forEach(function(e) { 750 element.addEventListener(e, fn, useCapture); 751 }); 752}; 753/** 754 * Add a set of event listener to an element 755 * @param {Element} element 756 * @param {string|Array<string>} eventType 757 * @param {function} fn 758 */ 759ol.ext.element.removeListener = function (element, eventType, fn
759) { 760 if (typeof eventType === 'string') eventType = eventType.split(' '); 761 eventType.forEach(function(e) { 762 element.removeEventListener(e, fn); 763 }); 764}; 765/** 766 * Show an element 767 * @param {Element} element 768 */ 769ol.ext.element.show = function (element) { 770 element.style.display = ''; 771}; 772/** 773 * Hide an element 774 * @param {Element} element 775 */ 776ol.ext.element.hide = function (element) { 777 element.style.display = 'none'; 778}; 779/** 780 * Test if an element is hihdden 781 * @param {Element} element 782 * @return {boolean} 783 */ 784ol.ext.element.hidden = function (element) { 785 return ol.ext.element.getStyle(element, 'display') === 'none'; 786}; 787/** 788 * Toggle an element 789 * @param {Element} element 790 */ 791ol.ext.element.toggle = function (element) { 792 element.style.display = (element.style.display==='none' ? '' : 'none'); 793}; 794/** Set style of an element 795 * @param {DOMElement} el the element 796 * @param {*} st list of style 797 */ 798ol.ext.element.setStyle = function(el, st) { 799 for (var s in st) { 800 switch (s) { 801 case 'top': 802 case 'left': 803 case 'bottom': 804 case 'right': 805 case 'minWidth': 806 case 'maxWidth': 807 case 'width': 808 case 'height': { 809 if (typeof(st[s]) === 'number') { 810 el.style[s] = st[s]+'px'; 811 } else { 812 el.style[s] = st[s]; 813 } 814 break; 815 } 816 default: { 817 el.style[s] = st[s]; 818 } 819 } 820 } 821}; 822/** 823 * Get style propertie of an element 824 * @param {DOMElement} el the element 825 * @param {string} styleProp Propertie name 826 * @return {*} style value 827 */ 828ol.ext.element.getStyle = function(el, styleProp) { 829 var value, defaultView = (el.ownerDocument || document).defaultView; 830 // W3C standard way: 831 if (defaultView && defaultView.getComputedStyle) { 832 // sanitize property name to css notation 833 // (hypen separated words eg. font-Size) 834 styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase(); 835 value = defaultView.getComputedStyle(el, null).getPropertyValue(styleProp); 836 } else if (el.currentStyle) { // IE 837 // sanitize property name to camelCase 838 styleProp = styleProp.replace(/-(\w)/g, function(str, letter) { 839 return letter.toUpperCase(); 840 }); 841 value = el.currentStyle[styleProp]; 842 // convert other units to pixels on IE 843 if (/^\d+(em|pt|%|ex)?$/i.test(value)) { 844 return (function(value) { 845 var oldLeft = el.style.left, oldRsLeft = el.runtimeStyle.left; 846 el.runtimeStyle.left = el.currentStyle.left; 847 el.style.left = value || 0; 848 value = el.style.pixelLeft + "px"; 849 el.style.left = oldLeft; 850 el.runtimeStyle.left = oldRsLeft; 851 return value; 852 })(value); 853 } 854 } 855 if (/px$/.test(value)) return parseInt(value); 856 return value; 857}; 858/** Get outerHeight of an elemen 859 * @param {DOMElement} elt 860 * @return {number} 861 */ 862ol.ext.element.outerHeight = function(elt) { 863 return elt.offsetHeight + ol.ext.element.getStyle(elt, 'marginBottom') 864}; 865/** Get outerWidth of an elemen 866 * @param {DOMElement} elt 867 * @return {number} 868 */ 869ol.ext.element.outerWidth = function(elt) { 870 return elt.offsetWidth + ol.ext.element.getStyle(elt, 'marginLeft') 871}; 872/** Get element offset rect 873 * @param {DOMElement} elt 874 * @return {*} 875 */ 876ol.ext.element.offsetRect = function(elt) { 877 var rect = elt.getBoundingClientRect(); 878 return { 879 top: rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0), 880 left: rect.left + (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0), 881 height: rect.height || (rect.bottom - rect.top), 882 width: rect.width || (rect.right - rect.left) 883 } 884}; 885/** Get element offset 886 * @param {ELement} elt 887 * @returns {Object} top/left offset 888 */ 889ol.ext.element.getFixedOffset = function(elt) { 890 var offset = { 891 left:0, 892 top:0 893 }; 894 var getOffset = function(parent) { 895 if (!parent) return offset; 896 // Check position when transform 897 if (ol.ext.element.getStyle(parent, 'position') === 'absolute' 898 && ol.ext.element.getStyle(parent, 'transform') !== "none") { 899 var r = parent.getBoundingClientRect(); 900 offset.left += r.left; 901 offset.top += r.top; 902 return offset; 903 } 904 return getOffset(parent.offsetParent) 905 } 906 return getOffset(elt.offsetParent) 907}; 908/** Get element offset rect
909 * @param {DOMElement} elt 910 * @param {boolean} fixed get fixed position 911 * @return {Object} 912 */ 913ol.ext.element.positionRect = function(elt, fixed) { 914 var gleft = 0; 915 var gtop = 0; 916 var getRect = function( parent ) { 917 if (parent) { 918 gleft += parent.offsetLeft; 919 gtop += parent.offsetTop; 920 return getRect(parent.offsetParent); 921 } else { 922 var r = { 923 top: elt.offsetTop + gtop, 924 left: elt.offsetLeft + gleft 925 }; 926 if (fixed) { 927 r.top -= (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0); 928 r.left -= (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0); 929 } 930 r.bottom = r.top + elt.offsetHeight; 931 r.right = r.top + elt.offsetWidth; 932 return r; 933 } 934 }; 935 return getRect(elt.offsetParent); 936} 937/** Make a div scrollable without scrollbar. 938 * On touch devices the default behavior is preserved 939 * @param {DOMElement} elt 940 * @param {*} options 941 * @param {function} [options.onmove] a function that takes a boolean indicating that the div is scrolling 942 * @param {boolean} [options.vertical=false] 943 * @param {boolean} [options.animate=true] add kinetic to scroll 944 * @param {boolean} [options.mousewheel=false] enable mousewheel to scroll 945 * @param {boolean} [options.minibar=false] add a mini scrollbar to the parent element (only vertical scrolling) 946 * @returns {Object} an object with a refresh function 947 */ 948ol.ext.element.scrollDiv = function(elt, options) { 949 options = options || {}; 950 var pos = false; 951 var speed = 0; 952 var d, dt = 0; 953 var onmove = (typeof(options.onmove) === 'function' ? options.onmove : function(){}); 954 //var page = options.vertical ? 'pageY' : 'pageX'; 955 var page = options.vertical ? 'screenY' : 'screenX'; 956 var scroll = options.vertical ? 'scrollTop' : 'scrollLeft'; 957 var moving = false; 958 // Factor scale content / container 959 var scale, isbar; 960 // Update the minibar 961 var updateCounter = 0; 962 var updateMinibar = function() { 963 if (scrollbar) { 964 updateCounter++; 965 setTimeout(updateMinibarDelay); 966 } 967 } 968 var updateMinibarDelay = function() { 969 if (scrollbar) { 970 updateCounter--; 971 // Prevent multi call 972 if (updateCounter) return; 973 // Container height 974 var pheight = elt.clientHeight; 975 // Content height 976 var height = elt.scrollHeight; 977 // Set scrollbar value 978 scale = pheight / height; 979 scrollbar.style.height = scale * 100 + '%'; 980 scrollbar.style.top = (elt.scrollTop / height * 100) + '%'; 981 scrollContainer.style.height = pheight + 'px'; 982 // No scroll 983 if (pheight > height - .5) scrollContainer.classList.add('ol-100pc'); 984 else scrollContainer.classList.remove('ol-100pc'); 985 } 986 } 987 // Handle pointer down 988 var onPointerDown = function(e) { 989 // Prevent scroll 990 if (e.target.classList.contains('ol-noscroll')) return; 991 // Start scrolling 992 moving = false; 993 pos = e[page]; 994 dt = new Date(); 995 elt.classList.add('ol-move'); 996 // Prevent elt dragging 997 e.preventDefault(); 998 // Listen scroll 999 window.addEventListener('pointermove', onPointerMove); 1000 ol.ext.element.addListener(window, ['pointerup','pointercancel'], onPointerUp); 1001 } 1002 // Register scroll 1003 var onPointerMove = function(e) { 1004 moving = true; 1005 if (pos !== false) { 1006 var delta = (isbar ? -1/scale : 1) * (pos - e[page]); 1007 elt[scroll] += delta; 1008 d = new Date(); 1009 if (d-dt) { 1010 speed = (speed + delta / (d - dt))/2; 1011 } 1012 pos = e[page]; 1013 dt = d; 1014 // Tell we are moving
1015 if (delta) onmove(true); 1016 } 1017 }; 1018 // Animate scroll 1019 var animate = function(to) { 1020 var step = (to>0) ? Math.min(100, to/2) : Math.max(-100, to/2); 1021 to -= step; 1022 elt[scroll] += step; 1023 if (-1 < to && to < 1) { 1024 if (moving) setTimeout(function() { elt.classList.remove('ol-move'); }); 1025 else elt.classList.remove('ol-move'); 1026 moving = false; 1027 onmove(false); 1028 } else { 1029 setTimeout(function() { 1030 animate(to); 1031 }, 40); 1032 } 1033 } 1034 // Initialize scroll container for minibar 1035 var scrollContainer, scrollbar; 1036 if (options.vertical && options.minibar) { 1037 var init = function(b) { 1038 // only once 1039 elt.removeEventListener('pointermove', init); 1040 elt.parentNode.classList.add('ol-miniscroll'); 1041 scrollbar = ol.ext.element.create('DIV'); 1042 scrollContainer = ol.ext.element.create('DIV', { 1043 className: 'ol-scroll', 1044 html: scrollbar 1045 }); 1046 elt.parentNode.insertBefore(scrollContainer, elt); 1047 // Move scrollbar 1048 scrollbar.addEventListener('pointerdown', function(e) { 1049 isbar = true; 1050 onPointerDown(e) 1051 }); 1052 // Handle mousewheel 1053 if (options.mousewheel) { 1054 ol.ext.element.addListener(scrollContainer, 1055 ['mousewheel', 'DOMMouseScroll', 'onmousewheel'], 1056 function(e) { onMouseWheel(e) } 1057 ); 1058 ol.ext.element.addListener(scrollbar, 1059 ['mousewheel', 'DOMMouseScroll', 'onmousewheel'], 1060 function(e) { onMouseWheel(e) } 1061 ); 1062 } 1063 // Update on enter 1064 elt.parentNode.addEventListener('pointerenter', updateMinibar); 1065 // Update on resize 1066 window.addEventListener('resize', updateMinibar); 1067 // Update 1068 if (b!==false) updateMinibar(); 1069 }; 1070 // Allready inserted in the DOM 1071 if (elt.parentNode) init(false); 1072 // or wait when ready 1073 else elt.addEventListener('pointermove', init); 1074 // Update on scroll 1075 elt.addEventListener('scroll', function() { 1076 updateMinibar(); 1077 }); 1078 } 1079 // Enable scroll 1080 elt.style['touch-action'] = 'none'; 1081 elt.style['overflow'] = 'hidden'; 1082 elt.classList.add('ol-scrolldiv'); 1083 // Start scrolling 1084 ol.ext.element.addListener(elt, ['pointerdown'], function(e) { 1085 isbar = false; 1086 onPointerDown(e) 1087 }); 1088 // Prevet click when moving... 1089 elt.addEventListener('click', function(e) { 1090 if (elt.classList.contains('ol-move')) { 1091 e.preventDefault(); 1092 e.stopPropagation(); 1093 } 1094 }, true); 1095 // Stop scrolling 1096 var onPointerUp = function(e) { 1097 dt = new Date() - dt; 1098 if (dt>100 || isbar) { 1099 // User stop: no speed 1100 speed = 0; 1101 } else if (dt>0) { 1102 // Calculate new speed 1103 speed = ((speed||0) + (pos - e[page]) / dt) / 2; 1104 } 1105 animate(options.animate===false ? 0 : speed*200); 1106 pos = false; 1107 speed = 0; 1108 dt = 0; 1109 // Add class to handle click (on iframe / double-click) 1110 if (!elt.classList.contains('ol-move')) { 1111 elt.classList.add('ol-hasClick') 1112 setTimeout(function() { elt.classList.remove('ol-hasClick'); }, 500); 1113 } else { 1114 elt.classList.remove('ol-hasClick'); 1115 } 1116 isbar = false; 1117 window.removeEventListener('pointermove', onPointerMove) 1118 ol.ext.element.removeListener(window, ['pointerup','pointercancel'], onPointerUp); 1119 }; 1120 // Handle mousewheel 1121 var onMouseWheel = function(e) { 1122 var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))); 1123 elt.classList.add('ol-move'); 1124 elt[scroll] -= delta*30; 1125 elt.classList.remove('ol-move'); 1126 return false; 1127 } 1128 if (options.mousewheel) { // && !elt.classList.contains('ol-touch')) { 1129 ol.ext.element.addListener(elt, 1130 ['mousewheel', 'DOMMouseScroll', 'onmousewheel'], 1131 onMouseWheel 1132 ); 1133 } 1134 return { 1135 refresh: updateMinibar 1136 } 1137}; 1138/** Dispatch an event to an Element 1139 * @param {string} eventName 1140 * @param {Element} element 1141 */ 1142ol.ext.element.dispatchEvent = function (eventName, element) { 1143 var event; 1144 try { 1145 event = new CustomEvent(eventName); 1146 } catch(e) { 1147 // Try customevent on IE 1148 event = document.createEvent("CustomEvent"); 1149 event.initCustomEvent(eventName, true, true, {}); 1150 } 1151 element.dispatchEvent(event); 1152}; 1153 1154/** Get a canvas overlay for a map (non rotated, on top of the map) 1155 * @param {ol.Map} map 1156 * @return {canvas} 1157 */ 1158ol.ext.getMapCanvas = function(map) { 1159 if (!map) return null; 1160 var canvas = map.getViewport().getElementsByClassName('ol-fixedoverlay')[0]; 1161 if (!canvas) { 1162 if (map.getViewport().querySelector('.ol-layers')) { 1163 // Add a fixed canvas layer on top of the map 1164 canvas = document.createElement('canvas'); 1165 canvas.className = 'ol-fixedoverlay'; 1166 map.getViewport().querySelector('.ol-layers').after(canvas); 1167 // Clear before new compose 1168 map.on('precompose', function (e){ 1169 canvas.width = map.getSize()[0] * e.frameState.pixelRatio; 1170 canvas.height = map.getSize()[1] * e.frameState.pixelRatio; 1171 }); 1172 } else { 1173 canvas = map.getViewport().querySelector('canvas'); 1174 } 1175 } 1176 return canvas; 1177}; 1178 1179/*global ol*/ 1180if (window.ol) { 1181 if (!ol.util) { 1182 ol.util = { 1183 VERSION: ol.VERSION || '5.3.0' 1184 }; 1185 } else if (!ol.util.VERSION) { 1186 ol.util.VERSION = ol.VERSION || '6.1.0' 1187 } 1188} 1189ol.ext.olVersion = ol.util.VERSION.split('.'); 1190ol.ext.olVersion = parseInt(ol.ext.olVersion[0])*100 + parseInt(ol.ext.olVersion[1]); 1191/** Get style to use in a VectorContext 1192 * @param {} e 1193 * @param {ol.style.Style} s 1194 * @return {ol.style.Style} 1195 */ 1196ol.ext.getVectorContextStyle = function(e, s) { 1197 var ratio = e.frameState.pixelRatio; 1198 // Bug with Icon images 1199 if (ol.ext.olVersion > 605 && ratio !== 1 && (s.getImage() instanceof ol.style.Icon)) { 1200 s = s.clone(); 1201 var img = s.getImage(); 1202 img.setScale(img.getScale()*ratio); 1203 /* BUG anchor don't use ratio */ 1204 var anchor = img.getAnchor(); 1205 if (img.setDisplacement) { 1206 var disp = img.getDisplacement(); 1207 if (disp) { 1208 disp[0] -= anchor[0]/ratio; 1209 disp[1] += anchor[1]/ratio; 1210 img.setAnchor([0,0]); 1211 } 1212 } else { 1213 if (anchor) { 1214 anchor[0] /= ratio; 1215 anchor[1] /= ratio; 1216 } 1217 } 1218 /**/ 1219 } 1220 return s; 1221} 1222 1223/** @namespace ol.ext.imageLoader 1224 */ 1225if (window.ol) window.ol.ext.imageLoader = {}; 1226/** Helper for loading BIL-32 (Band Interleaved by Line) image 1227 * @param {string} src 1228 * @param {function} onload a function that takes a Float32Array and a ol.size.Size (array size) 1229 * @param {function} onerror 1230 * @private 1231 */ 1232ol.ext.imageLoader.loadBILImage = function(src, onload, onerror) { 1233 var size = [ 1234 parseInt(src.replace(/.*WIDTH=(\d*).*/i,'$1')), 1235 parseInt(src.replace(/.*HEIGHT=(\d*).*/i,'$1')) 1236 ]; 1237 var xhr = new XMLHttpRequest(); 1238 xhr.responseType = 'blob'; 1239 xhr.addEventListener('loadend', function () { 1240 var resp = this.response; 1241 if (resp !== undefined) { 1242 var reader = new FileReader(); 1243 // Get as array 1244 reader.addEventListener('loadend', (e) => { 1245 var data = new Float32Array(e.target.result); 1246 onload(data, size); 1247 }); 1248 // Start reading the blob 1249 reader.readAsArrayBuffer(resp); 1250 // tile.getImage().src = URL.createObjectURL(blob); 1251 } else {
1252 onerror(); 1253 } 1254 }); 1255 xhr.addEventListener('error', function () { 1256 onerror(); 1257 }); 1258 xhr.open('GET', src); 1259 xhr.send(); 1260}; 1261/** Helper for loading image 1262 * @param {string} src 1263 * @param {function} onload a function that takes a an image and a ol.size.Size 1264 * @param {function} onerror 1265 * @private 1266 */ 1267ol.ext.imageLoader.loadImage = function(src, onload, onerror) { 1268 var xhr = new XMLHttpRequest(); 1269 xhr.responseType = 'blob'; 1270 xhr.addEventListener('loadend', function () { 1271 var resp = this.response; 1272 if (resp !== undefined) { 1273 var img = new Image(); 1274 img.onload = function() { 1275 onload(img, [img.naturalWidth, img.naturalHeight]); 1276 } 1277 img.src = URL.createObjectURL(resp); 1278 } else { 1279 onerror(); 1280 } 1281 }); 1282 xhr.addEventListener('error', function () { 1283 onerror(); 1284 }); 1285 xhr.open('GET', src); 1286 xhr.send(); 1287}; 1288/** Get a TileLoadFunction to transform tiles images 1289 * @param {function} setPixel a function that takes a Uint8ClampedArray and the pixel position to transform 1290 * @returns {function} an ol/Tile~LoadFunction 1291 */ 1292ol.ext.imageLoader.pixelTransform = function(setPixel) { 1293 return function(tile, src) { 1294 ol.ext.imageLoader.loadImage( 1295 src, 1296 function(img, size) { 1297 var canvas = document.createElement('canvas'); 1298 canvas.width = size[0]; 1299 canvas.height = size[1]; 1300 var ctx = canvas.getContext('2d'); 1301 ctx.drawImage(img, 0, 0); 1302 var imgData = ctx.getImageData(0, 0, size[0], size[1]); 1303 var pixels = imgData.data; 1304 for (var i = 0; i < pixels.length; i += 4) { 1305 setPixel(pixels, i, size); 1306 } 1307 ctx.putImageData(imgData, 0, 0); 1308 tile.setImage(canvas); 1309 }, 1310 function() { 1311 tile.setState(3); 1312 } 1313 ); 1314 } 1315}; 1316/** Get a TileLoadFunction to transform tiles into grayscale images 1317 * @returns {function} an ol/Tile~LoadFunction 1318 */ 1319ol.ext.imageLoader.grayscale = function() { 1320 return ol.ext.imageLoader.pixelTransform(function(pixels, i) { 1321 pixels[i] = pixels[i + 1] = pixels[i + 2] = parseInt(3*pixels[i] + 4*pixels[i + 1] + pixels[i + 2] >>> 3); 1322 }) 1323}; 1324/** Get a TileLoadFunction to turn color or a color range transparent 1325 * @param {ol.color.Color|Array<ol.color.Color>} colors color or color range to turn transparent 1326 * @returns {function} an ol/Tile~LoadFunction 1327 */ 1328ol.ext.imageLoader.transparent = function(colors) { 1329 var color1, color2; 1330 if (colors instanceof Array) { 1331 color1 = colors[0]; 1332 color2 = colors[1]; 1333 } 1334 var color = color1 = ol.color.asArray(color1); 1335 if (!color2) { 1336 return ol.ext.imageLoader.pixelTransform(function(pixels, i) { 1337 if (pixels[i]===color[0] && pixels[i+1]===color[1] && pixels[i+2]===color[2]) { 1338 pixels[i+3] = 0; 1339 } 1340 }) 1341 } else { 1342 color2 = ol.color.asArray(color2); 1343 color = [Math.min(color1[0], color2[0]), Math.min(color1[1], color2[1]), Math.min(color1[2], color2[2])]; 1344 color2 = [Math.max(color1[0], color2[0]), Math.max(color1[1], color2[1]), Math.max(color1[2], color2[2])]; 1345 return ol.ext.imageLoader.pixelTransform(function(pixels, i) { 1346 if (pixels[i]>=color1[0] && pixels[i]<=color2[0] 1347 && pixels[i+1]>=color[1] && pixels[i+1]<=color2[1] 1348 && pixels[i+2]>=color[2] && pixels[i+2]<=color2[2]) { 1349 pixels[i+3] = 0; 1350 } 1351 }) 1352 } 1353}; 1354/** Returns an Imageloader function to load an x-bil-32 image as sea level map 1355 * to use as a ol/Tile~LoadFunction or ol/Image~LoadFunction 1356 * @param { number } level 1357 * @param {*} options 1358 * @param { ol.Color } [options.color] fill color 1359 * @param { boolean } [options.opacity=true] smooth color on border 1360 * @param { number } [options.minValue=-Infinity] minimum level value 1361 * @returns {function} an ol/Tile~LoadFunction 1362 */ 1363ol.ext.imageLoader.seaLevelMap = function(level, options) { 1364 options = options || {}; 1365 var h0 = Math.max(level + .01, .01); 1366 var c = options.color ? ol.color.asArray(options.color) : [135,203,249]; 1367 var min = typeof(options.minValue) === 'number' ? options.minValue : -Infinity; 1368 var opacity = options.opacity!==false; 1369 return ol.ext.imageLoader.elevationMap(function(h) { 1370 if (h < h0 && h > min) { 1371 return [c[0], c[1], c[2], opacity ? 255 * (h0-h) / h0 : 255]; 1372 } else { 1373 return [0,0,0,0]; 1374 } 1375 }) 1376}; 1377/** Shaded relief ? not/bad working yet... 1378 * @returns {function} an ol/Tile~LoadFunction 1379 * @private 1380 */ 1381ol.ext.imageLoader.shadedRelief = function() { 1382 var sunElev = Math.PI / 4; 1383 var sunAzimuth = 2*Math.PI - Math.PI / 4; 1384 return function (tile, src) { 1385 ol.ext.imageLoader.loadBILImage( 1386 src, 1387 function(data, size) { 1388 var canvas = document.createElement('canvas'); 1389 var ctx = canvas.getContext('2d'); 1390 var width = canvas.width = size[0]; 1391 var height = canvas.height = size[1]; 1392 var imgData = ctx.getImageData(0, 0, width, height); 1393 var pixels = imgData.data; 1394 function getIndexForCoordinates(x, y) { 1395 return x + y*width; 1396 } 1397 for (var x=0; x<width; x++) for(var y=0; y<height; y++) { 1398 var top = getIndexForCoordinates(x,Math.max(0,y-1)) 1399 var left = getIndexForCoordinates(Math.max(0,x-1),y); 1400 var right = getIndexForCoordinates(Math.min(width-1,x+1),y); 1401 var bottom = getIndexForCoordinates(x,Math.min(height,y+1)) 1402 // get slope values 1403 var topValue = data[top]; 1404 var leftValue = data[left]; 1405 var rightValue = data[right]; 1406 var bottomValue = data[bottom]; 1407 var slx = (rightValue - leftValue)/3; 1408 var sly = ( bottomValue - topValue )/3; 1409 var sl0 = Math.sqrt( slx*slx + sly*sly ); 1410 // get aspect 1411 var phi = Math.acos( slx/sl0 ); 1412 if ( sl0 == 0 ) { 1413 phi = 0; 1414 } 1415 var azimuth = 0; 1416 if ( slx > 0 ) { 1417 if ( sly > 0 ) azimuth = phi + 1.5*Math.PI; 1418 else if ( sly < 0 ) azimuth = 1.5*Math.PI - phi; 1419 else phi = 1.5*Math.PI; 1420 } else if ( slx < 0 ){ 1421 if ( sly < 0 ) azimuth = phi + .5*Math.PI; 1422 else if ( sly > 0 ) azimuth = .5*Math.PI - phi; 1423 else azimuth = .5*Math.PI; 1424 } else { 1425 if ( sly < 0 ) azimuth = Math.PI; 1426 else if ( sly > 0 ) azimuth = 0; 1427 } 1428 // get luminance
1429 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 ); 1430 if (lum<0) lum = 0; 1431 lum = Math.sqrt(lum*.8 + .5); 1432 var p = getIndexForCoordinates(x,y) * 4; 1433 pixels[p] = pixels[p+1] = pixels[p+2] = 0; 1434 pixels[p+3] = 255 - lum*255; 1435 } 1436 ctx.putImageData(imgData, 0, 0); 1437 tile.setImage(canvas); 1438 }, 1439 function () { 1440 tile.setState(3); 1441 } 1442 ) 1443 }; 1444}; 1445/** Get a TileLoadFunction to load an x-bil-32 image as elevation map (ie. pixels colors codes elevations as terrain-RGB) 1446 * If getPixelColor is not define pixel store elevation as rgb, use {@link ol.ext.getElevationFromPixel} to get elevation from pixel 1447 * @param {function} [getPixelColor] a function that taket an elevation and return a color array [r,g,b,a], default store elevation as terrain-RGB 1448 * @returns {function} an ol/Tile~LoadFunction 1449 */ 1450ol.ext.imageLoader.elevationMap = function(getPixelColor) { 1451 if (typeof(getPixelColor) !== 'function') getPixelColor = ol.ext.getPixelFromElevation; 1452 return function (tile, src) { 1453 ol.ext.imageLoader.loadBILImage( 1454 src, 1455 function(data, size) { 1456 var canvas = document.createElement('canvas'); 1457 var ctx = canvas.getContext('2d'); 1458 canvas.width = size[0]; 1459 canvas.height = size[1]; 1460 var imgData = ctx.getImageData(0, 0, size[0], size[1]); 1461 var pixels = imgData.data; 1462 for (var i=0; i<data.length; i++) { 1463 var p = getPixelColor(data[i]); 1464 pixels[4*i] = p[0]; 1465 pixels[4*i+1] = p[1]; 1466 pixels[4*i+2] = p[2]; 1467 pixels[4*i+3] = p[3]; 1468 } 1469 ctx.putImageData(imgData, 0, 0); 1470 tile.setImage(canvas); 1471 }, 1472 function () { 1473 tile.setState(3); 1474 } 1475 ) 1476 }; 1477}; 1478/** Convert elevation to pixel as terrain-RGB 1479 * Encode elevation data in raster tiles 1480 * - max deep watter trench min > -12000 m 1481 * - 2 digits (0.01 m) 1482 * @param {number} height elevation 1483 * @returns {Array<number>} pixel value 1484 */ 1485ol.ext.getPixelFromElevation = function(height) { 1486 var h = Math.round(height*100 + 1200000); 1487 var pixel = [ 1488 h >> 16, 1489 (h % 65536) >> 8, 1490 h % 256, 1491 255 1492 ]; 1493 return pixel; 1494}; 1495/** Convert pixel (terrain-RGB) to elevation 1496 * @see ol.ext.getPixelFromElevation 1497 * @param {Array<number>} pixel the pixel value 1498 * @returns {number} elevation 1499 */ 1500ol.ext.getElevationFromPixel = function(pixel) { 1501 // return -10000 + (pixel[0] * 65536 + pixel[1] * 256 + pixel[2]) * 0.01; 1502 return -12000 + ((pixel[0] << 16) + (pixel[1] << 8) + pixel[2]) * 0.01; 1503}; 1504 1505/* See 1506https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web 1507https://evanw.github.io/lightgl.js/docs/matrix.html 1508https://github.com/jlmakes/rematrix 1509https://jsfiddle.net/2znLxda2/ 1510*/ 1511/** Matrix3D; a set of functions to handle matrix3D 1512 */ 1513ol.matrix3D = {}; 1514/** Get transform matrix3D of an element 1515 * @param {Element} ele 1516 * @return {Array<Array<number>>} 1517 */ 1518ol.matrix3D.getTransform = function(ele) { 1519 var style = window.getComputedStyle(ele, null); 1520 var tr = style.getPropertyValue("-webkit-transform") 1521 || style.getPropertyValue("-moz-transform") 1522 || style.getPropertyValue("-ms-transform") 1523 || style.getPropertyValue("-o-transform") 1524 || style.getPropertyValue("transform"); 1525 var values = tr.split('(')[1].split(')')[0].split(','); 1526 var mx = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ]; 1527 var i, j; 1528 if (values.length === 16) { 1529 for (i = 0; i < 4; ++i) { 1530 for (j = 0; j < 4; ++j) { 1531 mx[j][i] = +values[i * 4 + j]; 1532 } 1533 } 1534 } else { 1535 for (i = 0; i < 3; ++i) { 1536 for (j = 0; j < 2; ++j) { 1537 mx[j][i] = +values[i * 2 + j]; 1538 } 1539 } 1540 } 1541 return mx; 1542}; 1543/** Get transform matrix3D of an element 1544 * @param {Element} ele 1545 * @return {Array<number>} 1546 */ 1547ol.matrix3D.getTransformOrigin = function (ele) { 1548 var style = window.getComputedStyle(ele, null); 1549 var tr = style.getPropertyValue("-webkit-transform-origin") 1550 || style.getPropertyValue("-moz-transform-origin") 1551 || style.getPropertyValue("-ms-transform-origin") 1552 || style.getPropertyValue("-o-transform-origin") 1553 || style.getPropertyValue("transform-origin"); 1554 var values = tr.split(' '); 1555 var mx = [ 0, 0, 0, 1 ]; 1556 for (var i = 0; i < values.length; ++i) { 1557 mx[i] = parseInt(values[i]); 1558 } 1559 return mx; 1560}; 1561/** Compute translate matrix 1562 * @param {number} x 1563 * @param {number} y 1564 * @param {number} z 1565 * @return {Array<Array<number>>} 1566 */ 1567ol.matrix3D.translateMatrix = function(x, y, z) { 1568 return [ 1569 [1, 0, 0, x], 1570 [0, 1, 0, y], 1571 [0, 0, 1, z], 1572 [0, 0, 0, 1] 1573 ]; 1574}; 1575/** Identity matrix 1576 * @return {Array<Array<number>>} 1577 */ 1578ol.matrix3D.identity = function() { 1579 return [ 1580 [1, 0, 0, 0], 1581 [0, 1, 0, 0], 1582 [0, 0, 1, 0], 1583 [0, 0, 0, 1] 1584 ]; 1585}; 1586/** Round matrix 1587 * @param {Array<Array<number>>} mx 1588 * @param {number} round Rounding value, default 1E-10 1589 */
1590ol.matrix3D.roundTo = function(mx, round) { 1591 if (!round) round = 1E-10; 1592 var m = [[],[],[],[]]; 1593 for (var i=0; i<4; i++) { 1594 for (var j=0; j<4; j++) { 1595 m[i][j] = Math.round(mx[i][j] / round) * round; 1596 } 1597 } 1598 return m; 1599}; 1600/** Multiply matrix3D 1601 * @param {Array<Array<number>>} mx1 1602 * @param {Array<Array<number>>} mx2 1603 * @return {Array<Array<number>>} 1604 */ 1605ol.matrix3D.multiply = function (mx1, mx2) { 1606 var mx = [ [], [], [], [] ]; 1607 for (var i = 0; i < 4; ++i) { 1608 for (var j = 0; j < 4; ++j) { 1609 var sum = 0; 1610 for (var k = 0; k < 4; ++k) { 1611 sum += (mx1[k][i] * mx2[j][k]); 1612 } 1613 mx[j][i] = sum; 1614 } 1615 } 1616 return mx; 1617}; 1618/** Compute the full transform that is applied to the transformed parent: -origin o tx o origin 1619 * @param {Array<Array<number>>} tx transform matrix 1620 * @param {Array<Array<number>>} origin transform origin 1621 * @return {Array<Array<number>>} 1622 */ 1623ol.matrix3D.computeTransformMatrix = function(tx, origin) { 1624 var preTx = ol.matrix3D.translateMatrix(-origin[0], -origin[1], -origin[2]); 1625 var postTx = ol.matrix3D.translateMatrix(origin[0], origin[1], origin[2]); 1626 var temp1 = ol.matrix3D.multiply(preTx, tx); 1627 return ol.matrix3D.multiply(temp1, postTx); 1628}; 1629/** Apply transform to a coordinate 1630 * @param {Array<Array<number>>} tx 1631 * @param {ol.pixel} px 1632 */ 1633ol.matrix3D.transformVertex = function(tx, px) { 1634 var vert = [px[0], px[1], 0, 1] 1635 var mx = [ ]; 1636 for (var i = 0; i < 4; ++i) { 1637 mx[i] = 0; 1638 for (var j = 0; j < 4; ++j) { 1639 mx[i] += +tx[i][j] * vert[j]; 1640 } 1641 } 1642 return mx; 1643} 1644/** Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component). 1645 * @param {Array<number>} vert 1646 * @return {Array<number>} 1647 */ 1648ol.matrix3D.projectVertex = function(vert) { 1649 var out = [ ]; 1650 for (var i = 0; i < 4; ++i) { 1651 out[i] = vert[i] / vert[3]; 1652 } 1653 return out; 1654}; 1655/** Inverse a matrix3D 1656 * @return {Array<Array<number>>} m matrix to transform 1657 * @return {Array<Array<number>>} 1658 */ 1659ol.matrix3D.inverse = function(m) { 1660 var s0 = m[0][0] * m[1][1] - m[1][0] * m[0][1] 1661 var s1 = m[0][0] * m[1][2] - m[1][0] * m[0][2] 1662 var s2 = m[0][0] * m[1][3] - m[1][0] * m[0][3] 1663 var s3 = m[0][1] * m[1][2] - m[1][1] * m[0][2] 1664 var s4 = m[0][1] * m[1][3] - m[1][1] * m[0][3] 1665 var s5 = m[0][2] * m[1][3] - m[1][2] * m[0][3] 1666 var c5 = m[2][2] * m[3][3] - m[3][2] * m[2][3] 1667 var c4 = m[2][1] * m[3][3] - m[3][1] * m[2][3] 1668 var c3 = m[2][1] * m[3][2] - m[3][1] * m[2][2] 1669 var c2 = m[2][0] * m[3][3] - m[3][0] * m[2][3] 1670 var c1 = m[2][0] * m[3][2] - m[3][0] * m[2][2] 1671 var c0 = m[2][0] * m[3][1] - m[3][0] * m[2][1] 1672 var determinant = 1 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0) 1673 if (isNaN(determinant) || determinant === Infinity) { 1674 throw new Error('Inverse determinant attempted to divide by zero.') 1675 } 1676 return [ 1677 [ 1678 (m[1][1] * c5 - m[1][2] * c4 + m[1][3] * c3) * determinant, 1679 (-m[0][1] * c5 + m[0][2] * c4 - m[0][3] * c3) * determinant, 1680 (m[3][1] * s5 - m[3][2] * s4 + m[3][3] * s3) * determinant, 1681 (-m[2][1] * s5 + m[2][2] * s4 - m[2][3] * s3) * determinant 1682 ],[ 1683 (-m[1][0] * c5 + m[1][2] * c2 - m[1][3] * c1) * determinant, 1684 (m[0][0] * c5 - m[0][2] * c2 + m[0][3] * c1) * determinant, 1685 (-m[3][0] * s5 + m[3][2] * s2 - m[3][3] * s1) * determinant, 1686 (m[2][0] * s5 - m[2][2] * s2 + m[2][3] * s1) * determinant 1687 ],[ 1688 (m[1][0] * c4 - m[1][1] * c2 + m[1][3] * c0) * determinant, 1689 (-m[0][0] * c4 + m[0][1] * c2 - m[0][3] * c0) * determinant, 1690 (m[3][0] * s4 - m[3][1] * s2 + m[3][3] * s0) * determinant, 1691 (-m[2][0] * s4 + m[2][1] * s2 - m[2][3] * s0) * determinant 1692 ],[ 1693 (-m[1][0] * c3 + m[1][1] * c1 - m[1][2] * c0) * determinant, 1694 (m[0][0] * c3 - m[0][1] * c1 + m[0][2] * c0) * determinant, 1695 (-m[3][0] * s3 + m[3][1] * s1 - m[3][2] * s0) * determinant, 1696 (m[2][0] * s3 - m[2][1] * s1 + m[2][2] * s0) * determinant 1697 ] 1698 ] 1699}; 1700 1701/* global ol */ 1702/* Create ol.sphere for backward compatibility with ol < 5.0 1703 * To use with Openlayers package 1704 */ 1705if (window.ol && !ol.sphere) { 1706 ol.sphere = {}; 1707 ol.sphere.getDistance = function (c1, c2, radius) { 1708 var sphere = new ol.Sphere(radius || 6371008.8); 1709 return sphere.haversineDistance(c1, c2); 1710 } 1711 ol.sphere.getArea = ol.Sphere.getArea; 1712 ol.sphere.getLength = ol.Sphere.getLength; 1713} 1714/* Copyright (c) 2016 Jean-Marc VIGLINO, 1715 released under the CeCILL-B license (French BSD license)
1716 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 1717*/ 1718/** A simple filter to detect edges on images 1719 * @constructor 1720 * @requires ol.filter 1721 * @extends {ol.ext.SVGFilter} 1722 * @param {*} options 1723 * @param {number} options.neighbours nb of neighbour (4 or 8), default 8 1724 * @param {boolean} options.grayscale get grayscale image, default false, 1725 * @param {boolean} options.alpha get alpha channel, default false 1726 */ 1727ol.ext.SVGFilter.Laplacian = function(options) { 1728 options = options || {}; 1729 ol.ext.SVGFilter.call(this, { id: options.id }); 1730 var operation = { 1731 feoperation: 'feConvolveMatrix', 1732 in: 'SourceGraphic', 1733 preserveAlpha: true, 1734 result: 'C1' 1735 }; 1736 if (options.neighbours===4) { 1737 operation.kernelMatrix = [ 1738 0, -1, 0, 1739 -1, 4, -1, 1740 0, -1, 0 1741 ]; 1742 } else { 1743 operation.kernelMatrix = [ 1744 -1, -1, -1, 1745 -1, 8, -1, 1746 -1, -1, -1 1747 ]; 1748 } 1749 this.addOperation(operation); 1750 if (options.grayscale) this.grayscale(); 1751 else if (options.alpha) this.luminanceToAlpha({ gamma: options.gamma }); 1752}; 1753ol.ext.inherits(ol.ext.SVGFilter.Laplacian, ol.ext.SVGFilter); 1754 1755/* Copyright (c) 2016 Jean-Marc VIGLINO, 1756 released under the CeCILL-B license (French BSD license) 1757 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 1758*/ 1759/** Apply a sobel filter on an image 1760 * @constructor 1761 * @requires ol.filter 1762 * @extends {ol.ext.SVGFilter} 1763 * @param {object} options 1764 * @param {string} [options.id] 1765 * @param {number} [options.scale=1] 1766 * @param {number} [options.ligth=50] light option. 0: darker, 100: lighter 1767 */ 1768ol.ext.SVGFilter.Paper = function(options) { 1769 options = options || {}; 1770 ol.ext.SVGFilter.call(this, { 1771 id: options.id 1772 }); 1773 this.addOperation({ 1774 feoperation: 'feTurbulence', 1775 numOctaves: 4, 1776 seed: 0, 1777 type: 'fractalNoise', 1778 baseFrequency: 0.2 / (options.scale || 1) 1779 }); 1780 this.addOperation({ 1781 feoperation: 'feDiffuseLighting', 1782 'lighting-color': 'rgb(255,255,255)', 1783 surfaceScale: 1.5, 1784 kernelUnitLength: 0.01, 1785 diffuseConstant: 1.1000000000000001, 1786 result: 'paper', 1787 operations: [{ 1788 feoperation: 'feDistantLight', 1789 elevation: options.light || 50, 1790 azimuth: 75 1791 }] 1792 }); 1793 this.addOperation({ 1794 feoperation: 'feBlend', 1795 in: 'SourceGraphic', 1796 in2: 'paper', 1797 mode: 'multiply' 1798 }) 1799}; 1800ol.ext.inherits(ol.ext.SVGFilter.Paper, ol.ext.SVGFilter); 1801/** Set filter light 1802 * @param {number} light light option. 0: darker, 100: lighter 1803 */ 1804ol.ext.SVGFilter.Paper.prototype.setLight = function(light) { 1805 this.element.querySelector('feDistantLight').setAttribute('elevation', light); 1806} 1807 1808/* Copyright (c) 2016 Jean-Marc VIGLINO, 1809 released under the CeCILL-B license (French BSD license) 1810 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 1811*/ 1812/** Apply a Prewitt filter on an image 1813 * @constructor 1814 * @requires ol.filter 1815 * @extends {ol.ext.SVGFilter} 1816 * @param {*} options 1817 * @param {boolean} options.grayscale get grayscale image, default false, 1818 * @param {boolean} options.alpha get alpha channel, default false 1819 */ 1820ol.ext.SVGFilter.Prewitt = function(options) { 1821 options = options || {}; 1822 ol.ext.SVGFilter.call(this, { id: options.id, color: 'sRGB' }); 1823 var operation = { 1824 feoperation: 'feConvolveMatrix', 1825 in: 'SourceGraphic', 1826 preserveAlpha: true, 1827 order: 3 1828 }; 1829 // Vertical 1830 operation.kernelMatrix = [ 1831 -1, -1, -1, 1832 0, 0, 0, 1833 1, 1, 1 1834 ]; 1835 operation.result = 'V1'; 1836 this.addOperation(operation); 1837 operation.kernelMatrix = [ 1838 1, 1, 1, 1839 0, 0, 0, 1840 -1, -1, -1 1841 ]; 1842 operation.result = 'V2'; 1843 this.addOperation(operation); 1844 // Horizontal 1845 operation.kernelMatrix = [ 1846 -1, 0, 1, 1847 -1, 0, 1, 1848 -1, 0, 1 1849 ]; 1850 operation.result = 'H1'; 1851 this.addOperation(operation); 1852 operation.kernelMatrix = [ 1853 1, -0, -1, 1854 1, 0, -1, 1855 1, 0, -1 1856 ]; 1857 operation.result = 'H2'; 1858 this.addOperation(operation); 1859 // Compose V 1860 this.addOperation({ 1861 feoperation: 'feComposite', 1862 operator: 'arithmetic', 1863 in: 'V1', 1864 in2: 'V2', 1865 k2: 1, 1866 k3: 1, 1867 result: 'V' 1868 }); 1869 // Compose H 1870 this.addOperation({ 1871 feoperation: 'feComposite', 1872 operator: 'arithmetic', 1873 in: 'H1', 1874 in2: 'H2', 1875 k2: 1, 1876 k3: 1, 1877 result: 'H' 1878 }); 1879 // Merge 1880 this.addOperation({ 1881 feoperation: 'feBlend', 1882 mode: 'lighten', 1883 in: 'H', 1884 in2: 'V' 1885 }); 1886 if (options.grayscale) this.grayscale(); 1887 else if (options.alpha) this.luminanceToAlpha(); 1888}; 1889ol.ext.inherits(ol.ext.SVGFilter.Prewitt, ol.ext.SVGFilter); 1890 1891/* Copyright (c) 2016 Jean-Marc VIGLINO, 1892 released under the CeCILL-B license (French BSD license)
1893 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 1894*/ 1895/** Apply a Roberts filter on an image 1896 * @constructor 1897 * @requires ol.filter 1898 * @extends {ol.ext.SVGFilter} 1899 * @param {*} options 1900 * @param {boolean} options.grayscale get grayscale image, default false, 1901 * @param {boolean} options.alpha get alpha channel, default false 1902 */ 1903ol.ext.SVGFilter.Roberts = function(options) { 1904 options = options || {}; 1905 ol.ext.SVGFilter.call(this, { id: options.id, color: 'sRGB' }); 1906 var operation = { 1907 feoperation: 'feConvolveMatrix', 1908 in: 'SourceGraphic', 1909 preserveAlpha: true, 1910 order: 3 1911 }; 1912 // Vertical 1913 operation.kernelMatrix = [ 1914 -1, 0, 0, 1915 0, 0, 0, 1916 0, 0, 1 1917 ]; 1918 operation.result = 'V1'; 1919 this.addOperation(operation); 1920 operation.kernelMatrix = [ 1921 1, 0, 0, 1922 0, 0, 0, 1923 0, 0, -1 1924 ]; 1925 operation.result = 'V2'; 1926 this.addOperation(operation); 1927 // Horizontal 1928 operation.kernelMatrix = [ 1929 0, 0, 1, 1930 0, 0, 0, 1931 -1, 0, 0 1932 ]; 1933 operation.result = 'H1'; 1934 this.addOperation(operation); 1935 operation.kernelMatrix = [ 1936 0, -0, -1, 1937 0, 0, 0, 1938 1, 0, 0 1939 ]; 1940 operation.result = 'H2'; 1941 this.addOperation(operation); 1942 // Compose V 1943 this.addOperation({ 1944 feoperation: 'feComposite', 1945 operator: 'arithmetic', 1946 in: 'V1', 1947 in2: 'V2', 1948 k2: 1, 1949 k3: 1, 1950 result: 'V' 1951 }); 1952 // Compose H 1953 this.addOperation({ 1954 feoperation: 'feComposite', 1955 operator: 'arithmetic', 1956 in: 'H1', 1957 in2: 'H2', 1958 k2: 1, 1959 k3: 1, 1960 result: 'H' 1961 }); 1962 // Merge 1963 this.addOperation({ 1964 feoperation: 'feBlend', 1965 mode: 'lighten', 1966 in: 'H', 1967 in2: 'V' 1968 }); 1969 if (options.grayscale) this.grayscale(); 1970 else if (options.alpha) this.luminanceToAlpha(); 1971}; 1972ol.ext.inherits(ol.ext.SVGFilter.Roberts, ol.ext.SVGFilter); 1973 1974/* Copyright (c) 2016 Jean-Marc VIGLINO, 1975 released under the CeCILL-B license (French BSD license) 1976 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 1977*/ 1978/** Apply a sobel filter on an image 1979 * @constructor 1980 * @requires ol.filter 1981 * @extends {ol.ext.SVGFilter} 1982 * @param {*} options 1983 * @param {boolean} options.grayscale get grayscale image, default false, 1984 * @param {boolean} options.alpha get alpha channel, default false 1985 */ 1986ol.ext.SVGFilter.Sobel = function(options) { 1987 options = options || {}; 1988 ol.ext.SVGFilter.call(this, { id: options.id, color: 'sRGB' }); 1989 var operation = { 1990 feoperation: 'feConvolveMatrix', 1991 in: 'SourceGraphic', 1992 preserveAlpha: true, 1993 order: 3 1994 }; 1995 // Vertical 1996 operation.kernelMatrix = [ 1997 -1, -2, -1, 1998 0, 0, 0, 1999 1, 2, 1 2000 ]; 2001 operation.result = 'V1'; 2002 this.addOperation(operation); 2003 operation.kernelMatrix = [ 2004 1, 2, 1, 2005 0, 0, 0, 2006 -1, -2, -1 2007 ]; 2008 operation.result = 'V2'; 2009 this.addOperation(operation); 2010 // Horizontal 2011 operation.kernelMatrix = [ 2012 -1, 0, 1, 2013 -2, 0, 2, 2014 -1, 0, 1 2015 ]; 2016 operation.result = 'H1'; 2017 this.addOperation(operation); 2018 operation.kernelMatrix = [ 2019 1, -0, -1, 2020 2, 0, -2, 2021 1, 0, -1 2022 ]; 2023 operation.result = 'H2'; 2024 this.addOperation(operation); 2025 // Compose V 2026 this.addOperation({ 2027 feoperation: 'feComposite', 2028 operator: 'arithmetic', 2029 in: 'V1', 2030 in2: 'V2', 2031 k2: 1, 2032 k3: 1, 2033 result: 'V' 2034 }); 2035 // Compose H 2036 this.addOperation({ 2037 feoperation: 'feComposite', 2038 operator: 'arithmetic', 2039 in: 'H1', 2040 in2: 'H2', 2041 k2: 1, 2042 k3: 1, 2043 result: 'H' 2044 }); 2045 // Merge 2046 this.addOperation({ 2047 feoperation: 'feBlend', 2048 mode: 'lighten', 2049 in: 'H', 2050 in2: 'V' 2051 }); 2052 if (options.grayscale) this.grayscale(); 2053 else if (options.alpha) this.luminanceToAlpha({ gamma: options.gamma }); 2054}; 2055ol.ext.inherits(ol.ext.SVGFilter.Sobel, ol.ext.SVGFilter); 2056 2057/** Vanilla JS geographic inputs 2058 * color, size, width, font, symboles, dash, arrow, pattern 2059 */ 2060/** @namespace ol.ext.input 2061 */ 2062/*global ol*/ 2063if (window.ol) { 2064 ol.ext.input = {}; 2065} 2066/** Abstract base class; normally only used for creating subclasses and not instantiated in apps. 2067 * @constructor 2068 * @extends {ol.Object} 2069 * @param {*} options 2070 * @param {Element} [options.input] input element, if none create one 2071 * @param {string} [options.type] input type, if no input 2072 * @param {number} [options.min] input min, if no input 2073 * @param {number} [options.max] input max, if no input 2074 * @param {number} [options.step] input step, if no input 2075 * @param {string|number} [options.val] input value 2076 * @param {boolean} [options.checked] check input 2077 * @param {boolean} [options.hidden] the input is display:none 2078 * @param {boolean} [options.disabled] disable input 2079 * @param {Element} [options.parent] parent element, if no input 2080 */ 2081ol.ext.input.Base = function(options) { 2082 options = options || {}; 2083 ol.Object.call(this); 2084 var input = this.input = options.input; 2085 if (!input) { 2086 input = this.input = document.createElement('INPUT'); 2087 if (options.type) input.setAttribute('type', options.type); 2088 if (options.min !== undefined) input.setAttribute('min', options.min); 2089 if (options.max !== undefined) input.setAttribute('max', options.max); 2090 if (options.step !== undefined) input.setAttribute('step', options.step); 2091 if (options.parent) options.parent.appendChild(input); 2092 } 2093 if (options.disabled) input.disabled = true; 2094 if (options.checked !== undefined) input.checked = !!options.checked; 2095 if (options.val !== undefined) input.value = options.val; 2096 if (options.hidden) input.style.display = 'none'; 2097 input.addEventListener('focus', function() { 2098 if (this.element) this.element.classList.add('ol-focus'); 2099 }.bind(this)) 2100 var tout; 2101 input.addEventListener('focusout', function() { 2102 if (this.element) { 2103 if (tout) clearTimeout(tout); 2104 tout = setTimeout(function() { 2105 this.element.classList.remove('ol-focus'); 2106 }.bind(this), 0); 2107 } 2108 }.bind(this)) 2109}; 2110ol.ext.inherits(ol.ext.input.Base, ol.Object); 2111/** Listen to drag event 2112 * @param {Element} elt 2113 * @param {function} cback when draggin on the element 2114 * @private 2115 */ 2116ol.ext.input.Base.prototype._listenDrag = function(elt, cback) { 2117 var handle = function(e) { 2118 this.moving = true; 2119 this.element.classList.add('ol-moving'); 2120 var listen = function(e) { 2121 if (e.type==='pointerup') { 2122 document.removeEventListener('pointermove', listen); 2123 document.removeEventListener('pointerup', listen); 2124 document.removeEventListener('pointercancel', listen); 2125 setTimeout(function() { 2126 this.moving = false;
2127 this.element.classList.remove('ol-moving'); 2128 }.bind(this)); 2129 } 2130 if (e.target === elt) cback(e); 2131 e.stopPropagation(); 2132 e.preventDefault(); 2133 }.bind(this); 2134 document.addEventListener('pointermove', listen, false); 2135 document.addEventListener('pointerup', listen, false); 2136 document.addEventListener('pointercancel', listen, false); 2137 e.stopPropagation(); 2138 e.preventDefault(); 2139 }.bind(this) 2140 elt.addEventListener('mousedown', handle, false); 2141 elt.addEventListener('touchstart', handle, false); 2142}; 2143/** Set the current value 2144 */ 2145ol.ext.input.Base.prototype.setValue = function(v) { 2146 if (v !== undefined) this.input.value = v; 2147 this.input.dispatchEvent(new Event('change')); 2148}; 2149/** Get the current getValue 2150 * @returns {string} 2151 */ 2152ol.ext.input.Base.prototype.getValue = function() { 2153 return this.input.value; 2154}; 2155/** Get the input element 2156 * @returns {Element} 2157 */ 2158ol.ext.input.Base.prototype.getInputElement = function() { 2159 return this.input; 2160}; 2161 2162/** Checkbox input 2163 * @constructor 2164 * @extends {ol.ext.input.Base} 2165 * @param {*} options 2166 * @param {string} [options.className] 2167 * @param {Element} [options.input] input element, if non create one (use parent to tell where) 2168 * @param {Element} [options.parent] element to use as parent if no input option 2169 * @param {booelan} [options.hover=true] show popup on hover 2170 * @param {string} [options.align=left] align popup left/right 2171 * @param {string} [options.type] a slide type as 'size' 2172 * @param {number} [options.min] min value, default use input min 2173 * @param {number} [options.max] max value, default use input max 2174 * @param {number} [options.step] step value, default use input step 2175 * @param {boolean} [options.overflow=false] enable values over min/max 2176 * @param {string|Element} [options.before] an element to add before the slider 2177 * @param {string|Element} [options.after] an element to add after the slider 2178 * @param {boolean} [options.fixed=false] no pupop 2179 */ 2180ol.ext.input.Slider = function(options) { 2181 options = options || {}; 2182 ol.ext.input.Base.call(this, options); 2183 this.set('overflow', !!options.overflow); 2184 this.element = ol.ext.element.create('DIV', { 2185 className: 'ol-input-slider' 2186 + (options.hover !== false ? ' ol-hover' : '') 2187 + (options.type ? ' ol-' + options.type : '') 2188 + (options.className ? ' ' + options.className : '') 2189 }); 2190 if (options.fixed) this.element.classList.add('ol-fixed'); 2191 var input = this.input; 2192 if (input.parentNode) input.parentNode.insertBefore(this.element, input); 2193 this.element.appendChild(input); 2194 if (options.align==='right') this.element.classList.add('ol-right'); 2195 var popup = ol.ext.element.create('DIV', { 2196 className: 'ol-popup', 2197 parent: this.element 2198 }) 2199 // Before element 2200 if (options.before) { 2201 ol.ext.element.create('DIV', { 2202 className: 'ol-before', 2203 html: options.before, 2204 parent: popup 2205 }); 2206 } 2207 // Slider 2208 var slider = this.slider = ol.ext.element.create('DIV', { 2209 className: 'ol-slider', 2210 parent: popup 2211 }); 2212 ol.ext.element.create('DIV', { 2213 className: 'ol-back', 2214 parent: this.slider 2215 }) 2216 // Cursor 2217 var cursor = ol.ext.element.create('DIV', { 2218 className: 'ol-cursor', 2219 parent: slider 2220 }) 2221 // After element 2222 if (options.after) { 2223 ol.ext.element.create('DIV', { 2224 className: 'ol-after', 2225 html: options.after, 2226 parent: popup 2227 }); 2228 } 2229 var min = (options.min !== undefined) ? options.min : parseFloat(input.min) || 0; 2230 var max = (options.max !== undefined) ? options.max : parseFloat(input.max) || 1; 2231 var step = (options.step !== undefined) ? options.step : parseFloat(input.step) || 1; 2232 var dstep = 1/step; 2233 // Handle popup drag 2234 this._listenDrag(slider, function(e) { 2235 var tx = Math.max(0, Math.min(e.offsetX / slider.clientWidth, 1)); 2236 cursor.style.left = Math.max(0, Math.min(100, Math.round(tx*100) )) + '%'; 2237 var v = input.value = Math.round((tx * (max - min) + min) * dstep) / dstep; 2238 this.dispatchEvent({ type: 'change:value', value: v }); 2239 }.bind(this)); 2240 // Set value 2241 var setValue = function() { 2242 var v = parseFloat(input.value) || 0; 2243 if (!this.get('overflow')) v = Math.max(min, Math.min(max, v)); 2244 if (v != input.value) input.value = v; 2245 var tx = (v - min) / (max - min); 2246 cursor.style.left = Math.max(0, Math.min(100, Math.round(tx*100) )) + '%'; 2247 this.dispatchEvent({ type: 'change:value', value: v }); 2248 }.bind(this); 2249 input.addEventListener('change', setValue); 2250 setValue(); 2251}; 2252ol.ext.inherits(ol.ext.input.Slider, ol.ext.input.Base); 2253 2254/** Base class for input popup 2255 * @constructor 2256 * @extends {ol.ext.input.Base} 2257 * @fires change:color 2258 * @fires color 2259 * @param {*} options 2260 * @param {string} [options.className] 2261 * @param {ol.colorLike} [options.color] default color 2262 * @param {Element} [options.input] input element, if non create one 2263 * @param {Element} [options.parent] parent element, if create an input 2264 * @param {string} [options.position='popup'] fixed | static | popup | inline (no popup) 2265 * @param {boolean} [options.autoClose=true] close when click on color 2266 * @param {boolean} [options.hidden=false] display the input 2267 */ 2268ol.ext.input.PopupBase = function(options) { 2269 options = options || {}; 2270 options.hidden = options.hidden!==false; 2271 ol.ext.input.Base.call(this, options); 2272 this.set('autoClose', options.autoClose !== false); 2273 this.element = ol.ext.element.create('DIV', { 2274 className: ('ol-ext-popup-input ' + (options.className || '')).trim(), 2275 }); 2276 switch (options.position) { 2277 case 'inline': break; 2278 case 'static': 2279 case 'fixed': { 2280 this.element.classList.add('ol-popup'); 2281 this.element.classList.add('ol-popup-fixed'); 2282 this._fixed = (options.position === 'fixed'); 2283 break; 2284 } 2285 default: { 2286 this.element.classList.add('ol-popup'); 2287 break; 2288 } 2289 } 2290 var input = this.input; 2291 if (input.parentNode) input.parentNode.insertBefore(this.element, input); 2292 // Show on element click 2293 this.element.addEventListener('click', function() { 2294 if (this.isCollapsed()) setTimeout( function() { this.collapse(false); }.bind(this) ); 2295 }.bind(this)); 2296 this._elt = {}; 2297 // Popup container 2298 this._elt.popup = ol.ext.element.create('DIV', { className: 'ol-popup', parent: this.element }); 2299 this._elt.popup.addEventListener('click', function(e) { e.stopPropagation(); }); 2300 // Hide on click outside 2301 var down = false;
2302 this._elt.popup.addEventListener('pointerdown', function() { 2303 down = true; 2304 }) 2305 this._elt.popup.addEventListener('click', function() { 2306 down = false; 2307 }) 2308 document.addEventListener('click', function() { 2309 if (!this.moving && !down) this.collapse(true); 2310 down = false; 2311 }.bind(this)) 2312 // Hide on window resize 2313 window.addEventListener('resize', function() { 2314 this.collapse(true); 2315 }.bind(this)); 2316}; 2317ol.ext.inherits(ol.ext.input.PopupBase, ol.ext.input.Base); 2318/** show/hide color picker 2319 * @param {boolean} [b=false] 2320 */ 2321ol.ext.input.PopupBase.prototype.collapse = function(b) { 2322 if (b != this.isCollapsed()) { 2323 this.dispatchEvent({ 2324 type: 'change:visible', 2325 visible: !this.isCollapsed() 2326 }); 2327 } 2328 this.dispatchEvent({ 2329 type: 'collapse', 2330 visible: !b 2331 }); 2332 if (b) { 2333 this._elt.popup.classList.remove('ol-visible'); 2334 } else { 2335 this._elt.popup.classList.add('ol-visible'); 2336 if (this._fixed) { 2337 // Get fixed position 2338 var pos = this.element.getBoundingClientRect(); 2339 var offset = ol.ext.element.getFixedOffset(this.element); 2340 pos = { 2341 bottom: pos.bottom - offset.top, 2342 left: pos.left - offset.left 2343 } 2344 // Test window overflow + recenter 2345 var dh = pos.bottom + this._elt.popup.offsetHeight + offset.top; 2346 if (dh > document.documentElement.clientHeight) { 2347 this._elt.popup.style.top = Math.max(document.documentElement.clientHeight - this._elt.popup.offsetHeight - offset.top, 0) + 'px'; 2348 } else { 2349 this._elt.popup.style.top = pos.bottom + 'px'; 2350 } 2351 var dw = pos.left + this._elt.popup.offsetWidth + offset.left; 2352 if (dw > document.documentElement.clientWidth) { 2353 this._elt.popup.style.left = Math.max(document.documentElement.clientWidth - this._elt.popup.offsetWidth - offset.left, 0) + 'px'; 2354 } else { 2355 this._elt.popup.style.left = pos.left + 'px'; 2356 } 2357 } 2358 } 2359}; 2360/** Is the popup collapsed ? 2361 * @returns {boolean} 2362 */ 2363ol.ext.input.PopupBase.prototype.isCollapsed = function() { 2364 return !this._elt.popup.classList.contains('ol-visible'); 2365}; 2366/** Toggle the popup 2367 */ 2368ol.ext.input.PopupBase.prototype.toggle = function() { 2369 this.collapse(!this.isCollapsed()); 2370}; 2371 2372/** Checkbox input 2373 * @constructor 2374 * @extends {ol.ext.input.Base} 2375 * @fires check 2376 * @param {*} options 2377 * @param {string} [options.className] 2378 * @param {Element|string} [options.html] label content 2379 * @param {string} [options.after] label garnish (placed after) 2380 * @param {Element} [options.input] input element, if non create one 2381 * @param {Element} [options.parent] parent element, if create an input 2382 * @param {boolean} [options.autoClose=true] 2383 * @param {boolean} [options.visible=false] display the input 2384 */ 2385ol.ext.input.Checkbox = function(options) { 2386 options = options || {}; 2387 ol.ext.input.Base.call(this, options); 2388 var label = this.element = document.createElement('LABEL'); 2389 if (options.html instanceof Element) label.appendChild(options.html) 2390 else if (options.html !== undefined) label.innerHTML = options.html; 2391 label.className = ('ol-ext-check ol-ext-checkbox ' + (options.className || '')).trim(); 2392 if (this.input.parentNode) this.input.parentNode.insertBefore(label, this.input); 2393 label.appendChild(this.input); 2394 label.appendChild(document.createElement('SPAN')); 2395 if (options.after) { 2396 label.appendChild(document.createTextNode(options.after)); 2397 } 2398 // Handle change 2399 this.input.addEventListener('change', function() { 2400 this.dispatchEvent({ type: 'check', checked: this.input.checked, value: this.input.value }); 2401 }.bind(this)); 2402}; 2403ol.ext.inherits(ol.ext.input.Checkbox, ol.ext.input.Base); 2404ol.ext.input.Checkbox.prototype.isChecked = function () { 2405 return this.input.checked; 2406}; 2407 2408/** A list element synchronize with a Collection. 2409 * Element in the list can be reordered interactively and the associated Collection is kept up to date. 2410 * @constructor 2411 * @fires item:select 2412 * @fires item:dblclick 2413 * @fires item:order 2414 * @extends {ol.Object} 2415 * @param {*} options 2416 * @param {Element} [options.target] 2417 * @param {Collection} [options.collection] the collection to display in the list 2418 * @param {function} [options.getTitle] a function that takes a collection item and returns an Element or a string 2419 */ 2420ol.ext.input.Collection = function(options) { 2421 ol.Object.call(this); 2422 this.element = ol.ext.element.create('UL', { 2423 className: ('ol-collection-list '+(options.className||'')).trim(), 2424 parent: options.target 2425 }) 2426 this.collection = options.collection; 2427 this._title = (typeof(options.getTitle) === 'function' ? options.getTitle : function(elt) { return elt.title }); 2428 this.refresh(); 2429 this.collection.on('change:length', function() { 2430 if (!this._reorder) { 2431 this.refresh();
2432 var pos = this.getSelectPosition(); 2433 if (pos < 0) { 2434 this.dispatchEvent({ type: 'item:select', position: -1, item: null }); 2435 } else { 2436 this.dispatchEvent({ type: 'item:order', position: pos, item: this._currentItem }); 2437 } 2438 } 2439 }.bind(this)); 2440}; 2441ol.ext.inherits(ol.ext.input.Collection, ol.Object); 2442/** Select an item 2443 * @param {*} item 2444 */ 2445ol.ext.input.Collection.prototype.select = function(item) { 2446 if (item === this._currentItem) return; 2447 var pos = -1; 2448 this._listElt.forEach(function (l, i) { 2449 if (l.item !== item) { 2450 l.li.classList.remove('ol-select'); 2451 } else { 2452 l.li.classList.add('ol-select'); 2453 pos = i; 2454 } 2455 }) 2456 this._currentItem = (pos >= 0 ? item : null); 2457 this.dispatchEvent({ type: 'item:select', position: pos, item: this._currentItem }); 2458}; 2459/** Select an item at 2460 * @param {number} n 2461 */ 2462ol.ext.input.Collection.prototype.selectAt = function(n) { 2463 this.select(this.collection.item(n)); 2464}; 2465/** Get current selection 2466 * @returns {*} 2467 */ 2468ol.ext.input.Collection.prototype.getSelect = function() { 2469 return this._currentItem; 2470}; 2471/** Get current selection 2472 * @returns {number} 2473 */ 2474ol.ext.input.Collection.prototype.getSelectPosition = function() { 2475 return this.collection.getArray().indexOf(this._currentItem); 2476}; 2477/** Redraw the list 2478 */ 2479ol.ext.input.Collection.prototype.refresh = function() { 2480 this.element.innerHTML = ''; 2481 this._listElt = []; 2482 this.collection.forEach((item, pos) => { 2483 var li = ol.ext.element.create('LI', { 2484 html: this._title(item), 2485 className: this._currentItem === item ? 'ol-select' : '', 2486 'data-position': pos, 2487 on: { 2488 click: function() { 2489 this.select(item); 2490 }.bind(this), 2491 dblclick: function() { 2492 this.dispatchEvent({ type: 'item:dblclick', position: pos, item: item }); 2493 }.bind(this), 2494 }, 2495 parent: this.element 2496 }); 2497 this._listElt.push({ li: li, item: item }); 2498 var order = ol.ext.element.create('DIV', { 2499 className: 'ol-noscroll ol-order', 2500 parent: li 2501 }); 2502 var current = pos; 2503 var move = function(e) { 2504 // Get target 2505 var target = e.pointerType==='touch' ? document.elementFromPoint(e.clientX, e.clientY) : e.target; 2506 while (target && target.parentNode !== this.element) { 2507 target = target.parentNode; 2508 } 2509 if (target && target !== li) { 2510 var over = parseInt(target.getAttribute('data-position')); 2511 if (target.getAttribute('data-position') < current) { 2512 target.insertAdjacentElement('beforebegin', li); 2513 current = over; 2514 } else { 2515 target.insertAdjacentElement('afterend', li); 2516 current = over+1; 2517 } 2518 } 2519 }.bind(this); 2520 var stop = function() { 2521 document.removeEventListener('pointermove', move); 2522 document.removeEventListener('pointerup', stop); 2523 document.removeEventListener('pointercancel', stop); 2524 if (current !== pos) { 2525 this._reorder = true; 2526 this.collection.removeAt(pos); 2527 this.collection.insertAt(current>pos ? current-1 : current, item); 2528 this._reorder = false; 2529 this.dispatchEvent({ type: 'item:order', position: current>pos ? current-1 : current, oldPosition: pos, item: item }) 2530 this.refresh(); 2531 } 2532 }.bind(this); 2533 order.addEventListener('pointerdown', function() { 2534 this.select(item) 2535 document.addEventListener('pointermove', move); 2536 document.addEventListener('pointerup', stop) 2537 document.addEventListener('pointercancel', stop) 2538 }.bind(this)); 2539 }); 2540} 2541 2542/** Color picker 2543 * @constructor 2544 * @extends {ol.ext.input.PopupBase} 2545 * @fires change:color 2546 * @fires color 2547 * @param {*} options 2548 * @param {string} [options.className] 2549 * @param {ol.colorLike} [options.color] default color 2550 * @param {Element} [options.input] input element, if non create one 2551 * @param {Element} [options.parent] parent element, if create an input 2552 * @param {boolean} [options.hastab=false] use tabs for palette / picker 2553 * @param {string} [options.paletteLabel="palette"] label for the palette tab 2554 * @param {string} [options.pickerLabel="picker"] label for the picker tab 2555 * @param {string} [options.position='popup'] fixed | static | popup | inline (no popup) 2556 * @param {boolean} [options.opacity=true] enable opacity 2557 * @param {boolean} [options.autoClose=true] close when click on color 2558 * @param {boolean} [options.hidden=true] display the input 2559 */ 2560ol.ext.input.Color = function(options) { 2561 options = options || {}; 2562 options.hidden = options.hidden!==false; 2563 options.className = ('ol-ext-colorpicker ' + (options.hastab ? 'ol-tab ' : '') + (options.className || '')).trim(); 2564 ol.ext.input.PopupBase.call(this, options); 2565 if (options.opacity===false) { 2566 this.element.classList.add('ol-nopacity'); 2567 } 2568 this._cursor = {}; 2569 var hsv = this._hsv = {}; 2570 // Vignet 2571 this._elt.vignet = ol.ext.element.create('DIV', { className: 'ol-vignet', parent: this.element }); 2572 // Bar 2573 var bar = ol.ext.element.create('DIV', { className: 'ol-tabbar', parent: this._elt.popup }); 2574 ol.ext.element.create('DIV', { 2575 className: 'ol-tab', 2576 html: options.paletteLabel || 'palette', 2577 click: function() { 2578 this.element.classList.remove('ol-picker-tab'); 2579 }.bind(this), 2580 parent: bar 2581 }); 2582 ol.ext.element.create('DIV', { 2583 className: 'ol-tab', 2584 html: options.pickerLabel || 'picker', 2585 click: function() { 2586 this.element.classList.add('ol-picker-tab'); 2587 }.bind(this), 2588 parent: bar 2589 }); 2590 // Popup container 2591 var container = ol.ext.element.create('DIV', { className: 'ol-container', parent: this._elt.popup }); 2592 // Color picker 2593 var picker = this._elt.picker = ol.ext.element.create('DIV', { className: 'ol-picker', parent: container }); 2594 var pickerCursor = this._cursor.picker = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: picker }); 2595 this._listenDrag(picker, function(e) { 2596 var tx = Math.max(0, Math.min(e.offsetX / picker.clientWidth, 1)); 2597 var ty = Math.max(0, Math.min(e.offsetY / picker.clientHeight, 1)); 2598 pickerCursor.style.left = Math.round(tx*100) + '%'; 2599 pickerCursor.style.top = Math.round(ty*100) + '%'; 2600 hsv.s = tx * 100; 2601 hsv.v = 100 - ty * 100; 2602 this.setColor(); 2603 }.bind(this)); 2604 // Opacity cursor 2605 var slider = ol.ext.element.create('DIV', { className: 'ol-slider', parent: container }); 2606 this._elt.slider = ol.ext.element.create('DIV', { parent: slider }); 2607 var sliderCursor = this._cursor.slide = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: slider }); 2608 this._listenDrag(slider, function(e) { 2609 var t = Math.max(0, Math.min(e.offsetX / slider.clientWidth, 1)); 2610 hsv.a = t*100; 2611 sliderCursor.style.left = Math.round(t*100) + '%'; 2612 this.setColor(); 2613 }.bind(this)); 2614 // Tint cursor 2615 var tint = ol.ext.element.create('DIV', { className: 'ol-tint', parent: container }); 2616 var tintCursor = this._cursor.tint = ol.ext.element.create('DIV', { className: 'ol-cursor', parent: tint }); 2617 this._listenDrag(tint, function(e) {
2618 var t = Math.max(0, Math.min(e.offsetY / tint.clientHeight, 1)); 2619 hsv.h = t*360; 2620 tintCursor.style.top = Math.round(t*100) + '%'; 2621 this.setColor(); 2622 }.bind(this)); 2623 // Clear button 2624 ol.ext.element.create('DIV', { 2625 className: 'ol-clear', 2626 click: function() { 2627 this.setColor([0,0,0,0]); 2628 }.bind(this), 2629 parent: container 2630 }); 2631 // RVB input 2632 var rgb = ol.ext.element.create('DIV', { 2633 className: 'ol-rgb', 2634 parent: container 2635 }); 2636 var changergb = function() { 2637 var r = Math.max(0, Math.min(255, parseInt(this._elt.r.value))); 2638 var g = Math.max(0, Math.min(255, parseInt(this._elt.g.value))); 2639 var b = Math.max(0, Math.min(255, parseInt(this._elt.b.value))); 2640 var a = Math.max(0, Math.min(1, parseFloat(this._elt.a.value))); 2641 this.setColor([r, g, b, a]); 2642 }.bind(this); 2643 this._elt.r = ol.ext.element.create('INPUT', { type: 'number', lang:'en-GB', change: changergb, min:0, max:255, parent: rgb }); 2644 this._elt.g = ol.ext.element.create('INPUT', { type: 'number', lang:'en-GB', change: changergb, min:0, max:255, parent: rgb }); 2645 this._elt.b = ol.ext.element.create('INPUT', { type: 'number', lang:'en-GB', change: changergb, min:0, max:255, parent: rgb }); 2646 this._elt.a = ol.ext.element.create('INPUT', { type: 'number', lang:'en-GB', change: changergb, min:0, max:1, step:.1, parent: rgb }); 2647 // Text color input 2648 this._elt.txtColor = ol.ext.element.create('INPUT', { 2649 type: 'text', 2650 className: 'ol-txt-color', 2651 change: function(){ 2652 var color; 2653 this._elt.txtColor.classList.remove('ol-error') 2654 try { 2655 color = ol.color.asArray(this._elt.txtColor.value); 2656 } catch(e) { 2657 this._elt.txtColor.classList.add('ol-error'); 2658 } 2659 if (color) this.setColor(color) 2660 }.bind(this), 2661 parent: container 2662 }); 2663 ol.ext.element.create('BUTTON', { 2664 html: 'OK', 2665 click: function() { 2666 this._addCustomColor(this.getColor()); 2667 this.collapse(true); 2668 }.bind(this), 2669 parent: container 2670 }); 2671 var i; 2672 // Color palette 2673 this._paletteColor = {}; 2674 this._elt.palette = ol.ext.element.create('DIV', { 2675 className: 'ol-palette', 2676 parent: this._elt.popup 2677 }) 2678 for (i=0; i<8; i++) { 2679 var c = Math.round(255 - 255*i/7); 2680 this.addPaletteColor([c,c,c], c);//ol.color.toHexa([c,c,c])); 2681 } 2682 var colors = ['#f00', '#f90', '#ff0', '#0f0', '#0ff', '#48e', '#00f', '#f0f'] 2683 colors.forEach(function(c){ 2684 this.addPaletteColor(c, ol.color.toHexa(ol.color.asArray(c))); 2685 }.bind(this)); 2686 for (i=0; i<5; i++) { 2687 colors.forEach(function(c){ 2688 c = ol.color.toHSV(ol.color.asArray(c)); 2689 c = [c[0], i/4*80+20, 100 - i/4*60]; 2690 c = ol.color.fromHSV(c,1) 2691 this.addPaletteColor(c, ol.color.toHexa(c)); 2692 }.bind(this)); 2693 } 2694 // Custom colors 2695 ol.ext.element.create('HR', { parent: this._elt.palette }); 2696 // Create custom color list 2697 if (!ol.ext.input.Color.customColorList) { 2698 ol.ext.input.Color.customColorList = new ol.Collection(); 2699 var ccolor = JSON.parse(localStorage.getItem('ol-ext@colorpicker') || '[]'); 2700 ccolor.forEach(function(c) { 2701 ol.ext.input.Color.customColorList.push(c); 2702 }) 2703 ol.ext.input.Color.customColorList.on(['add','remove'], function(){ 2704 localStorage.setItem('ol-ext@colorpicker', JSON.stringify(ol.ext.input.Color.customColorList.getArray())); 2705 }); 2706 } 2707 // Handle custom color 2708 ol.ext.input.Color.customColorList.on('add', function(e) { 2709 this.addPaletteColor(this.getColorFromID(e.element)); 2710 }.bind(this)); 2711 ol.ext.input.Color.customColorList.on('remove', function(e) { 2712 if (this._paletteColor[e.element]) this._paletteColor[e.element].element.remove(); 2713 delete this._paletteColor[e.element]; 2714 }.bind(this)); 2715 // Add new one 2716 ol.ext.input.Color.customColorList.forEach(function(c) { 2717 this._addCustomColor(this.getColorFromID(c)); 2718 }.bind(this)); 2719 // Current color 2720 this.setColor(options.color || [0,0,0,0]); 2721 this._currentColor = this.getColorID(this.getColor()); 2722 // Add new palette color 2723 this.on('color', function() { 2724 this._addCustomColor(this.getColor()); 2725 this._currentColor = this.getColorID(this.getColor()); 2726 this.setColor(); 2727 }.bind(this)); 2728 // Update color on hide 2729 this.on('collapse', function(e) { 2730 if (!e.visible) { 2731 var c = this.getColor(); 2732 if (this._currentColor !== this.getColorID(c)) { 2733 this.dispatchEvent({ type: 'color', color: c }); 2734 } 2735 } else { 2736 this._currentColor = this.getColorID(this.getColor()); 2737 } 2738 }.bind(this)); 2739}; 2740ol.ext.inherits(ol.ext.input.Color, ol.ext.input.PopupBase); 2741/** Custom color list 2742 * @private 2743 */ 2744ol.ext.input.Color.customColorList = null; 2745/** Add color to palette 2746 * @param {ol.colorLike} color 2747 * @param {string} title 2748 * @param {boolean} select 2749 */ 2750ol.ext.input.Color.prototype.addPaletteColor = function(color, title, select) { 2751 // Get color id 2752 try { 2753 color = ol.color.asArray(color); 2754 } catch(e) { 2755 return; 2756 } 2757 var id = this.getColorID(color); 2758 // Add new one 2759 if (!this._paletteColor[id] && color[3]) { 2760 this._paletteColor[id] = { 2761 color: color, 2762 element: ol.ext.element.create('DIV', { 2763 title: title || '', 2764 className: (color[3]<1 ? 'ol-alpha' : ''), 2765 style: { 2766 color: 'rgb('+(color.join(','))+')' 2767 }, 2768 click: function() { 2769 this.setColor(color); 2770 if (this.get('autoClose')) this.collapse(true); 2771 }.bind(this), 2772 parent: this._elt.palette 2773 }) 2774 } 2775 } 2776 if (select) {
2777 this._selectPalette(color); 2778 } 2779}; 2780/** Show palette or picker tab 2781 * @param {string} what palette or picker 2782 */ 2783ol.ext.input.Color.prototype.showTab = function(what) { 2784 if (what==='palette') this.element.classList.remove('ol-picker-tab'); 2785 else this.element.classList.add('ol-picker-tab'); 2786}; 2787/** Show palette or picker tab 2788 * @returns {string} palette or picker 2789 */ 2790ol.ext.input.Color.prototype.getTab = function() { 2791 return this.element.classList.contains('ol-picker-tab') ? 'picker' : 'palette'; 2792}; 2793/** Select a color in the palette 2794 * @private 2795 */ 2796ol.ext.input.Color.prototype._selectPalette = function(color) { 2797 var id = this.getColorID(color); 2798 Object.keys(this._paletteColor).forEach(function(c) { 2799 this._paletteColor[c].element.classList.remove('ol-select') 2800 }.bind(this)) 2801 if (this._paletteColor[id]) { 2802 this._paletteColor[id].element.classList.add('ol-select'); 2803 } 2804} 2805/** Set Color 2806 * @param { Array<number> } 2807 */ 2808ol.ext.input.Color.prototype.setColor = function(color) { 2809 var hsv = this._hsv; 2810 if (color) { 2811 color = ol.color.asArray(color); 2812 var hsv2 = ol.color.toHSV(color); 2813 hsv.h = hsv2[0]; 2814 hsv.s = hsv2[1]; 2815 hsv.v = hsv2[2]; 2816 if (hsv2.length > 3) hsv.a = hsv2[3]*100; 2817 else hsv.a = 100; 2818 this._cursor.picker.style.left = hsv.s + '%'; 2819 this._cursor.picker.style.top = (100-hsv.v) + '%'; 2820 this._cursor.tint.style.top = (hsv.h / 360 * 100) + '%'; 2821 this._cursor.slide.style.left = hsv.a + '%'; 2822 if (this.isCollapsed()) { 2823 this.dispatchEvent({ type: 'color', color: color }); 2824 } 2825 } else { 2826 /* 2827 hsv.h = Math.round(hsv.h) % 360; 2828 hsv.s = Math.round(hsv.s); 2829 hsv.v = Math.round(hsv.v); 2830 */ 2831 hsv.a = Math.round(hsv.a); 2832 color = this.getColor(); 2833 } 2834 var val = 'rgba('+color.join(', ')+')'; 2835 // Show color 2836 this._elt.picker.style.color = 'hsl(' + hsv.h + ', 100%, 50%)'; 2837 this._elt.slider.style.backgroundImage = 'linear-gradient(45deg, transparent, rgba('+this.getColor(false).join(',')+'))'; 2838 this._elt.vignet.style.color = val; 2839 // RGB 2840 this._elt.r.value = color[0]; 2841 this._elt.g.value = color[1]; 2842 this._elt.b.value = color[2]; 2843 this._elt.a.value = color[3]; 2844 // Txt color 2845 this._elt.txtColor.classList.remove('ol-error') 2846 if (color[3]===1) { 2847 this._elt.txtColor.value = ol.color.toHexa(color); 2848 } else { 2849 this._elt.txtColor.value = val; 2850 } 2851 this._selectPalette(color); 2852 // Set input value 2853 if (this.input.value !== val) { 2854 this.input.value = val; 2855 this.input.dispatchEvent(new Event('change')); 2856 } 2857}; 2858/** Get current color 2859 * @param {boolean} [opacity=true] 2860 * @return {Array<number>} 2861 */ 2862ol.ext.input.Color.prototype.getColor = function(opacity) { 2863 return ol.color.fromHSV([this._hsv.h, this._hsv.s, this._hsv.v, (opacity !== false) ? this._hsv.a/100 : 1], 1); 2864} 2865/** 2866 * @private 2867 */ 2868ol.ext.input.Color.prototype._addCustomColor = function(color) { 2869 var id = this.getColorID(color); 2870 if (this._paletteColor[id]) return; 2871 if (!color[3]) return; 2872 if (ol.ext.input.Color.customColorList.getArray().indexOf(id) < 0) { 2873 ol.ext.input.Color.customColorList.push(id); 2874 if (ol.ext.input.Color.customColorList.getLength() > 24) { 2875 ol.ext.input.Color.customColorList.removeAt(0) 2876 } 2877 } 2878 this.addPaletteColor(color); 2879}; 2880ol.ext.input.Color.prototype.clearCustomColor = function() { 2881 ol.ext.input.Color.customColorList.clear(); 2882}; 2883/** Convert color to id 2884 * @param {ol.colorLike} Color 2885 * @returns {number} 2886 */ 2887ol.ext.input.Color.prototype.getColorID = function(color) { 2888 color = ol.color.asArray(color); 2889 if (color[3]===undefined) color[3] = 1; 2890 return color.join('-'); 2891}; 2892/** Convert color to id 2893 * @param {number} id 2894 * @returns {Array<number>} Color 2895 */ 2896 ol.ext.input.Color.prototype.getColorFromID = function(id) { 2897 var c = id.split('-'); 2898 return ([parseFloat(c[0]), parseFloat(c[1]), parseFloat(c[2]), parseFloat(c[3])]); 2899}; 2900 2901/** Checkbox input 2902 * @constructor 2903 * @extends {ol.ext.input.Base} 2904 * @param {*} options 2905 * @param {string} [options.className] 2906 * @param {Array<Object>} options.options an array of options to place in the popup { html:, title:, value: } 2907 * @param {Element} [options.input] input element, if non create one 2908 * @param {Element} [options.parent] parent element, if create an input 2909 * @param {boolean} [options.hover=false] show popup on hover, default false or true if disabled or hidden 2910 * @param {boolean} [options.hidden] the input is display:none 2911 * @param {boolean} [options.disabled] disable input 2912 * @param {boolean} [options.fixed=false] don't use a popup, default use a popup 2913 * @param {string} [options.align=left] align popup left/right/middle 2914 */ 2915ol.ext.input.List = function(options) { 2916 options = options || {}; 2917 ol.ext.input.Base.call(this, options); 2918 this._content = ol.ext.element.create('DIV'); 2919 if (options.hidden || options.disabled) options.hover = true; 2920 this.element = ol.ext.element.create('DIV', { 2921 html: this._content, 2922 className: 'ol-input-popup' + (options.hover ? ' ol-hover' : '' ) 2923 }); 2924 this.set('hideOnClick', options.hideOnClick !== false); 2925 if (options.className) this.element.classList.add(options.className); 2926 if (options.fixed) { 2927 this.element.classList.add('ol-fixed'); 2928 this.set('hideOnClick', false); 2929 } 2930 switch (options.align) { 2931 case 'middle': 2932 this.set('hideOnClick', false); 2933 // fall through 2934 case 'rigth': 2935 this.element.classList.add('ol-' + options.align); 2936 break; 2937 default: 2938 break; 2939 } 2940 var input = this.input; 2941 if (input.parentNode) input.parentNode.insertBefore(this.element, input); 2942 this.element.appendChild(input); 2943 var popup = this.popup = ol.ext.element.create('UL', { 2944 className: 'ol-popup', 2945 parent: this.element 2946 }); 2947 var opts = []; 2948 options.options.forEach(option => { 2949 opts.push({ 2950 value: option.value, 2951 element: ol.ext.element.create('LI', { 2952 html: option.html, 2953 title: option.title || option.value, 2954 className: 'ol-option', 2955 on: { 2956 pointerdown: function() { 2957 this.setValue(option.value); 2958 if (this.get('hideOnClick')) { 2959 popup.style.display = 'none'; 2960 setTimeout(function() { popup.style.display = ''; }, 200); 2961 } 2962 }.bind(this) 2963 }, 2964 parent: this.popup 2965 }) 2966 }) 2967 }); 2968 this.input.addEventListener('change', function() { 2969 var v = this.input.value; 2970 var val;
2971 opts.forEach(function(o) { 2972 if (o.value == v) { 2973 o.element.classList.add('ol-selected'); 2974 val = o.element; 2975 } else { 2976 o.element.classList.remove('ol-selected'); 2977 } 2978 }); 2979 this.dispatchEvent({ type: 'change:value', value: this.getValue() }); 2980 this._content.innerHTML = val ? val.innerHTML : ''; 2981 }.bind(this)); 2982 // Initial value 2983 var event = new Event('change'); 2984 setTimeout(function() { this.input.dispatchEvent(event); }.bind(this)); 2985}; 2986ol.ext.inherits(ol.ext.input.List, ol.ext.input.Base); 2987 2988/** Switch input 2989 * @constructor 2990 * @extends {ol.ext.input.Checkbox} 2991 * @fires check 2992 * @param {*} options 2993 * @param {string} [options.className] 2994 * @param {Element} [options.input] input element, if non create one 2995 * @param {Element} [options.parent] parent element, if create an input 2996 */ 2997ol.ext.input.Radio = function(options) { 2998 options = options || {}; 2999 ol.ext.input.Checkbox.call(this, options); 3000 this.element.className = ('ol-ext-check ol-ext-radio ' + (options.className || '')).trim(); 3001}; 3002ol.ext.inherits(ol.ext.input.Radio, ol.ext.input.Checkbox); 3003 3004/** Checkbox input 3005 * @constructor 3006 * @extends {ol.ext.input.Base} 3007 * @param {*} options 3008 * @param {string} [options.className] 3009 * @param {Element} [options.input] input element, if non create one (use parent to tell where) 3010 * @param {Element} [options.input2] input element 3011 * @param {Element} [options.parent] element to use as parent if no input option 3012 * @param {number} [options.min] min value, default use input min 3013 * @param {number} [options.max] max value, default use input max 3014 * @param {number} [options.step] step value, default use input step 3015 * @param {boolean} [options.overflow=false] enable values over min/max 3016 */ 3017ol.ext.input.Range = function(options) { 3018 options = options || {}; 3019 ol.ext.input.Base.call(this, options); 3020 this.set('overflow', !!options.overflow); 3021 this.element = ol.ext.element.create('DIV', { 3022 className: 'ol-input-slider ol-input-range' 3023 + (options.className ? ' ' + options.className : '') 3024 }); 3025 var input = this.input; 3026 if (input.parentNode) input.parentNode.insertBefore(this.element, input); 3027 this.element.appendChild(input); 3028 // Slider 3029 var slider = this.slider = ol.ext.element.create('DIV', { 3030 className: 'ol-slider', 3031 parent: this.element 3032 }); 3033 var back = ol.ext.element.create('DIV', { 3034 className: 'ol-back', 3035 parent: this.slider 3036 }) 3037 var input2 = this.input2 = options.input2; 3038 if(input2) this.element.appendChild(input2); 3039 // Cursors 3040 var cursor = ol.ext.element.create('DIV', { 3041 className: 'ol-cursor', 3042 parent: slider 3043 }) 3044 var cursor2 = ol.ext.element.create('DIV', { 3045 className: 'ol-cursor', 3046 parent: input2 ? slider : undefined 3047 }) 3048 var currentCursor = cursor; 3049 function setCursor(e) { 3050 currentCursor = e.target; 3051 } 3052 cursor.addEventListener('mousedown', setCursor, false); 3053 cursor.addEventListener('touchstart', setCursor, false); 3054 cursor2.addEventListener('mousedown', setCursor, false); 3055 cursor2.addEventListener('touchstart', setCursor, false); 3056 var min = (options.min !== undefined) ? options.min : parseFloat(input.min) || 0; 3057 var max = (options.max !== undefined) ? options.max : parseFloat(input.max) || 1; 3058 var step = (options.step !== undefined) ? options.step : parseFloat(input.step) || 1; 3059 var dstep = 1/step; 3060 function setRange() { 3061 // range 3062 if (input2) { 3063 var l1 = parseFloat(cursor.style.left) || 0; 3064 var l2 = parseFloat(cursor2.style.left) || 0; 3065 back.style.left = Math.min(l1, l2) + '%'; 3066 back.style.right = (100 - Math.max(l1, l2)) + '%'; 3067 } else { 3068 back.style.left = 0; 3069 back.style.right = (100 - parseFloat(cursor.style.left) || 0) + '%'; 3070 } 3071 } 3072 function checkMinMax() { 3073 if (input2 && parseFloat(input.value) > parseFloat(input2.value)) { 3074 var v = input.value; 3075 input.value = input2.value; 3076 input2.value = v; 3077 setValue({ target: input }); 3078 if (input2) setValue({ target: input2 }); 3079 } 3080 } 3081 // Handle popup drag 3082 this._listenDrag(slider, function(e) { 3083 var current = (currentCursor===cursor ? input : input2); 3084 var tx = Math.max(0, Math.min(e.offsetX / slider.clientWidth, 1)); 3085 currentCursor.style.left = Math.max(0, Math.min(100, Math.round(tx*100) )) + '%'; 3086 var v = current.value = Math.round((tx * (max - min) + min) * dstep) / dstep; 3087 setRange(); 3088 this.dispatchEvent({ type: 'change:value', value: v }); 3089 if (e.type==='pointerup') { 3090 checkMinMax(); 3091 } 3092 }.bind(this)); 3093 // Set value 3094 var setValue = function(e) { 3095 var current = e.target;
3096 var curs = (current===input ? cursor : cursor2); 3097 var v = parseFloat(current.value) || 0; 3098 if (!this.get('overflow')) v = Math.max(min, Math.min(max, v)); 3099 if (v != current.value) current.value = v; 3100 var tx = (v - min) / (max - min); 3101 curs.style.left = Math.max(0, Math.min(100, Math.round(tx*100) )) + '%'; 3102 setRange(); 3103 this.dispatchEvent({ type: 'change:value', value: v }); 3104 checkMinMax(); 3105 }.bind(this); 3106 input.addEventListener('change', setValue); 3107 if (input2) input2.addEventListener('change', setValue); 3108 setValue({ target: input }); 3109 if (input2) setValue({ target: input2 }); 3110}; 3111ol.ext.inherits(ol.ext.input.Range, ol.ext.input.Base); 3112/** Set the current value (second input) 3113 */ 3114ol.ext.input.Range.prototype.setValue2 = function(v) { 3115 if (!this.input2) return; 3116 if (v !== undefined) this.input2.value = v; 3117 this.input2.dispatchEvent(new Event('change')); 3118}; 3119/** Get the current value (second input) 3120 */ 3121ol.ext.input.Range.prototype.getValue2 = function() { 3122 return this.input2 ? this.input2.value : null; 3123} 3124/** Get the current min value 3125 * @return {number} 3126 */ 3127ol.ext.input.Range.prototype.getMin = function() { 3128 return parseFloat(this.getValue()); 3129} 3130/** Get the current max value 3131 * @return {number} 3132 */ 3133ol.ext.input.Range.prototype.getMax = function() { 3134 return parseFloat(this.getValue2()); 3135} 3136 3137/** Checkbox input 3138 * @constructor 3139 * @extends {ol.ext.input.List} 3140 * @param {*} options 3141 * @param {string} [options.className] 3142 * @param {Element} [options.input] input element, if non create one 3143 * @param {Element} [options.parent] parent element, if create an input 3144 * @param {Array<number>} [options.size] a list of size (default 0,2,3,5,8,13,21,34,55) 3145 */ 3146ol.ext.input.Size = function(options) { 3147 options = options || {}; 3148 options.options = []; 3149 (options.size || [0,2,3,5,8,13,21,34,55]).forEach(function(i) { 3150 options.options.push({ 3151 value: i, 3152 html: ol.ext.element.create('DIV', { 3153 className: 'ol-option-'+i, 3154 style: { 3155 fontSize: i ? i+'px' : undefined 3156 } 3157 }) 3158 }) 3159 }) 3160 ol.ext.input.List.call(this, options); 3161 this._content.remove(); 3162 this.element.classList.add('ol-size'); 3163}; 3164ol.ext.inherits(ol.ext.input.Size, ol.ext.input.List); 3165/** Get the current value 3166 * @returns {number} 3167 */ 3168ol.ext.input.Size.prototype.getValue = function() { 3169 return parseFloat(ol.ext.input.List.prototype.getValue.call(this)); 3170}; 3171 3172/** Switch input 3173 * @constructor 3174 * @extends {ol.ext.input.Checkbox} 3175 * @fires check 3176 * @param {*} options 3177 * @param {string} [options.className] 3178 * @param {Element} [options.input] input element, if non create one 3179 * @param {Element} [options.parent] parent element, if create an input 3180 */ 3181ol.ext.input.Switch = function(options) { 3182 options = options || {}; 3183 ol.ext.input.Checkbox.call(this, options); 3184 this.element.className = ('ol-ext-toggle-switch ' + (options.className || '')).trim(); 3185}; 3186ol.ext.inherits(ol.ext.input.Switch, ol.ext.input.Checkbox); 3187 3188/** Checkbox input 3189 * @constructor 3190 * @extends {ol.ext.input.List} 3191 * @param {*} options 3192 * @param {string} [options.className] 3193 * @param {Element} [options.input] input element, if non create one 3194 * @param {Element} [options.parent] parent element, if create an input 3195 * @param {Array<number>} [options.size] a list of size (default 0,1,2,3,5,10,15,20) 3196 */ 3197ol.ext.input.Width = function(options) { 3198 options = options || {}; 3199 options.options = []; 3200 (options.size || [0,1,2,3,5,10,15,20]).forEach(function(i) { 3201 options.options.push({ 3202 value: i, 3203 html: ol.ext.element.create('DIV', { 3204 className: 'ol-option-'+i, 3205 style: { 3206 height: i || undefined 3207 } 3208 }) 3209 }) 3210 }); 3211 ol.ext.input.List.call(this, options); 3212 this._content.remove(); 3213 this.element.classList.add('ol-width'); 3214}; 3215ol.ext.inherits(ol.ext.input.Width, ol.ext.input.List); 3216/** Get the current value 3217 * @returns {number} 3218 */ 3219ol.ext.input.Width.prototype.getValue = function() { 3220 return parseFloat(ol.ext.input.List.prototype.getValue.call(this)); 3221} 3222 3223/** @namespace ol.legend 3224 */ 3225/*global ol*/ 3226if (window.ol && !ol.legend) { 3227 ol.legend = {}; 3228} 3229/** Legend class to draw features in a legend element 3230 * @constructor 3231 * @fires select 3232 * @fires refresh 3233 * @param {*} options 3234 * @param {String} options.title Legend title 3235 * @param {ol.size | undefined} options.size Size of the symboles in the legend, default [40, 25] 3236 * @param {number | undefined} options.margin Size of the symbole's margin, default 10 3237 * @param { ol.style.Text | undefined } options.textStyle a text style for the legend, default 16px sans-serif 3238 * @param { ol.style.Text | undefined } options.titleStyle a text style for the legend title, default textStyle + bold 3239 * @param { ol.style.Style | Array<ol.style.Style> | ol.StyleFunction | undefined } options.style a style or a style function to use with features 3240 */ 3241ol.legend.Legend = function(options) { 3242 options = options || {}; 3243 ol.Object.call(this); 3244 this._items = new ol.Collection(); 3245 var listeners = []; 3246 var tout; 3247 this._items.on('add', function(e) { 3248 listeners.push({ 3249 item: e.element, 3250 on: e.element.on('change', function() { 3251 this.refresh(); 3252 }.bind(this)) 3253 }); 3254 if (tout) { 3255 clearTimeout(tout); 3256 tout = null; 3257 } 3258 tout = setTimeout(function() { this.refresh(); }.bind(this), 0); 3259 }.bind(this)); 3260 this._items.on('remove', function(e) { 3261 for (var i=0; i<listeners; i++) { 3262 if (e.element === listeners[i].item) { 3263 ol.Observable.unByKey(listeners[i].on); 3264 listeners.splice(i, 1); 3265 break; 3266 } 3267 } 3268 if (tout) { 3269 clearTimeout(tout); 3270 tout = null; 3271 } 3272 tout = setTimeout(function() { this.refresh(); }.bind(this), 0); 3273 }.bind(this)); 3274 this._listElement = ol.ext.element.create('UL', { 3275 className: 'ol-legend' 3276 }); 3277 this._canvas = document.createElement('canvas'); 3278 this.set('size', options.size || [40, 25], true); 3279 this.set('margin', options.margin===0 ? 0 : options.margin || 10, true); 3280 this._textStyle = options.textStyle || new ol.style.Text({ 3281 font: '16px sans-serif', 3282 fill: new ol.style.Fill({ 3283 color: '#333' 3284 }), 3285 backgroundFill: new ol.style.Fill({ 3286 color: 'rgba(255,255,255,.8)' 3287 }) 3288 }); 3289 this._title = new ol.legend.Item({ title: options.title || '', className: 'ol-title' }); 3290 if (options.titleStyle) { 3291 this._titleStyle = options.titleStyle; 3292 } else { 3293 this._titleStyle = this._textStyle.clone(); 3294 this._titleStyle.setFont('bold '+this._titleStyle.getFont()); 3295 } 3296 this.setStyle(options.style); 3297 if (options.items instanceof Array) {
3298 options.items.forEach(function(item){ 3299 this.addItem(item); 3300 }.bind(this)); 3301 } 3302 this.refresh(); 3303}; 3304ol.ext.inherits(ol.legend.Legend, ol.Object); 3305/** Set legend title 3306 * @param {string} title 3307 */ 3308ol.legend.Legend.prototype.setTitle = function(title) { 3309 this._title.setTitle(title); 3310 this.refresh(); 3311}; 3312/** Get legend title 3313 * @returns {string} 3314 */ 3315ol.legend.Legend.prototype.getTitle = function() { 3316 return this._title.get('title'); 3317}; 3318/** Get text Style 3319 * @returns {ol.style.Text} 3320 */ 3321ol.legend.Legend.prototype.getTextStyle = function() { 3322 return this._textStyle; 3323}; 3324/** Set legend size 3325 * @param {ol.size} size 3326 */ 3327 ol.legend.Legend.prototype.set = function(key, value, opt_silent) { 3328 ol.Object.prototype.set.call(this, key, value, opt_silent); 3329 if (!opt_silent) this.refresh(); 3330}; 3331/** Get legend list element 3332 * @returns {Element} 3333 */ 3334ol.legend.Legend.prototype.getListElement = function() { 3335 return this._listElement; 3336}; 3337/** Get legend canvas 3338 * @returns {HTMLCanvasElement} 3339 */ 3340ol.legend.Legend.prototype.getCanvas = function() { 3341 return this._canvas; 3342}; 3343/** Set the style 3344 * @param { ol.style.Style | Array<ol.style.Style> | ol.StyleFunction | undefined } style a style or a style function to use with features 3345 */ 3346ol.legend.Legend.prototype.setStyle = function(style) { 3347 this._style = style; 3348 this.refresh(); 3349}; 3350/** Add a new item to the legend 3351 * @param {olLegendItemOptions|ol.legend.Item} item 3352 */ 3353ol.legend.Legend.prototype.addItem = function(item) { 3354 if (item instanceof ol.legend.Item) { 3355 this._items.push(item); 3356 } else { 3357 this._items.push(new ol.legend.Item(item)); 3358 } 3359}; 3360/** Get item collection 3361 * @param {ol.Collection} 3362 */ 3363ol.legend.Legend.prototype.getItems = function() { 3364 return this._items; 3365}; 3366/** Draw legend text 3367 * @private 3368 */ 3369ol.legend.Legend.prototype._drawText = function(ctx, text, x, y) { 3370 ctx.save(); 3371 ctx.scale(ol.has.DEVICE_PIXEL_RATIO, ol.has.DEVICE_PIXEL_RATIO); 3372 text = text || ''; 3373 var txt = text.split('\n'); 3374 if (txt.length===1) { 3375 ctx.fillText(text, x, y); 3376 } else { 3377 ctx.textBaseline = 'bottom'; 3378 ctx.fillText(txt[0], x, y); 3379 ctx.textBaseline = 'top'; 3380 ctx.fillText(txt[1], x, y); 3381 } 3382 ctx.restore(); 3383}; 3384/** Draw legend text 3385 * @private 3386 */ 3387ol.legend.Legend.prototype._measureText = function(ctx, text) { 3388 var txt = (text || '').split('\n'); 3389 if (txt.length===1) { 3390 return ctx.measureText(text); 3391 } else { 3392 var m1 = ctx.measureText(txt[0]); 3393 var m2 = ctx.measureText(txt[1]); 3394 return { width: Math.max(m1.width, m2.width), height: m1.height + m2.height } 3395 } 3396}; 3397/** Refresh the legend 3398 */ 3399ol.legend.Legend.prototype.refresh = function() { 3400 var table = this._listElement; 3401 table.innerHTML = ''; 3402 var margin = this.get('margin'); 3403 var width = this.get('size')[0] + 2 * margin; 3404 var height = this.get('lineHeight') || this.get('size')[1] + 2 * margin; 3405 var canvas = this.getCanvas(); 3406 var ctx = canvas.getContext('2d'); 3407 ctx.textAlign = 'left'; 3408 ctx.textBaseline = 'middle'; 3409 var ratio = ol.has.DEVICE_PIXEL_RATIO; 3410 // Calculate width 3411 ctx.font = this._titleStyle.getFont(); 3412 var textWidth = this._measureText(ctx, this.getTitle('title')).width; 3413 this._items.forEach(function(r) { 3414 if (r.get('feature') || r.get('typeGeom') ) { 3415 ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._textStyle.getFont(); 3416 textWidth = Math.max(textWidth, this._measureText(ctx, r.get('title')).width + width); 3417 } else { 3418 ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._titleStyle.getFont(); 3419 textWidth = Math.max(textWidth, this._measureText(ctx, r.get('title')).width); 3420 } 3421 }.bind(this)); 3422 canvas.width = (textWidth + 2*margin) * ratio; 3423 canvas.height = (this._items.getLength()+1) * height * ratio; 3424 canvas.style.height = ((this._items.getLength()+1) * height) + 'px'; 3425 ctx.textBaseline = 'middle'; 3426 ctx.fillStyle = ol.color.asString(this._textStyle.getFill().getColor()); 3427 // Add Title 3428 if (this.getTitle()) { 3429 table.appendChild(this._title.getElement([width, height], function(b) { 3430 this.dispatchEvent({ 3431 type: 'select', 3432 index: -1, 3433 symbol: b, 3434 item: this._title 3435 }); 3436 }.bind(this))); 3437 ctx.font = this._titleStyle.getFont(); 3438 ctx.textAlign = 'center'; 3439 this._drawText(ctx, this.getTitle(), canvas.width/ratio/2, height/2); 3440 } 3441 // Add items
3442 this._items.forEach(function(r,i) { 3443 var index = i + (this.getTitle() ? 1 : 0); 3444 table.appendChild(r.getElement([width, height], function(b) { 3445 this.dispatchEvent({ 3446 type: 'select', 3447 index: i, 3448 symbol: b, 3449 item: r 3450 }); 3451 }.bind(this))); 3452 var item = r.getProperties(); 3453 ctx.textAlign = 'left'; 3454 if (item.feature || item.typeGeom) { 3455 canvas = this.getLegendImage(item, canvas, index); 3456 ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._textStyle.getFont(); 3457 this._drawText(ctx, r.get('title'), width + margin, (i+1.5)*height); 3458 } else { 3459 ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._titleStyle.getFont(); 3460 if (/\bcenter\b/.test(item.className)) { 3461 ctx.textAlign = 'center'; 3462 this._drawText(ctx, r.get('title'), canvas.width/ratio/2, (i+1.5)*height); 3463 } else { 3464 this._drawText(ctx, r.get('title'), margin, (i+1.5)*height); 3465 } 3466 } 3467 }.bind(this)); 3468 // Done 3469 this.dispatchEvent({ 3470 type: 'refresh', 3471 width: width, 3472 height: (this._items.length+1)*height 3473 }); 3474}; 3475/** Get the image for a style 3476 * @param {olLegendItemOptions} item 3477 * @param {Canvas|undefined} canvas a canvas to draw in, if none creat one 3478 * @param {int|undefined} row row number to draw in canvas, default 0 3479 * @return {CanvasElement} 3480 */ 3481ol.legend.Legend.prototype.getLegendImage = function(options, canvas, row) { 3482 options = options || {}; 3483 return ol.legend.Legend.getLegendImage({ 3484 className: options.className, 3485 feature: options.feature, 3486 typeGeom: options.typeGeom, 3487 style: options.style || this._style, 3488 properties: options.properties, 3489 margin: options.margin || this.get('margin'), 3490 size: options.size || this.get('size'), 3491 lineHeight: options.lineHeight || this.get('lineHeight'), 3492 onload: function() { 3493 // Force refresh 3494 this.refresh(); 3495 }.bind(this) 3496 }, canvas, row); 3497}; 3498/** Get a symbol image for a given legend item 3499 * @param {olLegendItemOptions} item 3500 * @param {Canvas|undefined} canvas a canvas to draw in, if none creat one 3501 * @param {int|undefined} row row number to draw in canvas, default 0 3502 */ 3503ol.legend.Legend.getLegendImage = function(item, canvas, row) { 3504 item = item || {}; 3505 if (typeof(item.margin) === 'undefined') item.margin = 10; 3506 var size = item.size || [40,25]; 3507 item.onload = item.onload || function() { 3508 setTimeout(function() { 3509 ol.legend.Legend.getLegendImage(item, canvas, row); 3510 }, 100); 3511 }; 3512 var width = size[0] + 2 * item.margin; 3513 var height = item.lineHeight || (size[1] + 2 * item.margin); 3514 var ratio = ol.has.DEVICE_PIXEL_RATIO; 3515 if (!canvas) { 3516 row = 0; 3517 canvas = document.createElement('canvas'); 3518 canvas.width = width * ratio; 3519 canvas.height = height * ratio; 3520 } 3521 var ctx = canvas.getContext('2d'); 3522 ctx.save(); 3523 var vectorContext = ol.render.toContext(ctx, { pixelRatio: ratio }); 3524 var typeGeom = item.typeGeom; 3525 var style; 3526 var feature = item.feature; 3527 if (!feature && typeGeom) { 3528 if (/Point/.test(typeGeom)) feature = new ol.Feature(new ol.geom.Point([0,0])); 3529 else if (/LineString/.test(typeGeom)) feature = new ol.Feature(new ol.geom.LineString([0,0])); 3530 else feature = new ol.Feature(new ol.geom.Polygon([[0,0]])); 3531 if (item.properties) feature.setProperties(item.properties); 3532 } 3533 if (feature) { 3534 style = feature.getStyle(); 3535 if (typeof(style)==='function') style = style(feature); 3536 if (!style) { 3537 style = typeof(item.style) === 'function' ? item.style(feature) : item.style || []; 3538 } 3539 typeGeom = feature.getGeometry().getType(); 3540 } else { 3541 style = []; 3542 } 3543 if (!(style instanceof Array)) style = [style]; 3544 var cx = width/2; 3545 var cy = height/2; 3546 var sx = size[0]/2; 3547 var sy = size[1]/2; 3548 var i, s; 3549 // Get point offset 3550 if (typeGeom === 'Point') { 3551 var extent = null; 3552 for (i=0; s= style[i]; i++) { 3553 var img = s.getImage(); 3554 // Refresh legend on image load 3555 if (img) { 3556 var imgElt = img.getImage(); 3557 // Check image is loaded
3558 if (imgElt && imgElt.complete && !imgElt.naturalWidth) { 3559 if (typeof(item.onload) === 'function') { 3560 imgElt.addEventListener('load', function() { 3561 setTimeout(function() { 3562 item.onload() 3563 }, 100); 3564 }); 3565 } 3566 img.load(); 3567 } 3568 // Check anchor to center the image 3569 if (img.getAnchor) { 3570 var anchor = img.getAnchor(); 3571 if (anchor) { 3572 var si = img.getSize(); 3573 var dx = anchor[0] - si[0]; 3574 var dy = anchor[1] - si[1]; 3575 if (!extent) { 3576 extent = [dx, dy, dx+si[0], dy+si[1]]; 3577 } else { 3578 ol.extent.extend(extent, [dx, dy, dx+si[0], dy+si[1]]); 3579 } 3580 } 3581 } 3582 } 3583 } 3584 if (extent) { 3585 cx = cx + (extent[2] + extent[0])/2; 3586 cy = cy + (extent[3] + extent[1])/2; 3587 } 3588 } 3589 // Draw image 3590 cy += (row*height) || 0; 3591 for (i=0; s= style[i]; i++) { 3592 vectorContext.setStyle(s); 3593 switch (typeGeom) { 3594 case ol.geom.Point: 3595 case 'Point': 3596 case 'MultiPoint': 3597 vectorContext.drawGeometry(new ol.geom.Point([cx, cy])); 3598 break; 3599 case ol.geom.LineString: 3600 case 'LineString': 3601 case 'MultiLineString': 3602 ctx.save(); 3603 ctx.rect(item.margin * ratio, 0, size[0] * ratio, canvas.height); 3604 ctx.clip(); 3605 vectorContext.drawGeometry(new ol.geom.LineString([[cx-sx, cy], [cx+sx, cy]])); 3606 ctx.restore(); 3607 break; 3608 case ol.geom.Polygon: 3609 case 'Polygon': 3610 case 'MultiPolygon': 3611 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]]])); 3612 break; 3613 } 3614 } 3615 ctx.restore(); 3616 return canvas; 3617}; 3618 3619/** ol/legend/Item options 3620 * @typedef {Object} olLegendItemOptions 3621 * @property {string} title row title 3622 * @property {className} className 3623 * @property {ol.Feature} feature a feature to draw on the legend 3624 * @property {string} typeGeom type geom to draw with the style or the properties if no feature is provided 3625 * @property {Object} properties a set of properties to use with a style function 3626 * @property {ol.style.Style.styleLike} style a style or a style function to use to draw the legend symbol 3627 * @property {ol.style.Text} textStyle a text style to draw the item title in the legend 3628 * @property {ol.size|undefined} size 3629 * @property {number|undefined} margin 3630 */ 3631/** A class for legend items 3632 * @constructor 3633 * @fires select 3634 * @param {olLegendItemOptions} options 3635 */ 3636ol.legend.Item = function(options) { 3637 options = options || {}; 3638 ol.Object.call(this, options); 3639 if (options.feature) this.set('feature', options.feature.clone()); 3640}; 3641ol.ext.inherits(ol.legend.Item, ol.Object); 3642/** Set the legend title 3643 * @param {string} title 3644 */ 3645ol.legend.Item.prototype.setTitle = function(title) { 3646 this.set('title', title || ''); 3647 this.changed(); 3648}; 3649/** Get element 3650 * @param {ol.size} size symbol size 3651 */ 3652ol.legend.Item.prototype.getElement = function(size, onclick) { 3653 var element = ol.ext.element.create('LI', { 3654 className : this.get('className'), 3655 click: function(e) { 3656 onclick(false); 3657 e.stopPropagation(); 3658 }, 3659 style: { height: size[1] + 'px' }, 3660 'aria-label': this.get('title') 3661 }); 3662 ol.ext.element.create ('DIV', { 3663 click: function(e) { 3664 onclick(true); 3665 e.stopPropagation(); 3666 }, 3667 style: { 3668 width: size[0] + 'px', 3669 height: size[1] + 'px' 3670 }, 3671 parent: element 3672 }); 3673 return element; 3674}; 3675 3676/** 3677 * @classdesc 3678 * Attribution Control integrated in the canvas (for jpeg/png 3679 * @see http://www.kreidefossilien.de/webgis/dokumentation/beispiele/export-map-to-png-with-scale 3680 * 3681 * @constructor 3682 * @extends {ol.control.Control} 3683 * @param {Object=} options extend the ol.control options. 3684 * @param {ol.style.Style} options.style style used to draw the title. 3685 */ 3686ol.control.CanvasBase = function(options) { 3687 if (!options) options = {}; 3688 // Define a style to draw on the canvas 3689 this.setStyle(options.style); 3690 ol.control.Control.call(this, options); 3691} 3692ol.ext.inherits(ol.control.CanvasBase, ol.control.Control); 3693/** 3694 * Remove the control from its current map and attach it to the new map. 3695 * Subclasses may set up event handlers to get notified about changes to 3696 * the map here. 3697 * @param {ol.Map} map Map. 3698 * @api stable 3699 */ 3700ol.control.CanvasBase.prototype.setMap = function (map) { 3701 this.getCanvas(map); 3702 var oldmap = this.getMap(); 3703 if (this._listener) { 3704 ol.Observable.unByKey(this._listener); 3705 this._listener = null; 3706 } 3707 ol.control.Control.prototype.setMap.call(this, map); 3708 if (oldmap) { 3709 try { oldmap.renderSync(); } catch(e) { /* ok */ } 3710 } 3711 if (map) { 3712 this._listener = map.on('postcompose', this._draw.bind(this)); 3713 // Get a canvas layer on top of the map 3714 } 3715}; 3716/** Get canvas overlay 3717 */ 3718ol.control.CanvasBase.prototype.getCanvas = function(map) { 3719 return ol.ext.getMapCanvas(map); 3720}; 3721/** Get map Canvas 3722 * @private 3723 */ 3724ol.control.CanvasBase.prototype.getContext = function(e) { 3725 var ctx = e.context; 3726 if (!ctx && this.getMap()) { 3727 var c = this.getMap().getViewport().getElementsByClassName('ol-fixedoverlay')[0]; 3728 ctx = c ? c.getContext('2d') : null; 3729 } 3730 return ctx; 3731}; 3732/** Set Style 3733 * @api 3734 */ 3735ol.control.CanvasBase.prototype.setStyle = function(style) { 3736 this._style = style || new ol.style.Style ({}); 3737}; 3738/** Get style 3739 * @api 3740 */ 3741ol.control.CanvasBase.prototype.getStyle = function() { 3742 return this._style; 3743}; 3744/** Get stroke 3745 * @api 3746 */ 3747ol.control.CanvasBase.prototype.getStroke = function() { 3748 var t = this._style.getStroke(); 3749 if (!t) this._style.setStroke(new ol.style.Stroke ({ color:'#000', width:1.25 })); 3750 return this._style.getStroke(); 3751}; 3752/** Get fill 3753 * @api 3754 */ 3755ol.control.CanvasBase.prototype.getFill = function() {
3756 var t = this._style.getFill(); 3757 if (!t) this._style.setFill(new ol.style.Fill ({ color:'#fff' })); 3758 return this._style.getFill(); 3759}; 3760/** Get stroke 3761 * @api 3762 */ 3763ol.control.CanvasBase.prototype.getTextStroke = function() { 3764 var t = this._style.getText(); 3765 if (!t) t = new ol.style.Text({}); 3766 if (!t.getStroke()) t.setStroke(new ol.style.Stroke ({ color:'#fff', width:3 })); 3767 return t.getStroke(); 3768}; 3769/** Get text fill 3770 * @api 3771 */ 3772ol.control.CanvasBase.prototype.getTextFill = function() { 3773 var t = this._style.getText(); 3774 if (!t) t = new ol.style.Text({}); 3775 if (!t.getFill()) t.setFill(new ol.style.Fill ({ color:'#fff' })); 3776 return t.getFill(); 3777}; 3778/** Get text font 3779 * @api 3780 */ 3781ol.control.CanvasBase.prototype.getTextFont = function() { 3782 var t = this._style.getText(); 3783 if (!t) t = new ol.style.Text({}); 3784 if (!t.getFont()) t.setFont('12px sans-serif'); 3785 return t.getFont(); 3786}; 3787/** Draw the control on canvas 3788 * @protected 3789 */ 3790ol.control.CanvasBase.prototype._draw = function(/* e */) { 3791 console.warn('[CanvasBase] draw function not implemented.'); 3792}; 3793 3794/* Copyright (c) 2019 Jean-Marc VIGLINO, 3795 released under the CeCILL-B license (French BSD license) 3796 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 3797*/ 3798/** 3799 * This is the base class for Select controls on attributes values. 3800 * Abstract base class; 3801 * normally only used for creating subclasses and not instantiated in apps. 3802 * 3803 * @constructor 3804 * @extends {ol.control.Control} 3805 * @fires select 3806 * @param {Object=} options 3807 * @param {string} options.className control class name 3808 * @param {Element} options.content form element 3809 * @param {Element | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 3810 * @param {ol.Collection<ol.Feature>} options.features a collection of feature to search in, the collection will be kept in date while selection 3811 * @param {ol.source.Vector | Array<ol.source.Vector>} options.source the source to search in if no features set 3812 * @param {string} options.btInfo ok button label 3813 */ 3814ol.control.SelectBase = function(options) { 3815 if (!options) options = {}; 3816 this._features = this.setFeatures(options.features); 3817 var element; 3818 if (options.target) { 3819 element = document.createElement("div"); 3820 } else { 3821 element = document.createElement("div"); 3822 element.className = 'ol-select ol-unselectable ol-control ol-collapsed'; 3823 ol.ext.element.create('BUTTON', { 3824 type: 'button', 3825 on: { 3826 'click touchstart': function(e) { 3827 element.classList.toggle('ol-collapsed'); 3828 e.preventDefault(); 3829 } 3830 }, 3831 parent: element 3832 }); 3833 } 3834 if (options.className) element.classList.add(options.className); 3835 var content = options.content || ol.ext.element.create('DIV'); 3836 element.appendChild(content); 3837 // OK button 3838 ol.ext.element.create('BUTTON', { 3839 html: options.btInfo || 'OK', 3840 className: 'ol-ok', 3841 on: { 'click': this.doSelect.bind(this) }, 3842 parent: content 3843 }); 3844 ol.control.Control.call(this, { 3845 element: element, 3846 target: options.target 3847 }); 3848 this.setSources(options.source); 3849}; 3850ol.ext.inherits(ol.control.SelectBase, ol.control.Control); 3851/** Set the current sources 3852 * @param {ol.source.Vector|Array<ol.source.Vector>|undefined} source 3853 */ 3854ol.control.SelectBase.prototype.setSources = function (source) { 3855 if (source) { 3856 this.set ('source', (source instanceof Array) ? source : [source]); 3857 } else { 3858 this.unset('source'); 3859 } 3860}; 3861/** Set feature collection to search in 3862 * @param {ol.Collection<ol.Feature>} features 3863 */ 3864ol.control.SelectBase.prototype.setFeatures = function (features) { 3865 if (features instanceof ol.Collection) this._features = features; 3866 else this._features = null; 3867}; 3868/** Get feature collection to search in 3869 * @return {ol.Collection<ol.Feature>} 3870 */ 3871ol.control.SelectBase.prototype.getFeatures = function () { 3872 return this._features; 3873}; 3874/** List of operators / translation 3875 * @api 3876 */ 3877ol.control.SelectBase.prototype.operationsList = { 3878 '=': '=', 3879 '!=': 'â ', 3880 '<': '<', 3881 '<=': 'â¤', 3882 '>=': 'â¥', 3883 '>': '>', 3884 'contain': 'â', // â 3885 '!contain': 'â', // â 3886 'regexp': 'â', 3887 '!regexp': 'â' 3888}; 3889/** Escape string for regexp 3890 * @param {string} search 3891 * @return {string} 3892 */ 3893ol.control.SelectBase.prototype._escape = function (s) { 3894 return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string 3895}; 3896/** 3897 * Test if a feature check aconditino 3898 * @param {ol.Feature} f the feature to check condition 3899 * @param {Object} condition an object to use for test 3900 * @param {string} condition.attr attribute name 3901 * @param {string} condition.op operator
3902 * @param {any} condition.val value to test 3903 * @param {boolean} usecase use case or not when testing strings 3904 * @return {boolean} 3905 * @private 3906 */ 3907ol.control.SelectBase.prototype._checkCondition = function (f, condition, usecase) { 3908 if (!condition.attr) return true; 3909 var val = f.get(condition.attr); 3910 // Try to test numeric values 3911 var isNumber = (Number(val) == val && Number(condition.val) == condition.val); 3912 if (isNumber) val = Number(val); 3913 // Check 3914 var rex; 3915 switch (condition.op) { 3916 case '=': 3917 if (isNumber) { 3918 return val == condition.val; 3919 } else { 3920 rex = new RegExp('^'+this._escape(condition.val)+'$', usecase ? '' : 'i'); 3921 return rex.test(val); 3922 } 3923 case '!=': 3924 if (isNumber) { 3925 return val != condition.val; 3926 } else { 3927 rex = new RegExp('^'+this._escape(condition.val)+'$', usecase ? '' : 'i'); 3928 return !rex.test(val); 3929 } 3930 case '<': 3931 return val < condition.val; 3932 case '<=': 3933 return val <= condition.val; 3934 case '>': 3935 return val > condition.val; 3936 case '>=': 3937 return val >= condition.val; 3938 case 'contain': 3939 rex = new RegExp(this._escape(condition.val), usecase ? '' : 'i'); 3940 return rex.test(val); 3941 case '!contain': 3942 rex = new RegExp(this._escape(condition.val), usecase ? '' : 'i'); 3943 return !rex.test(val); 3944 case 'regexp': 3945 rex = new RegExp(condition.val, usecase ? '' : 'i'); 3946 return rex.test(val); 3947 case '!regexp': 3948 rex = new RegExp(condition.val, usecase ? '' : 'i'); 3949 return !rex.test(val); 3950 default: 3951 return false; 3952 } 3953}; 3954/** Selection features in a list of features 3955 * @param {Array<ol.Feature>} result the current list of features 3956 * @param {Array<ol.Feature>} features to test in 3957 * @param {Object} condition 3958 * @param {string} condition.attr attribute name 3959 * @param {string} condition.op operator 3960 * @param {any} condition.val value to test 3961 * @param {boolean} all all conditions must be valid 3962 * @param {boolean} usecase use case or not when testing strings 3963 */ 3964ol.control.SelectBase.prototype._selectFeatures = function (result, features, conditions, all, usecase) { 3965 conditions = conditions || []; 3966 var f; 3967 for (var i=features.length-1; f=features[i]; i--) { 3968 var isok = all; 3969 for (var k=0, c; c=conditions[k]; k++) { 3970 if (c.attr) { 3971 if (all) { 3972 isok = isok && this._checkCondition(f,c,usecase); 3973 } 3974 else { 3975 isok = isok || this._checkCondition(f,c,usecase); 3976 } 3977 } 3978 } 3979 if (isok) { 3980 result.push(f); 3981 } else if (this._features) { 3982 this._features.removeAt(i); 3983 } 3984 } 3985 return result; 3986}; 3987/** Get vector source 3988 * @return {Array<ol.source.Vector>} 3989 */ 3990ol.control.SelectBase.prototype.getSources = function () { 3991 if (this.get('source')) return this.get('source'); 3992 var sources = []; 3993 function getSources(layers) { 3994 layers.forEach(function(l){ 3995 if (l.getLayers) { 3996 getSources(l.getLayers()); 3997 } else if (l.getSource && l.getSource() instanceof ol.source.Vector) { 3998 sources.push(l.getSource()); 3999 } 4000 }); 4001 } 4002 if (this.getMap()) { 4003 getSources(this.getMap().getLayers()); 4004 } 4005 return sources; 4006}; 4007/** Select features by attributes 4008 * @param {*} options 4009 * @param {Array<ol.source.Vector>|undefined} options.sources source to apply rules, default the select sources 4010 * @param {bool} options.useCase case sensitive, default false 4011 * @param {bool} options.matchAll match all conditions, default false 4012 * @param {Array<conditions>} options.conditions array of conditions 4013 * @return {Array<ol.Feature>} 4014 * @fires select 4015 */ 4016ol.control.SelectBase.prototype.doSelect = function (options) { 4017 options = options || {}; 4018 var features = []; 4019 if (options.features) { 4020 this._selectFeatures(features, options.features, options.conditions, options.matchAll, options.useCase); 4021 } else if (this._features) { 4022 this._selectFeatures(features, this._features.getArray(), options.conditions, options.matchAll, options.useCase); 4023 } else {
4024 var sources = options.sources || this.getSources(); 4025 sources.forEach(function(s) { 4026 this._selectFeatures(features, s.getFeatures(), options.conditions, options.matchAll, options.useCase); 4027 }.bind(this)); 4028 } 4029 this.dispatchEvent({ type:"select", features: features }); 4030 return features; 4031}; 4032 4033/* Copyright (c) 2016 Jean-Marc VIGLINO, 4034 released under the CeCILL-B license (French BSD license) 4035 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 4036*/ 4037/** A simple push button control 4038 * @constructor 4039 * @extends {ol.control.Control} 4040 * @param {Object=} options Control options. 4041 * @param {String} options.className class of the control 4042 * @param {String} options.title title of the control 4043 * @param {String} options.name an optional name, default none 4044 * @param {String} options.html html to insert in the control 4045 * @param {function} options.handleClick callback when control is clicked (or use change:active event) 4046 */ 4047ol.control.Button = function(options){ 4048 options = options || {}; 4049 var element = document.createElement("div"); 4050 element.className = (options.className || '') + " ol-button ol-unselectable ol-control"; 4051 var self = this; 4052 var bt = this.button_ = document.createElement(/ol-text-button/.test(options.className) ? "div": "button"); 4053 bt.type = "button"; 4054 if (options.title) bt.title = options.title; 4055 if (options.name) bt.name = options.name; 4056 if (options.html instanceof Element) bt.appendChild(options.html) 4057 else bt.innerHTML = options.html || ""; 4058 var evtFunction = function(e) { 4059 if (e && e.preventDefault) { 4060 e.preventDefault(); 4061 e.stopPropagation(); 4062 } 4063 if (options.handleClick) { 4064 options.handleClick.call(self, e); 4065 } 4066 }; 4067 bt.addEventListener("click", evtFunction); 4068 // bt.addEventListener("touchstart", evtFunction); 4069 element.appendChild(bt); 4070 // Try to get a title in the button content 4071 if (!options.title && bt.firstElementChild) { 4072 bt.title = bt.firstElementChild.title; 4073 } 4074 ol.control.Control.call(this, { 4075 element: element, 4076 target: options.target 4077 }); 4078 if (options.title) { 4079 this.set("title", options.title); 4080 } 4081 if (options.title) this.set("title", options.title); 4082 if (options.name) this.set("name", options.name); 4083}; 4084ol.ext.inherits(ol.control.Button, ol.control.Control); 4085/** Set the control visibility 4086* @param {boolean} b 4087*/ 4088ol.control.Button.prototype.setVisible = function (val) { 4089 if (val) ol.ext.element.show(this.element); 4090 else ol.ext.element.hide(this.element); 4091}; 4092/** 4093 * Set the button title 4094 * @param {string} title 4095 */ 4096ol.control.Button.prototype.setTitle = function(title) { 4097 this.button_.setAttribute('title', title); 4098}; 4099/** 4100 * Set the button html 4101 * @param {string} html 4102 */ 4103ol.control.Button.prototype.setHtml = function(html) { 4104 ol.ext.element.setHTML (this.button_, html); 4105}; 4106/** 4107 * Get the button element 4108 * @returns {Element} 4109 */ 4110ol.control.Button.prototype.getButtonElement = function() { 4111 return this.button_; 4112}; 4113 4114/* Copyright (c) 2016 Jean-Marc VIGLINO, 4115 released under the CeCILL-B license (French BSD license) 4116 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 4117*/ 4118/** A simple toggle control 4119 * The control can be created with an interaction to control its activation. 4120 * 4121 * @constructor 4122 * @extends {ol.control.Button} 4123 * @fires change:active, change:disable 4124 * @param {Object=} options Control options. 4125 * @param {String} options.className class of the control 4126 * @param {String} options.title title of the control 4127 * @param {String} options.html html to insert in the control 4128 * @param {ol.interaction} options.interaction interaction associated with the control 4129 * @param {bool} options.active the control is created active, default false 4130 * @param {bool} options.disable the control is created disabled, default false 4131 * @param {ol.control.Bar} options.bar a subbar associated with the control (drawn when active if control is nested in a ol.control.Bar) 4132 * @param {bool} options.autoActive the control will activate when shown in an ol.control.Bar, default false 4133 * @param {function} options.onToggle callback when control is clicked (or use change:active event) 4134 */ 4135ol.control.Toggle = function(options) { 4136 options = options || {}; 4137 var self = this; 4138 this.interaction_ = options.interaction; 4139 if (this.interaction_) { 4140 this.interaction_.setActive(options.active); 4141 this.interaction_.on("change:active", function() { 4142 self.setActive(self.interaction_.getActive()); 4143 }); 4144 } 4145 if (options.toggleFn) options.onToggle = options.toggleFn; // compat old version 4146 options.handleClick = function() {
4147 self.toggle(); 4148 if (options.onToggle) options.onToggle.call(self, self.getActive()); 4149 }; 4150 options.className = (options.className||"") + " ol-toggle"; 4151 ol.control.Button.call(this, options); 4152 this.set("title", options.title); 4153 this.set ("autoActivate", options.autoActivate); 4154 if (options.bar) this.setSubBar(options.bar); 4155 this.setActive (options.active); 4156 this.setDisable (options.disable); 4157}; 4158ol.ext.inherits(ol.control.Toggle, ol.control.Button); 4159/** 4160 * Set the map instance the control is associated with 4161 * and add interaction attached to it to this map. 4162 * @param {_ol_Map_} map The map instance. 4163 */ 4164ol.control.Toggle.prototype.setMap = function(map) { 4165 if (!map && this.getMap()) { 4166 if (this.interaction_) { 4167 this.getMap().removeInteraction (this.interaction_); 4168 } 4169 if (this.subbar_) this.getMap().removeControl (this.subbar_); 4170 } 4171 ol.control.Button.prototype.setMap.call(this, map); 4172 if (map) { 4173 if (this.interaction_) map.addInteraction (this.interaction_); 4174 if (this.subbar_) map.addControl (this.subbar_); 4175 } 4176}; 4177/** Get the subbar associated with a control 4178 * @return {ol.control.Bar} 4179 */ 4180ol.control.Toggle.prototype.getSubBar = function () { 4181 return this.subbar_; 4182}; 4183/** Set the subbar associated with a control 4184 * @param {ol.control.Bar} [bar] a subbar if none remove the current subbar 4185 */ 4186ol.control.Toggle.prototype.setSubBar = function (bar) { 4187 var map = this.getMap(); 4188 if (map && this.subbar_) map.removeControl (this.subbar_); 4189 this.subbar_ = bar; 4190 if (bar) { 4191 this.subbar_.setTarget(this.element); 4192 this.subbar_.element.classList.add("ol-option-bar"); 4193 if (map) map.addControl (this.subbar_); 4194 } 4195}; 4196/** 4197 * Test if the control is disabled. 4198 * @return {bool}. 4199 * @api stable 4200 */ 4201ol.control.Toggle.prototype.getDisable = function() { 4202 var button = this.element.querySelector("button"); 4203 return button && button.disabled; 4204}; 4205/** Disable the control. If disable, the control will be deactivated too. 4206* @param {bool} b disable (or enable) the control, default false (enable) 4207*/ 4208ol.control.Toggle.prototype.setDisable = function(b) { 4209 if (this.getDisable()==b) return; 4210 this.element.querySelector("button").disabled = b; 4211 if (b && this.getActive()) this.setActive(false); 4212 this.dispatchEvent({ type:'change:disable', key:'disable', oldValue:!b, disable:b }); 4213}; 4214/** 4215 * Test if the control is active. 4216 * @return {bool}. 4217 * @api stable 4218 */ 4219ol.control.Toggle.prototype.getActive = function() { 4220 return this.element.classList.contains("ol-active"); 4221}; 4222/** Toggle control state active/deactive 4223 */ 4224ol.control.Toggle.prototype.toggle = function() { 4225 if (this.getActive()) this.setActive(false); 4226 else this.setActive(true); 4227}; 4228/** Change control state 4229 * @param {bool} b activate or deactivate the control, default false 4230 */ 4231ol.control.Toggle.prototype.setActive = function(b) { 4232 if (this.interaction_) this.interaction_.setActive (b); 4233 if (this.subbar_) this.subbar_.setActive(b); 4234 if (this.getActive()===b) return; 4235 if (b) this.element.classList.add("ol-active"); 4236 else this.element.classList.remove("ol-active"); 4237 this.dispatchEvent({ type:'change:active', key:'active', oldValue:!b, active:b }); 4238}; 4239/** Set the control interaction 4240* @param {_ol_interaction_} i interaction to associate with the control 4241*/ 4242ol.control.Toggle.prototype.setInteraction = function(i) { 4243 this.interaction_ = i; 4244}; 4245/** Get the control interaction 4246* @return {_ol_interaction_} interaction associated with the control 4247*/ 4248ol.control.Toggle.prototype.getInteraction = function() { 4249 return this.interaction_; 4250}; 4251 4252/* Copyright (c) 2017 Jean-Marc VIGLINO, 4253 released under the CeCILL-B license (French BSD license) 4254 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 4255*/ 4256/** 4257 * Search Control. 4258 * This is the base class for search controls. You can use it for simple custom search or as base to new class. 4259 * @see ol.control.SearchFeature 4260 * @see ol.control.SearchPhoton 4261 * 4262 * @constructor 4263 * @extends {ol.control.Control} 4264 * @fires select 4265 * @fires change:input 4266 * @param {Object=} options 4267 * @param {string} options.className control class name 4268 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 4269 * @param {string | undefined} options.title Title to use for the search button tooltip, default "Search" 4270 * @param {string | undefined} options.reverseTitle Title to use for the reverse geocoding button tooltip, default "Click on the map..." 4271 * @param {string | undefined} options.placeholder placeholder, default "Search..." 4272 * @param {boolean | undefined} options.reverse enable reverse geocoding, default false 4273 * @param {string | undefined} options.inputLabel label for the input, default none 4274 * @param {string | undefined}
4274 options.collapsed search is collapsed on start, default true 4275 * @param {string | undefined} options.noCollapse prevent collapsing on input blur, default false 4276 * @param {number | undefined} options.typing a delay on each typing to start searching (ms) use -1 to prevent autocompletion, default 300. 4277 * @param {integer | undefined} options.minLength minimum length to start searching, default 1 4278 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 4279 * @param {integer | undefined} options.maxHistory maximum number of items to display in history. Set -1 if you don't want history, default maxItems 4280 * @param {function} options.getTitle a function that takes a feature and return the name to display in the index. 4281 * @param {function} options.autocomplete a function that take a search string and callback function to send an array 4282 * @param {function} options.onselect a function called when a search is selected 4283 * @param {boolean} options.centerOnSelect center map on search, default false 4284 * @param {number|boolean} options.zoomOnSelect center map on search and zoom to value if zoom < value, default false 4285 */ 4286ol.control.Search = function(options) { 4287 var self = this; 4288 if (!options) options = {}; 4289 if (options.typing == undefined) options.typing = 300; 4290 // Class name for history 4291 this._classname = options.className || 'search'; 4292 var classNames = (options.className||'')+ ' ol-search' 4293 + (options.target ? '' : ' ol-unselectable ol-control'); 4294 var element = ol.ext.element.create('DIV',{ 4295 className: classNames 4296 }) 4297 if (options.collapsed!==false) element.classList.add('ol-collapsed'); 4298 if (!options.target) { 4299 this.button = document.createElement('BUTTON'); 4300 this.button.setAttribute('type', 'button'); 4301 this.button.setAttribute('title', options.title || options.label || 'Search'); 4302 this.button.addEventListener('click', function() { 4303 element.classList.toggle('ol-collapsed'); 4304 if (!element.classList.contains('ol-collapsed')) { 4305 element.querySelector('input.search').focus(); 4306 var listElements = element.querySelectorAll('li'); 4307 for (var i = 0; i < listElements.length; i++) { 4308 listElements[i].classList.remove('select'); 4309 } 4310 // Display history 4311 if (!input.value) { 4312 self.drawList_(); 4313 } 4314 } 4315 }); 4316 element.appendChild(this.button); 4317 } 4318 // Input label 4319 if (options.inputLabel) { 4320 var label = document.createElement("LABEL"); 4321 label.innerText = options.inputLabel; 4322 element.appendChild(label); 4323 } 4324 // Search input 4325 var tout, cur=""; 4326 var input = this._input = document.createElement("INPUT"); 4327 input.setAttribute("type", "search"); 4328 input.setAttribute("class", "search"); 4329 input.setAttribute("autocomplete", "off"); 4330 input.setAttribute("placeholder", options.placeholder||"Search..."); 4331 input.addEventListener("change", function(e) { 4332 self.dispatchEvent({ type:"change:input", input:e, value:input.value }); 4333 }); 4334 var doSearch = function(e) { 4335 // console.log(e.type+" "+e.key)' 4336 var li = element.querySelector("ul.autocomplete li.select"); 4337 var val = input.value; 4338 // move up/down 4339 if (e.key=='ArrowDown' || e.key=='ArrowUp' || e.key=='Down' || e.key=='Up') { 4340 if (li) { 4341 li.classList.remove("select"); 4342 li = (/Down/.test(e.key)) ? li.nextElementSibling : li.previousElementSibling; 4343 if (li) li.classList.add("select"); 4344 } 4345 else element.querySelector("ul.autocomplete li").classList.add("select"); 4346 } 4347 // Clear input 4348 else if (e.type=='input' && !val) { 4349 setTimeout(function(){ 4350 self.drawList_(); 4351 }, 200); 4352 } 4353 // Select in the list 4354 else if (li && (e.type=="search" || e.key =="Enter")) { 4355 if (element.classList.contains("ol-control")) input.blur(); 4356 li.classList.remove("select"); 4357 cur = val; 4358 self._handleSelect(self._list[li.getAttribute("data-search")]); 4359 } 4360 // Search / autocomplete 4361 else if ( (e.type=="search" || e.key =='Enter') 4362 || (cur!=val && options.typing>=0)) { 4363 // current search 4364 cur = val; 4365 if (cur) { 4366 // prevent searching on each typing 4367 if (tout) clearTimeout(tout); 4368 var minLength = self.get("minLength"); 4369 tout = setTimeout(function() { 4370 if (cur.length >= minLength) { 4371 var s = self.autocomplete (cur, function(auto) { self.drawList_(auto); }); 4372 if (s) self.drawList_(s); 4373 } 4374 else self.drawList_(); 4375 }, options.typing); 4376 } 4377 else self.drawList_(); 4378 } 4379 // Clear list selection 4380 else { 4381 li = element.querySelector("ul.autocomplete li"); 4382 if (li) li.classList.remove('select'); 4383 } 4384 }; 4385 input.addEventListener("keyup", doSearch); 4386 input.addEventListener("search", doSearch); 4387 input.addEventListener("cut", doSearch); 4388 input.addEventListener("paste", doSearch); 4389 input.addEventListener("input", doSearch); 4390 if (!options.noCollapse) { 4391 input.addEventListener('blur', function() { 4392 setTimeout(function(){ 4393 if (input !== document.activeElement) { 4394 element.classList.add('ol-collapsed'); 4395 this.set('reverse', false); 4396 element.classList.remove('ol-revers'); 4397 } 4398 }.bind(this), 200); 4399 }.bind(this)); 4400 input.addEventListener('focus', function() { 4401 if (!this.get('reverse')) { 4402 element.classList.remove('ol-collapsed'); 4403 element.classList.remove('ol-revers'); 4404 } 4405 }.bind(this)); 4406 } 4407 element.appendChild(input); 4408 // Reverse geocode 4409 if (options.reverse) { 4410 var reverse = ol.ext.element.create('BUTTON', { 4411 type: 'button', 4412 class: 'ol-revers', 4413 title: options.reverseTitle || 'click on the map', 4414 click: function() { 4415 if (!this.get('reverse')) { 4416 this.set('reverse', !this.get('reverse')); 4417 input.focus(); 4418 element.classList.add('ol-revers'); 4419 } else { 4420 this.set('reverse', false); 4421 } 4422 }.bind(this) 4423 }); 4424 element.appendChild(reverse); 4425 } 4426 // Autocomplete list 4427 var ul = document.createElement('UL'); 4428 ul.classList.add('autocomplete'); 4429 element.appendChild(ul); 4430 ol.control.Control.call(this, { 4431 element: element, 4432 target: options.target 4433 }); 4434 if (typeof (options.getTitle)=='function') this.getTitle = options.getTitle; 4435 if (typeof (options.autocomplete)=='function') this.autocomplete = options.autocomplete; 4436 // Options 4437 this.set('copy', options.copy); 4438 this.set('minLength', options.minLength || 1); 4439 this.set('maxItems', options.maxItems || 10); 4440 this.set('maxHistory', options.maxHistory || options.maxItems || 10); 4441 // Select 4442 if (options.onselect) this.on('select', options.onselect); 4443 // Center on select 4444 if (options.centerOnSelect) this.on('select', function(e) { 4445 var map = this.getMap(); 4446 if (map) {
4447 map.getView().setCenter(e.coordinate); 4448 } 4449 }.bind(this)); 4450 // Zoom on select 4451 if (options.zoomOnSelect) this.on('select', function(e) { 4452 var map = this.getMap(); 4453 if (map) { 4454 map.getView().setCenter(e.coordinate); 4455 if (map.getView().getZoom() < options.zoomOnSelect) map.getView().setZoom(options.zoomOnSelect); 4456 } 4457 }.bind(this)); 4458 // History 4459 this.restoreHistory(); 4460 this.drawList_(); 4461}; 4462ol.ext.inherits(ol.control.Search, ol.control.Control); 4463/** 4464 * Remove the control from its current map and attach it to the new map. 4465 * Subclasses may set up event handlers to get notified about changes to 4466 * the map here. 4467 * @param {ol.Map} map Map. 4468 * @api stable 4469 */ 4470ol.control.Search.prototype.setMap = function (map) { 4471 if (this._listener) ol.Observable.unByKey(this._listener); 4472 this._listener = null; 4473 ol.control.Control.prototype.setMap.call(this, map); 4474 if (map) { 4475 this._listener = map.on('click', this._handleClick.bind(this)); 4476 } 4477}; 4478/** Collapse the search 4479 * @param {boolean} [b=true] 4480 * @api 4481 */ 4482ol.control.Search.prototype.collapse = function (b) { 4483 if (b===false) this.element.classList.remove('ol-collapsed') 4484 else this.element.classList.add('ol-collapsed') 4485}; 4486/** Get the input field 4487* @return {Element} 4488* @api 4489*/ 4490ol.control.Search.prototype.getInputField = function () { 4491 return this._input; 4492}; 4493/** Returns the text to be displayed in the menu 4494 * @param {any} f feature to be displayed 4495 * @return {string} the text to be displayed in the index, default f.name 4496 * @api 4497 */ 4498ol.control.Search.prototype.getTitle = function (f) { 4499 return f.name || "No title"; 4500}; 4501/** Returns title as text 4502 * @param {any} f feature to be displayed 4503 * @return {string} 4504 * @api 4505 */ 4506ol.control.Search.prototype._getTitleTxt = function (f) { 4507 return ol.ext.element.create('DIV', { 4508 html: this.getTitle(f) 4509 }).innerText; 4510}; 4511/** Force search to refresh 4512 */ 4513ol.control.Search.prototype.search = function () { 4514 var search = this.element.querySelector("input.search"); 4515 this._triggerCustomEvent('search', search); 4516}; 4517/** Reverse geocode 4518 * @param {Object} event 4519 * @param {ol.coordinate} event.coordinate 4520 * @private 4521 */ 4522ol.control.Search.prototype._handleClick = function (e) { 4523 if (this.get('reverse')) { 4524 document.activeElement.blur(); 4525 this.reverseGeocode(e.coordinate); 4526 } 4527}; 4528/** Reverse geocode 4529 * @param {ol.coordinate} coord 4530 * @param {function | undefined} cback a callback function, default trigger a select event 4531 * @api 4532 */ 4533ol.control.Search.prototype.reverseGeocode = function (/*coord, cback*/) { 4534 // this._handleSelect(f); 4535}; 4536/** Trigger custom event on elemebt 4537 * @param {*} eventName 4538 * @param {*} element 4539 * @private 4540 */ 4541ol.control.Search.prototype._triggerCustomEvent = function (eventName, element) { 4542 ol.ext.element.dispatchEvent(eventName, element); 4543}; 4544/** Set the input value in the form (for initialisation purpose) 4545* @param {string} value 4546* @param {boolean} search to start a search 4547* @api 4548*/ 4549ol.control.Search.prototype.setInput = function (value, search) { 4550 var input = this.element.querySelector("input.search"); 4551 input.value = value; 4552 if (search) this._triggerCustomEvent("keyup", input); 4553}; 4554/** A line has been clicked in the menu > dispatch event 4555 * @param {any} f the feature, as passed in the autocomplete 4556 * @param {boolean} reverse true if reverse geocode 4557 * @param {ol.coordinate} coord 4558 * @param {*} options options passed to the event 4559 * @api 4560 */ 4561ol.control.Search.prototype.select = function (f, reverse, coord, options) { 4562 var event = { type:"select", search:f, reverse: !!reverse, coordinate: coord }; 4563 if (options) { 4564 for (var i in options) { 4565 event[i] = options[i]; 4566 } 4567 } 4568 this.dispatchEvent(event); 4569}; 4570/** 4571 * Save history and select 4572 * @param {*} f 4573 * @param {boolean} reverse true if reverse geocode 4574 * @param {*} options options send in the event 4575 * @private 4576 */ 4577ol.control.Search.prototype._handleSelect = function (f, reverse, options) { 4578 if (!f) return; 4579 // Save input in history 4580 var hist = this.get('history'); 4581 // Prevent error on stringify 4582 var i; 4583 try { 4584 var fstr = JSON.stringify(f); 4585 for (i=hist.length-1; i>=0; i--) { 4586 if (!hist[i] || JSON.stringify(hist[i]) === fstr) { 4587 hist.splice(i,1); 4588 } 4589 } 4590 } catch (e) { 4591 for (i=hist.length-1; i>=0; i--) { 4592 if (hist[i] === f) { 4593 hist.splice(i,1); 4594 } 4595 } 4596 } 4597 hist.unshift(f); 4598 var size = Math.max(0, this.get('maxHistory')||10) || 0; 4599 while (hist.length > size) { 4600 hist.pop(); 4601 } 4602 this.saveHistory(); 4603 // Select feature 4604 this.select(f, reverse, null, options); 4605 if (reverse) { 4606 this.setInput(this._getTitleTxt(f)); 4607 this.drawList_(); 4608 setTimeout(function() { this.collapse(false); }.bind(this), 300); 4609 } 4610}; 4611/** Current history */ 4612ol.control.Search.prototype._history = {}; 4613/** Save history (in the localstorage) 4614 */ 4615ol.control.Search.prototype.saveHistory = function () { 4616 try { 4617 if (this.get('maxHistory')>=0) { 4618 localStorage["ol@search-"+this._classname] = JSON.stringify(this.get('history')); 4619 } else { 4620 localStorage.removeItem("ol@search-"+this._classname); 4621 } 4622 } catch(e) { console.warn('Failed to access localStorage...'); } 4623}; 4624/** Restore history (from the localstorage) 4625 */ 4626ol.control.Search.prototype.restoreHistory = function () { 4627 if (this._history[this._classname]) { 4628 this.set('history', this._history[this._classname]); 4629 } else { 4630 try { 4631 this._history[this._classname] = JSON.parse(localStorage["ol@search-"+this._classname]); 4632 this.set('history', this._history[this._classname]); 4633 } catch(e) { 4634 this.set('history', []); 4635 } 4636 } 4637}; 4638/** 4639 * Remove previous history 4640 */ 4641ol.control.Search.prototype.clearHistory = function () { 4642 this.set('history', []); 4643 this.saveHistory(); 4644 this.drawList_(); 4645}; 4646/** 4647 * Get history table 4648 */ 4649ol.control.Search.prototype.getHistory = function () { 4650 return this.get('history'); 4651}; 4652/** Autocomplete function 4653* @param {string} s search string 4654* @param {function} cback a callback function that takes an array to display in the autocomplete field (for asynchronous search) 4655* @return {Array|false} an array of search solutions or false if the array is send with the cback argument (asnchronous) 4656* @api 4657*/ 4658ol.control.Search.prototype.autocomplete = function (s, cback) { 4659 cback ([]); 4660 return false;
4661 // or just return []; 4662}; 4663/** Draw the list 4664* @param {Array} auto an array of search result 4665* @private 4666*/ 4667ol.control.Search.prototype.drawList_ = function (auto) { 4668 var self = this; 4669 var ul = this.element.querySelector("ul.autocomplete"); 4670 ul.innerHTML = ''; 4671 this._list = []; 4672 if (!auto) { 4673 var input = this.element.querySelector("input.search"); 4674 var value = input.value; 4675 if (!value) { 4676 auto = this.get('history'); 4677 } else { 4678 return; 4679 } 4680 ul.setAttribute('class', 'autocomplete history'); 4681 } else { 4682 ul.setAttribute('class', 'autocomplete'); 4683 } 4684 var li, max = Math.min (self.get("maxItems"),auto.length); 4685 for (var i=0; i<max; i++) { 4686 if (auto[i]) { 4687 if (!i || !self.equalFeatures(auto[i], auto[i-1])) { 4688 li = document.createElement("LI"); 4689 li.setAttribute("data-search", this._list.length); 4690 this._list.push(auto[i]); 4691 li.addEventListener("click", function(e) { 4692 self._handleSelect(self._list[e.currentTarget.getAttribute("data-search")]); 4693 }); 4694 var title = self.getTitle(auto[i]); 4695 if (title instanceof Element) li.appendChild(title); 4696 else li.innerHTML = title; 4697 ul.appendChild(li); 4698 } 4699 } 4700 } 4701 if (max && this.get("copy")) { 4702 li = document.createElement("LI"); 4703 li.classList.add("copy"); 4704 li.innerHTML = this.get("copy"); 4705 ul.appendChild(li); 4706 } 4707}; 4708/** Test if 2 features are equal 4709 * @param {any} f1 4710 * @param {any} f2 4711 * @return {boolean} 4712 */ 4713ol.control.Search.prototype.equalFeatures = function (/* f1, f2 */) { 4714 return false; 4715}; 4716 4717/* Copyright (c) 2017 Jean-Marc VIGLINO, 4718 released under the CeCILL-B license (French BSD license) 4719 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 4720*/ 4721/** 4722 * This is the base class for search controls that use a json service to search features. 4723 * You can use it for simple custom search or as base to new class. 4724 * 4725 * @constructor 4726 * @extends {ol.control.Search} 4727 * @fires select 4728 * @param {any} options extend ol.control.Search options 4729 * @param {string} options.className control class name 4730 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 4731 * @param {string | undefined} options.title Title to use for the search button tooltip, default "Search" 4732 * @param {string | undefined} options.reverseTitle Title to use for the reverse geocoding button tooltip, default "Click on the map..." 4733 * @param {string | undefined} options.placeholder placeholder, default "Search..." 4734 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 1000. 4735 * @param {integer | undefined} options.minLength minimum length to start searching, default 3 4736 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 4737 * @param {function | undefined} options.handleResponse Handle server response to pass the features array to the list 4738 * 4739 * @param {string|undefined} options.url Url of the search api 4740 * @param {string | undefined} options.authentication: basic authentication for the search API as btoa("login:pwd") 4741 */ 4742ol.control.SearchJSON = function(options) { 4743 options = options || {}; 4744 options.className = options.className || 'JSON'; 4745 delete options.autocomplete; 4746 options.minLength = options.minLength || 3; 4747 options.typing = options.typing || 800; 4748 ol.control.Search.call(this, options); 4749 // Handle Mix Content Warning 4750 // If the current connection is an https connection all other connections must be https either 4751 var url = options.url || ""; 4752 if (window.location.protocol === "https:") { 4753 var parser = document.createElement('a'); 4754 parser.href = url; 4755 parser.protocol = window.location.protocol; 4756 url = parser.href; 4757 } 4758 this.set('url', url); 4759 this._ajax = new ol.ext.Ajax({ dataType:'JSON', auth: options.authentication }); 4760 this._ajax.on('success', function (resp) { 4761 if (resp.status >= 200 && resp.status < 400) { 4762 if (typeof(this._callback) === 'function') this._callback(resp.response); 4763 } else { 4764 if (typeof(this._callback) === 'function') this._callback(false, 'error'); 4765 console.log('AJAX ERROR', arguments); 4766 } 4767 }.bind(this)); 4768 this._ajax.on('error', function() { 4769 if (typeof(this._callback) === 'function') this._callback(false, 'error'); 4770 console.log('AJAX ERROR', arguments); 4771 }.bind(this)); 4772 // Handle searchin 4773 this._ajax.on('loadstart', function() { 4774 this.element.classList.add('searching'); 4775 }.bind(this)); 4776 this._ajax.on('loadend', function() { 4777 this.element.classList.remove('searching'); 4778 }.bind(this)); 4779 // Overwrite handleResponse 4780 if (typeof(options.handleResponse)==='function') this.handleResponse = options.handleResponse; 4781}; 4782ol.ext.inherits(ol.control.SearchJSON, ol.control.Search); 4783/** Send ajax request 4784 * @param {string} url 4785 * @param {*} data 4786 * @param {function} cback a callback function that takes an array of {name, feature} to display in the autocomplete field 4787 */ 4788ol.control.SearchJSON.prototype.ajax = function (url, data, cback, options) { 4789 options = options || {}; 4790 this._callback = cback; 4791 this._ajax.set('dataType', options.dataType || 'JSON'); 4792 this._ajax.send(url, data, options); 4793}; 4794/** Autocomplete function (ajax request to the server) 4795 * @param {string} s search string 4796 * @param {function} cback a callback function that takes an array of {name, feature} to display in the autocomplete field 4797 */ 4798ol.control.SearchJSON.prototype.autocomplete = function (s, cback) { 4799 var data = this.requestData(s); 4800 var url = encodeURI(this.get('url')); 4801 this.ajax(url, data, function(resp) { 4802 if (typeof(cback) === 'function') cback(this.handleResponse(resp)); 4803 }); 4804}; 4805/** 4806 * @param {string} s the search string 4807 * @return {Object} request data (as key:value) 4808 * @api 4809 */ 4810ol.control.SearchJSON.prototype.requestData = function (s){ 4811 return { q: s }; 4812}; 4813/** 4814 * Handle server response to pass the features array to the display list 4815 * @param {any} response server response 4816 * @return {Array<any>} an array of feature 4817 * @api 4818 */ 4819ol.control.SearchJSON.prototype.handleResponse = function (response) { 4820 return response; 4821}; 4822 4823/* Copyright (c) 2017 Jean-Marc VIGLINO, 4824 released under the CeCILL-B license (French BSD license)
4825 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 4826*/ 4827/** 4828 * Search places using the photon API. 4829 * 4830 * @constructor 4831 * @extends {ol.control.SearchJSON} 4832 * @fires select 4833 * @param {Object=} Control options. 4834 * @param {string} options.className control class name 4835 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 4836 * @param {string | undefined} options.title Title to use for the search button tooltip, default "Search" 4837 * @param {string | undefined} options.reverseTitle Title to use for the reverse geocoding button tooltip, default "Click on the map..." 4838 * @param {string | undefined} options.placeholder placeholder, default "Search..." 4839 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 1000. 4840 * @param {integer | undefined} options.minLength minimum length to start searching, default 3 4841 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 4842 * @param {function | undefined} options.handleResponse Handle server response to pass the features array to the list 4843 * 4844 * @param {string|undefined} options.url Url to photon api, default "http://photon.komoot.de/api/" 4845 * @param {string|undefined} options.lang Force preferred language, default none 4846 * @param {boolean} options.position Search, with priority to geo position, default false 4847 * @param {function} options.getTitle a function that takes a feature and return the name to display in the index, default return street + name + contry 4848 */ 4849ol.control.SearchPhoton = function(options) { 4850 options = options || {}; 4851 options.className = options.className || 'photon'; 4852 options.url = options.url || 'https://photon.komoot.io/api/'; 4853 options.copy = options.copy || '<a href="http://www.openstreetmap.org/copyright" target="new">© OpenStreetMap contributors</a>'; 4854 ol.control.SearchJSON.call(this, options); 4855 this.set('lang', options.lang); 4856 this.set('position', options.position); 4857}; 4858ol.ext.inherits(ol.control.SearchPhoton, ol.control.SearchJSON); 4859/** Returns the text to be displayed in the menu 4860* @param {ol.Feature} f the feature 4861* @return {string} the text to be displayed in the index 4862* @api 4863*/ 4864ol.control.SearchPhoton.prototype.getTitle = function (f) { 4865 var p = f.properties; 4866 return (p.housenumber||"") 4867 + " "+(p.street || p.name || "") 4868 + "<i>" 4869 + " "+(p.postcode||"") 4870 + " "+(p.city||"") 4871 + " ("+p.country 4872 + ")</i>"; 4873}; 4874/** 4875 * @param {string} s the search string 4876 * @return {Object} request data (as key:value) 4877 * @api 4878 */ 4879ol.control.SearchPhoton.prototype.requestData = function (s) { 4880 var data = { 4881 q: s, 4882 lang: this.get('lang'), 4883 limit: this.get('maxItems') 4884 } 4885 // Handle position proirity 4886 if (this.get('position')) { 4887 var view = this.getMap().getView(); 4888 var pt = new ol.geom.Point(view.getCenter()); 4889 pt = (pt.transform (view.getProjection(), "EPSG:4326")).getCoordinates(); 4890 data.lon = pt[0]; 4891 data.lat = pt[1]; 4892 } 4893 return data; 4894}; 4895/** 4896 * Handle server response to pass the features array to the list 4897 * @param {any} response server response 4898 * @return {Array<any>} an array of feature 4899 */ 4900ol.control.SearchPhoton.prototype.handleResponse = function (response) { 4901 return response.features; 4902}; 4903/** Prevent same feature to be drawn twice: test equality 4904 * @param {} f1 First feature to compare 4905 * @param {} f2 Second feature to compare 4906 * @return {boolean} 4907 * @api 4908 */ 4909ol.control.SearchPhoton.prototype.equalFeatures = function (f1, f2) { 4910 return (this.getTitle(f1) === this.getTitle(f2) 4911 && f1.geometry.coordinates[0] === f2.geometry.coordinates[0] 4912 && f1.geometry.coordinates[1] === f2.geometry.coordinates[1]); 4913}; 4914/** A ligne has been clicked in the menu > dispatch event 4915* @param {any} f the feature, as passed in the autocomplete 4916* @api 4917*/ 4918ol.control.SearchPhoton.prototype.select = function (f) { 4919 var c = f.geometry.coordinates; 4920 // Add coordinate to the event 4921 try { 4922 c = ol.proj.transform (f.geometry.coordinates, 'EPSG:4326', this.getMap().getView().getProjection()); 4923 } catch(e) { /* ok */ } 4924 this.dispatchEvent({ type:"select", search:f, coordinate: c }); 4925}; 4926/** Get data for reverse geocode 4927 * @param {ol.coordinate} coord 4928 */ 4929ol.control.SearchPhoton.prototype.reverseData = function (coord) { 4930 var lonlat = ol.proj.transform (coord, this.getMap().getView().getProjection(), 'EPSG:4326'); 4931 return { lon: lonlat[0], lat: lonlat[1] }; 4932}; 4933/** Reverse geocode 4934 * @param {ol.coordinate} coord 4935 * @api 4936 */ 4937ol.control.SearchPhoton.prototype.reverseGeocode = function (coord, cback) { 4938 this.ajax( 4939 this.get('url').replace('/api/', '/reverse/').replace('/search/', '/reverse/'), 4940 this.reverseData(coord), 4941 function(resp) { 4942 if (resp.features) resp = resp.features; 4943 if (!(resp instanceof Array)) resp = [resp]; 4944 if (cback) { 4945 cback.call(this, resp); 4946 } else { 4947 this._handleSelect(resp[0], true); 4948 // this.setInput('', true); 4949 } 4950 }.bind(this) 4951 ); 4952}; 4953 4954/* Copyright (c) 2017 Jean-Marc VIGLINO, 4955 released under the CeCILL-B license (French BSD license)
4956 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 4957*/ 4958/** 4959 * Search places using the French National Base Address (BAN) API. 4960 * 4961 * @constructor 4962 * @extends {ol.control.SearchJSON} 4963 * @fires select 4964 * @param {any} options extend ol.control.SearchJSON options 4965 * @param {string} options.className control class name 4966 * @param {string | undefined} [options.apiKey] the service api key. 4967 * @param {string | undefined} options.authentication: basic authentication for the service API as btoa("login:pwd") 4968 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 4969 * @param {string | undefined} options.label Text label to use for the search button, default "search" 4970 * @param {boolean | undefined} options.reverse enable reverse geocoding, default false 4971 * @param {string | undefined} options.placeholder placeholder, default "Search..." 4972 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500. 4973 * @param {integer | undefined} options.minLength minimum length to start searching, default 3 4974 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 4975 * 4976 * @param {StreetAddress|PositionOfInterest|CadastralParcel|Commune} options.type type of search. Using Commune will return the INSEE code, default StreetAddress,PositionOfInterest 4977 * @see {@link https://geoservices.ign.fr/documentation/geoservices/geocodage.html} 4978 */ 4979ol.control.SearchGeoportail = function(options) { 4980 options = options || {}; 4981 options.className = options.className || 'IGNF'; 4982 options.typing = options.typing || 500; 4983 options.url = 'https://wxs.ign.fr/' + (options.apiKey || 'essentiels') + '/ols/apis/completion'; 4984 options.copy = '<a href="https://www.geoportail.gouv.fr/" target="new">© IGN-Géoportail</a>'; 4985 ol.control.SearchJSON.call(this, options); 4986 this.set('type', options.type || 'StreetAddress,PositionOfInterest'); 4987 this.set('timeout', options.timeout || 2000); 4988 // Authentication 4989 // this._auth = options.authentication; 4990}; 4991ol.ext.inherits(ol.control.SearchGeoportail, ol.control.SearchJSON); 4992/** Reverse geocode 4993 * @param {ol.coordinate} coord 4994 * @param {function|*} options callback function called when revers located or options passed to the select event 4995 * @api 4996 */ 4997ol.control.SearchGeoportail.prototype.reverseGeocode = function (coord, options) { 4998 var lonlat = ol.proj.transform(coord, this.getMap().getView().getProjection(), 'EPSG:4326'); 4999 this._handleSelect({ 5000 x: lonlat[0], 5001 y: lonlat[1], 5002 fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) 5003 }, true, options); 5004 // Search type 5005 var type = this.get('type')==='Commune' ? 'PositionOfInterest' : this.get('type') || 'StreetAddress'; 5006 if (/,/.test(type)) type = 'StreetAddress'; 5007 // request 5008 var request = '<?xml version="1.0" encoding="UTF-8"?>' 5009 +'<XLS xmlns:xls="http://www.opengis.net/xls" xmlns:gml="http://www.opengis.net/gml" xmlns="http://www.opengis.net/xls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="http://www.opengis.net/xls http://schemas.opengis.net/ols/1.2/olsAll.xsd">' 5010 +' <Request requestID="1" version="1.2" methodName="ReverseGeocodeRequest" maximumResponses="1" >' 5011 +' <ReverseGeocodeRequest>' 5012 +' <ReverseGeocodePreference>'+type+'</ReverseGeocodePreference>' 5013 +' <Position>' 5014 +' <gml:Point><gml:pos>'+lonlat[1]+' '+lonlat[0]+'</gml:pos></gml:Point>' 5015 +' </Position>' 5016 +' </ReverseGeocodeRequest>' 5017 +' </Request>' 5018 +'</XLS>'; 5019 this.ajax (this.get('url').replace('ols/apis/completion','geoportail/ols'), 5020 { xls: request }, 5021 function(xml) { 5022 var f = {}; 5023 if (!xml) { 5024 f = { x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) } 5025 } else { 5026 xml = xml.replace(/\n|\r/g,''); 5027 var p = (xml.replace(/.*<gml:pos>(.*)<\/gml:pos>.*/, "$1")).split(' '); 5028 if (!Number(p[1]) && !Number(p[0])) { 5029 f = { x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) } 5030 } else { 5031 f.x = lonlat[0]; 5032 f.y = lonlat[1]; 5033 f.city = (xml.replace(/.*<Place type="Municipality">([^<]*)<\/Place>.*/, "$1")); 5034 f.insee = (xml.replace(/.*<Place type="INSEE">([^<]*)<\/Place>.*/, "$1")); 5035 f.zipcode = (xml.replace(/.*<PostalCode>([^<]*)<\/PostalCode>.*/, "$1")); 5036 if (/<Street>/.test(xml)) { 5037 f.kind = ''; 5038 f.country = 'StreetAddress'; 5039 f.street = (xml.replace(/.*<Street>([^<]*)<\/Street>.*/, "$1")); 5040 var number = (xml.replace(/.*<Building number="([^"]*).*/, "$1")); 5041 f.fulltext = number+' '+f.street+', '+f.zipcode+' '+f.city; 5042 } else { 5043 f.kind = (xml.replace(/.*<Place type="Nature">([^<]*)<\/Place>.*/, "$1")); 5044 f.country = 'PositionOfInterest'; 5045 f.street = ''; 5046 f.fulltext = f.zipcode+' '+f.city; 5047 } 5048 } 5049 } 5050 if (typeof(options)==='function') { 5051 options.call(this, [f]); 5052 } else { 5053 this.getHistory().shift(); 5054 this._handleSelect(f, true, options); 5055 // this.setInput('', true); 5056 // this.drawList_(); 5057 } 5058 }.bind(this), { 5059 timeout: this.get('timeout'), 5060 dataType: 'XML' 5061 } 5062 ); 5063}; 5064/** Returns the text to be displayed in the menu 5065 * @param {ol.Feature} f the feature 5066 * @return {string} the text to be displayed in the index 5067 * @api 5068 */ 5069ol.control.SearchGeoportail.prototype.getTitle = function (f) { 5070 return (f.fulltext); 5071}; 5072/** 5073 * @param {string} s the search string 5074 * @return {Object} request data (as key:value) 5075 * @api 5076 */ 5077ol.control.SearchGeoportail.prototype.requestData = function (s) { 5078 return { 5079 text: s, 5080 type: this.get('type')==='Commune' ? 'PositionOfInterest' : this.get('type') || 'StreetAddress,PositionOfInterest', 5081 maximumResponses: this.get('maxItems') 5082 }; 5083}; 5084/** 5085 * Handle server response to pass the features array to the display list 5086 * @param {any} response server response 5087 * @return {Array<any>} an array of feature 5088 * @api 5089 */ 5090ol.control.SearchGeoportail.prototype.handleResponse = function (response) { 5091 var features = response.results; 5092 if (this.get('type') === 'Commune') { 5093 for (var i=features.length-1; i>=0; i--) { 5094 if ( features[i].kind 5095 && (features[i].classification>5 || features[i].kind=="Département") ) { 5096 features.splice(i,1); 5097 } 5098 } 5099 } 5100 return features; 5101}; 5102/** A ligne has been clicked in the menu > dispatch event 5103 * @param {any} f the feature, as passed in the autocomplete 5104 * @param {boolean} reverse true if reverse geocode 5105 * @param {ol.coordinate} coord 5106 * @param {*} options options passed to the event 5107 * @api 5108 */ 5109ol.control.SearchGeoportail.prototype.select = function (f, reverse, coord, options){ 5110 if (f.x || f.y) { 5111 var c = [Number(f.x), Number(f.y)]; 5112 // Add coordinate to the event 5113 try { 5114 c = ol.proj.transform (c, 'EPSG:4326', this.getMap().getView().getProjection()); 5115 } catch(e) { /* ok */} 5116 // Get insee commune ? 5117 if (this.get('type')==='Commune') { 5118 this.searchCommune(f, function () { 5119 ol.control.Search.prototype.select.call(this, f, reverse, c, options); 5120 //this.dispatchEvent({ type:"select", search:f, coordinate: c, revers: reverse, options: options }); 5121 }); 5122 } else { 5123 ol.control.Search.prototype.select.call(this, f, reverse, c, options); 5124 //this.dispatchEvent({ type:"select", search:f, coordinate: c, revers: reverse, options: options }); 5125 } 5126 } else { 5127 this.searchCommune(f); 5128 } 5129}; 5130/** Search if no position and get the INSEE code 5131 * @param {string} s le nom de la commune 5132 */ 5133ol.control.SearchGeoportail.prototype.searchCommune = function (f, cback) { 5134 var request = '<?xml version="1.0" encoding="UTF-8"?>' 5135 +'<XLS xmlns:xls="http://www.opengis.net/xls" xmlns:gml="http://www.opengis.net/gml" xmlns="http://www.opengis.net/xls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="http://www.opengis.net/xls http://schemas.opengis.net/ols/1.2/olsAll.xsd">' 5136 +'<RequestHeader/>' 5137 +'<Request requestID="1" version="1.2" methodName="LocationUtilityService">' 5138 +'<GeocodeRequest returnFreeForm="false">' 5139 +'<Address countryCode="PositionOfInterest">' 5140 +'<freeFormAddress>'+f.zipcode+' '+f.city+'+</freeFormAddress>' 5141 +'</Address>' 5142 +'</GeocodeRequest>' 5143 +'</Request>' 5144 +'</XLS>' 5145 // Search 5146 this.ajax (this.get('url').replace('ols/apis/completion','geoportail/ols'), 5147 { 'xls': request }, 5148 function(xml) { 5149 if (xml) { 5150 // XML to JSON 5151 var parser = new DOMParser(); 5152 var xmlDoc = parser.parseFromString(xml,"text/xml"); 5153 var com = xmlDoc.getElementsByTagName('GeocodedAddress')[0]; 5154 var coord = com.getElementsByTagName('gml:Point')[0].textContent.trim().split(' '); 5155 f.x = Number(coord[1]); 5156 f.y = Number(coord[0]); 5157 var place = com.getElementsByTagName('Place'); 5158 for (var i=0; i<place.length; i++) { 5159 switch (place[i].attributes.type.value) { 5160 case 'Nature': 5161 f.kind = place[i].textContent; 5162 break; 5163 case 'INSEE': 5164 f.insee = place[i].textContent; 5165 break; 5166 } 5167 } 5168 if (f.x || f.y) { 5169 if (cback) cback.call(this, [f]); 5170 else this._handleSelect(f); 5171 } 5172 } 5173 }.bind(this), 5174 { dataType: 'XML' } 5175 ); 5176}; 5177 5178/* Copyright (c) 2015 Jean-Marc VIGLINO, 5179 released under the CeCILL-B license (French BSD license)
5180 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 5181*/ 5182// ol < 6 compatibility VectorImage is not defined 5183// 5184/** Layer Switcher Control. 5185 * @fires select 5186 * @fires drawlist 5187 * @fires toggle 5188 * @fires reorder-start 5189 * @fires reorder-end 5190 * @fires layer:visible 5191 * @fires layer:opacity 5192 * 5193 * @constructor 5194 * @extends {ol.control.Control} 5195 * @param {Object=} options 5196 * @param {boolean} options.selection enable layer selection when click on the title 5197 * @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 5198 * @param {boolean} options.show_progress show a progress bar on tile layers, default false 5199 * @param {boolean} options.mouseover show the panel on mouseover, default false 5200 * @param {boolean} options.reordering allow layer reordering, default true 5201 * @param {boolean} options.trash add a trash button to delete the layer, default false 5202 * @param {function} options.oninfo callback on click on info button, if none no info button is shown DEPRECATED: use on(info) instead 5203 * @param {boolean} options.extent add an extent button to zoom to the extent of the layer 5204 * @param {function} options.onextent callback when click on extent, default fits view to extent 5205 * @param {number} options.drawDelay delay in ms to redraw the layer (usefull to prevent flickering when manipulating the layers) 5206 * @param {boolean} options.collapsed collapse the layerswitcher at beginning, default true 5207 * @param {ol.layer.Group} options.layerGroup a layer group to display in the switcher, default display all layers of the map 5208 * @param {boolean} options.noScroll prevent handle scrolling, default false 5209 * @param {function} options.onchangeCheck optional callback on click on checkbox, you can call this method for doing operations after check/uncheck a layer 5210 * 5211 * Layers attributes that control the switcher 5212 * - allwaysOnTop {boolean} true to force layer stay on top of the others while reordering, default false 5213 * - displayInLayerSwitcher {boolean} display the layer in switcher, default true 5214 * - noSwitcherDelete {boolean} to prevent layer deletion (w. trash option = true), default false 5215 */ 5216ol.control.LayerSwitcher = function(options) { 5217 options = options || {}; 5218 var self = this; 5219 this.dcount = 0; 5220 this.show_progress = options.show_progress; 5221 this.oninfo = (typeof (options.oninfo) == "function" ? options.oninfo: null); 5222 this.onextent = (typeof (options.onextent) == "function" ? options.onextent: null); 5223 this.hasextent = options.extent || options.onextent; 5224 this.hastrash = options.trash; 5225 this.reordering = (options.reordering!==false); 5226 this._layers = []; 5227 this._layerGroup = (options.layerGroup && options.layerGroup.getLayers) ? options.layerGroup : null; 5228 this.onchangeCheck = (typeof (options.onchangeCheck) == "function" ? options.onchangeCheck : null); 5229 // displayInLayerSwitcher 5230 if (typeof(options.displayInLayerSwitcher) === 'function') { 5231 this.displayInLayerSwitcher = options.displayInLayerSwitcher; 5232 } 5233 var element; 5234 if (options.target) { 5235 element = ol.ext.element.create('DIV', { 5236 className: options.switcherClass || "ol-layerswitcher" 5237 }); 5238 } else { 5239 element = ol.ext.element.create('DIV', { 5240 className: (options.switcherClass || "ol-layerswitcher") +' ol-unselectable ol-control' 5241 }); 5242 if (options.collapsed !== false) element.classList.add('ol-collapsed'); 5243 else element.classList.add('ol-forceopen'); 5244 this.button = ol.ext.element.create('BUTTON', { 5245 type: 'button', 5246 parent: element 5247 }); 5248 this.button.addEventListener('touchstart', function(e){ 5249 element.classList.toggle('ol-forceopen'); 5250 element.classList.add('ol-collapsed'); 5251 self.dispatchEvent({ type: 'toggle', collapsed: element.classList.contains('ol-collapsed') }); 5252 e.preventDefault(); 5253 self.overflow(); 5254 }); 5255 this.button.addEventListener('click', function() { 5256 element.classList.toggle('ol-forceopen'); 5257 element.classList.add('ol-collapsed');
5258 self.dispatchEvent({ type: 'toggle', collapsed: !element.classList.contains('ol-forceopen') }); 5259 self.overflow(); 5260 }); 5261 if (options.mouseover) { 5262 element.addEventListener ('mouseleave', function(){ 5263 element.classList.add("ol-collapsed"); 5264 self.dispatchEvent({ type: 'toggle', collapsed: true }); 5265 }); 5266 element.addEventListener ('mouseover', function(){ 5267 element.classList.remove("ol-collapsed"); 5268 self.dispatchEvent({ type: 'toggle', collapsed: false }); 5269 }); 5270 } 5271 if (options.minibar) options.noScroll = true; 5272 if (!options.noScroll) { 5273 this.topv = ol.ext.element.create('DIV', { 5274 className: 'ol-switchertopdiv', 5275 parent: element, 5276 click: function() { 5277 self.overflow("+50%"); 5278 } 5279 }); 5280 this.botv = ol.ext.element.create('DIV', { 5281 className: 'ol-switcherbottomdiv', 5282 parent: element, 5283 click: function() { 5284 self.overflow("-50%"); 5285 } 5286 }); 5287 } 5288 this._noScroll = options.noScroll; 5289 } 5290 this.panel_ = ol.ext.element.create ('UL', { 5291 className: 'panel', 5292 }); 5293 this.panelContainer_ = ol.ext.element.create ('DIV', { 5294 className: 'panel-container', 5295 html: this.panel_, 5296 parent: element 5297 }); 5298 // Handle mousewheel 5299 if (!options.target && !options.noScroll) { 5300 ol.ext.element.addListener (this.panel_, 'mousewheel DOMMouseScroll onmousewheel', function(e) { 5301 if (self.overflow(Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))))) { 5302 e.stopPropagation(); 5303 e.preventDefault(); 5304 } 5305 }); 5306 } 5307 this.header_ = ol.ext.element.create('LI', { 5308 className: 'ol-header', 5309 parent: this.panel_ 5310 }); 5311 ol.control.Control.call (this, { 5312 element: element, 5313 target: options.target 5314 }); 5315 this.set('drawDelay',options.drawDelay||0); 5316 this.set('selection', options.selection); 5317 if (options.minibar) { 5318 // Wait init complete (for child classes) 5319 setTimeout(function() { 5320 var mbar = ol.ext.element.scrollDiv(this.panelContainer_, { 5321 mousewheel: true, 5322 vertical: true, 5323 minibar: true 5324 }); 5325 this.on(['drawlist', 'toggle'], function() { 5326 mbar.refresh(); 5327 }) 5328 }.bind(this)); 5329 } 5330}; 5331ol.ext.inherits(ol.control.LayerSwitcher, ol.control.Control); 5332/** List of tips for internationalization purposes 5333*/ 5334ol.control.LayerSwitcher.prototype.tip = { 5335 up: "up/down", 5336 down: "down", 5337 info: "informations...", 5338 extent: "zoom to extent", 5339 trash: "remove layer", 5340 plus: "expand/shrink" 5341}; 5342/** Test if a layer should be displayed in the switcher 5343 * @param {ol.layer} layer 5344 * @return {boolean} true if the layer is displayed 5345 */ 5346ol.control.LayerSwitcher.prototype.displayInLayerSwitcher = function(layer) { 5347 return (layer.get('displayInLayerSwitcher')!==false); 5348}; 5349/** 5350 * Set the map instance the control is associated with. 5351 * @param {_ol_Map_} map The map instance. 5352 */ 5353ol.control.LayerSwitcher.prototype.setMap = function(map) { 5354 ol.control.Control.prototype.setMap.call(this, map); 5355 this.drawPanel(); 5356 if (this._listener) { 5357 for (var i in this._listener) ol.Observable.unByKey(this._listener[i]); 5358 } 5359 this._listener = null; 5360 // Get change (new layer added or removed) 5361 if (map) { 5362 this._listener = { 5363 moveend: map.on('moveend', this.viewChange.bind(this)), 5364 size: map.on('change:size', this.overflow.bind(this)) 5365 } 5366 // Listen to a layer group 5367 if (this._layerGroup) { 5368 this._listener.change = this._layerGroup.getLayers().on('change:length', this.drawPanel.bind(this)); 5369 } else { 5370 //Listen to all layers 5371 this._listener.change = map.getLayerGroup().getLayers().on('change:length', this.drawPanel.bind(this)); 5372 } 5373 } 5374}; 5375/** Show control 5376 */ 5377ol.control.LayerSwitcher.prototype.show = function() { 5378 this.element.classList.add('ol-forceopen'); 5379 this.overflow(); 5380 self.dispatchEvent({ type: 'toggle', collapsed: false }); 5381}; 5382/** Hide control 5383 */ 5384ol.control.LayerSwitcher.prototype.hide = function() { 5385 this.element.classList.remove('ol-forceopen'); 5386 this.overflow(); 5387 self.dispatchEvent({ type: 'toggle', collapsed: true }); 5388}; 5389/** Toggle control 5390 */ 5391ol.control.LayerSwitcher.prototype.toggle = function() { 5392 this.element.classList.toggle("ol-forceopen"); 5393 this.overflow(); 5394}; 5395/** Is control open 5396 * @return {boolean} 5397 */ 5398ol.control.LayerSwitcher.prototype.isOpen = function() { 5399 return this.element.classList.contains("ol-forceopen"); 5400}; 5401/** Add a custom header 5402 * @param {Element|string} html content html 5403 */ 5404ol.control.LayerSwitcher.prototype.setHeader = function(html) { 5405 ol.ext.element.setHTML(this.header_, html); 5406}; 5407/** Calculate overflow and add scrolls 5408 * @param {Number} dir scroll direction -1|0|1|'+50%'|'-50%' 5409 * @private 5410 */ 5411ol.control.LayerSwitcher.prototype.overflow = function(dir) { 5412 if (this.button && !this._noScroll) { 5413 // Nothing to show 5414 if (ol.ext.element.hidden(this.panel_)) { 5415 ol.ext.element.setStyle(this.element, { height: 'auto' }); 5416 return; 5417 } 5418 // Calculate offset 5419 var h = ol.ext.element.outerHeight(this.element); 5420 var hp = ol.ext.element.outerHeight(this.panel_); 5421 var dh = this.button.offsetTop + ol.ext.element.outerHeight(this.button); 5422 //var dh = this.button.position().top + this.button.outerHeight(true); 5423 var top = this.panel_.offsetTop - dh; 5424 if (hp > h-dh) { 5425 // Bug IE: need to have an height defined 5426 ol.ext.element.setStyle(this.element, { height: '100%' }); 5427 var li = this.panel_.querySelectorAll('li.visible .li-content')[0]; 5428 var lh = li ? 2 * ol.ext.element.getStyle(li, 'height') : 0; 5429 switch (dir) { 5430 case 1: top += lh; break; 5431 case -1: top -= lh; break; 5432 case "+50%": top += Math.round(h/2); break; 5433 case "-50%": top -= Math.round(h/2); break; 5434 default: break; 5435 } 5436 // Scroll div 5437 if (top+hp <= h-3*dh/2) { 5438 top = h-3*dh/2-hp; 5439 ol.ext.element.hide(this.botv); 5440 } else { 5441 ol.ext.element.show(this.botv); 5442 } 5443 if (top >= 0) { 5444 top = 0; 5445 ol.ext.element.hide(this.topv); 5446 } else { 5447 ol.ext.element.show(this.topv); 5448 } 5449 // Scroll ? 5450 ol.ext.element.setStyle(this.panel_, { top: top+"px" }); 5451 return true; 5452 } else { 5453 ol.ext.element.setStyle(this.element, { height: "auto" }); 5454 ol.ext.element.setStyle(this.panel_, { top: 0 }); 5455 ol.ext.element.hide(this.botv); 5456 ol.ext.element.hide(this.topv); 5457 return false;
5458 } 5459 } 5460 else return false; 5461}; 5462/** Set the layer associated with a li 5463 * @param {Element} li 5464 * @param {ol.layer} layer 5465 * @private 5466 */ 5467ol.control.LayerSwitcher.prototype._setLayerForLI = function(li, layer) { 5468 var listeners = []; 5469 if (layer.getLayers) { 5470 listeners.push(layer.getLayers().on('change:length', this.drawPanel.bind(this))); 5471 } 5472 if (li) { 5473 // Handle opacity change 5474 listeners.push(layer.on('change:opacity', (function() { 5475 this.setLayerOpacity(layer, li); 5476 }).bind(this))); 5477 // Handle visibility chage 5478 listeners.push(layer.on('change:visible', (function() { 5479 this.setLayerVisibility(layer, li); 5480 }).bind(this))); 5481 } 5482 // Other properties 5483 listeners.push(layer.on('propertychange', (function(e) { 5484 if (e.key === 'displayInLayerSwitcher' 5485 || e.key === 'openInLayerSwitcher') { 5486 this.drawPanel(e); 5487 } 5488 }).bind(this))); 5489 this._layers.push({ li:li, layer:layer, listeners: listeners }); 5490}; 5491/** Set opacity for a layer 5492 * @param {ol.layer.Layer} layer 5493 * @param {Element} li the list element 5494 * @private 5495 */ 5496ol.control.LayerSwitcher.prototype.setLayerOpacity = function(layer, li) { 5497 var i = li.querySelector('.layerswitcher-opacity-cursor') 5498 if (i) i.style.left = (layer.getOpacity()*100)+"%" 5499 this.dispatchEvent({ type: 'layer:opacity', layer: layer }); 5500}; 5501/** Set visibility for a layer 5502 * @param {ol.layer.Layer} layer 5503 * @param {Element} li the list element 5504 * @api 5505 */ 5506ol.control.LayerSwitcher.prototype.setLayerVisibility = function(layer, li) { 5507 var i = li.querySelector('.ol-visibility'); 5508 if (i) i.checked = layer.getVisible(); 5509 if (layer.getVisible()) li.classList.add('ol-visible'); 5510 else li.classList.remove('ol-visible'); 5511 this.dispatchEvent({ type: 'layer:visible', layer: layer }); 5512}; 5513/** Clear layers associated with li 5514 * @private 5515 */ 5516ol.control.LayerSwitcher.prototype._clearLayerForLI = function() { 5517 this._layers.forEach(function (li) { 5518 li.listeners.forEach(function(l) { 5519 ol.Observable.unByKey(l); 5520 }); 5521 }) 5522 this._layers = []; 5523}; 5524/** Get the layer associated with a li 5525 * @param {Element} li 5526 * @return {ol.layer} 5527 * @private 5528 */ 5529ol.control.LayerSwitcher.prototype._getLayerForLI = function(li) { 5530 for (var i=0, l; l=this._layers[i]; i++) { 5531 if (l.li===li) return l.layer; 5532 } 5533 return null; 5534}; 5535/** 5536 * On view change hide layer depending on resolution / extent 5537 * @private 5538 */ 5539ol.control.LayerSwitcher.prototype.viewChange = function() { 5540 this.panel_.querySelectorAll('li').forEach( function(li) { 5541 var l = this._getLayerForLI(li); 5542 if (l) { 5543 if (this.testLayerVisibility(l)) li.classList.remove('ol-layer-hidden'); 5544 else li.classList.add('ol-layer-hidden'); 5545 } 5546 }.bind(this)); 5547}; 5548/** Get control panel 5549 * @api 5550 */ 5551ol.control.LayerSwitcher.prototype.getPanel = function() { 5552 return this.panelContainer_; 5553}; 5554/** Draw the panel control (prevent multiple draw due to layers manipulation on the map with a delay function) 5555 * @api 5556 */ 5557ol.control.LayerSwitcher.prototype.drawPanel = function() { 5558 if (!this.getMap()) return; 5559 var self = this; 5560 // Multiple event simultaneously / draw once => put drawing in the event queue 5561 this.dcount++; 5562 setTimeout (function(){ self.drawPanel_(); }, this.get('drawDelay') || 0); 5563}; 5564/** Delayed draw panel control 5565 * @private 5566 */ 5567ol.control.LayerSwitcher.prototype.drawPanel_ = function() { 5568 if (--this.dcount || this.dragging_) return; 5569 var scrollTop = this.panelContainer_.scrollTop; 5570 // Remove existing layers 5571 this._clearLayerForLI(); 5572 this.panel_.querySelectorAll('li').forEach (function(li) { 5573 if (!li.classList.contains('ol-header')) li.remove(); 5574 }.bind(this)); 5575 // Draw list 5576 if (this._layerGroup) this.drawList (this.panel_, this._layerGroup.getLayers()); 5577 else if (this.getMap()) this.drawList (this.panel_, this.getMap().getLayers()); 5578 // Reset scrolltop 5579 this.panelContainer_.scrollTop = scrollTop; 5580}; 5581/** Change layer visibility according to the baselayer option 5582 * @param {ol.layer} 5583 * @param {Array<ol.layer>} related layers 5584 * @private 5585 */ 5586ol.control.LayerSwitcher.prototype.switchLayerVisibility = function(l, layers) { 5587 if (!l.get('baseLayer')) { 5588 l.setVisible(!l.getVisible()); 5589 } else { 5590 if (!l.getVisible()) l.setVisible(true);
5591 layers.forEach(function(li) { 5592 if (l!==li && li.get('baseLayer') && li.getVisible()) li.setVisible(false); 5593 }); 5594 } 5595}; 5596/** Check if layer is on the map (depending on resolution / zoom and extent) 5597 * @param {ol.layer} 5598 * @return {boolean} 5599 * @private 5600 */ 5601ol.control.LayerSwitcher.prototype.testLayerVisibility = function(layer) { 5602 if (!this.getMap()) return true; 5603 var res = this.getMap().getView().getResolution(); 5604 var zoom = this.getMap().getView().getZoom(); 5605 // Calculate visibility on resolution or zoom 5606 if (layer.getMaxResolution()<=res || layer.getMinResolution()>=res) { 5607 return false; 5608 } else if (layer.getMinZoom && (layer.getMinZoom()>=zoom || layer.getMaxZoom()<zoom)) { 5609 return false; 5610 } else { 5611 // Check extent 5612 var ex0 = layer.getExtent(); 5613 if (ex0) { 5614 var ex = this.getMap().getView().calculateExtent(this.getMap().getSize()); 5615 return ol.extent.intersects(ex, ex0); 5616 } 5617 return true; 5618 } 5619}; 5620/** Start ordering the list 5621* @param {event} e drag event 5622* @private 5623*/ 5624ol.control.LayerSwitcher.prototype.dragOrdering_ = function(e) { 5625 e.stopPropagation(); 5626 e.preventDefault(); 5627 // Get params 5628 var self = this; 5629 var elt = e.currentTarget.parentNode.parentNode; 5630 var start = true; 5631 var panel = this.panel_; 5632 var pageY; 5633 var pageY0 = e.pageY 5634 || (e.touches && e.touches.length && e.touches[0].pageY) 5635 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY); 5636 var target, dragElt; 5637 var layer, group; 5638 elt.parentNode.classList.add('drag'); 5639 // Stop ordering 5640 function stop() { 5641 if (target) { 5642 // Get drag on parent 5643 var drop = layer; 5644 var isSelected = self.getSelection() === drop; 5645 if (drop && target) { 5646 var collection ; 5647 if (group) collection = group.getLayers(); 5648 else collection = self._layerGroup ? self._layerGroup.getLayers() : self.getMap().getLayers(); 5649 var layers = collection.getArray(); 5650 // Switch layers 5651 for (var i=0; i<layers.length; i++) { 5652 if (layers[i]==drop) { 5653 collection.removeAt (i); 5654 break; 5655 } 5656 } 5657 for (var j=0; j<layers.length; j++) { 5658 if (layers[j] === target) { 5659 if (i>j) collection.insertAt (j,drop); 5660 else collection.insertAt (j+1,drop); 5661 break; 5662 } 5663 } 5664 } 5665 if (isSelected) self.selectLayer(drop); 5666 self.dispatchEvent({ type: "reorder-end", layer: drop, group: group }); 5667 } 5668 elt.parentNode.querySelectorAll('li').forEach(function(li){ 5669 li.classList.remove('dropover'); 5670 li.classList.remove('dropover-after'); 5671 li.classList.remove('dropover-before'); 5672 }); 5673 elt.classList.remove("drag"); 5674 elt.parentNode.classList.remove("drag"); 5675 self.element.classList.remove('drag'); 5676 if (dragElt) dragElt.remove(); 5677 ol.ext.element.removeListener(document, 'mousemove touchmove', move); 5678 ol.ext.element.removeListener(document, 'mouseup touchend touchcancel', stop); 5679 } 5680 // Ordering 5681 function move(e) { 5682 // First drag (more than 2 px) => show drag element (ghost) 5683 pageY = e.pageY 5684 || (e.touches && e.touches.length && e.touches[0].pageY) 5685 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY); 5686 if (start && Math.abs(pageY0 - pageY) > 2) { 5687 start = false;
5688 elt.classList.add("drag"); 5689 layer = self._getLayerForLI(elt); 5690 target = false; 5691 group = self._getLayerForLI(elt.parentNode.parentNode); 5692 // Ghost div 5693 dragElt = ol.ext.element.create('LI', { 5694 className: 'ol-dragover', 5695 html: elt.innerHTML, 5696 style: { 5697 position: "absolute", 5698 "z-index": 10000, 5699 left: elt.offsetLeft, 5700 opacity: 0.5, 5701 width: ol.ext.element.outerWidth(elt), 5702 height: ol.ext.element.getStyle(elt,'height'), 5703 }, 5704 parent: panel 5705 }); 5706 self.element.classList.add('drag'); 5707 self.dispatchEvent({ type: "reorder-start", layer: layer, group: group }); 5708 } 5709 // Start a new drag sequence 5710 if (!start) { 5711 e.preventDefault(); 5712 e.stopPropagation(); 5713 // Ghost div 5714 ol.ext.element.setStyle(dragElt, { top: pageY - ol.ext.element.offsetRect(panel).top + panel.scrollTop +5 }); 5715 var li; 5716 if (!e.touches) { 5717 li = e.target; 5718 } else { 5719 li = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY); 5720 } 5721 if (li.classList.contains("ol-switcherbottomdiv")) { 5722 self.overflow(-1); 5723 } else if (li.classList.contains("ol-switchertopdiv")) { 5724 self.overflow(1); 5725 } 5726 // Get associated li 5727 while (li && li.tagName!=='LI') { 5728 li = li.parentNode; 5729 } 5730 if (!li || !li.classList.contains('dropover')) { 5731 elt.parentNode.querySelectorAll('li').forEach(function(li) { 5732 li.classList.remove('dropover'); 5733 li.classList.remove('dropover-after'); 5734 li.classList.remove('dropover-before'); 5735 }); 5736 } 5737 if (li && li.parentNode.classList.contains('drag') && li !== elt) { 5738 target = self._getLayerForLI(li); 5739 // Don't mix layer level 5740 if (target && !target.get('allwaysOnTop') == !layer.get('allwaysOnTop')) { 5741 li.classList.add("dropover"); 5742 li.classList.add((elt.offsetTop < li.offsetTop) ? 'dropover-after' : 'dropover-before'); 5743 } else { 5744 target = false;
5745 } 5746 ol.ext.element.show(dragElt); 5747 } else { 5748 target = false; 5749 if (li===elt) ol.ext.element.hide(dragElt); 5750 else ol.ext.element.show(dragElt); 5751 } 5752 if (!target) dragElt.classList.add("forbidden"); 5753 else dragElt.classList.remove("forbidden"); 5754 } 5755 } 5756 // Start ordering 5757 ol.ext.element.addListener(document, 'mousemove touchmove', move); 5758 ol.ext.element.addListener(document, 'mouseup touchend touchcancel', stop); 5759}; 5760/** Change opacity on drag 5761* @param {event} e drag event 5762* @private 5763*/ 5764ol.control.LayerSwitcher.prototype.dragOpacity_ = function(e) { 5765 e.stopPropagation(); 5766 e.preventDefault(); 5767 var self = this 5768 // Register start params 5769 var elt = e.target; 5770 var layer = this._getLayerForLI(elt.parentNode.parentNode.parentNode); 5771 if (!layer) return; 5772 var x = e.pageX 5773 || (e.touches && e.touches.length && e.touches[0].pageX) 5774 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); 5775 var start = ol.ext.element.getStyle (elt, 'left') - x; 5776 self.dragging_ = true; 5777 // stop dragging 5778 function stop() { 5779 ol.ext.element.removeListener(document, "mouseup touchend touchcancel", stop); 5780 ol.ext.element.removeListener(document, "mousemove touchmove", move); 5781 self.dragging_ = false; 5782 } 5783 // On draggin 5784 function move(e) { 5785 var x = e.pageX 5786 || (e.touches && e.touches.length && e.touches[0].pageX) 5787 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); 5788 var delta = (start + x) / ol.ext.element.getStyle(elt.parentNode, 'width'); 5789 var opacity = Math.max (0, Math.min(1, delta)); 5790 ol.ext.element.setStyle (elt, { left: (opacity*100) + "%" }); 5791 elt.parentNode.nextElementSibling.innerHTML = Math.round(opacity*100); 5792 layer.setOpacity(opacity); 5793 } 5794 // Register move 5795 ol.ext.element.addListener(document, "mouseup touchend touchcancel", stop); 5796 ol.ext.element.addListener(document, "mousemove touchmove", move); 5797}; 5798/** Render a list of layer 5799 * @param {Elemen} element to render 5800 * @layers {Array{ol.layer}} list of layer to show 5801 * @api stable 5802 * @private 5803 */ 5804ol.control.LayerSwitcher.prototype.drawList = function(ul, collection) { 5805 var self = this; 5806 var layers = collection.getArray(); 5807 // Change layer visibility 5808 var setVisibility = function(e) { 5809 e.stopPropagation(); 5810 e.preventDefault(); 5811 var l = self._getLayerForLI(this.parentNode.parentNode); 5812 self.switchLayerVisibility(l, collection); 5813 if (self.get('selection') && l.getVisible()) { 5814 self.selectLayer(l); 5815 } 5816 if (self.onchangeCheck) { 5817 self.onchangeCheck(l); 5818 } 5819 }; 5820 // Info button click 5821 function onInfo(e) { 5822 e.stopPropagation(); 5823 e.preventDefault(); 5824 var l = self._getLayerForLI(this.parentNode.parentNode); 5825 self.oninfo(l); 5826 self.dispatchEvent({ type: "info", layer: l }); 5827 } 5828 // Zoom to extent button 5829 function zoomExtent(e) { 5830 e.stopPropagation(); 5831 e.preventDefault(); 5832 var l = self._getLayerForLI(this.parentNode.parentNode); 5833 if (self.onextent) self.onextent(l); 5834 else self.getMap().getView().fit (l.getExtent(), self.getMap().getSize()); 5835 self.dispatchEvent({ type: "extent", layer: l }); 5836 } 5837 // Remove a layer on trash click 5838 function removeLayer(e) { 5839 e.stopPropagation(); 5840 e.preventDefault(); 5841 var li = this.parentNode.parentNode.parentNode.parentNode; 5842 var layer, group = self._getLayerForLI(li); 5843 // Remove the layer from a group or from a map 5844 if (group) { 5845 layer = self._getLayerForLI(this.parentNode.parentNode); 5846 group.getLayers().remove(layer); 5847 if (group.getLayers().getLength()==0 && !group.get('noSwitcherDelete')) { 5848 removeLayer.call(li.querySelectorAll('.layerTrash')[0], e); 5849 } 5850 } else { 5851 li = this.parentNode.parentNode; 5852 self.getMap().removeLayer(self._getLayerForLI(li)); 5853 } 5854 } 5855 // Create a list for a layer 5856 function createLi(layer) { 5857 if (!this.displayInLayerSwitcher(layer)) { 5858 this._setLayerForLI(null, layer); 5859 return; 5860 } 5861 var li = ol.ext.element.create('LI', { 5862 className: (layer.getVisible()?"visible ":" ")+(layer.get('baseLayer')?"baselayer":""), 5863 parent: ul 5864 }); 5865 this._setLayerForLI(li, layer); 5866 if (this._selectedLayer === layer) { 5867 li.classList.add('ol-layer-select'); 5868 } 5869 var layer_buttons = ol.ext.element.create('DIV', { 5870 className: 'ol-layerswitcher-buttons', 5871 parent: li 5872 }); 5873 // Content div 5874 var d = ol.ext.element.create('DIV', { 5875 className: 'li-content',// + (this.testLayerVisibility(layer) ? '' : ' ol-layer-hidden'), 5876 parent: li 5877 }); 5878 // Visibility 5879 ol.ext.element.create('INPUT', { 5880 type: layer.get('baseLayer') ? 'radio' : 'checkbox', 5881 className: 'ol-visibility', 5882 checked: layer.getVisible(), 5883 click: setVisibility, 5884 parent: d 5885 }); 5886 // Label 5887 var label = ol.ext.element.create('LABEL', { 5888 title: layer.get('title') || layer.get('name'), 5889 click: setVisibility, 5890 unselectable: 'on', 5891 style: { 5892 userSelect: 'none' 5893 }, 5894 parent: d 5895 }); 5896 label.addEventListener('selectstart', function(){ return false; }); 5897 ol.ext.element.create('SPAN', { 5898 html: layer.get('title') || layer.get('name'), 5899 click: function(e) { 5900 if (this.get('selection')) { 5901 e.stopPropagation(); 5902 this.selectLayer(layer); 5903 } 5904 }.bind(this), 5905 parent: label 5906 }); 5907 // up/down 5908 if (this.reordering) { 5909 if ( (i<layers.length-1 && (layer.get("allwaysOnTop") || !layers[i+1].get("allwaysOnTop")) ) 5910 || (i>0 && (!layer.get("allwaysOnTop") || layers[i-1].get("allwaysOnTop")) ) ) { 5911 ol.ext.element.create('DIV', { 5912 className: 'layerup ol-noscroll', 5913 title: this.tip.up, 5914 on: { 'mousedown touchstart': function(e) { self.dragOrdering_(e) } }, 5915 parent: layer_buttons 5916 }); 5917 } 5918 } 5919 // Show/hide sub layers 5920 if (layer.getLayers) { 5921 var nb = 0;
5922 layer.getLayers().forEach(function(l) { 5923 if (self.displayInLayerSwitcher(l)) nb++; 5924 }); 5925 if (nb) { 5926 ol.ext.element.create('DIV', { 5927 className: layer.get("openInLayerSwitcher") ? "collapse-layers" : "expend-layers", 5928 title: this.tip.plus, 5929 click: function() { 5930 var l = self._getLayerForLI(this.parentNode.parentNode); 5931 l.set("openInLayerSwitcher", !l.get("openInLayerSwitcher") ) 5932 }, 5933 parent: layer_buttons 5934 }); 5935 } 5936 } 5937 // Info button 5938 if (this.oninfo) { 5939 ol.ext.element.create('DIV', { 5940 className: 'layerInfo', 5941 title: this.tip.info, 5942 click: onInfo, 5943 parent: layer_buttons 5944 }); 5945 } 5946 // Layer remove 5947 if (this.hastrash && !layer.get("noSwitcherDelete")) { 5948 ol.ext.element.create('DIV', { 5949 className: 'layerTrash', 5950 title: this.tip.trash, 5951 click: removeLayer, 5952 parent: layer_buttons 5953 }); 5954 } 5955 // Layer extent 5956 if (this.hasextent && layers[i].getExtent()) { 5957 var ex = layers[i].getExtent(); 5958 if (ex.length==4 && ex[0]<ex[2] && ex[1]<ex[3]) { 5959 ol.ext.element.create('DIV', { 5960 className: 'layerExtent', 5961 title: this.tip.extent, 5962 click: zoomExtent, 5963 parent: layer_buttons 5964 }); 5965 } 5966 } 5967 // Progress 5968 if (this.show_progress && layer instanceof ol.layer.Tile) { 5969 var p = ol.ext.element.create('DIV', { 5970 className: 'layerswitcher-progress', 5971 parent: d 5972 }); 5973 this.setprogress_(layer); 5974 layer.layerswitcher_progress = ol.ext.element.create('DIV', { parent: p }); 5975 } 5976 // Opacity 5977 var opacity = ol.ext.element.create('DIV', { 5978 className: 'layerswitcher-opacity', 5979 // Click on the opacity line 5980 click: function(e){ 5981 if (e.target !== this) return; 5982 e.stopPropagation(); 5983 e.preventDefault(); 5984 var op = Math.max ( 0, Math.min( 1, e.offsetX / ol.ext.element.getStyle(this, 'width'))); 5985 self._getLayerForLI(this.parentNode.parentNode).setOpacity(op); 5986 }, 5987 parent: d 5988 }); 5989 // Start dragging 5990 ol.ext.element.create('DIV', { 5991 className: 'layerswitcher-opacity-cursor ol-noscroll', 5992 style: { left: (layer.getOpacity()*100)+"%" }, 5993 on: { 5994 'mousedown touchstart': function(e) { self.dragOpacity_ (e); } 5995 }, 5996 parent: opacity 5997 }); 5998 // Percent 5999 ol.ext.element.create('DIV', { 6000 className: 'layerswitcher-opacity-label', 6001 html: Math.round(layer.getOpacity()*100), 6002 parent: d 6003 }); 6004 // Layer group 6005 if (layer.getLayers) { 6006 li.classList.add('ol-layer-group'); 6007 if (layer.get("openInLayerSwitcher") === true) { 6008 var ul2 = ol.ext.element.create('UL', { 6009 parent: li 6010 }); 6011 this.drawList (ul2, layer.getLayers()); 6012 } 6013 } 6014 li.classList.add(this.getLayerClass(layer)); 6015 // Dispatch a dralist event to allow customisation 6016 this.dispatchEvent({ type:'drawlist', layer:layer, li:li }); 6017 } 6018 // Add the layer list 6019 for (var i=layers.length-1; i>=0; i--) { 6020 createLi.call(this, layers[i]); 6021 } 6022 this.viewChange(); 6023 if (ul === this.panel_) this.overflow(); 6024}; 6025/** Select a layer 6026 * @param {ol.layer.Layer} layer 6027 * @returns {string} the layer classname 6028 * @api 6029 */ 6030ol.control.LayerSwitcher.prototype.getLayerClass = function(layer) { 6031 if (!layer) return 'none'; 6032 if (layer.getLayers) return 'ol-layer-group'; 6033 if (layer instanceof ol.layer.Vector) return 'ol-layer-vector'; 6034 if (layer instanceof ol.layer.VectorTile) return 'ol-layer-vectortile'; 6035 if (layer instanceof ol.layer.Tile) return 'ol-layer-tile'; 6036 if (layer instanceof ol.layer.Image) return 'ol-layer-image'; 6037 if (layer instanceof ol.layer.Heatmap) return 'ol-layer-heatmap'; 6038 /* ol < 6 compatibility VectorImage is not defined */ 6039 // if (layer instanceof ol.layer.VectorImage) return 'ol-layer-vectorimage'; 6040 if (layer.getFeatures) return 'ol-layer-vectorimage'; 6041 /* */ 6042 return 'unknown'; 6043}; 6044/** Select a layer 6045 * @param {ol.layer.Layer} layer 6046 * @api 6047 */ 6048ol.control.LayerSwitcher.prototype.selectLayer = function(layer, silent) { 6049 if (!layer) { 6050 if (!this.getMap()) return; 6051 layer = this.getMap().getLayers().item(this.getMap().getLayers().getLength()-1) 6052 } 6053 this._selectedLayer = layer; 6054 this.drawPanel(); 6055 if (!silent) this.dispatchEvent({ type: 'select', layer: layer }); 6056}; 6057/** Get selected layer 6058 * @returns {ol.layer.Layer} 6059 */ 6060ol.control.LayerSwitcher.prototype.getSelection = function() { 6061 return this._selectedLayer; 6062}; 6063/** Handle progress bar for a layer 6064* @private 6065*/ 6066ol.control.LayerSwitcher.prototype.setprogress_ = function(layer) { 6067 if (!layer.layerswitcher_progress) { 6068 var loaded = 0; 6069 var loading = 0; 6070 var draw = function() { 6071 if (loading === loaded) { 6072 loading = loaded = 0; 6073 ol.ext.element.setStyle(layer.layerswitcher_progress, { width: 0 });// layer.layerswitcher_progress.width(0); 6074 } else { 6075 ol.ext.element.setStyle(layer.layerswitcher_progress, { width: (loaded / loading * 100).toFixed(1) + '%' });// layer.layerswitcher_progress.css('width', (loaded / loading * 100).toFixed(1) + '%'); 6076 } 6077 } 6078 layer.getSource().on('tileloadstart', function() { 6079 loading++; 6080 draw(); 6081 }); 6082 layer.getSource().on('tileloadend', function() { 6083 loaded++; 6084 draw(); 6085 }); 6086 layer.getSource().on('tileloaderror', function() { 6087 loaded++; 6088 draw(); 6089 }); 6090 } 6091}; 6092 6093/* Copyright (c) 2016 Jean-Marc VIGLINO, 6094 released under the CeCILL-B license (French BSD license)
6095 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 6096*/ 6097/** Control bar for OL3 6098 * The control bar is a container for other controls. It can be used to create toolbars. 6099 * Control bars can be nested and combined with ol.control.Toggle to handle activate/deactivate. 6100 * @class 6101 * @constructor 6102 * @extends ol.control.Control 6103 * @param {Object=} options Control options. 6104 * @param {String} options.className class of the control 6105 * @param {boolean} options.group is a group, default false 6106 * @param {boolean} options.toggleOne only one toggle control is active at a time, default false 6107 * @param {boolean} options.autoDeactivate used with subbar to deactivate all control when top level control deactivate, default false 6108 * @param {Array<ol.control.Control> } options.controls a list of control to add to the bar 6109 */ 6110ol.control.Bar = function(options) { 6111 if (!options) options={}; 6112 var element = document.createElement("div"); 6113 element.classList.add('ol-unselectable', 'ol-control', 'ol-bar'); 6114 if (options.className) { 6115 var classes = options.className.split(' ').filter(function(className) { 6116 return className.length > 0; 6117 }); 6118 element.classList.add.apply(element.classList, classes) 6119 } 6120 if (options.group) element.classList.add('ol-group'); 6121 ol.control.Control.call(this, { 6122 element: element, 6123 target: options.target 6124 }); 6125 this.set('toggleOne', options.toggleOne); 6126 this.set('autoDeactivate', options.autoDeactivate); 6127 this.controls_ = []; 6128 if (options.controls instanceof Array) { 6129 for (var i=0; i<options.controls.length; i++) { 6130 this.addControl(options.controls[i]); 6131 } 6132 } 6133}; 6134ol.ext.inherits(ol.control.Bar, ol.control.Control); 6135/** Set the control visibility 6136 * @param {boolean} val 6137 */ 6138ol.control.Bar.prototype.setVisible = function (val) { 6139 if (val) this.element.style.display = ''; 6140 else this.element.style.display = 'none'; 6141} 6142/** Get the control visibility 6143 * @return {boolean} b 6144 */ 6145ol.control.Bar.prototype.getVisible = function () { 6146 return this.element.style.display != 'none'; 6147} 6148/** 6149 * Set the map instance the control is associated with 6150 * and add its controls associated to this map. 6151 * @param {ol.Map} map The map instance. 6152 */ 6153ol.control.Bar.prototype.setMap = function (map) { 6154 ol.control.Control.prototype.setMap.call(this, map); 6155 for (var i=0; i<this.controls_.length; i++) { 6156 var c = this.controls_[i]; 6157 // map.addControl(c); 6158 c.setMap(map); 6159 } 6160}; 6161/** Get controls in the panel 6162 * @param {Array<ol.control.Control>} 6163 */ 6164ol.control.Bar.prototype.getControls = function () { 6165 return this.controls_; 6166}; 6167/** Set tool bar position 6168 * @param {string} pos a combinaison of top|left|bottom|right 6169 */ 6170ol.control.Bar.prototype.setPosition = function (pos) { 6171 this.element.classList.remove('ol-left', 'ol-top', 'ol-bottom', 'ol-right'); 6172 pos = pos.split ('-'); 6173 for (var i=0; i<pos.length; i++) { 6174 switch (pos[i]) { 6175 case 'top': 6176 case 'left': 6177 case 'bottom': 6178 case 'right': 6179 this.element.classList.add("ol-"+pos[i]); 6180 break; 6181 default: break; 6182 } 6183 } 6184}; 6185/** Add a control to the bar 6186 * @param {ol.control.Control} c control to add 6187 */ 6188ol.control.Bar.prototype.addControl = function (c) { 6189 this.controls_.push(c); 6190 c.setTarget(this.element); 6191 if (this.getMap()) { 6192 this.getMap().addControl(c); 6193 } 6194 // Activate and toogleOne 6195 c.on ('change:active', function(e) { this.onActivateControl_(e, c); }.bind(this)); 6196 if (c.getActive) { 6197 // c.dispatchEvent({ type:'change:active', key:'active', oldValue:false, active:true }); 6198 this.onActivateControl_({ target: c, active: c.getActive() }, c); 6199 } 6200}; 6201/** Deativate all controls in a bar 6202 * @param {ol.control.Control} [except] a control 6203 */ 6204ol.control.Bar.prototype.deactivateControls = function (except) { 6205 for (var i=0; i<this.controls_.length; i++) { 6206 if (this.controls_[i] !== except && this.controls_[i].setActive) { 6207 this.controls_[i].setActive(false); 6208 } 6209 } 6210}; 6211/** Get active control in the bar 6212 * @returns {Array<ol.control.Control>} 6213 */ 6214ol.control.Bar.prototype.getActiveControls = function () { 6215 var active = []; 6216 for (var i=0, c; c=this.controls_[i]; i++) { 6217 if (c.getActive && c.getActive()) active.push(c); 6218 } 6219 return active; 6220} 6221/** Auto activate/deactivate controls in the bar 6222 * @param {boolean} b activate/deactivate 6223 */ 6224ol.control.Bar.prototype.setActive = function (b) { 6225 if (!b && this.get("autoDeactivate")) { 6226 this.deactivateControls(); 6227 } 6228 if (b) { 6229 var ctrls = this.getControls(); 6230 for (var i=0, sb; (sb = ctrls[i]); i++) { 6231 if (sb.get("autoActivate")) sb.setActive(true); 6232 } 6233 } 6234} 6235/** Post-process an activated/deactivated control 6236 * @param {ol.event} e :an object with a target {_ol_control_} and active flag {bool} 6237 */ 6238ol.control.Bar.prototype.onActivateControl_ = function (e, ctrl) { 6239 if (this.get('toggleOne')) { 6240 if (e.active) { 6241 var n; 6242 //var ctrl = e.target; 6243 for (n=0; n<this.controls_.length; n++) { 6244 if (this.controls_[n]===ctrl) break; 6245 } 6246 // Not here! 6247 if (n==this.controls_.length) return; 6248 this.deactivateControls (this.controls_[n]); 6249 } else { 6250 // No one active > test auto activate 6251 if (!this.getActiveControls().length) { 6252 for (var i=0, c; c=this.controls_[i]; i++) { 6253 if (c.get("autoActivate")) { 6254 c.setActive(true); 6255 break; 6256 } 6257 } 6258 } 6259 } 6260 } 6261}; 6262/** 6263 * @param {string} name of the control to search 6264 * @return {ol.control.Control} 6265 */ 6266ol.control.Bar.prototype.getControlsByName = function(name) { 6267 var controls = this.getControls(); 6268 return controls.filter( 6269 function(control) { 6270 return (control.get('name') === name); 6271 } 6272 ); 6273}; 6274 6275/* Copyright (c) 2015 Jean-Marc VIGLINO, 6276 released under the CeCILL-B license (French BSD license)
6277 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 6278*/ 6279/** 6280 * @classdesc 6281 * OpenLayers 3 Attribution Control integrated in the canvas (for jpeg/png 6282 * @see http://www.kreidefossilien.de/webgis/dokumentation/beispiele/export-map-to-png-with-scale 6283 * 6284 * @constructor 6285 * @extends ol.control.Attribution 6286 * @param {Object=} options extend the ol.control.Attribution options. 6287 * @param {ol.style.Style} options.style option is usesd to draw the text. 6288 * @paream {boolean} [options.canvas=false] draw on canvas 6289 */ 6290ol.control.CanvasAttribution = function(options) { 6291 if (!options) options = {}; 6292 ol.control.Attribution.call(this, options); 6293 this.element.classList.add('ol-canvas-control'); 6294 // Draw in canvas 6295 this.setCanvas(!!options.canvas); 6296 // Get style options 6297 if (!options) options={}; 6298 if (!options.style) options.style = new ol.style.Style(); 6299 this.setStyle (options.style); 6300} 6301ol.ext.inherits(ol.control.CanvasAttribution, ol.control.Attribution); 6302/** 6303 * Draw attribution on canvas 6304 * @param {boolean} b draw the attribution on canvas. 6305 */ 6306ol.control.CanvasAttribution.prototype.setCanvas = function (b) { 6307 this.isCanvas_ = b; 6308 if (b) this.setCollapsed(false); 6309 this.element.style.visibility = b ? "hidden":"visible"; 6310 if (this.getMap()) { 6311 try { 6312 this.getMap().renderSync(); 6313 } catch(e) { /* ok */ } 6314 } 6315}; 6316/** Get map Canvas 6317 * @private 6318 */ 6319ol.control.CanvasAttribution.prototype.getContext = ol.control.CanvasBase.prototype.getContext; 6320/** 6321 * Change the control style 6322 * @param {ol.style.Style} style 6323 */ 6324ol.control.CanvasAttribution.prototype.setStyle = function (style) { 6325 var text = style.getText(); 6326 this.font_ = text ? text.getFont() : "10px sans-serif"; 6327 var stroke = text ? text.getStroke() : null; 6328 var fill = text ? text.getFill() : null; 6329 this.fontStrokeStyle_ = stroke ? ol.color.asString(stroke.getColor()) : "#fff"; 6330 this.fontFillStyle_ = fill ? ol.color.asString(fill.getColor()) : "#000"; 6331 this.fontStrokeWidth_ = stroke ? stroke.getWidth() : 3; 6332 if (this.getMap()) this.getMap().render(); 6333}; 6334/** 6335 * Remove the control from its current map and attach it to the new map. 6336 * Subclasses may set up event handlers to get notified about changes to 6337 * the map here. 6338 * @param {ol.Map} map Map. 6339 * @api stable 6340 */ 6341ol.control.CanvasAttribution.prototype.setMap = function (map) { 6342 ol.control.CanvasBase.prototype.getCanvas.call(this, map); 6343 var oldmap = this.getMap(); 6344 if (this._listener) ol.Observable.unByKey(this._listener); 6345 this._listener = null; 6346 ol.control.Attribution.prototype.setMap.call(this, map); 6347 if (oldmap) { 6348 try { oldmap.renderSync(); } catch(e) { /* ok */ } 6349 } 6350 // Get change (new layer added or removed) 6351 if (map) { 6352 this._listener = map.on('postcompose', this.drawAttribution_.bind(this)); 6353 } 6354 this.setCanvas (this.isCanvas_); 6355}; 6356/** 6357 * Draw attribution in the final canvas 6358 * @private 6359 */ 6360ol.control.CanvasAttribution.prototype.drawAttribution_ = function(e) { 6361 if (!this.isCanvas_) return; 6362 var ctx = this.getContext(e); 6363 if (!ctx) return; 6364 var text = ""; 6365 Array.prototype.slice.call(this.element.querySelectorAll('li')) 6366 .filter(function(el) { 6367 return el.style.display !== "none"; 6368 }) 6369 .map(function(el) { 6370 text += (text ? " - ":"") + el.textContent; 6371 }); 6372 // Retina device 6373 var ratio = e.frameState.pixelRatio; 6374 ctx.save(); 6375 ctx.scale(ratio,ratio); 6376 // Position 6377 var eltRect = this.element.getBoundingClientRect(); 6378 var mapRect = this.getMap().getViewport().getBoundingClientRect(); 6379 var sc = this.getMap().getSize()[0] / mapRect.width; 6380 ctx.translate((eltRect.left-mapRect.left)*sc, (eltRect.top-mapRect.top)*sc); 6381 var h = this.element.clientHeight; 6382 var w = this.element.clientWidth; 6383 var textAlign = ol.ext.element.getStyle(this.element, 'textAlign') || 'center'; 6384 var left; 6385 switch(textAlign) { 6386 case 'left': { 6387 left = 0; 6388 break; 6389 } 6390 case 'right': { 6391 left = w; 6392 break; 6393 } 6394 default: { 6395 left = w/2; 6396 break; 6397 } 6398 } 6399 // Draw scale text 6400 ctx.beginPath(); 6401 ctx.strokeStyle = this.fontStrokeStyle_; 6402 ctx.fillStyle = this.fontFillStyle_; 6403 ctx.lineWidth = this.fontStrokeWidth_; 6404 ctx.textAlign = textAlign; 6405 ctx.textBaseline = 'middle'; 6406 ctx.font = this.font_; 6407 ctx.strokeText(text, left, h/2); 6408 ctx.fillText(text, left, h/2); 6409 ctx.closePath();
6410 ctx.restore(); 6411}; 6412 6413/* Copyright (c) 2015 Jean-Marc VIGLINO, 6414 released under the CeCILL-B license (French BSD license) 6415 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 6416*/ 6417/** 6418 * @classdesc 6419 * OpenLayers Scale Line Control integrated in the canvas (for jpeg/png 6420 * @see http://www.kreidefossilien.de/webgis/dokumentation/beispiele/export-map-to-png-with-scale 6421 * 6422 * @constructor 6423 * @extends {ol.control.ScaleLine} 6424 * @param {Object=} options extend the ol.control.ScaleLine options. 6425 * @param {ol.style.Style} options.style used to draw the scale line (default is black/white, 10px Arial). 6426 */ 6427ol.control.CanvasScaleLine = function(options) { 6428 ol.control.ScaleLine.call(this, options); 6429 this.element.classList.add('ol-canvas-control'); 6430 this.scaleHeight_ = 6; 6431 // Get style options 6432 if (!options) options={}; 6433 if (!options.style) options.style = new ol.style.Style(); 6434 this.setStyle(options.style); 6435} 6436ol.ext.inherits(ol.control.CanvasScaleLine, ol.control.ScaleLine); 6437/** Get map Canvas 6438 * @private 6439 */ 6440ol.control.CanvasScaleLine.prototype.getContext = ol.control.CanvasBase.prototype.getContext; 6441/** 6442 * Remove the control from its current map and attach it to the new map. 6443 * Subclasses may set up event handlers to get notified about changes to 6444 * the map here. 6445 * @param {ol.Map} map Map. 6446 * @api stable 6447 */ 6448ol.control.CanvasScaleLine.prototype.setMap = function (map) { 6449 ol.control.CanvasBase.prototype.getCanvas.call(this, map); 6450 var oldmap = this.getMap(); 6451 if (this._listener) ol.Observable.unByKey(this._listener); 6452 this._listener = null; 6453 ol.control.ScaleLine.prototype.setMap.call(this, map); 6454 if (oldmap) { 6455 try { oldmap.renderSync(); } catch(e) { /* ok */ } 6456 } 6457 // Add postcompose on the map 6458 if (map) { 6459 this._listener = map.on('postcompose', this.drawScale_.bind(this)); 6460 } 6461 // Hide the default DOM element 6462 this.element.style.visibility = 'hidden'; 6463 this.olscale = this.element.querySelector(".ol-scale-line-inner"); 6464} 6465/** 6466 * Change the control style 6467 * @param {ol.style.Style} style 6468 */ 6469ol.control.CanvasScaleLine.prototype.setStyle = function (style) { 6470 var stroke = style.getStroke(); 6471 this.strokeStyle_ = stroke ? ol.color.asString(stroke.getColor()) : "#000"; 6472 this.strokeWidth_ = stroke ? stroke.getWidth() : 2; 6473 var fill = style.getFill(); 6474 this.fillStyle_ = fill ? ol.color.asString(fill.getColor()) : "#fff"; 6475 var text = style.getText(); 6476 this.font_ = text ? text.getFont() : "10px Arial"; 6477 stroke = text ? text.getStroke() : null; 6478 fill = text ? text.getFill() : null; 6479 this.fontStrokeStyle_ = stroke ? ol.color.asString(stroke.getColor()) : this.fillStyle_; 6480 this.fontStrokeWidth_ = stroke ? stroke.getWidth() : 3; 6481 this.fontFillStyle_ = fill ? ol.color.asString(fill.getColor()) : this.strokeStyle_; 6482 // refresh 6483 if (this.getMap()) this.getMap().render(); 6484} 6485/** 6486 * Draw attribution in the final canvas 6487 * @param {ol.render.Event} e 6488 * @private 6489 */ 6490ol.control.CanvasScaleLine.prototype.drawScale_ = function(e) { 6491 if ( this.element.style.visibility !== 'hidden' || ol.ext.element.getStyle(this.element, 'display') === 'none' ) return; 6492 var ctx = this.getContext(e); 6493 if (!ctx) return; 6494 // Get size of the scale div 6495 var scalewidth = parseInt(this.olscale.style.width); 6496 if (!scalewidth) return; 6497 var text = this.olscale.textContent; 6498 var position = {left: this.element.offsetLeft, top: this.element.offsetTop}; 6499 // Retina device 6500 var ratio = e.frameState.pixelRatio; 6501 ctx.save(); 6502 ctx.scale(ratio,ratio); 6503 // On top 6504 position.top += this.element.clientHeight - this.scaleHeight_; 6505 // Draw scale text 6506 ctx.beginPath(); 6507 ctx.strokeStyle = this.fontStrokeStyle_; 6508 ctx.fillStyle = this.fontFillStyle_; 6509 ctx.lineWidth = this.fontStrokeWidth_; 6510 ctx.textAlign = "center"; 6511 ctx.textBaseline ="bottom"; 6512 ctx.font = this.font_; 6513 ctx.strokeText(text, position.left+scalewidth/2, position.top); 6514 ctx.fillText(text, position.left+scalewidth/2, position.top); 6515 ctx.closePath(); 6516 // Draw scale bar 6517 position.top += 2; 6518 ctx.lineWidth = this.strokeWidth_; 6519 ctx.strokeStyle = this.strokeStyle_; 6520 var max = 4; 6521 var n = parseInt(text); 6522 while (n%10 === 0) n/=10; 6523 if (n%5 === 0) max = 5; 6524 for (var i=0; i<max; i++) { 6525 ctx.beginPath(); 6526 ctx.fillStyle = i%2 ? this.fillStyle_ : this.strokeStyle_; 6527 ctx.rect(position.left+i*scalewidth/max, position.top, scalewidth/max, this.scaleHeight_); 6528 ctx.stroke(); 6529 ctx.fill(); 6530 ctx.closePath(); 6531 }
6532 ctx.restore(); 6533} 6534 6535/* Copyright (c) 2015 Jean-Marc VIGLINO, 6536 released under the CeCILL-B license (French BSD license) 6537 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 6538*/ 6539/** 6540 * A title Control integrated in the canvas (for jpeg/png 6541 * 6542 * @constructor 6543 * @extends {ol.control.CanvasBase} 6544 * @param {Object=} options extend the ol.control options. 6545 * @param {string} options.title the title, default 'Title' 6546 * @param {ol.style.Style} options.style style used to draw the title. 6547 */ 6548ol.control.CanvasTitle = function(options) { 6549 if (!options) options = {}; 6550 var elt = ol.ext.element.create('DIV', { 6551 className: (options.className || '') + ' ol-control-title ol-unselectable', 6552 style: { 6553 display: 'block', 6554 visibility: 'hidden' 6555 } 6556 }); 6557 ol.control.CanvasBase.call(this, { 6558 element: elt, 6559 style: options.style 6560 }); 6561 this.setTitle(options.title || ''); 6562 this.setVisible(options.visible); 6563 this.element.style.font = this.getTextFont(); 6564}; 6565ol.ext.inherits(ol.control.CanvasTitle, ol.control.CanvasBase); 6566/** 6567 * Change the control style 6568 * @param {ol.style.Style} style 6569 */ 6570ol.control.CanvasTitle.prototype.setStyle = function (style) { 6571 ol.control.CanvasBase.prototype.setStyle.call(this, style); 6572 // Element style 6573 if (this.element) { 6574 this.element.style.font = this.getTextFont(); 6575 } 6576 // refresh 6577 if (this.getMap()) this.getMap().render(); 6578}; 6579/** 6580 * Set the map title 6581 * @param {string} map title. 6582 * @api stable 6583 */ 6584ol.control.CanvasTitle.prototype.setTitle = function (title) { 6585 this.element.textContent = title; 6586 this.set('title', title); 6587 if (this.getMap()) { 6588 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 6589 } 6590}; 6591/** 6592 * Get the map title 6593 * @param {string} map title. 6594 * @api stable 6595 */ 6596ol.control.CanvasTitle.prototype.getTitle = function () { 6597 return this.get('title'); 6598}; 6599/** 6600 * Set control visibility 6601 * @param {bool} b 6602 * @api stable 6603 */ 6604ol.control.CanvasTitle.prototype.setVisible = function (b) { 6605 this.element.style.display = (b ? 'block' : 'none'); 6606 if (this.getMap()) { 6607 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 6608 } 6609}; 6610/** 6611 * Get control visibility 6612 * @return {bool} 6613 * @api stable 6614 */ 6615ol.control.CanvasTitle.prototype.getVisible = function () { 6616 return this.element.style.display !== 'none'; 6617}; 6618/** Draw title in the final canvas 6619 * @private 6620*/ 6621ol.control.CanvasTitle.prototype._draw = function(e) { 6622 if (!this.getVisible()) return; 6623 var ctx = this.getContext(e); 6624 if (!ctx) return; 6625 // Retina device 6626 var ratio = e.frameState.pixelRatio; 6627 ctx.save(); 6628 ctx.scale(ratio,ratio); 6629 // Position 6630 var eltRect = this.element.getBoundingClientRect(); 6631 var mapRect = this.getMap().getViewport().getBoundingClientRect(); 6632 var sc = this.getMap().getSize()[0] / mapRect.width; 6633 ctx.translate( 6634 Math.round((eltRect.left-mapRect.left)*sc), 6635 Math.round((eltRect.top-mapRect.top)*sc) 6636 ); 6637 var h = this.element.clientHeight; 6638 var w = this.element.clientWidth; 6639 var left = w/2; 6640 ctx.beginPath(); 6641 ctx.fillStyle = ol.color.asString(this.getFill().getColor()); 6642 ctx.rect(0,0, w, h); 6643 ctx.fill(); 6644 ctx.closePath(); 6645 ctx.beginPath(); 6646 ctx.fillStyle = ol.color.asString(this.getTextFill().getColor()); 6647 ctx.strokeStyle = ol.color.asString(this.getTextStroke().getColor()); 6648 ctx.lineWidth = this.getTextStroke().getWidth(); 6649 ctx.textAlign = 'center'; 6650 ctx.textBaseline = 'middle'; 6651 ctx.font = this.getTextFont(); 6652 if (ctx.lineWidth) ctx.strokeText(this.getTitle(), left, h/2); 6653 ctx.fillText(this.getTitle(), left, h/2); 6654 ctx.closePath(); 6655 ctx.restore(); 6656}; 6657 6658/* Copyright (c) 2015 Jean-Marc VIGLINO, 6659 released under the CeCILL-B license (French BSD license) 6660 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 6661*/ 6662/** 6663 * A Control to display map center coordinates on the canvas. 6664 * 6665 * @constructor 6666 * @extends {ol.control.CanvasBase} 6667 * @param {Object=} options extend the ol.control options. 6668 * @param {string} options.className CSS class name 6669 * @param {ol.style.Style} options.style style used to draw in the canvas 6670 * @param {ol.proj.ProjectionLike} options.projection Projection. Default is the view projection. 6671 * @param {ol.coordinate.CoordinateFormat} options.coordinateFormat A function that takes a ol.Coordinate and transforms it into a string. 6672 * @param {boolean} options.canvas true to draw in the canvas 6673 */ 6674ol.control.CenterPosition = function(options) { 6675 if (!options) options = {}; 6676 var elt = ol.ext.element.create('DIV', { 6677 className: (options.className || '') + ' ol-center-position ol-unselectable', 6678 style: { 6679 display: 'block', 6680 visibility: 'hidden' 6681 } 6682 }); 6683 ol.control.CanvasBase.call(this, { 6684 element: elt, 6685 style: options.style 6686 }); 6687 this.element.style.font = this.getTextFont(); 6688 this.set('projection', options.projection); 6689 this.setCanvas(options.canvas); 6690 this._format = (typeof options.coordinateFormat === 'function') ? options.coordinateFormat : ol.coordinate.toStringXY; 6691}; 6692ol.ext.inherits(ol.control.CenterPosition, ol.control.CanvasBase); 6693/** 6694 * Change the control style 6695 * @param {ol.style.Style} style 6696 */ 6697ol.control.CenterPosition.prototype.setStyle = function (style) { 6698 ol.control.CanvasBase.prototype.setStyle.call(this, style); 6699 // Element style 6700 if (this.element) { 6701 this.element.style.font = this.getTextFont(); 6702 } 6703 // refresh 6704 if (this.getMap()) this.getMap().render(); 6705}; 6706/** 6707 * Draw on canvas 6708 * @param {boolean} b draw the attribution on canvas. 6709 */ 6710ol.control.CenterPosition.prototype.setCanvas = function (b) { 6711 this.set('canvas', b); 6712 this.element.style.visibility = b ? "hidden":"visible"; 6713 if (this.getMap()) { 6714 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 6715 } 6716}; 6717/** 6718 * Set control visibility 6719 * @param {bool} b 6720 * @api stable 6721 */ 6722ol.control.CenterPosition.prototype.setVisible = function (b) { 6723 this.element.style.display = (b ? '' : 'none'); 6724 if (this.getMap()) { 6725 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 6726 } 6727}; 6728/** 6729 * Get control visibility 6730 * @return {bool} 6731 * @api stable 6732 */ 6733ol.control.CenterPosition.prototype.getVisible = function () { 6734 return this.element.style.display !== 'none'; 6735}; 6736/** Draw position in the final canvas 6737 * @private 6738*/ 6739ol.control.CenterPosition.prototype._draw = function(e) { 6740 if (!this.getVisible() || !this.getMap()) return; 6741 // Coordinate 6742 var coord = this.getMap().getView().getCenter(); 6743 if (this.get('projection')) coord = ol.proj.transform (coord, this.getMap().getView().getProjection(), this.get('projection')); 6744 coord = this._format(coord); 6745 this.element.textContent = coord; 6746 if (!this.get('canvas')) return; 6747 var ctx = this.getContext(e); 6748 if (!ctx) return; 6749 // Retina device 6750 var ratio = e.frameState.pixelRatio; 6751 ctx.save(); 6752 ctx.scale(ratio,ratio); 6753 // Position 6754 var eltRect = this.element.getBoundingClientRect(); 6755 var mapRect = this.getMap().getViewport().getBoundingClientRect(); 6756 var sc = this.getMap().getSize()[0] / mapRect.width;
6757 ctx.translate((eltRect.left-mapRect.left)*sc, (eltRect.top-mapRect.top)*sc); 6758 var h = this.element.clientHeight; 6759 var w = this.element.clientWidth; 6760 ctx.beginPath(); 6761 ctx.fillStyle = ol.color.asString(this.getTextFill().getColor()); 6762 ctx.strokeStyle = ol.color.asString(this.getTextStroke().getColor()); 6763 ctx.lineWidth = this.getTextStroke().getWidth(); 6764 ctx.textAlign = 'center'; 6765 ctx.textBaseline = 'middle'; 6766 ctx.font = this.getTextFont(); 6767 if (ctx.lineWidth) ctx.strokeText(coord, w/2, h/2); 6768 ctx.fillText(coord, w/2, h/2); 6769 ctx.closePath(); 6770 ctx.restore(); 6771}; 6772 6773/* Copyright (c) 2017 Jean-Marc VIGLINO, 6774 released under the CeCILL-B license (French BSD license) 6775 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 6776*/ 6777/** 6778 * Draw a compass on the map. The position/size of the control is defined in the css. 6779 * 6780 * @constructor 6781 * @extends {ol.control.CanvasBase} 6782 * @param {Object=} options Control options. The style {_ol_style_Stroke_} option is usesd to draw the text. 6783 * @param {string} options.className class name for the control 6784 * @param {boolean} [options.visible=true] 6785 * @param {Image} options.image an image, default use the src option or a default image 6786 * @param {string} options.src image src or 'default' or 'compact', default use the image option or a default image 6787 * @param {boolean} options.rotateVithView rotate vith view (false to show watermark), default true 6788 * @param {ol.style.Stroke} options.style style to draw the lines, default draw no lines 6789 */ 6790ol.control.Compass = function(options) { 6791 if (!options) options = {}; 6792 // Initialize parent 6793 var elt = document.createElement("div"); 6794 elt.className = "ol-control ol-compassctrl ol-unselectable ol-hidden" + (options.className ? " "+options.className : ""); 6795 elt.style.position = "absolute"; 6796 elt.style.visibility = "hidden"; 6797 var style = (options.style instanceof ol.style.Stroke) ? new ol.style.Style({stroke: options.style}) : options.style; 6798 if (!options.style) { 6799 style = new ol.style.Style({stroke: new ol.style.Stroke({width:0}) }); 6800 } 6801 ol.control.CanvasBase.call(this, { 6802 element: elt, 6803 style: style 6804 }); 6805 this.set('rotateVithView', options.rotateWithView!==false); 6806 this.setVisible(options.visible!==false); 6807 this.setImage(options.image || options.src); 6808}; 6809ol.ext.inherits(ol.control.Compass, ol.control.CanvasBase); 6810/** Set compass image 6811 * @param {Image|string} [img=default] the image or an url or 'compact' or 'default' 6812 */ 6813ol.control.Compass.prototype.setImage = function (img) { 6814 // The image 6815 if (img instanceof Image) { 6816 this.img_ = img; 6817 this.img_.onload = function(){ 6818 if (this.getMap()) { 6819 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 6820 } 6821 }.bind(this); 6822 } else if (typeof(img) === 'string') { 6823 // Load source 6824 switch (img) { 6825 case 'compact': { 6826 this.img_ = this.compactCompass_(this.element.clientWidth, this.getStroke().getColor()); 6827 break; 6828 } 6829 case 'default': { 6830 this.img_ = this.defaultCompass_(this.element.clientWidth, this.getStroke().getColor()); 6831 break; 6832 } 6833 default: { 6834 this.img_ = new Image(); 6835 this.img_.onload = function(){ 6836 if (this.getMap()) { 6837 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 6838 } 6839 }.bind(this); 6840 this.img_.src = img; 6841 break; 6842 } 6843 } 6844 } else { 6845 this.img_ = this.defaultCompass_(this.element.clientWidth, this.getStroke().getColor()); 6846 } 6847} 6848/** Create a default image. 6849 * @param {number} s the size of the compass 6850 * @private 6851 */ 6852ol.control.Compass.prototype.compactCompass_ = function (s, color) { 6853 var canvas = document.createElement('canvas'); 6854 var ctx = canvas.getContext("2d"); 6855 s = canvas.width = canvas.height = s || 150; 6856 var r = s/2; 6857 ctx.translate(r,r); 6858 ctx.fillStyle = color || '#963'; 6859 ctx.lineWidth = 5; 6860 ctx.lineJoin = ctx.lineCap = 'round'; 6861 ctx.font = 'bold '+(r*0.4)+'px sans-serif'; 6862 ctx.textBaseline = 'bottom'; 6863 ctx.textAlign = 'center'; 6864 ctx.strokeStyle = '#fff'; 6865 ctx.globalAlpha = .75; 6866 ctx.strokeText('N', 0,-r/2); 6867 ctx.globalAlpha = 1; 6868 ctx.fillText('N', 0,-r/2); 6869 ctx.beginPath(); 6870 ctx.moveTo(0,r/4); 6871 ctx.lineTo(r/3,r/2); 6872 ctx.lineTo(0,-r/2); 6873 ctx.lineTo(-r/3,r/2); 6874 ctx.lineTo(0,r/4); 6875 ctx.lineWidth = 12; 6876 ctx.fillStyle = "#fff"; 6877 ctx.globalAlpha = .75; 6878 ctx.fill(); 6879 ctx.stroke(); 6880 ctx.globalAlpha = 1; 6881 ctx.fillStyle = ctx.strokeStyle = color || '#963'; 6882 ctx.lineWidth = 5; 6883 ctx.beginPath(); 6884 ctx.moveTo(0,r/4); 6885 ctx.lineTo(0,-r/2); 6886 ctx.lineTo(r/3,r/2); 6887 ctx.lineTo(0,r/4); 6888 ctx.fill(); 6889 ctx.stroke(); 6890 ctx.beginPath(); 6891 ctx.moveTo(0,r/4); 6892 ctx.lineTo(0,-r/2); 6893 ctx.lineTo(-r/3,r/2); 6894 ctx.lineTo(0,r/4); 6895 ctx.stroke(); 6896 return canvas; 6897}; 6898/** Create a default image. 6899 * @param {number} s the size of the compass 6900 * @private 6901 */ 6902ol.control.Compass.prototype.defaultCompass_ = function (s, color) { 6903 var canvas = document.createElement('canvas'); 6904 var ctx = canvas.getContext("2d"); 6905 s = canvas.width = canvas.height = s || 150; 6906 var r = s/2; 6907 var r2 = 0.22*r; 6908 function draw (r, r2) { 6909 ctx.fillStyle = color ||"#963"; 6910 ctx.beginPath(); 6911 ctx.moveTo (0,0); 6912 ctx.lineTo (r,0); ctx.lineTo (r2,r2); ctx.moveTo (0,0); 6913 ctx.lineTo (-r,0); ctx.lineTo (-r2,-r2); ctx.moveTo (0,0); 6914 ctx.lineTo (0,r); ctx.lineTo (-r2,r2); ctx.moveTo (0,0); 6915 ctx.lineTo (0,-r); ctx.lineTo (r2,-r2); ctx.moveTo (0,0); 6916 ctx.fill(); 6917 ctx.stroke(); 6918 } 6919 function draw2 (r, r2) { 6920 ctx.globalCompositeOperation = "destination-out"; 6921 ctx.fillStyle = "#fff"; 6922 ctx.beginPath(); 6923 ctx.moveTo (0,0); 6924 ctx.lineTo (r,0); ctx.lineTo (r2,-r2); ctx.moveTo (0,0); 6925 ctx.lineTo (-r,0); ctx.lineTo (-r2,r2); ctx.moveTo (0,0); 6926 ctx.lineTo (0,r); ctx.lineTo (r2,r2); ctx.moveTo (0,0); 6927 ctx.lineTo (0,-r); ctx.lineTo (-r2,-r2); ctx.moveTo (0,0); 6928 ctx.fill(); 6929 ctx.globalCompositeOperation="source-over"; 6930 ctx.beginPath(); 6931 ctx.moveTo (0,0); 6932 ctx.lineTo (r,0); ctx.lineTo (r2,-r2); ctx.moveTo (0,0); 6933 ctx.lineTo (-r,0); ctx.lineTo (-r2,r2); ctx.moveTo (0,0); 6934 ctx.lineTo (0,r); ctx.lineTo (r2,r2); ctx.moveTo (0,0); 6935 ctx.lineTo (0,-r); ctx.lineTo (-r2,-r2); ctx.moveTo (0,0); 6936 ctx.stroke(); 6937 } 6938 ctx.translate(r,r); 6939 ctx.strokeStyle = color || "#963"; 6940 ctx.lineWidth = 1.5; 6941 ctx.beginPath(); 6942 ctx.arc (0,0, s*0.41, 0, 2*Math.PI); 6943 ctx.arc (0,0, s*0.44, 0, 2*Math.PI); 6944 ctx.stroke(); 6945 ctx.rotate(Math.PI/4) 6946 draw (r*0.9, r2*0.8); 6947 draw2 (r*0.9, r2*0.8); 6948 ctx.rotate(-Math.PI/4) 6949 draw (r, r2); 6950 draw2 (r, r2); 6951 return canvas; 6952}; 6953/** Get control visibility 6954 * @return {boolean} 6955 */ 6956ol.control.Compass.prototype.getVisible = function() { 6957 return ol.ext.element.getStyle(this.element, 'display') === 'block'; 6958}; 6959/** Set visibility 6960 * @param {boolean} b 6961 */ 6962ol.control.Compass.prototype.setVisible = function(b) { 6963 if (b) this.element.classList.add('ol-visible'); 6964 else this.element.classList.remove('ol-visible'); 6965 if (this.getMap()) this.getMap().render(); 6966}; 6967/** Draw compass 6968* @param {ol.event} e postcompose event 6969* @private 6970*/ 6971ol.control.Compass.prototype._draw = function(e) { 6972 var ctx = this.getContext(e); 6973 if (!ctx || !this.getVisible()) return; 6974 var canvas = ctx.canvas; 6975 // 8 angles 6976 var i, da = []; 6977 for (i=0; i<8; i++) da[i] = [ Math.cos(Math.PI*i/8), Math.sin(Math.PI*i/8) ]; 6978 // Retina device 6979 var ratio = e.frameState.pixelRatio; 6980 ctx.save(); 6981 ctx.scale(ratio,ratio); 6982 var w = this.element.clientWidth;
6983 var h = this.element.clientHeight; 6984 var pos = {left: this.element.offsetLeft, top: this.element.offsetTop}; 6985 var compass = this.img_; 6986 var rot = e.frameState.viewState.rotation; 6987 ctx.beginPath(); 6988 ctx.translate(pos.left+w/2, pos.top+h/2); 6989 if (this.get('rotateVithView')) ctx.rotate(rot); 6990 if (this.getStroke().getWidth()) { 6991 ctx.beginPath(); 6992 ctx.strokeStyle = this.getStroke().getColor(); 6993 ctx.lineWidth = this.getStroke().getWidth(); 6994 var m = Math.max(canvas.width, canvas.height); 6995 for (i=0; i<8; i++) { 6996 ctx.moveTo (-da[i][0]*m, -da[i][1]*m); 6997 ctx.lineTo (da[i][0]*m, da[i][1]*m); 6998 } 6999 ctx.stroke(); 7000 } 7001 if (compass.width) { 7002 ctx.drawImage (compass, -w/2, -h/2, w, h); 7003 } 7004 ctx.closePath(); 7005 ctx.restore(); 7006}; 7007 7008/** 7009 * @classdesc 7010 * Application dialog 7011 * @extends {ol.control.Control} 7012 * @constructor 7013 * @param {*} options 7014 * @param {string} options.className 7015 * @param {ol.Map} options.map the map to place the dialog inside 7016 * @param {Element} options.target target to place the dialog 7017 * @param {boolean} options.fullscreen view dialog fullscreen (same as options.target = document.body) 7018 * @param {boolean} options.zoom add a zoom effect 7019 * @param {boolean} options.closeBox add a close button 7020 * @param {number} options.max if not null add a progress bar to the dialog, default null 7021 * @param {boolean} options.hideOnClick close dialog when click 7022 * @param {boolean} options.hideOnBack close dialog when click the background 7023 * @param {boolean} options.closeOnSubmit Prevent closing the dialog on submit 7024 */ 7025ol.control.Dialog = function(options) { 7026 options = options || {}; 7027 if (options.fullscreen) options.target = document.body; 7028 // Constructor 7029 var element = ol.ext.element.create('DIV', { 7030 className: ((options.className || '') + (options.zoom ? ' ol-zoom':'') + ' ol-ext-dialog').trim(), 7031 click: function(e) { 7032 if (this.get('hideOnBack') && e.target===element) this.close(); 7033 if (this.get('hideOnClick')) this.close(); 7034 }.bind(this) 7035 }); 7036 // form 7037 var form = ol.ext.element.create('FORM', { 7038 on: { 7039 submit: this._onButton('submit') 7040 }, 7041 parent: element 7042 }); 7043 // Title 7044 ol.ext.element.create('H2', { 7045 parent: form 7046 }); 7047 // Close box 7048 ol.ext.element.create('DIV', { 7049 className: 'ol-closebox', 7050 click: this._onButton('cancel'), 7051 parent: form 7052 }); 7053 // Content 7054 ol.ext.element.create('DIV', { 7055 className: 'ol-content', 7056 parent: form 7057 }); 7058 // Progress 7059 this._progress = ol.ext.element.create('DIV', { 7060 style: { display: 'none' }, 7061 parent: form 7062 }); 7063 var bar = ol.ext.element.create('DIV', { 7064 className: 'ol-progress-bar', 7065 parent: this._progress 7066 }); 7067 this._progressbar = ol.ext.element.create('DIV', { 7068 parent: bar 7069 }); 7070 this._progressMessage = ol.ext.element.create('DIV', { 7071 className: 'ol-progress-message', 7072 parent: this._progress 7073 }); 7074 // Buttons 7075 ol.ext.element.create('DIV', { 7076 className: 'ol-buttons', 7077 parent: form 7078 }); 7079 ol.control.Control.call(this, { 7080 element: element, 7081 target: options.target 7082 }); 7083 this.set('closeBox', options.closeBox !== false); 7084 this.set('zoom', !!options.zoom); 7085 this.set('hideOnClick', !!options.hideOnClick); 7086 this.set('hideOnBack', !!options.hideOnBack); 7087 this.set('className', element.className); 7088 this.set('closeOnSubmit', options.closeOnSubmit); 7089 this.set('buttons', options.buttons); 7090 this.setContent(options) 7091}; 7092ol.ext.inherits(ol.control.Dialog, ol.control.Control); 7093/** Show a new dialog 7094 * @param { * | Element | string } options options or a content to show 7095 * @param {Element | String} options.content dialog content 7096 * @param {string} options.title title of the dialog 7097 * @param {string} options.className dialog class name 7098 * @param {number} options.autoclose a delay in ms before auto close 7099 * @param {boolean} options.hideOnBack close dialog when click the background 7100 * @param {number} options.max if not null add a progress bar to the dialog 7101 * @param {number} options.progress set the progress bar value 7102 * @param {Object} options.buttons a key/value list of button to show 7103 * @param {function} [options.onButton] a function that takes the button id and a list of input by className 7104 */ 7105ol.control.Dialog.prototype.show = function(options) { 7106 if (options) { 7107 if (options instanceof Element || typeof(options) === 'string') { 7108 options = { content: options }; 7109 } 7110 this.setContent(options); 7111 } 7112 this.element.classList.add('ol-visible'); 7113 var input = this.element.querySelector('input[type="text"],input[type="search"],input[type="number"]'); 7114 if (input) input.focus(); 7115 this.dispatchEvent ({ type: 'show' }); 7116 if (options) { 7117 // Auto close 7118 if (options.autoclose) { 7119 var listener = setTimeout(function() { this.hide() }.bind(this), options.autoclose); 7120 this.once('hide', function(){ 7121 clearTimeout(listener); 7122 }); 7123 } 7124 // hideOnBack 7125 if (options.hideOnBack) { 7126 // save value 7127 var value = this.get('hideOnBack'); 7128 this.set('hideOnBack', true); 7129 this.once('hide', function() { 7130 this.set('hideOnBack', value); 7131 }.bind(this)); 7132 } 7133 } 7134}; 7135/** Open the dialog 7136 */ 7137ol.control.Dialog.prototype.open = function() { 7138 this.show(); 7139}; 7140/** Set the dialog content 7141 * @param {Element | String} content dialog content 7142 */ 7143 ol.control.Dialog.prototype.setContentMessage = function(content) { 7144 if (content !== undefined) { 7145 var elt = this.getContentElement(); 7146 if (content instanceof Element) ol.ext.element.setHTML(elt, ''); 7147 ol.ext.element.setHTML(elt, content || ''); 7148 } 7149}; 7150/** Set the dialog title 7151 * @param {Element | String} content dialog content 7152 */ 7153ol.control.Dialog.prototype.setTitle = function(title) { 7154 var form = this.element.querySelector('form'); 7155 form.querySelector('h2').innerText = title || ''; 7156 if (title) { 7157 form.classList.add('ol-title'); 7158 } else { 7159 form.classList.remove('ol-title'); 7160 } 7161}; 7162/** Set the dialog content 7163 * @param {*} options 7164 * @param {Element | String} options.content dialog content 7165 * @param {string} options.title title of the dialog 7166 * @param {string} options.className dialog class name 7167 * @param {number} options.max if not null add a progress bar to the dialog 7168 * @param {number} options.progress set the progress bar value 7169 * @param {Object} options.buttons a key/value list of button to show 7170 * @param {function} [options.onButton] a function that takes the button id and a list of input by className 7171 */ 7172ol.control.Dialog.prototype.setContent = function(options) { 7173 if (!options) return; 7174 this.element.className = this.get('className'); 7175 if (typeof(options) === 'string') options = { content: options }; 7176 options = options || {}; 7177 this.setProgress(false); 7178 if (options.max) this.setProgress(0, options.max); 7179 if (options.progress !== undefined) this.setProgress(options.progress); 7180 //this.element.className = 'ol-ext-dialog' + (this.get('zoom') ? ' ol-zoom' : ''); 7181 if (this.get('zoom')) this.element.classList.add('ol-zoom'); 7182 else this.element.classList.remove('ol-zoom'); 7183 if (options.className) { 7184 options.className.split(' ').forEach(function(c) { 7185 this.element.classList.add(c); 7186 }.bind(this)); 7187 } 7188 var form = this.element.querySelector('form'); 7189 // Content 7190 if (options.content !== undefined) { 7191 if (options.content instanceof Element) ol.ext.element.setHTML(form.querySelector('.ol-content'), ''); 7192 ol.ext.element.setHTML(form.querySelector('.ol-content'), options.content || ''); 7193 } 7194 // Title 7195 this.setTitle(options.title); 7196 // Closebox 7197 if (options.closeBox || (this.get('closeBox') && options.closeBox !== false)) { 7198 form.classList.add('ol-closebox'); 7199 } else { 7200 form.classList.remove('ol-closebox'); 7201 } 7202 // Buttons 7203 var buttons = this.element.querySelector('.ol-buttons'); 7204 buttons.innerHTML = ''; 7205 var btn = options.buttons || this.get('buttons'); 7206 if (btn) { 7207 form.classList.add('ol-button'); 7208 for (var i in btn) { 7209 ol.ext.element.create ('INPUT', { 7210 type: (i==='submit' ? 'submit':'button'), 7211 value: btn[i], 7212 click: this._onButton(i, options.onButton), 7213 parent: buttons 7214 }); 7215 } 7216 } else { 7217 form.classList.remove('ol-button'); 7218 } 7219}; 7220/** Get dialog content element 7221 * @returns {Element} 7222 */ 7223ol.control.Dialog.prototype.getContentElement = function() { 7224 return this.element.querySelector('form .ol-content') 7225}; 7226/** Set progress 7227 * @param {number|boolean} val the progress value or false to hide the progressBar 7228 * @param {number} max 7229 * @param {string|element} message 7230 */ 7231ol.control.Dialog.prototype.setProgress = function(val, max, message) { 7232 if (val===false) { 7233 ol.ext.element.setStyle(this._progress, { display: 'none' }) 7234 return; 7235 } 7236 if (max > 0) { 7237 this.set('max', Number(max)); 7238 } else { 7239 max = this.get('max'); 7240 } 7241 if (!max) { 7242 ol.ext.element.setStyle(this._progress, { display: 'none' }) 7243 } else { 7244 var p = Math.round(val / max * 100); 7245 ol.ext.element.setStyle(this._progress, { display: '' }) 7246 this._progressbar.className = p ? '' : 'notransition'; 7247 ol.ext.element.setStyle(this._progressbar, { width: p+'%' }) 7248 } 7249 this._progressMessage.innerHTML = ''; 7250 ol.ext.element.setHTML(this._progressMessage, message || ''); 7251}; 7252/** Returns a function to do something on button click 7253 * @param {strnig} button button id 7254 * @param {function} callback 7255 * @returns {function} 7256 * @private 7257 */ 7258 ol.control.Dialog.prototype._onButton = function(button, callback) { 7259 // Dispatch a button event 7260 var fn = function(e) { 7261 e.preventDefault(); 7262 if (button!=='submit' || this.get('closeOnSubmit')!==false) this.hide(); 7263 var inputs = this.getInputs(); 7264 this.dispatchEvent ({ type: 'button', button: button, inputs: inputs }); 7265 if (typeof(callback) === 'function') callback(button, inputs); 7266 }.bind(this); 7267 return fn; 7268}; 7269/** Get inputs, textarea an select of the dialog by classname 7270 * @return {Object} a {key:value} list of Elements by classname 7271 */ 7272ol.control.Dialog.prototype.getInputs = function() { 7273 var inputs = {}; 7274 ['input', 'textarea', 'select'].forEach(function(type) { 7275 this.element.querySelectorAll('form '+type).forEach (function(input) { 7276 if (input.className) { 7277 input.className.split(' ').forEach(function(n) { 7278 inputs[n] = input; 7279 }) 7280 } 7281 }); 7282 }.bind(this));
7283 return inputs; 7284}; 7285/** Close the dialog 7286 */ 7287ol.control.Dialog.prototype.hide = function() { 7288 this.element.classList.remove('ol-visible'); 7289 this.dispatchEvent ({ type: 'hide' }); 7290}; 7291/** Close the dialog 7292 * @method Dialog.close 7293 * @return {bool} true if a dialog is closed 7294 */ 7295ol.control.Dialog.prototype.close = ol.control.Dialog.prototype.hide; 7296/** The dialog is shown 7297 * @return {bool} true if a dialog is open 7298 */ 7299ol.control.Dialog.prototype.isOpen = function() { 7300 return (this.element.classList.contains('ol-visible')); 7301}; 7302 7303/** A simple control to disable all actions on the map. 7304 * The control will create an invisible div over the map. 7305 * @constructor 7306 * @extends {ol.control.Control} 7307 * @param {Object=} options Control options. 7308 * @param {String} options.class class of the control 7309 * @param {String} options.html html code to insert in the control 7310 * @param {bool} options.on the control is on 7311 * @param {function} options.toggleFn callback when control is clicked 7312 */ 7313ol.control.Disable = function(options) 7314{ options = options||{}; 7315 var element = document.createElement("div"); 7316 element.className = (options.className||""+' ol-disable ol-unselectable ol-control').trim(); 7317 var stylesOptions = { top:"0px", left:"0px", right:"0px", bottom:"0px", "zIndex":10000, background:"none", display:"none" }; 7318 Object.keys(stylesOptions).forEach(function(styleKey) { 7319 element.style[styleKey] = stylesOptions[styleKey]; 7320 }); 7321 ol.control.Control.call(this, 7322 { element: element 7323 }); 7324} 7325ol.ext.inherits(ol.control.Disable, ol.control.Control); 7326/** Test if the control is on 7327 * @return {bool} 7328 * @api stable 7329 */ 7330ol.control.Disable.prototype.isOn = function() 7331{ return this.element.classList.contains("ol-disable"); 7332} 7333/** Disable all action on the map 7334 * @param {bool} b, default false 7335 * @api stable 7336 */ 7337ol.control.Disable.prototype.disableMap = function(b) 7338{ if (b) 7339 { this.element.classList.add("ol-enable").show(); 7340 } 7341 else 7342 { this.element.classList.remove("ol-enable").hide(); 7343 } 7344} 7345 7346/** Control bar for editing in a layer 7347 * @constructor 7348 * @extends {ol.control.Bar} 7349 * @fires info 7350 * @param {Object=} options Control options. 7351 * @param {String} options.className class of the control 7352 * @param {String} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 7353 * @param {boolean} options.edition false to remove the edition tools, default true 7354 * @param {Object} options.interactions List of interactions to add to the bar 7355 * ie. Select, Delete, Info, DrawPoint, DrawLine, DrawPolygon 7356 * Each interaction can be an interaction or true (to get the default one) or false to remove it from bar 7357 * @param {ol.source.Vector} options.source Source for the drawn features. 7358 */ 7359ol.control.EditBar = function(options) { 7360 options = options || {}; 7361 options.interactions = options.interactions || {}; 7362 // New bar 7363 ol.control.Bar.call(this, { 7364 className: (options.className ? options.className+' ': '') + 'ol-editbar', 7365 toggleOne: true, 7366 target: options.target 7367 }); 7368 this._source = options.source; 7369 // Add buttons / interaction 7370 this._interactions = {}; 7371 this._setSelectInteraction(options); 7372 if (options.edition!==false) this._setEditInteraction(options); 7373 this._setModifyInteraction(options); 7374}; 7375ol.ext.inherits(ol.control.EditBar, ol.control.Bar); 7376/** 7377 * Set the map instance the control is associated with 7378 * and add its controls associated to this map. 7379 * @param {_ol_Map_} map The map instance. 7380 */ 7381ol.control.EditBar.prototype.setMap = function (map) { 7382 if (this.getMap()) { 7383 if (this._interactions.Delete) this.getMap().removeInteraction(this._interactions.Delete); 7384 if (this._interactions.ModifySelect) this.getMap().removeInteraction(this._interactions.ModifySelect); 7385 } 7386 ol.control.Bar.prototype.setMap.call(this, map); 7387 if (this.getMap()) { 7388 if (this._interactions.Delete) this.getMap().addInteraction(this._interactions.Delete); 7389 if (this._interactions.ModifySelect) this.getMap().addInteraction(this._interactions.ModifySelect); 7390 } 7391}; 7392/** Get an interaction associated with the bar 7393 * @param {string} name 7394 */ 7395ol.control.EditBar.prototype.getInteraction = function (name) { 7396 return this._interactions[name]; 7397}; 7398/** Get the option title */ 7399ol.control.EditBar.prototype._getTitle = function (option) { 7400 if (option) { 7401 if (option.get) return option.get('title'); 7402 else if (typeof(option) === 'string') return option; 7403 else return option.title; 7404 } 7405}; 7406/** Get the option html */ 7407ol.control.EditBar.prototype._getHtml = function (option) { 7408 return (option && option.html) ? option.html : option; 7409}; 7410/** Add selection tool: 7411 * 1. a toggle control with a select interaction 7412 * 2. an option bar to delete / get information on the selected feature 7413 * @private 7414 */ 7415ol.control.EditBar.prototype._setSelectInteraction = function (options) { 7416 var self = this; 7417 // Sub bar 7418 var sbar = new ol.control.Bar(); 7419 var selectCtrl; 7420 // Delete button 7421 if (options.interactions.Delete !== false) { 7422 if (options.interactions.Delete instanceof ol.interaction.Delete) { 7423 this._interactions.Delete = options.interactions.Delete; 7424 } else { 7425 this._interactions.Delete = new ol.interaction.Delete(); 7426 } 7427 var del = this._interactions.Delete; 7428 del.setActive(false); 7429 if (this.getMap()) this.getMap().addInteraction(del); 7430 sbar.addControl (new ol.control.Button({ 7431 className: 'ol-delete', 7432 title: this._getTitle(options.interactions.Delete) || "Delete", 7433 name: 'Delete', 7434 handleClick: function(e) { 7435 // Delete selection 7436 del.delete(selectCtrl.getInteraction().getFeatures()); 7437 var evt = { 7438 type: 'select', 7439 selected: [], 7440 deselected: selectCtrl.getInteraction().getFeatures().getArray().slice(), 7441 mapBrowserEvent: e.mapBrowserEvent 7442 }; 7443 selectCtrl.getInteraction().getFeatures().clear(); 7444 selectCtrl.getInteraction().dispatchEvent(evt); 7445 } 7446 })); 7447 } 7448 // Info button 7449 if (options.interactions.Info !== false) { 7450 sbar.addControl (new ol.control.Button({ 7451 className: 'ol-info', 7452 name: 'Info', 7453 title: this._getTitle(options.interactions.Info) || "Show informations", 7454 handleClick: function() {
7455 self.dispatchEvent({ 7456 type: 'info', 7457 features: selectCtrl.getInteraction().getFeatures() 7458 }); 7459 } 7460 })); 7461 } 7462 // Select button 7463 if (options.interactions.Select !== false) { 7464 if (options.interactions.Select instanceof ol.interaction.Select) { 7465 this._interactions.Select = options.interactions.Select 7466 } else { 7467 this._interactions.Select = new ol.interaction.Select({ 7468 condition: ol.events.condition.click 7469 }); 7470 } 7471 var sel = this._interactions.Select; 7472 selectCtrl = new ol.control.Toggle({ 7473 className: 'ol-selection', 7474 name: 'Select', 7475 title: this._getTitle(options.interactions.Select) || "Select", 7476 interaction: sel, 7477 bar: sbar.getControls().length ? sbar : undefined, 7478 autoActivate:true, 7479 active:true 7480 }); 7481 this.addControl(selectCtrl); 7482 sel.on('change:active', function() { 7483 sel.getFeatures().clear(); 7484 }); 7485 } 7486}; 7487/** Add editing tools 7488 * @private 7489 */ 7490ol.control.EditBar.prototype._setEditInteraction = function (options) { 7491 if (options.interactions.DrawPoint !== false) { 7492 if (options.interactions.DrawPoint instanceof ol.interaction.Draw) { 7493 this._interactions.DrawPoint = options.interactions.DrawPoint; 7494 } else { 7495 this._interactions.DrawPoint = new ol.interaction.Draw({ 7496 type: 'Point', 7497 source: this._source 7498 }); 7499 } 7500 var pedit = new ol.control.Toggle({ 7501 className: 'ol-drawpoint', 7502 name: 'DrawPoint', 7503 title: this._getTitle(options.interactions.DrawPoint) || 'Point', 7504 interaction: this._interactions.DrawPoint 7505 }); 7506 this.addControl ( pedit ); 7507 } 7508 if (options.interactions.DrawLine !== false) { 7509 if (options.interactions.DrawLine instanceof ol.interaction.Draw) { 7510 this._interactions.DrawLine = options.interactions.DrawLine 7511 } else { 7512 this._interactions.DrawLine = new ol.interaction.Draw ({ 7513 type: 'LineString', 7514 source: this._source, 7515 // Count inserted points 7516 geometryFunction: function(coordinates, geometry) { 7517 if (geometry) geometry.setCoordinates(coordinates); 7518 else geometry = new ol.geom.LineString(coordinates); 7519 this.nbpts = geometry.getCoordinates().length; 7520 return geometry; 7521 } 7522 }); 7523 } 7524 var ledit = new ol.control.Toggle({ 7525 className: 'ol-drawline', 7526 title: this._getTitle(options.interactions.DrawLine) || 'LineString', 7527 name: 'DrawLine', 7528 interaction: this._interactions.DrawLine, 7529 // Options bar associated with the control 7530 bar: new ol.control.Bar ({ 7531 controls:[ 7532 new ol.control.TextButton({ 7533 html: this._getTitle(options.interactions.UndoDraw) || 'undo', 7534 title: this._getTitle(options.interactions.UndoDraw) || "delete last point", 7535 handleClick: function() { 7536 if (ledit.getInteraction().nbpts>1) ledit.getInteraction().removeLastPoint(); 7537 } 7538 }), 7539 new ol.control.TextButton ({ 7540 html: this._getTitle(options.interactions.FinishDraw) || 'finish', 7541 title: this._getTitle(options.interactions.FinishDraw) || "finish", 7542 handleClick: function() { 7543 // Prevent null objects on finishDrawing 7544 if (ledit.getInteraction().nbpts>2) ledit.getInteraction().finishDrawing(); 7545 } 7546 }) 7547 ] 7548 }) 7549 }); 7550 this.addControl ( ledit ); 7551 } 7552 if (options.interactions.DrawPolygon !== false) { 7553 if (options.interactions.DrawPolygon instanceof ol.interaction.Draw){ 7554 this._interactions.DrawPolygon = options.interactions.DrawPolygon 7555 } else { 7556 this._interactions.DrawPolygon = new ol.interaction.Draw ({ 7557 type: 'Polygon', 7558 source: this._source, 7559 // Count inserted points 7560 geometryFunction: function(coordinates, geometry) { 7561 this.nbpts = coordinates[0].length; 7562 if (geometry) geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])]); 7563 else geometry = new ol.geom.Polygon(coordinates); 7564 return geometry; 7565 } 7566 }); 7567 } 7568 this._setDrawPolygon( 7569 'ol-drawpolygon', 7570 this._interactions.DrawPolygon, 7571 this._getTitle(options.interactions.DrawPolygon) || 'Polygon', 7572 'DrawPolygon', 7573 options 7574 ); 7575 } 7576 // Draw hole 7577 if (options.interactions.DrawHole !== false) { 7578 if (options.interactions.DrawHole instanceof ol.interaction.DrawHole){ 7579 this._interactions.DrawHole = options.interactions.DrawHole; 7580 } else { 7581 this._interactions.DrawHole = new ol.interaction.DrawHole (); 7582 } 7583 this._setDrawPolygon( 7584 'ol-drawhole', 7585 this._interactions.DrawHole, 7586 this._getTitle(options.interactions.DrawHole) || 'Hole', 7587 'DrawHole', 7588 options 7589 ); 7590 } 7591 // Draw regular
7592 if (options.interactions.DrawRegular !== false) { 7593 var label = { pts: 'pts', circle: 'circle' }; 7594 if (options.interactions.DrawRegular instanceof ol.interaction.DrawRegular) { 7595 this._interactions.DrawRegular = options.interactions.DrawRegular; 7596 label.pts = this._interactions.DrawRegular.get('ptsLabel') || label.pts; 7597 label.circle = this._interactions.DrawRegular.get('circleLabel') || label.circle; 7598 } else { 7599 this._interactions.DrawRegular = new ol.interaction.DrawRegular ({ 7600 source: this._source, 7601 sides: 4 7602 }); 7603 if (options.interactions.DrawRegular) { 7604 label.pts = options.interactions.DrawRegular.ptsLabel || label.pts; 7605 label.circle = options.interactions.DrawRegular.circleLabel || label.circle; 7606 } 7607 } 7608 var regular = this._interactions.DrawRegular; 7609 var div = document.createElement('DIV'); 7610 var down = ol.ext.element.create('DIV', { parent: div }); 7611 ol.ext.element.addListener(down, ['click', 'touchstart'], function() { 7612 var sides = regular.getSides() -1; 7613 if (sides < 2) sides = 2; 7614 regular.setSides (sides); 7615 text.textContent = sides>2 ? sides+' '+label.pts : label.circle; 7616 }.bind(this)); 7617 var text = ol.ext.element.create('TEXT', { html:'4 '+label.pts, parent: div }); 7618 var up = ol.ext.element.create('DIV', { parent: div }); 7619 ol.ext.element.addListener(up, ['click', 'touchstart'], function() { 7620 var sides = regular.getSides() +1; 7621 if (sides<3) sides=3; 7622 regular.setSides(sides); 7623 text.textContent = sides+' '+label.pts; 7624 }.bind(this)); 7625 var ctrl = new ol.control.Toggle({ 7626 className: 'ol-drawregular', 7627 title: this._getTitle(options.interactions.DrawRegular) || 'Regular', 7628 name: 'DrawRegular', 7629 interaction: this._interactions.DrawRegular, 7630 // Options bar associated with the control 7631 bar: new ol.control.Bar ({ 7632 controls:[ 7633 new ol.control.TextButton({ 7634 html: div 7635 }) 7636 ] 7637 }) 7638 }); 7639 this.addControl (ctrl); 7640 } 7641}; 7642/** 7643 * @private 7644 */ 7645ol.control.EditBar.prototype._setDrawPolygon = function (className, interaction, title, name, options) { 7646 var fedit = new ol.control.Toggle ({ 7647 className: className, 7648 name: name, 7649 title: title, 7650 interaction: interaction, 7651 // Options bar associated with the control 7652 bar: new ol.control.Bar({ 7653 controls:[ 7654 new ol.control.TextButton ({ 7655 html: this._getTitle(options.interactions.UndoDraw) || 'undo', 7656 title: this._getTitle(options.interactions.UndoDraw) || 'undo last point', 7657 handleClick: function(){ 7658 if (fedit.getInteraction().nbpts>1) fedit.getInteraction().removeLastPoint(); 7659 } 7660 }), 7661 new ol.control.TextButton({ 7662 html: this._getTitle(options.interactions.FinishDraw) || 'finish', 7663 title: this._getTitle(options.interactions.FinishDraw) || 'finish', 7664 handleClick: function() { 7665 // Prevent null objects on finishDrawing 7666 if (fedit.getInteraction().nbpts>3) fedit.getInteraction().finishDrawing(); 7667 } 7668 }) 7669 ] 7670 }) 7671 }); 7672 this.addControl (fedit); 7673 return fedit; 7674}; 7675/** Add modify tools 7676 * @private 7677 */ 7678ol.control.EditBar.prototype._setModifyInteraction = function (options) { 7679 // Modify on selected features 7680 if (options.interactions.ModifySelect !== false && options.interactions.Select !== false) { 7681 if (options.interactions.ModifySelect instanceof ol.interaction.ModifyFeature) { 7682 this._interactions.ModifySelect = options.interactions.ModifySelect; 7683 } else { 7684 this._interactions.ModifySelect = new ol.interaction.ModifyFeature({ 7685 features: this.getInteraction('Select').getFeatures() 7686 }); 7687 } 7688 if (this.getMap()) this.getMap().addInteraction(this._interactions.ModifySelect); 7689 // Activate with select 7690 this._interactions.ModifySelect.setActive(this._interactions.Select.getActive()); 7691 this._interactions.Select.on('change:active', function() { 7692 this._interactions.ModifySelect.setActive(this._interactions.Select.getActive()); 7693 }.bind(this)); 7694 } 7695 if (options.interactions.Transform !== false) { 7696 if (options.interactions.Transform instanceof ol.interaction.Transform) { 7697 this._interactions.Transform = options.interactions.Transform; 7698 } else { 7699 this._interactions.Transform = new ol.interaction.Transform ({ 7700 addCondition: ol.events.condition.shiftKeyOnly 7701 }); 7702 } 7703 var transform = new ol.control.Toggle ({ 7704 html: '<i></i>', 7705 className: 'ol-transform', 7706 title: this._getTitle(options.interactions.Transform) || 'Transform', 7707 name: 'Transform', 7708 interaction: this._interactions.Transform 7709 }); 7710 this.addControl (transform); 7711 } 7712 if (options.interactions.Split !== false) { 7713 if (options.interactions.Split instanceof ol.interaction.Split) { 7714 this._interactions.Split = options.interactions.Split; 7715 } else { 7716 this._interactions.Split = new ol.interaction.Split ({ 7717 sources: this._source 7718 }); 7719 } 7720 var split = new ol.control.Toggle ({ 7721 className: 'ol-split', 7722 title: this._getTitle(options.interactions.Split) || 'Split', 7723 name: 'Split', 7724 interaction: this._interactions.Split 7725 }); 7726 this.addControl (split); 7727 }
7728 if (options.interactions.Offset !== false) { 7729 if (options.interactions.Offset instanceof ol.interaction.Offset) { 7730 this._interactions.Offset = options.interactions.Offset; 7731 } else { 7732 this._interactions.Offset = new ol.interaction.Offset ({ 7733 source: this._source 7734 }); 7735 } 7736 var offset = new ol.control.Toggle ({ 7737 html: '<i></i>', 7738 className: 'ol-offset', 7739 title: this._getTitle(options.interactions.Offset) || 'Offset', 7740 name: 'Offset', 7741 interaction: this._interactions.Offset 7742 }); 7743 this.addControl (offset); 7744 } 7745}; 7746 7747/* Copyright (c) 2017 Jean-Marc VIGLINO, 7748 released under the CeCILL-B license (French BSD license) 7749 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 7750*/ 7751/** A simple gauge control to display level information on the map. 7752 * 7753 * @constructor 7754 * @extends {ol.control.Control} 7755 * @param {Object=} options Control options. 7756 * @param {String} options.className class of the control 7757 * @param {String} options.title title of the control 7758 * @param {number} options.max maximum value, default 100; 7759 * @param {number} options.val the value, default 0 7760 */ 7761ol.control.Gauge = function(options) { 7762 options = options || {}; 7763 var element = ol.ext.element.create('DIV', { 7764 className: ((options.className||"") + ' ol-gauge ol-unselectable ol-control').trim() 7765 }); 7766 this.title_ = ol.ext.element.create('SPAN', { 7767 parent: element 7768 }); 7769 var div = ol.ext.element.create('DIV', { 7770 parent: element 7771 }); 7772 this.gauge_ = ol.ext.element.create('BUTTON', { 7773 type: 'button', 7774 style: { width: '0px' }, 7775 parent: div 7776 }); 7777 /* 7778 var element = document.createElement("div"); 7779 element.className = ((options.className||"") + ' ol-gauge ol-unselectable ol-control').trim(); 7780 this.title_ = document.createElement("span"); 7781 element.appendChild(this.title_); 7782 this.gauge_ = document.createElement("button"); 7783 this.gauge_.setAttribute('type','button'); 7784 element.appendChild(document.createElement("div").appendChild(this.gauge_)) 7785 this.gauge_.style.width = '0px'; 7786 */ 7787 ol.control.Control.call(this, { 7788 element: element, 7789 target: options.target 7790 }); 7791 this.setTitle(options.title); 7792 this.set("max", options.max||100); 7793 this.val(options.val); 7794}; 7795ol.ext.inherits(ol.control.Gauge, ol.control.Control); 7796/** Set the control title 7797* @param {string} title 7798*/ 7799ol.control.Gauge.prototype.setTitle = function(title) { 7800 this.title_.innerHTML = title||""; 7801 if (!title) this.title_.display = 'none'; 7802 else this.title_.display = ''; 7803}; 7804/** Set/get the gauge value 7805* @param {number|undefined} v the value or undefined to get it 7806* @return {number} the value 7807*/ 7808ol.control.Gauge.prototype.val = function(v) { 7809 if (v!==undefined) { 7810 this.val_ = v; 7811 this.gauge_.style.width = (v/this.get('max')*100)+"%"; 7812 } 7813 return this.val_; 7814}; 7815 7816/** Bookmark positions on ol maps. 7817 * 7818 * @constructor 7819 * @extends {ol.control.Control} 7820 * @fires add 7821 * @fires remove 7822 * @fires select 7823 * @param {} options Geobookmark's options 7824 * @param {string} options.className default ol-bookmark 7825 * @param {string | undefined} options.title Title to use for the button tooltip, default "Geobookmarks" 7826 * @param {string} options.placeholder input placeholder, default Add a new geomark... 7827 * @param {string} [options.deleteTitle='Suppr.'] title for delete buttons 7828 * @param {bool} options.editable enable modification, default true 7829 * @param {string} options.namespace a namespace to save the boolmark (if more than one on a page), default ol 7830 * @param {Array<any>} options.marks a list of default bookmarks: 7831 * @see [Geobookmark example](../../examples/control/map.control.geobookmark.html) 7832 * @example 7833var bm = new GeoBookmark ({ 7834 marks: { 7835 "Paris": {pos:ol.proj.transform([2.351828, 48.856578], 'EPSG:4326', 'EPSG:3857'), zoom:11, permanent: true }, 7836 "London": {pos:ol.proj.transform([-0.1275,51.507222], 'EPSG:4326', 'EPSG:3857'), zoom:12} 7837 } 7838}); 7839 */ 7840ol.control.GeoBookmark = function(options) { 7841 options = options || {}; 7842 var self = this; 7843 var element = document.createElement('div'); 7844 if (options.target) { 7845 element.className = options.className || "ol-bookmark"; 7846 } else { 7847 element.className = (options.className || "ol-bookmark") + 7848 " ol-unselectable ol-control ol-collapsed"; 7849 element.addEventListener("mouseleave", function() { 7850 if (input !== document.activeElement) { 7851 menu.style.display = 'none'; 7852 } 7853 }); 7854 // Show bookmarks on click 7855 this.button = ol.ext.element.create('BUTTON', { 7856 type: 'button', 7857 title: options.title || 'Geobookmarks', 7858 click: function() { 7859 var show = (menu.style.display === '' || menu.style.display === 'none'); 7860 menu.style.display = (show ? 'block': 'none'); 7861 if (show) this.setBookmarks(); 7862 }.bind(this) 7863 }); 7864 element.appendChild(this.button); 7865 } 7866 // The menu 7867 var menu = document.createElement('div'); 7868 element.appendChild(menu); 7869 var ul = document.createElement('ul'); 7870 menu.appendChild(ul); 7871 var input = document.createElement('input'); 7872 input.setAttribute("placeholder", options.placeholder || "Add a new geomark...") 7873 input.addEventListener("keydown", function(e) { 7874 e.stopPropagation(); 7875 if (e.keyCode === 13) { 7876 e.preventDefault(); 7877 var title = this.value; 7878 if (title) { 7879 self.addBookmark(title); 7880 this.value = '';
7881 self.dispatchEvent({ 7882 type: "add", 7883 name: title 7884 }); 7885 } 7886 menu.style.display = 'none'; 7887 } 7888 }); 7889 input.addEventListener("blur", function() { 7890 menu.style.display = 'none'; 7891 }); 7892 menu.appendChild(input); 7893 // Init 7894 ol.control.Control.call(this, { 7895 element: element, 7896 target: options.target 7897 }); 7898 this.on("propertychange", function(e) { 7899 if (e.key==='editable') { 7900 element.className = element.className.replace(" ol-editable",""); 7901 if (this.get('editable')) { 7902 element.className += " ol-editable"; 7903 } 7904 } 7905 // console.log(e); 7906 }.bind(this)); 7907 this.set("namespace", options.namespace || 'ol'); 7908 this.set("editable", options.editable !== false); 7909 this.set('deleteTitle', options.deleteTitle || 'Suppr.'); 7910 // Set default bmark 7911 var bmark = {}; 7912 try { 7913 if (localStorage[this.get('namespace')+"@bookmark"]) { 7914 bmark = JSON.parse(localStorage[this.get('namespace')+"@bookmark"]); 7915 } 7916 } catch(e) { console.warn('Failed to access localStorage...'); } 7917 if (options.marks) { 7918 for (var i in options.marks) { 7919 bmark[i] = options.marks[i]; 7920 } 7921 } 7922 this.setBookmarks(bmark); 7923}; 7924ol.ext.inherits(ol.control.GeoBookmark, ol.control.Control); 7925/** Set bookmarks 7926 * @param {} bmark a list of bookmarks, default retreave in the localstorage 7927 * @example 7928bm.setBookmarks({ 7929 "Paris": {pos:_ol_proj_.transform([2.351828, 48.856578], 'EPSG:4326', 'EPSG:3857'), zoom:11, permanent: true }, 7930 "London": {pos:_ol_proj_.transform([-0.1275,51.507222], 'EPSG:4326', 'EPSG:3857'), zoom:12} 7931}); 7932 */ 7933ol.control.GeoBookmark.prototype.setBookmarks = function(bmark) { 7934 if (!bmark) { 7935 bmark = {}; 7936 try { 7937 bmark = JSON.parse(localStorage[this.get('namespace')+"@bookmark"] || "{}"); 7938 } catch(e) { console.warn('Failed to access localStorage...'); } 7939 } 7940 var modify = this.get("editable"); 7941 var ul = this.element.querySelector("ul"); 7942 var menu = this.element.querySelector("div"); 7943 var self = this; 7944 ul.innerHTML = ''; 7945 for (var b in bmark) { 7946 var li = document.createElement('li'); 7947 li.textContent = b; 7948 li.setAttribute('data-bookmark', JSON.stringify(bmark[b])); 7949 li.setAttribute('data-name', b); 7950 li.addEventListener('click', function() { 7951 var bm = JSON.parse(this.getAttribute("data-bookmark")); 7952 self.getMap().getView().setCenter(bm.pos); 7953 self.getMap().getView().setZoom(bm.zoom); 7954 self.getMap().getView().setRotation(bm.rot || 0); 7955 menu.style.display = 'none'; 7956 self.dispatchEvent({ type: 'select', name: this.getAttribute("data-name"), bookmark: bm }); 7957 }); 7958 ul.appendChild(li); 7959 if (modify && !bmark[b].permanent) { 7960 var button = document.createElement('button'); 7961 button.setAttribute('data-name', b); 7962 button.setAttribute('type', 'button'); 7963 button.setAttribute('title', this.get('deleteTitle') ||'Suppr.'); 7964 button.addEventListener('click', function(e) { 7965 self.removeBookmark(this.getAttribute("data-name")); 7966 self.dispatchEvent({ type: "remove", name: this.getAttribute("data-name") }); 7967 e.stopPropagation(); 7968 }); 7969 li.appendChild(button); 7970 } 7971 } 7972 try { 7973 localStorage[this.get('namespace')+"@bookmark"] = JSON.stringify(bmark); 7974 } catch(e) { console.warn('Failed to access localStorage...'); } 7975}; 7976/** Get Geo bookmarks 7977 * @return {any} a list of bookmarks : { BM1:{pos:ol.coordinates, zoom: integer}, BM2:{pos:ol.coordinates, zoom: integer} } 7978 */ 7979ol.control.GeoBookmark.prototype.getBookmarks = function() { 7980 var bm = {}; 7981 try { 7982 bm = JSON.parse(localStorage[this.get('namespace')+"@bookmark"] || "{}"); 7983 } catch(e) { console.warn('Failed to access localStorage...'); } 7984 return bm; 7985}; 7986/** Remove a Geo bookmark 7987 * @param {string} name 7988 */ 7989ol.control.GeoBookmark.prototype.removeBookmark = function(name) { 7990 if (!name) { 7991 return; 7992 } 7993 var bmark = this.getBookmarks(); 7994 delete bmark[name]; 7995 this.setBookmarks(bmark); 7996}; 7997/** Add a new Geo bookmark (replace existing one if any) 7998 * @param {string} name name of the bookmark (display in the menu) 7999 * @param {*} options 8000 * @param {ol.coordinate} position default current position 8001 * @param {number} zoom default current map zoom 8002 * @param {number} rotation default current map rotation 8003 * @param {bool} permanent prevent from deletion, default false 8004 */ 8005ol.control.GeoBookmark.prototype.addBookmark = function(name, position, zoom, permanent) { 8006 if (!name) return; 8007 var options = position; 8008 var rot; 8009 if (options && options.position) { 8010 zoom = options.zoom; 8011 permanent = options.permanent; 8012 rot = options.rotation ; 8013 position = options.position; 8014 } else { 8015 rot = this.getMap().getView().getRotation(); 8016 } 8017 var bmark = this.getBookmarks(); 8018 // Don't override permanent bookmark 8019 if (bmark[name] && bmark[name].permanent) return; 8020 // Create or override 8021 bmark[name] = { 8022 pos: position || this.getMap().getView().getCenter(), 8023 zoom: zoom || this.getMap().getView().getZoom(), 8024 permanent: !!permanent 8025 }; 8026 if (rot) { 8027 bmark[name].rot = rot; 8028 } 8029 this.setBookmarks(bmark); 8030}; 8031 8032/* Copyright (c) 2016 Jean-Marc VIGLINO, 8033 released under the CeCILL-B license (French BSD license)
8034 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 8035*/ 8036/** Geolocation bar 8037 * The control bar is a container for other controls. It can be used to create toolbars. 8038 * Control bars can be nested and combined with ol.control.Toggle to handle activate/deactivate. 8039 * 8040 * @constructor 8041 * @extends {ol.control.Bar} 8042 * @param {Object=} options Control bar options. 8043 * @param {String} options.className class of the control 8044 * @param {String} options.centerLabel label for center button, default center 8045 * @param {String} options.position position of the control, default bottom-right 8046 */ 8047ol.control.GeolocationBar = function(options) { 8048 if (!options) options = {}; 8049 options.className = options.className || 'ol-geobar'; 8050 ol.control.Bar.call(this, options); 8051 this.setPosition(options.position || 'bottom-right'); 8052 var element = this.element; 8053 // Geolocation draw interaction 8054 var interaction = new ol.interaction.GeolocationDraw({ 8055 source: options.source, 8056 zoom: options.zoom, 8057 minZoom: options.minZoom, 8058 tolerance: options.tolerance, 8059 followTrack: options.followTrack, 8060 minAccuracy: options.minAccuracy || 10000 8061 }); 8062 this._geolocBt = new ol.control.Toggle ({ 8063 className: 'geolocBt', 8064 interaction: interaction, 8065 onToggle: function() { 8066 interaction.pause(true); 8067 interaction.setFollowTrack(options.followTrack); 8068 element.classList.remove('pauseTrack'); 8069 } 8070 }); 8071 this.addControl(this._geolocBt); 8072 this._geolocBt.setActive(false); 8073 // Buttons 8074 var bar = new ol.control.Bar(); 8075 this.addControl(bar); 8076 var centerBt = new ol.control.TextButton ({ 8077 className: 'centerBt', 8078 html: options.centerLabel ||'center', 8079 handleClick: function() { 8080 interaction.setFollowTrack('auto'); 8081 } 8082 }); 8083 bar.addControl(centerBt); 8084 var startBt = new ol.control.Button ({ 8085 className: 'startBt', 8086 handleClick: function(){ 8087 interaction.pause(false); 8088 interaction.setFollowTrack('auto'); 8089 element.classList.add('pauseTrack'); 8090 } 8091 }); 8092 bar.addControl(startBt); 8093 var pauseBt = new ol.control.Button ({ 8094 className: 'pauseBt', 8095 handleClick: function(){ 8096 interaction.pause(true); 8097 interaction.setFollowTrack('auto'); 8098 element.classList.remove('pauseTrack'); 8099 } 8100 }); 8101 bar.addControl(pauseBt); 8102 interaction.on('follow', function(e) { 8103 if (e.following) { 8104 element.classList.remove('centerTrack'); 8105 } else { 8106 element.classList.add('centerTrack'); 8107 } 8108 }); 8109 // Activate 8110 this._geolocBt.on('change:active', function(e) { 8111 if (e.active) { 8112 element.classList.add('ol-active'); 8113 } else { 8114 element.classList.remove('ol-active'); 8115 } 8116 }); 8117}; 8118ol.ext.inherits(ol.control.GeolocationBar, ol.control.Bar); 8119/** 8120 * Remove the control from its current map and attach it to the new map. 8121 * Subclasses may set up event handlers to get notified about changes to 8122 * the map here. 8123 * @param {ol.Map} map Map. 8124 * @api stable 8125 */ 8126ol.control.GeolocationBar.prototype.setMap = function (map) { 8127 if (this._listener) ol.Observable.unByKey(this._listener); 8128 this._listener = null; 8129 ol.control.Bar.prototype.setMap.call(this, map); 8130 // Get change (new layer added or removed) 8131 if (map) { 8132 this._listener = map.on('moveend', function() { 8133 var geo = this.getInteraction(); 8134 if (geo.getActive() && geo.get('followTrack') === 'auto' && geo.path_.length) { 8135 if (geo.path_[geo.path_.length-1][0] !== map.getView().getCenter()[0]) { 8136 this.element.classList.add('centerTrack'); 8137 } 8138 } 8139 }.bind(this)); 8140 } 8141}; 8142/** Get the ol.interaction.GeolocationDraw associatedwith the bar 8143 * @return {ol.interaction.GeolocationDraw} 8144 */ 8145ol.control.GeolocationBar.prototype.getInteraction = function () { 8146 return this._geolocBt.getInteraction(); 8147}; 8148 8149/* Copyright (c) 2016 Jean-Marc VIGLINO, 8150 released under the CeCILL-B license (French BSD license) 8151 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 8152*/ 8153/** Geolocation bar 8154 * The control bar is a container for other controls. It can be used to create toolbars. 8155 * Control bars can be nested and combined with ol.control.Toggle to handle activate/deactivate. 8156 * 8157 * @constructor 8158 * @fires tracking 8159 * @extends {ol.control.Toggle} 8160 * @param {Object=} options ol.interaction.GeolocationDraw option. 8161 * @param {String} options.className class of the control 8162 * @param {String} options.title title of the control to display as tooltip, default Geolocation 8163 * @param {number} options.delay delay before removing the location in ms, delfaut 3000 (3s) 8164 */ 8165ol.control.GeolocationButton = function(options) { 8166 if (!options) options = {}; 8167 // Geolocation draw interaction 8168 options.followTrack = options.followTrack || 'auto'; 8169 options.zoom = options.zoom || 16; 8170 //options.minZoom = options.minZoom || 16; 8171 var interaction = new ol.interaction.GeolocationDraw(options); 8172 ol.control.Toggle.call (this, { 8173 className: options.className = ((options.className || '') + ' ol-geobt').trim(), 8174 interaction: interaction, 8175 title: options.title || 'Geolocation', 8176 onToggle: function() { 8177 interaction.pause(true); 8178 interaction.setFollowTrack(options.followTrack || 'auto'); 8179 } 8180 }); 8181 this.setActive(false); 8182 interaction.on('tracking', function(e) { 8183 this.dispatchEvent({ type: 'position', coordinate: e.geolocation.getPosition() }); 8184 }.bind(this)); 8185 // Timeout delay 8186 var tout; 8187 interaction.on('change:active', function() { 8188 this.dispatchEvent({ type:'position' }); 8189 if (tout) { 8190 clearTimeout(tout); 8191 tout = null; 8192 } 8193 if (interaction.getActive()) { 8194 tout = setTimeout(function() { 8195 interaction.setActive(false); 8196 tout = null; 8197 }.bind(this), options.delay || 3000); 8198 } 8199 }.bind(this)); 8200}; 8201ol.ext.inherits(ol.control.GeolocationButton, ol.control.Toggle); 8202 8203/* Copyright (c) 2016 Jean-Marc VIGLINO, 8204 released under the CeCILL-B license (French BSD license)
8205 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 8206*/ 8207/** 8208 * OpenLayers 3 lobe Overview Control. 8209 * The globe can rotate with map (follow.) 8210 * 8211 * @constructor 8212 * @extends {ol.control.Control} 8213 * @param {Object=} options Control options. 8214 * @param {boolean} follow follow the map when center change, default false 8215 * @param {top|bottom-left|right} align position as top-left, etc. 8216 * @param {Array<ol.layer>} layers list of layers to display on the globe 8217 * @param {ol.style.Style | Array.<ol.style.Style> | undefined} style style to draw the position on the map , default a marker 8218 */ 8219ol.control.Globe = function(opt_options) 8220{ var options = opt_options || {}; 8221 var self = this; 8222 // API 8223 var element; 8224 if (options.target) 8225 { element = document.createElement("div"); 8226 this.panel_ = options.target; 8227 } 8228 else 8229 { element = document.createElement("div"); 8230 element.classList.add('ol-globe', 'ol-unselectable', 'ol-control'); 8231 if (/top/.test(options.align)) element.classList.add('ol-control-top'); 8232 if (/right/.test(options.align)) element.classList.add('ol-control-right'); 8233 this.panel_ = document.createElement("div"); 8234 this.panel_.classList.add("panel") 8235 element.appendChild(this.panel_); 8236 this.pointer_ = document.createElement("div"); 8237 this.pointer_.classList.add("ol-pointer"); 8238 element.appendChild(this.pointer_); 8239 } 8240 ol.control.Control.call(this, 8241 { element: element, 8242 target: options.target 8243 }); 8244// http://openlayers.org/en/latest/examples/sphere-mollweide.html ??? 8245 // Create a globe map 8246 this.ovmap_ = new ol.Map( 8247 { controls: new ol.Collection(), 8248 interactions: new ol.Collection(), 8249 target: this.panel_, 8250 view: new ol.View 8251 ({ zoom: 0, 8252 center: [0,0] 8253 }), 8254 layers: options.layers 8255 }); 8256 setTimeout (function() 8257 { self.ovmap_.updateSize(); 8258 }, 0); 8259 this.set('follow', options.follow || false); 8260 // Cache extent 8261 this.extentLayer = new ol.layer.Vector( 8262 { name: 'Cache extent', 8263 source: new ol.source.Vector(), 8264 style: options.style || [new ol.style.Style( 8265 { image: new ol.style.Circle( 8266 { fill: new ol.style.Fill({ 8267 color: 'rgba(255,0,0, 1)' 8268 }), 8269 stroke: new ol.style.Stroke( 8270 { width: 7, 8271 color: 'rgba(255,0,0, 0.8)' 8272 }), 8273 radius: 5 8274 }) 8275 } 8276 )] 8277 }) 8278 this.ovmap_.addLayer(this.extentLayer); 8279}; 8280ol.ext.inherits(ol.control.Globe, ol.control.Control); 8281/** 8282 * Set the map instance the control associated with. 8283 * @param {ol.Map} map The map instance. 8284 */ 8285ol.control.Globe.prototype.setMap = function(map) { 8286 if (this._listener) ol.Observable.unByKey(this._listener); 8287 this._listener = null; 8288 ol.control.Control.prototype.setMap.call(this, map); 8289 // Get change (new layer added or removed) 8290 if (map) 8291 { this._listener = map.getView().on('propertychange', this.setView.bind(this)); 8292 this.setView(); 8293 } 8294}; 8295/** Set the globe center with the map center 8296*/ 8297ol.control.Globe.prototype.setView = function() 8298{ if (this.getMap() && this.get('follow')) 8299 { this.setCenter(this.getMap().getView().getCenter()); 8300 } 8301} 8302/** Get globe map 8303* @return {ol.Map} 8304*/ 8305ol.control.Globe.prototype.getGlobe = function() 8306{ return this.ovmap_; 8307} 8308/** Show/hide the globe 8309*/ 8310ol.control.Globe.prototype.show = function(b) 8311{ if (b!==false) this.element.classList.remove("ol-collapsed"); 8312 else this.element.classList.add("ol-collapsed"); 8313 this.ovmap_.updateSize(); 8314} 8315/** Set position on the map 8316* @param {top|bottom-left|right} align 8317*/ 8318ol.control.Globe.prototype.setPosition = function(align) 8319{ if (/top/.test(align)) this.element.classList.add("ol-control-top"); 8320 else this.element.classList.remove("ol-control-top"); 8321 if (/right/.test(align)) this.element.classList.add("ol-control-right"); 8322 else this.element.classList.remove("ol-control-right"); 8323} 8324/** Set the globe center 8325* @param {_ol_coordinate_} center the point to center to 8326* @param {boolean} show show a pointer on the map, defaylt true 8327*/ 8328ol.control.Globe.prototype.setCenter = function (center, show) 8329{ var self = this; 8330 this.pointer_.classList.add("hidden"); 8331 if (center) 8332 { var map = this.ovmap_; 8333 var p = map.getPixelFromCoordinate(center); 8334 if (p) { 8335 if (show!==false) { 8336 var h = this.element.clientHeight; 8337 setTimeout(function() { 8338 self.pointer_.style.top = String(Math.min(Math.max(p[1],0),h)) + 'px'; 8339 self.pointer_.style.left = "50%"; 8340 self.pointer_.classList.remove("hidden"); 8341 }, 800); 8342 } 8343 map.getView().animate({ center: [center[0],0] }); 8344 } 8345 } 8346}; 8347 8348/* Copyright (c) 2017 Jean-Marc VIGLINO, 8349 released under the CeCILL-B license (French BSD license)
8350 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 8351*/ 8352/** 8353 * Draw a graticule on the map. 8354 * 8355 * @constructor 8356 * @extends {ol.control.CanvasBase} 8357 * @param {Object=} _ol_control_ options. 8358 * @param {ol.projectionLike} options.projection projection to use for the graticule, default EPSG:4326 8359 * @param {number} options.maxResolution max resolution to display the graticule 8360 * @param {ol.style.Style} options.style Style to use for drawing the graticule, default black. 8361 * @param {number} options.step step beetween lines (in proj units), default 1 8362 * @param {number} options.stepCoord show a coord every stepCoord, default 1 8363 * @param {number} options.spacing spacing beetween lines (in px), default 40px 8364 * @param {number} options.borderWidth width of the border (in px), default 5px 8365 * @param {number} options.margin margin of the border (in px), default 0px 8366 * @param {number} options.formatCoord a function that takes a coordinate and a position and return the formated coordinate 8367 */ 8368ol.control.Graticule = function(options) { 8369 if (!options) options = {}; 8370 // Initialize parent 8371 var elt = document.createElement("div"); 8372 elt.className = "ol-graticule ol-unselectable ol-hidden"; 8373 ol.control.CanvasBase.call(this, { element: elt }); 8374 this.set('projection', options.projection || 'EPSG:4326'); 8375 // Use to limit calculation 8376 var p = new ol.proj.Projection({code:this.get('projection')}); 8377 var m = p.getMetersPerUnit(); 8378 this.fac = 1; 8379 while (m/this.fac>10) { 8380 this.fac *= 10; 8381 } 8382 this.fac = 10000/this.fac; 8383 this.set('maxResolution', options.maxResolution || Infinity); 8384 this.set('step', options.step || 0.1); 8385 this.set('stepCoord', options.stepCoord || 1); 8386 this.set('spacing', options.spacing || 40); 8387 this.set('margin', options.margin || 0); 8388 this.set('borderWidth', options.borderWidth || 5); 8389 this.set('stroke', options.stroke!==false); 8390 this.formatCoord = options.formatCoord || function(c){return c;}; 8391 if (options.style instanceof ol.style.Style) { 8392 this.setStyle(options.style); 8393 } 8394 else { 8395 this.setStyle(new ol.style.Style({ 8396 stroke: new ol.style.Stroke({ color:"#000", width:1 }), 8397 fill: new ol.style.Fill({ color: "#fff" }), 8398 text: new ol.style.Text({ 8399 stroke: new ol.style.Stroke({ color:"#fff", width:2 }), 8400 fill: new ol.style.Fill({ color:"#000" }), 8401 }) 8402 })); 8403 } 8404}; 8405ol.ext.inherits(ol.control.Graticule, ol.control.CanvasBase); 8406ol.control.Graticule.prototype.setStyle = function (style) { 8407 this._style = style; 8408}; 8409ol.control.Graticule.prototype._draw = function (e) { 8410 if (this.get('maxResolution')<e.frameState.viewState.resolution) return; 8411 var ctx = this.getContext(e); 8412 var canvas = ctx.canvas; 8413 var ratio = e.frameState.pixelRatio; 8414 var w = canvas.width/ratio; 8415 var h = canvas.height/ratio; 8416 var proj = this.get('projection'); 8417 var map = this.getMap(); 8418 var bbox = 8419 [ map.getCoordinateFromPixel([0,0]), 8420 map.getCoordinateFromPixel([w,0]), 8421 map.getCoordinateFromPixel([w,h]), 8422 map.getCoordinateFromPixel([0,h]) 8423 ]; 8424 var xmax = -Infinity; 8425 var xmin = Infinity; 8426 var ymax = -Infinity; 8427 var ymin = Infinity; 8428 for (var i=0, c; c=bbox[i]; i++) 8429 { bbox[i] = ol.proj.transform (c, map.getView().getProjection(), proj); 8430 xmax = Math.max (xmax, bbox[i][0]); 8431 xmin = Math.min (xmin, bbox[i][0]); 8432 ymax = Math.max (ymax, bbox[i][1]); 8433 ymin = Math.min (ymin, bbox[i][1]); 8434 } 8435 var spacing = this.get('spacing'); 8436 var step = this.get('step'); 8437 var step2 = this.get('stepCoord'); 8438 var borderWidth = this.get('borderWidth'); 8439 var margin = this.get('margin'); 8440 // Limit max line draw 8441 var ds = (xmax-xmin)/step*spacing; 8442 if (ds>w) 8443 { var dt = Math.round((xmax-xmin)/w*spacing /step); 8444 step *= dt; 8445 if (step>this.fac) step = Math.round(step/this.fac)*this.fac; 8446 } 8447 xmin = (Math.floor(xmin/step))*step -step; 8448 ymin = (Math.floor(ymin/step))*step -step; 8449 xmax = (Math.floor(xmax/step))*step +2*step; 8450 ymax = (Math.floor(ymax/step))*step +2*step; 8451 var extent = ol.proj.get(proj).getExtent(); 8452 if (extent) 8453 { if (xmin < extent[0]) xmin = extent[0]; 8454 if (ymin < extent[1]) ymin = extent[1]; 8455 if (xmax > extent[2]) xmax = extent[2]+step; 8456 if (ymax > extent[3]) ymax = extent[3]+step; 8457 }
8458 var hasLines = this.getStyle().getStroke() && this.get("stroke"); 8459 var hasText = this.getStyle().getText(); 8460 var hasBorder = this.getStyle().getFill(); 8461 ctx.save(); 8462 ctx.scale(ratio,ratio); 8463 ctx.beginPath(); 8464 ctx.rect(margin, margin, w-2*margin, h-2*margin); 8465 ctx.clip(); 8466 ctx.beginPath(); 8467 var txt = {top:[],left:[],bottom:[], right:[]}; 8468 var x, y, p, p0, p1; 8469 for (x=xmin; x<xmax; x += step) 8470 { p0 = ol.proj.transform ([x, ymin], proj, map.getView().getProjection()); 8471 p0 = map.getPixelFromCoordinate(p0); 8472 if (hasLines) ctx.moveTo(p0[0], p0[1]); 8473 p = p0; 8474 for (y=ymin+step; y<=ymax; y+=step) 8475 { p1 = ol.proj.transform ([x, y], proj, map.getView().getProjection()); 8476 p1 = map.getPixelFromCoordinate(p1); 8477 if (hasLines) ctx.lineTo(p1[0], p1[1]); 8478 if (p[1]>0 && p1[1]<0) txt.top.push([x, p]); 8479 if (p[1]>h && p1[1]<h) txt.bottom.push([x,p]); 8480 p = p1; 8481 } 8482 } 8483 for (y=ymin; y<ymax; y += step) 8484 { p0 = ol.proj.transform ([xmin, y], proj, map.getView().getProjection()); 8485 p0 = map.getPixelFromCoordinate(p0); 8486 if (hasLines) ctx.moveTo(p0[0], p0[1]); 8487 p = p0; 8488 for (x=xmin+step; x<=xmax; x+=step) 8489 { p1 = ol.proj.transform ([x, y], proj, map.getView().getProjection()); 8490 p1 = map.getPixelFromCoordinate(p1); 8491 if (hasLines) ctx.lineTo(p1[0], p1[1]); 8492 if (p[0]<0 && p1[0]>0) txt.left.push([y,p]); 8493 if (p[0]<w && p1[0]>w) txt.right.push([y,p]); 8494 p = p1; 8495 } 8496 } 8497 if (hasLines) 8498 { ctx.strokeStyle = this.getStyle().getStroke().getColor(); 8499 ctx.lineWidth = this.getStyle().getStroke().getWidth(); 8500 ctx.stroke(); 8501 } 8502 // Draw text 8503 if (hasText) 8504 { 8505 ctx.fillStyle = this.getStyle().getText().getFill().getColor(); 8506 ctx.strokeStyle = this.getStyle().getText().getStroke().getColor(); 8507 ctx.lineWidth = this.getStyle().getText().getStroke().getWidth(); 8508 ctx.font = this.getStyle().getText().getFont(); 8509 ctx.textAlign = 'center'; 8510 ctx.textBaseline = 'hanging'; 8511 var t, tf; 8512 var offset = (hasBorder ? borderWidth : 0) + margin + 2; 8513 for (i=0; t = txt.top[i]; i++) if (!(Math.round(t[0]/this.get('step'))%step2)) 8514 { tf = this.formatCoord(t[0], 'top'); 8515 ctx.strokeText(tf, t[1][0], offset); 8516 ctx.fillText(tf, t[1][0], offset); 8517 } 8518 ctx.textBaseline = 'alphabetic'; 8519 for (i=0; t = txt.bottom[i]; i++) if (!(Math.round(t[0]/this.get('step'))%step2)) 8520 { tf = this.formatCoord(t[0], 'bottom'); 8521 ctx.strokeText(tf, t[1][0], h-offset); 8522 ctx.fillText(tf, t[1][0], h-offset); 8523 } 8524 ctx.textBaseline = 'middle'; 8525 ctx.textAlign = 'left'; 8526 for (i=0; t = txt.left[i]; i++) if (!(Math.round(t[0]/this.get('step'))%step2)) 8527 { tf = this.formatCoord(t[0], 'left'); 8528 ctx.strokeText(tf, offset, t[1][1]); 8529 ctx.fillText(tf, offset, t[1][1]); 8530 } 8531 ctx.textAlign = 'right'; 8532 for (i=0; t = txt.right[i]; i++) if (!(Math.round(t[0]/this.get('step'))%step2)) 8533 { tf = this.formatCoord(t[0], 'right'); 8534 ctx.strokeText(tf, w-offset, t[1][1]); 8535 ctx.fillText(tf, w-offset, t[1][1]); 8536 } 8537 } 8538 // Draw border 8539 if (hasBorder) 8540 { var fillColor = this.getStyle().getFill().getColor(); 8541 var color, stroke; 8542 if (stroke = this.getStyle().getStroke()) 8543 { color = this.getStyle().getStroke().getColor(); 8544 } 8545 else 8546 { color = fillColor; 8547 fillColor = "#fff"; 8548 } 8549 ctx.strokeStyle = color; 8550 ctx.lineWidth = stroke ? stroke.getWidth() : 1; 8551 // 8552 for (i=1; i<txt.top.length; i++) 8553 { ctx.beginPath(); 8554 ctx.rect(txt.top[i-1][1][0], margin, txt.top[i][1][0]-txt.top[i-1][1][0], borderWidth); 8555 ctx.fillStyle = Math.round(txt.top[i][0]/step)%2 ? color: fillColor; 8556 ctx.fill(); 8557 ctx.stroke(); 8558 } 8559 for (i=1; i<txt.bottom.length; i++) 8560 { ctx.beginPath(); 8561 ctx.rect(txt.bottom[i-1][1][0], h-borderWidth-margin, txt.bottom[i][1][0]-txt.bottom[i-1][1][0], borderWidth); 8562 ctx.fillStyle = Math.round(txt.bottom[i][0]/step)%2 ? color: fillColor; 8563 ctx.fill(); 8564 ctx.stroke(); 8565 } 8566 for (i=1; i<txt.left.length; i++) 8567 { ctx.beginPath(); 8568 ctx.rect(margin, txt.left[i-1][1][1], borderWidth, txt.left[i][1][1]-txt.left[i-1][1][1]); 8569 ctx.fillStyle = Math.round(txt.left[i][0]/step)%2 ? color: fillColor; 8570 ctx.fill(); 8571 ctx.stroke(); 8572 } 8573 for (i=1; i<txt.right.length; i++) 8574 { ctx.beginPath(); 8575 ctx.rect(w-borderWidth-margin, txt.right[i-1][1][1], borderWidth, txt.right[i][1][1]-txt.right[i-1][1][1]); 8576 ctx.fillStyle = Math.round(txt.right[i][0]/step)%2 ? color: fillColor; 8577 ctx.fill(); 8578 ctx.stroke(); 8579 } 8580 ctx.beginPath(); 8581 ctx.fillStyle = color; 8582 ctx.rect(margin,margin, borderWidth, borderWidth); 8583 ctx.rect(margin,h-borderWidth-margin, borderWidth,borderWidth); 8584 ctx.rect(w-borderWidth-margin,margin, borderWidth, borderWidth); 8585 ctx.rect(w-borderWidth-margin,h-borderWidth-margin, borderWidth,borderWidth); 8586 ctx.fill(); 8587 }
8588 ctx.restore(); 8589}; 8590 8591/* Copyright (c) 2017 Jean-Marc VIGLINO, 8592 released under the CeCILL-B license (French BSD license) 8593 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 8594*/ 8595/** 8596 * Draw a grid reference on the map and add an index. 8597 * 8598 * @constructor 8599 * @extends {ol.control.CanvasBase} 8600 * @fires select 8601 * @param {Object=} Control options. 8602 * @param {ol.style.Style} options.style Style to use for drawing the grid (stroke and text), default black. 8603 * @param {number} options.maxResolution max resolution to display the graticule 8604 * @param {ol.extent} options.extent extent of the grid, required 8605 * @param {ol.size} options.size number of lines and cols, required 8606 * @param {number} options.margin margin to display text (in px), default 0px 8607 * @param {ol.source.Vector} options.source source to use for the index, default none (use setIndex to reset the index) 8608 * @param {string | function} options.property a property to display in the index or a function that takes a feature and return the name to display in the index, default 'name'. 8609 * @param {function|undefined} options.sortFeatures sort function to sort 2 features in the index, default sort on property option 8610 * @param {function|undefined} options.indexTitle a function that takes a feature and return the title to display in the index, default the first letter of property option 8611 * @param {string} options.filterLabel label to display in the search bar, default 'filter' 8612 */ 8613ol.control.GridReference = function(options) { 8614 if (!options) options = {}; 8615 // Initialize parent 8616 var elt = document.createElement("div"); 8617 elt.className = (!options.target ? "ol-control ":"") +"ol-gridreference ol-unselectable "+(options.className||""); 8618 options.style = options.style || new ol.style.Style({ 8619 stroke: new ol.style.Stroke({ color:"#000", width:1 }), 8620 text: new ol.style.Text({ 8621 font: "bold 14px Arial", 8622 stroke: new ol.style.Stroke({ color:"#fff", width:2 }), 8623 fill: new ol.style.Fill({ color:"#000" }), 8624 }) 8625 }); 8626 ol.control.CanvasBase.call(this, { 8627 element: elt, 8628 target: options.target, 8629 style: options.style 8630 }); 8631 if (typeof (options.property)=='function') this.getFeatureName = options.property; 8632 if (typeof (options.sortFeatures)=='function') this.sortFeatures = options.sortFeatures; 8633 if (typeof (options.indexTitle)=='function') this.indexTitle = options.indexTitle; 8634 // Set index using the source 8635 this.source_ = options.source; 8636 if (options.source) { 8637 this.setIndex(options.source.getFeatures(), options); 8638 // reload on ready 8639 options.source.once('change',function() { 8640 if (options.source.getState() === 'ready') { 8641 this.setIndex(options.source.getFeatures(), options); 8642 } 8643 }.bind(this)); 8644 } 8645 // Options 8646 this.set('maxResolution', options.maxResolution || Infinity); 8647 this.set('extent', options.extent); 8648 this.set('size', options.size); 8649 this.set('margin', options.margin || 0); 8650 this.set('property', options.property || 'name'); 8651 this.set('filterLabel', options.filterLabel || 'filter'); 8652}; 8653ol.ext.inherits(ol.control.GridReference, ol.control.CanvasBase); 8654/** 8655 * Set the map instance the control is associated with. 8656 * @param {ol.Map} map The map instance. 8657 */ 8658 ol.control.GridReference.prototype.setMap = function(map) { 8659 ol.control.CanvasBase.prototype.setMap.call(this, map); 8660 this.setIndex(this.source_.getFeatures()); 8661 }; 8662/** Returns the text to be displayed in the index 8663 * @param {ol.Feature} f the feature 8664 * @return {string} the text to be displayed in the index 8665 * @api 8666 */ 8667ol.control.GridReference.prototype.getFeatureName = function (f) { 8668 return f.get(this.get('property')||'name'); 8669}; 8670/** Sort function 8671 * @param {ol.Feature} a first feature 8672 * @param {ol.Feature} b second feature 8673 * @return {Number} 0 if a==b, -1 if a<b, 1 if a>b 8674 * @api 8675 */ 8676ol.control.GridReference.prototype.sortFeatures = function (a,b) { 8677 return (this.getFeatureName(a) == this.getFeatureName(b)) ? 0 : (this.getFeatureName(a) < this.getFeatureName(b)) ? -1 : 1; 8678}; 8679/** Get the feature title 8680 * @param {ol.Feature} f 8681 * @return the first letter of the eature name (getFeatureName) 8682 * @api 8683 */ 8684ol.control.GridReference.prototype.indexTitle = function (f) { 8685 return this.getFeatureName(f).charAt(0); 8686}; 8687/** Display features in the index 8688 * @param { Array<ol.Feature> | ol.Collection<ol.Feature> } features 8689 */ 8690ol.control.GridReference.prototype.setIndex = function (features) { 8691 if (!this.getMap()) return; 8692 var self = this; 8693 if (features.getArray) features = features.getArray(); 8694 features.sort ( function(a,b) { return self.sortFeatures(a,b); } ); 8695 this.element.innerHTML = ""; 8696 var elt = this.element; 8697 var search = document.createElement("input"); 8698 search.setAttribute('type', 'search'); 8699 search.setAttribute('placeholder', this.get('filterLabel') || 'filter'); 8700 var searchKeyupFunction = function() { 8701 var v = this.value.replace(/^\*/,''); 8702 // console.log(v) 8703 var r = new RegExp (v, 'i'); 8704 var list = ul.querySelectorAll('li'); 8705 Array.prototype.forEach.call(list, function(li) { 8706 if (li.classList.contains('ol-title')) { 8707 li.style.display = ''; 8708 } else { 8709 if (r.test(li.querySelector('.ol-name').textContent)) li.style.display = ''; 8710 else li.style.display = 'none'; 8711 } 8712 }); 8713 Array.prototype.forEach.call(ul.querySelectorAll("li.ol-title"), function(li) { 8714 var nextVisible; 8715 var start = false;
8716 for (var i=0; i<list.length; i++) { 8717 if (start) { 8718 if (list[i].classList.contains('ol-title')) break; 8719 if (!list[i].style.display) { 8720 nextVisible = list[i]; 8721 break; 8722 } 8723 } 8724 if (list[i] === li) start = true; 8725 } 8726 if (nextVisible) li.style.display = ''; 8727 else li.style.display = 'none'; 8728 }); 8729 }; 8730 search.addEventListener('search', searchKeyupFunction); 8731 search.addEventListener('keyup', searchKeyupFunction); 8732 elt.appendChild(search); 8733 var ul = document.createElement("ul"); 8734 elt.appendChild(ul); 8735 var r, title; 8736 features.forEach(function(feat) { 8737 r = this.getReference(feat.getGeometry().getFirstCoordinate()); 8738 if (r) { 8739 var name = this.getFeatureName(feat); 8740 var c = this.indexTitle(feat); 8741 if (c != title) { 8742 var li_title = document.createElement("li"); 8743 li_title.classList.add('ol-title'); 8744 li_title.textContent = c; 8745 ul.appendChild(li_title); 8746 } 8747 title = c; 8748 var li_ref_name = document.createElement("li"); 8749 var span_name = document.createElement("span"); 8750 span_name.classList.add("ol-name"); 8751 span_name.textContent = name; 8752 li_ref_name.appendChild(span_name); 8753 var span_ref = document.createElement("span"); 8754 span_ref.classList.add("ol-ref"); 8755 span_ref.textContent = r; 8756 li_ref_name.appendChild(span_ref); 8757 var feature = feat; 8758 li_ref_name.addEventListener("click", function() { 8759 this.dispatchEvent({ type:"select", feature: feature }); 8760 }.bind(this)); 8761 ul.appendChild(li_ref_name); 8762 } 8763 }.bind(this)); 8764}; 8765/** Get reference for a coord 8766* @param {ol.coordinate} coords 8767* @return {string} the reference 8768*/ 8769ol.control.GridReference.prototype.getReference = function (coords) { 8770 if (!this.getMap()) return; 8771 var extent = this.get('extent'); 8772 var size = this.get('size'); 8773 var dx = Math.floor ( (coords[0] - extent[0]) / (extent[2]- extent[0]) * size[0] ); 8774 if (dx<0 || dx>=size[0]) return ""; 8775 var dy = Math.floor ( (extent[3] - coords[1]) / (extent[3]- extent[1]) * size[1] ); 8776 if (dy<0 || dy>=size[1]) return ""; 8777 return this.getHIndex(dx)+this.getVIndex(dy); 8778}; 8779/** Get vertical index (0,1,2,3...) 8780 * @param {number} index 8781 * @returns {string} 8782 * @api 8783 */ 8784 ol.control.GridReference.prototype.getVIndex = function (index) { 8785 return index; 8786}; 8787/** Get horizontal index (A,B,C...) 8788 * @param {number} index 8789 * @returns {string} 8790 * @api 8791 */ 8792 ol.control.GridReference.prototype.getHIndex = function (index) { 8793 return String.fromCharCode(65 + index); 8794}; 8795/** Draw the grid 8796* @param {ol.event} e postcompose event 8797* @private 8798*/ 8799ol.control.GridReference.prototype._draw = function (e) { 8800 if (this.get('maxResolution')<e.frameState.viewState.resolution) return; 8801 var ctx = this.getContext(e); 8802 var canvas = ctx.canvas; 8803 var ratio = e.frameState.pixelRatio; 8804 var w = canvas.width/ratio; 8805 var h = canvas.height/ratio; 8806 var extent = this.get('extent'); 8807 var size = this.get('size'); 8808 var map = this.getMap(); 8809 var ex = ol.extent.boundingExtent([map.getPixelFromCoordinate([extent[0],extent[1]]), map.getPixelFromCoordinate([extent[2],extent[3]])]); 8810 var p0 = [ex[0],ex[1]]; 8811 var p1 = [ex[2],ex[3]]; 8812 var dx = (p1[0]-p0[0])/size[0]; 8813 var dy = (p1[1]-p0[1])/size[1]; 8814 ctx.save(); 8815 var margin = this.get('margin'); 8816 ctx.scale(ratio,ratio); 8817 ctx.strokeStyle = this.getStroke().getColor(); 8818 ctx.lineWidth = this.getStroke().getWidth(); 8819 // Draw grid 8820 ctx.beginPath(); 8821 var i; 8822 for (i=0; i<=size[0]; i++) { 8823 ctx.moveTo(p0[0]+i*dx, p0[1]); 8824 ctx.lineTo(p0[0]+i*dx, p1[1]); 8825 } 8826 for (i=0; i<=size[1]; i++) { 8827 ctx.moveTo(p0[0], p0[1]+i*dy); 8828 ctx.lineTo(p1[0], p0[1]+i*dy); 8829 } 8830 ctx.stroke(); 8831 // Draw text 8832 ctx.font = this.getTextFont(); 8833 ctx.fillStyle = this.getTextFill().getColor(); 8834 ctx.strokeStyle = this.getTextStroke().getColor(); 8835 var lw = ctx.lineWidth = this.getTextStroke().getWidth(); 8836 var spacing = margin +lw; 8837 ctx.textAlign = 'center'; 8838 var letter, x, y; 8839 for (i=0; i<size[0]; i++) { 8840 letter = this.getHIndex(i); 8841 x = p0[0]+i*dx+dx/2; 8842 y = p0[1]-spacing; 8843 if (y<0) { 8844 y = spacing; 8845 ctx.textBaseline = 'hanging'; 8846 } 8847 else ctx.textBaseline = 'alphabetic'; 8848 ctx.strokeText(letter, x, y); 8849 ctx.fillText(letter, x, y); 8850 y = p1[1]+spacing; 8851 if (y>h) { 8852 y = h-spacing; 8853 ctx.textBaseline = 'alphabetic'; 8854 } 8855 else ctx.textBaseline = 'hanging'; 8856 ctx.strokeText(letter, x, y); 8857 ctx.fillText(letter, x, y); 8858 } 8859 ctx.textBaseline = 'middle'; 8860 for (i=0; i<size[1]; i++) { 8861 letter = this.getVIndex(i); 8862 y = p0[1]+i*dy+dy/2; 8863 ctx.textAlign = 'right'; 8864 x = p0[0] - spacing; 8865 if (x<0) { 8866 x = spacing; 8867 ctx.textAlign = 'left'; 8868 } 8869 else ctx.textAlign = 'right'; 8870 ctx.strokeText(letter, x, y); 8871 ctx.fillText(letter, x, y); 8872 x = p1[0] + spacing; 8873 if (x>w) { 8874 x = w-spacing; 8875 ctx.textAlign = 'right'; 8876 } 8877 else ctx.textAlign = 'left'; 8878 ctx.strokeText(letter, x, y); 8879 ctx.fillText(letter, x, y); 8880 }
8881 ctx.restore(); 8882}; 8883 8884/** Image line control 8885 * 8886 * @constructor 8887 * @extends {ol.control.Control} 8888 * @fires select 8889 * @fires collapse 8890 * @param {Object=} options Control options. 8891 * @param {String} options.className class of the control 8892 * @param {Array<ol.source.Vector>|ol.source.Vector} options.source vector sources that contains the images 8893 * @param {Array<ol.layer.Vector>} options.layers A list of layers to display images. If no source and no layers, all visible layers will be considered. 8894 * @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 8895 * @param {function} options.getTitle a function that gets a feature and return the title, default return an empty string 8896 * @param {boolean} options.collapsed the line is collapse, default false 8897 * @param {boolean} options.collapsible the line is collapsible, default false 8898 * @param {number} options.maxFeatures the maximum image element in the line, default 100 8899 * @param {number} options.useExtent only show feature in the current extent 8900 * @param {boolean} options.hover select image on hover, default false 8901 * @param {string|boolean} options.linkColor link color or false if no link, default false 8902 */ 8903ol.control.Imageline = function(options) { 8904 var element = ol.ext.element.create('DIV', { 8905 className: (options.className || '') + ' ol-imageline' 8906 + (options.target ? '': ' ol-unselectable ol-control') 8907 + (options.collapsed && options.collapsible ? 'ol-collapsed' : '') 8908 }); 8909 if (!options.target && options.collapsible) { 8910 ol.ext.element.create('BUTTON', { 8911 type: 'button', 8912 click: function() { 8913 this.toggle(); 8914 }.bind(this), 8915 parent: element 8916 }); 8917 } 8918 // Source 8919 if (options.source) this._sources = options.source.forEach ? options.source : [options.source]; 8920 if (options.layers) { 8921 this.setLayers(options.layers); 8922 } 8923 // Initialize 8924 ol.control.Control.call(this, { 8925 element: element, 8926 target: options.target 8927 }); 8928 // Scroll imageline 8929 this._setScrolling(); 8930 this._scrolldiv.addEventListener("scroll", function() { 8931 if (this.getMap()) this.getMap().render(); 8932 }.bind(this)); 8933 // Parameters 8934 if (typeof(options.getImage)==='function') this._getImage = options.getImage; 8935 if (typeof(options.getTitle)==='function') this._getTitle = options.getTitle; 8936 this.set('maxFeatures', options.maxFeatures || 100); 8937 this.set('linkColor', options.linkColor || false); 8938 this.set('hover', options.hover || false); 8939 this.set('useExtent', options.useExtent || false); 8940 this.refresh(); 8941}; 8942ol.ext.inherits(ol.control.Imageline, ol.control.Control); 8943/** 8944 * Remove the control from its current map and attach it to the new map. 8945 * @param {ol.Map} map Map. 8946 * @api stable 8947 */ 8948ol.control.Imageline.prototype.setMap = function (map) { 8949 if (this._listener) { 8950 this._listener.forEach(function(l) { 8951 ol.Observable.unByKey(l); 8952 }.bind(this)); 8953 } 8954 this._listener = null; 8955 ol.control.Control.prototype.setMap.call(this, map); 8956 if (map) { 8957 this._listener = [ 8958 map.on('postcompose', this._drawLink.bind(this)), 8959 map.on('moveend', function() { 8960 if (this.get('useExtent')) this.refresh(); 8961 }.bind(this)) 8962 ] 8963 this.refresh(); 8964 } 8965}; 8966/** Set layers to use in the control 8967 * @param {Array<ol.Layer} layers 8968 */ 8969ol.control.Imageline.prototype.setLayers = function (layers) { 8970 this._sources = this._getSources(layers); 8971}; 8972/** Get source from a set of layers 8973 * @param {Array<ol.Layer} layers 8974 * @returns {Array<ol.source.Vector} 8975 * @private 8976 */ 8977ol.control.Imageline.prototype._getSources = function (layers) { 8978 var sources = []; 8979 layers.forEach(function(l) { 8980 if (l.getVisible()) { 8981 if (l.getSource() && l.getSource().getFeatures) sources.push(l.getSource()); 8982 else if (l.getLayers) this._getSources(l.getLayers()); 8983 } 8984 }.bind(this)); 8985 return sources; 8986}; 8987/** Set useExtent param and refresh the line 8988 * @param {boolean} b 8989 */ 8990ol.control.Imageline.prototype.useExtent = function(b) { 8991 this.set('useExtent', b); 8992 this.refresh(); 8993}; 8994/** Is the line collapsed 8995 * @return {boolean} 8996 */ 8997ol.control.Imageline.prototype.isCollapsed = function() { 8998 return this.element.classList.contains('ol-collapsed'); 8999}; 9000/** Collapse the line 9001 * @param {boolean} b 9002 */ 9003ol.control.Imageline.prototype.collapse = function(b) { 9004 if (b) this.element.classList.add('ol-collapsed'); 9005 else this.element.classList.remove('ol-collapsed'); 9006 if (this.getMap()) { 9007 setTimeout ( function() { 9008 this.getMap().render(); 9009 }.bind(this), this.isCollapsed() ? 0 : 250); 9010 } 9011 this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() }); 9012}; 9013/** Collapse the line 9014 */ 9015ol.control.Imageline.prototype.toggle = function() { 9016 this.element.classList.toggle('ol-collapsed'); 9017 if (this.getMap()) { 9018 setTimeout ( function() { 9019 this.getMap().render(); 9020 }.bind(this), this.isCollapsed() ? 0 : 250); 9021 } 9022 this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() }); 9023}; 9024/** Default function to get an image of a feature 9025 * @param {ol.Feature} f 9026 * @private 9027 */ 9028ol.control.Imageline.prototype._getImage = function(f) { 9029 return f.get('img'); 9030}; 9031/** Default function to get an image title 9032 * @param {ol.Feature} f 9033 * @private 9034 */ 9035ol.control.Imageline.prototype._getTitle = function(/* f */) { 9036 return ''; 9037}; 9038/** 9039 * Get features 9040 * @return {Array<ol.Feature>} 9041 */ 9042ol.control.Imageline.prototype.getFeatures = function() { 9043 var map = this.getMap(); 9044 if (!map) return []; 9045 var features = []; 9046 var sources = this._sources || this._getSources(map.getLayers());
9047 sources.forEach(function(s) { 9048 if (features.length < this.get('maxFeatures')) { 9049 if (!this.get('useExtent') || !map) { 9050 features.push(s.getFeatures()); 9051 } else { 9052 var extent = map.getView().calculateExtent(map.getSize()); 9053 features.push(s.getFeaturesInExtent(extent)); 9054 } 9055 } 9056 }.bind(this)) 9057 return features; 9058}; 9059/** Set element scrolling with a acceleration effect on desktop 9060 * (on mobile it uses the scroll of the browser) 9061 * @private 9062 */ 9063ol.control.Imageline.prototype._setScrolling = function() { 9064 var elt = this._scrolldiv = ol.ext.element.create('DIV', { 9065 parent: this.element 9066 }); 9067 ol.ext.element.scrollDiv(elt, { 9068 // Prevent selection when moving 9069 onmove: function(b) { 9070 this._moving = b; 9071 }.bind(this) 9072 }); 9073 elt.addEventListener('scroll', this._updateScrollBounds.bind(this)); 9074 this._updateScrollBounds(); 9075}; 9076/** Set element scrolling with a acceleration effect on desktop 9077 * (on mobile it uses the scroll of the browser) 9078 * @private 9079 */ 9080ol.control.Imageline.prototype._updateScrollBounds = function() { 9081 var elt = this._scrolldiv; 9082 if (elt.scrollLeft < 5) { 9083 this.element.classList.add('ol-scroll0') 9084 } else { 9085 this.element.classList.remove('ol-scroll0'); 9086 } 9087 if (elt.scrollWidth - elt.scrollLeft - elt.offsetWidth < 5) { 9088 this.element.classList.add('ol-scroll1'); 9089 } else { 9090 this.element.classList.remove('ol-scroll1'); 9091 } 9092}; 9093/** 9094 * Refresh the imageline with new data 9095 */ 9096ol.control.Imageline.prototype.refresh = function() { 9097 this._scrolldiv.innerHTML = ''; 9098 var allFeatures = this.getFeatures(); 9099 var current = this._select ? this._select.feature : null; 9100 if (this._select) this._select.elt = null; 9101 this._iline = []; 9102 if (this.getMap()) this.getMap().render(); 9103 // Add a new image 9104 var addImage = function(f) { 9105 if (this._getImage(f)) { 9106 var img = ol.ext.element.create('DIV', { 9107 className: 'ol-image', 9108 parent: this._scrolldiv 9109 }); 9110 ol.ext.element.create('IMG', { 9111 src: this._getImage(f), 9112 parent: img 9113 }).addEventListener('load', function(){ 9114 this.classList.add('ol-loaded'); 9115 }); 9116 ol.ext.element.create('SPAN', { 9117 html: this._getTitle(f), 9118 parent: img 9119 }); 9120 // Current image 9121 var sel = { elt: img, feature: f }; 9122 // On click > dispatch event 9123 img.addEventListener('click', function(){ 9124 if (!this._moving) { 9125 this.dispatchEvent({type: 'select', feature: f }); 9126 this._scrolldiv.scrollLeft = img.offsetLeft 9127 + ol.ext.element.getStyle(img, 'width')/2 9128 - ol.ext.element.getStyle(this.element, 'width')/2; 9129 if (this._select) this._select.elt.classList.remove('select'); 9130 this._select = sel; 9131 this._select.elt.classList.add('select'); 9132 } 9133 }.bind(this)); 9134 // Show link 9135 img.addEventListener('mouseover', function(e) { 9136 if (this.get('hover')) { 9137 if (this._select) this._select.elt.classList.remove('select'); 9138 this._select = sel; 9139 this._select.elt.classList.add('select'); 9140 this.getMap().render(); 9141 e.stopPropagation(); 9142 } 9143 }.bind(this)); 9144 // Remove link 9145 img.addEventListener('mouseout', function(e) { 9146 if (this.get('hover')) { 9147 if (this._select) this._select.elt.classList.remove('select'); 9148 this._select = false; 9149 this.getMap().render(); 9150 e.stopPropagation(); 9151 } 9152 }.bind(this)); 9153 // Prevent image dragging 9154 img.ondragstart = function(){ return false; }; 9155 // Add image 9156 this._iline.push(sel); 9157 if (current===f) { 9158 this._select = sel; 9159 sel.elt.classList.add('select'); 9160 } 9161 } 9162 }.bind(this); 9163 // Add images 9164 var nb = this.get('maxFeatures'); 9165 allFeatures.forEach(function(features) { 9166 for (var i=0, f; f=features[i]; i++) { 9167 if (nb-- < 0) break; 9168 addImage(f); 9169 } 9170 }.bind(this)); 9171 // Add the selected one 9172 if (this._select && this._select.feature && !this._select.elt) { 9173 addImage(this._select.feature); 9174 } 9175 this._updateScrollBounds(); 9176}; 9177/** Center image line on a feature 9178 * @param {ol.feature} feature 9179 * @param {boolean} scroll scroll the line to center on the image, default true 9180 * @api 9181 */ 9182ol.control.Imageline.prototype.select = function(feature, scroll) { 9183 this._select = false;
9184 // Find the image 9185 this._iline.forEach(function (f) { 9186 if (f.feature === feature) { 9187 f.elt.classList.add('select'); 9188 this._select = f; 9189 if (scroll!==false) { 9190 this._scrolldiv.scrollLeft = f.elt.offsetLeft 9191 + ol.ext.element.getStyle(f.elt, 'width')/2 9192 - ol.ext.element.getStyle(this.element, 'width')/2; 9193 } 9194 } else { 9195 f.elt.classList.remove('select'); 9196 } 9197 }.bind(this)); 9198}; 9199/** Draw link on the map 9200 * @private 9201 */ 9202ol.control.Imageline.prototype._drawLink = function(e) { 9203 if (!this.get('linkColor') | this.isCollapsed()) return; 9204 var map = this.getMap(); 9205 if (map && this._select && this._select.elt) { 9206 var ctx = e.context || ol.ext.getMapCanvas(this.getMap()).getContext('2d'); 9207 var ratio = e.frameState.pixelRatio; 9208 var pt = [ 9209 this._select.elt.offsetLeft 9210 - this._scrolldiv.scrollLeft 9211 + ol.ext.element.getStyle(this._select.elt, 'width')/2, 9212 parseFloat(ol.ext.element.getStyle(this.element, 'top')) || this.getMap().getSize()[1] 9213 ]; 9214 var geom = this._select.feature.getGeometry().getFirstCoordinate(); 9215 geom = this.getMap().getPixelFromCoordinate(geom); 9216 ctx.save(); 9217 ctx.fillStyle = this.get('linkColor'); 9218 ctx.beginPath(); 9219 if (geom[0]>pt[0]) { 9220 ctx.moveTo((pt[0]-5)*ratio, pt[1]*ratio); 9221 ctx.lineTo((pt[0]+5)*ratio, (pt[1]+5)*ratio); 9222 } else { 9223 ctx.moveTo((pt[0]-5)*ratio, (pt[1]+5)*ratio); 9224 ctx.lineTo((pt[0]+5)*ratio, pt[1]*ratio); 9225 } 9226 ctx.lineTo(geom[0]*ratio, geom[1]*ratio); 9227 ctx.fill(); 9228 ctx.restore(); 9229 } 9230}; 9231 9232/* Copyright (c) 2018 Jean-Marc VIGLINO, 9233 released under the CeCILL-B license (French BSD license) 9234 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 9235*/ 9236/** 9237 * Geoportail isochrone Control. 9238 * @see https://geoservices.ign.fr/documentation/geoservices/isochrones.html 9239 * @constructor 9240 * @extends {ol.control.Control} 9241 * @fires isochrone 9242 * @fires error 9243 * @param {Object=} options 9244 * @param {string} options.className control class name 9245 * @param {string} [options.apiKey] Geoportail apo key 9246 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 9247 * @param {string | undefined} options.label Text label to use for the search button, default "search" 9248 * @param {string | undefined} options.placeholder placeholder, default "Search..." 9249 * @param {string | undefined} options.inputLabel label for the input, default none 9250 * @param {string | undefined} options.noCollapse prevent collapsing on input blur, default false 9251 * @param {number | undefined} options.typing a delay on each typing to start searching (ms) use -1 to prevent autocompletion, default 300. 9252 * @param {integer | undefined} options.minLength minimum length to start searching, default 1 9253 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 9254 * @param {integer | undefined} options.maxHistory maximum number of items to display in history. Set -1 if you don't want history, default maxItems 9255 * @param {function} options.getTitle a function that takes a feature and return the name to display in the index. 9256 * @param {function} options.autocomplete a function that take a search string and callback function to send an array 9257 * 9258 * @param {string} options.exclusions Exclusion list separate with a comma 'Toll,Tunnel,Bridge' 9259 */ 9260ol.control.IsochroneGeoportail = function(options) { 9261 var self = this; 9262 if (!options) options = {}; 9263 if (options.typing == undefined) options.typing = 300; 9264 var classNames = (options.className ? options.className : '')+ ' ol-isochrone ol-routing'; 9265 if (!options.target) classNames += ' ol-unselectable ol-control'; 9266 var element = ol.ext.element.create('DIV', { className: classNames }) 9267 if (!options.target) { 9268 var bt = ol.ext.element.create('BUTTON', { parent: element }) 9269 bt.addEventListener('click', function(){ 9270 element.classList.toggle('ol-collapsed'); 9271 }); 9272 } 9273 // Inherits 9274 ol.control.Control.call(this, { 9275 element: element, 9276 target: options.target 9277 }); 9278 this.set('iter', 1); 9279 var content = ol.ext.element.create('DIV', { className: 'content', parent: element } ) 9280 // Search control 9281 this._addSearchCtrl(content, options); 9282 // Method buttons 9283 ol.ext.element.create('BUTTON', { className: 'ol-button ol-method-time selected', title:'isochrone', parent: content }) 9284 .addEventListener('click', function(){ 9285 this.setMethod('time'); 9286 }.bind(this)); 9287 ol.ext.element.create('I', { className: 'ol-button ol-method-distance', title:'isodistance', parent: content }) 9288 .addEventListener('click', function(){ 9289 this.setMethod('distance'); 9290 }.bind(this)); 9291 // Mode buttons 9292 ol.ext.element.create('I', { className: 'ol-button ol-car selected', title:'by car', parent: content }) 9293 .addEventListener('click', function(){ 9294 this.setMode('car'); 9295 }.bind(this)); 9296 ol.ext.element.create('I', { className: 'ol-button ol-pedestrian', title:'by foot', parent: content }) 9297 .addEventListener('click', function(){ 9298 this.setMode('pedestrian'); 9299 }.bind(this)); 9300 // Direction buttons 9301 ol.ext.element.create('I', { className: 'ol-button ol-direction-direct selected', title:'direct', parent: content }) 9302 .addEventListener('click', function(){ 9303 this.setDirection('direct'); 9304 }.bind(this)); 9305 ol.ext.element.create('I', { className: 'ol-button ol-direction-reverse', title:'reverse', parent: content }) 9306 .addEventListener('click', function(){ 9307 this.setDirection('reverse'); 9308 }.bind(this)); 9309 // Input 9310 var div = ol.ext.element.create('DIV', { className: 'ol-time', parent: content }) 9311 ol.ext.element.create('DIV', { html:'isochrone:', parent: div }); 9312 ol.ext.element.create('INPUT', { type: 'number', parent: div, min: 0 }) 9313 .addEventListener('change', function(){ 9314 self.set('hour', Number(this.value)); 9315 }); 9316 ol.ext.element.create('TEXT', { parent: div, html: 'h' }); 9317 ol.ext.element.create('INPUT', { type: 'number', parent: div, min: 0 }) 9318 .addEventListener('change', function(){ 9319 self.set('minute', Number(this.value)); 9320 }); 9321 ol.ext.element.create('TEXT', { parent: div, html: 'mn' }); 9322 div = ol.ext.element.create('DIV', { className: 'ol-distance', parent: content }); 9323 ol.ext.element.create('DIV', { html:'isodistance:', parent: div }); 9324 ol.ext.element.create('INPUT', { type: 'number', step: 'any', parent: div, min: 0 }) 9325 .addEventListener('change', function(){ 9326 self.set('distance', Number(this.value)); 9327 }); 9328 ol.ext.element.create('TEXT', { parent: div, html: 'km' }); 9329 div = ol.ext.element.create('DIV', { className: 'ol-iter', parent: content }) 9330 ol.ext.element.create('DIV', { html:'Iteration:', parent: div }); 9331 ol.ext.element.create('INPUT', { type: 'number', parent: div, value: 1, min: 1 }) 9332 .addEventListener('change', function(){ 9333 self.set('iter', Number(this.value)); 9334 }); 9335 // OK button 9336 ol.ext.element.create('I', { className:'ol-ok', html:'ok', parent: content }) 9337 .addEventListener('click', function() { 9338 var val = 0; 9339 switch (this.get('method')) { 9340 case 'distance': { 9341 val = this.get('distance')*1000; 9342 break; 9343 } 9344 default: { 9345 val = (this.get('hour')||0)*3600 + (this.get('minute')||0)*60; 9346 break; 9347 } 9348 } 9349 if (val && this.get('coordinate')) { 9350 this.search(this.get('coordinate'), val); 9351 } 9352 }.bind(this)); 9353 this.set('url', 'https://wxs.ign.fr/'+(options.apiKey || 'essentiels')+'/isochrone/isochrone.json'); 9354 this._ajax = new ol.ext.Ajax({ 9355 dataType: 'JSON', 9356 auth: options.auth 9357 }); 9358 this._ajax.on('success', this._success.bind(this)); 9359 this._ajax.on('error', this._error.bind(this)); 9360 // searching 9361 this._ajax.on('loadstart', function() { 9362 this.element.classList.add('ol-searching'); 9363 }.bind(this)); 9364 this._ajax.on('loadend', function() { 9365 this.element.classList.remove('ol-searching'); 9366 }.bind(this)); 9367 this.setMethod(options.method); 9368}; 9369ol.ext.inherits(ol.control.IsochroneGeoportail, ol.control.Control); 9370/** 9371 * Set the map instance the control is associated with 9372 * and add its controls associated to this map. 9373 * @param {_ol_Map_} map The map instance. 9374 */ 9375ol.control.IsochroneGeoportail.prototype.setMap = function (map) { 9376 ol.control.Control.prototype.setMap.call(this, map);
9377 this._search.setMap(map); 9378}; 9379/** Add a new search input 9380 * @private 9381 */ 9382ol.control.IsochroneGeoportail.prototype._addSearchCtrl = function (element, options) { 9383 var div = ol.ext.element.create("DIV", { parent: element }); 9384 var search = this._search = new ol.control.SearchGeoportail({ 9385 className: 'IGNF ol-collapsed', 9386 apiKey: options.apiKey, 9387 target: div 9388 }); 9389 search.on('select', function(e){ 9390 search.setInput(e.search.fulltext); 9391 this.set('coordinate', e.coordinate); 9392 }.bind(this)); 9393 search.on('change:input', function(){ 9394 this.set('coordinate', false); 9395 }.bind(this)); 9396}; 9397/** Set the travel method 9398 * @param [string] method The method (time or distance) 9399 */ 9400ol.control.IsochroneGeoportail.prototype.setMethod = function(method) {7 9401 method = (/distance/.test(method) ? 'distance' : 'time'); 9402 this.element.querySelector(".ol-method-time").classList.remove("selected"); 9403 this.element.querySelector(".ol-method-distance").classList.remove("selected"); 9404 this.element.querySelector(".ol-method-"+method).classList.add("selected"); 9405 this.element.querySelector("div.ol-time").classList.remove("selected"); 9406 this.element.querySelector("div.ol-distance").classList.remove("selected"); 9407 this.element.querySelector("div.ol-"+method).classList.add("selected"); 9408 this.set('method', method); 9409}; 9410/** Set mode 9411 * @param {string} mode The mode: 'car' or 'pedestrian', default 'car' 9412 */ 9413ol.control.IsochroneGeoportail.prototype.setMode = function (mode) { 9414 this.set('mode', mode); 9415 this.element.querySelector(".ol-car").classList.remove("selected"); 9416 this.element.querySelector(".ol-pedestrian").classList.remove("selected"); 9417 this.element.querySelector(".ol-"+mode).classList.add("selected"); 9418}; 9419/** Set direction 9420 * @param {string} direction The direction: 'direct' or 'reverse', default direct 9421 */ 9422ol.control.IsochroneGeoportail.prototype.setDirection = function (direction) { 9423 this.set('direction', direction); 9424 this.element.querySelector(".ol-direction-direct").classList.remove("selected"); 9425 this.element.querySelector(".ol-direction-reverse").classList.remove("selected"); 9426 this.element.querySelector(".ol-direction-"+direction).classList.add("selected"); 9427}; 9428/** Calculate an isochrone 9429 * @param {ol.coordinate} coord 9430 * @param {number|string} option A number as time (in second) or distance (in meter), depend on method propertie 9431 * or a string with a unit (s, mn, h for time or km, m) 9432 */ 9433ol.control.IsochroneGeoportail.prototype.search = function(coord, option, iter) { 9434 var proj = this.getMap() ? this.getMap().getView().getProjection() : 'EPSG:3857'; 9435 var method = /distance/.test(this.get('method')) ? 'distance' : 'time'; 9436 if (typeof(option)==='string') { 9437 var unit = option.replace(/([0-9|.]*)([a-z]*)$/,'$2'); 9438 method = 'time'; 9439 option = parseFloat(option); 9440 // convert unit 9441 switch (unit) { 9442 case 'mn': { 9443 option = option*60; 9444 break; 9445 } 9446 case 'h': { 9447 option = option*3600; 9448 break; 9449 } 9450 case 'm': { 9451 method = 'distance'; 9452 break; 9453 } 9454 case 'km': { 9455 method = 'distance'; 9456 option = option*1000; 9457 break; 9458 } 9459 } 9460 } 9461 var dt = Math.round(option * (this.get('iter')-(iter||0)) / this.get('iter')); 9462 if (typeof option === 'number') { 9463 // Send data 9464 var data = { 9465 'gp-access-lib': '2.1.0', 9466 location: ol.proj.toLonLat(coord, proj), 9467 graphName: (this.get('mode')==='pedestrian' ? 'Pieton' : 'Voiture'), 9468 exclusions: this.get('exclusions') || undefined, 9469 method: method, 9470 time: method==='time' ? dt : undefined, 9471 distance: method==='distance' ? dt : undefined, 9472 reverse: (this.get('direction') === 'reverse'), 9473 smoothing: this.get('smoothing') || true, 9474 holes: this.get('holes') || false 9475 }; 9476 this._ajax.send(this.get('url'), data, { 9477 coord: coord, 9478 option: option, 9479 data: data, 9480 iteration: (iter||0)+1 9481 }); 9482 } 9483}; 9484/** Trigger result 9485 * @private 9486 */ 9487ol.control.IsochroneGeoportail.prototype._success = function(e) { 9488 var proj = this.getMap() ? this.getMap().getView().getProjection() : 'EPSG:3857'; 9489 // Convert to features 9490 var format = new ol.format.WKT(); 9491 var evt = e.response; 9492 evt.feature = format.readFeature(evt.wktGeometry, { 9493 dataProjection: 'EPSG:4326', 9494 featureProjection: proj 9495 }); 9496 evt.feature.set('iteration', e.options.iteration); 9497 evt.feature.set('method', e.options.data.method); 9498 evt.feature.set(e.options.data.method, e.options.data[e.options.data.method]); 9499 delete evt.wktGeometry; 9500 evt.type = 'isochrone'; 9501 evt.iteration = e.options.iteration-1; 9502 this.dispatchEvent (evt); 9503 if (e.options.iteration < this.get('iter')) { 9504 this.search(e.options.coord, e.options.option, e.options.iteration); 9505 } 9506}; 9507/** Trigger error 9508 * @private 9509 */ 9510ol.control.IsochroneGeoportail.prototype._error = function() { 9511 this.dispatchEvent ({ type:'error' }); 9512}; 9513 9514/* Copyright (c) 2015 Jean-Marc VIGLINO, 9515 released under the CeCILL-B license (French BSD license)
9516 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 9517*/ 9518/** 9519 * OpenLayers Layer Switcher Control. 9520 * 9521 * @constructor 9522 * @extends {ol.control.LayerSwitcher} 9523 * @param {Object=} options Control options. 9524 */ 9525ol.control.LayerPopup = function(options) { 9526 options = options || {}; 9527 options.switcherClass="ol-layerswitcher-popup"; 9528 if (options.mouseover!==false) options.mouseover=true; 9529 ol.control.LayerSwitcher.call(this, options); 9530}; 9531ol.ext.inherits(ol.control.LayerPopup, ol.control.LayerSwitcher); 9532/** Disable overflow 9533*/ 9534ol.control.LayerPopup.prototype.overflow = function(){}; 9535/** Render a list of layer 9536 * @param {elt} element to render 9537 * @layers {Array{ol.layer}} list of layer to show 9538 * @api stable 9539 */ 9540ol.control.LayerPopup.prototype.drawList = function(ul, layers) { 9541 var self=this; 9542 var setVisibility = function(e) { 9543 e.preventDefault(); 9544 var l = self._getLayerForLI(this); 9545 self.switchLayerVisibility(l,layers); 9546 if (e.type=="touchstart") self.element.classList.add("ol-collapsed"); 9547 }; 9548 layers.forEach(function(layer) { 9549 if (self.displayInLayerSwitcher(layer)) { 9550 var d = ol.ext.element.create('LI', { 9551 html: layer.get("title") || layer.get("name"), 9552 on: { 'click touchstart': setVisibility }, 9553 parent: ul 9554 }); 9555 self._setLayerForLI(d, layer); 9556 if (self.testLayerVisibility(layer)) d.classList.add("ol-layer-hidden"); 9557 if (layer.getVisible()) d.classList.add('ol-visible'); 9558 } 9559 }); 9560}; 9561 9562/* Copyright (c) 2015 Jean-Marc VIGLINO, 9563 released under the CeCILL-B license (French BSD license) 9564 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 9565*/ 9566/** LayerShop a layer switcher with special controls to handle operation on layers. 9567 * @fires select 9568 * @fires drawlist 9569 * @fires toggle 9570 * @fires reorder-start 9571 * @fires reorder-end 9572 * @fires layer:visible 9573 * @fires layer:opacity 9574 * 9575 * @constructor 9576 * @extends {ol.control.LayerSwitcher} 9577 * @param {Object=} options 9578 * @param {boolean} options.selection enable layer selection when click on the title 9579 * @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 9580 * @param {boolean} options.show_progress show a progress bar on tile layers, default false 9581 * @param {boolean} options.mouseover show the panel on mouseover, default false 9582 * @param {boolean} options.reordering allow layer reordering, default true 9583 * @param {boolean} options.trash add a trash button to delete the layer, default false 9584 * @param {function} options.oninfo callback on click on info button, if none no info button is shown DEPRECATED: use on(info) instead 9585 * @param {boolean} options.extent add an extent button to zoom to the extent of the layer 9586 * @param {function} options.onextent callback when click on extent, default fits view to extent 9587 * @param {number} options.drawDelay delay in ms to redraw the layer (usefull to prevent flickering when manipulating the layers) 9588 * @param {boolean} options.collapsed collapse the layerswitcher at beginning, default true 9589 * @param {ol.layer.Group} options.layerGroup a layer group to display in the switcher, default display all layers of the map 9590 * @param {boolean} options.noScroll prevent handle scrolling, default false 9591 * 9592 * Layers attributes that control the switcher 9593 * - allwaysOnTop {boolean} true to force layer stay on top of the others while reordering, default false 9594 * - displayInLayerSwitcher {boolean} display the layer in switcher, default true 9595 * - noSwitcherDelete {boolean} to prevent layer deletion (w. trash option = true), default false 9596 */ 9597ol.control.LayerShop = function(options) { 9598 options = options || {}; 9599 options.selection = true; 9600 options.noScroll = true; 9601 ol.control.LayerSwitcher.call (this, options); 9602 this.element.classList.add('ol-layer-shop'); 9603 // Control title (selected layer) 9604 var title = this.element.insertBefore(ol.ext.element.create('DIV', { className: 'ol-title-bar' }), this.getPanel()); 9605 this.on('select', function(e) { 9606 title.innerText = e.layer ? e.layer.get('title') : ''; 9607 this.element.setAttribute('data-layerClass', this.getLayerClass(e.layer)); 9608 }.bind(this)); 9609 // Top/bottom bar 9610 this._topbar = this.element.insertBefore(ol.ext.element.create('DIV', { 9611 className: 'ol-bar ol-top-bar' 9612 }), this.getPanel()); 9613 this._bottombar = ol.ext.element.create('DIV', { 9614 className: 'ol-bar ol-bottom-bar', 9615 parent: this.element 9616 }); 9617 this._controls = []; 9618}; 9619ol.ext.inherits(ol.control.LayerShop, ol.control.LayerSwitcher); 9620/** Set the map instance the control is associated with. 9621 * @param {_ol_Map_} map The map instance. 9622 */ 9623ol.control.LayerShop.prototype.setMap = function(map) { 9624 if (this.getMap()) { 9625 // Remove map controls
9626 this._controls.forEach(function(c) { 9627 this.getMap().removeControl(c) 9628 }.bind(this)); 9629 } 9630 ol.control.LayerSwitcher.prototype.setMap.call(this, map); 9631 if (map) { 9632 // Select first layer 9633 this.selectLayer(); 9634 // Remove a layer 9635 this._listener.removeLayer = map.getLayers().on('remove', function(e) { 9636 // Select first layer 9637 if (e.element === this.getSelection()) { 9638 this.selectLayer(); 9639 } 9640 }.bind(this)); 9641 // Add controls 9642 this._controls.forEach(function(c) { 9643 this.getMap().addControl(c) 9644 }.bind(this)); 9645 } 9646}; 9647/** Get the bar element (to add new element in it) 9648 * @param {string} [position='top'] bar position bottom or top, default top 9649 * @returns {Element} 9650 */ 9651ol.control.LayerShop.prototype.getBarElement = function(position) { 9652 return position==='bottom' ? this._bottombar : this._topbar; 9653}; 9654/** Add a control to the panel 9655 * @param {ol.control.Control} control 9656 * @param {string} [position='top'] bar position bottom or top, default top 9657 */ 9658ol.control.LayerShop.prototype.addControl = function(control, position) { 9659 this._controls.push(control); 9660 control.setTarget(position==='bottom' ? this._bottombar : this._topbar); 9661 if (this.getMap()) { 9662 this.getMap().addControl(control); 9663 } 9664}; 9665 9666/* Copyright (c) 2016 Jean-Marc VIGLINO, 9667 released under the CeCILL-B license (French BSD license) 9668 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 9669*/ 9670/** 9671 * @classdesc OpenLayers Layer Switcher Control. 9672 * @require layer.getPreview 9673 * 9674 * @constructor 9675 * @extends {ol.control.LayerSwitcher} 9676 * @param {Object=} options Control options. 9677 */ 9678ol.control.LayerSwitcherImage = function(options) { 9679 options = options || {}; 9680 options.switcherClass = "ol-layerswitcher-image"; 9681 if (options.mouseover!==false) options.mouseover=true; 9682 ol.control.LayerSwitcher.call(this, options); 9683}; 9684ol.ext.inherits(ol.control.LayerSwitcherImage, ol.control.LayerSwitcher); 9685/** Render a list of layer 9686 * @param {elt} element to render 9687 * @layers {Array{ol.layer}} list of layer to show 9688 * @api stable 9689 */ 9690ol.control.LayerSwitcherImage.prototype.drawList = function(ul, layers) { 9691 var self = this; 9692 var setVisibility = function(e) { 9693 e.preventDefault(); 9694 var l = self._getLayerForLI(this); 9695 self.switchLayerVisibility(l,layers); 9696 if (e.type=="touchstart") self.element.classList.add("ol-collapsed"); 9697 }; 9698 ol.ext.element.setStyle(ul, { height: 'auto' }); 9699 layers.forEach(function(layer) { 9700 if (self.displayInLayerSwitcher(layer)) { 9701 var preview = layer.getPreview ? layer.getPreview() : ["none"]; 9702 var d = ol.ext.element.create('LI', { 9703 className: 'ol-imgcontainer' + (layer.getVisible() ? ' ol-visible':''), 9704 on: { 'touchstart click': setVisibility }, 9705 parent: ul 9706 }); 9707 self._setLayerForLI(d, layer); 9708 preview.forEach(function(img){ 9709 ol.ext.element.create('IMG', { 9710 src: img, 9711 parent: d 9712 }) 9713 }); 9714 ol.ext.element.create('p', { 9715 html: layer.get("title") || layer.get("name"), 9716 parent: d 9717 }); 9718 if (self.testLayerVisibility(layer)) d.classList.add('ol-layer-hidden'); 9719 } 9720 }); 9721}; 9722/** Disable overflow 9723*/ 9724ol.control.LayerSwitcherImage.prototype.overflow = function(){}; 9725 9726// eslint-disable-next-line no-unused-vars 9727/** Create a legend for styles 9728 * @constructor 9729 * @extends {ol.control.CanvasBase} 9730 * @fires select 9731 * @param {*} options 9732 * @param {String} options.className class of the control 9733 * @param {ol.legend.Legend} options.legend 9734 * @param {boolean | undefined} options.collapsed Specify if legend should be collapsed at startup. Default is true. 9735 * @param {boolean | undefined} options.collapsible Specify if legend can be collapsed, default true. 9736 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 9737 */ 9738ol.control.Legend = function(options) { 9739 options = options || {}; 9740 var element = document.createElement('div'); 9741 if (options.target) { 9742 element.className = options.className || 'ol-legend'; 9743 } else { 9744 element.className = (options.className || 'ol-legend') 9745 +' ol-unselectable ol-control' 9746 +(options.collapsible===false ? ' ol-uncollapsible': ' ol-collapsed'); 9747 // Show on click 9748 var button = document.createElement('button'); 9749 button.setAttribute('type', 'button'); 9750 button.addEventListener('click', function() { 9751 this.toggle(); 9752 }.bind(this)); 9753 element.appendChild(button); 9754 // Hide on click 9755 button = document.createElement('button'); 9756 button.setAttribute('type', 'button'); 9757 button.className = 'ol-closebox'; 9758 button.addEventListener('click', function() { 9759 this.toggle(); 9760 }.bind(this)); 9761 element.appendChild(button); 9762 } 9763 ol.control.CanvasBase.call(this, { 9764 element: element, 9765 target: options.target 9766 }); 9767 // The legend 9768 this._legend = options.legend; 9769 this._legend.getCanvas().className = 'ol-legendImg'; 9770 element.appendChild(this._legend.getCanvas()); 9771 element.appendChild(this._legend.getListElement()); 9772 if (options.collapsible!==false && options.collapsed===false) this.show(); 9773 this._legend.on('select', function(e) { 9774 this.dispatchEvent(e); 9775 }.bind(this)); 9776 this._legend.on('refresh', function() { 9777 if (this._onCanvas && this.getMap()) {
9778 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 9779 } 9780 }.bind(this)); 9781}; 9782ol.ext.inherits(ol.control.Legend, ol.control.CanvasBase); 9783/** Get the legend associated with the control 9784 * @returns {ol.legend.Legend} 9785 */ 9786ol.control.Legend.prototype.getLegend = function () { 9787 return this._legend; 9788}; 9789/** Draw control on canvas 9790 * @param {boolean} b draw on canvas. 9791 */ 9792ol.control.Legend.prototype.setCanvas = function (b) { 9793 this._onCanvas = b; 9794 this.element.style.visibility = b ? "hidden":"visible"; 9795 if (this.getMap()) { 9796 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 9797 } 9798}; 9799/** Is control on canvas 9800 * @returns {boolean} 9801 */ 9802ol.control.Legend.prototype.onCanvas = function () { 9803 return !!this._onCanvas; 9804}; 9805/** Draw legend on canvas 9806 * @private 9807 */ 9808ol.control.Legend.prototype._draw = function (e) { 9809 if (this._onCanvas && !this.element.classList.contains('ol-collapsed')) { 9810 var canvas = this._legend.getCanvas(); 9811 var ctx = this.getContext(e); 9812 var h = ctx.canvas.height - canvas.height; 9813 ctx.save(); 9814 ctx.rect(0, h, canvas.width, canvas.height); 9815 var col = '#fff'; 9816 if (this._legend.getTextStyle().getBackgroundFill()) { 9817 col = ol.color.asString(this._legend.getTextStyle().getBackgroundFill().getColor()); 9818 } 9819 ctx.fillStyle = ctx.strokeStyle = col; 9820 ctx.lineWidth = 10; 9821 ctx.lineJoin = 'round'; 9822 ctx.stroke(); 9823 ctx.clearRect(0, h, canvas.width, canvas.height); 9824 ctx.fill(); 9825 ctx.drawImage(canvas, 0, h); 9826 ctx.restore(); 9827 } 9828}; 9829/** Show control 9830 */ 9831ol.control.Legend.prototype.show = function() { 9832 if (this.element.classList.contains('ol-collapsed')) { 9833 this.element.classList.remove('ol-collapsed'); 9834 this.dispatchEvent({ type:'change:collapse', collapsed: false }); 9835 if (this.getMap()) { 9836 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 9837 } 9838 } 9839}; 9840/** Hide control 9841 */ 9842ol.control.Legend.prototype.hide = function() { 9843 if (!this.element.classList.contains('ol-collapsed')) { 9844 this.element.classList.add('ol-collapsed'); 9845 this.dispatchEvent({ type:'change:collapse', collapsed: true }); 9846 if (this.getMap()) { 9847 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 9848 } 9849 } 9850}; 9851/** Show/hide control 9852 * @returns {boolean} 9853 */ 9854ol.control.Legend.prototype.collapse = function(b) { 9855 if (b===false) this.show(); 9856 else this.hide(); 9857}; 9858/** Is control collapsed 9859 * @returns {boolean} 9860 */ 9861ol.control.Legend.prototype.isCollapsed = function() { 9862 return (this.element.classList.contains('ol-collapsed')); 9863}; 9864/** Toggle control 9865 */ 9866ol.control.Legend.prototype.toggle = function() { 9867 this.element.classList.toggle('ol-collapsed'); 9868 this.dispatchEvent({ type:'change:collapse', collapsed: this.element.classList.contains('ol-collapsed') }); 9869 if (this.getMap()) { 9870 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 9871 } 9872}; 9873 9874/* Copyright (c) 2019 Jean-Marc VIGLINO, 9875 released under the CeCILL-B license (French BSD license) 9876 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 9877*/ 9878// 9879/** A control to jump from one zone to another. 9880 * @constructor 9881 * @fires select 9882 * @extends {ol.control.Control} 9883 * @param {Object=} options Control options. 9884 * @param {string} options.className class name 9885 * @param {Array<any>} options.zone an array of zone: { name, extent (in EPSG:4326) } 9886 * @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 9887 * @param {ol.ProjectionLike} options.projection projection of the control, Default is EPSG:3857 (Spherical Mercator). 9888 * @param {bolean} options.centerOnClick center on click when a zone is clicked (or listen to 'select' event to do something), default true 9889 */ 9890ol.control.MapZone = function(options) { 9891 if (!options) options={}; 9892 var element = document.createElement("div"); 9893 if (options.target) { 9894 element = ol.ext.element.create('DIV', { 9895 className: options.className || "ol-mapzone" 9896 }); 9897 } else { 9898 element = ol.ext.element.create('DIV', { 9899 className: (options.className || "ol-mapzone") +' ol-unselectable ol-control ol-collapsed' 9900 }); 9901 var bt = ol.ext.element.create('BUTTON', { 9902 type: 'button', 9903 on: { 9904 'click': function() { 9905 element.classList.toggle("ol-collapsed"); 9906 maps.forEach(function (m) { 9907 m.updateSize(); 9908 }); 9909 }.bind(this) 9910 }, 9911 parent: element 9912 }); 9913 ol.ext.element.create('I', { 9914 parent: bt 9915 }); 9916 } 9917 // Parent control 9918 ol.control.Control.call(this, { 9919 element: element, 9920 target: options.target 9921 }); 9922 this.set('centerOnClick', options.centerOnClick); 9923 // Create maps 9924 var maps = this._maps = []; 9925 this._projection = options.projection; 9926 this._layer = options.layer;
9927 options.zones.forEach(this.addZone.bind(this)); 9928 // Refresh the maps 9929 setTimeout(function() { 9930 maps.forEach(function (m) { 9931 m.updateSize(); 9932 }); 9933 }); 9934}; 9935ol.ext.inherits(ol.control.MapZone, ol.control.Control); 9936/** Collapse the control 9937 * @param {boolean} b 9938 */ 9939ol.control.MapZone.prototype.setCollapsed = function (b) { 9940 if (b) { 9941 this.element.classList.remove('ol-collapsed'); 9942 // Force map rendering 9943 this.getMaps().forEach(function (m) { 9944 m.updateSize(); 9945 }); 9946 } else { 9947 this.element.classList.add('ol-collapsed'); 9948 } 9949}; 9950/** Get control collapsed 9951 * @return {boolean} 9952 */ 9953ol.control.MapZone.prototype.getCollapsed = function () { 9954 return this.element.classList.contains('ol-collapsed'); 9955}; 9956/** Set the control visibility (collapsed) 9957 * @param {boolean} b 9958 * @deprecated use setCollapsed instead 9959 */ 9960ol.control.MapZone.prototype.setVisible = ol.control.MapZone.prototype.setCollapsed; 9961/** Get associated maps 9962 * @return {ol.Map} 9963 */ 9964ol.control.MapZone.prototype.getMaps = function () { 9965 return this._maps; 9966}; 9967/** Get nb zone */ 9968ol.control.MapZone.prototype.getLength = function () { 9969 return this._maps.length; 9970}; 9971/** Add a new zone to the control 9972 * @param {Object} z 9973 * @param {string} title 9974 * @param {ol.extent} extent if map is not defined 9975 * @param {ol.Map} map if map is defined use the map extent 9976 * @param {ol.layer.Layer} [layer] layer of the zone, default use default control layer 9977 */ 9978ol.control.MapZone.prototype.addZone = function (z) { 9979 var view = new ol.View({ zoom: 6, center: [0,0], projection: this._projection }); 9980 var extent; 9981 if (z.map) { 9982 extent = ol.proj.transformExtent(z.map.getView().calculateExtent(), z.map.getView().getProjection(), view.getProjection()) ; 9983 } else { 9984 extent = ol.proj.transformExtent(z.extent, 'EPSG:4326', view.getProjection()); 9985 } 9986 // console.log(extent, z.extent) 9987 var div = ol.ext.element.create('DIV', { 9988 className: 'ol-mapzonezone', 9989 parent: this.element, 9990 click : function() { 9991 // Get index 9992 var index = -1; 9993 this._maps.forEach(function(m, i) { 9994 if (m.get('zone') === z) { 9995 index = i; 9996 } 9997 }) 9998 this.dispatchEvent({ 9999 type: 'select', 10000 zone: z, 10001 index: index, 10002 coordinate: ol.extent.getCenter(extent), 10003 extent: extent 10004 }); 10005 if (this.get('centerOnClick') !== false) { 10006 this.getMap().getView().fit(extent); 10007 } 10008 this.setVisible(false); 10009 }.bind(this) 10010 }); 10011 var layer; 10012 if (z.layer) { 10013 layer = z.layer; 10014 } else if (typeof(this._layer) === 'function') { 10015 layer = this._layer(z); 10016 } else { 10017 // Try to clone the layer 10018 layer = new this._layer.constructor({ 10019 source: this._layer.getSource() 10020 }); 10021 } 10022 var map = new ol.Map({ 10023 target: div, 10024 view: view, 10025 controls: [], 10026 interactions:[], 10027 layers: [layer] 10028 }); 10029 map.set('zone', z); 10030 this._maps.push(map); 10031 view.fit(extent); 10032 // Name 10033 ol.ext.element.create('P', { 10034 html: z.title, 10035 parent: div 10036 }); 10037}; 10038/** Remove a zone from the control 10039 * @param {number} index 10040 */ 10041ol.control.MapZone.prototype.removeZone = function (index) { 10042 var z = this.element.querySelectorAll('.ol-mapzonezone')[index]; 10043 if (z) { 10044 z.remove(); 10045 this._maps.splice(index, 1); 10046 } 10047}; 10048/** Pre-defined zones */ 10049ol.control.MapZone.zones = {}; 10050/** French overseas departments */ 10051ol.control.MapZone.zones.DOM = [{ 10052 "title": "Guadeloupe", 10053 "extent": [ -61.898594315312444, 15.75623038647845, -60.957887532935324, 16.575317670979473 ] 10054},{ 10055 "title": "Guyane", 10056 "extent": [ -54.72525931072715, 2.1603763430019, -51.528236062921344, 5.7984307809552575 ] 10057},{ 10058 "title": "Martinique", 10059 "extent": [ -61.257556528564756, 14.387506317407514, -60.76934912110432, 14.895067461729951 ] 10060},{ 10061 "title": "Mayotte", 10062 "extent": [ 44.959844536967815, -13.01674138212816, 45.35328866510648, -12.65521942207829 ] 10063},{ 10064 "title": "La réunion", 10065 "extent": [ 55.17059012967656, -21.407680069231688, 55.88195702001797, -20.85560221637526 ] 10066}]; 10067/** French overseas territories */ 10068ol.control.MapZone.zones.TOM = [{ 10069 "title": "Polynésie Française", 10070 "extent": [ 206.23664226630862, -22.189040615809787, 221.85920743981987, -10.835039595040698 ] 10071},{ 10072 "title": "Nouvelle Calédonie", 10073 "extent": [ 163.76420580160925, -22.581641092751838, 167.66984709498706, -19.816411635668445 ] 10074},{ 10075 "title": "St-Pierre et Miquelon", 10076 "extent": [ -56.453698765748676, 46.74449858188555, -56.0980198121544, 47.14669874229787 ] 10077},{ 10078 "title": "Wallis et Futuna", 10079 "extent": [ 181.7588623143665, -14.7341169873267, 183.95612353301715, -13.134720799175085 ] 10080},{ 10081 "title": "St-Martin St-Barthélemy", 10082 "extent": [ -63.1726389501678, 17.806097291313506, -62.7606535945649, 18.13267688837938 ] 10083}]; 10084/** French overseas departments and territories */ 10085ol.control.MapZone.zones.DOMTOM = [{ 10086 title: 'Métropole', 10087 extent: [ -5.318421740712579, 41.16082274292913, 9.73284186155716, 51.21957336557702 ] 10088}].concat(ol.control.MapZone.zones.DOM, ol.control.MapZone.zones.TOM); 10089 10090/* Copyright (c) 2017 Jean-Marc VIGLINO, 10091 released under the CeCILL-B license (French BSD license)
10092 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 10093*/ 10094/** Control overlay for OL3 10095 * The overlay control is a control that display an overlay over the map 10096 * 10097 * @constructor 10098 * @extends {ol.control.Control} 10099 * @fire change:visible 10100 * @param {Object=} options Control options. 10101 * @param {string} className class of the control 10102 * @param {boolean} options.closeBox add a close button 10103 * @param {boolean} options.hideOnClick close dialog when click 10104 */ 10105ol.control.Notification = function(options) { 10106 options = options || {}; 10107 var element = document.createElement('DIV'); 10108 this.contentElement = ol.ext.element.create('DIV', { 10109 click: function() { 10110 if (this.get('hideOnClick')) this.hide(); 10111 }.bind(this), 10112 parent: element 10113 }); 10114 var classNames = (options.className||"")+ " ol-notification"; 10115 if (!options.target) { 10116 classNames += " ol-unselectable ol-control ol-collapsed"; 10117 } 10118 element.setAttribute('class', classNames); 10119 ol.control.Control.call(this, { 10120 element: element, 10121 target: options.target 10122 }); 10123 this.set('closeBox', options.closeBox); 10124 this.set('hideOnClick', options.hideOnClick); 10125}; 10126ol.ext.inherits(ol.control.Notification, ol.control.Control); 10127/** 10128 * Display a notification on the map 10129 * @param {string|node|undefined} what the notification to show, default get the last one 10130 * @param {number} [duration=3000] duration in ms, if -1 never hide 10131 */ 10132ol.control.Notification.prototype.show = function(what, duration) { 10133 var self = this; 10134 var elt = this.element; 10135 if (what) { 10136 if (what instanceof Node) { 10137 this.contentElement.innerHTML = ''; 10138 this.contentElement.appendChild(what); 10139 } else { 10140 this.contentElement.innerHTML = what; 10141 } 10142 if (this.get('closeBox')) { 10143 this.contentElement.classList.add('ol-close') 10144 ol.ext.element.create('SPAN', { 10145 className: 'closeBox', 10146 click: function() { this.hide(); }.bind(this), 10147 parent: this.contentElement 10148 }) 10149 } else { 10150 this.contentElement.classList.remove('ol-close') 10151 } 10152 } 10153 if (this._listener) { 10154 clearTimeout(this._listener); 10155 this._listener = null; 10156 } 10157 elt.classList.add('ol-collapsed'); 10158 this._listener = setTimeout(function() { 10159 elt.classList.remove('ol-collapsed'); 10160 if (!duration || duration >= 0) { 10161 self._listener = setTimeout(function() { 10162 elt.classList.add('ol-collapsed'); 10163 self._listener = null; 10164 }, duration || 3000); 10165 } else { 10166 self._listener = null; 10167 } 10168 }, 100); 10169}; 10170/** 10171 * Remove a notification on the map 10172 */ 10173ol.control.Notification.prototype.hide = function() { 10174 if (this._listener) { 10175 clearTimeout(this._listener); 10176 this._listener = null; 10177 } 10178 this.element.classList.add('ol-collapsed'); 10179}; 10180/** 10181 * Toggle a notification on the map 10182 * @param {number} [duration=3000] duration in ms 10183 */ 10184ol.control.Notification.prototype.toggle = function(duration) { 10185 if (this.element.classList.contains('ol-collapsed')) { 10186 this.show(null, duration); 10187 } else { 10188 this.hide(); 10189 } 10190}; 10191 10192/* Copyright (c) 2017 Jean-Marc VIGLINO, 10193 released under the CeCILL-B license (French BSD license) 10194 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 10195*/ 10196/** Control overlay for OL3 10197 * The overlay control is a control that display an overlay over the map 10198 * 10199 * @constructor 10200 * @extends {ol.control.Control} 10201 * @fire change:visible 10202 * @param {Object=} options Control options. 10203 * @param {String} options.className class of the control 10204* @param {String|Element} options.content 10205* @param {bool} options.hideOnClick hide the control on click, default false 10206* @param {bool} options.closeBox add a closeBox to the control, default false 10207*/ 10208ol.control.Overlay = function(options) { 10209 if (!options) options={}; 10210/* 10211 var element = document.createElement("div"); 10212 element.classList.add('ol-unselectable', 'ol-overlay'); 10213 //if (options.className) element.classList.add(options.className); 10214*/ 10215 var element = ol.ext.element.create('DIV', { 10216 className: 'ol-unselectable ol-overlay '+(options.className||''), 10217 html: options.content 10218 }); 10219 ol.control.Control.call(this, { 10220 element: element, 10221 target: options.target 10222 }); 10223 var self = this; 10224 if (options.hideOnClick) element.addEventListener("click", function(){self.hide();}); 10225 this.set("closeBox", options.closeBox); 10226 this._timeout = false;
10227 this.setContent (options.content); 10228}; 10229ol.ext.inherits(ol.control.Overlay, ol.control.Control); 10230/** Set the content of the overlay 10231* @param {string|Element} html the html to display in the control 10232*/ 10233ol.control.Overlay.prototype.setContent = function (html) { 10234 var self = this; 10235 if (html) { 10236 var elt = this.element; 10237 if (html instanceof Element) { 10238 elt.innerHTML=''; 10239 elt.appendChild(html) 10240 } 10241 else if (html!==undefined) elt.innerHTML = html; 10242 if (this.get("closeBox")) { 10243 var cb = document.createElement("div"); 10244 cb.classList.add("ol-closebox"); 10245 cb.addEventListener("click", function(){self.hide();}); 10246 elt.insertBefore(cb, elt.firstChild); 10247 } 10248 } 10249}; 10250/** Set the control visibility 10251* @param {string|Element} html the html to display in the control 10252* @param {ol.coordinate} coord coordinate of the top left corner of the control to start from 10253*/ 10254ol.control.Overlay.prototype.show = function (html, coord) { 10255 var self = this; 10256 var elt = this.element; 10257 elt.style.display = 'block'; 10258 if (coord) { 10259 this.center_ = this.getMap().getPixelFromCoordinate(coord); 10260 elt.style.top = this.center_[1]+'px'; 10261 elt.style.left = this.center_[0]+'px'; 10262 } else { 10263 //TODO: Do fix from hkollmann pull request 10264 this.center_ = false; 10265 elt.style.top = ""; 10266 elt.style.left = ""; 10267 } 10268 if (html) this.setContent(html); 10269 if (this._timeout) clearTimeout(this._timeout); 10270 this._timeout = setTimeout(function() { 10271 elt.classList.add("ol-visible") 10272 elt.style.top = ""; 10273 elt.style.left = ""; 10274 self.dispatchEvent({ type:'change:visible', visible:true, element: self.element }); 10275 }, 10); 10276}; 10277/** Show an image 10278 * @param {string} src image url 10279 * @param {*} options 10280 * @param {string} options.title 10281 * @param {ol.coordinate} coordinate 10282 */ 10283ol.control.Overlay.prototype.showImage = function (src, options) { 10284 options = options || {}; 10285 var content = ol.ext.element.create('DIV', { 10286 className: 'ol-fullscreen-image' 10287 }); 10288 ol.ext.element.create('IMG', { 10289 src: src, 10290 parent: content 10291 }); 10292 if (options.title) { 10293 content.classList.add('ol-has-title'); 10294 ol.ext.element.create('P', { 10295 html: options.title, 10296 parent: content 10297 }); 10298 } 10299 this.show(content, options.coordinate); 10300}; 10301/** Set the control visibility hidden 10302*/ 10303ol.control.Overlay.prototype.hide = function () { 10304 var elt = this.element; 10305 this.element.classList.remove("ol-visible"); 10306 if (this.center_) { 10307 elt.style.top = this.center_[1]+'px'; 10308 elt.style.left = this.center_[0]+'px'; 10309 this.center_ = false; 10310 } 10311 if (this._timeout) clearTimeout(this._timeout); 10312 this._timeout = setTimeout(function(){ elt.style.display = 'none'; }, 500); 10313 this.dispatchEvent({ type:'change:visible', visible:false, element: this.element }); 10314}; 10315/** Toggle control visibility 10316*/ 10317ol.control.Overlay.prototype.toggle = function () { 10318 if (this.getVisible()) this.hide(); 10319 else this.show(); 10320} 10321/** Get the control visibility 10322* @return {boolean} b 10323*/ 10324ol.control.Overlay.prototype.getVisible = function () { 10325 return ol.ext.element.getStyle(this.element, 'display') !== 'none'; 10326}; 10327/** Change class name 10328* @param {String} className a class name or a list of class names separated by a space 10329*/ 10330ol.control.Overlay.prototype.setClass = function (className) { 10331 var vis = this.element.classList.contains('ol-visible'); 10332 this.element.className = ('ol-unselectable ol-overlay '+(vis ? 'ol-visible ' : '')+className).trim(); 10333}; 10334 10335/* Copyright (c) 2016 Jean-Marc VIGLINO, 10336 released under the CeCILL-B license (French BSD license) 10337 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 10338*/ 10339/** 10340 * OpenLayers 3 Layer Overview Control. 10341 * The overview can rotate with map. 10342 * Zoom levels are configurable. 10343 * Click on the overview will center the map. 10344 * Change width/height of the overview trough css. 10345 * 10346 * @constructor 10347 * @extends {ol.control.Control} 10348 * @param {Object=} options Control options. 10349 * @param {ol.ProjectionLike} options.projection The projection. Default is EPSG:3857 (Spherical Mercator). 10350 * @param {Number} options.minZoom default 0 10351 * @param {Number} options.maxZoom default 18 10352 * @param {boolean} options.rotation enable rotation, default false 10353 * @param {top|bottom-left|right} options.align position 10354 * @param {Array<ol.layer>} options.layers list of layers 10355 * @param {ol.style.Style | Array.<ol.style.Style> | undefined} options.style style to draw the map extent on the overveiw 10356 * @param {bool|elastic} options.panAnimation use animation to center map on click, default true 10357 */ 10358ol.control.Overview = function(options) 10359{ options = options || {}; 10360 var self = this; 10361 // API 10362 this.minZoom = options.minZoom || 0; 10363 this.maxZoom = options.maxZoom || 18; 10364 this.rotation = options.rotation; 10365 var element; 10366 if (options.target) { 10367 element = document.createElement("div"); 10368 this.panel_ = options.target; 10369 } else { 10370 element = document.createElement("div"); 10371 element.classList.add('ol-overview', 'ol-unselectable', 'ol-control', 'ol-collapsed'); 10372 if (/top/.test(options.align)) element.classList.add('ol-control-top'); 10373 if (/right/.test(options.align)) element.classList.add('ol-control-right'); 10374 var button = document.createElement("button"); 10375 button.setAttribute('type','button'); 10376 button.addEventListener("touchstart", function(e){ self.toggleMap(); e.preventDefault(); }); 10377 button.addEventListener("click", function(){self.toggleMap()}); 10378 element.appendChild(button); 10379 this.panel_ = document.createElement("div"); 10380 this.panel_.classList.add("panel"); 10381 element.appendChild(this.panel_); 10382 } 10383 ol.control.Control.call(this, { 10384 element: element, 10385 target: options.target 10386 }); 10387 // Create a overview map 10388 this.ovmap_ = new ol.Map({ 10389 controls: new ol.Collection(), 10390 interactions: new ol.Collection(), 10391 target: this.panel_, 10392 view: new ol.View ({ 10393 zoom: 2, 10394 center: [0,0], 10395 projection: options.projection 10396 }), 10397 layers: options.layers 10398 }); 10399 this.oview_ = this.ovmap_.getView(); 10400 // Cache extent 10401 this.extentLayer = new ol.layer.Vector({ 10402 name: 'Cache extent', 10403 source: new ol.source.Vector(), 10404 style: options.style || [new ol.style.Style({ 10405 image: new ol.style.Circle({ 10406 fill: new ol.style.Fill({ 10407 color: 'rgba(255,0,0, 1)' 10408 }), 10409 stroke: new ol.style.Stroke({ 10410 width: 7, 10411 color: 'rgba(255,0,0, 0.8)' 10412 }), 10413 radius: 5 10414 }), 10415 stroke: new ol.style.Stroke({ 10416 width: 5, 10417 color: "rgba(255,0,0,0.8)" 10418 }) 10419 } 10420 )] 10421 }) 10422 this.ovmap_.addLayer(this.extentLayer); 10423 /** Elastic bounce 10424 * @param {Int} bounce number of bounce 10425 * @param {Number} amplitude amplitude of the bounce [0,1] 10426 * @return {Number} 10427 * / 10428 var bounceFn = function (bounce, amplitude){ 10429 var a = (2*bounce+1) * Math.PI/2; 10430 var b = amplitude>0 ? -1/amplitude : -100; 10431 var c = - Math.cos(a) * Math.pow(2, b); 10432 return function(t) { 10433 t = 1-Math.cos(t*Math.PI/2); 10434 return 1 + Math.abs( Math.cos(a*t) ) * Math.pow(2, b*t) + c*t; 10435 } 10436 } 10437 /** Elastic bounce 10438 * @param {Int} bounce number of bounce 10439 * @param {Number} amplitude amplitude of the bounce [0,1] 10440 * @return {Number} 10441 */ 10442 var elasticFn = function (bounce, amplitude) { 10443 var a = 3*bounce * Math.PI/2; 10444 var b = amplitude>0 ? -1/amplitude : -100; 10445 var c = Math.cos(a) * Math.pow(2, b); 10446 return function(t){ 10447 t = 1-Math.cos(t*Math.PI/2); 10448 return 1 - Math.cos(a*t) * Math.pow(2, b*t) + c*t; 10449 } 10450 } 10451 // Click on the preview center the map 10452 this.ovmap_.addInteraction (new ol.interaction.Pointer({ 10453 handleDownEvent: function(evt) { 10454 if (options.panAnimation !==false) { 10455 if (options.panAnimation=="elastic" || options.elasticPan) { 10456 self.getMap().getView().animate({ 10457 center: evt.coordinate, 10458 easing: elasticFn(2,0.3), 10459 duration: 1000 10460 }); 10461 } else { 10462 self.getMap().getView().animate({ 10463 center: evt.coordinate, 10464 duration: 300 10465 }); 10466 } 10467 }
10468 else self.getMap().getView().setCenter(evt.coordinate); 10469 return false; 10470 } 10471 })); 10472}; 10473ol.ext.inherits(ol.control.Overview, ol.control.Control); 10474/** Get overview map 10475* @return {ol.Map} 10476*/ 10477ol.control.Overview.prototype.getOverviewMap = function(){ 10478 return this.ovmap_; 10479}; 10480/** Toggle overview map 10481*/ 10482ol.control.Overview.prototype.toggleMap = function(){ 10483 this.element.classList.toggle("ol-collapsed"); 10484 this.ovmap_.updateSize(); 10485 this.setView(); 10486}; 10487/** Set overview map position 10488* @param {top|bottom-left|right} 10489*/ 10490ol.control.Overview.prototype.setPosition = function(align){ 10491 if (/top/.test(align)) this.element.classList.add("ol-control-top"); 10492 else this.element.classList.remove("ol-control-top"); 10493 if (/right/.test(align)) this.element.classList.add("ol-control-right"); 10494 else this.element.classList.remove("ol-control-right"); 10495}; 10496/** 10497 * Set the map instance the control associated with. 10498 * @param {ol.Map} map The map instance. 10499 */ 10500ol.control.Overview.prototype.setMap = function(map) { 10501 if (this._listener) { 10502 for (var i in this._listener) { 10503 ol.Observable.unByKey(this._listener[i]); 10504 } 10505 } 10506 this._listener = {}; 10507 ol.control.Control.prototype.setMap.call(this, map); 10508 if (map) { 10509 this._listener.map = map.on('change:view', function() { 10510 if (this._listener.view) ol.Observable.unByKey(this._listener.view); 10511 if (map.getView()) { 10512 this._listener.view = map.getView().on('propertychange', this.setView.bind(this)); 10513 this.setView(); 10514 } 10515 }.bind(this)); 10516 this._listener.view = map.getView().on('propertychange', this.setView.bind(this)); 10517 this.setView(); 10518 } 10519}; 10520/** Calculate the extent of the map and draw it on the overview 10521*/ 10522ol.control.Overview.prototype.calcExtent_ = function(extent){ 10523 var map = this.getMap(); 10524 if (!map) return; 10525 var source = this.extentLayer.getSource(); 10526 source.clear(); 10527 var f = new ol.Feature(); 10528 var size = map.getSize(); 10529 var resolution = map.getView().getResolution(); 10530 var rotation = map.getView().getRotation(); 10531 var center = map.getView().getCenter(); 10532 if (!resolution) return; 10533 var dx = resolution * size[0] / 2; 10534 var dy = resolution * size[1] / 2; 10535 var res2 = this.oview_.getResolution(); 10536 if (dx/res2>5 || dy/res2>5) { 10537 var cos = Math.cos(rotation); 10538 var sin = Math.sin(rotation); 10539 var i, x, y; 10540 extent=[[-dx,-dy],[-dx,dy],[dx,dy],[dx,-dy]]; 10541 for (i = 0; i < 4; ++i) { 10542 x = extent[i][0]; 10543 y = extent[i][1]; 10544 extent[i][0] = center[0] + x * cos - y * sin; 10545 extent[i][1] = center[1] + x * sin + y * cos; 10546 } 10547 f.setGeometry (new ol.geom.Polygon( [ extent ])); 10548 } else { 10549 f.setGeometry (new ol.geom.Point( center )); 10550 } 10551 source.addFeature(f); 10552}; 10553/** 10554* @private 10555*/ 10556ol.control.Overview.prototype.setView = function(e){ 10557 if (!e) { 10558 // refresh all 10559 this.setView({key:'rotation'}); 10560 this.setView({key:'resolution'}); 10561 this.setView({key:'center'}); 10562 return; 10563 } 10564 // Set the view params 10565 switch (e.key){ 10566 case 'rotation': { 10567 if (this.rotation) this.oview_.setRotation(this.getMap().getView().getRotation()); 10568 else if (this.oview_.getRotation()) this.oview_.setRotation(0); 10569 break; 10570 } 10571 case 'center': { 10572 var mapExtent = this.getMap().getView().calculateExtent(this.getMap().getSize()); 10573 var extent = this.oview_.calculateExtent(this.ovmap_.getSize()); 10574 if (mapExtent[0]<extent[0] || mapExtent[1]<extent[1] 10575 || mapExtent[2]>extent[2] || mapExtent[3]>extent[3]){ 10576 this.oview_.setCenter(this.getMap().getView().getCenter()); 10577 } 10578 break; 10579 } 10580 case 'resolution': { 10581 //var z = Math.round(this.getMap().getView().getZoom()/2)*2-4; 10582 var z = Math.round(this.oview_.getZoomForResolution(this.getMap().getView().getResolution())/2)*2-4; 10583 z = Math.min ( this.maxZoom, Math.max(this.minZoom, z) ); 10584 this.oview_.setZoom(z); 10585 break; 10586 } 10587 default: break; 10588 } 10589 this.calcExtent_(); 10590}; 10591 10592/* Copyright (c) 2015-2018 Jean-Marc VIGLINO, 10593 released under the CeCILL-B license (French BSD license) 10594 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 10595*/ 10596/** 10597 * Set an hyperlink that will return the user to the current map view. 10598 * Just add a `permalink`property to layers to be handled by the control (and added in the url). 10599 * The layer's permalink property is used to name the layer in the url. 10600 * The control must be added after all layer are inserted in the map to take them into acount. 10601 * 10602 * @constructor 10603 * @extends {ol.control.Control} 10604 * @param {Object=} options 10605 * @param {boolean} options.urlReplace replace url or not, default true 10606 * @param {boolean|string} [options.localStorage=false] save current map view in localStorage, if 'position' only store map position 10607 * @param {boolean} options.geohash use geohash instead of lonlat, default false 10608 * @param {integer} options.fixed number of digit in coords, default 6 10609 * @param {boolean} options.anchor use "#" instead of "?" in href 10610 * @param {boolean} options.visible hide the button on the map, default true 10611 * @param {boolean} options.hidden hide the button on the map, default false DEPRECATED: use visible instead 10612 * @param {function} options.onclick a function called when control is clicked 10613*/ 10614ol.control.Permalink = function(opt_options) { 10615 var options = opt_options || {}; 10616 var self = this; 10617 var button = document.createElement('button'); 10618 this.replaceState_ = (options.urlReplace!==false); 10619 this.fixed_ = options.fixed || 6; 10620 this.hash_ = options.anchor ? "#" : "?"; 10621 this._localStorage = options.localStorage; 10622 if (!this._localStorage) { 10623 try { 10624 localStorage.removeItem('ol@permalink'); 10625 } catch(e) { console.warn('Failed to access localStorage...'); } 10626 } 10627 function linkto() { 10628 if (typeof(options.onclick) == 'function') options.onclick(self.getLink()); 10629 else self.setUrlReplace(!self.replaceState_); 10630 } 10631 button.addEventListener('click', linkto, false); 10632 button.addEventListener('touchstart', linkto, false); 10633 var element = document.createElement('div'); 10634 element.className = (options.className || "ol-permalink") + " ol-unselectable ol-control"; 10635 element.appendChild(button); 10636 if (options.hidden || options.visible===false) ol.ext.element.hide(element); 10637 ol.control.Control.call(this, { 10638 element: element, 10639 target: options.target 10640 }); 10641 this.set('geohash', options.geohash); 10642 this.set('initial', false); 10643 this.on ('change', this.viewChange_.bind(this)); 10644 // Save search params 10645 this.search_ = {}; 10646 var init = {}; 10647 var hash = document.location.hash || document.location.search || ''; 10648// console.log('hash', hash) 10649 if (this.replaceState_ && !hash && this._localStorage) { 10650 try { 10651 hash = localStorage['ol@permalink']; 10652 } catch(e) { console.warn('Failed to access localStorage...'); } 10653 } 10654 if (hash) { 10655 hash = hash.replace(/(^#|^\?)/,"").split("&"); 10656 for (var i=0; i<hash.length; i++) { 10657 var t = hash[i].split("="); 10658 switch(t[0]) { 10659 case 'lon': 10660 case 'lat': 10661 case 'z': 10662 case 'r': { 10663 init[t[0]] = t[1]; 10664 break 10665 } 10666 case 'gh': { 10667 var ghash = t[1].split('-'); 10668 var lonlat = ol.geohash.toLonLat(ghash[0]); 10669 init.lon = lonlat[0]; 10670 init.lat = lonlat[1]; 10671 init.z = ghash[1]; 10672 break; 10673 } 10674 case 'l': break; 10675 default: this.search_[t[0]] = t[1]; 10676 } 10677 } 10678 } 10679 if (init.hasOwnProperty('lon')) { 10680 this.set('initial', init); 10681 } 10682 // Decode permalink 10683 if (this.replaceState_) this.setPosition(); 10684}; 10685ol.ext.inherits(ol.control.Permalink, ol.control.Control); 10686/** 10687 * Get the initial position passed by the url 10688 */ 10689ol.control.Permalink.prototype.getInitialPosition = function() { 10690 return this.get('initial'); 10691}; 10692/** 10693 * Set the map instance the control associated with. 10694 * @param {ol.Map} map The map instance. 10695 */ 10696ol.control.Permalink.prototype.setMap = function(map) { 10697 if (this._listener) { 10698 ol.Observable.unByKey(this._listener.change); 10699 ol.Observable.unByKey(this._listener.moveend); 10700 } 10701 this._listener = null; 10702 ol.control.Control.prototype.setMap.call(this, map); 10703 // Get change 10704 if (map) { 10705 this._listener = { 10706 change: map.getLayerGroup().on('change', this.layerChange_.bind(this)), 10707 moveend: map.on('moveend', this.viewChange_.bind(this)) 10708 }; 10709 this.setPosition(); 10710 } 10711}; 10712/** Get layer given a permalink name (permalink propertie in the layer) 10713* @param {string} the permalink to search for 10714* @param {Array<ol.layer>|undefined} an array of layer to search in 10715* @return {ol.layer|false} 10716*/ 10717ol.control.Permalink.prototype.getLayerByLink = function (id, layers) { 10718 if (!layers && this.getMap()) layers = this.getMap().getLayers().getArray(); 10719 for (var i=0; i<layers.length; i++) { 10720 if (layers[i].get('permalink') == id) return layers[i]; 10721 // Layer Group 10722 if (layers[i].getLayers) { 10723 var li = this.getLayerByLink ( id, layers[i].getLayers().getArray() ); 10724 if (li) return li; 10725 } 10726 } 10727 return false;
10728}; 10729/** Set coordinates as geohash 10730 * @param {boolean} 10731 */ 10732ol.control.Permalink.prototype.setGeohash = function(b) { 10733 this.set('geohash', b); 10734 this.setUrlParam(); 10735}; 10736/** Set map position according to the current link 10737 * @param {boolean} [force=false] if true set the position even if urlReplace is disabled 10738 */ 10739ol.control.Permalink.prototype.setPosition = function(force) { 10740 var map = this.getMap(); 10741 if (!map) return; 10742 var hash = (this.replaceState_ || force) ? document.location.hash || document.location.search : ''; 10743 if (!hash && this._localStorage) { 10744 try { 10745 hash = localStorage['ol@permalink']; 10746 } catch(e) { console.warn('Failed to access localStorage...'); } 10747 } 10748 if (!hash) return; 10749 var i, t, param = {}; 10750 hash = hash.replace(/(^#|^\?)/,"").split("&"); 10751 for (i=0; i<hash.length; i++) { 10752 t = hash[i].split("="); 10753 param[t[0]] = t[1]; 10754 } 10755 if (param.gh) { 10756 var ghash = param.gh.split('-'); 10757 var lonlat = ol.geohash.toLonLat(ghash[0] ); 10758 param.lon = lonlat[0]; 10759 param.lat = lonlat[1]; 10760 param.z = ghash[1]; 10761 } 10762 var c = ol.proj.transform([Number(param.lon),Number(param.lat)], 'EPSG:4326', map.getView().getProjection()); 10763 if (c[0] && c[1]) map.getView().setCenter(c); 10764 if (param.z) map.getView().setZoom(Number(param.z)); 10765 if (param.r) map.getView().setRotation(Number(param.r)); 10766 // Reset layers visibility 10767 function resetLayers(layers) { 10768 if (!layers) layers = map.getLayers().getArray(); 10769 for (var i=0; i<layers.length; i++){ 10770 if (layers[i].get('permalink')) { 10771 layers[i].setVisible(false); 10772 // console.log("hide "+layers[i].get('permalink')); 10773 } 10774 if (layers[i].getLayers) { 10775 resetLayers (layers[i].getLayers().getArray()); 10776 } 10777 } 10778 } 10779 if (param.l) { 10780 resetLayers(); 10781 var l = param.l.split("|"); 10782 for (i=0; i<l.length; i++) { 10783 t = l[i].split(":"); 10784 var li = this.getLayerByLink(t[0]); 10785 var op = Number(t[1]); 10786 if (li) { 10787 li.setOpacity(op); 10788 li.setVisible(true); 10789 } 10790 } 10791 } 10792}; 10793/** 10794 * Get the parameters added to the url. The object can be changed to add new values. 10795 * @return {Object} a key value object added to the url as &key=value 10796 * @api stable 10797 */ 10798ol.control.Permalink.prototype.getUrlParams = function() { 10799 return this.search_; 10800}; 10801/** 10802 * Set a parameter to the url. 10803 * @param {string} key the key parameter 10804 * @param {string|undefined} value the parameter's value, if undefined or empty string remove the parameter 10805 * @api stable 10806 */ 10807ol.control.Permalink.prototype.setUrlParam = function(key, value) { 10808 if (key) { 10809 if (value===undefined || value==='') delete (this.search_[encodeURIComponent(key)]) 10810 else this.search_[encodeURIComponent(key)] = encodeURIComponent(value); 10811 } 10812 this.viewChange_(); 10813}; 10814/** 10815 * Get a parameter url. 10816 * @param {string} key the key parameter 10817 * @return {string} the parameter's value or empty string if not set 10818 * @api stable 10819 */ 10820ol.control.Permalink.prototype.getUrlParam = function(key) { 10821 return decodeURIComponent (this.search_[encodeURIComponent(key)] || ''); 10822}; 10823/** 10824 * Has a parameter url. 10825 * @param {string} key the key parameter 10826 * @return {boolean} 10827 * @api stable 10828 */ 10829ol.control.Permalink.prototype.hasUrlParam = function(key) { 10830 return this.search_.hasOwnProperty(encodeURIComponent(key)); 10831}; 10832/** Get the permalink 10833 * @param {boolean|string} [search=false] false: return full link | true: return the search string only | 'position': return position string 10834 * @return {permalink} 10835 */ 10836ol.control.Permalink.prototype.getLink = function(search) { 10837 var map = this.getMap(); 10838 var c = ol.proj.transform(map.getView().getCenter(), map.getView().getProjection(), 'EPSG:4326'); 10839 var z = Math.round(map.getView().getZoom()*10)/10; 10840 var r = map.getView().getRotation(); 10841 var l = this.layerStr_; 10842 // Change anchor 10843 var anchor = (r ? "&r="+(Math.round(r*10000)/10000) : "") + (l ? "&l="+l : ""); 10844 if (this.get('geohash')) { 10845 var ghash = ol.geohash.fromLonLat(c,8); 10846 anchor = "gh=" + ghash + '-' + z + anchor; 10847 } else { 10848 anchor = "lon="+c[0].toFixed(this.fixed_)+"&lat="+c[1].toFixed(this.fixed_)+"&z="+z + anchor; 10849 } 10850 if (search === 'position') return anchor; 10851 // Add other params 10852 for (var i in this.search_) { 10853 anchor += "&" + i + (typeof(this.search_[i])!=='undefined' ? "="+this.search_[i] : ''); 10854 } 10855 if (search) return anchor; 10856 //return document.location.origin+document.location.pathname+this.hash_+anchor; 10857 return document.location.protocol+"//"+document.location.host+document.location.pathname+this.hash_+anchor; 10858}; 10859/** Check if urlreplace is on 10860 * @return {boolean} 10861 */ 10862ol.control.Permalink.prototype.getUrlReplace = function() { 10863 return this.replaceState_; 10864}; 10865/** Enable / disable url replacement (replaceSate) 10866 * @param {bool} 10867 */ 10868ol.control.Permalink.prototype.setUrlReplace = function(replace) { 10869 try { 10870 this.replaceState_ = replace; 10871 if (!replace) { 10872 var s = ""; 10873 for (var i in this.search_) { 10874 s += (s==""?"?":"&") + i + (typeof(this.search_[i]) !== 'undefined' ? "="+this.search_[i] : ''); 10875 } 10876 window.history.replaceState (null,null, document.location.origin+document.location.pathname+s); 10877 } 10878 else window.history.replaceState (null,null, this.getLink()); 10879 } catch(e) {/* ok */} 10880 /* 10881 if (this._localStorage) { 10882 localStorage['ol@permalink'] = this.getLink(true); 10883 } 10884 */ 10885}; 10886/** 10887 * On view change refresh link 10888 * @param {ol.event} The map instance. 10889 * @private 10890 */ 10891ol.control.Permalink.prototype.viewChange_ = function() { 10892 try { 10893 if (this.replaceState_) window.history.replaceState (null,null, this.getLink()); 10894 } catch(e) {/* ok */} 10895 if (this._localStorage) { 10896 try { 10897 localStorage['ol@permalink'] = this.getLink(this._localStorage); 10898 } catch(e) { console.warn('Failed to access localStorage...'); } 10899 } 10900}; 10901/** 10902 * Layer change refresh link 10903 * @private 10904 */ 10905ol.control.Permalink.prototype.layerChange_ = function() { 10906 // Prevent multiple change at the same time 10907 if (this._tout) { 10908 clearTimeout(this._tout); 10909 this._tout = null; 10910 } 10911 this._tout = setTimeout(function() { 10912 this._tout = null; 10913 // Get layers 10914 var l = ""; 10915 function getLayers(layers) { 10916 for (var i=0; i<layers.length; i++) { 10917 if (layers[i].getVisible() && layers[i].get("permalink")) { 10918 if (l) l += "|"; 10919 l += layers[i].get("permalink")+":"+layers[i].get("opacity"); 10920 } 10921 // Layer Group 10922 if (layers[i].getLayers) getLayers(layers[i].getLayers().getArray()); 10923 } 10924 } 10925 getLayers(this.getMap().getLayers().getArray()); 10926 this.layerStr_ = l; 10927 this.viewChange_(); 10928 }.bind(this), 200); 10929}; 10930 10931/* 10932 Copyright (c) 2019 Jean-Marc VIGLINO, 10933 released under the CeCILL-B license (French BSD license)
10934 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 10935*/ 10936/** Print control to get an image of the map 10937 * @constructor 10938 * @fire print 10939 * @fire error 10940 * @fire printing 10941 * @extends {ol.control.Control} 10942 * @param {Object=} options Control options. 10943 * @param {String} options.className class of the control 10944 * @param {String} options.title button title 10945 * @param {string} options.imageType A string indicating the image format, default image/jpeg 10946 * @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 10947 * @param {string} options.orientation Page orientation (landscape/portrait), default guest the best one 10948 * @param {boolean} options.immediate force print even if render is not complete, default false 10949 */ 10950ol.control.Print = function(options) { 10951 if (!options) options = {}; 10952 var element = ol.ext.element.create('DIV', { 10953 className: (options.className || 'ol-print') 10954 }); 10955 if (!options.target) { 10956 element.classList.add('ol-unselectable', 'ol-control'); 10957 ol.ext.element.create('BUTTON', { 10958 type: 'button', 10959 title: options.title || 'Print', 10960 click: function() { this.print(); }.bind(this), 10961 parent: element 10962 }); 10963 } 10964 ol.control.Control.call(this, { 10965 element: element, 10966 target: options.target 10967 }); 10968 this.set('immediate', options.immediate); 10969 this.set('imageType', options.imageType || 'image/jpeg'); 10970 this.set('quality', options.quality || .8); 10971 this.set('orientation', options.orientation); 10972}; 10973ol.ext.inherits(ol.control.Print, ol.control.Control); 10974/** Helper function to copy result to clipboard 10975 * @param {Event} e print event 10976 * @return {boolean} 10977 * @private 10978 */ 10979ol.control.Print.prototype.toClipboard = function(e, callback) { 10980 try { 10981 e.canvas.toBlob(function(blob) { 10982 try { 10983 navigator.clipboard.write([ 10984 new window.ClipboardItem( 10985 Object.defineProperty({}, blob.type, { 10986 value: blob, 10987 enumerable: true 10988 }) 10989 ) 10990 ]) 10991 if (typeof(callback) === 'function') callback(true); 10992 } catch (err) { 10993 if (typeof(callback) === 'function') callback(false); 10994 } 10995 }); 10996 } catch(err) { 10997 if (typeof(callback) === 'function') callback(false); 10998 } 10999}; 11000/** Helper function to copy result to clipboard 11001 * @param {any} options print options 11002 * @param {function} callback a callback function that takes a boolean if copy 11003 */ 11004ol.control.Print.prototype.copyMap = function(options, callback) { 11005 this.once('print', function(e) { 11006 this.toClipboard(e, callback); 11007 }.bind(this)); 11008 this.print(options); 11009}; 11010/** Get map canvas 11011 * @private 11012 */ 11013ol.control.Print.prototype._getCanvas = function(event, imageType, canvas) { 11014 var ctx; 11015 // ol <= 5 : get the canvas 11016 if (event.context) { 11017 canvas = event.context.canvas; 11018 } else { 11019 // Create a canvas if none 11020 if (!canvas) { 11021 canvas = document.createElement('canvas'); 11022 var size = this.getMap().getSize(); 11023 canvas.width = size[0]; 11024 canvas.height = size[1]; 11025 ctx = canvas.getContext('2d'); 11026 if (/jp.*g$/.test(imageType)) { 11027 ctx.fillStyle = this.get('bgColor') || 'white'; 11028 ctx.fillRect(0,0,canvas.width,canvas.height); 11029 } 11030 } else { 11031 ctx = canvas.getContext('2d'); 11032 } 11033 // ol6+ : create canvas using layer canvas 11034 this.getMap().getViewport().querySelectorAll('.ol-layers canvas, canvas.ol-fixedoverlay').forEach(function(c) { 11035 if (c.width) { 11036 ctx.save(); 11037 // opacity 11038 if (c.parentNode.style.opacity==='0') return; 11039 ctx.globalAlpha = parseFloat(c.parentNode.style.opacity) || 1; 11040 // Blend mode ? 11041 if (ol.ext.element.getStyle(c.parentNode, 'mix-blend-mode') === 'multiply') { 11042 ctx.globalCompositeOperation = 'multiply'; 11043 } 11044 // transform 11045 var tr = ol.ext.element.getStyle(c,'transform') || ol.ext.element.getStyle(c,'-webkit-transform'); 11046 if (/^matrix/.test(tr)) { 11047 tr = tr.replace(/^matrix\(|\)$/g,'').split(','); 11048 tr.forEach(function(t,i) { tr[i] = parseFloat(t); }); 11049 ctx.transform(tr[0],tr[1],tr[2],tr[3],tr[4],tr[5]); 11050 ctx.drawImage(c, 0, 0); 11051 } else { 11052 ctx.drawImage(c, 0, 0, ol.ext.element.getStyle(c,'width'), ol.ext.element.getStyle(c,'height')); 11053 }
11054 ctx.restore(); 11055 } 11056 }.bind(this)); 11057 } 11058 return canvas; 11059}; 11060/** Fast print 11061 * @param {*} options print options 11062 * @param {HTMLCanvasElement|undefined} [options.canvas] if none create one, only for ol@6+ 11063 * @parama {string} options.imageType 11064 */ 11065ol.control.Print.prototype.fastPrint = function(options, callback) { 11066 options = options || {}; 11067 if (this._ol6) { 11068 requestAnimationFrame(function() { 11069 callback(this._getCanvas({}, options.imageType, options.canvas)); 11070 }.bind(this)); 11071 } else { 11072 this.getMap().once('postcompose', function(event) { 11073 if (!event.context) this._ol6 = true; 11074 callback(this._getCanvas(event, options.imageType, options.canvas)); 11075 }.bind(this)); 11076 this.getMap().render(); 11077 } 11078}; 11079/** Print the map 11080 * @param {function} cback a callback function that take a string containing the requested data URI. 11081 * @param {Object} options 11082 * @param {string} options.imageType A string indicating the image format, default the control one 11083 * @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 11084 * @param {boolean} options.immediate true to prevent delay for printing 11085 * @param {boolean} [options.size=[210,297]] 11086 * @param {boolean} [options.format=a4] 11087 * @param {boolean} [options.orient] default control orientation 11088 * @param {boolean} [options.margin=10] 11089 * @param {*} options.any any options passed to the print event when fired 11090 * @api 11091 */ 11092ol.control.Print.prototype.print = function(options) { 11093 options = options || {}; 11094 var imageType = options.imageType || this.get('imageType'); 11095 var quality = options.quality || this.get('quality'); 11096 if (this.getMap()) { 11097 if (options.immediate !== 'silent') { 11098 this.dispatchEvent(Object.assign({ 11099 type: 'printing', 11100 }, options)); 11101 } 11102 // Start printing after delay to var user show info in the DOM 11103 if (!options.immediate) { 11104 setTimeout (function () { 11105 options = Object.assign({}, options); 11106 options.immediate = 'silent'; 11107 this.print(options); 11108 }.bind(this), 200); 11109 return; 11110 } 11111 // Run printing 11112 this.getMap().once(this.get('immediate') ? 'postcompose' : 'rendercomplete', function(event) { 11113 var canvas = this._getCanvas(event, imageType); 11114 // Calculate print format 11115 var size = options.size || [210,297]; 11116 var format = options.format || 'a4'; 11117 var w, h, position; 11118 var orient = options.orient || this.get('orientation'); 11119 var margin = typeof(options.margin)==='number' ? options.margin : 10; 11120 if (canvas) { 11121 // Calculate size 11122 if (orient!=='landscape' && orient!=='portrait') { 11123 orient = (canvas.width > canvas.height) ? 'landscape' : 'portrait'; 11124 } 11125 if (orient === 'landscape') size = [size[1],size[0]]; 11126 var sc = Math.min ((size[0]-2*margin)/canvas.width,(size[1]-2*margin)/canvas.height); 11127 w = sc * canvas.width; 11128 h = sc * canvas.height; 11129 // Image position 11130 position = [(size[0] - w)/2, (size[1] - h)/2]; 11131 } 11132 // get the canvas image 11133 var image; 11134 try { 11135 image = canvas ? canvas.toDataURL(imageType, quality) : null; 11136 } catch(e) { 11137 // Fire error event 11138 this.dispatchEvent({ 11139 type: 'error', 11140 canvas: canvas 11141 }); 11142 return; 11143 } 11144 // Fire print event 11145 var e = Object.assign({ 11146 type: 'print', 11147 print: { 11148 format: format, 11149 orientation: orient, 11150 unit: 'mm', 11151 size: size, 11152 position: position, 11153 imageWidth: w, 11154 imageHeight: h 11155 }, 11156 image: image, 11157 imageType: imageType, 11158 quality: quality, 11159 canvas: canvas 11160 }, options); 11161 this.dispatchEvent(e); 11162 }.bind(this)); 11163 this.getMap().render(); 11164 } 11165}; 11166 11167/* 11168 Copyright (c) 2019 Jean-Marc VIGLINO, 11169 released under the CeCILL-B license (French BSD license) 11170 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 11171*/ 11172/** Print control to get an image of the map 11173 * @constructor 11174 * @fire show 11175 * @fire print 11176 * @fire error 11177 * @fire printing 11178 * @extends {ol.control.Control} 11179 * @param {Object=} options Control options. 11180 * @param {string} options.className class of the control 11181 * @param {String} options.title button title 11182 * @param {string} [options.lang=en] control language, default en 11183 * @param {string}
11183 options.imageType A string indicating the image format, default image/jpeg 11184 * @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 11185 * @param {string} options.orientation Page orientation (landscape/portrait), default guest the best one 11186 * @param {boolean} options.immediate force print even if render is not complete, default false 11187 * @param {boolean} [options.openWindow=false] open the file in a new window on print 11188 * @param {boolean} [options.copy=true] add a copy select option 11189 * @param {boolean} [options.print=true] add a print select option 11190 * @param {boolean} [options.pdf=true] add a pdf select option 11191 * @param {function} [options.saveAs] a function to save the image as blob 11192 * @param {*} [options.jsPDF] jsPDF object to save map as pdf 11193 */ 11194ol.control.PrintDialog = function(options) { 11195 if (!options) options = {}; 11196 this._lang = options.lang || 'en'; 11197 var element = ol.ext.element.create('DIV', { 11198 className: (options.className || 'ol-print') + ' ol-unselectable ol-control' 11199 }); 11200 ol.ext.element.create('BUTTON', { 11201 type: 'button', 11202 title: options.title || 'Print', 11203 click: function() { 11204 this.print(); 11205 }.bind(this), 11206 parent: element 11207 }); 11208 ol.control.Control.call(this, { 11209 element: element 11210 }); 11211 // Open in a new window 11212 if (options.openWindow) { 11213 this.on('print', function(e) { 11214 // Print success 11215 if (e.canvas) {
11216 window.open().document.write('<img src="'+e.canvas.toDataURL()+'"/>'); 11217 } 11218 }); 11219 } 11220 // Print control 11221 options.target = ol.ext.element.create('DIV'); 11222 var printCtrl = this._printCtrl = new ol.control.Print(options); 11223 printCtrl.on(['print','error','printing'], function(e) { 11224 content.setAttribute('data-status', e.type); 11225 if (!e.clipboard) { 11226 this.dispatchEvent(e); 11227 } 11228 }.bind(this)); 11229 // North arrow 11230 this._compass = new ol.control.Compass({ 11231 src: options.northImage || 'compact', 11232 visible: false, 11233 className: 'olext-print-compass', 11234 style: new ol.style.Stroke({ color: '#333', width: 0 }) 11235 }); 11236 // Print dialog 11237 var printDialog = this._printDialog = new ol.control.Dialog({ 11238 target: document.body, 11239 closeBox: true, 11240 className: 'ol-ext-print-dialog' 11241 }); 11242 var content = printDialog.getContentElement(); 11243 this._input = {}; 11244 var param = ol.ext.element.create('DIV',{ 11245 className: 'ol-print-param', 11246 parent: content 11247 }); 11248 this._pages = [ ol.ext.element.create('DIV', { 11249 className: 'ol-page' 11250 })]; 11251 var printMap = ol.ext.element.create('DIV', { 11252 className: 'ol-map', 11253 parent: this._pages[0] 11254 }); 11255 ol.ext.element.create('DIV', { 11256 html: this._pages[0], 11257 className: 'ol-print-map', 11258 parent: content 11259 }); 11260 ol.ext.element.create('H2',{ 11261 html: this.i18n('title'), 11262 parent: param 11263 }); 11264 var ul = ol.ext.element.create('UL',{ parent: param }); 11265 // Orientation 11266 var li = ol.ext.element.create('LI', { 11267 /* 11268 html: ol.ext.element.create('LABEL', { 11269 html: this.18n('orientation') 11270 }), 11271 */ 11272 className: 'ol-orientation', 11273 parent: ul 11274 }); 11275 this._input.orientation = { list: li }; 11276 var label = ol.ext.element.create('LABEL', { 11277 className: 'portrait', 11278 parent: li 11279 }); 11280 this._input.orientation.portrait = ol.ext.element.create('INPUT', { 11281 type: 'radio', 11282 name: 'ol-print-orientation', 11283 value: 'portrait', 11284 checked: true, 11285 on: { change: function(e) { 11286 this.setOrientation(e.target.value); 11287 }.bind(this) }, 11288 parent: label 11289 }); 11290 ol.ext.element.create('SPAN', { 11291 html: this.i18n('portrait'), 11292 parent: label 11293 }); 11294 label = ol.ext.element.create('LABEL', { 11295 className: 'landscape', 11296 parent: li 11297 }); 11298 this._input.orientation.landscape = ol.ext.element.create('INPUT',{ 11299 type: 'radio', 11300 name: 'ol-print-orientation', 11301 value: 'landscape', 11302 on: { change: function(e) { 11303 this.setOrientation(e.target.value); 11304 }.bind(this) }, 11305 parent: label 11306 }); 11307 ol.ext.element.create('SPAN', { 11308 html: this.i18n('landscape'), 11309 parent: label 11310 }); 11311 // Page size 11312 var s; 11313 li = ol.ext.element.create('LI',{ 11314 html: ol.ext.element.create('LABEL', { 11315 html: this.i18n('size'), 11316 }), 11317 className: 'ol-size', 11318 parent: ul 11319 }); 11320 var size = this._input.size = ol.ext.element.create('SELECT', { 11321 on: { change: function(){ 11322 this.setSize(size.value || originalSize); 11323 }.bind(this) }, 11324 parent: li 11325 }); 11326 for (s in this.paperSize) { 11327 ol.ext.element.create('OPTION', { 11328 html: s + (this.paperSize[s] ? ' - '+this.paperSize[s][0]+'x'+this.paperSize[s][1]+' mm' : this.i18n('custom')), 11329 value: s, 11330 parent: size 11331 }); 11332 } 11333 // Margin 11334 li = ol.ext.element.create('LI',{ 11335 html: ol.ext.element.create('LABEL', { 11336 html: this.i18n('margin'), 11337 }), 11338 className: 'ol-margin', 11339 parent: ul 11340 }); 11341 var margin = this._input.margin = ol.ext.element.create('SELECT', { 11342 on: { change: function(){ 11343 this.setMargin(margin.value); 11344 }.bind(this) }, 11345 parent: li 11346 }); 11347 for (s in this.marginSize) { 11348 ol.ext.element.create('OPTION', { 11349 html: this.i18n(s) + ' - ' + this.marginSize[s] + ' mm', 11350 value: this.marginSize[s], 11351 parent: margin 11352 }); 11353 } 11354 // Scale 11355 li = ol.ext.element.create('LI',{ 11356 html: ol.ext.element.create('LABEL', { 11357 html: this.i18n('scale'), 11358 }), 11359 className: 'ol-scale', 11360 parent: ul 11361 }); 11362 var scale = this._input.scale = ol.ext.element.create('SELECT', { 11363 on: { change: function() { 11364 this.setScale(parseInt(scale.value)) 11365 }.bind(this) }, 11366 parent: li 11367 });
11368 Object.keys(this.scales).forEach(function(s) { 11369 ol.ext.element.create('OPTION', { 11370 html: this.scales[s], 11371 value: s, 11372 parent: scale 11373 }); 11374 }.bind(this)); 11375 // Legend 11376 li = ol.ext.element.create('LI',{ 11377 className: 'ol-legend', 11378 parent: ul 11379 }); 11380 var legend = ol.ext.element.createSwitch({ 11381 html: (this.i18n('legend')), 11382 checked: false, 11383 on: { change: function() { 11384 extraCtrl.legend.control.setCanvas(legend.checked); 11385 }.bind(this) }, 11386 parent: li 11387 }); 11388 // North 11389 li = ol.ext.element.create('LI',{ 11390 className: 'ol-print-north', 11391 parent: ul 11392 }); 11393 var north = this._input.north = ol.ext.element.createSwitch({ 11394 html: this.i18n('north'), 11395 checked: 'checked', 11396 on: { change: function() { 11397 if (north.checked) this._compass.element.classList.add('ol-print-compass'); 11398 else this._compass.element.classList.remove('ol-print-compass'); 11399 this.getMap().render(); 11400 }.bind(this)}, 11401 parent: li 11402 }); 11403 // Title 11404 li = ol.ext.element.create('LI',{ 11405 className: 'ol-print-title', 11406 parent: ul 11407 }); 11408 var title = ol.ext.element.createSwitch({ 11409 html: this.i18n('mapTitle'), 11410 checked: false, 11411 on: { change: function(e) { 11412 extraCtrl.title.control.setVisible(e.target.checked); 11413 }.bind(this) }, 11414 parent: li 11415 }); 11416 var titleText = ol.ext.element.create('INPUT', { 11417 type: 'text', 11418 placeholder: this.i18n('mapTitle'), 11419 on: { 11420 keydown: function(e) { 11421 if (e.keyCode === 13) e.preventDefault(); 11422 }, 11423 keyup: function() { 11424 extraCtrl.title.control.setTitle(titleText.value); 11425 }, 11426 change: function() { 11427 extraCtrl.title.control.setTitle(titleText.value); 11428 }.bind(this) 11429 }, 11430 parent: li 11431 }); 11432 // User div element 11433 var userElt = ol.ext.element.create('DIV', { 11434 className: 'ol-user-param', 11435 parent: param 11436 }); 11437 // Save as 11438 li = ol.ext.element.create('LI',{ 11439 className: 'ol-saveas', 11440 parent: ul 11441 }); 11442 var copied = ol.ext.element.create('DIV', { 11443 html: this.i18n('copied'), 11444 className: 'ol-clipboard-copy', 11445 parent: li 11446 }); 11447 var save = ol.ext.element.create('SELECT', { 11448 on: { change: function() { 11449 // Copy to clipboard 11450 if (this.formats[save.value].clipboard) { 11451 printCtrl.copyMap(this.formats[save.value], function(isok) { 11452 if (isok) { 11453 copied.classList.add('visible'); 11454 setTimeout(function() { copied.classList.remove('visible'); }, 1000); 11455 } 11456 }); 11457 } else { 11458 // Print to file 11459 var format = (typeof(this.getSize())==='string' ? this.getSize() : null); 11460 var opt = Object.assign({ 11461 format: format, 11462 size: format ? this.paperSize[format] : null, 11463 orient: this.getOrientation(), 11464 margin: this.getMargin(), 11465 }, this.formats[save.value]); 11466 printCtrl.print(opt); 11467 } 11468 save.value = ''; 11469 }.bind(this) }, 11470 parent: li 11471 }); 11472 ol.ext.element.create('OPTION', { 11473 html: this.i18n('saveas'), 11474 style: { display: 'none' }, 11475 value: '', 11476 parent: save 11477 }); 11478 this.formats.forEach(function(format, i) { 11479 if (format.pdf) { 11480 if (options.pdf === false) return; 11481 } else if (format.clipboard) { 11482 if (options.copy === false) return; 11483 } else if (options.save === false) { 11484 return; 11485 } 11486 ol.ext.element.create('OPTION', {
11487 html: this.i18n(format.title), 11488 value: i, 11489 parent: save 11490 }); 11491 }.bind(this)); 11492 // Save Legend 11493 li = ol.ext.element.create('LI',{ 11494 className: 'ol-savelegend', 11495 parent: ul 11496 }); 11497 var copylegend = ol.ext.element.create('DIV', { 11498 html: this.i18n('copied'), 11499 className: 'ol-clipboard-copy', 11500 parent: li 11501 }); 11502 var saveLegend = ol.ext.element.create('SELECT', { 11503 on: { change: function() { 11504 // Print canvas (with white background) 11505 var clegend = extraCtrl.legend.control.getLegend().getCanvas(); 11506 var canvas = document.createElement('CANVAS'); 11507 canvas.width = clegend.width; 11508 canvas.height = clegend.height; 11509 var ctx = canvas.getContext('2d'); 11510 ctx.fillStyle = '#fff'; 11511 ctx.fillRect(0, 0, canvas.width, canvas.height); 11512 ctx.drawImage(clegend, 0, 0); 11513 // Copy to clipboard 11514 if (this.formats[saveLegend.value].clipboard) { 11515 canvas.toBlob(function(blob) { 11516 try { 11517 navigator.clipboard.write([ 11518 new window.ClipboardItem( 11519 Object.defineProperty({}, blob.type, { 11520 value: blob, 11521 enumerable: true 11522 }) 11523 ) 11524 ]) 11525 copylegend.classList.add('visible'); 11526 setTimeout(function() { copylegend.classList.remove('visible'); }, 1000); 11527 } catch (err) { /* errror */ } 11528 }, 'image/png'); 11529 } else { 11530 var image; 11531 try { 11532 image = canvas.toDataURL(this.formats[saveLegend.value].imageType, this.formats[saveLegend.value].qual
11532ity); 11533 var format = (typeof(this.getSize())==='string' ? this.getSize() : 'A4'); 11534 var w = canvas.width / 96 * 25.4; 11535 var h = canvas.height / 96 * 25.4; 11536 var size = this.paperSize[format]; 11537 if (this.getOrientation()==='landscape') size = [size[1], size[0]]; 11538 var position = [ 11539 (size[0] - w) /2, 11540 (size[1] - h) /2 11541 ]; 11542 this.dispatchEvent({ 11543 type: 'print', 11544 print: { 11545 legend: true, 11546 format: format, 11547 orientation: this.getOrientation(), 11548 unit: 'mm', 11549 size: this.paperSize[format], 11550 position: position, 11551 imageWidth: w, 11552 imageHeight: h 11553 }, 11554 image: image, 11555 imageType: this.formats[saveLegend.value].imageType, 11556 pdf: this.formats[saveLegend.value].pdf, 11557 quality: this.formats[saveLegend.value].quality, 11558 canvas: canvas 11559 }) 11560 } catch(err) { /* error */ } 11561 } 11562 saveLegend.value = ''; 11563 }.bind(this) }, 11564 parent: li 11565 }); 11566 ol.ext.element.create('OPTION', { 11567 html: this.i18n('saveLegend'), 11568 style: { display: 'none' }, 11569 value: '', 11570 parent: saveLegend 11571 }); 11572 this.formats.forEach(function(format, i) { 11573 ol.ext.element.create('OPTION', {
11574 html: this.i18n(format.title), 11575 value: i, 11576 parent: saveLegend 11577 }); 11578 }.bind(this)); 11579 // Print 11580 var prButtons = ol.ext.element.create('DIV', { 11581 className: 'ol-ext-buttons', 11582 parent: param 11583 }); 11584 ol.ext.element.create('BUTTON', { 11585 html: this.i18n('printBt'), 11586 type: 'submit', 11587 click: function(e) { 11588 e.preventDefault(); 11589 window.print(); 11590 }, 11591 parent: prButtons 11592 }); 11593 ol.ext.element.create('BUTTON', { 11594 html: this.i18n('cancel'), 11595 type: 'button', 11596 click: function() { printDialog.hide(); }, 11597 parent: prButtons 11598 }); 11599 ol.ext.element.create('DIV', { 11600 html: this.i18n('errorMsg'), 11601 className: 'ol-error', 11602 parent: param 11603 }); 11604 // Handle dialog show/hide 11605 var originalTarget; 11606 var originalSize; 11607 var scalelistener; 11608 var extraCtrl = {}; 11609 printDialog.on('show', function() { 11610 // Dialog is showing 11611 this.dispatchEvent({ type: 'show', userElement: userElt, dialog: this._printDialog, page: this.getPage() }); 11612 // 11613 var map = this.getMap(); 11614 if (!map) return; 11615 // Print document 11616 document.body.classList.add('ol-print-document'); 11617 originalTarget = map.getTargetElement(); 11618 originalSize = map.getSize(); 11619 if (typeof(this.getSize()) === 'string') this.setSize(this.getSize()); 11620 else this.setSize(originalSize); 11621 map.setTarget(printMap); 11622 // Refresh on move end 11623 if (scalelistener) ol.Observable.unByKey(scalelistener); 11624 scalelistener = map.on('moveend', function() { 11625 this.setScale(ol.sphere.getMapScale(map)); 11626 }.bind(this)); 11627 this.setScale(ol.sphere.getMapScale(map)); 11628 // Get extra controls 11629 extraCtrl = {}; 11630 this.getMap().getControls().forEach(function(c) { 11631 if (c instanceof ol.control.Legend) { 11632 extraCtrl.legend = { control: c }; 11633 } 11634 if (c instanceof ol.control.CanvasTitle) { 11635 extraCtrl.title = { control: c }; 11636 } 11637 if (c instanceof ol.control.Compass) { 11638 if (extraCtrl.compass) { 11639 c.element.classList.remove('ol-print-compass') 11640 } else { 11641 if (this._input.north.checked) c.element.classList.add('ol-print-compass') 11642 else c.element.classList.remove('ol-print-compass') 11643 this._compass = c; 11644 extraCtrl.compass = { control: c }; 11645 } 11646 } 11647 }.bind(this)); 11648 // Show hide title 11649 if (extraCtrl.title) { 11650 title.checked = extraCtrl.title.isVisible = extraCtrl.title.control.getVisible(); 11651 titleText.value = extraCtrl.title.control.getTitle(); 11652 title.parentNode.parentNode.classList.remove('hidden'); 11653 } else { 11654 title.parentNode.parentNode.classList.add('hidden'); 11655 } 11656 // Show hide legend 11657 if (extraCtrl.legend) { 11658 extraCtrl.legend.ison = extraCtrl.legend.control.onCanvas(); 11659 extraCtrl.legend.collapsed = extraCtrl.legend.control.isCollapsed(); 11660 extraCtrl.legend.control.collapse(false); 11661 saveLegend.parentNode.classList.remove('hidden'); 11662 legend.parentNode.parentNode.classList.remove('hidden'); 11663 legend.checked = !extraCtrl.legend.collapsed; 11664 extraCtrl.legend.control.setCanvas(!extraCtrl.legend.collapsed); 11665 } else { 11666 saveLegend.parentNode.classList.add('hidden'); 11667 legend.parentNode.parentNode.classList.add('hidden'); 11668 } 11669 }.bind(this)); 11670 printDialog.on('hide', function() { 11671 // No print 11672 document.body.classList.remove('ol-print-document'); 11673 if (!originalTarget) return; 11674 this.getMap().setTarget(originalTarget); 11675 originalTarget = null; 11676 if (scalelistener) ol.Observable.unByKey(scalelistener); 11677 // restore 11678 if (extraCtrl.title) { 11679 extraCtrl.title.control.setVisible(extraCtrl.title.isVisible); 11680 } 11681 if (extraCtrl.legend) { 11682 extraCtrl.legend.control.setCanvas(extraCtrl.legend.ison); 11683 extraCtrl.legend.control.collapse(extraCtrl.legend.collapsed); 11684 } 11685 this.dispatchEvent({ type: 'hide' }); 11686 }.bind(this)); 11687 // Update preview on resize 11688 window.addEventListener('resize', function() { 11689 this.setSize(); 11690 }.bind(this)); 11691 // Save or print 11692 if (options.saveAs) { 11693 this.on('print', function(e) { 11694 if (!e.pdf) { 11695 // Save image as file 11696 e.canvas.toBlob(function(blob) { 11697 var name = (e.print.legend ? 'legend.' : 'map.')+e.imageType.replace('image/',''); 11698 options.saveAs(blob, name); 11699 }, e.imageType, e.quality); 11700 } 11701 }); 11702 } 11703 // Save or print 11704 if (options.jsPDF) { 11705 this.on('print', function(e) { 11706 if (e.pdf) { 11707 // Export pdf using the print info 11708 var pdf = new options.jsPDF({ 11709 orientation: e.print.orientation, 11710 unit: e.print.unit, 11711 format: e.print.size 11712 }); 11713 pdf.addImage(e.image, 'JPEG', e.print.position[0], e.print.position[0], e.print.imageWidth, e.print.imageHeight); 11714 pdf.save(e.print.legend ? 'legend.pdf' : 'map.pdf'); 11715 } 11716 }); 11717 } 11718}; 11719ol.ext.inherits(ol.control.PrintDialog, ol.control.Control); 11720/** Check if the dialog is oprn 11721 * @return {boolean} 11722 */ 11723 ol.control.PrintDialog.prototype.isOpen = function() { 11724 return this._printDialog.isOpen(); 11725}; 11726/** Add a new language 11727 * @param {string} lang lang id 11728 * @param {Objetct} labels 11729 */ 11730ol.control.PrintDialog.addLang = function(lang, labels) { 11731 ol.control.PrintDialog.prototype._labels[lang] = labels; 11732}; 11733/** Translate 11734 * @param {string} what 11735 * @returns {string} 11736 */ 11737ol.control.PrintDialog.prototype.i18n = function(what) { 11738 var rep = this._labels.en[what] || 'bad param'; 11739 if (this._labels[this._lang] && this._labels[this._lang][what]) { 11740 rep = this._labels[this._lang][what]; 11741 } 11742 return rep; 11743}; 11744/** Print dialog labels (for customisation) */ 11745ol.control.PrintDialog.prototype._labels = { 11746 en: { 11747 title: 'Print', 11748 orientation: 'Orientation', 11749 portrait: 'Portrait', 11750 landscape: 'Landscape', 11751 size: 'Page size', 11752 custom: 'screen size', 11753 margin: 'Margin', 11754 scale: 'Scale', 11755 legend: 'Legend', 11756 north: 'North arrow', 11757 mapTitle: 'Map title', 11758 saveas: 'Save as...', 11759 saveLegend: 'Save legend...', 11760 copied: 'â Copied to clipboard', 11761 errorMsg: 'Can\'t save map canvas...', 11762 printBt: 'Print...', 11763 clipboardFormat: 'copy to clipboard...', 11764 jpegFormat: 'save as jpeg', 11765 pngFormat: 'save as png', 11766 pdfFormat: 'save as pdf', 11767 none: 'none', 11768 small: 'small', 11769 large: 'large', 11770 cancel: 'cancel' 11771 }, 11772 fr: { 11773 title: 'Imprimer', 11774 orientation: 'Orientation', 11775 portrait: 'Portrait', 11776 landscape: 'Paysage', 11777 size: 'Taille du papier', 11778 custom: 'taille écran', 11779 margin: 'Marges', 11780 scale: 'Echelle', 11781 legend: 'Légende', 11782 north: 'Flèche du nord', 11783 mapTitle: 'Titre de la carte', 11784 saveas: 'Enregistrer sous...', 11785 saveLegend: 'Enregistrer la légende...', 11786 copied: 'â Carte copiée', 11787 errorMsg: 'Impossible d\'enregistrer la carte', 11788 printBt: 'Imprimer', 11789 clipboardFormat: 'copier dans le presse-papier...', 11790 jpegFormat: 'enregistrer un jpeg', 11791 pngFormat: 'enregistrer un png', 11792 pdfFormat: 'enregistrer un pdf', 11793 none: 'aucune', 11794 small: 'petites', 11795 large: 'larges', 11796 cancel: 'annuler' 11797 }, 11798 de: { 11799 title: 'Drucken', 11800 orientation: 'Ausrichtung', 11801 portrait: 'Hochformat', 11802 landscape: 'Querformat', 11803 size: 'Papierformat', 11804 custom: 'BildschirmgröÃe', 11805 margin: 'Rand', 11806 scale: 'MaÃstab', 11807 legend: 'Legende', 11808 north: 'Nordpfeil', 11809 mapTitle: 'Kartentitel', 11810 saveas: 'Speichern als...', 11811 saveLegend: 'Legende speichern...', 11812 copied: 'â In die Zwischenablage kopiert', 11813 errorMsg: 'Kann Karte nicht speichern...', 11814 printBt: 'Drucken...', 11815 clipboardFormat: 'in die Zwischenablage kopieren...', 11816 jpegFormat: 'speichern als jpeg', 11817 pngFormat: 'speichern als png', 11818 pdfFormat: 'speichern als pdf', 11819 none: 'kein', 11820 small: 'klein', 11821 large: 'groÃ', 11822 cancel: 'abbrechen' 11823 }, 11824 zh:{ 11825 title: 'æå°', 11826 orientation: 'æ¹å', 11827 portrait: '纵å', 11828 landscape: '横å', 11829 size: '页é¢å¤§å°', 11830 custom: 'å±å¹å¤§å°', 11831 margin: 'å¤è¾¹è·', 11832 scale: '尺度', 11833 legend: 'å¾ä¾', 11834 north: 'æåé', 11835 mapTitle: 'å°å¾åå', 11836 saveas: 'ä¿å为...', 11837 saveLegend: 'ä¿åå¾ä¾ä¸º...', 11838 copied: 'â å·²å¤å¶å°åªè´´æ¿', 11839 errorMsg: 'æ æ³ä¿åå°å¾...', 11840 printBt: 'æå°...', 11841 cancel: 'åæ¶' 11842 } 11843}; 11844/** List of paper size */ 11845ol.control.PrintDialog.prototype.paperSize = { 11846 '': null, 11847 'A0': [841,1189], 11848 'A1': [594,841], 11849 'A2': [420,594], 11850 'A3': [297,420], 11851 'A4': [210,297], 11852 'US Letter': [215.9,279.4], 11853 'A5': [148,210], 11854 'B4': [257,364], 11855 'B5': [182,257] 11856}; 11857/** List of margin size */ 11858ol.control.PrintDialog.prototype.marginSize = { 11859 none: 0, 11860 small: 5, 11861 large: 10 11862}; 11863/** List of legeng options * / 11864ol.control.PrintDialog.prototype.legendOptions = { 11865 off: 'Hide legend', 11866 on: 'Show legend' 11867}; 11868/** List of print image file formats */ 11869ol.control.PrintDialog.prototype.formats = [{ 11870 title: 'clipboardFormat', 11871 imageType: 'image/png', 11872 clipboard: true 11873 }, { 11874 title: 'jpegFormat', 11875 imageType: 'image/jpeg', 11876 quality: .8 11877 }, { 11878 title: 'pngFormat', 11879 imageType: 'image/png', 11880 quality: .8 11881 }, { 11882 title: 'pdfFormat', 11883 imageType: 'image/jpeg', 11884 pdf: true 11885 } 11886]; 11887/** List of print scale */ 11888ol.control.PrintDialog.prototype.scales = { 11889 ' 5000': '1/5.000', 11890 ' 10000': '1/10.000',
11891 ' 25000': '1/25.000', 11892 ' 50000': '1/50.000', 11893 ' 100000': '1/100.000', 11894 ' 250000': '1/250.000', 11895 ' 1000000': '1/1.000.000' 11896}; 11897/** Get print orientation 11898 * @returns {string} 11899 */ 11900ol.control.PrintDialog.prototype.getOrientation = function () { 11901 return this._orientation || 'portrait'; 11902}; 11903/** Set print orientation 11904 * @param {string} ori landscape or portrait 11905 */ 11906ol.control.PrintDialog.prototype.setOrientation = function (ori) { 11907 this._orientation = (ori==='landscape' ? 'landscape' : 'portrait'); 11908 this._input.orientation[this._orientation].checked = true; 11909 this.setSize(); 11910}; 11911/** Get print margin 11912 * @returns {number} 11913 */ 11914ol.control.PrintDialog.prototype.getMargin = function () { 11915 return this._margin || 0; 11916}; 11917/** Set print margin 11918 * @param {number} 11919 */ 11920ol.control.PrintDialog.prototype.setMargin = function (margin) { 11921 this._margin = margin; 11922 this._input.margin.value = margin; 11923 this.setSize(); 11924}; 11925/** Get print size 11926 * @returns {ol.size} 11927 */ 11928ol.control.PrintDialog.prototype.getSize = function () { 11929 return this._size; 11930}; 11931/** Set map print size 11932 * @param {ol/size|string} size map size as ol/size or A4, etc. 11933 */ 11934ol.control.PrintDialog.prototype.setSize = function (size) { 11935 // reset status 11936 this._printDialog.getContentElement().setAttribute('data-status',''); 11937 if (size) this._size = size; 11938 else size = this._size; 11939 if (!size) return; 11940 if (typeof(size) === 'string') { 11941 // Test uppercase 11942 for (var k in this.paperSize) { 11943 if (k && new RegExp(k, 'i').test(size)) { 11944 size = k; 11945 } 11946 } 11947 // Default 11948 if (!this.paperSize[size]) size = this._size = 'A4'; 11949 this._input.size.value = size; 11950 size = [ 11951 Math.trunc(this.paperSize[size][0]* 96/25.4), 11952 Math.trunc(this.paperSize[size][1]* 96/25.4) 11953 ] 11954 if (this.getOrientation() === 'landscape') { 11955 size = [size[1], size[0]]; 11956 } 11957 this.getPage().classList.remove('margin'); 11958 } else { 11959 this._input.size.value = ''; 11960 this.getPage().classList.add('margin'); 11961 } 11962 var printElement = this.getPage(); 11963 var s = printElement.parentNode.getBoundingClientRect(); 11964 var scx = (s.width - 40) / size[0]; 11965 var scy = (s.height - 40) / size[1]; 11966 var sc = Math.min(scx, scy, 1); 11967 printElement.style.width = size[0]+'px'; 11968 printElement.style.height = size[1]+'px'; 11969 printElement.style['-webkit-transform'] = 11970 printElement.style.transform = 'translate(-50%,-50%) scale('+sc+')'; 11971 var px = Math.round(5/sc); 11972 printElement.style['-webkit-box-shadow'] = 11973 printElement.style['box-shadow'] = px+'px '+px+'px '+px+'px rgba(0,0,0,.6)'; 11974 printElement.style['padding'] = (this.getMargin() * 96/25.4)+'px'; 11975 if (this.getMap()) { 11976 this.getMap().updateSize(); 11977 } 11978 this.dispatchEvent({ type: 'dialog:refresh' }); 11979}; 11980/** Get dialog content element 11981 * @return {Element} 11982 */ 11983ol.control.PrintDialog.prototype.getContentElement = function () { 11984 return this._printDialog.getContentElement(); 11985}; 11986/** Get dialog user element 11987 * @return {Element} 11988 */ 11989ol.control.PrintDialog.prototype.getUserElement = function () { 11990 return this._printDialog.getContentElement().querySelector('.ol-user-param'); 11991}; 11992/** Get page element 11993 * @return {Element} 11994 */ 11995ol.control.PrintDialog.prototype.getPage = function () { 11996 return this._pages[0] 11997}; 11998/** 11999 * Remove the control from its current map and attach it to the new map. 12000 * Subclasses may set up event handlers to get notified about changes to 12001 * the map here. 12002 * @param {ol.Map} map Map. 12003 * @api stable 12004 */ 12005ol.control.PrintDialog.prototype.setMap = function (map) { 12006 if (this.getMap()) { 12007 this.getMap().removeControl(this._compass); 12008 this.getMap().removeControl(this._printCtrl); 12009 this.getMap().removeControl(this._printDialog); 12010 } 12011 ol.control.Control.prototype.setMap.call(this, map); 12012 if (this.getMap()) { 12013 this.getMap().addControl(this._compass); 12014 this.getMap().addControl(this._printCtrl); 12015 this.getMap().addControl(this._printDialog); 12016 } 12017}; 12018/** Set the current scale (will change the scale of the map) 12019 * @param {number|string} value the scale factor or a scale string as 1/xxx 12020 */ 12021ol.control.PrintDialog.prototype.setScale = function (value) { 12022 ol.sphere.setMapScale(this.getMap(), value); 12023 this._input.scale.value = ' '+(Math.round(value/100) * 100); 12024}; 12025/** Get the current map scale factor 12026 * @return {number} 12027 */ 12028ol.control.PrintDialog.prototype.getScale = function () { 12029 return ol.sphere.getMapScale(this.getMap()); 12030}; 12031/** Show print dialog 12032 * @param {*} 12033 * @param {ol/size|string} options.size map size as ol/size or A4, etc. 12034 * @param {number|string} options.value the scale factor or a scale string as 1/xxx 12035 * @param {string} options.orientation landscape or portrait 12036 * @param {number} options.margin 12037 */ 12038ol.control.PrintDialog.prototype.print = function(options) { 12039 options = options || {}; 12040 if (options.size) this.setSize(options.size); 12041 if (options.scale) this.setScale(options.scale); 12042 if (options.orientation) this.setOrientation(options.orientation); 12043 if (options.margin) this.setMargin(options.margin); 12044 this._printDialog.show(); 12045}; 12046/** Get print control 12047 * @returns {ol.control.Print} 12048 */ 12049ol.control.PrintDialog.prototype.getrintControl = function() { 12050 return this._printCtrl; 12051} 12052
12053/* Copyright (c) 2016 Jean-Marc VIGLINO, 12054 released under the CeCILL-B license (French BSD license) 12055 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 12056*/ 12057/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ 12058/** 12059 * @classdesc OpenLayers 3 Profil Control. 12060 * Draw a profile of a feature (with a 3D geometry) 12061 * 12062 * @constructor 12063 * @extends {ol.control.Control} 12064 * @fires over 12065 * @fires out 12066 * @fires show 12067 * @fires dragstart 12068 * @fires dragging 12069 * @fires dragend 12070 * @fires dragcancel 12071 * @param {Object=} options 12072 * @param {string} options.className 12073 * @param {String} options.title button title 12074 * @param {ol.style.Style} [options.style] style to draw the profil, default darkblue 12075 * @param {ol.style.Style} [options.selectStyle] style for selection, default darkblue fill 12076 * @param {*} options.info keys/values for i19n 12077 * @param {number} [options.width=300] 12078 * @param {number} [options.height=150] 12079 * @param {ol.Feature} [options.feature] the feature to draw profil 12080 * @param {boolean} [options.selectable=false] enable selection on the profil, default false 12081 * @param {boolean} [options.zoomable=false] can zoom in the profil 12082 */ 12083ol.control.Profil = function(options) { 12084 options = options || {}; 12085 this.info = options.info || ol.control.Profil.prototype.info; 12086 var self = this; 12087 var element; 12088 if (options.target) { 12089 element = document.createElement("div"); 12090 element.classList.add(options.className || "ol-profil"); 12091 } else { 12092 element = document.createElement("div"); 12093 element.className = ((options.className || 'ol-profil') +' ol-unselectable ol-control ol-collapsed').trim(); 12094 this.button = document.createElement("button"); 12095 this.button.title = options.title || 'Profile', 12096 this.button.setAttribute('type','button'); 12097 var click_touchstart_function = function(e) { 12098 self.toggle(); 12099 e.preventDefault(); 12100 }; 12101 this.button.addEventListener("click", click_touchstart_function); 12102 this.button.addEventListener("touchstart", click_touchstart_function); 12103 element.appendChild(this.button); 12104 } 12105 // Drawing style 12106 if (options.style instanceof ol.style.Style) { 12107 this._style = options.style; 12108 } else { 12109 this._style = new ol.style.Style({ 12110 text: new ol.style.Text(), 12111 stroke: new ol.style.Stroke({ 12112 width: 1.5, 12113 color: '#369' 12114 }) 12115 }); 12116 } 12117 if (!this._style.getText()) this._style.setText(new ol.style.Text()); 12118 // Selection style 12119 if (options.selectStyle instanceof ol.style.Style) { 12120 this._selectStyle = options.selectStyle; 12121 } else { 12122 this._selectStyle = new ol.style.Style({ 12123 fill: new ol.style.Fill({ color: '#369' }) 12124 }); 12125 } 12126 var div_inner = document.createElement("div"); 12127 div_inner.classList.add("ol-inner"); 12128 element.appendChild(div_inner); 12129 var div = document.createElement("div"); 12130 div.style.position = "relative"; 12131 div_inner.appendChild(div); 12132 var ratio = this.ratio = 2; 12133 this.canvas_ = document.createElement('canvas'); 12134 this.canvas_.width = (options.width || 300)*ratio; 12135 this.canvas_.height = (options.height || 150)*ratio; 12136 var styles = { 12137 "msTransform":"scale(0.5,0.5)", "msTransformOrigin":"0 0", 12138 "webkitTransform":"scale(0.5,0.5)", "webkitTransformOrigin":"0 0", 12139 "mozTransform":"scale(0.5,0.5)", "mozTransformOrigin":"0 0", 12140 "transform":"scale(0.5,0.5)", "transformOrigin":"0 0" 12141 }; 12142 Object.keys(styles).forEach(function(style) { 12143 if (style in self.canvas_.style) { 12144 self.canvas_.style[style] = styles[style]; 12145 } 12146 }); 12147 var div_to_canvas = document.createElement("div"); 12148 div.appendChild(div_to_canvas); 12149 div_to_canvas.style.width = this.canvas_.width/ratio + "px"; 12150 div_to_canvas.style.height = this.canvas_.height/ratio + "px"; 12151 div_to_canvas.appendChild(this.canvas_); 12152 div_to_canvas.addEventListener('pointerdown', this.onMove.bind(this)); 12153 document.addEventListener('pointerup', this.onMove.bind(this)); 12154 div_to_canvas.addEventListener('mousemove', this.onMove.bind(this)); 12155 div_to_canvas.addEventListener('touchmove', this.onMove.bind(this)); 12156 ol.control.Control.call(this, { 12157 element: element, 12158 target: options.target 12159 }); 12160 this.set('selectable', options.selectable); 12161 // Offset in px 12162 this.margin_ = { top:10*ratio, left:45*ratio, bottom:30*ratio, right:10*ratio }; 12163 if (!this.info.ytitle) this.margin_.left -= 20*ratio; 12164 if (!this.info.xtitle) this.margin_.bottom -= 20*ratio; 12165 // Cursor 12166 this.bar_ = document.createElement("div"); 12167 this.bar_.classList.add("ol-profilbar"); 12168 this.bar_.style.top = (this.margin_.top/ratio)+"px"; 12169 this.bar_.style.height = (this.canvas_.height-this.margin_.top-this.margin_.bottom)/ratio+"px"; 12170 div.appendChild(this.bar_); 12171 this.cursor_ = document.createElement("div");
12172 this.cursor_.classList.add("ol-profilcursor"); 12173 div.appendChild(this.cursor_); 12174 this.popup_ = document.createElement("div"); 12175 this.popup_.classList.add("ol-profilpopup"); 12176 this.cursor_.appendChild(this.popup_); 12177 // Track information 12178 var t = document.createElement("table"); 12179 t.cellPadding = '0'; 12180 t.cellSpacing = '0'; 12181 t.style.clientWidth = this.canvas_.width/ratio + "px"; 12182 div.appendChild(t); 12183 var firstTr = document.createElement("tr"); 12184 firstTr.classList.add("track-info"); 12185 t.appendChild(firstTr); 12186 var div_zmin = document.createElement("td"); 12187 div_zmin.innerHTML = (this.info.zmin||"Zmin")+': <span class="zmin">'; 12188 firstTr.appendChild(div_zmin); 12189 var div_zmax = document.createElement("td"); 12190 div_zmax.innerHTML = (this.info.zmax||"Zmax")+': <span class="zmax">'; 12191 firstTr.appendChild(div_zmax); 12192 var div_distance = document.createElement("td"); 12193 div_distance.innerHTML = (this.info.distance||"Distance")+': <span class="dist">'; 12194 firstTr.appendChild(div_distance); 12195 var div_time = document.createElement("td"); 12196 div_time.innerHTML = (this.info.time||"Time")+': <span class="time">'; 12197 firstTr.appendChild(div_time); 12198 var secondTr = document.createElement("tr"); 12199 secondTr.classList.add("point-info") 12200 t.appendChild(secondTr); 12201 var div_altitude = document.createElement("td"); 12202 div_altitude.innerHTML = (this.info.altitude||"Altitude")+': <span class="z">'; 12203 secondTr.appendChild(div_altitude); 12204 var div_distance2 = document.createElement("td"); 12205 div_distance2.innerHTML = (this.info.distance||"Distance")+': <span class="dist">'; 12206 secondTr.appendChild(div_distance2); 12207 var div_time2 = document.createElement("td"); 12208 div_time2.innerHTML = (this.info.time||"Time")+': <span class="time">'; 12209 secondTr.appendChild(div_time2); 12210 // Array of data 12211 this.tab_ = []; 12212 // Show feature 12213 if (options.feature) { 12214 this.setGeometry (options.feature); 12215 } 12216 // Zoom on profile 12217 if (options.zoomable) { 12218 this.set('selectable', true); 12219 var start, geom; 12220 this.on('change:geometry', function() { 12221 geom = null; 12222 }); 12223 this.on('dragstart', function(e) { 12224 start = e.index; 12225 }) 12226 this.on('dragend', function(e) { 12227 if (Math.abs(start - e.index) > 10) { 12228 if (!geom) { 12229 var bt = ol.ext.element.create('BUTTON', { 12230 parent: element, 12231 className: 'ol-zoom-out', 12232 click: function(e) { 12233 e.stopPropagation(); 12234 e.preventDefault(); 12235 if (geom) { 12236 this.dispatchEvent({ type:'zoom' }); 12237 this.setGeometry(geom, this._geometry[1]); 12238 } 12239 element.removeChild(bt); 12240 }.bind(this) 12241 }) 12242 } 12243 var saved = geom || this._geometry[0]; 12244 var g = new ol.geom.LineString(this.getSelection(start, e.index)); 12245 this.setGeometry(g, this._geometry[1]); 12246 geom = saved; 12247 this.dispatchEvent({ type:'zoom', geometry: g, start: start, end: e.index }); 12248 } 12249 }.bind(this)); 12250 } 12251}; 12252ol.ext.inherits(ol.control.Profil, ol.control.Control); 12253/** Custom infos list 12254* @api stable 12255*/ 12256ol.control.Profil.prototype.info = { 12257 "zmin": "Zmin", 12258 "zmax": "Zmax", 12259 "ytitle": "Altitude (m)", 12260 "xtitle": "Distance (km)", 12261 "time": "Time", 12262 "altitude": "Altitude", 12263 "distance": "Distance", 12264 "altitudeUnits": "m", 12265 "distanceUnitsM": "m", 12266 "distanceUnitsKM": "km", 12267}; 12268/** Show popup info 12269* @param {string} info to display as a popup 12270* @api stable 12271*/ 12272ol.control.Profil.prototype.popup = function(info) { 12273 this.popup_.innerHTML = info; 12274}; 12275/** Show point on profil 12276 * @param {*} p 12277 * @param {number} dx 12278 * @private 12279 */ 12280ol.control.Profil.prototype._drawAt = function(p, dx) { 12281 if (p) { 12282 this.cursor_.style.left = dx+"px"; 12283 this.cursor_.style.top = (this.canvas_.height-this.margin_.bottom+p[1]*this.scale_[1]+this.dy_)/this.ratio+"px"; 12284 this.cursor_.style.display = "block"; 12285 this.bar_.parentElement.classList.add("over"); 12286 this.bar_.style.left = dx+"px"; 12287 this.bar_.style.display = "block"; 12288 this.element.querySelector(".point-info .z").textContent = p[1]+this.info.altitudeUnits; 12289 this.element.querySelector(".point-info .dist").textContent = (p[0]/1000).toFixed(1)+this.info.distanceUnitsKM; 12290 this.element.querySelector(".point-info .time").textContent = p[2]; 12291 if (dx>this.canvas_.width/this.ratio/2) this.popup_.classList.add('ol-left'); 12292 else this.popup_.classList.remove('ol-left'); 12293 } else { 12294 this.cursor_.style.display = "none"; 12295 this.bar_.style.display = 'none'; 12296 this.cursor_.style.display = 'none'; 12297 this.bar_.parentElement.classList.remove("over"); 12298 } 12299}; 12300/** Show point at coordinate or a distance on the profil 12301 * @param { ol.coordinates|number } where a coordinate or a distance from begining, if none it will hide the point 12302 * @return { ol.coordinates } current point 12303 */ 12304ol.control.Profil.prototype.showAt = function(where) { 12305 var i, p, p0, d0 = Infinity; 12306 if (typeof(where) === 'undefined') { 12307 if (this.bar_.parentElement.classList.contains("over")) { 12308 // Remove it 12309 this._drawAt(); 12310 } 12311 } else if (where.length) { 12312 // Look for closest the point 12313 for (i=1; p=this.tab_[i]; i++) { 12314 var d = ol.coordinate.dist2d(p[3], where); 12315 if (d<d0) { 12316 p0 = p; 12317 d0 = d; 12318 } 12319 } 12320 } else { 12321 for (i=0; p=this.tab_[i]; i++) { 12322 p0 = p; 12323 if (p[0] >= where) { 12324 break; 12325 } 12326 } 12327 } 12328 if (p0) { 12329 var dx = (p0[0] * this.scale_[0] + this.margin_.left) / this.ratio; 12330 this._drawAt(p0, dx); 12331 return p0[3]; 12332 } 12333 return null; 12334}; 12335/** Show point at a time on the profil 12336 * @param { Date|number }
12336 time a Date or a DateTime (in s) to show the profile on, if none it will hide the point 12337 * @param { booelan } delta true if time is a delta from the start, default false 12338 * @return { ol.coordinates } current point 12339 */ 12340ol.control.Profil.prototype.showAtTime = function(time, delta) { 12341 var i, p, p0; 12342 if (time instanceof Date) { 12343 time = time.getTime()/1000; 12344 } else if (delta) { 12345 time += this.tab_[0][3][3]; 12346 } 12347 if (typeof(time) === 'undefined') { 12348 if (this.bar_.parentElement.classList.contains("over")) { 12349 // Remove it 12350 this._drawAt(); 12351 } 12352 } else { 12353 for (i=0; p=this.tab_[i]; i++) { 12354 p0 = p; 12355 if (p[3][3] >= time) { 12356 break; 12357 } 12358 } 12359 } 12360 if (p0) { 12361 var dx = (p0[0] * this.scale_[0] + this.margin_.left) / this.ratio; 12362 this._drawAt(p0, dx); 12363 return p0[3]; 12364 } 12365 return null; 12366}; 12367/** Get the point at a given time on the profil 12368 * @param { number } time time at which to show the point 12369 * @return { ol.coordinates } current point 12370 */ 12371ol.control.Profil.prototype.pointAtTime = function(time) { 12372 var i, p; 12373 // Look for closest the point 12374 for (i=1; p=this.tab_[i]; i++) { 12375 var t = p[3][3]; 12376 if (t >= time) { 12377 // Previous one ? 12378 var pt = this.tab_[i-1][3]; 12379 if ((pt[3]+t)/2 < time) return pt; 12380 else return p; 12381 } 12382 } 12383 return this.tab_[this.tab_.length-1][3]; 12384}; 12385/** Mouse move over canvas 12386 */ 12387ol.control.Profil.prototype.onMove = function(e) { 12388 if (!this.tab_.length) return; 12389 var box_canvas = this.canvas_.getBoundingClientRect(); 12390 var pos = { 12391 top: box_canvas.top + window.pageYOffset - document.documentElement.clientTop, 12392 left: box_canvas.left + window.pageXOffset - document.documentElement.clientLeft 12393 }; 12394 var pageX = e.pageX 12395 || (e.touches && e.touches.length && e.touches[0].pageX) 12396 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); 12397 var pageY = e.pageY 12398 || (e.touches && e.touches.length && e.touches[0].pageY) 12399 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY); 12400 var dx = pageX -pos.left; 12401 var dy = pageY -pos.top; 12402 var ratio = this.ratio; 12403 if (dx > this.margin_.left/ratio - 20 && dx < (this.canvas_.width-this.margin_.right) / ratio + 8 12404 && dy > this.margin_.top/ratio && dy < (this.canvas_.height-this.margin_.bottom) / ratio) { 12405 var d = (dx*ratio-this.margin_.left)/this.scale_[0]; 12406 var p0 = this.tab_[0]; 12407 var index, p; 12408 for (index=1; p=this.tab_[index]; index++) { 12409 if (p[0] >= d) { 12410 if (d < (p[0]+p0[0])/2) { 12411 index = 0; 12412 p = p0; 12413 } 12414 break; 12415 } 12416 } 12417 if (!p) p = this.tab_[this.tab_.length-1]; 12418 dx = Math.max(this.margin_.left/ratio, Math.min(dx, (this.canvas_.width-this.margin_.right)/ratio)); 12419 this._drawAt(p, dx); 12420 this.dispatchEvent({ type:'over', click:e.type==='click', index: index, coord: p[3], time: p[2], distance: p[0] }); 12421 // Handle drag / click 12422 switch (e.type) { 12423 case 'pointerdown': { 12424 this._dragging = { 12425 event: { type:'dragstart', index: index, coord: p[3], time: p[2], distance: p[0] }, 12426 pageX: pageX, 12427 pageY: pageY 12428 } 12429 break; 12430 } 12431 case 'pointerup': { 12432 if (this._dragging && this._dragging.pageX) { 12433 if (Math.abs(this._dragging.pageX - pageX)<3 && Math.abs(this._dragging.pageY - pageY) < 3) { 12434 this.dispatchEvent({ type:'click', index: index, coord: p[3], time: p[2], distance: p[0] }); 12435 this.refresh(); 12436 } 12437 } else { 12438 this.dispatchEvent({ type:'dragend', index: index, coord: p[3], time: p[2], distance: p[0] }); 12439 } 12440 this._dragging = false; 12441 break; 12442 } 12443 default: { 12444 if (this._dragging) { 12445 if (this._dragging.pageX) { 12446 if (Math.abs(this._dragging.pageX - pageX)>3 || Math.abs(this._dragging.pageY - pageY) > 3) { 12447 this._dragging.pageX = this._dragging.pageY = false; 12448 this.dispatchEvent(this._dragging.event); 12449 } 12450 } else { 12451 this.dispatchEvent({ type:'dragging', index: index, coord: p[3], time: p[2], distance: p[0] }); 12452 var min = Math.min(this._dragging.event.index, index); 12453 var max = Math.max(this._dragging.event.index, index); 12454 this.refresh(); 12455 if (this.get('selectable')) this._drawGraph(this.tab_.slice(min, max), this._selectStyle); 12456 } 12457 } 12458 break; 12459 } 12460 } 12461 } else { 12462 if (this.bar_.parentElement.classList.contains('over')) { 12463 this._drawAt(); 12464 this.dispatchEvent({ type:'out' }); 12465 } 12466 if (e.type === 'pointerup' && this._dragging) { 12467 this.dispatchEvent({ type:'dragcancel' }); 12468 this._dragging = false;
12469 } 12470 } 12471}; 12472/** Show panel 12473* @api stable 12474*/ 12475ol.control.Profil.prototype.show = function() { 12476 this.element.classList.remove("ol-collapsed"); 12477 this.dispatchEvent({ type:'show', show: true }); 12478}; 12479/** Hide panel 12480* @api stable 12481*/ 12482ol.control.Profil.prototype.hide = function() { 12483 this.element.classList.add("ol-collapsed"); 12484 this.dispatchEvent({ type:'show', show: false }); 12485}; 12486/** Toggle panel 12487* @api stable 12488*/ 12489ol.control.Profil.prototype.toggle = function() { 12490 this.element.classList.toggle("ol-collapsed"); 12491 var b = this.element.classList.contains("ol-collapsed"); 12492 this.dispatchEvent({ type:'show', show: !b }); 12493} 12494/** Is panel visible 12495*/ 12496ol.control.Profil.prototype.isShown = function() { 12497 return (!this.element.classList.contains("ol-collapsed")); 12498}; 12499/** Get selection 12500 * @param {number} starting point 12501 * @param {number} ending point 12502 * @return {Array<ol.coordinate>} 12503 */ 12504ol.control.Profil.prototype.getSelection = function(start, end) { 12505 var sel = []; 12506 var min = Math.max(Math.min(start, end), 0); 12507 var max = Math.min(Math.max(start, end), this.tab_.length-1); 12508 for (var i=min; i <= max; i++) { 12509 sel.push(this.tab_[i][3]) 12510 } 12511 return sel; 12512}; 12513/** Draw the graph 12514 * @private 12515 */ 12516ol.control.Profil.prototype._drawGraph = function(t, style) { 12517 if (!t.length) return; 12518 var ctx = this.canvas_.getContext('2d'); 12519 var scx = this.scale_[0]; 12520 var scy = this.scale_[1]; 12521 var dy = this.dy_; 12522 var ratio = this.ratio; 12523 var i, p; 12524 // Draw Path 12525 ctx.beginPath(); 12526 for (i=0; p=t[i]; i++) { 12527 if (i==0) ctx.moveTo(p[0]*scx,p[1]*scy+dy); 12528 else ctx.lineTo(p[0]*scx,p[1]*scy+dy); 12529 } 12530 if (style.getStroke()) { 12531 ctx.strokeStyle = style.getStroke().getColor() || '#000'; 12532 ctx.lineWidth = style.getStroke().getWidth() * ratio; 12533 ctx.setLineDash([]); 12534 ctx.stroke(); 12535 } 12536 // Fill path 12537 if (style.getFill()) { 12538 ctx.fillStyle = style.getFill().getColor() || '#000'; 12539 ctx.Style = style.getFill().getColor() || '#000'; 12540 ctx.lineTo(t[t.length-1][0]*scx, 0); 12541 ctx.lineTo(t[0][0]*scx, 0); 12542 ctx.fill(); 12543 } 12544}; 12545/** 12546 * Set the geometry to draw the profil. 12547 * @param {ol.Feature|ol.geom.Geometry} f the feature. 12548 * @param {Object=} options 12549 * @param {ol.ProjectionLike} [options.projection] feature projection, default projection of the map 12550 * @param {string} [options.zunit='m'] 'm' or 'km', default m 12551 * @param {string} [options.unit='km'] 'm' or 'km', default km 12552 * @param {Number|undefined} [options.zmin=0] default 0 12553 * @param {Number|undefined} options.zmax default max Z of the feature 12554 * @param {integer|undefined} [options.zDigits=0] number of digits for z graduation, default 0 12555 * @param {integer|undefined} [options.zMaxChars] maximum number of chars to be used for z graduation before switching to scientific notation 12556 * @param {Number|undefined} [options.graduation=100] z graduation default 100 12557 * @param {integer|undefined} [options.amplitude] amplitude of the altitude, default zmax-zmin 12558 * @api stable 12559 */ 12560ol.control.Profil.prototype.setGeometry = function(g, options) { 12561 if (!options) options = {}; 12562 if (g instanceof ol.Feature) g = g.getGeometry(); 12563 this._geometry = [g, options]; 12564 // No Z 12565 if (!/Z/.test(g.getLayout())) return; 12566 // No time 12567 if(/M/.test(g.getLayout())) this.element.querySelector(".time").parentElement.style.display = 'block'; 12568 else this.element.querySelector(".time").parentElement.style.display = 'none'; 12569 // Coords 12570 var c = g.getCoordinates(); 12571 switch (g.getType()) { 12572 case "LineString": break; 12573 case "MultiLineString": c = c[0]; break; 12574 default: return; 12575 } 12576 // Distance beetween 2 coords 12577 var proj = options.projection || this.getMap().getView().getProjection(); 12578 function dist2d(p1,p2) { 12579 return ol.sphere.getDistance( 12580 ol.proj.transform(p1, proj, 'EPSG:4326'), 12581 ol.proj.transform(p2, proj, 'EPSG:4326') 12582 ); 12583 } 12584 function getTime(t0, t1) { 12585 if (!t0 || !t1) return "-" 12586 var dt = (t1-t0) / 60; // mn 12587 var ti = Math.trunc(dt/60); 12588 var mn = Math.trunc(dt-ti*60); 12589 return ti+"h"+(mn<10?"0":"")+mn+"mn"; 12590 } 12591 // Calculate [distance, altitude, time, point] for each points 12592 var zmin=Infinity, zmax=-Infinity; 12593 var i, p, d, z, ti, t = this.tab_ = []; 12594 for (i=0, p; p=c[i]; i++) { 12595 z = p[2]; 12596 if (z<zmin) zmin=z; 12597 if (z>zmax) zmax=z; 12598 if (i==0) d = 0; 12599 else d += dist2d(c[i-1], p); 12600 ti = getTime(c[0][3],p[3]); 12601 t.push ([d, z, ti, p]); 12602 } 12603 this._z = [zmin,zmax]; 12604 this.set('graduation', options.graduation || 100); 12605 this.set('zmin', options.zmin); 12606 this.set('zmax', options.zmax); 12607 this.set('amplitude', options.amplitude); 12608 this.set('unit', options.unit); 12609 this.set('zunit', options.zunit); 12610 this.set('zDigits', options.zDigits); 12611 this.set('zMaxChars', options.zMaxChars); 12612 this.dispatchEvent({ type: 'change:geometry', geometry: g }) 12613 this.refresh(); 12614}; 12615/** Refresh the profil 12616 */ 12617ol.control.Profil.prototype.refresh = function() { 12618 var canvas = this.canvas_; 12619 var ctx = canvas.getContext('2d'); 12620 var w = canvas.width;
12621 var h = canvas.height; 12622 ctx.setTransform(1, 0, 0, 1, 0, 0); 12623 ctx.clearRect(0,0, w, h); 12624 var zmin = this._z[0]; 12625 var zmax = this._z[1]; 12626 var t = this.tab_; 12627 var d = t[t.length-1][0]; 12628 var ti = t[t.length-1][2]; 12629 var i; 12630 if (!d) { 12631 console.error('[ol/control/Profil] no data...', t); 12632 return; 12633 } 12634 // Margin 12635 ctx.setTransform(1, 0, 0, 1, this.margin_.left, h-this.margin_.bottom); 12636 var ratio = this.ratio; 12637 w -= this.margin_.right + this.margin_.left; 12638 h -= this.margin_.top + this.margin_.bottom; 12639 // Draw axes 12640 ctx.strokeStyle = this._style.getText().getFill().getColor() || '#000'; 12641 ctx.lineWidth = 0.5*ratio; 12642 ctx.beginPath(); 12643 ctx.moveTo(0,0); ctx.lineTo(0,-h); 12644 ctx.moveTo(0,0); ctx.lineTo(w, 0); 12645 ctx.stroke(); 12646 // Info 12647 this.element.querySelector(".track-info .zmin").textContent = zmin.toFixed(2)+this.info.altitudeUnits; 12648 this.element.querySelector(".track-info .zmax").textContent = zmax.toFixed(2)+this.info.altitudeUnits; 12649 if (d>1000) { 12650 this.element.querySelector(".track-info .dist").textContent = (d/1000).toFixed(1)+this.info.distanceUnitsKM; 12651 } else { 12652 this.element.querySelector(".track-info .dist").textContent= (d).toFixed(1)+this.info.distanceUnitsM; 12653 } 12654 this.element.querySelector(".track-info .time").textContent = ti; 12655 // Set graduation 12656 var grad = this.get('graduation'); 12657 while (true) { 12658 zmax = Math.ceil(zmax/grad)*grad; 12659 zmin = Math.floor(zmin/grad)*grad; 12660 var nbgrad = (zmax-zmin)/grad; 12661 if (h/nbgrad < 15*ratio) { 12662 grad *= 2; 12663 } 12664 else break; 12665 } 12666 // Set amplitude 12667 if (typeof(this.get('zmin'))=='number' && zmin > this.get('zmin')) zmin = this.get('zmin'); 12668 if (typeof(this.get('zmax'))=='number' && zmax < this.get('zmax')) zmax = this.get('zmax'); 12669 var amplitude = this.get('amplitude'); 12670 if (amplitude) { 12671 zmax = Math.max (zmin + amplitude, zmax); 12672 } 12673 // Scales lines 12674 var scx = w/d; 12675 var scy = -h/(zmax-zmin); 12676 var dy = this.dy_ = -zmin*scy; 12677 this.scale_ = [scx,scy]; 12678 this._drawGraph(t, this._style); 12679 // Draw 12680 ctx.textAlign = 'right'; 12681 ctx.textBaseline = 'top'; 12682 ctx.fillStyle = this._style.getText().getFill().getColor() || '#000'; 12683 // Scale Z 12684 ctx.beginPath(); 12685 var fix = this.get('zDigits') || 0; 12686 var exp = null; 12687 if (typeof(this.get('zMaxChars'))=='number') { 12688 var usedChars; 12689 if (this.get('zunit') != 'km') usedChars = Math.max(zmin.toFixed(fix).length, zmax.toFixed(fix).length); 12690 else usedChars = Math.max((zmin/1000).toFixed(1).length, (zmax/1000).toFixed(1).length); 12691 if (this.get('zMaxChars') < usedChars) { 12692 exp = Math.floor(Math.log10(Math.max(Math.abs(zmin), Math.abs(zmax),Number.MIN_VALUE))); 12693 ctx.font = 'bold '+(9*ratio)+'px arial'; 12694 ctx.fillText(exp.toString(), -8*ratio, 8*ratio); 12695 var expMetrics = ctx.measureText(exp.toString()); 12696 var expWidth = expMetrics.width; 12697 var expHeight = expMetrics.actualBoundingBoxAscent + expMetrics.actualBoundingBoxDescent; 12698 ctx.font = 'bold '+(12*ratio)+'px arial'; 12699 ctx.fillText("10", -8*ratio-expWidth, 8*ratio+0.5*expHeight); 12700 } 12701 } 12702 ctx.font = (10*ratio)+'px arial'; 12703 ctx.textBaseline = 'middle'; 12704 for (i=zmin; i<=zmax; i+=grad) { 12705 if (exp !== null) { 12706 var baseNumber = i / Math.pow(10, exp); 12707 if (this.get('zunit') == 'km') 12708 baseNumber /= 1000; 12709 var nbDigits = this.get('zMaxChars') - Math.floor(Math.log10(Math.max(Math.abs(baseNumber),1))+1) - 1; 12710 if (baseNumber < 0) nbDigits -= 1 12711 if (this.get('zunit') != 'km') ctx.fillText(baseNumber.toFixed(Math.max(nbDigits, 0)), -4*ratio, i*scy+dy); 12712 else ctx.fillText(baseNumber.toFixed(Math.max(nbDigits,0)), -4*ratio, i*scy+dy); 12713 } else { 12714 if (this.get('zunit') != 'km') ctx.fillText(i.toFixed(fix), -4*ratio, i*scy+dy); 12715 else ctx.fillText((i/1000).toFixed(1), -4*ratio, i*scy+dy); 12716 } 12717 ctx.moveTo (-2*ratio, i*scy+dy); 12718 if (i!=0) ctx.lineTo (d*scx, i*scy+dy); 12719 else ctx.lineTo (0, i*scy+dy); 12720 } 12721 // Scale X 12722 ctx.textAlign = "center"; 12723 ctx.textBaseline = "top"; 12724 ctx.setLineDash([ratio,3*ratio]); 12725 var unit = this.get('unit') ||"km"; 12726 var step; 12727 if (d>1000) { 12728 step = Math.round(d/1000)*100; 12729 if (step > 1000) step = Math.ceil(step/1000)*1000; 12730 } else { 12731 unit = "m"; 12732 if (d>100) step = Math.round(d/100)*10; 12733 else if (d>10) step = Math.round(d/10); 12734 else if (d>1) step = Math.round(d)/10; 12735 else step = d; 12736 } 12737 for (i=0; i<=d; i+=step) { 12738 var txt = (unit=="m") ? i : (i/1000); 12739 //if (i+step>d) txt += " "+ (options.zunits || "km"); 12740 ctx.fillText(Math.round(txt*10)/10, i*scx, 4*ratio); 12741 ctx.moveTo (i*scx, 2*ratio); ctx.lineTo (i*scx, 0); 12742 }
12743 ctx.font = (12*ratio)+"px arial"; 12744 ctx.fillText(this.info.xtitle.replace("(km)","("+unit+")"), w/2, 18*ratio); 12745 ctx.save(); 12746 ctx.rotate(-Math.PI/2); 12747 ctx.fillText(this.info.ytitle, h/2, -this.margin_.left); 12748 ctx.restore(); 12749 ctx.stroke(); 12750}; 12751/** Get profil image 12752* @param {string|undefined} type image format or 'canvas' to get the canvas image, default image/png. 12753* @param {Number|undefined} encoderOptions between 0 and 1 indicating image quality image/jpeg or image/webp, default 0.92. 12754* @return {string} requested data uri 12755* @api stable 12756*/ 12757ol.control.Profil.prototype.getImage = function(type, encoderOptions) { 12758 if (type==="canvas") return this.canvas_; 12759 return this.canvas_.toDataURL(type, encoderOptions); 12760}; 12761 12762/* Copyright (c) 2016 Jean-Marc VIGLINO, 12763 released under the CeCILL-B license (French BSD license) 12764 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 12765*/ 12766/** Add a progress bar to a map. 12767 * Use the layers option listen to tileload event and show the layer loading progress. 12768 * @constructor 12769 * @extends {ol.control.Control} 12770 * @param {Object=} options Control options. 12771 * @param {String} [options.className] class of the control 12772 * @param {String} [options.label] waiting label 12773 * @param {ol.layer.Layer|Array<ol.layer.Layer>} [options.layers] tile layers with tileload events 12774 */ 12775ol.control.ProgressBar = function(options) { 12776 options = options || {}; 12777 var element = ol.ext.element.create('DIV', { 12778 className: ((options.className || '') + ' ol-progress-bar ol-unselectable ol-control').trim() 12779 }); 12780 this._waiting = ol.ext.element.create('DIV', { 12781 html: options.label || '', 12782 className: 'ol-waiting', 12783 parent: element 12784 }); 12785 this._bar = ol.ext.element.create('DIV', { 12786 className: 'ol-bar', 12787 parent: element 12788 }); 12789 ol.control.Control.call(this, { 12790 element: element, 12791 target: options.target 12792 }); 12793 this._layerlistener = []; 12794 this.setLayers(options.layers); 12795}; 12796ol.ext.inherits(ol.control.ProgressBar, ol.control.Control); 12797/** Set the control visibility 12798 * @param {Number} [n] progress percentage, a number beetween 0,1, default hide progress bar 12799 */ 12800ol.control.ProgressBar.prototype.setPercent = function (n) { 12801 this._bar.style.width = ((Number(n) || 0) * 100)+'%'; 12802 if (n===undefined) { 12803 ol.ext.element.hide(this.element); 12804 } else { 12805 ol.ext.element.show(this.element); 12806 } 12807}; 12808/** Set waiting text 12809 * @param {string} label 12810 */ 12811ol.control.ProgressBar.prototype.setLabel = function (label) { 12812 this._waiting.innerHTML = label; 12813}; 12814/** Use a list of tile layer to shown tile load 12815 * @param {ol.layer.Layer|Array<ol.layer.Layer>} layers a layer or a list of layer 12816 */ 12817ol.control.ProgressBar.prototype.setLayers = function (layers) { 12818 // reset 12819 this._layerlistener.forEach(function (l) { 12820 ol.Observable.unByKey(l); 12821 }); 12822 this._layerlistener = []; 12823 this.setPercent(); 12824 var loading=0, loaded=0; 12825 if (layers instanceof ol.layer.Layer) layers = [layers]; 12826 if (!layers || !layers.forEach) return; 12827 var tout; 12828 // Listeners 12829 layers.forEach(function(layer) { 12830 if (layer instanceof ol.layer.Layer) { 12831 this._layerlistener.push(layer.getSource().on('tileloadstart', function () { 12832 loading++; 12833 this.setPercent(loaded/loading); 12834 clearTimeout(tout); 12835 }.bind(this))); 12836 this._layerlistener.push(layer.getSource().on(['tileloadend', 'tileloaderror'], function () { 12837 loaded++; 12838 if (loaded === loading) { 12839 loading = loaded = 0; 12840 this.setPercent(1); 12841 tout = setTimeout(this.setPercent.bind(this), 300); 12842 } else { 12843 this.setPercent(loaded/loading); 12844 } 12845 }.bind(this))); 12846 } 12847 }.bind(this)); 12848} 12849 12850/* Copyright (c) 2018 Jean-Marc VIGLINO, 12851 released under the CeCILL-B license (French BSD license) 12852 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 12853*/ 12854/** Geoportail routing Control. 12855 * @constructor 12856 * @extends {ol.control.Control} 12857 * @fires select 12858 * @fires change:input 12859 * @fires routing:start 12860 * @fires routing 12861 * @fires step:select 12862 * @fires step:hover 12863 * @fires error 12864 * @fires abort 12865 * @param {Object=} options 12866 * @param {string} options.className control class name 12867 * @param {string | undefined} [options.apiKey] the service api key. 12868 * @param {string | undefined} options.authentication: basic authentication for the service API as btoa("login:pwd") 12869 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 12870 * @param {string | undefined} options.label Text label to use for the search button, default "search" 12871 * @param {string | undefined} options.placeholder placeholder, default "Search..." 12872 * @param {string | undefined} options.inputLabel label for the input, default none 12873 * @param {string | undefined} options.noCollapse prevent collapsing on input blur, default false 12874 * @param {number | undefined} options.typing a delay on each typing to start searching (ms) use -1 to prevent autocompletion, default 300. 12875 * @param {integer | undefined} options.minLength minimum length to start searching, default 1 12876 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 12877 * @param {integer | undefined} options.maxHistory maximum number of items to display in history. Set -1 if you don't want history, default maxItems 12878 * @param {function} options.getTitle a function that takes a feature and return the name to display in the index. 12879 * @param {function} options.autocomplete a function that take a search string and callback function to send an array 12880 * @param {number} options.timeout default 20s 12881 */ 12882ol.control.RoutingGeoportail = function(options) { 12883 var self = this; 12884 if (!options) options = {}; 12885 if (options.typing == undefined) options.typing = 300; 12886 options.apiKey = options.apiKey || 'itineraire'; 12887 if (!options.search) options.search = {}; 12888 options.search.apiKey = options.search.apiKey || 'essentiels'; 12889 // Class name for history 12890 this._classname = options.className || 'search'; 12891 this._source = new ol.source.Vector(); 12892 // Authentication 12893 this._auth = options.authentication; 12894 var element = document.createElement("DIV"); 12895 var classNames = (options.className||"")+ " ol-routing"; 12896 if (!options.target) { 12897 classNames += " ol-unselectable ol-control"; 12898 } 12899 element.setAttribute('class', classNames); 12900 if (!options.target) { 12901 var bt = ol.ext.element.create('BUTTON', { parent: element }) 12902 bt.addEventListener('click', function(){ 12903 element.classList.toggle('ol-collapsed'); 12904 }); 12905 } 12906 ol.control.Control.call(this, { 12907 element: element, 12908 target: options.target 12909 }); 12910 this.set('url', 'https://wxs.ign.fr/calcul/geoportail/'+options.apiKey+'/rest/1.0.0/route'); 12911 var content = ol.ext.element.create('DIV', { className: 'content', parent: element } ) 12912 var listElt = ol.ext.element.create('DIV', { className: 'search-input', parent: content }); 12913 this._search = []; 12914 this.addSearch(listElt, options); 12915 this.addSearch(listElt, options); 12916 ol.ext.element.create('I', { className: 'ol-car', title: options.carlabel||'by car', parent: content }) 12917 .addEventListener("click", function() { 12918 self.setMode('car'); 12919 }); 12920 ol.ext.element.create('I', { className: 'ol-pedestrian', title: options.pedlabel||'pedestrian', parent: content }) 12921 .addEventListener("click", function() { 12922 self.setMode('pedestrian'); 12923 }); 12924 ol.ext.element.create('I', { className: 'ol-ok', title: options.runlabel||'search', html:'OK', parent: content }) 12925 .addEventListener("click", function() { 12926 self.calculate(); 12927 }); 12928 ol.ext.element.create('I', { className: 'ol-cancel', html:'cancel', parent: content }) 12929 .addEventListener("click", function() { 12930 this.resultElement.innerHTML = ''; 12931 }.bind(this)); 12932 this.resultElement = document.createElement("DIV"); 12933 this.resultElement.setAttribute('class', 'ol-result'); 12934 element.appendChild(this.resultElement); 12935 this.setMode(options.mode || 'car'); 12936 this.set('timeout', options.timeout || 20000); 12937}; 12938ol.ext.inherits(ol.control.RoutingGeoportail, ol.control.Control); 12939ol.control.RoutingGeoportail.prototype.setMode = function (mode, silent) { 12940 this.set('mode', mode); 12941 this.element.querySelector(".ol-car").classList.remove("selected"); 12942 this.element.querySelector(".ol-pedestrian").classList.remove("selected"); 12943 this.element.querySelector(".ol-"+mode).classList.add("selected"); 12944 if (!silent) this.calculate(); 12945}; 12946ol.control.RoutingGeoportail.prototype.setMethod = function (method, silent) { 12947 this.set('method', method); 12948 if (!silent) this.calculate(); 12949}; 12950ol.control.RoutingGeoportail.prototype.addButton = function (className, title, info) { 12951 var bt = document.createElement("I"); 12952 bt.setAttribute("class", className); 12953 bt.setAttribute("type", "button"); 12954 bt.setAttribute("title", title); 12955 bt.innerHTML = info||''; 12956 this.element.appendChild(bt); 12957 return bt; 12958}; 12959/** Get point source 12960 * @return {ol.source.Vector } 12961 */ 12962ol.control.RoutingGeoportail.prototype.getSource = function () { 12963 return this._source; 12964}; 12965ol.control.RoutingGeoportail.prototype._resetArray = function (element) { 12966 this._search = []; 12967 var q = element.parentNode.querySelectorAll('.search-input > div') 12968 q.forEach(function(d) { 12969 if (d.olsearch) { 12970 if (d.olsearch.get('feature')) { 12971 d.olsearch.get('feature').set('step', this._search.length); 12972 if (this._search.length===0) d.olsearch.get('feature').set('pos', 'start'); 12973 else if (this._search.length === q.length-1) d.olsearch.get('feature').set('pos', 'end'); 12974 else d.olsearch.get('feature').set('pos', ''); 12975 } 12976 this._search.push(d.olsearch); 12977 } 12978 }.bind(this)); 12979}; 12980/** Remove a new search input 12981 * @private 12982 */ 12983ol.control.RoutingGeoportail.prototype.removeSearch = function (element, options, after) { 12984 element.removeChild(after);
12985 if (after.olsearch.get('feature')) this._source.removeFeature(after.olsearch.get('feature')); 12986 if (this.getMap()) this.getMap().removeControl(after.olsearch); 12987 this._resetArray(element); 12988}; 12989/** Add a new search input 12990 * @private 12991 */ 12992ol.control.RoutingGeoportail.prototype.addSearch = function (element, options, after) { 12993 var self = this; 12994 var div = ol.ext.element.create('DIV'); 12995 if (after) element.insertBefore(div, after.nextSibling); 12996 else element.appendChild(div); 12997 ol.ext.element.create ('BUTTON', { title: options.startlabel||"add/remove", parent: div}) 12998 .addEventListener('click', function(e) { 12999 if (e.ctrlKey) { 13000 if (this._search.length>2) this.removeSearch(element, options, div); 13001 } else if (e.shiftKey) { 13002 this.addSearch(element, options, div); 13003 } 13004 }.bind(this)); 13005 var search = div.olsearch = new ol.control.SearchGeoportail({ 13006 className: 'IGNF ol-collapsed', 13007 apiKey: options.search.apiKey, 13008 authentication: options.search.authentication, 13009 target: div, 13010 reverse: true 13011 }); 13012 search._changeCounter = 0; 13013 this._resetArray(element); 13014 search.on('select', function(e){ 13015 search.setInput(e.search.fulltext); 13016 var f = search.get('feature'); 13017 if (!f) { 13018 f = new ol.Feature(new ol.geom.Point(e.coordinate)); 13019 search.set('feature', f); 13020 this._source.addFeature(f); 13021 // Check geometry change 13022 search.checkgeom = true; 13023 f.getGeometry().on('change', function() { 13024 if (search.checkgeom) this.onGeometryChange(search, f); 13025 }.bind(this)); 13026 } else { 13027 search.checkgeom = false; 13028 if (!e.silent) f.getGeometry().setCoordinates(e.coordinate); 13029 search.checkgeom = true; 13030 } 13031 f.set('name', search.getTitle(e.search)); 13032 f.set('step', this._search.indexOf(search)); 13033 if (f.get('step') === 0) f.set('pos','start'); 13034 else if (f.get('step') === this._search.length-1) f.set('pos','end'); 13035 search.set('selection', e.search); 13036 }.bind(this)); 13037 search.element.querySelector('input').addEventListener('change', function(){ 13038 search.set('selection', null); 13039 self.resultElement.innerHTML = ''; 13040 }); 13041 if (this.getMap()) this.getMap().addControl(search); 13042}; 13043/** Geometry has changed 13044 * @private 13045 */ 13046ol.control.RoutingGeoportail.prototype.onGeometryChange = function (search, f, delay) { 13047 // Set current geom 13048 var lonlat = ol.proj.transform(f.getGeometry().getCoordinates(), this.getMap().getView().getProjection(), 'EPSG:4326'); 13049 search._handleSelect({ 13050 x: lonlat[0], 13051 y: lonlat[1], 13052 fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) 13053 }, true, { silent: true }); 13054 // Try to revers geocode 13055 if (delay) { 13056 search._changeCounter--; 13057 if (!search._changeCounter) { 13058 search.reverseGeocode(f.getGeometry().getCoordinates(), { silent: true }); 13059 return; 13060 } 13061 } else { 13062 search._changeCounter++; 13063 setTimeout(function() { 13064 this.onGeometryChange(search, f, true); 13065 }.bind(this), 1000); 13066 } 13067} 13068/** 13069 * Set the map instance the control is associated with 13070 * and add its controls associated to this map. 13071 * @param {_ol_Map_} map The map instance. 13072 */ 13073ol.control.RoutingGeoportail.prototype.setMap = function (map) { 13074 ol.control.Control.prototype.setMap.call(this, map); 13075 for (var i=0; i<this._search.length; i++) { 13076 var c = this._search[i]; 13077 c.setMap(map); 13078 } 13079}; 13080/** Get request data 13081 * @private 13082 */ 13083ol.control.RoutingGeoportail.prototype.requestData = function (steps) { 13084 var start = steps[0]; 13085 var end = steps[steps.length-1]; 13086 var waypoints = ''; 13087 for (var i=1; i<steps.length-1; i++) { 13088 waypoints += (waypoints ? ';':'') + steps[i].x+','+steps[i].y; 13089 } 13090 return { 13091 resource: 'bdtopo-osrm', // 'bdtopo-pgr', 13092 profile: this.get('mode')==='pedestrian' ? 'pedestrian' : 'car', 13093 optimization: this.get('method') || 'fastest', // 'distance' 13094 start: start.x+','+start.y, 13095 end: end.x+','+end.y, 13096 intermediates: waypoints, 13097 geometryFormat: 'geojson' 13098 }; 13099}; 13100/** Gets time as string 13101 * @param {*} routing routing response 13102 * @return {string} 13103 * @api 13104 */ 13105ol.control.RoutingGeoportail.prototype.getTimeString = function (t) { 13106 t /= 60; 13107 return (t<1) ? '' : (t<60) ? t.toFixed(0)+' min' : (t/60).toFixed(0)+' h '+(t%60).toFixed(0)+' min'; 13108}; 13109/** Gets distance as string 13110 * @param {number} d distance 13111 * @return {string} 13112 * @api 13113 */ 13114ol.control.RoutingGeoportail.prototype.getDistanceString = function (d) { 13115 return (d<1000) ? d.toFixed(0)+' m' : (d/1000).toFixed(2)+' km'; 13116}; 13117/** Show routing as a list 13118 * @private 13119 */ 13120ol.control.RoutingGeoportail.prototype.listRouting = function (routing) { 13121 this.resultElement.innerHTML = ''; 13122 var t = this.getTimeString(routing.duration); 13123 t += ' ('+this.getDistanceString(routing.distance)+')'; 13124 var iElement = document.createElement('i'); 13125 iElement.textContent = t; 13126 this.resultElement.appendChild(iElement) 13127 var ul = document.createElement('ul'); 13128 this.resultElement.appendChild(ul); 13129 var info = { 13130 'none': 'Prendre sur ', 13131 'R': 'Tourner à droite sur ', 13132 'FR': 'Tourner légèrement à droite sur ', 13133 'L': 'Tourner à gauche sur ', 13134 'FL': 'Tourner légèrement à gauche sur ', 13135 'F': 'Continuer tout droit sur ', 13136 }
13137 routing.features.forEach(function(f, i) { 13138 var d = this.getDistanceString(f.get('distance')); 13139 t = this.getTimeString(f.get('durationT')); 13140 ol.ext.element.create('LI', { 13141 className: f.get('instruction'), 13142 html: (info[f.get('instruction')||'none']||'#') 13143 + ' ' + f.get('name') 13144 + '<i>' + d + (t ? ' - ' + t : '') +'</i>', 13145 on: { 13146 pointerenter: function() { 13147 this.dispatchEvent({ type: 'step:hover', hover: false, index: i, feature: f }); 13148 }.bind(this), 13149 pointerleave: function() { 13150 this.dispatchEvent({ type: 'step:hover', hover: false, index: i, feature: f }); 13151 }.bind(this) 13152 }, 13153 click: function() { 13154 this.dispatchEvent({ type: 'step:select', index: i, feature: f }); 13155 }.bind(this), 13156 parent: ul 13157 }); 13158 }.bind(this)); 13159}; 13160/** Handle routing response 13161 * @private 13162 */ 13163ol.control.RoutingGeoportail.prototype.handleResponse = function (data, start, end) { 13164 if (data.status === 'ERROR') { 13165 this.dispatchEvent({ 13166 type: 'errror', 13167 status: '200', 13168 statusText: data.message 13169 }) 13170 return; 13171 } 13172 // console.log(data) 13173 var routing = { type:'routing' }; 13174 routing.features = []; 13175 var distance = 0; 13176 var duration = 0; 13177 var f; 13178 var parser = new ol.format.GeoJSON(); 13179 var lastPt; 13180 for (var i=0, l; l=data.portions[i]; i++) { 13181 for (var j=0, s; s=l.steps[j]; j++) { 13182 /* 13183 var options = { 13184 geometry: geom.transform('EPSG:4326',this.getMap().getView().getProjection()), 13185 name: s.name, 13186 instruction: s.navInstruction, 13187 distance: parseFloat(s.distanceMeters), 13188 duration: parseFloat(s.durationSeconds) 13189 } 13190 //console.log(duration, options.duration, s) 13191 distance += options.distance; 13192 duration += options.duration; 13193 options.distanceT = distance; 13194 options.durationT = duration; 13195 f = new ol.Feature(options); 13196 */ 13197 s.type = 'Feature'; 13198 s.properties = s.attributes.name || s.attributes; 13199 s.properties.distance = s.distance; 13200 s.properties.duration = Math.round(s.duration * 60); 13201 // Route info 13202 if (s.instruction) { 13203 s.properties.instruction_type = s.instruction.type; 13204 s.properties.instruction_modifier = s.instruction.modifier; 13205 } 13206 // Distance / time 13207 distance += s.distance; 13208 duration += s.duration; 13209 s.properties.distanceT = Math.round(distance * 100) / 100; 13210 s.properties.durationT = Math.round(duration * 60); 13211 s.properties.name = s.properties.cpx_toponyme_route_nommee || s.properties.cpx_toponyme || s.properties.cpx_numero || s.properties.nom_1_droite || s.properties.nom_1_gauche || ''; 13212 // TODO: BUG ? 13213 var lp = s.geometry.coordinates[s.geometry.coordinates.length-1] 13214 if (lastPt && !ol.coordinate.equal(lp, s.geometry.coordinates[s.geometry.coordinates.length-1])) { 13215 s.geometry.coordinates.unshift(lastPt); 13216 } 13217 lastPt = s.geometry.coordinates[s.geometry.coordinates.length-1]; 13218 // 13219 f = parser.readFeature(s, { 13220 featureProjection: this.getMap().getView().getProjection() 13221 }); 13222 routing.features.push(f); 13223 } 13224 } 13225 routing.distance = parseFloat(data.distance); 13226 routing.duration = parseFloat(data.duration) / 60; 13227 // Full route 13228 var route = parser.readGeometry(data.geometry, { 13229 featureProjection: this.getMap().getView().getProjection() 13230 }); 13231 routing.feature = new ol.Feature ({ 13232 geometry: route, 13233 start: this._search[0].getTitle(start), 13234 end: this._search[0].getTitle(end), 13235 distance: routing.distance, 13236 duration: routing.duration 13237 }); 13238 // console.log(data, routing); 13239 this.dispatchEvent(routing); 13240 this.path = routing; 13241 return routing; 13242}; 13243/** Abort request 13244 */ 13245ol.control.RoutingGeoportail.prototype.abort = function () { 13246 // Abort previous request 13247 if (this._request) { 13248 this._request.abort(); 13249 this._request = null; 13250 this.dispatchEvent({ type: 'abort' }); 13251 } 13252}; 13253/** Calculate route 13254 * @param {Array<ol.coordinate>|undefined} steps an array of steps in EPSG:4326, default use control input values 13255 * @return {boolean} true is a new request is send (more than 2 points to calculate) 13256 */ 13257ol.control.RoutingGeoportail.prototype.calculate = function (steps) { 13258 this.resultElement.innerHTML = ''; 13259 if (steps) { 13260 var convert = []; 13261 steps.forEach(function(s) { 13262 convert.push({ x: s[0], y: s[1] }); 13263 }); 13264 steps = convert; 13265 } else { 13266 steps = [] 13267 for (var i=0; i<this._search.length; i++) { 13268 if (this._search[i].get('selection')) steps.push(this._search[i].get('selection')); 13269 } 13270 } 13271 if (steps.length<2) return false;
13272 var start = steps[0]; 13273 var end = steps[steps.length-1]; 13274 var data = this.requestData(steps); 13275 var url = encodeURI(this.get('url')); 13276 var parameters = ''; 13277 for (var index in data) { 13278 parameters += (parameters) ? '&' : '?'; 13279 if (data.hasOwnProperty(index)) parameters += index + '=' + data[index]; 13280 } 13281 var self = this; 13282 this.dispatchEvent({ type: 'routing:start' }); 13283 this.ajax(url + parameters, 13284 function (resp) { 13285 if (resp.status >= 200 && resp.status < 400) { 13286 self.listRouting(self.handleResponse (JSON.parse(resp.response), start, end)); 13287 } else { 13288 //console.log(url + parameters, arguments); 13289 this.dispatchEvent({ type: 'error', status: resp.status, statusText: resp.statusText}); 13290 } 13291 }.bind(this), 13292 function(resp){ 13293 // console.log('ERROR', resp) 13294 this.dispatchEvent({ type: 'error', status: resp.status, statusText: resp.statusText}); 13295 }.bind(this) 13296 ); 13297 return true; 13298}; 13299/** Send an ajax request (GET) 13300 * @param {string} url 13301 * @param {function} onsuccess callback 13302 * @param {function} onerror callback 13303 */ 13304ol.control.RoutingGeoportail.prototype.ajax = function (url, onsuccess, onerror){ 13305 var self = this; 13306 // Abort previous request 13307 if (this._request) { 13308 this._request.abort(); 13309 } 13310 // New request 13311 var ajax = this._request = new XMLHttpRequest(); 13312 ajax.open('GET', url, true); 13313 ajax.timeout = this.get('timeout') || 20000; 13314 if (this._auth) { 13315 ajax.setRequestHeader("Authorization", "Basic " + this._auth); 13316 } 13317 this.element.classList.add('ol-searching'); 13318 // Load complete 13319 ajax.onload = function() { 13320 self._request = null; 13321 self.element.classList.remove('ol-searching'); 13322 onsuccess.call(self, this); 13323 }; 13324 // Timeout 13325 ajax.ontimeout = function () { 13326 self._request = null; 13327 self.element.classList.remove('ol-searching'); 13328 if (onerror) onerror.call(self, this); 13329 }; 13330 // Oops, TODO do something ? 13331 ajax.onerror = function() { 13332 self._request = null; 13333 self.element.classList.remove('ol-searching'); 13334 if (onerror) onerror.call(self, this); 13335 }; 13336 // GO! 13337 ajax.send(); 13338}; 13339 13340/* Copyright (c) 2017 Jean-Marc VIGLINO, 13341 released under the CeCILL-B license (French BSD license) 13342 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 13343*/ 13344/** 13345 * Scale Control. 13346 * A control to display the scale of the center on the map 13347 * 13348 * @constructor 13349 * @extends {ol.control.Control} 13350 * @fires select 13351 * @fires change:input 13352 * @param {Object=} options 13353 * @param {string} options.className control class name 13354 * @param {string} options.ppi screen ppi, default 96 13355 * @param {string} options.editable make the control editable, default true 13356 */ 13357ol.control.Scale = function(options) { 13358 if (!options) options = {}; 13359 if (options.typing == undefined) options.typing = 300; 13360 var element = document.createElement("DIV"); 13361 var classNames = (options.className||"")+ " ol-scale"; 13362 if (!options.target) { 13363 classNames += " ol-unselectable ol-control"; 13364 } 13365 this._input = document.createElement("INPUT"); 13366 this._input.value = '-'; 13367 element.setAttribute('class', classNames); 13368 if (options.editable===false) this._input.readOnly = true; 13369 element.appendChild(this._input); 13370 ol.control.Control.call(this, { 13371 element: element, 13372 target: options.target 13373 }); 13374 this._input.addEventListener("change", this.setScale.bind(this)); 13375 this.set('ppi', options.ppi || 96) 13376}; 13377ol.ext.inherits(ol.control.Scale, ol.control.Control); 13378/** 13379 * Remove the control from its current map and attach it to the new map. 13380 * Subclasses may set up event handlers to get notified about changes to 13381 * the map here. 13382 * @param {ol.Map} map Map. 13383 * @api stable 13384 */ 13385ol.control.Scale.prototype.setMap = function (map) { 13386 if (this._listener) ol.Observable.unByKey(this._listener); 13387 this._listener = null; 13388 ol.control.Control.prototype.setMap.call(this, map); 13389 // Get change (new layer added or removed) 13390 if (map) { 13391 this._listener = map.on('moveend', this.getScale.bind(this)); 13392 } 13393}; 13394/** Display the scale 13395 */ 13396ol.control.Scale.prototype.getScale = function () { 13397 var map = this.getMap(); 13398 if (map) { 13399 var d = ol.sphere.getMapScale(map, this.get('ppi')); 13400 this._input.value = this.formatScale(d); 13401 return d; 13402 } 13403}; 13404/** Format the scale 1/d 13405 * @param {Number} d 13406 * @return {string} formated string 13407 */ 13408ol.control.Scale.prototype.formatScale = function (d) { 13409 if (d>100) d = Math.round(d/100) * 100; 13410 else d = Math.round(d);
13411 return '1 / '+ d.toLocaleString(); 13412}; 13413/** Set the current scale (will change the scale of the map) 13414 * @param {Number} value the scale factor 13415 */ 13416ol.control.Scale.prototype.setScale = function (value) { 13417 var map = this.getMap(); 13418 if (map && value) { 13419 if (value.target) value = value.target.value; 13420 ol.sphere.setMapScale(map, value, this.get('ppi')); 13421 } 13422 this.getScale(); 13423}; 13424 13425/* Copyright (c) 2017 Jean-Marc VIGLINO, 13426 released under the CeCILL-B license (French BSD license) 13427 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 13428*/ 13429/** 13430 * Search places using the French National Base Address (BAN) API. 13431 * 13432 * @constructor 13433 * @extends {ol.control.Search} 13434 * @fires select 13435 * @param {Object=} Control options. 13436 * @param {string} options.className control class name 13437 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 13438 * @param {string | undefined} options.reverseTitle Title to use for the reverse geocoding button tooltip, default "Click on the map..." 13439 * @param {string | undefined} options.label Text label to use for the search button, default "search" 13440 * @param {string | undefined} options.placeholder placeholder, default "Search..." 13441 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500. 13442 * @param {integer | undefined} options.minLength minimum length to start searching, default 3 13443 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 13444 * 13445 * @param {string|undefined} options.url Url to BAN api, default "https://api-adresse.data.gouv.fr/search/" 13446 * @param {boolean} options.position Search, with priority to geo position, default false 13447 * @param {function} options.getTitle a function that takes a feature and return the text to display in the menu, default return label attribute 13448 * @param {string|undefined} options.citycode limit search to an administrative area defined by its city code (code commune insee) 13449 * @param {string|undefined} options.postcode limit search to a postal code 13450 * @param {string|undefined} options.type type of result: 'housenumber' | 'street' 13451 * @see {@link https://adresse.data.gouv.fr/api/} 13452 */ 13453 ol.control.SearchBAN = function(options) { 13454 options = options || {}; 13455 options.typing = options.typing || 500; 13456 options.url = options.url || 'https://api-adresse.data.gouv.fr/search/'; 13457 options.className = options.className || 'BAN'; 13458 options.copy = '<a href="https://adresse.data.gouv.fr/" target="new">© BAN-data.gouv.fr</a>'; 13459 ol.control.SearchPhoton.call(this, options); 13460 this.set("postcode", options.postcode); 13461 this.set("citycode", options.citycode); 13462 this.set("type", options.type); 13463 }; 13464ol.ext.inherits(ol.control.SearchBAN, ol.control.SearchPhoton); 13465/** Returns the text to be displayed in the menu 13466 * @param {ol.Feature} f the feature 13467 * @return {string} the text to be displayed in the index 13468 * @api 13469 */ 13470ol.control.SearchBAN.prototype.getTitle = function (f) { 13471 var p = f.properties; 13472 return (p.label); 13473}; 13474/** A ligne has been clicked in the menu > dispatch event 13475 * @param {any} f the feature, as passed in the autocomplete 13476 * @api 13477 */ 13478ol.control.SearchBAN.prototype.select = function (f){ 13479 var c = f.geometry.coordinates; 13480 // Add coordinate to the event 13481 try { 13482 c = ol.proj.transform (f.geometry.coordinates, 'EPSG:4326', this.getMap().getView().getProjection()); 13483 } catch(e) { /* ok */ } 13484 this.dispatchEvent({ type:"select", search:f, coordinate: c }); 13485}; 13486ol.control.SearchBAN.prototype.requestData = function (s) { 13487 var data = ol.control.SearchPhoton.prototype.requestData.call(this, s); 13488 data.postcode = this.get('postcode'), 13489 data.citycode = this.get('citycode'), 13490 data.type = this.get('type') 13491 return data; 13492}; 13493 13494/* Copyright (c) 2017 Jean-Marc VIGLINO, 13495 released under the CeCILL-B license (French BSD license) 13496 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 13497*/ 13498/** 13499 * Search on DFCI grid. 13500 * 13501 * @constructor 13502 * @extends {ol.control.Search} 13503 * @fires select 13504 * @param {Object=} Control options. 13505 * @param {string} options.className control class name 13506 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 13507 * @param {string | undefined} options.label Text label to use for the search button, default "search" 13508 * @param {string | undefined} options.placeholder placeholder, default "Search..." 13509 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 300. 13510 * @param {integer | undefined} options.minLength minimum length to start searching, default 1 13511 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 13512 * 13513 * @param {string | undefined} options.property a property to display in the index, default 'name'. 13514 * @param {function} options.getTitle a function that takes a feature and return the name to display in the index, default return the property 13515 * @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 13516 */ 13517ol.control.SearchDFCI = function(options) { 13518 if (!options) options = {}; 13519 options.className = options.className || 'dfci'; 13520 options.placeholder = options.placeholder || 'Code DFCI'; 13521 ol.control.Search.call(this, options); 13522}; 13523ol.ext.inherits(ol.control.SearchDFCI, ol.control.Search); 13524/** Autocomplete function 13525* @param {string} s search string 13526* @return {Array<any>|false} an array of search solutions or false if the array is send with the cback argument (asnchronous) 13527* @api 13528*/ 13529ol.control.SearchDFCI.prototype.autocomplete = function (s) { 13530 s = s.toUpperCase(); 13531 s = s.replace(/[^0-9,^A-H,^K-N]/g,''); 13532 if (s.length<2) { 13533 this.setInput(s); 13534 return []; 13535 } 13536 var i; 13537 var proj = this.getMap().getView().getProjection(); 13538 var result = []; 13539 var c = ol.coordinate.fromDFCI(s, proj); 13540 var level = Math.floor(s.length/2)-1; 13541 var dfci = ol.coordinate.toDFCI(c, level, proj); 13542 dfci = dfci.replace(/[^0-9,^A-H,^K-N]/g,''); 13543 // Valid DFCI ? 13544 if (!/NaN/.test(dfci) && dfci) { 13545 console.log('ok', dfci) 13546 this.setInput(dfci + s.substring(dfci.length, s.length)); 13547 result.push({ coordinate: ol.coordinate.fromDFCI(dfci, proj), name: dfci }); 13548 if (s.length===5) { 13549 c = ol.coordinate.fromDFCI(s+0, proj); 13550 dfci = (ol.coordinate.toDFCI(c, level+1, proj)).substring(0,5); 13551 for (i=0; i<10; i++) { 13552 result.push({ coordinate: ol.coordinate.fromDFCI(dfci+i, proj), name: dfci+i }); 13553 } 13554 } 13555 if (level === 2) { 13556 for (i=0; i<6; i++) { 13557 result.push({ coordinate: ol.coordinate.fromDFCI(dfci+'.'+i, proj), name: dfci+'.'+i }); 13558 } 13559 } 13560 } 13561 return result; 13562}; 13563 13564/* Copyright (c) 2017 Jean-Marc VIGLINO, 13565 released under the CeCILL-B license (French BSD license)
13566 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 13567*/ 13568/** 13569 * Search features. 13570 * 13571 * @constructor 13572 * @extends {ol.control.Search} 13573 * @fires select 13574 * @param {Object=} Control options. 13575 * @param {string} options.className control class name 13576 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 13577 * @param {string | undefined} options.label Text label to use for the search button, default "search" 13578 * @param {string | undefined} options.placeholder placeholder, default "Search..." 13579 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 300. 13580 * @param {integer | undefined} options.minLength minimum length to start searching, default 1 13581 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 13582 * 13583 * @param {string | undefined} options.property a property to display in the index, default 'name'. 13584 * @param {function} options.getTitle a function that takes a feature and return the name to display in the index, default return the property 13585 * @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 13586 */ 13587ol.control.SearchFeature = function(options) { 13588 if (!options) options = {}; 13589 options.className = options.className || 'feature'; 13590 ol.control.Search.call(this, options); 13591 if (typeof(options.getSearchString)=="function") this.getSearchString = options.getSearchString; 13592 this.set('property', options.property || 'name'); 13593 this.source_ = options.source; 13594}; 13595ol.ext.inherits(ol.control.SearchFeature, ol.control.Search); 13596/** No history avaliable on features 13597 */ 13598ol.control.SearchFeature.prototype.restoreHistory = function () { 13599 this.set('history', []); 13600}; 13601/** No history avaliable on features 13602 */ 13603ol.control.SearchFeature.prototype.saveHistory = function () { 13604 try { 13605 localStorage.removeItem("ol@search-"+this._classname); 13606 } catch(e) { console.warn('Failed to access localStorage...'); } 13607} 13608/** Returns the text to be displayed in the menu 13609* @param {ol.Feature} f the feature 13610* @return {string} the text to be displayed in the index 13611* @api 13612*/ 13613ol.control.SearchFeature.prototype.getTitle = function (f) { 13614 return f.get(this.get('property')||'name'); 13615}; 13616/** Return the string to search in 13617* @param {ol.Feature} f the feature 13618* @return {string} the text to be used as search string 13619* @api 13620*/ 13621ol.control.SearchFeature.prototype.getSearchString = function (f) { 13622 return this.getTitle(f); 13623}; 13624/** Get the source 13625* @return {ol.source.Vector} 13626* @api 13627*/ 13628ol.control.SearchFeature.prototype.getSource = function () { 13629 return this.source_; 13630} 13631/** Get the source 13632* @param {ol.source.Vector} source 13633* @api 13634*/ 13635ol.control.SearchFeature.prototype.setSource = function (source) { 13636 this.source_ = source; 13637}; 13638/** Autocomplete function 13639* @param {string} s search string 13640* @param {int} max max 13641* @param {function} cback a callback function that takes an array to display in the autocomplete field (for asynchronous search) 13642* @return {Array<any>|false} an array of search solutions or false if the array is send with the cback argument (asnchronous) 13643* @api 13644*/ 13645ol.control.SearchFeature.prototype.autocomplete = function (s) { 13646 var result = []; 13647 if (this.source_) { 13648 // regexp 13649 s = s.replace(/^\*/,''); 13650 var rex = new RegExp(s, 'i'); 13651 // The source 13652 var features = this.source_.getFeatures(); 13653 var max = this.get('maxItems') 13654 for (var i=0, f; f=features[i]; i++) { 13655 var att = this.getSearchString(f); 13656 if (att !== undefined && rex.test(att)) { 13657 result.push(f); 13658 if ((--max)<=0) break; 13659 } 13660 } 13661 } 13662 return result; 13663}; 13664 13665/* Copyright (c) 2019 Jean-Marc VIGLINO, 13666 released under the CeCILL-B license (French BSD license) 13667 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 13668*/ 13669/** 13670 * Search on GPS coordinate. 13671 * 13672 * @constructor 13673 * @extends {ol.control.Search} 13674 * @fires select 13675 * @param {Object=} Control options. 13676 * @param {string} options.className control class name 13677 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 13678 * @param {string | undefined} options.label Text label to use for the search button, default "search" 13679 * @param {string | undefined} options.placeholder placeholder, default "Search..." 13680 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 300. 13681 * @param {integer | undefined} options.minLength minimum length to start searching, default 1 13682 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 13683 */ 13684ol.control.SearchGPS = function(options) { 13685 if (!options) options = {}; 13686 options.className = (options.className || '') + ' ol-searchgps'; 13687 options.placeholder = options.placeholder || 'lon,lat'; 13688 ol.control.Search.call(this, options); 13689 // Geolocation 13690 this.geolocation = new ol.Geolocation({ 13691 projection: "EPSG:4326", 13692 trackingOptions: { 13693 maximumAge: 10000, 13694 enableHighAccuracy: true, 13695 timeout: 600000 13696 } 13697 }); 13698 ol.ext.element.create ('BUTTON', { 13699 className: 'ol-geoloc', 13700 title: 'Locate with GPS', 13701 parent: this.element, 13702 click: function(){ 13703 this.geolocation.setTracking(true); 13704 }.bind(this) 13705 }) 13706 // DMS switcher 13707 ol.ext.element.createSwitch({ 13708 html: 'decimal', 13709 after: 'DMS', 13710 change: function(e) { 13711 if (e.target.checked) this.element.classList.add('ol-dms'); 13712 else this.element.classList.remove('ol-dms'); 13713 }.bind(this), 13714 parent: this.element 13715 }); 13716 this._createForm(); 13717 // Move list to the end 13718 var ul = this.element.querySelector("ul.autocomplete"); 13719 this.element.appendChild(ul); 13720}; 13721ol.ext.inherits(ol.control.SearchGPS, ol.control.Search); 13722/** Create input form 13723 * @private 13724 */ 13725ol.control.SearchGPS.prototype._createForm = function () { 13726 // Value has change 13727 var onchange = function(e) { 13728 if (e.target.classList.contains('ol-dms')) { 13729 lon.value = (lond.value<0 ? -1:1) * Number(lond.value) + Number(lonm.value)/60 + Number(lons.value)/3600; 13730 lon.value = (lond.value<0 ? -1:1) * Math.round(lon.value*10000000)/10000000; 13731 lat.value = (latd.value<0 ? -1:1) * Number(latd.value) + Number(latm.value)/60 + Number(lats.value)/3600; 13732 lat.value = (latd.value<0 ? -1:1) * Math.round(lat.value*10000000)/10000000; 13733 } 13734 if (lon.value||lat.value) { 13735 this._input.value = lon.value+','+lat.value; 13736 } else { 13737 this._input.value = ''; 13738 } 13739 if (!e.target.classList.contains('ol-dms')) { 13740 var s = ol.coordinate.toStringHDMS([Number(lon.value), Number(lat.value)]); 13741 var c = s.replace(/(N|S|E|W)/g,'').split('â³');
13742 c[1] = c[1].trim().split(' '); 13743 lond.value = (/W/.test(s) ? -1 : 1) * parseInt(c[1][0]); 13744 lonm.value = parseInt(c[1][1]); 13745 lons.value = parseInt(c[1][2]); 13746 c[0] = c[0].trim().split(' '); 13747 latd.value = (/W/.test(s) ? -1 : 1) * parseInt(c[0][0]); 13748 latm.value = parseInt(c[0][1]); 13749 lats.value = parseInt(c[0][2]); 13750 } 13751 this.search(); 13752 }.bind(this); 13753 function createInput(className, unit) { 13754 var input = ol.ext.element.create('INPUT', { 13755 className: className, 13756 type:'number', 13757 step:'any', 13758 lang: 'en', 13759 parent: div, 13760 on: { 13761 'change keyup': onchange 13762 } 13763 }); 13764 if (unit) { 13765 ol.ext.element.create('SPAN', { 13766 className: 'ol-dms', 13767 html: unit, 13768 parent: div, 13769 }); 13770 } 13771 return input; 13772 } 13773 // Longitude 13774 var div = ol.ext.element.create('DIV', { 13775 className: 'ol-longitude', 13776 parent: this.element 13777 }); 13778 ol.ext.element.create('LABEL', { 13779 html: 'Longitude', 13780 parent: div 13781 }); 13782 var lon = createInput('ol-decimal'); 13783 var lond = createInput('ol-dms','°'); 13784 var lonm = createInput('ol-dms','\''); 13785 var lons = createInput('ol-dms','"'); 13786 // Latitude 13787 div = ol.ext.element.create('DIV', { 13788 className: 'ol-latitude', 13789 parent: this.element 13790 }) 13791 ol.ext.element.create('LABEL', { 13792 html: 'Latitude', 13793 parent: div 13794 }); 13795 var lat = createInput('ol-decimal'); 13796 var latd = createInput('ol-dms','°'); 13797 var latm = createInput('ol-dms','\''); 13798 var lats = createInput('ol-dms','"'); 13799 // Focus on open 13800 if (this.button) { 13801 this.button.addEventListener("click", function() { 13802 lon.focus(); 13803 }); 13804 } 13805 // Change value on click 13806 this.on('select', function(e){ 13807 lon.value = e.search.gps[0]; 13808 lat.value = e.search.gps[1]; 13809 }.bind(this)); 13810 // Change value on geolocation 13811 this.geolocation.on('change', function(){ 13812 this.geolocation.setTracking(false); 13813 var coord = this.geolocation.getPosition(); 13814 lon.value = coord[0]; 13815 lat.value = coord[1]; 13816 this._triggerCustomEvent('keyup', lon); 13817 }.bind(this)); 13818}; 13819/** Autocomplete function 13820* @param {string} s search string 13821* @return {Array<any>|false} an array of search solutions 13822* @api 13823*/ 13824ol.control.SearchGPS.prototype.autocomplete = function (s) { 13825 var result = []; 13826 var c = s.split(','); 13827 c[0] = Number(c[0]); 13828 c[1] = Number(c[1]); 13829 // Name 13830 s = ol.coordinate.toStringHDMS(c) 13831 if (s) s= s.replace(/(°|â²|â³) /g,'$1'); 13832 // 13833 var coord = ol.proj.transform ([c[0], c[1]], 'EPSG:4326', this.getMap().getView().getProjection()); 13834 result.push({ gps: c, coordinate: coord, name: s }); 13835 return result; 13836}; 13837 13838/* Copyright (c) 2017 Jean-Marc VIGLINO, 13839 released under the CeCILL-B license (French BSD license) 13840 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 13841*/ 13842/** 13843 * Search places using the French National Base Address (BAN) API. 13844 * 13845 * @constructor 13846 * @extends {ol.control.SearchJSON} 13847 * @fires select 13848 * @param {any} options extend ol.control.SearchJSON options 13849 * @param {string} options.className control class name 13850 * @param {boolean | undefined} [options.apiKey] the service api key. 13851 * @param {string | undefined} options.authentication: basic authentication for the service API as btoa("login:pwd") 13852 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 13853 * @param {string | undefined} options.label Text label to use for the search button, default "search" 13854 * @param {string | undefined} options.placeholder placeholder, default "Search..." 13855 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500. 13856 * @param {integer | undefined} options.minLength minimum length to start searching, default 3 13857 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 13858 * 13859 * @param {Number} options.pageSize item per page for parcelle list paging, use -1 for no paging, default 5 13860 * @see {@link https://geoservices.ign.fr/documentation/geoservices/geocodage.html} 13861 */ 13862ol.control.SearchGeoportailParcelle = function(options) { 13863 var self = this; 13864 options.type = "Commune"; 13865 options.className = (options.className ? options.className:"")+" IGNF-parcelle ol-collapsed-list ol-collapsed-num"; 13866 options.inputLabel = "Commune"; 13867 options.noCollapse = true; 13868 options.placeholder = options.placeholder || "Choisissez une commune..."; 13869 ol.control.SearchGeoportail.call(this, options); 13870 this.set('copy', null); 13871 var element = this.element; 13872 // Add parcel form 13873 var div = document.createElement("DIV"); 13874 element.appendChild(div); 13875 var label = document.createElement("LABEL"); 13876 label.innerText = 'Préfixe' 13877 div.appendChild(label); 13878 label = document.createElement("LABEL"); 13879 label.innerText = 'Section' 13880 div.appendChild(label); 13881 label = document.createElement("LABEL"); 13882 label.innerText = 'Numéro' 13883 div.appendChild(label); 13884 div.appendChild(document.createElement("BR")); 13885 // Input 13886 this._inputParcelle = { 13887 prefix: document.createElement("INPUT"), 13888 section: document.createElement("INPUT"), 13889 numero: document.createElement("INPUT") 13890 }; 13891 this._inputParcelle.prefix.setAttribute('maxlength',3); 13892 this._inputParcelle.section.setAttribute('maxlength',2); 13893 this._inputParcelle.numero.setAttribute('maxlength',4); 13894 // Delay search 13895 var tout; 13896 var doSearch = function() { 13897 if (tout) clearTimeout(tout); 13898 tout = setTimeout(function() { 13899 self.autocompleteParcelle(); 13900 }, options.typing || 0); 13901 } 13902 // Add inputs 13903 for (var i in this._inputParcelle) { 13904 div.appendChild(this._inputParcelle[i]); 13905 this._inputParcelle[i].addEventListener("keyup", doSearch); 13906 this._inputParcelle[i].addEventListener('blur', function() { 13907 tout = setTimeout(function(){ element.classList.add('ol-collapsed-num'); }, 200); 13908 }); 13909 this._inputParcelle[i].addEventListener('focus', function() { 13910 clearTimeout(tout); 13911 element.classList.remove('ol-collapsed-num'); 13912 }); 13913 } 13914 this.activateParcelle(false); 13915 // Autocomplete list 13916 var auto = document.createElement('DIV'); 13917 auto.className = 'autocomplete-parcelle'; 13918 element.appendChild(auto); 13919 var ul = document.createElement('UL'); 13920 ul.classList.add('autocomplete-parcelle'); 13921 auto.appendChild(ul); 13922 ul = document.createElement('UL'); 13923 ul.classList.add('autocomplete-page'); 13924 auto.appendChild(ul); 13925 // Show/hide list on fcus/blur 13926 this._input.addEventListener('blur', function() { 13927 setTimeout(function(){ element.classList.add('ol-collapsed-list') }, 200); 13928 }); 13929 this._input.addEventListener('focus', function() { 13930 element.classList.remove('ol-collapsed-list'); 13931 self._listParcelle([]); 13932 if (self._commune) { 13933 self._commune = null; 13934 self._input.value = ''; 13935 self.drawList_(); 13936 } 13937 self.activateParcelle(false); 13938 }); 13939 this.on('select', this.selectCommune.bind(this)); 13940 this.set('pageSize', options.pageSize || 5); 13941}; 13942ol.ext.inherits(ol.control.SearchGeoportailParcelle, ol.control.SearchGeoportail); 13943/** Select a commune => start searching parcelle 13944 * @param {any} e 13945 * @private 13946 */ 13947ol.control.SearchGeoportailParcelle.prototype.selectCommune = function(e) { 13948 this._commune = e.search.insee; 13949 this._input.value = e.search.insee + ' - ' + e.search.fulltext; 13950 this.activateParcelle(true); 13951 this._inputParcelle.numero.focus(); 13952 this.autocompleteParcelle(); 13953}; 13954/** Set the input parcelle 13955 * @param {*} p parcel 13956 * @param {string} p.Commune 13957 * @param {string} p.CommuneAbsorbee 13958 * @param {string} p.Section 13959 * @param {string} p.Numero 13960 * @param {boolean} search start a search 13961 */ 13962ol.control.SearchGeoportailParcelle.prototype.setParcelle = function(p, search) { 13963 this._inputParcelle.prefix.value = (p.Commune||'') + (p.CommuneAbsorbee||''); 13964 this._inputParcelle.section.value = p.Section||''; 13965 this._inputParcelle.numero.value = p.Numero||''; 13966 if (search) this._triggerCustomEvent("keyup", this._inputParcelle.prefix); 13967}; 13968/** Activate parcelle inputs 13969 * @param {bolean} b 13970 */ 13971ol.control.SearchGeoportailParcelle.prototype.activateParcelle = function(b) { 13972 for (var i in this._inputParcelle) { 13973 this._inputParcelle[i].readOnly = !b; 13974 } 13975 if (b) { 13976 this._inputParcelle.section.parentElement.classList.add('ol-active'); 13977 } else { 13978 this._inputParcelle.section.parentElement.classList.remove('ol-active'); 13979 } 13980}; 13981/** Send search request for the parcelle 13982 * @private 13983 */ 13984ol.control.SearchGeoportailParcelle.prototype.autocompleteParcelle = function() { 13985 // Add 0 to fit the format 13986 function complete (s, n, c) { 13987 if (!s) return s; 13988 c = c || "0"; 13989 while (s.length < n) s = c+s; 13990 return s.replace(/\*/g,'_'); 13991 } 13992 // The selected commune 13993 var commune = this._commune; 13994 var prefix = complete (this._inputParcelle.prefix.value, 3); 13995 if (prefix === '000') { 13996 prefix = '___'; 13997 } 13998 // Get parcelle number 13999 var section = complete (this._inputParcelle.section.value, 2); 14000 var numero = complete (this._inputParcelle.numero.value, 4, "0"); 14001 var search = commune + (prefix||'___') + (section||"__") + (numero ? numero : section ? "____":"0001"); 14002 this.searchParcelle(search, 14003 function(jsonResp) { 14004 this._listParcelle(jsonResp); 14005 }.bind(this), 14006 function() { 14007 console.log('oops') 14008 }) 14009}; 14010/** Send search request for a parcelle number 14011 * @param {string} search search parcelle number 14012 * @param {function} success callback function called on success 14013 * @param {function} error callback function called on error 14014 */ 14015ol.control.SearchGeoportailParcelle.prototype.searchParcelle = function(search, success /*, error */) { 14016 // Request 14017 var request = '<?xml version="1.0" encoding="UTF-8"?>' 14018 +'<XLS xmlns:xls="http://www.opengis.net/xls" xmlns:gml="http://www.opengis.net/gml" xmlns="http://www.opengis.net/xls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="http://www.opengis.net/xls http://schemas.opengis.net/ols/1.2/olsAll.xsd">' 14019 +'<RequestHeader/>' 14020 +'<Request requestID="1" version="1.2" methodName="LocationUtilityService">' 14021 +'<GeocodeRequest returnFreeForm="false">' 14022 +'<Address countryCode="CadastralParcel">' 14023 +'<freeFormAddress>'+search+'+</freeFormAddress>' 14024 +'</Address>' 14025 +'</GeocodeRequest>' 14026 +'</Request>' 14027 +'</XLS>' 14028 // Geocode 14029 this.ajax( 14030 this.get('url').replace('ols/apis/completion','geoportail/ols'), 14031 { xls: request }, 14032 function(xml) { 14033 // XML to JSON 14034 var parser = new DOMParser(); 14035 var xmlDoc = parser.parseFromString(xml,"text/xml"); 14036 var parcelles = xmlDoc.getElementsByTagName('GeocodedAddress'); 14037 var jsonResp = [] 14038 for (var i=0, parc; parc= parcelles[i]; i++) { 14039 var node = parc.getElementsByTagName('gml:pos')[0] || parc.getElementsByTagName('pos')[0]; 14040 var p = node.childNodes[0].nodeValue.split(' '); 14041 var att = parc.getElementsByTagName('Place'); 14042 var json = { 14043 lon: Number(p[1]), 14044 lat: Number(p[0]) 14045 }; 14046 for (var k=0, a; a=att[k]; k++) { 14047 json[a.attributes.type.value] = a.childNodes[0].nodeValue; 14048 } 14049 jsonResp.push(json); 14050 } 14051 success(jsonResp); 14052 }, 14053 { dataType: 'XML' } 14054 ); 14055}; 14056/** 14057 * Draw the autocomplete list 14058 * @param {*} resp 14059 * @private 14060 */ 14061ol.control.SearchGeoportailParcelle.prototype._listParcelle = function(resp) { 14062 var self = this; 14063 var ul = this.element.querySelector("ul.autocomplete-parcelle"); 14064 ul.innerHTML='';
14065 var page = this.element.querySelector("ul.autocomplete-page"); 14066 page.innerHTML=''; 14067 this._listParc = []; 14068 // Show page i 14069 function showPage(i) { 14070 var l = ul.children; 14071 var visible = "ol-list-"+i; 14072 var k; 14073 for (k=0; k<l.length; k++) { 14074 l[k].style.display = (l[k].className===visible) ? '' : 'none'; 14075 } 14076 l = page.children; 14077 for (k=0; k<l.length; k++) { 14078 l[k].className = (l[k].innerText==i) ? 'selected' : ''; 14079 } 14080 page.style.display = l.length>1 ? '' : 'none'; 14081 } 14082 // Sort table 14083 resp.sort(function(a,b) { 14084 var na = a.INSEE+a.CommuneAbsorbee+a.Section+a.Numero; 14085 var nb = b.INSEE+b.CommuneAbsorbee+b.Section+b.Numero; 14086 return na===nb ? 0 : na<nb ? -1 : 1; 14087 }); 14088 // Show list 14089 var n = this.get('pageSize'); 14090 for (var i=0, r; r = resp[i]; i++) { 14091 var li = document.createElement("LI"); 14092 li.setAttribute("data-search", i); 14093 if (n>0) li.classList.add("ol-list-"+Math.floor(i/n)); 14094 this._listParc.push(r); 14095 li.addEventListener("click", function(e) { 14096 self._handleParcelle(self._listParc[e.currentTarget.getAttribute("data-search")]); 14097 }); 14098 li.innerHTML = r.INSEE+r.CommuneAbsorbee+r.Section+r.Numero; 14099 ul.appendChild(li); 14100 // 14101 if (n>0 && !(i%n)) { 14102 li = document.createElement("LI"); 14103 li.innerText = Math.floor(i/n); 14104 li.addEventListener("click", function(e) { 14105 showPage(e.currentTarget.innerText); 14106 }); 14107 page.appendChild(li); 14108 } 14109 } 14110 if (n>0) showPage(0); 14111}; 14112/** 14113 * Handle parcelle section 14114 * @param {*} parc 14115 * @private 14116 */ 14117ol.control.SearchGeoportailParcelle.prototype._handleParcelle = function(parc) { 14118 this.dispatchEvent({ 14119 type:"parcelle", 14120 search: parc, 14121 coordinate: ol.proj.fromLonLat([parc.lon, parc.lat], this.getMap().getView().getProjection()) 14122 }); 14123}; 14124 14125/* Copyright (c) 2017 Jean-Marc VIGLINO, 14126 released under the CeCILL-B license (French BSD license) 14127 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 14128*/ 14129/** 14130 * Search places using the Nominatim geocoder from the OpenStreetmap project. 14131 * 14132 * @constructor 14133 * @extends {ol.control.Search} 14134 * @fires select 14135 * @param {Object=} Control options. 14136 * @param {string} options.className control class name 14137 * @param {boolean | undefined} options.polygon To get output geometry of results (in geojson format), default false. 14138 * @param {Array<Number> | 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. 14139 * @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 14140 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 14141 * @param {string | undefined} options.title Title to use for the search button tooltip, default "Search" 14142 * @param {string | undefined} options.reverseTitle Title to use for the reverse geocoding button tooltip, default "Click on the map..." 14143 * @param {string | undefined} options.placeholder placeholder, default "Search..." 14144 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500. 14145 * @param {integer | undefined} options.minLength minimum length to start searching, default 3 14146 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 14147 * @params {string | undefined} options.countrycodes 14148 * 14149 * @param {string|undefined} options.url URL to Nominatim API, default "https://nominatim.openstreetmap.org/search" 14150 * @see {@link https://wiki.openstreetmap.org/wiki/Nominatim} 14151 */ 14152ol.control.SearchNominatim = function(options) { 14153 options = options || {}; 14154 options.className = options.className || 'nominatim'; 14155 options.typing = options.typing || 500; 14156 options.url = options.url || 'https://nominatim.openstreetmap.org/search'; 14157 options.copy = '<a href="http://www.openstreetmap.org/copyright" target="new">© OpenStreetMap contributors</a>'; 14158 options.countrycodes = options.countrycodes || 'de'; 14159 ol.control.SearchJSON.call(this, options); 14160 this.set('polygon', options.polygon); 14161 this.set('viewbox', options.viewbox); 14162 this.set('bounded', options.bounded);
14163 this.set('countrycodes',options.countrycodes); 14164}; 14165ol.ext.inherits(ol.control.SearchNominatim, ol.control.SearchJSON); 14166/** Returns the text to be displayed in the menu 14167 * @param {ol.Feature} f the feature 14168* @return {string} the text to be displayed in the index 14169* @api 14170*/ 14171ol.control.SearchNominatim.prototype.getTitle = function (f) { 14172 var info = []; 14173 if (f.class) info.push(f.class); 14174 if (f.type) info.push(f.type); 14175 var title = f.display_name+(info.length ? "<i>"+info.join(' - ')+"</i>" : ''); 14176 if (f.icon) title = "<img src='"+f.icon+"' />" + title; 14177 return (title); 14178}; 14179/** 14180 * @param {string} s the search string 14181 * @return {Object} request data (as key:value) 14182 * @api 14183 */ 14184ol.control.SearchNominatim.prototype.requestData = function (s) { 14185 var data = { 14186 format: "json", 14187 addressdetails: 1, 14188 q: s, 14189 polygon_geojson: this.get('polygon') ? 1:0, 14190 bounded: this.get('bounded') ? 1:0, 14191 limit: this.get('maxItems'), 14192 countrycodes: this.get('countrycodes'), 14193 'accept-language': 'de' 14194 }; 14195 if (this.get('viewbox')) data.viewbox = this.get('viewbox'); 14196 return data; 14197}; 14198/** A ligne has been clicked in the menu > dispatch event 14199 * @param {any} f the feature, as passed in the autocomplete 14200* @api 14201*/ 14202ol.control.SearchNominatim.prototype.select = function (f){ 14203 var c = [Number(f.lon), Number(f.lat)]; 14204 // Add coordinate to the event 14205 try { 14206 c = ol.proj.transform (c, 'EPSG:4326', this.getMap().getView().getProjection()); 14207 } catch(e) { /* ok */} 14208 this.dispatchEvent({ type:"select", search:f, coordinate: c }); 14209}; 14210/** 14211 * Handle server response to pass the features array to the display list 14212 * @param {any} response server response 14213 * @return {Array<any>} an array of feature 14214 * @api 14215 */ 14216ol.control.SearchJSON.prototype.handleResponse = function (response) { 14217 return response.results || response; 14218}; 14219/** Reverse geocode 14220 * @param {ol.coordinate} coord 14221 * @api 14222 */ 14223ol.control.SearchNominatim.prototype.reverseGeocode = function (coord, cback) { 14224 var lonlat = ol.proj.transform (coord, this.getMap().getView().getProjection(), 'EPSG:4326'); 14225 this.ajax( 14226 this.get('url').replace('search', 'reverse'), 14227 { lon: lonlat[0], lat: lonlat[1], format: 'json' }, 14228 function(resp) { 14229 if (cback) { 14230 cback.call(this, [resp]); 14231 } else { 14232 if (resp && !resp.error) { 14233 this._handleSelect(resp, true); 14234 } 14235 //this.setInput('', true); 14236 } 14237 }.bind(this) 14238 ); 14239}; 14240 14241/* Copyright (c) 2019 Jean-Marc VIGLINO, 14242 released under the CeCILL-B license (French BSD license) 14243 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 14244*/ 14245/** 14246 * Search places using the MediaWiki API. 14247 * @see https://www.mediawiki.org/wiki/API:Main_page 14248 * 14249 * @constructor 14250 * @extends {ol.control.SearchJSON} 14251 * @fires select 14252 * @param {Object=} Control options. 14253 * @param {string} options.className control class name 14254 * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 14255 * @param {string | undefined} options.label Text label to use for the search button, default "search" 14256 * @param {string | undefined} options.placeholder placeholder, default "Search..." 14257 * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 1000. 14258 * @param {integer | undefined} options.minLength minimum length to start searching, default 3 14259 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 14260 * @param {function | undefined} options.handleResponse Handle server response to pass the features array to the list 14261 * 14262 * @param {string|undefined} options.lang API language, default none 14263 */ 14264ol.control.SearchWikipedia = function(options){ 14265 options = options || {}; 14266 options.lang = options.lang||'en'; 14267 options.className = options.className || 'ol-search-wikipedia'; 14268 options.url = 'https://'+options.lang+'.wikipedia.org/w/api.php'; 14269 options.placeholder = options.placeholder || 'search string, File:filename'; 14270 options.copy = '<a href="https://'+options.lang+'.wikipedia.org/" target="new">Wikipedia® - CC-By-SA</a>'; 14271 ol.control.SearchJSON.call(this, options); 14272 this.set('lang', options.lang); 14273}; 14274ol.ext.inherits(ol.control.SearchWikipedia, ol.control.SearchJSON); 14275/** Returns the text to be displayed in the menu 14276* @param {ol.Feature} f the feature 14277* @return {string} the text to be displayed in the index 14278* @api 14279*/ 14280ol.control.SearchWikipedia.prototype.getTitle = function (f){ 14281 return ol.ext.element.create('DIV', { 14282 html: f.title, 14283 title: f.desc 14284 }); 14285 //return f.desc; 14286}; 14287/** Set the current language 14288 * @param {string} lang the current language as ISO string (en, fr, de, es, it, ja, ...) 14289 */ 14290ol.control.SearchWikipedia.prototype.setLang = function (lang){ 14291 this.set('lang', lang) 14292 this.set('url', 'https://'+lang+'.wikipedia.org/w/api.php'); 14293}; 14294/** 14295 * @param {string} s the search string 14296 * @return {Object} request data (as key:value) 14297 * @api 14298 */ 14299ol.control.SearchWikipedia.prototype.requestData = function (s) { 14300 var data = { 14301 action: 'opensearch', 14302 search: s, 14303 lang: this.get('lang'), 14304 format: 'json', 14305 origin: '*', 14306 limit: this.get('maxItems') 14307 } 14308 return data; 14309}; 14310/** 14311 * Handle server response to pass the features array to the list 14312 * @param {any} response server response 14313 * @return {Array<any>} an array of feature 14314 */ 14315ol.control.SearchWikipedia.prototype.handleResponse = function (response) { 14316 var features = []; 14317 for (var i=0; i<response[1].length; i++) { 14318 features.push({ 14319 title: response[1][i], 14320 desc: response[2][i], 14321 uri: response[3][i] 14322 }) 14323 } 14324 return features; 14325}; 14326/** A ligne has been clicked in the menu query for more info and disatch event 14327* @param {any} f the feature, as passed in the autocomplete 14328* @api 14329*/ 14330ol.control.SearchWikipedia.prototype.select = function (f){ 14331 var title = decodeURIComponent(f.uri.split('/').pop()).replace(/'/,'%27'); 14332 // Search for coords 14333 ol.ext.Ajax.get({ 14334 url: f.uri.split('wiki/')[0]+'w/api.php', 14335 data: { 14336 action: 'query', 14337 prop: 'pageimages|coordinates|extracts', 14338 exintro: 1, 14339 explaintext: 1, 14340 piprop: 'original', 14341 origin: '*', 14342 format: 'json', 14343 redirects: 1, 14344 titles: title 14345 }, 14346 options: { 14347 encode: false 14348 }, 14349 success: function (e) { 14350 var page = e.query.pages[Object.keys(e.query.pages).pop()]; 14351 console.log(page); 14352 var feature = { 14353 title: f.title, 14354 desc: page.extract || f.desc, 14355 url: f.uri, 14356 img: page.original ? page.original.source : undefined, 14357 pageid: page.pageid 14358 } 14359 var c; 14360 if (page.coordinates) { 14361 feature.lon = page.coordinates[0].lon; 14362 feature.lat = page.coordinates[0].lat; 14363 c = [feature.lon, feature.lat]; 14364 c = ol.proj.transform (c, 'EPSG:4326', this.getMap().getView().getProjection()); 14365 } 14366 this.dispatchEvent({ type:"select", search:feature, coordinate: c }); 14367 }.bind(this) 14368 }) 14369}; 14370/** */ 14371 14372/* Copyright (c) 2017 Jean-Marc VIGLINO, 14373 released under the CeCILL-B license (French BSD license)
14374 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 14375*/ 14376/** 14377 * Select Control. 14378 * A control to select features by attributes 14379 * 14380 * @constructor 14381 * @extends {ol.control.SelectBase} 14382 * @fires select 14383 * @param {Object=} options 14384 * @param {string} options.className control class name 14385 * @param {Element | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 14386 * @param {ol.source.Vector | Array<ol.source.Vector>} options.source the source to search in 14387 * @param {string} [options.selectLabel=select] select button label 14388 * @param {string} [options.addLabel=add] add button label 14389 * @param {string} [options.caseLabel=case sensitive] case checkbox label 14390 * @param {string} [options.allLabel=match all] match all checkbox label 14391 * @param {string} [options.attrPlaceHolder=attribute] 14392 * @param {string} [options.valuePlaceHolder=value] 14393 */ 14394ol.control.Select = function(options) { 14395 var self = this; 14396 if (!options) options = {}; 14397 // Container 14398 var div = options.content = document.createElement("div"); 14399 // Autocompletion list 14400 this._ul = ol.ext.element.create('UL', { 14401 parent: div 14402 }); 14403 // All conditions 14404 this._all = ol.ext.element.create('INPUT', { 14405 type: 'checkbox', 14406 checked: true 14407 }); 14408 var label_match_all = ol.ext.element.create('LABEL',{ 14409 html: this._all, 14410 parent: div 14411 }); 14412 ol.ext.element.appendText(label_match_all, options.allLabel || 'match all'); 14413 // Use case 14414 this._useCase = ol.ext.element.create('INPUT', { 14415 type: 'checkbox' 14416 }); 14417 var label_case_sensitive = ol.ext.element.create('LABEL',{ 14418 html: this._useCase, 14419 parent: div 14420 }); 14421 ol.ext.element.appendText(label_case_sensitive, options.caseLabel || 'case sensitive'); 14422 ol.control.SelectBase.call(this, options); 14423 // Add button 14424 ol.ext.element.create('BUTTON', { 14425 className: 'ol-append', 14426 html: options.addLabel || 'add rule', 14427 click: function(){ 14428 self.addCondition(); 14429 }, 14430 parent: div 14431 }); 14432 this._conditions = []; 14433 this.set('attrPlaceHolder', options.attrPlaceHolder || 'attribute'); 14434 this.set('valuePlaceHolder', options.valuePlaceHolder || 'value'); 14435 this.addCondition(); 14436}; 14437ol.ext.inherits(ol.control.Select, ol.control.SelectBase); 14438/** Add a new condition 14439 * @param {*} options 14440 * @param {string} options.attr attribute name 14441 * @param {string} options.op operator
14442 * @param {string} options.val attribute value 14443 */ 14444ol.control.Select.prototype.addCondition = function (options) { 14445 options = options || {}; 14446 this._conditions.push({ 14447 attr: options.attr || '', 14448 op: options.op || '=', 14449 val: options.val || '' 14450 }); 14451 this._drawlist(); 14452}; 14453/** Get the condition list 14454 */ 14455ol.control.Select.prototype.getConditions = function () { 14456 return { 14457 usecase: this._useCase.checked, 14458 all: this._all.checked, 14459 conditions: this._conditions 14460 } 14461}; 14462/** Set the condition list 14463 */ 14464ol.control.Select.prototype.setConditions = function (cond) { 14465 this._useCase.checked = cond.usecase; 14466 this._all.checked = cond.all; 14467 this._conditions = cond.conditions; 14468 this._drawlist(); 14469}; 14470/** Get the conditions as string 14471 */ 14472ol.control.Select.prototype.getConditionsString = function (cond) { 14473 var st = ''; 14474 for (var i=0,c; c=cond.conditions[i]; i++) { 14475 if (c.attr) { 14476 st += (st ? (cond.all ? ' AND ' : ' OR ') : '') 14477 + c.attr 14478 + this.operationsList[c.op] 14479 + c.val; 14480 } 14481 } 14482 return st 14483}; 14484/** Draw the liste 14485 * @private 14486 */ 14487ol.control.Select.prototype._drawlist = function () { 14488 this._ul.innerHTML = ''; 14489 for (var i=0; i < this._conditions.length; i++) { 14490 this._ul.appendChild(this._getLiCondition(i)); 14491 } 14492}; 14493/** Get a line 14494 * @return {*} 14495 * @private 14496 */ 14497ol.control.Select.prototype._autocomplete = function (val, ul) { 14498 ul.classList.remove('ol-hidden'); 14499 ul.innerHTML = ''; 14500 var attributes = {}; 14501 var sources = this.get('source'); 14502 for (var i=0, s; s=sources[i]; i++) { 14503 var features = s.getFeatures(); 14504 for (var j=0, f; f=features[j]; j++) { 14505 Object.assign(attributes, f.getProperties()); 14506 if (j>100) break; 14507 } 14508 } 14509 var rex = new RegExp(val, 'i'); 14510 for (var a in attributes) { 14511 if (a==='geometry') continue; 14512 if (rex.test(a)) { 14513 var li = document.createElement('li'); 14514 li.textContent = a; 14515 li.addEventListener("click", function() { 14516 ul.previousElementSibling.value = this.textContent; 14517 var event = document.createEvent('HTMLEvents'); 14518 event.initEvent('change', true, false); 14519 ul.previousElementSibling.dispatchEvent(event); 14520 ul.classList.add('ol-hidden'); 14521 }); 14522 ul.appendChild(li); 14523 } 14524 } 14525}; 14526/** Get a line 14527 * @return {*} 14528 * @private 14529 */ 14530ol.control.Select.prototype._getLiCondition = function (i) { 14531 var self = this; 14532 var li = document.createElement('li'); 14533 // Attribut 14534 var autocomplete = document.createElement('div'); 14535 autocomplete.classList.add('ol-autocomplete'); 14536 autocomplete.addEventListener("mouseleave", function() { 14537 this.querySelector('ul'). classList.add('ol-hidden'); 14538 }); 14539 li.appendChild(autocomplete); 14540 var input_attr = document.createElement('input'); 14541 input_attr.classList.add('ol-attr'); 14542 input_attr.setAttribute('type', 'search'); 14543 input_attr.setAttribute('placeholder', this.get('attrPlaceHolder')); 14544 input_attr.addEventListener('keyup', function () { 14545 self._autocomplete( this.value, this.nextElementSibling ); 14546 }) 14547 input_attr.addEventListener('focusout', function() { 14548 setTimeout(function() { 14549 autocomplete.querySelector('ul'). classList.add('ol-hidden'); 14550 }, 300); 14551 }); 14552 input_attr.addEventListener('click', function(){ 14553 setTimeout(function() { 14554 self._autocomplete( this.value, this.nextElementSibling ); 14555 this.nextElementSibling.classList.remove('ol-hidden'); 14556 }.bind(this)); 14557 }) 14558 input_attr.addEventListener('change', function() { 14559 self._conditions[i].attr = this.value; 14560 }) 14561 input_attr.value = self._conditions[i].attr; 14562 autocomplete.appendChild(input_attr); 14563 // Autocomplete list 14564 var ul_autocomplete = document.createElement('ul'); 14565 ul_autocomplete.classList.add('ol-hidden') 14566 autocomplete.appendChild(ul_autocomplete); 14567 // Operation 14568 var select = document.createElement('select'); 14569 li.appendChild(select); 14570 for (var k in this.operationsList) { 14571 var option = document.createElement('option'); 14572 option.value = k; 14573 option.textContent = this.operationsList[k]; 14574 select.appendChild(option); 14575 } 14576 select.value = self._conditions[i].op; 14577 select.addEventListener('change', function() { 14578 self._conditions[i].op = this.value; 14579 }); 14580 // Value 14581 var input_value = document.createElement('input'); 14582 input_value.setAttribute('type', 'text'); 14583 input_value.setAttribute('placeholder', this.get('valuePlaceHolder')); 14584 input_value.addEventListener('change', function() { 14585 self._conditions[i].val = this.value; 14586 }) 14587 input_value.value = self._conditions[i].val; 14588 li.appendChild(input_value); 14589 if (this._conditions.length > 1) { 14590 var div_delete = document.createElement('div'); 14591 div_delete.classList.add('ol-delete'); 14592 div_delete.addEventListener("click", function(){ self.removeCondition(i); }) 14593 li.appendChild(div_delete); 14594 } 14595 // 14596 return li; 14597}; 14598/** Remove the ith condition 14599 * @param {int} i condition index 14600 */ 14601ol.control.Select.prototype.removeCondition = function (i) { 14602 this._conditions.splice(i,1); 14603 this._drawlist(); 14604}; 14605/** Select features by attributes 14606 * @param {*} options 14607 * @param {Array<ol.source.Vector>|undefined} options.sources source to apply rules, default the select sources 14608 * @param {bool} options.useCase case sensitive, default checkbox state 14609 * @param {bool} options.matchAll match all conditions, , default checkbox state 14610 * @param {Array<conditions>} options.conditions array of conditions 14611 * @fires select 14612 */ 14613ol.control.Select.prototype.doSelect = function (options) { 14614 options = options || {}; 14615 options.useCase = options.useCase || this._useCase.checked; 14616 options.matchAll = options.matchAll || this._all.checked; 14617 options.conditions = options.conditions || this._conditions 14618 return ol.control.SelectBase.prototype.doSelect.call(this, options); 14619}; 14620 14621/* Copyright (c) 2019 Jean-Marc VIGLINO, 14622 released under the CeCILL-B license (French BSD license)
14623 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 14624*/ 14625/** 14626 * Select features by property using a popup 14627 * 14628 * @constructor 14629 * @extends {ol.control.SelectBase} 14630 * @fires select 14631 * @param {Object=} options 14632 * @param {string} options.className control class name 14633 * @param {Element | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 14634 * @param {ol/source/Vector | Array<ol/source/Vector>} options.source the source to search in 14635 * @param {string} options.property property to select on 14636 * @param {string} options.label control label 14637 * @param {number} options.max max feature to test to get the values, default 10000 14638 * @param {number} options.selectAll select all features if no option selected 14639 * @param {string} options.type check type: checkbox or radio, default checkbox 14640 * @param {number} options.defaultLabel label for the default radio button 14641 * @param {function|undefined} options.onchoice function triggered when an option is clicked, default doSelect 14642 */ 14643ol.control.SelectCheck = function(options) { 14644 if (!options) options = {}; 14645 // Container 14646 var div = options.content = ol.ext.element.create('DIV'); 14647 if (options.label) { 14648 ol.ext.element.create('LABEL', { 14649 html: options.label, 14650 parent: div 14651 }); 14652 } 14653 // Input div 14654 this._input = ol.ext.element.create('DIV', { 14655 parent: div 14656 }); 14657 options.className = options.className || 'ol-select-check'; 14658 ol.control.SelectBase.call(this, options); 14659 this.set('property', options.property || 'name'); 14660 this.set('max', options.max || 10000); 14661 this.set('defaultLabel', options.defaultLabel); 14662 this.set('type', options.type); 14663 this._selectAll = options.selectAll; 14664 this._onchoice = options.onchoice; 14665 // Set select options 14666 if (options.values) { 14667 this.setValues({ values: options.values, sort: true }); 14668 } else { 14669 this.setValues(); 14670 } 14671}; 14672ol.ext.inherits(ol.control.SelectCheck, ol.control.SelectBase); 14673/** 14674* Set the map instance the control associated with. 14675* @param {o.Map} map The map instance. 14676*/ 14677ol.control.SelectCheck.prototype.setMap = function(map) { 14678 ol.control.SelectBase.prototype.setMap.call(this, map); 14679 this.setValues(); 14680}; 14681/** Select features by attributes 14682 */ 14683ol.control.SelectCheck.prototype.doSelect = function(options) { 14684 console.log('select') 14685 options = options || {}; 14686 var conditions = []; 14687 this._checks.forEach(function(c) { 14688 if (c.checked) { 14689 if (c.value) { 14690 conditions.push({ 14691 attr: this.get('property'), 14692 op: '=', 14693 val: c.value 14694 }); 14695 } 14696 } 14697 }.bind(this)); 14698 if (!conditions.length) { 14699 return ol.control.SelectBase.prototype.doSelect.call(this, { 14700 features: options.features, 14701 matchAll: this._selectAll 14702 }); 14703 } else { 14704 return ol.control.SelectBase.prototype.doSelect.call(this, { 14705 features: options.features, 14706 conditions: conditions 14707 }) 14708 } 14709}; 14710/** Set the popup values 14711 * @param {Object} options 14712 * @param {Object} options.values a key/value list with key = property value, value = title shown in the popup, default search values in the sources 14713 * @param {boolean} options.sort sort values 14714 */ 14715ol.control.SelectCheck.prototype.setValues = function(options) { 14716 options = options || {}; 14717 var values, vals; 14718 if (options.values) { 14719 if (options.values instanceof Array) { 14720 vals = {}; 14721 options.values.forEach(function(v) { vals[v] = v; }); 14722 } else { 14723 vals = options.values; 14724 } 14725 } else { 14726 vals = {}; 14727 var prop = this.get('property'); 14728 this.getSources().forEach(function(s){ 14729 var features = s.getFeatures(); 14730 var max = Math.min(features.length, this.get('max')) 14731 for (var i=0; i<max; i++) { 14732 var p = features[i].get(prop); 14733 if (p) vals[p] = p; 14734 } 14735 }.bind(this)); 14736 } 14737 if (!Object.keys(vals).length) return; 14738 if (options.sort) { 14739 values = {}; 14740 Object.keys(vals).sort().forEach(function(key) { 14741 values[key] = vals[key]; 14742 }); 14743 } else { 14744 values = vals; 14745 } 14746 ol.ext.element.setHTML(this._input, ''); 14747 this._checks = []; 14748 var id = 'radio_'+(new Date().getTime()); 14749 var addCheck = function(val, info) { 14750 this._checks.push( ol.ext.element.createCheck({ 14751 after: info, 14752 name: id, 14753 val: val, 14754 type: this.get('type'), 14755 change: function () { 14756 if (this._onchoice) this._onchoice() 14757 else this.doSelect(); 14758 }.bind(this), 14759 parent: this._input 14760 })); 14761 }.bind(this); 14762 if (this.get('defaultLabel') && this.get('type')==='radio') { 14763 addCheck('', this.get('defaultLabel')); 14764 } 14765 for (var k in values) addCheck(k, values[k]); 14766}; 14767 14768/* Copyright (c) 2019 Jean-Marc VIGLINO, 14769 released under the CeCILL-B license (French BSD license)
14770 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 14771*/ 14772/** 14773 * Select features by property using a condition 14774 * 14775 * @constructor 14776 * @extends {ol.control.SelectBase} 14777 * @fires select 14778 * @param {Object=} options 14779 * @param {string} options.className control class name 14780 * @param {Element | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 14781 * @param {ol/source/Vector | Array<ol/source/Vector>} options.source the source to search in 14782 * @param {string} options.label control label, default 'condition' 14783 * @param {number} options.selectAll select all features if no option selected 14784 * @param {condition|Array<condition>} options.condition conditions 14785 * @param {function|undefined} options.onchoice function triggered when an option is clicked, default doSelect 14786 */ 14787ol.control.SelectCondition = function(options) { 14788 if (!options) options = {}; 14789 // Container 14790 var div = options.content = ol.ext.element.create('DIV'); 14791 this._check = ol.ext.element.createSwitch({ 14792 after: options.label || 'condition', 14793 change: function () { 14794 if (this._onchoice) this._onchoice() 14795 else this.doSelect(); 14796 }.bind(this), 14797 parent: div 14798 }) 14799 // Input div 14800 this._input = ol.ext.element.create('DIV', { 14801 parent: div 14802 }); 14803 options.className = options.className || 'ol-select-condition'; 14804 ol.control.SelectBase.call(this, options); 14805 this.setCondition(options.condition); 14806 this._selectAll = options.selectAll; 14807 this._onchoice = options.onchoice; 14808}; 14809ol.ext.inherits(ol.control.SelectCondition, ol.control.SelectBase); 14810/** Set condition to select on 14811 * @param {condition | Array<condition>} condition 14812 * @param {string} attr property to select on 14813 * @param {string} op operator (=, !=, <; <=, >, >=, contain, !contain, regecp) 14814 * @param {*} val value to select on 14815 */ 14816ol.control.SelectCondition.prototype.setCondition = function(condition) { 14817 if (!condition) this._conditions = []; 14818 else this._conditions = (condition instanceof Array ? condition : [condition]); 14819}; 14820/** Add a condition to select on 14821 * @param {condition} condition 14822 * @param {string} attr property to select on 14823 * @param {string} op operator (=, !=, <; <=, >, >=, contain, !contain, regecp) 14824 * @param {*} val value to select on 14825 */ 14826ol.control.SelectCondition.prototype.addCondition = function(condition) { 14827 this._conditions.push(condition); 14828}; 14829/** Select features by condition 14830 */ 14831ol.control.SelectCondition.prototype.doSelect = function(options) { 14832 options = options || {}; 14833 var conditions = this._conditions; 14834 if (!this._check.checked) { 14835 return ol.control.SelectBase.prototype.doSelect.call(this, { features: options.features, matchAll: this._selectAll }); 14836 } else { 14837 return ol.control.SelectBase.prototype.doSelect.call(this, { 14838 features: options.features, 14839 conditions: conditions 14840 }) 14841 } 14842}; 14843 14844/* Copyright (c) 2019 Jean-Marc VIGLINO, 14845 released under the CeCILL-B license (French BSD license) 14846 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 14847*/ 14848/** 14849 * Select features by property using a simple text input 14850 * 14851 * @constructor 14852 * @extends {ol.control.SelectBase} 14853 * @fires select 14854 * @param {Object=} options 14855 * @param {string} options.className control class name 14856 * @param {Element | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 14857 * @param {ol/source/Vector | Array<ol/source/Vector>} options.source the source to search in 14858 * @param {string} options.property property to select on 14859 * @param {function|undefined} options.onchoice function triggered the text change, default nothing 14860 */ 14861ol.control.SelectFulltext = function(options) { 14862 if (!options) options = {}; 14863 // Container 14864 var div = options.content =ol.ext.element.create('DIV'); 14865 if (options.label) { 14866 ol.ext.element.create('LABEL', { 14867 html: options.label, 14868 parent: div 14869 }); 14870 } 14871 this._input = ol.ext.element.create('INPUT', { 14872 placeHolder: options.placeHolder || 'search...', 14873 change: function() { 14874 if (this._onchoice) this._onchoice(); 14875 }.bind(this), 14876 parent: div 14877 }); 14878 ol.control.SelectBase.call(this, options); 14879 this._onchoice = options.onchoice; 14880 this.set('property', options.property || 'name'); 14881}; 14882ol.ext.inherits(ol.control.SelectFulltext, ol.control.SelectBase); 14883/** Select features by condition 14884 */ 14885ol.control.SelectFulltext.prototype.doSelect= function(options) { 14886 options = options || {}; 14887 return ol.control.SelectBase.prototype.doSelect.call(this, { 14888 features: options.features, 14889 useCase: false, 14890 conditions: [{ 14891 attr: this.get('property'), 14892 op: 'contain', 14893 val: this._input.value 14894 }] 14895 }); 14896} 14897 14898/* Copyright (c) 2019 Jean-Marc VIGLINO, 14899 released under the CeCILL-B license (French BSD license)
14900 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 14901*/ 14902/** 14903 * A multiselect control. 14904 * A container that manage other control Select 14905 * 14906 * @constructor 14907 * @extends {ol.control.SelectBase} 14908 * @fires select 14909 * @param {Object=} options 14910 * @param {string} options.className control class name 14911 * @param {Element | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 14912 * @param {ol/source/Vector | Array<ol/source/Vector>} options.source the source to search in 14913 * @param {Array<ol.control.SelectBase>} options.controls an array of controls 14914 */ 14915ol.control.SelectMulti = function(options) { 14916 if (!options) options = {}; 14917 // Container 14918 options.content = ol.ext.element.create('DIV'); 14919 this._container = ol.ext.element.create('UL', { 14920 parent: options.content 14921 }); 14922 options.className = options.className || 'ol-select-multi'; 14923 ol.control.SelectBase.call(this, options); 14924 this._controls = []; 14925 options.controls.forEach(this.addControl.bind(this)); 14926}; 14927ol.ext.inherits(ol.control.SelectMulti, ol.control.SelectBase); 14928/** 14929* Set the map instance the control associated with. 14930* @param {o.Map} map The map instance. 14931*/ 14932ol.control.SelectMulti.prototype.setMap = function(map) { 14933 if (this.getMap()) { 14934 this._controls.forEach(function(c) { 14935 this.getMap().remveControl(c); 14936 }.bind(this)); 14937 } 14938 ol.control.SelectBase.prototype.setMap.call(this, map); 14939 if (this.getMap()) { 14940 this._controls.forEach(function(c) { 14941 this.getMap().addControl(c); 14942 }.bind(this)); 14943 } 14944}; 14945/** Add a new control 14946 * @param {ol.control.SelectBase} c 14947 */ 14948ol.control.SelectMulti.prototype.addControl = function(c) { 14949 if (c instanceof ol.control.SelectBase) { 14950 this._controls.push(c); 14951 c.setTarget(ol.ext.element.create('LI', { 14952 parent: this._container 14953 })); 14954 c._selectAll = true; 14955 c._onchoice = this.doSelect.bind(this); 14956 if (this.getMap()) { 14957 this.getMap().addControl(c); 14958 } 14959 } 14960}; 14961/** Get select controls 14962 * @return {Aray<ol.control.SelectBase>} 14963 */ 14964ol.control.SelectMulti.prototype.getControls = function() { 14965 return this._controls; 14966}; 14967/** Select features by condition 14968 */ 14969ol.control.SelectMulti.prototype.doSelect = function() { 14970 var features = []; 14971 this.getSources().forEach(function(s) { 14972 features = features.concat(s.getFeatures()); 14973 }); 14974 this._controls.forEach(function(c) { 14975 features = c.doSelect({ features: features }); 14976 }); 14977 this.dispatchEvent({ type:"select", features: features }); 14978 return features; 14979}; 14980 14981/* Copyright (c) 2019 Jean-Marc VIGLINO, 14982 released under the CeCILL-B license (French BSD license) 14983 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 14984*/ 14985/** 14986 * Select features by property using a popup 14987 * 14988 * @constructor 14989 * @extends {ol.control.SelectBase} 14990 * @fires select 14991 * @param {Object=} options 14992 * @param {string} options.className control class name 14993 * @param {Element | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. 14994 * @param {ol/source/Vector | Array<ol/source/Vector>} options.source the source to search in 14995 * @param {string} options.property property to select on 14996 * @param {number} options.max max feature to test to get the values, default 10000 14997 * @param {number} options.selectAll select all features if no option selected 14998 * @param {string} options.defaultLabel label for the default selection 14999 * @param {function|undefined} options.onchoice function triggered when an option is clicked, default doSelect 15000 */ 15001ol.control.SelectPopup = function(options) { 15002 if (!options) options = {}; 15003 // Container 15004 var div = options.content = ol.ext.element.create('DIV'); 15005 if (options.label) { 15006 ol.ext.element.create('LABEL', { 15007 html: options.label, 15008 parent: div 15009 }); 15010 } 15011 this._input = ol.ext.element.create('SELECT', { 15012 on: { change: function () { 15013 if (this._onchoice) this._onchoice(); 15014 else this.doSelect(); 15015 }.bind(this) }, 15016 parent: div 15017 }); 15018 options.className = options.className || 'ol-select-popup'; 15019 ol.control.SelectBase.call(this, options); 15020 this.set('property', options.property || 'name'); 15021 this.set('max', options.max || 10000); 15022 this.set('defaultLabel', options.defaultLabel); 15023 this._selectAll = options.selectAll; 15024 this._onchoice = options.onchoice; 15025 // Set select options 15026 this.setValues(); 15027}; 15028ol.ext.inherits(ol.control.SelectPopup, ol.control.SelectBase); 15029/** 15030* Set the map instance the control associated with. 15031* @param {o.Map} map The map instance. 15032*/ 15033ol.control.SelectPopup.prototype.setMap = function(map) { 15034 ol.control.SelectBase.prototype.setMap.call(this, map); 15035 this.setValues(); 15036}; 15037/** Select features by attributes 15038 */ 15039ol.control.SelectPopup.prototype.doSelect = function(options) { 15040 options = options || {}; 15041 if (!this._input.value) { 15042 return ol.control.SelectBase.prototype.doSelect.call(this, { features: options.features, matchAll: this._selectAll }); 15043 } else { 15044 return ol.control.SelectBase.prototype.doSelect.call(this, { 15045 features: options.features, 15046 conditions: [{ 15047 attr: this.get('property'), 15048 op: '=', 15049 val: this._input.value 15050 }] 15051 }) 15052 } 15053}; 15054/** Set the popup values 15055 * @param {Object} values a key/value list with key = property value, value = title shown in the popup, default search values in the sources 15056 */ 15057ol.control.SelectPopup.prototype.setValues = function(options) { 15058 options = options || {}; 15059 var values, vals; 15060 if (options.values) { 15061 if (options.values instanceof Array) { 15062 vals = {};
15063 options.values.forEach(function(v) { vals[v] = v; }); 15064 } else { 15065 vals = options.values; 15066 } 15067 } else { 15068 vals = {}; 15069 var prop = this.get('property'); 15070 this.getSources().forEach(function(s){ 15071 var features = s.getFeatures(); 15072 var max = Math.min(features.length, this.get('max')) 15073 for (var i=0; i<max; i++) { 15074 var p = features[i].get(prop); 15075 if (p) vals[p] = p; 15076 } 15077 }.bind(this)); 15078 } 15079 if (options.sort) { 15080 values = {}; 15081 Object.keys(vals).sort().forEach(function(key) { 15082 values[key] = vals[key]; 15083 }); 15084 } else { 15085 values = vals; 15086 } 15087 ol.ext.element.setHTML(this._input, ''); 15088 ol.ext.element.create('OPTION', { 15089 className: 'ol-default', 15090 html: this.get('defaultLabel') || '', 15091 value: '', 15092 parent: this._input 15093 }); 15094 for (var k in values) { 15095 ol.ext.element.create('OPTION', { 15096 html: values[k], 15097 value: k, 15098 parent: this._input 15099 }); 15100 } 15101}; 15102 15103/** A control to display status information on top of the map 15104 * @constructor 15105 * @extends {ol.control.Control} 15106 * @param {Object=} options Control options. 15107 * @param {String} options.className class of the control 15108 * @param {string} options.status status, default none 15109 * @param {string} options.position position of the status 'top', 'left', 'bottom' or 'right', default top 15110 * @param {boolean} options.visible default true 15111 */ 15112ol.control.Status = function(options) { 15113 options = options || {}; 15114 // New element 15115 var element = ol.ext.element.create('DIV', { 15116 className: (options.className || '') + ' ol-status' 15117 + (options.target ? '': ' ol-unselectable ol-control') 15118 }); 15119 // Initialize 15120 ol.control.Control.call(this, { 15121 element: element, 15122 target: options.target 15123 }); 15124 this.setVisible(options.visible!==false); 15125 if (options.position) this.setPosition(options.position); 15126 this.status(options.status || ''); 15127}; 15128ol.ext.inherits(ol.control.Status, ol.control.Control); 15129/** Set visiblitity 15130 * @param {boolean} visible 15131 */ 15132ol.control.Status.prototype.setVisible = function(visible) { 15133 if (visible) this.element.classList.add ('ol-visible'); 15134 else this.element.classList.remove('ol-visible'); 15135}; 15136/** Show status on the map 15137 * @param {string|Element} html status text or DOM element 15138 */ 15139ol.control.Status.prototype.status = function(html) { 15140 var s = html || ''; 15141 if (s) { 15142 ol.ext.element.show(this.element); 15143 if (s instanceof Element || typeof(s) === 'string') { 15144 ol.ext.element.setHTML(this.element, s) 15145 } else { 15146 s = ''; 15147 for (var i in html) { 15148 s += '<label>'+i+':</label> '+html[i]+'<br/>'; 15149 } 15150 } 15151 ol.ext.element.setHTML(this.element, s); 15152 } else { 15153 ol.ext.element.hide(this.element); 15154 } 15155}; 15156/** Set status position 15157 * @param {string} position position of the status 'top', 'left', 'bottom' or 'right', default top 15158 */ 15159ol.control.Status.prototype.setPosition = function(position) { 15160 this.element.classList.remove('ol-left'); 15161 this.element.classList.remove('ol-right'); 15162 this.element.classList.remove('ol-bottom'); 15163 this.element.classList.remove('ol-center'); 15164 if (/^left$|^right$|^bottom$|^center$/.test(position)) { 15165 this.element.classList.add('ol-'+position); 15166 } 15167}; 15168/** Show the status 15169 * @param {boolean} show show or hide the control, default true 15170 */ 15171ol.control.Status.prototype.show = function(show) { 15172 if (show===false) ol.ext.element.hide(this.element); 15173 else ol.ext.element.show(this.element); 15174}; 15175/** Hide the status 15176 */ 15177ol.control.Status.prototype.hide = function() { 15178 ol.ext.element.hide(this.element); 15179}; 15180/** Toggle the status 15181 */ 15182ol.control.Status.prototype.toggle = function() { 15183 ol.ext.element.toggle(this.element); 15184}; 15185/** Is status visible 15186 */ 15187ol.control.Status.prototype.isShown = function() { 15188 return this.element.style.display==='none'; 15189}; 15190 15191// Add flyTo 15192/** A control with scroll-driven navigation to create narrative maps 15193 * 15194 * @constructor 15195 * @extends {ol.control.Control} 15196 * @fires scrollto 15197 * @fires clickimage 15198 * @param {Object=} options Control options. 15199 * @param {String} options.className class of the control 15200 * @param {Element | string | undefined} [options.html] The storymap content 15201 * @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. 15202 * @param {boolean} [options.minibar=false] add a mini scroll bar 15203 */ 15204ol.control.Storymap = function(options) { 15205 // Remove or get target content 15206 if (options.target) { 15207 if (!options.html) { 15208 options.html = options.target.innerHTML; 15209 } else if (options.html instanceof Element) { 15210 options.html = options.html.innerHTML; 15211 } 15212 options.target.innerHTML = ''; 15213 } 15214 // New element 15215 var element = ol.ext.element.create('DIV', { 15216 className: (options.className || '') + ' ol-storymap'
15217 + (options.target ? '': ' ol-unselectable ol-control'), 15218 }); 15219 this.content = ol.ext.element.create('DIV', { 15220 parent: element 15221 }); 15222 // Initialize 15223 ol.control.Control.call(this, { 15224 element: element, 15225 target: options.target 15226 }); 15227 // Make a scroll div 15228 ol.ext.element.scrollDiv (this.content, { 15229 vertical: true, 15230 mousewheel: true, 15231 minibar: options.minibar 15232 }); 15233 this.setStory(options.html); 15234}; 15235ol.ext.inherits(ol.control.Storymap, ol.control.Control); 15236/** Scroll to a chapter 15237 * @param {string} name Name of the chapter to scroll to 15238 */ 15239ol.control.Storymap.prototype.setChapter = function (name) { 15240 var chapter = this.content.querySelectorAll('.chapter'); 15241 for (var i=0, s; s=chapter[i]; i++) { 15242 if (s.getAttribute('name')===name) { 15243 this.content.scrollTop = s.offsetTop - 30; 15244 } 15245 } 15246}; 15247/** Scroll to a chapter 15248 * @param {string} name Name of the chapter to scroll to 15249 */ 15250ol.control.Storymap.prototype.setStory = function (html) { 15251 if (html instanceof Element) { 15252 this.content.innerHTML = ''; 15253 this.content.appendChild(html); 15254 } else { 15255 this.content.innerHTML = html; 15256 } 15257 this.content.querySelectorAll('.chapter').forEach(function(c) { 15258 c.addEventListener('click', function(e) { 15259 if (!c.classList.contains('ol-select')) { 15260 this.content.scrollTop = c.offsetTop - 30; 15261 e.preventDefault(); 15262 } else { 15263 if (e.target.tagName==='IMG' && e.target.dataset.title) { 15264 this.dispatchEvent({ 15265 coordinate: this.getMap() ? this.getMap().getCoordinateFromPixel([e.layerX,e.layerY]) : null, 15266 type: 'clickimage', 15267 img: e.target, 15268 title: e.target.dataset.title, 15269 element: c, 15270 name: c.getAttribute('name'), 15271 originalEvent: e 15272 }); 15273 } 15274 } 15275 }.bind(this)); 15276 }.bind(this)); 15277 // Scroll to the next chapter 15278 var sc = this.content.querySelectorAll('.ol-scroll-next'); 15279 sc.forEach(function(s) { 15280 s.addEventListener('click', function(e) { 15281 if (s.parentElement.classList.contains('ol-select')) { 15282 var chapter = this.content.querySelectorAll('.chapter'); 15283 var scrollto = s.offsetTop; 15284 for (var i=0, c; c=chapter[i]; i++) { 15285 if (c.offsetTop > scrollto) { 15286 scrollto = c.offsetTop; 15287 break; 15288 } 15289 } 15290 this.content.scrollTop = scrollto - 30; 15291 e.stopPropagation(); 15292 e.preventDefault(); 15293 } 15294 }.bind(this)); 15295 }.bind(this)); 15296 // Scroll top 15297 sc = this.content.querySelectorAll('.ol-scroll-top'); 15298 sc.forEach(function(i) { 15299 i.addEventListener('click', function(e){ 15300 this.content.scrollTop = 0; 15301 e.stopPropagation(); 15302 e.preventDefault(); 15303 }.bind(this)); 15304 }.bind(this)); 15305 var getEvent = function(currentDiv) { 15306 var lonlat = [ parseFloat(currentDiv.getAttribute('data-lon')), 15307 parseFloat(currentDiv.getAttribute('data-lat'))]; 15308 var coord = ol.proj.fromLonLat(lonlat, this.getMap().getView().getProjection()); 15309 var zoom = parseFloat(currentDiv.getAttribute('data-zoom')); 15310 return { 15311 type: 'scrollto', 15312 element: currentDiv, 15313 name: currentDiv.getAttribute('name'), 15314 coordinate: coord, 15315 lon: lonlat, 15316 zoom: zoom 15317 }; 15318 }.bind(this); 15319 // Handle scrolling 15320 var currentDiv = this.content.querySelectorAll('.chapter')[0]; 15321 setTimeout (function (){ 15322 currentDiv.classList.add('ol-select'); 15323 this.dispatchEvent(getEvent(currentDiv)); 15324 }.bind(this)); 15325 // Trigger change event on scroll 15326 this.content.addEventListener('scroll', function() { 15327 var current, chapter = this.content.querySelectorAll('.chapter'); 15328 var height = ol.ext.element.getStyle(this.content, 'height'); 15329 if (!this.content.scrollTop) { 15330 current = chapter[0]; 15331 } else { 15332 for (var i=0, s; s=chapter[i]; i++) { 15333 var p = s.offsetTop - this.content.scrollTop; 15334 if (p > height/3) break; 15335 current = s; 15336 } 15337 } 15338 if (current && current!==currentDiv) { 15339 if (currentDiv) currentDiv.classList.remove('ol-select'); 15340 currentDiv = current; 15341 currentDiv.classList.add('ol-select'); 15342 var e = getEvent(currentDiv); 15343 var view = this.getMap().getView(); 15344 view.cancelAnimations(); 15345 switch (currentDiv.getAttribute('data-animation')) { 15346 case 'flyto': { 15347 view.flyTo({ 15348 center: e.coordinate, 15349 zoomAt: Math.min(view.getZoom(), e.zoom)-1, 15350 zoom: e.zoom 15351 }); 15352 break; 15353 } 15354 default: break; 15355 } 15356 this.dispatchEvent(e); 15357 } 15358 }.bind(this)); 15359}; 15360 15361/* 15362 Copyright (c) 2015 Jean-Marc VIGLINO, 15363 released under the CeCILL-B license (French BSD license)
15364 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 15365*/ 15366/** 15367 * @classdesc Swipe Control. 15368 * @fires moving 15369 * @constructor 15370 * @extends {ol.control.Control} 15371 * @param {Object=} Control options. 15372 * @param {ol.layer} options.layers layer to swipe 15373 * @param {ol.layer} options.rightLayer layer to swipe on right side 15374 * @param {string} options.className control class name 15375 * @param {number} options.position position propertie of the swipe [0,1], default 0.5 15376 * @param {string} options.orientation orientation propertie (vertical|horizontal), default vertical 15377 */ 15378ol.control.Swipe = function(options) { 15379 options = options || {}; 15380 var button = document.createElement('button'); 15381 var element = document.createElement('div'); 15382 element.className = (options.className || "ol-swipe") + " ol-unselectable ol-control"; 15383 element.appendChild(button); 15384 element.addEventListener("mousedown", this.move.bind(this)); 15385 element.addEventListener("touchstart", this.move.bind(this)); 15386 ol.control.Control.call(this, { 15387 element: element 15388 }); 15389 // An array of listener on layer postcompose 15390 this.precomposeRight_ = this.precomposeRight.bind(this); 15391 this.precomposeLeft_ = this.precomposeLeft.bind(this); 15392 this.postcompose_ = this.postcompose.bind(this); 15393 this.layers = []; 15394 if (options.layers) this.addLayer(options.layers, false); 15395 if (options.rightLayers) this.addLayer(options.rightLayers, true); 15396 this.on('propertychange', function(e) { 15397 if (this.getMap()) { 15398 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 15399 } 15400 if (this.get('orientation') === "horizontal") { 15401 this.element.style.top = this.get('position')*100+"%"; 15402 this.element.style.left = ""; 15403 } else { 15404 if (this.get('orientation') !== "vertical") this.set('orientation', "vertical"); 15405 this.element.style.left = this.get('position')*100+"%"; 15406 this.element.style.top = ""; 15407 } 15408 if (e.key==='orientation') { 15409 this.element.classList.remove("horizontal", "vertical"); 15410 this.element.classList.add(this.get('orientation')); 15411 } 15412 // Force VectorImage to refresh 15413 if (!this.isMoving) { 15414 this.layers.forEach(function(l) { 15415 if (l.layer.getImageRatio) l.layer.changed(); 15416 }) 15417 } 15418 }.bind(this)); 15419 this.set('position', options.position || 0.5); 15420 this.set('orientation', options.orientation || 'vertical'); 15421}; 15422ol.ext.inherits(ol.control.Swipe, ol.control.Control); 15423/** 15424 * Set the map instance the control associated with. 15425 * @param {_ol_Map_} map The map instance. 15426 */ 15427ol.control.Swipe.prototype.setMap = function(map) { 15428 var i; 15429 var l; 15430 if (this.getMap()) { 15431 for (i=0; i<this.layers.length; i++) { 15432 l = this.layers[i]; 15433 if (l.right) l.layer.un(['precompose','prerender'], this.prec
15433omposeRight_); 15434 else l.layer.un(['precompose','prerender'], this.precomposeLeft_); 15435 l.layer.un(['postcompose','postrender'], this.postcompose_); 15436 } 15437 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 15438 } 15439 ol.control.Control.prototype.setMap.call(this, map); 15440 if (map) { 15441 this._listener = []; 15442 for (i=0; i<this.layers.length; i++) { 15443 l = this.layers[i]; 15444 if (l.right) l.layer.on(['precompose','prerender'], this.precomposeRight_); 15445 else l.layer.on(['precompose','prerender'], this.precomposeLeft_); 15446 l.layer.on(['postcompose','postrender'], this.postcompose_); 15447 } 15448 try { map.renderSync(); } catch(e) { /* ok */ } 15449 } 15450}; 15451/** @private 15452*/ 15453ol.control.Swipe.prototype.isLayer_ = function(layer){ 15454 for (var k=0; k<this.layers.length; k++) { 15455 if (this.layers[k].layer === layer) return k; 15456 } 15457 return -1; 15458}; 15459/** Add a layer to clip 15460 * @param {ol.layer|Array<ol.layer>} layer to clip 15461 * @param {bool} add layer in the right part of the map, default left. 15462 */ 15463ol.control.Swipe.prototype.addLayer = function(layers, right) { 15464 if (!(layers instanceof Array)) layers = [layers]; 15465 for (var i=0; i<layers.length; i++) { 15466 var l = layers[i]; 15467 if (this.isLayer_(l) < 0) { 15468 this.layers.push({ layer:l, right:right }); 15469 if (this.getMap()) {
15470 if (right) l.on(['precompose','prerender'], this.precomposeRight_); 15471 else l.on(['precompose','prerender'], this.precomposeLeft_); 15472 l.on(['postcompose','postrender'], this.postcompose_); 15473 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 15474 } 15475 } 15476 } 15477}; 15478/** Remove all layers 15479 */ 15480ol.control.Swipe.prototype.removeLayers = function() { 15481 var layers = []; 15482 this.layers.forEach(function(l) { layers.push(l.layer); }); 15483 this.removeLayer(layers) 15484}; 15485/** Remove a layer to clip 15486 * @param {ol.layer|Array<ol.layer>} layer to clip 15487 */ 15488ol.control.Swipe.prototype.removeLayer = function(layers) { 15489 if (!(layers instanceof Array)) layers = [layers]; 15490 for (var i=0; i<layers.length; i++) { 15491 var k = this.isLayer_(layers[i]); 15492 if (k >=0 && this.getMap()) { 15493 if (this.layers[k].right) layers[i].un(['precompose','prerender'], this.precomposeRight_); 15494 else layers[i].un(['precompose','prerender'], this.precomposeLeft_); 15495 layers[i].un(['postcompose','postrender'], this.postcompose_); 15496 this.layers.splice(k,1); 15497 } 15498 } 15499 if (this.getMap()) { 15500 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 15501 } 15502}; 15503/** Get visible rectangle 15504 * @returns {ol.extent} 15505 */ 15506ol.control.Swipe.prototype.getRectangle = function() { 15507 var s; 15508 if (this.get('orientation') === 'vertical') { 15509 s = this.getMap().getSize(); 15510 return [ 0, 0, s[0]*this.get('position'), s[1]]; 15511 } else { 15512 s = this.getMap().getSize(); 15513 return [ 0, 0, s[0], s[1]*this.get('position')]; 15514 } 15515}; 15516/** @private 15517 */ 15518ol.control.Swipe.prototype.move = function(e) { 15519 var self = this; 15520 var l; 15521 if (!this._movefn) this._movefn = this.move.bind(this); 15522 switch (e.type) { 15523 case 'touchcancel': 15524 case 'touchend': 15525 case 'mouseup': { 15526 self.isMoving = false; 15527 ["mouseup", "mousemove", "touchend", "touchcancel", "touchmove"] 15528 .forEach(function(eventName) { 15529 document.removeEventListener(eventName, self._movefn); 15530 }); 15531 // Force VectorImage to refresh 15532 this.layers.forEach(function(l) { 15533 if (l.layer.getImageRatio) l.layer.changed(); 15534 }) 15535 break; 15536 } 15537 case 'mousedown': 15538 case 'touchstart': { 15539 self.isMoving = true; 15540 ["mouseup", "mousemove", "touchend", "touchcancel", "touchmove"] 15541 .forEach(function(eventName) { 15542 document.addEventListener(eventName, self._movefn); 15543 }); 15544 } 15545 // fallthrough 15546 case 'mousemove': 15547 case 'touchmove': { 15548 if (self.isMoving) { 15549 if (self.get('orientation') === 'vertical') { 15550 var pageX = e.pageX 15551 || (e.touches && e.touches.length && e.touches[0].pageX) 15552 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); 15553 if (!pageX) break; 15554 pageX -= self.getMap().getTargetElement().getBoundingClientRect().left + 15555 window.pageXOffset - document.documentElement.clientLeft; 15556 l = self.getMap().getSize()[0]; 15557 var w = l - Math.min(Math.max(0, l-pageX), l); 15558 l = w/l; 15559 self.set('position', l); 15560 self.dispatchEvent({ type: 'moving', size: [w, self.getMap().getSize()[1]], position: [l,0] }); 15561 } else { 15562 var pageY = e.pageY 15563 || (e.touches && e.touches.length && e.touches[0].pageY) 15564 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY); 15565 if (!pageY) break; 15566 pageY -= self.getMap().getTargetElement().getBoundingClientRect().top + 15567 window.pageYOffset - document.documentElement.clientTop; 15568 l = self.getMap().getSize()[1]; 15569 var h = l - Math.min(Math.max(0, l-pageY), l); 15570 l = h/l; 15571 self.set('position', l); 15572 self.dispatchEvent({ type: 'moving', size: [self.getMap().getSize()[0],h], position: [0,l] }); 15573 } 15574 } 15575 break; 15576 } 15577 default: break; 15578 } 15579}; 15580/** @private 15581 */ 15582ol.control.Swipe.prototype._transformPt = function(e, pt) { 15583 var tr = e.inversePixelTransform; 15584 var x = pt[0]; 15585 var y = pt[1]; 15586 pt[0] = tr[0] * x + tr[2] * y + tr[4]; 15587 pt[1] = tr[1] * x + tr[3] * y + tr[5]; 15588 return pt; 15589}; 15590/** @private 15591 */ 15592ol.control.Swipe.prototype._drawRect = function(e, pts) { 15593 var tr = e.inversePixelTransform; 15594 if (tr) { 15595 var r = [ 15596 [pts[0][0], pts[0][1]], 15597 [pts[0][0], pts[1][1]], 15598 [pts[1][0], pts[1][1]], 15599 [pts[1][0], pts[0][1]], 15600 [pts[0][0], pts[0][1]] 15601 ]; 15602 e.context.save() 15603 // Rotate VectorImages 15604 if (e.target.getImageRatio) { 15605 var rot = -Math.atan2(e.frameState.pixelToCoordinateTransform[1], e.frameState.pixelToCoordinateTransform[0]); 15606 e.context.translate(e.frameState.size[0]/2, e.frameState.size[1]/2) 15607 e.context.rotate(rot) 15608 e.context.translate(-e.frameState.size[0]/2, -e.frameState.size[1]/2) 15609 } 15610 r.forEach(function (pt, i) { 15611 pt = [ 15612 (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), 15613 (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) 15614 ]; 15615 if (!i) { 15616 e.context.moveTo(pt[0], pt[1]); 15617 } else { 15618 e.context.lineTo(pt[0], pt[1]); 15619 } 15620 });
15621 e.context.restore(); 15622 } else { 15623 var ratio = e.frameState.pixelRatio; 15624 e.context.rect(pts[0][0]*ratio,pts[0][1]*ratio,pts[1][0]*ratio,pts[1][1]*ratio); 15625 } 15626}; 15627/** @private 15628*/ 15629ol.control.Swipe.prototype.precomposeLeft = function(e) { 15630 var ctx = e.context; 15631 if (ctx instanceof WebGLRenderingContext) { 15632 if (e.type === 'prerender') { 15633 // Clear 15634 ctx.clearColor(0, 0, 0, 0); 15635 ctx.clear(ctx.COLOR_BUFFER_BIT); 15636 // Clip 15637 ctx.enable(ctx.SCISSOR_TEST); 15638 var mapSize = this.getMap().getSize(); // [width, height] in CSS pixels 15639 // get render coordinates and dimensions given CSS coordinates 15640 var bottomLeft = this._transformPt(e, [0, mapSize[1]]); 15641 var topRight = this._transformPt(e, [mapSize[0], 0]); 15642 var fullWidth = topRight[0] - bottomLeft[0]; 15643 var fullHeight = topRight[1] - bottomLeft[1]; 15644 if (this.get('orientation') === "vertical") { 15645 var width = Math.round(fullWidth * this.get('position')); 15646 var height = fullHeight; 15647 } else { 15648 var width = fullWidth; 15649 var height = Math.round((fullHeight * this.get('position'))); 15650 bottomLeft[1] += fullHeight - height; 15651 } 15652 ctx.scissor(bottomLeft[0], bottomLeft[1], width, height); 15653 } 15654 } else { 15655 var size = e.frameState.size; 15656 ctx.save(); 15657 ctx.beginPath(); 15658 var pts = [[0,0],[size[0],size[1]]]; 15659 if (this.get('orientation') === "vertical") { 15660 pts[1] = [ 15661 size[0]*.5 + this.getMap().getSize()[0] * (this.get('position') - .5), 15662 size[1] 15663 ]; 15664 } else { 15665 pts[1] = [ 15666 size[0], 15667 size[1]*.5 + this.getMap().getSize()[1] * (this.get('position') - .5) 15668 ]; 15669 } 15670 this._drawRect(e, pts); 15671 ctx.clip(); 15672 } 15673}; 15674/** @private 15675*/ 15676ol.control.Swipe.prototype.precomposeRight = function(e) { 15677 var ctx = e.context; 15678 if (ctx instanceof WebGLRenderingContext) { 15679 if (e.type === 'prerender') { 15680 // Clear 15681 ctx.clearColor(0, 0, 0, 0); 15682 ctx.clear(ctx.COLOR_BUFFER_BIT); 15683 // Clip 15684 ctx.enable(ctx.SCISSOR_TEST); 15685 var mapSize = this.getMap().getSize(); // [width, height] in CSS pixels 15686 // get render coordinates and dimensions given CSS coordinates 15687 var bottomLeft = this._transformPt(e, [0, mapSize[1]]); 15688 var topRight = this._transformPt(e, [mapSize[0], 0]); 15689 var fullWidth = topRight[0] - bottomLeft[0]; 15690 var fullHeight = topRight[1] - bottomLeft[1]; 15691 if (this.get('orientation') === "vertical") { 15692 var height = fullHeight; 15693 var width = Math.round(fullWidth * (1-this.get('position'))); 15694 bottomLeft[0] += fullWidth - width; 15695 } else { 15696 var width = fullWidth; 15697 var height = Math.round(fullHeight * (1-this.get('position'))); 15698 } 15699 ctx.scissor(bottomLeft[0], bottomLeft[1], width, height); 15700 } 15701 } else { 15702 var size = e.frameState.size; 15703 ctx.save(); 15704 ctx.beginPath(); 15705 var pts = [[0,0],[size[0],size[1]]]; 15706 if (this.get('orientation') === "vertical") { 15707 pts[0] = [ 15708 size[0]*.5 + this.getMap().getSize()[0] * (this.get('position') - .5), 15709 0 15710 ]; 15711 } else { 15712 pts[0] = [ 15713 0, 15714 size[1]*.5 + this.getMap().getSize()[1] * (this.get('position') - .5) 15715 ] 15716 } 15717 this._drawRect(e, pts); 15718 ctx.clip(); 15719 } 15720}; 15721/** @private 15722*/ 15723ol.control.Swipe.prototype.postcompose = function(e) { 15724 if (e.context instanceof WebGLRenderingContext) { 15725 if (e.type === 'postrender') { 15726 var gl = e.context; 15727 gl.disable(gl.SCISSOR_TEST); 15728 } 15729 } else { 15730 // restore context when decluttering is done (ol>=6) 15731 // https://github.com/openlayers/openlayers/issues/10096 15732 if (e.target.getClassName && e.target.getClassName()!=='ol-layer' && e.target.get('declutter')) { 15733 setTimeout(function () { 15734 e.context.restore(); 15735 }, 0); 15736 } else { 15737 e.context.restore(); 15738 } 15739 } 15740}; 15741 15742/* 15743 Copyright (c) 2015 Jean-Marc VIGLINO, 15744 released under the CeCILL-B license (French BSD license) 15745 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 15746*/ 15747/** A control that use a CSS clip rect to swipe the map 15748 * @classdesc Swipe Control. 15749 * @fires moving 15750 * @constructor 15751 * @extends {ol.control.Control} 15752 * @param {Object=} Control options. 15753 * @param {ol.layer} options.layers layer to swipe 15754 * @param {ol.layer} options.rightLayer layer to swipe on right side 15755 * @param {string} options.className control class name 15756 * @param {number} options.position position propertie of the swipe [0,1], default 0.5 15757 * @param {string} options.orientation orientation propertie (vertical|horizontal), default vertical 15758 */ 15759ol.control.SwipeMap = function(options) { 15760 options = options || {}; 15761 var button = document.createElement('button'); 15762 var element = document.createElement('div'); 15763 element.className = (options.className || "ol-swipe") + " ol-unselectable ol-control"; 15764 element.appendChild(button); 15765 element.addEventListener("mousedown", this.move.bind(this)); 15766 element.addEventListener("touchstart", this.move.bind(this)); 15767 ol.control.Control.call(this, { 15768 element: element 15769 }); 15770 this.on('propertychange', function(e) { 15771 if (this.get('orientation') === "horizontal") { 15772 this.element.style.top = this.get('position')*100+"%"; 15773 this.element.style.left = ""; 15774 } else { 15775 if (this.get('orientation') !== "vertical") this.set('orientation', "vertical"); 15776 this.element.style.left = this.get('position')*100+"%"; 15777 this.element.style.top = ""; 15778 } 15779 if (e.key === 'orientation') { 15780 this.element.classList.remove("horizontal", "vertical"); 15781 this.element.classList.add(this.get('orientation')); 15782 } 15783 this._clip(); 15784 }.bind(this)); 15785 this.on('change:active', this._clip.bind(this)); 15786 this.set('position', options.position || 0.5); 15787 this.set('orientation', options.orientation || 'vertical'); 15788 this.set('right', options.right); 15789}; 15790ol.ext.inherits(ol.control.SwipeMap, ol.control.Control); 15791/** Set the map instance the control associated with. 15792 * @param {ol.Map} map The map instance. 15793 */ 15794ol.control.SwipeMap.prototype.setMap = function(map) { 15795 if (this.getMap()) { 15796 if (this._listener) ol.Observable.unByKey(this._listener); 15797 var layerDiv = this.getMap().getViewport().querySelector('.ol-layers'); 15798 layerDiv.style.clip = ''; 15799 } 15800 ol.control.Control.prototype.setMap.call(this, map); 15801 if (map) { 15802 this._listener = map.on('change:size', this._clip.bind(this)); 15803 } 15804}; 15805/** Clip 15806 * @private 15807 */ 15808ol.control.SwipeMap.prototype._clip = function() { 15809 if (this.getMap()) { 15810 var layerDiv = this.getMap().getViewport().querySelector('.ol-layers'); 15811 var rect = this.getRectangle(); 15812 layerDiv.style.clip = 'rect(' 15813 + rect[1]+'px,' // top 15814 + rect[2]+'px,' // right 15815 + rect[3]+'px,' // bottom 15816 + rect[0]+'px' //left 15817 + ')'; 15818 } 15819}; 15820/** Get visible rectangle 15821 * @returns {ol.extent} 15822 */ 15823ol.control.SwipeMap.prototype.getRectangle = function() { 15824 var s = this.getMap().getSize(); 15825 if (this.get('orientation') === 'vertical') { 15826 if (this.get('right')) { 15827 return [ s[0]*this.get('position'), 0, s[0], s[1]]; 15828 } else { 15829 return [ 0, 0, s[0]*this.get('position'), s[1]]; 15830 } 15831 } else { 15832 if (this.get('right')) { 15833 return [ 0, s[1]*this.get('position'), s[0], s[1]]; 15834 } else { 15835 return [ 0, 0, s[0], s[1]*this.get('position')]; 15836 } 15837 } 15838}; 15839/** @private 15840*/ 15841ol.control.SwipeMap.prototype.move = function(e) { 15842 var self = this; 15843 var l; 15844 if (!this._movefn) this._movefn = this.move.bind(this); 15845 switch (e.type) { 15846 case 'touchcancel': 15847 case 'touchend': 15848 case 'mouseup': { 15849 self.isMoving = false;
15850 ["mouseup", "mousemove", "touchend", "touchcancel", "touchmove"] 15851 .forEach(function(eventName) { 15852 document.removeEventListener(eventName, self._movefn); 15853 }); 15854 break; 15855 } 15856 case 'mousedown': 15857 case 'touchstart': { 15858 self.isMoving = true; 15859 ["mouseup", "mousemove", "touchend", "touchcancel", "touchmove"] 15860 .forEach(function(eventName) { 15861 document.addEventListener(eventName, self._movefn); 15862 }); 15863 } 15864 // fallthrough 15865 case 'mousemove': 15866 case 'touchmove': { 15867 if (self.isMoving) { 15868 if (self.get('orientation') === 'vertical') { 15869 var pageX = e.pageX 15870 || (e.touches && e.touches.length && e.touches[0].pageX) 15871 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX); 15872 if (!pageX) break; 15873 pageX -= self.getMap().getTargetElement().getBoundingClientRect().left + 15874 window.pageXOffset - document.documentElement.clientLeft; 15875 l = self.getMap().getSize()[0]; 15876 var w = l - Math.min(Math.max(0, l-pageX), l); 15877 l = w/l; 15878 self.set('position', l); 15879 self.dispatchEvent({ type: 'moving', size: [w, self.getMap().getSize()[1]], position: [l,0] }); 15880 } else { 15881 var pageY = e.pageY 15882 || (e.touches && e.touches.length && e.touches[0].pageY) 15883 || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY); 15884 if (!pageY) break; 15885 pageY -= self.getMap().getTargetElement().getBoundingClientRect().top + 15886 window.pageYOffset - document.documentElement.clientTop; 15887 l = self.getMap().getSize()[1]; 15888 var h = l - Math.min(Math.max(0, l-pageY), l); 15889 l = h/l; 15890 self.set('position', l); 15891 self.dispatchEvent({ type: 'moving', size: [self.getMap().getSize()[0],h], position: [0,l] }); 15892 } 15893 } 15894 break; 15895 } 15896 default: break; 15897 } 15898}; 15899 15900/* Copyright (c) 2016 Jean-Marc VIGLINO, 15901 released under the CeCILL-B license (French BSD license) 15902 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 15903*/ 15904/** ol.control.Target draw a target at the center of the map. 15905 * @constructor 15906 * @extends {ol.control.CanvasBase} 15907 * @param {Object} options 15908 * @param {ol.style.Style|Array<ol.style.Style>} options.style 15909 * @param {string} options.composite composite operation = difference|multiply|xor|screen|overlay|darken|lighter|lighten|... 15910 */ 15911ol.control.Target = function(options) { 15912 options = options || {}; 15913 this.style = options.style || [ 15914 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 }) }) }), 15915 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 }) }) }) 15916 ]; 15917 if (!(this.style instanceof Array)) this.style = [this.style]; 15918 this.composite = options.composite || ''; 15919 var div = document.createElement('div'); 15920 div.className = "ol-target ol-unselectable ol-control"; 15921 ol.control.CanvasBase.call(this, { 15922 element: div, 15923 target: options.target 15924 }); 15925 this.setVisible(options.visible!==false); 15926}; 15927ol.ext.inherits(ol.control.Target, ol.control.CanvasBase); 15928/** Set the control visibility 15929 * @paraam {boolean} b 15930 */ 15931ol.control.Target.prototype.setVisible = function (b) { 15932 this.set("visible",b); 15933 if (this.getMap()) { 15934 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 15935 } 15936}; 15937/** Get the control visibility 15938 * @return {boolean} b 15939 */ 15940ol.control.Target.prototype.getVisible = function () { 15941 return this.get("visible"); 15942}; 15943/** Draw the target 15944 * @private 15945 */ 15946ol.control.Target.prototype._draw = function (e) { 15947 var ctx = this.getContext(e); 15948 if (!ctx || !this.getMap() || !this.getVisible()) return; 15949 var ratio = e.frameState.pixelRatio; 15950 ctx.save(); 15951 ctx.scale(ratio,ratio); 15952 var cx = ctx.canvas.width/(2*ratio);
15953 var cy = ctx.canvas.height/(2*ratio); 15954 var geom = new ol.geom.Point (this.getMap().getCoordinateFromPixel([cx,cy])); 15955 if (this.composite) ctx.globalCompositeOperation = this.composite; 15956 for (var i=0; i<this.style.length; i++) { 15957 var style = this.style[i]; 15958 if (style instanceof ol.style.Style) { 15959 var vectorContext = e.vectorContext; 15960 if (!vectorContext) { 15961 var event = { 15962 inversePixelTransform: [ratio,0,0,ratio,0,0], 15963 context: ctx, 15964 frameState: { 15965 pixelRatio: ratio, 15966 extent: e.frameState.extent, 15967 coordinateToPixelTransform: e.frameState.coordinateToPixelTransform, 15968 viewState: e.frameState.viewState 15969 } 15970 } 15971 vectorContext = ol.render.getVectorContext(event); 15972 } 15973 vectorContext.setStyle(style); 15974 vectorContext.drawGeometry(geom); 15975 } 15976 } 15977 ctx.restore(); 15978}; 15979 15980/* Copyright (c) 2016 Jean-Marc VIGLINO, 15981 released under the CeCILL-B license (French BSD license) 15982 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 15983*/ 15984/** A simple push button control drawn as text 15985 * @constructor 15986 * @extends {ol.control.Button} 15987 * @param {Object=} options Control options. 15988 * @param {String} options.className class of the control 15989 * @param {String} options.title title of the control 15990 * @param {String} options.html html to insert in the control 15991 * @param {function} options.handleClick callback when control is clicked (or use change:active event) 15992 */ 15993ol.control.TextButton = function(options) 15994{ options = options || {}; 15995 options.className = (options.className||"") + " ol-text-button"; 15996 ol.control.Button.call(this, options); 15997}; 15998ol.ext.inherits(ol.control.TextButton, ol.control.Button); 15999 16000/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ 16001/** Timeline control 16002 * 16003 * @constructor 16004 * @extends {ol.control.Control} 16005 * @fires select 16006 * @fires scroll 16007 * @fires collapse 16008 * @param {Object=} options Control options. 16009 * @param {String} options.className class of the control 16010 * @param {Array<ol.Feature>} options.features Features to show in the timeline 16011 * @param {ol.SourceImageOptions.vector} options.source class of the control 16012 * @param {Number} options.interval time interval length in ms or a text with a format d, h, mn, s (31 days = '31d'), default none 16013 * @param {String} options.maxWidth width of the time line in px, default 2000px 16014 * @param {String} options.minDate minimum date 16015 * @param {String} options.maxDate maximum date 16016 * @param {Number} options.minZoom Minimum zoom for the line, default .2 16017 * @param {Number} options.maxZoom Maximum zoom for the line, default 4 16018 * @param {boolean} options.zoomButton Are zoom buttons avaliable, default false 16019 * @param {function} options.getHTML a function that takes a feature and returns the html to display 16020 * @param {function} options.getFeatureDate a function that takes a feature and returns its date, default the date propertie 16021 * @param {function} options.endFeatureDate a function that takes a feature and returns its end date, default no end date 16022 * @param {String} options.graduation day|month to show month or day graduation, default show only years 16023 * @param {String} options.scrollTimeout Time in milliseconds to get a scroll event, default 15ms 16024 */ 16025ol.control.Timeline = function(options) { 16026 var element = ol.ext.element.create('DIV', { 16027 className: (options.className || '') + ' ol-timeline' 16028 + (options.target ? '': ' ol-unselectable ol-control') 16029 + (options.zoomButton ? ' ol-hasbutton':'') 16030 }); 16031 // Initialize 16032 ol.control.Control.call(this, { 16033 element: element, 16034 target: options.target 16035 }); 16036 // Scroll div 16037 this._scrollDiv = ol.ext.element.create('DIV', { 16038 className: 'ol-scroll', 16039 parent: this.element 16040 }); 16041 // Add a button bar 16042 this._buttons = ol.ext.element.create('DIV', { 16043 className: 'ol-buttons', 16044 parent: this.element 16045 }); 16046 // Zoom buttons 16047 if (options.zoomButton) { 16048 // Zoom in 16049 this.addButton({ 16050 className: 'ol-zoom-in', 16051 handleClick: function(){ 16052 var zoom = this.get('zoom'); 16053 if (zoom>=1) { 16054 zoom++; 16055 } else { 16056 zoom = Math.min(1, zoom + 0.1); 16057 } 16058 zoom = Math.round(zoom*100)/100; 16059 this.refresh(zoom); 16060 }.bind(this) 16061 }); 16062 // Zoom out 16063 this.addButton({ 16064 className: 'ol-zoom-out', 16065 handleClick: function(){ 16066 var zoom = this.get('zoom'); 16067 if (zoom>1) { 16068 zoom--; 16069 } else { 16070 zoom -= 0.1; 16071 } 16072 zoom = Math.round(zoom*100)/100; 16073 this.refresh(zoom); 16074 }.bind(this) 16075 }); 16076 } 16077 // Draw center date 16078 this._intervalDiv = ol.ext.element.create('DIV', { 16079 className: 'ol-center-date', 16080 parent: this.element 16081 }); 16082 // Remove selection 16083 this.element.addEventListener('mouseover', function(){ 16084 if (this._select) this._select.elt.classList.remove('ol-select'); 16085 }.bind(this));
16086 // Trigger scroll event 16087 var scrollListener = null; 16088 this._scrollDiv.addEventListener('scroll', function() { 16089 this._setScrollLeft(); 16090 if (scrollListener) { 16091 clearTimeout(scrollListener); 16092 scrollListener = null; 16093 } 16094 scrollListener = setTimeout(function() { 16095 this.dispatchEvent({ 16096 type: 'scroll', 16097 date: this.getDate(), 16098 dateStart: this.getDate('start'), 16099 dateEnd: this.getDate('end') 16100 }); 16101 }.bind(this), options.scrollTimeout || 15); 16102 }.bind(this)); 16103 // Magic to give "live" scroll events on touch devices 16104 // this._scrollDiv.addEventListener('gesturechange', function() {}); 16105 // Scroll timeline 16106 ol.ext.element.scrollDiv(this._scrollDiv, { 16107 onmove: function(b) { 16108 // Prevent selection on moving 16109 this._moving = b; 16110 }.bind(this) 16111 }); 16112 this._tline = []; 16113 // Parameters 16114 this._scrollLeft = 0; 16115 this.set('maxWidth', options.maxWidth || 2000); 16116 this.set('minDate', options.minDate || Infinity); 16117 this.set('maxDate', options.maxDate || -Infinity); 16118 this.set('graduation', options.graduation); 16119 this.set('minZoom', options.minZoom || .2); 16120 this.set('maxZoom', options.maxZoom || 4); 16121 this.setInterval(options.interval); 16122 if (options.getHTML) this._getHTML = options.getHTML; 16123 if (options.getFeatureDate) this._getFeatureDate = options.getFeatureDate; 16124 if (options.endFeatureDate) this._endFeatureDate = options.endFeatureDate; 16125 // Feature source 16126 this.setFeatures(options.features || options.source, options.zoom); 16127}; 16128ol.ext.inherits(ol.control.Timeline, ol.control.Control); 16129/** 16130 * Set the map instance the control is associated with 16131 * and add interaction attached to it to this map. 16132 * @param {_ol_Map_} map The map instance. 16133 */ 16134ol.control.Timeline.prototype.setMap = function(map) { 16135 ol.control.Control.prototype.setMap.call(this, map); 16136 this.refresh(this.get('zoom')||1, true); 16137}; 16138/** Add a button on the timeline 16139 * @param {*} button 16140 * @param {string} button.className 16141 * @param {title} button.className 16142 * @param {Element|string} button.html Content of the element 16143 * @param {function} button.click a function called when the button is clicked 16144 */ 16145ol.control.Timeline.prototype.addButton = function(button) { 16146 this.element.classList.add('ol-hasbutton'); 16147 ol.ext.element.create('BUTTON', { 16148 className: button.className || undefined, 16149 title: button.title, 16150 html : button.html, 16151 click: button.handleClick, 16152 parent: this._buttons 16153 }) 16154}; 16155/** Set an interval 16156 * @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 16157 */ 16158ol.control.Timeline.prototype.setInterval = function(length) { 16159 if (typeof(length)==='string') { 16160 if (/s$/.test(length)) { 16161 length = parseFloat(length) * 1000; 16162 } else if (/mn$/.test(length)) { 16163 length = parseFloat(length) * 1000 * 60; 16164 } else if (/h$/.test(length)) { 16165 length = parseFloat(length) * 1000 * 3600; 16166 } else if (/d$/.test(length)) { 16167 length = parseFloat(length) * 1000 * 3600 * 24; 16168 } else if (/y$/.test(length)) { 16169 length = parseFloat(length) * 1000 * 3600 * 24 * 365; 16170 } else { 16171 length = 0; 16172 } 16173 } 16174 this.set('interval', length || 0); 16175 if (length) this.element.classList.add('ol-interval'); 16176 else this.element.classList.remove('ol-interval'); 16177 this.refresh(this.get('zoom')); 16178} 16179/** Default html to show in the line 16180 * @param {ol.Feature} feature 16181 * @return {DOMElement|string} 16182 * @private 16183 */ 16184ol.control.Timeline.prototype._getHTML = function(feature) { 16185 return feature.get('name') || ''; 16186}; 16187/** Default function to get the date of a feature, returns the date attribute 16188 * @param {ol.Feature} feature 16189 * @return {Data|string} 16190 * @private 16191 */ 16192ol.control.Timeline.prototype._getFeatureDate = function(feature) { 16193 return (feature && feature.get) ? feature.get('date') : null; 16194}; 16195/** Default function to get the end date of a feature, return undefined 16196 * @param {ol.Feature} feature 16197 * @return {Data|string} 16198 * @private 16199 */ 16200ol.control.Timeline.prototype._endFeatureDate = function(/* feature */) { 16201 return undefined; 16202}; 16203/** Is the line collapsed 16204 * @return {boolean} 16205 */ 16206ol.control.Timeline.prototype.isCollapsed = function() { 16207 return this.element.classList.contains('ol-collapsed'); 16208}; 16209/** Collapse the line 16210 * @param {boolean} b 16211 */ 16212ol.control.Timeline.prototype.collapse = function(b) { 16213 if (b) this.element.classList.add('ol-collapsed'); 16214 else this.element.classList.remove('ol-collapsed'); 16215 this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() }); 16216}; 16217/** Collapse the line 16218 */ 16219ol.control.Timeline.prototype.toggle = function() { 16220 this.element.classList.toggle('ol-collapsed'); 16221 this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() }); 16222}; 16223/** Set the features to display in the timeline 16224 * @param {Array<ol.Features>|ol.source.Vector} features An array of features or a vector source 16225 * @param {number} zoom zoom to draw the line default 1 16226 */ 16227ol.control.Timeline.prototype.setFeatures = function(features, zoom) { 16228 this._features = this._source = null; 16229 if (features instanceof ol.source.Vector) this._source = features; 16230 else if (features instanceof Array) this._features = features; 16231 else this._features = []; 16232 this.refresh(zoom); 16233}; 16234/** 16235 * Get features 16236 * @return {Array<ol.Feature>} 16237 */ 16238ol.control.Timeline.prototype.getFeatures = function() { 16239 return this._features || this._source.getFeatures(); 16240} 16241/** 16242 * Refresh the timeline with new data 16243 * @param {Number} zoom Zoom factor from 0.25 to 10, default 1 16244 */ 16245ol.control.Timeline.prototype.refresh = function(zoom, first) { 16246 if (!this.getMap()) return; 16247 if (!zoom) zoom = this.get('zoom'); 16248 zoom = Math.min(this.get('maxZoom'), Math.max(this.get('minZoom'), zoom || 1)); 16249 this.set('zoom', zoom); 16250 this._scrollDiv.innerHTML = ''; 16251 var features = this.getFeatures(); 16252 var d, d2; 16253 // Get features sorted by date 16254 var tline = this._tline = [];
16255 features.forEach(function(f) { 16256 if (d = this._getFeatureDate(f)) { 16257 if (!(d instanceof Date)) { 16258 d = new Date(d) 16259 } 16260 if (this._endFeatureDate) { 16261 d2 = this._endFeatureDate(f); 16262 if (!(d2 instanceof Date)) { 16263 d2 = new Date(d2) 16264 } 16265 } 16266 if (!isNaN(d)) { 16267 tline.push({ 16268 date: d, 16269 end: isNaN(d2) ? null : d2, 16270 feature: f 16271 }); 16272 } 16273 } 16274 }.bind(this)); 16275 tline.sort(function(a,b) { 16276 return (a.date < b.date ? -1 : (a.date===b.date ? 0: 1)) 16277 }); 16278 // Draw 16279 var div = ol.ext.element.create('DIV', { 16280 parent: this._scrollDiv 16281 }); 16282 // Calculate width 16283 var min = this._minDate = Math.min(this.get('minDate'), tline.length ? tline[0].date : Infinity); 16284 var max = this._maxDate = Math.max(this.get('maxDate'), tline.length ? tline[tline.length-1].date : -Infinity); 16285 if (!isFinite(min)) this._minDate = min = new Date(); 16286 if (!isFinite(max)) this._maxDate = max = new Date(); 16287 var delta = (max-min); 16288 var maxWidth = this.get('maxWidth'); 16289 var scale = this._scale = (delta > maxWidth ? maxWidth/delta : 1) * zoom; 16290 // Leave 10px on right 16291 min = this._minDate = this._minDate - 10/scale; 16292 delta = (max-min) * scale; 16293 ol.ext.element.setStyle(div, { 16294 width: delta, 16295 maxWidth: 'unset' 16296 }); 16297 // Draw time's bar 16298 this._drawTime(div, min, max, scale); 16299 // Set interval 16300 if (this.get('interval')) { 16301 ol.ext.element.setStyle (this._intervalDiv, { width: this.get('interval') * scale }); 16302 } else { 16303 ol.ext.element.setStyle (this._intervalDiv, { width: '' }); 16304 } 16305 // Draw features 16306 var line = []; 16307 var lineHeight = ol.ext.element.getStyle(this._scrollDiv, 'lineHeight'); 16308 // Wrapper 16309 var fdiv = ol.ext.element.create('DIV', { 16310 className: 'ol-features', 16311 parent: div 16312 }); 16313 // Add features on the line 16314 tline.forEach(function(f) { 16315 var d = f.date; 16316 var t = f.elt = ol.ext.element.create('DIV', { 16317 className: 'ol-feature', 16318 style: { 16319 left: Math.round((d-min)*scale), 16320 }, 16321 html: this._getHTML(f.feature), 16322 parent: fdiv 16323 }); 16324 // Prevent image dragging 16325 var img = t.querySelectorAll('img'); 16326 for (var i=0; i<img.length; i++) { 16327 img[i].ondragstart = function(){ return false; }; 16328 } 16329 // Calculate image width 16330 if (f.end) { 16331 ol.ext.element.setStyle(t, { 16332 minWidth: (f.end-d) * scale, 16333 width: (f.end-d) * scale, 16334 maxWidth: 'unset' 16335 }); 16336 } 16337 var left = ol.ext.element.getStyle(t, 'left'); 16338 // Select on click 16339 t.addEventListener('click', function() { 16340 if (!this._moving) { 16341 this.dispatchEvent({type: 'select', feature: f.feature }); 16342 } 16343 }.bind(this)); 16344 // Find first free Y position 16345 var pos, l; 16346 for (pos=0; l=line[pos]; pos++) { 16347 if (left > l) { 16348 break; 16349 } 16350 } 16351 line[pos] = left + ol.ext.element.getStyle(t, 'width'); 16352 ol.ext.element.setStyle(t, { top: pos*lineHeight }); 16353 }.bind(this)); 16354 this._nbline = line.length; 16355 if (first) this.setDate(this._minDate, { anim: false, position: 'start' }); 16356 // Dispatch scroll event 16357 this.dispatchEvent({ 16358 type: 'scroll', 16359 date: this.getDate(), 16360 dateStart: this.getDate('start'), 16361 dateEnd: this.getDate('end') 16362 }); 16363}; 16364/** Get offset given a date 16365 * @param {Date} date 16366 * @return {number} 16367 * @private 16368 */ 16369ol.control.Timeline.prototype._getOffsetFromDate = function(date) { 16370 return (date - this._minDate) * this._scale; 16371}; 16372/** Get date given an offset 16373 * @param {Date} date 16374 * @return {number} 16375 * @private 16376 */ 16377ol.control.Timeline.prototype._getDateFromOffset = function(offset) { 16378 return offset / this._scale + this._minDate; 16379}; 16380/** Set the current position 16381 * @param {number} scrollLeft current position (undefined when scrolling) 16382 * @returns {number} 16383 * @private 16384 */ 16385ol.control.Timeline.prototype._setScrollLeft = function(scrollLeft) { 16386 this._scrollLeft = scrollLeft; 16387 if (scrollLeft !== undefined) { 16388 this._scrollDiv.scrollLeft = scrollLeft; 16389 } 16390}; 16391/** Get the current position 16392 * @returns {number} 16393 * @private 16394 */ 16395ol.control.Timeline.prototype._getScrollLeft = function() { 16396 // Unset when scrolling 16397 if (this._scrollLeft === undefined) { 16398 return this._scrollDiv.scrollLeft; 16399 } else { 16400 // St by user 16401 return this._scrollLeft; 16402 } 16403}; 16404/** 16405 * Draw dates on line 16406 * @private 16407 */ 16408ol.control.Timeline.prototype._drawTime = function(div, min, max, scale) { 16409 // Times div 16410 var tdiv = ol.ext.element.create('DIV', { 16411 className: 'ol-times', 16412 parent: div 16413 }); 16414 var d, dt, month, dmonth; 16415 var dx = ol.ext.element.getStyle(tdiv, 'left'); 16416 var heigth = ol.ext.element.getStyle(tdiv, 'height'); 16417 // Year 16418 var year = (new Date(this._minDate)).getFullYear(); 16419 dt = ((new Date(0)).setFullYear(String(year)) - new Date(0).setFullYear(String(year-1))) * scale; 16420 var dyear = Math.round(2*heigth/dt)+1; 16421 while(true) { 16422 d = new Date(0).setFullYear(year); 16423 if (d > this._maxDate) break; 16424 ol.ext.element.create('DIV', { 16425 className: 'ol-time ol-year', 16426 style: { 16427 left: this._getOffsetFromDate(d) - dx 16428 }, 16429 html: year, 16430 parent: tdiv 16431 }); 16432 year += dyear; 16433 } 16434 // Month 16435 if (/day|month/.test(this.get('graduation'))) { 16436 dt = ((new Date(0,0,1)).setFullYear(String(year)) - new Date(0,0,1).setFullYear(String(year-1))) * scale; 16437 dmonth = Math.max(1, Math.round(12 / Math.round(dt/heigth/2))); 16438 if (dmonth < 12) { 16439 year = (new Date(this._minDate)).getFullYear();
16440 month = dmonth+1; 16441 while(true) { 16442 d = new Date(0,0,1); 16443 d.setFullYear(year); 16444 d.setMonth(month-1); 16445 if (d > this._maxDate) break; 16446 ol.ext.element.create('DIV', { 16447 className: 'ol-time ol-month', 16448 style: { 16449 left: this._getOffsetFromDate(d) - dx 16450 }, 16451 html: d.toLocaleDateString(undefined, { month: 'short'}), 16452 parent: tdiv 16453 }); 16454 month += dmonth; 16455 if (month > 12) { 16456 year++; 16457 month = dmonth+1; 16458 } 16459 } 16460 } 16461 } 16462 // Day 16463 if (this.get('graduation')==='day') { 16464 dt = (new Date(0,1,1) - new Date(0,0,1)) * scale; 16465 var dday = Math.max(1, Math.round(31 / Math.round(dt/heigth/2))); 16466 if (dday < 31) { 16467 year = (new Date(this._minDate)).getFullYear(); 16468 month = 0; 16469 var day = dday; 16470 while(true) { 16471 d = new Date(0,0,1); 16472 d.setFullYear(year); 16473 d.setMonth(month); 16474 d.setDate(day); 16475 if (isNaN(d)) { 16476 month++; 16477 if (month>12) { 16478 month = 1; 16479 year++; 16480 } 16481 day = dday; 16482 } else { 16483 if (d > this._maxDate) break; 16484 if (day>1) { 16485 var offdate = this._getOffsetFromDate(d); 16486 if (this._getOffsetFromDate(new Date(year, month+1, 1)) - offdate > heigth) { 16487 ol.ext.element.create('DIV', { 16488 className: 'ol-time ol-day', 16489 style: { 16490 left: offdate - dx 16491 }, 16492 html: day, 16493 parent: tdiv 16494 }); 16495 } 16496 } 16497 year = d.getFullYear(); 16498 month = d.getMonth(); 16499 day = d.getDate() + dday; 16500 if (day > new Date(year, month+1, 0).getDate()) { 16501 month++; 16502 day = dday; 16503 } 16504 } 16505 } 16506 } 16507 } 16508}; 16509/** Center timeline on a date 16510 * @param {Date|String|ol.feature} feature a date or a feature with a date 16511 * @param {Object} options 16512 * @param {boolean} options.anim animate scroll 16513 * @param {string} options.position start, end or middle, default middle 16514 */ 16515ol.control.Timeline.prototype.setDate = function(feature, options) { 16516 var date; 16517 options = options || {}; 16518 // It's a date 16519 if (feature instanceof Date) { 16520 date = feature; 16521 } else { 16522 // Get date from Feature 16523 if (this.getFeatures().indexOf(feature) >= 0) { 16524 date = this._getFeatureDate(feature); 16525 } 16526 if (date && !(date instanceof Date)) { 16527 date = new Date(date); 16528 } 16529 if (!date || isNaN(date)) { 16530 date = new Date(String(feature)); 16531 } 16532 } 16533 if (!isNaN(date)) { 16534 if (options.anim === false) this._scrollDiv.classList.add('ol-move'); 16535 var scrollLeft = this._getOffsetFromDate(date); 16536 if (options.position==='start') { 16537 scrollLeft += ol.ext.element.outerWidth(this._scrollDiv)/2 - ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; 16538 } else if (options.position==='end') { 16539 scrollLeft -= ol.ext.element.outerWidth(this._scrollDiv)/2 - ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; 16540 } 16541 this._setScrollLeft(scrollLeft); 16542 if (options.anim === false) this._scrollDiv.classList.remove('ol-move'); 16543 if (feature) { 16544 for (var i=0, f; f = this._tline[i]; i++) { 16545 if (f.feature === feature) { 16546 f.elt.classList.add('ol-select'); 16547 this._select = f; 16548 } else { 16549 f.elt.classList.remove('ol-select'); 16550 } 16551 } 16552 } 16553 } 16554}; 16555/** Get round date (sticked to mn, hour day or month) 16556 * @param {Date} d 16557 * @param {string} stick sticking option to stick date to: 'mn', 'hour', 'day', 'month', default no stick 16558 * @return {Date} 16559 */ 16560ol.control.Timeline.prototype.roundDate = function(d, stick) { 16561 switch (stick) { 16562 case 'mn': { 16563 return new Date(this._roundTo(d, 60*1000)); 16564 } 16565 case 'hour': { 16566 return new Date(this._roundTo(d, 60*60*1000)); 16567 } 16568 case 'day': { 16569 return new Date(this._roundTo(d, 24*60*60*1000)); 16570 } 16571 case 'month': { 16572 d = new Date(this._roundTo(d, 24*60*60*1000)); 16573 if (d.getDate() > 15) { 16574 d = new Date(d.setMonth(d.getMonth() + 1)); 16575 } 16576 d = d.setDate(1); 16577 return new Date(d); 16578 } 16579 default: return new Date(d); 16580 } 16581}; 16582/** Get the date of the center 16583 * @param {string} position position to get 'start', 'end' or 'middle', default middle 16584 * @param {string} stick sticking option to stick date to: 'mn', 'hour', 'day', 'month', default no stick 16585 * @return {Date} 16586 */ 16587ol.control.Timeline.prototype.getDate = function(position, stick) { 16588 var pos; 16589 if (!stick) stick = position; 16590 switch (position) { 16591 case 'start': { 16592 if (this.get('interval')) { 16593 pos = - ol.ext.element.getStyle(this._intervalDiv, 'width')/2 + ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; 16594 } else { 16595 pos = - ol.ext.element.outerWidth(this._scrollDiv)/2 + ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; 16596 } 16597 break; 16598 } 16599 case 'end': { 16600 if (this.get('interval')) { 16601 pos = ol.ext.element.getStyle(this._intervalDiv, 'width')/2 - ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; 16602 } else { 16603 pos = ol.ext.element.outerWidth(this._scrollDiv)/2 - ol.ext.element.getStyle(this._scrollDiv, 'marginLeft')/2; 16604 } 16605 break; 16606 } 16607 default: { 16608 pos = 0; 16609 break; 16610 } 16611 } 16612 var d = this._getDateFromOffset(this._getScrollLeft() + pos); 16613 d = this.roundDate(d, stick); 16614 return new Date(d); 16615}; 16616/** Round number to 16617 * @param {number} d 16618 * @param {number} r 16619 * @return {number} 16620 * @private 16621 */ 16622ol.control.Timeline.prototype._roundTo = function(d, r) { 16623 return Math.round(d/r) * r; 16624}; 16625/** Get the start date of the control 16626 * @return {Date} 16627 */ 16628ol.control.Timeline.prototype.getStartDate = function() { 16629 return new Date(this.get('minDate')); 16630} 16631/** Get the end date of the control 16632 * @return {Date} 16633 */ 16634ol.control.Timeline.prototype.getEndDate = function() { 16635 return new Date(this.get('maxDate')); 16636} 16637 16638/* 16639 Copyright (c) 2019 Jean-Marc VIGLINO, 16640 released under the CeCILL-B license (French BSD license)
16641 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 16642*/ 16643/** Record map canvas as video 16644 * @constructor 16645 * @fire start 16646 * @fire error 16647 * @fire stop 16648 * @fire pause 16649 * @fire resume 16650 * @extends {ol.control.Control} 16651 * @param {Object=} options Control options. 16652 * @param {String} options.className class of the control 16653 * @param {number} [options.framerate=30] framerate for the video 16654 * @param {number} [options.videoBitsPerSecond=5000000] bitrate for the video 16655 * @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 16656 */ 16657ol.control.VideoRecorder = function(options) { 16658 if (!options) options = {}; 16659 var element = ol.ext.element.create('DIV', { 16660 className: (options.className || 'ol-videorec') + ' ol-unselectable ol-control' 16661 }); 16662 // buttons 16663 ol.ext.element.create('BUTTON', { 16664 type: 'button', 16665 className: 'ol-start', 16666 title: 'start', 16667 click: function() { 16668 this.start(); 16669 }.bind(this), 16670 parent: element 16671 }); 16672 ol.ext.element.create('BUTTON', { 16673 type: 'button', 16674 className: 'ol-stop', 16675 title: 'stop', 16676 click: function() { 16677 this.stop(); 16678 }.bind(this), 16679 parent: element 16680 }); 16681 ol.ext.element.create('BUTTON', { 16682 type: 'button', 16683 className: 'ol-pause', 16684 title: 'pause', 16685 click: function() { 16686 this.pause(); 16687 }.bind(this), 16688 parent: element 16689 }); 16690 ol.ext.element.create('BUTTON', { 16691 type: 'button', 16692 className: 'ol-resume', 16693 title: 'resume', 16694 click: function() { 16695 this.resume(); 16696 }.bind(this), 16697 parent: element 16698 }); 16699 // Start 16700 ol.control.Control.call(this, { 16701 element: element, 16702 target: options.target 16703 }); 16704 this.set('framerate', 30); 16705 this.set('videoBitsPerSecond', 5000000); 16706 if (options.videoTarget === 'DIALOG') { 16707 this._dialog = new ol.control.Dialog({ 16708 className: 'ol-fullscreen-dialog', 16709 target: document.body, 16710 closeBox: true 16711 }); 16712 this._videoTarget = this._dialog.getContentElement(); 16713 } else { 16714 this._videoTarget = options.videoTarget; 16715 } 16716 // Print control 16717 this._printCtrl = new ol.control.Print({ 16718 target: ol.ext.element.create('DIV') 16719 }); 16720} 16721ol.ext.inherits(ol.control.VideoRecorder, ol.control.Control); 16722/** 16723 * Remove the control from its current map and attach it to the new map. 16724 * Subclasses may set up event handlers to get notified about changes to 16725 * the map here. 16726 * @param {ol.Map} map Map. 16727 * @api stable 16728 */ 16729ol.control.VideoRecorder.prototype.setMap = function (map) { 16730 if (this.getMap()) { 16731 this.getMap().removeControl(this._printCtrl); 16732 if (this._dialog) this.getMap().removeControl(this._dialog); 16733 } 16734 ol.control.Control.prototype.setMap.call(this, map); 16735 if (this.getMap()) { 16736 this.getMap().addControl(this._printCtrl); 16737 if (this._dialog) this.getMap().addControl(this._dialog); 16738 } 16739}; 16740/** Start recording */ 16741ol.control.VideoRecorder.prototype.start = function () { 16742 var print = this._printCtrl; 16743 var stop = false; 16744 function capture(canvas) { 16745 if (stop) return; 16746 print.fastPrint({ 16747 canvas: canvas 16748 }, capture); 16749 } 16750 print.fastPrint({}, function(canvas) { 16751 var videoStream; 16752 try { 16753 videoStream = canvas.captureStream(this.get('framerate') || 30); 16754 } catch(e) { 16755 this.dispatchEvent({ 16756 type: 'error', 16757 error: e 16758 }); 16759 // console.warn(e); 16760 return; 16761 } 16762 this._mediaRecorder = new MediaRecorder(videoStream, { 16763 videoBitsPerSecond : this.get('videoBitsPerSecond') || 5000000 16764 }); 16765 var chunks = []; 16766 this._mediaRecorder.ondataavailable = function(e) { 16767 chunks.push(e.data); 16768 }; 16769 this._mediaRecorder.onstop = function() { 16770 stop = true; 16771 var blob = new Blob(chunks, { 'type' : 'video/mp4' }); // other types are available such as 'video/webm' for instance, see the doc for more info 16772 chunks = []; 16773 if (this._videoTarget instanceof Element) { 16774 var video; 16775 if (this._videoTarget.tagName === 'VIDEO') { 16776 video = this._videoTarget; 16777 } else { 16778 video = this._videoTarget.querySelector('video'); 16779 if (!video) { 16780 video = ol.ext.element.create('VIDEO', { 16781 controls: '', 16782 parent: this._videoTarget 16783 }); 16784 } 16785 } 16786 if (this._dialog) this._dialog.show(); 16787 video.src = URL.createObjectURL(blob); 16788 this.dispatchEvent({ type: 'stop', videoURL: video.src }); 16789 } else { 16790 this.dispatchEvent({ type: 'stop', videoURL: URL.createObjectURL(blob) }); 16791 } 16792 }.bind(this); 16793 this._mediaRecorder.onpause = function() { 16794 stop = true; 16795 this.dispatchEvent({ type: 'pause' }); 16796 }.bind(this); 16797 this._mediaRecorder.onresume = function() { 16798 stop = false;
16799 capture(canvas); 16800 this.dispatchEvent({ type: 'resume' }); 16801 }.bind(this); 16802 this._mediaRecorder.onerror = function(e) { 16803 this.dispatchEvent({ type: 'error', error: e }); 16804 }.bind(this); 16805 stop = false; 16806 capture(canvas); 16807 this._mediaRecorder.start(); 16808 this.dispatchEvent({ type: 'start', canvas: canvas }); 16809 this.element.setAttribute('data-state', 'rec'); 16810 }.bind(this)) 16811}; 16812/** Stop recording */ 16813ol.control.VideoRecorder.prototype.stop = function () { 16814 if (this._mediaRecorder) { 16815 this._mediaRecorder.stop(); 16816 this._mediaRecorder = null; 16817 this.element.setAttribute('data-state', 'inactive'); 16818 } 16819}; 16820/** Pause recording */ 16821ol.control.VideoRecorder.prototype.pause = function () { 16822 if (this._mediaRecorder) { 16823 this._mediaRecorder.pause(); 16824 this.element.setAttribute('data-state', 'pause'); 16825 } 16826}; 16827/** Resume recording after pause */ 16828ol.control.VideoRecorder.prototype.resume = function () { 16829 if (this._mediaRecorder) { 16830 this._mediaRecorder.resume(); 16831 this.element.setAttribute('data-state', 'rec'); 16832 } 16833}; 16834 16835/* 16836 Using WMS Layer with EPSG:4326 projection. 16837 The tiles will be reprojected to map pojection (EPSG:3857). 16838 NB: reduce tileSize to minimize deformations on small scales. 16839*/ 16840/** WMSCapabilities 16841 * @constructor 16842 * @fires load 16843 * @fires capabilities 16844 * @extends {ol.control.Button} 16845 * @param {*} options 16846 * @param {string|Element} [options.target] the target to set the dialog, use document.body to have fullwindow dialog 16847 * @param {string} [options.proxy] proxy to use when requesting Getcapabilites, default none (suppose the service use CORS) 16848 * @param {string} [options.placeholder='service url...'] input placeholder, default 'service url...' 16849 * @param {string} [options.title=WMS] dialog title, default 'WMS' 16850 * @param {string} [options.searchLabel='search'] Label for search button, default 'search' 16851 * @param {string} [options.loadLabel='load'] Label for load button, default 'load' 16852 * @param {Array<string>} [options.srs] an array of supported srs, default map projection code or 'EPSG:3857' 16853 * @param {number} [options.timeout=1000] Timeout for getCapabilities request, default 1000 16854 * @param {boolean} [options.cors=false] Use CORS, default false 16855 * @param {string} [options.optional] a list of optional url properties (when set in the request url), separated with ',' 16856 * @param {boolean} [options.trace=false] Log layer info, default false 16857 * @param {*} [options.services] a key/url object of services for quick access in a menu 16858 */ 16859ol.control.WMSCapabilities = function (options) { 16860 options = options || {}; 16861 var buttonOptions = Object.assign({}, options || {}); 16862 this._proxy = options.proxy; 16863 if (buttonOptions.target===document.body) delete buttonOptions.target; 16864 if (buttonOptions.target) { 16865 buttonOptions.className = ((buttonOptions.className||'') + ' ol-wmscapabilities ol-hidden').trim(); 16866 delete buttonOptions.target; 16867 } else { 16868 buttonOptions.className = ((buttonOptions.className||'') + ' ol-wmscapabilities').trim(); 16869 buttonOptions.handleClick = function () { 16870 this.showDialog(); 16871 }.bind(this) 16872 } 16873 ol.control.Button.call(this, buttonOptions); 16874 // WMS options 16875 this.set('srs', options.srs || []); 16876 this.set('cors', options.cors); 16877 this.set('trace', options.trace); 16878 this.set('title', options.title); 16879 this.set('loadLabel', options.loadLabel); 16880 this.set('optional', options.optional); 16881 // Dialog 16882 this.createDialog(options); 16883 // Default version 16884 this._elements.formVersion.value = '1.0.0'; 16885 // Ajax request 16886 var parser = this._getParser(); 16887 this._ajax = new ol.ext.Ajax({ dataType:'text', auth: options.authentication }); 16888 this._ajax.on('success', function (evt) { 16889 var caps; 16890 try { 16891 caps = parser.read(evt.response); 16892 } catch (e) { 16893 this.showError({ type: 'load', error: e }); 16894 } 16895 if (caps) { 16896 if (!caps.Capability.Layer.Layer) { 16897 this.showError({ type: 'noLayer' }); 16898 } else { 16899 this.showCapabilities(caps); 16900 } 16901 } 16902 this.dispatchEvent({ type: 'capabilities', capabilities: caps }); 16903 if (typeof(evt.options.callback) === 'function') evt.options.callback(caps); 16904 }.bind(this)); 16905 this._ajax.on('error', function(evt) { 16906 this.showError({ type: 'load', error: evt }); 16907 this.dispatchEvent({ type: 'capabilities' }); 16908 if (typeof(evt.options.callback) === 'function') false; 16909 }.bind(this)); 16910 // Handle waiting 16911 this._ajax.on('loadstart', function() { 16912 this._elements.element.classList.add('ol-searching'); 16913 }.bind(this)); 16914 this._ajax.on('loadend', function() { 16915 this._elements.element.classList.remove('ol-searching'); 16916 }.bind(this)); 16917 // Load a layer 16918 if (options.onselect) { 16919 this.on('load', function(e) { 16920 options.onselect(e.layer, e.options); 16921 }); 16922 } 16923}; 16924ol.ext.inherits(ol.control.WMSCapabilities, ol.control.Button); 16925/** Get service parser 16926 */ 16927ol.control.WMSCapabilities.prototype._getParser = function() { 16928 return new ol.format.WMSCapabilities(); 16929}; 16930/** Error list: a key/value list of error to display in the dialog 16931 * Overwrite it to handle internationalization 16932 */ 16933ol.control.WMSCapabilities.prototype.error = { 16934 load: 'Can\'t retrieve service capabilities, try to add it manually...', 16935 badUrl: 'The input value is not a valid url...', 16936 TileMatrix: 'No TileMatrixSet supported...', 16937 noLayer: 'No layer available for this service...', 16938 srs: 'The service projection looks different from that of your map, it may not display correctly...' 16939}; 16940/** Form labels: a key/value list of form labels to display in the dialog 16941 * Overwrite it to handle internationalization 16942 */ 16943ol.control.WMSCapabilities.prototype.labels = { 16944 formTitle: 'Title:', 16945 formLayer: 'Layers:', 16946 formMap: 'Map:', 16947 formStyle: 'Style:', 16948 formFormat: 'Format:', 16949 formMinZoom: 'Min zoom level:',
16950 formMaxZoom: 'Max zoom level:', 16951 formExtent: 'Extent:', 16952 mapExtent: 'use map extent...', 16953 formProjection: 'Projection:', 16954 formCrossOrigin: 'CrossOrigin:', 16955 formVersion: 'Version:', 16956 formAttribution: 'Attribution:', 16957}; 16958/** Create dialog 16959 * @private 16960 */ 16961ol.control.WMSCapabilities.prototype.createDialog = function (options) { 16962 var target = options.target; 16963 if (!target || target===document.body) { 16964 this._dialog = new ol.control.Dialog({ 16965 className: 'ol-wmscapabilities', 16966 closeBox: true, 16967 closeOnSubmit: false, 16968 target: options.target 16969 }); 16970 this._dialog.on('button', function(e) { 16971 if (e.button==='submit') { 16972 this.getCapabilities(e.inputs.url.value); 16973 } 16974 }.bind(this)); 16975 target = null; 16976 } 16977 var element = ol.ext.element.create('DIV', { 16978 className: ('ol-wmscapabilities '+(options.className||'')).trim(), 16979 parent: target 16980 }); 16981 this._elements = { 16982 element: target || element 16983 }; 16984 var inputdiv = ol.ext.element.create('DIV', { 16985 className: 'ol-url', 16986 parent: element 16987 }); 16988 var input = this._elements.input = ol.ext.element.create('INPUT', { 16989 className: 'url', 16990 type: 'text', 16991 tabIndex: 1, 16992 placeholder: options.placeholder || 'service url...', 16993 autocorrect: 'off', 16994 autocapitalize: 'off', 16995 parent: inputdiv 16996 }); 16997 input.addEventListener('keyup', function(e) { 16998 if (e.keyCode===13) { 16999 this.getCapabilities(input.value, options); 17000 } 17001 }.bind(this)); 17002 if (options.services) { 17003 var qaccess = ol.ext.element.create('SELECT', { 17004 className: 'url', 17005 on: { 17006 change: function(e) { 17007 var url = e.target.options[e.target.selectedIndex].value; 17008 this.getCapabilities(url, options); 17009 e.target.selectedIndex = 0; 17010 }.bind(this) 17011 }, 17012 parent: inputdiv 17013 }); 17014 ol.ext.element.create('OPTION', { 17015 html: ' ', 17016 parent: qaccess 17017 }); 17018 for (var k in options.services) { 17019 ol.ext.element.create('OPTION', { 17020 html: k, 17021 value: options.services[k], 17022 parent: qaccess 17023 }); 17024 } 17025 } 17026 ol.ext.element.create('BUTTON', { 17027 click: function() { 17028 this.getCapabilities(input.value, options); 17029 }.bind(this), 17030 html: options.searchLabel || 'search', 17031 parent: inputdiv 17032 }); 17033 // Errors 17034 this._elements.error = ol.ext.element.create('DIV', { 17035 className: 'ol-error', 17036 parent: inputdiv 17037 }); 17038 // Result div 17039 var rdiv = this._elements.result = ol.ext.element.create('DIV', { 17040 className: 'ol-result', 17041 parent: element 17042 }); 17043 // Preview 17044 var preview = ol.ext.element.create('DIV', { 17045 className: 'ol-preview', 17046 html: options.previewLabel || 'preview', 17047 parent: rdiv 17048 }); 17049 this._elements.preview = ol.ext.element.create('IMG', { 17050 parent: preview 17051 }); 17052 // Check tainted canvas 17053 this._img = new Image; 17054 this._img.crossOrigin = 'Anonymous'; 17055 this._img.addEventListener('error', function() { 17056 preview.className = 'ol-preview tainted'; 17057 this._elements.formCrossOrigin.checked = false; 17058 }.bind(this)); 17059 this._img.addEventListener('load', function() { 17060 preview.className = 'ol-preview ok'; 17061 this._elements.formCrossOrigin.checked = true; 17062 }.bind(this)); 17063 // Select list 17064 this._elements.select = ol.ext.element.create('DIV', { 17065 className: 'ol-select-list', 17066 tabIndex: 2, 17067 parent: rdiv 17068 }); 17069 // Info data 17070 this._elements.data = ol.ext.element.create('DIV', { 17071 className: 'ol-data', 17072 parent: rdiv 17073 }); 17074 this._elements.buttons = ol.ext.element.create('DIV', { 17075 className: 'ol-buttons', 17076 parent: rdiv 17077 }); 17078 this._elements.legend = ol.ext.element.create('IMG', { 17079 className: 'ol-legend', 17080 parent: rdiv 17081 }); 17082 // WMS form 17083 var form = this._elements.form = ol.ext.element.create('UL', { 17084 className: 'ol-wmsform', 17085 parent: element 17086 }); 17087 var addLine = function(label, val, pholder) { 17088 var li = ol.ext.element.create('LI', { 17089 parent: form 17090 }); 17091 ol.ext.element.create('LABEL', { 17092 html: this.labels[label], 17093 parent: li 17094 }); 17095 if (typeof(val) === 'boolean') { 17096 this._elements[label] = ol.ext.element.create('INPUT', { 17097 type: 'checkbox', 17098 checked: val, 17099 parent: li 17100 }); 17101 } else if (val instanceof Array) { 17102 var sel = this._elements[label] = ol.ext.element.create('SELECT', { 17103 parent: li 17104 }); 17105 val.forEach(function(v) { 17106 ol.ext.element.create('OPTION', { 17107 html: v, 17108 value: v, 17109 parent: sel 17110 }); 17111 }.bind(this)); 17112 } else { 17113 this._elements[label] = ol.ext.element.create('INPUT', { 17114 value: (val===undefined ? '' : val), 17115 placeholder: pholder || '', 17116 type: typeof(val)==='number' ? 'number' : 'text', 17117 parent: li 17118 }); 17119 } 17120 return li; 17121 }.bind(this); 17122 addLine('formTitle'); 17123 addLine('formLayer', '', 'layer1,layer2,...'); 17124 var li = addLine('formMap'); 17125 li.setAttribute('data-param', 'map'); 17126 li = addLine('formStyle'); 17127 li.setAttribute('data-param', 'style'); 17128 addLine('formFormat', ['image/png', 'image/jpeg']); 17129 addLine('formMinZoom', 0); 17130 addLine('formMaxZoom', 20); 17131 li = addLine('formExtent', '', 'xmin,ymin,xmax,ymax'); 17132 li.setAttribute('data-param', 'extent'); 17133 var extent = li.querySelector('input'); 17134 ol.ext.element.create('BUTTON', { 17135 title: this.labels.mapExtent, 17136 click: function() { 17137 extent.value = this.getMap().getView().calculateExtent(this.getMap().getSize()).join(','); 17138 }.bind(this), 17139 parent: li 17140 }); 17141 li = addLine('formProjection', ''); 17142 li.setAttribute('data-param', 'proj'); 17143 addLine('formCrossOrigin', false); 17144 li = addLine('formVersion', '1.3.0'); 17145 li.setAttribute('data-param', 'version'); 17146 addLine('formAttribution', ''); 17147 ol.ext.element.create('BUTTON', { 17148 html: this.get('loadLabel') || 'Load', 17149 click: function() { 17150 var opt = this._getFormOptions(); 17151 var layer = this.getLayerFromOptions(opt); 17152 this.dispatchEvent({ type: 'load', layer: layer, options: opt }); 17153 this._dialog.hide(); 17154 }.bind(this), 17155 parent: form 17156 }); 17157 return element; 17158}; 17159/** Create a new layer using options received by getOptionsFromCap method 17160 * @param {*} options 17161 */ 17162ol.control.WMSCapabilities.prototype.getLayerFromOptions = function (options) { 17163 options.layer.source = new ol.source.TileWMS(options.source); 17164 var layer = new ol.layer.Tile(options.layer); 17165 delete options.layer.source; 17166 return layer; 17167}; 17168/** 17169 * Set the map instance the control is associated with 17170 * and add its controls associated to this map. 17171 * @param {_ol_Map_} map The map instance. 17172 */ 17173ol.control.WMSCapabilities.prototype.setMap = function (map) { 17174 ol.control.Button.prototype.setMap.call(this, map);
17175 if (this._dialog) this._dialog.setMap(map); 17176}; 17177/** Get the dialog 17178 * @returns {ol.control.Dialog} 17179 */ 17180ol.control.WMSCapabilities.prototype.getDialog = function() { 17181 return this._dialog; 17182}; 17183/** Show dialog for url 17184 * @param {string} [url] service url, default ask for an url 17185 * @param {*} options capabilities options 17186 * @param {string} options.map WMS map or get map in url?map=xxx 17187 * @param {string} options.version WMS version (yet only 1.3.0 is implemented), default 1.3.0 17188 * @param {number} options.timeout timout to get the capabilities, default 10000 17189 */ 17190ol.control.WMSCapabilities.prototype.showDialog = function(url, options) { 17191 this.showError(); 17192 if (!this._elements.formProjection.value) { 17193 this._elements.formProjection.value = this.getMap().getView().getProjection().getCode(); 17194 } 17195 if (this._dialog) { 17196 this._dialog.show({ 17197 title: this.get('title')===undefined ? 'WMS' : this.get('title'), 17198 content: this._elements.element 17199 }); 17200 } 17201 this.getCapabilities(url, options); 17202 // Center on selection 17203 var sel = this._elements.select.querySelector('.selected'); 17204 if (sel) { 17205 this._elements.select.scrollTop = sel.offsetTop - 20; 17206 } 17207}; 17208/** Test url and return true if it is a valid url string 17209 * @param {string} url 17210 * @return {bolean} 17211 * @api 17212 */ 17213ol.control.WMSCapabilities.prototype.testUrl = function(url) { 17214 // var pattern = /(https?:\/\/)([\da-z.-]+)\.([a-z]{2,6})([/\w.-]*)*\/?/ 17215 var pattern = new RegExp( 17216 // protocol 17217 '^(https?:\\/\\/)'+ 17218 // domain name 17219 '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ 17220 // OR ip (v4) address 17221 '((\\d{1,3}\\.){3}\\d{1,3}))'+ 17222 // port and path 17223 '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ 17224 // query string 17225 '(\\?[;&a-z\\d%_.~+=\\/-]*)?'+ 17226 // fragment locator 17227 '(\\#[-a-z\\d_]*)?$','i'); 17228 return !!pattern.test(url); 17229}; 17230/** Get Capabilities request parameters 17231 * @param {*} options 17232 */ 17233ol.control.WMSCapabilities.prototype.getRequestParam = function(options) { 17234 return { 17235 SERVICE: 'WMS', 17236 REQUEST: 'GetCapabilities', 17237 VERSION: options.version || '1.3.0' 17238 } 17239}; 17240/** Get WMS capabilities for a server 17241 * @fire load 17242 * @param {string} url service url 17243 * @param {*} options 17244 * @param {string} options.map WMS map or get map in url?map=xxx 17245 * @param {string} [options.version=1.3.0] WMS version (yet only 1.3.0 is implemented), default 1.3.0 17246 * @param {number} [options.timeout=10000] timout to get the capabilities, default 10000 17247 * @param {function} [options.onload] callback function 17248 */ 17249ol.control.WMSCapabilities.prototype.getCapabilities = function(url, options) { 17250 if (!url) return; 17251 if (!this.testUrl(url)) { 17252 this.showError({ 17253 type: 'badUrl' 17254 }) 17255 return; 17256 } 17257 options = options || {}; 17258 // Extract map attributes 17259 url = url.split('?'); 17260 var search = url[1]; 17261 url = url[0]; 17262 // reset 17263 this._elements.formMap.value = ''; 17264 this._elements.formLayer.value = ''; 17265 this._elements.formStyle.value = ''; 17266 this._elements.formTitle.value = ''; 17267 this._elements.formProjection.value = this.getMap().getView().getProjection().getCode(); 17268 this._elements.formFormat.selectedIndex = 0; 17269 var map = options.map || ''; 17270 var optional = {}; 17271 if (search) { 17272 search = search.replace(/^\?/,'').split('&'); 17273 search.forEach(function(s) { 17274 s = s.split('='); 17275 s[1] = decodeURIComponent(s[1] || ''); 17276 if (/^map$/i.test(s[0])) { 17277 map = s[1]; 17278 this._elements.formMap.value = map; 17279 } 17280 if (/^layers$/i.test(s[0])) { 17281 this._elements.formLayer.value = s[1]; 17282 this._elements.formTitle.value = s[1].split(',')[0]; 17283 } 17284 if (/^style$/i.test(s[0])) { 17285 this._elements.formStyle.value = s[1]; 17286 } 17287 if (/^crs$/i.test(s[0])) { 17288 this._elements.formProjection.value = s[1]; 17289 } 17290 if (/^format$/i.test(s[0])) { 17291 for (var o,i=0; o=this._elements.formFormat.options[i]; i++) { 17292 if (o.value===s[1]) { 17293 this._elements.formFormat.selectedIndex = i; 17294 break; 17295 } 17296 } 17297 } 17298 // Check optionals 17299 if (this.get('optional')) { 17300 this.get('optional').split(',').forEach(function(o) { 17301 if (o === s[0]) { 17302 optional[o] = s[1]; 17303 } 17304 }.bind(this)) 17305 } 17306 }.bind(this)) 17307 } 17308 // Get request params 17309 var request = this.getRequestParam(options); 17310 var opt = []; 17311 if (map) { 17312 request.MAP = map; 17313 opt.push('map='+map); 17314 } 17315 for (var o in optional) { 17316 request[o] = optional[o]; 17317 opt.push(o+'='+optional[o]); 17318 } 17319 // Fill form 17320 this._elements.input.value = (url || '') + (opt ? '?'+opt.join('&') : ''); 17321 this.clearForm(); 17322 // Sen drequest 17323 if (this._proxy) { 17324 var q = ''; 17325 for (var r in request) q += (q?'&':'')+r+'='+request[r]; 17326 this._ajax.send(this._proxy, { 17327 url: q 17328 }, {
17329 timeout: options.timeout || 10000, 17330 callback: options.onload, 17331 abort: false 17332 }); 17333 } else { 17334 this._ajax.send(url, request, { 17335 timeout: options.timeout || 10000, 17336 callback: options.onload, 17337 abort: false 17338 }); 17339 } 17340}; 17341/** Display error 17342 * @param {*} error event 17343 */ 17344ol.control.WMSCapabilities.prototype.showError = function(e) { 17345 if (!e) this._elements.error.innerHTML = ''; 17346 else this._elements.error.innerHTML = this.error[e.type] || ('ERROR ('+e.type+')'); 17347 if (e && e.type === 'load') { 17348 this._elements.form.classList.add('visible'); 17349 } else { 17350 this._elements.form.classList.remove('visible'); 17351 } 17352}; 17353/** Clear form 17354 */ 17355ol.control.WMSCapabilities.prototype.clearForm = function() { 17356 this._elements.result.classList.remove('ol-visible') 17357 this.showError(); 17358 this._elements.select.innerHTML = ''; 17359 this._elements.data.innerHTML = ''; 17360 this._elements.preview.src = ''; 17361 this._elements.legend.src = ''; 17362 this._elements.legend.classList.remove('visible'); 17363}; 17364/** Display capabilities in the dialog 17365 * @param {*} caps JSON capabilities 17366 */ 17367ol.control.WMSCapabilities.prototype.showCapabilities = function(caps) { 17368 this._elements.result.classList.add('ol-visible') 17369// console.log(caps) 17370 var list = []; 17371 var addLayers = function(parent, level) { 17372 level = level || 0; 17373 parent.Layer.forEach(function(l) { 17374 if (!l.Attribution) l.Attribution = parent.Attribution; 17375 if (!l.EX_GeographicBoundingBox) l.EX_GeographicBoundingBox = parent.EX_GeographicBoundingBox; 17376 var li = ol.ext.element.create('DIV', { 17377 className: (l.Layer ? 'ol-title ' : '') + 'level-'+level, 17378 html: l.Name || l.Title, 17379 click: function() { 17380 // Reset 17381 this._elements.buttons.innerHTML = ''; 17382 this._elements.data.innerHTML = ''; 17383 this._elements.legend.src = this._elements.preview.src = ''; 17384 this._elements.element.classList.remove('ol-form'); 17385 this.showError(); 17386 // Load layer 17387 var options = this.getOptionsFromCap(l, caps); 17388 var layer = this.getLayerFromOptions(options); 17389 this._currentOptions = options; 17390 // 17391 list.forEach(function(i) { 17392 i.classList.remove('selected'); 17393 }) 17394 li.classList.add('selected'); 17395 // Fill form 17396 if (layer) { 17397 ol.ext.element.create('BUTTON', { 17398 html: this.get('loadLabel') || 'Load', 17399 className: 'ol-load', 17400 click: function() { 17401 this.dispatchEvent({type: 'load', layer: layer, options: options }); 17402 if (this._dialog) this._dialog.hide(); 17403 }.bind(this), 17404 parent: this._elements.buttons 17405 }); 17406 ol.ext.element.create('BUTTON', { 17407 className: 'ol-wmsform', 17408 click: function() { 17409 this._elements.element.classList.toggle('ol-form'); 17410 }.bind(this), 17411 parent: this._elements.buttons 17412 }); 17413 // Show preview 17414 var reso = this.getMap().getView().getResolution(); 17415 var center = this.getMap().getView().getCenter(); 17416 this._elements.preview.src = layer.getPreview(center, reso, this.getMap().getView().getProjection()); 17417 this._img.src = this._elements.preview.src; 17418 // ShowInfo 17419 ol.ext.element.create('p', { 17420 className: 'ol-title', 17421 html: options.data.title, 17422 parent: this._elements.data 17423 }); 17424 ol.ext.element.create('p', { 17425 html: options.data.abstract, 17426 parent: this._elements.data 17427 }); 17428 if (options.data.legend.length) { 17429 this._elements.legend.src = options.data.legend[0]; 17430 this._elements.legend.classList.add('visible'); 17431 } else { 17432 this._elements.legend.src = ''; 17433 this._elements.legend.classList.remove('visible'); 17434 } 17435 } 17436 }.bind(this), 17437 parent: this._elements.select 17438 }); 17439 list.push(li); 17440 if (l.Layer) { 17441 addLayers(l, level+1); 17442 } 17443 }.bind(this)); 17444 }.bind(this); 17445 // Show layers 17446 this._elements.select.innerHTML = ''; 17447 addLayers(caps.Capability.Layer); 17448}; 17449/** Get resolution for a layer 17450 * @param {string} 'min' or 'max' 17451 * @param {*} layer 17452 * @param {number} val 17453 * @return {number} 17454 * @private 17455 */ 17456ol.control.WMSCapabilities.prototype.getLayerResolution = function(m, layer, val) { 17457 var att = m==='min' ? 'MinScaleDenominator' : 'MaxScaleDenominator'; 17458 if (layer[att] !== undefined) return layer[att]/(72/2.54*100); 17459 if (!layer.Layer) return (m==='min' ? 0 : 156543.03392804097); 17460 // Get min / max of contained layers 17461 val = (m==='min' ? 156543.03392804097 : 0); 17462 for (var i=0; i<layer.Layer.length; i++) { 17463 var res = this.getLayerResolution(m, layer.Layer[i], val); 17464 if (res !== undefined) val = Math[m](val, res); 17465 } 17466 return val; 17467}; 17468/** Return a WMS ol.layer.Tile for the given capabilities 17469 * @param {*} caps layer capabilities (read from the capabilities) 17470 * @param {*} parent capabilities 17471 * @return {*} options 17472 */ 17473ol.control.WMSCapabilities.prototype.getOptionsFromCap = function(caps, parent) { 17474 var formats = parent.Capability.Request.GetMap.Format; 17475 var format, i; 17476 // Look for prefered format first 17477 var pref = [/png/,/jpeg/,/gif/]; 17478 for (i=0; i<3; i++) { 17479 for (var f=0; f<formats.length; f++) { 17480 if (pref[i].test(formats[f])) { 17481 format = formats[f]; 17482 break; 17483 } 17484 } 17485 if (format) break; 17486 } 17487 if (!format) format = formats[0];
17488 // Check srs 17489 var srs = this.getMap().getView().getProjection().getCode(); 17490 this.showError(); 17491 var crs = false; 17492 if (!caps.CRS) { 17493 crs = false; 17494 } else if (caps.CRS.indexOf(srs)>=0) { 17495 crs = true; 17496 } else if (caps.CRS.indexOf('EPSG:4326')>=0) { 17497 // try to set EPSG:4326 instead 17498 srs = 'EPSG:4326'; 17499 crs = true; 17500 } else { 17501 this.get('srs').forEach(function(s) { 17502 if (caps.CRS.indexOf(s)>=0) { 17503 srs = s; 17504 crs = true; 17505 } 17506 }) 17507 } 17508 if (!crs) { 17509 this.showError({ type:'srs' }); 17510 if (this.get('trace')) console.log('BAD srs: ', caps.CRS); 17511 } 17512 var bbox = caps.EX_GeographicBoundingBox; 17513 //bbox = ol.proj.transformExtent(bbox, 'EPSG:4326', srs); 17514 if (bbox) bbox = ol.proj.transformExtent(bbox, 'EPSG:4326', this.getMap().getView().getProjection()); 17515 var attributions = []; 17516 if (caps.Attribution) { 17517 attributions.push('<a href="'+encodeURI(caps.Attribution.OnlineResource)+'">© '+caps.Attribution.Title.replace(/</g,'<')+'</a>'); 17518 } 17519 var layer_opt = { 17520 title: caps.Title, 17521 extent: bbox, 17522 queryable: caps.queryable, 17523 abstract: caps.Abstract, 17524 minResolution: this.getLayerResolution('min', caps), 17525 maxResolution: this.getLayerResolution('max', caps) || 156543.03392804097 17526 }; 17527 var source_opt = { 17528 url: parent.Capability.Request.GetMap.DCPType[0].HTTP.Get.OnlineResource, //parent.Service.OnlineResource, 17529 projection: srs, 17530 attributions: attributions, 17531 crossOrigin: this.get('cors') ? 'anonymous' : null, 17532 params: { 17533 'LAYERS': caps.Name, 17534 'FORMAT': format, 17535 'VERSION': parent.version || '1.3.0' 17536 } 17537 } 17538 // Resolution to zoom 17539 var view = new ol.View({ 17540 projection: this.getMap().getView().getProjection() 17541 }) 17542 view.setResolution(layer_opt.minResolution); 17543 var maxZoom = Math.round(view.getZoom()); 17544 view.setResolution(layer_opt.maxResolution); 17545 var minZoom = Math.round(view.getZoom()); 17546 // Fill form 17547 this._fillForm({ 17548 title: layer_opt.title, 17549 layers: source_opt.params.LAYERS, 17550 format: source_opt.params.FORMAT, 17551 minZoom: minZoom, 17552 maxZoom: maxZoom, 17553 extent: bbox ? bbox.join(',') : '', 17554 projection: source_opt.projection, 17555 attribution: source_opt.attributions[0] || '', 17556 version: source_opt.params.VERSION 17557 }); 17558 // Trace 17559 if (this.get('trace')) { 17560 var tso = JSON.stringify([ source_opt ], null, "\t").replace(/\\"/g,'"'); 17561 layer_opt.source = "SOURCE"; 17562 var t = "new ol.layer.Tile (" +JSON.stringify(layer_opt, null, "\t")+ ")" 17563 t = t.replace(/\\"/g,'"') 17564 .replace('"SOURCE"', "new ol.source.TileWMS("+tso+")")
17565 .replace(/\\t/g,"\t").replace(/\\n/g,"\n") 17566 .replace("([\n\t","(") 17567 .replace("}\n])","})"); 17568 console.log(t); 17569 delete layer_opt.source; 17570 } 17571 // Legend ? 17572 var legend = []; 17573 if (caps.Style) { 17574 caps.Style.forEach(function(s) { 17575 if (s.LegendURL) { 17576 legend.push(s.LegendURL[0].OnlineResource); 17577 } 17578 }); 17579 } 17580 return ({ 17581 layer: layer_opt, 17582 source: source_opt, 17583 data: { 17584 title: caps.Title, 17585 abstract: caps.Abstract, 17586 logo: caps.Attribution && caps.Attribution.LogoURL ? caps.Attribution.LogoURL.OnlineResource : undefined, 17587 keyword: caps.KeywordList, 17588 legend: legend, 17589 opaque: caps.opaque, 17590 queryable: caps.queryable 17591 } 17592 }); 17593}; 17594/** Get WMS options from control form 17595 * @return {*} options 17596 * @private 17597 */ 17598ol.control.WMSCapabilities.prototype._getFormOptions = function() { 17599 var minZoom = parseInt(this._elements.formMinZoom.value); 17600 var maxZoom = parseInt(this._elements.formMaxZoom.value); 17601 var view = new ol.View({ 17602 projection: this.getMap().getView().getProjection() 17603 }) 17604 view.setZoom(minZoom); 17605 var maxResolution = view.getResolution(); 17606 view.setZoom(maxZoom); 17607 var minResolution = view.getResolution(); 17608 var ext = []; 17609 if (this._elements.formExtent.value) { 17610 this._elements.formExtent.value.split(',').forEach(function(b) { 17611 ext.push(parseFloat(b)); 17612 }) 17613 } 17614 if (ext.length !== 4) ext = undefined; 17615 var attributions = [] 17616 if (this._elements.formAttribution.value) attributions.push(this._elements.formAttribution.value); 17617 var options = { 17618 layer: { 17619 title: this._elements.formTitle.value, 17620 extent: ext, 17621 maxResolution: maxResolution, 17622 minResolution: minResolution 17623 }, 17624 source: { 17625 url: this._elements.input.value, 17626 crossOrigin: this._elements.formCrossOrigin.checked ? 'anonymous' : null, 17627 projection: this._elements.formProjection.value, 17628 attributions: attributions, 17629 params: { 17630 FORMAT: this._elements.formFormat.options[this._elements.formFormat.selectedIndex].value, 17631 LAYERS: this._elements.formLayer.value, 17632 VERSION: this._elements.formVersion.value 17633 } 17634 }, 17635 data: { 17636 title: this._elements.formTitle.value 17637 } 17638 } 17639 if (this._elements.formMap.value) options.source.params.MAP = this._elements.formMap.value; 17640 return options; 17641}; 17642/** Fill dialog form 17643 * @private 17644 */ 17645ol.control.WMSCapabilities.prototype._fillForm = function(opt) { 17646 this._elements.formTitle.value = opt.title; 17647 this._elements.formLayer.value = opt.layers; 17648 this._elements.formStyle.value = opt.style; 17649 var o, i; 17650 for (i=0; o=this._elements.formFormat.options[i]; i++) { 17651 if (o.value === opt.format) { 17652 this._elements.formFormat.selectedIndex = i; 17653 break; 17654 } 17655 } 17656 this._elements.formExtent.value = opt.extent || ''; 17657 this._elements.formMaxZoom.value = opt.maxZoom; 17658 this._elements.formMinZoom.value = opt.minZoom; 17659 this._elements.formProjection.value = opt.projection; 17660 this._elements.formAttribution.value = opt.attribution; 17661 this._elements.formVersion.value = opt.version; 17662}; 17663/** Load a layer using service 17664 * @param {string} url service url 17665 * @param {string} layername 17666 * @param {function} [onload] callback function (or listen to 'load' event) 17667 */ 17668ol.control.WMSCapabilities.prototype.loadLayer = function(url, layerName, onload) { 17669 this.getCapabilities(url, { 17670 onload: function(cap) { 17671 if (cap) { 17672 cap.Capability.Layer.Layer.forEach(function(l) { 17673 if (l.Name===layerName || l.Identifier===layerName) { 17674 var options = this.getOptionsFromCap(l, cap); 17675 var layer = this.getLayerFromOptions(options); 17676 this.dispatchEvent({ type: 'load', layer: layer, options: options }); 17677 if (typeof(onload) === 'function') onload({ layer: layer, options: options }); 17678 } 17679 }.bind(this)) 17680 } else { 17681 this.dispatchEvent({ type: 'load', error: true }); 17682 } 17683 }.bind(this) 17684 }); 17685}; 17686 17687/** WMTSCapabilities 17688 * @constructor 17689 * @fires load 17690 * @fires capabilities 17691 * @extends {ol.control.WMSCapabilities} 17692 * @param {*} options 17693 * @param {string|Element} [options.target] the target to set the dialog, use document.body to have fullwindow dialog 17694 * @param {string} [options.proxy] proxy to use when requesting Getcapabilites, default none (suppose the service use CORS) 17695 * @param {string} [options.placeholder='service url...'] input placeholder, default 'service url...' 17696 * @param {string} [options.title=WMTS] dialog title, default 'WMTS' 17697 * @param {string} [options.searchLabel='search'] Label for search button, default 'search' 17698 * @param {string} [options.loadLabel='load'] Label for load button, default 'load' 17699 * @param {Array<string>} [options.srs] an array of supported srs, default map projection code or 'EPSG:3857' 17700 * @param {number} [options.timeout=1000] Timeout for getCapabilities request, default 1000 17701 * @param {boolean} [options.cors=false] Use CORS, default false 17702 * @param {string} [options.optional] a list of optional url properties (when set in the request url), separated with ',' 17703 * @param {boolean} [options.trace=false] Log layer info, default false 17704 * @param {*} [options.services] a key/url object of services for quick access in a menu 17705 */ 17706ol.control.WMTSCapabilities = function (options) { 17707 options = options || {}; 17708 options.title = options.title || 'WMTS'; 17709 ol.control.WMSCapabilities.call(this, options); 17710 this.getDialog().element.classList.add('ol-wmtscapabilities'); 17711}; 17712ol.ext.inherits(ol.control.WMTSCapabilities, ol.control.WMSCapabilities); 17713/** Get service parser 17714 * @private 17715 */ 17716ol.control.WMTSCapabilities.prototype._getParser = function() { 17717 var pars = new ol.format.WMTSCapabilities(); 17718 return { 17719 read: function(data) { 17720 var resp = pars.read(data); 17721 resp.Capability = { 17722 Layer: resp.Contents, 17723 } 17724 // Generic attribution for layers 17725 resp.Capability.Layer.Attribution = { 17726 Title: resp.ServiceProvider.ProviderName 17727 } 17728 // Remove non image format 17729 var layers = []; 17730 resp.Contents.Layer.forEach(function(l) { 17731 if (l.Format && /jpeg|png/.test(l.Format[0])) { 17732 layers.push(l); 17733 } 17734 }) 17735 resp.Contents.Layer = layers; 17736 return resp; 17737 }.bind(this) 17738 } 17739}; 17740/** Get Capabilities request parameters 17741 * @param {*} options 17742 */ 17743ol.control.WMTSCapabilities.prototype.getRequestParam = function(options) { 17744 return { 17745 SERVICE: 'WMTS', 17746 REQUEST: 'GetCapabilities', 17747 VERSION: options.version || '1.0.0'
17748 } 17749}; 17750/** Get tile grid options only for EPSG:3857 projection 17751 * @returns {*} 17752 * @private 17753 */ 17754ol.control.WMTSCapabilities.prototype._getTG = function(tileMatrixSet, minZoom, maxZoom) { 17755 var matrixIds = new Array(); 17756 var resolutions = new Array(); 17757 var size = ol.extent.getWidth(ol.proj.get('EPSG:3857').getExtent()) / 256; 17758 for (var z=0; z <= (maxZoom ? maxZoom : 20) ; z++) { 17759 var id = tileMatrixSet !== 'PM' ? tileMatrixSet+':'+z : z; 17760 matrixIds[z] = id ; 17761 resolutions[z] = size / Math.pow(2, z); 17762 } 17763 return { 17764 origin: [-20037508, 20037508], 17765 resolutions: resolutions, 17766 matrixIds: matrixIds, 17767 minZoom: (minZoom ? minZoom : 0) 17768 } 17769}; 17770/** Get WMTS tile grid (only EPSG:3857) 17771 * @param {sting} tileMatrixSet 17772 * @param {number} minZoom 17773 * @param {number} maxZoom 17774 * @returns {ol.tilegrid.WMTS} 17775 * @private 17776 */ 17777ol.control.WMTSCapabilities.prototype.getTileGrid = function(tileMatrixSet, minZoom, maxZoom) { 17778 return new ol.tilegrid.WMTS(this._getTG(tileMatrixSet, minZoom, maxZoom)); 17779}; 17780/** Return a WMTS options for the given capabilities 17781 * @param {*} caps layer capabilities (read from the capabilities) 17782 * @param {*} parent capabilities 17783 * @return {*} options 17784 */ 17785ol.control.WMTSCapabilities.prototype.getOptionsFromCap = function(caps, parent) { 17786 var bbox = caps.WGS84BoundingBox; 17787 if (bbox) bbox = ol.proj.transformExtent(bbox, 'EPSG:4326', this.getMap().getView().getProjection()); 17788 // Tilematrix zoom 17789 var minZoom = Infinity, maxZoom = -Infinity; 17790 var tmatrix; 17791 caps.TileMatrixSetLink.forEach(function(tm) { 17792 if (tm.TileMatrixSet === 'PM' || tm.TileMatrixSet === 'EPSG:3857') { 17793 tmatrix = tm; 17794 caps.TileMatrixSet = tm.TileMatrixSet; 17795 } 17796 }); 17797 if (!tmatrix) { 17798 this.showError({ type: 'TileMatrix' }); 17799 return; 17800 } 17801 tmatrix.TileMatrixSetLimits.forEach(function(tm) { 17802 var zoom = tm.TileMatrix.split(':').pop(); 17803 minZoom = Math.min(minZoom, parseInt(zoom)); 17804 maxZoom = Math.max(maxZoom, parseInt(zoom)); 17805 }); 17806 var view = new ol.View(); 17807 view.setZoom(minZoom); 17808 var layer_opt = { 17809 title: caps.Title, 17810 extent: bbox, 17811 abstract: caps.Abstract, 17812 maxResolution: view.getResolution() 17813 }; 17814 var source_opt = { 17815 url: parent.OperationsMetadata.GetTile.DCP.HTTP.Get[0].href, 17816 layer: caps.Identifier, 17817 matrixSet: caps.TileMatrixSet, 17818 format: caps.Format[0] || 'image/jpeg', 17819 projection: 'EPSG:3857', 17820 //tileGrid: tg, 17821 minZoom: minZoom, 17822 maxZoom: maxZoom, 17823 style: caps.Style ? caps.Style[0].Identifier : 'normal', 17824 attributions: caps.Attribution.Title, 17825 crossOrigin: this.get('cors') ? 'anonymous' : null, 17826 wrapX: (this.get('wrapX') !== false), 17827 }; 17828 // Fill form 17829 this._fillForm({ 17830 title: layer_opt.title, 17831 layers: source_opt.layer, 17832 style: source_opt.style, 17833 format: source_opt.format, 17834 minZoom: minZoom, 17835 maxZoom: maxZoom, 17836 extent: bbox ? bbox.join(',') : '', 17837 projection: source_opt.projection, 17838 attribution: source_opt.attributions || '', 17839 version: '1.0.0' 17840 }); 17841 // Trace 17842 if (this.get('trace')) { 17843 // Source 17844 source_opt.tileGrid = 'TILEGRID'; 17845 var tso = JSON.stringify([ source_opt ], null, "\t").replace(/\\"/g,'"'); 17846 tso = tso.replace('"TILEGRID"', 'new ol.tilegrid.WMTS(' 17847 + JSON.stringify(this._getTG(source_opt.matrixSet, source_opt.minZoom, source_opt.maxZoom), null, '\t').replace(/\n/g, '\n\t\t') 17848 + ')' 17849 ); 17850 delete source_opt.tileGrid; 17851 // Layer 17852 layer_opt.source = "SOURCE"; 17853 var t = "new ol.layer.Tile (" +JSON.stringify(layer_opt, null, "\t")+ ")" 17854 t = t.replace(/\\"/g,'"') 17855 .replace('"SOURCE"', "new ol.source.WMTS("+tso+")")
17856 .replace(/\\t/g,"\t").replace(/\\n/g,"\n") 17857 .replace(/"tileGrid": {/g, '"tileGrid": new ol.tilegrid.WMTS({') 17858 .replace(/},\n(\t*)"style"/g, '}),\n$1"style"') 17859 .replace("([\n\t","(") 17860 .replace("}\n])","})"); 17861 console.log(t); 17862 delete layer_opt.source; 17863 } 17864 var returnedLegend=undefined; 17865 if(caps.Style && caps.Style[0] && caps.Style[0].LegendURL && caps.Style[0].LegendURL[0] ) 17866 returnedLegend=caps.Style[0].LegendURL[0].href; 17867 return ({ 17868 layer: layer_opt, 17869 source: source_opt, 17870 data: { 17871 title: caps.Title, 17872 abstract: caps.Abstract, 17873 legend: returnedLegend, 17874 } 17875 }); 17876}; 17877/** Get WMS options from control form 17878 * @return {*} original original options 17879 * @return {*} options 17880 * @private 17881 */ 17882ol.control.WMTSCapabilities.prototype._getFormOptions = function() { 17883 var options = this._currentOptions || {}; 17884 if (!options.layer) options.layer = {}; 17885 if (!options.source) options.source = {}; 17886 if (!options.data) options.data = {}; 17887 var minZoom = parseInt(this._elements.formMinZoom.value) || 0; 17888 var maxZoom = parseInt(this._elements.formMaxZoom.value) || 20; 17889 var ext = []; 17890 if (this._elements.formExtent.value) { 17891 this._elements.formExtent.value.split(',').forEach(function(b) { 17892 ext.push(parseFloat(b)); 17893 }) 17894 } 17895 if (ext.length !== 4) ext = undefined; 17896 var attributions = [] 17897 if (this._elements.formAttribution.value) attributions.push(this._elements.formAttribution.value); 17898 var view = new ol.View({ 17899 projection: this.getMap().getView().getProjection() 17900 }) 17901 view.setZoom(minZoom); 17902 var layer_opt = { 17903 title: this._elements.formTitle.value, 17904 extent: ext, 17905 abstract: options.layer.abstract || '', 17906 maxResolution: view.getResolution() 17907 } 17908 var source_opt = { 17909 url: this._elements.input.value, 17910 layer: this._elements.formLayer.value, 17911 matrixSet: options.source.matrixSet || 'PM', 17912 format: this._elements.formFormat.options[this._elements.formFormat.selectedIndex].value, 17913 projection: 'EPSG:3857', 17914 minZoom: minZoom, 17915 maxZoom: maxZoom, 17916 // tileGrid: this._getTG(options.source.matrixSet || 'PM', minZoom, maxZoom), 17917 style: this._elements.formStyle.value || 'normal', 17918 attributions: attributions, 17919 crossOrigin: this._elements.formCrossOrigin.checked ? 'anonymous' : null, 17920 wrapX: (this.get('wrapX') !== false), 17921 } 17922 return ({ 17923 layer: layer_opt, 17924 source: source_opt, 17925 data: { 17926 title: this._elements.formTitle.value, 17927 abstract: options.data.abstract, 17928 legend: options.data.legend, 17929 } 17930 }); 17931}; 17932/** Create a new layer using options received by getOptionsFromCap method 17933 * @param {*} options 17934 */ 17935ol.control.WMTSCapabilities.prototype.getLayerFromOptions = function (options) { 17936 if (!options) return; 17937 options.source.tileGrid = this.getTileGrid(options.source.matrixSet, options.source.minZoom, options.source.maxZoom); 17938 options.layer.source = new ol.source.WMTS(options.source); 17939 var layer = new ol.layer.Tile(options.layer); 17940 // Restore options 17941 delete options.layer.source; 17942 delete options.source.tileGrid; 17943 return layer; 17944}; 17945 17946/* 17947 Copyright (c) 2016 Jean-Marc VIGLINO, 17948 released under the CeCILL license (http://www.cecill.info/). 17949*/ 17950/** Feature animation base class 17951 * Use the {@link ol.Map#animateFeature} or {@link ol.layer.Vector#animateFeature} to animate a feature 17952 * on postcompose in a map or a layer 17953* @constructor 17954* @fires animationstart 17955* @fires animating 17956* @fires animationend 17957* @param {ol.featureAnimationOptions} options 17958* @param {Number} options.duration duration of the animation in ms, default 1000 17959* @param {bool} options.revers revers the animation direction 17960* @param {Number} options.repeat number of time to repeat the animation, default 0 17961* @param {ol.style.Style} options.hiddenStyle a style to display the feature when playing the animation 17962* to be used to make the feature selectable when playing animation 17963* (@see {@link ../examples/map.featureanimation.select.html}), default the feature 17964* will be hidden when playing (and not selectable) 17965* @param {ol.easing.Function} options.fade an easing function used to fade in the feature, default none 17966* @param {ol.easing.Function} options.easing an easing function for the animation, default ol.easing.linear 17967*/ 17968ol.featureAnimation = function(options) { 17969 options = options || {}; 17970 this.duration_ = typeof (options.duration)=='number' ? (options.duration>=0 ? options.duration : 0) : 1000; 17971 this.fade_ = typeof(options.fade) == 'function' ? options.fade : null; 17972 this.repeat_ = Number(options.repeat); 17973 var easing = typeof(options.easing) =='function' ? options.easing : ol.easing.linear; 17974 if (options.revers) this.easing_ = function(t) { return (1 - easing(t)); }; 17975 else this.easing_ = easing; 17976 this.hiddenStyle = options.hiddenStyle; 17977 ol.Object.call(this); 17978}; 17979ol.ext.inherits(ol.featureAnimation, ol.Object); 17980/** Hidden style: a transparent style 17981 */ 17982ol.featureAnimation.hiddenStyle = new ol.style.Style({ 17983 image: new ol.style.Circle({}), 17984 stroke: new ol.style.Stroke({ 17985 color: 'transparent' 17986 }) 17987}); 17988/** Draw a geometry 17989* @param {olx.animateFeatureEvent} e 17990* @param {ol.geom} geom geometry for shadow 17991* @param {ol.geom} shadow geometry for shadow (ie. style with zIndex = -1) 17992* @private 17993*/ 17994ol.featureAnimation.prototype.drawGeom_ = function (e, geom, shadow) { 17995 if (this.fade_) { 17996 e.context.globalAlpha = this.fade_(1-e.elapsed); 17997 } 17998 var style = e.style; 17999 for (var i=0; i<style.length; i++) { 18000 // Prevent crach if the style is not ready (image not loaded) 18001 try { 18002 var vectorContext = e.vectorContext || ol.render.getVectorContext(e); 18003 var s = ol.ext.getVectorContextStyle(e, style[i]); 18004 vectorContext.setStyle(s); 18005 if (s.getZIndex()<0) vectorContext.drawGeometry(shadow||geom); 18006 else vectorContext.drawGeometry(geom); 18007 } catch(e) { /* ok */ } 18008 } 18009}; 18010/** Function to perform manipulations onpostcompose. 18011 * This function is called with an ol.featureAnimationEvent argument. 18012 * The function will be overridden by the child implementation. 18013 * Return true to keep this function for the next frame, false to remove it. 18014 * @param {ol.featureAnimationEvent} e 18015 * @return {bool} true to continue animation. 18016 * @api 18017 */ 18018ol.featureAnimation.prototype.animate = function (/* e */) { 18019 return false;
18020}; 18021/** An animation controler object an object to control animation with start, stop and isPlaying function. 18022 * To be used with {@link olx.Map#animateFeature} or {@link ol.layer.Vector#animateFeature} 18023 * @typedef {Object} animationControler 18024 * @property {function} start - start animation. 18025 * @property {function} stop - stop animation option arguments can be passed in animationend event. 18026 * @property {function} isPlaying - return true if animation is playing. 18027 */ 18028/** Animate feature on a map 18029 * @function 18030 * @param {ol.Feature} feature Feature to animate 18031 * @param {ol.featureAnimation|Array<ol.featureAnimation>} fanim the animation to play 18032 * @return {animationControler} an object to control animation with start, stop and isPlaying function 18033 */ 18034ol.Map.prototype.animateFeature = function(feature, fanim) { 18035 // Get or create an animation layer associated with the map 18036 var layer = this._featureAnimationLayer; 18037 if (!layer) { 18038 layer = this._featureAnimationLayer = new ol.layer.Vector({ source: new ol.source.Vector() }); 18039 layer.setMap(this); 18040 } 18041 // Animate feature on this layer 18042 layer.getSource().addFeature(feature); 18043 var listener = fanim.on('animationend', function(e) { 18044 if (e.feature===feature) { 18045 // Remove feature on end 18046 layer.getSource().removeFeature(feature); 18047 ol.Observable.unByKey(listener); 18048 } 18049 }); 18050 layer.animateFeature(feature, fanim); 18051}; 18052/** Animate feature on a vector layer 18053 * @fires animationstart, animationend 18054 * @param {ol.Feature} feature Feature to animate 18055 * @param {ol.featureAnimation|Array<ol.featureAnimation>} fanim the animation to play 18056 * @param {boolean} useFilter use the filters of the layer 18057 * @return {animationControler} an object to control animation with start, stop and isPlaying function 18058 */ 18059ol.layer.Base.prototype.animateFeature = function(feature, fanim, useFilter) { 18060 var self = this; 18061 var listenerKey; 18062 // Save style 18063 var style = feature.getStyle(); 18064 var flashStyle = style || (this.getStyleFunction ? this.getStyleFunction()(feature) : null); 18065 if (!flashStyle) flashStyle=[]; 18066 if (!(flashStyle instanceof Array)) flashStyle = [flashStyle]; 18067 // Structure pass for animating 18068 var event = { 18069 // Frame context 18070 vectorContext: null, 18071 frameState: null, 18072 start: 0, 18073 time: 0, 18074 elapsed: 0, 18075 extent: false, 18076 // Feature information 18077 feature: feature, 18078 geom: feature.getGeometry(), 18079 typeGeom: feature.getGeometry().getType(), 18080 bbox: feature.getGeometry().getExtent(), 18081 coord: ol.extent.getCenter(feature.getGeometry().getExtent()), 18082 style: flashStyle 18083 }; 18084 if (!(fanim instanceof Array)) fanim = [fanim]; 18085 // Remove null animations 18086 for (var i=fanim.length-1; i>=0; i--) { 18087 if (fanim[i].duration_===0) fanim.splice(i,1); 18088 } 18089 var nb=0, step = 0; 18090 // Filter availiable on the layer 18091 var filters = (useFilter && this.getFilters) ? this.getFilters() : []; 18092 function animate(e) { 18093 event.type = e.type; 18094 try { 18095 event.vectorContext = e.vectorContext || ol.render.getVectorContext(e); 18096 } catch(e) { /* nothing todo */ } 18097 event.frameState = e.frameState; 18098 event.inversePixelTransform = e.inversePixelTransform; 18099 if (!event.extent) { 18100 event.extent = e.frameState.extent; 18101 event.start = e.frameState.time; 18102 event.context = e.context; 18103 } 18104 event.time = e.frameState.time - event.start; 18105 event.elapsed = event.time / fanim[step].duration_; 18106 if (event.elapsed > 1) event.elapsed = 1; 18107 // Filter 18108 e.context.save(); 18109 filters.forEach(function(f) { 18110 if (f.get('active')) f.precompose(e); 18111 }); 18112 if (this.getOpacity) { 18113 e.context.globalAlpha = this.getOpacity(); 18114 } 18115 // Stop animation? 18116 if (!fanim[step].animate(event)) { 18117 nb++; 18118 // Repeat animation 18119 if (nb < fanim[step].repeat_) { 18120 event.extent = false; 18121 } else if (step < fanim.length-1) { 18122 // newt step 18123 fanim[step].dispatchEvent({ type:'animationend', feature: feature }); 18124 step++; 18125 nb=0; 18126 event.extent = false; 18127 } else { 18128 // the end 18129 stop(); 18130 } 18131 } else { 18132 var animEvent = { 18133 type: 'animating', 18134 step: step, 18135 start: event.start, 18136 time: event.time, 18137 elapsed: event.elapsed, 18138 rotation: event.rotation||0, 18139 geom: event.geom, 18140 coordinate: event.coord, 18141 feature: feature 18142 }; 18143 fanim[step].dispatchEvent(animEvent);
18144 self.dispatchEvent(animEvent); 18145 } 18146 filters.forEach(function(f) { 18147 if (f.get('active')) f.postcompose(e); 18148 }); 18149 e.context.restore(); 18150 // tell OL3 to continue postcompose animation 18151 e.frameState.animate = true; 18152 } 18153 // Stop animation 18154 function stop(options) { 18155 ol.Observable.unByKey(listenerKey); 18156 listenerKey = null; 18157 feature.setStyle(style); 18158 // Send event 18159 var event = { type:'animationend', feature: feature }; 18160 if (options) { 18161 for (var i in options) if (options.hasOwnProperty(i)) { 18162 event[i] = options[i]; 18163 } 18164 } 18165 fanim[step].dispatchEvent(event); 18166 self.dispatchEvent(event); 18167 } 18168 // Launch animation 18169 function start(options) { 18170 if (fanim.length && !listenerKey) { 18171 listenerKey = self.on(['postcompose','postrender'], animate.bind(self)); 18172 // map or layer? 18173 if (self.renderSync) { 18174 try { self.renderSync(); } catch(e) { /* ok */ } 18175 } else { 18176 self.changed(); 18177 } 18178 // Hide feature while animating 18179 feature.setStyle(fanim[step].hiddenStyle || ol.featureAnimation.hiddenStyle); 18180 // Send event 18181 var event = { type:'animationstart', feature: feature }; 18182 if (options) { 18183 for (var i in options) if (options.hasOwnProperty(i)) { 18184 event[i] = options[i]; 18185 } 18186 } 18187 fanim[step].dispatchEvent(event); 18188 self.dispatchEvent(event); 18189 } 18190 } 18191 start(); 18192 // Return animation controler 18193 return { 18194 start: start, 18195 stop: stop, 18196 isPlaying: function() { return (!!listenerKey); } 18197 }; 18198}; 18199 18200/* 18201 Copyright (c) 2016 Jean-Marc VIGLINO, 18202 released under the CeCILL license (http://www.cecill.info/). 18203*/ 18204/** Blink a feature 18205 * @constructor 18206 * @extends {ol.featureAnimation} 18207 * @param {ol.featureAnimationOptions} options 18208 * @param {Number} options.nb number of blink, default 10 18209 */ 18210ol.featureAnimation.Blink = function(options) { 18211 ol.featureAnimation.call(this, options); 18212 this.set('nb', options.nb || 10) 18213} 18214ol.ext.inherits(ol.featureAnimation.Blink, ol.featureAnimation); 18215/** Animate: Show or hide feature depending on the laptimes 18216* @param {ol.featureAnimationEvent} e 18217*/ 18218ol.featureAnimation.Blink.prototype.animate = function (e) { 18219 if (!(Math.round(this.easing_(e.elapsed)*this.get('nb'))%2)) { 18220 this.drawGeom_(e, e.geom); 18221 } 18222 return (e.time <= this.duration_); 18223} 18224 18225/* 18226 Copyright (c) 2016 Jean-Marc VIGLINO, 18227 released under the CeCILL license (http://www.cecill.info/). 18228*/ 18229/** Bounce animation: 18230 * @constructor 18231 * @extends {ol.featureAnimation} 18232 * @param {ol.featureAnimationBounceOptions} options 18233 * @param {Integer} options.bounce number of bounce, default 3 18234 * @param {Integer} options.amplitude bounce amplitude,default 40 18235 * @param {ol.easing} options.easing easing used for decaying amplitude, use function(){return 0} for no decay, default ol.easing.linear 18236 * @param {Integer} options.duration duration in ms, default 1000 18237 */ 18238ol.featureAnimation.Bounce = function(options) 18239{ options = options || {}; 18240 ol.featureAnimation.call(this, options); 18241 this.amplitude_ = options.amplitude || 40; 18242 this.bounce_ = -Math.PI*(options.bounce || 3); 18243} 18244ol.ext.inherits(ol.featureAnimation.Bounce, ol.featureAnimation); 18245/** Animate 18246* @param {ol.featureAnimationEvent} e 18247*/ 18248ol.featureAnimation.Bounce.prototype.animate = function (e) 18249{ // Animate 18250 var flashGeom = e.geom.clone(); 18251 /* 18252 var t = this.easing_(e.elapsed) 18253 t = Math.abs(Math.sin(this.bounce_*t)) * this.amplitude_ * (1-t) * e.frameState.viewState.resolution; 18254 */ 18255 var t = Math.abs(Math.sin(this.bounce_*e.elapsed)) * this.amplitude_ * (1-this.easing_(e.elapsed)) * e.frameState.viewState.resolution; 18256 flashGeom.translate(0, t); 18257 this.drawGeom_(e, flashGeom, e.geom); 18258 return (e.time <= this.duration_); 18259} 18260 18261/* 18262 Copyright (c) 2016 Jean-Marc VIGLINO, 18263 released under the CeCILL license (http://www.cecill.info/). 18264*/ 18265/** Drop animation: drop a feature on the map 18266 * @constructor 18267 * @extends {ol.featureAnimation} 18268 * @param {ol.featureAnimationDropOptions} options 18269 * @param {Number} options.speed speed of the feature if 0 the duration parameter will be used instead, default 0 18270 * @param {Number} options.side top or bottom, default top 18271 */ 18272ol.featureAnimation.Drop = function(options) 18273{ options = options || {}; 18274 this.speed_ = options.speed || 0; 18275 ol.featureAnimation.call(this, options); 18276 this.side_ = options.side || 'top'; 18277} 18278ol.ext.inherits(ol.featureAnimation.Drop, ol.featureAnimation); 18279/** Animate 18280* @param {ol.featureAnimationEvent} e 18281*/ 18282ol.featureAnimation.Drop.prototype.animate = function (e) 18283{ // First time > calculate duration / speed 18284 if (!e.time) 18285 { var angle = e.frameState.viewState.rotation; 18286 var s = e.frameState.size[1] * e.frameState.viewState.resolution; 18287 if (this.side_!='top') s *= -1; 18288 this.dx = -Math.sin(angle)*s;
18289 this.dy = Math.cos(angle)*s; 18290 if (this.speed_) 18291 { this.duration_ = s/this.speed_/e.frameState.viewState.resolution; 18292 } 18293 } 18294 // Animate 18295 var flashGeom = e.geom.clone(); 18296 flashGeom.translate( 18297 this.dx*(1-this.easing_(e.elapsed)), 18298 this.dy*(1-this.easing_(e.elapsed)) 18299 ); 18300 this.drawGeom_(e, flashGeom, e.geom); 18301 return (e.time <= this.duration_); 18302} 18303 18304/* 18305 Copyright (c) 2016 Jean-Marc VIGLINO, 18306 released under the CeCILL license (http://www.cecill.info/). 18307*/ 18308/** Fade animation: feature fade in 18309 * @constructor 18310 * @extends {ol.featureAnimation} 18311 * @param {ol.featureAnimationOptions} options 18312 */ 18313ol.featureAnimation.Fade = function(options) 18314{ options = options || {}; 18315 this.speed_ = options.speed || 0; 18316 ol.featureAnimation.call(this, options); 18317} 18318ol.ext.inherits(ol.featureAnimation.Fade, ol.featureAnimation); 18319/** Animate 18320* @param {ol.featureAnimationEvent} e 18321*/ 18322ol.featureAnimation.Fade.prototype.animate = function (e) 18323{ e.context.globalAlpha = this.easing_(e.elapsed); 18324 this.drawGeom_(e, e.geom); 18325 return (e.time <= this.duration_); 18326} 18327 18328/* 18329 Copyright (c) 2016 Jean-Marc VIGLINO, 18330 released under the CeCILL license (http://www.cecill.info/). 18331*/ 18332/** Do nothing for a given duration 18333 * @constructor 18334 * @extends {ol.featureAnimation} 18335 * @param {ol.featureAnimationShowOptions} options 18336 * 18337 */ 18338ol.featureAnimation.None = function(options) 18339{ ol.featureAnimation.call(this, options); 18340}; 18341ol.ext.inherits(ol.featureAnimation.None, ol.featureAnimation); 18342/** Animate: do nothing during the laps time 18343* @param {ol.featureAnimationEvent} e 18344*/ 18345ol.featureAnimation.None.prototype.animate = function (e) 18346{ 18347 return (e.time <= this.duration_); 18348}; 18349 18350/* 18351 Copyright (c) 2016 Jean-Marc VIGLINO, 18352 released under the CeCILL license (http://www.cecill.info/). 18353*/ 18354/** Do nothing 18355 * @constructor 18356 * @extends {ol.featureAnimation} 18357 */ 18358ol.featureAnimation.Null = function() { 18359 ol.featureAnimation.call(this, { duration:0 }); 18360}; 18361ol.ext.inherits(ol.featureAnimation.Null, ol.featureAnimation); 18362 18363/* 18364 Copyright (c) 2016-2018 Jean-Marc VIGLINO, 18365 released under the CeCILL license (http://www.cecill.info/). 18366*/ 18367/** Path animation: feature follow a path 18368 * @constructor 18369 * @extends {ol.featureAnimation} 18370 * @param {ol.featureAnimationPathOptions} options extend ol.featureAnimation options 18371 * @param {Number} options.speed speed of the feature, if 0 the duration parameter will be used instead, default 0 18372 * @param {Number|boolean} options.rotate rotate the symbol when following the path, true or the initial rotation, default false 18373 * @param {ol.geom.LineString|ol.Feature} options.path the path to follow 18374 * @param {Number} options.duration duration of the animation in ms 18375 */ 18376ol.featureAnimation.Path = function(options){ 18377 options = options || {}; 18378 ol.featureAnimation.call(this, options); 18379 this.speed_ = options.speed || 0; 18380 this.path_ = options.path; 18381 switch (options.rotate) { 18382 case true: 18383 case 0: 18384 this.rotate_ = 0; 18385 break; 18386 default: 18387 this.rotate_ = options.rotate || false; 18388 break; 18389 } 18390 if (this.path_ && this.path_.getGeometry) this.path_ = this.path_.getGeometry(); 18391 if (this.path_ && this.path_.getLineString) this.path_ = this.path_.getLineString(); 18392 if (this.path_.getLength) { 18393 this.dist_ = this.path_.getLength() 18394 if (this.path_ && this.path_.getCoordinates) this.path_ = this.path_.getCoordinates(); 18395 } else { 18396 this.dist_ = 0; 18397 } 18398 if (this.speed_>0) this.duration_ = this.dist_/this.speed_; 18399} 18400ol.ext.inherits(ol.featureAnimation.Path, ol.featureAnimation); 18401/** Animate 18402* @param {ol.featureAnimationEvent} e 18403*/ 18404ol.featureAnimation.Path.prototype.animate = function (e) { 18405 // First time 18406 if (!e.time) { 18407 if (!this.dist_) return false; 18408 } 18409 var dmax = this.dist_*this.easing_(e.elapsed); 18410 var p0, p, s, dx,dy, dl, d = 0; 18411 p = this.path_[0]; 18412 // Linear interpol 18413 for (var i = 1; i<this.path_.length; i++) { 18414 p0 = p; 18415 p = this.path_[i]; 18416 dx = p[0]-p0[0]; 18417 dy = p[1]-p0[1]; 18418 dl = Math.sqrt(dx*dx+dy*dy); 18419 if (dl && d+dl>=dmax) { 18420 s = (dmax-d)/dl; 18421 p = [ p0[0] + (p[0]-p0[0])*s, p0[1] + (p[1]-p0[1])*s]; 18422 break; 18423 } 18424 d += dl; 18425 } 18426 // Rotate symbols 18427 var style = e.style; 18428 e.rotation = Math.PI/2 + Math.atan2(p0[1] - p[1], p0[0] - p[0]); 18429 if (this.rotate_!==false) { 18430 var st = [] 18431 var angle = this.rotate_ - e.rotation + e.frameState.viewState.rotation; 18432 e.rotation = Math.PI/2 + Math.atan2(p0[1] - p[1], p0[0] - p[0]); 18433 for (var k=0; s=e.style[k]; k++) { 18434 if (s.getImage()) { 18435 //s = s.clone(); 18436 s.getImage().setRotation(angle); 18437 } 18438 st.push(s); 18439 } 18440 // Rotated style 18441 e.style = st; 18442 } 18443 e.geom.setCoordinates(p); 18444 // Animate 18445 this.drawGeom_(e, e.geom); 18446 // restore style (if modify by rotation) 18447 e.style = style; 18448 return (e.time <= this.duration_); 18449} 18450 18451/* 18452 Copyright (c) 2016 Jean-Marc VIGLINO, 18453 released under the CeCILL license (http://www.cecill.info/). 18454*/ 18455/** Shakee animation: 18456 * @constructor 18457 * @extends {ol.featureAnimation} 18458 * @param {ol.featureAnimationShakeOptions} options 18459 * @param {Integer} options.bounce number o bounds, default 6 18460 * @param {Integer} options.amplitude amplitude of the animation, default 40 18461 * @param {bool} options.horizontal shake horizontally default false (vertical) 18462 */ 18463ol.featureAnimation.Shake = function(options) 18464{ options = options || {}; 18465 ol.featureAnimation.call(this, options); 18466// this.easing_ = options.easing_ || function(t){return (0.5+t)*t -0.5*t ;}; 18467 this.amplitude_ = options.amplitude || 40; 18468 this.bounce_ = -Math.PI*(options.bounce || 6); 18469 this.horizontal_ = options.horizontal; 18470} 18471ol.ext.inherits(ol.featureAnimation.Shake, ol.featureAnimation); 18472/** Animate 18473* @param {ol.featureAnimationEvent} e 18474*/ 18475ol.featureAnimation.Shake.prototype.animate = function (e) 18476{ // Animate 18477 var flashGeom = e.geom.clone(); 18478 var shadow = e.geom.clone(); 18479 var t = this.easing_(e.elapsed) 18480 t = Math.sin(this.bounce_*t) * this.amplitude_ * (1-t) * e.frameState.viewState.resolution; 18481 if (this.horizontal_) 18482 { flashGeom.translate(t, 0); 18483 shadow.translate(t, 0); 18484 } 18485 else flashGeom.translate(0, t); 18486 this.drawGeom_(e, flashGeom, shadow); 18487 return (e.time <= this.duration_); 18488} 18489 18490/* 18491 Copyright (c) 2016 Jean-Marc VIGLINO, 18492 released under the CeCILL license (http://www.cecill.info/). 18493*/ 18494/** Show an object for a given duration 18495 * @constructor 18496 * @extends {ol.featureAnimation} 18497 * @param {ol.featureAnimationOptions} options 18498 */ 18499ol.featureAnimation.Show = function(options) { 18500 ol.featureAnimation.call(this, options); 18501} 18502ol.ext.inherits(ol.featureAnimation.Show, ol.featureAnimation); 18503/** Animate: just show the object during the laps time 18504* @param {ol.featureAnimationEvent} e 18505*/ 18506ol.featureAnimation.Show.prototype.animate = function (e) { 18507 this.drawGeom_(e, e.geom); 18508 return (e.time <= this.duration_); 18509} 18510 18511/* 18512 Copyright (c) 2016 Jean-Marc VIGLINO, 18513 released under the CeCILL license (http://www.cecill.info/). 18514*/ 18515/** Slice animation: feature enter from left 18516 * @constructor 18517 * @extends {ol.featureAnimation} 18518 * @param {ol.featureAnimationSlideOptions} options 18519 * @param {Number} options.speed speed of the animation, if 0 the duration parameter will be used instead, default 0 18520 */ 18521ol.featureAnimation.Slide = function(options) 18522{ options = options || {}; 18523 this.speed_ = options.speed || 0; 18524 ol.featureAnimation.call(this, options); 18525 this.side_ = options.side || 'left'; 18526} 18527ol.ext.inherits(ol.featureAnimation.Slide, ol.featureAnimation); 18528/** Animate 18529* @param {ol.featureAnimationEvent} e 18530*/ 18531ol.featureAnimation.Slide.prototype.animate = function (e) 18532{ // First time > calculate duration / speed 18533 if (!e.time) 18534 { if (this.side_=='left') this.dx = (e.extent[0]-e.bbox[2]) 18535 else this.dx = (e.extent[2]-e.bbox[0]) 18536 if (this.speed_) this.duration_ = Math.abs(this.dx)/this.speed_/e.frameState.viewState.resolution; 18537 } 18538 // Animate 18539 var flashGeom = e.geom.clone(); 18540 flashGeom.translate(this.dx*(1-this.easing_(e.elapsed)), 0); 18541 this.drawGeom_(e, flashGeom); 18542 return (e.time <= this.duration_); 18543} 18544 18545/* 18546 Copyright (c) 2016 Jean-Marc VIGLINO, 18547 released under the CeCILL license (http://www.cecill.info/). 18548*/ 18549/** Teleport a feature at a given place 18550 * @constructor 18551 * @extends {ol.featureAnimation} 18552 * @param {ol.featureAnimationOptions} options 18553 */ 18554ol.featureAnimation.Teleport = function(options) 18555{ ol.featureAnimation.call(this, options); 18556} 18557ol.ext.inherits(ol.featureAnimation.Teleport, ol.featureAnimation); 18558/** Animate 18559* @param {ol.featureAnimationEvent} e 18560*/ 18561ol.featureAnimation.Teleport.prototype.animate = function (e) 18562{ var sc = this.easing_(e.elapsed); 18563 if (sc) 18564 { e.context.save() 18565 var ratio = e.frameState.pixelRatio; 18566 e.context.globalAlpha = sc; 18567 e.context.scale(sc,1/sc); 18568 var m = e.frameState.coordinateToPixelTransform; 18569 var dx = (1/sc-1) * ratio * (m[0]*e.coord[0] + m[1]*e.coord[1] +m[4]);
18570 var dy = (sc-1) * ratio * (m[2]*e.coord[0] + m[3]*e.coord[1] +m[5]); 18571 e.context.translate(dx,dy); 18572 this.drawGeom_(e, e.geom); 18573 e.context.restore() 18574 } 18575 return (e.time <= this.duration_); 18576} 18577 18578/* 18579 Copyright (c) 2016 Jean-Marc VIGLINO, 18580 released under the CeCILL license (http://www.cecill.info/). 18581*/ 18582/** Slice animation: feature enter from left 18583 * @constructor 18584 * @extends {ol.featureAnimation} 18585 * @param {ol.featureAnimationThrowOptions} options 18586 * @param {left|right} options.side side of the animation, default left 18587 */ 18588ol.featureAnimation.Throw = function(options) 18589{ options = options || {}; 18590 ol.featureAnimation.call(this, options); 18591 this.speed_ = options.speed || 0; 18592 this.side_ = options.side || 'left'; 18593} 18594ol.ext.inherits(ol.featureAnimation.Throw, ol.featureAnimation); 18595/** Animate 18596* @param {ol.featureAnimationEvent} e 18597*/ 18598ol.featureAnimation.Throw.prototype.animate = function (e) 18599{ // First time > calculate duration / speed 18600 if (!e.time && this.speed_) 18601 { var dx, dy; 18602 if (this.side_=='left') 18603 { dx = this.dx = e.extent[0]-e.bbox[2]; 18604 dy = this.dy = e.extent[3]-e.bbox[1]; 18605 } 18606 else 18607 { dx = this.dx = e.extent[2]-e.bbox[0]; 18608 dy = this.dy = e.extent[3]-e.bbox[1]; 18609 } 18610 this.duration_ = Math.sqrt(dx*dx+dy*dy)/this.speed_/e.frameState.viewState.resolution; 18611 } 18612 // Animate 18613 var flashGeom = e.geom.clone(); 18614 var shadow = e.geom.clone(); 18615 flashGeom.translate(this.dx*(1-this.easing_(e.elapsed)), 18616 this.dy*Math.cos(Math.PI/2*this.easing_(e.elapsed))); 18617 shadow.translate(this.dx*(1-this.easing_(e.elapsed)), 0); 18618 this.drawGeom_(e, flashGeom, shadow); 18619 return (e.time <= this.duration_); 18620} 18621 18622/* 18623 Copyright (c) 2016 Jean-Marc VIGLINO, 18624 released under the CeCILL license (http://www.cecill.info/). 18625*/ 18626/** Zoom animation: feature zoom in (for points) 18627 * @constructor 18628 * @extends {ol.featureAnimation} 18629 * @param {ol.featureAnimationZoomOptions} options 18630 * @param {bool} options.zoomOut to zoom out 18631 */ 18632ol.featureAnimation.Zoom = function(options){ 18633 options = options || {}; 18634 ol.featureAnimation.call(this, options); 18635 this.set('zoomout', options.zoomOut); 18636} 18637ol.ext.inherits(ol.featureAnimation.Zoom, ol.featureAnimation); 18638/** Zoom animation: feature zoom out (for points) 18639 * @constructor 18640 * @extends {ol.featureAnimation} 18641 * @param {ol.featureAnimationZoomOptions} options 18642 */ 18643ol.featureAnimation.ZoomOut = function(options) { 18644 options = options || {}; 18645 options.zoomOut = true; 18646 ol.featureAnimation.Zoom.call(this, options); 18647} 18648ol.ext.inherits(ol.featureAnimation.ZoomOut, ol.featureAnimation.Zoom); 18649/** Animate 18650* @param {ol.featureAnimationEvent} e 18651*/ 18652ol.featureAnimation.Zoom.prototype.animate = function (e) { 18653 var fac = this.easing_(e.elapsed); 18654 if (fac) { 18655 if (this.get('zoomout')) fac = 1/fac; 18656 var style = e.style; 18657 var i, imgs, sc=[] 18658 for (i=0; i<style.length; i++) { 18659 imgs = style[i].getImage(); 18660 if (imgs) { 18661 sc[i] = imgs.getScale(); 18662 // ol >= v6 18663 if (e.type==='postrender') imgs.setScale(sc[i]*fac/e.frameState.pixelRatio); 18664 else imgs.setScale(sc[i]*fac); 18665 } 18666 } 18667 this.drawGeom_(e, e.geom); 18668 for (i=0; i<style.length; i++) { 18669 imgs = style[i].getImage(); 18670 if (imgs) imgs.setScale(sc[i]); 18671 } 18672 } 18673/* 18674 var sc = this.easing_(e.elapsed); 18675 if (sc) 18676 { e.context.save() 18677 console.log(e) 18678 var ratio = e.frameState.pixelRatio; 18679 var m = e.frameState.coordinateToPixelTransform; 18680 var dx = (1/(sc)-1)* ratio * (m[0]*e.coord[0] + m[1]*e.coord[1] +m[4]); 18681 var dy = (1/(sc)-1)*ratio * (m[2]*e.coord[0] + m[3]*e.coord[1] +m[5]); 18682 e.context.scale(sc,sc); 18683 e.context.translate(dx,dy); 18684 this.drawGeom_(e, e.geom); 18685 e.context.restore() 18686 } 18687*/ 18688 return (e.time <= this.duration_); 18689} 18690 18691/* Copyright (c) 2016 Jean-Marc VIGLINO, 18692 released under the CeCILL-B license (French BSD license) 18693 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 18694*/ 18695/* Namespace */ 18696ol.filter = {}; 18697/** 18698 * @classdesc 18699 * Abstract base class; normally only used for creating subclasses and not instantiated in apps. 18700 * Used to create filters 18701 * Use {@link ol.layer.Base#addFilter}, {@link ol.layer.Base#removeFilter} or {@link ol.layer.Base#getFilters} 18702 * to handle filters on layers. 18703 * 18704 * @constructor 18705 * @extends {ol.Object} 18706 * @param {Object} options 18707 * @param {boolean} [options.active] 18708 */ 18709ol.filter.Base = function(options) { 18710 ol.Object.call(this, options); 18711 // Array of postcompose listener 18712 this._listener = []; 18713 if (options && options.active===false) this.set('active', false); 18714 else this.set('active', true); 18715}; 18716ol.ext.inherits(ol.filter.Base, ol.Object); 18717/** Activate / deactivate filter 18718* @param {boolean} b 18719*/ 18720ol.filter.Base.prototype.setActive = function (b) { 18721 this.set('active', b===true); 18722}; 18723/** Get filter active 18724* @return {boolean} 18725*/ 18726ol.filter.Base.prototype.getActive = function () { 18727 return this.get('active'); 18728}; 18729(function(){ 18730/** Internal function 18731* @this {ol.filter} this the filter 18732* @private 18733*/ 18734function precompose_(e) {
18735 if (this.get('active') && e.context) this.precompose(e); 18736} 18737/** Internal function 18738* @this {ol.filter} this the filter 18739* @private 18740*/ 18741function postcompose_(e) { 18742 if (this.get('active') && e.context) this.postcompose(e); 18743} 18744/** Force filter redraw / Internal function 18745* @this {ol.Map|ol.layer.Layer} this: the map or layer the filter is added to 18746* @private 18747*/ 18748function filterRedraw_(/* e */) { 18749 if (this.renderSync) { 18750 try { this.renderSync(); } catch(e) { /* ok */ } 18751 } else { 18752 this.changed(); 18753 } 18754} 18755/** Add a filter to an ol object 18756* @this {ol.Map|ol.layer.Layer} this: the map or layer the filter is added to 18757* @private 18758*/ 18759function addFilter_(filter) { 18760 if (!this.filters_) this.filters_ = []; 18761 this.filters_.push(filter); 18762 if (filter.addToLayer) filter.addToLayer(this); 18763 if (filter.precompose) filter._listener.push ( { listener: this.on(['precompose','prerender'], precompose_.bind(filter)), target: this }); 18764 if (filter.postcompose) filter._listener.push ( { listener: this.on(['postcompose','postrender'], postcompose_.bind(filter)), target: this }); 18765 filter._listener.push ( { listener: filter.on('propertychange', filterRedraw_.bind(this)), target: this }); 18766 filterRedraw_.call (this); 18767} 18768/** Remove a filter to an ol object 18769* @this {ol.Map|ol.layer.Layer} this: the map or layer the filter is added to 18770* @private 18771*/ 18772function removeFilter_(filter) { 18773 var i 18774 if (!this.filters_) this.filters_ = []; 18775 if (!filter) { 18776 this.filters_.forEach(function(f) { 18777 this.removeFilter(f) 18778 }.bind(this)) 18779 return; 18780 } 18781 for (i=this.filters_.length-1; i>=0; i--) { 18782 if (this.filters_[i]===filter) this.filters_.splice(i,1); 18783 } 18784 for (i=filter._listener.length-1; i>=0; i--) { 18785 // Remove listener on this object 18786 if (filter._listener[i].target === this) { 18787 if (filter.removeFromLayer) filter.removeFromLayer(this); 18788 ol.Observable.unByKey(filter._listener[i].listener); 18789 filter._listener.splice(i,1); 18790 } 18791 } 18792 filterRedraw_.call (this); 18793} 18794/** Add a filter to an ol.Map 18795* @param {ol.filter} 18796*/ 18797ol.Map.prototype.addFilter = function (filter) { 18798 console.warn('[OL-EXT] addFilter deprecated on map.') 18799 addFilter_.call (this, filter); 18800}; 18801/** Remove a filter to an ol.Map 18802* @param {ol.filter} 18803*/ 18804ol.Map.prototype.removeFilter = function (filter) { 18805 removeFilter_.call (this, filter); 18806}; 18807/** Get filters associated with an ol.Map 18808* @return {Array<ol.filter>} 18809*/ 18810ol.Map.prototype.getFilters = function () { 18811 return this.filters_ || []; 18812}; 18813/** Add a filter to an ol.Layer 18814* @param {ol.filter} 18815*/ 18816ol.layer.Base.prototype.addFilter = function (filter) { 18817 addFilter_.call (this, filter); 18818}; 18819/** Remove a filter to an ol.Layer 18820* @param {ol.filter} 18821*/ 18822ol.layer.Base.prototype.removeFilter = function (filter) { 18823 removeFilter_.call (this, filter); 18824}; 18825/** Get filters associated with an ol.Map 18826* @return {Array<ol.filter>} 18827*/ 18828ol.layer.Base.prototype.getFilters = function () { 18829 return this.filters_ || []; 18830}; 18831})(); 18832 18833/* Copyright (c) 2016 Jean-Marc VIGLINO, 18834 released under the CeCILL-B license (French BSD license) 18835 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 18836*/ 18837/** Mask drawing using an ol.Feature 18838 * @constructor 18839 * @requires ol.filter 18840 * @extends {ol.filter.Base} 18841 * @param {Object} [options] 18842 * @param {ol.Feature} [options.feature] feature to mask with 18843 * @param {ol.style.Fill} [options.fill] style to fill with 18844 * @param {boolean} [options.inner=false] mask inner, default false 18845 * @param {boolean} [options.wrapX=false] wrap around the world, default false 18846 */ 18847ol.filter.Mask = function(options) { 18848 options = options || {}; 18849 ol.filter.Base.call(this, options); 18850 if (options.feature) { 18851 switch (options.feature.getGeometry().getType()) { 18852 case 'Polygon': 18853 case 'MultiPolygon': 18854 this.feature_ = options.feature; 18855 break; 18856 default: break; 18857 } 18858 } 18859 this.set('inner', options.inner); 18860 this.fillColor_ = options.fill ? ol.color.asString(options.fill.getColor()) || "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.2)"; 18861} 18862ol.ext.inherits(ol.filter.Mask, ol.filter.Base); 18863/** Draw the feature into canvas */ 18864ol.filter.Mask.prototype.drawFeaturePath_ = function(e, out) { 18865 var ctx = e.context; 18866 var canvas = ctx.canvas; 18867 var ratio = e.frameState.pixelRatio; 18868 // Transform 18869 var tr; 18870 if (e.frameState.coordinateToPixelTransform) { 18871 var m = e.frameState.coordinateToPixelTransform; 18872 // ol > 6 18873 if (e.inversePixelTransform) { 18874 var ipt = e.inversePixelTransform; 18875 tr = function(pt) { 18876 pt = [ 18877 (pt[0]*m[0]+pt[1]*m[1]+m[4]), 18878 (pt[0]*m[2]+pt[1]*m[3]+m[5]) 18879 ]; 18880 return [ 18881 (pt[0]*ipt[0] - pt[1]*ipt[1] + ipt[4]), 18882 (-pt[0]*ipt[2] + pt[1]*ipt[3] + ipt[5]) 18883 ] 18884 } 18885 } else { 18886 // ol 5 18887 tr = function(pt) { 18888 return [ 18889 (pt[0]*m[0]+pt[1]*m[1]+m[4])*ratio, 18890 (pt[0]*m[2]+pt[1]*m[3]+m[5])*ratio 18891 ]; 18892 } 18893 } 18894 } else { 18895 // Older version 18896 m = e.frameState.coordinateToPixelMatrix; 18897 tr = function(pt) { 18898 return [ 18899 (pt[0]*m[0]+pt[1]*m[1]+m[12])*ratio, 18900 (pt[0]*m[4]+pt[1]*m[5]+m[13])*ratio 18901 ]; 18902 } 18903 } 18904 // Geometry 18905 var ll = this.feature_.getGeometry().getCoordinates(); 18906 if (this.feature_.getGeometry().getType()==='Polygon') ll = [ll]; 18907 // Draw feature at dx world 18908 function drawll(dx) { 18909 for (var l=0; l<ll.length; l++) { 18910 var c = ll[l]; 18911 for (var i=0; i<c.length; i++) { 18912 var pt = tr([c[i][0][0] + dx, c[i][0][1]]); 18913 ctx.moveTo (pt[0], pt[1]); 18914 for (var j=1; j<c[i].length; j++) { 18915 pt = tr([c[i][j][0] + dx, c[i][j][1]]); 18916 ctx.lineTo (pt[0], pt[1]); 18917 } 18918 } 18919 } 18920 } 18921 ctx.beginPath(); 18922 if (out) { 18923 ctx.moveTo (0,0); 18924 ctx.lineTo (canvas.width, 0); 18925 ctx.lineTo (canvas.width, canvas.height); 18926 ctx.lineTo (0, canvas.height); 18927 ctx.lineTo (0, 0); 18928 } 18929 // Draw current world 18930 if (this.get('wrapX')) { 18931 var worldExtent = e.frameState.viewState.projection.getExtent() 18932 var worldWidth = worldExtent[2] - worldExtent[0]; 18933 var extent = e.frameState.extent; 18934 var fExtent = this.feature_.getGeometry().getExtent(); 18935 var fWidth = fExtent[2] - fExtent[1]; 18936 var start = Math.floor((extent[0] + fWidth - worldExtent[0]) / worldWidth); 18937 var end = Math.floor((extent[2] - fWidth - worldExtent[2]) / worldWidth) +1; 18938 if(start > end) { 18939 [start, end] = [end, start]; 18940 } 18941 for (var i=start; i<=end; i++) { 18942 drawll(i*worldWidth); 18943 } 18944 } else { 18945 drawll(0); 18946 } 18947}; 18948ol.filter.Mask.prototype.postcompose = function(e) { 18949 if (!this.feature_) return; 18950 var ctx = e.context; 18951 ctx.save(); 18952 this.drawFeaturePath_(e, !this.get("inner")); 18953 ctx.fillStyle = this.fillColor_; 18954 ctx.fill("evenodd");
18955 ctx.restore(); 18956}; 18957 18958/** Add a mix-blend-mode CSS filter (not working with IE or ol<6). 18959 * Add a className to the layer to apply the filter to a specific layer. 18960 * With ol<6 use {@link ol.filter.Composite} instead. 18961 * Use {@link ol.layer.Base#addFilter}, {@link ol.layer.Base#removeFilter} or {@link ol.layer.Base#getFilters} 18962 * @constructor 18963 * @extends {ol.Object} 18964 * @param {Object} options 18965 * @param {string} options.blend mix-blend-mode to apply (as {@link https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode CSS property}) 18966 * @param {string} options.filter filter to apply (as {@link https://developer.mozilla.org/en-US/docs/Web/CSS/filter CSS property}) 18967 * @param {boolan} options.display show/hide layer from CSS (but keep it in layer list) 18968 */ 18969ol.filter.CSS = function(options) { 18970 ol.filter.Base.call(this, options); 18971 this._layers = []; 18972}; 18973ol.ext.inherits(ol.filter.CSS, ol.filter.Base); 18974/** Modify blend mode 18975 * @param {string} blend mix-blend-mode to apply (as {@link https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode CSS property}) 18976 */ 18977ol.filter.CSS.prototype.setBlend = function(blend) { 18978 this.set('blend', blend); 18979 this._layers.forEach(function(layer) { 18980 layer.once('postrender', function(e) { 18981 e.context.canvas.parentNode.style['mix-blend-mode'] = blend || ''; 18982 }.bind(this)); 18983 layer.changed(); 18984 }); 18985}; 18986/** Modify filter mode 18987 * @param {string} filter filter to apply (as {@link https://developer.mozilla.org/en-US/docs/Web/CSS/filter CSS property}) 18988 */ 18989ol.filter.CSS.prototype.setFilter = function(filter) { 18990 this.set('filter', filter); 18991 this._layers.forEach(function(layer) { 18992 layer.once('postrender', function(e) { 18993 e.context.canvas.parentNode.style['filter'] = filter || ''; 18994 }.bind(this)); 18995 layer.changed(); 18996 }); 18997}; 18998/** Modify layer visibility (but keep it in the layer list) 18999 * @param {bolean} display 19000 */ 19001 ol.filter.CSS.prototype.setDisplay = function(display) { 19002 this.set('display', display); 19003 this._layers.forEach(function(layer) { 19004 layer.once('postrender', function(e) { 19005 e.context.canvas.parentNode.style['display'] = display ? '' : 'none'; 19006 }.bind(this)); 19007 layer.changed(); 19008 }); 19009}; 19010/** Add CSS filter to the layer 19011 * @param {ol.layer.Base} layer 19012 */ 19013ol.filter.CSS.prototype.addToLayer = function(layer) { 19014 layer.once('postrender', function(e) { 19015 e.context.canvas.parentNode.style['mix-blend-mode'] = this.get('blend') || ''; 19016 e.context.canvas.parentNode.style['filter'] = this.get('filter') || ''; 19017 e.context.canvas.parentNode.style['display'] = this.get('display')!==false ? '' : 'none'; 19018 }.bind(this)); 19019 layer.changed(); 19020 this._layers.push(layer); 19021 // layer.getRenderer().getImage().parentNode.style['mix-blend-mode'] = 'multiply'; 19022}; 19023/** Remove CSS filter from the layer 19024 * @param {ol.layer.Base} layer 19025 */ 19026ol.filter.CSS.prototype.removeFromLayer = function(layer) { 19027 var pos = this._layers.indexOf(layer); 19028 if (pos>=0) { 19029 layer.once('postrender', function(e) { 19030 e.context.canvas.parentNode.style['mix-blend-mode'] = ''; 19031 e.context.canvas.parentNode.style['filter'] = ''; 19032 e.context.canvas.parentNode.style['display'] = ''; 19033 }.bind(this)); 19034 layer.changed(); 19035 this._layers.splice(pos, 1); 19036 } 19037}; 19038 19039/* Copyright (c) 2016 Jean-Marc VIGLINO, 19040 released under the CeCILL-B license (French BSD license) 19041 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19042*/ 19043/** @typedef {Object} CanvasFilterOptions 19044 * @property {url} url Takes an IRI pointing to an SVG filter element 19045 * @property {number} blur Gaussian blur value in px 19046 * @property {number} brightness linear multiplier to the drawing, under 100: darkens the image, over 100 brightens it 19047 * @property {number} contrast Adjusts the contrast, under 0: black, 100 no change 19048 * @property {ol.pixel} shadow Applies a drop shadow effect, pixel offset 19049 * @property {number} shadowBlur Blur radius 19050 * @property {number} shadowColor 19051 * @property {number} grayscale 0: unchanged, 100: completely grayscale 19052 * @property {number} hueRotate Hue rotation angle in deg 19053 * @property {number} invert Inverts the drawing, 0: unchanged, 100: invert 19054 * @property {number} saturate Saturates the drawing, 0: unsaturated, 100: unchanged 19055 * @property {number} sepia Converts the drawing to sepia, 0: sepia, 100: unchanged 19056 */ 19057/** Add a canvas Context2D filter to a layer 19058 * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter 19059 * @constructor 19060 * @requires ol.filter 19061 * @extends {ol.filter.Base} 19062 * @param {CanvasFilterOptions} options 19063 */ 19064ol.filter.CanvasFilter = function(options) { 19065 ol.filter.Base.call(this, options); 19066 this._svg = {}; 19067}; 19068ol.ext.inherits(ol.filter.CanvasFilter, ol.filter.Base); 19069/** Add a new svg filter 19070 * @param {string|ol.ext.SVGFilter} url IRI pointing to an SVG filter element 19071 */ 19072ol.filter.CanvasFilter.prototype.addSVGFilter = function(url) { 19073 if (url.getId) url = '#'+url.getId(); 19074 this._svg[url] = 1; 19075 this.dispatchEvent({ type: 'propertychange', key: 'svg', oldValue: this._svg }); 19076}; 19077/** Remove a svg filter 19078 * @param {string|ol.ext.SVGFilter} url IRI pointing to an SVG filter element 19079 */ 19080ol.filter.CanvasFilter.prototype.removeSVGFilter = function(url) { 19081 if (url.getId) url = '#'+url.getId(); 19082 delete this._svg[url] 19083 this.dispatchEvent({ type: 'propertychange', key: 'svg', oldValue: this._svg }); 19084}; 19085/** 19086 * @private 19087 */ 19088ol.filter.CanvasFilter.prototype.precompose = function() { 19089}; 19090/** 19091 * @private 19092 */ 19093ol.filter.CanvasFilter.prototype.postcompose = function(e) { 19094 var filter = [] 19095 // Set filters 19096 if (this.get('url')!==undefined) filter.push('url('+this.get('url')+')'); 19097 for (var f in this._svg) { 19098 filter.push('url('+f+')'); 19099 } 19100 if (this.get('blur')!==undefined) filter.push('blur('+this.get('blur')+'px)'); 19101 if (this.get('brightness')!==undefined) filter.push('brightness('+this.get('brightness')+'%)'); 19102 if (this.get('contrast')!==undefined) filter.push('contrast('+this.get('contrast')+'
19102%)'); 19103 if (this.get('shadow')!==undefined) { 19104 filter.push('drop-shadow(' 19105 + this.get('shadow')[0]+'px ' 19106 + this.get('shadow')[1]+'px ' 19107 + (this.get('shadowBlur')||0)+'px ' 19108 + this.get('shadowColor')+')'); 19109 } 19110 if (this.get('grayscale')!==undefined) filter.push('grayscale('+this.get('grayscale')+'%)'); 19111 if (this.get('sepia')!==undefined) filter.push('sepia('+this.get('sepia')+'%)'); 19112 if (this.get('hueRotate')!==undefined) filter.push('hue-rotate('+this.get('hueRotate')+'deg)'); 19113 if (this.get('invert')!==undefined) filter.push('invert('+this.get('invert')+'%)'); 19114 if (this.get('saturate')!==undefined) filter.push('saturate('+this.get('saturate')+'%)'); 19115 filter = filter.join(' '); 19116 // Apply filter 19117 if (filter) { 19118 e.context.save(); 19119 e.context.filter = filter; 19120 e.context.drawImage(e.context.canvas, 0,0); 19121 e.context.restore(); 19122 } 19123}; 19124 19125/* Copyright (c) 2016 Jean-Marc VIGLINO, 19126 released under the CeCILL-B license (French BSD license) 19127 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19128*/ 19129/** Clip layer or map 19130* @constructor 19131* @requires ol.filter 19132* @extends {ol.filter.Base} 19133* @param {Object} [options] 19134* @param {Array<ol.Coordinate>} [options.coords] 19135* @param {ol.Extent} [options.extent] 19136* @param {string} [options.units] coords units percent (%) or pixel (px) 19137* @param {boolean} [options.keepAspectRatio] keep aspect ratio 19138* @param {string} [options.color] backgroundcolor 19139*/ 19140ol.filter.Clip = function(options) { 19141 options = options || {}; 19142 ol.filter.Base.call(this, options); 19143 this.set("coords", options.coords); 19144 this.set("units", options.units); 19145 this.set("keepAspectRatio", options.keepAspectRatio); 19146 this.set("extent", options.extent || [0,0,1,1]); 19147 this.set("color", options.color); 19148 if (!options.extent && options.units!="%" && options.coords) { 19149 var xmin = Infinity; 19150 var ymin = Infinity; 19151 var xmax = -Infinity; 19152 var ymax = -Infinity; 19153 for (var i=0, p; p=options.coords[i]; i++) { 19154 if (xmin > p[0]) xmin = p[0]; 19155 if (xmax < p[0]) xmax = p[0]; 19156 if (ymin > p[1]) ymin = p[1]; 19157 if (ymax < p[1]) ymax = p[1]; 19158 } 19159 options.extent = [xmin,ymin,xmax,ymax]; 19160 } 19161} 19162ol.ext.inherits(ol.filter.Clip, ol.filter.Base); 19163ol.filter.Clip.prototype.clipPath_ = function(e) { 19164 var ctx = e.context; 19165 var size = e.frameState.size; 19166 var coords = this.get("coords"); 19167 if (!coords) return; 19168 var ex = this.get('extent'); 19169 var scx = 1, scy = 1; 19170 if (this.get("units")=="%") { 19171 scx = size[0]/(ex[2]-ex[0]); 19172 scy = size[1]/(ex[3]-ex[1]); 19173 } 19174 if (this.get("keepAspectRatio")) { 19175 scx = scy = Math.min (scx, scy); 19176 } 19177 var pos = this.get('position'); 19178 var dx=0, dy=0; 19179 if (/left/.test(pos)) { 19180 dx = -ex[0]*scx; 19181 } else if (/center/.test(pos)) { 19182 dx = size[0]/2 - (ex[2]-ex[0])*scx/2; 19183 } else if (/right/.test(pos)) { 19184 dx = size[0] - (ex[2]-ex[0])*scx; 19185 } 19186 var fx = function(x) { return x*scx + dx }; 19187 if (/top/.test(pos)) { 19188 dy = -ex[1]*scy; 19189 } else if (/middle/.test(pos)) { 19190 dy = size[1]/2 - (ex[3]-ex[1])*scy/2; 19191 } else if (/bottom/.test(pos)) { 19192 dy = size[1] - (ex[3]-ex[1])*scy; 19193 } 19194 var fy = function(y) { return y*scy + dy; }; 19195 var pt = [ fx(coords[0][0]), fy(coords[0][1]) ]; 19196 var tr = e.inversePixelTransform; 19197 if (tr) { 19198 pt = [ 19199 (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), 19200 (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) 19201 ]; 19202 } 19203 ctx.moveTo ( pt[0], pt[1] ); 19204 for (var i=1, p; p=coords[i]; i++) { 19205 pt = [ fx(p[0]), fy(p[1]) ]; 19206 if (tr) { 19207 pt = [ 19208 (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), 19209 (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) 19210 ]; 19211 } 19212 ctx.lineTo ( pt[0], pt[1] ); 19213 } 19214 pt = [ fx(coords[0][0]), fy(coords[0][1]) ]; 19215 if (tr) { 19216 pt = [ 19217 (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), 19218 (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) 19219 ]; 19220 } 19221 ctx.moveTo ( pt[0], pt[1] ); 19222}; 19223/** 19224 * @private 19225 */ 19226ol.filter.Clip.prototype.precompose = function(e) { 19227 if (!this.get("color")){ 19228 e.context.save(); 19229 e.context.beginPath(); 19230 this.clipPath_(e); 19231 e.context.clip(); 19232 } 19233}; 19234/** 19235 * @private 19236 */ 19237ol.filter.Clip.prototype.postcompose = function(e) { 19238 if (this.get("color")) { 19239 var ctx = e.context; 19240 var canvas = e.context.canvas; 19241 ctx.save(); 19242 ctx.beginPath(); 19243 ctx.moveTo(0,0); 19244 ctx.lineTo(0,canvas.height); 19245 ctx.lineTo(canvas.width, canvas.height); 19246 ctx.lineTo(canvas.width, canvas.height); 19247 ctx.lineTo(canvas.width, 0); 19248 ctx.lineTo(0, 0); 19249 this.clipPath_(e); 19250 ctx.fillStyle = this.get("color"); 19251 ctx.fill("evenodd"); 19252 }
19253 e.context.restore(); 19254}; 19255 19256/* Copyright (c) 2016 Jean-Marc VIGLINO, 19257 released under the CeCILL-B license (French BSD license) 19258 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19259*/ 19260/** @typedef {Object} FilterColorizeOptions 19261 * @property {ol.Color} color style to fill with 19262 * @property {string} operation 'enhance' or a CanvasRenderingContext2D.globalCompositeOperation 19263 * @property {number} value a value to modify the effect value [0-1] 19264 * @property {boolean} inner mask inner, default false 19265 * @property {boolean} preserveAlpha preserve alpha channel, default false 19266 */ 19267/** Colorize map or layer 19268 * @constructor 19269 * @requires ol.filter 19270 * @extends {ol.filter.Base} 19271 * @author Thomas Tilak https://github.com/thhomas 19272 * @author Jean-Marc Viglino https://github.com/viglino 19273 * @param {FilterColorizeOptions} options 19274 */ 19275ol.filter.Colorize = function(options) { 19276 ol.filter.Base.call(this, options); 19277 this.setFilter(options); 19278} 19279ol.ext.inherits(ol.filter.Colorize, ol.filter.Base); 19280/** Set options to the filter 19281 * @param {FilterColorizeOptions} [options] 19282 */ 19283ol.filter.Colorize.prototype.setFilter = function(options) { 19284 options = options || {}; 19285 switch (options) { 19286 case "grayscale": options = { operation:'hue', color: [0,0,0], value:1 }; break; 19287 case "invert": options = { operation:'difference', color: [255,255,255], value:1 }; break; 19288 case "sepia": options = { operation:'color', color: [153,102,51], value:0.6 }; break; 19289 default: break; 19290 } 19291 var color = options.color ? ol.color.asArray(options.color) : [ options.red, options.green, options.blue, options.value]; 19292 this.set('color', ol.color.asString(color)) 19293 this.set ('value', options.value||1); 19294 this.set ('preserveAlpha', options.preserveAlpha); 19295 var v; 19296 switch (options.operation){ 19297 case 'hue': 19298 case 'difference': 19299 case 'color-dodge': 19300 case 'enhance': { 19301 this.set ('operation', options.operation); 19302 break; 19303 } 19304 case 'saturation': { 19305 v = 255*(options.value || 0); 19306 this.set('color', ol.color.asString([0,0,v,v||1])); 19307 this.set ('operation', options.operation); 19308 break; 19309 } 19310 case 'luminosity': { 19311 v = 255*(options.value || 0); 19312 this.set('color', ol.color.asString([v,v,v,255])); 19313 //this.set ('operation', 'luminosity') 19314 this.set ('operation', 'hard-light'); 19315 break; 19316 } 19317 case 'contrast': { 19318 v = 255*(options.value || 0); 19319 this.set('color', ol.color.asString([v,v,v,255])); 19320 this.set('operation', 'soft-light'); 19321 break; 19322 } 19323 default: { 19324 this.set ('operation', 'color'); 19325 this.setValue(options.value||1); 19326 break; 19327 } 19328 } 19329} 19330/** Set the filter value 19331 * @param {ol.Color} options.color style to fill with 19332 */ 19333ol.filter.Colorize.prototype.setValue = function(v) { 19334 this.set ('value', v); 19335 var c = ol.color.asArray(this.get("color")); 19336 c[3] = v; 19337 this.set("color", ol.color.asString(c)); 19338} 19339/** Set the color value 19340 * @param {number} options.value a [0-1] value to modify the effect value 19341 */ 19342ol.filter.Colorize.prototype.setColor = function(c) { 19343 c = ol.color.asArray(c); 19344 if (c) { 19345 c[3] = this.get("value"); 19346 this.set("color", ol.color.asString(c)); 19347 } 19348} 19349/** @private 19350 */ 19351ol.filter.Colorize.prototype.precompose = function(/* e */) { 19352} 19353/** @private 19354 */ 19355ol.filter.Colorize.prototype.postcompose = function(e) { 19356 // Set back color hue 19357 var c2, ctx2; 19358 var ctx = e.context; 19359 var canvas = ctx.canvas; 19360 ctx.save(); 19361 if (this.get('operation')=='enhance') { 19362 var v = this.get('value'); 19363 if (v) { 19364 var w = canvas.width; 19365 var h = canvas.height; 19366 if (this.get('preserveAlpha')) { 19367 c2 = document.createElement('CANVAS'); 19368 c2.width = canvas.width; 19369 c2.height = canvas.height; 19370 ctx2 = c2.getContext('2d'); 19371 ctx2.drawImage (canvas, 0, 0, w, h); 19372 ctx2.globalCompositeOperation = 'color-burn'; 19373 console.log(v) 19374 ctx2.globalAlpha = v; 19375 ctx2.drawImage (c2, 0, 0, w, h); 19376 ctx2.drawImage (c2, 0, 0, w, h); 19377 ctx2.drawImage (c2, 0, 0, w, h); 19378 ctx.globalCompositeOperation = 'source-in'; 19379 ctx.drawImage(c2, 0,0); 19380 } else { 19381 ctx.globalCompositeOperation = 'color-burn' 19382 ctx.globalAlpha = v; 19383 ctx.drawImage (canvas, 0, 0, w, h); 19384 ctx.drawImage (canvas, 0, 0, w, h); 19385 ctx.drawImage (canvas, 0, 0, w, h); 19386 } 19387 } 19388 } else { 19389 if (this.get('preserveAlpha')) { 19390 c2 = document.createElement('CANVAS'); 19391 c2.width = canvas.width; 19392 c2.height = canvas.height; 19393 ctx2 = c2.getContext('2d'); 19394 ctx2.drawImage(canvas, 0,0); 19395 ctx2.globalCompositeOperation = this.get('operation'); 19396 ctx2.fillStyle = this.get('color'); 19397 ctx2.fillRect(0,0,canvas.width,canvas.height); 19398 ctx.globalCompositeOperation = 'source-in'; 19399 ctx.drawImage(c2, 0,0); 19400 } else { 19401 ctx.globalCompositeOperation = this.get('operation'); 19402 ctx.fillStyle = this.get('color'); 19403 ctx.fillRect(0,0,canvas.width,canvas.height); 19404 } 19405 }
19406 ctx.restore(); 19407} 19408 19409/* Copyright (c) 2016 Jean-Marc VIGLINO, 19410 released under the CeCILL-B license (French BSD license) 19411 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19412*/ 19413/** Add a composite filter on a layer. 19414 * With ol6+ you'd better use {@link ol.filter.CSS} instead. 19415 * Use {@link ol.layer.Base#addFilter}, {@link ol.layer.Base#removeFilter} or {@link ol.layer.Base#getFilters} 19416 * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation 19417 * @constructor 19418 * @requires ol.filter 19419 * @extends {ol.filter.Base} 19420 * @param {Object} options 19421 * @param {string} options.operation composite operation 19422 */ 19423ol.filter.Composite = function(options) { 19424 ol.filter.Base.call(this, options); 19425 this.set("operation", options.operation || "source-over"); 19426} 19427ol.ext.inherits(ol.filter.Composite, ol.filter.Base); 19428/** Change the current operation 19429* @param {string} operation composite function 19430*/ 19431ol.filter.Composite.prototype.setOperation = function(operation) { 19432 this.set('operation', operation || "source-over"); 19433} 19434ol.filter.Composite.prototype.precompose = function(e) { 19435 var ctx = e.context; 19436 ctx.save(); 19437 ctx.globalCompositeOperation = this.get('operation'); 19438} 19439ol.filter.Composite.prototype.postcompose = function(e) { 19440 e.context.restore(); 19441} 19442 19443/* Copyright (c) 2016 Jean-Marc VIGLINO, 19444 released under the CeCILL-B license (French BSD license) 19445 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19446*/ 19447/** Crop drawing using an ol.Feature 19448* @constructor 19449* @requires ol.filter 19450* @requires ol.filter.Mask 19451* @extends {ol.filter.Mask} 19452* @param {Object} [options] 19453* @param {ol.Feature} [options.feature] feature to crop with 19454* @param {boolean} [options.inner=false] mask inner, default false 19455*/ 19456ol.filter.Crop = function(options) { 19457 options = options || {}; 19458 ol.filter.Mask.call(this, options); 19459} 19460ol.ext.inherits(ol.filter.Crop, ol.filter.Mask); 19461ol.filter.Crop.prototype.precompose = function(e) { 19462 if (this.feature_) { 19463 var ctx = e.context; 19464 ctx.save(); 19465 this.drawFeaturePath_(e, this.get("inner")); 19466 ctx.clip("evenodd"); 19467 } 19468} 19469ol.filter.Crop.prototype.postcompose = function(e) { 19470 if (this.feature_) e.context.restore(); 19471} 19472 19473/* Copyright (c) 2017 Jean-Marc VIGLINO, 19474 released under the CeCILL-B license (French BSD license) 19475 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19476*/ 19477/** Fold filer map 19478 * @constructor 19479 * @requires ol.filter 19480 * @extends {ol.filter.Base} 19481 * @param {Object} [options] 19482 * @param {Array<number>} [options.fold[8,4]] number of fold (horizontal and vertical) 19483 * @param {number} [options.margin=8] margin in px, default 8 19484 * @param {number} [options.padding=8] padding in px, default 8 19485 * @param {number|number[]} [options.fsize=[8,10]] fold size in px, default 8,10 19486 * @param {boolean} [options.fill=false] true to fill the background, default false 19487 * @param {boolean} [options.shadow=true] true to display shadow 19488 * @param {boolean} [options.opacity=.2] effect opacity 19489 */ 19490ol.filter.Fold = function(options) { 19491 options = options || {}; 19492 ol.filter.Base.call(this, options); 19493 this.set('fold', options.fold || [8,4]); 19494 this.set('margin', options.margin || 8); 19495 this.set('padding', options.padding || 8); 19496 if (typeof options.fsize == 'number') options.fsize = [options.fsize,options.fsize]; 19497 this.set('fsize', options.fsize || [8,10]); 19498 this.set('fill', options.fill); 19499 this.set('shadow', options.shadow!==false); 19500 this.set('opacity', (options.hasOwnProperty('opacity') ? options.opacity : .2)); 19501}; 19502ol.ext.inherits(ol.filter.Fold, ol.filter.Base); 19503ol.filter.Fold.prototype.drawLine_ = function(ctx, d, m) { 19504 var canvas = ctx.canvas; 19505 var fold = this.get("fold"); 19506 var w = canvas.width; 19507 var h = canvas.height; 19508 var x, y, i; 19509 ctx.beginPath(); 19510 ctx.moveTo ( m, m ); 19511 for (i=1; i<=fold[0]; i++) { 19512 x = i*w/fold[0] - (i==fold[0] ? m : 0); 19513 y = d[1]*(i%2) +m; 19514 ctx.lineTo ( x, y ); 19515 } 19516 for (i=1; i<=fold[1]; i++) { 19517 x = w - d[0]*(i%2) - m; 19518 y = i*h/fold[1] - (i==fold[1] ? d[0]*(fold[0]%2) + m : 0); 19519 ctx.lineTo ( x, y ); 19520 } 19521 for (i=fold[0]; i>0; i--) { 19522 x = i*w/fold[0] - (i==fold[0] ? d[0]*(fold[1]%2) + m : 0); 19523 y = h - d[1]*(i%2) -m; 19524 ctx.lineTo ( x, y ); 19525 } 19526 for (i=fold[1]; i>0; i--) { 19527 x = d[0]*(i%2) + m; 19528 y = i*h/fold[1] - (i==fold[1] ? m : 0); 19529 ctx.lineTo ( x, y ); 19530 } 19531 ctx.closePath(); 19532}; 19533ol.filter.Fold.prototype.precompose = function(e) { 19534 var ctx = e.context; 19535 ctx.save(); 19536 ctx.shadowColor = "rgba(0,0,0,0.3)"; 19537 ctx.shadowBlur = 8; 19538 ctx.shadowOffsetX = 2; 19539 ctx.shadowOffsetY = 3;
19540 this.drawLine_(ctx, this.get("fsize"), this.get("margin")); 19541 ctx.fillStyle="#fff"; 19542 if (this.get('fill')) ctx.fill(); 19543 ctx.strokeStyle = "rgba(0,0,0,0.1)"; 19544 ctx.stroke(); 19545 ctx.restore(); 19546 ctx.save(); 19547 this.drawLine_(ctx, this.get("fsize"), this.get("margin") + this.get("padding")); 19548 ctx.clip(); 19549}; 19550ol.filter.Fold.prototype.postcompose = function(e) { 19551 var ctx = e.context; 19552 var canvas = ctx.canvas; 19553 ctx.restore(); 19554 ctx.save(); 19555 this.drawLine_(ctx, this.get("fsize"), this.get("margin")); 19556 ctx.clip(); 19557 if (this.get('shadow')) { 19558 var fold = this.get("fold"); 19559 var w = canvas.width/fold[0]; 19560 var h = canvas.height/fold[1]; 19561 var grd = ctx.createRadialGradient(5*w/8,5*w/8,w/4,w/2,w/2,w); 19562 grd.addColorStop(0,"transparent"); 19563 grd.addColorStop(1,"rgba(0,0,0," + this.get('opacity') + ")"); 19564 ctx.fillStyle = grd; 19565 ctx.scale (1,h/w); 19566 for (var i=0; i<fold[0]; i++) for (var j=0; j<fold[1]; j++) { 19567 ctx.save() 19568 ctx.translate(i*w, j*w); 19569 ctx.fillRect(0,0,w,w); 19570 ctx.restore() 19571 } 19572 } 19573 ctx.restore(); 19574}; 19575 19576/* Copyright (c) 2017 Jean-Marc VIGLINO, 19577 released under the CeCILL-B license (French BSD license) 19578 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19579*/ 19580/** Make a map or layer look like made of a set of Lego bricks.
19581 * @constructor 19582 * @requires ol.filter 19583 * @extends {ol.filter.Base} 19584 * @param {Object} [options] 19585 * @param {string} [options.channel] RGB channels: 'r', 'g' or 'b', default use intensity 19586 * @param {ol.colorlike} [options.color] color, default black 19587 * @param {number} [options.size] point size, default 30 19588 * @param {null | string | undefined} [options.crossOrigin] crossOrigin attribute for loaded images. 19589 */ 19590ol.filter.Halftone = function(options) { 19591 if (!options) options = {}; 19592 ol.filter.Base.call(this, options); 19593 this.internal_ = document.createElement('canvas'); 19594 this.setSize(options.size); 19595 this.set('channel', options.channel); 19596} 19597ol.ext.inherits(ol.filter.Halftone, ol.filter.Base); 19598/** Set the current size 19599* @param {number} width the pattern width, default 30 19600*/ 19601ol.filter.Halftone.prototype.setSize = function (size) { 19602 size = Number(size) || 30; 19603 this.set("size", size); 19604}; 19605/** Postcompose operation 19606*/ 19607ol.filter.Halftone.prototype.postcompose = function(e) { 19608 var ctx = e.context; 19609 var canvas = ctx.canvas; 19610 var ratio = e.frameState.pixelRatio; 19611 // ol v6+ 19612 if (e.type === 'postrender') { 19613 ratio = 1; 19614 } 19615 ctx.save(); 19616 // resize 19617 var step = this.get('size')*ratio; 19618 var p = e.frameState.extent; 19619 var res = e.frameState.viewState.resolution/ratio; 19620 var offset = [ -Math.round((p[0]/res)%step), Math.round((p[1]/res)%step) ]; 19621 var ctx2 = this.internal_.getContext("2d"); 19622 var w = this.internal_.width = canvas.width; 19623 var h = this.internal_.height = canvas.height; 19624 // No smoothing please 19625 ctx2.webkitImageSmoothingEnabled = 19626 ctx2.mozImageSmoothingEnabled = 19627 ctx2.msImageSmoothingEnabled = 19628 ctx2.imageSmoothingEnabled = false; 19629 var w2 = Math.floor((w-offset[0])/step); 19630 var h2 = Math.floor((h-offset[1])/step); 19631 ctx2.drawImage (canvas, offset[0], offset[1], w2*step, h2*step, 0, 0, w2, h2); 19632 var data = ctx2.getImageData(0, 0, w2,h2).data; 19633 // Draw tone 19634 ctx.clearRect (0, 0, w,h); 19635 ctx.fillStyle = ol.color.asString(this.get('color') || '#000'); 19636 for (var x=0; x<w2; x++) for (var y=0; y<h2; y++) { 19637 var pix; 19638 switch (this.get('channel')) { 19639 case 'r': pix = data[x*4+y*w2*4] / 2.55; break; 19640 case 'g': pix = data[x*4+1+y*w2*4] / 2.55; break; 19641 case 'b': pix = data[x*4+2+y*w2*4] / 2.55; break; 19642 default: 19643 pix = ol.color.toHSL([data[x*4+y*w2*4], data[x*4+1+y*w2*4], data[x*4+2+y*w2*4]]); 19644 pix = pix[2]; 19645 break; 19646 } 19647 var l = (100-pix)/140; 19648 if (l) { 19649 ctx.beginPath(); 19650 ctx.arc(offset[0]+step/2+x*step, offset[1]+step/2+y*step, step*l, 0, 2 * Math.PI); 19651 ctx.closePath(); 19652 ctx.fill(); 19653 } 19654 } 19655 ctx.restore(); 19656}; 19657 19658/* Copyright (c) 2017 Jean-Marc VIGLINO, 19659 released under the CeCILL-B license (French BSD license) 19660 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19661*/ 19662/** Make a map or layer look like made of a set of Lego bricks.
19663 * @constructor 19664 * @requires ol.filter 19665 * @extends {ol.filter.Base} 19666 * @param {Object} [options] 19667 * @param {string} [options.img] 19668 * @param {number} [options.brickSize] size of te brick, default 30 19669 * @param {null | string | undefined} [options.crossOrigin] crossOrigin attribute for loaded images. 19670 */ 19671ol.filter.Lego = function(options) { 19672 if (!options) options = {}; 19673 ol.filter.Base.call(this, options); 19674 var img = new Image(); 19675 // Default image 19676 img.src = this.img[options.img] || this.img.ol3; 19677 img.crossOrigin = options.crossOrigin || null; 19678 // and pattern 19679 this.pattern = { 19680 canvas: document.createElement('canvas') 19681 }; 19682 this.setBrick (options.brickSize, img); 19683 this.internal_ = document.createElement('canvas'); 19684} 19685ol.ext.inherits(ol.filter.Lego, ol.filter.Base); 19686/** Image definition 19687*/ 19688ol.filter.Lego.prototype.img = { 19689 brick: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAAAnNCSVQICFXsRgQAAAAJcEhZcwAAD10AAA9dAah0GUAAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAGAElEQVRo3sWZy4tkVx3HP+fcc29Vd1dP17TdTcbJPDKPMGR0kVEZkuBCF0EE9Z8QXLhxMUsRF4oLwYWQTSCgSxUXroQhoiEuskgEUUQh+BhHOpkZO11dr3vvefxc3FPlNHNvPbrD1Dl016XoqvM539/znFbcZo3VjbFmxcMA3Mg2fSoAiQJDov7/B1o9+aEgkycv4PBSPU9eHeDEixNwOAFXPYvFia0+rcnQEeBr218cfLIwCqW1UWillEYphUKpCmCCIQAiCEhAJIggTiSISBAfggTvJZTifQghWO+89cOQexuOXN8Pwz/9ff9X/xF0uEA7AmTsjLp/2xZQCgXHlj0OEBEAeRwGkep3qN6pfibDB3DBixMnvdCXt8J3FZowNYFSjgv71RtPaehjD0alalVOqCtHU3qlAGrVAGbidCtUYLUAiV6dCUx8XV4BhUKjY0AJgUB4LE8sA7CkCRSalFYnE72WiBrLSCKCp6TALZNRDEDCwgAKQ/vyRidN9c32K1sbqlCP/C+P9kXJI597PA7HkGJRCLNUGCY767udF9e+9dz1S5ueoRzIEZa1OxcK9td+/fAHvYH0LY6MkgHFIuYwS0ifXe1+qXvn1vk99QfzCwokToUylPrre1/de/vMnf9+5MsSg2HMELegAsl86duvnP3e8y/f1r83v8Li1RO7k/9c2t/avHnt27xpyhRDguEIuxDA3OXXX93+8a0rz6ZvcKgadqUEL73wx+9sb5//WWKTGCOHsxEWM0H71e2ffmF3lPyEkZppVyVYefCw/9a5f3epSvsWh7MMsUgeaL20/dpLu4fJXZUvFCgi46/8i5RNFCCc4bA5JuZ7f/Kp7g9fuLSdvLnY8lEHxz8ItOPcaN7gPAB1tvPl7udupT9nvGSmLLlHSosWLdbJTgpgLna+eVv9hiO1ZIpFOGBEFmejBnrO/tc/0znXTf+sHMuPwD0MrSnETID6/SXPrH/junp3Xiw3atCjxJCRktKu10DHzrZ+pOvpc5cP/6T8CWtt4BATZ4tkBoCvTz8tbTb8TnHiYi/0pgCmPufMUkB1ss9vtU7Trgt9EgyGhIS0zgjRB6RukaSdfHpLPly2xTg2chQJmgRN2qiAa3DBtu5kYXgqAIFYEzTJDAVCnQIqaA+O0wyFjj8q1oY6AB/qd5nLw9JvcpqOOcFMT5dqlg/UAoy5exS2TgGg6DxhkHofqHVCGYf3ho/S904DcHZ6jpZ6lWMY1iogCDxsn8oDduP3BEI9QvSBWgU8YRDeGezsyEk1SNlD8HF51wjQoEAgHNkffXBw+XfJiZbXXCTBT2fZaAJfn4iEEt+z73bTk92jZTxPwOFxVCeGRif0tt4HCtxB+f0P7l//rTlBAN6gjcNicThcfU2NCnjf0NU43L59vf2XZf1A8wzX8JRTgLw+Ckx17SahIZGOyMri7dHalXf6DJdYfovPAgVlRLAzAXwI0gCQU5La8m6SXeH9pi+pWf5lUooIUFKSN6V0A1AE39RyeAYYEpvYNjf4OwP8XNuf50UycnKKKURjSTMALkjzzgpyEhI0LW7ygHvYRh00G7zARQL5dBYU9JtLWvQB52e0VX0MOl5anmOP+3yIjZldpteZijZXuIbBxZ1PAEbkc05GVspZtnX04hlHEDKucpUePYbklCgyNjjDLp9AERhjKSNAQc6IwSzPMQClt37OIeOQ7vQWxJPSZSf2OZMyK1h8jHsbNSgY0Z/tNRWA2HmuVXLIZsxnliw2mROAyR2Rjwmn8vyC0XynrUwQ3PzGs6QX06rDRgD9GIDEjF9pUFLSXyRsowLFIp2/44icDpZ02umq6S3ZxDwupp3hYs1cVMAu1noLBZaMNbJoAD3tl6prOodnTF5feBoBRmGweO8fyClISMlIowkkApRYyqbeZ5YJQrHc4UNieeGYArL8NeUkFcvgJKc/AU56ajxejod+/DT/W/IkQC4P3GoBwoGsFKAf9v2qAGIxej9MU8rTGdNjWtVsJv315aL3YwDYqG5MTDxAPMvTNkJS3ReY6AmtlTrhKsf/AHgAA6ezGE+FAAAAAElFTkSuQmCC", 19690 ol3: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAAAnNCSVQICFXsRgQAAAAJcEhZcwAAD10AAA9dAah0GUAAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAHtUlEQVRo3sWZTWxcVxXHf/d9zIztcTz+pE6cOHXiyLJJadKgKE2oCBLlQwIWSCxYI0WiGxZZIYRAArFAYoEEi0hIsGBBURd0g4iK2lJAaWlaojZVKkU0H26cxB8Zz/f7uPeweHdebDLPnqlQ5l2N5/mN7tz/+Z//OffcM4rPUKCPl0eBAqqfAEAt5Ia1LwCuAg93CyCnAzgj7TstEKMluW+/x0AsWmKBmFggTu4lIpYome2Qw0kA8I2xL9T2Bp5COY6ncJRSDkopFEolANowBEAEATGIGBEkFjEiYkQbI0ZrMaFobYwxkY51pOumpSNTiau6bm7oZX1NP4Ai+ylYADkmGqUPxwSUQsG2ZbcDsBAA2QoGkeSvSZ4kr/alDcRGSyyxbJqqvG5+pHAwbRegVMz+leTBY7qcbTee8vsmQycRmnL6CkD1G4DXFl0fGegvANfpnws8+947AwqFg2MDSjAYzJY80QuAHl2gcPDJF3PiDLiimtIQC0ETEhD3klE8AJeuASg8CgeHir7vLBVOjwypQK3plyoromRNtzSamJg6QbcgvJ7C0J0YnCweG/jek/Ozw5q6bEiFiIHz+wNWBv68+rPNmlQjYnKE1Ai6cYfXA/W5Q6Uvl84f3zel3vH+SIDYoVAeofOdqa9PvbHn/PoDHYZ4eDSpE3fJgLs79YXToz858uxJ5+/en4jQ6hHr5OPZlZHhpcM/4BUv9PFw8agQdQVg1+UHnx/75fG5Gf83lFWGVUrQsmmu/HBsbN8f3Mi1MVLeGUJ3Lig8P/a7s5MN97c01I5+VUIk91err0/fLqFwgBHKOzmimzyQPzX2q1OTZfeianUVKCLNr93EZxiFIOyhnB0Tu6vf/XTp54uzY+4r3S1veYj5CEPBjqFsA3cDoEaLXy199rj/Is0eM2XILXzy5MkzSO6TAvAOFF84qf5KRfWYYhE2aJCzI5MDbxf7B58pTpf89x8qX1yWGKXKFaUBZIF1tWo/KzJPiYi3VAgYbrFEnpiYiBzBTgx0ts99YvDcvHr7YSBJka/Q4k1u3jz5eQ/EYebkXvL241NUeZN/31gkDwibhHjk8PGzTh+OrWw7X/6g/+TB8nuJrQCc4Z/KU08rb+1f/1gCSqy9NUNoP72txtXRb40dfJ+nkgMEZTw78riZLhDRndNP3vGG9GBKnRzhrppmilfhmcWoRYkxyuxv86euUaT24h4W2WN53WQmheB1ygc7MaCKuc+N5LeW6wfOXeUorwFQZIV5RlnbNqcGjBMyaAFUcfHwcHHxOznBakA6JQq34B4dkXtt+8QjvnCQa
19690/Z/jxpFCmdbpPSJI7NyhMVzK/j2UQuFi4OLkz57FECcIcGCU8yZeirQvdxjjuvpTKGAem2EcjpjkjnUC5cvfIm/bRG3Y4e7AwOmEwPKOJotfhvlPj61dGaBEChtAdD88Yeq9et1LqWOUTj2lYzOItSmcxi2ZDXUw+k0n0bqDoXDJBsMM8rHKeIKFbxgIV9nL3cSFlPpZQBoa6AjgCYXK2YkndbckkxmWWfu2D00ozzYNinOlagwbRct/k92zNJARxFK01yur/mX2wDWGE0jfuHyNfa+Y6hQYNsmJQ45hqwwFaPpOVo6s2zDsCMDgsBq2sBR9xj8ZvX70+LJc9w+scA1Sjz49rjMy7zMywE5IY64PMcNDlkHKCbt9xhMZwhOooGODGhMzVyqTUxIm4Pll9797ixnWFZ3WORdSqz//hI+Pv7LT5dXOcNZltUa49y3qplC0Hb5uBMAbwcGDKYS/eLu6YMfrSZCUhWY+QCfGZ7iZYRbarSdYMfd0bvXazh8ii/yF2vcAVwitB1hZirWnROREFLYjN4uLQ5QTZ/WmeA2VwDUHbBks351HRxK3OaqtTTHEQwxmpjkxJApQh111kBAvBH+9O7y/KveFsfcYyNj82qywqZdxmWBAjEREbHdkrNEqNE6o6qJiVeiC4UPHuqg20PvExxGE6YAWp2jwEvabmIyqpoGuTB4ozEwd6lKvYflRzgBBIQWQrQjAG2MZABoEeJH4UU3N8f1rC/psPyz+AQWQEhIK6s09wACk+EC0NTwcCM3KrDAf6ihd6ui2ccxcrRoEaQg6lnQPYDYSLZlAS1cXBzyLHGfW0SZPDgMscgBDK10BARUs48mVgNxtl2GKh6ObVpOM8Uy94hsZpe0nakoMMdhPGJreRtAg9YuJ6NIwp18G7OJsilVyHGIQ2yySZ0WIYocQ+xhknEUhiYRoQUQ0KJBbSfleAChjvQuh4wypbQLovEpMWHrnPY2K0RoG/eR5SCgQXVn1SQAJNpNWiFlhm0+i8jZIrMNoN0j0jbhJMoPaOwu2sQFJt69oRKyadNqTGQBOFsAiM34CQchIdVuwtYyEOgu4jumQosiEX5a6aq0S9Z2T2zTThfdkS0MRN21lISAiBwD5KwDnLReStp0MZomrc4bTyaAhql131gztAhw8cnhWxeIBRASEWbVPju5wAS9/VYgdnthGwPSe5uynYqlpun9EuCTzHt0O67r5uP8teRRAC25H/cXgNmQvgKomhXdLwB2M7pu0pTyeK70mJYUm251sLfo/T8AGEoKes8eIGZ43E5wk36BBwhO2mbqgwZa9C0CAP4LFLGzNDDzmrAAAAAASUVORK5CYII=", 19691 lego: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD8AAAA/CAQAAAD9VthUAAAAAnNCSVQICFXsRgQAAAAJcEhZcwAADzoAAA86AZc528IAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAHvElEQVRYw8WZWWxcVxnHf+fec2fGu+M4qbPH2EmTLk4FApqQNrgiVYyKeClIwBsvPCAQkZAQPND2gRekCoGQEItYHhBLurCksoTKA6QNJW0CBZLWCc3qEBvHy3hsz93O+XiYMxM7nhmPGxGf++Dre2fO7/v+5/u+s4zigzSxVq3osaZNAwzkuq1nPeUrAE9p99JTAKWn5WYEwErpv9TdGbECRlKMgBEjRlIgsqlAKBBaSKUokAjgkcFz+Ce6BvM9sVbKU55WKKWUh1IeoJQCFhsgIIIgIohYEWwq1ooVK9ZasdbG1hhrjUmMsYlZsJEJzYIpmLwZs/8xZ9JpaGYHOYfPyvrChrdbpAxbjFRltCqhnQ2yxBTKf0WQUgNrwYqIFStGUkkllqIU5E/2aQBbEV8pz/ZM3Or8/95UmeUB+J63RiHoAWi1ZHTvNl6pNfXe99Taeq/W1HvuYOzvKG5c4q1afIWHj4eHBwgWwWCQ1aWvBvC8VXngE5DbmO3UxrOeqEhmTFEcPiIiadwEfVttWxmd623tyu7Mfnrjru5cM0Th+Nyp2Z/MztvJNDLkSImZJ27MhNWIr8j1tn+g9at7+/ubivaSHYkmjPF1f+sj7Uc3Xc29VPjm1JSJEzJkCZkjaVT8hvzubDvQ8cz9AwPeG/rHFD3BZkoeTqLwCuqzrQf7nw9+UJhOidEEFAhX0sCJr1fyXm/uPLr5849n/u1/j3mMWtqtYEFm5v/2pXUHdhzNjgaxzunQQzNX3wDdWIT0dT3bP3Qo8wIXSVWNDpWQys2xmW/3fbn1WpAWXUrWNaARvN+/7lu7jzysf8q4siuEh5A8fX5/+8XepLyEs8zfCd7raP/K9scf1T9iQjUUzU+JynOR3TQBgpAS1a16dVtusONTH8kc42ZjcFFKEApcJyBHjizt+O8Wr3e2P7Uv+3curyT7InhJ8nFCMmTJkqWlVlnzVsj0psc69vbrV1SyKnjJgCsEZMiQoanWINcfe39v6xfv808Suu6f5EVlQA7QAcC/1DXp42GmuazOiaJbjjDFSTUNCLOEZMiQEJAjrZYB9b0PmoPe7fpNZQAkYFb1A9CphtWwGlbNkmX/R59TpzhPAAwdf37XKWac1JZJAnc1VSfp0ufSqtK3NT/Y3DJVKZ5tYbHiwfvJAjc5dO7Pw4cZOb4vc51ccvZjh7ZfubaTC8y4evgeAjQaTYCpgZfq06TXpD++Rd6hHHTdZ8JKDs8yAsAD92/gjxSfGNvYzp7Wt3nj6sS2D5NxtXAeHNwnIFpOqSe+bg+2d6ejFXzXS8WlJUSyhBiKoAqj1yFuYQLQZCvFOMLDx8evPFuOF7HV0sqzXmsuP1mJ5tbfVirYc++VITnItvyN8rhJjqIrL7qS50KCX1mWeLXFr5Z02nqiJ2+lXOasIQHJkD75C6DjtQ8dH6Eg99FHyD+LBRclaomnqgL3lo++w4utWsBVbNYtr1htYZFBZgm2299Z5rmXl4+ZtwaPjDlt9CJ0gIeqXNXFN7WKDtMLnW1y+9e6Txc5z2le25Te0BTVic89ovf3yIXE1QeP4FbJbmCla21V723evjklncued/0mZA6AcEABfH/6rXzb2IM5fJD1zLvIB02zm3ak+iK0hK8mvmBnzA/Hoy3LJoyW4XIITn5daAbaX0w3XBnIIsCBL7zDpFNvPWoRvBY+larBZ5Gb6eX20xXxf/2QDMkgmc+sl8MyJH2cf/Seka3yGFv+kR7Ok/1riwxhvruJUYffhGCxWKS0IqqReFXFN5g583qaNokC0aSf/JUaVn95ufNrJ9SwGlapMkkUXuPMAy/E24CJbQVeVWeIXDAFbEYwWCymes3XAMZW9d5gC8k3Rn++79hJjErvvcBB0P53/sBBAOa5knmdnWwl
19691ywlQZ7mHfQivOsd6yVDEkGIwxDVrfo2yY4nJ5tMTLe9rkYKSkUtcEqXk9/DKok9d5nLlfpzxyn0Tu7Gk7jLVNx8eQFw98oUi6Vz07NiZ3c/4y+bz+i1gHxliEhJnQFKn6MbVu01ISRaSX2b8vk/4q4D77GErCTGxM2EBW1P8pLr4YJkjiKORhZ91hR1qpsG9m89O9pASOXxMXF6wrCb0ACIidBJe8ZNdjHID24DsA/RhCImInAnztQqr897UeI1lDp3ToU8TO2jiat39q0cLD7GJlNBdERFhLd8dPjamtldx98K8dhNGD91cZ6zKPl6hyNJPP5rYIcsGFGprVva+Nl4GF455lVzI0UcvU0ySX7R5aKabHnrwMRSJlhiQr7fT1QCprYPnmKHgzjQtliwZNrIZHyHBkHHLSMG4KI+JK6Lna+9wFuETUzecLAUHN6QkBARofHwCFImr6Mbld+Lw0Upwhy/acKWUMswS07YI77tllHJTqsW4t4lLtcLKBwyl0JN05YQSiqS0knW+a7eGu4W3rrgmJMwRNpCkLvRsaBoqKAkzZGgi66S/HV+Sf4GQxvor4xPbYDkVIuLS2RZ6CV4wRMQkNNpXGb9go1V8BSElJXRrWIXCupM9We2hvMPPG1bbaqxf3sWhamTzhjVpHsCc/a9dQ3xo82uJL9jRNRLfTTnnBO+u/pTkLT5c8fPNd9nt5tLmRbsVynbsXR704Bbeq775v0uht3btfyZT7OA5knjdAAAAAElFTkSuQmCC" 19692}; 19693/** Overwrite to handle brickSize 19694* @param {string} key 19695* @param {any} val 19696*/ 19697ol.filter.Lego.prototype.set = function (key, val) { 19698 ol.filter.Base.prototype.set.call(this, key, val); 19699 if (key=="brickSize" && this.pattern && this.pattern.canvas.width!=val) { 19700 this.setBrick(val); 19701 } 19702} 19703/** Set the current brick 19704* @param {number} width the pattern width, default 30 19705* @param {'brick'|'ol3'|'lego'|undefined} img the pattern, default ol3 19706* @param {string} crossOrigin 19707*/ 19708ol.filter.Lego.prototype.setBrick = function (width, img, crossOrigin) { 19709 width = Number(width) || 30; 19710 if (typeof(img) === 'string') { 19711 var i = new Image; 19712 i.src = this.img[img] || this.img.ol3; 19713 i.crossOrigin = crossOrigin || null; 19714 img = i; 19715 } 19716 if (img) this.pattern.img = img; 19717 if (!this.pattern.img.width) { 19718 var self = this; 19719 this.pattern.img.onload = function() { 19720 self.setBrick(width,img); 19721 } 19722 return; 19723 } 19724 this.pattern.canvas.width = this.pattern.canvas.height = width; 19725 this.pattern.ctx = this.pattern.canvas.getContext("2d"); 19726 this.pattern.ctx.fillStyle = this.pattern.ctx.createPattern (this.pattern.img, 'repeat'); 19727 this.set("brickSize", width); 19728 if (img) this.set("img", img.src); 19729}; 19730/** Get translated pattern 19731* @param {number} offsetX x offset 19732* @param {number} offsetY y offset 19733*/ 19734ol.filter.Lego.prototype.getPattern = function (offsetX, offsetY) { 19735 if (!this.pattern.ctx) return "transparent"; 19736 //return this.pattern.ctx.fillStyle 19737 var c = this.pattern.canvas; 19738 var ctx = this.pattern.ctx; 19739 var sc = c.width / this.pattern.img.width; 19740 ctx.save(); 19741 ctx.clearRect(0,0,c.width,c.height); 19742 ctx.scale(sc,sc); 19743 offsetX /= sc; 19744 offsetY /= sc; 19745 ctx.translate(offsetX, offsetY); 19746 ctx.beginPath(); 19747 ctx.clearRect(-2*c.width, -2*c.height, 4*c.width, 4*c.height); 19748 ctx.rect(-offsetX, -offsetY, 2*c.width/sc, 2*c.height/sc); 19749 ctx.fill(); 19750 ctx.restore(); 19751 return ctx.createPattern(c, 'repeat'); 19752}; 19753/** Postcompose operation 19754*/ 19755ol.filter.Lego.prototype.postcompose = function(e) { 19756 // Set back color hue 19757 var ctx = e.context; 19758 var canvas = ctx.canvas; 19759 var ratio = e.frameState.pixelRatio; 19760 // ol v6+ 19761 if (e.type==='postrender') { 19762 ratio = 1; 19763 } 19764 ctx.save(); 19765 // resize 19766 var step = this.pattern.canvas.width*ratio; 19767 var p = e.frameState.extent; 19768 var res = e.frameState.viewState.resolution/ratio; 19769 var offset = [ -Math.round((p[0]/res)%step), Math.round((p[1]/res)%step) ]; 19770 var ctx2 = this.internal_.getContext("2d"); 19771 var w = this.internal_.width = canvas.width; 19772 var h = this.internal_.height = canvas.height; 19773 // No smoothing please 19774 ctx2.webkitImageSmoothingEnabled = 19775 ctx2.mozImageSmoothingEnabled = 19776 ctx2.msImageSmoothingEnabled = 19777 ctx2.imageSmoothingEnabled = false; 19778 var w2 = Math.floor((w-offset[0])/step); 19779 var h2 = Math.floor((h-offset[1])/step); 19780 ctx2.drawImage (canvas, offset[0], offset[1], w2*step, h2*step, 0, 0, w2, h2); 19781 // 19782 ctx.webkitImageSmoothingEnabled = 19783 ctx.mozImageSmoothingEnabled = 19784 ctx.msImageSmoothingEnabled = 19785 ctx.imageSmoothingEnabled = false; 19786 ctx.clearRect (0, 0, w,h); 19787 ctx.drawImage (this.internal_, 0,0, w2,h2, offset[0],offset[1], w2*step, h2*step); 19788/* 19789 for (var x=offset[0]; x<w; x+=step) for (var y=offset[1]; y<h; y+=step) 19790 { if (x>=0 && y<h) ctx2.drawImage (canvas, x, y, 1, 1, x, y, step, step); 19791 } 19792 ctx.clearRect (0, 0, w,h); 19793 ctx.drawImage (c, 0, 0);
19794*/ 19795 // Draw brick stud 19796 ctx.scale(ratio,ratio); 19797 ctx.fillStyle = this.getPattern (offset[0]/ratio, offset[1]/ratio); 19798 ctx.rect(0,0, w, h); 19799 ctx.fill(); 19800 ctx.restore(); 19801}; 19802 19803/* Copyright (c) 2016 Jean-Marc VIGLINO, 19804 released under the CeCILL-B license (French BSD license) 19805 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19806*/ 19807/** @typedef {Object} FilterPointillismOptions 19808 * @property {number} saturate saturation, default 2 19809 */ 19810/** A pointillism filter to turn maps into pointillism paintings 19811 * @constructor 19812 * @extends {ol.filter.Base} 19813 * @param {object} options 19814 * @param {boolean} [options.active] 19815 * @param {number} [options.scale=1] 19816 */ 19817ol.filter.Paper = function(options) { 19818 options = options || {}; 19819 ol.filter.Base.call(this, options); 19820 this._svgfilter = new ol.ext.SVGFilter.Paper(options); 19821}; 19822ol.ext.inherits(ol.filter.Paper, ol.filter.Base); 19823/** @private 19824 */ 19825ol.filter.Paper.prototype.precompose = function(/* e */) { 19826}; 19827/** @private 19828 */ 19829ol.filter.Paper.prototype.postcompose = function(e) { 19830 // var ratio = e.frameState.pixelRatio; 19831 var ctx = e.context; 19832 var canvas = ctx.canvas; 19833 ctx.save(); 19834 ctx.filter = 'url(#' + this._svgfilter.getId() +')'; 19835 ctx.globalAlpha = 1; 19836 ctx.globalCompositeOperation = 'multiply'; 19837 ctx.fillRect(0, 0, canvas.width, canvas.height); 19838 ctx.restore(); 19839}; 19840/** Set filter light 19841 * @param {number} light light option. 0: darker, 100: lighter 19842 */ 19843ol.filter.Paper.prototype.setLight = function(light) { 19844 this._svgfilter.setLight(light); 19845}; 19846 19847/* Copyright (c) 2016 Jean-Marc VIGLINO, 19848 released under the CeCILL-B license (French BSD license) 19849 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19850*/ 19851/** @typedef {Object} FilterPencilSketchOptions 19852 * @property {number} blur blur value in pixel, default 8 19853 * @property {number} value intensity value [0,1], default .8 19854 */ 19855/** Colorize map or layer 19856 * Original idea: https://www.freecodecamp.org/news/sketchify-turn-any-image-into-a-pencil-sketch-with-10-lines-of-code-cf67fa4f68ce/ 19857 * @constructor 19858 * @requires ol.filter 19859 * @extends {ol.filter.Base} 19860 * @param {FilterPencilSketchOptions} options 19861 */ 19862ol.filter.PencilSketch = function(options) { 19863 options = options || {}; 19864 ol.filter.Base.call(this, options); 19865 this.set('blur', options.blur || 8); 19866 this.set('intensity', options.intensity || .8); 19867}; 19868ol.ext.inherits(ol.filter.PencilSketch, ol.filter.Base); 19869/** @private 19870 */ 19871ol.filter.PencilSketch.prototype.precompose = function(/* e */) { 19872}; 19873/** @private 19874 */ 19875ol.filter.PencilSketch.prototype.postcompose = function(e) { 19876 // Set back color hue 19877 var ctx = e.context; 19878 var canvas = ctx.canvas; 19879 var w = canvas.width; 19880 var h = canvas.height; 19881 // Grayscale image 19882 var bwimg = document.createElement('canvas'); 19883 bwimg.width = w; 19884 bwimg.height = h; 19885 var bwctx = bwimg.getContext('2d'); 19886 bwctx.filter = 'grayscale(1) invert(1) blur('+this.get('blur')+'px)'; 19887 bwctx.drawImage(canvas, 0,0); 19888 ctx.save(); 19889 if (!this.get('color')) { 19890 ctx.filter = 'grayscale(1)'; 19891 ctx.drawImage(canvas, 0,0); 19892 } else { 19893 ctx.globalCompositeOperation = 'darken'; 19894 ctx.globalAlpha = .3; 19895 ctx.drawImage(canvas, 0,0); 19896 } 19897 ctx.globalCompositeOperation = 'color-dodge'; 19898 ctx.globalAlpha = this.get('intensity'); 19899 ctx.drawImage(bwimg, 0,0); 19900 ctx.restore(); 19901}; 19902 19903/* Copyright (c) 2016 Jean-Marc VIGLINO, 19904 released under the CeCILL-B license (French BSD license) 19905 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19906*/ 19907/** @typedef {Object} FilterPointillismOptions 19908 * @property {number} saturate saturation, default 2 19909 */ 19910/** A pointillism filter to turn maps into pointillism paintings 19911 * @constructor 19912 * @extends {ol.filter.Base} 19913 * @param {FilterPointillismOptions} options 19914 */ 19915ol.filter.Pointillism = function(options) { 19916 options = options || {}; 19917 ol.filter.Base.call(this, options); 19918 this.set('saturate', Number(options.saturate) || 2); 19919 this.pixels = []; 19920}; 19921ol.ext.inherits(ol.filter.Pointillism, ol.filter.Base); 19922/** Create points to place on the map 19923 * @private 19924 */ 19925ol.filter.Pointillism.prototype._getPixels = function(nb) { 19926 if (nb > this.pixels.length) { 19927 while (this.pixels.length < nb) { 19928 this.pixels.push([Math.random(), Math.random(), Math.random()*4+2]); 19929 } 19930 } 19931 return nb; 19932}; 19933/** @private 19934 */ 19935ol.filter.Pointillism.prototype.precompose = function(/* e */) { 19936}; 19937/** @private 19938 */ 19939ol.filter.Pointillism.prototype.postcompose = function(e) { 19940 // var ratio = e.frameState.pixelRatio; 19941 // Set back color hue 19942 var ctx = e.context; 19943 var canvas = ctx.canvas; 19944 var w = canvas.width;
19945 var h = canvas.height; 19946 // Grayscale image 19947 var img = document.createElement('canvas'); 19948 img.width = w; 19949 img.height = h; 19950 var ictx = img.getContext('2d'); 19951 ictx.filter = 'saturate('+Math.round(2*this.get('saturate')*100)+'%)'; 19952 ictx.drawImage(canvas, 0,0); 19953 ctx.save(); 19954 // Saturate and blur 19955 ctx.filter = 'blur(3px) saturate('+(this.get('saturate')*100)+'%)'; 19956 ctx.drawImage(canvas, 0,0); 19957 // ctx.clearRect(0,0,w,h); // debug 19958 // Draw points 19959 ctx.filter = 'none'; 19960 ctx.opacity = .5; 19961 var max = this._getPixels(w*h/50); 19962 for (var i=0; i<max; i++) { 19963 var x = Math.floor(this.pixels[i][0]*w); 19964 var y = Math.floor(this.pixels[i][1]*h); 19965 ctx.fillStyle = ol.color.asString(ictx.getImageData(x, y, 1, 1).data); 19966 ctx.beginPath(); 19967 ctx.arc(x,y, this.pixels[i][2], 0, 2*Math.PI); 19968 ctx.fill(); 19969 } 19970 ctx.restore(); 19971}; 19972 19973/* Copyright (c) 2016 Jean-Marc VIGLINO, 19974 released under the CeCILL-B license (French BSD license) 19975 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 19976*/ 19977/** Add a canvas Context2D SVG filter to a layer 19978 * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter 19979 * @constructor 19980 * @requires ol.filter 19981 * @extends {ol.filter.Base} 19982 * @param {ol.ext.SVGFilter|Array<ol.ext.SVGFilter>} filters 19983 */ 19984ol.filter.SVGFilter = function(filters) { 19985 ol.filter.Base.call(this); 19986 this._svg = {}; 19987 if (filters) { 19988 if (!(filters instanceof Array)) filters = [filters]; 19989 filters.forEach(function(f) { 19990 this.addSVGFilter(f); 19991 }.bind(this)); 19992 } 19993}; 19994ol.ext.inherits(ol.filter.SVGFilter, ol.filter.Base); 19995/** Add a new svg filter 19996 * @param {ol.ext.SVGFilter} filter 19997 */ 19998ol.filter.SVGFilter.prototype.addSVGFilter = function(filter) { 19999 var url = '#'+filter.getId(); 20000 this._svg[url] = 1; 20001 this.dispatchEvent({ type: 'propertychange', key: 'svg', oldValue: this._svg }); 20002}; 20003/** Remove a svg filter 20004 * @param {ol.ext.SVGFilter} filter 20005 */ 20006ol.filter.SVGFilter.prototype.removeSVGFilter = function(filter) { 20007 var url = '#'+filter.getId(); 20008 delete this._svg[url] 20009 this.dispatchEvent({ type: 'propertychange', key: 'svg', oldValue: this._svg }); 20010}; 20011/** 20012 * @private 20013 */ 20014ol.filter.SVGFilter.prototype.precompose = function() { 20015}; 20016/** 20017 * @private 20018 */ 20019ol.filter.SVGFilter.prototype.postcompose = function(e) { 20020 var filter = [] 20021 // Set filters 20022 for (var f in this._svg) { 20023 filter.push('url('+f+')'); 20024 } 20025 filter = filter.join(' '); 20026 var canvas = document.createElement('canvas'); 20027 canvas.width = e.context.canvas.width; 20028 canvas.height = e.context.canvas.height; 20029 canvas.getContext('2d').drawImage(e.context.canvas,0,0); 20030 // Apply filter 20031 if (filter) { 20032 e.context.save(); 20033 e.context.clearRect(0,0,canvas.width, canvas.height); 20034 e.context.filter = filter; 20035 e.context.drawImage(canvas, 0,0); 20036 e.context.restore(); 20037 } 20038}; 20039 20040/* Copyright (c) 2016 Jean-Marc VIGLINO, 20041 released under the CeCILL-B license (French BSD license) 20042 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 20043*/ 20044/** @typedef {Object} FilterTextureOptions 20045 * @property {Image | undefined} img Image object for the texture 20046 * @property {string} src Image source URI 20047 * @property {number} scale scale to draw the image. Default 1. 20048 * @property {number} [opacity] 20049 * @property {boolean} rotate Whether to rotate the texture with the view (may cause animation lags on mobile or slow devices). Default is true. 20050 * @property {null | string | undefined} crossOrigin The crossOrigin attribute for loaded images. 20051 */ 20052/** Add texture effects on maps or layers 20053 * @constructor 20054 * @requires ol.filter 20055 * @extends {ol.filter.Base} 20056 * @param {FilterTextureOptions} options 20057 */ 20058ol.filter.Texture = function(options) 20059{ ol.filter.Base.call(this, options); 20060 this.setFilter(options); 20061} 20062ol.ext.inherits(ol.filter.Texture, ol.filter.Base); 20063/** Set texture 20064 * @param {FilterTextureOptions} [options] 20065 */ 20066ol.filter.Texture.prototype.setFilter = function(options) 20067{ var img; 20068 options = options || {}; 20069 if (options.img) img = options.img; 20070 else 20071 { img = new Image(); 20072 if (options.src) { 20073 // Look for a texture stored in ol.filter.Texture.Image 20074 if (ol.filter.Texture.Image && ol.filter.Texture.Image[options.src]) { 20075 img.src = ol.filter.Texture.Image[options.src]; 20076 } 20077 // default source 20078 else { 20079 if (!img.src) img.src = options.src; 20080 } 20081 } 20082 img.crossOrigin = options.crossOrigin || null; 20083 } 20084 this.set('rotateWithView', options.rotateWithView !== false); 20085 this.set('opacity', typeof(options.opacity)=='number' ? options.opacity : 1); 20086 this.set('ready', false); 20087 var self = this; 20088 function setPattern(img) 20089 { self.pattern = {}; 20090 self.pattern.scale = options.scale || 1;
20091 self.pattern.canvas = document.createElement('canvas'); 20092 self.pattern.canvas.width = img.width * self.pattern.scale; 20093 self.pattern.canvas.height = img.height * self.pattern.scale; 20094 self.pattern.canvas.width = img.width;// * self.pattern.scale; 20095 self.pattern.canvas.height = img.height;// * self.pattern.scale; 20096 self.pattern.ctx = self.pattern.canvas.getContext("2d"); 20097 self.pattern.ctx.fillStyle = self.pattern.ctx.createPattern(img, 'repeat'); 20098 // Force refresh 20099 self.set('ready', true); 20100 } 20101 if (img.width) 20102 { setPattern(img); 20103 } 20104 else 20105 { img.onload = function() 20106 { setPattern(img); 20107 } 20108 } 20109} 20110/** Get translated pattern 20111 * @param {number} offsetX x offset 20112 * @param {number} offsetY y offset 20113 */ 20114ol.filter.Texture.prototype.getPattern = function (offsetX, offsetY) 20115{ var c = this.pattern.canvas; 20116 var ctx = this.pattern.ctx; 20117 ctx.save(); 20118 /* 20119 offsetX /= this.pattern.scale; 20120 offsetY /= this.pattern.scale; 20121 ctx.scale(this.pattern.scale,this.pattern.scale); 20122 */ 20123 ctx.translate(-offsetX, offsetY); 20124 ctx.beginPath(); 20125 ctx.rect(offsetX, -offsetY, c.width, c.height); 20126 ctx.fill(); 20127 ctx.restore(); 20128 return ctx.createPattern(c, 'repeat'); 20129} 20130/** Draw pattern over the map on postcompose */ 20131ol.filter.Texture.prototype.postcompose = function(e) 20132{ // not ready 20133 if (!this.pattern) return; 20134 // Set back color hue 20135 var ctx = e.context; 20136 var canvas = ctx.canvas; 20137 var m = 1.5 * Math.max(canvas.width, canvas.height); 20138 var mt = e.frameState.pixelToCoordinateTransform; 20139 // Old version (matrix) 20140 if (!mt) 20141 { mt = e.frameState.pixelToCoordinateMatrix, 20142 mt[2] = mt[4]; 20143 mt[3] = mt[5]; 20144 mt[4] = mt[12]; 20145 mt[5] = mt[13]; 20146 } 20147 var ratio = e.frameState.pixelRatio; 20148 var res = e.frameState.viewState.resolution; 20149 var w = canvas.width/2, 20150 h = canvas.height/2; 20151 ctx.save(); 20152 ctx.globalCompositeOperation = "multiply"; 20153 //ctx.globalCompositeOperation = "overlay"; 20154 //ctx.globalCompositeOperation = "color"; 20155 ctx.globalAlpha = this.get('opacity'); 20156 ctx.scale(ratio*this.pattern.scale,ratio*this.pattern.scale); 20157 if (this.get('rotateWithView')) 20158 { // Translate pattern 20159 res *= this.pattern.scale 20160 ctx.fillStyle = this.getPattern ((w*mt[0] + h*mt[1] + mt[4])/res, (w*mt[2] + h*mt[3] + mt[5])/res); 20161 // Rotate on canvas center and fill 20162 ctx.translate(w/this.pattern.scale, h/this.pattern.scale); 20163 ctx.rotate(e.frameState.viewState.rotation); 20164 ctx.beginPath(); 20165 ctx.rect(-w-m, -h-m, 2*m, 2*m); 20166 ctx.fill(); 20167 } 20168 else 20169 { 20170 /**/ 20171 var dx = -(w*mt[0] + h*mt[1] + mt[4])/res; 20172 var dy = (w*mt[2] + h*mt[3] + mt[5])/res; 20173 var cos = Math.cos(e.frameState.viewState.rotation); 20174 var sin = Math.sin(e.frameState.viewState.rotation); 20175 var offsetX = (dx*cos - dy*sin) / this.pattern.scale; 20176 var offsetY = (dx*sin + dy*cos) / this.pattern.scale; 20177 ctx.translate(offsetX, offsetY); 20178 ctx.beginPath(); 20179 ctx.fillStyle = this.pattern.ctx.fillStyle; 20180 ctx.rect(-offsetX -m , -offsetY -m, 2*m, 2*m); 20181 ctx.fill(); 20182 /* //old version without centered rotation 20183 var offsetX = -(e.frameState.extent[0]/res) % this.pattern.canvas.width; 20184 var offsetY = (e.frameState.extent[1]/res) % this.pattern.canvas.height; 20185 ctx.rotate(e.frameState.viewState.rotation); 20186 ctx.translate(offsetX, offsetY); 20187 ctx.beginPath(); 20188 ctx.fillStyle = this.pattern.ctx.fillStyle 20189 ctx.rect(-offsetX -m , -offsetY -m, 2*m, 2*m); 20190 ctx.fill(); 20191 */ 20192 } 20193 ctx.restore(); 20194} 20195 20196/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ 20197/** Feature format for reading and writing data in the GeoJSONX format. 20198 * @constructor 20199 * @extends {ol.format.GeoJSON} 20200 * @param {*} options options. 20201 * @param {number} options.decimals number of decimals to save, default 7 for EPSG:4326, 2 for other projections 20202 * @param {boolean|Array<*>} options.deleteNullProperties An array of property values to remove, if false, keep all properties, default [null,undefined,""] 20203 * @param {boolean|Array<*>} options.extended Decode/encode extended GeoJSON with foreign members (id, bbox, title, etc.), default false 20204 * @param {Array<string>|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 20205 * @param {Array<string>|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 20206 * @param {string} [options.layout='XY'] layout layout (XY or XYZ or XYZM) 20207 * @param {ol.ProjectionLike} options.dataProjection Projection of the data we are reading. If not provided `EPSG:4326` 20208 * @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. 20209 */ 20210ol.format.GeoJSONX = function(options) { 20211 options = options || {}; 20212 ol.format.GeoJSON.call (this, options); 20213 this._hash = {}; 20214 this._count = 0; 20215 this._extended = options.extended; 20216 if (typeof(options.whiteList)==='function') { 20217 this._whiteList = options.whiteList; 20218 } else if (options.whiteList && options.whiteList.indexOf) { 20219 this._whiteList = function (k) { return options.whiteList.indexOf(k) > -1 }; 20220 } else { 20221 this._whiteList = function() { return true }; 20222 } 20223 if (typeof(options.blackList)==='function') { 20224 this._blackList = options.blackList; 20225 } else if (options.blackList && options.blackList.indexOf) { 20226 this._blackList = function (k) { return options.blackList.indexOf(k) > -1 }; 20227 } else { 20228 this._blackList = function() { return false }; 20229 } 20230 this._deleteNull = options.deleteNullProperties===false ? false : [null,undefined,""]; 20231 var decimals = 2; 20232 if (!options.dataProjection || options.dataProjection === 'EPSG:4326') decimals = 7; 20233 if (!isNaN(parseInt(options.decimals))) decimals = parseInt(options.decimals); 20234 this._decimals = decimals; 20235 this.setLayout(options.layout || 'XY'); 20236}; 20237ol.ext.inherits(ol.format.GeoJSONX, ol.format.GeoJSON); 20238/** Radix */ 20239ol.format.GeoJSONX.prototype._radix =
20240'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ !#$%&\'()*-.:<=>?@[]^_`{|}~'; 20241/** Radix size */ 20242ol.format.GeoJSONX.prototype._size = ol.format.GeoJSONX.prototype._radix.length; 20243/** GeoSJON types */ 20244ol.format.GeoJSONX.prototype._type = { 20245 "Point": 0, 20246 "LineString": 1, 20247 "Polygon": 2, 20248 "MultiPoint": 3, 20249 "MultiLineString": 4, 20250 "MultiPolygon": 5, 20251 "GeometryCollection": null // Not supported 20252}; 20253/** GeoSJONX types */ 20254ol.format.GeoJSONX.prototype._toType = [ 20255 "Point", 20256 "LineString", 20257 "Polygon", 20258 "MultiPoint", 20259 "MultiLineString", 20260 "MultiPolygon" 20261]; 20262/** Set geometry layout 20263 * @param {string} layout the geometry layout (XY or XYZ or XYZM) 20264 */ 20265ol.format.GeoJSONX.prototype.setLayout = function(layout) { 20266 switch(layout) { 20267 case 'XYZ': 20268 case 'XYZM': { 20269 this._layout = layout; 20270 break; 20271 } 20272 default: { 20273 this._layout = 'XY'; 20274 break; 20275 } 20276 } 20277}; 20278/** Get geometry layout 20279 * @return {string} layout 20280 */ 20281ol.format.GeoJSONX.prototype.getLayout = function() { 20282 return this._layout; 20283}; 20284/** Encode a number 20285 * @param {number} number Number to encode 20286 * @param {number} decimals Number of decimals 20287 * @param {string} 20288 */ 20289ol.format.GeoJSONX.prototype.encodeNumber = function(number, decimals) { 20290 if (isNaN(Number(number)) || number === null || !isFinite(number)) { 20291 number = 0; 20292 } 20293 if (!decimals && decimals!==0) decimals = this._decimals; 20294 // Round number 20295 number = Math.round(number * Math.pow(10, decimals)); 20296 // Zigzag encoding (get positive number) 20297 if (number<0) number = -2*number - 1; 20298 else number = 2*number; 20299 // Encode 20300 var result = ''; 20301 var modulo, residual = number; 20302 while (true) { 20303 modulo = residual % this._size 20304 result = this._radix.charAt(modulo) + result; 20305 residual = Math.floor(residual / this._size); 20306 if (residual == 0) break; 20307 } 20308 return result; 20309}; 20310/** Decode a number 20311 * @param {string} s 20312 * @param {number} decimals Number of decimals 20313 * @return {number} 20314 */ 20315ol.format.GeoJSONX.prototype.decodeNumber = function(s, decimals) { 20316 if (!decimals && decimals!==0) decimals = this._decimals; 20317 var decode = 0; 20318 s.split('').forEach(function (c) { 20319 decode = (decode * this._size) + this._radix.indexOf(c); 20320 }.bind(this)); 20321 // Zigzag encoding 20322 var result = Math.floor(decode/2) 20323 if (result !== decode/2) result = -1-result; 20324 return result / Math.pow(10, decimals); 20325}; 20326/** Encode coordinates 20327 * @param {ol.coordinate|Array<ol.coordinate>} v 20328 * @param {number} decimal 20329 * @return {string|Array<string>} 20330 * @api 20331 */ 20332ol.format.GeoJSONX.prototype.encodeCoordinates = function(v, decimal) { 20333 var i, p, tp; 20334 if (typeof(v[0]) === 'number') { 20335 p = this.encodeNumber(v[0], decimal) +','+ this.encodeNumber(v[1], decimal); 20336 if (this._layout[2]=='Z' && v.length > 2) p += ',' + this.encodeNumber(v[i][2], 2); 20337 if (this._layout[3]=='M' && v.length > 3) p += ',' + this.encodeNumber(v[i][3], 0); 20338 return p; 20339 } else if (v.length && v[0]) { 20340 if (typeof(v[0][0]) === 'number') { 20341 var dxy = [0,0,0,0]; 20342 var xy = []; 20343 var hasZ = (this._layout[2]=='Z' && v[0].length > 2); 20344 var hasM = (this._layout[3]=='M' && v[0].length > 3); 20345 for (i=0; i<v.length; i++) { 20346 tp = [ 20347 Math.round( v[i][0] * Math.pow(10, decimal)), 20348 Math.round( v[i][1] * Math.pow(10, decimal)) 20349 ]; 20350 if (hasZ) tp[2] = v[i][2]; 20351 if (hasM) tp[3] = v[i][3]; 20352 v[i] = tp; 20353 var dx = v[i][0] - dxy[0]; 20354 var dy = v[i][1] - dxy[1]; 20355 if (i==0 || (dx!==0 || dy!==0)) { 20356 p = this.encodeNumber(dx, 0) +',' 20357 + this.encodeNumber(dy, 0) 20358 + (hasZ ? ',' + this.encodeNumber(v[i][2] - dxy[2], 2) : '') 20359 + (hasM ? ',' + this.encodeNumber(v[i][3] - dxy[3], 0) : ''); 20360 xy.push(p); 20361 dxy = v[i]; 20362 } 20363 } 20364 // Almost 2 points... 20365 // if (xy.length<2) xy.push('A,A'); 20366 return xy.join(';'); 20367 } else { 20368 for (i=0; i<v.length; i++) { 20369 v[i] = this.encodeCoordinates(v[i], decimal); 20370 } 20371 return v; 20372 } 20373 } else { 20374 return this.encodeCoordinates([0,0], decimal); 20375 } 20376}; 20377/** Decode coordinates 20378 * @param {string|Array<string>} 20379 * @param {number} decimal Number of decimals 20380 * @return {ol.coordinate|Array<ol.coordinate>} v 20381 * @api 20382 */ 20383ol.format.GeoJSONX.prototype.decodeCoordinates = function(v, decimals) { 20384 var i, p; 20385 if (typeof(v) === 'string') { 20386 v = v.split(';'); 20387 if (v.length>1) { 20388 var pow = Math.pow(10,decimals); 20389 var dxy = [0,0,0,0]; 20390 v.forEach(function(vi, i) { 20391 v[i] = vi.split(','); 20392 v[i][0] = Math.round((this.decodeNumber(v[i][0], decimals) + dxy[0]) * pow) / pow; 20393 v[i][1] = Math.round((this.decodeNumber(v[i][1], decimals) + dxy[1]) * pow) / pow; 20394 if (v[i].length > 2) v[i][2] = Math.round((this.decodeNumber(v[i][2], 2) + dxy[2]) * pow) / pow; 20395 if (v[i].length > 3) v[i][3] = Math.round((this.decodeNumber(v[i][3], 0) + dxy[3]) * pow) / pow; 20396 dxy = v[i]; 20397 }.bind(this)); 20398 return v; 20399 } else { 20400 v = v[0].split(','); 20401 p = [ this.decodeNumber(v[0], decimals), this.decodeNumber(v[1], decimals) ]; 20402 if (v.length > 2) p[2] = this.decodeNumber(v[2], 2); 20403 if (v.length > 3) p[3] = this.decodeNumber(v[3], 0); 20404 return p; 20405 } 20406 } else if (v.length) { 20407 var r = []; 20408 for (i=0; i<v.length; i++) { 20409 r[i] = this.decodeCoordinates(v[i], decimals); 20410 } 20411 return r; 20412 } else { 20413 return [0,0]; 20414 } 20415}; 20416/** Encode an array of features as a GeoJSONX object. 20417 * @param {Array<ol.Feature>} features Features. 20418 * @param {*} options Write options. 20419 * @return {*} GeoJSONX Object. 20420 * @override 20421 * @api 20422 */ 20423ol.format.GeoJSONX.prototype.writeFeaturesObject = function (features, options) { 20424 options = options || {}; 20425 this._count = 0; 20426 this._hash = {}; 20427 var geojson = ol.format.GeoJSON.prototype.writeFeaturesObject.call(this, features, options); 20428 geojson.decimals = this._decimals; 20429 geojson.hashProperties = [];
20430 Object.keys(this._hash).forEach(function(k) { 20431 geojson.hashProperties.push(k); 20432 }.bind(this)); 20433 this._count = 0; 20434 this._hash = {}; 20435 // Push features at the end of the object 20436 var temp = geojson.features; 20437 delete geojson.features; 20438 geojson.features = temp; 20439 return geojson; 20440}; 20441/** Encode a set of features as a GeoJSONX object. 20442 * @param {ol.Feature} feature Feature 20443 * @param {*} options Write options. 20444 * @return {*} GeoJSONX Object. 20445 * @override 20446 * @api 20447 */ 20448ol.format.GeoJSONX.prototype.writeFeatureObject = function(source, options) { 20449 var f0 = ol.format.GeoJSON.prototype.writeFeatureObject.call(this, source, options); 20450 // Only features supported yet 20451 if (f0.type !== 'Feature') throw 'GeoJSONX doesn\'t support '+f0.type+'.'; 20452 var f = []; 20453 // Encode geometry 20454 if (f0.geometry.type==='Point') { 20455 f.push(this.encodeCoordinates(f0.geometry.coordinates, this._decimals)); 20456 } else if (f0.geometry.type==='MultiPoint') { 20457 var pts = []; 20458 f0.geometry.coordinates.forEach(function(p) { 20459 pts.push(this.encodeCoordinates(p, this._decimals)); 20460 }.bind(this)); 20461 f.push ([ 20462 this._type[f0.geometry.type], 20463 pts.join(';') 20464 ]); 20465 } else { 20466 if (!this._type[f0.geometry.type]) { 20467 throw 'GeoJSONX doesn\'t support '+f0.geometry.type+'.'; 20468 } 20469 f.push ([ 20470 this._type[f0.geometry.type], 20471 this.encodeCoordinates(f0.geometry.coordinates, this._decimals) 20472 ]); 20473 } 20474 // Encode properties 20475 var k; 20476 var prop = []; 20477 for (k in f0.properties) { 20478 if (!this._whiteList(k) || this._blackList(k)) continue; 20479 if (!this._hash.hasOwnProperty(k)) { 20480 this._hash[k] = this._count; 20481 this._count++; 20482 } 20483 if (!this._deleteNull || this._deleteNull.indexOf(f0.properties[k])<0) { 20484 prop.push (this._hash[k], f0.properties[k]); 20485 } 20486 } 20487 // Create prop table 20488 if (prop.length || this._extended) { 20489 f.push(prop); 20490 } 20491 // Other properties (id, title, bbox, centerline... 20492 if (this._extended) { 20493 var found = false; 20494 prop = {}; 20495 for (k in f0) { 20496 if (!/^type$|^geometry$|^properties$/.test(k)) { 20497 prop[k] = f0[k]; 20498 found = true; 20499 } 20500 } 20501 if (found) f.push(prop); 20502 } 20503 return f; 20504}; 20505/** Encode a geometry as a GeoJSONX object. 20506 * @param {ol.geom.Geometry} geometry Geometry. 20507 * @param {*} options Write options. 20508 * @return {*} Object. 20509 * @override 20510 * @api 20511 */ 20512ol.format.GeoJSONX.prototype.writeGeometryObject = function(source, options) { 20513 var g = ol.format.GeoJSON.prototype.writeGeometryObject.call(this, source, options); 20514 // Encode geometry 20515 if (g.type==='Point') { 20516 return this.encodeCoordinates(g.coordinates, this._decimals) 20517 } else { 20518 return [ 20519 this._type[g.type], 20520 this.encodeCoordinates(g.coordinates, this._decimals) 20521 ]; 20522 } 20523}; 20524/** Decode a GeoJSONX object. 20525 * @param {*} object GeoJSONX 20526 * @param {*} options Read options. 20527 * @return {Array<ol.Feature>} 20528 * @override 20529 * @api 20530 */ 20531ol.format.GeoJSONX.prototype.readFeaturesFromObject = function (object, options) { 20532 this._hashProperties = object.hashProperties || []; 20533 options = options || {}; 20534 options.decimals = parseInt(object.decimals); 20535 if (!options.decimals && options.decimals!==0) throw 'Bad file format...'; 20536 var features = ol.format.GeoJSON.prototype.readFeaturesFromObject.call(this, object, options); 20537 return features; 20538}; 20539/** Decode GeoJSONX Feature object. 20540 * @param {*} object GeoJSONX 20541 * @param {*} options Read options. 20542 * @return {ol.Feature} 20543 */ 20544ol.format.GeoJSONX.prototype.readFeatureFromObject = function (f0, options) { 20545 var f = { 20546 type: 'Feature' 20547 } 20548 if (typeof(f0[0]) === 'string') { 20549 f.geometry = { 20550 type: 'Point', 20551 coordinates: this.decodeCoordinates(f0[0], typeof(options.decimals) === 'number' ? options.decimals : this.decimals) 20552 } 20553 } else { 20554 f.geometry = { 20555 type: this._toType[f0[0][0]] 20556 } 20557 if (f.geometry.type === 'MultiPoint') { 20558 var g = f.geometry.coordinates = []; 20559 var coords = f0[0][1].split(';'); 20560 coords.forEach(function(c) { 20561 c = c.split(','); 20562 g.push([this.decodeNumber(c[0], options.decimals), this.decodeNumber(c[1], options.decimals)]) 20563 }.bind(this)); 20564 } else { 20565 f.geometry.coordinates = this.decodeCoordinates(f0[0][1], typeof(options.decimals) === 'number' ? options.decimals : this.decimals); 20566 } 20567 } 20568 if (this._hashProperties && f0[1]) { 20569 f.properties = {}; 20570 var t = f0[1]; 20571 for (var i=0; i<t.length; i+=2) { 20572 f.properties[this._hashProperties[t[i]]] = t[i+1]; 20573 } 20574 } else { 20575 f.properties = f0[1]; 20576 } 20577 // Extended properties 20578 if (f0[2]) { 20579 for (var k in f0[2]) { 20580 f[k] = f0[2][k]; 20581 } 20582 } 20583 var feature = ol.format.GeoJSON.prototype.readFeatureFromObject.call(this, f, options); 20584 return feature; 20585}; 20586 20587/** Feature format for reading and writing data in the GeoJSONP format, 20588 * using Polyline Algorithm to encode geometry. 20589 * @constructor 20590 * @extends {ol.format.GeoJSON} 20591 * @param {*} options options. 20592 * @param {number} options.decimals number of decimals to save, default 6 20593 * @param {ol.ProjectionLike} options.dataProjection Projection of the data we are reading. If not provided `EPSG:4326` 20594 * @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. 20595 */ 20596ol.format.GeoJSONP = function(options) { 20597 options = options || {}; 20598 ol.format.GeoJSONX.call (this, options); 20599 this._lineFormat = new ol.format.Polyline({ factor: Math.pow(10, options.decimals || 6) }); 20600}; 20601ol.ext.inherits(ol.format.GeoJSONP, ol.format.GeoJSONX); 20602/** Encode coordinates 20603 * @param {ol.coordinate|Array<ol.coordinate>} v 20604 * @return {string|Array<string>} 20605 * @api 20606 */ 20607ol.format.GeoJSONP.prototype.encodeCoordinates = function(v) { 20608 var g; 20609 if (typeof(v[0]) === 'number') { 20610 g = new ol.geom.Point(v);
20611 return this._lineFormat.writeGeometry(g); 20612 } else if (v.length && v[0]) { 20613 var tab = (typeof(v[0][0]) === 'number'); 20614 if (tab) { 20615 g = new ol.geom.LineString(v); 20616 return this._lineFormat.writeGeometry(g); 20617 } else { 20618 var r = []; 20619 for (var i=0; i<v.length; i++) { 20620 r[i] = this.encodeCoordinates(v[i]); 20621 } 20622 return r; 20623 } 20624 } else { 20625 return this.encodeCoordinates([0,0]); 20626 } 20627}; 20628/** Decode coordinates 20629 * @param {string|Array<string>} 20630 * @return {ol.coordinate|Array<ol.coordinate>} v 20631 * @api 20632 */ 20633ol.format.GeoJSONP.prototype.decodeCoordinates = function(v) { 20634 var i, g; 20635 if (typeof(v) === 'string') { 20636 g = this._lineFormat.readGeometry(v); 20637 return g.getCoordinates()[0]; 20638 } else if (v.length) { 20639 var tab = (typeof(v[0]) === 'string'); 20640 var r = []; 20641 if (tab) { 20642 for (i=0; i<v.length; i++) { 20643 g = this._lineFormat.readGeometry(v[i]); 20644 r[i] = g.getCoordinates(); 20645 } 20646 } else { 20647 for (i=0; i<v.length; i++) { 20648 r[i] = this.decodeCoordinates(v[i]); 20649 } 20650 } 20651 return r; 20652 } else { 20653 return null; 20654 } 20655}; 20656 20657/* Copyright (c) 2019 Jean-Marc VIGLINO, 20658 released under the CeCILL-B license (French BSD license) 20659 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 20660*/ 20661/** Feature format for reading data in the GeoRSS format. 20662 * @constructor ol.fromat.GeoRSS 20663 * @extends {ol.Object} 20664 * @param {*} options options. 20665 * @param {ol.ProjectionLike} options.dataProjection Projection of the data we are reading. If not provided `EPSG:4326` 20666 * @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. 20667 */ 20668ol.format.GeoRSS = function(options) { 20669 options = options || {}; 20670 ol.Object.call (this, options); 20671}; 20672ol.ext.inherits(ol.format.GeoRSS, ol.Object); 20673/** 20674 * Read a feature. Only works for a single feature. Use `readFeatures` to 20675 * read a feature collection. 20676 * 20677 * @param {Node|string} source Source. 20678 * @param {*} options Read options. 20679 * @param {ol.ProjectionLike} options.dataProjection Projection of the data we are reading. If not provided `EPSG:4326` 20680 * @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. 20681 * @return {ol.Feature} Feature or null if no feature read 20682 * @api 20683 */ 20684ol.format.GeoRSS.prototype.readFeature = function(source, options) { 20685 options = options || {}; 20686 var att, atts = source.children; 20687 var f = new ol.Feature(); 20688 // Get attributes 20689 for (var j=0; att = atts[j]; j++) { 20690 f.set(att.tagName, att.innerHTML); 20691 } 20692 var temp, g, coord=[]; 20693 // Get geometry 20694 if (f.get('geo:long')) { 20695 // LonLat 20696 g = new ol.geom.Point([parseFloat(f.get('geo:long')), parseFloat(f.get('geo:lat'))]); 20697 f.unset('geo:long'); 20698 f.unset('geo:lat'); 20699 } else if (f.get('georss:point')) { 20700 // Point 20701 coord = f.get('georss:point').trim().split(' '); 20702 g = new ol.geom.Point([parseFloat(coord[1]), parseFloat(coord[0])]); 20703 f.unset('georss:point'); 20704 } else if (f.get('georss:polygon')) { 20705 // Polygon 20706 temp = f.get('georss:polygon').trim().split(' '); 20707 for (var i=0; i<temp.length; i += 2) { 20708 coord.push([parseFloat(temp[i+1]), parseFloat(temp[i])]) 20709 } 20710 g = new ol.geom.Polygon([coord]); 20711 f.unset('georss:polygon'); 20712 } else if (f.get('georss:where')) { 20713 // GML 20714 console.warn('[GeoRSS] GML format not implemented') 20715 f.unset('georss:where'); 20716 return null; 20717 } else { 20718 console.warn('[GeoRSS] unknown geometry') 20719 return null; 20720 } 20721 if (options.featureProjection || this.get('featureProjection')) { 20722 g.transform (options.dataProjection || this.get('dataProjection') || 'EPSG:4326', options.featureProjection || this.get('featureProjection')); 20723 } 20724 f.setGeometry(g); 20725 return f; 20726}; 20727/** 20728 * Read all features. Works with both a single feature and a feature 20729 * collection. 20730 * 20731 * @param {Document|Node|string} source Source. 20732 * @param {*} options Read options. 20733 * @param {ol.ProjectionLike} options.dataProjection Projection of the data we are reading. If not provided `EPSG:4326` 20734 * @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. 20735 * @return {Array<ol.Feature>} Features. 20736 * @api 20737 */ 20738ol.format.GeoRSS.prototype.readFeatures = function(source, options) { 20739 var items; 20740 if (typeof(source)==='string') { 20741 var parser = new DOMParser(); 20742 var xmlDoc = parser.parseFromString(source,"text/xml"); 20743 items = xmlDoc.getElementsByTagName(this.getDocumentItemsTagName(xmlDoc)); 20744 } else if (source instanceof Document) { 20745 items = source.getElementsByTagName(this.getDocumentItemsTagName(source)); 20746 } else if (source instanceof Node) { 20747 items = source; 20748 } else { 20749 return []; 20750 } 20751 var features = [] 20752 for (var i=0, item; item = items[i]; i++) { 20753 var f = this.readFeature(item, options); 20754 if (f) features.push(f); 20755 } 20756 return features; 20757}; 20758/** 20759 * Get the tag name for the items in the XML Document depending if we are 20760 * dealing with an atom base document or not. 20761 * @param {Document} xmlDoc document to extract the tag name for the items 20762 * @return {string} tag name 20763 * @private 20764 */ 20765ol.format.GeoRSS.prototype.getDocumentItemsTagName = function(xmlDoc) { 20766 switch (xmlDoc.documentElement.tagName) { 20767 case 'feed': 20768 return 'entry'; 20769 default: 20770 return 'item'; 20771 } 20772} 20773 20774/** Clip interaction to clip layers in a circle 20775 * @constructor 20776 * @extends {ol.interaction.Pointer} 20777 * @param {ol.interaction.Clip.options} options flashlight param 20778 * @param {number}
20778 options.radius radius of the clip, default 100 20779 * @param {ol.layer|Array<ol.layer>} options.layers layers to clip 20780 */ 20781ol.interaction.Clip = function(options) { 20782 this.layers_ = []; 20783 ol.interaction.Pointer.call(this, { 20784 handleDownEvent: this._setPosition, 20785 handleMoveEvent: this._setPosition 20786 }); 20787 this.precomposeBind_ = this.precompose_.bind(this); 20788 this.postcomposeBind_ = this.postcompose_.bind(this); 20789 // Default options 20790 options = options || {}; 20791 this.pos = false; 20792 this.radius = (options.radius||100); 20793 if (options.layers) this.addLayer(options.layers); 20794}; 20795ol.ext.inherits(ol.interaction.Clip, ol.interaction.Pointer); 20796/** Set the map > start postcompose 20797*/ 20798ol.interaction.Clip.prototype.setMap = function(map) { 20799 var i; 20800 if (this.getMap()) { 20801 for (i=0; i<this.layers_.length; i++) { 20802 this.layers_[i].un(['precompose','prerender'], this.precomposeBind_); 20803 this.layers_[i].un(['postcompose','postrender'], this.postcomposeBind_); 20804 } 20805 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 20806 } 20807 ol.interaction.Pointer.prototype.setMap.call(this, map); 20808 if (map) { 20809 for (i=0; i<this.layers_.length; i++) {
20810 this.layers_[i].on(['precompose','prerender'], this.precomposeBind_); 20811 this.layers_[i].on(['postcompose','postrender'], this.postcomposeBind_); 20812 } 20813 try { map.renderSync(); } catch(e) { /* ok */ } 20814 } 20815}; 20816/** Set clip radius 20817 * @param {integer} radius 20818 */ 20819ol.interaction.Clip.prototype.setRadius = function(radius) { 20820 this.radius = radius; 20821 if (this.getMap()) { 20822 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 20823 } 20824}; 20825/** Get clip radius 20826 * @returns {integer} radius 20827 */ 20828ol.interaction.Clip.prototype.getRadius = function() { 20829 return this.radius; 20830}; 20831/** Add a layer to clip 20832 * @param {ol.layer|Array<ol.layer>} layer to clip 20833*/ 20834ol.interaction.Clip.prototype.addLayer = function(layers) { 20835 if (!(layers instanceof Array)) layers = [layers]; 20836 for (var i=0; i<layers.length; i++) { 20837 if (this.getMap()) { 20838 layers[i].on(['precompose','prerender'], this.precomposeBind_); 20839 layers[i].on(['postcompose','postrender'], this.postcomposeBind_); 20840 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 20841 } 20842 this.layers_.push(layers[i]); 20843 } 20844}; 20845/** Remove all layers 20846 */ 20847ol.interaction.Clip.prototype.removeLayers = function() { 20848 this.removeLayer(this.layers_); 20849}; 20850/** Remove a layer to clip 20851 * @param {ol.layer|Array<ol.layer>} layer to clip 20852*/ 20853ol.interaction.Clip.prototype.removeLayer = function(layers) { 20854 if (!(layers instanceof Array)) layers = [layers]; 20855 for (var i=0; i<layers.length; i++) { 20856 var k; 20857 for (k=0; k<this.layers_.length; k++) { 20858 if (this.layers_[k]===layers[i]) { 20859 break; 20860 } 20861 } 20862 if (k!=this.layers_.length && this.getMap()) {
20863 this.layers_[k].un(['precompose','prerender'], this.precomposeBind_); 20864 this.layers_[k].un(['postcompose','postrender'], this.postcomposeBind_); 20865 this.layers_.splice(k,1); 20866 } 20867 } 20868 if (this.getMap()) { 20869 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 20870 } 20871}; 20872/** Set position of the clip 20873 * @param {ol.coordinate} coord 20874 */ 20875ol.interaction.Clip.prototype.setPosition = function(coord) { 20876 if (this.getMap()) { 20877 this.pos = this.getMap().getPixelFromCoordinate(coord); 20878 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 20879 } 20880}; 20881/** Get position of the clip 20882 * @returns {ol.coordinate} 20883 */ 20884ol.interaction.Clip.prototype.getPosition = function() { 20885 if (this.pos) return this.getMap().getCoordinateFromPixel(this.pos); 20886 return null; 20887}; 20888/** Set position of the clip 20889 * @param {ol.Pixel} pixel 20890 */ 20891 ol.interaction.Clip.prototype.setPixelPosition = function(pixel) { 20892 this.pos = pixel; 20893 if (this.getMap()) { 20894 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 20895 } 20896}; 20897/** Get position of the clip 20898 * @returns {ol.Pixel} pixel 20899 */ 20900 ol.interaction.Clip.prototype.getPixelPosition = function() { 20901 return this.pos; 20902}; 20903/** Set position of the clip 20904 * @param {ol.MapBrowserEvent} e 20905 * @privata 20906 */ 20907ol.interaction.Clip.prototype._setPosition = function(e) { 20908 if (e.type==='pointermove' && this.get('action')==='onclick') return; 20909 if (e.pixel) { 20910 this.pos = e.pixel; 20911 } 20912 if (this.getMap()) { 20913 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 20914 } 20915}; 20916/* @private 20917*/ 20918ol.interaction.Clip.prototype.precompose_ = function(e) { 20919 if (!this.getActive()) return; 20920 var ctx = e.context; 20921 var ratio = e.frameState.pixelRatio; 20922 ctx.save(); 20923 ctx.beginPath(); 20924 var pt = [ this.pos[0], this.pos[1] ]; 20925 var radius = this.radius; 20926 var tr = e.inversePixelTransform; 20927 if (tr) { 20928 // Transform pt 20929 pt = [ 20930 (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), 20931 (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) 20932 ]; 20933 // Get radius / transform 20934 radius = pt[0] - ((this.pos[0]-radius)*tr[0] - this.pos[1]*tr[1] + tr[4]); 20935 } else { 20936 pt[0] *= ratio; 20937 pt[1] *= ratio; 20938 radius *= ratio; 20939 } 20940 ctx.arc (pt[0], pt[1], radius, 0, 2*Math.PI); 20941 ctx.clip(); 20942}; 20943/* @private 20944*/ 20945ol.interaction.Clip.prototype.postcompose_ = function(e) { 20946 if (!this.getActive()) return; 20947 e.context.restore(); 20948}; 20949/** 20950 * Activate or deactivate the interaction. 20951 * @param {boolean} active Active. 20952 * @observable 20953 * @api 20954 */ 20955ol.interaction.Clip.prototype.setActive = function(b) { 20956 if (b===this.getActive()) return; 20957 ol.interaction.Pointer.prototype.setActive.call (this, b); 20958 var i; 20959 if (b) { 20960 for(i=0; i<this.layers_.length; i++) {
20961 this.layers_[i].on(['precompose','prerender'], this.precomposeBind_); 20962 this.layers_[i].on(['postcompose','postrender'], this.postcomposeBind_); 20963 } 20964 } else { 20965 for(i=0; i<this.layers_.length; i++) { 20966 this.layers_[i].un(['precompose','prerender'], this.precomposeBind_); 20967 this.layers_[i].un(['postcompose','postrender'], this.postcomposeBind_); 20968 } 20969 } 20970 if (this.getMap()) { 20971 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 20972 } 20973}; 20974 20975/** An interaction to check the current map and add key events listeners. 20976 * 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). 20977 * @constructor 20978 * @fires focus 20979 * @param {*} options 20980 * @param {function} condition a function that takes a mapBrowserEvent and returns true if the map must be activated, default always true 20981 * @param {function} onKeyDown a function that takes a keydown event is fired on the active map 20982 * @param {function} onKeyPress a function that takes a keypress event is fired on the active map 20983 * @param {function} onKeyUp a function that takes a keyup event is fired on the active map 20984 * @extends {ol.interaction.Interaction} 20985 */ 20986ol.interaction.CurrentMap = function(options) { 20987 options = options || {}; 20988 var condition = options.condition || function() { 20989 return true; 20990 } 20991 // Check events on the map 20992 ol.interaction.Interaction.call(this, { 20993 handleEvent: function(e) { 20994 if (condition(e)) { 20995 if (!this.isCurrentMap()) { 20996 this.setCurrentMap(this.getMap()); 20997 this.dispatchEvent({ type: 'focus', map: this.getMap() }); 20998 this.getMap().dispatchEvent({ type: 'focus', map: this.getMap() }); 20999 } 21000 } 21001 return true; 21002 }.bind(this) 21003 }); 21004 // Add a key listener 21005 if (options.onKeyDown) { 21006 document.addEventListener('keydown', function(e) { 21007 if (this.isCurrentMap() && !/INPUT|TEXTAREA|SELECT/.test(document.activeElement.tagName)) { 21008 options.onKeyDown ({ type: e.type, map: this.getMap(), originalEvent: e }); 21009 } 21010 }.bind(this)); 21011 } 21012 if (options.onKeyPress) { 21013 document.addEventListener('keydown', function(e) { 21014 if (this.isCurrentMap() && !/INPUT|TEXTAREA|SELECT/.test(document.activeElement.tagName)) { 21015 options.onKeyPress ({ type: e.type, map: this.getMap(), originalEvent: e }); 21016 } 21017 }.bind(this)); 21018 } 21019 if (options.onKeyUp) { 21020 document.addEventListener('keydown', function(e) { 21021 if (this.isCurrentMap() && !/INPUT|TEXTAREA|SELECT/.test(document.activeElement.tagName)) { 21022 options.onKeyUp ({ type: e.type, map: this.getMap(), originalEvent: e }); 21023 } 21024 }.bind(this)); 21025 } 21026}; 21027ol.ext.inherits(ol.interaction.CurrentMap, ol.interaction.Interaction); 21028/** The current map */ 21029ol.interaction.CurrentMap.prototype._currentMap = undefined; 21030/** Check if is the current map 21031 * @return {boolean} 21032 */ 21033ol.interaction.CurrentMap.prototype.isCurrentMap = function() { 21034 return this.getMap() === ol.interaction.CurrentMap.prototype._currentMap; 21035}; 21036/** Get the current map 21037 * @return {ol.Map} 21038 */ 21039ol.interaction.CurrentMap.prototype.getCurrentMap = function() { 21040 return ol.interaction.CurrentMap.prototype._currentMap; 21041}; 21042/** Set the current map 21043 * @param {ol.Map} map 21044 */ 21045ol.interaction.CurrentMap.prototype.setCurrentMap = function(map) { 21046 ol.interaction.CurrentMap.prototype._currentMap = map; 21047}; 21048 21049/** Blob interaction to clip layers in a blob 21050 * @constructor 21051 * @extends {ol.interaction.Clip} 21052 * @param {*} options blob options 21053 * @param {number} options.radius radius of the clip, default 100 21054 * @param {ol.layer|Array<ol.layer>} options.layers layers to clip 21055 * @param {number} [options.stiffness=20] spring stiffness coef, default 20 21056 * @param {number} [options.damping=7] spring damping coef 21057 * @param {number} [options.mass=1] blob mass 21058 * @param {number} [options.points=10] number of points for the blob polygon 21059 * @param {number} [options.tension=.5] blob polygon spline tension 21060 * @param {number} [options.fuss] bob fussing factor 21061 * @param {number} [options.amplitude=1] blob deformation amplitude factor 21062 */ 21063ol.interaction.Blob = function(options) { 21064 ol.interaction.Clip.call(this, options); 21065}; 21066ol.ext.inherits(ol.interaction.Blob, ol.interaction.Clip); 21067/** Animate the blob 21068 * @private 21069 */ 21070ol.interaction.Blob.prototype.precompose_ = function(e) { 21071 if (!this.getActive()) return; 21072 var ctx = e.context; 21073 var ratio = e.frameState.pixelRatio; 21074 ctx.save(); 21075 if (!this.pos) { 21076 ctx.beginPath(); 21077 ctx.moveTo (0,0); 21078 ctx.clip(); 21079 return; 21080 } 21081 var pt = [ this.pos[0], this.pos[1] ]; 21082 var tr = e.inversePixelTransform; 21083 if (tr) { 21084 pt = [ 21085 (pt[0]*tr[0] - pt[1]*tr[1] + tr[4]), 21086 (-pt[0]*tr[2] + pt[1]*tr[3] + tr[5]) 21087 ]; 21088 } else { 21089 pt[0] *= ratio; 21090 pt[1] *= ratio; 21091 } 21092 // Time laps 21093 if (!this.frame) this.frame = e.frameState.time; 21094 var dt = e.frameState.time - this.frame; 21095 this.frame = e.frameState.time; 21096 // Blob position 21097 pt = this._getCenter(pt, dt); 21098 // Blob geom 21099 var blob = this._calculate(dt); 21100 // Draw 21101 var p = blob[0]; 21102 ctx.beginPath(); 21103 ctx.moveTo (pt[0] + p[0], pt[1] + p[1]); 21104 for (var i=1; p=blob[i]; i++) { 21105 ctx.lineTo (pt[0] + p[0], pt[1] + p[1]); 21106 } 21107 ctx.clip(); 21108 e.frameState.animate = true; 21109}; 21110/** Get blob center with kinetic 21111 * @param {number} dt0 time laps 21112 * @private 21113 */ 21114ol.interaction.Blob.prototype._getCenter = function(pt, dt0) { 21115 if (!this._center) { 21116 this._center = pt; 21117 this._velocity = [0, 0]; 21118 } else { 21119 var k = this.get('stiffness') || 20; // stiffness 21120 var d = -1* (this.get('damping') || 7); // damping 21121 var mass = Math.max(this.get('mass') || 1, .1); 21122 var dt = Math.min(dt0/1000, 1/30); 21123 var fSpring = [ 21124 k * (pt[0] - this._center[0]), 21125 k * (pt[1] - this._center[1]) 21126 ]; 21127 var fDamping = [ 21128 d * this._velocity[0], 21129 d * this._velocity[1] 21130 ]; 21131 var accel = [ 21132 (fSpring[0] + fDamping[0]) / mass, 21133 (fSpring[1] + fDamping[1]) / mass 21134 ]; 21135 this._velocity[0] += accel[0] * dt; 21136 this._velocity[1] += accel[1] * dt; 21137 this._center[0] += this._velocity[0] * dt; 21138 this._center[1] += this._velocity[1] * dt; 21139 } 21140 return this._center; 21141}; 21142/** Calculate the blob geom 21143 * @param {number} dt time laps 21144 * @returns {Array<ol.coordinate>} 21145 * @private 21146 */ 21147ol.interaction.Blob.prototype._calculate = function(dt) { 21148 var i, nb = this.get('points') || 10; 21149 if (!this._waves || this._waves.length !== nb) { 21150 this._waves = []; 21151 for (i = 0; i < nb; i++) { 21152 this._waves.push({ 21153 angle: Math.random()*Math.PI, 21154 noise: Math.random() 21155 }); 21156 } 21157 } 21158 var blob = []; 21159 var speed = (this._velocity[0]*this._velocity[0] + this._velocity[1]*this._velocity[1]) / 500; 21160 this._rotation = (this._rotation||0) + (this._velocity[0]>0 ? 1 : -1) * Math.min(.015, speed/70000*dt); 21161 for (i = 0; i < nb; i++) { 21162 var angle = i * 2 * Math.PI / nb + this._rotation; 21163 var radius = this.radius + Math.min(this.radius, speed); 21164 var delta = Math.cos(this._waves[i].angle) * radius/4 * this._waves[i].noise * (this.get('amplitude') || 1); 21165 blob.push([ 21166 (this.radius + delta) * Math.cos(angle), 21167 (this.radius + delta) * Math.sin(angle) 21168 ]); 21169 // Add noise 21170 this._waves[i].angle += (Math.PI + Math.random() + speed/200)/350 * dt * (this.get('fuss') || 1); 21171 this._waves[i].noise = Math.min(1, Math.max(0, this._waves[i].noise + (Math.random() - .5) *.1 *(this.get('fuss') || 1))); 21172 } 21173 blob.push(blob[0]); 21174 return ol.coordinate.cspline(blob, { tension: this.get('tension') }); 21175}; 21176 21177/* Copyright (c) 2016 Jean-Marc VIGLINO, 21178 released under the CeCILL-B license (French BSD license)
21179 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 21180*/ 21181/** Handles coordinates on the center of the viewport. 21182 * It can be used as abstract base class used for creating subclasses. 21183 * The CenterTouch interaction modifies map browser event coordinate and pixel properties to force pointer on the viewport center to any interaction that them. 21184 * Only pointermove pointerup are concerned with it. 21185 * @constructor 21186 * @extends {ol.interaction.Interaction} 21187 * @param {olx.interaction.InteractionOptions} options Options 21188 * @param {ol.style.Style|Array<ol.style.Style>} options.targetStyle a style to draw the target point, default cross style 21189 * @param {string} options.composite composite operation for the target : difference|multiply|xor|screen|overlay|darken|lighter|lighten|... 21190 */ 21191ol.interaction.CenterTouch = function(options) { 21192 options = options || {}; 21193 // LIst of listerner on the object 21194 this._listener = {}; 21195 // Filter event 21196 var rex = /^pointermove$|^pointerup$/; 21197 // Interaction to defer center on top of the interaction 21198 // this is done to enable other coordinates manipulation inserted after the interaction (snapping) 21199 this.ctouch = new ol.interaction.Interaction({ 21200 handleEvent: function(e) { 21201 if (rex.test(e.type) && this.getMap()) { 21202 e.coordinate = this.getMap().getView().getCenter(); 21203 e.pixel = this.getMap().getSize(); 21204 e.pixel = [ e.pixel[0]/2, e.pixel[1]/2 ]; 21205 } 21206 return true; 21207 } 21208 }); 21209 // Target on map center 21210 this._target = new ol.control.Target({ 21211 style: options.targetStyle, 21212 composite: options.composite 21213 }); 21214 ol.interaction.Interaction.call(this, { 21215 handleEvent: function(e) { 21216 if (rex.test(e.type)) this.pos_ = e.coordinate; 21217 if (options.handleEvent) return options.handleEvent.call (this,e); 21218 return true; 21219 } 21220 }); 21221}; 21222ol.ext.inherits(ol.interaction.CenterTouch, ol.interaction.Interaction); 21223/** 21224 * Remove the interaction from its current map, if any, and attach it to a new 21225 * map, if any. Pass `null` to just remove the interaction from the current map. 21226 * @param {_ol_Map_} map Map. 21227 * @api stable 21228 */ 21229ol.interaction.CenterTouch.prototype.setMap = function(map) { 21230 if (this.getMap()) { 21231 this.getMap().removeInteraction(this.ctouch); 21232 this.getMap().removeInteraction(this._target); 21233 } 21234 ol.interaction.Interaction.prototype.setMap.call (this, map); 21235 if (this.getMap()) { 21236 if (this.getActive()) { 21237 this.getMap().addInteraction(this.ctouch); 21238 this.getMap().addControl(this._target); 21239 } 21240 } 21241}; 21242/** 21243 * Activate or deactivate the interaction. 21244 * @param {boolean} active Active. 21245 * @observable 21246 * @api 21247 */ 21248ol.interaction.CenterTouch.prototype.setActive = function(b) { 21249 ol.interaction.Interaction.prototype.setActive.call (this, b); 21250 this.pos_ = null; 21251 if (this.getMap()) { 21252 if (this.getActive()) { 21253 this.getMap().addInteraction(this.ctouch); 21254 this.getMap().addControl(this._target); 21255 } else { 21256 this.getMap().removeInteraction(this.ctouch); 21257 this.getMap().removeControl(this._target); 21258 } 21259 } 21260}; 21261/** Get the position of the target 21262 * @return {ol.coordinate} 21263 */ 21264ol.interaction.CenterTouch.prototype.getPosition = function () { 21265 if (!this.pos_) { 21266 var px =this.getMap().getSize(); 21267 px = [ px[0]/2, px[1]/2 ]; 21268 this.pos_ = this.getMap().getCoordinateFromPixel(px); 21269 } 21270 return this.pos_; 21271}; 21272 21273/** Clip interaction to clip layers in a circle 21274 * @constructor 21275 * @extends {ol.interaction.Pointer} 21276 * @param {ol.interaction.ClipMap.options} options flashlight param 21277 * @param {number} options.radius radius of the clip, default 100 (px) 21278 */ 21279ol.interaction.ClipMap = function(options) { 21280 this.layers_ = []; 21281 ol.interaction.Pointer.call(this, { 21282 handleDownEvent: this._clip, 21283 handleMoveEvent: this._clip 21284 }); 21285 // Default options 21286 options = options || {}; 21287 this.pos = false; 21288 this.radius = (options.radius||100); 21289 this.pos = [-1000, -1000]; 21290}; 21291ol.ext.inherits(ol.interaction.ClipMap, ol.interaction.Pointer); 21292/** Set the map > start postcompose 21293*/ 21294ol.interaction.ClipMap.prototype.setMap = function(map) { 21295 if (this.getMap()) { 21296 if (this._listener) ol.Observable.unByKey(this._listener); 21297 var layerDiv = this.getMap().getViewport().querySelector('.ol-layers'); 21298 layerDiv.style.clipPath = ''; 21299 } 21300 ol.interaction.Pointer.prototype.setMap.call(this, map); 21301 if (map) { 21302 this._listener = map.on('change:size', this._clip.bind(this)); 21303 } 21304}; 21305/** Set clip radius 21306 * @param {integer} radius 21307 */ 21308ol.interaction.ClipMap.prototype.setRadius = function(radius) {
21309 this.radius = radius; 21310 this._clip(); 21311}; 21312/** Get clip radius 21313 * @returns {integer} radius 21314 */ 21315ol.interaction.ClipMap.prototype.getRadius = function() { 21316 return this.radius; 21317}; 21318/** Set position of the clip 21319 * @param {ol.coordinate} coord 21320 */ 21321ol.interaction.ClipMap.prototype.setPosition = function(coord) { 21322 if (this.getMap()) { 21323 this.pos = this.getMap().getPixelFromCoordinate(coord); 21324 this._clip(); 21325 } 21326}; 21327/** Get position of the clip 21328 * @returns {ol.coordinate} 21329 */ 21330ol.interaction.ClipMap.prototype.getPosition = function() { 21331 if (this.pos) return this.getMap().getCoordinateFromPixel(this.pos); 21332 return null; 21333}; 21334/** Set position of the clip 21335 * @param {ol.Pixel} pixel 21336 */ 21337 ol.interaction.ClipMap.prototype.setPixelPosition = function(pixel) { 21338 this.pos = pixel; 21339 this._clip(); 21340}; 21341/** Get position of the clip 21342 * @returns {ol.Pixel} pixel 21343 */ 21344 ol.interaction.ClipMap.prototype.getPixelPosition = function() { 21345 return this.pos; 21346}; 21347/** Set position of the clip 21348 * @param {ol.MapBrowserEvent} e 21349 * @privata 21350 */ 21351ol.interaction.ClipMap.prototype._setPosition = function(e) { 21352 if (e.type==='pointermove' && this.get('action')==='onclick') return; 21353 if (e.pixel) { 21354 this.pos = e.pixel; 21355 } 21356 if (this.getMap()) { 21357 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 21358 } 21359}; 21360/** Clip 21361 * @private 21362 */ 21363 ol.interaction.ClipMap.prototype._clip = function(e) { 21364 if (e && e.pixel) { 21365 this.pos = e.pixel; 21366 } 21367 if (this.pos && this.getMap()) { 21368 var layerDiv = this.getMap().getViewport().querySelector('.ol-layers'); 21369 layerDiv.style.clipPath = 'circle(' 21370 + this.getRadius() + 'px' // radius 21371 + ' at ' 21372 + this.pos[0] + 'px ' 21373 + this.pos[1] + 'px)'; 21374 } 21375}; 21376 21377/** An interaction to copy/paste features on a map. 21378 * 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). 21379 * @constructor 21380 * @fires focus 21381 * @fires copy 21382 * @fires paste 21383 * @extends {ol.interaction.Interaction} 21384 * @param {Object} options Options 21385 * @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 21386 * @param {function} options.mapCondition a function that takes a mapBrowserEvent and return true if the map is the active map, default always returns true 21387 * @param {ol.Collection<ol.Feature>} options.features list of features to copy 21388 * @param {ol.source.Vector | Array<ol.source.Vector>} options.sources the source to copy from (used for cut), if not defined, it will use the destination 21389 * @param {ol.source.Vector} options.destination the source to copy to 21390 */ 21391ol.interaction.CopyPaste = function(options) { 21392 options = options || {}; 21393 // Features to copy 21394 this.features = []; 21395 this._cloneFeature = true; 21396 var condition = options.condition; 21397 if (typeof (condition) !== 'function') { 21398 condition = function (e) { 21399 if (e.originalEvent.ctrlKey) { 21400 if (/^c$/i.test(e.originalEvent.key)) return 'copy'; 21401 if (/^x$/i.test(e.originalEvent.key)) return 'cut'; 21402 if (/^v$/i.test(e.originalEvent.key)) return 'paste'; 21403 } 21404 return false; 21405 } 21406 } 21407 this._featuresSource = options.features || new ol.Collection(); 21408 this.setSources(options.sources); 21409 this.setDestination(options.destination); 21410 // Create intreaction 21411 ol.interaction.CurrentMap.call(this, { 21412 condition: options.mapCondition, 21413 onKeyDown: function (e) { 21414 switch (condition(e)) { 21415 case 'copy': { 21416 this.copy({ silent: false }); 21417 break; 21418 } 21419 case 'cut': { 21420 this.copy({ cut: true, silent: false }); 21421 break; 21422 } 21423 case 'paste': { 21424 this.paste({ silent: false }); 21425 break; 21426 } 21427 default: break; 21428 } 21429 }.bind(this) 21430 }); 21431}; 21432ol.ext.inherits(ol.interaction.CopyPaste, ol.interaction.CurrentMap); 21433/** Sources to cut feature from 21434 * @param { ol.source.Vector | Array<ol.source.Vector> } sources 21435 */ 21436ol.interaction.CopyPaste.prototype.setSources = function (sources) { 21437 if (sources) { 21438 this._source = []; 21439 this._source = sources instanceof Array ? sources : [sources]; 21440 } else { 21441 this._source = null; 21442 } 21443}; 21444/** Get sources to cut feature from 21445 * @return { Array<ol.source.Vector> } 21446 */ 21447ol.interaction.CopyPaste.prototype.getSources = function () { 21448 return this._source; 21449}; 21450/** Source to paste features 21451 * @param { ol.source.Vector } source 21452 */ 21453ol.interaction.CopyPaste.prototype.setDestination = function (destination) { 21454 this._destination = destination; 21455}; 21456/** Get source to paste features 21457 * @param { ol.source.Vector } 21458 */ 21459ol.interaction.CopyPaste.prototype.getDestination = function () { 21460 return this._destination; 21461}; 21462/** Get current feature to copy 21463 * @return {Array<ol.Feature>} 21464 */ 21465ol.interaction.CopyPaste.prototype.getFeatures = function() { 21466 return this.features; 21467}; 21468/** Set current feature to copy 21469 * @param {Object} options 21470 * @param {Array<ol.Feature> | ol.Collection<ol.Feature>} options.features feature to copy, default get in the provided collection 21471 * @param {boolean} options.cut try to cut feature from the sources, default false 21472 * @param {boolean} options.silent true to send an event, default true 21473 */ 21474ol.interaction.CopyPaste.prototype.copy = function (options) { 21475 options = options || {}; 21476 var features = options.features || this._featuresSource.getArray(); 21477 // Try to remove feature from sources 21478 if (options.cut) { 21479 var sources = this._source || [this._destination]; 21480 // Remove feature from sources
21481 features.forEach(function(f) { 21482 sources.forEach(function(source) { 21483 try { 21484 source.removeFeature(f); 21485 } catch(e) {/*ok*/} 21486 }); 21487 }); 21488 } 21489 if (this._cloneFeature) { 21490 this.features = []; 21491 features.forEach(function(f) { 21492 this.features.push(f.clone()); 21493 }.bind(this)); 21494 } else { 21495 this.features = features; 21496 } 21497 // Send an event 21498 if (options.silent===false) this.dispatchEvent({ type: options.cut ? 'cut' : 'copy', time: (new Date).getTime() }); 21499}; 21500/** Paste features 21501 * @param {Object} options 21502 * @param {Array<ol.Feature> | ol.Collection<ol.Feature>} features feature to copy, default get current features 21503 * @param {ol.source.Vector} options.destination Source to paste to, default the current source 21504 * @param {boolean} options.silent true to send an event, default true 21505 */ 21506ol.interaction.CopyPaste.prototype.paste = function(options) { 21507 options = options || {}; 21508 var features = options.features || this.features; 21509 if (features) { 21510 var destination = options.destination || this._destination; 21511 if (destination) { 21512 destination.addFeatures(this.features); 21513 if (this._cloneFeature) this.copy({ features: this.features }); 21514 } 21515 } 21516 // Send an event 21517 if (options.silent===false) this.dispatchEvent({ type:'paste', features: features, time: (new Date).getTime() }); 21518}; 21519 21520/* Copyright (c) 2018 Jean-Marc VIGLINO, 21521 released under the CeCILL-B license (French BSD license) 21522 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 21523*/ 21524/** A Select interaction to delete features on click. 21525 * @constructor 21526 * @extends {ol.interaction.Interaction} 21527 * @fires deletestart 21528 * @fires deleteend 21529 * @param {*} options ol.interaction.Select options 21530 */ 21531ol.interaction.Delete = function(options) { 21532 ol.interaction.Select.call(this, options); 21533 this.on('select', function(e) { 21534 this.getFeatures().clear(); 21535 this.delete(e.selected); 21536 }.bind(this)); 21537}; 21538ol.ext.inherits(ol.interaction.Delete, ol.interaction.Select); 21539/** Get vector source of the map 21540 * @return {Array<ol.source.Vector>} 21541 */ 21542ol.interaction.Delete.prototype._getSources = function(layers) { 21543 if (!this.getMap()) return []; 21544 if (!layers) layers = this.getMap().getLayers(); 21545 var sources = []; 21546 layers.forEach(function (l) { 21547 // LayerGroup 21548 if (l.getLayers) { 21549 sources = sources.concat(this._getSources(l.getLayers())); 21550 } else { 21551 if (l.getSource && l.getSource() instanceof ol.source.Vector) { 21552 sources.push(l.getSource()); 21553 } 21554 } 21555 }.bind(this)); 21556 return sources; 21557}; 21558/** Delete features: remove the features from the map (from all layers in the map) 21559 * @param {ol.Collection<ol.Feature>|Array<ol.Feature>} features The features to delete 21560 * @api 21561 */ 21562ol.interaction.Delete.prototype.delete = function(features) { 21563 if (features && (features.length || features.getLength())) { 21564 this.dispatchEvent({ type: 'deletestart', features: features }); 21565 var delFeatures = []; 21566 // Get the sources concerned 21567 this._getSources().forEach(function (source) { 21568 try { 21569 // Try to delete features in the source 21570 features.forEach(function(f) { 21571 source.removeFeature(f); 21572 delFeatures.push(f); 21573 }); 21574 } catch(e) { /* ok */ } 21575 }) 21576 this.dispatchEvent({ type: 'deleteend', features: delFeatures }); 21577 } 21578}; 21579 21580/** Drag an overlay on the map 21581 * @constructor 21582 * @extends {ol.interaction.Pointer} 21583 * @fires dragstart 21584 * @fires dragging 21585 * @fires dragend 21586 * @param {any} options 21587 * @param {ol.Overlay|Array<ol.Overlay>} options.overlays the overlays to drag 21588 * @param {ol.Size} options.offset overlay offset, default [0,0] 21589 */ 21590ol.interaction.DragOverlay = function(options) { 21591 if (!options) options = {}; 21592 var offset = options.offset || [0,0]; 21593 // Extend pointer 21594 ol.interaction.Pointer.call(this, { 21595 // start draging on an overlay 21596 handleDownEvent: function(evt) { 21597 var res = evt.frameState.viewState.resolution 21598 var coordinate = [evt.coordinate[0] + offset[0]*res, evt.coordinate[1] - offset[1]*res]; 21599 // Click on a button (closeBox) or on a link: don't drag! 21600 if (/^(BUTTON|A)$/.test(evt.originalEvent.target.tagName)) { 21601 this._dragging = false;
21602 return true; 21603 } 21604 // Start dragging 21605 if (this._dragging) { 21606 if (options.centerOnClick !== false) { 21607 this._dragging.setPosition(coordinate, true); 21608 } else { 21609 coordinate = this._dragging.getPosition(); 21610 } 21611 this.dispatchEvent({ 21612 type: 'dragstart', 21613 overlay: this._dragging, 21614 originalEvent: evt.originalEvent, 21615 frameState: evt.frameState, 21616 coordinate: coordinate 21617 }); 21618 return true; 21619 } 21620 return false; 21621 }, 21622 // Drag 21623 handleDragEvent: function(evt) { 21624 var res = evt.frameState.viewState.resolution 21625 var coordinate = [evt.coordinate[0] + offset[0]*res, evt.coordinate[1] - offset[1]*res]; 21626 if (this._dragging) { 21627 this._dragging.setPosition(coordinate, true); 21628 this.dispatchEvent({ 21629 type: 'dragging', 21630 overlay: this._dragging, 21631 originalEvent: evt.originalEvent, 21632 frameState: evt.frameState, 21633 coordinate: coordinate 21634 }); 21635 } 21636 }, 21637 // Stop dragging 21638 handleUpEvent: function(evt) { 21639 var res = evt.frameState.viewState.resolution 21640 var coordinate = [evt.coordinate[0] + offset[0]*res, evt.coordinate[1] - offset[1]*res]; 21641 if (this._dragging) { 21642 this.dispatchEvent({ 21643 type: 'dragend', 21644 overlay: this._dragging, 21645 originalEvent: evt.originalEvent, 21646 frameState: evt.frameState, 21647 coordinate: coordinate 21648 }); 21649 this._dragging = false; 21650 return true; 21651 } 21652 return false; 21653 } 21654 }); 21655 // List of overlays / listeners 21656 this._overlays = []; 21657 if (!(options.overlays instanceof Array)) options.overlays = [options.overlays]; 21658 options.overlays.forEach(this.addOverlay.bind(this)); 21659}; 21660ol.ext.inherits(ol.interaction.DragOverlay, ol.interaction.Pointer); 21661/** Add an overlay to the interacton 21662 * @param {ol.Overlay} ov 21663 */ 21664ol.interaction.DragOverlay.prototype.addOverlay = function (ov) { 21665 for (var i=0, o; o=this._overlays[i]; i++) { 21666 if (o===ov) return; 21667 } 21668 // Stop event overlay 21669 if (ov.element.parentElement && ov.element.parentElement.classList.contains('ol-overlaycontainer-stopevent')) { 21670 console.warn('[DragOverlay.addOverlay] overlay must be created with stopEvent set to false!'); 21671 return; 21672 } 21673 // Add listener on overlay of the same map 21674 var handler = function() { 21675 if (this.getMap()===ov.getMap()) this._dragging = ov; 21676 }.bind(this); 21677 this._overlays.push({ 21678 overlay: ov, 21679 listener: handler 21680 }); 21681 ov.element.addEventListener('pointerdown', handler); 21682}; 21683/** Remove an overlay from the interacton 21684 * @param {ol.Overlay} ov 21685 */ 21686ol.interaction.DragOverlay.prototype.removeOverlay = function (ov) { 21687 for (var i=0, o; o=this._overlays[i]; i++) { 21688 if (o.overlay===ov) { 21689 var l = this._overlays.splice(i,1)[0]; 21690 ov.element.removeEventListener('pointerdown', l.listener); 21691 break; 21692 } 21693 } 21694}; 21695 21696/* Copyright (c) 2017 Jean-Marc VIGLINO, 21697 released under the CeCILL-B license (French BSD license) 21698 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 21699*/ 21700/** Interaction to draw holes in a polygon. 21701 * It fires a drawstart, drawend event when drawing the hole 21702 * and a modifystart, modifyend event before and after inserting the hole in the feature geometry. 21703 * @constructor 21704 * @extends {ol.interaction.Interaction} 21705 * @fires drawstart 21706 * @fires drawend 21707 * @fires modifystart 21708 * @fires modifyend 21709 * @param {olx.interaction.DrawHoleOptions} options extend olx.interaction.DrawOptions 21710 * @param {Array<ol.layer.Vector> | 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 21711 * @param {Array<ol.Feature> | ol.Collection<ol.Feature> | 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 21712 * @param { ol.style.Style | Array<ol.style.Style> | StyleFunction | undefined } Style for the selected features, default: default edit style 21713 */ 21714ol.interaction.DrawHole = function(options) { 21715 if (!options) options = {}; 21716 var self = this; 21717 // Select interaction for the current feature 21718 this._select = new ol.interaction.Select({ style: options.style }); 21719 this._select.setActive(false); 21720 // Geometry function that test points inside the current 21721 var geometryFn, geomFn = options.geometryFunction; 21722 if (geomFn) { 21723 geometryFn = function(c,g) { 21724 g = self._geometryFn (c, g); 21725 return geomFn (c,g); 21726 } 21727 } else { 21728 geometryFn = function(c,g) { return self._geometryFn (c, g); } 21729 } 21730 // Create draw interaction 21731 options.type = "Polygon"; 21732 options.geometryFunction = geometryFn; 21733 ol.interaction.Draw.call(this, options); 21734 // Layer filter function 21735 if (options.layers) { 21736 if (typeof (options.layers) === 'function') { 21737 this.layers_ = options.layers; 21738 } else if (options.layers.indexOf) { 21739 this.layers_ = function(l) { 21740 return (options.layers.indexOf(l) >= 0); 21741 }; 21742 } 21743 } 21744 // Features to apply on 21745 if (typeof(options.features) === 'function') { 21746 this._features = options.features; 21747 } else if (options.features) { 21748 var features = options.features; 21749 this._features = function(f) { 21750 if (features.indexOf) { 21751 return !!features[features.indexOf(f)]; 21752 } else { 21753 return !!features.item(features.getArray().indexOf(f)); 21754 } 21755 } 21756 } else { 21757 this._features = function() { return true } 21758 } 21759 // Start drawing if inside a feature 21760 this.on('drawstart', this._startDrawing.bind(this)); 21761 // End drawing add the hole to the current Polygon 21762 this.on('drawend', this._finishDrawing.bind(this)); 21763}; 21764ol.ext.inherits(ol.interaction.DrawHole, ol.interaction.Draw); 21765/** 21766 * Remove the interaction from its current map, if any, and attach it to a new 21767 * map, if any. Pass `null` to just remove the interaction from the current map. 21768 * @param {ol.Map} map Map. 21769 * @api stable 21770 */ 21771ol.interaction.DrawHole.prototype.setMap = function(map) { 21772 if (this.getMap()) this.getMap().removeInteraction(this._select); 21773 if (map) map.addInteraction(this._select); 21774 ol.interaction.Draw.prototype.setMap.call (this, map); 21775}; 21776/** 21777 * Activate/deactivate the interaction 21778 * @param {boolean} 21779 * @api stable 21780 */ 21781ol.interaction.DrawHole.prototype.setActive = function(b) { 21782 this._select.getFeatures().clear(); 21783 ol.interaction.Draw.prototype.setActive.call (this, b); 21784}; 21785/** 21786 * Remove last point of the feature currently being drawn 21787 * (test if points to remove before). 21788 */ 21789ol.interaction.DrawHole.prototype.removeLastPoint = function() { 21790 if (this._feature && this._feature.getGeometry().getCoordinates()[0].length>2) { 21791 ol.interaction.Draw.prototype.removeLastPoint.call(this); 21792 } 21793}; 21794/** 21795 * Get the current polygon to hole 21796 * @return {ol.Feature} 21797 */ 21798ol.interaction.DrawHole.prototype.getPolygon = function() { 21799 return this._polygon; 21800 // return this._select.getFeatures().item(0).getGeometry(); 21801}; 21802/** 21803 * Get current feature to add a hole and start drawing 21804 * @param {ol.interaction.Draw.Event} e 21805 * @private 21806 */ 21807ol.interaction.DrawHole.prototype._startDrawing = function(e) { 21808 var map = this.getMap(); 21809 this._feature = e.feature; 21810 var coord = e.feature.getGeometry().getCoordinates()[0][0]; 21811 this._current = null; 21812 // Check object under the pointer
21813 map.forEachFeatureAtPixel( 21814 map.getPixelFromCoordinate(coord), 21815 function(feature, layer) { 21816 // Not yet found? 21817 if (!this._current && this._features(feature, layer)) { 21818 var poly = feature.getGeometry(); 21819 if (poly.getType() === "Polygon" 21820 && poly.intersectsCoordinate(coord)) { 21821 this._polygonIndex = false; 21822 this._polygon = poly; 21823 this._current = feature; 21824 } else if (poly.getType() === "MultiPolygon" 21825 && poly.intersectsCoordinate(coord)) { 21826 for (var i=0, p; p=poly.getPolygon(i); i++) { 21827 if (p.intersectsCoordinate(coord)) { 21828 this._polygonIndex = i; 21829 this._polygon = p; 21830 this._current = feature; 21831 break; 21832 } 21833 } 21834 } 21835 } 21836 }.bind(this), { 21837 layerFilter: this.layers_ 21838 } 21839 ); 21840 this._select.getFeatures().clear(); 21841 if (!this._current) { 21842 this.setActive(false); 21843 this.setActive(true); 21844 } else { 21845 this._select.getFeatures().push(this._current); 21846 } 21847}; 21848/** 21849 * Stop drawing and add the sketch feature to the target feature. 21850 * @param {ol.interaction.Draw.Event} e 21851 * @private 21852 */ 21853ol.interaction.DrawHole.prototype._finishDrawing = function(e) { 21854 // The feature is the hole 21855 e.hole = e.feature; 21856 // Get the current feature 21857 e.feature = this._select.getFeatures().item(0); 21858 this.dispatchEvent({ type: 'modifystart', features: [ this._current ] }); 21859 // Create the hole 21860 var c = e.hole.getGeometry().getCoordinates()[0]; 21861 if (c.length > 3) { 21862 if (this._polygonIndex!==false) { 21863 var geom = e.feature.getGeometry(); 21864 var newGeom = new ol.geom.MultiPolygon([]); 21865 for (var i=0, pi; pi=geom.getPolygon(i); i++) { 21866 if (i===this._polygonIndex) { 21867 pi.appendLinearRing(new ol.geom.LinearRing(c)); 21868 newGeom.appendPolygon(pi); 21869 } else { 21870 newGeom.appendPolygon(pi); 21871 } 21872 } 21873 e.feature.setGeometry(newGeom); 21874 } else { 21875 this.getPolygon().appendLinearRing(new ol.geom.LinearRing(c)); 21876 } 21877 } 21878 this.dispatchEvent({ type: 'modifyend', features: [ this._current ] }); 21879 // reset 21880 this._feature = null; 21881 this._select.getFeatures().clear(); 21882}; 21883/** 21884 * Function that is called when a geometry's coordinates are updated. 21885 * @param {Array<ol.coordinate>} coordinates 21886 * @param {ol.geom.Polygon} geometry 21887 * @return {ol.geom.Polygon} 21888 * @private 21889 */ 21890ol.interaction.DrawHole.prototype._geometryFn = function(coordinates, geometry) { 21891 var coord = coordinates[0].pop(); 21892 if (!this.getPolygon() || this.getPolygon().intersectsCoordinate(coord)) { 21893 this.lastOKCoord = [coord[0],coord[1]]; 21894 } 21895 coordinates[0].push([this.lastOKCoord[0],this.lastOKCoord[1]]); 21896 if (geometry) { 21897 geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])]); 21898 } else { 21899 geometry = new ol.geom.Polygon(coordinates); 21900 } 21901 return geometry; 21902}; 21903 21904/* Copyright (c) 2016 Jean-Marc VIGLINO, 21905 released under the CeCILL-B license (French BSD license) 21906 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 21907*/ 21908/** Interaction rotate 21909 * @constructor 21910 * @extends {ol.interaction.Interaction} 21911 * @fires drawstart, drawing, drawend, drawcancel 21912 * @param {olx.interaction.TransformOptions} options 21913 * @param {Array<ol.Layer>} options.source Destination source for the drawn features 21914 * @param {ol.Collection<ol.Feature>} options.features Destination collection for the drawn features 21915 * @param {ol.style.Style | Array.<ol.style.Style> | ol.style.StyleFunction | undefined} options.style style for the sketch 21916 * @param {integer} options.sides number of sides, default 0 = circle 21917 * @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. 21918 * @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 21919 * @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 21920 * @param { bool } options.canRotate Allow rotation when centered + square, default: true 21921 * @param { string } [options.geometryName=geometry] 21922 * @param { number } options.clickTolerance click tolerance on touch devices, default: 6 21923 * @param { number } options.maxCircleCoordinates Maximum number of point on a circle, default: 100 21924 */ 21925ol.interaction.DrawRegular = function(options) { 21926 if (!options) options={}; 21927 this.squaredClickTolerance_ = options.clickTolerance ? options.clickTolerance * options.clickTolerance : 36; 21928 this.maxCircleCoordinates_ = options.maxCircleCoordinates || 100; 21929 // Collection of feature to transform 21930 this.features_ = options.features; 21931 // List of layers to transform 21932 this.source_ = options.source; 21933 // Square condition 21934 this.conditionFn_ = options.condition; 21935 // Square condition 21936 this.squareFn_ = options.squareCondition; 21937 // Centered condition 21938 this.centeredFn_ = options.centerCondition; 21939 // Allow rotation when centered + square 21940 this.canRotate_ = (options.canRotate !== false); 21941 // Specify custom geometry name 21942 this.geometryName_ = options.geometryName; 21943 // Number of sides (default=0: circle) 21944 this.setSides(options.sides); 21945 // Style 21946 var defaultStyle = ol.style.Style.defaultStyle(true); 21947 // Create a new overlay layer for the sketch 21948 this.sketch_ = new ol.Collection(); 21949 this.overlayLayer_ = new ol.layer.Vector({ 21950 source: new ol.source.Vector({ 21951 features: this.sketch_,
21952 useSpatialIndex: false 21953 }), 21954 name:'DrawRegular overlay', 21955 displayInLayerSwitcher: false, 21956 style: options.style || defaultStyle 21957 }); 21958 ol.interaction.Interaction.call(this, { 21959 /* 21960 handleDownEvent: this.handleDownEvent_, 21961 handleMoveEvent: this.handleMoveEvent_, 21962 handleUpEvent: this.handleUpEvent_, 21963 */ 21964 handleEvent: this.handleEvent_ 21965 }); 21966}; 21967ol.ext.inherits(ol.interaction.DrawRegular, ol.interaction.Interaction); 21968/** 21969 * Remove the interaction from its current map, if any, and attach it to a new 21970 * map, if any. Pass `null` to just remove the interaction from the current map. 21971 * @param {ol.Map} map Map. 21972 * @api stable 21973 */ 21974ol.interaction.DrawRegular.prototype.setMap = function(map) { 21975 if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_); 21976 ol.interaction.Interaction.prototype.setMap.call (this, map); 21977 this.overlayLayer_.setMap(map); 21978}; 21979/** 21980 * Activate/deactivate the interaction 21981 * @param {boolean} 21982 * @api stable 21983 */ 21984ol.interaction.DrawRegular.prototype.setActive = function(b) { 21985 this.reset(); 21986 ol.interaction.Interaction.prototype.setActive.call (this, b); 21987} 21988/** 21989 * Reset the interaction 21990 * @api stable 21991 */ 21992ol.interaction.DrawRegular.prototype.reset = function() { 21993 this.overlayLayer_.getSource().clear(); 21994 this.started_ = false; 21995} 21996/** 21997 * Set the number of sides. 21998 * @param {int} number of sides. 21999 * @api stable 22000 */ 22001ol.interaction.DrawRegular.prototype.setSides = function (nb) { 22002 nb = parseInt(nb); 22003 this.sides_ = nb>2 ? nb : 0; 22004} 22005/** 22006 * Allow rotation when centered + square 22007 * @param {bool} 22008 * @api stable 22009 */ 22010ol.interaction.DrawRegular.prototype.canRotate = function (b) { 22011 if (b===true || b===false) this.canRotate_ = b; 22012 return this.canRotate_; 22013} 22014/** 22015 * Get the number of sides. 22016 * @return {int} number of sides. 22017 * @api stable 22018 */ 22019ol.interaction.DrawRegular.prototype.getSides = function () { 22020 return this.sides_; 22021} 22022/** Default start angle array for each sides 22023*/ 22024ol.interaction.DrawRegular.prototype.startAngle = { 22025 'default':Math.PI/2, 22026 3: -Math.PI/2, 22027 4: Math.PI/4 22028}; 22029/** Get geom of the current drawing 22030* @return {ol.geom.Polygon | ol.geom.Point} 22031*/ 22032ol.interaction.DrawRegular.prototype.getGeom_ = function () { 22033 this.overlayLayer_.getSource().clear(); 22034 if (!this.center_) return false; 22035 var g; 22036 if (this.coord_) { 22037 var center = this.center_; 22038 var coord = this.coord_; 22039 // Specific case: circle 22040 var d, dmax, r, circle, centerPx; 22041 if (!this.sides_ && this.square_ && !this.centered_) { 22042 center = [(coord[0] + center[0])/2, (coord[1] + center[1])/2]; 22043 d = [coord[0] - center[0], coord[1] - center[1]]; 22044 r = Math.sqrt(d[0]*d[0]+d[1]*d[1]); 22045 circle = new ol.geom.Circle(center, r, 'XY'); 22046 // Optimize points on the circle 22047 centerPx = this.getMap().getPixelFromCoordinate(center); 22048 dmax = Math.max (100, Math.abs(centerPx[0]-this.coordPx_[0]), Math.abs(centerPx[1]-this.coordPx_[1])); 22049 dmax = Math.min ( this.maxCircleCoordinates_, Math.round(dmax / 3 )); 22050 return ol.geom.Polygon.fromCircle (circle, dmax, 0); 22051 } else { 22052 var hasrotation = this.canRotate_ && this.centered_ && this.square_; 22053 d = [coord[0] - center[0], coord[1] - center[1]]; 22054 if (this.square_ && !hasrotation) { 22055 //var d = [coord[0] - center[0], coord[1] - center[1]]; 22056 var dm = Math.max (Math.abs(d[0]), Math.abs(d[1])); 22057 coord = [ 22058 center[0] + (d[0]>0 ? dm:-dm), 22059 center[1] + (d[1]>0 ? dm:-dm) 22060 ]; 22061 } 22062 r = Math.sqrt(d[0]*d[0]+d[1]*d[1]); 22063 if (r>0) { 22064 circle = new ol.geom.Circle(center, r, 'XY'); 22065 var a; 22066 if (hasrotation) a = Math.atan2(d[1], d[0]); 22067 else a = this.startAngle[this.sides_] || this.startAngle['default']; 22068 if (this.sides_) { 22069 g = ol.geom.Polygon.fromCircle (circle, this.sides_, a); 22070 } else { 22071 // Optimize points on the circle 22072 centerPx = this.getMap().getPixelFromCoordinate(this.center_); 22073 dmax = Math.max (100, Math.abs(centerPx[0]-this.coordPx_[0]), Math.abs(centerPx[1]-this.coordPx_[1])); 22074 dmax = Math.min ( this.maxCircleCoordinates_, Math.round(dmax / (this.centered_ ? 3:5) )); 22075 g = ol.geom.Polygon.fromCircle (circle, dmax, 0); 22076 } 22077 if (hasrotation) return g; 22078 // Scale polygon to fit extent 22079 var ext = g.getExtent(); 22080 if (!this.centered_) center = this.center_; 22081 else center = [ 2*this.center_[0]-this.coord_[0], 2*this.center_[1]-this.coord_[1] ]; 22082 var scx = (center[0] - coord[0]) / (ext[0] - ext[2]); 22083 var scy = (center[1] - coord[1]) / (ext[1] - ext[3]); 22084 if (this.square_) { 22085 var sc = Math.min(Math.abs(scx),Math.abs(scy)); 22086 scx = Math.sign(scx)*sc; 22087 scy = Math.sign(scy)*sc; 22088 } 22089 var t = [ center[0] - ext[0]*scx, center[1] - ext[1]*scy ]; 22090 g.applyTransform(function(g1, g2, dim) { 22091 for (var i=0; i<g1.length; i+=dim) { 22092 g2[i] = g1[i]*scx + t[0]; 22093 g2[i+1] = g1[i+1]*scy + t[1]; 22094 } 22095 return g2; 22096 }); 22097 return g; 22098 } 22099 } 22100 } 22101 // No geom => return a point 22102 return new ol.geom.Point(this.center_); 22103}; 22104/** Draw sketch 22105* @return {ol.Feature} The feature being drawn. 22106*/ 22107ol.interaction.DrawRegular.prototype.drawSketch_ = function(evt) { 22108 this.overlayLayer_.getSource().clear(); 22109 if (evt) { 22110 this.square_ = this.squareFn_ ? this.squareFn_(evt) : evt.originalEvent.shiftKey; 22111 this.centered_ = this.centeredFn_ ? this.centeredFn_(evt) : evt.originalEvent.metaKey || evt.originalEvent.ctrlKey; 22112 var g = this.getGeom_(); 22113 if (g) { 22114 var f = this.feature_; 22115 //f.setGeometry (g); 22116 if (g.getType()==='Polygon') f.getGeometry().setCoordinates(g.getCoordinates()); 22117 this.overlayLayer_.getSource().addFeature(f); 22118 if (this.coord_ 22119 && this.square_ 22120 && ((this.canRotate_ && this.centered_ && this.coord_) || (!this.sides_ && !this.centered_))) { 22121 this.overlayLayer_.getSource().addFeature(new ol.Feature(new ol.geom.LineString([this.center_,this.coord_]))); 22122 } 22123 return f; 22124 } 22125 } 22126}; 22127/** Draw sketch (Point) 22128*/ 22129ol.interaction.DrawRegular.prototype.drawPoint_ = function(pt, noclear) { 22130 if (!noclear) this.overlayLayer_.getSource().clear(); 22131 this.overlayLayer_.getSource().addFeature(new ol.Feature(new ol.geom.Point(pt))); 22132}; 22133/** 22134 * @param {ol.MapBrowserEvent} evt Map browser event. 22135 */ 22136ol.interaction.DrawRegular.prototype.handleEvent_ = function(evt) { 22137 var dx, dy; 22138 // Event date time 22139 this._eventTime = new Date(); 22140 switch (evt.type) { 22141 case "pointerdown": { 22142 if (this.conditionFn_ && !this.conditionFn_(evt)) break; 22143 this.downPx_ = evt.pixel; 22144 this.start_(evt); 22145 // Test long touch 22146 var dt = 500; 22147 this._longTouch = false;
22148 setTimeout(function() { 22149 this._longTouch = (new Date() - this._eventTime > .9*dt); 22150 if (this._longTouch) this.handleMoveEvent_(evt); 22151 }.bind(this), dt); 22152 break; 22153 } 22154 case "pointerup": { 22155 // Started and fisrt move 22156 if (this.started_ && this.coord_) { 22157 dx = this.downPx_[0] - evt.pixel[0]; 22158 dy = this.downPx_[1] - evt.pixel[1]; 22159 if (dx*dx + dy*dy <= this.squaredClickTolerance_) { 22160 // The pointer has moved 22161 if ( this.lastEvent == "pointermove" || this.lastEvent == "keydown" ) { 22162 this.end_(evt); 22163 } 22164 // On touch device there is no move event : terminate = click on the same point 22165 else { 22166 dx = this.upPx_[0] - evt.pixel[0]; 22167 dy = this.upPx_[1] - evt.pixel[1]; 22168 if ( dx*dx + dy*dy <= this.squaredClickTolerance_) { 22169 this.end_(evt); 22170 } else { 22171 this.handleMoveEvent_(evt); 22172 this.drawPoint_(evt.coordinate,true); 22173 } 22174 } 22175 } 22176 } 22177 this.upPx_ = evt.pixel; 22178 break; 22179 } 22180 case "pointerdrag": { 22181 if (this.started_) { 22182 var centerPx = this.getMap().getPixelFromCoordinate(this.center_); 22183 dx = centerPx[0] - evt.pixel[0]; 22184 dy = centerPx[1] - evt.pixel[1]; 22185 if (dx*dx + dy*dy <= this.squaredClickTolerance_) { 22186 this.reset(); 22187 } 22188 } 22189 return !this._longTouch; 22190 // break; 22191 } 22192 case "pointermove": { 22193 if (this.started_) { 22194 dx = this.downPx_[0] - evt.pixel[0]; 22195 dy = this.downPx_[1] - evt.pixel[1]; 22196 if (dx*dx + dy*dy > this.squaredClickTolerance_) { 22197 this.handleMoveEvent_(evt); 22198 this.lastEvent = evt.type; 22199 } 22200 } 22201 break; 22202 } 22203 default: { 22204 this.lastEvent = evt.type; 22205 // Prevent zoom in on dblclick 22206 if (this.started_ && evt.type==='dblclick') { 22207 //evt.stopPropagation(); 22208 return false; 22209 } 22210 break; 22211 } 22212 } 22213 return true; 22214} 22215/** Stop drawing. 22216 */ 22217ol.interaction.DrawRegular.prototype.finishDrawing = function() { 22218 if (this.started_ && this.coord_) { 22219 this.end_({ pixel: this.upPx_, coordinate: this.coord_}); 22220 } 22221}; 22222/** 22223 * @param {ol.MapBrowserEvent} evt Event. 22224 */ 22225ol.interaction.DrawRegular.prototype.handleMoveEvent_ = function(evt) { 22226 if (this.started_) { 22227 this.coord_ = evt.coordinate; 22228 this.coordPx_ = evt.pixel; 22229 var f = this.drawSketch_(evt); 22230 this.dispatchEvent({ 22231 type:'drawing', 22232 feature: f, 22233 pixel: evt.pixel, 22234 startCoordinate: this.center_, 22235 coordinate: evt.coordinate, 22236 square: this.square_, 22237 centered: this.centered_ 22238 }); 22239 } else { 22240 this.drawPoint_(evt.coordinate); 22241 } 22242}; 22243/** Start an new draw 22244 * @param {ol.MapBrowserEvent} evt Map browser event. 22245 * @return {boolean} `false` to stop the drag sequence. 22246 */ 22247ol.interaction.DrawRegular.prototype.start_ = function(evt) { 22248 if (!this.started_) { 22249 this.started_ = true; 22250 this.center_ = evt.coordinate; 22251 this.coord_ = null; 22252 var f = this.feature_ = new ol.Feature({}); 22253 f.setGeometryName(this.geometryName_); 22254 f.setGeometry(new ol.geom.Polygon([[evt.coordinate,evt.coordinate,evt.coordinate]])); 22255 this.drawSketch_(evt); 22256 this.dispatchEvent({ type:'drawstart', feature: f, pixel: evt.pixel, coordinate: evt.coordinate }); 22257 } else { 22258 this.coord_ = evt.coordinate; 22259 } 22260}; 22261/** End drawing 22262 * @param {ol.MapBrowserEvent} evt Map browser event. 22263 * @return {boolean} `false` to stop the drag sequence. 22264 */ 22265ol.interaction.DrawRegular.prototype.end_ = function(evt) { 22266 this.coord_ = evt.coordinate; 22267 this.started_ = false; 22268 if (this.coord_ && (this.center_[0]!==this.coord_[0] || this.center_[1]!==this.coord_[1])) { 22269 var f = this.feature_; 22270 f.setGeometry(this.getGeom_()); 22271 if (this.source_) this.source_.addFeature(f); 22272 else if (this.features_) this.features_.push(f); 22273 this.dispatchEvent({ type:'drawend', feature: f, pixel: evt.pixel, coordinate: evt.coordinate, square: this.square_, centered: this.centered_ }); 22274 } else { 22275 this.dispatchEvent({ type:'drawcancel', feature: null, pixel: evt.pixel, coordinate: evt.coordinate, square: this.square_, centered: this.centered_ }); 22276 } 22277 this.center_ = this.coord_ = null; 22278 this.drawSketch_(); 22279}; 22280 22281/* Copyright (c) 2016 Jean-Marc VIGLINO, 22282 released under the CeCILL-B license (French BSD license)
22283 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 22284*/ 22285/** Interaction DrawTouch : pointer is deferred to the center of the viewport and a target is drawn to materialize this point 22286 * The interaction modifies map browser event coordinate and pixel properties to force pointer on the viewport center to any interaction that them. 22287 * @constructor 22288 * @fires drawstart 22289 * @fires drawend 22290 * @fires drawabort 22291 * @extends {ol.interaction.CenterTouch} 22292 * @param {olx.interaction.DrawOptions} options 22293 * @param {ol.source.Vector | undefined} options.source Destination source for the drawn features. 22294 * @param {ol.geom.GeometryType} options.type Drawing type ('Point', 'LineString', 'Polygon') not ('MultiPoint', 'MultiLineString', 'MultiPolygon' or 'Circle'). Required. 22295 * @param {boolean} [options.tap=true] enable point insertion on tap, default true 22296 * @param {ol.style.Style|Array<ol.style.Style>} [options.style] Drawing style 22297 * @param {ol.style.Style|Array<ol.style.Style>} [options.sketchStyle] Sketch style 22298 * @param {ol.style.Style|Array<ol.style.Style>} [options.targetStyle] a style to draw the target point, default cross style 22299 * @param {string} [options.composite] composite operation : difference|multiply|xor|screen|overlay|darken|lighter|lighten|... 22300 */ 22301ol.interaction.DrawTouch = function(options) { 22302 options = options||{}; 22303 options.handleEvent = function(e) { 22304 if (this.get('tap')) { 22305 this.sketch.setPosition(this.getPosition()); 22306 switch (e.type) { 22307 case 'singleclick': { 22308 this.addPoint(); 22309 break; 22310 } 22311 case 'dblclick': { 22312 this.addPoint(); 22313 this.finishDrawing(); 22314 return false; 22315 //break; 22316 } 22317 default: break; 22318 } 22319 } 22320 return true; 22321 } 22322 if (!options.sketchStyle) { 22323 options.sketchStyle = ol.style.Style.defaultStyle(); 22324 } 22325 var sketch = this.sketch = new ol.layer.SketchOverlay(options); 22326 sketch.on(['drawstart', 'drawabort'], function(e) { this.dispatchEvent(e); }.bind(this)); 22327 sketch.on(['drawend'], function(e) { 22328 if (e.feature && e.valid && options.source) options.source.addFeature(e.feature); 22329 this.dispatchEvent(e); 22330 }.bind(this)); 22331 ol.interaction.CenterTouch.call(this, options); 22332 this._source = options.source; 22333 this.set('tap', options.tap!==false); 22334}; 22335ol.ext.inherits(ol.interaction.DrawTouch, ol.interaction.CenterTouch); 22336/** 22337 * Remove the interaction from its current map, if any, and attach it to a new 22338 * map, if any. Pass `null` to just remove the interaction from the current map. 22339 * @param {ol.Map} map Map. 22340 * @api stable 22341 */ 22342ol.interaction.DrawTouch.prototype.setMap = function(map) { 22343 if (this._listener) { 22344 for(var l in this._listener) ol.Observable.unByKey(l); 22345 } 22346 this._listener = {}; 22347 ol.interaction.CenterTouch.prototype.setMap.call (this, map); 22348 this.sketch.setMap(map); 22349 if (map){ 22350 this._listener.center = map.on('postcompose', function() { 22351 if (!ol.coordinate.equal(this.getPosition(), this.sketch.getPosition() || [])) { 22352 this.sketch.setPosition(this.getPosition()); 22353 } 22354 }.bind(this)); 22355 } 22356}; 22357/** Set geometry type 22358 * @param {ol.geom.GeometryType} type 22359 */ 22360ol.interaction.DrawTouch.prototype.setGeometryType = function(type) { 22361 return this.sketch.setGeometryType(type); 22362}; 22363/** Get geometry type 22364 * @return {ol.geom.GeometryType} 22365 */ 22366ol.interaction.DrawTouch.prototype.getGeometryType = function() { 22367 return this.sketch.getGeometryType(); 22368}; 22369/** Start drawing and add the sketch feature to the target layer. 22370 * The ol.interaction.Draw.EventType.DRAWEND event is dispatched before inserting the feature. 22371 */ 22372ol.interaction.DrawTouch.prototype.finishDrawing = function() { 22373 this.sketch.finishDrawing(true); 22374}; 22375/** Add a new Point to the drawing 22376 */ 22377ol.interaction.DrawTouch.prototype.addPoint = function() { 22378 this.sketch.addPoint(this.getPosition()); 22379}; 22380/** Remove last point of the feature currently being drawn. 22381 */ 22382ol.interaction.DrawTouch.prototype.removeLastPoint = function() { 22383 this.sketch.removeLastPoint(); 22384}; 22385/** 22386 * Activate or deactivate the interaction. 22387 * @param {boolean} active Active. 22388 * @observable 22389 * @api 22390 */ 22391ol.interaction.DrawTouch.prototype.setActive = function(b) { 22392 ol.interaction.CenterTouch.prototype.setActive.call (this, b); 22393 this.sketch.abortDrawing(); 22394 this.sketch.setVisible(b); 22395}; 22396 22397/** Extend DragAndDrop choose drop zone + fires loadstart, loadend 22398 * @constructor 22399 * @extends {ol.interaction.DragAndDrop} 22400 * @fires loadstart, loadend, addfeatures 22401 * @param {*} options 22402 * @param {string} options.zone selector for the drop zone, default document 22403 * @param{ol.projection} options.projection default projection of the map 22404 * @param {Array<function(new:ol.format.Feature)>|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 ] 22405 * @param {Array<string>|undefined} options.accept list of accepted format, default ["gpx","json","geojsonx","geojsonp","geojson","igc","kml","topojson"] 22406 */ 22407ol.interaction.DropFile = function(options) { 22408 options = options||{}; 22409 ol.interaction.DragAndDrop.call(this, {}); 22410 var zone = options.zone || document; 22411 zone.addEventListener('dragenter', this.onstop ); 22412 zone.addEventListener('dragover', this.onstop ); 22413 zone.addEventListener('dragleave', this.onstop ); 22414 // Options 22415 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 ]; 22416 this.projection_ = options.projection; 22417 this.accept_ = options.accept || ["gpx","json","geojsonx","geojsonp","geojson","igc","kml","topojson"]; 22418 var self = this; 22419 zone.addEventListener('drop', function(e){ return self.ondrop(e);}); 22420}; 22421ol.ext.inherits(ol.interaction.DropFile, ol.interaction.DragAndDrop); 22422/** Set the map 22423*/ 22424ol.interaction.DropFile.prototype.setMap = function(map) { 22425 ol.interaction.Interaction.prototype.setMap.call(this, map); 22426}; 22427/** Do something when over 22428*/ 22429ol.interaction.DropFile.prototype.onstop = function(e) { 22430 e.preventDefault(); 22431 e.stopPropagation(); 22432 return false;
22433} 22434/** Do something when over 22435*/ 22436ol.interaction.DropFile.prototype.ondrop = function(e) { 22437 e.preventDefault(); 22438 if (e.dataTransfer && e.dataTransfer.files.length) { 22439 var self = this; 22440 var projection = this.projection_ || (this.getMap() ? this.getMap().getView().getProjection() : null); 22441 // fetch FileList object 22442 var files = e.dataTransfer.files; // e.originalEvent.target.files ? 22443 // process all File objects 22444 var file; 22445 var pat = /\.([0-9a-z]+)(?=[?#])|(\.)(?:[\w]+)$/; 22446 for (var i=0; file=files[i]; i++) { 22447 var ex = file.name.match(pat)[0]; 22448 var isok = (this.accept_.indexOf(ex.toLocaleLowerCase()) >= 0); 22449 self.dispatchEvent({ type:'loadstart', file: file, filesize: file.size, filetype: file.type, fileextension: ex, projection: projection, isok: isok }); 22450 // Don't load file 22451 if (!this.formatConstructors_.length) continue; 22452 // Load file 22453 var reader = new FileReader(); 22454 var formatConstructors = this.formatConstructors_ 22455 var theFile = file; 22456 reader.onload = function(e) { 22457 var result = e.target.result; 22458 var features = []; 22459 var i, ii; 22460 for (i = 0, ii = formatConstructors.length; i < ii; ++i) { 22461 var formatConstructor = formatConstructors[i]; 22462 try { 22463 var format = new formatConstructor(); 22464 features = format.readFeatures(result, { featureProjection: projection }); 22465 if (features && features.length > 0) { 22466 self.dispatchEvent({ type:'addfeatures', features: features, file: theFile, projection: projection }); 22467 self.dispatchEvent({ type:'loadend', features: features, file: theFile, projection: projection }); 22468 return; 22469 } 22470 } catch(e) { /* ok */ } 22471 } 22472 // Nothing match, try to load by yourself 22473 self.dispatchEvent({ type:'loadend', file: theFile, result: result }); 22474 }; 22475 // Start loading 22476 reader.readAsText(file); 22477 } 22478 } 22479 return false; 22480}; 22481 22482/** A Select interaction to fill feature's properties on click. 22483 * @constructor 22484 * @extends {ol.interaction.Interaction} 22485 * @fires setattributestart 22486 * @fires setattributeend 22487 * @param {*} options extentol.interaction.Select options 22488 * @param {boolean} options.active activate the interaction on start, default true 22489 * @param {string=} options.name 22490 * @param {boolean|string} options.cursor interaction cursor if false use default, default use a paint bucket cursor 22491 * @param {*} properties The properties as key/value 22492 */ 22493ol.interaction.FillAttribute = function(options, properties) { 22494 options = options || {}; 22495 if (!options.condition) options.condition = ol.events.condition.click; 22496 ol.interaction.Select.call(this, options); 22497 this.setActive(options.active!==false) 22498 this.set('name', options.name); 22499 this._attributes = properties; 22500 this.on('select', function(e) { 22501 this.getFeatures().clear(); 22502 this.fill(e.selected, this._attributes); 22503 }.bind(this)); 22504 if (options.cursor === undefined) { 22505 var canvas = document.createElement('CANVAS'); 22506 canvas.width = canvas.height = 32; 22507 var ctx = canvas.getContext("2d"); 22508 ctx.beginPath(); 22509 ctx.moveTo(9,3); 22510 ctx.lineTo(2,9); 22511 ctx.lineTo(10,17); 22512 ctx.lineTo(17,11); 22513 ctx.closePath(); 22514 ctx.fillStyle = "#fff"; 22515 ctx.fill(); 22516 ctx.stroke(); 22517 ctx.beginPath(); 22518 ctx.moveTo(6,4); 22519 ctx.lineTo(0,8); 22520 ctx.lineTo(0,13); 22521 ctx.lineTo(3,17); 22522 ctx.lineTo(3,8); 22523 ctx.closePath(); 22524 ctx.fillStyle = "#000"; 22525 ctx.fill(); 22526 ctx.stroke(); 22527 ctx.moveTo(8,8); 22528 ctx.lineTo(10,0); 22529 ctx.lineTo(11,0); 22530 ctx.lineTo(13,3); 22531 ctx.lineTo(13,7); 22532 ctx.stroke(); 22533 this._cursor = 'url('+canvas.toDataURL()+') 0 13, auto'; 22534 } 22535 if (options.cursor) { 22536 this._cursor = options.cursor; 22537 } 22538}; 22539ol.ext.inherits(ol.interaction.FillAttribute, ol.interaction.Select); 22540/** Define the interaction cursor 22541 * @param {string} cursor CSS cursor 22542 */ 22543ol.interaction.FillAttribute.prototype.setCursor = function(cursor) { 22544 this._cursor = cursor; 22545}; 22546/** Get the interaction cursor 22547 * @return {string} cursor 22548 */ 22549ol.interaction.FillAttribute.prototype.getCursor = function() { 22550 return this._cursor; 22551}; 22552/** Activate the interaction 22553 * @param {boolean} active 22554 */ 22555ol.interaction.FillAttribute.prototype.setActive = function(active) { 22556 if(active === this.getActive()) return; 22557 ol.interaction.Select.prototype.setActive.call(this, active); 22558 if (this.getMap() && this._cursor) { 22559 if (active) { 22560 this._previousCursor = this.getMap().getTargetElement().style.cursor; 22561 this.getMap().getTargetElement().style.cursor = this._cursor; 22562// console.log('setCursor',this._cursor) 22563 } else { 22564 this.getMap().getTargetElement().style.cursor = this._previousCursor; 22565 this._previousCursor = undefined; 22566 } 22567 } 22568}; 22569/** Set attributes 22570 * @param {*} properties The properties as key/value 22571 */ 22572ol.interaction.FillAttribute.prototype.setAttributes = function(properties) { 22573 this._attributes = properties; 22574}; 22575/** Set an attribute 22576 * @param {string} key 22577 * @param {*} val 22578 */ 22579ol.interaction.FillAttribute.prototype.setAttribute = function(key, val) { 22580 this._attributes[key] = val; 22581}; 22582/** get attributes 22583 * @return {*} The properties as key/value 22584 */ 22585ol.interaction.FillAttribute.prototype.getAttributes = function() { 22586 return this._attributes; 22587}; 22588/** Get an attribute 22589 * @param {string} key 22590 * @return {*} val 22591 */ 22592ol.interaction.FillAttribute.prototype.getAttribute = function(key) { 22593 return this._attributes[key]; 22594}; 22595/** Fill feature attributes 22596 * @param {Array<ol.Feature>} features The features to modify 22597 * @param {*} properties The properties as key/value 22598 */ 22599ol.interaction.FillAttribute.prototype.fill = function(features, properties) { 22600 if (features.length && properties) { 22601 // Test changes 22602 var changes = false;
22603 for (var i=0, f; f = features[i]; i++) { 22604 for (var p in properties) { 22605 if (f.get(p) !== properties[p]) changes = true; 22606 } 22607 if (changes) break; 22608 } 22609 // Set Attributes 22610 if (changes) { 22611 this.dispatchEvent({ 22612 type: 'setattributestart', 22613 features: features, 22614 properties: properties 22615 }); 22616 features.forEach(function(f) { 22617 for (var p in properties) { 22618 f.set(p, properties[p]); 22619 } 22620 }); 22621 this.dispatchEvent({ 22622 type: 'setattributeend', 22623 features: features, 22624 properties: properties 22625 }); 22626 } 22627 } 22628}; 22629 22630/** 22631 * @constructor 22632 * @extends {ol.interaction.Pointer} 22633 * @param {ol.flashlight.options} flashlight options param 22634 * @param {ol.Color} options.color light color, default transparent 22635 * @param {ol.Color} options.fill fill color, default rgba(0,0,0,0.8) 22636 * @param {number} options.radius radius of the flash 22637 */ 22638ol.interaction.Flashlight = function(options) { 22639 ol.interaction.Pointer.call(this, { 22640 handleDownEvent: this.setPosition, 22641 handleMoveEvent: this.setPosition 22642 }); 22643 // Default options 22644 options = options||{}; 22645 this.pos = false; 22646 this.radius = (options.radius||100); 22647 this.setColor(options); 22648}; 22649ol.ext.inherits(ol.interaction.Flashlight, ol.interaction.Pointer); 22650/** Set the map > start postcompose 22651*/ 22652ol.interaction.Flashlight.prototype.setMap = function(map) { 22653 if (this.getMap()) { 22654 this.getMap().render(); 22655 } 22656 if (this._listener) ol.Observable.unByKey(this._listener); 22657 this._listener = null; 22658 ol.interaction.Pointer.prototype.setMap.call(this, map); 22659 if (map) { 22660 this._listener = map.on('postcompose', this.postcompose_.bind(this)); 22661 } 22662} 22663/** Set flashlight radius 22664 * @param {integer} radius 22665*/ 22666ol.interaction.Flashlight.prototype.setRadius = function(radius) { 22667 this.radius = radius 22668 if (this.getMap()) { 22669 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 22670 } 22671} 22672/** Set flashlight color 22673 * @param {ol.flashlight.options} flashlight options param 22674* - color {ol.Color} light color, default transparent 22675* - fill {ol.Color} fill color, default rgba(0,0,0,0.8) 22676*/ 22677ol.interaction.Flashlight.prototype.setColor = function(options) { 22678 // Backcolor 22679 var color = (options.fill ? options.fill : [0,0,0,0.8]); 22680 var c = ol.color.asArray(color); 22681 this.startColor = ol.color.asString(c); 22682 // Halo color 22683 if (options.color) { 22684 c = this.endColor = ol.color.asString(ol.color.asArray(options.color)||options.color); 22685 } else { 22686 c[3] = 0 22687 this.endColor = ol.color.asString(c); 22688 } 22689 c[3] = 0.1; 22690 this.midColor = ol.color.asString(c); 22691 if (this.getMap()) { 22692 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 22693 } 22694} 22695/** Set position of the flashlight 22696* @param {ol.Pixel|ol.MapBrowserEvent} 22697*/ 22698ol.interaction.Flashlight.prototype.setPosition = function(e) { 22699 if (e.pixel) this.pos = e.pixel; 22700 else this.pos = e; 22701 if (this.getMap()) { 22702 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 22703 } 22704} 22705/** Postcompose function 22706*/ 22707ol.interaction.Flashlight.prototype.postcompose_ = function(e) { 22708 var ctx = ol.ext.getMapCanvas(this.getMap()).getContext('2d'); 22709 var ratio = e.frameState.pixelRatio; 22710 var w = ctx.canvas.width; 22711 var h = ctx.canvas.height; 22712 ctx.save(); 22713 ctx.scale(ratio,ratio); 22714 if (!this.pos) { 22715 ctx.fillStyle = this.startColor; 22716 ctx.fillRect( 0,0,w,h ); 22717 } else { 22718 var d = Math.max(w, h); 22719 // reveal wherever we drag 22720 var radGrd = ctx.createRadialGradient( this.pos[0], this.pos[1], w*this.radius/d, this.pos[0], this.pos[1], h*this.radius/d ); 22721 radGrd.addColorStop( 0, this.startColor ); 22722 radGrd.addColorStop( 0.8, this.midColor ); 22723 radGrd.addColorStop( 1, this.endColor ); 22724 ctx.fillStyle = radGrd; 22725 ctx.fillRect( this.pos[0] - d, this.pos[1] - d, 2*d, 2*d ); 22726 } 22727 ctx.restore(); 22728}; 22729 22730/** An interaction to focus on the map on click. Usefull when using keyboard event on the map. 22731 * @constructor 22732 * @fires focus 22733 * @extends {ol.interaction.Interaction} 22734 */ 22735ol.interaction.FocusMap = function() { 22736 // 22737 ol.interaction.Interaction.call(this, {}); 22738 // Focus (hidden) button to focus on the map when click on it 22739 this.focusBt = ol.ext.element.create('BUTTON', { 22740 on: { 22741 focus: function() { 22742 this.dispatchEvent({ type:'focus' }); 22743 }.bind(this) 22744 }, 22745 style: { 22746 position: 'absolute', 22747 zIndex: -1, 22748 top: 0, 22749 opacity: 0 22750 } 22751 }); 22752}; 22753ol.ext.inherits(ol.interaction.FocusMap, ol.interaction.Interaction); 22754/** Set the map > add the focus button and focus on the map when pointerdown to enable keyboard events. 22755 */ 22756ol.interaction.FocusMap.prototype.setMap = function(map) { 22757 if (this._listener) ol.Observable.unByKey(this._listener); 22758 this._listener = null; 22759 if (this.getMap()) { this.getMap().getViewport().removeChild(this.focusBt); } 22760 ol.interaction.Interaction.prototype.setMap.call (this, map); 22761 if (this.getMap()) { 22762 // Force focus on the clicked map 22763 this._listener = this.getMap().on('pointerdown', function() { 22764 if (this.getActive()) this.focusBt.focus(); 22765 }.bind(this)); 22766 this.getMap().getViewport().appendChild(this.focusBt); 22767 } 22768}; 22769 22770// 22771/** Interaction to draw on the current geolocation 22772 * It combines a draw with a ol.Geolocation 22773 * @constructor 22774 * @extends {ol.interaction.Interaction} 22775 * @fires drawstart, drawend, drawing, tracking, follow 22776 * @param {any} options 22777 * @param { ol.Collection.<ol.Feature> | undefined } option.features Destination collection for the drawn features. 22778 * @param { ol.source.Vector | undefined } options.source Destination source for the drawn features. 22779 * @param {ol.geom.GeometryType} options.type Drawing type ('Point', 'LineString', 'Polygon'), default LineString. 22780 * @param {Number | undefined} options.minAccuracy minimum accuracy underneath a new point will be register (if no condition), default 20 22781 * @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 22782 * @param {Object} options.attributes a list of attributes to register as Point properties: {accuracy:true,accuracyGeometry:true,heading:true,speed:true}, default none. 22783 * @param {Number} options.tolerance tolerance to add a new point (in meter), default 5 22784 * @param {Number} options.zoom zoom for tracking, default 16 22785 * @param {Number} options.minZoom min zoom for tracking, if zoom is less it will zoom to it, default use zoom option 22786 * @param {boolean|auto|position|visible} options.followTrack true if you want the interaction to follow the track on the map, default true 22787 * @param { ol.style.Style | Array.<ol.style.Style> | ol.StyleFunction | undefined } options.style Style for sketch features. 22788 */ 22789ol.interaction.GeolocationDraw = function(options) { 22790 if (!options) options={}; 22791 // Geolocation 22792 this.geolocation = new ol.Geolocation(({ 22793 projection: "EPSG:4326", 22794 trackingOptions: { 22795 maximumAge: 10000, 22796 enableHighAccuracy: true, 22797 timeout: 600000 22798 } 22799 })); 22800 this.geolocation.on('change', this.draw_.bind(this)); 22801 // Current path 22802 this.path_ = []; 22803 this.lastPosition_ = false;
22804 // Default style 22805 var white = [255, 255, 255, 1]; 22806 var blue = [0, 153, 255, 1]; 22807 var width = 3; 22808 var circle = new ol.style.Circle({ 22809 radius: width * 2, 22810 fill: new ol.style.Fill({ color: blue }), 22811 stroke: new ol.style.Stroke({ color: white, width: width / 2 }) 22812 }); 22813 var style = [ 22814 new ol.style.Style({ 22815 stroke: new ol.style.Stroke({ color: white, width: width + 2 }) 22816 }), 22817 new ol.style.Style({ 22818 stroke: new ol.style.Stroke({ color: blue, width: width }), 22819 fill: new ol.style.Fill({ 22820 color: [255, 255, 255, 0.5] 22821 }) 22822 }) 22823 ]; 22824 var triangle = new ol.style.RegularShape({ 22825 radius: width * 3.5, 22826 points: 3, 22827 rotation: 0, 22828 fill: new ol.style.Fill({ color: blue }), 22829 stroke: new ol.style.Stroke({ color: white, width: width / 2 }) 22830 }); 22831 // stretch the symbol 22832 var c = triangle.getImage(); 22833 var ctx = c.getContext("2d"); 22834 var c2 = document.createElement('canvas'); 22835 c2.width = c2.height = c.width; 22836 c2.getContext("2d").drawImage(c, 0,0); 22837 ctx.clearRect(0,0,c.width,c.height); 22838 ctx.drawImage(c2, 0,0, c.width, c.height, width, 0, c.width-2*width, c.height); 22839 var defaultStyle = function(f) { 22840 if (f.get('heading')===undefined) { 22841 style[1].setImage(circle); 22842 } else { 22843 style[1].setImage(triangle); 22844 triangle.setRotation( f.get('heading') || 0); 22845 } 22846 return style; 22847 } 22848 // Style for the accuracy geometry 22849 this.locStyle = { 22850 error: new ol.style.Style({ fill: new ol.style.Fill({ color: [255, 0, 0, 0.2] }) }), 22851 warn: new ol.style.Style({ fill: new ol.style.Fill({ color: [255, 192, 0, 0.2] }) }), 22852 ok: new ol.style.Style({ fill: new ol.style.Fill({ color: [0, 255, 0, 0.2] }) }), 22853 }; 22854 // Create a new overlay layer for the sketch 22855 this.overlayLayer_ = new ol.layer.Vector({ 22856 source: new ol.source.Vector(), 22857 name:'GeolocationDraw overlay', 22858 style: options.style || defaultStyle 22859 }); 22860 this.sketch_ = [new ol.Feature(), new ol.Feature(), new ol.Feature()]; 22861 this.overlayLayer_.getSource().addFeatures(this.sketch_); 22862 this.features_ = options.features; 22863 this.source_ = options.source; 22864 this.condition_ = options.condition || function(loc) { 22865 return loc.getAccuracy() < this.get("minAccuracy") 22866 }; 22867 // Prevent interaction when tracking 22868 ol.interaction.Interaction.call(this, { 22869 handleEvent: function() { 22870 return (!this.get('followTrack') || this.get('followTrack')=='auto');// || !geoloc.getTracking()); 22871 } 22872 }); 22873 this.set('type', options.type||"LineString"); 22874 this.set('attributes', options.attributes||{}); 22875 this.set('minAccuracy', options.minAccuracy||20); 22876 this.set('tolerance', options.tolerance||5); 22877 this.set('zoom', options.zoom); 22878 this.set('minZoom', options.minZoom); 22879 this.setFollowTrack (options.followTrack===undefined ? true : options.followTrack); 22880 this.setActive(false); 22881}; 22882ol.ext.inherits(ol.interaction.GeolocationDraw, ol.interaction.Interaction); 22883/** Simplify 3D geometry 22884 * @param {ol.geom.Geometry} geo 22885 * @param {number} tolerance 22886 */ 22887ol.interaction.GeolocationDraw.prototype.simplify3D = function(geo, tolerance) { 22888 var geom = geo.getCoordinates(); 22889 var proj = this.getMap().getView().getProjection(); 22890 if (this.get("type")==='Polygon') { 22891 geom = geom[0]; 22892 } 22893 var simply = [geom[0]]; 22894 var pi, p = ol.proj.transform(geom[0], proj, 'EPSG:4326') 22895 for (var i=1; i<geom.length; i++) { 22896 pi = ol.proj.transform(geom[i], proj, 'EPSG:4326') 22897 var d = ol.sphere.getDistance(p, pi); 22898 if (d > tolerance) { 22899 simply.push(geom[i]); 22900 p = pi; 22901 } 22902 } 22903 if (simply[simply.length-1] !== geom[geom.length-1]) { 22904 simply.push(geom[geom.length-1]); 22905 } 22906 /* 22907 var simply = geo.simplify(tolerance).getCoordinates(); 22908 if (this.get("type")==='Polygon') { 22909 simply = simply[0]; 22910 } 22911 var step=0; 22912 simply.forEach(function(p) { 22913 for (; step<this.path_.length; step++) { 22914 if (ol.coordinate.equal(p, this.path_[step])) { 22915 p[2] = this.path_[step][2]; 22916 p[3] = this.path_[step][3]; 22917 break; 22918 } 22919 } 22920 }.bind(this)); 22921 */ 22922 // Get 3D geom 22923 if (this.get("type")==='Polygon') { 22924 geo = new ol.geom.Polygon([simply], 'XYZM'); 22925 } else { 22926 geo = new ol.geom.LineString(simply, 'XYZM'); 22927 } 22928 return geo; 22929}; 22930/** 22931 * Remove the interaction from its current map, if any, and attach it to a new 22932 * map, if any. Pass `null` to just remove the interaction from the current map. 22933 * @param {ol.Map} map Map. 22934 * @api stable 22935 */ 22936ol.interaction.GeolocationDraw.prototype.setMap = function(map) { 22937 if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_); 22938 ol.interaction.Pointer.prototype.setMap.call (this, map);
22939 this.overlayLayer_.setMap(map); 22940 if (map) this.geolocation.setProjection(map.getView().getProjection()); 22941}; 22942/** Activate or deactivate the interaction. 22943 * @param {boolean} active 22944 */ 22945ol.interaction.GeolocationDraw.prototype.setActive = function(active) { 22946 if (active === this.getActive()) return; 22947 ol.interaction.Interaction.prototype.setActive.call(this, active); 22948 this.overlayLayer_.setVisible(active); 22949 if (this.getMap()) { 22950 this.geolocation.setTracking(active); 22951 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 22952 } 22953 this.pause(!active); 22954 if (active) { 22955 // Start drawing 22956 this.reset(); 22957 this.dispatchEvent({ type:'drawstart', feature: this.sketch_[1]}); 22958 } else { 22959 var f = this.sketch_[1].clone(); 22960 if (f.getGeometry()) { 22961 if (this.features_) this.features_.push(f); 22962 if (this.source_) this.source_.addFeature(f); 22963 } 22964 this.dispatchEvent({ type:'drawend', feature: f}); 22965 } 22966}; 22967/** Simulate a track and override current geolocation 22968 * @param {Array<ol.coordinate>|boolean} track a list of point or false to stop 22969 * @param {*} options 22970 * @param {number} delay delay in ms, default 1000 (1s) 22971 * @param {number} accuracy gps accuracy, default 10 22972 * @param {boolean} repeat repeat track, default true 22973 */ 22974ol.interaction.GeolocationDraw.prototype.simulate = function(track, options) { 22975 if (this._track) { 22976 clearTimeout(this._track.timeout); 22977 } 22978 if (!track) { 22979 this._track = false; 22980 return; 22981 } 22982 options = options || {}; 22983 var delay = options.delay || 1000; 22984 function handleTrack() { 22985 if (this._track.pos >= this._track.track.length) { 22986 this._track = false; 22987 return; 22988 } 22989 var coord = this._track.track[this._track.pos]; 22990 coord[2] = coord[3] || 0; 22991 coord[3] = (new Date()).getTime(); 22992 this._track.pos++; 22993 if (options.repeat !== false) { 22994 this._track.pos = this._track.pos % this._track.track.length; 22995 } 22996 if (this.getActive()) this.draw_(true, coord, options.accuracy); 22997 this._track.timeout = setTimeout(handleTrack.bind(this), delay); 22998 } 22999 this._track = { 23000 track: track, 23001 pos: 0, 23002 timeout: setTimeout(handleTrack.bind(this), 0) 23003 } 23004}; 23005/** Is simulation on ? 23006 * @returns {boolean} 23007 */ 23008ol.interaction.GeolocationDraw.prototype.simulating = function() { 23009 return !!this._track; 23010}; 23011/** Reset drawing 23012*/ 23013ol.interaction.GeolocationDraw.prototype.reset = function() { 23014 this.sketch_[1].setGeometry(); 23015 this.path_ = []; 23016 this.lastPosition_ = false; 23017}; 23018/** Start tracking = setActive(true) 23019 */ 23020ol.interaction.GeolocationDraw.prototype.start = function() { 23021 this.setActive(true); 23022}; 23023/** Stop tracking = setActive(false) 23024 */ 23025ol.interaction.GeolocationDraw.prototype.stop = function() { 23026 this.setActive(false); 23027}; 23028/** Pause drawing 23029 * @param {boolean} b 23030 */ 23031ol.interaction.GeolocationDraw.prototype.pause = function(b) { 23032 this.pause_ = (b!==false); 23033}; 23034/** Is paused 23035 * @return {boolean} b 23036 */ 23037ol.interaction.GeolocationDraw.prototype.isPaused = function() { 23038 return this.pause_; 23039}; 23040/** Enable following the track on the map 23041* @param {boolean|auto|position|visible} follow, 23042* false: don't follow, 23043* true: follow (position+zoom), 23044* 'position': follow only position, 23045* 'auto': start following until user move the map, 23046* 'visible': center when position gets out of the visible extent 23047*/ 23048ol.interaction.GeolocationDraw.prototype.setFollowTrack = function(follow) { 23049 this.set('followTrack', follow); 23050 var map = this.getMap(); 23051 // Center if wanted 23052 if (this.getActive() && map) { 23053 var zoom; 23054 if (follow !== 'position') { 23055 if (this.get('minZoom')) { 23056 zoom = Math.max(this.get('minZoom'), map.getView().getZoom()); 23057 } else { 23058 zoom = this.get('zoom'); 23059 } 23060 } 23061 if (follow !== false && !this.lastPosition_) { 23062 var pos = this.path_[this.path_.length-1]; 23063 if (pos) { 23064 map.getView().animate({ 23065 center: pos, 23066 zoom: zoom 23067 }); 23068 } 23069 } else if (follow==='auto' && this.lastPosition_) { 23070 map.getView().animate({ 23071 center: this.lastPosition_, 23072 zoom: zoom 23073 }); 23074 } 23075 } 23076 this.lastPosition_ = false; 23077 this.dispatchEvent({ type:'follow', following: follow!==false }); 23078}; 23079/** Add a new point to the current path 23080 * @private 23081 */ 23082ol.interaction.GeolocationDraw.prototype.draw_ = function(simulate, coord, accuracy) { 23083 var map = this.getMap(); 23084 if (!map) return; 23085 var accu, pos, p, loc, heading; 23086 // Simulation mode 23087 if (this._track) { 23088 if (simulate!==true) return; 23089 pos = coord; 23090 accu = accuracy || 10; 23091 if (this.path_ && this.path_.length) { 23092 var pt = this.path_[this.path_.length-1]; 23093 heading = Math.atan2(coord[0]-pt[0],coord[1]-pt[1]) 23094 } 23095 var circle = new ol.geom.Circle(pos, map.getView().getResolution()*accu); 23096 p = ol.geom.Polygon.fromCircle(circle); 23097 } else { 23098 // Current location 23099 loc = this.geolocation; 23100 accu = loc.getAccuracy(); 23101 pos = this.getPosition(loc); 23102 p = loc.getAccuracyGeometry(); 23103 heading = loc.getHeading(); 23104 } 23105 // Center on point 23106 // console.log(this.get('followTrack')) 23107 switch (this.get('followTrack')) { 23108 // Follow center + zoom 23109 case true: { 23110 // modify zoom 23111 if (this.get('followTrack') == true) { 23112 if (this.get('minZoom')) { 23113 if (this.get('minZoom') > map.getView().getZoom()) { 23114 map.getView().setZoom(this.get('minZoom')); 23115 } 23116 } else { 23117 map.getView().setZoom( this.get('zoom') || 16 ); 23118 } 23119 if (!ol.extent.containsExtent(map.getView().calculateExtent(map.getSize()), p.getExtent())) { 23120 map.getView().fit(p.getExtent()); 23121 } 23122 } 23123 map.getView().setCenter( pos ); 23124 break; 23125 } 23126 // Follow position 23127 case 'position': { 23128 // modify center 23129 map.getView().setCenter( pos ); 23130 break; 23131 } 23132 // Keep on following 23133 case 'auto': { 23134 if (this.lastPosition_) { 23135 var center = map.getView().getCenter(); 23136 // console.log(center,this.lastPosition_) 23137 if (center[0]!=this.lastPosition_[0] || center[1]!=this.lastPosition_[1]) { 23138 //this.dispatchEvent({ type:'follow', following: false }); 23139 this.setFollowTrack (false); 23140 } else { 23141 map.getView().setCenter( pos ); 23142 this.lastPosition_ = pos; 23143 } 23144 } else { 23145 map.getView().setCenter( pos ); 23146 if (this.get('minZoom')) { 23147 if (this.get('minZoom') > map.getView().getZoom()) { 23148 map.getView().setZoom(this.get('minZoom')); 23149 } 23150 } else if (this.get('zoom')) { 23151 map.getView().setZoom( this.get('zoom')); 23152 } 23153 this.lastPosition_ = pos; 23154 } 23155 break; 23156 } 23157 // Force to stay on the map 23158 case 'visible': { 23159 if (!ol.extent.containsCoordinate(map.getView().calculateExtent(map.getSize()), pos)) { 23160 map.getView().setCenter (pos); 23161 } 23162 break; 23163 } 23164 // Don't follow 23165 default: break; 23166 } 23167 // Draw occuracy 23168 var f = this.sketch_[0]; 23169 f.setGeometry(p); 23170 if (accu < this.get("minAccuracy")/2) f.setStyle(this.locStyle.ok); 23171 else if (accu < this.get("minAccuracy")) f.setStyle(this.locStyle.warn); 23172 else f.setStyle(this.locStyle.error); 23173 var geo; 23174 if (this.pause_) { 23175 this.lastPosition_ = pos; 23176 } 23177 if (!this.pause_ && (!loc || this.condition_.call(this, loc))) { 23178 f = this.sketch_[1]; 23179 this.path_.push(pos); 23180 switch (this.get("type")) { 23181 case "Point": 23182 this.path_ = [pos]; 23183 f.setGeometry(new ol.geom.Point(pos, 'XYZM')); 23184 var attr = this.get('attributes'); 23185 if (attr.heading) f.set("heading",loc.getHeading()); 23186 if (attr.accuracy) f.set("accuracy",loc.getAccuracy()); 23187 if (attr.altitudeAccuracy) f.set("altitudeAccuracy", loc.getAltitudeAccuracy()); 23188 if (attr.speed) f.set("speed",loc.getSpeed()); 23189 break; 23190 case "LineString": 23191 if (this.path_.length>1) { 23192 geo = new ol.geom.LineString(this.path_, 'XYZM'); 23193 if (this.get("tolerance")) geo = this.simplify3D (geo, this.get("tolerance")); 23194 f.setGeometry(geo); 23195 } else { 23196 f.setGeometry(); 23197 } 23198 break; 23199 case "Polygon": 23200 if (this.path_.length>2) { 23201 geo = new ol.geom.Polygon([this.path_], 'XYZM'); 23202 if (this.get("tolerance")) geo = this.simplify3D (geo, this.get("tolerance")); 23203 f.setGeometry(geo); 23204 } 23205 else f.setGeometry(); 23206 break; 23207 } 23208 this.dispatchEvent({ type:'drawing', feature: this.sketch_[1], geolocation: loc }); 23209 } 23210 this.sketch_[2].setGeometry(new ol.geom.Point(pos)); 23211 this.sketch_[2].set("heading", heading); 23212 // Drawing 23213 this.dispatchEvent({ type:'tracking', feature: this.sketch_[1], geolocation: loc }); 23214}; 23215/** Get a position according to the geolocation 23216 * @param {Geolocation} loc 23217 * @returns {Array<any>} an array of measure X,Y,Z,T 23218 * @api 23219 */ 23220ol.interaction.GeolocationDraw.prototype.getPosition = function (loc) { 23221 var pos = loc.getPosition(); 23222 pos.push (Math.round((loc.getAltitude()||0)*100)/100); 23223 pos.push (Math.round((new Date()).getTime()/1000)); 23224 return pos; 23225} 23226 23227/** Interaction hover do to something when hovering a feature 23228 * @constructor 23229 * @extends {ol.interaction.Interaction} 23230 * @fires hover, enter, leave 23231 * @param {olx.interaction.HoverOptions} 23232 * @param { string | undefined } options.cursor css cursor propertie or a function that gets a feature, default: none 23233 * @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 23234 * @param {function | undefined} options.layerFilter filter a function with one argument, the layer to test. Return true to test the layer 23235 * @param {Array<ol.layer> | undefined} options.layers a set of layers to test 23236 * @param {number | undefined} options.hitTolerance Hit-detection tolerance in pixels. 23237 * @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. 23238 */ 23239ol.interaction.Hover = function(options) { 23240 if (!options) options={}; 23241 var self = this; 23242 var dragging = false;
23243 ol.interaction.Interaction.call(this, { 23244 handleEvent: function(e) { 23245 if (!self.getActive()) return true; 23246 switch(e.type) { 23247 case 'pointerdrag': { 23248 dragging = true; 23249 break; 23250 } 23251 case 'pointerup': { 23252 dragging = false; 23253 break; 23254 } 23255 case 'pointermove': { 23256 if (!dragging) { 23257 self.handleMove_(e); 23258 } 23259 break; 23260 } 23261 } 23262 if (options.handleEvent) return options.handleEvent(e); 23263 return true; 23264 } 23265 }); 23266 this.setLayerFilter (options.layerFilter); 23267 if (options.layers && options.layers.length) { 23268 this.setLayerFilter(function(l) { 23269 return (options.layers.indexOf(l) >= 0); 23270 }) 23271 } 23272 this.setFeatureFilter (options.featureFilter); 23273 this.set('hitTolerance', options.hitTolerance) 23274 this.setCursor (options.cursor); 23275}; 23276ol.ext.inherits(ol.interaction.Hover, ol.interaction.Interaction); 23277/** 23278 * Remove the interaction from its current map, if any, and attach it to a new 23279 * map, if any. Pass `null` to just remove the interaction from the current map. 23280 * @param {ol.Map} map Map. 23281 * @api stable 23282 */ 23283ol.interaction.Hover.prototype.setMap = function(map) { 23284 if (this.previousCursor_!==undefined && this.getMap()) { 23285 this.getMap().getTargetElement().style.cursor = this.previousCursor_; 23286 this.previousCursor_ = undefined; 23287 } 23288 ol.interaction.Interaction.prototype.setMap.call (this, map); 23289}; 23290/** Activate / deactivate interaction 23291 * @param {boolean} b 23292 */ 23293ol.interaction.Hover.prototype.setActive = function(b) { 23294 ol.interaction.Interaction.prototype.setActive.call (this, b); 23295 if (this.cursor_ && this.getMap() && this.getMap().getTargetElement()) { 23296 var style = this.getMap().getTargetElement().style; 23297 if (this.previousCursor_ !== undefined) { 23298 style.cursor = this.previousCursor_; 23299 this.previousCursor_ = undefined; 23300 } 23301 } 23302}; 23303/** 23304 * Set cursor on hover 23305 * @param { string } cursor css cursor propertie or a function that gets a feature, default: none 23306 * @api stable 23307 */ 23308ol.interaction.Hover.prototype.setCursor = function(cursor) { 23309 if (!cursor && this.previousCursor_!==undefined && this.getMap()) { 23310 this.getMap().getTargetElement().style.cursor = this.previousCursor_; 23311 this.previousCursor_ = undefined; 23312 } 23313 this.cursor_ = cursor; 23314}; 23315/** Feature filter to get only one feature 23316* @param {function} filter a function with two arguments, the feature and the layer of the feature. Return true to select the feature 23317*/ 23318ol.interaction.Hover.prototype.setFeatureFilter = function(filter) { 23319 if (typeof (filter) == 'function') this.featureFilter_ = filter; 23320 else this.featureFilter_ = function(){ return true; }; 23321}; 23322/** Feature filter to get only one feature 23323* @param {function} filter a function with one argument, the layer to test. Return true to test the layer 23324*/ 23325ol.interaction.Hover.prototype.setLayerFilter = function(filter) { 23326 if (typeof (filter) == 'function') this.layerFilter_ = filter; 23327 else this.layerFilter_ = function(){ return true; }; 23328}; 23329/** Get features whenmove 23330* @param {ol.event} e "move" event 23331*/ 23332ol.interaction.Hover.prototype.handleMove_ = function(e) { 23333 var map = this.getMap(); 23334 if (map) { 23335 //var b = map.hasFeatureAtPixel(e.pixel); 23336 var feature, layer; 23337 var self = this; 23338 var b = map.forEachFeatureAtPixel( 23339 e.pixel, 23340 function(f, l) { 23341 if (self.featureFilter_.call(null,f,l)) { 23342 feature = f; 23343 layer = l; 23344 return true; 23345 } else { 23346 feature = layer = null; 23347 return false; 23348 } 23349 },{ 23350 hitTolerance: this.get('hitTolerance'), 23351 layerFilter: self.layerFilter_ 23352 } 23353 ); 23354 if (b) this.dispatchEvent({ 23355 type: 'hover', 23356 feature: feature, 23357 layer: layer, 23358 coordinate: e.coordinate, 23359 pixel: e.pixel, 23360 map: e.map, 23361 originalEvent: e.originalEvent, 23362 dragging: e.dragging 23363 }); 23364 if (this.feature_===feature && this.layer_===layer){ 23365 /* ok */ 23366 } else { 23367 this.feature_ = feature; 23368 this.layer_ = layer; 23369 if (feature) { 23370 this.dispatchEvent({ 23371 type: 'enter', 23372 feature: feature, 23373 layer: layer, 23374 coordinate: e.coordinate, 23375 pixel: e.pixel, 23376 map: e.map, 23377 originalEvent: e.originalEvent, 23378 dragging: e.dragging 23379 }); 23380 } else { 23381 this.dispatchEvent({ 23382 type: 'leave', 23383 coordinate: e.coordinate, 23384 pixel: e.pixel, 23385 map: e.map, 23386 originalEvent: e.originalEvent, 23387 dragging: e.dragging 23388 }); 23389 } 23390 } 23391 if (this.cursor_) { 23392 var style = map.getTargetElement().style; 23393 if (b) { 23394 if (style.cursor != this.cursor_) { 23395 this.previousCursor_ = style.cursor; 23396 style.cursor = this.cursor_; 23397 } 23398 } else if (this.previousCursor_ !== undefined) { 23399 style.cursor = this.previousCursor_; 23400 this.previousCursor_ = undefined; 23401 } 23402 } 23403 } 23404}; 23405 23406/* Copyright (c) 2016 Jean-Marc VIGLINO, 23407 released under the CeCILL-B license (French BSD license)
23408 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 23409*/ 23410/** Interaction to handle longtouch events 23411 * @constructor 23412 * @extends {ol.interaction.Interaction} 23413 * @param {olx.interaction.LongTouchOptions} 23414 * @param {function | undefined} options.handleLongTouchEvent Function handling 'longtouch' events, it will receive a mapBrowserEvent. Or listen to the map 'longtouch' event. 23415 * @param {integer | undefined} [options.pixelTolerance=0] pixel tolerance before drag, default 0 23416 * @param {integer | undefined} [options.delay=1000] The delay for a long touch in ms, default is 1000 23417 */ 23418ol.interaction.LongTouch = function(options) { 23419 if (!options) options = {}; 23420 this.delay_ = options.delay || 1000; 23421 var ltouch = options.handleLongTouchEvent || function(){}; 23422 var _timeout = null; 23423 var position, event; 23424 var tol = options.pixelTolerance || 0; 23425 ol.interaction.Interaction.call(this, { 23426 handleEvent: function(e) { 23427 if (this.getActive()) { 23428 switch (e.type) { 23429 case 'pointerdown': { 23430 if (_timeout) clearTimeout(_timeout); 23431 position = e.pixel; 23432 event = { 23433 type: 'longtouch', 23434 originalEvent: e.originalEvent, 23435 frameState: e.frameState, 23436 pixel: e.pixel, 23437 coordinate: e.coordinate, 23438 map: this.getMap() 23439 } 23440 _timeout = setTimeout (function() { 23441 ltouch(event); 23442 event.map.dispatchEvent(event); 23443 }, this.delay_); 23444 break; 23445 } 23446 case 'pointerdrag': { 23447 // Check if dragging over tolerance 23448 if (_timeout && (Math.abs(e.pixel[0] - position[0]) > tol || Math.abs(e.pixel[1] - position[1]) > tol)) { 23449 clearTimeout(_timeout); 23450 _timeout = null; 23451 } 23452 break; 23453 } 23454 case 'pointerup': { 23455 if (_timeout) { 23456 clearTimeout(_timeout); 23457 _timeout = null; 23458 } 23459 break; 23460 } 23461 default: break; 23462 } 23463 } else { 23464 if (_timeout) { 23465 clearTimeout(_timeout); 23466 _timeout = null; 23467 } 23468 } 23469 return true; 23470 } 23471 }); 23472}; 23473ol.ext.inherits(ol.interaction.LongTouch, ol.interaction.Interaction); 23474 23475// Use ol.getUid for Openlayers < v6 23476/** Extent the ol/interaction/Modify with a getModifyFeatures 23477 * Get the features modified by the interaction 23478 * @return {Array<ol.Feature>} the modified features 23479 * @deprecated 23480 */ 23481ol.interaction.Modify.prototype.getModifiedFeatures = function() { 23482 var featuresById = {}; 23483 this.dragSegments_.forEach( function(s) { 23484 var feature = s[0].feature; 23485 // Openlayers > v.6 23486 if (window.ol && window.ol.util) featuresById[ol.util.getUid(feature)] = feature; 23487 // old version of Openlayers (< v.6) or ol all versions 23488 else featuresById[ol.getUid(feature)] = feature; 23489 }); 23490 var features = []; 23491 for (var i in featuresById) features.push(featuresById[i]); 23492 return features; 23493}; 23494 23495/* Copyright (c) 2016 Jean-Marc VIGLINO, 23496 released under the CeCILL-B license (French BSD license) 23497 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 23498*/ 23499/** Interaction for modifying feature geometries. Similar to the core ol/interaction/Modify. 23500 * The interaction is more suitable to use to handle feature modification: only features concerned 23501 * by the modification are passed to the events (instead of all feature with ol/interaction/Modify) 23502 * - the modifystart event is fired before the feature is modified (no points still inserted) 23503 * - the modifyend event is fired after the modification 23504 * - it fires a modifying event 23505 * @constructor 23506 * @extends {ol.interaction.Pointer} 23507 * @fires modifystart 23508 * @fires modifying 23509 * @fires modifyend 23510 * @fires select 23511 * @param {*} options 23512 * @param {ol.source.Vector} options.source a source to modify (configured with useSpatialIndex set to true) 23513 * @param {ol.source.Vector|Array<ol.source.Vector>} options.sources a list of source to modify (configured with useSpatialIndex set to true) 23514 * @param {ol.Collection.<ol.Feature>} options.features collection of feature to modify 23515 * @param {integer} options.pixelTolerance Pixel tolerance for considering the pointer close enough to a segment or vertex for editing. Default is 10. 23516 * @param {function|undefined} options.filter a filter that takes a feature and return true if it can be modified, default always true. 23517 * @param {ol.style.Style | Array<ol.style.Style> | undefined} options.style Style for the sketch features. 23518 * @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. 23519 * @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.altKeyOnl
23519y results in a vertex deletion. 23520 * @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 23521 * @param {boolean} options.wrapX Wrap the world horizontally on the sketch overlay, default false 23522 */ 23523ol.interaction.ModifyFeature = function(options){ 23524 if (!options) options = {}; 23525 var dragging, modifying; 23526 ol.interaction.Pointer.call(this,{ 23527 /* 23528 handleDownEvent: this.handleDownEvent, 23529 handleDragEvent: this.handleDragEvent, 23530 handleMoveEvent: this.handleMoveEvent, 23531 handleUpEvent: this.handleUpEvent, 23532 */ 23533 handleEvent: function(e) { 23534 switch(e.type) { 23535 case 'pointerdown': { 23536 dragging = this.handleDownEvent(e); 23537 modifying = dragging || this._deleteCondition(e); 23538 return !dragging; 23539 } 23540 case 'pointerup': { 23541 dragging = false; 23542 return this.handleUpEvent(e); 23543 } 23544 case 'pointerdrag': { 23545 if (dragging) return this.handleDragEvent(e); 23546 else return true; 23547 } 23548 case 'pointermove': { 23549 if (!dragging) return this.handleMoveEvent(e); 23550 else return true; 23551 } 23552 case 'singleclick': 23553 case 'click': { 23554 // Prevent click when modifying 23555 return !modifying; 23556 } 23557 default: return true; 23558 } 23559 } 23560 }); 23561 // Snap distance (in px) 23562 this.snapDistance_ = options.pixelTolerance || 10; 23563 // Split tolerance between the calculated intersection and the geometry 23564 this.tolerance_ = 1e-10; 23565 // Cursor 23566 this.cursor_ = options.cursor; 23567 // List of source to split 23568 this.sources_ = options.sources ? (options.sources instanceof Array) ? options.sources:[options.sources] : []; 23569 if (options.source) this.sources_.push (options.source); 23570 if (options.features) this.sources_.push (new ol.source.Vector({ features: options.features })); 23571 // Get all features candidate 23572 this.filterSplit_ = options.filter || function(){ return true; }; 23573 this._condition = options.condition || ol.events.condition.primaryAction; 23574 this._deleteCondition = options.deleteCondition || ol.events.condition.altKeyOnl
23574y; 23575 this._insertVertexCondition = options.insertVertexCondition || ol.events.condition.always; 23576 // Default style 23577 var sketchStyle = function() { 23578 return [ new ol.style.Style({ 23579 image: new ol.style.Circle({ 23580 radius: 6, 23581 fill: new ol.style.Fill({ color: [0, 153, 255, 1] }), 23582 stroke: new ol.style.Stroke({ color: '#FFF', width: 1.25 }) 23583 }) 23584 }) 23585 ]; 23586 } 23587 // Custom style 23588 if (options.style) { 23589 if (typeof(options.style) === 'function') { 23590 sketchStyle = options.style 23591 } else { 23592 sketchStyle = function() { return options.style; } 23593 } 23594 } 23595 // Create a new overlay for the sketch 23596 this.overlayLayer_ = new ol.layer.Vector({ 23597 source: new ol.source.Vector({ 23598 useSpatialIndex: false 23599 }), 23600 name:'Modify overlay', 23601 displayInLayerSwitcher: false, 23602 style: sketchStyle, 23603 wrapX: options.wrapX 23604 }); 23605}; 23606ol.ext.inherits(ol.interaction.ModifyFeature, ol.interaction.Pointer); 23607/** 23608 * Remove the interaction from its current map, if any, and attach it to a new 23609 * map, if any. Pass `null` to just remove the interaction from the current map. 23610 * @param {ol.Map} map Map. 23611 * @api stable 23612 */ 23613ol.interaction.ModifyFeature.prototype.setMap = function(map) { 23614 if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_); 23615 ol.interaction.Interaction.prototype.setMap.call (this, map); 23616 this.overlayLayer_.setMap(map); 23617}; 23618/** 23619 * Activate or deactivate the interaction + remove the sketch. 23620 * @param {boolean} active. 23621 * @api stable 23622 */ 23623ol.interaction.ModifyFeature.prototype.setActive = function(active) { 23624 ol.interaction.Interaction.prototype.setActive.call (this, active); 23625 if (this.overlayLayer_) this.overlayLayer_.getSource().clear(); 23626}; 23627/** Change the filter function 23628 * @param {function|undefined} options.filter a filter that takes a feature and return true if it can be modified, default always true. 23629 */ 23630ol.interaction.ModifyFeature.prototype.setFilter = function(filter) { 23631 if (typeof(filter) === 'function') this.filterSplit_ = filter; 23632 else if (filter === undefined) this.filterSplit_ = function(){ return true; }; 23633}; 23634/** Get closest feature at pixel 23635 * @param {ol.Pixel} 23636 * @return {*} 23637 * @private 23638 */ 23639ol.interaction.ModifyFeature.prototype.getClosestFeature = function(e) { 23640 var f, c, d = this.snapDistance_+1; 23641 for (var i=0; i<this.sources_.length; i++) { 23642 var source = this.sources_[i]; 23643 f = source.getClosestFeatureToCoordinate(e.coordinate); 23644 if (f && this.filterSplit_(f)) { 23645 var ci = f.getGeometry().getClosestPoint(e.coordinate); 23646 var di = ol.coordinate.dist2d(e.coordinate,ci) / e.frameState.viewState.resolution; 23647 if (di < d){ 23648 d = di; 23649 c = ci; 23650 } 23651 break; 23652 } 23653 } 23654 if (d > this.snapDistance_) { 23655 if (this.currentFeature) this.dispatchEvent({ type: 'select', selected: [], deselected: [this.currentFeature] }) 23656 this.currentFeature = null; 23657 return false; 23658 } else { 23659 // Snap to node 23660 var coord = this.getNearestCoord (c, f.getGeometry()); 23661 if (coord) { 23662 coord = coord.coord; 23663 var p = this.getMap().getPixelFromCoordinate(coord); 23664 if (ol.coordinate.dist2d(e.pixel, p) < this.snapDistance_) { 23665 c = coord; 23666 } 23667 // 23668 if (this.currentFeature !== f) this.dispatchEvent({ type: 'select', selected: [f], deselected: [this.currentFeature] }) 23669 this.currentFeature = f; 23670 return { source:source, feature:f, coord: c }; 23671 } 23672 } 23673} 23674/** Get nearest coordinate in a list 23675* @param {ol.coordinate} pt the point to find nearest 23676* @param {ol.geom} coords list of coordinates 23677* @return {*} the nearest point with a coord (projected point), dist (distance to the geom), ring (if Polygon) 23678*/ 23679ol.interaction.ModifyFeature.prototype.getNearestCoord = function(pt, geom) { 23680 var i, l, p, p0, dm; 23681 switch (geom.getType()) { 23682 case 'Point': { 23683 return { coord: geom.getCoordinates(), dist: ol.coordinate.dist2d(geom.getCoordinates(), pt) }; 23684 } 23685 case 'MultiPoint': { 23686 return this.getNearestCoord (pt, new ol.geom.LineString(geom.getCoordinates())); 23687 } 23688 case 'LineString': 23689 case 'LinearRing': { 23690 var d; 23691 dm = Number.MAX_VALUE; 23692 var coords = geom.getCoordinates(); 23693 for (i=0; i < coords.length; i++) { 23694 d = ol.coordinate.dist2d (pt, coords[i]); 23695 if (d < dm) { 23696 dm = d; 23697 p0 = coords[i]; 23698 } 23699 } 23700 return { coord: p0, dist: dm }; 23701 } 23702 case 'MultiLineString': { 23703 var lstring = geom.getLineStrings(); 23704 p0 = false, dm = Number.MAX_VALUE; 23705 for (i=0; l=lstring[i]; i++) { 23706 p = this.getNearestCoord(pt, l); 23707 if (p && p.dist<dm) { 23708 p0 = p; 23709 dm = p.dist; 23710 p0.ring = i; 23711 } 23712 } 23713 return p0; 23714 } 23715 case 'Polygon': { 23716 var lring = geom.getLinearRings(); 23717 p0 = false;
23718 dm = Number.MAX_VALUE; 23719 for (i=0; l=lring[i]; i++) { 23720 p = this.getNearestCoord(pt, l); 23721 if (p && p.dist<dm) { 23722 p0 = p; 23723 dm = p.dist; 23724 p0.ring = i; 23725 } 23726 } 23727 return p0; 23728 } 23729 case 'MultiPolygon': { 23730 var poly = geom.getPolygons(); 23731 p0 = false; 23732 dm = Number.MAX_VALUE; 23733 for (i=0; l=poly[i]; i++) { 23734 p = this.getNearestCoord(pt, l); 23735 if (p && p.dist<dm) { 23736 p0 = p; 23737 dm = p.dist; 23738 p0.poly = i; 23739 } 23740 } 23741 return p0; 23742 } 23743 case 'GeometryCollection': { 23744 var g = geom.getGeometries(); 23745 p0 = false; 23746 dm = Number.MAX_VALUE; 23747 for (i=0; l=g[i]; i++) { 23748 p = this.getNearestCoord(pt, l); 23749 if (p && p.dist<dm) { 23750 p0 = p; 23751 dm = p.dist; 23752 p0.geom = i; 23753 } 23754 } 23755 return p0; 23756 } 23757 default: return false; 23758 } 23759}; 23760/** Get arcs concerned by a modification 23761 * @param {ol.geom} geom the geometry concerned 23762 * @param {ol.coordinate} coord pointed coordinates 23763 */ 23764ol.interaction.ModifyFeature.prototype.getArcs = function(geom, coord) { 23765 var arcs = false; 23766 var coords, i, s, l, g; 23767 switch(geom.getType()) { 23768 case 'Point': { 23769 if (ol.coordinate.equal(coord, geom.getCoordinates())) { 23770 arcs = { 23771 geom: geom, 23772 type: geom.getType(), 23773 coord1: [], 23774 coord2: [], 23775 node: true 23776 } 23777 } 23778 break; 23779 } 23780 case 'MultiPoint': { 23781 coords = geom.getCoordinates(); 23782 for (i=0; i < coords.length; i++) { 23783 if (ol.coordinate.equal(coord, coords[i])) { 23784 arcs = { 23785 geom: geom, 23786 type: geom.getType(), 23787 index: i, 23788 coord1: [], 23789 coord2: [], 23790 node: true 23791 } 23792 break; 23793 } 23794 } 23795 break; 23796 } 23797 case 'LinearRing': 23798 case 'LineString': { 23799 var p = geom.getClosestPoint(coord); 23800 if (ol.coordinate.dist2d(p,coord) < 1.5*this.tolerance_) { 23801 var split; 23802 // Split the line in two 23803 if (geom.getType() === 'LinearRing') { 23804 g = new ol.geom.LineString(geom.getCoordinates()); 23805 split = g.splitAt(coord, this.tolerance_); 23806 } else { 23807 split = geom.splitAt(coord, this.tolerance_); 23808 } 23809 // If more than 2 23810 if (split.length>2) { 23811 coords = split[1].getCoordinates(); 23812 for (i=2; s=split[i]; i++) { 23813 var c = s.getCoordinates(); 23814 c.shift(); 23815 coords = coords.concat(c); 23816 } 23817 split = [ split[0], new ol.geom.LineString(coords) ]; 23818 } 23819 // Split in two 23820 if (split.length === 2) { 23821 var c0 = split[0].getCoordinates(); 23822 var c1 = split[1].getCoordinates(); 23823 var nbpt = c0.length + c1.length -1; 23824 c0.pop(); 23825 c1.shift(); 23826 arcs = { 23827 geom: geom, 23828 type: geom.getType(), 23829 coord1: c0, 23830 coord2: c1, 23831 node: (geom.getCoordinates().length === nbpt), 23832 closed: false 23833 } 23834 } else if (split.length === 1) { 23835 s = split[0].getCoordinates(); 23836 var start = ol.coordinate.equal(s[0], coord); 23837 var end = ol.coordinate.equal(s[s.length-1], coord); 23838 // Move first point 23839 if (start) { 23840 s.shift(); 23841 if (end) s.pop(); 23842 arcs = { 23843 geom: geom, 23844 type: geom.getType(), 23845 coord1: [], 23846 coord2: s, 23847 node: true, 23848 closed: end 23849 } 23850 } else if (end) { 23851 // Move last point 23852 s.pop() 23853 arcs = { 23854 geom: geom, 23855 type: geom.getType(), 23856 coord1: s, 23857 coord2: [], 23858 node: true, 23859 closed: false 23860 } 23861 } 23862 } 23863 } 23864 break; 23865 } 23866 case 'MultiLineString': { 23867 var lstring = geom.getLineStrings(); 23868 for (i=0; l=lstring[i]; i++) { 23869 arcs = this.getArcs(l, coord); 23870 if (arcs) { 23871 arcs.geom = geom; 23872 arcs.type = geom.getType(); 23873 arcs.lstring = i; 23874 break; 23875 } 23876 } 23877 break; 23878 } 23879 case 'Polygon': { 23880 var lring = geom.getLinearRings(); 23881 for (i=0; l=lring[i]; i++) { 23882 arcs = this.getArcs(l, coord); 23883 if (arcs) { 23884 arcs.geom = geom; 23885 arcs.type = geom.getType(); 23886 arcs.index = i; 23887 break; 23888 } 23889 } 23890 break; 23891 } 23892 case 'MultiPolygon': { 23893 var poly = geom.getPolygons(); 23894 for (i=0; l=poly[i]; i++) { 23895 arcs = this.getArcs(l, coord); 23896 if (arcs) { 23897 arcs.geom = geom; 23898 arcs.type = geom.getType(); 23899 arcs.poly = i; 23900 break; 23901 } 23902 } 23903 break; 23904 } 23905 case 'GeometryCollection': { 23906 g = geom.getGeometries(); 23907 for (i=0; l=g[i]; i++) { 23908 arcs = this.getArcs(l, coord); 23909 if (arcs) { 23910 arcs.geom = geom; 23911 arcs.g = i; 23912 arcs.typeg = arcs.type; 23913 arcs.type = geom.getType(); 23914 break; 23915 } 23916 } 23917 break; 23918 } 23919 default: { 23920 console.error('ol/interaction/ModifyFeature '+geom.getType()+' not supported!'); 23921 break; 23922 } 23923 } 23924 return arcs; 23925}; 23926/** 23927 * @param {ol.MapBrowserEvent} evt Map browser event. 23928 * @return {boolean} `true` to start the drag sequence. 23929 */ 23930ol.interaction.ModifyFeature.prototype.handleDownEvent = function(evt) { 23931 if (!this.getActive()) return false;
23932 // Something to move ? 23933 var current = this.getClosestFeature(evt); 23934 if (current && (this._condition(evt) || this._deleteCondition(evt))) { 23935 var features = []; 23936 this.arcs = []; 23937 // Get features concerned 23938 this.sources_.forEach(function(s) { 23939 var extent = ol.extent.buffer (ol.extent.boundingExtent([current.coord]), this.tolerance_); 23940 features = features.concat(features, s.getFeaturesInExtent(extent)); 23941 }.bind(this)); 23942 // Get arcs concerned 23943 this._modifiedFeatures = []; 23944 features.forEach(function(f) { 23945 var a = this.getArcs(f.getGeometry(), current.coord); 23946 if (a) { 23947 if (this._insertVertexCondition(evt) || a.node) { 23948 a.feature = f; 23949 this._modifiedFeatures.push(f); 23950 this.arcs.push(a); 23951 } 23952 } 23953 }.bind(this)); 23954 if (this._modifiedFeatures.length) { 23955 if (this._deleteCondition(evt)) { 23956 return !this._removePoint(current, evt); 23957 } else { 23958 this.dispatchEvent({ 23959 type:'modifystart', 23960 coordinate: current.coord, 23961 originalEvent: evt.originalEvent, 23962 features: this._modifiedFeatures 23963 }); 23964 this.handleDragEvent({ 23965 coordinate: current.coord, 23966 originalEvent: evt.originalEvent 23967 }) 23968 return true; 23969 } 23970 } else { 23971 return true; 23972 } 23973 } else { 23974 return false; 23975 } 23976}; 23977/** Get modified features 23978 * @return {Array<ol.Feature>} list of modified features 23979 */ 23980ol.interaction.ModifyFeature.prototype.getModifiedFeatures = function() { 23981 return this._modifiedFeatures || []; 23982}; 23983/** Removes the vertex currently being pointed. 23984 */ 23985ol.interaction.ModifyFeature.prototype.removePoint = function() { 23986 this._removePoint({},{}); 23987}; 23988/** 23989 * @private 23990 */ 23991ol.interaction.ModifyFeature.prototype._getModification = function(a) { 23992 var coords = a.coord1.concat(a.coord2); 23993 switch (a.type) { 23994 case 'LineString': { 23995 if (a.closed) coords.push(coords[0]); 23996 if (coords.length>1) { 23997 if (a.geom.getCoordinates().length != coords.length) { 23998 a.coords = coords; 23999 return true; 24000 } 24001 } 24002 break; 24003 } 24004 case 'MultiLineString': { 24005 if (a.closed) coords.push(coords[0]); 24006 if (coords.length>1) { 24007 var c = a.geom.getCoordinates(); 24008 if (c[a.lstring].length != coords.length) { 24009 c[a.lstring] = coords; 24010 a.coords = c; 24011 return true; 24012 } 24013 } 24014 break; 24015 } 24016 case 'Polygon': { 24017 if (a.closed) coords.push(coords[0]); 24018 if (coords.length>3) { 24019 c = a.geom.getCoordinates(); 24020 if (c[a.index].length != coords.length) { 24021 c[a.index] = coords; 24022 a.coords = c; 24023 return true; 24024 } 24025 } 24026 break; 24027 } 24028 case 'MultiPolygon': { 24029 if (a.closed) coords.push(coords[0]); 24030 if (coords.length>3) { 24031 c = a.geom.getCoordinates(); 24032 if (c[a.poly][a.index].length != coords.length) { 24033 c[a.poly][a.index] = coords; 24034 a.coords = c; 24035 return true; 24036 } 24037 } 24038 break; 24039 } 24040 case 'GeometryCollection': { 24041 a.type = a.typeg; 24042 var geom = a.geom; 24043 var geoms = geom.getGeometries(); 24044 a.geom = geoms[a.g]; 24045 var found = this._getModification(a); 24046 // Restore current arc 24047 geom.setGeometries(geoms); 24048 a.geom = geom; 24049 a.type = 'GeometryCollection'; 24050 return found; 24051 } 24052 default: { 24053 //console.error('ol/interaction/ModifyFeature '+a.type+' not supported!'); 24054 break; 24055 } 24056 } 24057 return false; 24058}; 24059/** Removes the vertex currently being pointed. 24060 * @private 24061 */ 24062ol.interaction.ModifyFeature.prototype._removePoint = function(current, evt) { 24063 if (!this.arcs) return false; 24064 this.overlayLayer_.getSource().clear(); 24065 var found = false; 24066 // Get all modifications 24067 this.arcs.forEach(function(a) { 24068 found = found || this._getModification(a); 24069 }.bind(this)); 24070 // Almost one point is removed 24071 if (found) { 24072 this.dispatchEvent({ 24073 type:'modifystart', 24074 coordinate: current.coord, 24075 originalEvent: evt.originalEvent, 24076 features: this._modifiedFeatures 24077 }); 24078 this.arcs.forEach(function(a) { 24079 if (a.geom.getType() === 'GeometryCollection') { 24080 if (a.coords) { 24081 var geoms = a.geom.getGeometries(); 24082 geoms[a.g].setCoordinates(a.coords); 24083 a.geom.setGeometries(geoms); 24084 } 24085 } else { 24086 if (a.coords) a.geom.setCoordinates(a.coords); 24087 } 24088 }.bind(this)); 24089 this.dispatchEvent({ 24090 type:'modifyend', 24091 coordinate: current.coord, 24092 originalEvent: evt.originalEvent, 24093 features: this._modifiedFeatures 24094 }); 24095 } 24096 this.arcs = []; 24097 return found; 24098}; 24099/** 24100 * @private 24101 */ 24102ol.interaction.ModifyFeature.prototype.handleUpEvent = function(e) { 24103 if (!this.getActive()) return false;
24104 if (!this.arcs || !this.arcs.length) return true; 24105 this.overlayLayer_.getSource().clear(); 24106 this.dispatchEvent({ 24107 type:'modifyend', 24108 coordinate: e.coordinate, 24109 originalEvent: e.originalEvent, 24110 features: this._modifiedFeatures 24111 }); 24112 this.arcs = []; 24113 return true; 24114}; 24115/** 24116 * @private 24117 */ 24118ol.interaction.ModifyFeature.prototype.setArcCoordinates = function(a, coords) { 24119 var c; 24120 switch (a.type) { 24121 case 'Point': { 24122 a.geom.setCoordinates(coords[0]); 24123 break; 24124 } 24125 case 'MultiPoint': { 24126 c = a.geom.getCoordinates(); 24127 c[a.index] = coords[0]; 24128 a.geom.setCoordinates(c); 24129 break; 24130 } 24131 case 'LineString': { 24132 a.geom.setCoordinates(coords); 24133 break; 24134 } 24135 case 'MultiLineString': { 24136 c = a.geom.getCoordinates(); 24137 c[a.lstring] = coords; 24138 a.geom.setCoordinates(c); 24139 break; 24140 } 24141 case 'Polygon': { 24142 c = a.geom.getCoordinates(); 24143 c[a.index] = coords; 24144 a.geom.setCoordinates(c); 24145 break; 24146 } 24147 case 'MultiPolygon': { 24148 c = a.geom.getCoordinates(); 24149 c[a.poly][a.index] = coords; 24150 a.geom.setCoordinates(c); 24151 break; 24152 } 24153 case 'GeometryCollection': { 24154 a.type = a.typeg; 24155 var geom = a.geom; 24156 var geoms = geom.getGeometries(); 24157 a.geom = geoms[a.g]; 24158 this.setArcCoordinates(a, coords); 24159 geom.setGeometries(geoms); 24160 a.geom = geom; 24161 a.type = 'GeometryCollection'; 24162 break; 24163 } 24164 } 24165}; 24166/** 24167 * @private 24168 */ 24169ol.interaction.ModifyFeature.prototype.handleDragEvent = function(e) { 24170 if (!this.getActive()) return false; 24171 if (!this.arcs) return true; 24172 // Show sketch 24173 this.overlayLayer_.getSource().clear(); 24174 var p = new ol.Feature(new ol.geom.Point(e.coordinate)); 24175 this.overlayLayer_.getSource().addFeature(p); 24176 // Nothing to do 24177 if (!this.arcs.length) return true; 24178 // Move arcs 24179 this.arcs.forEach(function(a) { 24180 var coords = a.coord1.concat([e.coordinate], a.coord2); 24181 if (a.closed) coords.push(e.coordinate); 24182 this.setArcCoordinates(a, coords); 24183 }.bind(this)); 24184 this.dispatchEvent({ 24185 type:'modifying', 24186 coordinate: e.coordinate, 24187 originalEvent: e.originalEvent, 24188 features: this._modifiedFeatures 24189 }); 24190 return true; 24191}; 24192/** 24193 * @param {ol.MapBrowserEvent} evt Event. 24194 * @private 24195 */ 24196ol.interaction.ModifyFeature.prototype.handleMoveEvent = function(e) { 24197 if (!this.getActive()) return false; 24198 this.overlayLayer_.getSource().clear(); 24199 var current = this.getClosestFeature(e); 24200 // Draw sketch 24201 if (current) { 24202 var p = new ol.Feature(new ol.geom.Point(current.coord)); 24203 this.overlayLayer_.getSource().addFeature(p); 24204 } 24205 // Show cursor 24206 var element = e.map.getTargetElement(); 24207 if (this.cursor_) { 24208 if (current) { 24209 if (element.style.cursor != this.cursor_) { 24210 this.previousCursor_ = element.style.cursor; 24211 element.style.cursor = this.cursor_; 24212 } 24213 } else if (this.previousCursor_ !== undefined) { 24214 element.style.cursor = this.previousCursor_; 24215 this.previousCursor_ = undefined; 24216 } 24217 } 24218}; 24219/** Get the current feature to modify 24220 * @return {ol.Feature} 24221 */ 24222ol.interaction.ModifyFeature.prototype.getCurrentFeature = function() { 24223 return this.currentFeature; 24224}; 24225 24226/* Copyright (c) 2016 Jean-Marc VIGLINO, 24227 released under the CeCILL-B license (French BSD license) 24228 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 24229*/ 24230/** Modify interaction with a popup to delet a point on touch device 24231 * @constructor 24232 * @fires showpopup 24233 * @fires hidepopup 24234 * @extends {ol.interaction.Modify} 24235 * @param {olx.interaction.ModifyOptions} options 24236 * @param {String|undefined} options.title title to display, default "remove point" 24237 * @param {String|undefined} options.className CSS class name for the popup 24238 * @param {String|undefined} options.positioning positioning for the popup 24239 * @param {Number|Array<number>|undefined} options.offsetBox offset box for the popup 24240 * @param {Boolean|undefined} options.usePopup use a popup, default true 24241 */ 24242ol.interaction.ModifyTouch = function(options) { 24243 var self = this; 24244 if (!options) options = {}; 24245 this._popup = new ol.Overlay.Popup ({ 24246 popupClass: options.className || 'modifytouch', 24247 positioning: options.positioning || 'bottom-rigth', 24248 offsetBox: options.offsetBox || 10 24249 }); 24250 this._source = options.source; 24251 this._features = options.features; 24252 // popup content 24253 var a = document.createElement('a'); 24254 a.appendChild(document.createTextNode(options.title || "remove point")); 24255 a.onclick = function() { 24256 self.removePoint(); 24257 }; 24258 this.setPopupContent(a); 24259 var pixelTolerance = options.pixelTolerance || 0; 24260 var searchDist = pixelTolerance +5; 24261 // Check if there is a feature to select 24262 options.condition = function(e) { 24263 var features = this.getMap().getFeaturesAtPixel(e.pixel,{ 24264 hitTolerance: searchDist 24265 }); 24266 var p0, p1, found = false;
24267 if (features) { 24268 var search = this._features; 24269 if (!search) { 24270 p0 = [e.pixel[0] - searchDist, e.pixel[1] - searchDist] 24271 p1 = [e.pixel[0] + searchDist, e.pixel[1] + searchDist] 24272 p0 = this.getMap().getCoordinateFromPixel(p0); 24273 p1 = this.getMap().getCoordinateFromPixel(p1); 24274 var ext = ol.extent.boundingExtent([p0,p1]); 24275 search = this._source.getFeaturesInExtent(ext); 24276 } 24277 if (search.getArray) search = search.getArray(); 24278 for (var i=0, f; f=features[i]; i++) { 24279 if (search.indexOf(f) >= 0) break; 24280 } 24281 if (f) { 24282 p0 = e.pixel; 24283 p1 = f.getGeometry().getClosestPoint(e.coordinate); 24284 p1 = this.getMap().getPixelFromCoordinate(p1); 24285 var dx = p0[0] - p1[0]; 24286 var dy = p0[1] - p1[1]; 24287 found = (Math.sqrt(dx*dx+dy*dy) < searchDist); 24288 } 24289 } 24290 // Show popup if any 24291 this.showDeleteBt(found ? { type:'show', feature:f, coordinate: e.coordinate } : { type:'hide' }); 24292 return true; 24293 }; 24294 // Hide popup on insert 24295 options.insertVertexCondition = function() { 24296 this.showDeleteBt({ type:'hide' }); 24297 return true; 24298 } 24299 ol.interaction.Modify.call(this, options); 24300 this.on(['modifystart','modifyend'], function(){ 24301 this.showDeleteBt({ type:'hide', modifying: true }); 24302 }); 24303 // Use a popup ? 24304 this.set('usePopup', options.usePopup !== false); 24305}; 24306ol.ext.inherits(ol.interaction.ModifyTouch, ol.interaction.Modify); 24307/** 24308 * Remove the interaction from its current map, if any, and attach it to a new 24309 * map, if any. Pass `null` to just remove the interaction from the current map. 24310 * @param {ol.Map} map Map. 24311 * @api stable 24312 */ 24313ol.interaction.ModifyTouch.prototype.setMap = function(map) { 24314 if (this.getMap()) { 24315 this.getMap().removeOverlay(this._popup); 24316 } 24317 ol.interaction.Modify.prototype.setMap.call (this, map); 24318 if (this.getMap()) { 24319 this.getMap().addOverlay(this._popup); 24320 } 24321}; 24322/** Activate the interaction and remove popup 24323 * @param {Boolean} b 24324 */ 24325ol.interaction.ModifyTouch.prototype.setActive = function(b) { 24326 ol.interaction.Modify.prototype.setActive.call (this, b); 24327 this.showDeleteBt({ type:'hide' }); 24328}; 24329/** 24330 * Remove the current point 24331 */ 24332ol.interaction.ModifyTouch.prototype.removePoint = function() { 24333 // Prevent touch + click on popup 24334 if (new Date() - this._timeout < 200) return; 24335 // Remove point 24336 ol.interaction.Modify.prototype.removePoint.call (this); 24337 this.showDeleteBt({ type:'hide' }); 24338} 24339/** 24340 * Show the delete button (menu) 24341 * @param {Event} e 24342 * @api stable 24343 */ 24344ol.interaction.ModifyTouch.prototype.showDeleteBt = function(e) { 24345 if (this.get('usePopup') && e.type==='show') { 24346 this._popup.show(e.coordinate, this._menu); 24347 } else { 24348 this._popup.hide(); 24349 } 24350 e.type += 'popup'; 24351 this.dispatchEvent(e); 24352 // Date if popup start a timeout to prevent touch + click on the popup 24353 this._timeout = new Date(); 24354}; 24355/** 24356 * Change the popup content 24357 * @param {DOMElement} html 24358 */ 24359ol.interaction.ModifyTouch.prototype.setPopupContent = function(html) { 24360 this._menu = html; 24361} 24362/** 24363 * Get the popup content 24364 * @return {DOMElement} 24365 */ 24366ol.interaction.ModifyTouch.prototype.getPopupContent = function() { 24367 return this._menu; 24368} 24369 24370/* Copyright (c) 2016 Jean-Marc VIGLINO, 24371 released under the CeCILL-B license (French BSD license) 24372 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 24373*/ 24374/** Offset interaction for offseting feature geometry 24375 * @constructor 24376 * @extends {ol.interaction.Pointer} 24377 * @fires offsetstart 24378 * @fires offsetting 24379 * @fires offsetend 24380 * @param {any} options 24381 * @param {ol.layer.Vector | Array<ol.layer.Vector>} options.layers list of feature to transform 24382 * @param {ol.Collection.<ol.Feature>} options.features collection of feature to transform 24383 * @param {ol.source.Vector | undefined} options.source source to duplicate feature when ctrl key is down 24384 * @param {boolean} options.duplicate force feature to duplicate (source must be set) 24385 * @param {ol.style.Style | Array.<ol.style.Style> | ol.style.StyleFunction | undefined} style style for the sketch 24386 */ 24387ol.interaction.Offset = function(options) { 24388 if (!options) options = {}; 24389 // Extend pointer 24390 ol.interaction.Pointer.call(this, { 24391 handleDownEvent: this.handleDownEvent_, 24392 handleDragEvent: this.handleDragEvent_, 24393 handleMoveEvent: this.handleMoveEvent_, 24394 handleUpEvent: this.handleUpEvent_ 24395 }); 24396 // Collection of feature to transform 24397 this.features_ = options.features; 24398 // List of layers to transform 24399 this.layers_ = options.layers ? (options.layers instanceof Array) ? options.layers:[options.layers] : null; 24400 // duplicate 24401 this.set('duplicate', options.duplicate); 24402 this.source_ = options.source; 24403 // Style 24404 this._style = (typeof (options.style) === 'function') ? options.style : function () { 24405 if (options.style) return options.style; 24406 else return ol.style.Style.defaultStyle(true); 24407 }; 24408 // init 24409 this.previousCursor_ = false;
24410}; 24411ol.ext.inherits(ol.interaction.Offset, ol.interaction.Pointer); 24412/** 24413 * Remove the interaction from its current map, if any, and attach it to a new 24414 * map, if any. Pass `null` to just remove the interaction from the current map. 24415 * @param {ol.Map} map Map. 24416 * @api stable 24417 */ 24418ol.interaction.Offset.prototype.setMap = function(map) { 24419 ol.interaction.Pointer.prototype.setMap.call (this, map); 24420}; 24421/** Get Feature at pixel 24422 * @param {ol.MapBrowserEvent} evt Map browser event. 24423 * @return {any} a feature and the hit point 24424 * @private 24425 */ 24426ol.interaction.Offset.prototype.getFeatureAtPixel_ = function(e) { 24427 var self = this; 24428 return this.getMap().forEachFeatureAtPixel(e.pixel, 24429 function(feature, layer) { 24430 var current; 24431 // feature belong to a layer 24432 if (self.layers_) { 24433 for (var i=0; i<self.layers_.length; i++) { 24434 if (self.layers_[i]===layer) { 24435 current = feature; 24436 break; 24437 } 24438 } 24439 } 24440 // feature in the collection 24441 else if (self.features_) { 24442 self.features_.forEach (function(f) { 24443 if (f===feature) { 24444 current = feature 24445 } 24446 }); 24447 } 24448 // Others 24449 else { 24450 current = feature; 24451 } 24452 // Only poygon or linestring 24453 var typeGeom = current.getGeometry().getType(); 24454 if (current && /Polygon|LineString/.test(typeGeom)) { 24455 if (typeGeom==='Polygon' && current.getGeometry().getCoordinates().length>1) return false; 24456 // test distance 24457 var p = current.getGeometry().getClosestPoint(e.coordinate); 24458 var dx = p[0]-e.coordinate[0]; 24459 var dy = p[1]-e.coordinate[1]; 24460 var d = Math.sqrt(dx*dx+dy*dy) / e.frameState.viewState.resolution; 24461 if (d<5) { 24462 return { 24463 feature: current, 24464 hit: p, 24465 coordinates: current.getGeometry().getCoordinates(), 24466 geom: current.getGeometry().clone(), 24467 geomType: typeGeom 24468 } 24469 } else { 24470 return false; 24471 } 24472 } else { 24473 return false; 24474 } 24475 }, { hitTolerance: 5 }); 24476}; 24477/** 24478 * @param {ol.MapBrowserEvent} e Map browser event. 24479 * @return {boolean} `true` to start the drag sequence. 24480 * @private 24481 */ 24482ol.interaction.Offset.prototype.handleDownEvent_ = function(e) { 24483 this.current_ = this.getFeatureAtPixel_(e); 24484 if (this.current_) { 24485 this.currentStyle_ = this.current_.feature.getStyle(); 24486 if (this.source_ && (this.get('duplicate') || e.originalEvent.ctrlKey)) { 24487 this.current_.feature = this.current_.feature.clone(); 24488 this.current_.feature.setStyle(this._style(this.current_.feature)); 24489 this.source_.addFeature(this.current_.feature); 24490 } else { 24491 // Modify the current feature 24492 this.current_.feature.setStyle(this._style(this.current_.feature)); 24493 this._modifystart = true; 24494 } 24495 this.dispatchEvent({ type:'offsetstart', feature: this.current_.feature, offset: 0 }); 24496 return true; 24497 } else { 24498 return false; 24499 } 24500}; 24501/** 24502 * @param {ol.MapBrowserEvent} e Map browser event. 24503 * @private 24504 */ 24505ol.interaction.Offset.prototype.handleDragEvent_ = function(e) { 24506 if (this._modifystart) { 24507 this.dispatchEvent({ type:'modifystart', features: [ this.current_.feature ] }); 24508 this._modifystart = false;
24509 } 24510 var p = this.current_.geom.getClosestPoint(e.coordinate); 24511 var d = ol.coordinate.dist2d(p, e.coordinate); 24512 var seg, v1, v2, offset; 24513 switch (this.current_.geomType) { 24514 case 'Polygon': { 24515 seg = ol.coordinate.findSegment(p, this.current_.coordinates[0]).segment; 24516 if (seg) { 24517 v1 = [ seg[1][0]-seg[0][0], seg[1][1]-seg[0][1] ]; 24518 v2 = [ e.coordinate[0]-p[0], e.coordinate[1]-p[1] ]; 24519 if (v1[0]*v2[1] - v1[1]*v2[0] > 0) { 24520 d = -d; 24521 } 24522 offset = []; 24523 for (var i=0; i<this.current_.coordinates.length; i++) { 24524 offset.push( ol.coordinate.offsetCoords(this.current_.coordinates[i], i==0 ? d : -d) ); 24525 } 24526 this.current_.feature.setGeometry(new ol.geom.Polygon(offset)); 24527 } 24528 break; 24529 } 24530 case 'LineString': { 24531 seg = ol.coordinate.findSegment(p, this.current_.coordinates).segment; 24532 if (seg) { 24533 v1 = [ seg[1][0]-seg[0][0], seg[1][1]-seg[0][1] ]; 24534 v2 = [ e.coordinate[0]-p[0], e.coordinate[1]-p[1] ]; 24535 if (v1[0]*v2[1] - v1[1]*v2[0] > 0) { 24536 d = -d; 24537 } 24538 offset = ol.coordinate.offsetCoords(this.current_.coordinates, d); 24539 this.current_.feature.setGeometry(new ol.geom.LineString(offset)); 24540 } 24541 break; 24542 } 24543 default: { 24544 break; 24545 } 24546 } 24547 this.dispatchEvent({ type:'offsetting', feature: this.current_.feature, offset: d, segment: [p, e.coordinate], coordinate: e.coordinate }); 24548}; 24549/** 24550 * @param {ol.MapBrowserEvent} e Map browser event. 24551 * @private 24552 */ 24553ol.interaction.Offset.prototype.handleUpEvent_ = function(e) { 24554 if (!this._modifystart) { 24555 this.dispatchEvent({ type:'offsetend', feature: this.current_.feature, coordinate: e.coordinate }); 24556 } 24557 this.current_.feature.setStyle(this.currentStyle_); 24558 this.current_ = false; 24559}; 24560/** 24561 * @param {ol.MapBrowserEvent} e Event. 24562 * @private 24563 */ 24564ol.interaction.Offset.prototype.handleMoveEvent_ = function(e) { 24565 var f = this.getFeatureAtPixel_(e); 24566 if (f) { 24567 if (this.previousCursor_ === false) { 24568 this.previousCursor_ = e.map.getTargetElement().style.cursor; 24569 } 24570 e.map.getTargetElement().style.cursor = 'pointer'; 24571 } else { 24572 e.map.getTargetElement().style.cursor = this.previousCursor_; 24573 this.previousCursor_ = false; 24574 } 24575}; 24576 24577/* 24578 Water ripple effect. 24579 Original code (Java) by Neil Wallis 24580 @link http://www.neilwallis.com/java/water.html 24581 Original code (JS) by Sergey Chikuyonok ([email protected]) 24582 @link http://chikuyonok.ru 24583 @link http://media.chikuyonok.ru/ripple/ 24584 Copyright (c) 2015 Jean-Marc VIGLINO, 24585 released under the CeCILL-B license (French BSD license) 24586 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 24587 @link https://github.com/Viglino 24588*/ 24589/** 24590 * @constructor 24591 * @extends {ol.interaction.Pointer} 24592 * @param {*} options 24593 * @param {ol/layer/Layer} options.layer layer to animate 24594 * @param {number} options.radius raindrop radius 24595 * @param {number} options.interval raindrop interval (in ms), default 1000 24596 */ 24597ol.interaction.Ripple = function(options) { 24598 ol.interaction.Pointer.call(this, { 24599 handleDownEvent: this.rainDrop, 24600 handleMoveEvent: this.rainDrop 24601 }); 24602 // Default options 24603 options = options||{}; 24604 this.riprad = options.radius || 3; 24605 this.ripplemap = []; 24606 this.last_map = []; 24607 // Generate random ripples 24608 this.rains (this.interval); 24609 options.layer.on(['postcompose', 'postrender'], this.postcompose_.bind(this)); 24610}; 24611ol.ext.inherits(ol.interaction.Ripple, ol.interaction.Pointer); 24612/** Generate random rain drop 24613* @param {integer} interval 24614*/ 24615ol.interaction.Ripple.prototype.rains = function(interval) { 24616 if (this.onrain) clearTimeout (this.onrain); 24617 var self = this;
24618 var vdelay = (typeof(interval)=="number" ? interval : 1000)/2; 24619 var delay = 3*vdelay/2; 24620 var rnd = Math.random; 24621 function rain() { 24622 if (self.width) self.rainDrop([rnd() * self.width, rnd() * self.height]); 24623 self.onrain = setTimeout (rain, rnd()*vdelay + delay); 24624 } 24625 // Start raining 24626 if (delay) rain(); 24627} 24628/** Disturb water at specified point 24629* @param {ol.Pixel|ol.MapBrowserEvent} 24630*/ 24631ol.interaction.Ripple.prototype.rainDrop = function(e) { 24632 if (!this.width) return; 24633 var dx,dy; 24634 if (e.pixel) { 24635 dx = e.pixel[0]*this.ratio; 24636 dy = e.pixel[1]*this.ratio; 24637 } else { 24638 dx = e[0]*this.ratio; 24639 dy = e[1]*this.ratio; 24640 } 24641 dx <<= 0; 24642 dy <<= 0; 24643 for (var j = dy - this.riprad*this.ratio; j < dy + this.riprad*this.ratio; j++) { 24644 for (var k = dx - this.riprad*this.ratio; k < dx + this.riprad*this.ratio; k++) { 24645 this.ripplemap[this.oldind + (j * this.width) + k] += 128; 24646 } 24647 } 24648} 24649/** Postcompose function 24650*/ 24651ol.interaction.Ripple.prototype.postcompose_ = function(e) { 24652 var ctx = e.context; 24653 var canvas = ctx.canvas; 24654 // Initialize when canvas is ready / modified 24655 if (this.width != canvas.width || this.height != canvas.height) { 24656 this.width = canvas.width; 24657 this.height = canvas.height; 24658 this.ratio = e.frameState.pixelRatio; 24659 this.half_width = this.width >> 1; 24660 this.half_height = this.height >> 1; 24661 this.size = this.width * (this.height + 2) * 2; 24662 this.oldind = this.width; 24663 this.newind = this.width * (this.height + 3); 24664 for (var i = 0; i < this.size; i++) { 24665 this.last_map[i] = this.ripplemap[i] = 0; 24666 } 24667 } 24668 this.texture = ctx.getImageData(0, 0, this.width, this.height); 24669 this.ripple = ctx.getImageData(0, 0, this.width, this.height); 24670 // Run animation 24671 var a, b, data, cur_pixel, new_pixel; 24672 var t = this.oldind; this.oldind = this.newind; this.newind = t; 24673 i = 0; 24674 var _rd = this.ripple.data, 24675 _td = this.texture.data; 24676 for (var y = 0; y < this.height; y++) { 24677 for (var x = 0; x < this.width; x++) { 24678 var _newind = this.newind + i, 24679 _mapind = this.oldind + i; 24680 data = ( 24681 this.ripplemap[_mapind - this.width] + 24682 this.ripplemap[_mapind + this.width] + 24683 this.ripplemap[_mapind - 1] + 24684 this.ripplemap[_mapind + 1]) >> 1; 24685 data -= this.ripplemap[_newind]; 24686 data -= data >> 5; 24687 this.ripplemap[_newind] = data; 24688 //where data=0 then still, where data>0 then wave 24689 data = 1024 - data; 24690 if (this.last_map[i] != data) { 24691 this.last_map[i] = data; 24692 //offsets 24693 a = (((x - this.half_width) * data / 1024) << 0) + this.half_width; 24694 b = (((y - this.half_height) * data / 1024) << 0) + this.half_height; 24695 //bounds check 24696 if (a >= this.width) a = this.width - 1; 24697 if (a < 0) a = 0; 24698 if (b >= this.height) b = this.height - 1; 24699 if (b < 0) b = 0; 24700 new_pixel = (a + (b * this.width)) * 4; 24701 cur_pixel = i * 4; 24702 /**/ 24703 _rd[cur_pixel] = _td[new_pixel]; 24704 _rd[cur_pixel + 1] = _td[new_pixel + 1]; 24705 _rd[cur_pixel + 2] = _td[new_pixel + 2]; 24706 /*/ 24707 // only in blue pixels 24708 if (_td[new_pixel + 2]>_td[new_pixel + 1] 24709 && _td[new_pixel + 2]>_td[new_pixel]) 24710 { 24711 _rd[cur_pixel] = _td[new_pixel]; 24712 _rd[cur_pixel + 1] = _td[new_pixel + 1]; 24713 _rd[cur_pixel + 2] = _td[new_pixel + 2]; 24714 } 24715 else this.ripplemap[_newind] = 0; 24716 /**/ 24717 } 24718 ++i; 24719 } 24720 } 24721 ctx.putImageData(this.ripple, 0, 0); 24722 // tell OL3 to continue postcompose animation 24723 this.getMap().render(); 24724}; 24725 24726/* 24727 Copyright (c) 2015 Jean-Marc VIGLINO, 24728 released under the CeCILL-B license (http://www.cecill.info/). 24729 ol/interaction/SelectCluster is an interaction for selecting vector features in a cluster. 24730*/ 24731/** 24732 * @classdesc 24733 * Interaction for selecting vector features in a cluster. 24734 * It can be used as an ol.interaction.Select. 24735 * When clicking on a cluster, it springs apart to reveal the features in the cluster. 24736 * Revealed features are selectable and you can pick the one you meant. 24737 * Revealed features are themselves a cluster with an attribute features that contain the original feature. 24738 * 24739 * @constructor 24740 * @extends {ol.interaction.Select} 24741 * @param {olx.interaction.SelectOptions=} options SelectOptions. 24742 * @param {ol.style} options.featureStyle used to style the revealed features as options.style is used by the Select interaction. 24743 * @param {boolean} options.selectCluster false if you don't want to get cluster selected 24744 * @param {Number} options.pointRadius to calculate distance between the features 24745 * @param {bool} options.spiral means you want the feature to be placed on a spiral (or a circle) 24746 * @param {Number} options.circleMaxObjects number of object that can be place on a circle 24747 * @param {Number} options.maxObjects number of object that can be drawn, other are hidden 24748 * @param {bool} options.animate if the cluster will animate when features spread out, default is false 24749 * @param {Number} options.animationDuration animation duration in ms, default is 500ms 24750 * @param {boolean} options.autoClose if selecting a cluster should close previously selected clusters. False to get toggle feature. Default is true 24751 * @fires ol.interaction.SelectEvent 24752 * @api stable 24753 */ 24754ol.interaction.SelectCluster = function(options) { 24755 options = options || {}; 24756 this.pointRadius = options.pointRadius || 12; 24757 this.circleMaxObjects = options.circleMaxObjects || 10; 24758 this.maxObjects = options.maxObjects || 60; 24759 this.spiral = (options.spiral !== false); 24760 this.animate = options.animate; 24761 this.animationDuration = options.animationDuration || 500; 24762 this.selectCluster_ = (options.selectCluster !== false); 24763 this._autoClose = (options.autoClose !== false) 24764 // Create a new overlay layer for
24765 var overlay = this.overlayLayer_ = new ol.layer.Vector({ 24766 source: new ol.source.Vector({ 24767 features: new ol.Collection(), 24768 wrapX: options.wrapX, 24769 useSpatialIndex: true 24770 }), 24771 name:'Cluster overlay', 24772 updateWhileAnimating: true, 24773 updateWhileInteracting: true, 24774 displayInLayerSwitcher: false, 24775 style: options.featureStyle 24776 }); 24777 // Add the overlay to selection 24778 if (options.layers) { 24779 if (typeof(options.layers) == "function") { 24780 var fnLayers = options.layers; 24781 options.layers = function(layer) { 24782 return (layer===overlay || fnLayers(layer)); 24783 }; 24784 } else if (options.layers.push) { 24785 options.layers.push(this.overlayLayer_); 24786 } 24787 } 24788 // Don't select links 24789 if (options.filter) { 24790 var fnFilter = options.filter; 24791 options.filter = function(f,l){ 24792 //if (l===overlay && f.get("selectclusterlink")) return false; 24793 if (!l && f.get("selectclusterlink")) return false; 24794 else return fnFilter(f,l); 24795 }; 24796 } else options.filter = function(f,l) { 24797 //if (l===overlay && f.get("selectclusterlink")) return false; 24798 if (!l && f.get("selectclusterlink")) return false; 24799 else return true; 24800 }; 24801 this.filter_ = options.filter; 24802 if (!this._autoClose && !options.toggleCondition) { 24803 options.toggleCondition = ol.events.condition.singleClick; 24804 } 24805 ol.interaction.Select.call(this, options); 24806 this.on("select", this.selectCluster.bind(this)); 24807}; 24808ol.ext.inherits(ol.interaction.SelectCluster, ol.interaction.Select); 24809/** 24810 * Remove the interaction from its current map, if any, and attach it to a new 24811 * map, if any. Pass `null` to just remove the interaction from the current map. 24812 * @param {ol.Map} map Map. 24813 * @api stable 24814 */ 24815ol.interaction.SelectCluster.prototype.setMap = function(map) { 24816 if (this.getMap()) { 24817 this.getMap().removeLayer(this.overlayLayer_); 24818 } 24819 if (this._listener) ol.Observable.unByKey(this._listener); 24820 this._listener = null; 24821 ol.interaction.Select.prototype.setMap.call (this, map); 24822 this.overlayLayer_.setMap(map); 24823 // map.addLayer(this.overlayLayer_); 24824 if (map && map.getView()) { 24825 this._listener = map.getView().on('change:resolution', this.clear.bind(this)); 24826 } 24827}; 24828/** 24829 * Clear the selection, close the cluster and remove revealed features 24830 * @api stable 24831 */ 24832ol.interaction.SelectCluster.prototype.clear = function() { 24833 this.getFeatures().clear(); 24834 this.overlayLayer_.getSource().clear(); 24835}; 24836/** 24837 * Get the layer for the revealed features 24838 * @api stable 24839 */ 24840ol.interaction.SelectCluster.prototype.getLayer = function() { 24841 return this.overlayLayer_; 24842}; 24843/** 24844 * Select a cluster 24845 * @param {ol.SelectEvent | ol.Feature} a cluster feature ie. a feature with a 'features' attribute. 24846 * @api stable 24847 */ 24848ol.interaction.SelectCluster.prototype.selectCluster = function (e) { 24849 // It's a feature => convert to SelectEvent 24850 if (e instanceof ol.Feature) { 24851 e = { selected: [e] }; 24852 } 24853 // Nothing selected 24854 if (!e.selected.length) { 24855 if (this._autoClose) { 24856 this.clear(); 24857 } else { 24858 var deselectedFeatures = e.deselected; 24859 deselectedFeatures.forEach(deselectedFeature => { 24860 var selectClusterFeatures = deselectedFeature.get('selectcluserfeatures'); 24861 if (selectClusterFeatures) { 24862 selectClusterFeatures.forEach(selectClusterFeature => { 24863 this.overlayLayer_.getSource().removeFeature(selectClusterFeature); 24864 }); 24865 } 24866 }); 24867 } 24868 return; 24869 } 24870 // Get selection 24871 var feature = e.selected[0]; 24872 // It's one of ours 24873 if (feature.get('selectclusterfeature')) return; 24874 // Clic out of the cluster => close it 24875 var source = this.overlayLayer_.getSource(); 24876 if (this._autoClose) { 24877 source.clear(); 24878 } 24879 var cluster = feature.get('features'); 24880 // Not a cluster (or just one feature) 24881 if (!cluster || cluster.length==1) return; 24882 // Remove cluster from selection 24883 if (!this.selectCluster_) this.getFeatures().clear(); 24884 var center = feature.getGeometry().getCoordinates(); 24885 // Pixel size in map unit 24886 var pix = this.getMap().getView().getResolution(); 24887 var r, a, i, max; 24888 var p, cf, lk; 24889 // The features 24890 var features = []; 24891 // Draw on a circle 24892 if (!this.spiral || cluster.length <= this.circleMaxObjects) { 24893 max = Math.min(cluster.length, this.circleMaxObjects); 24894 r = pix * this.pointRadius * (0.5 + max / 4); 24895 for (i=0; i<max; i++) { 24896 a = 2*Math.PI*i/max; 24897 if (max==2 || max == 4) a += Math.PI/4; 24898 p = [ center[0]+r*Math.sin(a), center[1]+r*Math.cos(a) ]; 24899 cf = new ol.Feature({ 'selectclusterfeature':true, 'features':[cluster[
24899i]], geometry: new ol.geom.Point(p) }); 24900 cf.setStyle(cluster[i].getStyle()); 24901 features.push(cf); 24902 lk = new ol.Feature({ 'selectclusterlink':true, geometry: new ol.geom.LineString([center,p]) }); 24903 features.push(lk); 24904 } 24905 } 24906 // Draw on a spiral 24907 else { 24908 // Start angle 24909 a = 0; 24910 var d = 2*this.pointRadius; 24911 max = Math.min (this.maxObjects, cluster.length); 24912 // Feature on a spiral 24913 for (i=0; i<max; i++) { 24914 // New radius => increase d in one turn 24915 r = d/2 + d*a/(2*Math.PI); 24916 // Angle 24917 a = a + (d+0.1)/r; 24918 var dx = pix*r*Math.sin(a) 24919 var dy = pix*r*Math.cos(a) 24920 p = [ center[0]+dx, center[1]+dy ]; 24921 cf = new ol.Feature({ 'selectclusterfeature':true, 'features':[cluster[i]], geometry: new ol.geom.Point(p) }); 24922 cf.setStyle(cluster[i].getStyle()); 24923 features.push(cf); 24924 lk = new ol.Feature({ 'selectclusterlink':true, geometry: new ol.geom.LineString([center,p]) }); 24925 features.push(lk); 24926 } 24927 } 24928 feature.set('selectcluserfeatures', features); 24929 if (this.animate) { 24930 this.animateCluster_(center, features); 24931 } else { 24932 source.addFeatures(features); 24933 } 24934}; 24935/** 24936 * Animate the cluster and spread out the features 24937 * @param {ol.Coordinates} the center of the cluster 24938 */ 24939ol.interaction.SelectCluster.prototype.animateCluster_ = function(center, features) { 24940 // Stop animation (if one is running) 24941 if (this.listenerKey_) { 24942 ol.Observable.unByKey(this.listenerKey_); 24943 } 24944 // Features to animate 24945 // var features = this.overlayLayer_.getSource().getFeatures(); 24946 if (!features.length) return; 24947 var style = this.overlayLayer_.getStyle(); 24948 var stylefn = (typeof(style) == 'function') ? style : style.length ? function(){ return style; } : function(){ return [style]; } ; 24949 var duration = this.animationDuration || 500; 24950 var start = new Date().getTime(); 24951 // Animate function 24952 function animate(event) { 24953 var vectorContext = event.vectorContext || ol.render.getVectorContext(event); 24954 // Retina device 24955 var ratio = event.frameState.pixelRatio; 24956 var res = this.getMap().getView().getResolution(); 24957 var e = ol.easing.easeOut((event.frameState.time - start) / duration); 24958 for (var i=0, feature; feature = features[i]; i++) if (feature.get('features')) { 24959 var pt = feature.getGeometry().getCoordinates(); 24960 pt[0] = center[0] + e * (pt[0]-center[0]); 24961 pt[1] = center[1] + e * (pt[1]-center[1]); 24962 var geo = new ol.geom.Point(pt); 24963 // Image style 24964 var st = stylefn(feature, res); 24965 for (var s=0; s<st.length; s++) { 24966 var sc; 24967 // OL < v4.3 : setImageStyle doesn't check retina 24968 var imgs = ol.Map.prototype.getFeaturesAtPixel ? false : st[s].getImage(); 24969 if (imgs) { 24970 sc = imgs.getScale(); 24971 imgs.setScale(ratio); 24972 } 24973 // OL3 > v3.14 24974 if (vectorContext.setStyle) { 24975 vectorContext.setStyle(st[s]); 24976 vectorContext.drawGeometry(geo); 24977 } 24978 // older version 24979 else { 24980 vectorContext.setImageStyle(imgs); 24981 vectorContext.drawPointGeometry(geo); 24982 } 24983 if (imgs) imgs.setScale(sc); 24984 } 24985 } 24986 // Stop animation and restore cluster visibility 24987 if (e > 1.0) { 24988 ol.Observable.unByKey(this.listenerKey_); 24989 this.overlayLayer_.getSource().addFeatures(features); 24990 this.overlayLayer_.changed(); 24991 return; 24992 } 24993 // tell OL3 to continue postcompose animation 24994 event.frameState.animate = true; 24995 } 24996 // Start a new postcompose animation 24997 this.listenerKey_ = this.overlayLayer_.on(['postcompose','postrender'], animate.bind(this)); 24998 // Start animation with a ghost feature 24999 var feature = new ol.Feature(new ol.geom.Point(this.getMap().getView().getCenter())); 25000 feature.setStyle(new ol.style.Style({ image: new ol.style.Circle({}) })); 25001 this.overlayLayer_.getSource().addFeature(feature); 25002}; 25003/** Helper function to get the extent of a cluster 25004 * @param {ol.feature} feature 25005 * @return {ol.extent|null} the extent or null if extent is empty (no cluster or superimposed points) 25006 */ 25007ol.interaction.SelectCluster.prototype.getClusterExtent = function(feature) { 25008 if (!feature.get('features')) return null; 25009 var extent = ol.extent.createEmpty(); 25010 feature.get('features').forEach(function(f) { 25011 extent = ol.extent.extend(extent, f.getGeometry().getExtent()); 25012 }); 25013 if (extent[0]===extent[2] && extent[1]===extent[3]) return null; 25014 return extent; 25015}; 25016 25017/* Copyright (c) 2016 Jean-Marc VIGLINO, 25018 released under the CeCILL-B license (French BSD license)
25019 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 25020*/ 25021/** Interaction to snap to guidelines 25022 * @constructor 25023 * @extends {ol.interaction.Interaction} 25024 * @param {*} options 25025 * @param {number | undefined} options.pixelTolerance distance (in px) to snap to a guideline, default 10 px 25026 * @param {bool | undefined} options.enableInitialGuides whether to draw initial guidelines based on the maps orientation, default false. 25027 * @param {ol.style.Style | Array<ol.style.Style> | undefined} options.style Style for the sektch features. 25028 * @param {*} options.vectorClass a vector layer class to create the guides with ol6, use ol/layer/VectorImage using ol6 25029 */ 25030ol.interaction.SnapGuides = function(options) { 25031 if (!options) options = {}; 25032 // Intersect 2 guides 25033 function getIntersectionPoint (d1, d2) { 25034 var d1x = d1[1][0] - d1[0][0]; 25035 var d1y = d1[1][1] - d1[0][1]; 25036 var d2x = d2[1][0] - d2[0][0]; 25037 var d2y = d2[1][1] - d2[0][1]; 25038 var det = d1x * d2y - d1y * d2x; 25039 if (det != 0) { 25040 var k = (d1x * d1[0][1] - d1x * d2[0][1] - d1y * d1[0][0] + d1y * d2[0][0]) / det; 25041 return [d2[0][0] + k*d2x, d2[0][1] + k*d2y]; 25042 } 25043 else return false; 25044 } 25045 function dist2D (p1,p2) { 25046 var dx = p1[0]-p2[0]; 25047 var dy = p1[1]-p2[1]; 25048 return Math.sqrt(dx*dx+dy*dy); 25049 } 25050 // Snap distance (in px) 25051 this.snapDistance_ = options.pixelTolerance || 10; 25052 this.enableInitialGuides_ = options.enableInitialGuides || false; 25053 // Default style 25054 var sketchStyle = [ 25055 new ol.style.Style({ 25056 stroke: new ol.style.Stroke({ 25057 color: '#ffcc33', 25058 lineDash: [8,5], 25059 width: 1.25 25060 }) 25061 }) 25062 ]; 25063 // Custom style 25064 if (options.style) sketchStyle = options.style instanceof Array ? options.style : [options.style]; 25065 // Create a new overlay for the sketch 25066 this.overlaySource_ = new ol.source.Vector({ 25067 features: new ol.Collection(), 25068 useSpatialIndex: false 25069 }); 25070 // Use ol/layer/VectorImage to render the snap guides as an image to improve performance on rerenderers 25071 var vectorClass = options.vectorClass || ol.layer.Vector; 25072 this.overlayLayer_ = new vectorClass({ 25073 // render the snap guides as an image to improve performance on rerenderers 25074 renderMode: 'image', 25075 source: this.overlaySource_, 25076 style: function() { 25077 return sketchStyle; 25078 }, 25079 name:'Snap overlay', 25080 displayInLayerSwitcher: false 25081 }); 25082 // Use snap interaction 25083 ol.interaction.Interaction.call(this, { 25084 handleEvent: function(e) { 25085 if (this.getActive()) { 25086 var features = this.overlaySource_.getFeatures(); 25087 var prev = null; 25088 var p = null; 25089 var res = e.frameState.viewState.resolution; 25090 for (var i=0, f; f = features[i]; i++) { 25091 var c = f.getGeometry().getClosestPoint(e.coordinate); 25092 if ( dist2D(c, e.coordinate) / res < this.snapDistance_) { 25093 // Intersection on 2 lines 25094 if (prev) { 25095 var c2 = getIntersectionPoint(prev.getGeometry().getCoordinates(), f.getGeometry().getCoordinates()); 25096 if (c2) { 25097 if (dist2D(c2, e.coordinate) / res < this.snapDistance_) { 25098 p = c2; 25099 } 25100 } 25101 } else { 25102 p = c; 25103 } 25104 prev = f; 25105 } 25106 } 25107 if (p) e.coordinate = p; 25108 } 25109 return true; 25110 } 25111 }); 25112}; 25113ol.ext.inherits(ol.interaction.SnapGuides, ol.interaction.Interaction); 25114/** 25115 * Remove the interaction from its current map, if any, and attach it to a new 25116 * map, if any. Pass `null` to just remove the interaction from the current map. 25117 * @param {ol.Map} map Map. 25118 * @api stable 25119 */ 25120ol.interaction.SnapGuides.prototype.setMap = function(map) { 25121 if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_); 25122 ol.interaction.Interaction.prototype.setMap.call (this, map); 25123 this.overlayLayer_.setMap(map); 25124 if (map) this.projExtent_ = map.getView().getProjection().getExtent(); 25125}; 25126/** Activate or deactivate the interaction. 25127 * @param {boolean} active 25128 */ 25129ol.interaction.SnapGuides.prototype.setActive = function(active) { 25130 this.overlayLayer_.setVisible(active); 25131 ol.interaction.Interaction.prototype.setActive.call (this, active); 25132} 25133/** Clear previous added guidelines 25134 * @param {Array<ol.Feature> | undefined} features a list of feature to remove, default remove all feature 25135 */ 25136ol.interaction.SnapGuides.prototype.clearGuides = function(features) { 25137 if (!features) { 25138 this.overlaySource_.clear(); 25139 } else { 25140 for (var i=0, f; f=features[i]; i++) {
25141 try { 25142 this.overlaySource_.removeFeature(f); 25143 } catch(e) {/* nothing to to */} 25144 } 25145 } 25146}; 25147/** Get guidelines 25148 * @return {ol.Collection} guidelines features 25149 */ 25150ol.interaction.SnapGuides.prototype.getGuides = function() { 25151 return this.overlaySource_.getFeaturesCollection(); 25152} 25153/** Add a new guide to snap to 25154 * @param {Array<ol.coordinate>} v the direction vector 25155 * @return {ol.Feature} feature guide 25156 */ 25157ol.interaction.SnapGuides.prototype.addGuide = function(v, ortho) { 25158 if (v) { 25159 var map = this.getMap(); 25160 // Limit extent 25161 var extent = map.getView().calculateExtent(map.getSize()); 25162 var guideLength = Math.max( 25163 this.projExtent_[2] - this.projExtent_[0], 25164 this.projExtent_[3] - this.projExtent_[1] 25165 ); 25166 extent = ol.extent.buffer(extent, guideLength * 1.5); 25167 //extent = ol.extent.boundingExtent(extent, this.projExtent_); 25168 if (extent[0]<this.projExtent_[0]) extent[0]=this.projExtent_[0]; 25169 if (extent[1]<this.projExtent_[1]) extent[1]=this.projExtent_[1]; 25170 if (extent[2]>this.projExtent_[2]) extent[2]=this.projExtent_[2]; 25171 if (extent[3]>this.projExtent_[3]) extent[3]=this.projExtent_[3]; 25172 var dx = v[0][0] - v[1][0]; 25173 var dy = v[0][1] - v[1][1]; 25174 var d = 1 / Math.sqrt(dx*dx+dy*dy); 25175 var generateLine = function(loopDir) { 25176 var p, g = []; 25177 var loopCond = guideLength*loopDir*2; 25178 for (var i=0; loopDir > 0 ? i < loopCond : i > loopCond; i+=(guideLength * loopDir) / 100) { 25179 if (ortho) p = [ v[0][0] + dy*d*i, v[0][1] - dx*d*i]; 25180 else p = [ v[0][0] + dx*d*i, v[0][1] + dy*d*i]; 25181 if (ol.extent.containsCoordinate(extent, p)) g.push(p); 25182 else break; 25183 } 25184 return new ol.Feature(new ol.geom.LineString([g[0], g[g.length-1]])); 25185 } 25186 var f0 = generateLine(1); 25187 var f1 = generateLine(-1); 25188 this.overlaySource_.addFeature(f0); 25189 this.overlaySource_.addFeature(f1); 25190 return [f0, f1]; 25191 } 25192}; 25193/** Add a new orthogonal guide to snap to 25194 * @param {Array<ol.coordinate>} v the direction vector 25195 * @return {ol.Feature} feature guide 25196 */ 25197ol.interaction.SnapGuides.prototype.addOrthoGuide = function(v) { 25198 return this.addGuide(v, true); 25199}; 25200/** Listen to draw event to add orthogonal guidelines on the first and last point. 25201 * @param {_ol_interaction_Draw_} drawi a draw interaction to listen to 25202 * @api 25203 */ 25204ol.interaction.SnapGuides.prototype.setDrawInteraction = function(drawi) { 25205 var self = this; 25206 // Number of points currently drawing 25207 var nb = 0; 25208 // Current guidelines 25209 var features = []; 25210 function setGuides(e) { 25211 var coord = e.target.getCoordinates(); 25212 var s = 2; 25213 switch (e.target.getType()) { 25214 case 'Point': 25215 return; 25216 case 'Polygon': 25217 coord = coord[0].slice(0, -1); 25218 break; 25219 default: break; 25220 } 25221 var l = coord.length; 25222 if (l === s && self.enableInitialGuides_) { 25223 var x = coord[0][0]; 25224 var y = coord[0][1]; 25225 coord = [[x, y], [x, y - 1]]; 25226 } 25227 if (l != nb && (self.enableInitialGuides_ ? l >= s : l > s)) { 25228 self.clearGuides(features); 25229 // use try catch to remove a bug on freehand draw... 25230 try { 25231 var p1 = coord[l - s], p2 = coord[l - s - 1]; 25232 if (l > s && !(p1[0] === p2[0] && p1[1] === p2[1])) { 25233 features = self.addOrthoGuide([coord[l - s], coord[l - s - 1]]); 25234 } 25235 features = features.concat(self.addGuide([coord[0], coord[1]])); 25236 features = features.concat(self.addOrthoGuide([coord[0], coord[1]])); 25237 nb = l; 25238 } catch (e) { /* ok*/ } 25239 } 25240 } 25241 // New drawing 25242 drawi.on ("drawstart", function(e) { 25243 // When geom is changing add a new orthogonal direction 25244 e.feature.getGeometry().on("change", setGuides); 25245 }); 25246 // end drawing / deactivate => clear directions 25247 drawi.on (["drawend", "change:active"], function(e) { 25248 self.clearGuides(features); 25249 if (e.feature) e.feature.getGeometry().un("change", setGuides); 25250 nb = 0; 25251 features = []; 25252 }); 25253}; 25254/** Listen to modify event to add orthogonal guidelines relative to the currently dragged point 25255 * @param {_ol_interaction_Modify_} modifyi a modify interaction to listen to 25256 * @api 25257 */ 25258ol.interaction.SnapGuides.prototype.setModifyInteraction = function (modifyi) { 25259 function mod(d, n) { 25260 return ((d % n) + n) % n; 25261 } 25262 var self = this; 25263 // Current guidelines 25264 var features = []; 25265 function computeGuides(e) { 25266 var selectedVertex = e.target.vertexFeature_ 25267 if (!selectedVertex) return; 25268 var f = e.target.getModifiedFeatures()[0]; 25269 var geom = f.getGeometry(); 25270 var coord = geom.getCoordinates(); 25271 switch (geom.getType()) { 25272 case 'Point': 25273 return; 25274 case 'Polygon': 25275 coord = coord[0].slice(0, -1); 25276 break; 25277 default: break; 25278 } 25279 var modifyVertex = selectedVertex.getGeometry().getCoordinates(); 25280 var idx = coord.findIndex(function(c) { 25281 return c[0] === modifyVertex[0] && c[1] === modifyVertex[1] 25282 }); 25283 var l = coord.length; 25284 self.clearGuides(features); 25285 features = self.addOrthoGuide([coord[mod(idx - 1, l)], coord[mod(idx - 2, l)]]); 25286 features = features.concat(self.addGuide([coord[mod(idx - 1, l)], coord[mod(idx - 2, l)]])); 25287 features = features.concat(self.addGuide([coord[mod(idx + 1, l)], coord[mod(idx + 2, l)]])); 25288 features = features.concat(self.addOrthoGuide([coord[mod(idx + 1, l)], coord[mod(idx + 2, l)]])); 25289 } 25290 function setGuides(e) { 25291 // This callback is called before ol adds the vertex to the feature, so 25292 // defer a moment for openlayers to add the new vertex 25293 setTimeout(computeGuides, 0, e); 25294 } 25295 function drawEnd() { 25296 self.clearGuides(features); 25297 features = []; 25298 } 25299 // New drawing 25300 modifyi.on("modifystart", setGuides); 25301 // end drawing, clear directions 25302 modifyi.on("modifyend", drawEnd); 25303}; 25304 25305/* Copyright (c) 2016 Jean-Marc VIGLINO, 25306 released under the CeCILL-B license (French BSD license)
25307 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 25308*/ 25309/** An interaction to snap on pixel on a layer 25310 * The CenterTouch interaction modifies map browser event coordinate and pixel properties to force pointer on the viewport center to any interaction that them. 25311 * @constructor 25312 * @extends {ol.interaction.Interaction} 25313 * @param {olx.interaction.InteractionOptions} options Options 25314 * @param {ol.layer.Layer} options.layer layer to snap on 25315 */ 25316ol.interaction.SnapLayerPixel = function(options) { 25317 options = options || {}; 25318 // Get layer canevas context 25319 this._layer = options.layer; 25320 this._layer.on(['postcompose', 'postrender'], function(e) { 25321 this._ctx = e.context; 25322 }.bind(this)); 25323 var radius = options.radius || 8; 25324 var size = 2*radius; 25325 ol.interaction.Interaction.call(this, { 25326 handleEvent: function(e) { 25327 if (this._layer.getVisible() && this._layer.getOpacity() 25328 && ol.events.condition.altKeyOnly(e) && this.getMap()) { 25329 var x0 = e.pixel[0] - radius; 25330 var y0 = e.pixel[1] - radius; 25331 var imgd = this._ctx.getImageData(x0, y0, size, size); 25332 var pix = imgd.data; 25333 // Loop over each pixel and invert the color. 25334 var x, y, xm, ym, max=-1; 25335 var t = []; 25336 for (x=0; x < size; x++) { 25337 t.push([]) 25338 for (y=0; y < size; y++) { 25339 var l = pix[3+ 4 * (x + y*size)]; 25340 t[x].push(l>10 ? l : 0) 25341 } 25342 } 25343 for (x=1; x < size-1; x++) { 25344 for (y=1; y < size-1; y++) { 25345 var m = t[x][y+1] + t[x][y] + t[x][y+1] 25346 + t[x-1][y-1] + t[x-1][y] + t[x-1][y+1] 25347 + t[x+1][y-1] + t[x+1][y] + t[x+1][y+1]; 25348 if (m > max) { 25349 max = m; 25350 xm = x; 25351 ym = y; 25352 } 25353 } 25354 } 25355 e.pixel = [x0+xm, y0+ym]; 25356 e.coordinate = this.getMap().getCoordinateFromPixel(e.pixel); 25357 /* 25358 e.coordinate = this.getMap().getView().getCenter(); 25359 e.pixel = this.getMap().getSize(); 25360 e.pixel = [ e.pixel[0]/2, e.pixel[1]/2 ]; 25361 */ 25362 } 25363 return true; 25364 } 25365 }); 25366}; 25367ol.ext.inherits(ol.interaction.SnapLayerPixel, ol.interaction.Interaction); 25368 25369/* Copyright (c) 2016 Jean-Marc VIGLINO, 25370 released under the CeCILL-B license (French BSD license) 25371 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 25372*/ 25373/** Interaction split interaction for splitting feature geometry 25374 * @constructor 25375 * @extends {ol.interaction.Interaction} 25376 * @fires beforesplit, aftersplit, pointermove 25377 * @param {*} 25378 * @param {ol.source.Vector|Array<ol.source.Vector>} [options.sources] a list of source to split (configured with useSpatialIndex set to true), if none use map visible layers. 25379 * @param {ol.Collection.<ol.Feature>} options.features collection of feature to split (instead of a list of sources) 25380 * @param {integer} options.snapDistance distance (in px) to snap to an object, default 25px 25381 * @param {string|undefined} options.cursor cursor name to display when hovering an objet 25382 * @param {function|undefined} opttion.filter a filter that takes a feature and return true if it can be clipped, default always split. 25383 * @param ol.style.Style | Array<ol.style.Style> | 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. 25384 * @param {ol.style.Style | Array<ol.style.Style> | undefined} options.sketchStyle Style for the sektch features. 25385 * @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. 25386 */ 25387ol.interaction.Split = function(options) { 25388 if (!options) options = {}; 25389 ol.interaction.Interaction.call(this, { 25390 handleEvent: function(e) { 25391 switch (e.type) { 25392 case "singleclick": 25393 return this.handleDownEvent(e); 25394 case "pointermove": 25395 return this.handleMoveEvent(e); 25396 default: 25397 return true; 25398 } 25399 //return true; 25400 } 25401 }); 25402 // Snap distance (in px) 25403 this.snapDistance_ = options.snapDistance || 25; 25404 // Split tolerance between the calculated intersection and the geometry 25405 this.tolerance_ = options.tolerance || 1e-10; 25406 // Cursor 25407 this.cursor_ = options.cursor; 25408 // List of source to split 25409 this.sources_ = this.setSources(options.sources); 25410 if (options.features) { 25411 this.sources_.push (new ol.source.Vector({ features: options.features })); 25412 } 25413 // Get all features candidate 25414 this.filterSplit_ = options.filter || function(){ return true; }; 25415 // Default style 25416 var white = [255, 255, 255, 1]; 25417 var blue = [0, 153, 255, 1]; 25418 var width = 3; 25419 var fill = new ol.style.Fill({ color: 'rgba(255,255,255,0.4)' }); 25420 var stroke = new ol.style.Stroke({ 25421 color: '#3399CC', 25422 width: 1.25 25423 }); 25424 var sketchStyle = [ 25425 new ol.style.Style({ 25426 image: new ol.style.Circle({ 25427 fill: fill, 25428 stroke: stroke, 25429 radius: 5 25430 }), 25431 fill: fill, 25432 stroke: stroke 25433 }) 25434 ]; 25435 var featureStyle = [ 25436 new ol.style.Style({ 25437 stroke: new ol.style.Stroke({ 25438 color: white, 25439 width: width + 2 25440 }) 25441 }), 25442 new ol.style.Style({ 25443 image: new ol.style.Circle({ 25444 radius: 2*width, 25445 fill: new ol.style.Fill({ 25446 color: blue 25447 }), 25448 stroke: new ol.style.Stroke({ 25449 color: white, 25450 width: width/2 25451 }) 25452 }), 25453 stroke: new ol.style.Stroke({ 25454 color: blue, 25455 width: width 25456 }) 25457 }), 25458 ]; 25459 // Custom style 25460 if (options.sketchStyle) sketchStyle = options.sketchStyle instanceof Array ? options.sketchStyle : [options.sketchStyle]; 25461 if (options.featureStyle) featureStyle = options.featureStyle instanceof Array ? options.featureStyle : [options.featureStyle]; 25462 // Create a new overlay for the sketch 25463 this.overlayLayer_ = new ol.layer.Vector({ 25464 source: new ol.source.Vector({
25465 useSpatialIndex: false 25466 }), 25467 name:'Split overlay', 25468 displayInLayerSwitcher: false, 25469 style: function(f) { 25470 if (f._sketch_) return sketchStyle; 25471 else return featureStyle; 25472 } 25473 }); 25474}; 25475ol.ext.inherits(ol.interaction.Split, ol.interaction.Interaction); 25476/** 25477 * Remove the interaction from its current map, if any, and attach it to a new 25478 * map, if any. Pass `null` to just remove the interaction from the current map. 25479 * @param {ol.Map} map Map. 25480 * @api stable 25481 */ 25482ol.interaction.Split.prototype.setMap = function(map) { 25483 if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_); 25484 ol.interaction.Interaction.prototype.setMap.call (this, map); 25485 this.overlayLayer_.setMap(map); 25486}; 25487/** Get sources to split features in 25488 * @return {Array<ol.source.Vector>} 25489 */ 25490ol.interaction.Split.prototype.getSources = function() { 25491 if (!this.sources_ && this.getMap()) { 25492 var sources = [] 25493 function getSources(layers) { 25494 layers.forEach(function(layer) { 25495 if (layer.getVisible()) { 25496 if (layer.getSource && layer.getSource() instanceof ol.source.Vector) { 25497 sources.unshift(layer.getSource()); 25498 } else if (layer.getLayers) { 25499 getSources(layer.getLayers()); 25500 } 25501 } 25502 }) 25503 } 25504 getSources(this.getMap().getLayers()) 25505 return sources; 25506 } 25507 return this.sources_ || []; 25508}; 25509/** Set sources to split features in 25510 * @param {ol.source.Vector|Array<ol.source.Vector>} [sources] 25511 */ 25512ol.interaction.Split.prototype.setSources = function(sources) { 25513 this.sources_ = sources ? (sources instanceof Array ? sources||false : [sources]) : false; 25514}; 25515/** Get closest feature at pixel 25516 * @param {ol.Pixel} 25517 * @return {ol.feature} 25518 * @private 25519 */ 25520ol.interaction.Split.prototype.getClosestFeature = function(e) { 25521 var source, f, c, g, d = this.snapDistance_+1; 25522 // Look for closest point in the sources 25523 this.getSources().forEach(function(si) { 25524 var fi = si.getClosestFeatureToCoordinate(e.coordinate); 25525 if (fi && fi.getGeometry().splitAt) { 25526 var ci = fi.getGeometry().getClosestPoint(e.coordinate); 25527 var gi = new ol.geom.LineString([e.coordinate,ci]); 25528 var di = gi.getLength() / e.frameState.viewState.resolution; 25529 if (di < d) { 25530 source = si; 25531 d = di; 25532 f = fi; 25533 g = gi; 25534 c = ci; 25535 } 25536 } 25537 }); 25538 // Snap ? 25539 if (d > this.snapDistance_) { 25540 return false; 25541 } else { 25542 // Snap to node 25543 var coord = this.getNearestCoord (c, f.getGeometry().getCoordinates()); 25544 var p = this.getMap().getPixelFromCoordinate(coord); 25545 if (ol.coordinate.dist2d(e.pixel, p) < this.snapDistance_) { 25546 c = coord; 25547 } 25548 // 25549 return { source:source, feature:f, coord: c, link: g }; 25550 } 25551} 25552/** Get nearest coordinate in a list 25553* @param {ol.coordinate} pt the point to find nearest 25554* @param {Array<ol.coordinate>} coords list of coordinates 25555* @return {ol.coordinate} the nearest coordinate in the list 25556*/ 25557ol.interaction.Split.prototype.getNearestCoord = function(pt, coords) { 25558 var d, dm=Number.MAX_VALUE, p0; 25559 for (var i=0; i < coords.length; i++) { 25560 d = ol.coordinate.dist2d (pt, coords[i]); 25561 if (d < dm) { 25562 dm = d; 25563 p0 = coords[i]; 25564 } 25565 } 25566 return p0; 25567}; 25568/** 25569 * @param {ol.MapBrowserEvent} evt Map browser event. 25570 * @return {boolean} `true` to start the drag sequence. 25571 */ 25572ol.interaction.Split.prototype.handleDownEvent = function(evt) { 25573 // Something to split ? 25574 var current = this.getClosestFeature(evt); 25575 if (current) { 25576 var self = this; 25577 self.overlayLayer_.getSource().clear(); 25578 var split = current.feature.getGeometry().splitAt(current.coord, this.tolerance_); 25579 var i; 25580 if (split.length > 1) { 25581 var tosplit = []; 25582 for (i=0; i<split.length; i++) { 25583 var f = current.feature.clone(); 25584 f.setGeometry(split[i]); 25585 tosplit.push(f); 25586 } 25587 self.dispatchEvent({ type:'beforesplit', original: current.feature, features: tosplit }); 25588 current.source.dispatchEvent({ type:'beforesplit', original: current.feature, features: tosplit }); 25589 current.source.removeFeature(current.feature); 25590 for (i=0; i<tosplit.length; i++) { 25591 current.source.addFeature(tosplit[i]); 25592 }
25593 self.dispatchEvent({ type:'aftersplit', original: current.feature, features: tosplit }); 25594 current.source.dispatchEvent({ type:'aftersplit', original: current.feature, features: tosplit }); 25595 } 25596 } 25597 return false; 25598}; 25599/** 25600 * @param {ol.MapBrowserEvent} evt Event. 25601 */ 25602ol.interaction.Split.prototype.handleMoveEvent = function(e) { 25603 var map = e.map; 25604 this.overlayLayer_.getSource().clear(); 25605 var current = this.getClosestFeature(e); 25606 if (current && this.filterSplit_(current.feature)) { 25607 var p, l; 25608 // Draw sketch 25609 this.overlayLayer_.getSource().addFeature(current.feature); 25610 p = new ol.Feature(new ol.geom.Point(current.coord)); 25611 p._sketch_ = true; 25612 this.overlayLayer_.getSource().addFeature(p); 25613 // 25614 l = new ol.Feature(current.link); 25615 l._sketch_ = true; 25616 this.overlayLayer_.getSource().addFeature(l); 25617 // move event 25618 this.dispatchEvent({ 25619 type: 'pointermove', 25620 coordinate: e.coordinate, 25621 frameState: e.frameState, 25622 originalEvent: e.originalEvent, 25623 map: e.map, 25624 pixel: e.pixel, 25625 feature: current.feature, 25626 linkGeometry: current.link 25627 }); 25628 } else { 25629 this.dispatchEvent(e); 25630 } 25631 var element = map.getTargetElement(); 25632 if (this.cursor_) { 25633 if (current) { 25634 if (element.style.cursor != this.cursor_) { 25635 this.previousCursor_ = element.style.cursor; 25636 element.style.cursor = this.cursor_; 25637 } 25638 } else if (this.previousCursor_ !== undefined) { 25639 element.style.cursor = this.previousCursor_; 25640 this.previousCursor_ = undefined; 25641 } 25642 } 25643}; 25644 25645/* Copyright (c) 2016 Jean-Marc VIGLINO, 25646 released under the CeCILL-B license (French BSD license) 25647 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 25648*/ 25649/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ 25650/** Interaction splitter: acts as a split feature agent while editing vector features (LineString). 25651 * @constructor 25652 * @extends {ol.interaction.Interaction} 25653 * @fires beforesplit, aftersplit 25654 * @param {options} 25655 * @param {ol.source.Vector|Array{ol.source.Vector}} options.source The target source (or array of source) with features to be split (configured with useSpatialIndex set to true) 25656 * @param {ol.source.Vector} options.triggerSource Any newly created or modified features from this source will be used to split features on the target source. If none is provided the target source is used instead. 25657 * @param {ol.Collection.<ol.Feature>} options.features A collection of feature to be split (replace source target). 25658 * @param {ol.Collection.<ol.Feature>} options.triggerFeatures Any newly created or modified features from this collection will be used to split features on the target source (replace triggerSource). 25659 * @param {function|undefined} options.filter a filter that takes a feature and return true if the feature is eligible for splitting, default always split. 25660 * @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. 25661 * @todo verify auto intersection on features that split. 25662 */ 25663ol.interaction.Splitter = function(options) { 25664 if (!options) options = {}; 25665 ol.interaction.Interaction.call(this, { 25666 handleEvent: function(e) { 25667 // Hack to get only one changeFeature when draging with ol.interaction.Modify on. 25668 if (e.type != "pointermove" && e.type != "pointerdrag") { 25669 if (this.lastEvent_) { 25670 this.splitSource(this.lastEvent_.feature); 25671 this.lastEvent_ = null; 25672 } 25673 this.moving_ = false; 25674 } 25675 else this.moving_ = true; 25676 return true; 25677 }, 25678 }); 25679 // Features added / remove 25680 this.added_ = []; 25681 this.removed_ = []; 25682 // Source to split 25683 if (options.features) { 25684 this.source_ = new ol.source.Vector({ features: options.features }); 25685 } else { 25686 this.source_ = options.source ? options.source : new ol.source.Vector({ features: new ol.Collection() }); 25687 } 25688 var trigger = this.triggerSource; 25689 if (options.triggerFeatures) { 25690 trigger = new ol.source.Vector({ features: options.triggerFeatures }); 25691 } 25692 if (trigger) { 25693 trigger.on("addfeature", this.onAddFeature.bind(this)); 25694 trigger.on("changefeature", this.onChangeFeature.bind(this)); 25695 trigger.on("removefeature", this.onRemoveFeature.bind(this)); 25696 } else { 25697 this.source_.on("addfeature", this.onAddFeature.bind(this)); 25698 this.source_.on("changefeature", this.onChangeFeature.bind(this)); 25699 this.source_.on("removefeature", this.onRemoveFeature.bind(this)); 25700 } 25701 // Split tolerance between the calculated intersection and the geometry 25702 this.tolerance_ = options.tolerance || 1e-10; 25703 // Get all features candidate 25704 this.filterSplit_ = options.filter || function(){ return true; }; 25705}; 25706ol.ext.inherits(ol.interaction.Splitter, ol.interaction.Interaction); 25707/** Calculate intersection on 2 segs 25708* @param {Array<_ol_coordinate_>} s1 first seg to intersect (2 points) 25709* @param {Array<_ol_coordinate_>} s2 second seg to intersect (2 points) 25710* @return { boolean | _ol_coordinate_ } intersection point or false no intersection 25711*/ 25712ol.interaction.Splitter.prototype.intersectSegs = function(s1,s2) { 25713 var tol = this.tolerance_; 25714 // Solve 25715 var x12 = s1[0][0] - s1[1][0]; 25716 var x34 = s2[0][0] - s2[1][0]; 25717 var y12 = s1[0][1] - s1[1][1]; 25718 var y34 = s2[0][1] - s2[1][1]; 25719 var det = x12 * y34 - y12 * x34; 25720 // No intersection 25721 if (Math.abs(det) < tol) { 25722 return false;
25723 } else { 25724 // Outside segement 25725 var r1 = ((s1[0][0] - s2[1][0])*y34 - (s1[0][1] - s2[1][1])*x34) / det; 25726 if (Math.abs(r1)<tol) return s1[0]; 25727 if (Math.abs(1-r1)<tol) return s1[1]; 25728 if (r1<0 || r1>1) return false; 25729 var r2 = ((s1[0][1] - s2[1][1])*x12 - (s1[0][0] - s2[1][0])*y12) / det; 25730 if (Math.abs(r2)<tol) return s2[1]; 25731 if (Math.abs(1-r2)<tol) return s2[0]; 25732 if (r2<0 || r2>1) return false; 25733 // Intersection 25734 var a = s1[0][0] * s1[1][1] - s1[0][1] * s1[1][0]; 25735 var b = s2[0][0] * s2[1][1] - s2[0][1] * s2[1][0]; 25736 var p = [(a * x34 - b * x12) / det, (a * y34 - b * y12) / det]; 25737 // Test start / end 25738/* 25739console.log("r1: "+r1) 25740console.log("r2: "+r2) 25741console.log ("s10: "+(_ol_coordinate_.dist2d(p,s1[0])<tol)) ; 25742console.log ("s11: "+(_ol_coordinate_.dist2d(p,s1[1])<tol)) ; 25743console.log ("s20: "+(_ol_coordinate_.dist2d(p,s2[0])<tol)) ; 25744console.log ("s21: "+(_ol_coordinate_.dist2d(p,s2[1])<tol)) ; 25745*/ 25746 return p; 25747 } 25748}; 25749/** Split the source using a feature 25750* @param {ol.Feature} feature The feature to use to split. 25751* @private 25752*/ 25753ol.interaction.Splitter.prototype.splitSource = function(feature, change) { 25754 if (!this.getActive()) return; 25755 // Allready perform a split 25756 if (this.splitting) return; 25757 // Start splitting 25758 this.source_.dispatchEvent({ type:'beforesplit', feaure: feature, source: this.source_ }); 25759 this.dispatchEvent({ type:'beforesplit', feaure: feature, source: this.source_ }); 25760 // If the interaction is inserted other interaction, the objet is not consistant 25761 // > wait end of other interactions 25762 if (change) { 25763 this._splitSource(feature); 25764 } else { 25765 setTimeout(function() { this._splitSource(feature); }.bind(this)); 25766 } 25767} 25768/** Split the source using a feature 25769* @param {ol.Feature} feature The feature to use to split. 25770* @private 25771*/ 25772ol.interaction.Splitter.prototype._splitSource = function(feature) { 25773 var i, k, f2; 25774 this.splitting = true; 25775 this.added_ = []; 25776 this.removed_ = []; 25777 var c = feature.getGeometry().getCoordinates(); 25778 var seg, split = []; 25779 function intersect (f) { 25780 if (f !== feature) { 25781 var c2 = f.getGeometry().getCoordinates(); 25782 for (var j=0; j<c2.length-1; j++) { 25783 var p = this.intersectSegs (seg, [c2[j],c2[j+1]]); 25784 if (p) { 25785 split.push(p); 25786 g = f.getGeometry().splitAt(p, this.tolerance_); 25787 if (g && g.length>1) { 25788 found = f; 25789 return true; 25790 } 25791 } 25792 } 25793 } 25794 return false; 25795 } 25796 // Split existing features 25797 for (i=0; i<c.length-1; i++) { 25798 seg = [c[i],c[i+1]]; 25799 var extent = ol.extent.buffer(ol.extent.boundingExtent(seg), this.tolerance_ /*0.01*/ ); 25800 var g; 25801 while (true) { 25802 var found = false; 25803 this.source_.forEachFeatureIntersectingExtent(extent, intersect.bind(this)); 25804 // Split feature 25805 if (found) { 25806 var f = found; 25807 this.source_.removeFeature(f); 25808 for (k=0; k<g.length; k++) { 25809 f2 = f.clone(); 25810 f2.setGeometry(g[k]); 25811 this.source_.addFeature(f2); 25812 } 25813 } 25814 else break; 25815 } 25816 } 25817 // Auto intersect 25818 for (i=0; i<c.length-2; i++) { 25819 for (var j=i+1; j<c.length-1; j++) { 25820 var p = this.intersectSegs ([c[i],c[i+1]], [c[j],c[j+1]]); 25821 if (p && p!=c[i+1]) { 25822 split.push(p); 25823 } 25824 } 25825 } 25826 // Split original 25827 var splitOriginal = false;
25828 if (split.length) { 25829 var result = feature.getGeometry().splitAt(split, this.tolerance_); 25830 if (result.length>1) { 25831 for (k=0; k<result.length; k++) { 25832 f2 = feature.clone(); 25833 f2.setGeometry(result[k]); 25834 this.source_.addFeature(f2); 25835 } 25836 splitOriginal = true; 25837 } 25838 } 25839 // End splitting 25840 setTimeout(function() { 25841 if (splitOriginal) this.source_.removeFeature(feature); 25842 this.source_.dispatchEvent({ type:'aftersplit', featureAdded: this.added_, featureRemoved: this.removed_, source: this.source_ }); 25843 this.dispatchEvent({ type:'aftersplit', featureAdded: this.added_, featureRemoved: this.removed_, source: this.source_ }); 25844 // Finish 25845 this.splitting = false; 25846 }.bind(this)); 25847}; 25848/** New feature source is added 25849 * @private 25850*/ 25851ol.interaction.Splitter.prototype.onAddFeature = function(e) { 25852 this.splitSource(e.feature); 25853 if (this.splitting) { 25854 this.added_.push(e.feature); 25855 } 25856}; 25857/** Feature source is removed > count features added/removed 25858 * @private 25859*/ 25860ol.interaction.Splitter.prototype.onRemoveFeature = function(e) { 25861 if (this.splitting) { 25862 var n = this.added_.indexOf(e.feature); 25863 if (n==-1) { 25864 this.removed_.push(e.feature); 25865 } else { 25866 this.added_.splice(n,1); 25867 } 25868 } 25869}; 25870/** Feature source is changing 25871 * @private 25872*/ 25873ol.interaction.Splitter.prototype.onChangeFeature = function(e) { 25874 if (this.moving_) { 25875 this.lastEvent_ = e; 25876 } else { 25877 this.splitSource(e.feature, true); 25878 } 25879}; 25880 25881/* Copyright (c) 2016 Jean-Marc VIGLINO, 25882 released under the CeCILL-B license (French BSD license) 25883 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 25884*/ 25885/** Interaction synchronize 25886 * @constructor 25887 * @extends {ol.interaction.Interaction} 25888 * @param {*} options 25889 * @param {Array<ol.Map>} options maps An array of maps to synchronize with the map of the interaction 25890 */ 25891ol.interaction.Synchronize = function(options) { 25892 if (!options) options={}; 25893 var self = this; 25894 ol.interaction.Interaction.call(this, { 25895 handleEvent: function(e) { 25896 if (e.type=="pointermove") { self.handleMove_(e); } 25897 return true; 25898 } 25899 }); 25900 this.maps = options.maps || []; 25901 if (options.active === false) this.setActive(false); 25902}; 25903ol.ext.inherits(ol.interaction.Synchronize, ol.interaction.Interaction); 25904/** 25905 * Remove the interaction from its current map, if any, and attach it to a new 25906 * map, if any. Pass `null` to just remove the interaction from the current map. 25907 * @param {ol.Map} map Map. 25908 * @api stable 25909 */ 25910ol.interaction.Synchronize.prototype.setMap = function(map) { 25911 if (this._listener) { 25912 ol.Observable.unByKey(this._listener.center); 25913 ol.Observable.unByKey(this._listener.rotation); 25914 ol.Observable.unByKey(this._listener.resolution); 25915 this.getMap().getTargetElement().removeEventListener('mouseout', this._listener.mouseout); 25916 } 25917 this._listener = null; 25918 ol.interaction.Interaction.prototype.setMap.call (this, map); 25919 if (map) { 25920 this._listener = {}; 25921 this._listener.center = this.getMap().getView().on('change:center', this.syncMaps.bind(this)); 25922 this._listener.rotation = this.getMap().getView().on('change:rotation', this.syncMaps.bind(this)); 25923 this._listener.resolution = this.getMap().getView().on('change:resolution', this.syncMaps.bind(this)); 25924 this._listener.mouseout = this.handleMouseOut_.bind(this); 25925 if (this.getMap().getTargetElement()) { 25926 this.getMap().getTargetElement().addEventListener('mouseout', this._listener.mouseout); 25927 } 25928 this.syncMaps(); 25929 } 25930}; 25931/** Auto activate/deactivate controls in the bar 25932 * @param {boolean} b activate/deactivate 25933 */ 25934ol.interaction.Synchronize.prototype.setActive = function (b) { 25935 ol.interaction.Interaction.prototype.setActive.call(this, b); 25936 this.syncMaps(); 25937}; 25938/** Synchronize the maps 25939 */ 25940ol.interaction.Synchronize.prototype.syncMaps = function() { 25941 if (!this.getActive()) return; 25942 var map = this.getMap(); 25943 if (map) { 25944 if (map.get('lockView')) return; 25945 for (var i=0; i<this.maps.length; i++) { 25946 this.maps[i].set('lockView', true); 25947 // sync 25948 if (this.maps[i].getView().getRotation() != map.getView().getRotation()) { 25949 this.maps[i].getView().setRotation(map.getView().getRotation()); 25950 } 25951 if (this.maps[i].getView().getCenter() != map.getView().getCenter()) { 25952 this.maps[i].getView().setCenter(map.getView().getCenter()); 25953 } 25954 if (this.maps[i].getView().getResolution() != map.getView().getResolution()) { 25955 this.maps[i].getView().setResolution(map.getView().getResolution()); 25956 } 25957 this.maps[i].set('lockView', false); 25958 } 25959 } 25960}; 25961/** Cursor move > tells other maps to show the cursor 25962* @param {ol.event} e "move" event 25963*/ 25964ol.interaction.Synchronize.prototype.handleMove_ = function(e) { 25965 for (var i=0; i<this.maps.length; i++) { 25966 this.maps[i].showTarget(e.coordinate); 25967 } 25968 this.getMap().showTarget(); 25969}; 25970/** Cursor out of map > tells other maps to hide the cursor 25971* @param {event} e "mouseOut" event 25972*/ 25973ol.interaction.Synchronize.prototype.handleMouseOut_ = function(/*e*/) { 25974 for (var i=0; i<this.maps.length; i++) { 25975 if (this.maps[i]._targetOverlay) this.maps[i]._targetOverlay.setPosition(undefined); 25976 } 25977}; 25978/** Show a target overlay at coord 25979* @param {ol.coordinate} coord 25980*/ 25981ol.Map.prototype.showTarget = function(coord) { 25982 if (!this._targetOverlay) { 25983 var elt = document.createElement("div"); 25984 elt.classList.add("ol-target"); 25985 this._targetOverlay = new ol.Overlay({ element: elt }); 25986 this._targetOverlay.setPositioning('center-center'); 25987 this.addOverlay(this._targetOverlay); 25988 elt.parentElement.classList.add("ol-target-overlay"); 25989 // hack to render targetOverlay before positioning it 25990 this._targetOverlay.setPosition([0,0]); 25991 } 25992 this._targetOverlay.setPosition(coord); 25993}; 25994/** Hide the target overlay 25995*/ 25996ol.Map.prototype.hideTarget = function() { 25997 this.removeOverlay(this._targetOverlay); 25998 this._targetOverlay = undefined; 25999}; 26000 26001/* 26002 Tinker Bell effect on maps. 26003 Copyright (c) 2015 Jean-Marc VIGLINO, 26004 released under the CeCILL-B license (French BSD license)
26005 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 26006 @link https://github.com/Viglino 26007*/ 26008/** 26009 * @constructor 26010 * @extends {ol.interaction.Pointer} 26011 * @param {ol.interaction.TinkerBell.options} options flashlight param 26012* - color {ol.color} color of the sparkles 26013*/ 26014ol.interaction.TinkerBell = function(options) { 26015 options = options || {}; 26016 ol.interaction.Pointer.call(this, { 26017 handleDownEvent: this.onMove, 26018 handleMoveEvent: this.onMove 26019 }); 26020 this.set('color', options.color ? ol.color.asString(options.color) : "#fff"); 26021 this.sparkle = [0,0]; 26022 this.sparkles = []; 26023 this.lastSparkle = this.time = new Date(); 26024 var self = this; 26025 this.out_ = function() { self.isout_=true; }; 26026 this.isout_ = true; 26027}; 26028ol.ext.inherits(ol.interaction.TinkerBell, ol.interaction.Pointer); 26029/** Set the map > start postcompose 26030*/ 26031ol.interaction.TinkerBell.prototype.setMap = function(map) { 26032 if (this._listener) ol.Observable.unByKey(this._listener); 26033 this._listener = null; 26034 if (this.getMap()) { 26035 map.getViewport().removeEventListener('mouseout', this.out_, false); 26036 this.getMap().render(); 26037 } 26038 ol.interaction.Pointer.prototype.setMap.call(this, map); 26039 if (map) { 26040 this._listener = map.on('postcompose', this.postcompose_.bind(this)); 26041 map.getViewport().addEventListener('mouseout', this.out_, false); 26042 } 26043}; 26044ol.interaction.TinkerBell.prototype.onMove = function(e) { 26045 this.sparkle = e.pixel; 26046 this.isout_ = false; 26047 this.getMap().render(); 26048}; 26049/** Postcompose function 26050*/ 26051ol.interaction.TinkerBell.prototype.postcompose_ = function(e) { 26052 var delta = 15; 26053 var ctx = e.context || ol.ext.getMapCanvas(this.getMap()).getContext('2d'); 26054 var dt = e.frameState.time - this.time; 26055 this.time = e.frameState.time; 26056 if (e.frameState.time-this.lastSparkle > 30 && !this.isout_) { 26057 this.lastSparkle = e.frameState.time; 26058 this.sparkles.push({ p:[this.sparkle[0]+Math.random()*delta-delta/2, this.sparkle[1]+Math.random()*delta], o:1 }); 26059 } 26060 ctx.save(); 26061 ctx.scale(e.frameState.pixelRatio,e.frameState.pixelRatio); 26062 ctx.fillStyle = this.get("color"); 26063 for (var i=this.sparkles.length-1, p; p=this.sparkles[i]; i--) { 26064 if (p.o < 0.2) { 26065 this.sparkles.splice(0,i+1); 26066 break; 26067 } 26068 ctx.globalAlpha = p.o; 26069 ctx.beginPath(); 26070 ctx.arc (p.p[0], p.p[1], 2.2, 0, 2 * Math.PI, false); 26071 ctx.fill(); 26072 p.o *= 0.98; 26073 p.p[0] += (Math.random()-0.5); 26074 p.p[1] += dt*(1+Math.random())/30; 26075 } 26076 ctx.restore(); 26077 // continue postcompose animation 26078 if (this.sparkles.length) this.getMap().render(); 26079}; 26080 26081/* Copyright (c) 2016 Jean-Marc VIGLINO, 26082 released under the CeCILL-B license (French BSD license) 26083 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 26084*/ 26085/** Interaction splitter: acts as a split feature agent while editing vector features (LineString). 26086 * @constructor 26087 * @extends {ol.interaction.Pointer} 26088 * @param {olx.interaction.TouchCompass} 26089 * - onDrag {function|undefined} Function handling "drag" events. It provides a dpixel and a traction (in projection) vector form the center of the compas 26090 * - size {Number} size of the compass in px, default 80 26091 * - alpha {Number} opacity of the compass, default 0.5 26092 */ 26093ol.interaction.TouchCompass = function(options) { 26094 options = options||{}; 26095 var opt = {}; 26096 // Click on the compass 26097 opt.handleDownEvent = function(e) 26098 { var s = this.getCenter_(); 26099 var dx = e.pixel[0]-s[0]; 26100 var dy = e.pixel[1]-s[1]; 26101 this.start = e; 26102 return (Math.sqrt(dx*dx+dy*dy) < this.size/2); 26103 }; 26104 // Pn drag 26105 opt.handleDragEvent = function(e) { 26106 if (!this.pos) { 26107 this.pos = this.start; 26108 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 26109 } 26110 this.pos = e; 26111 }; 26112 // Stop drag 26113 opt.handleUpEvent = function() 26114 { this.pos = false; 26115 return true; 26116 }; 26117 ol.interaction.Pointer.call(this, opt); 26118 this.ondrag_ = options.onDrag; 26119 this.size = options.size || 80; 26120 this.alpha = options.alpha || 0.5; 26121 if (!ol.interaction.TouchCompass.prototype.compass) 26122 { var canvas = ol.interaction.TouchCompass.prototype.compass = document.createElement('canvas'); 26123 var ctx = canvas.getContext("2d"); 26124 var s = canvas.width = canvas.height = this.size; 26125 var w = s/10; 26126 var r = s/2; 26127 var r2 = 0.22*r; 26128 ctx.translate(r,r); 26129 ctx.fillStyle = "#999"; 26130 ctx.strokeStyle = "#ccc"; 26131 ctx.lineWidth = w; 26132 ctx.beginPath(); 26133 ctx.arc (0,0, s*0.42, 0, 2*Math.PI); 26134 ctx.fill(); 26135 ctx.stroke(); 26136 ctx.fillStyle = "#99f"; 26137 ctx.beginPath(); 26138 ctx.moveTo (0,0); 26139 ctx.lineTo (r,0); ctx.lineTo (r2,r2); ctx.moveTo (0,0); 26140 ctx.lineTo (-r,0); ctx.lineTo (-r2,-r2); ctx.moveTo (0,0); 26141 ctx.lineTo (0,r); ctx.lineTo (-r2,r2); ctx.moveTo (0,0); 26142 ctx.lineTo (0,-r); ctx.lineTo (r2,-r2); ctx.moveTo (0,0); 26143 ctx.fill(); 26144 ctx.fillStyle = "#eee"; 26145 ctx.beginPath(); 26146 ctx.moveTo (0,0); 26147 ctx.lineTo (r,0); ctx.lineTo (r2,-r2); ctx.moveTo (0,0); 26148 ctx.lineTo (-r,0); ctx.lineTo (-r2,r2); ctx.moveTo (0,0); 26149 ctx.lineTo (0,r); ctx.lineTo (r2,r2); ctx.moveTo (0,0); 26150 ctx.lineTo (0,-r); ctx.lineTo (-r2,-r2); ctx.moveTo (0,0); 26151 ctx.fill(); 26152 } 26153}; 26154ol.ext.inherits(ol.interaction.TouchCompass, ol.interaction.Pointer); 26155/** Compass Image as a JS Image object 26156* @api 26157*/ 26158ol.interaction.TouchCompass.prototype.compass = null; 26159/** 26160 * Remove the interaction from its current map, if any, and attach it to a new 26161 * map, if any. Pass `null` to just remove the interaction from the current map. 26162 * @param {_ol_Map_} map Map. 26163 * @api stable 26164 */ 26165ol.interaction.TouchCompass.prototype.setMap = function(map) { 26166 if (this._listener) ol.Observable.unByKey(this._listener); 26167 this._listener = null; 26168 ol.interaction.Pointer.prototype.setMap.call (this, map); 26169 if (map) { 26170 this._listener = map.on('postcompose', this.drawCompass_.bind(this)); 26171 ol.ext.getMapCanvas(map); 26172 } 26173}; 26174/** 26175 * Activate or deactivate the interaction. 26176 * @param {boolean} active Active. 26177 * @observable 26178 * @api 26179 */ 26180ol.interaction.TouchCompass.prototype.setActive = function(b) 26181{ ol.interaction.Pointer.prototype.setActive.call (this, b); 26182 if (this.getMap()) { 26183 try { this.getMap().renderSync(); } catch(e) { /* ok */ } 26184 } 26185} 26186/** 26187 * Get the center of the compass 26188 * @param {_ol_coordinate_} 26189 * @private 26190 */ 26191ol.interaction.TouchCompass.prototype.getCenter_ = function() 26192{ var margin = 10; 26193 var s = this.size; 26194 var c = this.getMap().getSize(); 26195 return [c[0]/2, c[1]-margin-s/2]; 26196} 26197/** 26198 * Draw the compass on post compose 26199 * @private 26200 */ 26201ol.interaction.TouchCompass.prototype.drawCompass_ = function(e) 26202{ if (!this.getActive()) return; 26203 var ctx = e.context || ol.ext.getMapCanvas(this.getMap()).getContext('2d'); 26204 var ratio = e.frameState.pixelRatio; 26205 ctx.save(); 26206 ctx.scale(ratio,ratio); 26207 ctx.globalAlpha = this.alpha; 26208 ctx.strokeStyle = "#fff"; 26209 ctx.lineWidth = 5; 26210 var s = this.size; 26211 var c = this.getCenter_(); 26212 ctx.drawImage(this.compass, 0,0,this.compass.width,this.compass.height, c[0]-s/2, c[1]-s/2, s,s); 26213 if (this.pos) 26214 { var dx = this.pos.pixel[0]-this.start.pixel[0]; 26215 var dy = this.pos.pixel[1]-this.start.pixel[1]; 26216 for (var i=1; i<=4; i++) 26217 { ctx.beginPath(); 26218 ctx.arc (c[0] +dx/4*i, c[1] +dy/4*i, s/2*(0.6+0.4*i/4), 0, 2*Math.PI); 26219 ctx.stroke(); 26220 } 26221 }
26222 ctx.restore(); 26223 if (this.pos) 26224 { // Get delta 26225 if (this.ondrag_) 26226 { var r = this.getMap().getView().getResolution(); 26227 var delta = 26228 { dpixel: [ this.pos.pixel[0] - this.start.pixel[0], this.pos.pixel[1] - this.start.pixel[1] ] 26229 } 26230 delta.traction = [ delta.dpixel[0]*r, -delta.dpixel[1]*r]; 26231 this.ondrag_(delta, this.pos); 26232 } 26233 // Continue animation 26234 e.frameState.animate = true; 26235 } 26236}; 26237 26238/* Copyright (c) 2016 Jean-Marc VIGLINO, 26239 released under the CeCILL-B license (French BSD license) 26240 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 26241*/ 26242/** Handle a touch cursor to defer event position on overlay position 26243 * It can be used as abstract base class used for creating subclasses. 26244 * 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. 26245 * @constructor 26246 * @extends {ol.interaction.DragOverlay} 26247 * @param {olx.interaction.InteractionOptions} options Options 26248 * @param {string} options.className cursor class name 26249 * @param {ol.coordinate} options.coordinate position of the cursor 26250 * @param {Array<*>} options.buttons an array of buttons 26251 * @param {number} options.maxButtons maximum number of buttons (default 5) 26252 */ 26253ol.interaction.TouchCursor = function(options) { 26254 options = options || {}; 26255 // List of listerner on the object 26256 this._listeners = {}; 26257 // Interaction to defer position on top of the interaction 26258 // this is done to enable other coordinates manipulation inserted after the interaction (snapping) 26259 var offset = [-35,-35]; 26260 this.ctouch = new ol.interaction.Interaction({ 26261 handleEvent: function(e) { 26262 if (!/drag/.test(e.type) && this.getMap()) { 26263 e.coordinate = this.overlay.getPosition(); 26264 e.pixel = this.getMap().getPixelFromCoordinate(e.coordinate); 26265 this._lastEvent = e; 26266 } else { 26267 var res = e.frameState.viewState.resolution 26268 var cosa = Math.cos(e.frameState.viewState.rotation); 26269 var sina = Math.sin(e.frameState.viewState.rotation); 26270 e.coordinate = [ 26271 e.coordinate[0] + cosa*offset[0]*res + sina*offset[1]*res, 26272 e.coordinate[1] + sina*offset[0]*res - cosa*offset[1]*res 26273 ]; 26274 e.pixel = this.getMap().getPixelFromCoordinate(e.coordinate); 26275 } 26276 return true; 26277 }.bind(this) 26278 }); 26279 // Force interaction on top 26280 this.ctouch.set('onTop', true); 26281 // Add Overlay 26282 this.overlay = new ol.Overlay.Fixed({ 26283 className: ('ol-touch-cursor '+(options.className||'')).trim(), 26284 positioning: 'top-left', 26285 element: ol.ext.element.create('DIV', {}), 26286 stopEvent: false, 26287 }); 26288 ol.interaction.DragOverlay.call(this, { 26289 centerOnClick: false, 26290 //offset: [-20,-20], 26291 overlays: this.overlay 26292 }); 26293 this.setPosition(options.coordinate, true); 26294 this.set('maxButtons', options.maxButtons || 5); 26295 if (options.buttons) { 26296 if (options.buttons.length > this.get('maxButtons')) this.set('maxButtons', options.buttons.length); 26297 var elt = this.overlay.element; 26298 var begin = options.buttons.length > 4 ? 0 : 1; 26299 options.buttons.forEach((function (b, i) { 26300 if (i<5) { 26301 ol.ext.element.create('DIV', { 26302 className: ((b.className||'')+' ol-button ol-button-' + (i+begin)).trim(), 26303 html: ol.ext.element.create('DIV', { html: b.html }), 26304 click: b.click, 26305 on: b.on, 26306 parent: elt 26307 }) 26308 } 26309 })) 26310 } 26311 // Replace events to handle click 26312 var dragging = false; 26313 var start = false;
26314 this.on('dragstart', function (e) { 26315 this._pixel = this.getMap().getPixelFromCoordinate(this.overlay.getPosition()) 26316 start = e; 26317 return !e.overlay; 26318 }) 26319 this.on('dragend', function (e) { 26320 this._pixel = this.getMap().getPixelFromCoordinate(this.overlay.getPosition()) 26321 if (!e.overlay) return true; 26322 if (dragging) { 26323 this.dispatchEvent({ 26324 type: 'dragend', 26325 dragging: dragging, 26326 originalEvent: e.originalEvent, 26327 frameState: e.frameState, 26328 pixel: this._pixel, 26329 coordinate: this.overlay.getPosition() 26330 }); 26331 dragging = false; 26332 } else { 26333 if (e.originalEvent.target === this.overlay.element) { 26334 this.dispatchEvent({ 26335 type: 'click', 26336 dragging: dragging, 26337 originalEvent: e.originalEvent, 26338 frameState: e.frameState, 26339 pixel: this._pixel, 26340 coordinate: this.overlay.getPosition() 26341 }); 26342 } 26343 } 26344 return false; 26345 }.bind(this)) 26346 this.on('dragging', function (e) { 26347 this._pixel = this.getMap().getPixelFromCoordinate(this.overlay.getPosition()); 26348 if (!e.overlay) return true; 26349 dragging = true; 26350 if (start) { 26351 this.dispatchEvent({ 26352 type: 'dragstart', 26353 dragging: dragging, 26354 originalEvent: start.originalEvent, 26355 frameState: e.frameState, 26356 pixel: this._pixel, 26357 coordinate: start.coordinate 26358 }); 26359 start = false;
26360 } 26361 this.dispatchEvent({ 26362 type: 'dragging', 26363 dragging: dragging, 26364 originalEvent: e.originalEvent, 26365 frameState: e.frameState, 26366 pixel: this._pixel, 26367 coordinate: this.overlay.getPosition() 26368 }); 26369 return false; 26370 }.bind(this)) 26371}; 26372ol.ext.inherits(ol.interaction.TouchCursor, ol.interaction.DragOverlay); 26373/** 26374 * Remove the interaction from its current map, if any, and attach it to a new 26375 * map, if any. Pass `null` to just remove the interaction from the current map. 26376 * @param {_ol_Map_} map Map. 26377 * @api stable 26378 */ 26379ol.interaction.TouchCursor.prototype.setMap = function(map) { 26380 // Reset 26381 if (this.getMap()) { 26382 this.getMap().removeInteraction(this.ctouch); 26383 if (this.getActive()) this.getMap().removeOverlay(this.overlay); 26384 } 26385 for (var l in this._listeners) { 26386 ol.Observable.unByKey(this._listeners[l]); 26387 } 26388 this._listeners = {}; 26389 ol.interaction.DragOverlay.prototype.setMap.call (this, map); 26390 // Set listeners 26391 if (map) { 26392 if (this.getActive()) { 26393 map.addOverlay(this.overlay); 26394 setTimeout( function() { 26395 this.setPosition(this.getPosition() || map.getView().getCenter()); 26396 }.bind(this)); 26397 } 26398 map.addInteraction(this.ctouch); 26399 this._listeners.addInteraction = map.getInteractions().on('add', function(e) { 26400 // Move on top 26401 if (!e.element.get('onTop')) { 26402 map.removeInteraction(this.ctouch); 26403 map.addInteraction(this.ctouch); 26404 } 26405 }.bind(this)); 26406 } 26407}; 26408/** 26409 * Activate or deactivate the interaction. 26410 * @param {boolean} active Active. 26411 * @param {ol.coordinate|null} position position of the cursor (when activating), default viewport center. 26412 * @observable 26413 * @api 26414 */ 26415ol.interaction.TouchCursor.prototype.setActive = function(b, position) { 26416 if (b!==this.getActive()) { 26417 this.ctouch.setActive(b); 26418 if (!b) { 26419 this.setPosition(); 26420 this.overlay.element.classList.remove('active'); 26421 if (this._activate) clearTimeout(this._activate); 26422 if (this.getMap()) this.getMap().removeOverlay(this.overlay); 26423 } else { 26424 if (this.getMap()) { 26425 this.getMap().addOverlay(this.overlay); 26426 } 26427 if (position) { 26428 this.setPosition(position); 26429 } else if (this.getMap()) { 26430 this.setPosition(this.getMap().getView().getCenter()); 26431 } 26432 this._activate = setTimeout(function() { 26433 this.overlay.element.classList.add('active'); 26434 }.bind(this), 100); 26435 } 26436 ol.interaction.DragOverlay.prototype.setActive.call (this, b); 26437 } else if (position) { 26438 this.setPosition(position); 26439 } else if (this.getMap()) { 26440 this.setPosition(this.getMap().getView().getCenter()); 26441 } 26442}; 26443/** Set the position of the target 26444 * @param {ol.coordinate} coord 26445 */ 26446ol.interaction.TouchCursor.prototype.setPosition = function (coord) { 26447 this.overlay.setPosition(coord, true); 26448 if (this.getMap() && coord) { 26449 this._pixel = this.getMap().getPixelFromCoordinate(coord); 26450 } 26451}; 26452/** Offset the target position 26453 * @param {ol.coordinate} coord 26454 */ 26455ol.interaction.TouchCursor.prototype.offsetPosition = function (coord) { 26456 var pos = this.overlay.getPosition(); 26457 if (pos) this.overlay.setPosition([pos[0]+coord[0], pos[1]+coord[1]]); 26458}; 26459/** Get the position of the target 26460 * @return {ol.coordinate} 26461 */ 26462ol.interaction.TouchCursor.prototype.getPosition = function () { 26463 return this.overlay.getPosition(); 26464}; 26465/** Get pixel position 26466 * @return {ol.pixel} 26467 */ 26468ol.interaction.TouchCursor.prototype.getPixel = function () { 26469 if (this.getMap()) return this.getMap().getPixelFromCoordinate(this.getPosition()); 26470}; 26471/** Get cursor overlay 26472 * @return {ol.Overlay} 26473 */ 26474ol.interaction.TouchCursor.prototype.getOverlay = function () { 26475 return this.overlay; 26476}; 26477/** Get cursor overlay element 26478 * @return {Element} 26479 */ 26480ol.interaction.TouchCursor.prototype.getOverlayElement = function () { 26481 return this.overlay.element; 26482}; 26483/** Get cursor button element 26484 * @param {string|number} button the button className or the button index 26485 * @return {Element} 26486 */ 26487ol.interaction.TouchCursor.prototype.getButtonElement = function (button) { 26488 if (typeof(button) === 'number') return this.getOverlayElement().getElementsByClassName('ol-button')[button]; 26489 return this.getOverlayElement().getElementsByClassName(button)[0]; 26490}; 26491/** Remove a button element 26492 * @param {string|number|undefined} button the button className or the button index, if undefined remove all buttons, default remove all 26493 * @return {Element} 26494 */ 26495ol.interaction.TouchCursor.prototype.removeButton = function (button) { 26496 if (button===undefined) { 26497 var buttons = this.getOverlayElement().getElementsByClassName('ol-button'); 26498 for (var i=buttons.length-1; i>=0; i--) { 26499 this.getOverlayElement().removeChild(buttons[i]); 26500 } 26501 } else { 26502 var elt = this.getButtonElement(button); 26503 if (elt) this.getOverlayElement().removeChild(elt); 26504 } 26505}; 26506/** Add a button element 26507 * @param {*} button 26508 * @param {string} options.className button class name 26509 * @param {DOMElement|string} options.html button content 26510 * @param {function} options.click onclick function 26511 * @param {*} options.on an object with 26512 * @param {boolean} options.before 26513 */ 26514ol.interaction.TouchCursor.prototype.addButton = function (b) { 26515 var buttons = this.getOverlayElement().getElementsByClassName('ol-button'); 26516 var max = (this.get('maxButtons') || 5); 26517 if (buttons.length >= max) { 26518 console.error('[ol/interaction/TouchCursor~addButton] too many button on the cursor (max='+max+')...') 26519 return; 26520 } 26521 var button = ol.ext.element.create('DIV', { 26522 className: ((b.className||'')+' ol-button').trim(), 26523 html: ol.ext.element.create('DIV', { html: b.html }), 26524 click: b.click, 26525 on: b.on 26526 }); 26527 if (!b.before || buttons.length===0) this.getOverlayElement().appendChild(button); 26528 else this.getOverlayElement().insertBefore(button, buttons[0]); 26529 // Reorder buttons 26530 var start = buttons.length >= max ? 0 : 1; 26531 for (var i=0; i<buttons.length; i++) { 26532 buttons[i].className = buttons[i].className.replace(/ol-button-\d/g, '').trim() + ' ol-button-' + (i+start); 26533 } 26534}; 26535 26536/* Copyright (c) 2016 Jean-Marc VIGLINO, 26537 released under the CeCILL-B license (French BSD license)
26538 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 26539*/ 26540/** TouchCursor interaction + ModifyFeature 26541 * @constructor 26542 * @extends {ol.interaction.TouchCursor} 26543 * @fires drawend 26544 * @fires change:type 26545 * @param {olx.interaction.InteractionOptions} options Options 26546 * @param {string} options.className cursor class name 26547 * @param {ol.coordinate} options.coordinate cursor position 26548 * @param {string} options.type geometry type 26549 * @param {Array<string>} options.types geometry types avaliable, default none 26550 * @param {ol.source.Vector} options.source Destination source for the drawn features 26551 * @param {ol.Collection<ol.Feature>} options.features Destination collection for the drawn features 26552 * @param {number} options.clickTolerance The maximum distance in pixels for "click" event to add a point/vertex to the geometry being drawn. default 6 26553 * @param {number} options.snapTolerance Pixel distance for snapping to the drawing finish, default 12 26554 * @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. 26555 * @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. 26556 * @param {ol.style.Style} options.style Style for sketch features. 26557 * @param {function} options.geometryFunction Function that is called when a geometry's coordinates are updated. 26558 * @param {string} options.geometryName Geometry name to use for features created by the draw interaction. 26559 * @param {boolean} options.wrapX Wrap the world horizontally on the sketch overlay, default false 26560 */ 26561ol.interaction.TouchCursorDraw = function(options) { 26562 options = options || {}; 26563 // Draw 26564 var sketch = this.sketch = new ol.layer.SketchOverlay({ 26565 type: options.type 26566 }); 26567 sketch.on('drawend', function(e) { 26568 if (e.valid && options.source) options.source.addFeature(e.feature); 26569 this.getOverlayElement().classList.add('nodrawing'); 26570 this.dispatchEvent(e); 26571 }.bind(this)); 26572 sketch.on('drawstart', function(e) { 26573 this.getOverlayElement().classList.remove('nodrawing'); 26574 this.dispatchEvent(e); 26575 }.bind(this)); 26576 sketch.on('drawabort', function(e) { 26577 this.getOverlayElement().classList.add('nodrawing'); 26578 this.dispatchEvent(e); 26579 }.bind(this)); 26580 // Create cursor 26581 ol.interaction.TouchCursor.call(this, { 26582 className: options.className, 26583 coordinate: options.coordinate, 26584 }); 26585 this.getOverlayElement().classList.add('nodrawing'); 26586 this.set('types', options.types); 26587 this.setType(options.type); 26588 this.on('click', function() { 26589 this.sketch.addPoint(this.getPosition()); 26590 }.bind(this)) 26591 this.on('dragging', function() { 26592 this.sketch.setPosition(this.getPosition()); 26593 }.bind(this)) 26594}; 26595ol.ext.inherits(ol.interaction.TouchCursorDraw, ol.interaction.TouchCursor); 26596/** 26597 * Remove the interaction from its current map, if any, and attach it to a new 26598 * map, if any. Pass `null` to just remove the interaction from the current map. 26599 * @param {_ol_Map_} map Map. 26600 * @api stable 26601 */ 26602ol.interaction.TouchCursorDraw.prototype.setMap = function(map) { 26603 ol.interaction.TouchCursor.prototype.setMap.call (this, map); 26604 this.sketch.setMap(map); 26605 if (map) { 26606 this._listeners.movend = map.on('moveend', function() { 26607 this.sketch.setPosition(this.getPosition()) 26608 }.bind(this)) 26609 } 26610}; 26611/** 26612 * Activate or deactivate the interaction. 26613 * @param {boolean} active Active. 26614 * @param {ol.coordinate|null} position position of the cursor (when activating), default viewport center. 26615 * @observable 26616 * @api 26617 */ 26618ol.interaction.TouchCursorDraw.prototype.setActive = function(b, position) { 26619 ol.interaction.TouchCursor.prototype.setActive.call (this, b, position); 26620 this.sketch.abortDrawing(); 26621 this.sketch.setPosition(position); 26622 this.sketch.setVisible(b); 26623}; 26624/** 26625 * Set Geometry type 26626 * @param {string} type Geometry type 26627 */ 26628ol.interaction.TouchCursorDraw.prototype.setType = function(type) { 26629 this.removeButton(); 26630 var sketch = this.sketch; 26631 this.getOverlayElement().classList.remove(sketch.getGeometryType()); 26632 // Set type 26633 var oldValue = sketch.setGeometryType(); 26634 type = sketch.setGeometryType(type); 26635 this.getOverlayElement().classList.add(type); 26636 this.dispatchEvent({ 26637 type: 'change:type', 26638 oldValue: oldValue 26639 }); 26640 // Next type 26641 var types = this.get('types'); 26642 if (types && types.length) { 26643 var next = types[(types.indexOf(type) + 1) % types.length]; 26644 this.addButton({ 26645 className: 'ol-button-type '+next, 26646 click: function() { 26647 this.setType(next) 26648 }.bind(this) 26649 }); 26650 } 26651 // Add buttons 26652 if (type !== 'Point') { 26653 // Cancel drawing 26654 this.addButton({ 26655 className: 'ol-button-x', 26656 click: function() { 26657 sketch.abortDrawing(); 26658 } 26659 }); 26660 if (type !== 'Circle') { 26661 // Add a new point (nothing to do, just click) 26662 this.addButton({ 26663 className: 'ol-button-check', 26664 click: function() { 26665 sketch.finishDrawing(true); 26666 } 26667 }); 26668 // Remove last point 26669 this.addButton({ 26670 className: 'ol-button-remove', 26671 click: function() { 26672 sketch.removeLastPoint(); 26673 } 26674 }); 26675 } 26676 } 26677}; 26678/** Get geometry type 26679 */ 26680ol.interaction.TouchCursorDraw.prototype.getType = function() { 26681 return this.sketch.getGeometryType(); 26682}; 26683 26684/* Copyright (c) 2016 Jean-Marc VIGLINO, 26685 released under the CeCILL-B license (French BSD license)
26686 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 26687*/ 26688/** TouchCursor interaction + ModifyFeature 26689 * @constructor 26690 * @extends {ol.interaction.TouchCursor} 26691 * @param {olx.interaction.InteractionOptions} options Options 26692 * @param {string} options.className cursor class name 26693 * @param {ol.coordinate} options.coordinate cursor position 26694 * @param {ol.source.Vector} options.source a source to modify (configured with useSpatialIndex set to true) 26695 * @param {ol.source.Vector|Array<ol.source.Vector>} options.sources a list of source to modify (configured with useSpatialIndex set to true) 26696 * @param {ol.Collection.<ol.Feature>} options.features collection of feature to modify 26697 * @param {function|undefined} options.filter a filter that takes a feature and return true if it can be modified, default always true. 26698 * @param {number} pixelTolerance Pixel tolerance for considering the pointer close enough to a segment or vertex for editing, default 10 26699 * @param {ol.style.Style | Array<ol.style.Style> | undefined} options.style Style for the sketch features. 26700 * @param {boolean} options.wrapX Wrap the world horizontally on the sketch overlay, default false 26701 */ 26702ol.interaction.TouchCursorModify = function(options) { 26703 options = options || {}; 26704 var drag = false; // enable drag 26705 var dragging = false; // dragging a point 26706 var del = false; // deleting a point 26707 // Modify interaction 26708 var mod = this._modify = new ol.interaction.ModifyFeature ({ 26709 source: options.source, 26710 sources: options.sources, 26711 features: options.features, 26712 pixelTolerance: options.pixelTolerance, 26713 filter: options.filter, 26714 style: options.style || [ 26715 new ol.style.Style({ 26716 image: new ol.style.RegularShape ({ 26717 points: 4, 26718 radius: 10, 26719 radius2: 0, 26720 stroke: new ol.style.Stroke({ 26721 color: [255,255,255, .5], 26722 width: 3 26723 }) 26724 }) 26725 }), 26726 new ol.style.Style({ 26727 image: new ol.style.RegularShape ({ 26728 points: 4, 26729 radius: 10, 26730 radius2: 0, 26731 stroke: new ol.style.Stroke({ 26732 color: [0, 153, 255, 1], 26733 width: 1.25 26734 }) 26735 }) 26736 }) 26737 ], 26738 wrapX: options.wrapX, 26739 condition: function(e) { 26740 return e.dragging || dragging; 26741 }, 26742 deleteCondition: function() { 26743 return del; 26744 } 26745 }); 26746 ol.interaction.TouchCursor.call(this, { 26747 className: ('disable '+options.className).trim(), 26748 coordinate: options.coordinate, 26749 buttons: [{ 26750 // Dragging button 26751 className: 'ol-button-move', 26752 on: { 26753 pointerdown: function() { drag = true; }, 26754 pointerup: function() { drag = false; } 26755 } 26756 }, { 26757 // Add a new point to a line 26758 className: 'ol-button-add', 26759 click: function() { 26760 dragging = true; 26761 mod.handleDownEvent(this._lastEvent); 26762 mod.handleUpEvent(this._lastEvent); 26763 dragging = false; 26764 }.bind(this) 26765 }, { 26766 // Remove a point 26767 className: 'ol-button-remove', 26768 click: function() { 26769 del = true; 26770 mod.handleDownEvent(this._lastEvent); 26771 del = false; 26772 }.bind(this) 26773 } 26774 ] 26775 }); 26776 // Show when modification is active 26777 mod.on('select', function(e) { 26778 if (e.selected.length) { 26779 this.getOverlayElement().classList.remove('disable') 26780 } else { 26781 this.getOverlayElement().classList.add('disable') 26782 } 26783 }.bind(this)); 26784 // Handle dragging, prevent drag outside the control 26785 this.on('dragstart', function() { 26786 if (drag) { 26787 mod.handleDownEvent(this._lastEvent); 26788 } 26789 }.bind(this)); 26790 this.on('dragging', function(e) { 26791 if (drag) mod.handleDragEvent(e); 26792 }); 26793 this.on('dragend', function(e) { 26794 mod.handleUpEvent(e); 26795 drag = false; 26796 }); 26797}; 26798ol.ext.inherits(ol.interaction.TouchCursorModify, ol.interaction.TouchCursor); 26799/** 26800 * Remove the interaction from its current map, if any, and attach it to a new 26801 * map, if any. Pass `null` to just remove the interaction from the current map. 26802 * @param {_ol_Map_} map Map. 26803 * @api stable 26804 */ 26805ol.interaction.TouchCursorModify.prototype.setMap = function(map) { 26806 if (this.getMap()) { 26807 this.getMap().removeInteraction(this._modify); 26808 } 26809 if (map) { 26810 map.addInteraction(this._modify); 26811 } 26812 ol.interaction.TouchCursor.prototype.setMap.call (this, map); 26813}; 26814/** 26815 * Activate or deactivate the interaction. 26816 * @param {boolean} active Active. 26817 * @param {ol.coordinate|null} position position of the cursor (when activating), default viewport center. 26818 * @observable 26819 * @api 26820 */ 26821ol.interaction.TouchCursorModify.prototype.setActive = function(b, position) { 26822 ol.interaction.TouchCursor.prototype.setActive.call (this, b, position); 26823 this._modify.setActive(b); 26824}; 26825/** 26826 * Get the modify interaction. 26827 * @retunr {ol.interaction.ModifyFeature} 26828 * @observable 26829 * @api 26830 */ 26831ol.interaction.TouchCursorModify.prototype.getInteraction = function() { 26832 return this._modify; 26833}; 26834 26835/* Copyright (c) 2016 Jean-Marc VIGLINO, 26836 released under the CeCILL-B license (French BSD license)
26837 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 26838*/ 26839/** A TouchCursor to select objects on hovering the cursor 26840 * @constructor 26841 * @extends {ol.interaction.DragOverlay} 26842 * @param {olx.interaction.InteractionOptions} options Options 26843 * @param {string} options.className cursor class name 26844 * @param {ol.coordinate} options.coordinate position of the cursor 26845 */ 26846ol.interaction.TouchCursorSelect = function(options) { 26847 options = options || {}; 26848 ol.interaction.TouchCursor.call(this, { 26849 className: 'ol-select '+(options.className || ''), 26850 coordinate: options.coordinate 26851 }); 26852 this._selection = null; 26853 this._layerFilter = options.layerFilter; 26854 this._filter = options.filter; 26855 this._style = options.style || ol.style.Style.defaultStyle(true); 26856 this.set('hitTolerance', options.hitTolerance || 0); 26857 this.on(['change:active', 'dragging'], function() { this.select() }); 26858}; 26859ol.ext.inherits(ol.interaction.TouchCursorSelect, ol.interaction.TouchCursor); 26860/** 26861 * Remove the interaction from its current map, if any, and attach it to a new 26862 * map, if any. Pass `null` to just remove the interaction from the current map. 26863 * @param {_ol_Map_} map Map. 26864 * @api stable 26865 */ 26866ol.interaction.TouchCursorSelect.prototype.setMap = function(map) { 26867 ol.interaction.TouchCursor.prototype.setMap.call (this, map); 26868 if (map) { 26869 // Select on move end 26870 this._listeners.movend = map.on('moveend', function() { 26871 this.select() 26872 }.bind(this)) 26873 } 26874}; 26875/** Get current selection 26876 * @return {ol.Feature|null} 26877 */ 26878ol.interaction.TouchCursorSelect.prototype.getSelection = function() { 26879 return this._selection ? this._selection.feature : null; 26880}; 26881/** Set position 26882 * @param {ol.coordinate} coord 26883 */ 26884ol.interaction.TouchCursorSelect.prototype.setPosition = function(coord) { 26885 ol.interaction.TouchCursor.prototype.setPosition.call (this, coord); 26886 this.select(); 26887}; 26888/** Select feature 26889 * @param {ol.Feature|undefined} f a feature to select or select at the cursor position 26890 */ 26891ol.interaction.TouchCursorSelect.prototype.select = function(f) { 26892 var current = this._selection; 26893 if (this.getActive() && this.getPosition()) { 26894 if (!f) { 26895 var sel = this.getMap().getFeaturesAtPixel(this.getPixel(), { 26896 layerFilter: this._layerFilter, 26897 filter: this._filter, 26898 hitTolerance: this.get('hitTolerance') 26899 }); 26900 f = sel ? sel[0] : null; 26901 } 26902 if (f) { 26903 if (current && f === current.feature) { 26904 current = null; 26905 } else { 26906 this._selection = { 26907 feature: f, 26908 style: f.getStyle() 26909 } 26910 f.setStyle(this._style); 26911 this.dispatchEvent({ type:'select', selected: [f], deselected: current ? [current.feature] : [] }); 26912 } 26913 } else { 26914 this._selection = null; 26915 this.dispatchEvent({ type:'select', selected: [], deselected: current ? [current.feature] : [] }); 26916 } 26917 } else { 26918 this._selection = null; 26919 this.dispatchEvent({ type:'select', selected: [], deselected: current ? [current.feature] : [] }); 26920 } 26921 // Restore current style 26922 if (current) { 26923 current.feature.setStyle(current.style); 26924 } 26925 // 26926 if (this._selection) this.getOverlayElement().classList.remove('disable'); 26927 else this.getOverlayElement().classList.add('disable'); 26928}; 26929 26930/** Interaction rotate 26931 * @constructor 26932 * @extends {ol.interaction.Pointer} 26933 * @fires select | rotatestart | rotating | rotateend | translatestart | translating | translateend | scalestart | scaling | scaleend 26934 * @param {any} options 26935 * @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. 26936 * @param {Array<ol.Layer>} options.layers array of layers to transform, 26937 * @param {ol.Collection<ol.Feature>} options.features collection of feature to transform, 26938 * @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. 26939 * @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. 26940 * @param {number | undefined} options.hitTolerance Tolerance to select feature in pixel, default 0 26941 * @param {bool} options.translateFeature Translate when click on feature 26942 * @param {bool} options.translate Can translate the feature 26943 * @param {bool} options.translateBBox Enable translate when the user drags inside the bounding box 26944 * @param {bool} options.stretch can stretch the feature 26945 * @param {bool} options.scale can scale the feature 26946 * @param {bool} options.rotate can rotate the feature 26947 * @param {bool} options.noFlip prevent the feature geometry to flip, default false 26948 * @param {bool} options.selection the intraction handle selection/deselection, if not use the select prototype to add features to transform, default true 26949 * @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. 26950 * @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. 26951 * @param {boolean} options.enableRotatedTransform Enable transform when map is rotated 26952 * @param {boolean} [options.keepRectangle=false] keep rectangle when possible 26953 * @param {*} options.style list of ol.style for handles 26954 * @param {number|Array<number>|function}
26954 [options.pointRadius=0] radius for points or a function that takes a feature and returns the radius (or [radiusX, radiusY]). If not null show handles to transform the points 26955 */ 26956ol.interaction.Transform = function(options) { 26957 if (!options) options = {}; 26958 var self = this; 26959 this.selection_ = new ol.Collection(); 26960 // Create a new overlay layer for the sketch 26961 this.handles_ = new ol.Collection(); 26962 this.overlayLayer_ = new ol.layer.Vector({ 26963 source: new ol.source.Vector({ 26964 features: this.handles_, 26965 useSpatialIndex: false, 26966 wrapX: false // For vector editing across the -180° and 180° meridians to work properly, this should be set to false 26967 }), 26968 name:'Transform overlay', 26969 displayInLayerSwitcher: false, 26970 // Return the style according to the handle type 26971 style: function (feature) { 26972 return (self.style[(feature.get('handle')||'default')+(feature.get('constraint')||'')+(feature.get('option')||'')]); 26973 }, 26974 }); 26975 // Extend pointer 26976 ol.interaction.Pointer.call(this, { 26977 handleDownEvent: this.handleDownEvent_, 26978 handleDragEvent: this.handleDragEvent_, 26979 handleMoveEvent: this.handleMoveEvent_, 26980 handleUpEvent: this.handleUpEvent_ 26981 }); 26982 // Collection of feature to transform 26983 this.features_ = options.features; 26984 // Filter or list of layers to transform 26985 if (typeof(options.filter)==='function') this._filter = options.filter; 26986 this.layers_ = options.layers ? (options.layers instanceof Array) ? options.layers:[options.layers] : null; 26987 this._handleEvent = options.condition || function() { return true; }; 26988 this.addFn_ = options.addCondition || function() { return false; }; 26989 this.setPointRadius(options.pointRadius); 26990 /* Translate when click on feature */ 26991 this.set('translateFeature', (options.translateFeature!==false)); 26992 /* Can translate the feature */ 26993 this.set('translate', (options.translate!==false)); 26994 /* Translate when click on the bounding box */ 26995 this.set('translateBBox', (options.translateBBox===true)); 26996 /* Can stretch the feature */ 26997 this.set('stretch', (options.stretch!==false)); 26998 /* Can scale the feature */ 26999 this.set('scale', (options.scale!==false)); 27000 /* Can rotate the feature */ 27001 this.set('rotate', (options.rotate!==false)); 27002 /* Keep aspect ratio */ 27003 this.set('keepAspectRatio', (options.keepAspectRatio || function(e){ return e.originalEvent.shiftKey })); 27004 /* Modify center */ 27005 this.set('modifyCenter', (options.modifyCenter || function(e){ return e.originalEvent.metaKey || e.originalEvent.ctrlKey })); 27006 /* Prevent flip */ 27007 this.set('noFlip', (options.noFlip || false)); 27008 /* Handle selection */ 27009 this.set('selection', (options.selection !== false)); 27010 /* */ 27011 this.set('hitTolerance', (options.hitTolerance || 0)); 27012 /* Enable view rotated transforms */ 27013 this.set('enableRotatedTransform', (options.enableRotatedTransform || false)); 27014 /* Keep rectangle angles 90 degrees */ 27015 this.set('keepRectangle', (options.keepRectangle || false)); 27016 // Force redraw when changed 27017 this.on ('propertychange', function() { 27018 this.drawSketch_(); 27019 }); 27020 // setstyle 27021 this.setDefaultStyle(); 27022}; 27023ol.ext.inherits(ol.interaction.Transform, ol.interaction.Pointer); 27024/** Cursors for transform 27025*/ 27026ol.interaction.Transform.prototype.Cursors = { 27027 'default': 'auto', 27028 'select': 'pointer', 27029 'translate': 'move', 27030 'rotate': 'move', 27031 'rotate0': 'move', 27032 'scale': 'nesw-resize', 27033 'scale1': 'nwse-resize', 27034 'scale2': 'nesw-resize', 27035 'scale3': 'nwse-resize', 27036 'scalev': 'ew-resize', 27037 'scaleh1': 'ns-resize', 27038 'scalev2': 'ew-resize', 27039 'scaleh3': 'ns-resize' 27040}; 27041/** 27042 * Remove the interaction from its current map, if any, and attach it to a new 27043 * map, if any. Pass `null` to just remove the interaction from the current map. 27044 * @param {ol.Map} map Map. 27045 * @api stable 27046 */ 27047ol.interaction.Transform.prototype.setMap = function(map) { 27048 var oldMap = this.getMap(); 27049 if (oldMap) { 27050 var targetElement = oldMap.getTargetElement(); 27051 oldMap.removeLayer(this.overlayLayer_); 27052 if (this.previousCursor_ && targetElement) { 27053 targetElement.style.cursor = this.previousCursor_; 27054 } 27055 this.previousCursor_ = undefined; 27056 } 27057 ol.interaction.Pointer.prototype.setMap.call (this, map);
27058 this.overlayLayer_.setMap(map); 27059 if (map === null) { 27060 this.select(null); 27061 } 27062 if (map !== null) { 27063 this.isTouch = /touch/.test(map.getViewport().className); 27064 this.setDefaultStyle(); 27065 } 27066}; 27067/** 27068 * Activate/deactivate interaction 27069 * @param {bool} 27070 * @api stable 27071 */ 27072ol.interaction.Transform.prototype.setActive = function(b) { 27073 this.select(null); 27074 this.overlayLayer_.setVisible(b); 27075 ol.interaction.Pointer.prototype.setActive.call (this, b); 27076}; 27077/** Set default sketch style 27078 * @param {Object|undefined} options 27079 * @param {ol.style.Stroke} stroke stroke style for selection rectangle 27080 * @param {ol.style.Fill} fill fill style for selection rectangle 27081 * @param {ol.style.Stroke} pointStroke stroke style for handles 27082 * @param {ol.style.Fill} pointFill fill style for handles 27083 */ 27084ol.interaction.Transform.prototype.setDefaultStyle = function(options) { 27085 options = options || {} 27086 // Style 27087 var stroke = options.pointStroke || new ol.style.Stroke({ color: [255,0,0,1], width: 1 }); 27088 var strokedash = options.stroke || new ol.style.Stroke({ color: [255,0,0,1], width: 1, lineDash:[4,4] }); 27089 var fill0 = options.fill || new ol.style.Fill({ color:[255,0,0,0.01] }); 27090 var fill = options.pointFill || new ol.style.Fill({ color:[255,255,255,0.8] }); 27091 var circle = new ol.style.RegularShape({ 27092 fill: fill, 27093 stroke: stroke, 27094 radius: this.isTouch ? 12 : 6, 27095 displacement: this.isTouch ? [24, -24] : [12, -12], 27096 points: 15 27097 }); 27098 // Old version with no displacement 27099 if (!circle.setDisplacement) circle.getAnchor()[0] = this.isTouch ? -10 : -5; 27100 var bigpt = new ol.style.RegularShape({ 27101 fill: fill, 27102 stroke: stroke, 27103 radius: this.isTouch ? 16 : 8, 27104 points: 4, 27105 angle: Math.PI/4 27106 }); 27107 var smallpt = new ol.style.RegularShape({ 27108 fill: fill, 27109 stroke: stroke, 27110 radius: this.isTouch ? 12 : 6, 27111 points: 4, 27112 angle: Math.PI/4 27113 }); 27114 function createStyle (img, stroke, fill) { 27115 return [ new ol.style.Style({image:img, stroke:stroke, fill:fill}) ]; 27116 } 27117 /** Style for handles */ 27118 this.style = { 27119 'default': createStyle (bigpt, strokedash, fill0), 27120 'translate': createStyle (bigpt, stroke, fill), 27121 'rotate': createStyle (circle, stroke, fill), 27122 'rotate0': createStyle (bigpt, stroke, fill), 27123 'scale': createStyle (bigpt, stroke, fill), 27124 'scale1': createStyle (bigpt, stroke, fill), 27125 'scale2': createStyle (bigpt, stroke, fill), 27126 'scale3': createStyle (bigpt, stroke, fill), 27127 'scalev': createStyle (smallpt, stroke, fill), 27128 'scaleh1': createStyle (smallpt, stroke, fill), 27129 'scalev2': createStyle (smallpt, stroke, fill), 27130 'scaleh3': createStyle (smallpt, stroke, fill), 27131 }; 27132 this.drawSketch_(); 27133} 27134/** 27135 * Set sketch style. 27136 * @param {style} style Style name: 'default','translate','rotate','rotate0','scale','scale1','scale2','
27136scale3','scalev','scaleh1','scalev2','scaleh3' 27137 * @param {ol.style.Style|Array<ol.style.Style>} olstyle 27138 * @api stable 27139 */ 27140ol.interaction.Transform.prototype.setStyle = function(style, olstyle) { 27141 if (!olstyle) return; 27142 if (olstyle instanceof Array) this.style[style] = olstyle; 27143 else this.style[style] = [ olstyle ]; 27144 for (var i=0; i<this.style[style].length; i++) { 27145 var im = this.style[style][i].getImage(); 27146 if (im) { 27147 if (style == 'rotate') { 27148 im.getAnchor()[0] = -5; 27149 } 27150 if (this.isTouch) im.setScale(1.8); 27151 } 27152 var tx = this.style[style][i].getText(); 27153 if (tx) { 27154 if (style == 'rotate') tx.setOffsetX(this.isTouch ? 14 : 7); 27155 if (this.isTouch) tx.setScale(1.8); 27156 } 27157 } 27158 this.drawSketch_(); 27159}; 27160/** Get Feature at pixel 27161 * @param {ol.Pixel} 27162 * @return {ol.feature} 27163 * @private 27164 */ 27165ol.interaction.Transform.prototype.getFeatureAtPixel_ = function(pixel) { 27166 var self = this; 27167 return this.getMap().forEachFeatureAtPixel(pixel, 27168 function(feature, layer) { 27169 var found = false; 27170 // Overlay ? 27171 if (!layer) { 27172 if (feature===self.bbox_) { 27173 if (self.get('translateBBox')) { 27174 return { feature: feature, handle: 'translate', constraint:'', option: '' }; 27175 } else { 27176 return false; 27177 } 27178 } 27179 self.handles_.forEach (function(f) { if (f===feature) found=true; }); 27180 if (found) return { feature: feature, handle:feature.get('handle'), constraint:feature.get('constraint'), option:feature.get('option') }; 27181 } 27182 // No seletion 27183 if (!self.get('selection')) { 27184 // Return the currently selected feature the user is interacting with. 27185 if (self.selection_.getArray().some(function(f) { return feature === f; })) { 27186 return { feature: feature }; 27187 } 27188 return null; 27189 } 27190 // filter condition 27191 if (self._filter) { 27192 if (self._filter(feature,layer)) return { feature: feature }; 27193 else return null; 27194 } 27195 // feature belong to a layer 27196 else if (self.layers_) { 27197 for (var i=0; i<self.layers_.length; i++) { 27198 if (self.layers_[i]===layer) return { feature: feature }; 27199 } 27200 return null; 27201 } 27202 // feature in the collection 27203 else if (self.features_) { 27204 self.features_.forEach (function(f) { if (f===feature) found=true; }); 27205 if (found) return { feature: feature }; 27206 else return null; 27207 } 27208 // Others 27209 else return { feature: feature }; 27210 }, 27211 { hitTolerance: this.get('hitTolerance') } 27212 ) || {}; 27213} 27214/** Rotate feature from map view rotation 27215 * @param {ol.Feature} f the feature 27216 * @param {boolean} clone clone resulting geom 27217 * @param {ol.geom.Geometry} rotated geometry 27218 */ 27219ol.interaction.Transform.prototype.getGeometryRotateToZero_ = function(f, clone) { 27220 var origGeom = f.getGeometry(); 27221 var viewRotation = this.getMap().getView().getRotation(); 27222 if (viewRotation === 0 || !this.get('enableRotatedTransform')) { 27223 return (clone) ? origGeom.clone() : origGeom; 27224 } 27225 var rotGeom = origGeom.clone(); 27226 rotGeom.rotate(viewRotation * -1, this.getMap().getView().getCenter()); 27227 return rotGeom; 27228}; 27229/** Test if rectangle 27230 * @param {ol.Geometry} geom 27231 * @returns {boolean} 27232 * @private 27233 */ 27234ol.interaction.Transform.prototype._isRectangle = function(geom) { 27235 if (this.get('keepRectangle') && geom.getType() === 'Polygon') { 27236 var coords = geom.getCoordinates()[0]; 27237 return coords.length === 5; 27238 } 27239 return false; 27240}; 27241/** Draw transform sketch 27242* @param {boolean} draw only the center 27243*/ 27244ol.interaction.Transform.prototype.drawSketch_ = function(center) { 27245 var i, f, geom; 27246 var keepRectangle = this.selection_.item(0) && this._isRectangle(this.selection_.item(0).getGeometry()); 27247 this.overlayLayer_.getSource().clear(); 27248 if (!this.selection_.getLength()) return; 27249 var viewRotation = this.getMap().getView().getRotation(); 27250 var ext = this.getGeometryRotateToZero_(this.selection_.item(0)).getExtent(); 27251 var coords; 27252 if (keepRectangle) { 27253 coords = this.getGeometryRotateToZero_(this.selection_.item(0)).getCoordinates()[0].slice(0, 4); 27254 coords.unshift(coords[3]); 27255 } 27256 // Clone and extend 27257 ext = ol.extent.buffer(ext, 0); 27258 this.selection_.forEach(function (f) { 27259 var extendExt = this.getGeometryRotateToZero_(f).getExtent(); 27260 ol.extent.extend(ext, extendExt); 27261 }.bind(this)); 27262 var ptRadius = (this.selection_.getLength() === 1 ? this._pointRadius(this.selection_.item(0)) : 0); 27263 if (ptRadius && !(ptRadius instanceof Array)) ptRadius = [ptRadius, ptRadius]; 27264 if (center===true) { 27265 if (!this.ispt_) { 27266 this.overlayLayer_.getSource().addFeature(new ol.Feature( { geometry: new ol.geom.Point(this.center_), handle:'rotate0' }) ); 27267 geom = ol.geom.Polygon.fromExtent(ext); 27268 if (this.get('enableRotatedTransform') && viewRotation !== 0) { 27269 geom.rotate(viewRotation, this.getMap().getView().getCenter()) 27270 } 27271 f = this.bbox_ = new ol.Feature(geom); 27272 this.overlayLayer_.getSource().addFeature (f); 27273 } 27274 } else { 27275 if (this.ispt_) { 27276 // Calculate extent arround the point
27277 var p = this.getMap().getPixelFromCoordinate([ext[0], ext[1]]); 27278 if (p) { 27279 var dx = ptRadius ? ptRadius[0] || 10 : 10; 27280 var dy = ptRadius ? ptRadius[1] || 10 : 10; 27281 ext = ol.extent.boundingExtent([ 27282 this.getMap().getCoordinateFromPixel([p[0] - dx, p[1] - dy]), 27283 this.getMap().getCoordinateFromPixel([p[0] + dx, p[1] + dy]) 27284 ]); 27285 } 27286 } 27287 geom = keepRectangle ? new ol.geom.Polygon([coords]) : ol.geom.Polygon.fromExtent(ext); 27288 if (this.get('enableRotatedTransform') && viewRotation !== 0) { 27289 geom.rotate(viewRotation, this.getMap().getView().getCenter()) 27290 } 27291 f = this.bbox_ = new ol.Feature(geom); 27292 var features = []; 27293 var g = geom.getCoordinates()[0]; 27294 if (!this.ispt_ || ptRadius) { 27295 features.push(f); 27296 // Middle 27297 if (!this.iscircle_ && !this.ispt_ && this.get('stretch') && this.get('scale')) for (i=0; i<g.length-1; i++) { 27298 f = new ol.Feature( { geometry: new ol.geom.Point([(g[i][0]+g[i+1][0])/2,(g[i][1]+g[i+1][1])/2]), handle:'scale', constraint:i%2?"h":"v", option:i }); 27299 features.push(f); 27300 } 27301 // Handles 27302 if (this.get('scale')) for (i=0; i<g.length-1; i++) { 27303 f = new ol.Feature( { geometry: new ol.geom.Point(g[i]), handle:'scale', option:i }); 27304 features.push(f); 27305 } 27306 // Center 27307 if (this.get('translate') && !this.get('translateFeature')) { 27308 f = new ol.Feature( { geometry: new ol.geom.Point([(g[0][0]+g[2][0])/2, (g[0][1]+g[2][1])/2]), handle:'translate' }); 27309 features.push(f); 27310 } 27311 } 27312 // Rotate 27313 if (!this.iscircle_ && this.get('rotate')) { 27314 f = new ol.Feature( { geometry: new ol.geom.Point(g[3]), handle:'rotate' }); 27315 features.push(f); 27316 } 27317 // Add sketch 27318 this.overlayLayer_.getSource().addFeatures(features); 27319 } 27320}; 27321/** Select a feature to transform 27322* @param {ol.Feature} feature the feature to transform 27323* @param {boolean} add true to add the feature to the selection, default false 27324*/ 27325ol.interaction.Transform.prototype.select = function(feature, add) { 27326 if (!feature) { 27327 this.selection_.clear(); 27328 this.drawSketch_(); 27329 return; 27330 } 27331 if (!feature.getGeometry || !feature.getGeometry()) return; 27332 // Add to selection 27333 if (add) { 27334 this.selection_.push(feature); 27335 } else { 27336 var index = this.selection_.getArray().indexOf(feature); 27337 this.selection_.removeAt(index); 27338 } 27339 this.ispt_ = (this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Point") : false); 27340 this.iscircle_ = (this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Circle") : false); 27341 this.drawSketch_(); 27342 this.watchFeatures_(); 27343 // select event 27344 this.dispatchEvent({ type:'select', feature: feature, features: this.selection_ }); 27345}; 27346/** Update the selection collection. 27347* @param {ol.Collection<ol.Feature>} features the features to transform 27348*/ 27349ol.interaction.Transform.prototype.setSelection = function(features) { 27350 this.selection_.clear(); 27351 features.forEach(function(feature) { 27352 this.selection_.push(feature); 27353 }.bind(this)); 27354 this.ispt_ = (this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Point") : false); 27355 this.iscircle_ = (this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Circle") : false); 27356 this.drawSketch_(); 27357 this.watchFeatures_(); 27358 // select event 27359 this.dispatchEvent({ type:'select', features: this.selection_ }); 27360}; 27361/** Watch selected features 27362 * @private 27363 */ 27364ol.interaction.Transform.prototype.watchFeatures_ = function() { 27365 // Listen to feature modification 27366 if (this._featureListeners) { 27367 this._featureListeners.forEach(function (l) { 27368 ol.Observable.unByKey(l) 27369 }); 27370 } 27371 this._featureListeners = []; 27372 this.selection_.forEach(function(f) { 27373 this._featureListeners.push( 27374 f.on('change', function() { 27375 if (!this.isUpdating_) { 27376 this.drawSketch_(); 27377 } 27378 }.bind(this)) 27379 ); 27380 }.bind(this)); 27381}; 27382/** 27383 * @param {ol.MapBrowserEvent} evt Map browser event. 27384 * @return {boolean} `true` to start the drag sequence. 27385 * @private 27386 */ 27387ol.interaction.Transform.prototype.handleDownEvent_ = function(evt) { 27388 if (!this._handleEvent(evt, this.selection_)) return; 27389 var sel = this.getFeatureAtPixel_(evt.pixel); 27390 var feature = sel.feature; 27391 if (this.selection_.getLength() 27392 && this.selection_.getArray().indexOf(feature) >= 0 27393 && ((this.ispt_ && this.get('translate')) || this.get('translateFeature')) 27394 ){ 27395 sel.handle = 'translate'; 27396 } 27397 if (sel.handle) { 27398 this.mode_ = sel.handle; 27399 this.opt_ = sel.option; 27400 this.constraint_ = sel.constraint; 27401 // Save info 27402 var viewRotation = this.getMap().getView().getRotation(); 27403 this.coordinate_ = evt.coordinate; 27404 this.pixel_ = evt.pixel; 27405 this.geoms_ = []; 27406 this.rotatedGeoms_ = []; 27407 var extent = ol.extent.createEmpty(); 27408 var rotExtent = ol.extent.createEmpty(); 27409 for (var i=0, f; f=this.selection_.item(i); i++) { 27410 this.geoms_.push(f.getGeometry().clone()); 27411 extent = ol.extent.extend(extent, f.getGeometry().getExtent()); 27412 if (this.get('enableRotatedTransform') && viewRotation !== 0) { 27413 var rotGeom = this.getGeometryRotateToZero_(f, true); 27414 this.rotatedGeoms_.push(rotGeom); 27415 rotExtent = ol.extent.extend(rotExtent, rotGeom.getExtent()); 27416 } 27417 } 27418 this.extent_ = (ol.geom.Polygon.fromExtent(extent)).getCoordinates()[0]; 27419 if (this.get('enableRotatedTransform') && viewRotation !== 0) { 27420 this.rotatedExtent_ = (ol.geom.Polygon.fromExtent(rotExtent)).getCoordinates()[0]; 27421 } 27422 if (this.mode_==='rotate') { 27423 this.center_ = this.getCenter() || ol.extent.getCenter(extent); 27424 // we are now rotating (cursor down on rotate mode), so apply the grabbing cursor 27425 var element = evt.map.getTargetElement(); 27426 element.style.cursor = this.Cursors.rotate0; 27427 this.previousCursor_ = element.style.cursor; 27428 } else { 27429 this.center_ = ol.extent.getCenter(extent); 27430 } 27431 this.angle_ = Math.atan2(this.center_[1]-evt.coordinate[1], this.center_[0]-evt.coordinate[0]); 27432 this.dispatchEvent({ 27433 type: this.mode_+'start', 27434 feature: this.selection_.item(0), // backward compatibility 27435 features: this.selection_, 27436 pixel: evt.pixel, 27437 coordinate: evt.coordinate 27438 }); 27439 return true; 27440 } 27441 else if (this.get('selection')) { 27442 if (feature){ 27443 if (!this.addFn_(evt)) this.selection_.clear(); 27444 var index = this.selection_.getArray().indexOf(feature); 27445 if (index < 0) this.selection_.push(feature); 27446 else this.selection_.removeAt(index); 27447 } else { 27448 this.selection_.clear(); 27449 } 27450 this.ispt_ = this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Point") : false;
27451 this.iscircle_ = (this.selection_.getLength()===1 ? (this.selection_.item(0).getGeometry().getType() == "Circle") : false); 27452 this.drawSketch_(); 27453 this.watchFeatures_(); 27454 this.dispatchEvent({ type:'select', feature: feature, features: this.selection_, pixel: evt.pixel, coordinate: evt.coordinate }); 27455 return false; 27456 } 27457}; 27458/** 27459 * Get features to transform 27460 * @return {ol.Collection<ol.Feature>} 27461 */ 27462ol.interaction.Transform.prototype.getFeatures = function() { 27463 return this.selection_; 27464}; 27465/** 27466 * Get the rotation center 27467 * @return {ol.coordinates|undefined} 27468 */ 27469ol.interaction.Transform.prototype.getCenter = function() { 27470 return this.get('center'); 27471}; 27472/** 27473 * Set the rotation center 27474 * @param {ol.coordinates|undefined} c the center point, default center on the objet 27475 */ 27476ol.interaction.Transform.prototype.setCenter = function(c) { 27477 return this.set('center', c); 27478} 27479function projectVectorOnVector(displacement_vector, base) { 27480 var k = (displacement_vector[0] * base[0] + displacement_vector[1] * base[1]) / (base[0] * base[0] + base[1] * base[1]); 27481 return [base[0] * k, base[1] * k]; 27482} 27483function countVector(start, end) { 27484 return [end[0] - start[0], end[1] - start[1]]; 27485} 27486function movePoint(point, displacementVector) { 27487 return [point[0]+displacementVector[0], point[1]+displacementVector[1]]; 27488} 27489/** 27490 * @param {ol.MapBrowserEvent} evt Map browser event. 27491 * @private 27492 */ 27493ol.interaction.Transform.prototype.handleDragEvent_ = function(evt) { 27494 if (!this._handleEvent(evt, this.features_)) return; 27495 var viewRotation = this.getMap().getView().getRotation(); 27496 var i, j, f, geometry; 27497 var pt0 = [this.coordinate_[0], this.coordinate_[1]]; 27498 var pt = [evt.coordinate[0], evt.coordinate[1]]; 27499 this.isUpdating_ = true; 27500 switch (this.mode_) { 27501 case 'rotate': { 27502 var a = Math.atan2(this.center_[1]-pt[1], this.center_[0]-pt[0]); 27503 if (!this.ispt) { 27504 // var geometry = this.geom_.clone(); 27505 // geometry.rotate(a-this.angle_, this.center_); 27506 // this.feature_.setGeometry(geometry); 27507 for (i=0, f; f=this.selection_.item(i); i++) { 27508 geometry = this.geoms_[i].clone(); 27509 geometry.rotate(a - this.angle_, this.center_); 27510 // bug: ol, bad calculation circle geom extent 27511 if (geometry.getType() == 'Circle') geometry.setCenterAndRadius(geometry.getCenter(), geometry.getRadius()); 27512 f.setGeometry(geometry); 27513 } 27514 } 27515 this.drawSketch_(true); 27516 this.dispatchEvent({ 27517 type:'rotating', 27518 feature: this.selection_.item(0), 27519 features: this.selection_, 27520 angle: a-this.angle_, 27521 pixel: evt.pixel, 27522 coordinate: evt.coordinate 27523 }); 27524 break; 27525 } 27526 case 'translate': { 27527 var deltaX = pt[0] - pt0[0]; 27528 var deltaY = pt[1] - pt0[1]; 27529 //this.feature_.getGeometry().translate(deltaX, deltaY); 27530 for (i=0, f; f=this.selection_.item(i); i++) { 27531 f.getGeometry().translate(deltaX, deltaY); 27532 } 27533 this.handles_.forEach(function(f) { 27534 f.getGeometry().translate(deltaX, deltaY); 27535 }); 27536 this.coordinate_ = evt.coordinate; 27537 this.dispatchEvent({ 27538 type:'translating', 27539 feature: this.selection_.item(0), 27540 features: this.selection_, 27541 delta:[deltaX,deltaY], 27542 pixel: evt.pixel, 27543 coordinate: evt.coordinate 27544 }); 27545 break; 27546 } 27547 case 'scale': { 27548 var center = this.center_; 27549 if (this.get('modifyCenter')(evt)) { 27550 var extentCoordinates = this.extent_; 27551 if (this.get('enableRotatedTransform') && viewRotation !== 0) { 27552 extentCoordinates = this.rotatedExtent_; 27553 } 27554 center = extentCoordinates[(Number(this.opt_)+2)%4]; 27555 } 27556 var keepRectangle = (this.geoms_.length == 1 && this._isRectangle(this.geoms_[0])); 27557 var stretch = this.constraint_; 27558 var opt = this.opt_; 27559 var downCoordinate = this.coordinate_; 27560 var dragCoordinate = evt.coordinate; 27561 if (this.get('enableRotatedTransform') && viewRotation !== 0) { 27562 var downPoint = new ol.geom.Point(this.coordinate_); 27563 downPoint.rotate(viewRotation * -1, center); 27564 downCoordinate = downPoint.getCoordinates(); 27565 var dragPoint = new ol.geom.Point(evt.coordinate); 27566 dragPoint.rotate(viewRotation * -1, center); 27567 dragCoordinate = dragPoint.getCoordinates(); 27568 } 27569 var scx = ((dragCoordinate)[0] - (center)[0]) / (downCoordinate[0] - (center)[0]); 27570 var scy = ((dragCoordinate)[1] - (center)[1]) / (downCoordinate[1] - (center)[1]); 27571 var displacementVector = [dragCoordinate[0] - downCoordinate[0], (dragCoordinate)[1] - downCoordinate[1]]; 27572 if (this.get('enableRotatedTransform') && viewRotation !== 0) { 27573 var centerPoint = new ol.geom.Point(center); 27574 centerPoint.rotate(viewRotation * -1, this.getMap().getView().getCenter()); 27575 center = centerPoint.getCoordinates(); 27576 } 27577 if (this.get('noFlip')) { 27578 if (scx<0) scx=-scx; 27579 if (scy<0) scy=-scy; 27580 } 27581 if (this.constraint_) { 27582 if (this.constraint_=="h") scx=1; 27583 else scy=1; 27584 } else { 27585 if (this.get('keepAspectRatio')(evt)) { 27586 scx = scy = Math.min(scx,scy); 27587 } 27588 } 27589 for (i=0, f; f=this.selection_.item(i); i++) { 27590 geometry = (viewRotation === 0 || !this.get('enableRotatedTransform')) ? this.geoms_[i].clone() : this.rotatedGeoms_[i].clone(); 27591 geometry.applyTransform(function(g1, g2, dim) { 27592 if (dim<2) return g2; 27593 if (!keepRectangle) { 27594 for (j=0; j<g1.length; j+=dim) { 27595 if (scx!=1) g2[j] = center[0] + (g1[j]-center[0])*scx; 27596 if (scy!=1) g2[j+1] = center[1] + (g1[j+1]-center[1])*scy; 27597 } 27598 } else { 27599 var pointArray = [[6], [0, 8], [2], [4]] 27600 var pointA = [g1[0], g1[1]]; 27601 var pointB = [g1[2], g1[3]]; 27602 var pointC = [g1[4], g1[5]]; 27603 var pointD = [g1[6], g1[7]]; 27604 var pointA1 = [g1[8], g1[9]]; 27605 if (stretch) { 27606 var base = (opt % 2 === 0) ? countVector(pointA, pointB) : countVector(pointD, pointA); 27607 var projectedVector = projectVectorOnVector(displacementVector, base); 27608 var nextIndex = opt+1 < pointArray.length ? opt+1 : 0; 27609 var coordsToChange = [...pointArray[opt], ...pointArray[nextIndex]]; 27610 for (j = 0; j < g1.length; j += dim) {
27611 g2[j] = coordsToChange.includes(j) ? g1[j] + projectedVector[0] : g1[j]; 27612 g2[j + 1] = coordsToChange.includes(j) ? g1[j + 1] + projectedVector[1] : g1[j + 1]; 27613 } 27614 } else { 27615 var projectedLeft, projectedRight; 27616 switch (opt) { 27617 case 0: 27618 displacementVector = countVector(pointD, dragCoordinate); 27619 projectedLeft = projectVectorOnVector(displacementVector, countVector(pointC, pointD)); 27620 projectedRight = projectVectorOnVector(displacementVector, countVector(pointA, pointD)); 27621 [g2[0], g2[1]] = movePoint(pointA, projectedLeft); 27622 [g2[4], g2[5]] = movePoint(pointC, projectedRight); 27623 [g2[6], g2[7]] = movePoint(pointD, displacementVector); 27624 [g2[8], g2[9]] = movePoint(pointA1, projectedLeft); 27625 break; 27626 case 1: 27627 displacementVector = countVector(pointA, dragCoordinate); 27628 projectedLeft = projectVectorOnVector(displacementVector, countVector(pointD, pointA)); 27629 projectedRight = projectVectorOnVector(displacementVector, countVector(pointB, pointA)); 27630 [g2[0], g2[1]] = movePoint(pointA, displacementVector); 27631 [g2[2], g2[3]] = movePoint(pointB, projectedLeft); 27632 [g2[6], g2[7]] = movePoint(pointD, projectedRight); 27633 [g2[8], g2[9]] = movePoint(pointA1, displacementVector); 27634 break; 27635 case 2: 27636 displacementVector = countVector(pointB, dragCoordinate); 27637 projectedLeft = projectVectorOnVector(displacementVector, countVector(pointA, pointB)); 27638 projectedRight = projectVectorOnVector(displacementVector, countVector(pointC, pointB)); 27639 [g2[0], g2[1]] = movePoint(pointA, projectedRight); 27640 [g2[2], g2[3]] = movePoint(pointB, displacementVector); 27641 [g2[4], g2[5]] = movePoint(pointC, projectedLeft); 27642 [g2[8], g2[9]] = movePoint(pointA1, projectedRight); 27643 break; 27644 case 3: 27645 displacementVector = countVector(pointC, dragCoordinate); 27646 projectedLeft = projectVectorOnVector(displacementVector, countVector(pointB, pointC)); 27647 projectedRight = projectVectorOnVector(displacementVector, countVector(pointD, pointC)); 27648 [g2[2], g2[3]] = movePoint(pointB, projectedRight); 27649 [g2[4], g2[5]] = movePoint(pointC, displacementVector); 27650 [g2[6], g2[7]] = movePoint(pointD, projectedLeft); 27651 break; 27652 } 27653 } 27654 } 27655 // bug: ol, bad calculation circle geom extent 27656 if (geometry.getType() == 'Circle') geometry.setCenterAndRadius(geometry.getCenter(), geometry.getRadius()); 27657 return g2; 27658 }); 27659 if (this.get('enableRotatedTransform') && viewRotation !== 0) { 27660 //geometry.rotate(viewRotation, rotationCenter); 27661 geometry.rotate(viewRotation, this.getMap().getView().getCenter()); 27662 } 27663 f.setGeometry(geometry); 27664 } 27665 this.drawSketch_(); 27666 this.dispatchEvent({ 27667 type:'scaling', 27668 feature: this.selection_.item(0), 27669 features: this.selection_, 27670 scale:[scx,scy], 27671 pixel: evt.pixel, 27672 coordinate: evt.coordinate 27673 }); 27674 break; 27675 } 27676 default: break; 27677 } 27678 this.isUpdating_ = false; 27679}; 27680/** 27681 * @param {ol.MapBrowserEvent} evt Event. 27682 * @private 27683 */ 27684ol.interaction.Transform.prototype.handleMoveEvent_ = function(evt) { 27685 if (!this._handleEvent(evt, this.features_)) return; 27686 // console.log("handleMoveEvent"); 27687 if (!this.mode_) { 27688 var sel = this.getFeatureAtPixel_(evt.pixel); 27689 var element = evt.map.getTargetElement(); 27690 if (sel.feature) { 27691 var c = sel.handle ? this.Cursors[(sel.handle||'default')+(sel.constraint||'')+(sel.option||'')] : this.Cursors.select; 27692 if (this.previousCursor_===undefined) { 27693 this.previousCursor_ = element.style.cursor; 27694 } 27695 element.style.cursor = c; 27696 } else { 27697 if (this.previousCursor_!==undefined) element.style.cursor = this.previousCursor_; 27698 this.previousCursor_ = undefined; 27699 } 27700 } 27701}; 27702/** 27703 * @param {ol.MapBrowserEvent} evt Map browser event. 27704 * @return {boolean} `false` to stop the drag sequence. 27705 */ 27706ol.interaction.Transform.prototype.handleUpEvent_ = function(evt) { 27707 // remove rotate0 cursor on Up event, otherwise it's stuck on grab/grabbing 27708 if (this.mode_ === 'rotate') { 27709 var element = evt.map.getTargetElement(); 27710 element.style.cursor = this.Cursors.default; 27711 this.previousCursor_ = undefined; 27712 } 27713 //dispatchEvent 27714 this.dispatchEvent({ 27715 type:this.mode_+'end', 27716 feature: this.selection_.item(0), 27717 features: this.selection_, 27718 oldgeom: this.geoms_[0], 27719 oldgeoms: this.geoms_ 27720 }); 27721 this.drawSketch_(); 27722 this.mode_ = null; 27723 return false;
27724}; 27725/** Get the features that are selected for transform 27726 * @return ol.Collection 27727 */ 27728ol.interaction.Transform.prototype.getFeatures = function() { 27729 return this.selection_; 27730}; 27731/** Set the point radius to calculate handles on points 27732 * @param {number|Array<number>|function} [pointRadius=0] radius for points or a function that takes a feature and returns the radius (or [radiusX, radiusY]). If not null show handles to transform the points 27733 */ 27734ol.interaction.Transform.prototype.setPointRadius = function(pointRadius) { 27735 if (typeof(pointRadius)==='function') { 27736 this._pointRadius = pointRadius; 27737 } else { 27738 this._pointRadius = function(){ return pointRadius }; 27739 } 27740}; 27741 27742/** Undo/redo interaction 27743 * @constructor 27744 * @extends {ol.interaction.Interaction} 27745 * @fires undo 27746 * @fires redo 27747 * @fires change:add 27748 * @fires change:remove 27749 * @fires change:clear 27750 * @param {Object} options 27751 * @param {number=} options.maxLength max undo stack length (0=Infinity), default Infinity 27752 * @param {Array<ol.Layer>} options.layers array of layers to undo/redo 27753 */ 27754ol.interaction.UndoRedo = function(options) { 27755 if (!options) options = {}; 27756 ol.interaction.Interaction.call(this, { 27757 handleEvent: function() { 27758 return true; 27759 } 27760 }); 27761 //array of layers to undo/redo 27762 this._layers = options.layers 27763 this._undoStack = new ol.Collection(); 27764 this._redoStack = new ol.Collection(); 27765 // Zero level stack 27766 this._undo = []; 27767 this._redo = []; 27768 this._undoStack.on('add', function(e) { 27769 if (e.element.level === undefined) { 27770 e.element.level = this._level; 27771 if (!e.element.level) { 27772 e.element.view = { 27773 center: this.getMap().getView().getCenter(), 27774 zoom: this.getMap().getView().getZoom() 27775 }; 27776 this._undo.push(e.element); 27777 } 27778 } else {
27779 if (!e.element.level) this._undo.push(this._redo.shift()); 27780 } 27781 if (!e.element.level) { 27782 this.dispatchEvent({ 27783 type: 'stack:add', 27784 action: e.element 27785 }); 27786 } 27787 this._reduce(); 27788 }.bind(this)); 27789 this._undoStack.on('remove', function(e) { 27790 if (!e.element.level) { 27791 if (this._doShift) { 27792 this._undo.shift(); 27793 } else { 27794 if (this._undo.length) this._redo.push(this._undo.pop()); 27795 } 27796 if (!this._doClear) { 27797 this.dispatchEvent({ 27798 type: 'stack:remove', 27799 action: e.element, 27800 shift: this._doShift 27801 }); 27802 } 27803 } 27804 }.bind(this)); 27805 // Block counter 27806 this._block = 0; 27807 this._level = 0; 27808 // Shift an undo action ? 27809 this._doShift = false; 27810 // Start recording 27811 this._record = true; 27812 // Custom definitions 27813 this._defs = {}; 27814}; 27815ol.ext.inherits(ol.interaction.UndoRedo, ol.interaction.Interaction); 27816/** Add a custom undo/redo 27817 * @param {string} action the action key name 27818 * @param {function} undoFn function called when undoing 27819 * @param {function} redoFn function called when redoing 27820 * @api 27821 */ 27822ol.interaction.UndoRedo.prototype.define = function(action, undoFn, redoFn) { 27823 this._defs[action] = { undo: undoFn, redo: redoFn }; 27824}; 27825/** Get first level undo / redo length 27826 * @param {string} [type] get redo stack length, default get undo 27827 * @return {number} 27828 */ 27829ol.interaction.UndoRedo.prototype.length = function(type) { 27830 return (type==='redo') ? this._redo.length : this._undo.length; 27831}; 27832/** Set undo stack max length 27833 * @param {number} length 27834 */ 27835ol.interaction.UndoRedo.prototype.setMaxLength = function(length) { 27836 length = parseInt(length); 27837 if (length && length<0) length = 0; 27838 this.set('maxLength', length); 27839 this._reduce(); 27840}; 27841/** Get undo / redo size (includes all block levels) 27842 * @param {string} [type] get redo stack length, default get undo 27843 * @return {number} 27844 */ 27845ol.interaction.UndoRedo.prototype.size = function(type) { 27846 return (type==='redo') ? this._redoStack.getLength() : this._undoStack.getLength(); 27847}; 27848/** Set undo stack max size 27849 * @param {number} size 27850 */ 27851ol.interaction.UndoRedo.prototype.setMaxSize = function(size) { 27852 size = parseInt(size); 27853 if (size && size<0) size = 0; 27854 this.set('maxSize', size); 27855 this._reduce(); 27856}; 27857/** Reduce stack: shift undo to set size 27858 * @private 27859 */ 27860ol.interaction.UndoRedo.prototype._reduce = function() { 27861 if (this.get('maxLength')) { 27862 while (this.length() > this.get('maxLength')) { 27863 this.shift(); 27864 } 27865 } 27866 if (this.get('maxSize')) { 27867 while (this.length() > 1 && this.size() > this.get('maxSize')) { 27868 this.shift(); 27869 } 27870 } 27871}; 27872/** Get first level undo / redo first level stack 27873 * @param {string} [type] get redo stack, default get undo 27874 * @return {Array<*>} 27875 */ 27876ol.interaction.UndoRedo.prototype.getStack = function(type) { 27877 return (type==='redo') ? this._redo : this._undo; 27878}; 27879/** Add a new custom undo/redo 27880 * @param {string} action the action key name 27881 * @param {any} prop an object that will be passed in the undo/redo functions of the action 27882 * @param {string} name action name 27883 * @return {boolean} true if the action is defined 27884 */ 27885ol.interaction.UndoRedo.prototype.push = function(action, prop, name) { 27886 if (this._defs[action]) { 27887 this._undoStack.push({ 27888 type: action, 27889 name: name, 27890 custom: true, 27891 prop: prop 27892 }); 27893 return true; 27894 } else { 27895 console.warn('[UndoRedoInteraction]: "'+action+'" is not defined.'); 27896 return false; 27897 } 27898}; 27899/** Remove undo action from the beginning of the stack. 27900 * The action is not returned. 27901 */ 27902ol.interaction.UndoRedo.prototype.shift = function() { 27903 this._doShift = true; 27904 var a = this._undoStack.removeAt(0); 27905 this._doShift = false; 27906 // Remove all block 27907 if (a.type==='blockstart') { 27908 a = this._undoStack.item(0); 27909 while (this._undoStack.getLength() && a.level>0) { 27910 this._undoStack.removeAt(0); 27911 a = this._undoStack.item(0); 27912 } 27913 } 27914}; 27915/** Activate or deactivate the interaction, ie. records or not events on the map. 27916 * @param {boolean} active 27917 * @api stable 27918 */ 27919ol.interaction.UndoRedo.prototype.setActive = function(active) { 27920 ol.interaction.Interaction.prototype.setActive.call (this, active); 27921 this._record = active; 27922}; 27923/** 27924 * Remove the interaction from its current map, if any, and attach it to a new 27925 * map, if any. Pass `null` to just remove the interaction from the current map. 27926 * @param {ol.Map} map Map. 27927 * @api stable 27928 */ 27929ol.interaction.UndoRedo.prototype.setMap = function(map) { 27930 if (this._mapListener) { 27931 this._mapListener.forEach(function(l) { ol.Observable.unByKey(l); }) 27932 } 27933 this._mapListener = []; 27934 ol.interaction.Interaction.prototype.setMap.call (this, map); 27935 // Watch blocks 27936 if (map) { 27937 this._mapListener.push(map.on('undoblockstart', this.blockStart.bind(this))); 27938 this._mapListener.push(map.on('undoblockend', this.blockEnd.bind(this))); 27939 } 27940 // Watch sources 27941 this._watchSources(); 27942 this._watchInteractions(); 27943}; 27944/** Watch for changes in the map sources 27945 * @private 27946 */ 27947ol.interaction.UndoRedo.prototype._watchSources = function() { 27948 var map = this.getMap(); 27949 // Clear listeners 27950 if (this._sourceListener) { 27951 this._sourceListener.forEach(function(l) { ol.Observable.unByKey(l); }) 27952 } 27953 this._sourceListener = []; 27954 var self = this; 27955 // Ges vector layers 27956 function getVectorLayers(layers, init) { 27957 if (!init) init = [];
27958 layers.forEach(function(l) { 27959 if (l instanceof ol.layer.Vector) { 27960 if (!self._layers || self._layers.indexOf(l) >= 0) { 27961 init.push(l); 27962 } 27963 } else if (l.getLayers) { 27964 getVectorLayers(l.getLayers(), init); 27965 } 27966 }); 27967 return init; 27968 } 27969 if (map) { 27970 // Watch the vector sources in the map 27971 var vectors = getVectorLayers(map.getLayers()); 27972 vectors.forEach((function(l) { 27973 var s = l.getSource(); 27974 this._sourceListener.push( s.on(['addfeature', 'removefeature'], this._onAddRemove.bind(this)) ); 27975 this._sourceListener.push( s.on('clearstart', function() { 27976 this.blockStart('clear') 27977 }.bind(this))); 27978 this._sourceListener.push( s.on('clearend', this.blockEnd.bind(this)) ); 27979 }).bind(this)); 27980 // Watch new inserted/removed 27981 this._sourceListener.push( map.getLayers().on(['add', 'remove'], this._watchSources.bind(this) ) ); 27982 } 27983}; 27984/** Watch for interactions 27985 * @private 27986 */ 27987ol.interaction.UndoRedo.prototype._watchInteractions = function() { 27988 var map = this.getMap(); 27989 // Clear listeners 27990 if (this._interactionListener) { 27991 this._interactionListener.forEach(function(l) { ol.Observable.unByKey(l); }) 27992 } 27993 this._interactionListener = []; 27994 if (map) { 27995 // Watch the interactions in the map 27996 map.getInteractions().forEach((function(i) { 27997 this._interactionListener.push(i.on( 27998 ['setattributestart', 'modifystart', 'rotatestart', 'translatestart', 'scalestart', 'deletestart', 'deleteend', 'beforesplit', 'aftersplit'], 27999 this._onInteraction.bind(this) 28000 )); 28001 }).bind(this)); 28002 // Watch new inserted / unwatch removed 28003 this._interactionListener.push( map.getInteractions().on( 28004 ['add', 'remove'], 28005 this._watchInteractions.bind(this) 28006 )); 28007 } 28008}; 28009/** A feature is added / removed 28010 */ 28011ol.interaction.UndoRedo.prototype._onAddRemove = function(e) { 28012 if (this._record) { 28013 this._redoStack.clear(); 28014 this._redo.length = 0; 28015 this._undoStack.push({ 28016 type: e.type, 28017 source: e.target, 28018 feature: e.feature 28019 }); 28020 } 28021}; 28022/** Perform an interaction 28023 * @private 28024 */ 28025ol.interaction.UndoRedo.prototype._onInteraction = function(e) { 28026 var fn = this._onInteraction[e.type]; 28027 if (fn) fn.call(this,e); 28028}; 28029/** Set attribute 28030 * @private 28031 */ 28032ol.interaction.UndoRedo.prototype._onInteraction.setattributestart = function(e) { 28033 this.blockStart(e.target.get('name') || 'setattribute'); 28034 var newp = Object.assign({}, e.properties); 28035 e.features.forEach(function(f) { 28036 var oldp = {}; 28037 for (var p in newp) { 28038 oldp[p] = f.get(p); 28039 } 28040 this._undoStack.push({ 28041 type: 'changeattribute', 28042 feature: f, 28043 newProperties: newp, 28044 oldProperties: oldp 28045 }); 28046 }.bind(this)); 28047 this.blockEnd(); 28048}; 28049ol.interaction.UndoRedo.prototype._onInteraction.rotatestart = 28050ol.interaction.UndoRedo.prototype._onInteraction.translatestart = 28051ol.interaction.UndoRedo.prototype._onInteraction.scalestart = 28052ol.interaction.UndoRedo.prototype._onInteraction.modifystart = function (e) { 28053 this.blockStart(e.type.replace(/start$/,'')); 28054 e.features.forEach(function(m) { 28055 this._undoStack.push({ 28056 type: 'changegeometry', 28057 feature: m, 28058 oldGeom: m.getGeometry().clone() 28059 }); 28060 }.bind(this)); 28061 this.blockEnd(); 28062}; 28063/** Start an undo block 28064 * @param {string} [name] name f the action 28065 * @api 28066 */ 28067ol.interaction.UndoRedo.prototype.blockStart = function (name) { 28068 this._redoStack.clear(); 28069 this._redo.length = 0; 28070 this._undoStack.push({ 28071 type: 'blockstart', 28072 name: name 28073 }); 28074 this._level++; 28075}; 28076/** @private 28077 */ 28078ol.interaction.UndoRedo.prototype._onInteraction.beforesplit = function() { 28079 // Check modify before split 28080 var l = this._undoStack.getLength(); 28081 if (l>2 28082 && this._undoStack.item(l-1).type === 'blockend' 28083 && this._undoStack.item(l-2).type === 'changegeometry') { 28084 this._undoStack.pop(); 28085 } else { 28086 this.blockStart('split'); 28087 } 28088}; 28089ol.interaction.UndoRedo.prototype._onInteraction.deletestart = function() { 28090 this.blockStart('delete'); 28091} 28092/** End an undo block 28093 * @api 28094 */ 28095ol.interaction.UndoRedo.prototype.blockEnd = function () {
28096 this._undoStack.push({ type: 'blockend' }); 28097 this._level--; 28098}; 28099/** @private 28100 */ 28101ol.interaction.UndoRedo.prototype._onInteraction.aftersplit = 28102ol.interaction.UndoRedo.prototype._onInteraction.deleteend = 28103ol.interaction.UndoRedo.prototype.blockEnd; 28104/** handle undo/redo 28105 * @private 28106 */ 28107ol.interaction.UndoRedo.prototype._handleDo = function(e, undo) { 28108 // Not active 28109 if (!this.getActive()) return; 28110 // Stop recording while undoing 28111 this._record = false; 28112 if (e.custom) { 28113 if (this._defs[e.type]) { 28114 if (undo) this._defs[e.type].undo(e.prop); 28115 else this._defs[e.type].redo(e.prop); 28116 } else { 28117 console.warn('[UndoRedoInteraction]: "'+e.type+'" is not defined.'); 28118 } 28119 } else { 28120 switch (e.type) { 28121 case 'addfeature': { 28122 if (undo) e.source.removeFeature(e.feature); 28123 else e.source.addFeature(e.feature); 28124 break; 28125 } 28126 case 'removefeature': { 28127 if (undo) e.source.addFeature(e.feature); 28128 else e.source.removeFeature(e.feature); 28129 break; 28130 } 28131 case 'changegeometry': { 28132 var geom = e.feature.getGeometry(); 28133 e.feature.setGeometry(e.oldGeom); 28134 e.oldGeom = geom; 28135 break; 28136 } 28137 case 'changeattribute': { 28138 var newp = e.newProperties; 28139 var oldp = e.oldProperties; 28140 for (var p in oldp) { 28141 if (oldp === undefined) e.feature.unset(p); 28142 else e.feature.set(p, oldp[p]); 28143 } 28144 e.oldProperties = newp; 28145 e.newProperties = oldp; 28146 break; 28147 } 28148 case 'blockstart': { 28149 this._block += undo ? -1 : 1; 28150 break; 28151 } 28152 case 'blockend': { 28153 this._block += undo ? 1 : -1; 28154 break; 28155 } 28156 default: { 28157 console.warn('[UndoRedoInteraction]: "'+e.type+'" is not defined.'); 28158 } 28159 } 28160 } 28161 // Handle block 28162 if (this._block<0) this._block = 0; 28163 if (this._block) { 28164 if (undo) this.undo(); 28165 else this.redo(); 28166 } 28167 this._record = true; 28168 // Dispatch event 28169 this.dispatchEvent( { 28170 type: undo ? 'undo' : 'redo', 28171 action: e 28172 }); 28173}; 28174/** Undo last operation 28175 * @api 28176 */ 28177ol.interaction.UndoRedo.prototype.undo = function() { 28178 var e = this._undoStack.item(this._undoStack.getLength() - 1); 28179 if (!e) return; 28180 this._redoStack.push(e); 28181 this._undoStack.pop(); 28182 this._handleDo(e, true); 28183}; 28184/** Redo last operation 28185 * @api 28186 */ 28187ol.interaction.UndoRedo.prototype.redo = function() { 28188 var e = this._redoStack.item(this._redoStack.getLength() - 1); 28189 if (!e) return; 28190 this._undoStack.push(e); 28191 this._redoStack.pop(); 28192 this._handleDo(e, false); 28193}; 28194/** Clear undo stack 28195 * @api 28196 */ 28197ol.interaction.UndoRedo.prototype.clear = function() { 28198 this._doClear = true; 28199 this._undo.length = this._redo.length = 0; 28200 this._undoStack.clear(); 28201 this._redoStack.clear(); 28202 this._doClear = false; 28203 this.dispatchEvent({ type: 'stack:clear' }); 28204}; 28205/** Check if undo is avaliable 28206 * @return {number} the number of undo 28207 * @api 28208 */ 28209ol.interaction.UndoRedo.prototype.hasUndo = function() { 28210 return this._undoStack.getLength(); 28211}; 28212/** Check if redo is avaliable 28213 * @return {number} the number of redo 28214 * @api 28215 */ 28216ol.interaction.UndoRedo.prototype.hasRedo = function() { 28217 return this._redoStack.getLength(); 28218}; 28219 28220/* Copyright (c) 2019 Jean-Marc VIGLINO, 28221 released under the CeCILL-B license (French BSD license) 28222 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 28223*/ 28224/** Abstract base class; normally only used for creating subclasses. Bin collector for data 28225 * @constructor 28226 * @extends {ol.source.Vector} 28227 * @param {Object}
28227 options ol.source.VectorOptions + grid option 28228 * @param {ol.source.Vector} options.source Source 28229 * @param {boolean} options.listenChange listen changes (move) on source features to recalculate the bin, default true 28230 * @param {fucntion} [options.geometryFunction] Function that takes an ol.Feature as argument and returns an ol.geom.Point as feature's center. 28231 * @param {function} [options.flatAttributes] Function takes a bin and the features it contains and aggragate the features in the bin attributes when saving 28232 */ 28233ol.source.BinBase = function (options) { 28234 options = options || {}; 28235 this._bindModify = this._onModifyFeature.bind(this); 28236 this._watch = true; 28237 ol.source.Vector.call(this, options); 28238 this._origin = options.source; 28239 this._listen = (options.listenChange !== false); 28240 // Geometry function 28241 this._geomFn = options.geometryFunction || ol.coordinate.getFeatureCenter || function (f) { return f.getGeometry().getFirstCoordinate(); }; 28242 // Existing features 28243 this.reset(); 28244 // Future features 28245 this._origin.on('addfeature', this._onAddFeature.bind(this)); 28246 this._origin.on('removefeature', this._onRemoveFeature.bind(this)); 28247 this._origin.on('clearstart', this._onClearFeature.bind(this)); 28248 this._origin.on('clearend', this._onClearFeature.bind(this)); 28249 if (typeof (options.flatAttributes) === 'function') this._flatAttributes = options.flatAttributes; 28250}; 28251ol.ext.inherits(ol.source.BinBase, ol.source.Vector); 28252/** 28253 * On add feature 28254 * @param {ol.events.Event} e 28255 * @param {ol.Feature} bin 28256 * @private 28257 */ 28258ol.source.BinBase.prototype._onAddFeature = function (e, bin, listen) { 28259 var f = e.feature || e.target; 28260 bin = bin || this.getBinAt(this._geomFn(f), true); 28261 if (bin) bin.get('features').push(f); 28262 if (this._listen && listen!==false) f.on('change', this._bindModify); 28263}; 28264/** 28265 * On remove feature 28266 * @param {ol.events.Event} e 28267 * @param {ol.Feature} bin 28268 * @private 28269 */ 28270ol.source.BinBase.prototype._onRemoveFeature = function (e, bin, listen) { 28271 if (!this._watch) return; 28272 var f = e.feature || e.target; 28273 bin = bin || this.getBinAt(this._geomFn(f)); 28274 if (bin) { 28275 // Remove feature from bin 28276 var features = bin.get('features'); 28277 for (var i=0, fi; fi=features[i]; i++) { 28278 if (fi===f) { 28279 features.splice(i, 1); 28280 break; 28281 } 28282 } 28283 // Remove bin if no features 28284 if (!features.length) { 28285 this.removeFeature(bin); 28286 } 28287 } else { 28288 // console.log("[ERROR:Bin] remove feature: feature doesn't exists anymore."); 28289 } 28290 if (this._listen && listen!==false) f.un('change', this._bindModify); 28291}; 28292/** When clearing features remove the listener 28293 * @private 28294 */ 28295ol.source.BinBase.prototype._onClearFeature = function (e) { 28296 if (e.type==='clearstart') { 28297 if (this._listen) { 28298 this._origin.getFeatures().forEach(function (f) { 28299 f.un('change', this._bindModify); 28300 }.bind(this)); 28301 } 28302 this.clear(); 28303 this._watch = false; 28304 } else { 28305 this._watch = true; 28306 } 28307}; 28308/** 28309 * Get the bin that contains a feature 28310 * @param {ol.Feature} f the feature 28311 * @return {ol.Feature} the bin or null it doesn't exit 28312 */ 28313ol.source.BinBase.prototype.getBin = function (feature) { 28314 var bins = this.getFeatures(); 28315 for (var i=0, b; b = bins[i]; i++) { 28316 var features = b.get('features'); 28317 for (var j=0, f; f=features[j]; j++) { 28318 if (f===feature) return b; 28319 } 28320 } 28321 return null; 28322} 28323/** Get the grid geometry at the coord 28324 * @param {ol.Coordinate} coord 28325 * @param {Object} attributes add key/value to this object to add properties to the grid feature 28326 * @returns {ol.geom.Polygon} 28327 * @api 28328 */ 28329ol.source.BinBase.prototype.getGridGeomAt = function (coord /*, attributes */) { 28330 return new ol.geom.Polygon([coord]); 28331}; 28332/** Get the bean at a coord 28333 * @param {ol.Coordinate} coord 28334 * @param {boolean} create true to create if doesn't exit 28335 * @return {ol.Feature} the bin or null it doesn't exit 28336 */ 28337ol.source.BinBase.prototype.getBinAt = function (coord, create) { 28338 var attributes = {}; 28339 var g = this.getGridGeomAt(coord, attributes); 28340 if (!g) return null; 28341 var center = g.getInteriorPoint ? g.getInteriorPoint().getCoordinates() : g.getInteriorPoints().getCoordinates()[0];// ol.extent.getCenter(g.getExtent()); 28342 var features = this.getFeaturesAtCoordinate( center ); 28343 var bin = features[0]; 28344 if (!bin && create) { 28345 attributes.geometry = g; 28346 attributes.features = []; 28347 attributes.center = center; 28348 bin = new ol.Feature(attributes); 28349 this.addFeature(bin); 28350 } 28351 return bin || null; 28352}; 28353/** 28354 * A feature has been modified 28355 * @param {ol.events.Event} e 28356 * @private 28357 */ 28358ol.source.BinBase.prototype._onModifyFeature = function (e) { 28359 var bin = this.getBin(e.target); 28360 var bin2 = this.getBinAt(this._geomFn(e.target), 'create'); 28361 if (bin !== bin2) { 28362 // remove from the bin 28363 if (bin) {
28364 this._onRemoveFeature(e, bin, false); 28365 } 28366 // insert in the new bin 28367 if (bin2) { 28368 this._onAddFeature(e, bin2, false); 28369 } 28370 } 28371 this.changed(); 28372}; 28373/** Clear all bins and generate a new one. 28374 */ 28375ol.source.BinBase.prototype.reset = function () { 28376 this.clear(); 28377 var features = this._origin.getFeatures(); 28378 for (var i = 0, f; f = features[i]; i++) { 28379 this._onAddFeature({ feature: f }); 28380 } 28381 this.changed(); 28382}; 28383/** 28384 * Get features without circular dependencies (vs. getFeatures) 28385 * @return {Array<ol.Feature>} 28386 */ 28387ol.source.BinBase.prototype.getGridFeatures = function () { 28388 var features = []; 28389 this.getFeatures().forEach(function (f) { 28390 var bin = new ol.Feature(f.getGeometry().clone()); 28391 for (var i in f.getProperties()) { 28392 if (i!=='features' && i!=='geometry') { 28393 bin.set(i, f.get(i)); 28394 } 28395 } 28396 bin.set('nb', f.get('features').length); 28397 this._flatAttributes(bin, f.get('features')); 28398 features.push(bin); 28399 }.bind(this)); 28400 return features; 28401}; 28402/** Create bin attributes using the features it contains when exporting 28403 * @param {ol.Feature} bin the bin to export 28404 * @param {Array<ol.Features>} features the features it contains 28405 */ 28406ol.source.BinBase.prototype._flatAttributes = function(/*bin, features*/) { 28407}; 28408/** Set the flatAttribute function 28409 * @param {function} fn Function that takes a bin and the features it contains and aggragate the features in the bin attributes when saving 28410 */ 28411 ol.source.BinBase.prototype.setFlatAttributesFn = function(fn) { 28412 if (typeof(fn)==='function') this._flatAttributes = fn; 28413}; 28414/** 28415 * Get the orginal source 28416 * @return {ol.source.Vector} 28417 */ 28418ol.source.BinBase.prototype.getSource = function () { 28419 return this._origin; 28420}; 28421 28422/* Copyright (c) 2015 Jean-Marc VIGLINO, 28423 released under the CeCILL-B license (French BSD license) 28424 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 28425 @classdesc 28426 ol.source.DBPedia is a DBPedia layer source that load DBPedia located content in a vector layer. 28427 olx.source.DBPedia: olx.source.Vector 28428 { url: {string} Url for DBPedia SPARQL 28429 } 28430 Inherits from: 28431 <ol.source.Vector> 28432*/ 28433/** 28434* @constructor ol.source.DBPedia 28435* @extends {ol.source.Vector} 28436* @param {olx.source.DBPedia=} opt_options 28437*/ 28438ol.source.DBPedia = function(opt_options) { 28439 var options = opt_options || {}; 28440 options.loader = this._loaderFn; 28441 /** Url for DBPedia SPARQL */ 28442 this._url = options.url || "http://fr.dbpedia.org/sparql"; 28443 /** Max resolution to load features */ 28444 this._maxResolution = options.maxResolution || 100; 28445 /** Result language */ 28446 this._lang = options.lang || "fr"; 28447 /** Query limit */ 28448 this._limit = options.limit || 1000; 28449 /** Default attribution */ 28450 if (!options.attributions) options.attributions = [ "© <a href='http://dbpedia.org/'>DBpedia</a> CC-by-SA" ]; 28451 // Bbox strategy : reload at each move 28452 if (!options.strategy) options.strategy = ol.loadingstrategy.bbox; 28453 ol.source.Vector.call (this, options); 28454}; 28455ol.ext.inherits(ol.source.DBPedia, ol.source.Vector); 28456/** Decode RDF attributes and choose to add feature to the layer 28457* @param {feature} the feature 28458* @param {attributes} RDF attributes 28459* @param {lastfeature} last feature added (null if none) 28460* @return {boolean} true: add the feature to the layer 28461* @API stable 28462*/ 28463ol.source.DBPedia.prototype.readFeature = function (feature, attributes, lastfeature) { 28464 // Copy RDF attributes values 28465 for (var i in attributes) { 28466 if (attributes[i].type==='uri') attributes[i].value = encodeURI(attributes[i].value); 28467 feature.set (i, attributes[i].value); 28468 } 28469 // Prevent same feature with different type duplication 28470 if (lastfeature && lastfeature.get("subject") == attributes.subject.value) { 28471 // Kepp dbpedia.org type ? 28472 // if (bindings[i].type.match ("dbpedia.org") lastfeature.get("type") = bindings[i].type.value; 28473 // Concat types 28474 lastfeature.set("type", lastfeature.get("type") +"\n"+ attributes.type.value); 28475 return false; 28476 } else { 28477 return true; 28478 } 28479}; 28480/** Set RDF query subject, default: select label, thumbnail, abstract and type 28481* @API stable 28482*/
28483ol.source.DBPedia.prototype.querySubject = function () { 28484 return "?subject rdfs:label ?label. " 28485 + "OPTIONAL {?subject dbpedia-owl:thumbnail ?thumbnail}." 28486 + "OPTIONAL {?subject dbpedia-owl:abstract ?abstract} . " 28487 + "OPTIONAL {?subject rdf:type ?type}"; 28488} 28489/** Set RDF query filter, default: select language 28490* @API stable 28491*/ 28492ol.source.DBPedia.prototype.queryFilter = function () { 28493 return "lang(?label) = '"+this._lang+"' " 28494 + "&& lang(?abstract) = '"+this._lang+"'" 28495 // Filter on type 28496 //+ "&& regex (?type, 'Monument|Sculpture|Museum', 'i')" 28497} 28498/** Loader function used to load features. 28499* @private 28500*/ 28501ol.source.DBPedia.prototype._loaderFn = function(extent, resolution, projection) { 28502 if (resolution > this._maxResolution) return; 28503 var self = this; 28504 var bbox = ol.proj.transformExtent(extent, projection, "EPSG:4326"); 28505 // SPARQL request: for more info @see http://fr.dbpedia.org/ 28506 var query = "PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#> " 28507 + "SELECT DISTINCT * WHERE { " 28508 + "?subject geo:lat ?lat . " 28509 + "?subject geo:long ?long . " 28510 + this.querySubject()+" . " 28511 + "FILTER("+this.queryFilter()+") . " 28512 // Filter bbox 28513 + "FILTER(xsd:float(?lat) <= " + bbox[3] + " && " + bbox[1] + " <= xsd:float(?lat) " 28514 + "&& xsd:float(?long) <= " + bbox[2] + " && " + bbox[0] + " <= xsd:float(?long) " 28515 + ") . " 28516 + "} LIMIT "+this._limit; 28517 // Ajax request to get the tile 28518 ol.ext.Ajax.get({ 28519 url: this._url, 28520 data: { query: query, format:"json" }, 28521 success: function(data) { 28522 var bindings = data.results.bindings; 28523 var features = []; 28524 var att, pt, feature, lastfeature = null; 28525 for ( var i in bindings ) { 28526 att = bindings[i]; 28527 pt = [Number(bindings[i].long.value), Number(bindings[i].lat.value)]; 28528 feature = new ol.Feature(new ol.geom.Point(ol.proj.transform (pt,"EPSG:4326",projection))); 28529 if (self.readFeature(feature, att, lastfeature)) { 28530 features.push(feature); 28531 lastfeature = feature; 28532 } 28533 } 28534 self.addFeatures(features); 28535 }}); 28536}; 28537ol.style.clearDBPediaStyleCache; 28538ol.style.dbPediaStyleFunction; 28539(function(){ 28540// Style cache 28541var styleCache = {}; 28542/** Reset the cache (when fonts are loaded) 28543*/ 28544ol.style.clearDBPediaStyleCache = function() { 28545 styleCache = {}; 28546} 28547/** Get a default style function for dbpedia 28548* @param {} options 28549* @param {string|function|undefined} options.glyph a glyph name or a function that takes a feature and return a glyph 28550* @param {number} options.radius radius of the symbol, default 8 28551* @param {ol.style.Fill} options.fill style for fill, default navy 28552* @param {ol.style.stroke} options.stroke style for stroke, default 2px white 28553* @param {string} options.prefix a prefix if many style used for the same type 28554* 28555* @require ol.style.FontSymbol and FontAwesome defs are required for dbPediaStyleFunction() 28556*/ 28557ol.style.dbPediaStyleFunction = function(options) { 28558 if (!options) options={}; 28559 // Get font function using dbPedia type 28560 var getFont; 28561 switch (typeof(options.glyph)) { 28562 case "function": getFont = options.glyph; break; 28563 case "string": getFont = function(){ return options.glyph; }; break; 28564 default: { 28565 getFont = function (f) { 28566 var type = f.get("type"); 28567 if (type) { 28568 if (type.match("/Museum")) return "fa-camera"; 28569 else if (type.match("/Monument")) return "fa-building"; 28570 else if (type.match("/Sculpture")) return "fa-android"; 28571 else if (type.match("/Religious")) return "fa-institution"; 28572 else if (type.match("/Castle")) return "fa-key"; 28573 else if (type.match("Water")) return "fa-tint"; 28574 else if (type.match("Island")) return "fa-leaf"; 28575 else if (type.match("/Event")) return "fa-heart"; 28576 else if (type.match("/Artwork")) return "fa-asterisk"; 28577 else if (type.match("/Stadium")) return "fa-futbol-o"; 28578 else if (type.match("/Place")) return "fa-street-view"; 28579 } 28580 return "fa-star"; 28581 } 28582 break; 28583 } 28584 } 28585 // Default values 28586 var radius = options.radius || 8; 28587 var fill = options.fill || new ol.style.Fill({ color:"navy"}); 28588 var stroke = options.stroke || new ol.style.Stroke({ color: "#fff", width: 2 }); 28589 var prefix = options.prefix ? options.prefix+"_" : ""; 28590 // Vector style function 28591 return function (feature) { 28592 var glyph = getFont(feature); 28593 var k = prefix + glyph; 28594 var style = styleCache[k]; 28595 if (!style) { 28596 styleCache[k] = style = new ol.style.Style ({ 28597 image: new ol.style.FontSymbol({ 28598 glyph: glyph, 28599 radius: radius, 28600 fill: fill, 28601 stroke: stroke 28602 }) 28603 }); 28604 } 28605 return [style]; 28606 } 28607}; 28608})(); 28609 28610/* Copyright (c) 2018 Jean-Marc VIGLINO, 28611 released under the CeCILL-B license (French BSD license)
28612 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 28613*/ 28614/** DFCI source: a source to display the French DFCI grid on a map 28615 * @see http://ccffpeynier.free.fr/Files/dfci.pdf 28616 * @constructor ol.source.DFCI 28617 * @extends {ol/source/Vector} 28618 * @param {any} options Vector source options 28619 * @param {Array<Number>} resolutions a list of resolution to change the drawing level, default [1000,100,20] 28620 */ 28621ol.source.DFCI = function(options) { 28622 options = options || {}; 28623 options.loader = this._calcGrid; 28624 options.strategy = function(extent, resolution) { 28625 if (this.resolution && this.resolution != resolution){ 28626 this.clear(); 28627 this.refresh(); 28628 } 28629 return [extent]; 28630 } 28631 this._bbox = [[0,1600000],[11*100000, 1600000+10*100000]]; 28632 ol.source.Vector.call (this, options); 28633 this.set('resolutions', options.resolutions || [1000,100,20]); 28634 // Add Lambert IIe proj 28635 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"); 28636 ol.proj.proj4.register(proj4); 28637}; 28638ol.ext.inherits(ol.source.DFCI, ol.source.Vector); 28639/** Cacluate grid according extent/resolution 28640 */ 28641ol.source.DFCI.prototype._calcGrid = function (extent, resolution, projection) { 28642 // Show step 0 28643 var f, ext, res = this.get('resolutions'); 28644 if (resolution > (res[0] || 1000)) { 28645 if (this.resolution != resolution) { 28646 if (!this._features0) { 28647 ext = [this._bbox[0][0], this._bbox[0][1],this._bbox[1][0], this._bbox[1][1]]; 28648 this._features0 = this._getFeatures(0, ext, projection); 28649 } 28650 this.addFeatures(this._features0); 28651 } 28652 } 28653 else if (resolution > (res[1] || 100)) { 28654 this.clear(); 28655 ext = ol.proj.transformExtent(extent, projection, 'EPSG:27572'); 28656 f = this._getFeatures(1, ext, projection) 28657 this.addFeatures(f); 28658 } 28659 else if (resolution > (res[2] || 0)) { 28660 this.clear(); 28661 ext = ol.proj.transformExtent(extent, projection, 'EPSG:27572'); 28662 f = this._getFeatures(2, ext, projection) 28663 this.addFeatures(f); 28664 } 28665 else { 28666 this.clear(); 28667 ext = ol.proj.transformExtent(extent, projection, 'EPSG:27572'); 28668 f = this._getFeatures(3, ext, projection) 28669 this.addFeatures(f); 28670 } 28671 // reset load 28672 this.resolution = resolution; 28673}; 28674/** 28675 * Get middle point 28676 * @private 28677 */ 28678ol.source.DFCI.prototype._midPt = function(p1,p2) { 28679 return [(p1[0]+p2[0])/2, (p1[1]+p2[1])/2]; 28680}; 28681/** 28682 * Get feature with geom 28683 * @private 28684 */ 28685ol.source.DFCI.prototype._trFeature = function(geom, id, level, projection) { 28686 var g = new ol.geom.Polygon([geom]); 28687 var f = new ol.Feature(g.transform('EPSG:27572', projection)); 28688 f.set('id', id); 28689 f.set('level', level); 28690 return f; 28691}; 28692/** Get features 28693 * 28694 */ 28695ol.source.DFCI.prototype._getFeatures = function (level, extent, projection) { 28696 var features = []; 28697 var i; 28698 var step = 100000; 28699 if (level>0) step /= 5; 28700 if (level>1) step /= 10; 28701 var p0 = [ 28702 Math.max(this._bbox[0][0], Math.floor(extent[0]/step)*step), 28703 Math.max(this._bbox[0][1], Math.floor(extent[1]/step)*step) 28704 ]; 28705 var p1 = [ 28706 Math.min(this._bbox[1][0]+99999, Math.floor(extent[2]/step)*step), 28707 Math.min(this._bbox[1][1]+99999, Math.floor(extent[3]/step)*step) 28708 ]; 28709 for (var x=p0[0]; x<=p1[0]; x += step) { 28710 for (var y=p0[1]; y<=p1[1]; y += step) { 28711 var p, geom = [ [x,y], [x+step, y], [x+step, y+step], [x , y+step], [x,y]]; 28712 if (level>2) { 28713 var m = this._midPt(geom[0],geom[2]); 28714 // .5 28715 var g = []; 28716 for (i=0; i<geom.length; i++) { 28717 g.push(this._midPt(geom[i],m)) 28718 } 28719 features.push(this._trFeature(g, ol.coordinate.toDFCI([x,y], 2)+'.5', level, projection)); 28720 // .1 > .4 28721 for (i=0; i<4; i++) { 28722 g = []; 28723 g.push(geom[i]); 28724 g.push(this._midPt(geom[i],geom[(i+1)%4])); 28725 g.push(this._midPt(m,g[1])); 28726 g.push(this._midPt(m,geom[i])); 28727 p = this._midPt(geom[i],geom[(i+3)%4]); 28728 g.push(this._midPt(m,p)); 28729 g.push(p); 28730 g.push(geom[i]); 28731 features.push(this._trFeature(g, ol.coordinate.toDFCI([x,y], 2)+'.'+(4-i), level, projection)); 28732 } 28733 } else { 28734 features.push(this._trFeature(geom, ol.coordinate.toDFCI([x,y], level), level, projection)); 28735 } 28736 } 28737 } 28738 return features 28739}; 28740 28741/* Copyright (c) 2018 Jean-Marc VIGLINO, 28742 released under the CeCILL-B license (French BSD license)
28743 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 28744*/ 28745/** DayNight source: a source to display day/night on a map 28746 * @constructor 28747 * @extends {ol.source.Vector} 28748 * @param {any} options Vector source options 28749 * @param {string|Date} time source date time 28750 * @param {number} step step in degree for coordinate precision 28751 */ 28752ol.source.DayNight = function(options) { 28753 options = options || {}; 28754 options.loader = this._loader 28755 options.strategy = ol.loadingstrategy.all; 28756 ol.source.Vector.call (this, options); 28757 this.set('time', options.time || new Date()); 28758 this.set('step', options.step || 1); 28759}; 28760ol.ext.inherits(ol.source.DayNight, ol.source.Vector); 28761(function(){ 28762/** Loader 28763 * @private 28764 */ 28765ol.source.DayNight.prototype._loader = function(extent, resolution, projection) { 28766 var lonlat = this.getCoordinates(this.get('time')); 28767 var geom = new ol.geom.Polygon([lonlat]); 28768 geom.transform('EPSG:4326', projection); 28769 this.addFeature(new ol.Feature(geom)); 28770}; 28771/** Set source date time 28772 * @param {string|Date} time source date time 28773 */ 28774ol.source.DayNight.prototype.setTime = function(time) { 28775 this.set('time', time); 28776 this.refresh(); 28777}; 28778/** Compute the position of the Sun in ecliptic coordinates at julianDay. 28779 * @see http://en.wikipedia.org/wiki/Position_of_the_Sun 28780 * @param {number} julianDay 28781 * @private 28782 */ 28783function _sunEclipticPosition(julianDay) { 28784 var deg2rad = Math.PI / 180; 28785 // Days since start of J2000.0 28786 var n = julianDay - 2451545.0; 28787 // mean longitude of the Sun 28788 var L = 280.460 + 0.9856474 * n; 28789 L %= 360; 28790 // mean anomaly of the Sun 28791 var g = 357.528 + 0.9856003 * n; 28792 g %= 360; 28793 // ecliptic longitude of Sun 28794 var lambda = L + 1.915 * Math.sin(g * deg2rad) + 28795 0.02 * Math.sin(2 * g * deg2rad); 28796 // distance from Sun in AU 28797 var R = 1.00014 - 0.01671 * Math.cos(g * deg2rad) - 28798 0.0014 * Math.cos(2 * g * deg2rad); 28799 return { lambda: lambda, R: R }; 28800} 28801/** 28802 * @see http://en.wikipedia.org/wiki/Axial_tilt#Obliquity_of_the_ecliptic_.28Earth.27s_axial_tilt.29 28803 * @param {number} julianDay 28804 * @private 28805 */ 28806function _eclipticObliquity(julianDay) { 28807 var n = julianDay - 2451545.0; 28808 // Julian centuries since J2000.0 28809 var T = n / 36525; 28810 var epsilon = 23.43929111 - 28811 T * (46.836769 / 3600 28812 - T * (0.0001831 / 3600 28813 + T * (0.00200340 / 3600 28814 - T * (0.576e-6 / 3600 28815 - T * 4.34e-8 / 3600)))); 28816 return epsilon; 28817} 28818/* Compute the Sun's equatorial position from its ecliptic position. 28819 * @param {number} sunEclLng sun lon in degrees 28820 * @param {number} eclObliq secliptic position in degrees 28821 * @return {number} position in degrees 28822 * @private 28823 */ 28824function _sunEquatorialPosition(sunEclLon, eclObliq) { 28825 var rad2deg = 180 / Math.PI; 28826 var deg2rad = Math.PI / 180; 28827 var alpha = Math.atan(Math.cos(eclObliq * deg2rad) 28828 * Math.tan(sunEclLon * deg2rad)) * rad2deg; 28829 var delta = Math.asin(Math.sin(eclObliq * deg2rad) 28830 * Math.sin(sunEclLon * deg2rad)) * rad2deg; 28831 var lQuadrant = Math.floor(sunEclLon / 90) * 90; 28832 var raQuadrant = Math.floor(alpha / 90) * 90; 28833 alpha = alpha + (lQuadrant - raQuadrant); 28834 return {alpha: alpha, delta: delta}; 28835} 28836/** Get sun coordinates on earth 28837 * @param {string} time DateTime string, default yet 28838 * @returns {ol.coordinate} position in lonlat 28839 */ 28840ol.source.DayNight.prototype.getSunPosition = function (time) { 28841 var date = time ? new Date(time) : new Date(); 28842 // Calculate the present UTC Julian Date. 28843 // Function is valid after the beginning of the UNIX epoch 1970-01-01 and ignores leap seconds. 28844 var julianDay = (date / 86400000) + 2440587.5; 28845 // Calculate Greenwich Mean Sidereal Time (low precision equation). 28846 // http://aa.usno.navy.mil/faq/docs/GAST.php 28847 var gst = (18.697374558 + 24.06570982441908 * (julianDay - 2451545.0)) % 24; 28848 var sunEclPos = _sunEclipticPosition(julianDay); 28849 var eclObliq = _eclipticObliquity(julianDay); 28850 var sunEqPos = _sunEquatorialPosition(sunEclPos.lambda, eclObliq); 28851 return [sunEqPos.alpha - gst * 15, sunEqPos.delta] 28852}; 28853/** Get the day/night separation latitude 28854 * @param {number} lon 28855 * @param {Date} time 28856 * @returns {number} 28857 */ 28858ol.source.DayNight.getNightLat = function (lon, time) { 28859 var rad2deg = 180 / Math.PI; 28860 var deg2rad = Math.PI / 180; 28861 var date = time ? new Date(time) : new Date(); 28862 // Calculate the present UTC Julian Date. 28863 // Function is valid after the beginning of the UNIX epoch 1970-01-01 and ignores leap seconds. 28864 var julianDay = (date / 86400000) + 2440587.5; 28865 // Calculate Greenwich Mean Sidereal Time (low precision equation). 28866 // http://aa.usno.navy.mil/faq/docs/GAST.php 28867 var gst = (18.697374558 + 24.06570982441908 * (julianDay - 2451545.0)) % 24; 28868 var sunEclPos = _sunEclipticPosition(julianDay); 28869 var eclObliq = _eclipticObliquity(julianDay); 28870 var sunEqPos = _sunEquatorialPosition(sunEclPos.lambda, eclObliq); 28871 // Hour angle (indegrees) of the sun for a longitude on Earth. 28872 var ha = (gst * 15 + lon) - sunEqPos.alpha; 28873 // Latitude 28874 var lat = Math.atan(-Math.cos(ha * deg2rad) / 28875 Math.tan(sunEqPos.delta * deg2rad)) * rad2deg; 28876 return lat; 28877}; 28878/** Get night-day separation line 28879 * @param {string} time DateTime string, default yet 28880 * @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' 28881 * @return {Array<ol.Point>|Array<Array<ol.Point>>} 28882 */ 28883ol.source.DayNight.prototype.getCoordinates = function (time, options) { 28884 var rad2deg = 180 / Math.PI; 28885 var deg2rad = Math.PI / 180; 28886 var date = time ? new Date(time) : new Date(); 28887 // Calculate the present UTC Julian Date. 28888 // Function is valid after the beginning of the UNIX epoch 1970-01-01 and ignores leap seconds. 28889 var julianDay = (date / 86400000) + 2440587.5; 28890 // Calculate Greenwich Mean Sidereal Time (low precision equation). 28891 // http://aa.usno.navy.mil/faq/docs/GAST.php 28892 var gst = (18.697374558 + 24.06570982441908 * (julianDay - 2451545.0)) % 24; 28893 var lonlat = []; 28894 var sunEclPos = _sunEclipticPosition(julianDay); 28895 var eclObliq = _eclipticObliquity(julianDay); 28896 var sunEqPos = _sunEquatorialPosition(sunEclPos.lambda, eclObliq); 28897 var step = this.get('step') || 1;
28898 for (var i = -180; i <= 180; i += step) { 28899 var lon = i; 28900 // Hour angle (indegrees) of the sun for a longitude on Earth. 28901 var ha = (gst * 15 + lon) - sunEqPos.alpha; 28902 // Latitude 28903 var lat = Math.atan(-Math.cos(ha * deg2rad) / 28904 Math.tan(sunEqPos.delta * deg2rad)) * rad2deg; 28905 // New point 28906 lonlat.push([lon, lat]); 28907 } 28908 switch (options) { 28909 case 'line': break; 28910 case 'day': sunEqPos.delta *= -1; 28911 // fallthrough 28912 default: { 28913 // Close polygon 28914 lat = (sunEqPos.delta < 0) ? 90 : -90; 28915 for(var tlon=180; tlon>=-180; tlon-=step){ 28916 lonlat.push([tlon,lat]); 28917 } 28918 lonlat.push(lonlat[0]) 28919 break; 28920 } 28921 } 28922 // Return night + day polygon 28923 if (options === 'daynight') { 28924 var day = []; 28925 lonlat.forEach(function (t) { day.push(t.slice()); }); 28926 day[0][1] = -day[0][1]; 28927 day[day.length-1][1] = -day[0][1]; 28928 day[day.length-1][1] = -day[0][1]; 28929 lonlat = [ lonlat, day ]; 28930 } 28931 // Return polygon 28932 return lonlat; 28933}; 28934})(); 28935 28936/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ 28937/** Delaunay source 28938 * Calculate a delaunay triangulation from points in a source 28939 * @param {*} options extend ol/source/Vector options 28940 * @param {ol/source/Vector} options.source the source that contains the points 28941 */ 28942ol.source.Delaunay = function(options) { 28943 options = options || {}; 28944 this._nodes = options.source; 28945 delete options.source; 28946 ol.source.Vector.call (this, options); 28947 // Convex hull 28948 this.hull = []; 28949 // A new node is added to the source node: calculate the new triangulation 28950 this._nodes.on('addfeature', this._onAddNode.bind(this)); 28951 // A new node is removed from the source node: calculate the new triangulation 28952 this._nodes.on('removefeature', this._onRemoveNode.bind(this)); 28953 this.set ('epsilon', options.epsilon || .0001) 28954}; 28955ol.ext.inherits(ol.source.Delaunay, ol.source.Vector); 28956/** Clear source (and points) 28957 * @param {boolean} opt_fast 28958 */ 28959ol.source.Delaunay.prototype.clear = function(opt_fast) { 28960 ol.source.Vector.prototype.clear.call(this, opt_fast); 28961 this.getNodeSource().clear(opt_fast); 28962}; 28963/** Add a new triangle in the source 28964 * @param {Array<ol/coordinates>} pts 28965 */ 28966ol.source.Delaunay.prototype._addTriangle = function(pts) { 28967 pts.push(pts[0]); 28968 var triangle = new ol.Feature(new ol.geom.Polygon([pts])); 28969 this.addFeature(triangle); 28970 this.flip.push(triangle); 28971 return triangle; 28972}; 28973/** Get nodes 28974 */ 28975ol.source.Delaunay.prototype.getNodes = function () { 28976 return this._nodes.getFeatures(); 28977}; 28978/** Get nodes source 28979 */ 28980ol.source.Delaunay.prototype.getNodeSource = function () { 28981 return this._nodes; 28982}; 28983/** 28984 * A point has been removed 28985 * @param {ol/source/Vector.Event} evt 28986 */ 28987ol.source.Delaunay.prototype._onRemoveNode = function(evt) { 28988 // console.log(evt) 28989 var pt = evt.feature.getGeometry().getCoordinates(); 28990 if (!pt) return; 28991 // Still there (when removing duplicated points) 28992 if (this.getNodesAt(pt).length) return; 28993 // console.log('removenode', evt.feature) 28994 // Get associated triangles 28995 var triangles = this.getTrianglesAt(pt); 28996 this.flip=[]; 28997 // Get hole 28998 var i; 28999 var edges = []; 29000 while (triangles.length) { 29001 var tr = triangles.pop() 29002 this.removeFeature(tr); 29003 tr = tr.getGeometry().getCoordinates()[0]; 29004 var pts = []; 29005 for (i=0; i<3; i++) { 29006 p = tr[i]; 29007 if (!ol.coordinate.equal(p,pt)) { 29008 pts.push(p); 29009 } 29010 } 29011 edges.push(pts); 29012 } 29013 pts = edges.pop(); 29014/* DEBUG 29015var se = ''; 29016edges.forEach(function(e){ 29017 se += ' - '+this.listpt(e); 29018}.bind(this)); 29019console.log('EDGES', se); 29020*/ 29021 i = 0; 29022 function testEdge(p0, p1, index) { 29023 if (ol.coordinate.equal(p0, pts[index])) { 29024 if (index) pts.push(p1); 29025 else pts.unshift(p1); 29026 return true 29027 } 29028 return false; 29029 } 29030 while (true) { 29031 var e = edges[i]; 29032 if ( testEdge(e[0], e[1], 0) 29033 || testEdge(e[1], e[0], 0) 29034 || testEdge(e[0], e[1], pts.length-1) 29035 || testEdge(e[1], e[0], pts.length-1) 29036 ) { 29037 edges.splice(i,1); 29038 i = 0; 29039 } else { 29040 i++ 29041 } 29042 if (!edges.length) break; 29043 if (i>=edges.length) { 29044// console.log(this.listpt(pts), this.listpt(edges)); 29045 throw '[DELAUNAY:removePoint] No edge found'; 29046 } 29047 } 29048 // Closed = interior 29049// console.log('PTS', this.listpt(pts)) 29050 var closed = ol.coordinate.equal(pts[0], pts[pts.length-1]); 29051 if (closed) pts.pop(); 29052 // Update convex hull: remove pt + add new ones 29053 var p; 29054 for (i; p=this.hull[i]; i++) { 29055 if (ol.coordinate.equal(pt,p)) { 29056 this.hull.splice(i,1); 29057 break; 29058 } 29059 } 29060 this.hull = ol.coordinate.convexHull(this.hull.concat(pts)); 29061// select.getFeatures().clear(); 29062 // 29063 var clockwise = function (t) { 29064 var i1, s = 0; 29065 for (var i=0; i<t.length; i++) { 29066 i1 = (i+1) % t.length;
29067 s += (t[i1][0] - t[i][0]) * (t[i1][1] + t[i][1]); 29068 } 29069// console.log(s) 29070 return (s>=0 ? 1:-1) 29071 }; 29072 // Add ears 29073 // interior point : ear area and object area have the same sign 29074 // extrior point : add a new point and close 29075 var clock; 29076 var enveloppe = pts.slice(); 29077 if (closed) { 29078 clock = clockwise(pts); 29079 } else { 29080// console.log('ouvert', pts, pts.slice().push(pt)) 29081 enveloppe.push(pt); 29082 clock = clockwise(enveloppe); 29083 } 29084// console.log('S=',clock,'CLOSED',closed) 29085// console.log('E=',this.listpt(enveloppe)) 29086 for (i=0; i<=pts.length+1; i++) { 29087 if (pts.length<3) break; 29088 var t = [ 29089 pts[i % pts.length], 29090 pts[(i+1) % pts.length], 29091 pts[(i+2) % pts.length] 29092 ]; 29093 if (clockwise(t)===clock) { 29094 var ok = true; 29095 for (var k=i+3; k<i+pts.length; k++) { 29096// console.log('test '+k, this.listpt([pts[k % pts.length]])) 29097 if (this.inCircle(pts[k % pts.length], t)) { 29098 ok = false; 29099 break; 29100 } 29101 } 29102 if (ok) { 29103// console.log(this.listpt(t),'ok'); 29104 this._addTriangle(t); 29105 // remove 29106 pts.splice((i+1) % pts.length, 1); 29107 // and restart 29108 i = -1; 29109 } 29110 } 29111// else console.log(this.listpt(t),'nok'); 29112 } 29113/* DEBUG * / 29114if (pts.length>3) console.log('oops'); 29115console.log('LEAV',this.listpt(pts)); 29116var ul = $('ul.triangles').html(''); 29117$('<li>') 29118.text('E:'+this.listpt(enveloppe)+' - '+clock+' - '+closed) 29119.data('triangle', new ol.Feature(new ol.geom.Polygon([enveloppe]))) 29120.click(function(){ 29121 var t = $(this).data('triangle'); 29122 select.getFeatures().clear(); 29123 select.getFeatures().push(t); 29124}) 29125.appendTo(ul); 29126for (var i=0; i<this.flip.length; i++) { 29127 $('<li>') 29128 .text(this.listpt(this.flip[i].getGeometry().getCoordinates()[0]) 29129 +' - ' + clockwise(this.flip[i].getGeometry().getCoordinates()[0])) 29130 .data('triangle', this.flip[i]) 29131 .click(function(){ 29132 var t = $(this).data('triangle'); 29133 select.getFeatures().clear(); 29134 select.getFeatures().push(t); 29135 }) 29136 .appendTo(ul); 29137} 29138/**/ 29139 // Flip? 29140 this.flipTriangles(); 29141}; 29142/** 29143 * A new point has been added 29144 * @param {ol/source/VectorEvent} e 29145 */ 29146ol.source.Delaunay.prototype._onAddNode = function(e) { 29147 var finserted = e.feature; 29148 var i, p; 29149 // Not a point! 29150 if (finserted.getGeometry().getType() !== 'Point') { 29151 this._nodes.removeFeature(finserted); 29152 return; 29153 } 29154 // Reset flip table 29155 this.flip = []; 29156 var nodes = this.getNodes(); 29157 // The point 29158 var pt = finserted.getGeometry().getCoordinates(); 29159 // Test existing point 29160 if (this.getNodesAt(pt).length > 1) { 29161 // console.log('remove duplicated points') 29162 this._nodes.removeFeature(finserted); 29163 return; 29164 } 29165 // Triangle needs at least 3 points 29166 if (nodes.length <= 3) { 29167 if (nodes.length===3) { 29168 var pts = []; 29169 for (i=0; i<3; i++) pts.push(nodes[i].getGeometry().getCoordinates()); 29170 this._addTriangle(pts); 29171 this.hull = ol.coordinate.convexHull(pts); 29172 } 29173 return; 29174 } 29175 // Get the triangle 29176 var t = this.getFeaturesAtCoordinate(pt)[0]; 29177 if (t) { 29178 this.removeFeature(t); 29179 t.set('del', true); 29180 var c = t.getGeometry().getCoordinates()[0]; 29181 for (i=0; i<3; i++) { 29182 this._addTriangle([ pt, c[i], c[(i+1)%3]]); 29183 } 29184 } else { 29185 // Calculate new convex hull 29186 var hull2 = this.hull.slice(); 29187 hull2.push(pt); 29188 hull2 = ol.coordinate.convexHull(hull2); 29189 // Search for points 29190 for (i=0; p=hull2[i]; i++) { 29191 if (ol.coordinate.equal(p,pt)) break; 29192 } 29193 i = (i!==0 ? i-1 : hull2.length-1); 29194 var p0 = hull2[i]; 29195 var stop = hull2[(i+2) % hull2.length]; 29196 for (i=0; p=this.hull[i]; i++) { 29197 if (ol.coordinate.equal(p,p0)) break; 29198 } 29199 // Connect to the hull 29200 while (true) { 29201 // DEBUG: prevent infinit loop 29202 if (i>1000) { 29203 console.error('[DELAUNAY:addPoint] Too many iterations') 29204 break; 29205 } 29206 i++; 29207 p = this.hull[i % this.hull.length]; 29208 this._addTriangle([pt, p, p0]); 29209 p0 = p; 29210 if (p[0] === stop[0] && p[1] === stop[1]) break; 29211 } 29212 this.hull = hull2; 29213 } 29214 this.flipTriangles(); 29215}; 29216/** Flipping algorithme: test new inserted triangle and flip 29217 */ 29218ol.source.Delaunay.prototype.flipTriangles = function () { 29219 var count = 1000; // Count to prevent too many iterations 29220 var pi; 29221 while (this.flip.length) { 29222 // DEBUG: prevent infinite loop 29223 if (count--<0) { 29224 console.error('[DELAUNAY:flipTriangles] Too many iterations') 29225 break; 29226 } 29227 var tri = this.flip.pop(); 29228 if (tri.get('del')) continue; 29229 var ti = tri.getGeometry().getCoordinates()[0]; 29230 for (var k=0; k<3; k++) { 29231 // Get facing triangles 29232 var mid = [(ti[(k+1)%3][0]+ti[k][0])/2, (ti[(k+1)%3][1]+ti[k][1])/2]; 29233 var triangles = this.getTrianglesAt(mid); 29234 var pt1 = null; 29235 // Get opposite point 29236 if (triangles.length>1) { 29237 var t0 = triangles[0].getGeometry().getCoordinates()[0]; 29238 var t1 = triangles[1].getGeometry().getCoordinates()[0]; 29239 for (pi=0; pi<t1.length; pi++) { 29240 if (!this._ptInTriangle(t1[pi], t0)) { 29241 pt1 = t1[pi]; 29242 break; 29243 } 29244 } 29245 } 29246 if (pt1) { 29247 // Is in circle ? 29248 if (this.inCircle(pt1, t0)) { 29249 var pt2; 29250 // Get opposite point 29251 for (pi=0; pi<t0.length; pi++) { 29252 if (!this._ptInTriangle(t0[pi], t1)) { 29253 pt2 = t0.splice(pi,1)[0]; 29254 break; 29255 } 29256 } 29257 // Flip triangles 29258 if (this.intersectSegs([pt1, pt2], t0)) { 29259 while (triangles.length) { 29260 var tmp = triangles.pop(); 29261 tmp.set('del', true);
29262 this.removeFeature(tmp); 29263 } 29264 this._addTriangle([pt1, pt2, t0[0]]); 29265 this._addTriangle([pt1, pt2, t0[1]]); 29266 } 29267 } 29268 } 29269 } 29270 } 29271}; 29272/** Test intersection beetween 2 segs 29273 * @param {Array<ol.coordinates>} d1 29274 * @param {Array<ol.coordinates>} d2 29275 * @return {bbolean} 29276 */ 29277ol.source.Delaunay.prototype.intersectSegs = function (d1, d2) { 29278 var d1x = d1[1][0] - d1[0][0]; 29279 var d1y = d1[1][1] - d1[0][1]; 29280 var d2x = d2[1][0] - d2[0][0]; 29281 var d2y = d2[1][1] - d2[0][1]; 29282 var det = d1x * d2y - d1y * d2x; 29283 if (det != 0) { 29284 var k = (d1x * d1[0][1] - d1x * d2[0][1] - d1y * d1[0][0] + d1y * d2[0][0]) / det; 29285 // Intersection: return [d2[0][0] + k*d2x, d2[0][1] + k*d2y]; 29286 return (0<k && k<1); 29287 } 29288 else return false; 29289}; 29290/** Test pt is a triangle's node 29291 * @param {ol.coordinate} pt 29292 * @param {Array<ol.coordinate>} triangle 29293 * @return {boolean} 29294 */ 29295ol.source.Delaunay.prototype._ptInTriangle = function(pt, triangle) { 29296 for (var i=0, p; p=triangle[i]; i++) { 29297 if (ol.coordinate.equal(pt,p)) return true; 29298 } 29299 return false; 29300}; 29301/** List points in a triangle (assume points get an id) for debug purposes 29302 * @param {Array<ol.coordinate>} pts 29303 * @return {String} ids list 29304 */ 29305ol.source.Delaunay.prototype.listpt = function (pts) { 29306 var s = ''; 29307 for (var i=0, p; p = pts[i]; i++) { 29308 var c = this._nodes.getClosestFeatureToCoordinate(p); 29309 if (!ol.coordinate.equal(c.getGeometry().getCoordinates(), p)) c=null; 29310 s += (s?', ':'') + (c ? c.get('id') : '?'); 29311 } 29312 return s; 29313}; 29314/** Test if coord is within triangle's circumcircle 29315 * @param {ol.coordinate} coord 29316 * @param {Array<ol.coordinate>} triangle 29317 * @return {boolean} 29318 */ 29319ol.source.Delaunay.prototype.inCircle = function (coord, triangle) { 29320 var c = this.getCircumCircle(triangle); 29321 return ol.coordinate.dist2d(coord, c.center) < c.radius; 29322} 29323/** Calculate the circumcircle of a triangle 29324 * @param {Array<ol.coordinate>} triangle 29325 * @return {*} 29326 */ 29327ol.source.Delaunay.prototype.getCircumCircle = function (triangle) { 29328 var x1 = triangle[0][0]; 29329 var y1 = triangle[0][1]; 29330 var x2 = triangle[1][0]; 29331 var y2 = triangle[1][1]; 29332 var x3 = triangle[2][0]; 29333 var y3 = triangle[2][1]; 29334 var m1 = (x1-x2)/(y2-y1); 29335 var m2 = (x1-x3)/(y3-y1); 29336 var b1 = ((y1+y2)/2) - m1*(x1+x2)/2; 29337 var b2 = ((y1+y3)/2) - m2*(x1+x3)/2; 29338 var cx = (b2-b1)/(m1-m2); 29339 var cy = m1*cx + b1; 29340 var center = [cx, cy]; 29341 return { 29342 center: center, 29343 radius: ol.coordinate.dist2d(center,triangle[0]) 29344 }; 29345}; 29346/** Get triangles at a point 29347 */ 29348ol.source.Delaunay.prototype.getTrianglesAt = function(coord) { 29349 var extent = ol.extent.buffer (ol.extent.boundingExtent([coord]), this.get('epsilon')); 29350 var result = []; 29351 this.forEachFeatureIntersectingExtent(extent, function(f){ 29352 result.push(f); 29353 }); 29354 return result; 29355}; 29356/** Get nodes at a point 29357 */ 29358ol.source.Delaunay.prototype.getNodesAt = function(coord) { 29359 var extent = ol.extent.buffer (ol.extent.boundingExtent([coord]), this.get('epsilon')); 29360 return this._nodes.getFeaturesInExtent(extent); 29361}; 29362/** Get Voronoi 29363 * @param {boolean} border include border, default false 29364 * @return { Array< ol.geom.Polygon > } 29365 */ 29366ol.source.Delaunay.prototype.calculateVoronoi = function(border) { 29367 var voronoi = []; 29368 this.getNodes().forEach(function(f) { 29369 var pt = f.getGeometry().getCoordinates(); 29370 var isborder = false; 29371 if (border !== true) { 29372 for (var i=0; i<this.hull.length; i++) { 29373 if (ol.coordinate.equal(pt, this.hull[i])) { 29374 isborder = true; 29375 break; 29376 } 29377 } 29378 } 29379 if (!isborder) { 29380 var tr = this.getTrianglesAt(pt); 29381 var pts = []; 29382 tr.forEach(function(triangle) { 29383 var c = this.getCircumCircle(triangle.getGeometry().getCoordinates()[0]); 29384 pts.push({ 29385 pt: c.center, 29386 d: Math.atan2(c.center[1]-pt[1],c.center[0]-pt[0]) 29387 }) 29388 }.bind(this)); 29389 pts.sort(function(a,b) { return a.d - b.d }); 29390 var poly = []; 29391 pts.forEach(function(p) { 29392 poly.push(p.pt); 29393 }); 29394 poly.push(poly[0]); 29395 var prop = f.getProperties(); 29396 prop.geometry = new ol.geom.Polygon([poly]); 29397 voronoi.push(new ol.Feature(prop)); 29398 } 29399 }.bind(this)); 29400 return voronoi; 29401}; 29402 29403/* Copyright (c) 2019 Jean-Marc VIGLINO, 29404 released under the CeCILL-B license (French BSD license)
29405 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 29406*/ 29407/** A source that use a set of feature to collect data on it. 29408 * If a binSource is provided the bin is recalculated when features change. 29409 * @constructor 29410 * @extends {ol.source.BinBase} 29411 * @param {Object} options ol.source.VectorOptions + grid option 29412 * @param {ol.source.Vector} options.source source to collect in the bin 29413 * @param {ol.source.Vector} options.binSource a source to use as bin collector, default none 29414 * @param {Array<ol.Feature>} options.features the features, ignored if binSource is provided, default none 29415 * @param {number} [options.size] size of the grid in meter, default 200m 29416 * @param {function} [options.geometryFunction] Function that takes an ol.Feature as argument and returns an ol.geom.Point as feature's center. 29417 * @param {function} [options.flatAttributes] Function takes a bin and the features it contains and aggragate the features in the bin attributes when saving 29418 */ 29419ol.source.FeatureBin = function (options) { 29420 options = options || {}; 29421 if (options.binSource) { 29422 this._sourceFeature = options.binSource; 29423 // When features change recalculate the bin... 29424 var timout; 29425 this._sourceFeature.on(['addfeature','changefeature','removefeature'], function() { 29426 if (timout) { 29427 // Do it only one time 29428 clearTimeout(timout); 29429 } 29430 timout = setTimeout(function () { 29431 this.reset(); 29432 }.bind(this)); 29433 }.bind(this)); 29434 } else { 29435 this._sourceFeature = new ol.source.Vector ({ features: options.features || [] }); 29436 } 29437 ol.source.BinBase.call(this, options); 29438}; 29439ol.ext.inherits(ol.source.FeatureBin, ol.source.BinBase); 29440/** Set features to use as bin collector 29441 * @param {ol.Feature} features 29442 */ 29443ol.source.FeatureBin.prototype.setFeatures = function (features) { 29444 this._sourceFeature.clear(); 29445 this._sourceFeature.addFeatures(features || []); 29446 this.reset(); 29447}; 29448/** Get the grid geometry at the coord 29449 * @param {ol.Coordinate} coord 29450 * @returns {ol.geom.Polygon} 29451 * @api 29452 */ 29453ol.source.FeatureBin.prototype.getGridGeomAt = function (coord, attributes) { 29454 var f = this._sourceFeature.getFeaturesAtCoordinate(coord)[0]; 29455 if (!f) return null; 29456 var a = f.getProperties(); 29457 for (var i in a) { 29458 if (i!=='geometry') attributes[i] = a[i]; 29459 } 29460 return f.getGeometry(); 29461}; 29462 29463/* Copyright (c) 2015 Jean-Marc VIGLINO, 29464 released under the CeCILL-B license (French BSD license) 29465 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 29466 ol.source.GeoImage is a layer source with georeferencement to place it on a map. 29467*/ 29468/** @typedef {Object} GeoImageOptions 29469 * @property {url} url url of the static image 29470 * @property {image} image the static image, if not provided, use url to load an image 29471 * @property {ol.Coordinate} imageCenter coordinate of the center of the image 29472 * @property {ol.Size|number} imageScale [scalex, scaley] of the image 29473 * @property {number} imageRotate angle of the image in radian, default 0 29474 * @property {ol.Extent} imageCrop of the image to be show (in the image) default: [0,0,imageWidth,imageHeight] 29475 * @property {Array.<ol.Coordinate>} imageMask linestring to mask the image on the map 29476 */ 29477/** Layer source with georeferencement to place it on a map 29478 * @constructor 29479 * @extends {ol.source.ImageCanvas} 29480 * @param {GeoImageOptions} options 29481 */ 29482ol.source.GeoImage = function(opt_options) { 29483 var options = { 29484 attributions: opt_options.attributions, 29485 logo: opt_options.logo, 29486 projection: opt_options.projection 29487 }; 29488 // options.projection = opt_options.projection; 29489 // Load Image 29490 this._image = (opt_options.image ? opt_options.image : new Image ); 29491 this._image.crossOrigin = opt_options.crossOrigin; // 'anonymous'; 29492 // Show image on load 29493 var self = this; 29494 this._image.onload = function() { 29495 self.setCrop (self.crop); 29496 self.changed(); 29497 } 29498 if (!opt_options.image) this._image.src = opt_options.url; 29499 // Draw image on canvas 29500 options.canvasFunction = this.calculateImage; 29501 ol.source.ImageCanvas.call (this, options); 29502 // Coordinate of the image center 29503 this.center = opt_options.imageCenter; 29504 // Image scale 29505 this.setScale(opt_options.imageScale); 29506 // Rotation of the image 29507 this.rotate = opt_options.imageRotate ? opt_options.imageRotate : 0; 29508 // Crop of the image 29509 this.crop = opt_options.imageCrop; 29510 // Mask of the image 29511 this.mask = opt_options.imageMask; 29512 // Crop 29513 this.setCrop (this.crop); 29514 // Calculate extent on change 29515 this.on('change', function() { 29516 this.set('extent', this.calculateExtent()); 29517 }.bind(this)); 29518}; 29519ol.ext.inherits(ol.source.GeoImage, ol.source.ImageCanvas); 29520/** calculate image at extent / resolution 29521 * @param {ol/extent/Extent} extent 29522 * @param {number} resolution 29523 * @param {number} pixelRatio 29524 * @param {ol/size/Size} size 29525 * @return {HTMLCanvasElement} 29526 */ 29527ol.source.GeoImage.prototype.calculateImage = function(extent, resolution, pixelRatio, size) { 29528 if (!this.center) return; 29529 var canvas = document.createElement('canvas'); 29530 canvas.width = size[0]; 29531 canvas.height = size[1]; 29532 var ctx = canvas.getContext('2d'); 29533 if (!this._imageSize) return canvas; 29534 // transform coords to pixel 29535 function tr(xy) { 29536 return [ 29537 (xy[0]-extent[0])/(extent[2]-extent[0]) * size[0], 29538 (xy[1]-extent[3])/(extent[1]-extent[3]) * size[1] 29539 ]; 29540 } 29541 // Clipping mask 29542 if (this.mask) { 29543 ctx.beginPath(); 29544 var p = tr(this.mask[0]); 29545 ctx.moveTo(p[0],p[1]); 29546 for (var i=1; i<this.mask.length; i++) { 29547 p = tr(this.mask[i]); 29548 ctx.lineTo(p[0],p[1]); 29549 } 29550 ctx.clip(); 29551 } 29552 // Draw 29553 var pixel = tr(this.center);
29554 var dx = (this._image.naturalWidth/2 - this.crop[0]) *this.scale[0] /resolution *pixelRatio; 29555 var dy = (this._image.naturalHeight/2 - this.crop[1]) *this.scale[1] /resolution *pixelRatio; 29556 var sx = this._imageSize[0]*this.scale[0]/resolution *pixelRatio; 29557 var sy = this._imageSize[1]*this.scale[1]/resolution *pixelRatio; 29558 ctx.translate(pixel[0],pixel[1]); 29559 if (this.rotate) ctx.rotate(this.rotate); 29560 ctx.drawImage(this._image, this.crop[0], this.crop[1], this._imageSize[0], this._imageSize[1], -dx, -dy, sx,sy); 29561 return canvas; 29562} 29563/** 29564 * Get coordinate of the image center. 29565 * @return {ol.Coordinate} coordinate of the image center. 29566 * @api stable 29567 */ 29568ol.source.GeoImage.prototype.getCenter = function() { 29569 return this.center; 29570} 29571/** 29572 * Set coordinate of the image center. 29573 * @param {ol.Coordinate} coordinate of the image center. 29574 * @api stable 29575 */ 29576ol.source.GeoImage.prototype.setCenter = function(center) { 29577 this.center = center; 29578 this.changed(); 29579} 29580/** 29581 * Get image scale. 29582 * @return {ol.size} image scale (along x and y axis). 29583 * @api stable 29584 */ 29585ol.source.GeoImage.prototype.getScale = function() { 29586 return this.scale; 29587} 29588/** 29589 * Set image scale. 29590 * @param {ol.size|Number} image scale (along x and y axis or both). 29591 * @api stable 29592 */ 29593ol.source.GeoImage.prototype.setScale = function(scale) { 29594 switch (typeof(scale)) { 29595 case 'number': 29596 scale = [scale,scale]; 29597 break; 29598 case 'object': 29599 if (scale.length != 2) return; 29600 break; 29601 default: return; 29602 } 29603 this.scale = scale; 29604 this.changed(); 29605}; 29606/** 29607 * Get image rotation. 29608 * @return {Number} rotation in degre. 29609 * @api stable 29610 */ 29611ol.source.GeoImage.prototype.getRotation = function() { 29612 return this.rotate; 29613}; 29614/** 29615 * Set image rotation. 29616 * @param {Number} rotation in radian. 29617 * @api stable 29618 */ 29619ol.source.GeoImage.prototype.setRotation = function(angle) { 29620 this.rotate = angle; 29621 this.changed(); 29622}; 29623/** 29624 * Get the image. 29625 * @api stable 29626 */ 29627ol.source.GeoImage.prototype.getGeoImage = function() { 29628 return this._image; 29629}; 29630/** 29631 * Get image crop extent. 29632 * @return {ol.extent} image crop extent. 29633 * @api stable 29634 */ 29635ol.source.GeoImage.prototype.getCrop = function() { 29636 return this.crop; 29637}; 29638/** 29639 * Set image mask. 29640 * @param {ol.geom.LineString} coords of the mask 29641 * @api stable 29642 */ 29643ol.source.GeoImage.prototype.setMask = function(mask) { 29644 this.mask = mask; 29645 this.changed(); 29646}; 29647/** 29648 * Get image mask. 29649 * @return {ol.geom.LineString} coords of the mask 29650 * @api stable 29651 */ 29652ol.source.GeoImage.prototype.getMask = function() { 29653 return this.mask; 29654}; 29655/** 29656 * Set image crop extent. 29657 * @param {ol.extent|Number} image crop extent or a number to crop from original size. 29658 * @api stable 29659 */ 29660ol.source.GeoImage.prototype.setCrop = function(crop) { 29661 // Image not loaded => get it latter 29662 if (!this._image.naturalWidth) { 29663 this.crop = crop; 29664 return; 29665 } 29666 if (crop) { 29667 switch (typeof(crop)) { 29668 case 'number': 29669 crop = [crop,crop,this._image.naturalWidth-crop,this._image.naturalHeight-crop]; 29670 break; 29671 case 'object': 29672 if (crop.length != 4) return; 29673 break; 29674 default: return; 29675 } 29676 crop = ol.extent.boundingExtent([ [crop[0],crop[1]], [crop[2],crop[3]] ]); 29677 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]) ]; 29678 } 29679 else this.crop = [0,0, this._image.naturalWidth,this._image.naturalHeight]; 29680 if (this.crop[2]<=this.crop[0]) this.crop[2] = this.crop[0]+1; 29681 if (this.crop[3]<=this.crop[1]) this.crop[3] = this.crop[1]+1; 29682 this._imageSize = [ this.crop[2]-this.crop[0], this.crop[3]-this.crop[1] ]; 29683 this.changed(); 29684}; 29685/** Get the extent of the source. 29686 * @param {module:ol/extent~Extent} extent If provided, no new extent will be created. Instead, that extent's coordinates will be overwritten. 29687 * @return {ol.extent} 29688 */ 29689ol.source.GeoImage.prototype.getExtent = function(opt_extent) { 29690 var ext = this.get('extent'); 29691 if (!ext) ext = this.calculateExtent(); 29692 if (opt_extent) { 29693 for (var i=0; i<opt_extent.length; i++) { 29694 opt_extent[i] = ext[i]; 29695 } 29696 } 29697 return ext; 29698}; 29699/** Calculate the extent of the source image. 29700 * @param {boolean} usemask return the mask extent, default return the image extent 29701 * @return {ol.extent} 29702 */ 29703ol.source.GeoImage.prototype.calculateExtent = function(usemask) { 29704 var polygon; 29705 if (usemask!==false && this.getMask()) { 29706 polygon = new ol.geom.Polygon([this.getMask()]) 29707 } else { 29708 var center = this.getCenter();
29709 var scale = this.getScale(); 29710 var width = this.getGeoImage().width * scale[0]; 29711 var height = this.getGeoImage().height * scale[1]; 29712 var extent = ol.extent.boundingExtent([ 29713 [ center[0]-width/2, center[1]-height/2 ], 29714 [ center[0]+width/2, center[1]+height/2 ] 29715 ]); 29716 polygon = ol.geom.Polygon.fromExtent(extent); 29717 polygon.rotate(-this.getRotation(), center); 29718 } 29719 var ext = polygon.getExtent(); 29720 return ext; 29721}; 29722 29723/* Copyright (c) 2019 Jean-Marc VIGLINO, 29724 released under the CeCILL-B license (French BSD license) 29725 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 29726*/ 29727/** ol.source.GeoRSS is a source that load Wikimedia Commons content in a vector layer. 29728 * @constructor 29729 * @extends {ol.source.Vector} 29730 * @param {*} options source options 29731 * @param {string} options.url GeoRSS feed url 29732 */ 29733ol.source.GeoRSS = function(options) { 29734 options = options || {}; 29735 options.loader = this._loaderFn; 29736 ol.source.Vector.call (this, options); 29737}; 29738ol.ext.inherits(ol.source.GeoRSS, ol.source.Vector); 29739/** Loader function used to load features. 29740* @private 29741*/ 29742ol.source.GeoRSS.prototype._loaderFn = function(extent, resolution, projection){ 29743 // Ajax request to get source 29744 ol.ext.Ajax.get({ 29745 url: this.getUrl(), 29746 dataType: 'XML', 29747 error: function(){ console.log('oops'); }, 29748 success: function(xml) { 29749 var features = (new ol.format.GeoRSS()).readFeatures(xml, { featureProjection: projection }); 29750 this.addFeatures(features); 29751 }.bind(this) 29752 }); 29753}; 29754 29755/* Copyright (c) 2019 Jean-Marc VIGLINO, 29756 released under the CeCILL-B license (French BSD license) 29757 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 29758*/ 29759/** IGN's Geoportail WMTS source 29760 * @constructor 29761 * @extends {ol.source.WMTS} 29762 * @param {olx.source.Geoportail=} options WMTS options 29763 * @param {string=} options.layer Geoportail layer name 29764 * @param {number} options.minZoom 29765 * @param {number} options.maxZoom 29766 * @param {string} options.server 29767 * @param {string} options.gppKey api key, default 'choisirgeoportail' 29768 * @param {string} options.authentication basic authentication associated with the gppKey as btoa("login:pwd") 29769 * @param {string} options.format image format, default 'image/jpeg' 29770 * @param {string} options.style layer style, default 'normal' 29771 * @param {string} options.crossOrigin default 'anonymous' 29772 * @param {string} options.wrapX default true 29773 */ 29774ol.source.Geoportail = function (layer, options) { 29775 options = options || {}; 29776 if (layer.layer) { 29777 options = layer; 29778 layer = options.layer; 29779 } 29780 var matrixIds = new Array(); 29781 var resolutions = new Array();//[156543.03392804103,78271.5169640205,39135.75848201024,19567.879241005125,9783.939620502562,4891.969810251281,2445.9849051256406,1222.9924525628203,611.4962262814101,305.74811314070485,152.87405657035254,76.43702828517625,38.218514142588134,19.109257071294063,9.554628535647034,4.777314267823517,2.3886571339117584,1.1943285669558792,0.5971642834779396,0.29858214173896974,0.14929107086948493,0.07464553543474241]; 29782 var size = ol.extent.getWidth(ol.proj.get('EPSG:3857').getExtent()) / 256; 29783 for (var z=0; z <= (options.maxZoom ? options.maxZoom : 20) ; z++) { 29784 matrixIds[z] = z ; 29785 resolutions[z] = size / Math.pow(2, z); 29786 } 29787 var tg = new ol.tilegrid.WMTS ({ 29788 origin: [-20037508, 20037508], 29789 resolutions: resolutions, 29790 matrixIds: matrixIds 29791 }); 29792 tg.minZoom = (options.minZoom ? options.minZoom : 0); 29793 var attr = [ ol.source.Geoportail.prototype.attribution ]; 29794 if (options.attributions) attr = options.attributions; 29795 this._server = options.server || 'https://wxs.ign.fr/geoportail/wmts'; 29796 this._gppKey = options.gppKey || options.key || 'choisirgeoportail'; 29797 var wmts_options = { 29798 url: this.serviceURL(), 29799 layer: layer, 29800 matrixSet: 'PM', 29801 format: options.format ? options.format : 'image/jpeg', 29802 projection: 'EPSG:3857', 29803 tileGrid: tg, 29804 style: options.style ? options.style : 'normal', 29805 attributions: attr, 29806 crossOrigin: (typeof options.crossOrigin == 'undefined') ? 'anonymous' : options.crossOrigin, 29807 wrapX: !(options.wrapX===false) 29808 }; 29809 ol.source.WMTS.call(this, wmts_options); 29810 // Load url using basic authentification 29811 if (options.authentication) { 29812 this.setTileLoadFunction(ol.source.Geoportail.tileLoadFunctionWithAuthentication(options.authentication, this.getFormat())); 29813 } 29814}; 29815ol.ext.inherits(ol.source.Geoportail, ol.source.WMTS); 29816/** Standard IGN-GEOPORTAIL attribution 29817*/ 29818ol.source.Geoportail.prototype.attribution = '<a href="http://www.geoportail.gouv.fr/">Géoportail</a> © <a href="http://www.ign.fr/">IGN-France</a>'; 29819/** Get service URL according to server url or standard url 29820*/ 29821ol.source.Geoportail.prototype.serviceURL = function() { 29822 if (this._server) { 29823 return this._server.replace (/^(https?:\/\/[^/]*)(.*)$/, "$1/"+this._gppKey+"$2") ; 29824 } else { 29825 return (window.geoportailConfig ? window.geoportailConfig.url : "https://wxs.ign.fr/") +this._gppKey+ "/geoportail/wmts" ; 29826 } 29827}; 29828/** 29829 * Return the associated API key of the Map. 29830 * @function 29831 * @return the API key. 29832 * @api stable 29833 */ 29834ol.source.Geoportail.prototype.getGPPKey = function() { 29835 return this._gppKey; 29836}; 29837/** 29838 * Set the associated API key to the Map. 29839 * @param {String} key the API key. 29840 * @param {String} authentication as btoa("login:pwd") 29841 * @api stable 29842 */ 29843ol.source.Geoportail.prototype.setGPPKey = function(key, authentication) { 29844 this._gppKey = key; 29845 var serviceURL = this.serviceURL(); 29846 this.setTileUrlFunction (function() { 29847 var url = ol.source.Geoportail.prototype.getTileUrlFunction().apply(this, arguments); 29848 if (url) { 29849 var args = url.split("?"); 29850 return serviceURL+"?"+args[1]; 29851 } 29852 else return url; 29853 }); 29854 // Load url using basic authentification 29855 if (authentication) { 29856 this.setTileLoadFunction(ol.source.Geoportail.tileLoadFunctionWithAuthentication(authentication, this.getFormat())); 29857 } 29858}; 29859/** Return the GetFeatureInfo URL for the passed coordinate, resolution, and 29860 * projection. Return `undefined` if the GetFeatureInfo URL cannot be 29861 * constructed. 29862 * @param {ol.Coordinate} coord 29863 * @param {Number} resolution 29864 * @param {ol.proj.Projection} projection default the source projection 29865 * @param {Object} options 29866 * @param {string} options.INFO_FORMAT response format text/plain, text/html, application/json, default text/plain 29867 * @return {String|undefined} GetFeatureInfo URL. 29868 */ 29869ol.source.Geoportail.prototype.getFeatureInfoUrl = function(coord, resolution, projection, options) { 29870 options = options || {}; 29871 if (!projection) projection = this.getProjection(); 29872 var tileCoord = this.tileGrid.getTileCoordForCoordAndResolution(coord, resolution); 29873 var ratio = 1; 29874 var url = this.getTileUrlFunction()(tileCoord, ratio, projection); 29875 if (!url) return url; 29876 var tileResolution = this.tileGrid.getResolution(tileCoord[0]); 29877 var tileExtent = this.tileGrid.getTileCoordExtent(tileCoord); 29878 var i = Math.floor((coord[0] - tileExtent[0]) / (tileResolution / ratio));
29879 var j = Math.floor((tileExtent[3] - coord[1]) / (tileResolution / ratio)); 29880 return url.replace(/Request=GetTile/i, 'Request=getFeatureInfo') 29881 +'&INFOFORMAT='+(options.INFO_FORMAT||'text/plain') 29882 +'&I='+i 29883 +'&J='+j; 29884}; 29885/** Get feature info 29886 * @param {ol.Coordinate} coord 29887 * @param {Number} resolution 29888 * @param {ol.proj.Projection} projection default the source projection 29889 * @param {Object} options 29890 * @param {string} options.INFO_FORMAT response format text/plain, text/html, application/json, default text/plain 29891 * @param {function} options.callback a function that take the response as parameter 29892 * @param {function} options.error function called when an error occurred 29893 */ 29894ol.source.Geoportail.prototype.getFeatureInfo = function(coord, resolution, options) { 29895 var url = this.getFeatureInfoUrl(coord, resolution, null, options); 29896 ol.ext.Ajax.get({ 29897 url: url, 29898 dataType: options.format || 'text/plain', 29899 options: { 29900 encode: false 29901 }, 29902 success: function(resp) { 29903 if (options.callback) options.callback(resp); 29904 }, 29905 error: options.error || function(){} 29906 }) 29907}; 29908/** Get a tile load function to load tiles with basic authentication 29909 * @param {string} authentication as btoa("login:pwd") 29910 * @param {string} format mime type 29911 * @return {function} tile load function to load tiles with basic authentication 29912 */ 29913ol.source.Geoportail.tileLoadFunctionWithAuthentication = function(authentication, format) { 29914 if (!authentication) return undefined; 29915 return function(tile, src) { 29916 var xhr = new XMLHttpRequest(); 29917 xhr.open("GET", src); 29918 xhr.setRequestHeader("Authorization", "Basic " + authentication); 29919 xhr.responseType = "arraybuffer"; 29920 xhr.onload = function () { 29921 var arrayBufferView = new Uint8Array(this.response); 29922 var blob = new Blob([arrayBufferView], { type: format }); 29923 var urlCreator = window.URL || window.webkitURL; 29924 var imageUrl = urlCreator.createObjectURL(blob); 29925 tile.getImage().src = imageUrl; 29926 }; 29927 xhr.onerror = function () { 29928 tile.getImage().src = ""; 29929 }; 29930 xhr.send(); 29931 }; 29932}; 29933 29934/* Copyright (c) 2019 Jean-Marc VIGLINO, 29935 released under the CeCILL-B license (French BSD license) 29936 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 29937*/ 29938/** A source for grid binning 29939 * @constructor 29940 * @extends {ol.source.Vector} 29941 * @param {Object}
29941 options ol.source.VectorOptions + grid option 29942 * @param {ol.source.Vector} options.source Source 29943 * @param {number} [options.size] size of the grid in meter, default 200m 29944 * @param {function} [options.geometryFunction] Function that takes an ol.Feature as argument and returns an ol.geom.Point as feature's center. 29945 * @param {function} [options.flatAttributes] Function takes a bin and the features it contains and aggragate the features in the bin attributes when saving 29946 */ 29947ol.source.GridBin = function (options) { 29948 options = options || {}; 29949 ol.source.BinBase.call(this, options); 29950 this.set('gridProjection', options.gridProjection || 'EPSG:4326'); 29951 this.set('size', options.size || 1); 29952}; 29953ol.ext.inherits(ol.source.GridBin, ol.source.BinBase); 29954/** Set grid projection 29955 * @param {ol.ProjectionLike} proj 29956 */ 29957ol.source.GridBin.prototype.setGridProjection = function (proj) { 29958 this.set('gridProjection', proj); 29959 this.reset(); 29960}; 29961/** Set grid size 29962 * @param {number} size 29963 */ 29964ol.source.GridBin.prototype.setSize = function (size) { 29965 this.set('size', size); 29966 this.reset(); 29967}; 29968/** Get the grid geometry at the coord 29969 * @param {ol.Coordinate} coord 29970 * @returns {ol.geom.Polygon} 29971 * @api 29972 */ 29973ol.source.GridBin.prototype.getGridGeomAt = function (coord) { 29974 coord = ol.proj.transform (coord, this.getProjection() || 'EPSG:3857', this.get('gridProjection')); 29975 var size = this.get('size'); 29976 var x = size * Math.floor(coord[0] / size); 29977 var y = size * Math.floor(coord[1] / size); 29978 var geom = new ol.geom.Polygon([[[x,y], [x+size,y], [x+size,y+size], [x,y+size], [x,y]]]); 29979 return geom.transform(this.get('gridProjection'), this.getProjection() || 'EPSG:3857'); 29980}; 29981 29982/* Copyright (c) 2017-2019 Jean-Marc VIGLINO, 29983 released under the CeCILL-B license (French BSD license) 29984 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 29985*/ 29986/** A source for hexagonal binning 29987 * @constructor 29988 * @extends {ol.source.Vector} 29989 * @param {Object}
29989 options ol.source.VectorOptions + ol.HexGridOptions 29990 * @param {ol.source.Vector} options.source Source 29991 * @param {number} [options.size] size of the hexagon in map units, default 80000 29992 * @param {ol.coordinate} [options.origin] origin of the grid, default [0,0] 29993 * @param {HexagonLayout} [options.layout] grid layout, default pointy 29994 * @param {function} [options.geometryFunction] Function that takes an ol.Feature as argument and returns an ol.geom.Point as feature's center. 29995 * @param {function} [options.flatAttributes] Function takes a bin and the features it contains and aggragate the features in the bin attributes when saving 29996 */ 29997ol.source.HexBin = function (options) { 29998 options = options || {}; 29999 /** The HexGrid 30000 * @type {ol.HexGrid} 30001 */ 30002 this._hexgrid = new ol.HexGrid(options); 30003 ol.source.BinBase.call(this, options); 30004}; 30005ol.ext.inherits(ol.source.HexBin, ol.source.BinBase); 30006/** Get the hexagon geometry at the coord 30007 * @param {ol.Coordinate} coord 30008 * @returns {ol.geom.Polygon} 30009 * @api 30010 */ 30011ol.source.HexBin.prototype.getGridGeomAt = function (coord) {
30012 var h = this._hexgrid.coord2hex(coord); 30013 return new ol.geom.Polygon([this._hexgrid.getHexagon(h)]) 30014}; 30015/** Set the inner HexGrid size. 30016 * @param {number} newSize 30017 * @param {boolean} noreset If true, reset will not be called (It need to be called through) 30018 */ 30019ol.source.HexBin.prototype.setSize = function (newSize, noreset) { 30020 this._hexgrid.setSize(newSize); 30021 if (!noreset) { 30022 this.reset(); 30023 } 30024} 30025/** Get the inner HexGrid size. 30026 * @return {number} 30027 */ 30028ol.source.HexBin.prototype.getSize = function () { 30029 return this._hexgrid.getSize(); 30030} 30031/** Set the inner HexGrid layout. 30032 * @param {HexagonLayout} newLayout 30033 * @param {boolean} noreset If true, reset will not be called (It need to be called through) 30034 */ 30035ol.source.HexBin.prototype.setLayout = function (newLayout, noreset) { 30036 this._hexgrid.setLayout(newLayout); 30037 if (!noreset) { 30038 this.reset(); 30039 } 30040} 30041/** Get the inner HexGrid layout. 30042 * @return {HexagonLayout} 30043 */ 30044ol.source.HexBin.prototype.getLayout = function () { 30045 return this._hexgrid.getLayout(); 30046}; 30047/** Set the inner HexGrid origin. 30048 * @param {ol.Coordinate} newLayout 30049 * @param {boolean} noreset If true, reset will not be called (It need to be called through) 30050 */ 30051ol.source.HexBin.prototype.setOrigin = function (newLayout, noreset) { 30052 this._hexgrid.setOrigin(newLayout); 30053 if (!noreset) { 30054 this.reset(); 30055 } 30056}; 30057/** Get the inner HexGrid origin. 30058 * @return {ol.Coordinate} 30059 */ 30060ol.source.HexBin.prototype.getOrigin = function () { 30061 return this._hexgrid.getOrigin(); 30062}; 30063/** 30064 * Get hexagons without circular dependencies (vs. getFeatures) 30065 * @return {Array<ol.Feature>} 30066 */ 30067ol.source.HexBin.prototype.getHexFeatures = function () { 30068 return ol.source.BinBase.prototype.getGridFeatures.call(this); 30069}; 30070 30071/* Copyright (c) 2021 Jean-Marc VIGLINO, 30072 released under the CeCILL-B license (French BSD license) 30073*/ 30074/** Inverse distance weighting interpolated source - Shepard's method 30075 * @see https://en.wikipedia.org/wiki/Inverse_distance_weighting 30076 * @constructor 30077 * @extends {ol.source.ImageCanvas} 30078 * @param {*} [options] 30079 * @param {ol.source.vector} options.source a source to interpolate 30080 * @param {number} [options.scale=4] scale factor, use large factor to enhance performances (but minor accuracy) 30081 * @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. 30082 */ 30083ol.source.IDW = function(options) { 30084 options = options || {}; 30085 // Draw image on canvas 30086 options.canvasFunction = this.calculateImage; 30087 this._source = options.source; 30088 this._canvas = document.createElement('CANVAS'); 30089 this._source.on(['addfeature','removefeature','clear','removefeature'], function() { 30090 this.changed(); 30091 }.bind(this)); 30092 ol.source.ImageCanvas.call (this, options); 30093 this.set('scale', options.scale || 4); 30094 this._weight = typeof(options.weight) === 'function' ? options.weight : function(f) { return f.get(options.weight||'weight'); } 30095}; 30096ol.ext.inherits(ol.source.IDW, ol.source.ImageCanvas); 30097/** Get the source 30098 */ 30099ol.source.IDW.prototype.getSource = function() { 30100 return this._source; 30101}; 30102/** Convert hue to rgb factor 30103 * @param {number} h 30104 * @return {number} 30105 * @private 30106 */ 30107ol.source.IDW.prototype.hue2rgb = function(h) { 30108 h = (h + 6) % 6; 30109 if (h < 1) return Math.round(h * 255); 30110 if (h < 3) return 255; 30111 if (h < 4) return Math.round((4 - h) * 255); 30112 return 0; 30113}; 30114/** Get color for a value. Return an array of RGBA values. 30115 * @param {number} v value 30116 * @returns {Array<number>} 30117 * @api 30118 */ 30119ol.source.IDW.prototype.getColor = function(v) { 30120 // Get hue 30121 var h = 4 - (0.04 * v); 30122 // Convert to RGB 30123 return [ 30124 this.hue2rgb(h + 2), 30125 this.hue2rgb(h), 30126 this.hue2rgb(h - 2), 30127 255 30128 ] 30129}; 30130/** Apply the value to the map RGB. Overwrite this function to set your own colors. 30131 * @param {number} v value 30132 * @param {Uint8ClampedArray} data RGBA array 30133 * @param {number} i index in the RGBA array 30134 * @api 30135 */ 30136ol.source.IDW.prototype.setData = function(v, data, i) { 30137 // Get color 30138 var color = this.getColor(v) 30139 // Convert to RGB 30140 data[i] = color[0]; 30141 data[i+1] = color[1]; 30142 data[i+2] = color[2]; 30143 data[i+3] = color[3]; 30144}; 30145/** Get image value at coord (RGBA) 30146 * @param {l.coordinate} coord 30147 * @return {Uint8ClampedArray} 30148 */ 30149ol.source.IDW.prototype.getValue = function(coord) { 30150 if (!this._canvas) return null 30151 var pt = this.transform(coord); 30152 var v = this._canvas.getContext('2d').getImageData(Math.round(pt[0]), Math.round(pt[1]), 1, 1).data; 30153 return (v); 30154}; 30155/** Calculate IDW at extent / resolution 30156 * @param {ol/extent/Extent} extent 30157 * @param {number} resolution 30158 * @param {number} pixelRatio 30159 * @param {ol/size/Size} size 30160 * @return {HTMLCanvasElement} 30161 * @private 30162 */ 30163ol.source.IDW.prototype.calculateImage = function(extent, resolution, pixelRatio, size) { 30164 if (!this._source) return this._canvas; 30165 // Calculation canvas at small resolution 30166 var canvas = document.createElement('CANVAS'); 30167 var width = canvas.width = Math.round(size[0] / (this.get('scale')*pixelRatio)); 30168 var height = canvas.height = Math.round(size[1] / (this.get('scale')*pixelRatio)); 30169 var ctx = canvas.getContext('2d'); 30170 var imageData = ctx.getImageData(0, 0, width, height); 30171 // Transform coords to pixel / value 30172 var pts = []; 30173 var dw = width / (extent[2]-extent[0]); 30174 var dh = height / (extent[1]-extent[3]); 30175 var tr = this.transform = function(xy, v) { 30176 return [ 30177 (xy[0]-extent[0]) * dw, 30178 (xy[1]-extent[3]) * dh, 30179 v 30180 ]; 30181 } 30182 // Get features / weight
30183 this._source.getFeatures().forEach(function(f) { 30184 pts.push(tr(f.getGeometry().getFirstCoordinate(), this._weight(f))); 30185 }.bind(this)); 30186 // Compute image 30187 var x, y; 30188 for (y = 0; y < height; y++) { 30189 for (x = 0; x < width; x++) { 30190 var t = 0, b = 0; 30191 for(var i = 0; i < pts.length; ++i) { 30192 var dx = x - pts[i][0]; 30193 var dy = y - pts[i][1]; 30194 var d = dx*dx + dy*dy; 30195 // Inverse distance weighting - Shepard's method 30196 if (d === 0) { 30197 b = 1; 30198 t = pts[i][2]; 30199 break; 30200 } 30201 var inv = 1 / (d*d); 30202 t += inv * pts[i][2]; 30203 b += inv; 30204 } 30205 this.setData(t/b, imageData.data, (y*width + x)*4); 30206 } 30207 } 30208 ctx.putImageData(imageData, 0, 0); 30209 /* DEBUG : Draw points * / 30210 pts.forEach(function(p) { 30211 ctx.fillRect(p[0], p[1], 1, 1); 30212 }); 30213 /**/ 30214 // Draw full resolution canvas 30215 this._canvas.width = Math.round(size[0]); 30216 this._canvas.height = Math.round(size[1]); 30217 this._canvas.getContext('2d').drawImage(canvas, 0, 0, size[0], size[1]); 30218 return this._canvas; 30219} 30220 30221/* Copyright (c) 2019 Jean-Marc VIGLINO, 30222 released under the CeCILL-B license (French BSD license) 30223 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 30224*/ 30225/** A source for INSEE grid 30226 * @constructor 30227 * @extends {ol.source.Vector} 30228 * @param {Object}
30228 options ol.source.VectorOptions + grid option 30229 * @param {ol.source.Vector} options.source Source 30230 * @param {number} [options.size] size of the grid in meter, default 200m 30231 * @param {function} [options.geometryFunction] Function that takes an ol.Feature as argument and returns an ol.geom.Point as feature's center. 30232 * @param {function} [options.flatAttributes] Function takes a bin and the features it contains and aggragate the features in the bin attributes when saving 30233 */ 30234ol.source.InseeBin = function (options) { 30235 options = options || {}; 30236 this._grid = new ol.InseeGrid({ size: options.size }); 30237 ol.source.BinBase.call(this, options); 30238}; 30239ol.ext.inherits(ol.source.InseeBin, ol.source.BinBase); 30240/** Set grid size 30241 * @param {number} size 30242 */ 30243ol.source.InseeBin.prototype.setSize = function (size) { 30244 if (this.getSize() !== size) { 30245 this._grid.set('size', size); 30246 this.reset(); 30247 } 30248}; 30249/** Get grid size 30250 * @return {number} size 30251 */ 30252ol.source.InseeBin.prototype.getSize = function () { 30253 return this._grid.get('size'); 30254}; 30255/** Get the grid geometry at the coord 30256 * @param {ol.Coordinate} coord 30257 * @returns {ol.geom.Polygon} 30258 * @api 30259 */ 30260ol.source.InseeBin.prototype.getGridGeomAt = function (coord) { 30261 return this._grid.getGridAtCoordinate(coord, this.getProjection()); 30262}; 30263/** Get grid extent 30264 * @param {ol.ProjectionLike} proj 30265 * @return {ol.Extent} 30266 */ 30267ol.source.InseeBin.prototype.getGridExtent = function (proj) { 30268 return this._grid.getExtent(proj); 30269}; 30270 30271/* Copyright (c) 2017 Jean-Marc VIGLINO, 30272 released under the CeCILL-B license (French BSD license) 30273 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 30274 @classdesc 30275 ol.source.Mapillary is a source that load Mapillary's geotagged photos in a vector layer. 30276 Inherits from: 30277 <ol.source.Vector> 30278*/ 30279/** 30280* @constructor ol.source.Mapillary 30281* @extends {ol.source.Vector} 30282* @param {olx.source.Mapillary=} options 30283*/ 30284ol.source.Mapillary = function(opt_options) 30285{ var options = opt_options || {}; 30286 options.loader = this._loaderFn; 30287 /** Max resolution to load features */ 30288 this._maxResolution = options.maxResolution || 100; 30289 /** Query limit */ 30290 this._limit = options.limit || 100; 30291 /** Default attribution */ 30292 if (!options.attributions) options.attributions = [ "© <a href='https://www.mapillary.com/'>Mapillary</a>" ]; 30293 // Bbox strategy : reload at each move 30294 if (!options.strategy) options.strategy = ol.loadingstrategy.bbox; 30295 // Init parent 30296 ol.source.Vector.call (this, options); 30297 // Client ID 30298 // this.set("clientId", options.clientId); 30299}; 30300ol.ext.inherits(ol.source.Mapillary, ol.source.Vector); 30301/** Decode wiki attributes and choose to add feature to the layer 30302* @param {feature} the feature 30303* @param {attributes} wiki attributes 30304* @return {boolean} true: add the feature to the layer 30305* @API stable 30306*/ 30307ol.source.Mapillary.prototype.readFeature = function (/*feature, attributes*/) 30308{ // Allways read feature (no filter) 30309 return true; 30310}; 30311/** Loader function used to load features. 30312* @private 30313*/ 30314ol.source.Mapillary.prototype._loaderFn = function(extent, resolution, projection) 30315{ if (resolution > this._maxResolution) return; 30316 var bbox = ol.proj.transformExtent(extent, projection, "EPSG:4326"); 30317 // Commons API: for more info @see https://www.mapillary.com/developer 30318 var date = Date.now() - 6 * 30 * 24 * 60 * 60 * 1000; 30319 var url = "https://a.mapillary.com/v2/search/im?client_id=" 30320 + this.get('clientId') 30321 + "&max_lat=" + bbox[3] 30322 + "&max_lon=" + bbox[2] 30323 + "&min_lat=" + bbox[1] 30324 + "&min_lon=" + bbox[0] 30325 + "&limit="+(this._limit-1) 30326 + "&start_time=" + date; 30327 // Ajax request to get the tile 30328 ol.ext.Ajax.get( 30329 { url: url, 30330 dataType: 'jsonp', 30331 success: function(data) 30332 { console.log(data); 30333 /* 30334 var features = []; 30335 var att, pt, feature, lastfeature = null; 30336 if (data.query && data.query.pages) return; 30337 for ( var i in data.query.pages) 30338 { att = data.query.pages[i]; 30339 if (att.coordinates && att.coordinates.length ) 30340 { pt = [att.coordinates[0].lon, att.coordinates[0].lat]; 30341 } 30342 else 30343 { var meta = att.imageinfo[0].metadata; 30344 if (!meta) 30345 { //console.log(att); 30346 continue; 30347 } 30348 pt = []; 30349 for (var k=0; k<meta.length; k++) 30350 { if (meta[k].name=="GPSLongitude") pt[0] = meta[k].value; 30351 if (meta[k].name=="GPSLatitude") pt[1] = meta[k].value; 30352 } 30353 if (!pt.length) 30354 { //console.log(att); 30355 continue; 30356 } 30357 } 30358 feature = new ol.Feature(new ol.geom.Point(ol.proj.transform (pt,"EPSG:4326",projection))); 30359 att.imageinfo[0].title = att.title; 30360 if (self.readFeature(feature, att.imageinfo[0])) 30361 { features.push(feature); 30362 } 30363 } 30364 self.addFeatures(features); 30365 */ 30366 }}
30366); 30367}; 30368 30369/* Copyright (c) 2019 Jean-Marc VIGLINO, 30370 released under the CeCILL-B license (French BSD license) 30371 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 30372*/ 30373/** A source to turn your maps into oil paintings... 30374 * Original idea: Santhosh G https://www.codeproject.com/Articles/471994/OilPaintEffect 30375 * JS implementation: Loktar (https://github.com/loktar00) https://codepen.io/loktar00/full/Fhzot/ 30376 * @constructor 30377 * @extends {ol.source.Vector} 30378 * @param {Object} options 30379 * @param {Array<ol/source/Source|ol/layer/Layer>} sources Input sources or layers. For vector data, use an VectorImage layer. 30380 * @param {number} radius default 4 30381 * @param {number} intensity default 25 30382 */ 30383ol.source.OilPainting = function (options) { 30384 options.operation = this._operation; 30385 options.operationType = 'image'; 30386 ol.source.Raster.call(this, options); 30387 this.set('radius', options.radius || 4); 30388 this.set('intensity', options.intensity || 25); 30389 this.on('beforeoperations', function (event) { 30390 var w = Math.round((event.extent[2]-event.extent[0])/event.resolution); 30391 var h = Math.round((event.extent[3]-event.extent[1])/event.resolution); 30392 event.data.image = new ImageData(w,h); 30393 event.data.radius = Number(this.get('radius')) || 1; 30394 event.data.intensity = Number(this.get('intensity')); 30395 }.bind(this)); 30396}; 30397ol.ext.inherits(ol.source.OilPainting, ol.source.Raster); 30398/** Set value and force change 30399 */ 30400ol.source.OilPainting.prototype.set = function(key, val) { 30401 if (val) { 30402 switch (key) { 30403 case 'intensity': 30404 case 'radius': { 30405 val = Number(val); 30406 if (val<1) val = 1; 30407 this.changed(); 30408 break; 30409 } 30410 } 30411 } 30412 return ol.source.Raster.prototype.set.call(this, key, val); 30413}; 30414/** 30415 * @private 30416 */ 30417ol.source.OilPainting.prototype._operation = function(pixels, data) { 30418 var width = pixels[0].width, 30419 height = pixels[0].height, 30420 imgData = pixels[0], 30421 pixData = imgData.data, 30422 pixelIntensityCount = []; 30423 var destImageData = data.image, 30424 destPixData = destImageData.data, 30425 intensityLUT = [], 30426 rgbLUT = []; 30427 for (var y = 0; y < height; y++) { 30428 intensityLUT[y] = []; 30429 rgbLUT[y] = []; 30430 for (var x = 0; x < width; x++) { 30431 var idx = (y * width + x) * 4, 30432 r = pixData[idx], 30433 g = pixData[idx + 1], 30434 b = pixData[idx + 2], 30435 avg = (r + g + b) / 3; 30436 intensityLUT[y][x] = Math.round((avg * data.intensity) / 255); 30437 rgbLUT[y][x] = { 30438 r: r, 30439 g: g, 30440 b: b 30441 }; 30442 } 30443 } 30444 var radius = data.radius; 30445 for (y = 0; y < height; y++) { 30446 for (x = 0; x < width; x++) { 30447 pixelIntensityCount = []; 30448 // Find intensities of nearest pixels within radius. 30449 for (var yy = -radius; yy <= radius; yy++) { 30450 for (var xx = -radius; xx <= radius; xx++) { 30451 if (y + yy > 0 && y + yy < height && x + xx > 0 && x + xx < width) { 30452 var intensityVal = intensityLUT[y + yy][x + xx]; 30453 if (!pixelIntensityCount[intensityVal]) { 30454 pixelIntensityCount[intensityVal] = { 30455 val: 1, 30456 r: rgbLUT[y + yy][x + xx].r, 30457 g: rgbLUT[y + yy][x + xx].g, 30458 b: rgbLUT[y + yy][x + xx].b 30459 } 30460 } else { 30461 pixelIntensityCount[intensityVal].val++; 30462 pixelIntensityCount[intensityVal].r += rgbLUT[y + yy][x + xx].r; 30463 pixelIntensityCount[intensityVal].g += rgbLUT[y + yy][x + xx].g; 30464 pixelIntensityCount[intensityVal].b += rgbLUT[y + yy][x + xx].b; 30465 } 30466 } 30467 } 30468 } 30469 pixelIntensityCount.sort(function (a, b) { 30470 return b.val - a.val; 30471 }); 30472 var curMax = pixelIntensityCount[0].val, 30473 dIdx = (y * width + x) * 4; 30474 destPixData[dIdx] = ~~ (pixelIntensityCount[0].r / curMax); 30475 destPixData[dIdx + 1] = ~~ (pixelIntensityCount[0].g / curMax); 30476 destPixData[dIdx + 2] = ~~ (pixelIntensityCount[0].b / curMax); 30477 destPixData[dIdx + 3] = 255; 30478 } 30479 } 30480 return destImageData; 30481}; 30482 30483/* Copyright (c) 2018 Jean-Marc VIGLINO, 30484 released under the CeCILL-B license (French BSD license)
30485 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 30486*/ 30487/** 30488 * OSM layer using the Ovepass API 30489 * @constructor ol.source.Overpass 30490 * @extends {ol.source.Vector} 30491 * @param {any} options 30492 * @param {string} options.url service url, default: https://overpass-api.de/api/interpreter 30493 * @param {Array<string>} options.filter an array of tag filters, ie. ["key", "key=value", "key~value", ...] 30494 * @param {boolean} options.node get nodes, default: true 30495 * @param {boolean} options.way get ways, default: true 30496 * @param {boolean} options.rel get relations, default: false 30497 * @param {number} options.maxResolution maximum resolution to load features 30498 * @param {string|ol.Attribution|Array<string>} options.attributions source attribution, default OSM attribution 30499 * @param {ol.loadingstrategy} options.strategy loading strategy, default ol.loadingstrategy.bbox 30500 */ 30501ol.source.Overpass = function(options) { 30502 options = options || {}; 30503 options.loader = this._loaderFn; 30504 /** Ovepass API Url */ 30505 this._url = options.url || 'https://overpass-api.de/api/interpreter'; 30506 /** Max resolution to load features */ 30507 this._maxResolution = options.maxResolution || 100; 30508 /** Default attribution */ 30509 if (!options.attributions) { 30510 options.attributions = ol.source.OSM.ATTRIBUTION; 30511 } 30512 // Bbox strategy : reload at each move 30513 if (!options.strategy) options.strategy = ol.loadingstrategy.bbox; 30514 ol.source.Vector.call (this, options); 30515 this._types = { 30516 node: options.node!==false, 30517 way: options.way!==false, 30518 rel: options.rel===true 30519 }; 30520 this._filter = options.filter; 30521}; 30522ol.ext.inherits(ol.source.Overpass, ol.source.Vector); 30523/** Loader function used to load features. 30524* @private 30525*/ 30526ol.source.Overpass.prototype._loaderFn = function(extent, resolution, projection) { 30527 if (resolution > this._maxResolution) return; 30528 var self = this; 30529 var bbox = ol.proj.transformExtent(extent, projection, "EPSG:4326"); 30530 bbox = bbox[1] + ',' + bbox[0] + ',' + bbox[3] + ',' + bbox[2]; 30531 // Overpass QL 30532 var query = '[bbox:'+bbox+'][out:xml][timeout:25];'; 30533 query += '('; 30534 // Search attributes 30535 for (var t in this._types) { 30536 if (this._types[t]) { 30537 query += t; 30538 for (var n=0, filter; filter = this._filter[n]; n++) { 30539 query += '['+filter+']'; 30540 } 30541 query += ';' 30542 } 30543 } 30544 query +=');out;>;out skel qt;' 30545 var ajax = new XMLHttpRequest(); 30546 ajax.open('POST', this._url, true); 30547 ajax.onload = function () { 30548 var features = new ol.format.OSMXML().readFeatures(this.responseText,{featureProjection: projection}); 30549 var result = []; 30550 // Remove duplicated features 30551 for (var i=0, f; f=features[i]; i++) { 30552 if (!self.hasFeature(f)) result.push(f); 30553 } 30554 self.addFeatures(result); 30555 }; 30556 ajax.onerror = function () { 30557 console.log(arguments); 30558 }; 30559 ajax.send('data='+query); 30560}; 30561/** 30562 * Search if feature is allready loaded 30563 * @param {ol.Feature} feature 30564 * @return {boolean} 30565 * @private 30566 */ 30567ol.source.Overpass.prototype.hasFeature = function(feature) { 30568 var p = feature.getGeometry().getFirstCoordinate(); 30569 var id = feature.getId(); 30570 var existing = this.getFeaturesInExtent([p[0]-0.1, p[1]-0.1, p[0]+0.1, p[1]+0.1]); 30571 for (var i=0, f; f=existing[i]; i++) { 30572 if (id===f.getId()) { 30573 return true; 30574 } 30575 } 30576 return false; 30577}; 30578 30579/* Copyright (c) 2019 Jean-Marc VIGLINO, 30580 released under the CeCILL-B license (French BSD license) 30581 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 30582*/ 30583/** A vector source to load WFS at a tile zoom level 30584 * @constructor 30585 * @fires tileloadstart 30586 * @fires tileloadend 30587 * @fires tileloaderror 30588 * @fires overload 30589 * @extends {ol.source.Vector} 30590 * @param {Object} options 30591 * @param {string} [options.version=1.1.0] WFS version to use. Can be either 1.0.0, 1.1.0 or 2.0.0. 30592 * @param {string} options.typeName WFS type name parameter 30593 * @param {number} options.tileZoom zoom to load the tiles 30594 * @param {number} options.maxFeatures maximum features returned in the WFS 30595 * @param {number} options.featureLimit maximum features in the source before refresh, default Infinity 30596 * @param {boolean} [options.pagination] experimental enable pagination, default no pagination 30597 */ 30598ol.source.TileWFS = function (options) { 30599 options = options || {}; 30600 if (!options.featureLimit) options.featureLimit = Infinity; 30601 // Tile loading strategy 30602 var zoom = options.tileZoom || 14; 30603 var sourceOpt = { 30604 strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ minZoom: zoom, maxZoom: zoom, tileSize:512 })) 30605 }; 30606 // Loading params 30607 var format = new ol.format.GeoJSON(); 30608 var url = options.url 30609 + '?service=WFS' 30610 + '&request=GetFeature' 30611 + '&version=' + (options.version || '1.1.0') 30612 + '&typename=' + (options.typeName || '') 30613 + '&outputFormat=application/json'; 30614 if (options.maxFeatures) { 30615 url += '&maxFeatures=' + options.maxFeatures + '&count=' + options.maxFeatures; 30616 } 30617 var loader = { loading: 0, loaded: 0 }; 30618 // Loading fn 30619 sourceOpt.loader = function(extent, resolution, projection) { 30620 if (loader.loading === loader.loaded) { 30621 loader.loading = loader.loaded = 0; 30622 if (this.getFeatures().length > options.maxFeatures) { 30623 this.clear(); 30624 this.refresh(); 30625 } 30626 } 30627 loader.loading++; 30628 this.dispatchEvent({ 30629 type: 'tileloadstart', 30630 loading: loader.loading, 30631 loaded: loader.loaded 30632 }); 30633 this._loadTile(url, extent, projection, format, loader); 30634 } 30635 ol.source.Vector.call(this, sourceOpt); 30636 this.set('pagination', options.pagination); 30637}; 30638ol.ext.inherits(ol.source.TileWFS, ol.source.Vector); 30639/** 30640 * 30641 */ 30642ol.source.TileWFS.prototype._loadTile = function(url, extent, projection, format, loader) { 30643 var req = url 30644 + '&srsname=' + projection.getCode() 30645 + '&bbox=' + extent.join(',') + ',' + projection.getCode(); 30646 if (this.get('pagination') && !/&startIndex/.test(url)) { 30647 req += '&startIndex=0'; 30648 } 30649 ol.ext.Ajax.get({ 30650 url: req, 30651 success: function(response) { 30652 loader.loaded++; 30653 if (response.error) { 30654 this.dispatchEvent({ 30655 type: 'tileloaderror', 30656 error: response, 30657 loading: loader.loading, 30658 loaded: loader.loaded 30659 }); 30660 } else { 30661 // Load features 30662 var features = format.readFeatures(response, { 30663 featureProjection: projection 30664 }); 30665 if (features.length > 0) { 30666 this.addFeatures(features); 30667 } 30668 // Next page? 30669 var pos = response.numberReturned || 0; 30670 if (/&startIndex/.test(url)) { 30671 pos += parseInt(url.replace(/.*&startIndex=(\d*).*/, '$1')); 30672 url = url.replace(/&startIndex=(\d*)/, ''); 30673 } 30674 // Still something to load ? 30675 if (pos < response.totalFeatures) { 30676 if (!this.get('pagination')) { 30677 this.dispatchEvent({ type: 'overload', total: response.totalFeatures, returned: response.numberReturned }); 30678 this.dispatchEvent({ 30679 type: 'tileloadend', 30680 loading: loader.loading, 30681 loaded: loader.loaded 30682 }); 30683 } else { 30684 url += '&startIndex='+pos; 30685 loader.loaded--; 30686 this._loadTile(url, extent, projection, format, loader); 30687 } 30688 } else { 30689 this.dispatchEvent({ 30690 type: 'tileloadend', 30691 loading: loader.loading, 30692 loaded: loader.loaded 30693 }); 30694 } 30695 } 30696 }.bind(this), 30697 error: function(e) { 30698 loader.loaded++; 30699 this.dispatchEvent({ 30700 type: 'tileloaderror', 30701 error: e, 30702 loading: loader.loading, 30703 loaded: loader.loaded 30704 }); 30705 }.bind(this) 30706 }) 30707}; 30708 30709(function () { 30710 var clear = ol.source.Vector.prototype.clear; 30711 /** Overwrite ol/source/Vector clear to fire clearstart / clearend event 30712 */ 30713 ol.source.Vector.prototype.clear = function(opt_fast) { 30714 this.dispatchEvent({ type: 'clearstart' }); 30715 clear.call(this, opt_fast) 30716 this.dispatchEvent({ type: 'clearend' }); 30717 }; 30718})(); 30719 30720/** 30721 * @classdesc 3D vector layer rendering 30722 * @constructor 30723 * @extends {pl.layer.Image} 30724 * @param {Object} options 30725 * @param {ol.layer.Vector} options.source the source to display in 3D 30726 * @param {ol.style.Style} options.styler drawing style 30727 * @param {number} options.maxResolution max resolution to render 3D 30728 * @param {number} options.defaultHeight default height if none is return by a propertie 30729 * @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 30730 * @param {Array<number>} options.center center of the view, default [.5,1] 30731 */ 30732ol.layer.Vector3D = function (options) { 30733 options = options || {}; 30734 this._source = options.source; 30735 this.height_ = this.getHfn (options.height); 30736 var canvas = document.createElement('canvas'); 30737 ol.layer.Image.call (this, { 30738 source: new ol.source.ImageCanvas({ 30739 canvasFunction: function(extent, resolution, pixelRatio, size /*, projection*/ ) { 30740 canvas.width = size[0]; 30741 canvas.height = size[1]; 30742 return canvas; 30743 } 30744 }), 30745 center: options.center || [.5,1], 30746 defaultHeight: options.defaultHeight || 0, 30747 maxResolution: options.maxResolution || Infinity 30748 }); 30749 this.setStyle(options.style); 30750 this.on (['postcompose', 'postrender'], this.onPostcompose_.bind(this)); 30751} 30752ol.ext.inherits(ol.layer.Vector3D, ol.layer.Image); 30753/** 30754 * Set the height function for the layer 30755 * @param {function|string|Number} height a height function (returns height giving a feature) or a popertie name or a fixed value 30756 */ 30757ol.layer.Vector3D.prototype.setHeight = function(height) { 30758 this.height_ = this.getHfn (height); 30759 this.changed(); 30760}; 30761/** 30762 * Set style associated with the layer 30763 * @param {ol.style.Style} s 30764 */ 30765ol.layer.Vector3D.prototype.setStyle = function(s) { 30766 if (s instanceof ol.style.Style) this._style = s; 30767 else this._style = new ol.style.Style(); 30768 if (!this._style.getStroke()) { 30769 this._style.setStroke(new ol.style.Stroke({ 30770 width: 1, 30771 color: 'red' 30772 })); 30773 }
30774 if (!this._style.getFill()) { 30775 this._style.setFill( new ol.style.Fill({ color: 'rgba(0,0,255,0.5)'}) ); 30776 } 30777 if (!this._style.getText()) { 30778 this._style.setText( new ol.style.Fill({ 30779 color: 'red'}) 30780 ); 30781 } 30782 // Get the geometry 30783 if (s && s.getGeometry()) { 30784 var geom = s.getGeometry(); 30785 if (typeof(geom)==='function') { 30786 this.set('geometry', geom); 30787 } else { 30788 this.set('geometry', function() { return geom; }); 30789 } 30790 } else { 30791 this.set('geometry', function(f) { return f.getGeometry(); }); 30792 } 30793}; 30794/** 30795 * Get style associated with the layer 30796 * @return {ol.style.Style} 30797 */ 30798ol.layer.Vector3D.prototype.getStyle = function() { 30799 return this._style; 30800}; 30801/** Calculate 3D at potcompose 30802 * @private 30803 */ 30804ol.layer.Vector3D.prototype.onPostcompose_ = function(e) { 30805 var res = e.frameState.viewState.resolution; 30806 if (res > this.get('maxResolution')) return; 30807 this.res_ = res*400; 30808 if (this.animate_) { 30809 var elapsed = e.frameState.time - this.animate_; 30810 if (elapsed < this.animateDuration_) { 30811 this.elapsedRatio_ = this.easing_(elapsed / this.animateDuration_); 30812 // tell OL3 to continue postcompose animation 30813 e.frameState.animate = true; 30814 } else { 30815 this.animate_ = false; 30816 this.height_ = this.toHeight_ 30817 } 30818 } 30819 var ratio = e.frameState.pixelRatio; 30820 var ctx = e.context; 30821 var m = this.matrix_ = e.frameState.coordinateToPixelTransform; 30822 // Old version (matrix) 30823 if (!m) { 30824 m = e.frameState.coordinateToPixelMatrix, 30825 m[2] = m[4]; 30826 m[3] = m[5]; 30827 m[4] = m[12]; 30828 m[5] = m[13]; 30829 } 30830 this.center_ = [ 30831 ctx.canvas.width*this.get('center')[0]/ratio, 30832 ctx.canvas.height*this.get('center')[1]/ratio 30833 ]; 30834 var f = this._source.getFeaturesInExtent(e.frameState.extent); 30835 ctx.save(); 30836 ctx.scale(ratio,ratio); 30837 var s = this.getStyle(); 30838 ctx.lineWidth = s.getStroke().getWidth(); 30839 ctx.lineCap = s.getStroke().getLineCap(); 30840 ctx.strokeStyle = ol.color.asString(s.getStroke().getColor()); 30841 ctx.fillStyle = ol.color.asString(s.getFill().getColor()); 30842 var builds = []; 30843 for (var i=0; i<f.length; i++) { 30844 builds.push (this.getFeature3D_ (f[i], this._getFeatureHeight(f[i]))); 30845 } 30846 this.drawFeature3D_ (ctx, builds); 30847 ctx.restore(); 30848}; 30849/** Create a function that return height of a feature 30850 * @param {function|string|number} h a height function or a popertie name or a fixed value 30851 * @return {function} function(f) return height of the feature f 30852 * @private 30853 */ 30854ol.layer.Vector3D.prototype.getHfn= function(h) { 30855 switch (typeof(h)) { 30856 case 'function': return h; 30857 case 'string': { 30858 var dh = this.get('defaultHeight'); 30859 return (function(f) { 30860 return (Number(f.get(h)) || dh); 30861 }); 30862 } 30863 case 'number': return (function(/*f*/) { return h; }); 30864 default: return (function(/*f*/) { return 10; }); 30865 } 30866}; 30867/** Animate rendering 30868 * @param {*} options 30869 * @param {string|function|number} options.height an attribute name or a function returning height of a feature or a fixed value 30870 * @param {number} options.duration the duration of the animatioin ms, default 1000 30871 * @param {ol.easing} options.easing an ol easing function 30872 * @api 30873 */ 30874ol.layer.Vector3D.prototype.animate = function(options) { 30875 options = options || {}; 30876 this.toHeight_ = this.getHfn(options.height); 30877 this.animate_ = new Date().getTime(); 30878 this.animateDuration_ = options.duration ||1000; 30879 this.easing_ = options.easing || ol.easing.easeOut; 30880 // Force redraw 30881 this.changed(); 30882}; 30883/** Check if animation is on 30884* @return {bool} 30885*/ 30886ol.layer.Vector3D.prototype.animating = function() { 30887 if (this.animate_ && new Date().getTime() - this.animate_ > this.animateDuration_) { 30888 this.animate_ = false; 30889 } 30890 return !!this.animate_; 30891} 30892/** Get height for a feature 30893 * @param {ol.Feature} f 30894 * @return {number} 30895 * @private 30896 */ 30897ol.layer.Vector3D.prototype._getFeatureHeight = function (f) { 30898 if (this.animate_) { 30899 var h1 = this.height_(f); 30900 var h2 = this.toHeight_(f); 30901 return (h1*(1-this.elapsedRatio_)+this.elapsedRatio_*h2); 30902 } 30903 else return this.height_(f); 30904}; 30905/** Get hvector for a point 30906 * @private 30907 */ 30908ol.layer.Vector3D.prototype.hvector_ = function (pt, h) { 30909 var p0 = [ 30910 pt[0]*this.matrix_[0] + pt[1]*this.matrix_[1] + this.matrix_[4], 30911 pt[0]*this.matrix_[2] + pt[1]*this.matrix_[3] + this.matrix_[5] 30912 ]; 30913 return { 30914 p0: p0, 30915 p1: [ 30916 p0[0] + h/this.res_ * (p0[0]-this.center_[0]), 30917 p0[1] + h/this.res_ * (p0[1]-this.center_[1]) 30918 ] 30919 }; 30920}; 30921/** Get a vector 3D for a feature 30922 * @private 30923 */ 30924ol.layer.Vector3D.prototype.getFeature3D_ = function (f, h) { 30925 var geom = this.get('geometry')(f); 30926 var c = geom.getCoordinates(); 30927 switch (geom.getType()) { 30928 case "Polygon": 30929 c = [c]; 30930 // fallthrough 30931 case "MultiPolygon": 30932 var build = []; 30933 for (var i=0; i<c.length; i++) { 30934 for (var j=0; j<c[i].length; j++) { 30935 var b = []; 30936 for (var k=0; k<c[i][j].length; k++) { 30937 b.push( this.hvector_(c[i][j][k], h) ); 30938 } 30939 build.push(b); 30940 } 30941 } 30942 return { type:"MultiPolygon", feature: f, geom: build }; 30943 case "Point": 30944 return { type:"Point", feature: f, geom: this.hvector_(c,h) }; 30945 default: return {}; 30946 } 30947}; 30948/** Draw 3D feature 30949 * @private 30950 */ 30951ol.layer.Vector3D.prototype.drawFeature3D_ = function(ctx, build) { 30952 var i,j, b, k; 30953 // Construct 30954 for (i=0; i<build.length; i++) { 30955 switch (build[i].type) { 30956 case "MultiPolygon": { 30957 for (j=0; j<build[i].geom.length; j++) { 30958 b = build[i].geom[j]; 30959 for (k=0; k < b.length; k++) { 30960 ctx.beginPath(); 30961 ctx.moveTo(b[k].p0[0], b[k].p0[1]); 30962 ctx.lineTo(b[k].p1[0], b[k].p1[1]); 30963 ctx.stroke(); 30964 } 30965 } 30966 break; 30967 } 30968 case "Point": { 30969 var g = build[i].geom; 30970 ctx.beginPath(); 30971 ctx.moveTo(g.p0[0], g.p0[1]); 30972 ctx.lineTo(g.p1[0], g.p1[1]); 30973 ctx.stroke(); 30974 break; 30975 } 30976 default: break; 30977 } 30978 } 30979 // Roof 30980 for (i=0; i<build.length; i++) { 30981 switch (build[i].type) { 30982 case "MultiPolygon": { 30983 ctx.beginPath(); 30984 for (j=0; j<build[i].geom.length; j++) { 30985 b = build[i].geom[j]; 30986 if (j==0) { 30987 ctx.moveTo(b[0].p1[0], b[0].p1[1]); 30988 for (k=1; k < b.length; k++) { 30989 ctx.lineTo(b[k].p1[0], b[k].p1[1]); 30990 } 30991 } else { 30992 ctx.moveTo(b[0].p1[0], b[0].p1[1]); 30993 for (k=b.length-2; k>=0; k--) { 30994 ctx.lineTo(b[k].p1[0], b[k].p1[1]); 30995 } 30996 } 30997 ctx.closePath(); 30998 } 30999 ctx.fill("evenodd"); 31000 ctx.stroke(); 31001 break; 31002 } 31003 case "Point": { 31004 b = build[i]; 31005 var t = b.feature.get('label'); 31006 if (t) { 31007 var p = b.geom.p1; 31008 var m = ctx.measureText(t); 31009 var h = Number (ctx.font.match(/\d+(\.\d+)?/g).join([])); 31010 ctx.fillRect (p[0]-m.width/2 -5, p[1]-h -5, m.width +10, h +10) 31011 ctx.strokeRect (p[0]-m.width/2 -5, p[1]-h -5, m.width +10, h +10) 31012 ctx.save() 31013 ctx.fillStyle = ol.color.asString(this._style.getText().getFill().getColor()); 31014 ctx.textAlign = 'center'; 31015 ctx.textBaseline = 'bottom'; 31016 ctx.fillText ( t, p[0], p[1] );
31017 ctx.restore(); 31018 } 31019 break; 31020 } 31021 default: break; 31022 } 31023 } 31024}; 31025 31026/* Copyright (c) 2016 Jean-Marc VIGLINO, 31027 released under the CeCILL-B license (French BSD license) 31028 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 31029 @classdesc 31030 ol.source.WikiCommons is a source that load Wikimedia Commons content in a vector layer. 31031 Inherits from: 31032 <ol.source.Vector> 31033*/ 31034/** 31035* @constructor ol.source.WikiCommons 31036* @extends {ol.source.Vector} 31037* @param {olx.source.WikiCommons=} options 31038*/ 31039ol.source.WikiCommons = function(opt_options) { 31040 var options = opt_options || {}; 31041 options.loader = this._loaderFn; 31042 /** Max resolution to load features */ 31043 this._maxResolution = options.maxResolution || 100; 31044 /** Result language */ 31045 this._lang = options.lang || "fr"; 31046 /** Query limit */ 31047 this._limit = options.limit || 100; 31048 /** Default attribution */ 31049 if (!options.attributions) options.attributions = [ "© <a href='https://commons.wikimedia.org/'>Wikimedia Commons</a>" ]; 31050 // Bbox strategy : reload at each move 31051 if (!options.strategy) options.strategy = ol.loadingstrategy.bbox; 31052 ol.source.Vector.call (this, options); 31053}; 31054ol.ext.inherits(ol.source.WikiCommons, ol.source.Vector); 31055/** Decode wiki attributes and choose to add feature to the layer 31056* @param {feature} the feature 31057* @param {attributes} wiki attributes 31058* @return {boolean} true: add the feature to the layer 31059* @API stable 31060*/ 31061ol.source.WikiCommons.prototype.readFeature = function (feature, attributes){ 31062 feature.set("descriptionurl", attributes.descriptionurl); 31063 feature.set("url", attributes.url); 31064 feature.set("title", attributes.title.replace(/^file:|.jpg$/ig,"")); 31065 feature.set("thumbnail", attributes.url.replace(/^(.+wikipedia\/commons)\/([a-zA-Z0-9]\/[a-zA-Z0-9]{2})\/(.+)$/,"$1/thumb/$2/$3/200px-$3")); 31066 feature.set("user", attributes.user); 31067 if (attributes.extmetadata && attributes.extmetadata.LicenseShortName) feature.set("copy", attributes.extmetadata.LicenseShortName.value); 31068 return true; 31069}; 31070/** Loader function used to load features. 31071* @private 31072*/ 31073ol.source.WikiCommons.prototype._loaderFn = function(extent, resolution, projection){ 31074 if (resolution > this._maxResolution) return; 31075 var self = this; 31076 var bbox = ol.proj.transformExtent(extent, projection, "EPSG:4326"); 31077 // Commons API: for more info @see https://commons.wikimedia.org/wiki/Commons:API/MediaWiki 31078 var url = "https://commons.wikimedia.org/w/api.php?action=query&format=json&origin=*&prop=coordinates|imageinfo" 31079 + "&generator=geosearch&iiprop=timestamp|user|url|extmetadata|metadata|size&iiextmetadatafilter=LicenseShortName" 31080 + "&ggsbbox=" + bbox[3] + "|" + bbox[0] + "|" + bbox[1] + "|" + bbox[2] 31081 + "&ggslimit="+this._limit 31082 + "&iilimit="+(this._limit-1) 31083 + "&ggsnamespace=6"; 31084 // Ajax request to get the tile 31085 ol.ext.Ajax.get({ 31086 url: url, 31087 success: function(data) { 31088 //console.log(data); 31089 var features = []; 31090 var att, pt, feature; 31091 if (!data.query || !data.query.pages) return; 31092 for ( var i in data.query.pages){ 31093 att = data.query.pages[i]; 31094 if (att.coordinates && att.coordinates.length ) { 31095 pt = [att.coordinates[0].lon, att.coordinates[0].lat]; 31096 } else { 31097 var meta = att.imageinfo[0].metadata; 31098 if (!meta) { 31099 //console.log(att); 31100 continue; 31101 } 31102 pt = []; 31103 var found=0; 31104 for (var k=0; k<meta.length; k++) { 31105 if (meta[k].name=="GPSLongitude") { 31106 pt[0] = meta[k].value; 31107 found++; 31108 } 31109 if (meta[k].name=="GPSLatitude") { 31110 pt[1] = meta[k].value; 31111 found++; 31112 } 31113 } 31114 if (found!=2) { 31115 //console.log(att); 31116 continue; 31117 } 31118 } 31119 feature = new ol.Feature(new ol.geom.Point(ol.proj.transform (pt,"EPSG:4326",projection))); 31120 att.imageinfo[0].title = att.title; 31121 if (self.readFeature(feature, att.imageinfo[0])) { 31122 features.push(feature); 31123 } 31124 } 31125 self.addFeatures(features); 31126 }}); 31127}; 31128 31129/* 31130 Copyright (c) 2015 Jean-Marc VIGLINO, 31131 released under the CeCILL-B license (http://www.cecill.info/). 31132 ol.layer.AnimatedCluster is a vector layer that animate cluster 31133*/ 31134/** 31135 * A vector layer for animated cluster 31136 * @constructor 31137 * @extends {ol.layer.Vector} 31138 * @param {olx.layer.AnimatedClusterOptions=} options extend olx.layer.Options 31139 * @param {Number} options.animationDuration animation duration in ms, default is 700m
31139s 31140 * @param {ol.easingFunction} animationMethod easing method to use, default ol.easing.easeOut 31141 */ 31142ol.layer.AnimatedCluster = function(opt_options) { 31143 var options = opt_options || {}; 31144 ol.layer.Vector.call (this, options); 31145 this.oldcluster = new ol.source.Vector(); 31146 this.clusters = []; 31147 this.animation={start:false}; 31148 this.set('animationDuration', typeof(options.animationDuration)=='number' ? options.animationDuration : 700); 31149 this.set('animationMethod', options.animationMethod || ol.easing.easeOut); 31150 // Save cluster before change 31151 this.getSource().on('change', this.saveCluster.bind(this)); 31152 // Animate the cluster 31153 this.on(['precompose','prerender'], this.animate.bind(this)); 31154 this.on(['postcompose','postrender'], this.postanimate.bind(this)); 31155}; 31156ol.ext.inherits(ol.layer.AnimatedCluster, ol.layer.Vector); 31157/** save cluster features before change 31158 * @private 31159 */ 31160ol.layer.AnimatedCluster.prototype.saveCluster = function() { 31161 if (this.oldcluster) { 31162 this.oldcluster.clear(); 31163 if (!this.get('animationDuration')) return; 31164 var features = this.getSource().getFeatures(); 31165 if (features.length && features[0].get('features')) {
31166 this.oldcluster.addFeatures (this.clusters); 31167 this.clusters = features.slice(0); 31168 this.sourceChanged = true; 31169 } 31170 } 31171}; 31172/** 31173 * Get the cluster that contains a feature 31174 * @private 31175*/ 31176ol.layer.AnimatedCluster.prototype.getClusterForFeature = function(f, cluster) { 31177 for (var j=0, c; c=cluster[j]; j++) { 31178 var features = c.get('features'); 31179 if (features && features.length) { 31180 for (var k=0, f2; f2=features[k]; k++) { 31181 if (f===f2) { 31182 return c; 31183 } 31184 } 31185 } 31186 } 31187 return false; 31188}; 31189/** 31190 * Stop animation 31191 * @private 31192 */ 31193ol.layer.AnimatedCluster.prototype.stopAnimation = function() { 31194 this.animation.start = false; 31195 this.animation.cA = []; 31196 this.animation.cB = []; 31197}; 31198/** 31199 * animate the cluster 31200 * @private 31201 */ 31202ol.layer.AnimatedCluster.prototype.animate = function(e) { 31203 var duration = this.get('animationDuration'); 31204 if (!duration) return; 31205 var resolution = e.frameState.viewState.resolution; 31206 // var ratio = e.frameState.pixelRatio; 31207 var i, c0, a = this.animation; 31208 var time = e.frameState.time; 31209 // Start a new animation, if change resolution and source has changed 31210 if (a.resolution != resolution && this.sourceChanged) { 31211 var extent = e.frameState.extent; 31212 if (a.resolution < resolution) { 31213 extent = ol.extent.buffer(extent, 100*resolution); 31214 a.cA = this.oldcluster.getFeaturesInExtent(extent); 31215 a.cB = this.getSource().getFeaturesInExtent(extent); 31216 a.revers = false; 31217 } else { 31218 extent = ol.extent.buffer(extent, 100*resolution); 31219 a.cA = this.getSource().getFeaturesInExtent(extent); 31220 a.cB = this.oldcluster.getFeaturesInExtent(extent); 31221 a.revers = true; 31222 } 31223 a.clusters = []; 31224 for (i=0, c0; c0=a.cA[i]; i++) { 31225 var f = c0.get('features'); 31226 if (f && f.length) { 31227 var c = this.getClusterForFeature (f[0], a.cB); 31228 if (c) a.clusters.push({ f:c0, pt:c.getGeometry().getCoordinates() }); 31229 } 31230 } 31231 // Save state 31232 a.resolution = resolution; 31233 this.sourceChanged = false; 31234 // No cluster or too much to animate 31235 if (!a.clusters.length || a.clusters.length>1000) { 31236 this.stopAnimation(); 31237 return; 31238 } 31239 // Start animation from now 31240 time = a.start = (new Date()).getTime(); 31241 } 31242 // Run animation 31243 if (a.start) { 31244 var vectorContext = e.vectorContext || ol.render.getVectorContext(e); 31245 var d = (time - a.start) / duration; 31246 // Animation ends 31247 if (d > 1.0) { 31248 this.stopAnimation(); 31249 d = 1; 31250 } 31251 d = this.get('animationMethod')(d); 31252 // Animate 31253 var style = this.getStyle(); 31254 var stylefn = (typeof(style) == 'function') ? style : style.length ? function(){ return style; } : function(){ return [style]; } ; 31255 // Layer opacity 31256 e.context.save(); 31257 e.context.globalAlpha = this.getOpacity(); 31258 for (i=0, c; c=a.clusters[i]; i++) { 31259 var pt = c.f.getGeometry().getCoordinates(); 31260 var dx = pt[0]-c.pt[0]; 31261 var dy = pt[1]-c.pt[1]; 31262 if (a.revers) { 31263 pt[0] = c.pt[0] + d * dx; 31264 pt[1] = c.pt[1] + d * dy; 31265 } else { 31266 pt[0] = pt[0] - d * dx; 31267 pt[1] = pt[1] - d * dy; 31268 } 31269 // Draw feature 31270 var st = stylefn(c.f, resolution, true); 31271 if (!st.length) st = [st]; 31272 // If one feature: draw the feature 31273 if (c.f.get("features").length===1 && !dx && !dy) { 31274 f = c.f.get("features")[0]; 31275 } 31276 // else draw a point 31277 else { 31278 var geo = new ol.geom.Point(pt); 31279 f = new ol.Feature(geo); 31280 } 31281 for (var k=0, s; s=st[k]; k++) { 31282 // Multi-line text 31283 if (s.getText() && /\n/.test(s.getText().getText())) { 31284 var offsetX = s.getText().getOffsetX(); 31285 var offsetY = s.getText().getOffsetY(); 31286 var rot = s.getText().getRotation() || 0; 31287 var fontSize = Number((s.getText().getFont() || '10px').match(/\d+/)) * 1.2; 31288 var str = s.getText().getText().split('\n') 31289 var dl, nb = str.length-1; 31290 var s2 = s.clone(); 31291 // Draw each lines 31292 str.forEach(function(t, i) { 31293 if (i==1) { 31294 // Allready drawn 31295 s2.setImage(); 31296 s2.setFill(); 31297 s2.setStroke(); 31298 } 31299 switch (s.getText().getTextBaseline()) { 31300 case 'alphabetic': 31301 case 'ideographic': 31302 case 'bottom': { 31303 dl = nb; 31304 break; 31305 } 31306 case 'hanging': 31307 case 'top': { 31308 dl = 0; 31309 break; 31310 } 31311 default : { 31312 dl = nb/2; 31313 break; 31314 } 31315 } 31316 s2.getText().setOffsetX(offsetX - Math.sin(rot)*fontSize*(i - dl)); 31317 s2.getText().setOffsetY(offsetY + Math.cos(rot)*fontSize*(i - dl)); 31318 s2.getText().setText(t); 31319 vectorContext.drawFeature(f, ol.ext.getVectorContextStyle(e, s2)); 31320 }); 31321 } else { 31322 vectorContext.drawFeature(f, ol.ext.getVectorContextStyle(e, s)); 31323 } 31324 /* OLD VERSION OL < 4.3 31325 // Retina device 31326 var ratio = e.frameState.pixelRatio; 31327 var sc; 31328 // OL < v4.3 : setImageStyle doesn't check retina 31329 var imgs = ol.Map.prototype.getFeaturesAtPixel ? false : s.getImage(); 31330 if (imgs) 31331 { sc = imgs.getScale();
31332 imgs.setScale(sc*ratio); 31333 } 31334 // OL3 > v3.14 31335 if (vectorContext.setStyle) 31336 { // If one feature: draw the feature 31337 if (c.f.get("features").length===1 && !dx && !dy) { 31338 vectorContext.drawFeature(c.f.get("features")[0], s); 31339 } 31340 // else draw a point 31341 else { 31342 vectorContext.setStyle(s); 31343 vectorContext.drawGeometry(geo); 31344 } 31345 } 31346 // older version 31347 else 31348 { vectorContext.setImageStyle(imgs); 31349 vectorContext.setTextStyle(s.getText()); 31350 vectorContext.drawPointGeometry(geo); 31351 } 31352 if (imgs) imgs.setScale(sc); 31353 */ 31354 } 31355 } 31356 e.context.restore(); 31357 // tell ol to continue postcompose animation 31358 e.frameState.animate = true; 31359 // Prevent layer drawing (clip with null rect) 31360 e.context.save(); 31361 e.context.beginPath(); 31362 e.context.rect(0,0,0,0); 31363 e.context.clip(); 31364 this.clip_ = true; 31365 } 31366 return; 31367}; 31368/** 31369 * remove clipping after the layer is drawn 31370 * @private 31371 */ 31372ol.layer.AnimatedCluster.prototype.postanimate = function(e) { 31373 if (this.clip_) { 31374 e.context.restore(); 31375 this.clip_ = false;
31376 } 31377}; 31378 31379/* Copyright (c) 2019 Jean-Marc VIGLINO, 31380 released under the CeCILL-B license (French BSD license) 31381 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 31382*/ 31383/** 31384 * @classdesc 31385 * Image layer to use with a GeoImage source and return the extent calcaulted with this source. 31386 * @extends {ol.layer.Image} 31387 * @param {Object=} options Layer Image options. 31388 * @api 31389 */ 31390ol.layer.GeoImage = function(options) { 31391 ol.layer.Image.call(this, options); 31392} 31393ol.ext.inherits (ol.layer.GeoImage, ol.layer.Image); 31394/** 31395 * Return the {@link module:ol/extent~Extent extent} of the source associated with the layer. 31396 * @return {ol.Extent} The layer extent. 31397 * @observable 31398 * @api 31399 */ 31400ol.layer.GeoImage.prototype.getExtent = function() { 31401 return this.getSource().getExtent(); 31402} 31403 31404/* Copyright (c) 2019 Jean-Marc VIGLINO, 31405 released under the CeCILL-B license (French BSD license) 31406 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 31407*/ 31408// 31409/** IGN's Geoportail WMTS layer definition 31410 * @constructor 31411 * @extends {ol.layer.Tile} 31412 * @param {olx.layer.WMTSOptions=} options WMTS options if not defined default are used 31413 * @param {string} options.layer Geoportail layer name 31414 * @param {string} options.gppKey Geoportail API key, default use layer registered key 31415 * @param {ol.projectionLike} [options.projection=EPSG:3857] projection for the extent, default EPSG:3857 31416 * @param {olx.source.WMTSOptions=} tileoptions WMTS options if not defined default are used 31417 */ 31418ol.layer.Geoportail = function(layer, options, tileoptions) { 31419 options = options || {}; 31420 tileoptions = tileoptions || {}; 31421 // use function(options, tileoption) when layer is set in options 31422 if (typeof(layer)!=='string') { 31423 tileoptions = options || {}; 31424 options = layer; 31425 layer = options.layer; 31426 } 31427 var maxZoom = options.maxZoom; 31428 // A source is defined 31429 if (options.source) { 31430 layer = options.source.getLayer(); 31431 options.gppKey = options.source.getGPPKey(); 31432 } 31433 var capabilities = window.geoportailConfig ? window.geoportailConfig.capabilities[options.gppKey || options.key] || window.geoportailConfig.capabilities["default"] || ol.layer.Geoportail.capabilities : ol.layer.Geoportail.capabilities; 31434 capabilities = capabilities[layer]; 31435 if (!capabilities) capabilities = ol.layer.Geoportail.capabilities[layer]; 31436 if (!capabilities) { 31437 capabilities = { title: layer, originators: [] }; 31438 console.error("ol.layer.Geoportail: no layer definition for \""+layer+"\"\nTry to use ol/layer/Geoportail~loadCapabilities() to get it."); 31439 // throw new Error("ol.layer.Geoportail: no layer definition for \""+layer+"\""); 31440 } 31441 // tile options & default params 31442 for (var i in capabilities) { 31443 if (typeof tileoptions[i]== "undefined") tileoptions[i] = capabilities[i]; 31444 } 31445 this._originators = capabilities.originators; 31446 if (!tileoptions.gppKey && !tileoptions.key) tileoptions.gppKey = options.gppKey || options.key; 31447 if (!options.source) options.source = new ol.source.Geoportail(layer, tileoptions); 31448 if (!options.title) options.title = capabilities.title; 31449 if (!options.name) options.name = layer; 31450 options.layer = layer; 31451 if (!options.queryable) options.queryable = capabilities.queryable; 31452 if (!options.desc) options.desc = capabilities.desc; 31453 if (!options.extent && capabilities.bbox) { 31454 if (capabilities.bbox[0]>-170 && capabilities.bbox[2]<170) { 31455 options.extent = ol.proj.transformExtent(capabilities.bbox, 'EPSG:4326', options.projection || 'EPSG:3857'); 31456 } 31457 } 31458 options.maxZoom = maxZoom; 31459 // calculate layer max resolution 31460 if (!options.maxResolution && tileoptions.minZoom) { 31461 options.source.getTileGrid().minZoom -= (tileoptions.minZoom>1 ? 2 : 1); 31462 options.maxResolution = options.source.getTileGrid().getResolution(options.source.getTileGrid().minZoom) 31463 options.source.getTileGrid().minZoom = tileoptions.minZoom; 31464 } 31465 ol.layer.Tile.call (this, options); 31466 // BUG GPP: Attributions constraints are not set properly :( 31467/** / 31468 // Set attribution according to the originators 31469 var counter = 0; 31470 // Get default attribution 31471 var getAttrib = function(title, o) { 31472 if (this.get('attributionMode')==='logo') { 31473 if (!title) return ol.source.Geoportail.prototype.attribution; 31474 else return '<a href="'+o.href+'"><img src="'+o.logo+'" title="© '+o.attribution+'" /></a>'; 31475 } else { 31476 if (!title) return ol.source.Geoportail.prototype.attribution; 31477 else return '© <a href="'+o.href+'" title="© '+(o.attribution||title)+'" >'+title+'</a>' 31478 } 31479 }.bind(this); 31480 var currentZ, currentCenter = []; 31481 var setAttribution = function(e) { 31482 var a, o, i; 31483 counter--; 31484 if (!counter) { 31485 var z = e.frameState.viewState.zoom; 31486 console.log(e) 31487 if (z===currentZ 31488 && e.frameState.viewState.center[0]===currentCenter[0] 31489 && e.frameState.viewState.center[1]===currentCenter[1]){ 31490 return; 31491 } 31492 currentZ = z; 31493 currentCenter = e.frameState.viewState.center; 31494 var ex = e.frameState.extent; 31495 ex = ol.proj.transformExtent (ex, e.frameState.viewState.projection, 'EPSG:4326'); 31496 if (this._originators) {
31497 var attrib = this.getSource().getAttributions(); 31498 // ol v5 31499 if (typeof(attrib)==='function') attrib = attrib(); 31500 attrib.splice(0, attrib.length); 31501 var maxZoom = 0; 31502 for (a in this._originators) { 31503 o = this._originators[a]; 31504 for (i=0; i<o.constraint.length; i++) { 31505 if (o.constraint[i].maxZoom > maxZoom 31506 && ol.extent.intersects(ex, o.constraint[i].bbox)) { 31507 maxZoom = o.constraint[i].maxZoom; 31508 } 31509 } 31510 } 31511 if (maxZoom < z) z = maxZoom; 31512 if (this.getSource().getTileGrid() && z < this.getSource().getTileGrid().getMinZoom()) { 31513 z = this.getSource().getTileGrid().getMinZoom(); 31514 } 31515 for (a in this._originators) { 31516 o = this._originators[a]; 31517 if (!o.constraint.length) { 31518 attrib.push (getAttrib(a, o)); 31519 } else { 31520 for (i=0; i<o.constraint.length; i++) { 31521 if ( z <= o.constraint[i].maxZoom 31522 && z >= o.constraint[i].minZoom 31523 && ol.extent.intersects(ex, o.constraint[i].bbox)) { 31524 attrib.push (getAttrib(a, o)); 31525 break; 31526 } 31527 } 31528 } 31529 } 31530 if (!attrib.length) attrib.push ( getAttrib() );
31531 this.getSource().setAttributions(attrib); 31532 } 31533 } 31534 }.bind(this); 31535 this.on('precompose', function(e) { 31536 counter++; 31537 setTimeout(function () { setAttribution(e) }, 500); 31538 }); 31539/**/ 31540}; 31541ol.ext.inherits (ol.layer.Geoportail, ol.layer.Tile); 31542/** Default capabilities for main layers 31543 */ 31544ol.layer.Geoportail.capabilities = { 31545 // choisirgeoportail 31546 "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]}]}}}, 31547 "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]}]}}}, 31548 "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.8
3154808697]}]},"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]}]}}}, 31549 // Deprecated 31550 "GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN-EXPRESS.STANDARD": {"server"
31550:"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]}]}}}, 31551 // Need API key 31552 "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]}]}}}, 31553 // Other layers 31554 "ADMINEXPRESS-COG-CARTO.LATEST": {"key": "administratif", "server":"https://wxs.ign.fr/geoportail/wmts","layer":"ADMINEXPRESS-COG-CARTO.LATEST","title":"ADMINEXPRESS COG CARTO","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 Express COG code officiel géographique 2021","originators":{"IGN":{"href":"https://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]}]}}}, 31555 "GEOGRAPHICALGRIDSYSTEMS.SLOPES.MOUNTAIN": {"key":"altimetrie","server"
31555:"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]}]}}}, 31556 "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]}]}}}, 31557 "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]}]}}}, 31558 "TRANSPORTNETWORKS.ROADS": { "key": "topographie", "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]}]}}}, 31559}; 31560/** Register new layer capability 31561 * @param {string} layer layer name 31562 * @param {*} capability 31563 */ 31564ol.layer.Geoportail.register = function(layer, capability) { 31565 ol.layer.Geoportail.capabilities[layer] = capability; 31566}; 31567/** Check if a layer registered with a key? 31568 * @param {string} layer layer name 31569 * @returns {boolean} 31570 */ 31571ol.layer.Geoportail.isRegistered = function(layer) { 31572 return ol.layer.Geoportail.capabilities[layer] && ol.layer.Geoportail.capabilities[layer].key; 31573}; 31574/** Load capabilities from the service 31575 * @param {string} gppKey the API key to get capabilities for 31576 * @return {*} Promise-like response 31577 */ 31578ol.layer.Geoportail.loadCapabilities = function(gppKey, all) { 31579 var onSuccess = function() {} 31580 var onError = function() {} 31581 var onFinally = function() {}; 31582 this.getCapabilities(gppKey,all).then(function(c) { 31583 ol.layer.Geoportail.capabilities = c; 31584 onSuccess(c); 31585 }).catch(function(e) { 31586 onError(e); 31587 }).finally(function(c) { 31588 onFinally(c); 31589 }); 31590 var response = { 31591 then: function (callback) { 31592 if (typeof(callback)==='function') onSuccess = callback; 31593 return response; 31594 }, 31595 catch: function (callback) { 31596 if (typeof(callback)==='function') onError = callback; 31597 return response; 31598 }, 31599 finally: function (callback) { 31600 if (typeof(callback)==='function') onFinally = callback; 31601 return response; 31602 } 31603 } 31604 return response; 31605}; 31606/** Get Key capabilities 31607 * @param {string} gppKey the API key to get capabilities for 31608 * @return {*} Promise-like response 31609 */ 31610ol.layer.Geoportail.getCapabilities = function(gppKey) { 31611 var capabilities = {}; 31612 var onSuccess = function() {} 31613 var onError = function() {} 31614 var onFinally = function() {} 31615 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]; 31616 // Transform resolution to zoom 31617 function getZoom(res) { 31618 res = Number(res) * 0.000281; 31619 for (var r=0; r<geopresolutions.length; r++) 31620 if (res>geopresolutions[r]) return r; 31621 }
31622 // Merge constraints 31623 function mergeConstraints(ori) { 31624 for (var i=ori.constraint.length-1; i>0; i--) { 31625 for (var j=0; j<i; j++) { 31626 var bok = true; 31627 for (var k=0; k<4; k++) { 31628 if (ori.constraint[i].bbox[k] != ori.constraint[j].bbox[k]) { 31629 bok = false; 31630 break; 31631 } 31632 } 31633 if (!bok) continue; 31634 if (ori.constraint[i].maxZoom == ori.constraint[j].minZoom 31635 || ori.constraint[j].maxZoom == ori.constraint[i].minZoom 31636 || ori.constraint[i].maxZoom+1 == ori.constraint[j].minZoom 31637 || ori.constraint[j].maxZoom+1 == ori.constraint[i].minZoom 31638 || ori.constraint[i].minZoom-1 == ori.constraint[j].maxZoom 31639 || ori.constraint[j].minZoom-1 == ori.constraint[i].maxZoom) { 31640 ori.constraint[j].maxZoom = Math.max(ori.constraint[i].maxZoom, ori.constraint[j].maxZoom); 31641 ori.constraint[j].minZoom = Math.min(ori.constraint[i].minZoom, ori.constraint[j].minZoom); 31642 ori.constraint.splice(i,1); 31643 break; 31644 } 31645 } 31646 } 31647 } 31648 // Get capabilities 31649 ol.ext.Ajax.get({ 31650 url: 'https://wxs.ign.fr/'+gppKey+'/autoconf/', 31651 dataType: 'TEXT', 31652 error: function (e) { 31653 onError(e); 31654 onFinally({}); 31655 }, 31656 success: function(resp) { 31657 var parser = new DOMParser(); 31658 var config = parser.parseFromString(resp,"text/xml"); 31659 var layers = config.getElementsByTagName('Layer'); 31660 for (var i=0, l; l=layers[i]; i++) { 31661 // WMTS ? 31662 if (!/WMTS/.test(l.getElementsByTagName('Server')[0].attributes['service'].value)) continue; 31663// if (!all && !/geoportail\/wmts/.test(l.find("OnlineResource").attr("href"))) continue; 31664 var service = { 31665 key: gppKey, 31666 server: l.getElementsByTagName('gpp:Key')[0].innerHTML.replace(gppKey+"/",""), 31667 layer: l.getElementsByTagName('Name')[0].innerHTML, 31668 title: l.getElementsByTagName('Title')[0].innerHTML, 31669 format: l.getElementsByTagName('Format')[0] ? l.getElementsByTagName('Format')[0].innerHTML : 'image.jpeg', 31670 style: l.getElementsByTagName('Style')[0].getElementsByTagName('Name')[0].innerHTML, 31671 queryable: (l.attributes.queryable.value==='1'), 31672 tilematrix: 'PM', 31673 minZoom: getZoom(l.getElementsByTagName('sld:MaxScaleDenominator')[0].innerHTML), 31674 maxZoom: getZoom(l.getElementsByTagName('sld:MinScaleDenominator')[0].innerHTML), 31675 bbox: JSON.parse('['+l.getElementsByTagName('gpp:BoundingBox')[0].innerHTML+']'), 31676 desc: l.getElementsByTagName('Abstract')[0].innerHTML.replace(/^<!\[CDATA\[(.*)\]\]>$/, '$1') 31677 }; 31678 service.originators = {}; 31679 var origin = l.getElementsByTagName('gpp:Originator'); 31680 for (var k=0, o; o=origin[k]; k++) { 31681 var ori = service.originators[o.attributes['name'].value] = { 31682 href: o.getElementsByTagName('gpp:URL')[0].innerHTML, 31683 attribution: o.getElementsByTagName('gpp:Attribution')[0].innerHTML, 31684 logo: o.getElementsByTagName('gpp:Logo')[0].innerHTML, 31685 minZoom: 20, 31686 maxZoom: 0, 31687 constraint: [] 31688 }; 31689 // Scale contraints 31690 var constraint = o.getElementsByTagName('gpp:Constraint'); 31691 for (var j=0, c; c=constraint[j]; j++) { 31692 var zmax = getZoom(c.getElementsByTagName('sld:MinScaleDenominator')[0].innerHTML); 31693 var zmin = getZoom(c.getElementsByTagName('sld:MaxScaleDenominator')[0].innerHTML); 31694 if (zmin > ori.maxZoom) ori.maxZoom = zmin; 31695 if (zmin < ori.minZoom) ori.minZoom = zmin; 31696 if (zmax>ori.maxZoom) ori.maxZoom = zmax; 31697 if (zmax<ori.minZoom) ori.minZoom = zmax; 31698 ori.constraint.push({ 31699 minZoom: zmin, 31700 maxZoom: zmax, 31701 bbox: JSON.parse('['+c.getElementsByTagName('gpp:BoundingBox')[0].innerHTML+']') 31702 }); 31703 } 31704 // Merge constraints 31705 mergeConstraints(ori) 31706 } 31707 capabilities[service.layer] = service; 31708 } 31709 onSuccess(capabilities); 31710 onFinally(capabilities); 31711 } 31712 }); 31713 // Promise like response 31714 var response = { 31715 then: function (callback) { 31716 if (typeof(callback)==='function') onSuccess = callback; 31717 return response; 31718 }, 31719 catch: function (callback) { 31720 if (typeof(callback)==='function') onError = callback; 31721 return response; 31722 }, 31723 finally: function (callback) { 31724 if (typeof(callback)==='function') onFinally = callback; 31725 return response; 31726 }, 31727 } 31728 return response; 31729}; 31730 31731/* Copyright (c) 2015 Jean-Marc VIGLINO, 31732 released under the CeCILL-B license (French BSD license)
31733 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 31734*/ 31735/** 31736 * Return a preview image of the source. 31737 * @param {ol.Coordinate|undefined} lonlat The center of the preview. 31738 * @param {number} resolution of the preview. 31739 * @return {String} the preview url 31740 * @api 31741 */ 31742ol.source.Source.prototype.getPreview = function(/*lonlat, resolution*/) { 31743 return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAk6QAAJOkBUCTn+AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANeSURBVHic7ZpPiE1RHMc/780MBhkik79JSUlIUbOxI+wkI2yRhYSUlJLNpJF/xcpiJBmZGBZsNM1CkmhKITGkGbH0/BuPmXnP4rxbb/TOn3fvOffeec6nfqvb/b7f93fveeec37ng8Xg8Ho/nf6Uu4d+fDswFssCvhHOJhaXAMeApMAQUyyIPPAdOAiuTStAVy4EHjDWsix5gdRLJ2mY34ulWYz6IEeA4kIk9awtkgTOEM/5vdAKT4k0/Ou3YMR/ELcbRm9AKFLBbgCJwNE4TYZkJfMG++SIwDCyLz0o4bI17WdyJz0r1TAZ+oDcxCBwAFgIzEIuhvcBbg3sLwOK4DFXLFvQGniCGSSUagS4DjUPOHESkA3XiOWCORqMR6Nfo9DjI3QqPUSd+ylBnv0Zn0GrWFvmIOvGNhjqrNDp/EAutyFgRKUM2tgO+Gur81FxvAKYZaimxXYBvmuuLDHWWaK4X0RfJCNsF6NdcbzXU2a65PohYFKWOc+jn8PUajbWIXaBKp9NB7lZYh34OzwFbFfd/NtDYYSth27urLGIm0M31AL3APWAAmIooymaDnPIl/Vz4NN1yHrd7gcvxWQnHAuA3bsyPop8hUsE13BSgK04TUViBeFo2zedJ8S6wElexW4D2eNOPTjNi6WvD/DtEr8E6tk6GGoAmxFY2iFHE9NZiQf8gogiB9gTEH23izAZuE77vHyU+ANucO1QwD3hD/MbLowAcdm20EmkwXx4n3NodS9rMB2HabYpEWs0HcRqHp0fNwAvJD+eBTZr7p6BvmQVxUaEzEbiruNfJekH15L8jtrEm7JJolEcOmKXRqQOuKDQuY7HZY8s8iNfzkSLxIuI43FTrkkLnOlBfRW4VsWk+oAX5weknxFAxJQNckGgVgZuIRVoomoGXEmGTMa+iQ6K7M4SW7k24QYgiuDQPYinbhugiF4H3RGtzZYCzyIvQXfpNI1ybLyeLpf5+iTbkRbiP2EcocTHm4+YI8iI8RFHwWjAfsA95Q+YZFU6wasl8wB7kReijtNbIILa0vcg/PRlGfPQwHmlCviDqAzaA+OREtzqr1ejOIDorxlNEjTGUBV4nnUWCvAJxGDlA8q9j3DEArAn2zvXAfOwfl6eVAmJrPpJ0Ih6Px+PxeJLjLwPul3vj5d0eAAAAAElFTkSuQmCC"; 31744}; 31745/** 31746 * Return the tile image of the source. 31747 * @param {ol.Coordinate|undefined} lonlat The center of the preview. 31748 * @param {number} resolution of the preview. 31749 * @return {String} the preview url 31750 * @api 31751 */ 31752ol.source.Tile.prototype.getPreview = function(lonlat, resolution) { 31753 if (!lonlat) lonlat = [21020, 6355964]; 31754 if (!resolution) resolution = 150; 31755 var coord = this.getTileGrid().getTileCoordForCoordAndResolution(lonlat, resolution); 31756 var fn = this.getTileUrlFunction(); 31757 return fn.call(this, coord, this.getProjection()); 31758}; 31759/** 31760 * Return the tile image of the source. 31761 * @param {ol.Coordinate|undefined} lonlat The center of the preview. 31762 * @param {number} resolution of the preview. 31763 * @return {String} the preview url 31764 * @api 31765 */ 31766ol.source.TileWMS.prototype.getPreview = function(lonlat, resolution) { 31767 if (!lonlat) lonlat = [21020, 6355964]; 31768 if (!resolution) resolution = 150; 31769 var fn = this.getTileUrlFunction(); 31770 if (fn) { 31771 var tileGrid = this.getTileGrid() || this.getTileGridForProjection(this.getProjection()); 31772 var coord = tileGrid.getTileCoordForCoordAndResolution(lonlat, resolution); 31773 return fn.call(this, coord, 1, this.getProjection()); 31774 } 31775 // Use getfeature info instead 31776 var url = this.getGetFeatureInfoUrl ? 31777 this.getGetFeatureInfoUrl(lonlat, resolution, this.getProjection() || 'EPSG:3857', {}) 31778 : this.getFeatureInfoUrl(lonlat, resolution, this.getProjection() || 'EPSG:3857', {}); 31779 url = url.replace(/getfeatureinfo/i,"GetMap"); 31780 return url; 31781}; 31782/** 31783 * Return a preview for the layer. 31784 * @param {ol.Coordinate|undefined} lonlat The center of the preview. 31785 * @param {number} resolution of the preview. 31786 * @return {Array<String>} list of preview url 31787 * @api 31788 */ 31789ol.layer.Base.prototype.getPreview = function(lonlat, resolution, projection) { 31790 if (this.get("preview")) return [ this.get("preview") ]; 31791 if (!resolution) resolution = 150; 31792 // Get middle resolution 31793 if (resolution < this.getMinResolution() || resolution > this.getMaxResolution()) { 31794 var rmin = this.getMinResolution(), 31795 rmax = this.getMaxResolution(); 31796 if (rmax>100000) rmax = 156543; // min zoom : world 31797 if (rmin<0.15) rmin = 0.15; // max zoom 31798 resolution = rmax; 31799 while (rmax>rmin) { 31800 rmin *= 2; 31801 rmax /= 2; 31802 resolution = rmin; 31803 } 31804 } 31805 var e = this.getExtent(); 31806 if (!lonlat) lonlat = [21020, 6355964]; // Default lonlat
31807 if (e && !ol.extent.containsCoordinate(e,lonlat)) lonlat = [ (e[0]+e[2])/2, (e[1]+e[3])/2 ]; 31808 if (projection) lonlat = ol.proj.transform (lonlat, projection, this.getSource().getProjection()); 31809 if (this.getSource && this.getSource()) { 31810 return [ this.getSource().getPreview(lonlat, resolution) ]; 31811 } 31812 return []; 31813}; 31814/** 31815 * Return a preview for the layer. 31816 * @param {_ol_coordinate_|undefined} lonlat The center of the preview. 31817 * @param {number} resolution of the preview. 31818 * @return {Array<String>} list of preview url 31819 * @api 31820 */ 31821ol.layer.Group.prototype.getPreview = function(lonlat, resolution) { 31822 if (this.get("preview")) return [ this.get("preview") ]; 31823 var t = []; 31824 if (this.getLayers) { 31825 var l = this.getLayers().getArray(); 31826 for (var i=0; i<l.length; i++) { 31827 t = t.concat(l[i].getPreview(lonlat, resolution)); 31828 } 31829 } 31830 return t; 31831}; 31832 31833/* Copyright (c) 2019 Jean-Marc VIGLINO, 31834 released under the CeCILL-B license (French BSD license) 31835 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 31836*/ 31837/** Layer that use Maplibre GL as render 31838 * @constructor 31839 * @extends {ol.layer.Layer} 31840 * @param {any} options layer options 31841 * @param {string} options.layer Geoportail layer name 31842 * @param {string} options.gppKey Geoportail API key 31843 * @param {olx.source.WMTSOptions=} tileoptions WMTS options if not defined default are used 31844 */ 31845ol.layer.Maplibre = function(options) { 31846 if (!ol.layer.Layer) { 31847 console.error('[ol/layer/MapLibre] bad ol version (need ol@6+)'); 31848 } 31849 options = options || {}; 31850 options.render = function (frameState) { 31851 // Create map if not exists 31852 if (!this._container) { 31853 this._create(options.style); 31854 } 31855 /**/ 31856 var fac = 1 31857 var dzoom = 1 31858 //fac = Math.pow(2,.5); 31859 //dzoom = .5; 31860 /*/ 31861 var fac = 2 31862 var dzoom = 0 31863 /**/ 31864 this._container.style.width = fac*frameState.size[0]+'px'; 31865 this._container.style.height = fac*frameState.size[1]+'px'; 31866 var glMap = this.glMap; 31867 if (!glMap) return null; 31868 var canvas = glMap.getCanvas(); 31869 canvas.style.transform = 'scale('+(1/fac)+')'; 31870 // Force map to resize 31871 if (frameState.size[0] !== canvas.width || frameState.size[1] !== canvas.height) { 31872 glMap.resize(); 31873 } 31874 canvas.style.opacity = this.getOpacity(); 31875 // adjust view parameters in mapbox 31876 var viewState = frameState.viewState; 31877 glMap.jumpTo({ 31878 center: ol.proj.toLonLat(viewState.center), 31879 zoom: viewState.zoom - dzoom, 31880 bearing: (-viewState.rotation * 180) / Math.PI, 31881 animate: false, 31882 }); 31883 // cancel the scheduled update & trigger synchronous redraw 31884 // see https://github.com/mapbox/mapbox-gl-js/issues/7893#issue-408992184 31885 // NOTE: THIS MIGHT BREAK IF UPDATING THE MAPBOX VERSION 31886 if (glMap._frame) { 31887 glMap._frame.cancel(); 31888 glMap._frame = null; 31889 } 31890 glMap._render(); 31891 return this._container; 31892 } 31893 ol.layer.Layer.call(this, options); 31894}; 31895if (!ol.layer.Layer) ol.layer.Layer = function() {}; 31896ol.ext.inherits (ol.layer.Maplibre, ol.layer.Layer); 31897/** Get the Maplibre map 31898 * @return {Object} 31899 */ 31900ol.layer.Maplibre.prototype.getMapGL = function() { 31901 return this.glMap; 31902} 31903/** Set style 31904 * @param {Object|string} style Mapbox style Object or a URL to JSON 31905 */ 31906ol.layer.Maplibre.prototype.setStyle = function(style) { 31907 this.set('style', style); 31908 if (this.getMapGL()) { 31909 this.getMapGL().setStyle(style); 31910 } 31911 this.changed(); 31912} 31913/** Returns the map's Mapbox style object. 31914 * @returns {Object} 31915 */ 31916ol.layer.Maplibre.prototype.getStyle = function() { 31917 return this.getMapGL().get('style'); 31918} 31919/** Create the map libre map 31920 * @param {Object|string} style Mapbox style Object or a URL to JSON 31921 * @private 31922 */ 31923ol.layer.Maplibre.prototype._create = function(style) { 31924 this._container = ol.ext.element.create('DIV', { 31925 className: 'ol-maplibre-gl', 31926 style: { 31927 position: 'absolute', 31928 top: 0, 31929 left: 0 31930 }, 31931 parent: document.body 31932 }) 31933 this.glMap = new mapboxgl.Map({ 31934 container: this._container, 31935 style: style, 31936 center: [3, 47], 31937 zoom: 5, 31938 pitch: 0, 31939 antialias: true, 31940 attributionControl: false, 31941 boxZoom: false, 31942 doubleClickZoom: false,
31943 dragPan: false, 31944 dragRotate: false, 31945 interactive: false, 31946 keyboard: false, 31947 pitchWithRotate: false, 31948 scrollZoom: false, 31949 touchZoomRotate: false, 31950 }); 31951}; 31952 31953/** ol.layer.Vector.prototype.setRender3D 31954 * @extends {ol.layer.Vector} 31955 * @param {ol.render3D} 31956 */ 31957ol.layer.Vector.prototype.setRender3D = function (r) { 31958 r.setLayer(this); 31959} 31960/** 31961 * @classdesc 31962 * 3D vector layer rendering 31963 * @constructor 31964 * @param {Object} param 31965 * @param {ol.layer.Vector} param.layer the layer to display in 3D 31966 * @param {ol.style.Style} options.style drawing style 31967 * @param {function|boolean} param.active a function that returns a boolean or a boolean ,default true 31968 * @param {boolean} param.ghost use ghost style 31969 * @param {number} param.maxResolution max resolution to render 3D 31970 * @param {number} param.defaultHeight default height if none is return by a propertie 31971 * @param {function|string|Number} param.height a height function (returns height giving a feature) or a popertie name for the height or a fixed value 31972 */ 31973ol.render3D = function (options) { 31974 options = options || {}; 31975 options.maxResolution = options.maxResolution || 100 31976 options.defaultHeight = options.defaultHeight || 0; 31977 ol.Object.call (this, options); 31978 this.setStyle(options.style); 31979 this.set('ghost', options.ghost); 31980 this.setActive(options.active || options.active!==false); 31981 this.height_ = options.height = this.getHfn (options.height); 31982 if (options.layer) this.setLayer(options.layer); 31983} 31984ol.ext.inherits(ol.render3D, ol.Object); 31985/** 31986 * Set style associated with the renderer 31987 * @param {ol.style.Style} s 31988 */ 31989ol.render3D.prototype.setStyle = function(s) { 31990 if (s instanceof ol.style.Style) this._style = s; 31991 else this._style = new ol.style.Style (); 31992 if (!this._style.getStroke()) { 31993 this._style.setStroke(new ol.style.Stroke({ 31994 width: 1, 31995 color: 'red' 31996 })); 31997 } 31998 if (!this._style.getFill()) { 31999 this._style.setFill( new ol.style.Fill({ color: 'rgba(0,0,255,0.5)'}) ); 32000 } 32001 // Get the geometry 32002 if (s && s.getGeometry()) { 32003 var geom = s.getGeometry(); 32004 if (typeof(geom)==='function') { 32005 this.set('geometry', geom); 32006 } else { 32007 this.set('geometry', function() { return geom; }); 32008 } 32009 } else { 32010 this.set('geometry', function(f) { return f.getGeometry(); }); 32011 } 32012}; 32013/** 32014 * Get style associated with the renderer 32015 * @return {ol.style.Style} 32016 */ 32017ol.render3D.prototype.getStyle = function() { 32018 return this._style; 32019}; 32020/** Set active 32021 * @param {function|boolean} active 32022 */ 32023ol.render3D.prototype.setActive = function(active) { 32024 if (typeof(active)==='function') { 32025 this._active = active; 32026 } 32027 else { 32028 this._active = function() { return active; }; 32029 } 32030 if (this.layer_) this.layer_.changed(); 32031}; 32032/** Get active 32033 * @return {boolean} 32034 */ 32035ol.render3D.prototype.getActive = function() { 32036 return this._active(); 32037}; 32038/** Calculate 3D at potcompose 32039 * @private 32040 */ 32041ol.render3D.prototype.onPostcompose_ = function(e) { 32042 if (!this.getActive()) return; 32043 var res = e.frameState.viewState.resolution; 32044 if (res > this.get('maxResolution')) return; 32045 this.res_ = res*400; 32046 if (this.animate_) { 32047 var elapsed = e.frameState.time - this.animate_; 32048 if (elapsed < this.animateDuration_) { 32049 this.elapsedRatio_ = this.easing_(elapsed / this.animateDuration_); 32050 // tell OL3 to continue postcompose animation 32051 e.frameState.animate = true; 32052 } else { 32053 this.animate_ = false; 32054 this.height_ = this.toHeight_ 32055 } 32056 } 32057 var ratio = e.frameState.pixelRatio; 32058 var ctx = e.context; 32059 var m = this.matrix_ = e.frameState.coordinateToPixelTransform; 32060 // Old version (matrix) 32061 if (!m) { 32062 m = e.frameState.coordinateToPixelMatrix, 32063 m[2] = m[4]; 32064 m[3] = m[5]; 32065 m[4] = m[12]; 32066 m[5] = m[13]; 32067 } 32068 this.center_ = [ ctx.canvas.width/2/ratio, ctx.canvas.height/ratio ]; 32069 var f = this.layer_.getSource().getFeaturesInExtent(e.frameState.extent); 32070 ctx.save(); 32071 ctx.scale(ratio,ratio); 32072 var s = this.getStyle(); 32073 ctx.lineWidth = s.getStroke().getWidth(); 32074 ctx.strokeStyle = ol.color.asString(s.getStroke().getColor()); 32075 ctx.fillStyle = ol.color.asString(s.getFill().getColor()); 32076 var builds = []; 32077 for (var i=0; i<f.length; i++) { 32078 var h = this.getFeatureHeight(f[i]) 32079 if (h) builds.push (this.getFeature3D_ (f[i], h)); 32080 } 32081 if (this.get('ghost')) this.drawGhost3D_ (ctx, builds); 32082 else this.drawFeature3D_ (ctx, builds);
32083 ctx.restore(); 32084}; 32085/** Set layer to render 3D 32086 */ 32087ol.render3D.prototype.setLayer = function(l) { 32088 if (this._listener) { 32089 this._listener.forEach( function(l) { 32090 ol.Observable.unByKey(l); 32091 }); 32092 } 32093 this.layer_ = l; 32094 this._listener = l.on (['postcompose', 'postrender'], this.onPostcompose_.bind(this)); 32095} 32096/** Create a function that return height of a feature 32097 * @param {function|string|number} h a height function or a popertie name or a fixed value 32098 * @return {function} function(f) return height of the feature f 32099 */ 32100ol.render3D.prototype.getHfn= function(h) { 32101 switch (typeof(h)) { 32102 case 'function': return h; 32103 case 'string': { 32104 var dh = this.get('defaultHeight'); 32105 return (function(f) { 32106 return (Number(f.get(h)) || dh); 32107 }); 32108 } 32109 case 'number': return (function(/*f*/) { return h; }); 32110 default: return (function(/*f*/) { return 10; }); 32111 } 32112} 32113/** Animate rendering 32114 * @param {olx.render3D.animateOptions} 32115 * @param {string|function|number} param.height an attribute name or a function returning height of a feature or a fixed value 32116 * @param {number} param.duration the duration of the animatioin ms, default 1000 32117 * @param {ol.easing} param.easing an ol easing function 32118 * @api 32119 */ 32120ol.render3D.prototype.animate = function(options) { 32121 options = options || {}; 32122 this.toHeight_ = this.getHfn(options.height); 32123 this.animate_ = new Date().getTime(); 32124 this.animateDuration_ = options.duration ||1000; 32125 this.easing_ = options.easing || ol.easing.easeOut; 32126 // Force redraw 32127 this.layer_.changed(); 32128} 32129/** Check if animation is on 32130 * @return {bool} 32131 */ 32132ol.render3D.prototype.animating = function() { 32133 if (this.animate_ && new Date().getTime() - this.animate_ > this.animateDuration_) { 32134 this.animate_ = false; 32135 } 32136 return !!this.animate_; 32137} 32138/** Get feature height 32139 * @param {ol.Feature} f 32140 */ 32141ol.render3D.prototype.getFeatureHeight = function (f) { 32142 if (this.animate_) { 32143 var h1 = this.height_(f); 32144 var h2 = this.toHeight_(f); 32145 return (h1*(1-this.elapsedRatio_)+this.elapsedRatio_*h2); 32146 } 32147 else return this.height_(f); 32148}; 32149/** Get elevation line 32150 * @private 32151 */ 32152ol.render3D.prototype.hvector_ = function (pt, h) { 32153 var p0 = [ 32154 pt[0]*this.matrix_[0] + pt[1]*this.matrix_[1] + this.matrix_[4], 32155 pt[0]*this.matrix_[2] + pt[1]*this.matrix_[3] + this.matrix_[5] 32156 ]; 32157 return { 32158 p0: p0, 32159 p1: [ 32160 p0[0] + h/this.res_ * (p0[0]-this.center_[0]), 32161 p0[1] + h/this.res_ * (p0[1]-this.center_[1]) 32162 ] 32163 }; 32164}; 32165/** Get drawing 32166 * @private 32167 */ 32168ol.render3D.prototype.getFeature3D_ = function (f, h) { 32169 var geom = this.get('geometry')(f); 32170 var c = geom.getCoordinates(); 32171 switch (geom.getType()) { 32172 case "Polygon": 32173 c = [c]; 32174 // fallthrough 32175 case "MultiPolygon": 32176 var build = []; 32177 for (var i=0; i<c.length; i++) { 32178 for (var j=0; j<c[i].length; j++) { 32179 var b = []; 32180 for (var k=0; k<c[i][j].length; k++) { 32181 b.push( this.hvector_(c[i][j][k], h) ); 32182 } 32183 build.push(b); 32184 } 32185 } 32186 return { type:"MultiPolygon", feature: f, geom: build, height: h }; 32187 case "Point": 32188 return { type:"Point", feature: f, geom: this.hvector_(c,h), height: h }; 32189 default: return {}; 32190 } 32191} 32192/** Draw a feature 32193 * @param {CanvasRenderingContext2D} ctx 32194 * @param {ol.Feature} build 32195 * @private 32196 */ 32197ol.render3D.prototype.drawFeature3D_ = function(ctx, build) { 32198 var i,j, b, k; 32199 // Construct 32200 for (i=0; i<build.length; i++) { 32201 switch (build[i].type) { 32202 case "MultiPolygon": { 32203 for (j=0; j<build[i].geom.length; j++) { 32204 b = build[i].geom[j]; 32205 for (k=0; k < b.length; k++) { 32206 ctx.beginPath(); 32207 ctx.moveTo(b[k].p0[0], b[k].p0[1]); 32208 ctx.lineTo(b[k].p1[0], b[k].p1[1]); 32209 ctx.stroke(); 32210 } 32211 } 32212 break; 32213 } 32214 case "Point": { 32215 var g = build[i].geom; 32216 ctx.beginPath(); 32217 ctx.moveTo(g.p0[0], g.p0[1]); 32218 ctx.lineTo(g.p1[0], g.p1[1]); 32219 ctx.stroke(); 32220 break; 32221 } 32222 default: break; 32223 } 32224 } 32225 // Roof 32226 for (i=0; i<build.length; i++) { 32227 switch (build[i].type) { 32228 case "MultiPolygon": { 32229 ctx.beginPath(); 32230 for (j=0; j<build[i].geom.length; j++) { 32231 b = build[i].geom[j]; 32232 if (j==0) { 32233 ctx.moveTo(b[0].p1[0], b[0].p1[1]); 32234 for (k=1; k < b.length; k++) { 32235 ctx.lineTo(b[k].p1[0], b[k].p1[1]); 32236 } 32237 } else { 32238 ctx.moveTo(b[0].p1[0], b[0].p1[1]); 32239 for (k=b.length-2; k>=0; k--) { 32240 ctx.lineTo(b[k].p1[0], b[k].p1[1]); 32241 } 32242 } 32243 ctx.closePath(); 32244 } 32245 ctx.fill("evenodd"); 32246 ctx.stroke(); 32247 break; 32248 } 32249 case "Point": { 32250 b = build[i]; 32251 var t = b.feature.get('label'); 32252 if (t) { 32253 var p = b.geom.p1; 32254 var f = ctx.fillStyle; 32255 ctx.fillStyle = ctx.strokeStyle; 32256 ctx.textAlign = 'center'; 32257 ctx.textBaseline = 'bottom'; 32258 ctx.fillText ( t, p[0], p[1] ); 32259 var m = ctx.measureText(t); 32260 var h = Number (ctx.font.match(/\d+(\.\d+)?/g).join([])); 32261 ctx.fillStyle = "rgba(255,255,255,0.5)"; 32262 ctx.fillRect (p[0]-m.width/2 -5, p[1]-h -5, m.width +10, h +10) 32263 ctx.strokeRect (p[0]-m.width/2 -5, p[1]-h -5, m.width +10, h +10) 32264 ctx.fillStyle = f; 32265 //console.log(build[i].feature.getProperties()) 32266 } 32267 break; 32268 } 32269 default: break; 32270 } 32271 } 32272}; 32273/** 32274 * @private 32275 */ 32276ol.render3D.prototype.drawGhost3D_ = function(ctx, build) { 32277 var i,j, b, k; 32278 // Construct 32279 for (i=0; i<build.length; i++) { 32280 switch (build[i].type) { 32281 case "MultiPolygon": { 32282 for (j=0; j<build[i].geom.length; j++) { 32283 b = build[i].geom[j]; 32284 for (k=0; k < b.length-1; k++) { 32285 ctx.beginPath(); 32286 ctx.moveTo(b[k].p0[0], b[k].p0[1]); 32287 ctx.lineTo(b[k].p1[0], b[k].p1[1]); 32288 ctx.lineTo(b[k+1].p1[0], b[k+1].p1[1]); 32289 ctx.lineTo(b[k+1].p0[0], b[k+1].p0[1]); 32290 ctx.lineTo(b[k].p0[0], b[k].p0[1]); 32291 var m = [(b[k].p0[0] + b[k+1].p0[0]) /2, (b[k].p0[1] + b[k+1].p0[1]) /2]; 32292 var h = [b[k].p0[1] - b[k+1].p0[1], - b[k].p0[0] + b[k+1].p0[0]]; 32293 var c = ol.coordinate.getIntersectionPoint( 32294 [m, [m[0] + h[0], m[1]+ h[1]]], 32295 [b[k].p1, b[k+1].p1] 32296 ); 32297 var gradient = ctx.createLinearGradient( 32298 m[0], m[1], 32299 c[0], c[1] 32300 ); 32301 gradient.addColorStop(0, 'rgba(255,255,255,.2)'); 32302 gradient.addColorStop(1, 'rgba(255,255,255,0)'); 32303 ctx.fillStyle = gradient; 32304 ctx.fill(); 32305 } 32306 } 32307 break; 32308 } 32309 case "Point": { 32310 var g = build[i].geom; 32311 ctx.beginPath(); 32312 ctx.moveTo(g.p0[0], g.p0[1]); 32313 ctx.lineTo(g.p1[0], g.p1[1]); 32314 ctx.stroke(); 32315 break; 32316 } 32317 default: break; 32318 } 32319 } 32320}; 32321 32322/* Copyright (c) 2019 Jean-Marc VIGLINO, 32323 released under the CeCILL-B license (French BSD license)
32324 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 32325*/ 32326/** A sketch layer used as overlay to handle drawing sketch (helper for drawing tools) 32327 * @constructor 32328 * @extends {ol/layer/Vector} 32329 * @fires drawstart 32330 * @fires drawend 32331 * @fires drawabort 32332 * @param {*} options 32333 * @param {string} options.type Geometry type, default LineString 32334 * @param {ol.style.Style|Array<ol.style.Style>} options.style Drawing style 32335 * @param {ol.style.Style|Array<ol.style.Style>} options.sketchStyle Sketch style 32336 */ 32337ol.layer.SketchOverlay = function(options) { 32338 options = options || {}; 32339 var style = options.style || ol.style.Style.defaultStyle(true); 32340 var sketchStyle = options.sketchStyle; 32341 if (!sketchStyle) { 32342 sketchStyle = ol.style.Style.defaultStyle(); 32343 sketchStyle = [ 32344 new ol.style.Style({ 32345 image: new ol.style.RegularShape ({ 32346 points: 4, 32347 radius: 10, 32348 radius2: 0, 32349 stroke: new ol.style.Stroke({ 32350 color: [255,255,255, .5], 32351 width: 3 32352 }) 32353 }) 32354 }), 32355 sketchStyle[0].clone() 32356 ]; 32357 sketchStyle[1].setImage(new ol.style.RegularShape ({ 32358 points: 4, 32359 radius: 10, 32360 radius2: 0, 32361 stroke: new ol.style.Stroke({ 32362 color: [0, 153, 255, 1], 32363 width: 1.25 32364 }) 32365 })); 32366 } 32367 this._geom = []; 32368 ol.layer.Vector.call (this, { 32369 name: 'sketch', 32370 source: new ol.source.Vector({ useSpatialIndex: false }), 32371 style: function(f) { 32372 return (f.get('sketch') ? sketchStyle : style); 32373 }, 32374 updateWhileAnimating: true, 32375 updateWhileInteracting: true 32376 }); 32377 // Sketch features 32378 this.getSource().addFeatures([ 32379 new ol.Feature({ 32380 sketch: true, 32381 geometry: new ol.geom.Point([]) 32382 }), 32383 new ol.Feature({ 32384 sketch: true, 32385 geometry: new ol.geom.LineString([]) 32386 }), 32387 new ol.Feature(), 32388 new ol.Feature(new ol.geom.Point([])) 32389 ]); 32390 this.setGeometryType(options.type); 32391}; 32392ol.ext.inherits (ol.layer.SketchOverlay, ol.layer.Vector); 32393/** Set geometry type 32394 * @param {string} type Geometry type 32395 * @return {string} the current type 32396 */ 32397ol.layer.SketchOverlay.prototype.setGeometryType = function(type) { 32398 var t = /^Point$|^LineString$|^Polygon$|^Circle$/.test(type) ? type : 'LineString'; 32399 if (t !== this._type) { 32400 this.abortDrawing(); 32401 this._type = t; 32402 } 32403 return this._type; 32404}; 32405/** Get geometry type 32406 * @return {string} Geometry type 32407 */ 32408ol.layer.SketchOverlay.prototype.getGeometryType = function() { 32409 return this._type; 32410}; 32411/** Add a new Point to the sketch 32412 * @param {ol.coordinate} coord 32413 * @return {boolean} true if point has been added, false if same coord 32414 */ 32415ol.layer.SketchOverlay.prototype.addPoint = function(coord) { 32416 if (this._lastCoord !== this._position) { 32417 if (!this._geom.length) { 32418 this.startDrawing(); 32419 } 32420 this._geom.push(coord); 32421 this._lastCoord = coord; 32422 this._position = coord; 32423 this.drawSketch(); 32424 if (this.getGeometryType() === 'Point') { 32425 this.finishDrawing(); 32426 } 32427 if (this.getGeometryType() === 'Circle' && this._geom.length>=2) { 32428 this.finishDrawing(); 32429 } 32430 return true; 32431 } 32432 return false; 32433}; 32434/** Remove the last Point from the sketch 32435 */ 32436ol.layer.SketchOverlay.prototype.removeLastPoint = function() { 32437 this._geom.pop(); 32438 this._lastCoord = this._geom[this._geom.length-1]; 32439 this.drawSketch(); 32440}; 32441/** Strat a new drawing 32442 * @param {*} options 32443 * @param {string} type Geometry type, default the current type 32444 * @param {Array<ol.coordinate>} coordinates a list of coordinates to extend 32445 * @param {ol.Feature} feature a feature to extend (LineString or Polygon only) 32446 * @param {boolean} atstart extent coordinates or feature at start, default false (extend at end) 32447 */ 32448ol.layer.SketchOverlay.prototype.startDrawing = function(options) { 32449 options = options || {}; 32450 this._geom = []; 32451 if (options.type) this.setGeometryType(options.type); 32452 this.drawSketch(); 32453 if (!this._drawing) { 32454 this.dispatchEvent({ 32455 type: 'drawstart', 32456 feature: this.getFeature() 32457 }); 32458 } 32459 this._drawing = true; 32460}; 32461/** Finish drawing 32462 * @return {ol.Feature} the drawed feature 32463 */
32464ol.layer.SketchOverlay.prototype.finishDrawing = function(valid) { 32465 var f = this.getSource().getFeatures()[2].clone(); 32466 var isvalid = !!f; 32467 switch (this.getGeometryType()) { 32468 case 'Circle': 32469 case 'LineString': { 32470 isvalid = this._geom.length > 1; 32471 break; 32472 } 32473 case 'Polygon': { 32474 isvalid = this._geom.length > 2; 32475 break; 32476 } 32477 } 32478 if (valid && !isvalid) return false; 32479 this._geom = []; 32480 this._lastCoord = null; 32481 this.drawSketch(); 32482 if (this._drawing) { 32483 this.dispatchEvent({ 32484 type: 'drawend', 32485 valid: isvalid, 32486 feature: f 32487 }); 32488 } 32489 this._drawing = false 32490 return f; 32491}; 32492/** Abort drawing 32493 */ 32494ol.layer.SketchOverlay.prototype.abortDrawing = function() { 32495 if (this._drawing) { 32496 this.dispatchEvent({ 32497 type: 'drawabort', 32498 feature: this.getFeature() 32499 }); 32500 } 32501 this._drawing = false; 32502 this._geom = []; 32503 this._lastCoord = null; 32504 this.drawSketch(); 32505}; 32506/** Set current position 32507 * @param {ol.coordinate} coord 32508 */ 32509ol.layer.SketchOverlay.prototype.setPosition = function(coord) { 32510 this._position = coord; 32511 this.drawLink(); 32512}; 32513/** Get current position 32514 * @return {ol.coordinate} 32515 */ 32516ol.layer.SketchOverlay.prototype.getPosition = function() { 32517 return this._position; 32518}; 32519/** Draw/refresh link 32520 */ 32521ol.layer.SketchOverlay.prototype.drawLink = function() { 32522 var features = this.getSource().getFeatures(); 32523 if (this._position) { 32524 if (this._lastCoord && this._lastCoord === this._position) { 32525 features[0].getGeometry().setCoordinates([]); 32526 } else { 32527 features[0].getGeometry().setCoordinates(this._position); 32528 } 32529 if (this._geom.length) { 32530 if (this.getGeometryType()==='Circle') { 32531 features[1].setGeometry(new ol.geom.Circle(this._geom[0], ol.coordinate.dist2d(this._geom[0], this._position))); 32532 } else if (this.getGeometryType()==='Polygon') { 32533 features[1].setGeometry(new ol.geom.LineString([ this._lastCoord, this._position, this._geom[0] ])); 32534 } else { 32535 features[1].setGeometry(new ol.geom.LineString([ this._lastCoord, this._position ])); 32536 } 32537 } else { 32538 features[1].setGeometry(new ol.geom.LineString([])); 32539 } 32540 } else { 32541 features[0].getGeometry().setCoordinates([]); 32542 features[1].setGeometry(new ol.geom.LineString([])); 32543 } 32544}; 32545/** Get current feature 32546 */ 32547ol.layer.SketchOverlay.prototype.getFeature = function() { 32548 return this.getSource().getFeatures()[2]; 32549}; 32550/** Draw/refresh sketch 32551 */ 32552ol.layer.SketchOverlay.prototype.drawSketch = function() { 32553 this.drawLink(); 32554 var features = this.getSource().getFeatures(); 32555 if (!this._geom.length) { 32556 features[2].setGeometry(null); 32557 features[3].setGeometry(new ol.geom.Point([])); 32558 } else { 32559 if (!this._lastCoord) this._lastCoord = this._geom[this._geom.length-1]; 32560 features[3].getGeometry().setCoordinates(this._lastCoord); 32561 switch (this._type) { 32562 case 'Point': { 32563 features[2].setGeometry(new ol.geom.Point(this._lastCoord)); 32564 break; 32565 } 32566 case 'Circle': { 32567 if (!features[2].getGeometry()) { 32568 features[2].setGeometry(new ol.geom.Circle(this._geom[0], ol.coordinate.dist2d(this._geom[0], this._geom[this._geom.length-1]))); 32569 } else { 32570 features[2].getGeometry().setRadius(ol.coordinate.dist2d(this._geom[0], this._geom[this._geom.length-1])); 32571 } 32572 break; 32573 } 32574 case 'LineString': { 32575 if (!features[2].getGeometry()) { 32576 features[2].setGeometry(new ol.geom.LineString(this._geom)); 32577 } else { 32578 features[2].getGeometry().setCoordinates(this._geom); 32579 } 32580 break; 32581 } 32582 case 'Polygon': { 32583 this._geom.push(this._geom[0]); 32584 if (!features[2].getGeometry()) { 32585 features[2].setGeometry(new ol.geom.Polygon([this._geom])); 32586 } else { 32587 features[2].getGeometry().setCoordinates([this._geom]); 32588 } 32589 this._geom.pop(); 32590 break; 32591 } 32592 default: { 32593 console.error('[ol/layer/SketchOverlay~drawSketch] geometry type not supported ('+this._type+')'); 32594 break; 32595 } 32596 } 32597 } 32598}; 32599 32600/* Copyright (c) 2019 Jean-Marc VIGLINO, 32601 released under the CeCILL-B license (French BSD license)
32602 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 32603*/ 32604/** A map with a perspective 32605 * @constructor 32606 * @extends {ol.Map} 32607 * @fires change:perspective 32608 * @param {olx.MapOptions=} options 32609 * @param {ol.events.condition} tiltCondition , default altKeyOnly 32610 */ 32611ol.PerspectiveMap = function(options) { 32612 // Map div 32613 var divMap = options.target instanceof Element ? options.target : document.getElementById(options.target); 32614 if (window.getComputedStyle(divMap).position !== 'absolute') { 32615 divMap.style.position = 'relative'; 32616 } 32617 divMap.style.overflow = 'hidden'; 32618 // Create map inside 32619 var map = ol.ext.element.create('DIV', { 32620 className: 'ol-perspective-map', 32621 parent: divMap 32622 }); 32623 var opts = {}; 32624 Object.assign(opts, options); 32625 opts.target = map; 32626 // enhance pixel ratio 32627 //opts.pixelRatio = 2; 32628 ol.Map.call (this, opts); 32629 this._tiltCondition = options.tiltCondition || ol.events.condition.altKeyOnly; 32630}; 32631ol.ext.inherits (ol.PerspectiveMap, ol.Map); 32632/** Get pixel ratio for the map 32633 */ 32634ol.PerspectiveMap.prototype.getPixelRatio = function(){ 32635 return window.devicePixelRatio; 32636}; 32637/** Set perspective angle 32638 * @param {number} angle the perspective angle 0 (vertical) - 30 (max), default 0 32639 * @param {*} options 32640 * @param {number} options.duration The duration of the animation in milliseconds, default 500 32641 * @param {function} options.easing The easing function used during the animation, defaults to ol.easing.inAndOut). 32642 */ 32643ol.PerspectiveMap.prototype.setPerspective = function(angle, options) { 32644 options = options || {}; 32645 // max angle 32646 if (angle > 30) angle = 30; 32647 else if (angle<0) angle = 0; 32648 var fromAngle = this._angle || 0; 32649 var toAngle = Math.round(angle*10)/10; 32650 var style = this.getTarget().querySelector('.ol-layers').style; 32651 cancelAnimationFrame(this._animatedPerspective) 32652 requestAnimationFrame(function(t) { 32653 this._animatePerpective(t, t, style, fromAngle, toAngle, options.duration, options.easing||ol.easing.inAndOut); 32654 }.bind(this)) 32655}; 32656/** Animate the perspective 32657 * @param {number} t0 starting timestamp 32658 * @param {number} t current timestamp 32659 * @param {CSSStyleDeclaration} style style to modify 32660 * @param {number} fromAngle starting angle 32661 * @param {number} toAngle ending angle 32662 * @param {number} duration The duration of the animation in milliseconds, default 500 32663 * @param {function} easing The easing function used during the animation, defaults to ol.easing.inAndOut). 32664 * @private 32665 */ 32666ol.PerspectiveMap.prototype._animatePerpective = function(t0, t, style, fromAngle, toAngle, duration, easing ) { 32667 var dt, end; 32668 if (duration === 0) { 32669 dt = 1; 32670 end = true; 32671 } else { 32672 dt = (t-t0)/(duration||500); 32673 end = (dt>=1); 32674 } 32675 dt = easing(dt); 32676 var angle; 32677 if (end) { 32678 angle = this._angle = toAngle; 32679 } else { 32680 angle = this._angle = fromAngle + (toAngle-fromAngle)*dt; 32681 } 32682 var fac = angle/30; 32683 // apply transform to the style 32684 style.transform = 'translateY(-'+(17*fac)+'%) perspective(200px) rotateX('+angle+'deg) scaleY('+(1-fac/2)+')'; 32685 this.getMatrix3D(true); 32686 this.render(); 32687 if (!end) { 32688 requestAnimationFrame(function(t) { 32689 this._animatePerpective(t0, t, style, fromAngle, toAngle, duration||500, easing||ol.easing.inAndOut); 32690 }.bind(this)) 32691 } 32692 // Dispatch event 32693 this.dispatchEvent({ 32694 type: 'change:perspective', 32695 angle: angle, 32696 animating: !end 32697 }); 32698}; 32699/** Convert to pixel coord according to the perspective 32700 * @param {MapBrowserEvent} mapBrowserEvent The event to handle. 32701 */ 32702ol.PerspectiveMap.prototype.handleMapBrowserEvent = function(e) { 32703 e.pixel = [ 32704 e.originalEvent.offsetX / this.getPixelRatio(), 32705 e.originalEvent.offsetY / this.getPixelRatio() 32706 ]; 32707 e.coordinate = this.getCoordinateFromPixel(e.pixel); 32708 ol.Map.prototype.handleMapBrowserEvent.call (this, e); 32709 // Change perspective on tilt condition 32710 if (this._tiltCondition(e)) { 32711 switch(e.type) { 32712 case 'pointerdown': { 32713 this._dragging = e.originalEvent.offsetY; 32714 break; 32715 } 32716 case 'pointerup': { 32717 this._dragging = false; 32718 break; 32719 } 32720 case 'pointerdrag': { 32721 if (this._dragging !== false) { 32722 var angle = e.originalEvent.offsetY > this._dragging ? .5 : -.5; 32723 if (angle) { 32724 this.setPerspective((this._angle||0) + angle, { duration: 0 }); 32725 } 32726 this._dragging = e.originalEvent.offsetY; 32727 } 32728 break; 32729 } 32730 } 32731 } else { 32732 this._dragging = false;
32733 } 32734}; 32735/** Get map full teansform matrix3D 32736 * @return {Array<Array<number>>} 32737 */ 32738ol.PerspectiveMap.prototype.getMatrix3D = function (compute) { 32739 if (compute) { 32740 var ele = this.getTarget().querySelector('.ol-layers'); 32741 // Get transform matrix3D from CSS 32742 var tx = ol.matrix3D.getTransform(ele); 32743 // Get the CSS transform origin from the transformed parent - default is '50% 50%' 32744 var txOrigin = ol.matrix3D.getTransformOrigin(ele); 32745 // Compute the full transform that is applied to the transformed parent (-origin * tx * origin) 32746 this._matrixTransform = ol.matrix3D.computeTransformMatrix(tx, txOrigin); 32747 } 32748 if (!this._matrixTransform) this._matrixTransform = ol.matrix3D.identity(); 32749 return this._matrixTransform; 32750}; 32751/** Get pixel at screen from coordinate. 32752 * The default getPixelFromCoordinate get pixel in the perspective. 32753 * @param {ol.coordinate} coord 32754 * @param {ol.pixel} 32755 */ 32756ol.PerspectiveMap.prototype.getPixelScreenFromCoordinate = function (coord) { 32757 // Get pixel in the transform system 32758 var px = this.getPixelFromCoordinate(coord); 32759 // Get transform matrix3D from CSS 32760 var fullTx = this.getMatrix3D(); 32761 // Transform the point using full transform 32762 var pixel = ol.matrix3D.transformVertex(fullTx, px); 32763 // Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component). 32764 pixel = ol.matrix3D.projectVertex(pixel); 32765 return [pixel[0], pixel[1]]; 32766}; 32767/** Not working... 32768 * 32769 */ 32770ol.PerspectiveMap.prototype.getPixelFromPixelScreen = function (px) { 32771 // Get transform matrix3D from CSS 32772 var fullTx = ol.matrix3D.inverse(this.getMatrix3D()); 32773 // Transform the point using full transform 32774 var pixel = ol.matrix3D.transformVertex(fullTx, px); 32775 // Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component). 32776 pixel = ol.matrix3D.projectVertex(pixel); 32777 return [pixel[0], pixel[1]]; 32778}; 32779/* Overwrited Overlay function to handle overlay positing in a perspective map */ 32780(function() { 32781var _updatePixelPosition = ol.Overlay.prototype.updatePixelPosition; 32782/** Update pixel projection in a perspective map (apply projection to the position) 32783 * @private 32784 */ 32785ol.Overlay.prototype.updatePixelPosition = function () { 32786 var map = this.getMap(); 32787 if (map && map instanceof ol.PerspectiveMap) { 32788 var position = this.getPosition(); 32789 if (!map || !map.isRendered() || !position) { 32790 this.setVisible(false); 32791 return; 32792 } 32793 // Get pixel at screen 32794 var pixel = map.getPixelScreenFromCoordinate(position); 32795 var mapSize = map.getSize(); 32796 pixel[0] -= mapSize[0]/4 32797 pixel[1] -= mapSize[1]/4 32798 /* for ol v6.2.x 32799 // Offset according positioning 32800 var pos = this.getPositioning(); 32801 if (/bottom/.test(pos)) { 32802 pixel[1] += mapSize[1]/4 32803 } else { 32804 pixel[1] -= mapSize[1]/4 32805 } 32806 if (/right/.test(pos)) { 32807 pixel[0] += mapSize[0]/4 32808 } else { 32809 pixel[0] -= mapSize[0]/4 32810 } 32811 */ 32812 // Update 32813 this.updateRenderedPosition(pixel , mapSize); 32814 } else { 32815 _updatePixelPosition.call(this); 32816 } 32817}; 32818/**/ 32819})(); 32820 32821/* 32822 Copyright (c) 2020 Jean-Marc VIGLINO, 32823 released under the CeCILL-B license (French BSD license) 32824 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 32825*/ 32826/*global ol*/ 32827if (window.ol && !ol.particule) { 32828 ol.particule = {}; 32829} 32830/** Abstract base class; normally only used for creating subclasses. 32831 * An object with coordinates, draw and update 32832 * @constructor 32833 * @extends {ol.Object} 32834 * @param {*} options 32835 * @param {ol.Overlay} options.overlay 32836 * @param {ol.pixel} coordinate the position of the particule 32837 */ 32838ol.particule.Base = function(options) { 32839 if (!options) options = {}; 32840 ol.Object.call(this); 32841 this.setOverlay(options.overlay) ; 32842 this.coordinate = options.coordinate || [0,0]; 32843}; 32844ol.ext.inherits(ol.particule.Base, ol.Object); 32845/** Set the particule overlay 32846 * @param {ol.Overlay} overl 32847 */ 32848ol.particule.Base.prototype.setOverlay = function(overlay) { 32849 this._overlay = overlay; 32850}; 32851/** Get the particule overlay 32852 * @return {ol.Overlay} 32853 */ 32854ol.particule.Base.prototype.getOverlay = function() { 32855 return this._overlay; 32856}; 32857/** Draw the particule 32858 * @param { CanvasRenderingContext2D } ctx 32859 */ 32860ol.particule.Base.prototype.draw = function(/* ctx */) { 32861}; 32862/** Update the particule 32863 * @param {number} dt timelapes since last call 32864 */ 32865ol.particule.Base.prototype.update = function(/* dt */) { 32866}; 32867/** Update the particule 32868 * @param {number} dt timelapes since last call 32869 */ 32870ol.particule.Base.prototype.getRandomCoord = function(dt) { 32871 if (this.getOverlay().randomCoord) return this.getOverlay().randomCoord(); 32872 else return [dt,0]; 32873}; 32874 32875/* 32876 Copyright (c) 2020 Jean-Marc VIGLINO, 32877 released under the CeCILL-B license (French BSD license)
32878 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 32879*/ 32880/** A cloud particule to display clouds over the map 32881 * @constructor 32882 * @extends {ol.particule.Base} 32883 * @param {*} options 32884 * @param {ol.Overlay} options.overlay 32885 * @param {ol.pixel} coordinate the position of the particule 32886 */ 32887ol.particule.Bird = function(options) { 32888 if (!options) options = {}; 32889 ol.particule.Base.call(this, options); 32890 this.bird = new Image(); 32891 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=="; 32892 this.set('size', [this.bird.width || 50, this.bird.height || 50]); 32893}; 32894ol.ext.inherits(ol.particule.Bird, ol.particule.Base); 32895/** Draw the particule 32896 * @param {CanvasRenderingContext2D } ctx 32897 */ 32898ol.particule.Bird.prototype.draw = function(ctx) { 32899 //ctx.drawImage(this.bird, this.coordinate[0], this.coordinate[1]); 32900 var angle = this.getOverlay().get('angle'); 32901 ctx.save(); 32902 ctx.translate (this.coordinate[0], this.coordinate[1]); 32903 ctx.rotate(angle+Math.PI/2); 32904 ctx.scale(.5,.5); 32905 ctx.drawImage( this.bird, -this.bird.width/2, -this.bird.height/2 ); 32906 ctx.restore(); 32907}; 32908/** Update the particule 32909 * @param {number} dt timelapes since last call 32910 */ 32911ol.particule.Bird.prototype.update = function(dt) { 32912 var speed = this.getOverlay().get('speed') * dt / this.getOverlay()._fps; 32913 var angle = this.getOverlay().get('angle'); 32914 this.coordinate[0] += speed * Math.cos(angle); 32915 this.coordinate[1] += speed * Math.sin(angle); 32916}; 32917 32918/* 32919 Copyright (c) 2020 Jean-Marc VIGLINO, 32920 released under the CeCILL-B license (French BSD license)
32921 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 32922*/ 32923/** A cloud particule to display clouds over the map 32924 * @constructor 32925 * @extends {ol.particule.Base} 32926 * @param {*} options 32927 * @param {ol.Overlay} options.overlay 32928 * @param {ol.pixel} coordinate the position of the particule 32929 */ 32930ol.particule.Cloud = function(options) { 32931 if (!options) options = {}; 32932 ol.particule.Base.call(this, options); 32933 this.set('size', [100,100]) 32934 var canvas = document.createElement('CANVAS'); 32935 canvas.width = 200; 32936 canvas.height = 200; 32937 var ctx = canvas.getContext('2d'); 32938 var grd = this.gradient = ctx.createRadialGradient(50,50, 0, 50,50, 50); 32939 grd.addColorStop(0, 'rgba(255,255,255,.2'); 32940 grd.addColorStop(1, 'rgba(255,255,255,0'); 32941 // Create cloud image 32942 this.image = canvas; 32943 for (var k=0; k<7; k++) { 32944 ctx.save(); 32945 var x = Math.random()*100; 32946 var y = Math.random()*100; 32947 ctx.translate (x,y); 32948 ctx.fillStyle = grd; 32949 ctx.fillRect(0,0,canvas.width,canvas.height); 32950 ctx.restore(); 32951 } 32952}; 32953ol.ext.inherits(ol.particule.Cloud, ol.particule.Base); 32954/** Draw the particule 32955 * @param {CanvasRenderingContext2D } ctx 32956 */ 32957ol.particule.Cloud.prototype.draw = function(ctx) { 32958 ctx.save(); 32959 ctx.translate (this.coordinate[0], this.coordinate[1]); 32960 ctx.drawImage(this.image, -this.image.width/2, -this.image.width/2); 32961 ctx.restore(); 32962}; 32963/** Update the particule 32964 * @param {number} dt timelapes since last call 32965 */ 32966ol.particule.Cloud.prototype.update = function(dt) { 32967 var speed = this.getOverlay().get('speed') * dt / this.getOverlay()._fps; 32968 var angle = this.getOverlay().get('angle'); 32969 this.coordinate[0] += speed * Math.cos(angle); 32970 this.coordinate[1] += speed * Math.sin(angle); 32971}; 32972 32973/* 32974 Copyright (c) 2020 Jean-Marc VIGLINO, 32975 released under the CeCILL-B license (French BSD license) 32976 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 32977*/ 32978/** Rain particules to display clouds over the map 32979 * @constructor 32980 * @extends {ol.particule.Base} 32981 * @param {*} options 32982 * @param {ol.Overlay} options.overlay 32983 * @param {ol.pixel} coordinate the position of the particule 32984 */ 32985ol.particule.Rain = function(options) { 32986 if (!options) options = {}; 32987 ol.particule.Base.call(this, options); 32988 this.z = Math.floor(Math.random()*5) + 1; 32989 var canvas = document.createElement('CANVAS'); 32990 canvas.width = 50; 32991 canvas.height = 50; 32992 var ctx = canvas.getContext('2d'); 32993 this.gradient = ctx.createRadialGradient(0,0, 0, 0,0, 25); 32994 this.gradient.addColorStop(0, 'rgba(0,0,80,0)'); 32995 this.gradient.addColorStop(1, 'rgba(0,0,80,.3)'); 32996}; 32997ol.ext.inherits(ol.particule.Rain, ol.particule.Base); 32998/** Draw the particule 32999 * @param {CanvasRenderingContext2D } ctx 33000 */ 33001ol.particule.Rain.prototype.draw = function(ctx) { 33002 ctx.save(); 33003 var angle = this.getOverlay().get('angle'); 33004 ctx.beginPath(); 33005 var x1 = Math.cos(angle) * 10 * (1+this.z/2); 33006 var y1 = Math.sin(angle) * 10 * (1+this.z/2); 33007 ctx.lineWidth = Math.round(this.z/2); 33008 ctx.strokeStyle = this.gradient; 33009 ctx.translate (this.coordinate[0], this.coordinate[1]); 33010 ctx.moveTo(0,0); 33011 ctx.lineTo(x1, y1); 33012 ctx.stroke(); 33013 ctx.restore(); 33014}; 33015/** Update the particule 33016 * @param {number} dt timelapes since last call 33017 */ 33018ol.particule.Rain.prototype.update = function(dt) { 33019 var dl = this.getOverlay().get('speed') * dt / this.getOverlay()._fps * this.z; 33020 var angle = this.getOverlay().get('angle'); 33021 this.coordinate[0] += dl * Math.cos(angle); 33022 this.coordinate[1] += dl * Math.sin(angle); 33023}; 33024 33025/* 33026 Copyright (c) 2020 Jean-Marc VIGLINO, 33027 released under the CeCILL-B license (French BSD license) 33028 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 33029*/ 33030/** Raindrop particules to display clouds over the map 33031 * @constructor 33032 * @extends {ol.particule.Base} 33033 * @param {*} options 33034 * @param {ol.Overlay} options.overlay 33035 * @param {ol.pixel} coordinate the position of the particule 33036 */ 33037ol.particule.RainDrop = function(options) { 33038 if (!options) options = {}; 33039 ol.particule.Base.call(this, options); 33040 this.size = 0; 33041 // Drops 33042 var canvas = document.createElement('CANVAS'); 33043 canvas.width = 100; 33044 canvas.height = 100; 33045 var ctx = canvas.getContext('2d'); 33046 var grd = ctx.createRadialGradient(50,50, 0, 50,50, 50); 33047 grd.addColorStop(0, 'rgba(128,128,192,.8'); 33048 grd.addColorStop(1, 'rgba(128,128,192,0'); 33049 this.image = canvas; 33050 ctx.fillStyle = grd; 33051 ctx.fillRect(0,0,canvas.width,canvas.height); 33052}; 33053ol.ext.inherits(ol.particule.RainDrop, ol.particule.Base); 33054/** Draw the particule 33055 * @param {CanvasRenderingContext2D } ctx 33056 */ 33057ol.particule.RainDrop.prototype.draw = function(ctx) { 33058 if (this.size>0) { 33059 ctx.save(); 33060 ctx.translate (this.coordinate[0], this.coordinate[1]); 33061 ctx.globalAlpha = this.size/50; 33062 ctx.scale(1-this.size/50,1-this.size/50); 33063 ctx.drawImage(this.image, -50,-50);
33064 ctx.restore(); 33065 } 33066}; 33067/** Update the particule 33068 * @param {number} dt timelapes since last call 33069 */ 33070ol.particule.RainDrop.prototype.update = function(dt) { 33071 if (this.size>0 || Math.random() < .01) { 33072 if (this.size<=0) { 33073 this.size = 50; 33074 this.coordinates = this.getRandomCoord(); 33075 } 33076 this.size = this.size - Math.round(dt/20); 33077 } 33078}; 33079 33080/* 33081 Copyright (c) 2020 Jean-Marc VIGLINO, 33082 released under the CeCILL-B license (French BSD license) 33083 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 33084*/ 33085/** Rain particules to display clouds over the map 33086 * @constructor 33087 * @extends {ol.particule.Base} 33088 * @param {*} options 33089 * @param {ol.Overlay} options.overlay 33090 * @param {ol.pixel} coordinate the position of the particule 33091 */ 33092ol.particule.Snow = function(options) { 33093 if (!options) options = {}; 33094 ol.particule.Base.call(this, options); 33095 this.z = (Math.floor(Math.random()*5) + 1) / 5; 33096 this.angle = Math.random() * Math.PI; 33097 // Snow fakes 33098 var canvas = document.createElement('CANVAS'); 33099 canvas.width = 20; 33100 canvas.height = 20; 33101 var ctx = canvas.getContext('2d'); 33102 var grd = ctx.createRadialGradient(10,10, 0, 10,10, 10); 33103 grd.addColorStop(0, "rgba(255, 255, 255,1)"); // white 33104 grd.addColorStop(.8, "rgba(210, 236, 242,.8)"); // bluish 33105 grd.addColorStop(1, "rgba(237, 247, 249,0)"); // lighter bluish 33106 this.image = canvas; 33107 ctx.fillStyle = grd; 33108 ctx.fillRect(0,0,canvas.width,canvas.height); 33109}; 33110ol.ext.inherits(ol.particule.Snow, ol.particule.Base); 33111/** Draw the particule 33112 * @param {CanvasRenderingContext2D } ctx 33113 */ 33114ol.particule.Snow.prototype.draw = function(ctx) { 33115 ctx.save(); 33116 ctx.translate (this.coordinate[0], this.coordinate[1]); 33117 ctx.globalAlpha = .4 + this.z/2; 33118 ctx.scale(this.z,this.z); 33119 ctx.drawImage(this.image, -10,-10); 33120 ctx.restore(); 33121}; 33122/** Update the particule 33123 * @param {number} dt timelapes since last call 33124 */ 33125ol.particule.Snow.prototype.update = function(dt) { 33126 var speed = this.getOverlay().get('speed') * dt / this.getOverlay()._fps * this.z * 5; 33127 var angle = this.getOverlay().get('angle'); 33128 this.angle = this.angle + dt / this.getOverlay()._fps / 100; 33129 this.coordinate[0] += Math.sin(this.angle + this.z) * 2 + speed * Math.cos(angle); 33130 this.coordinate[1] += Math.cos(this.angle) + speed * Math.sin(angle); 33131}; 33132 33133/* Copyright (c) 2016 Jean-Marc VIGLINO, 33134 released under the CeCILL-B license (French BSD license) 33135 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 33136*/ 33137/** 33138 * @classdesc 33139 * A popup element to be displayed over the map and attached to a single map 33140 * location. The popup are customized using CSS. 33141 * 33142 * @example 33143var popup = new ol.Overlay.Popup(); 33144map.addOverlay(popup); 33145popup.show(coordinate, "Hello!"); 33146popup.hide(); 33147* 33148* @constructor 33149* @extends {ol.Overlay} 33150* @fires show 33151* @fires hide 33152* @param {} options Extend Overlay options 33153* @param {String} [options.popupClass] the a class of the overlay to style the popup. 33154* @param {boolean} [options.anim Animate=false] the popup the popup, default false. 33155* @param {bool} [options.closeBox=false] popup has a close box, default false. 33156* @param {function|undefined} [options.onclose] callback function when popup is closed 33157* @param {function|undefined} [options.onshow] callback function when popup is shown 33158* @param {Number|Array<number>} [options.offsetBox] an offset box 33159* @param {ol.OverlayPositioning | string | undefined} options.positioning 33160* the 'auto' positioning var the popup choose its positioning to stay on the map. 33161* @param {boolean} [options.minibar=false] add a mini vertical bar 33162* @api stable 33163*/ 33164ol.Overlay.Popup = function (options) { 33165 options = options || {}; 33166 if (typeof(options.offsetBox)==='number') this.offsetBox = [options.offsetBox,options.offsetBox,options.offsetBox,options.offsetBox]; 33167 else this.offsetBox = options.offsetBox; 33168 // Popup div 33169 var element = document.createElement("div"); 33170 //element.classList.add('ol-overlaycontainer-stopevent'); 33171 options.element = element; 33172 // Closebox 33173 this.closeBox = options.closeBox; 33174 this.onclose = options.onclose; 33175 this.onshow = options.onshow; 33176 ol.ext.element.create('BUTTON', { 33177 className: 'closeBox' + (options.closeBox ? ' hasclosebox':''), 33178 type: 'button', 33179 click: function() { 33180 this.hide(); 33181 }.bind(this), 33182 parent: element 33183 }); 33184 // Anchor div 33185 if (options.anchor!==false) { 33186 ol.ext.element.create('DIV', { 33187 className: 'anchor', 33188 parent: element 33189 }) 33190 } 33191 // Content 33192 this.content = ol.ext.element.create('DIV', { 33193 html: options.html || '', 33194 className: "ol-popup-content", 33195 parent: element 33196 }); 33197 if (options.minibar) { 33198 ol.ext.element.scrollDiv(this.content, { 33199 vertical: true, 33200 mousewheel: true, 33201 minibar: true 33202 }); 33203 } 33204 // Stop event 33205 if (options.stopEvent) { 33206 element.addEventListener("mousedown", function(e){ e.stopPropagation(); }); 33207 element.addEventListener("touchstart", function(e){ e.stopPropagation(); }); 33208 } 33209 ol.Overlay.call(this, options); 33210 this._elt = element; 33211 // call setPositioning first in constructor so getClassPositioning is called only once 33212 this.setPositioning(options.positioning || 'auto'); 33213 this.setPopupClass(options.popupClass || options.className || 'default'); 33214 if (options.anim) this.addPopupClass('anim'); 33215 // Show popup on timeout (for animation purposes) 33216 if (options.position) { 33217 setTimeout(function(){ this.show(options.position); }.bind(this)); 33218 } 33219}; 33220ol.ext.inherits(ol.Overlay.Popup, ol.Overlay); 33221/** 33222 * Get CSS class of the popup according to its positioning. 33223 * @private 33224 */ 33225ol.Overlay.Popup.prototype.getClassPositioning = function () { 33226 var c = ""; 33227 var pos = this.getPositioning(); 33228 if (/bottom/.test(pos)) c += "ol-popup-bottom "; 33229 if (/top/.test(pos)) c += "ol-popup-top "; 33230 if (/left/.test(pos)) c += "ol-popup-left ";
33231 if (/right/.test(pos)) c += "ol-popup-right "; 33232 if (/^center/.test(pos)) c += "ol-popup-middle "; 33233 if (/center$/.test(pos)) c += "ol-popup-center "; 33234 return c; 33235}; 33236/** 33237 * Set a close box to the popup. 33238 * @param {bool} b 33239 * @api stable 33240 */ 33241ol.Overlay.Popup.prototype.setClosebox = function (b) { 33242 this.closeBox = b; 33243 if (b) this.element.classList.add("hasclosebox"); 33244 else this.element.classList.remove("hasclosebox"); 33245}; 33246/** 33247 * Set the CSS class of the popup. 33248 * @param {string} c class name. 33249 * @api stable 33250 */ 33251ol.Overlay.Popup.prototype.setPopupClass = function (c) { 33252 var classes = ["ol-popup"]; 33253 if (this.getVisible()) classes.push('visible'); 33254 this.element.className = ""; 33255 var classesPositioning = this.getClassPositioning().split(' ') 33256 .filter(function(className) { 33257 return className.length > 0; 33258 }); 33259 if (c) { 33260 c.split(' ').filter(function(className) { 33261 return className.length > 0; 33262 }) 33263 .forEach(function(className) { 33264 classes.push(className); 33265 }); 33266 } else { 33267 classes.push("default"); 33268 } 33269 classesPositioning.forEach(function(className) { 33270 classes.push(className); 33271 }); 33272 if (this.closeBox) { 33273 classes.push("hasclosebox"); 33274 } 33275 this.element.classList.add.apply(this.element.classList, classes); 33276}; 33277/** 33278 * Add a CSS class to the popup. 33279 * @param {string} c class name. 33280 * @api stable 33281 */ 33282ol.Overlay.Popup.prototype.addPopupClass = function (c) { 33283 this.element.classList.add(c); 33284}; 33285/** 33286 * Remove a CSS class to the popup. 33287 * @param {string} c class name. 33288 * @api stable 33289 */ 33290ol.Overlay.Popup.prototype.removePopupClass = function (c) { 33291 this.element.classList.remove(c); 33292}; 33293/** 33294 * Set positionning of the popup 33295 * @param {ol.OverlayPositioning | string | undefined} pos an ol.OverlayPositioning 33296 * or 'auto' to var the popup choose the best position 33297 * @api stable 33298 */ 33299ol.Overlay.Popup.prototype.setPositioning = function (pos) { 33300 if (pos === undefined) 33301 return; 33302 if (/auto/.test(pos)) { 33303 this.autoPositioning = pos.split('-'); 33304 if (this.autoPositioning.length==1) this.autoPositioning[1]="auto"; 33305 } else { 33306 this.autoPositioning = false; 33307 } 33308 pos = pos.replace(/auto/g,"center"); 33309 if (pos=="center") pos = "bottom-center"; 33310 this.setPositioning_(pos); 33311}; 33312/** @private 33313 * @param {ol.OverlayPositioning | string | undefined} pos 33314 */ 33315ol.Overlay.Popup.prototype.setPositioning_ = function (pos) { 33316 if (this.element) { 33317 ol.Overlay.prototype.setPositioning.call(this, pos); 33318 this.element.classList.remove("ol-popup-top", "ol-popup-bottom", "ol-popup-left", "ol-popup-right", "ol-popup-center", "ol-popup-middle"); 33319 var classes = this.getClassPositioning().split(' ') 33320 .filter(function(className) { 33321 return className.length > 0; 33322 }); 33323 this.element.classList.add.apply(this.element.classList, classes); 33324 } 33325}; 33326/** Check if popup is visible 33327* @return {boolean} 33328*/ 33329ol.Overlay.Popup.prototype.getVisible = function () { 33330 return this.element.classList.contains("visible"); 33331}; 33332/** 33333 * Set the position and the content of the popup. 33334 * @param {ol.Coordinate|string} coordinate the coordinate of the popup or the HTML content. 33335 * @param {string|undefined} html the HTML content (undefined = previous content). 33336 * @example 33337var popup = new ol.Overlay.Popup(); 33338// Show popup 33339popup.show([166000, 5992000], "Hello world!"); 33340// Move popup at coord with the same info 33341popup.show([167000, 5990000]); 33342// set new info 33343popup.show("New informations"); 33344* @api stable 33345*/ 33346ol.Overlay.Popup.prototype.show = function (coordinate, html) { 33347 if (!html && typeof(coordinate)=='string') { 33348 html = coordinate; 33349 coordinate = null; 33350 } 33351 if (coordinate===true) { 33352 coordinate = this.getPosition(); 33353 } 33354 var self = this; 33355 var map = this.getMap(); 33356 if (!map) return; 33357 if (html && html !== this.prevHTML) { 33358 // Prevent flickering effect 33359 this.prevHTML = html; 33360 this.content.innerHTML = ''; 33361 if (html instanceof Element) { 33362 this.content.appendChild(html); 33363 } else { 33364 ol.ext.element.create('DIV', { 33365 html: html, 33366 parent: this.content 33367 }) 33368 } 33369 // Refresh when loaded (img) 33370 Array.prototype.slice.call(this.content.querySelectorAll('img')) 33371 .forEach(function(image) { 33372 image.addEventListener('load', function() { 33373 try { map.renderSync(); } catch(e) { /* ok */ } 33374 self.content.dispatchEvent(new Event('scroll')); 33375 }); 33376 }); 33377 } 33378 if (coordinate) { 33379 // Auto positionning 33380 if (this.autoPositioning) {
33381 var p = map.getPixelFromCoordinate(coordinate); 33382 var s = map.getSize(); 33383 var pos=[]; 33384 if (this.autoPositioning[0]=='auto') { 33385 pos[0] = (p[1]<s[1]/3) ? "top" : "bottom"; 33386 } 33387 else pos[0] = this.autoPositioning[0]; 33388 pos[1] = (p[0]<2*s[0]/3) ? "left" : "right"; 33389 this.setPositioning_(pos[0]+"-"+pos[1]); 33390 if (this.offsetBox) { 33391 this.setOffset([this.offsetBox[pos[1]=="left"?2:0], this.offsetBox[pos[0]=="top"?3:1] ]); 33392 } 33393 } else { 33394 if (this.offsetBox){ 33395 this.setOffset(this.offsetBox); 33396 } 33397 } 33398 // Show 33399 this.setPosition(coordinate); 33400 // Set visible class (wait to compute the size/position first) 33401 this.element.parentElement.style.display = ''; 33402 if (typeof (this.onshow) == 'function') this.onshow(); 33403 this.dispatchEvent({ type: 'show' }) 33404 this._tout = setTimeout (function() { 33405 self.element.classList.add('visible'); 33406 }, 0); 33407 } 33408}; 33409/** 33410 * Hide the popup 33411 * @api stable 33412 */ 33413ol.Overlay.Popup.prototype.hide = function () { 33414 if (this.getPosition() == undefined) return; 33415 if (typeof (this.onclose) == 'function') this.onclose(); 33416 this.setPosition(undefined); 33417 if (this._tout) clearTimeout(this._tout); 33418 this.element.classList.remove("visible"); 33419 this.dispatchEvent({ type: 'hide' }); 33420}; 33421 33422/* Copyright (c) 2020 Jean-Marc VIGLINO, 33423 released under the CeCILL-B license (French BSD license) 33424 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 33425*/ 33426/** An overlay to play animations on top of the map 33427 * The overlay define a set of particules animated on top of the map. 33428 * Particules are objects with coordinates. 33429 * They are dawn in a canvas using the draw particule method. 33430 * The update particule method updates the particule position according to the timelapse 33431 * 33432 * @constructor 33433 * @extends {ol.Overlay} 33434 * @param {*} options 33435 * @param {String} options.className class of the Overlay 33436 * @param {number} option.density particule density, default .5 33437 * @param {number} option.speed particule speed, default 4 33438 * @param {number} option.angle particule angle in radian, default PI/4 33439 * @param {boolean} options.animate start animation, default true 33440 * @param {number} options.fps frame per second, default 25 33441 */ 33442ol.Overlay.AnimatedCanvas = function(options) { 33443 if (!options) options = {}; 33444 this._canvas = ol.ext.element.create('CANVAS', { 33445 className: ((options.className || '') + ' ol-animated-overlay').trim() 33446 }); 33447 this._ctx = this._canvas.getContext('2d'); 33448 ol.Overlay.call(this, { 33449 element: this._canvas, 33450 stopEvent: false 33451 }); 33452 this._listener = []; 33453 this._time = 0; 33454 this._particuleClass = options.particule || ol.particule.Base; 33455 if (options.createParticule) this._createParticule = options.createParticule; 33456 // 25fps 33457 this._fps = 1000 / (options.fps || 25); 33458 // Default particules properties 33459 var p = this._createParticule(); 33460 this._psize = p.get('size') || [50,50]; 33461 this.set('density', options.density || .5); 33462 this.set('speed', options.speed || 4); 33463 this.set('angle', typeof(options.angle) === 'number' ? options.angle : Math.PI / 4); 33464 if (options.animate !== false) this.setAnimation(true); 33465 // Prevent animation when window on background 33466 document.addEventListener("visibilitychange", function() { 33467 this._pause = true; 33468 }.bind(this)); 33469}; 33470ol.ext.inherits(ol.Overlay.AnimatedCanvas, ol.Overlay); 33471/** Set the visibility 33472 * @param {boolean} b 33473 */ 33474ol.Overlay.AnimatedCanvas.prototype.setVisible = function (b) { 33475 this.element.style.display = b ? 'block' : 'none'; 33476 if (b) this.setAnimation(this.get('animation')); 33477} 33478/** Get the visibility 33479 * @return {boolean} b 33480 */ 33481ol.Overlay.AnimatedCanvas.prototype.getVisible = function () { 33482 return this.element.style.display != 'none'; 33483} 33484/** No update for this overlay 33485 */ 33486ol.Overlay.AnimatedCanvas.prototype.updatePixelPosition = function () {}; 33487/** 33488 * Set the map instance the overlay is associated with 33489 * @param {ol.Map} map The map instance. 33490 */ 33491ol.Overlay.AnimatedCanvas.prototype.setMap = function (map) { 33492 if (this.getMap()) { 33493 this.getMap().getViewport().querySelector('.ol-overlaycontainer').removeChild(this._canvas); 33494 } 33495 this._listener.forEach(function(l) { 33496 ol.Observable.unByKey(l); 33497 }); 33498 this._listener = []; 33499 ol.Overlay.prototype.setMap.call(this, map); 33500 if (map) { 33501 var size = map.getSize(); 33502 this._canvas.width = size[0]; 33503 this._canvas.height = size[1]; 33504 this.draw(); 33505 this._listener.push (map.on('change:size', function() { 33506 var size = map.getSize(); 33507 if (this._canvas.width !== size[0] || this._canvas.height !== size[1]) { 33508 this._canvas.width = size[0]; 33509 this._canvas.height = size[1]; 33510 this.draw(); 33511 } 33512 }.bind(this))); 33513 } 33514}; 33515/** Create particules or return exiting ones 33516 */ 33517ol.Overlay.AnimatedCanvas.prototype.getParticules = function() { 33518 var w = this._psize[0];
33519 var h = this._psize[1]; 33520 var d = (this.get('density') * this._canvas.width * this._canvas.height / w / h) << 0; 33521 if (!this._particules) this._particules = []; 33522 if (d > this._particules.length) { 33523 for (var i=this._particules.length; i<d; i++) { 33524 this._particules.push(this._createParticule(this, this.randomCoord())); 33525 } 33526 } else { 33527 this._particules.length = d; 33528 } 33529 return this._particules; 33530}; 33531/** Create a particule 33532 * @private 33533 */ 33534ol.Overlay.AnimatedCanvas.prototype._createParticule = function(overlay, coordinate) { 33535 return new this._particuleClass({ 33536 overlay: overlay, 33537 coordinate: coordinate 33538 }); 33539}; 33540/** Get random coordinates on canvas 33541 */ 33542ol.Overlay.AnimatedCanvas.prototype.randomCoord = function() { 33543 return [ 33544 Math.random()*(this._canvas.width + this._psize[0]) - this._psize[0]/2 , 33545 Math.random()*(this._canvas.height + this._psize[1]) - this._psize[1]/2 33546 ]; 33547}; 33548/** Draw canvas overlay (draw each particules) 33549 * @param {number} dt timelapes since last call 33550 */ 33551ol.Overlay.AnimatedCanvas.prototype.draw = function(dt) { 33552 var ctx = this._ctx; 33553 this.clear(); 33554 ctx.beginPath();
33555 this.getParticules().forEach(function(p) { 33556 if (dt) { 33557 p.update(dt); 33558 this.testExit(p); 33559 } 33560 p.draw(this._ctx); 33561 }.bind(this)); 33562}; 33563/** Test if particule exit the canvas and add it on other side 33564 * @param {*} p the point to test 33565 * @param {ol.size} size size of the overlap 33566 */ 33567ol.Overlay.AnimatedCanvas.prototype.testExit = function(p) { 33568 var size = this._psize; 33569 if (p.coordinate[0] < -size[0]) { 33570 p.coordinate[0] = this._canvas.width + size[0]; 33571 p.coordinate[1] = Math.random() * (this._canvas.height+size[1]) - size[1]/2; 33572 } else if (p.coordinate[0] > this._canvas.width +size[0]) { 33573 p.coordinate[0] = -size[0]; 33574 p.coordinate[1] = Math.random() * (this._canvas.height+size[1]) - size[1]/2; 33575 } else if (p.coordinate[1] < -size[1]) { 33576 p.coordinate[0] = Math.random() * (this._canvas.width+size[0]) - size[0]/2; 33577 p.coordinate[1] = this._canvas.height + size[1]; 33578 } else if (p.coordinate[1] > this._canvas.height +size[1]) { 33579 p.coordinate[0] = Math.random() * (this._canvas.width+size[0]) - size[0]/2; 33580 p.coordinate[1] = -size[1]; 33581 } 33582}; 33583/** Clear canvas 33584 */ 33585ol.Overlay.AnimatedCanvas.prototype.clear = function() { 33586 this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); 33587}; 33588/** Get overlay canvas 33589 * @return {CanvasElement} 33590 */ 33591ol.Overlay.AnimatedCanvas.prototype.getCanvas = function() { 33592 return this._canvas; 33593}; 33594/** Set canvas animation 33595 * @param {boolean} anim, default true 33596 * @api 33597 */ 33598ol.Overlay.AnimatedCanvas.prototype.setAnimation = function(anim) { 33599 anim = (anim!==false); 33600 this.set('animation', anim); 33601 if (anim) { 33602 this._pause = true; 33603 requestAnimationFrame(this._animate.bind(this)); 33604 } else { 33605 this.dispatchEvent({ type:'animation:stop', time: this._time }); 33606 } 33607}; 33608/** 33609 * @private 33610 */ 33611ol.Overlay.AnimatedCanvas.prototype._animate = function(time) { 33612 if (this.getVisible() && this.get('animation')) { 33613 if (this._pause) { 33614 // reset time 33615 requestAnimationFrame(function(time){ 33616 this._time = time; 33617 requestAnimationFrame(this._animate.bind(this)); 33618 }.bind(this)); 33619 } else { 33620 // Test fps 33621 if (time - this._time > this._fps) { 33622 this.draw(time - this._time); 33623 this._time = time; 33624 } 33625 requestAnimationFrame(this._animate.bind(this)); 33626 } 33627 } 33628 this._pause = false; 33629}; 33630 33631/* Copyright (c) 2016 Jean-Marc VIGLINO, 33632 released under the CeCILL-B license (French BSD license) 33633 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 33634*/ 33635/** 33636 * @classdesc 33637 * An overlay fixed on the map. 33638 * Use setPosition(coord, true) to force the overlay position otherwise the position will be ignored. 33639 * 33640 * @example 33641var popup = new ol.Overlay.Fixed(); 33642map.addOverlay(popup); 33643popup.setposition(position, true); 33644* 33645* @constructor 33646* @extends {ol.Overlay} 33647* @param {} options Extend Overlay options 33648* @api stable 33649*/ 33650ol.Overlay.Fixed = function (options) { 33651 ol.Overlay.call(this, options); 33652}; 33653ol.ext.inherits(ol.Overlay.Fixed, ol.Overlay); 33654/** Prevent modifying position and use a force argument to force positionning. 33655 * @param {ol.coordinate} position 33656 * @param {boolean} force true to change the position, default false 33657 */ 33658ol.Overlay.Fixed.prototype.setPosition = function(position, force) { 33659 if (this.getMap() && position) { 33660 this._pixel = this.getMap().getPixelFromCoordinate(position); 33661 } 33662 ol.Overlay.prototype.setPosition.call(this, position) 33663 if (force) { 33664 ol.Overlay.prototype.updatePixelPosition.call(this); 33665 } 33666}; 33667/** Update position according the pixel position 33668 */ 33669ol.Overlay.Fixed.prototype.updatePixelPosition = function() { 33670 if (this.getMap() && this._pixel && this.getPosition()) { 33671 var pixel = this.getMap().getPixelFromCoordinate(this.getPosition()) 33672 if (Math.round(pixel[0]*1000) !== Math.round(this._pixel[0]*1000) 33673 || Math.round(pixel[0]*1000) !== Math.round(this._pixel[0]*1000) ) { 33674 this.setPosition(this.getMap().getCoordinateFromPixel(this._pixel)); 33675 } 33676 } 33677}; 33678 33679/* Copyright (c) 2016 Jean-Marc VIGLINO, 33680 released under the CeCILL-B license (French BSD license)
33681 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 33682*/ 33683/** 33684 * A popup element to be displayed over the map and attached to a single map 33685 * location. The popup are customized using CSS. 33686 * 33687 * @constructor 33688 * @extends {ol.Overlay.Popup} 33689 * @fires show 33690 * @fires hide 33691 * @param {} options Extend Overlay options 33692 * @param {String} options.popupClass the a class of the overlay to style the popup. 33693 * @param {ol.style.Style} options.style a style to style the link on the map. 33694 * @param {number} options.minScale min scale for the popup, default .5 33695 * @param {number} options.maxScale max scale for the popup, default 2 33696 * @param {bool} options.closeBox popup has a close box, default false. 33697 * @param {function|undefined} options.onclose: callback function when popup is closed 33698 * @param {function|undefined} options.onshow callback function when popup is shown 33699 * @param {Number|Array<number>} options.offsetBox an offset box 33700 * @param {ol.OverlayPositioning | string | undefined} options.positioning 33701 * the 'auto' positioning var the popup choose its positioning to stay on the map. 33702 * @api stable 33703 */ 33704ol.Overlay.FixedPopup = function (options) { 33705 options.anchor = false; 33706 options.positioning = options.positioning || 'center-center'; 33707 options.className = (options.className || '') + ' ol-fixPopup'; 33708 ol.Overlay.Popup.call(this, options); 33709 this.set('minScale', options.minScale || .5); 33710 this.set('maxScale', options.maxScale || 2); 33711 var canvas = document.createElement('canvas'); 33712 this._overlay = new ol.layer.Image({ 33713 source: new ol.source.ImageCanvas({ 33714 canvasFunction: function(extent, res, ratio, size) { 33715 canvas.width = size[0]; 33716 canvas.height = size[1]; 33717 return canvas 33718 } 33719 }) 33720 }); 33721 this._style = options.style || new ol.style.Style({ 33722 fill: new ol.style.Fill({ color: [102,153,255] }) 33723 }); 33724 this._overlay.on(['postcompose','postrender'], function(e) { 33725 if (this.getVisible() && this._pixel) { 33726 var map = this.getMap(); 33727 var position = this.getPosition(); 33728 var pixel = map.getPixelFromCoordinate(position); 33729 var r1 = this.element.getBoundingClientRect() 33730 var r2 = this.getMap().getTargetElement().getBoundingClientRect(); 33731 var pixel2 = [r1.left-r2.left+r1.width/2, r1.top-r2.top+r1.height/2] 33732 e.context.save(); 33733 var tr = e.inversePixelTransform; 33734 if (tr) { 33735 e.context.transform(tr[0],tr[1],tr[2],tr[3],tr[4],tr[5]); 33736 } else { 33737 // ol ~ v5.3.0 33738 e.context.scale(e.frameState.pixelRatio,e.frameState.pixelRatio) 33739 } 33740 e.context.beginPath(); 33741 e.context.moveTo(pixel[0], pixel[1]); 33742 if (Math.abs(pixel2[0]-pixel[0]) > Math.abs(pixel2[1]-pixel[1])) { 33743 e.context.lineTo(pixel2[0],pixel2[1]-8); 33744 e.context.lineTo(pixel2[0],pixel2[1]+8); 33745 } else { 33746 e.context.lineTo(pixel2[0]-8,pixel2[1]); 33747 e.context.lineTo(pixel2[0]+8,pixel2[1]); 33748 } 33749 e.context.moveTo(pixel[0], pixel[1]); 33750 if (this._style.getFill()) { 33751 e.context.fillStyle = ol.color.asString(this._style.getFill().getColor()); 33752 e.context.fill(); 33753 } 33754 if (this._style.getStroke()) { 33755 e.context.strokeStyle = ol.color.asString(this._style.getStroke().getColor()); 33756 e.context.lineWidth = this._style.getStroke().width(); 33757 e.context.stroke(); 33758 } 33759 e.context.restore(); 33760 } 33761 }.bind(this)); 33762 var update = function() { 33763 this.setPixelPosition(); 33764 }.bind(this); 33765 this.on(['hide', 'show'], function() { 33766 setTimeout(update) 33767 }.bind(this)) 33768 // Get events centroid 33769 function centroid(pevents) { 33770 var clientX = 0; 33771 var clientY = 0; 33772 var length = 0; 33773 for (var i in pevents) { 33774 clientX += pevents[i].clientX; 33775 clientY += pevents[i].clientY; 33776 length++; 33777 } 33778 return [clientX / length, clientY / length]; 33779 } 33780 // Get events angle 33781 function angle() { 33782 var p1,p2, v = Object.keys(pointerEvents); 33783 if (v.length<2) return false; 33784 p1 = pointerEvents[v[0]]; 33785 p2 = pointerEvents[v[1]]; 33786 var v1 = [p2.clientX - p1.clientX, p2.clientY - p1.clientY]; 33787 p1 = pointerEvents2[v[0]]; 33788 p2 = pointerEvents2[v[1]]; 33789 var v2 = [p2.clientX - p1.clientX, p2.clientY - p1.clientY]; 33790 var d1 = Math.sqrt(v1[0]*v1[0]+v1[1]*v1[1]); 33791 var d2 = Math.sqrt(v2[0]*v2[0]+v2[1]*v2[1]);
33792 var a = Math.acos((v1[0]*v2[0]+v1[1]*v2[1]) / (d1*d2)) * 360 / Math.PI; 33793 if (v1[0]*v2[1]-v1[1]*v2[0] < 0) return -a; 33794 else return a; 33795 } 33796 // Get distance beetween events 33797 function distance(pevents) { 33798 var v = Object.keys(pevents); 33799 if (v.length<2) return false; 33800 return ol.coordinate.dist2d([pevents[v[0]].clientX, pevents[v[0]].clientY], [pevents[v[1]].clientX, pevents[v[1]].clientY]); 33801 } 33802 // Handle popup move 33803 var pointerEvents = {}; 33804 var pointerEvents2 = {}; 33805 var pixelPosition = []; 33806 var distIni, rotIni, scaleIni, move; 33807 // down 33808 this.element.addEventListener('pointerdown', function(e) { 33809 e.preventDefault(); 33810 e.stopPropagation(); 33811 // Reset events to this position 33812 for (var i in pointerEvents) { 33813 if (pointerEvents2[i]) { 33814 pointerEvents[i] = pointerEvents2[i]; 33815 } 33816 } 33817 pointerEvents[e.pointerId] = e; 33818 pixelPosition = this._pixel; 33819 rotIni = this.get('rotation') || 0; 33820 scaleIni = this.get('scale') || 1; 33821 distIni = distance(pointerEvents); 33822 move = false; 33823 }.bind(this)); 33824 // Prevent click when move 33825 this.element.addEventListener('click', function(e) { 33826 if (move) { 33827 e.preventDefault(); 33828 e.stopPropagation(); 33829 } 33830 }, true); 33831 // up / cancel 33832 var removePointer = function(e) { 33833 if (pointerEvents[e.pointerId]) { 33834 delete pointerEvents[e.pointerId]; 33835 e.preventDefault(); 33836 } 33837 if (pointerEvents2[e.pointerId]) { 33838 delete pointerEvents2[e.pointerId]; 33839 } 33840 /* Simulate a second touch pointer * / 33841 if (e.metaKey || e.ctrlKey) { 33842 pointerEvents['touch'] = e; 33843 pointerEvents2['touch'] = e; 33844 } else { 33845 delete pointerEvents['touch']; 33846 delete pointerEvents2['touch']; 33847 } 33848 /**/ 33849 }.bind(this); 33850 document.addEventListener('pointerup', removePointer); 33851 document.addEventListener('pointercancel', removePointer); 33852 // move 33853 document.addEventListener('pointermove', function(e) { 33854 if (pointerEvents[e.pointerId]) { 33855 e.preventDefault(); 33856 pointerEvents2[e.pointerId] = e; 33857 var c1 = centroid(pointerEvents); 33858 var c2 = centroid(pointerEvents2); 33859 var dx = c2[0] - c1[0]; 33860 var dy = c2[1] - c1[1]; 33861 move = move || Math.abs(dx) > 3 || Math.abs(dy) > 3; 33862 var a = angle(); 33863 if (a) { 33864 this.setRotation(rotIni + a*1.5, false); 33865 } 33866 var d = distance(pointerEvents2); 33867 if (d!==false && distIni) { 33868 this.setScale(scaleIni * d / distIni, false); 33869 distIni = scaleIni * d / this.get('scale'); 33870 } 33871 this.setPixelPosition([pixelPosition[0]+dx, pixelPosition[1]+dy]); 33872 } 33873 }.bind(this)); 33874}; 33875ol.ext.inherits(ol.Overlay.FixedPopup, ol.Overlay.Popup); 33876/** 33877 * Set the map instance the control is associated with 33878 * and add its controls associated to this map. 33879 * @param {_ol_Map_} map The map instance. 33880 */ 33881ol.Overlay.FixedPopup.prototype.setMap = function (map) { 33882 ol.Overlay.Popup.prototype.setMap.call(this, map); 33883 this._overlay.setMap(this.getMap()); 33884 if (this._listener) { 33885 ol.Observable.unByKey(this._listener); 33886 } 33887 if (map) { 33888 // Force popup inside the viewport 33889 this._listener = map.on('change:size', function() { 33890 this.setPixelPosition(); 33891 }.bind(this)) 33892 } 33893}; 33894/** Update pixel position 33895 * @return {boolean} 33896 * @private 33897 */ 33898ol.Overlay.FixedPopup.prototype.updatePixelPosition = function () { 33899 var map = this.getMap(); 33900 var position = this.getPosition(); 33901 if (!map || !map.isRendered() || !position) { 33902 this.setVisible(false); 33903 return; 33904 } 33905 if (!this._pixel) { 33906 this._pixel = map.getPixelFromCoordinate(position); 33907 var mapSize = map.getSize(); 33908 this.updateRenderedPosition(this._pixel, mapSize); 33909 } else { 33910 this.setVisible(true); 33911 } 33912}; 33913/** updateRenderedPosition 33914 * @private 33915 */ 33916ol.Overlay.FixedPopup.prototype.updateRenderedPosition = function (pixel, mapsize) { 33917 ol.Overlay.Popup.prototype.updateRenderedPosition.call(this, pixel, mapsize); 33918 this.setRotation(); 33919 this.setScale() 33920}; 33921/** Set pixel position 33922 * @param {ol.pixel} pix 33923 * @param {string} position top/bottom/middle-left/right/center 33924 */ 33925ol.Overlay.FixedPopup.prototype.setPixelPosition = function (pix, position) { 33926 var r, map = this.getMap(); 33927 var mapSize = map ? map.getSize() : [0,0]; 33928 if (position) { 33929 this.setPositioning(position); 33930 r = ol.ext.element.offsetRect(this.element); 33931 r.width = r.height = 0; 33932 if (/top/.test(position)) pix[1] += r.height/2; 33933 else if (/bottom/.test(position)) pix[1] = mapSize[1] - r.height/2 - pix[1]; 33934 else pix[1] = mapSize[1]/2 + pix[1]; 33935 if (/left/.test(position)) pix[0] += r.width/2; 33936 else if (/right/.test(position)) pix[0] = mapSize[0] - r.width/2 - pix[0]; 33937 else pix[0] = mapSize[0]/2 + pix[0]; 33938 } 33939 if (pix) this._pixel = pix; 33940 if (map && this._pixel) { 33941 this.updateRenderedPosition(this._pixel, mapSize); 33942 // Prevent outside 33943 var outside = false;
33944 r = ol.ext.element.offsetRect(this.element); 33945 var rmap = ol.ext.element.offsetRect(map.getTargetElement()); 33946 if (r.left < rmap.left) { 33947 this._pixel[0] = this._pixel[0] + rmap.left - r.left; 33948 outside = true; 33949 } else if (r.left + r.width > rmap.left + rmap.width) { 33950 this._pixel[0] = this._pixel[0] + rmap.left - r.left + rmap.width - r.width; 33951 outside = true; 33952 } 33953 if (r.top < rmap.top) { 33954 this._pixel[1] = this._pixel[1] + rmap.top - r.top; 33955 outside = true; 33956 } else if (r.top + r.height > rmap.top + rmap.height) { 33957 this._pixel[1] = this._pixel[1] + rmap.top - r.top + rmap.height - r.height; 33958 outside = true; 33959 } 33960 if (outside) this.updateRenderedPosition(this._pixel, mapSize); 33961 this._overlay.changed(); 33962 } 33963}; 33964/** Set pixel position 33965 * @returns {ol.pixel} 33966 */ 33967ol.Overlay.FixedPopup.prototype.getPixelPosition = function () { 33968 return this._pixel; 33969}; 33970/** 33971 * Set the CSS class of the popup. 33972 * @param {string} c class name. 33973 * @api stable 33974 */ 33975ol.Overlay.FixedPopup.prototype.setPopupClass = function (c) { 33976 ol.Overlay.Popup.prototype.setPopupClass.call(this, c); 33977 this.addPopupClass('ol-fixPopup'); 33978}; 33979/** Set poppup rotation 33980 * @param {number} angle 33981 * @param {booelan} update update popup, default true 33982 * @api 33983 */ 33984ol.Overlay.FixedPopup.prototype.setRotation = function (angle, update) { 33985 if (typeof(angle) === 'number') this.set('rotation', angle); 33986 if (update!==false) { 33987 if (/rotate/.test(this.element.style.transform)) { 33988 this.element.style.transform = this.element.style.transform.replace(/rotate\((-?[\d,.]+)deg\)/,'rotate('+(this.get('rotation')||0)+'deg)') 33989 } else { 33990 this.element.style.transform = this.element.style.transform + ' rotate('+(this.get('rotation')||0)+'deg)'; 33991 } 33992 } 33993}; 33994/** Set poppup scale 33995 * @param {number} scale 33996 * @param {booelan} update update popup, default true 33997 * @api 33998 */ 33999ol.Overlay.FixedPopup.prototype.setScale = function (scale, update) { 34000 if (typeof(scale) === 'number') this.set('scale', scale); 34001 scale = Math.min(Math.max(this.get('minScale')||0, this.get('scale')||1 ), this.get('maxScale')||2); 34002 this.set('scale', scale); 34003 if (update!==false) { 34004 if (/scale/.test(this.element.style.transform)) { 34005 this.element.style.transform = this.element.style.transform.replace(/scale\(([\d,.]+)\)/,'scale('+(scale)+')') 34006 } else { 34007 this.element.style.transform = this.element.style.transform + ' scale('+(scale)+')'; 34008 } 34009 } 34010}; 34011/** Set link style 34012 * @param {ol.style.Style} style 34013 */ 34014ol.Overlay.FixedPopup.prototype.setLinkStyle = function (style) { 34015 this._style = style; 34016 this._overlay.changed(); 34017}; 34018/** Get link style 34019 * @return {ol.style.Style} style 34020 */ 34021ol.Overlay.FixedPopup.prototype.getLinkStyle = function () { 34022 return this._style; 34023}; 34024 34025/* Copyright (c) 2016 Jean-Marc VIGLINO, 34026 released under the CeCILL-B license (French BSD license) 34027 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 34028*/ 34029/** 34030 * @classdesc 34031 * The Magnify overlay add a "magnifying glass" effect to an OL3 map that displays 34032* a portion of the map in a different zoom (and actually display different content). 34033* 34034* @constructor 34035* @extends {ol.Overlay} 34036* @param {olx.OverlayOptions} options Overlay options 34037* @api stable 34038*/ 34039ol.Overlay.Magnify = function (options) { 34040 var elt = document.createElement("div"); 34041 elt.className = "ol-magnify"; 34042 this._elt = elt; 34043 ol.Overlay.call(this, { 34044 positioning: options.positioning || "center-center", 34045 element: this._elt, 34046 stopEvent: false 34047 }); 34048 // Create magnify map 34049 this.mgmap_ = new ol.Map({ 34050 controls: new ol.Collection(), 34051 interactions: new ol.Collection(), 34052 target: options.target || this._elt, 34053 view: new ol.View({ projection: options.projection }), 34054 layers: options.layers 34055 }); 34056 this.mgview_ = this.mgmap_.getView(); 34057 this.external_ = options.target?true:false; 34058 this.set("zoomOffset", options.zoomOffset||1); 34059 this.set("active", true); 34060 this.on("propertychange", this.setView_.bind(this)); 34061}; 34062ol.ext.inherits(ol.Overlay.Magnify, ol.Overlay); 34063/** 34064 * Set the map instance the overlay is associated with. 34065 * @param {ol.Map} map The map instance. 34066 */ 34067ol.Overlay.Magnify.prototype.setMap = function(map) { 34068 if (this.getMap()) { 34069 this.getMap().getViewport().removeEventListener("mousemove", this.onMouseMove_); 34070 } 34071 if (this._listener) ol.Observable.unByKey(this._listener); 34072 this._listener = null; 34073 ol.Overlay.prototype.setMap.call(this, map); 34074 map.getViewport().addEventListener("mousemove", this.onMouseMove_.bind(this)); 34075 this._listener = map.getView().on('propertychange', this.setView_.bind(this)); 34076 this.setView_(); 34077}; 34078/** Get the magnifier map 34079* @return {_ol_Map_} 34080*/ 34081ol.Overlay.Magnify.prototype.getMagMap = function() { 34082 return this.mgmap_; 34083}; 34084/** Magnify is active 34085* @return {boolean} 34086*/ 34087ol.Overlay.Magnify.prototype.getActive = function() { 34088 return this.get("active"); 34089}; 34090/** Activate or deactivate 34091* @param {boolean} active 34092*/ 34093ol.Overlay.Magnify.prototype.setActive = function(active) { 34094 return this.set("active", active); 34095}; 34096/** Mouse move 34097 * @private 34098 */ 34099ol.Overlay.Magnify.prototype.onMouseMove_ = function(e) { 34100 var self = this; 34101 if (!self.get("active")) { 34102 self.setPosition(); 34103 } else {
34104 var px = self.getMap().getEventCoordinate(e); 34105 if (!self.external_) self.setPosition(px); 34106 self.mgview_.setCenter(px); 34107 if (!self._elt.querySelector('canvas') || self._elt.querySelector('canvas').style.display =="none") self.mgmap_.updateSize(); 34108 } 34109}; 34110/** View has changed 34111 * @private 34112 */ 34113ol.Overlay.Magnify.prototype.setView_ = function(e) { 34114 if (!this.get("active")) { 34115 this.setPosition(); 34116 return; 34117 } 34118 if (!e) { 34119 // refresh all 34120 this.setView_({key:'rotation'}); 34121 this.setView_({key:'resolution'}); 34122 return; 34123 } 34124 // Set the view params 34125 switch (e.key) { 34126 case 'rotation': 34127 this.mgview_.setRotation(this.getMap().getView().getRotation()); 34128 break; 34129 case 'zoomOffset': 34130 case 'resolution': { 34131 var z = Math.max(0,this.getMap().getView().getZoom()+Number(this.get("zoomOffset"))); 34132 this.mgview_.setZoom(z); 34133 break; 34134 } 34135 default: break; 34136 } 34137}; 34138 34139/* Copyright (c) 2018 Jean-Marc VIGLINO, 34140 released under the CeCILL-B license (French BSD license) 34141 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 34142*/ 34143/** 34144 * @classdesc 34145 * A placemark element to be displayed over the map and attached to a single map 34146 * location. The placemarks are customized using CSS. 34147 * 34148 * @example 34149var popup = new ol.Overlay.Placemark(); 34150map.addOverlay(popup); 34151popup.show(coordinate); 34152popup.hide(); 34153* 34154* @constructor 34155* @extends {ol.Overlay} 34156* @param {} options Extend ol/Overlay/Popup options 34157* @param {String} options.color placemark color 34158* @param {String} options.backgroundColor placemark color 34159* @param {String} options.contentColor placemark color 34160* @param {Number} options.radius placemark radius in pixel 34161* @param {String} options.popupClass the a class of the overlay to style the popup. 34162* @param {function|undefined} options.onclose: callback function when popup is closed 34163* @param {function|undefined} options.onshow callback function when popup is shown 34164* @api stable 34165*/ 34166ol.Overlay.Placemark = function (options) { 34167 options = options || {}; 34168 options.popupClass = (options.popupClass || '') + ' placemark anim' 34169 options.positioning = 'bottom-center', 34170 ol.Overlay.Popup.call(this, options); 34171 this.setPositioning = function(){}; 34172 if (options.color) this.element.style.color = options.color; 34173 if (options.backgroundColor ) this.element.style.backgroundColor = options.backgroundColor ; 34174 if (options.contentColor ) this.setContentColor(options.contentColor); 34175 if (options.size) this.setRadius(options.size); 34176}; 34177ol.ext.inherits(ol.Overlay.Placemark, ol.Overlay.Popup); 34178/** 34179 * Set the position and the content of the placemark (hide it before to enable animation). 34180 * @param {ol.Coordinate|string} coordinate the coordinate of the popup or the HTML content. 34181 * @param {string|undefined} html the HTML content (undefined = previous content). 34182 */ 34183ol.Overlay.Placemark.prototype.show = function(coordinate, html) { 34184 if (coordinate===true) { 34185 coordinate = this.getPosition(); 34186 } 34187 this.hide(); 34188 ol.Overlay.Popup.prototype.show.apply(this, [coordinate, html]); 34189}; 34190/** 34191 * Set the placemark color. 34192 * @param {string} color 34193 */ 34194ol.Overlay.Placemark.prototype.setColor = function(color) { 34195 this.element.style.color = color; 34196}; 34197/** 34198 * Set the placemark background color. 34199 * @param {string} color 34200 */ 34201ol.Overlay.Placemark.prototype.setBackgroundColor = function(color) { 34202 this._elt.style.backgroundColor = color; 34203}; 34204/** 34205 * Set the placemark content color. 34206 * @param {string} color 34207 */ 34208ol.Overlay.Placemark.prototype.setContentColor = function(color) { 34209 var c = this.element.getElementsByClassName('ol-popup-content')[0]; 34210 if (c) c.style.color = color; 34211}; 34212/** 34213 * Set the placemark class. 34214 * @param {string} name 34215 */ 34216ol.Overlay.Placemark.prototype.setClassName = function(name) { 34217 var oldclass = this.element.className; 34218 this.element.className = 'ol-popup placemark ol-popup-bottom ol-popup-center ' 34219 + (/visible/.test(oldclass) ? 'visible ' : '') 34220 + (/anim/.test(oldclass) ? 'anim ' : '') 34221 + name; 34222}; 34223/** 34224 * Set the placemark radius. 34225 * @param {number} size size in pixel 34226 */ 34227ol.Overlay.Placemark.prototype.setRadius = function(size) { 34228 this.element.style.fontSize = size + 'px'; 34229}; 34230 34231/* Copyright (c) 2018 Jean-Marc VIGLINO, 34232 released under the CeCILL-B license (French BSD license)
34233 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 34234*/ 34235/** Template attributes for popup 34236 * @typedef {Object} TemplateAttributes 34237 * @property {string} title 34238 * @property {function} format a function that takes an attribute and a feature and returns the formated attribute 34239 * @property {string} before string to instert before the attribute (prefix) 34240 * @property {string} after string to instert after the attribute (sudfix) 34241 * @property {boolean|function} visible boolean or a function (feature, value) that decides the visibility of a attribute entry 34242 */ 34243/** Template 34244 * @typedef {Object} Template 34245 * @property {string|function} title title of the popup, attribute name or a function that takes a feature and returns the title 34246 * @property {Object.<TemplateAttributes>} attributes a list of template attributes 34247 */ 34248/** 34249 * A popup element to be displayed on a feature. 34250 * 34251 * @constructor 34252 * @extends {ol.Overlay.Popup} 34253 * @fires show 34254 * @fires hide 34255 * @fires select 34256 * @param {} options Extend Popup options 34257 * @param {String} options.popupClass the a class of the overlay to style the popup. 34258 * @param {bool} options.closeBox popup has a close box, default false. 34259 * @param {function|undefined} options.onclose: callback function when popup is closed 34260 * @param {function|undefined} options.onshow callback function when popup is shown 34261 * @param {Number|Array<number>} options.offsetBox an offset box 34262 * @param {ol.OverlayPositioning | string | undefined} options.positionning 34263 * the 'auto' positioning var the popup choose its positioning to stay on the map. 34264 * @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, default use all feature properties 34265 * @param {ol.interaction.Select} options.select a select interaction to get features from 34266 * @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 34267 * @param {boolean} options.canFix Enable popup to be fixed, default false 34268 * @param {boolean} options.showImage display image url as image, default false 34269 * @param {boolean} options.maxChar max char to display in a cell, default 200 34270 * @api stable 34271 */ 34272ol.Overlay.PopupFeature = function (options) { 34273 options = options || {}; 34274 ol.Overlay.Popup.call(this, options); 34275 this.setTemplate(options.template); 34276 this.set('canFix', options.canFix) 34277 this.set('showImage', options.showImage) 34278 this.set('maxChar', options.maxChar||200) 34279 this.set('keepSelection', options.keepSelection) 34280 // Bind with a select interaction 34281 if (options.select && (typeof options.select.on ==='function')) { 34282 this._select = options.select; 34283 options.select.on('select', function(e){ 34284 if (!this._noselect) { 34285 if (e.selected[0]) { 34286 this.show(e.mapBrowserEvent.coordinate, options.select.getFeatures().getArray(), e.selected[0]); 34287 } else { 34288 this.hide(); 34289 } 34290 } 34291 }.bind(this)); 34292 } 34293}; 34294ol.ext.inherits(ol.Overlay.PopupFeature, ol.Overlay.Popup); 34295/** Set the template 34296 * @param {Template} [template] A template with a list of properties to use in the popup, default use all features properties 34297 */ 34298ol.Overlay.PopupFeature.prototype.setTemplate = function(template) { 34299 if (!template) { 34300 template = function(f) { 34301 var prop = f.getProperties(); 34302 delete prop[f.getGeometryName()]; 34303 return { 34304 attributes: Object.keys(prop) 34305 } 34306 } 34307 } 34308 this._template = template; 34309 this._attributeObject(this._template); 34310} 34311/** 34312 * @private 34313 */ 34314 ol.Overlay.PopupFeature.prototype._attributeObject = function (temp) { 34315 if (temp && temp.attributes instanceof Array) { 34316 var att = {}; 34317 temp.attributes.forEach(function (a) { 34318 att[a] = true; 34319 }); 34320 temp.attributes = att; 34321 } 34322 return temp.attributes; 34323}; 34324/** Show the popup on the map 34325 * @param {ol.coordinate|undefined} coordinate Position of the popup 34326 * @param {ol.Feature|Array<ol.Feature>} features The features on the popup 34327 * @param {ol.Feature} current The current feature if keepSelection = true, otherwise get the first feature 34328 */ 34329ol.Overlay.PopupFeature.prototype.show = function(coordinate, features, current) { 34330 if (coordinate instanceof ol.Feature 34331 || (coordinate instanceof Array && coordinate[0] instanceof ol.Feature)) { 34332 features = coordinate; 34333 coordinate = null; 34334 } 34335 if (!(features instanceof Array)) features = [features]; 34336 this._features = features.slice(); 34337 if (!this._count) this._count = 1; 34338 // Calculate html upon feaures attributes 34339 this._count = 1; 34340 var f = this.get('keepSelection') ? current || features[0] : features[0]; 34341 var html = this._getHtml(f); 34342 if (html) { 34343 if (!this.element.classList.contains('ol-fixed')) this.hide(); 34344 if (!coordinate || features[0].getGeometry().getType()==='Point') { 34345 coordinate = features[0].getGeometry().getFirstCoordinate(); 34346 } 34347 ol.Overlay.Popup.prototype.show.call(this, coordinate, html); 34348 } else { 34349 this.hide(); 34350 } 34351}; 34352/** 34353 * @private 34354 */ 34355ol.Overlay.PopupFeature.prototype._getHtml = function(feature) { 34356 if (!feature) return ''; 34357 var html = ol.ext.element.create('DIV', { className: 'ol-popupfeature' }); 34358 if (this.get('canFix')) { 34359 ol.ext.element.create('I', { className:'ol-fix', parent: html }) 34360 .addEventListener('click', function(){ 34361 this.element.classList.toggle('ol-fixed'); 34362 }.bind(this)); 34363 } 34364 var template = this._template; 34365 // calculate template 34366 if (typeof(template) === 'function') { 34367 template = template(feature, this._count, this._features.length); 34368 } else if (!template || !template.attributes) { 34369 template = template || {}; 34370 template. attributes = {}; 34371 for (var i in feature.getProperties()) if (i!='geometry') { 34372 template.attributes[i] = i; 34373 } 34374 } 34375 // Display title 34376 if (template.title) { 34377 var title; 34378 if (typeof template.title === 'function') { 34379 title = template.title(feature); 34380 } else { 34381 title = feature.get(template.title); 34382 } 34383 ol.ext.element.create('H1', { html:title, parent: html }); 34384 } 34385 // Display properties in a table 34386 if (template.attributes) { 34387 var tr, table = ol.ext.element.create('TABLE', { parent: html }); 34388 var atts = this._attributeObject(template); 34389 var featureAtts = feature.getProperties(); 34390 for (var att in atts) { 34391 if (featureAtts.hasOwnProperty(att)) { 34392 var a = atts[att]; 34393 var content, val = featureAtts[att]; 34394 // Get calculated value 34395 if (typeof(a.format)==='function') { 34396 val = a.format(val, feature); 34397 } 34398 // Is entry visible? 34399 var visible = true; 34400 if (typeof(a.visible)==='boolean') { 34401 visible = a.visible; 34402 } else if (typeof(a.visible)==='function') { 34403 visible = a.visible(feature, val); 34404 } 34405 if (visible) { 34406 tr = ol.ext.element.create('TR', { parent: table }); 34407 ol.ext.element.create('TD', { html: a.title || att, parent: tr }); 34408 // Show image or content 34409 if (this.get('showImage') && /(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png)/.test(val)) { 34410 content = ol.ext.element.create('IMG',{ 34411 src: val 34412 }); 34413 } else { 34414 content = (a.before||'') + val + (a.after||''); 34415 var maxc = this.get('maxChar') || 200; 34416 if (typeof(content) === 'string' && content.length>maxc) content = content.substr(0,maxc)+'[...]'; 34417 } 34418 // Add value 34419 ol.ext.element.create('TD', { 34420 html: content, 34421 parent: tr 34422 }); 34423 } 34424 } 34425 } 34426 } 34427 // Zoom button 34428 ol.ext.element.create('BUTTON', { className: 'ol-zoombt', parent: html }) 34429 .addEventListener('click', function() { 34430 if (feature.getGeometry().getType()==='Point') { 34431 this.getMap().getView().animate({ 34432 center: feature.getGeometry().getFirstCoordinate(), 34433 zoom: Math.max(this.getMap().getView().getZoom(), 18) 34434 }); 34435 } else { 34436 var ext = feature.getGeometry().getExtent(); 34437 this.getMap().getView().fit(ext, { duration:1000 }); 34438 } 34439 }.bind(this)); 34440 // Counter 34441 if (!this.get('keepSelection') && this._features.length > 1) { 34442 var div = ol.ext.element.create('DIV', { className: 'ol-count', parent: html }); 34443 ol.ext.element.create('DIV', { 34444 className: 'ol-prev', 34445 parent: div, 34446 click: function() { 34447 this._count--; 34448 if (this._count<1) this._count = this._features.length; 34449 html = this._getHtml(this._features[this._count-1]); 34450 setTimeout(function() { 34451 ol.Overlay.Popup.prototype.show.call(this, this.getPosition(), html); 34452 }.bind(this), 350 ); 34453 }.bind(this) 34454 }); 34455 ol.ext.element.create('TEXT', { html:this._count+'/'+this._features.length, parent: div }); 34456 ol.ext.element.create('DIV', { 34457 className: 'ol-next', 34458 parent: div, 34459 click: function() { 34460 this._count++; 34461 if (this._count>this._features.length) this._count = 1; 34462 html = this._getHtml(this._features[this._count-1]); 34463 setTimeout(function() { 34464 ol.Overlay.Popup.prototype.show.call(this, this.getPosition(), html); 34465 }.bind(this), 350 ); 34466 }.bind(this) 34467 }); 34468 } 34469 // Use select interaction 34470 if (this._select && !this.get('keepSelection')) { 34471 this._noselect = true; 34472 this._select.getFeatures().clear(); 34473 this._select.getFeatures().push(feature); 34474 this._noselect = false;
34475 } 34476 this.dispatchEvent({ type: 'select', feature: feature, index: this._count }) 34477 return html; 34478}; 34479/** Fix the popup 34480 * @param {boolean} fix 34481 */ 34482ol.Overlay.PopupFeature.prototype.setFix = function (fix) { 34483 if (fix) this.element.classList.add('ol-fixed'); 34484 else this.element.classList.remove('ol-fixed'); 34485}; 34486/** Is a popup fixed 34487 * @return {boolean} 34488 */ 34489ol.Overlay.PopupFeature.prototype.getFix = function () { 34490 return this.element.classList.contains('ol-fixed'); 34491}; 34492/** Get a function to use as format to get local string for an attribute 34493 * if the attribute is a number: Number.toLocaleString() 34494 * if the attribute is a date: Date.toLocaleString() 34495 * otherwise the attibute itself 34496 * @param {string} locales string with a BCP 47 language tag, or an array of such strings 34497 * @param {*} options Number or Date toLocaleString options 34498 * @return {function} a function that takes an attribute and return the formated attribute 34499 */ 34500ol.Overlay.PopupFeature.localString = function (locales , options) { 34501 return function (a) { 34502 if (a && a.toLocaleString) { 34503 return a.toLocaleString(locales , options); 34504 } else { 34505 // Try to get a date from a string 34506 var date = new Date(a); 34507 if (isNaN(date)) return a; 34508 else return date.toLocaleString(locales , options); 34509 } 34510 }; 34511}; 34512 34513/* Copyright (c) 2018 Jean-Marc VIGLINO, 34514 released under the CeCILL-B license (French BSD license) 34515 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 34516*/ 34517/** A tooltip element to be displayed over the map and attached on the cursor position. 34518 * @constructor 34519 * @extends {ol.Overlay.Popup} 34520 * @param {} options Extend Popup options 34521 * @param {String} options.popupClass the a class of the overlay to style the popup. 34522 * @param {number} options.maximumFractionDigits maximum digits to display on measure, default 2 34523 * @param {function} options.formatLength a function that takes a number and returns the formated value, default length in meter 34524 * @param {function} options.formatArea a function that takes a number and returns the formated value, default length in square-meter 34525 * @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 34526 * @param {Number|Array<number>} options.offsetBox an offset box 34527 * @param {ol.OverlayPositioning | string | undefined} options.positionning 34528 * the 'auto' positioning var the popup choose its positioning to stay on the map. 34529 * @api stable 34530 */ 34531ol.Overlay.Tooltip = function (options) { 34532 options = options || {}; 34533 options.popupClass = options.popupClass || options.className || 'tooltips black'; 34534 options.positioning = options.positioning || 'center-left'; 34535 options.stopEvent = !!(options.stopEvent); 34536 ol.Overlay.Popup.call(this, options); 34537 this.set('maximumFractionDigits', options.maximumFractionDigits||2); 34538 if (typeof(options.formatLength)==='function') this.formatLength = options.formatLength; 34539 if (typeof(options.formatArea)==='function') this.formatArea = options.formatArea; 34540 if (typeof(options.getHTML)==='function') this.getHTML = options.getHTML; 34541 this._interaction = new ol.interaction.Interaction({ 34542 handleEvent: function(e){ 34543 if (e.type==='pointermove' || e.type==='click') { 34544 var info = this.getHTML(this._feature, this.get('info')); 34545 if (info) { 34546 this.show(e.coordinate, info); 34547 } 34548 else this.hide(); 34549 this._coord = e.coordinate; 34550 } 34551 return true; 34552 }.bind(this) 34553 }); 34554}; 34555ol.ext.inherits(ol.Overlay.Tooltip, ol.Overlay.Popup); 34556/** 34557 * Set the map instance the control is associated with 34558 * and add its controls associated to this map. 34559 * @param {_ol_Map_} map The map instance. 34560 */ 34561ol.Overlay.Tooltip.prototype.setMap = function (map) { 34562 if (this.getMap()) this.getMap().removeInteraction(this._interaction); 34563 ol.Overlay.Popup.prototype.setMap.call(this, map); 34564 if (this.getMap()) this.getMap().addInteraction(this._interaction); 34565}; 34566/** Get the information to show in the tooltip 34567 * The area/length will be added if a feature is attached.
34568 * @param {ol.Feature|undefined} feature the feature 34569 * @param {string} info the info string 34570 * @api 34571 */ 34572ol.Overlay.Tooltip.prototype.getHTML = function(feature, info) { 34573 if (this.get('measure')) return this.get('measure') + (info ? '<br/>'+ info : ''); 34574 else return info || ''; 34575}; 34576/** Set the Tooltip info 34577 * If information is not null it will be set with a delay, 34578 * thus watever the information is inserted, the significant information will be set. 34579 * ie. ttip.setInformation('ok'); ttip.setInformation(null); will set 'ok' 34580 * ttip.set('info','ok'); ttip.set('info', null); will set null 34581 * @param {string} what The information to display in the tooltip, default remove information 34582 */ 34583ol.Overlay.Tooltip.prototype.setInfo = function(what) { 34584 if (!what) { 34585 this.set('info',''); 34586 this.hide(); 34587 } 34588 else setTimeout(function() { 34589 this.set('info', what); 34590 this.show(this._coord, this.get('info')); 34591 }.bind(this)); 34592}; 34593/** Remove the current featue attached to the tip 34594 * Similar to setFeature() with no argument 34595 */ 34596ol.Overlay.Tooltip.prototype.removeFeature = function() { 34597 this.setFeature(); 34598}; 34599/** Format area to display in the popup. 34600 * Can be overwritten to display measure in a different unit (default: square-metter). 34601 * @param {number} area area in m2 34602 * @return {string} the formated area 34603 * @api 34604 */ 34605ol.Overlay.Tooltip.prototype.formatArea = function(area) { 34606 if (area > Math.pow(10,-1*this.get('maximumFractionDigits'))) { 34607 if (area>10000) { 34608 return (area/1000000).toLocaleString(undefined, {maximumFractionDigits: this.get('maximumFractionDigits)')}) + ' km²'; 34609 } else { 34610 return area.toLocaleString(undefined, {maximumFractionDigits: this.get('maximumFractionDigits')}) + ' m²'; 34611 } 34612 } else { 34613 return ''; 34614 } 34615}; 34616/** Format area to display in the popup 34617 * Can be overwritten to display measure in different unit (default: meter). 34618 * @param {number} length length in m 34619 * @return {string} the formated length 34620 * @api 34621 */ 34622ol.Overlay.Tooltip.prototype.formatLength = function(length) { 34623 if (length > Math.pow(10,-1*this.get('maximumFractionDigits'))) { 34624 if (length>100) { 34625 return (length/1000).toLocaleString(undefined, {maximumFractionDigits: this.get('maximumFractionDigits')}) + ' km'; 34626 } else { 34627 return length.toLocaleString(undefined, {maximumFractionDigits: this.get('maximumFractionDigits')}) + ' m'; 34628 } 34629 } else { 34630 return ''; 34631 } 34632}; 34633/** Set a feature associated with the tooltips, measure info on the feature will be added in the tooltip 34634 * @param {ol.Feature|ol.Event} feature an ol.Feature or an event (object) with a feature property 34635 */ 34636ol.Overlay.Tooltip.prototype.setFeature = function(feature) { 34637 // Handle event with a feature as property. 34638 if (feature && feature.feature) feature = feature.feature; 34639 // The feature 34640 this._feature = feature; 34641 if (this._listener) { 34642 this._listener.forEach(function(l) { 34643 ol.Observable.unByKey(l); 34644 }); 34645 } 34646 this._listener = []; 34647 this.set('measure', ''); 34648 if (feature) { 34649 this._listener.push(feature.getGeometry().on('change', function(e){ 34650 var geom = e.target; 34651 var measure; 34652 if (geom.getArea) { 34653 measure = this.formatArea(ol.sphere.getArea(geom, { projection: this.getMap().getView().getProjection() })); 34654 } else if (geom.getLength) { 34655 measure = this.formatLength(ol.sphere.getLength(geom, { projection: this.getMap().getView().getProjection() })); 34656 } 34657 this.set('measure', measure); 34658 }.bind(this))); 34659 } 34660}; 34661 34662/* 34663 Copyright (c) 2017 Jean-Marc VIGLINO, 34664 released under the CeCILL-B license (http://www.cecill.info/). 34665 ol.coordinate.convexHull compute a convex hull using Andrew's Monotone Chain Algorithm. 34666 @see https://en.wikipedia.org/wiki/Convex_hull_algorithms 34667*/ 34668ol.coordinate.convexHull; 34669(function(){ 34670/** Tests if a point is left or right of line (a,b). 34671* @param {ol.coordinate} a point on the line 34672* @param {ol.coordinate} b point on the line 34673* @param {ol.coordinate} o 34674* @return {bool} true if (a,b,o) turns clockwise 34675*/ 34676var clockwise = function (a, b, o) { 34677 return ((a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) <= 0); 34678}; 34679/** Compute a convex hull using Andrew's Monotone Chain Algorithm 34680 * @param {Array<ol.geom.Point>} points an array of 2D points 34681 * @return {Array<ol.geom.Point>} the convex hull vertices 34682 */ 34683ol.coordinate.convexHull = function (points) { // Sort by increasing x and then y coordinate 34684 var i; 34685 points.sort(function(a, b) { 34686 return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]; 34687 }); 34688 // Compute the lower hull 34689 var lower = []; 34690 for (i = 0; i < points.length; i++) { 34691 while (lower.length >= 2 && clockwise(lower[lower.length - 2], lower[lower.length - 1], points[i])) { 34692 lower.pop(); 34693 } 34694 lower.push(points[i]); 34695 } 34696 // Compute the upper hull 34697 var upper = []; 34698 for (i = points.length - 1; i >= 0; i--) { 34699 while (upper.length >= 2 && clockwise(upper[upper.length - 2], upper[upper.length - 1], points[i])) { 34700 upper.pop(); 34701 } 34702 upper.push(points[i]); 34703 } 34704 upper.pop(); 34705 lower.pop(); 34706 return lower.concat(upper); 34707}; 34708/* Get coordinates of a geometry */ 34709var getCoordinates = function (geom) { 34710 var i, p
34711 var h = []; 34712 switch (geom.getType()) { 34713 case "Point":h.push(geom.getCoordinates()); 34714 break; 34715 case "LineString": 34716 case "LinearRing": 34717 case "MultiPoint":h = geom.getCoordinates(); 34718 break; 34719 case "MultiLineString": 34720 p = geom.getLineStrings(); 34721 for (i = 0; i < p.length; i++) h.concat(getCoordinates(p[i])); 34722 break; 34723 case "Polygon": 34724 h = getCoordinates(geom.getLinearRing(0)); 34725 break; 34726 case "MultiPolygon": 34727 p = geom.getPolygons(); 34728 for (i = 0; i < p.length; i++) h.concat(getCoordinates(p[i])); 34729 break; 34730 case "GeometryCollection": 34731 p = geom.getGeometries(); 34732 for (i = 0; i < p.length; i++) h.concat(getCoordinates(p[i])); 34733 break; 34734 default:break; 34735 } 34736 return h; 34737}; 34738/** Compute a convex hull on a geometry using Andrew's Monotone Chain Algorithm 34739 * @return {Array<ol.geom.Point>} the convex hull vertices 34740 */ 34741ol.geom.Geometry.prototype.convexHull = function() { 34742 return ol.coordinate.convexHull(getCoordinates(this)); 34743}; 34744})(); 34745 34746/* 34747 Copyright (c) 2018 Jean-Marc VIGLINO, 34748 released under the CeCILL-B license (http://www.cecill.info/). 34749*/ 34750/** Convert coordinate to French DFCI grid 34751 * @param {ol/coordinate} coord 34752 * @param {number} level [0-3] 34753 * @param {ol/proj/Projection} projection of the coord, default EPSG:27572 34754 * @return {String} the DFCI index 34755 */ 34756ol.coordinate.toDFCI = function (coord, level, projection) { 34757 if (!level && level !==0) level = 3; 34758 if (projection) { 34759 if (!ol.proj.get('EPSG:27572')) { 34760 // Add Lambert IIe proj 34761 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"); 34762 ol.proj.proj4.register(proj4); 34763 } 34764 coord = ol.proj.transform(coord, projection, 'EPSG:27572'); 34765 } 34766 var x = coord[0]; 34767 var y = coord[1]; 34768 var s = ''; 34769 // Level 0 34770 var step = 100000; 34771 s += String.fromCharCode(65 + Math.floor((x<800000?x:x+200000)/step)) 34772 + String.fromCharCode(65 + Math.floor((y<2300000?y:y+200000)/step) - 1500000/step); 34773 if (level === 0) return s; 34774 // Level 1 34775 var step1 = 100000/5; 34776 s += 2*Math.floor((x%step)/step1); 34777 s += 2*Math.floor((y%step)/step1); 34778 if (level === 1) return s; 34779 // Level 2 34780 var step2 = step1 / 10; 34781 var x0 = Math.floor((x%step1)/step2); 34782 s += String.fromCharCode(65 + (x0<8 ? x0 : x0+2)); 34783 s += Math.floor((y%step1)/step2); 34784 if (level === 2) return s; 34785 // Level 3 34786 var x3 = Math.floor((x%step2)/500); 34787 var y3 = Math.floor((y%step2)/500); 34788 if (x3<1) { 34789 if (y3>1) s += '.1'; 34790 else s += '.4'; 34791 } else if (x3>2) { 34792 if (y3>1) s += '.2'; 34793 else s += '.3'; 34794 } else if (y3>2) { 34795 if (x3<2) s += '.1'; 34796 else s += '.2'; 34797 } else if (y3<1) { 34798 if (x3<2) s += '.4'; 34799 else s += '.3'; 34800 } else { 34801 s += '.5'; 34802 } 34803 return s; 34804}; 34805/** Get coordinate from French DFCI index 34806 * @param {String} index the DFCI index 34807 * @param {ol/proj/Projection} projection result projection, default EPSG:27572 34808 * @return {ol/coordinate} coord 34809 */ 34810ol.coordinate.fromDFCI = function (index, projection) { 34811 var coord; 34812 // Level 0 34813 var step = 100000; 34814 var x = index.charCodeAt(0) - 65; 34815 x = (x<8 ? x : x-2)*step; 34816 var y = index.charCodeAt(1) - 65; 34817 y = (y<8 ? y : y-2)*step + 1500000; 34818 if (index.length===2) { 34819 coord = [x+step/2, y+step/2]; 34820 } else { 34821 // Level 1 34822 step /= 5; 34823 x += Number(index.charAt(2))/2*step; 34824 y += Number(index.charAt(3))/2*step; 34825 if (index.length===4) { 34826 coord = [x+step/2, y+step/2]; 34827 } else { 34828 // Level 2 34829 step /= 10; 34830 var x0 = index.charCodeAt(4) - 65; 34831 x += (x0<8 ? x0 : x0-2)*step; 34832 y += Number(index.charAt(5))*step; 34833 if (index.length === 6) { 34834 coord = [x+step/2, y+step/2]; 34835 } else { 34836 // Level 3 34837 switch (index.charAt(7)) { 34838 case '1': 34839 coord = [x+step/4, y+3*step/4]; 34840 break; 34841 case '2': 34842 coord = [x+3*step/4, y+3*step/4]; 34843 break; 34844 case '3': 34845 coord = [x+3*step/4, y+step/4]; 34846 break; 34847 case '4': 34848 coord = [x+step/4, y+step/4]; 34849 break; 34850 default: 34851 coord = [x+step/2, y+step/2]; 34852 break; 34853 } 34854 } 34855 } 34856 } 34857 // Convert ? 34858 if (projection) { 34859 if (!ol.proj.get('EPSG:27572')) { 34860 // Add Lambert IIe proj 34861 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"); 34862 ol.proj.proj4.register(proj4); 34863 } 34864 coord = ol.proj.transform(coord, 'EPSG:27572', projection); 34865 } 34866 return coord; 34867}; 34868/** The string is a valid DFCI index 34869 * @param {string} index DFCI index 34870 * @return {boolean} 34871 */ 34872ol.coordinate.validDFCI = function (index) { 34873 if (index.length<2 || index.length>8) return false;
34874 if (/[^A-H|^K-N]/.test(index.substr(0,1))) return false; 34875 if (/[^B-H|^K-N]/.test(index.substr(1,1))) return false; 34876 if (index.length>2) { 34877 if (index.length<4) return false; 34878 if (/[^0,^2,^4,^6,^8]/.test(index.substr(2,1))) return false; 34879 if (/[^0,^2,^4,^6,^8]/.test(index.substr(3,1))) return false; 34880 } 34881 if (index.length>4) { 34882 if (index.length<6) return false; 34883 if (/[^A-H|^K-L]/.test(index.substr(4,1))) return false; 34884 if (/[^0-9]/.test(index.substr(5,1))) return false; 34885 } 34886 if (index.length>6) { 34887 if (index.length<8) return false; 34888 if (index.substr(6,1)!=='.') return false; 34889 if (/[^1-5]/.test(index.substr(7,1))) return false; 34890 } 34891 return true; 34892} 34893/** Coordinate is valid for DFCI 34894 * @param {ol/coordinate} coord 34895 * @param {ol/proj/Projection} projection result projection, default EPSG:27572 34896 * @return {boolean} 34897 */ 34898ol.coordinate.validDFCICoord = function (coord, projection) { 34899 if (projection) { 34900 if (!ol.proj.get('EPSG:27572')) { 34901 // Add Lambert IIe proj 34902 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"); 34903 ol.proj.proj4.register(proj4); 34904 } 34905 coord = ol.proj.transform(coord, projection, 'EPSG:27572'); 34906 } 34907 // Test extent 34908 if (0 > coord[0] || coord[0] > 1200000 ) return false; 34909 if (1600000 > coord[1] || coord[1] > 2700000 ) return false; 34910 return true; 34911}; 34912 34913/* 34914 Copyright (c) 2018 Jean-Marc VIGLINO, 34915 released under the CeCILL-B license (http://www.cecill.info/). 34916*/ 34917/* Define namespace 34918 */ 34919ol.graph = {}; 34920/** 34921 * @classdesc 34922 * Compute the shortest paths between nodes in a graph source 34923 * The source must only contains LinesString. 34924 * 34925 * It uses a A* optimisation. 34926 * You can overwrite methods to customize the result. 34927 * @see https://en.wikipedia.org/wiki/Dijkstras_algorithm 34928 * @constructor 34929 * @fires calculating 34930 * @fires start 34931 * @fires finish 34932 * @fires pause 34933 * @param {any} options 34934 * @param {ol/source/Vector} options.source the source for the edges 34935 * @param {integer} [options.maxIteration=20000] maximum iterations before a pause event is fired, default 20000 34936 * @param {integer} [options.stepIteration=2000] number of iterations before a calculating event is fired, default 2000 34937 * @param {number} [options.epsilon=1E-6] geometric precision (min distance beetween 2 points), default 1E-6 34938 */ 34939ol.graph.Dijskra = function (options) { 34940 options = options || {}; 34941 this.source = options.source; 34942 this.nodes = new ol.source.Vector(); 34943 // Maximum iterations 34944 this.maxIteration = options.maxIteration || 20000; 34945 this.stepIteration = options.stepIteration || 2000; 34946 // A* optimisation 34947 this.astar = true; 34948 this.candidat = []; 34949 ol.Object.call (this); 34950 this.set ('epsilon', options.epsilon || 1E-6); 34951}; 34952ol.ext.inherits(ol.graph.Dijskra, ol.Object); 34953/** Get the weighting of the edge, for example a speed factor 34954 * The function returns a value beetween ]0,1] 34955 * - 1 = no weighting 34956 * - 0.5 = goes twice more faster on this road 34957 * 34958 * If no feature is provided you must return the lower weighting you're using 34959 * @param {ol/Feature} feature 34960 * @return {number} a number beetween 0-1 34961 * @api 34962 */ 34963ol.graph.Dijskra.prototype.weight = function(/* feature */) { 34964 return 1; 34965}; 34966/** Get the edge direction 34967 * - 0 : the road is blocked 34968 * - 1 : direct way 34969 * - -1 : revers way 34970 * - 2 : both way 34971 * @param {ol/Feature} feature 34972 * @return {Number} 0: blocked, 1: direct way, -1: revers way, 2:both way 34973 * @api 34974 */ 34975ol.graph.Dijskra.prototype.direction = function(/* feature */) { 34976 return 2; 34977}; 34978/** Calculate the length of an edge 34979 * @param {ol/Feature|ol/geom/LineString} geom 34980 * @return {number} 34981 * @api 34982 */ 34983ol.graph.Dijskra.prototype.getLength = function(geom) { 34984 if (geom.getGeometry) geom = geom.getGeometry(); 34985 return geom.getLength(); 34986}; 34987/** Get the nodes source concerned in the calculation 34988 * @return {ol/source/Vector} 34989 */ 34990ol.graph.Dijskra.prototype.getNodeSource = function() { 34991 return this.nodes; 34992}; 34993/** Get all features at a coordinate 34994 * @param {ol/coordinate} coord 34995 * @return {Array<ol/Feature>} 34996 */ 34997ol.graph.Dijskra.prototype.getEdges = function(coord) { 34998 var extent = ol.extent.buffer (ol.extent.boundingExtent([coord]), this.get('epsilon')); 34999 var result = []; 35000 this.source.forEachFeatureIntersectingExtent(extent, function(f){ 35001 result.push(f); 35002 }); 35003 return result; 35004}; 35005/** Get a node at a coordinate 35006 * @param {ol/coordinate} coord 35007 * @return {ol/Feature} the node 35008 */ 35009ol.graph.Dijskra.prototype.getNode = function(coord) { 35010 var extent = ol.extent.buffer (ol.extent.boundingExtent([coord]), this.get('epsilon')); 35011 var result = [];
35012 this.nodes.forEachFeatureIntersectingExtent(extent, function(f){ 35013 result.push(f); 35014 }); 35015 return result[0]; 35016}; 35017/** Add a node 35018 * @param {ol/coorindate} p 35019 * @param {number} wdist the distance to reach this node 35020 * @param {ol/Feature} from the feature used to come to this node 35021 * @param {ol/Feature} prev the previous node 35022 * @return {ol/Feature} the node 35023 * @private 35024 */ 35025ol.graph.Dijskra.prototype.addNode = function(p, wdist, dist, from, prev) { 35026 // Final condition 35027 if (this.wdist && wdist > this.wdist) return false; 35028 // Look for existing point 35029 var node = this.getNode(p); 35030 // Optimisation ? 35031 var dtotal = wdist + this.getLength(new ol.geom.LineString([this.end, p])) * this.weight(); 35032 if (this.astar && this.wdist && dtotal > this.wdist) return false; 35033 if (node) { 35034 // Allready there 35035 if (node!==this.arrival && node.get('wdist') <= wdist) return node; 35036 // New candidat 35037 node.set('dist', dist); 35038 node.set('wdist', wdist); 35039 node.set('dtotal', dtotal); 35040 node.set('from', from); 35041 node.set('prev', prev); 35042 if (node===this.arrival) { 35043 this.wdist = wdist; 35044 } 35045 this.candidat.push (node); 35046 } else { 35047 // New candidat 35048 node = new ol.Feature({ 35049 geometry: new ol.geom.Point(p), 35050 from: from, 35051 prev: prev, 35052 dist: dist || 0, 35053 wdist: wdist, 35054 dtotal: dtotal, 35055 }); 35056 if (wdist<0) { 35057 node.set('wdist', false); 35058 } 35059 else this.candidat.push (node); 35060 // Add it in the node source 35061 this.nodes.addFeature(node); 35062 } 35063 return node; 35064}; 35065/** Get the closest coordinate of a node in the graph source (an edge extremity) 35066 * @param {ol/coordinate} p 35067 * @return {ol/coordinate} 35068 * @private 35069 */ 35070ol.graph.Dijskra.prototype.closestCoordinate = function(p) { 35071 var e = this.source.getClosestFeatureToCoordinate(p); 35072 var p0 = e.getGeometry().getFirstCoordinate(); 35073 var p1 = e.getGeometry().getLastCoordinate(); 35074 if (ol.coordinate.dist2d(p, p0) < ol.coordinate.dist2d(p, p1)) return p0; 35075 else return p1; 35076}; 35077/** Calculate a path beetween 2 points 35078 * @param {ol/coordinate} start 35079 * @param {ol/coordinate} end 35080 * @return {boolean|Array<ol/coordinate>} false if don't start (still running) or start and end nodes 35081 */ 35082ol.graph.Dijskra.prototype.path = function(start, end) { 35083 if (this.running) return false; 35084 // Starting nodes 35085 start = this.closestCoordinate(start); 35086 this.end = this.closestCoordinate(end); 35087 if (start[0]===this.end[0] 35088 && start[1]===this.end[1]) { 35089 this.dispatchEvent({ 35090 type: 'finish', 35091 route: [], 35092 wDistance: -1, 35093 distance: this.wdist 35094 }); 35095 return false; 35096 } 35097 // Initialize 35098 var self = this; 35099 this.nodes.clear(); 35100 this.candidat = []; 35101 this.wdist = 0; 35102 this.running = true; 35103 // Starting point 35104 this.addNode(start, 0); 35105 // Arrival 35106 this.arrival = this.addNode(this.end, -1); 35107 // Start 35108 this.nb = 0; 35109 this.dispatchEvent({ 35110 type: 'start' 35111 }); 35112 setTimeout(function() { self._resume(); }); 35113 return [start, this.end]; 35114}; 35115/** Restart after pause 35116 */ 35117ol.graph.Dijskra.prototype.resume = function() { 35118 if (this.running) return; 35119 if (this.candidat.length) { 35120 this.running = true; 35121 this.nb = 0; 35122 this._resume(); 35123 } 35124}; 35125/** Pause 35126 */ 35127ol.graph.Dijskra.prototype.pause = function() { 35128 if (!this.running) return; 35129 this.nb = -1; 35130}; 35131/** Get the current 'best way'. 35132 * This may be used to animate while calculating. 35133 * @return {Array<ol/Feature>} 35134 */ 35135ol.graph.Dijskra.prototype.getBestWay = function() { 35136 var node, max = -1; 35137 for (var i=0, n; n = this.candidat[i]; i++) { 35138 if (n.get('wdist') > max) { 35139 node = n; 35140 max = n.get('wdist'); 35141 } 35142 } 35143 // Calculate route to this node 35144 return this.getRoute(node); 35145}; 35146/** Go on searching new candidats 35147 * @private 35148 */ 35149ol.graph.Dijskra.prototype._resume = function() { 35150 if (!this.running) return; 35151 while (this.candidat.length) { 35152 // Sort by wdist 35153 this.candidat.sort (function(a,b) { 35154 return (a.get('dtotal') < b.get('dtotal') ? 1 : a.get('dtotal')===b.get('dtotal') ? 0 : -1); 35155 }); 35156 // First candidate 35157 var node = this.candidat.pop(); 35158 var p = node.getGeometry().getCoordinates(); 35159 // Find connected edges 35160 var edges = this.getEdges(p); 35161 for (var i=0, e; e=edges[i]; i++) { 35162 if (node.get('from')!==e) { 35163 var dist = this.getLength (e); 35164 if (dist < 0) { 35165 console.log ('distance < 0!'); 35166 // continue; 35167 } 35168 var wdist = node.get('wdist') + dist * this.weight(e); 35169 dist = node.get('dist') + dist; 35170 var pt1 = e.getGeometry().getFirstCoordinate(); 35171 var pt2 = e.getGeometry().getLastCoordinate(); 35172 var sens = this.direction(e); 35173 if (sens!==0) { 35174 if (p[0]===pt1[0] && p[1]===pt1[1] && sens!==-1) { 35175 this.addNode(pt2, wdist, dist, e, node); 35176 } 35177 if (p[0]===pt2[0] && p[0]===pt2[0] && sens!==1) { 35178 this.addNode(pt1, wdist, dist, e, node); 35179 } 35180 } 35181 } 35182 // Test overflow or pause 35183 if (this.nb === -1 || this.nb++ > this.maxIteration) { 35184 this.running = false;
35185 this.dispatchEvent({ 35186 type: 'pause', 35187 overflow: (this.nb !== -1) 35188 }); 35189 return; 35190 } 35191 // Take time to do something 35192 if (!(this.nb % this.stepIteration)){ 35193 var self = this; 35194 window.setTimeout(function() { self._resume() }, 5); 35195 this.dispatchEvent({ 35196 type: 'calculating' 35197 }); 35198 return; 35199 } 35200 } 35201 } 35202 // Finish! 35203 this.nodes.clear(); 35204 this.running = false; 35205 this.dispatchEvent({ 35206 type: 'finish', 35207 route: this.getRoute(this.arrival), 35208 wDistance: this.wdist, 35209 distance: this.arrival.get('dist') 35210 }); 35211}; 35212/** Get the route to a node 35213 * @param {ol/Feature} node 35214 * @return {Array<ol/Feature>} 35215 * @private 35216 */ 35217ol.graph.Dijskra.prototype.getRoute = function(node) { 35218 var route = []; 35219 while (node) { 35220 route.unshift(node.get('from')); 35221 node = node.get('prev'); 35222 } 35223 route.shift(); 35224 return route; 35225}; 35226 35227/** French Geoportail alti coding 35228 * @param {ol.geom.Geometry} geom 35229 * @param {Object} options 35230 * @param {ol/proj~ProjectionLike} [options.projection='EPSG:3857'] geometry projection, default 'EPSG:3857' 35231 * @param {string} [options.apiKey='essentiels'] Geoportail API key 35232 * @param {number} [options.sampling=0] number of resulting point, max 5000, if none keep input points or use samplingDist 35233 * @param {number} [options.samplingDist=0] distance for sampling the line or use sampling if lesser 35234 * @param {string} options.success a function that takes the resulting XYZ geometry 35235 * @param {string} options.error 35236 */ 35237ol.geom.GPAltiCode = function(geom, options) { 35238 options = options || {}; 35239 var typeGeom = geom.getType(); 35240 if (typeGeom !== 'Point' && typeGeom !== 'LineString') { 35241 console.warn('[GPAltiCode] '+typeGeom+' not supported...') 35242 return; 35243 } 35244 var proj = options.projection || 'EPSG:3857'; 35245 var sampling = options.sampling || 0; 35246 if (options.samplingDist) { 35247 var d = geom.getLength(); 35248 sampling = Math.max(sampling, Math.round(d / options.samplingDist)); 35249 } 35250 if (sampling > 5000) sampling = 5000; 35251 if (sampling < 2) sampling = 0; 35252 geom = geom.clone().transform(proj, 'EPSG:4326'); 35253 var g, lon = [], lat = []; 35254 switch (typeGeom) { 35255 case 'Point': { 35256 g = [geom.getCoordinates()]; 35257 break; 35258 } 35259 case 'LineString': { 35260 g = geom.getCoordinates(); 35261 break; 35262 } 35263 default: return; 35264 } 35265 if (sampling <= g.length) sampling = 0; 35266 g.forEach(function(p) { 35267 lon.push(Math.round(p[0]*1000000)/1000000); 35268 lat.push(Math.round(p[1]*1000000)/1000000); 35269 }); 35270 // Get elevation 35271 var param = 'lon='+lon.join('|')+'&lat='+lat.join('|'); 35272 if (sampling) param += '&sampling='+sampling; 35273 ol.ext.Ajax.get({ 35274 url: 'https://wxs.ign.fr/'+(options.apiKey || 'essentiels')+'/alti/rest/'+(lon.length>1 ? 'elevationLine' : 'elevation')+'.json?'+param, 35275 success: function(res) { 35276 var pts = []; 35277 res.elevations.forEach(function(e, i) { 35278 if (sampling) { 35279 pts.push([e.lon, e.lat, e.z]); 35280 } else { 35281 pts.push([g[i][0], g[i][1], e.z]); 35282 } 35283 }); 35284 if (typeGeom==='Point') pts = pts[0]; 35285 var result = ol.geom.createFromType(typeGeom, pts); 35286 result.transform('EPSG:4326', proj); 35287 if (typeof(options.success) === 'function') options.success(result); 35288 }, 35289 error: function(e) { 35290 if (typeof(options.error) === 'function') options.error(e); 35291 } 35292 }); 35293} 35294/** Calculate elevation on coordinates or on a set of coordinates 35295 * @param {ol.coordinate|Array<ol.coordinate>} coord coordinate or an array of coordinates 35296 * @param {Object} options 35297 * @param {ol/proj~ProjectionLike} [options.projection='EPSG:3857'] geometry projection, default 'EPSG:3857' 35298 * @param {string} [options.apiKey='essentiels'] Geoportail API key 35299 * @param {number} [options.sampling=0] number of resulting point, max 5000, if none keep input points or use samplingDist 35300 * @param {number} [options.samplingDist=0] distance for sampling the line or use sampling if lesser 35301 * @param {string} options.success a function that takes the resulting XYZ coordinates 35302 * @param {string} options.error 35303 */ 35304ol.coordinate.GPAltiCode = function(coord, options) { 35305 options = options || {}; 35306 var unique = !coord[0].length; 35307 var g = unique ? new ol.geom.Point(coord) : new ol.geom.LineString(coord); 35308 ol.geom.GPAltiCode(g, { 35309 projection: options.projection, 35310 apiKey: options.apiKey, 35311 sampling: options.sampling, 35312 samplingDist: options.samplingDist, 35313 success: function(g) { 35314 if (typeof(options.success) === 'function') { 35315 options.success(g.getCoordinates()) 35316 } 35317 }, 35318 error: options.error 35319 }) 35320} 35321 35322/* Copyright (c) 2016 Jean-Marc VIGLINO, 35323 released under the CeCILL-B license (French BSD license)
35324 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 35325 Usefull function to handle geometric operations 35326*/ 35327/** Distance beetween 2 points 35328 * Usefull geometric functions 35329 * @param {ol.Coordinate} p1 first point 35330 * @param {ol.Coordinate} p2 second point 35331 * @return {number} distance 35332 */ 35333ol.coordinate.dist2d = function(p1, p2) { 35334 var dx = p1[0]-p2[0]; 35335 var dy = p1[1]-p2[1]; 35336 return Math.sqrt(dx*dx+dy*dy); 35337} 35338/** 2 points are equal 35339 * Usefull geometric functions 35340 * @param {ol.Coordinate} p1 first point 35341 * @param {ol.Coordinate} p2 second point 35342 * @return {boolean} 35343 */ 35344ol.coordinate.equal = function(p1, p2) { 35345 return (p1[0]==p2[0] && p1[1]==p2[1]); 35346} 35347/** Get center coordinate of a feature 35348 * @param {ol.Feature} f 35349 * @return {ol.coordinate} the center 35350 */ 35351ol.coordinate.getFeatureCenter = function(f) { 35352 return ol.coordinate.getGeomCenter (f.getGeometry()); 35353}; 35354/** Get center coordinate of a geometry 35355* @param {ol.geom.Geometry} geom 35356* @return {ol.Coordinate} the center 35357*/ 35358ol.coordinate.getGeomCenter = function(geom) { 35359 switch (geom.getType()) { 35360 case 'Point': 35361 return geom.getCoordinates(); 35362 case "MultiPolygon": 35363 geom = geom.getPolygon(0); 35364 // fallthrough 35365 case "Polygon": 35366 return geom.getInteriorPoint().getCoordinates(); 35367 default: 35368 return geom.getClosestPoint(ol.extent.getCenter(geom.getExtent())); 35369 } 35370}; 35371/** Offset a polyline 35372 * @param {Array<ol.Coordinate>} coords 35373 * @param {number} offset 35374 * @return {Array<ol.Coordinate>} resulting coord 35375 * @see http://stackoverflow.com/a/11970006/796832 35376 * @see https://drive.google.com/viewerng/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxqa2dhZGdldHN0b3JlfGd4OjQ4MzI5M2Y0MjNmNzI2MjY 35377 */ 35378ol.coordinate.offsetCoords = function (coords, offset) { 35379 var path = []; 35380 var N = coords.length-1; 35381 var max = N; 35382 var mi, mi1, li, li1, ri, ri1, si, si1, Xi1, Yi1; 35383 var p0, p1, p2; 35384 var isClosed = ol.coordinate.equal(coords[0],coords[N]); 35385 if (!isClosed) { 35386 p0 = coords[0]; 35387 p1 = coords[1]; 35388 p2 = [ 35389 p0[0] + (p1[1] - p0[1]) / ol.coordinate.dist2d(p0,p1) *offset, 35390 p0[1] - (p1[0] - p0[0]) / ol.coordinate.dist2d(p0,p1) *offset 35391 ]; 35392 path.push(p2); 35393 coords.push(coords[N]) 35394 N++; 35395 max--; 35396 } 35397 for (var i = 0; i < max; i++) { 35398 p0 = coords[i]; 35399 p1 = coords[(i+1) % N]; 35400 p2 = coords[(i+2) % N]; 35401 mi = (p1[1] - p0[1])/(p1[0] - p0[0]); 35402 mi1 = (p2[1] - p1[1])/(p2[0] - p1[0]); 35403 // Prevent alignements 35404 if (Math.abs(mi-mi1) > 1e-10) { 35405 li = Math.sqrt((p1[0] - p0[0])*(p1[0] - p0[0])+(p1[1] - p0[1])*(p1[1] - p0[1])); 35406 li1 = Math.sqrt((p2[0] - p1[0])*(p2[0] - p1[0])+(p2[1] - p1[1])*(p2[1] - p1[1])); 35407 ri = p0[0] + offset*(p1[1] - p0[1])/li; 35408 ri1 = p1[0] + offset*(p2[1] - p1[1])/li1; 35409 si = p0[1] - offset*(p1[0] - p0[0])/li; 35410 si1 = p1[1] - offset*(p2[0] - p1[0])/li1; 35411 Xi1 = (mi1*ri1-mi*ri+si-si1) / (mi1-mi); 35412 Yi1 = (mi*mi1*(ri1-ri)+mi1*si-mi*si1) / (mi1-mi); 35413 // Correction for vertical lines 35414 if(p1[0] - p0[0] == 0) { 35415 Xi1 = p1[0] + offset*(p1[1] - p0[1])/Math.abs(p1[1] - p0[1]); 35416 Yi1 = mi1*Xi1 - mi1*ri1 + si1; 35417 } 35418 if (p2[0] - p1[0] == 0 ) { 35419 Xi1 = p2[0] + offset*(p2[1] - p1[1])/Math.abs(p2[1] - p1[1]); 35420 Yi1 = mi*Xi1 - mi*ri + si; 35421 } 35422 path.push([Xi1, Yi1]); 35423 } 35424 } 35425 if (isClosed) { 35426 path.push(path[0]); 35427 } else { 35428 coords.pop(); 35429 p0 = coords[coords.length-1]; 35430 p1 = coords[coords.length-2]; 35431 p2 = [ 35432 p0[0] - (p1[1] - p0[1]) / ol.coordinate.dist2d(p0,p1) *offset, 35433 p0[1] + (p1[0] - p0[0]) / ol.coordinate.dist2d(p0,p1) *offset 35434 ]; 35435 path.push(p2); 35436 } 35437 return path; 35438} 35439/** Find the segment a point belongs to 35440 * @param {ol.Coordinate} pt 35441 * @param {Array<ol.Coordinate>} coords 35442 * @return {} the index (-1 if not found) and the segment 35443 */ 35444ol.coordinate.findSegment = function (pt, coords) { 35445 for (var i=0; i<coords.length-1; i++) { 35446 var p0 = coords[i]; 35447 var p1 = coords[i+1]; 35448 if (ol.coordinate.equal(pt, p0) || ol.coordinate.equal(pt, p1)) { 35449 return { index:1, segment: [p0,p1] }; 35450 } else { 35451 var d0 = ol.coordinate.dist2d(p0,p1); 35452 var v0 = [ (p1[0] - p0[0]) / d0, (p1[1] - p0[1]) / d0 ]; 35453 var d1 = ol.coordinate.dist2d(p0,pt); 35454 var v1 = [ (pt[0] - p0[0]) / d1, (pt[1] - p0[1]) / d1 ]; 35455 if (Math.abs(v0[0]*v1[1] - v0[1]*v1[0]) < 1e-10) { 35456 return { index:1, segment: [p0,p1] }; 35457 } 35458 } 35459 } 35460 return { index: -1 }; 35461}; 35462/** 35463 * Split a Polygon geom with horizontal lines 35464 * @param {Array<ol.Coordinate>} geom 35465 * @param {number} y the y to split 35466 * @param {number} n contour index 35467 * @return {Array<Array<ol.Coordinate>>} 35468 */ 35469ol.coordinate.splitH = function (geom, y, n) { 35470 var x, abs; 35471 var list = []; 35472 for (var i=0; i<geom.length-1; i++) { 35473 // Hole separator? 35474 if (!geom[i].length || !geom[i+1].length) continue; 35475 // Intersect 35476 if (geom[i][1]<=y && geom[i+1][1]>y || geom[i][1]>=y && geom[i+1][1]<y) { 35477 abs = (y-geom[i][1]) / (geom[i+1][1]-geom[i][1]); 35478 x = abs * (geom[i+1][0]-geom[i][0]) + geom[i][0]; 35479 list.push ({ contour: n, index: i, pt: [x,y], abs: abs }); 35480 } 35481 } 35482 // Sort x 35483 list.sort(function(a,b) { return a.pt[0] - b.pt[0] }); 35484 // Horizontal segment 35485 var result = []; 35486 for (var j=0; j<list.length-1; j += 2) { 35487 result.push([list[j], list[j+1]]) 35488 } 35489 return result; 35490}; 35491/** Create a geometry given a type and coordinates */ 35492ol.geom.createFromType = function (type, coordinates) { 35493 switch (type) { 35494 case 'LineString': return new ol.geom.LineString(coordinates); 35495 case 'LinearRing': return new ol.geom.LinearRing(coordinates); 35496 case 'MultiLineString': return new ol.geom.MultiLineString(coordinates); 35497 case 'MultiPoint': return new ol.geom.MultiPoint(coordinates); 35498 case 'MultiPolygon': return new ol.geom.MultiPolygon(coordinates); 35499 case 'Point': return new ol.geom.Point(coordinates); 35500 case 'Polygon': return new ol.geom.Polygon(coordinates); 35501 default: 35502 console.error('[createFromType] Unsupported type: '+type); 35503 return null; 35504 } 35505}; 35506/** Intersect 2 lines 35507 * @param {Arrar<ol.coordinate>} d1 35508 * @param {Arrar<ol.coordinate>} d2 35509 */ 35510ol.coordinate.getIntersectionPoint = function (d1, d2) { 35511 var d1x = d1[1][0] - d1[0][0]; 35512 var d1y = d1[1][1] - d1[0][1]; 35513 var d2x = d2[1][0] - d2[0][0]; 35514 var d2y = d2[1][1] - d2[0][1]; 35515 var det = d1x * d2y - d1y * d2x; 35516 if (det != 0) { 35517 var k = (d1x * d1[0][1] - d1x * d2[0][1] - d1y * d1[0][0] + d1y * d2[0][0]) / det; 35518 return [d2[0][0] + k*d2x, d2[0][1] + k*d2y]; 35519 } else { 35520 return false;
35521 } 35522}; 35523ol.extent.intersection; 35524(function() { 35525// Split at x 35526function splitX(pts, x) { 35527 var pt; 35528 for (var i=pts.length-1; i>0; i--) { 35529 if ((pts[i][0]>x && pts[i-1][0]<x) || (pts[i][0]<x && pts[i-1][0]>x)) { 35530 pt = [ x, (x - pts[i][0]) / (pts[i-1][0]-pts[i][0]) * (pts[i-1][1]-pts[i][1]) + pts[i][1]]; 35531 pts.splice(i, 0, pt); 35532 } 35533 } 35534} 35535// Split at y 35536function splitY(pts, y) { 35537 var pt; 35538 for (var i=pts.length-1; i>0; i--) { 35539 if ((pts[i][1]>y && pts[i-1][1]<y) || (pts[i][1]<y && pts[i-1][1]>y)) { 35540 pt = [ (y - pts[i][1]) / (pts[i-1][1]-pts[i][1]) * (pts[i-1][0]-pts[i][0]) + pts[i][0], y]; 35541 pts.splice(i, 0, pt); 35542 } 35543 } 35544} 35545/** Fast polygon intersection with an extent (used for area calculation) 35546 * @param {ol.extent.Extent} extent 35547 * @param {ol.geom.Polygon|ol.geom.MultiPolygon} polygon 35548 * @returns {ol.geom.Polygon|ol.geom.MultiPolygon|null} return null if not a polygon geometry 35549 */ 35550ol.extent.intersection = function(extent, polygon) { 35551 var poly = (polygon.getType() === 'Polygon'); 35552 if (!poly && polygon.getType() !== 'MultiPolygon') return null; 35553 var geom = polygon.getCoordinates(); 35554 if (poly) geom = [geom]; 35555 geom.forEach(function(g) { 35556 g.forEach(function(c) { 35557 splitX(c, extent[0]); 35558 splitX(c, extent[2]); 35559 splitY(c, extent[1]); 35560 splitY(c, extent[3]); 35561 }); 35562 }) 35563 // Snap geom to the extent 35564 geom.forEach(function(g) { 35565 g.forEach(function(c) { 35566 c.forEach(function(p) { 35567 if (p[0]<extent[0]) p[0] = extent[0]; 35568 else if (p[0]>extent[2]) p[0] = extent[2]; 35569 if (p[1]<extent[1]) p[1] = extent[1]; 35570 else if (p[1]>extent[3]) p[1] = extent[3]; 35571 }) 35572 }) 35573 }) 35574 if (poly) { 35575 return new ol.geom.Polygon(geom[0]); 35576 } else { 35577 return new ol.geom.MultiPolygon(geom); 35578 } 35579}; 35580})(); 35581/** Add points along a segment 35582 * @param {ol.Coordinate} p1 35583 * @param {ol.Coordinate} p2 35584 * @param {number} d 35585 * @param {boolean} start include starting point, default true 35586 * @returns {Array<ol.Coordinate>} 35587 */ 35588ol.coordinate.sampleAt = function(p1, p2, d, start) { 35589 var pts = []; 35590 if (start!==false) pts.push(p1); 35591 var dl = ol.coordinate.dist2d(p1,p2); 35592 if (dl) { 35593 var nb = Math.round(dl/d); 35594 if (nb>1) { 35595 var dx = (p2[0]-p1[0]) / nb; 35596 var dy = (p2[1]-p1[1]) / nb; 35597 for (var i=1; i<nb; i++) { 35598 pts.push([p1[0] + dx*i, p1[1] + dy*i]) 35599 } 35600 } 35601 } 35602 pts.push(p2); 35603 return pts; 35604}; 35605/** Sample a LineString at a distance 35606 * @param {number} d 35607 * @returns {ol.geom.LineString} 35608 */ 35609ol.geom.LineString.prototype.sampleAt = function(d) { 35610 var line = this.getCoordinates(); 35611 var result = []; 35612 for (var i=1; i<line.length; i++) { 35613 result = result.concat(ol.coordinate.sampleAt(line[i-1], line[i], d, i===1)); 35614 } 35615 return new ol.geom.LineString(result); 35616}; 35617/** Sample a MultiLineString at a distance 35618 * @param {number} d 35619 * @returns {ol.geom.MultiLineString} 35620 */ 35621ol.geom.MultiLineString.prototype.sampleAt = function(d) { 35622 var lines = this.getCoordinates(); 35623 var result = []; 35624 lines.forEach(function(p) { 35625 var l = []; 35626 for (var i=1; i<p.length; i++) { 35627 l = l.concat(ol.coordinate.sampleAt(p[i-1], p[i], d, i===1)); 35628 } 35629 result.push(l); 35630 }) 35631 return new ol.geom.MultiLineString(result); 35632}; 35633/** Sample a Polygon at a distance 35634 * @param {number} d 35635 * @returns {ol.geom.Polygon} 35636 */ 35637ol.geom.Polygon.prototype.sampleAt = function(res) { 35638 var poly = this.getCoordinates(); 35639 var result = []; 35640 poly.forEach(function(p) { 35641 var l = []; 35642 for (var i=1; i<p.length; i++) { 35643 l = l.concat(ol.coordinate.sampleAt(p[i-1], p[i], res, i===1)); 35644 } 35645 result.push(l); 35646 }) 35647 return new ol.geom.Polygon(result); 35648}; 35649/** Sample a MultiPolygon at a distance 35650 * @param {number} res 35651 * @returns {ol.geom.MultiPolygon} 35652 */ 35653ol.geom.MultiPolygon.prototype.sampleAt = function(res) { 35654 var mpoly = this.getCoordinates(); 35655 var result = []; 35656 mpoly.forEach(function(poly) { 35657 var a = []; 35658 result.push(a); 35659 poly.forEach(function(p) { 35660 var l = []; 35661 for (var i=1; i<p.length; i++) { 35662 l = l.concat(ol.coordinate.sampleAt(p[i-1], p[i], res, i===1)); 35663 } 35664 a.push(l); 35665 }) 35666 }); 35667 return new ol.geom.MultiPolygon(result); 35668}; 35669/** Intersect a geometry using a circle 35670 * @param {ol.geom.Geometry} geom 35671 * @param {number} resolution circle resolution to sample the polygon on the circle, default 1 35672 * @returns {ol.geom.Geometry} 35673 */ 35674ol.geom.Circle.prototype.intersection = function(geom, resolution) { 35675 if (geom.sampleAt) { 35676 var ext = ol.extent.buffer(this.getCenter().concat(this.getCenter()), this.getRadius()); 35677 geom = ol.extent.intersection(ext, geom); 35678 geom = geom.simplify(resolution); 35679 var c = this.getCenter(); 35680 var r = this.getRadius(); 35681 //var res = (resolution||1) * r / 100; 35682 var g = geom.sampleAt(resolution).getCoordinates(); 35683 switch (geom.getType()) { 35684 case 'Polygon': g = [g]; 35685 // fallthrough 35686 case 'MultiPolygon': { 35687 var hasout = false;
35688 // var hasin = false; 35689 var result = []; 35690 g.forEach(function(poly) { 35691 var a = []; 35692 result.push(a); 35693 poly.forEach(function(ring) { 35694 var l = []; 35695 a.push(l); 35696 ring.forEach(function(p) { 35697 var d = ol.coordinate.dist2d(c, p); 35698 if (d > r) { 35699 hasout = true; 35700 l.push([ 35701 c[0] + r / d * (p[0]-c[0]), 35702 c[1] + r / d * (p[1]-c[1]) 35703 ]); 35704 } else { 35705 // hasin = true; 35706 l.push(p); 35707 } 35708 }); 35709 }) 35710 }); 35711 if (!hasout) return geom; 35712 if (geom.getType() === 'Polygon') { 35713 return new ol.geom.Polygon(result[0]); 35714 } else { 35715 return new ol.geom.MultiPolygon(result); 35716 } 35717 } 35718 } 35719 } else { 35720 console.warn('[ol/geom/Circle~intersection] Unsupported geometry type: '+geom.getType()); 35721 } 35722 return geom; 35723}; 35724 35725/** Split a lineString by a point or a list of points 35726 * NB: points must be on the line, use getClosestPoint() to get one 35727 * @param {ol.Coordinate | Array<ol.Coordinate>} pt points to split the line 35728 * @param {Number} tol distance tolerance for 2 points to be equal 35729 */ 35730ol.geom.LineString.prototype.splitAt = function(pt, tol) { 35731 var i; 35732 if (!pt) return [this]; 35733 if (!tol) tol = 1e-10; 35734 // Test if list of points 35735 if (pt.length && pt[0].length) { 35736 var result = [this]; 35737 for (i=0; i<pt.length; i++) { 35738 var r = []; 35739 for (var k=0; k<result.length; k++) { 35740 var ri = result[k].splitAt(pt[i], tol); 35741 r = r.concat(ri); 35742 } 35743 result = r; 35744 } 35745 return result; 35746 } 35747 // Nothing to do 35748 if (ol.coordinate.equal(pt,this.getFirstCoordinate()) 35749 || ol.coordinate.equal(pt,this.getLastCoordinate())) { 35750 return [this]; 35751 } 35752 // Get 35753 var c0 = this.getCoordinates(); 35754 var ci=[c0[0]]; 35755 var c = []; 35756 for (i=0; i<c0.length-1; i++) { 35757 // Filter equal points 35758 if (ol.coordinate.equal(c0[i],c0[i+1])) continue; 35759 // Extremity found 35760 if (ol.coordinate.equal(pt,c0[i+1])) { 35761 ci.push(c0[i+1]); 35762 c.push(new ol.geom.LineString(ci)); 35763 ci = []; 35764 } 35765 // Test alignement 35766 else if (!ol.coordinate.equal(pt,c0[i])) { 35767 var d1, d2, split=false; 35768 if (c0[i][0] == c0[i+1][0]) { 35769 d1 = (c0[i][1]-pt[1]) / (c0[i][1]-c0[i+1][1]); 35770 split = (c0[i][0] == pt[0]) && (0 < d1 && d1 <= 1) 35771 } else if (c0[i][1] == c0[i+1][1]) { 35772 d1 = (c0[i][0]-pt[0]) / (c0[i][0]-c0[i+1][0]); 35773 split = (c0[i][1] == pt[1]) && (0 < d1 && d1 <= 1) 35774 } else { 35775 d1 = (c0[i][0]-pt[0]) / (c0[i][0]-c0[i+1][0]); 35776 d2 = (c0[i][1]-pt[1]) / (c0[i][1]-c0[i+1][1]); 35777 split = (Math.abs(d1-d2) <= tol && 0 < d1 && d1 <= 1) 35778 } 35779 // pt is inside the segment > split 35780 if (split) { 35781 ci.push(pt); 35782 c.push (new ol.geom.LineString(ci)); 35783 ci = [pt]; 35784 } 35785 } 35786 ci.push(c0[i+1]); 35787 } 35788 if (ci.length>1) c.push (new ol.geom.LineString(ci)); 35789 if (c.length) return c; 35790 else return [this]; 35791} 35792// import('ol-ext/geom/LineStringSplitAt') 35793/* Copyright (c) 2016 Jean-Marc VIGLINO, 35794 released under the CeCILL-B license (French BSD license) 35795 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 35796 Usefull function to handle geometric operations 35797*/ 35798/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ 35799/** 35800 * Calculate a MultiPolyline to fill a Polygon with a scribble effect that appears hand-made 35801 * @param {} options 35802 * @param {Number} options.interval interval beetween lines 35803 * @param {Number} options.angle hatch angle in radian, default PI/2 35804 * @return {ol.geom.MultiLineString|null} the resulting MultiLineString geometry or null if none 35805 */ 35806ol.geom.MultiPolygon.prototype.scribbleFill = function (options) { 35807 var scribbles = []; 35808 var poly = this.getPolygons(); 35809 var i, p, s; 35810 for (i=0; p=poly[i]; i++) { 35811 var mls = p.scribbleFill(options); 35812 if (mls) scribbles.push(mls); 35813 } 35814 if (!scribbles.length) return null; 35815 // Merge scribbles 35816 var scribble = scribbles[0]; 35817 var ls; 35818 for (i = 0; s = scribbles[i]; i++) { 35819 ls = s.getLineStrings(); 35820 for (var k = 0; k < ls.length; k++) { 35821 scribble.appendLineString(ls[k]); 35822 } 35823 } 35824 return scribble; 35825}; 35826/** 35827 * Calculate a MultiPolyline to fill a Polygon with a scribble effect that appears hand-made 35828 * @param {} options 35829 * @param {Number} options.interval interval beetween lines 35830 * @param {Number} options.angle hatch angle in radian, default PI/2 35831 * @return {ol.geom.MultiLineString|null} the resulting MultiLineString geometry or null if none 35832 */ 35833ol.geom.Polygon.prototype.scribbleFill = function (options) { 35834 var step = options.interval; 35835 var angle = options.angle || Math.PI/2; 35836 var i, k,l; 35837 // Geometry + rotate 35838 var geom = this.clone(); 35839 geom.rotate(angle, [0,0]); 35840 var coords = geom.getCoordinates(); 35841 // Merge holes 35842 var coord = coords[0]; 35843 for (i=1; i<coords.length; i++) { 35844 // Add a separator 35845 coord.push([]); 35846 // Add the hole 35847 coord = coord.concat(coords[i]); 35848 } 35849 // Extent 35850 var ext = geom.getExtent(); 35851 // Split polygon with horizontal lines 35852 var lines = []; 35853 for (var y = (Math.floor(ext[1]/step)+1)*step; y<ext[3]; y += step) { 35854 l = ol.coordinate.splitH(coord, y, i); 35855 lines = lines.concat(l); 35856 } 35857 if (!lines.length) return null; 35858 // Order lines on segment index 35859 var mod = coord.length-1; 35860 var first = lines[0][0].index; 35861 for (k=0; l=lines[k]; k++) { 35862 lines[k][0].index = (lines[k][0].index-first+mod) % mod; 35863 lines[k][1].index = (lines[k][1].index-first+mod) % mod; 35864 } 35865 var scribble = []; 35866 while (true) { 35867 for (k=0; l=lines[k]; k++) { 35868 if (!l[0].done) break; 35869 } 35870 if (!l) break; 35871 var scrib = []; 35872 while (l) { 35873 l[0].done = true; 35874 scrib.push(l[0].pt); 35875 scrib.push(l[1].pt); 35876 var nexty = l[0].pt[1] + step; 35877 var d0 = Infinity; 35878 var l2 = null; 35879 while (lines[k]) { 35880 if (lines[k][0].pt[1] > nexty) break; 35881 if (lines[k][0].pt[1] === nexty) { 35882 var d = Math.min( 35883 (lines[k][0].index - l[0].index + mod) % mod, 35884 (l[0].index - lines[k][0].index + mod) % mod 35885 ); 35886 var d2 = Math.min( 35887 (l[1].index - l[0].index + mod) % mod, 35888 (l[0].index - l[1].index + mod) % mod 35889 ); 35890 if (d<d0 && d<d2) { 35891 d0 = d; 35892 if (!lines[k][0].done) l2 = lines[k]; 35893 else l2 = null; 35894 } 35895 } 35896 k++; 35897 } 35898 l = l2; 35899 } 35900 if (scrib.length) { 35901 scribble.push(scrib); 35902 } 35903 } 35904 // Return the scribble as MultiLineString 35905 if (!scribble.length) return null; 35906 var mline = new ol.geom.MultiLineString(scribble); 35907 mline.rotate(-angle,[0,0]); 35908 return mline.cspline({ pointsPerSeg:8, tension:.9 }); 35909}; 35910// import('ol-ext/geom/Scribble') 35911/** Geohash encoding/decoding and associated functions 35912 * (c) Chris Veness 2014-2019 / MIT Licence 35913 * https://github.com/chrisveness/latlon-geohash 35914 */ 35915ol.geohash = { 35916 // (geohash-specific) Base32 map 35917 base32: '0123456789bcdefghjkmnpqrstuvwxyz' 35918}; 35919/** Encodes latitude/longitude to geohash, either to specified precision or to automatically 35920 * evaluated precision. 35921 * @param {ol.coordinate} lonlat Longitude, Latitude in degrees. 35922 * @param {number} [precision] Number of characters in resulting geohash. 35923 * @returns {string} Geohash of supplied latitude/longitude. 35924 */ 35925ol.geohash.fromLonLat = function(lonlat, precision) { 35926 var lon = lonlat[0]; 35927 var lat = lonlat[1]; 35928 // infer precision? 35929 if (!precision) { 35930 // refine geohash until it matches precision of supplied lat/lon 35931 for (var p=1; p<=12; p++) { 35932 var hash = ol.geohash.fromLonLat([lon, lat], p); 35933 var posn = ol.geohash.toLonLat(hash); 35934 if (posn.lat==lat && posn.lon==lon) return hash; 35935 } 35936 precision = 12; // set to maximum 35937 } 35938 if (precision < 1 || precision > 12) precision = 12; 35939 var idx = 0; // index into base32 map 35940 var bit = 0; // each char holds 5 bits 35941 var evenBit = true; 35942 var geohash = ''; 35943 var latMin = -90, latMax = 90; 35944 var lonMin = -180, lonMax = 180; 35945 while (geohash.length < precision) { 35946 if (evenBit) { 35947 // bisect E-W longitude 35948 var lonMid = (lonMin + lonMax) / 2; 35949 if (lon >= lonMid) { 35950 idx = idx*2 + 1; 35951 lonMin = lonMid; 35952 } else { 35953 idx = idx*2; 35954 lonMax = lonMid; 35955 } 35956 } else { 35957 // bisect N-S latitude 35958 var latMid = (latMin + latMax) / 2; 35959 if (lat >= latMid) { 35960 idx = idx*2 + 1; 35961 latMin = latMid; 35962 } else { 35963 idx = idx*2; 35964 latMax = latMid; 35965 } 35966 } 35967 evenBit = !evenBit; 35968 if (++bit == 5) { 35969 // 5 bits gives us a character: append it and start over 35970 geohash += ol.geohash.base32.charAt(idx); 35971 bit = 0; 35972 idx = 0; 35973 } 35974 } 35975 return geohash; 35976}; 35977/** Decode geohash to latitude/longitude 35978 * (location is approximate centre of geohash cell, to reasonable precision). 35979 * @param {string} geohash - Geohash string to be converted to latitude/longitude. 35980 * @returns {ol.coordinate} 35981 */ 35982ol.geohash.toLonLat = function(geohash) { 35983 var extent = ol.geohash.getExtent(geohash);
35983 // <-- the hard work 35984 // now just determine the centre of the cell... 35985 var latMin = extent[1], lonMin = extent[0]; 35986 var latMax = extent[3], lonMax = extent[2]; 35987 // cell centre 35988 var lat = (latMin + latMax)/2; 35989 var lon = (lonMin + lonMax)/2; 35990 // round to close to centre without excessive precision: â2-log10(ΰ)â decimal places 35991 lat = lat.toFixed(Math.floor(2-Math.log(latMax-latMin)/Math.LN10)); 35992 lon = lon.toFixed(Math.floor(2-Math.log(lonMax-lonMin)/Math.LN10)); 35993 return [Number(lon), Number(lat)]; 35994}; 35995/** Returns SW/NE latitude/longitude bounds of specified geohash. 35996 * @param {string} geohash Cell that bounds are required of. 35997 * @returns {ol.extent | false} 35998 */ 35999ol.geohash.getExtent = function(geohash) { 36000 if (!geohash) return false; 36001 geohash = geohash.toLowerCase(); 36002 var evenBit = true; 36003 var latMin = -90, latMax = 90; 36004 var lonMin = -180, lonMax = 180; 36005 for (var i=0; i<geohash.length; i++) { 36006 var chr = geohash.charAt(i); 36007 var idx = ol.geohash.base32.indexOf(chr); 36008 if (idx == -1) return false; 36009 for (var n=4; n>=0; n--) { 36010 var bitN = idx >> n & 1; 36011 if (evenBit) { 36012 // longitude 36013 var lonMid = (lonMin+lonMax) / 2; 36014 if (bitN == 1) { 36015 lonMin = lonMid; 36016 } else { 36017 lonMax = lonMid; 36018 } 36019 } else { 36020 // latitude 36021 var latMid = (latMin+latMax) / 2; 36022 if (bitN == 1) { 36023 latMin = latMid; 36024 } else { 36025 latMax = latMid; 36026 } 36027 } 36028 evenBit = !evenBit; 36029 } 36030 } 36031 return [lonMin, latMin, lonMax, latMax]; 36032}; 36033/** Determines adjacent cell in given direction. 36034 * @param {string} geohash Geohash cel 36035 * @param {string} direction direction as char : N/S/E/W. 36036 * @returns {string|false} 36037 */ 36038ol.geohash.getAdjacent = function (geohash, direction) { 36039 // based on github.com/davetroy/geohash-js 36040 geohash = geohash.toLowerCase(); 36041 direction = direction.toLowerCase(); 36042 if (!geohash) return false; 36043 if ('nsew'.indexOf(direction) == -1) return false; 36044 var neighbour = { 36045 n: [ 'p0r21436x8zb9dcf5h7kjnmqesgutwvy', 'bc01fg45238967deuvhjyznpkmstqrwx' ], 36046 s: [ '14365h7k9dcfesgujnmqp0r2twvyx8zb', '238967debc01fg45kmstqrwxuvhjyznp' ], 36047 e: [ 'bc01fg45238967deuvhjyznpkmstqrwx', 'p0r21436x8zb9dcf5h7kjnmqesgutwvy' ], 36048 w: [ '238967debc01fg45kmstqrwxuvhjyznp', '14365h7k9dcfesgujnmqp0r2twvyx8zb' ], 36049 }; 36050 var border = { 36051 n: [ 'prxz', 'bcfguvyz' ], 36052 s: [ '028b', '0145hjnp' ], 36053 e: [ 'bcfguvyz', 'prxz' ], 36054 w: [ '0145hjnp', '028b' ], 36055 }; 36056 var lastCh = geohash.slice(-1); // last character of hash 36057 var parent = geohash.slice(0, -1); // hash without last character 36058 var type = geohash.length % 2; 36059 // check for edge-cases which don't share common prefix 36060 if (border[direction][type].indexOf(lastCh) != -1 && parent != '') { 36061 parent = ol.geohash.getAdjacent(parent, direction); 36062 } 36063 // append letter for direction to parent 36064 return parent + ol.geohash.base32.charAt(neighbour[direction][type].indexOf(lastCh)); 36065} 36066/** Returns all 8 adjacent cells to specified geohash. 36067 * @param {string} geohash Geohash neighbours are required of. 36068 * @returns {{n,ne,e,se,s,sw,w,nw: string}} 36069 */ 36070ol.geohash.getNeighbours = function(geohash) { 36071 return { 36072 'n': ol.geohash.getAdjacent(geohash, 'n'), 36073 'ne': ol.geohash.getAdjacent(ol.geohash.getAdjacent(geohash, 'n'), 'e'), 36074 'e': ol.geohash.getAdjacent(geohash, 'e'), 36075 'se': ol.geohash.getAdjacent(ol.geohash.getAdjacent(geohash, 's'), 'e'), 36076 's': ol.geohash.getAdjacent(geohash, 's'), 36077 'sw': ol.geohash.getAdjacent(ol.geohash.getAdjacent(geohash, 's'), 'w'), 36078 'w': ol.geohash.getAdjacent(geohash, 'w'), 36079 'nw': ol.geohash.getAdjacent(ol.geohash.getAdjacent(geohash, 'n'), 'w'), 36080 }; 36081} 36082 36083/** Compute great circle bearing of two points. 36084 * @See http://www.movable-type.co.uk/scripts/latlong.html for the original code 36085 * @param {ol.coordinate} origin origin in lonlat 36086 * @param {ol.coordinate} destination destination in lonlat 36087 * @return {number} bearing angle in radian 36088 */ 36089ol.sphere.greatCircleBearing = function(origin, destination) { 36090 var toRad = Math.PI/180; 36091 var ori = [ origin[0]*toRad, origin[1]*toRad ]; 36092 var dest = [ destination[0]*toRad, destination[1]*toRad ]; 36093 var bearing = Math.atan2( 36094 Math.sin(dest[0] - ori[0]) * Math.cos(dest[1]), 36095 Math.cos(ori[1]) * Math.sin(dest[1]) - Math.sin(ori[1]) * Math.cos(dest[1]) * Math.cos(dest[0] - ori[0]) 36096 ); 36097 return bearing; 36098}; 36099/** 36100 * Computes the destination point given an initial point, a distance and a bearing 36101 * @See http://www.movable-type.co.uk/scripts/latlong.html for the original co
36101de 36102 * @param {ol.coordinate} origin stating point in lonlat coords 36103 * @param {number} distance 36104 * @param {number} bearing bearing angle in radian 36105 * @param {*} options 36106 * @param {booelan} normalize normalize longitude beetween -180/180, deafulet true 36107 * @param {number|undefined} options.radius sphere radius, default 6371008.8 36108 */ 36109ol.sphere.computeDestinationPoint = function(origin, distance, bearing, options) { 36110 options = options || {}; 36111 var toRad = Math.PI/180; 36112 var radius = options.radius || 6371008.8; 36113 var phi1 = origin[1] * toRad; 36114 var lambda1 = origin[0] * toRad; 36115 var delta = distance / radius; 36116 var phi2 = Math.asin( 36117 Math.sin(phi1) * Math.cos(delta) + 36118 Math.cos(phi1) * Math.sin(delta) * Math.cos(bearing) 36119 ); 36120 var lambda2 = lambda1 + 36121 Math.atan2( 36122 Math.sin(bearing) * Math.sin(delta) * Math.cos(phi1), 36123 Math.cos(delta) - Math.sin(phi1) * Math.sin(phi2) 36124 ); 36125 var lon = lambda2 / toRad; 36126 // normalise to >=-180 and <=180° 36127 if (options.normalize!==false && (lon < -180 || lon > 180)) { 36128 lon = ((lon * 540) % 360) - 180; 36129 } 36130 return [ lon, phi2 / toRad ]; 36131}; 36132/** Calculate a track along the great circle given an origin and a destination 36133 * @param {ol.coordinate} origin origin in lonlat 36134 * @param {ol.coordinate} destination destination in lonlat 36135 * @param {number} distance distance between point along the track in meter, default 1km (1000) 36136 * @param {number|undefined} radius sphere radius, default 6371008.8 36137 * @return {Array<ol.coordinate>} 36138 */ 36139ol.sphere.greatCircleTrack = function(origin, destination, options) { 36140 options = options || {}; 36141 var bearing = ol.sphere.greatCircleBearing(origin, destination); 36142 var dist = ol.sphere.getDistance(origin, destination, options.radius); 36143 var distance = options.distance || 1000; 36144 var d = distance; 36145 var geom = [origin]; 36146 while (d < dist) { 36147 geom.push(ol.sphere.computeDestinationPoint(origin, d, bearing, { radius: options.radius, normalize: false })); 36148 d += distance; 36149 } 36150 var pt = ol.sphere.computeDestinationPoint(origin, dist, bearing, { radius: options.radius, normalize: false }); 36151 if (Math.abs(pt[0]-destination[0]) > 1) { 36152 if (pt[0] > destination[0]) destination[0] += 360; 36153 else destination[0] -= 360; 36154 } 36155 geom.push(destination); 36156 return geom; 36157}; 36158/** Get map scale factor 36159 * @param {ol.Map} map 36160 * @param {number} [dpi=96] dpi, default 96 36161 * @return {number} 36162 */ 36163ol.sphere.getMapScale = function (map, dpi) { 36164 var view = map.getView(); 36165 var proj = view.getProjection(); 36166 var center = view.getCenter(); 36167 var px = map.getPixelFromCoordinate(center); 36168 px[1] += 1; 36169 var coord = map.getCoordinateFromPixel(px); 36170 var d = ol.sphere.getDistance( 36171 ol.proj.transform(center, proj, 'EPSG:4326'), 36172 ol.proj.transform(coord, proj, 'EPSG:4326')); 36173 d *= (dpi||96) /.0254 36174 return d; 36175}; 36176/** Set map scale factor 36177 * @param {ol.Map} map 36178 * @param {number|string} scale the scale factor or a scale string as 1/xxx 36179 * @param {number} [dpi=96] dpi, default 96 36180 * @return {number} scale factor 36181 */ 36182ol.sphere.setMapScale = function (map, scale, dpi) { 36183 if (map && scale) { 36184 var fac = scale; 36185 if (typeof(scale)==='string') { 36186 fac = scale.split('/')[1]; 36187 if (!fac) fac = scale; 36188 fac = fac.replace(/[^\d]/g,''); 36189 fac = parseInt(fac); 36190 } 36191 if (!fac) return; 36192 // Calculate new resolution 36193 var view = map.getView(); 36194 var proj = view.getProjection(); 36195 var center = view.getCenter(); 36196 var px = map.getPixelFromCoordinate(center); 36197 px[1] += 1; 36198 var coord = map.getCoordinateFromPixel(px); 36199 var d = ol.sphere.getDistance( 36200 ol.proj.transform(center, proj, 'EPSG:4326'), 36201 ol.proj.transform(coord, proj, 'EPSG:4326')); 36202 d *= (dpi || 96) /.0254 36203 view.setResolution(view.getResolution()*fac/d); 36204 return fac; 36205 } 36206}; 36207 36208/* Copyright (c) 2015 Jean-Marc VIGLINO, 36209 released under the CeCILL-B license (French BSD license) 36210 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 36211*/ 36212/** Pulse an extent on postcompose 36213* @param {ol.coordinates} point to pulse 36214* @param {ol.pulse.options} options pulse options param 36215* @param {ol.projectionLike|undefined} options.projection projection of coords, default no transform 36216* @param {Number} options.duration animation duration in ms, default 2000 36217* @param {ol.easing} options.easing easing function, default ol.easing.upAndDown 36218* @param {ol.style.Stroke} options.style stroke style, default 2px red 36219*/ 36220ol.Map.prototype.animExtent = function(extent, options) 36221{ var listenerKey; 36222 options = options || {}; 36223 // Change to map's projection 36224 if (options.projection) 36225 { extent = ol.proj.transformExtent (extent, options.projection, this.getView().getProjection()); 36226 } 36227 // options 36228 var start = new Date().getTime(); 36229 var duration = options.duration || 1000; 36230 var easing = options.easing || ol.easing.upAndDown;
36231 var width = options.style ? options.style.getWidth() || 2 : 2; 36232 var color = options.style ? options.style.getColr() || 'red' : 'red'; 36233 // Animate function 36234 function animate(event) 36235 { var frameState = event.frameState; 36236 var ratio = frameState.pixelRatio; 36237 var elapsed = frameState.time - start; 36238 if (elapsed > duration) ol.Observable.unByKey(listenerKey); 36239 else 36240 { var elapsedRatio = elapsed / duration; 36241 var p0 = this.getPixelFromCoordinate([extent[0],extent[1]]); 36242 var p1 = this.getPixelFromCoordinate([extent[2],extent[3]]); 36243 var context = event.context; 36244 context.save(); 36245 context.scale(ratio,ratio); 36246 context.beginPath(); 36247 // var e = easing(elapsedRatio) 36248 context.globalAlpha = easing(1 - elapsedRatio); 36249 context.lineWidth = width; 36250 context.strokeStyle = color; 36251 context.rect(p0[0], p0[1], p1[0]-p0[0], p1[1]-p0[1]); 36252 context.stroke(); 36253 context.restore(); 36254 // tell OL3 to continue postcompose animation 36255 frameState.animate = true; 36256 } 36257 } 36258 // Launch animation 36259 listenerKey = this.on('postcompose', animate.bind(this)); 36260 try { this.renderSync(); } catch(e) { /* ok */ } 36261} 36262 36263/** Create a cardinal spline version of this geometry. 36264 * Original https://github.com/epistemex/cardinal-spline-js 36265 * @see https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline 36266 * 36267 * @param {} options 36268 * @param {Number} options.tension a [0,1] number / can be interpreted as the "length" of the tangent, default 0.5 36269 * @param {Number} options.resolution size of segment to split 36270 * @param {Integer} options.pointsPerSeg number of points per segment to add if no resolution is provided, default add 10 points per segment 36271 */ 36272/** Cache cspline calculation on a geometry 36273 * @param {} options 36274 * @param {Number} options.tension a [0,1] number / can be interpreted as the "length" of the tangent, default 0.5 36275 * @param {Number} options.resolution size of segment to split 36276 * @param {Integer} options.pointsPerSeg number of points per segment to add if no resolution is provided, default add 10 points per segment 36277 * @return {ol.geom.Geometry} 36278 */ 36279ol.geom.Geometry.prototype.cspline = function(options){ 36280 // Calculate cspline 36281 if (this.calcCSpline_){ 36282 if (this.csplineGeometryRevision != this.getRevision() 36283 || this.csplineOption != JSON.stringify(options)) { 36284 this.csplineGeometry_ = this.calcCSpline_(options) 36285 this.csplineGeometryRevision = this.getRevision(); 36286 this.csplineOption = JSON.stringify(options); 36287 } 36288 return this.csplineGeometry_; 36289 } else { 36290 // Default do nothing 36291 return this; 36292 } 36293}; 36294ol.geom.GeometryCollection.prototype.calcCSpline_ = function(options) { 36295 var g=[], g0=this.getGeometries(); 36296 for (var i=0; i<g0.length; i++) { 36297 g.push(g0[i].cspline(options)); 36298 } 36299 return new ol.geom.GeometryCollection(g); 36300}; 36301ol.geom.MultiLineString.prototype.calcCSpline_ = function(options) { 36302 var g=[], lines = this.getLineStrings(); 36303 for (var i=0; i<lines.length; i++) { 36304 g.push(lines[i].cspline(options).getCoordinates()); 36305 } 36306 return new ol.geom.MultiLineString(g); 36307}; 36308ol.geom.Polygon.prototype.calcCSpline_ = function(options){ 36309 var g=[], g0=this.getCoordinates(); 36310 for (var i=0; i<g0.length; i++){ 36311 g.push((new ol.geom.LineString(g0[i])).cspline(options).getCoordinates()); 36312 } 36313 return new ol.geom.Polygon(g); 36314}; 36315ol.geom.MultiPolygon.prototype.calcCSpline_ = function(options) { 36316 var g=[], g0=this.getPolygons(); 36317 for (var i=0; i<g0.length; i++) { 36318 g.push(g0[i].cspline(options).getCoordinates()); 36319 } 36320 return new ol.geom.MultiPolygon(g); 36321} 36322/** Calculate cspline on coordinates 36323 * @param {Array<ol.geom.Geometry.coordinate>} line 36324 * @param {} options 36325 * @param {Number} options.tension a [0,1] number / can be interpreted as the "length" of the tangent, default 0.5 36326 * @param {Number} options.resolution size of segment to split 36327 * @param {Integer} options.pointsPerSeg number of points per segment to add if no resolution is provided, default add 10 points per segment 36328 * @return {Array<ol.geom.Geometry.coordinate>} 36329 */ 36330ol.coordinate.cspline = function(line, options) { 36331 if (!options) options={}; 36332 var tension = typeof options.tension === "number" ? options.tension : 0.5; 36333 var length = 0; 36334 var p0 = line[0]; 36335 line.forEach(function(p) { 36336 length += ol.coordinate.dist2d(p0, p); 36337 p0 = p; 36338 }) 36339 var resolution = options.resolution || (length / line.length / (options.pointsPerSeg || 10)); 36340 var pts, res = [], // clone array 36341 x, y, // our x,y coords 36342 t1x, t2x, t1y, t2y, // tension vectors 36343 c1, c2, c3, c4, // cardinal points 36344 st, t, i; // steps based on num. of segments 36345 // clone array so we don't change the original 36346 // 36347 pts = line.slice(0); 36348 // The algorithm require a previous and next point to the actual point array. 36349 // Check if we will draw closed or open curve. 36350 // If closed, copy end points to beginning and first points to end 36351 // If open, duplicate first points to beginning, end points to end 36352 if (line.length>2 && line[0][0]==line[line.length-1][0] && line[0][1]==line[line.length-1][1]) { 36353 pts.unshift(line[line.length-2]); 36354 pts.push(line[1]); 36355 } else { 36356 pts.unshift(line[0]); 36357 pts.push(line[line.length-1]); 36358 } 36359 // ok, lets start.. 36360 function dist2d(x1, y1, x2, y2) { 36361 var dx = x2-x1; 36362 var dy = y2-y1; 36363 return Math.sqrt(dx*dx+dy*dy); 36364 }
36365 // 1. loop goes through point array 36366 // 2. loop goes through each segment between the 2 pts + 1e point before and after 36367 for (i=1; i < (pts.length - 2); i++) { 36368 var d1 = dist2d (pts[i][0], pts[i][1], pts[i+1][0], pts[i+1][1]); 36369 var numOfSegments = Math.round(d1/resolution); 36370 var d=1; 36371 if (options.normalize) { 36372 d1 = dist2d (pts[i+1][0], pts[i+1][1], pts[i-1][0], pts[i-1][1]); 36373 var d2 = dist2d (pts[i+2][0], pts[i+2][1], pts[i][0], pts[i][1]); 36374 if (d1<d2) d = d1/d2; 36375 else d = d2/d1; 36376 } 36377 // calc tension vectors 36378 t1x = (pts[i+1][0] - pts[i-1][0]) * tension *d; 36379 t2x = (pts[i+2][0] - pts[i][0]) * tension *d; 36380 t1y = (pts[i+1][1] - pts[i-1][1]) * tension *d; 36381 t2y = (pts[i+2][1] - pts[i][1]) * tension *d; 36382 for (t=0; t <= numOfSegments; t++) { 36383 // calc step 36384 st = t / numOfSegments; 36385 // calc cardinals 36386 c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1; 36387 c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2); 36388 c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st; 36389 c4 = Math.pow(st, 3) - Math.pow(st, 2); 36390 // calc x and y cords with common control vectors 36391 x = c1 * pts[i][0] + c2 * pts[i+1][0] + c3 * t1x + c4 * t2x; 36392 y = c1 * pts[i][1] + c2 * pts[i+1][1] + c3 * t1y + c4 * t2y; 36393 //store points in array 36394 if (x && y) res.push([x,y]); 36395 } 36396 } 36397 return res; 36398}; 36399/** @private */ 36400ol.geom.LineString.prototype.calcCSpline_ = function(options) { 36401 var line = this.getCoordinates(); 36402 var res = ol.coordinate.cspline(line, options) 36403 return new ol.geom.LineString(res); 36404} 36405// To use this module with ol/geom/Geometry 36406// 36407 36408/* Copyright (c) 2017 Jean-Marc VIGLINO, 36409 released under the CeCILL-B license (French BSD license) 36410 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 36411*/ 36412/** @typedef {'pointy' | 'flat'} HexagonLayout 36413 * Layout of a Hexagon. Flat means the bottom part of the hexagon is flat. 36414 */ 36415/** 36416* Hexagonal grids 36417* @classdesc ol.HexGrid is a class to compute hexagonal grids 36418* @see http://www.redblobgames.com/grids/hexagons 36419* 36420* @constructor ol.HexGrid 36421* @extends {ol.Object} 36422* @param {Object} [options] 36423* @param {number} [options.size] size of the exagon in map units, default 80000 36424* @param {ol.Coordinate} [options.origin] orgin of the grid, default [0,0] 36425* @param {HexagonLayout} [options.layout] grid layout, default pointy 36426*/ 36427ol.HexGrid = function (options) 36428{ options = options || {}; 36429 ol.Object.call (this, options); 36430 // Options 36431 this.size_ = options.size||80000; 36432 this.origin_ = options.origin || [0,0]; 36433 this.layout_ = this.layout[options.layout] || this.layout.pointy; 36434}; 36435ol.ext.inherits(ol.HexGrid, ol.Object); 36436/** Layout 36437*/ 36438ol.HexGrid.prototype.layout = 36439{ pointy: 36440 [ Math.sqrt(3), Math.sqrt(3)/2, 0, 3/2, 36441 Math.sqrt(3)/3, -1/3, 0, 2/3, 36442 // corners 36443 Math.cos(Math.PI / 180 * (60 * 0 + 30)), Math.sin(Math.PI / 180 * (60 * 0 + 30)), 36444 Math.cos(Math.PI / 180 * (60 * 1 + 30)), Math.sin(Math.PI / 180 * (60 * 1 + 30)), 36445 Math.cos(Math.PI / 180 * (60 * 2 + 30)), Math.sin(Math.PI / 180 * (60 * 2 + 30)), 36446 Math.cos(Math.PI / 180 * (60 * 3 + 30)), Math.sin(Math.PI / 180 * (60 * 3 + 30)), 36447 Math.cos(Math.PI / 180 * (60 * 4 + 30)), Math.sin(Math.PI / 180 * (60 * 4 + 30)), 36448 Math.cos(Math.PI / 180 * (60 * 5 + 30)), Math.sin(Math.PI / 180 * (60 * 5 + 30)) 36449 ], 36450 flat: 36451 [ 3/2, 0, Math.sqrt(3)/2, Math.sqrt(3), 2/3, 36452 0, -1/3, Math.sqrt(3) / 3, 36453 // corners 36454 Math.cos(Math.PI / 180 * (60 * 0)), Math.sin(Math.PI / 180 * (60 * 0)), 36455 Math.cos(Math.PI / 180 * (60 * 1)), Math.sin(Math.PI / 180 * (60 * 1)), 36456 Math.cos(Math.PI / 180 * (60 * 2)), Math.sin(Math.PI / 180 * (60 * 2)), 36457 Math.cos(Math.PI / 180 * (60 * 3)), Math.sin(Math.PI / 180 * (60 * 3)), 36458 Math.cos(Math.PI / 180 * (60 * 4)), Math.sin(Math.PI / 180 * (60 * 4)), 36459 Math.cos(Math.PI / 180 * (60 * 5)), Math.sin(Math.PI / 180 * (60 * 5)) 36460 ] 36461}; 36462/** Set layout 36463* @param {HexagonLayout | undefined} layout name, default pointy 36464*/ 36465ol.HexGrid.prototype.setLayout = function (layout) 36466{ this.layout_ = this.layout[layout] || this.layout.pointy; 36467 this.changed(); 36468} 36469/** Get layout 36470* @return {HexagonLayout} layout name 36471*/ 36472ol.HexGrid.prototype.getLayout = function () 36473{ return (this.layout_[9]!=0 ? 'pointy' : 'flat'); 36474} 36475/** Set hexagon origin 36476* @param {ol.Coordinate} coord origin 36477*/ 36478ol.HexGrid.prototype.setOrigin = function (coord) 36479{ this.origin_ = coord; 36480 this.changed(); 36481} 36482/** Get hexagon origin 36483* @return {ol.Coordinate} coord origin 36484*/ 36485ol.HexGrid.prototype.getOrigin = function () 36486{ return this.origin_; 36487} 36488/** Set hexagon size 36489* @param {number} hexagon size 36490*/ 36491ol.HexGrid.prototype.setSize = function (s) { 36492 this.size_ = s || 80000; 36493 this.changed(); 36494} 36495/** Get hexagon size 36496* @return {number} hexagon size 36497*/ 36498ol.HexGrid.prototype.getSize = function () { 36499 return this.size_; 36500} 36501/** Convert cube to axial coords 36502* @param {ol.Coordinate} c cube coordinate 36503* @return {ol.Coordinate} axial coordinate 36504*/ 36505ol.HexGrid.prototype.cube2hex = function (c) 36506{ return [c[0], c[2]]; 36507};
36508/** Convert axial to cube coords 36509* @param {ol.Coordinate} h axial coordinate 36510* @return {ol.Coordinate} cube coordinate 36511*/ 36512ol.HexGrid.prototype.hex2cube = function(h) 36513{ return [h[0], -h[0]-h[1], h[1]]; 36514}; 36515/** Convert offset to axial coords 36516* @param {ol.Coordinate} h axial coordinate 36517* @return {ol.Coordinate} offset coordinate 36518*/ 36519ol.HexGrid.prototype.hex2offset = function (h) 36520{ if (this.layout_[9]) return [ h[0] + (h[1] - (h[1]&1)) / 2, h[1] ]; 36521 else return [ h[0], h[1] + (h[0] + (h[0]&1)) / 2 ]; 36522} 36523/** Convert axial to offset coords 36524* @param {ol.Coordinate} o offset coordinate 36525* @return {ol.Coordinate} axial coordinate 36526*/ 36527ol.HexGrid.prototype.offset2hex = function(o) 36528{ if (this.layout_[9]) return [ o[0] - (o[1] - (o[1]&1)) / 2, o[1] ]; 36529 else return [ o[0], o[1] - (o[0] + (o[0]&1)) / 2 ]; 36530} 36531/** Convert offset to cube coords 36532* @param {ol.Coordinate} c cube coordinate 36533* @return {ol.Coordinate} offset coordinate 36534* / 36535ol.HexGrid.prototype.cube2offset = function(c) 36536{ return hex2offset(cube2hex(c)); 36537}; 36538/** Convert cube to offset coords 36539* @param {ol.Coordinate} o offset coordinate 36540* @return {ol.Coordinate} cube coordinate 36541* / 36542ol.HexGrid.prototype.offset2cube = function (o) 36543{ return hex2cube(offset2Hex(o)); 36544}; 36545/** Round cube coords 36546* @param {ol.Coordinate} h cube coordinate 36547* @return {ol.Coordinate} rounded cube coordinate 36548*/ 36549ol.HexGrid.prototype.cube_round = function(h) 36550{ var rx = Math.round(h[0]) 36551 var ry = Math.round(h[1]) 36552 var rz = Math.round(h[2]) 36553 var x_diff = Math.abs(rx - h[0]) 36554 var y_diff = Math.abs(ry - h[1]) 36555 var z_diff = Math.abs(rz - h[2]) 36556 if (x_diff > y_diff && x_diff > z_diff) rx = -ry-rz 36557 else if (y_diff > z_diff) ry = -rx-rz 36558 else rz = -rx-ry 36559 return [rx, ry, rz]; 36560}; 36561/** Round axial coords 36562* @param {ol.Coordinate} h axial coordinate 36563* @return {ol.Coordinate} rounded axial coordinate 36564*/ 36565ol.HexGrid.prototype.hex_round = function(h) 36566{ return this.cube2hex( this.cube_round( this.hex2cube(h )) ); 36567}; 36568/** Get hexagon corners 36569*/ 36570ol.HexGrid.prototype.hex_corner = function(center, size, i) 36571{ return [ center[0] + size * this.layout_[8+(2*(i%6))], center[1] + size * this.layout_[9+(2*(i%6))]]; 36572}; 36573/** Get hexagon coordinates at a coordinate 36574* @param {ol.Coordinate} coord 36575* @return {Arrary<ol.Coordinate>} 36576*/ 36577ol.HexGrid.prototype.getHexagonAtCoord = function (coord) 36578{ return (this.getHexagon(this.coord2hex(coord))); 36579}; 36580/** Get hexagon coordinates at hex 36581* @param {ol.Coordinate} hex 36582* @return {Arrary<ol.Coordinate>} 36583*/ 36584ol.HexGrid.prototype.getHexagon = function (hex) 36585{ var p = []; 36586 var c = this.hex2coord(hex); 36587 for (var i=0; i<=7; i++) 36588 { p.push(this.hex_corner(c, this.size_, i, this.layout_[8])); 36589 } 36590 return p; 36591}; 36592/** Convert hex to coord 36593* @param {ol.hex} hex 36594* @return {ol.Coordinate} 36595*/ 36596ol.HexGrid.prototype.hex2coord = function (hex) 36597{ return [ 36598 this.origin_[0] + this.size_ * (this.layout_[0] * hex[0] + this.layout_[1] * hex[1]), 36599 this.origin_[1] + this.size_ * (this.layout_[2] * hex[0] + this.layout_[3] * hex[1]) 36600 ]; 36601}; 36602/** Convert coord to hex 36603* @param {ol.Coordinate} coord 36604* @return {ol.hex} 36605*/ 36606ol.HexGrid.prototype.coord2hex = function (coord) 36607{ var c = [ (coord[0]-this.origin_[0]) / this.size_, (coord[1]-this.origin_[1]) / this.size_ ]; 36608 var q = this.layout_[4] * c[0] + this.layout_[5] * c[1]; 36609 var r = this.layout_[6] * c[0] + this.layout_[7] * c[1]; 36610 return this.hex_round([q, r]); 36611}; 36612/** Calculate distance between to hexagon (number of cube) 36613* @param {ol.Coordinate} a first cube coord 36614* @param {ol.Coordinate} a second cube coord 36615* @return {number} distance 36616*/ 36617ol.HexGrid.prototype.cube_distance = function (a, b) 36618{ //return ( (Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2])) / 2 ); 36619 return ( Math.max (Math.abs(a[0] - b[0]), Math.abs(a[1] - b[1]), Math.abs(a[2] - b[2])) ); 36620}; 36621(function(){ 36622/** Line interpolation 36623*/ 36624function lerp(a, b, t) 36625{ // for floats 36626 return a + (b - a) * t; 36627} 36628function cube_lerp(a, b, t) 36629{ // for hexes 36630 return [ 36631 lerp (a[0]+1e-6, b[0], t), 36632 lerp (a[1]+1e-6, b[1], t), 36633 lerp (a[2]+1e-6, b[2], t) 36634 ]; 36635} 36636/** Calculate line between to hexagon 36637* @param {ol.Coordinate} a first cube coord 36638* @param {ol.Coordinate} b second cube coord 36639* @return {Array<ol.Coordinate>} array of cube coordinates 36640*/ 36641ol.HexGrid.prototype.cube_line = function (a, b) 36642{ var d = this.cube_distance(a, b); 36643 if (!d) return [a]; 36644 var results = [] 36645 for (var i=0; i<=d; i++)
36646 { results.push ( this.cube_round ( cube_lerp(a, b, i/d) ) ); 36647 } 36648 return results; 36649}; 36650})(); 36651ol.HexGrid.prototype.neighbors = 36652{ 'cube': [ [+1, -1, 0], [+1, 0, -1], [0, +1, -1], [-1, +1, 0], [-1, 0, +1], [0, -1, +1] ], 36653 'hex': [ [+1, 0], [+1, -1], [0, -1], [-1, 0], [-1, +1], [0, +1] ] 36654}; 36655/** Get the neighbors for an hexagon 36656* @param {ol.Coordinate} h axial coord 36657* @param {number} direction 36658* @return { ol.Coordinate | Array<ol.Coordinate> } neighbor || array of neighbors 36659*/ 36660ol.HexGrid.prototype.hex_neighbors = function (h, d) 36661{ if (d!==undefined) 36662 { return [ h[0] + this.neighbors.hex[d%6][0], h[1] + this.neighbors.hex[d%6][1] ]; 36663 } 36664 else 36665 { var n = []; 36666 for (d=0; d<6; d++) 36667 { n.push ([ h[0] + this.neighbors.hex[d][0], h[1] + this.neighbors.hex[d][1] ]); 36668 } 36669 return n; 36670 } 36671}; 36672/** Get the neighbors for an hexagon 36673* @param {ol.Coordinate} c cube coord 36674* @param {number} direction 36675* @return { ol.Coordinate | Array<ol.Coordinate> } neighbor || array of neighbors 36676*/ 36677ol.HexGrid.prototype.cube_neighbors = function (c, d) 36678{ if (d!==undefined) 36679 { 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] ]; 36680 } 36681 else 36682 { var n = []; 36683 for (d=0; d<6; d++) 36684 { n.push ([ c[0] + this.neighbors.cube[d][0], c[1] + this.neighbors.cube[d][1], c[2] + this.neighbors.cube[d][2] ]); 36685 } 36686 for (d=0; d<6; d++) n[d] = this.cube2hex(n[d]) 36687 return n; 36688 } 36689}; 36690 36691/* Copyright (c) 2017 Jean-Marc VIGLINO, 36692 released under the CeCILL-B license (French BSD license) 36693 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 36694*/ 36695/** 36696 * French INSEE grids 36697 * @classdesc a class to compute French INSEE grids, ie. fix area (200x200m) square grid, 36698 * based appon EPSG:3035 36699 * 36700 * @requires proj4 36701 * @constructor 36702 * @extends {ol.Object} 36703 * @param {Object} [options] 36704 * @param {number} [options.size] size grid size in meter, default 200 (200x200m) 36705 */ 36706ol.InseeGrid = function (options) { 36707 options = options || {}; 36708 // Define EPSG:3035 if none 36709 if (!proj4.defs["EPSG:3035"]) { 36710 proj4.defs("EPSG:3035","+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs"); 36711 ol.proj.proj4.register(proj4); 36712 } 36713 ol.Object.call (this, options); 36714 // Options 36715 var size = Math.max(200, Math.round((options.size||0)/200) * 200); 36716 this.set('size', size); 36717}; 36718ol.ext.inherits (ol.InseeGrid, ol.Object); 36719/** Grid extent (in EPSG:3035) 36720 */ 36721ol.InseeGrid.extent = [3200000,2000000,4300000,3140000]; 36722/** Get the grid extent 36723 * @param {ol.proj.ProjLike} [proj='EPSG:3857'] 36724 */ 36725ol.InseeGrid.prototype.getExtent = function (proj) { 36726 return ol.proj.transformExtent(ol.InseeGrid.extent, proj||'EPSG:3035', 'EPSG:3857') 36727}; 36728/** Get grid geom at coord 36729 * @param {ol.Coordinate} coord 36730 * @param {ol.proj.ProjLike} [proj='EPSG:3857'] 36731 */ 36732ol.InseeGrid.prototype.getGridAtCoordinate = function (coord, proj) { 36733 var c = ol.proj.transform(coord, proj||'EPSG:3857', 'EPSG:3035') 36734 var s = this.get('size'); 36735 var x = Math.floor(c[0]/s) * s;
36736 var y = Math.floor(c[1]/s) * s; 36737 var geom = new ol.geom.Polygon([[[x,y],[x+s,y],[x+s,y+s],[x,y+s],[x,y]]]); 36738 geom.transform('EPSG:3035', proj||'EPSG:3857'); 36739 return geom; 36740}; 36741 36742/* Copyright (c) 2015 Jean-Marc VIGLINO, 36743 released under the CeCILL-B license (French BSD license) 36744 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 36745*/ 36746/** Show a markup a point on postcompose 36747* @deprecated use map.animateFeature instead 36748* @param {ol.coordinates} point to pulse 36749* @param {ol.markup.options} pulse options param 36750* - projection {ol.projection|String|undefined} projection of coords, default none 36751* - delay {Number} delay before mark fadeout 36752* - maxZoom {Number} zoom when mark fadeout 36753* - style {ol.style.Image|ol.style.Style|Array<ol.style.Style>} Image to draw as markup, default red circle 36754* @return Unique key for the listener with a stop function to stop animation 36755*/ 36756ol.Map.prototype.markup = function(coords, options) 36757{ var listenerKey; 36758 var self = this; 36759 options = options || {}; 36760 // Change to map's projection 36761 if (options.projection) 36762 { coords = ol.proj.transform(coords, options.projection, this.getView().getProjection()); 36763 } 36764 // options 36765 var start = new Date().getTime(); 36766 var delay = options.delay || 3000; 36767 var duration = 1000; 36768 var maxZoom = options.maxZoom || 100; 36769 var easing = ol.easing.easeOut; 36770 var style = options.style; 36771 if (!style) style = new ol.style.Circle({ radius:10, stroke:new ol.style.Stroke({color:'red', width:2 }) }); 36772 if (style instanceof ol.style.Image) style = new ol.style.Style({ image: style }); 36773 if (!(style instanceof Array)) style = [style]; 36774 // Animate function 36775 function animate(event) 36776 { var frameState = event.frameState; 36777 var elapsed = frameState.time - start; 36778 if (elapsed > delay+duration) 36779 { ol.Observable.unByKey(listenerKey); 36780 listenerKey = null; 36781 } 36782 else 36783 { if (delay>elapsed && this.getView().getZoom()>maxZoom) delay = elapsed; 36784 var ratio = frameState.pixelRatio; 36785 var elapsedRatio = 0; 36786 if (elapsed > delay) elapsedRatio = (elapsed-delay) / duration; 36787 var context = event.context; 36788 context.save(); 36789 context.beginPath(); 36790 context.globalAlpha = easing(1 - elapsedRatio); 36791 for (var i=0; i<style.length; i++) 36792 { var imgs = style[i].getImage(); 36793 var sc = imgs.getScale(); 36794 imgs.setScale(sc*ratio); 36795 event.vectorContext.setStyle(style[i]); 36796 event.vectorContext.drawGeometry(new ol.geom.Point(coords)); 36797 imgs.setScale(sc); 36798 } 36799 context.restore(); 36800 // tell OL3 to continue postcompose animation 36801 if (elapsed >= delay) frameState.animate = true; 36802 } 36803 } 36804 setTimeout (function() { 36805 if (listenerKey) { 36806 try { self.renderSync(); } catch(e) { /* ok */ } 36807 } 36808 }, delay); 36809 // Launch animation 36810 listenerKey = this.on('postcompose', animate.bind(this)); 36811 try { this.renderSync(); } catch(e) { /* ok */ } 36812 listenerKey.stop = function()
36813 { delay = duration = 0; 36814 try { this.target.renderSync(); } catch(e) { /* ok */ } 36815 }; 36816 return listenerKey; 36817} 36818/* Copyright (c) 2015 Jean-Marc VIGLINO, 36819 released under the CeCILL-B license (French BSD license) 36820 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 36821*/ 36822/** Ordering function for ol.layer.Vector renderOrder parameter 36823* ol.ordering.fn (options) 36824* It will return an ordering function (f0,f1) 36825* @namespace 36826*/ 36827ol.ordering = {}; 36828/** y-Ordering 36829* @return ordering function (f0,f1) 36830*/ 36831ol.ordering.yOrdering = function() 36832{ return function(f0,f1) 36833 { return f1.getGeometry().getExtent()[1] - f0.getGeometry().getExtent()[1] ; 36834 }; 36835}; 36836/** Order with a feature attribute 36837 * @param options 36838 * @param {string} options.attribute ordering attribute, default zIndex 36839 * @param {function} options.equalFn ordering function for equal values 36840 * @return ordering function (f0,f1) 36841 */ 36842ol.ordering.zIndex = function(options) 36843{ if (!options) options = {}; 36844 var attr = options.attribute || 'zIndex'; 36845 if (options.equalFn) 36846 { return function(f0,f1) 36847 { if (f0.get(attr) == f1.get(attr)) return options.equalFn(f0,f1); 36848 else return f0.get(attr) < f1.get(attr) ? 1:-1; 36849 }; 36850 } 36851 else 36852 { return function(f0,f1) 36853 { if (f0.get(attr) == f1.get(attr)) return 0; 36854 else return f0.get(attr) < f1.get(attr) ? 1:-1; 36855 }; 36856 } 36857}; 36858 36859/* Copyright (c) 2015 Jean-Marc VIGLINO, 36860 released under the CeCILL-B license (French BSD license) 36861 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 36862*/ 36863/** Pulse a point on postcompose 36864* @deprecated use map.animateFeature instead 36865* @param {ol.coordinates} point to pulse 36866* @param {ol.pulse.options} pulse options param 36867* - projection {ol.projection||String} projection of coords 36868* - duration {Number} animation duration in ms, default 3000 36869* - amplitude {Number} movement amplitude 0: none - 0.5: start at 0.5*radius of the image - 1: max, default 1 36870* - easing {ol.easing} easing function, default ol.easing.easeOut 36871* - style {ol.style.Image|ol.style.Style|Array<ol.style.Style>} Image to draw as markup, default red circle 36872*/ 36873ol.Map.prototype.pulse = function(coords, options) 36874{ var listenerKey; 36875 options = options || {}; 36876 // Change to map's projection 36877 if (options.projection) 36878 { coords = ol.proj.transform(coords, options.projection, this.getView().getProjection()); 36879 } 36880 // options 36881 var start = new Date().getTime(); 36882 var duration = options.duration || 3000; 36883 var easing = options.easing || ol.easing.easeOut; 36884 var style = options.style; 36885 if (!style) style = new ol.style.Circle({ radius:30, stroke:new ol.style.Stroke({color:'red', width:2 }) }); 36886 if (style instanceof ol.style.Image) style = new ol.style.Style({ image: style }); 36887 if (!(style instanceof Array)) style = [style]; 36888 var amplitude = options.amplitude || 1; 36889 if (amplitude<0) amplitude=0; 36890 var maxRadius = options.radius || 15; 36891 if (maxRadius<0) maxRadius = 5; 36892 /* 36893 var minRadius = maxRadius - (options.amplitude || maxRadius); //options.minRadius || 0; 36894 var width = options.lineWidth || 2; 36895 var color = options.color || 'red'; 36896 console.log("pulse") 36897 */ 36898 // Animate function 36899 function animate(event) 36900 { var frameState = event.frameState; 36901 var ratio = frameState.pixelRatio; 36902 var elapsed = frameState.time - start; 36903 if (elapsed > duration) ol.Observable.unByKey(listenerKey); 36904 else 36905 { var elapsedRatio = elapsed / duration; 36906 var context = event.context; 36907 context.save(); 36908 context.beginPath(); 36909 var e = easing(elapsedRatio) 36910 context.globalAlpha = easing(1 - elapsedRatio); 36911 console.log("anim") 36912 for (var i=0; i<style.length; i++) 36913 { var imgs = style[i].getImage(); 36914 var sc = imgs.getScale(); 36915 imgs.setScale(ratio*sc*(1+amplitude*(e-1))); 36916 event.vectorContext.setStyle(style[i]); 36917 event.vectorContext.drawGeometry(new ol.geom.Point(coords)); 36918 imgs.setScale(sc); 36919 } 36920 context.restore(); 36921 // tell OL3 to continue postcompose animation 36922 frameState.animate = true; 36923 } 36924 } 36925 // Launch animation 36926 listenerKey = this.on('postcompose', animate.bind(this)); 36927 try { this.renderSync(); } catch(e) { /* ok */ } 36928} 36929 36930/* Copyright (c) 2015 Jean-Marc VIGLINO, 36931 released under the CeCILL-B license (French BSD license)
36932 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 36933* 36934* Add a chart style to display charts (pies or bars) on a map 36935*/ 36936/** 36937 * @requires ol.style.Circle 36938 * @requires ol.structs.IHasChecksum 36939 */ 36940/** 36941 * @classdesc 36942 * Set chart style for vector features. 36943 * 36944 * @constructor 36945 * @param {} options 36946 * @param {String} options.type Chart type: pie,pie3D, donut or bar 36947 * @param {number} options.radius Chart radius/size, default 20 36948 * @param {number} options.rotation Rotation in radians (positive rotation clockwise). Default is 0. 36949 * @param {bool} options.snapToPixel use integral numbers of pixels, default true 36950 * @param {_ol_style_Stroke_} options.stroke stroke style 36951 * @param {String|Array<ol.color>} options.colors predefined color set "classic","dark","pale","pastel","neon" / array of color string, default classic 36952 * @param {Array<number>} options.displacement 36953 * @param {number} options.offsetX X offset in px (deprecated, use displacement) 36954 * @param {number} options.offsetY Y offset in px (deprecated, use displacement) 36955 * @param {number} options.animation step in an animation sequence [0,1] 36956 * @param {number} options.max maximum value for bar chart 36957 * @see [Statistic charts example](../../examples/style/map.style.chart.html) 36958 * @extends {ol.style.RegularShape} 36959 * @implements {ol.structs.IHasChecksum} 36960 * @api 36961 */ 36962ol.style.Chart = function(opt_options) { 36963 var options = opt_options || {}; 36964 var strokeWidth = 0; 36965 if (opt_options.stroke) strokeWidth = opt_options.stroke.getWidth(); 36966 ol.style.RegularShape.call (this, { 36967 radius: options.radius + strokeWidth, 36968 fill: new ol.style.Fill({color: [0,0,0]}), 36969 rotation: options.rotation, 36970 displacement: options.displacement, 36971 snapToPixel: options.snapToPixel 36972 }); 36973 if (options.scale) this.setScale(options.scale); 36974 this._stroke = options.stroke; 36975 this._radius = options.radius || 20; 36976 this._donutratio = options.donutRatio || 0.5; 36977 this._type = options.type; 36978 this._offset = [options.offsetX ? options.offsetX : 0, options.offsetY ? options.offsetY : 0]; 36979 this._animation = (typeof(options.animation) == 'number') ? { animate:true, step:options.animation } : this._animation = { animate:false, step:1 }; 36980 this._max = options.max; 36981 this._data = options.data; 36982 if (options.colors instanceof Array) { 36983 this._colors = options.colors; 36984 } else { 36985 this._colors = ol.style.Chart.colors[options.colors]; 36986 if(!this._colors) this._colors = ol.style.Chart.colors.classic; 36987 } 36988 this.renderChart_(); 36989}; 36990ol.ext.inherits(ol.style.Chart, ol.style.RegularShape); 36991/** Default color set: classic, dark, pale, pastel, neon 36992*/ 36993ol.style.Chart.colors = { 36994 "classic": ["#ffa500","blue","red","green","cyan","magenta","yellow","#0f0"], 36995 "dark": ["#960","#003","#900","#060","#099","#909","#990","#090"], 36996 "pale": ["#fd0","#369","#f64","#3b7","#880","#b5d","#666"], 36997 "pastel": ["#fb4","#79c","#f66","#7d7","#acc","#fdd","#ff9","#b9b"], 36998 "neon": ["#ff0","#0ff","#0f0","#f0f","#f00","#00f"] 36999} 37000/** 37001 * Clones the style. 37002 * @return {ol.style.Chart} 37003 */ 37004ol.style.Chart.prototype.clone = function() { 37005 var s = new ol.style.Chart({ 37006 type: this._type, 37007 radius: this._radius, 37008 rotation: this.getRotation(), 37009 scale: this.getScale(), 37010 data: this.getData(),
37011 snapToPixel: this.getSnapToPixel ? this.getSnapToPixel() : false, 37012 stroke: this._stroke, 37013 colors: this._colors, 37014 offsetX: this._offset[0], 37015 offsetY: this._offset[1], 37016 animation: this._animation 37017 }); 37018 s.setScale(this.getScale()); 37019 s.setOpacity(this.getOpacity()); 37020 return s; 37021}; 37022/** Get data associatied with the chart 37023*/ 37024ol.style.Chart.prototype.getData = function() { 37025 return this._data; 37026} 37027/** Set data associatied with the chart 37028* @param {Array<number>} 37029*/ 37030ol.style.Chart.prototype.setData = function(data) { 37031 this._data = data; 37032 this.renderChart_(); 37033} 37034/** Get symbol radius 37035*/ 37036ol.style.Chart.prototype.getRadius = function() { 37037 return this._radius; 37038} 37039/** Set symbol radius 37040* @param {number} symbol radius 37041* @param {number} donut ratio 37042*/ 37043ol.style.Chart.prototype.setRadius = function(radius, ratio) { 37044 this._radius = radius; 37045 this.donuratio_ = ratio || this.donuratio_; 37046 this.renderChart_(); 37047} 37048/** Set animation step 37049* @param {false|number} false to stop animation or the step of the animation [0,1] 37050*/ 37051ol.style.Chart.prototype.setAnimation = function(step) { 37052 if (step===false) { 37053 if (this._animation.animate == false) return; 37054 this._animation.animate = false; 37055 } else { 37056 if (this._animation.step == step) return; 37057 this._animation.animate = true; 37058 this._animation.step = step; 37059 } 37060 this.renderChart_(); 37061} 37062/** @private 37063*/ 37064ol.style.Chart.prototype.renderChart_ = function(pixelratio) { 37065 if (!pixelratio) { 37066 if (this.getPixelRatio) { 37067 pixelratio = window.devicePixelRatio; 37068 this.renderChart_(pixelratio); 37069 if (this.getPixelRatio && pixelratio!==1) this.renderChart_(1); 37070 } else { 37071 this.renderChart_(1); 37072 } 37073 return; 37074 } 37075 var strokeStyle; 37076 var strokeWidth = 0; 37077 if (this._stroke) { 37078 strokeStyle = ol.color.asString(this._stroke.getColor()); 37079 strokeWidth = this._stroke.getWidth(); 37080 } 37081 // no atlas manager is used, create a new canvas 37082 var canvas = this.getImage(pixelratio); 37083 // draw the circle on the canvas 37084 var context = (canvas.getContext('2d')); 37085 context.save(); 37086 // reset transform 37087 context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0); 37088 context.clearRect(0, 0, canvas.width, canvas.height); 37089 context.lineJoin = 'round'; 37090 var sum=0; 37091 var i, c; 37092 for (i=0; i<this._data.length; i++) { 37093 sum += this._data[i]; 37094 } 37095 // then move to (x, y) 37096 context.translate(0,0); 37097 var step = this._animation.animate ? this._animation.step : 1; 37098 //console.log(this._animation.step) 37099 // Draw pie 37100 switch (this._type) { 37101 case "donut": 37102 case "pie3D": 37103 case "pie": { 37104 var a, a0 = Math.PI * (step-1.5); 37105 c = canvas.width/2/pixelratio; 37106 context.strokeStyle = strokeStyle; 37107 context.lineWidth = strokeWidth; 37108 context.save(); 37109 if (this._type=="pie3D") { 37110 context.translate(0, c*0.3); 37111 context.scale(1, 0.7); 37112 context.beginPath(); 37113 context.fillStyle = "#369"; 37114 context.arc ( c, c*1.4, this._radius *step, 0, 2*Math.PI); 37115 context.fill(); 37116 context.stroke(); 37117 } 37118 if (this._type=="donut") { 37119 context.save(); 37120 context.beginPath(); 37121 context.rect ( 0,0,2*c,2*c ); 37122 context.arc ( c, c, this._radius *step *this._donutratio, 0, 2*Math.PI); 37123 context.clip("evenodd"); 37124 } 37125 for (i=0; i<this._data.length; i++) { 37126 context.beginPath(); 37127 context.moveTo(c,c); 37128 context.fillStyle = this._colors[i%this._colors.length]; 37129 a = a0 + 2*Math.PI*this._data[i]/sum *step; 37130 context.arc ( c, c, this._radius *step, a0, a); 37131 context.closePath(); 37132 context.fill(); 37133 context.stroke(); 37134 a0 = a; 37135 } 37136 if (this._type=="donut") { 37137 context.restore(); 37138 context.beginPath(); 37139 context.strokeStyle = strokeStyle; 37140 context.lineWidth = strokeWidth; 37141 context.arc ( c, c, this._radius *step *this._donutratio, Math.PI * (step-1.5), a0); 37142 context.stroke(); 37143 } 37144 context.restore(); 37145 break; 37146 } 37147 case "bar": 37148 default: { 37149 var max=0; 37150 if (this._max) { 37151 max = this._max; 37152 } else { 37153 for (i=0; i<this._data.length; i++) { 37154 if (max < this._data[i]) max = this._data[i]; 37155 } 37156 } 37157 var s = Math.min(5,2*this._radius/this._data.length); 37158 c = canvas.width/2/pixelratio; 37159 var b = canvas.width/pixelratio - strokeWidth; 37160 var x, x0 = c - this._data.length*s/2 37161 context.strokeStyle = strokeStyle; 37162 context.lineWidth = strokeWidth; 37163 for (i=0; i<this._data.length; i++) { 37164 context.beginPath(); 37165 context.fillStyle = this._colors[i%this._colors.length]; 37166 x = x0 + s; 37167 var h = this._data[i]/max*2*this._radius *step; 37168 context.rect ( x0, b-h, s, h); 37169 //console.log ( x0+", "+(b-this._data[i]/max*2*this._radius)+", "+x+", "+b); 37170 context.closePath(); 37171 context.fill(); 37172 context.stroke(); 37173 x0 = x; 37174 } 37175 } 37176 }
37177 context.restore(); 37178 // Set Anchor 37179 if (!this.setDisplacement) { 37180 var anchor = this.getAnchor(); 37181 anchor[0] = c - this._offset[0]; 37182 anchor[1] = c - this._offset[1]; 37183 } 37184}; 37185 37186/* Copyright (c) 2016 Jean-Marc VIGLINO, 37187 released under the CeCILL-B license (French BSD license) 37188 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 37189*/ 37190/** 37191 * @classdesc 37192 * Fill style with named pattern 37193 * 37194 * @constructor 37195 * @param {olx.style.FillPatternOption=} options 37196 * @param {ol.style.Image|undefined} options.image an image pattern, image must be preloaded to draw on first call 37197* @param {number|undefined} options.opacity opacity with image pattern, default:1 37198* @param {olx.style.fillPattern} options.pattern pattern name (override by image option) 37199* @param {ol.color} options.color pattern color 37200* @param {ol.style.Fill} options.fill fill color (background) 37201* @param {number} options.offset pattern offset for hash/dot/circle/cross pattern 37202* @param {number} options.size line size for hash/dot/circle/cross pattern 37203* @param {number} options.spacing spacing for hash/dot/circle/cross pattern 37204* @param {number|bool} options.angle angle for hash pattern / true for 45deg dot/circle/cross 37205* @param {number} options.scale pattern scale 37206* @extends {ol.style.Fill} 37207* @implements {ol.structs.IHasChecksum} 37208* @api 37209*/ 37210ol.style.FillPattern = function(options) { 37211 if (!options) options = {}; 37212 var pattern; 37213 var canvas = this.canvas_ = document.createElement('canvas'); 37214 var scale = Number(options.scale)>0 ? Number(options.scale) : 1; 37215 var ratio = scale*ol.has.DEVICE_PIXEL_RATIO || ol.has.DEVICE_PIXEL_RATIO; 37216 var ctx = canvas.getContext('2d'); 37217 if (options.image) { 37218 options.image.load(); 37219 var i; 37220 var img = options.image.getImage(); 37221 if (img.width) { 37222 canvas.width = Math.round(img.width *ratio); 37223 canvas.height = Math.round(img.height *ratio); 37224 ctx.globalAlpha = typeof(options.opacity) == 'number' ? options.opacity:1; 37225 ctx.drawImage(img, 0,0, img.width, img.height, 0, 0, canvas.width, canvas.height); 37226 pattern = ctx.createPattern(canvas, 'repeat'); 37227 } else { 37228 var self = this; 37229 pattern = [0,0,0,0]; 37230 img.onload = function () { 37231 canvas.width = Math.round(img.width *ratio); 37232 canvas.height = Math.round(img.height *ratio); 37233 ctx.globalAlpha = typeof(options.opacity) == 'number' ? options.opacity:1; 37234 ctx.drawImage(img, 0,0, img.width, img.height, 0, 0, canvas.width, canvas.height); 37235 pattern = ctx.createPattern(canvas, 'repeat'); 37236 self.setColor(pattern); 37237 } 37238 } 37239 } else { 37240 var pat = this.getPattern_(options); 37241 canvas.width = Math.round(pat.width *ratio); 37242 canvas.height = Math.round(pat.height *ratio); 37243 ctx.beginPath(); 37244 if (options.fill) { 37245 ctx.fillStyle = ol.color.asString(options.fill.getColor()); 37246 ctx.fillRect(0,0, canvas.width, canvas.height); 37247 } 37248 ctx.scale(ratio,ratio); 37249 ctx.lineCap = "round"; 37250 ctx.lineWidth = pat.stroke || 1; 37251 ctx.fillStyle = ol.color.asString(options.color||"#000"); 37252 ctx.strokeStyle = ol.color.asString(options.color||"#000"); 37253 if (pat.circles) for (i=0; i<pat.circles.length; i++) { 37254 var ci = pat.circles[i]; 37255 ctx.beginPath(); 37256 ctx.arc(ci[0], ci[1], ci[2], 0,2*Math.PI); 37257 if (pat.fill) ctx.fill(); 37258 if (pat.stroke) ctx.stroke(); 37259 } 37260 if (!pat.repeat) pat.repeat=[[0,0]]; 37261 if (pat.char) { 37262 ctx.font = pat.font || (pat.width)+"px Arial"; 37263 ctx.textAlign = 'center'; 37264 ctx.textBaseline = 'middle'; 37265 if (pat.angle) { 37266 ctx.fillText(pat.char, pat.width/4, pat.height/4); 37267 ctx.fillText(pat.char, 5*pat.width/4, 5*pat.height/4); 37268 ctx.fillText(pat.char, pat.width/4, 5*pat.height/4); 37269 ctx.fillText(pat.char, 5*pat.width/4, pat.height/4); 37270 ctx.fillText(pat.char, 3*pat.width/4, 3*pat.height/4); 37271 ctx.fillText(pat.char, -pat.width/4, -pat.height/4); 37272 ctx.fillText(pat.char, 3*pat.width/4, -pat.height/4); 37273 ctx.fillText(pat.char, -pat.width/4, 3*pat.height/4); 37274 } 37275 else ctx.fillText(pat.char, pat.width/2, pat.height/2); 37276 } 37277 if (pat.lines) for (i=0; i<pat.lines.length; i++) for (var r=0; r<pat.repeat.length; r++) { 37278 var li = pat.lines[i]; 37279 ctx.beginPath(); 37280 ctx.moveTo(li[0]+pat.repeat[r][0],li[1]+pat.repeat[r][1]); 37281 for (var k=2; k<li.length; k+=2) { 37282 ctx.lineTo(li[k]+pat.repeat[r][0],li[k+1]+pat.repeat[r][1]); 37283 } 37284 if (pat.fill) ctx.fill(); 37285 if (pat.stroke) ctx.stroke(); 37286 ctx.save() 37287 ctx.strokeStyle = 'red'; 37288 ctx.strokeWidth = 0.1; 37289 //ctx.strokeRect(0,0,canvas.width,canvas.height);
37290 ctx.restore() 37291 } 37292 pattern = ctx.createPattern(canvas, 'repeat'); 37293 if (options.offset) { 37294 var offset = options.offset; 37295 if (typeof(offset) == "number") offset = [offset,offset]; 37296 if (offset instanceof Array) { 37297 var dx = Math.round((offset[0]*ratio)); 37298 var dy = Math.round((offset[1]*ratio)); 37299 // New pattern 37300 ctx.scale(1/ratio,1/ratio) 37301 ctx.clearRect(0,0,canvas.width,canvas.height); 37302 ctx.translate(dx,dy); 37303 ctx.fillStyle = pattern; 37304 ctx.fillRect(-dx, -dy, canvas.width,canvas.height); 37305 pattern = ctx.createPattern(canvas, 'repeat'); 37306 } 37307 } 37308 } 37309 ol.style.Fill.call (this, { color: pattern }); 37310}; 37311ol.ext.inherits(ol.style.FillPattern, ol.style.Fill); 37312/** 37313 * Clones the style. 37314 * @return {ol.style.FillPattern} 37315 */ 37316ol.style.FillPattern.prototype.clone = function() { 37317 var s = ol.style.Fill.prototype.clone.call(this); 37318 s.canvas_ = this.canvas_; 37319 return s; 37320}; 37321/** Get canvas used as pattern 37322* @return {canvas} 37323*/ 37324ol.style.FillPattern.prototype.getImage = function() { 37325 return this.canvas_; 37326} 37327/** Get pattern 37328* @param {olx.style.FillPatternOption} 37329*/ 37330ol.style.FillPattern.prototype.getPattern_ = function(options) { 37331 var pat = ol.style.FillPattern.prototype.patterns[options.pattern] 37332 || ol.style.FillPattern.prototype.patterns.dot; 37333 var d = Math.round(options.spacing)||10; 37334 var size; 37335 switch (options.pattern) { 37336 case 'dot': 37337 // fallsthrough 37338 case 'circle': { 37339 size = options.size===0 ? 0 : options.size/2 || 2; 37340 if (!options.angle) { 37341 pat.width = pat.height = d; 37342 pat.circles = [[ d/2, d/2, size ]] 37343 if (options.pattern=='circle') { 37344 pat.circles = pat.circles.concat([ 37345 [ d/2+d, d/2, size ], 37346 [ d/2-d, d/2, size ], 37347 [ d/2, d/2+d, size ], 37348 [ d/2, d/2-d, size ], 37349 [ d/2+d, d/2+d, size ], 37350 [ d/2+d, d/2-d, size ], 37351 [ d/2-d, d/2+d, size ], 37352 [ d/2-d, d/2-d, size ] 37353 ]); 37354 } 37355 } else { 37356 d = pat.width = pat.height = Math.round(d*1.4); 37357 pat.circles = [[ d/4, d/4, size ], [ 3*d/4, 3*d/4, size ]]; 37358 if (options.pattern=='circle') { 37359 pat.circles = pat.circles.concat([ 37360 [ d/4+d, d/4, size ], 37361 [ d/4, d/4+d, size ], 37362 [ 3*d/4-d, 3*d/4, size ], 37363 [ 3*d/4, 3*d/4-d, size ], 37364 [ d/4+d, d/4+d, size ], 37365 [ 3*d/4-d, 3*d/4-d, size ] 37366 ]); 37367 } 37368 } 37369 break; 37370 } 37371 case 'tile': 37372 // fallsthrough 37373 case 'square': { 37374 size = options.size===0 ? 0 : options.size/2 || 2; 37375 if (!options.angle) { 37376 pat.width = pat.height = d; 37377 pat.lines = [[ d/2-size, d/2-size, d/2+size, d/2-size, d/2+size, d/2+size, d/2-size,d/2+size, d/2-size, d/2-size ]] 37378 } else { 37379 pat.width = pat.height = d; 37380 //size *= Math.sqrt(2); 37381 pat.lines = [[ d/2-size,d/2, d/2,d/2-size, d/2+size,d/2, d/2,d/2+size, d/2-size,d/2 ]] 37382 } 37383 if (options.pattern=='square') pat.repeat = [[0,0], [0,d], [d,0], [0,-d], [-d,0], [-d,-d], [d,d], [-d,d], [d,-d] ] 37384 break; 37385 } 37386 case 'cross': { 37387 // Limit angle to 0 | 45 37388 if (options.angle) options.angle = 45; 37389 } 37390 // fallsthrough 37391 case 'hatch': { 37392 var a = Math.round(((options.angle||0)-90)%360); 37393 if (a>180) a -= 360; 37394 a *= Math.PI/180; 37395 var cos = Math.cos(a); 37396 var sin = Math.sin(a); 37397 if (Math.abs(sin)<0.0001) { 37398 pat.width = pat.height = d; 37399 pat.lines = [ [ 0,0.5, d, 0.5 ] ]; 37400 pat.repeat = [ [0,0], [0,d] ]; 37401 } else if (Math.abs(cos)<0.0001) { 37402 pat.width = pat.height = d; 37403 pat.lines = [ [ 0.5,0, 0.5, d] ]; 37404 pat.repeat = [ [0,0], [d,0] ]; 37405 if (options.pattern=='cross') { 37406 pat.lines.push ([ 0,0.5, d, 0.5 ]); 37407 pat.repeat.push([0,d]); 37408 } 37409 } else { 37410 var w = pat.width = Math.round(Math.abs(d/sin)) || 1; 37411 var h = pat.height = Math.round(Math.abs(d/cos)) || 1; 37412 if (options.pattern=='cross') { 37413 pat.lines = [ [-w,-h, 2*w,2*h], [2*w,-h, -w,2*h] ]; 37414 pat.repeat = [ [0,0] ]; 37415 } else if (cos*sin>0) { 37416 pat.lines = [ [-w,-h, 2*w,2*h] ]; 37417 pat.repeat = [ [0,0], [w,0], [0,h] ]; 37418 } else { 37419 pat.lines = [ [2*w,-h, -w,2*h] ]; 37420 pat.repeat = [ [0,0], [-w,0], [0,h] ]; 37421 } 37422 } 37423 pat.stroke = options.size===0 ? 0 : options.size||4; 37424 break; 37425 } 37426 default: break; 37427 } 37428 return pat 37429}
37430/** Static fuction to add char patterns 37431 * @param {title} 37432 * @param {*} options 37433 * @param {integer} [options.size=10] default 10 37434 * @param {integer} [options. width=10] default 10 37435 * @param {integer} [options.height=10] default 10 37436 * @param {Array<circles>} [options.circles] 37437 * @param {Array<pointlist>} [options.lines] 37438 * @param {integer} [options.stroke] 37439 * @param {bool} [options.fill] 37440 * @param {char} [option.char] 37441 * @param {string} [font="10px Arial"] 37442 */ 37443ol.style.FillPattern.addPattern = function (title, options) { 37444 if (!options) options={}; 37445 ol.style.FillPattern.prototype.patterns[title || options.char] = { 37446 width: options.width || options.size || 10, 37447 height: options.height || options.size || 10, 37448 font: options.font, 37449 char: options.char, 37450 circles: options.circles, 37451 lines: options.lines, 37452 repeat: options.repeat, 37453 stroke: options.stroke, 37454 angle: options.angle, 37455 fill: options.fill 37456 } 37457} 37458/** Patterns definitions 37459 * @see pattern generator http://www.imagico.de/map/jsdotpattern.php 37460 */ 37461ol.style.FillPattern.prototype.patterns = { 37462 "hatch": { 37463 width:5, 37464 height:5, 37465 lines:[[0,2.5,5,2.5]], 37466 stroke:1 37467 }, 37468 "cross": { 37469 width:7, 37470 height:7, 37471 lines:[[0,3,10,3],[3,0,3,10]], 37472 stroke:1 37473 }, 37474 "dot": { 37475 width:8, 37476 height:8, 37477 circles:[[5,5,2]], 37478 stroke:false, 37479 fill:true, 37480 }, 37481 "circle": { 37482 width:10, 37483 height:10, 37484 circles:[[5,5,2]], 37485 stroke:1, 37486 fill:false, 37487 }, 37488 "square": { 37489 width:10, 37490 height:10, 37491 lines:[[3,3, 3,8, 8,8, 8,3, 3,3]], 37492 stroke:1, 37493 fill:false, 37494 }, 37495 "tile": { 37496 width:10, 37497 height:10, 37498 lines:[[3,3, 3,8, 8,8, 8,3, 3,3]], 37499 fill:true, 37500 }, 37501 "woven": { 37502 width: 12, 37503 height: 12, 37504 lines: [[ 3,3, 9,9 ],[0,12, 3,9], [9,3, 12,0], [-1,1,1,-1], [13,11,11,13]], 37505 stroke: 1 37506 }, 37507 "crosses": { 37508 width: 8, 37509 height: 8, 37510 lines: [[ 2,2, 6,6 ],[2,6,6,2]], 37511 stroke: 1 37512 }, 37513 "caps": { 37514 width: 8, 37515 height: 8, 37516 lines: [[ 2,6, 4,2, 6,6 ]], 37517 stroke: 1 37518 }, 37519 "nylon": { 37520 width: 20, 37521 height: 20, 37522// 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 ]], 37523// repeat: [[0,0], [20,0], [0,20], [-20,0], [0,-20], [-20,-20]], 37524 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 ]], 37525 repeat: [[0,0], [-20,0], [0,-20] ], 37526 stroke: 1 37527 }, 37528 "hexagon": { 37529 width: 20, 37530 height: 12, 37531 lines: [[ 0,10, 4,4, 10,4, 14,10, 10,16, 4,16, 0,10 ]], 37532 stroke:1, 37533 repeat:[[0,0],[10,6],[10,-6],[-10,-6]] 37534 }, 37535 "cemetry": { 37536 width:15, 37537 height:19, 37538 lines:[[0,3.5,7,3.5],[3.5,0,3.5,10], 37539 //[7,12.5,14,12.5],[10.5,9,10.5,19] 37540 ], 37541 stroke:1, 37542 repeat:[[0,0],[7,9]] 37543 }, 37544 "sand": { 37545 width: 20, 37546 height: 20, 37547 circles:[ 37548 [1,2,1],[9,3,1],[2,16,1], 37549 [7,8,1],[6,14,1],[4,19,1], 37550 [14,2,1],[12,10,1],[14,18,1], 37551 [18,8,1],[18,14,1] 37552 ], 37553 fill:1 37554 }, 37555 "conglomerate": { 37556 width: 60, 37557 height: 40, 37558 circles:[[2,4,1],[17,3,1],[26,18,1],[12,17,1],[5,17,2],[28,11,2]], 37559 lines:[[7,5, 6,7, 9,9, 11,8, 11,6, 9,5, 7,5], 37560 [16,10, 15,13, 16,14, 19,15, 21,13, 22,9, 20,8, 19,8, 16,10], 37561 [24,6, 26,7, 27,5, 26,4, 24,4, 24,6]], 37562 repeat: [[30,0], [-15,20], [15,20], [45,20]], 37563 stroke:1 37564 }, 37565 "conglomerate2": { 37566 width:60, 37567 height:40, 37568 circles:[[2,4,1],[17,3,1],[26,18,1],[12,17,1],[5,17,2],[28,11,2]], 37569 lines:[ 37570 [7,5, 6,7, 9,9, 11,8, 11,6, 9,5, 7,5], 37571 [16,10, 15,13, 16,14, 19,15, 21,13, 22,9, 20,8, 19,8, 16,10], 37572 [24,6, 26,7, 27,5, 26,4, 24,4, 24,6] 37573 ], 37574 repeat: [[30,0], [-15,20], [15,20], [45,20]], 37575 fill:1 37576 }, 37577 "gravel": { 37578 width:15, 37579 height:10, 37580 circles:[[4,2,1],[5,9,1],[1,7,1]],//[9,9,1],,[15,2,1]], 37581 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]], 37582 stroke:1 37583 }, 37584 "brick": { 37585 width:18, 37586 height:16, 37587 lines:[ [0,1,18,1],[0,10,18,10], [6,1,6,10],[12,10,12,18],[12,0,12,1]], 37588 stroke:1 37589 }, 37590 "dolomite": { 37591 width:20, 37592 height:16, 37593 lines:[[0,1,20,1],[0,9,20,9],[1,9,6,1],[11,9,14,16],[14,0,14.4,1]], 37594 stroke:1 37595 }, 37596 "coal": { 37597 width:20, 37598 height:16, 37599 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]], 37600 fill:1 37601 }, 37602 "breccia": { 37603 width:20, 37604 height:16, 37605 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] ], 37606 stroke:1, 37607 }, 37608 "clay": { 37609 width:20, 37610 height:20, 37611 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]], 37612 stroke:1 37613 }, 37614 "flooded": { 37615 width:15, 37616 height:10,
37617 lines:[ [0,1,10,1],[0,6,5,6], [10,6,15,6]], 37618 stroke:1 37619 }, 37620 "chaos": { 37621 width:40, 37622 height:40, 37623 lines:[[40,2, 40,0, 38,0, 40,2], 37624 [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, 37625 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, 37626 12,1, 11,0, 10,1, 11,4, 10,7, 9,8, 8,5, 6,4, 5,3, 5,1, 5,0, 4,0], 37627 [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], 37628 [ 27,0, 23,2, 21,8, 30,0, 27,0], 37629 [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, 37630 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, 37631 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, 37632 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], 37633 [13,15, 9,19, 14,18, 13,15], [18,23, 14,27, 16,27, 17,25, 20,26, 18,23], 37634 [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], 37635 [29,27, 32,28, 33,31, 30,29, 27,28, 29,27], 37636 [5,35, 1,33, 3,36, 13,38, 15,35, 10,36, 5,35]], 37637 fill:1, 37638 }, 37639 "grass": { 37640 width:27, 37641 height:22, 37642 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]], 37643 repeat: [[0,0],[14,10]], 37644 stroke:1 37645 }, 37646 "swamp": { 37647 width:24, 37648 height:23, 37649 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] ], 37650 repeat: [[0,0],[14,10]], 37651 stroke:1 37652 }, 37653 "reed": { 37654 width:26, 37655 height:23, 37656 lines:[ [2.5,10,2,7], [4.5,10,4.2,4], [6.5,10,6.8,4], [8.5,10,9,6], 37657 [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] ], 37658 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] ], 37659 repeat: [[0,0],[14,10]], 37660 stroke:1 37661 }, 37662 "wave": { 37663 width:10, 37664 height:8, 37665 lines:[ [0,0, 5,4, 10,0] ], 37666 stroke:1 37667 }, 37668 "vine": { 37669 width:13, 37670 height:13, 37671 lines:[[3,0,3,6],[9,7,9,13]], 37672 stroke:1.0 37673 }, 37674 "forest": { 37675 width:55, 37676 height:30, 37677 circles:[[7,7,3.5],[20,20,1.5],[42,22,3.5],[35,5,1.5]], 37678 stroke:1 37679 }, 37680 "forest2": { 37681 width:55, 37682 height:30, 37683 circles:[[7,7,3.5],[20,20,1.5],[42,22,3.5],[35,5,1.5]], 37684 fill:1, 37685 stroke:1 37686 }, 37687 "scrub": { 37688 width:26, 37689 height:20, 37690 lines:[ [1,4, 4,8, 6,4] ], 37691 circles:[[20,13,1.5]], 37692 stroke:1, 37693 }, 37694 "tree": { 37695 width:30, 37696 height:30, 37697 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]], 37698 repeat: [[3,1],[18,16]], 37699 stroke:1 37700 }, 37701 "tree2": { 37702 width:30, 37703 height:30, 37704 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]], 37705 repeat: [[3,1],[18,16]], 37706 fill:1, 37707 stroke:1 37708 }, 37709 "pine": { 37710 width:30, 37711 height:30, 37712 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]], 37713 repeat:[[3,1],[18,16]], 37714 stroke:1 37715 }, 37716 "pine2": { 37717 width:30, 37718 height:30, 37719 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]], 37720 repeat:[[3,1],[18,16]], 37721 fill:1, 37722 stroke:1 37723 }, 37724 "mixtree": { 37725 width:30, 37726 height:30, 37727 lines:[ 37728 [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], 37729 [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] 37730 ], 37731 repeat: [[3,1]], 37732 stroke:1 37733 }, 37734 "mixtree2": { 37735 width:30, 37736 height:30, 37737 lines:[ 37738 [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], 37739 [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] 37740 ], 37741 repeat: [[3,1]], 37742 fill:1, 37743 stroke:1 37744 }, 37745 "pines": { 37746 width:22, 37747 height:20, 37748 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]], 37749 repeat: [[2,1]], 37750 stroke:1 37751 }, 37752 "rock": { 37753 width:20, 37754 height:20, 37755 lines:[ [1,0,1,9],[4,0,4,9],[7,0,7,9], 37756 [10,1,19,1],[10,4,19,4],[10,7,19,7], 37757 [0,11,9,11],[0,14,9,14],[0,17,9,17], 37758 [12,10,12,19],[15,10,15,19],[18,10,18,19] ], 37759 repeat:[[0.5,0.5]], 37760 stroke:1 37761 }, 37762 "rocks": { 37763 width:20, 37764 height:20,
37765 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, 37766 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, 37767 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, 37768 17,4, 16,3, 15,0, 14,0, 15,4, 11,5, 10,4, 11,0, 10,0, 9,4, 6,5, 5,0,], 37769 [18,5, 19,6, 18,10, 16,10, 14,9, 16,5, 18,5], 37770 [5,6, 9,5, 10,6, 10,9, 6,10, 5,6], 37771 [14,5, 14,8, 13,9, 12,9, 11,7, 12,5, 14,5], 37772 [ 5,11, 8,10, 9,11, 10,14, 6,15, 6,15, 5,11], 37773 [13,10, 14,11, 15,14, 15,14, 15,14, 11,15, 10,11, 11,10, 13,10], 37774 [15,12, 16,11, 19,11, 19,15, 16,14, 16,14, 15,12], 37775 [6,16, 9,15, 10,18, 5,19, 6,16], 37776 [10,16, 14,16, 14,18, 13,19, 11,18, 10,16], 37777 [15,15, 18,16, 18,18, 16,19, 15,18, 15,15]], 37778 stroke:1 37779 } 37780}; 37781 37782/* Copyright (c) 2019 Jean-Marc VIGLINO, 37783 released under the CeCILL-B license (French BSD license) 37784 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 37785*/ 37786/** Flow line style 37787 * Draw LineString with a variable color / width 37788 * NB: the FlowLine style doesn't impress the hit-detection. 37789 * If you want your lines to be sectionable you have to add your own style to handle this. 37790 * (with transparent line: stroke color opacity to .1 or zero width) 37791 * @constructor 37792 * @extends {ol.style.Style} 37793 * @param {Object} options 37794 * @param {boolean} options.visible draw only the visible part of the line, default true 37795 * @param {number|function} options.width Stroke width or a function that gets a feature and the position (beetween [0,1]) and returns current width 37796 * @param {number} options.width2 Final stroke width (if width is not a function) 37797 * @param {number} options.arrow Arrow at start (-1), at end (1), at both (2), none (0), default geta 37798 * @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 37799 * @param {ol.colorLike} options.color2 Final sroke color if color is nor a function 37800 * @param {ol.colorLike} options.arrowColor Color of arrows, if not defined used color or color2 37801 * @param {string} options.lineCap CanvasRenderingContext2D.lineCap 'butt' | 'round' | 'square', default 'butt' 37802 * @param {number|ol.size} options.arrowSize height and width of the arrow, default 16 37803 * @param {boolean} [options.noOverlap=false] prevent segments overlaping 37804 * @param {number} options.offset0 offset at line start 37805 * @param {number} options.offset1 offset at line end 37806 */ 37807ol.style.FlowLine = function(options) { 37808 if (!options) options = {}; 37809 ol.style.Style.call (this, { 37810 renderer: this._render.bind(this), 37811 stroke: options.stroke, 37812 text: options.text, 37813 zIndex: options.zIndex, 37814 geometry: options.geometry 37815 }); 37816 // Draw only visible 37817 this._visible = (options.visible !== false); 37818 // Width 37819 if (typeof options.width === 'function') { 37820 this._widthFn = options.width; 37821 } else { 37822 this.setWidth(options.width); 37823 } 37824 this.setWidth2(options.width2); 37825 // Color 37826 if (typeof options.color === 'function') { 37827 this._colorFn = options.color; 37828 } else { 37829 this.setColor(options.color); 37830 } 37831 this.setColor2(options.color2); 37832 // LineCap 37833 this.setLineCap(options.lineCap); 37834 // Arrow 37835 this.setArrow(options.arrow); 37836 this.setArrowSize(options.arrowSize); 37837 this.setArrowColor(options.arrowColor); 37838 // Offset 37839 this._offset = [0,0]; 37840 this.setOffset(options.offset0, 0); 37841 this.setOffset(options.offset1, 1); 37842 // Overlap 37843 this._noOverlap = options.noOverlap; 37844}; 37845ol.ext.inherits(ol.style.FlowLine, ol.style.Style); 37846/** Set the initial width 37847 * @param {number} width width, default 0 37848 */ 37849ol.style.FlowLine.prototype.setWidth = function(width) { 37850 this._width = width || 0; 37851}; 37852/** Set the final width 37853 * @param {number} width width, default 0 37854 */ 37855ol.style.FlowLine.prototype.setWidth2 = function(width) { 37856 this._width2 = width; 37857}; 37858/** Get offset at start or end 37859 * @param {number} where 0=start, 1=end 37860 * @return {number} width 37861 */ 37862ol.style.FlowLine.prototype.getOffset = function(where) { 37863 return this._offset[where]; 37864}; 37865/** Add an offset at start or end 37866 * @param {number} width 37867 * @param {number} where 0=start, 1=end 37868 */ 37869ol.style.FlowLine.prototype.setOffset = function(width, where) { 37870 width = Math.max(0, parseFloat(width)); 37871 switch(where) { 37872 case 0: { 37873 this._offset[0] = width; 37874 break; 37875 } 37876 case 1: { 37877 this._offset[1] = width; 37878 break; 37879 } 37880 } 37881}; 37882/** Set the LineCap 37883 * @param {steing} cap LineCap (round or butt), default butt 37884 */ 37885ol.style.FlowLine.prototype.setLineCap = function(cap) { 37886 this._lineCap = (cap==='round' ? 'round' : 'butt'); 37887}; 37888/** Get the current width at step 37889 * @param {ol.feature} feature 37890 * @param {number} step current drawing step beetween [0,1] 37891 * @return {number} 37892 */ 37893ol.style.FlowLine.prototype.getWidth = function(feature, step) { 37894 if (this._widthFn) return this._widthFn(feature, step); 37895 var w2 = (typeof(this._width2) === 'number') ? this._width2 : this._width; 37896 return this._width + (w2-this._width) * step; 37897}; 37898/** Set the initial color 37899 * @param {ol.colorLike} color 37900 */ 37901ol.style.FlowLine.prototype.setColor = function(color) { 37902 try{ 37903 this._color = ol.color.asArray(color); 37904 } catch(e) { 37905 this._color = [0,0,0,1]; 37906 } 37907}; 37908/** Set the final color 37909 * @param {ol.colorLike} color 37910 */ 37911ol.style.FlowLine.prototype.setColor2 = function(color) { 37912 try { 37913 this._color2 = ol.color.asArray(color); 37914 } catch(e) { 37915 this._color2 = null; 37916 } 37917}; 37918/** Set the arrow color 37919 * @param {ol.colorLike} color 37920 */ 37921ol.style.FlowLine.prototype.setArrowColor = function(color) { 37922 try { 37923 this._acolor = ol.color.asString(color); 37924 } catch(e) { 37925 this._acolor = null; 37926 } 37927}; 37928/** Get the current color at step 37929 * @param {ol.feature} feature 37930 * @param {number} step current drawing step beetween [0,1] 37931 * @return {string} 37932 */ 37933ol.style.FlowLine.prototype.getColor = function(feature, step) { 37934 if (this._colorFn) return ol.color.asString(this._colorFn(feature, step)); 37935 var color = this._color; 37936 var color2 = this._color2 || this._color 37937 return 'rgba('+ 37938 + Math.round(color[0] + (color2[0]-color[0]) * step) +',' 37939 + Math.round(color[1] + (color2[1]-color[1]) * step) +','
37940 + Math.round(color[2] + (color2[2]-color[2]) * step) +',' 37941 + (color[3] + (color2[3]-color[3]) * step) 37942 +')'; 37943}; 37944/** Get arrow 37945 */ 37946ol.style.FlowLine.prototype.getArrow = function() { 37947 return this._arrow; 37948}; 37949/** Set arrow 37950 * @param {number} n -1 | 0 | 1 | 2, default: 0 37951 */ 37952ol.style.FlowLine.prototype.setArrow = function(n) { 37953 this._arrow = parseInt(n); 37954 if (this._arrow < -1 || this._arrow > 2) this._arrow = 0; 37955} 37956/** getArrowSize 37957 * @return {ol.size} 37958 */ 37959ol.style.FlowLine.prototype.getArrowSize = function() { 37960 return this._arrowSize || [16,16]; 37961}; 37962/** setArrowSize 37963 * @param {number|ol.size} size 37964 */ 37965ol.style.FlowLine.prototype.setArrowSize = function(size) { 37966 if (Array.isArray(size)) this._arrowSize = size; 37967 else if (typeof(size) === 'number') this._arrowSize = [size,size]; 37968}; 37969/** drawArrow 37970 * @param {CanvasRenderingContext2D} ctx 37971 * @param {ol.coordinate} p0 37972 * @param ol.coordinate} p1 37973 * @param {number} width 37974 * @param {number} ratio pixelratio 37975 * @private 37976 */ 37977ol.style.FlowLine.prototype.drawArrow = function (ctx, p0, p1, width, ratio) { 37978 var asize = this.getArrowSize()[0] * ratio; 37979 var l = ol.coordinate.dist2d(p0, p1); 37980 var dx = (p0[0]-p1[0])/l; 37981 var dy = (p0[1]-p1[1])/l; 37982 width = Math.max(this.getArrowSize()[1]/2, width/2) * ratio; 37983 ctx.beginPath(); 37984 ctx.moveTo(p0[0],p0[1]); 37985 ctx.lineTo(p0[0]-asize*dx+width*dy, p0[1]-asize*dy-width*dx); 37986 ctx.lineTo(p0[0]-asize*dx-width*dy, p0[1]-asize*dy+width*dx); 37987 ctx.lineTo(p0[0],p0[1]); 37988 ctx.fill(); 37989}; 37990/** Renderer function 37991 * @param {Array<ol.coordinate>} geom The pixel coordinates of the geometry in GeoJSON notation 37992 * @param {ol.render.State} e The olx.render.State of the layer renderer 37993 */ 37994ol.style.FlowLine.prototype._render = function(geom, e) { 37995 if (e.geometry.getType()==='LineString') { 37996 var i, g, p, ctx = e.context; 37997 // Get geometry used at drawing 37998 if (!this._visible) { 37999 var a = e.pixelRatio / e.resolution; 38000 var cos = Math.cos(e.rotation) 38001 var sin = Math.sin(e.rotation) 38002 g = e.geometry.getCoordinates(); 38003 var dx = geom[0][0] - g[0][0] * a *cos - g[0][1] * a *sin ; 38004 var dy = geom[0][1] - g[0][0] * a * sin + g[0][1] * a * cos; 38005 geom = []; 38006 for (i=0; p=g[i]; i++) { 38007 geom[i] = [ 38008 dx + p[0] * a * cos + p[1] * a * sin, 38009 dy + p[0] * a * sin - p[1] * a * cos, 38010 p[2] 38011 ]; 38012 } 38013 } 38014 var asize = this.getArrowSize()[0] * e.pixelRatio; 38015 ctx.save(); 38016 // Offsets 38017 if (this.getOffset(0)) this._splitAsize(geom, this.getOffset(0) * e.pixelRatio) 38018 if (this.getOffset(1)) this._splitAsize(geom, this.getOffset(1) * e.pixelRatio, true) 38019 // Arrow 1 38020 if (geom.length>1 && (this.getArrow()===-1 || this.getArrow()===2)) { 38021 p = this._splitAsize(geom, asize); 38022 if (this._acolor) ctx.fillStyle = this._acolor; 38023 else ctx.fillStyle = this.getColor(e.feature, 0); 38024 this.drawArrow(ctx, p[0], p[1], this.getWidth(e.feature, 0), e.pixelRatio); 38025 } 38026 // Arrow 2 38027 if (geom.length>1 && this.getArrow()>0) { 38028 p = this._splitAsize(geom, asize, true); 38029 if (this._acolor) ctx.fillStyle = this._acolor; 38030 else ctx.fillStyle = this.getColor(e.feature, 1); 38031 this.drawArrow(ctx, p[0], p[1], this.getWidth(e.feature, 1), e.pixelRatio); 38032 } 38033 // Split into 38034 var geoms = this._splitInto(geom, 255, 2); 38035 var k = 0; 38036 var nb = geoms.length; 38037 // Draw 38038 ctx.lineJoin = 'round'; 38039 ctx.lineCap = this._lineCap || 'butt'; 38040 if (geoms.length > 1) { 38041 for (k=0; k<geoms.length; k++) { 38042 var step = k/nb; 38043 g = geoms[k]; 38044 ctx.lineWidth = this.getWidth(e.feature, step) * e.pixelRatio; 38045 ctx.strokeStyle = this.getColor(e.feature, step); 38046 ctx.beginPath(); 38047 ctx.moveTo(g[0][0],g[0][1]); 38048 for (i=1; p=g[i]; i++) { 38049 ctx.lineTo(p[0],p[1]); 38050 } 38051 ctx.stroke(); 38052 } 38053 } 38054 ctx.restore(); 38055 } 38056}; 38057/** Split extremity at 38058 * @param {ol.geom.LineString} geom 38059 * @param {number} asize 38060 * @param {boolean} end start=false or end=true, default false (start) 38061 */ 38062ol.style.FlowLine.prototype._splitAsize = function(geom, asize, end) { 38063 var p, p1, p0; 38064 var dl, d = 0; 38065 if (end) p0 = geom.pop(); 38066 else p0 = geom.shift(); 38067 p = p0; 38068 while(geom.length) { 38069 if (end) p1 = geom.pop(); 38070 else p1 = geom.shift(); 38071 dl = ol.coordinate.dist2d(p,p1); 38072 if (d+dl > asize) { 38073 p = [p[0]+(p1[0]-p[0])*(asize-d)/dl, p[1]+(p1[1]-p[1])*(asize-d)/dl]; 38074 dl = ol.coordinate.dist2d(p,p0); 38075 if (end) { 38076 geom.push(p1); 38077 geom.push(p); 38078 geom.push([p[0]+(p0[0]-p[0])/dl, p[1]+(p0[1]-p[1])/dl]); 38079 } else { 38080 geom.unshift(p1); 38081 geom.unshift(p); 38082 geom.unshift([p[0]+(p0[0]-p[0])/dl, p[1]+(p0[1]-p[1])/dl]); 38083 } 38084 break; 38085 } 38086 d += dl; 38087 p = p1; 38088 } 38089 return [p0,p]; 38090}; 38091/** Split line geometry into equal length geometries 38092 * @param {Array<ol.coordinate>} geom 38093 * @param {number} nb number of resulting geometries, default 255 38094 * @param {number} nim minimum length of the resulting geometries, default 1 38095 */ 38096ol.style.FlowLine.prototype._splitInto = function(geom, nb, min) { 38097 var i, p; 38098 var dt = this._noOverlap ? 1 : .9; 38099 // Split geom into equal length geoms 38100 var geoms = []; 38101 var dl, l = 0; 38102 for (i=1; p=geom[i]; i++) { 38103 l += ol.coordinate.dist2d(geom[i-1], p); 38104 } 38105 var length = Math.max (min||2, l/(nb||255)); 38106 var p0 = geom[0]; 38107 l = 0; 38108 var g = [p0]; 38109 i = 1; 38110 p = geom[1]; 38111 while (i < geom.length) { 38112 var dx = p[0]-p0[0]; 38113 var dy = p[1]-p0[1]; 38114 dl = Math.sqrt(dx*dx + dy*dy); 38115 if (l+dl > length) { 38116 var d = (length-l) / dl; 38117 g.push([ 38118 p0[0] + dx * d, 38119 p0[1] + dy * d 38120 ]); 38121 geoms.push(g); 38122 p0 =[ 38123 p0[0] + dx * d*dt, 38124 p0[1] + dy * d*dt 38125 ]; 38126 g = [p0]; 38127 l = 0; 38128 } else { 38129 l += dl; 38130 p0 = p; 38131 g.push(p0); 38132 i++;
38133 p = geom[i]; 38134 } 38135 } 38136 geoms.push(g); 38137 return geoms; 38138} 38139 38140/* Copyright (c) 2015 Jean-Marc VIGLINO, 38141 released under the CeCILL-B license (French BSD license) 38142 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 38143*/ 38144/** 38145 * @classdesc 38146 * A marker style to use with font symbols. 38147 * 38148 * @constructor 38149 * @param {} options Options. 38150 * @param {string} [options.color] default #000 38151 * @param {string} options.glyph the glyph name or a char to display as symbol. 38152 * The name must be added using the {@link ol.style.FontSymbol.addDefs} function. 38153 * @param {string} [options.text] a text to display as a glyph 38154 * @param {string} [options.font] font to use with the text option 38155 * @param {string} options.form 38156 * none|circle|poi|bubble|marker|coma|shield|blazon|bookmark|hexagon|diamond|triangle|sign|ban|lozenge|square 38157 * a form that will enclose the glyph, default none 38158 * @param {number} options.radius 38159 * @param {number} options.rotation 38160 * @param {boolean} options.rotateWithView 38161 * @param {number} [options.opacity=1] 38162 * @param {number} [options.fontSize=1] size of the font compare to the radius, fontSize greater than 1 will exceed the symbol extent 38163 * @param {string} [options.fontStyle] the font style (bold, italic, bold italic, etc), default none 38164 * @param {boolean} options.gradient true to display a gradient on the symbol 38165 * @param {number} [options.offsetX=0] default 0 38166 * @param {number} [options.offsetY=0] default 0 38167 * @param {_ol_style_Fill_} options.fill 38168 * @param {_ol_style_Stroke_} options.stroke 38169 * @extends {ol.style.RegularShape} 38170 * @implements {ol.structs.IHasChecksum} 38171 * @api 38172 */ 38173ol.style.FontSymbol = function(options) { 38174 options = options || {}; 38175 var strokeWidth = 0; 38176 if (options.stroke) { 38177 strokeWidth = options.stroke.getWidth(); 38178 } 38179 ol.style.RegularShape.call (this, { 38180 radius: options.radius, 38181 fill:options.fill, 38182 rotation:options.rotation, 38183 rotateWithView: options.rotateWithView 38184 }); 38185 if (typeof(options.opacity)=="number") this.setOpacity(options.opacity); 38186 this.color_ = options.color; 38187 this.fontSize_ = options.fontSize || 1; 38188 this.fontStyle_ = options.fontStyle || ''; 38189 this.stroke_ = options.stroke; 38190 this.fill_ = options.fill; 38191 this.radius_ = options.radius -strokeWidth; 38192 this.form_ = options.form || "none"; 38193 this.gradient_ = options.gradient; 38194 this.offset_ = [options.offsetX ? options.offsetX :0, options.offsetY ? options.offsetY :0]; 38195 if (options.glyph) this.glyph_ = this.getGlyph(options.glyph); 38196 else this.glyph_ = this.getTextGlyph(options.text||'', options.font); 38197 this.renderMarker_(); 38198}; 38199ol.ext.inherits(ol.style.FontSymbol, ol.style.RegularShape); 38200/** Cool stuff to get the image symbol for a style 38201 * @param {number} ratio pixelratio 38202 */ 38203ol.style.Image.prototype.getImagePNG = function(ratio) { 38204 ratio = ratio || window.devicePixelRatio; 38205 var canvas = this.getImage(ratio); 38206 if (canvas) { 38207 try { return canvas.toDataURL("image/png"); } 38208 catch(e) { return false; } 38209 } else { 38210 return false; 38211 } 38212} 38213/** Font defs 38214 */ 38215ol.style.FontSymbol.prototype.defs = { 'fonts':{}, 'glyphs':{} }; 38216/** Static function : add new font defs 38217 * @param {String|Object} font the font desciption 38218 * @param {} glyphs a key / value list of glyph definitions. 38219 * Each key is the name of the glyph, 38220 * the value is an object that code the font, the caracter code, 38221 * the name and a search string for the glyph. 38222 */ 38223ol.style.FontSymbol.addDefs = function(font, glyphs) { 38224 var thefont = font; 38225 if (typeof(font) == 'string') thefont = { font:font, name:font, copyright:'' }; 38226 if (!thefont.font || typeof(thefont.font) !== 'string') { 38227 console.log('bad font def'); 38228 return; 38229 } 38230 var fontname = thefont.font; 38231 ol.style.FontSymbol.prototype.defs.fonts[fontname] = thefont; 38232 for (var i in glyphs) { 38233 var g = glyphs[i]; 38234 if (typeof(g) === 'string' && g.length==1) g = { char: g }; 38235 ol.style.FontSymbol.prototype.defs.glyphs[i] = { 38236 font: thefont.font, 38237 char: g.char || ''+String.fromCharCode(g.code) || '', 38238 theme: g.theme || thefont.name, 38239 name: g.name || i, 38240 search: g.search || '' 38241 }; 38242 } 38243}; 38244/** Clones the style. 38245 * @return {ol.style.FontSymbol} 38246 */ 38247ol.style.FontSymbol.prototype.clone = function() { 38248 var g = new ol.style.FontSymbol({ 38249 glyph: '', 38250 color: this.color_, 38251 fontSize: this.fontSize_, 38252 fontStyle: this.fontStyle_, 38253 stroke: this.stroke_, 38254 fill: this.fill_, 38255 radius: this.radius_ + (this.stroke_ ? this.stroke_.getWidth():0), 38256 form: this.form_, 38257 gradient: this.gradient_, 38258 offsetX: this.offset_[0], 38259 offsetY: this.offset_[1], 38260 opacity: this.getOpacity(), 38261 rotation: this.getRotation(), 38262 rotateWithView: this.getRotateWithView() 38263 }); 38264 g.setScale(this.getScale()); 38265 g.glyph_ = this.glyph_; 38266 g.renderMarker_(); 38267 return g; 38268}; 38269/** Get the fill style for the symbol. 38270 * @return {ol.style.Fill} Fill style. 38271 * @api 38272 */ 38273ol.style.FontSymbol.prototype.getFill = function() { 38274 return this.fill_; 38275}; 38276/** Get the stroke style for the symbol. 38277 * @return {_ol_style_Stroke_} Stroke style. 38278 * @api 38279 */ 38280ol.style.FontSymbol.prototype.getStroke = function() { 38281 return this.stroke_; 38282}; 38283/** Get the glyph definition for the symbol. 38284 * @param {string|undefined} name a glyph name to get the definition, default return the glyph definition for the style. 38285 * @return {*} 38286 * @api 38287 */ 38288ol.style.FontSymbol.prototype.getGlyph = function(name) {
38289 if (name) return ol.style.FontSymbol.prototype.defs.glyphs[name] || { font:'sans-serif', char:name.charAt(0), theme:'none', name:'none', search:'' }; 38290 else return this.glyph_; 38291}; 38292/** Get glyph definition given a text and a font 38293 * @param {string|undefined} text 38294 * @param {string} [font] the font for the text 38295 * @return {*} 38296 * @api 38297 */ 38298ol.style.FontSymbol.prototype.getTextGlyph = function(text, font) { 38299 return { font: font || 'sans-serif', char:String(text), theme:'none', name:'none', search:'' }; 38300}; 38301/** 38302 * Get the glyph name. 38303 * @return {string} the name 38304 * @api 38305 */ 38306ol.style.FontSymbol.prototype.getGlyphName = function() { 38307 for (var i in ol.style.FontSymbol.prototype.defs.glyphs) { 38308 if (ol.style.FontSymbol.prototype.defs.glyphs[i] === this.glyph_) return i; 38309 } 38310 return ''; 38311}; 38312/** 38313 * Get the stroke style for the symbol. 38314 * @return {_ol_style_Stroke_} Stroke style. 38315 * @api 38316 */ 38317ol.style.FontSymbol.prototype.getFontInfo = function(glyph) { 38318 return ol.style.FontSymbol.prototype.defs.fonts[glyph.font]; 38319} 38320/** @private 38321 */ 38322ol.style.FontSymbol.prototype.renderMarker_ = function(pixelratio) { 38323 if (!pixelratio) { 38324 if (this.getPixelRatio) { 38325 pixelratio = window.devicePixelRatio; 38326 this.renderMarker_(pixelratio); 38327 if (this.getPixelRatio && pixelratio!==1) this.renderMarker_(1); 38328 } else { 38329 this.renderMarker_(1); 38330 } 38331 return; 38332 } 38333 var strokeStyle; 38334 var strokeWidth = 0; 38335 if (this.stroke_) { 38336 strokeStyle = ol.color.asString(this.stroke_.getColor()); 38337 strokeWidth = this.stroke_.getWidth(); 38338 } 38339 // get canvas 38340 var canvas = this.getImage(pixelratio); 38341 //console.log(this.getImage().width+" / "+(2 * (this.radius_ + strokeWidth) + 1)); 38342 /** @type {ol.style.FontSymbol.RenderOptions} */ 38343 var renderOptions = { 38344 strokeStyle: strokeStyle, 38345 strokeWidth: strokeWidth, 38346 size: canvas.width/pixelratio, 38347 }; 38348 // draw the circle on the canvas 38349 var context = (canvas.getContext('2d')); 38350 context.clearRect(0, 0, canvas.width, canvas.height); 38351 this.drawMarker_(renderOptions, context, 0, 0, pixelratio); 38352 // Set anchor / displacement 38353 if (this.setDisplacement) { 38354 this.setDisplacement([this.offset_[0], -this.offset_[1]]) 38355 } else { 38356 var a = this.getAnchor(); 38357 a[0] = canvas.width / 2 - this.offset_[0]; 38358 a[1] = canvas.width / 2 - this.offset_[1]; 38359 } 38360}; 38361/** 38362 * @private 38363 * @param {ol.style.FontSymbol.RenderOptions} renderOptions 38364 * @param {CanvasRenderingContext2D} context 38365 */ 38366ol.style.FontSymbol.prototype.drawPath_ = function(renderOptions, context) { 38367 var s = 2*this.radius_+renderOptions.strokeWidth+1; 38368 var w = renderOptions.strokeWidth/2; 38369 var c = renderOptions.size / 2; 38370 // Transfo to place the glyph at the right place 38371 var transfo = { fac:1, posX:renderOptions.size / 2, posY:renderOptions.size / 2 }; 38372 context.lineJoin = 'round'; 38373 context.beginPath(); 38374 // Draw the path with the form 38375 switch (this.form_) { 38376 case "none": { 38377 transfo.fac=1; 38378 break; 38379 } 38380 case "circle": 38381 case "ban": { 38382 context.arc ( c, c, s/2, 0, 2 * Math.PI, true); 38383 break; 38384 } 38385 case "poi": { 38386 context.arc ( c, c -0.4*this.radius_, 0.6*this.radius_, 0.15*Math.PI, 0.85*Math.PI, true); 38387 context.lineTo ( c-0.89*0.05*s, (0.95+0.45*0.05)*s+w); 38388 context.arc ( c, 0.95*s+w, 0.05*s, 0.85*Math.PI, 0.15*Math.PI, true); 38389 transfo = { fac:0.45, posX:c, posY:c -0.35*this.radius_ }; 38390 break; 38391 } 38392 case "bubble": { 38393 context.arc ( c, c -0.2*this.radius_, 0.8*this.radius_, 0.4*Math.PI, 0.6*Math.PI, true); 38394 context.lineTo ( 0.5*s+w, s+w); 38395 transfo = { fac:0.7, posX:c, posY:c -0.2*this.radius_ }; 38396 break; 38397 } 38398 case "marker": { 38399 context.arc ( c, c -0.2*this.radius_, 0.8*this.radius_, 0.25*Math.PI, 0.75*Math.PI, true); 38400 context.lineTo ( 0.5*s+w, s+w); 38401 transfo = { fac:0.7, posX: c, posY: c -0.2*this.radius_ }; 38402 break; 38403 } 38404 case "coma": { 38405 context.moveTo ( c + 0.8*this.radius_, c -0.2*this.radius_); 38406 context.quadraticCurveTo ( 0.95*s+w, 0.75*s+w, 0.5*s+w, s+w); 38407 context.arc ( c, c -0.2*this.radius_, 0.8*this.radius_, 0.45*Math.PI, 0, false); 38408 transfo = { fac:0.7, posX: c, posY: c -0.2*this.radius_ }; 38409 break; 38410 } 38411 default: { 38412 var pts; 38413 switch (this.form_) { 38414 case "shield": { 38415 pts = [ 0.05,0, 0.95,0, 0.95,0.8, 0.5,1, 0.05,0.8, 0.05,0 ]; 38416 transfo.posY = 0.45*s+w ; 38417 break; 38418 } 38419 case "blazon": { 38420 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 ]; 38421 transfo.fac = 0.8; 38422 transfo.posY = 0.4*s+w ; 38423 break; 38424 } 38425 case "bookmark": { 38426 pts = [ 0.05,0, 0.95,0, 0.95,1, 0.5,0.8, 0.05,1, 0.05,0 ]; 38427 transfo.fac = 0.9; 38428 transfo.posY = 0.4*s+w ; 38429 break; 38430 } 38431 case "hexagon": {
38432 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 ]; 38433 transfo.fac = 0.9; 38434 transfo.posY = 0.5*s+w ; 38435 break; 38436 } 38437 case "diamond": { 38438 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 ]; 38439 transfo.fac = 0.75 ; 38440 transfo.posY = 0.35*s+w ; 38441 break; 38442 } 38443 case "triangle": { 38444 pts = [ 0,0, 1,0, 0.5,1, 0,0 ]; 38445 transfo.fac = 0.6 ; 38446 transfo.posY = 0.3*s+w ; 38447 break; 38448 } 38449 case "sign": { 38450 pts = [ 0.5,0.05, 1,0.95, 0,0.95, 0.5,0.05 ]; 38451 transfo.fac = 0.7 ; 38452 transfo.posY = 0.65*s+w ; 38453 break; 38454 } 38455 case "lozenge": { 38456 pts = [ 0.5,0, 1,0.5, 0.5,1, 0,0.5, 0.5,0 ]; 38457 transfo.fac = 0.7; 38458 break; 38459 } 38460 case "square": 38461 default: { 38462 pts = [ 0,0, 1,0, 1,1, 0,1, 0,0 ]; 38463 break; 38464 } 38465 } 38466 for (var i=0; i<pts.length; i+=2) context.lineTo ( pts[i]*s+w, pts[i+1]*s+w); 38467 } 38468 } 38469 context.closePath(); 38470 return transfo; 38471} 38472/** 38473 * @private 38474 * @param {ol.style.FontSymbol.RenderOptions} renderOptions 38475 * @param {CanvasRenderingContext2D} context 38476 * @param {number} x The origin for the symbol (x). 38477 * @param {number} y The origin for the symbol (y). 38478 */ 38479ol.style.FontSymbol.prototype.drawMarker_ = function(renderOptions, context, x, y, pixelratio) { 38480 var fcolor = this.fill_ ? this.fill_.getColor() : "#000"; 38481 var scolor = this.stroke_ ? this.stroke_.getColor() : "#000"; 38482 if (this.form_ == "none" && this.stroke_ && this.fill_) { 38483 scolor = this.fill_.getColor(); 38484 fcolor = this.stroke_.getColor(); 38485 } 38486 // reset transform 38487 context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0); 38488 // then move to (x, y) 38489 context.translate(x, y); 38490 var tr = this.drawPath_(renderOptions, context, pixelratio); 38491 if (this.fill_) { 38492 if (this.gradient_ && this.form_!="none") { 38493 var grd = context.createLinearGradient(0,0,renderOptions.size/2,renderOptions.size); 38494 grd.addColorStop (1, ol.color.asString(fcolor)); 38495 grd.addColorStop (0, ol.color.asString(scolor)); 38496 context.fillStyle = grd; 38497 } else { 38498 context.fillStyle = ol.color.asString(fcolor); 38499 } 38500 context.fill(); 38501 } 38502 if (this.stroke_ && renderOptions.strokeWidth) { 38503 context.strokeStyle = renderOptions.strokeStyle; 38504 context.lineWidth = renderOptions.strokeWidth; 38505 context.stroke(); 38506 } 38507 // Draw the symbol 38508 if (this.glyph_.char) { 38509 context.font = this.fontStyle_ +' ' 38510 + (2*tr.fac*(this.radius_)*this.fontSize_)+"px " 38511 + this.glyph_.font; 38512 context.strokeStyle = context.fillStyle; 38513 context.lineWidth = renderOptions.strokeWidth * (this.form_ == "none" ? 2:1); 38514 context.fillStyle = ol.color.asString(this.color_ || scolor); 38515 context.textAlign = "center"; 38516 context.textBaseline = "middle"; 38517 var t = this.glyph_.char; 38518 if (renderOptions.strokeWidth && scolor!="transparent") context.strokeText(t, tr.posX, tr.posY); 38519 context.fillText(t, tr.posX, tr.posY); 38520 } 38521 if (this.form_=="ban" && this.stroke_ && renderOptions.strokeWidth) { 38522 context.strokeStyle = renderOptions.strokeStyle; 38523 context.lineWidth = renderOptions.strokeWidth; 38524 var r = this.radius_ + renderOptions.strokeWidth; 38525 var d = this.radius_ * Math.cos(Math.PI/4); 38526 context.moveTo(r + d, r - d); 38527 context.lineTo(r - d, r + d); 38528 context.stroke(); 38529 } 38530}; 38531/** 38532 * @inheritDoc 38533 */ 38534ol.style.FontSymbol.prototype.getChecksum = function() { 38535 var strokeChecksum = (this.stroke_!==null) ? this.stroke_.getChecksum() : '-'; 38536 var fillChecksum = (this.fill_!==null) ? this.fill_.getChecksum() : '-'; 38537 var recalculate = (this.checksums_===null) 38538 || (strokeChecksum != this.checksums_[1] 38539 || fillChecksum != this.checksums_[2] 38540 || this.radius_ != this.checksums_[3] 38541 || this.form_+"-"+this.glyphs_ != this.checksums_[4] 38542 ); 38543 if (recalculate) { 38544 var checksum = 'c' + strokeChecksum + fillChecksum 38545 + ((this.radius_ !== void 0) ? this.radius_.toString() : '-') 38546 + this.form_+"-"+this.glyphs_; 38547 this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_, this.form_+"-"+this.glyphs_]; 38548 } 38549 return this.checksums_[0]; 38550}; 38551 38552/* Copyright (c) 2015 Jean-Marc VIGLINO, 38553 released under the CeCILL-B license (French BSD license)
38554 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 38555* 38556* Photo style for vector features 38557*/ 38558/** 38559 * @classdesc 38560 * Set Photo style for vector features. 38561 * 38562 * @constructor 38563 * @param {} options 38564 * @param { default | square | circle | anchored | folio } options.kind 38565 * @param {boolean} options.crop crop within square, default is false 38566 * @param {Number} options.radius symbol size 38567 * @param {boolean} options.shadow drop a shadow 38568 * @param {ol.style.Stroke} options.stroke 38569 * @param {String} options.src image src 38570 * @param {String} options.crossOrigin The crossOrigin attribute for loaded images. Note that you must provide a crossOrigin value if you want to access pixel data with the Canvas renderer. 38571 * @param {Number} options.offsetX Horizontal offset in pixels. Default is 0. 38572 * @param {Number} options.offsetY Vertical offset in pixels. Default is 0. 38573 * @param {function} options.onload callback when image is loaded (to redraw the layer) 38574 * @extends {ol.style.RegularShape} 38575 * @implements {ol.structs.IHasChecksum} 38576 * @api 38577 */ 38578ol.style.Photo = function(options) { 38579 options = options || {}; 38580 this.sanchor_ = options.kind=="anchored" ? 8:0; 38581 this._shadow = (Number(options.shadow) || 0); 38582 if (!options.stroke) { 38583 options.stroke = new ol.style.Stroke({ width: 0, color: "#000"}) 38584 } 38585 var strokeWidth = options.stroke.getWidth(); 38586 if (strokeWidth<0) strokeWidth = 0; 38587 if (options.kind=='folio') strokeWidth += 6; 38588 options.stroke.setWidth(strokeWidth); 38589 ol.style.RegularShape.call (this, { 38590 radius: options.radius + strokeWidth + this.sanchor_/2 + this._shadow/2, 38591 points: 0, 38592 // No fill to create a hit detection Image (v5) or transparent (v6) 38593 fill: ol.style.RegularShape.prototype.render ? new ol.style.Fill({ color: [0,0,0,0] }) : null 38594 }); 38595 // Hack to get the hit detection Image (v4.6.5 ?) 38596 if (!this.getHitDetectionImage) { 38597 var img = this.getImage(); 38598 if (!this.hitDetectionCanvas_) { 38599 for (var i in this) { 38600 if (this[i] && this[i].getContext && this[i]!==img) { 38601 this.hitDetectionCanvas_ = this[i]; 38602 break; 38603 } 38604 } 38605 } 38606 // Clone canvas for hit detection (old versions) 38607 this.hitDetectionCanvas_ = document.createElement('canvas'); 38608 this.hitDetectionCanvas_.width = img.width; 38609 this.hitDetectionCanvas_.height = img.height; 38610 var hit = this.hitDetectionCanvas_ 38611 this.getHitDetectionImage = function() { 38612 return hit; 38613 } 38614 } 38615 this._stroke = options.stroke; 38616 this._fill = options.fill; 38617 this._crop = options.crop; 38618 this._crossOrigin = options.crossOrigin; 38619 this._kind = options.kind || "default"; 38620 this._radius = options.radius; 38621 this._src = options.src; 38622 this._offset = [options.offsetX ? options.offsetX :0, options.offsetY ? options.offsetY :0]; 38623 this._onload = options.onload; 38624 if (typeof(options.opacity)=='number') this.setOpacity(options.opacity); 38625 if (typeof(options.rotation)=='number') this.setRotation(options.rotation); 38626 this.renderPhoto_(); 38627}; 38628ol.ext.inherits(ol.style.Photo, ol.style.RegularShape); 38629/** Set photo offset 38630 * @param {ol.pixel} offset 38631 */ 38632ol.style.Photo.prototype.setOffset = function(offset) { 38633 this._offset = [offset[0]||0, offset[1]||0]; 38634 this.renderPhoto_(); 38635}; 38636/** 38637 * Clones the style. 38638 * @return {ol.style.Photo} 38639 */ 38640ol.style.Photo.prototype.clone = function() { 38641 var i = new ol.style.Photo({ 38642 stroke: this._stroke, 38643 fill: this._fill, 38644 shadow: this._shadow, 38645 crop: this._crop, 38646 crossOrigin: this._crossOrigin, 38647 kind: this._kind, 38648 radius: this._radius, 38649 src: this._src, 38650 offsetX: this._offset[0], 38651 offsetY: this._offset[1], 38652 opacity: this.getOpacity(), 38653 rotation: this.getRotation() 38654 }); 38655 i.renderPhoto_(); 38656 return i; 38657}; 38658/** 38659 * Draws a rounded rectangle using the current state of the canvas. 38660 * Draw a rectangle if the radius is null. 38661 * @param {Number} x The top left x coordinate 38662 * @param {Number} y The top left y coordinate 38663 * @param {Number} width The width of the rectangle 38664 * @param {Number} height The height of the rectangle 38665 * @param {Number} radius The corner radius. 38666 */ 38667CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { 38668 if (!r) { 38669 this.rect(x,y,w,h); 38670 } else { 38671 if (w < 2 * r) r = w / 2; 38672 if (h < 2 * r) r = h / 2; 38673 this.beginPath(); 38674 this.moveTo(x+r, y); 38675 this.arcTo(x+w, y, x+w, y+h, r); 38676 this.arcTo(x+w, y+h, x, y+h, r); 38677 this.arcTo(x, y+h, x, y, r); 38678 this.arcTo(x, y, x+w, y, r); 38679 this.closePath(); 38680 } 38681 return this; 38682}; 38683/** 38684 * Draw the form without the image 38685 * @private 38686 */ 38687ol.style.Photo.prototype.drawBack_ = function(context, color, strokeWidth, pixelratio) { 38688 var shadow = this._shadow; 38689 var canvas = context.canvas; 38690 context.beginPath(); 38691 context.fillStyle = color; 38692 context.clearRect(0, 0, canvas.width, canvas.height); 38693 var width = canvas.width/pixelratio; 38694 var height = canvas.height/pixelratio; 38695 switch (this._kind) { 38696 case 'square': { 38697 context.rect(0,0, width-shadow, height-shadow); 38698 break; 38699 } 38700 case 'circle': { 38701 context.arc(this._radius+strokeWidth, this._radius+strokeWidth, this._radius+strokeWidth, 0, 2 * Math.PI, false); 38702 break; 38703 } 38704 case 'folio': { 38705 var offset = 6; 38706 strokeWidth -= offset; 38707 context.strokeStyle = 'rgba(0,0,0,0.5)'; 38708 context.lineWidth = 1; 38709 var w = width-shadow-2*offset; 38710 var a = Math.atan(6/w); 38711 context.save(); 38712 context.rotate(-a); 38713 context.translate(-6,2); 38714 context.beginPath(); 38715 context.rect(offset,offset,w,w); 38716 context.stroke(); 38717 context.fill();
38718 context.restore(); 38719 context.save(); 38720 context.translate(6,-1); 38721 context.rotate(a); 38722 context.beginPath(); 38723 context.rect(offset,offset,w,w); 38724 context.stroke(); 38725 context.fill(); 38726 context.restore(); 38727 context.beginPath(); 38728 context.rect(offset,offset,w,w); 38729 context.stroke(); 38730 break; 38731 } 38732 case 'anchored': { 38733 context.roundRect(this.sanchor_/2,0,width-this.sanchor_-shadow, height-this.sanchor_-shadow, strokeWidth); 38734 context.moveTo(width/2-this.sanchor_-shadow/2,height-this.sanchor_-shadow); 38735 context.lineTo(width/2+this.sanchor_-shadow/2,height-this.sanchor_-shadow); 38736 context.lineTo(width/2-shadow/2,height-shadow);break; 38737 } 38738 default: { 38739 // roundrect 38740 context.roundRect(0,0,width-shadow, height-shadow, strokeWidth); 38741 break; 38742 } 38743 } 38744 context.closePath(); 38745}; 38746/** 38747 * @private 38748 */ 38749ol.style.Photo.prototype.renderPhoto_ = function(pixelratio) { 38750 if (!pixelratio) { 38751 if (this.getPixelRatio) { 38752 pixelratio = window.devicePixelRatio; 38753 this.renderPhoto_(pixelratio); 38754 } else { 38755 this.renderPhoto_(1); 38756 } 38757 return; 38758 } 38759 var strokeStyle; 38760 var strokeWidth = 0; 38761 if (this._stroke) { 38762 strokeStyle = ol.color.asString(this._stroke.getColor()); 38763 strokeWidth = this._stroke.getWidth(); 38764 } 38765 var canvas = this.getImage(pixelratio); 38766 // Draw hitdetection image 38767 var context = this.getHitDetectionImage().getContext('2d'); 38768 context.save(); 38769 context.setTransform(1,0,0,1,0,0) 38770 this.drawBack_(context,"#000",strokeWidth, 1); 38771 context.fill(); 38772 context.restore(); 38773 // Draw the image 38774 context = canvas.getContext('2d'); 38775 context.save(); 38776 context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0); 38777 this.drawBack_(context,strokeStyle,strokeWidth, pixelratio); 38778 // Draw a shadow 38779 if (this._shadow) { 38780 context.shadowColor = 'rgba(0,0,0,0.5)'; 38781 context.shadowBlur = pixelratio*this._shadow/2; 38782 context.shadowOffsetX = pixelratio*this._shadow/2; 38783 context.shadowOffsetY = pixelratio*this._shadow/2; 38784 } 38785 context.fill(); 38786 context.restore(); 38787 var self = this; 38788 var img = this.img_ = new Image(); 38789 if (this._crossOrigin) img.crossOrigin = this._crossOrigin; 38790 img.src = this._src; 38791 // Draw image 38792 if (img.width) { 38793 self.drawImage_(img); 38794 } else { 38795 img.onload = function() { 38796 self.drawImage_(img); 38797 // Force change (?!) 38798 // self.setScale(1); 38799 if (self._onload) self._onload(); 38800 }; 38801 } 38802 // Set anchor 38803 var a = this.getAnchor(); 38804 a[0] = (canvas.width/pixelratio - this._shadow)/2 - this._offset[0]; 38805 if (this.sanchor_) { 38806 a[1] = canvas.height/pixelratio - this._shadow - this._offset[1]; 38807 } else { 38808 a[1] = (canvas.height/pixelratio - this._shadow)/2 - this._offset[1]; 38809 } 38810}; 38811/** 38812 * Draw an timage when loaded 38813 * @private 38814 */ 38815ol.style.Photo.prototype.drawImage_ = function(img) { 38816 var pixelratio = window.devicePixelRatio; 38817 var canvas = this.getImage(pixelratio); 38818 // Remove the circle on the canvas 38819 var context = (canvas.getContext('2d')); 38820 var strokeWidth = 0; 38821 if (this._stroke) strokeWidth = this._stroke.getWidth(); 38822 var size = 2*this._radius; 38823 context.save(); 38824 if (ol.style.RegularShape.prototype.render) context.setTransform(pixelratio,0,0,pixelratio,0,0); 38825 if (this._kind=='circle') { 38826 context.beginPath(); 38827 context.arc(this._radius+strokeWidth, this._radius+strokeWidth, this._radius, 0, 2 * Math.PI, false); 38828 context.clip(); 38829 } 38830 var s, x, y, w, h, sx, sy, sw, sh; 38831 // Crop the image to a square vignette 38832 if (this._crop) { 38833 s = Math.min (img.width/size, img.height/size); 38834 sw = sh = s*size; 38835 sx = (img.width-sw)/2; 38836 sy = (img.height-sh)/2; 38837 x = y = 0; 38838 w = h = size+1; 38839 } else { 38840 // Fit the image to the size 38841 s = Math.min (size/img.width, size/img.height); 38842 sx = sy = 0; 38843 sw = img.width; 38844 sh = img.height; 38845 w = s*sw; 38846 h = s*sh; 38847 x = (size-w)/2; 38848 y = (size-h)/2; 38849 } 38850 x += strokeWidth + this.sanchor_/2; 38851 y += strokeWidth; 38852 context.drawImage(img, sx, sy, sw, sh, x, y, w, h); 38853 // Draw a circle to avoid aliasing on clip 38854 if (this._kind=='circle' && strokeWidth) { 38855 context.beginPath(); 38856 context.strokeStyle = ol.color.asString(this._stroke.getColor()); 38857 context.lineWidth = strokeWidth/4; 38858 context.arc(this._radius+strokeWidth, this._radius+strokeWidth, this._radius, 0, 2 * Math.PI, false); 38859 context.stroke(); 38860 }
38861 context.restore(); 38862}; 38863 38864/* Copyright (c) 2019 Jean-Marc VIGLINO, 38865 released under the CeCILL-B license (French BSD license) 38866 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 38867*/ 38868/** Profile style 38869 * Draw a profile on the map 38870 * @extends {ol.style.Style} 38871 * @constructor 38872 * @param {Object} options 38873 * @param {ol.style.Stroke} options.stroke 38874 * @param {ol.style.Fill} options.fill 38875 * @param {number} options.scale z scale 38876 * @param {number} options.zIndex 38877 * @param {ol.geom.Geometry} options.geometry 38878 */ 38879ol.style.Profile = function(options) { 38880 if (!options) options = {}; 38881 ol.style.Style.call (this, { 38882 renderer: this._render.bind(this), 38883 zIndex: options.zIndex, 38884 geometry: options.geometry 38885 }); 38886 this.setStroke(options.stroke); 38887 this.setFill(options.fill); 38888 this.setScale(options.scale); 38889}; 38890ol.ext.inherits(ol.style.Profile, ol.style.Style); 38891/** Set style stroke 38892 * @param {ol.style.Stroke} 38893 */ 38894ol.style.Profile.prototype.setStroke = function(stroke) { 38895 this._stroke = stroke || new ol.style.Stroke({ color: '#fff', width: 1 }); 38896} 38897/** Get style stroke 38898 * @return {ol.style.Stroke} 38899 */ 38900ol.style.Profile.prototype.getStroke = function() { 38901 return this._stroke; 38902} 38903/** Set style stroke 38904 * @param {ol.style.Fill} 38905 */ 38906ol.style.Profile.prototype.setFill = function(fill) { 38907 this._fill = fill || new ol.style.Fill({ color: 'rgba(255,255,255,.3' }); 38908} 38909/** Get style stroke 38910 * @return {ol.style.Fill} 38911 */ 38912ol.style.Profile.prototype.getFill = function() { 38913 return this._fill; 38914} 38915/** Set z scale 38916 * @param {number} 38917 */ 38918ol.style.Profile.prototype.setScale = function(sc) { 38919 this._scale = sc || .2; 38920} 38921/** Get z scale 38922 * @return {number} 38923 */ 38924ol.style.Profile.prototype.getScale = function() { 38925 return this._scale; 38926} 38927/** Renderer function 38928 * @param {Array<ol.coordinate>} geom The pixel coordinates of the geometry in GeoJSON notation 38929 * @param {ol.render.State} e The olx.render.State of the layer renderer 38930 */ 38931ol.style.Profile.prototype._render = function(geom, e) { 38932 if (!/Z/.test(e.feature.getGeometry().getLayout())) return; 38933 var g = e.geometry.getCoordinates(); 38934 switch (e.geometry.getType()) { 38935 case 'LineString': { 38936 this._renderLine(geom, g, e.feature.getGeometry(), e); 38937 break; 38938 } 38939 case 'MultiLineString': {
38940 e.feature.getGeometry().getLineStrings().forEach(function(l, i) { 38941 this._renderLine(geom[i], g[i], l, e); 38942 }.bind(this)); 38943 break; 38944 } 38945 case 'Point': { 38946 break; 38947 } 38948 } 38949}; 38950/** @private */ 38951ol.style.Profile.prototype._renderLine = function(geom, g, l, e) { 38952 var i, p, ctx = e.context; 38953 var cos = Math.cos(e.rotation) 38954 var sin = Math.sin(e.rotation) 38955 // var a = e.pixelRatio / e.resolution; 38956 var a = ol.coordinate.dist2d(geom[0],geom[1]) / ol.coordinate.dist2d(g[0],g[1]) 38957 var dx = geom[0][0] - g[0][0] * a *cos - g[0][1] * a *sin ; 38958 var dy = geom[0][1] - g[0][0] * a * sin + g[0][1] * a * cos; 38959 geom = l.getCoordinates(); 38960 var dz = Infinity; 38961 for (i=0; p=geom[i]; i++) { 38962 var x = dx + p[0] * a * cos + p[1] * a * sin; 38963 var y = dy + p[0] * a * sin - p[1] * a * cos; 38964 dz = Math.min(dz, p[2]); 38965 geom[i] = [x, y, p[2]]; 38966 } 38967 ctx.save(); 38968 ctx.fillStyle = ol.color.asString(this.getFill().getColor()); 38969 ctx.strokeStyle = ol.color.asString(this.getStroke().getColor()); 38970 ctx.strokeWidth = this.getStroke().getWidth(); 38971 var p0 = geom[0]; 38972 var ez = this.getScale() * e.pixelRatio; 38973 for (i=1; p=geom[i]; i++) { 38974 ctx.beginPath(); 38975 ctx.moveTo(p0[0],p0[1]); 38976 ctx.lineTo(p[0],p[1]); 38977 ctx.lineTo(p[0],p[1]-(p[2]-dz)*ez); 38978 ctx.lineTo(p0[0],p0[1]-(p0[2]-dz)*ez); 38979 ctx.lineTo(p0[0],p0[1]); 38980 ctx.fill(); 38981 p0 = p; 38982 } 38983 p0 = geom[0]; 38984 ctx.beginPath(); 38985 ctx.moveTo(p0[0],p0[1]-(p0[2]-dz)*ez); 38986 for (i=1; p=geom[i]; i++) { 38987 ctx.lineTo(p[0],p[1]-(p[2]-dz)*ez); 38988 } 38989 ctx.stroke(); 38990 ctx.restore(); 38991}; 38992 38993/** Add a setTextPath style to draw text along linestrings 38994@toto letterpadding/spacing, wordpadding/spacing 38995*/ 38996(function() 38997{ 38998/** Internal drawing function called on postcompose 38999* @param {ol.eventPoscompose} e postcompose event 39000*/ 39001function drawTextPath (e) 39002{ // Prent drawing at large resolution 39003 if (e.frameState.viewState.resolution > this.textPathMaxResolution_) return; 39004 var extent = e.frameState.extent; 39005 var c2p = e.frameState.coordinateToPixelTransform; 39006 // Get pixel path with coordinates 39007 var k; 39008 function getPath(c, readable) 39009 { var path1 = []; 39010 for (k=0; k<c.length; k++) 39011 { path1.push(c2p[0]*c[k][0]+c2p[1]*c[k][1]+c2p[4]); 39012 path1.push(c2p[2]*c[k][0]+c2p[3]*c[k][1]+c2p[5]); 39013 } 39014 // Revert line ? 39015 if (readable && path1[0]>path1[path1.length-2]) 39016 { var path2 = []; 39017 for (k=path1.length-2; k>=0; k-=2) 39018 { path2.push(path1[k]); 39019 path2.push(path1[k+1]); 39020 } 39021 return path2; 39022 } 39023 else return path1; 39024 } 39025 var ctx = e.context; 39026 ctx.save(); 39027 ctx.scale(e.frameState.pixelRatio,e.frameState.pixelRatio); 39028 var features = this.getSource().getFeaturesInExtent(extent); 39029 for (var i=0, f; f=features[i]; i++) 39030 { { var style = this.textPathStyle_(f,e.frameState.viewState.resolution); 39031 for (var s,j=0; s=style[j]; j++) 39032 { 39033 var g = s.getGeometry() || f.getGeometry(); 39034 var c; 39035 switch (g.getType()) 39036 { case "LineString": c = g.getCoordinates(); break; 39037 case "MultiLineString": c = g.getLineString(0).getCoordinates(); break; 39038 default: continue; 39039 } 39040 var st = s.getText(); 39041 var path = getPath(c, st.getRotateWithView() ); 39042 ctx.font = st.getFont(); 39043 ctx.textBaseline = st.getTextBaseline(); 39044 ctx.textAlign = st.getTextAlign(); 39045 ctx.lineWidth = st.getStroke() ? (st.getStroke().getWidth()||0) : 0; 39046 ctx.strokeStyle = st.getStroke() ? (st.getStroke().getColor()||"#fff") : "#fff";
39047 ctx.fillStyle = st.getFill() ? st.getFill().getColor()||"#000" : "#000"; 39048 // New params 39049 ctx.textJustify = st.getTextAlign()=="justify"; 39050 ctx.textOverflow = st.getTextOverflow ? st.getTextOverflow():""; 39051 ctx.minWidth = st.getMinWidth ? st.getMinWidth():0; 39052 // Draw textpath 39053 ctx.textPath(st.getText()||f.get("name"), path); 39054 } 39055 } 39056 } 39057 ctx.restore(); 39058} 39059/** Set the style for features. 39060* This can be a single style object, an array of styles, or a function that takes a feature and resolution and 39061* returns an array of styles. If it is undefined the default style is used. 39062* If it is null the layer has no style (a null style). 39063* See ol.style for information on the default style. 39064* @param {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction} style 39065* @param {Number} maxResolution to display text, default: 0 39066*/ 39067ol.layer.Vector.prototype.setTextPathStyle = function(style, maxResolution) 39068{ 39069 // Remove existing style 39070 if (style===null) 39071 { if (this.textPath_) this.unByKey(this.textPath_); 39072 this.textPath_ = null; 39073 this.changed(); 39074 return; 39075 } 39076 // New postcompose 39077 if (!this.textPath_) 39078 { this.textPath_ = this.on(['postcompose','postrender'], drawTextPath.bind(this)); 39079 } 39080 // Set textPathStyle 39081 if (style===undefined) 39082 { style = [ new ol.style.Style({ text: new ol.style.Text()}) ]; 39083 } 39084 if (typeof(style) == "function") this.textPathStyle_ = style; 39085 else this.textPathStyle_ = function() { return style; }; 39086 this.textPathMaxResolution_ = Number(maxResolution) || Number.MAX_VALUE; 39087 // Force redraw 39088 this.changed(); 39089} 39090/** Add new properties to ol.style.Text 39091* to use with ol.layer.Vector.prototype.setTextPathStyle 39092* @constructor 39093* @param {} options 39094* @param {visible|ellipsis|string} textOverflow 39095* @param {number} minWidth minimum width (px) to draw text, default 0 39096*/ 39097ol.style.TextPath = function(options) 39098{ if (!options) options={}; 39099 ol.style.Text.call (this, options); 39100 this.textOverflow_ = typeof(options.textOverflow)!="undefined" ? options.textOverflow : "visible"; 39101 this.minWidth_ = options.minWidth || 0; 39102} 39103ol.ext.inherits(ol.style.TextPath, ol.style.Text); 39104ol.style.TextPath.prototype.getTextOverflow = function() 39105{ return this.textOverflow_; 39106}; 39107ol.style.TextPath.prototype.getMinWidth = function() 39108{ return this.minWidth_; 39109}; 39110/**/ 39111})(); 39112/** CanvasRenderingContext2D: draw text along path 39113* @param {string} text 39114* @param {Array<Number>} path 39115*/ 39116CanvasRenderingContext2D.prototype.textPath = function (text, path) 39117{ 39118 var ctx = this; 39119 function dist2D(x1,y1,x2,y2) 39120 { var dx = x2-x1; 39121 var dy = y2-y1; 39122 return Math.sqrt(dx*dx+dy*dy); 39123 } 39124 var di, dpos=0; 39125 var pos=2; 39126 function getPoint(path, dl) 39127 { if (!di || dpos+di<dl) 39128 { for (; pos<path.length; ) 39129 { di = dist2D(path[pos-2],path[pos-1],path[pos],path[pos+1]); 39130 if (dpos+di>dl) break; 39131 pos += 2; 39132 if (pos>=path.length) break; 39133 dpos += di; 39134 } 39135 } 39136 var x, y, a, dt = dl-dpos; 39137 if (pos>=path.length) 39138 { pos = path.length-2; 39139 } 39140 if (!dt) 39141 { x = path[pos-2]; 39142 y = path[pos-1]; 39143 a = Math.atan2(path[pos+1]-path[pos-1], path[pos]-path[pos-2]); 39144 } 39145 else 39146 { x = path[pos-2]+ (path[pos]-path[pos-2])*dt/di; 39147 y = path[pos-1]+(path[pos+1]-path[pos-1])*dt/di; 39148 a = Math.atan2(path[pos+1]-path[pos-1], path[pos]-path[pos-2]); 39149 } 39150 return [x,y,a]; 39151 } 39152 var letterPadding = ctx.measureText(" ").width *0.25; 39153 var start = 0; 39154 var d = 0; 39155 for (var i=2; i<path.length; i+=2) 39156 { d += dist2D(path[i-2],path[i-1],path[i],path[i+1]) 39157 } 39158 if (d < ctx.minWidth) return; 39159 var nbspace = text.split(" ").length -1; 39160 // Remove char for overflow 39161 if (ctx.textOverflow != "visible") 39162 { if (d < ctx.measureText(text).width + (text.length-1 + nbspace) * letterPadding) 39163 { var overflow = (ctx.textOverflow=="ellipsis") ? '\u2026' : ctx.textOverflow; 39164 do 39165 { nbspace = text.split(" ").length -1; 39166 text = text.slice(0,text.length-1); 39167 } while (text && d < ctx.measureText(text+overflow).width + (text.length + overflow.length-1 + nbspace) * letterPadding) 39168 text += overflow; 39169 } 39170 } 39171 switch (ctx.textJustify || ctx.textAlign) 39172 { case true: // justify 39173 case "center": 39174 case "end": 39175 case "right": 39176 { // Text align 39177 if (ctx.textJustify) 39178 { start = 0; 39179 letterPadding = (d - ctx.measureText(text).width) / (text.length-1 + nbspace); 39180 } 39181 else 39182 { start = d - ctx.measureText(text).width - (text.length + nbspace) * letterPadding;
39183 if (ctx.textAlign == "center") start /= 2; 39184 } 39185 break; 39186 } 39187 default: break; 39188 } 39189 for (var t=0; t<text.length; t++) 39190 { var letter = text[t]; 39191 var wl = ctx.measureText(letter).width; 39192 var p = getPoint(path, start+wl/2); 39193 ctx.save(); 39194 ctx.textAlign = "center"; 39195 ctx.translate(p[0], p[1]); 39196 ctx.rotate(p[2]); 39197 if (ctx.lineWidth) ctx.strokeText(letter,0,0); 39198 ctx.fillText(letter,0,0); 39199 ctx.restore(); 39200 start += wl+letterPadding*(letter==" "?2:1); 39201 } 39202}; 39203//NB: (Not confirmed)To use this module, you just have to : 39204// import('ol-ext/layer/getpreview') 39205/* Copyright (c) 2015 Jean-Marc VIGLINO, 39206 released under the CeCILL-B license (French BSD license) 39207 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 39208* 39209* Shadow image style for point vector features 39210*/ 39211/** 39212 * @classdesc 39213 * Set Shadow style for point vector features. 39214 * 39215 * @constructor 39216 * @param {} options Options. 39217 * @param {ol.style.Fill | undefined} options.fill fill style, default rgba(0,0,0,0.5) 39218 * @param {number} options.radius point radius 39219 * @param {number} options.blur lur radius, default radius/3 39220 * @param {number} options.offsetX x offset, default 0 39221 * @param {number} options.offsetY y offset, default 0 39222 * @extends {ol.style.RegularShape} 39223 * @api 39224 */ 39225ol.style.Shadow = function(options) { 39226 options = options || {}; 39227 if (!options.fill) options.fill = new ol.style.Fill({ color: "rgba(0,0,0,0.5)" }); 39228 ol.style.RegularShape.call (this,{ radius: options.radius, fill: options.fill }); 39229 this._fill = options.fill; 39230 this._radius = options.radius; 39231 this._blur = options.blur===0 ? 0 : options.blur || options.radius/3; 39232 this._offset = [options.offsetX ? options.offsetX : 0, options.offsetY ? options.offsetY : 0]; 39233 this.renderShadow_(); 39234}; 39235ol.ext.inherits(ol.style.Shadow, ol.style.RegularShape); 39236/** 39237 * Clones the style. 39238 * @return {ol.style.Shadow} 39239 */ 39240ol.style.Shadow.prototype.clone = function() { 39241 var s = new ol.style.Shadow({ 39242 fill: this._fill, 39243 radius: this._radius, 39244 blur: this._blur, 39245 offsetX: this._offset[0], 39246 offsetY: this._offset[1] 39247 }); 39248 s.setScale(this.getScale()); 39249 s.setOpacity(this.getOpacity()); 39250 return s; 39251}; 39252/** 39253 * @private 39254 */ 39255ol.style.Shadow.prototype.renderShadow_ = function(pixelratio) { 39256 if (!pixelratio) { 39257 if (this.getPixelRatio) { 39258 pixelratio = window.devicePixelRatio; 39259 this.renderShadow_(pixelratio); 39260 if (this.getPixelRatio && pixelratio!==1) this.renderShadow_(1); 39261 } else { 39262 this.renderShadow_(1); 39263 } 39264 return; 39265 } 39266 var radius = this._radius; 39267 var canvas = this.getImage(pixelratio); 39268 // Remove the circle on the canvas 39269 var context = (canvas.getContext('2d')); 39270 context.clearRect(0, 0, canvas.width, canvas.height); 39271 context.save(); 39272 context.beginPath(); 39273 context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0); 39274 context.scale(1,0.5); 39275 context.arc(radius, -radius, radius-this._blur, 0, 2 * Math.PI, false); 39276 context.fillStyle = '#000'; 39277 context.shadowColor = this._fill.getColor(); 39278 context.shadowBlur = 0.7*this._blur*pixelratio; 39279 context.shadowOffsetX = 0; 39280 context.shadowOffsetY = 1.5*radius*pixelratio; 39281 context.closePath(); 39282 context.fill(); 39283 context.shadowColor = 'transparent'; 39284 context.restore(); 39285 // Set anchor 39286 var a = this.getAnchor(); 39287 a[0] = canvas.width /2 - this._offset[0]; 39288 a[1] = canvas.height /2 - this._offset[1]; 39289} 39290 39291/* Copyright (c) 2018 Jean-Marc VIGLINO, 39292 released under the CeCILL-B license (French BSD license) 39293 (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 39294*/ 39295/** 39296 * @classdesc 39297 * Stroke style with named pattern 39298 * 39299 * @constructor 39300 * @param {any} options 39301 * @param {ol.style.Image|undefined} options.image an image pattern, image must be preloaded to draw on first call 39302 * @param {number|undefined} options.opacity opacity with image pattern, default:1 39303 * @param {olx.style.fillPattern} options.pattern pattern name (override by image option) 39304 * @param {ol.colorLike} options.color pattern color 39305 * @param {ol.style.Fill} options.fill fill color (background) 39306 * @param {number} options.offset pattern offset for hash/dot/circle/cross pattern 39307 * @param {number} options.size line size for hash/dot/circle/cross pattern 39308 * @param {number} options.spacing spacing for hash/dot/circle/cross pattern 39309 * @param {number|bool} options.angle angle for hash pattern / true for 45deg dot/circle/cross 39310 * @param {number} options.scale pattern scale 39311 * @extends {ol.style.Fill} 39312 * @implements {ol.structs.IHasChecksum} 39313 * @api 39314 */ 39315ol.style.StrokePattern = function(options) 39316{ if (!options) options = {}; 39317 var pattern, i; 39318 var canvas = this.canvas_ = document.createElement('canvas'); 39319 var scale = Number(options.scale)>0 ? Number(options.scale) : 1; 39320 var ratio = scale*ol.has.DEVICE_PIXEL_RATIO || ol.has.DEVICE_PIXEL_RATIO; 39321 var ctx = canvas.getContext('2d'); 39322 if (options.image) 39323 { options.image.load(); 39324 var img = options.image.getImage(); 39325 if (img.width) 39326 { canvas.width = Math.round(img.width *ratio);
39327 canvas.height = Math.round(img.height *ratio); 39328 ctx.globalAlpha = typeof(options.opacity) == 'number' ? options.opacity:1; 39329 ctx.drawImage(img, 0,0, img.width, img.height, 0, 0, canvas.width, canvas.height); 39330 pattern = ctx.createPattern(canvas, 'repeat'); 39331 } 39332 else 39333 { var self = this; 39334 pattern = [0,0,0,0]; 39335 img.onload = function () 39336 { canvas.width = Math.round(img.width *ratio); 39337 canvas.height = Math.round(img.height *ratio); 39338 ctx.globalAlpha = typeof(options.opacity) == 'number' ? options.opacity:1; 39339 ctx.drawImage(img, 0,0, img.width, img.height, 0, 0, canvas.width, canvas.height); 39340 pattern = ctx.createPattern(canvas, 'repeat'); 39341 self.setColor(pattern); 39342 } 39343 } 39344 } 39345 else 39346 { var pat = this.getPattern_(options); 39347 canvas.width = Math.round(pat.width *ratio); 39348 canvas.height = Math.round(pat.height *ratio); 39349 ctx.beginPath(); 39350 if (options.fill) 39351 { ctx.fillStyle = ol.color.asString(options.fill.getColor()); 39352 ctx.fillRect(0,0, canvas.width, canvas.height); 39353 } 39354 ctx.scale(ratio,ratio); 39355 ctx.lineCap = "round"; 39356 ctx.lineWidth = pat.stroke || 1; 39357 ctx.fillStyle = ol.color.asString(options.color||"#000"); 39358 ctx.strokeStyle = ol.color.asString(options.color||"#000"); 39359 if (pat.circles) for (i=0; i<pat.circles.length; i++) 39360 { var ci = pat.circles[i]; 39361 ctx.beginPath(); 39362 ctx.arc(ci[0], ci[1], ci[2], 0,2*Math.PI); 39363 if (pat.fill) ctx.fill(); 39364 if (pat.stroke) ctx.stroke(); 39365 } 39366 if (!pat.repeat) pat.repeat=[[0,0]]; 39367 if (pat.char) 39368 { ctx.font = pat.font || (pat.width)+"px Arial"; 39369 ctx.textAlign = 'center'; 39370 ctx.textBaseline = 'middle'; 39371 if (pat.angle) 39372 { ctx.fillText(pat.char, pat.width/4, pat.height/4); 39373 ctx.fillText(pat.char, 5*pat.width/4, 5*pat.height/4); 39374 ctx.fillText(pat.char, pat.width/4, 5*pat.height/4); 39375 ctx.fillText(pat.char, 5*pat.width/4, pat.height/4); 39376 ctx.fillText(pat.char, 3*pat.width/4, 3*pat.height/4); 39377 ctx.fillText(pat.char, -pat.width/4, -pat.height/4); 39378 ctx.fillText(pat.char, 3*pat.width/4, -pat.height/4); 39379 ctx.fillText(pat.char, -pat.width/4, 3*pat.height/4); 39380 } 39381 else ctx.fillText(pat.char, pat.width/2, pat.height/2); 39382 } 39383 if (pat.lines) for (i=0; i<pat.lines.length; i++) for (var r=0; r<pat.repeat.length; r++) 39384 { var li = pat.lines[i]; 39385 ctx.beginPath(); 39386 ctx.moveTo(li[0]+pat.repeat[r][0],li[1]+pat.repeat[r][1]); 39387 for (var k=2; k<li.length; k+=2) 39388 { ctx.lineTo(li[k]+pat.repeat[r][0],li[k+1]+pat.repeat[r][1]); 39389 } 39390 if (pat.fill) ctx.fill(); 39391 if (pat.stroke) ctx.stroke(); 39392 ctx.save() 39393 ctx.strokeStyle = 'red'; 39394 ctx.strokeWidth = 0.1; 39395 //ctx.strokeRect(0,0,canvas.width,canvas.height); 39396 ctx.restore() 39397 } 39398 pattern = ctx.createPattern(canvas, 'repeat'); 39399 if (options.offset) 39400 { var offset = options.offset; 39401 if (typeof(offset) == "number") offset = [offset,offset]; 39402 if (offset instanceof Array) 39403 { var dx = Math.round((offset[0]*ratio)); 39404 var dy = Math.round((offset[1]*ratio)); 39405 // New pattern 39406 ctx.scale(1/ratio,1/ratio) 39407 ctx.clearRect(0,0,canvas.width,canvas.height); 39408 ctx.translate(dx,dy); 39409 ctx.fillStyle = pattern; 39410 ctx.fillRect(-dx, -dy, canvas.width,canvas.height); 39411 pattern = ctx.createPattern(canvas, 'repeat'); 39412 } 39413 } 39414 } 39415 options.color = pattern; 39416 ol.style.Stroke.call (this, options); 39417}; 39418ol.ext.inherits(ol.style.StrokePattern, ol.style.Stroke); 39419/** 39420 * Clones the style. 39421 * @return {ol.style.StrokePattern} 39422 */ 39423ol.style.StrokePattern.prototype.clone = function() { 39424 var s = ol.style.Fill.prototype.clone.call(this); 39425 s.canvas_ = this.canvas_; 39426 return s; 39427}; 39428/** Get canvas used as pattern 39429* @return {canvas} 39430*/ 39431ol.style.StrokePattern.prototype.getImage = function() 39432{ return this.canvas_; 39433} 39434/** Get pattern 39435* @param {olx.style.FillPatternOption} 39436*/ 39437ol.style.StrokePattern.prototype.getPattern_ = function(options) 39438{ var pat = ol.style.FillPattern.prototype.patterns[options.pattern] 39439 || ol.style.FillPattern.prototype.patterns.dot; 39440 var d = Math.round(options.spacing)||10; 39441 var size; 39442// var d2 = Math.round(d/2)+0.5; 39443 switch (options.pattern) 39444 { case 'dot': 39445 case 'circle': 39446 { size = options.size===0 ? 0 : options.size/2 || 2; 39447 if (!options.angle) 39448 { pat.width = pat.height = d; 39449 pat.circles = [[ d/2, d/2, size ]] 39450 if (options.pattern=='circle') 39451 { pat.circles = pat.circles.concat([ 39452 [ d/2+d, d/2, size ], 39453 [ d/2-d, d/2, size ], 39454 [ d/2, d/2+d, size ], 39455 [ d/2, d/2-d, size ], 39456 [ d/2+d, d/2+d, size ], 39457 [ d/2+d, d/2-d, size ], 39458 [ d/2-d, d/2+d, size ], 39459 [ d/2-d, d/2-d, size ] ]) 39460 } 39461 } 39462 else 39463 { d = pat.width = pat.height = Math.round(d*1.4); 39464 pat.circles = [[ d/4, d/4, size ], [ 3*d/4, 3*d/4, size ]]; 39465 if (options.pattern=='circle') 39466 { pat.circles = pat.circles.concat([ 39467 [ d/4+d, d/4, size ], 39468 [ d/4, d/4+d, size ], 39469 [ 3*d/4-d, 3*d/4, size ], 39470 [ 3*d/4, 3*d/4-d, size ], 39471 [ d/4+d, d/4+d, size ], 39472 [ 3*d/4-d, 3*d/4-d, size ] ]); 39473 } 39474 } 39475 break; 39476 } 39477 case 'tile': 39478 case 'square': 39479 { size = options.size===0 ? 0 : options.size/2 || 2; 39480 if (!options.angle) 39481 { pat.width = pat.height = d; 39482 pat.lines = [[ d/2-size, d/2-size, d/2+size, d/2-size, d/2+size, d/2+size, d/2-size,d/2+size, d/2-size, d/2-size ]] 39483 } 39484 else 39485 { pat.width = pat.height = d; 39486 //size *= Math.sqrt(2); 39487 pat.lines = [[ d/2-size,d/2, d/2,d/2-size, d/2+size,d/2, d/2,d/2+size, d/2-size,d/2 ]] 39488 } 39489 if (options.pattern=='square') pat.repeat = [[0,0], [0,d], [d,0], [0,-d], [-d,0], [-d,-d], [d,d], [-d,d], [d,-d] ] 39490 break; 39491 } 39492 case 'cross': 39493 { // Limit angle to 0 | 45 39494 if (options.angle) options.angle = 45; 39495 } 39496 // fallthrough 39497 case 'hatch': 39498 { var a = Math.round(((options.angle||0)-90)%360); 39499 if (a>180) a -= 360; 39500 a *= Math.PI/180; 39501 var cos = Math.cos(a); 39502 var sin = Math.sin(a); 39503 if (Math.abs(sin)<0.0001) 39504 { pat.width = pat.height = d; 39505 pat.lines = [ [ 0,0.5, d, 0.5 ] ]; 39506 pat.repeat = [ [0,0], [0,d] ]; 39507 } 39508 else if (Math.abs(cos)<0.0001) 39509 { pat.width = pat.height = d; 39510 pat.lines = [ [ 0.5,0, 0.5, d] ]; 39511 pat.repeat = [ [0,0], [d,0] ]; 39512 if (options.pattern=='cross') 39513 { pat.lines.push ([ 0,0.5, d, 0.5 ]);
39514 pat.repeat.push([0,d]); 39515 } 39516 } 39517 else 39518 { var w = pat.width = Math.round(Math.abs(d/sin)) || 1; 39519 var h = pat.height = Math.round(Math.abs(d/cos)) || 1; 39520 if (options.pattern=='cross') 39521 { pat.lines = [ [-w,-h, 2*w,2*h], [2*w,-h, -w,2*h] ]; 39522 pat.repeat = [ [0,0] ]; 39523 } 39524 else if (cos*sin>0) 39525 { pat.lines = [ [-w,-h, 2*w,2*h] ]; 39526 pat.repeat = [ [0,0], [w,0], [0,h] ]; 39527 } 39528 else 39529 { pat.lines = [ [2*w,-h, -w,2*h] ]; 39530 pat.repeat = [ [0,0], [-w,0], [0,h] ]; 39531 } 39532 } 39533 pat.stroke = options.size===0 ? 0 : options.size||4; 39534 break; 39535 } 39536 default: { 39537 break; 39538 } 39539 } 39540 return pat 39541}; 39542 39543ol.style.Style.defaultStyle; 39544(function() { 39545// Style 39546var white = [255, 255, 255, 1]; 39547var blue = [0, 153, 255, 1]; 39548var width = 3; 39549var defaultEditStyle = [ 39550 new ol.style.Style({ 39551 stroke: new ol.style.Stroke({ color: white, width: width + 2 }) 39552 }), 39553 new ol.style.Style({ 39554 image: new ol.style.Circle({ 39555 radius: width * 2, 39556 fill: new ol.style.Fill({ color: blue }), 39557 stroke: new ol.style.Stroke({ color: white, width: width / 2 }) 39558 }), 39559 stroke: new ol.style.Stroke({ color: blue, width: width }), 39560 fill: new ol.style.Fill({ 39561 color: [255, 255, 255, 0.5] 39562 }) 39563 }) 39564]; 39565/** 39566 * Get the default style 39567 * @param {boolean|*} [edit] true to get editing style or a { color, fillColor } object, default get default blue style 39568 * @return {Array<ol.style.Style>} 39569 */ 39570ol.style.Style.defaultStyle = function(edit) { 39571 if (edit===true) { 39572 return defaultEditStyle; 39573 } else { 39574 edit = edit || {}; 39575 var fill = new ol.style.Fill({ 39576 color: edit.fillColor || 'rgba(255,255,255,0.4)' 39577 }); 39578 var stroke = new ol.style.Stroke({ 39579 color: edit.color || '#3399CC', 39580 width: 1.25 39581 }); 39582 var style = new ol.style.Style({ 39583 image: new ol.style.Circle({ 39584 fill: fill, 39585 stroke: stroke, 39586 radius: 5 39587 }), 39588 fill: fill, 39589 stroke: stroke 39590 }); 39591 return [ style ]; 39592 } 39593}; 39594})(); 39595 39596/* 39597* Copyright (c) 2015 Jean-Marc VIGLINO, 39598* released under the CeCILL-B license (French BSD license) 39599* (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). 39600*/ 39601/** 39602 * Get a style for Geoportail WFS features 39603 * 39604 * @param {String} options.typeName 39605 * @param {any} options 39606 * @param {boolean|number} options.sens true show flow direction or a max resolution to show it, default false 39607 * @param {boolean} options.vert 'vert' road section (troncon_de_route) style, default false 39608 * @param {boolean} options.symbol show symbol on buildings (batiment), default false 39609 * @return {Array<ol.style.Style>} 39610 */ 39611ol.style.geoportailStyle; 39612(function(){ 39613var cache = {}; 39614var styleCount = 0; 39615// Troncon de route 39616function troncon_de_route(options) { 39617 // Get color according to road properties 39618 var getColor = function (feature) { 39619 if (options.vert && feature.get('itineraire_vert')) { 39620 if (feature.get('position_par_rapport_au_sol') < 0) return [0, 128, 0, .7]; 39621 else if (feature.get('position_par_rapport_au_sol') > 0) return [0, 100, 0, 1]; 39622 else return [0, 128, 0, 1]; 39623 } 39624 if (!feature.get('importance')) return "magenta"; 39625 if (feature.get('nature') === 'Piste cyclable') { 39626 return [27,177,27,.5] 39627 } 39628 if (feature.get('position_par_rapport_au_sol') != "0") { 39629 var col; 39630 switch(feature.get('importance')) { 39631 case "1": col = [177, 27, 177, 1]; break; 39632 case "2": col = [177, 27, 27, 1]; break; 39633 case "3": col = [217, 119, 0, 1]; break; 39634 case "4": col = [255, 225, 0, 1]; break; 39635 case "5": col = [204, 204, 204, 1]; break; 39636 default: col = [211, 211, 211, 1]; break; 39637 } 39638 if (feature.get('position_par_rapport_au_sol') < 0) col[3] = .7; 39639 return col; 39640 } else { 39641 switch(feature.get('importance')) { 39642 case "1": return [255,0,255,1]; 39643 case "2": return [255,0,0,1]; 39644 case "3": return [255, 165, 0, 1]; 39645 case "4": return [255,255,0,1]; 39646 case "5": return [255,255,255,1]; 39647 default: return [211, 211, 211, 1]; 39648 } 39649 } 39650 // return "#808080"; 39651 } 39652 // Get Width 39653 var getWidth = function (feature) { 39654 return Math.max ( feature.get('largeur_de_chaussee')||2 , 2 ); 39655 } 39656 // Zindex 39657 var getZindex = function (feature) { 39658 if (!feature.get('position_par_rapport_au_sol')) return 100; 39659 var pos = Number(feature.get('position_par_rapport_au_sol')); 39660 if (pos>0) return 10 + pos*10 - (Number(feature.get('importance')) || 10); 39661 else if (pos<0) return Math.max(4 + pos, 0); 39662 else return 10 - (Number(feature.get('importance')) || 10); 39663 // return 0; 39664 } 39665 // Get rotation on the center of the line 39666 var lrot = function (geom) { 39667 //if (sens != options.direct && sens != options.inverse) return 0; 39668 var geo = geom.getCoordinates(); 39669 var x, y, dl=0, l = geom.getLength(); 39670 for (var i=0; i<geo.length-1; i++) { 39671 x = geo[i+1][0]-geo[i][0];
39672 y = geo[i+1][1]-geo[i][1]; 39673 dl += Math.sqrt(x*x+y*y); 39674 if (dl>=l/2) break; 39675 } 39676 return -Math.atan2(y,x); 39677 } 39678 // Sens circulation 39679 var getSens = function (feature) { 39680 if (options.sens && !/double|sans/i.test(feature.get('sens_de_circulation'))) { 39681 return new ol.style.Text({ 39682 text: (feature.get('sens_de_circulation') == 'Sens direct' ? 'â' : 'â'), 39683 font: 'bold 12px sans-serif', 39684 placement: 'point', 39685 textAlign: 'center', 39686 fill: new ol.style.Fill({ color: [0,0,0,.3] }), 39687 stroke: new ol.style.Stroke({ color: [0,0,0,.3], width: 1.5 }), 39688 rotateWithView: true 39689 }) 39690 } 39691 return null; 39692 } 39693 var getDash = function(feature) { 39694 switch (feature.get('nature')) { 39695 case 'Escalier': { 39696 return [1,4] 39697 } 39698 case 'Sentier': { 39699 return [8,10] 39700 } 39701 } 39702 } 39703 var styleId = 'ROUT-'+(styleCount++)+'-' 39704 return function (feature, res) { 39705 var useSens = (options.sens === true || res < options.sens); 39706 var id = styleId 39707 + feature.get('nature') + '-' 39708 + feature.get('position_par_rapport_au_sol') + '-' 39709 + (useSens ? feature.get('sens_de_circulation') : 'Sans objet') + '-' 39710 + feature.get('position_par_rapport_au_sol') + '-' 39711 + feature.get('importance') + '-' 39712 + feature.get('largeur_de_chaussee') + '-' 39713 + feature.get('itineraire_vert'); 39714 var style = cache[id]; 39715 if (!style) { 39716 style = cache[id] = [ 39717 new ol.style.Style ({ 39718 text: useSens ? getSens(feature) : null, 39719 stroke: new ol.style.Stroke({ 39720 color: getColor(feature), 39721 width: getWidth(feature), 39722 lineDash: getDash(feature) 39723 }), 39724 zIndex: getZindex(feature)-100 39725 }) 39726 ]; 39727 } 39728 // Rotation 39729 if (style[0].getText()) style[0].getText().setRotation(lrot(feature.getGeometry())); 39730 return style; 39731 }; 39732} 39733/** Style for batiments */ 39734function batiment(options) { 39735 var getBatiColor = function (feature) { 39736 switch (feature.get('nature')) { 39737 case "Industriel, agricole ou commercial": return [51, 102, 153,1]; 39738 case "Remarquable": return [0,192,0,1]; 39739 default: 39740 switch ( feature.get('usage_1') ) { 39741 case 'Résidentiel': 39742 case 'Indifférencié': 39743 return [128,128,128,1]; 39744 case 'Industriel': 39745 case 'Commercial et services': 39746 return [51, 102, 153,1]; 39747 case "Sportif": 39748 return [51,153,102,1]; 39749 case "Religieux": 39750 return [153,102,51,1]; 39751 default: return [153,51,51,1]; 39752 } 39753 } 39754 } 39755 var getSymbol = function (feature) { 39756 switch ( feature.get('usage_1') ) { 39757 case "Commercial et services": return "\uf217"; 39758 case "Sportif": return "\uf1e3"; 39759 default: return null; 39760 } 39761 } 39762 var styleId = 'BATI-'+(styleCount++)+'-' 39763 return function (feature) { 39764 if (feature.get('detruit')) return []; 39765 var id = styleId 39766 + feature.get('usage_1') + '-' 39767 + feature.get('nature') + '-' 39768 + feature.get('etat_de_l_objet'); 39769 var style = cache[id]; 39770 if (!style) { 39771 var col = getBatiColor(feature); 39772 var colfill = [col[0], col[1], col[1], .5] 39773 var projet = !/en service/i.test(feature.get('etat_de_l_objet')); 39774 if (projet) colfill[3] = .1; 39775 var symbol = (options.symbol ? getSymbol(feature): null); 39776 return [ 39777 new ol.style.Style({ 39778 text: symbol ? new ol.style.Text({ 39779 text: symbol, 39780 font: '12px FontAwesome', 39781 fill: new ol.style.Fill({ 39782 color: [0,0,0, .6] //col 39783 }) 39784 }) : null, 39785 fill: new ol.style.Fill({ 39786 color: colfill 39787 }), 39788 stroke: new ol.style.Stroke ({ 39789 color: col, 39790 width: 1.5, 39791 lineDash: projet ? [5,5] : null 39792 }) 39793 }) 39794 ] 39795 } 39796 return style 39797 } 39798} 39799// Parcelle / cadastre 39800function parcelle(options) { 39801 var style = new ol.style.Style({ 39802 text: new ol.style.Text({ 39803 text: '0000', 39804 font: 'bold 12px sans-serif', 39805 fill: new ol.style.Fill({ 39806 color: [100, 0, 255, 1] 39807 }), 39808 stroke: new ol.style.Stroke ({
39809 color: [255,255,255, .8], 39810 width: 3 39811 }) 39812 }), 39813 stroke: new ol.style.Stroke ({ 39814 color: [255, 165, 0, 1], 39815 width: 1.5 39816 }), 39817 fill: new ol.style.Fill({ 39818 color: [100, 0, 255, .1] 39819 }) 39820 }) 39821 return function(feature, resolution) { 39822 if (resolution < .8) style.getText().setFont('bold 12px sans-serif'); 39823 else style.getText().setFont('bold 10px sans-serif'); 39824 if (options.section) { 39825 style.getText().setText(feature.get('section') +'-'+ (feature.get('numero')||'').replace(/^0*/,'')); 39826 } else { 39827 style.getText().setText((feature.get('numero')||'').replace(/^0*/,'')); 39828 } 39829 return style; 39830 } 39831} 39832// Corine Land Cover Style 39833var clcColors = { 39834 111: { color: [230,0,77,255], title: 'Continuous urban fabric'}, 39835 112: { color: [255,0,0,255], title: 'Discontinuous urban fabric'}, 39836 121: { color: [204,77,242,255], title: 'Industrial or commercial units'}, 39837 122: { color: [204,0,0,255], title: 'Road and rail networks and associated land'}, 39838 123: { color: [230,204,204,255], title: 'Port areas'}, 39839 124: { color: [230,204,230,255], title: 'Airports'}, 39840 131: { color: [166,0,204,255], title: 'Mineral extraction sites'}, 39841 132: { color: [166,77,0,255], title: 'Dump sites'}, 39842 133: { color: [255,77,255,255], title: 'Construction sites'}, 39843 141: { color: [255,166,255,255], title: 'Green urban areas'}, 39844 142: { color: [255,230,255,255], title: 'Sport and leisure facilities'}, 39845 211: { color: [255,255,168,255], title: 'Non-irrigated arable land'}, 39846 212: { color: [255,255,0,255], title: 'Permanently irrigated land'}, 39847 213: { color: [230,230,0,255], title: 'Rice fields'}, 39848 221: { color: [230,128,0,255], title: 'Vineyards'}, 39849 222: { color: [242,166,77,255], title: 'Fruit trees and berry plantations'}, 39850 223: { color: [230,166,0,255], title: 'Olive groves'}, 39851 231: { color: [230,230,77,255], title: 'Pastures'}, 39852 241: { color: [255,230,166,255], title: 'Annual crops associated with permanent crops'}, 39853 242: { color: [255,230,77,255], title: 'Complex cultivation patterns'}, 39854 243: { color: [230,204,77,255], title: 'Land principally occupied by agriculture with significant areas of natural vegetation'}, 39855 244: { color: [242,204,166,255], title: 'Agro-forestry areas'}, 39856 311: { color: [128,255,0,255], title: 'Broad-leaved forest'}, 39857 312: { color: [0,166,0,255], title: 'Coniferous forest'}, 39858 313: { color: [77,255,0,255], title: 'Mixed forest'}, 39859 321: { color: [204,242,77,255], title: 'Natural grasslands'}, 39860 322: { color: [166,255,128,255], title: 'Moors and heathland'}, 39861 323: { color: [166,230,77,255], title: 'Sclerophyllous vegetation'}, 39862 324: { color: [166,242,0,255], title: 'Transitional woodland-shrub'}, 39863 331: { color: [230,230,230,255], title: 'Beaches dunes sands'}, 39864 332: { color: [204,204,204,255], title: 'Bare rocks'}, 39865 333: { color: [204,255,204,255], title: 'Sparsely vegetated areas'}, 39866 334: { color: [0,0,0,255], title: 'Burnt areas'}, 39867 335: { color: [166,230,204,255], title: 'Glaciers and perpetual snow'}, 39868 411: { color: [166,166,255,255], title: 'Inland marshes'}, 39869 412: { color: [77,77,255,255], title: 'Peat bogs'}, 39870 421: { color: [204,204,255,255], title: 'Salt marshes'}, 39871 422: { color: [230,230,255,255], title: 'Salines'}, 39872 423: { color: [166,166,230,255], title: 'Intertidal flats'}, 39873 511: { color: [0,204,242,255], title: 'Water courses'}, 39874 512: { color: [128,242,230,255], title: 'Water bodies'}, 39875 521: { color: [0,255,166,255], title: 'Coastal lagoons'}, 39876 522: { color: [166,255,230,255], title: 'Estuaries'}, 39877 523: { color: [230,242,255,255], title: 'Sea and ocean'}, 39878}; 39879function corineLandCover (options) { 39880 return function(feature) { 39881 var code = feature.get('code_'+options.date); 39882 var style = cache['CLC-'+code]; 39883 if (!style) { 39884 var color = clcColors[code].color.slice(); 39885 color[3] = options.opacity || 1; 39886 style = cache['CLC-'+code] = new ol.style.Style({ 39887 fill: new ol.style.Fill({ 39888 color: color || [255,255,255,.5] 39889 }) 39890 }) 39891 } 39892 return style; 39893 } 39894} 39895/** Get ol style for an IGN WFS layer 39896 * @param {string} typeName 39897 * @param {Object} options 39898 */ 39899ol.style.geoportailStyle = function(typeName, options) { 39900 options = options || {}; 39901 switch (typeName) { 39902 // Troncons de route 39903 case 'BDTOPO_V3:troncon_de_route': return troncon_de_route(options); 39904 // Bati 39905 case 'BDTOPO_V3:batiment': return batiment(options); 39906 // Parcelles 39907 case 'CADASTRALPARCELS.PARCELLAIRE_EXPRESS:parcelle': return parcelle(options); 39908 default: { 39909 // CLC 39910 if (/LANDCOVER/.test(typeName)) { 39911 options.date = typeName.replace(/[^\d]*(\d*).*/,'$1'); 39912 return (corineLandCover(options)); 39913 } else { 39914 // Default style 39915 console.warn('[ol/style/geoportailStyle] no style defined for type: ' + typeName) 39916 return ol.style.Style.defaultStyle();
39917 } 39918 } 39919 } 39920}; 39921/** List of clc colors */ 39922ol.style.geoportailStyle.clcColors = JSON.parse(JSON.stringify(clcColors)); 39923})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.