Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加calculate-expression类型 #13

Merged
merged 2 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 95 additions & 18 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 @@ -160,8 +153,82 @@ function evaluateColorInterpolateFunction(parameters, input) {
}

function evaluateIdentityFunction(parameters, input) {
// console.log('parameters11', parameters)
// console.log('input11', input)
// console.log('coalesce',coalesce(input, parameters.default))
return coalesce(input, parameters.default);
}
// 2添加修改计算函数
// 处理空字符串和未定义属性的函数
function evaluateCalculateExpressionFunction(parameters, input) {
console.log('parameters', parameters);
const targetVariable = String(parameters.property);
const expression = parameters.expression;
const newValue = input;
console.log('input', input);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要去掉代码里的console.log


// 定义函数来替换表达式中的变量
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));
console.log('operands', operands);

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 +255,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 +273,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 +303,6 @@ export function interpolated(parameters) {
return createFunction1(parameters, 'exponential');
}


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

export function loadFunctionTypes(obj, argFn) {
if (!obj) {
return null;
Expand All @@ -258,7 +333,7 @@ export function loadFunctionTypes(obj, argFn) {
}
return hit ? multResult : obj;
}
var result = { '__fn_types_loaded': true },
var result = { fnTypesLoaded: true },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个属性名因为别的地方也会引用,不能随便修改

props = [],
p;
for (p in obj) {
Expand Down Expand Up @@ -316,7 +391,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
158 changes: 77 additions & 81 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,77 @@
var expect = require('expect.js');
const { loadFunctionTypes } = require('./index');
import { registerCanvas } from 'colorin';
const { createCanvas } = require('@napi-rs/canvas');
const canvas = createCanvas(1, 1);
registerCanvas(canvas);

const colors = [
[0, 'red'],
[5, 'black'],
[10, 'white']
];

const symbol = {
markerFill: {
property: 'value',
stops: colors,
type: 'color-interpolate'
},
};

function rgbatoStr(rgba) {
return rgba.join(',');
}

describe('color-interpolate', () => {
it('interpolate by property', () => {
const result = loadFunctionTypes(symbol, () => {
return [11, {
value: 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
}];
});
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
}];
});
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
}];
});
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
}];
});
expect(rgbatoStr(result.markerFill)).to.be(rgbatoStr([0.21176470588235294, 0.21176470588235294, 0.21176470588235294, 1]));
});
});
const expect = require('expect.js');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

原来的测试用例不应删除,需要还原回来,用例的分类可以这样处理:

  • describe方法里的测试集名称,改为概括性的 specs
  • mocha的context方法增加一个子分类 color-interpolate,把原有用例挪到该分类下
  • 同样用context方法增加子类 calculate-expression,把新的用例放到该分类下

const { loadFunctionTypes } = require('./index');

const symbol = {
lineColor: [0.56, 0.75, 0.13, 1],
lineWidth: {
type: 'calculate-expression',
property: '管径',
expression: ['*', '管径', 4000],
default: 4000,
},
};
// 如果输入值缺失、 null、负数、非数值类型、未定义属性、非法运算返回default
describe('calculate-expression', () => {
// 正常情况
it('calculates based on expression', () => {
const result = loadFunctionTypes(symbol, () => {
return [11, { 管径: 2 }];
});
expect(result.lineWidth).to.be(8000); // 2 * 4000
});
// 属性缺失
it('returns default when property is missing', () => {
const result = loadFunctionTypes(symbol, () => {
return [11, {}];
});
expect(result.lineWidth).to.be(4000); // default value when 管径 is missing
});

// 属性为 null
it('returns default when property is null', () => {
const result = loadFunctionTypes(symbol, () => {
return [11, { 管径: null }];
});
expect(result.lineWidth).to.be(4000); // default value when 管径 is null
});

// 属性为负数
it('handles negative value correctly', () => {
const result = loadFunctionTypes(symbol, () => {
return [11, { 管径: -2 }];
});
expect(result.lineWidth).to.be(4000); //
});

// 属性为非数值类型
it('handles non-numeric property value', () => {
const result = loadFunctionTypes(symbol, () => {
return [11, { 管径: 'abc' }];
});
expect(result.lineWidth).to.be(4000); // 'abc' * 4000 should result in NaN
});

// 未定义属性
it('handles undefined property value', () => {
const result = loadFunctionTypes(symbol, () => {
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
});
});