@mapwhit/tilerenderer 0.48.0 → 0.50.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 (42) hide show
  1. package/build/min/package.json +1 -1
  2. package/build/min/src/shaders/fill_extrusion.fragment.glsl.txt +1 -9
  3. package/build/min/src/shaders/fill_extrusion.vertex.glsl.txt +4 -4
  4. package/package.json +3 -2
  5. package/src/data/array_types.js +169 -0
  6. package/src/data/bucket/symbol_attributes.js +18 -0
  7. package/src/data/bucket/symbol_bucket.js +108 -78
  8. package/src/geo/transform.js +13 -5
  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/shaders/fill_extrusion.fragment.glsl +0 -7
  13. package/src/shaders/fill_extrusion.vertex.glsl +4 -4
  14. package/src/style/style_layer/symbol_style_layer_properties.js +6 -0
  15. package/src/style-spec/expression/definitions/assertion.js +52 -5
  16. package/src/style-spec/expression/definitions/coalesce.js +1 -3
  17. package/src/style-spec/expression/definitions/coercion.js +32 -21
  18. package/src/style-spec/expression/definitions/collator.js +1 -23
  19. package/src/style-spec/expression/definitions/{formatted.js → format.js} +3 -29
  20. package/src/style-spec/expression/definitions/index.js +8 -26
  21. package/src/style-spec/expression/definitions/let.js +1 -1
  22. package/src/style-spec/expression/definitions/literal.js +1 -1
  23. package/src/style-spec/expression/evaluation_context.js +3 -0
  24. package/src/style-spec/expression/index.js +18 -17
  25. package/src/style-spec/expression/parsing_context.js +28 -24
  26. package/src/style-spec/expression/types/collator.js +24 -0
  27. package/src/style-spec/expression/types/formatted.js +39 -0
  28. package/src/style-spec/expression/types.js +1 -1
  29. package/src/style-spec/expression/values.js +24 -1
  30. package/src/style-spec/feature_filter/convert.js +197 -0
  31. package/src/style-spec/feature_filter/index.js +4 -1
  32. package/src/style-spec/function/convert.js +86 -102
  33. package/src/style-spec/function/index.js +4 -0
  34. package/src/style-spec/reference/v8.json +43 -6
  35. package/src/symbol/collision_index.js +0 -1
  36. package/src/symbol/cross_tile_symbol_index.js +12 -7
  37. package/src/symbol/mergelines.js +2 -2
  38. package/src/symbol/placement.js +71 -54
  39. package/src/symbol/shaping.js +9 -18
  40. package/src/symbol/symbol_layout.js +33 -33
  41. package/src/symbol/transform_text.js +5 -8
  42. package/src/style-spec/expression/definitions/array.js +0 -82
@@ -1,10 +1,10 @@
1
+ const assert = require('assert');
1
2
  const Scope = require('./scope');
2
3
 
3
4
  const { checkSubtype } = require('./types');
4
5
  const ParsingError = require('./parsing_error');
5
6
  const Literal = require('./definitions/literal');
6
7
  const Assertion = require('./definitions/assertion');
7
- const ArrayAssertion = require('./definitions/array');
8
8
  const Coercion = require('./definitions/coercion');
9
9
  const EvaluationContext = require('./evaluation_context');
10
10
  const { CollatorExpression } = require('./definitions/collator');
@@ -49,6 +49,16 @@ class ParsingContext {
49
49
  expr = ['literal', expr];
50
50
  }
51
51
 
