@mapwhit/tilerenderer 0.47.2 → 0.49.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.
Files changed (43) hide show
  1. package/build/min/package.json +1 -1
  2. package/package.json +3 -2
  3. package/src/data/array_types.js +169 -0
  4. package/src/data/bucket/symbol_attributes.js +18 -0
  5. package/src/data/bucket/symbol_bucket.js +116 -73
  6. package/src/data/program_configuration.js +18 -10
  7. package/src/geo/transform.js +17 -7
  8. package/src/index.js +1 -1
  9. package/src/render/glyph_atlas.js +3 -6
  10. package/src/render/image_atlas.js +3 -6
  11. package/src/render/image_manager.js +41 -41
  12. package/src/source/rtl_text_plugin.js +1 -0
  13. package/src/source/source_cache.js +1 -1
  14. package/src/source/tile.js +6 -5
  15. package/src/source/worker.js +1 -10
  16. package/src/style/style.js +4 -19
  17. package/src/style/style_layer/symbol_style_layer_properties.js +7 -1
  18. package/src/style-spec/expression/compound_expression.js +30 -16
  19. package/src/style-spec/expression/definitions/assertion.js +52 -5
  20. package/src/style-spec/expression/definitions/coercion.js +13 -0
  21. package/src/style-spec/expression/definitions/comparison.js +193 -0
  22. package/src/style-spec/expression/definitions/formatted.js +123 -0
  23. package/src/style-spec/expression/definitions/index.js +11 -62
  24. package/src/style-spec/expression/definitions/interpolate.js +17 -7
  25. package/src/style-spec/expression/definitions/literal.js +5 -0
  26. package/src/style-spec/expression/parsing_context.js +6 -7
  27. package/src/style-spec/expression/types.js +12 -1
  28. package/src/style-spec/feature_filter/convert.js +197 -0
  29. package/src/style-spec/feature_filter/index.js +5 -2
  30. package/src/style-spec/function/convert.js +78 -100
  31. package/src/style-spec/reference/v8.json +160 -52
  32. package/src/symbol/collision_index.js +0 -1
  33. package/src/symbol/cross_tile_symbol_index.js +12 -7
  34. package/src/symbol/get_anchors.js +11 -22
  35. package/src/symbol/mergelines.js +4 -1
  36. package/src/symbol/placement.js +58 -54
  37. package/src/symbol/quads.js +7 -6
  38. package/src/symbol/shaping.js +185 -40
  39. package/src/symbol/symbol_layout.js +40 -37
  40. package/src/symbol/transform_text.js +12 -1
  41. package/src/ui/map.js +8 -25
  42. package/src/style-spec/expression/definitions/array.js +0 -82
  43. package/src/style-spec/expression/definitions/equals.js +0 -93
