-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Add "default" property for functions #4175
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,12 @@ | ||
## master | ||
|
||
* BREAKING CHANGE: the API for the `function` module has changed. The `interpolated` and `piecewise-constant` exports | ||
were replaced with a single unnamed export, a function which accepts an object conforming to the style spec "function" | ||
definition, and an object defining a style spec property. It handles color parsing and validation of feature values | ||
internally. | ||
* Functions now support a "default" property. | ||
* `parseColor` was promoted from gl-js. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
## 8.11.0 | ||
|
||
* Merge `feature-filter` repository into this repository #639 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,23 @@ | ||
'use strict'; | ||
|
||
const colorSpaces = require('./color_spaces'); | ||
const parseColor = require('../util/parse_color'); | ||
const extend = require('../util/extend'); | ||
const getType = require('../util/get_type'); | ||
|
||
function identityFunction(x) { | ||
return x; | ||
} | ||
|
||
function createFunction(parameters, defaultType) { | ||
function createFunction(parameters, propertySpec) { | ||
const isColor = propertySpec.type === 'color'; | ||
|
||
let fun; | ||
|
||
if (!isFunctionDefinition(parameters)) { | ||
if (isColor && parameters) { | ||
parameters = parseColor(parameters); | ||
} | ||
fun = function() { | ||
return parameters; | ||
}; | ||
|
@@ -20,15 +28,38 @@ function createFunction(parameters, defaultType) { | |
const zoomAndFeatureDependent = parameters.stops && typeof parameters.stops[0][0] === 'object'; | ||
const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined; | ||
const zoomDependent = zoomAndFeatureDependent || !featureDependent; | ||
const type = parameters.type || defaultType || 'exponential'; | ||
const type = parameters.type || (propertySpec.function === 'interpolated' ? 'exponential' : 'interval'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you thought about renaming the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filed #4194 for what I think we should do there. |
||
|
||
if (isColor) { | ||
parameters = extend({}, parameters); | ||
|
||
if (parameters.stops) { | ||
parameters.stops = parameters.stops.map((stop) => { | ||
return [stop[0], parseColor(stop[1])]; | ||
}); | ||
} | ||
|
||
if (parameters.default) { | ||
parameters.default = parseColor(parameters.default); | ||
} else { | ||
parameters.default = parseColor(propertySpec.default); | ||
} | ||
} | ||
|
||
let innerFun; | ||
let hashedStops; | ||
if (type === 'exponential') { | ||
innerFun = evaluateExponentialFunction; | ||
} else if (type === 'interval') { | ||
innerFun = evaluateIntervalFunction; | ||
} else if (type === 'categorical') { | ||
innerFun = evaluateCategoricalFunction; | ||
|
||
// For categorical functions, generate an Object as a hashmap of the stops for fast searching | ||
hashedStops = Object.create(null); | ||
for (const stop of parameters.stops) { | ||
hashedStops[stop[0]] = stop[1]; | ||
} | ||
} else if (type === 'identity') { | ||
innerFun = evaluateIdentityFunction; | ||
} else { | ||
|
@@ -60,15 +91,6 @@ function createFunction(parameters, defaultType) { | |
outputFunction = identityFunction; | ||
} | ||
|
||
|
||
// For categorical functions, generate an Object as a hashmap of the stops for fast searching | ||
const hashedStops = Object.create(null); | ||
if (innerFun === evaluateCategoricalFunction) { | ||
for (let i = 0; i < parameters.stops.length; i++) { | ||
hashedStops[parameters.stops[i][0]] = parameters.stops[i][1]; | ||
} | ||
} | ||
|
||
if (zoomAndFeatureDependent) { | ||
const featureFunctions = {}; | ||
const featureFunctionStops = []; | ||
|
@@ -86,34 +108,30 @@ function createFunction(parameters, defaultType) { | |
} | ||
|
||
for (const z in featureFunctions) { | ||
featureFunctionStops.push([featureFunctions[z].zoom, createFunction(featureFunctions[z])]); | ||
featureFunctionStops.push([featureFunctions[z].zoom, createFunction(featureFunctions[z], propertySpec)]); | ||
} | ||
fun = function(zoom, feature) { | ||
return outputFunction(evaluateExponentialFunction({ | ||
stops: featureFunctionStops, | ||
base: parameters.base | ||
}, zoom)(zoom, feature)); | ||
}, propertySpec, zoom)(zoom, feature)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need to pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah. I misread the code. This is 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is it embedded within There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oop, didn't see your followup comment. 👍 |
||
}; | ||
fun.isFeatureConstant = false; | ||
fun.isZoomConstant = false; | ||
|
||
} else if (zoomDependent) { | ||
fun = function(zoom) { | ||
if (innerFun === evaluateCategoricalFunction) { | ||
return outputFunction(innerFun(parameters, zoom, hashedStops)); | ||
} else { | ||
return outputFunction(innerFun(parameters, zoom)); | ||
} | ||
return outputFunction(innerFun(parameters, propertySpec, zoom, hashedStops)); | ||
}; | ||
fun.isFeatureConstant = true; | ||
fun.isZoomConstant = false; | ||
} else { | ||
fun = function(zoom, feature) { | ||
if (innerFun === evaluateCategoricalFunction) { | ||
return outputFunction(innerFun(parameters, feature[parameters.property], hashedStops)); | ||
} else { | ||
return outputFunction(innerFun(parameters, feature[parameters.property])); | ||
const value = feature[parameters.property]; | ||
if (value === undefined) { | ||
return coalesce(parameters.default, propertySpec.default); | ||
} | ||
return outputFunction(innerFun(parameters, propertySpec, value, hashedStops)); | ||
}; | ||
fun.isFeatureConstant = false; | ||
fun.isZoomConstant = true; | ||
|
@@ -123,21 +141,21 @@ function createFunction(parameters, defaultType) { | |
return fun; | ||
} | ||
|
||
function evaluateCategoricalFunction(parameters, input, hashedStops) { | ||
const value = hashedStops[input]; | ||
if (value === undefined) { | ||
// If the input is not found, return the first value from the original array by default | ||
return parameters.stops[0][1]; | ||
} | ||
function coalesce(a, b, c) { | ||
if (a !== undefined) return a; | ||
if (b !== undefined) return b; | ||
if (c !== undefined) return c; | ||
} | ||
|
||
return value; | ||
function evaluateCategoricalFunction(parameters, propertySpec, input, hashedStops) { | ||
return coalesce(hashedStops[input], parameters.default, propertySpec.default); | ||
} | ||
|
||
function evaluateIntervalFunction(parameters, input) { | ||
function evaluateIntervalFunction(parameters, propertySpec, input) { | ||
// Edge cases | ||
if (getType(input) !== 'number') return coalesce(parameters.default, propertySpec.default); | ||
const n = parameters.stops.length; | ||
if (n === 1) return parameters.stops[0][1]; | ||
if (input === undefined || input === null) return parameters.stops[n - 1][1]; | ||
if (input <= parameters.stops[0][0]) return parameters.stops[0][1]; | ||
if (input >= parameters.stops[n - 1][0]) return parameters.stops[n - 1][1]; | ||
|
||
|
@@ -146,13 +164,13 @@ function evaluateIntervalFunction(parameters, input) { | |
return parameters.stops[index][1]; | ||
} | ||
|
||
function evaluateExponentialFunction(parameters, input) { | ||
function evaluateExponentialFunction(parameters, propertySpec, input) { | ||
const base = parameters.base !== undefined ? parameters.base : 1; | ||
|
||
// Edge cases | ||
if (getType(input) !== 'number') return coalesce(parameters.default, propertySpec.default); | ||
const n = parameters.stops.length; | ||
if (n === 1) return parameters.stops[0][1]; | ||
if (input === undefined || input === null) return parameters.stops[n - 1][1]; | ||
if (input <= parameters.stops[0][0]) return parameters.stops[0][1]; | ||
if (input >= parameters.stops[n - 1][0]) return parameters.stops[n - 1][1]; | ||
|
||
|
@@ -168,8 +186,13 @@ function evaluateExponentialFunction(parameters, input) { | |
); | ||
} | ||
|
||
function evaluateIdentityFunction(parameters, input) { | ||
return input; | ||
function evaluateIdentityFunction(parameters, propertySpec, input) { | ||
if (propertySpec.type === 'color') { | ||
input = parseColor(input); | ||
} else if (getType(input) !== propertySpec.type) { | ||
input = undefined; | ||
} | ||
return coalesce(input, parameters.default, propertySpec.default); | ||
} | ||
|
||
function binarySearchForIndex(stops, input) { | ||
|
@@ -200,6 +223,10 @@ function interpolate(input, base, inputLower, inputUpper, outputLower, outputUpp | |
return function() { | ||
const evaluatedLower = outputLower.apply(undefined, arguments); | ||
const evaluatedUpper = outputUpper.apply(undefined, arguments); | ||
// Special case for fill-outline-color, which has no spec default. | ||
if (evaluatedLower === undefined || evaluatedUpper === undefined) { | ||
return undefined; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ref #4088 |
||
return interpolate(input, base, inputLower, inputUpper, evaluatedLower, evaluatedUpper); | ||
}; | ||
} else if (outputLower.length) { | ||
|
@@ -235,13 +262,5 @@ function isFunctionDefinition(value) { | |
return typeof value === 'object' && (value.stops || value.type === 'identity'); | ||
} | ||
|
||
|
||
module.exports = createFunction; | ||
module.exports.isFunctionDefinition = isFunctionDefinition; | ||
|
||
module.exports.interpolated = function(parameters) { | ||
return createFunction(parameters, 'exponential'); | ||
}; | ||
|
||
module.exports['piecewise-constant'] = function(parameters) { | ||
return createFunction(parameters, 'interval'); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
'use strict'; | ||
|
||
const parseColorString = require('csscolorparser').parseCSSColor; | ||
|
||
module.exports = function parseColor(input) { | ||
if (typeof input === 'string') { | ||
const rgba = parseColorString(input); | ||
if (!rgba) { return undefined; } | ||
|
||
// GL expects all components to be in the range [0, 1] and to be | ||
// multipled by the alpha value. | ||
return [ | ||
rgba[0] / 255 * rgba[3], | ||
rgba[1] / 255 * rgba[3], | ||
rgba[2] / 255 * rgba[3], | ||
rgba[3] | ||
]; | ||
|
||
} else if (Array.isArray(input)) { | ||
return input; | ||
|
||
} else { | ||
return undefined; | ||
} | ||
}; |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI @anandthakker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jfirebaugh ah, whoops - I made the bad assumption that
layout
contained the style JSON.