52
+ function annotate(parsed, type, typeAnnotation) {
53
+ if (typeAnnotation === 'assert') {
54
+ return new Assertion(type, [parsed]);
55
+ }
56
+ if (typeAnnotation === 'coerce') {
57
+ return new Coercion(type, [parsed]);
58
+ }
59
+ return parsed;
60
+ }
61
+
52
62
  if (Array.isArray(expr)) {
53
63
  if (expr.length === 0) {
54
64
  return this.error(
@@ -74,34 +84,29 @@ class ParsingContext {
74
84
  const expected = this.expectedType;
75
85
  const actual = parsed.type;
76
86
 
77
- // When we expect a number, string, boolean, or array but
78
- // have a Value, we can wrap it in a refining assertion.
79
- // When we expect a Color but have a String or Value, we
80
- // can wrap it in "to-color" coercion.
87
+ // When we expect a number, string, boolean, or array but have a value, wrap it in an assertion.
88
+ // When we expect a color or formatted string, but have a string or value, wrap it in a coercion.
81
89
  // Otherwise, we do static type-checking.
90
+ //
91
+ // These behaviors are overridable for:
92
+ // * The "coalesce" operator, which needs to omit type annotations.
93
+ // * String-valued properties (e.g. `text-field`), where coercion is more convenient than assertion.
94
+ //
82
95
  if (
83
96
  (expected.kind === 'string' ||
84
97
  expected.kind === 'number' ||
85
98
  expected.kind === 'boolean' ||
86
- expected.kind === 'object') &&
99
+ expected.kind === 'object' ||
100
+ expected.kind === 'array') &&
87
101
  actual.kind === 'value'
88
102
  ) {
89
- if (!options.omitTypeAnnotations) {
90
- parsed = new Assertion(expected, [parsed]);
91
- }
92
- } else if (expected.kind === 'array' && actual.kind === 'value') {
93
- if (!options.omitTypeAnnotations) {
94
- parsed = new ArrayAssertion(expected, parsed);
95
- }
96
- } else if (expected.kind === 'color' && (actual.kind === 'value' || actual.kind === 'string')) {
97
- if (!options.omitTypeAnnotations) {
98
- parsed = new Coercion(expected, [parsed]);
99
- }
100
- } else if (expected.kind === 'formatted' && (actual.kind === 'value' || actual.kind === 'string')) {
101
- if (!options.omitTypeAnnotations) {
102
- parsed = new Coercion(expected, [parsed]);
103
- }
104
- } else if (this.checkSubtype(this.expectedType, parsed.type)) {
103
+ parsed = annotate(parsed, expected, options.typeAnnotation || 'assert');
104
+ } else if (
105
+ (expected.kind === 'color' || expected.kind === 'formatted') &&
106
+ (actual.kind === 'value' || actual.kind === 'string')
107
+ ) {
108
+ parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
109
+ } else if (this.checkSubtype(expected, actual)) {
105
110
  return null;
106
111
  }
107
112
  }
@@ -188,8 +193,7 @@ function isConstant(expression) {
188
193
  return false;
189
194
  }
190
195
 
191
- const isTypeAnnotation =
192
- expression instanceof Coercion || expression instanceof Assertion || expression instanceof ArrayAssertion;
196
+ const isTypeAnnotation = expression instanceof Coercion || expression instanceof Assertion;
193
197
 
194
198
  let childrenConstant = true;
195
199
  expression.eachChild(child => {
@@ -0,0 +1,24 @@
1
+ class Collator {
2
+ constructor(caseSensitive, diacriticSensitive, locale) {
3
+ if (caseSensitive) this.sensitivity = diacriticSensitive ? 'variant' : 'case';
4
+ else this.sensitivity = diacriticSensitive ? 'accent' : 'base';
5
+
6
+ this.locale = locale;
7
+ this.collator = new Intl.Collator(this.locale ? this.locale : [], {
8
+ sensitivity: this.sensitivity,
9
+ usage: 'search'
10
+ });
11
+ }
12
+
13
+ compare(lhs, rhs) {
14
+ return this.collator.compare(lhs, rhs);
15
+ }
16
+
17
+ resolvedLocale() {
18
+ // We create a Collator without "usage: search" because we don't want
19
+ // the search options encoded in our result (e.g. "en-u-co-search")
20
+ return new Intl.Collator(this.locale ? this.locale : []).resolvedOptions().locale;
21
+ }
22
+ }
23
+
24
+ module.exports = { Collator };
@@ -0,0 +1,39 @@
1
+ class FormattedSection {
2
+ constructor(text, scale, fontStack = null) {
3
+ this.text = text;
4
+ this.scale = scale;
5
+ this.fontStack = fontStack;
6
+ }
7
+ }
8
+
9
+ class Formatted {
10
+ constructor(sections) {
11
+ this.sections = sections;
12
+ }
13
+
14
+ static fromString(unformatted) {
15
+ return new Formatted([new FormattedSection(unformatted, null, null)]);
16
+ }
17
+
18
+ toString() {
19
+ return this.sections.map(section => section.text).join('');
20
+ }
21
+
22
+ serialize() {
23
+ const serialized = ['format'];
24
+ for (const section of this.sections) {
25
+ serialized.push(section.text);
26
+ const options = {};
27
+ if (section.fontStack) {
28
+ options['text-font'] = ['literal', section.fontStack.split(',')];
29
+ }
30
+ if (section.scale) {
31
+ options['font-scale'] = section.scale;
32
+ }
33
+ serialized.push(options);
34
+ }
35
+ return serialized;
36
+ }
37
+ }
38
+
39
+ module.exports = { Formatted, FormattedSection };
@@ -53,7 +53,7 @@ function checkSubtype(expected, t) {
53
53
  if (expected.kind === 'array') {
54
54
  if (
55
55
  t.kind === 'array' &&
56
- !checkSubtype(expected.itemType, t.itemType) &&
56
+ ((t.N === 0 && t.itemType.kind === 'value') || !checkSubtype(expected.itemType, t.itemType)) &&
57
57
  (typeof expected.N !== 'number' || expected.N === t.N)
58
58
  ) {
59
59
  return null;
@@ -1,7 +1,8 @@
1
1
  const assert = require('assert');
2
2
 
3
3
  const Color = require('../util/color');
4
- const { Collator } = require('./definitions/collator');
4
+ const { Collator } = require('./types/collator');
5
+ const { Formatted } = require('./types/formatted');
5
6
  const {
6
7
  NullType,
7
8
  NumberType,
@@ -11,6 +12,7 @@ const {
11
12
  ObjectType,
12
13
  ValueType,
13
14
  CollatorType,
15
+ FormattedType,
14
16
  array
15
17
  } = require('./types');
16
18
 
@@ -58,6 +60,9 @@ function isValue(mixed) {
58
60
  if (mixed instanceof Collator) {
59
61
  return true;
60
62
  }
63
+ if (mixed instanceof Formatted) {
64
+ return true;
65
+ }
61
66
  if (Array.isArray(mixed)) {
62
67
  for (const item of mixed) {
63
68
  if (!isValue(item)) {
@@ -96,6 +101,9 @@ function typeOf(value) {
96
101
  if (value instanceof Collator) {
97
102
  return CollatorType;
98
103
  }
104
+ if (value instanceof Formatted) {
105
+ return FormattedType;
106
+ }
99
107
  if (Array.isArray(value)) {
100
108
  const length = value.length;
101
109
  let itemType;
@@ -117,7 +125,22 @@ function typeOf(value) {
117
125
  return ObjectType;
118
126
  }
119
127
 
128
+ function toString(value) {
129
+ const type = typeof value;
130
+ if (value === null) {
131
+ return '';
132
+ }
133
+ if (type === 'string' || type === 'number' || type === 'boolean') {
134
+ return String(value);
135
+ }
136
+ if (value instanceof Color || value instanceof Formatted) {
137
+ return value.toString();
138
+ }
139
+ return JSON.stringify(value);
140
+ }
141
+
120
142
  module.exports = {
143
+ toString,
121
144
  Color,
122
145
  Collator,
123
146
  validateRGBA,
@@ -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
  }
@@ -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