/**
* @classdesc
*
* Marker graphical format with which perform a set of operations and customizations.
* The core of this class is based on OpenLayers3 <a href="http://openlayers.org/en/v3.0.0/apidoc/ol.Feature.html">`ol.Feature`</a> class
* For obtain the reference of this object, use the `getMap()` function call.
*
* @constructor
* @param {cercaliax.MarkerOptions} options Marker options
*/
cercalia.Marker = function(options) {
/**
* Class name
* @private
* @type {string}
*/
this.CLASS_NAME_ = "cercalia.Marker";
/**
* @private
* @type {string}
*/
this.id_ = options.id ? options.id : 'CercaliaMarker_id_' + Date.now() + (Math.random()*0xffffffff|0);
/**
* @private
* @type {cercalia.Map}
*/
this.map_ = null;
/**
* @private
* @type {cercalia.LonLat}
*/
this.position_ = options.position ? options.position : new cercalia.LonLat(0,0);
/**
* @private
* @type {cercalia.Icon}
*/
this.icon_ = options.icon ? options.icon : cercalia.Icon.createDefault();
/**
* @private
* @type {cercalia.SimpleLabel}
*/
this.simpleLabel_ = options.simpleLabel ? options.simpleLabel : null;
/**
* @private
* @type {boolean}
*/
this.draggable_ = undefined;
/**
* @private
* @type {string}
*/
this.label_ = options.label ? options.label : null;
/**
* @private
* @type {cercalia.Popup}
*/
this.popup_ = options.popup ? options.popup : null;
/**
* @private
* @type {number}
*/
this.zIndex_ = options.zIndex ? options.zIndex : 200;
/**
* @type {Function}
*/
this.onClick = options.onClick ? options.onClick : null;
/**
* @type {Function}
*/
this.onDoubleClick = options.onDoubleClick ? options.onDoubleClick : null;
/**
* @type {Function}
*/
this.onRightClick = options.onRightClick ? options.onRightClick : null;
/**
* @type {Function}
*/
this.onMouseOver = options.onMouseOver ? options.onMouseOver : null;
/**
* onMouseOver Function, if specified
* @type {Function}
*/
this.onMouseOut = options.onMouseOut ? options.onMouseOut : null;
/**
* @private
* @type {Function}
*/
this.onDragStartCallBack_ = options.onDragStart ? options.onDragStart : null;
/**
* @private
* @type {Function}
*/
this.onDragMoveCallBack_ = options.onDragMove ? options.onDragMove : null;
/**
* @private
* @type {Function}
*/
this.onDragEndCallBack_ = options.onDragEnd ? options.onDragEnd : null;
/**
* @private
* @type {Function}
*/
this.onDropCallBack_ = options.onDrop ? options.onDrop : null;
/**
* @type {Function}
*/
this.onDragStart = function ( marker,lonlat, evt ) {
if(this.popup_ && this.popup_.isOpen()) {
this.popup_.hide();
}
if( this.onDragStartCallBack_ ) {
this.onDragStartCallBack_(marker, lonlat, evt);
}
};
/**
* @type {Function}
*/
this.onDragMove = function ( marker,lonlat, evt ) {
this.isDragging_ = true;
this.refreshPositionFromGeometry();
if(this.onDragMoveCallBack_) {
this.onDragMoveCallBack_(marker, lonlat, evt);
}
};
/**
* @type {Function}
*/
this.onDragEnd = function ( marker,lonlat, evt ) {
this.isDragging_ = false;
this.refreshPositionFromGeometry();
if(this.onDragEndCallBack_) {
this.onDragEndCallBack_(marker, lonlat, evt);
}
if(this.onDropCallBack_) {
this.onDropFunction_();
}
};
/**
* @private
* @type {boolean}
*/
this.isDragging_ = false;
/**
* @private
* @type {boolean}
*/
this.visible_ = true;
/**
* @private
* @type {Object}
*/
this.optionsStyle_ = {
image: this.icon_.getIconStyle(),
zIndex: this.zIndex_
};
this.icon_.setMarker(this);
if(this.simpleLabel_) {
this.optionsStyle_.text = this.simpleLabel_.getTextStyle();
this.simpleLabel_.setMarker(this);
}
/**
* Creates the feature
* @private
* @type {ol.Feature}
*/
this.feature_ = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.transform([this.position_.getLon(),this.position_.getLat()], 'EPSG:4326', 'EPSG:3857'))
});
this.feature_.setId(this.id_);
/**
* @private
* @type {ol.style.Style}
*/
this.style_ = null;
/**
* @private
* @type {Array.<ol.style.Style>}
*/
this.extraStyles_ = [];
/**
* @private
* @type {Array.<cercalia.Icon>}
*/
this.extraIcons_ = [];
this.setStyle_();
/**
* Set the marker feature cyclic. For enabling class access from marker
* @private
* @type {cercalia.Marker}
*/
this.feature_.marker_ = this;
this.setDraggable(options.draggable ? options.draggable : false);
/**
* @private
* @type {string}
*/
this.feature_.featureType_ = this.CLASS_NAME_;
if (this.popup_) {
this.popup_.setMarker(this);
}
};
/**
* @return {ol.Feature} Object <a href="http://openlayers.org/en/v3.0.0/apidoc/ol.Feature.html">`ol.Feature`</a>
*/
cercalia.Marker.prototype.getFeature = function() {
return this.feature_;
};
/**
* @return {string} Marker ID. If specified, if not returns null.
*/
cercalia.Marker.prototype.getId = function() {
return this.id_;
};
/**
* Assign ID to marker
* @param {string} id
*/
cercalia.Marker.prototype.setId = function(id) {
this.id_ = id;
};
/**
*
* @return {cercalia.LonLat} position The marker Array position `[lat, lon]`.
*/
cercalia.Marker.prototype.getPosition = function() {
return this.position_;
};
/**
* Updates the marker position through its geometry. This function is usefull on marker dragging,
* where its position (obtained through its geometry) has been updated, but not its popup or label.
*/
cercalia.Marker.prototype.refreshPositionFromGeometry = function() {
try{
var coordinatesGeo = ol.proj.transform(this.getFeature().getGeometry().getCoordinates(), this.map_.getProjectionCode(), 'EPSG:4326') ;
this.position_ = new cercalia.LonLat(coordinatesGeo[0],coordinatesGeo[1]);
if(this.label_) {
this.label_.setPosition(this.position_, this.map_.getProjectionCode());
}
if(this.popup_) {
this.popup_.hide();
this.popup_.setPosition(this.position_, this.map_.getProjectionCode());
}
}catch(e) {}
};
/**
* Move the marker position to the specified via parameter
* @param {cercalia.LonLat} position
*/
cercalia.Marker.prototype.setPosition = function(position) {
var projectionCode = this.map_ ? this.map_.getProjectionCode() : 'EPSG:3857';
var coordGeo = ol.proj.transform([position.getLon(),position.getLat()], 'EPSG:4326', projectionCode) ;
this.position_ = position;
var geometry = this.feature_.getGeometry();
geometry.setCoordinates(coordGeo);
//this.feature_.setGeometry(new ol.geom.Point(coordGeo));
if(this.label_) {
this.label_.setPosition(position, projectionCode);
}
if(this.popup_) {
this.popup_.setPosition(position, projectionCode);//aqui es passa Array (ataca directament a ol.Overlay)
}
//BUG. Lo dejamos así porque después de actualizar la posición no se podía hacer dragging.
// if(this.map_ && this.draggable_) {
// this.map_.dragFeatures_.remove(this.feature_);
// this.map_.dragFeatures_.push(this.feature_);
// this.map_.markerLayer_.getSource().removeFeature(this.feature_);
// this.map_.markerLayer_.getSource().addFeature(this.feature_);
// this.map_.addMarker(this);
// }
// this.setDraggable(this.draggable_);
};
/**
* Move the parker position with an animation movement from a starting to ending position,
* in a specified time (duration). Optionally it's possible to call a function specified via parameter.
* @param {cercalia.LonLat} position Coordinate.
* @param {number|undefined} duration Movement duration, in milliseconds (default value 2000ms)
* @param {Function|undefined} callbackFn Callback function, when movement ends
* @param {boolean|undefined} centerMapDuringMovement Center map during movement. Default `false`.
* @param {number|undefined} numSteps Number of steps to animate. Default `100`.
*/
cercalia.Marker.prototype.movePosition = function(position, duration, callbackFn, centerMapDuringMovement, numSteps) {
if(this.map_) {
var projectionCode = this.map_.getProjectionCode();
var fullDuration = duration?duration:2000;
var steps = numSteps ? numSteps : 100;
var start = new Date().getTime();
var stepTime = fullDuration/steps;
var posActOL = ol.proj.transform([this.position_.getLon(),this.position_.getLat()], 'EPSG:4326', projectionCode);
var posOL = ol.proj.transform([position.getLon(),position.getLat()], 'EPSG:4326', projectionCode);
//Calculem la distancia per etapa
var totalDistanceX = posOL[0] - posActOL[0];
var totalDistanceY = posOL[1] - posActOL[1];
var stepDistanceX = totalDistanceX/steps;
var stepDistanceY = totalDistanceY/steps;
var lonAct = posActOL[0];
var latAct = posActOL[1];
var self = this;
var lastIteration = function(intervalIdToDelete) {
clearInterval(intervalId);
self.position_ = position;
self.getFeature().setGeometry(new ol.geom.Point(posOL));
self.refreshPositionFromGeometry();
if(callbackFn) {
callbackFn(self);
}
};
var numIteration = 0;
var intervalId = setInterval(function() {
//Ultima posicio
if( (new Date().getTime()-start) >= fullDuration ) {
lastIteration(intervalId);
} else {
lonAct+=stepDistanceX;
latAct+=stepDistanceY;
if(centerMapDuringMovement) {
self.map_.getMap().getView().setCenter([lonAct,latAct]);
}
//Si ha llegado al punto final antes de que se acabe el tiempo forzamos a detener el bucle
if( Math.abs(posOL[0]-lonAct) < 5 || Math.abs(posOL[1]-latAct) < 5 ) {
lastIteration(intervalId);
} else {
if(self.label_) {
self.label_.getOverlay().setPosition([lonAct,latAct]);//aqui es passa Array (ataca directament a ol.Overlay)
}
if(self.popup_) {
self.popup_.getOverlay().setPosition([lonAct,latAct]);
}
self.getFeature().setGeometry( new ol.geom.Point( [lonAct,latAct] ));
}
}
numIteration++;
}, stepTime*0.95);
}
};
/**
* Change the zIndex value
* @param {number} zIndex
*/
cercalia.Marker.prototype.setZIndex = function(zIndex) {
this.zIndex_ = zIndex;
this.optionsStyle_.zIndex = zIndex;
this.setStyle_();
};
/**
* @return {number} CSS marker zIndex
*/
cercalia.Marker.prototype.getZIndex = function() {
return this.zIndex_;
};
/**
* @param {boolean} draggable
*/
cercalia.Marker.prototype.setDraggable = function(draggable) {
var map = this.map_;
if(this.map_)this.map_.removeMarkers(this);
this.draggable_ = draggable;
if(map)map.addMarker(this);
};
/**
* @private
*/
cercalia.Marker.prototype.onDropFunction_ = function() {
var map = this.map_;
var mapOL = this.map_.getMap();
var pixel = this.map_.coordToPixel(this.getPosition());
var iconStyle = this.getIcon().getIconStyle();
var iconSize = iconStyle.getSize();
var iconAnchor = iconStyle.getAnchor();
var iconScale = iconStyle.getScale();
pixel[0] = Math.round(pixel[0] - Math.round(iconAnchor[0] * iconScale));
pixel[1] = Math.round(pixel[1] - Math.round(iconAnchor[1] * iconScale));
//Aquí el pixel está en el centro del icono
//var pixelNW = pixel;
//var pixelSE = [pixel[0] + Math.round(iconSize[0]*iconScale), pixel[1] + Math.round(iconSize[1]*iconScale)];
var pixelNE = [pixel[0] + Math.round(iconSize[0] * iconScale), pixel[1]];
var pixelSW = [pixel[0], pixel[1] + Math.round(iconSize[1] * iconScale)];
// //DEBUG (nomes per mostrar contorn)
// this.map_.getLayerByName("MarkerLayer").getSource().addFeature(new ol.Feature(new ol.geom.Point(mapOL.getCoordinateFromPixel(pixel))));
// var linestring = new cercalia.Linestring({
// path: [
// map.pixelToCoord(pixelNW),
// map.pixelToCoord(pixelNE),
// map.pixelToCoord(pixelSE),
// map.pixelToCoord(pixelSW),
// map.pixelToCoord(pixelNW)
// ]
// });
// this.map_.addLinestring(linestring);
// //FIDEBUG
//Buscamos las features (tipo marker)
var coordSW = mapOL.getCoordinateFromPixel(pixelSW);
var coordNE = mapOL.getCoordinateFromPixel(pixelNE);
var extent = coordSW.concat(coordNE);
var listMarkersInExtent = [];
//Recorrido por features dentro de un 'extent'
this.map_.getLayerByName("MarkerLayer").getSource().forEachFeatureInExtent(extent, function(feature) {
//Comprobamos para no devolver el mismo
if(feature.marker_ && this.getId() != feature.marker_.getId()) {
listMarkersInExtent.push(feature.marker_);
}
}, this);
this.map_.getLayerByName("DragLayer").getSource().forEachFeatureInExtent(extent, function(feature) {
//Comprobamos para no devolver el mismo
if(feature.marker_ && this.getId() != feature.marker_.getId()) {
listMarkersInExtent.push(feature.marker_);
}
}, this);
this.onDropCallBack_(listMarkersInExtent);
};
/**
* @return {boolean} Boolean showing if marker it's draggable.
*/
cercalia.Marker.prototype.isDraggable = function() {
return this.draggable_;
};
/**
* Show or hide the marker, including its SimpleLabel, Label or Popup.
* @param {boolean} visible
*/
cercalia.Marker.prototype.setVisible = function(visible) {
this.visible_ = visible;
this.icon_.setOpacity(visible ? 1 : 0);
for (var i = 0; i < this.extraIcons_.length; i++) {
this.extraIcons_[i].setOpacity(visible ? 1 : 0);
}
this.optionsStyle_.image = this.icon_.getIconStyle();
this.setStyle_();
//Si tiene simpleLabel. Lo escondemos
if(this.simpleLabel_) {
this.simpleLabel_.setVisible(visible);
}
if(this.label_) {
visible ? this.label_.show() : this.label_.hide();
}
if(this.popup_ && ! visible ) {
this.popup_.hide();
}
//if(this.isDraggable && !visible ) {
//this.popup_.hide();
//}
};
/**
* Change the marker icon
* @param {cercalia.Icon} icon
*/
cercalia.Marker.prototype.setIcon = function(icon) {
this.icon_ = icon;
this.icon_.setMarker(this);
this.optionsStyle_.image = icon.getIconStyle();
this.setStyle_();
};
/**
* @return {cercalia.Icon} Icon.
*/
cercalia.Marker.prototype.getIcon = function() {
return this.icon_;
};
/**
* SimpleLabel to add/modify related with the marker
* @param {cercalia.SimpleLabel} simpleLabel
*/
cercalia.Marker.prototype.setSimpleLabel = function(simpleLabel) {
this.simpleLabel_ = simpleLabel;
this.simpleLabel_.setMarker(this);
this.optionsStyle_.text = simpleLabel.getTextStyle();
this.setStyle_();
};
/**
* @return {cercalia.SimpleLabel} Returns {@link cercalia.SimpleLabel} if created. If not, returns `null`.
*/
cercalia.Marker.prototype.getSimpleLabel = function() {
return this.simpleLabel_;
};
/**
* @param {cercalia.Popup} popup
*/
cercalia.Marker.prototype.addPopup = function(popup) {
var projectionCode = this.map_ ? this.map_.getProjectionCode() : 'EPSG:3857';
if(popup==null) {
this.popup_ = null;
} else {
if(this.popup_ == null ) {
this.popup_ = popup;
this.popup_.setPosition(this.position_, projectionCode);
this.popup_.setMarker(this);
}
}
if(this.map_ != null) {
this.map_.getMap().addOverlay(popup.getOverlay());
}
};
/**
* @return {cercalia.Map} The map where it's painted, if not returns `null`.
*/
cercalia.Marker.prototype.getMap = function() {
return this.map_;
};
/**
* @return {cercalia.Popup} Popup, if don't have returns `null`.
*/
cercalia.Marker.prototype.getPopup = function() {
return this.popup_;
};
/**
* @param {cercalia.Map} map
*/
cercalia.Marker.prototype.setMap = function(map) {
if(map) {
if(this.map_ == null) {
this.map_ = map;
var projectionCode = this.map_.getProjectionCode();
//Si té una altra projecció que no és la original
if( projectionCode != 'EPSG:3857') {
var point = new ol.geom.Point(ol.proj.transform([this.position_.getLon(),this.position_.getLat()], 'EPSG:4326', projectionCode));
this.getFeature().setGeometry(point);
}
//Si se ha especificado el label
if(this.label_) {
this.map_.getMap().addOverlay(this.label_.getOverlay());
this.label_.setMarker(this);
}
//Si tiene popup lo añadimos
if(this.popup_) {
this.map_.getMap().addOverlay(this.popup_.getOverlay());
this.popup_.setPosition(this.position_, projectionCode); //Aqui lo pasamos con geograficas
}
}
} else {
if(this.map_) {
if(this.popup_) {
this.map_.getMap().removeOverlay(this.popup_.getOverlay());
}
if(this.label_) {
this.map_.getMap().removeOverlay(this.label_.getOverlay());
}
}
this.map_ = null;
}
};
/**
* @private
*/
cercalia.Marker.prototype.setStyle_ = function() {
this.style_ = new ol.style.Style(this.optionsStyle_);
if (this.feature_) {
if (this.extraStyles_.length === 0) {
this.feature_.setStyle(this.style_);
} else {
this.feature_.setStyle([this.style_].concat(this.extraStyles_));
}
}
};
/**
* Destroy popup
*/
cercalia.Marker.prototype.destroy = function() {
if(this.popup_) {
if(this.map_!=null) {
this.map_.getMap().removeOverlay(this.popup_.getOverlay());
}
this.popup_.destroy();
this.popup_ = null;
}
if(this.label_) {
if(this.map_!=null) {
this.map_.getMap().removeOverlay(this.label_.getOverlay());
}
this.removeLabel();
this.label_ = null;
}
if(this.simpleLabel_) {
this.simpleLabel_.destroy();
this.simpleLabel_ = null;
}
if(this.map_!=null) {
this.map_.removeMarkers(this);
this.map_ = null;
}
for (var key in this) {
delete this[key];
}
};
/**
* Assign/change marker label text
* @param {cercalia.Label} label Label content
*/
cercalia.Marker.prototype.setLabel = function(label) {
if(this.label_) {
this.label_.destroy();
}
if(this.map_) {
this.label_ = label;
this.label_.setPosition(this.position_, this.map_.getProjectionCode());
this.map_.getMap().addOverlay(label.getOverlay());
if( !this.map_.showingLabels() ){
this.hideLabel();
}
}
};
/**
* Show marker label
*/
cercalia.Marker.prototype.showLabel = function() {
if(this.label_) {
this.label_.show();
}
};
/**
* Hide marker label
*/
cercalia.Marker.prototype.hideLabel = function() {
if(this.label_) {
this.label_.hide();
}
};
/**
* @return {cercalia.Label} label Label assigned to th marker.
*/
cercalia.Marker.prototype.getLabel = function() {
return this.label_;
};
/**
* Destroy the label object
*/
cercalia.Marker.prototype.removeLabel = function() {
if(this.label_) {
this.label_.destroy();
this.label_ = null;
}
};
/**
* @public
* @return true if marker is dragging, otherwise false.
*/
cercalia.Marker.prototype.isDragging = function() {
return this.isDragging_;
};
/**
* @return {string} ClassName: `cercalia.Marker`
*/
cercalia.Marker.prototype.getClass = function() {
return this.CLASS_NAME_;
};
/**
* @return {boolean} `true` if visible, else `false`.
*/
cercalia.Marker.prototype.isVisible = function(){
return this.visible_;
};
/**
* Add extra icon with a plus zIndex. You can add multiple icons to a marker.
* @param {cercalia.Icon} icon Icon
*/
cercalia.Marker.prototype.addExtraIcon = function(icon) {
var iconStyle = icon.getIconStyle();
var styles = this.feature_.getStyle();
icon.setMarker(this);
this.extraIcons_.push(icon);
if (!Array.isArray(styles)) {
styles = [styles];
}
var lastStyle = styles[styles.length - 1];
var newStyle = new ol.style.Style({
image: iconStyle,
zIndex: lastStyle.getZIndex() + 1
});
styles.push(newStyle);
this.extraStyles_ = styles.slice(1); //Clone array except first argument.
this.setStyle_();
//this.feature_.setStyle(styles);
};
/**
* @return {boolean} `true` if extra icons.
*/
cercalia.Marker.prototype.hasExtraIcons = function() {
return this.extraStyles_.length > 0;
};
/**
* @return {Array.<cercalia.Icon>} Return extra icons
*/
cercalia.Marker.prototype.getExtraIcons = function() {
return this.extraIcons_;
};
/**
* Force to draw feature.
*/
cercalia.Marker.prototype.redraw = function() {
this.feature_.changed();
};