Edit

WebGL 点图层

webgl15 point2 layer4 feature12

在下边列表中选择一个提前定义的样式,或者作为 JSON 手动编辑。  

本示例演示使用 WebGL-optimized 图层在地图展示海量数据点

本示例演示使用 WebGLPointsLayer 在地图展示海量数据点。 此图层赋予了一个 JSON 格式的样式,这个样式允许对最终的展现进行适当的定制。 可以使用以下

  • 读取操作符:

    • ['get', 'attributeName'] fetches a feature attribute (it will be prefixed by a_ in the shader) Note: those will be taken from the attributes provided to the renderer
    • ['var', 'varName'] fetches a value from the style variables, or 0 if undefined
    • ['time'] returns the time in seconds since the creation of the layer
    • ['zoom'] returns the current zoom level
    • ['resolution'] returns the current resolution
  • 数学操作符:

    • ['*', value1, value2] multiplies value1 by value2
    • ['/', value1, value2] divides value1 by value2
    • ['+', value1, value2] adds value1 and value2
    • ['-', value1, value2] subtracts value2 from value1
    • ['clamp', value, low, high] clamps value between low and high
    • ['%', value1, value2] returns the result of value1 % value2 (modulo)
    • ['^', value1, value2] returns the value of value1 raised to the value2 power
  • 转换操作符:

    • ['case', condition1, output1, ...conditionN, outputN, fallback] selects the first output whose corresponding condition evaluates to true. If no match is found, returns the fallback value. All conditions should be boolean, output and fallback can be any kind.
    • ['match', input, match1, output1, ...matchN, outputN, fallback] compares the input value against all provided matchX values, returning the output associated with the first valid match. If no match is found, returns the fallback value. input and matchX values must all be of the same type, and can be number or string. outputX and fallback values must be of the same type, and can be of any kind.
    • ['interpolate', interpolation, input, stop1, output1, ...stopN, outputN] returns a value by interpolating between pairs of inputs and outputs; interpolation can either be ['linear'] or ['exponential', base] where base is the rate of increase from stop A to stop B (i.e. power to which the interpolation ratio is raised); a value of 1 is equivalent to ['linear']. input and stopX values must all be of type number. outputX values can be number or color values. Note: input will be clamped between stop1 and stopN, meaning that all output values will be comprised between output1 and outputN.
  • 逻辑操作符 operators:

    • ['<', value1, value2] returns true if value1 is strictly lower than value2, or false otherwise.
    • ['<=', value1, value2] returns true if value1 is lower than or equals value2, or false otherwise.
    • ['>', value1, value2] returns true if value1 is strictly greater than value2, or false otherwise.
    • ['>=', value1, value2] returns true if value1 is greater than or equals value2, or false otherwise.
    • ['==', value1, value2] returns true if value1 equals value2, or false otherwise.
    • ['!=', value1, value2] returns true if value1 does not equal value2, or false otherwise.
    • ['!', value1] returns false if value1 is true or greater than 0, or true otherwise.
    • ['between', value1, value2, value3] returns true if value1 is contained between value2 and value3 (inclusively), or false otherwise.
  • 转变 operators:

    • ['array', value1, ...valueN] creates a numerical array from number values; please note that the amount of values can currently only be 2, 3 or 4.
    • ['color', red, green, blue, alpha] creates a color value from number values; the alpha parameter is optional; if not specified, it will be set to 1. Note: red, green and blue components must be values between 0 and 255; alpha between 0 and 1. Values can either be literals or another operator, as they will be evaluated recursively. Literal values can be of the following types:
  • boolean

  • number

  • string

main.js
import GeoJSON from 'ol/format/GeoJSON.js';
import Map from 'ol/Map.js';
import OSM from 'ol/source/OSM.js';
import TileLayer from 'ol/layer/Tile.js';
import Vector from 'ol/source/Vector.js';
import View from 'ol/View.js';
import WebGLPointsLayer from 'ol/layer/WebGLPoints.js';

const vectorSource = new Vector({
  url: 'data/geojson/world-cities.geojson',
  format: new GeoJSON(),
  wrapX: true,
});

