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 all commits
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
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]));
});
});