maplibre-gl 6.5.0 → 6.7.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 (118) hide show
  1. package/build/generate-unicode-data.ts +83 -0
  2. package/dist/maplibre-gl-dev.mjs +2062 -1531
  3. package/dist/maplibre-gl-dev.mjs.map +1 -1
  4. package/dist/maplibre-gl-shared-dev.mjs +1906 -1181
  5. package/dist/maplibre-gl-shared-dev.mjs.map +1 -1
  6. package/dist/maplibre-gl-shared.mjs +2 -2
  7. package/dist/maplibre-gl-shared.mjs.map +1 -1
  8. package/dist/maplibre-gl-worker-dev.mjs +7 -6
  9. package/dist/maplibre-gl-worker-dev.mjs.map +1 -1
  10. package/dist/maplibre-gl-worker.mjs +2 -2
  11. package/dist/maplibre-gl-worker.mjs.map +1 -1
  12. package/dist/maplibre-gl.d.ts +409 -153
  13. package/dist/maplibre-gl.mjs +42 -42
  14. package/dist/maplibre-gl.mjs.map +1 -1
  15. package/package.json +26 -21
  16. package/src/data/array_types.g.ts +43 -32
  17. package/src/data/bucket/symbol_attributes.ts +6 -3
  18. package/src/data/bucket/symbol_bucket.test.ts +45 -12
  19. package/src/data/bucket/symbol_bucket.ts +51 -26
  20. package/src/data/bucket.ts +11 -3
  21. package/src/data/feature_index.ts +38 -9
  22. package/src/geo/projection/covering_tiles.test.ts +36 -0
  23. package/src/geo/projection/covering_tiles.ts +2 -3
  24. package/src/geo/projection/globe_transform.test.ts +4 -5
  25. package/src/geo/projection/globe_transform.ts +7 -2
  26. package/src/geo/projection/globe_utils.ts +57 -0
  27. package/src/geo/projection/mercator_transform.bench.ts +55 -0
  28. package/src/geo/projection/mercator_transform.test.ts +227 -17
  29. package/src/geo/projection/mercator_transform.ts +119 -28
  30. package/src/geo/projection/mercator_utils.test.ts +50 -1
  31. package/src/geo/projection/mercator_utils.ts +20 -1
  32. package/src/geo/projection/vertical_perspective_transform.test.ts +97 -0
  33. package/src/geo/projection/vertical_perspective_transform.ts +72 -68
  34. package/src/geo/transform_helper.ts +14 -5
  35. package/src/geo/transform_interface.ts +22 -1
  36. package/src/render/font_face_manager.test.ts +295 -0
  37. package/src/render/font_face_manager.ts +249 -0
  38. package/src/render/glyph_atlas.ts +3 -7
  39. package/src/render/glyph_manager.test.ts +251 -94
  40. package/src/render/glyph_manager.ts +195 -52
  41. package/src/render/painter.test.ts +1 -3
  42. package/src/render/painter.ts +7 -20
  43. package/src/render/terrain.test.ts +109 -84
  44. package/src/render/terrain.ts +138 -117
  45. package/src/shaders/glsl/symbol_icon.vertex.glsl +4 -1
  46. package/src/shaders/glsl/symbol_icon.vertex.glsl.g.ts +1 -1
  47. package/src/shaders/glsl/symbol_sdf.vertex.glsl +4 -1
  48. package/src/shaders/glsl/symbol_sdf.vertex.glsl.g.ts +1 -1
  49. package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl +4 -1
  50. package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl.g.ts +1 -1
  51. package/src/shaders/shaders.ts +0 -4
  52. package/src/source/geojson_worker_source.test.ts +4 -53
  53. package/src/source/image_source.test.ts +53 -0
  54. package/src/source/image_source.ts +24 -2
  55. package/src/source/raster_dem_tile_source.ts +1 -0
  56. package/src/source/vector_tile_worker_source.test.ts +46 -38
  57. package/src/source/vector_tile_worker_source.ts +6 -1
  58. package/src/source/worker_source.ts +1 -5
  59. package/src/source/worker_tile.test.ts +6 -22
  60. package/src/source/worker_tile.ts +8 -5
  61. package/src/style/query_utils.ts +8 -8
  62. package/src/style/style.test.ts +92 -0
  63. package/src/style/style.ts +67 -3
  64. package/src/style/style_image.ts +5 -1
  65. package/src/style/style_layer/symbol_style_layer.ts +7 -7
  66. package/src/style/style_layer/symbol_style_layer_properties.g.ts +9 -3
  67. package/src/style/style_layer.ts +4 -6
  68. package/src/symbol/collision_index.ts +25 -19
  69. package/src/symbol/placement.test.ts +1 -1
  70. package/src/symbol/placement.ts +28 -13
  71. package/src/symbol/projection.test.ts +79 -15
  72. package/src/symbol/projection.ts +38 -20
  73. package/src/symbol/shaping.test.ts +436 -2
  74. package/src/symbol/shaping.ts +298 -113
  75. package/src/symbol/symbol_elevation.ts +28 -0
  76. package/src/symbol/symbol_layout.ts +15 -7
  77. package/src/symbol/symbol_size.test.ts +100 -0
  78. package/src/symbol/symbol_size.ts +78 -50
  79. package/src/symbol/tagged_string.test.ts +29 -6
  80. package/src/symbol/tagged_string.ts +151 -89
  81. package/src/tile/terrain_tile_manager.test.ts +23 -0
  82. package/src/tile/terrain_tile_manager.ts +8 -3
  83. package/src/tile/tile.ts +3 -2
  84. package/src/tile/tile_manager_raster_dem.ts +1 -0
  85. package/src/ui/camera.test.ts +6 -5
  86. package/src/ui/camera.ts +3 -3
  87. package/src/ui/control/navigation_control.test.ts +32 -0
  88. package/src/ui/control/navigation_control.ts +3 -3
  89. package/src/ui/handler_manager.test.ts +11 -23
  90. package/src/ui/handler_manager.ts +1 -1
  91. package/src/ui/map.ts +87 -21
  92. package/src/ui/map_tests/map_pitch.test.ts +12 -0
  93. package/src/ui/map_tests/map_render.test.ts +139 -1
  94. package/src/ui/map_tests/map_style.test.ts +66 -27
  95. package/src/ui/map_tests/map_terrain.test.ts +60 -1
  96. package/src/ui/map_tests/map_webgl.test.ts +41 -11
  97. package/src/ui/map_tests/map_zoom.test.ts +23 -0
  98. package/src/util/actor_messages.ts +3 -6
  99. package/src/util/gpu_initialization_error.ts +2 -1
  100. package/src/util/graphemes.test.ts +79 -0
  101. package/src/util/graphemes.ts +79 -0
  102. package/src/util/test/util.ts +80 -4
  103. package/src/util/unicode_properties.g.ts +28 -0
  104. package/src/util/util.ts +6 -2
  105. package/src/webgl/draw/draw_color_relief.ts +7 -10
  106. package/src/webgl/draw/draw_symbol.ts +17 -13
  107. package/src/webgl/draw/draw_terrain.ts +4 -39
  108. package/src/webgl/draw/index.ts +2 -4
  109. package/src/webgl/program/program_uniforms.ts +1 -3
  110. package/src/webgl/program/symbol_program.ts +15 -5
  111. package/src/webgl/program/terrain_program.ts +1 -22
  112. package/src/webgl/render_to_texture.test.ts +0 -1
  113. package/src/shaders/glsl/terrain_coords.fragment.glsl +0 -11
  114. package/src/shaders/glsl/terrain_coords.fragment.glsl.g.ts +0 -2
  115. package/src/shaders/glsl/terrain_coords.vertex.glsl +0 -12
  116. package/src/shaders/glsl/terrain_coords.vertex.glsl.g.ts +0 -2
  117. package/src/style/load_glyph_range.test.ts +0 -74
  118. package/src/style/load_glyph_range.ts +0 -32
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * MapLibre GL JS
3
- * @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v6.5.0/LICENSE.txt
3
+ * @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v6.7.0/LICENSE.txt
4
4
  */
5
- import { $ as tileCoordinatesToMercatorCoordinates, $n as deepEqual, $r as mul, $t as TRANSITION_SUFFIX, A as evaluateSizeForFeature, Ai as perspective, An as getVideo, Ar as remapSaturate, At as toEvaluationFeature, Bi as invert, Br as wrap, Bt as SegmentVector, C as isCustomStyleLayer, Ci as equals, Cn as Event, Cr as nextPowerOfTwo, Ct as HEATMAP_FULL_RENDER_FBO_KEY, D as SymbolBucket, Di as invert$1, Dn as getArrayBuffer, Dr as radiansToDegrees, Dt as RGBAImage, E as isSymbolStyleLayer, Ei as identity, En as GLOBAL_DISPATCHER_ID, Er as pointPlaneSignedDistance, Et as AlphaImage, F as ImagePosition, Fi as translate, Fn as removeProtocol, Fr as subscribe, Ft as Uniform4f, G as isFillExtrusionStyleLayer, Gn as bezier, Gr as length, Gt as PosArray, H as isLineStyleLayer, Hi as isOffscreenCanvasDistorted, Hn as angleToRotateBetweenVectors2D, Hr as pixelsToTileUnits, Ht as CollisionCircleLayoutArray, I as potpack, Ii as create$4, In as config, Ir as threePlaneIntersection, It as UniformColor, J as cameraDirectionFromPitchBearing, Jn as createIdentityMat4f32, Jr as zero, Jt as TriangleIndexArray, Kn as clamp, Kr as scale$1, Kt as QuadTriangleArray, L as isStyleImageWebGLData, Li as fromRotation, Ln as AbortError, Lr as translatePosition, Lt as UniformColorArray, Mi as rotateY$1, Mn as sameOrigin, Mr as rollPitchBearingToQuat, Mt as Uniform1i, N as getAnchorAlignment, Ni as rotateZ, Nn as addProtocol, Nr as scaleZoom, Nt as Uniform2f, O as addDynamicAttributes, Oi as multiply, On as getJSON, Or as rayPlaneIntersection, Ot as isCircleStyleLayer, Pi as scale, Pn as getProtocol, Pr as sphericalToCartesian, Pt as Uniform3f, Q as projectToWorldCoordinates, Qn as createVec4f64, Qr as slerp, Qt as Properties, R as renderStyleImage, Ri as create$2, Rn as isAbortError, Rr as uniqueId, Rt as UniformFloatArray, S as createStyleLayer, Si as create$1, Sn as ErrorEvent, Sr as mod, St as isHillshadeStyleLayer, T as isBackgroundStyleLayer, Ti as fromScaling, Tn as AJAXError, Tr as pick, Tt as renderColorRamp, Ui as offscreenCanvasSupported, Un as arrayBufferToImage, Ur as EXTENT, Ut as LineStripIndexArray, V as collisionCircleLayout, Vi as rotate, Vn as MAX_VALID_LATITUDE, Vr as zoomScale, Vt as CollisionBoxArray, Wi as Point, Wn as arrayBufferToImageBitmap, Wr as create$3, Wt as Pos3dArray, X as getMercatorHorizon, Xn as createMat4f64, Xr as fromValues, Xt as isRasterStyleLayer, Y as cameraMercatorCoordinateFromCenterAndRotation, Yn as createIdentityMat4f64, Yr as fromEuler, Yt as createLayout, Z as maxMercatorHorizonAngle, Zn as createVec3f64, Zr as multiply$1, Zt as DataConstantProperty, _ as UnwrappedTileID, _i as transformMat4$1, _n as emptyStyle, _r as isTouchableEvent, _t as SubdivisionGranularityExpression, a as clipLine, ai as dot, ar as evaluateZoomSnap, at as mercatorXfromLng, b as isInBoundsForZoomLngLat, bi as clone$1, bn as interpolateFactory, br as lerp, ci as negate, cn as validateAndEmit, cr as findLineIntersection, ct as LngLat, di as rotateY, dn as Color, dr as getEdgeTiles, dt as EXTENT_BOUNDS, ei as scale$3, en as Transitionable, er as defaultEasing, et as unprojectFromWorldCoordinates, f as GEOJSON_TILE_LAYER_NAME, fi as rotateZ$1, fn as ProjectionDefinition, ft as Bounds, g as OverscaledTileID, gi as transformMat3, gn as diff, gr as isSafari, gt as SOUTH_POLE_Y, h as CanonicalTileID, hi as sub, hn as derefLayers, hr as isPointableEvent, ht as NORTH_POLE_Y, ii as cross, in as ZoomHistory, ir as ensureError, it as lngFromMercatorX, j as evaluateSizeForZoom, ji as rotateX, jn as makeRequest, jr as rollPitchBearingEqual, jt as Uniform1f, k as getOverlapMode, ki as ortho, kn as getReferrer, kr as readImageUsingVideoFrame, kt as polygonIntersectsPolygon, li as normalize, ln as validateStyle, lr as getAABB, lt as earthRadius, mi as scaleAndAdd, mr as isImageBitmap, ni as add, nr as differenceOfAnglesDegrees, nt as altitudeFromMercatorZ, oi as len, on as SPEC_SOURCE_TYPES, or as extend, ot as mercatorYfromLat, pi as scale$2, pn as ValidationError, pr as getRollPitchBearing, pt as isFillStyleLayer, q as calculateTileMatrix, qn as clone$2, qr as sqrLen, qt as RasterBoundsArray, r as TextAnchorEnum, ri as clone, rn as codePointUsesLocalIdeographFontFamily, rr as distanceOfAnglesRadians, rt as latFromMercatorY, s as TileCache, si as length$1, sn as emitValidationErrors, sr as filterObject, st as mercatorZfromAltitude, t as getAnchorJustification, ti as transformMat4, tn as EvaluationParameters, tr as degreesToRadians, tt as MercatorCoordinate, u as GeoJSONFeature, ui as rotateX$1, un as validateStyleAndEmit, ur as getAngleDelta, v as calculateTileKey, vi as transformQuat, vn as featureFilter, vr as isTouchableOrPointableType, vt as SubdivisionGranularitySetting, w as validateCustomStyleLayer, wi as exactEquals, wn as Evented, wr as parseCacheControl, wt as isHeatmapStyleLayer, x as Actor, xi as copy, xn as latest, xr as mapObject, xt as Texture, y as compareTileId, yi as zero$1, yr as isWorker, yt as isColorReliefStyleLayer, z as parseGlyphPbf, zi as determinant, zn as throwIfAborted, zr as warnOnce, zt as UniformMatrix4f } from "./maplibre-gl-shared-dev.mjs";
5
+ import { $ as maxMercatorHorizonAngle, $n as createVec3f64, $r as multiply$1, $t as DataConstantProperty, A as evaluateSizeForFeature, Ai as multiply, An as getJSON, Ar as rayPlaneIntersection, At as isCircleStyleLayer, Bi as create$2, Bn as isAbortError, Br as uniqueId, Bt as UniformFloatArray, C as isCustomStyleLayer, Ci as copy, Cn as latest, Cr as mapObject, Ct as Texture, D as SymbolBucket, Di as fromScaling, Dn as AJAXError, Dr as pick, Dt as renderColorRamp, E as isSymbolStyleLayer, Ei as exactEquals, En as Evented, Er as parseCacheControl, Et as isHeatmapStyleLayer, F as ImagePosition, Fi as rotateZ, Fn as addProtocol, Fr as scaleZoom, Ft as Uniform2f, Gi as offscreenCanvasSupported, Gn as arrayBufferToImage, Gr as EXTENT, Gt as LineStripIndexArray, H as collisionCircleLayout, Hi as invert, Hr as wrap, Ht as SegmentVector, I as potpack, Ii as scale, In as getProtocol, Ir as sphericalToCartesian, It as Uniform3f, J as calculateTileMatrix, Jn as clamp, Jr as scale$1, Jt as QuadTriangleArray, K as isFillExtrusionStyleLayer, Ki as Point, Kn as arrayBufferToImageBitmap, Kr as create$3, Kt as Pos3dArray, L as isStyleImageWebGLData, Li as translate, Ln as removeProtocol, Lr as subscribe, Lt as Uniform4f, Mi as perspective, Mn as getVideo, Mr as remapSaturate, Mt as toEvaluationFeature, N as getAnchorAlignment, Ni as rotateX, Nn as makeRequest, Nr as rollPitchBearingEqual, Nt as Uniform1f, O as addDynamicAttributes, Oi as identity, On as GLOBAL_DISPATCHER_ID, Or as pointPlaneSignedDistance, Ot as AlphaImage, Pi as rotateY$1, Pn as sameOrigin, Pr as rollPitchBearingToQuat, Pt as Uniform1i, Q as getMercatorHorizon, Qn as createMat4f64, Qr as fromValues, Qt as isRasterStyleLayer, R as renderStyleImage, Ri as create$4, Rn as config, Rr as threePlaneIntersection, Rt as UniformColor, S as createStyleLayer, Si as clone$1, Sn as derefLayers, Sr as lerp, T as isBackgroundStyleLayer, Ti as equals, Tn as Event, Tr as nextPowerOfTwo, Tt as HEATMAP_FULL_RENDER_FBO_KEY, U as isLineStyleLayer, Ui as rotate, Un as MAX_VALID_LATITUDE, Ur as zoomScale, Ut as CollisionBoxArray, V as isCluster, Vi as determinant, Vn as throwIfAborted, Vr as warnOnce, Vt as UniformMatrix4f, Wi as isOffscreenCanvasDistorted, Wn as angleToRotateBetweenVectors2D, Wr as pixelsToTileUnits, Wt as CollisionCircleLayoutArray, X as cameraMercatorCoordinate, Xn as createIdentityMat4f32, Xr as zero, Xt as TriangleIndexArray, Y as cameraDirectionFromPitchBearing, Yn as clone$2, Yr as sqrLen, Yt as RasterBoundsArray, Z as cameraMercatorCoordinateFromCenterAndRotation, Zn as createIdentityMat4f64, Zr as fromEuler, Zt as createLayout, _ as UnwrappedTileID, _i as sub, _n as interpolateFactory, _r as isPointableEvent, _t as NORTH_POLE_Y, a as clipLine, ai as clone, an as codePointUsesLocalIdeographFontFamily, ar as distanceOfAnglesRadians, at as latFromMercatorY, b as isInBoundsForZoomLngLat, bi as transformQuat, bn as ValidationError, br as isTouchableOrPointableType, bt as SubdivisionGranularitySetting, ci as len, cn as SPEC_SOURCE_TYPES, cr as extend, ct as mercatorYfromLat, di as normalize, dn as validateStyle, dr as getAABB, dt as earthRadius, ei as slerp, en as Properties, er as createVec4f64, et as projectToWorldCoordinates, f as GEOJSON_TILE_LAYER_NAME, fi as rotateX$1, fn as validateStyleAndEmit, fr as getAngleDelta, g as OverscaledTileID, gi as scaleAndAdd, gr as isImageBitmap, h as CanonicalTileID, hi as scale$2, hn as featureFilter, hr as getRollPitchBearing, ht as isFillStyleLayer, ii as add, ir as differenceOfAnglesDegrees, it as altitudeFromMercatorZ, j as evaluateSizeForZoom, ji as ortho, jn as getReferrer, jr as readImageUsingVideoFrame, jt as polygonIntersectsPolygon, k as getOverlapMode, ki as invert$1, kn as getArrayBuffer, kr as radiansToDegrees, kt as RGBAImage, li as length$1, ln as emitValidationErrors, lr as filterObject, lt as mercatorZfromAltitude, mi as rotateZ$1, mt as Bounds, ni as scale$3, nn as Transitionable, nr as defaultEasing, nt as unprojectFromWorldCoordinates, oi as cross, on as ZoomHistory, or as ensureError, ot as lngFromMercatorX, pi as rotateY, pn as emptyStyle, pr as getEdgeTiles, pt as EXTENT_BOUNDS, qn as bezier, qr as length, qt as PosArray, r as TextAnchorEnum, ri as transformMat4, rn as EvaluationParameters, rr as degreesToRadians, rt as MercatorCoordinate, s as TileCache, si as dot, sr as evaluateZoomSnap, st as mercatorXfromLng, t as getAnchorJustification, ti as mul, tn as TRANSITION_SUFFIX, tr as deepEqual, tt as tileCoordinatesToMercatorCoordinates, u as GeoJSONFeature, ui as negate, un as validateAndEmit, ur as findLineIntersection, ut as LngLat, v as calculateTileKey, vi as transformMat3, vn as ProjectionDefinition, vr as isSafari, vt as SOUTH_POLE_Y, w as validateCustomStyleLayer, wi as create$1, wn as ErrorEvent, wr as mod, wt as isHillshadeStyleLayer, x as Actor, xi as zero$1, xn as diff, xr as isWorker, xt as isColorReliefStyleLayer, y as compareTileId, yi as transformMat4$1, yn as Color, yr as isTouchableEvent, yt as SubdivisionGranularityExpression, z as parseGlyphPbf, zi as fromRotation, zn as AbortError, zr as translatePosition, zt as UniformColorArray } from "./maplibre-gl-shared-dev.mjs";
6
6
  //#region package.json
7
- var version$2 = "6.5.0";
7
+ var version$2 = "6.7.0";
8
8
  //#endregion
9
9
  //#region src/util/browser.ts
10
10
  let linkEl;
@@ -1165,17 +1165,183 @@ var PatternAtlas = class {
1165
1165
  }
1166
1166
  };
1167
1167
  //#endregion
