Source: temp/jsdocinputdirs/cercalia.marker.js

/**
 * @classdesc
 *
 * Marker graphical format with which perform a set of operations and customizations.
 * The core of this class is based on OpenLayers3 <a href="http://openlayers.org/en/v3.0.0/apidoc/ol.Feature.html">`ol.Feature`</a> class
 * For obtain the reference of this object, use the `getMap()` function call.
 *
 * @constructor
 * @param {cercaliax.MarkerOptions} options Marker options
 */
cercalia.Marker = function(options) {

	/**
	 * Class name
	 * @private
	 * @type {string}
	 */
	this.CLASS_NAME_ = "cercalia.Marker";

	/**
	 * @private
	 * @type {string}
	 */
	this.id_ = options.id ? options.id : 'CercaliaMarker_id_' + Date.now() + (Math.random()*0xffffffff|0);

	/**
	 * @private
	 * @type {cercalia.Map}
	 */
	this.map_ = null;

	/**
	 * @private
	 * @type {cercalia.LonLat}
	 */
	this.position_ = options.position ? options.position : new cercalia.LonLat(0,0);

	/**
	 * @private
	 * @type {cercalia.Icon}
	 */
	this.icon_ = options.icon ? options.icon : cercalia.Icon.createDefault();

	/**
	 * @private
	 * @type {cercalia.SimpleLabel}
	 */
	this.simpleLabel_ = options.simpleLabel ? options.simpleLabel : null;

	/**
	 * @private
	 * @type {boolean}
	 */
	this.draggable_ = undefined;

	/**
	 * @private
	 * @type {string}
	 */
	this.label_ = options.label ? options.label : null;

	/**
	 * @private
	 * @type {cercalia.Popup}
	 */
	this.popup_ = options.popup ? options.popup : null;

	/**
	 * @private
	 * @type {number}
	 */
	this.zIndex_ = options.zIndex ? options.zIndex : 200;

	/**
	 * @type {Function}
	 */
	this.onClick = options.onClick ? options.onClick : null;

	/**
   * @type {Function}
   */
  this.onDoubleClick = options.onDoubleClick ? options.onDoubleClick : null;

	/**
	 * @type {Function}
	 */
	this.onRightClick = options.onRightClick ? options.onRightClick : null;

	/**
	 * @type {Function}
	 */
	this.onMouseOver = options.onMouseOver ? options.onMouseOver : null;

	/**
	 * onMouseOver Function, if specified
	 * @type {Function}
	 */
	this.onMouseOut = options.onMouseOut ? options.onMouseOut : null;

	/**
	 * @private
	 * @type {Function}
	 */
	this.onDragStartCallBack_ = options.onDragStart ? options.onDragStart : null;

	/**
	 * @private
	 * @type {Function}
	 */
	this.onDragMoveCallBack_ = options.onDragMove ? options.onDragMove : null;

	/**
	 * @private
	 * @type {Function}
	 */
	this.onDragEndCallBack_ = options.onDragEnd ? options.onDragEnd : null;

	/**
	 * @private
	 * @type {Function}
	 */
	this.onDropCallBack_ = options.onDrop ? options.onDrop : null;

	/**
	 * @type {Function}
	 */
	this.onDragStart = function ( marker,lonlat, evt ) {

		if(this.popup_ && this.popup_.isOpen()) {
			this.popup_.hide();
		}

		if( this.onDragStartCallBack_ ) {
			this.onDragStartCallBack_(marker, lonlat, evt);
		}
	};

	/**
	 * @type {Function}
	 */
	this.onDragMove = function ( marker,lonlat, evt ) {
		this.isDragging_ = true;
		this.refreshPositionFromGeometry();

		if(this.onDragMoveCallBack_) {
			this.onDragMoveCallBack_(marker, lonlat, evt);
		}
	};

	/**
	 * @type {Function}
	 */
	this.onDragEnd = function ( marker,lonlat, evt ) {

		this.isDragging_ = false;
		this.refreshPositionFromGeometry();

		if(this.onDragEndCallBack_) {
			this.onDragEndCallBack_(marker, lonlat, evt);
		}

		if(this.onDropCallBack_) {
			this.onDropFunction_();
		}
	};

	/**
	 * @private
	 * @type {boolean}
	 */
	this.isDragging_ = false;

	/**
	 * @private
	 * @type {boolean}
	 */
	this.visible_ = true;

	/**
	 * @private
	 * @type {Object}
	 */
	this.optionsStyle_ = {
			image: this.icon_.getIconStyle(),
			zIndex: this.zIndex_
	};

	this.icon_.setMarker(this);

	if(this.simpleLabel_) {
		this.optionsStyle_.text = this.simpleLabel_.getTextStyle();
		this.simpleLabel_.setMarker(this);
	}

	/**
	 * Creates the feature
	 * @private
	 * @type {ol.Feature}
	 */
	this.feature_ = new ol.Feature({
		geometry: new ol.geom.Point(ol.proj.transform([this.position_.getLon(),this.position_.getLat()], 'EPSG:4326', 'EPSG:3857'))
	});
	this.feature_.setId(this.id_);

	/**
	 * @private
	 * @type {ol.style.Style}
	 */
	this.style_ = null;

	/**
	 * @private
	 * @type {Array.<ol.style.Style>}
	 */
	this.extraStyles_ = [];

	/**
	 * @private
	 * @type {Array.<cercalia.Icon>}
	 */
	this.extraIcons_ = [];

	this.setStyle_();

	/**
	 * Set the marker feature cyclic. For enabling class access from marker
	 * @private
	 * @type {cercalia.Marker}
	 */
	this.feature_.marker_ = this;

	this.setDraggable(options.draggable ? options.draggable : false);

	/**
	 * @private
	 * @type {string}
	 */
	this.feature_.featureType_ = this.CLASS_NAME_;
};




