PageSourceSearch

https://www.marseilletourisme.fr/static/js/openlayers-map.js

js marseilletourisme.fr collected 2026-10-02 07:51:22 UTC 18,135 bytes, 581 lines download raw bytes

1/**
2    settings.geojsonObject
3    settings.layerStyle
4    settings.target
5*/
6
7
8// Feel free to add/edit styles here.
9// They are used as "constants"
10
11const iconFont = 'Font Awesome 5 Pro';
12const iconFontText = "\uf3c5";
13const iconFontWeight = 900;
14const iconSize = 40;
15const iconColor = 'rgba(236, 87, 38, 1)';
16const iconColorSelect = 'rgba(54, 94, 123, 1)';
17const iconColorPros = 'rgba(0, 0, 0, 0.4)';
18const strokeColor = new ol.style.Stroke({ color: 'rgba(255, 255, 255,0.9)'});
19const strokeColorPros = new ol.style.Stroke({ color: 'rgba(0, 0, 0, 0.6)'});
20const clusterColorStroke = 'rgba(255, 255, 255,0.9)';
21const pathStrokeColor = 'rgba(73, 111, 140, 0.9)';
22
23// Set fonticon icon
24function locationText(color,stroke) {
25  return new ol.style.Text({
26    text: iconFontText,
27    font: '900 40px "Font Awesome 5 Pro"',
28    textBaseline: 'bottom',
29    fill: new ol.style.Fill({ color: color }),
30    stroke: stroke,
31  })
32}
33
34function selectText(color,stroke) {
35  return new ol.style.Text({
36    text: iconFontText,
37    font: '900 40px "Font Awesome 5 Pro"',
38    textBaseline: 'bottom',
39    fill: new ol.style.Fill({ color: color }),
40    stroke: stroke,
41  })
42}
43
44// Set cluster style
45function clusterStyle(size, iconColor, strokeColor) {
46  return new ol.style.Style({
47    image: new ol.style.Circle({
48      radius: 18,
49      stroke: new ol.style.Stroke({
50        color: strokeColor
51      }),
52      fill: new ol.style.Fill({
53        color: iconColor,
54      })
55    }),
56    text: new ol.style.Text({
57      text: size.toString(),
58      font:'bold 18px serif',
59      fill: new ol.style.Fill({
60        color: strokeColor
61      })
62    })
63  })
64}
65
66var MSTMapStyles = {
67
68  ITINERARY: new ol.style.Style({
69    stroke: new ol.style.Stroke({
70        color: pathStrokeColor,
71        width: 4
72    }),
73  }),
74  LOCATIONS: new ol.style.Style({
75    text: locationText(iconColor, strokeColor)
76  }),
77  LOCATIONS_CLUSTER: function (feature) {
78    if (!feature.get('features') || feature.get('features').length === 1) {
79      return new ol.style.Style({
80        text: locationText(iconColor, strokeColor)
81      });
82    }
83    let size = feature.get('features').length;
84    return clusterStyle(size, iconColor, clusterColorStroke);
85  },
86  PROS: new ol.style.Style({
87    text: locationText(iconColorPros, strokeColorPros)
88  }),
89  PROS_CLUSTER: function (feature) {
90    if (!feature.get('features') || feature.get('features').length === 1) {
91      return new ol.style.Style({
92        text: locationText(iconColorPros, strokeColorPros)
93      });
94    }
95    let size = feature.get('features').length;
96    return clusterStyle(size, iconColorPros, clusterColorStroke);
97  },
98  DS: new ol.style.Style({
99    text: locationText(iconColor, strokeColor)
100  }),
101  DS_CLUSTER: function (feature) {
102    // if (!feature.get('features') || feature.get('features').length === 1) {
103      return new ol.style.Style({
104        text: locationText(iconColor, strokeColor),
105      });
106    // }
107    // let size = feature.get('features').length;
108    // return clusterStyle(size, iconColor, clusterColorStroke);
109  },
110};
111
112class MSTMap {
113  constructor (identifier, settings = []) {
114    let self = this;
115    // Defaults
116    let options = {
117      onClickEmpty: function () {},
118    };
119    $.extend(options, settings)
120
121    /****
122     * PROPERTIES
123     */
124    this.map = undefined;
125    this.attribution = undefined;
126    this.layers = {};
127    this.onPointerMove = {};
128    this.onPointerClick = {};
129    this.popupMouseOver = undefined;
130    this.popupClick = undefined;
131    this.clickedFeature = undefined;
132    this.hoveredFeature = undefined;
133    this.view = undefined;
134    this.menu = undefined;
135    this.select = undefined;
136    this.featuresList = {};
137    this.options = options;
138
139    // Types of geometry that are interactable (hover, click)
140    this.INTERACTABLE_TYPES = ['Point'];
141    // Contributors that will be displayed (html format)
142    this.CONTRIBUTORS = '© <a href="/">Marseille Tourisme</a> & \
143        <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.';
144    // Minimum size of the map for contributors to appear expanded in pixels. (they will appear collapsed if size is inferior)
145    this.MIN_SIZE_EXPANDED_CONTRIBUTORS = 9999;
146    // Projection used by the DB is 4326 (World Geodetic System 84)
147    this.DATA_PROJECTION = 'EPSG:4326';
148    // Projection used by the map view is 3857 (Web Mercator projection)
149    this.MAPVIEW_PROJECTION = 'EPSG:3857';
150
151
152    this.attribution = new ol.control.Attribution({
153        collapsible: true,
154    });
155
156    let layers = Array(new ol.layer.Tile({
157      source: new ol.source.XYZ({
158          url: MAP_TILE_XYZ_URL,
159          attributions: [this.CONTRIBUTORS],
160      }),
161      preload: 10
162    }));
163
164    this.view = new ol.View({
165      projection: this.MAPVIEW_PROJECTION,
166      center: [MAP_INITIAL_LON, MAP_INITIAL_LAT],
167      zoom: MAP_MIN_ZOOM,
168      minZoom: MAP_MIN_ZOOM,
169      maxZoom: MAP_MAX_ZOOM
170    });
171
172    this.map = new ol.Map({
173      layers: layers,
174      target: identifier,
175      interactions: ol.interaction.defaults({
176          //mouseWheelZoom: true
177          constrainResolution: true,
178          onFocusOnly: true
179      }),
180      view: this.view,
181      controls: [
182          new ol.control.ScaleLine({
183              units: 'metric'
184          }),
185          new ol.control.Zoom({
186            'zoomInTipLabel': "{% trans 'Zoom in' %}",
187            'zoomOutTipLabel': "{% trans 'Zoom out' %}"
188          }),
189      ],
190    });
191    this.map.addControl(this.attribution);
192
193    window.addEventListener('resize', function () {
194      self.checkSize()
195    });
196
197    // ADD AN OVERLAY TO DISPLAY FEATURES
198    this.menu = new ol.control.Overlay({
199      closeBox : true,
200      className: "slide-left menu",
201      content: $("#menu").get(0),
202    });
203    this.map.addControl(this.menu);
204
205    // ADD A SELECT STATE
206    this.select =  new ol.interaction.Select({
207      style: new ol.style.Style({
208        text: locationText(iconColorSelect,strokeColor)
209      }),
210    });
211    this.map.addInteraction(this.select);
212
213    this.popupMouseOver = new Popup();
214    this.map.addOverlay(this.popupMouseOver);
215    this.map.on('pointermove', function(evt) {
216      // If that's a drag event
217      if (evt.dragging) { self.popupMouseOver.hide(); return; }
218      // Find the feature
219      var feature = self.getInteractableFeatureAtPixel(evt.pixel);
220      // Last feature hovered was the same. Return
221      if (feature && self.hoveredFeature && self.hoveredFeature.ol_uid == feature.ol_uid) return;
222      self.hoveredFeature = feature;
223      if (feature !== null) {
224        let subFeatures = feature.get('features');
225        // Fix : in some cases (TBD), only one feature is returned by getInterectableFeatureAtPixel
226        if (subFeatures === undefined) {
227          subFeatures = [feature]
228        }
229        // Array of features with the same layer_id
230        let finalFeatures = []
231        subFeatures.forEach(function (ft) {
232          if (finalFeatures.length === 0) {
233            // First item
234            finalFeatures.push(ft)
235          } else {
236            if (ft.layer_id === finalFeatures[0].layer_id) {
237              finalFeatures.push(ft)
238            }
239          }
240        });
241
242        // Call the corresponding pointerMove callbacks
243        if (finalFeatures.length > 0) {
244          let onPointerMove = self.onPointerMove[finalFeatures[0].layer_id]
245          if (!onPointerMove.interactWithCluster && subFeatures.length > 1) {
246            // Disable popup & interaction for clusters if not needed
247            self.hidePopup(self.popupMouseOver); return;
248          }
249          self.setCursorStyle('pointer');
250          let featureIsClicked = (self.getClickedFeature() && feature.ol_uid === self.getClickedFeature().ol_uid)
251          if (onPointerMove.interactWithCluster) {
252            onPointerMove.callback(self, evt, finalFeatures, featureIsClicked);
253          } else {
254            onPointerMove.callback(self, evt, finalFeatures[0], featureIsClicked);
255          }
256        }
257
258      } else {
259        // Default cursor + hide popup
260        self.hidePopup(self.popupMouseOver);
261      }
262    });
263
264    this.popupClick = new Popup();
265    this.map.addOverlay(this.popupClick);
266    this.map.on('click', function(evt) {
267      // If that's a drag event
268      if (evt.dragging) { self.popupClick.hide(); return; }
269      self.hidePopups()
270      // Find the feature
271      var feature = self.getInteractableFeatureAtPixel(evt.pixel);
272      if (feature !== null) {
273        let subFeatures = feature.get('features');
274        // Fix : in some cases (TBD), only one feature is returned by getInterectableFeatureAtPixel
275        if (subFeatures === undefined) {
276          subFeatures = [feature]
277        }
278        // Array of features with the same layer_id
279        let finalFeatures = []
280        subFeatures.forEach(function (ft) {
281          if (finalFeatures.length === 0) {
282            // First item
283            finalFeatures.push(ft)
284          } else {
285            if (ft.layer_id === finalFeatures[0].layer_id) {
286              finalFeatures.push(ft)
287            }
288          }
289        });
290
291        // Call the corresponding pointerClick callbacks
292        if (finalFeatures.length > 0) {
293          let onPointerClick = self.onPointerClick[finalFeatures[0].layer_id]
294          if (!onPointerClick.interactWithCluster && subFeatures.length > 1) {
295            // Disable popup & interaction for clusters if not needed
296            self.hidePopup(self.popupMouseOver); return;
297          }
298          self.setCursorStyle('pointer');
299          if (onPointerClick.interactWithCluster) {
300            onPointerClick.callback(self, evt, finalFeatures);
301          } else {
302            onPointerClick.callback(self, evt, finalFeatures[0]);
303          }
304        }
305      } else {
306        // Default cursor + hide popup
307        self.hidePopup(self.popupClick);
308        self.options.onClickEmpty(self);
309      }
310      self.clickedFeature = feature;
311    });
312
313  }
314
315  getClickedFeature() {
316    return this.clickedFeature;
317  }
318
319  showMenu(content){
320      this.menu.show();
321
322      var contentOld = $("<div>")
323          .append(content);
324      $(".data").html(contentOld);
325  };
326
327  setCenter(source) {
328    this.map.getView().fit(source.getExtent(), {
329      size: this.map.getSize(),
330      maxZoom: 14
331    });
332  }
333
334  checkSize() {
335    // under MIN_SIZE_EXPANDED_CONTRIBUTORS px width, the contributions will be collapsed
336    var small = this.map.getSize()[0] < this.MIN_SIZE_EXPANDED_CONTRIBUTORS;
337    this.attribution.setCollapsible(small);
338    this.attribution.setCollapsed(small);
339  }
340
341  // Settings :
342  // features, style, isCentered (default: false), onPointerMove: defaulted, onPointerClick: defaulted
343  addLayer(settings) {
344
345    // Is used to calculate the layer_id if ID is not given
346    function randomString(length) {
347      var result           = '';
348      var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
349      var charactersLength = characters.length;
350      for ( var i = 0; i < length; i++ ) {
351         result += characters.charAt(Math.floor(Math.random() * charactersLength));
352      }
353      return result;
354    }
355
356    // Defaults
357    let options = {
358      isCentered: false,
359      defaultCenter: null, // Center if features is empty and isCentered is true
360      interactWithCluster: false,
361      zIndex:0,
362      featuresList:'',
363      onPointerMove: function () {},
364      onPointerClick: function () {},
365    };
366    $.extend(options, settings)
367
368    if (!options.features) {
369      throw "addLayer parameter should have an `features` attribute."
370    }
371    if (!options.style) {
372      throw "addLayer parameter should have an `style` attribute."
373    }
374    if (options.id && this.layers[options.id]) {
375      throw "This `id` is already taken. You may use the function updateLayer (still unemplemented) to update the layer with a given id."
376    }
377
378    // Find out an unique identifier for the layer (layer_id)
379    let identifier = options.id
380    while (!identifier) {
381      identifier = randomString(10)
382      if (this.layers[identifier]) identifier = undefined;
383    }
384
385    // All features of this addLayer() call will end up in finalFeatures
386    let finalFeatures = [];
387    let self = this;
388
389    // Since we can feed features with an array of features, act like it's always an array.
390    if (!Array.isArray(options.features)) options.features = [options.features];
391    options.features.forEach(function (geojsonFeature) {
392      let jsonFeatures = new ol.format.GeoJSON({
393        dataProjection: self.DATA_PROJECTION,
394        featureProjection: self.MAPVIEW_PROJECTION
395      }).readFeatures(geojsonFeature);
396      // Push everything in finalFeatures & tag its layer_id in order to find it afterwards
397      for (var i=0; i < jsonFeatures.length; i++) {
398        var feature = jsonFeatures[i];
399        if (feature.getGeometry()) {
400          feature.layer_id = identifier;
401          finalFeatures.push(feature);
402        }
403      }
404    });
405    let source = new ol.source.Vector({
406      features: finalFeatures
407    });
408
409    // if options.style is a function, this means it is a clustered style.
410    // To apply a clustered style, we have to create a ol.source.cluster
411    if (options.style instanceof Function) {
412      var clusterSource = new ol.source.Cluster({
413        distance: parseInt(MAP_CLUSTER_DISTANCE, 10),
414        source: source,
415      });
416      var vector = new ol.layer.Vector({
417        source: clusterSource,
418        style: options.style,
419        zIndex: options.zIndex,
420      });
421    } else {
422      var vector = new ol.layer.Vector({
423        source: source,
424        style: options.style,
425        zIndex: options.zIndex,
426      });
427    }
428
429    // Actually add the layer to the OpenLayers map
430    this.map.addLayer(vector)
431    this.layers[identifier] = finalFeatures;
432
433    // Center on the layer if asked to do so
434    if (options.isCentered) {
435      if (finalFeatures.length > 0) {
436        this.setCenter(source)
437      } else {
438        let centerFeature = new ol.format.GeoJSON({
439          dataProjection: self.DATA_PROJECTION,
440          featureProjection: self.MAPVIEW_PROJECTION
441        }).readFeatures(options.defaultCenter);
442        this.setCenter(new ol.source.Vector({
443          features: centerFeature
444        }));
445      }
446    }
447
448    // Register callbacks functions
449    this.onPointerMove[identifier] = {
450      'interactWithCluster': options.interactWithCluster,
451      'callback': options.onPointerMove,
452    }
453    this.onPointerClick[identifier] = {
454      'interactWithCluster': options.interactWithCluster,
455      'callback': options.onPointerClick,
456    }
457
458    // Preload popup content.
459    // This is useful to preload images and async assets
460    // If we dont preload, the image will load asynchronously and will change the size of the popup after a short time period.
461    // The popup will look OK but the map wont be able to adapt its position to show the whole popup, since the calculation is
462    // done at the popup.show() and not afterwards.
463    // finalFeatures.forEach(function (feature) {
464    //   var img = new Image()
465    //   img.src = feature.get('image')
466    // });
467
468    return {
469      identifier: identifier,
470      features: finalFeatures,
471    }
472  }
473
474  // Hide all popups
475  hidePopups() {
476    if (this.popupMouseOver !== undefined) {
477      this.popupMouseOver.hide();
478    }
479    if (this.popupClick !== undefined) {
480      this.popupClick.hide();
481    }
482  }
483
484  /**
485   *  Default cursor + hide popup
486   */
487  hidePopup(popup) {
488      this.setCursorStyle('');
489
490      if (popup !== undefined){
491          popup.hide();
492      }
493  }
494
495  getFeature(layer_id, ol_uid) {
496    // Get the feature using ol_uid
497    let features = this.layers[layer_id];
498    let feature = features.filter(x => x.ol_uid == ol_uid)[0]
499    return feature
500  }
501
502  centerOnFeature(feature) {
503
504    // Center on it
505    let geometry = feature.getGeometry();
506    let coord = geometry.getCoordinates();
507
508    // Move map in order to offset menu width
509    coord[0] += -1000
510
511    this.view.setCenter(coord);
512  }
513
514  selectFeature(feature) {
515
516    // Empty selected features
517    while (this.select.getFeatures().values_.length > 0) {
518      this.select.getFeatures().pop();
519    }
520    this.select.getFeatures().push(feature);
521
522    // Add selected class
523    $("div.card").removeClass("selected");
524    $("div#ol-id-" + feature.ol_uid ).addClass("selected");
525  }
526
527  showPopup(popup, feature, content, positionModifier) {
528    let geometry = feature.getGeometry();
529    let coord = geometry.getCoordinates();
530    let pixel = this.map.getPixelFromCoordinate(coord);
531    pixel[0] += positionModifier[0];
532    pixel[1] += positionModifier[1];
533    popup.show(this.map.getCoordinateFromPixel(pixel), content);
534  }
535
536  // Return the feature found at this position, null if none.
537  // To find if a feature is interactable, it tries to match its type against the INTERECTABLE_TYPES constant.
538  getInteractableFeatureAtPixel(pixel) {
539    let features = this.map.getFeaturesAtPixel(pixel);
540    if (features !== null) {
541        for (let i = 0; i < features.length; i++) {
542            let feature = features[i];
543            if (this.INTERACTABLE_TYPES.indexOf(feature.getGeometry().getType()) !== -1) {
544                return feature;
545            }
546        }
547    }
548    return null;
549  }
550
551  // Changes the cursor style (pointer for example)
552  setCursorStyle(cursorStyle) {
553    $("#" + this.map.getTarget())[0].style.cursor = cursorStyle;
554  }
555
556  centerOnLocation() {
557    let self = this;
558
559    // set up the geolocation api to track our position
560    var geolocation = new ol.Geolocation({
561      tracking: true,
562      projection: this.map.getView().getProjection(),
563    });
564    // bind the view's projection
565    //geolocation.bindTo('projection', this.view);
566    var stopInterval = function() {};
567    var centerOnPosition = function () {
568      var coordinate = geolocation.getPosition();
569      if (coordinate !== undefined) {
570        self.view.setCenter(coordinate);
571        self.view.setZoom(13);
572        stopInterval();
573      }
574    }
575    var interval = setInterval(centerOnPosition, 500);
576    var stopInterval = function() {
577      clearInterval(interval);
578    };
579
580  }
581}

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.