import {
RendererType,
SimpleFillSymbol,
SimpleLineSymbol,
SimpleMarkerSymbol,
SymbolType,
defaultValue
} from '@mapgis/webclient-common'
import MapboxglPlugin from './MapboxglPlugin'
import UniqueValueSteps from './support/UniqueValueSteps'
import ClassBreakSteps from './support/ClassBreakSteps'
/**
* @description: 运算符计算
* @param {*} map 取值映射,取值表示的mapboxgl表达式
* @param {*} calcArr 计算组
* @param {*} calcFlag 运算符
* @param {*}
* @return {*}
*/
function setFeatureAttributeMap(map, calcArr, calcFlag, currentInfo) {
while (calcArr.findIndex((v) => calcFlag.some((t) => t === v)) > -1) {
currentInfo.index += 1
const key = `calc_${currentInfo.index}`
const flagIndex = calcArr.findIndex((v) => calcFlag.some((t) => t === v))
const first =
map[calcArr[flagIndex - 1]] ||
(currentInfo.isNum
? parseFloat(calcArr[flagIndex - 1])
: calcArr[flagIndex - 1])
const end =
map[calcArr[flagIndex + 1]] ||
(currentInfo.isNum
? parseFloat(calcArr[flagIndex + 1])
: calcArr[flagIndex + 1])
map[key] = [calcArr[flagIndex], first, end]
currentInfo.key = key
currentInfo.expression = calcArr.splice(flagIndex - 1, 3, key)
}
}
/**
* @description: 解析表达式为mapboxgl表达式
* @param {*} expression
* @return {*}
*/
function parseExpressionToMapboxglExpress(expression, isNum) {
// 由于表达式规则的复杂性,将符号定义的字段表达式转换为mapboxgl表达式过程会出现解析错误,提前进行错误判断。
try {
let mapboxglExpression = expression
// $feature.name
// $feature.val / 10
// 将$feature.xxx 替换为get表达式
const features = mapboxglExpression
.replace(/\s/g, '')
.match(/\$feature\.[^+-/*%>=<]+/g)
if (!features || features.length === 0) return null
const featureAttributeMap = {}
const currentInfo = {
index: 0,
key: null,
isNum
}
features.forEach((v) => {
currentInfo.index += 1
const key = `calc_${currentInfo.index}`
currentInfo.key = key
featureAttributeMap[key] = isNum
? ['to-number', ['get', v.split('.')[1]]]
: ['get', v.split('.')[1]]
mapboxglExpression = mapboxglExpression.replace(v, `${key}`)
})
// 按照算法运算符分段 + - * / %
// 按照逻辑运算符分段 < <= == > >=
// 添加空格处理
const flagMatchs = mapboxglExpression.match(/>=|<=|==|\+|-|\*|\/|>|<|=/)
if (flagMatchs && flagMatchs.length > 0) {
flagMatchs.forEach((v) => {
mapboxglExpression = mapboxglExpression.replace(v, ` ${v} `)
})
}
const calcArr = mapboxglExpression.split(' ').filter((v) => v !== '')
setFeatureAttributeMap(
featureAttributeMap,
calcArr,
['*', '/', '%'],
currentInfo
)
setFeatureAttributeMap(
featureAttributeMap,
calcArr,
['+', '-'],
currentInfo
)
setFeatureAttributeMap(
featureAttributeMap,
calcArr,
['<', '<=', '>', '>='],
currentInfo
)
setFeatureAttributeMap(featureAttributeMap, calcArr, ['=='], currentInfo)
if (currentInfo.key) {
return featureAttributeMap[currentInfo.key]
}
} catch (err) {}
}
/**
* @description: 解析字段表达式
* @param {*} field
* @param {*} expression
* @return {*}
*/
function parseFieldAndExpression(field, expression, isNum) {
if (field) return isNum ? ['to-number', ['get', field]] : ['get', field]
const express = parseExpressionToMapboxglExpress(expression, isNum)
if (express) return express
return ['get', 'undefined']
}
// 转换定义的symbol
function covertSimpleLineSymbol(
type,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
) {
const color = MapboxglPlugin.applyVisibleAndOpacityToColor(
symbol.color,
visible,
opacity
)
paintStyle['line-color'] = color.toCssRGBString()
paintStyle['line-opacity'] = symbol.opacity
paintStyle['line-width'] = symbol.width
layoutStyle['line-cap'] = symbol.cap
layoutStyle['line-miter-limit'] = symbol.miterLimit
layoutStyle['line-join'] = symbol.join
const lineDasharray = MapboxglPlugin.convertLineStyle(
symbol.style,
symbol.cap === 'butt'
)
if (lineDasharray) {
paintStyle['line-dasharray'] = lineDasharray
}
// 无法设置marker
}
function covertSimpleFillSymbol(
type,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
) {
switch (type) {
case 'fill': {
const color = MapboxglPlugin.applyVisibleAndOpacityToColor(
symbol.color,
visible,
opacity
)
const outline = MapboxglPlugin.applyVisibleAndOpacityToColor(
symbol.outline.color,
visible,
opacity
)
paintStyle['fill-color'] = color.toCssRGBString()
paintStyle['fill-opacity'] = color.alpha
paintStyle['fill-outline-color'] = outline.toCssRGBAString()
break
}
default: {
break
}
}
}
// 转换定义的symbol
function convertPictureMarkerSymbol(
type,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
) {
// ----------------- paint -----------------------------
const color = MapboxglPlugin.applyVisibleAndOpacityToColor(
symbol.color,
visible,
opacity
)
paintStyle['icon-color'] = color.toCssRGBString()
paintStyle['icon-opacity'] = color.alpha
// ----------------- layout -----------------------------
layoutStyle['icon-allow-overlap'] = false
layoutStyle['icon-size'] = Math.max(symbol.width, symbol.height) / 20
layoutStyle['icon-rotate'] = symbol.angle
layoutStyle['icon-image'] = 'marker-15'
layoutStyle['icon-offset'] = [symbol.xoffset, symbol.yoffset]
}
// 转换定义的symbol
function convertTextSymbol(
type,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
) {
// ----------------- paint -----------------------------
const color = MapboxglPlugin.applyVisibleAndOpacityToColor(
symbol.color,
visible,
opacity
)
const haloColor = MapboxglPlugin.applyVisibleAndOpacityToColor(
symbol.haloColor,
visible,
opacity
)
paintStyle['text-color'] = color.toCssRGBString()
paintStyle['text-opacity'] = color.alpha
paintStyle['text-halo-color'] = haloColor.toCssRGBAString()
paintStyle['text-halo-width'] = symbol.haloSize
// ----------------- layout -----------------------------
// 设置默认文字field
layoutStyle['text-field'] = symbol.text || ''
// 开启注记避让
layoutStyle['text-allow-overlap'] = false
layoutStyle['text-font'] = ['Open Sans Regular', 'Arial Unicode MS Regular']
layoutStyle['text-size'] = symbol.font.size
layoutStyle['text-justify'] = symbol.horizontalAlignment
layoutStyle['text-letter-spacing'] = symbol.kerning ? 0.1 : 0
layoutStyle['text-rotate'] =
symbol.rotated && symbol.angle !== 0 ? symbol.angle : 0
layoutStyle['text-offset'] = [symbol.xoffset, symbol.yoffset]
layoutStyle['text-line-height'] = symbol.lineHeight ? 1.2 : 0
layoutStyle['text-max-width'] = defaultValue(symbol.lineWidth, 192)
// 不支持文字基线
layoutStyle['text-anchor'] = 'center'
switch (symbol.verticalAlignment) {
case 'top': {
switch (symbol.horizontalAlignment) {
case 'left': {
layoutStyle['text-anchor'] = 'top-left'
break
}
case 'right': {
layoutStyle['text-anchor'] = 'top-right'
break
}
default: {
layoutStyle['text-anchor'] = 'top'
break
}
}
break
}
case 'bottom': {
switch (symbol.horizontalAlignment) {
case 'left': {
layoutStyle['text-anchor'] = 'bottom-left'
break
}
case 'right': {
layoutStyle['text-anchor'] = 'bottom-right'
break
}
default: {
layoutStyle['text-anchor'] = 'bottom'
break
}
}
break
}
default: {
switch (symbol.horizontalAlignment) {
case 'left': {
layoutStyle['text-anchor'] = 'left'
break
}
case 'right': {
layoutStyle['text-anchor'] = 'right'
break
}
default: {
layoutStyle['text-anchor'] = 'center'
break
}
}
}
}
}
function covertSimpleMarkerSymbol(
type,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
) {
switch (type) {
case 'circle': {
const x = symbol.xoffset
const y = symbol.yoffset
paintStyle['circle-translate'] = [parseFloat(x), parseFloat(y)]
const color = MapboxglPlugin.applyVisibleAndOpacityToColor(
symbol.color,
visible,
opacity
)
paintStyle['circle-opacity'] = color.alpha
paintStyle['circle-color'] = color.toCssRGBString()
// stroke color
const strokeColor = MapboxglPlugin.applyVisibleAndOpacityToColor(
symbol.outline.color,
visible,
opacity
)
paintStyle['circle-stroke-color'] = strokeColor.toCssRGBString()
paintStyle['circle-stroke-opacity'] = strokeColor.alpha
paintStyle['circle-stroke-width'] = symbol.outline.width
// size
paintStyle['circle-radius'] = symbol.size / 2
// 无法支持angle
// 无法支持path
// 无法支持style
break
}
default: {
break
}
}
}
function convertSymbol(
symbol,
renderType,
paintStyle,
layoutStyle,
visible,
opacity
) {
switch (renderType) {
case 'line': {
if (!(symbol instanceof SimpleLineSymbol)) {
symbol = new SimpleLineSymbol()
}
covertSimpleLineSymbol(
renderType,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
)
break
}
case 'fill': {
if (!(symbol instanceof SimpleFillSymbol)) {
symbol = new SimpleFillSymbol()
}
covertSimpleFillSymbol(
renderType,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
)
break
}
case 'circle': {
if (!(symbol instanceof SimpleMarkerSymbol)) {
symbol = new SimpleMarkerSymbol()
}
covertSimpleMarkerSymbol(
renderType,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
)
break
}
case 'symbol': {
if (symbol.type === SymbolType.text) {
convertTextSymbol(
renderType,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
)
} else if (symbol.type === SymbolType.pictureMarker) {
convertPictureMarkerSymbol(
renderType,
paintStyle,
layoutStyle,
symbol,
visible,
opacity
)
}
break
}
default: {
break
}
}
}
/**
* @description: 更新统一专题图
* @param {*} renderer
* @param {*} renderType
* @param {*} paint
* @param {*} layout
* @param {*} visible
* @param {*} opacity
* @return {*}
*/
function updateSimpleRenderer(
renderer,
renderType,
paint,
layout,
visible,
opacity
) {
const symbol = renderer.symbol
// 根据不同图层类型
convertSymbol(symbol, renderType, paint, layout, visible, opacity)
}
/**
* @description: 更新单值专题图
* @param {*} renderer
* @param {*} renderType
* @param {*} paint
* @param {*} layout
* @param {*} visible
* @param {*} opacity
* @return {*}
*/
function updateUniqueValueRenderer(
renderer,
renderType,
paint,
layout,
visible,
opacity
) {
const symbol = renderer.defaultSymbol
convertSymbol(symbol, renderType, paint, layout, visible, opacity)
const expression = parseFieldAndExpression(
renderer.field,
renderer.valueExpression
)
const paintStepObject = {}
const layoutStepObject = {}
Object.keys(paint).forEach((key) => {
paintStepObject[key] = new UniqueValueSteps(expression, paint[key])
})
Object.keys(layout).forEach((key) => {
layoutStepObject[key] = new UniqueValueSteps(expression, layout[key])
})
const uniqueValueInfos = renderer.uniqueValueInfos || []
uniqueValueInfos.forEach((info) => {
// 不能走默认样式,否者会一致对属性分段
const uniquePaint = {}
const uniqueLayout = {}
convertSymbol(
info.symbol,
renderType,
uniquePaint,
uniqueLayout,
info.visible,
opacity
)
const name = info.value
// 添加分段样式
Object.keys(paint).forEach((key) => {
if (!paintStepObject[key]) {
paintStepObject[key] = new UniqueValueSteps(
expression,
uniquePaint[key]
)
}
paintStepObject[key].addStep(name, uniquePaint[key])
})
Object.keys(layout).forEach((key) => {
if (!layoutStepObject[key]) {
layoutStepObject[key] = new UniqueValueSteps(
expression,
uniqueLayout[key]
)
}
layoutStepObject[key].addStep(name, uniqueLayout[key])
})
})
// 重新设置style
Object.keys(paintStepObject).forEach((key) => {
paint[key] = paintStepObject[key].toExpress()
})
Object.keys(layoutStepObject).forEach((key) => {
layout[key] = layoutStepObject[key].toExpress()
})
}
/**
* @description: 分段专题图渲染
* @param {*} renderer
* @param {*} renderType
* @param {*} paint
* @param {*} layout
* @param {*} visible
* @param {*} opacity
* @return {*}
*/
function updateClassBreakRenderer(
renderer,
renderType,
paint,
layout,
visible,
opacity
) {
const symbol = renderer.defaultSymbol
convertSymbol(symbol, renderType, paint, layout, visible, opacity)
const expression = parseFieldAndExpression(
renderer.field,
renderer.valueExpression,
true
)
const paintStepObject = {}
const layoutStepObject = {}
Object.keys(paint).forEach((key) => {
paintStepObject[key] = new ClassBreakSteps(expression, paint[key])
})
Object.keys(layout).forEach((key) => {
layoutStepObject[key] = new ClassBreakSteps(expression, layout[key])
})
const classBreakInfos = renderer.classBreakInfos || []
classBreakInfos.forEach((info) => {
const classBreakPaint = {}
const classBreakLayout = {}
convertSymbol(
info.symbol,
renderType,
classBreakPaint,
classBreakLayout,
info.visible,
opacity
)
// 添加分段样式
Object.keys(paint).forEach((key) => {
if (!paintStepObject[key]) {
paintStepObject[key] = new ClassBreakSteps(
expression,
classBreakPaint[key]
)
}
paintStepObject[key].addStep(
info.minValue,
info.maxValue,
classBreakPaint[key]
)
})
Object.keys(layout).forEach((key) => {
if (!layoutStepObject[key]) {
layoutStepObject[key] = new ClassBreakSteps(
expression,
classBreakLayout[key]
)
}
layoutStepObject[key].addStep(
info.minValue,
info.maxValue,
classBreakLayout[key]
)
})
})
// 重新设置style
Object.keys(paintStepObject).forEach((key) => {
paint[key] = paintStepObject[key].toExpress()
})
Object.keys(layoutStepObject).forEach((key) => {
layout[key] = layoutStepObject[key].toExpress()
})
}
/**
* @description: 将renderer转换为styleLayer样式
* @param {*} renderer 渲染器
* @param {*} renderType 渲染类型
* @param {*} visible 显示或隐藏
* @param {*} opacity 透明度
* @param {*} paint 绘制属性
* @param {*} layout 布局属性
* @return {*}
*/
function updateStyleLayer(
renderer,
renderType,
visible,
opacity,
paint,
layout
) {
// 针对renderer渲染,可参考common层renderer的解析
switch (renderer.type) {
case RendererType.simple: {
updateSimpleRenderer(
renderer,
renderType,
paint,
layout,
visible,
opacity
)
break
}
case RendererType.uniqueValue: {
updateUniqueValueRenderer(
renderer,
renderType,
paint,
layout,
visible,
opacity
)
break
}
case RendererType.classBreak: {
updateClassBreakRenderer(
renderer,
renderType,
paint,
layout,
visible,
opacity
)
break
}
case RendererType.random: {
break
}
default: {
break
}
}
}
export { updateStyleLayer, convertSymbol }