s2maps-gpu
Version:
S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.
180 lines (179 loc) • 6.96 kB
JavaScript
import getEasingFunction from './easingFunctions.js';
import parseFilter from './parseFilter.js';
/**
* This functionality is built for the tile worker. It helps with building data the GPU can parse
* to manipulate input values into output values on the GPU.
* @param input - input value or property
* @param cb - callback function
* @returns a generic layer worker function
*/
export default function parseFeature(input, cb = (i) => i) {
if (typeof input === 'object' && !Array.isArray(input)) {
if ('inputValue' in input && input.inputValue !== undefined) {
return inputValueFunction(input.inputValue, cb);
}
else if ('dataCondition' in input && input.dataCondition !== undefined) {
return dataConditionFunction(input.dataCondition, cb);
}
else if ('dataRange' in input && input.dataRange !== undefined) {
return dataRangeFunction(input.dataRange, cb);
}
else if ('inputRange' in input && input.inputRange !== undefined) {
return inputRangeFunction(input.inputRange, cb);
}
else if ('fallback' in input && input.fallback !== undefined) {
return parseFeature(input.fallback, cb);
}
else {
throw Error('invalid input');
}
}
else {
return () => cb(input);
}
}
/**
* Input value function parser
* @param inputValue - input value
* @param cb - callback function
* @returns a generic layer worker function designed for input values
*/
function inputValueFunction(inputValue, cb) {
return (code, properties) => {
let endKey = inputValue.key;
// dive into nested properties if needed
while (typeof endKey === 'object' && 'key' in endKey) {
properties = (properties[endKey.nestedKey ?? ''] ?? {});
endKey = endKey.key;
}
// return the input if it exists, otherwise fallback
const res = (properties[endKey] ?? inputValue.fallback);
const cbValue = cb(res);
if (typeof cbValue === 'number')
code.push(1, cbValue);
else if (Array.isArray(cbValue))
code.push(cbValue.length, ...cbValue);
return cbValue;
};
}
/**
* Data condition function parser
* @param dataCondition - data condition input
* @param cb - callback function
* @returns a generic layer worker function designed for data conditions
*/
function dataConditionFunction(dataCondition, cb) {
const { conditions, fallback } = dataCondition;
const conditionList = [];
const fallbackCondition = parseFeature(fallback);
// store conditions
for (const condition of conditions) {
conditionList.push({
condition: parseFilter(condition.filter),
result: parseFeature(condition.input, cb),
});
}
// build function
return (code, properties, zoom = 0) => {
if (properties !== undefined) {
for (let i = 0, cl = conditionList.length; i < cl; i++) {
// run through the conditionList
const { condition, result } = conditionList[i];
if (condition(properties) === true) {
code.push(i + 1);
return result(code, properties, zoom);
}
}
}
// if we made it here, just run the fallback
code.push(0);
const fallback = fallbackCondition?.(code, properties, zoom);
return cb(fallback);
};
}
/**
* Data range function parser
* @param dataRange - data range
* @param cb - callback function
* @returns a generic layer worker function for data ranges
*/
function dataRangeFunction(dataRange, cb) {
const { key, ranges, ease, base } = dataRange;
const easeFunction = getEasingFunction(ease, base);
const parsedRanges = ranges.map(({ stop, input }) => {
return {
stop,
input: parseFeature(input, cb),
};
});
return (code, properties, _zoom) => {
let endKey = key;
// dive into nested properties if needed
while (typeof endKey === 'object' && 'key' in endKey) {
properties = (properties[endKey.nestedKey ?? ''] ?? {});
endKey = endKey.key;
}
const dataInput = properties[endKey] !== undefined && !isNaN(properties[endKey])
? Number(properties[endKey])
: 0;
if (dataInput <= parsedRanges[0].stop) {
// less then or equal to first stop
return parsedRanges[0].input(code, properties, dataInput);
}
else if (dataInput >= parsedRanges[parsedRanges.length - 1].stop) {
// greater then or equal to last stop
return parsedRanges[parsedRanges.length - 1].input(code, properties, dataInput);
}
else {
// somewhere inbetween two stops. lets interpolate
let i = 0;
while (parsedRanges[i] !== undefined && parsedRanges[i].stop <= dataInput)
i++;
if (parsedRanges.length === i)
i--;
const startValue = parsedRanges[i - 1].input(code, properties, dataInput);
const startStop = parsedRanges[i - 1].stop;
const endValue = parsedRanges[i].input(code, properties, dataInput);
const endStop = parsedRanges[i].stop;
return easeFunction(dataInput, startStop, endStop, startValue, endValue);
}
};
}
// TODO: Support type property (defaults to 'zoom')
/**
* Input range function parser
* @param inputRange - input range
* @param cb - callback function
* @returns a generic layer worker function for input ranges
*/
function inputRangeFunction(inputRange, cb) {
const { ranges, ease, base } = inputRange;
const easeFunction = getEasingFunction(ease, base);
// first ensure each result property is parsed:
const parsedRanges = ranges.map(({ stop, input }) => {
return {
stop,
input: parseFeature(input, cb),
};
});
return (code, properties, zoom) => {
if (zoom <= parsedRanges[0].stop) {
return parsedRanges[0].input(code, properties, zoom);
}
else if (zoom >= parsedRanges[parsedRanges.length - 1].stop) {
return parsedRanges[parsedRanges.length - 1].input(code, properties, zoom);
}
else {
let i = 0;
while (parsedRanges[i] !== undefined && parsedRanges[i].stop <= zoom)
i++;
if (parsedRanges.length === i)
i--;
const startValue = parsedRanges[i - 1].input(code, properties, zoom);
const startStop = parsedRanges[i - 1].stop;
const endValue = parsedRanges[i].input(code, properties, zoom);
const endStop = parsedRanges[i].stop;
return easeFunction(zoom, startStop, endStop, startValue, endValue);
}
};
}