@@ -0,0 +1,197 @@
1
+ const { isExpressionFilter } = require('./index');
2
+
3
+ module.exports = convertFilter;
4
+
5
+ /**
6
+ * Convert the given legacy filter to (the JSON representation of) an
7
+ * equivalent expression
8
+ * @private
9
+ */
10
+ function convertFilter(filter) {
11
+ return _convertFilter(filter, {});
12
+ }
13
+
14
+ /*
15
+ * Convert the given filter to an expression, storing the expected types for
16
+ * any feature properties referenced in expectedTypes.
17
+ *
18
+ * These expected types are needed in order to construct preflight type checks
19
+ * needed for handling 'any' filters. A preflight type check is necessary in
20
+ * order to mimic legacy filters' semantics around expected type mismatches.
21
+ * For example, consider the legacy filter:
22
+ *
23
+ * ["any", ["all", [">", "y", 0], [">", "y", 0]], [">", "x", 0]]
24
+ *
25
+ * Naively, we might convert this to the expression:
26
+ *
27
+ * ["any", ["all", [">", ["get", "y"], 0], [">", ["get", "z"], 0]], [">", ["get", "x"], 0]]
28
+ *
29
+ * But if we tried to evaluate this against, say `{x: 1, y: null, z: 0}`, the
30
+ * [">", ["get", "y"], 0] would cause an evaluation error, leading to the
31
+ * entire filter returning false. Legacy filter semantics, though, ask for
32
+ * [">", "y", 0] to simply return `false` when `y` is of the wrong type,
33
+ * allowing the subsequent terms of the outer "any" expression to be evaluated
34
+ * (resulting, in this case, in a `true` value, because x > 0).
35
+ *
36
+ * We account for this by inserting a preflight type-checking expression before
37
+ * each "any" term, allowing us to avoid evaluating the actual converted filter
38
+ * if any type mismatches would cause it to produce an evalaution error:
39
+ *
40
+ * ["any",
41
+ * ["case",
42
+ * ["all", ["==", ["typeof", ["get", "y"]], "number"], ["==", ["typeof", ["get", "z"], "number]],
43
+ * ["all", [">", ["get", "y"], 0], [">", ["get", "z"], 0]],
44
+ * false
45
+ * ],
46
+ * ["case",
47
+ * ["==", ["typeof", ["get", "x"], "number"]],
48
+ * [">", ["get", "x"], 0],
49
+ * false
50
+ * ]
51
+ * ]
52
+ *
53
+ * An alternative, possibly more direct approach would be to use type checks
54
+ * in the conversion of each comparison operator, so that the converted version
55
+ * of each individual ==, >=, etc. would mimic the legacy filter semantics. The
56
+ * downside of this approach is that it can lead to many more type checks than
57
+ * would otherwise be necessary: outside the context of an "any" expression,
58
+ * bailing out due to a runtime type error (expression semantics) and returning
59
+ * false (legacy filter semantics) are equivalent: they cause the filter to
60
+ * produce a `false` result.
61
+ */
62
+ function _convertFilter(filter, expectedTypes) {
63
+ if (isExpressionFilter(filter)) {
64
+ return filter;
65
+ }
66
+
67
+ if (!filter) return true;
68
+ const op = filter[0];
69
+ if (filter.length <= 1) return op !== 'any';
70
+
71
+ let converted;
72
+
73
+ if (op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=') {
74
+ const [, property, value] = filter;
75
+ converted = convertComparisonOp(property, value, op, expectedTypes);
76
+ } else if (op === 'any') {
77
+ const children = filter.slice(1).map(f => {
78
+ const types = {};
79
+ const child = _convertFilter(f, types);
80
+ const typechecks = runtimeTypeChecks(types);
81
+ return typechecks === true ? child : ['case', typechecks, child, false];
82
+ });
83
+ return ['any'].concat(children);
84
+ } else if (op === 'all') {
85
+ const children = filter.slice(1).map(f => _convertFilter(f, expectedTypes));
86
+ return ['all'].concat(children);
87
+ } else if (op === 'none') {
88
+ return ['!', _convertFilter(['any'].concat(filter.slice(1)), {})];
89
+ } else if (op === 'in') {
90
+ converted = convertInOp(filter[1], filter.slice(2));
91
+ } else if (op === '!in') {
92
+ converted = convertInOp(filter[1], filter.slice(2), true);
93
+ } else if (op === 'has') {
94
+ converted = convertHasOp(filter[1]);
95
+ } else if (op === '!has') {
96
+ converted = ['!', convertHasOp(filter[1])];
97
+ } else {
98
+ converted = true;
99
+ }
100
+
101
+ return converted;
102
+ }
103
+
104
+ // Given a set of feature properties and an expected type for each one,
105
+ // construct an boolean expression that tests whether each property has the
106
+ // right type.
107
+ // E.g.: for {name: 'string', population: 'number'}, return
108
+ // [ 'all',
109
+ // ['==', ['typeof', ['get', 'name'], 'string']],
110
+ // ['==', ['typeof', ['get', 'population'], 'number]]
111
+ // ]
112
+ function runtimeTypeChecks(expectedTypes) {
113
+ const conditions = [];
114
+ for (const property in expectedTypes) {
115
+ const get = property === '$id' ? ['id'] : ['get', property];
116
+ conditions.push(['==', ['typeof', get], expectedTypes[property]]);
117
+ }
118
+ if (conditions.length === 0) return true;
119
+ if (conditions.length === 1) return conditions[0];
120
+ return ['all'].concat(conditions);
121
+ }
122
+
123
+ function convertComparisonOp(property, value, op, expectedTypes) {
124
+ let get;
125
+ if (property === '$type') {
126
+ return [op, ['geometry-type'], value];
127
+ }
128
+ if (property === '$id') {
129
+ get = ['id'];
130
+ } else {
131
+ get = ['get', property];
132
+ }
133
+
134
+ if (expectedTypes && value !== null) {
135
+ const type = typeof value;
136
+ expectedTypes[property] = type;
137
+ }
138
+
139
+ if (op === '==' && property !== '$id' && value === null) {
140
+ return [
141
+ 'all',
142
+ ['has', property], // missing property != null for legacy filters
143
+ ['==', get, null]
144
+ ];
145
+ }
146
+ if (op === '!=' && property !== '$id' && value === null) {
147
+ return [
148
+ 'any',
149
+ ['!', ['has', property]], // missing property != null for legacy filters
150
+ ['!=', get, null]
151
+ ];
152
+ }
153
+
154
+ return [op, get, value];
155
+ }
156
+
157
+ function convertInOp(property, values, negate = false) {
158
+ if (values.length === 0) return negate;
159
+
160
+ let get;
161
+ if (property === '$type') {
162
+ get = ['geometry-type'];
163
+ } else if (property === '$id') {
164
+ get = ['id'];
165
+ } else {
166
+ get = ['get', property];
167
+ }
168
+
169
+ // Determine if the list of values to be searched is homogenously typed.
170
+ // If so (and if the type is string or number), then we can use a
171
+ // [match, input, [...values], true, false] construction rather than a
172
+ // bunch of `==` tests.
173
+ let uniformTypes = true;
174
+ const type = typeof values[0];
175
+ for (const value of values) {
176
+ if (typeof value !== type) {
177
+ uniformTypes = false;
178
+ break;
179
+ }
180
+ }
181
+
182
+ if (uniformTypes && (type === 'string' || type === 'number')) {
183
+ return ['match', get, values, !negate, negate];
184
+ }
185
+
186
+ return [negate ? 'all' : 'any'].concat(values.map(v => [negate ? '!=' : '==', get, v]));
187
+ }
188
+
189
+ function convertHasOp(property) {
190
+ if (property === '$type') {
191
+ return true;
192
+ }
193
+ if (property === '$id') {
194
+ return ['!=', ['id'], null];
195
+ }
196
+ return ['has', property];
197
+ }
@@ -5,6 +5,9 @@ module.exports = createFilter;
5
5
  createFilter.isExpressionFilter = isExpressionFilter;
