类名 common/view/support/RendererUtil.js
import { Color, GeometryType, Log, RendererType, SymbolType } from '../../index'
import { defined, isNumber } from '../../util'
import {
  SimpleFillSymbol,
  SimpleMarkerSymbol,
  SimpleLineSymbol,
  Symbol
} from '../../base/symbol'
import VisualVariableType from '../../base/enum/VisualVariableType'

/**
 * @description: 简单解析数值表达式
 * @param {*} feature
 * @param {String} expression
 * @return {*}
 */
function parseExpression(feature, expression) {
  if (!expression) return ''
  const excuteStr = expression.replace(/\$feature/g, `feature.attributes`)
  try {
    // eslint-disable-next-line no-eval
    const v = eval(excuteStr)
    return v
  } catch (err) {
    Log.info('字段表达式输入值非法')
  }
  return ''
}

/**
 * @description: 解析要素字段和表达式
 * @param {*} feature
 * @param {*} field
 * @param {*} expression
 * @return {*}
 */
function parseFieldAndExpression(feature, field, expression) {
  const attributes = feature.attributes || {}
  let value = attributes[field]
  value = defined(value) ? value : parseExpression(feature, expression)
  return value
}

/**
 * @description: 随机颜色
 * @return {*}
 */
function randomColor() {
  return `rgba(${Math.round(Math.random() * 255)},${Math.round(
    Math.random() * 255
  )},${Math.round(Math.random() * 255)},1)`
}

/**
 * @description: 计算比例
 * @param {*} value
 * @param {*} spaceMinValue
 * @param {*} spaceMaxValue
 * @return {*}
 */
function calcSpaceRatio(value, spaceMinValue, spaceMaxValue) {
  const space = spaceMaxValue - spaceMinValue
  if (Number.isNaN(space) || space <= 0) {
    Log.error('传入的视觉变量不合法')
  }
  if (value <= spaceMinValue) {
    return 0
  } else if (value > spaceMaxValue) {
    return 1
  } else {
    return (value - spaceMinValue) / space
  }
}

/**
 * @description: 计算取值分段
 * @param {*} value
 * @param {*} stops
 * @param {*} func
 * @return {*}
 */
function calcGradientInStops(value, stops, func) {
  if (!Array.isArray(stops) || stops.length <= 0) return
  // 排序颜色stop
  const sortStops = stops.sort((a, b) => a.value - b.value)
  if (sortStops.length === 1) return func(stops[0], stops[0], 1)
  // 如果小于stop.value
  const firstStop = sortStops[0]
  const endStop = sortStops[sortStops.length - 1]
  // 分段处理stop
  if (value < firstStop.value) {
    return func(firstStop, firstStop, 1)
  } else if (value >= endStop.value) {
    return func(endStop, endStop, 1)
  } else {
    for (let i = 1; i < sortStops.length; i++) {
      const f = sortStops[i - 1]
      const e = sortStops[i]
      if (value >= f.value && value <= e.value) {
        return func(f, e, calcSpaceRatio(value, f.value, e.value))
      }
    }
  }
}

/**
 * @description: 更新要素的符号信息
 * @param {*} feature
 * @param {*} rendererSymbol
 * @param {Boolean} visible
 * @param {Array<VisualVariable>} 视觉变量
 * @return {*}
 */
