maplibre-gl 6.3.0 → 6.4.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 (56) hide show
  1. package/dist/maplibre-gl-dev.mjs +590 -208
  2. package/dist/maplibre-gl-dev.mjs.map +1 -1
  3. package/dist/maplibre-gl-shared-dev.mjs +258 -235
  4. package/dist/maplibre-gl-shared-dev.mjs.map +1 -1
  5. package/dist/maplibre-gl-shared.mjs +2 -2
  6. package/dist/maplibre-gl-shared.mjs.map +1 -1
  7. package/dist/maplibre-gl-worker-dev.mjs +2 -2
  8. package/dist/maplibre-gl-worker.mjs +2 -2
  9. package/dist/maplibre-gl-worker.mjs.map +1 -1
  10. package/dist/maplibre-gl.d.ts +258 -97
  11. package/dist/maplibre-gl.mjs +18 -18
  12. package/dist/maplibre-gl.mjs.map +1 -1
  13. package/package.json +7 -7
  14. package/src/data/bucket/fill_extrusion_bucket.ts +1 -13
  15. package/src/data/bucket/round_polygon_corners.test.ts +84 -36
  16. package/src/data/bucket/round_polygon_corners.ts +90 -39
  17. package/src/data/extent_bounds.ts +28 -1
  18. package/src/geo/projection/globe_transform.test.ts +129 -1
  19. package/src/geo/projection/globe_utils.ts +189 -2
  20. package/src/geo/projection/vertical_perspective_camera_helper.test.ts +24 -0
  21. package/src/geo/projection/vertical_perspective_camera_helper.ts +11 -10
  22. package/src/render/image_atlas.test.ts +110 -2
  23. package/src/render/image_atlas.ts +35 -4
  24. package/src/render/image_manager.test.ts +66 -1
  25. package/src/render/image_manager.ts +136 -144
  26. package/src/render/painter.ts +5 -2
  27. package/src/render/pattern_atlas.test.ts +131 -0
  28. package/src/render/pattern_atlas.ts +144 -0
  29. package/src/shaders/glsl/_prelude.vertex.glsl +8 -8
  30. package/src/shaders/glsl/_prelude.vertex.glsl.g.ts +1 -1
  31. package/src/shaders/glsl/color_relief.fragment.glsl +1 -2
  32. package/src/shaders/glsl/color_relief.fragment.glsl.g.ts +1 -1
  33. package/src/shaders/glsl/hillshade_prepare.fragment.glsl +12 -12
  34. package/src/shaders/glsl/hillshade_prepare.fragment.glsl.g.ts +1 -1
  35. package/src/source/image_source.test.ts +41 -31
  36. package/src/source/image_source.ts +14 -15
  37. package/src/source/raster_dem_tile_source.test.ts +37 -3
  38. package/src/source/raster_dem_tile_source.ts +1 -2
  39. package/src/source/raster_tile_source.test.ts +5 -26
  40. package/src/source/raster_tile_source.ts +4 -3
  41. package/src/style/style.test.ts +64 -0
  42. package/src/style/style.ts +37 -64
  43. package/src/symbol/cross_tile_symbol_index.test.ts +31 -0
  44. package/src/symbol/cross_tile_symbol_index.ts +67 -37
  45. package/src/tile/tile_manager.test.ts +69 -1
  46. package/src/tile/tile_manager.ts +14 -9
  47. package/src/ui/handler/two_fingers_touch.test.ts +14 -3
  48. package/src/ui/handler_inertia.test.ts +91 -0
  49. package/src/ui/handler_inertia.ts +65 -14
  50. package/src/ui/map_tests/map_images.test.ts +26 -0
  51. package/src/ui/map_tests/map_webgl.test.ts +2 -1
  52. package/src/ui/marker.test.ts +151 -0
  53. package/src/ui/marker.ts +120 -10
  54. package/src/util/image_request.ts +31 -1
  55. package/src/webgl/draw/draw_background.ts +1 -1
  56. package/src/webgl/program/pattern.ts +3 -3
@@ -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.3.0/LICENSE.txt
3
+ * @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v6.4.0/LICENSE.txt
4
4
  */
5
- import { $ as projectToWorldCoordinates, $n as defaultEasing, $r as add, $t as Transitionable, A as getOverlapMode, Ai as rotateZ, An as makeRequest, Ar as rollPitchBearingEqual, At as Uniform1f, B as parseGlyphPbf, Bi as offscreenCanvasSupported, Bn as MAX_VALID_LATITUDE, Br as zoomScale, Bt as CollisionBoxArray, C as createStyleLayer, Ci as identity, Cn as Evented, Cr as parseCacheControl, Ct as isHeatmapStyleLayer, D as isSymbolStyleLayer, Di as perspective, Dn as getJSON, Dr as rayPlaneIntersection, Dt as isCircleStyleLayer, E as isBackgroundStyleLayer, Ei as ortho, En as getArrayBuffer, Er as radiansToDegrees, Et as RGBAImage, Fi as create$2, Fn as config, Fr as threePlaneIntersection, Ft as UniformColor, Gn as clamp, Gr as scale$1, Gt as QuadTriangleArray, H as collisionCircleLayout, Hn as arrayBufferToImage, Hr as EXTENT, Ht as LineStripIndexArray, I as ImagePosition, Ii as determinant, In as AbortError, Ir as translatePosition, It as UniformColorArray, J as calculateTileMatrix, Jn as createIdentityMat4f64, Jr as fromEuler, Jt as createLayout, K as isFillExtrusionStyleLayer, Kn as clone$2, Kr as sqrLen, Kt as RasterBoundsArray, L as potpack, Li as invert, Ln as isAbortError, Lr as uniqueId, Lt as UniformFloatArray, M as evaluateSizeForZoom, Mi as translate, Mn as addProtocol, Mr as scaleZoom, Mt as Uniform2f, Ni as create$4, Nn as getProtocol, Nr as sphericalToCartesian, Nt as Uniform3f, O as SymbolBucket, Oi as rotateX, On as getReferrer, Or as readImageUsingVideoFrame, Ot as polygonIntersectsPolygon, P as getAnchorAlignment, Pi as fromRotation, Pn as removeProtocol, Pr as subscribe, Pt as Uniform4f, Q as maxMercatorHorizonAngle, Qn as deepEqual, Qr as transformMat4, Qt as TRANSITION_SUFFIX, R as isStyleImageWebGLData, Ri as rotate, Rn as throwIfAborted, Rr as warnOnce, Rt as UniformMatrix4f, S as Actor, Si as fromScaling, Sn as Event, Sr as nextPowerOfTwo, St as HEATMAP_FULL_RENDER_FBO_KEY, T as validateCustomStyleLayer, Ti as multiply, Tn as GLOBAL_DISPATCHER_ID, Tr as pointPlaneSignedDistance, Tt as AlphaImage, U as isLineStyleLayer, Un as arrayBufferToImageBitmap, Ur as create$3, Ut as Pos3dArray, Vi as Point, Vn as angleToRotateBetweenVectors2D, Vr as pixelsToTileUnits, Vt as CollisionCircleLayoutArray, Wn as bezier, Wr as length, Wt as PosArray, X as cameraMercatorCoordinateFromCenterAndRotation, Xn as createVec3f64, Xr as mul, Xt as DataConstantProperty, Y as cameraDirectionFromPitchBearing, Yn as createMat4f64, Yr as slerp, Yt as isRasterStyleLayer, Z as getMercatorHorizon, Zn as createVec4f64, Zr as scale$3, Zt as Properties, _ as OverscaledTileID, _i as clone$1, _n as featureFilter, _r as isTouchableOrPointableType, _t as SubdivisionGranularitySetting, a as clipLine, ai as negate, an as SPEC_SOURCE_TYPES, ar as extend, at as lngFromMercatorX, b as compareTileId, bi as equals, bn as latest, br as mapObject, bt as Texture, ci as rotateY, cn as validateStyle, cr as getAABB, ct as mercatorZfromAltitude, di as scaleAndAdd, dn as ProjectionDefinition, ei as clone, en as EvaluationParameters, er as degreesToRadians, et as tileCoordinatesToMercatorCoordinates, f as GEOJSON_TILE_LAYER_NAME, fi as sub, fn as ValidationError, fr as getRollPitchBearing, ft as isFillStyleLayer, g as CanonicalTileID, gi as zero$1, gn as emptyStyle, gr as isTouchableEvent, gt as SubdivisionGranularityExpression, h as Bounds, hi as transformQuat, hn as diff, hr as isSafari, ht as SOUTH_POLE_Y, ii as length$1, ir as evaluateZoomSnap, it as latFromMercatorY, j as evaluateSizeForFeature, ji as scale, jn as sameOrigin, jr as rollPitchBearingToQuat, jt as Uniform1i, k as addDynamicAttributes, ki as rotateY$1, kn as getVideo, kr as remapSaturate, kt as toEvaluationFeature, li as rotateZ$1, ln as validateStyleAndEmit, lr as getAngleDelta, lt as LngLat, mi as transformMat4$1, mn as derefLayers, mr as isPointableEvent, mt as NORTH_POLE_Y, ni as dot, nn as codePointUsesLocalIdeographFontFamily, nr as distanceOfAnglesRadians, nt as MercatorCoordinate, oi as normalize, on as emitValidationErrors, or as filterObject, ot as mercatorXfromLng, pi as transformMat3, pr as isImageBitmap, qn as createIdentityMat4f32, qr as zero, qt as TriangleIndexArray, r as TextAnchorEnum, ri as len, rn as ZoomHistory, rr as ensureError, rt as altitudeFromMercatorZ, s as TileCache, si as rotateX$1, sn as validateAndEmit, sr as findLineIntersection, st as mercatorYfromLat, t as getAnchorJustification, ti as cross, tr as differenceOfAnglesDegrees, tt as unprojectFromWorldCoordinates, u as GeoJSONFeature, ui as scale$2, un as Color, ur as getEdgeTiles, ut as earthRadius, v as UnwrappedTileID, vi as copy, vr as isWorker, vt as isColorReliefStyleLayer, w as isCustomStyleLayer, wi as invert$1, wn as AJAXError, wr as pick, wt as renderColorRamp, x as isInBoundsForZoomLngLat, xi as exactEquals, xn as ErrorEvent, xr as mod, xt as isHillshadeStyleLayer, y as calculateTileKey, yi as create$1, yn as interpolateFactory, yr as lerp, z as renderStyleImage, zi as isOffscreenCanvasDistorted, zr as wrap, zt as SegmentVector } from "./maplibre-gl-shared-dev.mjs";
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";
6
6
  //#region package.json
7
- var version$2 = "6.3.0";
7
+ var version$2 = "6.4.0";
8
8
  //#endregion
9
9
  //#region src/util/browser.ts
10
10
  let linkEl;
@@ -326,6 +326,12 @@ let ImageRequest;
326
326
  for (const key of Object.keys(throttleControlCallbacks)) if (throttleControlCallbacks[key]()) return true;
327
327
  return false;
328
328
  };