/**
 * @return {ol.Feature} Object <a href="http://openlayers.org/en/v3.0.0/apidoc/ol.Feature.html">`ol.Feature`</a>
 */
cercalia.Marker.prototype.getFeature = function() {
	return this.feature_;
};


/**
 * @return {string} Marker ID. If specified, if not returns null.
 */
cercalia.Marker.prototype.getId = function() {
	return this.id_;
};

/**
 * Assign ID to marker
 * @param {string} id
 */
cercalia.Marker.prototype.setId = function(id) {
	this.id_ = id;
};


/**
 *
 * @return {cercalia.LonLat} position The marker Array position `[lat, lon]`.
 */
cercalia.Marker.prototype.getPosition = function() {
	return this.position_;
};


/**
 * Updates the marker position through its geometry. This function is usefull on marker dragging,
 * where its position (obtained through its geometry) has been updated, but not its popup or label.
 */
cercalia.Marker.prototype.refreshPositionFromGeometry = function() {
	try{
		var coordinatesGeo = ol.proj.transform(this.getFeature().getGeometry().getCoordinates(), this.map_.getProjectionCode(), 'EPSG:4326') ;

		this.position_ = new cercalia.LonLat(coordinatesGeo[0],coordinatesGeo[1]);

		if(this.label_) {
			this.label_.setPosition(this.position_, this.map_.getProjectionCode());
		}

		if(this.popup_) {
			this.popup_.hide();
			this.popup_.setPosition(this.position_, this.map_.getProjectionCode());
		}
	}catch(e) {}
};


/**
 * Move the marker position to the specified via parameter
 * @param {cercalia.LonLat} position
 */
