Source: C:/temp/jsdocinputdirs/cercalia.map.js

/**
 * Javascript File class cercalia.Map
 * @author Edu Logrono
 * @version 0.1des
 */

/**
 * `cercalia.MapTypes.CERCALIA`<br/>
 * `cercalia.MapTypes.OSM`<br/>
 * `cercalia.MapTypes.OPENSTREETMAP`<br/>
 * `cercalia.MapTypes.SATELLITE`<br/>
 * `cercalia.MapTypes.HYBRID`<br/>
 * @enum {string}
 */
cercalia.MapTypes = {
	CERCALIA: 'default2',
	MAPQUEST: 'mapquest',
	OSM: 'osm',
	OPENSTREETMAP: 'openstreetmap',
	SATELLITE: 'satellite',
	HYBRID: 'hybrid'
};


/**
 * `cercalia.MapAnimation.ROTATE_LEFT`<br/>
 * `cercalia.MapAnimation.ROTATE_RIGHT`<br/>
 * `cercalia.MapAnimation.PAN`<br/>
 * `cercalia.MapAnimation.ELASTIC`<br/>
 * `cercalia.MapAnimation.BOUNCE`<br/>
 * `cercalia.MapAnimation.FLY`<br/>
 * `cercalia.MapAnimation.SPIRAL`
 * @enum {string}
 */
cercalia.MapAnimation = {
	ROTATE_LEFT: "rotate_left",
	ROTATE_RIGHT: "rotate_right",
	PAN: "pan",
	ELASTIC: "elastic",
	BOUNCE: "bounce",
	FLY: "fly",
	SPIRAL: "spiral"
};


/**
 * @private
 * @enum {string}
 */
cercalia.LayerEnumeration = {
	MarkerLayer: 'MarkerLayer',
	MarkerApiLayer: 'MarkerApiLayer',
	RouteLayer: 'RouteLayer',
	FeatureLayer: 'FeatureLayer',
	DragLayer: 'DragLayer',
	DrawLayer: 'DrawLayer',
	ClusterLayerMarker: 'ClusterLayerMarker',
	ClusterLayerMarkerDraggable: 'ClusterLayerMarkerDraggable',
	OpenlrGeojsonLayer: 'OpenlrGeojsonLayer',
	CercaliaTrafficReal: 'CercaliaTrafficReal',
	CercaliaPredictedTraffic: 'CercaliaPredictedTraffic',
	CercaliaAffectationsLines: 'CercaliaAffectationsLines',
	CercaliaPKs: 'CercaliaPKs',
	CercaliaWMSPois: 'CercaliaWMSPois'
};


/**
 * `cercalia.ContextMenuOptions.Location`<br/>
 * `cercalia.ContextMenuOptions.Routing`<br/>
 * @enum {string}
 */
cercalia.ContextMenuOptions = {
	Location: 'Location',
	Routing: 'Routing'
};


/**
 * @classdesc
 *
 * The map is the main API component. You can use it for painting markers, features, etc... <br/>
 * Uses ol.Map 'core' <a href='http://openlayers.org/en/v3.6.0/apidoc/'>OpenLayers3</a> .<br/>
 * For access to OpenLayers object, use the `getMap()` function<br/>
 *
 * Example for creating the map:
 *
 * 		var map = new cercalia.Map({
 * 			target:'divMap',
 * 			controls: []
 * 		});
 *
 *
 * We offer several basemaps: `cercalia.MapTypes.CERCALIA`,
 * `cercalia.MapTypes.OSM`, `cercalia.MapTypes.SATELLITE` or `cercalia.MapTypes.HYBRID`.
 * It's also possible to add or change a base map using WMS requests.
 *
 *
 * @constructor
 * @param {cercaliax.MapOptions} options Map options
 */
