block by ThomasG77 c7d1d5ae918f168323f6b5c82059608c

Create legend for proportional circles in OpenLayers

Full Screen

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <meta name=description content="">
    <meta name=viewport content="width=device-width, initial-scale=1">
    <title>Thematic map with legend</title>
    <link rel="stylesheet" href="https://openlayers.org/en/v4.6.4/css/ol.css" type="text/css">
    <style>
      html, body {
        height: 100%;
        padding: 0;
        margin: 0;
      }
      #map {
        width: 100%;
        height: 100%;
      }
      #popup {
        background-color: white;
        border: solid 1px #888888;
        padding: 10px;
        border-radius: 5px;
      }
      #container {
        position: relative;
        height: inherit;
        width: inherit;
      }
      #canvas, #map {
        position: absolute;
        background-color: white;
      }
      #canvas {
        bottom: 0px;
      }
    </style>
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
    <script src="https://openlayers.org/en/v4.6.4/build/ol.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js"></script>
  </head>
  <body>
    <div id="container">
      <div id="map" class="map"></div>
      <div id="popup"></div>
      <canvas id="canvas"></canvas>
    </div>
  <script src="main.js"></script>
  </body>
</html>

main.js

const radiusCalculation = (val, coeff) => {
  return (val / Math.PI) ** 0.5 * coeff;
};

const vectorLayerSource = new ol.source.Vector({
  url: 'departements-france-2017.json',
  format: new ol.format.GeoJSON()
});

const fillCircle = new ol.style.Fill({
  color: 'rgba(125, 125, 125, 0.6)'
});
const strokeCircle = new ol.style.Stroke({
  color: '#ffffff',
  width: 1
});

const styleProportionalCircle = (feature, resolution) => {
  const extent = feature.getGeometry().getExtent();
  const center = ol.extent.getCenter(extent);
  const geom = new ol.geom.Point(center);
  return new ol.style.Style({
    geometry: geom,
    image: new ol.style.Circle({
      stroke: strokeCircle,
      fill: fillCircle,
      radius: radiusCalculation(feature.get('pop_tot'), 0.02)
    }),
    zIndex: feature.get('rank')
  });
};

const vectorLayer = new ol.layer.Vector({
  source: vectorLayerSource,
  style: styleProportionalCircle,
  renderMode: 'image'
});

const rasterLayer = new ol.layer.Tile({
  opacity: 0.4,
  source: new ol.source.XYZ({
    url: 'https://{a-c}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
    attributions:
      '&copy; Openstreetmap France | &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
  })
});

const map = new ol.Map({
  layers: [rasterLayer, vectorLayer],
  controls: ol.control.defaults({
    attributionOptions: {
      collapsed: false
    }
  }),
  target: 'map',
  view: new ol.View({
    center: ol.proj.fromLonLat([2.21298, 46.44362]),
    zoom: 6
  })
});

const popupElement = document.getElementById('popup');
var popup = new ol.Overlay({
  element: popupElement
});
map.addOverlay(popup);

const intlNumberFormat = new Intl.NumberFormat('fr-FR');

var displayFeatureInfo = (pixel, coordinate) => {
  var feature = map.forEachFeatureAtPixel(pixel, feature => feature);
  if (feature) {
    const formated = intlNumberFormat.format(feature.get('pop_tot'));
    popupElement.innerHTML = `<b>Département:</b><br>
    ${feature.get('NOM_DEP')} (${feature.get('INSEE_DEP')})<br>
    <b>Population totale</b>:<br>
    ${formated} habitants (rang ${feature.get('rank')})`;
    popup.setPosition(coordinate);
    popupElement.style.display = '';
  } else {
    popupElement.innerHTML = '';
    popupElement.style.display = 'none';
  }
};

map.on('pointermove', e => {
  if (e.dragging) {
    popupElement.style.display = 'none';
    return;
  }
  displayFeatureInfo(e.pixel, e.coordinate);
});

const onChangeListener = vectorLayerSource.on('change', async e => {
  if (vectorLayerSource.getState() == 'ready') {
    ol.Observable.unByKey(onChangeListener);
    const response = await fetch('population-insee-2015.csv');
    const text = await response.text();
    const data = Papa.parse(text, {
      header: true
    });
    const sortedData = data.data
      .slice(0)
      .filter(ds => ds.code_dept.length < 3)
      .sort((a, b) => Number(b.pop_totale) - Number(a.pop_totale))
      .map((ds, i) => {
        ds.rank = i + 1;
        return ds;
      });
    const hashTable = sortedData.reduce((acc, curr) => {
      acc[curr.code_dept] = {
        pop_totale: Number(curr.pop_totale),
        rank: curr.rank
      };
      return acc;
    }, {});
    vectorLayerSource.forEachFeature(feature => {
      var codeDept = feature.get('INSEE_DEP');
      feature.set('pop_tot', hashTable[codeDept].pop_totale);
      feature.set('rank', hashTable[codeDept].rank);
    });
    generateLegend(vectorLayerSource.getFeatures());
  }
});

