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 deL.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 sourceol.source.Vector
, d’une coucheol.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)
)
- utilisez
- 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 coucheScatterplotLayer
- 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], }) ] });
- précisez la structure de nos données géographiques dans
- 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 attributhover
vauttrue
/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 ] } });
- la couleur est basée sur l’état
- modifiez les événements JS pour ajouter/enlever l’attribut
hover
à la feature survoléelet 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(); });