cercalia.Marker.prototype.setPosition = function(position) {

	var projectionCode = this.map_ ? this.map_.getProjectionCode() : 'EPSG:3857';

	var coordGeo = ol.proj.transform([position.getLon(),position.getLat()], 'EPSG:4326', projectionCode) ;

	this.position_ = position;

	var geometry = this.feature_.getGeometry();
	geometry.setCoordinates(coordGeo);
	//this.feature_.setGeometry(new ol.geom.Point(coordGeo));

	if(this.label_) {
		this.label_.setPosition(position, projectionCode);
	}

	if(this.popup_) {
		this.popup_.setPosition(position, projectionCode);//aqui es passa Array (ataca directament a ol.Overlay)
	}

	//BUG. Lo dejamos así porque después de actualizar la posición no se podía hacer dragging.
//	if(this.map_ && this.draggable_) {
//	    this.map_.dragFeatures_.remove(this.feature_);
//	    this.map_.dragFeatures_.push(this.feature_);
//		this.map_.markerLayer_.getSource().removeFeature(this.feature_);
//		this.map_.markerLayer_.getSource().addFeature(this.feature_);
//	    this.map_.addMarker(this);
//	}
//	this.setDraggable(this.draggable_);
};


/**
 * Move the parker position with an animation movement from a starting to ending position,
 * in a specified time (duration). Optionally it's possible to call a function specified via parameter.
 * @param {cercalia.LonLat} position Coordinate.
 * @param {number|undefined} duration Movement duration, in milliseconds (default value 2000ms)
 * @param {Function|undefined} callbackFn Callback function, when movement ends
 * @param {boolean|undefined} centerMapDuringMovement Center map during movement. Default `false`.
 * @param {number|undefined} numSteps Number of steps to animate. Default `100`.
 */
cercalia.Marker.prototype.movePosition = function(position, duration, callbackFn, centerMapDuringMovement, numSteps) {

	if(this.map_) {
		var projectionCode = this.map_.getProjectionCode();

		var fullDuration = duration?duration:2000;
		var steps = numSteps ? numSteps : 100;
		var start = new Date().getTime();
		var stepTime = fullDuration/steps;

		var posActOL = ol.proj.transform([this.position_.getLon(),this.position_.getLat()], 'EPSG:4326', projectionCode);
		var posOL = ol.proj.transform([position.getLon(),position.getLat()], 'EPSG:4326', projectionCode);

		//Calculem la distancia per etapa
		var totalDistanceX = posOL[0] - posActOL[0];
		var totalDistanceY = posOL[1] - posActOL[1];
		var stepDistanceX = totalDistanceX/steps;
		var stepDistanceY = totalDistanceY/steps;

		var lonAct = posActOL[0];
		var latAct = posActOL[1];

		var self = this;

		var lastIteration = function(intervalIdToDelete) {
			clearInterval(intervalId);

			self.position_ = position;
			self.getFeature().setGeometry(new ol.geom.Point(posOL));
			self.refreshPositionFromGeometry();

			if(callbackFn) {
				callbackFn(self);
			}

		};

		var numIteration = 0;
		var intervalId = setInterval(function() {

			//Ultima posicio
			if( (new Date().getTime()-start) >= fullDuration ) {
				lastIteration(intervalId);
			} else {
				lonAct+=stepDistanceX;
				latAct+=stepDistanceY;

				if(centerMapDuringMovement) {
					self.map_.getMap().getView().setCenter([lonAct,latAct]);
				}

				//Si ha llegado al punto final antes de que se acabe el tiempo forzamos a detener el bucle
				if( Math.abs(posOL[0]-lonAct) < 5 || Math.abs(posOL[1]-latAct) < 5 ) {
					lastIteration(intervalId);
				} else {
					if(self.label_) {
						self.label_.getOverlay().setPosition([lonAct,latAct]);//aqui es passa Array (ataca directament a ol.Overlay)
					}

					if(self.popup_) {
						self.popup_.getOverlay().setPosition([lonAct,latAct]);
					}
					self.getFeature().setGeometry( new ol.geom.Point( [lonAct,latAct] ));
				}
			}
			numIteration++;

		}, stepTime*0.95);
	}
};

/**
 * Change the zIndex value
 * @param {number} zIndex
 */