329
+ async function transformAndGetImage(requestManager, url, resourceType, abortController, supportImageRefresh = true, imageBitmapOptions) {
330
+ const requestParameters = await requestManager.transformRequest(url, resourceType);
331
+ throwIfAborted(abortController.signal);
332
+ return ImageRequest.getImage(requestParameters, abortController, supportImageRefresh, imageBitmapOptions);
333
+ }
334
+ _ImageRequest.transformAndGetImage = transformAndGetImage;
329
335
  _ImageRequest.getImage = (requestParameters, abortController, supportImageRefresh = true, imageBitmapOptions) => {
330
336
  return new Promise((resolve, reject) => {
331
337
  requestParameters.headers ||= {};
@@ -754,51 +760,25 @@ async function doOnceCompleted(jsonsMap, imagesMap) {
754
760
  //#endregion
755
761
  //#region src/render/image_manager.ts
756
762
  /**
757
- * When copied into the atlas texture, image data is padded by one pixel on each side. Icon
758
- * images are padded with fully transparent pixels, while pattern images are padded with a
759
- * copy of the image data wrapped from the opposite side. In both cases, this ensures the
760
- * correct behavior of GL_LINEAR texture sampling mode.
761
- */
762
- const padding = 1;
763
- /**
764
- * ImageManager does three things:
765
- *
766
- * 1. Tracks requests for icon images from tile workers and sends responses when the requests are fulfilled.
767
- * 2. Builds a texture atlas for pattern images.
768
- * 3. Rerenders renderable images once per frame
769
- *
770
- * These are disparate responsibilities and should eventually be handled by different classes. When we implement
771
- * data-driven support for `*-pattern`, we'll likely use per-bucket pattern atlases, and that would be a good time
772
- * to refactor this.
763
+ * Owns every image of the style - the ones the sprites bring in as well as the ones added at
764
+ * runtime - and tracks requests for them from the tile workers, sending responses when the
765
+ * requests are fulfilled.
773
766
  */
774
767
  var ImageManager = class extends Evented {
775
768
  constructor() {
776
769
  super();
777
770
  this.images = {};
778
- this.updatedImages = {};
779
- this.callbackDispatchedThisFrame = {};
771
+ this.updateVersion = 0;
780
772
  this.loaded = false;
781
773
  this.requestors = [];
782
774
  this.missingImageResolver = null;
783
- this.patterns = {};
784
- this.atlasImage = new RGBAImage({
785
- width: 1,
786
- height: 1
787
- });
788
- this.dirty = true;
775
+ this._spriteImagesIds = {};
776
+ this._imagesIds = null;
777
+ this._renderCallbacksDispatchedThisFrame = {};
789
778
  }
790
779
  destroy() {
791
- if (this.atlasTexture) {
792
- this.atlasTexture.destroy();
793
- this.atlasTexture = null;
794
- }
795
780
  for (const id of Object.keys(this.images)) this.removeImage(id);
796
- this.patterns = {};
797
- this.atlasImage = new RGBAImage({
798
- width: 1,
799
- height: 1
800
- });
801
- this.dirty = true;
781
+ this._spriteImagesIds = {};
802
782
  }
803
783
  isLoaded() {
804
784
  return this.loaded;
@@ -827,6 +807,7 @@ var ImageManager = class extends Evented {
827
807
  if (this.images[id]) throw new Error(`Image id ${id} already exist, use updateImage instead`);
828
808
  if (this._validate(id, image)) {
829
809
  this.images[id] = image;
810
+ this._imagesIds = null;
830
811
  if (image.isWebGLImage) this.updateImage(id, image, false);
831
812
  }
832
813
  }
@@ -869,21 +850,97 @@ var ImageManager = class extends Evented {
869
850
  if (content[2] < content[0]) return false;
870
851
  return content[3] >= content[1];
871
852
  }
853
+ /**
854
+ * Replaces an image that is already known under this id, bumping its version so that the
855
+ * atlases holding it notice.
856
+ *
857
+ * The old record is read directly rather than through {@link ImageManager.getImage}, which
858
+ * would force a synchronous decode of the sprite data of the very image being replaced. Its
859
+ * size therefore comes from whichever of the two the pixels are still in, since an image that
860
+ * came from a sprite and was never rendered has not been decoded yet, and its version is
861
+ * defaulted, since images start out without one.
862
+ *
863
+ * @param validate - whether to reject an image of a different size than the one it replaces
864
+ */
872
865
  updateImage(id, image, validate = true) {
873
- const oldImage = this.getImage(id);
874
- if (validate && (oldImage.data.width !== image.data.width || oldImage.data.height !== image.data.height)) throw new Error(`size mismatch between old image (${oldImage.data.width}x${oldImage.data.height}) and new image (${image.data.width}x${image.data.height}).`);
875
- image.version = oldImage.version + 1;
866
+ const oldImage = this.images[id];
867
+ if (validate) {
868
+ const oldData = oldImage.data || oldImage.spriteData;
869
+ if (oldData.width !== image.data.width || oldData.height !== image.data.height) throw new Error(`size mismatch between old image (${oldData.width}x${oldData.height}) and new image (${image.data.width}x${image.data.height}).`);
870
+ }
871
+ image.version = (oldImage.version ?? 0) + 1;
876
872
  this.images[id] = image;
877
- this.updatedImages[id] = true;
873
+ this.updateVersion++;
878
874
  }
879
875
  removeImage(id) {
880
876
  const image = this.images[id];
877
+ if (!image) return;
881
878
  delete this.images[id];
882
- delete this.patterns[id];
879
+ this._imagesIds = null;
883
880
  if (image.userImage?.onRemove) image.userImage.onRemove();
884
881
  }
882
+ /**
883
+ * @returns the ids of every image currently held, sprite images and runtime ones alike. The
884
+ * returned array is shared between callers and must not be modified.
885
+ */
885
886
  listImages() {
886
- return Object.keys(this.images);
887
+ this._imagesIds ??= Object.keys(this.images);
888
+ return this._imagesIds;
889
+ }
890
+ /**
891
+ * Images of the `default` sprite keep their plain id, the ones of any other sprite are
892
+ * namespaced by their sprite's id.
893
+ */
894
+ _getSpriteImageId(spriteId, imageId) {
895
+ return spriteId === "default" ? imageId : `${spriteId}:${imageId}`;
896
+ }
897
+ /**
898
+ * Takes over the images of a single sprite: adds the new ones, updates the ones that are
899
+ * already known, and removes the ones that a previous load of this same sprite had brought in
900
+ * but that are not part of it anymore.
901
+ *
902
+ * @param spriteId - the id of the sprite the images belong to
903
+ * @param images - the sprite's images, keyed by their id within the sprite
904
+ * @returns the ids of the images that this sprite now provides, and the ids of the ones that
905
+ * were removed because they are no longer part of it
906
+ */
907
+ setSpriteImages(spriteId, images) {
908
+ const previousIds = this._spriteImagesIds[spriteId] ?? [];
909
+ const loaded = [];
910
+ for (const id in images) {
911
+ const imageId = this._getSpriteImageId(spriteId, id);
912
+ loaded.push(imageId);
913
+ if (imageId in this.images) this.updateImage(imageId, images[id], false);
914
+ else this.addImage(imageId, images[id]);
915
+ }
916
+ const loadedIds = new Set(loaded);
917
+ const removed = previousIds.filter((imageId) => !loadedIds.has(imageId));
918
+ for (const imageId of removed) this.removeImage(imageId);
919
+ this._spriteImagesIds[spriteId] = loaded;
920
+ return {
921
+ loaded,
922
+ removed
923
+ };
924
+ }
925
+ /**
926
+ * Removes all the images a single sprite has brought in.
927
+ * @returns the ids of the removed images
928
+ */
929
+ removeSpriteImages(spriteId) {
930
+ const removed = this._spriteImagesIds[spriteId] ?? [];
931
+ for (const imageId of removed) this.removeImage(imageId);
932
+ delete this._spriteImagesIds[spriteId];
933
+ return removed;
934
+ }
935
+ /**
936
+ * Removes the images of every sprite, leaving the images added at runtime alone.
937
+ * @returns the ids of the removed images
938
+ */
939
+ removeAllSpriteImages() {
940
+ const removed = Object.values(this._spriteImagesIds).flat();
941
+ for (const imageId of removed) this.removeImage(imageId);
942
+ this._spriteImagesIds = {};
943
+ return removed;
887
944
  }
888
945
  setMissingImageResolver(resolver) {
889
946
  this.missingImageResolver = resolver;
@@ -904,7 +961,7 @@ var ImageManager = class extends Evented {
904
961
  async _getImagesForIds(ids) {
905
962
  const unresolvedIds = new Set(ids.filter((id) => !this.getImage(id)));
906
963
  const resolver = this.missingImageResolver;
907
- if (resolver) await Promise.all(Array.from(unresolvedIds, (id) => resolver(id)));
964
+ if (resolver) await Promise.allSettled(Array.from(unresolvedIds, (id) => resolver(id)));
908
965
  const response = {};
909
966
  for (const id of ids) {
910
967
  const image = this.getImage(id);
@@ -931,57 +988,126 @@ var ImageManager = class extends Evented {
931
988
  }
932
989
  return response;
933
990
  }
991
+ beginFrame() {
992
+ this._renderCallbacksDispatchedThisFrame = {};
993
+ }
994
+ /**
995
+ * Re-renders the images among `ids` that were added with a `render` callback (see
996
+ * `StyleImageInterface`), at most once per frame each.
997
+ */
998
+ dispatchRenderCallbacks(ids) {
999
+ for (const id of ids) {
1000
+ if (this._renderCallbacksDispatchedThisFrame[id]) continue;
1001
+ this._renderCallbacksDispatchedThisFrame[id] = true;
1002
+ const image = this.getImage(id);
1003
+ if (!image) warnOnce(`Image with ID: "${id}" was not found`);
1004
+ if (renderStyleImage(image)) this.updateImage(id, image);
1005
+ }
1006
+ }
1007
+ cloneImages() {
1008
+ const clonedImages = {};
1009
+ for (const id in this.images) {
1010
+ const image = this.images[id];
1011
+ clonedImages[id] = {
1012
+ ...image,
1013
+ data: image.data ? image.data.clone() : null
1014
+ };
1015
+ }
1016
+ return clonedImages;
1017
+ }
1018
+ };
1019
+ //#endregion
1020
+ //#region src/render/pattern_atlas.ts
1021
+ /**
1022
+ * When copied into the atlas texture, pattern images are padded by one pixel on each side with a
1023
+ * copy of the image data wrapped from the opposite side. This ensures the correct behavior of
1024
+ * GL_LINEAR texture sampling mode.
1025
+ */
1026
+ const PADDING = 1;
1027
+ /**
1028
+ * A texture atlas of the pattern images that are drawn without a tile's own {@link ImageAtlas},
1029
+ * i.e. `background-pattern`. Entries are packed on demand and re-packed whenever one of them is
1030
+ * added or changes, which is rare enough for the repacking cost not to matter.
1031
+ */
1032
+ var PatternAtlas = class {
1033
+ constructor(imageManager) {
1034
+ this._imageManager = imageManager;
1035
+ this._entries = {};
1036
+ this._image = new RGBAImage({
1037
+ width: 1,
1038
+ height: 1
1039
+ });
1040
+ this._dirty = true;
1041
+ }
1042
+ destroy() {
1043
+ if (this._texture) {
1044
+ this._texture.destroy();
1045
+ this._texture = null;
1046
+ }
1047
+ this._entries = {};
1048
+ this._image = new RGBAImage({
1049
+ width: 1,
1050
+ height: 1
1051
+ });
1052
+ this._dirty = true;
1053
+ }
934
1054
  getPixelSize() {
935
- const { width, height } = this.atlasImage;
1055
+ const { width, height } = this._image;
936
1056
  return {
937
1057
  width,
938
1058
  height
939
1059
  };
940
1060
  }
1061
+ /**
1062
+ * @returns the position of the pattern within the atlas, or `null` if there is no such image
1063
+ */
941
1064
  getPattern(id) {
942
- const pattern = this.patterns[id];
943
- const image = this.getImage(id);
1065
+ const image = this._imageManager.getImage(id);
944
1066
  if (!image) return null;
945
- if (pattern && pattern.position.version === image.version) return pattern.position;
946
- if (!pattern) {
1067
+ const entry = this._entries[id];
1068
+ if (entry?.image !== image) {
947
1069
  const bin = {
948
1070
  w: image.data.width + 2,
949
1071
  h: image.data.height + 2,
950
1072
  x: 0,
951
1073
  y: 0
952
1074
  };
953
- const position = new ImagePosition(bin, image);
954
- this.patterns[id] = {
1075
+ this._entries[id] = {
955
1076
  bin,
956
- position
1077
+ position: new ImagePosition(bin, image),
1078
+ image
957
1079
  };
958
- } else pattern.position.version = image.version;
959
- this._updatePatternAtlas();
960
- return this.patterns[id].position;
1080
+ } else if (entry.position.version !== image.version) entry.position.version = image.version;
1081
+ else return entry.position;
1082
+ this._update();
1083
+ return this._entries[id].position;
961
1084
  }
962
1085
  bind(context) {
963
1086
  const gl = context.gl;
964
- if (!this.atlasTexture) this.atlasTexture = new Texture(context, this.atlasImage, gl.RGBA);
965
- else if (this.dirty) {
966
- this.atlasTexture.update(this.atlasImage);
967
- this.dirty = false;
1087
+ if (!this._texture) {
1088
+ this._texture = new Texture(context, this._image, gl.RGBA);
1089
+ this._dirty = false;
1090
+ } else if (this._dirty) {
1091
+ this._texture.update(this._image);
1092
+ this._dirty = false;
968
1093
  }
969
- this.atlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
1094
+ this._texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
970
1095
  }
971
- _updatePatternAtlas() {
1096
+ _update() {
1097
+ for (const id in this._entries) if (!this._imageManager.getImage(id)) delete this._entries[id];
972
1098
  const bins = [];
973
- for (const id in this.patterns) bins.push(this.patterns[id].bin);
1099
+ for (const id in this._entries) bins.push(this._entries[id].bin);
974
1100
  const { w, h } = potpack(bins);
975
- const dst = this.atlasImage;
1101
+ const dst = this._image;
976
1102
  dst.resize({
977
1103
  width: w || 1,
978
1104
  height: h || 1
979
1105
  });
980
- for (const id in this.patterns) {
981
- const { bin } = this.patterns[id];
982
- const x = bin.x + padding;
983
- const y = bin.y + padding;
984
- const src = this.getImage(id).data;
1106
+ for (const id in this._entries) {
1107
+ const { bin } = this._entries[id];
1108
+ const x = bin.x + PADDING;
1109
+ const y = bin.y + PADDING;
1110
+ const src = this._entries[id].image.data;
985
1111
  const w = src.width;
986
1112
  const h = src.height;
987
1113
  RGBAImage.copy(src, dst, {
@@ -1035,30 +1161,7 @@ var ImageManager = class extends Evented {
1035
1161
  height: h
1036
1162
  });
1037
1163
  }
1038
- this.dirty = true;
1039
- }
1040
- beginFrame() {
1041
- this.callbackDispatchedThisFrame = {};
1042
- }
1043
- dispatchRenderCallbacks(ids) {
1044
- for (const id of ids) {
1045
- if (this.callbackDispatchedThisFrame[id]) continue;
1046
- this.callbackDispatchedThisFrame[id] = true;
1047
- const image = this.getImage(id);
1048
- if (!image) warnOnce(`Image with ID: "${id}" was not found`);
1049
- if (renderStyleImage(image)) this.updateImage(id, image);
1050
- }
1051
- }
1052
- cloneImages() {
1053
- const clonedImages = {};
1054
- for (const id in this.images) {
1055
- const image = this.images[id];
1056
- clonedImages[id] = {
1057
- ...image,
1058
- data: image.data ? image.data.clone() : null
1059
- };
1060
- }
1061
- return clonedImages;
1164
+ this._dirty = true;
1062
1165
  }
1063
1166
  };
1064
1167
  //#endregion
@@ -2790,10 +2893,9 @@ var RasterTileSource = class extends Evented {
2790
2893
  const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme);
2791
2894
  const premultiply = this._premultiplyAlpha;
2792
2895
  const imageBitmapOptions = premultiply ? void 0 : { premultiplyAlpha: "none" };
2793
- const request = await this.map._requestManager.transformRequest(url, "Tile");
2794
2896
  tile.abortController = new AbortController();
2795
2897
  try {
2796
- const response = await ImageRequest.getImage(request, tile.abortController, this.map._refreshExpiredTiles, imageBitmapOptions);
2898
+ const response = await ImageRequest.transformAndGetImage(this.map._requestManager, url, "Tile", tile.abortController, this.map._refreshExpiredTiles, imageBitmapOptions);
2797
2899
  delete tile.abortController;
2798
2900
  if (tile.aborted) {
2799
2901
  tile.state = "unloaded";
@@ -2875,11 +2977,10 @@ var RasterDEMTileSource = class extends RasterTileSource {
2875
2977
  }
2876
2978
  async loadTile(tile) {
2877
2979
  const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme);
2878
- const request = await this.map._requestManager.transformRequest(url, "Tile");
2879
2980
  tile.neighboringTiles = this._getNeighboringTiles(tile.tileID);
2880
2981
  tile.abortController = new AbortController();
2881
2982
  try {
2882
- const response = await ImageRequest.getImage(request, tile.abortController, this.map._refreshExpiredTiles);
2983
+ const response = await ImageRequest.transformAndGetImage(this.map._requestManager, url, "Tile", tile.abortController, this.map._refreshExpiredTiles, { colorSpaceConversion: "none" });
2883
2984
  delete tile.abortController;
2884
2985
  if (tile.aborted) {
2885
2986
  tile.state = "unloaded";
@@ -3847,11 +3948,10 @@ var ImageSource = class extends Evented {
3847
3948
  this._loaded = false;
3848
3949
  this.fire(new MapSourceDataEvent("dataloading"));
3849
3950
  this.url = this.options.url;
3850
- const request = await this.map._requestManager.transformRequest(this.url, "Image");
3851
- this._request = new AbortController();
3951
+ this._abortController = new AbortController();
3852
3952
  try {
3853
- const image = await ImageRequest.getImage(request, this._request);
3854
- this._request = null;
3953
+ const image = await ImageRequest.transformAndGetImage(this.map._requestManager, this.url, "Image", this._abortController);
3954
+ this._abortController = null;
3855
3955
  this._loaded = true;
3856
3956
  if (image?.data) {
3857
3957
  this._setImage(image.data);
@@ -3859,9 +3959,10 @@ var ImageSource = class extends Evented {
3859
3959
  this._finishLoading();
3860
3960
  }
3861
3961
  } catch (err) {
3862
- this._request = null;
3962
+ if (isAbortError(err)) return;
3963
+ this._abortController = null;
3863
3964
  this._loaded = true;
3864
- if (!isAbortError(err)) this.fire(new ErrorEvent(ensureError(err)));
3965
+ this.fire(new ErrorEvent(ensureError(err)));
3865
3966
  }
3866
3967
  }
3867
3968
  loaded() {
@@ -3878,9 +3979,9 @@ var ImageSource = class extends Evented {
3878
3979
  * @param options - The options object.
3879
3980
  */
3880
3981
  updateImage(options) {
3881
- if (this._request) {
3882
- this._request.abort();
3883
- this._request = null;
3982
+ if (this._abortController) {
3983
+ this._abortController.abort();
3984
+ this._abortController = null;
3884
3985
  }
3885
3986
  if ("image" in options) {
3886
3987
  this._loaded = true;
@@ -3915,9 +4016,9 @@ var ImageSource = class extends Evented {
3915
4016
  this.load();
3916
4017
  }
3917
4018
  onRemove() {
3918
- if (this._request) {
3919
- this._request.abort();
3920
- this._request = null;
4019
+ if (this._abortController) {
4020
+ this._abortController.abort();
4021
+ this._abortController = null;
3921
4022
  }
3922
4023
  this._disposeTexture();
3923
4024
  this.image = null;
@@ -5140,12 +5241,6 @@ function coveringTiles(transform, options) {
5140
5241
  return result.sort((a, b) => a.distanceSq - b.distanceSq).map((a) => a.tileID);
5141
5242
  }
5142
5243
  //#endregion
5143
- //#region src/data/extent_bounds.ts
5144
- /**
5145
- * The bounding box covering the entire extent of a tile.
5146
- */
5147
- const EXTENT_BOUNDS = Bounds.fromPoints([new Point(0, 0), new Point(EXTENT, EXTENT)]);
5148
- //#endregion
5149
5244
  //#region src/tile/tile_manager_raster.ts
5150
5245
  function isRasterType(type) {
5151
5246
  return type === "raster" || type === "image" || type === "video";
@@ -5463,10 +5558,10 @@ var TileManager = class TileManager extends Evented {
5463
5558
  if (shouldReload) this.reload();
5464
5559
  if (this.transform) this.update(this.transform, this.terrain);
5465
5560
  }
5466
- async _loadTile(tile, id, state) {
5561
+ async _loadTile(tile, id, state, hadData) {
5467
5562
  try {
5468
5563
  const result = await this._source.loadTile(tile);
5469
- this._tileLoaded(tile, id, state, result);
5564
+ this._tileLoaded(tile, id, state, hadData, result);
5470
5565
  } catch (err) {
5471
5566
  tile.state = "errored";
5472
5567
  if (err.status !== 404) this._source.fire(new ErrorEvent(ensureError(err), { tile }));
@@ -5532,12 +5627,15 @@ var TileManager = class TileManager extends Evented {
5532
5627
  async _reloadTile(id, state) {
5533
5628
  const tile = this._inViewTiles.getTileById(id);
5534
5629
  if (!tile) return;
5630
+ const hadData = tile.hasData();
5535
5631
  if (tile.state !== "loading") tile.state = state;
5536
- await this._loadTile(tile, id, state);
5632
+ await this._loadTile(tile, id, state, hadData);
5537
5633
  }
5538
- _tileLoaded(tile, id, previousState, result) {
5539
- tile.timeAdded = now();
5540
- if (tile.selfFading) tile.fadeEndTime = tile.timeAdded + this._rasterFadeDuration;
5634
+ _tileLoaded(tile, id, previousState, hadData, result) {
5635
+ if (!hadData) {
5636
+ tile.timeAdded = now();
5637
+ if (tile.selfFading) tile.fadeEndTime = tile.timeAdded + this._rasterFadeDuration;
5638
+ }
5541
5639
  if (previousState === "expired") tile.refreshedUponExpiration = true;
5542
5640
  this._setTileReloadTimer(id, tile);
5543
5641
  if (result?.unmodified) return;
@@ -5788,7 +5886,7 @@ var TileManager = class TileManager extends Evented {
5788
5886
  const cached = tile;
5789
5887
  if (!tile) {
5790
5888
  tile = new Tile(tileID, this._source.tileSize * tileID.overscaleFactor());
5791
- this._loadTile(tile, tileID.key, tile.state);
5889
+ this._loadTile(tile, tileID.key, tile.state, false);
5792
5890
  }
5793
5891
  tile.uses++;
5794
5892
  this._inViewTiles.setTile(tileID.key, tile);
@@ -8284,24 +8382,54 @@ var TileLayerIndex = class {
8284
8382
  const entry = this._symbolsByKey[symbolInstance.key];
8285
8383
  if (!entry) continue;
8286
8384
  const scaledSymbolCoord = this.getScaledCoordinates(symbolInstance, newTileID);
8287
- if (entry.index) {
8288
- const indexes = entry.index.range(scaledSymbolCoord.x - tolerance, scaledSymbolCoord.y - tolerance, scaledSymbolCoord.x + tolerance, scaledSymbolCoord.y + tolerance).sort();
8289
- for (const i of indexes) {
8290
- const crossTileID = entry.crossTileIDs[i];
8291
- if (!zoomCrossTileIDs[crossTileID]) {
8292
- zoomCrossTileIDs[crossTileID] = true;
8293
- symbolInstance.crossTileID = crossTileID;
8294
- break;
8295
- }
8296
- }
8297
- } else if (entry.positions) for (let i = 0; i < entry.positions.length; i++) {
8298
- const thisTileSymbol = entry.positions[i];
8299
- const crossTileID = entry.crossTileIDs[i];
8300
- if (Math.abs(thisTileSymbol.x - scaledSymbolCoord.x) <= tolerance && Math.abs(thisTileSymbol.y - scaledSymbolCoord.y) <= tolerance && !zoomCrossTileIDs[crossTileID]) {
8301
- zoomCrossTileIDs[crossTileID] = true;
8302
- symbolInstance.crossTileID = crossTileID;
8303
- break;
8304
- }
8385
+ if (entry.index) this.findMatchesForIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs);
8386
+ else if (entry.positions) this.findMatchesForNonIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs);
8387
+ }
8388
+ }
8389
+ /**
8390
+ * Matches a symbol instance against an entry backed by a KDBush spatial index
8391
+ * (used for keys with more than {@link KDBUSH_THRESHHOLD} symbols).
8392
+ *
8393
+ * Matches any symbol with the same key whose coordinates are within one grid
8394
+ * unit (with a 4px grid, this covers a 12px by 12px area). The lowest-index
8395
+ * unclaimed candidate is claimed in a single pass rather than sorting the
8396
+ * whole result set on every query: `range()` can return many candidates where
8397
+ * symbols are coincident (e.g. stacked point markers), so a per-query sort
8398
+ * would dominate placement cost on dense layers.
8399
+ *
8400
+ * Once a symbol is marked duplicate against a parent symbol, no other symbol
8401
+ * at the same zoom level may duplicate against the same parent (see issue #5993).
8402
+ */
8403
+ findMatchesForIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs) {
8404
+ const indexes = entry.index.range(scaledSymbolCoord.x - tolerance, scaledSymbolCoord.y - tolerance, scaledSymbolCoord.x + tolerance, scaledSymbolCoord.y + tolerance);
8405
+ let bestIndex = Infinity;
8406
+ for (const candidate of indexes) if (candidate < bestIndex && !zoomCrossTileIDs[entry.crossTileIDs[candidate]]) bestIndex = candidate;
8407
+ if (bestIndex !== Infinity) {
8408
+ const crossTileID = entry.crossTileIDs[bestIndex];
8409
+ zoomCrossTileIDs[crossTileID] = true;
8410
+ symbolInstance.crossTileID = crossTileID;
8411
+ }
8412
+ }
8413
+ /**
8414
+ * Matches a symbol instance against an entry that stores its positions as a
8415
+ * plain array (used for keys with at most {@link KDBUSH_THRESHHOLD} symbols).
8416
+ *
8417
+ * Matches any symbol with the same key whose coordinates are within one grid
8418
+ * unit (with a 4px grid, this covers a 12px by 12px area). Positions are
8419
+ * stored in symbol-instance order, so the first unclaimed match is also the
8420
+ * lowest-index one.
8421
+ *
8422
+ * Once a symbol is marked duplicate against a parent symbol, no other symbol
8423
+ * at the same zoom level may duplicate against the same parent (see issue #5993).
8424
+ */
8425
+ findMatchesForNonIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs) {
8426
+ for (let i = 0; i < entry.positions.length; i++) {
8427
+ const thisTileSymbol = entry.positions[i];
8428
+ const crossTileID = entry.crossTileIDs[i];
8429
+ if (Math.abs(thisTileSymbol.x - scaledSymbolCoord.x) <= tolerance && Math.abs(thisTileSymbol.y - scaledSymbolCoord.y) <= tolerance && !zoomCrossTileIDs[crossTileID]) {
8430
+ zoomCrossTileIDs[crossTileID] = true;
8431
+ symbolInstance.crossTileID = crossTileID;
8432
+ break;
8305
8433
  }
8306
8434
  }
8307
8435
  }
@@ -8423,7 +8551,7 @@ var CrossTileSymbolIndex = class {
8423
8551
  var _prelude_fragment_glsl_g_default = "#ifdef GL_ES\nprecision mediump float;\n#else\n#if !defined(lowp)\n#define lowp\n#endif\n#if !defined(mediump)\n#define mediump\n#endif\n#if !defined(highp)\n#define highp\n#endif\n#endif\nout highp vec4 fragColor;";
8424
8552
  //#endregion
8425
8553
  //#region src/shaders/glsl/_prelude.vertex.glsl.g.ts
8426
- var _prelude_vertex_glsl_g_default = "#ifdef GL_ES\nprecision highp float;\n#else\n#if !defined(lowp)\n#define lowp\n#endif\n#if !defined(mediump)\n#define mediump\n#endif\n#if !defined(highp)\n#define highp\n#endif\n#endif\nvec2 unpack_float(const float packedValue) {int packedIntValue=int(packedValue);int v0=packedIntValue/256;return vec2(v0,packedIntValue-v0*256);}vec2 unpack_opacity(const uint packedOpacity) {return vec2(float(packedOpacity >> 1u)/127.0,float(packedOpacity & 1u));}vec4 decode_color(const vec2 encodedColor) {return vec4(unpack_float(encodedColor[0])/255.0,unpack_float(encodedColor[1])/255.0\n);}float unpack_mix_vec2(const vec2 packedValue,const float t) {return mix(packedValue[0],packedValue[1],t);}vec4 unpack_mix_color(const vec4 packedColors,const float t) {vec4 minColor=decode_color(vec2(packedColors[0],packedColors[1]));vec4 maxColor=decode_color(vec2(packedColors[2],packedColors[3]));return mix(minColor,maxColor,t);}vec2 get_pattern_pos(const vec2 pixel_coord_upper,const vec2 pixel_coord_lower,const vec2 pattern_size,const float tile_units_to_pixels,const vec2 pos) {vec2 offset=mod(mod(mod(pixel_coord_upper,pattern_size)*256.0,pattern_size)*256.0+pixel_coord_lower,pattern_size);return (tile_units_to_pixels*pos+offset)/pattern_size;}mat3 rotationMatrixFromAxisAngle(vec3 u,float angle) {float c=cos(angle);float s=sin(angle);float c2=1.0-c;return mat3(u.x*u.x*c2+ c,u.x*u.y*c2-u.z*s,u.x*u.z*c2+u.y*s,u.y*u.x*c2+u.z*s,u.y*u.y*c2+ c,u.y*u.z*c2-u.x*s,u.z*u.x*c2-u.y*s,u.z*u.y*c2+u.x*s,u.z*u.z*c2+ c\n);}\n#ifdef TERRAIN3D\nuniform sampler2D u_terrain;uniform float u_terrain_dim;uniform mat4 u_terrain_matrix;uniform vec4 u_terrain_unpack;uniform float u_terrain_exaggeration;uniform highp sampler2D u_depth;\n#endif\nconst highp vec4 bitSh=vec4(256.*256.*256.,256.*256.,256.,1.);const highp vec4 bitShifts=vec4(1.)/bitSh;highp float unpack(highp vec4 color) {return dot(color,bitShifts);}highp float depthOpacity(vec3 frag) {\n#ifdef TERRAIN3D\nhighp float d=unpack(texture(u_depth,frag.xy*0.5+0.5))+0.0001-frag.z;return 1.0-max(0.0,min(1.0,-d*500.0));\n#else\nreturn 1.0;\n#endif\n}float calculate_visibility(vec4 pos) {\n#ifdef TERRAIN3D\nvec3 frag=pos.xyz/pos.w;highp float d=depthOpacity(frag);if (d > 0.95) return 1.0;return (d+depthOpacity(frag+vec3(0.0,0.01,0.0)))/2.0;\n#else\nreturn 1.0;\n#endif\n}float ele(vec2 pos) {\n#ifdef TERRAIN3D\nvec4 rgb=(texture(u_terrain,pos)*255.0)*u_terrain_unpack;return rgb.r+rgb.g+rgb.b-u_terrain_unpack.a;\n#else\nreturn 0.0;\n#endif\n}float get_elevation(vec2 pos) {\n#ifdef TERRAIN3D\n#ifdef GLOBE\nif ((pos.y <-32767.5) || (pos.y > 32766.5)) {return 0.0;}\n#endif\nvec2 coord=(u_terrain_matrix*vec4(pos,0.0,1.0)).xy*u_terrain_dim+1.0;vec2 f=fract(coord);vec2 c=(floor(coord)+0.5)/(u_terrain_dim+2.0);float d=1.0/(u_terrain_dim+2.0);float tl=ele(c);float tr=ele(c+vec2(d,0.0));float bl=ele(c+vec2(0.0,d));float br=ele(c+vec2(d,d));float elevation=mix(mix(tl,tr,f.x),mix(bl,br,f.x),f.y);return elevation*u_terrain_exaggeration;\n#else\nreturn 0.0;\n#endif\n}const float PI=3.141592653589793;uniform mat4 u_projection_matrix;";
8554
+ var _prelude_vertex_glsl_g_default = "#ifdef GL_ES\nprecision highp float;\n#else\n#if !defined(lowp)\n#define lowp\n#endif\n#if !defined(mediump)\n#define mediump\n#endif\n#if !defined(highp)\n#define highp\n#endif\n#endif\nvec2 unpack_float(const float packedValue) {int packedIntValue=int(packedValue);int v0=packedIntValue/256;return vec2(v0,packedIntValue-v0*256);}vec2 unpack_opacity(const uint packedOpacity) {return vec2(float(packedOpacity >> 1u)/127.0,float(packedOpacity & 1u));}vec4 decode_color(const vec2 encodedColor) {return vec4(unpack_float(encodedColor[0])/255.0,unpack_float(encodedColor[1])/255.0\n);}float unpack_mix_vec2(const vec2 packedValue,const float t) {return mix(packedValue[0],packedValue[1],t);}vec4 unpack_mix_color(const vec4 packedColors,const float t) {vec4 minColor=decode_color(vec2(packedColors[0],packedColors[1]));vec4 maxColor=decode_color(vec2(packedColors[2],packedColors[3]));return mix(minColor,maxColor,t);}vec2 get_pattern_pos(const vec2 pixel_coord_upper,const vec2 pixel_coord_lower,const vec2 pattern_size,const float tile_units_to_pixels,const vec2 pos) {vec2 offset=mod(mod(mod(pixel_coord_upper,pattern_size)*256.0,pattern_size)*256.0+pixel_coord_lower,pattern_size);return (tile_units_to_pixels*pos+offset)/pattern_size;}mat3 rotationMatrixFromAxisAngle(vec3 u,float angle) {float c=cos(angle);float s=sin(angle);float c2=1.0-c;return mat3(u.x*u.x*c2+ c,u.x*u.y*c2-u.z*s,u.x*u.z*c2+u.y*s,u.y*u.x*c2+u.z*s,u.y*u.y*c2+ c,u.y*u.z*c2-u.x*s,u.z*u.x*c2-u.y*s,u.z*u.y*c2+u.x*s,u.z*u.z*c2+ c\n);}\n#ifdef TERRAIN3D\nuniform sampler2D u_terrain;uniform float u_terrain_dim;uniform mat4 u_terrain_matrix;uniform vec4 u_terrain_unpack;uniform float u_terrain_exaggeration;uniform highp sampler2D u_depth;\n#endif\nconst highp vec4 bitSh=vec4(256.*256.*256.,256.*256.,256.,1.);const highp vec4 bitShifts=vec4(1.)/bitSh;highp float unpack(highp vec4 color) {return dot(color,bitShifts);}highp float depthOpacity(vec3 frag) {\n#ifdef TERRAIN3D\nhighp float d=unpack(texture(u_depth,frag.xy*0.5+0.5))+0.0001-frag.z;return 1.0-max(0.0,min(1.0,-d*500.0));\n#else\nreturn 1.0;\n#endif\n}float calculate_visibility(vec4 pos) {\n#ifdef TERRAIN3D\nvec3 frag=pos.xyz/pos.w;highp float d=depthOpacity(frag);if (d > 0.95) return 1.0;return (d+depthOpacity(frag+vec3(0.0,0.01,0.0)))/2.0;\n#else\nreturn 1.0;\n#endif\n}float ele(ivec2 pos) {\n#ifdef TERRAIN3D\nvec4 rgb=(texelFetch(u_terrain,pos,0)*255.0)*u_terrain_unpack;return rgb.r+rgb.g+rgb.b-u_terrain_unpack.a;\n#else\nreturn 0.0;\n#endif\n}float get_elevation(vec2 pos) {\n#ifdef TERRAIN3D\n#ifdef GLOBE\nif ((pos.y <-32767.5) || (pos.y > 32766.5)) {return 0.0;}\n#endif\nvec2 coord=(u_terrain_matrix*vec4(pos,0.0,1.0)).xy*u_terrain_dim+1.0;vec2 f=fract(coord);ivec2 c=ivec2(floor(coord));ivec2 hi=textureSize(u_terrain,0)-1;float tl=ele(clamp(c,ivec2(0),hi));float tr=ele(clamp(c+ivec2(1,0),ivec2(0),hi));float bl=ele(clamp(c+ivec2(0,1),ivec2(0),hi));float br=ele(clamp(c+ivec2(1,1),ivec2(0),hi));float elevation=mix(mix(tl,tr,f.x),mix(bl,br,f.x),f.y);return elevation*u_terrain_exaggeration;\n#else\nreturn 0.0;\n#endif\n}const float PI=3.141592653589793;uniform mat4 u_projection_matrix;";
8427
8555
  //#endregion
8428
8556
  //#region src/shaders/glsl/background.fragment.glsl.g.ts
8429
8557
  var background_fragment_glsl_g_default = "uniform vec4 u_color;uniform float u_opacity;void main() {fragColor=u_color*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}";
@@ -8459,7 +8587,7 @@ const shaders = {
8459
8587
  heatmapTexture: prepare("uniform sampler2D u_image;uniform sampler2D u_color_ramp;uniform float u_opacity;in vec2 v_pos;void main() {float t=texture(u_image,v_pos).r;vec4 color=texture(u_color_ramp,vec2(t,0.5));fragColor=color*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(0.0);\n#endif\n}", "uniform mat4 u_matrix;uniform vec2 u_world;layout(location=0) in vec2 a_pos;out vec2 v_pos;void main() {gl_Position=u_matrix*vec4(a_pos*u_world,0,1);v_pos.x=a_pos.x;v_pos.y=1.0-a_pos.y;}"),
8460
8588
  collisionBox: prepare("flat in float v_placed;flat in float v_notUsed;void main() {float alpha=0.5;fragColor=vec4(1.0,0.0,0.0,1.0)*alpha;if (v_placed > 0.5) {fragColor=vec4(0.0,0.0,1.0,0.5)*alpha;}if (v_notUsed > 0.5) {fragColor*=.1;}}", "layout(location=0) in vec2 a_anchor_pos;layout(location=1) in vec2 a_placed;layout(location=2) in vec2 a_box_real;uniform vec2 u_pixel_extrude_scale;flat out float v_placed;flat out float v_notUsed;void main() {gl_Position=projectTileWithElevation(a_anchor_pos,get_elevation(a_anchor_pos));gl_Position.xy=((a_box_real+0.5)*u_pixel_extrude_scale*2.0-1.0)*vec2(1.0,-1.0)*gl_Position.w;if (gl_Position.z/gl_Position.w < 1.1) {gl_Position.z=0.5;}v_placed=a_placed.x;v_notUsed=a_placed.y;}"),
8461
8589
  collisionCircle: prepare("flat in float v_radius;in vec2 v_extrude;flat in float v_collision;void main() {float alpha=0.5;float stroke_radius=0.9;float distance_to_center=length(v_extrude);float distance_to_edge=abs(distance_to_center-v_radius);float opacity_t=smoothstep(-stroke_radius,0.0,-distance_to_edge);vec4 color=mix(vec4(0.0,0.0,1.0,0.5),vec4(1.0,0.0,0.0,1.0),v_collision);fragColor=color*alpha*opacity_t;}", "layout(location=0) in vec2 a_pos;layout(location=1) in float a_radius;layout(location=2) in vec2 a_flags;uniform vec2 u_viewport_size;flat out float v_radius;out vec2 v_extrude;flat out float v_collision;void main() {float radius=a_radius;float collision=a_flags.x;float vertexIdx=a_flags.y;vec2 quadVertexOffset=vec2(mix(-1.0,1.0,float(vertexIdx >=2.0)),mix(-1.0,1.0,float(vertexIdx >=1.0 && vertexIdx <=2.0)));vec2 quadVertexExtent=quadVertexOffset*radius;float padding_factor=1.2;v_radius=radius;v_extrude=quadVertexExtent*padding_factor;v_collision=collision;gl_Position=vec4((a_pos/u_viewport_size*2.0-1.0)*vec2(1.0,-1.0),0.0,1.0)+vec4(quadVertexExtent*padding_factor/u_viewport_size*2.0,0.0,0.0);}"),
8462
- colorRelief: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nuniform sampler2D u_image;uniform vec4 u_unpack;uniform sampler2D u_elevation_stops;uniform sampler2D u_color_stops;uniform int u_color_ramp_size;uniform float u_opacity;in vec2 v_pos;float getElevation(vec2 coord) {vec4 data=texture(u_image,coord)*255.0;data.a=-1.0;return dot(data,u_unpack);}float getElevationStop(int stop) {float x=(float(stop)+0.5)/float(u_color_ramp_size);vec4 data=texture(u_elevation_stops,vec2(x,0))*255.0;data.a=-1.0;return dot(data,u_unpack);}void main() {float el=getElevation(v_pos);int r=(u_color_ramp_size-1);int l=0;float el_l=getElevationStop(l);float el_r=getElevationStop(r);while(r-l > 1){int m=(r+l)/2;float el_m=getElevationStop(m);if(el < el_m){r=m;el_r=el_m;}else\n{l=m;el_l=el_m;}}float x=(float(l)+(el-el_l)/(el_r-el_l)+0.5)/float(u_color_ramp_size);fragColor=u_opacity*texture(u_color_stops,vec2(x,0));\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_dimension;layout(location=0) in vec2 a_pos;out vec2 v_pos;void main() {gl_Position=projectTile(a_pos,a_pos);highp vec2 epsilon=1.0/u_dimension;float scale=(u_dimension.x-2.0)/u_dimension.x;v_pos=(a_pos/8192.0)*scale+epsilon;if (a_pos.y <-32767.5) {v_pos.y=0.0;}if (a_pos.y > 32766.5) {v_pos.y=1.0;}}"),
8590
+ colorRelief: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nuniform sampler2D u_image;uniform vec4 u_unpack;uniform sampler2D u_elevation_stops;uniform sampler2D u_color_stops;uniform int u_color_ramp_size;uniform float u_opacity;in vec2 v_pos;float getElevation(vec2 coord) {vec4 data=texture(u_image,coord)*255.0;data.a=-1.0;return dot(data,u_unpack);}float getElevationStop(int stop) {vec4 data=texelFetch(u_elevation_stops,ivec2(stop,0),0)*255.0;data.a=-1.0;return dot(data,u_unpack);}void main() {float el=getElevation(v_pos);int r=(u_color_ramp_size-1);int l=0;float el_l=getElevationStop(l);float el_r=getElevationStop(r);while(r-l > 1){int m=(r+l)/2;float el_m=getElevationStop(m);if(el < el_m){r=m;el_r=el_m;}else\n{l=m;el_l=el_m;}}float x=(float(l)+(el-el_l)/(el_r-el_l)+0.5)/float(u_color_ramp_size);fragColor=u_opacity*texture(u_color_stops,vec2(x,0));\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_dimension;layout(location=0) in vec2 a_pos;out vec2 v_pos;void main() {gl_Position=projectTile(a_pos,a_pos);highp vec2 epsilon=1.0/u_dimension;float scale=(u_dimension.x-2.0)/u_dimension.x;v_pos=(a_pos/8192.0)*scale+epsilon;if (a_pos.y <-32767.5) {v_pos.y=0.0;}if (a_pos.y > 32766.5) {v_pos.y=1.0;}}"),
8463
8591
  debug: prepare("uniform highp vec4 u_color;uniform sampler2D u_overlay;in vec2 v_uv;void main() {vec4 overlay_color=texture(u_overlay,v_uv);fragColor=mix(u_color,overlay_color,overlay_color.a);}", "layout(location=0) in vec2 a_pos;out vec2 v_uv;uniform float u_overlay_scale;void main() {v_uv=a_pos/8192.0;gl_Position=projectTileWithElevation(a_pos*u_overlay_scale,get_elevation(a_pos));}"),
8464
8592
  depth: prepare(clipping_mask_fragment_glsl_g_default, "layout(location=0) in vec2 a_pos;void main() {\n#ifdef GLOBE\ngl_Position=projectTileFor3D(a_pos,0.0);\n#else\ngl_Position=u_projection_matrix*vec4(a_pos,0.0,1.0);\n#endif\n}"),
8465
8593
  fill: prepare("#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize lowp float opacity\nfragColor=color*opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_fill_translate;layout(location=0) in vec2 a_pos;\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize lowp float opacity\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}gl_Position=projectTile(a_pos+u_fill_translate,a_pos);}"),
@@ -8468,7 +8596,7 @@ const shaders = {
8468
8596
  fillPattern: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nuniform vec2 u_texsize;uniform float u_fade;uniform sampler2D u_image;in vec2 v_pos_a;in vec2 v_pos_b;\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\nvoid main() {\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(pattern_tl_a/u_texsize,pattern_br_a/u_texsize,imagecoord);vec4 color1=texture(u_image,pos);vec2 imagecoord_b=mod(v_pos_b,1.0);vec2 pos2=mix(pattern_tl_b/u_texsize,pattern_br_b/u_texsize,imagecoord_b);vec4 color2=texture(u_image,pos2);fragColor=mix(color1,color2,u_fade)*opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform vec3 u_scale;uniform vec2 u_fill_translate;layout(location=0) in vec2 a_pos;out vec2 v_pos_a;out vec2 v_pos_b;\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\n#pragma maplibre: define lowp float pixel_ratio_from\n#pragma maplibre: define lowp float pixel_ratio_to\nvoid main() {\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\n#pragma maplibre: initialize lowp float pixel_ratio_from\n#pragma maplibre: initialize lowp float pixel_ratio_to\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}vec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;float tileZoomRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;vec2 display_size_a=(pattern_br_a-pattern_tl_a)/pixel_ratio_from;vec2 display_size_b=(pattern_br_b-pattern_tl_b)/pixel_ratio_to;gl_Position=projectTile(a_pos+u_fill_translate,a_pos);v_pos_a=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,fromScale*display_size_a,tileZoomRatio,a_pos);v_pos_b=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,toScale*display_size_b,tileZoomRatio,a_pos);}"),
8469
8597
  fillExtrusion: prepare("in vec4 v_color;void main() {fragColor=v_color;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec3 u_lightcolor;uniform lowp vec3 u_lightpos;uniform lowp vec3 u_lightpos_globe;uniform lowp float u_lightintensity;uniform float u_vertical_gradient;uniform lowp float u_opacity;uniform vec2 u_fill_translate;layout(location=0) in vec2 a_pos;layout(location=1) in ivec4 a_normal_ed;\n#ifdef TERRAIN3D\nlayout(location=2) in vec2 a_centroid;\n#endif\nout vec4 v_color;\n#pragma maplibre: define highp float base\n#pragma maplibre: define highp float height\n#pragma maplibre: define highp vec4 color\nvoid main() {\n#pragma maplibre: initialize highp float base\n#pragma maplibre: initialize highp float height\n#pragma maplibre: initialize highp vec4 color\nvec3 normal=vec3(a_normal_ed.xyz);\n#ifdef TERRAIN3D\nfloat height_terrain3d_offset=get_elevation(a_centroid);float base_terrain3d_offset=height_terrain3d_offset-(base > 0.0 ? 0.0 : 10.0);\n#else\nfloat height_terrain3d_offset=0.0;float base_terrain3d_offset=0.0;\n#endif\nbase=max(0.0,base)+base_terrain3d_offset;height=max(0.0,height)+height_terrain3d_offset;float t=float(a_normal_ed.x & 1);float elevation=t > 0.0 ? height : base;vec2 posInTile=a_pos+u_fill_translate;\n#ifdef GLOBE\nvec3 spherePos=projectToSphere(posInTile,a_pos);gl_Position=interpolateProjectionFor3D(posInTile,spherePos,elevation);\n#else\ngl_Position=u_projection_matrix*vec4(posInTile,elevation,1.0);\n#endif\nfloat colorvalue=color.r*0.2126+color.g*0.7152+color.b*0.0722;v_color=vec4(0.0,0.0,0.0,1.0);vec4 ambientlight=vec4(0.03,0.03,0.03,1.0);color+=ambientlight;vec3 normalForLighting=normal/16384.0;float directional=clamp(dot(normalForLighting,u_lightpos),0.0,1.0);\n#ifdef GLOBE\nmat3 rotMatrix=globeGetRotationMatrix(spherePos);normalForLighting=rotMatrix*normalForLighting;directional=mix(directional,clamp(dot(normalForLighting,u_lightpos_globe),0.0,1.0),u_projection_transition);\n#endif\ndirectional=mix((1.0-u_lightintensity),max((1.0-colorvalue+u_lightintensity),1.0),directional);if (normal.y !=0.0) {directional*=((1.0-u_vertical_gradient)+(u_vertical_gradient*clamp((t+base)*pow(height/150.0,0.5),mix(0.7,0.98,1.0-u_lightintensity),1.0)));}v_color.r+=clamp(color.r*directional*u_lightcolor.r,mix(0.0,0.3,1.0-u_lightcolor.r),1.0);v_color.g+=clamp(color.g*directional*u_lightcolor.g,mix(0.0,0.3,1.0-u_lightcolor.g),1.0);v_color.b+=clamp(color.b*directional*u_lightcolor.b,mix(0.0,0.3,1.0-u_lightcolor.b),1.0);v_color*=u_opacity;}"),
8470
8598
  fillExtrusionPattern: prepare("uniform vec2 u_texsize;uniform float u_fade;uniform sampler2D u_image;in vec2 v_pos_a;in vec2 v_pos_b;in vec4 v_lighting;\n#pragma maplibre: define lowp float base\n#pragma maplibre: define lowp float height\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\n#pragma maplibre: define lowp float pixel_ratio_from\n#pragma maplibre: define lowp float pixel_ratio_to\nvoid main() {\n#pragma maplibre: initialize lowp float base\n#pragma maplibre: initialize lowp float height\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\n#pragma maplibre: initialize lowp float pixel_ratio_from\n#pragma maplibre: initialize lowp float pixel_ratio_to\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(pattern_tl_a/u_texsize,pattern_br_a/u_texsize,imagecoord);vec4 color1=texture(u_image,pos);vec2 imagecoord_b=mod(v_pos_b,1.0);vec2 pos2=mix(pattern_tl_b/u_texsize,pattern_br_b/u_texsize,imagecoord_b);vec4 color2=texture(u_image,pos2);vec4 mixedColor=mix(color1,color2,u_fade);fragColor=mixedColor*v_lighting;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform float u_height_factor;uniform vec3 u_scale;uniform float u_vertical_gradient;uniform lowp float u_opacity;uniform vec2 u_fill_translate;uniform vec3 u_lightcolor;uniform lowp vec3 u_lightpos;uniform lowp vec3 u_lightpos_globe;uniform lowp float u_lightintensity;layout(location=0) in vec2 a_pos;layout(location=1) in ivec4 a_normal_ed;\n#ifdef TERRAIN3D\nlayout(location=2) in vec2 a_centroid;\n#endif\n#ifdef GLOBE\nout vec3 v_sphere_pos;\n#endif\nout vec2 v_pos_a;out vec2 v_pos_b;out vec4 v_lighting;\n#pragma maplibre: define lowp float base\n#pragma maplibre: define lowp float height\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\n#pragma maplibre: define lowp float pixel_ratio_from\n#pragma maplibre: define lowp float pixel_ratio_to\nvoid main() {\n#pragma maplibre: initialize lowp float base\n#pragma maplibre: initialize lowp float height\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\n#pragma maplibre: initialize lowp float pixel_ratio_from\n#pragma maplibre: initialize lowp float pixel_ratio_to\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;float tileRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;vec3 normal=vec3(a_normal_ed.xyz);float edgedistance=float(a_normal_ed.w);vec2 display_size_a=(pattern_br_a-pattern_tl_a)/pixel_ratio_from;vec2 display_size_b=(pattern_br_b-pattern_tl_b)/pixel_ratio_to;\n#ifdef TERRAIN3D\nfloat height_terrain3d_offset=get_elevation(a_centroid);float base_terrain3d_offset=height_terrain3d_offset-(base > 0.0 ? 0.0 : 10.0);\n#else\nfloat height_terrain3d_offset=0.0;float base_terrain3d_offset=0.0;\n#endif\nbase=max(0.0,base)+base_terrain3d_offset;height=max(0.0,height)+height_terrain3d_offset;float t=float(a_normal_ed.x & 1);float elevation=t > 0.0 ? height : base;vec2 posInTile=a_pos+u_fill_translate;\n#ifdef GLOBE\nvec3 spherePos=projectToSphere(posInTile,a_pos);vec3 elevatedPos=spherePos*(1.0+elevation/GLOBE_RADIUS);v_sphere_pos=elevatedPos;gl_Position=interpolateProjectionFor3D(posInTile,spherePos,elevation);\n#else\ngl_Position=u_projection_matrix*vec4(posInTile,elevation,1.0);\n#endif\nvec2 pos=a_normal_ed.x==1 && a_normal_ed.y==0 && a_normal_ed.z==16384\n? a_pos\n: vec2(edgedistance,elevation*u_height_factor);v_pos_a=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,fromScale*display_size_a,tileRatio,pos);v_pos_b=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,toScale*display_size_b,tileRatio,pos);v_lighting=vec4(0.0,0.0,0.0,1.0);float directional=clamp(dot(normal/16383.0,u_lightpos),0.0,1.0);directional=mix((1.0-u_lightintensity),max((0.5+u_lightintensity),1.0),directional);if (normal.y !=0.0) {directional*=((1.0-u_vertical_gradient)+(u_vertical_gradient*clamp((t+base)*pow(height/150.0,0.5),mix(0.7,0.98,1.0-u_lightintensity),1.0)));}v_lighting.rgb+=clamp(directional*u_lightcolor,mix(vec3(0.0),vec3(0.3),1.0-u_lightcolor),vec3(1.0));v_lighting*=u_opacity;}"),
8471
- hillshadePrepare: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nuniform sampler2D u_image;in vec2 v_pos;uniform vec2 u_dimension;uniform float u_zoom;uniform vec4 u_unpack;float getElevation(vec2 coord,float bias) {vec4 data=texture(u_image,coord)*255.0;data.a=-1.0;return dot(data,u_unpack);}void main() {vec2 epsilon=1.0/u_dimension;float tileSize=u_dimension.x-2.0;float a=getElevation(v_pos+vec2(-epsilon.x,-epsilon.y),0.0);float b=getElevation(v_pos+vec2(0,-epsilon.y),0.0);float c=getElevation(v_pos+vec2(epsilon.x,-epsilon.y),0.0);float d=getElevation(v_pos+vec2(-epsilon.x,0),0.0);float e=getElevation(v_pos,0.0);float f=getElevation(v_pos+vec2(epsilon.x,0),0.0);float g=getElevation(v_pos+vec2(-epsilon.x,epsilon.y),0.0);float h=getElevation(v_pos+vec2(0,epsilon.y),0.0);float i=getElevation(v_pos+vec2(epsilon.x,epsilon.y),0.0);float exaggerationFactor=u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3;float exaggeration=u_zoom < 15.0 ? (u_zoom-15.0)*exaggerationFactor : 0.0;vec2 deriv=vec2((c+f+f+i)-(a+d+d+g),(g+h+h+i)-(a+b+b+c))*tileSize/pow(2.0,exaggeration+(28.2562-u_zoom));fragColor=clamp(vec4(deriv.x/8.0+0.5,deriv.y/8.0+0.5,1.0,1.0),0.0,1.0);\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform mat4 u_matrix;uniform vec2 u_dimension;layout(location=0) in vec2 a_pos;layout(location=1) in vec2 a_texture_pos;out vec2 v_pos;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);highp vec2 epsilon=1.0/u_dimension;float scale=(u_dimension.x-2.0)/u_dimension.x;v_pos=(a_texture_pos/8192.0)*scale+epsilon;}"),
8599
+ hillshadePrepare: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nuniform sampler2D u_image;in vec2 v_pos;uniform vec2 u_dimension;uniform float u_zoom;uniform vec4 u_unpack;float getElevation(ivec2 texel) {vec4 data=texelFetch(u_image,texel,0)*255.0;data.a=-1.0;return dot(data,u_unpack);}void main() {ivec2 pos=ivec2(gl_FragCoord.xy)+ivec2(1);float tileSize=u_dimension.x-2.0;float a=getElevation(pos+ivec2(-1,-1));float b=getElevation(pos+ivec2(0,-1));float c=getElevation(pos+ivec2(1,-1));float d=getElevation(pos+ivec2(-1,0));float e=getElevation(pos);float f=getElevation(pos+ivec2(1,0));float g=getElevation(pos+ivec2(-1,1));float h=getElevation(pos+ivec2(0,1));float i=getElevation(pos+ivec2(1,1));float exaggerationFactor=u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3;float exaggeration=u_zoom < 15.0 ? (u_zoom-15.0)*exaggerationFactor : 0.0;vec2 deriv=vec2((c+f+f+i)-(a+d+d+g),(g+h+h+i)-(a+b+b+c))*tileSize/pow(2.0,exaggeration+(28.2562-u_zoom));fragColor=clamp(vec4(deriv.x/8.0+0.5,deriv.y/8.0+0.5,1.0,1.0),0.0,1.0);\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform mat4 u_matrix;uniform vec2 u_dimension;layout(location=0) in vec2 a_pos;layout(location=1) in vec2 a_texture_pos;out vec2 v_pos;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);highp vec2 epsilon=1.0/u_dimension;float scale=(u_dimension.x-2.0)/u_dimension.x;v_pos=(a_texture_pos/8192.0)*scale+epsilon;}"),
8472
8600
  hillshade: prepare("uniform sampler2D u_image;in vec2 v_pos;uniform vec2 u_latrange;uniform float u_exaggeration;uniform vec4 u_accent;uniform int u_method;uniform float u_altitudes[NUM_ILLUMINATION_SOURCES];uniform float u_azimuths[NUM_ILLUMINATION_SOURCES];uniform vec4 u_shadows[NUM_ILLUMINATION_SOURCES];uniform vec4 u_highlights[NUM_ILLUMINATION_SOURCES];\n#define PI 3.141592653589793\n#define STANDARD 0\n#define COMBINED 1\n#define IGOR 2\n#define MULTIDIRECTIONAL 3\n#define BASIC 4\nfloat get_aspect(vec2 deriv){return deriv.x !=0.0 ? atan(deriv.y,-deriv.x) : PI/2.0*(deriv.y > 0.0 ? 1.0 :-1.0);}void igor_hillshade(vec2 deriv){deriv=deriv*u_exaggeration*2.0;float aspect=get_aspect(deriv);float azimuth=u_azimuths[0]+PI;float slope_stength=atan(length(deriv))*2.0/PI;float aspect_strength=1.0-abs(mod((aspect+azimuth)/PI+0.5,2.0)-1.0);float shadow_strength=slope_stength*aspect_strength;float highlight_strength=slope_stength*(1.0-aspect_strength);fragColor=u_shadows[0]*shadow_strength+u_highlights[0]*highlight_strength;}void standard_hillshade(vec2 deriv){float azimuth=u_azimuths[0]+PI;float slope=atan(0.625*length(deriv));float aspect=get_aspect(deriv);float intensity=u_exaggeration;float base=1.875-intensity*1.75;float maxValue=0.5*PI;float scaledSlope=intensity !=0.5 ? ((pow(base,slope)-1.0)/(pow(base,maxValue)-1.0))*maxValue : slope;float accent=cos(scaledSlope);vec4 accent_color=(1.0-accent)*u_accent*clamp(intensity*2.0,0.0,1.0);float shade=abs(mod((aspect+azimuth)/PI+0.5,2.0)-1.0);vec4 shade_color=mix(u_shadows[0],u_highlights[0],shade)*sin(scaledSlope)*clamp(intensity*2.0,0.0,1.0);fragColor=accent_color*(1.0-shade_color.a)+shade_color;}void basic_hillshade(vec2 deriv){deriv=deriv*u_exaggeration*2.0;float azimuth=u_azimuths[0]+PI;float cos_az=cos(azimuth);float sin_az=sin(azimuth);float cos_alt=cos(u_altitudes[0]);float sin_alt=sin(u_altitudes[0]);float cang=(sin_alt-(deriv.y*cos_az*cos_alt-deriv.x*sin_az*cos_alt))/sqrt(1.0+dot(deriv,deriv));float shade=clamp(cang,0.0,1.0);if(shade > 0.5){fragColor=u_highlights[0]*(2.0*shade-1.0);}else\n{fragColor=u_shadows[0]*(1.0-2.0*shade);}}void multidirectional_hillshade(vec2 deriv){deriv=deriv*u_exaggeration*2.0;fragColor=vec4(0,0,0,0);for(int i=0; i < NUM_ILLUMINATION_SOURCES; i++){float cos_alt=cos(u_altitudes[i]);float sin_alt=sin(u_altitudes[i]);float cos_az=-cos(u_azimuths[i]);float sin_az=-sin(u_azimuths[i]);float cang=(sin_alt-(deriv.y*cos_az*cos_alt-deriv.x*sin_az*cos_alt))/sqrt(1.0+dot(deriv,deriv));float shade=clamp(cang,0.0,1.0);if(shade > 0.5){fragColor+=u_highlights[i]*(2.0*shade-1.0)/float(NUM_ILLUMINATION_SOURCES);}else\n{fragColor+=u_shadows[i]*(1.0-2.0*shade)/float(NUM_ILLUMINATION_SOURCES);}}}void combined_hillshade(vec2 deriv){deriv=deriv*u_exaggeration*2.0;float azimuth=u_azimuths[0]+PI;float cos_az=cos(azimuth);float sin_az=sin(azimuth);float cos_alt=cos(u_altitudes[0]);float sin_alt=sin(u_altitudes[0]);float cang=acos((sin_alt-(deriv.y*cos_az*cos_alt-deriv.x*sin_az*cos_alt))/sqrt(1.0+dot(deriv,deriv)));cang=clamp(cang,0.0,PI/2.0);float shade=cang*atan(length(deriv))*4.0/PI/PI;float highlight=(PI/2.0-cang)*atan(length(deriv))*4.0/PI/PI;fragColor=u_shadows[0]*shade+u_highlights[0]*highlight;}void main() {vec4 pixel=texture(u_image,v_pos);float scaleFactor=cos(radians((u_latrange[0]-u_latrange[1])*(1.0-v_pos.y)+u_latrange[1]));vec2 deriv=((pixel.rg*8.0)-4.0)/scaleFactor;if (u_method==BASIC) {basic_hillshade(deriv);} else if (u_method==COMBINED) {combined_hillshade(deriv);} else if (u_method==IGOR) {igor_hillshade(deriv);} else if (u_method==MULTIDIRECTIONAL) {multidirectional_hillshade(deriv);} else if (u_method==STANDARD) {standard_hillshade(deriv);} else {standard_hillshade(deriv);}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform mat4 u_matrix;layout(location=0) in vec2 a_pos;out vec2 v_pos;void main() {gl_Position=projectTile(a_pos,a_pos);v_pos=a_pos/8192.0;if (a_pos.y <-32767.5) {v_pos.y=0.0;}if (a_pos.y > 32766.5) {v_pos.y=1.0;}}"),
8473
8601
  line: prepare("uniform lowp float u_device_pixel_ratio;flat in vec2 v_width2;in vec2 v_normal;in float v_gamma_scale;\n#ifdef GLOBE\nin float v_depth;\n#endif\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\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);fragColor=color*(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\nlayout(location=0) in ivec2 a_pos_normal;layout(location=1) in uvec4 a_data;uniform vec2 u_translation;uniform mediump float u_ratio;uniform vec2 u_units_to_pixels;uniform lowp float u_device_pixel_ratio;out vec2 v_normal;flat out vec2 v_width2;out float v_gamma_scale;out highp float v_linesofar;\n#ifdef GLOBE\nout float v_depth;\n#endif\n#pragma maplibre: define highp vec4 color\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\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\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\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);v_linesofar=float((a_data.z >> 2u)+a_data.w*64u)*2.0;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\nv_width2=vec2(outset,inset);}"),
8474
8602
  lineGradient: prepare("uniform lowp float u_device_pixel_ratio;uniform sampler2D u_image;flat in vec2 v_width2;in vec2 v_normal;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\nvoid main() {\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\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);fragColor=color*(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\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;out vec2 v_normal;flat out vec2 v_width2;out float v_gamma_scale;out highp vec2 v_uv;\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\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\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);highp float texel_height=1.0/u_image_height;highp 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\nv_width2=vec2(outset,inset);}"),
@@ -10816,6 +10944,145 @@ function sphereSurfacePointToCoordinates(surface) {
10816
10944
  } else return new LngLat(0, latDegrees);
10817
10945
  }
10818
10946
  /**
10947
+ * Returns the globe orientation quaternion for the given map center and bearing.
10948
+ * The inverse of {@link lngLatBearingFromOrientation}.
10949
+ */
10950
+ function orientationFromLngLatBearing(lngLat, bearing) {
10951
+ return fromEuler(createVec4f64(), -lngLat.lng, -lngLat.lat, bearing);
10952
+ }
10953
+ /**
10954
+ * Given a globe orientation quaternion, returns the corresponding map center and bearing.
10955
+ * The inverse of {@link orientationFromLngLatBearing}.
10956
+ */
10957
+ function lngLatBearingFromOrientation(q) {
10958
+ const x = q[0], y = q[1], z = q[2], w = q[3];
10959
+ return {
10960
+ lng: -Math.atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y)) * 180 / Math.PI,
10961
+ lat: -Math.asin(clamp(2 * (w * y - z * x), -1, 1)) * 180 / Math.PI,
10962
+ bearing: Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)) * 180 / Math.PI
10963
+ };
10964
+ }
10965
+ /** Kept just below a half turn so the center cannot land exactly on the far side of the globe. */
10966
+ const PAN_MAX_ANGLE = Math.PI * .98;
10967
+ /**
10968
+ * Returns the point on the globe that a drag towards `point` should aim at.
10969
+ *
10970
+ * Exact tracking breaks down at the silhouette: the ray meets the globe at `asin(D sin a) - a`,
10971
+ * whose slope runs away to infinity as the ray goes tangent, and past it there is no intersection
10972
+ * at all. So the exact curve is left {@link PAN_FALLOFF_BAND} early and continued with a hyperbola
10973
+ * matching it in value and slope, easing off and saturating short of the far side. Bell's virtual
10974
+ * trackball takes the same idea of easing a sphere into a hyperbola before the rim, though it fits
10975
+ * the curve differently: SGI's `trackball.c`, described in Henriksen, Sporring and Hornbæk,
10976
+ * "Virtual Trackballs Revisited", IEEE TVCG 10(2):206-216, 2004.
10977
+ *
10978
+ * The angle comes from atan2, so a ray pointing away from the globe is a wide angle the falloff
10979
+ * saturates rather than a case to reject.
10980
+ * @param tr - The transform being dragged.
10981
+ * @param point - The cursor position.
10982
+ */
10983
+ function panSurfaceLocation(tr, point) {
10984
+ const origin = tr.cameraPosition;
10985
+ const distance = length$1(origin);
10986
+ if (distance <= 1) return tr.screenPointToLocation(point);
10987
+ const u = createVec3f64();
10988
+ normalize(u, origin);
10989
+ const direction = tr.getRayDirectionFromPixel(point);
10990
+ const c = -dot(direction, u);
10991
+ const lateral = createVec3f64();
10992
+ scaleAndAdd(lateral, direction, u, c);
10993
+ const s = length$1(lateral);
10994
+ if (s < 1e-9) return tr.screenPointToLocation(point);
10995
+ const angle = Math.atan2(s, c);
10996
+ const handoverAngle = Math.asin(1 / distance) * .9;
10997
+ if (angle < handoverAngle) return tr.screenPointToLocation(point);
10998
+ const sinHandover = distance * Math.sin(handoverAngle);
10999
+ const targetAtHandover = Math.asin(clamp(sinHandover, -1, 1)) - handoverAngle;
11000
+ const slopeAtHandover = distance * Math.cos(handoverAngle) / Math.sqrt(Math.max(1 - sinHandover * sinHandover, 1e-12)) - 1;
11001
+ const room = PAN_MAX_ANGLE - targetAtHandover;
11002
+ const excess = angle - handoverAngle;
11003
+ const target = targetAtHandover + room * (slopeAtHandover * excess) / (room + slopeAtHandover * excess);
11004
+ const e = createVec3f64();
11005
+ scale$2(e, lateral, 1 / s);
11006
+ const surface = createVec3f64();
11007
+ scale$2(surface, u, Math.cos(clamp(target, 0, PAN_MAX_ANGLE)));
11008
+ scaleAndAdd(surface, surface, e, Math.sin(clamp(target, 0, PAN_MAX_ANGLE)));
11009
+ normalize(surface, surface);
11010
+ return sphereSurfacePointToCoordinates(surface);
11011
+ }
11012
+ /**
11013
+ * Rotates the globe so that the given location appears at the given screen point, by composing
11014
+ * versors, the unit quaternions that represent rotations. Unlike the bearing-preserving
11015
+ * {@link ITransform.setLocationAtPoint}, this stays smooth near and across the poles, and keeps
11016
+ * panning once the cursor leaves the globe.
11017
+ *
11018
+ * Note: the delta rotation's axis is in the surface-vector frame of
11019
+ * {@link angularCoordinatesToSurfaceVector}, while the orientation quaternion uses the Euler frame
11020
+ * of {@link orientationFromLngLatBearing}, hence the component swizzle where they combine. Zoom is
11021
+ * adjusted to keep the planet the same size, as `setLocationAtPoint` does.
11022
+ * @param tr - The transform to rotate.
11023
+ * @param lnglat - The location to bring under `point`.
11024
+ * @param point - The screen point that `lnglat` should appear at.
11025
+ * @param panDelta - The drag's pixel delta. Used to re-derive the previous cursor location through
11026
+ * {@link panSurfaceLocation}, since both ends of the rotation must come from the same mapping.
11027
+ * @param fixedBearing - Applies the swing only, keeping the bearing fixed, which is what dragging
11028
+ * the globe does. Pass `false` to apply the twist about the view axis as well, so that the grabbed
11029
+ * location tracks the cursor exactly and the bearing drifts with it.
11030
+ */
11031
+ function versorSetLocationAtPoint(tr, lnglat, point, panDelta, fixedBearing = true) {
11032
+ const pointLngLat = panSurfaceLocation(tr, point);
11033
+ let sourceLngLat = lnglat;
11034
+ if (panDelta) sourceLngLat = panSurfaceLocation(tr, point.sub(panDelta));
11035
+ else if (!tr.isPointOnMapSurface(point)) return;
11036
+ const vecToPixelCurrent = angularCoordinatesToSurfaceVector(pointLngLat);
11037
+ const vecToTarget = angularCoordinatesToSurfaceVector(sourceLngLat);
11038
+ const centerQuat = orientationFromLngLatBearing(tr.center, tr.bearing);
11039
+ const w = cross(createVec3f64(), vecToTarget, vecToPixelCurrent);
11040
+ const l = Math.sqrt(dot(w, w));
11041
+ const t = Math.acos(clamp(dot(vecToTarget, vecToPixelCurrent), -1, 1)) / 2;
11042
+ const s = Math.sin(t);
11043
+ const delta = l ? fromValues(w[1] / l * s, -w[0] / l * s, w[2] / l * s, Math.cos(t)) : fromValues(0, 0, 0, 1);
11044
+ const { lng: newCenterLng, lat: newCenterLat, bearing: newBearing } = lngLatBearingFromOrientation(multiply$1(createVec4f64(), centerQuat, delta));
11045
+ const oldLat = tr.center.lat;
11046
+ const finalLat = clamp(newCenterLat, -90, 90);
11047
+ const finalLng = fixedBearing ? fixedBearingLongitude(tr, point, panDelta, newCenterLng) : newCenterLng;
11048
+ tr.setCenter(new LngLat(wrap(finalLng, -180, 180), finalLat));
11049
+ if (!fixedBearing) tr.setBearing(newBearing);
11050
+ tr.setZoom(tr.zoom + getZoomAdjustment(oldLat, tr.center.lat));
11051
+ }
11052
+ /**
11053
+ * Returns the center longitude for a bearing-preserving drag.
11054
+ *
11055
+ * The swing longitude becomes ill-conditioned near the pole and the grabbed location slips away
11056
+ * from the cursor, so within the last ~12 degrees of latitude the cursor is treated as turning a
11057
+ * dial around the pole, blended in with a smoothstep anchored on {@link MAX_VALID_LATITUDE}, the
11058
+ * highest latitude the center can reach. The sweep comes from the raw pixel delta rather than a
11059
+ * round-tripped previous cursor position, which would lose its sign to cancellation at the pole,
11060
+ * and is damped within {@link DIAL_MIN_RADIUS_PIXELS} so it eases to nothing there instead of
11061
+ * being dropped, which would leave a spot where the drag could not move at all.
11062
+ * @param tr - The transform being dragged.
11063
+ * @param point - The cursor position.
11064
+ * @param panDelta - The drag's pixel delta, or undefined to use the swing longitude only.
11065
+ * @param newCenterLng - The swing target longitude.
11066
+ * @returns the center longitude to apply.
11067
+ */
11068
+ function fixedBearingLongitude(tr, point, panDelta, newCenterLng) {
11069
+ const oldLng = tr.center.lng;
11070
+ const oldLat = tr.center.lat;
11071
+ const poleLat = oldLat >= 0 ? 90 : -90;
11072
+ const polePoint = tr.locationToScreenPoint(new LngLat(0, poleLat));
11073
+ const rx = point.x - polePoint.x, ry = point.y - polePoint.y;
11074
+ const r2 = rx * rx + ry * ry;
11075
+ const tRamp = clamp(1 - (MAX_VALID_LATITUDE - Math.abs(oldLat)) / 12, 0, 1);
11076
+ const dial = tRamp * tRamp * (3 - 2 * tRamp);
11077
+ const dLngSwing = mod(newCenterLng - oldLng + 180, 360) - 180;
11078
+ let dLngDial = 0;
11079
+ if (dial > 0 && panDelta) {
11080
+ const dTheta = (rx * panDelta.y - ry * panDelta.x) / Math.max(r2, 400);
11081
+ dLngDial = (poleLat > 0 ? 1 : -1) * dTheta * 180 / Math.PI;
11082
+ }
11083
+ return oldLng + (1 - dial) * dLngSwing + dial * dLngDial;
11084
+ }
11085
+ /**
10819
11086
  * Given a normalized horizon plane in Ax+By+Cz+D=0 format, compute the center and radius of
10820
11087
  * the circle in that plain that contains the entire visible portion of the unit sphere from horizon
10821
11088
  * to horizon.
@@ -12578,12 +12845,17 @@ var VerticalPerspectiveCameraHelper = class VerticalPerspectiveCameraHelper {
12578
12845
  tr.setCenter(new LngLat(exactCenter.lng + lngExactToHeuristic * heuristicFactor, exactCenter.lat + latExactToHeuristic * heuristicFactor).wrap());
12579
12846
  tr.setZoom(oldZoom + getZoomAdjustment(oldCenterLat, tr.center.lat));
12580
12847
  }
12581
- handleMapControlsPan(deltas, tr, _preZoomAroundLoc) {
12848
+ /**
12849
+ * Pans the globe by rotating it with a single quaternion that brings the grabbed location back
12850
+ * under the cursor. This stays consistent near and across the poles, where the previous
12851
+ * bearing-preserving mapping inverted and stalled (#5296).
12852
+ * @param deltas - The deltas accumulated for this frame.
12853
+ * @param tr - The transform to pan.
12854
+ * @param preZoomAroundLoc - The location that was under the cursor before this frame.
12855
+ */
12856
+ handleMapControlsPan(deltas, tr, preZoomAroundLoc) {
12582
12857
  if (!deltas.panDelta) return;
12583
- const oldLat = tr.center.lat;
12584
- const oldZoom = tr.zoom;
12585
- tr.setCenter(computeGlobePanCenter(deltas.panDelta, tr).wrap());
12586
- tr.setZoom(oldZoom + getZoomAdjustment(oldLat, tr.center.lat));
12858
+ versorSetLocationAtPoint(tr, preZoomAroundLoc, deltas.around, deltas.panDelta);
12587
12859
  }
12588
12860
  cameraForBoxAndBearing(options, padding, bounds, bearing, tr) {
12589
12861
  const result = cameraForBoxAndBearing(options, padding, bounds, bearing, tr);
@@ -12916,6 +13188,7 @@ var Style = class extends Evented {
12916
13188
  this.imageManager = new ImageManager();
12917
13189
  this.imageManager.setEventedParent(this);
12918
13190
  this.imageManager.setMissingImageResolver(map._missingStyleImageResolver);
13191
+ this.patternAtlas = new PatternAtlas(this.imageManager);
12919
13192
  const glyphLang = map._container?.lang || typeof document !== "undefined" && document.documentElement?.lang || void 0;
12920
13193
  this.glyphManager = new GlyphManager(map._requestManager, options.localIdeographFontFamily, glyphLang);
12921
13194
  this.lineAtlas = new LineAtlas(256, 512);
@@ -12937,12 +13210,10 @@ var Style = class extends Evented {
12937
13210
  });
12938
13211
  }
12939
13212
  _setInitialValues() {
12940
- this._spritesImagesIds = {};
12941
13213
  this._layers = {};
12942
13214
  this._order = [];
12943
13215
  this.tileManagers = {};
12944
13216
  this.zoomHistory = new ZoomHistory();
12945
- this._availableImages = [];
12946
13217
  this._imagesListDirty = false;
12947
13218
  this._globalState = {};
12948
13219
  this._serializedLayers = {};
@@ -13080,48 +13351,33 @@ var Style = class extends Evented {
13080
13351
  }
13081
13352
  }
13082
13353
  }
13083
- _loadSprite(sprite, isUpdate = false, completion = void 0) {
13354
+ async _loadSprite(sprite, isUpdate = false, completion = void 0) {
13084
13355
  this.imageManager.setLoaded(false);
13085
13356
  const abortController = new AbortController();
13086
13357
  this._spriteRequest = abortController;
13087
13358
  let err;
13088
- loadSprite(sprite, this.map._requestManager, this.map.getPixelRatio(), this._spriteRequest).then((images) => {
13089
- this._spriteRequest = null;
13090
- if (images) for (const spriteId in images) {
13091
- this._spritesImagesIds[spriteId] = [];
13092
- const imagesToRemove = this._spritesImagesIds[spriteId] ? this._spritesImagesIds[spriteId].filter((id) => !(id in images)) : [];
13093
- for (const id of imagesToRemove) {
13094
- this.imageManager.removeImage(id);
13095
- this._changedImages[id] = true;
13096
- }
13097
- for (const id in images[spriteId]) {
13098
- const imageId = spriteId === "default" ? id : `${spriteId}:${id}`;
13099
- this._spritesImagesIds[spriteId].push(imageId);
13100
- if (imageId in this.imageManager.images) this.imageManager.updateImage(imageId, images[spriteId][id], false);
13101
- else this.imageManager.addImage(imageId, images[spriteId][id]);
13102
- if (isUpdate) this._changedImages[imageId] = true;
13103
- }
13359
+ try {
13360
+ const images = await loadSprite(sprite, this.map._requestManager, this.map.getPixelRatio(), abortController);
13361
+ if (!images) return;
13362
+ for (const spriteId in images) {
13363
+ const { loaded, removed } = this.imageManager.setSpriteImages(spriteId, images[spriteId]);
13364
+ this._markImagesChanged(removed);
13365
+ if (isUpdate) this._markImagesChanged(loaded);
13104
13366
  }
13105
- }).catch((error) => {
13106
- this._spriteRequest = null;
13367
+ } catch (error) {
13107
13368
  err = error;
13108
13369
  if (!abortController.signal.aborted) this.fire(new ErrorEvent(err));
13109
- }).finally(() => {
13370
+ } finally {
13371
+ this._spriteRequest = null;
13110
13372
  this.imageManager.setLoaded(true);
13111
- this._availableImages = this.imageManager.listImages();
13112
13373
  if (isUpdate) this._changed = true;
13113
- this.dispatcher.broadcast("SI", this._availableImages);
13374
+ this.dispatcher.broadcast("SI", this.imageManager.listImages());
13114
13375
  this.fire(new MapStyleDataEvent("data"));
13115
- if (completion) completion(err);
13116
- });
13376
+ completion?.(err);
13377
+ }
13117
13378
  }
13118
13379
  _unloadSprite() {
13119
- for (const id of Object.values(this._spritesImagesIds).flat()) {
13120
- this.imageManager.removeImage(id);
13121
- this._changedImages[id] = true;
13122
- }
13123
- this._spritesImagesIds = {};
13124
- this._availableImages = this.imageManager.listImages();
13380
+ this._markImagesChanged(this.imageManager.removeAllSpriteImages());
13125
13381
  this._imagesListDirty = true;
13126
13382
  this._changed = true;
13127
13383
  this.fire(new MapStyleDataEvent("data"));
@@ -13193,7 +13449,7 @@ var Style = class extends Evented {
13193
13449
  const changed = this._changed;
13194
13450
  if (changed) {
13195
13451
  if (this._imagesListDirty) {
13196
- this.dispatcher.broadcast("SI", this._availableImages);
13452
+ this.dispatcher.broadcast("SI", this.imageManager.listImages());
13197
13453
  this._imagesListDirty = false;
13198
13454
  }
13199
13455
  const updatedIds = Object.keys(this._updatedLayers);
@@ -13218,9 +13474,10 @@ var Style = class extends Evented {
13218
13474
  managersUsedBefore[id] = tileManager.used;
13219
13475
  tileManager.used = false;
13220
13476
  }
13477
+ const availableImages = this.imageManager.listImages();
13221
13478
  for (const layerId of this._order) {
13222
13479
  const layer = this._layers[layerId];
13223
- layer.recalculate(parameters, this._availableImages);
13480
+ layer.recalculate(parameters, availableImages);
13224
13481
  if (!layer.isHidden(parameters.zoom) && layer.source) this.tileManagers[layer.source].used = true;
13225
13482
  }
13226
13483
  for (const id in managersUsedBefore) {
@@ -13382,8 +13639,14 @@ var Style = class extends Evented {
13382
13639
  this.imageManager.removeImage(id);
13383
13640
  this._afterImageUpdated(id);
13384
13641
  }
13642
+ /**
13643
+ * Queues the tiles that depend on these images to be reloaded on the next update, which is
13644
+ * needed whenever an image appears, disappears or changes size.
13645
+ */
13646
+ _markImagesChanged(ids) {
13647
+ for (const id of ids) this._changedImages[id] = true;
13648
+ }
13385
13649
  _afterImageUpdated(id) {
13386
- this._availableImages = this.imageManager.listImages();
13387
13650
  this._changedImages[id] = true;
13388
13651
  this._imagesListDirty = true;
13389
13652
  this._changed = true;
@@ -13816,7 +14079,7 @@ var Style = class extends Evented {
13816
14079
  }
13817
14080
  }
13818
14081
  const sourceResults = [];
13819
- params.availableImages = this._availableImages;
14082
+ params.availableImages = this.imageManager.listImages();
13820
14083
  const serializedLayers = this._serializedAllLayers();
13821
14084
  const layersAsSet = params.layers instanceof Set ? params.layers : Array.isArray(params.layers) ? new Set(params.layers) : null;
13822
14085
  const paramsStrict = {
@@ -14053,14 +14316,10 @@ var Style = class extends Evented {
14053
14316
  this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Sprite "${id}" doesn't exists on this map.`)));
14054
14317
  return;
14055
14318
  }
14056
- if (this._spritesImagesIds[id]) for (const imageId of this._spritesImagesIds[id]) {
14057
- this.imageManager.removeImage(imageId);
14058
- this._changedImages[imageId] = true;
14059
- }
14319
+ const changedImages = this.imageManager.removeSpriteImages(id);
14320
+ this._markImagesChanged(changedImages);
14060
14321
  internalSpriteRepresentation.splice(internalSpriteRepresentation.findIndex((sprite) => sprite.id === id), 1);
14061
14322
  this.stylesheet.sprite = internalSpriteRepresentation.length > 0 ? internalSpriteRepresentation : void 0;
14062
- delete this._spritesImagesIds[id];
14063
- this._availableImages = this.imageManager.listImages();
14064
14323
  this._imagesListDirty = true;
14065
14324
  this._changed = true;
14066
14325
  this.fire(new MapStyleDataEvent("data"));
@@ -14115,8 +14374,7 @@ var Style = class extends Evented {
14115
14374
  if (this.imageManager) {
14116
14375
  this.imageManager.setEventedParent(null);
14117
14376
  this.imageManager.destroy();
14118
- this._availableImages = [];
14119
- this._spritesImagesIds = {};
14377
+ this.patternAtlas.destroy();
14120
14378
  }
14121
14379
  if (this.glyphManager) this.glyphManager.destroy();
14122
14380
  for (const layerId in this._layers) {
@@ -14447,9 +14705,9 @@ function patternUniformValues(crossfade, painter, tile) {
14447
14705
  };
14448
14706
  }
14449
14707
  function bgPatternUniformValues(image, crossfade, painter, tile) {
14450
- const imagePosA = painter.imageManager.getPattern(image.from.toString());
14451
- const imagePosB = painter.imageManager.getPattern(image.to.toString());
14452
- const { width, height } = painter.imageManager.getPixelSize();
14708
+ const imagePosA = painter.patternAtlas.getPattern(image.from.toString());
14709
+ const imagePosB = painter.patternAtlas.getPattern(image.to.toString());
14710
+ const { width, height } = painter.patternAtlas.getPixelSize();
14453
14711
  const numTiles = Math.pow(2, tile.tileID.overscaledZ);
14454
14712
  const tileSizeAtNearestZoom = tile.tileSize * Math.pow(2, painter.transform.tileZoom) / numTiles;
14455
14713
  const pixelX = tileSizeAtNearestZoom * (tile.tileID.canonical.x + tile.tileID.wrap * numTiles);
@@ -17252,7 +17510,7 @@ function drawBackground(painter, tileManager, layer, coords, renderOptions) {
17252
17510
  });
17253
17511
  if (image) {
17254
17512
  context.activeTexture.set(gl.TEXTURE0);
17255
- painter.imageManager.bind(painter.context);
17513
+ painter.patternAtlas.bind(painter.context);
17256
17514
  }
17257
17515
  const crossfade = layer.getCrossfadeParameters();
17258
17516
  for (const tileID of tileIDs) {
@@ -17933,6 +18191,7 @@ var Painter = class Painter {
17933
18191
  this.options = options;
17934
18192
  this.lineAtlas = style.lineAtlas;
17935
18193
  this.imageManager = style.imageManager;
18194
+ this.patternAtlas = style.patternAtlas;
17936
18195
  this.glyphManager = style.glyphManager;
17937
18196
  this.symbolFadeChange = style.placement.symbolFadeChange(now());
17938
18197
  this.imageManager.beginFrame();
@@ -18140,8 +18399,8 @@ var Painter = class Painter {
18140
18399
  isPatternMissing(image) {
18141
18400
  if (!image) return false;
18142
18401
  if (!image.from || !image.to) return true;
18143
- const imagePosA = this.imageManager.getPattern(image.from.toString());
18144
- const imagePosB = this.imageManager.getPattern(image.to.toString());
18402
+ const imagePosA = this.patternAtlas.getPattern(image.from.toString());
18403
+ const imagePosB = this.patternAtlas.getPattern(image.to.toString());
18145
18404
  return !imagePosA || !imagePosB;
18146
18405
  }
18147
18406
  /**
@@ -18402,6 +18661,14 @@ const defaultRollInertiaOptions = extend({
18402
18661
  deceleration: 1e3,
18403
18662
  maxSpeed: 360
18404
18663
  }, defaultInertiaOptions);
18664
+ /**
18665
+ * The maximum age of a recorded gesture update, in milliseconds.
18666
+ */
18667
+ const BUFFER_CUTOFF = 160;
18668
+ /**
18669
+ * The time window the gesture velocity is measured over, in milliseconds.
18670
+ */
18671
+ const VELOCITY_WINDOW = 60;
18405
18672
  var HandlerInertia = class {
18406
18673
  constructor(map) {
18407
18674
  this._map = map;
@@ -18418,12 +18685,41 @@ var HandlerInertia = class {
18418
18685
  });
18419
18686
  }
18420
18687
  _drainInertiaBuffer() {
18421
- const inertia = this._inertiaBuffer, currentTime = now(), cutoff = 160;
18422
- while (inertia.length > 0 && currentTime - inertia[0].time > cutoff) inertia.shift();
18688
+ const inertia = this._inertiaBuffer, currentTime = now();
18689
+ while (inertia.length > 0 && currentTime - inertia[0].time > BUFFER_CUTOFF) inertia.shift();
18690
+ }
18691
+ /**
18692
+ * Returns the entries the velocity is measured from: everything recorded within
18693
+ * {@link VELOCITY_WINDOW} before now, but never fewer than the last two, so that
18694
+ * inertia also works below two recorded updates per window.
18695
+ */
18696
+ _getVelocityEntries() {
18697
+ const inertia = this._inertiaBuffer;
18698
+ const windowStart = now() - VELOCITY_WINDOW;
18699
+ let firstIndex = Math.max(0, inertia.length - 2);
18700
+ while (firstIndex > 0 && inertia[firstIndex - 1].time >= windowStart) firstIndex--;
18701
+ return inertia.slice(firstIndex);
18423
18702
  }
18703
+ /**
18704
+ * Returns the ease which continues the gesture that has just ended.
18705
+ *
18706
+ * The velocity is measured over the time up to the release and not up to the last
18707
+ * recorded movement, so that a gesture held still before being released has a lower
18708
+ * velocity, down to no inertia at all. The delta of an entry happened before it was
18709
+ * recorded, so the first entry only marks the start of the measured interval; anchors
18710
+ * are the exception, as they describe the state at the moment of their entry.
18711
+ *
18712
+ * @param panInertiaOptions - overrides for the pan inertia defaults
18713
+ * @returns the ease options, or `undefined` when the measured interval holds no
18714
+ * movement, which terrain gestures produce by recording updates without any delta
18715
+ */
18424
18716
  _onMoveEnd(panInertiaOptions) {
18425
18717
  this._drainInertiaBuffer();
18426
- if (this._inertiaBuffer.length < 2) return;
18718
+ const entries = this._getVelocityEntries();
18719
+ if (entries.length < 2) {
18720
+ this.clear();
18721
+ return;
18722
+ }
18427
18723
  const deltas = {
18428
18724
  zoom: 0,
18429
18725
  bearing: 0,
@@ -18433,16 +18729,22 @@ var HandlerInertia = class {
18433
18729
  pinchAround: void 0,
18434
18730
  around: void 0
18435
18731
  };
18436
- for (const { settings } of this._inertiaBuffer) {
18732
+ for (const { settings } of entries) {
18733
+ if (settings.around) deltas.around = settings.around;
18734
+ if (settings.pinchAround) deltas.pinchAround = settings.pinchAround;
18735
+ }
18736
+ for (const { settings } of entries.slice(1)) {
18437
18737
  deltas.zoom += settings.zoomDelta || 0;
18438
18738
  deltas.bearing += settings.bearingDelta || 0;
18439
18739
  deltas.pitch += settings.pitchDelta || 0;
18440
18740
  deltas.roll += settings.rollDelta || 0;
18441
18741
  if (settings.panDelta) deltas.pan._add(settings.panDelta);
18442
- if (settings.around) deltas.around = settings.around;
18443
- if (settings.pinchAround) deltas.pinchAround = settings.pinchAround;
18444
18742
  }
18445
- const duration = this._inertiaBuffer[this._inertiaBuffer.length - 1].time - this._inertiaBuffer[0].time;
18743
+ if (!deltas.pan.mag() && !deltas.zoom && !deltas.bearing && !deltas.pitch && !deltas.roll) {
18744
+ this.clear();
18745
+ return;
18746
+ }
18747
+ const duration = now() - entries[0].time;
18446
18748
  const easeOptions = {};
18447
18749
  if (deltas.pan.mag()) {
18448
18750
  const result = calculateEasing(deltas.pan.mag(), duration, extend({}, defaultPanInertiaOptions, panInertiaOptions || {}));
@@ -26196,6 +26498,17 @@ function applyAnchorClass(element, anchor, prefix) {
26196
26498
  //#endregion
26197
26499
  //#region src/ui/marker.ts
26198
26500
  /**
26501
+ * Screen-pixel deltas applied when a focused draggable marker is moved with the arrow keys.
26502
+ */
26503
+ const ARROW_KEY_DELTAS = {
26504
+ ArrowLeft: [-1, 0],
26505
+ ArrowRight: [1, 0],
26506
+ ArrowUp: [0, -1],
26507
+ ArrowDown: [0, 1]
26508
+ };
26509
+ const KEYBOARD_DRAG_SMALL_STEP = 1;
26510
+ const KEYBOARD_DRAG_LARGE_STEP = 10;
26511
+ /**
26199
26512
  * The event class for marker drag events (`dragstart`, `drag` and `dragend`).
26200
26513
  *
26201
26514
  * @group Event Related
@@ -26270,6 +26583,30 @@ var Marker = class extends Evented {
26270
26583
  this._onKeyPress = (e) => {
26271
26584
  if (e.code === "Space" || e.code === "Enter") this.togglePopup();
26272
26585
  };
26586
+ this._onKeyDown = (e) => {
26587
+ if (!this._defaultMarker || !this._draggable || !this._map || !this._lngLat) return;
26588
+ if (e.composedPath()[0] !== this._element) return;
26589
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
26590
+ const delta = ARROW_KEY_DELTAS[e.key];
26591
+ if (!delta) return;
26592
+ e.preventDefault();
26593
+ e.stopPropagation();
26594
+ const step = e.shiftKey ? KEYBOARD_DRAG_LARGE_STEP : KEYBOARD_DRAG_SMALL_STEP;
26595
+ const pos = this._map.project(this._lngLat);
26596
+ this.setLngLat(this._map.unproject(new Point(pos.x + delta[0] * step, pos.y + delta[1] * step)));
26597
+ if (!this._keyboardDragActive) {
26598
+ this._keyboardDragActive = true;
26599
+ this.fire(new MarkerDragEvent("dragstart"));
26600
+ }
26601
+ this.fire(new MarkerDragEvent("drag"));
26602
+ };
26603
+ this._onKeyUp = (e) => {
26604
+ if (!ARROW_KEY_DELTAS[e.key]) return;
26605
+ this._endKeyboardDrag();
26606
+ };
26607
+ this._onBlur = () => {
26608
+ this._endKeyboardDrag();
26609
+ };
26273
26610
  this._onMapClick = (e) => {
26274
26611
  const targetElement = e.originalEvent.target;
26275
26612
  const element = this._element;
@@ -26340,6 +26677,8 @@ var Marker = class extends Evented {
26340
26677
  this._subpixelPositioning = options?.subpixelPositioning || false;
26341
26678
  this._isDragging = false;
26342
26679
  this._roleManaged = false;
26680
+ this._tabIndexManaged = false;
26681
+ this._keyboardDragActive = false;
26343
26682
  this._state = "inactive";
26344
26683
  this._rotation = options?.rotation || 0;
26345
26684
  this._rotationAlignment = options?.rotationAlignment || "auto";
@@ -26515,6 +26854,11 @@ var Marker = class extends Evented {
26515
26854
  delete this._map;
26516
26855
  }
26517
26856
  this._element.removeEventListener("click", this._onClick);
26857
+ this._element.removeEventListener("keydown", this._onKeyDown);
26858
+ this._element.removeEventListener("keyup", this._onKeyUp);
26859
+ this._element.removeEventListener("blur", this._onBlur);
26860
+ this._element.removeEventListener("keypress", this._onKeyPress);
26861
+ this._keyboardDragActive = false;
26518
26862
  this._element.remove();
26519
26863
  if (this._popup) this._popup.remove();
26520
26864
  return this;
@@ -26584,7 +26928,6 @@ var Marker = class extends Evented {
26584
26928
  this._popup.remove();
26585
26929
  this._popup = null;
26586
26930
  this._element.removeEventListener("keypress", this._onKeyPress);
26587
- if (!this._originalTabIndex) this._element.removeAttribute("tabindex");
26588
26931
  }
26589
26932
  if (popup) {
26590
26933
  if (!("offset" in popup.options)) {
@@ -26602,10 +26945,9 @@ var Marker = class extends Evented {
26602
26945
  } : this._offset;
26603
26946
  }
26604
26947
  this._popup = popup;
26605
- this._originalTabIndex = this._element.getAttribute("tabindex");
26606
- if (!this._originalTabIndex) this._element.setAttribute("tabindex", "0");
26607
26948
  this._element.addEventListener("keypress", this._onKeyPress);
26608
26949
  }
26950
+ this._updateTabIndex();
26609
26951
  this._updateAccessibilityRole();
26610
26952
  return this;
26611
26953
  }
@@ -26624,6 +26966,12 @@ var Marker = class extends Evented {
26624
26966
  this._subpixelPositioning = value;
26625
26967
  return this;
26626
26968
  }
26969
+ _endKeyboardDrag() {
26970
+ if (this._keyboardDragActive) {
26971
+ this._keyboardDragActive = false;
26972
+ this.fire(new MarkerDragEvent("dragend"));
26973
+ }
26974
+ }
26627
26975
  /**
26628
26976
  * Returns the {@link Popup} instance that is bound to the {@link Marker}.
26629
26977
  * @returns popup
@@ -26760,7 +27108,10 @@ var Marker = class extends Evented {
26760
27108
  return this._element.classList.toggle(className);
26761
27109
  }
26762
27110
  /**
26763
- * Sets the `draggable` property and functionality of the marker
27111
+ * Sets the `draggable` property and functionality of the marker.
27112
+ * A draggable default marker is also keyboard focusable and movable with the
27113
+ * arrow keys (see {@link MarkerOptions.draggable}); custom marker elements keep
27114
+ * their focusability and keyboard behavior application-owned.
26764
27115
  * @param shouldBeDraggable - Turns drag functionality on/off
26765
27116
  */
26766
27117
  setDraggable(shouldBeDraggable) {
@@ -26775,6 +27126,19 @@ var Marker = class extends Evented {
26775
27126
  this._map.off("touchstart", this._addDragHandler);
26776
27127
  }
26777
27128
  }
27129
+ if (this._defaultMarker) {
27130
+ if (this._draggable) {
27131
+ this._element.addEventListener("keydown", this._onKeyDown);
27132
+ this._element.addEventListener("keyup", this._onKeyUp);
27133
+ this._element.addEventListener("blur", this._onBlur);
27134
+ } else {
27135
+ this._element.removeEventListener("keydown", this._onKeyDown);
27136
+ this._element.removeEventListener("keyup", this._onKeyUp);
27137
+ this._element.removeEventListener("blur", this._onBlur);
27138
+ this._endKeyboardDrag();
27139
+ }
27140
+ }
27141
+ this._updateTabIndex();
26778
27142
  this._updateAccessibilityRole();
26779
27143
  return this;
26780
27144
  }
@@ -26786,6 +27150,24 @@ var Marker = class extends Evented {
26786
27150
  return this._draggable;
26787
27151
  }
26788
27152
  /**
27153
+ * Keep the marker element focusable while it has built-in keyboard behavior.
27154
+ * A popup makes any marker element interactive; dragging only manages
27155
+ * focusability for the default marker so custom elements stay
27156
+ * application-owned (#7790). A tabindex supplied by the application is
27157
+ * never added, changed, or removed here.
27158
+ */
27159
+ _updateTabIndex() {
27160
+ if (!!this._popup || this._defaultMarker && this._draggable) {
27161
+ if (!this._element.hasAttribute("tabindex")) {
27162
+ this._element.setAttribute("tabindex", "0");
27163
+ this._tabIndexManaged = true;
27164
+ }
27165
+ } else if (this._tabIndexManaged) {
27166
+ if (this._element.getAttribute("tabindex") === "0") this._element.removeAttribute("tabindex");
27167
+ this._tabIndexManaged = false;
27168
+ }
27169
+ }
27170
+ /**
26789
27171
  * Keep the default marker role aligned with interactivity.
26790
27172
  * Default markers need a role because `aria-label` is set in {@link Marker.addTo}.
26791
27173
  * Non-interactive markers use `role=img`; interactive ones (draggable or with a popup) use `role=button`.