maplibre-gl 2.2.0-pre.2 → 2.2.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 (169) hide show
  1. package/README.md +73 -8
  2. package/build/generate-debug-index-file.ts +19 -0
  3. package/build/generate-style-code.ts +6 -1
  4. package/build/generate-style-spec.ts +151 -35
  5. package/build/rollup_plugins.ts +4 -1
  6. package/dist/maplibre-gl-csp-worker.js +1 -1
  7. package/dist/maplibre-gl-csp-worker.js.map +1 -1
  8. package/dist/maplibre-gl-csp.js +1 -1
  9. package/dist/maplibre-gl-csp.js.map +1 -1
  10. package/dist/maplibre-gl-dev.js +1317 -4304
  11. package/dist/maplibre-gl.css +1 -1
  12. package/dist/maplibre-gl.d.ts +443 -145
  13. package/dist/maplibre-gl.js +4 -4
  14. package/dist/maplibre-gl.js.map +1 -1
  15. package/package.json +70 -67
  16. package/src/css/maplibre-gl.css +48 -32
  17. package/src/data/bucket/fill_bucket.test.ts +1 -0
  18. package/src/data/bucket/symbol_bucket.test.ts +2 -0
  19. package/src/data/bucket/symbol_bucket.ts +1 -1
  20. package/src/data/evaluation_feature.ts +1 -1
  21. package/src/data/program_configuration.ts +2 -2
  22. package/src/geo/transform.test.ts +34 -1
  23. package/src/geo/transform.ts +25 -15
  24. package/src/gl/vertex_buffer.ts +4 -4
  25. package/src/index.ts +1 -1
  26. package/src/render/draw_debug.ts +1 -1
  27. package/src/render/draw_symbol.test.ts +2 -23
  28. package/src/render/draw_terrain.ts +1 -1
  29. package/src/render/image_atlas.ts +1 -0
  30. package/src/render/image_manager.ts +1 -0
  31. package/src/render/program/debug_program.ts +1 -1
  32. package/src/render/render_to_texture.ts +3 -0
  33. package/src/render/terrain.test.ts +119 -17
  34. package/src/render/terrain.ts +39 -21
  35. package/src/shaders/README.md +2 -2
  36. package/src/shaders/shaders.ts +3 -1
  37. package/src/source/geojson_worker_source.test.ts +2 -2
  38. package/src/source/geojson_wrapper.test.ts +1 -1
  39. package/src/source/image_source.test.ts +8 -8
  40. package/src/source/image_source.ts +1 -1
  41. package/src/source/load_tilejson.ts +6 -1
  42. package/src/source/pixels_to_tile_units.ts +1 -1
  43. package/src/source/raster_tile_source.test.ts +1 -1
  44. package/src/source/source_cache.test.ts +12 -12
  45. package/src/source/source_cache.ts +1 -1
  46. package/src/source/terrain_source_cache.test.ts +17 -2
  47. package/src/source/terrain_source_cache.ts +16 -12
  48. package/src/source/vector_tile_source.test.ts +1 -1
  49. package/src/source/vector_tile_worker_source.test.ts +1 -1
  50. package/src/source/video_source.test.ts +2 -2
  51. package/src/style/light.test.ts +1 -1
  52. package/src/style/load_sprite.ts +1 -1
  53. package/src/style/parse_glyph_pbf.ts +1 -1
  54. package/src/style/style.test.ts +3 -3
  55. package/src/style/style.ts +2 -2
  56. package/src/style/style_layer/background_style_layer_properties.g.ts +1 -0
  57. package/src/style/style_layer/circle_style_layer_properties.g.ts +1 -0
  58. package/src/style/style_layer/fill_extrusion_style_layer_properties.g.ts +1 -0
  59. package/src/style/style_layer/fill_style_layer_properties.g.ts +1 -0
  60. package/src/style/style_layer/heatmap_style_layer_properties.g.ts +1 -0
  61. package/src/style/style_layer/hillshade_style_layer_properties.g.ts +1 -0
  62. package/src/style/style_layer/line_style_layer_properties.g.ts +1 -0
  63. package/src/style/style_layer/raster_style_layer_properties.g.ts +1 -0
  64. package/src/style/style_layer/symbol_style_layer.ts +16 -1
  65. package/src/style/style_layer/symbol_style_layer_properties.g.ts +4 -3
  66. package/src/style-spec/CHANGELOG.md +5 -0
  67. package/src/style-spec/composite.test.ts +2 -0
  68. package/src/style-spec/composite.ts +3 -2
  69. package/src/style-spec/diff.test.ts +3 -3
  70. package/src/style-spec/empty.ts +3 -2
  71. package/src/style-spec/expression/compound_expression.ts +0 -4
  72. package/src/style-spec/expression/definitions/assertion.ts +0 -18
  73. package/src/style-spec/expression/definitions/at.ts +0 -4
  74. package/src/style-spec/expression/definitions/case.ts +0 -6
  75. package/src/style-spec/expression/definitions/coalesce.ts +0 -6
  76. package/src/style-spec/expression/definitions/coercion.ts +13 -18
  77. package/src/style-spec/expression/definitions/collator.ts +0 -10
  78. package/src/style-spec/expression/definitions/comparison.ts +0 -6
  79. package/src/style-spec/expression/definitions/format.ts +0 -19
  80. package/src/style-spec/expression/definitions/image.ts +0 -4
  81. package/src/style-spec/expression/definitions/in.ts +0 -4
  82. package/src/style-spec/expression/definitions/index_of.ts +0 -8
  83. package/src/style-spec/expression/definitions/interpolate.ts +1 -25
  84. package/src/style-spec/expression/definitions/length.ts +0 -6
  85. package/src/style-spec/expression/definitions/let.ts +0 -9
  86. package/src/style-spec/expression/definitions/literal.ts +1 -23
  87. package/src/style-spec/expression/definitions/match.ts +0 -41
  88. package/src/style-spec/expression/definitions/number_format.ts +0 -17
  89. package/src/style-spec/expression/definitions/slice.ts +0 -8
  90. package/src/style-spec/expression/definitions/step.ts +0 -11
  91. package/src/style-spec/expression/definitions/var.ts +0 -4
  92. package/src/style-spec/expression/definitions/within.ts +0 -5
  93. package/src/style-spec/expression/expression.test.ts +1 -1
  94. package/src/style-spec/expression/expression.ts +3 -3
  95. package/src/style-spec/expression/index.ts +8 -2
  96. package/src/style-spec/expression/parsing_context.ts +2 -0
  97. package/src/style-spec/expression/types/formatted.ts +0 -23
  98. package/src/style-spec/expression/types/resolved_image.ts +0 -4
  99. package/src/style-spec/expression/types.ts +6 -1
  100. package/src/style-spec/expression/values.ts +9 -4
  101. package/src/style-spec/feature_filter/convert.ts +65 -65
  102. package/src/style-spec/feature_filter/feature_filter.test.ts +45 -4
  103. package/src/style-spec/feature_filter/index.ts +2 -1
  104. package/src/style-spec/function/index.test.ts +117 -1
  105. package/src/style-spec/function/index.ts +24 -12
  106. package/src/style-spec/migrate/expressions.ts +2 -2
  107. package/src/style-spec/migrate/v8.test.ts +2 -0
  108. package/src/style-spec/migrate/v8.ts +8 -7
  109. package/src/style-spec/migrate/v9.test.ts +6 -4
  110. package/src/style-spec/migrate/v9.ts +3 -2
  111. package/src/style-spec/migrate.test.ts +3 -1
  112. package/src/style-spec/migrate.ts +5 -4
  113. package/src/style-spec/package.json +1 -1
  114. package/src/style-spec/read_style.ts +2 -1
  115. package/src/style-spec/reference/latest.ts +1 -1
  116. package/src/style-spec/reference/v8.json +9 -6
  117. package/src/style-spec/style-spec.test.ts +2 -1
  118. package/src/style-spec/style-spec.ts +8 -0
  119. package/src/style-spec/types.g.ts +152 -36
  120. package/src/style-spec/util/extend.ts +1 -1
  121. package/src/style-spec/util/interpolate.test.ts +5 -0
  122. package/src/style-spec/util/interpolate.ts +12 -0
  123. package/src/style-spec/util/padding.test.ts +27 -0
  124. package/src/style-spec/util/padding.ts +64 -0
  125. package/src/style-spec/util/ref_properties.ts +2 -1
  126. package/src/style-spec/validate/validate.ts +3 -1
  127. package/src/style-spec/validate/validate_expression.ts +2 -1
  128. package/src/style-spec/validate/validate_function.ts +2 -2
  129. package/src/style-spec/validate/validate_glyphs_url.ts +1 -1
  130. package/src/style-spec/validate/validate_object.ts +2 -2
  131. package/src/style-spec/validate/validate_padding.test.ts +82 -0
  132. package/src/style-spec/validate/validate_padding.ts +36 -0
  133. package/src/style-spec/validate_style.min.ts +4 -3
  134. package/src/style-spec/validate_style.ts +4 -3
  135. package/src/symbol/check_max_angle.test.ts +5 -5
  136. package/src/symbol/collision_feature.test.ts +22 -5
  137. package/src/symbol/collision_feature.ts +7 -5
  138. package/src/symbol/collision_index.ts +1 -1
  139. package/src/symbol/get_anchors.test.ts +4 -4
  140. package/src/symbol/{mergelines.test.ts → merge_lines.test.ts} +1 -1
  141. package/src/symbol/{mergelines.ts → merge_lines.ts} +1 -1
  142. package/src/symbol/projection.ts +1 -1
  143. package/src/symbol/quads.test.ts +1 -1
  144. package/src/symbol/shaping.ts +10 -10
  145. package/src/symbol/symbol_layout.ts +5 -4
  146. package/src/symbol/symbol_style_layer.test.ts +1 -1
  147. package/src/symbol/transform_text.ts +3 -3
  148. package/src/ui/camera.test.ts +11 -11
  149. package/src/ui/control/geolocate_control.ts +1 -1
  150. package/src/ui/control/terrain_control.ts +4 -4
  151. package/src/ui/handler/cooperative_gestures.test.ts +167 -0
  152. package/src/ui/handler/drag_pan.test.ts +2 -1
  153. package/src/ui/handler/scroll_zoom.ts +7 -0
  154. package/src/ui/handler/touch_pan.ts +22 -2
  155. package/src/ui/handler/touch_zoom_rotate.ts +18 -1
  156. package/src/ui/handler_manager.ts +2 -2
  157. package/src/ui/map.test.ts +17 -17
  158. package/src/ui/map.ts +76 -8
  159. package/src/ui/map_events.test.ts +33 -32
  160. package/src/ui/popup.test.ts +2 -2
  161. package/src/util/ajax.test.ts +5 -5
  162. package/src/util/ajax.ts +1 -1
  163. package/src/util/classify_rings.test.ts +27 -27
  164. package/src/util/find_pole_of_inaccessibility.ts +1 -1
  165. package/src/util/primitives.ts +4 -4
  166. package/src/util/resolve_tokens.test.ts +1 -1
  167. package/src/util/smart_wrap.ts +1 -1
  168. package/src/util/tile_request_cache.test.ts +5 -5
  169. package/src/util/util.test.ts +5 -5