6
6
 
7
7
  function isExpressionFilter(filter) {
8
+ if (filter === true || filter === false) {
9
+ return true;
10
+ }
8
11
  if (!Array.isArray(filter) || filter.length === 0) {
9
12
  return false;
10
13
  }
@@ -24,7 +27,7 @@ function isExpressionFilter(filter) {
24
27
  case '>=':
25
28
  case '<':
26
29
  case '<=':
27
- return filter.length === 3 && (Array.isArray(filter[1]) || Array.isArray(filter[2]));
30
+ return filter.length !== 3 || Array.isArray(filter[1]) || Array.isArray(filter[2]);
28
31
 
29
32
  case 'any':
30
33
  case 'all':
@@ -61,7 +64,7 @@ const filterSpec = {
61
64
  * @returns {Function} filter-evaluating function
62
65
  */
63
66
  function createFilter(filter) {
64
- if (!filter) {
67
+ if (filter === null || filter === undefined) {
65
68
  return () => true;
66
69
  }
67
70
 
@@ -3,85 +3,64 @@ const assert = require('assert');
3
3
  module.exports = convertFunction;
4
4
 
5
5
  function convertFunction(parameters, propertySpec) {
6
- let expression;
7
-
8
- parameters = { ...parameters };
9
- let defaultExpression;
10
- if (typeof parameters.default !== 'undefined') {
11
- defaultExpression = convertValue(parameters.default, propertySpec);
12
- } else {
13
- defaultExpression = convertValue(propertySpec.default, propertySpec);
14
- if (defaultExpression === null) {
15
- defaultExpression = ['error', 'No default property value available.'];
16
- }
6
+ let stops = parameters.stops;
7
+ if (!stops) {
8
+ // identity function
9
+ return convertIdentityFunction(parameters, propertySpec);
17
10
  }
18
11
 
19
- if (parameters.stops) {
20
- const zoomAndFeatureDependent = parameters.stops && typeof parameters.stops[0][0] === 'object';
21
- const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined;
22
- const zoomDependent = zoomAndFeatureDependent || !featureDependent;
23
-
24
- const stops = parameters.stops.map(stop => {
25
- if (!featureDependent && propertySpec.tokens && typeof stop[1] === 'string') {
26
- return [stop[0], convertTokenString(stop[1])];
27
- }
28
- return [stop[0], convertValue(stop[1], propertySpec)];
29
- });
12
+ const zoomAndFeatureDependent = stops && typeof stops[0][0] === 'object';
13
+ const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined;
14
+ const zoomDependent = zoomAndFeatureDependent || !featureDependent;
30
15
 
31
- if (parameters.colorSpace && parameters.colorSpace !== 'rgb') {
32
- throw new Error('Unimplemented');
16
+ stops = stops.map(stop => {
17
+ if (!featureDependent && propertySpec.tokens && typeof stop[1] === 'string') {
18
+ return [stop[0], convertTokenString(stop[1])];
33
19
  }
20
+ return [stop[0], ['literal', stop[1]]];
21
+ });
34
22
 
35
- if (zoomAndFeatureDependent) {
36
- expression = convertZoomAndPropertyFunction(parameters, propertySpec, stops, defaultExpression);
37
- } else if (zoomDependent) {
38
- expression = convertZoomFunction(parameters, propertySpec, stops);
39
- } else {
40
- expression = convertPropertyFunction(parameters, propertySpec, stops, defaultExpression);
41
- }
42
- } else {
43
- // identity function
44
- expression = convertIdentityFunction(parameters, propertySpec, defaultExpression);
23
+ if (zoomAndFeatureDependent) {
24
+ return convertZoomAndPropertyFunction(parameters, propertySpec, stops);
45
25
  }
46
-
47
- return expression;
26
+ if (zoomDependent) {
27
+ return convertZoomFunction(parameters, propertySpec, stops);
28
+ }
29
+ return convertPropertyFunction(parameters, propertySpec, stops);
48
30
  }
49
31
 
50
- function convertIdentityFunction(parameters, propertySpec, defaultExpression) {
32
+ function convertIdentityFunction(parameters, propertySpec) {
51
33
  const get = ['get', parameters.property];
52
34
 
53
- if (propertySpec.type === 'color') {
54
- return parameters.default === undefined ? get : ['to-color', get, parameters.default];
35
+ if (parameters.default === undefined) {
36
+ return get;
55
37
  }
56
- if (propertySpec.type === 'array' && typeof propertySpec.length === 'number') {
57
- return ['array', propertySpec.value, propertySpec.length, get];
38
+ if (propertySpec.type === 'enum') {
39
+ return ['match', get, Object.keys(propertySpec.values), get, parameters.default];
58
40
  }
41
+ const expression = [
42
+ propertySpec.type === 'color' ? 'to-color' : propertySpec.type,
43
+ get,
44
+ ['literal', parameters.default]
45
+ ];
59
46
  if (propertySpec.type === 'array') {
60
- return ['array', propertySpec.value, get];
61
- }
62
- if (propertySpec.type === 'enum') {
63
- return [
64
- 'let',
65
- 'property_value',
66
- ['string', get],
67
- ['match', ['var', 'property_value'], propertySpec.values, ['var', 'property_value'], defaultExpression]
68
- ];
47
+ expression.splice(1, 0, propertySpec.value, propertySpec.length || null);
69
48
  }
70
- return parameters.default === undefined ? get : [propertySpec.type, get, parameters.default];
49
+ return expression;
71
50
  }
72
51
 
73
- function convertValue(value, spec) {
74
- if (typeof value === 'undefined' || value === null) return null;
75
- if (spec.type === 'color') {
76
- return value;
77
- }
78
- if (spec.type === 'array') {
79
- return ['literal', value];
52
+ function getInterpolateOperator(parameters) {
53
+ switch (parameters.colorSpace) {
54
+ case 'hcl':
55
+ return 'interpolate-hcl';
56
+ case 'lab':
57
+ return 'interpolate-lab';
58
+ default:
59
+ return 'interpolate';
80
60
  }
81
- return value;
82
61
  }
83
62
 
84
- function convertZoomAndPropertyFunction(parameters, propertySpec, stops, defaultExpression) {
63
+ function convertZoomAndPropertyFunction(parameters, propertySpec, stops) {
85
64
  const featureFunctionParameters = {};
86
65
  const featureFunctionStops = {};
87
66
  const zoomStops = [];
@@ -107,15 +86,10 @@ function convertZoomAndPropertyFunction(parameters, propertySpec, stops, default
107
86
  // otherwise.
108
87
  const functionType = getFunctionType({}, propertySpec);
109
88
  if (functionType === 'exponential') {
110
- const expression = ['interpolate', ['linear'], ['zoom']];
89
+ const expression = [getInterpolateOperator(parameters), ['linear'], ['zoom']];
111
90
 
112
91
  for (const z of zoomStops) {
113
- const output = convertPropertyFunction(
114
- featureFunctionParameters[z],
115
- propertySpec,
116
- featureFunctionStops[z],
117
- defaultExpression
118
- );
92
+ const output = convertPropertyFunction(featureFunctionParameters[z], propertySpec, featureFunctionStops[z]);
119
93
  appendStopPair(expression, z, output, false);
120
94
  }
121
95
 
@@ -124,12 +98,7 @@ function convertZoomAndPropertyFunction(parameters, propertySpec, stops, default
124
98
  const expression = ['step', ['zoom']];
125
99
 
126
100
  for (const z of zoomStops) {
127
- const output = convertPropertyFunction(
128
- featureFunctionParameters[z],
129
- propertySpec,
130
- featureFunctionStops[z],
131
- defaultExpression
132
- );
101
+ const output = convertPropertyFunction(featureFunctionParameters[z], propertySpec, featureFunctionStops[z]);
133
102
  appendStopPair(expression, z, output, true);
134
103
  }
135
104
 
@@ -138,43 +107,52 @@ function convertZoomAndPropertyFunction(parameters, propertySpec, stops, default
138
107
  return expression;
139
108
  }
140
109
 
141
- function convertPropertyFunction(parameters, propertySpec, stops, defaultExpression) {
142
- const type = getFunctionType(parameters, propertySpec);
110
+ function coalesce(a, b) {
111
+ if (a !== undefined) return a;
112
+ if (b !== undefined) return b;
113
+ }
143
114
 
144
- let expression;
145
- let isStep = false;
115
+ function convertPropertyFunction(parameters, propertySpec, stops) {
116
+ const type = getFunctionType(parameters, propertySpec);
117
+ const get = ['get', parameters.property];
146
118
  if (type === 'categorical' && typeof stops[0][0] === 'boolean') {
147
119
  assert(parameters.stops.length > 0 && parameters.stops.length <= 2);
148
- expression = ['case'];
120
+ const expression = ['case'];
149
121
  for (const stop of stops) {
150
- expression.push(['==', ['get', parameters.property], stop[0]], stop[1]);
122
+ expression.push(['==', get, stop[0]], stop[1]);
151
123
  }
152
- expression.push(defaultExpression);
124
+ expression.push(['literal', coalesce(parameters.default, propertySpec.default)]);
153
125
  return expression;
154
126
  }
155
127
  if (type === 'categorical') {
156
- expression = ['match', ['get', parameters.property]];
157
- } else if (type === 'interval') {
158
- expression = ['step', ['number', ['get', parameters.property]]];
159
- isStep = true;
160
- } else if (type === 'exponential') {
161
- const base = parameters.base !== undefined ? parameters.base : 1;
162
- expression = ['interpolate', ['exponential', base], ['number', ['get', parameters.property]]];
163
- } else {
164
- throw new Error(`Unknown property function type ${type}`);
128
+ const expression = ['match', get];
129
+ for (const stop of stops) {
130
+ appendStopPair(expression, stop[0], stop[1], false);
131
+ }
132
+ expression.push(['literal', coalesce(parameters.default, propertySpec.default)]);
133
+ return expression;
165
134
  }
166
-
167
- for (const stop of stops) {
168
- appendStopPair(expression, stop[0], stop[1], isStep);
135
+ if (type === 'interval') {
136
+ const expression = ['step', ['number', get]];
137
+ for (const stop of stops) {
138
+ appendStopPair(expression, stop[0], stop[1], true);
139
+ }
140
+ fixupDegenerateStepCurve(expression);
141
+ return parameters.default === undefined
142
+ ? expression
143
+ : ['case', ['==', ['typeof', get], 'number'], expression, ['literal', parameters.default]];
169
144
  }
170
-
171
- if (expression[0] === 'match') {
172
- expression.push(defaultExpression);
145
+ if (type === 'exponential') {
146
+ const base = parameters.base !== undefined ? parameters.base : 1;
147
+ const expression = [getInterpolateOperator(parameters), ['exponential', base], ['number', get]];
148
+ for (const stop of stops) {
149
+ appendStopPair(expression, stop[0], stop[1], false);
150
+ }
151
+ return parameters.default === undefined
152
+ ? expression
153
+ : ['case', ['==', ['typeof', get], 'number'], expression, ['literal', parameters.default]];
173
154
  }
174
-
175
- fixupDegenerateStepCurve(expression);
176
-
177
- return expression;
155
+ throw new Error(`Unknown property function type ${type}`);
178
156
  }
179
157
 
180
158
  function convertZoomFunction(parameters, propertySpec, stops, input = ['zoom']) {
@@ -186,7 +164,7 @@ function convertZoomFunction(parameters, propertySpec, stops, input = ['zoom'])
186
164
  isStep = true;
187
165
  } else if (type === 'exponential') {
188
166
  const base = parameters.base !== undefined ? parameters.base : 1;
189
- expression = ['interpolate', ['exponential', base], input];
167
+ expression = [getInterpolateOperator(parameters), ['exponential', base], input];
190
168
  } else {
191
169
  throw new Error(`Unknown zoom function type "${type}"`);
192
170
  }