Source: temp/jsdocinputdirs/cercalia.tilepoimap.js

/**
 * Class constructor to draw Caercalia Map POIS.Para hacer una descarga de pois que no sea de Cercalia vease el <a href="#">ejemplo</a>.
 * @class
 * @constructor
 * @param {cercaliax.TilePoiMapOptions} options TilePoiMap options'
 */
cercalia.TilePoiMap = function(options) {
    
    /**
     * Class name
     * @private
     * @type {string}
     */
    this.CLASS_NAME_ = "cercalia.TilePoiMap";
    
    /* OPTIONS */
    
    /**
     * Tile size. Default [256, 256].
     * @private
     * @type {Array.<number>}
     */
    this.tileSize_ = options.tileSize ? options.tileSize : [256, 256]; 
    
    /**
     * Zoom level that the size of the icons change. Only it used in the default download version. 
     * @private
     * @type {number}
     */
    this.iconBigZoomLevel_ = options.iconBigZoomLevel ? options.iconBigZoomLevel : 15;

    /**
     * Custom function to create markers. This function must return an array of markers
     * @private
     * @type {Function}
     */
    this.createMarkersFunction_ = options.createMarkersFunction ? options.createMarkersFunction : null;
    
    /**
     * Map where markers are painted
     * @private
     * @type {cercalia.Map}
     */
    this.map_ = options.map ? options.map : null;

    
    /* VARIABLES PRIVADAS */
    
    /**
     * List of Cercalia categories to paint on map. 
     * @private
     * @type {Array.<string>}
     */
    this.categoriesList_ = [];

    /**
     * Layer simulating downloading tiles.
     * @private
     * @type {ol.layer.Vector}
     */
    this.vectorLayer_ = null;
    
    /**
     * Hash (key => zoom) painted markers.
     * @private
     * @type {Object}
     */
    this.markersPainted_ = {};
    
    /**
     * Hash (key => ZXY) ajax requests.
     * @private
     * @type {Object}
     */
    this.xhrPoisHash_ = {};
    
    /**
     * Tiles grid.
     * @private
     * @type {ol.tilegrid.XYZ}
     */
    this.tileGrid_ = null;
    
    /**
     * Current zoom level.
     * @private
     * @type {number}
     */
    this.zoomMap_ = null;
    
    this.timeoutLoadPois_ = null;
    
    /* SERVICIOS Y WIDGETS DE CERCALIA */
    
    /**
     * Get POIs service
     * @private
     * @type {cercaliax.service.Pois}
     */
    this.servicePoisOptions_ = {
        searchByScale : true,
        height: this.tileSize_,
        width: this.tileSize_//,
        //gridsize: 5000
    };
    
    // Inicialitze
    this.initialize_();
    
};

/**
 * Initializes the layer Source definition and create the function to be called for each of the downloaded tiles.
 * @private
 */
cercalia.TilePoiMap.prototype.initialize_ = function() {
    
    if(this.map_ != null) {
        
        // Si existe mapa añadimos el layer.
        this.vectorLayer_ = new ol.layer.Vector({ source: this.initSource_() });
        this.map_.addLayer(this.vectorLayer_);
        this.map_.getMap().on('moveend', this.moveEndFunction_, this);
        
    } else {
        console.error("No map defined.");        
    }
    
};

/**
 * Initializes the layer Source definition and create the function to be called for each of the downloaded tiles.
 * @private
 */
cercalia.TilePoiMap.prototype.initSource_ = function() {
    
    var self = this;
    var vectorSource = null;
    var viewOptions = this.map_.getViewOptions();
    
    // Creamos el tile grid.
    this.tileGrid_ = this.createTileGrid_({
        /*tileSize: this.tileSize_/*,
        maxZoom: viewOptions.maxZoom,
        minZoom: viewOptions.minZoom/*,
        extent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34]*/
    });
    
    // Inicializamos el source del layer. Se encarga de descargar y pintar los pois en el mapa.
    var sourceParams = {
        //format: new ol.format.GeoJSON(),
        loader: this.loader_(),
        projection: this.map_.getProjectionCode(),
        strategy: this.strategy_(this.tileGrid_),
        tileGrid: this.tileGrid_
    };

    // Creación del source
    vectorSource = new cercalia.tile.ServerVector(sourceParams);

    return vectorSource;
};