cercalia.Map = function(options) {

	/**
	 * @private
	 * @type {string}
	 */
	this.id_ = 'CercaliaMapId_' + new Date().getTime() + Math.round((Math.random() * 1000));

	/**
	 * @private
	 * @type {string}
	 */
	this.cercaliaKey_ = cercaliaGlobals.key; //Esta variable siempre se pasara al descargar la API

	/**
	 * @private
	 * @type {string}
	 */
	this.server_ = servers.servlet;

	/**
	 * @type {string}
	 * @private
	 */
	this.containerId_ = options.target;

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

	/**
	 * @private
	 * @type {string|undefined}
	 */
	this.scaleUnits_ = options.scaleUnits && ['metric', 'imperial'].indexOf(options.scaleUnits) !== -1 > 0 ? options.scaleUnits : 'metric';

	/**
	 * @private
	 * @type {Array.<string>|string|undefined}
	 */
	this.controls_ = this.initializeControls_(options.controls);

	/**
	 * @private
	 * @type {Array.<string>|string|undefined}
	 */
	this.mapLayers_ = options.mapLayers;

	/**
	 * @private
	 * @type {ol.Collection}
	 */
	this.controlsMap_ = this.initializeMapControls_(this.controls_);

	/**
	 * @private
	 * @type {ol.Collection}
	 */
	this.controlsBar_ = this.initializeBarControls_(this.controls_);

	/**
	 * @private
	 * @type {boolean}
	 */
	this.altRotation_ = typeof(options.altRotation) == "boolean" ? options.altRotation : true;

	/**
	 * @private
	 * @type {number}
	 */
	this.separateOverlapMarkersOnZoom_ = options.separateOverlapMarkersOnZoom ? options.separateOverlapMarkersOnZoom : null;

	/**
	 * @private
	 * @type {string}
	 */
	this.projectionCode_ = options.projectionCode ? options.projectionCode : 'EPSG:3857';

	/**
	 * @private
	 * @type {ol.proj}
	 */
	this.mapProjection_ = new ol.proj.Projection({
		code: ol.proj.get(this.projectionCode_).getCode(),
		extent: ol.proj.get(this.projectionCode_).getExtent(),
		units: ol.proj.get(this.projectionCode_).getUnits()
	});

	/**
	 * @private
	 * @type {cercalia.Marker}
	 */
	this.markerOver_ = null;

	/**
	 * @private
	 * @type {ol.Feature}
	 */
	this.clusterFeatureOver_ = null;

	/**
	 * @private
	 * @type {cercalia.WMS}
	 */
	this.layerPoiWMS_ = null;

	/**
	 * @private
	 * @type {cercalia.WMS}
	 */
	this.layerLiveTraffic_ = null;

	/**
	 * @private
	 * @type {cercalia.WMS}
	 */
	this.layerPredictedTraffic_ = null;

	/**
	 * @private
	 * @type {cercalia.WMS}
	 */
	this.layerPksTraffic_ = null;

	/**
	 * @private
	 * @type {Array.<string>}
	 */
	this.poicatsEnabled_ = [];

	/**
	 * @private
	 * @type {ol.layer.Vector}
	 */
	this.markerLayer_ = new ol.layer.Vector({
		source: new ol.source.Vector({}),
		name: cercalia.LayerEnumeration.MarkerLayer,
		opacity: 1,
		visible: true
	});

	/**
	 * @private
	 * @type {ol.layer.Vector}
	 */
	this.markerApiLayer_ = new ol.layer.Vector({
		source: new ol.source.Vector({}),
		name: cercalia.LayerEnumeration.MarkerApiLayer,
		opacity: 1,
		visible: true
	});

	/**
	 * @private
	 * @type {ol.layer.Vector}
	 */
	this.routeLayer_ = new ol.layer.Vector({
		source: new ol.source.Vector({}),
		name: cercalia.LayerEnumeration.RouteLayer,
		opacity: 1,
		visible: true
	});

	/**
	 * @private
	 * @type {ol.layer.Vector}
	 */
	this.featureLayer_ = new ol.layer.Vector({
		source: new ol.source.Vector({}),
		name: cercalia.LayerEnumeration.FeatureLayer,
		visible: true
	});

	/**
	 * @private
	 * @type {ol.layer.Vector}
	 */
	this.clusterLayer_ = null;

	/**
	 * @private
	 * @type {ol.layer.Vector}
	 */
	this.clusterLayerDraggable_ = null;

	/**
	 * @private
	 * @type {ol.layer.Vector}
	 */
	this.drawLayer_ = new ol.layer.Vector({
		source: new ol.source.Vector(),
		name: cercalia.LayerEnumeration.DrawLayer
	});

	/**
	 * @private
	 * @type {ol.Collection}
	 */
	this.dragLayer_ = new ol.layer.Vector({
		source: new ol.source.Vector({}),
		name: cercalia.LayerEnumeration.DragLayer,
		visible: true
	});


	/**
	 * @private
	 * @type {ol.layer.Tile}
	 */
	this.baseLayer_ = this.createBaseLayer_(options.mapType ? options.mapType : cercalia.MapTypes.CERCALIA);

	var layersToMap = [
		this.baseLayer_,
		this.routeLayer_,
		this.featureLayer_,
		this.markerLayer_,
		this.markerApiLayer_,
		this.drawLayer_,
		this.dragLayer_
	];

	/**
	 * @private
	 * @type {ol.Collection}
	 */
	this.editableFeaturesCollection_ = new ol.Collection();


	/**
	 * @private
	 * @type {ol.interaction.Modify}
	 */
	this.modifyControl_ = new ol.interaction.Modify({
		features: this.editableFeaturesCollection_
	});

	var center = options.center ? options.center : new cercalia.LonLat(-2.530985346701069, 40.14298996438731);

  /**
   * @private
   * @type {number}
   */
  this.maxZoom_ = options.maxZoom !== undefined ?
      (options.maxZoom > 20 ? 20 : options.maxZoom) : 19;

	/**
	 * @private
	 * @type {Object}
	 */
	this.viewOptions_ = {
		projection: this.mapProjection_,
		minZoom: options.minZoom ? options.minZoom : 2,
		maxZoom: this.maxZoom_,
		center: ol.proj.transform([center.getLon(), center.getLat()], 'EPSG:4326', this.projectionCode_),
		zoom: options.zoom ? options.zoom : 6,
		rotation: options.rotation ? Math.PI * options.rotation / 180 : 0
	};

	//Limitación de los límites
	if (options.restrictedBounds) {
		this.viewOptions_.extent = ol.proj.transformExtent(options.restrictedBounds.toArray(), 'EPSG:4326', this.projectionCode_);
	}

	var extendInteractions = [];
	if (this.altRotation_) {
		extendInteractions.push(new ol.interaction.DragRotateAndZoom({
			condition: ol.events.condition.altKeyOnly //Drag&Zoom con tecla 'Alt'
		}));
	}

	// l'Interaction dels dragables creats per nosaltes.
	this.dragInteraction_ = new cercalia.Interaction.DragFeatures();
	extendInteractions.push(this.dragInteraction_);

	/**
	 * @private
	 * @type {Array<cercalia.WMS|cercalia.KML>}
	 */
	this.arrayLayersAdded_ = [];

	/**
	 * @private
	 * @type {ol.Map}
	 */
	this.map_ = new ol.Map({
		controls: this.controlsMap_,
		interactions: ol.interaction.defaults({
			zoom: false,
			rotate: false,
			attributionControl: false,
			logoControl: false
		}).extend(extendInteractions),
		target: this.containerId_,
		//renderer: ['dom',/*'webgl',*/'canvas'],
		renderer: ['canvas', 'dom'],
		layers: layersToMap,
		view: new ol.View(this.viewOptions_)
	});

	if (options.bounds) {
		var bounds = options.bounds;
		var extent = ol.proj.transformExtent(bounds.toArray(), 'EPSG:4326', this.projectionCode_);
		this.map_.getView().fitExtent(extent, this.map_.getSize());
	}

	/**
	 * @private
	 * @type {ol.Geolocation}
	 */
	this.geolocation_ = null;

	if (options.centerOnGeolocation) {
		this.initialGeolocation_();
	}

	// Transformamos a button de jQuery el boton de maximizar mapa. Colocado por ol por defecto
	cercalia.jQuery("#" + this.containerId_ + " .ol-full-screen button").removeClass("ol-has-tooltip").button({
		icons: {
			primary: "ui-icon-arrow-4-diag"
		},
		text: false
	});
	cercalia.jQuery("#" + this.containerId_ + " .ol-rotate button").removeClass("ol-has-tooltip").button({
		icons: {
			primary: " ui-icon-arrowrefresh-1-n"
		},
		text: false
	});

	//Hacemos que el objeto sea ciclico
	this.map_.cercalia = this;

	//Cambiamos la clase del viewport
	var viewport = this.map_.getViewport();
	viewport.className = 'cercaliaMap';
	viewport.parentNode.style.position = 'relative';
	viewport.style.touchAction = 'none';
	viewport.style.msTouchAction = 'none';

	var self = this;
	cercalia.jQuery(window).resize(function() {
		setTimeout(function() {
			self.getMap().updateSize();
		}, 200);
	});


	/**
	 * @private
	 * @type {cercalia.widget.MainMenu}
	 */
	this.mainMenuWidget_ = null;

	/*  WIDGETS  */

	/**
	 * @private
	 * @type {cercalia.widget.PoiProximity}
	 */
	this.poiProximityWidget_ = null;


	if (options.hideRightClickMenuOptions && options.hideRightClickMenuOptions == true) {
		this.rightClickMenuOptions_ = [cercalia.ContextMenuOptions.Location];
	} else {
		this.rightClickMenuOptions_ = [cercalia.ContextMenuOptions.Location, cercalia.ContextMenuOptions.Routing];
	}

	/**
	 * @private
	 * @type {Collection<cercalia.ContextMenuOptions>}
	 */
	this.rightClickMenuOptions_ = options.rightClickMenuOptions ? options.rightClickMenuOptions : this.rightClickMenuOptions_;


	/**
	 * @private
	 * @type {ol.Overlay}
	 */
	this.contextMenuOverlay_ = null;

	/**
	 * @private
	 * @type {Function}
	 */
	this.defaultClickMap_ = null;

  /**
   * @private
   * @type {boolean}
   */
  this.disableMouseOverEvents_ = options.disableMouseOverEvents !== undefined ? options.disableMouseOverEvents : false;

	if (options.defaultClick) {
		this.setDefaultClick(options.defaultClick);
	}


	if (options.rightClickMenu == null || options.rightClickMenu) {
		this.contextMenuOverlay_ = this.initializeContextMenu_();
	} else if (options.rightClickFunction) {
		this.initializeRightClickFunction_(options.rightClickFunction);
	}

	// Events de zoom end i rotation end

	/**
	 * @private
	 * @type {Function}
	 */
	this.onRotationEnd_ = typeof(options.onRotationEnd) == "function" ? options.onRotationEnd : null;

	/**
	 * @private
	 */
	this.currentRotation_ = this.map_.getView().getRotation();

	/**
	 * @private
	 * @type {Function}
	 */
	this.onZoomEnd_ = typeof(options.onZoomEnd) == "function" ? options.onZoomEnd : null;

	/**
	 * @private
	 * @type {Function}
	 */
	this.onOverlapMarkersEnd_ = typeof(options.onOverlapMarkersEnd) == "function" ? options.onOverlapMarkersEnd : null;

	/**
	 * @private
	 */
	this.currentZoom_ = this.map_.getView().getZoom();

	//Inicializamos los eventosf
	this.initializeMapEvents_();

	//this.initializeDragFeatureEvents_();

	//Inicializemos la interficie grafica
	if (this.enabledAPIInterface_) {
		this.initializeTopBarInterface_();
	}

	/**
	 * @private
	 * @type {cercalia.service.Geocoding}
	 */
	this.geocodingService_ = new cercalia.service.Geocoding();

	/**
	 * @private
	 * @type {cercalia.service.ReverseGeocoding}
	 */
	this.reverseGeocodingService_ = new cercalia.service.ReverseGeocoding();

	/**
	 * @private
	 * @type {cercalia.RouteMap}
	 */
	this.routing_ = new cercalia.RouteMap({
		map: this,
		version: 2
	});

	/**
	 * @private
	 * @type {ol.interaction.Draw}
	 */
	this.measureInteractionActive_ = null;

	/**
	 * "Loading" overlay, blocks the map use
	 * @type {cercalia.widget.LoadingOverlay}
	 */
	this.overlayLoading = new cercalia.widget.LoadingOverlay({
		div: this.containerId_
	});

	/**
	 * @private
	 * @type {Object}
	 */
	this.logo_ = cercalia.jQuery('<div class="mapLogo"/>').appendTo(cercalia.jQuery('#' + this.containerId_ + ' .cercaliaMap'));

	/**
	 * @private
	 * @type {string}
	 */
	this.logoImg_ = null;

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

	/**
	 * @private
	 * @type {ol.interaction.Draw}
	 */
	this.drawInteraction_ = null;

	/**
	 * @private
	 * @type {ol.interaction.Modify}
	 */
	this.modifyDrawFeatures_ = null;

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

	/**
	 * @private
	 * @type {Element|null}
	 */
	this.topBarElement_ = null;

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

/**
 * @param {Function|undefined} func Function
 */
cercalia.Map.prototype.setDefaultClick = function(func) {

	if (this.defaultClickMap_) {
		this.map_.un('singleclick', this.defaultClickMap_);
	}

	var self = this;
	this.defaultClickMap_ = function(e) {
		e.preventDefault();
		if (func && typeof(func) == "function") {
			var pixel = self.map_.getEventPixel(e.originalEvent);
			var coordinateOL = self.map_.getCoordinateFromPixel(pixel);
			var coordinateGeo = ol.proj.transform([coordinateOL[0], coordinateOL[1]], self.projectionCode_, 'EPSG:4326');
			func(pixel, new cercalia.LonLat(coordinateGeo[0], coordinateGeo[1]));
		}
	};

	this.map_.on('singleclick', this.defaultClickMap_);
};


/**
 * Adds a layer <a href="http://openlayers.org/en/v3.6.0/apidoc/ol.layer.Layer.html">`ol.layer.Layer`</a> specifying
 * the position (optional). The layers are declarated in an array and the Z value works from less to more. The Layer
 * basemap (map), is fixed in the first position 0.<br/>
 * If you need to change the baseLayer, change it to position 1 or remove the baseLayer.
 * @param {ol.layer} layer
 * @param {number} position
 */
cercalia.Map.prototype.addLayer = function(layer, position) {

	if ((layer instanceof cercalia.Heatmap) || (layer instanceof cercalia.WMS)) {
		layer.setMap(this, position ? position : 1); //Pregunta Marc: pq position 1?
	} else if (layer instanceof cercalia.KML) {
		layer.setMap(this, position);
	} else {
		this.arrayLayersAdded_.push(layer);
		if (position || position == 0) {
			this.map_.getLayerGroup().getLayers().insertAt(position, layer);
		} else {
			//Si no se especifica la posición del layer
			this.map_.addLayer(layer);
		}
	}
};


/**
 * Clean the layer specified by parameter
 * @param {ol.layer} layer
 */
cercalia.Map.prototype.removeLayer = function(layer) {

	if (layer instanceof cercalia.KML) {
		layer = layer.getLayer();
	}

	if (layer instanceof ol.layer.Vector) {
		this.removeLayerFeatures_(layer);
	}

	this.map_.removeLayer(layer);

	var index = this.arrayLayersAdded_.indexOf(layer);
	if (index > -1) {
		this.arrayLayersAdded_.splice(index, 1);
	}
	return this;
};

/**
 *
 * Clean all features from layer
 * @private
 * @param {ol.layer} layer
 */
cercalia.Map.prototype.removeLayerFeatures_ = function(layer) {
	var source = layer.getSource();

	var arrFeatures = layer.getSource().getFeatures();

	for (var i = 0; i < arrFeatures.length; i++) {
		var feature = arrFeatures[i];
		if (feature.feature_) {
			feature.feature_.destroy();
		}
	}
	return this;
};



/**
 * Add a new option in context menu, adding a function on click to this option.
 * @param {string} buttonName Text to show in the neu context menu option
 * @param {Function} clickFunction Function to call linked to new button
 * @param {string} id Option's identifier.
 */
cercalia.Map.prototype.createContextMenuOption = function(buttonName, clickFunction, id) {
	if (this.contextMenuOverlay_) {
		this.contextMenuOverlay_.addOption(id, buttonName, clickFunction);
	} else {
		console.warn("No context menu inicialzed");
	}
};

/**
 * Open the context menu at the coordinates specified by parameters. If not specified, opens
 * the context menu in the map center according to the current position.
 * @param {cercalia.LonLat|undefined} lonLat Coordinate
 */
cercalia.Map.prototype.openContextMenu = function(lonLat) {
	if (this.contextMenuOverlay_) {
		var center = lonLat ? lonLat : this.getCenter();
		this.contextMenuOverlay_.open(center, this.getProjectionCode());
		if (self.routing_) {
			self.routing_.hideContextMenu();
		}
	} else {
		console.warn("No context menu inicialzed");
	}
};

cercalia.Map.prototype.getDirectionFunction_ = function(evnt, lonLat) {
	if (lonLat) {
		var map = this; //map
		this.reverseGeocodingService_.getDirection(lonLat, function(data) {
			var ge = data.cercalia.proximity.gelist.ge;
			var type = data.cercalia.proximity.gelist.type;

			var portalBefore = ["ARE", "AUS", "CAN", "EGY", "GBR", "IND", "IRL", "ISR", "KWT", "NZL", "OMN", "QAT", "SAU", "THA", "USA", "ZAF"].indexOf(ge.country.id) != -1;

			var content =
				"<table style='width:200px;font-size:7pt;font-family:Tahoma'>" +
				(type == 'adr' ? "<tr><td>" + cercalia.i18n._('Street') + "</td><td>" + (portalBefore ? ((ge.housenumber ? ge.housenumber.value + " " : "") + ge.name + (ge.km ? ", km: " + ge.km.value : "")) : (ge.name + (ge.housenumber ? ", " + ge.housenumber.value : (ge.km ? ", km: " + ge.km.value : "")))) + "</td></tr>" : "") +
				(type == 'st' ? "<tr><td>" + cercalia.i18n._('Street') + "</td><td>" + ge.name + "</td></tr>" : "") +
				(ge.city ? "<tr><td>" + cercalia.i18n._('City') + "</td><td>" + ge.city.value + "</td></tr>" : "") +
				(ge.postalcode ? "<tr><td>" + cercalia.i18n._('Postal code') + "</td><td>" + ge.postalcode.id + "</td></tr>" : "") +
				(ge.municipality ? "<tr><td>" + cercalia.i18n._('Municipality') + "</td><td>" + ge.municipality.value + "</td></tr>" : "") +
				(ge.subregion ? "<tr><td>" + cercalia.i18n._('Subregion') + "</td><td>" + ge.subregion.value + "</td></tr>" : "") +
				(ge.region ? "<tr><td>" + cercalia.i18n._('Region') + "</td><td>" + ge.region.value + "</td></tr>" : "") +
				"<tr><td>" + cercalia.i18n._('Country') + "</td><td>" + ge.country.value + "</td></tr>" +
				"<tr><td>" + cercalia.i18n._('Coords') + "</td><td>" + ge.coord.x + "," + ge.coord.y + "</td></tr>" +
				"</table>";

			var popup = new cercalia.Popup({
				title: cercalia.i18n._('Info'),
				content: content,
				visible: true
			});

			var marker = new cercalia.Marker({
				position: lonLat,
				popup: popup
			});

			map.addMarker(marker);
		});
	}
};


/**
 * Creates and returns  the context menu.
 * @private
 * @return {cercalia.ContextMenu}
 */
cercalia.Map.prototype.initializeContextMenu_ = function() {

	var self = this;

	var getProximityFunction = function(evnt, lonLat) {
		if (lonLat) {
			if (self.poiProximityWidget_ != null) {
				self.poiProximityWidget_.findByLonLat(lonLat);
			}
		}
	};

	// Create the context menu
	var idContextMenu = "contextMenu" + this.id_;
	var contextMenu = new cercalia.ContextMenu({
		id: idContextMenu,
		map: this
	});

	this.addRightClickMenuOptions_(contextMenu);

	if (this.poiProximityWidget_ !== null) {
		contextMenu.addOption('getProximity', 'Whats close by', getProximityFunction);
	}

	return contextMenu;
};

/**
 * @deprecated
 * deprecated function, equivalent: map.getRouteMap().removeRoute();
 */
cercalia.Map.prototype.removeApiRoute = function() {
	this.routing_.removeRoute();
};

/**
 * @private
 * @param {ol.Event} evnt
 * @param {cercalia.LonLat} lonLat
 */
cercalia.Map.prototype.setRouteEndFunction_ = function(evnt, lonLat) {
	var self = this;
	if (lonLat) {
		this.routing_.setDestination(lonLat, true)
	}
};

/**
 * @private
 * @param {ol.Event} evnt
 * @param {cercalia.LonLat} lonLat
 */
cercalia.Map.prototype.setRouteWaypoint_ = function(evnt, lonLat) {

	var self = this;
	if (lonLat) {
		this.routing_.setStep(lonLat, true);
	}
};

/**
 * @private
 * @param {ol.Event} evnt
 * @param {cercalia.LonLat} lonLat
 */
cercalia.Map.prototype.setRouteStartFunction_ = function(evnt, lonLat) {
	var self = this;
	if (lonLat) {
		this.routing_.setOrigin(lonLat, true);
	}
};


/**
 * @private
 * @param {Function} func
 */
cercalia.Map.prototype.initializeRightClickFunction_ = function(func) {
	var self = this;
	//Boton derecho sobre el mapa
	cercalia.jQuery(this.map_.getViewport()).bind('contextmenu', function(e) {
		e.preventDefault();
		if (func && typeof(func) == "function") {
			var pixel = self.map_.getEventPixel(e.originalEvent);
			var coordinateOL = self.map_.getCoordinateFromPixel(pixel);
			var coordinateGeo = ol.proj.transform([coordinateOL[0], coordinateOL[1]], self.projectionCode_, 'EPSG:4326');
			func(pixel, new cercalia.LonLat(coordinateGeo[0], coordinateGeo[1]));
		}
	});
};


/**
 * Centers the map  Centra la posición al mapa according to the latest geocode avaliable in 'localStorage'.
 * Inf localStorage is not avaliable
 * @private
 */
cercalia.Map.prototype.initialGeolocation_ = function() {

	this.geolocation_ = new ol.Geolocation();

	//Comprobamos primero si guardamos la ultima posicion en el localStorage
	if (localStorage && localStorage.getItem('cercaliaLastGeolocation')) {
		var coordinate = JSON.parse(localStorage.getItem('cercaliaLastGeolocation'));
		var lonLat = new cercalia.LonLat(coordinate[0], coordinate[1]);
		var bounds = new cercalia.Bounds();
		bounds.extend(lonLat);
		this.setCenter(lonLat, 13);
	} else {
		this.geolocation();
	}

	this.geolocation_.setTracking(true);

};

/**
 * Obtains the geocode at the API access point
 * It is possible to declare a callback function, to be executed when obtains the geocode.
 * When callback function is not avaliable, the map is centered to API access point.
 * @param {Function} callbackFn
 */
cercalia.Map.prototype.geolocation = function(callbackFn) {

	var self = this;

	this.geolocation_.once('change', function(evt) {
		if (callbackFn) {
			callbackFn(evt);
		} else {
			this.setTracking(false);

			var lonLatArr = this.getPosition()
			if (localStorage) {
				localStorage.setItem('cercaliaLastGeolocation', JSON.stringify(lonLatArr));
			}

			var lonLat = new cercalia.LonLat(lonLatArr[0], lonLatArr[1]);
			var bounds = new cercalia.Bounds();

			bounds.extend(lonLat);
			self.setCenter(lonLat, 13);
		}
	});

};


/**
 * Initializes events map
 * Events list: `[click, singleclick, dblclick, mousewheel, moveend, pointerup, pointerdown, pointermove, pointerdrag]`
 * @private
 */
cercalia.Map.prototype.initializeMapEvents_ = function() {
	var self = this;
	var listenLayersOnRightClick = [
		cercalia.LayerEnumeration.DragLayer,
		cercalia.LayerEnumeration.MarkerLayer,
		//cercalia.LayerEnumeration.MarkerApiLayer,
		cercalia.LayerEnumeration.FeatureLayer,
		cercalia.LayerEnumeration.ClusterLayerMarker,
		cercalia.LayerEnumeration.ClusterLayerMarkerDraggable
	];

	cercalia.jQuery(this.map_.getViewport()).bind('contextmenu', function(e) {
		e.preventDefault();
	});

	//Eventos botón derecho para las features
	cercalia.jQuery(this.map_.getViewport()).mousedown(function(e) {

		if (e.which == 3) { //boto dret
			e.preventDefault();

			var pixel = self.map_.getEventPixel(e.originalEvent);
			var rightClickFunctionsCalled = 0;

			self.map_.forEachFeatureAtPixel(
				pixel,
				function(feature, layer) {
					if (self.clusterLayer_ != null) {
						if (feature.get('features') && feature.get('features').length == 1) {
							feature = feature.get('features')[0];
						} else {
							feature.featureType_ = "cercalia.Cluster";
						}
					}

					switch (feature.featureType_) {
						case "cercalia.Marker":
							if (feature.marker_.onRightClick) {
								rightClickFunctionsCalled++;
								feature.marker_.onRightClick(feature.marker_, e.originalEvent); //Ejecutar evento 'onRightClick'
							}
							break;
						case "cercalia.Feature":
							var cercaliaFeature = feature.feature_;
							if (cercaliaFeature.onRightClick) {
								rightClickFunctionsCalled++;
								cercaliaFeature.onRightClick(cercaliaFeature, e.originalEvent);
							}
							break;
						default:
							break;
					}
				},
				undefined,
				function(layer) {
					return listenLayersOnRightClick.indexOf(layer.get('name')) !== -1; //Filtramos solo para los layers que nos interese
				}
			);

			if (self.contextMenuOverlay_ && rightClickFunctionsCalled <= 0) { // Si no se llaman funcines de marker, llamamos al rightclick del mapa.
				var coordinateOL = self.map_.getCoordinateFromPixel(pixel);
				self.contextMenuOverlay_.open(coordinateOL, self.projectionCode_);
				if (self.routing_) {
					self.routing_.hideContextMenu();
				}
			}
		}
	});

	var listenLayersOnMouseMove = [
		cercalia.LayerEnumeration.DragLayer,
		cercalia.LayerEnumeration.MarkerLayer,
		cercalia.LayerEnumeration.MarkerApiLayer,
		cercalia.LayerEnumeration.FeatureLayer,
		cercalia.LayerEnumeration.ClusterLayerMarker,
		cercalia.LayerEnumeration.ClusterLayerMarkerDraggable,
		cercalia.LayerEnumeration.OpenlrGeojsonLayer
	];

	var onMouseMove = function(e) {

		var pixel = self.map_.getEventPixel(e.originalEvent);
		var hit = self.map_.forEachFeatureAtPixel(pixel, function(feature, layer) {

				if (self.clusterLayer_ != null && self.clusterLayer_ == layer) {
					if (feature.get('features').length == 1) {
						feature = feature.get('features')[0];
					} else {
						feature.featureType_ = "cercalia.Cluster";
					}
				}

				if (feature.featureType_) {
					switch (feature.featureType_) {
						case "cercalia.Marker":
							if (self.markerOver_ && feature.marker_ != self.markerOver_) {
								if (self.markerOver_.onMouseOut) {
									self.markerOver_.onMouseOut(self.markerOver_); //Ejecutar evento 'onMouseOut'
								}
							}
							self.markerOver_ = feature.marker_;
							if (feature.marker_.onMouseOver) {
								feature.marker_.onMouseOver(self.markerOver_, e.originalEvent); //Ejecutar evento 'onMouseOver'
							}
							break;
						case "cercalia.Feature":
							if (self.markerOver_ && feature.feature_ != self.markerOver_) {
								if (self.markerOver_.onMouseOut) {
									self.markerOver_.onMouseOut(self.markerOver_); //Ejecutar evento 'onMouseOut'
								}
							}

							self.markerOver_ = feature.feature_
							if (self.markerOver_.onMouseOver) {
								self.markerOver_.onMouseOver(self.markerOver_, e.originalEvent);
							}
							break;
						case "cercalia.Cluster":
							if (layer.getProperties().onMouseOverFunction) {
								var arrayMarkers = [];
								for (var i = 0; i < feature.get('features').length; i++) {
									arrayMarkers.push(feature.get('features')[i].marker_);
								}
								layer.getProperties().onMouseOverFunction(feature, arrayMarkers);
								self.clusterFeatureOver_ = feature;
							}
							break;
						default:
							break;
					}
				}
				return true;
			},
			undefined,
			function(layer) {
				return listenLayersOnMouseMove.indexOf(layer.get('name')) !== -1 || self.arrayLayersAdded_.indexOf(layer) > -1; //Filtramos solo para los layers que nos interese
			}
		);

		if (hit) {
			document.getElementById(self.map_.getTarget()).style.cursor = 'pointer';
		} else {
			if (self.markerOver_) {
				if (self.markerOver_.onMouseOut) {
					self.markerOver_.onMouseOut(self.markerOver_); //Ejecutar evento 'onMouseOut'
				}
				self.markerOver_ = null;
			}

			if (self.clusterFeatureOver_) {
				if (self.clusterLayer_ && self.clusterLayer_.getProperties().onMouseOutFunction) {
					var arrayMarkers = [];
					var featuresClustered = self.clusterFeatureOver_.get('features');
					for (var i = 0; i < featuresClustered.length; i++) {
						arrayMarkers.push(featuresClustered[i].marker_);
					}
					self.clusterLayer_.getProperties().onMouseOutFunction(self.clusterFeatureOver_, arrayMarkers);
				}
				self.clusterFeatureOver_ = null;
			}

			document.getElementById(self.map_.getTarget()).style.cursor = '';
		}
	};

	var timeoutid = 0;

	if (!cercalia.isMobile && !this.disableMouseOverEvents_) {
		cercalia.jQuery(self.map_.getViewport()).on('mousemove', function(e) {
			if (timeoutid) {
				clearTimeout(timeoutid);
				timeoutid = 0;
			}
			timeoutid = setTimeout(function() {
				onMouseMove(e);
			}, 20);
		});
	}


	//Eventos al clickar una feature. Si se ha especificado el evento lo lanza
	//cercalia.jQuery(self.map_.getViewport()).on('click', function(e) {
	var listenLayersOnSingleClick = [
		cercalia.LayerEnumeration.DragLayer,
		cercalia.LayerEnumeration.MarkerLayer,
		cercalia.LayerEnumeration.MarkerApiLayer,
		cercalia.LayerEnumeration.ClusterLayerMarker,
		cercalia.LayerEnumeration.ClusterLayerMarkerDraggable,
		cercalia.LayerEnumeration.FeatureLayer,
		cercalia.LayerEnumeration.OpenlrGeojsonLayer
	];

	this.map_.on('singleclick', function(e) {

		if (self.contextMenuOverlay_ && self.contextMenuOverlay_.isVisible()) {
			self.contextMenuOverlay_.close();
		}
		if (self.routing_) {
			self.routing_.hideContextMenu();
		}

		var pixel = self.map_.getEventPixel(e.originalEvent);

		var coordinate = ol.proj.transform(e.coordinate, self.projectionCode_, 'EPSG:4326');
		var lonLat = new cercalia.LonLat(coordinate[0], coordinate[1]);

		//Si está activado el Layer de POIs hacemos petición AJAX para obtener información
		if (self.layerPoiWMS_ != null) {

			var params = {
				key: self.cercaliaKey_,
				cmd: 'proximity',
				x: lonLat.getLon(),
				y: lonLat.getLat(),
				srs: 'EPSG:4326',
				rqpoicats: self.poicatsEnabled_.join(","),
				rad: self.pixelsToMeters(12),
				rqge: 'adr'
			};

			cercalia.jQuery.ajax({
				url: self.server_,
				data: params,
				dataType: "json",
				crossDomain: true,
				timeout: 5000,
				success: function(data) {

					var poi = data.cercalia.proximity.poilist.poi[0];
					var marker = new cercalia.Marker({
						position: new cercalia.LonLat(poi.coord.x, poi.coord.y),
						popup: new cercalia.Popup({
							title: poi.name.value,
							content: '<div style="width:180px">' + poi.info.value + '</div>',
							visible: true
						})
					});
					marker.setVisible(false);
					self.addMarker(marker);

				},
				error: cercalia.AJAXException
			});
		}

		var eventLaunched = false;

		self.map_.forEachFeatureAtPixel(pixel, function(feature, layer) {

				if (!layer) {
					return;
				}

				var layerName = layer.get('name');

				if (self.clusterLayer_ != null && layerName != cercalia.LayerEnumeration.FeatureLayer) {
					// Capa clusterizada

					if (layerName == cercalia.LayerEnumeration.ClusterLayerMarker || layerName == cercalia.LayerEnumeration.ClusterLayerMarkerDraggable) {
						if (feature.get('features').length == 1) {
							feature = feature.get('features')[0];
						} else {
							feature.featureType_ = "cercalia.Cluster";
						}
					}
					// Else feature = feature;
				}


				if ([
						cercalia.LayerEnumeration.MarkerLayer,
						cercalia.LayerEnumeration.ClusterLayerMarker,
						cercalia.LayerEnumeration.ClusterLayerMarkerDraggable,
						cercalia.LayerEnumeration.FeatureLayer,
						cercalia.LayerEnumeration.DragLayer
					].indexOf(layerName) !== -1) {
					if (!eventLaunched) {
						var lonlat = new cercalia.LonLat(e.coordinate[0], e.coordinate[1], self.getProjectionCode());
						switch (feature.featureType_) {
							case "cercalia.Marker":
								var marker = feature.marker_;
								var popup = marker.getPopup();
								if (popup) {
									if (popup.isOpen()) {
										popup.hide();
									} else {
										popup.show();
									}
								}
								if (marker.onClick) {
									marker.onClick(feature.marker_, e.originalEvent, lonlat);
									eventLaunched = true;
								}
								break;
							case "cercalia.Feature":
								var cercaliaFeature = feature.feature_;
								if (cercaliaFeature.onClick) {
									cercaliaFeature.onClick(cercaliaFeature, e.originalEvent, lonlat);
									eventLaunched = true;
								}
								break;
							case "cercalia.Cluster":
								var arrayMarkers = [];
								for (var i = 0; i < feature.get('features').length; i++) {
									arrayMarkers.push(feature.get('features')[i].marker_);
								}
								eventLaunched = true;
								layer.getProperties().onClickFunction(feature, arrayMarkers, lonlat);
								break;
							default:
								break;
						}
					}

				} else if (layerName == cercalia.LayerEnumeration.OpenlrGeojsonLayer) {
					if (!this.controlTrafficAffectations_) {
						this.controlTrafficAffectations_ = self.getControlsByName(cercalia.MapControls.Traffic);
					}
					this.controlTrafficAffectations_.showAffectationPopup(feature);
				} else if (layerName == cercalia.LayerEnumeration.MarkerApiLayer) {
					var marker = feature.marker_;
					if (marker.onClick) {
						marker.onClick(feature.marker_, e.originalEvent);
						eventLaunched = true;
					}
				}
			},
			undefined,
			function(layer) {
				return listenLayersOnSingleClick.indexOf(layer.get('name')) !== -1; //Filtramos solo para los layers que nos interese
			}
		);
	});

	//Eventos al clickar una feature. Si se ha especificado el evento lo lanza
	//cercalia.jQuery(self.map_.getViewport()).on('click', function(e) {
	var listenLayersOnDoubleClick = [
		cercalia.LayerEnumeration.DragLayer,
		cercalia.LayerEnumeration.MarkerLayer,
		cercalia.LayerEnumeration.MarkerApiLayer,
		cercalia.LayerEnumeration.ClusterLayerMarker,
		cercalia.LayerEnumeration.ClusterLayerMarkerDraggable,
		cercalia.LayerEnumeration.FeatureLayer
	];

	this.map_.on('dblclick', function(e) {

		if (self.contextMenuOverlay_ && self.contextMenuOverlay_.isVisible()) {
			self.contextMenuOverlay_.close();
		}
		if (self.routing_) {
			self.routing_.hideContextMenu();
		}

		var pixel = self.map_.getEventPixel(e.originalEvent);

		var coordinate = ol.proj.transform(e.coordinate, self.projectionCode_, 'EPSG:4326');
		var lonLat = new cercalia.LonLat(coordinate[0], coordinate[1]);

		var eventLaunched = false;

		self.map_.forEachFeatureAtPixel(pixel, function(feature, layer) {

				if (!layer) {
					return;
				}

				var layerName = layer.get('name');

				if (self.clusterLayer_ != null && layerName != cercalia.LayerEnumeration.FeatureLayer) {
					// Capa clusterizada
					if (layerName == cercalia.LayerEnumeration.ClusterLayerMarker || layerName == cercalia.LayerEnumeration.ClusterLayerMarkerDraggable) {

						if (feature.get('features').length == 1) {
							feature = feature.get('features')[0];
						} else {
							feature.featureType_ = "cercalia.Cluster";
						}
					}
					// Else feature = feature;
				}

				if ([
						cercalia.LayerEnumeration.MarkerLayer,
						cercalia.LayerEnumeration.ClusterLayerMarker,
						cercalia.LayerEnumeration.ClusterLayerMarkerDraggable,
						cercalia.LayerEnumeration.FeatureLayer,
						cercalia.LayerEnumeration.DragLayer
					].indexOf(layerName) !== -1) {

					if (!eventLaunched) {

						switch (feature.featureType_) {
							case "cercalia.Marker":
								var marker = feature.marker_;
								if (marker.onDoubleClick) {
									marker.onDoubleClick(feature.marker_, e.originalEvent);
									eventLaunched = true;
								}
								break;
							case "cercalia.Feature":
								var cercaliaFeature = feature.feature_;
								if (cercaliaFeature.onDoubleClick) {
									cercaliaFeature.onDoubleClick(cercaliaFeature, e.originalEvent);
									eventLaunched = true;
								}
								break;
							case "cercalia.Cluster":
								var arrayMarkers = [];
								for (var i = 0; i < feature.get('features').length; i++) {
									arrayMarkers.push(feature.get('features')[i].marker_);
								}
								eventLaunched = true;
								layer.getProperties().onDoubleClickFunction(feature, arrayMarkers);
								break;
							default:
								break;
						}
					}


				} else if (layerName == cercalia.LayerEnumeration.MarkerApiLayer) {
					var marker = feature.marker_;
					if (marker && marker.onDoubleClick) {
						marker.onDoubleClick(feature.marker_, e.originalEvent);
						eventLaunched = true;
					}
				}
			},
			undefined,
			function(layer) {
				return listenLayersOnDoubleClick.indexOf(layer.get('name')) !== -1; //Filtramos solo para los layers que nos interese
			});

		return !eventLaunched;
	});


	//Obtener el logo del mapa según los tiles que se muestran
	this.map_.on('moveend', function(event) {
		self.onMoveEnd_();
		if (self.separateOverlapMarkersOnZoom_) { //Anadir condicion segun zoomOverlapChecking //check if overlap
			self.overlapMarkers()
		}
	});
};


/**
 * @private
 */
cercalia.Map.prototype.onMoveEnd_ = function() {

	// on zoom end i on rotation end
	if (this.onRotationEnd_ != null && this.map_.getView().getRotation() != this.currentRotation_) {
		var old = this.currentRotation_;
		this.currentRotation_ = this.map_.getView().getRotation();
		this.onRotationEnd_({
			'new': this.map_.getView().getRotation(),
			'old': old
		});
	}

	if (this.onZoomEnd_ != null && this.map_.getView().getZoom() != this.currentZoom_) {
		var old = this.currentZoom_;
		this.currentZoom_ = this.map_.getView().getZoom();
		this.onZoomEnd_({
			'new': this.map_.getView().getZoom(),
			'old': old
		});
	}

	var params = {
		key: this.cercaliaKey_,
		cmd: 'logo',
		center: ol.proj.transform(this.map_.getView().getCenter(), this.getProjectionCode(), 'EPSG:54004').join(","),
		scale: this.map_.getView().getZoom() - 6 >= 0 ? this.map_.getView().getZoom() - 6 : 0,
		style: this.getBaseLayer().getProperties().name
	};

	var self = this;

	cercalia.jQuery.ajax({
		url: self.server_,
		data: params,
		dataType: "json",
		crossDomain: true,
		timeout: 5000,
		success: function(data) {
			try {

				var serverLogo = self.server_.indexOf('https') == 1 ? 'http://api.cercalia.com' : 'https://api.cercalia.com';
				var baseLayerName = self.getBaseLayer().getProperties().name;

				if (baseLayerName == cercalia.MapTypes.CERCALIA) {
					//Si ess el mismo no lo cambiamos
					if (data.cercalia.logo && data.cercalia.logo.href && (self.logoImg_ != data.cercalia.logo.href)) {
						self.logoImg_ = data.cercalia.logo.href;
						self.logo_.empty().append(cercalia.jQuery('<img src="' + serverLogo + data.cercalia.logo.href + '">'));
					}
				} else if (baseLayerName == cercalia.MapTypes.OPENSTREETMAP || baseLayerName == cercalia.MapTypes.OSM) {
					var imgOSM = serverLogo + '/MapesNG/logo/logo_OSM.gif';
					if (self.logoImg_ != imgOSM) {
						self.logoImg_ = imgOSM;
						self.logo_.empty().append(cercalia.jQuery('<img src="' + self.logoImg_ + '">'));
					}
				} else if (baseLayerName == cercalia.MapTypes.SATELLITE || baseLayerName == cercalia.MapTypes.MAPQUEST || baseLayerName == cercalia.MapTypes.HYBRID) {
					var imgMapbox = serverLogo + '/MapesNG/logo/logo_mapbox.gif';
					if (self.logoImg_ != imgMapbox) {
						self.logoImg_ = imgMapbox;
						self.logo_.empty().append(cercalia.jQuery('<img src="' + self.logoImg_ + '">'));
					}
				} else {
					var logo = self.getBaseLayer().getProperties().logo;
					if (!logo) {
						self.logoImg_ = null;
						self.logo_.empty();
					} else {
						if (self.logoImg_ != logo) {
							self.logoImg_ = logo;
							self.logo_.empty().append(cercalia.jQuery('<img src="' + self.logoImg_ + '">'));
						}
					}
				}
			} catch (e) {}
		}
	});

};



/**
 * Get Overlapped markers
 * @param {boolean|undefined} currentExtentView If `true` returns only visible markers at the moment, if not `null` or `false` it returns all markers
 * @param {boolean|undefined} tolerance Tolerance in meters.
 * @return {Array.<Array.<cercalia.Marker>>}
 */
cercalia.Map.prototype.getOverlappedMarkers = function(currentExtentView, tolerance) {
	return this.getOverlapFeaturesByExtentAndDistance_(currentExtentView, tolerance);
};



/**
 * @private
 * @param {boolean|undefined} currentExtentView
 * @param {number|undefined} tolerance
 */
cercalia.Map.prototype.getOverlapFeaturesByExtentAndDistance_ = function(currentExtentView, tolerance) {

	var self = this;
	var sourceMarkerLayer = this.markerLayer_.getSource();
	var sourceDragLayer = this.dragLayer_.getSource();
	var features = [];
	var overlapGroupMarkers = [];

	if (currentExtentView) { //get only features in current extent view
		var extentAct = this.map_.getView().calculateExtent(this.map_.getSize());
		//Els dos layers poden tenir markers. Els separem entre layer draggable (dragLayer & markerLayer) segons si son draggable o no.
		features = sourceMarkerLayer.getFeaturesInExtent(extentAct).concat(sourceDragLayer.getFeaturesInExtent(extentAct));
	} else {
		//get allfeatures in map
		features = sourceMarkerLayer.getFeatures().concat(sourceDragLayer.getFeatures());
	}

	var clustered = {};
	var extent = ol.extent.createEmpty();

	for (var i = 0, ii = features.length; i < ii; i++) {
		var feature = features[i];
		//if (! goog.object.containsKey(clustered, goog.getUid(feature).toString())) {
		if (!(feature.getId() in clustered)) {
			var geometry = feature.getGeometry();
			var coordinates = geometry.getCoordinates();
			ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
			ol.extent.buffer(extent, tolerance, extent);

			var neighbors = sourceMarkerLayer.getFeaturesInExtent(extent).concat(sourceDragLayer.getFeaturesInExtent(extent));

			neighbors = neighbors.filter(function(neighbor) {
				if (neighbor.getCercaliaObject() instanceof cercalia.Marker) {
					var uid = neighbor.getId();
					if (!(uid in clustered)) {
						clustered[uid] = true;
						return true;
					} else {
						return false;
					}
				} else {
					return false;
				}
			});

			if (neighbors.length > 1) {
				overlapGroupMarkers.push(neighbors);
			}
		}
	}

	return overlapGroupMarkers;
};

/**
 * @private
 * @param {Array.<Array.<cercalia.Marker>>} overlapGroupMarkers
 * @param {boolean} restorePosition
 */
cercalia.Map.prototype.separateOverlapMarkers_ = function(overlapGroupMarkers, restorePosition) {
	for (var i = 0; i < overlapGroupMarkers.length; i++) {
		var features = overlapGroupMarkers[i];
		var separationAngle = 360 / features.length;
		var rotationAct = 0;

		for (var j = 0; j < features.length; j++) {
			var marker = features[j].getCercaliaObject();
			var icon = marker.getIcon();
			var size = icon.getIconStyle().getSize()
			icon.originalAnchor_ = icon.originalAnchor_ ? icon.originalAnchor_ : icon.getAnchor();
			icon.originalRotation_ = icon.originalRotation_ || icon.originalRotation_ == 0 ? icon.originalRotation_ : icon.getRotation();

			if (restorePosition) {
				icon.setRotation(icon.originalRotation_);
				icon.setAnchor(icon.originalAnchor_);
			} else {
				if (size) {
					if (icon.getAnchorUnits() == "fraction") {
						icon.setAnchor([0.5, 1.1]);
					} else {
						icon.setAnchor([icon.originalAnchor_[0], icon.originalAnchor_[1] + (size[1] - icon.originalAnchor_[1])]);
					}
				}
				rotationAct += separationAngle;
				icon.setRotation(rotationAct);
			}
		}
	}
};

/**
 * Separate overlapped markers when its have same position.
 */
cercalia.Map.prototype.overlapMarkers = function() {

	var self = this;
	var toleranceToOverlap = 5; //meters
	var overlapGroupMarkers = null;

	if (this.getZoom() >= this.separateOverlapMarkersOnZoom_) {
		overlapGroupMarkers = this.getOverlapFeaturesByExtentAndDistance_(true, toleranceToOverlap);
		this.separateOverlapMarkers_(overlapGroupMarkers, false);
		this.overlapMarkersSeparated_ = true;
	} else {
		if (this.overlapMarkersSeparated_) {
			overlapGroupMarkers = this.getOverlapFeaturesByExtentAndDistance_(true, toleranceToOverlap);
			this.separateOverlapMarkers_(overlapGroupMarkers, true);
			this.overlapMarkersSeparated_ = false;
		}
	}

	if (this.onOverlapMarkersEnd_ && overlapGroupMarkers) {
		this.onOverlapMarkersEnd_.call(this, overlapGroupMarkers, this.overlapMarkersSeparated_);
	}
};



/**
 * Returns an array with control objects already created. Send by parameter
 * an array with the Strings constants of controls declared in the file
 * "cercalia.control.js"
 * @private
 * @param {Array.<string>|string|undefined} opt_controls
 * @return {Array.<string>} controls
 */
cercalia.Map.prototype.initializeControls_ = function(opt_controls) {

	var listControlsToAdd = null;

	if (opt_controls) {

		if (opt_controls instanceof Array) {
			listControlsToAdd = opt_controls;
			// No hay zoom definido
			if (listControlsToAdd.indexOf(cercalia.MapControls.ZoomSlider) <= -1 && listControlsToAdd.indexOf(cercalia.MapControls.Zoom) <= -1) {
				listControlsToAdd.push(cercalia.MapControls.Zoom);
			}
		} else if (typeof(opt_controls) == "string" && opt_controls == "none") {
			listControlsToAdd = []; // opt_control == none
		} else {
			listControlsToAdd = [cercalia.MapControls.Zoom, cercalia.MapControls.FullScreen, cercalia.MapControls.ScaleLine]; // Controles que aparecerán al mapa por defecto
		}

	} else {
		listControlsToAdd = [cercalia.MapControls.Zoom, cercalia.MapControls.FullScreen, cercalia.MapControls.ScaleLine]; // Controles que aparecerán al mapa por defecto
	}

	return listControlsToAdd;
};

/**
 * Returns true if controls contains some included in toolsbar.
 * @private
 * @param {Array.<string>} allControls
 * @return {boolean}
 */
cercalia.Map.prototype.findBarControls_ = function(allControls) {
	return allControls.indexOf(cercalia.MapControls.CleanControl) >= 0 ||
		allControls.indexOf(cercalia.MapControls.DragControl) >= 0 ||
		allControls.indexOf(cercalia.MapControls.DrawControl) >= 0 ||
		allControls.indexOf(cercalia.MapControls.GasStations) >= 0 ||
		allControls.indexOf(cercalia.MapControls.Isochrones) >= 0 ||
		allControls.indexOf(cercalia.MapControls.LayerSwitcher) >= 0 ||
		allControls.indexOf(cercalia.MapControls.Logistics) >= 0 ||
		allControls.indexOf(cercalia.MapControls.MeasureControl) >= 0 ||
		allControls.indexOf(cercalia.MapControls.MeteoControl) >= 0 ||
		allControls.indexOf(cercalia.MapControls.Traffic) >= 0 ||
		allControls.indexOf(cercalia.MapControls.CustomSwitcherControl) >= 0;
};


/**
 * Returns an array with the map controls objects.
 * @private
 * @param {Array.<string>} allControls
 * @return {Array.<cercalia.Control>} controls
 */
cercalia.Map.prototype.initializeMapControls_ = function(allControls) {

	// Posicion top donde empezamos a colocar botones en el mapa
	var initTop = 5; // Posicion sin barra de controles
	if (this.findBarControls_(allControls)) {
		initTop = 65; // Posición con barra de controles.
	}

	var northTop = initTop,
		zoomTop = initTop,
		fullTop = initTop,
		controlZoomType = null;
	var controls = new ol.Collection();

	if (allControls.indexOf(cercalia.MapControls.RotateNorth) >= 0) {
		zoomTop += 45;
	}

	if (allControls.indexOf(cercalia.MapControls.ZoomSlider) != -1 && !cercalia.isMobile) {
		controlZoomType = cercalia.MapControls.ZoomSlider;
	} else {
		controlZoomType = cercalia.MapControls.Zoom;
	}

	for (var i = 0; i < allControls.length; i++) {

		switch (allControls[i]) {
			case cercalia.MapControls.RotateNorth:
				controls.push(new cercalia.Control.RotateNorth({
					controlZoomType: controlZoomType, //Le pasamos que tipo de zoom hay ya que variará su estilo
					top: northTop
				}));
				break;
			case cercalia.MapControls.FullScreen:
				//if (!cercalia.isMobile) {
				controls.push(new cercalia.Control.FullScreen({
					top: fullTop
				}));
				//}
				break;
			case cercalia.MapControls.ScaleLine:
				controls.push(new cercalia.Control.ScaleLine({
					units: this.scaleUnits_
				}));
				break;
			case cercalia.MapControls.ZoomSlider:
				controls.push(new cercalia.Control.ZoomSlider({
					top: zoomTop + 35
				}));
				break;
			case cercalia.MapControls.Zoom:
				controls.push(new cercalia.Control.Zoom({
					top: zoomTop
				}));
				break;
			case cercalia.MapControls.StreetView:
				if (!cercalia.isMobile) {
					controls.push(new cercalia.Control.StreetView({}));
				}
				break;
		}
	}

	return controls;
};

/**
 * Returns an array with the map controls objects.
 * @private
 * @param {Array.<string>} allControls
 * @return {Array.<string>} controls
 */
cercalia.Map.prototype.initializeBarControls_ = function(allControls) {

	var controls = [];

	if (this.findBarControls_(allControls)) {
		this.enabledAPIInterface_ = true;

		for (var i = 0; i < allControls.length; i++) {

			switch (allControls[i]) {
				case cercalia.MapControls.CleanControl:
					controls.push(allControls[i]);
					break;
				case cercalia.MapControls.DragControl:
					controls.push(allControls[i]);
					break;
				case cercalia.MapControls.DrawControl:
					controls.push(allControls[i]);
					break;
				case cercalia.MapControls.GasStations:
					controls.push(allControls[i]);
					break;
				case cercalia.MapControls.Isochrones:
					controls.push(allControls[i]);
					break;
				case cercalia.MapControls.LayerSwitcher:
					controls.push(allControls[i]);
					break;
				case cercalia.MapControls.Logistics:
					controls.push(allControls[i]);
					break;
				case cercalia.MapControls.MeteoControl:
					controls.push(allControls[i]);
					break;
				case cercalia.MapControls.MeasureControl:
					controls.push(allControls[i]);
					break;
				case cercalia.MapControls.Traffic:
					controls.push(allControls[i]);
					break;
			}
		}
	}

	return controls;
};



/**
 * Returns the map internal ID.
 * @return {string} map internal ID.
 */
cercalia.Map.prototype.getId = function() {
	return this.id_;
};

/**
 * Returns the container ID
 * @private
 * @return {string} id Map container Id
 */

cercalia.Map.prototype.getContainerId = function() {
	return this.containerId_;
};

/**
 * Returns the OpenLayers3 object <a href='http://openlayers.org/en/v3.6.0/apidoc/ol.Map.html'>`ol.Map`</a>
 * @return {ol.Map} Map
 */
cercalia.Map.prototype.getMap = function() {
	return this.map_;
};


/**
 * Returns the layer (<a href='http://openlayers.org/en/v3.6.0/apidoc/ol.layer.Tile.html'>ol.layer.Tile</a>) used as basemap
 * @return {ol.layer.Tile}
 */
cercalia.Map.prototype.getBaseLayer = function() {
	return this.getMap().getLayers().getArray()[0];
};


/**
 * Returns the drag interaction.
 * @return {ol.interaction.DragInteraction}
 */
cercalia.Map.prototype.getDragInteraction = function() {
	return this.dragInteraction_;
};

/**
 * Returns the draw interaction.
 * @return {ol.interaction.DrawInteraction}
 */
cercalia.Map.prototype.getDrawInteraction = function() {
  return this.drawInteraction_;
};

/**
 * Returns the map center.
 * @return {cercalia.LonLat} Coordinates.
 */
cercalia.Map.prototype.getCenter = function() {
	var coordsGeo = ol.proj.transform(this.map_.getView().getCenter(), this.projectionCode_, 'EPSG:4326');
	return new cercalia.LonLat(coordsGeo[0], coordsGeo[1]);
};

/**
 * Set the map center
 * @param {cercalia.LonLat} lonLat Coordinates
 * @param {number} zoom Zoom
 * @param {boolean|undefined} animation Animation, activated by default
 */
cercalia.Map.prototype.setCenter = function(lonLat, zoom, animation) {

	var currentResolution = this.map_.getView().getResolution();

	if (typeof(animation) == "undefined" || animation) {
		this.getMap().beforeRender(
			ol.animation.zoom({
				resolution: currentResolution,
				duration: 1000,
				easing: ol.easing.easeOut
			}),
			ol.animation.pan({
				duration: 1000,
				source: this.map_.getView().getCenter(),
				start: +new Date()
			})
		);
	}

	this.map_.getView().setCenter(ol.proj.transform([lonLat.getLon(), lonLat.getLat()], 'EPSG:4326', this.projectionCode_));
	if (zoom) {
		this.map_.getView().setZoom(zoom);
	}
};

/**
 * @return {number} Current zoom level of map.
 */
cercalia.Map.prototype.getZoom = function() {
	return this.map_.getView().getZoom();
};


/**
 * Set the zoom level of map. If second parameter is specified (boolean, optional), can apply animation effect
 * @param {number} zoom Zoom level to adjust
 * @param {boolean} animation True/False if want to apply animation effect. Defaulf: true
 */
cercalia.Map.prototype.setZoom = function(zoom, animation) {
	if (typeof(animation) == "undefined" || animation) {
		var zoomAnimation = ol.animation.zoom({
			resolution: this.map_.getView().getResolution()
		});
		this.map_.beforeRender(zoomAnimation);
	}
	this.map_.getView().setZoom(zoom);
};



/**
 * Returns the pixel value from a coordinate
 * @param {cercalia.LonLat} lonLat
 * @return {ol.Pixel}
 */
cercalia.Map.prototype.coordToPixel = function(lonLat) {
	var coordinate = ol.proj.transform([lonLat.getLon(), lonLat.getLat()], 'EPSG:4326', this.projectionCode_);
	return this.map_.getPixelFromCoordinate(coordinate);
};


/**
 * Returns the coordinate from a pixel value
 * @param {ol.Pixel} pixel
 * @return {cercalia.LonLat} Coordinate
 */
cercalia.Map.prototype.pixelToCoord = function(pixel) {
	var coordinate = this.map_.getCoordinateFromPixel(pixel);
	var coordGeo = ol.proj.transform(coordinate, this.projectionCode_, 'EPSG:4326');
	return new cercalia.LonLat(coordGeo[0], coordGeo[1]);
};


/**
 * Obtains pixel
 * @param {event} evt
 * @return {Pixel}
 */
cercalia.Map.prototype.getEventPixel = function(evt) {
	return this.map_.getEventPixel(evt.originalEvent);
};

/**
 * @param {event} evt
 * @return {cercalia.LonLat}
 */
cercalia.Map.prototype.getEventCoordinate = function(evt) {
	var coord = this.map_.getEventCoordinate(evt.originalEvent);
	var coordGeo = ol.proj.transform(coord, this.projectionCode_, 'EPSG:4326');
	return new cercalia.LonLat(coordGeo[0], coordGeo[1]);
};


/**
 * Calculate the real distance in meters, from pixels values.
 * @param {number} pixels
 * @return {number} meters
 */
cercalia.Map.prototype.pixelsToMeters = function(pixels) {
	var size = this.map_.getSize();
	var view = this.map_.getView();

	var extent = view.calculateExtent(size);

	var widthPixels = size[0];
	var widthMeters = extent[2] - extent[0];

	return (widthMeters / widthPixels) * pixels;
};



/**
 *
 * @param {string} layerName
 * @return {ol.layer} Layer objeto ol3
 */
cercalia.Map.prototype.getLayerByName = function(layerName) {
	var layers = this.map_.getLayers();
	var numLayers = layers.getLength();

	for (var i = 0; i < numLayers; i++) {
		if (layers.item(i).get('name') == layerName) {
			return layers.item(i);
		}
	}
	return null;
};


/**
 * Obtains the map control from its name. Complete controls list (if are created): <br/>
 * - `cercalia.MapControls.CleanControl`<br/>
 * - `cercalia.MapControls.DragControl`<br/>
 * - `cercalia.MapControls.DrawControl`<br/>
 * - `cercalia.MapControls.FullScreen`<br/>
 * - `cercalia.MapControls.LayerSwitcher`<br/>
 * - `cercalia.MapControls.Logistics`<br/>
 * - `cercalia.MapControls.MeasureControl`<br/>
 * - `cercalia.MapControls.MeteoControl`<br/>
 * - `cercalia.MapControls.RotateNorth`<br/>
 * - `cercalia.MapControls.ScaleLine`<br/>
 * - `cercalia.MapControls.StreetView`<br/>
 * - `cercalia.MapControls.Traffic`<br/>
 * - `cercalia.MapControls.Zoom`<br/>
 * - `cercalia.MapControls.ZoomSlider`
 *
 * @param {string} controlName
 * @return {ol.Control}
 */
cercalia.Map.prototype.getControlsByName = function(controlName) {
	var arrayControls = this.map_.getControls().getArray();
	for (var i = 0; i < arrayControls.length; i++) {
		if (arrayControls[i].getName && arrayControls[i].getName() == controlName) {
			return arrayControls[i];
		}
	}
	return null;
};



/**
 * Centers the map with animation (or not) to the markers list. Optionally it's possible to change the zoom for including all the markers.
 * If markers array is not specified, by default centers to all markers
 * @param {Array.<cercalia.Marker>|undefined} markers
 * @param {boolean} changeZoom Default: true
 * @param {boolean} animation Default: true
 */
cercalia.Map.prototype.centerToMarkers = function(markers, changeZoom, animation) {

	var bounds = new cercalia.Bounds();

	if (markers) {
		if (!(markers instanceof Array)) {
			markers = [markers];
		}

		for (var i = 0; i < markers.length; i++) {
			bounds.extend(markers[i].getPosition());
		}
	} else {
		//todos los markers (si no se pasa por parametro)
		var featuresOL = this.markerLayer_.getSource().getFeatures();
		var draggableFeaturesOl = this.dragLayer_.getSource().getFeatures();
		//Evitamos el marker draggable..
		for (var i = 0; i < draggableFeaturesOl.length; i++) {
			if (draggableFeaturesOl[i].getId().indexOf("MarkerRouteDrag") === -1) {
				featuresOL.push(draggableFeaturesOl[i]);
			}
		}

		for (var i = 0; i < featuresOL.length; i++) {
			if (featuresOL[i].marker_) {
				bounds.extend(featuresOL[i].marker_.getPosition());
			}
		}
	}

	if (bounds.getTop()) {
		if (typeof(changeZoom) == "undefined" || changeZoom) {
			if (bounds.getWidthInMeters() > 200 || bounds.getHeightInMeters() > 120) {
				this.fitBounds(bounds, typeof(animation) == "undefined" || animation);
			} else {
				this.setCenter(bounds.getCenterLonLat(), 19, false);
			}
		} else {
			this.panTo(bounds.getCenterLonLat(), typeof(animation) == "undefined" || animation);
		}
	}

};

/**
 * Center the map
 * @param {Array.<cercalia.Feature>|cercalia.Feature} features Features list or object.
 * @param {number} changeZoom Change zoom on features center. Default: true
 * @param {boolean} animation Animation. Default: true
 */
cercalia.Map.prototype.centerToFeatures = function(features, changeZoom, animation) {

	var bounds = new cercalia.Bounds();

	if (features) {
		if (!(features instanceof Array)) {
			features = [features];
		}

		for (var i = 0; i < features.length; i++) {
			bounds.extend(features[i].getBounds());
		}
	} else {
		//todos los markers (si no se pasa por parametro)
		var featuresOL = this.featureLayer_.getSource().getFeatures();

		for (var i = 0; i < featuresOL.length; i++) {
			if (featuresOL[i].feature_) {
				bounds.extend(featuresOL[i].feature_.getBounds());
			}
		}
	}

	if (bounds.getTop()) {
		if (typeof(changeZoom) == "undefined" || changeZoom) {
			this.fitBounds(bounds, typeof(animation) == "undefined" || animation);
		} else {
			this.panTo(bounds.getCenterLonLat(), animation);
		}
	}
};


/**
 * Returns the current bounds
 * @return {cercalia.Bounds} Current bounds, according to the zoom level, position and widht-height
 */
cercalia.Map.prototype.getBounds = function() {

	var extent = this.map_.getView().calculateExtent(this.map_.getSize());
	var extentGdd = ol.proj.transformExtent(extent, this.projectionCode_, 'EPSG:4326');

	return new cercalia.Bounds(extentGdd);
};

/**
 * Fit the bounds: change the current map bounds to new bounds.
 * By default applies animation effect
 * @param {cercalia.Bounds} bounds New bounds
 * @param {boolean} animation True/False, if want to apply animation or not. Default: true
 */
cercalia.Map.prototype.fitBounds = function(bounds, animation) {

	if (bounds instanceof cercalia.Bounds) {
		var currentResolution = this.map_.getView().getResolution();

		if (typeof(animation) == "undefined" || animation) {
			this.getMap().beforeRender(
				ol.animation.zoom({
					resolution: currentResolution,
					duration: 1000,
					easing: ol.easing.easeOut
				}),
				ol.animation.pan({
					duration: 1000,
					source: this.map_.getView().getCenter(),
					start: +new Date()
				})
			);
		}

		this.map_.getView().fitExtent(ol.proj.transformExtent(bounds.cloneIncreasingPercentage("15").toArray(), 'EPSG:4326', this.projectionCode_), this.map_.getSize());
	} else {
		cercalia.Exception(cercalia.i18n._('Type of parameter is not "%"', ['cercalia.Bounds']));
	}
};


/**
 * Map center maintaining the zoom level
 * @param {cercalia.Bounds} bounds Mapbounds.
 * @param {boolean} animation Boolean for apply or not animation effect. Values: `true` or `false`.
 * @param {number} duration Animation duration (in miliseconds). Default `2000`.
 */
cercalia.Map.prototype.panToBounds = function(bounds, animation, duration) {
	this.panTo(bounds.getCenterLonLat(), animation, duration);
};

/**
 * Moves the map to the new center. Optional: animation effect-
 * @param {cercalia.LonLat} lonLat Coordinate
 * @param {string} animation Boolean animation effect. Values `true` or `false`, specifying the type of animation:
 * 								`cercalia.MapAnimation.ROTATE_LEFT`,
 * 								`cercalia.MapAnimation.ROTATE_RIGHT`,
 * 								`cercalia.MapAnimation.PAN`,
 * 								`cercalia.MapAnimation.ELASTIC`,
 * 								`cercalia.MapAnimation.BOUNCE`,
 * 								`cercalia.MapAnimation.FLY`,
 * 								`cercalia.MapAnimation.SPIRAL`
 * @param {number} duration Animation duration (in miliseconds). Defaulf `2000`.
 */
cercalia.Map.prototype.panTo = function(lonLat, animation, duration) {

	var olAnimation = null;

	var durationAnim = duration ? duration : 2000;

	if (animation || typeof(animation) == "undefined") {
		switch (animation) {
			case cercalia.MapAnimation.ROTATE_LEFT:
				var start = +new Date();
				var olAnimation = ol.animation.pan({
					duration: durationAnim,
					source: this.map_.getView().getCenter(),
					start: start
				});
				var olAnimation2 = ol.animation.rotate({
					duration: durationAnim,
					rotation: -2 * Math.PI,
					start: start
				});

				this.map_.beforeRender(olAnimation, olAnimation2);
				break;
			case cercalia.MapAnimation.ROTATE_RIGHT:

				var start = +new Date();
				var olAnimation = ol.animation.pan({
					duration: durationAnim,
					source: this.map_.getView().getCenter(),
					start: start
				});
				var olAnimation2 = ol.animation.rotate({
					duration: durationAnim,
					rotation: 2 * Math.PI,
					start: start
				});

				this.map_.beforeRender(olAnimation, olAnimation2);
				break;
			case cercalia.MapAnimation.PAN:

				olAnimation = ol.animation.pan({
					duration: durationAnim,
					source: this.map_.getView().getCenter()
				});
				this.map_.beforeRender(olAnimation);
				break;
			case cercalia.MapAnimation.ELASTIC:

				olAnimation = ol.animation.rotate({
					duration: durationAnim,
					easing: ol.easing.elastic,
					source: this.map_.getView().getCenter()
				});
				this.map_.beforeRender(olAnimation);
				break;
			case cercalia.MapAnimation.BOUNCE:

				olAnimation = ol.animation.rotate({
					duration: durationAnim,
					easing: ol.easing.bounce,
					rotation: -4 * Math.PI
				});
				this.map_.beforeRender(olAnimation);
				break;
			case cercalia.MapAnimation.FLY:

				var start = +new Date();
				olAnimation = ol.animation.pan({
					duration: durationAnim,
					source: this.map_.getView().getCenter(),
					start: start
				});
				var olAnimation2 = ol.animation.bounce({
					duration: durationAnim,
					resolution: 4 * this.map_.getView().getResolution(),
					start: start
				});
				this.map_.beforeRender(olAnimation, olAnimation2);
				break;
			case cercalia.MapAnimation.SPIRAL:

				var start = +new Date();
				olAnimation = ol.animation.pan({
					duration: durationAnim,
					source: this.map_.getView().getCenter(),
					start: start
				});
				var olAnimation2 = ol.animation.bounce({
					duration: durationAnim,
					resolution: 2 * this.map_.getView().getResolution(),
					start: start
				});
				var olAnimation3 = ol.animation.rotate({
					duration: durationAnim,
					rotation: -4 * Math.PI,
					start: start
				});
				this.map_.beforeRender(olAnimation, olAnimation2, olAnimation3);
				break;
			default:
				olAnimation = ol.animation.pan({
					duration: durationAnim,
					source: this.map_.getView().getCenter()
				});
				this.map_.beforeRender(olAnimation);
				break;
		}
	}

	this.map_.getView().setCenter(ol.proj.transform([lonLat.getLon(), lonLat.getLat()], 'EPSG:4326', this.projectionCode_));
};


/**
 * Sets the map rotation
 * @param {number} angle
 */
cercalia.Map.prototype.setRotation = function(angle) {
	this.map_.getView().setRotation(Math.PI * angle / 180);
};

/**
 * Rotates the map <angle> degrees, by parameter
 * @param {number} angle Angle (degrees).
 */
cercalia.Map.prototype.rotate = function(angle) {
	var rotAct = this.map_.getView().getRotation();
	this.map_.getView().setRotation(rotAct + (Math.PI * angle / 180));
};

/**
 * Obtains the map projection code
 * @return {string} EPSG code. For example: `EPSG:3857`.
 */
cercalia.Map.prototype.getProjectionCode = function() {
	return this.map_.getView().getProjection().getCode();
};

/**
 * Fixes the minimum and maximum zoom level
 * @param {number|boolean} minZoom Min zoom, false if don't want this limit
 * @param {number|boolean} maxZoom Max zoom, false if don't want this limit
 * @param {cercalia.Bounds} bounds Map bounds limits
 */
cercalia.Map.prototype.setRestrictedView = function(minZoom, maxZoom, bounds) {
	if (minZoom) this.viewOptions_.minZoom = minZoom;
	if (maxZoom) this.viewOptions_.maxZoom = maxZoom;
	if (bounds) this.viewOptions_.extent = ol.proj.transformExtent(bounds.toArray(), 'EPSG:4326', this.projectionCode_);

	this.viewOptions_.resolution = this.map_.getView().getResolution();
	this.viewOptions_.center = this.map_.getView().getCenter();

	var view = new ol.View(this.viewOptions_);
	this.map_.setView(view);
};

/**
 * Changes the projection to basemap and all the layers painted over it (markers, features, WMS, etc).
 * @param {ol.Projection} projection Projection.
 */
cercalia.Map.prototype.setProjection = function(projection) {

	if (projection != this.mapProjection_) {
		var self = this;
		var center = this.getCenter();

		this.viewOptions_.projection = projection;
		this.viewOptions_.resolution = this.map_.getView().getResolution();
		this.viewOptions_.center = this.map_.getView().getCenter();

		var view = new ol.View(this.viewOptions_);
		var projectionAnt = this.getProjectionCode();

		this.mapProjection_ = projection;
		this.projectionCode_ = projection.getCode();
		this.map_.setView(view);

		//this.map_.getView().setResolution(map.getMap().getView().constrainResolution(map.getMap().getView().getResolution()));
		//this.map_.getView().setResolution(view.constrainResolution(view.getResolution()));
		//this.setCenter(center,false,false);

		var newExtent = ol.proj.transformExtent(this.getBounds().toArray(), 'EPSG:4326', this.projectionCode_);
		this.map_.getView().fitExtent(newExtent, this.map_.getSize());

		//Cambiar la proyeccion de todos los features de todos los layers
		var allLayers = this.map_.getLayers();

		//Desactivamos durante la reproyeccion si el clusternig está activado
		var clusteringEnabled = this.clusterLayer_ != null;
		if (clusteringEnabled) {
			this.disableClustering();
		}

		allLayers.forEach(function(layerAct, index) {
			if (layerAct instanceof ol.layer.Vector) {
				var source = layerAct.getSource();

				//Solució indefinida pels markers (si no borrem i treiem els markers, després falla tot el tema del dragging)
				if (layerAct == self.markerLayer_ || layerAct == self.dragLayer_) {
					var arrMarkers = [];
					source.getFeatures().forEach(function(featureAct, index) {
						featureAct.getGeometry().transform(projectionAnt, projection.getCode());
						if (featureAct.getCercaliaObject) {
							var markerAct = featureAct.getCercaliaObject();
							markerAct.refreshPositionFromGeometry(); //pels labels&simple labels
							arrMarkers.push(markerAct);
						}
					});
					if (arrMarkers.length > 0) {
						self.removeMarkers(arrMarkers);
						self.addMarkers(arrMarkers);
					}
				} else {
					source.getFeatures().forEach(function(featureAct, index) {
						featureAct.getGeometry().transform(projectionAnt, projection.getCode());
					});
				}

			}
		});

		//Activamos después de la reproyeccion si el clusternig está activado
		if (clusteringEnabled) {
			this.enableClustering();
		}

		var allOverlays = this.map_.getOverlays().getArray();

		//allOverlays.forEach(function(overlay, index) {
		//	if (overlay.getPosition()) {
		//		overlay.setPosition(ol.proj.transform(overlay.getPosition(),projectionAnt, projection.getCode()));
		//	}
		//});
	}

};



/**
 * Adds a WMS layer {@link cercalia.WMS} to the map
 * @param {cercalia.WMS} wmsLayer
 * @param {number} position WMSLayer position. If not declared, it's included just one level over the basemap.
 */
cercalia.Map.prototype.addWMSLayer = function(wmsLayer, position) {
	wmsLayer.setMap(this, position ? position : 1);
};


/**
 * Remove a WMS layer {@link cercalia.WMS} from the map
 * @param {cercalia.WMS} wmsLayer
 */
cercalia.Map.prototype.removeWMSLayer = function(wmsLayer) {
	wmsLayer.setMap(null);
};


/**
 * Returns the layer (<a href='http://openlayers.org/en/v3.6.0/apidoc/ol.layer.Vector.html'>`ol.layer.Vector`</a>) used
 * for painting on the map.
 * @return {ol.layer.Vector} layer
 */
cercalia.Map.prototype.getDrawLayer = function() {
	return this.drawLayer_;
};


/**
 * Obtains the layer position (Z value). The layers are painted respecting Z position: 0 is the base map.
 * @param {string} layerName
 * @return {number} Layer position
 */
cercalia.Map.prototype.getLayerIndexByName = function(layerName) {
	var layers = this.map_.getLayers();
	var numLayers = layers.getLength();

	for (var i = 0; i < numLayers; i++) {
		if (layers.item(i).get('name') == layerName) {
			return i;
		}
	}
	return null;
};


/**
 * @private
 * @param {Array.<cercalia.Marker>|Array.<cercalia.Linestring>|Array.<cercalia.Feature>} arrayElements
 * @param {boolean} checkIfMouseOver
 * @return {Array.<ol.Feature>}
 */
cercalia.Map.prototype.arrayElementsToFeatures_ = function(arrayElements, layerToAdd) {

	var arrayFeatures = [];
	var arrayFeaturesDraggables = [];
	var numElements = arrayElements.length;
	for (var i = 0; i < numElements; i++) {

		var featureOL = arrayElements[i].getFeature();
		arrayFeatures.push(featureOL);

		arrayElements[i].setMap(this);
		if (arrayElements[i].isDraggable()) {
			arrayFeaturesDraggables.push(featureOL);
		}
	}

	this.putMarkerToLayerNoDrag_(layerToAdd, arrayFeatures, this.dragLayer_);
	if (arrayFeaturesDraggables.length > 0) {
		this.addMarkerToDragLayer_(arrayFeaturesDraggables, layerToAdd);
	}

	return arrayFeatures;
};


/**
 * Paint {@link cercalia.Marker} list on the map.
 * @param {cercalia.Marker} marker
 */
cercalia.Map.prototype.addMarker = function(marker) {
	this.putMarkerToLayerNoDrag_(this.markerLayer_, marker.getFeature(), this.dragLayer_);
	if (marker.isDraggable()) {
		this.addMarkerToDragLayer_(marker, this.markerLayer_);
	} else {
		this.removeMarkerFromDragLayer_(marker);
	}
	marker.setMap(this);
};



/**
 * Paint on the map the {@link cercalia.Marker} list. In this function must send an `Array`.
 * @param {Array.<cercalia.Marker>} markers
 */
cercalia.Map.prototype.addMarkers = function(markers) {
	var arrayFeatures = this.arrayElementsToFeatures_(markers, this.markerLayer_);

};

/**
 * @private
 * @param {Array.<cercalia.Marker>} markers
 */
cercalia.Map.prototype.addApiMarkers = function(markers) {
	var arrayFeatures = this.arrayElementsToFeatures_(markers, this.markerApiLayer_);
};

/**
 * Paint {@link cercalia.Linestring} on the map
 * @param {cercalia.Linestring} linestring
 */
cercalia.Map.prototype.addLinestring = function(linestring) {
	this.putMarkerToLayerNoDrag_(this.routeLayer_, linestring.getFeature(), this.dragLayer_);
	if (linestring.isDraggable()) {
		this.addMarkerToDragLayer_(linestring, this.routeLayer_);
	}
	linestring.setMap(this);
};

/**
 * Paint on the map the {@link cercalia.Linestring} list. In this function must send an `Array`.
 * @param {Array.<cercalia.Linestring>} linestrings
 */
cercalia.Map.prototype.addLinestrings = function(linestrings) {
	var arrayFeatures = this.arrayElementsToFeatures_(linestrings, this.routeLayer_);
};

/**
 * Paint {@link cercalia.Feature} on the map.
 * @param {cercalia.Feature} feature
 * @param {ol.Layer|undefined} layer
 */
cercalia.Map.prototype.addFeature = function(feature, layer) {

	layer = layer ? layer : this.featureLayer_;

	//this.featureLayer_.getSource().addFeature(feature.getFeature());
	this.putMarkerToLayerNoDrag_(layer, feature, this.dragLayer_);

	if (feature.isDraggable()) {
		//		try {
		this.addMarkerToDragLayer_(feature, layer);
		//		}catch(e) {
		//			//Si no da tiempo a inicializarse del todo da un error aunque el push ha finalizado correctamente. Parece que no afecta a nada.
		//		}
	} else {
		this.removeMarkerFromDragLayer_(feature);
	}

	feature.setMap(this);

	if (feature.isEditable()) {
		//this.editableFeaturesCollection_.push(feature.getFeature());
	}

};

/**
 * Set feature editable.
 * @param {cercalia.Feature} feature Feature to be edited.
 * @param {boolean} edit Set the feature as editable or not.
 */
cercalia.Map.prototype.setFeatureEditable = function(feature, edit) {

	// mirem si la interaction està activa i la desactivem
	var isActive = this.isModifyControlActive_();
	this.enableFeatureEditable(false);

	// afegim o treiem feature
	if (edit) {
		this.editableFeaturesCollection_.push(feature.getFeature());
	} else {
		this.editableFeaturesCollection_.remove(feature.getFeature());
	}

	// activem la interacció si ho estava
	this.enableFeatureEditable(true);

};

/**
 * Set the features as editables if it isn't, or change to not editable if it's editable.
 * @param {boolean} enable Set on/off the editable mode.
 */
cercalia.Map.prototype.enableFeatureEditable = function(enable) {
	if (enable && !this.isModifyControlActive_()) {
		this.map_.addInteraction(this.modifyControl_);
	} else if (!enable && this.isModifyControlActive_()) {
		this.map_.removeInteraction(this.modifyControl_);
	}
};

/**
 * Returns interaction status
 * @private
 * @returns {boolean}
 */
cercalia.Map.prototype.isModifyControlActive_ = function() {
	return this.map_.getInteractions().getArray().indexOf(this.modifyControl_) != -1;
};

/**
 * Paint several {@link cercalia.Feature} on the map. Must send an `Array`.
 * @param {Array.<cercalia.Feature>} features Array de {@link cercalia.Feature}
 * @param {ol.layer} layer
 */
cercalia.Map.prototype.addFeatures = function(features, layer) {
	var arrayFeatures = this.arrayElementsToFeatures_(features, layer ? layer : this.featureLayer_);
};

/**
 * Obtain all the {@link cercalia.Marker} painted on the map
 * @return {Array.<cercalia.Marker>|null} Marker list painted on the map
 */
cercalia.Map.prototype.getMarkers = function() {

	var arrayMarkers = this.getFeaturesByLayer_(this.markerLayer_);
	var featuresDraggables = this.getFeaturesByLayer_(this.dragLayer_); //quan son draggables estan en un altre layer.

	for (var i = featuresDraggables.length - 1; i >= 0; i--) { //Evitem retornar el marker draggable de ruta que tambe esta al layer.
		if (!(featuresDraggables[i] instanceof cercalia.Marker) || (featuresDraggables[i].getId().indexOf("MarkerRouteDrag") !== -1)) {
			featuresDraggables.splice(i, 1);
		}
	}

	return arrayMarkers.concat(featuresDraggables);
};

/**
 * @return {cercalia.Marker} marker
 */
cercalia.Map.prototype.getMarkerById = function(id) {
	var feature = this.markerLayer_.getSource().getFeatureById(id);
	if (!feature) {
		feature = this.dragLayer_.getSource().getFeatureById(id);
	}

	var marker = feature ? feature.getCercaliaObject() : null;
	return marker;
};

/**
 * Obtain all the {@link cercalia.Linestring} painted on the map
 * @return {Array.<cercalia.Linestring>|null} Linestrings list painted on the map
 */
cercalia.Map.prototype.getLinestrings = function() {
	return this.getFeaturesByLayer_(this.routeLayer_);
};

/**
 * Obtain all the {@link cercalia.Feature} painted on the map
 * @return {Array.<cercalia.Linestring>|null} Linestring list painted on the map
 */
cercalia.Map.prototype.getFeatures = function() {
	var features = this.getFeaturesByLayer_(this.featureLayer_);
	var dragFeatures = this.getFeaturesByLayer_(this.dragLayer_, 'cercalia.Feature'); //dragLayer contiene cercalia.Feature, cercalia.Marker..
	return features.concat(dragFeatures);
};

/**
 * @private
 * @param {ol.layer} layer Layer
 * @param {opt_className} opt_type Optional filter by className. For example `cercalia.Marker` or `cercalia.Feature`
 * @return {Array.<cercalia.Marker>|Array.<cercalia.Linestring>|Array.<cercalia.Feature>}
 */
cercalia.Map.prototype.getFeaturesByLayer_ = function(layer, opt_type) {

	var arrayFeatures = layer.getSource().getFeatures();
	var features = [];
	for (var i = 0; i < arrayFeatures.length; i++) {
		var featureAct = arrayFeatures[i].getCercaliaObject();
		if (opt_type) {
			if (featureAct.getClass() === opt_type) {
				features.push(featureAct);
			}
		} else {
			//All. No filtering
			features.push(featureAct);
		}
	}

	return features;
};

/**
 * Show the markers painted on the map. This function is usefull when some or all markers are hidden.
 * @param {cercalia.Marker|Array.<cercalia.Marker>} markers
 * @param {boolean} show
 */
cercalia.Map.prototype.showMarkers = function(markers, show) {
	if (markers instanceof Array) {
		for (var i = 0; i < markers.length; i++) {
			markers[i].setVisible(show);
		}
	} else {
		markers.setVisible(show);
	}

	//this.refreshClustering_();
};

/**
 * Remove markers list from map
 * @param {cercalia.Marker|Array.cercalia.Marker} markers
 */
cercalia.Map.prototype.removeMarkers = function(markers) {
	if (markers != null) {
		this.removeFeaturesByLayer_(markers, this.markerLayer_);
	}
};

/**
 * Remove API markers list from map
 * @param {cercalia.Marker|Array.<cercalia.Marker>} markers
 */
cercalia.Map.prototype.removeApiMarkers = function(markers) {
	if (markers != null) {
		this.removeFeaturesByLayer_(markers, this.markerApiLayer_);
	}
};

/**
 * Remove linestring list from map
 * @param {cercalia.Linestring|Array.<cercalia.Linestring>} linestrings
 */
cercalia.Map.prototype.removeLinestrings = function(linestrings) {
	this.removeFeaturesByLayer_(linestrings, this.routeLayer_);
};


/**
 * Remove features list from map
 * @param {cercalia.Feature|Array.<cercalia.Feature>} features
 * @param {ol.Layer|undefined} layer If not defined it will remove features in internal Cercalia layer.
 */
cercalia.Map.prototype.removeFeatures = function(features, layer) {
	this.removeFeaturesByLayer_(features, layer ? layer : this.featureLayer_);
};

/**
 * @private
 * @param {cercalia.Marker|Array.<cercalia.Marker>|cercalia.Linestring|Array.<cercalia.Linestring>|cercalia.Feature|Array.<cercalia.Feature>} features
 * @param {ol.layer} layer
 */
cercalia.Map.prototype.removeFeaturesByLayer_ = function(features, layer) {

	if (features instanceof Array) {
		for (var i = features.length - 1; i >= 0; i--) {
			this.removeFeatureByLayer_(features[i], layer);
		}
	} else {
		this.removeFeatureByLayer_(features, layer);
	}
};

/**
 *
 * @private
 * @param {cercalia.Marker|cercalia.Linestring|cercalia.Feature} feature
 * @param {ol.layer} layer
 */
cercalia.Map.prototype.removeFeatureByLayer_ = function(feature, layer) {

	var featureOL, featureCercalia;
	if (feature instanceof ol.Feature) {
		featureOL = feature;
		featureCercalia = feature.getCercaliaObject();
	} else {
		featureOL = feature.getFeature();
		featureCercalia = feature;
	}

	if (featureOL) {
		this.removeMarkerToLayerNoDrag_(layer, featureOL);
	}

	if (featureCercalia) {
		featureCercalia.setMap(null);
		if (featureCercalia.isDraggable()) {
			this.removeMarkerFromDragLayer_(featureOL);
			//this.dragFeatures_.remove(featureOL);
		}
	}

};



/**
 * Clean all markers and popups
 */
cercalia.Map.prototype.removeAllMarkers = function() {
	var arrayFeatures = this.markerLayer_.getSource().getFeatures();
	this.removeFeaturesByLayer_(arrayFeatures, this.markerLayer_);
};

/**
 * Clean all linestrings
 */
cercalia.Map.prototype.removeAllLinestrings = function() {
	var arrayFeatures = this.routeLayer_.getSource().getFeatures();
	this.removeFeaturesByLayer_(arrayFeatures, this.routeLayer_);
};

/**
 * Clean all features
 */
cercalia.Map.prototype.removeAllFeatures = function() {
	var arrayFeatures = this.featureLayer_.getSource().getFeatures();
	this.removeFeaturesByLayer_(arrayFeatures, this.featureLayer_);
};

/**
 * Clean all the features painted on the map with draw tool.
 * Layer 'DrawLayer'
 */
cercalia.Map.prototype.removeDrawFeatures = function() {
	var source = this.getDrawLayer().getSource();
	var arrayFeatures = source.getFeatures();

	for (var i = arrayFeatures.length - 1; i >= 0; i--) {
		source.removeFeature(arrayFeatures[i]);
		arrayFeatures[i] = null;
	}
	arrayFeatures = null;
};

/**
 * Clean all overlays painted on the map
 */
cercalia.Map.prototype.removeAllOverlays = function() {
	this.removeAllMarkers();
	this.removeAllLinestrings();
	this.removeAllFeatures();
	this.removeDrawFeatures();
	if (this.routing_) {
		this.routing_.removeRoute();
	}
};

/**
 * Hide all labels {@link cercalia.Label} from markers painted on the map
 */
cercalia.Map.prototype.hideLabels = function() {

	var markers = this.getMarkers();
	for (var i = 0; i < markers.length; i++) {
		markers[i].hideLabel();
	}

	this.visibleLabels_ = false;
};

/**
 * Show all labels {@link cercalia.Label} from markers painted on the map
 */
cercalia.Map.prototype.showLabels = function() {
	//	var arrayFeatures = this.markerLayer_.getSource().getFeatures();
	//	for (var i = 0; i < arrayFeatures.length; i++) {
	//		arrayFeatures[i].marker_.showLabel();
	//	}

	var markers = this.getMarkers();
	for (var i = 0; i < markers.length; i++) {
		markers[i].showLabel();
	}

	this.visibleLabels_ = true;
};

/**
 * Returns boolean showing if all labels are showing or hidden.
 * @returns {Boolean}
 */
cercalia.Map.prototype.showingLabels = function() {
	return this.visibleLabels_;
};

/**
 * Close all popups opened
 */
cercalia.Map.prototype.closePopups = function() {
	var arrayFeatures = this.markerLayer_.getSource().getFeatures();
	for (var i = 0; i < arrayFeatures.length; i++) {
		var popup = arrayFeatures[i].marker_.getPopup();
		if (popup) {
			popup.hide();
		}
	}
};

/**
 * Show all markers popups. Only for markers with popup created
 */
cercalia.Map.prototype.openPopups = function() {
	var arrayFeatures = this.markerLayer_.getSource().getFeatures();
	for (var i = 0; i < arrayFeatures.length; i++) {
		arrayFeatures[i].marker_.getPopup().show();
	}
};

/**
 * @param {string|cercalia.WMS|ol.layer.Layer|ol.layer.Group} mapType Change base map. <br/>
 * Maps avaliable by default. String: <br/>
 *  - `cercalia.MapTypes.CERCALIA` Cercalia<br/>
 *  - `cercalia.MapTypes.OSM` Cercalia<br/>
 *  - `cercalia.MapTypes.SATELLITE` <br/>
 *  - `cercalia.MapTypes.HYBRID` <br/>
 *  <br/>
 *  Also can use a WMS layer (object cercalia.WMS), that will be set as base Layer.
 *  Also can add layers using OpenLayers classes
 *  <a href="http://openlayers.org/en/v3.6.0/apidoc/ol.layer.Layer.html">`ol.layer.Layer`</a> y
 *  <a href="http://openlayers.org/en/v3.6.0/apidoc/ol.layer.Group.html">`ol.layer.Group`</a>.
 */
cercalia.Map.prototype.setBaseLayer = function(mapType) {

	//Borrem el base Layer
	this.removeLayer(this.getBaseLayer());

	if (typeof(mapType) == "string") {
		var layer = this.createBaseLayer_(mapType);
		this.addLayer(layer, 0);
	} else if (mapType instanceof cercalia.WMS) {
		this.addLayer(mapType.getLayer(), 0);
	} else if (mapType instanceof ol.layer.Layer) {
		this.addLayer(mapType, 0);
	} else if (mapType instanceof ol.layer.Group) {
		this.addLayer(mapType, 0);
	} else {
		alert('Unknown setBaseLayer: ' + mapType);
	}

	this.onMoveEnd_();
};

/**
 * @private
 * @param {string} baseLayer
 * @return {ol.layer.Tile}
 */
cercalia.Map.prototype.createBaseLayer_ = function(baseLayer) {

	if (baseLayer == cercalia.MapTypes.CERCALIA) {

		var baseLayerSource = new ol.source.XYZ({
			urls: servers.backgroundXYZ,
			crossOrigin: 'anonymous'
		});

		return new ol.layer.Tile({
			source: baseLayerSource,
			name: cercalia.MapTypes.CERCALIA,
			baseLayer: true
		});

	} else if (baseLayer == cercalia.MapTypes.MAPQUEST || baseLayer == cercalia.MapTypes.OSM) {

		var baseLayerSource = new ol.source.XYZ({
			attributions: [new ol.Attribution({
				html: "Tiles &copy; <a href='http://mapbox.com/'>MapBox</a>"
			})],
			urls: servers.osm,
			crossOrigin: 'anonymous'
		});

		return new ol.layer.Tile({
			source: baseLayerSource,
			name: cercalia.MapTypes.OSM,
			baseLayer: true
		});

	} else if (baseLayer == cercalia.MapTypes.HYBRID) {

		var baseLayerSource = new ol.source.XYZ({
			attributions: [new ol.Attribution({
				html: "Tiles &copy; <a href='http://mapbox.com/'>MapBox</a>"
			})],
			urls: servers.hybrid,
			crossOrigin: 'anonymous'
		});

		return new ol.layer.Tile({
			source: baseLayerSource,
			name: cercalia.MapTypes.HYBRID,
			baseLayer: true
		});

	} else if (baseLayer == cercalia.MapTypes.OPENSTREETMAP) {
		return new ol.layer.Tile({
			source: new ol.source.OSM(),
			name: cercalia.MapTypes.OPENSTREETMAP,
			baseLayer: true
		});
	} else if (baseLayer == cercalia.MapTypes.SATELLITE) {

		var baseLayerSource = new ol.source.XYZ({
			attributions: [new ol.Attribution({
				html: "Tiles &copy; <a href='http://mapbox.com/'>MapBox</a>"
			})],
			crossOrigin: 'anonymous',
			urls: [
				"http://a.tiles.mapbox.com/v3/nexusgeografics.map-w0hjuuwg/{z}/{x}/{y}.png",
				"http://b.tiles.mapbox.com/v3/nexusgeografics.map-w0hjuuwg/{z}/{x}/{y}.png",
				"http://c.tiles.mapbox.com/v3/nexusgeografics.map-w0hjuuwg/{z}/{x}/{y}.png",
				"http://d.tiles.mapbox.com/v3/nexusgeografics.map-w0hjuuwg/{z}/{x}/{y}.png"
			]
		});

		return new ol.layer.Tile({
			source: baseLayerSource,
			name: cercalia.MapTypes.SATELLITE,
			baseLayer: true
		});
	}
};

/**
 * Create topbar
 * @return {Element}
 */
cercalia.Map.prototype.createTopBar = function() {
	if (!this.topBarElement_) {
		var topBarId = "topBar_" + this.id_;
		this.topBarElement_ = document.createElement('div');
		this.topBarElement_.setAttribute('id', topBarId);
		this.topBarElement_.className = 'cercalia-bar-top';

		var container = document.getElementById(this.containerId_);
		container.insertBefore(this.topBarElement_, container.firstChild);
		return container;
	}
};

/**
 * Function Function for API interface initialization.
 * @private
 */
cercalia.Map.prototype.initializeTopBarInterface_ = function() {

	this.createTopBar();

	//document.getElementById(this.containerId_).appendChild(topBarElement);

	//Controles
	for (var index in this.controlsBar_) {
		switch (this.controlsBar_[index]) {
			case cercalia.MapControls.LayerSwitcher:
				this.addControl(new cercalia.Control.LayerSwitcher({
					listMapTypes: this.mapLayers_
				}));
				break;
			case cercalia.MapControls.Logisitics:
				this.addControl(new cercalia.Control.Logistics({}));
				break;
			case cercalia.MapControls.MeteoControl:
				this.addControl(new cercalia.Control.Meteo({}));
				break;
			case cercalia.MapControls.MeasureControl:
				this.addControl(new cercalia.Control.Measure({
					scaleUnits: this.scaleUnits_
				}));
				break;
			case cercalia.MapControls.CleanControl:
				this.addControl(new cercalia.Control.Clean({}));
				break;
			case cercalia.MapControls.DrawControl:
				this.addControl(new cercalia.Control.Draw({}));
				break;
			case cercalia.MapControls.DragControl:
				this.addControl(new cercalia.Control.Drag({}));
				break;
			case cercalia.MapControls.Traffic:
				this.addControl(new cercalia.Control.Traffic({}));
				break;
			case cercalia.MapControls.GasStations:
				this.addControl(new cercalia.Control.GasStations());
				break;
			case cercalia.MapControls.Isochrones:
				this.addControl(new cercalia.Control.Isochrones());
				break;
		}
	}
};


/**
 * Show all controls
 */
cercalia.Map.prototype.showAllControls = function() {
	var topBarId = "topBar_" + this.id_;
	this.map_.getControls().forEach(function(control) {
		control.show();
	});
	cercalia.jQuery('#' + topBarId).show();
};

/**
 * Hide all controls
 */
cercalia.Map.prototype.hideAllControls = function() {
	var topBarId = "topBar_" + this.id_;
	this.map_.getControls().forEach(function(control) {
		control.hide();
	});
	cercalia.jQuery('#' + topBarId).hide();
};

/**
 * Add a control in the map, by parameter
 * @param {cercalia.Control} control
 */
cercalia.Map.prototype.addControl = function(control) {

	var topBarId = "topBar_" + this.id_;
	switch (control.getClass()) {
		case "cercalia.Control.Clean":
		case "cercalia.Control.CustomControl":
		case "cercalia.Control.CustomSwitcherControl":
		case "cercalia.Control.Drag":
		case "cercalia.Control.Draw":
		case "cercalia.Control.GasStations":
		case "cercalia.Control.Isochrones":
		case "cercalia.Control.LayerSwitcher":
		case "cercalia.Control.Logistics":
		case "cercalia.Control.Measure":
		case "cercalia.Control.Meteo":
		case "cercalia.Control.Traffic":
			control.setTopBar(topBarId);
			break;
	}

	this.map_.addControl(control);
};

/**
 * Disable the active controls
 */
cercalia.Map.prototype.deactivateControls = function() {

	var controls = this.map_.getControls().getArray();

	for (var i = 0; i < controls.length; i++) {
		if (controls[i].getName) {
			switch (controls[i].getName()) {
				case "DrawControl":
					if (controls[i].getDialog()) {
						controls[i].getDialog().dialog('close');
					}
					controls[i].disableDrawInteraction();
					break;

				case "MeasureControl":
					if (controls[i].getDialog()) {
						controls[i].getDialog().dialog('close');
					}
					controls[i].disableMeasureInteraction();
					break;
			}
		}
	}
};

/**
 * Clean POIs layers, if painted
 */
cercalia.Map.prototype.removeWMSPOIs = function() {
	if (this.layerPoiWMS_) {
		this.removeWMSLayer(this.layerPoiWMS_);
		this.layerPoiWMS_.destroy();
		this.layerPoiWMS_ = null;
	}
};

/**
 * Create a WMS POIs layer, specifying the categories to be painted.<br/>
 * Contact with Nexus Geografics for additional information and use.
 * @param {Array.<string>|string} categories
 */
cercalia.Map.prototype.createWMSPOIs = function(categories) {

	var self = this;
	var poicats = null;

	if (categories != null) {
		poicats = (categories instanceof Array) ? categories.join(",").replace(/ +(?= )/g, '') : categories.trim();
		this.poicatsEnabled_ = (poicats == "") ? [] : poicats.split(",");
	}

	//Si no se ha pasado ninguna categoria (si categories==null || categories==[])
	if (categories == null || this.poicatsEnabled_.length == 0) {
		this.removeWMSPOIs();
		this.poicatsEnabled_ = [];
	}

	//Si tenemos que hacer alguna petición..
	if (this.poicatsEnabled_.length > 0) {

		var params = {
			key: this.cercaliaKey_,
			cmd: 'authcategory',
			categories: poicats
		};

		cercalia.jQuery.ajax({
			url: this.server_,
			data: params,
			dataType: "json",
			crossDomain: true,
			timeout: 5000,
			beforeSend: function() {
				self.removeWMSPOIs();
			},
			success: function(data) {
				var authkey = data.cercalia.auth.key;

				var params = {
					LAYERS: "pois",
					FORMAT: "image/png",
					TRANSPARENT: "TRUE",
					VERSION: '1.1.1',
					SERVICE: 'WMS',
					authkey: authkey
				};

				var options = {
					gutter: 10
				};

				self.layerPoiWMS_ = new cercalia.WMS(cercalia.LayerEnumeration.CercaliaWMSPois, servers.pois, params, options);
				self.addWMSLayer(self.layerPoiWMS_);
			},
			error: cercalia.AJAXException
		});
	}
};

/**
 * Resets services
 */
cercalia.Map.prototype.clearCercaliaServices = function() {
	this.geocodingService_ = new cercalia.service.Geocoding();
	this.reverseGeocodingService_ = new cercalia.service.ReverseGeocoding();
	this.routing_.removeRoute();
	//this.routing_ = new cercalia.RouteMap({map: this, version: 2});
};

/**
 * Widget reference to map.
 * @private
 * @param {cercalia.widget} widget Widget to register.
 */
cercalia.Map.prototype.addWidget = function(widget) {

	switch (widget.getClass()) {
		case "cercalia.widget.Routing":
			widget.setMap(this);
			this.routing_.setWidget(widget);
			break;
		case "cercalia.widget.PoiProximity":
			this.poiProximityWidget_ = widget;
			widget.setMap(this);
			break;
		case "cercalia.widget.PoiMap":
		case "cercalia.widget.PoiSuggest":
		case "cercalia.widget.Geocoding":
		case "cercalia.widget.GeocodingSuggest":
		case "cercalia.widget.ReverseGeocoding":
			widget.setMap(this);
			break;
		default:
			cercalia.Exception("Widget no soportado: " + widget.getClass());
	}
};

/**
 *
 * @param {cercalia.RouteMap} routeMap
 */
cercalia.Map.prototype.setRouteMap = function(routeMap) {
	this.routing_ = routeMap;
};

/**
 *
 * @return routeMap {cercalia.RouteMap}
 */
cercalia.Map.prototype.getRouteMap = function() {
	return this.routing_;
};

/**
 * Enable the painting status. The features can be draggables, changing the features styles.
 * @param {string} type	Type `[Point|LineString|Polygon|Circle]`
 * @param {boolean|undefined} dragFeatures Boolean. `true` or `false`.
 * @param {Object|undefined} styles JS object with the style values: `[strokeColor, strokeWidth, strokeOpacity, fillColor, fillOpacity, radius]`. For example:
 * 			 `{strokeColor:'#ff0000',fillColor:'#00ff00',radius:5,fillOpacity:0.5}`
 * @param {Function|undefined} drawEndFunction Call function when user finish drawing the feature. Send the same feature painted
 */
cercalia.Map.prototype.enableDrawInteraction = function(type, dragFeatures, styles, drawEndFunction) {

	this.disableDrawInteraction();

	styles = styles || {};

	if (type) {

		this.drawInteraction_ = new ol.interaction.Draw({
			//source: this.drawLayer_.getSource(), // No fa falta perque volem afegir els features resultants nosaltres. Si posem aquest layer ell dibuixa automaticament el resultat aqui.
			type: type
		});
		this.map_.addInteraction(this.drawInteraction_);

		var self = this;

		this.drawInteraction_.on('drawstart', function(evt) {
			self.dragInteraction_.setActive(false);
		});

		this.drawInteraction_.on('drawend', function(evt) {

			var cercaliaFeature = new cercalia.Feature({
				geometry: evt.feature.getGeometry(),
				strokeColor: styles.strokeColor ? styles.strokeColor : '#B5E2FF',
				strokeOpacity: styles.strokeOpacity ? styles.strokeOpacity : 0.75,
				fillColor: styles.fillColor ? styles.fillColor : '#B5E2FF',
				fillOpacity: styles.fillOpacity ? styles.fillOpacity : 0.75,
				radius: styles.radius ? styles.radius : 5,
				zIndex: styles.zIndex ? styles.zIndex : null
			});

			self.addFeature(cercaliaFeature); //Para añadir nuestra feature 'cercalia.Feature'

			if (drawEndFunction) {
				drawEndFunction(cercaliaFeature);
			}
			self.dragInteraction_.setActive(true);
		});
	}
};

/**
 * Disable the painting status
 */
cercalia.Map.prototype.disableDrawInteraction = function() {
	if (this.drawInteraction_ != null) {
		this.map_.removeInteraction(this.drawInteraction_);
		this.drawInteraction_ = null;
	}
};



/**
 * Enable markers clustering. Note: When clustering is enabled drag markers won't work.
 * @param {number|undefined} [distance=40]	Distance, in pixels, to group markers with clusters. Default value: 40.
 * @param {Function|undefined} clickFunction Call function on cluster click. <br/>Parameters avaliable (clusterFeature, features). <br/>
 * 											 First parameter it's an object <a href="http://openlayers.org/en/v3.6.0/apidoc/ol.Feature.html">`ol.Feature`</a>
 * 											 visible on map as a cluster, <br/>
 * 											 Second parameter it's a  {@link cercalia.Marker} array objects with the features contained in a cluster.
 * @param {Function|undefined} mouseOverFunction Call function on cluster mouseover. Same parameters used in 'clickFunction' event
 * @param {Function|undefined} doubleClickFunction Call function on double click on cluster. <br/>Parameters avaliable (clusterFeature, features). <br/>
 * @param {Function|undefined} mouseOutFunction Call function on cluster mouseout. Same parameters used in 'clickFunction' event
 */
cercalia.Map.prototype.enableClustering = function(distance, clickFunction, mouseOverFunction, doubleClickFunction, mouseOutFunction) {

	if (this.clusterLayer_ == null && this.clusterLayerDraggable_ == null) {
		this.closePopups();

		this.removeLayer(this.markerLayer_);
		this.removeLayer(this.dragLayer_);

		var self = this;

		var clusterSource = new ol.source.Cluster({
			distance: distance ? distance : 40,
			source: this.markerLayer_.getSource()
		});

		var clusterSourceDraggable = new ol.source.Cluster({
			distance: distance ? distance : 40,
			source: this.dragLayer_.getSource()
		});

		var styleCache = {};

		//Funcion por defecto si no se especifica
		if (!clickFunction) {
			clickFunction = function(clusterFeature, markers) {
				//	Funcion por defecto: Hacer fitBounds a los markers
				var bounds = new cercalia.Bounds();
				for (var i = 0; i < markers.length; i++) {
					bounds.extend(markers[i].getPosition());
				}
				self.fitBounds(bounds);
			};
		}

		var optionsClusterLayerMarkerBase = {
			onClickFunction: clickFunction,
			onDoubleClickFunction: doubleClickFunction,
			onMouseOverFunction: mouseOverFunction,
			onMouseOutFunction: mouseOutFunction,
			style: function(feature, resolution) {
				var size = feature.get('features').length;
				var style = styleCache[size];
				if (!style) {
					if (size > 1) {
						var color, radius, strokeColor;
						if (size < 10) {
							color = '#008CFF';
							strokeColor = cercalia.Util.transformColor2rgba('#008CFF', 0.5);
							radius = 10;
						} else if (size < 20) {
							color = '#FFBF00';
							strokeColor = cercalia.Util.transformColor2rgba('#FFBF00', 0.5);
							radius = 12;
						} else {
							strokeColor = cercalia.Util.transformColor2rgba('#FF0001', 0.5);
							color = '#FF0001';
							radius = 14;
						}

						style = [new ol.style.Style({
							image: new ol.style.Circle({
								radius: radius,
								stroke: new ol.style.Stroke({
									color: strokeColor,
									width: 10
								}),
								fill: new ol.style.Fill({
									color: color
								})
							}),
							text: new ol.style.Text({
								text: size.toString(),
								fill: new ol.style.Fill({
									color: '#fff' //Color del texto
								})
							})
						})];
						styleCache[size] = style;

					} else {
						var featureStyle = feature.get('features')[0].getStyle();
						style = Array.isArray(featureStyle) ? featureStyle : [featureStyle];
						//style = [feature.get('features')[0].getStyle()]; //Estilo de la única feature que hay
					}
				}

				//Si se mostraban anteriormente los labels hacemos el tratamiento
				if (self.visibleLabels_) {
					//Comprobamos si tienen que mostrar o no el label. Solo lo mostrara cuando haya solo un marker, es decir, que no esté en cluster
					if (size > 1) {
						for (var i = 0; i < size; i++) {
							var marker = feature.get('features')[i].marker_;
							if (marker) marker.hideLabel();
						}
					} else {
						var marker = feature.get('features')[0].marker_;
						if (marker) marker.showLabel();
					}
				}

				return style;
			}
		};

		var optionsClusterLayerMarker = cercalia.jQuery.extend({
			name: cercalia.LayerEnumeration.ClusterLayerMarker,
			source: clusterSource
		}, optionsClusterLayerMarkerBase);

		var optionsClusterLayerMarkerDraggable = cercalia.jQuery.extend({
			name: cercalia.LayerEnumeration.ClusterLayerMarkerDraggable,
			source: clusterSourceDraggable
		}, optionsClusterLayerMarkerBase);

		this.clusterLayer_ = new ol.layer.Vector(optionsClusterLayerMarker);
		this.clusterLayerDraggable_ = new ol.layer.Vector(optionsClusterLayerMarkerDraggable);

		this.addLayer(this.clusterLayerDraggable_);
		this.addLayer(this.clusterLayer_);
	}
};


/**
 * Disable markers clustering. Every marker will be painted singly, with its icon.
 */
cercalia.Map.prototype.disableClustering = function() {
	if (this.clusterLayer_ != null && this.clusterLayerDraggable_ != null) {
		this.closePopups();

		//Layer markers no draggables
		this.removeLayer(this.clusterLayer_);
		if (this.clusterLayer_.getSource().unlistenSource) { //Cuidado! funcio afegida per nosaltres a OL
			this.clusterLayer_.getSource().unlistenSource();
		}
		this.clusterLayer_ = null;
		this.addLayer(this.markerLayer_);

		//Layer markers draggables
		this.removeLayer(this.clusterLayerDraggable_);
		if (this.clusterLayerDraggable_.getSource().unlistenSource) { //Cuidado! funcio afegida per nosaltres a OL!
			this.clusterLayerDraggable_.getSource().unlistenSource();
		}
		this.clusterLayerDraggable_ = null;
		this.addLayer(this.dragLayer_);

		if (this.visibleLabels_) {
			this.showLabels();
		}
	}
};

/**
 * @private
 * @param {cercalia.widget.MainMenu} mainMenu
 */
cercalia.Map.prototype.setMainMenu = function(mainMenuWidget) {
	this.mainMenuWidget_ = mainMenuWidget;
};

/**
 * @return {cercalia.widget.MainMenu} Returns the reference to {@link cercalia.widget.MainMenu}
 */
cercalia.Map.prototype.getMainMenu = function() {
	return this.mainMenuWidget_;
};

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

/**
 * @return {Object} View options from cercalia map.
 */
cercalia.Map.prototype.getViewOptions = function() {
	return this.viewOptions_;
};


/**
 * @param {cercalia.LonLat} position
 * @return {cercalia.Feature} Nearest feature to coordinate
 */
cercalia.Map.prototype.getClosestFeature = function(position) {
	return this.getClosestFeatureByLayer_(position, this.featureLayer_);
};


/**
 * @param {cercalia.LonLat} position
 * @return {cercalia.Marker} Nearest marker to coordinate
 */
cercalia.Map.prototype.getClosestMarker = function(position) {
	return this.getClosestFeatureByLayer_(position, this.markerLayer_);
};

/**
 * @private
 * @param {cercalia.LonLat} position
 * @return {cercalia.Marker|cercalia.Feature} Nearest marker to coordinate
 */
cercalia.Map.prototype.getClosestFeatureByLayer_ = function(position, layer) {
	var source = layer.getSource();
	var featureOL = source.getClosestFeatureToCoordinate(position.transform('EPSG:3857'));
	if (featureOL != null && featureOL.getCercaliaObject() != null) {
		return featureOL.getCercaliaObject();
	} else {
		return null;
	}
};


/**
 * @return {array<string>} return rightClick menu options.
 */
cercalia.Map.prototype.getRightClickMenuOptions = function() {
	return this.rightClickMenuOptions_
};


/**
 * @param {array<string>} Set rightClick menu options.
 */
cercalia.Map.prototype.setRightClickMenuOptions = function(array) {

	if (this.contextMenuOverlay_ != null) {
		for (var i = 0; i < this.rightClickMenuOptions_.length; i++) {
			//this.contextMenuOverlay_.removeOption(this.rightClickMenuOptions_[i]);
			var o = this.rightClickMenuOptions_[i];

			if (o === cercalia.ContextMenuOptions.Location) {
				// remove get direction option
				this.contextMenuOverlay_.removeOption('getDirection');

			} else if (o === cercalia.ContextMenuOptions.Routing) {
				// remove route options
				this.contextMenuOverlay_.removeOption('setRouteStart');
				this.contextMenuOverlay_.removeOption('setRouteWaypoint');
				this.contextMenuOverlay_.removeOption('setRouteEnd');
			}
		}
	}

	this.rightClickMenuOptions_ = array;

	this.addRightClickMenuOptions_(this.contextMenuOverlay_);
};

/**
 * @private
 * @param {ol.Overlay} contextMenu add rightClickMenuOption in contextMenu
 */
cercalia.Map.prototype.addRightClickMenuOptions_ = function(contextMenu) {

	var self = this;
	if (this.rightClickMenuOptions_ != null) {
		for (var i = 0; i < this.rightClickMenuOptions_.length; i++) {
			var o = this.rightClickMenuOptions_[i];
			if (o === cercalia.ContextMenuOptions.Location) {
				// Adding get direction option
				contextMenu.addOption('getDirection', 'Get address', function(evnt, lonLat) {
					self.getDirectionFunction_(evnt, lonLat);
				});

			} else if (o === cercalia.ContextMenuOptions.Routing) {
				// Adding route options
				contextMenu.addOption('setRouteStart', 'Route origin', function(evnt, lonLat) {
					self.setRouteStartFunction_(evnt, lonLat);
				});
				contextMenu.addOption('setRouteWaypoint', 'Route waypoint', function(evnt, lonLat) {
					self.setRouteWaypoint_(evnt, lonLat);
				});
				contextMenu.addOption('setRouteEnd', 'Route destination', function(evnt, lonLat) {
					self.setRouteEndFunction_(evnt, lonLat);
				});
			}
		}
	}
};

/**
 * Set the marker in the 'layer', and clean it from 'dragLayerToRemove'.
 * @private
 * @param {ol.layer} layer where set the marker
 * @param {cercalia.Marker|cercalia.Feature|ol.Feature|Array.<cercalia.Marker>|Array.<cercalia.Feature>|Array.<ol.Feature>} feature
 * @param {ol.layer} dragLayerToRemove layer where the marker is removed
 */
cercalia.Map.prototype.putMarkerToLayerNoDrag_ = function(layer, feature, dragLayerToRemove) {

	//var feature = marker instanceof cercalia.Feature || marker instanceof cercalia.Marker ? marker.getFeature() : marker;
	if (feature.constructor !== Array) {
		feature = [feature];
	}

	var arrFeatureOL = [];
	for (var i = 0; i < feature.length; i++) {
		var featureOL = feature[i].getFeature ? feature[i].getFeature() : feature[i];
		arrFeatureOL.push(featureOL);
		if (dragLayerToRemove.getSource().getFeatureById(featureOL.getId()) != null) {
			dragLayerToRemove.getSource().removeFeature(featureOL);
		}
	}

	layer.getSource().addFeatures(arrFeatureOL);
};


/**
 * Remove the marker from specified layer.
 * @private
 * @param {ol.layer.Vector} layer that contains the marker.
 * @param {cercalia.Marker|cercalia.Feature} feature
 */
cercalia.Map.prototype.removeMarkerToLayerNoDrag_ = function(layer, feature) {
	try {
		var source = layer.getSource();
		var featureOL = feature.getFeature ? feature.getFeature() : feature;
		if (layer && source.getFeatureById(featureOL.getId()) != null) {
			layer.getSource().removeFeature(featureOL);
		}
	} catch (err) {
		console.error(err);
	}
};

/**
 * Set the marker in the drag layer, and remove it from the specified layer.
 * @private
 * @param {cercalia.Marker|cercalia.Feature|ol.Feature|Array.<cercalia.Marker>|Array.<cercalia.Feature>|Array.<ol.Feature>} feature
 * @param {ol.layer} layerToRemove layer where the marker is removed.
 */
cercalia.Map.prototype.addMarkerToDragLayer_ = function(feature, layerToRemove) {

	if (feature.constructor !== Array) {
		feature = [feature];
	}

	var arrFeatureOL = [];
	for (var i = 0; i < feature.length; i++) {
		var featureOL = feature[i].getFeature ? feature[i].getFeature() : feature[i];
		arrFeatureOL.push(featureOL);
		if (layerToRemove.getSource().getFeatureById(featureOL.getId()) != null) {
			layerToRemove.getSource().removeFeature(featureOL);
		}
	}

	this.dragLayer_.getSource().addFeatures(arrFeatureOL);
};


/**
 * Remove the marker from drag layer.
 * @private
 * @param {cercalia.Marker|cercalia.Feature|ol.Feature} marker
 */
cercalia.Map.prototype.removeMarkerFromDragLayer_ = function(feature) {
	var featureOL = feature.getFeature ? feature.getFeature() : feature;

	if (this.dragLayer_ && this.dragLayer_.getSource().getFeatureById(featureOL.getId()) != null) {
		this.dragLayer_.getSource().removeFeature(featureOL);
	}
};

/**
 *
 * @return {cercalia.Interaction.DragFeatures} dragInteraction
 */
cercalia.Map.prototype.getInteractionDrag = function() {
		return this.dragInteraction_;
	}
	/**
	 * Destroy function
	 */
cercalia.Map.prototype.destroy = function() {
	this.map_.setTarget(null);

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

/**
 *
 * @returns {ol.Overlay}
 */
cercalia.Map.prototype.getContextMenuOverlay = function() {
	return this.contextMenuOverlay_;
}

/**
 * Shows "Loading..."
 */
cercalia.Map.prototype.showLoading = function() {
	this.overlayLoading.show();
};

/**
 * Hide "Loading..."
 */
cercalia.Map.prototype.hideLoading = function() {
	this.overlayLoading.hide();
};


/**
 * Export PNG image
 * @param {string|undefined} filename
 */
cercalia.Map.prototype.exportPNG = function(filename) {

	var self = this;
	var exportPNGElement = document.createElement('a');
	exportPNGElement.addEventListener('click', function() {
		self.map_.once('postcompose', function(event) {
			var canvas = event.context.canvas;
			exportPNGElement.download = filename ? filename : 'ExportCercaliaMap.png';
			exportPNGElement.href = canvas.toDataURL('image/png');
		});
		self.map_.renderSync();
	}, false);

	document.body.appendChild(exportPNGElement);
	exportPNGElement.click();
	document.body.removeChild(exportPNGElement);

};

/**
 * @return scale units. Values `metric` or `imperial`
 */
cercalia.Map.prototype.getScaleUnits = function() {
	return this.scaleUnits_;
};