weacast/weacast-client

View on GitHub
src/layers/scalar-layer.js

Summary

Maintainability
C
1 day
Test Coverage
F
0%
import L from 'leaflet'
import chroma from 'chroma-js'
import * as PIXI from 'pixi.js'
import 'leaflet-pixi-overlay'
import { ForecastLayer } from './forecast-layer'
import { Grid } from './../grid'
import { GridViewer } from './../grid-viewer'

window.chroma = chroma
// WebGL limit
const VERTEX_BUFFER_MAX_SIZE = 65536

function buildMesh (gridView, colorMap, utils, container, opacity) {
  if ((gridView.size[0] * gridView.size[1]) > VERTEX_BUFFER_MAX_SIZE) {
    let subgridViews = gridView.cut()
    subgridViews.forEach(subgridView => buildMesh(subgridView, colorMap, utils, container, opacity))
  } else {
    let vertices = []
    let colors = []
    let indices = []
    let width = gridView.size[0] + (gridView.sew ? 1 : 0)
    let height = gridView.size[1]
    for (let j = 0; j < height; j++) {
      for (let i = 0; i < width; i++) {
        let x = gridView.grid.origin[0] + ((gridView.origin[0] + i) * gridView.grid.resolution[0]) + gridView.offset
        let y = gridView.grid.origin[1] - ((gridView.origin[1] + j) * gridView.grid.resolution[1])
        let pos = utils.latLngToLayerPoint([y, x])
        vertices.push(pos.x)
        vertices.push(pos.y)
        let cellValue = gridView.getValue(i, j)
        let rgb = PIXI.utils.hex2rgb(colorMap(cellValue).num())
        colors.push(rgb[0])
        colors.push(rgb[1])
        colors.push(rgb[2])
        if (i < (width - 1) && j < (height - 1)) {
          let index00 = (j * width) + i
          let index01 = index00 + 1
          let index10 = index00 + width
          let index11 = index10 + 1
          indices.push(index00)
          indices.push(index10)
          indices.push(index01)
          indices.push(index01)
          indices.push(index10)
          indices.push(index11)
        }
      }
    }
    let mesh = new PIXI.mesh.Mesh(null, new Float32Array(vertices), null, new Uint16Array(indices), PIXI.mesh.Mesh.DRAW_MODES.TRIANGLES)
    mesh.colors = new Float32Array(colors)
    mesh.alpha = opacity
    container.addChild(mesh)
  }
}

function buildCells (gridView, colorMap, utils, container, opacity) {
  for (let j = 0; j < gridView.size[1]; j++) {
    for (let i = 0; i < gridView.size[0]; i++) {
      let x = gridView.grid.origin[0] + ((gridView.origin[0] + i) * gridView.grid.resolution[0]) + gridView.offset
      let y = gridView.grid.origin[1] - ((gridView.origin[1] + j) * gridView.grid.resolution[1])
      let xCell = x - (gridView.grid.resolution[0] / 2)
      let yCell = y - (gridView.grid.resolution[1] / 2)
      let minCell = utils.latLngToLayerPoint([yCell, xCell])
      let maxCell = utils.latLngToLayerPoint([yCell + gridView.grid.resolution[0], xCell + gridView.grid.resolution[1]])
      let cellValue = gridView.getValue(i, j)
      let cell = new PIXI.Graphics()
      cell.beginFill(colorMap(cellValue).num(), opacity)
      cell.drawRect(minCell.x, minCell.y, maxCell.x - minCell.x, maxCell.y - minCell.y)
      cell.endFill()
      container.addChild(cell)
    }
  }
}

