maplibre-gl 2.1.8 → 2.2.0-pre.2

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 (121) hide show
  1. package/build/generate-style-spec.ts +2 -0
  2. package/dist/maplibre-gl-csp-worker.js +1 -1
  3. package/dist/maplibre-gl-csp-worker.js.map +1 -1
  4. package/dist/maplibre-gl-csp.js +1 -1
  5. package/dist/maplibre-gl-csp.js.map +1 -1
  6. package/dist/maplibre-gl-dev.js +1517 -290
  7. package/dist/maplibre-gl.css +1 -1
  8. package/dist/maplibre-gl.d.ts +3476 -3112
  9. package/dist/maplibre-gl.js +4 -4
  10. package/dist/maplibre-gl.js.map +1 -1
  11. package/dist/package.json +1 -1
  12. package/package.json +15 -15
  13. package/src/css/maplibre-gl.css +20 -0
  14. package/src/css/svg/maplibregl-ctrl-terrain.svg +7 -0
  15. package/src/data/bucket/fill_extrusion_attributes.ts +4 -0
  16. package/src/data/bucket/fill_extrusion_bucket.ts +28 -4
  17. package/src/data/dem_data.test.ts +14 -1
  18. package/src/data/dem_data.ts +13 -0
  19. package/src/geo/transform.test.ts +56 -1
  20. package/src/geo/transform.ts +199 -47
  21. package/src/index.ts +2 -0
  22. package/src/render/draw_background.ts +6 -6
  23. package/src/render/draw_circle.ts +6 -2
  24. package/src/render/draw_collision_debug.ts +5 -1
  25. package/src/render/draw_debug.ts +5 -5
  26. package/src/render/draw_fill.ts +5 -2
  27. package/src/render/draw_fill_extrusion.ts +3 -2
  28. package/src/render/draw_heatmap.ts +2 -3
  29. package/src/render/draw_hillshade.ts +8 -7
  30. package/src/render/draw_line.ts +7 -5
  31. package/src/render/draw_raster.ts +8 -6
  32. package/src/render/draw_symbol.test.ts +34 -10
  33. package/src/render/draw_symbol.ts +23 -12
  34. package/src/render/draw_terrain.ts +123 -0
  35. package/src/render/painter.ts +52 -14
  36. package/src/render/program/hillshade_program.ts +7 -2
  37. package/src/render/program/line_program.ts +24 -10
  38. package/src/render/program/program_uniforms.ts +5 -1
  39. package/src/render/program/terrain_program.ts +83 -0
  40. package/src/render/program.ts +29 -5
  41. package/src/render/render_to_texture.test.ts +41 -0
  42. package/src/render/render_to_texture.ts +154 -0
  43. package/src/render/terrain.test.ts +53 -0
  44. package/src/render/terrain.ts +369 -0
  45. package/src/render/vertex_array_object.ts +21 -4
  46. package/src/shaders/_prelude.vertex.glsl +76 -0
  47. package/src/shaders/_prelude.vertex.glsl.g.ts +1 -1
  48. package/src/shaders/circle.fragment.glsl +2 -1
  49. package/src/shaders/circle.fragment.glsl.g.ts +1 -1
  50. package/src/shaders/circle.vertex.glsl +6 -2
  51. package/src/shaders/circle.vertex.glsl.g.ts +1 -1
  52. package/src/shaders/collision_box.vertex.glsl +1 -1
  53. package/src/shaders/collision_box.vertex.glsl.g.ts +1 -1
  54. package/src/shaders/debug.vertex.glsl +1 -1
  55. package/src/shaders/debug.vertex.glsl.g.ts +1 -1
  56. package/src/shaders/fill_extrusion.vertex.glsl +16 -2
  57. package/src/shaders/fill_extrusion.vertex.glsl.g.ts +1 -1
  58. package/src/shaders/fill_extrusion_pattern.vertex.glsl +15 -2
  59. package/src/shaders/fill_extrusion_pattern.vertex.glsl.g.ts +1 -1
  60. package/src/shaders/line.vertex.glsl +7 -3
  61. package/src/shaders/line.vertex.glsl.g.ts +1 -1
  62. package/src/shaders/line_gradient.vertex.glsl +7 -3
  63. package/src/shaders/line_gradient.vertex.glsl.g.ts +1 -1
  64. package/src/shaders/line_pattern.vertex.glsl +7 -3
  65. package/src/shaders/line_pattern.vertex.glsl.g.ts +1 -1
  66. package/src/shaders/line_sdf.vertex.glsl +7 -4
  67. package/src/shaders/line_sdf.vertex.glsl.g.ts +1 -1
  68. package/src/shaders/shaders.ts +11 -1
  69. package/src/shaders/symbol_icon.vertex.glsl +8 -8
  70. package/src/shaders/symbol_icon.vertex.glsl.g.ts +1 -1
  71. package/src/shaders/symbol_sdf.vertex.glsl +8 -5
  72. package/src/shaders/symbol_sdf.vertex.glsl.g.ts +1 -1
  73. package/src/shaders/symbol_text_and_icon.vertex.glsl +8 -5
  74. package/src/shaders/symbol_text_and_icon.vertex.glsl.g.ts +1 -1
  75. package/src/shaders/terrain.fragment.glsl +7 -0
  76. package/src/shaders/terrain.fragment.glsl.g.ts +2 -0
  77. package/src/shaders/terrain.vertex.glsl +12 -0
  78. package/src/shaders/terrain.vertex.glsl.g.ts +2 -0
  79. package/src/shaders/terrain_coords.fragment.glsl +11 -0
  80. package/src/shaders/terrain_coords.fragment.glsl.g.ts +2 -0
  81. package/src/shaders/terrain_depth.fragment.glsl +15 -0
  82. package/src/shaders/terrain_depth.fragment.glsl.g.ts +2 -0
  83. package/src/source/canvas_source.test.ts +1 -1
  84. package/src/source/geojson_source.test.ts +25 -0
  85. package/src/source/geojson_source.ts +1 -8
  86. package/src/source/geojson_worker_source.test.ts +19 -23
  87. package/src/source/geojson_worker_source.ts +19 -70
  88. package/src/source/raster_dem_tile_source.ts +4 -3
  89. package/src/source/raster_dem_tile_worker_source.ts +0 -1
  90. package/src/source/source_cache.test.ts +83 -0
  91. package/src/source/source_cache.ts +72 -11
  92. package/src/source/terrain_source_cache.test.ts +89 -0
  93. package/src/source/terrain_source_cache.ts +201 -0
  94. package/src/source/tile.ts +15 -0
  95. package/src/source/tile_id.ts +9 -0
  96. package/src/style/pauseable_placement.ts +3 -1
  97. package/src/style/style.test.ts +16 -0
  98. package/src/style/style.ts +57 -3
  99. package/src/style/validate_style.ts +2 -0
  100. package/src/style-spec/CHANGELOG.md +6 -0
  101. package/src/style-spec/error/validation_error.ts +1 -1
  102. package/src/style-spec/package.json +2 -2
  103. package/src/style-spec/reference/v8.json +42 -0
  104. package/src/style-spec/types.g.ts +7 -0
  105. package/src/style-spec/validate/validate.ts +2 -0
  106. package/src/style-spec/validate/validate_terrain.test.ts +46 -0
  107. package/src/style-spec/validate/validate_terrain.ts +41 -0
  108. package/src/style-spec/validate_style.min.ts +2 -0
  109. package/src/style-spec/validate_style.ts +1 -0
  110. package/src/symbol/collision_index.ts +28 -12
  111. package/src/symbol/placement.ts +24 -9
  112. package/src/symbol/projection.ts +42 -27
  113. package/src/ui/camera.ts +2 -0
  114. package/src/ui/control/terrain_control.ts +77 -0
  115. package/src/ui/default_locale.ts +3 -2
  116. package/src/ui/events.ts +18 -3
  117. package/src/ui/handler_manager.ts +33 -3
  118. package/src/ui/map.ts +36 -6
  119. package/src/ui/marker.test.ts +21 -0
  120. package/src/ui/marker.ts +14 -0
  121. package/src/util/primitives.ts +14 -11