/**
 * Returns the strategy function followed to download pois.
 * @private
 * @param {ol.tilegrid.XYZ} tileGrid Map tiles grid.
 * @returns {Function}
 */
cercalia.TilePoiMap.prototype.strategy_ = function(tileGrid) {
    
    var self = this;
    
    /**
     * @param {ol.Extent} extent Extent.
     * @param {number} resolution Resolution.
     * @return {Array.<ol.Extent>} Extents.
     */
     var startegy = function(extent, resolution) {
         
         var extents = [];
//         console.group("Strategy");
         
         // Si no hay categorias a pintar no devolvemos tiles.
         if(self.categoriesList_ && self.categoriesList_.length > 0) {
         
             // Obtenemos zoom y rango de tiles a pintar
             var z = tileGrid.getZForResolution(resolution);
             var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
             var tileCoord = [z, 0, 0];
//             console.log("Calculate range: ", tileRange);
             
             // Guardamos la extensión y el id de la tile.
             for (tileCoord[1] = tileRange.minX; tileCoord[1] <= tileRange.maxX; ++tileCoord[1]) {
                 for (tileCoord[2] = tileRange.minY; tileCoord[2] <= tileRange.maxY; ++tileCoord[2]) {
                     extents.push({
                         extent: tileGrid.getTileCoordExtent(tileCoord),
                         tileId: [tileCoord[0], tileCoord[1], tileCoord[2]]
                     });
                 }
             }
         }
         
//         console.log("Extents to paint: ", extents);
//         console.groupEnd();
         return extents;
     };
     
     return startegy;
};

/**
 * Function for painting downloaded markers.
 * @private
 * @return {Function}
 */
cercalia.TilePoiMap.prototype.loader_ = function() {
    
    var self = this;
    
    /**
     * Function for painting downloaded markers.
     * @private
     * @param {ol.Extent} extent Tile extent
     * @param {number} resolution Tile resolution
     * @param {string} projection Map projection.
     * @param {Object} tileId Tile ZXY id.
     */
     var loaderFuntion = function (extent, resolution, projection, tileId) {
         
         // Calculamos BBOX i zoom del tile.
         var ws = new cercalia.LonLat(extent[0], extent[1], projection.getCode());
         var en = new cercalia.LonLat(extent[2], extent[3], projection.getCode());
         //var zoomLevel = this.map_.getZoom();
         var zoomLevel = self.tileGrid_.getZForResolution(resolution);
         var bounds = new cercalia.Bounds([ws.getLon(), ws.getLat(), en.getLon(), en.getLat()]);
         
         // Inicializamos variables contenedores de Ids y markers
         if(!self.markersPainted_[zoomLevel]) self.markersPainted_[zoomLevel] = [];
         
         // Descargamos!
         if(self.checkZoomWidthMapZoom_(zoomLevel)) {
             
//             console.log("Tile " + tileId.join(", ") + ": download.");
             self.dowloadFunction_(bounds, self.categoriesList_, zoomLevel, function(list) { self.paintMarkersFuncion_(list, zoomLevel, tileId); });
             
             
         } else {
             
//             console.log("Tile " + tileId.join(", ") + ": descartada, " + zoomLevel);
             // Marcamos la tile como descartada
             self.vectorLayer_.getSource().setLoadedTile(tileId, false);
             
             // Cancelamos peticiones pendientes de ese nivel de zoom
             cercalia.jQuery(self.xhrPoisHash_[zoomLevel]).each(function (index, element) {
                 element.abortLast();
             });
             self.xhrPoisHash_[zoomLevel] = [];
             
         }
     };
     
     return loaderFuntion;
     
};

/**
 * Checks whether the zoom is like the map
 * @private
 * @param {number} zoom Zoom level to check
 * @returns {Boolean} True if zoom is == map zoom.
 */
cercalia.TilePoiMap.prototype.checkZoomWidthMapZoom_ = function(zoom) {
    
    var resolution = this.map_.getMap().getView().getResolution();
    var mapZoomLevel = this.tileGrid_.getZForResolution(resolution);
    
    return mapZoomLevel == zoom;
};

