Edit

Populated Places

vector82 interpolate1

 

Scaling and coloring points according to population.

The populated places dataset has a pop_max field that is used in rendering the style for each place. The interpolate operator is used to scale the circle-radius, rendering a point with a radius of 3 for places with a population under 500 thousand, growing to a point with a radius of 10 for places with a population of 10 million or more. The same interpolate operator is used to generate a color ramp for places based on the same pop_max field.

main.js
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import Link from 'ol/interaction/Link.js';
import TileLayer from 'ol/layer/Tile.js';
import VectorLayer from 'ol/layer/Vector.js';
import VectorSource from 'ol/source/Vector.js';
import XYZ from 'ol/source/XYZ.js';

const populatedPlaces = new VectorLayer({
  source: new VectorSource({
    url: 'https://openlayers.org/data/vector/populated-places.json',
    format: new GeoJSON(),
  }),
  style: {
    'circle-stroke-color': 'hsl(0 100% 100% / 0.9)',
    'circle-stroke-width': 0.75,
    'circle-radius': [
      'interpolate',
      ['linear'],
      ['get', 'pop_max'],
      500_000,
      3,
      10_000_000,
      10,
    ],
    'circle-fill-color': [
      'interpolate',
      ['linear'],
      ['get', 'pop_max'],
      1_000_000,
      'hsl(210 100% 40% / 0.9)',
      10_000_000,
      'hsl(0 80% 60% / 0.9)',
    ],
  },
});

const key = 'Get your own API key at https://www.maptiler.com/cloud/';

const map = new Map({
  layers: [
    new TileLayer({
      source: new XYZ({
        attributions:
          '<a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> ' +
          '<a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>',
        url:
          'https://api.maptiler.com/maps/satellite/{z}/{x}/{y}.jpg?key=' + key,
        tileSize: 512,
        maxZoom: 20,
      }),
    }),
    populatedPlaces,
  ],
  target: 'map',
  view: new View({
    center: [0, 0],
    zoom: 1,
  }),
});

map.addInteraction(new Link());

const info = document.getElementById('info');

let currentFeature = null;
function displayFeatureInfo(pixel, width) {
  const feature = map.getFeaturesAtPixel(pixel)[0];
  if (feature) {
    const featurePixel = map.getPixelFromCoordinate(
      feature.getGeometry().getCoordinates(),
    );
    if (featurePixel[0] > width) {
      featurePixel[0] = featurePixel[0] % width;
    } else if (featurePixel[1] < width) {
      featurePixel[0] = width + (featurePixel[0] % width);
    }
    info.style.top = featurePixel[1] + 'px';
    if (featurePixel[0] < width / 2) {
      info.style.left = featurePixel[0] + 'px';
      info.style.right = 'auto';
    } else {
      info.style.right = width - featurePixel[0] + 'px';
      info.style.left = 'auto';
    }
    if (feature !== currentFeature) {
      info.style.visibility = 'visible';
      info.innerHTML =
        feature.get('name') + '<br>' + feature.get('pop_max').toLocaleString();
    }
  } else if (currentFeature) {
    info.style.visibility = 'hidden';
  }
  currentFeature = feature;
}

map.on('pointermove', function (evt) {
  if (evt.dragging) {
    info.style.visibility = 'hidden';
    currentFeature = undefined;
    return;
  }
  displayFeatureInfo(evt.pixel, evt.frameState.size[0]);
});

map.on('click', function (evt) {
  displayFeatureInfo(evt.pixel, evt.frameState.size[0]);
});

map.getTargetElement().addEventListener('pointerleave', function () {
  currentFeature = undefined;
  info.style.visibility = 'hidden';
});
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Populated Places</title>
    <link rel="stylesheet" href="node_modules/ol/ol.css">
    <style>
      .map {
        width: 100%;
        height: 400px;
      }
      #map {
        position: relative;
      }

      #info {
        position: absolute;
        display: inline-block;
        height: auto;
        width: auto;
        z-index: 100;
        background-color: #333;
        color: #fff;
        text-align: center;
        border-radius: 4px;
        padding: 5px;
        left: 50%;
        transform: translateX(3%);
        visibility: hidden;
        pointer-events: none;
      }
    </style>
  </head>
  <body>
    <div id="map" class="map">
      <div id="info">&nbsp;</div>
    </div>

    <script type="module" src="main.js"></script>
  </body>
</html>
package.json
{
  "name": "populated-places",
  "dependencies": {
    "ol": "10.5.0"
  },
  "devDependencies": {
    "vite": "^3.2.3"
  },
  "scripts": {
    "start": "vite",
    "build": "vite build"
  }
}