@@ -54,6 +54,7 @@ void main() {
54
54
  float a_size_min = floor(a_size[0] * 0.5);
55
55
  float is_sdf = a_size[0] - 2.0 * a_size_min;
56
56
 
57
+ float ele = get_elevation(a_pos);
57
58
  highp float segment_angle = -a_projected_pos[2];
58
59
  float size;
59
60
 
@@ -65,7 +66,7 @@ void main() {
65
66
  size = u_size;
66
67
  }
67
68
 
68
- vec4 projectedPoint = u_matrix * vec4(a_pos, 0, 1);
69
+ vec4 projectedPoint = u_matrix * vec4(a_pos, ele, 1);
69
70
  highp float camera_to_anchor_distance = projectedPoint.w;
70
71
  // If the label is pitched with the map, layout is done in pitched space,
71
72
  // which makes labels in the distance smaller relative to viewport space.
@@ -90,7 +91,7 @@ void main() {
90
91
  // Point labels with 'rotation-alignment: map' are horizontal with respect to tile units
91
92
  // To figure out that angle in projected space, we draw a short horizontal line in tile
92
93
  // space, project it, and measure its angle in projected space.
93
- vec4 offsetProjectedPoint = u_matrix * vec4(a_pos + vec2(1, 0), 0, 1);
94
+ vec4 offsetProjectedPoint = u_matrix * vec4(a_pos + vec2(1, 0), ele, 1);
94
95
 
95
96
  vec2 a = projectedPoint.xy / projectedPoint.w;
96
97
  vec2 b = offsetProjectedPoint.xy / offsetProjectedPoint.w;
@@ -102,13 +103,15 @@ void main() {
102
103
  highp float angle_cos = cos(segment_angle + symbol_rotation);
103
104
  mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos);
104
105
 
105
- vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0);
106
- gl_Position = u_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * fontScale), 0.0, 1.0);
106
+ vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, ele, 1.0);
107
+ float z = float(u_pitch_with_map) * projected_pos.z / projected_pos.w;
108
+ gl_Position = u_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * fontScale), z, 1.0);
107
109
  float gamma_scale = gl_Position.w;