@@ -116,47 +116,6 @@ class Match implements Expression {
116
116
  outputDefined(): boolean {
117
117
  return this.outputs.every(out => out.outputDefined()) && this.otherwise.outputDefined();
118
118
  }
119
-
120
- serialize(): Array<unknown> {
121
- const serialized = ['match', this.input.serialize()];
122
-
123
- // Sort so serialization has an arbitrary defined order, even though
124
- // branch order doesn't affect evaluation
125
- const sortedLabels = Object.keys(this.cases).sort();
126
-
127
- // Group branches by unique match expression to support condensed
128
- // serializations of the form [case1, case2, ...] -> matchExpression
129
- const groupedByOutput: Array<[number, Array<number | string>]> = [];
130
- const outputLookup: {
131
- [index: number]: number;
132
- } = {}; // lookup index into groupedByOutput for a given output expression
133
- for (const label of sortedLabels) {
134
- const outputIndex = outputLookup[this.cases[label]];
135
- if (outputIndex === undefined) {
136
- // First time seeing this output, add it to the end of the grouped list
137
- outputLookup[this.cases[label]] = groupedByOutput.length;
138
- groupedByOutput.push([this.cases[label], [label]]);
139
- } else {
140
- // We've seen this expression before, add the label to that output's group
141
- groupedByOutput[outputIndex][1].push(label);
142
- }
143
- }
144
-
145
- const coerceLabel = (label) => this.inputType.kind === 'number' ? Number(label) : label;
146
-
147
- for (const [outputIndex, labels] of groupedByOutput) {
148
- if (labels.length === 1) {
149
- // Only a single label matches this output expression
150
- serialized.push(coerceLabel(labels[0]));
151
- } else {
152
- // Array of literal labels pointing to this output expression
153
- serialized.push(labels.map(coerceLabel));
154
- }
155
- serialized.push(this.outputs[outputIndex].serialize());
156
- }
157
- serialized.push(this.otherwise.serialize());
158
- return serialized;
159
- }
160
119
  }