cercalia.Marker.prototype.setZIndex = function(zIndex) {
	this.zIndex_ = zIndex;
	this.optionsStyle_.zIndex = zIndex;
	this.setStyle_();
};

/**
 * @return {number} CSS marker zIndex
 */
cercalia.Marker.prototype.getZIndex = function() {
	return this.zIndex_;
};

/**
 * @param {boolean} draggable
 */
cercalia.Marker.prototype.setDraggable = function(draggable) {
	var map = this.map_;
	if(this.map_)this.map_.removeMarkers(this);
	this.draggable_ = draggable;
	if(map)map.addMarker(this);
};

/**
 * @private
 */
cercalia.Marker.prototype.onDropFunction_ = function() {

	var map = this.map_;
	var mapOL = this.map_.getMap();
	var pixel = this.map_.coordToPixel(this.getPosition());
	var iconStyle = this.getIcon().getIconStyle();
	var iconSize = iconStyle.getSize();
	var iconAnchor = iconStyle.getAnchor();
	var iconScale = iconStyle.getScale();

	pixel[0] = Math.round(pixel[0] - Math.round(iconAnchor[0] * iconScale));
	pixel[1] = Math.round(pixel[1] - Math.round(iconAnchor[1] * iconScale));

	//Aquí el pixel está en el centro del icono
	//var pixelNW = pixel;
	//var pixelSE = [pixel[0] + Math.round(iconSize[0]*iconScale), pixel[1] + Math.round(iconSize[1]*iconScale)];
	var pixelNE = [pixel[0] + Math.round(iconSize[0] * iconScale), pixel[1]];
	var pixelSW = [pixel[0], pixel[1] + Math.round(iconSize[1] * iconScale)];

//				//DEBUG (nomes per mostrar contorn)
//				this.map_.getLayerByName("MarkerLayer").getSource().addFeature(new ol.Feature(new ol.geom.Point(mapOL.getCoordinateFromPixel(pixel))));
//				var linestring = new cercalia.Linestring({
//					path: [
//					       	map.pixelToCoord(pixelNW),
//					       	map.pixelToCoord(pixelNE),
//					       	map.pixelToCoord(pixelSE),
//					       	map.pixelToCoord(pixelSW),
//					       	map.pixelToCoord(pixelNW)
//					      ]
//				});
//				this.map_.addLinestring(linestring);
//				//FIDEBUG

	//Buscamos las features (tipo marker)
	var coordSW = mapOL.getCoordinateFromPixel(pixelSW);
	var coordNE = mapOL.getCoordinateFromPixel(pixelNE);

	var extent = coordSW.concat(coordNE);
	var listMarkersInExtent = [];

	//Recorrido por features dentro de un 'extent'
	this.map_.getLayerByName("MarkerLayer").getSource().forEachFeatureInExtent(extent, function(feature) {
		//Comprobamos para no devolver el mismo
		if(feature.marker_ && this.getId() != feature.marker_.getId()) {
			listMarkersInExtent.push(feature.marker_);
		}
	}, this);

	this.map_.getLayerByName("DragLayer").getSource().forEachFeatureInExtent(extent, function(feature) {
		//Comprobamos para no devolver el mismo
		if(feature.marker_ && this.getId() != feature.marker_.getId()) {
			listMarkersInExtent.push(feature.marker_);
		}
	}, this);

	this.onDropCallBack_(listMarkersInExtent);
};


/**
 * @return {boolean} Boolean showing if marker it's draggable.
 */
cercalia.Marker.prototype.isDraggable = function() {
	return this.draggable_;
};


/**
 * Show or hide the marker, including its SimpleLabel, Label or Popup.
 * @param {boolean} visible
 */