108
110
 
109
111
  vec2 fade_opacity = unpack_opacity(a_fade_opacity);
112
+ float visibility = calculate_visibility(projectedPoint);
110
113
  float fade_change = fade_opacity[1] > 0.5 ? u_fade_change : -u_fade_change;
111
- float interpolated_fade_opacity = max(0.0, min(1.0, fade_opacity[0] + fade_change));
114
+ float interpolated_fade_opacity = max(0.0, min(visibility, fade_opacity[0] + fade_change));
112
115
 
113
116
  v_data0.xy = a_tex / u_texsize;
114
117
  v_data0.zw = a_tex / u_texsize_icon;
@@ -1,2 +1,2 @@
1
1
  // This file is generated. Edit build/generate-shaders.ts, then run `npm run codegen`.
2
- export default 'const float PI=3.141592653589793;attribute vec4 a_pos_offset;attribute vec4 a_data;attribute vec3 a_projected_pos;attribute float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform mat4 u_matrix;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform highp float u_camera_to_center_distance;uniform float u_fade_change;uniform vec2 u_texsize;uniform vec2 u_texsize_icon;varying vec4 v_data0;varying vec4 v_data1;\n#pragma mapbox: define highp vec4 fill_color\n#pragma mapbox: define highp vec4 halo_color\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp float halo_width\n#pragma mapbox: define lowp float halo_blur\nvoid main() {\n#pragma mapbox: initialize highp vec4 fill_color\n#pragma mapbox: initialize highp vec4 halo_color\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize lowp float halo_width\n#pragma mapbox: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);float is_sdf=a_size[0]-2.0*a_size_min;highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}vec4 projectedPoint=u_matrix*vec4(a_pos,0,1);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ?\ncamera_to_anchor_distance/u_camera_to_center_distance :\nu_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(0.5+0.5*distance_ratio,0.0,4.0);size*=perspective_ratio;float fontScale=size/24.0;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=u_matrix*vec4(a_pos+vec2(1,0),0,1);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy,0.0,1.0);gl_Position=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*fontScale),0.0,1.0);float gamma_scale=gl_Position.w;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float interpolated_fade_opacity=max(0.0,min(1.0,fade_opacity[0]+fade_change));v_data0.xy=a_tex/u_texsize;v_data0.zw=a_tex/u_texsize_icon;v_data1=vec4(gamma_scale,size,interpolated_fade_opacity,is_sdf);}';
2
+ export default 'const float PI=3.141592653589793;attribute vec4 a_pos_offset;attribute vec4 a_data;attribute vec3 a_projected_pos;attribute float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform mat4 u_matrix;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform highp float u_camera_to_center_distance;uniform float u_fade_change;uniform vec2 u_texsize;uniform vec2 u_texsize_icon;varying vec4 v_data0;varying vec4 v_data1;\n#pragma mapbox: define highp vec4 fill_color\n#pragma mapbox: define highp vec4 halo_color\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp float halo_width\n#pragma mapbox: define lowp float halo_blur\nvoid main() {\n#pragma mapbox: initialize highp vec4 fill_color\n#pragma mapbox: initialize highp vec4 halo_color\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize lowp float halo_width\n#pragma mapbox: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);float is_sdf=a_size[0]-2.0*a_size_min;float ele=get_elevation(a_pos);highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}vec4 projectedPoint=u_matrix*vec4(a_pos,ele,1);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ?\ncamera_to_anchor_distance/u_camera_to_center_distance :\nu_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(0.5+0.5*distance_ratio,0.0,4.0);size*=perspective_ratio;float fontScale=size/24.0;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=u_matrix*vec4(a_pos+vec2(1,0),ele,1);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy,ele,1.0);float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;gl_Position=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*fontScale),z,1.0);float gamma_scale=gl_Position.w;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float visibility=calculate_visibility(projectedPoint);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float interpolated_fade_opacity=max(0.0,min(visibility,fade_opacity[0]+fade_change));v_data0.xy=a_tex/u_texsize;v_data0.zw=a_tex/u_texsize_icon;v_data1=vec4(gamma_scale,size,interpolated_fade_opacity,is_sdf);}';
@@ -0,0 +1,7 @@
1
+ uniform sampler2D u_texture;
2
+
3
+ varying vec2 v_texture_pos;
4
+
5
+ void main() {
6
+ gl_FragColor = texture2D(u_texture, v_texture_pos);
7
+ }
@@ -0,0 +1,2 @@
1
+ // This file is generated. Edit build/generate-shaders.ts, then run `npm run codegen`.
2
+ export default 'uniform sampler2D u_texture;varying vec2 v_texture_pos;void main() {gl_FragColor=texture2D(u_texture,v_texture_pos);}';
@@ -0,0 +1,12 @@
1
+ attribute vec2 a_pos;
2
+
3
+ uniform mat4 u_matrix;
4
+
5
+ varying vec2 v_texture_pos;
6
+ varying float v_depth;
7
+
8
+ void main() {
9
+ v_texture_pos = a_pos / 8192.0; // 8192.0 is the hardcoded vector-tiles coordinates resolution
10
+ gl_Position = u_matrix * vec4(a_pos, get_elevation(a_pos), 1.0);
11
+ v_depth = gl_Position.z / gl_Position.w;
12
+ }
@@ -0,0 +1,2 @@
1
+ // This file is generated. Edit build/generate-shaders.ts, then run `npm run codegen`.
2
+ export default 'attribute vec2 a_pos;uniform mat4 u_matrix;varying vec2 v_texture_pos;varying float v_depth;void main() {v_texture_pos=a_pos/8192.0;gl_Position=u_matrix*vec4(a_pos,get_elevation(a_pos),1.0);v_depth=gl_Position.z/gl_Position.w;}';
@@ -0,0 +1,11 @@
1
+ precision mediump float;
2
+
3
+ uniform sampler2D u_texture;
4
+ uniform float u_terrain_coords_id;
5
+
6
+ varying vec2 v_texture_pos;
7
+
8
+ void main() {
9
+ vec4 rgba = texture2D(u_texture, v_texture_pos);
10
+ gl_FragColor = vec4(rgba.r, rgba.g, rgba.b, u_terrain_coords_id);
11
+ }
@@ -0,0 +1,2 @@
1
+ // This file is generated. Edit build/generate-shaders.ts, then run `npm run codegen`.
2
+ export default 'precision mediump float;uniform sampler2D u_texture;uniform float u_terrain_coords_id;varying vec2 v_texture_pos;void main() {vec4 rgba=texture2D(u_texture,v_texture_pos);gl_FragColor=vec4(rgba.r,rgba.g,rgba.b,u_terrain_coords_id);}';
@@ -0,0 +1,15 @@
1
+ varying float v_depth;
2
+
3
+ // methods for pack/unpack depth value to texture rgba
4
+ // https://stackoverflow.com/questions/34963366/encode-floating-point-data-in-a-rgba-texture
5
+ const highp vec4 bitSh = vec4(256. * 256. * 256., 256. * 256., 256., 1.);
6
+ const highp vec4 bitMsk = vec4(0.,vec3(1./256.0));
7
+ highp vec4 pack(highp float value) {
8
+ highp vec4 comp = fract(value * bitSh);
9
+ comp -= comp.xxyz * bitMsk;
10
+ return comp;
11
+ }
12
+
13
+ void main() {
14
+ gl_FragColor = pack(v_depth);
15
+ }
@@ -0,0 +1,2 @@
1
+ // This file is generated. Edit build/generate-shaders.ts, then run `npm run codegen`.
2
+ export default 'varying float v_depth;const highp vec4 bitSh=vec4(256.*256.*256.,256.*256.,256.,1.);const highp vec4 bitMsk=vec4(0.,vec3(1./256.0));highp vec4 pack(highp float value) {highp vec4 comp=fract(value*bitSh);comp-=comp.xxyz*bitMsk;return comp;}void main() {gl_FragColor=pack(v_depth);}';
@@ -61,7 +61,7 @@ describe('CanvasSource', () => {
61
61
  });
