Source: api/MapService.js

Retour à la documentation
define([
        "dojo/_base/declare", "dijit/_WidgetBase", "dojo/_base/lang",
        "dojo/_base/array", "dojo/Evented", "esri/request",
        "spw/api/ClusterLayer","spw/api/HeatLayer", "dojo/dom-construct",
        "spw/api/ConfigLoader", "spw/api/MapServiceLayer", "dojo/on", "dojo/dom-style",
        "dojo/sniff", "dojo/aspect", "dojo/Deferred"
    ],
    function(declare, _WidgetBase, lang, array, Evented, esriRequest,
             ClusterLayer, HeatLayer, domConstruct, ConfigLoader, MapServiceLayer, on, domStyle,
             has, aspect, Deferred) {

        var MapService = null;

        /**
         * @class spw.api.MapService
         * @classdesc Classe abstraite à partir de laquelle il est possible de créer des services affichables sur la carte
         */

        MapService = declare("spw.api.MapService", [_WidgetBase, Evented], /** @lends spw.api.MapService.prototype */{

            /**
             * Le serviceId doit être unique dans le fichier de configuration et représente l'identifiant du service.
             * @type String
             */
            serviceId: null,

            /**
             * Le label correspond au nom du service qui sera affiché dans l'application.
             * @type String
             */
            label: null,

            /**
             * Le type de service. AGS_TILED, AGS_DYNAMIC, FEATURE_LAYER, WMS, STATIC_IMAGE.
             * @type String
             */
            type: null,

            /**
             * L'URL vers les données descriptives de ce service.
             * @type String
             */
            metadataUrl:null,

            /**
             * Cet object permet de fixer la taille de la popup si on ne désire pas ouvrir le lien 'metadataUrl' dans une nouvelle fenêtre.
             * @type String
             * @property metadataPopup.width {Number} largeur en pixel
             * @property metadataPopup.height {Number} hauteur en pixel
             */
            metadataPopup:null,

            /**
             * L'url du service.
             * @type String
             */
            url: null,

            /**
             * Active/désactive le caching des images du service côté client.
             * @type Boolean
             */
            disableClientCache: true,

            /**
             * Ce paramètre permet de déterminer si un service est par défaut visible quand celui-ci est ajouté sur la carte.
             * @type Boolean
             */
            visible: false,

            /**
             * Spécifie si le service possède une légende et si celle-ci doit être chargée.
             * @type Boolean
             */
            hasLegend: true,

            /**
             * L'opacité initiale du service [0;100].
             * @type Number
             */
            alpha: 100,

            /**
             * L'opacité pour un fond de plan de type Voyage dans le temps [0;1].
             * @type Number
             */
            alphaTimer: 1,

            /**
             * Ce service est-il sélectionné dans le BaseMapChooser
             * @type {Boolean}
             */
            checked: false,

            /**
             * Cette propriété permet de définir un tableau afin de pouvoir configurer de façon spécifique les couches du service de cartes.
             * @type Array.<Object>
             */
            layers: null,

            /**
             * Permet d'établir l'ordre dans lequel le layer sera dessiné (order supérieur = layer au-dessus)
             * @type {Integer}
             */
            order: null,

            /**
             * Ce paramètre permet de définir si le service est identifiable. Si c'est le cas, toutes les couches de ce service seront identifiables à moins qu'une configuration spécifique soit définie au niveau du tableau 'layers'.
             * @type Boolean
             */
            identifiable: true,

            /**
             * La liste des attributs à ignorer lors de l'affichage des résultats de l'identify.
             * @type Array.<String>
             */
            ignoreAttributes: null,

            /**
             * Permet au service de s'afficher en dessous de son échelle minimum définie en redéfinissant la dernière image disponible. Pour s'activer, le serveur fournissant l'image doit renvoyer une erreur 404.
             * @type Boolean
             */
            resampling: true,

            /**
             * Permet de déterminer si le service doit être ajouté par défaut (lors du chargement du viewer) à la carte.
             * @type Boolean
             */
            toLoad: false,

            /**
             * Echelle maximum du service.
             */
            maxScale: -1,

            /**
             * Echelle minimum du service.
             */
            minScale: -1,

            /**
             * Permet de déterminer si le service est visible dans la toc ou non
             * @type {Boolean}
             */
            inTOC: true,

            /**
             * Indique si le service est un fond de plan.
             * @type Boolean
             */
            isBaseMap: false,

            /**
             * Indique si le service est chargé.
             * @type Boolean
             */
            loaded: false,

            /**
             * Indique si le service est en erreur.
             * @type Boolean
             */
            inError: false,
            errorMessage: null,

            /**
             * Indique si le service a été ajouté à la carte.
             * @type Boolean
             */
            added: false,

            /**
             * Indique si les légendes du services sont chargées.
             * @type Boolean
             */
            legendLoaded: false,

            /**
             * Les objets layers encapsulés. Il s'agit des couches définies dans le service.
             * @type Array.<spw.api.MapServiceLayer>
             */
            mapServiceLayers: null,

            /**
             * Permet de fournir une "where clause" par couche du service. Voir "layerDefinition" Esri.
             * @type Array.<String>
             */
            layerDefinitions: [],

            /**
             * Référence vers l'objet original représentant le service Esri.
             * @type esri.Layer
             */
            layer: null,

            /**
             * Définit les niveaux auquels le service doit s'afficher.
             * @type Array.<Number>
             */
            displayLevels: null,

            /**
             *
             * @type Boolean
             */
            changeVisibility: true,

            /**
             * TODO
             */
            heatLayers: null,

            /**
             * TODO
             */
            clusterLayers: null,

            /**
             * Indique si le service est sécurisé via le système de token ArcGIS Server et s'il doit passer par le serveur de sécurisation pour les différents appels REST.
             * @type Boolean
             */
            tokenSecured: false,

            /**
             * Indique si le service est sécurisé via le réseau (intranet) et s'il doit passer par le serveur de sécurisation pour les différents appels REST.
             * @type Boolean
             */
            networkSecured: false,

            _propertiesToKeep: [
                'serviceId', 'label', 'alpha', 'toLoad', 'visible', 'type', 'metadataUrl', 'metadataPopup',
                'url', 'hasLegend', 'checked', 'order', 'inTOC', 'identifiable', 'expanded', 'layersExpanded', 'description', 'ruleLabel'
            ],

            visibleLayersIds: null,

            visibleLayers: null,

            removable: null,

            /**
             * Permet d'appliquer un filtre niveaux de gris sur le service
             */
            grayscale: null,

            expanded: null,
            layersExpanded: null,

            mapName: null,

            /**
             * Le construteur ne doit pas être appelé directement, il faut passer par la classe MapServiceFactory
             * pour instancier un service
             * @classdesc Classe encapsulant les types standard de "Layers" Esri.
             * @constructs
             * @param {Object} service
             * @param {String|Number} service.serviceId {@link spw.api.MapService#serviceId}
             * @param {String} service.label {@link spw.api.MapService#label}
             * @param {String} service.url  {@link spw.api.MapService#url}
             * @param {Boolean} service.disableClientCache {@link spw.api.MapService#disableClientCache}
             * @param {Boolean} service.visible {@link spw.api.MapService#visible}
             * @param {Boolean} service.hasLegend {@link spw.api.MapService#hasLegend}
             * @param {Number} service.alpha {@link spw.api.MapService#alpha}
             * @param {Array.<Object>} service.layers {@link spw.api.MapService#layers}
             * @param {Boolean} service.identifiable {@link spw.api.MapService#identifiable}
             * @param {Array.<String>} service.ignoreAttributes {@link spw.api.MapService#ignoreAttributes}
             * @param {Boolean} service.resampling {@link spw.api.MapService#resampling}
             * @param {Object} service.wmsParameters {@link spw.api.MapService#wmsParameters}
             * @param {Boolean} service.toLoad {@link spw.api.MapService#toLoad}
             */
            constructor: function(service) {
                this.inError = false;
                lang.mixin(this, {events: MapService.events});
                this.mapServiceLayers = {};

                this.heatLayers = [];
                this.clusterLayers = [];

                service.layersExpanded = service.layersExpanded || {};
            },

            postMixInProperties: function() {
                var viewerConfig = ConfigLoader.getInstance().get('viewer');
                if (this.url && this.url.indexOf(viewerConfig.secureServerUrl) != 0) {
                    if(this.tokenSecured && viewerConfig.secureServerUrl){
                        this.url = viewerConfig.secureServerUrl + "?service=" + this.url;
                    } else if(this.networkSecured && viewerConfig.secureServerUrl){
                        this.url = viewerConfig.secureServerUrl + "?noToken=true&service=" + this.url;
                    }
                }
                if(this.range){
                    if(typeof(this.range[0]) != "undefined" && typeof(this.range[1]) != "undefined"){
                        this.displayLevels = new Array();
                        for(var i=this.range[0];i<this.range[1]+1;i++){
                            this.displayLevels.push(i);
                        }
                    }
                }
                this.createMapLayer();

                this.own(
                    on(this.spwMap, this.spwMap.events.LayerReorder, lang.hitch(this, function(event) {
                        if (this.get('layer') == null || event.layer == null) {
                            return;
                        }

                        if (event.layer.id === this.get('layer').id) {
                            this.order = event.index + 1;
                        }
                    }))
                );
            },

            reset: function(esriMap) {
                this.removeFromMap(esriMap);

                this.loaded = false;
                this.inError = false;
                this.errorMessage = null;
                this.added = false;

                this.createMapLayer();

                if (this.mapDataControllerService) {
                    this.mapDataControllerService = null;
                }
            },

            /**
             * Crée le layer Esri sur base de la configuration du MapService.
             */
            createMapLayer: function(){
                this._enableGrayscaleForIE();

                if(this.layer && !this.get("isBaseMap")){
                    this.layer.on("load", lang.hitch(this, this.layerLoaded));
                }
                else{
                    this.layer.on("load", lang.hitch(this, this.baseMapLayerLoaded));
                }
                if(this.layer) {
                    this.layer.on("error", lang.hitch(this, this.layerError));
                }
            },

            addClusterAndHeatLayers: function(){
                var canvas = document.createElement("canvas");
                if(this.layers && canvas && canvas.getContext){
                    for(var i=0;i<this.layers.length;i++){
                        if(this.layers[i].clusterLayerOptions){

                            var options = lang.mixin({"id":this.serviceId+"_cluster"+this.layers[i].layerId,"fields":[],"serviceURL":this.url+"/"+this.layers[i].layerId,"visible":false, "originService": this, "originLayer": this.layers[i]},this.layers[i].clusterLayerOptions);

                            var clusterLayer = new ClusterLayer(options);

                            var clusterLayerInfo = {"clusterLayer":clusterLayer,"layerId":this.layers[i].layerId,"options":options};

                            this.clusterLayers.push(clusterLayerInfo);
                            this.layers[i].clusterLayer = clusterLayer;
                        }

                        if(this.layers[i].heatLayerOptions){
                            var options = lang.mixin({"id":this.serviceId+"_heat"+this.layers[i].layerId,"serviceURL":this.url+"/"+this.layers[i].layerId,"visible":false, "originService": this, "originLayer": this.layers[i]},this.layers[i].heatLayerOptions);

                            // TODO: new HeatLayer...mais faut réécrire HeatLayer.js alors...
                            var heatLayer = new spw.api.HeatLayer(options);

                            var heatLayerInfo = {"heatLayer":heatLayer,"layerId":this.layers[i].layerId,"options":options};

                            this.heatLayers.push(heatLayerInfo);
                            this.layers[i].heatLayer = heatLayer;
                        }
                    }
                }
            },

            /**
             * Ajoute le service courant à la carte.
             * @param {esri.Map} esriMap l'objet carte d'esri.
             */
            addToMap: function(esriMap) {
                if(!esriMap){
                    return;
                }

                if(this.get('isBaseMap')) {
                    esriMap.addLayer(this.get('layer'), 0);
                    return;
                }

                this.get('layer').visible = this.get('visible') != null ? this.get('visible') : true;

                var handler = null;

                handler = esriMap.on("layer-add-result", lang.hitch(this, function(evt) {
                    if(evt.layer == this.get('layer')){
                        if(handler) {
                            handler.remove();
                        }
                        this.set("added", true);

                        if (this.spwMap) {
                            this.spwMap.mapServiceAddedToMap(this);
                        }
                    }
                }));

                esriMap.addLayer(this.get('layer'));

                for(var i = 0; i < this.clusterLayers.length; i++) {
                    esriMap.addLayer(this.clusterLayers[i].clusterLayer);
                }

                for(var i = 0; i < this.heatLayers.length; i++) {
                    esriMap.addLayer(this.heatLayers[i].heatLayer);
                }
            },

            /**
             * Supprime le service courant de la carte.
             * @param {esri.Map} esriMap l'objet carte d'esri.
             */
            removeFromMap: function(esriMap){
                if (this.get('layer')) {
                    esriMap.removeLayer(this.get('layer'));
                }

                for(var i=0;i<this.clusterLayers.length;i++){
                    esriMap.removeLayer(this.clusterLayers[i].clusterLayer);
                }

                for(var i=0;i<this.heatLayers.length;i++){
                    esriMap.removeLayer(this.heatLayers[i].heatLayer);
                }
            },

            /**
             * Appelée lorsqu'un service est en erreur.
             * @param error L'erreur déclenchée par le service.
             */
            layerError: function(error) {
                if (this.get('inError')) {
                    return;
                }

                if (this.get('loaded')) {
                    if (error.error && error.error.message && error.error.message.indexOf('Unable to load tile') > -1) {
                        return;
                    }
                }

                if (typeof(error) === 'string') {
                    this.set('errorMessage', error);
                }
                this.set('inError', true);
                this.emit(this.events.MapServiceError, this, error);
            },

            /**
             * Appelé lorsque le layer principal est chargé avec succés.
             * @param loadedEvent
             */
            layerLoaded: function(loadedEvent) {
                array.forEach(loadedEvent.layer.layerInfos, lang.hitch(this, function(layerInfo){
                    this._mergeLayerInfo(layerInfo);
                }));

                this._afterLayerLoaded();
            },

            _enableGrayscaleForIE: function() {
//                var proxy = this.spwMap.spwViewer.get('grayscaleProxy');
//
//                // IE ou Edge
//                if (has('ie') < 11) {
//                    if (this.layer.getTileUrl) {
//                        var old = this.layer.getTileUrl;
//                        this.layer.getTileUrl = lang.hitch(this, function() {
//                            var url = old.apply(this.layer, arguments);
//                            return this.grayscale ? (proxy + '?' + url) : url;
//                        });
//                    }
//                    else if (this.layer.getImageUrl) {
//                        var old = this.layer.getImageUrl;
//                        this.layer.getImageUrl = lang.hitch(this, function(e, w, h, cb) {
//                            lang.hitch(this.layer, old)(e, w, h, function(url) {
//                                cb(this.grayscale ? (proxy + '?' + url) : url);
//                            });
//                        });
//                    }
//                }
            },

            /**
             * Appelé après la méthode layerLoaded
             */
            _afterLayerLoaded: function() {
                this.refreshLayersVisibility();
                if(this.maxScale == -1 || this.minScale == -1){
                    this._updateScale();
                } else {
                    this.get('layer').setScaleRange(this.minScale,this.maxScale);
                }

                this.set('loaded', true);
                this.emit(this.events.MapServiceLoaded, this);

                if(this.spwMap){
                    this.spwMap.mapServiceLoaded(this);
                }

                if(this.hasLegend){
                    this.loadLegend();
                }

                if(this.copyright){
                    this.layer.copyright = this.copyright;
                }

                this.set('grayscale', this.grayscale);
            },

            _mergeLayerInfo: function(layerInfo){
                // Get layer configuration if present
                var layerConfig = {};
                if(this.layers) {
                    for(var i = 0; i < this.layers.length; i++) {
                        if(this.layers[i].layerId == layerInfo.id) {
                            layerConfig = this.layers[i];

                            // Apply min and max scales to associated heat and cluster layers
//                        if(typeof(layerInfo.minScale) != "undefined" && typeof(layerInfo.maxScale) != "undefined") {
//                            if(typeof(layerConfig.heatLayer) != "undefined" && typeof(layerConfig.heatLayerOptions.minScale) == "undefined" && typeof(layerConfig.heatLayerOptions.maxScale) == "undefined"){
//                                layerConfig.heatLayer.setMinScale(layerInfo.minScale);
//                                layerConfig.heatLayer.setMaxScale(layerInfo.maxScale);
//                            }
//                            if(typeof(layerConfig.clusterLayer) != "undefined" && typeof(layerConfig.clusterLayerOptions.minScale) == "undefined" && typeof(layerConfig.clusterLayerOptions.maxScale) == "undefined"){
//                                layerConfig.clusterLayer.setMinScale(layerInfo.minScale);
//                                layerConfig.clusterLayer.setMaxScale(layerInfo.maxScale);
//                            }
//                        }
                            break;
                        }
                    }
                }

                if(typeof(layerInfo.identifiable) != 'undefined') {
                    layerConfig.identifiable = layerInfo.identifiable;
                }

                 if (this.visibleLayers) {
                     layerConfig.defaultVisibility = this.visibleLayers.find(function(lId){ return lId == layerInfo.id; }) != undefined;
                 }

                // debugger;
                // if(!this.checkIfLayerIsAlreadyContained(layerInfo) || this.isBaseMap){
                this.mapServiceLayers[layerInfo.id] = new this.MapServiceLayer(lang.mixin(this._layerInfoToLayer(layerInfo),{"identifiable":this.identifiable},layerConfig));
                if(this.mapServiceLayers[layerInfo.parentLayerId] != undefined){
                    this.mapServiceLayers[layerInfo.parentLayerId].addChildLayer(this.mapServiceLayers[layerInfo.id]);
                }
                // }
            },

            getLayerExtent: function(layerId) {
              var def = new Deferred();
              def.resolve(null);
              return def;
            },

            checkIfLayerIsAlreadyContained: function(layer) {
                var alreadyContained = false;
                if(this.mapServiceLayers && Object.keys(this.mapServiceLayers).length > 0){
                    array.forEach(Object.keys(this.mapServiceLayers), lang.hitch(this, function(key){
                        // debugger;
                        if(this.mapServiceLayers[key].name == layer.name){
                            alreadyContained = true;
                        }
                    }))
                }
                return alreadyContained;
            },

            _setGrayscaleAttr: function(value) {
                this.grayscale = value;

                if  (this.layer == null) {
                    return;
                }

                if (this.layer.loaded) {
                    if (this.layer.getMap() == null) {
                        var h = on(this.spwMap, this.spwMap.events.LayerAdd, lang.hitch(this, function(evt) {
                            if (evt.layer === this.layer) {
                                this._setGrayscale(evt.layer.getNode(), value);

//                                if (has('ie') || (window.navigator.appName === 'Netscape' && window.navigator.userAgent.indexOf('Trident') > -1)) {
//                                    this.layer.refresh();
//                                }

                                h.remove();
                            }
                        }));
                    }
                    else {
                        this._setGrayscale(this.layer.getNode(), value);
//                        if (has('ie') || (window.navigator.appName === 'Netscape' && window.navigator.userAgent.indexOf('Trident') > -1)) {
//                            this.layer.refresh();
//                        }
                    }
                }
                else {
                    on.once(this.layer, 'load', lang.hitch(this, function() {
                        if (this.layer.getMap() == null) {
                            var h = on(this.spwMap, this.spwMap.events.LayerAdd, lang.hitch(this, function(evt) {
                                if (evt.layer === this.layer) {
                                    this._setGrayscale(evt.layer.getNode(), value);
//                                    if (has('ie') || (window.navigator.appName === 'Netscape' && window.navigator.userAgent.indexOf('Trident') > -1)) {
//                                        this.layer.refresh();
//                                    }
                                    h.remove();
                                }
                            }));
                        }
                        else {
                            this._setGrayscale(this.layer.getNode(), value);
//                            if (has('ie') || (window.navigator.appName === 'Netscape' && window.navigator.userAgent.indexOf('Trident') > -1)) {
//                                this.layer.refresh();
//                            }
                        }
                    }));
                }
            },

            _setGrayscale: function(node, value) {
                if (node == null) {
                    return;
                }

                if (value == null) {
//                    domStyle.set(node, '-webkit-filter', '');
//                    domStyle.set(node, '-moz-filter', '');
//                    domStyle.set(node, '-ms-filter', '');
//                    domStyle.set(node, '-o-filter', '');
//                    domStyle.set(node, 'filter', '');
                    domStyle.set(node, 'filter', '');
                }
                else {
//                    domStyle.set(node, '-webkit-filter', 'grayscale(' + (value / 100) + ')');
//                    domStyle.set(node, '-moz-filter', 'grayscale(' + (value / 100) + ')');
//                    domStyle.set(node, '-ms-filter', 'grayscale(' + (value / 100) + ')');
//                    domStyle.set(node, '-o-filter', 'grayscale(' + (value / 100) + ')');
                    domStyle.set(node, 'filter', 'grayscale(' + (value / 100) + ')');
//                    domStyle.set(node, 'filter', 'gray');
                }
            },

            /**
             * Appelé lorsque le layer principal d'une baseMap est chargé avec succès.
             * @param loadedEvent
             */
            baseMapLayerLoaded: function(loadedEvent) {
                if(this.copyright) {
                    this.layer.copyright = this.copyright;
                }
                else{
                    this.layer.copyright = this.layer.copyright;
                }

                if(this.maxScale != null && this.maxScale > -1) {
                    this.layer.maxScale=this.maxScale;
                }

                if(this.minScale != null && this.minScale > -1) {
                    this.layer.minScale=this.minScale;
                }
                if (loadedEvent) {
                    array.forEach(loadedEvent.layer.layerInfos, lang.hitch(this, function(layerInfo){
                        this._mergeLayerInfo(layerInfo);
                    }));
                }

                this.refreshLayersVisibility();

                this.set('grayscale', this.grayscale);
            },

            /**
             * Met à jour les échelles du service courant en fonction des sous layers chargés.
             */
            _updateScale: function() {
                var greaterMaxScale = -1;
                var greaterMinScale = -1;

                var mapServiceLayers = this.getParentMapServiceLayers();

                if(mapServiceLayers && mapServiceLayers.length > 0){
                    array.some(mapServiceLayers, lang.hitch(this, function(subLayer){
                        //Compute min scale
                        var minScale = subLayer.get('minScale');

                        if (minScale == null) {
                            minScale = 0;
                        }

                        if(greaterMinScale == -1 || minScale === 0 || (greaterMinScale > 0 && minScale > greaterMinScale)){
                            greaterMinScale = minScale;
                        }

                        //Compute max scale
                        var maxScale = subLayer.get('maxScale');

                        if (maxScale == null) {
                            maxScale = 0;
                        }

                        if(greaterMaxScale == -1 || (maxScale >= 0 && maxScale < greaterMaxScale)) {
                            greaterMaxScale = maxScale;
                        }

                        //If scales are at max possible value, break loop
                        return (greaterMaxScale === 0 && greaterMinScale === 0);
                    }));
                }
                else if(!isNaN(this.layer.minScale) && !isNaN(this.layer.maxScale)) {
                    greaterMaxScale = this.layer.maxScale;
                    greaterMinScale = this.layer.minScale;
                }

                if(this.maxScale != -1){
                    greaterMaxScale = this.maxScale;
                }
                if(this.minScale != -1){
                    greaterMinScale = this.minScale;
                }

                this.set('maxScale', greaterMaxScale);
                this.set('minScale', greaterMinScale);

                this.get('layer').setScaleRange(greaterMinScale,greaterMaxScale);

                if(greaterMinScale == -1  || greaterMaxScale == -1){
                    console.error("Error when computing map service scales.");
                }
            },

            /**
             * Charge les légendes du service.
             */
            loadLegend: function() {
                if (this.get('url') == null || this.get('inError')) {
                    this.legendLoadedSuccess();
                }
                else {
                    this._requestLegend(this.get('url'))
                }
            },

            _requestLegend: function(legendUrl) {
                legendUrl += "/legend";

                esriRequest({
                    url:  legendUrl,
                    content: {
                        f: "json"
                    },
                    handleAs: "json",
                    callbackParamName: "callback"
                }).then(lang.hitch(this, this.legendLoadedSuccess), lang.hitch(this, this.legendLoadedError));
            },

            /**
             * Handler appelé lorsque les légendes du services sont chargées.
             * @param response Object contenant les informations des légendes.
             */
            legendLoadedSuccess: function(response) {
                if (response) {
                    array.forEach(response.layers, lang.hitch(this, function(layer){
                        if(this.mapServiceLayers[layer.layerId] && layer.legend && layer.legend.length > 0){
                            this.mapServiceLayers[layer.layerId].set('legends', layer.legend);
                        }
                    }));
                }

                this.set('legendLoaded', true);
                this.emit(this.events.MapServiceLegendLoaded, this);
            },

            /**
             * Handler appelé lorsque les légendes du services ont rencontré une erreur lors du chargement.
             * @param response Object contenant les informations des légendes.
             */
            legendLoadedError: function(error) {
                console.log("Error loading legend : ", error.message, this);
                this.set('legendLoaded', false);
                this.emit(this.events.MapServiceLegendError, this);
            },

            /**
             * Transforme un objet layerInfo Esri (esri.LayerInfo) en objet nécessaire à la contruction du MapServiceLayer
             * @param {esri.LayerInfo} layerInfo
             * @returns {Object}
             */
            _layerInfoToLayer: function(layerInfo) {
                return {
                    defaultVisibility: layerInfo.defaultVisibility,
                    layerId: layerInfo.id,
                    maxScale: layerInfo.maxScale,
                    minScale: layerInfo.minScale,
                    name: layerInfo.name,
                    inTOC: layerInfo.inTOC == null ? true : layerInfo.inTOC,
                    mapService: this
                };
            },

            _setAlphaAttr: function(alpha) {
                this.alpha = alpha;

                this.get('layer') && this.get('layer').setOpacity(this.alpha/100);
            },

            _setVisibleAttr: function(visible){
                this.visible = visible;
                if(this.visible){
                    this.show();
                } else {
                    this.hide();
                }
            },

            _getMapServiceLayersAttr: function(){
                var layers = [];
                for(key in this.mapServiceLayers){
                    layers.push(this.mapServiceLayers[key]);
                }
                return layers;
            },

            getMapServiceLayer: function(key){
                return this.mapServiceLayers[key];
            },

            getParentMapServiceLayers: function(){
                var layers = [];
                for(key in this.mapServiceLayers){
                    if(!this.mapServiceLayers[key].get('parentLayer')){
                        layers.push(this.mapServiceLayers[key]);
                    }
                }
                return layers;
            },

            _getIdentifiableAttr: function(){
                if(!this.identifiable){
                    for(key in this.mapServiceLayers){
                        if(this.mapServiceLayers[key].get('identifiable')){
                            return true;
                        }
                    }
                    return false;
                }
                return this.identifiable;
            },

            /**
             * Détermine si le service possède des layers identifiables à l'échelle donnée.
             * @param scale Le niveau d'échelle
             * @returns {Boolean}
             */
            hasIdentifiableVisibleLayers: function(scale){

                for(key in this.mapServiceLayers){
                    if(this.mapServiceLayers[key].get('identifiable') && this.mapServiceLayers[key].isVisible() && this.mapServiceLayers[key].visibleAtScale(scale)){
                        return true;
                    }
                }
                return false;
            },

            /**
             * Affiche le service sur la carte
             */
            show: function() {
                this.visible = true;

                if (this.get('layer')) {
                    this.get('layer').show();
                }


                for(var i=0;i<this.clusterLayers.length;i++){
                    this.clusterLayers[i].clusterLayer.show();
                }

                for(var i=0;i<this.heatLayers.length;i++){
                    this.heatLayers[i].heatLayer.show();
                }

                this.emit(this.events.MapServiceVisibilityChanged, this);

                if(this.spwMap){
                    this.spwMap.mapServiceVisibilityChanged(this);
                }
            },

            /**
             * Cache le service de la carte
             */
            hide: function() {
                this.visible = false;

                if (this.get('layer')) {
                    this.get('layer').hide();
                }


                for(var i=0;i<this.clusterLayers.length;i++){
                    this.clusterLayers[i].clusterLayer.hide();
                }

                for(var i=0;i<this.heatLayers.length;i++){
                    this.heatLayers[i].heatLayer.hide();
                }


                this.emit(this.events.MapServiceVisibilityChanged, this);

                if(this.spwMap){
                    this.spwMap.mapServiceVisibilityChanged(this);
                }
            },

            isGraphics: function() {
                var map = this.spwMap.get('esriMap');
                return index = map.graphicsLayerIds.indexOf(this.serviceId) > -1;
            },

            isFirst: function() {
                if (this.get('inError')) {
                    return false;
                }

                var map = this.spwMap.get('esriMap');
                var index = map.layerIds.indexOf(this.serviceId);

                if (index < 0) {
                    index = map.graphicsLayerIds.indexOf(this.serviceId);
                }

                return index === 0;
            },

            isLast: function() {
                if (this.get('inError')) {
                    return false;
                }

                var map = this.spwMap.get('esriMap');
                var index = map.layerIds.indexOf(this.serviceId);

                if (index < 0) {
                    index = map.graphicsLayerIds.indexOf(this.serviceId);
                    return index === (map.graphicsLayerIds.length - 1);
                }

                return index === (map.layerIds.length - 1);
            },

            moveLayer: function(delta){
                if(this.spwMap){
                    var map = this.spwMap.get('esriMap');
                    var index = map.layerIds.indexOf(this.serviceId);

                    if (index < 0) {
                        index = map.graphicsLayerIds.indexOf(this.serviceId);
                    }

                    map.reorderLayer(this.get('layer'), index + delta);
                }
            },

            moveUp: function(){
                this.moveLayer(1);
            },

            moveDown: function(){
                this.moveLayer(-1);
            },

            /**
             * Rafraichit la visibilité des layers
             */
            refreshLayersVisibility: function() {
                if(this.get('layer') && this.get('layer').setVisibleLayers /*&& !this.get('layer').isInstanceOf(WMSLayer)*/){
                    // var visibleLayersIds = [];
                    // var allVisibleLayersIds = [];
                    //
                    // for(var key in this.mapServiceLayers){
                    //     if(this.mapServiceLayers[key].isVisible()) {
                    //         if (this.mapServiceLayers[key].get('subLayers').length == 0
                    //             && (this.mapServiceLayers[key].defaultVisibility)) {
                    //             visibleLayersIds.push(this.mapServiceLayers[key].get('layerId'));
                    //         }
                    //         allVisibleLayersIds.push(this.mapServiceLayers[key].get('layerId'));
                    //     }
                    // }
                    //
                    // if(visibleLayersIds.length > 0){
                    //     this.get('layer').setVisibleLayers(visibleLayersIds);
                    // } else {
                    //     this.get('layer').setVisibleLayers([-1]);
                    // }
                    //
                    // this.visibleLayersIds = allVisibleLayersIds;
                    var visibleLayersIds = [];
                    var allVisibleLayers = [];
                    for (var key in this.mapServiceLayers) {
                        var layer = this.mapServiceLayers[key];
                        var par = layer.parentLayer, layerVisibility = layer.get('visible');
                        while(par && layerVisibility){
                            if(!par.get('visible')){
                                layerVisibility = false;
                                break;
                            } else {
                                par = par.parentLayer;
                            }
                        }
                        if(layer.get('visible')) {
                            allVisibleLayers.push(layer.get('layerId'));
                        }
                        if (layerVisibility) {
                            if(this.groupLayersIdMustBeAddedInURL(layer)) {
                                visibleLayersIds.push(layer.get('layerId'));
                            }
                        }
                    }
                    if(visibleLayersIds.length > 0){
                        this.effectivelyUpdateLayersVisibility(visibleLayersIds);
                    } else {
                        this.effectivelyUpdateLayersVisibility([-1]);
                    }
                    this.visibleLayersIds = allVisibleLayers;
                    this.emit(this.events.MapServiceLayersVisibilityChanged, this);
                    if(this.spwMap){
                        this.spwMap.mapServiceVisibilityChanged(this);
                    }
                }
            },

            groupLayersIdMustBeAddedInURL: function(layer) {
                if (layer.subLayers && layer.subLayers.length > 0) {
                    var atLeastOneSubLayerIsVisible = array.some(layer.subLayers, lang.hitch(this, function(sub) {
                        return sub.get('visible');
                    }));
                    if(!atLeastOneSubLayerIsVisible) {
                        return atLeastOneSubLayerIsVisible;
                    }
                    return !atLeastOneSubLayerIsVisible;
                } else {
                    return true;
                }
            },

            effectivelyUpdateLayersVisibility: function(visibleLayersIds) {
                if (this.refreshTimeout) {
                    clearTimeout(this.refreshTimeout);
                }
                this.refreshTimeout = setTimeout(lang.hitch(this, function() {
                    this.get('layer').setVisibleLayers(visibleLayersIds);
                }, 50))
            },

            getServiceConfig: function(toKeep) {
                toKeep = toKeep || this._propertiesToKeep;

                var cfg = null;

                for (var property in this) {
                    if (toKeep.indexOf(property) < 0) {
                        continue;
                    }

                    cfg = cfg || {};

                    cfg[property] = this[property];
                }

                cfg.layers = [];

                array.forEach(this.get('mapServiceLayers'), lang.hitch(this, function(mapServiceLayer) {
                    cfg.layers.push({
                        layerId: mapServiceLayer.get('layerId'),
                        defaultVisibility: mapServiceLayer.get('visible'),
                        inTOC: mapServiceLayer.get('inTOC')
                    });
                }));

                return cfg;
            },

            /**
             * Obtient les légendes d'un service en fonction de la visibilité de ses couches à une échelle donnée.
             * @param scale Le niveau d'échelle
             * @returns {Object} Mapping entre le label du service et les objets légende des layers.
             */
            getMapServiceLegend: function(scale){
                if(this.visible &&  this.legendLoaded){
                    var mapServiceLegend = {"mapServiceLabel" : this.get('label'), "layers": new Array()};
                    var layersAdded = 0;
                    array.forEach(this.get('MapServiceLayers'),lang.hitch(this,function(mapServiceLayer){
                        var layerLegend = mapServiceLayer.getLayerLegend(scale);
                        if(layerLegend){
                            layersAdded++;
                            mapServiceLegend.layers.push(layerLegend);
                        }
                    }));

                    if(layersAdded > 0)
                        return mapServiceLegend;
                }
                return null;
            },

            getAdditionnalLayers: function(){
                var additionnalLayers = new Array();

                for(var i=0;i<this.clusterLayers.length;i++){
                    additionnalLayers.push(this.clusterLayers[i].clusterLayer);
                }

                for(var i=0;i<this.heatLayers.length;i++){
                    additionnalLayers.push(this.heatLayers[i].heatLayer);
                }

                return additionnalLayers;
            },

            identify: function(options, success, error){
                if (typeof(error) === 'function') {
                    error("Unidentifiable service type", this);
                }
            },

            exportData: function() {
              return {};
            },

            MapServiceLayer: MapServiceLayer
        });

        MapService.events = {
            /**
             * Evènement déclenché lorsque le MapService est chargé.
             * @event spw.api.MapService#MapServiceLoaded
             */
            "MapServiceLoaded" : "MapServiceLoaded",
            /**
             * Evènement déclenché lorsque le MapService est en erreur.
             * @event spw.api.MapService#MapServiceError
             */
            "MapServiceError" : "MapServiceError",
            /**
             * Evènement déclenché lorsque les légendes du MapService sont chargées.
             * @event spw.api.MapService#MapServiceLegendLoaded
             */
            "MapServiceLegendLoaded":"MapServiceLegendLoaded",
            /**
             * Evènement déclenché lorsque les légendes du MapService sont en erreur.
             * @event spw.api.MapService#MapServiceLegendError
             */
            "MapServiceLegendError":"MapServiceLegendError",
            /**
             * Evènement déclenché lorsque la visibilité du MapService change.
             * @event spw.api.MapService#MapServiceVisibilityChanged
             */
            "MapServiceVisibilityChanged":"MapServiceVisibilityChanged",
            /**
             * Evènement déclenché lorsque la visibilité de tous les layers du MapService est changée.
             * @event spw.api.MapService#MapServiceLayersVisibilityChanged
             */
            "MapServiceLayersVisibilityChanged":"MapServiceLayersVisibilityChanged",
            /**
             * Evènement déclenché lorsque la visibilité d'un layer du MapService est changée.
             * @event spw.api.MapService#MapServiceLayerVisibilityChanged
             */
            "MapServiceLayerVisibilityChanged" : "MapServiceLayerVisibilityChanged"
        };

        return MapService;
    });