@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
@@ -2,86 +2,71 @@ const assert = require('assert');
2
2
 
3
3
  module.exports = convertFunction;
4
4
 
5
- function convertFunction(parameters, propertySpec) {
6
- let expression;
5
+ function convertLiteral(value) {
6
+ return typeof value === 'object' ? ['literal', value] : value;
7
+ }
7
8
 
8
- parameters = { ...parameters };
9
- let defaultExpression;
10
- if (typeof parameters.default !== 'undefined') {
11
- defaultExpression = convertValue(parameters.default, propertySpec);
12
- } else {
13
- defaultExpression = convertValue(propertySpec.default, propertySpec);
14
- if (defaultExpression === null) {
15
- defaultExpression = ['error', 'No default property value available.'];
16
- }
9
+ function convertFunction(parameters, propertySpec) {
10
+ let stops = parameters.stops;
11
+ if (!stops) {
12
+ // identity function
13
+ return convertIdentityFunction(parameters, propertySpec);
17
14
  }
18
15
 
19
- if (parameters.stops) {
20
- const zoomAndFeatureDependent = parameters.stops && typeof parameters.stops[0][0] === 'object';
21
- const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined;
22
- const zoomDependent = zoomAndFeatureDependent || !featureDependent;
23
-
24
- const stops = parameters.stops.map(stop => {
25
- if (!featureDependent && propertySpec.tokens && typeof stop[1] === 'string') {
26
- return [stop[0], convertTokenString(stop[1])];
27
- }
28
- return [stop[0], convertValue(stop[1], propertySpec)];
29
- });
16
+ const zoomAndFeatureDependent = stops && typeof stops[0][0] === 'object';
17
+ const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined;
18
+ const zoomDependent = zoomAndFeatureDependent || !featureDependent;
30
19
 
31
- if (parameters.colorSpace && parameters.colorSpace !== 'rgb') {
32
- throw new Error('Unimplemented');
20
+ stops = stops.map(stop => {
21
+ if (!featureDependent && propertySpec.tokens && typeof stop[1] === 'string') {
22
+ return [stop[0], convertTokenString(stop[1])];
33
23
  }
24
+ return [stop[0], convertLiteral(stop[1])];
25
+ });
34
26
 
35
- if (zoomAndFeatureDependent) {
36
- expression = convertZoomAndPropertyFunction(parameters, propertySpec, stops, defaultExpression);
37
- } else if (zoomDependent) {
38
- expression = convertZoomFunction(parameters, propertySpec, stops);
39
- } else {
40
- expression = convertPropertyFunction(parameters, propertySpec, stops, defaultExpression);
41
- }
42
- } else {
43
- // identity function
44
- expression = convertIdentityFunction(parameters, propertySpec, defaultExpression);
27
+ if (zoomAndFeatureDependent) {
28
+ return convertZoomAndPropertyFunction(parameters, propertySpec, stops);
45
29
  }
46
-
47
- return expression;
30
+ if (zoomDependent) {
31
+ return convertZoomFunction(parameters, propertySpec, stops);
32
+ }
33
+ return convertPropertyFunction(parameters, propertySpec, stops);
48
34
  }
49
35
 
50
- function convertIdentityFunction(parameters, propertySpec, defaultExpression) {
36
+ function convertIdentityFunction(parameters, propertySpec) {
51
37
  const get = ['get', parameters.property];
52
38
 
53
- if (propertySpec.type === 'color') {
54
- return parameters.default === undefined ? get : ['to-color', get, parameters.default];
39
+ if (parameters.default === undefined) {
40
+ // By default, expressions for string-valued properties get coerced. To preserve
41
+ // legacy function semantics, insert an explicit assertion instead.
42
+ return propertySpec.type === 'string' ? ['string', get] : get;
55
43
  }
56
- if (propertySpec.type === 'array' && typeof propertySpec.length === 'number') {
57
- return ['array', propertySpec.value, propertySpec.length, get];
44
+ if (propertySpec.type === 'enum') {
45
+ return ['match', get, Object.keys(propertySpec.values), get, parameters.default];
58
46
  }
47
+ const expression = [
48
+ propertySpec.type === 'color' ? 'to-color' : propertySpec.type,
49
+ get,
50
+ convertLiteral(parameters.default)
51
+ ];
59
52
  if (propertySpec.type === 'array') {
60
- return ['array', propertySpec.value, get];
53
+ expression.splice(1, 0, propertySpec.value, propertySpec.length || null);
61
54
  }
62
- if (propertySpec.type === 'enum') {
63
- return [
64
- 'let',
65
- 'property_value',
66
- ['string', get],
67
- ['match', ['var', 'property_value'], propertySpec.values, ['var', 'property_value'], defaultExpression]
68
- ];
69
- }
70
- return parameters.default === undefined ? get : [propertySpec.type, get, parameters.default];
55
+ return expression;
71
56
  }
72
57
 
73
- function convertValue(value, spec) {
74
- if (typeof value === 'undefined' || value === null) return null;
75
- if (spec.type === 'color') {
76
- return value;
58
+ function getInterpolateOperator(parameters) {
59
+ switch (parameters.colorSpace) {
60
+ case 'hcl':
61
+ return 'interpolate-hcl';
62
+ case 'lab':
63
+ return 'interpolate-lab';
64
+ default:
65
+ return 'interpolate';
77
66
  }
78
- if (spec.type === 'array') {
79
- return ['literal', value];
80
- }
81
- return value;
82
67
  }
83
68
 
84
- function convertZoomAndPropertyFunction(parameters, propertySpec, stops, defaultExpression) {
69
+ function convertZoomAndPropertyFunction(parameters, propertySpec, stops) {
85
70
  const featureFunctionParameters = {};
86
71
  const featureFunctionStops = {};
87
72
  const zoomStops = [];
@@ -107,15 +92,10 @@ function convertZoomAndPropertyFunction(parameters, propertySpec, stops, default
107
92
  // otherwise.
108
93
  const functionType = getFunctionType({}, propertySpec);
109
94
  if (functionType === 'exponential') {
110
- const expression = ['interpolate', ['linear'], ['zoom']];
95
+ const expression = [getInterpolateOperator(parameters), ['linear'], ['zoom']];
111
96
 
112
97
  for (const z of zoomStops) {
113
- const output = convertPropertyFunction(
114
- featureFunctionParameters[z],
115
- propertySpec,
116
- featureFunctionStops[z],
117
- defaultExpression
118
- );
98
+ const output = convertPropertyFunction(featureFunctionParameters[z], propertySpec, featureFunctionStops[z]);
119
99
  appendStopPair(expression, z, output, false);
120
100
  }
121
101
 
@@ -124,12 +104,7 @@ function convertZoomAndPropertyFunction(parameters, propertySpec, stops, default
124
104
  const expression = ['step', ['zoom']];
125
105
 
126
106
  for (const z of zoomStops) {
127
- const output = convertPropertyFunction(
128
- featureFunctionParameters[z],
129
- propertySpec,
130
- featureFunctionStops[z],
131
- defaultExpression
132
- );
107
+ const output = convertPropertyFunction(featureFunctionParameters[z], propertySpec, featureFunctionStops[z]);
133
108
  appendStopPair(expression, z, output, true);
134
109
  }
135
110
 
@@ -138,43 +113,52 @@ function convertZoomAndPropertyFunction(parameters, propertySpec, stops, default
138
113
  return expression;
139
114
  }
140
115
 
141
- function convertPropertyFunction(parameters, propertySpec, stops, defaultExpression) {
142
- const type = getFunctionType(parameters, propertySpec);
116
+ function coalesce(a, b) {
117
+ if (a !== undefined) return a;
118
+ if (b !== undefined) return b;
119
+ }
143
120
 
144
- let expression;
145
- let isStep = false;
121
+ function convertPropertyFunction(parameters, propertySpec, stops) {
122
+ const type = getFunctionType(parameters, propertySpec);
123
+ const get = ['get', parameters.property];
146
124
  if (type === 'categorical' && typeof stops[0][0] === 'boolean') {
147
125
  assert(parameters.stops.length > 0 && parameters.stops.length <= 2);
148
- expression = ['case'];
126
+ const expression = ['case'];
149
127
  for (const stop of stops) {
150
- expression.push(['==', ['get', parameters.property], stop[0]], stop[1]);
128
+ expression.push(['==', get, stop[0]], stop[1]);
151
129
  }
152
- expression.push(defaultExpression);
130
+ expression.push(convertLiteral(coalesce(parameters.default, propertySpec.default)));
153
131
  return expression;
154
132
  }
155
133
  if (type === 'categorical') {
156
- expression = ['match', ['get', parameters.property]];
157
- } else if (type === 'interval') {
158
- expression = ['step', ['number', ['get', parameters.property]]];
159
- isStep = true;
160
- } else if (type === 'exponential') {
161
- const base = parameters.base !== undefined ? parameters.base : 1;
162
- expression = ['interpolate', ['exponential', base], ['number', ['get', parameters.property]]];
163
- } else {
164
- throw new Error(`Unknown property function type ${type}`);
134
+ const expression = ['match', get];
135
+ for (const stop of stops) {
136
+ appendStopPair(expression, stop[0], stop[1], false);
137
+ }
138
+ expression.push(convertLiteral(coalesce(parameters.default, propertySpec.default)));
139
+ return expression;
165
140
  }
166
-
167
- for (const stop of stops) {
168
- appendStopPair(expression, stop[0], stop[1], isStep);
141
+ if (type === 'interval') {
142
+ const expression = ['step', ['number', get]];
143
+ for (const stop of stops) {
144
+ appendStopPair(expression, stop[0], stop[1], true);
145
+ }
146
+ fixupDegenerateStepCurve(expression);
147
+ return parameters.default === undefined
148
+ ? expression
149
+ : ['case', ['==', ['typeof', get], 'number'], expression, convertLiteral(parameters.default)];
169
150
  }
170
-
171
- if (expression[0] === 'match') {
172
- expression.push(defaultExpression);
151
+ if (type === 'exponential') {
152
+ const base = parameters.base !== undefined ? parameters.base : 1;
153
+ const expression = [getInterpolateOperator(parameters), ['exponential', base], ['number', get]];
154
+ for (const stop of stops) {
155
+ appendStopPair(expression, stop[0], stop[1], false);
156
+ }
157
+ return parameters.default === undefined
158
+ ? expression
159
+ : ['case', ['==', ['typeof', get], 'number'], expression, convertLiteral(parameters.default)];
173
160
  }
174
-
175
- fixupDegenerateStepCurve(expression);
176
-
177
- return expression;
161
+ throw new Error(`Unknown property function type ${type}`);
178
162
  }
179
163
 
180
164
  function convertZoomFunction(parameters, propertySpec, stops, input = ['zoom']) {
@@ -186,7 +170,7 @@ function convertZoomFunction(parameters, propertySpec, stops, input = ['zoom'])
186
170
  isStep = true;
187
171
  } else if (type === 'exponential') {
188
172
  const base = parameters.base !== undefined ? parameters.base : 1;
189
- expression = ['interpolate', ['exponential', base], input];
173
+ expression = [getInterpolateOperator(parameters), ['exponential', base], input];
190
174
  } else {
191
175
  throw new Error(`Unknown zoom function type "${type}"`);
192
176
  }
@@ -239,7 +223,7 @@ function convertTokenString(s) {
239
223
  const literal = s.slice(pos, re.lastIndex - match[0].length);
240
224
  pos = re.lastIndex;
241
225
  if (literal.length > 0) result.push(literal);
242
- result.push(['to-string', ['get', match[1]]]);
226
+ result.push(['get', match[1]]);
243
227
  }
244
228
 
245
229
  if (result.length === 1) {
@@ -249,7 +233,7 @@ function convertTokenString(s) {
249
233
  if (pos < s.length) {
250
234
  result.push(s.slice(pos));
251
235
  } else if (result.length === 2) {
252
- return result[1];
236
+ return ['to-string', result[1]];
253
237
  }
254
238
 
255
239
  return result;
@@ -3,6 +3,7 @@ const Color = require('../util/color');
3
3
  const getType = require('../util/get_type');
4
4
  const interpolate = require('../util/interpolate');
5
5
  const Interpolate = require('../expression/definitions/interpolate');
6
+ const { Formatted } = require('../expression/types/formatted');
6
7
  const { supportsInterpolation } = require('../util/properties');
7
8
 
8
9
  module.exports = {
@@ -203,6 +204,9 @@ function evaluateIdentityFunction(parameters, propertySpec, input) {
203
204
  case 'color':
204
205
  input = Color.parse(input);
205
206
  break;
207
+ case 'formatted':
208
+ input = Formatted.fromString(input.toString());
209
+ break;
206
210
  case 'enum':
207
211
  if (!propertySpec.values.includes(input)) {
208
212
  input = undefined;
@@ -70,12 +70,12 @@
70
70
  },
71
71
  "sprite": {
72
72
  "type": "string",
73
- "doc": "A base URL for retrieving the sprite image and metadata. The extensions `.png`, `.json` and scale factor `@2x.png` will be automatically appended. This property is required if any layer uses the `background-pattern`, `fill-pattern`, `line-pattern`, `fill-extrusion-pattern`, or `icon-image` properties.",
73
+ "doc": "A base URL for retrieving the sprite image and metadata. The extensions `.png`, `.json` and scale factor `@2x.png` will be automatically appended. This property is required if any layer uses the `background-pattern`, `fill-pattern`, `line-pattern`, `fill-extrusion-pattern`, or `icon-image` properties. The URL must be absolute, containing the [scheme, authority and path components](https://en.wikipedia.org/wiki/URL#Syntax).",
74
74
  "example": "mapbox://sprites/mapbox/bright-v8"
75
75
  },
76
76
  "glyphs": {
77
77
  "type": "string",
78
- "doc": "A URL template for loading signed-distance-field glyph sets in PBF format. The URL must include `{fontstack}` and `{range}` tokens. This property is required if any layer uses the `text-field` layout property.",
78
+ "doc": "A URL template for loading signed-distance-field glyph sets in PBF format. The URL must include `{fontstack}` and `{range}` tokens. This property is required if any layer uses the `text-field` layout property. The URL must be absolute, containing the [scheme, authority and path components](https://en.wikipedia.org/wiki/URL#Syntax).",
79
79
  "example": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf"
80
80
  },
81
81
  "transition": {
@@ -946,6 +946,32 @@
946
946
  },
947
947
  "property-type": "data-constant"
948
948
  },
949
+ "symbol-z-order": {
950
+ "type": "enum",
951
+ "values": {
952
+ "viewport-y": {
953
+ "doc": "Symbols will be sorted by their y-position relative to the viewport."
954
+ },
955
+ "source": {
956
+ "doc": "Symbols will be rendered in the same order as the source data with no sorting applied."
957
+ }
958
+ },
959
+ "default": "viewport-y",
960
+ "doc": "Controls the order in which overlapping symbols in the same layer are rendered",
961
+ "sdk-support": {
962
+ "basic functionality": {
963
+ "js": "0.49.0"
964
+ },
965
+ "data-driven styling": {}
966
+ },
967
+ "expression": {
968
+ "interpolated": false,
969
+ "parameters": [
970
+ "zoom"
971
+ ]
972
+ },
973
+ "property-type": "data-constant"
974
+ },
949
975
  "icon-allow-overlap": {
950
976
  "type": "boolean",
951
977
  "default": false,
@@ -3122,7 +3148,7 @@
3122
3148
  }
3123
3149
  },
3124
3150
  "concat": {
3125
- "doc": "Returns a `string` consisting of the concatenation of the inputs. If any inputs are `formatted`, returns a `formatted` with default formatting options for all unformatted inputs.",
3151
+ "doc": "Returns a `string` consisting of the concatenation of the inputs. Each input is converted to a string as if by `to-string`.",
3126
3152
  "group": "String",
3127
3153
  "sdk-support": {
3128
3154
  "basic functionality": {
@@ -3458,7 +3484,10 @@
3458
3484
  "macos": "0.1.0"
3459
3485
  },
3460
3486
  "data-driven styling": {
3461
- "js": "0.46.0"
3487
+ "js": "0.49.0",
3488
+ "android": "6.5.0",
3489
+ "macos": "0.11.0",
3490
+ "ios": "4.4.0"
3462
3491
  }
3463
3492
  },
3464
3493
  "expression": {
@@ -3600,7 +3629,12 @@
3600
3629
  "ios": "3.6.0",
3601
3630
  "macos": "0.5.0"
3602
3631
  },
3603
- "data-driven styling": {}
3632
+ "data-driven styling": {
3633
+ "js": "0.49.0",
3634
+ "android": "6.5.0",
3635
+ "macos": "0.11.0",
3636
+ "ios": "4.4.0"
3637
+ }
3604
3638
  },
3605
3639
  "expression": {
3606
3640
  "interpolated": false,
@@ -3980,7 +4014,10 @@
3980
4014
  "macos": "0.1.0"
3981
4015
  },
3982
4016
  "data-driven styling": {
3983
- "js": "0.46.0"
4017
+ "js": "0.49.0",
4018
+ "android": "6.5.0",
4019
+ "macos": "0.11.0",
4020
+ "ios": "4.4.0"
3984
4021
  }
3985
4022
  },
3986
4023
  "expression": {
@@ -99,7 +99,6 @@ class CollisionIndex {
99
99
  allowOverlap,
100
100
  scale,
101
101
  textPixelRatio,
102
- key,
103
102
  symbol,
104
103
  lineVertexArray,
105
104
  glyphOffsetArray,
@@ -1,5 +1,7 @@
1
1
  const EXTENT = require('../data/extent');
2
2
 
3
+ const { SymbolInstanceArray } = require('../data/array_types');
4
+
3
5
  /*
4
6
  The CrossTileSymbolIndex generally works on the assumption that
5
7
  a conceptual "unique symbol" can be identified by the text of
@@ -23,7 +25,8 @@ class TileLayerIndex {
23
25
  this.indexedSymbolInstances = {};
24
26
  this.bucketInstanceId = bucketInstanceId;
25
27
 
26
- for (const symbolInstance of symbolInstances) {
28
+ for (let i = 0; i < symbolInstances.length; i++) {
29
+ const symbolInstance = symbolInstances.get(i);
27
30
  const key = symbolInstance.key;
28
31
  if (!this.indexedSymbolInstances[key]) {
29
32
  this.indexedSymbolInstances[key] = [];
@@ -46,10 +49,9 @@ class TileLayerIndex {
46
49
  getScaledCoordinates(symbolInstance, childTileID) {
47
50
  const zDifference = childTileID.canonical.z - this.tileID.canonical.z;
48
51
  const scale = roundingFactor / 2 ** zDifference;
49
- const anchor = symbolInstance.anchor;
50
52
  return {
51
- x: Math.floor((childTileID.canonical.x * EXTENT + anchor.x) * scale),
52
- y: Math.floor((childTileID.canonical.y * EXTENT + anchor.y) * scale)
53
+ x: Math.floor((childTileID.canonical.x * EXTENT + symbolInstance.anchorX) * scale),
54
+ y: Math.floor((childTileID.canonical.y * EXTENT + symbolInstance.anchorY) * scale)
53
55
  };
54
56
  }
55
57
 
@@ -57,7 +59,8 @@ class TileLayerIndex {
57
59
  const tolerance =
58
60
  this.tileID.canonical.z < newTileID.canonical.z ? 1 : 2 ** (this.tileID.canonical.z - newTileID.canonical.z);
59
61
 
60
- for (const symbolInstance of symbolInstances) {
62
+ for (let i = 0; i < symbolInstances.length; i++) {
63
+ const symbolInstance = symbolInstances.get(i);
61
64
  if (symbolInstance.crossTileID) {
62
65
  // already has a match, skip
63
66
  continue;
@@ -143,7 +146,8 @@ class CrossTileSymbolLayerIndex {
143
146
  this.removeBucketCrossTileIDs(tileID.overscaledZ, this.indexes[tileID.overscaledZ][tileID.key]);
144
147
  }
145
148
 
146
- for (const symbolInstance of bucket.symbolInstances) {
149
+ for (let i = 0; i < bucket.symbolInstances.length; i++) {
150
+ const symbolInstance = bucket.symbolInstances.get(i);
147
151
  symbolInstance.crossTileID = 0;
148
152
  }
149
153
 
@@ -170,7 +174,8 @@ class CrossTileSymbolLayerIndex {
170
174
  }
171
175
  }
172
176
 
173
- for (const symbolInstance of bucket.symbolInstances) {
177
+ for (let i = 0; i < bucket.symbolInstances.length; i++) {
178
+ const symbolInstance = bucket.symbolInstances.get(i);
174
179
  if (!symbolInstance.crossTileID) {
175
180
  // symbol did not match any known symbol, assign a new id
176
181
  symbolInstance.crossTileID = crossTileIDs.generate();
@@ -1,4 +1,4 @@
1
- const { Formatted } = require('../style-spec/expression/definitions/formatted');
1
+ const { Formatted } = require('../style-spec/expression/types/formatted');
2
2
 
3
3
  module.exports = function (features) {
4
4
  const leftIndex = new Map();
@@ -8,7 +8,7 @@ module.exports = function (features) {
8
8
 
9
9
  for (let k = 0; k < features.length; k++) {
10
10
  const { geometry, text: featureText } = features[k];
11
- const text = featureText instanceof Formatted ? featureText.toString() : featureText;
11
+ const text = featureText ? featureText.toString() : null;
12
12
 
13
13
  if (!text) {
14
14
  add(k);
@@ -170,9 +170,33 @@ class Placement {
170
170
  const textOptional = layout.get('text-optional');
171
171
  const iconOptional = layout.get('icon-optional');
172
172
 
173
+ const textAllowOverlap = layout.get('text-allow-overlap');
174
+ const iconAllowOverlap = layout.get('icon-allow-overlap');
175
+ // This logic is similar to the "defaultOpacityState" logic below in updateBucketOpacities
176
+ // If we know a symbol is always supposed to show, force it to be marked visible even if
177
+ // it wasn't placed into the collision index (because some or all of it was outside the range
178
+ // of the collision grid).
179
+ // There is a subtle edge case here we're accepting:
180
+ // Symbol A has text-allow-overlap: true, icon-allow-overlap: true, icon-optional: false
181
+ // A's icon is outside the grid, so doesn't get placed
182
+ // A's text would be inside grid, but doesn't get placed because of icon-optional: false
183
+ // We still show A because of the allow-overlap settings.
184
+ // Symbol B has allow-overlap: false, and gets placed where A's text would be
185
+ // On panning in, there is a short period when Symbol B and Symbol A will overlap
186
+ // This is the reverse of our normal policy of "fade in on pan", but should look like any other
187
+ // collision and hopefully not be too noticeable.
188
+ // See https://github.com/mapbox/mapbox-gl-js/issues/7172
189
+ const alwaysShowText = textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || iconOptional);
190
+ const alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || textOptional);
191
+
173
192
  const collisionGroup = this.collisionGroups.get(bucket.sourceID);
174
193
 
175
- for (const symbolInstance of bucket.symbolInstances) {
194
+ if (!bucket.collisionArrays && collisionBoxArray) {
195
+ bucket.deserializeCollisionBoxes(collisionBoxArray);
196
+ }
197
+
198
+ for (let i = 0; i < bucket.symbolInstances.length; i++) {
199
+ const symbolInstance = bucket.symbolInstances.get(i);
176
200
  if (!seenCrossTileIDs[symbolInstance.crossTileID]) {
177
201
  if (holdingForFade) {
178
202
  // Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't
@@ -192,22 +216,14 @@ class Placement {
192
216
  let textFeatureIndex = 0;
193
217
  let iconFeatureIndex = 0;
194
218
 
195
- if (!symbolInstance.collisionArrays) {
196
- symbolInstance.collisionArrays = bucket.deserializeCollisionBoxes(
197
- collisionBoxArray,
198
- symbolInstance.textBoxStartIndex,
199
- symbolInstance.textBoxEndIndex,
200
- symbolInstance.iconBoxStartIndex,
201
- symbolInstance.iconBoxEndIndex
202
- );
203
- }
219
+ const collisionArrays = bucket.collisionArrays[i];
204
220
 
205
- if (symbolInstance.collisionArrays.textFeatureIndex) {
206
- textFeatureIndex = symbolInstance.collisionArrays.textFeatureIndex;
221
+ if (collisionArrays.textFeatureIndex) {
222
+ textFeatureIndex = collisionArrays.textFeatureIndex;
207
223
  }
208
- if (symbolInstance.collisionArrays.textBox) {
224
+ if (collisionArrays.textBox) {
209
225
  placedGlyphBoxes = this.collisionIndex.placeCollisionBox(
210
- symbolInstance.collisionArrays.textBox,
226
+ collisionArrays.textBox,
211
227
  layout.get('text-allow-overlap'),
212
228
  textPixelRatio,
213
229
  posMatrix,
@@ -216,9 +232,9 @@ class Placement {
216
232
  placeText = placedGlyphBoxes.box.length > 0;
217
233
  offscreen = offscreen && placedGlyphBoxes.offscreen;
218
234
  }
219
- const textCircles = symbolInstance.collisionArrays.textCircles;
235
+ const textCircles = collisionArrays.textCircles;
220
236
  if (textCircles) {
221
- const placedSymbol = bucket.text.placedSymbolArray.get(symbolInstance.placedTextSymbolIndices[0]);
237
+ const placedSymbol = bucket.text.placedSymbolArray.get(symbolInstance.horizontalPlacedTextSymbolIndex);
222
238
  const fontSize = symbolSize.evaluateSizeForFeature(
223
239
  bucket.textSizeData,
224
240
  partiallyEvaluatedTextSize,
@@ -229,7 +245,6 @@ class Placement {
229
245
  layout.get('text-allow-overlap'),
230
246
  scale,
231
247
  textPixelRatio,
232
- symbolInstance.key,
233
248
  placedSymbol,
234
249
  bucket.lineVertexArray,
235
250
  bucket.glyphOffsetArray,
@@ -248,12 +263,12 @@ class Placement {
248
263
  offscreen = offscreen && placedGlyphCircles.offscreen;
249
264
  }
250
265
 
251
- if (symbolInstance.collisionArrays.iconFeatureIndex) {
252
- iconFeatureIndex = symbolInstance.collisionArrays.iconFeatureIndex;
266
+ if (collisionArrays.iconFeatureIndex) {
267
+ iconFeatureIndex = collisionArrays.iconFeatureIndex;
253
268
  }
254
- if (symbolInstance.collisionArrays.iconBox) {
269
+ if (collisionArrays.iconBox) {
255
270
  placedIconBoxes = this.collisionIndex.placeCollisionBox(
256
- symbolInstance.collisionArrays.iconBox,
271
+ collisionArrays.iconBox,
257
272
  layout.get('icon-allow-overlap'),
258
273
  textPixelRatio,
259
274
  posMatrix,
@@ -308,8 +323,8 @@ class Placement {
308
323
  assert(bucket.bucketInstanceId !== 0);
309
324
 
310
325
  this.placements[symbolInstance.crossTileID] = new JointPlacement(
311
- placeText,
312
- placeIcon,
326
+ placeText || (alwaysShowText && placedGlyphBoxes),
327
+ placeIcon || alwaysShowIcon,
313
328
  offscreen || bucket.justReloaded
314
329
  );
315
330
  seenCrossTileIDs[symbolInstance.crossTileID] = true;
@@ -412,8 +427,16 @@ class Placement {
412
427
  true
413
428
  );
414
429
 
430
+ if (
431
+ !bucket.collisionArrays &&
432
+ collisionBoxArray &&
433
+ (bucket.hasCollisionBoxData() || bucket.hasCollisionCircleData())
434
+ ) {
435
+ bucket.deserializeCollisionBoxes(collisionBoxArray);
436
+ }
437
+
415
438
  for (let s = 0; s < bucket.symbolInstances.length; s++) {
416
- const symbolInstance = bucket.symbolInstances[s];
439
+ const symbolInstance = bucket.symbolInstances.get(s);
417
440
  const isDuplicate = seenCrossTileIDs[symbolInstance.crossTileID];
418
441
 
419
442
  let opacityState = this.opacities[symbolInstance.crossTileID];
@@ -438,11 +461,14 @@ class Placement {
438
461
  for (let i = 0; i < opacityEntryCount; i++) {
439
462
  bucket.text.opacityVertexArray.emplaceBack(packedOpacity);
440
463
  }
441
- for (const placedTextSymbolIndex of symbolInstance.placedTextSymbolIndices) {
442
- const placedSymbol = bucket.text.placedSymbolArray.get(placedTextSymbolIndex);
443
- // If this label is completely faded, mark it so that we don't have to calculate
444
- // its position at render time
445
- placedSymbol.hidden = opacityState.text.isHidden();
464
+ // If this label is completely faded, mark it so that we don't have to calculate
465
+ // its position at render time
466
+ bucket.text.placedSymbolArray.get(symbolInstance.horizontalPlacedTextSymbolIndex).hidden =
467
+ opacityState.text.isHidden();
468
+
469
+ if (symbolInstance.verticalPlacedTextSymbolIndex >= 0) {
470
+ bucket.text.placedSymbolArray.get(symbolInstance.verticalPlacedTextSymbolIndex).hidden =
471
+ opacityState.text.isHidden();
446
472
  }
447
473
  }
448
474
 
@@ -451,35 +477,26 @@ class Placement {
451
477
  for (let i = 0; i < symbolInstance.numIconVertices / 4; i++) {
452
478
  bucket.icon.opacityVertexArray.emplaceBack(packedOpacity);
453
479
  }
454
- const placedSymbol = bucket.icon.placedSymbolArray.get(s);
455
- placedSymbol.hidden = opacityState.icon.isHidden();
480
+ bucket.icon.placedSymbolArray.get(s).hidden = opacityState.icon.isHidden();
456
481
  }
457
482
 
458
- if (!symbolInstance.collisionArrays) {
459
- symbolInstance.collisionArrays = bucket.deserializeCollisionBoxes(
460
- collisionBoxArray,
461
- symbolInstance.textBoxStartIndex,
462
- symbolInstance.textBoxEndIndex,
463
- symbolInstance.iconBoxStartIndex,
464
- symbolInstance.iconBoxEndIndex
465
- );
466
- }
467
-
468
- const collisionArrays = symbolInstance.collisionArrays;
469
- if (collisionArrays) {
470
- if (collisionArrays.textBox && bucket.hasCollisionBoxData()) {
471
- updateCollisionVertices(bucket.collisionBox.collisionVertexArray, opacityState.text.placed, false);
472
- }
483
+ if (bucket.hasCollisionBoxData() || bucket.hasCollisionCircleData()) {
484
+ const collisionArrays = bucket.collisionArrays[s];
485
+ if (collisionArrays) {
486
+ if (collisionArrays.textBox) {
487
+ updateCollisionVertices(bucket.collisionBox.collisionVertexArray, opacityState.text.placed, false);
488
+ }
473
489
 
474
- if (collisionArrays.iconBox && bucket.hasCollisionBoxData()) {
475
- updateCollisionVertices(bucket.collisionBox.collisionVertexArray, opacityState.icon.placed, false);
476
- }
490
+ if (collisionArrays.iconBox) {
491
+ updateCollisionVertices(bucket.collisionBox.collisionVertexArray, opacityState.icon.placed, false);
492
+ }
477
493
 
478
- const textCircles = collisionArrays.textCircles;
479
- if (textCircles && bucket.hasCollisionCircleData()) {
480
- for (let k = 0; k < textCircles.length; k += 5) {
481
- const notUsed = isDuplicate || textCircles[k + 4] === 0;
482
- updateCollisionVertices(bucket.collisionCircle.collisionVertexArray, opacityState.text.placed, notUsed);
494
+ const textCircles = collisionArrays.textCircles;
495
+ if (textCircles && bucket.hasCollisionCircleData()) {
496
+ for (let k = 0; k < textCircles.length; k += 5) {
497
+ const notUsed = isDuplicate || textCircles[k + 4] === 0;
498
+ updateCollisionVertices(bucket.collisionCircle.collisionVertexArray, opacityState.text.placed, notUsed);
499
+ }
483
500
  }
484
501
  }
485
502
  }