let ScalarLayer = ForecastLayer.extend({

  initialize (api, options) {
    // Merge options with default for undefined ones
    this.options = Object.assign({
      interpolate: true,
      colorMap: 'OrRd',
      opacity: 0.25,
      mesh: true
    }, options)
    // Create empty PIXI container
    if (this.options.mesh) {
      var _pixiGlCore2 = PIXI.glCore
      PIXI.mesh.MeshRenderer.prototype.onContextChange = function onContextChange () {
        var gl = this.renderer.gl
        this.shader = new PIXI.Shader(gl,
          'attribute vec2 aVertexPosition;\n' +
          'attribute vec3 aVertexColor;\n' +
          'uniform mat3 projectionMatrix;\n' +
          'uniform mat3 translationMatrix;\n' +
          'varying vec3 vColor;\n' +
          'void main(void)\n{\n' +
          '  vColor = aVertexColor;\n' +
          '  gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n' +
          '}\n',
          'precision mediump float;' +
          'varying vec3 vColor;\n' +
          'uniform float alpha;\n' +
          'void main(void)\n{\n' +
          '  gl_FragColor.rgb = vec3(vColor[0]*alpha, vColor[1]*alpha, vColor[2]*alpha);\n' +
          '  gl_FragColor.a = alpha;\n' +
          '}\n'
        )
      }
      PIXI.mesh.MeshRenderer.prototype.render = function render (mesh) {
        var renderer = this.renderer
        var gl = renderer.gl
        var glData = mesh._glDatas[renderer.CONTEXT_UID]
        if (!glData) {
          renderer.bindVao(null)
          glData = {
            shader: this.shader,
            vertexBuffer: _pixiGlCore2.GLBuffer.createVertexBuffer(gl, mesh.vertices, gl.STREAM_DRAW),
            colorBuffer: _pixiGlCore2.GLBuffer.createVertexBuffer(gl, mesh.colors, gl.STREAM_DRAW),
            indexBuffer: _pixiGlCore2.GLBuffer.createIndexBuffer(gl, mesh.indices, gl.STATIC_DRAW)
          }
          // build the vao object that will render..
          glData.vao = new _pixiGlCore2.VertexArrayObject(gl)
            .addIndex(glData.indexBuffer)
            .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 2, 0)
            .addAttribute(glData.colorBuffer, glData.shader.attributes.aVertexColor, gl.FLOAT, false, 4 * 3, 0)
          mesh._glDatas[renderer.CONTEXT_UID] = glData
        }
        renderer.bindVao(glData.vao)
        renderer.bindShader(glData.shader)
        glData.shader.uniforms.alpha = mesh.alpha
        glData.shader.uniforms.translationMatrix = mesh.worldTransform.toArray(true)
        glData.vao.draw(gl.TRIANGLES, mesh.indices.length, 0)
      }
    }
    this.pixiContainer = new PIXI.Container()
    // Create the PIXI overlay
    let layer = L.pixiOverlay(utils => this.render(utils), this.pixiContainer, {
      autoPreventDefault: false
    })
    ForecastLayer.prototype.initialize.call(this, api, layer, options)
  },

  render (utils) {
    // If no data return
    if (!this.grid.data) return
    // Retrive utils objects
    let renderer = utils.getRenderer()
    let container = this.pixiContainer
    // It the PIXI container is null then build the grid
    if (this.pixiContainer.children.length === 0) {
      if (this.options.mesh) {
        renderer.gl.blendFunc(renderer.gl.ONE, renderer.gl.ZERO)
        this.viewer.getViews().forEach(view => buildMesh(view, this.colorMap, utils, container, this.options.opacity))
      } else {
        this.viewer.getViews().forEach(view => buildCells(view, this.colorMap, utils, container, this.options.opacity))
      }
    }
    renderer.render(container)
  },

  getColorMap () {
    let colorMap = []
    let colors = chroma.brewer[this.options.colorMap]
    for (let i = 0; i < colors.length; i++) {
      colorMap.push({
        value: this.minValue + i * (this.maxValue - this.minValue) / colors.length,
        color: colors[i]
      })
    }
    return colorMap
  },

  setData (data) {
    this.minValue = data[0].minValue
    this.maxValue = data[0].maxValue
    this.colorMap = chroma.scale(this.options.colorMap).domain([this.minValue, this.maxValue])
    if (this.options.colorClasses) this.colorMap.classes(this.options.colorClasses)
    this.grid.data = data[0].data
    this.pixiContainer.removeChildren()
    this._baseLayer.redraw()
    ForecastLayer.prototype.setData.call(this, data)
  },

  setForecastModel (model) {
    this.grid = new Grid(model)
    this.viewer = new GridViewer(this.grid)
    ForecastLayer.prototype.setForecastModel.call(this, model)
  }
})

L.Weacast.ScalarLayer = ScalarLayer
export { ScalarLayer }