function updateFeatureSymbol(
  feature,
  rendererSymbol,
  visible,
  visualVariables
) {
  if (!feature || !feature.symbol || !rendererSymbol) return
  // 处理显示或隐藏
  const applyVisible = defined(visible) ? visible : true
  if (feature.visible !== applyVisible) {
    feature.visible = applyVisible
  }

  // 对比符号JSON,决定是否更新
  const oldJSON = feature.symbol.toJSON()
  const newJSON = rendererSymbol.toJSON()
  if (oldJSON !== newJSON) {
    feature.symbol = newJSON
  }

  // 处理视觉变量
  if (
    !visualVariables ||
    !Array.isArray(visualVariables) ||
    visualVariables.length <= 0
  ) {
    return
  }
  const symbol =
    feature.symbol instanceof Symbol
      ? feature.symbol.toJSON()
      : JSON.stringify(feature.symbol)
  for (let i = 0; i < visualVariables.length; i++) {
    const visualVariable = visualVariables[i]
    const { field, type, valueExpression } = visualVariable
    let value = parseFieldAndExpression(feature, field, valueExpression)
    value = parseFloat(value)
    // eslint-disable-next-line no-continue
    if (Number.isNaN(value)) continue
    switch (type) {
      case VisualVariableType.color: {
        const { stops } = visualVariable
        const color = calcGradientInStops(
          value,
          stops,
          (firstStop, endStop, ratio) => {
            // 计算颜色分段
            const firstColor = Color.fromColor(firstStop.color)
            const endColor = Color.fromColor(endStop.color)
            const r = parseInt(
              firstColor.red + (endColor.red - firstColor.red) * ratio
            )
            const g = parseInt(
              firstColor.green + (endColor.green - firstColor.green) * ratio
            )
            const b = parseInt(
              firstColor.blue + (endColor.blue - firstColor.blue) * ratio
            )
            const a = parseFloat(
              firstColor.alpha + (endColor.alpha - firstColor.alpha) * ratio
            )
            return new Color(r, g, b, a)
          }
        )
        if (defined(color)) {
          symbol.color = color
        }
        break
      }
      case VisualVariableType.size: {
        const { stops, maxDataValue, minDataValue, maxSize, minSize } =
          visualVariable
        // size视觉的两种用法
        let calcStops = stops
        // 解析第一种视觉变量的使用方式,优先级最高
        if (
          isNumber(maxDataValue) &&
          isNumber(minDataValue) &&
          isNumber(maxSize) &&
          isNumber(minSize)
        ) {
          calcStops = [
            {
              size: minSize,
              value: minDataValue
            },
            {
              size: maxSize,
              value: maxDataValue
            }
          ]
        }
        const size = calcGradientInStops(
          value,
          calcStops,
          (firstStop, endStop, ratio) => {
            // 计算颜色分段
            const fSize = firstStop.size
            const eSize = endStop.size
            return parseInt(fSize + (eSize - fSize) * ratio)
          }
        )

        if (defined(size)) {
          symbol.size = size
        }
        break
      }
      case VisualVariableType.opacity: {
        const { stops } = visualVariable
        const alpha = calcGradientInStops(
          value,
          stops,
          (firstStop, endStop, ratio) => {
            // 计算颜色分段
            const fOpacity = firstStop.opacity
            const eOpacity = endStop.opacity
            const a =
              parseInt((fOpacity + (eOpacity - fOpacity) * ratio) * 100) / 100
            return a
          }
        )
        if (defined(alpha) && symbol.color) {
          const color = Color.fromColor(symbol.color)
          color.alpha = alpha
          symbol.color = color
        }
        break
      }
      default: {
        break
      }
    }
    // 设置视觉变量
    feature.symbol = symbol
  }
}

/**
 * @description: 更新随机符号
 * @param {*} feature
 * @return {*}
 */
function updateRandomFeatureSymbol(feature) {
  if (!feature || !feature.geometry) return
  const geometry = feature.geometry
  switch (geometry.type) {
    case GeometryType.multiPoint:
    case GeometryType.point: {
      feature.symbol = new SimpleMarkerSymbol({
        color: randomColor(),
        outline: new SimpleLineSymbol({
          color: randomColor()
        })
      })
      break
    }
    case GeometryType.multiLineString:
    case GeometryType.lineString: {
      feature.symbol = new SimpleLineSymbol({
        color: randomColor()
      })
      break
    }
    case GeometryType.polygon:
    case GeometryType.extent:
    case GeometryType.circle:
    case GeometryType.multiPolygon: {
      feature.symbol = new SimpleFillSymbol({
        color: randomColor(),
        outline: new SimpleLineSymbol({
          color: randomColor()
        })
      })
      break
    }
    default: {
      break
    }
  }
}

/**
 * @description 渲染要素集
 * @public
 * @param {FeatureSet|Array<Feature>} featureSet
 * @param {*} renderer
 * @return {*}
 */
