@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
package/src/index.js CHANGED
@@ -43,7 +43,7 @@ module.exports = {
43
43
  * @param {string} pluginURL URL pointing to the Mapbox RTL text plugin source.
44
44
  * @param {Function} callback Called with an error argument if there is an error.
45
45
  * @example
46
- * mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.1.2/mapbox-gl-rtl-text.js');
46
+ * mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.0/mapbox-gl-rtl-text.js');
47
47
  * @see [Add support for right-to-left scripts](https://www.mapbox.com/mapbox-gl-js/example/mapbox-gl-rtl-text/)
48
48
  */
49
49
 
@@ -1,14 +1,12 @@
1
- const ShelfPack = require('@mapbox/shelf-pack');
2
-
3
1
  const { AlphaImage } = require('../util/image');
4
2
  const { register } = require('../util/transfer_registry');
3
+ const potpack = require('potpack');
5
4
 
6
5
  const padding = 1;
7
6
 
8
7
  class GlyphAtlas {
9
8
  constructor(stacks) {
10
9
  const positions = {};
11
- const pack = new ShelfPack(0, 0, { autoResize: true });
12
10
  const bins = [];
13
11
 
14
12
  for (const stack in stacks) {
@@ -30,9 +28,8 @@ class GlyphAtlas {
30
28
  }
31
29
  }
32
30
 
33
- pack.pack(bins, { inPlace: true });
34
-
35
- const image = new AlphaImage({ width: pack.w, height: pack.h });
31
+ const { w, h } = potpack(bins);
32
+ const image = new AlphaImage({ width: w ?? 1, height: h ?? 1 });
36
33
 
37
34
  for (const stack in stacks) {
38
35
  const glyphs = stacks[stack];
@@ -1,7 +1,6 @@
1
- const ShelfPack = require('@mapbox/shelf-pack');
2
-
3
1
  const { RGBAImage } = require('../util/image');
4
2
  const { register } = require('../util/transfer_registry');
3
+ const potpack = require('potpack');
5
4
 
6
5
  const padding = 1;
7
6
 
@@ -32,7 +31,6 @@ class ImageAtlas {
32
31
  constructor(icons, patterns) {
33
32
  const iconPositions = {};
34
33
  const patternPositions = {};
35
- const pack = new ShelfPack(0, 0, { autoResize: true });
36
34
  const bins = [];
37
35
  for (const id in icons) {
38
36
  const src = icons[id];
@@ -58,9 +56,8 @@ class ImageAtlas {
58
56
  patternPositions[id] = new ImagePosition(bin, src);
59
57
  }
60
58
 
61
- pack.pack(bins, { inPlace: true });
62
-
63
- const image = new RGBAImage({ width: pack.w, height: pack.h });
59
+ const { w, h } = potpack(bins);
60
+ const image = new RGBAImage({ width: w || 1, height: h || 1 });
64
61
 
65
62
  for (const id in icons) {
66
63
  const src = icons[id];
@@ -1,4 +1,4 @@
1
- const ShelfPack = require('@mapbox/shelf-pack');
1
+ const potpack = require('potpack');
2
2
 
3
3
  const { RGBAImage } = require('../util/image');
4
4
  const { ImagePosition } = require('./image_atlas');
@@ -26,9 +26,8 @@ class ImageManager {
26
26
  constructor() {
27
27
  this.images = {};
28
28
 
29
- this.shelfPack = new ShelfPack(64, 64, { autoResize: true });
30
29
  this.patterns = {};
31
- this.atlasImage = new RGBAImage({ width: 64, height: 64 });
30
+ this.atlasImage = new RGBAImage({ width: 1, height: 1 });
32
31
  this.dirty = true;
33
32
  }
34
33
 
@@ -56,12 +55,7 @@ class ImageManager {
56
55
  removeImage(id) {
57
56
  assert(this.images[id]);
58
57
  delete this.images[id];
59
-
60
- const pattern = this.patterns[id];
61
- if (pattern) {
62
- this.shelfPack.unref(pattern.bin);
63
- delete this.patterns[id];
64
- }
58
+ delete this.patterns[id];
65
59
  }
66
60
 
67
61
  listImages() {
@@ -89,10 +83,8 @@ class ImageManager {
89
83
  // Pattern stuff
90
84
 
91
85
  getPixelSize() {
92
- return {
93
- width: this.shelfPack.w,
94
- height: this.shelfPack.h
95
- };
86
+ const { width, height } = this.atlasImage;
87
+ return { width, height };
96
88
  }
97
89
 
98
90
  getPattern(id) {
@@ -106,36 +98,13 @@ class ImageManager {
106
98
  return null;
107
99
  }
108
100
 
109
- const width = image.data.width + padding * 2;
110
- const height = image.data.height + padding * 2;
111
-
112
- const bin = this.shelfPack.packOne(width, height);
113
- if (!bin) {
114
- return null;
115
- }
116
-
117
- this.atlasImage.resize(this.getPixelSize());
118
-
119
- const src = image.data;
120
- const dst = this.atlasImage;
121
-
122
- const x = bin.x + padding;
123
- const y = bin.y + padding;
124
- const w = src.width;
125
- const h = src.height;
126
-
127
- RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x, y }, { width: w, height: h });
128
-
129
- // Add 1 pixel wrapped padding on each side of the image.
130
- RGBAImage.copy(src, dst, { x: 0, y: h - 1 }, { x: x, y: y - 1 }, { width: w, height: 1 }); // T
131
- RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x, y: y + h }, { width: w, height: 1 }); // B
132
- RGBAImage.copy(src, dst, { x: w - 1, y: 0 }, { x: x - 1, y: y }, { width: 1, height: h }); // L
133
- RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x + w, y: y }, { width: 1, height: h }); // R
134
-
135
- this.dirty = true;
136
-
101
+ const w = image.data.width + padding * 2;
102
+ const h = image.data.height + padding * 2;
103
+ const bin = { w, h, x: 0, y: 0 };
137
104
  const position = new ImagePosition(bin, image);
138
105
  this.patterns[id] = { bin, position };
106
+ this._updatePatternAtlas();
107
+
139
108
  return position;
140
109
  }
141
110
 
@@ -150,6 +119,37 @@ class ImageManager {
150
119
 
151
120
  this.atlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
152
121
  }
122
+
123
+ _updatePatternAtlas() {
124
+ const bins = [];
125
+ for (const id in this.patterns) {
126
+ bins.push(this.patterns[id].bin);
127
+ }
128
+
129
+ const { w, h } = potpack(bins);
130
+
131
+ const dst = this.atlasImage;
132
+ dst.resize({ width: w ?? 1, height: h ?? 1 });
133
+
134
+ for (const id in this.patterns) {
135
+ const { bin } = this.patterns[id];
136
+ const x = bin.x + padding;
137
+ const y = bin.y + padding;
138
+ const src = this.images[id].data;
139
+ const w = src.width;
140
+ const h = src.height;
141
+
142
+ RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x, y }, { width: w, height: h });
143
+
144
+ // Add 1 pixel wrapped padding on each side of the image.
145
+ RGBAImage.copy(src, dst, { x: 0, y: h - 1 }, { x: x, y: y - 1 }, { width: w, height: 1 }); // T
146
+ RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x, y: y + h }, { width: w, height: 1 }); // B
147
+ RGBAImage.copy(src, dst, { x: w - 1, y: 0 }, { x: x - 1, y: y }, { width: 1, height: h }); // L
148
+ RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x + w, y: y }, { width: 1, height: h }); // R
149
+ }
150
+
151
+ this.dirty = true;
152
+ }
153
153
  }