1168
- //#region src/style/load_glyph_range.ts
1169
- async function loadGlyphRange(fontstack, range, urlTemplate, requestManager) {
1170
- const begin = range * 256;
1171
- const end = begin + 255;
1172
- const request = await requestManager.transformRequest(urlTemplate.replace("{fontstack}", fontstack).replace("{range}", `${begin}-${end}`), "Glyphs");
1173
- const response = await getArrayBuffer(request, new AbortController());
1174
- if (!response?.data) throw new Error(`Could not load glyph range. range: ${range}, ${begin}-${end}`);
1175
- const glyphs = {};
1176
- for (const glyph of parseGlyphPbf(response.data)) glyphs[glyph.id] = glyph;
1177
- return glyphs;
1168
+ //#region src/render/font_face_manager.ts
1169
+ const MAX_CODE_POINT = 1114111;
1170
+ /**
1171
+ * What a font file covers when the style does not narrow it down with `unicode-range`.
1172
+ */
1173
+ const DEFAULT_UNICODE_RANGE = {
1174
+ start: 0,
1175
+ end: MAX_CODE_POINT
1176
+ };
1177
+ /**
1178
+ * Makes the family names unique across every map on the page, since `document.fonts` is shared.
1179
+ */
1180
+ let nextFamilyId = 0;
1181
+ /**
1182
+ * Parses one `unicode-range` entry, in the CSS grammar the style specification borrows: `U+A5`,
1183
+ * `U+0-10FFFF` or `U+4??`.
1184
+ * @param value - a single `unicode-range` entry
1185
+ * @returns the codepoints it covers, or `null` if it is not a range this can make sense of
1186
+ */
1187
+ function parseUnicodeRange(value) {
1188
+ const wildcard = /^u\+([0-9a-f]*)(\?+)$/i.exec(value);
1189
+ if (wildcard) {
1190
+ const [, prefix, questionMarks] = wildcard;
1191
+ if (prefix.length + questionMarks.length > 6) return null;
1192
+ return clampRange(parseInt(`${prefix}${"0".repeat(questionMarks.length)}`, 16), parseInt(`${prefix}${"f".repeat(questionMarks.length)}`, 16));
1193
+ }
1194
+ const explicit = /^u\+([0-9a-f]{1,6})(?:-([0-9a-f]{1,6}))?$/i.exec(value);
1195
+ if (!explicit) return null;
1196
+ const start = parseInt(explicit[1], 16);
1197
+ return clampRange(start, explicit[2] === void 0 ? start : parseInt(explicit[2], 16));
1198
+ }
1199
+ function clampRange(start, end) {
1200
+ if (start > end || start > MAX_CODE_POINT) return null;
1201
+ return {
1202
+ start,
1203
+ end: Math.min(end, MAX_CODE_POINT)
1204
+ };
1205
+ }
1206
+ /**
1207
+ * Whether the style lets this file cover the given codepoint.
1208
+ */
1209
+ function covers(face, codePoint) {
1210
+ return face.ranges.some(({ start, end }) => codePoint >= start && codePoint <= end);
1178
1211
  }
1212
+ /**
1213
+ * Keeps the font files a style declares in its
1214
+ * [`font-faces`](https://maplibre.org/maplibre-style-spec/root/#font-faces) property, and hands the
1215
+ * {@link GlyphManager} the CSS family to draw a given codepoint with.
1216
+ *
1217
+ * The files go to the browser's CSS Font Loading API, so any format it can render text with works.
1218
+ * Each is registered under a family name of our own making, so that a style cannot restyle the
1219
+ * surrounding page and a codepoint can be pinned to one file rather than to whatever font matching
1220
+ * picks.
1221
+ *
1222
+ * Rasterizing happens a grapheme cluster at a time, so a letter reaches the browser's text engine
1223
+ * with its marks. Shaping across cluster boundaries still needs the RTL text plugin.
1224
+ */
1225
+ var FontFaceManager = class {
1226
+ constructor(requestManager) {
1227
+ this.requestManager = requestManager;
1228
+ this._faces = {};
1229
+ this._registered = /* @__PURE__ */ new Set();
1230
+ }
1231
+ /**
1232
+ * Replaces the declared font faces. Nothing is downloaded here: each file waits until a
1233
+ * codepoint it covers is actually drawn.
1234
+ */
1235
+ setFontFaces(fontFaces) {
1236
+ this._unregisterAll();
1237
+ this._faces = {};
1238
+ for (const [fontName, declaration] of Object.entries(fontFaces ?? {})) {
1239
+ const declarations = Array.isArray(declaration) ? declaration : [declaration];
1240
+ this._faces[fontName] = declarations.map((entry) => this._declareFontFace(fontName, entry)).filter((face) => face !== null);
1241
+ }
1242
+ }
1243
+ /**
1244
+ * Whether the style declared any font face at all, so that the common case of a style without
1245
+ * `font-faces` costs nothing.
1246
+ */
1247
+ hasFontFaces() {
1248
+ return Object.keys(this._faces).length > 0;
1249
+ }
1250
+ /**
1251
+ * Finds the font file to draw a codepoint with: each name of the `text-font` stack in turn, and
1252
+ * within a name each declared file, until one covers it. A file that fails to load is skipped,
1253
+ * the specification asking for unsupported fonts to be ignored.
1254
+ *
1255
+ * @returns the CSS family to draw with, or `null` to leave the codepoint to the `glyphs` URL
1256
+ */
1257
+ async getFontFamily(fontStack, codePoint) {
1258
+ for (const fontName of fontStack.split(",")) for (const face of this._faces[fontName.trim()] ?? []) {
1259
+ if (!covers(face, codePoint)) continue;
1260
+ face.loaded ??= this._loadFontFace(face);
1261
+ if (await face.loaded) return face.family;
1262
+ }
1263
+ return null;
1264
+ }
1265
+ /**
1266
+ * Turns one declaration into a face to draw with, or `null` if there is nothing usable in it.
1267
+ *
1268
+ * A bare URL is the same as a face that names no `unicode-range`: it covers every codepoint.
1269
+ */
1270
+ _declareFontFace(fontName, declaration) {
1271
+ const face = typeof declaration === "string" ? { url: declaration } : declaration;
1272
+ if (typeof face?.url !== "string") {
1273
+ warnOnce(`Ignoring the font face declared for "${fontName}": it has no URL.`);
1274
+ return null;
1275
+ }
1276
+ const family = `maplibre-gl-font-face-${nextFamilyId++}`;
1277
+ const unicodeRange = face["unicode-range"];
1278
+ if (!unicodeRange?.length) return {
1279
+ url: face.url,
1280
+ ranges: [DEFAULT_UNICODE_RANGE],
1281
+ family
1282
+ };
1283
+ const ranges = [];
1284
+ for (const entry of unicodeRange) {
1285
+ const range = parseUnicodeRange(entry);
1286
+ if (!range) {
1287
+ warnOnce(`Ignoring the unicode range "${entry}" of the font face at ${face.url}: it is not a valid range.`);
1288
+ continue;
1289
+ }
1290
+ ranges.push(range);
1291
+ }
1292
+ if (!ranges.length) return null;
1293
+ return {
1294
+ url: face.url,
1295
+ ranges,
1296
+ family
1297
+ };
1298
+ }
1299
+ /**
1300
+ * Downloads a declared file and hands it to the browser, reporting whether it can be drawn with.
1301
+ * A failure is not an error: its codepoints fall through to the next file, then to `glyphs`.
1302
+ */
1303
+ async _loadFontFace(face) {
1304
+ if (typeof FontFace === "undefined" || typeof document === "undefined" || !document.fonts) {
1305
+ warnOnce(`Ignoring the font face at ${face.url}: this environment has no CSS Font Loading API.`);
1306
+ return false;
1307
+ }
1308
+ let fontFace;
1309
+ try {
1310
+ fontFace = new FontFace(face.family, await this._downloadFontFile(face.url));
1311
+ if (!Object.values(this._faces).some((faces) => faces.includes(face))) return false;
1312
+ document.fonts.add(fontFace);
1313
+ this._registered.add(fontFace);
1314
+ await fontFace.load();
1315
+ return true;
1316
+ } catch (e) {
1317
+ if (fontFace) this._unregister(fontFace);
1318
+ warnOnce(`Ignoring the font face at ${face.url}: ${ensureError(e).message}`);
1319
+ return false;
1320
+ }
1321
+ }
1322
+ /**
1323
+ * Downloads a font file, giving the map's `transformRequest` a chance to rewrite the request the
1324
+ * same way it gets one for a glyph range.
1325
+ */
1326
+ async _downloadFontFile(url) {
1327
+ const request = await this.requestManager.transformRequest(url, "Glyphs");
1328
+ const response = await getArrayBuffer(request, new AbortController());
1329
+ if (!response?.data) throw new Error(`the response was empty for the font file at ${url}`);
1330
+ return response.data;
1331
+ }
1332
+ _unregister(fontFace) {
1333
+ document.fonts?.delete(fontFace);
1334
+ this._registered.delete(fontFace);
1335
+ }
1336
+ _unregisterAll() {
1337
+ for (const fontFace of this._registered) document.fonts?.delete(fontFace);
1338
+ this._registered.clear();
1339
+ }
1340
+ destroy() {
1341
+ this._unregisterAll();
1342
+ this._faces = {};
1343
+ }
1344
+ };
1179
1345
  //#endregion
1180
1346
  //#region node_modules/@mapbox/tiny-sdf/index.js
1181
1347
  const INF = 0x56bc75e2d63100000;
@@ -1305,22 +1471,38 @@ const defaultGenericFontFamily = "sans-serif";
1305
1471
  * Client-generated glyphs are rendered at 2× because CJK glyphs are more detailed than others.
1306
1472
  */
1307
1473
  const textureScale = 2;
1308
- var GlyphManager = class GlyphManager {
1309
- static {
1310
- this.loadGlyphRange = loadGlyphRange;
1311
- }
1312
- static {
1313
- this.TinySDF = TinySDF;
1314
- }
1315
- constructor(requestManager, localIdeographFontFamily, lang) {
1474
+ const defaultCreateRasterizer = (options, padding) => {
1475
+ const tinySDF = new TinySDF(options);
1476
+ tinySDF.buffer = padding;
1477
+ return tinySDF;
1478
+ };
1479
+ /**
1480
+ * How wide a grapheme cluster may be drawn, in multiples of the font size.
1481
+ *
1482
+ * TinySDF sizes its canvas for a single character and cuts off the rest, but a cluster is a whole
1483
+ * syllable: Burmese `လား` is nearly twice the font size. Three ems fits the ones that occur.
1484
+ */
1485
+ const clusterEmsWide = 3;
1486
+ var GlyphManager = class {
1487
+ constructor(requestManager, localIdeographFontFamily, lang, createRasterizer = defaultCreateRasterizer) {
1316
1488
  this.requestManager = requestManager;
1317
1489
  this.localIdeographFontFamily = localIdeographFontFamily;
1318
1490
  this.entries = {};
1319
1491
  this.lang = lang;
1492
+ this.fontFaceManager = new FontFaceManager(requestManager);
1493
+ this.createRasterizer = createRasterizer;
1320
1494
  }
1321
1495
  setURL(url) {
1322
1496
  this.url = url;
1323
1497
  }
1498
+ /**
1499
+ * Replaces the font files the style declares in its `font-faces` property, dropping every glyph
1500
+ * drawn with the previous ones.
1501
+ */
1502
+ setFontFaces(fontFaces) {
1503
+ this.fontFaceManager.setFontFaces(fontFaces);
1504
+ this.entries = {};
1505
+ }
1324
1506
  async getGlyphs(glyphs) {
1325
1507
  const glyphsPromises = [];
1326
1508
  for (const stack in glyphs) for (const id of glyphs[stack]) glyphsPromises.push(this._getAndCacheGlyphsPromise(stack, id));
@@ -1336,6 +1518,14 @@ var GlyphManager = class GlyphManager {
1336
1518
  }
1337
1519
  return result;
1338
1520
  }
1521
+ /**
1522
+ * Gets one glyph, asked for by grapheme cluster so that a letter and its marks are drawn as the
1523
+ * one shape they are written as.
1524
+ *
1525
+ * Only a file the style pinned with `font-faces` can draw a cluster -- a glyphs URL serves
1526
+ * codepoints -- so where none covers it this returns nothing and layout falls back to codepoints.
1527
+ * For a single codepoint a declared file still wins over the glyphs URL and the local fallbacks.
1528
+ */
1339
1529
  async _getAndCacheGlyphsPromise(stack, id) {
1340
1530
  this.entries[stack] ??= {
1341
1531
  glyphs: {},
@@ -1349,7 +1539,25 @@ var GlyphManager = class GlyphManager {
1349
1539
  id,
1350
1540
  glyph
1351
1541
  };
1352
- if (!this.url || this._charUsesLocalIdeographFontFamily(id)) {
1542
+ const codePoint = id.codePointAt(0);
1543
+ const fontFaceFamily = this.fontFaceManager.hasFontFaces() ? await this.fontFaceManager.getFontFamily(stack, codePoint) : null;
1544
+ if (fontFaceFamily) {
1545
+ glyph = entry.glyphs[id] = await this._drawGlyph(entry, stack, id, fontFaceFamily);
1546
+ return {
1547
+ stack,
1548
+ id,
1549
+ glyph
1550
+ };
1551
+ }
1552
+ if (isCluster(id)) {
1553
+ glyph = entry.glyphs[id] = null;
1554
+ return {
1555
+ stack,
1556
+ id,
1557
+ glyph
1558
+ };
1559
+ }
1560
+ if (!this.url || this._charUsesLocalIdeographFontFamily(codePoint)) {
1353
1561
  glyph = entry.glyphs[id] = await this._drawGlyph(entry, stack, id);
1354
1562
  return {
1355
1563
  stack,
@@ -1359,27 +1567,34 @@ var GlyphManager = class GlyphManager {
1359
1567
  }
1360
1568
  return await this._downloadAndCacheRangePromise(stack, id);
1361
1569
  }
1570
+ /**
1571
+ * Gets a glyph from the server-side cache, downloading the PBF range it falls in if need be.
1572
+ *
1573
+ * Only reached for a single codepoint. What comes back is keyed by codepoint, as the file is,
1574
+ * and is cached by cluster -- which for one codepoint is the character itself.
1575
+ */
1362
1576
  async _downloadAndCacheRangePromise(stack, id) {
1577
+ const codePoint = id.codePointAt(0);
1363
1578
  const entry = this.entries[stack];
1364
- const range = Math.floor(id / 256);
1579
+ const range = Math.floor(codePoint / 256);
1365
1580
  if (entry.ranges[range]) return {
1366
1581
  stack,
1367
1582
  id,
1368
1583
  glyph: null
1369
1584
  };
1370
- entry.requests[range] ||= GlyphManager.loadGlyphRange(stack, range, this.url, this.requestManager);
1585
+ entry.requests[range] ||= this._loadGlyphRange(stack, range);
1371
1586
  try {
1372
1587
  const response = await entry.requests[range];
1373
- for (const id in response) entry.glyphs[+id] = response[+id];
1588
+ for (const responseId in response) entry.glyphs[String.fromCodePoint(+responseId)] = response[+responseId];
1374
1589
  entry.ranges[range] = true;
1375
1590
  return {
1376
1591
  stack,
1377
1592
  id,
1378
- glyph: response[id] || null
1593
+ glyph: response[codePoint] || null
1379
1594
  };
1380
1595
  } catch (e) {
1381
1596
  const glyph = entry.glyphs[id] = await this._drawGlyph(entry, stack, id);
1382
- this._warnOnMissingGlyphRange(glyph, range, id, ensureError(e));
1597
+ this._warnOnMissingGlyphRange(glyph, range, codePoint, ensureError(e));
1383
1598
  return {
1384
1599
  stack,
1385
1600
  id,
@@ -1387,6 +1602,19 @@ var GlyphManager = class GlyphManager {
1387
1602
  };
1388
1603
  }
1389
1604
  }
1605
+ /**
1606
+ * Downloads one range of 256 codepoints from the glyphs URL and parses the glyphs out of it.
1607
+ */
1608
+ async _loadGlyphRange(fontstack, range) {
1609
+ const begin = range * 256;
1610
+ const end = begin + 255;
1611
+ const request = await this.requestManager.transformRequest(this.url.replace("{fontstack}", fontstack).replace("{range}", `${begin}-${end}`), "Glyphs");
1612
+ const response = await getArrayBuffer(request, new AbortController());
1613
+ if (!response?.data) throw new Error(`Could not load glyph range. range: ${range}, ${begin}-${end}`);
1614
+ const glyphs = {};
1615
+ for (const glyph of parseGlyphPbf(response.data)) glyphs[glyph.id] = glyph;
1616
+ return glyphs;
1617
+ }
1390
1618
  _warnOnMissingGlyphRange(glyph, range, id, err) {
1391
1619
  const begin = range * 256;
1392
1620
  const end = begin + 255;
@@ -1400,13 +1628,13 @@ var GlyphManager = class GlyphManager {
1400
1628
  return !!this.localIdeographFontFamily && codePointUsesLocalIdeographFontFamily(id);
1401
1629
  }
1402
1630
  /**
1403
- * Draws a glyph offscreen using TinySDF, creating a TinySDF instance lazily.
1631
+ * Draws a glyph offscreen using TinySDF, created lazily. The whole cluster goes to TinySDF, which
1632
+ * is what lets the browser's text engine place a letter's marks on it.
1633
+ *
1634
+ * @param fontFaceFamily - the CSS family of the `font-faces` file covering this codepoint, if any
1404
1635
  */
1405
- async _drawGlyph(entry, stack, id) {
1406
- const usesLocalIdeographFontFamily = stack === defaultStack && this.localIdeographFontFamily !== "" && this._charUsesLocalIdeographFontFamily(id);
1407
- const tinySDFKey = usesLocalIdeographFontFamily ? "ideographTinySDF" : "tinySDF";
1408
- entry[tinySDFKey] ||= this._createTinySDF(usesLocalIdeographFontFamily ? this.localIdeographFontFamily : stack);
1409
- const char = (await entry[tinySDFKey]).draw(String.fromCodePoint(id));
1636
+ async _drawGlyph(entry, stack, id, fontFaceFamily) {
1637
+ const char = (await this._getTinySDF(entry, stack, id, fontFaceFamily)).draw(id);
1410
1638
  /**
1411
1639
  * TinySDF's "top" is the distance from the alphabetic baseline to the top of the glyph.
1412
1640
  * Server-generated fonts specify "top" relative to an origin above the em box (the origin
@@ -1422,9 +1650,9 @@ var GlyphManager = class GlyphManager {
1422
1650
  */
1423
1651
  const topAdjustment = 27.5;
1424
1652
  const leftAdjustment = .5;
1425
- const isControl = /^\p{gc=Cf}+$/u.test(String.fromCodePoint(id));
1653
+ const isControl = /^\p{gc=Cf}+$/u.test(id);
1426
1654
  return {
1427
- id,
1655
+ id: id.codePointAt(0),
1428
1656
  bitmap: new AlphaImage({
1429
1657
  width: char.width || 60,
1430
1658
  height: char.height || 60
@@ -1439,28 +1667,59 @@ var GlyphManager = class GlyphManager {
1439
1667
  }
1440
1668
  };
1441
1669
  }
1442
- async _createTinySDF(stack) {
1670
+ /**
1671
+ * Returns the TinySDF that draws this grapheme, created lazily. A stack keeps one per font
1672
+ * selection, so a fallback cannot bleed into the rest of the text, and one more per font file
1673
+ * for the clusters drawn from it, which need a wider canvas.
1674
+ *
1675
+ * A font file carries its own weight and style, so neither is sniffed out of the family name.
1676
+ * Where no file covers the grapheme, `localIdeographFontFamily` beats the last resort fontstack.
1677
+ */
1678
+ _getTinySDF(entry, stack, id, fontFaceFamily) {
1679
+ if (fontFaceFamily) {
1680
+ const cluster = isCluster(id);
1681
+ const cache = cluster ? "clusterTinySDFs" : "fontFaceTinySDFs";
1682
+ entry[cache] ??= {};
1683
+ entry[cache][fontFaceFamily] ||= this._createTinySDF(fontFaceFamily, false, cluster ? clusterEmsWide : 1);
1684
+ return entry[cache][fontFaceFamily];
1685
+ }
1686
+ const usesLocalIdeographFontFamily = stack === defaultStack && this.localIdeographFontFamily !== "" && this._charUsesLocalIdeographFontFamily(id.codePointAt(0));
1687
+ const cache = usesLocalIdeographFontFamily ? "ideographTinySDF" : "tinySDF";
1688
+ entry[cache] ||= this._createTinySDF(usesLocalIdeographFontFamily ? this.localIdeographFontFamily : stack);
1689
+ return entry[cache];
1690
+ }
1691
+ /**
1692
+ * Builds the TinySDF that draws with a given font selection.
1693
+ *
1694
+ * TinySDF derives its canvas from `fontSize + buffer * 4`, so the buffer is the only way in to a
1695
+ * wider one. It also stands for the padding the atlas expects to be `GLYPH_PBF_BORDER`, so the
1696
+ * two are passed separately.
1697
+ *
1698
+ * @param emsWide - how wide the glyphs may be, in font sizes, before TinySDF cuts them off
1699
+ */
1700
+ async _createTinySDF(stack, sniffFontStyles = true, emsWide = 1) {
1443
1701
  const fontFamilies = stack ? stack.split(",") : [];
1444
1702
  fontFamilies.push(defaultGenericFontFamily);
1445
1703
  const fontFamily = fontFamilies.map((fontName) => /[-\w]+/.test(fontName) ? fontName : `'${CSS.escape(fontName)}'`).join(",");
1446
1704
  const fontSize = 48;
1447
- const fontWeight = this._fontWeight(fontFamilies[0]);
1448
- const fontStyle = this._fontStyle(fontFamilies[0]);
1705
+ const fontWeight = sniffFontStyles ? this._fontWeight(fontFamilies[0]) : void 0;
1706
+ const fontStyle = sniffFontStyles ? this._fontStyle(fontFamilies[0]) : "normal";
1449
1707
  if (typeof document !== "undefined" && document.fonts?.load) try {
1450
1708
  await document.fonts.load(`${fontStyle} ${fontWeight || "normal"} ${fontSize}px ${fontFamily}`);
1451
1709
  } catch (e) {
1452
1710
  warnOnce(`Failed to load font "${fontFamily}": ${ensureError(e).message}`);
1453
1711
  }
1454
- return new GlyphManager.TinySDF({
1712
+ const padding = 6;
1713
+ return this.createRasterizer({
1455
1714
  fontSize,
1456
- buffer: 6,
1715
+ buffer: Math.max(padding, Math.ceil(fontSize * (emsWide - 1) / 4)),
1457
1716
  radius: 16,
1458
1717
  cutoff: .25,
1459
1718
  fontFamily,
1460
1719
  fontWeight,
1461
1720
  fontStyle,
1462
1721
  lang: this.lang
1463
- });
1722
+ }, padding);
1464
1723
  }
1465
1724
  /**
1466
1725
  * Sniffs the font style out of a font family name.
@@ -1502,11 +1761,13 @@ var GlyphManager = class GlyphManager {
1502
1761
  const entry = this.entries[stack];
1503
1762
  entry.tinySDF = null;
1504
1763
  entry.ideographTinySDF = null;
1764
+ entry.fontFaceTinySDFs = {};
1505
1765
  entry.glyphs = {};
1506
1766
  entry.requests = {};
1507
1767
  entry.ranges = {};
1508
1768
  }
1509
1769
  this.entries = {};
1770
+ this.fontFaceManager.destroy();
1510
1771
  }
1511
1772
  };
1512
1773
  //#endregion
@@ -3013,6 +3274,7 @@ var RasterDEMTileSource = class extends RasterTileSource {
3013
3274
  });
3014
3275
  tile.needsHillshadePrepare = true;
3015
3276
  tile.needsTerrainPrepare = true;
3277
+ tile.needsColorReliefPrepare = true;
3016
3278
  tile.state = "loaded";
3017
3279
  }
3018
3280
  } catch (err) {
@@ -4062,6 +4324,18 @@ const BILINEAR_FORESHORTENING = 512;
4062
4324
  * const bitmap = await createImageBitmap(myCanvas);
4063
4325
  * mySource.updateImage({image: bitmap});
4064
4326
  *
4327
+ * // create an empty source (no url), then feed it entirely via updateImage
4328
+ * map.addSource('empty id', {
4329
+ * type: 'image',
4330
+ * coordinates: [
4331
+ * [-76.54, 39.18],
4332
+ * [-76.52, 39.18],
4333
+ * [-76.52, 39.17],
4334
+ * [-76.54, 39.17]
4335
+ * ]
4336
+ * });
4337
+ * (map.getSource('empty id') as ImageSource).updateImage({image: bitmap});
4338
+ *
4065
4339
  * map.removeSource('some id'); // remove
4066
4340
  * ```
4067
4341
  */
@@ -4088,6 +4362,11 @@ var ImageSource = class extends Evented {
4088
4362
  this.options = options;
4089
4363
  }
4090
4364
  async load(newCoordinates) {
4365
+ if (this.options.url === void 0) {
4366
+ this._loaded = true;
4367
+ this._finishLoading();
4368
+ return;
4369
+ }
4091
4370
  this._loaded = false;
4092
4371
  this.fire(new MapSourceDataEvent("dataloading"));
4093
4372
  this.url = this.options.url;
@@ -4270,11 +4549,12 @@ var ImageSource = class extends Evented {
4270
4549
  } else tile.state = "errored";
4271
4550
  }
4272
4551
  serialize() {
4273
- return {
4552
+ const spec = {
4274
4553
  type: "image",
4275
- url: this.options.url,
4276
4554
  coordinates: this.coordinates
4277
4555
  };
4556
+ if (this.options.url !== void 0) spec.url = this.options.url;
4557
+ return spec;
4278
4558
  }
4279
4559
  hasTransition() {
4280
4560
  return false;
@@ -5322,9 +5602,8 @@ function getElevationForTileCulling(transform) {
5322
5602
  function coveringTiles(transform, options) {
5323
5603
  const frustum = transform.getCameraFrustum();
5324
5604
  const plane = transform.getClippingPlane();
5325
- const cameraCoord = transform.screenPointToMercatorCoordinate(transform.getCameraPoint());
5605
+ const cameraCoord = cameraMercatorCoordinate(transform);
5326
5606
  const centerCoord = MercatorCoordinate.fromLngLat(transform.center, transform.elevation);
5327
- cameraCoord.z = centerCoord.z + Math.cos(transform.pitchInRadians) * transform.cameraToCenterDistance / transform.worldSize;
5328
5607
  const elevationForTileCulling = getElevationForTileCulling(transform);
5329
5608
  const detailsProvider = transform.getCoveringTilesDetailsProvider();
5330
5609
  const allowVariableZoom = detailsProvider.allowVariableZoom(transform, options);
@@ -5565,6 +5844,7 @@ function backfillDEM(tile, inViewTiles) {
5565
5844
  function fillBorder(tile, borderTile) {
5566
5845
  tile.needsHillshadePrepare = true;
5567
5846
  tile.needsTerrainPrepare = true;
5847
+ tile.needsColorReliefPrepare = true;
5568
5848
  let dx = borderTile.tileID.canonical.x - tile.tileID.canonical.x;
5569
5849
  const dy = borderTile.tileID.canonical.y - tile.tileID.canonical.y;
5570
5850
  const dim = Math.pow(2, tile.tileID.canonical.z);
@@ -6285,6 +6565,30 @@ var PathInterpolator = class {
6285
6565
  }
6286
6566
  };
6287
6567
  //#endregion
6568
+ //#region src/symbol/symbol_elevation.ts
6569
+ /**
6570
+ * Resolves the elevation of a symbol at a tile coordinate, combining the terrain elevation with the
6571
+ * symbol's `symbol-height-offset`. `symbol-height-anchor` selects the datum the offset is measured
6572
+ * from: the terrain surface below the symbol (`ground`) or the zero elevation datum (`absolute`),
6573
+ * in which case the terrain below the symbol is ignored.
6574
+ *
6575
+ * Call this where the elevation is needed, so that `getElevation` keeps returning the plain terrain
6576
+ * elevation and never has to be substituted with a wrapper.
6577
+ *
6578
+ * @param getElevation - the terrain elevation function, absent when the map has no terrain
6579
+ * @param x - the tile x coordinate to evaluate the elevation at
6580
+ * @param y - the tile y coordinate to evaluate the elevation at
6581
+ * @param heightOffset - the evaluated `symbol-height-offset` of the symbol, in meters
6582
+ * @param heightAnchorGround - whether `symbol-height-anchor` is `ground`
6583
+ * @returns the elevation in meters, or undefined when the symbol sits at the zero elevation datum,
6584
+ * which lets the projection take its fast path that ignores the z coordinate
6585
+ */
6586
+ function getSymbolElevation(getElevation, x, y, heightOffset, heightAnchorGround) {
6587
+ if (!heightAnchorGround) return heightOffset;
6588
+ if (!getElevation) return heightOffset !== 0 ? heightOffset : void 0;
6589
+ return getElevation(x, y) + heightOffset;
6590
+ }
6591
+ //#endregion
6288
6592
  //#region src/symbol/grid_index.ts
6289
6593
  function overlapAllowed(overlapA, overlapB) {
6290
6594
  let allowed = true;
@@ -6678,16 +6982,22 @@ function getTileSkewVectors(transform) {
6678
6982
  };
6679
6983
  }
6680
6984
  /**
6985
+ * Resolves the elevation of the symbol described by `projectionContext` at a tile coordinate.
6986
+ */
6987
+ function elevationAt(projectionContext, x, y) {
6988
+ return getSymbolElevation(projectionContext.getElevation, x, y, projectionContext.heightOffset ?? 0, projectionContext.heightAnchorGround ?? true);
6989
+ }
6990
+ /**
6681
6991
  * Projects a point using a specified matrix, including the perspective divide.
6682
- * Uses a fast path if `getElevation` is undefined.
6992
+ * Uses a fast path if `elevation` is undefined.
6683
6993
  */
6684
- function projectWithMatrix(x, y, matrix, getElevation) {
6994
+ function projectWithMatrix(x, y, matrix, elevation) {
6685
6995
  let pos;
6686
- if (getElevation) {
6996
+ if (elevation != null) {
6687
6997
  pos = [
6688
6998
  x,
6689
6999
  y,
6690
- getElevation(x, y),
7000
+ elevation,
6691
7001
  1
6692
7002
  ];
6693
7003
  transformMat4(pos, pos, matrix);
@@ -6901,10 +7211,10 @@ function projectTileCoordinatesToLabelPlane(x, y, projectionContext) {
6901
7211
  const translatedY = y + projectionContext.translation[1];
6902
7212
  let projection;
6903
7213
  if (projectionContext.pitchWithMap) {
6904
- projection = projectWithMatrix(translatedX, translatedY, projectionContext.pitchedLabelPlaneMatrix, projectionContext.getElevation);
7214
+ projection = projectWithMatrix(translatedX, translatedY, projectionContext.pitchedLabelPlaneMatrix, elevationAt(projectionContext, translatedX, translatedY));
6905
7215
  projection.isOccluded = false;
6906
7216
  } else {
6907
- projection = projectionContext.transform.projectTileCoordinates(translatedX, translatedY, projectionContext.unwrappedTileID, projectionContext.getElevation);
7217
+ projection = projectionContext.transform.projectTileCoordinates(translatedX, translatedY, projectionContext.unwrappedTileID, elevationAt(projectionContext, translatedX, translatedY));
6908
7218
  projection.point.x = (projection.point.x * .5 + .5) * projectionContext.width;
6909
7219
  projection.point.y = (-projection.point.y * .5 + .5) * projectionContext.height;
6910
7220
  }
@@ -6919,7 +7229,9 @@ function projectFromLabelPlaneToClipSpace(x, y, projectionContext, pitchedLabelP
6919
7229
  1
6920
7230
  ];
6921
7231
  transformMat4(pos, pos, pitchedLabelPlaneMatrixInverse);
6922
- return projectionContext.transform.projectTileCoordinates(pos[0] / pos[3], pos[1] / pos[3], projectionContext.unwrappedTileID, projectionContext.getElevation).point;
7232
+ const tileX = pos[0] / pos[3];
7233
+ const tileY = pos[1] / pos[3];
7234
+ return projectionContext.transform.projectTileCoordinates(tileX, tileY, projectionContext.unwrappedTileID, elevationAt(projectionContext, tileX, tileY)).point;
6923
7235
  } else return {
6924
7236
  x: x / projectionContext.width * 2 - 1,
6925
7237
  y: 1 - y / projectionContext.height * 2
@@ -6929,7 +7241,7 @@ function projectFromLabelPlaneToClipSpace(x, y, projectionContext, pitchedLabelP
6929
7241
  * Projects the given point in tile coordinates to the GL clip space (-1..1).
6930
7242
  */
6931
7243
  function projectTileCoordinatesToClipSpace(x, y, projectionContext) {
6932
- return projectionContext.transform.projectTileCoordinates(x, y, projectionContext.unwrappedTileID, projectionContext.getElevation);
7244
+ return projectionContext.transform.projectTileCoordinates(x, y, projectionContext.unwrappedTileID, elevationAt(projectionContext, x, y));
6933
7245
  }
6934
7246
  /**
6935
7247
  * Calculate the normal vector for a line segment
@@ -7070,8 +7382,8 @@ function projectPathSpecialProjection(projectedPath, projectionContext) {
7070
7382
  const inverseLabelPlaneMatrix = tmpMat4;
7071
7383
  fastInvertSkewMat4(inverseLabelPlaneMatrix, projectionContext.pitchedLabelPlaneMatrix);
7072
7384
  return projectedPath.map((p) => {
7073
- const backProjected = projectWithMatrix(p.x, p.y, inverseLabelPlaneMatrix, projectionContext.getElevation);
7074
- const projected = projectionContext.transform.projectTileCoordinates(backProjected.point.x, backProjected.point.y, projectionContext.unwrappedTileID, projectionContext.getElevation);
7385
+ const backProjected = projectWithMatrix(p.x, p.y, inverseLabelPlaneMatrix, elevationAt(projectionContext, p.x, p.y));
7386
+ const projected = projectionContext.transform.projectTileCoordinates(backProjected.point.x, backProjected.point.y, projectionContext.unwrappedTileID, elevationAt(projectionContext, backProjected.point.x, backProjected.point.y));
7075
7387
  projected.point.x = (projected.point.x * .5 + .5) * projectionContext.width;
7076
7388
  projected.point.y = (-projected.point.y * .5 + .5) * projectionContext.height;
7077
7389
  return projected;
@@ -7122,10 +7434,10 @@ var CollisionIndex = class {
7122
7434
  this.gridBottomBoundary = transform.height + 200;
7123
7435
  this.perspectiveRatioCutoff = .6;
7124
7436
  }
7125
- placeCollisionBox(collisionBox, overlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translation, collisionGroupPredicate, getElevation, shift, simpleProjectionMatrix) {
7437
+ placeCollisionBox(collisionBox, overlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translation, collisionGroupPredicate, getElevation, shift, simpleProjectionMatrix, heightOffset = 0, heightAnchorGround = true) {
7126
7438
  const x = collisionBox.anchorPointX + translation[0];
7127
7439
  const y = collisionBox.anchorPointY + translation[1];
7128
- const projectedPoint = this.projectAndGetPerspectiveRatio(x, y, unwrappedTileID, getElevation, simpleProjectionMatrix);
7440
+ const projectedPoint = this.projectAndGetPerspectiveRatio(x, y, unwrappedTileID, getSymbolElevation(getElevation, x, y, heightOffset, heightAnchorGround), simpleProjectionMatrix);
7129
7441
  const tileToViewport = textPixelRatio * projectedPoint.perspectiveRatio;
7130
7442
  let projectedBox;
7131
7443
  if (!pitchWithMap && !rotateWithMap) {
@@ -7140,7 +7452,7 @@ var CollisionIndex = class {
7140
7452
  pointY + collisionBox.y2 * tileToViewport
7141
7453
  ]
7142
7454
  };
7143
- } else projectedBox = this._projectCollisionBox(collisionBox, tileToViewport, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translation, projectedPoint, getElevation, shift, simpleProjectionMatrix);
7455
+ } else projectedBox = this._projectCollisionBox(collisionBox, tileToViewport, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translation, projectedPoint, getElevation, shift, simpleProjectionMatrix, heightOffset, heightAnchorGround);
7144
7456
  const [tlX, tlY, brX, brY] = projectedBox.box;
7145
7457
  const occluded = pitchWithMap ? projectedBox.allPointsOccluded : projectedPoint.isOccluded;
7146
7458
  let unplaceable = occluded;
@@ -7172,7 +7484,7 @@ var CollisionIndex = class {
7172
7484
  placeCollisionCircles(overlapMode, symbol, lineVertexArray, glyphOffsetArray, fontSize, unwrappedTileID, pitchedLabelPlaneMatrix, showCollisionCircles, pitchWithMap, collisionGroupPredicate, circlePixelDiameter, textPixelPadding, translation, getElevation) {
7173
7485
  const placedCollisionCircles = [];
7174
7486
  const tileUnitAnchorPoint = new Point(symbol.anchorX, symbol.anchorY);
7175
- const perspectiveRatio = this.getPerspectiveRatio(tileUnitAnchorPoint.x, tileUnitAnchorPoint.y, unwrappedTileID, getElevation);
7487
+ const perspectiveRatio = this.getPerspectiveRatio(tileUnitAnchorPoint.x, tileUnitAnchorPoint.y, unwrappedTileID, getElevation?.(tileUnitAnchorPoint.x, tileUnitAnchorPoint.y));
7176
7488
  const labelPlaneFontScale = (pitchWithMap ? fontSize * this.transform.getPitchedTextCorrection(symbol.anchorX, symbol.anchorY, unwrappedTileID) / perspectiveRatio : fontSize * perspectiveRatio) / 24;
7177
7489
  const projectionCache = {
7178
7490
  projections: {},
@@ -7321,14 +7633,14 @@ var CollisionIndex = class {
7321
7633
  };
7322
7634
  for (let k = 0; k < collisionCircles.length; k += 4) grid.insertCircle(key, collisionCircles[k], collisionCircles[k + 1], collisionCircles[k + 2]);
7323
7635
  }
7324
- projectAndGetPerspectiveRatio(x, y, unwrappedTileID, getElevation, simpleProjectionMatrix) {
7636
+ projectAndGetPerspectiveRatio(x, y, unwrappedTileID, elevation, simpleProjectionMatrix) {
7325
7637
  if (simpleProjectionMatrix) {
7326
7638
  let pos;
7327
- if (getElevation) {
7639
+ if (elevation != null) {
7328
7640
  pos = [
7329
7641
  x,
7330
7642
  y,
7331
- getElevation(x, y),
7643
+ elevation,
7332
7644
  1
7333
7645
  ];
7334
7646
  transformMat4(pos, pos, simpleProjectionMatrix);
@@ -7350,7 +7662,7 @@ var CollisionIndex = class {
7350
7662
  signedDistanceFromCamera: w
7351
7663
  };
7352
7664
  } else {
7353
- const projected = this.transform.projectTileCoordinates(x, y, unwrappedTileID, getElevation);
7665
+ const projected = this.transform.projectTileCoordinates(x, y, unwrappedTileID, elevation);
7354
7666
  return {
7355
7667
  x: (projected.point.x + 1) / 2 * this.transform.width + 100,
7356
7668
  y: (-projected.point.y + 1) / 2 * this.transform.height + 100,
@@ -7360,8 +7672,8 @@ var CollisionIndex = class {
7360
7672
  };
7361
7673
  }
7362
7674
  }
7363
- getPerspectiveRatio(x, y, unwrappedTileID, getElevation) {
7364
- const projected = this.transform.projectTileCoordinates(x, y, unwrappedTileID, getElevation);
7675
+ getPerspectiveRatio(x, y, unwrappedTileID, elevation) {
7676
+ const projected = this.transform.projectTileCoordinates(x, y, unwrappedTileID, elevation);
7365
7677
  return .5 + .5 * (this.transform.cameraToCenterDistance / projected.signedDistanceFromCamera);
7366
7678
  }
7367
7679
  isOffscreen(x1, y1, x2, y2) {
@@ -7382,7 +7694,7 @@ var CollisionIndex = class {
7382
7694
  /**
7383
7695
  * Applies all layout+paint properties of the given box in order to find as good approximation of its screen-space bounding box as possible.
7384
7696
  */
7385
- _projectCollisionBox(collisionBox, tileToViewport, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translation, projectedPoint, getElevation, shift, simpleProjectionMatrix) {
7697
+ _projectCollisionBox(collisionBox, tileToViewport, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translation, projectedPoint, getElevation, shift, simpleProjectionMatrix, heightOffset = 0, heightAnchorGround = true) {
7386
7698
  let vecEastX = 1;
7387
7699
  let vecEastY = 0;
7388
7700
  let vecSouthX = 0;
@@ -7390,7 +7702,7 @@ var CollisionIndex = class {
7390
7702
  const translatedAnchorX = collisionBox.anchorPointX + translation[0];
7391
7703
  const translatedAnchorY = collisionBox.anchorPointY + translation[1];
7392
7704
  if (rotateWithMap && !pitchWithMap) {
7393
- const projectedEast = this.projectAndGetPerspectiveRatio(translatedAnchorX + 1, translatedAnchorY, unwrappedTileID, getElevation, simpleProjectionMatrix);
7705
+ const projectedEast = this.projectAndGetPerspectiveRatio(translatedAnchorX + 1, translatedAnchorY, unwrappedTileID, getSymbolElevation(getElevation, translatedAnchorX + 1, translatedAnchorY, heightOffset, heightAnchorGround), simpleProjectionMatrix);
7394
7706
  const toEastX = projectedEast.x - projectedPoint.x;
7395
7707
  const toEastY = projectedEast.y - projectedPoint.y;
7396
7708
  const angle = Math.atan(toEastY / toEastX) + (toEastX < 0 ? Math.PI : 0);
@@ -7470,7 +7782,7 @@ var CollisionIndex = class {
7470
7782
  for (const { offsetX, offsetY } of offsetsArray) points.push(new Point(basePointX + vecEastX * offsetX + vecSouthX * offsetY, basePointY + vecEastY * offsetX + vecSouthY * offsetY));
7471
7783
  let anyPointVisible = false;
7472
7784
  if (pitchWithMap) {
7473
- const projected = points.map((p) => this.projectAndGetPerspectiveRatio(p.x, p.y, unwrappedTileID, getElevation, simpleProjectionMatrix));
7785
+ const projected = points.map((p) => this.projectAndGetPerspectiveRatio(p.x, p.y, unwrappedTileID, getSymbolElevation(getElevation, p.x, p.y, heightOffset, heightAnchorGround), simpleProjectionMatrix));
7474
7786
  anyPointVisible = projected.some((p) => !p.isOccluded);
7475
7787
  points = projected.map((p) => new Point(p.x, p.y));
7476
7788
  } else anyPointVisible = true;
@@ -7568,7 +7880,7 @@ var Placement = class {
7568
7880
  }
7569
7881
  _getTerrainElevationFunc(tileID) {
7570
7882
  const terrain = this.terrain;
7571
- if (!terrain) return null;
7883
+ if (!terrain) return void 0;
7572
7884
  return (x, y) => terrain.getElevation(tileID, x, y);
7573
7885
  }
7574
7886
  getBucketParts(results, styleLayer, tile, sortAcrossTiles) {
@@ -7616,13 +7928,13 @@ var Placement = class {
7616
7928
  parameters
7617
7929
  });
7618
7930
  }
7619
- attemptAnchorPlacement(textAnchorOffset, textBox, width, height, textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, tileID, unwrappedTileID, collisionGroup, textOverlapMode, symbolInstance, bucket, orientation, translationText, translationIcon, iconBox, getElevation, simpleProjectionMatrix) {
7931
+ attemptAnchorPlacement(textAnchorOffset, textBox, width, height, textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, tileID, unwrappedTileID, collisionGroup, textOverlapMode, symbolInstance, bucket, orientation, translationText, translationIcon, iconBox, getElevation, simpleProjectionMatrix, heightOffset, heightAnchorGround) {
7620
7932
  const anchor = TextAnchorEnum[textAnchorOffset.textAnchor];
7621
7933
  const textOffset = [textAnchorOffset.textOffset0, textAnchorOffset.textOffset1];
7622
7934
  const shift = calculateVariableLayoutShift(anchor, width, height, textOffset, textBoxScale);
7623
- const placedGlyphBoxes = this.collisionIndex.placeCollisionBox(textBox, textOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationText, collisionGroup.predicate, getElevation, shift, simpleProjectionMatrix);
7935
+ const placedGlyphBoxes = this.collisionIndex.placeCollisionBox(textBox, textOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationText, collisionGroup.predicate, getElevation, shift, simpleProjectionMatrix, heightOffset, heightAnchorGround);
7624
7936
  if (iconBox) {
7625
- if (!this.collisionIndex.placeCollisionBox(iconBox, textOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationIcon, collisionGroup.predicate, getElevation, shift, simpleProjectionMatrix).placeable) return;
7937
+ if (!this.collisionIndex.placeCollisionBox(iconBox, textOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationIcon, collisionGroup.predicate, getElevation, shift, simpleProjectionMatrix, heightOffset, heightAnchorGround).placeable) return;
7626
7938
  }
7627
7939
  if (placedGlyphBoxes.placeable) {
7628
7940
  let prevAnchor;
@@ -7659,6 +7971,7 @@ var Placement = class {
7659
7971
  const pitchWithMap = layout.get("text-pitch-alignment") === "map";
7660
7972
  const hasIconTextFit = layout.get("icon-text-fit") !== "none";
7661
7973
  const zOrderByViewportY = layout.get("symbol-z-order") === "viewport-y";
7974
+ const heightAnchorGround = layout.get("symbol-height-anchor") === "ground";
7662
7975
  const alwaysShowText = textAlwaysOverlap && (iconAlwaysOverlap || !bucket.hasIconData() || iconOptional);
7663
7976
  const alwaysShowIcon = iconAlwaysOverlap && (textAlwaysOverlap || !bucket.hasTextData() || textOptional);
7664
7977
  if (!bucket.collisionArrays && collisionBoxArray) bucket.deserializeCollisionBoxes(collisionBoxArray);
@@ -7671,6 +7984,7 @@ var Placement = class {
7671
7984
  this.placements[symbolInstance.crossTileID] = new JointPlacement(false, false, false);
7672
7985
  return;
7673
7986
  }
7987
+ const symbolHeightOffset = symbolInstance.heightOffset;
7674
7988
  let placeText = false;
7675
7989
  let placeIcon = false;
7676
7990
  let offscreen = true;
@@ -7723,7 +8037,7 @@ var Placement = class {
7723
8037
  const textAnchorOffsetEnd = symbolInstance.textAnchorOffsetEndIndex;
7724
8038
  if (textAnchorOffsetEnd === textAnchorOffsetStart) {
7725
8039
  const placeBox = (collisionTextBox, orientation) => {
7726
- const placedFeature = this.collisionIndex.placeCollisionBox(collisionTextBox, textOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationText, collisionGroup.predicate, getElevation, void 0, simpleProjectionMatrix);
8040
+ const placedFeature = this.collisionIndex.placeCollisionBox(collisionTextBox, textOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationText, collisionGroup.predicate, getElevation, void 0, simpleProjectionMatrix, symbolHeightOffset, heightAnchorGround);
7727
8041
  if (placedFeature?.placeable) {
7728
8042
  this.markUsedOrientation(bucket, orientation, symbolInstance);
7729
8043
  this.placedOrientations[symbolInstance.crossTileID] = orientation;
@@ -7758,7 +8072,7 @@ var Placement = class {
7758
8072
  for (let i = textAnchorOffsetStart; i < textAnchorOffsetEnd; i++) {
7759
8073
  const textAnchorOffset = bucket.textAnchorOffsets.get(i);
7760
8074
  if (prevAnchor && textAnchorOffset.textAnchor !== prevAnchor) continue;
7761
- const result = this.attemptAnchorPlacement(textAnchorOffset, collisionTextBox, width, height, textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, tileID, unwrappedTileID, collisionGroup, overlapMode, symbolInstance, bucket, orientation, translationText, translationIcon, variableIconBox, getElevation);
8075
+ const result = this.attemptAnchorPlacement(textAnchorOffset, collisionTextBox, width, height, textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, tileID, unwrappedTileID, collisionGroup, overlapMode, symbolInstance, bucket, orientation, translationText, translationIcon, variableIconBox, getElevation, simpleProjectionMatrix, symbolHeightOffset, heightAnchorGround);
7762
8076
  if (result) {
7763
8077
  placedBox = result.placedGlyphBoxes;
7764
8078
  if (placedBox?.placeable) {
@@ -7772,7 +8086,7 @@ var Placement = class {
7772
8086
  else overlapMode = textOverlapMode;
7773
8087
  }
7774
8088
  if (showCollisionBoxes && !placedBox) placedBox = {
7775
- box: this.collisionIndex.placeCollisionBox(textBox, "always", textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationText, collisionGroup.predicate, getElevation, void 0, simpleProjectionMatrix).box,
8089
+ box: this.collisionIndex.placeCollisionBox(textBox, "always", textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationText, collisionGroup.predicate, getElevation, void 0, simpleProjectionMatrix, symbolHeightOffset, heightAnchorGround).box,
7776
8090
  offscreen: false,
7777
8091
  placeable: false,
7778
8092
  occluded: false
@@ -7823,7 +8137,7 @@ var Placement = class {
7823
8137
  if (collisionArrays.iconFeatureIndex) iconFeatureIndex = collisionArrays.iconFeatureIndex;
7824
8138
  if (collisionArrays.iconBox) {
7825
8139
  const placeIconFeature = (iconBox) => {
7826
- return this.collisionIndex.placeCollisionBox(iconBox, iconOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationIcon, collisionGroup.predicate, getElevation, hasIconTextFit && shift ? shift : void 0, simpleProjectionMatrix);
8140
+ return this.collisionIndex.placeCollisionBox(iconBox, iconOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationIcon, collisionGroup.predicate, getElevation, hasIconTextFit && shift ? shift : void 0, simpleProjectionMatrix, symbolHeightOffset, heightAnchorGround);
7827
8141
  };
7828
8142
  if (placedVerticalText && placedVerticalText.placeable && collisionArrays.verticalIconBox) {
7829
8143
  placedIconBoxes = placeIconFeature(collisionArrays.verticalIconBox);
@@ -8778,12 +9092,11 @@ const shaders = {
8778
9092
  lineGradientSDF: prepare("uniform lowp float u_device_pixel_ratio;uniform sampler2D u_image;uniform sampler2D u_image_dash;uniform float u_mix;uniform lowp float u_lineatlas_width;in vec2 v_normal;flat in vec2 v_width2;in vec2 v_tex_a;in vec2 v_tex_b;in float v_gamma_scale;in highp vec2 v_uv;\n#ifdef GLOBE\nin float v_depth;\n#endif\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define mediump float width\n#pragma maplibre: define lowp float floorwidth\n#pragma maplibre: define mediump vec4 dasharray_from\n#pragma maplibre: define mediump vec4 dasharray_to\nvoid main() {\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump float width\n#pragma maplibre: initialize lowp float floorwidth\n#pragma maplibre: initialize mediump vec4 dasharray_from\n#pragma maplibre: initialize mediump vec4 dasharray_to\nclipAntimeridian();float dist=length(v_normal)*v_width2.s;float blur2=(blur+1.0/u_device_pixel_ratio)*v_gamma_scale;float alpha=clamp(min(dist-(v_width2.t-blur2),v_width2.s-dist)/blur2,0.0,1.0);vec4 color=texture(u_image,v_uv);float sdfdist_a=texture(u_image_dash,v_tex_a).a;float sdfdist_b=texture(u_image_dash,v_tex_b).a;float sdfdist=mix(sdfdist_a,sdfdist_b,u_mix);float sdfgamma=(u_lineatlas_width/256.0)/min(dasharray_from.w,dasharray_to.w);float dash_alpha=smoothstep(0.5-sdfgamma/floorwidth,0.5+sdfgamma/floorwidth,sdfdist);fragColor=color*(alpha*dash_alpha*opacity);\n#ifdef GLOBE\nif (v_depth > 1.0) {discard;}\n#endif\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "\n#define scale 0.015873016\n#define LINE_DISTANCE_SCALE 2.0\nlayout(location=0) in ivec2 a_pos_normal;layout(location=1) in uvec4 a_data;layout(location=2) in float a_uv_x;layout(location=3) in float a_split_index;uniform vec2 u_translation;uniform mediump float u_ratio;uniform lowp float u_device_pixel_ratio;uniform vec2 u_units_to_pixels;uniform float u_image_height;uniform float u_tileratio;uniform float u_crossfade_from;uniform float u_crossfade_to;uniform float u_lineatlas_height;out vec2 v_normal;flat out vec2 v_width2;out float v_gamma_scale;out highp vec2 v_uv;out vec2 v_tex_a;out vec2 v_tex_b;\n#ifdef GLOBE\nout float v_depth;\n#endif\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define mediump float gapwidth\n#pragma maplibre: define lowp float offset\n#pragma maplibre: define mediump float width\n#pragma maplibre: define lowp float floorwidth\n#pragma maplibre: define mediump vec4 dasharray_from\n#pragma maplibre: define mediump vec4 dasharray_to\nvoid main() {\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump float gapwidth\n#pragma maplibre: initialize lowp float offset\n#pragma maplibre: initialize mediump float width\n#pragma maplibre: initialize lowp float floorwidth\n#pragma maplibre: initialize mediump vec4 dasharray_from\n#pragma maplibre: initialize mediump vec4 dasharray_to\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=vec2(ivec2(a_data.xy)-128);float a_direction=float(int(a_data.z & 3u)-1);float a_linesofar=float((a_data.z >> 2u)+a_data.w*64u)*LINE_DISTANCE_SCALE;float texel_height=1.0/u_image_height;float half_texel_height=0.5*texel_height;v_uv=vec2(a_uv_x,a_split_index*texel_height-half_texel_height);vec2 pos=vec2(a_pos_normal >> 1);mediump vec2 normal=vec2(a_pos_normal & 1);normal.y=normal.y*2.0-1.0;v_normal=normal;gapwidth=gapwidth/2.0;float halfwidth=width/2.0;offset=-1.0*offset;float inset=gapwidth+(gapwidth > 0.0 ? ANTIALIASING : 0.0);float outset=gapwidth+halfwidth*(gapwidth > 0.0 ? 2.0 : 1.0)+(halfwidth==0.0 ? 0.0 : ANTIALIASING);mediump vec2 dist=outset*a_extrude*scale;mediump float u=0.5*a_direction;mediump float t=1.0-abs(u);mediump vec2 offset2=offset*a_extrude*scale*normal.y*mat2(t,-u,u,t);float adjustedThickness=projectLineThickness(pos.y);vec4 projected_no_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation);vec4 projected_with_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation+dist/u_ratio*adjustedThickness);gl_Position=projected_with_extrude;\n#ifdef GLOBE\nv_depth=gl_Position.z/gl_Position.w;\n#endif\n#ifdef TERRAIN3D\nv_gamma_scale=1.0;\n#else\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length((projected_with_extrude.xy-projected_no_extrude.xy)/projected_with_extrude.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#endif\nfloat u_patternscale_a_x=u_tileratio/dasharray_from.w/u_crossfade_from;float u_patternscale_a_y=-dasharray_from.z/2.0/u_lineatlas_height;float u_patternscale_b_x=u_tileratio/dasharray_to.w/u_crossfade_to;float u_patternscale_b_y=-dasharray_to.z/2.0/u_lineatlas_height;v_tex_a=vec2(a_linesofar*u_patternscale_a_x/floorwidth,normal.y*u_patternscale_a_y+(float(dasharray_from.y)+0.5)/u_lineatlas_height);v_tex_b=vec2(a_linesofar*u_patternscale_b_x/floorwidth,normal.y*u_patternscale_b_y+(float(dasharray_to.y)+0.5)/u_lineatlas_height);v_width2=vec2(outset,inset);}"),
8779
9093
  layerOpacity: prepare("uniform sampler2D u_image;uniform float u_opacity;in vec2 v_pos;void main() {fragColor=texture(u_image,v_pos)*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(0.0);\n#endif\n}", "layout(location=0) in vec2 a_pos;out vec2 v_pos;void main() {gl_Position=vec4(a_pos.x*2.0-1.0,1.0-a_pos.y*2.0,0.0,1.0);v_pos.x=a_pos.x;v_pos.y=1.0-a_pos.y;}"),
8780
9094
  raster: prepare("uniform float u_fade_t;uniform float u_opacity;uniform sampler2D u_image0;uniform sampler2D u_image1;in vec3 v_pos0;in vec3 v_pos1;uniform float u_brightness_low;uniform float u_brightness_high;uniform float u_saturation_factor;uniform float u_contrast_factor;uniform vec3 u_spin_weights;void main() {vec4 color0=texture(u_image0,v_pos0.xy/v_pos0.z);vec4 color1=texture(u_image1,v_pos1.xy/v_pos1.z);if (color0.a > 0.0) {color0.rgb=color0.rgb/color0.a;}if (color1.a > 0.0) {color1.rgb=color1.rgb/color1.a;}vec4 color=mix(color0,color1,u_fade_t);color.a*=u_opacity;vec3 rgb=color.rgb;rgb=vec3(dot(rgb,u_spin_weights.xyz),dot(rgb,u_spin_weights.zxy),dot(rgb,u_spin_weights.yzx));float average=(color.r+color.g+color.b)/3.0;rgb+=(average-rgb)*u_saturation_factor;rgb=(rgb-0.5)*u_contrast_factor+0.5;vec3 u_high_vec=vec3(u_brightness_low,u_brightness_low,u_brightness_low);vec3 u_low_vec=vec3(u_brightness_high,u_brightness_high,u_brightness_high);fragColor=vec4(mix(u_high_vec,u_low_vec,rgb)*color.a,color.a);\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_tl_parent;uniform float u_scale_parent;uniform float u_buffer_scale;uniform vec3 u_image_warp;uniform vec4 u_coords_top;uniform vec4 u_coords_bottom;layout(location=0) in vec2 a_pos;out vec3 v_pos0;out vec3 v_pos1;void main() {vec2 fractionalPos=a_pos/8192.0;vec2 topLeft=u_coords_top.xy;vec2 topRight=u_coords_top.zw;vec2 bottomLeft=u_coords_bottom.xy;vec2 bottomRight=u_coords_bottom.zw;vec2 bilinearPos=mix(mix(topLeft,topRight,fractionalPos.x),mix(bottomLeft,bottomRight,fractionalPos.x),fractionalPos.y);float denominator=dot(u_image_warp.xy,fractionalPos)+1.0;vec2 acrossTop=topRight-topLeft+u_image_warp.x*topRight;vec2 downLeft=bottomLeft-topLeft+u_image_warp.y*bottomLeft;vec2 projectivePos=(acrossTop*fractionalPos.x+downLeft*fractionalPos.y+topLeft)/denominator;vec2 position=mix(projectivePos,bilinearPos,u_image_warp.z);gl_Position=projectTile(position,position);vec2 texturePos=((fractionalPos-0.5)/u_buffer_scale)+0.5;\n#ifdef GLOBE\nif (a_pos.y <-32767.5) {texturePos.y=0.0;}if (a_pos.y > 32766.5) {texturePos.y=1.0;}\n#endif\nfloat perspectiveRatio=mix(1.0/denominator,1.0,u_image_warp.z);v_pos0=vec3(texturePos*perspectiveRatio,perspectiveRatio);vec2 parentPos=(texturePos*u_scale_parent)+u_tl_parent;v_pos1=vec3(parentPos*perspectiveRatio,perspectiveRatio);}"),
8781
- symbolIcon: prepare("uniform sampler2D u_texture;in vec2 v_tex;flat in float v_total_opacity;void main() {fragColor=texture(u_texture,v_tex)*v_total_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "layout(location=0) in vec4 a_pos_offset;layout(location=1) in uvec4 a_data;layout(location=2) in vec4 a_pixeloffset;layout(location=3) in vec3 a_projected_pos;layout(location=4) in uint 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 highp float u_camera_to_center_distance;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform float u_fade_change;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform vec2 u_texsize;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform vec2 u_translation;uniform float u_pitched_scale;uniform bool u_is_offset;out vec2 v_tex;flat out float v_total_opacity;\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize lowp float opacity\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=vec2(a_data.xy);vec2 a_size=vec2(a_data.zw);float a_size_min=float(a_data.z >> 1u);vec2 a_pxoffset=a_pixeloffset.xy;vec2 a_minFontScale=a_pixeloffset.zw/256.0;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;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float visibility=calculate_visibility(projectedPoint);v_total_opacity=opacity*max(0.0,min(visibility,fade_opacity[0]+fade_change));if (v_total_opacity < 0.1){gl_Position=vec4(-2.,-2.,-2.,1.);return;}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);if (!u_is_offset) {size*=perspective_ratio;}float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);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;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0;\n#ifdef GLOBE\nif(u_pitch_with_map) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);}\n#endif\nvec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*max(a_minFontScale,fontScale)+a_pxoffset/16.0)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}gl_Position=finalPos;v_tex=a_tex/u_texsize;}"),
8782
- symbolSDF: prepare("#define SDF_PX 8.0\nuniform bool u_is_halo;uniform bool u_is_plain;uniform sampler2D u_texture;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;uniform bool u_is_text;in vec2 v_data0;in vec3 v_data1;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nfloat EDGE_GAMMA=0.105/u_device_pixel_ratio;vec2 tex=v_data0.xy;float gamma_scale=v_data1.x;float size=v_data1.y;float total_opacity=v_data1[2];float fontScale=u_is_text ? size/24.0 : size;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float inner_edge=(256.0-64.0)/256.0;lowp float dist=texture(u_texture,tex).a;lowp vec4 color_alpha_out_text,color_alpha_out_halo;if (u_is_plain){highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(inner_edge-gamma_scaled,inner_edge+gamma_scaled,dist);color_alpha_out_text=total_opacity*alpha*fill_color;}if (u_is_halo) {float gamma_halo=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);float inner_edge_halo=inner_edge+gamma_halo*gamma_scale;highp float gamma_scaled_halo=gamma_halo*gamma_scale;highp float alpha_halo=smoothstep(inner_edge_halo-gamma_scaled_halo,inner_edge_halo+gamma_scaled_halo,dist);highp float halo_edge=(6.0-halo_width/fontScale)/SDF_PX;alpha_halo= min(smoothstep(halo_edge-gamma_scaled_halo,halo_edge+gamma_scaled_halo,dist),1.0-alpha_halo);color_alpha_out_halo=total_opacity*alpha_halo*halo_color;}if (u_is_plain && u_is_halo) {fragColor=color_alpha_out_text+(1.-color_alpha_out_text.a)*color_alpha_out_halo;} else if (u_is_halo){fragColor=color_alpha_out_halo;} else {fragColor=color_alpha_out_text;}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "layout(location=0) in vec4 a_pos_offset;layout(location=1) in uvec4 a_data;layout(location=2) in vec4 a_pixeloffset;layout(location=3) in vec3 a_projected_pos;layout(location=4) in uint 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_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;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_translation;uniform float u_pitched_scale;uniform bool u_is_offset;out vec2 v_data0;out vec3 v_data1;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=vec2(a_data.xy);vec2 a_size=vec2(a_data.zw);float a_size_min=float(a_data.z >> 1u);vec2 a_pxoffset=a_pixeloffset.xy/16.0;vec2 a_minFontScale=a_pixeloffset.zw/256.0;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;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);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));float total_opacity=opacity*interpolated_fade_opacity;if (total_opacity < 0.1){gl_Position=vec4(-2.,-2.,-2.,1.);return;}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);if (!u_is_offset) {size*=perspective_ratio;}float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);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;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0;\n#ifdef GLOBE\nif(u_pitch_with_map) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);}\n#endif\nvec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*max(a_minFontScale,fontScale)+a_pxoffset)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}float gamma_scale=finalPos.w;gl_Position=finalPos;v_data0=a_tex/u_texsize;v_data1=vec3(gamma_scale,size,total_opacity);}"),
8783
- symbolTextAndIcon: prepare("#define SDF_PX 8.0\n#define SDF 1.0\n#define ICON 0.0\nuniform bool u_is_halo;uniform bool u_is_text;uniform sampler2D u_texture;uniform sampler2D u_texture_icon;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;in vec4 v_data0;in vec3 v_data1;flat in float v_is_sdf;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nfloat total_opacity=v_data1[2];if (v_is_sdf==ICON) {vec2 tex_icon=v_data0.zw;fragColor=texture(u_texture_icon,tex_icon)*total_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\nreturn;}vec2 tex=v_data0.xy;float EDGE_GAMMA=0.105/u_device_pixel_ratio;float gamma_scale=v_data1.x;float size=v_data1.y;float fontScale=size/24.0;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float buff=(256.0-64.0)/256.0;lowp float dist=texture(u_texture,tex).a;lowp vec4 color_alpha_out,color_alpha_out_halo;if (u_is_text) {highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(buff-gamma_scaled,buff+gamma_scaled,dist);color_alpha_out=fill_color*(alpha*total_opacity);}if (u_is_halo) {highp float gamma_halo=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);lowp float buff_halo=(6.0-halo_width/fontScale)/SDF_PX;highp float gamma_scaled_halo=gamma_halo*gamma_scale;highp float alpha_halo=smoothstep(buff_halo-gamma_scaled_halo,buff_halo+gamma_scaled_halo,dist);color_alpha_out_halo=halo_color*(alpha_halo*total_opacity);}if (u_is_text && u_is_halo) {fragColor=color_alpha_out+(1.-color_alpha_out.a)*color_alpha_out_halo;} else if (u_is_halo) {fragColor=color_alpha_out_halo;} else {fragColor=color_alpha_out;}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "layout(location=0) in vec4 a_pos_offset;layout(location=1) in uvec4 a_data;layout(location=2) in vec3 a_projected_pos;layout(location=3) in uint 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_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;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform vec2 u_translation;uniform float u_pitched_scale;uniform bool u_is_offset;out vec4 v_data0;out vec3 v_data1;flat out float v_is_sdf;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=vec2(a_data.xy);vec2 a_size=vec2(a_data.zw);float a_size_min=float(a_data.z >> 1u);float is_sdf=float(a_data.z & 1u);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;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);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));float total_opacity=opacity*interpolated_fade_opacity;if (total_opacity < 0.1){gl_Position=vec4(-2.,-2.,-2.,1.);return;}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);if (!u_is_offset) {size*=perspective_ratio;}float fontScale=size/24.0;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);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;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0;\n#ifdef GLOBE\nif(u_pitch_with_map && !u_is_along_line) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);}\n#endif\nvec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*fontScale)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}float gamma_scale=finalPos.w;gl_Position=finalPos;v_data0.xy=a_tex/u_texsize;v_data0.zw=a_tex/u_texsize_icon;v_data1=vec3(gamma_scale,size,total_opacity);v_is_sdf=is_sdf;}"),
9095
+ symbolIcon: prepare("uniform sampler2D u_texture;in vec2 v_tex;flat in float v_total_opacity;void main() {fragColor=texture(u_texture,v_tex)*v_total_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "layout(location=0) in vec4 a_pos_offset;layout(location=1) in uvec4 a_data;layout(location=2) in vec4 a_pixeloffset;layout(location=3) in vec3 a_projected_pos;layout(location=4) in uint a_fade_opacity;layout(location=5) in float a_height_offset;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 highp float u_camera_to_center_distance;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform float u_fade_change;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform vec2 u_texsize;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform vec2 u_translation;uniform float u_pitched_scale;uniform bool u_is_offset;uniform bool u_height_anchor_ground;out vec2 v_tex;flat out float v_total_opacity;\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize lowp float opacity\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=vec2(a_data.xy);vec2 a_size=vec2(a_data.zw);float a_size_min=float(a_data.z >> 1u);vec2 a_pxoffset=a_pixeloffset.xy;vec2 a_minFontScale=a_pixeloffset.zw/256.0;float ele=a_height_offset+(u_height_anchor_ground ? get_elevation(a_pos) : 0.0);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;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float visibility=calculate_visibility(projectedPoint);v_total_opacity=opacity*max(0.0,min(visibility,fade_opacity[0]+fade_change));if (v_total_opacity < 0.1){gl_Position=vec4(-2.,-2.,-2.,1.);return;}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);if (!u_is_offset) {size*=perspective_ratio;}float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);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;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0;\n#ifdef GLOBE\nif(u_pitch_with_map) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);}\n#endif\nvec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*max(a_minFontScale,fontScale)+a_pxoffset/16.0)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}gl_Position=finalPos;v_tex=a_tex/u_texsize;}"),
9096
+ symbolSDF: prepare("#define SDF_PX 8.0\nuniform bool u_is_halo;uniform bool u_is_plain;uniform sampler2D u_texture;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;uniform bool u_is_text;in vec2 v_data0;in vec3 v_data1;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nfloat EDGE_GAMMA=0.105/u_device_pixel_ratio;vec2 tex=v_data0.xy;float gamma_scale=v_data1.x;float size=v_data1.y;float total_opacity=v_data1[2];float fontScale=u_is_text ? size/24.0 : size;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float inner_edge=(256.0-64.0)/256.0;lowp float dist=texture(u_texture,tex).a;lowp vec4 color_alpha_out_text,color_alpha_out_halo;if (u_is_plain){highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(inner_edge-gamma_scaled,inner_edge+gamma_scaled,dist);color_alpha_out_text=total_opacity*alpha*fill_color;}if (u_is_halo) {float gamma_halo=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);float inner_edge_halo=inner_edge+gamma_halo*gamma_scale;highp float gamma_scaled_halo=gamma_halo*gamma_scale;highp float alpha_halo=smoothstep(inner_edge_halo-gamma_scaled_halo,inner_edge_halo+gamma_scaled_halo,dist);highp float halo_edge=(6.0-halo_width/fontScale)/SDF_PX;alpha_halo= min(smoothstep(halo_edge-gamma_scaled_halo,halo_edge+gamma_scaled_halo,dist),1.0-alpha_halo);color_alpha_out_halo=total_opacity*alpha_halo*halo_color;}if (u_is_plain && u_is_halo) {fragColor=color_alpha_out_text+(1.-color_alpha_out_text.a)*color_alpha_out_halo;} else if (u_is_halo){fragColor=color_alpha_out_halo;} else {fragColor=color_alpha_out_text;}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "layout(location=0) in vec4 a_pos_offset;layout(location=1) in uvec4 a_data;layout(location=2) in vec4 a_pixeloffset;layout(location=3) in vec3 a_projected_pos;layout(location=4) in uint a_fade_opacity;layout(location=5) in float a_height_offset;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_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;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_translation;uniform float u_pitched_scale;uniform bool u_is_offset;uniform bool u_height_anchor_ground;out vec2 v_data0;out vec3 v_data1;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=vec2(a_data.xy);vec2 a_size=vec2(a_data.zw);float a_size_min=float(a_data.z >> 1u);vec2 a_pxoffset=a_pixeloffset.xy/16.0;vec2 a_minFontScale=a_pixeloffset.zw/256.0;float ele=a_height_offset+(u_height_anchor_ground ? get_elevation(a_pos) : 0.0);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;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);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));float total_opacity=opacity*interpolated_fade_opacity;if (total_opacity < 0.1){gl_Position=vec4(-2.,-2.,-2.,1.);return;}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);if (!u_is_offset) {size*=perspective_ratio;}float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);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;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0;\n#ifdef GLOBE\nif(u_pitch_with_map) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);}\n#endif\nvec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*max(a_minFontScale,fontScale)+a_pxoffset)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}float gamma_scale=finalPos.w;gl_Position=finalPos;v_data0=a_tex/u_texsize;v_data1=vec3(gamma_scale,size,total_opacity);}"),
9097
+ symbolTextAndIcon: prepare("#define SDF_PX 8.0\n#define SDF 1.0\n#define ICON 0.0\nuniform bool u_is_halo;uniform bool u_is_text;uniform sampler2D u_texture;uniform sampler2D u_texture_icon;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;in vec4 v_data0;in vec3 v_data1;flat in float v_is_sdf;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nfloat total_opacity=v_data1[2];if (v_is_sdf==ICON) {vec2 tex_icon=v_data0.zw;fragColor=texture(u_texture_icon,tex_icon)*total_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\nreturn;}vec2 tex=v_data0.xy;float EDGE_GAMMA=0.105/u_device_pixel_ratio;float gamma_scale=v_data1.x;float size=v_data1.y;float fontScale=size/24.0;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float buff=(256.0-64.0)/256.0;lowp float dist=texture(u_texture,tex).a;lowp vec4 color_alpha_out,color_alpha_out_halo;if (u_is_text) {highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(buff-gamma_scaled,buff+gamma_scaled,dist);color_alpha_out=fill_color*(alpha*total_opacity);}if (u_is_halo) {highp float gamma_halo=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);lowp float buff_halo=(6.0-halo_width/fontScale)/SDF_PX;highp float gamma_scaled_halo=gamma_halo*gamma_scale;highp float alpha_halo=smoothstep(buff_halo-gamma_scaled_halo,buff_halo+gamma_scaled_halo,dist);color_alpha_out_halo=halo_color*(alpha_halo*total_opacity);}if (u_is_text && u_is_halo) {fragColor=color_alpha_out+(1.-color_alpha_out.a)*color_alpha_out_halo;} else if (u_is_halo) {fragColor=color_alpha_out_halo;} else {fragColor=color_alpha_out;}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "layout(location=0) in vec4 a_pos_offset;layout(location=1) in uvec4 a_data;layout(location=2) in vec3 a_projected_pos;layout(location=3) in uint a_fade_opacity;layout(location=4) in float a_height_offset;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_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;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform vec2 u_translation;uniform float u_pitched_scale;uniform bool u_is_offset;uniform bool u_height_anchor_ground;out vec4 v_data0;out vec3 v_data1;flat out float v_is_sdf;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=vec2(a_data.xy);vec2 a_size=vec2(a_data.zw);float a_size_min=float(a_data.z >> 1u);float is_sdf=float(a_data.z & 1u);float ele=a_height_offset+(u_height_anchor_ground ? get_elevation(a_pos) : 0.0);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;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);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));float total_opacity=opacity*interpolated_fade_opacity;if (total_opacity < 0.1){gl_Position=vec4(-2.,-2.,-2.,1.);return;}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);if (!u_is_offset) {size*=perspective_ratio;}float fontScale=size/24.0;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);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;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0;\n#ifdef GLOBE\nif(u_pitch_with_map && !u_is_along_line) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);}\n#endif\nvec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*fontScale)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}float gamma_scale=finalPos.w;gl_Position=finalPos;v_data0.xy=a_tex/u_texsize;v_data0.zw=a_tex/u_texsize_icon;v_data1=vec3(gamma_scale,size,total_opacity);v_is_sdf=is_sdf;}"),
8784
9098
  terrain: prepare("uniform sampler2D u_texture;uniform vec4 u_fog_color;uniform vec4 u_horizon_color;uniform float u_fog_ground_blend;uniform float u_fog_ground_blend_opacity;uniform float u_horizon_fog_blend;uniform bool u_is_globe_mode;in vec2 v_texture_pos;in float v_fog_depth;const float gamma=2.2;vec4 gammaToLinear(vec4 color) {return pow(color,vec4(gamma));}vec4 linearToGamma(vec4 color) {return pow(color,vec4(1.0/gamma));}void main() {vec4 surface_color=texture(u_texture,vec2(v_texture_pos.x,1.0-v_texture_pos.y));if (!u_is_globe_mode && u_fog_ground_blend_opacity > 0.0 && v_fog_depth > u_fog_ground_blend) {vec4 surface_color_linear=gammaToLinear(surface_color);float blend_color=smoothstep(0.0,1.0,max((v_fog_depth-u_horizon_fog_blend)/(1.0-u_horizon_fog_blend),0.0));vec4 fog_horizon_color_linear=mix(gammaToLinear(u_fog_color),gammaToLinear(u_horizon_color),blend_color);float factor_fog=max(v_fog_depth-u_fog_ground_blend,0.0)/(1.0-u_fog_ground_blend);fragColor=linearToGamma(mix(surface_color_linear,fog_horizon_color_linear,pow(factor_fog,2.0)*u_fog_ground_blend_opacity));} else {fragColor=surface_color;}}", "layout(location=0) in vec3 a_pos3d;uniform mat4 u_fog_matrix;uniform float u_ele_delta;out vec2 v_texture_pos;out float v_fog_depth;void main() {float ele=get_elevation(a_pos3d.xy);float ele_delta=a_pos3d.z==1.0 ? u_ele_delta : 0.0;v_texture_pos=a_pos3d.xy/8192.0;gl_Position=projectTileFor3D(a_pos3d.xy,ele-ele_delta);vec4 pos=u_fog_matrix*vec4(a_pos3d.xy,ele,1.0);v_fog_depth=pos.z/pos.w*0.5+0.5;}"),
8785
9099
  terrainDepth: prepare("in 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() {fragColor=pack(v_depth);}", "layout(location=0) in vec3 a_pos3d;uniform float u_ele_delta;out float v_depth;void main() {float ele=get_elevation(a_pos3d.xy);float ele_delta=a_pos3d.z==1.0 ? u_ele_delta : 0.0;gl_Position=projectTileFor3D(a_pos3d.xy,ele-ele_delta);v_depth=gl_Position.z/gl_Position.w;}"),
8786
- terrainCoords: prepare("precision mediump float;uniform sampler2D u_texture;uniform float u_terrain_coords_id;in vec2 v_texture_pos;void main() {vec4 rgba=texture(u_texture,v_texture_pos);fragColor=vec4(rgba.r,rgba.g,rgba.b,u_terrain_coords_id);}", "layout(location=0) in vec3 a_pos3d;uniform float u_ele_delta;out vec2 v_texture_pos;void main() {float ele=get_elevation(a_pos3d.xy);float ele_delta=a_pos3d.z==1.0 ? u_ele_delta : 0.0;v_texture_pos=a_pos3d.xy/8192.0;gl_Position=projectTileFor3D(a_pos3d.xy,ele-ele_delta);}"),
8787
9100
  atmosphere: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nin vec3 view_direction;uniform vec3 u_sun_pos;uniform vec3 u_globe_position;uniform float u_globe_radius;uniform float u_atmosphere_blend;/**Shader use from https:*Made some change to adapt to MapLibre Globe geometry*/const float PI=3.141592653589793;const int iSteps=5;const int jSteps=3;/*radius of the planet*/const float EARTH_RADIUS=6371e3;/*radius of the atmosphere*/const float ATMOS_RADIUS=6471e3;vec2 rsi(vec3 r0,vec3 rd,float sr) {float a=dot(rd,rd);float b=2.0*dot(rd,r0);float c=dot(r0,r0)-(sr*sr);float d=(b*b)-4.0*a*c;if (d < 0.0) return vec2(1e5,-1e5);return vec2((-b-sqrt(d))/(2.0*a),(-b+sqrt(d))/(2.0*a));}vec4 atmosphere(vec3 r,vec3 r0,vec3 pSun,float iSun,float rPlanet,float rAtmos,vec3 kRlh,float kMie,float shRlh,float shMie,float g) {pSun=normalize(pSun);r=normalize(r);vec2 p=rsi(r0,r,rAtmos);if (p.x > p.y) {return vec4(0.0,0.0,0.0,1.0);}if (p.x < 0.0) {p.x=0.0;}vec3 pos=r0+r*p.x;vec2 p2=rsi(r0,r,rPlanet);if (p2.x <=p2.y && p2.x > 0.0) {p.y=min(p.y,p2.x);}float iStepSize=(p.y-p.x)/float(iSteps);float iTime=p.x+iStepSize*0.5;vec3 totalRlh=vec3(0,0,0);vec3 totalMie=vec3(0,0,0);float iOdRlh=0.0;float iOdMie=0.0;float mu=dot(r,pSun);float mumu=mu*mu;float gg=g*g;float pRlh=3.0/(16.0*PI)*(1.0+mumu);float pMie=3.0/(8.0*PI)*((1.0-gg)*(mumu+1.0))/(pow(1.0+gg-2.0*mu*g,1.5)*(2.0+gg));for (int i=0; i < iSteps; i++) {vec3 iPos=r0+r*iTime;float iHeight=length(iPos)-rPlanet;float odStepRlh=exp(-iHeight/shRlh)*iStepSize;float odStepMie=exp(-iHeight/shMie)*iStepSize;iOdRlh+=odStepRlh;iOdMie+=odStepMie;float jStepSize=rsi(iPos,pSun,rAtmos).y/float(jSteps);float jTime=jStepSize*0.5;float jOdRlh=0.0;float jOdMie=0.0;for (int j=0; j < jSteps; j++) {vec3 jPos=iPos+pSun*jTime;float jHeight=length(jPos)-rPlanet;jOdRlh+=exp(-jHeight/shRlh)*jStepSize;jOdMie+=exp(-jHeight/shMie)*jStepSize;jTime+=jStepSize;}vec3 attn=exp(-(kMie*(iOdMie+jOdMie)+kRlh*(iOdRlh+jOdRlh)));totalRlh+=odStepRlh*attn;totalMie+=odStepMie*attn;iTime+=iStepSize;}float opacity=exp(-(length(kRlh)*length(totalRlh)+kMie*length(totalMie)));vec3 color=iSun*(pRlh*kRlh*totalRlh+pMie*kMie*totalMie);return vec4(color,opacity);}void main() {vec3 scale_camera_pos=-u_globe_position*EARTH_RADIUS/u_globe_radius;vec4 color=atmosphere(normalize(view_direction),scale_camera_pos,u_sun_pos,22.0,EARTH_RADIUS,ATMOS_RADIUS,vec3(5.5e-6,13.0e-6,22.4e-6),21e-6,8e3,1.2e3,0.758\n);color.rgb=1.0-exp(-1.0*color.rgb);color=pow(color,vec4(1.0/2.2));fragColor=vec4(color.rgb,1.0-color.a)*u_atmosphere_blend;}", "layout(location=0) in vec2 a_pos;uniform mat4 u_inv_proj_matrix;out vec3 view_direction;void main() {view_direction=(u_inv_proj_matrix*vec4(a_pos,0.0,1.0)).xyz;gl_Position=vec4(a_pos,0.0,1.0);}"),
8788
9101
  sky: prepare("uniform vec4 u_sky_color;uniform vec4 u_horizon_color;uniform vec2 u_horizon;uniform vec2 u_horizon_normal;uniform float u_sky_horizon_blend;uniform float u_sky_blend;void main() {float x=gl_FragCoord.x;float y=gl_FragCoord.y;float blend=(y-u_horizon.y)*u_horizon_normal.y+(x-u_horizon.x)*u_horizon_normal.x;if (blend > 0.0) {if (blend < u_sky_horizon_blend) {fragColor=mix(u_sky_color,u_horizon_color,pow(1.0-blend/u_sky_horizon_blend,2.0));} else {fragColor=u_sky_color;}}fragColor=mix(fragColor,vec4(vec3(0.0),0.0),u_sky_blend);}", "layout(location=0) in vec2 a_pos;void main() {gl_Position=vec4(a_pos,1.0,1.0);}")
8789
9102
  };
@@ -9127,7 +9440,9 @@ var TransformHelper = class {
9127
9440
  setMinZoom(zoom) {
9128
9441
  if (this._minZoom === zoom) return;
9129
9442
  this._minZoom = zoom;
9443
+ const unmodified = this._unmodified;
9130
9444
  this.setZoom(this.applyConstrain(this._center, this.zoom).zoom);
9445
+ this._unmodified = unmodified;
9131
9446
  }
9132
9447
  get maxZoom() {
9133
9448
  return this._maxZoom;
@@ -9135,7 +9450,9 @@ var TransformHelper = class {
9135
9450
  setMaxZoom(zoom) {
9136
9451
  if (this._maxZoom === zoom) return;
9137
9452
  this._maxZoom = zoom;
9453
+ const unmodified = this._unmodified;
9138
9454
  this.setZoom(this.applyConstrain(this._center, this.zoom).zoom);
9455
+ this._unmodified = unmodified;
9139
9456
  }
9140
9457
  get minPitch() {
9141
9458
  return this._minPitch;
@@ -9143,7 +9460,9 @@ var TransformHelper = class {
9143
9460
  setMinPitch(pitch) {
9144
9461
  if (this._minPitch === pitch) return;
9145
9462
  this._minPitch = pitch;
9463
+ const unmodified = this._unmodified;
9146
9464
  this.setPitch(Math.max(this.pitch, pitch));
9465
+ this._unmodified = unmodified;
9147
9466
  }
9148
9467
  get maxPitch() {
9149
9468
  return this._maxPitch;
@@ -9151,7 +9470,9 @@ var TransformHelper = class {
9151
9470
  setMaxPitch(pitch) {
9152
9471
  if (this._maxPitch === pitch) return;
9153
9472
  this._maxPitch = pitch;
9473
+ const unmodified = this._unmodified;
9154
9474
  this.setPitch(Math.min(this.pitch, pitch));
9475
+ this._unmodified = unmodified;
9155
9476
  }
9156
9477
  get renderWorldCopies() {
9157
9478
  return this._renderWorldCopies;
@@ -9523,9 +9844,7 @@ var TransformHelper = class {
9523
9844
  return Math.cos(this.pitchInRadians) * this._cameraToCenterDistance / this._pixelPerMeter + this.elevation;
9524
9845
  }
9525
9846
  getCameraLngLat() {
9526
- const pixelPerMeter = mercatorZfromAltitude(1, this.center.lat) * this.worldSize;
9527
- const cameraToCenterDistanceMeters = this.cameraToCenterDistance / pixelPerMeter;
9528
- return cameraMercatorCoordinateFromCenterAndRotation(this.center, this.elevation, this.pitch, this.bearing, cameraToCenterDistanceMeters).toLngLat();
9847
+ return cameraMercatorCoordinate(this).toLngLat();
9529
9848
  }
9530
9849
  getMercatorTileCoordinates(overscaledTileID) {
9531
9850
  if (!overscaledTileID) return [
@@ -9883,118 +10202,857 @@ function getIdealNearFarPlaneDistance(horizonPlane, nearPlaneNormalized) {
9883
10202
  } else return null;
9884
10203
  }
9885
10204
  //#endregion
9886
- //#region src/geo/projection/mercator_transform.ts
9887
- var MercatorTransform = class MercatorTransform {
9888
- get pixelsToClipSpaceMatrix() {
9889
- return this._helper.pixelsToClipSpaceMatrix;
9890
- }
9891
- get clipSpaceToPixelsMatrix() {
9892
- return this._helper.clipSpaceToPixelsMatrix;
9893
- }
9894
- get pixelsToGLUnits() {
9895
- return this._helper.pixelsToGLUnits;
10205
+ //#region src/data/pos3d_attributes.ts
10206
+ const pos3dAttributes = createLayout([{
10207
+ name: "a_pos3d",
10208
+ type: "Int16",
10209
+ components: 3
10210
+ }]);
10211
+ //#endregion
10212
+ //#region src/tile/terrain_tile_manager.ts
10213
+ /**
10214
+ * @internal
10215
+ * This class is a helper for the Terrain-class, it:
10216
+ *
10217
+ * - loads raster-dem tiles
10218
+ * - manages all renderToTexture tiles.
10219
+ * - caches previous rendered tiles.
10220
+ * - finds all necessary renderToTexture tiles for a OverscaledTileID area
10221
+ * - finds the corresponding raster-dem tile for OverscaledTileID
10222
+ */
10223
+ var TerrainTileManager = class extends Evented {
10224
+ constructor(tileManager) {
10225
+ super();
10226
+ this._lastTilesetChange = now();
10227
+ this.tileManager = tileManager;
10228
+ this._tiles = {};
10229
+ this._renderableTilesKeys = [];
10230
+ this._sourceTileCache = {};
10231
+ this.minzoom = 0;
10232
+ this.maxzoom = 22;
10233
+ this.deltaZoom = 1;
10234
+ this.tileSize = tileManager._source.tileSize * 2 ** this.deltaZoom;
10235
+ tileManager.usedForTerrain = true;
10236
+ tileManager.tileSize = this.tileSize;
9896
10237
  }
9897
- get centerOffset() {
9898
- return this._helper.centerOffset;
10238
+ destruct() {
10239
+ this.tileManager.usedForTerrain = false;
10240
+ this.tileManager.tileSize = null;
10241
+ this.releaseAllRTT();
9899
10242
  }
9900
- get size() {
9901
- return this._helper.size;
10243
+ getSource() {
10244
+ return this.tileManager._source;
9902
10245
  }
9903
- get rotationMatrix() {
9904
- return this._helper.rotationMatrix;
10246
+ /**
10247
+ * Load Terrain Tiles, create internal render-to-texture tiles, free GPU memory.
10248
+ * @param transform - the operation to do
10249
+ * @param terrain - the terrain
10250
+ * @returns true when the set of renderable tiles changed
10251
+ */
10252
+ update(transform, terrain) {
10253
+ this.tileManager.update(transform, terrain);
10254
+ this._renderableTilesKeys = [];
10255
+ const keys = {};
10256
+ let changed = false;
10257
+ for (const tileID of coveringTiles(transform, {
10258
+ tileSize: this.tileSize,
10259
+ minzoom: this.minzoom,
10260
+ maxzoom: this.maxzoom,
10261
+ reparseOverscaled: false,
10262
+ terrain,
10263
+ calculateTileZoom: this.tileManager._source.calculateTileZoom
10264
+ })) {
10265
+ keys[tileID.key] = true;
10266
+ this._renderableTilesKeys.push(tileID.key);
10267
+ if (!this._tiles[tileID.key]) {
10268
+ tileID.terrainRttPosMatrix32f = /* @__PURE__ */ new Float32Array(16);
10269
+ ortho(tileID.terrainRttPosMatrix32f, 0, EXTENT, EXTENT, 0, 0, 1);
10270
+ this._tiles[tileID.key] = new Tile(tileID, this.tileSize);
10271
+ this._lastTilesetChange = now();
10272
+ changed = true;
10273
+ }
10274
+ }
10275
+ for (const key in this._tiles) if (!keys[key]) {
10276
+ this._tiles[key].releaseRTT(this.tileManager.map.painter);
10277
+ delete this._tiles[key];
10278
+ changed = true;
10279
+ }
10280
+ return changed;
9905
10281
  }
9906
- get centerPoint() {
9907
- return this._helper.centerPoint;
10282
+ /**
10283
+ * Release the RTT objects for `tileID` (and its ancestors/descendants),
10284
+ */
10285
+ releaseRTT(tileID) {
10286
+ for (const key in this._tiles) {
10287
+ const tile = this._tiles[key];
10288
+ if (tile.tileID.equals(tileID) || tile.tileID.isChildOf(tileID) || tileID.isChildOf(tile.tileID)) tile.releaseRTT(this.tileManager.map.painter);
10289
+ }
9908
10290
  }
9909
- get pixelsPerMeter() {
9910
- return this._helper.pixelsPerMeter;
10291
+ /**
10292
+ * Release the A RTT objects for all tiles.
10293
+ */
10294
+ releaseAllRTT() {
10295
+ for (const key in this._tiles) this._tiles[key].releaseRTT(this.tileManager.map.painter);
9911
10296
  }
9912
- setMinZoom(zoom) {
9913
- this._helper.setMinZoom(zoom);
10297
+ /**
10298
+ * get a list of tiles, which are loaded and should be rendered in the current scene
10299
+ * @returns the renderable tiles
10300
+ */
10301
+ getRenderableTiles() {
10302
+ return this._renderableTilesKeys.map((key) => this.getTileByID(key));
9914
10303
  }
9915
- setMaxZoom(zoom) {
9916
- this._helper.setMaxZoom(zoom);
9917
- }
9918
- setMinPitch(pitch) {
9919
- this._helper.setMinPitch(pitch);
9920
- }
9921
- setMaxPitch(pitch) {
9922
- this._helper.setMaxPitch(pitch);
9923
- }
9924
- setRenderWorldCopies(renderWorldCopies) {
9925
- this._helper.setRenderWorldCopies(renderWorldCopies);
10304
+ /**
10305
+ * get terrain tile by the TileID key
10306
+ * @param id - the tile id
10307
+ * @returns the tile
10308
+ */
10309
+ getTileByID(id) {
10310
+ return this._tiles[id];
9926
10311
  }
9927
- setBearing(bearing) {
9928
- this._helper.setBearing(bearing);
10312
+ /**
10313
+ * Searches for the corresponding current renderable terrain-tiles
10314
+ * @param tileID - the tile to look for
10315
+ * @returns the tiles that were found
10316
+ */
10317
+ getTerrainCoords(tileID, terrainTileRanges) {
10318
+ if (terrainTileRanges) return this._getTerrainCoordsForTileRanges(tileID, terrainTileRanges);
10319
+ else return this._getTerrainCoordsForRegularTile(tileID);
9929
10320
  }
9930
- setPitch(pitch) {
9931
- this._helper.setPitch(pitch);
10321
+ /**
10322
+ * Searches for the corresponding current renderable terrain-tiles.
10323
+ * Includes terrain tiles that are either:
10324
+ * - the same as the tileID
10325
+ * - a parent of the tileID
10326
+ * - a child of the tileID
10327
+ * @param tileID - the tile to look for
10328
+ * @returns the tiles that were found
10329
+ */
10330
+ _getTerrainCoordsForRegularTile(tileID) {
10331
+ const coords = {};
10332
+ for (const key of this._renderableTilesKeys) {
10333
+ const terrainTileID = this._tiles[key].tileID;
10334
+ const coord = tileID.clone();
10335
+ const mat = createMat4f64();
10336
+ if (terrainTileID.canonical.equals(tileID.canonical)) ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1);
10337
+ else if (terrainTileID.canonical.isChildOf(tileID.canonical)) {
10338
+ const dz = terrainTileID.canonical.z - tileID.canonical.z;
10339
+ const dx = terrainTileID.canonical.x - (terrainTileID.canonical.x >> dz << dz);
10340
+ const dy = terrainTileID.canonical.y - (terrainTileID.canonical.y >> dz << dz);
10341
+ const size = EXTENT >> dz;
10342
+ ortho(mat, 0, size, size, 0, 0, 1);
10343
+ translate(mat, mat, [
10344
+ -dx * size,
10345
+ -dy * size,
10346
+ 0
10347
+ ]);
10348
+ } else if (tileID.canonical.isChildOf(terrainTileID.canonical)) {
10349
+ const dz = tileID.canonical.z - terrainTileID.canonical.z;
10350
+ const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz);
10351
+ const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz);
10352
+ const size = EXTENT >> dz;
10353
+ ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1);
10354
+ translate(mat, mat, [
10355
+ dx * size,
10356
+ dy * size,
10357
+ 0
10358
+ ]);
10359
+ scale(mat, mat, [
10360
+ 1 / 2 ** dz,
10361
+ 1 / 2 ** dz,
10362
+ 0
10363
+ ]);
10364
+ } else continue;
10365
+ coord.terrainRttPosMatrix32f = new Float32Array(mat);
10366
+ coords[key] = coord;
10367
+ }
10368
+ return coords;
9932
10369
  }
9933
- setRoll(roll) {
9934
- this._helper.setRoll(roll);
10370
+ /**
10371
+ * Searches for the corresponding current renderable terrain-tiles.
10372
+ * Includes terrain tiles that are within terrain tile ranges.
10373
+ * @param tileID - the tile to look for
10374
+ * @returns the tiles that were found
10375
+ */
10376
+ _getTerrainCoordsForTileRanges(tileID, terrainTileRanges) {
10377
+ const coords = {};
10378
+ for (const key of this._renderableTilesKeys) {
10379
+ const terrainTileID = this._tiles[key].tileID;
10380
+ if (!this._isWithinTileRanges(terrainTileID, terrainTileRanges)) continue;
10381
+ const coord = tileID.clone();
10382
+ const mat = createMat4f64();
10383
+ if (terrainTileID.canonical.z === tileID.canonical.z) {
10384
+ const dx = tileID.canonical.x - terrainTileID.canonical.x + tileID.wrap * (1 << tileID.canonical.z);
10385
+ const dy = tileID.canonical.y - terrainTileID.canonical.y;
10386
+ ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1);
10387
+ translate(mat, mat, [
10388
+ dx * EXTENT,
10389
+ dy * EXTENT,
10390
+ 0
10391
+ ]);
10392
+ } else if (terrainTileID.canonical.z > tileID.canonical.z) {
10393
+ const dz = terrainTileID.canonical.z - tileID.canonical.z;
10394
+ const dx = terrainTileID.canonical.x - (terrainTileID.canonical.x >> dz << dz) + tileID.wrap * (1 << terrainTileID.canonical.z);
10395
+ const dy = terrainTileID.canonical.y - (terrainTileID.canonical.y >> dz << dz);
10396
+ const dx2 = tileID.canonical.x - (terrainTileID.canonical.x >> dz);
10397
+ const dy2 = tileID.canonical.y - (terrainTileID.canonical.y >> dz);
10398
+ const size = EXTENT >> dz;
10399
+ ortho(mat, 0, size, size, 0, 0, 1);
10400
+ translate(mat, mat, [
10401
+ -dx * size + dx2 * EXTENT,
10402
+ -dy * size + dy2 * EXTENT,
10403
+ 0
10404
+ ]);
10405
+ } else {
10406
+ const dz = tileID.canonical.z - terrainTileID.canonical.z;
10407
+ const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz) + tileID.wrap * (1 << tileID.canonical.z);
10408
+ const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz);
10409
+ const dx2 = (tileID.canonical.x >> dz) - terrainTileID.canonical.x;
10410
+ const dy2 = (tileID.canonical.y >> dz) - terrainTileID.canonical.y;
10411
+ const size = EXTENT << dz;
10412
+ ortho(mat, 0, size, size, 0, 0, 1);
10413
+ translate(mat, mat, [
10414
+ dx * EXTENT + dx2 * size,
10415
+ dy * EXTENT + dy2 * size,
10416
+ 0
10417
+ ]);
10418
+ }
10419
+ coord.terrainRttPosMatrix32f = new Float32Array(mat);
10420
+ coords[key] = coord;
10421
+ }
10422
+ return coords;
9935
10423
  }
9936
- setFov(fov) {
9937
- this._helper.setFov(fov);
10424
+ /**
10425
+ * find the covering raster-dem tile
10426
+ * @param tileID - the tile to look for
10427
+ * @param searchForDEM - Optional parameter to search for (parent) source tiles with loaded dem.
10428
+ * @returns the tile
10429
+ */
10430
+ getSourceTile(tileID, searchForDEM) {
10431
+ const source = this.tileManager._source;
10432
+ let z = tileID.overscaledZ - this.deltaZoom;
10433
+ if (z > source.maxzoom) z = source.maxzoom;
10434
+ if (z < source.minzoom) return void 0;
10435
+ this._sourceTileCache[tileID.key] ||= tileID.scaledTo(z).key;
10436
+ let tile = this.findTileInCaches(this._sourceTileCache[tileID.key]);
10437
+ if (!tile?.dem && searchForDEM) while (z >= source.minzoom && !tile?.dem) tile = this.findTileInCaches(tileID.scaledTo(z--).key);
10438
+ return tile;
9938
10439
  }
9939
- setZoom(zoom) {
9940
- this._helper.setZoom(zoom);
10440
+ findTileInCaches(key) {
10441
+ let tile = this.tileManager.getTileByID(key);
10442
+ if (tile) return tile;
10443
+ tile = this.tileManager._outOfViewCache.getByKey(key);
10444
+ return tile;
9941
10445
  }
9942
- setCenter(center) {
9943
- this._helper.setCenter(center);
10446
+ /**
10447
+ * gets whether any tiles were loaded after a specific time. This is used to update the depth framebuffer.
10448
+ * @param time - the time
10449
+ * @returns true if any tiles came into view at or after the specified time
10450
+ */
10451
+ anyTilesAfterTime(time = now()) {
10452
+ return this._lastTilesetChange >= time;
9944
10453
  }
9945
- setElevation(elevation) {
9946
- this._helper.setElevation(elevation);
10454
+ /**
10455
+ * Checks whether a tile is within the canonical tile ranges.
10456
+ * @param tileID - Tile to check
10457
+ * @param canonicalTileRanges - Canonical tile ranges
10458
+ * @returns
10459
+ */
10460
+ _isWithinTileRanges(tileID, canonicalTileRanges) {
10461
+ const range = canonicalTileRanges[tileID.canonical.z];
10462
+ return !!range && (tileID.wrap > range.minWrap || tileID.wrap < range.maxWrap || tileID.canonical.x >= range.minTileXWrapped && tileID.canonical.x <= range.maxTileXWrapped && tileID.canonical.y >= range.minTileY && tileID.canonical.y <= range.maxTileY);
9947
10463
  }
9948
- setMinElevationForCurrentTile(elevation) {
9949
- this._helper.setMinElevationForCurrentTile(elevation);
10464
+ };
10465
+ //#endregion
10466
+ //#region src/render/terrain.ts
10467
+ const MAX_BISECTIONS = 40;
10468
+ const HIT_EPSILON_M = 1e-6;
10469
+ /** Keeps the elevation bracket non-degenerate when the terrain is entirely flat, such as unloaded DEMs. */
10470
+ const BRACKET_PADDING_M = 10;
10471
+ /** `DEMData.sampleBilinear` throws on the far tile edge, so samples stop just short of it. */
10472
+ const MAX_TILE_COORD = EXTENT * (1 - 1e-12);
10473
+ /**
10474
+ * @internal
10475
+ * This is the main class which handles most of the 3D Terrain logic. It has the following topics:
10476
+ *
10477
+ * 1. loads raster-dem tiles via the internal tileManager this.tileManager
10478
+ * 2. creates a depth-framebuffer, which is used to calculate the visibility of coordinates
10479
+ * 3. stores all render-to-texture tiles in the this.tileManager._tiles
10480
+ * 4. calculates the elevation for a specific tile-coordinate
10481
+ * 5. creates a terrain-mesh
10482
+ *
10483
+ * A note about the GPU resource-usage:
10484
+ *
10485
+ * Framebuffers:
10486
+ *
10487
+ * - one for the depth framebuffer with the size of the map-div.
10488
+ * - one for rendering a tile to texture with the size of tileSize (= 512x512).
10489
+ *
10490
+ * Textures:
10491
+ *
10492
+ * - one texture for an empty raster-dem tile with size 1x1
10493
+ * - one texture for an empty depth-buffer, when terrain is disabled with size 1x1
10494
+ * - one texture for an each loaded raster-dem with size of the source.tileSize
10495
+ * - one texture for the depth-framebuffer with the size of the map-div.
10496
+ * - finally for each render-to-texture tile (= this._tiles) a set of textures
10497
+ * for each render stack (The stack-concept is documented in painter.ts).
10498
+ *
10499
+ * Normally there exists 1-3 Textures per tile, depending on the stylesheet.
10500
+ * Each Textures has the size 2*tileSize (= 1024x1024). Also there exists a
10501
+ * cache of the last 150 newest rendered tiles.
10502
+ *
10503
+ */
10504
+ var Terrain = class {
10505
+ constructor(painter, tileManager, options, terrainSkirtLength = "auto") {
10506
+ this._meshCache = {};
10507
+ this.painter = painter;
10508
+ this.tileManager = new TerrainTileManager(tileManager);
10509
+ this.options = options;
10510
+ this.exaggeration = typeof options.exaggeration === "number" ? options.exaggeration : 1;
10511
+ this._terrainSkirtLength = terrainSkirtLength;
10512
+ this.qualityFactor = 2;
10513
+ this.meshSize = 128;
10514
+ this._demMatrixCache = /* @__PURE__ */ new Map();
10515
+ this._elevationSamplerCache = /* @__PURE__ */ new Map();
9950
10516
  }
9951
- setPadding(padding) {
9952
- this._helper.setPadding(padding);
10517
+ destroy() {
10518
+ if (this._fbo) {
10519
+ this._fbo.destroy();
10520
+ this._fbo = null;
10521
+ }
10522
+ if (this._fboDepthTexture) {
10523
+ this._fboDepthTexture.destroy();
10524
+ this._fboDepthTexture = null;
10525
+ }
10526
+ if (this._emptyDemTexture) {
10527
+ this._emptyDemTexture.destroy();
10528
+ this._emptyDemTexture = null;
10529
+ }
10530
+ if (this._emptyDepthTexture) {
10531
+ this._emptyDepthTexture.destroy();
10532
+ this._emptyDepthTexture = null;
10533
+ }
10534
+ for (const key in this._meshCache) this._meshCache[key].destroy();
10535
+ this._meshCache = {};
10536
+ this.tileManager.destruct();
9953
10537
  }
9954
- interpolatePadding(start, target, t) {
9955
- this._helper.interpolatePadding(start, target, t);
10538
+ /**
10539
+ * Get the elevation-value from original dem-data for a given tile-coordinate.
10540
+ * Coordinates that fall outside `[0, extent)` are normalized to the
10541
+ * appropriate neighbor tile before lookup.
10542
+ * @param tileID - the tile to get the elevation for
10543
+ * @param x - x coordinate relative to the tile, may be outside `[0, extent)`
10544
+ * @param y - y coordinate relative to the tile, may be outside `[0, extent)`
10545
+ * @param extent - optional, default 8192
10546
+ * @returns the elevation
10547
+ */
10548
+ getDEMElevation(tileID, x, y, extent = EXTENT) {
10549
+ const normalized = tileID.normalizeCoordinates(x, y, extent);
10550
+ if (!normalized) return 0;
10551
+ const sampler = this.getElevationSampler(normalized.tileID);
10552
+ return sampler ? sampler(normalized.x, normalized.y, extent) : 0;
9956
10553
  }
9957
- isPaddingEqual(padding) {
9958
- return this._helper.isPaddingEqual(padding);
9959
- }
9960
- resize(width, height, constrain = true) {
9961
- this._helper.resize(width, height, constrain);
10554
+ /**
10555
+ * Get the elevation for given {@link LngLat} in respect of exaggeration.
10556
+ * @param lnglat - the location
10557
+ * @param zoom - the zoom, use {@link getElevationForLngLat} if you don't want a specific zoom level, but more accurate results.
10558
+ * @returns the elevation
10559
+ */
10560
+ getElevationForLngLatZoom(lnglat, zoom) {
10561
+ if (!isInBoundsForZoomLngLat(zoom, lnglat.wrap())) return 0;
10562
+ const { tileID, mercatorX, mercatorY } = this._getOverscaledTileIDFromLngLatZoom(lnglat, zoom);
10563
+ return this.getElevation(tileID, mercatorX % EXTENT, mercatorY % EXTENT, EXTENT);
9962
10564
  }
9963
- getMaxBounds() {
9964
- return this._helper.getMaxBounds();
10565
+ /**
10566
+ * Get the elevation for given {@link LngLat} in respect of exaggeration.
10567
+ * Where the location is covered by a rendered tile with loaded DEM data this samples the
10568
+ * rendered surface, so the result agrees with what is drawn; elsewhere it traverses up the
10569
+ * zoom levels to find the first tile with data to return.
10570
+ * @param lnglat - the location
10571
+ * @returns the elevation
10572
+ */
10573
+ getElevationForLngLat(lnglat, transform) {
10574
+ const index = this.getCoverageIndex();
10575
+ if (index) {
10576
+ const mercator = MercatorCoordinate.fromLngLat(lnglat);
10577
+ const sample = sampleAt(index, this.exaggeration, mercator.x, mercator.y);
10578
+ if (sample.demLoaded) return sample.elevation;
10579
+ }
10580
+ const terrainCoveringTiles = coveringTiles(transform, {
10581
+ maxzoom: this.tileManager.maxzoom,
10582
+ minzoom: this.tileManager.minzoom,
10583
+ tileSize: 512,
10584
+ terrain: this
10585
+ });
10586
+ let zoom = 0;
10587
+ for (const tile of terrainCoveringTiles) if (tile.canonical.z > zoom) zoom = Math.min(tile.canonical.z, this.tileManager.maxzoom);
10588
+ return this.getElevationForLngLatZoom(lnglat, zoom);
9965
10589
  }
9966
- setMaxBounds(bounds) {
9967
- this._helper.setMaxBounds(bounds);
10590
+ /**
10591
+ * Get the elevation for given coordinate in respect of exaggeration.
10592
+ * @param tileID - the tile id
10593
+ * @param x - x coordinate relative to the tile, may be outside `[0, extent)`
10594
+ * @param y - y coordinate relative to the tile, may be outside `[0, extent)`
10595
+ * @param extent - optional, default 8192
10596
+ * @returns the elevation
10597
+ */
10598
+ getElevation(tileID, x, y, extent = EXTENT) {
10599
+ return this.getDEMElevation(tileID, x, y, extent) * this.exaggeration;
9968
10600
  }
9969
- setConstrainOverride(constrain) {
9970
- this._helper.setConstrainOverride(constrain);
10601
+ /**
10602
+ * Clear CPU elevation samplers that may retain a previously selected DEM tile.
10603
+ * @internal
10604
+ */
10605
+ resetElevationCache() {
10606
+ this._elevationSamplerCache.clear();
10607
+ this._coverageIndex = void 0;
10608
+ }
10609
+ /**
10610
+ * Index of the tiles the terrain currently renders, for sampling the terrain surface on the CPU.
10611
+ * Built on first use and kept until {@link resetElevationCache}.
10612
+ * @returns the index, or null when no terrain tile is renderable
10613
+ */
10614
+ getCoverageIndex() {
10615
+ if (this._coverageIndex === void 0) this._coverageIndex = this._buildCoverageIndex();
10616
+ return this._coverageIndex;
10617
+ }
10618
+ _buildCoverageIndex() {
10619
+ const zooms = [];
10620
+ const samplerPerTile = /* @__PURE__ */ new Map();
10621
+ let minElevation = 0;
10622
+ let maxElevation = 0;
10623
+ for (const tile of this.tileManager.getRenderableTiles()) {
10624
+ if (!tile) continue;
10625
+ const { canonical, wrap } = tile.tileID;
10626
+ if (!zooms.includes(canonical.z)) zooms.push(canonical.z);
10627
+ const sampler = this.getElevationSampler(tile.tileID);
10628
+ samplerPerTile.set(`${wrap}/${canonical.z}/${canonical.x}/${canonical.y}`, sampler);
10629
+ const { minElevation: tileMin, maxElevation: tileMax } = this.getMinMaxElevation(tile.tileID);
10630
+ minElevation = Math.min(minElevation, tileMin ?? 0);
10631
+ maxElevation = Math.max(maxElevation, tileMax ?? 0);
10632
+ }
10633
+ if (samplerPerTile.size === 0) return null;
10634
+ zooms.sort((a, b) => b - a);
10635
+ return {
10636
+ zooms,
10637
+ samplerPerTile,
10638
+ minElevation: minElevation - BRACKET_PADDING_M,
10639
+ maxElevation: maxElevation + BRACKET_PADDING_M
10640
+ };
9971
10641
  }
9972
- overrideNearFarZ(nearZ, farZ) {
9973
- this._helper.overrideNearFarZ(nearZ, farZ);
10642
+ /**
10643
+ * Get a function that samples the raw DEM elevation of a tile, without exaggeration.
10644
+ * @param tileID - the tile id
10645
+ * @returns the sampler, or null when the tile's DEM data is not loaded
10646
+ */
10647
+ getElevationSampler(tileID) {
10648
+ const key = tileID.key;
10649
+ const cachedSampler = this._elevationSamplerCache.get(key);
10650
+ if (cachedSampler) return cachedSampler;
10651
+ const sourceTile = this.tileManager.getSourceTile(tileID, true);
10652
+ const dem = sourceTile?.dem;
10653
+ if (!sourceTile || !dem) return null;
10654
+ const matrix = this._getDEMTileMatrix(tileID, sourceTile);
10655
+ const demPixelScaleX = matrix[0] * dem.dim;
10656
+ const demPixelScaleY = matrix[5] * dem.dim;
10657
+ const demPixelOffsetX = matrix[12] * dem.dim;
10658
+ const demPixelOffsetY = matrix[13] * dem.dim;
10659
+ const sampler = (x, y, extent) => {
10660
+ const extentScale = extent === 8192 ? 1 : EXTENT / extent;
10661
+ return dem.sampleBilinear(x * extentScale * demPixelScaleX + demPixelOffsetX, y * extentScale * demPixelScaleY + demPixelOffsetY);
10662
+ };
10663
+ this._elevationSamplerCache.set(key, sampler);
10664
+ return sampler;
9974
10665
  }
9975
- clearNearFarZOverride() {
9976
- this._helper.clearNearFarZOverride();
10666
+ _getDEMTileMatrix(tileID, sourceTile) {
10667
+ const matrixKey = `${sourceTile.tileID.key}/${tileID.key}`;
10668
+ const cachedMatrix = this._demMatrixCache.get(matrixKey);
10669
+ if (cachedMatrix) return cachedMatrix;
10670
+ const maxzoom = this.tileManager.getSource().maxzoom;
10671
+ let dz = tileID.canonical.z - sourceTile.tileID.canonical.z;
10672
+ if (tileID.overscaledZ > tileID.canonical.z) {
10673
+ if (tileID.canonical.z >= maxzoom) dz = tileID.canonical.z - maxzoom;
10674
+ else warnOnce("cannot calculate elevation if elevation maxzoom > source.maxzoom");
10675
+ }
10676
+ const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz);
10677
+ const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz);
10678
+ const demMatrix = fromScaling(/* @__PURE__ */ new Float64Array(16), [
10679
+ 1 / (EXTENT << dz),
10680
+ 1 / (EXTENT << dz),
10681
+ 0
10682
+ ]);
10683
+ translate(demMatrix, demMatrix, [
10684
+ dx * EXTENT,
10685
+ dy * EXTENT,
10686
+ 0
10687
+ ]);
10688
+ this._demMatrixCache.set(matrixKey, demMatrix);
10689
+ return demMatrix;
9977
10690
  }
9978
- getCameraQueryGeometry(queryGeometry) {
9979
- return this._helper.getCameraQueryGeometry(this.getCameraPoint(), queryGeometry);
10691
+ /**
10692
+ * returns a Terrain Object for a tile. Unless the tile corresponds to data (e.g. tile is loading), return a flat dem object
10693
+ * @param tileID - the tile to get the terrain for
10694
+ * @returns the terrain data to use in the program
10695
+ */
10696
+ getTerrainData(tileID) {
10697
+ if (!this._emptyDemTexture) {
10698
+ const context = this.painter.context;
10699
+ const image = new RGBAImage({
10700
+ width: 1,
10701
+ height: 1
10702
+ }, /* @__PURE__ */ new Uint8Array(4));
10703
+ this._emptyDepthTexture = new Texture(context, image, context.gl.RGBA, { premultiply: false });
10704
+ this._emptyDemUnpack = [
10705
+ 0,
10706
+ 0,
10707
+ 0,
10708
+ 0
10709
+ ];
10710
+ this._emptyDemTexture = new Texture(context, new RGBAImage({
10711
+ width: 1,
10712
+ height: 1
10713
+ }), context.gl.RGBA, { premultiply: false });
10714
+ this._emptyDemTexture.bind(context.gl.NEAREST, context.gl.CLAMP_TO_EDGE);
10715
+ this._emptyDemMatrix = identity([]);
10716
+ }
10717
+ const sourceTile = this.tileManager.getSourceTile(tileID, true);
10718
+ if (sourceTile?.dem && (!sourceTile.demTexture || sourceTile.needsTerrainPrepare)) {
10719
+ const context = this.painter.context;
10720
+ sourceTile.demTexture ||= this.painter.getTileTexture(sourceTile.dem.stride);
10721
+ if (sourceTile.demTexture) sourceTile.demTexture.update(sourceTile.dem.getPixels(), { premultiply: false });
10722
+ else sourceTile.demTexture = new Texture(context, sourceTile.dem.getPixels(), context.gl.RGBA, { premultiply: false });
10723
+ sourceTile.demTexture.bind(context.gl.NEAREST, context.gl.CLAMP_TO_EDGE);
10724
+ sourceTile.needsTerrainPrepare = false;
10725
+ }
10726
+ const terrainMatrix = sourceTile ? this._getDEMTileMatrix(tileID, sourceTile) : this._emptyDemMatrix;
10727
+ return {
10728
+ "u_depth": 2,
10729
+ "u_terrain": 3,
10730
+ "u_terrain_dim": sourceTile?.dem?.dim || 1,
10731
+ "u_terrain_matrix": terrainMatrix,
10732
+ "u_terrain_unpack": sourceTile?.dem?.getUnpackVector() || this._emptyDemUnpack,
10733
+ "u_terrain_exaggeration": this.exaggeration,
10734
+ texture: (sourceTile?.demTexture || this._emptyDemTexture).texture,
10735
+ depthTexture: (this._fboDepthTexture || this._emptyDepthTexture).texture,
10736
+ tile: sourceTile
10737
+ };
9980
10738
  }
9981
- get tileSize() {
9982
- return this._helper.tileSize;
10739
+ /**
10740
+ * get a framebuffer as big as the map-div, which will be used to render depth into a texture
10741
+ * @returns the frame buffer
10742
+ */
10743
+ getFramebuffer() {
10744
+ const painter = this.painter;
10745
+ const width = painter.width / devicePixelRatio;
10746
+ const height = painter.height / devicePixelRatio;
10747
+ if (this._fbo && (this._fbo.width !== width || this._fbo.height !== height)) {
10748
+ this._fbo.destroy();
10749
+ this._fboDepthTexture.destroy();
10750
+ delete this._fbo;
10751
+ delete this._fboDepthTexture;
10752
+ }
10753
+ if (!this._fboDepthTexture) {
10754
+ this._fboDepthTexture = new Texture(painter.context, {
10755
+ width,
10756
+ height,
10757
+ data: null
10758
+ }, painter.context.gl.RGBA, { premultiply: false });
10759
+ this._fboDepthTexture.bind(painter.context.gl.NEAREST, painter.context.gl.CLAMP_TO_EDGE);
10760
+ }
10761
+ if (!this._fbo) {
10762
+ this._fbo = painter.context.createFramebuffer(width, height, true, false);
10763
+ this._fbo.depthAttachment.set(painter.context.createRenderbuffer(painter.context.gl.DEPTH_COMPONENT16, width, height));
10764
+ }
10765
+ this._fbo.colorAttachment.set(this._fboDepthTexture.texture);
10766
+ return this._fbo;
9983
10767
  }
9984
- get tileZoom() {
9985
- return this._helper.tileZoom;
10768
+ /**
10769
+ * Reads the depth value from the depth-framebuffer at a given screen pixel
10770
+ * @param p - Screen coordinate
10771
+ * @returns depth value in clip space (between 0 and 1)
10772
+ */
10773
+ depthAtPoint(p) {
10774
+ const rgba = /* @__PURE__ */ new Uint8Array(4);
10775
+ const context = this.painter.context, gl = context.gl;
10776
+ context.bindFramebuffer.set(this.getFramebuffer().framebuffer);
10777
+ gl.readPixels(p.x, this.painter.height / devicePixelRatio - p.y - 1, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, rgba);
10778
+ context.bindFramebuffer.set(null);
10779
+ return (rgba[0] / 16777216 + rgba[1] / 65536 + rgba[2] / 256 + rgba[3]) / 256;
9986
10780
  }
9987
- get scale() {
9988
- return this._helper.scale;
10781
+ /**
10782
+ * create a regular mesh which will be used by all terrain-tiles
10783
+ * @returns the created regular mesh
10784
+ */
10785
+ getTerrainMesh(tileId) {
10786
+ const globeEnabled = this.painter.style.projection?.transitionState > 0;
10787
+ const northPole = globeEnabled && tileId.canonical.y === 0;
10788
+ const southPole = globeEnabled && tileId.canonical.y === (1 << tileId.canonical.z) - 1;
10789
+ const key = `m_${northPole ? "n" : ""}_${southPole ? "s" : ""}`;
10790
+ if (this._meshCache[key]) return this._meshCache[key];
10791
+ const context = this.painter.context;
10792
+ const vertexArray = new Pos3dArray();
10793
+ const indexArray = new TriangleIndexArray();
10794
+ const meshSize = this.meshSize;
10795
+ const delta = EXTENT / meshSize;
10796
+ const meshSize2 = meshSize * meshSize;
10797
+ for (let y = 0; y <= meshSize; y++) for (let x = 0; x <= meshSize; x++) vertexArray.emplaceBack(x * delta, y * delta, 0);
10798
+ for (let y = 0; y < meshSize2; y += meshSize + 1) for (let x = 0; x < meshSize; x++) {
10799
+ indexArray.emplaceBack(x + y, meshSize + x + y + 1, meshSize + x + y + 2);
10800
+ indexArray.emplaceBack(x + y, meshSize + x + y + 2, x + y + 1);
10801
+ }
10802
+ if (this._terrainSkirtLength !== "none") this._buildSkirts(vertexArray, indexArray, meshSize, delta, northPole, southPole);
10803
+ const mesh = new Mesh(context.createVertexBuffer(vertexArray, pos3dAttributes.members), context.createIndexBuffer(indexArray), SegmentVector.simpleSegment(0, 0, vertexArray.length, indexArray.length));
10804
+ this._meshCache[key] = mesh;
10805
+ return mesh;
9989
10806
  }
9990
- get worldSize() {
9991
- return this._helper.worldSize;
10807
+ /**
10808
+ * Calculates the height of the tile skirts for the "auto" strategy.
10809
+ * @see {@link MapOptions.terrainSkirtLength}
10810
+ * @param zoom - current zoomlevel
10811
+ * @returns the elevation delta in meters
10812
+ */
10813
+ getSkirtLength(zoom) {
10814
+ return 2 * Math.PI * earthRadius / Math.pow(2, Math.max(zoom, 0)) / 5;
9992
10815
  }
9993
- get width() {
9994
- return this._helper.width;
10816
+ getMinTileElevationForLngLatZoom(lnglat, zoom) {
10817
+ if (!isInBoundsForZoomLngLat(zoom, lnglat.wrap())) return 0;
10818
+ const { tileID } = this._getOverscaledTileIDFromLngLatZoom(lnglat, zoom);
10819
+ return this.getMinMaxElevation(tileID).minElevation ?? 0;
9995
10820
  }
9996
- get height() {
9997
- return this._helper.height;
10821
+ /**
10822
+ * Get the minimum and maximum elevation contained in a tile. This includes any
10823
+ * exaggeration included in the terrain.
10824
+ *
10825
+ * @param tileID - ID of the tile to be used as a source for the min/max elevation
10826
+ * @returns the minimum and maximum elevation found in the tile, including the terrain's
10827
+ * exaggeration
10828
+ */
10829
+ getMinMaxElevation(tileID) {
10830
+ const tile = this.tileManager.getSourceTile(tileID, true);
10831
+ const minMax = {
10832
+ minElevation: null,
10833
+ maxElevation: null
10834
+ };
10835
+ if (tile?.dem) {
10836
+ minMax.minElevation = tile.dem.min * this.exaggeration;
10837
+ minMax.maxElevation = tile.dem.max * this.exaggeration;
10838
+ }
10839
+ return minMax;
10840
+ }
10841
+ _getOverscaledTileIDFromLngLatZoom(lnglat, zoom) {
10842
+ const mercatorCoordinate = MercatorCoordinate.fromLngLat(lnglat.wrap());
10843
+ const worldSize = (1 << zoom) * EXTENT;
10844
+ const mercatorX = mercatorCoordinate.x * worldSize;
10845
+ const mercatorY = mercatorCoordinate.y * worldSize;
10846
+ const tileX = Math.floor(mercatorX / EXTENT), tileY = Math.floor(mercatorY / EXTENT);
10847
+ return {
10848
+ tileID: new OverscaledTileID(zoom, 0, zoom, tileX, tileY),
10849
+ mercatorX,
10850
+ mercatorY
10851
+ };
10852
+ }
10853
+ /** Add an extra frame around the mesh to avoid hairline gaps (stitching) on tile boundaries with different zoomlevels.
10854
+ * @see {@link MapOptions.terrainSkirtLength}
10855
+ */
10856
+ _buildSkirts(vertexArray, indexArray, meshSize, delta, northPole, southPole) {
10857
+ const offsetTop = vertexArray.length;
10858
+ const offsetTopEdge = 0;
10859
+ const offsetBottom = offsetTop + (meshSize + 1);
10860
+ const offsetBottomEdge = (meshSize + 1) * meshSize;
10861
+ const northY = northPole ? NORTH_POLE_Y : 0;
10862
+ const northZ = northPole ? 0 : 1;
10863
+ const southY = southPole ? SOUTH_POLE_Y : EXTENT;
10864
+ const southZ = southPole ? 0 : 1;
10865
+ for (let x = 0; x <= meshSize; x++) vertexArray.emplaceBack(x * delta, northY, northZ);
10866
+ for (let x = 0; x <= meshSize; x++) vertexArray.emplaceBack(x * delta, southY, southZ);
10867
+ for (let x = 0; x < meshSize; x++) {
10868
+ indexArray.emplaceBack(offsetBottomEdge + x, offsetBottom + x, offsetBottom + x + 1);
10869
+ indexArray.emplaceBack(offsetBottomEdge + x, offsetBottom + x + 1, offsetBottomEdge + x + 1);
10870
+ indexArray.emplaceBack(offsetTopEdge + x, offsetTop + x + 1, offsetTop + x);
10871
+ indexArray.emplaceBack(offsetTopEdge + x, offsetTopEdge + x + 1, offsetTop + x + 1);
10872
+ }
10873
+ const offsetLeft = vertexArray.length;
10874
+ const offsetRight = offsetLeft + (meshSize + 1) * 2;
10875
+ for (const x of [0, 1]) for (let y = 0; y <= meshSize; y++) for (const z of [0, 1]) vertexArray.emplaceBack(x * EXTENT, y * delta, z);
10876
+ for (let y = 0; y < meshSize * 2; y += 2) {
10877
+ indexArray.emplaceBack(offsetLeft + y, offsetLeft + y + 1, offsetLeft + y + 3);
10878
+ indexArray.emplaceBack(offsetLeft + y, offsetLeft + y + 3, offsetLeft + y + 2);
10879
+ indexArray.emplaceBack(offsetRight + y, offsetRight + y + 3, offsetRight + y + 1);
10880
+ indexArray.emplaceBack(offsetRight + y, offsetRight + y + 2, offsetRight + y + 3);
10881
+ }
10882
+ }
10883
+ };
10884
+ const NOT_COVERED = {
10885
+ covered: false,
10886
+ demLoaded: false,
10887
+ elevation: 0
10888
+ };
10889
+ /**
10890
+ * Elevation of the rendered terrain surface at a mercator position, and whether it is covered at all.
10891
+ * A covered tile whose DEM has not loaded yet is flat at zero, which is what the terrain mesh renders.
10892
+ */
10893
+ function sampleAt(index, exaggeration, mercatorX, mercatorY) {
10894
+ if (mercatorY < 0 || mercatorY >= 1) return NOT_COVERED;
10895
+ const wrap = Math.floor(mercatorX);
10896
+ const wrappedX = mercatorX - wrap;
10897
+ for (const z of index.zooms) {
10898
+ const scale = 1 << z;
10899
+ const scaledX = wrappedX * scale;
10900
+ const scaledY = mercatorY * scale;
10901
+ const tileX = Math.floor(scaledX);
10902
+ const tileY = Math.floor(scaledY);
10903
+ const key = `${wrap}/${z}/${tileX}/${tileY}`;
10904
+ if (!index.samplerPerTile.has(key)) continue;
10905
+ const sampler = index.samplerPerTile.get(key);
10906
+ if (!sampler) return {
10907
+ covered: true,
10908
+ demLoaded: false,
10909
+ elevation: 0
10910
+ };
10911
+ return {
10912
+ covered: true,
10913
+ demLoaded: true,
10914
+ elevation: sampler(Math.min((scaledX - tileX) * EXTENT, MAX_TILE_COORD), Math.min((scaledY - tileY) * EXTENT, MAX_TILE_COORD), EXTENT) * exaggeration
10915
+ };
10916
+ }
10917
+ return NOT_COVERED;
10918
+ }
10919
+ /**
10920
+ * Whether a height in meters is at or below the sampled terrain surface.
10921
+ * The epsilon absorbs rounding when a bracket endpoint lands exactly on the surface.
10922
+ */
10923
+ function isBelowTerrainSample(sample, height) {
10924
+ return sample.covered && height <= sample.elevation + HIT_EPSILON_M;
10925
+ }
10926
+ /**
10927
+ * Narrows the bracket `[lo, hi]` around the surface crossing until it is shorter than `tolerance` in ray parameter units.
10928
+ */
10929
+ function bisect(ray, isBelowTerrain, lo, hi, tolerance) {
10930
+ for (let j = 0; j < MAX_BISECTIONS && hi - lo > tolerance; j++) {
10931
+ const mid = (lo + hi) / 2;
10932
+ if (isBelowTerrain(ray, mid)) hi = mid;
10933
+ else lo = mid;
10934
+ }
10935
+ return {
10936
+ lo,
10937
+ hi
10938
+ };
10939
+ }
10940
+ //#endregion
10941
+ //#region src/geo/projection/mercator_transform.ts
10942
+ const TARGET_WORLD_STEP_PX = 4;
10943
+ const MAX_SAMPLES = 512;
10944
+ const MERCATOR_BISECT_EPSILON_WORLD_PX = .001;
10945
+ var MercatorTransform = class MercatorTransform {
10946
+ get pixelsToClipSpaceMatrix() {
10947
+ return this._helper.pixelsToClipSpaceMatrix;
10948
+ }
10949
+ get clipSpaceToPixelsMatrix() {
10950
+ return this._helper.clipSpaceToPixelsMatrix;
10951
+ }
10952
+ get pixelsToGLUnits() {
10953
+ return this._helper.pixelsToGLUnits;
10954
+ }
10955
+ get centerOffset() {
10956
+ return this._helper.centerOffset;
10957
+ }
10958
+ get size() {
10959
+ return this._helper.size;
10960
+ }
10961
+ get rotationMatrix() {
10962
+ return this._helper.rotationMatrix;
10963
+ }
10964
+ get centerPoint() {
10965
+ return this._helper.centerPoint;
10966
+ }
10967
+ get pixelsPerMeter() {
10968
+ return this._helper.pixelsPerMeter;
10969
+ }
10970
+ setMinZoom(zoom) {
10971
+ this._helper.setMinZoom(zoom);
10972
+ }
10973
+ setMaxZoom(zoom) {
10974
+ this._helper.setMaxZoom(zoom);
10975
+ }
10976
+ setMinPitch(pitch) {
10977
+ this._helper.setMinPitch(pitch);
10978
+ }
10979
+ setMaxPitch(pitch) {
10980
+ this._helper.setMaxPitch(pitch);
10981
+ }
10982
+ setRenderWorldCopies(renderWorldCopies) {
10983
+ this._helper.setRenderWorldCopies(renderWorldCopies);
10984
+ }
10985
+ setBearing(bearing) {
10986
+ this._helper.setBearing(bearing);
10987
+ }
10988
+ setPitch(pitch) {
10989
+ this._helper.setPitch(pitch);
10990
+ }
10991
+ setRoll(roll) {
10992
+ this._helper.setRoll(roll);
10993
+ }
10994
+ setFov(fov) {
10995
+ this._helper.setFov(fov);
10996
+ }
10997
+ setZoom(zoom) {
10998
+ this._helper.setZoom(zoom);
10999
+ }
11000
+ setCenter(center) {
11001
+ this._helper.setCenter(center);
11002
+ }
11003
+ setElevation(elevation) {
11004
+ this._helper.setElevation(elevation);
11005
+ }
11006
+ setMinElevationForCurrentTile(elevation) {
11007
+ this._helper.setMinElevationForCurrentTile(elevation);
11008
+ }
11009
+ setPadding(padding) {
11010
+ this._helper.setPadding(padding);
11011
+ }
11012
+ interpolatePadding(start, target, t) {
11013
+ this._helper.interpolatePadding(start, target, t);
11014
+ }
11015
+ isPaddingEqual(padding) {
11016
+ return this._helper.isPaddingEqual(padding);
11017
+ }
11018
+ resize(width, height, constrain = true) {
11019
+ this._helper.resize(width, height, constrain);
11020
+ }
11021
+ getMaxBounds() {
11022
+ return this._helper.getMaxBounds();
11023
+ }
11024
+ setMaxBounds(bounds) {
11025
+ this._helper.setMaxBounds(bounds);
11026
+ }
11027
+ setConstrainOverride(constrain) {
11028
+ this._helper.setConstrainOverride(constrain);
11029
+ }
11030
+ overrideNearFarZ(nearZ, farZ) {
11031
+ this._helper.overrideNearFarZ(nearZ, farZ);
11032
+ }
11033
+ clearNearFarZOverride() {
11034
+ this._helper.clearNearFarZOverride();
11035
+ }
11036
+ getCameraQueryGeometry(queryGeometry) {
11037
+ return this._helper.getCameraQueryGeometry(this.getCameraPoint(), queryGeometry);
11038
+ }
11039
+ get tileSize() {
11040
+ return this._helper.tileSize;
11041
+ }
11042
+ get tileZoom() {
11043
+ return this._helper.tileZoom;
11044
+ }
11045
+ get scale() {
11046
+ return this._helper.scale;
11047
+ }
11048
+ get worldSize() {
11049
+ return this._helper.worldSize;
11050
+ }
11051
+ get width() {
11052
+ return this._helper.width;
11053
+ }
11054
+ get height() {
11055
+ return this._helper.height;
9998
11056
  }
9999
11057
  get lngRange() {
10000
11058
  return this._helper.lngRange;
@@ -10197,7 +11255,7 @@ var MercatorTransform = class MercatorTransform {
10197
11255
  }
10198
11256
  recalculateZoomAndCenter(terrain) {
10199
11257
  const center = this.screenPointToLocation(this.centerPoint, terrain);
10200
- const elevation = terrain ? terrain.getElevationForLngLatZoom(center, this._helper._tileZoom) : 0;
11258
+ const elevation = terrain ? terrain.getElevationForLngLat(center, this) : 0;
10201
11259
  this._helper.recalculateZoomAndCenter(elevation);
10202
11260
  }
10203
11261
  /**
@@ -10228,17 +11286,64 @@ var MercatorTransform = class MercatorTransform {
10228
11286
  }
10229
11287
  screenPointToMercatorCoordinate(p, terrain) {
10230
11288
  if (terrain) {
10231
- const coordinate = terrain.pointCoordinate(p);
11289
+ const coordinate = this.screenTerrainPointToMercatorCoordinate(p, terrain);
10232
11290
  if (coordinate != null) return coordinate;
10233
11291
  }
10234
11292
  return this.screenPointToMercatorCoordinateAtZ(p);
10235
11293
  }
11294
+ /** {@inheritDoc ITransform.screenTerrainPointToMercatorCoordinate} */
11295
+ screenTerrainPointToMercatorCoordinate(p, terrain) {
11296
+ const index = terrain.getCoverageIndex();
11297
+ if (!index) return null;
11298
+ const { near, far } = this.getRaySegmentFromPixel(p);
11299
+ const worldSize = this.worldSize;
11300
+ const dx = far[0] - near[0];
11301
+ const dy = far[1] - near[1];
11302
+ const dz = far[2] - near[2];
11303
+ const ray = {
11304
+ index,
11305
+ exaggeration: terrain.exaggeration,
11306
+ near,
11307
+ dx,
11308
+ dy,
11309
+ dz,
11310
+ worldSize
11311
+ };
11312
+ let tStart = 0;
11313
+ let tEnd = 1;
11314
+ if (dz === 0) {
11315
+ if (near[2] > index.maxElevation || near[2] < index.minElevation) return null;
11316
+ } else {
11317
+ const tHigh = (index.maxElevation - near[2]) / dz;
11318
+ const tLow = (index.minElevation - near[2]) / dz;
11319
+ tStart = Math.max(tStart, Math.min(tHigh, tLow));
11320
+ tEnd = Math.min(tEnd, Math.max(tHigh, tLow));
11321
+ if (tStart > tEnd) return null;
11322
+ }
11323
+ const horizontalLength = Math.hypot(dx, dy);
11324
+ const samples = clamp(Math.ceil(horizontalLength * (tEnd - tStart) / TARGET_WORLD_STEP_PX), 1, MAX_SAMPLES);
11325
+ let previousT = 0;
11326
+ let aboveTerrain = !mercatorIsBelowTerrain(ray, 0);
11327
+ for (let i = 0; i <= samples; i++) {
11328
+ const t = tStart + (tEnd - tStart) * i / samples;
11329
+ if (!aboveTerrain) aboveTerrain = !mercatorIsBelowTerrain(ray, t);
11330
+ else if (mercatorIsBelowTerrain(ray, t)) {
11331
+ const { lo, hi } = bisect(ray, mercatorIsBelowTerrain, previousT, t, MERCATOR_BISECT_EPSILON_WORLD_PX / horizontalLength);
11332
+ const sampleLo = mercatorSampleAt(ray, lo);
11333
+ const sampleHi = mercatorSampleAt(ray, hi);
11334
+ const fLo = near[2] + lo * dz - sampleLo.elevation;
11335
+ const fHi = near[2] + hi * dz - sampleHi.elevation;
11336
+ const hit = sampleLo.covered && fLo > fHi ? clamp(lo + fLo * (hi - lo) / (fLo - fHi), lo, hi) : hi;
11337
+ return new MercatorCoordinate((near[0] + hit * dx) / worldSize, (near[1] + hit * dy) / worldSize, mercatorSampleAt(ray, hit).elevation);
11338
+ }
11339
+ previousT = t;
11340
+ }
11341
+ return null;
11342
+ }
10236
11343
  /**
10237
- * Intersects the ray through a screen point with the horizontal plane at `z`,
10238
- * given in meters relative to the plane at the center's elevation (not mercator units).
11344
+ * Returns the segment of the ray through the given screen pixel that lies inside the view frustum.
10239
11345
  */
10240
- screenPointToMercatorCoordinateAtZ(p, z) {
10241
- const targetZ = z ? z : 0;
11346
+ getRaySegmentFromPixel(p) {
10242
11347
  const coord0 = [
10243
11348
  p.x,
10244
11349
  p.y,
@@ -10255,14 +11360,29 @@ var MercatorTransform = class MercatorTransform {
10255
11360
  transformMat4(coord1, coord1, this._pixelMatrixInverse);
10256
11361
  const w0 = coord0[3];
10257
11362
  const w1 = coord1[3];
10258
- const x0 = coord0[0] / w0;
10259
- const x1 = coord1[0] / w1;
10260
- const y0 = coord0[1] / w0;
10261
- const y1 = coord1[1] / w1;
10262
- const z0 = coord0[2] / w0;
10263
- const z1 = coord1[2] / w1;
10264
- const t = z0 === z1 ? 0 : (targetZ - z0) / (z1 - z0);
10265
- return new MercatorCoordinate(interpolateFactory.number(x0, x1, t) / this.worldSize, interpolateFactory.number(y0, y1, t) / this.worldSize, targetZ);
11363
+ const elevation = this.elevation;
11364
+ return {
11365
+ near: [
11366
+ coord0[0] / w0,
11367
+ coord0[1] / w0,
11368
+ coord0[2] / w0 + elevation
11369
+ ],
11370
+ far: [
11371
+ coord1[0] / w1,
11372
+ coord1[1] / w1,
11373
+ coord1[2] / w1 + elevation
11374
+ ]
11375
+ };
11376
+ }
11377
+ /**
11378
+ * Intersects the ray through a screen point with the horizontal plane at `z`,
11379
+ * given in meters relative to the plane at the center's elevation (not mercator units).
11380
+ */
11381
+ screenPointToMercatorCoordinateAtZ(p, z) {
11382
+ const targetZ = z ? z : 0;
11383
+ const { near, far } = this.getRaySegmentFromPixel(p);
11384
+ const t = near[2] === far[2] ? 0 : (targetZ + this.elevation - near[2]) / (far[2] - near[2]);
11385
+ return new MercatorCoordinate(interpolateFactory.number(near[0], far[0], t) / this.worldSize, interpolateFactory.number(near[1], far[1], t) / this.worldSize, targetZ);
10266
11386
  }
10267
11387
  /**
10268
11388
  * Given a coordinate, return the screen point that corresponds to it
@@ -10286,7 +11406,7 @@ var MercatorTransform = class MercatorTransform {
10286
11406
  return new LngLatBounds().extend(this.screenPointToLocation(new Point(0, top))).extend(this.screenPointToLocation(new Point(this._helper._width, top))).extend(this.screenPointToLocation(new Point(this._helper._width, this._helper._height))).extend(this.screenPointToLocation(new Point(0, this._helper._height)));
10287
11407
  }
10288
11408
  isPointOnMapSurface(p, terrain) {
10289
- if (terrain) return terrain.pointCoordinate(p) != null;
11409
+ if (terrain) return this.screenTerrainPointToMercatorCoordinate(p, terrain) != null;
10290
11410
  return p.y > this.height / 2 - getMercatorHorizon(this);
10291
11411
  }
10292
11412
  calculatePosMatrix(tileID, aligned = false, useFloat32 = false) {
@@ -10523,14 +11643,14 @@ var MercatorTransform = class MercatorTransform {
10523
11643
  getRayDirectionFromPixel(_p) {
10524
11644
  throw new Error("Not implemented.");
10525
11645
  }
10526
- projectTileCoordinates(x, y, unwrappedTileID, getElevation) {
11646
+ projectTileCoordinates(x, y, unwrappedTileID, elevation) {
10527
11647
  const matrix = this.calculatePosMatrix(unwrappedTileID);
10528
11648
  let pos;
10529
- if (getElevation) {
11649
+ if (elevation != null) {
10530
11650
  pos = [
10531
11651
  x,
10532
11652
  y,
10533
- getElevation(x, y),
11653
+ elevation,
10534
11654
  1
10535
11655
  ];
10536
11656
  transformMat4(pos, pos, matrix);
@@ -10584,6 +11704,12 @@ var MercatorTransform = class MercatorTransform {
10584
11704
  return this.calculatePosMatrix(tileID);
10585
11705
  }
10586
11706
  };
11707
+ function mercatorSampleAt(ray, t) {
11708
+ return sampleAt(ray.index, ray.exaggeration, (ray.near[0] + t * ray.dx) / ray.worldSize, (ray.near[1] + t * ray.dy) / ray.worldSize);
11709
+ }
11710
+ function mercatorIsBelowTerrain(ray, t) {
11711
+ return isBelowTerrainSample(mercatorSampleAt(ray, t), ray.near[2] + t * ray.dz);
11712
+ }
10587
11713
  //#endregion
10588
11714
  //#region src/geo/projection/camera_helper.ts
10589
11715
  /**
@@ -11234,6 +12360,36 @@ function interpolateLngLatForGlobe(start, deltaLng, deltaLat, t) {
11234
12360
  return new LngLat(interpolatedLng, interpolatedLat);
11235
12361
  }
11236
12362
  }
12363
+ /**
12364
+ * Returns the two intersection points of the ray and a sphere centered at the origin,
12365
+ * or null if no intersection occurs.
12366
+ * The intersections are encoded as the parameter for parametric ray equation,
12367
+ * with `tMin` being the first intersection and `tMax` being the second.
12368
+ * The quadratic is solved as suggested in Ray Tracing Gems, chapter 7, since the
12369
+ * schoolbook approach leads to floating point precision issues:
12370
+ * https://www.realtimerendering.com/raytracinggems/rtg/index.html
12371
+ * @param origin - The ray origin.
12372
+ * @param direction - The normalized ray direction.
12373
+ * @param radius - The sphere radius, defaults to the unit sphere of the planet.
12374
+ */
12375
+ function raySphereIntersection(origin, direction, radius = 1) {
12376
+ const originDotDirection = dot(origin, direction);
12377
+ const radiusSquared = radius * radius;
12378
+ const inner = createVec3f64();
12379
+ const scaledDir = createVec3f64();
12380
+ scale$2(scaledDir, direction, originDotDirection);
12381
+ sub(inner, origin, scaledDir);
12382
+ const discriminant = radiusSquared - dot(inner, inner);
12383
+ if (discriminant < 0) return null;
12384
+ const c = dot(origin, origin) - radiusSquared;
12385
+ const q = -originDotDirection + (originDotDirection < 0 ? 1 : -1) * Math.sqrt(discriminant);
12386
+ const t0 = c / q;
12387
+ const t1 = q;
12388
+ return {
12389
+ tMin: Math.min(t0, t1),
12390
+ tMax: Math.max(t0, t1)
12391
+ };
12392
+ }
11237
12393
  //#endregion
11238
12394
  //#region src/util/primitives/bounding_volume_cache.ts
11239
12395
  var BoundingVolumeCache = class {
@@ -11669,6 +12825,10 @@ function findAxisMinMax(axis, points) {
11669
12825
  }
11670
12826
  //#endregion
11671
12827
  //#region src/geo/projection/vertical_perspective_transform.ts
12828
+ const GLOBE_SAMPLES = 256;
12829
+ const GLOBE_BISECT_EPSILON_T = 1e-12;
12830
+ /** Latitudes outside the mercator range project past the world edge; the globe mesh still covers them. */
12831
+ const MAX_MERCATOR_Y = 1 - 1e-9;
11672
12832
  var VerticalPerspectiveTransform = class VerticalPerspectiveTransform {
11673
12833
  get pixelsToClipSpaceMatrix() {
11674
12834
  return this._helper.pixelsToClipSpaceMatrix;
@@ -12008,10 +13168,10 @@ var VerticalPerspectiveTransform = class VerticalPerspectiveTransform {
12008
13168
  const angular = mercatorCoordinatesToAngularCoordinatesRadians(mercator.x, mercator.y);
12009
13169
  return this.getCircleRadiusCorrection() / Math.cos(angular[1]);
12010
13170
  }
12011
- projectTileCoordinates(x, y, unwrappedTileID, getElevation) {
13171
+ projectTileCoordinates(x, y, unwrappedTileID, elevation) {
12012
13172
  const canonical = unwrappedTileID.canonical;
12013
13173
  const spherePos = projectTileCoordinatesToSphere(x, y, canonical.x, canonical.y, canonical.z);
12014
- const vectorMultiplier = 1 + (getElevation ? getElevation(x, y) : 0) / earthRadius;
13174
+ const vectorMultiplier = 1 + (elevation ?? 0) / earthRadius;
12015
13175
  const pos = [
12016
13176
  spherePos[0] * vectorMultiplier,
12017
13177
  spherePos[1] * vectorMultiplier,
@@ -12271,11 +13431,41 @@ var VerticalPerspectiveTransform = class VerticalPerspectiveTransform {
12271
13431
  }
12272
13432
  screenPointToMercatorCoordinate(p, terrain) {
12273
13433
  if (terrain) {
12274
- const coordinate = terrain.pointCoordinate(p);
13434
+ const coordinate = this.screenTerrainPointToMercatorCoordinate(p, terrain);
12275
13435
  if (coordinate) return coordinate;
12276
13436
  }
12277
13437
  return MercatorCoordinate.fromLngLat(this.unprojectScreenPoint(p));
12278
13438
  }
13439
+ /** {@inheritDoc ITransform.screenTerrainPointToMercatorCoordinate} */
13440
+ screenTerrainPointToMercatorCoordinate(p, terrain) {
13441
+ const index = terrain.getCoverageIndex();
13442
+ if (!index) return null;
13443
+ const origin = this.cameraPosition;
13444
+ const direction = this.getRayDirectionFromPixel(p);
13445
+ const outer = raySphereIntersection(origin, direction, 1 + index.maxElevation / earthRadius);
13446
+ if (!outer) return null;
13447
+ const inner = raySphereIntersection(origin, direction, 1 + index.minElevation / earthRadius);
13448
+ const tStart = Math.max(outer.tMin, 0);
13449
+ const tEnd = inner ? inner.tMin : outer.tMax;
13450
+ if (tEnd <= tStart) return null;
13451
+ const ray = {
13452
+ index,
13453
+ exaggeration: terrain.exaggeration,
13454
+ origin,
13455
+ direction
13456
+ };
13457
+ let previousT = 0;
13458
+ for (let i = 0; i <= GLOBE_SAMPLES; i++) {
13459
+ const t = tStart + (tEnd - tStart) * i / GLOBE_SAMPLES;
13460
+ if (globeIsBelowTerrain(ray, t)) {
13461
+ const { hi } = bisect(ray, globeIsBelowTerrain, previousT, t, GLOBE_BISECT_EPSILON_T);
13462
+ const { sample, mercator } = globeSampleAt(ray, hi);
13463
+ return new MercatorCoordinate(mercator.x, mercator.y, sample.elevation);
13464
+ }
13465
+ previousT = t;
13466
+ }
13467
+ return null;
13468
+ }
12279
13469
  screenPointToLocation(p, terrain) {
12280
13470
  return this.screenPointToMercatorCoordinate(p, terrain)?.toLngLat();
12281
13471
  }
@@ -12284,8 +13474,7 @@ var VerticalPerspectiveTransform = class VerticalPerspectiveTransform {
12284
13474
  }
12285
13475
  isPointOnMapSurface(p, _terrain) {
12286
13476
  const rayOrigin = this._cameraPosition;
12287
- const rayDirection = this.getRayDirectionFromPixel(p);
12288
- return !!this.rayPlanetIntersection(rayOrigin, rayDirection);
13477
+ return !!raySphereIntersection(rayOrigin, this.getRayDirectionFromPixel(p));
12289
13478
  }
12290
13479
  /**
12291
13480
  * Computes normalized direction of a ray from the camera to the given screen pixel.
@@ -12330,33 +13519,6 @@ var VerticalPerspectiveTransform = class VerticalPerspectiveTransform {
12330
13519
  return projected[0] > -1 && projected[0] < 1 && projected[1] > -1 && projected[1] < 1 && projected[2] > -1 && projected[2] < 1;
12331
13520
  }
12332
13521
  /**
12333
- * Returns the two intersection points of the ray and the planet's sphere,
12334
- * or null if no intersection occurs.
12335
- * The intersections are encoded as the parameter for parametric ray equation,
12336
- * with `tMin` being the first intersection and `tMax` being the second.
12337
- * Eg. the nearer intersection point can then be computed as `origin + direction * tMin`.
12338
- * @param origin - The ray origin.
12339
- * @param direction - The normalized ray direction.
12340
- */
12341
- rayPlanetIntersection(origin, direction) {
12342
- const originDotDirection = dot(origin, direction);
12343
- const planetRadiusSquared = 1;
12344
- const inner = createVec3f64();
12345
- const scaledDir = createVec3f64();
12346
- scale$2(scaledDir, direction, originDotDirection);
12347
- sub(inner, origin, scaledDir);
12348
- const discriminant = planetRadiusSquared - dot(inner, inner);
12349
- if (discriminant < 0) return null;
12350
- const c = dot(origin, origin) - planetRadiusSquared;
12351
- const q = -originDotDirection + (originDotDirection < 0 ? 1 : -1) * Math.sqrt(discriminant);
12352
- const t0 = c / q;
12353
- const t1 = q;
12354
- return {
12355
- tMin: Math.min(t0, t1),
12356
- tMax: Math.max(t0, t1)
12357
- };
12358
- }
12359
- /**
12360
13522
  * @internal
12361
13523
  * Returns a {@link LngLat} representing geographical coordinates that correspond to the specified pixel coordinates.
12362
13524
  * Note: if the point does not lie on the globe, returns a location on the visible globe horizon (edge) that is
@@ -12367,7 +13529,7 @@ var VerticalPerspectiveTransform = class VerticalPerspectiveTransform {
12367
13529
  unprojectScreenPoint(p) {
12368
13530
  const rayOrigin = this._cameraPosition;
12369
13531
  const rayDirection = this.getRayDirectionFromPixel(p);
12370
- const intersection = this.rayPlanetIntersection(rayOrigin, rayDirection);
13532
+ const intersection = raySphereIntersection(rayOrigin, rayDirection);
12371
13533
  if (intersection) {
12372
13534
  const intersectionPoint = createVec3f64();
12373
13535
  add(intersectionPoint, rayOrigin, [
@@ -12421,6 +13583,30 @@ var VerticalPerspectiveTransform = class VerticalPerspectiveTransform {
12421
13583
  }
12422
13584
  getFastPathSimpleProjectionMatrix(_tileID) {}
12423
13585
  };
13586
+ function globeSampleAt(ray, t) {
13587
+ const position = createVec3f64();
13588
+ scaleAndAdd(position, ray.origin, ray.direction, t);
13589
+ const radius = length$1(position);
13590
+ const surface = createVec3f64();
13591
+ scale$2(surface, position, 1 / radius);
13592
+ const lngLat = sphereSurfacePointToCoordinates(surface);
13593
+ const projected = MercatorCoordinate.fromLngLat(lngLat);
13594
+ const mercator = new MercatorCoordinate(projected.x, clamp(projected.y, 0, MAX_MERCATOR_Y));
13595
+ const sample = sampleAt(ray.index, ray.exaggeration, mercator.x, mercator.y);
13596
+ const elevation = Math.abs(lngLat.lat) > 85.051129 ? 0 : sample.elevation;
13597
+ return {
13598
+ sample: {
13599
+ ...sample,
13600
+ elevation
13601
+ },
13602
+ radius,
13603
+ mercator
13604
+ };
13605
+ }
13606
+ function globeIsBelowTerrain(ray, t) {
13607
+ const { sample, radius } = globeSampleAt(ray, t);
13608
+ return isBelowTerrainSample(sample, (radius - 1) * earthRadius);
13609
+ }
12424
13610
  //#endregion
12425
13611
  //#region src/geo/projection/globe_transform.ts
12426
13612
  /**
@@ -12703,8 +13889,8 @@ var GlobeTransform = class GlobeTransform {
12703
13889
  const verticalCorrection = this._verticalPerspectiveTransform.getPitchedTextCorrection(textAnchorX, textAnchorY, tileID);
12704
13890
  return lerp(mercatorCorrection, verticalCorrection, this._globeness);
12705
13891
  }
12706
- projectTileCoordinates(x, y, unwrappedTileID, getElevation) {
12707
- return this.currentTransform.projectTileCoordinates(x, y, unwrappedTileID, getElevation);
13892
+ projectTileCoordinates(x, y, unwrappedTileID, elevation) {
13893
+ return this.currentTransform.projectTileCoordinates(x, y, unwrappedTileID, elevation);
12708
13894
  }
12709
13895
  _calcMatrices() {
12710
13896
  if (!this._helper._width || !this._helper._height) return;
@@ -12777,6 +13963,10 @@ var GlobeTransform = class GlobeTransform {
12777
13963
  screenPointToMercatorCoordinate(p, terrain) {
12778
13964
  return this.currentTransform.screenPointToMercatorCoordinate(p, terrain);
12779
13965
  }
13966
+ /** {@inheritDoc ITransform.screenTerrainPointToMercatorCoordinate} */
13967
+ screenTerrainPointToMercatorCoordinate(p, terrain) {
13968
+ return this.currentTransform.screenTerrainPointToMercatorCoordinate(p, terrain);
13969
+ }
12780
13970
  screenPointToLocation(p, terrain) {
12781
13971
  return this.currentTransform.screenPointToLocation(p, terrain);
12782
13972
  }
@@ -13286,6 +14476,8 @@ var Style = class extends Evented {
13286
14476
  this._glyphsDidChange = false;
13287
14477
  this._updatedPaintProps = {};
13288
14478
  this._layerOrderChanged = false;
14479
+ this._symbolPlacementTriggered = false;
14480
+ this._placedProjectionTransition = void 0;
13289
14481
  this.crossTileSymbolIndex = new ((this.crossTileSymbolIndex?.constructor) || Object)();
13290
14482
  this.pauseablePlacement = void 0;
13291
14483
  this.placement = void 0;
@@ -13380,6 +14572,7 @@ var Style = class extends Evented {
13380
14572
  if (nextState.sprite) this._loadSprite(nextState.sprite);
13381
14573
  else this.imageManager.setLoaded(true);
13382
14574
  this.glyphManager.setURL(nextState.glyphs);
14575
+ this.glyphManager.setFontFaces(nextState["font-faces"]);
13383
14576
  this._createLayers();
13384
14577
  this.light = new Light(this.stylesheet.light ?? {}, this._globalState);
13385
14578
  this._setProjectionInternal(this.stylesheet.projection?.type || "mercator");
@@ -13643,6 +14836,9 @@ var Style = class extends Evented {
13643
14836
  case "setGlyphs":
13644
14837
  operations.push(() => this.setGlyphs.apply(this, op.args));
13645
14838
  break;
14839
+ case "setFontFaces":
14840
+ operations.push(() => this.setFontFaces.apply(this, op.args));
14841
+ break;
13646
14842
  case "setSprite":
13647
14843
  operations.push(() => this.setSprite.apply(this, op.args));
13648
14844
  break;
@@ -13968,6 +15164,7 @@ var Style = class extends Evented {
13968
15164
  if (isRasterStyleLayer(layer) && name === "raster-fade-duration") this.tileManagers[layer.source].setRasterFadeDuration(value);
13969
15165
  this._changed = true;
13970
15166
  this._updatedPaintProps[layer.id] = true;
15167
+ if (layer.type === "symbol") this.triggerSymbolPlacement();
13971
15168
  this._serializedLayers = null;
13972
15169
  }
13973
15170
  getPaintProperty(layer, name) {
@@ -14066,8 +15263,10 @@ var Style = class extends Evented {
14066
15263
  pitch: myStyleSheet.pitch,
14067
15264
  sprite: myStyleSheet.sprite,
14068
15265
  glyphs: myStyleSheet.glyphs,
15266
+ "font-faces": myStyleSheet["font-faces"],
14069
15267
  transition: myStyleSheet.transition,
14070
15268
  projection: myStyleSheet.projection,
15269
+ state: myStyleSheet.state,
14071
15270
  sources,
14072
15271
  layers,
14073
15272
  terrain
@@ -14225,6 +15424,7 @@ var Style = class extends Evented {
14225
15424
  for (const key in this.tileManagers) this.tileManagers[key].reload();
14226
15425
  }
14227
15426
  _validate(validate, key, value, props, options = {}) {
15427
+ if (options.validate === false) return false;
14228
15428
  return validateAndEmit(this, validate, {
14229
15429
  key,
14230
15430
  style: this.serialize(),
@@ -14270,6 +15470,26 @@ var Style = class extends Evented {
14270
15470
  _generateCollisionBoxes() {
14271
15471
  for (const id in this.tileManagers) this._reloadSource(id);
14272
15472
  }
15473
+ /**
15474
+ * Re-places symbols on the next frame.
15475
+ * Placement is skipped while its inputs look unchanged, so call this when something
15476
+ * the map cannot see for itself has moved symbols.
15477
+ */
15478
+ triggerSymbolPlacement() {
15479
+ this._symbolPlacementTriggered = true;
15480
+ }
15481
+ /**
15482
+ * Whether re-running symbol placement could produce a different result than the last run.
15483
+ * Every camera and viewport parameter placement can observe perturbs the model-view-projection
15484
+ * matrix, so comparing the matrix covers them all. Inputs the matrix cannot see arrive as
15485
+ * {@link triggerSymbolPlacement} calls.
15486
+ *
15487
+ * @internal
15488
+ */
15489
+ _placementInputsChanged(transform, showCollisionBoxes, crossSourceCollisions) {
15490
+ const lastPlacement = this.pauseablePlacement;
15491
+ return !lastPlacement || this._symbolPlacementTriggered || this._placedProjectionTransition !== this.projection?.transitionState || lastPlacement._showCollisionBoxes !== showCollisionBoxes || lastPlacement.placement.collisionGroups.crossSourceCollisions !== crossSourceCollisions || lastPlacement.placement.transform.renderWorldCopies !== transform.renderWorldCopies || !exactEquals(lastPlacement.placement.transform.modelViewProjectionMatrix, transform.modelViewProjectionMatrix);
15492
+ }
14273
15493
  _updatePlacement(transform, showCollisionBoxes, fadeDuration, crossSourceCollisions, forceFullPlacement = false) {
14274
15494
  let symbolBucketsChanged = false;
14275
15495
  let placementCommitted = false;
@@ -14286,12 +15506,17 @@ var Style = class extends Evented {
14286
15506
  }
14287
15507
  this.crossTileSymbolIndex.pruneUnusedLayers(this._order);
14288
15508
  forceFullPlacement ||= this._layerOrderChanged || fadeDuration === 0;
14289
- if (forceFullPlacement || !this.pauseablePlacement || this.pauseablePlacement.isDone() && !this.placement.stillRecent(now(), transform.zoom)) {
15509
+ const placementInputsChanged = symbolBucketsChanged || this._placementInputsChanged(transform, showCollisionBoxes, crossSourceCollisions);
15510
+ const placementSettled = this.pauseablePlacement?.isDone() && !this.placement.stillRecent(now(), transform.zoom);
15511
+ if (forceFullPlacement || !this.pauseablePlacement || placementSettled && (placementInputsChanged || this.placement.stale)) {
15512
+ this._symbolPlacementTriggered = false;
15513
+ this._placedProjectionTransition = this.projection?.transitionState;
14290
15514
  this.pauseablePlacement = new PauseablePlacement(transform, this.map.terrain, this._order, forceFullPlacement, showCollisionBoxes, fadeDuration, crossSourceCollisions, this.placement);
14291
15515
  this._layerOrderChanged = false;
14292
15516
  }
14293
- if (this.pauseablePlacement.isDone()) this.placement.setStale();
14294
- else {
15517
+ if (this.pauseablePlacement.isDone()) {
15518
+ if (placementInputsChanged) this.placement.setStale();
15519
+ } else {
14295
15520
  this.pauseablePlacement.continuePlacement(this._order, this._layers, layerTiles);
14296
15521
  if (this.pauseablePlacement.isDone()) {
14297
15522
  this.placement = this.pauseablePlacement.commit(now());
@@ -14328,11 +15553,22 @@ var Style = class extends Evented {
14328
15553
  setGlyphs(glyphsUrl, options = {}) {
14329
15554
  this._checkLoaded();
14330
15555
  if (glyphsUrl && this._validate(validateStyle.glyphs, "glyphs", glyphsUrl, null, options)) return;
15556
+ this._changed = true;
14331
15557
  this._glyphsDidChange = true;
14332
15558
  this.stylesheet.glyphs = glyphsUrl;
14333
15559
  this.glyphManager.entries = {};
14334
15560
  this.glyphManager.setURL(glyphsUrl);
14335
15561
  }
15562
+ getFontFaces() {
15563
+ return this.stylesheet["font-faces"] || null;
15564
+ }
15565
+ setFontFaces(fontFaces) {
15566
+ this._checkLoaded();
15567
+ this._changed = true;
15568
+ this._glyphsDidChange = true;
15569
+ this.stylesheet["font-faces"] = fontFaces;
15570
+ this.glyphManager.setFontFaces(fontFaces);
15571
+ }
14336
15572
  async getDashes(mapId, params) {
14337
15573
  const result = {};
14338
15574
  for (const [key, dash] of Object.entries(params.dashes)) result[key] = this.lineAtlas.getDash(dash.dasharray, dash.round);
@@ -14556,11 +15792,6 @@ const terrainUniforms = (context, locations) => ({
14556
15792
  "u_is_globe_mode": new Uniform1f(context, locations.u_is_globe_mode)
14557
15793
  });
14558
15794
  const terrainDepthUniforms = (context, locations) => ({ "u_ele_delta": new Uniform1f(context, locations.u_ele_delta) });
14559
- const terrainCoordsUniforms = (context, locations) => ({
14560
- "u_texture": new Uniform1i(context, locations.u_texture),
14561
- "u_terrain_coords_id": new Uniform1f(context, locations.u_terrain_coords_id),
14562
- "u_ele_delta": new Uniform1f(context, locations.u_ele_delta)
14563
- });
14564
15795
  const terrainUniformValues = (eleDelta, fogMatrix, sky, pitch, isGlobeMode) => ({
14565
15796
  "u_texture": 0,
14566
15797
  "u_ele_delta": eleDelta,
@@ -14573,11 +15804,6 @@ const terrainUniformValues = (eleDelta, fogMatrix, sky, pitch, isGlobeMode) => (
14573
15804
  "u_is_globe_mode": isGlobeMode ? 1 : 0
14574
15805
  });
14575
15806
  const terrainDepthUniformValues = (eleDelta) => ({ "u_ele_delta": eleDelta });
14576
- const terrainCoordsUniformValues = (coordsId, eleDelta) => ({
14577
- "u_terrain_coords_id": coordsId / 255,
14578
- "u_texture": 0,
14579
- "u_ele_delta": eleDelta
14580
- });
14581
15807
  //#endregion
14582
15808
  //#region src/webgl/program/projection_program.ts
14583
15809
  const projectionUniforms = (context, locations) => ({
@@ -15207,7 +16433,8 @@ const symbolIconUniforms = (context, locations) => ({
15207
16433
  "u_texture": new Uniform1i(context, locations.u_texture),
15208
16434
  "u_translation": new Uniform2f(context, locations.u_translation),
15209
16435
  "u_pitched_scale": new Uniform1f(context, locations.u_pitched_scale),
15210
- "u_is_offset": new Uniform1i(context, locations.u_is_offset)
16436
+ "u_is_offset": new Uniform1i(context, locations.u_is_offset),
16437
+ "u_height_anchor_ground": new Uniform1i(context, locations.u_height_anchor_ground)
15211
16438
  });
15212
16439
  const symbolSDFUniforms = (context, locations) => ({
15213
16440
  "u_is_size_zoom_constant": new Uniform1i(context, locations.u_is_size_zoom_constant),
@@ -15233,7 +16460,8 @@ const symbolSDFUniforms = (context, locations) => ({
15233
16460
  "u_is_plain": new Uniform1i(context, locations.u_is_plain),
15234
16461
  "u_translation": new Uniform2f(context, locations.u_translation),
15235
16462
  "u_pitched_scale": new Uniform1f(context, locations.u_pitched_scale),
15236
- "u_is_offset": new Uniform1i(context, locations.u_is_offset)
16463
+ "u_is_offset": new Uniform1i(context, locations.u_is_offset),
16464
+ "u_height_anchor_ground": new Uniform1i(context, locations.u_height_anchor_ground)
15237
16465
  });
15238
16466
  const symbolTextAndIconUniforms = (context, locations) => ({
15239
16467
  "u_is_size_zoom_constant": new Uniform1i(context, locations.u_is_size_zoom_constant),
@@ -15260,9 +16488,10 @@ const symbolTextAndIconUniforms = (context, locations) => ({
15260
16488
  "u_is_halo": new Uniform1i(context, locations.u_is_halo),
15261
16489
  "u_translation": new Uniform2f(context, locations.u_translation),
15262
16490
  "u_pitched_scale": new Uniform1f(context, locations.u_pitched_scale),
15263
- "u_is_offset": new Uniform1i(context, locations.u_is_offset)
16491
+ "u_is_offset": new Uniform1i(context, locations.u_is_offset),
16492
+ "u_height_anchor_ground": new Uniform1i(context, locations.u_height_anchor_ground)
15264
16493
  });
15265
- const symbolIconUniformValues = (functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, isText, texSize, pitchedScale, isOffset) => {
16494
+ const symbolIconUniformValues = (functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, isText, texSize, pitchedScale, isOffset, heightAnchorGround) => {
15266
16495
  const transform = painter.transform;
15267
16496
  return {
15268
16497
  "u_is_size_zoom_constant": +(functionType === "constant" || functionType === "source"),
@@ -15284,20 +16513,21 @@ const symbolIconUniformValues = (functionType, size, rotateInShader, pitchWithMa
15284
16513
  "u_texture": 0,
15285
16514
  "u_translation": translation,
15286
16515
  "u_pitched_scale": pitchedScale,
15287
- "u_is_offset": isOffset
16516
+ "u_is_offset": isOffset,
16517
+ "u_height_anchor_ground": +heightAnchorGround
15288
16518
  };
15289
16519
  };
15290
- const symbolSDFUniformValues = (functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, isText, texSize, isHalo, pitchedScale, isOffset) => {
16520
+ const symbolSDFUniformValues = (functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, isText, texSize, isHalo, pitchedScale, isOffset, heightAnchorGround) => {
15291
16521
  const transform = painter.transform;
15292
- return extend(symbolIconUniformValues(functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, isText, texSize, pitchedScale, isOffset), {
16522
+ return extend(symbolIconUniformValues(functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, isText, texSize, pitchedScale, isOffset, heightAnchorGround), {
15293
16523
  "u_gamma_scale": pitchWithMap ? Math.cos(transform.pitch * Math.PI / 180) * transform.cameraToCenterDistance : 1,
15294
16524
  "u_device_pixel_ratio": painter.pixelRatio,
15295
16525
  "u_is_halo": isHalo ? 1 : 0,
15296
16526
  "u_is_plain": 1
15297
16527
  });
15298
16528
  };
15299
- const symbolTextAndIconUniformValues = (functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, texSizeSDF, texSizeIcon, pitchedScale, isOffset) => {
15300
- return extend(symbolSDFUniformValues(functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, true, texSizeSDF, true, pitchedScale, isOffset), {
16529
+ const symbolTextAndIconUniformValues = (functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, texSizeSDF, texSizeIcon, pitchedScale, isOffset, heightAnchorGround) => {
16530
+ return extend(symbolSDFUniformValues(functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, true, texSizeSDF, true, pitchedScale, isOffset, heightAnchorGround), {
15301
16531
  "u_texsize_icon": texSizeIcon,
15302
16532
  "u_texture_icon": 1
15303
16533
  });
@@ -15409,7 +16639,6 @@ const programUniforms = {
15409
16639
  backgroundPattern: backgroundPatternUniforms,
15410
16640
  terrain: terrainUniforms,
15411
16641
  terrainDepth: terrainDepthUniforms,
15412
- terrainCoords: terrainCoordsUniforms,
15413
16642
  atmosphere: atmosphereUniforms,
15414
16643
  sky: skyUniforms
15415
16644
  };
@@ -16383,7 +17612,7 @@ function drawSymbols(painter, tileManager, layer, coords, variableOffsets, rende
16383
17612
  const stencilMode = StencilMode.disabled;
16384
17613
  const colorMode = painter.colorModeForRenderPass();
16385
17614
  if (layer._unevaluatedLayout.hasValue("text-variable-anchor") || layer._unevaluatedLayout.hasValue("text-variable-anchor-offset")) updateVariableAnchors(coords, painter, layer, tileManager, layer.layout.get("text-rotation-alignment"), layer.layout.get("text-pitch-alignment"), layer.paint.get("text-translate"), layer.paint.get("text-translate-anchor"), variableOffsets);
16386
- if (layer.paint.get("icon-opacity").constantOr(1) !== 0) drawLayerSymbols(painter, tileManager, layer, coords, false, layer.paint.get("icon-translate"), layer.paint.get("icon-translate-anchor"), layer.layout.get("icon-rotation-alignment"), layer.layout.get("icon-pitch-alignment"), layer.layout.get("icon-keep-upright"), stencilMode, colorMode, isRenderingToTexture);
17615
+ if (layer.paint.get("icon-opacity").constantOr(1) !== 0) drawLayerSymbols(painter, tileManager, layer, coords, false, layer.paint.get("icon-translate"), layer.paint.get("icon-translate-anchor"), layer.layout.get("icon-rotation-alignment").constantOr("viewport"), layer.layout.get("icon-pitch-alignment"), layer.layout.get("icon-keep-upright"), stencilMode, colorMode, isRenderingToTexture);
16387
17616
  if (layer.paint.get("text-opacity").constantOr(1) !== 0) drawLayerSymbols(painter, tileManager, layer, coords, true, layer.paint.get("text-translate"), layer.paint.get("text-translate-anchor"), layer.layout.get("text-rotation-alignment"), layer.layout.get("text-pitch-alignment"), layer.layout.get("text-keep-upright"), stencilMode, colorMode, isRenderingToTexture);
16388
17617
  if (tileManager.map.showCollisionBoxes) {
16389
17618
  drawCollisionDebug(painter, tileManager, layer, coords, true);
@@ -16412,8 +17641,8 @@ function updateVariableAnchors(coords, painter, layer, tileManager, rotationAlig
16412
17641
  const updateTextFitIcon = layer.layout.get("icon-text-fit") !== "none" && bucket.hasIconData();
16413
17642
  if (size) {
16414
17643
  const tileScale = Math.pow(2, transform.zoom - tile.tileID.overscaledZ);
16415
- const getElevation = terrain ? (x, y) => terrain.getElevation(coord, x, y) : null;
16416
- updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, transform, pitchedLabelPlaneMatrix, tileScale, size, updateTextFitIcon, translatePosition(transform, tile, translate, translateAnchor), coord.toUnwrapped(), getElevation);
17644
+ const getElevation = terrain ? (x, y) => terrain.getElevation(coord, x, y) : void 0;
17645
+ updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, transform, pitchedLabelPlaneMatrix, tileScale, size, updateTextFitIcon, translatePosition(transform, tile, translate, translateAnchor), coord.toUnwrapped(), getElevation, layer.layout.get("symbol-height-anchor") === "ground");
16417
17646
  }
16418
17647
  }
16419
17648
  }
@@ -16423,14 +17652,14 @@ function getShiftedAnchor(projectedAnchorPoint, projectionContext, rotateWithMap
16423
17652
  let adjustedShift = shift.mult(pitchedTextShiftCorrection);
16424
17653
  if (!rotateWithMap) adjustedShift = adjustedShift.rotate(-transformAngle);
16425
17654
  const tileAnchorShifted = translatedAnchor.add(adjustedShift);
16426
- return projectWithMatrix(tileAnchorShifted.x, tileAnchorShifted.y, projectionContext.pitchedLabelPlaneMatrix, projectionContext.getElevation).point;
17655
+ return projectWithMatrix(tileAnchorShifted.x, tileAnchorShifted.y, projectionContext.pitchedLabelPlaneMatrix, elevationAt(projectionContext, tileAnchorShifted.x, tileAnchorShifted.y)).point;
16427
17656
  } else if (rotateWithMap) {
16428
17657
  const east = projectTileCoordinatesToLabelPlane(projectionContext.tileAnchorPoint.x + 1, projectionContext.tileAnchorPoint.y, projectionContext).point.sub(projectedAnchorPoint);
16429
17658
  const angle = Math.atan(east.y / east.x) + (east.x < 0 ? Math.PI : 0);
16430
17659
  return projectedAnchorPoint.add(shift.rotate(angle));
16431
17660
  } else return projectedAnchorPoint.add(shift);
16432
17661
  }
16433
- function updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, transform, pitchedLabelPlaneMatrix, tileScale, size, updateTextFitIcon, translation, unwrappedTileID, getElevation) {
17662
+ function updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, transform, pitchedLabelPlaneMatrix, tileScale, size, updateTextFitIcon, translation, unwrappedTileID, getElevation, heightAnchorGround) {
16434
17663
  const placedSymbols = bucket.text.placedSymbolArray;
16435
17664
  const dynamicTextLayoutVertexArray = bucket.text.dynamicLayoutVertexArray;
16436
17665
  const dynamicIconLayoutVertexArray = bucket.icon.dynamicLayoutVertexArray;
@@ -16445,6 +17674,8 @@ function updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, var
16445
17674
  const tileAnchor = new Point(symbol.anchorX, symbol.anchorY);
16446
17675
  const projectionContext = {
16447
17676
  getElevation,
17677
+ heightOffset: symbol.heightOffset,
17678
+ heightAnchorGround,
16448
17679
  width: transform.width,
16449
17680
  height: transform.height,
16450
17681
  pitchedLabelPlaneMatrix,
@@ -16554,10 +17785,11 @@ function drawLayerSymbols(painter, tileManager, layer, coords, isText, translate
16554
17785
  const hasVariableAnchors = hasVariablePlacement && bucket.hasTextData();
16555
17786
  const updateTextFitIcon = layer.layout.get("icon-text-fit") !== "none" && hasVariableAnchors && bucket.hasIconData();
16556
17787
  const isOffset = layer._unevaluatedLayout.hasValue("icon-offset");
17788
+ const heightAnchorGround = layer.layout.get("symbol-height-anchor") === "ground";
16557
17789
  if (alongLine) {
16558
17790
  const pitchedLabelPlaneMatrixInverse = create$1();
16559
17791
  fastInvertTransformMat4(pitchedLabelPlaneMatrixInverse, pitchedLabelPlaneMatrix);
16560
- const getElevation = painter.style.map.terrain ? (x, y) => painter.style.map.terrain.getElevation(coord, x, y) : null;
17792
+ const getElevation = painter.style.map.terrain ? (x, y) => painter.style.map.terrain.getElevation(coord, x, y) : void 0;
16561
17793
  updateLineLabels(bucket, painter, isText, pitchedLabelPlaneMatrix, pitchedLabelPlaneMatrixInverse, pitchWithMap, keepUpright, layer.layout.get("text-rotation-alignment") === "map", coord.toUnwrapped(), transform.width, transform.height, translation, getElevation);
16562
17794
  }
16563
17795
  const shaderVariableAnchor = isText && hasVariablePlacement || updateTextFitIcon;
@@ -16566,9 +17798,9 @@ function drawLayerSymbols(painter, tileManager, layer, coords, isText, translate
16566
17798
  const hasHalo = isSDF && layer.paint.get(isText ? "text-halo-width" : "icon-halo-width").constantOr(1) !== 0;
16567
17799
  let uniformValues;
16568
17800
  if (isSDF) {
16569
- if (!bucket.iconsInText) uniformValues = symbolSDFUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, alongLine, shaderVariableAnchor, painter, uLabelPlaneMatrix, glCoordMatrixForShader, translation, isText, texSize, hasHalo, pitchedTextRescaling, isOffset);
16570
- else uniformValues = symbolTextAndIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, alongLine, shaderVariableAnchor, painter, uLabelPlaneMatrix, glCoordMatrixForShader, translation, texSize, texSizeIcon, pitchedTextRescaling, isOffset);
16571
- } else uniformValues = symbolIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, alongLine, shaderVariableAnchor, painter, uLabelPlaneMatrix, glCoordMatrixForShader, translation, isText, texSize, pitchedTextRescaling, isOffset);
17801
+ if (!bucket.iconsInText) uniformValues = symbolSDFUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, alongLine, shaderVariableAnchor, painter, uLabelPlaneMatrix, glCoordMatrixForShader, translation, isText, texSize, hasHalo, pitchedTextRescaling, isOffset, heightAnchorGround);
17802
+ else uniformValues = symbolTextAndIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, alongLine, shaderVariableAnchor, painter, uLabelPlaneMatrix, glCoordMatrixForShader, translation, texSize, texSizeIcon, pitchedTextRescaling, isOffset, heightAnchorGround);
17803
+ } else uniformValues = symbolIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, alongLine, shaderVariableAnchor, painter, uLabelPlaneMatrix, glCoordMatrixForShader, translation, isText, texSize, pitchedTextRescaling, isOffset, heightAnchorGround);
16572
17804
  const state = {
16573
17805
  program,
16574
17806
  buffers,
@@ -17380,18 +18612,18 @@ function renderColorRelief(painter, tileManager, layer, coords, stencilModes, de
17380
18612
  }
17381
18613
  if (!dem?.data) continue;
17382
18614
  const textureStride = dem.stride;
17383
- const pixelData = dem.getPixels();
17384
18615
  context.activeTexture.set(gl.TEXTURE0);
17385
- context.pixelStoreUnpackPremultiplyAlpha.set(false);
17386
- tile.demTexture ||= painter.getTileTexture(textureStride);
17387
- if (tile.demTexture) {
17388
- const demTexture = tile.demTexture;
17389
- demTexture.update(pixelData, { premultiply: false });
17390
- demTexture.bind(textureFilter, gl.CLAMP_TO_EDGE);
17391
- } else {
17392
- tile.demTexture = new Texture(context, pixelData, gl.RGBA, { premultiply: false });
17393
- tile.demTexture.bind(textureFilter, gl.CLAMP_TO_EDGE);
18616
+ if (!tile.demTexture || tile.needsColorReliefPrepare) {
18617
+ context.pixelStoreUnpackPremultiplyAlpha.set(false);
18618
+ tile.demTexture ||= painter.getTileTexture(textureStride) ?? new Texture(context, {
18619
+ width: textureStride,
18620
+ height: textureStride,
18621
+ data: null
18622
+ }, gl.RGBA);
18623
+ tile.demTexture.update(dem.getPixels(), { premultiply: false });
18624
+ tile.needsColorReliefPrepare = false;
17394
18625
  }
18626
+ tile.demTexture.bind(textureFilter, gl.CLAMP_TO_EDGE);
17395
18627
  const mesh = projection.getMeshFromTileID(context, coord.canonical, useBorder, true, "raster");
17396
18628
  const terrainData = painter.getTerrainDataForTile(coord, isRenderingToTexture);
17397
18629
  const projectionData = transform.getProjectionData({
@@ -17746,7 +18978,7 @@ function drawDepth(painter, terrain) {
17746
18978
  const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, [0, 1]);
17747
18979
  const tiles = terrain.tileManager.getRenderableTiles();
17748
18980
  const program = painter.useProgram("terrainDepth");
17749
- context.bindFramebuffer.set(terrain.getFramebuffer("depth").framebuffer);
18981
+ context.bindFramebuffer.set(terrain.getFramebuffer().framebuffer);
17750
18982
  context.viewport.set([
17751
18983
  0,
17752
18984
  0,
@@ -17754,7 +18986,7 @@ function drawDepth(painter, terrain) {
17754
18986
  painter.height / devicePixelRatio
17755
18987
  ]);
17756
18988
  context.clear({
17757
- color: Color.transparent,
18989
+ color: Color.white,
17758
18990
  depth: 1
17759
18991
  });
17760
18992
  for (const tile of tiles) {
@@ -17776,54 +19008,6 @@ function drawDepth(painter, terrain) {
17776
19008
  painter.height
17777
19009
  ]);
17778
19010
  }
17779
- /**
17780
- * Redraw the Coords Framebuffers
17781
- * @param painter - the painter
17782
- * @param terrain - the terrain
17783
- */
17784
- function drawCoords(painter, terrain) {
17785
- const context = painter.context;
17786
- const gl = context.gl;
17787
- const tr = painter.transform;
17788
- const colorMode = ColorMode.unblended;
17789
- const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, [0, 1]);
17790
- const coords = terrain.getCoordsTexture();
17791
- const tiles = terrain.tileManager.getRenderableTiles();
17792
- const program = painter.useProgram("terrainCoords");
17793
- context.bindFramebuffer.set(terrain.getFramebuffer("coords").framebuffer);
17794
- context.viewport.set([
17795
- 0,
17796
- 0,
17797
- painter.width / devicePixelRatio,
17798
- painter.height / devicePixelRatio
17799
- ]);
17800
- context.clear({
17801
- color: Color.transparent,
17802
- depth: 1
17803
- });
17804
- terrain.coordsIndex = [];
17805
- for (const tile of tiles) {
17806
- const mesh = terrain.getTerrainMesh(tile.tileID);
17807
- const terrainData = terrain.getTerrainData(tile.tileID);
17808
- context.activeTexture.set(gl.TEXTURE0);
17809
- gl.bindTexture(gl.TEXTURE_2D, coords.texture);
17810
- const uniformValues = terrainCoordsUniformValues(255 - terrain.coordsIndex.length, terrain.getSkirtLength(tr.zoom));
17811
- const projectionData = tr.getProjectionData({
17812
- overscaledTileID: tile.tileID,
17813
- applyTerrainMatrix: false,
17814
- applyGlobeMatrix: true
17815
- });
17816
- program.draw(context, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData, "terrain", mesh.vertexBuffer, mesh.indexBuffer, mesh.segments);
17817
- terrain.coordsIndex.push(tile.tileID.key);
17818
- }
17819
- context.bindFramebuffer.set(null);
17820
- context.viewport.set([
17821
- 0,
17822
- 0,
17823
- painter.width,
17824
- painter.height
17825
- ]);
17826
- }
17827
19011
  function drawTerrain(painter, terrain, tiles, renderOptions) {
17828
19012
  const { isRenderingGlobe } = renderOptions;
17829
19013
  const context = painter.context;
@@ -17952,8 +19136,7 @@ const webglDrawFunctions = {
17952
19136
  custom: drawCustom,
17953
19137
  debug: drawDebug,
17954
19138
  debugPadding: drawDebugPadding,
17955
- terrainDepth: drawDepth,
17956
- terrainCoords: drawCoords
19139
+ terrainDepth: drawDepth
17957
19140
  };
17958
19141
  //#endregion
17959
19142
  //#region src/render/painter.ts
@@ -17972,7 +19155,6 @@ var Painter = class Painter {
17972
19155
  this._rttSharedFbo = null;
17973
19156
  this.terrainFacilitator = {
17974
19157
  depthDirty: true,
17975
- coordsDirty: false,
17976
19158
  matrix: identity(/* @__PURE__ */ new Float64Array(16)),
17977
19159
  renderTime: 0
17978
19160
  };
@@ -18273,7 +19455,7 @@ var Painter = class Painter {
18273
19455
  break;
18274
19456
  }
18275
19457
  }
18276
- this.maybeDrawDepth(false);
19458
+ this.maybeDrawDepth();
18277
19459
  if (this.renderToTexture) {
18278
19460
  this.renderToTexture.prepareForRender(this.style, this.transform.zoom);
18279
19461
  this.opaquePassCutoff = 0;
@@ -18335,30 +19517,20 @@ var Painter = class Painter {
18335
19517
  }
18336
19518
  /**
18337
19519
  * Update the depth framebuffer if the camera has moved or tiles have reloaded.
18338
- * Marks coords as depthDirty so they are re-rendered on next demand.
18339
19520
  */
18340
- maybeDrawDepth(requireExact) {
19521
+ maybeDrawDepth() {
18341
19522
  if (!this.style?.map?.terrain) return;
18342
19523
  const prevMatrix = this.terrainFacilitator.matrix;
18343
19524
  const currMatrix = this.transform.modelViewProjectionMatrix;
18344
19525
  let doUpdate = this.terrainFacilitator.depthDirty;
18345
- doUpdate ||= requireExact ? !exactEquals(prevMatrix, currMatrix) : !equals(prevMatrix, currMatrix);
19526
+ doUpdate ||= !equals(prevMatrix, currMatrix);
18346
19527
  doUpdate ||= this.style.map.terrain.tileManager.anyTilesAfterTime(this.terrainFacilitator.renderTime);
18347
19528
  if (!doUpdate) return;
18348
19529
  copy(prevMatrix, currMatrix);
18349
19530
  this.terrainFacilitator.renderTime = now();
18350
19531
  this.terrainFacilitator.depthDirty = false;
18351
- this.terrainFacilitator.coordsDirty = true;
18352
19532
  this.drawFunctions.terrainDepth(this, this.style.map.terrain);
18353
19533
  }
18354
- /**
18355
- * Render the coords framebuffer if it is coordsDirty
18356
- */
18357
- maybeDrawCoords() {
18358
- if (!this.style?.map?.terrain || !this.terrainFacilitator.coordsDirty) return;
18359
- this.terrainFacilitator.coordsDirty = false;
18360
- this.drawFunctions.terrainCoords(this, this.style.map.terrain);
18361
- }
18362
19534
  renderLayer(painter, tileManager, layer, coords, renderOptions) {
18363
19535
  if (layer.isHidden(this.transform.zoom)) return;
18364
19536
  if (layer.type !== "background" && layer.type !== "custom" && !(coords || []).length) return;
@@ -18551,7 +19723,8 @@ var Painter = class Painter {
18551
19723
  //#endregion
18552
19724
  //#region src/util/gpu_initialization_error.ts
18553
19725
  /**
18554
- * Thrown (via the map's `error` event) when a GPU rendering context cannot be created.
19726
+ * Thrown by the `Map` constructor when a GPU rendering context cannot be created,
19727
+ * or fired via the map's `error` event when recreating the context after a `webglcontextrestored` fails.
18555
19728
  *
18556
19729
  * Carries the canvas attributes that were requested and, when the browser provided one,
18557
19730
  * the originating `webglcontextcreationerror` `statusMessage`.
@@ -21094,7 +22267,7 @@ var HandlerManager = class {
21094
22267
  _terrainGestureElevation(terrain, around, aroundOnSurface, tr, combinedEventsInProgress) {
21095
22268
  if (!aroundOnSurface) return;
21096
22269
  if (!this._terrainMovement && (combinedEventsInProgress.drag || combinedEventsInProgress.zoom)) {
21097
- const anchor = terrain.pointCoordinate(around);
22270
+ const anchor = tr.screenTerrainPointToMercatorCoordinate(around, terrain);
21098
22271
  this._terrainGestureAnchorElevation = anchor ? anchor.z : null;
21099
22272
  }
21100
22273
  if (this._terrainGestureAnchorElevation === null) return;
@@ -21430,7 +22603,7 @@ var Camera = class extends Evented {
21430
22603
  let bearingChanged = false, pitchChanged = false;
21431
22604
  let rollChanged = false;
21432
22605
  const oldZoom = tr.zoom;
21433
- if (this.terrain) tr.setElevation(this.terrain.getElevationForLngLatZoom(options.center ? LngLat.convert(options.center) : tr.center, options.zoom || tr.tileZoom));
22606
+ if (this.terrain) tr.setElevation(this.terrain.getElevationForLngLat(options.center ? LngLat.convert(options.center) : tr.center, tr));
21434
22607
  this.cameraHelper.handleJumpToCenterZoom(tr, options);
21435
22608
  const zoomChanged = tr.zoom !== oldZoom;
21436
22609
  if ("elevation" in options && tr.elevation !== +options.elevation) tr.setElevation(+options.elevation);
@@ -21531,1232 +22704,532 @@ var Camera = class extends Evented {
21531
22704
  this._easeId = options.easeId;
21532
22705
  this._prepareEase(eventData, options.noMoveStart, currently);
21533
22706
  if (this.terrain) this._prepareElevation(easeHandler.elevationCenter);
21534
- this._ease((k) => {
21535
- easeHandler.easeFunc(k);
21536
- if (this.terrain && !options.freezeElevation) this._updateElevation(k);
21537
- this.applyUpdatedTransform(tr);
21538
- this._fireMoveEvents(eventData);
21539
- }, (interruptingEaseId) => {
21540
- if (this.terrain && options.freezeElevation) this._finalizeElevation();
21541
- this._afterEase(eventData, interruptingEaseId);
21542
- }, options);
21543
- return this;
21544
- }
21545
- _prepareEase(eventData, noMoveStart, currently = {}) {
21546
- this._moving = true;
21547
- if (!noMoveStart && !currently.moving) this.fire(new MapMovementEvent("movestart", eventData));
21548
- if (this._zooming && !currently.zooming) this.fire(new MapMovementEvent("zoomstart", eventData));
21549
- if (this._rotating && !currently.rotating) this.fire(new MapMovementEvent("rotatestart", eventData));
21550
- if (this._pitching && !currently.pitching) this.fire(new MapMovementEvent("pitchstart", eventData));
21551
- if (this._rolling && !currently.rolling) this.fire(new MapMovementEvent("rollstart", eventData));
21552
- }
21553
- _prepareElevation(center) {
21554
- this._elevationCenter = center;
21555
- this._elevationStart = this.transform.elevation;
21556
- this._elevationTarget = this.terrain.getElevationForLngLatZoom(center, this.transform.tileZoom);
21557
- this.elevationFreeze = true;
21558
- }
21559
- _updateElevation(k) {
21560
- if (this._elevationStart === void 0 || this._elevationCenter === void 0) this._prepareElevation(this.transform.center);
21561
- this.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._elevationCenter, this.transform.tileZoom));
21562
- const elevation = this.terrain.getElevationForLngLatZoom(this._elevationCenter, this.transform.tileZoom);
21563
- if (k < 1 && elevation !== this._elevationTarget) {
21564
- const pitch1 = this._elevationTarget - this._elevationStart;
21565
- const pitch2 = (elevation - (pitch1 * k + this._elevationStart)) / (1 - k);
21566
- this._elevationStart += k * (pitch1 - pitch2);
21567
- this._elevationTarget = elevation;
21568
- }
21569
- this.transform.setElevation(interpolateFactory.number(this._elevationStart, this._elevationTarget, k));
21570
- }
21571
- _finalizeElevation() {
21572
- this.elevationFreeze = false;
21573
- if (this.getCenterClampedToGround()) this.transform.recalculateZoomAndCenter(this.terrain);
21574
- }
21575
- /**
21576
- * @internal
21577
- * Called when the camera is about to be manipulated.
21578
- * If `transformCameraUpdate` is specified or terrain is enabled, a copy of
21579
- * the current transform is created to track the accumulated changes.
21580
- * This underlying transform represents the "desired state" proposed by input handlers / animations / UI controls.
21581
- * It may differ from the state used for rendering (`this.transform`).
21582
- * @returns Transform to apply changes to
21583
- */
21584
- getTransformForUpdate() {
21585
- if (!this.transformCameraUpdate && !this.terrain) return this.transform;
21586
- this._requestedCameraState ||= this.transform.clone();
21587
- return this._requestedCameraState;
21588
- }
21589
- /**
21590
- * @internal
21591
- * Checks the given transform for the camera being below terrain surface and
21592
- * returns new pitch and zoom to fix that.
21593
- *
21594
- * With the new pitch and zoom, the camera will be at the same ground
21595
- * position but at higher altitude. It will still point to the same spot on
21596
- * the map.
21597
- *
21598
- * @param tr - The transform to check.
21599
- */
21600
- _elevateCameraIfInsideTerrain(tr) {
21601
- if (!this.terrain && tr.elevation >= 0 && tr.pitch <= 90) return {};
21602
- const cameraLngLat = tr.getCameraLngLat();
21603
- const cameraAltitude = tr.getCameraAltitude();
21604
- const minAltitude = this.terrain ? this.terrain.getElevationForLngLatZoom(cameraLngLat, tr.zoom) : 0;
21605
- if (cameraAltitude < minAltitude) {
21606
- const newCamera = this.calculateCameraOptionsFromTo(cameraLngLat, minAltitude, tr.center, tr.elevation);
21607
- return {
21608
- pitch: newCamera.pitch,
21609
- zoom: newCamera.zoom
21610
- };
21611
- }
21612
- return {};
21613
- }
21614
- /**
21615
- * @internal
21616
- * Called after the camera is done being manipulated.
21617
- * @param tr - the requested camera end state
21618
- * If the camera is inside terrain, it gets elevated.
21619
- * Call `transformCameraUpdate` if present, and then apply the "approved" changes.
21620
- */
21621
- applyUpdatedTransform(tr) {
21622
- const modifiers = [];
21623
- modifiers.push((tr) => this._elevateCameraIfInsideTerrain(tr));
21624
- if (this.transformCameraUpdate) modifiers.push((tr) => this.transformCameraUpdate(tr));
21625
- if (!modifiers.length) return;
21626
- const finalTransform = tr.clone();
21627
- for (const modifier of modifiers) {
21628
- const nextTransform = finalTransform.clone();
21629
- const { center, zoom, roll, pitch, bearing, elevation } = modifier(nextTransform);
21630
- if (center) nextTransform.setCenter(center);
21631
- if (elevation !== void 0) nextTransform.setElevation(elevation);
21632
- if (zoom !== void 0) nextTransform.setZoom(zoom);
21633
- if (roll !== void 0) nextTransform.setRoll(roll);
21634
- if (pitch !== void 0) nextTransform.setPitch(pitch);
21635
- if (bearing !== void 0) nextTransform.setBearing(bearing);
21636
- finalTransform.apply(nextTransform, false);
21637
- }
21638
- this.transform.apply(finalTransform, false);
21639
- }
21640
- _fireMoveEvents(eventData) {
21641
- this.fire(new MapMovementEvent("move", eventData));
21642
- if (this._zooming) this.fire(new MapMovementEvent("zoom", eventData));
21643
- if (this._rotating) this.fire(new MapMovementEvent("rotate", eventData));
21644
- if (this._pitching) this.fire(new MapMovementEvent("pitch", eventData));
21645
- if (this._rolling) this.fire(new MapMovementEvent("roll", eventData));
21646
- }
21647
- _afterEase(eventData, easeId) {
21648
- if (this._easeId && easeId && this._easeId === easeId) return;
21649
- delete this._easeId;
21650
- const wasZooming = this._zooming;
21651
- const wasRotating = this._rotating;
21652
- const wasPitching = this._pitching;
21653
- const wasRolling = this._rolling;
21654
- this._moving = false;
21655
- this._zooming = false;
21656
- this._rotating = false;
21657
- this._pitching = false;
21658
- this._rolling = false;
21659
- this._padding = false;
21660
- if (wasZooming) this.fire(new MapMovementEvent("zoomend", eventData));
21661
- if (wasRotating) this.fire(new MapMovementEvent("rotateend", eventData));
21662
- if (wasPitching) this.fire(new MapMovementEvent("pitchend", eventData));
21663
- if (wasRolling) this.fire(new MapMovementEvent("rollend", eventData));
21664
- this.fire(new MapMovementEvent("moveend", eventData));
21665
- }
21666
- flyTo(options, eventData) {
21667
- if (!options.essential && browser.prefersReducedMotion) {
21668
- const coercedOptions = pick(options, [
21669
- "center",
21670
- "zoom",
21671
- "bearing",
21672
- "pitch",
21673
- "roll",
21674
- "elevation",
21675
- "padding"
21676
- ]);
21677
- return this.jumpTo(coercedOptions, eventData);
21678
- }
21679
- this.stop();
21680
- options = extend({
21681
- offset: [0, 0],
21682
- speed: 1.2,
21683
- curve: 1.42,
21684
- easing: defaultEasing
21685
- }, options);
21686
- if ("zoom" in options && this._zoomSnap) options.zoom = evaluateZoomSnap(options.zoom, this._zoomSnap);
21687
- const tr = this.getTransformForUpdate(), startBearing = tr.bearing, startPitch = tr.pitch, startRoll = tr.roll, startPadding = tr.padding;
21688
- const bearing = "bearing" in options ? this._normalizeBearing(options.bearing, startBearing) : startBearing;
21689
- const pitch = "pitch" in options ? +options.pitch : startPitch;
21690
- const roll = "roll" in options ? this._normalizeBearing(options.roll, startRoll) : startRoll;
21691
- const padding = "padding" in options ? options.padding : tr.padding;
21692
- const offsetAsPoint = Point.convert(options.offset);
21693
- let pointAtOffset = tr.centerPoint.add(offsetAsPoint);
21694
- const locationAtOffset = tr.screenPointToLocation(pointAtOffset);
21695
- const flyToHandler = this.cameraHelper.handleFlyTo(tr, {
21696
- bearing,
21697
- pitch,
21698
- roll,
21699
- padding,
21700
- locationAtOffset,
21701
- offsetAsPoint,
21702
- center: options.center,
21703
- minZoom: options.minZoom,
21704
- zoom: options.zoom
21705
- });
21706
- let rho = options.curve;
21707
- const w0 = Math.max(tr.width, tr.height);
21708
- const w1 = w0 / flyToHandler.scaleOfZoom;
21709
- const u1 = flyToHandler.pixelPathLength;
21710
- const wMax = w0 / flyToHandler.scaleOfMinZoom;
21711
- rho = Math.min(rho, Math.sqrt(wMax / u1 * 2));
21712
- const rho2 = rho * rho;
21713
- /**
21714
- * rᵢ: Returns the zoom-out factor at one end of the animation.
21715
- *
21716
- * @param descent - `true` for the descent, `false` for the ascent
21717
- */
21718
- function zoomOutFactor(descent) {
21719
- const b = (w1 * w1 - w0 * w0 + (descent ? -1 : 1) * rho2 * rho2 * u1 * u1) / (2 * (descent ? w1 : w0) * rho2 * u1);
21720
- return Math.log(Math.sqrt(b * b + 1) - b);
21721
- }
21722
- function sinh(n) {
21723
- return (Math.exp(n) - Math.exp(-n)) / 2;
21724
- }
21725
- function cosh(n) {
21726
- return (Math.exp(n) + Math.exp(-n)) / 2;
21727
- }
21728
- function tanh(n) {
21729
- return sinh(n) / cosh(n);
21730
- }
21731
- const r0 = zoomOutFactor(false);
21732
- let w = function(s) {
21733
- return cosh(r0) / cosh(r0 + rho * s);
21734
- };
21735
- let u = function(s) {
21736
- return w0 * ((cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2) / u1;
21737
- };
21738
- let S = (zoomOutFactor(true) - r0) / rho;
21739
- if (Math.abs(u1) < 2e-6 || !isFinite(S)) {
21740
- if (Math.abs(w0 - w1) < 1e-6) return this.easeTo(options, eventData);
21741
- const k = w1 < w0 ? -1 : 1;
21742
- S = Math.abs(Math.log(w1 / w0)) / rho;
21743
- u = () => 0;
21744
- w = (s) => Math.exp(k * rho * s);
21745
- }
21746
- if ("duration" in options) options.duration = +options.duration;
21747
- else {
21748
- const V = "screenSpeed" in options ? +options.screenSpeed / rho : +options.speed;
21749
- options.duration = 1e3 * S / V;
21750
- }
21751
- if (options.maxDuration && options.duration > options.maxDuration) options.duration = 0;
21752
- this._zooming = true;
21753
- this._rotating = startBearing !== bearing;
21754
- this._pitching = pitch !== startPitch;
21755
- this._rolling = roll !== startRoll;
21756
- this._padding = !tr.isPaddingEqual(padding);
21757
- this._prepareEase(eventData, false);
21758
- if (this.terrain) this._prepareElevation(flyToHandler.targetCenter);
21759
- this._ease((k) => {
21760
- const s = k * S;
21761
- const scale = 1 / w(s);
21762
- const centerFactor = u(s);
21763
- if (this._rotating) tr.setBearing(interpolateFactory.number(startBearing, bearing, k));
21764
- if (this._pitching) tr.setPitch(interpolateFactory.number(startPitch, pitch, k));
21765
- if (this._rolling) tr.setRoll(interpolateFactory.number(startRoll, roll, k));
21766
- if (this._padding) {
21767
- tr.interpolatePadding(startPadding, padding, k);
21768
- pointAtOffset = tr.centerPoint.add(offsetAsPoint);
21769
- }
21770
- flyToHandler.easeFunc(k, scale, centerFactor, pointAtOffset);
21771
- if (this.terrain && !options.freezeElevation) this._updateElevation(k);
21772
- this.applyUpdatedTransform(tr);
21773
- this._fireMoveEvents(eventData);
21774
- }, () => {
21775
- if (this.terrain && options.freezeElevation) this._finalizeElevation();
21776
- this._afterEase(eventData);
21777
- }, options);
21778
- return this;
21779
- }
21780
- isEasing() {
21781
- return !!this._easeFrameId;
21782
- }
21783
- stop(allowGestures) {
21784
- return this._stop(allowGestures);
21785
- }
21786
- _stop(allowGestures, easeId) {
21787
- if (this._easeFrameId) {
21788
- this._cancelRenderFrame(this._easeFrameId);
21789
- delete this._easeFrameId;
21790
- delete this._onEaseFrame;
21791
- }
21792
- if (this._onEaseEnd) {
21793
- const onEaseEnd = this._onEaseEnd;
21794
- delete this._onEaseEnd;
21795
- onEaseEnd.call(this, easeId);
21796
- }
21797
- if (!allowGestures) this._stopHandlers();
21798
- return this;
21799
- }
21800
- _ease(frame, finish, options) {
21801
- if (options.animate === false || options.duration === 0) {
21802
- frame(1);
21803
- finish();
21804
- } else {
21805
- this._easeStart = now();
21806
- this._easeOptions = options;
21807
- this._onEaseFrame = frame;
21808
- this._onEaseEnd = finish;
21809
- this._easeFrameId = this._requestRenderFrame(this._renderFrameCallback);
21810
- }
21811
- }
21812
- _normalizeBearing(bearing, currentBearing) {
21813
- bearing = wrap(bearing, -180, 180);
21814
- const diff = Math.abs(bearing - currentBearing);
21815
- if (Math.abs(bearing - 360 - currentBearing) < diff) bearing -= 360;
21816
- if (Math.abs(bearing + 360 - currentBearing) < diff) bearing += 360;
21817
- return bearing;
21818
- }
21819
- isMoving() {
21820
- return this._moving;
21821
- }
21822
- isZooming() {
21823
- return this._zooming;
21824
- }
21825
- isRotating() {
21826
- return this._rotating;
21827
- }
21828
- };
21829
- //#endregion
21830
- //#region src/ui/control/attribution_control.ts
21831
- const defaultAttributionControlOptions = {
21832
- compact: true,
21833
- customAttribution: "<a href=\"https://maplibre.org/\" target=\"_blank\">MapLibre</a>"
21834
- };
21835
- /**
21836
- * An `AttributionControl` control presents the map's attribution information. By default, the attribution control is expanded (regardless of map width).
21837
- * @group Markers and Controls
21838
- * @example
21839
- * ```ts
21840
- * let map = new Map({attributionControl: false})
21841
- * .addControl(new AttributionControl({
21842
- * compact: true
21843
- * }));
21844
- * ```
21845
- * @see [Change the default position for attribution](https://maplibre.org/maplibre-gl-js/docs/examples/change-the-default-position-for-attribution/)
21846
- */
21847
- var AttributionControl = class {
21848
- /**
21849
- * @param options - the attribution options
21850
- */
21851
- constructor(options = defaultAttributionControlOptions) {
21852
- this._toggleAttribution = () => {
21853
- if (this._container.classList.contains("maplibregl-compact")) {
21854
- if (this._container.classList.contains("maplibregl-compact-show")) {
21855
- this._container.setAttribute("open", "");
21856
- this._container.classList.remove("maplibregl-compact-show");
21857
- } else {
21858
- this._container.classList.add("maplibregl-compact-show");
21859
- this._container.removeAttribute("open");
21860
- }
21861
- }
21862
- };
21863
- this._updateData = (e) => {
21864
- if (e && (e.type === "terrain" || e.dataType === "style" || e.dataType === "source" && (e.sourceDataType === "metadata" || e.sourceDataType === "visibility"))) this._updateAttributions();
21865
- };
21866
- this._updateCompact = () => {
21867
- if (this._map.getCanvasContainer().offsetWidth <= 640 || this._compact) {
21868
- if (this._compact === false) this._container.setAttribute("open", "");
21869
- else if (!this._container.classList.contains("maplibregl-compact") && !this._container.classList.contains("maplibregl-attrib-empty")) {
21870
- this._container.setAttribute("open", "");
21871
- this._container.classList.add("maplibregl-compact", "maplibregl-compact-show");
21872
- }
21873
- } else {
21874
- this._container.setAttribute("open", "");
21875
- if (this._container.classList.contains("maplibregl-compact")) this._container.classList.remove("maplibregl-compact", "maplibregl-compact-show");
21876
- }
21877
- };
21878
- this._updateCompactMinimize = () => {
21879
- if (this._container.classList.contains("maplibregl-compact")) {
21880
- if (this._container.classList.contains("maplibregl-compact-show")) this._container.classList.remove("maplibregl-compact-show");
21881
- }
21882
- };
21883
- this.options = options;
21884
- }
21885
- getDefaultPosition() {
21886
- return "bottom-right";
21887
- }
21888
- /** {@inheritDoc IControl.onAdd} */
21889
- onAdd(map) {
21890
- this._map = map;
21891
- this._compact = this.options.compact;
21892
- this._container = DOM.create("details", "maplibregl-ctrl maplibregl-ctrl-attrib");
21893
- this._compactButton = DOM.create("summary", "maplibregl-ctrl-attrib-button", this._container);
21894
- this._compactButton.addEventListener("click", this._toggleAttribution);
21895
- this._setElementTitle(this._compactButton, "ToggleAttribution");
21896
- this._innerContainer = DOM.create("div", "maplibregl-ctrl-attrib-inner", this._container);
21897
- this._updateAttributions();
21898
- this._updateCompact();
21899
- this._map.on("styledata", this._updateData);
21900
- this._map.on("sourcedata", this._updateData);
21901
- this._map.on("terrain", this._updateData);
21902
- this._map.on("resize", this._updateCompact);
21903
- this._map.on("drag", this._updateCompactMinimize);
21904
- return this._container;
21905
- }
21906
- /** {@inheritDoc IControl.onRemove} */
21907
- onRemove() {
21908
- this._container.remove();
21909
- this._map.off("styledata", this._updateData);
21910
- this._map.off("sourcedata", this._updateData);
21911
- this._map.off("terrain", this._updateData);
21912
- this._map.off("resize", this._updateCompact);
21913
- this._map.off("drag", this._updateCompactMinimize);
21914
- this._map = void 0;
21915
- this._compact = void 0;
21916
- this._attribHTML = void 0;
21917
- }
21918
- _setElementTitle(element, title) {
21919
- const str = this._map._getUIString(`AttributionControl.${title}`);
21920
- element.title = str;
21921
- element.setAttribute("aria-label", str);
21922
- }
21923
- _updateAttributions() {
21924
- if (!this._map.style) return;
21925
- let attributions = [];
21926
- if (this.options.customAttribution) {
21927
- if (Array.isArray(this.options.customAttribution)) attributions = attributions.concat(this.options.customAttribution.map((attribution) => {
21928
- if (typeof attribution !== "string") return "";
21929
- return attribution;
21930
- }));
21931
- else if (typeof this.options.customAttribution === "string") attributions.push(this.options.customAttribution);
21932
- }
21933
- if (this._map.style.stylesheet) {
21934
- const stylesheet = this._map.style.stylesheet;
21935
- this.styleOwner = stylesheet.owner;
21936
- this.styleId = stylesheet.id;
21937
- }
21938
- const tileManagers = this._map.style.tileManagers;
21939
- for (const id in tileManagers) {
21940
- const tileManager = tileManagers[id];
21941
- if (tileManager.used || tileManager.usedForTerrain) {
21942
- const source = tileManager.getSource();
21943
- if (source.attribution && !attributions.includes(source.attribution)) attributions.push(source.attribution);
21944
- }
21945
- }
21946
- attributions = attributions.filter((e) => String(e).trim());
21947
- attributions.sort((a, b) => a.length - b.length);
21948
- attributions = attributions.filter((attrib, i) => {
21949
- for (let j = i + 1; j < attributions.length; j++) if (attributions[j].includes(attrib)) return false;
21950
- return true;
21951
- });
21952
- const attribHTML = attributions.join(" | ");
21953
- if (attribHTML === this._attribHTML) return;
21954
- this._attribHTML = attribHTML;
21955
- if (attributions.length) {
21956
- this._innerContainer.innerHTML = DOM.sanitize(attribHTML);
21957
- this._container.classList.remove("maplibregl-attrib-empty");
21958
- } else this._container.classList.add("maplibregl-attrib-empty");
21959
- this._updateCompact();
21960
- this._editLink = null;
21961
- }
21962
- };
21963
- //#endregion
21964
- //#region src/ui/control/logo_control.ts
21965
- /**
21966
- * A `LogoControl` is a control that adds the watermark.
21967
- *
21968
- * @group Markers and Controls
21969
- *
21970
- * @example
21971
- * ```ts
21972
- * map.addControl(new LogoControl({compact: false}));
21973
- * ```
21974
- **/
21975
- var LogoControl = class {
21976
- /**
21977
- * @param options - the control's options
21978
- */
21979
- constructor(options = {}) {
21980
- this._updateCompact = () => {
21981
- const containerChildren = this._container.children;
21982
- if (containerChildren.length) {
21983
- const anchor = containerChildren[0];
21984
- if (this._map.getCanvasContainer().offsetWidth <= 640 || this._compact) {
21985
- if (this._compact !== false) anchor.classList.add("maplibregl-compact");
21986
- } else anchor.classList.remove("maplibregl-compact");
21987
- }
21988
- };
21989
- this.options = options;
21990
- }
21991
- getDefaultPosition() {
21992
- return "bottom-left";
21993
- }
21994
- /** {@inheritDoc IControl.onAdd} */
21995
- onAdd(map) {
21996
- this._map = map;
21997
- this._compact = this.options?.compact;
21998
- this._container = DOM.create("div", "maplibregl-ctrl");
21999
- const anchor = DOM.create("a", "maplibregl-ctrl-logo");
22000
- anchor.target = "_blank";
22001
- anchor.rel = "noopener nofollow";
22002
- anchor.href = "https://maplibre.org/";
22003
- anchor.setAttribute("aria-label", this._map._getUIString("LogoControl.Title"));
22004
- anchor.setAttribute("rel", "noopener nofollow");
22005
- this._container.appendChild(anchor);
22006
- this._container.style.display = "block";
22007
- this._map.on("resize", this._updateCompact);
22008
- this._updateCompact();
22009
- return this._container;
22010
- }
22011
- /** {@inheritDoc IControl.onRemove} */
22012
- onRemove() {
22013
- this._container.remove();
22014
- this._map.off("resize", this._updateCompact);
22015
- this._map = void 0;
22016
- this._compact = void 0;
22017
- }
22018
- };
22019
- //#endregion
22020
- //#region src/util/task_queue.ts
22021
- var TaskQueue = class {
22022
- constructor() {
22023
- this._queue = [];
22024
- this._id = 0;
22025
- this._cleared = false;
22026
- this._currentlyRunning = false;
22027
- }
22028
- add(callback) {
22029
- const id = ++this._id;
22030
- this._queue.push({
22031
- callback,
22032
- id,
22033
- cancelled: false
22034
- });
22035
- return id;
22036
- }
22037
- remove(id) {
22038
- const running = this._currentlyRunning;
22039
- const queue = running ? this._queue.concat(running) : this._queue;
22040
- for (const task of queue) if (task.id === id) {
22041
- task.cancelled = true;
22042
- return;
22043
- }
22044
- }
22045
- run(timeStamp = 0) {
22046
- if (this._currentlyRunning) throw new Error("Attempting to run(), but is already running.");
22047
- const queue = this._currentlyRunning = this._queue;
22048
- this._queue = [];
22049
- for (const task of queue) {
22050
- if (task.cancelled) continue;
22051
- task.callback(timeStamp);
22052
- if (this._cleared) break;
22053
- }
22054
- this._cleared = false;
22055
- this._currentlyRunning = false;
22056
- }
22057
- clear() {
22058
- if (this._currentlyRunning) this._cleared = true;
22059
- this._queue = [];
22060
- }
22061
- };
22062
- //#endregion
22063
- //#region src/data/pos3d_attributes.ts
22064
- const pos3dAttributes = createLayout([{
22065
- name: "a_pos3d",
22066
- type: "Int16",
22067
- components: 3
22068
- }]);
22069
- //#endregion
22070
- //#region src/tile/terrain_tile_manager.ts
22071
- /**
22072
- * @internal
22073
- * This class is a helper for the Terrain-class, it:
22074
- *
22075
- * - loads raster-dem tiles
22076
- * - manages all renderToTexture tiles.
22077
- * - caches previous rendered tiles.
22078
- * - finds all necessary renderToTexture tiles for a OverscaledTileID area
22079
- * - finds the corresponding raster-dem tile for OverscaledTileID
22080
- */
22081
- var TerrainTileManager = class extends Evented {
22082
- constructor(tileManager) {
22083
- super();
22084
- this._lastTilesetChange = now();
22085
- this.tileManager = tileManager;
22086
- this._tiles = {};
22087
- this._renderableTilesKeys = [];
22088
- this._sourceTileCache = {};
22089
- this.minzoom = 0;
22090
- this.maxzoom = 22;
22091
- this.deltaZoom = 1;
22092
- this.tileSize = tileManager._source.tileSize * 2 ** this.deltaZoom;
22093
- tileManager.usedForTerrain = true;
22094
- tileManager.tileSize = this.tileSize;
22095
- }
22096
- destruct() {
22097
- this.tileManager.usedForTerrain = false;
22098
- this.tileManager.tileSize = null;
22099
- this.releaseAllRTT();
22100
- }
22101
- getSource() {
22102
- return this.tileManager._source;
22103
- }
22104
- /**
22105
- * Load Terrain Tiles, create internal render-to-texture tiles, free GPU memory.
22106
- * @param transform - the operation to do
22107
- * @param terrain - the terrain
22108
- */
22109
- update(transform, terrain) {
22110
- this.tileManager.update(transform, terrain);
22111
- this._renderableTilesKeys = [];
22112
- const keys = {};
22113
- for (const tileID of coveringTiles(transform, {
22114
- tileSize: this.tileSize,
22115
- minzoom: this.minzoom,
22116
- maxzoom: this.maxzoom,
22117
- reparseOverscaled: false,
22118
- terrain,
22119
- calculateTileZoom: this.tileManager._source.calculateTileZoom
22120
- })) {
22121
- keys[tileID.key] = true;
22122
- this._renderableTilesKeys.push(tileID.key);
22123
- if (!this._tiles[tileID.key]) {
22124
- tileID.terrainRttPosMatrix32f = /* @__PURE__ */ new Float32Array(16);
22125
- ortho(tileID.terrainRttPosMatrix32f, 0, EXTENT, EXTENT, 0, 0, 1);
22126
- this._tiles[tileID.key] = new Tile(tileID, this.tileSize);
22127
- this._lastTilesetChange = now();
22128
- }
22129
- }
22130
- for (const key in this._tiles) if (!keys[key]) {
22131
- this._tiles[key].releaseRTT(this.tileManager.map.painter);
22132
- delete this._tiles[key];
22133
- }
22707
+ this._ease((k) => {
22708
+ easeHandler.easeFunc(k);
22709
+ if (this.terrain && !options.freezeElevation) this._updateElevation(k);
22710
+ this.applyUpdatedTransform(tr);
22711
+ this._fireMoveEvents(eventData);
22712
+ }, (interruptingEaseId) => {
22713
+ if (this.terrain && options.freezeElevation) this._finalizeElevation();
22714
+ this._afterEase(eventData, interruptingEaseId);
22715
+ }, options);
22716
+ return this;
22134
22717
  }
22135
- /**
22136
- * Release the RTT objects for `tileID` (and its ancestors/descendants),
22137
- */
22138
- releaseRTT(tileID) {
22139
- for (const key in this._tiles) {
22140
- const tile = this._tiles[key];
22141
- if (tile.tileID.equals(tileID) || tile.tileID.isChildOf(tileID) || tileID.isChildOf(tile.tileID)) tile.releaseRTT(this.tileManager.map.painter);
22142
- }
22718
+ _prepareEase(eventData, noMoveStart, currently = {}) {
22719
+ this._moving = true;
22720
+ if (!noMoveStart && !currently.moving) this.fire(new MapMovementEvent("movestart", eventData));
22721
+ if (this._zooming && !currently.zooming) this.fire(new MapMovementEvent("zoomstart", eventData));
22722
+ if (this._rotating && !currently.rotating) this.fire(new MapMovementEvent("rotatestart", eventData));
22723
+ if (this._pitching && !currently.pitching) this.fire(new MapMovementEvent("pitchstart", eventData));
22724
+ if (this._rolling && !currently.rolling) this.fire(new MapMovementEvent("rollstart", eventData));
22143
22725
  }
22144
- /**
22145
- * Release the A RTT objects for all tiles.
22146
- */
22147
- releaseAllRTT() {
22148
- for (const key in this._tiles) this._tiles[key].releaseRTT(this.tileManager.map.painter);
22726
+ _prepareElevation(center) {
22727
+ this._elevationCenter = center;
22728
+ this._elevationStart = this.transform.elevation;
22729
+ this._elevationTarget = this.terrain.getElevationForLngLat(center, this.transform);
22730
+ this.elevationFreeze = true;
22149
22731
  }
22150
- /**
22151
- * get a list of tiles, which are loaded and should be rendered in the current scene
22152
- * @returns the renderable tiles
22153
- */
22154
- getRenderableTiles() {
22155
- return this._renderableTilesKeys.map((key) => this.getTileByID(key));
22732
+ _updateElevation(k) {
22733
+ if (this._elevationStart === void 0 || this._elevationCenter === void 0) this._prepareElevation(this.transform.center);
22734
+ this.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._elevationCenter, this.transform.tileZoom));
22735
+ const elevation = this.terrain.getElevationForLngLat(this._elevationCenter, this.transform);
22736
+ if (k < 1 && elevation !== this._elevationTarget) {
22737
+ const pitch1 = this._elevationTarget - this._elevationStart;
22738
+ const pitch2 = (elevation - (pitch1 * k + this._elevationStart)) / (1 - k);
22739
+ this._elevationStart += k * (pitch1 - pitch2);
22740
+ this._elevationTarget = elevation;
22741
+ }
22742
+ this.transform.setElevation(interpolateFactory.number(this._elevationStart, this._elevationTarget, k));
22156
22743
  }
22157
- /**
22158
- * get terrain tile by the TileID key
22159
- * @param id - the tile id
22160
- * @returns the tile
22161
- */
22162
- getTileByID(id) {
22163
- return this._tiles[id];
22744
+ _finalizeElevation() {
22745
+ this.elevationFreeze = false;
22746
+ if (this.getCenterClampedToGround()) this.transform.recalculateZoomAndCenter(this.terrain);
22164
22747
  }
22165
22748
  /**
22166
- * Searches for the corresponding current renderable terrain-tiles
22167
- * @param tileID - the tile to look for
22168
- * @returns the tiles that were found
22749
+ * @internal
22750
+ * Called when the camera is about to be manipulated.
22751
+ * If `transformCameraUpdate` is specified or terrain is enabled, a copy of
22752
+ * the current transform is created to track the accumulated changes.
22753
+ * This underlying transform represents the "desired state" proposed by input handlers / animations / UI controls.
22754
+ * It may differ from the state used for rendering (`this.transform`).
22755
+ * @returns Transform to apply changes to
22169
22756
  */
22170
- getTerrainCoords(tileID, terrainTileRanges) {
22171
- if (terrainTileRanges) return this._getTerrainCoordsForTileRanges(tileID, terrainTileRanges);
22172
- else return this._getTerrainCoordsForRegularTile(tileID);
22757
+ getTransformForUpdate() {
22758
+ if (!this.transformCameraUpdate && !this.terrain) return this.transform;
22759
+ this._requestedCameraState ||= this.transform.clone();
22760
+ return this._requestedCameraState;
22173
22761
  }
22174
22762
  /**
22175
- * Searches for the corresponding current renderable terrain-tiles.
22176
- * Includes terrain tiles that are either:
22177
- * - the same as the tileID
22178
- * - a parent of the tileID
22179
- * - a child of the tileID
22180
- * @param tileID - the tile to look for
22181
- * @returns the tiles that were found
22763
+ * @internal
22764
+ * Checks the given transform for the camera being below terrain surface and
22765
+ * returns new pitch and zoom to fix that.
22766
+ *
22767
+ * With the new pitch and zoom, the camera will be at the same ground
22768
+ * position but at higher altitude. It will still point to the same spot on
22769
+ * the map.
22770
+ *
22771
+ * @param tr - The transform to check.
22182
22772
  */
22183
- _getTerrainCoordsForRegularTile(tileID) {
22184
- const coords = {};
22185
- for (const key of this._renderableTilesKeys) {
22186
- const terrainTileID = this._tiles[key].tileID;
22187
- const coord = tileID.clone();
22188
- const mat = createMat4f64();
22189
- if (terrainTileID.canonical.equals(tileID.canonical)) ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1);
22190
- else if (terrainTileID.canonical.isChildOf(tileID.canonical)) {
22191
- const dz = terrainTileID.canonical.z - tileID.canonical.z;
22192
- const dx = terrainTileID.canonical.x - (terrainTileID.canonical.x >> dz << dz);
22193
- const dy = terrainTileID.canonical.y - (terrainTileID.canonical.y >> dz << dz);
22194
- const size = EXTENT >> dz;
22195
- ortho(mat, 0, size, size, 0, 0, 1);
22196
- translate(mat, mat, [
22197
- -dx * size,
22198
- -dy * size,
22199
- 0
22200
- ]);
22201
- } else if (tileID.canonical.isChildOf(terrainTileID.canonical)) {
22202
- const dz = tileID.canonical.z - terrainTileID.canonical.z;
22203
- const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz);
22204
- const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz);
22205
- const size = EXTENT >> dz;
22206
- ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1);
22207
- translate(mat, mat, [
22208
- dx * size,
22209
- dy * size,
22210
- 0
22211
- ]);
22212
- scale(mat, mat, [
22213
- 1 / 2 ** dz,
22214
- 1 / 2 ** dz,
22215
- 0
22216
- ]);
22217
- } else continue;
22218
- coord.terrainRttPosMatrix32f = new Float32Array(mat);
22219
- coords[key] = coord;
22773
+ _elevateCameraIfInsideTerrain(tr) {
22774
+ if (!this.terrain && tr.elevation >= 0 && tr.pitch <= 90) return {};
22775
+ const cameraLngLat = tr.getCameraLngLat();
22776
+ const cameraAltitude = tr.getCameraAltitude();
22777
+ const minAltitude = this.terrain ? this.terrain.getElevationForLngLatZoom(cameraLngLat, tr.zoom) : 0;
22778
+ if (cameraAltitude < minAltitude) {
22779
+ const newCamera = this.calculateCameraOptionsFromTo(cameraLngLat, minAltitude, tr.center, tr.elevation);
22780
+ return {
22781
+ pitch: newCamera.pitch,
22782
+ zoom: newCamera.zoom
22783
+ };
22220
22784
  }
22221
- return coords;
22785
+ return {};
22222
22786
  }
22223
22787
  /**
22224
- * Searches for the corresponding current renderable terrain-tiles.
22225
- * Includes terrain tiles that are within terrain tile ranges.
22226
- * @param tileID - the tile to look for
22227
- * @returns the tiles that were found
22788
+ * @internal
22789
+ * Called after the camera is done being manipulated.
22790
+ * @param tr - the requested camera end state
22791
+ * If the camera is inside terrain, it gets elevated.
22792
+ * Call `transformCameraUpdate` if present, and then apply the "approved" changes.
22228
22793
  */
22229
- _getTerrainCoordsForTileRanges(tileID, terrainTileRanges) {
22230
- const coords = {};
22231
- for (const key of this._renderableTilesKeys) {
22232
- const terrainTileID = this._tiles[key].tileID;
22233
- if (!this._isWithinTileRanges(terrainTileID, terrainTileRanges)) continue;
22234
- const coord = tileID.clone();
22235
- const mat = createMat4f64();
22236
- if (terrainTileID.canonical.z === tileID.canonical.z) {
22237
- const dx = tileID.canonical.x - terrainTileID.canonical.x + tileID.wrap * (1 << tileID.canonical.z);
22238
- const dy = tileID.canonical.y - terrainTileID.canonical.y;
22239
- ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1);
22240
- translate(mat, mat, [
22241
- dx * EXTENT,
22242
- dy * EXTENT,
22243
- 0
22244
- ]);
22245
- } else if (terrainTileID.canonical.z > tileID.canonical.z) {
22246
- const dz = terrainTileID.canonical.z - tileID.canonical.z;
22247
- const dx = terrainTileID.canonical.x - (terrainTileID.canonical.x >> dz << dz) + tileID.wrap * (1 << terrainTileID.canonical.z);
22248
- const dy = terrainTileID.canonical.y - (terrainTileID.canonical.y >> dz << dz);
22249
- const dx2 = tileID.canonical.x - (terrainTileID.canonical.x >> dz);
22250
- const dy2 = tileID.canonical.y - (terrainTileID.canonical.y >> dz);
22251
- const size = EXTENT >> dz;
22252
- ortho(mat, 0, size, size, 0, 0, 1);
22253
- translate(mat, mat, [
22254
- -dx * size + dx2 * EXTENT,
22255
- -dy * size + dy2 * EXTENT,
22256
- 0
22257
- ]);
22258
- } else {
22259
- const dz = tileID.canonical.z - terrainTileID.canonical.z;
22260
- const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz) + tileID.wrap * (1 << tileID.canonical.z);
22261
- const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz);
22262
- const dx2 = (tileID.canonical.x >> dz) - terrainTileID.canonical.x;
22263
- const dy2 = (tileID.canonical.y >> dz) - terrainTileID.canonical.y;
22264
- const size = EXTENT << dz;
22265
- ortho(mat, 0, size, size, 0, 0, 1);
22266
- translate(mat, mat, [
22267
- dx * EXTENT + dx2 * size,
22268
- dy * EXTENT + dy2 * size,
22269
- 0
22270
- ]);
22271
- }
22272
- coord.terrainRttPosMatrix32f = new Float32Array(mat);
22273
- coords[key] = coord;
22794
+ applyUpdatedTransform(tr) {
22795
+ const modifiers = [];
22796
+ modifiers.push((tr) => this._elevateCameraIfInsideTerrain(tr));
22797
+ if (this.transformCameraUpdate) modifiers.push((tr) => this.transformCameraUpdate(tr));
22798
+ if (!modifiers.length) return;
22799
+ const finalTransform = tr.clone();
22800
+ for (const modifier of modifiers) {
22801
+ const nextTransform = finalTransform.clone();
22802
+ const { center, zoom, roll, pitch, bearing, elevation } = modifier(nextTransform);
22803
+ if (center) nextTransform.setCenter(center);
22804
+ if (elevation !== void 0) nextTransform.setElevation(elevation);
22805
+ if (zoom !== void 0) nextTransform.setZoom(zoom);
22806
+ if (roll !== void 0) nextTransform.setRoll(roll);
22807
+ if (pitch !== void 0) nextTransform.setPitch(pitch);
22808
+ if (bearing !== void 0) nextTransform.setBearing(bearing);
22809
+ finalTransform.apply(nextTransform, false);
22274
22810
  }
22275
- return coords;
22276
- }
22277
- /**
22278
- * find the covering raster-dem tile
22279
- * @param tileID - the tile to look for
22280
- * @param searchForDEM - Optional parameter to search for (parent) source tiles with loaded dem.
22281
- * @returns the tile
22282
- */
22283
- getSourceTile(tileID, searchForDEM) {
22284
- const source = this.tileManager._source;
22285
- let z = tileID.overscaledZ - this.deltaZoom;
22286
- if (z > source.maxzoom) z = source.maxzoom;
22287
- if (z < source.minzoom) return void 0;
22288
- this._sourceTileCache[tileID.key] ||= tileID.scaledTo(z).key;
22289
- let tile = this.findTileInCaches(this._sourceTileCache[tileID.key]);
22290
- if (!tile?.dem && searchForDEM) while (z >= source.minzoom && !tile?.dem) tile = this.findTileInCaches(tileID.scaledTo(z--).key);
22291
- return tile;
22292
- }
22293
- findTileInCaches(key) {
22294
- let tile = this.tileManager.getTileByID(key);
22295
- if (tile) return tile;
22296
- tile = this.tileManager._outOfViewCache.getByKey(key);
22297
- return tile;
22298
- }
22299
- /**
22300
- * gets whether any tiles were loaded after a specific time. This is used to update depth & coords framebuffers.
22301
- * @param time - the time
22302
- * @returns true if any tiles came into view at or after the specified time
22303
- */
22304
- anyTilesAfterTime(time = now()) {
22305
- return this._lastTilesetChange >= time;
22811
+ this.transform.apply(finalTransform, false);
22306
22812
  }
22307
- /**
22308
- * Checks whether a tile is within the canonical tile ranges.
22309
- * @param tileID - Tile to check
22310
- * @param canonicalTileRanges - Canonical tile ranges
22311
- * @returns
22312
- */
22313
- _isWithinTileRanges(tileID, canonicalTileRanges) {
22314
- const range = canonicalTileRanges[tileID.canonical.z];
22315
- return !!range && (tileID.wrap > range.minWrap || tileID.wrap < range.maxWrap || tileID.canonical.x >= range.minTileXWrapped && tileID.canonical.x <= range.maxTileXWrapped && tileID.canonical.y >= range.minTileY && tileID.canonical.y <= range.maxTileY);
22813
+ _fireMoveEvents(eventData) {
22814
+ this.fire(new MapMovementEvent("move", eventData));
22815
+ if (this._zooming) this.fire(new MapMovementEvent("zoom", eventData));
22816
+ if (this._rotating) this.fire(new MapMovementEvent("rotate", eventData));
22817
+ if (this._pitching) this.fire(new MapMovementEvent("pitch", eventData));
22818
+ if (this._rolling) this.fire(new MapMovementEvent("roll", eventData));
22316
22819
  }
22317
- };
22318
- //#endregion
22319
- //#region src/render/terrain.ts
22320
- /**
22321
- * @internal
22322
- * This is the main class which handles most of the 3D Terrain logic. It has the following topics:
22323
- *
22324
- * 1. loads raster-dem tiles via the internal tileManager this.tileManager
22325
- * 2. creates a depth-framebuffer, which is used to calculate the visibility of coordinates
22326
- * 3. creates a coords-framebuffer, which is used the get to tile-coordinate for a screen-pixel
22327
- * 4. stores all render-to-texture tiles in the this.tileManager._tiles
22328
- * 5. calculates the elevation for a specific tile-coordinate
22329
- * 6. creates a terrain-mesh
22330
- *
22331
- * A note about the GPU resource-usage:
22332
- *
22333
- * Framebuffers:
22334
- *
22335
- * - one for the depth & coords framebuffer with the size of the map-div.
22336
- * - one for rendering a tile to texture with the size of tileSize (= 512x512).
22337
- *
22338
- * Textures:
22339
- *
22340
- * - one texture for an empty raster-dem tile with size 1x1
22341
- * - one texture for an empty depth-buffer, when terrain is disabled with size 1x1
22342
- * - one texture for an each loaded raster-dem with size of the source.tileSize
22343
- * - one texture for the coords-framebuffer with the size of the map-div.
22344
- * - one texture for the depth-framebuffer with the size of the map-div.
22345
- * - one texture for the encoded tile-coords with the size 2*tileSize (=1024x1024)
22346
- * - finally for each render-to-texture tile (= this._tiles) a set of textures
22347
- * for each render stack (The stack-concept is documented in painter.ts).
22348
- *
22349
- * Normally there exists 1-3 Textures per tile, depending on the stylesheet.
22350
- * Each Textures has the size 2*tileSize (= 1024x1024). Also there exists a
22351
- * cache of the last 150 newest rendered tiles.
22352
- *
22353
- */
22354
- var Terrain = class {
22355
- constructor(painter, tileManager, options, terrainSkirtLength = "auto") {
22356
- this._meshCache = {};
22357
- this.painter = painter;
22358
- this.tileManager = new TerrainTileManager(tileManager);
22359
- this.options = options;
22360
- this.exaggeration = typeof options.exaggeration === "number" ? options.exaggeration : 1;
22361
- this._terrainSkirtLength = terrainSkirtLength;
22362
- this.qualityFactor = 2;
22363
- this.meshSize = 128;
22364
- this._demMatrixCache = /* @__PURE__ */ new Map();
22365
- this._elevationSamplerCache = /* @__PURE__ */ new Map();
22366
- this.coordsIndex = [];
22367
- this._coordsTextureSize = 1024;
22820
+ _afterEase(eventData, easeId) {
22821
+ if (this._easeId && easeId && this._easeId === easeId) return;
22822
+ delete this._easeId;
22823
+ const wasZooming = this._zooming;
22824
+ const wasRotating = this._rotating;
22825
+ const wasPitching = this._pitching;
22826
+ const wasRolling = this._rolling;
22827
+ this._moving = false;
22828
+ this._zooming = false;
22829
+ this._rotating = false;
22830
+ this._pitching = false;
22831
+ this._rolling = false;
22832
+ this._padding = false;
22833
+ if (wasZooming) this.fire(new MapMovementEvent("zoomend", eventData));
22834
+ if (wasRotating) this.fire(new MapMovementEvent("rotateend", eventData));
22835
+ if (wasPitching) this.fire(new MapMovementEvent("pitchend", eventData));
22836
+ if (wasRolling) this.fire(new MapMovementEvent("rollend", eventData));
22837
+ this.fire(new MapMovementEvent("moveend", eventData));
22368
22838
  }
22369
- destroy() {
22370
- if (this._fbo) {
22371
- this._fbo.destroy();
22372
- this._fbo = null;
22839
+ flyTo(options, eventData) {
22840
+ if (!options.essential && browser.prefersReducedMotion) {
22841
+ const coercedOptions = pick(options, [
22842
+ "center",
22843
+ "zoom",
22844
+ "bearing",
22845
+ "pitch",
22846
+ "roll",
22847
+ "elevation",
22848
+ "padding"
22849
+ ]);
22850
+ return this.jumpTo(coercedOptions, eventData);
22373
22851
  }
22374
- if (this._fboCoordsTexture) {
22375
- this._fboCoordsTexture.destroy();
22376
- this._fboCoordsTexture = null;
22852
+ this.stop();
22853
+ options = extend({
22854
+ offset: [0, 0],
22855
+ speed: 1.2,
22856
+ curve: 1.42,
22857
+ easing: defaultEasing
22858
+ }, options);
22859
+ if ("zoom" in options && this._zoomSnap) options.zoom = evaluateZoomSnap(options.zoom, this._zoomSnap);
22860
+ const tr = this.getTransformForUpdate(), startBearing = tr.bearing, startPitch = tr.pitch, startRoll = tr.roll, startPadding = tr.padding;
22861
+ const bearing = "bearing" in options ? this._normalizeBearing(options.bearing, startBearing) : startBearing;
22862
+ const pitch = "pitch" in options ? +options.pitch : startPitch;
22863
+ const roll = "roll" in options ? this._normalizeBearing(options.roll, startRoll) : startRoll;
22864
+ const padding = "padding" in options ? options.padding : tr.padding;
22865
+ const offsetAsPoint = Point.convert(options.offset);
22866
+ let pointAtOffset = tr.centerPoint.add(offsetAsPoint);
22867
+ const locationAtOffset = tr.screenPointToLocation(pointAtOffset);
22868
+ const flyToHandler = this.cameraHelper.handleFlyTo(tr, {
22869
+ bearing,
22870
+ pitch,
22871
+ roll,
22872
+ padding,
22873
+ locationAtOffset,
22874
+ offsetAsPoint,
22875
+ center: options.center,
22876
+ minZoom: options.minZoom,
22877
+ zoom: options.zoom
22878
+ });
22879
+ let rho = options.curve;
22880
+ const w0 = Math.max(tr.width, tr.height);
22881
+ const w1 = w0 / flyToHandler.scaleOfZoom;
22882
+ const u1 = flyToHandler.pixelPathLength;
22883
+ const wMax = w0 / flyToHandler.scaleOfMinZoom;
22884
+ rho = Math.min(rho, Math.sqrt(wMax / u1 * 2));
22885
+ const rho2 = rho * rho;
22886
+ /**
22887
+ * rᵢ: Returns the zoom-out factor at one end of the animation.
22888
+ *
22889
+ * @param descent - `true` for the descent, `false` for the ascent
22890
+ */
22891
+ function zoomOutFactor(descent) {
22892
+ const b = (w1 * w1 - w0 * w0 + (descent ? -1 : 1) * rho2 * rho2 * u1 * u1) / (2 * (descent ? w1 : w0) * rho2 * u1);
22893
+ return Math.log(Math.sqrt(b * b + 1) - b);
22377
22894
  }
22378
- if (this._fboDepthTexture) {
22379
- this._fboDepthTexture.destroy();
22380
- this._fboDepthTexture = null;
22895
+ function sinh(n) {
22896
+ return (Math.exp(n) - Math.exp(-n)) / 2;
22381
22897
  }
22382
- if (this._emptyDemTexture) {
22383
- this._emptyDemTexture.destroy();
22384
- this._emptyDemTexture = null;
22898
+ function cosh(n) {
22899
+ return (Math.exp(n) + Math.exp(-n)) / 2;
22385
22900
  }
22386
- if (this._emptyDepthTexture) {
22387
- this._emptyDepthTexture.destroy();
22388
- this._emptyDepthTexture = null;
22901
+ function tanh(n) {
22902
+ return sinh(n) / cosh(n);
22903
+ }
22904
+ const r0 = zoomOutFactor(false);
22905
+ let w = function(s) {
22906
+ return cosh(r0) / cosh(r0 + rho * s);
22907
+ };
22908
+ let u = function(s) {
22909
+ return w0 * ((cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2) / u1;
22910
+ };
22911
+ let S = (zoomOutFactor(true) - r0) / rho;
22912
+ if (Math.abs(u1) < 2e-6 || !isFinite(S)) {
22913
+ if (Math.abs(w0 - w1) < 1e-6) return this.easeTo(options, eventData);
22914
+ const k = w1 < w0 ? -1 : 1;
22915
+ S = Math.abs(Math.log(w1 / w0)) / rho;
22916
+ u = () => 0;
22917
+ w = (s) => Math.exp(k * rho * s);
22389
22918
  }
22390
- if (this._coordsTexture) {
22391
- this._coordsTexture.destroy();
22392
- this._coordsTexture = null;
22919
+ if ("duration" in options) options.duration = +options.duration;
22920
+ else {
22921
+ const V = "screenSpeed" in options ? +options.screenSpeed / rho : +options.speed;
22922
+ options.duration = 1e3 * S / V;
22393
22923
  }
22394
- for (const key in this._meshCache) this._meshCache[key].destroy();
22395
- this._meshCache = {};
22396
- this.tileManager.destruct();
22924
+ if (options.maxDuration && options.duration > options.maxDuration) options.duration = 0;
22925
+ this._zooming = true;
22926
+ this._rotating = startBearing !== bearing;
22927
+ this._pitching = pitch !== startPitch;
22928
+ this._rolling = roll !== startRoll;
22929
+ this._padding = !tr.isPaddingEqual(padding);
22930
+ this._prepareEase(eventData, false);
22931
+ if (this.terrain) this._prepareElevation(flyToHandler.targetCenter);
22932
+ this._ease((k) => {
22933
+ const s = k * S;
22934
+ const scale = 1 / w(s);
22935
+ const centerFactor = u(s);
22936
+ if (this._rotating) tr.setBearing(interpolateFactory.number(startBearing, bearing, k));
22937
+ if (this._pitching) tr.setPitch(interpolateFactory.number(startPitch, pitch, k));
22938
+ if (this._rolling) tr.setRoll(interpolateFactory.number(startRoll, roll, k));
22939
+ if (this._padding) {
22940
+ tr.interpolatePadding(startPadding, padding, k);
22941
+ pointAtOffset = tr.centerPoint.add(offsetAsPoint);
22942
+ }
22943
+ flyToHandler.easeFunc(k, scale, centerFactor, pointAtOffset);
22944
+ if (this.terrain && !options.freezeElevation) this._updateElevation(k);
22945
+ this.applyUpdatedTransform(tr);
22946
+ this._fireMoveEvents(eventData);
22947
+ }, () => {
22948
+ if (this.terrain && options.freezeElevation) this._finalizeElevation();
22949
+ this._afterEase(eventData);
22950
+ }, options);
22951
+ return this;
22397
22952
  }
22398
- /**
22399
- * Get the elevation-value from original dem-data for a given tile-coordinate.
22400
- * Coordinates that fall outside `[0, extent)` are normalized to the
22401
- * appropriate neighbor tile before lookup.
22402
- * @param tileID - the tile to get the elevation for
22403
- * @param x - x coordinate relative to the tile, may be outside `[0, extent)`
22404
- * @param y - y coordinate relative to the tile, may be outside `[0, extent)`
22405
- * @param extent - optional, default 8192
22406
- * @returns the elevation
22407
- */
22408
- getDEMElevation(tileID, x, y, extent = EXTENT) {
22409
- const normalized = tileID.normalizeCoordinates(x, y, extent);
22410
- if (!normalized) return 0;
22411
- const sampler = this._getElevationSampler(normalized.tileID);
22412
- return sampler ? sampler(normalized.x, normalized.y, extent) : 0;
22953
+ isEasing() {
22954
+ return !!this._easeFrameId;
22413
22955
  }
22414
- /**
22415
- * Get the elevation for given {@link LngLat} in respect of exaggeration.
22416
- * @param lnglat - the location
22417
- * @param zoom - the zoom, use {@link getElevationForLngLat} if you don't want a specific zoom level, but more accurate results.
22418
- * @returns the elevation
22419
- */
22420
- getElevationForLngLatZoom(lnglat, zoom) {
22421
- if (!isInBoundsForZoomLngLat(zoom, lnglat.wrap())) return 0;
22422
- const { tileID, mercatorX, mercatorY } = this._getOverscaledTileIDFromLngLatZoom(lnglat, zoom);
22423
- return this.getElevation(tileID, mercatorX % EXTENT, mercatorY % EXTENT, EXTENT);
22956
+ stop(allowGestures) {
22957
+ return this._stop(allowGestures);
22424
22958
  }
22425
- /**
22426
- * Get the elevation for given {@link LngLat} in respect of exaggeration.
22427
- * This will traverse up the zoom levels to find the first tile with data to return.
22428
- * @param lnglat - the location
22429
- * @returns the elevation
22430
- */
22431
- getElevationForLngLat(lnglat, transform) {
22432
- const terrainCoveringTiles = coveringTiles(transform, {
22433
- maxzoom: this.tileManager.maxzoom,
22434
- minzoom: this.tileManager.minzoom,
22435
- tileSize: 512,
22436
- terrain: this
22437
- });
22438
- let zoom = 0;
22439
- for (const tile of terrainCoveringTiles) if (tile.canonical.z > zoom) zoom = Math.min(tile.canonical.z, this.tileManager.maxzoom);
22440
- return this.getElevationForLngLatZoom(lnglat, zoom);
22959
+ _stop(allowGestures, easeId) {
22960
+ if (this._easeFrameId) {
22961
+ this._cancelRenderFrame(this._easeFrameId);
22962
+ delete this._easeFrameId;
22963
+ delete this._onEaseFrame;
22964
+ }
22965
+ if (this._onEaseEnd) {
22966
+ const onEaseEnd = this._onEaseEnd;
22967
+ delete this._onEaseEnd;
22968
+ onEaseEnd.call(this, easeId);
22969
+ }
22970
+ if (!allowGestures) this._stopHandlers();
22971
+ return this;
22441
22972
  }
22442
- /**
22443
- * Get the elevation for given coordinate in respect of exaggeration.
22444
- * @param tileID - the tile id
22445
- * @param x - x coordinate relative to the tile, may be outside `[0, extent)`
22446
- * @param y - y coordinate relative to the tile, may be outside `[0, extent)`
22447
- * @param extent - optional, default 8192
22448
- * @returns the elevation
22449
- */
22450
- getElevation(tileID, x, y, extent = EXTENT) {
22451
- return this.getDEMElevation(tileID, x, y, extent) * this.exaggeration;
22973
+ _ease(frame, finish, options) {
22974
+ if (options.animate === false || options.duration === 0) {
22975
+ frame(1);
22976
+ finish();
22977
+ } else {
22978
+ this._easeStart = now();
22979
+ this._easeOptions = options;
22980
+ this._onEaseFrame = frame;
22981
+ this._onEaseEnd = finish;
22982
+ this._easeFrameId = this._requestRenderFrame(this._renderFrameCallback);
22983
+ }
22452
22984
  }
22453
- /**
22454
- * Clear CPU elevation samplers that may retain a previously selected DEM tile.
22455
- * @internal
22456
- */
22457
- resetElevationCache() {
22458
- this._elevationSamplerCache.clear();
22985
+ _normalizeBearing(bearing, currentBearing) {
22986
+ bearing = wrap(bearing, -180, 180);
22987
+ const diff = Math.abs(bearing - currentBearing);
22988
+ if (Math.abs(bearing - 360 - currentBearing) < diff) bearing -= 360;
22989
+ if (Math.abs(bearing + 360 - currentBearing) < diff) bearing += 360;
22990
+ return bearing;
22459
22991
  }
22460
- _getElevationSampler(tileID) {
22461
- const key = tileID.key;
22462
- const cachedSampler = this._elevationSamplerCache.get(key);
22463
- if (cachedSampler) return cachedSampler;
22464
- const sourceTile = this.tileManager.getSourceTile(tileID, true);
22465
- const dem = sourceTile?.dem;
22466
- if (!sourceTile || !dem) return null;
22467
- const matrix = this._getDEMTileMatrix(tileID, sourceTile);
22468
- const demPixelScaleX = matrix[0] * dem.dim;
22469
- const demPixelScaleY = matrix[5] * dem.dim;
22470
- const demPixelOffsetX = matrix[12] * dem.dim;
22471
- const demPixelOffsetY = matrix[13] * dem.dim;
22472
- const sampler = (x, y, extent) => {
22473
- const extentScale = extent === 8192 ? 1 : EXTENT / extent;
22474
- return dem.sampleBilinear(x * extentScale * demPixelScaleX + demPixelOffsetX, y * extentScale * demPixelScaleY + demPixelOffsetY);
22475
- };
22476
- this._elevationSamplerCache.set(key, sampler);
22477
- return sampler;
22992
+ isMoving() {
22993
+ return this._moving;
22478
22994
  }
22479
- _getDEMTileMatrix(tileID, sourceTile) {
22480
- const matrixKey = `${sourceTile.tileID.key}/${tileID.key}`;
22481
- const cachedMatrix = this._demMatrixCache.get(matrixKey);
22482
- if (cachedMatrix) return cachedMatrix;
22483
- const maxzoom = this.tileManager.getSource().maxzoom;
22484
- let dz = tileID.canonical.z - sourceTile.tileID.canonical.z;
22485
- if (tileID.overscaledZ > tileID.canonical.z) {
22486
- if (tileID.canonical.z >= maxzoom) dz = tileID.canonical.z - maxzoom;
22487
- else warnOnce("cannot calculate elevation if elevation maxzoom > source.maxzoom");
22488
- }
22489
- const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz);
22490
- const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz);
22491
- const demMatrix = fromScaling(/* @__PURE__ */ new Float64Array(16), [
22492
- 1 / (EXTENT << dz),
22493
- 1 / (EXTENT << dz),
22494
- 0
22495
- ]);
22496
- translate(demMatrix, demMatrix, [
22497
- dx * EXTENT,
22498
- dy * EXTENT,
22499
- 0
22500
- ]);
22501
- this._demMatrixCache.set(matrixKey, demMatrix);
22502
- return demMatrix;
22995
+ isZooming() {
22996
+ return this._zooming;
22503
22997
  }
22998
+ isRotating() {
22999
+ return this._rotating;
23000
+ }
23001
+ };
23002
+ //#endregion
23003
+ //#region src/ui/control/attribution_control.ts
23004
+ const defaultAttributionControlOptions = {
23005
+ compact: true,
23006
+ customAttribution: "<a href=\"https://maplibre.org/\" target=\"_blank\">MapLibre</a>"
23007
+ };
23008
+ /**
23009
+ * An `AttributionControl` control presents the map's attribution information. By default, the attribution control is expanded (regardless of map width).
23010
+ * @group Markers and Controls
23011
+ * @example
23012
+ * ```ts
23013
+ * let map = new Map({attributionControl: false})
23014
+ * .addControl(new AttributionControl({
23015
+ * compact: true
23016
+ * }));
23017
+ * ```
23018
+ * @see [Change the default position for attribution](https://maplibre.org/maplibre-gl-js/docs/examples/change-the-default-position-for-attribution/)
23019
+ */
23020
+ var AttributionControl = class {
22504
23021
  /**
22505
- * returns a Terrain Object for a tile. Unless the tile corresponds to data (e.g. tile is loading), return a flat dem object
22506
- * @param tileID - the tile to get the terrain for
22507
- * @returns the terrain data to use in the program
23022
+ * @param options - the attribution options
22508
23023
  */
22509
- getTerrainData(tileID) {
22510
- if (!this._emptyDemTexture) {
22511
- const context = this.painter.context;
22512
- const image = new RGBAImage({
22513
- width: 1,
22514
- height: 1
22515
- }, /* @__PURE__ */ new Uint8Array(4));
22516
- this._emptyDepthTexture = new Texture(context, image, context.gl.RGBA, { premultiply: false });
22517
- this._emptyDemUnpack = [
22518
- 0,
22519
- 0,
22520
- 0,
22521
- 0
22522
- ];
22523
- this._emptyDemTexture = new Texture(context, new RGBAImage({
22524
- width: 1,
22525
- height: 1
22526
- }), context.gl.RGBA, { premultiply: false });
22527
- this._emptyDemTexture.bind(context.gl.NEAREST, context.gl.CLAMP_TO_EDGE);
22528
- this._emptyDemMatrix = identity([]);
22529
- }
22530
- const sourceTile = this.tileManager.getSourceTile(tileID, true);
22531
- if (sourceTile?.dem && (!sourceTile.demTexture || sourceTile.needsTerrainPrepare)) {
22532
- const context = this.painter.context;
22533
- sourceTile.demTexture ||= this.painter.getTileTexture(sourceTile.dem.stride);
22534
- if (sourceTile.demTexture) sourceTile.demTexture.update(sourceTile.dem.getPixels(), { premultiply: false });
22535
- else sourceTile.demTexture = new Texture(context, sourceTile.dem.getPixels(), context.gl.RGBA, { premultiply: false });
22536
- sourceTile.demTexture.bind(context.gl.NEAREST, context.gl.CLAMP_TO_EDGE);
22537
- sourceTile.needsTerrainPrepare = false;
22538
- }
22539
- const terrainMatrix = sourceTile ? this._getDEMTileMatrix(tileID, sourceTile) : this._emptyDemMatrix;
22540
- return {
22541
- "u_depth": 2,
22542
- "u_terrain": 3,
22543
- "u_terrain_dim": sourceTile?.dem?.dim || 1,
22544
- "u_terrain_matrix": terrainMatrix,
22545
- "u_terrain_unpack": sourceTile?.dem?.getUnpackVector() || this._emptyDemUnpack,
22546
- "u_terrain_exaggeration": this.exaggeration,
22547
- texture: (sourceTile?.demTexture || this._emptyDemTexture).texture,
22548
- depthTexture: (this._fboDepthTexture || this._emptyDepthTexture).texture,
22549
- tile: sourceTile
23024
+ constructor(options = defaultAttributionControlOptions) {
23025
+ this._toggleAttribution = () => {
23026
+ if (this._container.classList.contains("maplibregl-compact")) {
23027
+ if (this._container.classList.contains("maplibregl-compact-show")) {
23028
+ this._container.setAttribute("open", "");
23029
+ this._container.classList.remove("maplibregl-compact-show");
23030
+ } else {
23031
+ this._container.classList.add("maplibregl-compact-show");
23032
+ this._container.removeAttribute("open");
23033
+ }
23034
+ }
23035
+ };
23036
+ this._updateData = (e) => {
23037
+ if (e && (e.type === "terrain" || e.dataType === "style" || e.dataType === "source" && (e.sourceDataType === "metadata" || e.sourceDataType === "visibility"))) this._updateAttributions();
23038
+ };
23039
+ this._updateCompact = () => {
23040
+ if (this._map.getCanvasContainer().offsetWidth <= 640 || this._compact) {
23041
+ if (this._compact === false) this._container.setAttribute("open", "");
23042
+ else if (!this._container.classList.contains("maplibregl-compact") && !this._container.classList.contains("maplibregl-attrib-empty")) {
23043
+ this._container.setAttribute("open", "");
23044
+ this._container.classList.add("maplibregl-compact", "maplibregl-compact-show");
23045
+ }
23046
+ } else {
23047
+ this._container.setAttribute("open", "");
23048
+ if (this._container.classList.contains("maplibregl-compact")) this._container.classList.remove("maplibregl-compact", "maplibregl-compact-show");
23049
+ }
23050
+ };
23051
+ this._updateCompactMinimize = () => {
23052
+ if (this._container.classList.contains("maplibregl-compact")) {
23053
+ if (this._container.classList.contains("maplibregl-compact-show")) this._container.classList.remove("maplibregl-compact-show");
23054
+ }
22550
23055
  };
23056
+ this.options = options;
22551
23057
  }
22552
- /**
22553
- * get a framebuffer as big as the map-div, which will be used to render depth & coords into a texture
22554
- * @param texture - the texture
22555
- * @returns the frame buffer
22556
- */
22557
- getFramebuffer(texture) {
22558
- const painter = this.painter;
22559
- const width = painter.width / devicePixelRatio;
22560
- const height = painter.height / devicePixelRatio;
22561
- if (this._fbo && (this._fbo.width !== width || this._fbo.height !== height)) {
22562
- this._fbo.destroy();
22563
- this._fboCoordsTexture.destroy();
22564
- this._fboDepthTexture.destroy();
22565
- delete this._fbo;
22566
- delete this._fboDepthTexture;
22567
- delete this._fboCoordsTexture;
22568
- }
22569
- if (!this._fboCoordsTexture) {
22570
- this._fboCoordsTexture = new Texture(painter.context, {
22571
- width,
22572
- height,
22573
- data: null
22574
- }, painter.context.gl.RGBA, { premultiply: false });
22575
- this._fboCoordsTexture.bind(painter.context.gl.NEAREST, painter.context.gl.CLAMP_TO_EDGE);
23058
+ getDefaultPosition() {
23059
+ return "bottom-right";
23060
+ }
23061
+ /** {@inheritDoc IControl.onAdd} */
23062
+ onAdd(map) {
23063
+ this._map = map;
23064
+ this._compact = this.options.compact;
23065
+ this._container = DOM.create("details", "maplibregl-ctrl maplibregl-ctrl-attrib");
23066
+ this._compactButton = DOM.create("summary", "maplibregl-ctrl-attrib-button", this._container);
23067
+ this._compactButton.addEventListener("click", this._toggleAttribution);
23068
+ this._setElementTitle(this._compactButton, "ToggleAttribution");
23069
+ this._innerContainer = DOM.create("div", "maplibregl-ctrl-attrib-inner", this._container);
23070
+ this._updateAttributions();
23071
+ this._updateCompact();
23072
+ this._map.on("styledata", this._updateData);
23073
+ this._map.on("sourcedata", this._updateData);
23074
+ this._map.on("terrain", this._updateData);
23075
+ this._map.on("resize", this._updateCompact);
23076
+ this._map.on("drag", this._updateCompactMinimize);
23077
+ return this._container;
23078
+ }
23079
+ /** {@inheritDoc IControl.onRemove} */
23080
+ onRemove() {
23081
+ this._container.remove();
23082
+ this._map.off("styledata", this._updateData);
23083
+ this._map.off("sourcedata", this._updateData);
23084
+ this._map.off("terrain", this._updateData);
23085
+ this._map.off("resize", this._updateCompact);
23086
+ this._map.off("drag", this._updateCompactMinimize);
23087
+ this._map = void 0;
23088
+ this._compact = void 0;
23089
+ this._attribHTML = void 0;
23090
+ }
23091
+ _setElementTitle(element, title) {
23092
+ const str = this._map._getUIString(`AttributionControl.${title}`);
23093
+ element.title = str;
23094
+ element.setAttribute("aria-label", str);
23095
+ }
23096
+ _updateAttributions() {
23097
+ if (!this._map.style) return;
23098
+ let attributions = [];
23099
+ if (this.options.customAttribution) {
23100
+ if (Array.isArray(this.options.customAttribution)) attributions = attributions.concat(this.options.customAttribution.map((attribution) => {
23101
+ if (typeof attribution !== "string") return "";
23102
+ return attribution;
23103
+ }));
23104
+ else if (typeof this.options.customAttribution === "string") attributions.push(this.options.customAttribution);
22576
23105
  }
22577
- if (!this._fboDepthTexture) {
22578
- this._fboDepthTexture = new Texture(painter.context, {
22579
- width,
22580
- height,
22581
- data: null
22582
- }, painter.context.gl.RGBA, { premultiply: false });
22583
- this._fboDepthTexture.bind(painter.context.gl.NEAREST, painter.context.gl.CLAMP_TO_EDGE);
23106
+ if (this._map.style.stylesheet) {
23107
+ const stylesheet = this._map.style.stylesheet;
23108
+ this.styleOwner = stylesheet.owner;
23109
+ this.styleId = stylesheet.id;
22584
23110
  }
22585
- if (!this._fbo) {
22586
- this._fbo = painter.context.createFramebuffer(width, height, true, false);
22587
- this._fbo.depthAttachment.set(painter.context.createRenderbuffer(painter.context.gl.DEPTH_COMPONENT16, width, height));
23111
+ const tileManagers = this._map.style.tileManagers;
23112
+ for (const id in tileManagers) {
23113
+ const tileManager = tileManagers[id];
23114
+ if (tileManager.used || tileManager.usedForTerrain) {
23115
+ const source = tileManager.getSource();
23116
+ if (source.attribution && !attributions.includes(source.attribution)) attributions.push(source.attribution);
23117
+ }
22588
23118
  }
22589
- this._fbo.colorAttachment.set(texture === "coords" ? this._fboCoordsTexture.texture : this._fboDepthTexture.texture);
22590
- return this._fbo;
22591
- }
22592
- /**
22593
- * create coords texture, needed to grab coordinates from canvas
22594
- * encode coords coordinate into 4 bytes:
22595
- * - 8 lower bits for x
22596
- * - 8 lower bits for y
22597
- * - 4 higher bits for x
22598
- * - 4 higher bits for y
22599
- * - 8 bits for coordsIndex (1 .. 255) (= number of terraintile), is later set in draw_terrain uniform value
22600
- * @returns the texture
22601
- */
22602
- getCoordsTexture() {
22603
- const context = this.painter.context;
22604
- if (this._coordsTexture) return this._coordsTexture;
22605
- const data = new Uint8Array(this._coordsTextureSize * this._coordsTextureSize * 4);
22606
- for (let y = 0, i = 0; y < this._coordsTextureSize; y++) for (let x = 0; x < this._coordsTextureSize; x++, i += 4) {
22607
- data[i + 0] = x & 255;
22608
- data[i + 1] = y & 255;
22609
- data[i + 2] = x >> 8 << 4 | y >> 8;
22610
- data[i + 3] = 0;
22611
- }
22612
- const image = new RGBAImage({
22613
- width: this._coordsTextureSize,
22614
- height: this._coordsTextureSize
22615
- }, new Uint8Array(data.buffer));
22616
- const texture = new Texture(context, image, context.gl.RGBA, { premultiply: false });
22617
- texture.bind(context.gl.NEAREST, context.gl.CLAMP_TO_EDGE);
22618
- this._coordsTexture = texture;
22619
- return texture;
22620
- }
22621
- /**
22622
- * Reads a pixel from the coords-framebuffer and translate this to mercator, or null, if the pixel doesn't lie on the terrain's surface (but the sky instead).
22623
- * @param p - Screen-Coordinate
22624
- * @returns Mercator coordinate for a screen pixel, or null, if the pixel is not covered by terrain (is in the sky).
22625
- */
22626
- pointCoordinate(p) {
22627
- this.painter.maybeDrawDepth(true);
22628
- this.painter.maybeDrawCoords();
22629
- const rgba = /* @__PURE__ */ new Uint8Array(4);
22630
- const context = this.painter.context, gl = context.gl;
22631
- const px = Math.round(p.x * this.painter.pixelRatio / devicePixelRatio);
22632
- const py = Math.round(p.y * this.painter.pixelRatio / devicePixelRatio);
22633
- const fbHeight = Math.round(this.painter.height / devicePixelRatio);
22634
- context.bindFramebuffer.set(this.getFramebuffer("coords").framebuffer);
22635
- gl.readPixels(px, fbHeight - py - 1, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, rgba);
22636
- context.bindFramebuffer.set(null);
22637
- const x = rgba[0] + (rgba[2] >> 4 << 8);
22638
- const y = rgba[1] + ((rgba[2] & 15) << 8);
22639
- const tileID = this.coordsIndex[255 - rgba[3]];
22640
- const tile = tileID && this.tileManager.getTileByID(tileID);
22641
- if (!tile) return null;
22642
- const coordsSize = this._coordsTextureSize;
22643
- const worldSize = (1 << tile.tileID.canonical.z) * coordsSize;
22644
- return new MercatorCoordinate((tile.tileID.canonical.x * coordsSize + x) / worldSize + tile.tileID.wrap, (tile.tileID.canonical.y * coordsSize + y) / worldSize, this.getElevation(tile.tileID, x, y, coordsSize));
23119
+ attributions = attributions.filter((e) => String(e).trim());
23120
+ attributions.sort((a, b) => a.length - b.length);
23121
+ attributions = attributions.filter((attrib, i) => {
23122
+ for (let j = i + 1; j < attributions.length; j++) if (attributions[j].includes(attrib)) return false;
23123
+ return true;
23124
+ });
23125
+ const attribHTML = attributions.join(" | ");
23126
+ if (attribHTML === this._attribHTML) return;
23127
+ this._attribHTML = attribHTML;
23128
+ if (attributions.length) {
23129
+ this._innerContainer.innerHTML = DOM.sanitize(attribHTML);
23130
+ this._container.classList.remove("maplibregl-attrib-empty");
23131
+ } else this._container.classList.add("maplibregl-attrib-empty");
23132
+ this._updateCompact();
23133
+ this._editLink = null;
22645
23134
  }
23135
+ };
23136
+ //#endregion
23137
+ //#region src/ui/control/logo_control.ts
23138
+ /**
23139
+ * A `LogoControl` is a control that adds the watermark.
23140
+ *
23141
+ * @group Markers and Controls
23142
+ *
23143
+ * @example
23144
+ * ```ts
23145
+ * map.addControl(new LogoControl({compact: false}));
23146
+ * ```
23147
+ **/
23148
+ var LogoControl = class {
22646
23149
  /**
22647
- * Reads the depth value from the depth-framebuffer at a given screen pixel
22648
- * @param p - Screen coordinate
22649
- * @returns depth value in clip space (between 0 and 1)
23150
+ * @param options - the control's options
22650
23151
  */
22651
- depthAtPoint(p) {
22652
- const rgba = /* @__PURE__ */ new Uint8Array(4);
22653
- const context = this.painter.context, gl = context.gl;
22654
- context.bindFramebuffer.set(this.getFramebuffer("depth").framebuffer);
22655
- gl.readPixels(p.x, this.painter.height / devicePixelRatio - p.y - 1, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, rgba);
22656
- context.bindFramebuffer.set(null);
22657
- return (rgba[0] / 16777216 + rgba[1] / 65536 + rgba[2] / 256 + rgba[3]) / 256;
23152
+ constructor(options = {}) {
23153
+ this._updateCompact = () => {
23154
+ const containerChildren = this._container.children;
23155
+ if (containerChildren.length) {
23156
+ const anchor = containerChildren[0];
23157
+ if (this._map.getCanvasContainer().offsetWidth <= 640 || this._compact) {
23158
+ if (this._compact !== false) anchor.classList.add("maplibregl-compact");
23159
+ } else anchor.classList.remove("maplibregl-compact");
23160
+ }
23161
+ };
23162
+ this.options = options;
22658
23163
  }
22659
- /**
22660
- * create a regular mesh which will be used by all terrain-tiles
22661
- * @returns the created regular mesh
22662
- */
22663
- getTerrainMesh(tileId) {
22664
- const globeEnabled = this.painter.style.projection?.transitionState > 0;
22665
- const northPole = globeEnabled && tileId.canonical.y === 0;
22666
- const southPole = globeEnabled && tileId.canonical.y === (1 << tileId.canonical.z) - 1;
22667
- const key = `m_${northPole ? "n" : ""}_${southPole ? "s" : ""}`;
22668
- if (this._meshCache[key]) return this._meshCache[key];
22669
- const context = this.painter.context;
22670
- const vertexArray = new Pos3dArray();
22671
- const indexArray = new TriangleIndexArray();
22672
- const meshSize = this.meshSize;
22673
- const delta = EXTENT / meshSize;
22674
- const meshSize2 = meshSize * meshSize;
22675
- for (let y = 0; y <= meshSize; y++) for (let x = 0; x <= meshSize; x++) vertexArray.emplaceBack(x * delta, y * delta, 0);
22676
- for (let y = 0; y < meshSize2; y += meshSize + 1) for (let x = 0; x < meshSize; x++) {
22677
- indexArray.emplaceBack(x + y, meshSize + x + y + 1, meshSize + x + y + 2);
22678
- indexArray.emplaceBack(x + y, meshSize + x + y + 2, x + y + 1);
22679
- }
22680
- if (this._terrainSkirtLength !== "none") this._buildSkirts(vertexArray, indexArray, meshSize, delta, northPole, southPole);
22681
- const mesh = new Mesh(context.createVertexBuffer(vertexArray, pos3dAttributes.members), context.createIndexBuffer(indexArray), SegmentVector.simpleSegment(0, 0, vertexArray.length, indexArray.length));
22682
- this._meshCache[key] = mesh;
22683
- return mesh;
23164
+ getDefaultPosition() {
23165
+ return "bottom-left";
22684
23166
  }
22685
- /**
22686
- * Calculates the height of the tile skirts for the "auto" strategy.
22687
- * @see {@link MapOptions.terrainSkirtLength}
22688
- * @param zoom - current zoomlevel
22689
- * @returns the elevation delta in meters
22690
- */
22691
- getSkirtLength(zoom) {
22692
- return 2 * Math.PI * earthRadius / Math.pow(2, Math.max(zoom, 0)) / 5;
23167
+ /** {@inheritDoc IControl.onAdd} */
23168
+ onAdd(map) {
23169
+ this._map = map;
23170
+ this._compact = this.options?.compact;
23171
+ this._container = DOM.create("div", "maplibregl-ctrl");
23172
+ const anchor = DOM.create("a", "maplibregl-ctrl-logo");
23173
+ anchor.target = "_blank";
23174
+ anchor.rel = "noopener nofollow";
23175
+ anchor.href = "https://maplibre.org/";
23176
+ anchor.setAttribute("aria-label", this._map._getUIString("LogoControl.Title"));
23177
+ anchor.setAttribute("rel", "noopener nofollow");
23178
+ this._container.appendChild(anchor);
23179
+ this._container.style.display = "block";
23180
+ this._map.on("resize", this._updateCompact);
23181
+ this._updateCompact();
23182
+ return this._container;
22693
23183
  }
22694
- getMinTileElevationForLngLatZoom(lnglat, zoom) {
22695
- if (!isInBoundsForZoomLngLat(zoom, lnglat.wrap())) return 0;
22696
- const { tileID } = this._getOverscaledTileIDFromLngLatZoom(lnglat, zoom);
22697
- return this.getMinMaxElevation(tileID).minElevation ?? 0;
23184
+ /** {@inheritDoc IControl.onRemove} */
23185
+ onRemove() {
23186
+ this._container.remove();
23187
+ this._map.off("resize", this._updateCompact);
23188
+ this._map = void 0;
23189
+ this._compact = void 0;
22698
23190
  }
22699
- /**
22700
- * Get the minimum and maximum elevation contained in a tile. This includes any
22701
- * exaggeration included in the terrain.
22702
- *
22703
- * @param tileID - ID of the tile to be used as a source for the min/max elevation
22704
- * @returns the minimum and maximum elevation found in the tile, including the terrain's
22705
- * exaggeration
22706
- */
22707
- getMinMaxElevation(tileID) {
22708
- const tile = this.tileManager.getSourceTile(tileID, true);
22709
- const minMax = {
22710
- minElevation: null,
22711
- maxElevation: null
22712
- };
22713
- if (tile?.dem) {
22714
- minMax.minElevation = tile.dem.min * this.exaggeration;
22715
- minMax.maxElevation = tile.dem.max * this.exaggeration;
22716
- }
22717
- return minMax;
23191
+ };
23192
+ //#endregion
23193
+ //#region src/util/task_queue.ts
23194
+ var TaskQueue = class {
23195
+ constructor() {
23196
+ this._queue = [];
23197
+ this._id = 0;
23198
+ this._cleared = false;
23199
+ this._currentlyRunning = false;
22718
23200
  }
22719
- _getOverscaledTileIDFromLngLatZoom(lnglat, zoom) {
22720
- const mercatorCoordinate = MercatorCoordinate.fromLngLat(lnglat.wrap());
22721
- const worldSize = (1 << zoom) * EXTENT;
22722
- const mercatorX = mercatorCoordinate.x * worldSize;
22723
- const mercatorY = mercatorCoordinate.y * worldSize;
22724
- const tileX = Math.floor(mercatorX / EXTENT), tileY = Math.floor(mercatorY / EXTENT);
22725
- return {
22726
- tileID: new OverscaledTileID(zoom, 0, zoom, tileX, tileY),
22727
- mercatorX,
22728
- mercatorY
22729
- };
23201
+ add(callback) {
23202
+ const id = ++this._id;
23203
+ this._queue.push({
23204
+ callback,
23205
+ id,
23206
+ cancelled: false
23207
+ });
23208
+ return id;
22730
23209
  }
22731
- /** Add an extra frame around the mesh to avoid hairline gaps (stitching) on tile boundaries with different zoomlevels.
22732
- * @see {@link MapOptions.terrainSkirtLength}
22733
- */
22734
- _buildSkirts(vertexArray, indexArray, meshSize, delta, northPole, southPole) {
22735
- const offsetTop = vertexArray.length;
22736
- const offsetTopEdge = 0;
22737
- const offsetBottom = offsetTop + (meshSize + 1);
22738
- const offsetBottomEdge = (meshSize + 1) * meshSize;
22739
- const northY = northPole ? NORTH_POLE_Y : 0;
22740
- const northZ = northPole ? 0 : 1;
22741
- const southY = southPole ? SOUTH_POLE_Y : EXTENT;
22742
- const southZ = southPole ? 0 : 1;
22743
- for (let x = 0; x <= meshSize; x++) vertexArray.emplaceBack(x * delta, northY, northZ);
22744
- for (let x = 0; x <= meshSize; x++) vertexArray.emplaceBack(x * delta, southY, southZ);
22745
- for (let x = 0; x < meshSize; x++) {
22746
- indexArray.emplaceBack(offsetBottomEdge + x, offsetBottom + x, offsetBottom + x + 1);
22747
- indexArray.emplaceBack(offsetBottomEdge + x, offsetBottom + x + 1, offsetBottomEdge + x + 1);
22748
- indexArray.emplaceBack(offsetTopEdge + x, offsetTop + x + 1, offsetTop + x);
22749
- indexArray.emplaceBack(offsetTopEdge + x, offsetTopEdge + x + 1, offsetTop + x + 1);
23210
+ remove(id) {
23211
+ const running = this._currentlyRunning;
23212
+ const queue = running ? this._queue.concat(running) : this._queue;
23213
+ for (const task of queue) if (task.id === id) {
23214
+ task.cancelled = true;
23215
+ return;
22750
23216
  }
22751
- const offsetLeft = vertexArray.length;
22752
- const offsetRight = offsetLeft + (meshSize + 1) * 2;
22753
- for (const x of [0, 1]) for (let y = 0; y <= meshSize; y++) for (const z of [0, 1]) vertexArray.emplaceBack(x * EXTENT, y * delta, z);
22754
- for (let y = 0; y < meshSize * 2; y += 2) {
22755
- indexArray.emplaceBack(offsetLeft + y, offsetLeft + y + 1, offsetLeft + y + 3);
22756
- indexArray.emplaceBack(offsetLeft + y, offsetLeft + y + 3, offsetLeft + y + 2);
22757
- indexArray.emplaceBack(offsetRight + y, offsetRight + y + 3, offsetRight + y + 1);
22758
- indexArray.emplaceBack(offsetRight + y, offsetRight + y + 2, offsetRight + y + 3);
23217
+ }
23218
+ run(timeStamp = 0) {
23219
+ if (this._currentlyRunning) throw new Error("Attempting to run(), but is already running.");
23220
+ const queue = this._currentlyRunning = this._queue;
23221
+ this._queue = [];
23222
+ for (const task of queue) {
23223
+ if (task.cancelled) continue;
23224
+ task.callback(timeStamp);
23225
+ if (this._cleared) break;
22759
23226
  }
23227
+ this._cleared = false;
23228
+ this._currentlyRunning = false;
23229
+ }
23230
+ clear() {
23231
+ if (this._currentlyRunning) this._cleared = true;
23232
+ this._queue = [];
22760
23233
  }
22761
23234
  };
22762
23235
  //#endregion
@@ -23069,8 +23542,12 @@ var Map$1 = class extends Evented {
23069
23542
  style: null,
23070
23543
  images: null
23071
23544
  };
23072
- this._setupPainter();
23073
- if (!this.painter) return;
23545
+ try {
23546
+ this._setupPainter();
23547
+ } catch (error) {
23548
+ this.fire(new ErrorEvent(error));
23549
+ return;
23550
+ }
23074
23551
  this.resize();
23075
23552
  this._update();
23076
23553
  this._resizeInternal();
@@ -23135,13 +23612,17 @@ var Map$1 = class extends Evented {
23135
23612
  this.cancelPendingTileRequestsWhileZooming = resolvedOptions.cancelPendingTileRequestsWhileZooming === true;
23136
23613
  this.setAnisotropicFilterPitch(resolvedOptions.anisotropicFilterPitch);
23137
23614
  if (resolvedOptions.reduceMotion !== void 0) browser.prefersReducedMotion = resolvedOptions.reduceMotion;
23138
- this._imageQueueHandle = ImageRequest.addThrottleControl(() => this.isMoving());
23139
23615
  this._requestManager = new RequestManager(resolvedOptions.transformRequest);
23140
23616
  this._container = this._resolveContainer(resolvedOptions.container);
23141
23617
  if (resolvedOptions.maxBounds) this.setMaxBounds(resolvedOptions.maxBounds);
23142
23618
  this._setupContainer();
23143
- this._setupPainter();
23144
- if (!this.painter) return;
23619
+ try {
23620
+ this._setupPainter();
23621
+ } catch (error) {
23622
+ this._cleanupContainer();
23623
+ throw error;
23624
+ }
23625
+ this._imageQueueHandle = ImageRequest.addThrottleControl(() => this.isMoving());
23145
23626
  this.on("move", () => this._update(false));
23146
23627
  this.on("moveend", () => this._update(false));
23147
23628
  this.on("zoom", () => this._update(true));
@@ -24116,14 +24597,17 @@ var Map$1 = class extends Evented {
24116
24597
  /**
24117
24598
  * Returns the map's minimum allowable zoom level.
24118
24599
  *
24600
+ * @param constrained - If `true`, returns the effective minimum zoom after applying the map's viewport constraints.
24601
+ * If `false` or omitted, returns the configured minimum zoom.
24119
24602
  * @returns minZoom
24120
24603
  * @example
24121
24604
  * ```ts
24122
24605
  * let minZoom = map.getMinZoom();
24123
24606
  * ```
24124
24607
  */
24125
- getMinZoom() {
24126
- return this._camera.transform.minZoom;
24608
+ getMinZoom(constrained = false) {
24609
+ const transform = this._camera.transform;
24610
+ return constrained ? transform.applyConstrain(transform.center, transform.minZoom).zoom : transform.minZoom;
24127
24611
  }
24128
24612
  /**
24129
24613
  * Sets or clears the map's maximum zoom level.
@@ -24918,10 +25402,11 @@ var Map$1 = class extends Evented {
24918
25402
  this.painter.renderToTexture = new RenderToTexture(this.painter, this.terrain);
24919
25403
  this._camera.terrain = this.terrain;
24920
25404
  this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
24921
- this._camera.transform.setElevation(this.terrain.getElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
25405
+ this._camera.transform.setElevation(this.terrain.getElevationForLngLat(this._camera.transform.center, this._camera.transform));
24922
25406
  this._terrainDataCallback = (e) => this._handleTerrainDataEvent(e, options.source);
24923
25407
  this.style.on("data", this._terrainDataCallback);
24924
25408
  }
25409
+ this.style.triggerSymbolPlacement();
24925
25410
  this.fire(new MapTerrainEvent({ terrain: options }));
24926
25411
  return this;
24927
25412
  }
@@ -24931,10 +25416,13 @@ var Map$1 = class extends Evented {
24931
25416
  return;
24932
25417
  }
24933
25418
  const isTerrainSourceEvent = event.sourceId === terrainSourceId;
24934
- if (isTerrainSourceEvent) this.terrain.resetElevationCache();
25419
+ if (isTerrainSourceEvent) {
25420
+ this.terrain.resetElevationCache();
25421
+ this.style.triggerSymbolPlacement();
25422
+ }
24935
25423
  if (isTerrainSourceEvent && event.tile && !this._camera.elevationFreeze) {
24936
25424
  this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
24937
- if (this.getCenterClampedToGround()) this._camera.transform.setElevation(this.terrain.getElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
25425
+ if (this.getCenterClampedToGround()) this._camera.transform.setElevation(this.terrain.getElevationForLngLat(this._camera.transform.center, this._camera.transform));
24938
25426
  }
24939
25427
  if (!event.tile) return;
24940
25428
  if (event.source?.type === "image") {
@@ -25589,6 +26077,42 @@ var Map$1 = class extends Evented {
25589
26077
  return this.style.getGlyphsUrl();
25590
26078
  }
25591
26079
  /**
26080
+ * Sets the value of the style's `font-faces` property, which points at the font files used to
26081
+ * draw text that the style's `glyphs` URL does not cover. Pass a falsy value (null or undefined)
26082
+ * to unset it.
26083
+ *
26084
+ * The files are handed to the browser's CSS Font Loading API, so any format the browser can
26085
+ * render text with may be used, and requests for them go through `transformRequest` as glyph
26086
+ * requests do. Text is drawn a grapheme cluster at a time, so a letter and the marks written on
26087
+ * it are handed to the browser's text engine together and come back as the one shape they are
26088
+ * written as -- which is what a `glyphs` URL, serving one codepoint at a time, cannot do.
26089
+ *
26090
+ * @param fontFaces - The font faces to set. Must conform to the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/root/#font-faces).
26091
+ * A declaration this cannot make sense of is skipped with a warning, as is a font file that
26092
+ * fails to load, so the text it would have drawn falls back to the `glyphs` URL.
26093
+ * @example
26094
+ * ```ts
26095
+ * map.setFontFaces({
26096
+ * 'Noto Sans Regular': [
26097
+ * {url: 'https://example.com/NotoSansKhmer-Regular.ttf', 'unicode-range': ['U+1780-17FF']}
26098
+ * ]
26099
+ * });
26100
+ * ```
26101
+ */
26102
+ setFontFaces(fontFaces) {
26103
+ this._lazyInitEmptyStyle();
26104
+ this.style.setFontFaces(fontFaces);
26105
+ return this._update(true);
26106
+ }
26107
+ /**
26108
+ * Returns the value of the style's `font-faces` property.
26109
+ *
26110
+ * @returns The style's font faces, or `null` if it declares none.
26111
+ */
26112
+ getFontFaces() {
26113
+ return this.style.getFontFaces();
26114
+ }
26115
+ /**
25592
26116
  * Adds a sprite to the map's style. Fires the `style` event.
25593
26117
  *
25594
26118
  * @param id - The ID of the sprite to add. Must not conflict with existing sprites.
@@ -25924,12 +26448,28 @@ var Map$1 = class extends Evented {
25924
26448
  ]) positions[positionName] = DOM.create("div", `maplibregl-ctrl-${positionName} `, controlContainer);
25925
26449
  this._container.addEventListener("scroll", this._onMapScroll, false);
25926
26450
  }
26451
+ /**
26452
+ * @internal
26453
+ * Reverses the DOM mutations of {@link Map._setupContainer}, returning the container element to its pre-construction state.
26454
+ */
26455
+ _cleanupContainer() {
26456
+ this._canvas.removeEventListener("webglcontextrestored", this._contextRestored, false);
26457
+ this._canvas.removeEventListener("webglcontextlost", this._contextLost, false);
26458
+ this._canvasContainer.remove();
26459
+ this._controlContainer.remove();
26460
+ this._container.removeEventListener("scroll", this._onMapScroll, false);
26461
+ this._container.classList.remove("maplibregl-map");
26462
+ }
25927
26463
  _resizeCanvas(width, height, pixelRatio) {
25928
26464
  this._canvas.width = Math.floor(pixelRatio * width);
25929
26465
  this._canvas.height = Math.floor(pixelRatio * height);
25930
26466
  this._canvas.style.width = `${width}px`;
25931
26467
  this._canvas.style.height = `${height}px`;
25932
26468
  }
26469
+ /**
26470
+ * @internal
26471
+ * Creates the WebGL2 context and the painter. Throws {@link GPUInitializationError} when the context cannot be created.
26472
+ */
25933
26473
  _setupPainter() {
25934
26474
  const attributes = {
25935
26475
  ...this._canvasContextAttributes,
@@ -25943,10 +26483,7 @@ var Map$1 = class extends Evented {
25943
26483
  creationEvent = event;
25944
26484
  }, { once: true });
25945
26485
  const gl = this._canvas.getContext("webgl2", attributes);
25946
- if (!gl) {
25947
- this.fire(new ErrorEvent(new GPUInitializationError(attributes, creationEvent)));
25948
- return;
25949
- }
26486
+ if (!gl) throw new GPUInitializationError(attributes, creationEvent);
25950
26487
  this.painter = new Painter(gl, this._camera.transform);
25951
26488
  }
25952
26489
  /**
@@ -26044,10 +26581,9 @@ var Map$1 = class extends Evented {
26044
26581
  this.style._updateSources(this._camera.transform);
26045
26582
  }
26046
26583
  if (this.terrain) {
26047
- this.terrain.tileManager.update(this._camera.transform, this.terrain);
26048
- this.terrain.resetElevationCache();
26584
+ if (this.terrain.tileManager.update(this._camera.transform, this.terrain)) this.terrain.resetElevationCache();
26049
26585
  this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
26050
- if (!this._camera.elevationFreeze && this.getCenterClampedToGround()) this._camera.transform.setElevation(this.terrain.getElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
26586
+ if (!this._camera.elevationFreeze && this.getCenterClampedToGround()) this._camera.transform.setElevation(this.terrain.getElevationForLngLat(this._camera.transform.center, this._camera.transform));
26051
26587
  } else {
26052
26588
  this._camera.transform.setMinElevationForCurrentTile(0);
26053
26589
  if (this.getCenterClampedToGround()) this._camera.transform.setElevation(0);
@@ -26120,12 +26656,7 @@ var Map$1 = class extends Evented {
26120
26656
  this._resizeObserver?.disconnect();
26121
26657
  const extension = this.painter.context.gl.getExtension("WEBGL_lose_context");
26122
26658
  if (extension?.loseContext) extension.loseContext();
26123
- this._canvas.removeEventListener("webglcontextrestored", this._contextRestored, false);
26124
- this._canvas.removeEventListener("webglcontextlost", this._contextLost, false);
26125
- this._canvasContainer.remove();
26126
- this._controlContainer.remove();
26127
- this._container.removeEventListener("scroll", this._onMapScroll, false);
26128
- this._container.classList.remove("maplibregl-map");
26659
+ this._cleanupContainer();
26129
26660
  this._removed = true;
26130
26661
  this.fire(new MapLibreEvent("remove"));
26131
26662
  }
@@ -26302,7 +26833,7 @@ var NavigationControl = class {
26302
26833
  this._updateZoomButtons = () => {
26303
26834
  const zoom = this._map.getZoom();
26304
26835
  const isMax = zoom === this._map.getMaxZoom();
26305
- const isMin = zoom === this._map.getMinZoom();
26836
+ const isMin = zoom === this._map.getMinZoom(true);
26306
26837
  this._zoomInButton.disabled = isMax;
26307
26838
  this._zoomOutButton.disabled = isMin;
26308
26839
  this._zoomInButton.setAttribute("aria-disabled", isMax.toString());
@@ -26356,7 +26887,7 @@ var NavigationControl = class {
26356
26887
  if (this.options.showZoom) {
26357
26888
  this._setButtonTitle(this._zoomInButton, "ZoomIn");
26358
26889
  this._setButtonTitle(this._zoomOutButton, "ZoomOut");
26359
- this._map.on("zoom", this._updateZoomButtons);
26890
+ this._map.on("move", this._updateZoomButtons);
26360
26891
  this._updateZoomButtons();
26361
26892
  }
26362
26893
  if (this.options.showCompass) {
@@ -26372,7 +26903,7 @@ var NavigationControl = class {
26372
26903
  /** {@inheritDoc IControl.onRemove} */
26373
26904
  onRemove() {
26374
26905
  this._container.remove();
26375
- if (this.options.showZoom) this._map.off("zoom", this._updateZoomButtons);
26906
+ if (this.options.showZoom) this._map.off("move", this._updateZoomButtons);
26376
26907
  if (this.options.showCompass) {
26377
26908
  if (this.options.visualizePitch) this._map.off("pitch", this._rotateCompassArrow);
26378
26909
  if (this.options.visualizeRoll) this._map.off("roll", this._rotateCompassArrow);