PageSourceSearch

https://www.inforoute29.fr/mod_turbolead/mod/inforoute/js/scriptOpenLayersMap.js

js inforoute29.fr collected 2026-10-09 03:08:42 UTC 169,234 bytes, 4,659 lines download raw bytes

1var pictoHoverFeatures = [];
2var barreauHoverFeatures = [];
3var zoneHoverFeatures = [];
4var blinkFeatureGeojson = [];
5var timer;
6
7$(document).ready(function (e) {
8    // Affiche un message si le fichier const internet n'est pas présent
9    if (typeof $.turbolead.get('CONST_INTERNET_JS') === 'undefined') {
10        console.log(
11            "Attention ! Le fichier constInternet n'est pas inclus. Certaines fonctionnalités peuvent ne pas fonctionner."
12        );
13    }
14
15    // Modifie les configurations de la couche
16    DDEInitializeOsmRewriteLayerConfig();
17
18    //carte principale
19    if ($('#map').length) {
20        DDEInitializeOsmMasterMap('map');
21    }
22
23    //carte export
24    if ($('#OSMExportMap').length) {
25        DDEInitializeOsmExportMap('OSMExportMap');
26    }
27
28    var urlParams = new URLSearchParams(window.location.search);
29    if (urlParams.has('lat') && urlParams.has('lng') && urlParams.has('id')) {
30        var idRepere = urlParams.get('id');
31        var maxTentative = 10;
32        var i = 0;
33        function checkFeatureLoaded() {
34            setTimeout(function () {
35                i++;
36                if ((feature = getRepereById(idRepere))) {
37                    i = maxTentative;
38                    zoomInEvent(feature);
39                }
40
41                if (i < maxTentative) {
42                    checkFeatureLoaded();
43                }
44            }, 100);
45        }
46        checkFeatureLoaded();
47    }
48});
49
50function zoomInEvent(feature) {
51    if ($(window).width() <= 980) {
52        $('.NavigationTab-item[aria-selected="true"]').click();
53    }
54    let coordiantes = feature.getGeometry().getCoordinates();
55    setViewZoomAndCenter(15, coordiantes[0], coordiantes[1]);
56    var addPopupOnClick = function (event) {
57        if (!event.frameState.animate) {
58            //simule un clique au pixel des coordonées.
59            var pixel = oMap.getPixelFromCoordinate(coordiantes);
60            var evt = {
61                type: 'singleclick',
62                pixel: pixel,
63                originalEvent: {
64                    pixel: pixel,
65                },
66            };
67            oMap.dispatchEvent(evt);
68            oMap.un('postrender', addPopupOnClick);
69        }
70    };
71    oMap.on('postrender', addPopupOnClick);
72}
73
74/**
75 * Réécrit les codes des couches
76 */
77function DDEInitializeOsmRewriteLayerConfig() {
78    // Récupère les calques par type
79    var ddeOsmCalquesByType = $.turbolead.get('calques_by_type');
80    if (typeof ddeOsmCalquesByType === 'undefined') {
81        return;
82    }
83
84    var calquesByTypes = $.turbolead.get('calques_by_type');
85
86    // Réaffecte les types aux calques
87    if (
88        typeof calquesByTypes['repere'] !== 'undefined' &&
89        typeof layerConfig.repere.sublayer !== 'undefined' &&
90        layerConfig.repere.sublayer.length > 0
91    ) {
92        // Récupère les calques
93        let calquesByCode = calquesByTypes['repere'];
94
95        for (var i = 0; i < layerConfig.repere.sublayer.length; i++) {
96            // Récupère le nom et les codes de la couche
97            var subLayerName = layerConfig.repere.sublayer[i].name;
98            var subLayerCodes = layerConfig.repere.sublayer[i].code;
99
100            // Récupère les nouveaux codes de la couche
101            var subLayerNewCodes = DDEGetLayerCodes(subLayerName, subLayerCodes, calquesByCode);
102
103            // Redéfinie les codes de la couche
104            layerConfig.repere.sublayer[i].code = subLayerNewCodes;
105        }
106    }
107
108    // Réaffecte les types aux calques
109    if (
110        typeof calquesByTypes['barreau'] !== 'undefined' &&
111        typeof layerConfig.barreau.sublayer !== 'undefined' &&
112        layerConfig.barreau.sublayer.length > 0
113    ) {
114        // Récupère les calques
115        calquesByCode = calquesByTypes['barreau'];
116
117        for (var i = 0; i < layerConfig.barreau.sublayer.length; i++) {
118            // Récupère le nom et les codes de la couche
119            var subLayerName = layerConfig.barreau.sublayer[i].name;
120            var subLayerCodes = layerConfig.barreau.sublayer[i].code;
121
122            // Récupère les nouveaux codes de la couche
123            var subLayerNewCodes = DDEGetLayerCodes(subLayerName, subLayerCodes, calquesByCode);
124
125            // Redéfinie les codes de la couche
126            layerConfig.barreau.sublayer[i].code = subLayerNewCodes;
127        }
128    }
129
130    // Réaffecte les types aux calques
131    if (
132        typeof calquesByTypes['zone'] !== 'undefined' &&
133        typeof layerConfig.zone.sublayer !== 'undefined' &&
134        layerConfig.zone.sublayer.length > 0
135    ) {
136        // Récupère les calques
137        calquesByCode = calquesByTypes['zone'];
138
139        for (var i = 0; i < layerConfig.zone.sublayer.length; i++) {
140            // Récupère le nom et les codes de la couche
141            var subLayerName = layerConfig.zone.sublayer[i].name;
142            var subLayerCodes = layerConfig.zone.sublayer[i].code;
143
144            // Récupère les nouveaux codes de la couche
145            var subLayerNewCodes = DDEGetLayerCodes(subLayerName, subLayerCodes, calquesByCode);
146
147            // Redéfinie les codes de la couche
148            layerConfig.zone.sublayer[i].code = subLayerNewCodes;
149        }
150    }
151}
152
153/**
154 * Retourne les codes de la couche en prenant en compte le paramétrage du logiciel
155 * @param layerName
156 * @param layerCodes
157 * @param calquesByCode
158 * @returns
159 */
160function DDEGetLayerCodes(layerName, layerCodes, calquesByCode) {
161    var newCodes = [];
162    for (var i = 0; i < layerCodes.length; i++) {
163        // Si le code n'est pas associé à un calque dans le paramétrage,
164        // on laisse le code dans la couche
165        if (typeof calquesByCode[layerCodes[i]] === 'undefined') {
166            newCodes.push(layerCodes[i]);
167        }
168    }
169
170    // Ajoute les codes associés à la couche
171    for (var code in calquesByCode) {
172        if (calquesByCode[code] === layerName) {
173            newCodes.push(code);
174        }
175    }
176
177    return newCodes;
178}
179
180/**
181 * Initialise la carte principale de l'interface
182 *
183 * @param string elementId: identifiant de l'élément du DOm qui va contenir la carte
184 *
185 * @return void
186 */
187function DDEInitializeOsmMasterMap(elementId) {
188    // Commentaire général
189    // Récupère le titre
190    var ddeOsmGeneralCommentTitle = $.turbolead.get('config_generalcomment_title');
191    if (typeof ddeOsmGeneralCommentTitle === 'undefined' || ddeOsmGeneralCommentTitle === '') {
192        // Définie une valeur par défaut si non définie
193        ddeOsmGeneralCommentTitle = 'Conditions de circulation';
194    }
195
196    // Récupère la largeur
197    var ddeOsmGeneralCommentWidth = $.turbolead.get('config_generalcomment_width');
198    if (typeof ddeOsmGeneralCommentWidth === 'undefined' || ddeOsmGeneralCommentWidth === '') {
199        // Définie une valeur par défaut si non définie
200        ddeOsmGeneralCommentWidth = 400;
201    }
202
203    // Récupère la hauteur
204    var ddeOsmGeneralCommentHeight = $.turbolead.get('config_generalcomment_height');
205    if (typeof ddeOsmGeneralCommentHeight === 'undefined' || ddeOsmGeneralCommentHeight === '') {
206        // Définie une valeur par défaut si non définie
207        ddeOsmGeneralCommentHeight = 300;
208    }
209
210    // Récupère le temps d'affichage
211    var ddeOsmGeneralCommentTimer = $.turbolead.get('config_generalcomment_timer');
212    if (typeof ddeOsmGeneralCommentTimer === 'undefined' || ddeOsmGeneralCommentTimer === '') {
213        // Définie une valeur par défaut si non définie
214        ddeOsmGeneralCommentTimer = 10000;
215    } else {
216        // Convertit le temps en secondes en millisecondes
217        ddeOsmGeneralCommentTimer = parseFloat(ddeOsmGeneralCommentTimer) * 1000.0;
218    }
219
220    // Affiche le commentaire general
221    if ($('#commentaire-general').length && $('#commentaire-general').data('affichage') === 1) {
222        showDialogBox(
223            ddeOsmGeneralCommentTitle,
224            ddeOsmGeneralCommentHeight,
225            ddeOsmGeneralCommentWidth,
226            'commentaire-general',
227            '',
228            ddeOsmGeneralCommentTimer
229        );
230    }
231
232    // Configure le bouton d'affichage du commentaire general
233    var commentLink = $('a.BodyHeader-morebtn');
234    if (commentLink.length) {
235        // Redéfinit la taille du popup
236        var parts = commentLink.attr('href').split('?');
237        for (var i = 0; i < parts.length; i++) {
238            var subparts = parts[i].split('&');
239            for (var j = 0; j < subparts.length; j++) {
240                if (subparts[j].substring(0, 6) == 'width=') {
241                    subparts[j] = 'width=' + ddeOsmGeneralCommentWidth;
242                }
243                if (subparts[j].substring(0, 7) == 'height=') {
244                    subparts[j] = 'height=' + ddeOsmGeneralCommentHeight;
245                }
246            }
247            parts[i] = subparts.join('&');
248        }
249        commentLink.attr('href', parts.join('?'));
250        commentLink.data('title', ddeOsmGeneralCommentTitle);
251
252        // Met en place le clignotement du bouton
253        if ($('#commentaire-general').length && $('#commentaire-general').data('clignot') === 1) {
254            function blinkCommentLink() {
255                commentLink.fadeIn(1000).delay(500).fadeOut(1000, blinkCommentLink);
256            }
257            blinkCommentLink();
258        }
259    }
260
261    // Parametres de la carte
262    var oViewOption = getMapParameters(elementId);
263    oViewOption.layers = createGroupeTile(tileConfig);
264
265    // Insertion de la carte
266    DDEAddMap(oViewOption);
267
268    // Insertion des controleurs
269    DDEAddControl(elementId);
270
271    //les calques
272    DDEAddAllLayer(layerConfig);
273
274    //selecteur de maps
275    if ($('#select-maps').length) {
276        $('#select-maps').change(function () {
277            toggleTileVisibilityByName($(this).val());
278        });
279    }
280
281    //creation des overlay
282    var oOverlaySingleClick = initializePopUp('popup', 'popover', true);
283    var oOverlayPointermove = initializePopUp('popup_pointermove', 'popover_pointermove', false);
284
285    //Ajout des popups "singleclick"
286    oMap.on('singleclick', function (evt) {
287        if (evt.dragging) return;
288        var clickOnCluster = manageClusterInteraction(evt);
289        if (!clickOnCluster) {
290            renderPopUp(
291                evt,
292                oOverlaySingleClick.overlay,
293                oOverlaySingleClick.title,
294                oOverlaySingleClick.content,
295                true,
296                oOverlayPointermove.overlay
297            );
298            renderBarreauPictos(evt);
299        }
300    });
301
302    //Ajout des popups "pointermove"
303    oMap.on('pointermove', function (evt) {
304        if (evt.dragging) return;
305        DDEDetectMouseOver(evt);
306        var showPopupPointermove = true;
307        if (oOverlaySingleClick.overlay.getPosition() !== undefined) showPopupPointermove = false;
308        renderPopUp(
309            evt,
310            oOverlayPointermove.overlay,
311            oOverlayPointermove.title,
312            oOverlayPointermove.content,
313            showPopupPointermove,
314            oOverlaySingleClick.overlay
315        );
316        if (showPopupPointermove) {
317            renderBarreauPictos(evt);
318        }
319    });
320
321    //gestion de la sélection d'une déviation
322    if (typeof layerConfig.deviation !== 'undefined') {
323        var layerDeviation = getLayerByName('layer-deviation');
324        var selectDeviation = new ol.interaction.Select({
325            condition: function (evt) {
326                return evt.type === 'singleclick';
327            },
328            layers: [layerDeviation],
329            style: complexeStyle,
330            toggleCondition: ol.events.condition.never,
331            filter: function (feature, layer) {
332                return (
333                    feature instanceof ol.Feature &&
334                    layer instanceof ol.layer.Vector &&
335                    layer.get('name') === 'layer-deviation' &&
336                    feature.get('type') === '_DEVIATION' &&
337                    feature.get('type_deviation') === 'ROUTE_COUPEE'
338                );
339            },
340        });
341        oMap.addInteraction(selectDeviation);
342    }
343
344    //statistiques internes
345    if (
346        typeof internalStatistics !== 'undefined' &&
347        typeof internalStatistics.enable !== 'undefined' &&
348        internalStatistics.enable === true &&
349        typeof internalStatistics.urlFolder !== 'undefined' &&
350        typeof internalStatistics.filePrefix !== 'undefined'
351    ) {
352        var oInternalStatistics = new statistiqueInterne(internalStatistics.urlFolder, internalStatistics.filePrefix);
353
354        //clic et rollover
355        oMap.on(['pointermove', 'singleclick'], function (evt) {
356            if (evt.dragging) return;
357            handleFeatureInteractionForStatistiqueInterne(evt, oInternalStatistics);
358        });
359
360        //fermeture de session
361        window.onbeforeunload = function () {
362            oInternalStatistics.remplirTableauStat('fermeture', '', '');
363            oInternalStatistics.envoyerStatistiques();
364            return undefined;
365        };
366    }
367}
368
369/**
370 * Initialise la carte de l'export des archives
371 *
372 * @param string elementId: identifiant de l'élément du DOm qui va contenir la carte
373 *
374 * @return void
375 */
376function DDEInitializeOsmExportMap(elementId) {
377    //Parametres de la carte
378    var oViewOption = getMapParameters(elementId);
379
380    //Insertion de la carte
381    DDEAddMap(oViewOption);
382
383    //Insertion des controleurs
384    DDEAddControl(elementId);
385
386    //Liste des calques visibles
387    layerConfig['barreau'].visibility =
388        typeof $('#' + elementId).data('barreauvisible') !== 'undefined' &&
389        $('#' + elementId).data('barreauvisible') === 1
390            ? true
391            : false;
392    layerConfig['zone'].visibility =
393        typeof $('#' + elementId).data('zonevisible') !== 'undefined' && $('#' + elementId).data('zonevisible') === 1
394            ? true
395            : false;
396    layerConfig['repere'].visibility =
397        typeof $('#' + elementId).data('reperevisible') !== 'undefined' &&
398        $('#' + elementId).data('reperevisible') === 1
399            ? true
400            : false;
401
402    //Insertion des calques
403    DDEAddAllLayer(layerConfig);
404
405    //Insertion du cadre de slection et zoom
406    DDEAddDragBoxInteraction();
407}
408
409/**
410 * Ajout de l'interaction de cadre de selection
411 *
412 * @param void
413 *
414 * @return void
415 */
416function DDEAddDragBoxInteraction() {
417    var oLayerBarreau = getLayerByName('layer-barreau'),
418        oLayerZone = getLayerByName('layer-zone'),
419        oLayerRepere = getLayerByName('layer-repere');
420
421    //Ajout de l'interaction de selection
422    var oSelect = new ol.interaction.Select({
423        layers: [oLayerBarreau, oLayerZone, oLayerRepere],
424    });
425    oMap.addInteraction(oSelect);
426    var tSelectedFeature = oSelect.getFeatures();
427
428    //Ajout de l'interaction de DragBox
429    var oDragBox = new ol.interaction.DragBox({
430        condition: ol.events.condition.platformModifierKeyOnly,
431    });
432    oMap.addInteraction(oDragBox);
433
434    //Vider la selection au clique sur la carte ou d'une nouvelle selection
435    oDragBox.on('boxstart', function () {
436        tSelectedFeature.clear();
437    });
438
439    //capturer les objets en intersection avec le cadre de selection
440    oDragBox.on('boxend', function () {
441        var tExtent = oDragBox.getGeometry().getExtent();
442
443        //calque des barreaux
444        if (oLayerBarreau !== null && oLayerBarreau.getVisible()) {
445            oLayerBarreau.getSource().forEachFeatureIntersectingExtent(tExtent, function (feature) {
446                if (typeof feature.getId() === 'number') {
447                    tSelectedFeature.push(feature);
448                }
449            });
450        }
451
452        //calque des zones
453        if (oLayerZone !== null && oLayerZone.getVisible()) {
454            oLayerZone.getSource().forEachFeatureIntersectingExtent(tExtent, function (feature) {
455                if (typeof feature.getId() === 'number') {
456                    tSelectedFeature.push(feature);
457                }
458            });
459        }
460
461        //calque des repere
462        if (oLayerRepere !== null && oLayerRepere.getVisible()) {
463            oLayerRepere.getSource().forEachFeatureIntersectingExtent(tExtent, function (feature) {
464                tSelectedFeature.push(feature);
465            });
466        }
467    });
468
469    //changement de la selection
470    tSelectedFeature.on(['add', 'remove'], function () {
471        var tInfo = {
472                barreau: [],
473                zone: [],
474                repere: [],
475            },
476            sType = '';
477
478        tSelectedFeature.getArray().map(function (feature) {
479            sType = feature.get('type');
480            if (sType === '_BARREAU') {
481                tInfo['barreau'].push(feature.get('id_archive'));
482            } else if (sType === '_ZONE') {
483                tInfo['zone'].push(feature.get('id_archive'));
484            } else {
485                tInfo['repere'].push(feature.get('id_archive'));
486            }
487            feature.changed();
488        });
489
490        //affichage
491        $('#exportbarreauselection').val(tInfo['barreau'].join('|'));
492        $('#exportzoneselection').val(tInfo['zone'].join('|'));
493        $('#exportrepereselection').val(tInfo['repere'].join('|'));
494    });
495}
496
497/**
498 * Retourner les paramétres de la carte à partir de l'élément du DOM qui va contenir la carte
499 *
500 * @param string elementId: identifiant de l'élément du DOm qui va contenir la carte
501 *
502 * @return string{}
503 */
504function getMapParameters(elementId) {
505    var sMapId = '#' + elementId,
506        oViewOption = {},
507        centerLongitude =
508            typeof $(sMapId).data('centerlon') !== 'undefined' ? parseFloat($(sMapId).data('centerlon')) : 0,
509        centerLatitude =
510            typeof $(sMapId).data('centerlat') !== 'undefined' ? parseFloat($(sMapId).data('centerlat')) : 0;
511
512    sUrlAjax = String($(sMapId).data('urlajax'));
513
514    oViewOption.target = elementId;
515    oViewOption.center = ol.proj.transform(
516        [centerLongitude, centerLatitude],
517        defaultDataProjection,
518        defaultOsmProjection
519    );
520    oViewOption.extent = getMapExtent(elementId);
521    oViewOption.zoom = typeof $(sMapId).data('zoom') !== 'undefined' ? parseInt($(sMapId).data('zoom'), 10) : 0;
522    oViewOption.minZoom =
523        typeof $(sMapId).data('minzoom') !== 'undefined' ? parseInt($(sMapId).data('minzoom'), 10) : 0;
524    oViewOption.maxZoom =
525        typeof $(sMapId).data('maxzoom') !== 'undefined' ? parseInt($(sMapId).data('maxzoom'), 10) : 28;
526
527    var fResolution =
528            typeof $(sMapId).data('resolution') !== 'undefined' && $(sMapId).data('resolution') !== ''
529                ? parseFloat($(sMapId).data('resolution'))
530                : undefined,
531        tResolutions,
532        sResolutions =
533            typeof $(sMapId).data('resolutions') !== 'undefined' && $(sMapId).data('resolutions') !== ''
534                ? String($(sMapId).data('resolutions'))
535                : undefined;
536    if (fResolution !== undefined && !isNaN(fResolution) && sResolutions !== undefined && sResolutions !== '') {
537        var tResolutionsTmp = sResolutions.split('|');
538        if (Array.isArray(tResolutionsTmp) && tResolutionsTmp.length !== 0) {
539            tResolutions = [];
540            var i = 0,
541                sValue = '',
542                nValue = 0;
543            for (i = 0; i < tResolutionsTmp.length; i++) {
544                sValue = tResolutionsTmp[i].trim();
545                nValue = parseFloat(sValue);
546                if (nValue !== 0 && !isNaN(nValue)) tResolutions.push(nValue);
547            }
548        }
549
550        if (tResolutions.length !== 0) {
551            oViewOption.resolution = fResolution;
552            oViewOption.resolutions = tResolutions;
553        }
554    }
555
556    return oViewOption;
557}
558
559/**
560 * Ajouter une carte OpenLayers
561 *
562 * @param option {object} : paramétres de la vue de la carte
563 * 	option.target {string} : identifiant de l'éélment DOM qui va contenir la carte
564 *  option.center {float[]|undefined} : coordonnées du centre de la carte[longitude,latitude]
565 * 	option.extent {float[]|undefined}  : contraintes sur le centre de la carte
566 *  option.zoom {integer|undefine} : zoom initial de la carte
567 *  option.minZoom {integer|undefine} : zoom minimal de la carte
568 *  option.maxZoom {integer|undefine} : zoom minimal de la carte
569 *  option.resolution {float|undefine} : resolution initial de la carte
570 *  option.resolutions {float[]|undefine} : tableau des resolutions de la carte
571 *
572 *  @return void
573 */
574function DDEAddMap(option) {
575    // Vue de la carte
576    oView = new ol.View({
577        center: option.center,
578        zoom: option.zoom,
579    });
580    //extent
581    if (typeof option.extent !== 'undefined') oView.set('extent', option.extent, true);
582    //minZoom
583    if (typeof option.minZoom !== 'undefined') oView.set('minZoom', option.minZoom, true);
584    //maxZoom
585    if (typeof option.maxZoom !== 'undefined') oView.set('maxZoom', option.maxZoom, true);
586    //resolution
587    if (typeof option.resolution !== 'undefined') oView.setResolution(option.resolution);
588    //resolutions
589    if (typeof option.resolutions !== 'undefined') oView.set('resolutions', option.resolutions, true);
590
591    //La carte
592    oMap = new ol.Map({
593        target: option.target,
594        controls: [],
595        view: oView,
596    });
597    //layers
598    if (typeof option.layers !== 'undefined') {
599        oMap.addLayer(option.layers);
600    } else {
601        //carte OSM par defaut
602        oMap.addLayer(
603            new ol.layer.Tile({
604                source: new ol.source.OSM(),
605            })
606        );
607    }
608}
609
610/**
611 * Création du groupe des cartes (tuile)
612 *
613 * @param layerConf {object} : parametres de configuration du calque
614 * @returns {ol.layer.Group}
615 */
616function createGroupeTile(layerConf) {
617    var layers = [];
618    var conf = {};
619
620    for (var i in layerConf.layers) {
621        conf = layerConf.layers[i];
622        if (conf) {
623            switch (conf.type) {
624                case 'TileWMS':
625                    layers.push(createWmsTile(conf));
626                    break;
627                case 'OSM':
628                    layers.push(createOsmTile(conf));
629                    break;
630                case 'XYZ':
631                    layers.push(createXyzTile(conf));
632                    break;
633                case 'Stamen':
634                    layers.push(createStamenTile(conf));
635                    break;
636                default:
637                    break;
638            }
639        } else {
640            layers.push(createOsmTile(conf));
641        }
642    }
643
644    return new ol.layer.Group({
645        name: layerConf.groupName,
646        label: layerConf.groupLabel,
647        layers: layers,
648    });
649}
650
651/**
652 * création d'un calque de tuile OSM
653 *
654 * @param layerConf {object} : parametres de configuration du calque
655 * @returns {ol.layer.Tile}
656 */
657function createOsmTile(layerConf) {
658    return new ol.layer.Tile({
659        name: layerConf.name,
660        label: layerConf.label,
661        visible: layerConf.visible,
662        source: new ol.source.OSM(),
663    });
664}
665
666/**
667 * création d'un calque de tuile XYZ
668 *
669 * @param layerConf {object} : parametres de configuration du calque
670 * @returns {ol.layer.Tile}
671 */
672function createXyzTile(layerConf) {
673    var sUrl =
674        typeof layerConf.url !== 'undefined' && layerConf.url !== ''
675            ? layerConf.url
676            : 'http://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png';
677    return new ol.layer.Tile({
678        name: layerConf.name,
679        label: layerConf.label,
680        visible: layerConf.visible,
681        source: new ol.source.XYZ({
682            attributions: [
683                new ol.Attribution({
684                    html: layerConf.attribution,
685                }),
686            ],
687            url: sUrl,
688        }),
689    });
690}
691
692/**
693 * création d'un calque de tuile WMS
694 *
695 * @param layerConf {object} : parametres de configuration du calque
696 * @returns {ol.layer.Tile}
697 */
698function createWmsTile(layerConf) {
699    return new ol.layer.Tile({
700        name: layerConf.name,
701        label: layerConf.label,
702        visible: layerConf.visible,
703        source: new ol.source.TileWMS({
704            url: layerConf.url,
705            params: { LAYERS: layerConf.layer, TILED: true },
706            attributions: [
707                new ol.Attribution({
708                    html: 'WMS &copy; ' + layerConf.attribution,
709                }),
710            ],
711        }),
712    });
713}
714
715/**
716 * création d'un calque de tuile Stamen
717 *
718 * @param layerConf {object} : parametres de configuration du calque
719 * @returns {ol.layer.Tile}
720 */
721function createStamenTile(layerConf) {
722    return new ol.layer.Tile({
723        name: layerConf.name,
724        label: layerConf.label,
725        visible: layerConf.visible,
726        source: new ol.source.Stamen({ layer: layerConf.layer }),
727    });
728}
729
730/**
731 *  fonction de geolocalisation
732 */
733function DDEGeolocateMe() {
734    var geolocation = new ol.Geolocation({
735        projection: oView.getProjection(),
736    });
737    geolocation.setTracking(true);
738
739    //on dispose d'une ancienne localisation
740    if (typeof defaultGeolocation !== 'undefined') {
741        oView.setCenter(defaultGeolocation);
742    }
743
744    //gestion d'une bonne gelocalisation
745    geolocation.once('change', function (evt) {
746        var tPostion = geolocation.getPosition();
747        var tExtent = getMapExtent();
748        var sTitre = '',
749            sMessage = '';
750        var bAddParagraph = false;
751        if (ol.extent.containsCoordinate(tExtent, tPostion)) {
752            oView.setCenter(tPostion);
753            defaultGeolocation = tPostion;
754        } else {
755            sTitre = 'Géolocalisation hors périmètre';
756            sMessage = 'La fonction de géolocalisation ne fonctionne pas en dehors du périmètre du département.';
757            bAddParagraph = addParagraphInHiddenDiv('message-geolocation', '', sMessage);
758            if (bAddParagraph) showDialogBox(sTitre, 100, 400, 'message-geolocation', '', 5000);
759        }
760    });
761
762    //gestion d'une erreur de geolocalisation
763    geolocation.on('error', function (error) {
764        var sTitre = 'Erreur de géolocalisation';
765        var sMessage = 'La fonction de géolocalisation a rencontré un problème :<br>"' + error.message + '"';
766        var bAddParagraph = addParagraphInHiddenDiv('message-geolocation', '', sMessage);
767        if (bAddParagraph) showDialogBox(sTitre, 150, 400, 'message-geolocation', '', 5000);
768    });
769}
770
771/**
772 * Ajouter le controleur de geolocalisation
773 */
774function addGeolocationControl() {
775    //défintion d'un espace de nom pour l'application
776    window.app = window.app || {};
777    var app = window.app;
778
779    /**
780     * @constructor
781     * @extends {ol.control.Control}
782     * @param {Object=} opt_options Control options.
783     */
784    app.GeolocationControl = function (opt_options) {
785        var options = opt_options || {};
786
787        var button = document.createElement('button');
788        button.innerHTML = controlConfig.geolocation.innerHTML;
789
790        var handleGeolocation = function (e) {
791            DDEGeolocateMe();
792        };
793
794        button.addEventListener('click', handleGeolocation, false);
795        button.addEventListener('touchstart', handleGeolocation, false);
796
797        var element = document.createElement('div');
798        element.className = 'ol-geolocation ol-unselectable ol-control';
799        element.appendChild(button);
800
801        ol.control.Control.call(this, {
802            element: element,
803            target: options.target,
804        });
805    };
806    ol.inherits(app.GeolocationControl, ol.control.Control);
807}
808
809/**
810 * Ajouter les controleurs de le carte
811 *
812 * @param string elementId: identifiant de l'élément du DOm qui va contenir la carte
813 *
814 * @return void
815 */
816function DDEAddControl(elementId) {
817    elementId = typeof elementId !== 'undefined' ? elementId : 'map';
818    var sMapId = '#' + elementId,
819        sControl = typeof $(sMapId).data('control') !== 'undefined' ? String($(sMapId).data('control')) : '';
820    sControl = sControl.trim();
821    if (sControl !== '') {
822        sControl = sControl.toLowerCase();
823        DDEAddAllControl(sControl);
824    }
825}
826
827/**
828 * Ajouter tous des controleurs à la carte
829 *
830 * @param sControl {string} : liste des controleurs séparés par "|"
831 */
832function DDEAddAllControl(sControl) {
833    var isSecureContext = window.location.protocol == 'https:';
834    var tControl = sControl.split('|');
835    for (i = 0; i < tControl.length; i++) {
836        switch (tControl[i]) {
837            case 'attribution':
838                var OControAttribution = new ol.control.Attribution({
839                    target: controlConfig.attribution.target,
840                    collapsible: controlConfig.attribution.collapsible,
841                    collapsed: controlConfig.attribution.collapsed,
842                    tipLabel: controlConfig.attribution.tipLabel,
843                    label: controlConfig.attribution.label,
844                    collapseLabel: controlConfig.attribution.collapseLabel,
845                });
846                oMap.addControl(OControAttribution);
847                break;
848            case 'zoom':
849                var OControZoom = new ol.control.Zoom({
850                    zoomInTipLabel: controlConfig.zoom.zoomInTipLabel,
851                    zoomOutTipLabel: controlConfig.zoom.zoomOutTipLabel,
852                    target: controlConfig.zoom.target,
853                });
854                oMap.addControl(OControZoom);
855                break;
856            case 'zoomslider':
857                var OControZoomSlider = new ol.control.ZoomSlider();
858                oMap.addControl(OControZoomSlider);
859                break;
860            case 'fullscreen':
861                var OControFullScreen = new ol.control.FullScreen({
862                    tipLabel: controlConfig.fullscreen.tipLabel,
863                });
864                oMap.addControl(OControFullScreen);
865                break;
866            case 'overviewmap':
867                var OControlOverviewMap = new ol.control.OverviewMap({
868                    className: controlConfig.overviewmap.className,
869                    layers: [
870                        new ol.layer.Tile({
871                            source: new ol.source.OSM(),
872                        }),
873                    ],
874                    collapseLabel: controlConfig.overviewmap.collapseLabel,
875                    label: controlConfig.overviewmap.label,
876                    collapsed: controlConfig.overviewmap.collapsed,
877                    tipLabel: controlConfig.overviewmap.tipLabel,
878                    target: controlConfig.overviewmap.target,
879                });
880                oMap.addControl(OControlOverviewMap);
881                break;
882            case 'zoomtoextent':
883                var tExtent = getMapExtent();
884                var zoomtoExtentIcon = document.createElement('span');
885                zoomtoExtentIcon.innerHTML = controlConfig.zoomtoextent.innerHTML;
886                var OControlZoomToExtent = new ol.control.ZoomToExtent({
887                    tipLabel: controlConfig.zoomtoextent.tipLabel,
888                    //label : controlConfig.zoomtoextent.label,
889                    label: zoomtoExtentIcon,
890                    extent: tExtent,
891                    target: controlConfig.zoomtoextent.target,
892                });
893                oMap.addControl(OControlZoomToExtent);
894                break;
895            case 'geolocation':
896                if (isSecureContext) {
897                    addGeolocationControl();
898                    var OGeolocation = new app.GeolocationControl({
899                        target: controlConfig.geolocation.target,
900                    });
901                    oMap.addControl(OGeolocation);
902                }
903                break;
904            case 'search':
905                addSearchControl();
906                var OSearch = new app.SearchControl(controlConfig.search);
907                oMap.addControl(OSearch);
908                break;
909            case 'itinerary':
910                if (isSecureContext) {
911                    addItineraryControl();
912                    var OItinerary = new app.ItineraryControl(controlConfig.itinerary);
913                    oMap.addControl(OItinerary);
914                }
915                break;
916        }
917    }
918}
919
920/**
921 * Renvoi l'etendue de la carte (limites visible de la carte)
922 *
923 * @param string elementId: identifiant de l'élément du DOm qui va contenir la carte
924 *
925 *@return {array}
926 */
927function getMapExtent(elementId) {
928    elementId = typeof elementId !== 'undefined' ? elementId : 'map';
929    var sMapId = '#' + elementId,
930        tExtent;
931    if (
932        typeof $(sMapId).data('minlon') !== 'undefined' &&
933        typeof $(sMapId).data('minlat') !== 'undefined' &&
934        typeof $(sMapId).data('maxlon') !== 'undefined' &&
935        typeof $(sMapId).data('maxlat') !== 'undefined'
936    ) {
937        var nMinLon = parseFloat($(sMapId).data('minlon'));
938        var nMinLat = parseFloat($(sMapId).data('minlat'));
939        var nMaxLon = parseFloat($(sMapId).data('maxlon'));
940        var nMaxLat = parseFloat($(sMapId).data('maxlat'));
941        // Trasnformer extent vers EPSG:3857
942        tExtent = [nMinLon, nMinLat, nMaxLon, nMaxLat];
943        tExtent = ol.extent.applyTransform(tExtent, ol.proj.getTransform('EPSG:4326', 'EPSG:3857'));
944    }
945    return tExtent;
946}
947
948/**
949 * Initialisation de l'arbre des calques
950 *
951 * @param {objet} map: ol.map
952 * @param {string} div: id du div qui va contenir l'arbre des calques
953 * @return {null}
954 */
955function DDEInitLayerTree(map, div) {
956    var elem = DDEBuildLayerTree(map.getLayerGroup());
957    $('#' + div)
958        .empty()
959        .append(elem);
960}
961
962/**
963 * Construit l'arbre des calques d'un calque
964 *
965 * @param {object} layer: layer
966 * @return {String}
967 */
968function DDEBuildLayerTree(layer) {
969    var elem;
970    var name = layer.get('name') ? layer.get('name') : 'Couche sans nom';
971    var label = layer.get('label') ? layer.get('label') : 'Couche sans label';
972    var div =
973        '<li> <label for="' +
974        name +
975        '" class="checkbox form-check-input"> </span>  <input type="checkbox" class="visible" id="' +
976        name +
977        '"> ' +
978        label +
979        ' </label>';
980    if (layer.getLayers) {
981        var sublayersElem = '';
982        var layers = layer.getLayers().getArray(),
983            len = layers.length;
984        for (var i = len - 1; i >= 0; i--) {
985            sublayersElem += DDEBuildLayerTree(layers[i]);
986        }
987        elem = div + ' <ul>' + sublayersElem + '</ul></li>';
988    } else {
989        elem = div + ' </li>';
990    }
991    return elem;
992}
993
994/**
995 * Relier les calques avec des cases a coches
996 *
997 * @param {objet} map: ol.map
998 * @return {null}
999 */
1000function DDEBindLayersInputs(map) {
1001    var oRootLayerGroup = map.getLayerGroup();
1002    var sName = oRootLayerGroup.get('name');
1003    DDEBindInputs('#' + sName, oRootLayerGroup);
1004    var tLayers = map.getLayers().getArray();
1005    tLayers.forEach(function (layer) {
1006        sName = layer.get('name');
1007        DDEBindInputs('#' + sName, layer);
1008        if (layer instanceof ol.layer.Group) {
1009            layer.getLayers().forEach(function (sublayer, j) {
1010                sName = sublayer.get('name');
1011                DDEBindInputs('#' + sName, sublayer);
1012            });
1013        }
1014    });
1015}
1016
1017/**
1018 * relier un calque a un input du dom
1019 *
1020 * @param {string} layerid: id de l'input
1021 * @param {objet} layer: layer
1022 */
1023function DDEBindInputs(layerid, layer) {
1024    new ol.dom.Input($(layerid)[0]).bindTo('checked', layer, 'visible');
1025}
1026
1027/**
1028 * Ajouter un calque à partir d'un fichier kml
1029 *
1030 * @param {object} option : les options du calque
1031 *		{string} option.name: id du layer
1032 * 		{string} option.label: etiquette (affichage) du calque (layer)
1033 * 		{string} option.url: chemin vers le fichier kml
1034 * 		{integer} option.action: numero de l'action à executer (ajax)
1035 * 		{ol.style.Style} option.style: objet de style
1036 * 		{boolean} option.visibility: calque visible ou pas
1037 * 		{boolean} option.protection: calque protégé ou pas (visible uniquement en extranet)
1038 * 		{string} option.protectionContainerId : id du div contenant l'information si l'utlisateur est logge en extranet ou non
1039 * 		{float} option.opacity: opacitie du calque entre 0 et 1
1040 * 		{float} option.minResolution : resolution minimale au-dessus de laquelle le calque est visible
1041 * 		{float} option.maxResolution : resolution maximale au-dessous de laquelle le calque est visible
1042 * 		{string} option.request : façon de récuperer les données (_GEOJSON, LOCAL)
1043 * 		{string} option.dataContainerId : id du div contenant les données si request == '_LOCAL'
1044 * 		{integer} option.order : ordre du calque
1045 *
1046 * @return void|null
1047 */
1048function DDEAddLayerFromKml(option) {
1049    //cas de calque protegé
1050    var oProtectionElement = document.getElementById(option.protectionContainerId);
1051    if (option.protection === true && oProtectionElement !== null) {
1052        var sProtection = oProtectionElement.textContent;
1053        if (sProtection !== '1') {
1054            return null;
1055        }
1056    }
1057
1058    var oLayer = new ol.layer.Vector({
1059        name: option.name,
1060        label: option.label,
1061        source: new ol.source.Vector({
1062            url: option.url,
1063            format: new ol.format.KML({
1064                extractStyles: false,
1065            }),
1066        }),
1067        style: option.style,
1068        visible: option.visibility,
1069        opacity: option.opacity,
1070        minResolution: option.minResolution,
1071        maxResolution: option.maxResolution,
1072    });
1073
1074    addLayerToMap(oLayer, option.order);
1075}
1076
1077/**
1078 * Ajouter un calque à partir d'un fichier GeoJson
1079 *
1080 * @param {object} option : les options du calque
1081 *		{string} option.name: id du layer
1082 * 		{string} option.label: etiquette (affichage) du calque (layer)
1083 * 		{string} option.url: chemin vers le fichier kml
1084 * 		{integer} option.action: numero de l'action à executer (ajax)
1085 * 		{ol.style.Style} option.style: objet de style
1086 * 		{boolean} option.visibility: calque visible ou pas
1087 * 		{boolean} option.protection: calque protégé ou pas (visible uniquement en extranet)
1088 * 		{string} option.protectionContainerId : id du div contenant l'information si l'utlisateur est logge en extranet ou non
1089 * 		{float} option.opacity: opacitie du calque entre 0 et 1
1090 * 		{float} option.minResolution : resolution minimale au-dessus de laquelle le calque est visible
1091 * 		{float} option.maxResolution : resolution maximale au-dessous de laquelle le calque est visible
1092 * 		{string} option.request : façon de récuperer les données (_GEOJSON, LOCAL)
1093 * 		{string} option.dataContainerId : id du div contenant les données si request == '_LOCAL'
1094 * 		{integer} option.order : ordre du calque
1095 *      {boolean} enableClustering : activer ou pas le clustering
1096 *      {object} cluster : objet regroupant les options du clustering
1097 *
1098 * @return void|null
1099 */
1100function DDEAddLayerFromGeoJson(option) {
1101    //cas de calque protegé
1102    var oProtectionElement = document.getElementById(option.protectionContainerId);
1103    if (option.protection === true && oProtectionElement !== null) {
1104        var sProtection = oProtectionElement.textContent;
1105        if (sProtection !== '1') {
1106            return null;
1107        }
1108    }
1109
1110    var sParam = option.url;
1111    if (option.action != '') {
1112        sParam += '?action=' + option.action;
1113    }
1114    if (option.protection === true) {
1115        sParam += '&protect=1';
1116    }
1117
1118    //source standard
1119    var vectorSource = new ol.source.Vector({
1120        url: sParam,
1121        format: new ol.format.GeoJSON(),
1122    });
1123
1124    var source = vectorSource,
1125        style = option.style;
1126
1127    //clustering
1128    if (typeof option.enableClustering !== 'undefined' && option.enableClustering === true) {
1129        var distance = typeof option.cluster.option.distance !== 'undefined' ? option.cluster.option.distance : 20,
1130            source = new ol.source.Cluster({
1131                distance: distance,
1132                source: vectorSource,
1133            });
1134        if (typeof option.cluster.style !== 'undefined') style = option.cluster.style;
1135    }
1136
1137    vectorSource.on('addfeature', function (event) {
1138        var feature = event.feature;
1139        // traitement affichage des recurrenes
1140        var recurrence = feature.get('recurrence_active');
1141        if (recurrence) {
1142            if (!DDETestRecurrence(recurrence)) {
1143                vectorSource.removeFeature(feature);
1144                return null;
1145            }
1146        }
1147
1148        // blinking
1149        var blink = feature.get('clignot');
1150        if (blink === true) {
1151            if (typeof timerBlinking === 'undefined') {
1152                timerBlinking = {};
1153            }
1154            if (typeof blinkFeatureGeojson[option.name] === 'undefined') {
1155                blinkFeatureGeojson[option.name] = [];
1156            }
1157
1158            if (typeof timerBlinking[option.name] === 'undefined') {
1159                timerBlinking[option.name] = setInterval(function () {
1160                    for (var i = 0; i < blinkFeatureGeojson[option.name].length; i++) {
1161                        setBlinking(blinkFeatureGeojson[option.name][i]);
1162                    }
1163                }, 1000);
1164            }
1165            blinkFeatureGeojson[option.name].push(feature);
1166        }
1167    });
1168
1169    var oLayer = new ol.layer.Vector({
1170        name: option.name,
1171        label: option.label,
1172        source: source,
1173        style: function (feature, resolution) {
1174            // Récupère le style de l'option
1175            var styleFeature = style(feature, resolution);
1176
1177            // Retourne vide si l'élément n'est pas visible
1178            if (feature.get('type') == 'CUSTOM_BARREAU' || feature.get('type') == 'CUSTOM_ZONE') {
1179                if (!feature.get('est_visible')) {
1180                    return null;
1181                }
1182            }
1183
1184            return styleFeature;
1185        },
1186        visible: option.visibility,
1187        opacity: option.opacity,
1188        minResolution: option.minResolution,
1189        maxResolution: option.maxResolution,
1190    });
1191
1192    addLayerToMap(oLayer, option.order);
1193}
1194
1195/**
1196 * Mis à jour un calque à partir d'un fichier GeoJson
1197 *
1198 * @param {string} layerName : nom du calque à mettre à jour
1199 *
1200 * @return boolean
1201 */
1202function DDEUpdateLayerFromGeoJson(layerName) {
1203    var vectorLayer = getLayerByName(layerName);
1204    if (vectorLayer === null) return false;
1205
1206    var url = vectorLayer.getSource().getUrl(),
1207        source = new ol.source.Vector({
1208            url: url,
1209            format: new ol.format.GeoJSON(),
1210        });
1211    //mise à jour de la source
1212    vectorLayer.setSource(source);
1213
1214    return true;
1215}
1216
1217/**
1218 * Ajouter un calque à partir d'un GeoJson sous format texte
1219 *
1220 * @param {string} text: données GeoJson sous format texte
1221 * @param {object} option : les options du calque
1222 *		{string} option.name: id du layer
1223 * 		{string} option.label: etiquette (affichage) du calque (layer)
1224 * 		{string} option.url: chemin vers le fichier kml
1225 * 		{integer} option.action: numero de l'action à executer (ajax)
1226 * 		{ol.style.Style} option.style: objet de style
1227 * 		{boolean} option.visibility: calque visible ou pas
1228 * 		{boolean} option.protection: calque protégé ou pas (visible uniquement en extranet)
1229 * 		{string} option.protectionContainerId : id du div contenant l'information si l'utlisateur est logge en extranet ou non
1230 * 		{float} option.opacity: opacitie du calque entre 0 et 1
1231 * 		{float} option.minResolution : resolution minimale au-dessus de laquelle le calque est visible
1232 * 		{float} option.maxResolution : resolution maximale au-dessous de laquelle le calque est visible
1233 * 		{string} option.request : façon de récuperer les données (_GEOJSON, LOCAL)
1234 * 		{string} option.dataContainerId : id du div contenant les données si request == '_LOCAL'
1235 * 		{integer} option.order : ordre du calque
1236 *
1237 * @return void|null
1238 */
1239function DDEAddLayerFromGeoJsonText(text, option) {
1240    //cas de calque protegé
1241    var oProtectionElement = document.getElementById(option.protectionContainerId);
1242    if (option.protection === true && oProtectionElement !== null) {
1243        var sProtection = oProtectionElement.textContent;
1244        if (sProtection !== '1') {
1245            return null;
1246        }
1247    }
1248
1249    //creation des features
1250    var geojsonFormat = new ol.format.GeoJSON();
1251    var features = geojsonFormat.readFeatures(text, { featureProjection: defaultOsmProjection });
1252
1253    var oLayer = new ol.layer.Vector({
1254        name: option.name,
1255        label: option.label,
1256        source: new ol.source.Vector({
1257            features: features,
1258            format: new ol.format.GeoJSON({
1259                defaultDataProjection: defaultOsmProjection,
1260            }),
1261        }),
1262        style: option.style,
1263        visible: option.visibility,
1264        opacity: option.opacity,
1265        minResolution: option.minResolution,
1266        maxResolution: option.maxResolution,
1267    });
1268
1269    addLayerToMap(oLayer, option.order);
1270}
1271
1272/**
1273 * Ajoute un calque spécial à la carte (calque ayant besoin de traitement suplémentaire des données)
1274 *
1275 * @param option : les options du calque spécial
1276 * 		{string} option.name: id du layer
1277 * 		{string} option.label: etiquette (affichage) du calque (layer)
1278 * 		{string} option.url: chemin vers le fichier kml
1279 * 		{integer} option.action: numero de l'action à executer (ajax)
1280 * 		{ol.style.Style} option.style: objet de style
1281 * 		{boolean} option.visibility: calque visible ou pas
1282 * 		{boolean} option.protection: calque protégé ou pas (visible uniquement en extranet)
1283 * 		{string} option.protectionContainerId : id du div contenant l'information si l'utlisateur est logge en extranet ou non
1284 * 		{float} option.opacity: opacitie du calque entre 0 et 1
1285 * 		{float} option.minResolution : resolution minimale au-dessus de laquelle le calque est visible
1286 * 		{float} option.maxResolution : resolution maximale au-dessous de laquelle le calque est visible
1287 * 		{string} option.request : façon de récuperer les données (_GEOJSON, LOCAL)
1288 * 		{string} option.dataContainerId : id du div contenant les données si request == '_LOCAL'
1289 * 		{integer} option.order : ordre du calque
1290 *
1291 * @return void|null
1292 */
1293function DDEAddSpecialLayer(option) {
1294    //cas de calque protegé
1295    var oProtectionElement = document.getElementById(option.protectionContainerId);
1296    if (option.protection === true && oProtectionElement !== null) {
1297        var sProtection = oProtectionElement.textContent;
1298        if (sProtection !== '1') {
1299            return null;
1300        }
1301    }
1302
1303    if (option.request === '_GEOJSON') {
1304        var oParam = { action: option.action };
1305        if (option.protection === true) {
1306            oParam.protect = '1';
1307        }
1308        if (option.date !== undefined && option.date !== '') {
1309            oParam.date = option.date;
1310        }
1311
1312        var drawLayer = function (optionLayer) {
1313            var cloneOption = $.extend({}, optionLayer);
1314            return function (layerData) {
1315                var tFeature = renderDataAsLayer(layerData, cloneOption);
1316
1317                if (
1318                    tFeature !== null &&
1319                    cloneOption.featureDisplayContainerId !== undefined &&
1320                    cloneOption.featureDisplayContainerId !== ''
1321                ) {
1322                    displayFeaturesInHtmlTable(tFeature, cloneOption.featureDisplayContainerId);
1323                }
1324            };
1325        };
1326
1327        $.ajax({
1328            type: 'GET',
1329            url: option.url,
1330            async: true,
1331            data: oParam,
1332            dataType: 'json',
1333        })
1334            .done(drawLayer(option))
1335            .fail(function () {
1336                return null;
1337            });
1338    } else if (option.request === '_LOCAL' && option.dataContainerId !== '') {
1339        var oElement = document.getElementById(option.dataContainerId);
1340        if (oElement !== null) {
1341            var data = null;
1342            try {
1343                data = JSON.parse(oElement.textContent);
1344                renderDataAsLayer(data, option);
1345            } catch (e) {}
1346        }
1347    } else {
1348        return null;
1349    }
1350}
1351
1352/**
1353 * Traiter les données et les ajouter à la carte sous format d'un calque
1354 *
1355 * @param {object} data : données sous format GeoJson
1356 * @param {object} option : les options des données
1357 * 			{string} option.name: id du layer
1358 * 			{string} option.label: etiquette (affichage) du calque (layer)
1359 * 			{ol.style.Style} option.style: objet de style
1360 * 			{boolean} option.visibility: calque visible ou pas
1361 * 			{float} option.opacity: opacitie du calque entre 0 et 1
1362 * 			{float} option.minResolution : resolution minimale au-dessus de laquelle le calque est visible
1363 * 			{float} option.maxResolution : resolution maximale au-dessous de laquelle le calque est visible
1364 * 			{integer} option.order : ordre du calque
1365 *
1366 * @return feature|null
1367 */
1368function renderDataAsLayer(data, option) {
1369    if (
1370        typeof data !== 'undefined' &&
1371        data !== null &&
1372        typeof data.features !== 'undefined' &&
1373        data.features.length > 0
1374    ) {
1375        var sGeometry = data.features[0].geometry.type;
1376        var oGeoJson = new ol.format.GeoJSON({
1377            defaultDataProjection: defaultDataProjection,
1378            geometryName: sGeometry,
1379        });
1380        var tFeature = oGeoJson.readFeatures(data, {
1381            dataProjection: defaultDataProjection,
1382            featureProjection: defaultOsmProjection,
1383        });
1384
1385        if (typeof option.sublayer !== 'undefined' && option.sublayer.length > 0) {
1386            //cas de sous calque
1387            DDEAddSubLayer(option.sublayer, tFeature);
1388        } else {
1389            //ajout d'un seul calque
1390            DDEAddVictorLayer(tFeature, option);
1391        }
1392
1393        return tFeature;
1394    } else {
1395        return null;
1396    }
1397}
1398
1399/**
1400 * Ajouter plusieurs sous calques
1401 *
1402 * @param {json{}} config : configuration des sous calques
1403 * @param {ol.Feature[]} features : tableau de caracteristiques
1404 *
1405 * @return void
1406 */
1407function DDEAddSubLayer(config, features) {
1408    var oConfig = {};
1409    var oFeature = {};
1410
1411    //initialisation de la matrice
1412    var tFeature = [[]];
1413    for (var l = 0; l < config.length; l++) {
1414        tFeature[l] = [];
1415    }
1416
1417    var bFind = true;
1418    for (var i = 0; i < features.length; i++) {
1419        // Vérifie si l'élément fait partie d'un calques
1420        bFind = false;
1421        oFeature = features[i];
1422        for (var j = 0; j < config.length; j++) {
1423            // Vérifie si le code de l'élément correspond à ceux du calque
1424            oConfig = config[j];
1425            for (var k = 0; k < oConfig.code.length; k++) {
1426                // verifie si besoin d'executer une fonctioin spécifique couche-feature
1427                if (typeof oConfig.afterLoadFunction !== 'undefined') {
1428                    oConfig.afterLoadFunction(oFeature);
1429                }
1430
1431                if (oFeature.get('code') == oConfig.code[k]) {
1432                    tFeature[j].push(oFeature);
1433                    bFind = true;
1434                    break;
1435                }
1436            }
1437
1438            if (bFind) {
1439                break;
1440            }
1441        }
1442    }
1443
1444    //ajout des calques
1445    for (l = 0; l < config.length; l++) {
1446        DDEAddVictorLayer(tFeature[l], config[l]);
1447    }
1448}
1449
1450/**
1451 * Ajoute ou mis à jour un calque vectoriel à la carte
1452 *
1453 * @param {ol.Feature[]} features : tableau de caracteristiques
1454 * @param {object} option : options du calque
1455 * 			{string} option.name: id du layer
1456 * 			{string} option.label: etiquette (affichage) du calque (layer)
1457 *			{ol.style.Style} option.style: objet de style
1458 * 			{boolean} option.visibility: calque visible ou pas
1459 * 			{float} option.opacity: opacité du calque entre 0 et 1
1460 * 			{float} option.minResolution : resolution minimale au-dessus de laquelle le calque est visible
1461 * 			{float} option.maxResolution : resolution maximale au-dessous de laquelle le calque est visible
1462 * 			{integer} option.order : ordre du calque
1463 *
1464 * @return void
1465 */
1466function DDEAddVictorLayer(tFeature, option) {
1467    // creation du calque
1468    var isCluster = false;
1469    var vectorLayer = getLayerByName(option.name);
1470    var createLayer = vectorLayer === null ? true : false;
1471    var vectorSource = new ol.source.Vector({
1472        projection: defaultOsmProjection,
1473    });
1474    vectorSource.addFeatures(tFeature);
1475    // Ajoute les pictos des barreaux
1476    if (option.name.substring(0, 13) == 'layer-barreau') {
1477        var data = {
1478            type: 'FeatureCollection',
1479            features: [],
1480        };
1481
1482        for (var i = 0; i < tFeature.length; i++) {
1483            tFeature[i].set('layername', option.name);
1484
1485            // Récupère les pictos
1486            var feature = tFeature[i];
1487            var pictos = feature.get('pictos');
1488
1489            if (typeof pictos != 'undefined') {
1490                for (var j = 0; j < pictos.length; j++) {
1491                    var picto = pictos[j];
1492
1493                    if (picto.visibility == 'AU_SURVOL' || picto.visibility == 'TOUJOURS') {
1494                        var scaling = -1;
1495                        if (picto.visibility == 'TOUJOURS') {
1496                            scaling = picto.icon_scale;
1497                        }
1498
1499                        var hasBlinking = false;
1500                        if (picto.blinking == '_T') {
1501                            hasBlinking = true;
1502                        }
1503
1504                        // Crée le picto pour la carte
1505                        var pictoFeature = {
1506                            type: 'Feature',
1507                            id: 'barreau_picto_' + picto.id_repere,
1508                            geometry: {
1509                                type: 'Point',
1510                                coordinates: picto.geometry.coordinates,
1511                            },
1512                            properties: {
1513                                id: 'barreau_picto_' + picto.id_repere,
1514                                url_icone: picto.icon_url,
1515                                echelle: scaling,
1516                                icon_scale: picto.icon_scale,
1517                                visibility: picto.visibility,
1518                                clignot: hasBlinking,
1519                                nopopup: true,
1520                                zIndex: 9999999999,
1521                                visibility: picto.visibility,
1522                            },
1523                        };
1524
1525                        // Ajoute le picto à la liste
1526                        data.features.push(pictoFeature);
1527                    }
1528                }
1529            }
1530        }
1531
1532        // Ajoute les pictos à la carte
1533        var geojson = new ol.format.GeoJSON({
1534            defaultDataProjection: defaultDataProjection,
1535            geometryName: 'Point',
1536        });
1537        var newFeatures = geojson.readFeatures(data, {
1538            dataProjection: defaultDataProjection,
1539            featureProjection: defaultOsmProjection,
1540        });
1541        vectorSource.addFeatures(newFeatures);
1542    }
1543
1544    if (typeof option.enableClustering !== 'undefined' && option.enableClustering === true) {
1545        //clustering
1546        isCluster = true;
1547
1548        var distance = typeof option.cluster.option.distance !== 'undefined' ? option.cluster.option.distance : 20;
1549        var clusterSource = new ol.source.Cluster({
1550            distance: distance,
1551            source: vectorSource,
1552        });
1553
1554        // Remet à zéro le clignotement des repères en cluster
1555        // Le code responsable de ça se trouve dans script-config.js
1556        if (typeof clusterTimerBlinking !== 'undefined') {
1557            // Récupère les codes du layer
1558            var layerCodeList = String(option.code);
1559            var layerCodes = layerCodeList.split(',');
1560
1561            // Remet à zéro le clignotement de chaque code de la couche
1562            for (var i = 0; i < layerCodes.length; i++) {
1563                if (
1564                    typeof clusterTimerBlinking[layerCodes[i]] !== 'undefined' &&
1565                    typeof clusterTimerBlinking[layerCodes[i]].timer === 'number'
1566                ) {
1567                    clearInterval(clusterTimerBlinking[layerCodes[i]].timer);
1568                    clusterTimerBlinking[layerCodes[i]] = undefined;
1569                }
1570            }
1571        }
1572
1573        if (createLayer) {
1574            vectorLayer = new ol.layer.Vector({
1575                name: option.name,
1576                label: option.label,
1577                source: clusterSource,
1578                style: option.cluster.style,
1579                visible: option.visibility,
1580                opacity: option.opacity,
1581                minResolution: option.minResolution,
1582                maxResolution: option.maxResolution,
1583            });
1584        } else {
1585            vectorLayer.getSource().clear();
1586            vectorLayer.setSource(clusterSource);
1587        }
1588    } else {
1589        if (createLayer) {
1590            vectorLayer = new ol.layer.Vector({
1591                name: option.name,
1592                label: option.label,
1593                source: vectorSource,
1594                style: function (feature, resolution) {
1595                    // Récupère le style de l'option
1596                    var style = option.style(feature, resolution);
1597
1598                    // Redéfinit le zIndex de l'élément s'il est définit
1599                    if (feature.get('zIndex')) {
1600                        for (var i = 0; i < style.length; i++) {
1601                            style[i].setZIndex(feature.get('zIndex'));
1602                        }
1603                    }
1604
1605                    return style;
1606                },
1607                visible: option.visibility,
1608                opacity: option.opacity,
1609                minResolution: option.minResolution,
1610                maxResolution: option.maxResolution,
1611            });
1612        } else {
1613            //mise à jour d'un calque existant
1614            vectorLayer.getSource().clear();
1615            vectorLayer.setSource(vectorSource);
1616        }
1617    }
1618
1619    //ajout du calque a la carte
1620    if (createLayer) {
1621        addLayerToMap(vectorLayer, option.order);
1622    }
1623
1624    //cas des evenements clignotants et recurrents
1625    if (!isCluster) {
1626        var blinkFeature = [];
1627        vectorSource.forEachFeature(function (feature) {
1628            // evenements recurrents
1629            var recurrence = feature.get('recurrence_active');
1630            if (recurrence) {
1631                if (!DDETestRecurrence(recurrence)) {
1632                    vectorSource.removeFeature(feature);
1633                }
1634            }
1635
1636            // evenements clignotants
1637            var blink = feature.get('clignot');
1638            if (blink) {
1639                blinkFeature.push(feature);
1640            }
1641        });
1642
1643        if (typeof timerBlinking !== 'undefined' && typeof timerBlinking[option.name] === 'number') {
1644            clearInterval(timerBlinking[option.name]);
1645            timerBlinking[option.name] = undefined;
1646        }
1647
1648        if (blinkFeature.length !== 0) {
1649            if (typeof timerBlinking === 'undefined') {
1650                timerBlinking = {};
1651            }
1652            timerBlinking[option.name] = setInterval(function () {
1653                for (var i = 0; i < blinkFeature.length; i++) {
1654                    setBlinking(blinkFeature[i]);
1655                }
1656            }, 1000);
1657        }
1658    }
1659}
1660
1661/**
1662 * retourne le message du PMV
1663 *
1664 * @param {object} attributes : objet des proprietes
1665 *
1666 * @return {string[]}
1667 */
1668function getPmvMessage(attributes) {
1669    var tLigne = [
1670        [attributes.ligne0_0, attributes.ligne0_1],
1671        [attributes.ligne1_0, attributes.ligne1_1],
1672        [attributes.ligne2_0, attributes.ligne2_1],
1673        [attributes.ligne3_0, attributes.ligne3_1],
1674    ];
1675    var sDateRegExp = new RegExp('^[ ]*[+]?DT[0-9]{1}[ ]*$');
1676    var sTimeRegExp = new RegExp('^[ ]*[+]?HR[0-9]{1}[ ]*$');
1677    var date = new Date();
1678    var sHtml = ['', ''];
1679    for (var i = 0; i <= 3; i++) {
1680        for (var j = 0; j <= 1; j++) {
1681            if (tLigne[i][j] !== '') {
1682                if (sDateRegExp.test(tLigne[i][j])) {
1683                    sHtml[j] += formaterDate(date);
1684                } else if (sTimeRegExp.test(tLigne[i][j])) {
1685                    sHtml[j] += formaterHeure(date);
1686                } else {
1687                    sHtml[j] += tLigne[i][j];
1688                }
1689
1690                sHtml[j] += '<br/>';
1691            }
1692        }
1693    }
1694
1695    return sHtml;
1696}
1697
1698/**
1699 * Formater la date sous le format jj/mm/aa
1700 *
1701 * @param {object} d : date
1702 *
1703 * @return {string}
1704 */
1705function formaterDate(d) {
1706    var month = d.getMonth();
1707    var day = d.getDate();
1708    var year = d.getFullYear();
1709
1710    //annee
1711    year = year.toString().substr(2, 2);
1712
1713    //mois
1714    month = month + 1;
1715    month = month + '';
1716    if (month.length == 1) {
1717        month = '0' + month;
1718    }
1719
1720    //jour
1721    day = day + '';
1722    if (day.length == 1) {
1723        day = '0' + day;
1724    }
1725
1726    return day + '/' + month + '/' + year;
1727}
1728
1729/**
1730 * Formater l'heure sous le format HH:MM
1731 *
1732 * @param {object} d : date
1733 *
1734 * @return {string}
1735 */
1736function formaterHeure(d) {
1737    var minute = d.getMinutes();
1738    var hour = d.getHours();
1739
1740    //heure
1741    hour = hour + '';
1742    if (hour.length == 1) {
1743        hour = '0' + hour;
1744    }
1745
1746    //minute
1747    minute = minute + '';
1748    if (minute.length == 1) {
1749        minute = '0' + minute;
1750    }
1751
1752    return hour + ':' + minute;
1753}
1754
1755/**
1756 * Initialisation de l'affichage des popup et creation d'un calque overlay qui va contenir les popups
1757 *
1758 * @param {string} divId : identifiant de la DIv contenant le popup
1759 * @param {string} elementId : base de l'identifiant des éléments de la popup
1760 * @param {boolean} autoPan : activer ou pas l'ajustement de la carte pour afficher l'intégralité du popup
1761 *
1762 * @return {object} {
1763 * 						'overlay'	: ol.Overlay,
1764 * 						'title'		: document.getElementById(elementId+'-title'),
1765 * 						'content'	: document.getElementById(elementId+'-content')
1766 * 					}
1767 */
1768function initializePopUp(divId, elementId, autoPan) {
1769    //Ajout du la div du popup en cas de non existance
1770    if ($('#' + divId).length === 0) {
1771        var newdiv = document.createElement('div');
1772        newdiv.setAttribute('id', divId);
1773        newdiv.setAttribute('class', 'ol-popup');
1774        newdiv.innerHTML =
1775            '<a href="#" id="' +
1776            elementId +
1777            '-closer" class="ol-popup-closer"></a> <h3 id="' +
1778            elementId +
1779            '-title" class="popover-title"></h3> <div id="' +
1780            elementId +
1781            '-content" class="popover-content"></div>';
1782        var parentDiv = document.getElementsByClassName('hiddenData')[0];
1783        parentDiv.appendChild(newdiv);
1784    }
1785
1786    var container = document.getElementById(divId);
1787
1788    //bouton close
1789    var closer = document.getElementById(elementId + '-closer');
1790    closer.onclick = function () {
1791        overlay.setPosition(undefined);
1792        closer.blur();
1793        return false;
1794    };
1795
1796    //overlay
1797    var overlay = new ol.Overlay({
1798        id: divId,
1799        element: container,
1800        autoPan: autoPan,
1801        stopEvent: true,
1802        autoPanAnimation: {
1803            duration: 250,
1804        },
1805    });
1806
1807    //ajout de l'ovelay a la carte
1808    oMap.addOverlay(overlay);
1809
1810    return {
1811        overlay: overlay,
1812        title: document.getElementById(elementId + '-title'),
1813        content: document.getElementById(elementId + '-content'),
1814    };
1815}
1816
1817function renderBarreauPictos(evt) {
1818    // Récupère la fonctionnalité
1819    var feature = oMap.forEachFeatureAtPixel(
1820        evt.pixel,
1821        function (feature, layer) {
1822            return DDEFeatureFilter(feature, layer);
1823        },
1824        null,
1825        function (layer) {
1826            return DDELayerFilter(layer);
1827        }
1828    );
1829
1830    // Retourne si aucune fonctionnalité
1831    if (!feature) {
1832        for (var i = 0; i < pictoHoverFeatures.length; i++) {
1833            var properties = pictoHoverFeatures[i].getProperties();
1834            properties.echelle = -1;
1835            pictoHoverFeatures[i].setProperties(properties);
1836        }
1837        pictoHoverFeatures = [];
1838
1839        for (var i = 0; i < barreauHoverFeatures.length; i++) {
1840            var properties = barreauHoverFeatures[i].getProperties();
1841            properties.est_visible = 0;
1842            barreauHoverFeatures[i].setProperties(properties);
1843        }
1844        barreauHoverFeatures = [];
1845
1846        for (var i = 0; i < zoneHoverFeatures.length; i++) {
1847            var properties = zoneHoverFeatures[i].getProperties();
1848            properties.est_visible = 0;
1849            zoneHoverFeatures[i].setProperties(properties);
1850        }
1851        zoneHoverFeatures = [];
1852
1853        // Retourne
1854        return false;
1855    }
1856
1857    properties = feature.getProperties();
1858
1859    if (feature.get('type') == '_BARREAU') {
1860        var pictos = feature.get('pictos');
1861        var layername = feature.get('layername');
1862
1863        if (typeof pictos != 'undefined') {
1864            for (var i = 0; i < pictos.length; i++) {
1865                var picto = pictos[i];
1866
1867                if (picto.visibility == 'AU_SURVOL') {
1868                    // Récupère l'élément
1869                    pictoFeature = getLayerFeatureById(layername, 'barreau_picto_' + picto.id_repere);
1870
1871                    var properties = pictoFeature.getProperties();
1872                    properties.echelle = properties.icon_scale;
1873                    pictoFeature.setProperties(properties);
1874
1875                    pictoHoverFeatures.push(pictoFeature);
1876                }
1877            }
1878        }
1879    } else if (
1880        feature.get('type') == 'CUSTOM_PICTO' ||
1881        feature.get('type') == 'CUSTOM_BARREAU' ||
1882        feature.get('type') == 'CUSTOM_ZONE'
1883    ) {
1884        var pictos = feature.get('pictos') ?? [];
1885        var barreaux = feature.get('barreaux') ?? [];
1886        var zones = feature.get('zones') ?? [];
1887        var layername = 'layer-custom';
1888
1889        for (var i = 0; i < pictos.length; i++) {
1890            var pictoId = pictos[i];
1891
1892            // Récupère l'élément
1893            pictoFeature = getLayerFeatureById(layername, pictoId);
1894
1895            if (pictoFeature.get('visibility') == 'AU_SURVOL') {
1896                var properties = pictoFeature.getProperties();
1897                properties.echelle = properties.icon_scale;
1898                pictoFeature.setProperties(properties);
1899
1900                pictoHoverFeatures.push(pictoFeature);
1901            }
1902        }
1903
1904        for (var i = 0; i < barreaux.length; i++) {
1905            var barreauId = barreaux[i];
1906
1907            // Récupère l'élément
1908            barreauFeature = getLayerFeatureById(layername, barreauId);
1909
1910            if (barreauFeature.get('visibility') == 'AU_SURVOL') {
1911                var properties = barreauFeature.getProperties();
1912                properties.est_visible = 1;
1913                barreauFeature.setProperties(properties);
1914
1915                barreauHoverFeatures.push(barreauFeature);
1916            }
1917        }
1918
1919        for (var i = 0; i < zones.length; i++) {
1920            var zoneId = zones[i];
1921
1922            // Récupère l'élément
1923            zoneFeature = getLayerFeatureById(layername, zoneId);
1924
1925            if (zoneFeature.get('visibility') == 'AU_SURVOL') {
1926                var properties = zoneFeature.getProperties();
1927                properties.est_visible = 1;
1928                zoneFeature.setProperties(properties);
1929
1930                zoneHoverFeatures.push(zoneFeature);
1931            }
1932        }
1933    }
1934}
1935/**
1936 *@returns {Boolean} : currentOnglet
1937 */
1938function getCurrentOnglet(){
1939    let currentOnglet;
1940    $('.NavigationTab li a').each(function() {
1941        if ($(this).attr('aria-selected') === 'true') {
1942            currentOnglet = $(this).attr('aria-controls');
1943            return ;
1944        }
1945    });
1946    return currentOnglet
1947
1948}
1949
1950/**
1951 * Afficher le popup lié à l'élément evt sur le calque overlay avec le titre le content du popup
1952 *
1953 * @param evt {object} evt : événement relier au clique de souris sur la carte (ol.MapBrowserPointerEvent)
1954 * @param overlay {object} : calque overlay (ol.overlay)
1955 * @param title {string} : titre du popup
1956 * @param content {string} : corps du popup
1957 * @param showPopup {boolean} : afficher ou pas le popup
1958 * @param otherOverlay {object[]} : les autres calques overlay (ol.overlay)
1959
1960 * @returns {Boolean} : affichage ou non du popup
1961 */
1962function renderPopUp(evt, overlay, title, content, showPopup, otherOverlay) {
1963    $('.NavigationTab li a').each(function() {
1964        $(this).click(function(e) {
1965            overlay.setPosition(undefined);
1966        })
1967    });
1968    // Retourne directement si on affiche pas le popup
1969    if (!showPopup) {
1970        return false;
1971    }
1972
1973    // Remet à zéro le minuteur des webcams
1974    if (typeof timer === 'number') {
1975        clearInterval(timer);
1976        timer = undefined;
1977        overlay.setPosition(undefined);
1978    }
1979
1980    // Ferme l'autre overlay
1981    if (otherOverlay instanceof ol.Overlay) {
1982        otherOverlay.setPosition(undefined);
1983    }
1984
1985    // Récupère la fonctionnalité
1986    var feature = oMap.forEachFeatureAtPixel(
1987        evt.pixel,
1988        function (feature, layer) {
1989            return DDEFeatureFilter(feature, layer);
1990        },
1991        null,
1992        function (layer) {
1993            return DDELayerFilter(layer);
1994        }
1995    );
1996
1997    // Retourne si aucune fonctionnalité
1998    if (!feature) {
1999        // Ferme l'overlay et le popup
2000        overlay.setPosition(undefined);
2001
2002        // Retourne
2003        return false;
2004    }
2005
2006    // Récupère le type de fonctionnalité
2007    var sType = feature.get('type');
2008
2009    // Cas des liens sans popup
2010    if (sType === '_LIEN' && feature.get('affich_popup') === '_F') {
2011        if (evt.type === 'singleclick') {
2012            // Ferme l'overlay et ouvre l'url
2013            var sUrl = feature.get('link');
2014            overlay.setPosition(undefined);
2015            window.open(sUrl, '_blank');
2016        }
2017
2018        // Retourne
2019        return false;
2020    }
2021
2022    // Actualise le contenu et le titre
2023    var sTemplateTitre = getTemplateOfPopup(sType + '_TITRE', '_TITRE');
2024    var sTitre = Mustache.render(sTemplateTitre, feature.getProperties());
2025    var sTemplateCorps = getTemplateOfPopup(sType, '_CORPS');
2026    var sCorps = '';
2027    var oProperties = feature.getProperties();
2028
2029    // Récupère les propriétés définies pour le site (si la fonction existe)
2030    // La fonction est déclarée pour le moment dans script-config.js
2031    if (typeof getPopupCustomExtraProperties === 'function') {
2032        // Récupère les propriétés
2033        var extraProperties = getPopupCustomExtraProperties(oProperties);
2034
2035        // Ajoute ou remplace les propriétés
2036        for (var key in extraProperties) {
2037            oProperties.key = extraProperties[key];
2038        }
2039    }
2040
2041    // Récupère le contenu du popup selon le type
2042    switch (sType) {
2043        case '_PMV':
2044            var tMessage = getPmvMessage(oProperties);
2045            oProperties.message0 = tMessage[0];
2046            oProperties.message1 = tMessage[1];
2047            sCorps = Mustache.render(sTemplateCorps, oProperties);
2048            break;
2049
2050        case '_WEBCAM':
2051        case '_WEBCAM_VIDEO':
2052            if (oProperties.type_media == 'DEFILEMENT_IMAGE') {
2053                sTemplateCorps = getTemplateOfPopup('_WEBCAM_DEFILEMENT', '_CORPS');
2054            } else if (oProperties.config == 'stream') {
2055                sTemplateCorps = getTemplateOfPopup('_WEBCAM_STREAM', '_CORPS');
2056            } else if (oProperties.config == 'animation') {
2057                sTemplateCorps = getTemplateOfPopup('_WEBCAM_ANIMATION', '_CORPS');
2058            } else {
2059                sTemplateCorps = getTemplateOfPopup(sType, '_CORPS');
2060            }
2061
2062            var sIdWebcam = 'id="webcam_image_' + evt.type + '"',
2063                sWebcamTemplateCorps = sTemplateCorps.replace('id="webcam_image"', sIdWebcam);
2064            sCorps = Mustache.render(sWebcamTemplateCorps, oProperties);
2065            break;
2066
2067        default:
2068            sCorps = Mustache.render(sTemplateCorps, oProperties);
2069            break;
2070    }
2071    title.innerHTML = sTitre;
2072    content.innerHTML = sCorps;
2073
2074    // Positionne l'overlay
2075    var coord = evt.coordinate;
2076    var geometry = feature.getGeometry();
2077    if (geometry instanceof ol.geom.Point) {
2078        coord = geometry.getCoordinates();
2079    }
2080    overlay.setPosition(coord);
2081
2082    // Rafraichie l'image de la webcam
2083    if (sType.substring(0, 7) === '_WEBCAM') {
2084        // mets en place le caroussel
2085        $(document).ready(function () {
2086            var owl = $('.owl-carousel');
2087            // teste si le pluging owlcarousel est chargé
2088            if (owl.length > 0 && typeof owl.owlCarousel === 'function') {
2089                owl.owlCarousel({
2090                    rtl: true,
2091                    margin: 0,
2092                    nav: true,
2093                    loop: true,
2094                    items: 1,
2095                });
2096            }
2097        });
2098
2099        // Récupère le délai
2100        var sDelai = oProperties.delai_rafraichissement || '1000';
2101        var nDelai = parseFloat(sDelai);
2102
2103        // Rafraichie l'image
2104        var bRefreshWebcam = DDERefreshWebcamFeature(feature, evt.type);
2105        // Met en place la mise à jour de l'image
2106        if (bRefreshWebcam === true) {
2107            timer = setInterval(DDERefreshWebcamFeature, nDelai, feature, evt.type);
2108        }
2109    }
2110
2111    // Gérer l'affichage des déviations
2112    if (sType === '_DEVIATION') {
2113        //Si le mode d'affichage de déviation par onglet est activé
2114        if($('button.popupAction[data-onglet]').length){
2115            let deviations = [{ selector: '#divDeviationPL', type: 'DEVIATION_PL' },{ selector: '#divDeviationVL', type: 'DEVIATION_VL' }];
2116            let currentOnglet = getCurrentOnglet();
2117            for (let i = deviations.length - 1; i >= 0; i--) {
2118                let onglets = [];
2119                if ($(deviations[i].selector).length > 0) {
2120                    onglets = $(deviations[i].selector).attr('data-onglet').split(',')    
2121                }
2122                if (!onglets.includes(currentOnglet)) {
2123                    $('#popup_pointermove '+deviations[i].selector).hide();
2124                    $('#popup '+deviations[i].selector).hide();
2125                }
2126            }
2127        }
2128        //mode d'affichage de déviation par onglet n'est pas activé
2129        $('button.popupAction:not([data-onglet])').click(function () {
2130            var sDeviations = $(this).attr('data-idsdeviations');
2131            var aDeviations = sDeviations.split(',');
2132
2133            // Afficher la déviation
2134            manageDeviationsDisplay(aDeviations, true);
2135            overlay.setPosition(undefined);
2136
2137            var hideDeviation = function (evt) {
2138                if (evt.dragging) return;
2139
2140                var pixel = oMap.getEventPixel(evt.originalEvent);
2141                var hit = oMap.forEachFeatureAtPixel(
2142                    pixel,
2143                    function (feature, layer) {
2144                        if (layer.get('name') == 'layer-deviation') {
2145                            return true;
2146                        }
2147                        return false;
2148                    },
2149                    null
2150                );
2151
2152                // Si n'y a pas d'intersection avec avec un élément
2153                if (!hit) {
2154                    // Masquer la déviation
2155                    manageDeviationsDisplay(aDeviations, false);
2156                    // Retirer l'event pour éviter de le cumuler à chaque fois qu'on clique pour voir la déviation
2157                    oMap.un('singleclick', hideDeviation);
2158                }
2159            };
2160            // Event pour gerer la déselection de la déviation
2161            oMap.on('singleclick', hideDeviation);
2162        });
2163
2164        //Mode d'affichage de déviation par onglet est activé
2165        $('button.popupAction[data-onglet]').click(function () {
2166            var sDeviations = $(this).attr('data-idsdeviations');
2167            var aDeviations = sDeviations.split(',');
2168            // Afficher la déviation
2169            manageDeviationsDisplayByOnglet(aDeviations, true);
2170            overlay.setPosition(undefined);
2171            
2172    
2173            var hideDeviation = function (evt) {
2174                if (evt.dragging) return;
2175    
2176                var pixel = oMap.getEventPixel(evt.originalEvent);
2177                var hit = oMap.forEachFeatureAtPixel(
2178                    pixel,
2179                    function (feature, layer) {
2180                        if (layer.get('name') == 'layer-deviation') {
2181                            return true;
2182                        }
2183                        return false;
2184                    },
2185                    null
2186                );
2187    
2188                // Si n'y a pas d'intersection avec avec un élément
2189                if (!hit) {
2190                    // Masquer la déviation
2191                    manageDeviationsDisplayByOnglet(aDeviations, false);
2192                    // Retirer l'event pour éviter de le cumuler à chaque fois qu'on clique pour voir la déviation
2193                    oMap.un('singleclick', hideDeviation);
2194                }
2195            };
2196            // Event pour gerer la déselection de la déviation
2197            oMap.on('singleclick', hideDeviation);
2198
2199            $('.NavigationTab li a').each(function() {
2200                $(this).click(function(e) {
2201                    manageDeviationsDisplayByOnglet(aDeviations, false);
2202                    overlay.setPosition(undefined);
2203                })
2204            });
2205
2206        });
2207
2208    }
2209
2210    return true;
2211}
2212
2213/**
2214 * Met à jour l'image de la webcam
2215 * @param feature
2216 * @param eventType
2217 * @returns
2218 */
2219function DDERefreshWebcamFeature(feature, eventType) {
2220    // Retourne si aucun élément
2221    if (!feature) {
2222        return false;
2223    }
2224
2225    // Récupère les propriétés
2226    var properties = feature.getProperties();
2227    var url = properties.url_script || '';
2228    var webcamType = properties.config || '';
2229    var directoryPath = properties.path_dossier || '';
2230    var directoryUrl = properties.url_dossier || '';
2231    var idWebcam = properties.id_webcam || '0';
2232    var webcamMediaType = properties.type_media || '';
2233
2234    var idDivWebcam = 'webcam_image_' + eventType;
2235    idElementWebcam = $('#' + idDivWebcam).data('id');
2236    // Vérifie que tout est bien renseigné
2237    if (url === '' || webcamType === '' || directoryPath === '' || idWebcam === '0') {
2238        return false;
2239    }
2240
2241    // pas de rafraichissement pour les vidéo stream
2242    if (properties.config == 'stream' || properties.config == 'animation') {
2243        return false;
2244    }
2245
2246    // Récupère le délai
2247    var delay = properties.delai_rafraichissement || '1000';
2248    delay = parseFloat(delay);
2249
2250    // Vérifie la date de dernière récupération
2251    var currentTimeStamp = Date.now();
2252    if (properties.lastCallTimeStamp !== undefined && properties.lastCallTimeStamp + delay > currentTimeStamp) {
2253        // Met à jour l'image
2254        DDERefreshWebcamFeatureSpan(feature, eventType);
2255        return true;
2256    }
2257
2258    // Garde la date de dernier appel
2259    feature.setProperties({ lastCallTimeStamp: currentTimeStamp });
2260
2261    // Récupère l'image
2262    var param = {
2263        type: 'POST',
2264        url: url,
2265        async: true,
2266        crossDomain: true,
2267        data: {
2268            type: webcamType,
2269            dossier: directoryPath,
2270            id: idWebcam,
2271            type_media: webcamMediaType,
2272        },
2273        dataType: 'json',
2274    };
2275
2276    // Met à jour l'image
2277    $.ajax(param).done(function (output) {
2278        if (output.result === 'OK') {
2279            // Garde les infos
2280            feature.setProperties({
2281                directoryUrl: directoryUrl,
2282                last5images: output.last_files_name,
2283                last5imagesIndex: 0,
2284                lastCallImageDate: output.strDate,
2285                lastCallImageSource: directoryUrl + output.filename,
2286                lastCallId: output.id,
2287            });
2288
2289            // Met à jour l'image
2290            DDERefreshWebcamFeatureSpan(feature, eventType);
2291        }
2292    });
2293
2294    return true;
2295}
2296
2297/**
2298 * Met à jour l'image de la webcam dans le popup
2299 * @param feature
2300 * @param eventType
2301 * @returns
2302 */
2303function DDERefreshWebcamFeatureSpan(feature, eventType) {
2304    // Récupère les identifiant des éléments
2305    var idDivWebcam = 'webcam_image_' + eventType;
2306    var idElementWebcam = $('#' + idDivWebcam).data('id');
2307
2308    // Récupère les propriétés
2309    var properties = feature.getProperties();
2310    // Met à jour l'image
2311    if (
2312        properties.lastCallImageDate !== undefined &&
2313        properties.lastCallImageSource !== undefined &&
2314        properties.lastCallId !== undefined &&
2315        properties.lastCallId == idElementWebcam
2316    ) {
2317        $('div[id=' + idDivWebcam + '] span.webcamDate').html(properties.lastCallImageDate);
2318
2319        var link = $('div[id=' + idDivWebcam + '] a[data-webcam-link]');
2320        if (link.length > 0) {
2321            link.attr('href', properties.lastCallImageSource);
2322        }
2323
2324        if (properties.type_media == 'DEFILEMENT_IMAGE') {
2325            for (var i = 0; i < 5; i++) {
2326                if (properties.last5images[i] === undefined) {
2327                    var val = properties.last5images[0];
2328                } else {
2329                    var val = properties.last5images[i];
2330                }
2331
2332                $('div[id^="' + idDivWebcam + '"] div[data-id="item-' + i + '"] img').attr(
2333                    'src',
2334                    properties.directoryUrl + val
2335                );
2336            }
2337        } else if (properties.type_media == 'IMAGE') {
2338            $('div[id^=' + idDivWebcam + '] img').attr('src', properties.lastCallImageSource + '?' + new Date().getTime());
2339        } else {
2340            var extension = properties.lastCallImageSource.split('.').pop();
2341
2342            $('div[id^=' + idDivWebcam + '] img').hide();
2343            $('div[id^=' + idDivWebcam + '] video').hide();
2344            if (extension == 'mp4' || extension == 'webm') {
2345                $('div[id^=' + idDivWebcam + '] video').show();
2346                $('div[id^=' + idDivWebcam + '] video source').attr('src', properties.lastCallImageSource);
2347                $('div[id^=' + idDivWebcam + '] video source').attr('type', 'video/' + extension);
2348            } else {
2349                $('div[id^=' + idDivWebcam + '] img').show();
2350                $('div[id^=' + idDivWebcam + '] img').attr('src', properties.lastCallImageSource + '?' + new Date().getTime());
2351            }
2352        }
2353    }
2354}
2355
2356/**
2357 * Retourner le template du poup correspondant au type en paramétre
2358 *
2359 * @param {string} type : nom du type de template
2360 * @param {string} position : emplacement dans le popup (_TITRE/_CORPS)
2361 *
2362 * @retrun {string}
2363 */
2364function getTemplateOfPopup(type, position) {
2365    var sTemplate = typeof templatePopup[type] !== 'undefined' ? templatePopup[type] : undefined;
2366
2367    if (sTemplate === undefined) {
2368        var sErreur = 'Erreur: le template "{{type}}" est indéfini.';
2369        sTemplate = sErreur;
2370        if (position === '_TITRE') {
2371            if (typeof templatePopup._TITRE !== 'undefined') {
2372                sTemplate = templatePopup._TITRE;
2373            }
2374        } else if (position === '_CORPS') {
2375            //cas particulier des reperes
2376            if (
2377                typeof templatePopup._REPERE !== 'undefined' &&
2378                (type === '_REPEREHEBDO' ||
2379                    type === '_REPEREANNUEL' ||
2380                    type === '_REPEREPERMANENT' ||
2381                    type === '_REPERETIPI')
2382            ) {
2383                sTemplate = templatePopup._REPERE;
2384            }
2385
2386            // Custom
2387            if (typeof templatePopup._CUSTOM_PICTO !== 'undefined' && type === 'CUSTOM_PICTO') {
2388                sTemplate = templatePopup._CUSTOM_PICTO;
2389            } else if (typeof templatePopup._CUSTOM_BARREAU !== 'undefined' && type === 'CUSTOM_BARREAU') {
2390                sTemplate = templatePopup._CUSTOM_BARREAU;
2391            } else if (typeof templatePopup._CUSTOM_ZONE !== 'undefined' && type === 'CUSTOM_ZONE') {
2392                sTemplate = templatePopup._CUSTOM_ZONE;
2393            } else if (
2394                typeof templatePopup.CUSTOM !== 'undefined' &&
2395                (type === 'CUSTOM_PICTO' || type === 'CUSTOM_BARREAU' || type === 'CUSTOM_ZONE')
2396            ) {
2397                sTemplate = templatePopup.CUSTOM;
2398            } else if (
2399                typeof templatePopup._CUSTOM !== 'undefined' &&
2400                (type === 'CUSTOM_PICTO' || type === 'CUSTOM_BARREAU' || type === 'CUSTOM_ZONE')
2401            ) {
2402                sTemplate = templatePopup._CUSTOM;
2403            }
2404
2405            if (sTemplate === sErreur && typeof templatePopup._DEFAUT !== 'undefined') {
2406                sTemplate = templatePopup._DEFAUT;
2407            }
2408        } else {
2409            sTemplate = 'Erreur: la position "' + position + '" est indéfini.';
2410        }
2411    }
2412
2413    return sTemplate;
2414}
2415
2416/**
2417 * Filtre les calques a associer a un popup ou pas
2418 * @param {object} layer : ol.layer objet calque a tester
2419 * @retun {boolean}
2420 */
2421function DDELayerFilter(layer) {
2422    var sName = layer.get('name');
2423    if (sName !== undefined) {
2424        var oConfig = {};
2425        for (var i in layerConfig) {
2426            oConfig = layerConfig[i];
2427            if (oConfig.name == sName && oConfig.showpopup) return true;
2428            //cas des sous calques de reperes ou barreaux
2429            if (
2430                (oConfig.name == 'layer-barreau' || oConfig.name == 'layer-repere') &&
2431                oConfig.sublayer !== undefined &&
2432                oConfig.sublayer.length > 0
2433            ) {
2434                var oConfigSubLayer = {};
2435                for (var j in oConfig.sublayer) {
2436                    oConfigSubLayer = oConfig.sublayer[j];
2437                    if (oConfigSubLayer.name == sName && oConfigSubLayer.showpopup) return true;
2438                }
2439            }
2440        }
2441    }
2442    return false;
2443}
2444
2445/**
2446 * Filtre les objets d'un calque a associer a un popup ou pas
2447 *
2448 * @param {object} feature : ol.feature objet contenu dans un calque
2449 * @param {object} layer : ol.layer objet calque a tester
2450 *
2451 * @retun ol.feature|false
2452 */
2453function DDEFeatureFilter(feature, layer) {
2454    if (feature.get('nopopup')) {
2455        return false;
2456    }
2457
2458    var tFeature = feature.get('features');
2459    if (tFeature === undefined) {
2460        return feature;
2461    } else {
2462        if (tFeature.length === 1) {
2463            //cas des cluster a un evt
2464            return tFeature[0];
2465        }
2466    }
2467
2468    return false;
2469}
2470
2471/**
2472 * Changer la forme du curseur au survol d'une feature
2473 *
2474 * @param evt
2475 *
2476 * @return void
2477 */
2478function DDEDetectMouseOver(evt) {
2479    var mapId = document.getElementById('map'),
2480        pixel = oMap.getEventPixel(evt.originalEvent);
2481    var hit = oMap.forEachFeatureAtPixel(
2482        pixel,
2483        function (feature, layer) {
2484            return true;
2485        },
2486        null,
2487        function (layer) {
2488            return DDELayerFilter(layer);
2489        }
2490    );
2491    if (hit) {
2492        mapId.style.cursor = 'pointer';
2493    } else {
2494        mapId.style.cursor = '';
2495    }
2496}
2497
2498/**
2499 * Retourner la configuration d'un calque
2500 *
2501 * @param {string} config : nom de la configuration
2502 *
2503 * @return object|null
2504 */
2505function getLayerConfiguration(config) {
2506    if (sUrlAjax !== 'undefined' && sUrlAjax !== '') {
2507        var option = {};
2508
2509        option.request = typeof config.request !== 'undefined' ? config.request : null;
2510        option.dataContainerId = typeof config.dataContainerId !== 'undefined' ? config.dataContainerId : '';
2511        option.name = typeof config.name !== 'undefined' ? config.name : 'layer-' + i;
2512        option.label = typeof config.label !== 'undefined' ? config.label : 'Layer ' + i;
2513        option.url = typeof config.url !== 'undefined' && config.url !== 'default' ? config.url : sUrlAjax;
2514        option.action = typeof config.action !== 'undefined' ? config.action : null;
2515        option.style = typeof config.style !== 'undefined' ? config.style : undefined;
2516        option.visibility = typeof config.visibility !== 'undefined' ? config.visibility : false;
2517        option.protection = typeof config.protection !== 'undefined' ? config.protection : false;
2518        option.protectionContainerId =
2519            typeof config.protectionContainerId !== 'undefined' ? config.protectionContainerId : '';
2520        option.showpopup = typeof config.showpopup !== 'undefined' ? config.showpopup : false;
2521        option.opacity = typeof config.opacity !== 'undefined' ? config.opacity : 1;
2522        option.minResolution = typeof config.minResolution !== 'undefined' ? config.minResolution : undefined;
2523        option.maxResolution = typeof config.maxResolution !== 'undefined' ? config.maxResolution : undefined;
2524        option.order = typeof config.order !== 'undefined' ? config.order : -1;
2525        option.autoUpdate = typeof config.autoUpdate !== 'undefined' ? config.autoUpdate : false;
2526        option.autoUpdateInterval = typeof config.autoUpdateInterval !== 'undefined' ? config.autoUpdateInterval : 0;
2527        option.featureDisplayContainerId =
2528            typeof config.featureDisplayContainerId !== 'undefined' ? config.featureDisplayContainerId : '';
2529        option.sublayer = typeof config.sublayer !== 'undefined' ? config.sublayer : [];
2530        option.enableClustering = typeof config.enableClustering !== 'undefined' ? config.enableClustering : false;
2531        option.cluster = typeof config.cluster !== 'undefined' ? config.cluster : {};
2532
2533        return option;
2534    }
2535
2536    return null;
2537}
2538
2539/**
2540 * Ajoute tous les calques à la carte
2541 *
2542 * @param {object} config : configuration des calques
2543 *
2544 * @return void
2545 */
2546function DDEAddAllLayer(config) {
2547    var option = null;
2548    for (var i in config) {
2549        option = getLayerConfiguration(config[i]);
2550        if (option === null) continue;
2551
2552        if (option.sublayer.length > 0) {
2553            DDEAddSpecialLayer(option);
2554            var sLoggedExtranet =
2555                document.getElementById('loggued-extranet') !== null
2556                    ? document.getElementById('loggued-extranet').textContent
2557                    : '0';
2558            var bEnableAutomaticUpdate = false;
2559            if (typeof ENABLE_AUTOMATIC_UPDATE !== 'undefined') bEnableAutomaticUpdate = ENABLE_AUTOMATIC_UPDATE;
2560            if (
2561                bEnableAutomaticUpdate === true &&
2562                option.autoUpdate === true &&
2563                (sLoggedExtranet === '1' || option.name === 'layer-trafic')
2564            ) {
2565                //mise a jour automatique des calques
2566                autoUpdateSpecialLayer(i, option.url, option.autoUpdateInterval);
2567            }
2568        } else {
2569            switch (option.request) {
2570                case '_LOCAL':
2571                    if (option.dataContainerId !== '') {
2572                        var oElement = document.getElementById(option.dataContainerId);
2573                        if (oElement !== null) {
2574                            var data = null;
2575                            try {
2576                                data = JSON.parse(oElement.textContent);
2577                                DDEAddLayerFromGeoJsonText(data, option);
2578                            } catch (e) {}
2579                        }
2580                    }
2581                    break;
2582                case '_KML':
2583                    DDEAddLayerFromKml(option);
2584                    break;
2585                default:
2586                    DDEAddLayerFromGeoJson(option);
2587                    var sLoggedExtranet =
2588                        document.getElementById('loggued-extranet') !== null
2589                            ? document.getElementById('loggued-extranet').textContent
2590                            : '0';
2591                    var bEnableAutomaticUpdate = false;
2592                    if (typeof ENABLE_AUTOMATIC_UPDATE !== 'undefined')
2593                        bEnableAutomaticUpdate = ENABLE_AUTOMATIC_UPDATE;
2594                    if (
2595                        bEnableAutomaticUpdate === true &&
2596                        option.autoUpdate === true &&
2597                        (sLoggedExtranet === '1' || option.name === 'layer-trafic')
2598                    ) {
2599                        //mise a jour automatique des calques
2600                        autoUpdateLayerFromGeoJson(option.name, option.autoUpdateInterval);
2601                    }
2602            }
2603        }
2604    }
2605}
2606
2607/**
2608 * Ajouter un calque à la carte à un index (ordre) donné
2609 *
2610 * @param {ol.layer} layer : calque
2611 * @param {integer} index : numéro d'ordre
2612 *
2613 * @return void
2614 */
2615function addLayerToMap(layer, index) {
2616    // Définie la profondeur du calque
2617    layer.setZIndex(index);
2618
2619    // Ajoute le calque
2620    oMap.addLayer(layer);
2621}
2622
2623/**
2624 * Recupérer un calque
2625 *
2626 * @param layerName {string} nom du calque
2627 * @returns {ol.layer.Layer}
2628 */
2629function getLayerByName(layerName) {
2630    var layer = null;
2631    oMap.getLayers().forEach(function (lyr) {
2632        if (typeof lyr === 'object' && lyr.get('name') == layerName) {
2633            layer = lyr;
2634        }
2635    });
2636
2637    return layer;
2638}
2639
2640/**
2641 * Recupérer un groupe de calques
2642 *
2643 * @param groupName {string} : nom du groupe de calques
2644 * @returns {ol.layer.Group}
2645 */
2646function getLayerGroupByName(groupName) {
2647    var group = null;
2648    var elementName = '';
2649    oMap.getLayers().forEach(function (elem) {
2650        if (typeof elem === 'object') {
2651            elementName = elem.get('name');
2652            if (elementName == groupName) {
2653                group = elem;
2654            }
2655        }
2656    });
2657
2658    return group;
2659}
2660
2661/**
2662 * Modifie la visibilité d'un calque
2663 *
2664 * @param layerName {string} : nom du calque
2665 * @param visible {boolean} : calque visible ou non
2666 * @returns {Boolean}
2667 */
2668function setLayerVisibilityByName(layerName, visible) {
2669    var layer;
2670    layer = getLayerByName(layerName);
2671    if (layer !== null) {
2672        layer.setVisible(visible);
2673        return true;
2674    }
2675
2676    return false;
2677}
2678
2679/**
2680 * Bascule la visibilité d'un calque de tuile (carte)
2681 *
2682 * @param layerName {string} : nom du calque
2683 * @returns {Boolean}
2684 */
2685function toggleTileVisibilityByName(layerName) {
2686    var layer = null;
2687    var visible = false;
2688    var result = false;
2689    var elementName = '';
2690    var groupName = tileConfig.groupName;
2691    var defaultLayer = tileConfig.defaultLayerName;
2692
2693    layer = getLayerGroupByName(groupName);
2694
2695    if (layer !== null) {
2696        layer.getLayers().forEach(function (elem) {
2697            elementName = elem.get('name');
2698            if (elementName === layerName && elementName !== defaultLayer) {
2699                visible = elem.getVisible();
2700                elem.setVisible(!visible);
2701                result = true;
2702            } else if (elementName !== defaultLayer) {
2703                elem.setVisible(false);
2704            }
2705        });
2706    }
2707
2708    return result;
2709}
2710
2711/**
2712 * Bascule la visibilité d'un calque
2713 *
2714 * exemple (repere cligneton) : setInterval(function(){toggleLayerVisibilityByName('layer-repere')},1000)
2715 *
2716 * @param layerName {string} : nom du calque
2717 * @returns {Boolean}
2718 */
2719function toggleLayerVisibilityByName(layerName) {
2720    if (layerName === 'layer-trafic' || (typeof visibleLayers !== 'undefined' && visibleLayers.length > 0)) {
2721        toggleDisplayThisLayer('layer-trafic');
2722        return false;
2723    }
2724
2725    var layer = getLayerByName(layerName);
2726    if (layer !== null) {
2727        var visible = layer.getVisible();
2728        layer.setVisible(!visible);
2729        setLayerConfigPropertie(layerName, 'visibility', !visible);
2730        return true;
2731    }
2732
2733    return false;
2734}
2735
2736/**
2737 * Zommer vers une region de la carte
2738 *
2739 * @param zoom {integer} : valeur du zoom
2740 * @param longitude {float} : longitude du centre de la vue
2741 * @param latitude {float} : latitude du centre de la vue
2742 * @param projection {string} : projection de la longitude et latitude (par défaut 'EPSG:3857', si 'EPSG:4326' reprojection)
2743 *
2744 * @return void
2745 */
2746function setViewZoomAndCenter(zoom, longitude, latitude, projection) {
2747    var sProj = projection || 'EPSG:3857';
2748    var centre = [longitude, latitude];
2749    if (sProj === 'EPSG:4326') centre = ol.proj.transform([longitude, latitude], 'EPSG:4326', 'EPSG:3857');
2750    setViewCenterWithAnimation(centre);
2751    setViewZoomWithAnimation(zoom);
2752}
2753
2754/**
2755 * Associer une fonction callback à un changement de propreites de la vue
2756 *
2757 * @param propertie string[] : tableau des properietes ('resoltion', 'center', ...)
2758 * @param callback function : fonction de calback a éxecuter
2759 *
2760 * @return boolean
2761 */
2762function onViewPropertyChange(propertie, callback) {
2763    if (typeof callback == 'function' && Array.isArray(propertie)) {
2764        var oView = oMap.getView();
2765        oView.on('propertychange', function (e) {
2766            for (var i in propertie) {
2767                if (propertie[i] === e.key) {
2768                    callback();
2769                }
2770            }
2771        });
2772
2773        return true;
2774    }
2775
2776    return false;
2777}
2778
2779/**
2780 * Retourner la configuration d'un calque (variable layerConfig du fichier script-config.js)
2781 *
2782 * @param string layerName : nom de la configuration du calque
2783 *
2784 * @returns object : configuration du calque ou undefined
2785 */
2786function getLayerConfig(layerNameConfig) {
2787    return layerConfig[layerNameConfig];
2788}
2789
2790/**
2791 * Affecter une valeur à une propriete de la configuration d'un calque
2792 *
2793 * @param string layerName : nom du calque
2794 * @param string propertie : nom de la propriete a modifier
2795 * @param string value : valeur a affecter a la propriete
2796 *
2797 * @returns {Boolean} opération réussis ou non
2798 */
2799function setLayerConfigPropertie(layerName, propertie, value) {
2800    var bResult = false;
2801    //trouver la configuration du calque
2802    for (var j in layerConfig) {
2803        if (layerConfig[j].name === layerName) {
2804            layerConfig[j][propertie] = value;
2805            bResult = true;
2806            break;
2807        }
2808    }
2809    return bResult;
2810}
2811
2812/**
2813 * Mise a jour automatique d'un calque GeoJson
2814 * @param string layerName : nom du calque à mette à jour
2815 * @param integer autoUpdateInterval : délais de mise à jour automatique
2816 * @returns void
2817 */
2818function autoUpdateLayerFromGeoJson(layerName, autoUpdateInterval) {
2819    setInterval(function () {
2820        DDEUpdateLayerFromGeoJson(layerName);
2821    }, autoUpdateInterval);
2822}
2823
2824/**
2825 * Mise a jour automatique d'un calque speciale (repere)
2826 *
2827 * @param string layerNameConfig : nom de la configuration du calque
2828 * @param string url : url de la requete AJAX
2829 * @param integer autoUpdateInterval : délais de mise à jour automatique
2830 *
2831 * @returns void
2832 */
2833function autoUpdateSpecialLayer(layerNameConfig, url, autoUpdateInterval) {
2834    setInterval(function () {
2835        var localLayerConfig = getLayerConfig(layerNameConfig);
2836        localLayerConfig.url = localLayerConfig.url !== 'default' ? localLayerConfig.url : url;
2837        DDEAddSpecialLayer(localLayerConfig);
2838    }, autoUpdateInterval);
2839}
2840
2841/**
2842 * Mise à clignoter d'un objet
2843 *
2844 * @param ol.feature feature: objet (evenement) d'un calque
2845 *
2846 * @return void
2847 */
2848function setBlinking(feature) {
2849    var style = feature.getStyle();
2850    if (style === null) {
2851        //pas de style <=> icone est visible
2852        var hideIcone = new ol.style.Style({
2853            image: undefined,
2854        });
2855        feature.setStyle(hideIcone);
2856    } else {
2857        //style <=> icone invisible
2858        feature.setStyle(null);
2859    }
2860    //refrech
2861    feature.changed();
2862}
2863
2864/**
2865 * Centrer la carte sur un repère identifié par son ID
2866 *
2867 * @param integer/string id : identifiant du repére
2868 * @param zoom : valeur du zoom
2869 *
2870 * @return boolean centrage réussis ou pas
2871 */
2872function centerMapOnFeatureId(id, zoom) {
2873    if (typeof zoom === 'undefined') {
2874        zoom = oMap.getView().getZoom();
2875    }
2876
2877    var oFeature = getRepereById(id);
2878    if (oFeature) {
2879        var tCoord = oFeature.getGeometry().getCoordinates();
2880        if (typeof tCoord === 'object') {
2881            var oView = oMap.getView();
2882            oView.setCenter(tCoord);
2883            oView.setZoom(zoom);
2884            return true;
2885        }
2886    }
2887
2888    return false;
2889}
2890
2891/**
2892 * Retourner la liste des noms des calques de repère
2893 * @return {Array}
2894 */
2895function getAllRepereLayer() {
2896    var tResult = [];
2897    var tRepereSubLayer = layerConfig.repere.sublayer;
2898    if (tRepereSubLayer.length > 0) {
2899        for (var i = 0; i < tRepereSubLayer.length; i++) {
2900            tResult.push(tRepereSubLayer[i].name);
2901        }
2902    } else {
2903        tResult.push(layerConfig.repere.name);
2904    }
2905    return tResult;
2906}
2907
2908/**
2909 * Retourner un repere d'apres son ID
2910 * @param {number|string} id: identifiant du repere
2911 * @return ol.Feature|false
2912 */
2913function getRepereById(id) {
2914    if (id !== undefined && id !== null) {
2915        var tRepereLayer = getAllRepereLayer(),
2916            i = 0,
2917            oLayer,
2918            oSource,
2919            oFeature;
2920        while (i < tRepereLayer.length) {
2921            oLayer = getLayerByName(tRepereLayer[i]);
2922            if (oLayer) {
2923                oSource = oLayer.getSource();
2924                if (oSource instanceof ol.source.Cluster) oSource = oSource.getSource();
2925                oFeature = oSource.getFeatureById(id);
2926                if (oFeature !== null) {
2927                    return oFeature;
2928                }
2929            }
2930            i++;
2931        }
2932    }
2933    return false;
2934}
2935
2936function showDialogBox(title, height, width, contentId, modal, timeOut) {
2937    // Retourne si la hauteur, la largeur ou l'élément avec le contenu ne sont
2938    // pas définies
2939    if (height === '' || width === '' || contentId === '' || $('#' + contentId).length <= 0) {
2940        return false;
2941    }
2942    if (typeof tb_show === 'function') {
2943        showDialogBoxTbShow(title, height, width, contentId, modal, timeOut);
2944    } else if (typeof $().dialog === 'function') {
2945        showDialogBoxJQuery(title, height, width, contentId, modal, timeOut);
2946    } else {
2947        return false;
2948    }
2949}
2950
2951function showDialogBoxJQuery(title, height, width, contentId, modal, timeOut) {
2952    $('#' + contentId).dialog({
2953        title: title,
2954        modal: modal,
2955        width: width,
2956        height: height,
2957        dialogClass: 'custom-dialog',
2958    });
2959    if (!isNaN(timeOut) && timeOut !== 0) {
2960        setTimeout(function () {
2961            $('#' + contentId).dialog('close');
2962        }, timeOut);
2963    }
2964    return true;
2965}
2966
2967/**
2968 * Afficher une fenetre de dialogue à fermeture automatique
2969 * utilise ThickBox UI dialog : http://codylindley.com/thickbox/
2970 *
2971 * @param {string} title : titre de la boite de dialogue
2972 * @param {integer} height : hauteur de la boite de dialogue
2973 * @param {integer} width : largeur de la boite de dialogue
2974 * @param {string} contentId : identifiant de l'element contenant le message à afficher dans la boite de dialogue
2975 * @param {string} modal : affichage modale ou pas ('true'/'false')
2976 * @param {integer} timeOut : delais en milisecondes avant la fermeture de la boite de dialogue (0 desactive la fermeture automatique)
2977 *
2978 * @return {boolean} affichage réussis ou pas de le fenetre de dialogue
2979 *
2980 * @FIXME Essayer de trouver et remplacer par un truc équivalent à ThickBox qui
2981 * soit un peu plus responsive ?
2982 */
2983function showDialogBoxTbShow(title, height, width, contentId, modal, timeOut) {
2984    // Définie l'url de paramétrage pour thickbox
2985    var sUrl = '#TB_inline?' + 'height=' + height + '&width=' + width + '&inlineId=' + contentId;
2986
2987    if (modal !== '') {
2988        sUrl += '&modal=' + modal;
2989    }
2990
2991    // Affiche la fenêtre
2992    tb_show(title, sUrl, 'false');
2993
2994    if (!isNaN(timeOut) && timeOut !== 0) {
2995        setTimeout(function () {
2996            tb_remove();
2997        }, timeOut);
2998    }
2999    return true;
3000}
3001
3002/**
3003 * Ajouter un paragraphe dans une DIV caché ou modifie son contenu s'il existe déja
3004 * @param {string} divId : identifiant de la DIV
3005 * @param {string} divClass : class de la DIV
3006 * @param {string} content : contenu du paragraph
3007 * @returns {Boolean} ajout réussis ou pas du paragraphe
3008 */
3009function addParagraphInHiddenDiv(divId, divClass, content) {
3010    var bResult = false;
3011    var parentDiv = document.getElementsByClassName('hiddenData')[0];
3012    if (parentDiv !== undefined) {
3013        if ($('#' + divId).length === 0) {
3014            var newDiv = document.createElement('div');
3015            newDiv.setAttribute('id', divId);
3016            newDiv.setAttribute('class', divClass);
3017            var newP = document.createElement('p');
3018            var newText = document.createTextNode(content);
3019            newP.appendChild(newText);
3020            newDiv.appendChild(newP);
3021            parentDiv.appendChild(newDiv);
3022        } else {
3023            var div = document.getElementById(divId);
3024            div.children[0].innerHTML = content;
3025        }
3026        bResult = true;
3027    }
3028    return bResult;
3029}
3030
3031/**
3032 * Gérer la sélection des déviations par la souris
3033 * - Afficher/cacher les déviations
3034 * @param {ol.feature} feature : élément d'une déviation (objet)
3035 * @parm {boolean} selected : objet selectionné ou pas
3036 * @return void
3037 */
3038function manageDeviationSelection(feature, selected) {
3039    if (
3040        feature instanceof ol.Feature &&
3041        feature.get('type') === '_DEVIATION' &&
3042        feature.get('type_deviation') === 'ROUTE_COUPEE' &&
3043        feature.get('ids_deviation').length >= 1
3044    ) {
3045        var tDeviation = feature.get('ids_deviation'),
3046            oFeature = null,
3047            tStyle,
3048            i;
3049        for (i = 0; i < tDeviation.length; i++) {
3050            oFeature = getLayerFeatureById('layer-deviation', tDeviation[i]);
3051            if (oFeature !== null) {
3052                if (selected) {
3053                    tStyle = getComplexeEventStyle(oFeature);
3054                    oFeature.setStyle(tStyle);
3055                } else oFeature.setStyle(null);
3056            }
3057        }
3058    }
3059}
3060
3061/**
3062 * Gérer l'affichage des déviations
3063 * - Afficher/cacher les déviations
3064 * @param {array} tDeviation : tableau des élément composant la déviation
3065 * @parm {boolean} selected : objet selectionné ou pas
3066 * @return void
3067 */
3068function manageDeviationsDisplay(tDeviation, selected) {
3069    for (i = 0; i < tDeviation.length; i++) {
3070        var oFeature = getLayerFeatureById('layer-deviation', tDeviation[i]);
3071        if (oFeature !== null) {
3072            if (selected) {
3073                var tStyle = getComplexeEventStyle(oFeature);
3074                oFeature.setStyle(tStyle);
3075            } else oFeature.setStyle(null);
3076        }
3077    }
3078}
3079/**
3080 * Gérer l'affichage des déviations par onglet
3081 * - Afficher/cacher les déviations par onglet
3082 * @param {array} tDeviation : tableau des élément composant la déviation
3083 * @parm {boolean} selected : objet selectionné ou pas
3084 * @return void
3085 */
3086function manageDeviationsDisplayByOnglet(tDeviation, selected){
3087    let currentonglet;
3088    $('.NavigationTab li a').each(function() {
3089        if ($(this).attr('aria-selected') === 'true') {
3090            currentonglet = $(this).attr('aria-controls');
3091        }
3092    });
3093    let typesDeviationTodisplay = getTypesDeviationToDisplay(currentonglet);
3094    for (i = 0; i < tDeviation.length; i++) {
3095        let oFeature = getLayerFeatureById('layer-deviation', tDeviation[i]);
3096        let typeDeviation = oFeature.get('type_deviation');
3097        if (oFeature !== null) {
3098            if (selected && typesDeviationTodisplay.includes(typeDeviation)) {
3099                let tStyle = getComplexeEventStyle(oFeature);
3100                oFeature.setStyle(tStyle);
3101            } else {
3102                oFeature.setStyle(null);
3103            };
3104        }
3105    }
3106}
3107/**
3108 * 
3109 * @param {*} onglet 
3110 * @returns array types 
3111 */
3112function getTypesDeviationToDisplay(onglet){
3113    let types=[];
3114    const deviations = [
3115        { selector: '#divDeviationPL', type: 'DEVIATION_PL' },
3116        { selector: '#divDeviationVL', type: 'DEVIATION_VL' }
3117    ];
3118
3119    for (let i = 0; i < deviations.length; i++) {
3120        let onglets = [];
3121        if ($(deviations[i].selector).length > 0) {
3122            onglets = $(deviations[i].selector).attr('data-onglet').split(',')
3123        }
3124
3125        if (onglets.includes(onglet)) {
3126            types.push(deviations[i].type);
3127        }
3128    }
3129    return types;
3130}
3131
3132/**
3133 * Retourner un objet d'un calque sur la base de l'id de l'object et le nom du calque
3134 * @param {string} layerName nom du calque
3135 * @param {string|number} id identifiant de l'objet
3136 * @return {ol.feature|null}
3137 */
3138function getLayerFeatureById(layerName, id) {
3139    var oLayer = getLayerByName(layerName);
3140    if (oLayer !== null) return oLayer.getSource().getFeatureById(id);
3141    return null;
3142}
3143
3144/**
3145 * Geocoder une adresse avec le service nominatiom (http://wiki.openstreetmap.org/wiki/FR:Nominatim)
3146 * - utilisation
3147 * var nom = new nominatim({limit:2});
3148 * var res = nom.search('dijon',console.log);
3149 *
3150 * @param {object} opt : option de configuration de l'API nominatim :
3151 * {string} opt.url : valeur par défaut = 'http://nominatim.openstreetmap.org/search/'
3152 * {object} opt.params : parmétres de la requete:
3153 * {string} opt.params.query : adresse a rechercher
3154 * {string html|xml|json|jsonv2} opt.params.format : format de la requete AJAX  (valeur par défaut 'json')
3155 * {integer 0|1} opt.params.addressdetails : inclure une décomposition de l'adresse en éléments (valeur par défaut 1)
3156 * {interger} opt.params.limit : limiter le nombre de résultats retournés (valeur par défaut 5)
3157 * {string} opt.params.countrycodes : limite les résultats de recherche à un pays spécifique (ou une liste de pays) (valeur par défaut 'FR')
3158 * {string} opt.params.lang : permet de surcharger le language par défaut du navigateur avec un langage pour les résultats de recherche (valeur par défaut 'fr-FR')
3159 *
3160 * @return void
3161 */
3162function nominatim(opt) {
3163    var options = opt || {};
3164    var htmlEscape = function (str) {
3165        return String(str)
3166            .replace(/&/g, '&amp;')
3167            .replace(/</g, '&lt;')
3168            .replace(/>/g, '&gt;')
3169            .replace(/"/g, '&quot;')
3170            .replace(/'/g, '&#039;');
3171    };
3172    this.settings = {
3173        url: 'https://nominatim.openstreetmap.org/search',
3174        params: {
3175            q: '',
3176            format: options.format || 'json',
3177            addressdetails: options.addressdetails || 1,
3178            limit: options.limit || 5,
3179            countrycodes: options.countrycodes || 'FR',
3180            'accept-language': options.lang || 'fr-FR',
3181        },
3182    };
3183
3184    var cleanCacheKey = function (str) {
3185        return str.replace(/\s/g, '').toLowerCase();
3186    };
3187
3188    var cache = {};
3189
3190    var setCache = function (q, results) {
3191        var query = cleanCacheKey(q);
3192        if (cache[query] === undefined) {
3193            cache[query] = JSON.stringify(results);
3194            return true;
3195        }
3196
3197        return false;
3198    };
3199
3200    var getCache = function (q) {
3201        var query = cleanCacheKey(q);
3202        if (cache[query] !== undefined) return JSON.parse(cache[query]);
3203        return false;
3204    };
3205
3206    this.search = function (query, callback) {
3207        if (query !== undefined && query !== '') this.settings.params.q = htmlEscape(query);
3208        if (this.settings.params.q !== '' && typeof callback === 'function') {
3209            //test cache
3210            var q = cleanCacheKey(query);
3211            if (cache[q] !== undefined) {
3212                callback(getCache(query));
3213            } else {
3214                //ajax
3215                $.ajax({
3216                    type: 'GET',
3217                    url: this.settings.url,
3218                    async: true,
3219                    data: this.settings.params,
3220                    dataType: 'json',
3221                })
3222                    .done(function (data) {
3223                        setCache(query, data);
3224                        callback(data);
3225                    })
3226                    .fail(function (jqXHR, textStatus) {
3227                        console.log('erreur Nominatim: ' + textStatus);
3228                    });
3229            }
3230        }
3231    };
3232}
3233
3234/**
3235 * Creation d'un élement du DOM
3236 *
3237 * @param {string} type : type d'élément à créer (button, input, ....
3238 * @param {string []} : tabelau des propriétées de l'élment a créer :[['prop1','value1'],['prop2','value2'], ...]
3239 *                     eExemple [['id', 'MapControl-search-result'], ['class', 'MapControl-search-result']]
3240 *
3241 * @return object
3242 */
3243function domCreateElement(type, attribute) {
3244    var element = document.createElement(type);
3245    if (Array.isArray(attribute)) {
3246        attribute.forEach(function (elem) {
3247            if (elem[0] !== undefined && elem[1] !== undefined) element.setAttribute(elem[0], elem[1]);
3248        });
3249    }
3250
3251    return element;
3252}
3253
3254/**
3255 * Ajouter le controleur de Recherche d'adresse (geocodage)
3256 * - Transformer une adresse en position géographique (latitude/longitude)
3257 * - afficher un popup à l'adresse choisis
3258 *
3259 * @return void
3260 */
3261function addSearchControl() {
3262    //défintion d'un espace de nom pour l'application
3263    window.app = window.app || {};
3264    var app = window.app;
3265
3266    /**
3267     * @constructor
3268     * @extends {ol.control.Control}
3269     * @param {Object=} opt_options Control options.
3270     */
3271    app.SearchControl = function (opt_options) {
3272        var options = opt_options || {};
3273        options.provider = new nominatim(options.providerOptions);
3274
3275        var handleAddressClik = function (address) {
3276            var view = oMap.getView(),
3277                projection = view.getProjection(),
3278                coord = ol.proj.transform([parseFloat(address.lon), parseFloat(address.lat)], 'EPSG:4326', projection),
3279                overlay = oMap.getOverlays().item(0),
3280                popUpTitle = document.getElementById('popover-title'),
3281                popUpContent = document.getElementById('popover-content');
3282
3283            overlay.setPosition(undefined);
3284            popUpTitle.innerHTML = address.address.state || address.address.country || '';
3285            popUpContent.innerHTML = address.display_name;
3286            overlay.setPosition(coord);
3287
3288            view.setCenter(coord);
3289        };
3290
3291        var setNoResult = function () {
3292            var ulElement = document.getElementById('MapControl-search-result'),
3293                noResultsLabel = options.noResultsLabel || '0 résultats',
3294                liElement;
3295            ulElement.innerHTML = '';
3296            liElement = document.createElement('li');
3297            liElement.innerHTML = noResultsLabel;
3298            ulElement.appendChild(liElement);
3299        };
3300
3301        var displaySearchResults = function (result) {
3302            if (result.length > 0) {
3303                var ulElement = document.getElementById('MapControl-search-result'),
3304                    liElement;
3305                ulElement.innerHTML = '';
3306
3307                result.forEach(function (address) {
3308                    var html = '<a href="#">' + address.display_name + '</a>';
3309                    liElement = document.createElement('li');
3310                    liElement.innerHTML = html;
3311                    liElement.addEventListener(
3312                        'click',
3313                        function (evt) {
3314                            handleAddressClik(address);
3315                        },
3316                        false
3317                    );
3318                    ulElement.appendChild(liElement);
3319                });
3320            } else setNoResult();
3321        };
3322
3323        var handleQuery = function (evt) {
3324            if (evt.keyCode == 13) {
3325                var inputElement = document.getElementById('MapControl-search-input'),
3326                    query = inputElement.value;
3327                options.provider.search(query, displaySearchResults);
3328            }
3329        };
3330
3331        var createSearchView = function () {
3332            //bouton
3333            var title = options.tipLabel || 'Rechercher un lieu',
3334                buttonAttribute = [
3335                    ['title', title],
3336                    ['id', 'MapControl-search-button'],
3337                    ['class', 'MapControl-search-button'],
3338                    ['aria-controls', 'SearchArea'],
3339                    ['aria-expanded', 'false'],
3340                ],
3341                button = domCreateElement('button', buttonAttribute);
3342
3343            //div recherche
3344            var divSearchAttribute = [
3345                    ['id', 'SearchArea'],
3346                    ['class', 'SearchArea'],
3347                    ['aria-hidden', 'true'],
3348                ],
3349                divSearch = domCreateElement('div', divSearchAttribute);
3350
3351            //input du div de recherche
3352            var placeHolder = options.placeHolder || 'Rechercher ...',
3353                divSearchInputAttribute = [
3354                    ['type', 'text'],
3355                    ['id', 'MapControl-search-input'],
3356                    ['class', 'MapControl-search-input'],
3357                    ['placeholder', placeHolder],
3358                ],
3359                divSearchInput = domCreateElement('input', divSearchInputAttribute);
3360            divSearchInput.addEventListener('keydown', handleQuery, false);
3361            divSearch.appendChild(divSearchInput);
3362
3363            //list des resultats
3364            var divSearchUnorderedListAttribute = [
3365                    ['id', 'MapControl-search-result'],
3366                    ['class', 'MapControl-search-result'],
3367                ],
3368                divSearchUnorderedList = domCreateElement('ul', divSearchUnorderedListAttribute);
3369            divSearch.appendChild(divSearchUnorderedList);
3370
3371            var element = document.createElement('div');
3372            element.className = 'ol-search ol-unselectable ol-control';
3373            element.appendChild(button);
3374            element.appendChild(divSearch);
3375
3376            return element;
3377        };
3378
3379        ol.control.Control.call(this, {
3380            element: createSearchView(),
3381            target: options.target,
3382        });
3383    };
3384
3385    ol.inherits(app.SearchControl, ol.control.Control);
3386}
3387
3388/**
3389 * calcul d'itinéraire avec OSRM (Open Source Routing Machine: The OpenStreetMap Data Routing Engine)
3390 * wiki: https://github.com/Project-OSRM/osrm-backend/blob/master/docs/http.md#general-
3390options
3391 * Exemple :
3392 * var osrm = new OSRM();
3393 * osrm.route({
3394 *       coordinates: [[13.438640,52.519930], [13.415852,52.513191]],
3395 *       steps: true,
3396 *       alternatives: false,
3397 *       overview: 'simplified',
3398 *       geometries: 'polyline'
3399 *       },console.log);
3400 *
3401 * @param {object} opt : option de configuration de l'API OSRM :
3402 * {string} opt.server : url du serveur (valeur par défaut = 'https://router.project-osrm.org')
3403 * {string} opt.version : version du service (valeur par défaut = 'v1')
3404 * {string} opt.profile : mode de transport (valeur par défaut = 'driving') *
3405 *
3406 * @return void
3407 *
3408 */
3409function OSRM(opt) {
3410    var options = opt || {};
3411
3412    this.server = options.server || 'https://router.project-osrm.org';
3413    this.version = options.version || 'v1';
3414    this.profile = options.profile || 'driving';
3415
3416    //filter une option des autres
3417    this.filterOptions = function (options, keys) {
3418        var filtered = {};
3419        for (var k in options) {
3420            if (keys.indexOf(k) >= 0) {
3421                continue;
3422            }
3423            filtered[k] = options[k];
3424        }
3425        return filtered;
3426    };
3427
3428    //transformer un tableau de coordonnées en texte
3429    this.stringifyCoordinates = function (lonLats) {
3430        return lonLats
3431            .map(function (c) {
3432                return c[0] + ',' + c[1];
3433            })
3434            .join(';');
3435    };
3436
3437    //former l'url de la requete
3438    this.encodeURL = function (service, query) {
3439        var url = this.server + '/' + service + '/' + this.version + '/' + this.profile + '/' + query + '.json';
3440        return url;
3441    };
3442
3443    //service route
3444    this.route = function (options, callback) {
3445        var query = this.stringifyCoordinates(options.coordinates),
3446            url = this.encodeURL('route', query),
3447            params = this.filterOptions(options, ['coordinates']);
3448
3449        var divItineraire = $('#ItineraryArea');
3450
3451        //ajax
3452        $.ajax({
3453            type: 'GET',
3454            url: url,
3455            async: true,
3456            data: params,
3457            dataType: 'json',
3458            statusCode: {
3459                429: function () {
3460                    // Cas où on a l'erreur too many request, on y peut rien don on affiche un message plus rassurant que rien du tout
3461                    if (divItineraire) {
3462                        var string =
3463                            '<p style="color:orange; font-weight: bold;"> Service temporairement indisponible. Nous nous excusons pour la gêne occasionnée, veuillez réessayer plus tard. Rétablissement imminent</p>';
3464                        $('#MapControl-itinerary-summary').html(string);
3465                    }
3466                },
3467            },
3468        })
3469            .done(function (data) {
3470                callback(data);
3471            })
3472            .fail(function (jqXHR, textStatus) {
3473                console.log('erreur OSRM: ' + textStatus);
3474            });
3475    };
3476}
3477
3478/**
3479 * Ajouter le controleur de calcul d'itinéraire
3480 *
3481 * @return void
3482 */
3483function addItineraryControl() {
3484    //défintion d'un espace de nom pour l'application
3485    window.app = window.app || {};
3486    var app = window.app;
3487
3488    /**
3489     * @constructor
3490     * @extends {ol.control.Control}
3491     * @param {Object=} opt_options Control options.
3492     */
3493    app.ItineraryControl = function (opt_options) {
3494        var options = opt_options || {};
3495
3496        //osrm
3497        options.provider = new OSRM();
3498        options.finder = new nominatim(options.searchOptions);
3499
3500        //layer
3501        options.layer = {};
3502        options.layer.styleFunction = function (feature, resolution) {
3503            var image = new ol.style.Icon({
3504                anchor: [0.5, 1],
3505                opacity: 1,
3506                src: feature.get('src') || 'images/image.png',
3507                scale: feature.get('scale') || 0.7,
3508            });
3509
3510            var styles = {
3511                Point: [
3512                    new ol.style.Style({
3513                        image: image,
3514                    }),
3515                ],
3516                LineString: [
3517                    new ol.style.Style({
3518                        stroke: new ol.style.Stroke({
3519                            color: [0, 51, 255, 0.3],
3520                            linecap: 'round',
3521                            linejoin: 'round',
3522                            width: 10,
3523                        }),
3524                    }),
3525                ],
3526            };
3527
3528            return styles[feature.getGeometry().getType()];
3529        };
3530        options.layer.vectorSource = new ol.source.Vector({});
3531        options.layer.vectorLayer = new ol.layer.Vector({
3532            name: 'layer-itinerary',
3533            label: 'Calque des itinéraires',
3534            source: options.layer.vectorSource,
3535            style: options.layer.styleFunction,
3536        });
3537        addLayerToMap(options.layer.vectorLayer, 99);
3538
3539        var getItineraryStepsHTML = function (steps) {
3540            var stepsHTML =
3541                    '<table class="MapControl-itinerary-steps-table"><colgroup class="MapControl-itinerary-steps-colgroup"><col class="MapControl-itinerary-steps-col-icon"><col class="MapControl-itinerary-steps-col-text"><col class="MapControl-itinerary-steps-col-duration"><col class="MapControl-itinerary-steps-col-distance"></colgroup><tbody class="MapControl-itinerary-steps-table-tbody">',
3542                modifier,
3543                type,
3544                duration,
3545                distance;
3546
3547            for (var i = 0; i < steps.length; i++) {
3548                stepsHTML += '<tr>';
3549
3550                //icon
3551                type =
3552                    typeof steps[i].maneuver.type !== 'undefined'
3553                        ? '-' + steps[i].maneuver.type.replace(/\s/g, '-')
3554                        : '';
3555                modifier =
3556                    typeof steps[i].maneuver.modifier !== 'undefined' && type !== '-depart' && type !== '-arrive'
3557                        ? '-' + steps[i].maneuver.modifier.replace(/\s/g, '-')
3558                        : '';
3559                stepsHTML +=
3560                    '<td ><span class="MapControl-itinerary-steps-icon MapControl-itinerary-steps-icon' +
3561                    type +
3562                    modifier +
3563                    '"></span></td>';
3564
3565                //text
3566                stepsHTML += '<td>' + steps[i].name + '</td>';
3567
3568                //duration
3569                duration = steps[i].duration / 60;
3570                stepsHTML += '<td>' + Math.round(duration) + ' min</td>';
3571
3572                //distance
3573                distance = steps[i].distance / 1000;
3574                stepsHTML += '<td>' + Math.round(distance * 100) / 100 + ' Km</td></tr>';
3575            }
3576
3577            stepsHTML += '</tbody></table>';
3578            return stepsHTML;
3579        };
3580
3581        var deleteFeatureById = function (id, vectorSource) {
3582            var oldFeature = vectorSource.getFeatureById(id);
3583            if (oldFeature !== null) vectorSource.removeFeature(oldFeature);
3584        };
3585
3586        var drawMarkerOnMap = function (coordinates, id, src, scale, vectorSource) {
3587            //delete old feature
3588            deleteFeatureById(id, vectorSource);
3589
3590            //new feature
3591            var markerGeom = new ol.geom.Point(coordinates);
3592
3593            var markerFeature = new ol.Feature({
3594                geometry: markerGeom,
3595            });
3596            markerFeature.setId(id);
3597            markerFeature.setProperties({
3598                src: src,
3599                scale: scale,
3600            });
3601
3602            //add feature
3603            vectorSource.addFeature(markerFeature);
3604        };
3605
3606        var drawItineraryOnMap = function (routeGeometry, id, vectorSource) {
3607            //delete old feature
3608            deleteFeatureById(id, vectorSource);
3609
3610            //new feature
3611            var routeGeom = new ol.format.Polyline({
3612                factor: 1e5,
3613            }).readGeometry(routeGeometry, {
3614                dataProjection: 'EPSG:4326',
3615                featureProjection: 'EPSG:3857',
3616            });
3617
3618            //add feature
3619            var routeFeature = new ol.Feature({
3620                geometry: routeGeom,
3621            });
3622            routeFeature.setId(id);
3623            vectorSource.addFeature(routeFeature);
3624
3625            //add start marker
3626            var routeCoords = routeGeom.getCoordinates();
3627            var routeLength = routeCoords.length;
3628            drawMarkerOnMap(routeCoords[0], 1, 'images/itinerary_marker_green.png', 0.7, vectorSource);
3629
3630            //add end marker
3631            drawMarkerOnMap(routeCoords[routeLength - 1], 3, 'images/itinerary_marker_red.png', 0.7, vectorSource);
3632
3633            //fit view
3634            var view = oMap.getView();
3635            view.fit(routeGeom, oMap.getSize());
3636        };
3637
3638        var displayItineraryResults = function (result) {
3639            var summaryElement = document.getElementById('MapControl-itinerary-summary'),
3640                resultElement = document.getElementById('MapControl-itinerary-steps');
3641            if (result.code === 'Ok') {
3642                var itinerary = typeof result.routes[0].legs[0] !== 'undefined' ? result.routes[0].legs[0] : undefined;
3643
3644                if (itinerary !== undefined) {
3645                    //summary
3646                    if (summaryElement !== null) {
3647                        var summaryHTML = '',
3648                            distance = typeof itinerary.distance !== 'undefined' ? itinerary.distance / 1000 : 0,
3649                            duration = typeof itinerary.duration !== 'undefined' ? itinerary.duration / 60 : 0;
3650                        summaryHTML += itinerary.summary !== '' ? '<p> Trajet : ' + itinerary.summary + '</p>' : '';
3651                        summaryHTML += '<p> Distance : ' + Math.round(distance * 10) / 10 + ' Km </p>';
3652                        summaryHTML += '<p> Durée : ' + Math.round(duration) + ' minutes </p>';
3653                        summaryElement.innerHTML = summaryHTML;
3654                    }
3655
3656                    //steps
3657                    if (
3658                        typeof itinerary.steps !== 'undefined' &&
3659                        itinerary.steps.length > 0 &&
3660                        resultElement !== null
3661                    ) {
3662                        resultElement.innerHTML = getItineraryStepsHTML(itinerary.steps);
3663                    }
3664
3665                    //trace
3666                    if (typeof result.routes[0].geometry !== 'undefined' && result.routes[0].geometry !== '')
3667                        drawItineraryOnMap(result.routes[0].geometry, 2, options.layer.vectorSource);
3668                }
3669            } else if (result.code === 'NoRoute') {
3670                summaryElement.innerHTML = '<p> Pas de route trouvée. </p>';
3671                resultElement.innerHTML = '';
3672            }
3673        };
3674
3675        var checkLonLat = function (str) {
3676            var pattern = /-?[0-9]{1,3}[.][0-9]+/;
3677            if (pattern.test(str)) {
3678                var tCoord = str.split(',');
3679                if (tCoord.length === 2 && pattern.test(tCoord[0]) && pattern.test(tCoord[1])) {
3680                    var lon = parseFloat(tCoord[0]),
3681                        lat = parseFloat(tCoord[1]);
3682                    if (!isNaN(lon) && !isNaN(lat) && lon >= -180 && lon <= 180 && lat >= -90 && lat <= 90) return true;
3683                }
3684            }
3685            return false;
3686        };
3687
3688        var deletePreviousSibling = function (evt) {
3689            var elementPreviousSibling = document.getElementById(evt.target.id).previousSibling;
3690            elementPreviousSibling.value = '';
3691        };
3692
3693        var changeActiveInput = function (evt) {
3694            var divItinerarySearch = document.getElementById('MapControl-itinerary-search');
3695            if (divItinerarySearch !== null) divItinerarySearch.setAttribute('data-active-input', evt.target.id);
3696        };
3697
3698        var handleQuery = function (evt) {
3699            var inputElementStart = document.getElementById('MapControl-itinerary-search-start-input'),
3700                inputElementEnd = document.getElementById('MapControl-itinerary-search-en
3700d-input');
3701            document.getElementById('MapControl-itinerary-search-Suggestions-list').innerHTML = '';
3702            if (inputElementStart !== null && inputElementEnd !== null) {
3703                var start = inputElementStart.getAttribute('data-position').replace(/\s/g, ''),
3704                    end = inputElementEnd.getAttribute('data-position').replace(/\s/g, '');
3705                if (start !== '' && end !== '' && checkLonLat(start) && checkLonLat(end)) {
3706                    var tStart = start.split(','),
3707                        tEnd = end.split(','),
3708                        tCoord = [tStart, tEnd];
3709                    options.provider.route(
3710                        {
3711                            coordinates: tCoord,
3712                            steps: false,
3713                            alternatives: false,
3714                            overview: 'simplified',
3715                            geometries: 'polyline',
3716                        },
3717                        displayItineraryResults
3718                    );
3719                }
3720            }
3721        };
3722
3723        var handleAddressClik = function (address) {
3724            document.getElementById('MapControl-itinerary-search-Suggestions-list').innerHTML = '';
3725            var divItinerarySearch = document.getElementById('MapControl-itinerary-search');
3726            if (divItinerarySearch !== null) {
3727                var IdActiveInput = divItinerarySearch.getAttribute('data-active-input'),
3728                    inputElement = document.getElementById(IdActiveInput);
3729                if (inputElement !== null && inputElement.offsetWidth > 0 && inputElement.offsetHeight > 0) {
3730                    var coord = address.lon + ', ' + address.lat;
3731                    inputElement.value = address.display_name || coord;
3732                    inputElement.setAttribute('data-position', coord);
3733                }
3734            }
3735        };
3736
3737        var displaySearchResults = function (result) {
3738            var ulElement = document.getElementById('MapControl-itinerary-search-Suggestions-list'),
3739                liElement;
3740            ulElement.innerHTML = '';
3741
3742            if (result.length > 0) {
3743                result.forEach(function (address) {
3744                    liElement = document.createElement('li');
3745                    liElement.innerHTML = address.display_name;
3746                    liElement.addEventListener(
3747                        'click',
3748                        function (evt) {
3749                            handleAddressClik(address);
3750                        },
3751                        false
3752                    );
3753                    ulElement.appendChild(liElement);
3754                });
3755            }
3756        };
3757
3758        var findAddress = function (evt) {
3759            if (evt.keyCode == 13) {
3760                options.finder.search(this.value, displaySearchResults);
3761            }
3762        };
3763
3764        var handleLayerVisibility = function () {
3765            options.layer.vectorLayer.setVisible(this.checked);
3766        };
3767
3768        var createItineraryView = function () {
3769            //bouton du controleur
3770            var title = options.tipLabel || "calcul d'itinéraire",
3771                buttonAttribute = [
3772                    ['title', title],
3773                    ['id', 'MapControl-itinerary-button'],
3774                    ['class', 'MapControl-itinerary-button'],
3775                    ['aria-controls', 'ItineraryArea'],
3776                    ['aria-expanded', 'false'],
3777                ],
3778                controlButton = domCreateElement('button', buttonAttribute);
3779
3780            //div itineraire
3781            var divItineraryAttribute = [
3782                    ['id', 'ItineraryArea'],
3783                    ['class', 'ItineraryArea'],
3784                    ['aria-hidden', 'true'],
3785                ],
3786                divItinerary = domCreateElement('div', divItineraryAttribute);
3787
3788            //div recherche
3789            var divItinerarySearchAttribute = [
3790                    ['id', 'MapControl-itinerary-search'],
3791                    ['class', 'MapControl-itinerary-search'],
3792                    ['data-active-input', 'MapControl-itinerary-search-start-input'],
3793                ],
3794                divItinerarySearch = domCreateElement('div', divItinerarySearchAttribute);
3795            divItinerary.appendChild(divItinerarySearch);
3796
3797            //div start
3798            var divStartInputAttribute = [
3799                    ['id', 'MapControl-itinerary-search-start'],
3800                    ['class', 'MapControl-itinerary-search-location'],
3801                ],
3802                divStartInput = domCreateElement('div', divStartInputAttribute);
3803            divItinerarySearch.appendChild(divStartInput);
3804
3805            //span icone
3806            var spanStartAttribute = [
3807                    ['id', 'MapControl-itinerary-search-icon-depart'],
3808                    ['class', 'MapControl-itinerary-search-icon'],
3809                ],
3810                spanStart = domCreateElement('span', spanStartAttribute);
3811            divStartInput.appendChild(spanStart);
3812
3813            //input
3814            var placeHolder = options.placeHolder || 'Entrez une adresse',
3815                inputStartAttribute = [
3816                    ['id', 'MapControl-itinerary-search-start-input'],
3817                    ['class', 'MapControl-itinerary-search-input'],
3818                    ['data-position', ''],
3819                    ['placeholder', placeHolder],
3820                ],
3821                inputStart = domCreateElement('input', inputStartAttribute);
3822            inputStart.addEventListener('click', changeActiveInput, false);
3823            inputStart.addEventListener('keydown', findAddress, false);
3824            divStartInput.appendChild(inputStart);
3825
3826            //button delete
3827            var buttonDelStartAttribute = [
3828                    ['id', 'MapControl-itinerary-search-start-delete'],
3829                    ['class', 'MapControl-itinerary-search-delete'],
3830                ],
3831                buttonStart = domCreateElement('button', buttonDelStartAttribute);
3832            buttonStart.addEventListener('click', deletePreviousSibling, false);
3833            divStartInput.appendChild(buttonStart);
3834
3835            //div end
3836            var divEndAttribute = [
3837                    ['id', 'MapControl-itinerary-search-end'],
3838                    ['class', 'MapControl-itinerary-search-location'],
3839                ],
3840                divEndInput = domCreateElement('div', divEndAttribute);
3841            divItinerarySearch.appendChild(divEndInput);
3842
3843            //span icone
3844            var spanEndAttribute = [
3845                    ['id', 'MapControl-itinerary-search-icon-end'],
3846                    ['class', 'MapControl-itinerary-search-icon'],
3847                ],
3848                spanEnd = domCreateElement('span', spanEndAttribute);
3849            divEndInput.appendChild(spanEnd);
3850
3851            //input
3852            var inputEndAttribute = [
3853                    ['id', 'MapControl-itinerary-search-end-input'],
3854                    ['class', 'MapControl-itinerary-search-input'],
3855                    ['data-position', ''],
3856                    ['placeholder', placeHolder],
3857                ],
3858                inputEnd = domCreateElement('input', inputEndAttribute);
3859            inputEnd.addEventListener('click', changeActiveInput, false);
3860            inputEnd.addEventListener('keydown', findAddress, false);
3861            divEndInput.appendChild(inputEnd);
3862
3863            //button delete
3864            var buttonDelEndAttribute = [
3865                    ['id', 'MapControl-itinerary-search-end-delete'],
3866                    ['class', 'MapControl-itinerary-search-delete'],
3867                ],
3868                buttonEnd = domCreateElement('button', buttonDelEndAttribute);
3869            buttonEnd.addEventListener('click', deletePreviousSibling, false);
3870            divEndInput.appendChild(buttonEnd);
3871
3872            //div suggestion
3873            var divSuggestionsAttribute = [
3874                    ['id', 'MapControl-itinerary-search-Suggestions'],
3875                    ['class', 'MapControl-itinerary-search-Suggestions'],
3876                ],
3877                divSuggestions = domCreateElement('div', divSuggestionsAttribute);
3878            divItinerarySearch.appendChild(divSuggestions);
3879
3880            //ul suggestion
3881            var ulSuggestionsAttribute = [
3882                    ['id', 'MapControl-itinerary-search-Suggestions-list'],
3883                    ['class', 'MapControl-itinerary-search-Suggestions-list'],
3884                ],
3885                ulSuggestionsList = domCreateElement('ul', ulSuggestionsAttribute);
3886            divSuggestions.appendChild(ulSuggestionsList);
3887
3888            //bouton Rechercher
3889            var SearchButtonAttribute = [
3890                    ['title', 'Rechercher'],
3891                    ['id', 'MapControl-itinerary-search-button'],
3892                    ['class', 'MapControl-itinerary-search-button'],
3893                ],
3894                SearchButton = domCreateElement('button', SearchButtonAttribute);
3895            SearchButton.innerHTML = options.SearchButtonContent || 'Rechercher';
3896            SearchButton.addEventListener('click', handleQuery, false);
3897            divItinerarySearch.appendChild(SearchButton);
3898
3899            //div layer
3900            var divItineraryLayerAttribute = [
3901                    ['id', 'MapControl-itinerary-layer'],
3902                    ['class', 'MapControl-itinerary-layer'],
3903                ],
3904                divItineraryLayer = domCreateElement('div', divItineraryLayerAttribute);
3905            divItinerary.appendChild(divItineraryLayer);
3906
3907            //label visible
3908            var labelVisibleAttribute = [['for', 'MapControl-itinerary-layer-visible']],
3909                labelVisible = domCreateElement('label', labelVisibleAttribute);
3910            labelVisible.innerHTML = options.labelCheckboxVisible || "Afficher l'itinéraire ";
3911            divItineraryLayer.appendChild(labelVisible);
3912
3913            //checkbox visible
3914            var checkboxLayerAttribute = [
3915                    ['type', 'checkbox'],
3916                    ['id', 'MapControl-itinerary-layer-visible'],
3917                    ['class', 'MapControl-itinerary-layer-checkbox'],
3918                    ['checked', ''],
3919                ],
3920                checkboxLayer = domCreateElement('input', checkboxLayerAttribute);
3921            checkboxLayer.addEventListener('click', handleLayerVisibility, false);
3922            divItineraryLayer.appendChild(checkboxLayer);
3923
3924            //div resume
3925            var divItinerarySummaryAttribute = [
3926                    ['id', 'MapControl-itinerary-summary'],
3927                    ['class', 'MapControl-itinerary-summary'],
3928                ],
3929                divItinerarySummary = domCreateElement('div', divItinerarySummaryAttribute);
3930            divItinerary.appendChild(divItinerarySummary);
3931
3932            //div resultats
3933            var divItineraryResultAttribute = [
3934                    ['id', 'MapControl-itinerary-steps'],
3935                    ['class', 'MapControl-itinerary-steps'],
3936                ],
3937                divItineraryResult = domCreateElement('div', divItineraryResultAttribute);
3938            divItinerary.appendChild(divItineraryResult);
3939
3940            //div attribution
3941            var divItineraryLicenseAttribute = [
3942                    ['id', 'MapControl-itinerary-license'],
3943                    ['class', 'MapControl-itinerary-license'],
3944                ],
3945                divItineraryLicense = domCreateElement('div', divItineraryLicenseAttribute),
3946                attribution = options.attribution || '<a href="http://project-osrm.org">OSRM</a>';
3947            divItineraryLicense.innerHTML = attribution;
3948            divItinerary.appendChild(divItineraryLicense);
3949
3950            var element = document.createElement('div');
3951            element.className = 'ol-itinerary ol-unselectable ol-control';
3952            element.appendChild(controlButton);
3953            element.appendChild(divItinerary);
3954
3955            return element;
3956        };
3957
3958        ol.control.Control.call(this, {
3959            element: createItineraryView(),
3960            target: options.target,
3961        });
3962    };
3963
3964    ol.inherits(app.ItineraryControl, ol.control.Control);
3965}
3966
3967/**
3968 * Enregistrement d'une statistique
3969 *
3970 * @param Date date : date
3971 * @param string type : type d'objet parmis : barreau, lien, repere, evtcplx, lancement et fermeture
3972 * @param string action : nom de l'action parmis : clic, rollover*
3973 * @param string nom : nom de l'objet
3974 *
3975 * @return void
3976 */
3977function statEnregistrement(date, type, action, nom) {
3978    this.oDate = date;
3979    var sH = this.oDate.getHours().toString(),
3980        sM = this.oDate.getMinutes().toString(),
3981        sS = this.oDate.getSeconds().toString();
3982    sH = sH.length === 1 ? '0' + sH : sH;
3983    sM = sM.length === 1 ? '0' + sM : sM;
3984    sS = sS.length === 1 ? '0' + sS : sS;
3985    this.sHeure = sH + ':' + sM + ':' + sS;
3986
3987    this.sTypeObjet = type;
3988    this.sAction = action;
3989    this.sNomObjet = nom;
3990}
3991
3992/**
3993 * Dupliquer un enregistrement de statistique
3994 *
3995 * @param object enregistrement : statEnregistrement
3996 * @return object statEnregistrement
3997 */
3998statEnregistrement.prototype.dupliquer = function (enregistrement) {
3999    return new statEnregistrement(
4000        enregistrement.oDate,
4001        enregistrement.sTypeObjet,
4002        enregistrement.sAction,
4003        enregistrement.sNomObjet
4004    );
4005};
4006
4007/**
4008 * Comparer 2 enregistrments enregistrement : un enregistrement de statistique
4009 *
4010 * @param statEnregistrement
4011 *
4012 * @return boolean
4013 */
4014statEnregistrement.prototype.comparer = function (enregistrement) {
4015    if (enregistrement === null) return false;
4016    if (
4017        this.sAction === enregistrement.sAction &&
4018        this.sTypeObjet === enregistrement.sTypeObjet &&
4019        this.sNomObjet === enregistrement.sNomObjet
4020    )
4021        return true;
4022};
4023
4024/**
4025 * Serialiser un enregistrement de statistique sous la forme d'une chaine de cacateres : 'heure,type,action,nom'
4026 *
4027 * @return string
4028 */
4029statEnregistrement.prototype.serialiser = function () {
4030    var serialise = '';
4031    serialise += this.sHeure + ',' + this.sTypeObjet + ',' + this.sAction + ',' + this.sNomObjet;
4032    return serialise;
4033};
4034
4035/**
4036 * classe des Statistiques internes
4037 *
4038 * @param string url : url du dossier ou enregistrer le fichier des statistiques
4039 * @param string prefixe : prefixe du nom des fichiers de statistiques
4040 *
4041 * @return statistiqueInterne
4042 */
4043function statistiqueInterne(url, prefixe) {
4044    this.tStat = [];
4045    this.sUrlDossier = url;
4046    this.sPrefixeFichier = prefixe;
4047
4048    this.oEnregPrecedent = null;
4049    this.nDelaiEnvoi = 5000; // en millisecondes
4050
4051    this.remplirTableauStat('lancement', '', '');
4052
4053    var _this = this;
4054    setInterval(function () {
4055        _this.envoyerStatistiques();
4056    }, this.nDelaiEnvoi);
4057}
4058
4059/**
4060 * Remplir le tableau des statistiques
4061 *
4062 * @param string type : type d'objet parmis : barreau, lien, repere, evtcplx, lancement et fermeture
4063 * @param string action : nom de l'action parmis : clic, rollover*
4064 * @param string nom : nom de l'objet
4065 *
4066 * @return void
4067 */
4068statistiqueInterne.prototype.remplirTableauStat = function (type, action, nom) {
4069    var oNewEnregistrement = new statEnregistrement(new Date(), type, action, nom);
4070    if (!oNewEnregistrement.comparer(this.oEnregPrecedent)) {
4071        this.tStat.push(oNewEnregistrement);
4072        this.oEnregPrecedent = oNewEnregistrement.dupliquer(oNewEnregistrement);
4073    }
4074};
4075
4076/**
4077 * serialiser le tableau des statistiques avec le séparateur d'enregistrement '|'
4078 *
4079 * @return void
4080 */
4081statistiqueInterne.prototype.serialiserStatistiques = function () {
4082    var serialise = '';
4083    for (var i = 0; i < this.tStat.length; i++) {
4084        serialise += this.tStat[i].serialiser() + '|';
4085    }
4086
4087    return serialise.substr(0, serialise.length - 1);
4088};
4089
4090/**
4091 * Enregistrer les statistiques en intranet (AJAX)
4092 *
4093 * @param string stat : tableau des statisques sous format d'une chaine de caracteres
4094 * @param stringurlDossier :  url du dossier d'enregeidtrement du fichier des statistiques
4095 * @param string prefixeFichier : prefixe du fichier des statistiques
4096 *
4097 * @return void
4098 */
4099statistiqueInterne.prototype.enregistrerStatistiques = function (stat, urlDossier, prefixeFichier) {
4100    var param = new Object();
4101    param.statistique = stat;
4102    if (urlDossier != '') param.url_dossier = urlDossier;
4103    if (prefixeFichier != '') param.prefixe_fichier = prefixeFichier;
4104    $.post('mod_turbolead/mod/inforoute/stat-ajax.php', param, this.retourEnregistrement, 'json');
4105};
4106
4107/**
4108 * Retour d'Enregistrement des statistiques en intranet (AJAX)
4109 *
4110 * @return void
4111 */
4112statistiqueInterne.prototype.retourEnregistrement = function (o) {};
4113
4114/**
4115 * Envoyer les statistiques en AJAX
4116 *
4117 * @return void
4118 */
4119statistiqueInterne.prototype.envoyerStatistiques = function () {
4120    if (this.tStat.length > 0) {
4121        var sStatistiques = this.serialiserStatistiques();
4122        this.enregistrerStatistiques(sStatistiques, this.sUrlDossier, this.sPrefixeFichier);
4123        this.tStat = [];
4124    }
4125};
4126
4127/**
4128 * Gestion des intercations (survol et clique) pour les statistiques internes
4129 *
4130 * @param object evt : evenement singleclick ou pointermove
4131 * @param object _this: objet statistiqueInterne
4132 *
4133 * @return void
4134 */
4135function handleFeatureInteractionForStatistiqueInterne(evt, _this) {
4136    var pixel = oMap.getEventPixel(evt.originalEvent);
4137    hit = oMap.forEachFeatureAtPixel(
4138        pixel,
4139        function (feature, layer) {
4140            var sStatAction = evt.type === 'singleclick' ? 'clic' : 'rollover',
4141                sStatName = feature.get('titre'),
4142                sStatId = feature.getId(),
4143                sStatType = null,
4144                sFeatureType = feature.get('type');
4145
4146            //cas des cluster
4147            if (sStatId === undefined && sFeatureType === undefined) {
4148                var tFeature = feature.get('features');
4149                if (tFeature !== undefined && typeof tFeature[0] !== 'undefined' && tFeature.length === 1) {
4150                    sStatName = tFeature[0].get('titre');
4151                    sStatId = tFeature[0].getId();
4152                    sFeatureType = tFeature[0].get('type');
4153                }
4154            }
4155
4156            if (sStatId !== undefined && sFeatureType !== undefined) {
4157                if (sFeatureType.substr(0, 8) === '_BARREAU') {
4158                    sStatType = 'barreau';
4159                } else if (sFeatureType.substr(0, 7) === '_REPERE') {
4160                    sStatType = 'repere';
4161                } else {
4162                    switch (sFeatureType) {
4163                        case '_LIEN':
4164                            sStatType = 'lien';
4165                            sStatId = sStatName;
4166                            break;
4167                        case '_BARREAU':
4168                            sStatType = 'barreau';
4169                            break;
4170                        case '_ZONE':
4171                            sStatType = 'barreau';
4172                            break;
4173                        case '_DEVIATION':
4174                            sStatType = 'evtcplx';
4175                            sStatId = sStatName;
4176                            break;
4177                        case '_REPERE':
4178                            sStatType = 'repere';
4179                            break;
4180                    }
4181                }
4182
4183                if (sStatType !== null) {
4184                    _this.remplirTableauStat(sStatType, sStatAction, sStatId);
4185                }
4186            }
4187
4188            return true;
4189        },
4190        _this,
4191        function (layer) {
4192            return DDELayerFilter(layer);
4193        }
4194    );
4195}
4196
4197/**
4198 *
4199 * partager sur les reseaux sociaux
4200 *
4201 * @param {string} 				nameSocialMedia : nom du réseau social (facebook ou twitter)
4202 * @param {string|null}			url : adresse url a partager, si valeur vide|null: url de la page actuelle (window.location.href)
4203 * @param {string|integer|null}
4203	idRepere : identifiant du repere
4204 * @param {number|null} 		width : largeur de la fenete ouverte
4205 * @param {number|null} 		height : hauteur de la fenetre ouverte
4206 *
4207 * @return {boolean}
4208 */
4209function shareSocialMedia(nameSocialMedia, url, idRepere, width, height) {
4210    var sUrl = '',
4211        sLocation = url !== '' && url !== null ? url : window.location.href,
4212        nWidth = width != null ? width : 400,
4213        nHeight = height != null ? height : 300,
4214        nLeft = screen.width / 2 - nWidth / 2,
4215        nTop = screen.height / 2 - nHeight / 2,
4216        sOption =
4217            'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=' +
4218            nWidth +
4219            ',height=' +
4220            nHeight +
4221            ',top=' +
4222            nTop +
4223            ',left=' +
4224            nLeft;
4225
4226    switch (nameSocialMedia) {
4227        case 'facebook':
4228            sUrl = 'https://www.facebook.com/sharer/sharer.php';
4229            sUrl += '?u=' + encodeURIComponent(sLocation);
4230            window.open(sUrl, '_blank', sOption);
4231            return true;
4232        case 'google+': //https://developers.google.com/+/web/share/#sharelink
4233            sUrl = 'https://plus.google.com/share';
4234            sUrl += '?url=' + encodeURIComponent(sLocation);
4235            window.open(sUrl, '_blank', sOption);
4236            return true;
4237        case 'twitter': //https://dev.twitter.com/web/tweet-button/web-intent
4238            var sText = '',
4239                iTextLength = 139 - Math.min(sLocation.length, 23),
4240                sTitre = '',
4241                sDescription = '',
4242                oRepere = getRepereById(idRepere);
4243
4244            if (oRepere) {
4245                sTitre = oRepere.get('titre');
4246                sDescription = oRepere.get('description');
4247            }
4248
4249            sUrl = 'https://twitter.com/intent/tweet';
4250
4251            sText += sTitre !== undefined && sTitre !== '' ? sTitre : '';
4252            sText +=
4253                sTitre !== undefined && sTitre !== '' && sDescription !== undefined && sDescription !== '' ? ': ' : '';
4254            sText += sDescription !== undefined && sDescription !== '' ? sDescription : '';
4255            sText = sText.substr(0, iTextLength);
4256
4257            sUrl += '?url=' + encodeURITwitter(sLocation);
4258            if (sText !== '') sUrl += '&text=' + encodeURITwitter(sText);
4259
4260            window.open(sUrl, '_blank', sOption);
4261            return true;
4262    }
4263    return false;
4264}
4265
4266/**
4267 * Encodage URI pour twitter
4268 *
4269 * @param {string} : chaine a converir
4270 *
4271 * @return {string}
4272 */
4273function encodeURITwitter(str) {
4274    return encodeURIComponent(str)
4275        .replace(/!/g, '%21')
4276        .replace(/'/g, '%27')
4277        .replace(/\(/g, '%28')
4278        .replace(/\)/g, '%29')
4279        .replace(/\*/g, '%2A')
4280        .replace(/%20/g, '+')
4281        .replace(/~/g, '%7E');
4282}
4283
4284/**
4285 * Retourner la résolution correspondante à la valeur du zoom en entrée
4286 *
4287 * @param {inetger} zoom : valeur du zoom à convertir
4288 *
4289 * @return float|undefined
4290 */
4291function getResolutionOfZoom(zoom) {
4292    var tResolution = [
4293        156543.03392804097, 78271.51696402048, 39135.75848201024, 19567.87924100512, 9783.93962050256, 4891.96981025128,
4294        2445.98490512564, 1222.99245256282, 611.49622628141, 305.748113140705, 152.8740565703525, 76.43702828517625,
4295        38.21851414258813, 19.109257071294063, 9.554628535647032, 4.777314267823516, 2.388657133911758,
4296        1.194328566955879, 0.5971642834779395, 0.29858214173896974, 0.14929107086948487, 0.07464553543474244,
4297        0.03732276771737122, 0.01866138385868561, 0.009330691929342804, 0.004665345964671402, 0.002332672982335701,
4298        0.0011663364911678506, 0.0005831682455839253,
4299    ];
4300    if (zoom === parseInt(zoom, 10) && zoom >= 0 && zoom <= 28) return tResolution[zoom];
4301    return undefined;
4302}
4303
4304/**
4305 * Assigner la resolution de la vue avec animation
4306 *
4307 * @param {float} resolution : resolution de la vue
4308 *
4309 * @return void
4310 */
4311function setViewResolutionWithAnimation(resolution) {
4312    // zoom à partir de la resolution courante
4313    var zoom = ol.animation.zoom({
4314        resolution: oMap.getView().getResolution(),
4315    });
4316    oMap.beforeRender(zoom);
4317    oMap.getView().setResolution(resolution);
4318}
4319
4320/**
4321 * Assigner la resolution (avec un facteur) de la vue avec animation
4322 *
4323 * @param {float} factor : facteur de modification de la resolution de la vue (zoom+1=> factor=0.5, zoom-1=>factor=2)
4324 *
4325 * @return void
4326 */
4327function setViewResolutionFactorWithAnimation(factor) {
4328    // zoom à partir de la resolution courante
4329    var zoom = ol.animation.zoom({
4330        resolution: oMap.getView().getResolution(),
4331    });
4332    oMap.beforeRender(zoom);
4333    oMap.getView().setResolution(oMap.getView().getResolution() * factor);
4334}
4335
4336/**
4337 * Assigner le zoom de la vue avec animation
4338 *
4339 * @param {integer} zoom : zoom de la vue
4340 *
4341 * @return void
4342 */
4343function setViewZoomWithAnimation(zoom) {
4344    var resolution = getResolutionOfZoom(zoom);
4345    if (resolution !== undefined) setViewResolutionWithAnimation(resolution);
4346}
4347
4348/**
4349 * Assigner les coordonnées du centre de la vue avec animation
4350 *
4351 * @param {float[]} location : vecteur des coordonnées du centre de la vue
4352 *
4353 * @return void
4354 */
4355function setViewCenterWithAnimation(location) {
4356    // pan à partir du centre courant
4357    var pan = ol.animation.pan({
4358        source: oMap.getView().getCenter(),
4359    });
4360    oMap.beforeRender(pan);
4361    oMap.getView().setCenter(location);
4362}
4363
4364/**
4365 * Gérer une interaction (clic ou survol) sur cluster
4366 *
4367 * @param {object} evt
4368 *
4369 * @return boolean
4370 */
4371function manageClusterInteraction(evt) {
4372    var pixel = oMap.getEventPixel(evt.originalEvent);
4373    var cluster = oMap.forEachFeatureAtPixel(
4374        pixel,
4375        function (feature, layer) {
4376            var tFeature = feature.get('features');
4377            if (tFeature !== undefined && tFeature.length !== 1) return feature;
4378            return false;
4379        },
4380        null,
4381        function (layer) {
4382            return DDELayerFilter(layer);
4383        }
4384    );
4385
4386    if (cluster) {
4387        var tCoordinate = cluster.getGeometry().getCoordinates(),
4388            nZoom = oMap.getView().getZoom();
4389        if (nZoom !== undefined) setViewZoomAndCenter(nZoom + 1, tCoordinate[0], tCoordinate[1], defaultOsmProjection);
4390        return true;
4391    }
4392
4393    return false;
4394}
4395
4396/**
4397 * Mettre à jour le ou les calques des repere à une date précise
4398 *
4399 * @parm {string} date : date de mise à jour du calque des reperes sous le format : timestamp|timestamp ou timestamp
4400 *
4401 * @return boolean
4402 */
4403function updateLayerRepereAtDate(date) {
4404    var layerConfig = getLayerConfig('repere'),
4405        option = getLayerConfiguration(layerConfig);
4406
4407    if (layerConfig !== undefined && layerConfig.name === 'layer-repere' && option !== null) {
4408        option.date = date;
4409        DDEAddSpecialLayer(option);
4410        return true;
4411    }
4412
4413    return false;
4414}
4415
4416/**
4417 * Afficher le calque en entree et cacher les autres calques ( a sauvegader) sauf les calques: 'group-tiles', 'layer-fond', 'layer-outline' et 'layer-link'
4418 * si le calque est visible afficher les calques sauvegadés
4419 *
4420 * @param {string} layerName : nom du calque à afficher
4421 *
4422 * @return boolean
4423 */
4424function toggleDisplayThisLayer(layerName) {
4425    var targetLayer = getLayerByName(layerName);
4426
4427    if (targetLayer !== null) {
4428        var targetLayerVisibility = !targetLayer.getVisible();
4429
4430        // change visility of target layer
4431        targetLayer.setVisible(targetLayerVisibility);
4432        if (targetLayerVisibility) {
4433            //target layer : visible
4434            var allwaysVisibleLayers = ['group-tiles', 'layer-fond', 'layer-outline', 'layer-link'];
4435            allwaysVisibleLayers.push(layerName);
4436            visibleLayers = [];
4437            oMap.getLayers().forEach(function (lyr) {
4438                if (typeof lyr === 'object') {
4439                    var currentLayerName = lyr.get('name'),
4440                        currentLayerVisibility = lyr.getVisible();
4441
4442                    if (allwaysVisibleLayers.indexOf(currentLayerName) === -1 && currentLayerVisibility === true) {
4443                        //other layers
4444                        lyr.setVisible(false);
4445                        setElementAttribute("[data-layer='" + currentLayerName + "']", 'aria-pressed', 'false');
4446                        visibleLayers.push(currentLayerName);
4447                    }
4448                }
4449            });
4450        } else {
4451            //target layer : invisible
4452            if (typeof visibleLayers !== 'undefined' && visibleLayers.length > 0) {
4453                for (var i = 0; i < visibleLayers.length; i++) {
4454                    var currentLayer = getLayerByName(visibleLayers[i]);
4455                    if (currentLayer !== null) {
4456                        currentLayer.setVisible(true);
4457                        setElementAttribute("[data-layer='" + visibleLayers[i] + "']", 'aria-pressed', 'true');
4458                    }
4459                }
4460
4461                //empty visibleLayers
4462                visibleLayers.length = 0;
4463            }
4464        }
4465
4466        return true;
4467    }
4468
4469    return false;
4470}
4471
4472/**
4473 * Afficher exclusivement la liste des calques en entrée et masquer le reste des calques sauf les calques visibles par défaut: 'group-tiles', 'layer-fond', 'layer-outline' et 'layer-link'
4474 *
4475 * @param {string[]} : tableau des noms des calques à afficher
4476 *
4477 * @return boolean : affichage ou pas d'au moins un calque
4478 */
4479function displayOnlyTheseLayers(layers) {
4480    var bModif = false;
4481    if (layers instanceof Array) {
4482        var defaultVisibleLayers = ['group-tiles', 'layer-fond', 'layer-outline', 'layer-link'],
4483            visibleLayers = defaultVisibleLayers.concat(layers);
4484
4485        oMap.getLayers().forEach(function (lyr) {
4486            if (typeof lyr === 'object') {
4487                var currentlayer = lyr.get('name');
4488
4489                if (visibleLayers.indexOf(currentlayer) === -1) {
4490                    lyr.setVisible(false);
4491                    setElementAttribute("[data-layer='" + currentlayer + "']", 'aria-pressed', 'false');
4492                } else {
4493                    lyr.setVisible(true);
4494                    setElementAttribute("[data-layer='" + currentlayer + "']", 'aria-pressed', 'true');
4495                    bModif = true;
4496                }
4497            }
4498        });
4499    }
4500
4501    return bModif;
4502}
4503
4504/**
4505 * initilaiser l'attribut d'un élement
4506 *
4507 * @param {string} selectorName : nom du selecteur (document.querySelector)
4508 * @param {string} attribute : nom de l'attribut à modifier
4509 * @param {string} value : valeur de l'attribut
4510 *
4511 * @return boolean
4512 */
4513function setElementAttribute(selectorName, attribute, value) {
4514    var element = document.querySelector(selectorName);
4515    if (element !== null && element.hasAttribute(attribute)) {
4516        element.setAttribute(attribute, value);
4517        return true;
4518    }
4519
4520    return false;
4521}
4522
4523/**
4524 * Afficher la listes des features sous forme d'une table HTML
4525 *
4526 * @param {ol.Feature[]} features : tables des feature
4527 * @param {string} tableId : identifiant de la table
4528 *
4529 * @return void
4530 */
4531function displayFeaturesInHtmlTable(features, tableId) {
4532    var oTable = document.getElementById(tableId);
4533    if (oTable !== null) {
4534        oTable = oTable.getElementsByTagName('tbody')[0];
4535    }
4536    if (features !== null && oTable !== null) {
4537        //delete old rows
4538        deleteHtmlTableRow(tableId);
4539
4540        //insert new rows
4541        for (var i = 0; i < features.length; i++) {
4542            var oFeature = features[i],
4543                row = oTable.insertRow(i),
4544                cell1 = row.insertCell(0),
4545                cell2 = row.insertCell(1),
4546                cell3 = row.insertCell(2);
4547
4548            cell1.innerHTML =
4549                '<img src="' + oFeature.get('url_icone') + '" class="calendarIcone">';
4550            cell2.innerHTML = '<p class="calendarTitre">' + oFeature.get('titre') + '</p>';
4551            cell3.innerHTML = '<p class="calendarDescription">' + oFeature.get('description') + '</p>';
4552        }
4553    }
4554}
4555
4556/**
4557 * Suppression de toutes les lignes d'une table HTML sauf la premiere (titre)
4558 *
4559 * @param {string} tableId : identifiant de la table
4560 *
4561 * @return void
4562 */
4563function deleteHtmlTableRow(tableId) {
4564    var oTable = document.getElementById(tableId);
4565    if (oTable !== null) {
4566        var iRowCount = oTable.rows.length,
4567            iRowIndex = 1;
4568        for (var i = iRowIndex; i < iRowCount; i++) {
4569            oTable.deleteRow(iRowIndex);
4570        }
4571    }
4572}
4573
4574/**
4575 * Met à jour le tableau des évènements visibles
4576 */
4577function ddeOpenLayersMapUpdateFeaturesTable() {
4578    var visibleFeatures = [];
4579    if (
4580        layerConfig.repere.featureDisplayContainerId !== undefined &&
4581        layerConfig.repere.featureDisplayContainerId !== ''
4582    ) {
4583        // Récupère les calques des repères
4584        var repereLayers = getAllRepereLayer();
4585
4586        // Récupère les éléments des calques visibles
4587        for (var layerIndex = 0; layerIndex < repereLayers.length; layerIndex++) {
4588            // Récupère le calque
4589            var repereLayer = getLayerByName(repereLayers[layerIndex]);
4590
4591            // Récupère les éléments
4592            if (!repereLayer.getVisible()) {
4593                continue;
4594            }
4595
4596            // Récupère la source
4597            var layerSource = repereLayer.getSource();
4598            if (layerSource instanceof ol.source.Cluster) {
4599                layerSource = layerSource.getSource();
4600            }
4601
4602            // Ajoute les éléments à la liste
4603            var layerFeatures = layerSource.getFeatures();
4604            for (var featureIndex = 0; featureIndex < layerFeatures.length; featureIndex++) {
4605                visibleFeatures.push(layerFeatures[featureIndex]);
4606            }
4607        }
4608
4609        // Remplit le tableau
4610        displayFeaturesInHtmlTable(visibleFeatures, layerConfig.repere.featureDisplayContainerId);
4611    }
4612}
4613
4614/**
4615 * teste si l'événement et dans sa periodede récurrence ou pas
4616 * si oui il sera affiché, non sinon
4617 * @param recurrence
4618 * @returns
4619 */
4620function DDETestRecurrence(recurrence) {
4621    var today = new Date();
4622    var nToday = today.getUTCDay();
4623
4624    var hToday = today.getUTCHours();
4625    var sH = hToday.toString();
4626    if (hToday < 10) {
4627        sH = '0' + sH;
4628    }
4629    var mToday = today.getUTCMinutes();
4630    var sM = mToday.toString();
4631    if (mToday < 10) {
4632        sM = '0' + mToday.toString();
4633    }
4634    var sHM = sH + ':' + sM;
4635
4636    if (!recurrence['next_day']) {
4637        // l'événement est sur la même journée => on la journée puis les heurs
4638        if (recurrence['jours_recurrence'][nToday]) {
4639            if (sHM >
4639= recurrence['heure_recurrence_debut'] && sHM <= recurrence['heure_recurrence_fin']) {
4640                return true;
4641            }
4642        }
4643    } else {
4644        // l'evenement s'étale sur deux jours => on verifie pour la veille, le jour meme
4645        var day0 = (nToday - 1) % 7;
4646        var day1 = nToday;
4647
4648        // l'évenement a commencé la veille et n'est pas encore fini
4649        if (recurrence['jours_recurrence'][day0] && sHM <= recurrence['heure_recurrence_fin']) {
4650            return true;
4651        }
4652
4653        // l'evénement commence aujourd'hui et ce termine demain
4654        if (recurrence['jours_recurrence'][day1] && sHM >= recurrence['heure_recurrence_debut']) {
4655            return true;
4656        }
4657    }
4658    return false;
4659}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.