/**
 * POIs download function.
 * @private
 * @param {cercalia.Bounds} bounds Tile BBOX.
 * @param {Array.<string>} categoriesList Categories list.
 * @param {number} zoomLevel Tile zoom level.
 * @param {Function} paintFunction Paint POIs function.
 */
cercalia.TilePoiMap.prototype.dowloadFunction_ = function(bounds, categoriesList, zoomLevel, paintFunction) {
    
    // Creamos servicio para obtener los pois por tiles.
    var servicePois = new cercalia.service.Pois(this.servicePoisOptions_);
    servicePois.setBounds(bounds);
    servicePois.setQueryPois(categoriesList);
    
    // Inicializamos hash del zoom de XHR si no lo esta.
    if(!this.xhrPoisHash_[zoomLevel]) this.xhrPoisHash_[zoomLevel] = [];
    this.xhrPoisHash_[zoomLevel].push(servicePois);
    
    // Hacemos el request de los pois
    var self = this;
	
	// El usuario puede especificar una funcion personalizada.
	var createMarkersFn = this.createMarkersFunction_ ? this.createMarkersFunction_ : this.createCercaliaMarkers_;
    servicePois.doPoisRequest(function (response) {
        
        // Generamos los markers
        var jsonMarkersArray = response.cercalia.map.gpoicats.poilist.poi instanceof Array ? response.cercalia.map.gpoicats.poilist.poi : [];
        var markersArray = createMarkersFn(jsonMarkersArray, bounds, zoomLevel);
//        console.log("Pois: ", bounds, zoomLevel, markersArray);
        
        // Pintamos
        paintFunction(markersArray, zoomLevel);
        
    });
};

/**
 * Paint markers on the map and saves the hash markers.
 * @private
 * @param {Array.<cercalia.Marker>} markersArray
 * @param {number} zoomLevel
 * @param {Object} tileId ZXY tile ID.
 */
cercalia.TilePoiMap.prototype.paintMarkersFuncion_ = function(markersArray, zoomLevel, tileId) {
    
    if(!(markersArray instanceof Array)) markersArray = [];
    
    // Pintamos los markers
    if(this.checkZoomWidthMapZoom_(zoomLevel)) {
        
        this.map_.addApiMarkers(markersArray);
        this.markersPainted_[zoomLevel] = this.markersPainted_[zoomLevel].concat(markersArray);
        
    } else {
        
        // Marcamos la tile como descartada
        this.vectorLayer_.getSource().setLoadedTile(tileId, false);
        
        // Cancelamos peticiones pendientes de ese nivel de zoom
        cercalia.jQuery(this.xhrPoisHash_[zoomLevel]).each(function (index, element) {
            element.abortLast();
        });
        this.xhrPoisHash_[zoomLevel] = [];
        
    }
    
};

/**
 * Create a list of markers to paint from server json, a BBOX and zoom level.
 * @private
 * @param {Object} json Json downloaded from server. 
 * @param {cercalia.Bounds} bounds BBOX (bounds). 
 * @param {number} zoomLevel Zoom level. 
 */
cercalia.TilePoiMap.prototype.createCercaliaMarkers_ = function(json, bounds, zoomLevel) {
    
    var listMarkers = [];
    var self = this;

    json.forEach(function(elem, index) {
        
        var markerLonLat = new cercalia.LonLat(elem.coord.x, elem.coord.y);
        if(bounds.containsLonLat(markerLonLat)) {
        	
        	// "subcategory_id":"SEC",
        	// és una prova
        	// el problema estava en els pois: D00ESC
        	// Els pois que tenen subcategoria no existeix la img de {category_id}.png
        	var imgPoiName = elem.category_id;
        	if( elem.subcategory_id !== undefined 
        			&& elem.subcategory_id != null 
        			&& elem.subcategory_id != "-1" ){
        		imgPoiName += "_" + elem.subcategory_id;
        	}
        	
            var img = cercaliaGlobals.img + '/pois/' + (zoomLevel >= self.iconBigZoomLevel_ ? 'big' : 'small') + '/' + imgPoiName + '.png';
            
            var marker = new cercalia.Marker({
                id: elem.id ? elem.id : null,
                position : markerLonLat,
                icon : new cercalia.Icon({src:img}),
                popup : new cercalia.Popup({
                    title: elem.name.value,
                    content: "<div>" + elem.info.value + "</div>"
                }),
                onClick: function() {
                    if(!this.getPopup().isOpen()) this.getPopup().show();
                    else this.getPopup().hide();
                }
            });
            
            listMarkers.push(marker);
        }
        
    });
    
    return listMarkers;
};

