Skip to content

Commit

Permalink
增加calculate-expression类型 (#13)
Browse files Browse the repository at this point in the history
* 增加calculate-expression类型,可以满足属性字段的单次加减乘除运算。本次提交包含处理代码和测试用例

* 删除不必要的console.log及注释,恢复原变量及测试用例
  • Loading branch information
aoyaZY authored Sep 20, 2024
1 parent dcd53de commit b52cd25
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 68 deletions.
104 changes: 87 additions & 17 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 + '"');
}
Expand Down Expand Up @@ -61,7 +64,6 @@ function createFunction(parameters, defaultType) {
};
isFeatureConstant = false;
isZoomConstant = false;

} else if (zoomDependent) {
fun = function (zoom) {
const value = innerFun(parameters, zoom);
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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') {
Expand All @@ -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) {
Expand All @@ -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
Expand All @@ -228,7 +296,6 @@ export function interpolated(parameters) {
return createFunction1(parameters, 'exponential');
}


export function piecewiseConstant(parameters) {
return createFunction1(parameters, 'interval');
}
Expand All @@ -239,6 +306,7 @@ export function piecewiseConstant(parameters) {
* @return {Object} loaded object
* @memberOf MapboxUtil
*/

export function loadFunctionTypes(obj, argFn) {
if (!obj) {
return null;
Expand Down Expand Up @@ -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;
Expand Down
166 changes: 115 additions & 51 deletions test.js
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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]));
});
});

0 comments on commit b52cd25

Please sign in to comment.