function updateRenderer(features, renderer) {
  renderer = renderer || {}
  const visualVariables = renderer.visualVariables || []
  switch (renderer.type) {
    case RendererType.simple: {
      const rendererSymbol = renderer.symbol
      features.forEach((feature) => {
        updateFeatureSymbol(feature, rendererSymbol, true, visualVariables)
      })
      break
    }
    case RendererType.uniqueValue: {
      const defaultSymbol = renderer.defaultSymbol || {}
      const defaultVisible = renderer.defaultVisible
      const uniqueValueInfos = renderer.uniqueValueInfos || []
      const valueExpression = renderer.valueExpression || ''
      for (let i = 0; i < features.length; i++) {
        const feature = features[i]
        const field = renderer.field || ''
        const value = parseFieldAndExpression(feature, field, valueExpression)
        if (defined(value)) {
          let uniqueSymbol = null
          let _uniqueValueInfo = null
          for (let j = 0; j < uniqueValueInfos.length; j++) {
            const uniqueValueInfo = uniqueValueInfos[j]
            if (uniqueValueInfo.value === value) {
              uniqueSymbol = uniqueValueInfo.symbol
              _uniqueValueInfo = uniqueValueInfo
              break
            }
          }
          if (uniqueSymbol) {
            updateFeatureSymbol(
              feature,
              uniqueSymbol,
              _uniqueValueInfo.visible,
              visualVariables
            )
            // eslint-disable-next-line no-continue
            continue
          }
        }
        // 采用默认的样式
        updateFeatureSymbol(
          feature,
          defaultSymbol,
          defaultVisible,
          visualVariables
        )
      }
      break
    }
    case RendererType.classBreak: {
      const defaultSymbol = renderer.defaultSymbol || {}
      const classBreakInfos = renderer.classBreakInfos || []
      const valueExpression = renderer.valueExpression || ''
      const defaultVisible = renderer.defaultVisible
      for (let i = 0; i < features.length; i++) {
        const feature = features[i]
        const field = renderer.field || ''
        const value = parseFieldAndExpression(feature, field, valueExpression)
        if (defined(value)) {
          let classBreakSymbol = null
          let _classBreakInfo = null
          for (let j = 0; j < classBreakInfos.length; j++) {
            const classBreakInfo = classBreakInfos[j]
            const maxValue = classBreakInfo.maxValue
            const minValue = classBreakInfo.minValue
            if (value >= minValue && value < maxValue) {
              classBreakSymbol = classBreakInfo.symbol
              _classBreakInfo = classBreakInfo
              break
            }
          }
          if (classBreakSymbol) {
            updateFeatureSymbol(
              feature,
              classBreakSymbol,
              _classBreakInfo.visible,
              visualVariables
            )
            // eslint-disable-next-line no-continue
            continue
          }
        }
        // 采用默认的样式
        updateFeatureSymbol(
          feature,
          defaultSymbol,
          defaultVisible,
          visualVariables
        )
      }
      break
    }
    case RendererType.random: {
      features.forEach((feature) => {
        updateRandomFeatureSymbol(feature)
      })
      break
    }
    case RendererType.rank: {
      const symbol = renderer.symbol
      const {
        minSize,
        maxSize,
        maxDataValue,
        minDataValue,
        field,
        valueExpression
      } = renderer
      if (!symbol || minSize > maxSize || (!field && !valueExpression)) {
        Log.error('等级符号专题图参数异常!')
      }
      // 计算最大数值
      let max
      let min

      features.forEach((feature) => {
        let value = parseFieldAndExpression(feature, field, valueExpression)
        value = parseFloat(value)
        if (!defined(max)) {
          max = value
        }
        if (!defined(min)) {
          min = value
        }
        max = Math.max(max, value)
        min = Math.min(min, value)
      })
      // 如果设置有数据最大范围,则用数据最大范围
      if (
        defined(minDataValue) &&
        defined(maxDataValue) &&
        maxDataValue > minDataValue
      ) {
        max = maxDataValue

        min = minDataValue
      }
      // 处理最大最小值相同的情况
      if (max - min === 0) {
        max += 0.0001
      }
      // 设置样式
      features.forEach((feature) => {
        const featureSymbol = symbol.clone()
        let value = parseFieldAndExpression(feature, field, valueExpression)
        value = parseFloat(value)

        // 计算比率
        let clampRate
        if (value < min) {
          clampRate = 0
        } else if (value > max) {
          clampRate = 1
        } else {
          clampRate = (value - min) / (max - min)
        }
        const clampSize = parseInt(clampRate * (maxSize - minSize) + minSize)
        // 判断是否有计算值
        if (isNumber(clampSize)) {
          switch (featureSymbol.type) {
            case SymbolType.simpleMarker: {
              featureSymbol.size = clampSize
              break
            }
            case SymbolType.pictureMarker: {
              featureSymbol.width = clampSize
              featureSymbol.height = clampSize
              break
            }
            case SymbolType.text: {
              const font = featureSymbol.font || {}
              font.size = clampSize
              featureSymbol.font = font
              break
            }
            default: {
              break
            }
          }
        }
        // 更新符号
        updateFeatureSymbol(feature, featureSymbol, true, visualVariables)
      })
      break
    }
    default: {
      const rendererSymbol = renderer.symbol
      features.forEach((feature) => {
        updateFeatureSymbol(feature, rendererSymbol, true, visualVariables)
      })
      break
    }
  }
}

export { updateRenderer, parseExpression }
构造函数
成员变量
方法
事件