cercalia.Marker.prototype.setVisible = function(visible) {
	this.visible_ = visible;
	this.icon_.setOpacity(visible ? 1 : 0);
	for (var i = 0; i < this.extraIcons_.length; i++) {
		this.extraIcons_[i].setOpacity(visible ? 1 : 0);
	}

	this.optionsStyle_.image = this.icon_.getIconStyle();
	this.setStyle_();

	//Si tiene simpleLabel. Lo escondemos
	if(this.simpleLabel_) {
		this.simpleLabel_.setVisible(visible);
	}

	if(this.label_) {
		visible ? this.label_.show() : this.label_.hide();
	}

	if(this.popup_ && ! visible ) {
		this.popup_.hide();
	}

	//if(this.isDraggable && !visible ) {
		//this.popup_.hide();
	//}

};

/**
 * Change the marker icon
 * @param {cercalia.Icon} icon
 */
cercalia.Marker.prototype.setIcon = function(icon) {
	this.icon_ = icon;
	this.icon_.setMarker(this);
	this.optionsStyle_.image = icon.getIconStyle();
	this.setStyle_();
};

/**
 * @return {cercalia.Icon} Icon.
 */
cercalia.Marker.prototype.getIcon = function() {
	return this.icon_;
};


/**
 * SimpleLabel to add/modify related with the marker
 * @param {cercalia.SimpleLabel} simpleLabel
 */
cercalia.Marker.prototype.setSimpleLabel = function(simpleLabel) {
	this.simpleLabel_ = simpleLabel;
	this.simpleLabel_.setMarker(this);
	this.optionsStyle_.text = simpleLabel.getTextStyle();
	this.setStyle_();
};


/**
 * @return {cercalia.SimpleLabel} Returns {@link cercalia.SimpleLabel} if created. If not, returns `null`.
 */
cercalia.Marker.prototype.getSimpleLabel = function() {
	return this.simpleLabel_;
};


/**
 * @param {cercalia.Popup} popup
 */
cercalia.Marker.prototype.addPopup = function(popup) {

	var projectionCode = this.map_ ? this.map_.getProjectionCode() : 'EPSG:3857';

	if(popup==null) {
		this.popup_ = null;
	} else {
		if(this.popup_ == null ) {
			this.popup_ = popup;
			this.popup_.setPosition(this.position_, projectionCode);
		}
	}

	if(this.map_ != null) {
		this.map_.getMap().addOverlay(popup.getOverlay());
	}
};


/**
 * @return {cercalia.Map} The map where it's painted, if not returns `null`.
 */
cercalia.Marker.prototype.getMap = function() {
	return this.map_;
};

/**
 * @return {cercalia.Popup} Popup, if don't have returns `null`.
 */
cercalia.Marker.prototype.getPopup = function() {
	return this.popup_;
};


/**
 * @param {cercalia.Map} map
 */
cercalia.Marker.prototype.setMap = function(map) {
	if(map) {
		if(this.map_ == null) {
			this.map_ = map;
			var projectionCode = this.map_.getProjectionCode();

			//Si té una altra projecció que no és la original
			if( projectionCode != 'EPSG:3857') {
				var point = new ol.geom.Point(ol.proj.transform([this.position_.getLon(),this.position_.getLat()], 'EPSG:4326', projectionCode));
				this.getFeature().setGeometry(point);
			}

			//Si se ha especificado el label
			if(this.label_) {
				this.map_.getMap().addOverlay(this.label_.getOverlay());
				this.label_.setMarker(this);
			}

			//Si tiene popup lo añadimos
			if(this.popup_) {
				this.map_.getMap().addOverlay(this.popup_.getOverlay());
				this.popup_.setPosition(this.position_, projectionCode); //Aqui lo pasamos con geograficas
			}
		}
	} else {
		if(this.map_) {
			if(this.popup_) {
				this.map_.getMap().removeOverlay(this.popup_.getOverlay());
			}

			if(this.label_) {
				this.map_.getMap().removeOverlay(this.label_.getOverlay());
			}
		}
		this.map_ = null;
	}
};

/**
 * @private
 */