const predefinedStyles = {
  'icons': {
    symbol: {
      symbolType: 'image',
      src: 'data/icon.png',
      size: [18, 28],
      color: 'lightyellow',
      rotateWithView: false,
      offset: [0, 9],
    },
  },
  'triangles': {
    symbol: {
      symbolType: 'triangle',
      size: 18,
      color: [
        'interpolate',
        ['linear'],
        ['get', 'population'],
        20000,
        '#5aca5b',
        300000,
        '#ff6a19',
      ],
      rotateWithView: true,
    },
  },
  'triangles-latitude': {
    symbol: {
      symbolType: 'triangle',
      size: [
        'interpolate',
        ['linear'],
        ['get', 'population'],
        40000,
        12,
        2000000,
        24,
      ],
      color: [
        'interpolate',
        ['linear'],
        ['get', 'latitude'],
        -60,
        '#ff14c3',
        -20,
        '#ff621d',
        20,
        '#ffed02',
        60,
        '#00ff67',
      ],
      offset: [0, 0],
      opacity: 0.95,
    },
  },
  'circles': {
    symbol: {
      symbolType: 'circle',
      size: [
        'interpolate',
        ['linear'],
        ['get', 'population'],
        40000,
        8,
        2000000,
        28,
      ],
      color: ['match', ['get', 'hover'], 1, '#ff3f3f', '#006688'],
      rotateWithView: false,
      offset: [0, 0],
      opacity: [
        'interpolate',
        ['linear'],
        ['get', 'population'],
        40000,
        0.6,
        2000000,
        0.92,
      ],
    },
  },
  'circles-zoom': {
    symbol: {
      symbolType: 'circle',
      size: ['interpolate', ['exponential', 2.5], ['zoom'], 2, 1, 14, 32],
      color: ['match', ['get', 'hover'], 1, '#ff3f3f', '#006688'],
      offset: [0, 0],
      opacity: 0.95,
    },
  },
  'rotating-bars': {
    symbol: {
      symbolType: 'square',
      rotation: ['*', ['time'], 0.1],
      size: [
        'array',
        4,
        [
          'interpolate',
          ['linear'],
          ['get', 'population'],
          20000,
          4,
          300000,
          28,
        ],
      ],
      color: [
        'interpolate',
        ['linear'],
        ['get', 'population'],
        20000,
        '#ffdc00',
        300000,
        '#ff5b19',
      ],
      offset: [
        'array',
        0,
        [
          'interpolate',
          ['linear'],
          ['get', 'population'],
          20000,
          2,
          300000,
          14,
        ],
      ],
    },
  },
};

const map = new Map({
  layers: [
    new TileLayer({
      source: new OSM(),
    }),
  ],
  target: document.getElementById('map'),
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});

let literalStyle;
let pointsLayer;

let selected = null;

map.on('pointermove', function (ev) {
  if (selected !== null) {
    selected.set('hover', 0);
    selected = null;
  }

  map.forEachFeatureAtPixel(ev.pixel, function (feature) {
    feature.set('hover', 1);
    selected = feature;
    return true;
  });
});

function refreshLayer(newStyle) {
  const previousLayer = pointsLayer;
  pointsLayer = new WebGLPointsLayer({
    source: vectorSource,
    style: newStyle,
  });
  map.addLayer(pointsLayer);

  if (previousLayer) {
    map.removeLayer(previousLayer);
    previousLayer.dispose();
  }
  literalStyle = newStyle;
}

const spanValid = document.getElementById('style-valid');
const spanInvalid = document.getElementById('style-invalid');
function setStyleStatus(errorMsg) {
  const isError = typeof errorMsg === 'string';
  spanValid.style.display = errorMsg === null ? 'initial' : 'none';
  spanInvalid.firstElementChild.innerText = isError ? errorMsg : '';
  spanInvalid.style.display = isError ? 'initial' : 'none';
}

const editor = document.getElementById('style-editor');
editor.addEventListener('input', function () {
  const textStyle = editor.value;
  try {
    const newLiteralStyle = JSON.parse(textStyle);
    if (JSON.stringify(newLiteralStyle) !== JSON.stringify(literalStyle)) {
      refreshLayer(newLiteralStyle);
    }
    setStyleStatus(null);
  } catch (e) {
    setStyleStatus(e.message);
  }
});

const select = document.getElementById('style-select');
select.value = 'circles';
function onSelectChange() {
  const style = select.value;
  const newLiteralStyle = predefinedStyles[style];
  editor.value = JSON.stringify(newLiteralStyle, null, 2);
  try {
    refreshLayer(newLiteralStyle);
    setStyleStatus();
  } catch (e) {
    setStyleStatus(e.message);
  }
}
onSelectChange();
select.addEventListener('change', onSelectChange);

// animate the map
function animate() {
  map.render();
  window.requestAnimationFrame(animate);
}
animate();
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>WebGL points layer</title>
    <link rel="stylesheet" href="node_modules/ol/ol.css">
    <style>
      .map {
        width: 100%;
        height: 400px;
      }
    </style>
  </head>
  <body>
    <div id="map" class="map"></div>
    Choose a predefined style from the list below or edit it as JSON manually.
    <select id="style-select">
      <option value="icons">Icons</option>
      <option value="triangles">Triangles, color related to population</option>
      <option value="triangles-latitude">Triangles, color related to latitude</option>
      <option value="circles">Circles, size related to population</option>
      <option value="circles-zoom">Circles, size related to zoom</option>
      <option value="rotating-bars">Rotating bars</option>
    </select>
    <textarea style="width: 100%; height: 20rem; font-family: monospace; font-size: small;" id="style-editor"></textarea>
    <small>
      <span id="style-valid" style="display: none; color: forestgreen">✓ style is valid</span>
      <span id="style-invalid" style="display: none; color: grey">✗ <span>style not yet valid...</span></span>
      &nbsp;
    </small>
    <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
    <script src="./resources/elm-pep.js"></script>
    <script type="module" src="main.js"></script>
  </body>
</html>
package.json
{
  "name": "webgl-points-layer",
  "dependencies": {
    "ol": "7.3.0"
  },
  "devDependencies": {
    "vite": "^3.2.3"
  },
  "scripts": {
    "start": "vite",
    "build": "vite build"
  }
}