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.
- package/README.md +73 -8
- package/build/generate-debug-index-file.ts +19 -0
- package/build/generate-style-code.ts +6 -1
- package/build/generate-style-spec.ts +151 -35
- package/build/rollup_plugins.ts +4 -1
- package/dist/maplibre-gl-csp-worker.js +1 -1
- package/dist/maplibre-gl-csp-worker.js.map +1 -1
- package/dist/maplibre-gl-csp.js +1 -1
- package/dist/maplibre-gl-csp.js.map +1 -1
- package/dist/maplibre-gl-dev.js +1317 -4304
- package/dist/maplibre-gl.css +1 -1
- package/dist/maplibre-gl.d.ts +443 -145
- package/dist/maplibre-gl.js +4 -4
- package/dist/maplibre-gl.js.map +1 -1
- package/package.json +70 -67
- package/src/css/maplibre-gl.css +48 -32
- package/src/data/bucket/fill_bucket.test.ts +1 -0
- package/src/data/bucket/symbol_bucket.test.ts +2 -0
- package/src/data/bucket/symbol_bucket.ts +1 -1
- package/src/data/evaluation_feature.ts +1 -1
- package/src/data/program_configuration.ts +2 -2
- package/src/geo/transform.test.ts +34 -1
- package/src/geo/transform.ts +25 -15
- package/src/gl/vertex_buffer.ts +4 -4
- package/src/index.ts +1 -1
- package/src/render/draw_debug.ts +1 -1
- package/src/render/draw_symbol.test.ts +2 -23
- package/src/render/draw_terrain.ts +1 -1
- package/src/render/image_atlas.ts +1 -0
- package/src/render/image_manager.ts +1 -0
- package/src/render/program/debug_program.ts +1 -1
- package/src/render/render_to_texture.ts +3 -0
- package/src/render/terrain.test.ts +119 -17
- package/src/render/terrain.ts +39 -21
- package/src/shaders/README.md +2 -2
- package/src/shaders/shaders.ts +3 -1
- package/src/source/geojson_worker_source.test.ts +2 -2
- package/src/source/geojson_wrapper.test.ts +1 -1
- package/src/source/image_source.test.ts +8 -8
- package/src/source/image_source.ts +1 -1
- package/src/source/load_tilejson.ts +6 -1
- package/src/source/pixels_to_tile_units.ts +1 -1
- package/src/source/raster_tile_source.test.ts +1 -1
- package/src/source/source_cache.test.ts +12 -12
- package/src/source/source_cache.ts +1 -1
- package/src/source/terrain_source_cache.test.ts +17 -2
- package/src/source/terrain_source_cache.ts +16 -12
- package/src/source/vector_tile_source.test.ts +1 -1
- package/src/source/vector_tile_worker_source.test.ts +1 -1
- package/src/source/video_source.test.ts +2 -2
- package/src/style/light.test.ts +1 -1
- package/src/style/load_sprite.ts +1 -1
- package/src/style/parse_glyph_pbf.ts +1 -1
- package/src/style/style.test.ts +3 -3
- package/src/style/style.ts +2 -2
- package/src/style/style_layer/background_style_layer_properties.g.ts +1 -0
- package/src/style/style_layer/circle_style_layer_properties.g.ts +1 -0
- package/src/style/style_layer/fill_extrusion_style_layer_properties.g.ts +1 -0
- package/src/style/style_layer/fill_style_layer_properties.g.ts +1 -0
- package/src/style/style_layer/heatmap_style_layer_properties.g.ts +1 -0
- package/src/style/style_layer/hillshade_style_layer_properties.g.ts +1 -0
- package/src/style/style_layer/line_style_layer_properties.g.ts +1 -0
- package/src/style/style_layer/raster_style_layer_properties.g.ts +1 -0
- package/src/style/style_layer/symbol_style_layer.ts +16 -1
- package/src/style/style_layer/symbol_style_layer_properties.g.ts +4 -3
- package/src/style-spec/CHANGELOG.md +5 -0
- package/src/style-spec/composite.test.ts +2 -0
- package/src/style-spec/composite.ts +3 -2
- package/src/style-spec/diff.test.ts +3 -3
- package/src/style-spec/empty.ts +3 -2
- package/src/style-spec/expression/compound_expression.ts +0 -4
- package/src/style-spec/expression/definitions/assertion.ts +0 -18
- package/src/style-spec/expression/definitions/at.ts +0 -4
- package/src/style-spec/expression/definitions/case.ts +0 -6
- package/src/style-spec/expression/definitions/coalesce.ts +0 -6
- package/src/style-spec/expression/definitions/coercion.ts +13 -18
- package/src/style-spec/expression/definitions/collator.ts +0 -10
- package/src/style-spec/expression/definitions/comparison.ts +0 -6
- package/src/style-spec/expression/definitions/format.ts +0 -19
- package/src/style-spec/expression/definitions/image.ts +0 -4
- package/src/style-spec/expression/definitions/in.ts +0 -4
- package/src/style-spec/expression/definitions/index_of.ts +0 -8
- package/src/style-spec/expression/definitions/interpolate.ts +1 -25
- package/src/style-spec/expression/definitions/length.ts +0 -6
- package/src/style-spec/expression/definitions/let.ts +0 -9
- package/src/style-spec/expression/definitions/literal.ts +1 -23
- package/src/style-spec/expression/definitions/match.ts +0 -41
- package/src/style-spec/expression/definitions/number_format.ts +0 -17
- package/src/style-spec/expression/definitions/slice.ts +0 -8
- package/src/style-spec/expression/definitions/step.ts +0 -11
- package/src/style-spec/expression/definitions/var.ts +0 -4
- package/src/style-spec/expression/definitions/within.ts +0 -5
- package/src/style-spec/expression/expression.test.ts +1 -1
- package/src/style-spec/expression/expression.ts +3 -3
- package/src/style-spec/expression/index.ts +8 -2
- package/src/style-spec/expression/parsing_context.ts +2 -0
- package/src/style-spec/expression/types/formatted.ts +0 -23
- package/src/style-spec/expression/types/resolved_image.ts +0 -4
- package/src/style-spec/expression/types.ts +6 -1
- package/src/style-spec/expression/values.ts +9 -4
- package/src/style-spec/feature_filter/convert.ts +65 -65
- package/src/style-spec/feature_filter/feature_filter.test.ts +45 -4
- package/src/style-spec/feature_filter/index.ts +2 -1
- package/src/style-spec/function/index.test.ts +117 -1
- package/src/style-spec/function/index.ts +24 -12
- package/src/style-spec/migrate/expressions.ts +2 -2
- package/src/style-spec/migrate/v8.test.ts +2 -0
- package/src/style-spec/migrate/v8.ts +8 -7
- package/src/style-spec/migrate/v9.test.ts +6 -4
- package/src/style-spec/migrate/v9.ts +3 -2
- package/src/style-spec/migrate.test.ts +3 -1
- package/src/style-spec/migrate.ts +5 -4
- package/src/style-spec/package.json +1 -1
- package/src/style-spec/read_style.ts +2 -1
- package/src/style-spec/reference/latest.ts +1 -1
- package/src/style-spec/reference/v8.json +9 -6
- package/src/style-spec/style-spec.test.ts +2 -1
- package/src/style-spec/style-spec.ts +8 -0
- package/src/style-spec/types.g.ts +152 -36
- package/src/style-spec/util/extend.ts +1 -1
- package/src/style-spec/util/interpolate.test.ts +5 -0
- package/src/style-spec/util/interpolate.ts +12 -0
- package/src/style-spec/util/padding.test.ts +27 -0
- package/src/style-spec/util/padding.ts +64 -0
- package/src/style-spec/util/ref_properties.ts +2 -1
- package/src/style-spec/validate/validate.ts +3 -1
- package/src/style-spec/validate/validate_expression.ts +2 -1
- package/src/style-spec/validate/validate_function.ts +2 -2
- package/src/style-spec/validate/validate_glyphs_url.ts +1 -1
- package/src/style-spec/validate/validate_object.ts +2 -2
- package/src/style-spec/validate/validate_padding.test.ts +82 -0
- package/src/style-spec/validate/validate_padding.ts +36 -0
- package/src/style-spec/validate_style.min.ts +4 -3
- package/src/style-spec/validate_style.ts +4 -3
- package/src/symbol/check_max_angle.test.ts +5 -5
- package/src/symbol/collision_feature.test.ts +22 -5
- package/src/symbol/collision_feature.ts +7 -5
- package/src/symbol/collision_index.ts +1 -1
- package/src/symbol/get_anchors.test.ts +4 -4
- package/src/symbol/{mergelines.test.ts → merge_lines.test.ts} +1 -1
- package/src/symbol/{mergelines.ts → merge_lines.ts} +1 -1
- package/src/symbol/projection.ts +1 -1
- package/src/symbol/quads.test.ts +1 -1
- package/src/symbol/shaping.ts +10 -10
- package/src/symbol/symbol_layout.ts +5 -4
- package/src/symbol/symbol_style_layer.test.ts +1 -1
- package/src/symbol/transform_text.ts +3 -3
- package/src/ui/camera.test.ts +11 -11
- package/src/ui/control/geolocate_control.ts +1 -1
- package/src/ui/control/terrain_control.ts +4 -4
- package/src/ui/handler/cooperative_gestures.test.ts +167 -0
- package/src/ui/handler/drag_pan.test.ts +2 -1
- package/src/ui/handler/scroll_zoom.ts +7 -0
- package/src/ui/handler/touch_pan.ts +22 -2
- package/src/ui/handler/touch_zoom_rotate.ts +18 -1
- package/src/ui/handler_manager.ts +2 -2
- package/src/ui/map.test.ts +17 -17
- package/src/ui/map.ts +76 -8
- package/src/ui/map_events.test.ts +33 -32
- package/src/ui/popup.test.ts +2 -2
- package/src/util/ajax.test.ts +5 -5
- package/src/util/ajax.ts +1 -1
- package/src/util/classify_rings.test.ts +27 -27
- package/src/util/find_pole_of_inaccessibility.ts +1 -1
- package/src/util/primitives.ts +4 -4
- package/src/util/resolve_tokens.test.ts +1 -1
- package/src/util/smart_wrap.ts +1 -1
- package/src/util/tile_request_cache.test.ts +5 -5
- 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;
|
|
@@ -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([
|
|
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
|
-
|
|
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 (
|
|
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
|
}
|
|
@@ -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]:
|
|
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
|
|
65
|
-
if (isExpressionFilter(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
|
-
|
|
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']
|
|
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
|
-
|
|
194
|
-
values.map(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
|
-
]
|
|
223
|
+
],
|
|
183
224
|
[
|
|
184
225
|
'match',
|
|
185
226
|
['get', 'type'],
|
|
186
227
|
['island'],
|
|
187
228
|
true,
|
|
188
229
|
false
|
|
189
|
-
]
|
|
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
|
}
|