cercalia.Marker.prototype.setStyle_ = function() {
	this.style_ = new ol.style.Style(this.optionsStyle_);
	if (this.feature_) {
		if (this.extraStyles_.length === 0) {
			this.feature_.setStyle(this.style_);
		} else {
			this.feature_.setStyle([this.style_].concat(this.extraStyles_));
		}
	}
};

/**
 * Destroy popup
 */
cercalia.Marker.prototype.destroy = function() {

	if(this.popup_) {
		if(this.map_!=null) {
			this.map_.getMap().removeOverlay(this.popup_.getOverlay());
		}
		this.popup_.destroy();
		this.popup_ = null;
	}

	if(this.label_) {
		if(this.map_!=null) {
			this.map_.getMap().removeOverlay(this.label_.getOverlay());
		}
		this.removeLabel();
		this.label_ = null;
	}

	if(this.simpleLabel_) {
		this.simpleLabel_.destroy();
		this.simpleLabel_ = null;
	}


	if(this.map_!=null) {
		this.map_.removeMarkers(this);
		this.map_ = null;
	}

	for (var key in this) {
		delete this[key];
	}

};

/**
 * Assign/change marker label text
 * @param {cercalia.Label} label Label content
 */
cercalia.Marker.prototype.setLabel = function(label) {

	if(this.label_) {
		this.label_.destroy();
	}

	if(this.map_) {
		this.label_ = label;
		this.label_.setPosition(this.position_, this.map_.getProjectionCode());
		this.map_.getMap().addOverlay(label.getOverlay());
		if( !this.map_.showingLabels() ){
			this.hideLabel();
		}
	}

};

/**
 * Show marker label
 */
cercalia.Marker.prototype.showLabel = function() {
	if(this.label_) {
		this.label_.show();
	}
};

/**
 * Hide marker label
 */
cercalia.Marker.prototype.hideLabel = function() {
	if(this.label_) {
		this.label_.hide();
	}
};

/**
 * @return {cercalia.Label} label Label assigned to th marker.
 */
cercalia.Marker.prototype.getLabel = function() {
	return this.label_;
};


/**
 * Destroy the label object
 */
cercalia.Marker.prototype.removeLabel = function() {
	if(this.label_) {
		this.label_.destroy();
		this.label_ = null;
	}
};

/**
 * @public
 * @return true if marker is dragging, otherwise false.
 */
cercalia.Marker.prototype.isDragging = function() {
	return this.isDragging_;
};



/**
* @return {string} ClassName: `cercalia.Marker`
*/
cercalia.Marker.prototype.getClass = function() {
	return this.CLASS_NAME_;
};


/**
 * @return {boolean} `true` if visible, else `false`.
 */
cercalia.Marker.prototype.isVisible = function(){
	return this.visible_;
};


/**
 * Add extra icon with a plus zIndex. You can add multiple icons to a marker.
 * @param {cercalia.Icon} icon Icon
 */
cercalia.Marker.prototype.addExtraIcon = function(icon) {
  var iconStyle = icon.getIconStyle();
  var styles = this.feature_.getStyle();
  icon.setMarker(this);
  this.extraIcons_.push(icon);

  if (!Array.isArray(styles)) {
    styles = [styles];
  }

  var lastStyle = styles[styles.length - 1];
  var newStyle = new ol.style.Style({
    image: iconStyle,
    zIndex: lastStyle.getZIndex() + 1
  });

  styles.push(newStyle);

  this.extraStyles_ = styles.slice(1); //Clone array except first argument.
	this.setStyle_();
  //this.feature_.setStyle(styles);
};

/**
 * @return {boolean} `true` if extra icons.
 */
cercalia.Marker.prototype.hasExtraIcons = function() {
  return this.extraStyles_.length > 0;
};

/**
 * @return {Array.<cercalia.Icon>} Return extra icons
 */
cercalia.Marker.prototype.getExtraIcons = function() {
	return this.extraIcons_;
};

/**
 * Force to draw feature.
 */
cercalia.Marker.prototype.redraw = function() {
	this.feature_.changed();
};