diff --git a/index.js b/index.js index fb4ce90..9645fcf 100644 --- a/index.js +++ b/index.js @@ -10,10 +10,11 @@ function createFunction(parameters, defaultType) { var fun; var isFeatureConstant, isZoomConstant; if (!isFunctionDefinition(parameters)) { - fun = function () { return parameters; }; + fun = function () { + return parameters; + }; isFeatureConstant = true; isZoomConstant = true; - } else { var zoomAndFeatureDependent = parameters.stops && typeof parameters.stops[0][0] === 'object'; var featureDependent = zoomAndFeatureDependent || parameters.property !== undefined; @@ -31,6 +32,8 @@ function createFunction(parameters, defaultType) { innerFun = evaluateIdentityFunction; } else if (type === 'color-interpolate') { innerFun = evaluateColorInterpolateFunction; + } else if (type === 'calculate-expression') { + innerFun = evaluateCalculateExpressionFunction; } else { throw new Error('Unknown function type "' + type + '"'); } @@ -61,7 +64,6 @@ function createFunction(parameters, defaultType) { }; isFeatureConstant = false; isZoomConstant = false; - } else if (zoomDependent) { fun = function (zoom) { const value = innerFun(parameters, zoom); @@ -118,19 +120,10 @@ function evaluateExponentialFunction(parameters, input) { if (i === 0) { return parameters.stops[i][1]; - } else if (i === parameters.stops.length) { return parameters.stops[i - 1][1]; - } else { - return interpolate( - input, - base, - parameters.stops[i - 1][0], - parameters.stops[i][0], - parameters.stops[i - 1][1], - parameters.stops[i][1] - ); + return interpolate(input, base, parameters.stops[i - 1][0], parameters.stops[i][0], parameters.stops[i - 1][1], parameters.stops[i][1]); } } const COLORIN_OPTIONS = { @@ -162,6 +155,73 @@ function evaluateColorInterpolateFunction(parameters, input) { function evaluateIdentityFunction(parameters, input) { return coalesce(input, parameters.default); } +// 2添加修改计算函数 +// 处理空字符串和未定义属性的函数 +function evaluateCalculateExpressionFunction(parameters, input) { + const targetVariable = String(parameters.property); + const expression = parameters.expression; + const newValue = input; + + // 定义函数来替换表达式中的变量 + function traverseAndAssign(expression, targetVariable, newValue) { + const newValueAsNumber = Number(newValue); + const targetVariableAsString = String(targetVariable); + + if (Array.isArray(expression)) { + return expression.map(subExpr => traverseAndAssign(subExpr, targetVariableAsString, newValueAsNumber)); + } else if (expression === targetVariableAsString) { + return newValueAsNumber; + } else { + return expression; + } + } + + // 定义函数来计算表达式的结果 + function evaluateExpression(expression) { + if (Array.isArray(expression)) { + const operator = expression[0]; + + if (!['+', '-', '*', '/'].includes(operator)) { + throw new Error(`Unknown operator: ${operator}`); + } + + const operands = expression.slice(1).map(op => evaluateExpression(op)); + switch (operator) { + case '+': + return operands.reduce((acc, curr) => acc + curr, 0); + case '-': + return operands.reduce((acc, curr) => acc - curr); + case '*': + return operands.reduce((acc, curr) => acc * curr, 1); + case '/': + // 如果发现有零作为除数,返回默认值 + if (operands.some(op => op === 0)) { + return parameters.default; + } + return operands.reduce((acc, curr) => acc / curr); + default: + throw new Error(`Unsupported operator: ${operator}`); + } + } else if (typeof expression === 'number') { + return expression; + } else { + throw new Error('Invalid expression format'); + } + } + + // 使用 coalesce 函数处理结果中的默认值 + function coalesce(value) { + return value === null || value === undefined || value === '' || isNaN(value) ? parameters.default : value; + } + + // 如果 input 存在且大于0,则处理表达式 + if (input !== undefined && input !== null && input !== '' && !isNaN(input) && !(input < 0)) { + const updatedExpression = traverseAndAssign(expression, targetVariable, newValue); + return coalesce(evaluateExpression(updatedExpression)); + } else { + return coalesce(parameters.default); + } +} function interpolate(input, base, inputLower, inputUpper, outputLower, outputUpper) { if (typeof outputLower === 'function') { @@ -188,7 +248,7 @@ function interpolateNumber(input, base, inputLower, inputUpper, outputLower, out ratio = (Math.pow(base, progress) - 1) / (Math.pow(base, difference) - 1); } - return (outputLower * (1 - ratio)) + (outputUpper * ratio); + return outputLower * (1 - ratio) + outputUpper * ratio; } function interpolateArray(input, base, inputLower, inputUpper, outputLower, outputUpper) { @@ -206,9 +266,17 @@ function interpolateArray(input, base, inputLower, inputUpper, outputLower, outp * @memberOf MapboxUtil */ export function isFunctionDefinition(obj) { - return obj && typeof obj === 'object' && (obj.stops || obj.property && obj.type === 'identity'); + return ( + obj && + typeof obj === 'object' && + (obj.stops || (obj.property && obj.type === 'identity') || (obj.expression && obj.type === 'calculate-expression')) + ); } +// export function isFunctionDefinition(obj) { +// return obj && typeof obj === 'object' && (obj.stops || (obj.property && obj.type === 'identity')); +// } + /** * Check if obj's properties has function definition * @param {Object} obj object to check @@ -228,7 +296,6 @@ export function interpolated(parameters) { return createFunction1(parameters, 'exponential'); } - export function piecewiseConstant(parameters) { return createFunction1(parameters, 'interval'); } @@ -239,6 +306,7 @@ export function piecewiseConstant(parameters) { * @return {Object} loaded object * @memberOf MapboxUtil */ + export function loadFunctionTypes(obj, argFn) { if (!obj) { return null; @@ -316,7 +384,9 @@ export function getFunctionTypeResources(t) { function createFunction1(parameters, defaultType) { if (!isFunctionDefinition(parameters)) { - return function () { return parameters; }; + return function () { + return parameters; + }; } parameters = JSON.parse(JSON.stringify(parameters)); let isZoomConstant = true; diff --git a/test.js b/test.js index 19a6f20..5341a59 100644 --- a/test.js +++ b/test.js @@ -1,4 +1,4 @@ -var expect = require('expect.js'); +const expect = require('expect.js'); const { loadFunctionTypes } = require('./index'); import { registerCanvas } from 'colorin'; const { createCanvas } = require('@napi-rs/canvas'); @@ -11,71 +11,135 @@ const colors = [ [10, 'white'] ]; -const symbol = { +const symbolColorInterpolate = { markerFill: { property: 'value', stops: colors, type: 'color-interpolate' - }, + } +}; + +const symbolCalculateExpression = { + lineColor: [0.56, 0.75, 0.13, 1], + lineWidth: { + type: 'calculate-expression', + property: '管径', + expression: ['*', '管径', 4000], + default: 4000 + } }; function rgbatoStr(rgba) { return rgba.join(','); } -describe('color-interpolate', () => { - it('interpolate by property', () => { - const result = loadFunctionTypes(symbol, () => { - return [11, { - value: 1 - }]; +describe('specs', () => { + + context('color-interpolate', () => { + it('interpolate by property', () => { + const result = loadFunctionTypes(symbolColorInterpolate, () => { + return [11, { value: 1 }]; + }); + expect(rgbatoStr(result.markerFill)).to.be(rgbatoStr([0.788235294117647, 0, 0, 1])); }); - expect(rgbatoStr(result.markerFill)).to.be(rgbatoStr([0.788235294117647, 0, 0, 1])); - }); - it('interpolate by property < min', () => { - const result = loadFunctionTypes(symbol, () => { - return [11, { - value: -100 - }]; + + it('interpolate by property < min', () => { + const result = loadFunctionTypes(symbolColorInterpolate, () => { + return [11, { value: -100 }]; + }); + expect(rgbatoStr(result.markerFill)).to.be(rgbatoStr([0.9882352941176471, 0, 0, 1])); }); - expect(rgbatoStr(result.markerFill)).to.be(rgbatoStr([0.9882352941176471, 0, 0, 1])); - }); - it('interpolate by property > max', () => { - const result = loadFunctionTypes(symbol, () => { - return [11, { - value: 100 - }]; + + it('interpolate by property > max', () => { + const result = loadFunctionTypes(symbolColorInterpolate, () => { + return [11, { value: 100 }]; + }); + expect(rgbatoStr(result.markerFill)).to.be(rgbatoStr([0.9882352941176471, 0.9882352941176471, 0.9882352941176471, 1])); }); - expect(rgbatoStr(result.markerFill)).to.be(rgbatoStr([0.9882352941176471, 0.9882352941176471, 0.9882352941176471, 1])); - }); - it('interpolate by property colors.length=1', () => { - const obj = { - markerFill: { - property: 'value', - stops: colors.slice(0, 1), - type: 'color-interpolate' - }, - }; - const result = loadFunctionTypes(obj, () => { - return [11, { - value: 2 - }]; + + it('interpolate by property colors.length=1', () => { + const obj = { + markerFill: { + property: 'value', + stops: colors.slice(0, 1), + type: 'color-interpolate' + } + }; + const result = loadFunctionTypes(obj, () => { + return [11, { value: 2 }]; + }); + expect(result.markerFill).to.be('red'); + }); + + it('interpolate by zoom', () => { + const obj = { + markerFill: { + stops: colors, + type: 'color-interpolate' + } + }; + const result = loadFunctionTypes(obj, () => { + return [6, { value: 11 }]; + }); + expect(rgbatoStr(result.markerFill)).to.be(rgbatoStr([0.21176470588235294, 0.21176470588235294, 0.21176470588235294, 1])); }); - expect(result.markerFill).to.be('red'); }); - it('interpolate by zoom ', () => { - const obj = { - markerFill: { - // property: 'value', - stops: colors, - type: 'color-interpolate' - }, - }; - const result = loadFunctionTypes(obj, () => { - return [6, { - value: 11 - }]; + + context('calculate-expression', () => { + it('calculates based on expression', () => { + const result = loadFunctionTypes(symbolCalculateExpression, () => { + return [11, { 管径: 2 }]; + }); + expect(result.lineWidth).to.be(8000); // 2 * 4000 + }); + it('returns default when property is missing', () => { + const result = loadFunctionTypes(symbolCalculateExpression, () => { + return [11, {}]; + }); + expect(result.lineWidth).to.be(4000); // default value when 管径 is missing + }); + + it('returns default when property is null', () => { + const result = loadFunctionTypes(symbolCalculateExpression, () => { + return [11, { 管径: null }]; + }); + expect(result.lineWidth).to.be(4000); // default value when 管径 is null + }); + + it('handles negative value correctly', () => { + const result = loadFunctionTypes(symbolCalculateExpression, () => { + return [11, { 管径: -2 }]; + }); + expect(result.lineWidth).to.be(4000); + }); + + it('handles non-numeric property value', () => { + const result = loadFunctionTypes(symbolCalculateExpression, () => { + return [11, { 管径: 'abc' }]; + }); + expect(result.lineWidth).to.be(4000); // 'abc' * 4000 should result in NaN + }); + + it('handles undefined property value', () => { + const result = loadFunctionTypes(symbolCalculateExpression, () => { + return [11, { 管径: undefined }]; + }); + expect(result.lineWidth).to.be(4000); // undefined property should return default value + }); + + it('handles 0 as divisor', () => { + const obj = { + lineWidth: { + type: 'calculate-expression', + property: '管径', + expression: ['/', '管径', 0], + default: 4000 + } + }; + const result = loadFunctionTypes(obj, () => { + return [11, { 管径: 10 }]; + }); + expect(result.lineWidth).to.be(4000); // 0 as divisor }); - expect(rgbatoStr(result.markerFill)).to.be(rgbatoStr([0.21176470588235294, 0.21176470588235294, 0.21176470588235294, 1])); }); });