/**
 * Call function on map moveend.
 * @private
 * @param {ol.Event} event Event. 
 */
cercalia.TilePoiMap.prototype.moveEndFunction_ = function(event) {
    
    var resolution = this.map_.getMap().getView().getResolution();
    var zoomLevel = this.tileGrid_.getZForResolution(resolution);
    
    if(zoomLevel != this.zoomMap_) {

//        console.log(zoomLevel + " != " + this.zoomMap_);
        this.zoomMap_ = zoomLevel;
        this.clearAllExcept(this.zoomMap_);
        
    }
    
    // Recargamos las tiles de la extension actual del mapa.
    if(this.timeoutLoadPois_ != null) {
        window.clearTimeout(this.timeoutLoadPois_);
        this.timeoutLoadPois_ = null;
    }
    
    var olMap = this.map_.getMap();
    var mapSize = olMap.getSize();
    var mapExtent = olMap.getView().calculateExtent(mapSize);
    this.vectorLayer_.getSource().loadFeatures(mapExtent, olMap.getView().getResolutionForExtent(mapExtent, mapSize), olMap.getView().getProjection());
    
};

/**
 * Remove all markers from a specific zoom level.
 * @private
 * @param {number} zoomLevel Zoom level to clean. 
 */
cercalia.TilePoiMap.prototype.clear_ = function(zoomLevel) {
    
    if(zoomLevel) {
    
        // Borramos markers del nivel seleccionado.
        if(this.markersPainted_[zoomLevel] && this.markersPainted_[zoomLevel].length > 0) {
            this.map_.removeApiMarkers(this.markersPainted_[zoomLevel]);
        }
        this.markersPainted_[zoomLevel] = [];
        
        // Marcamos las tiles de ese nivel de zoom como no cargadas.
        this.vectorLayer_.getSource().clear(zoomLevel);
        
        // Cancelamos peticiones activas de este nivel de zoom y las eliminamos
        cercalia.jQuery(this.xhrPoisHash_[zoomLevel]).each(function (index, element) {
            element.abortLast();
        });
        this.xhrPoisHash_[zoomLevel] = [];
        
        
//        console.group("Clear");
//        console.log("zoomLevel = " + zoomLevel);
//        console.log("markersPainted_ = ", this.markersPainted_);
//        console.log("tileLoadedHash_ = ", this.tileLoadedHash_);
//        console.groupEnd();
        
    }
    
};

/**
 * Remove all markers except a specified zoom.
 * @param {number} zoomLevel Zoom level to mantain (not clean). 
 */
cercalia.TilePoiMap.prototype.clearAllExcept = function(zoomLevel) {
    
    if(zoomLevel) {
    
        for(var index in this.markersPainted_) {
            if(zoomLevel != index) this.clear_(index);
        }
        
    }
    
};

/**
 * Remove painted markers, for all zoom levels.
 */
cercalia.TilePoiMap.prototype.clearAll = function() {
    
    for(var index in this.markersPainted_) {
        this.clear_(index);
    }
    
    this.markersPainted_ = {};
    
};

/**
 * Removes the map layer.
 */
cercalia.TilePoiMap.prototype.remove = function() {
    this.clearAll();
    this.map_.removeLayer(this.vectorLayer_);    
};

/**
 * Modify the Cercalia's categories list to paint on map.
 * @param {Array.<string>} categories Categories list to paint. 
 */