62
62
 
63
63
  test('self-validates', () => {
64
- const stub = jest.spyOn(console, 'error');
64
+ const stub = jest.spyOn(console, 'error').mockImplementation(() => {});
65
65
  createSource({coordinates: []});
66
66
  expect(stub).toHaveBeenCalled();
67
67
  stub.mockReset();
@@ -90,6 +90,18 @@ describe('GeoJSONSource#setData', () => {
90
90
  source.load();
91
91
  });
92
92
 
93
+ test('fires "dataabort" event', done => {
94
+ const source = new GeoJSONSource('id', {} as any, wrapDispatcher({
95
+ send(type, data, callback) {
96
+ setTimeout(() => callback(null, {abandoned: true}));
97
+ }
98
+ }), undefined);
99
+ source.on('dataabort', () => {
100
+ done();
101
+ });
102
+ source.load();
103
+ });
104
+
93
105
  test('respects collectResourceTiming parameter on source', done => {
94
106
  const source = createSource({collectResourceTiming: true});
95
107
  source.map = {
@@ -137,6 +149,19 @@ describe('GeoJSONSource#setData', () => {
137
149
  });
138
150
  source.setData({} as GeoJSON.GeoJSON);
139
151
  });
152
+
153
+ test('marks source as loaded before firing "dataabort" event', done => {
154
+ const source = new GeoJSONSource('id', {} as any, wrapDispatcher({
155
+ send(type, data, callback) {
156
+ setTimeout(() => callback(null, {abandoned: true}));
157
+ }
158
+ }), undefined);
159
+ source.on('dataabort', () => {
160
+ expect(source.loaded()).toBeTruthy();
161
+ done();
162
+ });
163
+ source.setData({} as GeoJSON.GeoJSON);
164
+ });
140
165
  });
141
166
 
142
167
  describe('GeoJSONSource#onRemove', () => {
@@ -256,20 +256,13 @@ class GeoJSONSource extends Evented implements Source {
256
256
  this._pendingLoads--;
257
257
 
258
258
  if (this._removed || (result && result.abandoned)) {
259
+ this.fire(new Event('dataabort', {dataType: 'source', sourceDataType}));
259
260
  return;
260
261
  }
261
262
 
262
263
  let resourceTiming = null;
263
264
  if (result && result.resourceTiming && result.resourceTiming[this.id])
264
265
  resourceTiming = result.resourceTiming[this.id].slice(0);
265
- // Any `loadData` calls that piled up while we were processing
266
- // this one will get coalesced into a single call when this
267
- // 'coalesce' message is processed.
268
- // We would self-send from the worker if we had access to its
269
- // message queue. Waiting instead for the 'coalesce' to round-trip
270
- // through the foreground just means we're throttling the worker
271
- // to run at a little less than full-throttle.
272
- this.actor.send(`${this.type}.coalesce`, {source: options.source}, null);
273
266
 
274
267
  if (err) {
275
268
  this.fire(new ErrorEvent(err));
@@ -46,7 +46,6 @@ describe('reloadTile', () => {
46
46
 
47
47
  function addData(callback) {
48
48
  source.loadData({source: 'sourceId', data: JSON.stringify(geoJson)} as LoadGeoJSONParameters, (err) => {
49
- source.coalesce();
50
49
  expect(err).toBeNull();
51
50
  callback();
52
51
  });
@@ -134,7 +133,10 @@ describe('resourceTiming', () => {
134
133
  window.performance.getEntriesByName = jest.fn().mockReturnValue([ exampleResourceTiming ]);
135
134
 
136
135
  const layerIndex = new StyleLayerIndex(layers);
137
- const source = new GeoJSONWorkerSource(actor, layerIndex, [], (params, callback) => { return callback(null, geoJson); });
136
+ const source = new GeoJSONWorkerSource(actor, layerIndex, [], (params, callback) => {
137
+ callback(null, geoJson);
138
+ return {cancel: () => {}};
139
+ });
138
140
 
139
141
  source.loadData({source: 'testSource', request: {url: 'http://localhost/nonexistent', collectResourceTiming: true}} as LoadGeoJSONParameters, (err, result) => {
140
142
  expect(err).toBeNull();
@@ -166,7 +168,10 @@ describe('resourceTiming', () => {
166
168
  jest.spyOn(perf, 'clearMeasures').mockImplementation(() => { return null; });
167
169
 
168
170
  const layerIndex = new StyleLayerIndex(layers);
169
- const source = new GeoJSONWorkerSource(actor, layerIndex, [], (params, callback) => { return callback(null, geoJson); });
171
+ const source = new GeoJSONWorkerSource(actor, layerIndex, [], (params, callback) => {
172
+ callback(null, geoJson);
173
+ return {cancel: () => {}};
174
+ });
170
175
 
171
176
  source.loadData({source: 'testSource', request: {url: 'http://localhost/nonexistent', collectResourceTiming: true}} as LoadGeoJSONParameters, (err, result) => {
172
177
  expect(err).toBeNull();
@@ -221,55 +226,46 @@ describe('loadData', () => {
221
226
  // (regardless of timing)
222
227
  const originalLoadGeoJSON = worker.loadGeoJSON;
223
228
  worker.loadGeoJSON = function(params, callback) {
224
- setTimeout(() => {
229
+ const timeout = setTimeout(() => {
225
230
  originalLoadGeoJSON(params, callback);
226
231
  }, 0);
232
+
233
+ return {cancel: () => clearTimeout(timeout)};
227
234
  };
228
235
  return worker;
229
236
  }
230
237
 
231
- test('abandons coalesced callbacks', done => {
232
- // Expect first call to run, second to be abandoned,
233
- // and third to run in response to coalesce
238
+ test('abandons previous callbacks', done => {
234
239
  const worker = createWorker();
235
- worker.loadData({source: 'source1', data: JSON.stringify(geoJson)} as LoadGeoJSONParameters, (err, result) => {
236
- expect(err).toBeNull();
237
- expect(result && result.abandoned).toBeFalsy();
238
- worker.coalesce();
239
- });
240
+ let firstCallbackHasRun = false;
240
241
 
241
242
  worker.loadData({source: 'source1', data: JSON.stringify(geoJson)} as LoadGeoJSONParameters, (err, result) => {
242
243
  expect(err).toBeNull();
243
244
  expect(result && result.abandoned).toBeTruthy();
245
+ firstCallbackHasRun = true;
244
246
  });
245
247
 
246
248
  worker.loadData({source: 'source1', data: JSON.stringify(geoJson)} as LoadGeoJSONParameters, (err, result) => {
247
249
  expect(err).toBeNull();
248
250
  expect(result && result.abandoned).toBeFalsy();
251
+ expect(firstCallbackHasRun).toBeTruthy();
249
252
  done();
250
253
  });
251
254
  });
252
255
 
253
256
  test('removeSource aborts callbacks', done => {
254
- // Expect:
255
- // First loadData starts running before removeSource arrives
256
- // Second loadData is pending when removeSource arrives, gets cancelled
257
- // removeSource is executed immediately
258
- // First loadData finishes running, sends results back to foreground
259
257
  const worker = createWorker();
260
- worker.loadData({source: 'source1', data: JSON.stringify(geoJson)} as LoadGeoJSONParameters, (err, result) => {
261
- expect(err).toBeNull();
262
- expect(result && result.abandoned).toBeFalsy();
263
- done();
264
- });
265
-
258
+ let loadDataCallbackHasRun = false;
266
259
  worker.loadData({source: 'source1', data: JSON.stringify(geoJson)} as LoadGeoJSONParameters, (err, result) => {
267
260
  expect(err).toBeNull();
268
261
  expect(result && result.abandoned).toBeTruthy();
262
+ loadDataCallbackHasRun = true;
269
263
  });
270
264
 
271
265
  worker.removeSource({source: 'source1'}, (err) => {
272
266
  expect(err).toBeFalsy();
267
+ expect(loadDataCallbackHasRun).toBeTruthy();
268
+ done();
273
269
  });
274
270
 
275
271
  });
@@ -21,6 +21,7 @@ import type StyleLayerIndex from '../style/style_layer_index';
21
21
  import type {LoadVectorDataCallback} from './vector_tile_worker_source';
22
22
  import type {RequestParameters, ResponseCallback} from '../util/ajax';
23
23
  import type {Callback} from '../types/callback';
24
+ import type {Cancelable} from '../types/cancelable';
24
25
 
25
26
  export type LoadGeoJSONParameters = {
26
27
  request?: RequestParameters;
@@ -33,7 +34,7 @@ export type LoadGeoJSONParameters = {
33
34
  filter?: Array<unknown>;
34
35
  };
35
36
 
36
- export type LoadGeoJSON = (params: LoadGeoJSONParameters, callback: ResponseCallback<any>) => void;
37
+ export type LoadGeoJSON = (params: LoadGeoJSONParameters, callback: ResponseCallback<any>) => Cancelable;
37
38
 
38
39
  export interface GeoJSONIndex {
39
40
  getTile(z: number, x: number, y: number): any;
@@ -72,10 +73,6 @@ function loadGeoJSONTile(params: WorkerTileParameters, callback: LoadVectorDataC
72
73
  });
73
74
  }
74
75
 
75
- export type SourceState = // Source empty or data loaded
76
- 'Idle' | // Data finished loading, but discard 'loadData' messages until receiving 'coalesced'
77
- 'Coalescing' | 'NeedsLoadData'; // 'loadData' received while coalescing, trigger one more 'loadData' on receiving 'coalesced'
78
-
79
76
  /**
80
77
  * The {@link WorkerSource} implementation that supports {@link GeoJSONSource}.
81
78
  * This class is designed to be easily reused to support custom source types
@@ -87,12 +84,11 @@ export type SourceState = // Source empty or data loaded
87
84
  * @private
88
85
  */
89
86
  class GeoJSONWorkerSource extends VectorTileWorkerSource {
90
- _state: SourceState;
91
87
  _pendingCallback: Callback<{
92
88
  resourceTiming?: {[_: string]: Array<PerformanceResourceTiming>};
93
89
  abandoned?: boolean;
94
90
  }>;
95
- _pendingLoadDataParams: LoadGeoJSONParameters;
91
+ _pendingRequest: Cancelable;
96
92
  _geoJSONIndex: GeoJSONIndex;
97
93
 
98
94
  /**
@@ -117,9 +113,8 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
117
113
  * expecting `callback(error, data)` to be called with either an error or a
118
114
  * parsed GeoJSON object.
119
115
  *
120
- * When `loadData` requests come in faster than they can be processed,
121
- * they are coalesced into a single request using the latest data.
122
- * See {@link GeoJSONWorkerSource#coalesce}
116
+ * When a `loadData` request comes in while a previous one is being processed,
117
+ * the previous one is aborted.
123
118
  *
124
119
  * @param params
125
120
  * @param callback
@@ -129,40 +124,20 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
129
124
  resourceTiming?: {[_: string]: Array<PerformanceResourceTiming>};
130
125
  abandoned?: boolean;
131
126
  }>) {
127
+ this._pendingRequest?.cancel();
132
128
  if (this._pendingCallback) {
133
129
  // Tell the foreground the previous call has been abandoned
134
130
  this._pendingCallback(null, {abandoned: true});
135
131
  }
136
- this._pendingCallback = callback;
137
- this._pendingLoadDataParams = params;
138
-
139
- if (this._state &&
140
- this._state !== 'Idle') {
141
- this._state = 'NeedsLoadData';
142
- } else {
143
- this._state = 'Coalescing';
144
- this._loadData();
145
- }
146
- }
147
-
148
- /**
149
- * Internal implementation: called directly by `loadData`
150
- * or by `coalesce` using stored parameters.
151
- */
152
- _loadData() {
153
- if (!this._pendingCallback || !this._pendingLoadDataParams) {
154
- assert(false);
155
- return;
156
- }
157
- const callback = this._pendingCallback;
158
- const params = this._pendingLoadDataParams;
159
- delete this._pendingCallback;
160
- delete this._pendingLoadDataParams;
161
132
 
162
133
  const perf = (params && params.request && params.request.collectResourceTiming) ?
163
134
  new RequestPerformance(params.request) : false;
164
135
 
165
- this.loadGeoJSON(params, (err?: Error | null, data?: any | null) => {
136
+ this._pendingCallback = callback;
137
+ this._pendingRequest = this.loadGeoJSON(params, (err?: Error | null, data?: any | null) => {
138
+ delete this._pendingCallback;
139
+ delete this._pendingRequest;
140
+
166
141
  if (err || !data) {
167
142
  return callback(err);
168
143
  } else if (typeof data !== 'object') {
@@ -204,35 +179,6 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
204
179
  });
205
180
  }
206
181
 
207
- /**
208
- * While processing `loadData`, we coalesce all further
209
- * `loadData` messages into a single call to _loadData
210
- * that will happen once we've finished processing the
211
- * first message. {@link GeoJSONSource#_updateWorkerData}
212
- * is responsible for sending us the `coalesce` message
213
- * at the time it receives a response from `loadData`
214
- *
215
- * State: Idle
216
- * ↑ |
217
- * 'coalesce' 'loadData'
218
- * | (triggers load)
219
- * | ↓
220
- * State: Coalescing
221
- * ↑ |
222
- * (triggers load) |
223
- * 'coalesce' 'loadData'
224
- * | ↓
225
- * State: NeedsLoadData
226
- */
227
- coalesce() {
228
- if (this._state === 'Coalescing') {
229
- this._state = 'Idle';
230
- } else if (this._state === 'NeedsLoadData') {
231
- this._state = 'Coalescing';
232
- this._loadData();
233
- }
234
- }
235
-
236
182
  /**
237
183
  * Implements {@link WorkerSource#reloadTile}.
238
184
  *
@@ -264,24 +210,27 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
264
210
  * @param params
265
211
  * @param [params.url] A URL to the remote GeoJSON data.
266
212
  * @param [params.data] Literal GeoJSON data. Must be provided if `params.url` is not.
213
+ * @returns {Cancelable} A Cancelable object.
267
214
  * @private
268
215
  */
269
- loadGeoJSON(params: LoadGeoJSONParameters, callback: ResponseCallback<any>) {
216
+ loadGeoJSON(params: LoadGeoJSONParameters, callback: ResponseCallback<any>): Cancelable {
270
217
  // Because of same origin issues, urls must either include an explicit
271
218
  // origin or absolute path.
272
219
  // ie: /foo/bar.json or http://example.com/bar.json
273
220
  // but not ../foo/bar.json
274
221
  if (params.request) {
275
- getJSON(params.request, callback);
222
+ return getJSON(params.request, callback);
276
223
  } else if (typeof params.data === 'string') {
277
224
  try {
278
- return callback(null, JSON.parse(params.data));
225
+ callback(null, JSON.parse(params.data));
279
226
  } catch (e) {
280
- return callback(new Error(`Input data given to '${params.source}' is not a valid GeoJSON object.`));
227
+ callback(new Error(`Input data given to '${params.source}' is not a valid GeoJSON object.`));
281
228
  }
282
229
  } else {
283
- return callback(new Error(`Input data given to '${params.source}' is not a valid GeoJSON object.`));
230
+ callback(new Error(`Input data given to '${params.source}' is not a valid GeoJSON object.`));
284
231
  }
232
+
233
+ return {cancel: () => {}};
285
234
  }
286
235
 
287
236
  removeSource(params: {
@@ -70,15 +70,16 @@ class RasterDEMTileSource extends RasterTileSource implements Source {
70
70
  }
71
71
  }
72
72
 
73
- function done(err, dem) {
73
+ function done(err, data) {
74
74
  if (err) {
75
75
  tile.state = 'errored';
76
76
  callback(err);
77
77
  }
78
78
 
79
- if (dem) {
80
- tile.dem = dem;
79
+ if (data) {
80
+ tile.dem = data;
81
81
  tile.needsHillshadePrepare = true;
82
+ tile.needsTerrainPrepare = true;
82
83
  tile.state = 'loaded';
83
84
  callback(null);
84
85
  }
@@ -1,6 +1,5 @@
1
1
  import DEMData from '../data/dem_data';
2
2
  import {RGBAImage} from '../util/image';
3
-
4
3
  import type Actor from '../util/actor';
5
4
  import type {
6
5
  WorkerDEMTileParameters,
@@ -1690,3 +1690,86 @@ describe('SourceCache#onRemove', () => {
1690
1690
  expect(sourceOnRemove).toHaveBeenCalled();
1691
1691
  });
1692
1692
  });
1693
+
1694
+ describe('SourceCache#usedForTerrain', () => {
1695
+ test('loads covering tiles with usedForTerrain with source zoom 0-14', done => {
1696
+ const transform = new Transform();
1697
+ transform.resize(511, 511);
1698
+ transform.zoom = 10;
1699
+
1700
+ const sourceCache = createSourceCache({});
1701
+ sourceCache.usedForTerrain = true;
1702
+ sourceCache.tileSize = 1024;
1703
+ expect(sourceCache.usedForTerrain).toBeTruthy();
1704
+ sourceCache.on('data', (e) => {
1705
+ if (e.sourceDataType === 'metadata') {
1706
+ sourceCache.update(transform);
1707
+ expect(Object.values(sourceCache._tiles).map(t => t.tileID.key)).toEqual(
1708
+ ['2tc099', '2tbz99', '2sxs99', '2sxr99', 'pds88', 'eo55', 'pdr88', 'en55', 'p6o88', 'ds55', 'p6n88', 'dr55']
1709
+ );
1710
+ done();
1711
+ }
1712
+ });
1713
+ sourceCache.onAdd(undefined);
1714
+ });
1715
+
1716
+ test('loads covering tiles with usedForTerrain with source zoom 8-14', done => {
1717
+ const transform = new Transform();
1718
+ transform.resize(511, 511);
1719
+ transform.zoom = 10;
1720
+
1721
+ const sourceCache = createSourceCache({minzoom: 8, maxzoom: 14});
1722
+ sourceCache.usedForTerrain = true;
1723
+ sourceCache.tileSize = 1024;
1724
+ sourceCache.on('data', (e) => {
1725
+ if (e.sourceDataType === 'metadata') {
1726
+ sourceCache.update(transform);
1727
+ expect(Object.values(sourceCache._tiles).map(t => t.tileID.key)).toEqual(
1728
+ ['2tc099', '2tbz99', '2sxs99', '2sxr99', 'pds88', 'pdr88', 'p6o88', 'p6n88']
1729
+ );
1730
+ done();
1731
+ }
1732
+ });
1733
+ sourceCache.onAdd(undefined);
1734
+ });
1735
+
1736
+ test('loads covering tiles with usedForTerrain with source zoom 0-4', done => {
1737
+ const transform = new Transform();
1738
+ transform.resize(511, 511);
1739
+ transform.zoom = 10;
1740
+
1741
+ const sourceCache = createSourceCache({minzoom: 0, maxzoom: 4});
1742
+ sourceCache.usedForTerrain = true;
1743
+ sourceCache.tileSize = 1024;
1744
+ sourceCache.on('data', (e) => {
1745
+ if (e.sourceDataType === 'metadata') {
1746
+ sourceCache.update(transform);
1747
+ expect(Object.values(sourceCache._tiles).map(t => t.tileID.key)).toEqual(
1748
+ ['1033', '3s44', '3r44', '3c44', '3b44', 'z33', 's33', 'r33']
1749
+ );
1750
+ done();
1751
+ }
1752
+ });
1753
+ sourceCache.onAdd(undefined);
1754
+ });
1755
+
1756
+ test('loads covering tiles with usedForTerrain with source zoom 4-4', done => {
1757
+ const transform = new Transform();
1758
+ transform.resize(511, 511);
1759
+ transform.zoom = 10;
1760
+
1761
+ const sourceCache = createSourceCache({minzoom: 4, maxzoom: 4});
1762
+ sourceCache.usedForTerrain = true;
1763
+ sourceCache.tileSize = 1024;
1764
+ sourceCache.on('data', (e) => {
1765
+ if (e.sourceDataType === 'metadata') {
1766
+ sourceCache.update(transform);
1767
+ expect(Object.values(sourceCache._tiles).map(t => t.tileID.key)).toEqual(
1768
+ ['3s44', '3r44', '3c44', '3b44']
1769
+ );
1770
+ done();
1771
+ }
1772
+ });
1773
+ sourceCache.onAdd(undefined);
1774
+ });
1775
+ });