UNPKG

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
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); } }; }