Aller au contenu

Exercices Restaurants

Exercice - Restaurants NY#

Le but de cet exercice est de visualiser des restaurants de New-York (env. 25000), sous forme de points. Voici les infos que l’on utilisera pour chaque exemple :

  • centrée sur New-York (longitude: -73.9, latitude: 40.7)
  • fond OSM
  • attention, les données ne sont pas en GeoJSON

Nous allons tester plusieurs librairies/configurations pour évaluer la performance de rendu.

Leaflet#

Pour commencer, sur une carte Leaflet :

  • (Dans une nouvelle page basée sur Leaflet)
  • Chargez le fichier restaurants.json et affichez chaque point sous forme de marqueur (par défaut)
  • Aie !
  • Pour améliorer le rendu, utilisez des L.circleMarker au lieu de L.marker
  • Pour améliorer encore, ajoutez l’option preferCanvas sur la carte Leaflet

OpenLayers#

Testons à présent OpenLayers :

  • (Dans une nouvelle page basée sur OpenLayers)
  • Chargez le fichier restaurants.json dans une source ol.source.Vector, d’une couche ol.layer.Vector
    • utilisez fetch
    • créez une feature pour chaque restaurant (new ol.Feature(new ol.geom.Point([lng,lat])))
    • ajoutez à la source (votre_layer.getSource().addFeature(feature))
  • Pour améliorer le rendu, une couche ol.layer.VectorImage peut être utilisée
  • Pour aller plus loin (notamment pour un style dynamique basé sur le zoom), testez une couche ol.layer.WebGLPoints (non stable, non documenté). Voir l’exemple dans le cours

OpenLayers + deck.gl#

La librairie deck.gl (basée sur WebGL) peut également être utilisée conjointement à OpenLayers.

  • chargez la librarie deck.gl
    <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
    
  • créez un nouveau Deck, avec nos données dans une couche ScatterplotLayer
    • précisez la structure de nos données géographiques dans getPosition
      let layerDeck = new deck.Deck({
          initialViewState: {longitude: -73.9, latitude: 40.7, zoom: 12},
          controller: false,
          parent: document.getElementById('map'),
          style: { pointerEvents: 'none', 'z-index': 1 },
          layers: [
              new deck.ScatterplotLayer({
                  id: 'scatterplot-layer',
                  data: 'restaurants.json',
                  pickable: true,
                  opacity: 1,
                  stroked: false,
                  filled: true,
                  radiusMinPixels: 2,
                  radiusMaxPixels: 10,
                  getRadius: 20, // meters
                  getPosition: (d) => [d.lnglat[0], d.lnglat[1]],
                  getFillColor: [0, 0, 255],
                  autoHighlight: true,
                  highlightColor: [255, 0, 0],
              })
          ]
      });
      
  • créez une couche générique dans OpenLayers, synchronisez-là avec la couche Deck et ajoutez-là dans votre carte
    let layerOL = new ol.layer.Layer({
        render({size, viewState}) {
            const [width, height] = size;
            const [longitude, latitude] = ol.proj.toLonLat(viewState.center);
            const zoom = viewState.zoom - 1;
            const bearing = (-viewState.rotation * 180) / Math.PI;
            const deckViewState = {bearing, longitude, latitude, zoom};
            layerDeck.setProps({width, height, viewState: deckViewState});
            layerDeck.redraw();
        }
    });
    map.addLayer(layerOL);
    
  • enfin, ajoutez une popup avec le nom du restaurant au survol
    let layerDeck = new deck.Deck({
        /* */,
        getTooltip: (info) => {
            if (info.object) {
                return {
                    html: `<p>${info.object.name}</p>`,
                    style: {
                        backgroundColor: '#fff',
                        color: '#000',
                        fontSize: '0.8em',
                        borderRadius: 10,
                        padding: 10,
                        boxShadow: '0 2px 3px rgba(0,0,0,.25)',
                        transform: `translate(calc(${info.x}px - 50%), ${info.y - 70}px)`
                    }
                };
            }
        },
    });
    

Maplibre#

Enfin, des librairies comme Maplibre ont un rendu WebGL par défaut

  • créez une nouvelle carte basée sur Maplibre
  • au chargement de la carte, faites un appel AJAX pour récupérer les données
    map.on('load', () => {
        fetch('restaurants.json')
        .then(r => r.json())
        .then(json => {})
    });
    
  • convertissez les données en GeoJSON
    let features = json.map((feature, i) => {
        return {
            type: 'Feature',
            geometry: {
                type: 'Point',
                coordinates: feature.lnglat,
            },
            properties: {
                name: feature.name
            },
            id: i
        }
    });
    
    let geojson = {
        type: 'FeatureCollection',
        features
    }
    
  • ajoutez une nouvelle source de données de type GeoJSON
  • ajoutez une couche basée sur cette source (de type circle)
  • pour l’interactivité, ajoutez une popup, et mettez-là à jour lors du survol du calque
    let popup = new maplibregl.Popup({ className: 'popup', closeButton: false, closeOnClick: false });
    
    map.on('mousemove', 'restaurants', (e) => {
        let coords = e.features[0].geometry.coordinates;
        let html = e.features[0].properties.name;
        popup.setLngLat(coords).setHTML(html).addTo(map);
    });
    map.on('mouseleave', 'restaurants', () => {
        popup.remove();
    });
    

Enfin, pour les changements de styles lors d’interactions :

  • modifiez les styles de votre couche
    • la couleur est basée sur l’état feature-state, dont un attribut hover vaut true/false
    • la taille est basée sur le niveau de zoom
      map.addLayer({
          id: 'restaurants',
          source: 'restaurants',
          type: 'circle',
          paint: {
              'circle-color': [
                  'case',
                  ['boolean', ['feature-state', 'hover'], false],
                  'mediumblue',
                  'deeppink',
              ],
              'circle-radius': [
                  "interpolate",
                  ["exponential", 2],
                  ["zoom"],
                  5,
                  2,
                  15,
                  6
              ]
          }
      });
      
  • modifiez les événements JS pour ajouter/enlever l’attribut hover à la feature survolée
    let hoveredStateId = null;
    map.on('mousemove', 'restaurants', (e) => {
        if (e.features.length > 0) {
            if (hoveredStateId) {
                map.setFeatureState(
                    {source: 'restaurants', id: hoveredStateId},
                    {hover: false}
                );
            }
            hoveredStateId = e.features[0].id;
            map.setFeatureState(
                {source: 'restaurants', id: hoveredStateId},
                {hover: true}
            );
        }
        popup.setLngLat(e.features[0].geometry.coordinates).setHTML(e.features[0].properties.name).addTo(map);
    });
    map.on('mouseleave', 'restaurants', () => {
        if (hoveredStateId) {
            map.setFeatureState(
                {source: 'restaurants', id: hoveredStateId},
                {hover: false}
            );
        }
        hoveredStateId = null;
        popup.remove();
    });