# Source: bemap-ol/bemap-map-ol.js

    /**
     * BeNomad BeMap JavaScript API - Map - OpenLayers
     */

    //bemap.require('ol.js');

    /**
     * @classdesc
     * Base class for OpenLayers.
     * @public
     * @constructor
     * @extends {bemap.Map}
     * @param {bemap.Context} context BeMap-JS-API Context.
     * @param {string} target HTML element.
     * @param options.
     */
    bemap.OlMap = function(context, target, options) {
      /**
       * @protected
       */
      this.target = target;

      bemap.Map.call(this, context, options);

      this.native = new ol.Map({
        target: target,
        view: new ol.View({
          projection: bemap.Map.PROJ.EPSG_MERCATOR,
          center: [0, 0],
          zoom: 3,
          minZoom: 3,
          maxZoom: 20
        })
      });

      // HACK for movestart event - Start
      var _nativeMap = this;
      var _onPropertychange = function() {
        _nativeMap.native.dispatchEvent('movestart');
        var _nativeView = _nativeMap.native.getView();
        _nativeView.un('propertychange', _onPropertychange);
        _nativeMap.native.on('moveend', function() {
          _nativeView.on('propertychange', _onPropertychange);
        });
      };
      _nativeMap.native.getView().on('propertychange', _onPropertychange);
      // HACK for movestart event - End
    };
    bemap.inherits(bemap.OlMap, bemap.Map);

    /**
     * Sotre the bemap object into the native OpenLayers properties.
     * @protected
     * @param {Object} bemapObject a bemap object like bemap.marker, bemap.multimarker or bemap.polyline.
     */
    bemap.OlMap.prototype._addOwnToProperties = function(bemapObject) {
      var props = bemapObject.native.getProperties() || {};
      props[bemap.Map.OWNREF] = bemapObject;
      bemapObject.native.setProperties(props);
    };

    /**
     * Get the bemap object from the native OpenLayers properties.
     * @protected
     * @param {Object} olObject native Openlayers object like ol.Feature.
     * @return {Object} a bemap object like bemap.marker, bemap.multimarker or bemap.polyline.
     */
    bemap.OlMap.prototype._getOwnFromProperties = function(olObject) {
      var props = olObject.getProperties();
      var ownRef = props[bemap.Map.OWNREF];
      if (ownRef === undefined) {
        props = props.features[0].getProperties();
        ownRef = props[bemap.Map.OWNREF];
      }
      return ownRef;
    };

    /**
     * @protected
     * @param {double} lon Longitude in degres decimal (WGS84).
     * @param {double} lat Latitude in degres decimal (WGS84).
     */
    bemap.OlMap.prototype._fromLonLat = function(lon, lat) {
      return ol.proj.transform([lon, lat], bemap.Map.PROJ.EPSG_WGS84, this.native.getView().getProjection());
    };

    /**
     * @protected
     * @param {array} coordinate Array contains the longitude and latitude in format of OpenLayers Map object.
     * @return {array} Array contains the longitude and latitude in degres decimal (WGS84).
     */
    bemap.OlMap.prototype._fromNativeToLonLat = function(coordinate) {
      var coords = ol.proj.transform(coordinate, this.native.getView().getProjection(), bemap.Map.PROJ.EPSG_WGS84);
      return new bemap.Coordinate(coords[0], coords[1]);
    };

    /**
     * Add a layer to the OpenLayers map.
     * @public
     * @param {bemap.Layer} layer
     * @param {object} options
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.addLayer = function(layer, options) {
      if (layer !== null && bemap.inheritsof(layer, bemap.Layer)) {
        if (bemap.inheritsof(layer, bemap.BemapLayer)) {
          layer.native = new ol.layer.Tile({
            source: new ol.source.TileWMS({
              url: this.ctx.getBaseUrl() + 'wms?' + this.ctx.getAuthUrlParams(),
              params: {
                'geoserver': layer.geoserver ? layer.geoserver : this.ctx.getGeoserver(),
                'LAYERS': layer.layers ? layer.layers : 'default',
                'STYLES': layer.styles ? layer.styles : '',
                'TILED': true,
                'TRANSPARENT': layer.transparent === true ? true : false,
                'FORMAT': layer.format ? layer.format : 'image/png'
              }
            })
          });

        } else if (bemap.inheritsof(layer, bemap.WmsLayer)) {
          layer.native = new ol.layer.Tile({
            source: new ol.source.TileWMS({
              url: layer.url,
              params: {
                'LAYERS': layer.layers ? layer.layers : '',
                'STYLES': layer.styles ? layer.styles : '',
                'TILED': layer.tiled === true ? true : false,
                'TRANSPARENT': layer.transparent === true ? true : false,
                'FORMAT': layer.format ? layer.format : 'image/png'
              }
            })
          });

        } else if (bemap.inheritsof(layer, bemap.OsmLayer)) {
          layer.native = new ol.layer.Tile({
            source: new ol.source.OSM()
          });

        } else if (bemap.inheritsof(layer, bemap.VectorLayer)) {
          layer.native = new ol.layer.Vector({
            source: new ol.source.Vector({
              features: []
            })
          });

        } else if (bemap.inheritsof(layer, bemap.ClusterLayer)) {
          var source = new ol.source.Vector({
            features: []
          });

          var clusterSource = new ol.source.Cluster({
            distance: layer.distance,
            source: source,
            projection: this.native.getView().getProjection()
          });

          var c = layer.style;

          layer.native = new ol.layer.Vector({
            projection: this.native.getView().getProjection(),
            source: clusterSource,
            style: function(feature, resolution) {
              var size = feature.get('features').length;
              var style;
              if (size > 1) {
                style = new ol.style.Style({
                  image: new ol.style.Circle({
                    radius: c.size,
                    stroke: new ol.style.Stroke({
                      width: c.borderSize,
                      color: c.borderColor.getRgbaArray()
                    }),
                    fill: new ol.style.Fill({
                      color: c.color.getRgbaArray()
                    })
                  }),
                  text: new ol.style.Text({
                    text: size.toString(),
                    fill: new ol.style.Fill({
                      color: c.textColor.getRgbaArray()
                    }),
                    scale: c.textSize
                  })
                });
              } else {
                style = new ol.style.Style({
                  image: new ol.style.Icon({
                    anchor: [c.icon.anchorX, c.icon.anchorY],
                    anchorXUnits: c.icon.anchorXUnits,
                    anchorYUnits: c.icon.anchorYUnits,
                    src: c.icon.src,
                    scale: c.icon.scale
                  })
                });
              }
              return style;
            }
          });

          this.native.getView().on('change:resolution', function(evt) {
            if (this.getResolution() <= 0.14929107086948487) {
              clusterSource.setDistance(0);
            } else {
              clusterSource.setDistance(40);
            }
          }, this.native.getView());

        } else {
          console.warn("Unsupport layer");
        }

        if (layer.native !== null) {
          bemap.OlMap.prototype._addOwnToProperties(layer);
          this.native.addLayer(layer.native);
          bemap.Map.prototype.addLayer.call(this, layer);
        }
      }

      if (layer.map === null) {
        layer.map = this;
      }

      return this;
    };

    bemap.OlMap.prototype._getDragPan = function() {
      var dragPan = null;
      this.native.getInteractions().forEach(function(interaction) {
        if (interaction instanceof ol.interaction.DragPan) {
          dragPan = interaction;
        }
      });
      return dragPan;
    };

    /**
     * Return the satus of drag pan of map.
     * @public
     * @abstract
     * @param {Object} options.dragPan.
     * @return {boolean} true to enable the drag pan of map, otherwise false.
     */
    bemap.OlMap.prototype.isDragPan = function(options) {
      var dragPan = options && options.dragPan ? options.dragPan : null;
      if (dragPan === null) {
        dragPan = this._getDragPan();
      }
      if (dragPan) {
        return dragPan.getActive();
      }
      return false;
    };

    /**
     * Enable or diable the drag pan of map.
     * @public
     * @abstract
     * @param {boolean} active true to enable the drag pan of map, otherwise false.
     * @param {Object} options.dragPan.
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.setDragPan = function(active, options) {
      var dragPan = options && options.dragPan ? options.dragPan : null;
      if (dragPan === null) {
        dragPan = this._getDragPan();
      }
      if (dragPan) {
        dragPan.setActive(active);
      }
      return this;
    };

    /**
     * Move map to new coordinate.
     * @public
     * @param {double} lon Longitude in degres decimal (WGS84).
     * @param {double} lat Latitude in degres decimal (WGS84).
     * @param {int} zoom Zoom level (optional).
     * @param {Object} options Options (optional).
     * @param {String} options.animate Enable the animation.
     * @param {String} options.fly Enable the animation (same as animate).
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.move = function(lon, lat, zoom, options) {
      var view = this.native.getView();

      if (zoom) {
        view.setZoom(zoom);
      }

      if (options && (options.animate || options.fly)) {
        var duration = 2000;
        var start = +new Date();
        var pan = ol.animation.pan({
          duration: duration,
          source: view.getCenter(),
          start: start
        });
        var bounce = ol.animation.bounce({
          duration: duration,
          resolution: 4 * view.getResolution(),
          start: start
        });
        this.native.beforeRender(pan, bounce);
        view.setCenter(bern);
      } else {
        view.setCenter(this._fromLonLat(lon, lat));
      }

      return this;
    };

    /**
     * Set the center and the zoom of the map to fit the bounding box.
     * @param {bemap.BoundingBox} boundingBox the bounding box to fit.
     * @return {bemap.OlMap} this.
     */
    bemap.OlMap.prototype.moveToBoundingBox = function(boundingBox, options) {
      if (boundingBox && bemap.inheritsof(boundingBox, bemap.BoundingBox) && boundingBox.maxLon && boundingBox.maxLat && boundingBox.minLon && boundingBox.minLat) {
        var ext = [boundingBox.minLon, boundingBox.minLat, boundingBox.maxLon, boundingBox.maxLat];
        ext = ol.proj.transformExtent(ext, bemap.Map.PROJ.EPSG_WGS84, this.native.getView().getProjection());
        this.native.getView().fit(ext, this.native.getSize());
      }
      return this;
    };

    /**
     * Move map and zoom on data contains in layers.
     * @public
     * @abstract
     * @param {bemap.Layer} layer Layer.
     * @param {Object} options Options (optional).
     * @return {bemap.Map} this
     */
    bemap.OlMap.prototype.moveToLayerData = function(layer, options) {
      var extent = null;
      if (layer && bemap.inheritsof(layer, bemap.ClusterLayer)) {
        extent = layer.native.getSource().getSource().getExtent();
      } else if (layer && bemap.inheritsof(layer, bemap.Layer)) {
        extent = layer.native.getSource().getExtent();
      }
      if (extent) {
        this.native.getView().fit(extent, this.native.getSize());
      }
      return this;
    };

    /**
     * Zoom on map, set new zoom level.
     * @public
     * @param {int} zoom Zoom level (optional).
     * @param {Object} options Options (optional).
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.zoom = function(zoom, options) {
      this.native.getView().setZoom(zoom);
      return this;
    };

    /**
     * Get current zoom level of map.
     * @public
     * @return {int} current zoom level of map.
     */
    bemap.OlMap.prototype.getZoom = function() {
      return this.native.getView().getZoom();
    };

    /**
     * Rotation of map, set new angle of map.
     * @public
     * @param {int} angle in degrees.
     * @param {Object} options Options (optional).
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.rotation = function(angle, options) {
      var angleRadian = angle * (Math.PI / 180);
      this.native.getView().setRotation(angleRadian);
      return this;
    };

    /**
     * Get current rotation angle of map.
     * @public
     * @return {int} current rotation angle of map in degrees.
     */
    bemap.OlMap.prototype.getRotation = function() {
      return this.native.getView().getRotation();
    };

    /**
     * Refresh map.
     * @public
     * @abstract
     * @param {Object} options Options (optional).
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.refresh = function(options) {
      this.native.updateSize();
      this.native.render();
      return this;
    };

    /**
     * Build icon resource.
     * @public
     * @param {bemap.Icon} icon
     * @param {object} options
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.buildIcon = function(icon, options) {
      if (icon !== null && bemap.inheritsof(icon, bemap.Icon)) {
        icon.native = new ol.style.Icon({
          anchor: [icon.anchorX, icon.anchorY],
          anchorXUnits: (icon.anchorXUnits && icon.anchorXUnits !== '') ? icon.anchorXUnits : 'fraction',
          anchorYUnits: (icon.anchorYUnits && icon.anchorYUnits !== '') ? icon.anchorYUnits : 'fraction',
          src: icon.src,
          opacity: icon.opacity ? icon.opacity : 1,
          scale: icon.scale ? icon.scale : 1
        });
      }
      return this;
    };

    /**
     * Build text style resource.
     * @public
     * @param {bemap.TextStyle} text style object.
     * @param {String} name Name (label) of marker.
     * @param {object} options
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.buildTextStyle = function(textStyle, name, options) {
      if (textStyle && bemap.inheritsof(textStyle, bemap.TextStyle)) {
        var nativeOpts = {
          text: name,
          offsetX: textStyle.getOffsetX(),
          offsetY: textStyle.getOffsetY(),
          scale: textStyle.getSize(),
          fill: new ol.style.Fill({
            color: textStyle.getColor().getRgbArray()
          })
        };

        if (textStyle.getBorderWidth() > 0) {
          nativeOpts.stroke = new ol.style.Stroke({
            color: textStyle.getBorderColor().getRgbArray(),
            width: textStyle.getBorderWidth()
          });
        }

        textStyle.native = new ol.style.Text(nativeOpts);
      }
      return this;
    };

    /**
     * Build lineStyle resource.
     * @public
     * @param {bemap.LineStyle} lineStyle
     * @param {object} options
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.buildLineStyle = function(style, options) {
      if (style !== null && bemap.inheritsof(style, bemap.LineStyle)) {
        style.native = new ol.style.Style({
          stroke: new ol.style.Stroke({
            color: [style.color.getRed(), style.color.getGreen(), style.color.getBlue(), style.color.getAlpha()],
            width: style.width,
            lineDash: style.type === bemap.LineStyle.TYPE.DASH ? [10] : style.type === bemap.LineStyle.TYPE.DOT_DASH ? [0.1, 10, 10] : style.type === bemap.LineStyle.TYPE.DOT ? [0.1, 10] : []
          }),
        });
      }
      return this;
    };

    /**
     * Build polygonStyle resource.
     * @public
     * @param {bemap.PolygonStyle} polygonStyle
     * @param {object} options
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.buildPolygonStyle = function(style, options) {
      if (style !== null && bemap.inheritsof(style, bemap.PolygonStyle)) {
        var fc = style.fillColor;
        var bc = style.borderColor;
        style.native = new ol.style.Style({
          fill: new ol.style.Fill({
            color: [fc.getRed(), fc.getGreen(), fc.getBlue(), fc.getAlpha()]
          }),
          stroke: new ol.style.Stroke({
            color: [bc.getRed(), bc.getGreen(), bc.getBlue(), bc.getAlpha()],
            width: style.borderWidth,
            lineDash: style.borderType === bemap.PolygonStyle.TYPE.DASH ? [10] : style.borderType === bemap.PolygonStyle.TYPE.DOT_DASH ? [0.1, 10, 10] : style.borderType === bemap.PolygonStyle.TYPE.DOT ? [0.1, 10] : []
          }),
        });
      }
      return this;
    };

    /**
     * Build circleStyle resource.
     * @public
     * @param {bemap.CircleStyle} circleStyle
     * @param {object} options
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.buildCircleStyle = function(style, options) {
      if (style !== null && bemap.inheritsof(style, bemap.CircleStyle)) {
        var fc = style.fillColor;
        var bc = style.borderColor;
        style.native = new ol.style.Style({
          fill: new ol.style.Fill({
            color: [fc.getRed(), fc.getGreen(), fc.getBlue(), fc.getAlpha()]
          }),
          stroke: new ol.style.Stroke({
            color: [bc.getRed(), bc.getGreen(), bc.getBlue(), bc.getAlpha()],
            width: style.borderWidth,
            lineDash: style.borderType === bemap.CircleStyle.TYPE.DASH ? [10] : style.borderType === bemap.CircleStyle.TYPE.DOT_DASH ? [0.1, 10, 10] : style.borderType === bemap.CircleStyle.TYPE.DOT ? [0.1, 10] : []
          }),
        });
      }
      return this;
    };

    /**
     * Refresh all objects from a layer.
     * @public
     * @param {bemap.Layer} layer the layer object to refresh.
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.refreshLayer = function(layer) {
      if (layer !== null && bemap.inheritsof(layer, bemap.Layer)) {
        layer.native.redraw();
      }
      return this;
    };

    /**
     * Remove all objects from a layer.
     * @public
     * @param {bemap.Layer} layer the layer object to clear.
     * @return {bemap.OlMap} this
     */
    bemap.OlMap.prototype.clearLayer = function(layer) {
      if (layer !== null && bemap.inheritsof(layer, bemap.ClusterLayer)) {
        layer.native.getSource().getSource().clear();
      } else if (layer !== null && bemap.inheritsof(layer, bemap.Layer)) {
        layer.native.getSource().clear();
      }
      return this;
    };

    /**
     * Set the visibility of the layer.
     * @public
     * @param {bemap.Layer} layer the layer of wich to set the visibility.
     * @param {boolean} visible.
     * @return {bemap.OlMap} return this.
     */
    bemap.OlMap.prototype.visibleLayer = function(layer, visible) {
      if (visible !== null && layer !== null && bemap.inheritsof(layer, bemap.Layer)) {
        layer.native.setVisible(visible);
        layer.visible = visible;
      }
      return this;
    };

    /**
     * Remove a layer from the map.
     * @param {bemap.Layer} layer layer to remove from the map.
     * @return {bemap.OlMap} this.
     */
    bemap.OlMap.prototype.removeLayer = function(layer) {
      if (layer !== null && bemap.inheritsof(layer, bemap.Layer)) {
        this.native.removeLayer(layer.native);
        layer.map = null;
      }
      return this;
    };

    /**
     * Check the event mode.
     * @protected
     * @param {object} mode enable modes.
     * @param {object} bemapObject bemap object like bemap.Marker, bemap.Polyline, etc.
     * @param {object} declaredObj bemap object like bemap.Marker, bemap.Polyline, etc.
     * @return {function}
     */
    bemap.OlMap.prototype._checkModeOfEvent = function(mode, bemapObject, eventType) {
      if (bemapObject && bemapObject.callback && bemapObject.callback[eventType] && typeof bemapObject.callback[eventType] === "function") {
        return bemapObject.callback[eventType];
      }
      if (this.events) {
        if (this.events[eventType + "markers"] && this.events[eventType + "markers"].callback && typeof this.events[eventType + "markers"].callback === "function" && bemap.inheritsof(bemapObject, bemap.Marker)) {
          return this.events[eventType + "markers"].callback;
        }
        if (this.events[eventType + "multiMarkers"] && this.events[eventType + "multiMarkers"].callback && typeof this.events[eventType + "multiMarkers"].callback === "function" && bemap.inheritsof(bemapObject, bemap.MultiMarker)) {
          return this.events[eventType + "multiMarkers"].callback;
        }
        if (this.events[eventType + "polylines"] && this.events[eventType + "polylines"].callback && typeof this.events[eventType + "polylines"].callback === "function" && bemap.inheritsof(bemapObject, bemap.Polyline)) {
          return this.events[eventType + "polylines"].callback;
        }
      }
      if (bemapObject && bemapObject.events.draggable && bemapObject.callback.draggable && typeof bemapObject.callback.draggable === "function") {
        return bemapObject.callback.draggable;
      }
      return null;
    };

    /**
     * Set the listner when a specified eventType occur on OpenLayers Feature object.
     * @protected
     * @param {bemap.Marker} marker
     * @param {bemap.Map.EventType} eventType Event type.
     * @param {function} callback Function will be called when the specified eventType is occur.
     * @param {object} options options.
     * @param {object} mode Mode of slection feature.
     * @return {bemap.Listener} listener.
     */
    bemap.OlMap.prototype._onFeature = function(declaredObj, eventType, callback, options, mode) {
      var opts = options ? options : {};

      var layerFilterCallback = function(layer) {
        if (opts.layerFilter) {
          return layer === opts.layerFilter.native;
        } else {
          return true;
        }
      };

      var nativeListener;

      if (!this.events[eventType]) {
        nativeListener = this.native.on(eventType, function(evt) {
            var feature = null;
            if (evt.pixel !== undefined)
              feature = this.native.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
                return feature;
              }, this, layerFilterCallback);
            var bemapObject;
            if (feature) {
              bemapObject = bemap.OlMap.prototype._getOwnFromProperties(feature);
            } else {
              bemapObject = this;
            }
            var mapEvent = new bemap.MapEvent({
              native: evt,
              bemapObject: bemapObject,
              x: evt.pixel !== undefined ? evt.pixel[0] : undefined,
              y: evt.pixel !== undefined ? evt.pixel[1] : undefined,
              coordinate: evt.coordinate !== undefined ? this._fromNativeToLonLat(evt.coordinate) : undefined,
              properties: options,
              map: this
            });
            callback = this._checkModeOfEvent(mode, bemapObject, eventType);
            if (callback) {
              return callback(mapEvent);
            }
          },
          this);
      } else {
        nativeListener = this.events[eventType].native;
      }

      var listener = new bemap.Listener({
        native: nativeListener,
        callback: callback,
        key: eventType,
        bemapObject: declaredObj
      });

      this.events[eventType] = listener;

      if (declaredObj && mode.singleFeature && typeof callback === "function") {
        declaredObj.callback[eventType] = callback;
      } else if (!declaredObj && mode.markers && typeof callback === "function") {
        this.events[eventType + "markers"] = new bemap.Listener({
          callback: callback,
          key: eventType + "markers"
        });
        return this.events[eventType + "markers"];
      } else if (!declaredObj && mode.multiMarkers && typeof callback === "function") {
        this.events[eventType + "multiMarkers"] = new bemap.Listener({
          callback: callback,
          key: eventType + "multiMarkers"
        });
        return this.events[eventType + "multiMarkers"];
      } else if (!declaredObj && mode.polylines && typeof callback === "function") {
        this.events[eventType + "polylines"] = new bemap.Listener({
          callback: callback,
          key: eventType + "polylines"
        });
        return this.events[eventType + "polylines"];
      }

      return listener;
    };

    /**
     * Set the listner when an specified eventType occur on bemap.OlMap.
     * @public
     * @param {bemap.Map.EventType} eventType Event type.
     * @param {function} callback Function will be called when the specified eventType is occur.
     * @param {object} options options.
     * @return {bemap.Listener} listner.
     */
    bemap.OlMap.prototype.on = function(eventType, callback, options) {
      return this._onFeature(this, eventType, callback, options, {
        singleFeature: true
      });
    };

    /**
     * Set get feature info call back.
     * @param {bemap.Layer} layer set the bemap layer.
     * @param {object} options options.
     * @param {function} options.beforeCallback callback called at data reception and before display the popup.
     * @param {function} options.afterCallback callback called after display the popup.
     * @param {function} options.panningMap enable the map panning animation. move map from the current position to the popup anchor at the center of map.
     * @return {bemap.Listener} listner;
     */
    bemap.OlMap.prototype.onGetFeatureInfo = function(layer, options) {
      var opts = options ? options : {};
      var popup = new bemap.Popup({
        information: "<p>onGetFeatureInfo</p>",
        coordinate: new bemap.Coordinate(0, 0),
        visible: false
      });
      this.addPopup(popup);

      var listener = this.on(bemap.Map.EventType.SINGLECLICK, function(evt) {
        var url = layer.native
          .getSource()
          .getGetFeatureInfoUrl(
            evt.native.coordinate,
            evt.native.map.getView().getResolution(),
            evt.native.map.getView().getProjection(), {
              'INFO_FORMAT': 'text/xml',
              'propertyName': 'NAME,AREA_CODE,DESCRIPTIO'
            }
          );

        bemap.ajax('GET', url, null, function(xhr, data) {
          evt.listener = listener;
          evt.popup = popup;
          evt.xhr = xhr;
          evt.data = data;
          evt.showPopup = true;

          var info = data;
          if (opts.beforeCallback) {
            info = opts.beforeCallback(evt, opts);
          }

          if (evt.showPopup) {
            popup.setInformation(info).setCoordinate(evt.coordinate, opts);

            if (opts.afterCallback) {
              opts.afterCallback(evt, opts);
            }
          }
        });
      });

      listener.popup = popup;
      return listener;
    };

    /**
     * Define the draggable capability for bemap object (like bemap.Marker,  bemap.MultiMarker, bemap.Polyline, etc.).
     * @protected
     * @param {Object} declaredObj bemap object like bemap.Marker,  bemap.MultiMarker, bemap.Polyline, etc.
     * @param {function} callback Function will be called when the specified eventType is occur.
     * @param {object} options Options.
     * @param {bemap.Layer} options.layerFilter set the bemap layer used as filter.
     * @param {object} mode Mode of slection feature.
     * @return {bemap.Listener} Listener.
     */
    bemap.OlMap.prototype._draggableFeature = function(declaredObj, callback, options, mode) {
      var _this = this,
        opts = options ? options : {},
        startPixel, startCoordinate, previousCoordinate,
        cursor = 'pointer',
        previousCursor, feature = null;

      var layerFilterCallback = function(layer) {
        if (opts.layerFilter) {
          return layer === opts.layerFilter.native;
        } else {
          return true;
        }
      };

      if (!declaredObj.events.draggable) {
        declaredObj.events.draggable = true;
        declaredObj.callback.draggable = callback;
      }

      if (!this.events.dragFeature) {
        var downFunc = function(evt) {
            var foundFeature = evt.map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
              var bemapObject = _this._getOwnFromProperties(feature);
              if (_this._checkModeOfEvent(mode, bemapObject, declaredObj)) {
                return feature;
              }
            }, this, layerFilterCallback);

            if (foundFeature) {
              startPixel = evt.pixel;
              startCoordinate = evt.coordinate;
              previousCoordinate = evt.coordinate;
              feature = foundFeature;
            }

            return !!feature;
          },
          dragFunc = function(evt) {
            if (feature) {
              feature.getGeometry().translate(evt.coordinate[0] - previousCoordinate[0], evt.coordinate[1] - previousCoordinate[1]);
              previousCoordinate = evt.coordinate;

              var bemapObject = bemap.OlMap.prototype._getOwnFromProperties(feature);
              if (bemapObject) {
                var newCoords = feature.getGeometry().getCoordinates(),
                  newCoord;

                if (bemap.inheritsof(bemapObject, bemap.Marker)) {
                  newCoord = _this._fromNativeToLonLat(newCoords);
                  bemapObject.setCoordinate(bemapObject.getCoordinate().setLon(newCoord.getLon()).setLat(newCoord.getLat()));

                } else if (bemap.inheritsof(bemapObject, bemap.MultiMarker) || bemap.inheritsof(bemapObject, bemap.Polyline)) {
                  var bemapObjCoords = bemapObject.getCoordinates();
                  for (var i = 0; i < newCoords.length; i++) {
                    newCoord = _this._fromNativeToLonLat(newCoords[i]);
                    bemapObjCoords[i].setLon(newCoord.getLon()).setLat(newCoord.getLat());
                  }
                }
              }
            }
          },
          moveFunc = function(evt) {
            if (cursor) {
              var nativeMap = evt.map;
              var foundFeature = nativeMap.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
                var bemapObject = _this._getOwnFromProperties(feature);
                if (_this._checkModeOfEvent(mode, bemapObject, declaredObj)) {
                  return feature;
                }
              }, this, layerFilterCallback);

              var element = nativeMap.getTargetElement();
              if (foundFeature) {
                if (element.style.cursor != cursor) {
                  previousCursor = element.style.cursor;
                  element.style.cursor = cursor;
                }
              } else if (previousCursor !== undefined) {
                element.style.cursor = previousCursor;
                previousCursor = undefined;
              }
            }
          },
          upFunc = function(evt) {
            if (evt.pixel[0] == startPixel[0] && evt.pixel[1] == startPixel[1]) {
              return false;
            }
            var mapEvent = new bemap.MapEvent({
              native: evt,
              bemapObject: bemap.OlMap.prototype._getOwnFromProperties(feature),
              x: evt.pixel[0],
              y: evt.pixel[1],
              coordinate: _this._fromNativeToLonLat(evt.coordinate),
              startX: startPixel[0],
              startY: startPixel[1],
              startCoordinate: _this._fromNativeToLonLat(evt.coordinate),
              properties: options
            });

            feature = null;
            callback = _this._checkModeOfEvent(mode, mapEvent.bemapObject, declaredObj);
            callback(mapEvent);

            return false;
          };

        var pointerInteraction = new ol.interaction.Pointer({
          handleDownEvent: downFunc,
          handleDragEvent: dragFunc,
          handleMoveEvent: moveFunc,
          handleUpEvent: upFunc
        });

        var nativeListener = this.native.addInteraction(pointerInteraction);

        this.events.dragFeature = new bemap.Listener({
          native: pointerInteraction,
          key: "dragFeature",
          bemapObject: declaredObj
        });

        return this.events.dragFeature;
      }
      return this.events.dragFeature;
    };

    /**
     * Remove listener.
     * @protected
     * @param {bemap.Listener} listener Function will be called when the specified eventType is occur.
     * @param {object} options Options.
     * @return {bemap.OlMap} this.
     */
    bemap.OlMap.prototype.removeListener = function(listener, options) {
      if (listener && bemap.inheritsof(listener, bemap.Listener)) {
        if (listener.bemapObject) {
          if (listener.key == "dragFeature") {
            listener.bemapObject.events.draggable = false;
            listener.bemapObject.callback.draggable = null;
          } else {
            listener.bemapObject.callback[listener.key] = null;
          }
        }
        // ol.Observable.unByKey(listener.native);
        // this.native.removeInteraction(listener.native);
        // this.events[listener.key] = null;
      }
      return this;
    };

    /**
     * Get the center of the map in bemap.Coordinate.
     * @return {bemap.Coordinate} the center of the map.
     */
    bemap.OlMap.prototype.getCenter = function() {
      var center = this.native.getView().getCenter();
      center = this._fromNativeToLonLat(center);
      return center;
    };

    /**
     * Get the limits of the map on the current zoom.
     * @return {bemap.BoundingBox} the bounding box containing the limits.
     */
    bemap.OlMap.prototype.getBoundingBox = function() {
      var extent = this.native.getView().calculateExtent(this.native.getSize());
      extent = ol.proj.transformExtent(extent, this.native.getView().getProjection(), bemap.Map.PROJ.EPSG_WGS84);
      return new bemap.BoundingBox(extent[0], extent[1], extent[2], extent[3]);
    };

    /**
     * Get the pixel coordinate from bemap coordinate.
     * @return {Array} the corresponding XY coords.
     */
    bemap.OlMap.prototype.getXYFromCoordinate = function(coordinate) {
      this.native.once("postrender", function() {
        return this.native.getPixelFromCoordinate(coordinate.getLonLatArray());
      }, this);
    };