@mapbox/mapbox-gl-style-spec 13.22.0-beta.1 → 13.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/diff.js +8 -1
- package/dist/index.cjs +475 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +475 -64
- package/dist/index.es.js.map +1 -1
- package/expression/definitions/index.js +10 -0
- package/expression/evaluation_context.js +30 -0
- package/expression/index.js +9 -3
- package/expression/parsing_context.js +1 -1
- package/feature_filter/index.js +183 -22
- package/package.json +1 -1
- package/reference/v8.json +190 -11
- package/style-spec.js +1 -1
- package/types.js +7 -0
- package/validate/validate.js +3 -1
- package/validate/validate_expression.js +33 -2
- package/validate/validate_filter.js +3 -1
- package/validate/validate_layer.js +3 -1
- package/validate/validate_projection.js +30 -0
|
@@ -201,6 +201,16 @@ CompoundExpression.register(expressions, {
|
|
|
201
201
|
[],
|
|
202
202
|
(ctx) => ctx.globals.zoom
|
|
203
203
|
],
|
|
204
|
+
'pitch': [
|
|
205
|
+
NumberType,
|
|
206
|
+
[],
|
|
207
|
+
(ctx) => ctx.globals.pitch || 0
|
|
208
|
+
],
|
|
209
|
+
'distance-from-center': [
|
|
210
|
+
NumberType,
|
|
211
|
+
[],
|
|
212
|
+
(ctx) => ctx.distanceFromCenter()
|
|
213
|
+
],
|
|
204
214
|
'heatmap-density': [
|
|
205
215
|
NumberType,
|
|
206
216
|
[],
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
3
|
import {Color} from './values.js';
|
|
4
|
+
|
|
5
|
+
import type Point from '@mapbox/point-geometry';
|
|
4
6
|
import type {FormattedSection} from './types/formatted.js';
|
|
5
7
|
import type {GlobalProperties, Feature, FeatureState} from './index.js';
|
|
6
8
|
import type {CanonicalTileID} from '../../source/tile_id.js';
|
|
9
|
+
import type {FeatureDistanceData} from '../feature_filter/index.js';
|
|
7
10
|
|
|
8
11
|
const geometryTypes = ['Unknown', 'Point', 'LineString', 'Polygon'];
|
|
9
12
|
|
|
@@ -14,6 +17,8 @@ class EvaluationContext {
|
|
|
14
17
|
formattedSection: ?FormattedSection;
|
|
15
18
|
availableImages: ?Array<string>;
|
|
16
19
|
canonical: ?CanonicalTileID;
|
|
20
|
+
featureTileCoord: ?Point;
|
|
21
|
+
featureDistanceData: ?FeatureDistanceData;
|
|
17
22
|
|
|
18
23
|
_parseColorCache: {[_: string]: ?Color};
|
|
19
24
|
|
|
@@ -25,6 +30,8 @@ class EvaluationContext {
|
|
|
25
30
|
this._parseColorCache = {};
|
|
26
31
|
this.availableImages = null;
|
|
27
32
|
this.canonical = null;
|
|
33
|
+
this.featureTileCoord = null;
|
|
34
|
+
this.featureDistanceData = null;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
id() {
|
|
@@ -47,6 +54,29 @@ class EvaluationContext {
|
|
|
47
54
|
return this.feature && this.feature.properties || {};
|
|
48
55
|
}
|
|
49
56
|
|
|
57
|
+
distanceFromCenter() {
|
|
58
|
+
if (this.featureTileCoord && this.featureDistanceData) {
|
|
59
|
+
|
|
60
|
+
const c = this.featureDistanceData.center;
|
|
61
|
+
const scale = this.featureDistanceData.scale;
|
|
62
|
+
const {x, y} = this.featureTileCoord;
|
|
63
|
+
|
|
64
|
+
// Calculate the distance vector `d` (left handed)
|
|
65
|
+
const dX = x * scale - c[0];
|
|
66
|
+
const dY = y * scale - c[1];
|
|
67
|
+
|
|
68
|
+
// The bearing vector `b` (left handed)
|
|
69
|
+
const bX = this.featureDistanceData.bearing[0];
|
|
70
|
+
const bY = this.featureDistanceData.bearing[1];
|
|
71
|
+
|
|
72
|
+
// Distance is calculated as `dot(d, v)`
|
|
73
|
+
const dist = (bX * dX + bY * dY);
|
|
74
|
+
return dist;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
50
80
|
parseColor(input: string): ?Color {
|
|
51
81
|
let cached = this._parseColorCache[input];
|
|
52
82
|
if (!cached) {
|
package/expression/index.js
CHANGED
|
@@ -27,6 +27,7 @@ import type {PropertyValueSpecification} from '../types.js';
|
|
|
27
27
|
import type {FormattedSection} from './types/formatted.js';
|
|
28
28
|
import type Point from '@mapbox/point-geometry';
|
|
29
29
|
import type {CanonicalTileID} from '../../source/tile_id.js';
|
|
30
|
+
import type {FeatureDistanceData} from '../feature_filter/index.js';
|
|
30
31
|
|
|
31
32
|
export type Feature = {
|
|
32
33
|
+type: 1 | 2 | 3 | 'Unknown' | 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | 'Polygon' | 'MultiPolygon',
|
|
@@ -40,6 +41,7 @@ export type FeatureState = {[_: string]: any};
|
|
|
40
41
|
|
|
41
42
|
export type GlobalProperties = $ReadOnly<{
|
|
42
43
|
zoom: number,
|
|
44
|
+
pitch?: number,
|
|
43
45
|
heatmapDensity?: number,
|
|
44
46
|
lineProgress?: number,
|
|
45
47
|
skyRadialProgress?: number,
|
|
@@ -63,24 +65,28 @@ export class StyleExpression {
|
|
|
63
65
|
this._enumValues = propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
evaluateWithoutErrorHandling(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array<string>, formattedSection?: FormattedSection): any {
|
|
68
|
+
evaluateWithoutErrorHandling(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array<string>, formattedSection?: FormattedSection, featureTileCoord?: Point, featureDistanceData?: FeatureDistanceData): any {
|
|
67
69
|
this._evaluator.globals = globals;
|
|
68
70
|
this._evaluator.feature = feature;
|
|
69
71
|
this._evaluator.featureState = featureState;
|
|
70
72
|
this._evaluator.canonical = canonical;
|
|
71
73
|
this._evaluator.availableImages = availableImages || null;
|
|
72
74
|
this._evaluator.formattedSection = formattedSection;
|
|
75
|
+
this._evaluator.featureTileCoord = featureTileCoord || null;
|
|
76
|
+
this._evaluator.featureDistanceData = featureDistanceData || null;
|
|
73
77
|
|
|
74
78
|
return this.expression.evaluate(this._evaluator);
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
evaluate(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array<string>, formattedSection?: FormattedSection): any {
|
|
81
|
+
evaluate(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array<string>, formattedSection?: FormattedSection, featureTileCoord?: Point, featureDistanceData?: FeatureDistanceData): any {
|
|
78
82
|
this._evaluator.globals = globals;
|
|
79
83
|
this._evaluator.feature = feature || null;
|
|
80
84
|
this._evaluator.featureState = featureState || null;
|
|
81
85
|
this._evaluator.canonical = canonical;
|
|
82
86
|
this._evaluator.availableImages = availableImages || null;
|
|
83
87
|
this._evaluator.formattedSection = formattedSection || null;
|
|
88
|
+
this._evaluator.featureTileCoord = featureTileCoord || null;
|
|
89
|
+
this._evaluator.featureDistanceData = featureDistanceData || null;
|
|
84
90
|
|
|
85
91
|
try {
|
|
86
92
|
const val = this.expression.evaluate(this._evaluator);
|
|
@@ -233,7 +239,7 @@ export function createPropertyExpression(expression: mixed, propertySpec: StyleP
|
|
|
233
239
|
return error([new ParsingError('', 'data expressions not supported')]);
|
|
234
240
|
}
|
|
235
241
|
|
|
236
|
-
const isZoomConstant = isConstant.isGlobalPropertyConstant(parsed, ['zoom']);
|
|
242
|
+
const isZoomConstant = isConstant.isGlobalPropertyConstant(parsed, ['zoom', 'pitch', 'distance-from-center']);
|
|
237
243
|
if (!isZoomConstant && !supportsZoomExpression(propertySpec)) {
|
|
238
244
|
return error([new ParsingError('', 'zoom expressions not supported')]);
|
|
239
245
|
}
|
|
@@ -229,5 +229,5 @@ function isConstant(expression: Expression) {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
return isFeatureConstant(expression) &&
|
|
232
|
-
isGlobalPropertyConstant(expression, ['zoom', 'heatmap-density', 'line-progress', 'sky-radial-progress', 'accumulated', 'is-supported-script']);
|
|
232
|
+
isGlobalPropertyConstant(expression, ['zoom', 'heatmap-density', 'line-progress', 'sky-radial-progress', 'accumulated', 'is-supported-script', 'pitch', 'distance-from-center']);
|
|
233
233
|
}
|
package/feature_filter/index.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
3
|
import {createExpression} from '../expression/index.js';
|
|
4
|
+
import {isFeatureConstant} from '../expression/is_constant.js';
|
|
5
|
+
import {deepUnbundle} from '../util/unbundle_jsonlint.js';
|
|
6
|
+
import latest from '../reference/latest.js';
|
|
4
7
|
import type {GlobalProperties, Feature} from '../expression/index.js';
|
|
5
8
|
import type {CanonicalTileID} from '../../source/tile_id.js';
|
|
9
|
+
import type Point from '@mapbox/point-geometry';
|
|
6
10
|
|
|
7
|
-
type
|
|
8
|
-
|
|
11
|
+
export type FeatureDistanceData = {bearing: [number, number], center: [number, number], scale: number};
|
|
12
|
+
type FilterExpression = (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID, featureTileCoord?: Point, featureDistanceData?: FeatureDistanceData) => boolean;
|
|
13
|
+
export type FeatureFilter = {filter: FilterExpression, dynamicFilter?: FilterExpression, needGeometry: boolean, needFeature: boolean};
|
|
9
14
|
|
|
10
15
|
export default createFilter;
|
|
11
|
-
export {isExpressionFilter};
|
|
16
|
+
export {isExpressionFilter, isDynamicFilter, extractStaticFilter};
|
|
12
17
|
|
|
13
18
|
function isExpressionFilter(filter: any) {
|
|
14
19
|
if (filter === true || filter === false) {
|
|
@@ -52,17 +57,6 @@ function isExpressionFilter(filter: any) {
|
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
const filterSpec = {
|
|
56
|
-
'type': 'boolean',
|
|
57
|
-
'default': false,
|
|
58
|
-
'transition': false,
|
|
59
|
-
'property-type': 'data-driven',
|
|
60
|
-
'expression': {
|
|
61
|
-
'interpolated': false,
|
|
62
|
-
'parameters': ['zoom', 'feature']
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
60
|
/**
|
|
67
61
|
* Given a filter expressed as nested arrays, return a new function
|
|
68
62
|
* that evaluates whether a given feature (with a .properties or .tags property)
|
|
@@ -70,25 +64,192 @@ const filterSpec = {
|
|
|
70
64
|
*
|
|
71
65
|
* @private
|
|
72
66
|
* @param {Array} filter mapbox gl filter
|
|
67
|
+
* @param {string} layerType the type of the layer this filter will be applied to.
|
|
73
68
|
* @returns {Function} filter-evaluating function
|
|
74
69
|
*/
|
|
75
|
-
function createFilter(filter: any): FeatureFilter {
|
|
70
|
+
function createFilter(filter: any, layerType?: string = 'fill'): FeatureFilter {
|
|
76
71
|
if (filter === null || filter === undefined) {
|
|
77
|
-
return {filter: () => true, needGeometry: false};
|
|
72
|
+
return {filter: () => true, needGeometry: false, needFeature: false};
|
|
78
73
|
}
|
|
79
74
|
|
|
80
75
|
if (!isExpressionFilter(filter)) {
|
|
81
76
|
filter = convertFilter(filter);
|
|
82
77
|
}
|
|
78
|
+
const filterExp = ((filter: any): string[] | string | boolean);
|
|
79
|
+
|
|
80
|
+
let staticFilter = true;
|
|
81
|
+
try {
|
|
82
|
+
staticFilter = extractStaticFilter(filterExp);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.warn(
|
|
85
|
+
`Failed to extract static filter. Filter will continue working, but at higher memory usage and slower framerate.
|
|
86
|
+
This is most likely a bug, please report this via https://github.com/mapbox/mapbox-gl-js/issues/new?assignees=&labels=&template=Bug_report.md
|
|
87
|
+
and paste the contents of this message in the report.
|
|
88
|
+
Thank you!
|
|
89
|
+
Filter Expression:
|
|
90
|
+
${JSON.stringify(filterExp, null, 2)}
|
|
91
|
+
`);
|
|
92
|
+
}
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
// Compile the static component of the filter
|
|
95
|
+
const filterSpec = latest[`filter_${layerType}`];
|
|
96
|
+
const compiledStaticFilter = createExpression(staticFilter, filterSpec);
|
|
97
|
+
|
|
98
|
+
let filterFunc = null;
|
|
99
|
+
if (compiledStaticFilter.result === 'error') {
|
|
100
|
+
throw new Error(compiledStaticFilter.value.map(err => `${err.key}: ${err.message}`).join(', '));
|
|
87
101
|
} else {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
102
|
+
filterFunc = (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiledStaticFilter.value.evaluate(globalProperties, feature, {}, canonical);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// If the static component is not equal to the entire filter then we have a dynamic component
|
|
106
|
+
// Compile the dynamic component separately
|
|
107
|
+
let dynamicFilterFunc = null;
|
|
108
|
+
let needFeature = null;
|
|
109
|
+
if (staticFilter !== filterExp) {
|
|
110
|
+
const compiledDynamicFilter = createExpression(filterExp, filterSpec);
|
|
111
|
+
|
|
112
|
+
if (compiledDynamicFilter.result === 'error') {
|
|
113
|
+
throw new Error(compiledDynamicFilter.value.map(err => `${err.key}: ${err.message}`).join(', '));
|
|
114
|
+
} else {
|
|
115
|
+
dynamicFilterFunc = (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID, featureTileCoord?: Point, featureDistanceData?: FeatureDistanceData) => compiledDynamicFilter.value.evaluate(globalProperties, feature, {}, canonical, undefined, undefined, featureTileCoord, featureDistanceData);
|
|
116
|
+
needFeature = !isFeatureConstant(compiledDynamicFilter.value.expression);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
filterFunc = ((filterFunc: any): FilterExpression);
|
|
121
|
+
const needGeometry = geometryNeeded(staticFilter);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
filter: filterFunc,
|
|
125
|
+
dynamicFilter: dynamicFilterFunc ? dynamicFilterFunc : undefined,
|
|
126
|
+
needGeometry,
|
|
127
|
+
needFeature: !!needFeature
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractStaticFilter(filter: any): any {
|
|
132
|
+
if (!isDynamicFilter(filter)) {
|
|
133
|
+
return filter;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Shallow copy so we can replace expressions in-place
|
|
137
|
+
let result = deepUnbundle(filter);
|
|
138
|
+
|
|
139
|
+
// 1. Union branches
|
|
140
|
+
unionDynamicBranches(result);
|
|
141
|
+
|
|
142
|
+
// 2. Collapse dynamic conditions to `true`
|
|
143
|
+
result = collapseDynamicBooleanExpressions(result);
|
|
144
|
+
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function collapseDynamicBooleanExpressions(expression: any): any {
|
|
149
|
+
if (!Array.isArray(expression)) {
|
|
150
|
+
return expression;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const collapsed = collapsedExpression(expression);
|
|
154
|
+
if (collapsed === true) {
|
|
155
|
+
return collapsed;
|
|
156
|
+
} else {
|
|
157
|
+
return collapsed.map((subExpression) => collapseDynamicBooleanExpressions(subExpression));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Traverses the expression and replaces all instances of branching on a
|
|
163
|
+
* `dynamic` conditional (such as `['pitch']` or `['distance-from-center']`)
|
|
164
|
+
* into an `any` expression.
|
|
165
|
+
* This ensures that all possible outcomes of a `dynamic` branch are considered
|
|
166
|
+
* when evaluating the expression upfront during filtering.
|
|
167
|
+
*
|
|
168
|
+
* @param {Array<any>} filter the filter expression mutated in-place.
|
|
169
|
+
*/
|
|
170
|
+
function unionDynamicBranches(filter: any) {
|
|
171
|
+
let isBranchingDynamically = false;
|
|
172
|
+
const branches = [];
|
|
173
|
+
|
|
174
|
+
if (filter[0] === 'case') {
|
|
175
|
+
for (let i = 1; i < filter.length - 1; i += 2) {
|
|
176
|
+
isBranchingDynamically = isBranchingDynamically || isDynamicFilter(filter[i]);
|
|
177
|
+
branches.push(filter[i + 1]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
branches.push(filter[filter.length - 1]);
|
|
181
|
+
} else if (filter[0] === 'match') {
|
|
182
|
+
isBranchingDynamically = isBranchingDynamically || isDynamicFilter(filter[1]);
|
|
183
|
+
|
|
184
|
+
for (let i = 2; i < filter.length - 1; i += 2) {
|
|
185
|
+
branches.push(filter[i + 1]);
|
|
186
|
+
}
|
|
187
|
+
branches.push(filter[filter.length - 1]);
|
|
188
|
+
} else if (filter[0] === 'step') {
|
|
189
|
+
isBranchingDynamically = isBranchingDynamically || isDynamicFilter(filter[1]);
|
|
190
|
+
|
|
191
|
+
for (let i = 1; i < filter.length - 1; i += 2) {
|
|
192
|
+
branches.push(filter[i + 1]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (isBranchingDynamically) {
|
|
197
|
+
filter.length = 0;
|
|
198
|
+
filter.push('any', ...branches);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// traverse and recurse into children
|
|
202
|
+
for (let i = 1; i < filter.length; i++) {
|
|
203
|
+
unionDynamicBranches(filter[i]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isDynamicFilter(filter: any): boolean {
|
|
208
|
+
// Base Cases
|
|
209
|
+
if (!Array.isArray(filter)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
if (isRootExpressionDynamic(filter[0])) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (let i = 1; i < filter.length; i++) {
|
|
217
|
+
const child = filter[i];
|
|
218
|
+
if (isDynamicFilter(child)) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isRootExpressionDynamic(expression: string): boolean {
|
|
227
|
+
return expression === 'pitch' ||
|
|
228
|
+
expression === 'distance-from-center';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const dynamicConditionExpressions = new Set([
|
|
232
|
+
'in',
|
|
233
|
+
'==',
|
|
234
|
+
'!=',
|
|
235
|
+
'>',
|
|
236
|
+
'>=',
|
|
237
|
+
'<',
|
|
238
|
+
'<=',
|
|
239
|
+
'to-boolean'
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
function collapsedExpression(expression: any): any {
|
|
243
|
+
if (dynamicConditionExpressions.has(expression[0])) {
|
|
244
|
+
|
|
245
|
+
for (let i = 1; i < expression.length; i++) {
|
|
246
|
+
const param = expression[i];
|
|
247
|
+
if (isDynamicFilter(param)) {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
91
251
|
}
|
|
252
|
+
return expression;
|
|
92
253
|
}
|
|
93
254
|
|
|
94
255
|
// Comparison function to sort numbers and strings
|