154
154
 
155
155
  module.exports = ImageManager;
@@ -47,6 +47,7 @@ function setRTLTextPlugin(url, callback) {
47
47
  const plugin = {
48
48
  applyArabicShaping: null,
49
49
  processBidirectionalText: null,
50
+ processStyledBidirectionalText: null,
50
51
  isLoaded: function () {
51
52
  return (
52
53
  foregroundLoadComplete || // Foreground: loaded if the completion callback returned successfully
@@ -181,7 +181,7 @@ class SourceCache extends Evented {
181
181
  this._cache.reset();
182
182
 
183
183
  for (const i in this._tiles) {
184
- this._reloadTile(i, 'reloading');
184
+ if (this._tiles[i].state !== 'errored') this._reloadTile(i, 'reloading');
185
185
  }
186
186
  }
187
187
 
@@ -101,8 +101,9 @@ class Tile {
101
101
  }
102
102
 
103
103
  this.queryPadding = 0;
104
- for (const bucket of buckets) {
105
- this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(bucket.layerIds[0]).queryRadius(bucket));
104
+ for (const id in this.buckets) {
105
+ const bucket = this.buckets[id];
106
+ this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(id).queryRadius(bucket));
106
107
  }
107
108
 
108
109
  if (data.imageAtlas) {
@@ -297,8 +298,8 @@ class Tile {
297
298
 
298
299
  const vtLayers = this.latestFeatureIndex.loadVTLayers();
299
300
 
300
- for (const i in this.buckets) {
301
- const bucket = this.buckets[i];
301
+ for (const id in this.buckets) {
302
+ const bucket = this.buckets[id];
302
303
  // Buckets are grouped by common source-layer
303
304
  const sourceLayerId = bucket.layers[0]['sourceLayer'] || '_geojsonTileLayer';
304
305
  const sourceLayer = vtLayers[sourceLayerId];
@@ -307,7 +308,7 @@ class Tile {
307
308
 
308
309
  bucket.update(sourceLayerStates, sourceLayer, this.imageAtlas?.patternPositions || {});
309
310
  if (painter?.style) {
310
- this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(bucket.layerIds[0]).queryRadius(bucket));
311
+ this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(id).queryRadius(bucket));
311
312
  }
312
313
  }
313
314
  }
@@ -38,6 +38,7 @@ class Worker {
38
38
  }
39
39
  globalRTLTextPlugin['applyArabicShaping'] = rtlTextPlugin.applyArabicShaping;
40
40
  globalRTLTextPlugin['processBidirectionalText'] = rtlTextPlugin.processBidirectionalText;
41
+ globalRTLTextPlugin['processStyledBidirectionalText'] = rtlTextPlugin.processStyledBidirectionalText;
41
42
  };
42
43
  }
43
44
 
@@ -71,16 +72,6 @@ class Worker {
71
72
  }
72
73
  }
73
74
 
74
- /**
75
- * Load a {@link WorkerSource} script at params.url. The script is run
76
- * (using importScripts) with `registerWorkerSource` in scope, which is a
77
- * function taking `(name, workerSourceObject)`.
78
- * @private
79
- */
80
- loadWorkerSource(map, params) {
81
- this.self.importScripts(params.url);
82
- }
83
-
84
75
  loadRTLTextPlugin(map, pluginURL) {
85
76
  if (!globalRTLTextPlugin.isLoaded()) {
86
77
  this.self.importScripts(pluginURL);
@@ -633,6 +633,10 @@ class Style extends Evented {
633
633
  this.fire(new ErrorEvent(new Error('The sourceLayer parameter must be provided for vector source types.')));
634
634
  return;
635
635
  }
636
+ if (feature.id == null || feature.id === '') {
637
+ this.fire(new ErrorEvent(new Error('The feature id parameter must be provided.')));
638
+ return;
639
+ }
636
640
 
637
641
  sourceCache.setFeatureState(sourceLayer, feature.id, state);
638
642
  }
@@ -767,25 +771,6 @@ class Style extends Evented {
767
771
  return sourceCache ? querySourceFeatures(sourceCache, params) : [];
768
772
  }
769
773
 
770
- addSourceType(name, SourceType, callback) {
771
- if (Style.getSourceType(name)) {
772
- return callback(new Error(`A source type called "${name}" already exists.`));
773
- }
774
-
775
- Style.setSourceType(name, SourceType);
776
-
777
- if (!SourceType.workerSourceURL) {
778
- return callback(null, null);
779
- }
780
-
781
- this.dispatcher
782
- .broadcast('loadWorkerSource', {
783
- name: name,
784
- url: SourceType.workerSourceURL
785
- })
786
- .then(data => callback(null, data), callback);
787
- }
788
-
789
774
  getLight() {
790
775
  return this.light.getLight();
791
776
  }
@@ -19,6 +19,12 @@ const layout = new Properties({
19
19
  default: false,
20
20
  expression: { parameters: ['zoom'] }
21
21
  }),
22
+ 'symbol-z-order': new DataConstantProperty({
23
+ type: 'enum',
24
+ values: ['viewport-y', 'source'],
25
+ default: 'viewport-y',
26
+ expression: { parameters: ['zoom'] }
27
+ }),
22
28
  'icon-allow-overlap': new DataConstantProperty({
23
29
  type: 'boolean',
24
30
  default: false,
@@ -106,7 +112,7 @@ const layout = new Properties({
106
112
  expression: { parameters: ['zoom'] }
107
113
  }),
108
114
  'text-field': new DataDrivenProperty({
109
- type: 'string',
115
+ type: 'formatted',
110
116
  default: '',
111
117
  tokens: true,
112
118
  expression: { parameters: ['zoom', 'feature'] }
@@ -46,20 +46,6 @@ class CompoundExpression {
46
46
  signature.length === args.length - 1 // correct param count
47
47
  );
48
48
 
49
- // First parse all the args
50
- const parsedArgs = [];
51
- for (let i = 1; i < args.length; i++) {
52
- const arg = args[i];
53
- let expected;
54
- if (overloads.length === 1) {
55
- const params = overloads[0][0];
56
- expected = Array.isArray(params) ? params[i - 1] : params.type;
57
- }
58
- const parsed = context.parse(arg, 1 + parsedArgs.length, expected);
59
- if (!parsed) return null;
60
- parsedArgs.push(parsed);
61
- }
62
-
63
49
  let signatureContext = null;
64
50
 
65
51
  for (const [params, evaluate] of overloads) {
@@ -67,6 +53,27 @@ class CompoundExpression {
67
53
  // we eventually succeed, we haven't polluted `context.errors`.
68
54
  signatureContext = new ParsingContext(context.registry, context.path, null, context.scope);
69
55
 
56
+ // First parse all the args, potentially coercing to the
57
+ // types expected by this overload.
58
+ const parsedArgs = [];
59
+ let argParseFailed = false;
60
+ for (let i = 1; i < args.length; i++) {
61
+ const arg = args[i];
62
+ const expectedType = Array.isArray(params) ? params[i - 1] : params.type;
63
+
64
+ const parsed = signatureContext.parse(arg, 1 + parsedArgs.length, expectedType);
65
+ if (!parsed) {
66
+ argParseFailed = true;
67
+ break;
68
+ }
69
+ parsedArgs.push(parsed);
70
+ }
71
+ if (argParseFailed) {
72
+ // Couldn't coerce args of this overload to expected type, move
73
+ // on to next one.
74
+ continue;
75
+ }
76
+
70
77
  if (Array.isArray(params)) {
71
78
  if (params.length !== parsedArgs.length) {
72
79
  signatureContext.error(`Expected ${params.length} arguments, but found ${parsedArgs.length} instead.`);
@@ -92,8 +99,15 @@ class CompoundExpression {
92
99
  } else {
93
100
  const expected = overloads.length ? overloads : availableOverloads;
94
101
  const signatures = expected.map(([params]) => stringifySignature(params)).join(' | ');
95
- const actualTypes = parsedArgs.map(arg => toString(arg.type)).join(', ');
96
- context.error(`Expected arguments of type ${signatures}, but found (${actualTypes}) instead.`);
102
+ const actualTypes = [];
103
+ // For error message, re-parse arguments without trying to
104
+ // apply any coercions
105
+ for (let i = 1; i < args.length; i++) {
106
+ const parsed = context.parse(args[i], 1 + actualTypes.length);
107
+ if (!parsed) return null;
108
+ actualTypes.push(toString(parsed.type));
109
+ }
110
+ context.error(`Expected arguments of type ${signatures}, but found (${actualTypes.join(', ')}) instead.`);
97
111
  }
98
112
 
99
113
  return null;
@@ -1,6 +1,15 @@
1
1
  const assert = require('assert');
2
2
 
3
- const { ObjectType, ValueType, StringType, NumberType, BooleanType, checkSubtype, toString } = require('../types');
3
+ const {
4
+ ObjectType,
5
+ ValueType,
6
+ StringType,
7
+ NumberType,
8
+ BooleanType,
9
+ checkSubtype,
10
+ toString,
11
+ array
12
+ } = require('../types');
4
13
  const RuntimeError = require('../runtime_error');
5
14
  const { typeOf } = require('../values');
6
15
 
@@ -20,13 +29,39 @@ class Assertion {
20
29
  static parse(args, context) {
21
30
  if (args.length < 2) return context.error('Expected at least one argument.');
22
31
 
32
+ let i = 1;
33
+ let type;
34
+
23
35
  const name = args[0];
24
- assert(types[name], name);
36
+ if (name === 'array') {
37
+ let itemType;
38
+ if (args.length > 2) {
39
+ const type = args[1];
40
+ if (typeof type !== 'string' || !(type in types) || type === 'object')
41
+ return context.error('The item type argument of "array" must be one of string, number, boolean', 1);
42
+ itemType = types[type];
43
+ i++;
44
+ } else {
45
+ itemType = ValueType;
46
+ }
25
47
 
26
- const type = types[name];
48
+ let N;
49
+ if (args.length > 3) {
50
+ if (args[2] !== null && (typeof args[2] !== 'number' || args[2] < 0 || args[2] !== Math.floor(args[2]))) {
51
+ return context.error('The length argument to "array" must be a positive integer literal', 2);
52
+ }
53
+ N = args[2];
54
+ i++;
55
+ }
56
+
57
+ type = array(itemType, N);
58
+ } else {
59
+ assert(types[name], name);
60
+ type = types[name];
61
+ }
27
62
 
28
63
  const parsed = [];
29
- for (let i = 1; i < args.length; i++) {
64
+ for (; i < args.length; i++) {
30
65
  const input = context.parse(args[i], i, ValueType);
31
66
  if (!input) return null;
32
67
  parsed.push(input);
@@ -62,7 +97,19 @@ class Assertion {
62
97
  }
63
98
 
64
99
  serialize() {
65
- return [this.type.kind].concat(this.args.map(arg => arg.serialize()));
100
+ const type = this.type;
101
+ const serialized = [type.kind];
102
+ if (type.kind === 'array') {
103
+ const itemType = type.itemType;
104
+ if (itemType.kind === 'string' || itemType.kind === 'number' || itemType.kind === 'boolean') {
105
+ serialized.push(itemType.kind);
106
+ const N = type.N;
107
+ if (typeof N === 'number' || this.args.length > 1) {
108
+ serialized.push(N);
109
+ }
110
+ }
111
+ }
112
+ return serialized.concat(this.args.map(arg => arg.serialize()));
66
113
  }
67
114
  }
68
115
 
@@ -3,6 +3,7 @@ const assert = require('assert');
3
3
  const { ColorType, ValueType, NumberType } = require('../types');
4
4
  const { Color, validateRGBA } = require('../values');
5
5
  const RuntimeError = require('../runtime_error');
6
+ const { Formatted, FormattedSection } = require('./formatted');
6
7
 
7
8
  const types = {
8
9
  'to-number': NumberType,
@@ -65,6 +66,18 @@ class Coercion {
65
66
  error || `Could not parse color from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`
66
67
  );
67
68
  }
69
+ if (this.type.kind === 'formatted') {
70
+ let input;
71
+ for (const arg of this.args) {
72
+ input = arg.evaluate(ctx);
73
+ if (typeof input === 'string') {
74
+ return new Formatted([new FormattedSection(input, null, null)]);
75
+ }
76
+ }
77
+ throw new RuntimeError(
78
+ `Could not parse formatted text from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`
79
+ );
80
+ }
68
81
  let value = null;
69
82
  for (const arg of this.args) {
70
83
  value = arg.evaluate(ctx);