PageSourceSearch

http://www.vorharz.net/res/openlayers/ol-ext/dist/ol-ext.js?v=v5.52.00

js vorharz.net collected 2026-09-25 04:11:01 UTC 1,398,794 bytes, 39,923 lines download raw bytes

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">&copy; 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">&copy; 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">&copy; 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">&copy; 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&reg; - 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)+'">&copy; '+caps.Attribution.Title.replace(/</g,'&lt;')+'</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 = [ "&copy; <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> &copy; <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 = [ "&copy; <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 = [ "&copy; <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="&copy; '+o.attribution+'" /></a>';
31475    } else {
31476      if (!title) return ol.source.Geoportail.prototype.attribution;
31477      else return '&copy; <a href="'+o.href+'" title="&copy; '+(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.