161
120
 
162
121
  export default Match;
@@ -112,21 +112,4 @@ export default class NumberFormat implements Expression {
112
112
  outputDefined() {
113
113
  return false;
114
114
  }
115
-
116
- serialize() {
117
- const options = {};
118
- if (this.locale) {
119
- options['locale'] = this.locale.serialize();
120
- }
121
- if (this.currency) {
122
- options['currency'] = this.currency.serialize();
123
- }
124
- if (this.minFractionDigits) {
125
- options['min-fraction-digits'] = this.minFractionDigits.serialize();
126
- }
127
- if (this.maxFractionDigits) {
128
- options['max-fraction-digits'] = this.maxFractionDigits.serialize();
129
- }
130
- return ['number-format', this.number.serialize(), options];
131
- }
132
115
  }
@@ -79,14 +79,6 @@ class Slice implements Expression {
79
79
  outputDefined() {
80
80
  return false;
81
81
  }
82
-
83
- serialize() {
84
- if (this.endIndex != null && this.endIndex !== undefined) {
85
- const endIndex = this.endIndex.serialize();
86
- return ['slice', this.input.serialize(), this.beginIndex.serialize(), endIndex];
87
- }
88
- return ['slice', this.input.serialize(), this.beginIndex.serialize()];
89
- }
90
82
  }
91
83
 
92
84
  export default Slice;
@@ -102,17 +102,6 @@ class Step implements Expression {
102
102
  outputDefined(): boolean {
103
103
  return this.outputs.every(out => out.outputDefined());
104
104
  }
105
-
106
- serialize() {
107
- const serialized = ['step', this.input.serialize()];
108
- for (let i = 0; i < this.labels.length; i++) {
109
- if (i > 0) {
110
- serialized.push(this.labels[i]);
111
- }
112
- serialized.push(this.outputs[i].serialize());
113
- }
114
- return serialized;
115
- }
116
105
  }
