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 }