cercalia.TilePoiMap.prototype.setCategories = function(categories) {
    
    this.categoriesList_ = categories;
    //console.log("Set categories to [" + this.categoriesList_.join(", ") + "]");
    
    if(this.map_) {
        
        // Quitamos todos los marcadores.
        this.clearAll();
        
        // Recargamos las tiles de la extension actual del mapa.
        var olMap = this.map_.getMap();
        var mapSize = olMap.getSize();
        var mapExtent = olMap.getView().calculateExtent(mapSize);
        this.vectorLayer_.getSource().loadFeatures(mapExtent, olMap.getView().getResolutionForExtent(mapExtent, mapSize), olMap.getView().getProjection());
        
    }
};

/**
 * Create a ol.tilegrid.XYZ object and adds functions.
 * @param {Object} options ol.tilegrid.XYZ initialization options
 * @private 
 */
cercalia.TilePoiMap.prototype.createTileGrid_ = function (options) {

    var tilegrid = new ol.tilegrid.createXYZ(options);
    
    /**
     * Returns the zoom level according to the resolution.
     * @param {number} resolution Resolution.
     * @return {Number}
     */
    tilegrid.getZForResolution = function (resolution) {
        
        var resolutions = this.getResolutions();
        var n = resolutions.length;
        for (i = 1; i < n; ++i) {
            if (resolutions[i] == resolution) {
                return i;
            } else if (resolutions[i] < resolution) {
                if (resolutions[i - 1] - resolution < resolution - resolutions[i]) {
                    return i - 1;
                } else {
                    return i;
                }
            }
        }
        
        return 0;
    };
    
    /**
     * @param {number} x X.
     * @param {number} y Y.
     * @param {number} resolution Resolution.
     * @param {Boolean} reverseIntersectionPolicy Instead of letting edge intersections go to the higher tile coordinate, let edge intersections go to the lower tile coordinate.
     * @return {ol.TileCoord} tile coordinate.
     */
    tilegrid.getTileCoordForXYAndResolution = function (x, y, resolution, reverseIntersectionPolicy) {
        
        var z = this.getZForResolution(resolution);
        var scale = resolution / this.getResolution(z);
        var origin = this.getOrigin(z);
        var tileSize = this.getTileSize(z);

        var tileCoordX = scale * (x - origin[0]) / (resolution * tileSize);
        var tileCoordY = scale * (y - origin[1]) / (resolution * tileSize);

        if (reverseIntersectionPolicy) {
          tileCoordX = Math.ceil(tileCoordX) - 1;
          tileCoordY = Math.ceil(tileCoordY) - 1;
        } else {
          tileCoordX = Math.floor(tileCoordX);
          tileCoordY = Math.floor(tileCoordY);
        }

        return [z, tileCoordX, tileCoordY];
    };
    
    /**
     * @param {ol.Extent} extent Extent.
     * @param {number} resolution Resolution.
     * @return {Object} Tile range.
     */
    tilegrid.getTileRangeForExtentAndResolution = function (extent, resolution) {
        
        var min = this.getTileCoordForXYAndResolution(extent[0], extent[1], resolution, false);
        var max = this.getTileCoordForXYAndResolution(extent[2], extent[3], resolution, true);
        
        return {
            minX: min[1],
            maxX: max[1],
            minY: min[2],
            maxY: max[2]
        };
    };
    
    /**
     * @param {ol.Extent} extent Extent.
     * @param {number} z Zoom level.
     * @return {Object} Tile range.
     */
    tilegrid.getTileRangeForExtentAndZ = function (extent, z) {
        var resolution = this.getResolution(z);
        return this.getTileRangeForExtentAndResolution(extent, resolution);
    };
    
    /**
     * @param {ol.TileCoord} tileCoord Tile coordinate.
     * @return {ol.Extent} Extent.
     */
    tilegrid.getTileCoordExtent = function(tileCoord) {
        
        var origin = this.getOrigin(tileCoord[0]);
        var resolution = this.getResolution(tileCoord[0]);
        var tileSize = this.getTileSize(tileCoord[0]);
        var minX = origin[0] + tileCoord[1] * tileSize * resolution;
        var minY = origin[1] + tileCoord[2] * tileSize * resolution;
        var maxX = minX + tileSize * resolution;
        var maxY = minY + tileSize * resolution;
        
        return [minX, minY, maxX, maxY];
      };
    
    return tilegrid;
};