117
106
 
118
107
  export default Step;
@@ -35,10 +35,6 @@ class Var implements Expression {
35
35
  outputDefined() {
36
36
  return false;
37
37
  }
38
-
39
- serialize() {
40
- return ['var', this.name];
41
- }
42
38
  }
43
39
 
44
40
  export default Var;
@@ -329,11 +329,6 @@ class Within implements Expression {
329
329
  outputDefined(): boolean {
330
330
  return true;
331
331
  }
332
-
333
- serialize(): Array<unknown> {
334
- return ['within', this.geojson];
335
- }
336
-
337
332
  }
338
333
 
339
334
  export default Within;
@@ -40,7 +40,7 @@ describe('createPropertyExpression', () => {
40
40
 
41
41
  describe('evaluate expression', () => {
42
42
  test('warns and falls back to default for invalid enum values', () => {
43
- const {value} = createPropertyExpression([ 'get', 'x' ], {
43
+ const {value} = createPropertyExpression(['get', 'x'], {
44
44
  type: 'enum',
45
45
  values: {a: {}, b: {}, c: {}},
46
46
  default: 'a',
@@ -2,8 +2,9 @@ import type {Type} from './types';
2
2
  import type ParsingContext from './parsing_context';
3
3
  import type EvaluationContext from './evaluation_context';
4
4
 
5
- type SerializedExpression = Array<unknown> | string | number | boolean | null;
6
-
5
+ /**
6
+ * Expression
7
+ */
7
8
  export interface Expression {
8
9
  readonly type: Type;
9
10
  evaluate(ctx: EvaluationContext): any;
@@ -13,7 +14,6 @@ export interface Expression {
13
14
  * false if the complete set of outputs is statically undecidable, otherwise true.
14
15
  */
15
16
  outputDefined(): boolean;
16
- serialize(): SerializedExpression;
17
17
  }
18
18
 
19
19
  export type ExpressionParser = (args: ReadonlyArray<unknown>, context: ParsingContext) => Expression;
@@ -379,8 +379,10 @@ export function normalizePropertyExpression<T>(
379
379
 
380
380
  } else {
381
381
  let constant: any = value;
382
- if (typeof value === 'string' && specification.type === 'color') {
382
+ if (specification.type === 'color' && typeof value === 'string') {
383
383
  constant = Color.parse(value);
384
+ } else if (specification.type === 'padding' && (typeof value === 'number' || Array.isArray(value))) {
385
+ constant = Padding.parse(value as (number | number[]));
384
386
  }
385
387
  return {
386
388
  kind: 'constant',
@@ -430,7 +432,8 @@ function findZoomCurve(expression: Expression): Step | Interpolate | ParsingErro
430
432
  return result;
431
433
  }
432
434
 
433
- import {ColorType, StringType, NumberType, BooleanType, ValueType, FormattedType, ResolvedImageType, array} from './types';
435
+ import {ColorType, StringType, NumberType, BooleanType, ValueType, FormattedType, PaddingType, ResolvedImageType, array} from './types';
436
+ import Padding from '../util/padding';
434
437
 
435
438
  function getExpectedType(spec: StylePropertySpecification): Type {
436
439
  const types = {
@@ -440,6 +443,7 @@ function getExpectedType(spec: StylePropertySpecification): Type {
440
443
  enum: StringType,
441
444
  boolean: BooleanType,
442
445
  formatted: FormattedType,
446
+ padding: PaddingType,
443
447
  resolvedImage: ResolvedImageType
444
448
  };
445
449
 
@@ -458,6 +462,8 @@ function getDefaultValue(spec: StylePropertySpecification): Value {
458
462
  return new Color(0, 0, 0, 0);
459
463
  } else if (spec.type === 'color') {
460
464
  return Color.parse(spec.default) || null;
465
+ } else if (spec.type === 'padding') {
466
+ return Padding.parse(spec.default) || null;
461
467
  } else if (spec.default === undefined) {
462
468
  return null;
463
469
  } else {
@@ -120,6 +120,8 @@ class ParsingContext {
120
120
  parsed = annotate(parsed, expected, options.typeAnnotation || 'assert');
121
121
  } else if ((expected.kind === 'color' || expected.kind === 'formatted' || expected.kind === 'resolvedImage') && (actual.kind === 'value' || actual.kind === 'string')) {
122
122
  parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
123
+ } else if (expected.kind === 'padding' && (actual.kind === 'value' || actual.kind === 'number' || actual.kind === 'array')) {
124
+ parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
123
125
  } else if (this.checkSubtype(expected, actual)) {
124
126
  return null;
125
127
  }
@@ -46,27 +46,4 @@ export default class Formatted {
46
46
  if (this.sections.length === 0) return '';
47
47
  return this.sections.map(section => section.text).join('');
48
48
  }
49
-
50
- serialize(): Array<unknown> {
51
- const serialized: Array<unknown> = ['format'];
52
- for (const section of this.sections) {
53
- if (section.image) {
54
- serialized.push(['image', section.image.name]);
55
- continue;
56
- }
57
- serialized.push(section.text);
58
- const options: {[key: string]: unknown} = {};
59
- if (section.fontStack) {
60
- options['text-font'] = ['literal', section.fontStack.split(',')];
61
- }
62
- if (section.scale) {
63
- options['font-scale'] = section.scale;
64
- }
65
- if (section.textColor) {
66
- options['text-color'] = (['rgba'] as Array<unknown>).concat(section.textColor.toArray());
67
- }
68
- serialized.push(options);
69
- }
70
- return serialized;
71
- }
72
49
  }
@@ -20,8 +20,4 @@ export default class ResolvedImage {
20
20
  if (!name) return null; // treat empty values as no image
21
21
  return new ResolvedImage({name, available: false});
22
22
  }
23
-
24
- serialize(): Array<string> {
25
- return ['image', this.name];
26
- }
27
23
  }
@@ -28,6 +28,9 @@ export type CollatorTypeT = {
28
28
  export type FormattedTypeT = {
29
29
  kind: 'formatted';
30
30
  };
31
+ export type PaddingTypeT = {
32
+ kind: 'padding';
33
+ };
31
34
  export type ResolvedImageTypeT = {
32
35
  kind: 'resolvedImage';
33
36
  };
@@ -35,7 +38,7 @@ export type ResolvedImageTypeT = {
35
38
  export type EvaluationKind = 'constant' | 'source' | 'camera' | 'composite';
36
39
 
37
40
  export type Type = NullTypeT | NumberTypeT | StringTypeT | BooleanTypeT | ColorTypeT | ObjectTypeT | ValueTypeT | // eslint-disable-line no-use-before-define
38
- ArrayType | ErrorTypeT | CollatorTypeT | FormattedTypeT | ResolvedImageTypeT;
41
+ ArrayType | ErrorTypeT | CollatorTypeT | FormattedTypeT | PaddingTypeT | ResolvedImageTypeT;
39
42
 
40
43
  export type ArrayType = {
41
44
  kind: 'array';
@@ -55,6 +58,7 @@ export const ValueType = {kind: 'value'} as ValueTypeT;
55
58
  export const ErrorType = {kind: 'error'} as ErrorTypeT;
56
59
  export const CollatorType = {kind: 'collator'} as CollatorTypeT;
57
60
  export const FormattedType = {kind: 'formatted'} as FormattedTypeT;
61
+ export const PaddingType = {kind: 'padding'} as PaddingTypeT;
58
62
  export const ResolvedImageType = {kind: 'resolvedImage'} as ResolvedImageTypeT;
59
63
 
60
64
  export function array(itemType: Type, N?: number | null): ArrayType {
@@ -85,6 +89,7 @@ const valueMemberTypes = [
85
89
  FormattedType,
86
90
  ObjectType,
87
91
  array(ValueType),
92
+ PaddingType,
88
93
  ResolvedImageType
89
94
  ];
90
95
 
@@ -3,8 +3,9 @@ import assert from 'assert';
3
3
  import Color from '../util/color';
4
4
  import Collator from './types/collator';
5
5
  import Formatted from './types/formatted';
6
+ import Padding from '../util/padding';
6
7
  import ResolvedImage from './types/resolved_image';
7
- import {NullType, NumberType, StringType, BooleanType, ColorType, ObjectType, ValueType, CollatorType, FormattedType, ResolvedImageType, array} from './types';
8
+ import {NullType, NumberType, StringType, BooleanType, ColorType, ObjectType, ValueType, CollatorType, FormattedType, ResolvedImageType, array, PaddingType} from './types';
8
9
 
9
10
  import type {Type} from './types';
10
11
 
@@ -27,7 +28,7 @@ export function validateRGBA(r: unknown, g: unknown, b: unknown, a?: unknown): s
27
28
  return null;
28
29
  }
29
30
 
30
- export type Value = null | string | boolean | number | Color | Collator | Formatted | ResolvedImage | ReadonlyArray<Value> | {
31
+ export type Value = null | string | boolean | number | Color | Collator | Formatted | Padding | ResolvedImage | ReadonlyArray<Value> | {
31
32
  readonly [x: string]: Value;
32
33
  };
33
34
 
@@ -46,6 +47,8 @@ export function isValue(mixed: unknown): boolean {
46
47
  return true;
47
48
  } else if (mixed instanceof Formatted) {
48
49
  return true;
50
+ } else if (mixed instanceof Padding) {
51
+ return true;
49
52
  } else if (mixed instanceof ResolvedImage) {
50
53
  return true;
51
54
  } else if (Array.isArray(mixed)) {
@@ -82,6 +85,8 @@ export function typeOf(value: Value): Type {
82
85
  return CollatorType;
83
86
  } else if (value instanceof Formatted) {
84
87
  return FormattedType;
88
+ } else if (value instanceof Padding) {
89
+ return PaddingType;
85
90
  } else if (value instanceof ResolvedImage) {
86
91
  return ResolvedImageType;
87
92
  } else if (Array.isArray(value)) {
@@ -113,11 +118,11 @@ export function toString(value: Value) {
113
118
  return '';
114
119
  } else if (type === 'string' || type === 'number' || type === 'boolean') {
115
120
  return String(value);
116
- } else if (value instanceof Color || value instanceof Formatted || value instanceof ResolvedImage) {
121
+ } else if (value instanceof Color || value instanceof Formatted || value instanceof Padding || value instanceof ResolvedImage) {
117
122
  return value.toString();
118
123
  } else {
119
124
  return JSON.stringify(value);
120
125
  }
121
126
  }
122
127
 
123
- export {Color, Collator};
128
+ export {Color, Collator, Padding};
@@ -1,17 +1,8 @@
1
1
  import {isExpressionFilter} from './index';
2
2
 
3
- import type {FilterSpecification} from '../types.g';
3
+ import type {ExpressionFilterSpecification, ExpressionInputType, ExpressionSpecification, FilterSpecification, LegacyFilterSpecification} from '../types.g';
4
4
 
5
- type ExpectedTypes = {[_: string]: 'string' | 'number' | 'boolean'};
6
-
7
- /**
8
- * Convert the given legacy filter to (the JSON representation of) an
9
- * equivalent expression
10
- * @private
11
- */
12
- export default function convertFilter(filter: FilterSpecification): unknown {
13
- return _convertFilter(filter, {});
14
- }
5
+ type ExpectedTypes = {[_: string]: ExpressionInputType};
15
6
 
16
7
  /*
17
8
  * Convert the given filter to an expression, storing the expected types for
@@ -61,51 +52,58 @@ export default function convertFilter(filter: FilterSpecification): unknown {
61
52
  * false (legacy filter semantics) are equivalent: they cause the filter to
62
53
  * produce a `false` result.
63
54
  */
64
- function _convertFilter(filter: FilterSpecification, expectedTypes: ExpectedTypes): unknown {
65
- if (isExpressionFilter(filter)) { return filter; }
66
-
55
+ export default function convertFilter(filter: FilterSpecification, expectedTypes: ExpectedTypes = {}): ExpressionFilterSpecification {
56
+ if (isExpressionFilter(filter)) return filter;
67
57
  if (!filter) return true;
68
- const op = filter[0];
69
- if (filter.length <= 1) return (op !== 'any');
70
-
71
- let converted;
72
-
73
- if (
74
- op === '==' ||
75
- op === '!=' ||
76
- op === '<' ||
77
- op === '>' ||
78
- op === '<=' ||
79
- op === '>='
80
- ) {
81
- const [, property, value] = filter;
82
- converted = convertComparisonOp(property as string, value, op, expectedTypes);
83
- } else if (op === 'any') {
84
- const children = (filter).slice(1).map((f: FilterSpecification) => {
85
- const types = {};
86
- const child = _convertFilter(f, types);
87
- const typechecks = runtimeTypeChecks(types);
88
- return typechecks === true ? child : ['case', typechecks, child, false];
89
- });
90
- return ['any'].concat(children as string[]);
91
- } else if (op === 'all') {
92
- const children = (filter).slice(1).map(f => _convertFilter(f as FilterSpecification, expectedTypes));
93
- return children.length > 1 ? ['all'].concat(children as string[]) : [].concat(...children);
94
- } else if (op === 'none') {
95
- return ['!', _convertFilter(['any'].concat(filter.slice(1) as string[]) as string[], {})];
96
- } else if (op === 'in') {
97
- converted = convertInOp(filter[1] as string, filter.slice(2));
98
- } else if (op === '!in') {
99
- converted = convertInOp(filter[1] as string, filter.slice(2), true);
100
- } else if (op === 'has') {
101
- converted = convertHasOp(filter[1] as string);
102
- } else if (op === '!has') {
103
- converted = ['!', convertHasOp(filter[1] as string)];
104
- } else {
105
- converted = true;
106
- }
107
58
 
108
- return converted;
59
+ const legacyFilter = filter as LegacyFilterSpecification;
60
+ const legacyOp = legacyFilter[0];
61
+ if (filter.length <= 1) return (legacyOp !== 'any');
62
+
63
+ switch (legacyOp) {
64
+ case '==':
65
+ case '!=':
66
+ case '<':
67
+ case '>':
68
+ case '<=':
69
+ case '>=': {
70
+ const [, property, value] = filter;
71
+ return convertComparisonOp(property as string, value, legacyOp, expectedTypes);
72
+ }
73
+ case 'any': {
74
+ const [, ...conditions] = legacyFilter;
75
+ const children = conditions.map((f: LegacyFilterSpecification) => {
76
+ const types = {};
77
+ const child = convertFilter(f, types);
78
+ const typechecks = runtimeTypeChecks(types);
79
+ return typechecks === true ? child : ['case', typechecks, child, false] as ExpressionSpecification;
80
+ });
81
+ return ['any', ...children];
82
+ }
83
+ case 'all': {
84
+ const [, ...conditions] = legacyFilter;
85
+ const children = conditions.map(f => convertFilter(f, expectedTypes));
86
+ return children.length > 1 ? ['all', ...children] : children[0];
87
+ }
88
+ case 'none': {
89
+ const [, ...conditions] = legacyFilter;
90
+ return ['!', convertFilter(['any', ...conditions], {})];
91
+ }
92
+ case 'in': {
93
+ const [, property, ...values] = legacyFilter;
94
+ return convertInOp(property, values);
95
+ }
96
+ case '!in': {
97
+ const [, property, ...values] = legacyFilter;
98
+ return convertInOp(property, values, true);
99
+ }
100
+ case 'has':
101
+ return convertHasOp(legacyFilter[1]);
102
+ case '!has':
103
+ return ['!', convertHasOp(legacyFilter[1])];
104
+ default:
105
+ return true;
106
+ }
109
107
  }
110
108
 
111
109
  // Given a set of feature properties and an expected type for each one,
@@ -116,7 +114,7 @@ function _convertFilter(filter: FilterSpecification, expectedTypes: ExpectedType
116
114
  // ['==', ['typeof', ['get', 'name'], 'string']],
117
115
  // ['==', ['typeof', ['get', 'population'], 'number]]
118
116
  // ]
119
- function runtimeTypeChecks(expectedTypes: ExpectedTypes) {
117
+ function runtimeTypeChecks(expectedTypes: ExpectedTypes): ExpressionFilterSpecification {
120
118
  const conditions = [];
121
119
  for (const property in expectedTypes) {
122
120
  const get = property === '$id' ? ['id'] : ['get', property];
@@ -124,13 +122,13 @@ function runtimeTypeChecks(expectedTypes: ExpectedTypes) {
124
122
  }
125
123
  if (conditions.length === 0) return true;
126
124
  if (conditions.length === 1) return conditions[0];
127
- return ['all'].concat(conditions);
125
+ return ['all', ...conditions];
128
126
  }
129
127
 
130
- function convertComparisonOp(property: string, value: any, op: string, expectedTypes?: ExpectedTypes | null) {
128
+ function convertComparisonOp(property: string, value: any, op: string, expectedTypes?: ExpectedTypes | null): ExpressionFilterSpecification {
131
129
  let get;
132
130
  if (property === '$type') {
133
- return [op, ['geometry-type'], value];
131
+ return [op, ['geometry-type'], value] as ExpressionFilterSpecification;
134
132
  } else if (property === '$id') {
135
133
  get = ['id'];
136
134
  } else {
@@ -156,13 +154,13 @@ function convertComparisonOp(property: string, value: any, op: string, expectedT
156
154
  ];
157
155
  }
158
156
 
159
- return [op, get, value];
157
+ return [op, get, value] as ExpressionFilterSpecification;
160
158
  }
161
159
 
162
- function convertInOp(property: string, values: Array<any>, negate = false) {
160
+ function convertInOp(property: string, values: Array<any>, negate = false): ExpressionFilterSpecification {
163
161
  if (values.length === 0) return negate;
164
162
 
165
- let get;
163
+ let get: ExpressionSpecification;
166
164
  if (property === '$type') {
167
165
  get = ['geometry-type'];
168
166
  } else if (property === '$id') {
@@ -190,12 +188,14 @@ function convertInOp(property: string, values: Array<any>, negate = false) {
190
188
  return ['match', get, uniqueValues, !negate, negate];
191
189
  }
192
190
 
193
- return [ negate ? 'all' : 'any' as any].concat(
194
- values.map(v => [negate ? '!=' : '==', get, v])
195
- );
191
+ if (negate) {
192
+ return ['all', ...values.map(v => ['!=', get, v] as ExpressionSpecification)];
193
+ } else {
194
+ return ['any', ...values.map(v => ['==', get, v] as ExpressionSpecification)];
195
+ }
196
196
  }
197
197
 
198
- function convertHasOp(property: string) {
198
+ function convertHasOp(property: string): ExpressionFilterSpecification {
199
199
  if (property === '$type') {
200
200
  return true;
201
201
  } else if (property === '$id') {
@@ -5,10 +5,39 @@ import Point from '@mapbox/point-geometry';
5
5
  import MercatorCoordinate from '../../geo/mercator_coordinate';
6
6
  import EXTENT from '../../data/extent';
7
7
  import {CanonicalTileID} from '../../source/tile_id';
8
- import {FilterSpecification} from '../types.g';
8
+ import {ExpressionFilterSpecification, FilterSpecification} from '../types.g';
9
9
  import {Feature} from '../expression';
10
10
 
11
11
  describe('filter', () => {
12
+ test('exprssions transpilation test', () => {
13
+ function compileTimeCheck(_: ExpressionFilterSpecification) {
14
+ expect(true).toBeTruthy();
15
+ }
16
+ compileTimeCheck(['any']);
17
+ compileTimeCheck(['at', 2, ['array', 1, 2, 3]]);
18
+ compileTimeCheck(['case', ['has', 'color'], ['get', 'color'], 'white']);
19
+ compileTimeCheck(['case', ['all', ['has', 'point_count'], ['<', ['get', 'point_count'], 3]], ['get', 'cluster_routes'], '']);
20
+ compileTimeCheck(['interpolate', ['linear'], ['get', 'point_count'], 2, 18.0, 10, 24.0]);
21
+ compileTimeCheck(['case', ['has', 'point_count'], ['interpolate', ['linear'], ['get', 'point_count'], 2, 18.0, 10, 24.0], 12.0]);
22
+ compileTimeCheck([
23
+ 'case',
24
+ ['has', 'point_count'], ['interpolate', ['linear'], ['get', 'point_count'], 2, '#ccc', 10, '#444'],
25
+ ['has', 'priorityValue'], ['interpolate', ['linear'], ['get', 'priorityValue'], 0, '#ff9', 1, '#f66'],
26
+ '#fcaf3e'
27
+ ]);
28
+ compileTimeCheck([
29
+ 'case',
30
+ ['==', ['get', 'CAPITAL'], 1], 'city-capital',
31
+ ['>=', ['get', 'POPULATION'], 1000000], 'city-1M',
32
+ ['>=', ['get', 'POPULATION'], 500000], 'city-500k',
33
+ ['>=', ['get', 'POPULATION'], 100000], 'city-100k',
34
+ 'city'
35
+ ]);
36
+ compileTimeCheck(['match', ['get', 'TYPE'], ['TARGETPOINT:HOSPITAL'], true, false]);
37
+ compileTimeCheck(['match', ['get', 'TYPE'], ['ADIZ', 'AMA', 'AWY', 'CLASS', 'NO-FIR', 'OCA', 'OTA', 'P', 'RAS', 'RCA', 'UTA', 'UTA-P'], true, false]);
38
+ compileTimeCheck(['==', ['get', 'MILITARYAIRPORT'], 1]);
39
+ });
40
+
12
41
  test('expression, zoom', () => {
13
42
  const f = createFilter(['>=', ['number', ['get', 'x']], ['zoom']]).filter;
14
43
  expect(f({zoom: 1}, {properties: {x: 0}} as any as Feature)).toBe(false);
@@ -52,6 +81,18 @@ describe('filter', () => {
52
81
  expect(createFilter(['any', false, false]).filter(undefined, undefined)).toBe(false);
53
82
  });
54
83
 
84
+ test('expression, literal', () => {
85
+ expect(createFilter(['literal', true]).filter(undefined, undefined)).toBe(true);
86
+ expect(createFilter(['literal', false]).filter(undefined, undefined)).toBe(false);
87
+ });
88
+
89
+ test('expression, match', () => {
90
+ const match = createFilter(['match', ['get', 'x'], ['a', 'b', 'c'], true, false]).filter;
91
+ expect(match(undefined, {properties: {x: 'a'}} as any as Feature)).toBe(true);
92
+ expect(match(undefined, {properties: {x: 'c'}} as any as Feature)).toBe(true);
93
+ expect(match(undefined, {properties: {x: 'd'}} as any as Feature)).toBe(false);
94
+ });
95
+
55
96
  test('expression, type error', () => {
56
97
  expect(() => {
57
98
  createFilter(['==', ['number', ['get', 'x']], ['string', ['get', 'y']]]);
@@ -77,7 +118,7 @@ describe('filter', () => {
77
118
  };
78
119
  const withinFilter = createFilter(['within', {'type': 'Polygon', 'coordinates': [[[0, 0], [5, 0], [5, 5], [0, 5], [0, 0]]]}]);
79
120
  expect(withinFilter.needGeometry).toBe(true);
80
- const canonical = {z: 3, x: 3, y:3} as CanonicalTileID;
121
+ const canonical = {z: 3, x: 3, y: 3} as CanonicalTileID;
81
122
  expect(
82
123
  withinFilter.filter({zoom: 3}, {type: 1, geometry: [[getPointFromLngLat(2, 2, canonical)]]} as Feature, canonical)
83
124
  ).toBe(true);
@@ -179,14 +220,14 @@ describe('convert legacy filters to expressions', () => {
179
220
  ['LineString', 'Point', 'Polygon'],
180
221
  true,
181
222
  false
182
- ] as FilterSpecification,
223
+ ],
183
224
  [
184
225
  'match',
185
226
  ['get', 'type'],
186
227
  ['island'],
187
228
  true,
188
229
  false
189
- ] as FilterSpecification
230
+ ]
190
231
  ];
191
232
 
192
233
  const converted = convertFilter(filter);
@@ -2,6 +2,7 @@ import {createExpression} from '../expression';
2
2
  import type {GlobalProperties, Feature} from '../expression';
3
3
  import type {CanonicalTileID} from '../../source/tile_id';
4
4
  import {StylePropertySpecification} from '../style-spec';
5
+ import {ExpressionFilterSpecification} from '../types.g';
5
6
 
6
7
  type FilterExpression = (
7
8
  globalProperties: GlobalProperties,
@@ -17,7 +18,7 @@ export type FeatureFilter = {
17
18
  export default createFilter;
18
19
  export {isExpressionFilter};
19
20
 
20
- function isExpressionFilter(filter: any) {
21
+ function isExpressionFilter(filter: any): filter is ExpressionFilterSpecification {
21
22
  if (filter === true || filter === false) {
22
23
  return true;
23
24
  }