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);
};