const generateLegend = features => {
  const vals = features.map(el => el.get('pop_tot'));
  const max = Math.max(...vals);
  const min = Math.min(...vals);
  const canvas = document.getElementById('canvas');
  var vectorContext = ol.render.toContext(canvas.getContext('2d'), {
    size: [140, 80]
  });

  [min, (min + max) / 3, max]
    .slice(0)
    .reverse()
    .forEach(val => {
      const radius = radiusCalculation(val, 0.02);
      const text = new ol.style.Text({
        offsetX: 60,
        offsetY: -radius,
        text: `${intlNumberFormat.format(val.toFixed(0))} habs`
      });
      const newStyle = new ol.style.Style({
        image: new ol.style.Circle({
          stroke: new ol.style.Stroke({
            color: '#ffffff',
            width: 1
          }),
          fill: fillCircle,
          radius: radius
        }),
        text: text
      });
      vectorContext.setStyle(newStyle);
      vectorContext.drawGeometry(new ol.geom.Point([30, 60 - (2 + radius)]));
    });
};

population-insee-2015.csv

code_region,nom_region,code_dept,nom_dept,nb_arrond,nb_cantons,nb_communes,pop_municipale,pop_totale
84,Auvergne-Rhône-Alpes,01,Ain,4,23,408,631877,649012
32,Hauts-de-France,02,Aisne,5,21,804,538659,552529
84,Auvergne-Rhône-Alpes,03,Allier,3,19,317,341613,351626
93,Provence-Alpes-Côte d'Azur,04,Alpes-de-Haute-Provence,4,15,198,161799,166635
93,Provence-Alpes-Côte d'Azur,05,Hautes-Alpes,2,15,167,140916,146060
93,Provence-Alpes-Côte d'Azur,06,Alpes-Maritimes,2,27,163,1082440,1097556
84,Auvergne-Rhône-Alpes,07,Ardèche,3,17,339,324209,333781
44,Grand Est,08,Ardennes,4,19,452,277752,285612
76,Occitanie,09,Ariège,3,13,331,152499,157904
44,Grand Est,10,Aube,3,17,431,309056,316888
76,Occitanie,11,Aude,3,19,436,366957,376667
76,Occitanie,12,Aveyron,3,23,285,279169,290199
93,Provence-Alpes-Côte d'Azur,13,Bouches-du-Rhône,4,29,119,2016622,2045149
28,Normandie,14,Calvados,4,25,538,693579,709986
84,Auvergne-Rhône-Alpes,15,Cantal,3,15,247,146219,151920
75,Nouvelle-Aquitaine,16,Charente,3,19,383,353613,366289
75,Nouvelle-Aquitaine,17,Charente-Maritime,5,27,469,639938,658529
24,Centre-Val de Loire,18,Cher,3,19,290,308992,317101
75,Nouvelle-Aquitaine,19,Corrèze,3,19,283,241871,250077
94,Corse,2A,Corse-du-Sud,2,11,124,152730,155285
94,Corse,2B,Haute-Corse,3,15,236,174553,177438
27,Bourgogne-Franche-Comté,21,Côte-d'Or,3,23,704,533147,546601
53,Bretagne,22,Côtes-d'Armor,4,27,356,598357,618114
75,Nouvelle-Aquitaine,23,Creuse,2,15,258,120365,124569
75,Nouvelle-Aquitaine,24,Dordogne,4,25,520,415417,428032
27,Bourgogne-Franche-Comté,25,Doubs,3,19,578,536959,551143
84,Auvergne-Rhône-Alpes,26,Drôme,3,19,367,504637,519264
28,Normandie,27,Eure,3,23,602,601948,619392
24,Centre-Val de Loire,28,Eure-et-Loir,4,15,375,434035,445361
53,Bretagne,29,Finistère,4,27,279,907796,936292
76,Occitanie,30,Gard,3,23,353,738189,754170
76,Occitanie,31,Haute-Garonne,3,27,588,1335103,1361286
76,Occitanie,32,Gers,3,17,462,190932,198213
75,Nouvelle-Aquitaine,33,Gironde,6,33,538,1548478,1578386
76,Occitanie,34,Hérault,3,25,343,1120190,1140030
53,Bretagne,35,Ille-et-Vilaine,4,27,345,1042884,1070285
24,Centre-Val de Loire,36,Indre,4,13,243,224200,230546
24,Centre-Val de Loire,37,Indre-et-Loire,3,19,273,604966,619651
84,Auvergne-Rhône-Alpes,38,Isère,3,29,521,1251060,1278347
27,Bourgogne-Franche-Comté,39,Jura,3,17,509,260587,270474
75,Nouvelle-Aquitaine,40,Landes,2,15,330,403234,416642
24,Centre-Val de Loire,41,Loir-et-Cher,3,15,276,333050,343392
84,Auvergne-Rhône-Alpes,42,Loire,3,21,326,759411,775977
84,Auvergne-Rhône-Alpes,43,Haute-Loire,3,19,257,227034,234555
52,Pays de la Loire,44,Loire-Atlantique,3,31,212,1365227,1400585
24,Centre-Val de Loire,45,Loiret,3,21,326,673349,691291
76,Occitanie,46,Lot,3,17,322,173400,179573
75,Nouvelle-Aquitaine,47,Lot-et-Garonne,4,21,319,333417,343059
76,Occitanie,48,Lozère,2,13,158,76309,80176
52,Pays de la Loire,49,Maine-et-Loire,4,21,186,810186,833080
28,Normandie,50,Manche,4,27,477,499287,517500
44,Grand Est,51,Marne,5,23,616,572293,585622
44,Grand Est,52,Haute-Marne,3,17,427,179154,184987
52,Pays de la Loire,53,Mayenne,3,17,255,307940,318079
44,Grand Est,54,Meurthe-et-Moselle,4,23,592,734403,748528
44,Grand Est,55,Meuse,3,17,501,190626,196681
53,Bretagne,56,Morbihan,3,21,253,744813,767538
44,Grand Est,57,Moselle,5,27,727,1044486,1064593
27,Bourgogne-Franche-Comté,58,Nièvre,4,17,309,211747,219019
32,Hauts-de-France,59,Nord,6,41,648,2605238,2641081
32,Hauts-de-France,60,Oise,4,21,687,821552,841252
28,Normandie,61,Orne,3,21,394,286618,295936
32,Hauts-de-France,62,Pas-de-Calais,7,39,891,1472648,1496824
84,Auvergne-Rhône-Alpes,63,Puy-de-Dôme,5,31,467,647501,664386
75,Nouvelle-Aquitaine,64,Pyrénées-Atlantiques,3,27,546,670032,690788
76,Occitanie,65,Hautes-Pyrénées,3,17,470,228582,236017
76,Occitanie,66,Pyrénées-Orientales,3,17,226,471038,479421
44,Grand Est,67,Bas-Rhin,5,23,517,1116658,1134800
44,Grand Est,68,Haut-Rhin,4,17,366,762607,777878
84,Auvergne-Rhône-Alpes,69,Rhône,2,13,280,1821995,1852002
27,Bourgogne-Franche-Comté,70,Haute-Saône,2,17,542,237706,245130
27,Bourgogne-Franche-Comté,71,Saône-et-Loire,5,29,567,555408,573281
52,Pays de la Loire,72,Sarthe,3,21,361,568445,583151
84,Auvergne-Rhône-Alpes,73,Savoie,3,19,285,428204,441669
84,Auvergne-Rhône-Alpes,74,Haute-Savoie,4,17,281,793938,816748
11,Île-de-France,75,Paris,1,,1,2206488,2228409
28,Normandie,76,Seine-Maritime,3,35,711,1257699,1283249
11,Île-de-France,77,Seine-et-Marne,5,23,510,1390121,1412250
11,Île-de-France,78,Yvelines,4,21,262,1427291,1454532
75,Nouvelle-Aquitaine,79,Deux-Sèvres,3,17,293,374435,385395
32,Hauts-de-France,80,Somme,4,23,779,571879,584143
76,Occitanie,81,Tarn,2,23,319,386543,398190
76,Occitanie,82,Tarn-et-Garonne,2,15,195,255274,261452
93,Provence-Alpes-Côte d'Azur,83,Var,3,23,153,1048652,1065985
93,Provence-Alpes-Côte d'Azur,84,Vaucluse,3,17,151,557548,569618
52,Pays de la Loire,85,Vendée,3,17,267,666714,685673
75,Nouvelle-Aquitaine,86,Vienne,3,19,274,434887,445927
75,Nouvelle-Aquitaine,87,Haute-Vienne,3,21,200,375795,384226
44,Grand Est,88,Vosges,3,17,507,372016,385043
27,Bourgogne-Franche-Comté,89,Yonne,3,21,428,340903,351302
27,Bourgogne-Franche-Comté,90,Territoire de Belfort,1,9,102,144483,147799
11,Île-de-France,91,Essonne,3,21,196,1276233,1294240
11,Île-de-France,92,Hauts-de-Seine,3,23,36,1601569,1620776
11,Île-de-France,93,Seine-Saint-Denis,3,21,40,1592663,1603095
11,Île-de-France,94,Val-de-Marne,3,25,47,1372389,1384068
11,Île-de-France,95,Val-d'Oise,3,21,185,1215390,1231356
01,Guadeloupe,971,Guadeloupe,2,21,32,397990,404542
02,Martinique,972,Martinique,4,,34,380877,386875
03,Guyane,973,Guyane,2,,22,259865,262381
04,La Réunion,974,La Réunion,4,25,24,850727,860896