diff --git a/.gitattributes b/.gitattributes index ee0cdbbe162f4577aeb6a5eb3e99f679df95bfa3..a302e4c8ab675217637d43f263405fbb3a207b2f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -821,3 +821,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text .venv/lib/python3.14/site-packages/ultralytics/nn/__pycache__/tasks.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text .venv/lib/python3.14/site-packages/ultralytics/nn/modules/__pycache__/block.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text .venv/lib/python3.14/site-packages/ultralytics/nn/modules/__pycache__/head.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text +.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/metrics.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text +.venv/lib/python3.14/site-packages/yaml/_yaml.cpython-314-darwin.so filter=lfs diff=lfs merge=lfs -text +.venv/share/jupyter/nbextensions/pydeck/index.js.map filter=lfs diff=lfs merge=lfs -text diff --git a/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/INSTALLER b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/METADATA b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..d83ff3cdbccd81e4c46298f6b72211b221ef8304 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/METADATA @@ -0,0 +1,398 @@ +Metadata-Version: 2.4 +Name: ultralytics +Version: 8.4.83 +Summary: Ultralytics YOLO 🚀 for SOTA object detection, multi-object tracking, instance segmentation, pose estimation, classification, and oriented object detection. +Author-email: Glenn Jocher , Jing Qiu +Maintainer-email: Ultralytics +License: AGPL-3.0 +Project-URL: Homepage, https://ultralytics.com +Project-URL: Source, https://github.com/ultralytics/ultralytics +Project-URL: Documentation, https://docs.ultralytics.com +Project-URL: Bug Reports, https://github.com/ultralytics/ultralytics/issues +Project-URL: Changelog, https://github.com/ultralytics/ultralytics/releases +Keywords: machine-learning,deep-learning,computer-vision,ML,DL,AI,RT-DETR,SAM3,YOLO,YOLOv3,YOLOv5,YOLOv8,YOLO11,YOLO26,Platform,Ultralytics +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Education +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Software Development +Classifier: Topic :: Scientific/Engineering +Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence +Classifier: Topic :: Scientific/Engineering :: Image Recognition +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: MacOS +Classifier: Operating System :: Microsoft :: Windows +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: numpy>=1.23.0 +Requires-Dist: matplotlib>=3.3.0 +Requires-Dist: opencv-python>=4.6.0 +Requires-Dist: pillow>=7.1.2 +Requires-Dist: pyyaml>=5.3.1 +Requires-Dist: requests>=2.23.0 +Requires-Dist: torch>=1.8.0 +Requires-Dist: torch!=2.4.0,>=1.8.0; sys_platform == "win32" +Requires-Dist: torchvision>=0.9.0 +Requires-Dist: psutil>=5.8.0 +Requires-Dist: polars>=0.20.0 +Requires-Dist: nvidia-ml-py>=12.0.0 +Requires-Dist: ultralytics-thop>=2.0.18 +Provides-Extra: dev +Requires-Dist: ipython; extra == "dev" +Requires-Dist: pytest; extra == "dev" +Requires-Dist: pytest-cov; extra == "dev" +Requires-Dist: pytest-xdist>=3.0.0; extra == "dev" +Requires-Dist: coverage[toml]; extra == "dev" +Requires-Dist: zensical>=0.0.15; python_version >= "3.10" and extra == "dev" +Requires-Dist: mkdocs-ultralytics-plugin>=0.2.4; extra == "dev" +Requires-Dist: minijinja>=2.0.0; extra == "dev" +Provides-Extra: export-base +Requires-Dist: onnx>=1.12.0; platform_system != "Darwin" and extra == "export-base" +Requires-Dist: onnx<1.18.0,>=1.12.0; (platform_system == "Darwin" and python_version < "3.13") and extra == "export-base" +Requires-Dist: onnx>=1.20.0; (platform_system == "Darwin" and python_version >= "3.13") and extra == "export-base" +Requires-Dist: onnxslim>=0.1.82; extra == "export-base" +Requires-Dist: onnxruntime<1.20.0; (python_version < "3.11" and (platform_machine != "aarch64" or platform_system != "Linux")) and extra == "export-base" +Requires-Dist: onnxruntime>=1.20.0; python_version >= "3.11" and extra == "export-base" +Requires-Dist: nncf<3.0.0,>=2.14.0; python_version >= "3.9" and extra == "export-base" +Requires-Dist: openvino>=2024.0.0; extra == "export-base" +Requires-Dist: packaging>=26.0; (platform_machine == "aarch64" and platform_system == "Linux" and python_version >= "3.9") and extra == "export-base" +Provides-Extra: export-legacy-torch +Requires-Dist: numpy<2; extra == "export-legacy-torch" +Requires-Dist: setuptools<81.0.0; python_version >= "3.9" and extra == "export-legacy-torch" +Provides-Extra: export-tensorflow +Requires-Dist: numpy<2.0.0; python_version < "3.13" and extra == "export-tensorflow" +Requires-Dist: tensorflow<=2.19.0,>=2.0.0; python_version < "3.13" and extra == "export-tensorflow" +Requires-Dist: tensorflow>2.19.0; python_version >= "3.13" and extra == "export-tensorflow" +Requires-Dist: ydf<0.13.0; (platform_machine != "aarch64" and python_version <= "3.12") and extra == "export-tensorflow" +Requires-Dist: googleapis-common-protos<1.74.0; python_version <= "3.12" and extra == "export-tensorflow" +Requires-Dist: tensorstore>=0.1.63; (platform_machine == "aarch64" and python_version >= "3.9") and extra == "export-tensorflow" +Requires-Dist: h5py!=3.11.0; platform_machine == "aarch64" and extra == "export-tensorflow" +Provides-Extra: export-coreml +Requires-Dist: numpy<=2.3.5; python_version >= "3.13" and extra == "export-coreml" +Requires-Dist: coremltools>=9.0; (platform_system != "Windows" and python_version <= "3.13") and extra == "export-coreml" +Requires-Dist: scikit-learn>=1.3.2; (platform_system != "Windows" and python_version <= "3.13") and extra == "export-coreml" +Provides-Extra: export-executorch +Requires-Dist: executorch; extra == "export-executorch" +Requires-Dist: numpy<=2.3.5; extra == "export-executorch" +Provides-Extra: export-deepx +Requires-Dist: dx_com; extra == "export-deepx" +Requires-Dist: numpy<2; extra == "export-deepx" +Provides-Extra: export-litert +Requires-Dist: litert-torch>=0.9.0; ((platform_system == "Linux" and platform_machine == "x86_64") or platform_system == "Darwin") and extra == "export-litert" +Requires-Dist: ai-edge-litert>=2.1.4; ((platform_system == "Linux" and platform_machine == "x86_64") or platform_system == "Darwin") and extra == "export-litert" +Requires-Dist: ai-edge-quantizer>=0.6.0; ((platform_system == "Linux" and platform_machine == "x86_64") or platform_system == "Darwin") and extra == "export-litert" +Provides-Extra: export +Requires-Dist: ultralytics[export-base]; extra == "export" +Requires-Dist: ultralytics[export-tensorflow]; extra == "export" +Requires-Dist: ultralytics[export-coreml]; extra == "export" +Requires-Dist: ultralytics[export-litert]; extra == "export" +Provides-Extra: solutions +Requires-Dist: shapely>=2.0.0; extra == "solutions" +Requires-Dist: lap>=0.5.12; extra == "solutions" +Requires-Dist: streamlit>=1.51.0; python_version >= "3.10" and extra == "solutions" +Requires-Dist: streamlit<1.51.0,>=1.29.0; (python_version < "3.10" and (platform_machine != "aarch64" or platform_system != "Linux")) and extra == "solutions" +Requires-Dist: flask>=3.0.1; extra == "solutions" +Provides-Extra: logging +Requires-Dist: wandb; extra == "logging" +Requires-Dist: tensorboard; extra == "logging" +Requires-Dist: mlflow; extra == "logging" +Provides-Extra: extra +Requires-Dist: ipython; extra == "extra" +Requires-Dist: albumentations>=1.4.6; extra == "extra" +Requires-Dist: faster-coco-eval>=1.6.7; extra == "extra" +Provides-Extra: typing +Requires-Dist: types-pillow; extra == "typing" +Requires-Dist: types-psutil; extra == "typing" +Requires-Dist: types-pyyaml; extra == "typing" +Requires-Dist: types-requests; extra == "typing" +Requires-Dist: types-shapely; extra == "typing" +Dynamic: license-file + +
+

+ + Ultralytics YOLO banner +

+ +[中文](https://docs.ultralytics.com/zh) | [한국어](https://docs.ultralytics.com/ko) | [日本語](https://docs.ultralytics.com/ja) | [Русский](https://docs.ultralytics.com/ru) | [Deutsch](https://docs.ultralytics.com/de) | [Français](https://docs.ultralytics.com/fr) | [Español](https://docs.ultralytics.com/es) | [Português](https://docs.ultralytics.com/pt) | [Türkçe](https://docs.ultralytics.com/tr) | [Tiếng Việt](https://docs.ultralytics.com/vi) | [العربية](https://docs.ultralytics.com/ar)
+ +
+ Ultralytics CI + Ultralytics Downloads + Ultralytics Discord + Ultralytics Forums + Ultralytics Reddit +
+ Run Ultralytics on Gradient + Open Ultralytics In Colab + Open Ultralytics In Kaggle + Open Ultralytics In Binder +
+
+
+ +
+ ultralytics%2Fultralytics | Trendshift +
+
+ +[Ultralytics](https://www.ultralytics.com/) creates cutting-edge, state-of-the-art (SOTA) [YOLO models](https://www.ultralytics.com/yolo) built on years of foundational research in computer vision and AI. Constantly updated for performance and flexibility, our models are **fast**, **accurate**, and **easy to use**. They excel at [object detection](https://docs.ultralytics.com/tasks/detect), [tracking](https://docs.ultralytics.com/modes/track), [instance segmentation](https://docs.ultralytics.com/tasks/segment), [semantic segmentation](https://docs.ultralytics.com/tasks/semantic), [image classification](https://docs.ultralytics.com/tasks/classify), and [pose estimation](https://docs.ultralytics.com/tasks/pose) tasks. + +Find detailed documentation in the [Ultralytics Docs](https://docs.ultralytics.com/). Get support via [GitHub Issues](https://github.com/ultralytics/ultralytics/issues/new/choose). Join discussions on [Discord](https://discord.com/invite/ultralytics), [Reddit](https://www.reddit.com/r/ultralytics/), and the [Ultralytics Community Forums](https://community.ultralytics.com/)! + +Request an Enterprise License for commercial use at [Ultralytics Licensing](https://www.ultralytics.com/license). + + + YOLO26 performance plots + + +
+ Ultralytics GitHub + space + Ultralytics LinkedIn + space + Ultralytics Twitter + space + Ultralytics YouTube + space + Ultralytics TikTok + space + Ultralytics BiliBili + space + Ultralytics Discord +
+ +## 📄 Documentation + +See below for quickstart installation and usage examples. For comprehensive guidance on training, validation, prediction, and deployment, refer to our full [Ultralytics Docs](https://docs.ultralytics.com/). + +
+Install + +Install the `ultralytics` package, including all [requirements](https://github.com/ultralytics/ultralytics/blob/main/pyproject.toml), in a [**Python>=3.8**](https://www.python.org/) environment with [**PyTorch>=1.8**](https://pytorch.org/get-started/locally/). + +[![PyPI - Version](https://img.shields.io/pypi/v/ultralytics?logo=pypi&logoColor=white)](https://pypi.org/project/ultralytics/) [![Ultralytics Downloads](https://static.pepy.tech/badge/ultralytics)](https://clickpy.clickhouse.com/dashboard/ultralytics) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ultralytics?logo=python&logoColor=gold)](https://pypi.org/project/ultralytics/) + +```bash +pip install ultralytics +``` + +For alternative installation methods, including [Conda](https://anaconda.org/conda-forge/ultralytics), [Docker](https://hub.docker.com/r/ultralytics/ultralytics), and building from source via Git, please consult the [Quickstart Guide](https://docs.ultralytics.com/quickstart). + +[![Conda Version](https://img.shields.io/conda/vn/conda-forge/ultralytics?logo=condaforge)](https://anaconda.org/conda-forge/ultralytics) [![Docker Image Version](https://img.shields.io/docker/v/ultralytics/ultralytics?sort=semver&logo=docker)](https://hub.docker.com/r/ultralytics/ultralytics) [![Ultralytics Docker Pulls](https://img.shields.io/docker/pulls/ultralytics/ultralytics?logo=docker)](https://hub.docker.com/r/ultralytics/ultralytics) + +
+ +
+Usage + +### CLI + +You can use Ultralytics YOLO directly from the Command Line Interface (CLI) with the `yolo` command: + +```bash +# Predict using a pretrained YOLO model (e.g., YOLO26n) on an image +yolo predict model=yolo26n.pt source='https://ultralytics.com/images/bus.jpg' +``` + +The `yolo` command supports various tasks and modes, accepting additional arguments like `imgsz=640`. Explore the YOLO [CLI Docs](https://docs.ultralytics.com/usage/cli) for more examples. + +### Python + +Ultralytics YOLO can also be integrated directly into your Python projects. It accepts the same [configuration arguments](https://docs.ultralytics.com/usage/cfg) as the CLI: + +```python +from ultralytics import YOLO + +# Load a pretrained YOLO26n model +model = YOLO("yolo26n.pt") + +# Train the model on the COCO8 dataset for 100 epochs +train_results = model.train( + data="coco8.yaml", # Path to dataset configuration file + epochs=100, # Number of training epochs + imgsz=640, # Image size for training + device="cpu", # Device to run on (e.g., 'cpu', 0, [0,1,2,3]) +) + +# Evaluate the model's performance on the validation set +metrics = model.val() + +# Perform object detection on an image +results = model("path/to/image.jpg") # Predict on an image +results[0].show() # Display results + +# Export the model to ONNX format for deployment +path = model.export(format="onnx") # Returns the path to the exported model +``` + +Discover more examples in the YOLO [Python Docs](https://docs.ultralytics.com/usage/python). + +
+ +## ✨ Models + +Ultralytics supports a wide range of YOLO models, from early versions like [YOLOv3](https://docs.ultralytics.com/models/yolov3) to the latest [YOLO26](https://docs.ultralytics.com/models/yolo26). The tables below showcase YOLO26 models pretrained on [COCO](https://docs.ultralytics.com/datasets/detect/coco) for [Detection](https://docs.ultralytics.com/tasks/detect), [Segmentation](https://docs.ultralytics.com/tasks/segment), and [Pose Estimation](https://docs.ultralytics.com/tasks/pose). [Semantic Segmentation](https://docs.ultralytics.com/tasks/semantic) models are pretrained on [Cityscapes](https://docs.ultralytics.com/datasets/semantic/cityscapes), and [Classification](https://docs.ultralytics.com/tasks/classify) models are pretrained on [ImageNet](https://docs.ultralytics.com/datasets/classify/imagenet). [Tracking](https://docs.ultralytics.com/modes/track) mode is compatible with Detection, Segmentation, and Pose models. All [Models](https://docs.ultralytics.com/models) download automatically from the latest Ultralytics [release](https://github.com/ultralytics/assets/releases) on first use. + + + Ultralytics YOLO supported tasks + +
+
+ +
Detection (COCO) + +Explore the [Detection Docs](https://docs.ultralytics.com/tasks/detect) for usage examples. These models are trained on the [COCO dataset](https://cocodataset.org/), featuring 80 object classes. + +| Model | size
(pixels) | mAPval
50-95
| mAPval
50-95(e2e)
| Speed
CPU ONNX
(ms)
| Speed
T4 TensorRT10
(ms)
| params
(M) | FLOPs
(B) | +| ------------------------------------------------------------------------------------ | --------------------------- | -------------------------- | ------------------------------- | ------------------------------------ | ----------------------------------------- | ------------------------ | ----------------------- | +| [YOLO26n](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26n.pt) | 640 | 40.9 | 40.1 | 38.9 ± 0.7 | 1.7 ± 0.0 | 2.4 | 5.4 | +| [YOLO26s](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26s.pt) | 640 | 48.6 | 47.8 | 87.2 ± 0.9 | 2.5 ± 0.0 | 9.5 | 20.7 | +| [YOLO26m](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26m.pt) | 640 | 53.1 | 52.5 | 220.0 ± 1.4 | 4.7 ± 0.1 | 20.4 | 68.2 | +| [YOLO26l](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26l.pt) | 640 | 55.0 | 54.4 | 286.2 ± 2.0 | 6.2 ± 0.2 | 24.8 | 86.4 | +| [YOLO26x](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26x.pt) | 640 | 57.5 | 56.9 | 525.8 ± 4.0 | 11.8 ± 0.2 | 55.7 | 193.9 | + +- **mAPval** values refer to single-model single-scale performance on the [COCO val2017](https://cocodataset.org/) dataset. See [YOLO Performance Metrics](https://docs.ultralytics.com/guides/yolo-performance-metrics) for details.
Reproduce with `yolo val detect data=coco.yaml device=0` +- **Speed** metrics are averaged over COCO val images using an [Amazon EC2 P4d](https://aws.amazon.com/ec2/instance-types/p4/) instance. CPU speeds measured with [ONNX](https://onnx.ai/) export. GPU speeds measured with [TensorRT](https://developer.nvidia.com/tensorrt) export.
Reproduce with `yolo val detect data=coco.yaml batch=1 device=0|cpu` + +
+ +
Segmentation (COCO) + +Refer to the [Segmentation Docs](https://docs.ultralytics.com/tasks/segment) for usage examples. These models are trained on [COCO-Seg](https://docs.ultralytics.com/datasets/segment/coco), including 80 classes. + +| Model | size
(pixels) | mAPbox
50-95(e2e)
| mAPmask
50-95(e2e)
| Speed
CPU ONNX
(ms)
| Speed
T4 TensorRT10
(ms)
| params
(M) | FLOPs
(B) | +| -------------------------------------------------------------------------------------------- | --------------------------- | ------------------------------- | -------------------------------- | ------------------------------------ | ----------------------------------------- | ------------------------ | ----------------------- | +| [YOLO26n-seg](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26n-seg.pt) | 640 | 39.6 | 33.9 | 53.3 ± 0.5 | 2.1 ± 0.0 | 2.7 | 9.1 | +| [YOLO26s-seg](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26s-seg.pt) | 640 | 47.3 | 40.0 | 118.4 ± 0.9 | 3.3 ± 0.0 | 10.4 | 34.2 | +| [YOLO26m-seg](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26m-seg.pt) | 640 | 52.5 | 44.1 | 328.2 ± 2.4 | 6.7 ± 0.1 | 23.6 | 121.5 | +| [YOLO26l-seg](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26l-seg.pt) | 640 | 54.4 | 45.5 | 387.0 ± 3.7 | 8.0 ± 0.1 | 28.0 | 139.8 | +| [YOLO26x-seg](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26x-seg.pt) | 640 | 56.5 | 47.0 | 787.0 ± 6.8 | 16.4 ± 0.1 | 62.8 | 313.5 | + +- **mAPval** values are for single-model single-scale on the [COCO val2017](https://cocodataset.org/) dataset. See [YOLO Performance Metrics](https://docs.ultralytics.com/guides/yolo-performance-metrics) for details.
Reproduce with `yolo val segment data=coco.yaml device=0` +- **Speed** metrics are averaged over COCO val images using an [Amazon EC2 P4d](https://aws.amazon.com/ec2/instance-types/p4/) instance. CPU speeds measured with [ONNX](https://onnx.ai/) export. GPU speeds measured with [TensorRT](https://developer.nvidia.com/tensorrt) export.
Reproduce with `yolo val segment data=coco.yaml batch=1 device=0|cpu` + +
+ +
Semantic Segmentation (Cityscapes) + +See the [Semantic Segmentation Docs](https://docs.ultralytics.com/tasks/semantic) for usage examples. These models are trained on [Cityscapes](https://docs.ultralytics.com/datasets/semantic/cityscapes), including 19 classes. + +| Model | size
(pixels) | mIoUval | Speed
RTX3090 PyTorch
(ms)
| params
(M) | FLOPs
(B) | +| -------------------------------------------------------------------------------------------- | --------------------------- | ------------------ | ------------------------------------------- | ------------------------ | ----------------------- | +| [YOLO26n-sem](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26n-sem.pt) | 1024 × 2048 | 78.3 | 4.4 ± 0.0 | 1.6 | 22.7 | +| [YOLO26s-sem](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26s-sem.pt) | 1024 × 2048 | 80.8 | 8.4 ± 0.0 | 6.5 | 88.8 | +| [YOLO26m-sem](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26m-sem.pt) | 1024 × 2048 | 82.0 | 19.9 ± 0.1 | 14.3 | 304.5 | +| [YOLO26l-sem](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26l-sem.pt) | 1024 × 2048 | 82.9 | 26.5 ± 0.1 | 17.9 | 384.7 | +| [YOLO26x-sem](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26x-sem.pt) | 1024 × 2048 | 83.6 | 48.9 ± 0.2 | 40.2 | 861.7 | + +- **mIoUval** values are for single-model single-scale on the [Cityscapes](https://www.cityscapes-dataset.com/) validation set.
Reproduce with `yolo semantic val data=cityscapes.yaml device=0 imgsz=2048` +- **Speed** metrics are averaged over Cityscapes validation images using an RTX3090 instance.
Reproduce with `yolo semantic val data=cityscapes.yaml batch=1 device=0|cpu imgsz=2048` + +
+ +
Classification (ImageNet) + +Consult the [Classification Docs](https://docs.ultralytics.com/tasks/classify) for usage examples. These models are trained on [ImageNet](https://docs.ultralytics.com/datasets/classify/imagenet), covering 1000 classes. + +| Model | size
(pixels) | acc
top1 | acc
top5 | Speed
CPU ONNX
(ms)
| Speed
T4 TensorRT10
(ms)
| params
(M) | FLOPs
(B) at 224 | +| -------------------------------------------------------------------------------------------- | --------------------------- | ---------------------- | ---------------------- | ------------------------------------ | ----------------------------------------- | ------------------------ | ------------------------------ | +| [YOLO26n-cls](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26n-cls.pt) | 224 | 71.4 | 90.1 | 5.0 ± 0.3 | 1.1 ± 0.0 | 2.8 | 0.5 | +| [YOLO26s-cls](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26s-cls.pt) | 224 | 76.0 | 92.9 | 7.9 ± 0.2 | 1.3 ± 0.0 | 6.7 | 1.6 | +| [YOLO26m-cls](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26m-cls.pt) | 224 | 78.1 | 94.2 | 17.2 ± 0.4 | 2.0 ± 0.0 | 11.6 | 4.9 | +| [YOLO26l-cls](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26l-cls.pt) | 224 | 79.0 | 94.6 | 23.2 ± 0.3 | 2.8 ± 0.0 | 14.1 | 6.2 | +| [YOLO26x-cls](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26x-cls.pt) | 224 | 79.9 | 95.0 | 41.4 ± 0.9 | 3.8 ± 0.0 | 29.6 | 13.6 | + +- **acc** values represent model accuracy on the [ImageNet](https://www.image-net.org/) dataset validation set.
Reproduce with `yolo val classify data=path/to/ImageNet device=0` +- **Speed** metrics are averaged over ImageNet val images using an [Amazon EC2 P4d](https://aws.amazon.com/ec2/instance-types/p4/) instance. CPU speeds measured with [ONNX](https://onnx.ai/) export. GPU speeds measured with [TensorRT](https://developer.nvidia.com/tensorrt) export.
Reproduce with `yolo val classify data=path/to/ImageNet batch=1 device=0|cpu` + +
+ +
Pose (COCO) + +See the [Pose Estimation Docs](https://docs.ultralytics.com/tasks/pose) for usage examples. These models are trained on [COCO-Pose](https://docs.ultralytics.com/datasets/pose/coco), focusing on the 'person' class. + +| Model | size
(pixels) | mAPpose
50-95(e2e)
| mAPpose
50(e2e)
| Speed
CPU ONNX
(ms)
| Speed
T4 TensorRT10
(ms)
| params
(M) | FLOPs
(B) | +| ---------------------------------------------------------------------------------------------- | --------------------------- | -------------------------------- | ----------------------------- | ------------------------------------ | ----------------------------------------- | ------------------------ | ----------------------- | +| [YOLO26n-pose](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26n-pose.pt) | 640 | 57.2 | 83.3 | 40.3 ± 0.5 | 1.8 ± 0.0 | 2.9 | 7.5 | +| [YOLO26s-pose](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26s-pose.pt) | 640 | 63.0 | 86.6 | 85.3 ± 0.9 | 2.7 ± 0.0 | 10.4 | 23.9 | +| [YOLO26m-pose](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26m-pose.pt) | 640 | 68.8 | 89.6 | 218.0 ± 1.5 | 5.0 ± 0.1 | 21.5 | 73.1 | +| [YOLO26l-pose](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26l-pose.pt) | 640 | 70.4 | 90.5 | 275.4 ± 2.4 | 6.5 ± 0.1 | 25.9 | 91.3 | +| [YOLO26x-pose](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26x-pose.pt) | 640 | 71.6 | 91.6 | 565.4 ± 3.0 | 12.2 ± 0.2 | 57.6 | 201.7 | + +- **mAPval** values are for single-model single-scale on the [COCO Keypoints val2017](https://docs.ultralytics.com/datasets/pose/coco) dataset. See [YOLO Performance Metrics](https://docs.ultralytics.com/guides/yolo-performance-metrics) for details.
Reproduce with `yolo val pose data=coco-pose.yaml device=0` +- **Speed** metrics are averaged over COCO val images using an [Amazon EC2 P4d](https://aws.amazon.com/ec2/instance-types/p4/) instance. CPU speeds measured with [ONNX](https://onnx.ai/) export. GPU speeds measured with [TensorRT](https://developer.nvidia.com/tensorrt) export.
Reproduce with `yolo val pose data=coco-pose.yaml batch=1 device=0|cpu` + +
+ +
Oriented Bounding Boxes (DOTAv1) + +Check the [OBB Docs](https://docs.ultralytics.com/tasks/obb) for usage examples. These models are trained on [DOTAv1](https://docs.ultralytics.com/datasets/obb/dota-v2#dota-v10), including 15 classes. + +| Model | size
(pixels) | mAPtest
50-95(e2e)
| mAPtest
50(e2e)
| Speed
CPU ONNX
(ms)
| Speed
T4 TensorRT10
(ms)
| params
(M) | FLOPs
(B) | +| -------------------------------------------------------------------------------------------- | --------------------------- | -------------------------------- | ----------------------------- | ------------------------------------ | ----------------------------------------- | ------------------------ | ----------------------- | +| [YOLO26n-obb](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26n-obb.pt) | 1024 | 52.4 | 78.9 | 97.7 ± 0.9 | 2.8 ± 0.0 | 2.5 | 14.0 | +| [YOLO26s-obb](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26s-obb.pt) | 1024 | 54.8 | 80.9 | 218.0 ± 1.4 | 4.9 ± 0.1 | 9.8 | 55.1 | +| [YOLO26m-obb](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26m-obb.pt) | 1024 | 55.3 | 81.0 | 579.2 ± 3.8 | 10.2 ± 0.3 | 21.2 | 183.3 | +| [YOLO26l-obb](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26l-obb.pt) | 1024 | 56.2 | 81.6 | 735.6 ± 3.1 | 13.0 ± 0.2 | 25.6 | 230.0 | +| [YOLO26x-obb](https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26x-obb.pt) | 1024 | 56.7 | 81.7 | 1485.7 ± 11.5 | 30.5 ± 0.9 | 57.6 | 516.5 | + +- **mAPtest** values are for single-model multiscale performance on the [DOTAv1 test set](https://captain-whu.github.io/DOTA/dataset.html).
Reproduce by `yolo val obb data=DOTAv1.yaml device=0 split=test` and submit merged results to the [DOTA evaluation server](https://captain-whu.github.io/DOTA/evaluation.html). +- **Speed** metrics are averaged over [DOTAv1 val images](https://docs.ultralytics.com/datasets/obb/dota-v2#dota-v10) using an [Amazon EC2 P4d](https://aws.amazon.com/ec2/instance-types/p4/) instance. CPU speeds measured with [ONNX](https://onnx.ai/) export. GPU speeds measured with [TensorRT](https://developer.nvidia.com/tensorrt) export.
Reproduce by `yolo val obb data=DOTAv1.yaml batch=1 device=0|cpu` + +
+ +## 🧩 Integrations + +Our key integrations with leading AI platforms extend the functionality of Ultralytics' offerings, enhancing tasks like dataset labeling, training, visualization, and model management. Discover how Ultralytics, in collaboration with partners like [Weights & Biases](https://docs.ultralytics.com/integrations/weights-biases), [Comet ML](https://docs.ultralytics.com/integrations/comet), [Roboflow](https://docs.ultralytics.com/integrations/roboflow), and [Intel OpenVINO](https://docs.ultralytics.com/integrations/openvino), can optimize your AI workflow. Explore more at [Ultralytics Integrations](https://docs.ultralytics.com/integrations). + + + Ultralytics active learning integrations + + +## 🤝 Contribute + +We thrive on community collaboration! Ultralytics YOLO wouldn't be the SOTA framework it is without contributions from developers like you. Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing) to get started. We also welcome your feedback—share your experience by completing our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey). A huge **Thank You** 🙏 to everyone who contributes! + + + +[![Ultralytics open-source contributors](https://raw.githubusercontent.com/ultralytics/assets/main/im/image-contributors.png)](https://github.com/ultralytics/ultralytics/graphs/contributors) + +We look forward to your contributions to help make the Ultralytics ecosystem even better! + +## 📜 License + +Ultralytics offers two licensing options to suit different needs: + +- **AGPL-3.0 License**: This [OSI-approved](https://opensource.org/license/agpl-3.0) open-source license is perfect for students, researchers, and enthusiasts. It encourages open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/ultralytics/blob/main/LICENSE) file for full details. +- **Ultralytics Enterprise License**: For development and production use, this license enables seamless integration of Ultralytics software and AI models into business products and services, including internal tools, automated workflows, and production deployments, bypassing the open-source requirements of AGPL-3.0. To get started, please contact us via [Ultralytics Licensing](https://www.ultralytics.com/license). + +## 📞 Contact + +For bug reports and feature requests related to Ultralytics software, please visit [GitHub Issues](https://github.com/ultralytics/ultralytics/issues). For questions, discussions, and community support, join our active communities on [Discord](https://discord.com/invite/ultralytics), [Reddit](https://www.reddit.com/r/ultralytics/), and the [Ultralytics Community Forums](https://community.ultralytics.com/). We're here to help with all things Ultralytics! + +
+
+ Ultralytics GitHub + space + Ultralytics LinkedIn + space + Ultralytics Twitter + space + Ultralytics YouTube + space + Ultralytics TikTok + space + Ultralytics BiliBili + space + Ultralytics Discord +
diff --git a/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/RECORD b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..b80ec16bed61d5c4a7151d3345942ce6e46efbd9 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/RECORD @@ -0,0 +1,604 @@ +../../../bin/ultralytics,sha256=lvXJu3IMtOnv6tRwpHYHFJ0EXUyLBlj3sqdCStE-Q64,225 +../../../bin/yolo,sha256=lvXJu3IMtOnv6tRwpHYHFJ0EXUyLBlj3sqdCStE-Q64,225 +tests/__init__.py,sha256=aBOT1alCmdyNRPWC4VLK7yvNozZFYLB7BhLhEIkN2Vc,1382 +tests/__pycache__/__init__.cpython-314.pyc,, +tests/__pycache__/cache_test_assets.cpython-314.pyc,, +tests/__pycache__/conftest.cpython-314.pyc,, +tests/__pycache__/test_cli.cpython-314.pyc,, +tests/__pycache__/test_cuda.cpython-314.pyc,, +tests/__pycache__/test_engine.cpython-314.pyc,, +tests/__pycache__/test_exports.cpython-314.pyc,, +tests/__pycache__/test_integrations.cpython-314.pyc,, +tests/__pycache__/test_python.cpython-314.pyc,, +tests/__pycache__/test_solutions.cpython-314.pyc,, +tests/cache_test_assets.py,sha256=v0EU7gS3AXoCwUmlCszJUlEwZ6FNF20RVnCgtowwySc,3937 +tests/conftest.py,sha256=1jSGGXfYmE4zVBkB1w2vjaQepPf2AWUA0qcm93jtvVA,5263 +tests/test_cli.py,sha256=_KVuji9NvnJlNDPLmkBo7PgJBYMyDyt3ax6YovRqZ84,7196 +tests/test_cuda.py,sha256=5YdMbv2iFup23CbwPMMinwu_FNDapq7bgPxlIiNoUDo,8844 +tests/test_engine.py,sha256=NMPaSJdY7JHhIPyHMgiEdiRzH6oNzlL4jYaWPS42xgU,15276 +tests/test_exports.py,sha256=7LpOpkjTJ0rp4qk_KXYjOWG1ow4ncOozb88QKwQheiY,23502 +tests/test_integrations.py,sha256=H3ND6namUB8Ajwvb_ul12aRFeemslwwa1f7aMmZdAlg,6481 +tests/test_python.py,sha256=KbIr7qjsU0Q7TPAWFhmtZRd8AhufEpjr1m5mzgBdcSc,40607 +tests/test_solutions.py,sha256=nvTP6MtuwU742pdlzqexdEo8lCkybHURmwALZzwBxHo,14158 +ultralytics-8.4.83.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +ultralytics-8.4.83.dist-info/METADATA,sha256=FVTZQev05uLqMTu5_c3nOAenvByDHl4ld8fcOJHfjPs,41801 +ultralytics-8.4.83.dist-info/RECORD,, +ultralytics-8.4.83.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ultralytics-8.4.83.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91 +ultralytics-8.4.83.dist-info/entry_points.txt,sha256=YM_wiKyTe9yRrsEfqvYolNO5ngwfoL4-NwgKzc8_7sI,93 +ultralytics-8.4.83.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523 +ultralytics-8.4.83.dist-info/top_level.txt,sha256=XP49TwiMw4QGsvTLSYiJhz1xF_k7ev5mQ8jJXaXi45Q,12 +ultralytics/__init__.py,sha256=EvUELO5v2GlZOoi7JxmL0HR1evkt3TPDlWh9knVhtFs,1301 +ultralytics/__pycache__/__init__.cpython-314.pyc,, +ultralytics/assets/bus.jpg,sha256=wCAZxJecGR63Od3ZRERe9Aja1Weayrb9Ug751DS_vGM,137419 +ultralytics/assets/zidane.jpg,sha256=Ftc4aeMmen1O0A3o6GCDO9FlfBslLpTAw0gnetx7bts,50427 +ultralytics/cfg/__init__.py,sha256=d_Aj9m6tCjQy9SKnJvn4LFf9yS8t5QsU9yrvfqEL_bg,45019 +ultralytics/cfg/__pycache__/__init__.cpython-314.pyc,, +ultralytics/cfg/datasets/Argoverse.yaml,sha256=VLuhz_gPQZTAaBFHEzSC9PxMOY3z8TUyUBZVBAm_HFE,3353 +ultralytics/cfg/datasets/DOTAv1.5.yaml,sha256=X-u1bHSwFqf5-vgPIWiJfYev3KK9LHhXZz1ShQNkcc0,1212 +ultralytics/cfg/datasets/DOTAv1.yaml,sha256=GOPKL5JUbdFU_B7jWtOPdz27OnX_DJMCm7WIWrZABr8,1182 +ultralytics/cfg/datasets/GlobalWheat2020.yaml,sha256=_GjE5sxGcpLov7r-M06XsjWlolMiy3xLxs4ksw2lZRc,2143 +ultralytics/cfg/datasets/HomeObjects-3K.yaml,sha256=uQ-wWTT9W-Hr7hiuqa4rMS5ATnkQeMuGReaJsqxeP7U,933 +ultralytics/cfg/datasets/ImageNet.yaml,sha256=06smoReuJ9Q0XmzKlK0Y48z----SeaqgC6zdORy3b28,42528 +ultralytics/cfg/datasets/Objects365.yaml,sha256=fKgiwvntM-RpbfJmAgPmfL3k1lyXl0JZZb6e_RE8v14,9648 +ultralytics/cfg/datasets/SKU-110K.yaml,sha256=cVXoMAOHbik-MAOKqtke6TU2QCaEyn9cBlytqQeKh20,2608 +ultralytics/cfg/datasets/TT100K.yaml,sha256=CMb94ZxiADjg1oKZThY2DrSt7VsqxrxIK3zlCotfQHw,6895 +ultralytics/cfg/datasets/VOC.yaml,sha256=p-ZqQf5aP26FDqCfHZjfYOrDtzHB9VE5gpKlGlU9X2g,3802 +ultralytics/cfg/datasets/VisDrone.yaml,sha256=z0lG4KNLohaNKbVBHoMh4avjpbKCv90CyLs__roJu58,3396 +ultralytics/cfg/datasets/ade20k.yaml,sha256=DLj7DKcOa0rZHuJp72GgcU2XnBlypwp9ub-9tKdlid4,4322 +ultralytics/cfg/datasets/african-wildlife.yaml,sha256=GH81S5fG5dAibTw_p1c0ivaLnnvXlGF3kzWtbyU3ejo,914 +ultralytics/cfg/datasets/brain-tumor.yaml,sha256=y5Skd-Yy9wmAfF3E40LYvHQkYsgbDlWU5aKg0EhOwJY,799 +ultralytics/cfg/datasets/carparts-seg.yaml,sha256=6Xy2UDEzsdUk-s3qgOSrrCFFByiunaJ7qHpL-JrsXb8,1252 +ultralytics/cfg/datasets/cityscapes.yaml,sha256=hvqK65DEjCM0Xjl9xTqlQxUkYyBQjfRQ2zn561GCnEA,2998 +ultralytics/cfg/datasets/cityscapes8.yaml,sha256=MM7KLZW1Jh2duhIaNM5v6PkUtlFgIypzXDrdnhL5kII,1623 +ultralytics/cfg/datasets/coco-pose.yaml,sha256=fgiCiMjacLl9g-npcl19AqFJd9kkitUZEOHFakAmQc4,1927 +ultralytics/cfg/datasets/coco.yaml,sha256=fgUmbfvfIGlPWgkm0GBb1xJKzkSc8tzow4nOAJ2jctA,2572 +ultralytics/cfg/datasets/coco12-formats.yaml,sha256=ZzXepkW2h3u9xJpYYZCPNS9E7htGLKfzxGLTq-aioQo,1952 +ultralytics/cfg/datasets/coco128-seg.yaml,sha256=lr8lrEnrsIM4bqG1YI9hYBrAfygf5CMu9opG8FXJf5Y,1992 +ultralytics/cfg/datasets/coco128.yaml,sha256=B6CDxu3TCY1Mm3x-iRXEbM2N1IyOuXC_sU2ViVg5KEQ,1967 +ultralytics/cfg/datasets/coco8-grayscale.yaml,sha256=ZbTr4lQpqVWCHw9d4Pghi9e3fJagrMLqXqX0wtU06G8,1960 +ultralytics/cfg/datasets/coco8-multispectral.yaml,sha256=mDcNFGmSKT-bBk88EDDnIvuy18utSjAXlso1S581Aok,2062 +ultralytics/cfg/datasets/coco8-pose.yaml,sha256=oaFyz668gJUfiL83UleJSch6UNV-biiL2rLTM6RrZPs,1335 +ultralytics/cfg/datasets/coco8-seg.yaml,sha256=iXAMV5GP2xIO2PHl1JDHXvyIob2neo8RlKHkOVDac7Q,1912 +ultralytics/cfg/datasets/coco8.yaml,sha256=Awqv-gj4oWHcNYfSFqqVbAyJR-WUstnYUVo2uaxE-fo,1887 +ultralytics/cfg/datasets/construction-ppe.yaml,sha256=iSa27Cwowv1vycVK_o3hK8UXwHE8EdNTI6NvDVBynck,1007 +ultralytics/cfg/datasets/crack-seg.yaml,sha256=jY7IZUxOamioCdcBdNO5eXt8fVeS40gCmbebouQpzkM,836 +ultralytics/cfg/datasets/dog-pose.yaml,sha256=OboPj_y8QlEGH9ECqLNuT32RY_YapEo3IUn8Walvym0,1407 +ultralytics/cfg/datasets/dota128.yaml,sha256=Kzktc64z93wTZdg3hvHrZyszlhNTZ4oPVsbXkkmtVGg,1079 +ultralytics/cfg/datasets/dota8-multispectral.yaml,sha256=sZzrP1SUMryEJe8syMb6BsAghIbyfMQuqHI0BxiMRS4,1216 +ultralytics/cfg/datasets/dota8.yaml,sha256=W_3Bcp8dRHeOL0VFWsaaNEXq8Kj2PzgYwVEQqeOtNqQ,1058 +ultralytics/cfg/datasets/hand-keypoints.yaml,sha256=wPLYeZmGQ8lWI89oLwRVMDGnBka6H7yPZCtxeg8P878,1362 +ultralytics/cfg/datasets/kitti.yaml,sha256=PRyDF6bkfG3X4aYv6PTUTSGHiCUsVAA2i9i8KO9OElI,894 +ultralytics/cfg/datasets/lvis.yaml,sha256=0XwKkQW5d7VVkIHJP8KmLq2oxaUYPcR2L-mlp1hA870,29595 +ultralytics/cfg/datasets/medical-pills.yaml,sha256=5GLYrB0rwkOrDSe1KswZAuly1LWdNyW1GsASRGHaKEM,791 +ultralytics/cfg/datasets/open-images-v7.yaml,sha256=SXRor53GRNFJEB78CQcn08b9ZEWR9MwujFresA9K2Eg,12115 +ultralytics/cfg/datasets/package-seg.yaml,sha256=uBzKJHCJtyfV-Omza8Fww6h_NDpNGIQco2wN513vj5w,848 +ultralytics/cfg/datasets/signature.yaml,sha256=134eo-EpaK9XxISzTZnUP2bNtn3Hb-5j_-nO4Pt-JJM,773 +ultralytics/cfg/datasets/tiger-pose.yaml,sha256=DQk5S7uH5JbP3pD6Q7I12AAagNRA3AKiZTeubsjhYR4,1195 +ultralytics/cfg/datasets/xView.yaml,sha256=m2pWXw_SheDPwkPdNEVLIlx9uLhTEOTgOxePe7MG2Jo,5405 +ultralytics/cfg/default.yaml,sha256=9uGatyKIJjQcxjcLzIx40QR40r1gTiWhPZl1te_JtBA,9690 +ultralytics/cfg/models/11/yolo11-cls-resnet18.yaml,sha256=1Ycp9qMrwpb8rq7cqht3Q-1gMN0R87U35nm2j_isdro,524 +ultralytics/cfg/models/11/yolo11-cls.yaml,sha256=17l5GdN-Vst4LvafsK2-q6Li9VX9UlUcT5ClCtikweE,1412 +ultralytics/cfg/models/11/yolo11-obb.yaml,sha256=FHo7xWrhKdN2BBn2xdZ099T2NcqcUJiuyQ0a6zdvrqY,2031 +ultralytics/cfg/models/11/yolo11-pose.yaml,sha256=dWOT9VBRxD8h-jmQC0qWEOvFvyx1CUa0tekt1kOdvmk,2131 +ultralytics/cfg/models/11/yolo11-seg.yaml,sha256=xyTWVhFoSszF1LhNEIznIW8QGKU1z0EAIvkp4OwYtls,2046 +ultralytics/cfg/models/11/yolo11.yaml,sha256=Q9inyGrMdygt30lm1lJuCR5bBkwUDtSm5MC2jsvDeEw,2012 +ultralytics/cfg/models/11/yoloe-11-seg.yaml,sha256=j053cYgHMCRFUxJ7MumNkHz7eq6hxvcMMuNOzNsdV1w,2072 +ultralytics/cfg/models/11/yoloe-11.yaml,sha256=qxKDa8Xt-embNVOLOthfQlt1N0kFFHnGFO74R_CQzUw,2038 +ultralytics/cfg/models/12/yolo12-cls.yaml,sha256=yJBuDOXRYLa-LQipGkLBT97ujKSyXx755zts612JVWc,1407 +ultralytics/cfg/models/12/yolo12-obb.yaml,sha256=2cPJKV7kU1thYxnnNsCJPfWNoTk0l29ZD22U4XTX3wQ,1972 +ultralytics/cfg/models/12/yolo12-pose.yaml,sha256=eBo-9b3o_qv3-jftgCaX0FedeJWOfKHDLJPPfCMwu7M,2072 +ultralytics/cfg/models/12/yolo12-seg.yaml,sha256=0JTqSjyhOTLNTIrIFAOuu6F7yQqMkq1soscAkSZtsz8,1985 +ultralytics/cfg/models/12/yolo12.yaml,sha256=ZeA8LuymJXPNjZ5xkxkZHkcktDaKDzUBb2Kc3gCLC1w,1953 +ultralytics/cfg/models/26/yolo26-cls.yaml,sha256=GmaLvnB62X6r9_mZwpx0b2LTRYIXzVYqSqR3Mcy336g,1432 +ultralytics/cfg/models/26/yolo26-obb.yaml,sha256=A9Ng5nZOow74tYkejxoVyGB2wztIi_cuNqxYrqB5T38,2146 +ultralytics/cfg/models/26/yolo26-p2.yaml,sha256=Dbwvo9_t5e3NolZquMfqFQrQZuop155o1sTaK6tHYnk,2405 +ultralytics/cfg/models/26/yolo26-p6.yaml,sha256=YFWaH6NAOHoLYmH4Te3edcj5-c7QJ-glzri-xPFk4U0,2457 +ultralytics/cfg/models/26/yolo26-pose.yaml,sha256=B7qC2WNwI_Jh5SbDj6muuKsF2RF2b_2M9PSM0z8nGI0,2245 +ultralytics/cfg/models/26/yolo26-seg.yaml,sha256=U8A0ijaj4Zu8O9T2lfUMu_yXowWfYumyUfJ4rXT918s,2156 +ultralytics/cfg/models/26/yolo26-sem.yaml,sha256=rX3g2vBk7U9T7ejtCjLWqjOaXaPNMEeR2BWq2oe5nYI,1850 +ultralytics/cfg/models/26/yolo26.yaml,sha256=sdH6DGns7WT5k5U2A4vY1pfDLnIgCzQYenZ95DGvfvA,2120 +ultralytics/cfg/models/26/yoloe-26-seg.yaml,sha256=QlyK3awjLOjs9q0raj0we5QCG1tDy-NvQ2uQzKDoBYk,2223 +ultralytics/cfg/models/26/yoloe-26.yaml,sha256=YWsBS4D3f7QhqLQzYT-LVdCOISo0VaTILIaLKmujDdo,2191 +ultralytics/cfg/models/rt-detr/rtdetr-l.yaml,sha256=hXFvYmdpy13fANWfz2yvtYFKrRljKBAL3HyTMG9lDoM,2043 +ultralytics/cfg/models/rt-detr/rtdetr-resnet101.yaml,sha256=5vCPfihyzXMKCR7kYYIDSHbSHvAzYlWbOZ5Wp9m84Ms,1696 +ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml,sha256=Xm-U_m-_okdspiW7qxb5mgN_NPZPvyjXrS1A1KcoaFo,1692 +ultralytics/cfg/models/rt-detr/rtdetr-x.yaml,sha256=QHa3B5Pwd9TA9B3W5lEwrUl-_bAvwGXVqSlvnLSbKNs,2250 +ultralytics/cfg/models/v10/yolov10b.yaml,sha256=W9K-lqgUEKOCpV0Ae7dk8iBUFpSTTFzDPDL3AHnMKfs,1488 +ultralytics/cfg/models/v10/yolov10l.yaml,sha256=YtdL74ivxWxY2jUsYkkUX_WraWsLJtcOKhRNioCwvJg,1488 +ultralytics/cfg/models/v10/yolov10m.yaml,sha256=YQuxn4KjiGqxwK-3BuRSprt86aWxcxn2VngIlHQKxy4,1479 +ultralytics/cfg/models/v10/yolov10n.yaml,sha256=iL9T4XUrTnf001i6e9qF9wEBCUoral_fF5bjIFxJdMU,1474 +ultralytics/cfg/models/v10/yolov10s.yaml,sha256=dG_z0fSweIzCi7Km9OY-Q9IztEOjX67wFsdP4Eam_to,1483 +ultralytics/cfg/models/v10/yolov10x.yaml,sha256=IFIYR5p46OECsHK3yz5UO5CMxR1JNDWeXKzUQQvxM_M,1491 +ultralytics/cfg/models/v3/yolov3-spp.yaml,sha256=hsM-yhdWv-8XlWuaSOVqFJcHUVZ-FmjH4QjkA9CHJZU,1625 +ultralytics/cfg/models/v3/yolov3-tiny.yaml,sha256=SYrSg0m1A6ErUapdrJDI5E-edLaH0oF-NRb558DZgmQ,1330 +ultralytics/cfg/models/v3/yolov3.yaml,sha256=Fvt4_PTwLBpRw3R4v4VQ-1PIiojpoFZD1uuTZySUYSw,1612 +ultralytics/cfg/models/v5/yolov5-p6.yaml,sha256=VKEWykksykSlzvuy7if4yFo9WlblC3hdqcNxJ9bwHek,1994 +ultralytics/cfg/models/v5/yolov5.yaml,sha256=QD8dRe5e5ys52wXPKvNJn622H_3iX0jPzE_2--2dZx0,1626 +ultralytics/cfg/models/v6/yolov6.yaml,sha256=tl04iHe4dVg_78jgupVul5gbqOn5hBhtLKfP3xYxcWA,1813 +ultralytics/cfg/models/v8/yoloe-v8-seg.yaml,sha256=5cjZ2xCMUF2pV7gv1ZoRFEV6Lo6v4slMQt1lU7_Jqtc,2064 +ultralytics/cfg/models/v8/yoloe-v8.yaml,sha256=dujtgHql2RzqqEEpB4UzsEpAXMRarkltnt8vb1usILc,2015 +ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml,sha256=TAiAkZwUckzjWdY6yn_ulGzM-lnHaY7Yx9v8rI-2WoA,1014 +ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml,sha256=gbIttNMvj02Rk3eKjq45qgjdmdCo5n_mV9R5xt65OdU,1010 +ultralytics/cfg/models/v8/yolov8-cls.yaml,sha256=G50mnw-C0SWrZpZl5wzov1dugdjZMM6zT30t5cQrcJQ,1019 +ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml,sha256=5MX8D3_8lkj9aKVwpAIC1PZAaMoTFs_Wq-XZUTVn9PU,2505 +ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml,sha256=c7YYnEPx2vrwHjbgr4o_Q9f_NI2X1dx2MrLo9xeOvXc,2575 +ultralytics/cfg/models/v8/yolov8-ghost.yaml,sha256=nxzIlvntFajHbu4DN8ujEx65PREgn7o0Dd6cHp8pHSM,2198 +ultralytics/cfg/models/v8/yolov8-obb.yaml,sha256=Owlncx5peKBTNo3xhkbVxz_1qlzVJR4Z2zNeoR_NDxg,2016 +ultralytics/cfg/models/v8/yolov8-p2.yaml,sha256=ulQ2Pp8oPo9gsPoIQ-s3EVCTM41vknPM1PdPYGOXVPQ,1831 +ultralytics/cfg/models/v8/yolov8-p6.yaml,sha256=TqIsa8gNEW04KmdLxxC9rqhd7PCHlUqkzoiDxnMTio0,2363 +ultralytics/cfg/models/v8/yolov8-pose-p6.yaml,sha256=tfgfYrbVu5biWCWmdTZRr7ZRC-zlAzycsRyaJbDtI1g,2047 +ultralytics/cfg/models/v8/yolov8-pose.yaml,sha256=LdzbiIVknZQMLYB2wzCHqul3NilfKp4nx5SdaGQsF6s,1676 +ultralytics/cfg/models/v8/yolov8-rtdetr.yaml,sha256=5cupHKPHD43cuvd3H3zvNijiqpW9-bFLKg-KMeRzOPE,2055 +ultralytics/cfg/models/v8/yolov8-seg-p6.yaml,sha256=7FlNlY-sB8bCcVty2Hf_nYD8fxZpsqaTgGxTfac8DRI,1958 +ultralytics/cfg/models/v8/yolov8-seg.yaml,sha256=hFeiOFVwTV4zv08IrmTIuzJcUZmYkY7SIi2oV322e6U,1587 +ultralytics/cfg/models/v8/yolov8-world.yaml,sha256=2hNQoybITO5FBNnKKz05Pk9ygeAf4qW7XvkMEfBd90A,2174 +ultralytics/cfg/models/v8/yolov8-worldv2.yaml,sha256=UkdRhk7pauLIZto7QdOrWpW8Mb-3J61RdRuxS5lZC24,2124 +ultralytics/cfg/models/v8/yolov8.yaml,sha256=QFo8MC62CWEDqZr02CwdLYsrv_RpoijFWqyUSywZZyo,1977 +ultralytics/cfg/models/v9/yolov9c-seg.yaml,sha256=UBHoQ_cJV2yp6rMzHXRp46uBAUmKIrbgd3jiEBPRvqI,1447 +ultralytics/cfg/models/v9/yolov9c.yaml,sha256=x1kus_2mQdU9V3ZGg0XdE5WTUU3j8fwGe1Ou3x2aX5I,1426 +ultralytics/cfg/models/v9/yolov9e-seg.yaml,sha256=WVpU5jHgoUuCMVirvmn_ScOmH9d1MyVVIX8XAY8787c,2377 +ultralytics/cfg/models/v9/yolov9e.yaml,sha256=Olr2PlADpkD6N1TiVyAJEMzkrA7SbNul1nOaUF8CS38,2355 +ultralytics/cfg/models/v9/yolov9m.yaml,sha256=WcKQ3xRsC1JMgA42Hx4xzr4FZmtE6B3wKvqhlQxkqw8,1411 +ultralytics/cfg/models/v9/yolov9s.yaml,sha256=cWkQtYNWWOckOBXjd8XrJ_q5v6T_C54xGMP1S3qnpZU,1392 +ultralytics/cfg/models/v9/yolov9t.yaml,sha256=Q8GpSXE7fumhuJiQg4a2SkuS_UmnXqp-eoZxW_C0vEo,1375 +ultralytics/cfg/trackers/botsort.yaml,sha256=jNiJMNO-VVe-B3fzhO7P8KNqcBGFD-YT_ApoF4v9WwE,1385 +ultralytics/cfg/trackers/bytetrack.yaml,sha256=OVcB2UenSRed7j4yexGBtzDEypjnysWiqwUoCq5XO4s,856 +ultralytics/cfg/trackers/deepocsort.yaml,sha256=2N6cOrpVc5LYSWsVRS0NxtWM86LotVLT2iZvImwlLMA,1901 +ultralytics/cfg/trackers/fasttrack.yaml,sha256=YAeLULJ9sh54zQ3BCTY5yO53tPhqzNvFZlfTrClEPtc,1584 +ultralytics/cfg/trackers/ocsort.yaml,sha256=VduR8U2z8BRm2Banrw-fqu63T0SF2mMBnH7WmpqvtuM,1267 +ultralytics/cfg/trackers/tracktrack.yaml,sha256=M_zQz0ICP6vBz_k-h2n5SeP5Kjkhmot94Vf8YSod43o,1889 +ultralytics/data/__init__.py,sha256=KsV__8wWRNzjd-iUeIX57Pvtmet28MSxjHqG1ux-YoM,702 +ultralytics/data/__pycache__/__init__.cpython-314.pyc,, +ultralytics/data/__pycache__/annotator.cpython-314.pyc,, +ultralytics/data/__pycache__/augment.cpython-314.pyc,, +ultralytics/data/__pycache__/base.cpython-314.pyc,, +ultralytics/data/__pycache__/build.cpython-314.pyc,, +ultralytics/data/__pycache__/converter.cpython-314.pyc,, +ultralytics/data/__pycache__/dataset.cpython-314.pyc,, +ultralytics/data/__pycache__/loaders.cpython-314.pyc,, +ultralytics/data/__pycache__/split.cpython-314.pyc,, +ultralytics/data/__pycache__/split_dota.cpython-314.pyc,, +ultralytics/data/__pycache__/utils.cpython-314.pyc,, +ultralytics/data/annotator.py,sha256=iu1En-LzlR4RyR3ocftthnAog_peQHV9ForPRo_QcX8,2985 +ultralytics/data/augment.py,sha256=gjcuITBVF8mdeOzbtotdSWJspmk-77gE83Nu0lyfYJw,137544 +ultralytics/data/base.py,sha256=aRWZbN8aApEmycYRcdZbpgz4Y6IFV77S4q88NM6csCk,21125 +ultralytics/data/build.py,sha256=aH-W8UzHZHntjJtq_tSQ-Q7IbuqJj86wCrfbz8Fdlvc,17816 +ultralytics/data/converter.py,sha256=r4JcwdMWHy6LIRo1Mgk-OS2-H03D8t4h2SzIAPDiizA,41508 +ultralytics/data/dataset.py,sha256=dpAKQkdPsTt4ciZ9FMbJZFqecGBAagz7Athtw5ITaQM,50820 +ultralytics/data/loaders.py,sha256=3S6ypo1pamNk5MWBHGzeVtB-kiXBMwQQHhYY2AXnmyM,31697 +ultralytics/data/scripts/download_weights.sh,sha256=0y8XtZxOru7dVThXDFUXLHBuICgOIqZNUwpyL4Rh6lg,595 +ultralytics/data/scripts/get_coco.sh,sha256=UuJpJeo3qQpTHVINeOpmP0NYmg8PhEFE3A8J3jKrnPw,1768 +ultralytics/data/scripts/get_coco128.sh,sha256=qmRQl_hOKrsdHrTrnyQuFIH01oDz3lfaz138OgGfLt8,650 +ultralytics/data/scripts/get_imagenet.sh,sha256=hr42H16bM47iT27rgS7MpEo-GeOZAYUQXgr0B2cwn48,1705 +ultralytics/data/split.py,sha256=Ds8Etjc9lLpfNmY3mChxvg_wuuWd350QLf2mQHu7-kg,5122 +ultralytics/data/split_dota.py,sha256=dqz3DApL8eAkMCKeD8UEyR2UV8rjXbCqQzUrrd702Fk,12873 +ultralytics/data/utils.py,sha256=XqVrAiI2vBJREwkvGKFXlsOWHwqjpaFHIGHFawmnqc4,40392 +ultralytics/engine/__init__.py,sha256=lm6MckFYCPTbqIoX7w0s_daxdjNeBeKW6DXppv1-QUM,70 +ultralytics/engine/__pycache__/__init__.cpython-314.pyc,, +ultralytics/engine/__pycache__/exporter.cpython-314.pyc,, +ultralytics/engine/__pycache__/model.cpython-314.pyc,, +ultralytics/engine/__pycache__/predictor.cpython-314.pyc,, +ultralytics/engine/__pycache__/results.cpython-314.pyc,, +ultralytics/engine/__pycache__/trainer.cpython-314.pyc,, +ultralytics/engine/__pycache__/tuner.cpython-314.pyc,, +ultralytics/engine/__pycache__/validator.cpython-314.pyc,, +ultralytics/engine/exporter.py,sha256=I1MOSeVAdqWWMi0IAodhOLTYvC8heHbyioOyv9YGaFE,74087 +ultralytics/engine/model.py,sha256=4xxyMqTMTE6VtcKX0SV0ypTpPTXpuy3JZ0TlxFchUxc,55936 +ultralytics/engine/predictor.py,sha256=LcgTiMZqEKBpv-pUnHe0txE6Sfbel8SwcRaaghPgg9A,23490 +ultralytics/engine/results.py,sha256=sUc7ir53VOk3bVarmNHDcHFgRQ8DTF_AULVOJO-MDdo,71545 +ultralytics/engine/trainer.py,sha256=RINrpq_oAfyWsg_IssVcFpRfql82gWUdFYUvAuRAI1M,63982 +ultralytics/engine/tuner.py,sha256=0hP39op56BS8GX29RLdO-a5h3YTNjc-xExcp1gxSgkc,27999 +ultralytics/engine/validator.py,sha256=yaOtrP0W2a_Oh1-L92B2foDQShXRajj7wVQdsBZtqYI,18703 +ultralytics/hub/__init__.py,sha256=7LyzMguIzOnUz1CgrXYvAy93w_navq9oGuoVa3iZmII,6729 +ultralytics/hub/__pycache__/__init__.cpython-314.pyc,, +ultralytics/hub/__pycache__/auth.cpython-314.pyc,, +ultralytics/hub/__pycache__/session.cpython-314.pyc,, +ultralytics/hub/__pycache__/utils.cpython-314.pyc,, +ultralytics/hub/auth.py,sha256=sNT8jyHXTHe5egiy9WvRgyIWjI22I57enGiHkhQlJXM,6699 +ultralytics/hub/google/__init__.py,sha256=r06Ld4TuZEBOqg4iagpeN-eMAkg43T2OTxOH4_7IfkM,8445 +ultralytics/hub/google/__pycache__/__init__.cpython-314.pyc,, +ultralytics/hub/session.py,sha256=bHUYgKkhzWSzH_VHd6Tg3MrlrEwnODx5hQ4McMB1jWY,18354 +ultralytics/hub/utils.py,sha256=NyG2aQdA-OJtHhJfETZ5QQCuZ-CICIkNDneDWb5lidg,6822 +ultralytics/models/__init__.py,sha256=ZJ0u7d_x0DTU1BLrF8L-8E4QoUHtrEiQ41jIFNWLBW0,642 +ultralytics/models/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/fastsam/__init__.py,sha256=Ku89Fy_X8ok3YPEUajjUZ5i4O08jdJMjJHt-3Z99Frk,231 +ultralytics/models/fastsam/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/fastsam/__pycache__/model.cpython-314.pyc,, +ultralytics/models/fastsam/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/fastsam/__pycache__/utils.cpython-314.pyc,, +ultralytics/models/fastsam/__pycache__/val.cpython-314.pyc,, +ultralytics/models/fastsam/model.py,sha256=HN6CAHCTwMmyBCQlXx4wMBU7XqkvVHyUawRaxn2Gur8,3426 +ultralytics/models/fastsam/predict.py,sha256=01araYUgwWO_Yz_W6mTZusOj7v-S3175eUqkpbxv5P8,8853 +ultralytics/models/fastsam/utils.py,sha256=de9ieh4pBUuTNh5HTiNdRpWZhXAaSfNo3R1FNMt2GOE,879 +ultralytics/models/fastsam/val.py,sha256=MJUKx_NpFEeuqSq9ynX1FJSK6F5RhUJB-PqhQp2Q0YM,2091 +ultralytics/models/nas/__init__.py,sha256=Q4ZQak8xNWtV5YSw_pFu0anbCyDxxEAuMMDfMzu6-0s,207 +ultralytics/models/nas/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/nas/__pycache__/model.cpython-314.pyc,, +ultralytics/models/nas/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/nas/__pycache__/val.cpython-314.pyc,, +ultralytics/models/nas/model.py,sha256=as1NSv1pCDbyA-vNzD6pgmEIJVlinx4y20slISYsIoY,3910 +ultralytics/models/nas/predict.py,sha256=4nbuo9nbvnvI3qVH1ylhLCjo-7oW39MumIesm-1eU3Y,2692 +ultralytics/models/nas/val.py,sha256=MIRym3LQNDIRxnYs5xcOiLkKOgv3enZFXh5_g9Pq2hA,1543 +ultralytics/models/rtdetr/__init__.py,sha256=F4NEQqtcVKFxj97Dh7rkn2Vu3JG4Ea_nxqrBB-9P1vc,225 +ultralytics/models/rtdetr/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/rtdetr/__pycache__/model.cpython-314.pyc,, +ultralytics/models/rtdetr/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/rtdetr/__pycache__/train.cpython-314.pyc,, +ultralytics/models/rtdetr/__pycache__/val.cpython-314.pyc,, +ultralytics/models/rtdetr/model.py,sha256=jJzSh_5E__rVQO7_IkmncpC4jIdu9xNiIxlTTIaFJVw,2269 +ultralytics/models/rtdetr/predict.py,sha256=SU5UPoyxLmFCZ2IsSaYb6vux8bsrw90K79FajFDdl80,3969 +ultralytics/models/rtdetr/train.py,sha256=exPmset_CWK3ZBer3btEvDLttgMPXusMr21WsJF3bkg,3740 +ultralytics/models/rtdetr/val.py,sha256=dvDDlV44X3Yi51EJ-vln_8_JOTuX0b4pPIMcqDg2qbo,8936 +ultralytics/models/sam/__init__.py,sha256=hofz9cGGhxEWpZXX8yLp5k_LQUmWL_Shd9kfzK4U6z0,592 +ultralytics/models/sam/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/sam/__pycache__/amg.cpython-314.pyc,, +ultralytics/models/sam/__pycache__/build.cpython-314.pyc,, +ultralytics/models/sam/__pycache__/build_sam3.cpython-314.pyc,, +ultralytics/models/sam/__pycache__/model.cpython-314.pyc,, +ultralytics/models/sam/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/sam/amg.py,sha256=aYvJ7jQMkTR3X9KV7SHi3qP3yNchQggWNUurTRZwxQg,11786 +ultralytics/models/sam/build.py,sha256=si1hdoTAeHK4tNd8GhpzTTlPvis_baGDlYm-EVelkPU,12860 +ultralytics/models/sam/build_sam3.py,sha256=dFlaZIdEP7ZPPWD4p5vV8YzrnNnFvEdnigee6xF3kFU,12206 +ultralytics/models/sam/model.py,sha256=uySve5lRbaKqzpyeAMWazzrghXvEf2Hhat9NrlY8d5g,7372 +ultralytics/models/sam/modules/__init__.py,sha256=lm6MckFYCPTbqIoX7w0s_daxdjNeBeKW6DXppv1-QUM,70 +ultralytics/models/sam/modules/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/sam/modules/__pycache__/blocks.cpython-314.pyc,, +ultralytics/models/sam/modules/__pycache__/decoders.cpython-314.pyc,, +ultralytics/models/sam/modules/__pycache__/encoders.cpython-314.pyc,, +ultralytics/models/sam/modules/__pycache__/memory_attention.cpython-314.pyc,, +ultralytics/models/sam/modules/__pycache__/sam.cpython-314.pyc,, +ultralytics/models/sam/modules/__pycache__/tiny_encoder.cpython-314.pyc,, +ultralytics/models/sam/modules/__pycache__/transformer.cpython-314.pyc,, +ultralytics/models/sam/modules/__pycache__/utils.cpython-314.pyc,, +ultralytics/models/sam/modules/blocks.py,sha256=8YzIRTXywRovNb-ds6VMcvHd-gPsij336Muo8LzPV3U,44466 +ultralytics/models/sam/modules/decoders.py,sha256=MGUC8zs8FFtSacBwrp4Nva7sgYmCXpULPW-TmVXz4wg,25045 +ultralytics/models/sam/modules/encoders.py,sha256=dggzE0L6f-Yt3UCQRUBfihZtmAspgCyySb6TqdFZW1U,35521 +ultralytics/models/sam/modules/memory_attention.py,sha256=x8AjFkmK7sx4qXDCdJ_yBsxtQBqeldYNBMRe8ntpLHs,13287 +ultralytics/models/sam/modules/sam.py,sha256=91DEeUIwHhREoJ7Bxr6lD5ztonGUlnZOV9fR7x98ho8,61669 +ultralytics/models/sam/modules/tiny_encoder.py,sha256=xnroC5KLwHG4rdcto2IWyJrR7hbUUF7201-meGh5XlM,42355 +ultralytics/models/sam/modules/transformer.py,sha256=qGcEt5coPyWeaXjK1nzt-ID5HW2Q30u6tAxSyOkx3Sg,14884 +ultralytics/models/sam/modules/utils.py,sha256=HwHAf_yxMnYc1cs5bAnPIDyOkIbrUMw0qha9TB8olU0,21371 +ultralytics/models/sam/predict.py,sha256=-YXDCbX-fIn1bFlZZzInmdnwFXnVcp0UaPqsrK-HW2U,204699 +ultralytics/models/sam/sam3/__init__.py,sha256=aM4-KimnYgIFe-e5ctLT8e6k9PagvuvKFaHaagDZM7E,144 +ultralytics/models/sam/sam3/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/decoder.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/encoder.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/geometry_encoders.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/maskformer_segmentation.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/model_misc.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/necks.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/sam3_image.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/text_encoder_ve.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/vitdet.cpython-314.pyc,, +ultralytics/models/sam/sam3/__pycache__/vl_combiner.cpython-314.pyc,, +ultralytics/models/sam/sam3/decoder.py,sha256=pjNDET-GRcaDhB9oWnuAOGED_pTsM-3MGjL7LSBLj5U,23006 +ultralytics/models/sam/sam3/encoder.py,sha256=zHDR-8W5e9dH_2KG4BbXb89PnHY08V2v-bzrEOSYnjc,21751 +ultralytics/models/sam/sam3/geometry_encoders.py,sha256=C2u5RD0sCdShp6KsrratSOwpNUmqRQ72wJ409-_q5oE,17667 +ultralytics/models/sam/sam3/maskformer_segmentation.py,sha256=jf9qJj7xyTVGp7OZ5uJQF0EUD468EOnBm1PsjiTO2ug,10735 +ultralytics/models/sam/sam3/model_misc.py,sha256=rA0ZOMH-S2NrO7tyBcVDmmI0DdAmWRyR827AZIHi3nA,7926 +ultralytics/models/sam/sam3/necks.py,sha256=SmkgJZBJSMXW5XBY5rEsSu5mjtWT2IE0jH4eWtwLOFA,4767 +ultralytics/models/sam/sam3/sam3_image.py,sha256=IxwnI0TM1oRiUcOjAmOolDlFNcWTirtlIeA8QpYE6GE,13454 +ultralytics/models/sam/sam3/text_encoder_ve.py,sha256=iv8-6VA3t4yJ1M42RPjHDlFuH9P_nNRSNyaoFn2sjMw,12283 +ultralytics/models/sam/sam3/vitdet.py,sha256=Ni6fR3WBfp_FLvOP8MVdnRUk_1jOae16yjyox4cSJBg,22130 +ultralytics/models/sam/sam3/vl_combiner.py,sha256=l7IiBmljzotrNb3egQG4aGEp2RZIKugeQ25MJn3QBE4,6998 +ultralytics/models/utils/__init__.py,sha256=lm6MckFYCPTbqIoX7w0s_daxdjNeBeKW6DXppv1-QUM,70 +ultralytics/models/utils/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/utils/__pycache__/loss.cpython-314.pyc,, +ultralytics/models/utils/__pycache__/ops.cpython-314.pyc,, +ultralytics/models/utils/loss.py,sha256=JlSDpkqrndY-VrP6GoZLg4kW3d3suiHa-yEh_FE7v68,21142 +ultralytics/models/utils/ops.py,sha256=7yEfr8ESpxUwWoBw8AU_rDj69ND58ovATZHJoRJFF3g,15203 +ultralytics/models/yolo/__init__.py,sha256=eV7Rvr7cwHILl0LYbCiqNChori_FPk_yahpNge_x_6M,329 +ultralytics/models/yolo/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/yolo/__pycache__/model.cpython-314.pyc,, +ultralytics/models/yolo/classify/__init__.py,sha256=9--HVaNOfI1K7rn_rRqclL8FUAnpfeBrRqEQIaQw2xM,383 +ultralytics/models/yolo/classify/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/yolo/classify/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/yolo/classify/__pycache__/train.cpython-314.pyc,, +ultralytics/models/yolo/classify/__pycache__/val.cpython-314.pyc,, +ultralytics/models/yolo/classify/predict.py,sha256=pRNrwGVdAuDJdm-0rSY5C7MCdjmHA75tmTk8phtPACI,4202 +ultralytics/models/yolo/classify/train.py,sha256=1PeNeMtgkQJsb_fFNB-Ll6hEVqnu8uZJJNUjpw8mW2Q,10161 +ultralytics/models/yolo/classify/val.py,sha256=PSpCXXG9sSMCffRX4rM4bFlHMvq6VWVaq3ygnnx48os,10534 +ultralytics/models/yolo/detect/__init__.py,sha256=GIRsLYR-kT4JJx7lh4ZZAFGBZj0aebokuU0A7JbjDVA,257 +ultralytics/models/yolo/detect/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/yolo/detect/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/yolo/detect/__pycache__/train.cpython-314.pyc,, +ultralytics/models/yolo/detect/__pycache__/val.cpython-314.pyc,, +ultralytics/models/yolo/detect/predict.py,sha256=L01OgXeC2lbZ1oZAhHJ6lXal0nY3DMGBFDViKUGiOF0,5474 +ultralytics/models/yolo/detect/train.py,sha256=w4XGYTfd0QkHBb9sv35haasRR9onQXikp1YTu9DA00Y,12565 +ultralytics/models/yolo/detect/val.py,sha256=VQaL4b0y3cNJHP47C_UUnOtG_TAavwAm5mmSZPaWRWo,24109 +ultralytics/models/yolo/model.py,sha256=RMWkHQercvbdtqrLK3Byv8G34OqqrArXW_k3zNc1smk,19181 +ultralytics/models/yolo/obb/__init__.py,sha256=tQmpG8wVHsajWkZdmD6cjGohJ4ki64iSXQT8JY_dydo,221 +ultralytics/models/yolo/obb/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/yolo/obb/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/yolo/obb/__pycache__/train.cpython-314.pyc,, +ultralytics/models/yolo/obb/__pycache__/val.cpython-314.pyc,, +ultralytics/models/yolo/obb/predict.py,sha256=VA5yRo32IVakZ_5nY75KArfaXnZbANzTkxMVGDm-co4,2634 +ultralytics/models/yolo/obb/train.py,sha256=I7mxTdoR1-BtAgMAI9jkhzm6QuTr546jeT_ere4nzyI,3437 +ultralytics/models/yolo/obb/val.py,sha256=Vchk94T5AOUXmKncCPIOlnfmDlkzgD6oglZm6BrOc58,14563 +ultralytics/models/yolo/pose/__init__.py,sha256=_9OFLj19XwvJHBRxQtVW5CV7rvJ_3hDPE97miit0sPc,227 +ultralytics/models/yolo/pose/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/yolo/pose/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/yolo/pose/__pycache__/train.cpython-314.pyc,, +ultralytics/models/yolo/pose/__pycache__/val.cpython-314.pyc,, +ultralytics/models/yolo/pose/predict.py,sha256=a_D6-0qCRhTL_ccFe6UTNB1jgaB3Mp1wedLDQpeLPeM,3175 +ultralytics/models/yolo/pose/train.py,sha256=OpySjq7H_0zgGaHlMPne_0etFginGHzrfrMiyQ9rhwA,5073 +ultralytics/models/yolo/pose/val.py,sha256=cynNhSzq7PEpTAr1Tc-e7t8ed5YGaoYXn_9faVFFCcU,12111 +ultralytics/models/yolo/segment/__init__.py,sha256=3IThhZ1wlkY9FvmWm9cE-5-ZyE6F1FgzAtQ6jOOFzzw,275 +ultralytics/models/yolo/segment/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/yolo/segment/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/yolo/segment/__pycache__/train.cpython-314.pyc,, +ultralytics/models/yolo/segment/__pycache__/val.cpython-314.pyc,, +ultralytics/models/yolo/segment/predict.py,sha256=L4DCWXgvMwsSs_S16xE0VBa4PpnWr4baO75ZQIm94Bw,5556 +ultralytics/models/yolo/segment/train.py,sha256=o5sz1iTc5jZqc9dq1ZEdW_-KSaVBnltppJjlYbls1Hk,3042 +ultralytics/models/yolo/segment/val.py,sha256=CANSHjE1Pz4OJIEfPBCeCqT7XibZYgA_6-1lIr0vtsk,13374 +ultralytics/models/yolo/semantic/__init__.py,sha256=yzL8Q1Ap2etFevc5YPsPv57zDFXldRM0X2vRjArFP7E,323 +ultralytics/models/yolo/semantic/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/yolo/semantic/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/yolo/semantic/__pycache__/train.cpython-314.pyc,, +ultralytics/models/yolo/semantic/__pycache__/val.cpython-314.pyc,, +ultralytics/models/yolo/semantic/predict.py,sha256=froSr3vLy1B4MpSzsTw_pezKAJhHJm5BR-99KDAfAbM,4133 +ultralytics/models/yolo/semantic/train.py,sha256=Mw7U5xq-u1yq95IQCAT9guEuk-Uqub_chgwp0eqs3JU,6359 +ultralytics/models/yolo/semantic/val.py,sha256=7xDs1eraL_BYzMn9qwKpdSnRy8mKwQ0xXAky9G-rtbs,8556 +ultralytics/models/yolo/world/__init__.py,sha256=nlh8I6t8hMGz_vZg8QSlsUW1R-2eKvn9CGUoPPQEGhA,131 +ultralytics/models/yolo/world/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/yolo/world/__pycache__/train.cpython-314.pyc,, +ultralytics/models/yolo/world/__pycache__/train_world.cpython-314.pyc,, +ultralytics/models/yolo/world/train.py,sha256=Q9cYDLkS3V-ijwA1nGNRyhzfix_ghILv7wsZoVNP4YQ,7960 +ultralytics/models/yolo/world/train_world.py,sha256=yb0FVhOGAlhbc6-pqPPjEtFZpYdQyb4nwvfyskt8ZVE,9449 +ultralytics/models/yolo/yoloe/__init__.py,sha256=zaZo1_ommaxNv7mD7xpdSomNF4s8mpOcCVTXspg0ncY,760 +ultralytics/models/yolo/yoloe/__pycache__/__init__.cpython-314.pyc,, +ultralytics/models/yolo/yoloe/__pycache__/predict.cpython-314.pyc,, +ultralytics/models/yolo/yoloe/__pycache__/train.cpython-314.pyc,, +ultralytics/models/yolo/yoloe/__pycache__/train_seg.cpython-314.pyc,, +ultralytics/models/yolo/yoloe/__pycache__/val.cpython-314.pyc,, +ultralytics/models/yolo/yoloe/predict.py,sha256=LOoVkBxoUYIlZppnEVL8bJKSPoF1mhdk821ci6r74N4,7097 +ultralytics/models/yolo/yoloe/train.py,sha256=9ac3_1F6LRgCTAt6AIVcoL5kKERcjgEMpPrMgw5nDTQ,12595 +ultralytics/models/yolo/yoloe/train_seg.py,sha256=dZ3zxIOWIQ4myqrfWmQswr_r4VGtFirdMHPzHUMojg4,5097 +ultralytics/models/yolo/yoloe/val.py,sha256=VWBIeuBeK0k_U6S1t-mDHxg-t9hokqmVAERAwHccunk,9713 +ultralytics/nn/__init__.py,sha256=538LZPUKKvc3JCMgiQ4VLGqRN2ZAaVLFcQbeNNHFkEA,545 +ultralytics/nn/__pycache__/__init__.cpython-314.pyc,, +ultralytics/nn/__pycache__/autobackend.cpython-314.pyc,, +ultralytics/nn/__pycache__/distill_model.cpython-314.pyc,, +ultralytics/nn/__pycache__/tasks.cpython-314.pyc,, +ultralytics/nn/__pycache__/text_model.cpython-314.pyc,, +ultralytics/nn/autobackend.py,sha256=46Ziy87SJzVV5Q1YzXNDTHGKaxbEJV9YaKICLaFhrBE,15549 +ultralytics/nn/backends/__init__.py,sha256=BOZ1KMblYX5Pj0CP5piUuj30bU2ey5x_s6C22fnsTwM,1444 +ultralytics/nn/backends/__pycache__/__init__.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/axelera.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/base.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/coreml.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/deepx.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/executorch.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/litert.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/mnn.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/ncnn.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/onnx.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/openvino.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/paddle.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/pytorch.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/qnn.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/rknn.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/tensorflow.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/tensorrt.cpython-314.pyc,, +ultralytics/nn/backends/__pycache__/triton.cpython-314.pyc,, +ultralytics/nn/backends/axelera.py,sha256=ICm5vWpmG0Kj9n7u8NdSk1OMYMbnmcKSIhWZKsqoqzw,1808 +ultralytics/nn/backends/base.py,sha256=oKiRw45T5D_65bHmTQQPxVevChVZQimPLMJzMlC8hMQ,5610 +ultralytics/nn/backends/coreml.py,sha256=K6MwsadI1pWGvOn1g2A-C0nchc4Uc7RbbJ2F6elpHk8,2914 +ultralytics/nn/backends/deepx.py,sha256=M4Zfvbft2fSNLilO5Pr_nHwNf-jfNB5xlSa5_KvwCtQ,2795 +ultralytics/nn/backends/executorch.py,sha256=-Wi2Rc1iw6y-919AzcbM6OiQz4wkXAo3fUHUw2lZdXs,1867 +ultralytics/nn/backends/litert.py,sha256=7yRTtCVSpA-U5OM52FNkftLtviKwgJcljO6GinSVnUw,5472 +ultralytics/nn/backends/mnn.py,sha256=8egQJ1oiYZIe4QXXCwPuEhsiIFwX45eB_Qiy5-lU8ho,1954 +ultralytics/nn/backends/ncnn.py,sha256=9ra3OfcpGQbAqV0UO8VyGLZ3IGQVT1kx_q4_Q4ikT_o,2474 +ultralytics/nn/backends/onnx.py,sha256=DTUupgde6zNPegexEKJomRkI2KX5irDEezRSptLbIHo,8892 +ultralytics/nn/backends/openvino.py,sha256=G_DpC_roE9x2Er0lzWppipAPK94DW4IeT7D0XQE3e1Y,4330 +ultralytics/nn/backends/paddle.py,sha256=AQGdiQk9SHZl0waOm_5B5wD46mOMsB1spYxPtrg6MRY,3020 +ultralytics/nn/backends/pytorch.py,sha256=IqJQtsLXBN1oKO7N1ZiE_Qbq0LmKaW_cUVEOIuAR6q4,5512 +ultralytics/nn/backends/qnn.py,sha256=QlPu7L3VaYQGBAdN-kX1WIMe0n7_Ok48aYUi4JpRMkA,3402 +ultralytics/nn/backends/rknn.py,sha256=3rt5VxIAnX339fyieASo2WQZntoVe9L454SuKBZJ5Kw,2303 +ultralytics/nn/backends/tensorflow.py,sha256=_AuU2xXUs-vxioKvj7FUjtd74iEpVoLo4nw5IorT_7o,7670 +ultralytics/nn/backends/tensorrt.py,sha256=FKTIpxa-CkCzNsRmxuO8h9XhXKJqr9Q7O3ejLl3VutI,6128 +ultralytics/nn/backends/triton.py,sha256=tfPmifMNxSlH95BPiqASGQd-6k_9URGHYE9HTlQpYFc,1463 +ultralytics/nn/distill_model.py,sha256=iZufaosRy4RIb0qWRuYZFYeguwZBXiLuiL9J3YrK99Y,13607 +ultralytics/nn/modules/__init__.py,sha256=hwd6eMhbHFCcf3WYzZhZ5byX7fjh47v0iB5BrBaaZOY,3366 +ultralytics/nn/modules/__pycache__/__init__.cpython-314.pyc,, +ultralytics/nn/modules/__pycache__/activation.cpython-314.pyc,, +ultralytics/nn/modules/__pycache__/block.cpython-314.pyc,, +ultralytics/nn/modules/__pycache__/conv.cpython-314.pyc,, +ultralytics/nn/modules/__pycache__/head.cpython-314.pyc,, +ultralytics/nn/modules/__pycache__/transformer.cpython-314.pyc,, +ultralytics/nn/modules/__pycache__/utils.cpython-314.pyc,, +ultralytics/nn/modules/activation.py,sha256=J6n-CJKFK0YbhwcRDqm9zEJM9pSAEycj5quQss_3x6E,2219 +ultralytics/nn/modules/block.py,sha256=IO2ga-etf-pp24FhyR9mgTcapq7bHcrhjbGgBHg-HL0,74891 +ultralytics/nn/modules/conv.py,sha256=yALzbqFZbYkQ8rZR1Xh3tQpJYIV7l8u1og1Eodu6p3Q,21090 +ultralytics/nn/modules/head.py,sha256=VwERbYaIGCesnh50Yt-qRMM5N71o4jMkdjRZaFcp4G8,82857 +ultralytics/nn/modules/transformer.py,sha256=XWxpBPt3NyLrhYZj3k2GAXMdDvvac04vprpocHLGdIs,32713 +ultralytics/nn/modules/utils.py,sha256=DBtRQkH8Xc7SU5j287Ow9cJHiKXl65MMUhggY0EyYRE,5665 +ultralytics/nn/tasks.py,sha256=ZkwUIshXWgi20KTp8kbyeZ6UnHgp0WlC0PyJbp8tDiQ,85035 +ultralytics/nn/text_model.py,sha256=Dd0UszKWIJd-hECeTQv7ROzrg_uejxz7b-LbOXP9IuA,15429 +ultralytics/optim/__init__.py,sha256=Sl3Dx2eiaJd_u4VbmqcBqWWDF8FHnO5W0nBEL8_M_C4,130 +ultralytics/optim/__pycache__/__init__.cpython-314.pyc,, +ultralytics/optim/__pycache__/muon.cpython-314.pyc,, +ultralytics/optim/muon.py,sha256=Iu-WCU6JFpbMuLsUwXxmmh4uIZaYlD8uKapV2ygSwe4,14241 +ultralytics/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8 +ultralytics/solutions/__init__.py,sha256=Jj7OcRiYjHH-e104H4xTgjjR5W6aPB4mBRndbaSPmgU,1209 +ultralytics/solutions/__pycache__/__init__.cpython-314.pyc,, +ultralytics/solutions/__pycache__/ai_gym.cpython-314.pyc,, +ultralytics/solutions/__pycache__/analytics.cpython-314.pyc,, +ultralytics/solutions/__pycache__/config.cpython-314.pyc,, +ultralytics/solutions/__pycache__/distance_calculation.cpython-314.pyc,, +ultralytics/solutions/__pycache__/heatmap.cpython-314.pyc,, +ultralytics/solutions/__pycache__/instance_segmentation.cpython-314.pyc,, +ultralytics/solutions/__pycache__/object_blurrer.cpython-314.pyc,, +ultralytics/solutions/__pycache__/object_counter.cpython-314.pyc,, +ultralytics/solutions/__pycache__/object_cropper.cpython-314.pyc,, +ultralytics/solutions/__pycache__/parking_management.cpython-314.pyc,, +ultralytics/solutions/__pycache__/queue_management.cpython-314.pyc,, +ultralytics/solutions/__pycache__/region_counter.cpython-314.pyc,, +ultralytics/solutions/__pycache__/security_alarm.cpython-314.pyc,, +ultralytics/solutions/__pycache__/similarity_search.cpython-314.pyc,, +ultralytics/solutions/__pycache__/solutions.cpython-314.pyc,, +ultralytics/solutions/__pycache__/speed_estimation.cpython-314.pyc,, +ultralytics/solutions/__pycache__/streamlit_inference.cpython-314.pyc,, +ultralytics/solutions/__pycache__/trackzone.cpython-314.pyc,, +ultralytics/solutions/__pycache__/vision_eye.cpython-314.pyc,, +ultralytics/solutions/ai_gym.py,sha256=MuoDqkaNm8Et8L9Ze3ryH31wvlM0wsT338HEH3iwdxk,5246 +ultralytics/solutions/analytics.py,sha256=UpOwO15NVJhLzBhHLay6TCbUk8EWSUKAplQMb6KWIxI,12776 +ultralytics/solutions/config.py,sha256=S-OF0oUnRKnO85v_Naj8FjTaoMhbR7tLY03Cm5PPKY4,5857 +ultralytics/solutions/distance_calculation.py,sha256=RcpRDodEHAJUug9tobtQKt5_bySNA8NMSRiaL347Q1U,5891 +ultralytics/solutions/heatmap.py,sha256=JdAbk338nWsR7j6KT_K1ts3lAhhO5BRtc6UAJhSVSNQ,5474 +ultralytics/solutions/instance_segmentation.py,sha256=uP810WlECIRAbwgX4lXqV5l8vvLp7zvf0FG0RlaYjUE,3786 +ultralytics/solutions/object_blurrer.py,sha256=EZrv3oU68kEaahAxlhk9cF5ZKFtoVaW8bDB4Css9xe0,3981 +ultralytics/solutions/object_counter.py,sha256=omt2wsfj-E6G5wqODiQeNgaMBfecRaIHaTMJIxFDG1c,9359 +ultralytics/solutions/object_cropper.py,sha256=9NLr4egGJNPX_2wtf4nQtRo1f_wZlpv7fgJt2Xkp0j0,3551 +ultralytics/solutions/parking_management.py,sha256=Q0fEFKlv6dKKWuw_4jmWaeHQVXGppzuU7Vr_HqVYqHM,13770 +ultralytics/solutions/queue_management.py,sha256=NtrGJmxKSlu5gsQw2vmj5E9fQWgIvGDE56IQ_PfMZ_Q,4393 +ultralytics/solutions/region_counter.py,sha256=A6j9MBy8cguZabGP4jl06UQ5sOi2uxiEu6KyZprOD2k,6387 +ultralytics/solutions/security_alarm.py,sha256=c7odhqvxXOSiLVk1W1deAzDyJ5bQukS_shHayVbl8gc,6273 +ultralytics/solutions/similarity_search.py,sha256=ARkqa47WtSRgdcAeOvX64zLIWDcD3sFcec7O-u17CJ4,9907 +ultralytics/solutions/solutions.py,sha256=m8wJ1UXOu2LlxjRJyycif6Tm0Ix5xgsI2WN_LCbUGuc,37600 +ultralytics/solutions/speed_estimation.py,sha256=-DAhcWd8b4acfnbeUCSvqkUGRwL5ogkcphACQCkMV6o,5861 +ultralytics/solutions/streamlit_inference.py,sha256=MyT3xItA0ZygnHEircYWg28hS0QV2jnU3LKTHPvNAo0,13352 +ultralytics/solutions/templates/similarity-search.html,sha256=epX8XDLasOUx8ISPuKP9M0SRUjRcv_FE1r_EG3QpBtw,4177 +ultralytics/solutions/trackzone.py,sha256=An9tNETx_5DY6xy2ZI8mcrNMXRzEezjm3HRseoO7R2A,3910 +ultralytics/solutions/vision_eye.py,sha256=bSXmJ93DyLu4_CWgbF3GkHzh_VpiEmkK5vVJDPPGzI4,2982 +ultralytics/trackers/__init__.py,sha256=-1tfYXBxpb-y2rYB7H5oD2R0bxbwEfKXIRdz8dzW5JI,482 +ultralytics/trackers/__pycache__/__init__.cpython-314.pyc,, +ultralytics/trackers/__pycache__/basetrack.cpython-314.pyc,, +ultralytics/trackers/__pycache__/bot_sort.cpython-314.pyc,, +ultralytics/trackers/__pycache__/byte_tracker.cpython-314.pyc,, +ultralytics/trackers/__pycache__/deep_oc_sort.cpython-314.pyc,, +ultralytics/trackers/__pycache__/fast_tracker.cpython-314.pyc,, +ultralytics/trackers/__pycache__/oc_sort.cpython-314.pyc,, +ultralytics/trackers/__pycache__/track.cpython-314.pyc,, +ultralytics/trackers/__pycache__/track_tracker.cpython-314.pyc,, +ultralytics/trackers/basetrack.py,sha256=nvCUuaVP8vUskRi1vRovhLozGqVXJ39UGG3hFyjC9-g,3752 +ultralytics/trackers/bot_sort.py,sha256=mzLPzJ8LUtAfTLz3bOJ535Bun2yyoQvfkrPc33sr6dk,9914 +ultralytics/trackers/byte_tracker.py,sha256=AMfB_4sQdvyJNHBxUZrgsGowv84A3e2UgmYg4Gixko8,22774 +ultralytics/trackers/deep_oc_sort.py,sha256=71i4eEbr9CBvVHPOnngsVBJoLiIiN4stdgXv6xNlNoA,12131 +ultralytics/trackers/fast_tracker.py,sha256=p5WsgyQdu5YrjLHyNJ6pJmVOaK0cOMpsh0Of_T9XirY,17615 +ultralytics/trackers/oc_sort.py,sha256=1WPlyaNvtf8V01-SuuBJOaLxd0uq_z4oetFfuX0G9lI,15425 +ultralytics/trackers/track.py,sha256=G8hnMhWwXbC5TR4LiTDDT2JW15brP2MyBqtp0WL1k4M,5679 +ultralytics/trackers/track_tracker.py,sha256=-00hMzXO11r82mCONiK3FN_-vSxV73dv6MJhlWdJtb0,26992 +ultralytics/trackers/utils/__init__.py,sha256=lm6MckFYCPTbqIoX7w0s_daxdjNeBeKW6DXppv1-QUM,70 +ultralytics/trackers/utils/__pycache__/__init__.cpython-314.pyc,, +ultralytics/trackers/utils/__pycache__/gmc.cpython-314.pyc,, +ultralytics/trackers/utils/__pycache__/kalman_filter.cpython-314.pyc,, +ultralytics/trackers/utils/__pycache__/matching.cpython-314.pyc,, +ultralytics/trackers/utils/__pycache__/reid.cpython-314.pyc,, +ultralytics/trackers/utils/__pycache__/stracks.cpython-314.pyc,, +ultralytics/trackers/utils/gmc.py,sha256=nDqdj8Uy3mu-1u0_PswtAps8cigdsH6NpOIHT6U4sLA,13800 +ultralytics/trackers/utils/kalman_filter.py,sha256=qajNBNN9aOHp-2aQctXcl5lw0Jee20zW0KRARgQ3P64,22597 +ultralytics/trackers/utils/matching.py,sha256=DF3N3edc40PKliYnPj2V4uoGG89p8uCsDvUZrBqv_gc,7789 +ultralytics/trackers/utils/reid.py,sha256=8gqRJBvLzYanFcmteoI5v6uAk8aauneZLiWtHkqHDfw,7440 +ultralytics/trackers/utils/stracks.py,sha256=shlxdya-Qu7bXHEUKPfKUQUPitciKRpDzNmBlPWxzk4,7522 +ultralytics/utils/__init__.py,sha256=wxeotoLBMD3hpnvjxtb8BZ8v9M34g_EJJRdBS3ukUHQ,57905 +ultralytics/utils/__pycache__/__init__.cpython-314.pyc,, +ultralytics/utils/__pycache__/autobatch.cpython-314.pyc,, +ultralytics/utils/__pycache__/autodevice.cpython-314.pyc,, +ultralytics/utils/__pycache__/benchmarks.cpython-314.pyc,, +ultralytics/utils/__pycache__/checks.cpython-314.pyc,, +ultralytics/utils/__pycache__/cpu.cpython-314.pyc,, +ultralytics/utils/__pycache__/dist.cpython-314.pyc,, +ultralytics/utils/__pycache__/downloads.cpython-314.pyc,, +ultralytics/utils/__pycache__/errors.cpython-314.pyc,, +ultralytics/utils/__pycache__/events.cpython-314.pyc,, +ultralytics/utils/__pycache__/files.cpython-314.pyc,, +ultralytics/utils/__pycache__/git.cpython-314.pyc,, +ultralytics/utils/__pycache__/instance.cpython-314.pyc,, +ultralytics/utils/__pycache__/logger.cpython-314.pyc,, +ultralytics/utils/__pycache__/loss.cpython-314.pyc,, +ultralytics/utils/__pycache__/metrics.cpython-314.pyc,, +ultralytics/utils/__pycache__/nms.cpython-314.pyc,, +ultralytics/utils/__pycache__/ops.cpython-314.pyc,, +ultralytics/utils/__pycache__/patches.cpython-314.pyc,, +ultralytics/utils/__pycache__/plotting.cpython-314.pyc,, +ultralytics/utils/__pycache__/tal.cpython-314.pyc,, +ultralytics/utils/__pycache__/torch_utils.cpython-314.pyc,, +ultralytics/utils/__pycache__/tqdm.cpython-314.pyc,, +ultralytics/utils/__pycache__/triton.cpython-314.pyc,, +ultralytics/utils/__pycache__/tuner.cpython-314.pyc,, +ultralytics/utils/__pycache__/uploads.cpython-314.pyc,, +ultralytics/utils/autobatch.py,sha256=nwixv8vPmIXHPq_A8nF1cxWobdduk_5nDjmJTeVu7_8,5675 +ultralytics/utils/autodevice.py,sha256=ZC-C_hfuHsyx3VcNrk85U4Rtyr0rWoSWhJlOuOkWHXQ,9006 +ultralytics/utils/benchmarks.py,sha256=o1cC5TnGTrcPsMEVhMLDHG5l1C9iY4OuuFIT_DPi-G4,28409 +ultralytics/utils/callbacks/__init__.py,sha256=hzL63Rce6VkZhP4Lcim9LKjadixaQG86nKqPhk7IkS0,242 +ultralytics/utils/callbacks/__pycache__/__init__.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/base.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/clearml.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/comet.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/dvc.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/hub.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/mlflow.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/neptune.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/platform.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/raytune.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/tensorboard.cpython-314.pyc,, +ultralytics/utils/callbacks/__pycache__/wb.cpython-314.pyc,, +ultralytics/utils/callbacks/base.py,sha256=pqpFn3mZsLa8sXBkxabjHd6S098Ngf3-qpoyES29GPo,7468 +ultralytics/utils/callbacks/clearml.py,sha256=AH7JwdZ8qtzlj11Q3MBzoUAzI6fYTb5drqAdpmxpSC8,5902 +ultralytics/utils/callbacks/comet.py,sha256=NNfwkYRYKF4lgml931aXOZsOOilAIm1uvH34dlAyWLA,25146 +ultralytics/utils/callbacks/dvc.py,sha256=DzT-Q314fap4g7rzXWeTfKw_5aTfnJIkqACAaFGXeg8,7494 +ultralytics/utils/callbacks/hub.py,sha256=tOFYxZLOO7o3cJcrJvz3Y-RqzonXZAy3yZZBVexRsyo,4249 +ultralytics/utils/callbacks/mlflow.py,sha256=5WaeD3Kfp7x3j7wJwb_kjPxs24IjLRhN3l9wVHc09-0,6744 +ultralytics/utils/callbacks/neptune.py,sha256=_vt3cMwDHCR-LyT3KtRikGpj6AG11oQ-skUUUUdZ74o,4391 +ultralytics/utils/callbacks/platform.py,sha256=PvGS7Ay8DU5yi3Lnq8wJ4rUY3aNaNI6HHigZufVR_7Q,20278 +ultralytics/utils/callbacks/raytune.py,sha256=Y0dFyNZVRuFovSh7nkgUIHTQL3xIXOACElgHuYbg_5I,1278 +ultralytics/utils/callbacks/tensorboard.py,sha256=K7b6KtC7rimfzqFu-NDZ_55Tbd7eC6TckqQdTNPuQ6U,5039 +ultralytics/utils/callbacks/wb.py,sha256=PUdXGIFCRpTB3l0ElBWiOBa855Np6362PRWZBUtvgxE,8172 +ultralytics/utils/checks.py,sha256=lLTAhhAzUXl5jnVyVgJ8LzmyI6HBSMF98LLocJ-2F-U,42574 +ultralytics/utils/cpu.py,sha256=OksKOlX93AsbSsFuoYvLXRXgpkOibrZSwQyW6lipt4Q,3493 +ultralytics/utils/dist.py,sha256=2kKQ5M6TGHFiTzYvkoAFk7EalpcJ7muZH980RqrMd4c,5003 +ultralytics/utils/downloads.py,sha256=p3F3iGI0-L74E4HHbuvfgcNYI5OGJpQgFq-_xtx4FDo,25277 +ultralytics/utils/errors.py,sha256=dUZcTWpbJJHqEuWHM6IbeoJJ4TzA_yHBP8E7tEEpBVs,1388 +ultralytics/utils/events.py,sha256=kfu8j-JKG7dNn8AHNkylUn1g4dc_gyTb40gy2SWk_lk,4911 +ultralytics/utils/export/__init__.py,sha256=zCgN-9-KWp18ouy5YsQ_7a81gKnosIerhc4S3o_2wxg,918 +ultralytics/utils/export/__pycache__/__init__.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/axelera.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/coreml.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/deepx.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/engine.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/executorch.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/imx.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/litert.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/mnn.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/ncnn.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/onnx.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/openvino.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/paddle.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/qnn.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/rknn.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/tensorflow.cpython-314.pyc,, +ultralytics/utils/export/__pycache__/torchscript.cpython-314.pyc,, +ultralytics/utils/export/axelera.py,sha256=TweXW60VBlzvcvilaWmqJzP5b54wZGwvRVn24v0jWYI,5545 +ultralytics/utils/export/coreml.py,sha256=BDKtWWz4Nm9c3Bi0nx8yoCaozjHgAdkkb96Z8KgfqdU,11523 +ultralytics/utils/export/deepx.py,sha256=gjvFL5Gm_dwKBjCsAQEqDSuUx25Vf9TLwxCvxtroa4o,2713 +ultralytics/utils/export/engine.py,sha256=PX2bqXJM90FuHcL2812Uogdzk0wRCme2Pgiu9m9G29o,17931 +ultralytics/utils/export/executorch.py,sha256=AcIAh0kLWLO2EN2DK2jw5lkRNyNBgbK68XMMDg1E-f4,2829 +ultralytics/utils/export/imx.py,sha256=ag2sBHgV588RUHH7UKkU1OueKPM1gafOg5N5OiEJjwQ,15243 +ultralytics/utils/export/litert.py,sha256=FrCiOJqvHvGfwikVocPKqd_m6Vij_ScJCLtjUGgE4Ns,8493 +ultralytics/utils/export/mnn.py,sha256=bc-OoP3mFBP1L4oD5b1vP9FST0kLkXRMd2FMnWgTfBs,1892 +ultralytics/utils/export/ncnn.py,sha256=ZbEIZpfe11H7oumoHKvR0WqOfjPJgw10EkIwZIX4Lv4,2457 +ultralytics/utils/export/onnx.py,sha256=XCVK9dicgpjY0n6qphq8Wzj4GsiBRbMXA7iCThJHDxM,2118 +ultralytics/utils/export/openvino.py,sha256=lPtPGa_PmA2znzUJLM1F-nWQTxY8cR271xuAkFn2mzo,2657 +ultralytics/utils/export/paddle.py,sha256=UsclTfcKSGDnDcFGbepAgJvdpvhDKnhmWshMYy34nQw,1812 +ultralytics/utils/export/qnn.py,sha256=xYcJ7T7hacJCMpUQD5d1Z1zzY64xdgBp3_PWzXcxiFk,8108 +ultralytics/utils/export/rknn.py,sha256=NIIzNchOdow84KuGBRLasndAfDLfRToMrUG3voLvn7c,3434 +ultralytics/utils/export/tensorflow.py,sha256=oejAd88q_qSPXHr5TvxUS_LA_YhuguUoe0yyfR3pDIo,12536 +ultralytics/utils/export/torchscript.py,sha256=fQ9R26g5dmkmoo8U3Xef7gSL6iN3Ow6ub78KlNEHPO8,1627 +ultralytics/utils/files.py,sha256=6Tb_-9a6PJ8HqzUH7GNhJSy05NOqrTjz1UyU_Virx6k,8194 +ultralytics/utils/git.py,sha256=pAQMO940AN7k84dose1YxxP5Q9XnQ5QwFB1QVz9t6mI,6961 +ultralytics/utils/instance.py,sha256=1J96OcekaHAzU6MmzhslQ4LIOsn2fjrvN4A1usf0VsE,19458 +ultralytics/utils/logger.py,sha256=wkB8-qOs4vaCF7BFwFgNAXDClXZWWE36BQgV4o7n-uA,25796 +ultralytics/utils/loss.py,sha256=8zHE_0O3PY7KcE28BSPPLi-O2Se8p4nMctKCucvApB8,64366 +ultralytics/utils/metrics.py,sha256=mLGJb9Nz-tnUv7W9mj-Bp9YSXIwEksUMveaNwi_e7xk,82605 +ultralytics/utils/nms.py,sha256=HqIpiuzBr1lFNm_kemp2TnyNUKPhav_Vwbucj4RuyNc,14376 +ultralytics/utils/ops.py,sha256=LXtA4hxoQ3MTcP1tZhWIgglXgpDhusx2i2Ww-yACKGs,31385 +ultralytics/utils/patches.py,sha256=aUAfRBneYVSzYozmF4bVV2HIik20vCEdOfL-cbVHlf4,9560 +ultralytics/utils/plotting.py,sha256=ppbf5uGvMoHcz4MXQFIWp1x3ri4pMF_6QjkGISa0d2c,52948 +ultralytics/utils/tal.py,sha256=dViDwOf6qlggWgMDDhEYcoKfpM5QdDi_RFtKejA1DD8,24671 +ultralytics/utils/torch_utils.py,sha256=_2tOtMqugg0dKAiUmpH1WR1rjLYA9mZYWVJ210YSrYQ,42511 +ultralytics/utils/tqdm.py,sha256=IFqrg15NUngaWJ6kXkcGWl47RhOmsvBNu1d5-TcM9Pg,16402 +ultralytics/utils/triton.py,sha256=qfA4VJvGVUjUj-uuQzHc0eZ989-iZkdMmuqo4WnotU0,5215 +ultralytics/utils/tuner.py,sha256=fHaIMz8gGwZEylSEb10sCHqVeoBCZ1BsxZtccPw19Hs,20840 +ultralytics/utils/uploads.py,sha256=wLIIdzQmJYk3yyV-JC3GP2hxuEZ_ypNZeuh59QZuc7o,3809 diff --git a/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/REQUESTED b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/WHEEL b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..14a883f292bc96b20c2b76a3081991f2676523a9 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/entry_points.txt b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..48edf7be907d0c391c22e4e3a836e170a30b946b --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +ultralytics = ultralytics.cfg:entrypoint +yolo = ultralytics.cfg:entrypoint diff --git a/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/licenses/LICENSE b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..be3f7b28e564e7dd05eaf59d64adba1a4065ac0e --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/licenses/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/top_level.txt b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..e1177227960fd7edd00c10e611224168632b2856 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics-8.4.83.dist-info/top_level.txt @@ -0,0 +1 @@ +ultralytics diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/files.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/files.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8aa08b722936c3fa657b2928c5b0931f0d8d591 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/files.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/git.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/git.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ae95170e9f0372bf72327701d304612a517c51f Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/git.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/instance.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/instance.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8aef8240db9fa86b9364550ad7e6653d865150a0 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/instance.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/logger.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/logger.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04162da6646f8262532dafb90a60564c95f9e565 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/logger.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/loss.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/loss.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6ac0b67941fa344d05746e2f9ec8a78a3614586 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/loss.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/metrics.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/metrics.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f4c6efd5acf1f6b8dc64f08c155dc6cc6907a66 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/metrics.cpython-314.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40456bcab958ad592b9e08bba95131a6e23467fb19429af5a01d99e971c7a032 +size 125188 diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/nms.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/nms.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b51ca15a31ed81cdf0fd83a59d4f5b982b7202f8 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/nms.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/ops.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/ops.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1692fa28b9da7cbee87374cd34659bc2a9701e18 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/ops.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/patches.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/patches.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18dea9d8b671b04a80bbaaf59a03f490d6e671f5 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/patches.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/plotting.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/plotting.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fa862803fd3476b5a0304f7994e86ea2b7ac913 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/plotting.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/tal.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/tal.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77fb0a7e22e38c38cac0beea6d68287d9c0d180c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/tal.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/torch_utils.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/torch_utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef8e43b2738e6f78e5d21c29245b70f7a5d88372 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/torch_utils.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/tqdm.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/tqdm.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4651a80ee7ad9c6d29ee51a32d1a9e90123ef06 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/tqdm.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/triton.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/triton.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d097c6ab249f8985f3df708ff3e4adcd8a2bb45 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/triton.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/tuner.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/tuner.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..092cb50ba2af600d93ae7fe0fabac839e6f03eeb Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/tuner.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/uploads.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/uploads.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28f7c3851bffec74a10adffa4c558ffa1cc4f408 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/__pycache__/uploads.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/autobatch.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/autobatch.py new file mode 100644 index 0000000000000000000000000000000000000000..abcb227db504346435ff138012d1cea89d3a0616 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/autobatch.py @@ -0,0 +1,131 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license +"""Functions for estimating the best YOLO batch size to use a fraction of the available CUDA memory in PyTorch.""" + +from __future__ import annotations + +import os +from copy import deepcopy + +import numpy as np +import torch + +from ultralytics.utils import DEFAULT_CFG, LOGGER, colorstr +from ultralytics.utils.torch_utils import autocast, profile_ops + + +def check_train_batch_size( + model: torch.nn.Module, + imgsz: int = 640, + amp: bool = True, + batch: int | float = -1, + max_num_obj: int = 1, + dataset_size: int = 0, +) -> int: + """Compute optimal YOLO training batch size using the autobatch() function. + + Args: + model (torch.nn.Module): YOLO model to check batch size for. + imgsz (int, optional): Image size used for training. + amp (bool, optional): Use automatic mixed precision if True. + batch (int | float, optional): Fraction of GPU memory to use. If -1, use default. + max_num_obj (int, optional): The maximum number of objects from dataset. + dataset_size (int, optional): Total number of training images. If > 0, batch size will not exceed this value. + + Returns: + (int): Optimal batch size computed using the autobatch() function. + + Notes: + If 0.0 < batch < 1.0, it's used as the fraction of GPU memory to use. + Otherwise, a default fraction of 0.6 is used. + """ + with autocast(enabled=amp): + return autobatch( + deepcopy(model).train(), + imgsz, + fraction=batch if 0.0 < batch < 1.0 else 0.6, + max_num_obj=max_num_obj, + dataset_size=dataset_size, + ) + + +def autobatch( + model: torch.nn.Module, + imgsz: int = 640, + fraction: float = 0.60, + batch_size: int = DEFAULT_CFG.batch, + max_num_obj: int = 1, + dataset_size: int = 0, +) -> int: + """Automatically estimate the best YOLO batch size to use a fraction of the available CUDA memory. + + Args: + model (torch.nn.Module): YOLO model to compute batch size for. + imgsz (int, optional): The image size used as input for the YOLO model. + fraction (float, optional): The fraction of available CUDA memory to use. + batch_size (int, optional): The default batch size to use if an error is detected. + max_num_obj (int, optional): The maximum number of objects from dataset. + dataset_size (int, optional): Total number of training images. If > 0, batch size will not exceed this value. + + Returns: + (int): The optimal batch size. + """ + # Check device + prefix = colorstr("AutoBatch: ") + LOGGER.info(f"{prefix}Computing optimal batch size for imgsz={imgsz} at {fraction * 100}% CUDA memory utilization.") + device = next(model.parameters()).device # get model device + if device.type in {"cpu", "mps"}: + LOGGER.warning(f"{prefix}intended for CUDA devices, using default batch-size {batch_size}") + return batch_size + if torch.backends.cudnn.benchmark: + LOGGER.warning(f"{prefix}Requires torch.backends.cudnn.benchmark=False, using default batch-size {batch_size}") + return batch_size + + # Inspect CUDA memory + gb = 1 << 30 # bytes to GiB (1024 ** 3) + d = f"CUDA:{os.getenv('CUDA_VISIBLE_DEVICES', '0').strip()[0]}" # 'CUDA:0' + properties = torch.cuda.get_device_properties(device) # device properties + t = properties.total_memory / gb # GiB total + r = torch.cuda.memory_reserved(device) / gb # GiB reserved + a = torch.cuda.memory_allocated(device) / gb # GiB allocated + f = t - (r + a) # GiB free + LOGGER.info(f"{prefix}{d} ({properties.name}) {t:.2f}G total, {r:.2f}G reserved, {a:.2f}G allocated, {f:.2f}G free") + + # Profile batch sizes + batch_sizes = [1, 2, 4, 8, 16] if t < 16 else [1, 2, 4, 8, 16, 32, 64] + if dataset_size > 0: + batch_sizes = [b for b in batch_sizes if b <= dataset_size] + ch = model.yaml.get("channels", 3) + try: + img = [torch.empty(b, ch, imgsz, imgsz) for b in batch_sizes] + results = profile_ops(img, model, n=1, device=device, max_num_obj=max_num_obj) + + # Fit a solution + xy = [ + [x, y[2]] + for i, (x, y) in enumerate(zip(batch_sizes, results)) + if y # valid result + and isinstance(y[2], (int, float)) # is numeric + and 0 < y[2] < t # between 0 and GPU limit + and (i == 0 or not results[i - 1] or y[2] > results[i - 1][2]) # first item or increasing memory + ] + fit_x, fit_y = zip(*xy) if xy else ([], []) + p = np.polyfit(fit_x, fit_y, deg=1) # first-degree (linear) polynomial fit + b = int((round(f * fraction) - p[1]) / p[0]) # y intercept (optimal batch size) + if None in results: # some sizes failed + i = results.index(None) # first fail index + if b >= batch_sizes[i]: # y intercept above failure point + b = batch_sizes[max(i - 1, 0)] # select prior safe point + if b < 1 or b > 1024: # b outside of safe range + LOGGER.warning(f"{prefix}batch={b} outside safe range, using default batch-size {batch_size}.") + b = batch_size + if dataset_size > 0: + b = min(b, dataset_size) + + fraction = (np.polyval(p, b) + r + a) / t # predicted fraction + LOGGER.info(f"{prefix}Using batch-size {b} for {d} {t * fraction:.2f}G/{t:.2f}G ({fraction * 100:.0f}%) ✅") + return b + except Exception as e: + LOGGER.warning(f"{prefix}error detected: {e}, using default batch-size {batch_size}.") + return batch_size + finally: + torch.cuda.empty_cache() diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/autodevice.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/autodevice.py new file mode 100644 index 0000000000000000000000000000000000000000..59aa47e383479f9ed3eebb316c925e60d05eb5f6 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/autodevice.py @@ -0,0 +1,207 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import random +from typing import Any + +from ultralytics.utils import LOGGER + + +class GPUInfo: + """Manages NVIDIA GPU information via pynvml with robust error handling. + + Provides methods to query detailed GPU statistics (utilization, memory, temp, power) and select the most idle GPUs + based on configurable criteria. It safely handles the absence or initialization failure of the pynvml library by + logging warnings and disabling related features, preventing application crashes. + + Includes fallback logic using `torch.cuda` for basic device counting if NVML is unavailable during GPU + selection. Manages NVML initialization and shutdown internally. + + Attributes: + pynvml (module | None): The `pynvml` module if successfully imported and initialized, otherwise `None`. + nvml_available (bool): Indicates if `pynvml` is ready for use. True if `nvmlInit()` succeeded, False otherwise. + gpu_stats (list[dict[str, Any]]): A list of dictionaries, each holding stats for one GPU, populated on + initialization and by `refresh_stats()`. Keys include: 'index', 'name', 'utilization' (%), 'memory_used' (MiB), + 'memory_total' (MiB), 'memory_free' (MiB), 'temperature' (C), 'power_draw' (W), 'power_limit' (W or 'N/A'). + Empty if NVML is unavailable or queries fail. + + Methods: + refresh_stats: Refresh the internal gpu_stats list by querying NVML. + print_status: Print GPU status in a compact table format using current stats. + select_idle_gpu: Select the most idle GPUs based on utilization and free memory. + shutdown: Shut down NVML if it was initialized. + + Examples: + Initialize GPUInfo and print status + >>> gpu_info = GPUInfo() + >>> gpu_info.print_status() + + Select idle GPUs with minimum memory requirements + >>> selected = gpu_info.select_idle_gpu(count=2, min_memory_fraction=0.2) + >>> print(f"Selected GPU indices: {selected}") + """ + + def __init__(self): + """Initialize GPUInfo, attempting to import and initialize pynvml.""" + self.pynvml: Any | None = None + self.nvml_available: bool = False + self.gpu_stats: list[dict[str, Any]] = [] + + try: + import pynvml # scoped as slow import + + self.pynvml = pynvml + pynvml.nvmlInit() + self.nvml_available = True + self.refresh_stats() + except Exception as e: + LOGGER.warning(f"Failed to initialize pynvml, GPU stats disabled: {e}") + + def __del__(self): + """Ensure NVML is shut down when the object is garbage collected.""" + self.shutdown() + + def shutdown(self): + """Shut down NVML if it was initialized.""" + if self.nvml_available and self.pynvml: + try: + self.pynvml.nvmlShutdown() + except Exception: + pass + self.nvml_available = False + + def refresh_stats(self): + """Refresh the internal gpu_stats list by querying NVML.""" + self.gpu_stats = [] + if not self.nvml_available or not self.pynvml: + return + + try: + device_count = self.pynvml.nvmlDeviceGetCount() + self.gpu_stats.extend(self._get_device_stats(i) for i in range(device_count)) + except Exception as e: + LOGGER.warning(f"Error during device query: {e}") + self.gpu_stats = [] + + def _get_device_stats(self, index: int) -> dict[str, Any]: + """Get stats for a single GPU device.""" + handle = self.pynvml.nvmlDeviceGetHandleByIndex(index) + memory = self.pynvml.nvmlDeviceGetMemoryInfo(handle) + util = self.pynvml.nvmlDeviceGetUtilizationRates(handle) + + def safe_get(func, *args, default=-1, divisor=1): + try: + val = func(*args) + return val // divisor if divisor != 1 and isinstance(val, (int, float)) else val + except Exception: + return default + + temp_type = getattr(self.pynvml, "NVML_TEMPERATURE_GPU", -1) + + return { + "index": index, + "name": self.pynvml.nvmlDeviceGetName(handle), + "utilization": util.gpu if util else -1, + "memory_used": memory.used >> 20 if memory else -1, # Convert bytes to MiB + "memory_total": memory.total >> 20 if memory else -1, + "memory_free": memory.free >> 20 if memory else -1, + "temperature": safe_get(self.pynvml.nvmlDeviceGetTemperature, handle, temp_type), + "power_draw": safe_get(self.pynvml.nvmlDeviceGetPowerUsage, handle, divisor=1000), # Convert mW to W + "power_limit": safe_get(self.pynvml.nvmlDeviceGetEnforcedPowerLimit, handle, divisor=1000), + } + + def print_status(self): + """Print GPU status in a compact table format using current stats.""" + self.refresh_stats() + if not self.gpu_stats: + LOGGER.warning("No GPU stats available.") + return + + stats = self.gpu_stats + name_len = max(len(gpu.get("name", "N/A")) for gpu in stats) + hdr = f"{'Idx':<3} {'Name':<{name_len}} {'Util':>6} {'Mem (MiB)':>15} {'Temp':>5} {'Pwr (W)':>10}" + LOGGER.info(f"\n--- GPU Status ---\n{hdr}\n{'-' * len(hdr)}") + + for gpu in stats: + u = f"{gpu['utilization']:>5}%" if gpu["utilization"] >= 0 else " N/A " + m = f"{gpu['memory_used']:>6}/{gpu['memory_total']:<6}" if gpu["memory_used"] >= 0 else " N/A / N/A " + t = f"{gpu['temperature']}C" if gpu["temperature"] >= 0 else " N/A " + p = f"{gpu['power_draw']:>3}/{gpu['power_limit']:<3}" if gpu["power_draw"] >= 0 else " N/A " + + LOGGER.info(f"{gpu.get('index'):<3d} {gpu.get('name', 'N/A'):<{name_len}} {u:>6} {m:>15} {t:>5} {p:>10}") + + LOGGER.info(f"{'-' * len(hdr)}\n") + + def select_idle_gpu( + self, count: int = 1, min_memory_fraction: float = 0, min_util_fraction: float = 0 + ) -> list[int]: + """Select the most idle GPUs based on utilization and free memory. + + Args: + count (int): The number of idle GPUs to select. + min_memory_fraction (float): Minimum free memory required as a fraction of total memory. + min_util_fraction (float): Minimum free utilization rate required from 0.0 - 1.0. + + Returns: + (list[int]): Indices of the selected GPUs, sorted by idleness (lowest utilization first). + + Notes: + Returns fewer than 'count' if not enough qualify or exist. + Returns empty list if NVML stats are unavailable or no GPUs meet the criteria. + """ + assert min_memory_fraction <= 1.0, f"min_memory_fraction must be <= 1.0, got {min_memory_fraction}" + assert min_util_fraction <= 1.0, f"min_util_fraction must be <= 1.0, got {min_util_fraction}" + criteria = ( + f"free memory >= {min_memory_fraction * 100:.1f}% and free utilization >= {min_util_fraction * 100:.1f}%" + ) + LOGGER.info(f"Searching for {count} idle GPUs with {criteria}...") + + if count <= 0: + return [] + + self.refresh_stats() + if not self.gpu_stats: + LOGGER.warning("NVML stats unavailable.") + return [] + + # Filter and sort eligible GPUs + eligible_gpus = [ + gpu + for gpu in self.gpu_stats + if gpu.get("memory_free", 0) / gpu.get("memory_total", 1) >= min_memory_fraction + and (100 - gpu.get("utilization", 100)) >= min_util_fraction * 100 + ] + # Random tiebreaker prevents race conditions when multiple processes start simultaneously + # and all GPUs appear equally idle (same utilization and free memory) + eligible_gpus.sort(key=lambda x: (x.get("utilization", 101), -x.get("memory_free", 0), random.random())) + + # Select top 'count' indices + selected = [gpu["index"] for gpu in eligible_gpus[:count]] + + if selected: + if len(selected) < count: + LOGGER.warning(f"Requested {count} GPUs but only {len(selected)} met the idle criteria.") + LOGGER.info(f"Selected idle CUDA devices {selected}") + else: + LOGGER.warning(f"No GPUs met criteria ({criteria}).") + + return selected + + +if __name__ == "__main__": + required_free_mem_fraction = 0.2 # Require 20% free VRAM + required_free_util_fraction = 0.2 # Require 20% free utilization + num_gpus_to_select = 1 + + gpu_info = GPUInfo() + gpu_info.print_status() + + if selected := gpu_info.select_idle_gpu( + count=num_gpus_to_select, + min_memory_fraction=required_free_mem_fraction, + min_util_fraction=required_free_util_fraction, + ): + print(f"\n==> Using selected GPU indices: {selected}") + devices = [f"cuda:{idx}" for idx in selected] + print(f" Target devices: {devices}") diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/benchmarks.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/benchmarks.py new file mode 100644 index 0000000000000000000000000000000000000000..e45ff188f7249981abd92bcae8aa88d9a86f9612 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/benchmarks.py @@ -0,0 +1,618 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license +""" +Benchmark YOLO model formats for speed and accuracy. + +Usage: + from ultralytics.utils.benchmarks import ProfileModels, benchmark + ProfileModels(['yolo26n.yaml', 'yolov8s.yaml']).run() + benchmark(model='yolo26n.pt', imgsz=160) + +Format | `format=argument` | Model +--- | --- | --- +PyTorch | - | yolo26n.pt +TorchScript | `torchscript` | yolo26n.torchscript +ONNX | `onnx` | yolo26n.onnx +OpenVINO | `openvino` | yolo26n_openvino_model/ +TensorRT | `engine` | yolo26n.engine +CoreML | `coreml` | yolo26n.mlpackage +TensorFlow SavedModel | `saved_model` | yolo26n_saved_model/ +TensorFlow GraphDef | `pb` | yolo26n.pb +TensorFlow Edge TPU | `edgetpu` | yolo26n_edgetpu.tflite +PaddlePaddle | `paddle` | yolo26n_paddle_model/ +MNN | `mnn` | yolo26n.mnn +NCNN | `ncnn` | yolo26n_ncnn_model/ +IMX | `imx` | yolo26n_imx_model/ +RKNN | `rknn` | yolo26n_rknn_model/ +ExecuTorch | `executorch` | yolo26n_executorch_model/ +Axelera AI | `axelera` | yolo26n_axelera_model/ +""" + +from __future__ import annotations + +import glob +import platform +import shutil +import time +from copy import deepcopy +from pathlib import Path + +import numpy as np +import torch.cuda + +from ultralytics import YOLO, YOLOWorld +from ultralytics.cfg import TASK2DATA, TASK2METRIC +from ultralytics.engine.exporter import export_formats +from ultralytics.nn.modules import Segment26 +from ultralytics.utils import ( + ARM64, + ASSETS, + IS_DOCKER, + IS_JETSON, + LINUX, + LOGGER, + MACOS, + TQDM, + WEIGHTS_DIR, + is_github_action_running, +) +from ultralytics.utils.checks import IS_PYTHON_MINIMUM_3_13, check_imgsz, check_requirements, check_yolo, is_rockchip +from ultralytics.utils.files import file_size +from ultralytics.utils.torch_utils import get_cpu_info, select_device + + +def benchmark( + model=WEIGHTS_DIR / "yolo26n.pt", + data=None, + imgsz=160, + quantize=None, + device="cpu", + verbose=False, + eps=1e-3, + format="", + **kwargs, +): + """Benchmark a YOLO model across different formats for speed and accuracy. + + Args: + model (str | Path): Path to the model file or directory. + data (str | None): Dataset to evaluate on, inherited from TASK2DATA if not passed. + imgsz (int): Image size for the benchmark. + quantize (int | str | None): Precision for export and inference: 16 (FP16), 8 (INT8), or None/32 (FP32). + device (str): Device to run the benchmark on, either 'cpu' or 'cuda'. + verbose (bool | float): If True or a float, assert benchmarks pass with given metric. + eps (float): Epsilon value for divide by zero prevention. + format (str): Export format for benchmarking. If not supplied all formats are benchmarked. + **kwargs (Any): Additional keyword arguments for exporter. + + Returns: + (polars.DataFrame): A Polars DataFrame with benchmark results for each format, including file size, metric, and + inference time. + + Examples: + Benchmark a YOLO model with default settings: + >>> from ultralytics.utils.benchmarks import benchmark + >>> benchmark(model="yolo26n.pt", imgsz=640) + """ + imgsz = check_imgsz(imgsz) + assert imgsz[0] == imgsz[1] if isinstance(imgsz, list) else True, "benchmark() only supports square imgsz." + + import polars as pl # scope for faster 'import ultralytics' + + pl.Config.set_tbl_cols(-1) # Show all columns + pl.Config.set_tbl_rows(-1) # Show all rows + pl.Config.set_tbl_width_chars(-1) # No width limit + pl.Config.set_tbl_hide_column_data_types(True) # Hide data types + pl.Config.set_tbl_hide_dataframe_shape(True) # Hide shape info + pl.Config.set_tbl_formatting("ASCII_BORDERS_ONLY_CONDENSED") + + device = select_device(device, verbose=False) + if isinstance(model, (str, Path)): + model = YOLO(model) + data = data or TASK2DATA[model.task] # task to dataset, i.e. coco8.yaml for task=detect + key = TASK2METRIC[model.task] # task to metric, i.e. metrics/mAP50-95(B) for task=detect + + y = [] + t0 = time.time() + + format_arg = format.lower() + if format_arg: + formats = frozenset(export_formats()["Argument"]) + assert format in formats, f"Expected format to be one of {formats}, but got '{format_arg}'." + for name, format, suffix, cpu, gpu, valid_args, _ in zip(*export_formats().values()): + emoji, filename = "❌", None # export defaults + try: + if format_arg and format_arg != format: + continue + if IS_PYTHON_MINIMUM_3_13 and not format_arg and format in {"saved_model", "pb", "edgetpu"}: + continue + + # Checks + if format == "pb": + assert model.task != "obb", "TensorFlow GraphDef not supported for OBB task" + elif format == "edgetpu": + assert LINUX and not ARM64, "Edge TPU export only supported on non-aarch64 Linux" + assert shutil.which("edgetpu_compiler"), "Edge TPU benchmark requires edgetpu_compiler" + elif format == "coreml": + assert MACOS or (LINUX and not ARM64), "CoreML export only supported on macOS and non-aarch64 Linux" + # coremltools deadlocks after OpenVINO on macOS Python 3.13 (conflicting OpenMP runtimes); CoreML + # is still benchmarked on non-aarch64 Linux Python 3.13. + assert not (MACOS and IS_PYTHON_MINIMUM_3_13), ( + "CoreML not benchmarked on macOS Python>=3.13 (coremltools/OpenVINO OpenMP deadlock)" + ) + if format in {"saved_model", "pb", "edgetpu"}: + assert not isinstance(model, YOLOWorld), "YOLOWorldv2 TensorFlow exports not supported by onnx2tf yet" + if format == "paddle": + assert not isinstance(model, YOLOWorld), "YOLOWorldv2 Paddle exports not supported yet" + assert model.task != "obb", "Paddle OBB bug https://github.com/PaddlePaddle/Paddle/issues/72024" + assert (LINUX and not IS_JETSON) or MACOS, "Windows and Jetson Paddle exports not supported yet" + # PaddlePaddle export works standalone on Python 3.13 but its native protobuf clashes with the + # protobuf>=6.31.1 that TensorFlow loads earlier in this shared benchmark process, causing a segfault. + assert not IS_PYTHON_MINIMUM_3_13, ( + "PaddlePaddle not benchmarked on Python>=3.13 (protobuf ABI conflict with TensorFlow)" + ) + if format == "mnn": + assert not isinstance(model, YOLOWorld), "YOLOWorldv2 MNN exports not supported yet" + # MNN export works standalone on Python 3.13 but its ONNX-parsing protobuf clashes with the + # protobuf>=6.31.1 that TensorFlow loads earlier in this shared benchmark process, aborting the run. + assert not IS_PYTHON_MINIMUM_3_13, ( + "MNN not benchmarked on Python>=3.13 (protobuf ABI conflict with TensorFlow)" + ) + if format == "ncnn": + assert not isinstance(model, YOLOWorld), "YOLOWorldv2 NCNN exports not supported yet" + if format == "imx": + assert not isinstance(model, YOLOWorld), "YOLOWorldv2 IMX exports not supported" + assert model.task in {"detect", "classify", "pose", "segment"}, ( + "IMX export is only supported for detection, classification, pose estimation and segmentation tasks" + ) + assert "C2f" in model.__str__(), "IMX only supported for YOLOv8n and YOLO11n" + if format == "rknn": + assert not isinstance(model, YOLOWorld), "YOLOWorldv2 RKNN exports not supported yet" + assert LINUX, "RKNN only supported on Linux" + assert not is_rockchip(), "RKNN Inference only supported on Rockchip devices" + if format == "executorch": + assert not isinstance(model, YOLOWorld), "YOLOWorldv2 ExecuTorch exports not supported yet" + if format == "axelera": + assert not isinstance(model, YOLOWorld), "YOLOWorldv2 Axelera exports not supported" + assert LINUX and not (ARM64 and IS_DOCKER), ( + "export is only supported on Linux and is not supported on ARM64 Docker." + ) + assert not (model.task == "segment" and any(isinstance(m, Segment26) for m in model.model.modules())), ( + "Axelera export does not currently support YOLO26 segmentation models" + ) + if format == "litert": + assert MACOS or (LINUX and not ARM64), "LiteRT benchmark only supported on Linux x86 and macOS" + # benchmark() deadlocks on the ai-edge-litert/TensorFlow abseil mutex (RAW: Lock blocking) on macOS CI + # when litert runs after other TF-based formats in the shared process; still benchmarked locally. + assert not (MACOS and is_github_action_running()), ( + "LiteRT not benchmarked on macOS CI (ai-edge-litert/TF abseil mutex deadlock)" + ) + if "cpu" in device.type: + assert cpu, "inference not supported on CPU" + if "cuda" in device.type: + assert gpu, "inference not supported on GPU" + + # Export + if format == "-": + filename = model.pt_path or model.ckpt_path or model.model_name + exported_model = deepcopy(model) # PyTorch format + else: + export_data = data if "data" in valid_args else None + filename = deepcopy(model).export( + imgsz=imgsz, + format=format, + quantize=quantize, + data=export_data, + device=device, + verbose=False, + **kwargs, + ) + exported_model = YOLO(filename, task=model.task) + assert suffix in str(filename), "export failed" + emoji = "❎" # indicates export succeeded + + # Predict + assert model.task != "pose" or format != "pb", "GraphDef Pose inference is not supported" + assert format != "edgetpu", "inference not supported" + assert format != "coreml" or platform.system() == "Darwin", "inference only supported on macOS>=10.13" + assert format != "axelera", "inference only supported on Axelera hardware" + exported_model.predict(ASSETS / "bus.jpg", imgsz=imgsz, device=device, quantize=quantize, verbose=False) + + # Validate + results = exported_model.val( + data=data, + batch=1, + imgsz=imgsz, + plots=False, + device=device, + quantize=quantize, + verbose=False, + conf=0.001, # all the pre-set benchmark mAP values are based on conf=0.001 + ) + metric, speed = results.results_dict[key], results.speed["inference"] + fps = round(1000 / (speed + eps), 2) # frames per second + y.append([name, "✅", round(file_size(filename), 1), round(metric, 4), round(speed, 2), fps]) + except Exception as e: + if verbose: + assert type(e) is AssertionError, f"Benchmark failure for {name}: {e}" + LOGGER.error(f"Benchmark failure for {name}: {e}") + y.append([name, emoji, round(file_size(filename), 1), None, None, None]) # mAP, t_inference + + # Print results + check_yolo(device=device) # print system info + df = pl.DataFrame(y, schema=["Format", "Status❔", "Size (MB)", key, "Inference time (ms/im)", "FPS"], orient="row") + df = df.with_row_index(" ", offset=1) # add index info + df_display = df.with_columns(pl.all().cast(pl.String).fill_null("-")) + + name = model.model_name + dt = time.time() - t0 + legend = "Benchmarks legend: - ✅ Success - ❎ Export passed but validation failed - ❌️ Export failed" + s = f"\nBenchmarks complete for {name} on {data} at imgsz={imgsz} ({dt:.2f}s)\n{legend}\n{df_display}\n" + LOGGER.info(s) + with open("benchmarks.log", "a", errors="ignore", encoding="utf-8") as f: + f.write(s) + + if verbose and isinstance(verbose, float): + metrics = df[key].to_numpy() # values to compare to floor + floor = verbose # minimum metric floor to pass, i.e. = 0.29 mAP for YOLOv5n + assert all(x > floor for x in metrics if not np.isnan(x)), f"Benchmark failure: metric(s) < floor {floor}" + + return df_display + + +class ProfileModels: + """ProfileModels class for profiling different models on ONNX and TensorRT. + + This class profiles the performance of different models, returning results such as model speed and FLOPs. + + Attributes: + paths (list[str]): Paths of the models to profile. + num_timed_runs (int): Number of timed runs for the profiling. + num_warmup_runs (int): Number of warmup runs before profiling. + min_time (float): Minimum number of seconds to profile for. + imgsz (int): Image size used in the models. + quantize (int | str | None): Export precision for TensorRT profiling, e.g. 16 (FP16) or 8 (INT8). + trt (bool): Flag to indicate whether to profile using TensorRT. + device (torch.device): Device used for profiling. + + Methods: + run: Profile YOLO models for speed and accuracy across various formats. + get_files: Get all relevant model files. + get_onnx_model_info: Extract metadata from an ONNX model. + iterative_sigma_clipping: Apply sigma clipping to remove outliers. + profile_tensorrt_model: Profile a TensorRT model. + profile_onnx_model: Profile an ONNX model. + generate_table_row: Generate a table row with model metrics. + generate_results_dict: Generate a dictionary of profiling results. + print_table: Print a formatted table of results. + + Examples: + Profile models and print results + >>> from ultralytics.utils.benchmarks import ProfileModels + >>> profiler = ProfileModels(["yolo26n.yaml", "yolov8s.yaml"], imgsz=640) + >>> profiler.run() + """ + + def __init__( + self, + paths: list[str], + num_timed_runs: int = 100, + num_warmup_runs: int = 10, + min_time: float = 60, + imgsz: int = 640, + quantize: int | str | None = 16, + trt: bool = True, + device: torch.device | str | None = None, + ): + """Initialize the ProfileModels class for profiling models. + + Args: + paths (list[str]): List of paths of the models to be profiled. + num_timed_runs (int): Number of timed runs for the profiling. + num_warmup_runs (int): Number of warmup runs before the actual profiling starts. + min_time (float): Minimum time in seconds for profiling a model. + imgsz (int): Size of the image used during profiling. + quantize (int | str | None): Export precision for TensorRT profiling, e.g. 16 (FP16, default) or 8 (INT8). + trt (bool): Flag to indicate whether to profile using TensorRT. + device (torch.device | str | None): Device used for profiling. If None, it is determined automatically. + + Notes: + quantize applies only to the TensorRT profiling export; ONNX profiling stays FP32 (FP16 is slower on CPU). + """ + self.paths = paths + self.num_timed_runs = num_timed_runs + self.num_warmup_runs = num_warmup_runs + self.min_time = min_time + self.imgsz = imgsz + self.quantize = quantize + self.trt = trt # run TensorRT profiling + self.device = device if isinstance(device, torch.device) else select_device(device) + + def run(self): + """Profile YOLO models for speed and accuracy across various formats including ONNX and TensorRT. + + Returns: + (list[dict]): List of dictionaries containing profiling results for each model. + + Examples: + Profile models and print results + >>> from ultralytics.utils.benchmarks import ProfileModels + >>> profiler = ProfileModels(["yolo26n.yaml", "yolo11s.yaml"]) + >>> results = profiler.run() + """ + files = self.get_files() + + if not files: + LOGGER.warning("No matching *.pt or *.onnx files found.") + return [] + + table_rows = [] + output = [] + for file in files: + engine_file = file.with_suffix(".engine") + if file.suffix in {".pt", ".yaml", ".yml"}: + model = YOLO(str(file)) + model.fuse() # to report correct params and GFLOPs in model.info() + model_info = model.info(imgsz=self.imgsz) + if self.trt and self.device.type != "cpu" and not engine_file.is_file(): + engine_file = model.export( + format="engine", + quantize=self.quantize, + imgsz=self.imgsz, + device=self.device, + verbose=False, + ) + onnx_file = model.export( + format="onnx", + imgsz=self.imgsz, + device=self.device, + verbose=False, + ) + elif file.suffix == ".onnx": + model_info = self.get_onnx_model_info(file) + onnx_file = file + else: + continue + + t_engine = self.profile_tensorrt_model(str(engine_file)) + t_onnx = self.profile_onnx_model(str(onnx_file)) + table_rows.append(self.generate_table_row(file.stem, t_onnx, t_engine, model_info)) + output.append(self.generate_results_dict(file.stem, t_onnx, t_engine, model_info)) + + self.print_table(table_rows) + return output + + def get_files(self): + """Return a list of paths for all relevant model files given by the user. + + Returns: + (list[Path]): List of Path objects for the model files. + """ + files = [] + for path in self.paths: + path = Path(path) + if path.is_dir(): + extensions = ["*.pt", "*.onnx", "*.yaml"] + files.extend([file for ext in extensions for file in glob.glob(str(path / ext))]) + elif path.suffix in {".pt", ".yaml", ".yml"}: # add non-existing + files.append(str(path)) + else: + files.extend(glob.glob(str(path))) + + LOGGER.info(f"Profiling: {sorted(files)}") + return [Path(file) for file in sorted(files)] + + @staticmethod + def get_onnx_model_info(onnx_file: str): + """Extract metadata from an ONNX model file including layers, parameters, gradients, and FLOPs.""" + return 0.0, 0.0, 0.0, 0.0 # return (num_layers, num_params, num_gradients, num_flops) + + @staticmethod + def iterative_sigma_clipping(data: np.ndarray, sigma: float = 2, max_iters: int = 3): + """Apply iterative sigma clipping to data to remove outliers. + + Args: + data (np.ndarray): Input data array. + sigma (float): Number of standard deviations to use for clipping. + max_iters (int): Maximum number of iterations for the clipping process. + + Returns: + (np.ndarray): Clipped data array with outliers removed. + """ + data = np.array(data) + for _ in range(max_iters): + mean, std = np.mean(data), np.std(data) + clipped_data = data[(data > mean - sigma * std) & (data < mean + sigma * std)] + if len(clipped_data) == len(data): + break + data = clipped_data + return data + + def profile_tensorrt_model(self, engine_file: str, eps: float = 1e-3): + """Profile YOLO model performance with TensorRT, measuring average run time and standard deviation. + + Args: + engine_file (str): Path to the TensorRT engine file. + eps (float): Small epsilon value to prevent division by zero. + + Returns: + (tuple[float, float]): Mean and standard deviation of inference time in milliseconds. + """ + if not self.trt or not Path(engine_file).is_file(): + return 0.0, 0.0 + + # Model and input + model = YOLO(engine_file) + input_data = np.zeros((self.imgsz, self.imgsz, 3), dtype=np.uint8) # use uint8 for Classify + + # Warmup runs + elapsed = 0.0 + for _ in range(3): + start_time = time.time() + for _ in range(self.num_warmup_runs): + model(input_data, imgsz=self.imgsz, verbose=False) + elapsed = time.time() - start_time + + # Compute number of runs as higher of min_time or num_timed_runs + num_runs = max(round(self.min_time / (elapsed + eps) * self.num_warmup_runs), self.num_timed_runs * 50) + + # Timed runs + run_times = [] + for _ in TQDM(range(num_runs), desc=engine_file): + results = model(input_data, imgsz=self.imgsz, verbose=False) + run_times.append(results[0].speed["inference"]) # Convert to milliseconds + + run_times = self.iterative_sigma_clipping(np.array(run_times), sigma=2, max_iters=3) # sigma clipping + return np.mean(run_times), np.std(run_times) + + @staticmethod + def check_dynamic(tensor_shape): + """Check whether the tensor shape in the ONNX model is dynamic.""" + return not all(isinstance(dim, int) and dim >= 0 for dim in tensor_shape) + + def profile_onnx_model(self, onnx_file: str, eps: float = 1e-3): + """Profile an ONNX model, measuring average inference time and standard deviation across multiple runs. + + Args: + onnx_file (str): Path to the ONNX model file. + eps (float): Small epsilon value to prevent division by zero. + + Returns: + (tuple[float, float]): Mean and standard deviation of inference time in milliseconds. + """ + check_requirements([("onnxruntime", "onnxruntime-gpu")]) # either package meets requirements + import onnxruntime as ort + + # Session with either 'TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider' + sess_options = ort.SessionOptions() + sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL + sess_options.intra_op_num_threads = 8 # Limit the number of threads + sess = ort.InferenceSession(onnx_file, sess_options, providers=["CPUExecutionProvider"]) + + input_data_dict = {} + for input_tensor in sess.get_inputs(): + input_type = input_tensor.type + if self.check_dynamic(input_tensor.shape): + if len(input_tensor.shape) != 4 and self.check_dynamic(input_tensor.shape[1:]): + raise ValueError(f"Unsupported dynamic shape {input_tensor.shape} of {input_tensor.name}") + input_shape = ( + (1, 3, self.imgsz, self.imgsz) if len(input_tensor.shape) == 4 else (1, *input_tensor.shape[1:]) + ) + else: + input_shape = input_tensor.shape + + # Mapping ONNX datatype to numpy datatype + if "float16" in input_type: + input_dtype = np.float16 + elif "float" in input_type: + input_dtype = np.float32 + elif "double" in input_type: + input_dtype = np.float64 + elif "int64" in input_type: + input_dtype = np.int64 + elif "int32" in input_type: + input_dtype = np.int32 + else: + raise ValueError(f"Unsupported ONNX datatype {input_type}") + + input_data = np.random.rand(*input_shape).astype(input_dtype) + input_name = input_tensor.name + input_data_dict[input_name] = input_data + + output_name = sess.get_outputs()[0].name + + # Warmup runs + elapsed = 0.0 + for _ in range(3): + start_time = time.time() + for _ in range(self.num_warmup_runs): + sess.run([output_name], input_data_dict) + elapsed = time.time() - start_time + + # Compute number of runs as higher of min_time or num_timed_runs + num_runs = max(round(self.min_time / (elapsed + eps) * self.num_warmup_runs), self.num_timed_runs) + + # Timed runs + run_times = [] + for _ in TQDM(range(num_runs), desc=onnx_file): + start_time = time.time() + sess.run([output_name], input_data_dict) + run_times.append((time.time() - start_time) * 1000) # Convert to milliseconds + + run_times = self.iterative_sigma_clipping(np.array(run_times), sigma=2, max_iters=5) # sigma clipping + return np.mean(run_times), np.std(run_times) + + def generate_table_row( + self, + model_name: str, + t_onnx: tuple[float, float], + t_engine: tuple[float, float], + model_info: tuple[float, float, float, float], + ): + """Generate a table row string with model performance metrics. + + Args: + model_name (str): Name of the model. + t_onnx (tuple): ONNX model inference time statistics (mean, std). + t_engine (tuple): TensorRT engine inference time statistics (mean, std). + model_info (tuple): Model information (layers, params, gradients, flops). + + Returns: + (str): Formatted table row string with model metrics. + """ + _layers, params, _gradients, flops = model_info + return ( + f"| {model_name:18s} | {self.imgsz} | - | {t_onnx[0]:.1f}±{t_onnx[1]:.1f} ms | {t_engine[0]:.1f}±" + f"{t_engine[1]:.1f} ms | {params / 1e6:.1f} | {flops:.1f} |" + ) + + @staticmethod + def generate_results_dict( + model_name: str, + t_onnx: tuple[float, float], + t_engine: tuple[float, float], + model_info: tuple[float, float, float, float], + ): + """Generate a dictionary of profiling results. + + Args: + model_name (str): Name of the model. + t_onnx (tuple): ONNX model inference time statistics (mean, std). + t_engine (tuple): TensorRT engine inference time statistics (mean, std). + model_info (tuple): Model information (layers, params, gradients, flops). + + Returns: + (dict): Dictionary containing profiling results. + """ + _layers, params, _gradients, flops = model_info + return { + "model/name": model_name, + "model/parameters": params, + "model/GFLOPs": round(flops, 3), + "model/speed_ONNX(ms)": round(t_onnx[0], 3), + "model/speed_TensorRT(ms)": round(t_engine[0], 3), + } + + @staticmethod + def print_table(table_rows: list[str]): + """Print a formatted table of model profiling results. + + Args: + table_rows (list[str]): List of formatted table row strings. + """ + gpu = torch.cuda.get_device_name(0) if torch.cuda.is_available() else "GPU" + headers = [ + "Model", + "size
(pixels)", + "mAPval
50-95", + f"Speed
CPU ({get_cpu_info()}) ONNX
(ms)", + f"Speed
{gpu} TensorRT
(ms)", + "params
(M)", + "FLOPs
(B)", + ] + header = "|" + "|".join(f" {h} " for h in headers) + "|" + separator = "|" + "|".join("-" * (len(h) + 2) for h in headers) + "|" + + LOGGER.info(f"\n\n{header}") + LOGGER.info(separator) + for row in table_rows: + LOGGER.info(row) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__init__.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..920cc4fad9d6fa2b7b99707d2fe33941e4612e5b --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__init__.py @@ -0,0 +1,5 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from .base import add_integration_callbacks, default_callbacks, get_default_callbacks + +__all__ = "add_integration_callbacks", "default_callbacks", "get_default_callbacks" diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a982d172b8d1e751f529baf167ffb017d2e388f Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/base.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/base.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea868b4c00bc2a1e894bae463174cbf19bbc87d2 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/base.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/clearml.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/clearml.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66a9fb4db7760555a9b490f3bd52800570e67a25 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/clearml.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/comet.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/comet.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26715acc8ac96929aeb6b0c56efa9deb7b066502 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/comet.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/dvc.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/dvc.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4338524d04371a089b06a07545309ecb5fc02d8e Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/dvc.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/hub.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/hub.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45e1f90b05ec3cb21d1dcf130efa1525b5af7041 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/hub.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/mlflow.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/mlflow.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a319cd0e497c3f57d3c6b0f3b0845a839e10e0d5 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/mlflow.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/neptune.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/neptune.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b6ae781f11a8814be73d70ab1b1a9831fe2b513 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/neptune.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/platform.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/platform.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..461258785a93c8d18feb52293a6ad3b1f1ee1626 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/platform.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/raytune.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/raytune.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cc382e86e89eb7d6a430add249fcde40256bd87 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/raytune.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/tensorboard.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/tensorboard.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f7ccb70506e1ac04a3f6644eeb4c3f1ef6123c9 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/tensorboard.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/wb.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/wb.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12ca5b0d6442d9cc02bb1073fa1592bc70214599 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/__pycache__/wb.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/base.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/base.py new file mode 100644 index 0000000000000000000000000000000000000000..5b35c326bfdb1b7d94876d612b11cc242b40e6c3 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/base.py @@ -0,0 +1,235 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license +"""Base callbacks for Ultralytics training, validation, prediction, and export processes.""" + +from collections import defaultdict +from copy import deepcopy + +# Trainer callbacks ---------------------------------------------------------------------------------------------------- + + +def on_pretrain_routine_start(trainer): + """Called at the beginning of the pre-training routine, before data loading and model setup.""" + pass + + +def on_pretrain_routine_end(trainer): + """Called at the end of the pre-training routine, after data loading and model setup are complete.""" + pass + + +def on_train_start(trainer): + """Called when the training starts, before the first epoch begins.""" + pass + + +def on_train_epoch_start(trainer): + """Called at the start of each training epoch, before batch iteration begins.""" + pass + + +def on_train_batch_start(trainer): + """Called at the start of each training batch, before the forward pass.""" + pass + + +def optimizer_step(trainer): + """Called during the optimizer step. Reserved for custom integrations; not called by default.""" + pass + + +def on_before_zero_grad(trainer): + """Called before the gradients are set to zero. Reserved for custom integrations; not called by default.""" + pass + + +def on_train_batch_end(trainer): + """Called at the end of each training batch, after the backward pass. Optimizer step may be deferred by + accumulation. + """ + pass + + +def on_train_epoch_end(trainer): + """Called at the end of each training epoch, after all batches but before validation.""" + pass + + +def on_fit_epoch_end(trainer): + """Called at the end of each fit epoch (train + val), after validation and any checkpoint save.""" + pass + + +def on_model_save(trainer): + """Called when the model checkpoint is saved, after validation.""" + pass + + +def on_train_end(trainer): + """Called when the training ends, after final evaluation of the best model.""" + pass + + +def on_params_update(trainer): + """Called when the model parameters are updated. Reserved for custom integrations; not called by default.""" + pass + + +def teardown(trainer): + """Called during the teardown of the training process.""" + pass + + +# Validator callbacks -------------------------------------------------------------------------------------------------- + + +def on_val_start(validator): + """Called when the validation starts.""" + pass + + +def on_val_batch_start(validator): + """Called at the start of each validation batch.""" + pass + + +def on_val_batch_end(validator): + """Called at the end of each validation batch.""" + pass + + +def on_val_end(validator): + """Called when the validation ends.""" + pass + + +# Predictor callbacks -------------------------------------------------------------------------------------------------- + + +def on_predict_start(predictor): + """Called when the prediction starts.""" + pass + + +def on_predict_batch_start(predictor): + """Called at the start of each prediction batch.""" + pass + + +def on_predict_batch_end(predictor): + """Called at the end of each prediction batch.""" + pass + + +def on_predict_postprocess_end(predictor): + """Called after the post-processing of the prediction ends.""" + pass + + +def on_predict_end(predictor): + """Called when the prediction ends.""" + pass + + +# Exporter callbacks --------------------------------------------------------------------------------------------------- + + +def on_export_start(exporter): + """Called when the model export starts.""" + pass + + +def on_export_end(exporter): + """Called when the model export ends.""" + pass + + +default_callbacks = { + # Run in trainer + "on_pretrain_routine_start": [on_pretrain_routine_start], + "on_pretrain_routine_end": [on_pretrain_routine_end], + "on_train_start": [on_train_start], + "on_train_epoch_start": [on_train_epoch_start], + "on_train_batch_start": [on_train_batch_start], + "optimizer_step": [optimizer_step], + "on_before_zero_grad": [on_before_zero_grad], + "on_train_batch_end": [on_train_batch_end], + "on_train_epoch_end": [on_train_epoch_end], + "on_fit_epoch_end": [on_fit_epoch_end], # fit = train + val + "on_model_save": [on_model_save], + "on_train_end": [on_train_end], + "on_params_update": [on_params_update], + "teardown": [teardown], + # Run in validator + "on_val_start": [on_val_start], + "on_val_batch_start": [on_val_batch_start], + "on_val_batch_end": [on_val_batch_end], + "on_val_end": [on_val_end], + # Run in predictor + "on_predict_start": [on_predict_start], + "on_predict_batch_start": [on_predict_batch_start], + "on_predict_postprocess_end": [on_predict_postprocess_end], + "on_predict_batch_end": [on_predict_batch_end], + "on_predict_end": [on_predict_end], + # Run in exporter + "on_export_start": [on_export_start], + "on_export_end": [on_export_end], +} + + +def get_default_callbacks(): + """Get the default callbacks for Ultralytics training, validation, prediction, and export processes. + + Returns: + (dict): Dictionary of default callbacks for various training events. Each key represents an event during the + training process, and the corresponding value is a list of callback functions executed when that + event occurs. + + Examples: + >>> callbacks = get_default_callbacks() + >>> print(list(callbacks.keys())) # show all available callback events + ['on_pretrain_routine_start', 'on_pretrain_routine_end', ...] + """ + return defaultdict(list, deepcopy(default_callbacks)) + + +def add_integration_callbacks(instance): + """Add integration callbacks to the instance's callbacks dictionary. + + This function loads and adds various integration callbacks to the provided instance. The specific callbacks added + depend on the type of instance provided. All instances receive HUB callbacks, while Trainer instances also receive + additional callbacks for various integrations like ClearML, Comet, DVC, MLflow, Neptune, Ray Tune, TensorBoard, and + Weights & Biases. + + Args: + instance (Trainer | Predictor | Validator | Exporter): The object instance to which callbacks will be added. The + type of instance determines which callbacks are loaded. + + Examples: + >>> from ultralytics.engine.trainer import BaseTrainer + >>> trainer = BaseTrainer() + >>> add_integration_callbacks(trainer) + """ + from .hub import callbacks as hub_cb + from .platform import callbacks as platform_cb + + # Load Ultralytics callbacks + callbacks_list = [hub_cb, platform_cb] + + # Load training callbacks + if "Trainer" in instance.__class__.__name__: + from .clearml import callbacks as clear_cb + from .comet import callbacks as comet_cb + from .dvc import callbacks as dvc_cb + from .mlflow import callbacks as mlflow_cb + from .neptune import callbacks as neptune_cb + from .raytune import callbacks as tune_cb + from .tensorboard import callbacks as tb_cb + from .wb import callbacks as wb_cb + + callbacks_list.extend([clear_cb, comet_cb, dvc_cb, mlflow_cb, neptune_cb, tune_cb, tb_cb, wb_cb]) + + # Add the callbacks to the callbacks dictionary + for callbacks in callbacks_list: + for k, v in callbacks.items(): + if v not in instance.callbacks[k]: + instance.callbacks[k].append(v) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/clearml.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/clearml.py new file mode 100644 index 0000000000000000000000000000000000000000..29d7a8b327ceca76050a6d992192108964eea4a9 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/clearml.py @@ -0,0 +1,146 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from ultralytics.utils import LOGGER, SETTINGS, TESTS_RUNNING + +try: + assert not TESTS_RUNNING # do not log pytest + assert SETTINGS["clearml"] is True # verify integration is enabled + import clearml + from clearml import Task + + assert hasattr(clearml, "__version__") # verify package is not directory + +except (ImportError, AssertionError): + clearml = None + + +def _log_debug_samples(files, title: str = "Debug Samples") -> None: + """Log files (images) as debug samples in the ClearML task. + + Args: + files (list[Path]): A list of file paths in PosixPath format. + title (str): A title that groups together images with the same values. + """ + import re + + if task := Task.current_task(): + for f in files: + if f.exists(): + it = re.search(r"_batch(\d+)", f.name) + iteration = int(it.groups()[0]) if it else 0 + task.get_logger().report_image( + title=title, series=f.name.replace(it.group(), ""), local_path=str(f), iteration=iteration + ) + + +def _log_plot(title: str, plot_path: str) -> None: + """Log an image as a plot in the plot section of ClearML. + + Args: + title (str): The title of the plot. + plot_path (str | Path): The path to the saved image file. + """ + import matplotlib.image as mpimg + import matplotlib.pyplot as plt + + img = mpimg.imread(plot_path) + fig = plt.figure() + ax = fig.add_axes([0, 0, 1, 1], frameon=False, aspect="auto", xticks=[], yticks=[]) # no ticks + ax.imshow(img) + + Task.current_task().get_logger().report_matplotlib_figure( + title=title, series="", figure=fig, report_interactive=False + ) + + +def on_pretrain_routine_start(trainer) -> None: + """Initialize and connect ClearML task at the start of pretraining routine.""" + try: + if task := Task.current_task(): + # WARNING: make sure the automatic pytorch and matplotlib bindings are disabled! + # We are logging these plots and model files manually in the integration + from clearml.binding.frameworks.pytorch_bind import PatchPyTorchModelIO + from clearml.binding.matplotlib_bind import PatchedMatplotlib + + PatchPyTorchModelIO.update_current_task(None) + PatchedMatplotlib.update_current_task(None) + else: + task = Task.init( + project_name=str(trainer.args.project or "Ultralytics").lstrip("/") or "Ultralytics", + task_name=trainer.args.name, + tags=["Ultralytics"], + output_uri=True, + reuse_last_task_id=False, + auto_connect_frameworks={"pytorch": False, "matplotlib": False}, + ) + LOGGER.warning( + "ClearML Initialized a new task. If you want to run remotely, " + "please add clearml-init and connect your arguments before initializing YOLO." + ) + task.connect(vars(trainer.args), name="General", ignore_remote_overrides=True) + except Exception as e: + LOGGER.warning(f"ClearML installed but not initialized correctly, not logging this run. {e}") + + +def on_train_epoch_end(trainer) -> None: + """Log debug samples for the first epoch and report current training progress.""" + if task := Task.current_task(): + # Log debug samples for first epoch only + if trainer.epoch == 1: + _log_debug_samples(sorted(trainer.save_dir.glob("train_batch*.jpg")), "Mosaic") + # Report the current training progress + for k, v in trainer.label_loss_items(trainer.tloss, prefix="train").items(): + task.get_logger().report_scalar("train", k, v, iteration=trainer.epoch) + for k, v in trainer.lr.items(): + task.get_logger().report_scalar("lr", k, v, iteration=trainer.epoch) + + +def on_fit_epoch_end(trainer) -> None: + """Report model information and metrics to logger at the end of an epoch.""" + if task := Task.current_task(): + # Report epoch time and validation metrics + task.get_logger().report_scalar( + title="Epoch Time", series="Epoch Time", value=trainer.epoch_time, iteration=trainer.epoch + ) + for k, v in trainer.metrics.items(): + title = k.split("/")[0] + task.get_logger().report_scalar(title, k, v, iteration=trainer.epoch) + if trainer.epoch == 0: + from ultralytics.utils.torch_utils import model_info_for_loggers + + for k, v in model_info_for_loggers(trainer).items(): + task.get_logger().report_single_value(k, v) + + +def on_val_end(validator) -> None: + """Log validation results including labels and predictions.""" + if Task.current_task(): + # Log validation labels and predictions + _log_debug_samples(sorted(validator.save_dir.glob("val*.jpg")), "Validation") + + +def on_train_end(trainer) -> None: + """Log final model and training results on training completion.""" + if task := Task.current_task(): + # Log final results, confusion matrix and PR plots + for f in [*trainer.plots.keys(), *trainer.validator.plots.keys()]: + if "batch" not in f.name: + _log_plot(title=f.stem, plot_path=f) + # Report final metrics + for k, v in trainer.validator.metrics.results_dict.items(): + task.get_logger().report_single_value(k, v) + # Log the final model + task.update_output_model(model_path=str(trainer.best), model_name=trainer.args.name, auto_delete_file=False) + + +callbacks = ( + { + "on_pretrain_routine_start": on_pretrain_routine_start, + "on_train_epoch_end": on_train_epoch_end, + "on_fit_epoch_end": on_fit_epoch_end, + "on_val_end": on_val_end, + "on_train_end": on_train_end, + } + if clearml + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/comet.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/comet.py new file mode 100644 index 0000000000000000000000000000000000000000..74dfcaa12f185c0e5e87e8eb7875c579ea0ca70f --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/comet.py @@ -0,0 +1,622 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from collections.abc import Callable +from types import SimpleNamespace +from typing import Any + +import cv2 +import numpy as np + +from ultralytics.utils import LOGGER, RANK, SETTINGS, TESTS_RUNNING, ops +from ultralytics.utils.metrics import ClassifyMetrics, DetMetrics, OBBMetrics, PoseMetrics, SegmentMetrics + +try: + assert not TESTS_RUNNING # do not log pytest + assert SETTINGS["comet"] is True # verify integration is enabled + import comet_ml + + assert hasattr(comet_ml, "__version__") # verify package is not directory + + import os + from pathlib import Path + + # Ensures certain logging functions only run for supported tasks + COMET_SUPPORTED_TASKS = ["detect", "segment"] + + # Names of plots created by Ultralytics that are logged to Comet + CONFUSION_MATRIX_PLOT_NAMES = "confusion_matrix", "confusion_matrix_normalized" + EVALUATION_PLOT_NAMES = "F1_curve", "P_curve", "R_curve", "PR_curve" + LABEL_PLOT_NAMES = ["labels"] + SEGMENT_METRICS_PLOT_PREFIX = "Box", "Mask" + POSE_METRICS_PLOT_PREFIX = "Box", "Pose" + DETECTION_METRICS_PLOT_PREFIX = ["Box"] + RESULTS_TABLE_NAME = "results.csv" + ARGS_YAML_NAME = "args.yaml" + + _comet_image_prediction_count = 0 + +except (ImportError, AssertionError): + comet_ml = None + + +def _get_comet_mode() -> str: + """Return the Comet mode from environment variables, defaulting to 'online'.""" + comet_mode = os.getenv("COMET_MODE") + if comet_mode is not None: + LOGGER.warning( + "The COMET_MODE environment variable is deprecated. " + "Please use COMET_START_ONLINE to set the Comet experiment mode. " + "To start an offline Comet experiment, use 'export COMET_START_ONLINE=0'. " + "If COMET_START_ONLINE is not set or is set to '1', an online Comet experiment will be created." + ) + return comet_mode + + return "online" + + +def _get_comet_model_name() -> str: + """Return the Comet model name from environment variable or default to 'Ultralytics'.""" + return os.getenv("COMET_MODEL_NAME", "Ultralytics") + + +def _get_eval_batch_logging_interval() -> int: + """Get the evaluation batch logging interval from environment variable or use default value 1.""" + return int(os.getenv("COMET_EVAL_BATCH_LOGGING_INTERVAL", 1)) + + +def _get_max_image_predictions_to_log() -> int: + """Get the maximum number of image predictions to log from environment variables.""" + return int(os.getenv("COMET_MAX_IMAGE_PREDICTIONS", 100)) + + +def _scale_confidence_score(score: float) -> float: + """Scale the confidence score by a factor specified in environment variable.""" + scale = float(os.getenv("COMET_MAX_CONFIDENCE_SCORE", 100.0)) + return score * scale + + +def _should_log_confusion_matrix() -> bool: + """Determine if the confusion matrix should be logged based on environment variable settings.""" + return os.getenv("COMET_EVAL_LOG_CONFUSION_MATRIX", "false").lower() == "true" + + +def _should_log_image_predictions() -> bool: + """Determine whether to log image predictions based on environment variable.""" + return os.getenv("COMET_EVAL_LOG_IMAGE_PREDICTIONS", "true").lower() == "true" + + +def _resume_or_create_experiment(args: SimpleNamespace) -> None: + """Resume CometML experiment or create a new experiment based on args. + + Ensures that the experiment object is only created in a single process during distributed training. + + Args: + args (SimpleNamespace): Training arguments containing project configuration and other parameters. + """ + if RANK not in {-1, 0}: + return + + # Set environment variable (if not set by the user) to configure the Comet experiment's online mode under the hood. + # IF COMET_START_ONLINE is set by the user it will override COMET_MODE value. + if os.getenv("COMET_START_ONLINE") is None: + comet_mode = _get_comet_mode() + os.environ["COMET_START_ONLINE"] = "1" if comet_mode != "offline" else "0" + + try: + _project_name = os.getenv("COMET_PROJECT_NAME", args.project) + experiment = comet_ml.start(project_name=_project_name) + experiment.log_parameters(vars(args)) + experiment.log_others( + { + "eval_batch_logging_interval": _get_eval_batch_logging_interval(), + "log_confusion_matrix_on_eval": _should_log_confusion_matrix(), + "log_image_predictions": _should_log_image_predictions(), + "max_image_predictions": _get_max_image_predictions_to_log(), + } + ) + experiment.log_other("Created from", "ultralytics") + + except Exception as e: + LOGGER.warning(f"Comet installed but not initialized correctly, not logging this run. {e}") + + +def _fetch_trainer_metadata(trainer) -> dict: + """Return metadata for YOLO training including epoch and asset saving status. + + Args: + trainer (ultralytics.engine.trainer.BaseTrainer): The YOLO trainer object containing training state and config. + + Returns: + (dict): Dictionary containing current epoch, step, save assets flag, and final epoch flag. + """ + curr_epoch = trainer.epoch + 1 + + train_num_steps_per_epoch = len(trainer.train_loader.dataset) // trainer.batch_size + curr_step = curr_epoch * train_num_steps_per_epoch + final_epoch = curr_epoch == trainer.epochs + + save = trainer.args.save + save_period = trainer.args.save_period + save_interval = curr_epoch % save_period == 0 + save_assets = save and save_period > 0 and save_interval and not final_epoch + + return dict(curr_epoch=curr_epoch, curr_step=curr_step, save_assets=save_assets, final_epoch=final_epoch) + + +def _scale_bounding_box_to_original_image_shape( + box, resized_image_shape, original_image_shape, ratio_pad +) -> list[float]: + """Scale bounding box from resized image coordinates to original image coordinates. + + YOLO resizes images during training and the label values are normalized based on this resized shape. This function + rescales the bounding box labels to the original image shape. + + Args: + box (torch.Tensor): Bounding box in normalized xywh format. + resized_image_shape (tuple): Shape of the resized image (height, width). + original_image_shape (tuple): Shape of the original image (height, width). + ratio_pad (tuple): Ratio and padding information for scaling. + + Returns: + (list[float]): Scaled bounding box coordinates in xywh format with top-left corner adjustment. + """ + resized_image_height, resized_image_width = resized_image_shape + + # Convert normalized xywh format predictions to xyxy in resized scale format + box = ops.xywhn2xyxy(box, h=resized_image_height, w=resized_image_width) + # Scale box predictions from resized image scale back to original image scale + box = ops.scale_boxes(resized_image_shape, box, original_image_shape, ratio_pad) + # Convert bounding box format from xyxy to xywh for Comet logging + box = ops.xyxy2xywh(box) + # Adjust xy center to correspond top-left corner + box[:2] -= box[2:] / 2 + box = box.tolist() + + return box + + +def _format_ground_truth_annotations_for_detection(img_idx, image_path, batch, class_name_map=None) -> dict | None: + """Format ground truth annotations for object detection. + + This function processes ground truth annotations from a batch of images for object detection tasks. It extracts + bounding boxes, class labels, and other metadata for a specific image in the batch, and formats them for + visualization or evaluation. + + Args: + img_idx (int): Index of the image in the batch to process. + image_path (str | Path): Path to the image file. + batch (dict): Batch dictionary containing detection data with keys: + - 'batch_idx': Tensor of batch indices + - 'bboxes': Tensor of bounding boxes in normalized xywh format + - 'cls': Tensor of class labels + - 'ori_shape': Original image shapes + - 'resized_shape': Resized image shapes + - 'ratio_pad': Ratio and padding information + class_name_map (dict, optional): Mapping from class indices to class names. + + Returns: + (dict | None): Formatted ground truth annotations with keys 'name' and 'data', where 'data' is a list of + annotation dicts each containing 'boxes', 'label', and 'score' keys. Returns None if no bounding boxes are + found for the image. + """ + indices = batch["batch_idx"] == img_idx + bboxes = batch["bboxes"][indices] + if len(bboxes) == 0: + LOGGER.debug(f"Comet Image: {image_path} has no bounding boxes labels") + return None + + cls_labels = batch["cls"][indices].squeeze(1).tolist() + if class_name_map: + cls_labels = [str(class_name_map[label]) for label in cls_labels] + + original_image_shape = batch["ori_shape"][img_idx] + resized_image_shape = batch["resized_shape"][img_idx] + ratio_pad = batch["ratio_pad"][img_idx] + + data = [] + for box, label in zip(bboxes, cls_labels): + box = _scale_bounding_box_to_original_image_shape(box, resized_image_shape, original_image_shape, ratio_pad) + data.append( + { + "boxes": [box], + "label": f"gt_{label}", + "score": _scale_confidence_score(1.0), + } + ) + + return {"name": "ground_truth", "data": data} + + +def _format_prediction_annotations(image_path, metadata, class_label_map=None, class_map=None) -> dict | None: + """Format YOLO predictions for object detection visualization. + + Args: + image_path (Path): Path to the image file. + metadata (dict): Prediction metadata containing bounding boxes and class information. + class_label_map (dict, optional): Mapping from class indices to class names. + class_map (dict, optional): Additional class mapping for label conversion. + + Returns: + (dict | None): Formatted prediction annotations or None if no predictions exist. + """ + stem = image_path.stem + image_id = int(stem) if stem.isnumeric() else stem + + predictions = metadata.get(image_id) + if not predictions: + LOGGER.debug(f"Comet Image: {image_path} has no bounding boxes predictions") + return None + + # apply the mapping that was used to map the predicted classes when the JSON was created + if class_label_map and class_map: + class_label_map = {class_map[k]: v for k, v in class_label_map.items()} + try: + # import faster_coco_eval utilities to decompress annotations for various tasks, e.g. segmentation + from faster_coco_eval.core.mask import decode + except ImportError: + decode = None + + data = [] + for prediction in predictions: + boxes = prediction["bbox"] + score = _scale_confidence_score(prediction["score"]) + cls_label = prediction["category_id"] + if class_label_map: + cls_label = str(class_label_map[cls_label]) + + annotation_data = {"boxes": [boxes], "label": cls_label, "score": score} + + if decode is not None: + # do segmentation processing only if we are able to decode it + segments = prediction.get("segmentation", None) + if segments is not None: + segments = _extract_segmentation_annotation(segments, decode) + if segments is not None: + annotation_data["points"] = segments + + data.append(annotation_data) + + return {"name": "prediction", "data": data} + + +def _extract_segmentation_annotation(segmentation_raw: str, decode: Callable) -> list[list[Any]] | None: + """Extract segmentation annotation from compressed segmentations as list of polygons. + + Args: + segmentation_raw (str): Raw segmentation data in compressed format. + decode (Callable): Function to decode the compressed segmentation data. + + Returns: + (list[list[Any]] | None): List of polygon points or None if extraction fails. + """ + try: + mask = decode(segmentation_raw) + contours, _ = cv2.findContours(mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) + annotations = [np.array(polygon).squeeze() for polygon in contours if len(polygon) >= 3] + return [annotation.ravel().tolist() for annotation in annotations] + except Exception as e: + LOGGER.warning(f"Comet Failed to extract segmentation annotation: {e}") + return None + + +def _fetch_annotations(img_idx, image_path, batch, prediction_metadata_map, class_label_map, class_map) -> list | None: + """Join the ground truth and prediction annotations if they exist. + + Args: + img_idx (int): Index of the image in the batch. + image_path (Path): Path to the image file. + batch (dict): Batch data containing ground truth annotations. + prediction_metadata_map (dict): Map of prediction metadata by image ID. + class_label_map (dict): Mapping from class indices to class names. + class_map (dict): Additional class mapping for label conversion. + + Returns: + (list | None): List of annotation dictionaries or None if no annotations exist. + """ + ground_truth_annotations = _format_ground_truth_annotations_for_detection( + img_idx, image_path, batch, class_label_map + ) + prediction_annotations = _format_prediction_annotations( + image_path, prediction_metadata_map, class_label_map, class_map + ) + + annotations = [ + annotation for annotation in [ground_truth_annotations, prediction_annotations] if annotation is not None + ] + return [annotations] if annotations else None + + +def _create_prediction_metadata_map(model_predictions) -> dict: + """Create metadata map for model predictions by grouping them based on image ID.""" + pred_metadata_map = {} + for prediction in model_predictions: + pred_metadata_map.setdefault(prediction["image_id"], []) + pred_metadata_map[prediction["image_id"]].append(prediction) + + return pred_metadata_map + + +def _log_confusion_matrix(experiment, trainer, curr_step, curr_epoch) -> None: + """Log the confusion matrix to Comet experiment.""" + conf_mat = trainer.validator.confusion_matrix.matrix + names = [*list(trainer.data["names"].values()), "background"] + experiment.log_confusion_matrix( + matrix=conf_mat, labels=names, max_categories=len(names), epoch=curr_epoch, step=curr_step + ) + + +def _log_images(experiment, image_paths, curr_step: int | None, annotations=None) -> None: + """Log images to the experiment with optional annotations. + + This function logs images to a Comet ML experiment, optionally including annotation data for visualization such as + bounding boxes or segmentation masks. + + Args: + experiment (comet_ml.CometExperiment): The Comet ML experiment to log images to. + image_paths (list[Path]): List of paths to images that will be logged. + curr_step (int | None): Current training step/iteration for tracking in the experiment timeline. + annotations (list[list[dict]], optional): Nested list of annotation dictionaries for each image. Each annotation + contains visualization data like bounding boxes, labels, and confidence scores. + """ + if annotations: + for image_path, annotation in zip(image_paths, annotations): + experiment.log_image(image_path, name=image_path.stem, step=curr_step, annotations=annotation) + + else: + for image_path in image_paths: + experiment.log_image(image_path, name=image_path.stem, step=curr_step) + + +def _log_image_predictions(experiment, validator, curr_step) -> None: + """Log image predictions to a Comet ML experiment during model validation. + + This function processes validation data and formats both ground truth and prediction annotations for visualization + in the Comet dashboard. The function respects configured limits on the number of images to log. + + Args: + experiment (comet_ml.CometExperiment): The Comet ML experiment to log to. + validator (BaseValidator): The validator instance containing validation data and predictions. + curr_step (int): The current training step for logging timeline. + + Notes: + This function uses global state to track the number of logged predictions across calls. + It only logs predictions for supported tasks defined in COMET_SUPPORTED_TASKS. + The number of logged images is limited by the COMET_MAX_IMAGE_PREDICTIONS environment variable. + """ + global _comet_image_prediction_count + + task = validator.args.task + if task not in COMET_SUPPORTED_TASKS: + return + + jdict = validator.jdict + if not jdict: + return + + predictions_metadata_map = _create_prediction_metadata_map(jdict) + dataloader = validator.dataloader + class_label_map = validator.names + class_map = getattr(validator, "class_map", None) + + batch_logging_interval = _get_eval_batch_logging_interval() + max_image_predictions = _get_max_image_predictions_to_log() + + for batch_idx, batch in enumerate(dataloader): + if (batch_idx + 1) % batch_logging_interval != 0: + continue + + image_paths = batch["im_file"] + for img_idx, image_path in enumerate(image_paths): + if _comet_image_prediction_count >= max_image_predictions: + return + + image_path = Path(image_path) + annotations = _fetch_annotations( + img_idx, + image_path, + batch, + predictions_metadata_map, + class_label_map, + class_map=class_map, + ) + _log_images( + experiment, + [image_path], + curr_step, + annotations=annotations, + ) + _comet_image_prediction_count += 1 + + +def _log_plots(experiment, trainer) -> None: + """Log evaluation plots and label plots for the experiment. + + This function logs various evaluation plots and confusion matrices to the experiment tracking system. It handles + different types of metrics (SegmentMetrics, PoseMetrics, DetMetrics, OBBMetrics) and logs the appropriate plots for + each type. + + Args: + experiment (comet_ml.CometExperiment): The Comet ML experiment to log plots to. + trainer (ultralytics.engine.trainer.BaseTrainer): The trainer object containing validation metrics and save + directory information. + + Examples: + >>> from ultralytics.utils.callbacks.comet import _log_plots + >>> _log_plots(experiment, trainer) + """ + plot_filenames = None + if isinstance(trainer.validator.metrics, SegmentMetrics): + plot_filenames = [ + trainer.save_dir / f"{prefix}{plots}.png" + for plots in EVALUATION_PLOT_NAMES + for prefix in SEGMENT_METRICS_PLOT_PREFIX + ] + elif isinstance(trainer.validator.metrics, PoseMetrics): + plot_filenames = [ + trainer.save_dir / f"{prefix}{plots}.png" + for plots in EVALUATION_PLOT_NAMES + for prefix in POSE_METRICS_PLOT_PREFIX + ] + elif isinstance(trainer.validator.metrics, (DetMetrics, OBBMetrics)): + plot_filenames = [ + trainer.save_dir / f"{prefix}{plots}.png" + for plots in EVALUATION_PLOT_NAMES + for prefix in DETECTION_METRICS_PLOT_PREFIX + ] + + if plot_filenames is not None: + _log_images(experiment, plot_filenames, None) + + confusion_matrix_filenames = [trainer.save_dir / f"{plots}.png" for plots in CONFUSION_MATRIX_PLOT_NAMES] + _log_images(experiment, confusion_matrix_filenames, None) + + if not isinstance(trainer.validator.metrics, ClassifyMetrics): + label_plot_filenames = [trainer.save_dir / f"{labels}.jpg" for labels in LABEL_PLOT_NAMES] + _log_images(experiment, label_plot_filenames, None) + + +def _log_model(experiment, trainer) -> None: + """Log the best-trained model to Comet.ml.""" + model_name = _get_comet_model_name() + experiment.log_model(model_name, file_or_folder=str(trainer.best), file_name="best.pt", overwrite=True) + + +def _log_image_batches(experiment, trainer, curr_step: int) -> None: + """Log samples of image batches for train and validation.""" + _log_images(experiment, trainer.save_dir.glob("train_batch*.jpg"), curr_step) + _log_images(experiment, trainer.save_dir.glob("val_batch*.jpg"), curr_step) + + +def _log_asset(experiment, asset_path) -> None: + """Logs a specific asset file to the given experiment. + + This function facilitates logging an asset, such as a file, to the provided + experiment. It enables integration with experiment tracking platforms. + + Args: + experiment (comet_ml.CometExperiment): The experiment instance to which the asset will be logged. + asset_path (Path): The file path of the asset to log. + """ + experiment.log_asset(asset_path) + + +def _log_table(experiment, table_path) -> None: + """Logs a table to the provided experiment. + + This function is used to log a table file to the given experiment. The table is identified by its file path. + + Args: + experiment (comet_ml.CometExperiment): The experiment object where the table file will be logged. + table_path (Path): The file path of the table to be logged. + """ + experiment.log_table(str(table_path)) + + +def on_pretrain_routine_start(trainer) -> None: + """Create or resume a CometML experiment at the start of a YOLO pre-training routine.""" + _resume_or_create_experiment(trainer.args) + + +def on_train_epoch_end(trainer) -> None: + """Log metrics and save batch images at the end of training epochs.""" + experiment = comet_ml.get_running_experiment() + if not experiment: + return + + metadata = _fetch_trainer_metadata(trainer) + curr_epoch = metadata["curr_epoch"] + curr_step = metadata["curr_step"] + + experiment.log_metrics(trainer.label_loss_items(trainer.tloss, prefix="train"), step=curr_step, epoch=curr_epoch) + + +def on_fit_epoch_end(trainer) -> None: + """Log model assets at the end of each epoch during training. + + This function is called at the end of each training epoch to log metrics, learning rates, and model information to a + Comet ML experiment. It also logs model assets, confusion matrices, and image predictions based on configuration + settings. + + The function retrieves the current Comet ML experiment and logs various training metrics. If it's the first epoch, + it also logs model information. On specified save intervals, it logs the model, confusion matrix (if enabled), and + image predictions (if enabled). + + Args: + trainer (BaseTrainer): The YOLO trainer object containing training state, metrics, and configuration. + + Examples: + >>> # Inside a training loop + >>> on_fit_epoch_end(trainer) # Log metrics and assets to Comet ML + """ + experiment = comet_ml.get_running_experiment() + if not experiment: + return + + metadata = _fetch_trainer_metadata(trainer) + curr_epoch = metadata["curr_epoch"] + curr_step = metadata["curr_step"] + save_assets = metadata["save_assets"] + + experiment.log_metrics(trainer.metrics, step=curr_step, epoch=curr_epoch) + experiment.log_metrics(trainer.lr, step=curr_step, epoch=curr_epoch) + if curr_epoch == 1: + from ultralytics.utils.torch_utils import model_info_for_loggers + + experiment.log_metrics(model_info_for_loggers(trainer), step=curr_step, epoch=curr_epoch) + + if not save_assets: + return + + _log_model(experiment, trainer) + if _should_log_confusion_matrix(): + _log_confusion_matrix(experiment, trainer, curr_step, curr_epoch) + if _should_log_image_predictions(): + _log_image_predictions(experiment, trainer.validator, curr_step) + + +def on_train_end(trainer) -> None: + """Perform operations at the end of training.""" + experiment = comet_ml.get_running_experiment() + if not experiment: + return + + metadata = _fetch_trainer_metadata(trainer) + curr_epoch = metadata["curr_epoch"] + curr_step = metadata["curr_step"] + plots = trainer.args.plots + + _log_model(experiment, trainer) + if plots: + _log_plots(experiment, trainer) + + _log_confusion_matrix(experiment, trainer, curr_step, curr_epoch) + _log_image_predictions(experiment, trainer.validator, curr_step) + _log_image_batches(experiment, trainer, curr_step) + # log results table + table_path = trainer.save_dir / RESULTS_TABLE_NAME + if table_path.exists(): + _log_table(experiment, table_path) + + # log arguments YAML + args_path = trainer.save_dir / ARGS_YAML_NAME + if args_path.exists(): + _log_asset(experiment, args_path) + + experiment.end() + + global _comet_image_prediction_count + _comet_image_prediction_count = 0 + + +callbacks = ( + { + "on_pretrain_routine_start": on_pretrain_routine_start, + "on_train_epoch_end": on_train_epoch_end, + "on_fit_epoch_end": on_fit_epoch_end, + "on_train_end": on_train_end, + } + if comet_ml + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/dvc.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/dvc.py new file mode 100644 index 0000000000000000000000000000000000000000..b6ce8853621272e22813975aecea485886829b64 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/dvc.py @@ -0,0 +1,197 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from pathlib import Path + +from ultralytics.utils import LOGGER, SETTINGS, TESTS_RUNNING, checks + +try: + assert not TESTS_RUNNING # do not log pytest + assert SETTINGS["dvc"] is True # verify integration is enabled + import dvclive + + assert checks.check_version("dvclive", "2.11.0", verbose=True) + + import os + import re + + # DVCLive logger instance + live = None + _processed_plots = {} + + # `on_fit_epoch_end` is called on final validation (probably need to be fixed) for now this is the way we + # distinguish final evaluation of the best model vs last epoch validation + _training_epoch = False + +except (ImportError, AssertionError, TypeError): + dvclive = None + + +def _log_images(path: Path, prefix: str = "") -> None: + """Log images at specified path with an optional prefix using DVCLive. + + This function logs images found at the given path to DVCLive, organizing them by batch to enable slider + functionality in the UI. It processes image filenames to extract batch information and restructures the path + accordingly. + + Args: + path (Path): Path to the image file to be logged. + prefix (str, optional): Optional prefix to add to the image name when logging. + + Examples: + >>> from pathlib import Path + >>> _log_images(Path("runs/train/exp/val_batch0_pred.jpg"), prefix="validation") + """ + if live: + name = path.name + + # Group images by batch to enable sliders in UI + if m := re.search(r"_batch(\d+)", name): + ni = m[1] + new_stem = re.sub(r"_batch(\d+)", "_batch", path.stem) + name = (Path(new_stem) / ni).with_suffix(path.suffix) + + live.log_image(os.path.join(prefix, name), path) + + +def _log_plots(plots: dict, prefix: str = "") -> None: + """Log plot images for training progress if they have not been previously processed. + + Args: + plots (dict): Dictionary containing plot information with timestamps. + prefix (str, optional): Optional prefix to add to the logged image paths. + """ + for name, params in plots.items(): + timestamp = params["timestamp"] + if _processed_plots.get(name) != timestamp: + _log_images(name, prefix) + _processed_plots[name] = timestamp + + +def _log_confusion_matrix(validator) -> None: + """Log confusion matrix for a validator using DVCLive. + + This function processes the confusion matrix from a validator object and logs it to DVCLive by converting the matrix + into lists of target and prediction labels. + + Args: + validator (BaseValidator): The validator object containing the confusion matrix and class names. Must have + attributes confusion_matrix.matrix, confusion_matrix.task, and names. + """ + targets = [] + preds = [] + matrix = validator.confusion_matrix.matrix + names = list(validator.names.values()) + if validator.confusion_matrix.task in {"detect", "obb"}: + names += ["background"] + + for ti, pred in enumerate(matrix.T.astype(int)): + for pi, num in enumerate(pred): + targets.extend([names[ti]] * num) + preds.extend([names[pi]] * num) + + live.log_sklearn_plot("confusion_matrix", targets, preds, name="cf.json", normalized=True) + + +def on_pretrain_routine_start(trainer) -> None: + """Initialize DVCLive logger for training metadata during pre-training routine.""" + try: + global live + live = dvclive.Live(save_dvc_exp=True, cache_images=True) + LOGGER.info("DVCLive is detected and auto logging is enabled (run 'yolo settings dvc=False' to disable).") + except Exception as e: + LOGGER.warning(f"DVCLive installed but not initialized correctly, not logging this run. {e}") + + +def on_pretrain_routine_end(trainer) -> None: + """Log plots related to the training process at the end of the pretraining routine.""" + _log_plots(trainer.plots, "train") + + +def on_train_start(trainer) -> None: + """Log the training parameters if DVCLive logging is active.""" + if live: + live.log_params(trainer.args) + + +def on_train_epoch_start(trainer) -> None: + """Set the global variable _training_epoch value to True at the start of each training epoch.""" + global _training_epoch + _training_epoch = True + + +def on_fit_epoch_end(trainer) -> None: + """Log training metrics, model info, and advance to next step at the end of each fit epoch. + + This function is called at the end of each fit epoch during training. It logs various metrics including training + loss items, validation metrics, and learning rates. On the first epoch, it also logs model + information. Additionally, it logs training and validation plots and advances the DVCLive step counter. + + Args: + trainer (BaseTrainer): The trainer object containing training state, metrics, and plots. + + Notes: + This function only performs logging operations when DVCLive logging is active and during a training epoch. + The global variable _training_epoch is used to track whether the current epoch is a training epoch. + """ + global _training_epoch + if live and _training_epoch: + all_metrics = {**trainer.label_loss_items(trainer.tloss, prefix="train"), **trainer.metrics, **trainer.lr} + for metric, value in all_metrics.items(): + live.log_metric(metric, value) + + if trainer.epoch == 0: + from ultralytics.utils.torch_utils import model_info_for_loggers + + for metric, value in model_info_for_loggers(trainer).items(): + live.log_metric(metric, value, plot=False) + + _log_plots(trainer.plots, "train") + _log_plots(trainer.validator.plots, "val") + + live.next_step() + _training_epoch = False + + +def on_train_end(trainer) -> None: + """Log best metrics, plots, and confusion matrix at the end of training. + + This function is called at the conclusion of the training process to log final metrics, visualizations, and model + artifacts if DVCLive logging is active. It captures the best model performance metrics, training plots, validation + plots, and confusion matrix for later analysis. + + Args: + trainer (BaseTrainer): The trainer object containing training state, metrics, and validation results. + + Examples: + >>> # Inside a custom training loop + >>> from ultralytics.utils.callbacks.dvc import on_train_end + >>> on_train_end(trainer) # Log final metrics and artifacts + """ + if live: + # At the end log the best metrics. It runs validator on the best model internally. + all_metrics = {**trainer.label_loss_items(trainer.tloss, prefix="train"), **trainer.metrics, **trainer.lr} + for metric, value in all_metrics.items(): + live.log_metric(metric, value, plot=False) + + _log_plots(trainer.plots, "val") + _log_plots(trainer.validator.plots, "val") + _log_confusion_matrix(trainer.validator) + + if trainer.best.exists(): + live.log_artifact(trainer.best, copy=True, type="model") + + live.end() + + +callbacks = ( + { + "on_pretrain_routine_start": on_pretrain_routine_start, + "on_pretrain_routine_end": on_pretrain_routine_end, + "on_train_start": on_train_start, + "on_train_epoch_start": on_train_epoch_start, + "on_fit_epoch_end": on_fit_epoch_end, + "on_train_end": on_train_end, + } + if dvclive + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/hub.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/hub.py new file mode 100644 index 0000000000000000000000000000000000000000..0a6740705edc586e72496b29164eeba9ffde54a4 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/hub.py @@ -0,0 +1,111 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +import json +from time import time + +from ultralytics.hub import HUB_WEB_ROOT, PREFIX, HUBTrainingSession +from ultralytics.utils import LOGGER, RANK, SETTINGS +from ultralytics.utils.events import events + + +def on_pretrain_routine_start(trainer): + """Create a remote Ultralytics HUB session to log local model training.""" + if RANK in {-1, 0} and SETTINGS["hub"] is True and SETTINGS["api_key"] and trainer.hub_session is None: + trainer.hub_session = HUBTrainingSession.create_session(trainer.args.model, trainer.args) + + +def on_pretrain_routine_end(trainer): + """Initialize timers for upload rate limiting before training begins.""" + if session := getattr(trainer, "hub_session", None): + # Start timer for upload rate limit + session.timers = {"metrics": time(), "ckpt": time()} # start timer for session rate limiting + + +def on_fit_epoch_end(trainer): + """Upload training progress metrics to Ultralytics HUB at the end of each epoch.""" + if session := getattr(trainer, "hub_session", None): + # Upload metrics after validation ends + all_plots = { + **trainer.label_loss_items(trainer.tloss, prefix="train"), + **trainer.metrics, + } + if trainer.epoch == 0: + from ultralytics.utils.torch_utils import model_info_for_loggers + + all_plots = {**all_plots, **model_info_for_loggers(trainer)} + + session.metrics_queue[trainer.epoch] = json.dumps(all_plots) + + # If any metrics failed to upload previously, add them to the queue to attempt uploading again + if session.metrics_upload_failed_queue: + session.metrics_queue.update(session.metrics_upload_failed_queue) + + if time() - session.timers["metrics"] > session.rate_limits["metrics"]: + session.upload_metrics() + session.timers["metrics"] = time() # reset timer + session.metrics_queue = {} # reset queue + + +def on_model_save(trainer): + """Upload model checkpoints to Ultralytics HUB with rate limiting.""" + if session := getattr(trainer, "hub_session", None): + # Upload checkpoints with rate limiting + is_best = trainer.best_fitness == trainer.fitness + if time() - session.timers["ckpt"] > session.rate_limits["ckpt"]: + LOGGER.info(f"{PREFIX}Uploading checkpoint {HUB_WEB_ROOT}/models/{session.model.id}") + session.upload_model(trainer.epoch, trainer.last, is_best) + session.timers["ckpt"] = time() # reset timer + + +def on_train_end(trainer): + """Upload final model and metrics to Ultralytics HUB at the end of training.""" + if session := getattr(trainer, "hub_session", None): + # Upload final model and metrics with exponential backoff + LOGGER.info(f"{PREFIX}Syncing final model...") + session.upload_model( + trainer.epoch, + trainer.best, + map=trainer.metrics.get("metrics/mAP50-95(B)", 0), + final=True, + ) + session.alive = False # stop heartbeats + LOGGER.info(f"{PREFIX}Done ✅\n{PREFIX}View model at {session.model_url} 🚀") + + +def on_train_start(trainer): + """Run events on train start.""" + events(trainer.args, trainer.device) + + +def on_val_start(validator): + """Run events on validation start.""" + if not validator.training: + events(validator.args, validator.device) + + +def on_predict_start(predictor): + """Run events on predict start.""" + backend = getattr(getattr(predictor, "model", None), "backend", None) + events(predictor.args, predictor.device, backend=backend) + + +def on_export_start(exporter): + """Run events on export start.""" + events(exporter.args, exporter.device) + + +callbacks = ( + { + "on_pretrain_routine_start": on_pretrain_routine_start, + "on_pretrain_routine_end": on_pretrain_routine_end, + "on_fit_epoch_end": on_fit_epoch_end, + "on_model_save": on_model_save, + "on_train_end": on_train_end, + "on_train_start": on_train_start, + "on_val_start": on_val_start, + "on_predict_start": on_predict_start, + "on_export_start": on_export_start, + } + if SETTINGS["hub"] is True + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/mlflow.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/mlflow.py new file mode 100644 index 0000000000000000000000000000000000000000..f39ed4d98d154084859f02b8b7be56129019fb63 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/mlflow.py @@ -0,0 +1,162 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license +""" +MLflow Logging for Ultralytics YOLO. + +This module enables MLflow logging for Ultralytics YOLO. It logs metrics, parameters, and model artifacts. +For setting up, a tracking URI should be specified. The logging can be customized using environment variables. + +Commands: + 1. To set a project name: + `export MLFLOW_EXPERIMENT_NAME=` or use the project= argument + + 2. To set a run name: + `export MLFLOW_RUN=` or use the name= argument + + 3. To start a local MLflow server: + mlflow server --backend-store-uri runs/mlflow + It will by default start a local server at http://127.0.0.1:5000. + To specify a different URI, set the MLFLOW_TRACKING_URI environment variable. + + 4. To kill all running MLflow server instances: + ps aux | grep 'mlflow' | grep -v 'grep' | awk '{print $2}' | xargs kill -9 +""" + +import os +from pathlib import Path + +from ultralytics.utils import LOGGER, RUNS_DIR, SETTINGS, TESTS_RUNNING, colorstr + +PREFIX = colorstr("MLflow: ") + +try: + import mlflow + + assert hasattr(mlflow, "__version__") # verify package is not a local directory +except (ImportError, AssertionError): + mlflow = None + + +def sanitize_dict(x: dict) -> dict: + """Sanitize dictionary keys by removing parentheses and converting values to floats.""" + return {k.replace("(", "").replace(")", ""): float(v) for k, v in x.items()} + + +def on_pretrain_routine_end(trainer): + """Log training parameters to MLflow at the end of the pretraining routine. + + This function sets up MLflow logging based on environment variables and trainer arguments. It sets the tracking URI, + experiment name, and run name, then starts the MLflow run if not already active. It finally logs the parameters from + the trainer. + + Args: + trainer (ultralytics.engine.trainer.BaseTrainer): The training object with arguments and parameters to log. + + Notes: + MLFLOW_TRACKING_URI: The URI for MLflow tracking. If not set, defaults to 'runs/mlflow'. + MLFLOW_EXPERIMENT_NAME: The name of the MLflow experiment. If not set, defaults to trainer.args.project. + MLFLOW_RUN: The name of the MLflow run. If not set, defaults to trainer.args.name. + MLFLOW_KEEP_RUN_ACTIVE: Boolean indicating whether to keep the MLflow run active after training ends. + """ + # Resolve enablement at call time (not import time) so test/training order can never permanently disable MLflow: + # `add_integration_callbacks` imports this module on the first training, which may run with mlflow off. + if not mlflow or SETTINGS["mlflow"] is not True: + return + if TESTS_RUNNING and "test_mlflow" not in os.environ.get("PYTEST_CURRENT_TEST", ""): + return # do not log during unrelated pytest tests + + uri = os.environ.get("MLFLOW_TRACKING_URI") or str(RUNS_DIR / "mlflow") + LOGGER.debug(f"{PREFIX} tracking uri: {uri}") + + # Set experiment and run names + experiment_name = os.environ.get("MLFLOW_EXPERIMENT_NAME") or trainer.args.project or "/Shared/Ultralytics" + run_name = os.environ.get("MLFLOW_RUN") or trainer.args.name + + trainer._mlflow_active = False + trainer._mlflow_started_run = False + try: + mlflow.set_tracking_uri(uri) + mlflow.set_experiment(experiment_name) + mlflow.autolog() + active_run = mlflow.active_run() + if active_run is None: + active_run = mlflow.start_run(run_name=run_name) + trainer._mlflow_started_run = True + LOGGER.info(f"{PREFIX}logging run_id({active_run.info.run_id}) to {uri}") + if Path(uri).is_dir(): + LOGGER.info(f"{PREFIX}view at http://127.0.0.1:5000 with 'mlflow server --backend-store-uri {uri}'") + LOGGER.info(f"{PREFIX}disable with 'yolo settings mlflow=False'") + mlflow.log_params(dict(trainer.args)) + trainer._mlflow_active = True + except Exception as e: + LOGGER.warning(f"{PREFIX}Failed to initialize: {e}") + LOGGER.warning(f"{PREFIX}Not tracking this run") + if trainer._mlflow_started_run: + try: + mlflow.end_run() + except Exception: + pass + + +def _log_metrics(trainer, metrics): + """Log metrics to MLflow, disabling tracking for this run on failure so it never crashes training.""" + try: + mlflow.log_metrics(metrics=metrics, step=trainer.epoch) + except Exception as e: + LOGGER.warning(f"{PREFIX}metric logging failed, disabling tracking for this run: {e}") + trainer._mlflow_active = False + + +def on_train_epoch_end(trainer): + """Log training metrics at the end of each train epoch to MLflow.""" + if mlflow and getattr(trainer, "_mlflow_active", False): + _log_metrics( + trainer, + { + **sanitize_dict(trainer.lr), + **sanitize_dict(trainer.label_loss_items(trainer.tloss, prefix="train")), + }, + ) + + +def on_fit_epoch_end(trainer): + """Log training metrics at the end of each fit epoch to MLflow.""" + if mlflow and getattr(trainer, "_mlflow_active", False): + _log_metrics(trainer, sanitize_dict(trainer.metrics)) + + +def on_train_end(trainer): + """Log model artifacts at the end of training and close any run this callback opened.""" + if not mlflow: + return + if getattr(trainer, "_mlflow_active", False): + try: + mlflow.log_artifact(str(trainer.best.parent)) # log save_dir/weights directory with best.pt and last.pt + for f in trainer.save_dir.glob("*"): # log all other files in save_dir + if f.suffix in {".png", ".jpg", ".csv", ".pt", ".yaml"}: + mlflow.log_artifact(str(f)) + LOGGER.info( + f"{PREFIX}results logged to {mlflow.get_tracking_uri()}\n{PREFIX}disable with 'yolo settings mlflow=False'" + ) + except Exception as e: + LOGGER.warning(f"{PREFIX}failed to log artifacts: {e}") + if getattr(trainer, "_mlflow_started_run", False): # only close a run we created + if os.environ.get("MLFLOW_KEEP_RUN_ACTIVE", "False").lower() == "true": + LOGGER.info(f"{PREFIX}mlflow run still alive, remember to close it using mlflow.end_run()") + else: + try: + mlflow.end_run() + LOGGER.debug(f"{PREFIX}mlflow run ended") + except Exception: + pass + + +callbacks = ( + { + "on_pretrain_routine_end": on_pretrain_routine_end, + "on_train_epoch_end": on_train_epoch_end, + "on_fit_epoch_end": on_fit_epoch_end, + "on_train_end": on_train_end, + } + if mlflow + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/neptune.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/neptune.py new file mode 100644 index 0000000000000000000000000000000000000000..232f8e932869c7c2a10f8703d661515bf68bc5d7 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/neptune.py @@ -0,0 +1,126 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from ultralytics.utils import LOGGER, SETTINGS, TESTS_RUNNING + +try: + assert not TESTS_RUNNING # do not log pytest + assert SETTINGS["neptune"] is True # verify integration is enabled + + import neptune + from neptune.types import File + + assert hasattr(neptune, "__version__") + + run = None # NeptuneAI experiment logger instance + +except (ImportError, AssertionError): + neptune = None + + +def _log_scalars(scalars: dict, step: int = 0) -> None: + """Log scalars to the NeptuneAI experiment logger. + + Args: + scalars (dict): Dictionary of scalar values to log to NeptuneAI. + step (int, optional): The current step or iteration number for logging. + + Examples: + >>> metrics = {"mAP": 0.85, "loss": 0.32} + >>> _log_scalars(metrics, step=100) + """ + if run: + for k, v in scalars.items(): + run[k].append(value=v, step=step) + + +def _log_images(imgs_dict: dict, group: str = "") -> None: + """Log images to the NeptuneAI experiment logger. + + This function logs image data to Neptune.ai when a valid Neptune run is active. Images are organized under the + specified group name. + + Args: + imgs_dict (dict): Dictionary of images to log, with keys as image names and values as image data. + group (str, optional): Group name to organize images under in the Neptune UI. + + Examples: + >>> # Log validation images + >>> _log_images({"val_batch": img_tensor}, group="validation") + """ + if run: + for k, v in imgs_dict.items(): + run[f"{group}/{k}"].upload(File(v)) + + +def _log_plot(title: str, plot_path: str) -> None: + """Log plots to the NeptuneAI experiment logger.""" + import matplotlib.image as mpimg + import matplotlib.pyplot as plt + + img = mpimg.imread(plot_path) + fig = plt.figure() + ax = fig.add_axes([0, 0, 1, 1], frameon=False, aspect="auto", xticks=[], yticks=[]) # no ticks + ax.imshow(img) + run[f"Plots/{title}"].upload(fig) + + +def on_pretrain_routine_start(trainer) -> None: + """Initialize NeptuneAI run and log hyperparameters before training starts.""" + try: + global run + run = neptune.init_run( + project=trainer.args.project or "Ultralytics", + name=trainer.args.name, + tags=["Ultralytics"], + ) + run["Configuration/Hyperparameters"] = {k: "" if v is None else v for k, v in vars(trainer.args).items()} + except Exception as e: + LOGGER.warning(f"NeptuneAI installed but not initialized correctly, not logging this run. {e}") + + +def on_train_epoch_end(trainer) -> None: + """Log training metrics and learning rate at the end of each training epoch.""" + _log_scalars(trainer.label_loss_items(trainer.tloss, prefix="train"), trainer.epoch + 1) + _log_scalars(trainer.lr, trainer.epoch + 1) + if trainer.epoch == 1: + _log_images({f.stem: str(f) for f in trainer.save_dir.glob("train_batch*.jpg")}, "Mosaic") + + +def on_fit_epoch_end(trainer) -> None: + """Log model info and validation metrics at the end of each fit epoch.""" + if run and trainer.epoch == 0: + from ultralytics.utils.torch_utils import model_info_for_loggers + + run["Configuration/Model"] = model_info_for_loggers(trainer) + _log_scalars(trainer.metrics, trainer.epoch + 1) + + +def on_val_end(validator) -> None: + """Log validation images at the end of validation.""" + if run: + # Log val_labels and val_pred + _log_images({f.stem: str(f) for f in validator.save_dir.glob("val*.jpg")}, "Validation") + + +def on_train_end(trainer) -> None: + """Log final results, plots, and model weights at the end of training.""" + if run: + # Log final results, CM matrix + PR plots + for f in [*trainer.plots.keys(), *trainer.validator.plots.keys()]: + if "batch" not in f.name: + _log_plot(title=f.stem, plot_path=f) + # Log the final model + run[f"weights/{trainer.args.name or trainer.args.task}/{trainer.best.name}"].upload(File(str(trainer.best))) + + +callbacks = ( + { + "on_pretrain_routine_start": on_pretrain_routine_start, + "on_train_epoch_end": on_train_epoch_end, + "on_fit_epoch_end": on_fit_epoch_end, + "on_val_end": on_val_end, + "on_train_end": on_train_end, + } + if neptune + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/platform.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/platform.py new file mode 100644 index 0000000000000000000000000000000000000000..e9b544ffa8d85f617b4249d09a197a8eb31a8573 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/platform.py @@ -0,0 +1,553 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +import os +import platform +import re +import socket +import sys +from concurrent.futures import ThreadPoolExecutor +from math import isfinite +from pathlib import Path +from time import sleep, time + +from ultralytics.utils import ( + ENVIRONMENT, + GIT, + LOGGER, + PLATFORM_URL, + PYTHON_VERSION, + RANK, + SETTINGS, + TESTS_RUNNING, + Retry, + colorstr, +) + +PREFIX = colorstr("Platform: ") +PLATFORM_API_URL = f"{PLATFORM_URL}/api/webhooks" + + +def slugify(text): + """Convert text to URL-safe slug (e.g., 'My Project 1' -> 'my-project-1').""" + if not text: + return text + return re.sub(r"-+", "-", re.sub(r"[^a-z0-9\s-]", "", str(text).lower()).replace(" ", "-")).strip("-")[:128] + + +try: + assert not TESTS_RUNNING # do not log pytest + assert SETTINGS.get("platform", False) is True or os.getenv("ULTRALYTICS_API_KEY") or SETTINGS.get("api_key") + _api_key = os.getenv("ULTRALYTICS_API_KEY") or SETTINGS.get("api_key") + assert _api_key # verify API key is present + + import requests + + from ultralytics.utils.logger import ConsoleLogger, SystemLogger + from ultralytics.utils.torch_utils import model_info_for_loggers + + _executor = ThreadPoolExecutor(max_workers=10) # Bounded thread pool for async operations + +except (AssertionError, ImportError): + _api_key = None + + +def resolve_platform_uri(uri, hard=True): + """Resolve ul:// URIs to signed URLs by authenticating with Ultralytics Platform. + + Formats: + ul://username/datasets/slug -> Returns signed URL to NDJSON file + ul://username/project/model -> Returns signed URL to .pt file + + Args: + uri (str): Platform URI starting with "ul://". + hard (bool): Whether to raise an error if resolution fails. + + Returns: + (str | None): Signed URL on success, None if not found and hard=False. + + Raises: + ValueError: If API key is missing/invalid or URI format is wrong. + PermissionError: If access is denied. + RuntimeError: If resource is not ready (e.g., dataset still processing). + FileNotFoundError: If resource not found and hard=True. + ConnectionError: If network request fails and hard=True. + """ + import requests + + path = uri[5:] # Remove "ul://" + parts = path.split("/") + + api_key = os.getenv("ULTRALYTICS_API_KEY") or SETTINGS.get("api_key") + if not api_key: + raise ValueError(f"ULTRALYTICS_API_KEY required for '{uri}'. Get key at {PLATFORM_URL}/settings") + + base = PLATFORM_API_URL + headers = {"Authorization": f"Bearer {api_key}"} + + # ul://username/datasets/slug + if len(parts) == 3 and parts[1] == "datasets": + username, _, slug = parts + url = f"{base}/datasets/{username}/{slug}/export" + + # ul://username/project/model + elif len(parts) == 3: + username, project, model = parts + url = f"{base}/models/{username}/{project}/{model}/download" + + else: + raise ValueError(f"Invalid platform URI: {uri}. Use ul://user/datasets/name or ul://user/project/model") + + # (connect_timeout, read_timeout) — short connect so retries are fast, long read for server-side generation + timeout = (10, 3600) if "/datasets/" in url else (10, 90) + + try: + for attempt in range(5): + try: + r = requests.head(url, headers=headers, allow_redirects=False, timeout=timeout) + if r.status_code in {408, 429} or r.status_code >= 500: + raise requests.exceptions.HTTPError(f"HTTP {r.status_code}", response=r) + break + except ( + requests.exceptions.ConnectionError, + requests.exceptions.ReadTimeout, + requests.exceptions.HTTPError, + ) as e: + if attempt >= 4: + raise + delay = 2 * (2**attempt) # 2s, 4s, 8s, 16s backoff + LOGGER.warning(f"Retry {attempt + 1}/5 for {uri} in {delay}s: {e}") + sleep(delay) + except Exception as e: + if hard: + raise ConnectionError(f"Failed to resolve {uri}: {e}") from e + LOGGER.warning(f"Failed to resolve {uri}: {e}") + return None + + # Handle redirect responses (301, 302, 303, 307, 308) + if 300 <= r.status_code < 400 and "location" in r.headers: + return r.headers["location"] # Return signed URL + + # Handle error responses + if r.status_code == 401: + raise ValueError(f"Invalid ULTRALYTICS_API_KEY for '{uri}'") + if r.status_code == 403: + raise PermissionError(f"Access denied for '{uri}'. Check dataset/model visibility settings.") + if r.status_code == 404: + if hard: + raise FileNotFoundError(f"Not found on platform: {uri}") + LOGGER.warning(f"Not found on platform: {uri}") + return None + if r.status_code == 409: + raise RuntimeError(f"Resource not ready: {uri}. Dataset may still be processing.") + + # Unexpected response + r.raise_for_status() + raise RuntimeError(f"Unexpected response from platform for '{uri}': {r.status_code}") + + +def _interp_plot(plot, n=101): + """Interpolate plot curve data to n points to reduce storage size.""" + import numpy as np + + if not plot.get("x") or not plot.get("y"): + return plot # No interpolation needed (e.g., confusion_matrix) + + x, y = np.array(plot["x"]), np.array(plot["y"]) + if len(x) <= n: + return plot # Already small enough + + # New x values (101 points gives clean 0.01 increments: 0, 0.01, 0.02, ..., 1.0) + x_new = np.linspace(x[0], x[-1], n) + + # Interpolate y values (handle both 1D and 2D arrays) + if y.ndim == 1: + y_new = np.interp(x_new, x, y) + else: + y_new = np.array([np.interp(x_new, x, yi) for yi in y]) + + # Also interpolate ap if present (for PR curves) + result = {**plot, "x": x_new.tolist(), "y": y_new.tolist()} + if "ap" in plot: + result["ap"] = plot["ap"] # Keep AP values as-is (per-class scalars) + + return result + + +def _sanitize_json_value(value): + """Replace non-finite floats in payloads with None so requests JSON encoding succeeds.""" + if isinstance(value, dict): + return {k: _sanitize_json_value(v) for k, v in value.items()} + if isinstance(value, (list, tuple)): + return [_sanitize_json_value(v) for v in value] + if isinstance(value, float): + return value if isfinite(value) else None # avoid "Out of range float values are not JSON compliant" warnings + return value + + +def _send(event, data, project, name, model_id=None, retry=2): + """Send event to Platform endpoint with retry logic.""" + payload = {"event": event, "project": project, "name": name, "data": _sanitize_json_value(data)} + if model_id: + payload["modelId"] = model_id + + @Retry(times=retry, delay=1) + def post(): + r = requests.post( + f"{PLATFORM_API_URL}/training/metrics", + json=payload, + headers={"Authorization": f"Bearer {_api_key}"}, + timeout=30, + ) + if 400 <= r.status_code < 500 and r.status_code not in {408, 429}: + try: + msg = r.json().get("error", r.reason) + except Exception: + msg = r.reason + LOGGER.warning(f"{PREFIX}{msg}") + return None # Don't retry client errors (except 408 timeout, 429 rate limit) + r.raise_for_status() + return r.json() + + try: + return post() + except Exception as e: + LOGGER.debug(f"{PREFIX}Failed to send {event}: {e}") + return None + + +def _send_async(event, data, project, name, model_id=None): + """Send event asynchronously using bounded thread pool.""" + _executor.submit(_send, event, data, project, name, model_id) + + +def _handle_control_response(trainer, ctx, response): + """Apply centralized stop signals returned by Platform webhook responses. + + Notes: + ``ctx["cancelled"]`` is the durable cancellation signal. During startup, trainer setup later resets + ``trainer.stop``, so early stop requests still rely on ``on_pretrain_routine_end()`` to reapply the flag after + setup completes. + """ + if response and response.get("cancelled"): + ctx["cancelled"] = True + trainer.stop = True + LOGGER.info(f"{PREFIX}Training cancelled from Platform ⚠️") + + +def _upload_model(model_path, project, name, progress=False, retry=1, model_id=None): + """Upload model checkpoint to Platform via signed URL.""" + from ultralytics.utils.uploads import safe_upload + + model_path = Path(model_path) + if not model_path.exists(): + LOGGER.warning(f"{PREFIX}Model file not found: {model_path}") + return None + + # Get signed upload URL from Platform (server sanitizes filename for storage safety) + @Retry(times=3, delay=2) + def get_signed_url(): + payload = {"project": project, "name": name, "filename": model_path.name} + if model_id: + payload["modelId"] = model_id # Direct lookup avoids slug mismatch from auto-increment + r = requests.post( + f"{PLATFORM_API_URL}/models/upload", + json=payload, + headers={"Authorization": f"Bearer {_api_key}"}, + timeout=30, + ) + r.raise_for_status() + return r.json() + + try: + data = get_signed_url() + except Exception as e: + LOGGER.warning(f"{PREFIX}Failed to get upload URL: {e}") + return None + + # Upload to GCS using safe_upload with retry logic and optional progress bar + if safe_upload(file=model_path, url=data["uploadUrl"], retry=retry, progress=progress): + return data.get("gcsPath") + return None + + +def _upload_model_async(model_path, project, name, model_id=None): + """Upload model asynchronously using bounded thread pool.""" + _executor.submit(_upload_model, model_path, project, name, model_id=model_id) + + +def _get_environment_info(): + """Collect comprehensive environment info using existing ultralytics utilities.""" + import shutil + + import psutil + import torch + + from ultralytics import __version__ + from ultralytics.utils.torch_utils import get_cpu_info, get_gpu_info + + # Get RAM and disk totals + memory = psutil.virtual_memory() + disk_usage = shutil.disk_usage("/") + + env = { + "ultralyticsVersion": __version__, + "hostname": socket.gethostname(), + "os": platform.platform(), + "environment": ENVIRONMENT, + "pythonVersion": PYTHON_VERSION, + "pythonExecutable": sys.executable, + "cpuCount": os.cpu_count() or 0, + "cpu": get_cpu_info(), + "command": " ".join(sys.argv), + "totalRamGb": round(memory.total / (1 << 30), 1), # Total RAM in GB + "totalDiskGb": round(disk_usage.total / (1 << 30), 1), # Total disk in GB + } + + # Git info using cached GIT singleton (no subprocess calls) + try: + if GIT.is_repo: + if GIT.origin: + env["gitRepository"] = GIT.origin + if GIT.branch: + env["gitBranch"] = GIT.branch + if GIT.commit: + env["gitCommit"] = GIT.commit[:12] # Short hash + if GIT.message: + env["gitCommitMessage"] = GIT.message + except Exception: + pass + + # GPU info + try: + if torch.cuda.is_available(): + env["gpuCount"] = torch.cuda.device_count() + env["gpuType"] = get_gpu_info(0) if torch.cuda.device_count() > 0 else None + except Exception: + pass + + return env + + +def _get_project_name(trainer): + """Get slugified project and name from trainer args.""" + raw = str(trainer.args.project) + parts = raw.split("/", 1) + project = f"{parts[0]}/{slugify(parts[1])}" if len(parts) == 2 else slugify(raw) + return project, slugify(str(trainer.args.name or "train")) + + +def on_pretrain_routine_start(trainer): + """Initialize Platform logging at training start.""" + if RANK not in {-1, 0} or not trainer.args.project: + return + + project, name = _get_project_name(trainer) + LOGGER.info(f"{PREFIX}Streaming training metrics to Platform") + + # Single dict for all platform callback state (like trainer.hub_session for HUB callbacks) + ctx = {"model_id": None, "last_upload": time(), "cancelled": False, "console_logger": None, "system_logger": None} + trainer.platform = ctx + + # Create callback to send console output to Platform + def send_console_output(content, line_count, chunk_id): + """Send batched console output to Platform webhook.""" + _send_async( + "console_output", + {"chunkId": chunk_id, "content": content, "lineCount": line_count}, + project, + name, + ctx["model_id"], + ) + + # Start console capture with batching (5 lines or 5 seconds) + ctx["console_logger"] = ConsoleLogger(batch_size=5, flush_interval=5.0, on_flush=send_console_output) + ctx["console_logger"].start_capture() + + # Collect environment info (W&B-style metadata) + environment = _get_environment_info() + + # Build trainArgs - callback runs before get_dataset() so args.data is still original (e.g., ul:// URIs) + # Note: model_info is sent later in on_fit_epoch_end (epoch 0) when the model is actually loaded + train_args = {k: str(v) for k, v in vars(trainer.args).items()} + + # Send synchronously to get modelId for subsequent webhooks (critical, more retries) + response = _send( + "training_started", + { + "trainArgs": train_args, + "epochs": trainer.epochs, + "device": str(trainer.device), + "environment": environment, + }, + project, + name, + retry=4, + ) + if response and response.get("modelId"): + ctx["model_id"] = response["modelId"] + # Server returns actual slug (may differ from requested name due to auto-increment, e.g. "train" → "train-2") + if response.get("modelSlug"): + ctx["model_slug"] = response["modelSlug"] + url = f"{PLATFORM_URL}/{project}/{ctx['model_slug']}" + LOGGER.info(f"{PREFIX}View model at {url}") + # Note: trainer.stop is set in on_pretrain_routine_end (after _setup_train resets it) + _handle_control_response(trainer, ctx, response) + else: + LOGGER.warning(f"{PREFIX}Training will not be tracked on Platform") + trainer.platform = None # Disable further callbacks + + +def on_pretrain_routine_end(trainer): + """Apply pre-start cancellation after _setup_train resets trainer.stop.""" + ctx = getattr(trainer, "platform", None) + if ctx and ctx["cancelled"]: + LOGGER.info(f"{PREFIX}Training cancelled from Platform before starting ✅") + trainer.stop = True + + +def on_fit_epoch_end(trainer): + """Log training and system metrics at epoch end.""" + ctx = getattr(trainer, "platform", None) + if not ctx or RANK not in {-1, 0} or not trainer.args.project: + return + + project, name = _get_project_name(trainer) + metrics = {**trainer.label_loss_items(trainer.tloss, prefix="train"), **trainer.metrics} + + if trainer.optimizer and trainer.optimizer.param_groups: + metrics["lr"] = trainer.optimizer.param_groups[0]["lr"] + + # Extract model info at epoch 0 (sent as separate field, not in metrics) + model_info = None + if trainer.epoch == 0: + try: + info = model_info_for_loggers(trainer) + model_info = { + "parameters": info.get("model/parameters", 0), + "gflops": info.get("model/GFLOPs", 0), + "speedMs": info.get("model/speed_PyTorch(ms)", 0), + } + except Exception: + pass + + # Get system metrics (cache SystemLogger in platform context for efficiency) + system = {} + try: + if not ctx["system_logger"]: + ctx["system_logger"] = SystemLogger(all_drives=True) + system = ctx["system_logger"].get_metrics(rates=True) + except Exception: + pass + + payload = { + "epoch": trainer.epoch, + "metrics": metrics, + "system": system, + "fitness": trainer.fitness, + "best_fitness": trainer.best_fitness, + } + if model_info: + payload["modelInfo"] = model_info + + def _send_and_check_cancel(): + """Send epoch_end and check response for cancellation (runs in background thread).""" + response = _send("epoch_end", payload, project, name, ctx["model_id"], retry=1) + _handle_control_response(trainer, ctx, response) + + _executor.submit(_send_and_check_cancel) + + +def on_model_save(trainer): + """Upload model checkpoint (rate limited to every 15 min).""" + ctx = getattr(trainer, "platform", None) + if not ctx or RANK not in {-1, 0} or not trainer.args.project: + return + + # Rate limit to every 15 minutes (900 seconds) + if time() - ctx["last_upload"] < 900: + return + + model_path = trainer.best if trainer.best and Path(trainer.best).exists() else trainer.last + if not model_path: + return + + project, name = _get_project_name(trainer) + _upload_model_async(model_path, project, name, model_id=ctx["model_id"]) + ctx["last_upload"] = time() + + +def on_train_end(trainer): + """Log final results, upload best model, and send validation plot data.""" + ctx = getattr(trainer, "platform", None) + if not ctx or RANK not in {-1, 0} or not trainer.args.project: + return + + project, name = _get_project_name(trainer) + + if ctx["cancelled"]: + LOGGER.info(f"{PREFIX}Uploading partial results for cancelled training") + + # Stop console capture + if ctx["console_logger"]: + ctx["console_logger"].stop_capture() + ctx["console_logger"] = None + + # Upload best model (blocking with progress bar to ensure it completes) + gcs_path = None + model_size = None + if trainer.best and Path(trainer.best).exists(): + model_size = Path(trainer.best).stat().st_size + gcs_path = _upload_model(trainer.best, project, name, progress=True, retry=3, model_id=ctx["model_id"]) + if not gcs_path: + LOGGER.warning(f"{PREFIX}Model will not be available for download on Platform (upload failed)") + + # Collect plots from trainer and validator, deduplicating by type + plots_by_type = {} + for info in getattr(trainer, "plots", {}).values(): + if info.get("data") and info["data"].get("type"): + plots_by_type[info["data"]["type"]] = info["data"] + for info in getattr(getattr(trainer, "validator", None), "plots", {}).values(): + if info.get("data") and info["data"].get("type"): + plots_by_type.setdefault(info["data"]["type"], info["data"]) # Don't overwrite trainer plots + plots = [_interp_plot(p) for p in plots_by_type.values()] # Interpolate curves to reduce size + + # Get class names + names = getattr(getattr(trainer, "validator", None), "names", None) or (trainer.data or {}).get("names") + class_names = list(names.values()) if isinstance(names, dict) else list(names) if names else None + + # stopper.best_epoch is 1-indexed; -1 aligns with the 0-indexed `epoch` field + best_epoch = max(0, getattr(getattr(trainer, "stopper", None), "best_epoch", trainer.epoch + 1) - 1) + + _send( + "training_complete", + { + "results": { + "metrics": {**trainer.metrics, "fitness": trainer.fitness}, + "bestEpoch": best_epoch, + "bestFitness": trainer.best_fitness, + "modelPath": gcs_path, # Only send GCS path, not local path + "modelSize": model_size, + }, + "classNames": class_names, + "plots": plots, + }, + project, + name, + ctx["model_id"], + retry=4, # Critical, more retries + ) + url = f"{PLATFORM_URL}/{project}/{ctx.get('model_slug', name)}" + LOGGER.info(f"{PREFIX}View results at {url}") + + +callbacks = ( + { + "on_pretrain_routine_start": on_pretrain_routine_start, + "on_pretrain_routine_end": on_pretrain_routine_end, + "on_fit_epoch_end": on_fit_epoch_end, + "on_model_save": on_model_save, + "on_train_end": on_train_end, + } + if _api_key + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/raytune.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/raytune.py new file mode 100644 index 0000000000000000000000000000000000000000..e2a287318b9cf71f31e7dfa2e3bf5238ec0d20d8 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/raytune.py @@ -0,0 +1,42 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from ultralytics.utils import SETTINGS + +try: + assert SETTINGS["raytune"] is True # verify integration is enabled + import ray + from ray import tune + from ray.air import session + +except (ImportError, AssertionError): + tune = None + + +def on_fit_epoch_end(trainer): + """Report training metrics to Ray Tune at epoch end when a Ray session is active. + + Captures metrics from the trainer object and sends them to Ray Tune with the current epoch number, enabling + hyperparameter tuning optimization. Only executes when within an active Ray Tune session. + + Args: + trainer (ultralytics.engine.trainer.BaseTrainer): The Ultralytics trainer object containing metrics and epochs. + + Examples: + >>> # Called automatically by the Ultralytics training loop + >>> on_fit_epoch_end(trainer) + + References: + Ray Tune docs: https://docs.ray.io/en/latest/tune/index.html + """ + if ray.train._internal.session.get_session(): # check if Ray Tune session is active + metrics = trainer.metrics + session.report({**metrics, **{"epoch": trainer.epoch + 1}}) + + +callbacks = ( + { + "on_fit_epoch_end": on_fit_epoch_end, + } + if tune + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/tensorboard.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/tensorboard.py new file mode 100644 index 0000000000000000000000000000000000000000..35a1cb14ab543072fe8bd78fe1d97013cdbff91c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/tensorboard.py @@ -0,0 +1,125 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from ultralytics.utils import LOGGER, SETTINGS, TESTS_RUNNING, colorstr, torch_utils +from ultralytics.utils.torch_utils import smart_inference_mode + +try: + assert not TESTS_RUNNING # do not log pytest + assert SETTINGS["tensorboard"] is True # verify integration is enabled + WRITER = None # TensorBoard SummaryWriter instance + PREFIX = colorstr("TensorBoard: ") + + # Imports below only required if TensorBoard enabled + from copy import deepcopy + + import torch + from torch.utils.tensorboard import SummaryWriter + +except (ImportError, AssertionError, TypeError, AttributeError): + # TypeError for handling 'Descriptors cannot not be created directly.' protobuf errors in Windows + # AttributeError: module 'tensorflow' has no attribute 'io' if 'tensorflow' not installed + SummaryWriter = None + + +def _log_scalars(scalars: dict, step: int = 0) -> None: + """Log scalar values to TensorBoard. + + Args: + scalars (dict): Dictionary of scalar values to log to TensorBoard. Keys are scalar names and values are the + corresponding scalar values. + step (int): Global step value to record with the scalar values. Used for x-axis in TensorBoard graphs. + + Examples: + Log training metrics + >>> metrics = {"loss": 0.5, "accuracy": 0.95} + >>> _log_scalars(metrics, step=100) + """ + if WRITER: + for k, v in scalars.items(): + WRITER.add_scalar(k, v, step) + + +@smart_inference_mode() +def _log_tensorboard_graph(trainer) -> None: + """Log model graph to TensorBoard. + + This function attempts to visualize the model architecture in TensorBoard by tracing the model with a dummy input + tensor. It first tries a simple method suitable for YOLO models, and if that fails, falls back to a more complex + approach for models like RTDETR that may require special handling. + + Args: + trainer (ultralytics.engine.trainer.BaseTrainer): The trainer object containing the model to visualize. Must + have attributes model and args with imgsz. + + Notes: + This function requires TensorBoard integration to be enabled and the global WRITER to be initialized. + It handles potential warnings from the PyTorch JIT tracer and attempts to gracefully handle different + model architectures. + """ + # Input image + imgsz = trainer.args.imgsz + imgsz = (imgsz, imgsz) if isinstance(imgsz, int) else imgsz + p = next(trainer.model.parameters()) # for device, type + im = torch.zeros((1, 3, *imgsz), device=p.device, dtype=p.dtype) # input image (must be zeros, not empty) + + # Try simple method first (YOLO) + try: + trainer.model.eval() # place in .eval() mode to avoid BatchNorm statistics changes + WRITER.add_graph(torch.jit.trace(torch_utils.unwrap_model(trainer.model), im, strict=False), []) + LOGGER.info(f"{PREFIX}model graph visualization added ✅") + return + except Exception as e1: + # Fallback to TorchScript export steps (RTDETR) + try: + model = deepcopy(torch_utils.unwrap_model(trainer.model)) + model.eval() + model = model.fuse(verbose=False) + for m in model.modules(): + if hasattr(m, "export"): # Detect, RTDETRDecoder (Segment and Pose use Detect base class) + m.export = True + m.format = "torchscript" + model(im) # dry run + WRITER.add_graph(torch.jit.trace(model, im, strict=False), []) + LOGGER.info(f"{PREFIX}model graph visualization added ✅") + except Exception as e2: + LOGGER.warning(f"{PREFIX}TensorBoard graph visualization failure: {e1} -> {e2}") + + +def on_pretrain_routine_start(trainer) -> None: + """Initialize TensorBoard logging with SummaryWriter.""" + if SummaryWriter: + try: + global WRITER + WRITER = SummaryWriter(str(trainer.save_dir)) + LOGGER.info(f"{PREFIX}Start with 'tensorboard --logdir {trainer.save_dir}', view at http://localhost:6006/") + except Exception as e: + LOGGER.warning(f"{PREFIX}TensorBoard not initialized correctly, not logging this run. {e}") + + +def on_train_start(trainer) -> None: + """Log TensorBoard graph.""" + if WRITER: + _log_tensorboard_graph(trainer) + + +def on_train_epoch_end(trainer) -> None: + """Log scalar statistics at the end of a training epoch.""" + _log_scalars(trainer.label_loss_items(trainer.tloss, prefix="train"), trainer.epoch + 1) + _log_scalars(trainer.lr, trainer.epoch + 1) + + +def on_fit_epoch_end(trainer) -> None: + """Log epoch metrics at end of training epoch.""" + _log_scalars(trainer.metrics, trainer.epoch + 1) + + +callbacks = ( + { + "on_pretrain_routine_start": on_pretrain_routine_start, + "on_train_start": on_train_start, + "on_fit_epoch_end": on_fit_epoch_end, + "on_train_epoch_end": on_train_epoch_end, + } + if SummaryWriter + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/wb.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/wb.py new file mode 100644 index 0000000000000000000000000000000000000000..b7e8dc36342728ca57a752036f188d68f676661c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/callbacks/wb.py @@ -0,0 +1,199 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from ultralytics.utils import SETTINGS, TESTS_RUNNING +from ultralytics.utils.torch_utils import model_info_for_loggers + +try: + assert not TESTS_RUNNING # do not log pytest + assert SETTINGS["wandb"] is True # verify integration is enabled + import wandb as wb + + assert hasattr(wb, "__version__") # verify package is not directory + _processed_plots = {} + +except (ImportError, AssertionError): + wb = None + + +def _custom_table(x, y, classes, title="Precision Recall Curve", x_title="Recall", y_title="Precision"): + """Create and log a custom metric visualization table. + + This function crafts a custom metric visualization that mimics the behavior of the default wandb precision-recall + curve while allowing for enhanced customization. The visual metric is useful for monitoring model performance across + different classes. + + Args: + x (list): Values for the x-axis; expected to have length N. + y (list): Corresponding values for the y-axis; also expected to have length N. + classes (list): Labels identifying the class of each point; length N. + title (str, optional): Title for the plot. + x_title (str, optional): Label for the x-axis. + y_title (str, optional): Label for the y-axis. + + Returns: + (wandb.Object): A wandb object suitable for logging, showcasing the crafted metric visualization. + """ + import polars as pl # scope for faster 'import ultralytics' + import polars.selectors as cs + + df = pl.DataFrame({"class": classes, "y": y, "x": x}).with_columns(cs.numeric().round(3)) + data = df.select(["class", "y", "x"]).rows() + + fields = {"x": "x", "y": "y", "class": "class"} + string_fields = {"title": title, "x-axis-title": x_title, "y-axis-title": y_title} + return wb.plot_table( + "wandb/area-under-curve/v0", + wb.Table(data=data, columns=["class", "y", "x"]), + fields=fields, + string_fields=string_fields, + ) + + +def _plot_curve( + x, + y, + names=None, + id="precision-recall", + title="Precision Recall Curve", + x_title="Recall", + y_title="Precision", + num_x=100, + only_mean=False, +): + """Log a metric curve visualization. + + This function generates a metric curve based on input data and logs the visualization to wandb. The curve can + represent aggregated data (mean) or individual class data, depending on the 'only_mean' flag. + + Args: + x (np.ndarray): Data points for the x-axis with length N. + y (np.ndarray): Corresponding data points for the y-axis with shape (C, N), where C is the number of classes. + names (list, optional): Names of the classes corresponding to the y-axis data; length C. + id (str, optional): Unique identifier for the logged data in wandb. + title (str, optional): Title for the visualization plot. + x_title (str, optional): Label for the x-axis. + y_title (str, optional): Label for the y-axis. + num_x (int, optional): Number of interpolated data points for visualization. + only_mean (bool, optional): Flag to indicate if only the mean curve should be plotted. + + Notes: + The function leverages the '_custom_table' function to generate the actual visualization. + """ + import numpy as np + + # Create new x + if names is None: + names = [] + x_new = np.linspace(x[0], x[-1], num_x).round(5) + + # Create arrays for logging + x_log = x_new.tolist() + y_log = np.interp(x_new, x, np.mean(y, axis=0)).round(3).tolist() + + if only_mean: + table = wb.Table(data=list(zip(x_log, y_log)), columns=[x_title, y_title]) + wb.run.log({title: wb.plot.line(table, x_title, y_title, title=title)}) + else: + classes = ["mean"] * len(x_log) + for i, yi in enumerate(y): + x_log.extend(x_new) # add new x + y_log.extend(np.interp(x_new, x, yi)) # interpolate y to new x + classes.extend([names[i]] * len(x_new)) # add class names + wb.log({id: _custom_table(x_log, y_log, classes, title, x_title, y_title)}, commit=False) + + +def _log_plots(plots, step): + """Log plots to WandB at a specific step if they haven't been logged already. + + This function checks each plot in the input dictionary against previously processed plots and logs new or updated + plots to WandB at the specified step. + + Args: + plots (dict): Dictionary of plots to log, where keys are plot names and values are dictionaries containing plot + metadata including timestamps. + step (int): The step/epoch at which to log the plots in the WandB run. + + Notes: + The function uses a shallow copy of the plots dictionary to prevent modification during iteration. + Plots are identified by their stem name (filename without extension). + Each plot is logged as a WandB Image object. + """ + for name, params in plots.copy().items(): # shallow copy to prevent plots dict changing during iteration + timestamp = params["timestamp"] + if _processed_plots.get(name) != timestamp: + wb.run.log({name.stem: wb.Image(str(name))}, step=step) + _processed_plots[name] = timestamp + + +def on_pretrain_routine_start(trainer): + """Initialize and start wandb project if module is present.""" + if not wb.run: + from datetime import datetime + from pathlib import Path + + name = str(trainer.args.name).replace("/", "-").replace(" ", "_") + latest_run = Path(trainer.save_dir) / "wandb" / "latest-run" + resuming = trainer.args.resume and latest_run.exists() + wb.init( + project=str(trainer.args.project).replace("/", "-") if trainer.args.project else "Ultralytics", + name=name, + config=vars(trainer.args), + id=latest_run.resolve().name.split("-", 2)[2] + if resuming + else f"{name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}", + resume="allow" if resuming else None, + dir=str(trainer.save_dir), + ) + + +def on_fit_epoch_end(trainer): + """Log training metrics and model information at the end of an epoch.""" + _log_plots(trainer.plots, step=trainer.epoch + 1) + _log_plots(trainer.validator.plots, step=trainer.epoch + 1) + if trainer.epoch == 0: + wb.run.log(model_info_for_loggers(trainer), step=trainer.epoch + 1) + wb.run.log(trainer.metrics, step=trainer.epoch + 1, commit=True) # commit forces sync + + +def on_train_epoch_end(trainer): + """Log metrics and save images at the end of each training epoch.""" + wb.run.log(trainer.label_loss_items(trainer.tloss, prefix="train"), step=trainer.epoch + 1) + wb.run.log(trainer.lr, step=trainer.epoch + 1) + if trainer.epoch == 1: + _log_plots(trainer.plots, step=trainer.epoch + 1) + + +def on_train_end(trainer): + """Save the best model as an artifact and log final plots at the end of training.""" + _log_plots(trainer.validator.plots, step=trainer.epoch + 1) + _log_plots(trainer.plots, step=trainer.epoch + 1) + art = wb.Artifact(type="model", name=f"run_{wb.run.id}_model") + if trainer.best.exists(): + art.add_file(trainer.best) + wb.run.log_artifact(art, aliases=["best"]) + # Check if we actually have plots to save + if trainer.args.plots and hasattr(trainer.validator.metrics, "curves_results"): + for curve_name, curve_values in zip(trainer.validator.metrics.curves, trainer.validator.metrics.curves_results): + x, y, x_title, y_title = curve_values + _plot_curve( + x, + y, + names=list(trainer.validator.metrics.names.values()), + id=f"curves/{curve_name}", + title=curve_name, + x_title=x_title, + y_title=y_title, + ) + wb.run.finish() # required or run continues on dashboard + + +callbacks = ( + { + "on_pretrain_routine_start": on_pretrain_routine_start, + "on_train_epoch_end": on_train_epoch_end, + "on_fit_epoch_end": on_fit_epoch_end, + "on_train_end": on_train_end, + } + if wb + else {} +) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/checks.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/checks.py new file mode 100644 index 0000000000000000000000000000000000000000..2c7a825d21350a239dd1669771904ede6d8f15da --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/checks.py @@ -0,0 +1,1084 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import ast +import functools +import glob +import inspect +import math +import os +import platform +import re +import shutil +import subprocess +import sys +import time +from importlib import metadata +from pathlib import Path +from types import SimpleNamespace + +import cv2 +import numpy as np +import torch + +from ultralytics.utils import ( + ARM64, + ASSETS, + ASSETS_URL, + AUTOINSTALL, + GIT, + IS_COLAB, + IS_DOCKER, + IS_JETSON, + IS_KAGGLE, + IS_PIP_PACKAGE, + LINUX, + LOGGER, + MACOS, + ONLINE, + PLATFORM_URL, + PYTHON_VERSION, + RKNN_CHIPS, + ROOT, + TORCH_VERSION, + TORCHVISION_VERSION, + USER_CONFIG_DIR, + WINDOWS, + Retry, + ThreadingLocked, + TryExcept, + clean_url, + colorstr, + downloads, + is_github_action_running, + url2file, +) + +REMOTE_FILE_PREFIXES = ("https://", "http://", "rtsp://", "rtmp://", "tcp://", "ul://", "gs://") + + +def normalize_platform_uri(uri): + """Rewrite an Ultralytics Platform web URL to its ul:// URI so it can be loaded directly as data or model. + + Args: + uri (str | Path): Resource identifier, e.g. "https://platform.ultralytics.com/user/datasets/slug". + + Returns: + (str | Path): "ul://user/datasets/slug" for Platform web URLs, otherwise the input unchanged. + """ + s = str(uri) + return f"ul://{s[len(PLATFORM_URL) + 1 :].strip('/')}" if s.startswith(f"{PLATFORM_URL}/") else uri + + +def parse_requirements(file_path=ROOT.parent / "requirements.txt", package=""): + """Parse a requirements.txt file, ignoring lines that start with '#' and any text after '#'. + + Args: + file_path (Path): Path to the requirements.txt file. + package (str, optional): Python package to use instead of requirements.txt file. + + Returns: + requirements (list[SimpleNamespace]): List of parsed requirements as SimpleNamespace objects with `name` and + `specifier` attributes. + + Examples: + >>> from ultralytics.utils.checks import parse_requirements + >>> parse_requirements(package="ultralytics") + """ + if package: + requires = [x for x in metadata.distribution(package).requires if "extra == " not in x] + else: + requires = Path(file_path).read_text().splitlines() + + requirements = [] + for line in requires: + line = line.strip() + if line and not line.startswith("#"): + line = line.partition("#")[0].strip() # ignore inline comments + if match := re.match(r"([a-zA-Z0-9-_]+)\s*([<>!=~]+.*)?", line): + requirements.append(SimpleNamespace(name=match[1], specifier=match[2].strip() if match[2] else "")) + + return requirements + + +def get_distribution_name(import_name: str) -> str: + """Get the pip distribution name for a given import name (e.g., 'cv2' -> 'opencv-python-headless').""" + for dist in metadata.distributions(): + top_level = (dist.read_text("top_level.txt") or "").split() + if import_name in top_level: + return dist.metadata["Name"] + return import_name + + +@functools.lru_cache +def parse_version(version="0.0.0") -> tuple: + """Convert a version string to a tuple of integers, ignoring any extra non-numeric string attached to the version. + + Args: + version (str): Version string, i.e. '2.0.1+cpu' + + Returns: + (tuple): Tuple of integers representing the numeric part of the version, i.e. (2, 0, 1) + """ + try: + nums = [int(x) for x in re.findall(r"\d+", version)[:3]] + return tuple(nums + [0] * (3 - len(nums))) # pad to 3, i.e. '2.0.1+cpu' -> (2, 0, 1), '2' -> (2, 0, 0) + except Exception as e: + LOGGER.warning(f"failure for parse_version({version}), returning (0, 0, 0): {e}") + return 0, 0, 0 + + +def is_ascii(s) -> bool: + """Check if a string is composed of only ASCII characters. + + Args: + s (str | list | tuple | dict): Input to be checked (all are converted to string for checking). + + Returns: + (bool): True if the string is composed only of ASCII characters, False otherwise. + """ + return all(ord(c) < 128 for c in str(s)) + + +def check_imgsz(imgsz, stride=32, min_dim=1, max_dim=2, floor=0): + """Verify image size is a multiple of the given stride in each dimension. If the image size is not a multiple of the + stride, update it to the nearest multiple of the stride that is greater than or equal to the given floor value. + + Args: + imgsz (int | list[int]): Image size. + stride (int): Stride value. + min_dim (int): Minimum number of dimensions. + max_dim (int): Maximum number of dimensions. + floor (int): Minimum allowed value for image size. + + Returns: + (list[int] | int): Updated image size. + """ + # Convert stride to integer if it is a tensor + stride = int(stride.max() if isinstance(stride, torch.Tensor) else stride) + + # Convert image size to list if it is an integer + if isinstance(imgsz, int): + imgsz = [imgsz] + elif isinstance(imgsz, (list, tuple)): + imgsz = list(imgsz) + elif isinstance(imgsz, str): # i.e. '640' or '[640,640]' + imgsz = [int(imgsz)] if imgsz.isnumeric() else ast.literal_eval(imgsz) + else: + raise TypeError( + f"'imgsz={imgsz}' is of invalid type {type(imgsz).__name__}. " + f"Valid imgsz types are int i.e. 'imgsz=640' or list i.e. 'imgsz=[640,640]'" + ) + + # Apply max_dim + if len(imgsz) > max_dim: + msg = ( + "'train' and 'val' imgsz must be an integer, while 'predict' and 'export' imgsz may be a [h, w] list " + "or an integer, i.e. 'yolo export imgsz=640,480' or 'yolo export imgsz=640'" + ) + if max_dim != 1: + raise ValueError(f"imgsz={imgsz} is not a valid image size. {msg}") + LOGGER.warning(f"updating to 'imgsz={max(imgsz)}'. {msg}") + imgsz = [max(imgsz)] + # Make image size a multiple of the stride + sz = [max(math.ceil(x / stride) * stride, floor) for x in imgsz] + + # Print warning message if image size was updated + if sz != imgsz: + LOGGER.warning(f"imgsz={imgsz} must be multiple of max stride {stride}, updating to {sz}") + + # Add missing dimensions if necessary + sz = [sz[0], sz[0]] if min_dim == 2 and len(sz) == 1 else sz[0] if min_dim == 1 and len(sz) == 1 else sz + + return sz + + +@functools.lru_cache +def check_uv(): + """Check if uv package manager is installed and can run successfully.""" + try: + return subprocess.run(["uv", "-V"], capture_output=True).returncode == 0 + except FileNotFoundError: + return False + + +@functools.lru_cache +def check_version( + current: str = "0.0.0", + required: str = "0.0.0", + name: str = "version", + hard: bool = False, + verbose: bool = False, + msg: str = "", +) -> bool: + """Check current version against the required version or range. + + Args: + current (str): Current version or package name to get version from. + required (str): Required version or range (in pip-style format). + name (str): Name to be used in warning message. + hard (bool): If True, raise a ModuleNotFoundError if the requirement is not met. + verbose (bool): If True, print warning message if requirement is not met. + msg (str): Extra message to display if verbose. + + Returns: + (bool): True if requirement is met, False otherwise. + + Examples: + Check if current version is exactly 22.04 + >>> check_version(current="22.04", required="==22.04") + + Check if current version is greater than or equal to 22.04 + >>> check_version(current="22.10", required="22.04") # assumes '>=' inequality if none passed + + Check if current version is less than or equal to 22.04 + >>> check_version(current="22.04", required="<=22.04") + + Check if current version is between 20.04 (inclusive) and 22.04 (exclusive) + >>> check_version(current="21.10", required=">20.04,<22.04") + """ + if not current: # if current is '' or None + LOGGER.warning(f"invalid check_version({current}, {required}) requested, please check values.") + return True + elif not current[0].isdigit(): # current is package name rather than version string, i.e. current='ultralytics' + try: + name = current # assigned package name to 'name' arg + current = metadata.version(current) # get version string from package name + except metadata.PackageNotFoundError as e: + if hard: + raise ModuleNotFoundError(f"{current} package is required but not installed") from e + else: + return False + + if not required: # if required is '' or None + return True + + if "sys_platform" in required and ( # i.e. required='<2.4.0,>=1.8.0; sys_platform == "win32"' + (WINDOWS and "win32" not in required) + or (LINUX and "linux" not in required) + or (MACOS and "macos" not in required and "darwin" not in required) + ): + return True + + op = "" + version = "" + result = True + c = parse_version(current) # '1.2.3' -> (1, 2, 3) + for r in required.strip(",").split(","): + op, version = re.match(r"([^0-9]*)([\d.]+)", r).groups() # split '>=22.04' -> ('>=', '22.04') + if not op: + op = ">=" # assume >= if no op passed + v = parse_version(version) # '1.2.3' -> (1, 2, 3) + if op == "==" and c != v: + result = False + elif op == "!=" and c == v: + result = False + elif op == ">=" and not (c >= v): + result = False + elif op == "<=" and not (c <= v): + result = False + elif op == ">" and not (c > v): + result = False + elif op == "<" and not (c < v): + result = False + if not result: + warning = f"{name}{required} is required, but {name}=={current} is currently installed {msg}" + if hard: + raise ModuleNotFoundError(warning) # assert version requirements met + if verbose: + LOGGER.warning(warning) + return result + + +def check_latest_pypi_version(package_name="ultralytics"): + """Return the latest version of a PyPI package without downloading or installing it. + + Args: + package_name (str): The name of the package to find the latest version for. + + Returns: + (str | None): The latest version of the package, or None if unavailable. + """ + import requests # scoped as slow import + + try: + requests.packages.urllib3.disable_warnings() # Disable the InsecureRequestWarning + response = requests.get(f"https://pypi.org/pypi/{package_name}/json", timeout=3) + if response.status_code == 200: + return response.json()["info"]["version"] + except Exception: + return None + + +def check_pip_update_available(): + """Check if a new version of the ultralytics package is available on PyPI. + + Returns: + (bool): True if an update is available, False otherwise. + """ + if ONLINE and IS_PIP_PACKAGE: + try: + from ultralytics import __version__ + + latest = check_latest_pypi_version() + if check_version(__version__, f"<{latest}"): # check if current version is < latest version + LOGGER.info( + f"New https://pypi.org/project/ultralytics/{latest} available 😃 " + f"Update with 'pip install -U ultralytics'" + ) + return True + except Exception: + pass + return False + + +@ThreadingLocked() +@functools.lru_cache +def check_font(font="Arial.ttf"): + """Find font locally or download to user's configuration directory if it does not already exist. + + Args: + font (str): Path or name of font. + + Returns: + (Path | str): Resolved font file path. + """ + from matplotlib import font_manager # scope for faster 'import ultralytics' + + # Check USER_CONFIG_DIR + name = Path(font).name + file = USER_CONFIG_DIR / name + if file.exists(): + return file + + # Check system fonts + matches = [s for s in font_manager.findSystemFonts() if font in s] + if any(matches): + return matches[0] + + # Download to USER_CONFIG_DIR if missing + url = f"{ASSETS_URL}/{name}" + if downloads.is_url(url, check=True): + downloads.safe_download(url=url, file=file) + return file + + +def check_python(minimum: str = "3.8.0", hard: bool = True, verbose: bool = False) -> bool: + """Check current python version against the required minimum version. + + Args: + minimum (str): Required minimum version of python. + hard (bool): If True, raise a ModuleNotFoundError if the requirement is not met. + verbose (bool): If True, print warning message if requirement is not met. + + Returns: + (bool): Whether the installed Python version meets the minimum constraints. + """ + return check_version(PYTHON_VERSION, minimum, name="Python", hard=hard, verbose=verbose) + + +@TryExcept() +def check_apt_requirements(requirements): + """Check if apt packages are installed and install missing ones. + + Args: + requirements (list[str]): List of apt package names to check and install. + """ + prefix = colorstr("red", "bold", "apt requirements:") + # Check which packages are missing + missing_packages = [] + for package in requirements: + try: + # Use dpkg -l to check if package is installed + result = subprocess.run(["dpkg", "-l", package], capture_output=True, text=True, check=False) + # Check if package is installed (look for "ii" status) + if result.returncode != 0 or not any( + line.startswith("ii") and package in line for line in result.stdout.splitlines() + ): + missing_packages.append(package) + except Exception: + # If check fails, assume package is not installed + missing_packages.append(package) + + # Install missing packages if any + if missing_packages: + LOGGER.info( + f"{prefix} Ultralytics requirement{'s' * (len(missing_packages) > 1)} {missing_packages} not found, attempting AutoUpdate..." + ) + # Optionally update package list first + cmd = (["sudo"] if is_sudo_available() else []) + ["apt", "update"] + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + + # Build and run the install command + cmd = (["sudo"] if is_sudo_available() else []) + ["apt", "install", "-y"] + missing_packages + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + + LOGGER.info(f"{prefix} AutoUpdate success ✅") + LOGGER.warning(f"{prefix} {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n") + + +@TryExcept() +def check_requirements(requirements=ROOT.parent / "requirements.txt", exclude=(), install=True, cmds="", constrain=()): + """Check if installed dependencies meet Ultralytics YOLO models requirements and attempt to auto-update if needed. + + Args: + requirements (Path | str | list[str|tuple] | tuple[str]): Path to a requirements.txt file, a single package + requirement as a string, a list of package requirements as strings, or a list containing strings and tuples + of interchangeable packages. + exclude (tuple): Tuple of package names to exclude from checking. + install (bool): If True, attempt to auto-update packages that don't meet requirements. + cmds (str): Additional commands to pass to the pip install command when auto-updating. + constrain (tuple | list): Extra version constraints always appended to the install command even if already + satisfied, preventing the resolver from upgrading those packages during install. + + Examples: + >>> from ultralytics.utils.checks import check_requirements + + Check a requirements.txt file + >>> check_requirements("path/to/requirements.txt") + + Check a single package + >>> check_requirements("ultralytics>=8.3.200", cmds="--index-url https://download.pytorch.org/whl/cpu") + + Check multiple packages + >>> check_requirements(["numpy", "ultralytics"]) + + Check with interchangeable packages + >>> check_requirements([("onnxruntime", "onnxruntime-gpu"), "numpy"]) + """ + prefix = colorstr("red", "bold", "requirements:") + + if os.environ.get("ULTRALYTICS_SKIP_REQUIREMENTS_CHECKS", "0") == "1": + LOGGER.info(f"{prefix} ULTRALYTICS_SKIP_REQUIREMENTS_CHECKS=1 detected, skipping requirements check.") + return True + + if isinstance(requirements, Path): # requirements.txt file + file = requirements.resolve() + assert file.exists(), f"{prefix} {file} not found, check failed." + requirements = [f"{x.name}{x.specifier}" for x in parse_requirements(file) if x.name not in exclude] + elif isinstance(requirements, str): + requirements = [requirements] + + pkgs = [] + for r in requirements: + candidates = r if isinstance(r, (list, tuple)) else [r] + satisfied = False + + for candidate in candidates: + r_stripped = candidate.rpartition("/")[-1].replace(".git", "") # replace git+https://org/repo.git -> 'repo' + match = re.match(r"([a-zA-Z0-9-_]+)([<>!=~]+.*)?", r_stripped) + name, required = match[1], match[2].strip() if match[2] else "" + try: + if check_version(metadata.version(name), required): + satisfied = True + break + except (AssertionError, metadata.PackageNotFoundError): + continue + + if not satisfied: + pkg = candidates[0] + if "git+" in pkg: # strip version constraints from git URLs for pip + url, sep, marker = pkg.partition(";") + pkg = re.sub(r"[<>!=~]+.*$", "", url) + sep + marker + pkgs.append(pkg) + + @Retry(times=2, delay=1) + def attempt_install(packages, commands, use_uv): + """Attempt package installation with uv if available, falling back to pip.""" + if use_uv: + # Use --python to explicitly target current interpreter (venv or system) + # This ensures correct installation when VIRTUAL_ENV env var isn't set + return subprocess.check_output( + f'uv pip install --no-cache-dir --python "{sys.executable}" {packages} {commands} ' + f"--index-strategy=unsafe-best-match --break-system-packages", + shell=True, + stderr=subprocess.STDOUT, + text=True, + ) + return subprocess.check_output( + f'"{sys.executable}" -m pip install --no-cache-dir {packages} {commands}', + shell=True, + stderr=subprocess.STDOUT, + text=True, + ) + + s = " ".join(f'"{x}"' for x in pkgs) # console string + if s and constrain: # append version constraints to prevent upgrades during install + s += " " + " ".join(f'"{c}"' for c in constrain) + if s: + if install and AUTOINSTALL: # check environment variable + # Note uv fails on arm64 macOS and Raspberry Pi runners + n = len(pkgs) # number of packages updates + LOGGER.info(f"{prefix} Ultralytics requirement{'s' * (n > 1)} {pkgs} not found, attempting AutoUpdate...") + try: + t = time.time() + assert ONLINE, "AutoUpdate skipped (offline)" + use_uv = not ARM64 and check_uv() # uv fails on ARM64 + LOGGER.info(attempt_install(s, cmds, use_uv=use_uv)) + dt = time.time() - t + LOGGER.info(f"{prefix} AutoUpdate success ✅ {dt:.1f}s") + LOGGER.warning( + f"{prefix} {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n" + ) + except Exception as e: + msg = f"{prefix} ❌ {e}" + if hasattr(e, "output") and e.output: + msg += f"\n{e.output}" + LOGGER.warning(msg) + return False + else: + return False + + return True + + +def check_executorch_requirements(): + """Check and install ExecuTorch requirements including platform-specific dependencies.""" + # BUG executorch build on arm64 Docker requires packaging>=22.0 https://github.com/pypa/setuptools/issues/4483 + if LINUX and ARM64 and IS_DOCKER: + check_requirements("packaging>=22.0") + + check_requirements("executorch", cmds=f"torch=={TORCH_VERSION.split('+')[0]}") + + +def check_tensorrt(min_version: str = "7.0.0"): + """Check and install TensorRT requirements including platform-specific dependencies. + + Args: + min_version (str): Minimum supported TensorRT version (default: "7.0.0"). + """ + if LINUX: + cuda_version = torch.version.cuda.split(".")[0] + check_requirements(f"tensorrt-cu{cuda_version}>={min_version},!=10.2.0") + + +def check_torchvision(): + """Check the installed versions of PyTorch and Torchvision to ensure they're compatible. + + This function checks the installed versions of PyTorch and Torchvision, and warns if they're incompatible according + to the compatibility table based on: https://github.com/pytorch/vision#installation. + """ + compatibility_table = { + "2.10": ["0.25"], + "2.9": ["0.24"], + "2.8": ["0.23"], + "2.7": ["0.22"], + "2.6": ["0.21"], + "2.5": ["0.20"], + "2.4": ["0.19"], + "2.3": ["0.18"], + "2.2": ["0.17"], + "2.1": ["0.16"], + "2.0": ["0.15"], + "1.13": ["0.14"], + "1.12": ["0.13"], + } + + # Check major and minor versions + v_torch = ".".join(TORCH_VERSION.split("+", 1)[0].split(".")[:2]) + if v_torch in compatibility_table: + compatible_versions = compatibility_table[v_torch] + v_torchvision = ".".join(TORCHVISION_VERSION.split("+", 1)[0].split(".")[:2]) + if all(v_torchvision != v for v in compatible_versions): + LOGGER.warning( + f"torchvision=={v_torchvision} is incompatible with torch=={v_torch}.\n" + f"Run 'pip install torchvision=={compatible_versions[0]}' to fix torchvision or " + "'pip install -U torch torchvision' to update both.\n" + "For a full compatibility table see https://github.com/pytorch/vision#installation" + ) + + +def check_suffix(file="yolo26n.pt", suffix=".pt", msg=""): + """Check file(s) for acceptable suffix. + + Args: + file (str | list[str]): File or list of files to check. + suffix (str | tuple): Acceptable suffix or tuple of suffixes. + msg (str): Additional message to display in case of error. + """ + if file and suffix: + if isinstance(suffix, str): + suffix = {suffix} + for f in file if isinstance(file, (list, tuple)) else [file]: + if s := str(f).rpartition(".")[-1].lower().strip(): # file suffix + assert f".{s}" in suffix, f"{msg}{f} acceptable suffix is {suffix}, not .{s}" + + +def check_yolov5u_filename(file: str, verbose: bool = True) -> str: + """Replace legacy YOLOv5 filenames with updated YOLOv5u filenames. + + Args: + file (str): Filename to check and potentially update. + verbose (bool): Whether to print information about the replacement. + + Returns: + (str): Updated filename. + """ + if "yolov3" in file or "yolov5" in file: + if "u.yaml" in file: + file = file.replace("u.yaml", ".yaml") # i.e. yolov5nu.yaml -> yolov5n.yaml + elif ".pt" in file and "u" not in file: + original_file = file + file = re.sub(r"(.*yolov5([nsmlx]))\.pt", "\\1u.pt", file) # i.e. yolov5n.pt -> yolov5nu.pt + file = re.sub(r"(.*yolov5([nsmlx])6)\.pt", "\\1u.pt", file) # i.e. yolov5n6.pt -> yolov5n6u.pt + file = re.sub(r"(.*yolov3(|-tiny|-spp))\.pt", "\\1u.pt", file) # i.e. yolov3-spp.pt -> yolov3-sppu.pt + if file != original_file and verbose: + LOGGER.info( + f"PRO TIP 💡 Replace 'model={original_file}' with new 'model={file}'.\nYOLOv5 'u' models are " + f"trained with https://github.com/ultralytics/ultralytics and feature improved performance vs " + f"standard YOLOv5 models trained with https://github.com/ultralytics/yolov5." + ) + return file + + +def check_model_file_from_stem(model: str = "yolo11n") -> str | Path: + """Return a model filename from a valid model stem. + + Args: + model (str): Model stem to check. + + Returns: + (str | Path): Model filename with appropriate suffix. + """ + path = Path(model) + if not path.suffix and path.stem in downloads.GITHUB_ASSETS_STEMS: + return path.with_suffix(".pt") # add suffix, i.e. yolo26n -> yolo26n.pt + return model + + +def check_file(file, suffix="", download=True, download_dir=".", hard=True): + """Search/download file (if necessary), check suffix (if provided), and return path. + + Args: + file (str): File name or path, URL, platform URI (ul://), or GCS path (gs://). + suffix (str | tuple): Acceptable suffix or tuple of suffixes to validate against the file. + download (bool): Whether to download the file if it doesn't exist locally. + download_dir (str): Directory to download the file to. + hard (bool): Whether to raise an error if the file is not found. + + Returns: + (str | list): Path to the file, or an empty list if not found. + """ + file = normalize_platform_uri(file) # accept Platform web URLs (rewritten to ul://) + check_suffix(file, suffix) # optional + file = str(file).strip() # convert to string and strip spaces + file = check_yolov5u_filename(file) # yolov5n -> yolov5nu + if ( + not file + or ("://" not in file and Path(file).exists()) # '://' check required in Windows Python<3.10 + or file.lower().startswith("grpc://") + ): # file exists or gRPC Triton images + return file + elif download and file.lower().startswith("ul://"): # Ultralytics Platform URI + from ultralytics.utils.callbacks.platform import resolve_platform_uri + + url = resolve_platform_uri(file, hard=hard) # Convert to signed HTTPS URL + if url is None: + return [] # Not found, soft fail (consistent with file search behavior) + # Use URI path for unique directory structure: ul://user/project/model -> user/project/model/filename.pt + uri_path = Path(file[5:]) # Remove "ul://" + if uri_path.is_absolute() or ".." in uri_path.parts: + raise ValueError(f"Unsafe Ultralytics Platform URI path: {file}") + local_file = Path(download_dir) / uri_path / url2file(url) + # Always re-download NDJSON datasets (cheap, ensures fresh data after updates) + if local_file.suffix == ".ndjson": + local_file.unlink(missing_ok=True) + if local_file.exists(): + LOGGER.info(f"Found {clean_url(url)} locally at {local_file}") + else: + local_file.parent.mkdir(parents=True, exist_ok=True) + downloads.safe_download(url=url, file=local_file, unzip=False) + return str(local_file) + elif download and file.lower().startswith(REMOTE_FILE_PREFIXES): # download + if file.startswith("gs://"): + file = "https://storage.googleapis.com/" + file[5:] # convert gs:// to public HTTPS URL + url = file # warning: Pathlib turns :// -> :/ + file = Path(download_dir) / url2file(file) # '%2F' to '/', split authentication query strings + if file.exists(): + LOGGER.info(f"Found {clean_url(url)} locally at {file}") # file already exists + else: + downloads.safe_download(url=url, file=file, unzip=False) + return str(file) + else: # search + files = glob.glob(str(ROOT / "**" / file), recursive=True) or glob.glob(str(ROOT.parent / file)) # find file + if not files and hard: + raise FileNotFoundError(f"'{file}' does not exist") + elif len(files) > 1 and hard: + raise FileNotFoundError(f"Multiple files match '{file}', specify exact path: {files}") + return files[0] if len(files) else [] # return file + + +def check_yaml(file, suffix=(".yaml", ".yml"), hard=True): + """Search/download YAML file (if necessary) and return path, checking suffix. + + Args: + file (str | Path): File name or path. + suffix (tuple): Tuple of acceptable YAML file suffixes. + hard (bool): Whether to raise an error if the file is not found or multiple files are found. + + Returns: + (str): Path to the YAML file. + """ + return check_file(file, suffix, hard=hard) + + +def check_is_path_safe(basedir: Path | str, path: Path | str) -> bool: + """Check if the resolved path is under the intended directory to prevent path traversal. + + Args: + basedir (Path | str): The intended directory. + path (Path | str): The path to check. + + Returns: + (bool): True if the path is safe, False otherwise. + """ + base_dir_resolved = Path(basedir).resolve() + path_resolved = Path(path).resolve() + + return path_resolved.exists() and path_resolved.parts[: len(base_dir_resolved.parts)] == base_dir_resolved.parts + + +@functools.lru_cache +def check_imshow(warn=False): + """Check if environment supports image displays. + + Args: + warn (bool): Whether to warn if environment doesn't support image displays. + + Returns: + (bool): True if environment supports image displays, False otherwise. + """ + try: + if LINUX: + assert not IS_COLAB and not IS_KAGGLE + assert "DISPLAY" in os.environ, "The DISPLAY environment variable isn't set." + cv2.imshow("test", np.zeros((8, 8, 3), dtype=np.uint8)) # show a small 8-pixel image + cv2.waitKey(1) + cv2.destroyAllWindows() + cv2.waitKey(1) + return True + except Exception as e: + if warn: + LOGGER.warning(f"Environment does not support cv2.imshow() or PIL Image.show()\n{e}") + return False + + +def check_yolo(verbose=True, device=""): + """Print a human-readable YOLO software and hardware summary. + + Args: + verbose (bool): Whether to print verbose information. + device (str | torch.device): Device to use for YOLO. + """ + import psutil # scoped as slow import + + from ultralytics.utils.torch_utils import select_device + + if IS_COLAB: + shutil.rmtree("sample_data", ignore_errors=True) # remove colab /sample_data directory + + if verbose: + # System info + gib = 1 << 30 # bytes per GiB + ram = psutil.virtual_memory().total + total, _used, free = shutil.disk_usage("/") + s = f"({os.cpu_count()} CPUs, {ram / gib:.1f} GB RAM, {(total - free) / gib:.1f}/{total / gib:.1f} GB disk)" + try: + from IPython import display + + display.clear_output() # clear display if notebook + except ImportError: + pass + else: + s = "" + + if GIT.is_repo: + check_multiple_install() # check conflicting installation if using local clone + + select_device(device=device, newline=False) + LOGGER.info(f"Setup complete ✅ {s}") + + +def collect_system_info(): + """Collect and print relevant system information including OS, Python, RAM, CPU, and CUDA. + + Returns: + (dict): Dictionary containing system information. + """ + import psutil # scoped as slow import + + from ultralytics.utils import ENVIRONMENT # scope to avoid circular import + from ultralytics.utils.torch_utils import get_cpu_info, get_gpu_info + + gib = 1 << 30 # bytes per GiB + cuda = torch.cuda.is_available() + check_yolo() + total, _, free = shutil.disk_usage("/") + + info_dict = { + "OS": platform.platform(), + "Environment": ENVIRONMENT, + "Python": PYTHON_VERSION, + "Install": "git" if GIT.is_repo else "pip" if IS_PIP_PACKAGE else "other", + "Path": str(ROOT), + "RAM": f"{psutil.virtual_memory().total / gib:.2f} GB", + "Disk": f"{(total - free) / gib:.1f}/{total / gib:.1f} GB", + "CPU": get_cpu_info(), + "CPU count": os.cpu_count(), + "GPU": get_gpu_info(index=0) if cuda else None, + "GPU count": torch.cuda.device_count() if cuda else None, + "CUDA": torch.version.cuda if cuda else None, + } + LOGGER.info("\n" + "\n".join(f"{k:<23}{v}" for k, v in info_dict.items()) + "\n") + + package_info = {} + for r in parse_requirements(package=get_distribution_name("ultralytics")): + try: + current = metadata.version(r.name) + is_met = "✅ " if check_version(current, str(r.specifier), name=r.name, hard=True) else "❌ " + except metadata.PackageNotFoundError: + current = "(not installed)" + is_met = "❌ " + package_info[r.name] = f"{is_met}{current}{r.specifier}" + LOGGER.info(f"{r.name:<23}{package_info[r.name]}") + + info_dict["Package Info"] = package_info + + if is_github_action_running(): + github_info = { + "RUNNER_OS": os.getenv("RUNNER_OS"), + "GITHUB_EVENT_NAME": os.getenv("GITHUB_EVENT_NAME"), + "GITHUB_WORKFLOW": os.getenv("GITHUB_WORKFLOW"), + "GITHUB_ACTOR": os.getenv("GITHUB_ACTOR"), + "GITHUB_REPOSITORY": os.getenv("GITHUB_REPOSITORY"), + "GITHUB_REPOSITORY_OWNER": os.getenv("GITHUB_REPOSITORY_OWNER"), + } + LOGGER.info("\n" + "\n".join(f"{k}: {v}" for k, v in github_info.items())) + info_dict["GitHub Info"] = github_info + + return info_dict + + +def check_amp(model): + """Check the PyTorch Automatic Mixed Precision (AMP) functionality of a YOLO model. + + If the checks fail, it means there are anomalies with AMP on the system that may cause NaN losses or zero-mAP + results, so AMP will be disabled during training. + + Args: + model (torch.nn.Module): A YOLO model instance. + + Returns: + (bool): Returns True if the AMP functionality works correctly with YOLO model, else False. + + Examples: + >>> from ultralytics import YOLO + >>> from ultralytics.utils.checks import check_amp + >>> model = YOLO("yolo26n.pt").model.cuda() + >>> check_amp(model) + """ + from ultralytics.utils.torch_utils import autocast + + device = next(model.parameters()).device # get model device + prefix = colorstr("AMP: ") + if device.type in {"cpu", "mps"}: + return False # AMP only used on CUDA devices + else: + # GPUs that have issues with AMP + pattern = re.compile( + r"(nvidia|geforce|quadro|tesla).*?(1660|1650|1630|t400|t550|t600|t1000|t1200|t2000|k40m)", re.IGNORECASE + ) + + gpu = torch.cuda.get_device_name(device) + if bool(pattern.search(gpu)): + LOGGER.warning( + f"{prefix}checks failed ❌. AMP training on {gpu} GPU may cause " + f"NaN losses or zero-mAP results, so AMP will be disabled during training." + ) + return False + + def amp_allclose(m, im): + """All close FP32 vs AMP results.""" + batch = [im] * 8 + imgsz = max(256, int(model.stride.max() * 4)) # max stride P5-32 and P6-64 + a = m(batch, imgsz=imgsz, device=device, verbose=False)[0].boxes.data # FP32 inference + with autocast(enabled=True): + b = m(batch, imgsz=imgsz, device=device, verbose=False)[0].boxes.data # AMP inference + del m + return a.shape == b.shape and torch.allclose(a, b.float(), atol=0.5) # close to 0.5 absolute tolerance + + im = ASSETS / "bus.jpg" # image to check + LOGGER.info(f"{prefix}running Automatic Mixed Precision (AMP) checks...") + warning_msg = "Setting 'amp=True'. If you experience zero-mAP or NaN losses you can disable AMP with amp=False." + try: + from ultralytics import YOLO + + assert amp_allclose(YOLO("yolo26n.pt"), im) + LOGGER.info(f"{prefix}checks passed ✅") + except ConnectionError: + LOGGER.warning(f"{prefix}checks skipped. Offline and unable to download YOLO26n for AMP checks. {warning_msg}") + except (AttributeError, ModuleNotFoundError): + LOGGER.warning( + f"{prefix}checks skipped. " + f"Unable to load YOLO26n for AMP checks due to possible Ultralytics package modifications. {warning_msg}" + ) + except AssertionError: + LOGGER.error( + f"{prefix}checks failed. Anomalies were detected with AMP on your system that may lead to " + f"NaN losses or zero-mAP results, so AMP will be disabled during training." + ) + return False + return True + + +def check_multiple_install(): + """Check if there are multiple Ultralytics installations.""" + import sys + + try: + result = subprocess.run([sys.executable, "-m", "pip", "show", "ultralytics"], capture_output=True, text=True) + install_msg = ( + f"Install your local copy in editable mode with 'pip install -e {ROOT.parent}' to avoid " + "issues. See https://docs.ultralytics.com/quickstart/" + ) + if result.returncode != 0: + if "not found" in result.stderr.lower(): # Package not pip-installed but locally imported + LOGGER.warning(f"Ultralytics not found via pip but importing from: {ROOT}. {install_msg}") + return + yolo_path = (Path(re.findall(r"location:\s+(.+)", result.stdout, flags=re.I)[-1]) / "ultralytics").resolve() + if not yolo_path.samefile(ROOT.resolve()): + LOGGER.warning( + f"Multiple Ultralytics installations detected. The `yolo` command uses: {yolo_path}, " + f"but current session imports from: {ROOT}. This may cause version conflicts. {install_msg}" + ) + except Exception: + return + + +def print_args(args: dict | None = None, show_file=True, show_func=False): + """Print function arguments (optional args dict). + + Args: + args (dict, optional): Arguments to print. + show_file (bool): Whether to show the file name. + show_func (bool): Whether to show the function name. + """ + + def strip_auth(v): + """Clean longer Ultralytics HUB URLs by stripping potential authentication information.""" + return clean_url(v) if (isinstance(v, str) and v.startswith("http") and len(v) > 100) else v + + x = inspect.currentframe().f_back # previous frame + file, _, func, _, _ = inspect.getframeinfo(x) + if args is None: # get args automatically + args, _, _, frm = inspect.getargvalues(x) + args = {k: v for k, v in frm.items() if k in args} + try: + file = Path(file).resolve().relative_to(ROOT).with_suffix("") + except ValueError: + file = Path(file).stem + s = (f"{file}: " if show_file else "") + (f"{func}: " if show_func else "") + LOGGER.info(colorstr(s) + ", ".join(f"{k}={strip_auth(v)}" for k, v in sorted(args.items()))) + + +def cuda_device_count() -> int: + """Get the number of NVIDIA GPUs available in the environment. + + Returns: + (int): The number of NVIDIA GPUs available. + """ + if IS_JETSON: + # NVIDIA Jetson does not fully support nvidia-smi and therefore use PyTorch instead + return torch.cuda.device_count() + else: + try: + # Run the nvidia-smi command and capture its output + output = subprocess.check_output( + ["nvidia-smi", "--query-gpu=count", "--format=csv,noheader,nounits"], encoding="utf-8" + ) + + # Take the first line and strip any leading/trailing white space + first_line = output.strip().split("\n", 1)[0] + + return int(first_line) + except (subprocess.CalledProcessError, FileNotFoundError, ValueError): + # If the command fails, nvidia-smi is not found, or output is not an integer, assume no GPUs are available + return 0 + + +def cuda_is_available() -> bool: + """Check if CUDA is available in the environment. + + Returns: + (bool): True if one or more NVIDIA GPUs are available, False otherwise. + """ + return cuda_device_count() > 0 + + +def is_rockchip(): + """Check if the current environment is running on a Rockchip SoC. + + Returns: + (bool): True if running on a Rockchip SoC, False otherwise. + """ + if LINUX and ARM64: + try: + with open("/proc/device-tree/compatible") as f: + dev_str = f.read() + *_, soc = dev_str.split(",") + if soc.replace("\x00", "").split("-", 1)[0] in RKNN_CHIPS: + return True + except OSError: + return False + else: + return False + + +def is_intel(): + """Check if the system has Intel hardware (CPU or GPU). + + Returns: + (bool): True if Intel hardware is detected, False otherwise. + """ + from ultralytics.utils.torch_utils import get_cpu_info + + # Check CPU + if "intel" in get_cpu_info().lower(): + return True + + # Check GPU via xpu-smi + try: + result = subprocess.run(["xpu-smi", "discovery"], capture_output=True, text=True, timeout=5) + return "intel" in result.stdout.lower() + except Exception: # broad clause to capture all Intel GPU exception types + return False + + +def is_sudo_available() -> bool: + """Check if the sudo command is available in the environment. + + Returns: + (bool): True if the sudo command is available, False otherwise. + """ + if WINDOWS: + return False + cmd = "sudo --version" + return subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0 + + +# Run checks and define constants +check_python("3.8", hard=False, verbose=True) # check python version +check_torchvision() # check torch-torchvision compatibility + +# Define constants +IS_PYTHON_3_8 = PYTHON_VERSION.startswith("3.8") +IS_PYTHON_3_9 = PYTHON_VERSION.startswith("3.9") +IS_PYTHON_3_10 = PYTHON_VERSION.startswith("3.10") +IS_PYTHON_3_12 = PYTHON_VERSION.startswith("3.12") +IS_PYTHON_3_13 = PYTHON_VERSION.startswith("3.13") + +IS_PYTHON_MINIMUM_3_9 = check_python("3.9", hard=False) +IS_PYTHON_MINIMUM_3_10 = check_python("3.10", hard=False) +IS_PYTHON_MINIMUM_3_12 = check_python("3.12", hard=False) +IS_PYTHON_MINIMUM_3_13 = check_python("3.13", hard=False) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/cpu.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/cpu.py new file mode 100644 index 0000000000000000000000000000000000000000..b2c5e247fca21dd923be247be59b88ab883254b4 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/cpu.py @@ -0,0 +1,85 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import platform +import re +import subprocess +import sys +from pathlib import Path + + +class CPUInfo: + """Provide cross-platform CPU brand and model information. + + Query platform-specific sources to retrieve a human-readable CPU descriptor and normalize it for consistent + presentation across macOS, Linux, and Windows. If platform-specific probing fails, generic platform identifiers are + used to ensure a stable string is always returned. + + Methods: + name: Return the normalized CPU name using platform-specific sources with robust fallbacks. + _clean: Normalize and prettify common vendor brand strings and frequency patterns. + __str__: Return the normalized CPU name for string contexts. + + Examples: + >>> CPUInfo.name() + 'Apple M4 Pro' + >>> str(CPUInfo()) + 'Intel Core i7-9750H 2.60GHz' + """ + + @staticmethod + def name() -> str: + """Return a normalized CPU model string from platform-specific sources.""" + try: + if sys.platform == "darwin": + # Query macOS sysctl for the CPU brand string + s = subprocess.run( + ["sysctl", "-n", "machdep.cpu.brand_string"], capture_output=True, text=True + ).stdout.strip() + if s: + return CPUInfo._clean(s) + elif sys.platform.startswith("linux"): + # Parse /proc/cpuinfo for the first "model name" entry + p = Path("/proc/cpuinfo") + if p.exists(): + for line in p.read_text(errors="ignore").splitlines(): + if "model name" in line: + return CPUInfo._clean(line.split(":", 1)[1]) + elif sys.platform.startswith("win"): + try: + import winreg as wr + + with wr.OpenKey(wr.HKEY_LOCAL_MACHINE, r"HARDWARE\DESCRIPTION\System\CentralProcessor\0") as k: + val, _ = wr.QueryValueEx(k, "ProcessorNameString") + if val: + return CPUInfo._clean(val) + except Exception: + # Fall through to generic platform fallbacks on Windows registry access failure + pass + # Generic platform fallbacks + s = platform.processor() or getattr(platform.uname(), "processor", "") or platform.machine() + return CPUInfo._clean(s or "Unknown CPU") + except Exception: + # Ensure a string is always returned even on unexpected failures + s = platform.processor() or platform.machine() or "" + return CPUInfo._clean(s or "Unknown CPU") + + @staticmethod + def _clean(s: str) -> str: + """Normalize and prettify a raw CPU descriptor string.""" + s = re.sub(r"\s+", " ", s.strip()) + s = s.replace("(TM)", "").replace("(tm)", "").replace("(R)", "").replace("(r)", "").strip() + if m := re.search(r"(Intel.*?i\d[\w-]*) CPU @ ([\d.]+GHz)", s, re.I): + return f"{m.group(1)} {m.group(2)}" + if m := re.search(r"(AMD.*?Ryzen.*?[\w-]*) CPU @ ([\d.]+GHz)", s, re.I): + return f"{m.group(1)} {m.group(2)}" + return s + + def __str__(self) -> str: + """Return the normalized CPU name.""" + return self.name() + + +if __name__ == "__main__": + print(CPUInfo.name()) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/dist.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/dist.py new file mode 100644 index 0000000000000000000000000000000000000000..a8917c73e8345c0ca71ac42444a31fc1307a88b2 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/dist.py @@ -0,0 +1,142 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import os +import shutil +import sys +import tempfile +from typing import TYPE_CHECKING + +from . import USER_CONFIG_DIR +from .torch_utils import TORCH_1_9 + +if TYPE_CHECKING: + from ultralytics.engine.trainer import BaseTrainer + + +def find_free_network_port() -> int: + """Find a free port on localhost. + + It is useful in single-node training when we don't want to connect to a real main node but have to set the + `MASTER_PORT` environment variable. + + Returns: + (int): The available network port number. + """ + import socket + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("127.0.0.1", 0)) + return s.getsockname()[1] # port + + +def generate_ddp_file(trainer: BaseTrainer) -> str: + """Generate a DDP (Distributed Data Parallel) file for multi-GPU training. + + This function creates a temporary Python file that enables distributed training across multiple GPUs. The file + contains the necessary configuration to initialize the trainer in a distributed environment. + + Args: + trainer (ultralytics.engine.trainer.BaseTrainer): The trainer containing training configuration and arguments. + Must have args attribute and be a class instance. + + Returns: + (str): Path to the generated temporary DDP file. + + Notes: + The generated file is saved in the USER_CONFIG_DIR/DDP directory and includes: + - Trainer class import + - Configuration overrides from the trainer arguments + - Model path configuration + - Training initialization code + """ + module, name = f"{trainer.__class__.__module__}.{trainer.__class__.__name__}".rsplit(".", 1) + + # Serialize augmentations to JSON-safe dicts to avoid NameError in DDP subprocess + overrides = vars(trainer.args).copy() + if overrides.get("augmentations") is not None: + import albumentations as A + + overrides["augmentations"] = [A.to_dict(t) for t in overrides["augmentations"]] + + content = f""" +# Ultralytics Multi-GPU training temp file (should be automatically deleted after use) +from pathlib import Path, PosixPath # For model arguments stored as Path instead of str +overrides = {overrides} + +if __name__ == "__main__": + from {module} import {name} + from ultralytics.utils import DEFAULT_CFG_DICT + + # Deserialize augmentations from dicts back to Albumentations transform objects + if overrides.get("augmentations") is not None: + import albumentations as A + overrides["augmentations"] = [A.from_dict(t) for t in overrides["augmentations"]] + + cfg = DEFAULT_CFG_DICT.copy() + cfg.update(save_dir='') # handle the extra key 'save_dir' + trainer = {name}(cfg=cfg, overrides=overrides) + trainer.args.model = "{getattr(trainer.hub_session, "model_url", trainer.args.model)}" + results = trainer.train() +""" + (USER_CONFIG_DIR / "DDP").mkdir(exist_ok=True) + with tempfile.NamedTemporaryFile( + prefix="_temp_", + suffix=f"{id(trainer)}.py", + mode="w+", + encoding="utf-8", + dir=USER_CONFIG_DIR / "DDP", + delete=False, + ) as file: + file.write(content) + return file.name + + +def generate_ddp_command(trainer: BaseTrainer) -> tuple[list[str], str]: + """Generate command for distributed training. + + Args: + trainer (ultralytics.engine.trainer.BaseTrainer): The trainer containing configuration for distributed training. + + Returns: + cmd (list[str]): The command to execute for distributed training. + file (str): Path to the temporary file created for DDP training. + """ + import __main__ # noqa local import to avoid https://github.com/Lightning-AI/pytorch-lightning/issues/15218 + + if not trainer.resume: + shutil.rmtree(trainer.save_dir) # remove the save_dir + file = generate_ddp_file(trainer) + dist_cmd = "torch.distributed.run" if TORCH_1_9 else "torch.distributed.launch" + port = find_free_network_port() + cmd = [ + sys.executable, + "-m", + dist_cmd, + "--nproc_per_node", + f"{trainer.world_size}", + "--master_port", + f"{port}", + file, + ] + return cmd, file + + +def ddp_cleanup(trainer: BaseTrainer, file: str) -> None: + """Delete temporary file if created during distributed data parallel (DDP) training. + + This function checks if the provided file contains the trainer's ID in its name, indicating it was created as a + temporary file for DDP training, and deletes it if so. + + Args: + trainer (ultralytics.engine.trainer.BaseTrainer): The trainer used for distributed training. + file (str): Path to the file that might need to be deleted. + + Examples: + >>> trainer = YOLOTrainer() + >>> file = "/tmp/ddp_temp_123456789.py" + >>> ddp_cleanup(trainer, file) + """ + if f"{id(trainer)}.py" in file: # if temp_file suffix in file + os.remove(file) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/downloads.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/downloads.py new file mode 100644 index 0000000000000000000000000000000000000000..f4ca8aa161d06d6bb995885421c236fed524fc18 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/downloads.py @@ -0,0 +1,575 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import re +import shutil +import subprocess +import tarfile +from itertools import repeat +from multiprocessing.pool import ThreadPool +from pathlib import Path +from urllib import parse, request + +from ultralytics.utils import ASSETS_URL, LOGGER, TQDM, checks, clean_url, emojis, is_online, url2file + +# Define Ultralytics GitHub assets maintained at https://github.com/ultralytics/assets +GITHUB_ASSETS_REPO = "ultralytics/assets" +GITHUB_ASSETS_NAMES = frozenset( + [f"yolov8{k}{suffix}.pt" for k in "nsmlx" for suffix in ("", "-cls", "-seg", "-pose", "-obb", "-oiv7")] + + [f"yolo11{k}{suffix}.pt" for k in "nsmlx" for suffix in ("", "-cls", "-seg", "-pose", "-obb")] + + [f"yolo12{k}{suffix}.pt" for k in "nsmlx" for suffix in ("",)] # detect models only currently + + [f"yolo26{k}{suffix}.pt" for k in "nsmlx" for suffix in ("", "-cls", "-seg", "-sem", "-pose", "-obb")] + + [f"yolov5{k}{resolution}u.pt" for k in "nsmlx" for resolution in ("", "6")] + + [f"yolov3{k}u.pt" for k in ("", "-spp", "-tiny")] + + [f"yolov8{k}-world.pt" for k in "smlx"] + + [f"yolov8{k}-worldv2.pt" for k in "smlx"] + + [f"yoloe-v8{k}{suffix}.pt" for k in "sml" for suffix in ("-seg", "-seg-pf")] + + [f"yoloe-11{k}{suffix}.pt" for k in "sml" for suffix in ("-seg", "-seg-pf")] + + [f"yoloe-26{k}{suffix}.pt" for k in "nsmlx" for suffix in ("-seg", "-seg-pf")] + + [f"yolov9{k}.pt" for k in "tsmce"] + + [f"yolov10{k}.pt" for k in "nsmblx"] + + [f"yolo_nas_{k}.pt" for k in "sml"] + + [f"sam_{k}.pt" for k in "bl"] + + [f"sam2_{k}.pt" for k in "blst"] + + [f"sam2.1_{k}.pt" for k in "blst"] + + [f"FastSAM-{k}.pt" for k in "sx"] + + [f"rtdetr-{k}.pt" for k in "lx"] + + [ + "mobile_sam.pt", + "mobileclip_blt.ts", + "yolo11n-grayscale.pt", + "calibration_image_sample_data_20x128x128x3_float32.npy.zip", + ] +) +GITHUB_ASSETS_STEMS = frozenset(k.rpartition(".")[0] for k in GITHUB_ASSETS_NAMES) + + +def is_url(url: str | Path, check: bool = False) -> bool: + """Validate if the given string is a URL and optionally check if the URL exists online. + + Args: + url (str | Path): The string to be validated as a URL. + check (bool, optional): If True, performs an additional check to see if the URL exists online. + + Returns: + (bool): True for a valid URL. If 'check' is True, also returns True if the URL exists online. + + Examples: + >>> valid = is_url("https://www.example.com") + >>> valid_and_exists = is_url("https://www.example.com", check=True) + """ + try: + url = str(url) + result = parse.urlparse(url) + if not (result.scheme and result.netloc): + return False + if check: + r = request.urlopen(request.Request(url, method="HEAD"), timeout=3) + return 200 <= r.getcode() < 400 + return True + except Exception: + return False + + +def delete_dsstore(path: str | Path, files_to_delete: tuple[str, ...] = (".DS_Store", "__MACOSX")) -> None: + """Delete all specified system files in a directory. + + Args: + path (str | Path): The directory path where the files should be deleted. + files_to_delete (tuple[str, ...]): The files to be deleted. + + Examples: + >>> from ultralytics.utils.downloads import delete_dsstore + >>> delete_dsstore("path/to/dir") + + Notes: + ".DS_Store" files are created by the Apple operating system and contain metadata about folders and files. They + are hidden system files and can cause issues when transferring files between different operating systems. + """ + for file in files_to_delete: + matches = list(Path(path).rglob(file)) + LOGGER.info(f"Deleting {file} files: {matches}") + for f in matches: + f.unlink() + + +def zip_directory( + directory: str | Path, + compress: bool = True, + exclude: tuple[str, ...] = (".DS_Store", "__MACOSX"), + progress: bool = True, +) -> Path: + """Zip the contents of a directory, excluding specified files. + + The resulting zip file is named after the directory and placed alongside it. + + Args: + directory (str | Path): The path to the directory to be zipped. + compress (bool): Whether to compress the files while zipping. + exclude (tuple[str, ...], optional): A tuple of filename strings to be excluded. + progress (bool, optional): Whether to display a progress bar. + + Returns: + (Path): The path to the resulting zip file. + + Examples: + >>> from ultralytics.utils.downloads import zip_directory + >>> file = zip_directory("path/to/dir") + """ + from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile + + delete_dsstore(directory) + directory = Path(directory) + if not directory.is_dir(): + raise FileNotFoundError(f"Directory '{directory}' does not exist.") + + # Zip with progress bar + files = [f for f in directory.rglob("*") if f.is_file() and all(x not in f.name for x in exclude)] # files to zip + zip_file = directory.with_suffix(".zip") + compression = ZIP_DEFLATED if compress else ZIP_STORED + with ZipFile(zip_file, "w", compression) as f: + for file in TQDM(files, desc=f"Zipping {directory} to {zip_file}...", unit="files", disable=not progress): + f.write(file, file.relative_to(directory)) + + return zip_file # return path to zip file + + +def unzip_file( + file: str | Path, + path: str | Path | None = None, + exclude: tuple[str, ...] = (".DS_Store", "__MACOSX"), + exist_ok: bool = False, + progress: bool = True, +) -> Path: + """Unzip a *.zip file to the specified path, excluding specified files. + + If the zipfile does not contain a single top-level directory, the function will create a new directory with the same + name as the zipfile (without the extension) to extract its contents. If a path is not provided, the function will + use the parent directory of the zipfile as the default path. + + Args: + file (str | Path): The path to the zipfile to be extracted. + path (str | Path, optional): The path to extract the zipfile to. + exclude (tuple[str, ...], optional): A tuple of filename strings to be excluded. + exist_ok (bool, optional): Whether to overwrite existing contents if they exist. + progress (bool, optional): Whether to display a progress bar. + + Returns: + (Path): The path to the directory where the zipfile was extracted. + + Raises: + BadZipFile: If the provided file does not exist or is not a valid zipfile. + + Examples: + >>> from ultralytics.utils.downloads import unzip_file + >>> directory = unzip_file("path/to/file.zip") + """ + from zipfile import BadZipFile, ZipFile, is_zipfile + + if not (Path(file).exists() and is_zipfile(file)): + raise BadZipFile(f"File '{file}' does not exist or is a bad zip file.") + if path is None: + path = Path(file).parent # default path + + # Unzip the file contents + with ZipFile(file) as zipObj: + files = [f for f in zipObj.namelist() if all(x not in f for x in exclude)] + top_level_dirs = {Path(f).parts[0] for f in files} + + # Decide to unzip directly or unzip into a directory + unzip_as_dir = len(top_level_dirs) == 1 # (len(files) > 1 and not files[0].endswith("/")) + if unzip_as_dir: + # Zip has 1 top-level directory + extract_path = path # i.e. ../datasets + path = Path(path) / next(iter(top_level_dirs)) # i.e. extract coco8/ dir to ../datasets/ + else: + # Zip has multiple files at top level + path = extract_path = Path(path) / Path(file).stem # i.e. extract multiple files to ../datasets/coco8/ + + # Check if destination directory already exists and contains files + if path.exists() and any(path.iterdir()) and not exist_ok: + # If it exists and is not empty, return the path without unzipping + LOGGER.warning(f"Skipping {file} unzip as destination directory {path} is not empty.") + return path + + extract_path = Path(extract_path).resolve() + for f in TQDM(files, desc=f"Unzipping {file} to {Path(path).resolve()}...", unit="files", disable=not progress): + f_path = Path(f) + target = (extract_path / f_path).resolve() + if ( + f_path.is_absolute() + or ".." in f_path.parts + or target.parts[: len(extract_path.parts)] != extract_path.parts + ): + LOGGER.warning(f"Potentially insecure file path: {f}, skipping extraction.") + continue + zipObj.extract(f, extract_path) + + return path # return unzip dir + + +def check_disk_space( + file_bytes: int, + path: str | Path = Path.cwd(), + sf: float = 1.5, + hard: bool = True, +) -> bool: + """Check if there is sufficient disk space to download and store a file. + + Args: + file_bytes (int): The file size in bytes. + path (str | Path, optional): The path or drive to check the available free space on. + sf (float, optional): Safety factor, the multiplier for the required free space. + hard (bool, optional): Whether to throw an error or not on insufficient disk space. + + Returns: + (bool): True if there is sufficient disk space, False otherwise. + """ + _total, _used, free = shutil.disk_usage(path) # bytes + if file_bytes * sf < free: + return True # sufficient space + + def fmt_bytes(b): + return f"{b / (1 << 20):.1f} MB" if b < (1 << 30) else f"{b / (1 << 30):.3f} GB" + + # Insufficient space + text = ( + f"Insufficient free disk space {fmt_bytes(free)} < {fmt_bytes(int(file_bytes * sf))} required, " + f"Please free {fmt_bytes(int(file_bytes * sf - free))} additional disk space and try again." + ) + if hard: + raise MemoryError(text) + LOGGER.warning(text) + return False + + +def get_google_drive_file_info(link: str) -> tuple[str, str | None]: + """Retrieve the direct download link and filename for a shareable Google Drive file link. + + Args: + link (str): The shareable link of the Google Drive file. + + Returns: + url (str): Direct download URL for the Google Drive file. + filename (str | None): Original filename of the Google Drive file. If filename extraction fails, returns None. + + Examples: + >>> from ultralytics.utils.downloads import get_google_drive_file_info + >>> link = "https://drive.google.com/file/d/1cqT-cJgANNrhIHCrEufUYhQ4RqiWG_lJ/view?usp=drive_link" + >>> url, filename = get_google_drive_file_info(link) + """ + import requests # scoped as slow import + + file_id = link.split("/d/")[1].split("/view", 1)[0] + drive_url = f"https://drive.google.com/uc?export=download&id={file_id}" + filename = None + + # Start session + with requests.Session() as session: + response = session.get(drive_url, stream=True) + if "quota exceeded" in str(response.content.lower()): + raise ConnectionError( + emojis( + f"❌ Google Drive file download quota exceeded. " + f"Please try again later or download this file manually at {link}." + ) + ) + for k, v in response.cookies.items(): + if k.startswith("download_warning"): + drive_url += f"&confirm={v}" # v is token + if cd := response.headers.get("content-disposition"): + filename = re.findall('filename="(.+)"', cd)[0] + return drive_url, filename + + +def safe_download( + url: str | Path, + file: str | Path | None = None, + dir: str | Path | None = None, + unzip: bool = True, + delete: bool = False, + curl: bool = False, + retry: int = 3, + min_bytes: float = 1e0, + exist_ok: bool = False, + progress: bool = True, +) -> Path | str: + """Download files from a URL with options for retrying, unzipping, and deleting the downloaded file. Enhanced with + robust partial download detection using Content-Length validation. + + Args: + url (str | Path): The URL of the file to be downloaded. + file (str | Path, optional): The filename of the downloaded file. If not provided, the file will be saved with + the same name as the URL. + dir (str | Path, optional): The directory to save the downloaded file. If not provided, the file will be saved + in the current working directory. + unzip (bool, optional): Whether to unzip the downloaded file. + delete (bool, optional): Whether to delete the downloaded file after unzipping. + curl (bool, optional): Whether to use curl command line tool for downloading. + retry (int, optional): The number of times to retry the download in case of failure. + min_bytes (float, optional): The minimum number of bytes that the downloaded file should have, to be considered + a successful download. + exist_ok (bool, optional): Whether to overwrite existing contents during unzipping. + progress (bool, optional): Whether to display a progress bar during the download. + + Returns: + (Path | str): The path to the downloaded file or extracted directory. + + Examples: + >>> from ultralytics.utils.downloads import safe_download + >>> link = "https://ultralytics.com/assets/bus.jpg" + >>> path = safe_download(link) + """ + url = str(url) + if "://" not in url and Path(url).is_file(): # local file path ('://' check required in Windows Python<3.10) + f = Path(url) + else: + gdrive = url.startswith("https://drive.google.com/") # check if the URL is a Google Drive link + if gdrive: + url, file = get_google_drive_file_info(url) + url = url.replace(" ", "%20") # encode spaces for curl/urllib compatibility + + f = Path(dir or ".") / (file or url2file(url)) # URL converted to filename + if not f.is_file(): # URL and file do not exist + uri = (url if gdrive else clean_url(url)).replace(ASSETS_URL, "https://ultralytics.com/assets") # clean + desc = f"Downloading {uri} to '{f}'" + f.parent.mkdir(parents=True, exist_ok=True) # make directory if missing + curl_installed = shutil.which("curl") + expected_size = None # set by urllib from Content-Length; reused to validate curl retries + for i in range(retry + 1): + try: + if (curl or i > 0) and curl_installed: # curl download with retry, continue + s = "sS" * (not progress) # silent + r = subprocess.run(["curl", "-#", f"-{s}L", url, "-o", f, "--retry", "3", "-C", "-"]).returncode + assert r == 0, f"Curl return value {r}" + else: # urllib download + with request.urlopen(url) as response: + expected_size = int(response.getheader("Content-Length", 0)) + if i == 0 and expected_size > 1048576: + check_disk_space(expected_size, path=f.parent) + buffer_size = max(8192, min(1048576, expected_size // 1000)) if expected_size else 8192 + with TQDM( + total=expected_size, + desc=desc, + disable=not progress, + unit="B", + unit_scale=True, + unit_divisor=1024, + ) as pbar: + with open(f, "wb") as f_opened: + while True: + data = response.read(buffer_size) + if not data: + break + f_opened.write(data) + pbar.update(len(data)) + + if f.exists(): + file_size = f.stat().st_size + if file_size > min_bytes: + # Check if download is complete (only if we have expected_size) + if expected_size and file_size != expected_size: + LOGGER.warning( + f"Partial download: {file_size}/{expected_size} bytes ({file_size / expected_size * 100:.1f}%)" + ) + else: + break # success + f.unlink() # remove partial downloads + except MemoryError: + raise # Re-raise immediately - no point retrying if insufficient disk space + except Exception as e: + if i == 0 and not is_online(): + raise ConnectionError( + emojis(f"❌ Download failure for {uri}. Environment may be offline.") + ) from e + elif i >= retry: + raise ConnectionError( + emojis(f"❌ Download failure for {uri}. Retry limit reached. {e}") + ) from e + LOGGER.warning(f"Download failure, retrying {i + 1}/{retry} {uri}... {e}") + + if unzip and f.exists() and f.suffix in {"", ".zip", ".tar", ".gz"}: + from zipfile import is_zipfile + + unzip_dir = (dir or f.parent).resolve() # unzip to dir if provided else unzip in place + if is_zipfile(f): + unzip_dir = unzip_file(file=f, path=unzip_dir, exist_ok=exist_ok, progress=progress) # unzip + elif f.suffix in {".tar", ".gz"}: + LOGGER.info(f"Unzipping {f} to {unzip_dir}...") + with tarfile.open(f, "r:*") as tar: + for m in tar: + if not (m.isfile() or m.isdir()) or m.issym() or m.islnk(): + LOGGER.warning(f"Potentially insecure tar member: {m.name}, skipping extraction.") + continue + m_path = Path(m.name) + target = (unzip_dir / m_path).resolve() + if ( + m_path.is_absolute() + or ".." in m_path.parts + or target.parts[: len(unzip_dir.parts)] != unzip_dir.parts + ): + LOGGER.warning(f"Potentially insecure file path: {m.name}, skipping extraction.") + continue + if m.isdir(): + target.mkdir(parents=True, exist_ok=True) + elif source := tar.extractfile(m): + target.parent.mkdir(parents=True, exist_ok=True) + with source, open(target, "wb") as f: + shutil.copyfileobj(source, f) + if delete: + f.unlink() # remove zip + return unzip_dir + return f + + +def get_github_assets( + repo: str = "ultralytics/assets", + version: str = "latest", + retry: bool = False, +) -> tuple[str, list[str]]: + """Retrieve the specified version's tag and assets from a GitHub repository. + + If the version is not specified, the function fetches the latest release assets. + + Args: + repo (str, optional): The GitHub repository in the format 'owner/repo'. + version (str, optional): The release version to fetch assets from. + retry (bool, optional): Flag to retry the request in case of a failure. + + Returns: + tag (str): The release tag. + assets (list[str]): A list of asset names. + + Examples: + >>> tag, assets = get_github_assets(repo="ultralytics/assets", version="latest") + """ + import requests # scoped as slow import + + if version != "latest": + version = f"tags/{version}" # i.e. tags/v6.2 + url = f"https://api.github.com/repos/{repo}/releases/{version}" + attempts = 2 if retry else 1 # retry once on transient network errors or non-200 responses + for attempt in range(attempts): + try: + r = requests.get(url, timeout=30) # github api + except requests.exceptions.RequestException as e: + if attempt < attempts - 1: + continue # transient network error, try again + LOGGER.warning(f"GitHub assets check failure for {url}: {e}") + return "", [] + if r.status_code == 200 or r.reason == "rate limit exceeded": # do not retry 403 rate limits + break + if r.status_code != 200: + LOGGER.warning(f"GitHub assets check failure for {url}: {r.status_code} {r.reason}") + return "", [] + data = r.json() + return data["tag_name"], [x["name"] for x in data["assets"]] # tag, assets i.e. ['yolo26n.pt', 'yolo11s.pt', ...] + + +def attempt_download_asset( + file: str | Path, + repo: str = "ultralytics/assets", + release: str = "v8.4.0", + **kwargs, +) -> str: + """Attempt to download a file from GitHub release assets if it is not found locally. + + Args: + file (str | Path): The filename or file path to be downloaded. + repo (str, optional): The GitHub repository in the format 'owner/repo'. + release (str, optional): The specific release version to be downloaded. + **kwargs (Any): Additional keyword arguments for the download process. + + Returns: + (str): The path to the downloaded file. + + Examples: + >>> file_path = attempt_download_asset("yolo26n.pt", repo="ultralytics/assets", release="latest") + """ + from ultralytics.utils import SETTINGS # scoped for circular import + + # YOLOv3/5u updates + file = str(file) + file = checks.check_yolov5u_filename(file) + file = Path(file.strip().replace("'", "")) + if file.exists(): + return str(file) + elif (SETTINGS["weights_dir"] / file).exists(): + return str(SETTINGS["weights_dir"] / file) + else: + # URL specified + name = Path(parse.unquote(str(file))).name # decode '%2F' to '/' etc. + download_url = f"https://github.com/{repo}/releases/download" + if str(file).startswith(("http:/", "https:/")): # download + url = str(file).replace(":/", "://") # Pathlib turns :// -> :/ + file = url2file(name) # parse authentication query strings + if Path(file).is_file(): + LOGGER.info(f"Found {clean_url(url)} locally at {file}") # file already exists + else: + safe_download(url=url, file=file, min_bytes=1e5, **kwargs) + + elif repo == GITHUB_ASSETS_REPO and name in GITHUB_ASSETS_NAMES: + safe_download(url=f"{download_url}/{release}/{name}", file=file, min_bytes=1e5, **kwargs) + + else: + tag, assets = get_github_assets(repo, release) + if not assets: + tag, assets = get_github_assets(repo) # latest release + if name in assets: + safe_download(url=f"{download_url}/{tag}/{name}", file=file, min_bytes=1e5, **kwargs) + + return str(file) + + +def download( + url: str | list[str] | Path, + dir: Path = Path.cwd(), + unzip: bool = True, + delete: bool = False, + curl: bool = False, + threads: int = 1, + retry: int = 3, + exist_ok: bool = False, +) -> None: + """Download files from specified URLs to a given directory. + + Supports concurrent downloads if multiple threads are specified. + + Args: + url (str | list[str] | Path): The URL or list of URLs of the files to be downloaded. + dir (Path, optional): The directory where the files will be saved. + unzip (bool, optional): Flag to unzip the files after downloading. + delete (bool, optional): Flag to delete the zip files after extraction. + curl (bool, optional): Flag to use curl for downloading. + threads (int, optional): Number of threads to use for concurrent downloads. + retry (int, optional): Number of retries in case of download failure. + exist_ok (bool, optional): Whether to overwrite existing contents during unzipping. + + Examples: + >>> download("https://github.com/ultralytics/assets/releases/download/v0.0.0/bus.jpg", dir="path/to/dir") + """ + dir = Path(dir) + dir.mkdir(parents=True, exist_ok=True) # make directory + urls = [url] if isinstance(url, (str, Path)) else url + if threads > 1: + LOGGER.info(f"Downloading {len(urls)} file(s) with {threads} threads to {dir}...") + with ThreadPool(threads) as pool: + pool.map( + lambda x: safe_download( + url=x[0], + dir=x[1], + unzip=unzip, + delete=delete, + curl=curl, + retry=retry, + exist_ok=exist_ok, + progress=True, + ), + zip(urls, repeat(dir)), + ) + pool.close() + pool.join() + else: + for u in urls: + safe_download(url=u, dir=dir, unzip=unzip, delete=delete, curl=curl, retry=retry, exist_ok=exist_ok) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/errors.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/errors.py new file mode 100644 index 0000000000000000000000000000000000000000..f77284995a522de9b63638a02d3295b20ddc4f9d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/errors.py @@ -0,0 +1,35 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from ultralytics.utils import emojis + + +class HUBModelError(Exception): + """Exception raised when a model cannot be found or retrieved from Ultralytics HUB. + + This custom exception is used specifically for handling errors related to model fetching in Ultralytics YOLO. The + error message is processed to include emojis for better user experience. + + Attributes: + message (str): The error message displayed when the exception is raised. + + Methods: + __init__: Initialize the HUBModelError with a custom message. + + Examples: + >>> try: + ... # Code that might fail to find a model + ... raise HUBModelError("Custom model not found message") + ... except HUBModelError as e: + ... print(e) # Displays the emoji-enhanced error message + """ + + def __init__(self, message: str = "Model not found. Please check model URL and try again."): + """Initialize a HUBModelError exception. + + This exception is raised when a requested model is not found or cannot be retrieved from Ultralytics HUB. The + message is processed to include emojis for better user experience. + + Args: + message (str, optional): The error message to display when the exception is raised. + """ + super().__init__(emojis(message)) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/events.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/events.py new file mode 100644 index 0000000000000000000000000000000000000000..e87526780dd4ce36495e43ea189007fed680e2d2 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/events.py @@ -0,0 +1,116 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +import json +import random +import time +from pathlib import Path +from threading import Thread +from urllib.request import Request, urlopen + +from ultralytics import SETTINGS, __version__ +from ultralytics.utils import ARGV, ENVIRONMENT, GIT, IS_PIP_PACKAGE, ONLINE, PYTHON_VERSION, RANK, TESTS_RUNNING +from ultralytics.utils.downloads import GITHUB_ASSETS_NAMES +from ultralytics.utils.torch_utils import get_cpu_info + + +def _post(url: str, data: dict, timeout: float = 5.0) -> None: + """Send a one-shot JSON POST request.""" + try: + body = json.dumps(data, separators=(",", ":")).encode() # compact JSON + req = Request(url, data=body, headers={"Content-Type": "application/json"}) + urlopen(req, timeout=timeout).close() + except Exception: + pass + + +class Events: + """Collect and send anonymous usage analytics with rate-limiting. + + Event collection and transmission are enabled when sync is enabled in settings, the current process is rank -1 or 0, + tests are not running, the environment is online, and the installation source is either pip or the official + Ultralytics GitHub repository. + + Attributes: + url (str): Measurement Protocol endpoint for receiving anonymous events. + events (list[dict]): In-memory queue of event payloads awaiting transmission. + rate_limit (float): Minimum time in seconds between POST requests. + t (float): Timestamp of the last transmission in seconds since the epoch. + metadata (dict): Static metadata describing runtime, installation source, and environment. + enabled (bool): Flag indicating whether analytics collection is active. + + Methods: + __init__: Initialize the event queue, rate limiter, and runtime metadata. + __call__: Queue an event and trigger a non-blocking send when the rate limit elapses. + """ + + url = "https://www.google-analytics.com/mp/collect?measurement_id=G-X8NCJYTQXM&api_secret=QLQrATrNSwGRFRLE-cbHJw" + + def __init__(self) -> None: + """Initialize the Events instance with queue, rate limiter, and environment metadata.""" + self.events = [] # pending events + self.rate_limit = 30.0 # rate limit (seconds) + self.t = 0.0 # last send timestamp (seconds) + self.metadata = { + "cli": Path(ARGV[0]).name == "yolo", + "install": "git" if GIT.is_repo else "pip" if IS_PIP_PACKAGE else "other", + "python": PYTHON_VERSION.rsplit(".", 1)[0], # i.e. 3.13 + "CPU": get_cpu_info(), + # "GPU": get_gpu_info(index=0) if cuda else None, + "version": __version__, + "env": ENVIRONMENT, + "session_id": round(random.random() * 1e15), + "engagement_time_msec": 1000, + } + self.enabled = ( + SETTINGS["sync"] + and RANK in {-1, 0} + and not TESTS_RUNNING + and ONLINE + and (IS_PIP_PACKAGE or GIT.origin == "https://github.com/ultralytics/ultralytics.git") + ) + + def __call__(self, cfg, device=None, backend=None) -> None: + """Queue an event and flush the queue asynchronously when the rate limit elapses. + + Args: + cfg (IterableSimpleNamespace): The configuration object containing mode and task information. + device (torch.device | str, optional): The device type (e.g., 'cpu', 'cuda'). + backend (object | None, optional): The inference backend instance used during prediction. + """ + if not self.enabled: + # Events disabled, do nothing + return + + # Attempt to enqueue a new event + if len(self.events) < 25: # Queue limited to 25 events to bound memory and traffic + params = { + **self.metadata, + "task": cfg.task, + "model": cfg.model if cfg.model in GITHUB_ASSETS_NAMES else "custom", + "device": str(device), + } + if cfg.mode == "export": + params["format"] = cfg.format + if cfg.mode == "predict": + params["backend"] = type(backend).__name__ if backend is not None else None + self.events.append({"name": cfg.mode, "params": params}) + + # Check rate limit and return early if under limit + t = time.time() + if (t - self.t) < self.rate_limit: + return + + # Overrate limit: send a snapshot of queued events in a background thread + payload_events = list(self.events) # snapshot to avoid race with queue reset + Thread( + target=_post, + args=(self.url, {"client_id": SETTINGS["uuid"], "events": payload_events}), # SHA-256 anonymized + daemon=True, + ).start() + + # Reset queue and rate limit timer + self.events = [] + self.t = t + + +events = Events() diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__init__.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5b5980609c8fa459a264cf101c9b8a2c781b396c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__init__.py @@ -0,0 +1,36 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from .axelera import torch2axelera +from .coreml import torch2coreml +from .deepx import onnx2deepx +from .engine import onnx2engine, torch2onnx +from .executorch import torch2executorch +from .imx import torch2imx +from .mnn import onnx2mnn +from .ncnn import torch2ncnn +from .openvino import torch2openvino +from .paddle import torch2paddle +from .qnn import onnx2qnn +from .rknn import onnx2rknn +from .tensorflow import keras2pb, onnx2saved_model, tflite2edgetpu +from .torchscript import torch2torchscript + +__all__ = [ + "keras2pb", + "onnx2deepx", + "onnx2engine", + "onnx2mnn", + "onnx2qnn", + "onnx2rknn", + "onnx2saved_model", + "tflite2edgetpu", + "torch2axelera", + "torch2coreml", + "torch2executorch", + "torch2imx", + "torch2ncnn", + "torch2onnx", + "torch2openvino", + "torch2paddle", + "torch2torchscript", +] diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d06d712e8a99cc5ce11c5cd1675dfd1938abdd20 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/axelera.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/axelera.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..000d0cd9c7f6591025fc238910621cc7cfac4c35 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/axelera.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/coreml.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/coreml.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fca1b55567d17cacc809eaa5c820438e515b8f5b Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/coreml.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/deepx.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/deepx.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a26dcb3a47ba6e49fbcd088a307c70302e4af9e9 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/deepx.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/engine.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/engine.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f0ffa4eb90c1b80d6c773a0b5f7b5261ae03c290 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/engine.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/executorch.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/executorch.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..060b1bd6e9e6b00017b8ad47b7356c301537aa52 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/executorch.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/imx.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/imx.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9458be25eb5f1c5f1fabee7d5accb19e1bdf64c5 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/imx.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/litert.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/litert.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9e76cf820afff25386a29c4f82b591793c25779 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/litert.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/mnn.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/mnn.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ec36098d050ab4732c89de425103a3dbc1d8e17 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/mnn.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/ncnn.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/ncnn.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cca3ea138b5c51e1aa6ac295787ea9b6dc0eb03 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/ncnn.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/onnx.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/onnx.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c0236a737d695dbf5bce414f02e62af3416dd46 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/onnx.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/openvino.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/openvino.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6591f0ef7881ed330557dc79b6b89e871aa3f01 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/openvino.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/paddle.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/paddle.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8405ebce5fb65fb186f1d220e35296f5278d84dd Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/paddle.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/qnn.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/qnn.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..120967e8b82a117bc12d287971e44587053006b0 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/qnn.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/rknn.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/rknn.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee07e7d1667333c8b1750201561919864a1a3654 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/rknn.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/tensorflow.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/tensorflow.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d888704ec5ecfd1200e19d97e6912e3ce8aabc8 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/tensorflow.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/torchscript.cpython-314.pyc b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/torchscript.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58b6c7c5cea228eae06c572d3b4dcd23795bd0a1 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/__pycache__/torchscript.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/axelera.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/axelera.py new file mode 100644 index 0000000000000000000000000000000000000000..a29e5571bb1ea6117e4fddb8778a88dfbdafbb69 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/axelera.py @@ -0,0 +1,121 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import os +import shutil +import tempfile +import threading +from collections.abc import Callable +from pathlib import Path +from typing import Any + +import numpy as np +import torch + +from ultralytics.utils import LOGGER, YAML +from ultralytics.utils.checks import check_requirements + +# Axelera exports mutate process-global state (the PROTOCOL_BUFFERS env var below, plus any working-directory +# files the compiler emits), so a module-level lock serializes concurrent in-process exports. Cross-process +# Platform workers each hold their own lock and never contend. +_AXELERA_EXPORT_LOCK = threading.Lock() + + +def torch2axelera( + model: torch.nn.Module, + output_dir: Path | str, + calibration_dataset: torch.utils.data.DataLoader, + transform_fn: Callable[[Any], np.ndarray], + model_name: str = "model", + metadata: dict | None = None, + prefix: str = "", +) -> str: + """Convert a YOLO model to Axelera format. + + Args: + model (torch.nn.Module): Source YOLO model for quantization. + output_dir (Path | str): Directory to save the exported Axelera model. + calibration_dataset (torch.utils.data.DataLoader): Calibration dataloader for quantization. + transform_fn (Callable[[Any], np.ndarray]): Calibration preprocessing transform function. + model_name (str, optional): Name for the compiled model. Defaults to "model". + metadata (dict | None, optional): Optional metadata to save as YAML. Defaults to None. + prefix (str, optional): Prefix for log messages. Defaults to "". + + Returns: + (str): Path to exported Axelera model directory. + """ + # Serialize within the process: the steps below mutate process-global state (the protobuf env var and any + # working-directory files the compiler writes), so concurrent in-process exports must not overlap. + with _AXELERA_EXPORT_LOCK: + prev_protobuf = os.environ.get("PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION") + os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" + try: + try: + from axelera import compiler + except ImportError: + check_requirements( + ["axelera-devkit==1.7.0", "numpy<=2.3.5"], + cmds="--extra-index-url https://software.axelera.ai/artifactory/api/pypi/axelera-pypi/simple", + ) + from axelera import compiler + + from axelera.compiler import CompilerConfig + from axelera.compiler.config.model_specific import extract_ultralytics_metadata + + LOGGER.info(f"\n{prefix} starting export with Axelera compiler...") + + # Resolve to an absolute path so the relative compile dir below can never alias it. + output_dir = Path(output_dir).resolve() + if output_dir.exists(): + shutil.rmtree(output_dir) + + axelera_model_metadata = extract_ultralytics_metadata(model) + config = CompilerConfig( + model_metadata=axelera_model_metadata, + model_name=model_name, + resources_used=0.25, + aipu_cores_used=1, + multicore_mode="batch", + output_axm_format=True, + ) + qmodel = compiler.quantize( + model=model, + calibration_dataset=calibration_dataset, + config=config, + transform_fn=transform_fn, + ) + + # The Axelera compiler emits invalid artifacts for absolute output paths, so compile into a local + # relative directory. TemporaryDirectory gives it a unique name (so back-to-back exports of identically + # named models never collide) in the current working directory, and removes it on exit even if + # compilation raises; passing its relative basename keeps it from aliasing the absolute output_dir, so + # cleanup can never delete the result. + with tempfile.TemporaryDirectory(prefix="axelera_compile_", dir=".") as compile_root: + compile_dir = Path(Path(compile_root).name) + compiler.compile(model=qmodel, config=config, output_dir=compile_dir) + + output_dir.mkdir(parents=True, exist_ok=True) + for artifact in [f"{model_name}.axm", "compiler_config_final.toml"]: + for artifact_path in [compile_dir / artifact, Path(artifact)]: + if artifact_path.exists(): + artifact_path.replace(output_dir / artifact_path.name) + break + + # Remove intermediate compiler artifacts, keeping only the compiled model and config. + keep_suffixes = {".axm"} + keep_names = {"compiler_config_final.toml", "metadata.yaml"} + for f in output_dir.iterdir(): + if f.is_file() and f.suffix not in keep_suffixes and f.name not in keep_names: + f.unlink() + + if metadata is not None: + YAML.save(output_dir / "metadata.yaml", metadata) + + return str(output_dir) + finally: + # Restore original PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION value + if prev_protobuf is None: + os.environ.pop("PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION", None) + else: + os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = prev_protobuf diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/coreml.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/coreml.py new file mode 100644 index 0000000000000000000000000000000000000000..8f57ec4623dfd803043a28c0e1d62c23de3e651d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/coreml.py @@ -0,0 +1,254 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import torch +import torch.nn as nn + +from ultralytics.utils import LOGGER +from ultralytics.utils.checks import check_requirements + + +class IOSDetectModel(nn.Module): + """Wrap an Ultralytics YOLO model for Apple iOS CoreML export.""" + + def __init__(self, model: nn.Module, im: torch.Tensor, mlprogram: bool = True): + """Initialize the IOSDetectModel class with a YOLO model and example image. + + Args: + model (nn.Module): The YOLO model to wrap. + im (torch.Tensor): Example input tensor with shape (B, C, H, W). + mlprogram (bool): Whether exporting to MLProgram format. + """ + super().__init__() + _, _, h, w = im.shape # batch, channel, height, width + self.model = model + self.nc = len(model.names) # number of classes + self.mlprogram = mlprogram + if w == h: + self.normalize = 1.0 / w # scalar + else: + self.normalize = torch.tensor( + [1.0 / w, 1.0 / h, 1.0 / w, 1.0 / h], # broadcast (slower, smaller) + device=next(model.parameters()).device, + ) + + def forward(self, x: torch.Tensor): + """Normalize predictions of object detection model with input size-dependent factors.""" + xywh, cls = self.model(x)[0].transpose(0, 1).split((4, self.nc), 1) + if self.mlprogram and self.nc % 80 != 0: # NMS bug https://github.com/ultralytics/ultralytics/issues/22309 + pad_length = int(((self.nc + 79) // 80) * 80) - self.nc # pad class length to multiple of 80 + cls = torch.nn.functional.pad(cls, (0, pad_length, 0, 0), "constant", 0) + return cls, xywh * self.normalize + + +def pipeline_coreml( + model: Any, + output_shape: tuple[int, ...], + metadata: dict, + mlmodel: bool = False, + iou: float = 0.45, + conf: float = 0.25, + agnostic_nms: bool = False, + weights_dir: Path | str | None = None, + prefix: str = "", +): + """Create CoreML pipeline with NMS for YOLO detection models. + + Args: + model: CoreML model. + output_shape (tuple[int, ...]): Output shape tuple from the exporter. + metadata (dict): Model metadata. + mlmodel (bool): Whether the model is an MLModel (vs MLProgram). + iou (float): IoU threshold for NMS. + conf (float): Confidence threshold for NMS. + agnostic_nms (bool): Whether to use class-agnostic NMS. + weights_dir (Path | str | None): Weights directory for MLProgram models. + prefix (str): Prefix for log messages. + + Returns: + CoreML pipeline model. + """ + import coremltools as ct + + LOGGER.info(f"{prefix} starting pipeline with coremltools {ct.__version__}...") + + spec = model.get_spec() + outs = list(iter(spec.description.output)) + if mlmodel: # mlmodel doesn't infer shapes automatically + outs[0].type.multiArrayType.shape[:] = output_shape[2], output_shape[1] - 4 + outs[1].type.multiArrayType.shape[:] = output_shape[2], 4 + + names = metadata["names"] + nx = spec.description.input[0].type.imageType.width + ny = spec.description.input[0].type.imageType.height + nc = outs[0].type.multiArrayType.shape[-1] + if len(names) != nc: # Hack fix for MLProgram NMS bug https://github.com/ultralytics/ultralytics/issues/22309 + names = {**names, **{i: str(i) for i in range(len(names), nc)}} + + model = ct.models.MLModel(spec, weights_dir=weights_dir, skip_model_load=True) + + # Create NMS protobuf + nms_spec = ct.proto.Model_pb2.Model() + nms_spec.specificationVersion = spec.specificationVersion + for i in range(len(outs)): + decoder_output = model._spec.description.output[i].SerializeToString() + nms_spec.description.input.add() + nms_spec.description.input[i].ParseFromString(decoder_output) + nms_spec.description.output.add() + nms_spec.description.output[i].ParseFromString(decoder_output) + + output_names = ["confidence", "coordinates"] + for i, name in enumerate(output_names): + nms_spec.description.output[i].name = name + + for i, out in enumerate(outs): + ma_type = nms_spec.description.output[i].type.multiArrayType + ma_type.shapeRange.sizeRanges.add() + ma_type.shapeRange.sizeRanges[0].lowerBound = 0 + ma_type.shapeRange.sizeRanges[0].upperBound = -1 + ma_type.shapeRange.sizeRanges.add() + ma_type.shapeRange.sizeRanges[1].lowerBound = out.type.multiArrayType.shape[-1] + ma_type.shapeRange.sizeRanges[1].upperBound = out.type.multiArrayType.shape[-1] + del ma_type.shape[:] + + nms = nms_spec.nonMaximumSuppression + nms.confidenceInputFeatureName = outs[0].name # 1x507x80 + nms.coordinatesInputFeatureName = outs[1].name # 1x507x4 + nms.confidenceOutputFeatureName = output_names[0] + nms.coordinatesOutputFeatureName = output_names[1] + nms.iouThresholdInputFeatureName = "iouThreshold" + nms.confidenceThresholdInputFeatureName = "confidenceThreshold" + nms.iouThreshold = iou + nms.confidenceThreshold = conf + nms.pickTop.perClass = not agnostic_nms + nms.stringClassLabels.vector.extend(names.values()) + nms_model = ct.models.MLModel(nms_spec, skip_model_load=True) + + # Pipeline models together + pipeline = ct.models.pipeline.Pipeline( + input_features=[ + ("image", ct.models.datatypes.Array(3, ny, nx)), + ("iouThreshold", ct.models.datatypes.Double()), + ("confidenceThreshold", ct.models.datatypes.Double()), + ], + output_features=output_names, + ) + pipeline.add_model(model) + pipeline.add_model(nms_model) + + # Correct datatypes + pipeline.spec.description.input[0].ParseFromString(model._spec.description.input[0].SerializeToString()) + pipeline.spec.description.output[0].ParseFromString(nms_model._spec.description.output[0].SerializeToString()) + pipeline.spec.description.output[1].ParseFromString(nms_model._spec.description.output[1].SerializeToString()) + + # Update metadata + pipeline.spec.specificationVersion = spec.specificationVersion + pipeline.spec.description.metadata.userDefined.update( + {"IoU threshold": str(nms.iouThreshold), "Confidence threshold": str(nms.confidenceThreshold)} + ) + + # Save the model + model = ct.models.MLModel(pipeline.spec, weights_dir=weights_dir, skip_model_load=True) + model.input_description["image"] = "Input image" + model.input_description["iouThreshold"] = f"(optional) IoU threshold override (default: {nms.iouThreshold})" + model.input_description["confidenceThreshold"] = ( + f"(optional) Confidence threshold override (default: {nms.confidenceThreshold})" + ) + model.output_description["confidence"] = 'Boxes × Class confidence (see user-defined metadata "classes")' + model.output_description["coordinates"] = "Boxes × [x, y, width, height] (relative to image size)" + LOGGER.info(f"{prefix} pipeline success") + return model + + +def torch2coreml( + model: nn.Module, + inputs: list, + im: torch.Tensor, + classifier_names: list[str] | None, + output_file: Path | str | None = None, + mlmodel: bool = False, + quantize: int | str | None = None, + metadata: dict | None = None, + prefix: str = "", +) -> Any: + """Export a PyTorch model to CoreML ``.mlpackage`` or ``.mlmodel`` format. + + Args: + model (nn.Module): The PyTorch model to export. + inputs (list): CoreML input descriptions for the model. + im (torch.Tensor): Example input tensor for tracing. + classifier_names (list[str] | None): Class names for classifier config, or None if not a classifier. + output_file (Path | str | None): Output file path, or None to skip saving. + mlmodel (bool): Whether to export as ``.mlmodel`` (neural network) instead of ``.mlpackage`` (ML program). + quantize (int | str | None): Precision scheme, e.g. 16 for FP16 or 8/``"w8a16"`` for INT8 weights. + metadata (dict | None): Metadata to embed in the CoreML model. + prefix (str): Prefix for log messages. + + Returns: + (ct.models.MLModel): The converted CoreML model. + """ + import coremltools as ct + + LOGGER.info(f"\n{prefix} starting export with coremltools {ct.__version__}...") + ts = torch.jit.trace(model.eval(), im, strict=False) # TorchScript model + fp16 = quantize == 16 + weight_int8 = quantize in {8, "w8a16"} + + # Based on apple's documentation it is better to leave out the minimum_deployment target and let that get set + # Internally based on the model conversion and output type. + # Setting minimum_deployment_target >= iOS16 will require setting compute_precision=ct.precision.FLOAT32. + # iOS16 adds in better support for FP16, but none of the CoreML NMS specifications handle FP16 as input. + convert_kwargs = dict( + inputs=inputs, + classifier_config=ct.ClassifierConfig(classifier_names) if classifier_names else None, + convert_to="neuralnetwork" if mlmodel else "mlprogram", + skip_model_load=True, + ) + if not mlmodel: + # ML Program conversion defaults to FP16. Pin FP32 unless FP16/INT8 was requested. + from ultralytics.nn.modules.head import RTDETRDecoder + + if not (fp16 or weight_int8): + convert_kwargs["compute_precision"] = ct.precision.FLOAT32 + elif any(isinstance(m, RTDETRDecoder) for m in model.modules()): + # RT-DETR decoder class logits and deformable-sampling indices drift in fp16; pin those op types to fp32. + fp32_ops = {"linear", "gather", "gather_nd", "gather_along_axis"} + convert_kwargs["compute_precision"] = ct.transform.FP16ComputePrecision( + op_selector=lambda op: op.op_type not in fp32_ops + ) + ct_model = ct.convert(ts, **convert_kwargs) + bits, mode = (8, "kmeans") if weight_int8 else (16, "linear") if fp16 else (32, None) + if bits < 32: + if "kmeans" in mode: + check_requirements("scikit-learn") # scikit-learn package required for k-means quantization + if mlmodel: + ct_model = ct.models.neural_network.quantization_utils.quantize_weights(ct_model, bits, mode) + elif bits == 8: # mlprogram already quantized to FP16 + import coremltools.optimize.coreml as cto + + op_config = cto.OpPalettizerConfig(mode="kmeans", nbits=bits, weight_threshold=512) + config = cto.OptimizationConfig(global_config=op_config) + ct_model = cto.palettize_weights(ct_model, config=config) + + m = dict(metadata or {}) # copy to avoid mutating original + ct_model.short_description = m.pop("description", "") + ct_model.author = m.pop("author", "") + ct_model.license = m.pop("license", "") + ct_model.version = m.pop("version", "") + ct_model.user_defined_metadata.update({k: str(v) for k, v in m.items()}) + + if output_file is not None: + try: + ct_model.save(str(output_file)) # save *.mlpackage + except Exception as e: + LOGGER.warning( + f"{prefix} CoreML export to *.mlpackage failed ({e}), reverting to *.mlmodel export. " + f"Known coremltools Python 3.11 and Windows bugs https://github.com/apple/coremltools/issues/1928." + ) + output_file = Path(output_file).with_suffix(".mlmodel") + ct_model.save(str(output_file)) + return ct_model diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/deepx.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/deepx.py new file mode 100644 index 0000000000000000000000000000000000000000..6bf1183643c80ce100d670327b67a36f05729a09 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/deepx.py @@ -0,0 +1,72 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import json +from pathlib import Path + +from ultralytics.utils import LOGGER, YAML +from ultralytics.utils.checks import check_requirements + + +def onnx2deepx( + onnx_file: str | Path, + imgsz: tuple[int, int], + dataset, + metadata: dict | None = None, + optimize: bool = False, + prefix: str = "", +) -> Path: + """Convert an ONNX model to DEEPX format using the DEEPX DX-Compiler. + + Args: + onnx_file (str | Path): Input ONNX model path. + imgsz (tuple[int, int]): Export image size as ``(height, width)``. + dataset (DataLoader): Calibration dataloader used to build the DEEPX config. + metadata (dict | None, optional): Optional metadata to save as YAML. Defaults to None. + optimize (bool, optional): If True, enables higher compiler optimization which reduces inference latency and + increases compilation time. Defaults to False. + prefix (str, optional): Logging prefix. Defaults to "". + + Returns: + (Path): Path to the exported DEEPX model directory. + """ + try: + import dx_com + except ImportError: + check_requirements("dx_com", cmds="-f https://sdk.deepx.ai/release/dxcom/v2.3.0/index.html") + import dx_com + + LOGGER.info(f"\n{prefix} starting export with DEEPX...") + + onnx_file = Path(onnx_file) + export_path = onnx_file.parent / f"{onnx_file.stem}_deepx_model" + export_path.mkdir(exist_ok=True) + config_path = export_path / "config.json" + + config = { + "inputs": {"images": [1, 3, imgsz[0], imgsz[1]]}, + "calibration_num": 100, # number of steps used during calibration + "calibration_method": "ema", # calibration method used during quantization + "default_loader": { + "dataset_path": str(dataset.dataset.img_path), # JSON needs str, classify returns a Path + "file_extensions": [val for x in ["jpeg", "jpg", "png"] for val in (x.lower(), x.upper())], + "preprocessings": [ + {"resize": {"mode": "pad", "size": imgsz[0], "pad_location": "edge", "pad_value": [114, 114, 114]}}, + {"div": {"x": 255.0}}, + {"convertColor": {"form": "BGR2RGB"}}, + {"transpose": {"axis": [2, 0, 1]}}, + {"expandDim": {"axis": 0}}, + ], + }, + } + + with open(config_path, "w") as file: + json.dump(config, file) + + dx_com.compile(model=str(onnx_file), output_dir=str(export_path), config=str(config_path), opt_level=int(optimize)) + + if metadata is not None: + YAML.save(export_path / "metadata.yaml", metadata) + + return export_path diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/engine.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/engine.py new file mode 100644 index 0000000000000000000000000000000000000000..1bb58ee809ffb2142dedecab435a35c7d1a246bb --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/engine.py @@ -0,0 +1,396 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import json +import types +from pathlib import Path + +import torch + +from ultralytics.utils import IS_JETSON, LOGGER, TORCH_VERSION, ThreadingLocked, is_dgx, is_jetson +from ultralytics.utils.checks import check_requirements, check_tensorrt, check_version +from ultralytics.utils.torch_utils import TORCH_2_4, TORCH_2_9 + + +def best_onnx_opset(onnx: types.ModuleType, cuda: bool = False) -> int: + """Return max ONNX opset for this torch version with ONNX fallback.""" + if TORCH_2_4: # _constants.ONNX_MAX_OPSET first defined in torch 1.13 + opset = torch.onnx.utils._constants.ONNX_MAX_OPSET - 1 # use second-latest version for safety + if TORCH_2_9: + opset = min(opset, 20) # legacy TorchScript exporter caps at opset 20 in torch 2.9+ + if cuda: + opset -= 2 # fix CUDA ONNXRuntime NMS squeeze op errors + else: + version = ".".join(TORCH_VERSION.split(".")[:2]) + opset = { + "1.8": 12, + "1.9": 12, + "1.10": 13, + "1.11": 14, + "1.12": 15, + "1.13": 17, + "2.0": 17, # reduced from 18 to fix ONNX errors + "2.1": 17, # reduced from 19 + "2.2": 17, # reduced from 19 + "2.3": 17, # reduced from 19 + "2.4": 20, + "2.5": 20, + "2.6": 20, + "2.7": 20, + "2.8": 23, + }.get(version, 12) + return min(opset, onnx.defs.onnx_opset_version()) + + +@ThreadingLocked() +def torch2onnx( + model: torch.nn.Module, + im: torch.Tensor | tuple[torch.Tensor, ...], + output_file: Path | str, + opset: int = 14, + input_names: list[str] | None = None, + output_names: list[str] | None = None, + dynamic: dict | None = None, +) -> str: + """Export a PyTorch model to ONNX format. + + Args: + model (torch.nn.Module): The PyTorch model to export. + im (torch.Tensor | tuple[torch.Tensor, ...]): Example input tensor(s) for tracing. + output_file (Path | str): Path to save the exported ONNX file. + opset (int): ONNX opset version to use for export. + input_names (list[str] | None): List of input tensor names. Defaults to ``["images"]``. + output_names (list[str] | None): List of output tensor names. Defaults to ``["output0"]``. + dynamic (dict | None): Dictionary specifying dynamic axes for inputs and outputs. + + Returns: + (str): Path to the exported ONNX file. + + Notes: + Setting `do_constant_folding=True` may cause issues with DNN inference for torch>=1.12. + """ + if input_names is None: + input_names = ["images"] + if output_names is None: + output_names = ["output0"] + kwargs = {"dynamo": False} if TORCH_2_4 else {} + torch.onnx.export( + model, + im, + output_file, + verbose=False, + opset_version=opset, + do_constant_folding=True, # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False + input_names=input_names, + output_names=output_names, + dynamic_axes=dynamic, + **kwargs, + ) + return str(output_file) + + +def modelopt_quantize_onnx( + onnx_file: str, + quantize: int | str | None = None, + dataset=None, + shape: tuple[int, int, int, int] = (1, 3, 640, 640), + dynamic: bool = False, + prefix: str = "", +) -> str: + """Bake reduced precision into an ONNX model for TensorRT 11 strongly-typed builds using NVIDIA ModelOpt. + + TensorRT 11 is strongly-typed only: it removed the FP16/INT8 builder flags and the ``IInt8Calibrator`` interface, so + reduced precision must be expressed in the ONNX graph itself before building. FP16 is applied via ModelOpt AutoCast + mixed-precision conversion and INT8 via explicit Q/DQ quantization with calibration. + + Args: + onnx_file (str): Path to the FP32 ONNX file to convert. + quantize (int | str | None): Precision scheme, 8 for INT8 Q/DQ nodes or 16 for FP16 precision. + dataset (ultralytics.data.build.InfiniteDataLoader | None): Dataloader providing INT8 calibration images. + Required when ``quantize=8``. + shape (tuple[int, int, int, int]): Input shape (batch, channels, height, width) used for dynamic calibration. + dynamic (bool): Whether the ONNX model uses dynamic input shapes. + prefix (str): Prefix for log messages. + + Returns: + (str): Path to the precision-converted ONNX file. + """ + if quantize == 8 and dataset is None: + raise ValueError("INT8 ModelOpt quantization requires a calibration dataset.") + + # Require modelopt >= 0.44: older releases import onnx.mapping which was removed in onnx >= 1.18 and crash + check_requirements("nvidia-modelopt[onnx]>=0.44") + import onnx + + input_name = onnx.load(onnx_file, load_external_data=False).graph.input[0].name + if quantize == 8: + from modelopt.onnx.quantization import quantize as modelopt_quantize + + out_file = str(Path(onnx_file).with_suffix(".int8.onnx")) + # Collect up to ~500 calibration images (TensorRT recommendation); ModelOpt holds them in memory at once, + # so cap the count to bound memory instead of materializing the entire (possibly thousands-image) dataset. + images, n = [], 0 + for batch in dataset: + images.append(batch["img"]) + n += images[-1].shape[0] + if n >= 512: + break + calib = torch.cat(images).to(torch.float32) / 255.0 + LOGGER.info(f"{prefix} quantizing ONNX to INT8 with ModelOpt using {calib.shape[0]} calibration images...") + kwargs = {"calibration_shapes": f"{input_name}:{'x'.join(str(d) for d in shape)}"} if dynamic else {} + modelopt_quantize( + onnx_file, + quantize_mode="int8", + calibration_data={input_name: calib.cpu().numpy()}, + calibration_method="max", + # Calibrate on CPU. ModelOpt's CUDA EP session can hit an uncatchable cuDNN-ABI segfault (its pinned + # onnxruntime-gpu's cuDNN vs the installed torch's) and the TensorRT EP aborts on RTX cards (NvTensorRTRTX); + # scales are EP-independent, so the INT8 engine is equivalent and only this one-time step is slower. + calibration_eps=["cpu"], + output_path=out_file, + **kwargs, + ) + return out_file + + import modelopt.onnx.autocast as autocast + + out_file = str(Path(onnx_file).with_suffix(".fp16.onnx")) + LOGGER.info(f"{prefix} converting ONNX to FP16 mixed precision with ModelOpt AutoCast...") + onnx.save( + autocast.convert_to_mixed_precision( + onnx_file, + low_precision_type="fp16", + keep_io_types=True, + calibration_data={input_name: torch.randn(*shape).cpu().numpy()}, + ), + out_file, + ) + return out_file + + +def onnx2engine( + onnx_file: str, + output_file: Path | str | None = None, + workspace: int | None = None, + quantize: int | str | None = None, + dynamic: bool = False, + shape: tuple[int, int, int, int] = (1, 3, 640, 640), + dla: int | None = None, + dataset=None, + metadata: dict | None = None, + verbose: bool = False, + prefix: str = "", +) -> str: + """Export a YOLO model to TensorRT engine format. + + Args: + onnx_file (str): Path to the ONNX file to be converted. + output_file (Path | str | None): Path to save the generated TensorRT engine file. + workspace (int | None): Workspace size in GB for TensorRT. + quantize (int | str | None): Precision scheme, 16 for FP16 or 8 for INT8. + dynamic (bool, optional): Enable dynamic input shapes. + shape (tuple[int, int, int, int], optional): Input shape (batch, channels, height, width). + dla (int | None): DLA core to use (Jetson devices only). + dataset (ultralytics.data.build.InfiniteDataLoader, optional): Dataset for INT8 calibration. + metadata (dict | None): Metadata to include in the engine file. + verbose (bool, optional): Enable verbose logging. + prefix (str, optional): Prefix for log messages. + + Returns: + (str): Path to the exported engine file. + + Raises: + ValueError: If DLA is enabled on non-Jetson devices or required precision is not set. + RuntimeError: If the ONNX file cannot be parsed. + + Notes: + TensorRT version compatibility is handled for workspace size and engine building. On TensorRT 7-10, INT8 + calibration uses an ``IInt8Calibrator`` over ``dataset`` and writes a calibration cache, while FP16/INT8 are + enabled with builder flags. On TensorRT 11 these were removed in favor of strongly-typed networks, so reduced + precision is baked into the ONNX with NVIDIA ModelOpt before building (FP16 AutoCast, INT8 explicit Q/DQ) by + `modelopt_quantize_onnx`. Metadata is serialized and written to the engine file if provided. + """ + # Force re-install TensorRT on CUDA 13 ARM devices to 10.15.x versions for RT-DETR exports + # https://github.com/ultralytics/ultralytics/issues/22873 + if is_jetson(jetpack=7) or is_dgx(): + check_tensorrt("10.15") + + try: + import tensorrt as trt + except ImportError: + check_tensorrt() + import tensorrt as trt + check_version(trt.__version__, ">=7.0.0", hard=True) + check_version(trt.__version__, "!=10.2.0", msg="https://github.com/ultralytics/ultralytics/pull/24367") + + LOGGER.info(f"\n{prefix} starting export with TensorRT {trt.__version__}...") + output_file = output_file or Path(onnx_file).with_suffix(".engine") + + logger = trt.Logger(trt.Logger.INFO) + if verbose: + logger.min_severity = trt.Logger.Severity.VERBOSE + + # Engine builder + builder = trt.Builder(logger) + config = builder.create_builder_config() + workspace_bytes = int((workspace or 0) * (1 << 30)) + trt_major = int(trt.__version__.split(".", 1)[0]) + is_trt10 = trt_major >= 10 + # TensorRT >= 11 is strongly-typed only: precision builder flags and IInt8Calibrator removed + is_trt11 = trt_major >= 11 + if workspace_bytes > 0: + if hasattr(config, "set_memory_pool_limit"): + config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace_bytes) + else: # TensorRT 7 fallback + config.max_workspace_size = workspace_bytes + # EXPLICIT_BATCH flag is removed in TensorRT 10 (explicit batch is the only/default mode); keep it for TRT 7/8 + flag = 0 if is_trt10 else (1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) + network = builder.create_network(flag) + # platform_has_fast_fp16/int8 were removed from the Builder in TensorRT 10; default to True when absent + use_fp16 = getattr(builder, "platform_has_fast_fp16", True) and quantize == 16 + use_int8 = getattr(builder, "platform_has_fast_int8", True) and quantize == 8 + if use_int8 and dataset is None: + raise ValueError("INT8 TensorRT export requires a calibration dataset.") + + # Optionally switch to DLA if enabled + if dla is not None: + if not IS_JETSON: + raise ValueError("DLA is only available on NVIDIA Jetson devices") + if check_version(trt.__version__, ">=11.0.0,<11.1.0"): + # DLA is unsupported in TensorRT 11.0 and is planned to return in a later release + # https://docs.nvidia.com/deeplearning/tensorrt/latest/api/migration/tensorrt-10x-to-11x-jetson.html + raise ValueError("DLA is not supported in TensorRT 11.0; export with TensorRT 10.x to use DLA.") + LOGGER.info(f"{prefix} enabling DLA on core {dla}...") + if not use_fp16 and not use_int8: + raise ValueError( + "DLA requires either quantize=16 (FP16) or quantize=8 (INT8). Please enable one of them and try again." + ) + config.default_device_type = trt.DeviceType.DLA + config.DLA_core = int(dla) + config.set_flag(trt.BuilderFlag.GPU_FALLBACK) + + # TensorRT 11 is strongly-typed and removed the FP16/INT8 builder flags and INT8 calibrator, so reduced + # precision must be baked into the ONNX graph with NVIDIA ModelOpt before parsing (FP16 AutoCast, INT8 Q/DQ) + if is_trt11 and (use_fp16 or use_int8): + onnx_file = modelopt_quantize_onnx(onnx_file, quantize, dataset, shape, dynamic, prefix) + + # Read ONNX file + parser = trt.OnnxParser(network, logger) + if not parser.parse_from_file(onnx_file): + raise RuntimeError(f"failed to load ONNX file: {onnx_file}") + + # Network inputs + inputs = [network.get_input(i) for i in range(network.num_inputs)] + outputs = [network.get_output(i) for i in range(network.num_outputs)] + for inp in inputs: + LOGGER.info(f'{prefix} input "{inp.name}" with shape{inp.shape} {inp.dtype}') + for out in outputs: + LOGGER.info(f'{prefix} output "{out.name}" with shape{out.shape} {out.dtype}') + + if dynamic: + profile = builder.create_optimization_profile() + min_shape = (1, shape[1], 32, 32) # minimum input shape + max_shape = (*shape[:2], *(int(max(2, workspace or 2) * d) for d in shape[2:])) # max input shape + for inp in inputs: + profile.set_shape(inp.name, min=min_shape, opt=shape, max=max_shape) + config.add_optimization_profile(profile) + if use_int8 and not is_trt10: # deprecated in TensorRT 10, causes internal errors + config.set_calibration_profile(profile) + + LOGGER.info( + f"{prefix} building {'INT8' if use_int8 else 'FP' + ('16' if use_fp16 else '32')} engine as {output_file}" + ) + if use_int8 and not is_trt11: + config.set_flag(trt.BuilderFlag.INT8) + config.profiling_verbosity = trt.ProfilingVerbosity.DETAILED + + class EngineCalibrator(trt.IInt8Calibrator): + """Custom INT8 calibrator for TensorRT engine optimization. + + This calibrator provides the necessary interface for TensorRT to perform INT8 quantization calibration using + a dataset. It handles batch generation, caching, and calibration algorithm selection. + + Attributes: + dataset: Dataset for calibration. + data_iter: Iterator over the calibration dataset. + algo (trt.CalibrationAlgoType): Calibration algorithm type. + batch (int): Batch size for calibration. + cache (Path): Path to save the calibration cache. + + Methods: + get_algorithm: Get the calibration algorithm to use. + get_batch_size: Get the batch size to use for calibration. + get_batch: Get the next batch to use for calibration. + read_calibration_cache: Use existing cache instead of calibrating again. + write_calibration_cache: Write calibration cache to disk. + """ + + def __init__( + self, + dataset, # ultralytics.data.build.InfiniteDataLoader + cache: str = "", + ) -> None: + """Initialize the INT8 calibrator with dataset and cache path.""" + trt.IInt8Calibrator.__init__(self) + self.dataset = dataset + self.data_iter = iter(dataset) + self.algo = ( + trt.CalibrationAlgoType.ENTROPY_CALIBRATION_2 # DLA quantization needs ENTROPY_CALIBRATION_2 + if dla is not None + else trt.CalibrationAlgoType.MINMAX_CALIBRATION + ) + self.batch = dataset.batch_size + self.cache = Path(cache) + + def get_algorithm(self) -> trt.CalibrationAlgoType: + """Get the calibration algorithm to use.""" + return self.algo + + def get_batch_size(self) -> int: + """Get the batch size to use for calibration.""" + return self.batch or 1 + + def get_batch(self, names) -> list[int] | None: + """Get the next batch to use for calibration, as a list of device memory pointers.""" + try: + im0s = next(self.data_iter)["img"] / 255.0 + im0s = im0s.to("cuda") if im0s.device.type == "cpu" else im0s + return [int(im0s.data_ptr())] + except StopIteration: + # Return None to signal to TensorRT there is no calibration data remaining + return None + + def read_calibration_cache(self) -> bytes | None: + """Use existing cache instead of calibrating again, otherwise, implicitly return None.""" + if self.cache.exists() and self.cache.suffix == ".cache": + return self.cache.read_bytes() + + def write_calibration_cache(self, cache: bytes) -> None: + """Write calibration cache to disk.""" + _ = self.cache.write_bytes(cache) + + # Load dataset w/ builder (for batching) and calibrate + config.int8_calibrator = EngineCalibrator( + dataset=dataset, + cache=str(Path(onnx_file).with_suffix(".cache")), + ) + + elif use_fp16 and not is_trt11: + config.set_flag(trt.BuilderFlag.FP16) + + # Write file + if hasattr(builder, "build_serialized_network"): + engine = builder.build_serialized_network(network, config) + else: + engine = builder.build_engine(network, config) + engine = None if engine is None else engine.serialize() + if engine is None: + raise RuntimeError("TensorRT engine build failed, check logs for errors") + with open(output_file, "wb") as t: + if metadata is not None: + meta = json.dumps(metadata) + t.write(len(meta).to_bytes(4, byteorder="little", signed=True)) + t.write(meta.encode()) + t.write(engine) + return str(output_file) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/executorch.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/executorch.py new file mode 100644 index 0000000000000000000000000000000000000000..9360daec10fa1666358aadcf3147c2a2ccf55a30 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/executorch.py @@ -0,0 +1,80 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from functools import partial +from pathlib import Path + +import torch + +from ultralytics.nn.modules import Pose, Pose26 +from ultralytics.utils import LOGGER, YAML +from ultralytics.utils.checks import check_executorch_requirements + + +def executorch_wrapper(model: torch.nn.Module) -> torch.nn.Module: + """Apply ExecuTorch-specific model patches required for export/runtime compatibility.""" + import types + + for m in model.modules(): + if not isinstance(m, Pose): + continue + m.kpts_decode = types.MethodType(partial(_executorch_kpts_decode, is_pose26=type(m) is Pose26), m) + return model + + +def _executorch_kpts_decode(self, kpts: torch.Tensor, is_pose26: bool = False) -> torch.Tensor: + """Decode pose keypoints for ExecuTorch export with XNNPACK-safe broadcasting.""" + ndim = self.kpt_shape[1] + bs = kpts.shape[0] + y = kpts.view(bs, *self.kpt_shape, -1) + + # XNNPACK requires explicit dim matching for broadcasting, expand 2D tensors to 4D. + anchors = self.anchors[None, None] + strides = self.strides[None, None] + a = ((y[:, :, :2] + anchors) if is_pose26 else (y[:, :, :2] * 2.0 + (anchors - 0.5))) * strides + if ndim == 3: + a = torch.cat((a, y[:, :, 2:3].sigmoid()), 2) + return a.view(bs, self.nk, -1) + + +def torch2executorch( + model: torch.nn.Module, + im: torch.Tensor, + output_dir: Path | str, + metadata: dict | None = None, + prefix: str = "", +) -> str: + """Export a PyTorch model to ExecuTorch format. + + Args: + model (torch.nn.Module): The PyTorch model to export. + im (torch.Tensor): Example input tensor for tracing/export. + output_dir (Path | str): Directory to save the exported ExecuTorch model. + metadata (dict | None, optional): Optional metadata to save as YAML. + prefix (str, optional): Prefix for log messages. + + Returns: + (str): Path to the exported ExecuTorch model directory. + """ + check_executorch_requirements() + from executorch import version as executorch_version + from executorch.backends.xnnpack.partition.xnnpack_partitioner import XnnpackPartitioner + from executorch.exir import to_edge_transform_and_lower + + LOGGER.info(f"\n{prefix} starting export with ExecuTorch {executorch_version.__version__}...") + + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + pte_file = output_dir / "model.pte" + et_program = to_edge_transform_and_lower( + torch.export.export(model, (im,)), + partitioner=[XnnpackPartitioner()], + ).to_executorch() + pte_file.write_bytes(et_program.buffer) + + if metadata is not None: + YAML.save(output_dir / "metadata.yaml", metadata) + + return str(output_dir) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/imx.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/imx.py new file mode 100644 index 0000000000000000000000000000000000000000..3eaadf4993ee29f2ab435e3d8761a22fa660e6d2 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/imx.py @@ -0,0 +1,372 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import re +import subprocess +import sys +import types +from pathlib import Path +from shutil import which + +import numpy as np +import torch + +from ultralytics.nn.modules import Detect, Pose, Segment +from ultralytics.utils import IS_DEBIAN_BOOKWORM, IS_DEBIAN_TRIXIE, IS_RASPBERRYPI, IS_UBUNTU, LOGGER, WINDOWS +from ultralytics.utils.checks import check_apt_requirements, check_requirements +from ultralytics.utils.patches import onnx_export_patch +from ultralytics.utils.tal import make_anchors +from ultralytics.utils.torch_utils import copy_attr + +# Configuration for Model Compression Toolkit (MCT) quantization +MCT_CONFIG = { + "YOLO11": { + "detect": { + "layer_names": ["sub", "mul_2", "add_14", "cat_19"], + "weights_memory": 2585350.2439, + "n_layers": {238, 239}, + }, + "pose": { + "layer_names": ["sub", "mul_2", "add_14", "cat_21", "cat_22", "mul_4", "add_15"], + "weights_memory": 2437771.67, + "n_layers": {257, 258}, + }, + "classify": {"layer_names": [], "weights_memory": np.inf, "n_layers": {112}}, + "segment": { + "layer_names": ["sub", "mul_2", "add_14", "cat_21"], + "weights_memory": 2466604.8, + "n_layers": {265, 266}, + }, + }, + "YOLOv8": { + "detect": { + "layer_names": ["sub", "mul", "add_6", "cat_15"], + "weights_memory": 2550540.8, + "n_layers": {168, 169}, + }, + "pose": { + "layer_names": ["add_7", "mul_2", "cat_17", "mul", "sub", "add_6", "cat_18"], + "weights_memory": 2482451.85, + "n_layers": {187, 188}, + }, + "classify": {"layer_names": [], "weights_memory": np.inf, "n_layers": {73}}, + "segment": { + "layer_names": ["sub", "mul", "add_6", "cat_17"], + "weights_memory": 2580060.0, + "n_layers": {195, 196}, + }, + }, +} + + +class FXModel(torch.nn.Module): + """A custom model class for torch.fx compatibility. + + This class extends `torch.nn.Module` and is designed to ensure compatibility with torch.fx for tracing and graph + manipulation. It copies attributes from an existing model and explicitly sets the model attribute to ensure proper + copying. + + Attributes: + model (nn.Module): The original model's layers. + imgsz (tuple[int, int]): The input image size (height, width). + """ + + def __init__(self, model, imgsz=(640, 640)): + """Initialize the FXModel. + + Args: + model (nn.Module): The original model to wrap for torch.fx compatibility. + imgsz (tuple[int, int]): The input image size (height, width). Default is (640, 640). + """ + super().__init__() + copy_attr(self, model) + # Explicitly set `model` since `copy_attr` somehow does not copy it. + self.model = model.model + self.imgsz = imgsz + + def forward(self, x): + """Forward pass through the model. + + This method performs the forward pass through the model, handling the dependencies between layers and saving + intermediate outputs. + + Args: + x (torch.Tensor): The input tensor to the model. + + Returns: + (torch.Tensor): The output tensor from the model. + """ + y = [] # outputs + for m in self.model: + if m.f != -1: # if not from previous layer + # from earlier layers + x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] + if isinstance(m, Detect): + m._inference = types.MethodType(_inference, m) # bind method to Detect + m.anchors, m.strides = ( + x.transpose(0, 1) + for x in make_anchors( + torch.cat([s / m.stride.unsqueeze(-1) for s in self.imgsz], dim=1), m.stride, 0.5 + ) + ) + if type(m) is Pose: + m.forward = types.MethodType(pose_forward, m) # bind method to Pose + if type(m) is Segment: + m.forward = types.MethodType(segment_forward, m) # bind method to Segment + x = m(x) # run + y.append(x) # save output + return x + + +def _inference(self, x: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Decode boxes and cls scores for imx object detection.""" + dbox = self.decode_bboxes(self.dfl(x["boxes"]), self.anchors.unsqueeze(0)) * self.strides + return dbox.transpose(1, 2), x["scores"].sigmoid().permute(0, 2, 1) + + +def pose_forward(self, x: list[torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """Forward pass for imx pose estimation, including keypoint decoding.""" + bs = x[0].shape[0] # batch size + nk_out = getattr(self, "nk_output", self.nk) + kpt = torch.cat([self.cv4[i](x[i]).view(bs, nk_out, -1) for i in range(self.nl)], -1) + + # If using Pose26 with 5 dims, convert to 3 dims for export + if hasattr(self, "nk_output") and self.nk_output != self.nk: + spatial = kpt.shape[-1] + kpt = kpt.view(bs, self.kpt_shape[0], self.kpt_shape[1] + 2, spatial) + kpt = kpt[:, :, :-2, :] # Remove sigma_x, sigma_y + kpt = kpt.view(bs, self.nk, spatial) + x = Detect.forward(self, x) + pred_kpt = self.kpts_decode(kpt) + return *x, pred_kpt.permute(0, 2, 1) + + +def segment_forward(self, x: list[torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + """Forward pass for imx segmentation.""" + p = self.proto(x[0]) # mask protos + bs = p.shape[0] # batch size + mc = torch.cat([self.cv4[i](x[i]).view(bs, self.nm, -1) for i in range(self.nl)], 2) # mask coefficients + x = Detect.forward(self, x) + return *x, mc.transpose(1, 2), p + + +class NMSWrapper(torch.nn.Module): + """Wrap PyTorch Module with multiclass_nms layer from edge-mdt-cl.""" + + def __init__( + self, + model: torch.nn.Module, + score_threshold: float = 0.001, + iou_threshold: float = 0.7, + max_detections: int = 300, + task: str = "detect", + ): + """Initialize NMSWrapper with PyTorch Module and NMS parameters. + + Args: + model (torch.nn.Module): Model instance. + score_threshold (float): Score threshold for non-maximum suppression. + iou_threshold (float): Intersection over union threshold for non-maximum suppression. + max_detections (int): The number of detections to return. + task (str): Task type, one of 'detect', 'pose', or 'segment'. + """ + super().__init__() + self.model = model + self.score_threshold = score_threshold + self.iou_threshold = iou_threshold + self.max_detections = max_detections + self.task = task + + def forward(self, images): + """Forward pass with model inference and NMS post-processing.""" + from edgemdt_cl.pytorch.nms.nms_with_indices import multiclass_nms_with_indices + + # model inference + outputs = self.model(images) + boxes, scores = outputs[0], outputs[1] + nms_outputs = multiclass_nms_with_indices( + boxes=boxes, + scores=scores, + score_threshold=self.score_threshold, + iou_threshold=self.iou_threshold, + max_detections=self.max_detections, + ) + if self.task == "pose": + kpts = outputs[2] # (bs, max_detections, kpts 17*3) + out_kpts = torch.gather(kpts, 1, nms_outputs.indices.unsqueeze(-1).expand(-1, -1, kpts.size(-1))) + return nms_outputs.boxes, nms_outputs.scores, nms_outputs.labels, out_kpts + if self.task == "segment": + mc, proto = outputs[2], outputs[3] + out_mc = torch.gather(mc, 1, nms_outputs.indices.unsqueeze(-1).expand(-1, -1, mc.size(-1))) + return nms_outputs.boxes, nms_outputs.scores, nms_outputs.labels, out_mc, proto + return nms_outputs.boxes, nms_outputs.scores, nms_outputs.labels, nms_outputs.n_valid + + +def torch2imx( + model: torch.nn.Module, + output_dir: Path | str, + conf: float, + iou: float, + max_det: int, + metadata: dict | None = None, + gptq: bool = False, + dataset=None, + prefix: str = "", +) -> str: + """Export YOLO model to IMX format for deployment on Sony IMX500 devices. + + This function quantizes a YOLO model using Model Compression Toolkit (MCT) and exports it to IMX format compatible + with Sony IMX500 edge devices. It supports both YOLOv8n and YOLO11n models for detection, segmentation, pose + estimation, and classification tasks. + + Args: + model (torch.nn.Module): The YOLO model to export. Must be YOLOv8n or YOLO11n. + output_dir (Path | str): Directory to save the exported IMX model. + conf (float): Confidence threshold for NMS post-processing. + iou (float): IoU threshold for NMS post-processing. + max_det (int): Maximum number of detections to return. + metadata (dict | None, optional): Metadata to embed in the ONNX model. Defaults to None. + gptq (bool, optional): Whether to use Gradient-Based Post Training Quantization. If False, uses standard Post + Training Quantization. Defaults to False. + dataset (optional): Representative dataset for quantization calibration. Defaults to None. + prefix (str, optional): Logging prefix string. Defaults to "". + + Returns: + (str): Path to the exported IMX model directory. + + Raises: + ValueError: If the model is not a supported YOLOv8n or YOLO11n variant. + + Examples: + >>> from ultralytics import YOLO + >>> model = YOLO("yolo11n.pt") + >>> path = torch2imx(model, "output_dir/", conf=0.25, iou=0.7, max_det=300) + + Notes: + - Auto-installs Java>=17, model-compression-toolkit, imx500-converter, and related packages if not present + - Only supports YOLOv8n and YOLO11n models (detection, segmentation, pose, and classification tasks) + - Output includes quantized ONNX model, IMX binary, and labels.txt file + """ + # Install Java>=17 + try: + java_output = subprocess.run(["java", "--version"], check=True, capture_output=True).stdout.decode() + version_match = re.search(r"(?:openjdk|java) (\d+)", java_output) + java_version = int(version_match.group(1)) if version_match else 0 + assert java_version >= 17, "Java version too old" + except (FileNotFoundError, subprocess.CalledProcessError, AssertionError): + if IS_UBUNTU or IS_DEBIAN_TRIXIE: + LOGGER.info(f"\n{prefix} installing Java 21 for Ubuntu...") + check_apt_requirements(["openjdk-21-jre"]) + elif IS_RASPBERRYPI or IS_DEBIAN_BOOKWORM: + LOGGER.info(f"\n{prefix} installing Java 17 for Raspberry Pi or Debian ...") + check_apt_requirements(["openjdk-17-jre"]) + + check_requirements( + ( + "model-compression-toolkit>=2.4.1", + "edge-mdt-cl<1.1.0", + "edge-mdt-tpc>=1.2.0", + "pydantic<2.12", + ) + ) + check_requirements("imx500-converter[pt]>=3.17.3") + dataset = dataset() if callable(dataset) else dataset # resolve lazy dataloader + + import model_compression_toolkit as mct + import onnx + from edgemdt_tpc import get_target_platform_capabilities + + LOGGER.info(f"\n{prefix} starting export with model_compression_toolkit {mct.__version__}...") + + def representative_dataset_gen(dataloader=dataset): + for batch in dataloader: + img = batch["img"] + img = img / 255.0 + yield [img] + + # NOTE: need tpc_version to be "4.0" for IMX500 Pose estimation models + tpc = get_target_platform_capabilities(tpc_version="4.0", device_type="imx500") + + bit_cfg = mct.core.BitWidthConfig() + mct_config = MCT_CONFIG["YOLO11" if "C2PSA" in model.__str__() else "YOLOv8"][model.task] + + # Check if the model has the expected number of layers + if len(list(model.modules())) not in mct_config["n_layers"]: + raise ValueError("IMX export only supported for YOLOv8n and YOLO11n models.") + + for layer_name in mct_config["layer_names"]: + bit_cfg.set_manual_activation_bit_width([mct.core.common.network_editors.NodeNameFilter(layer_name)], 16) + + config = mct.core.CoreConfig( + mixed_precision_config=mct.core.MixedPrecisionQuantizationConfig(num_of_images=10), + quantization_config=mct.core.QuantizationConfig(concat_threshold_update=True), + bit_width_config=bit_cfg, + ) + + resource_utilization = mct.core.ResourceUtilization(weights_memory=mct_config["weights_memory"]) + + quant_model = ( + mct.gptq.pytorch_gradient_post_training_quantization( # Perform Gradient-Based Post Training Quantization + model=model, + representative_data_gen=representative_dataset_gen, + target_resource_utilization=resource_utilization, + gptq_config=mct.gptq.get_pytorch_gptq_config( + n_epochs=1000, use_hessian_based_weights=False, use_hessian_sample_attention=False + ), + core_config=config, + target_platform_capabilities=tpc, + )[0] + if gptq + else mct.ptq.pytorch_post_training_quantization( # Perform post training quantization + in_module=model, + representative_data_gen=representative_dataset_gen, + target_resource_utilization=resource_utilization, + core_config=config, + target_platform_capabilities=tpc, + )[0] + ) + + if model.task != "classify": + quant_model = NMSWrapper( + model=quant_model, + score_threshold=conf or 0.001, + iou_threshold=iou, + max_detections=max_det, + task=model.task, + ) + + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + onnx_model = output_dir / "model_imx.onnx" + + with onnx_export_patch(): + mct.exporter.pytorch_export_model( + model=quant_model, save_model_path=onnx_model, repr_dataset=representative_dataset_gen + ) + + model_onnx = onnx.load(onnx_model) # load onnx model + for k, v in (metadata or {}).items(): + meta = model_onnx.metadata_props.add() + meta.key, meta.value = k, str(v) + + onnx.save(model_onnx, onnx_model) + + # Find imxconv-pt binary - check venv bin directory first, then PATH + bin_dir = Path(sys.executable).parent + imxconv = bin_dir / ("imxconv-pt.exe" if WINDOWS else "imxconv-pt") + if not imxconv.exists(): + imxconv = which("imxconv-pt") # fallback to PATH + if not imxconv: + raise FileNotFoundError("imxconv-pt not found. Install with: pip install imx500-converter[pt]") + + subprocess.run( + [str(imxconv), "-i", str(onnx_model), "-o", str(output_dir), "--no-input-persistency", "--overwrite-output"], + check=True, + ) + + # Needed for imx models. + with open(output_dir / "labels.txt", "w", encoding="utf-8") as labels_file: + labels_file.writelines([f"{name}\n" for _, name in model.names.items()]) + + return str(output_dir) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/litert.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/litert.py new file mode 100644 index 0000000000000000000000000000000000000000..e66d20209d787ccc7946642eb7a35041b643cdd1 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/litert.py @@ -0,0 +1,150 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import json +import zipfile +from pathlib import Path + +import torch + +from ultralytics.utils import LOGGER + + +class _NormalizeCoords(torch.nn.Module): + """Wrap a model so box (and pose keypoint) coordinates are output normalized to [0, 1]. + + LiteRT exports trace the raw PyTorch model, whose detection output concatenates pixel-space box coordinates + (0-imgsz) with [0, 1] class scores in a single tensor. A single per-tensor INT8 scale cannot represent both ranges, + so the scores collapse to zero. Normalizing coordinates to [0, 1] keeps the whole tensor in a unit range so + quantization preserves score resolution; ``LiteRTBackend`` denormalizes by image size at runtime. + + Only the coordinate channels are divided — x/width and y/height — so the divisor is uniform-magnitude and quantizes + cleanly to a single per-tensor scale. (Multiplying the whole tensor by a mixed-magnitude per-channel vector instead + would round the small coordinate factors to zero and destroy box accuracy.) Per-axis division also supports + non-square ``imgsz``; ``LiteRTBackend`` denormalizes by width/height to match. + """ + + def __init__(self, model: torch.nn.Module, h: int, w: int, task: str, nc: int, kpt_shape: tuple | None): + """Initialize with the wrapped model, input height/width, task, class count and optional keypoint shape.""" + super().__init__() + self.model = model + self.h = h + self.w = w + self.task = task + self.nc = nc + self.kpt_shape = kpt_shape + + def forward(self, x: torch.Tensor): + """Run the wrapped model and normalize coordinate channels of the detection output to [0, 1].""" + y = self.model(x) + det = y[0] if isinstance(y, (tuple, list)) else y # segment returns (detections, protos) + box_wh = torch.tensor([self.w, self.h, self.w, self.h], dtype=det.dtype, device=det.device).view(1, 4, 1) + parts = [det[:, :4] / box_wh] # box xywh: x,w by width; y,h by height + if self.task == "pose" and self.kpt_shape: + parts.append(det[:, 4 : 4 + self.nc]) # class scores + b, _, a = det.shape + kpts = det[:, 4 + self.nc :].view(b, self.kpt_shape[0], self.kpt_shape[1], a) + kpt_wh = torch.tensor([self.w, self.h], dtype=det.dtype, device=det.device).view(1, 1, 2, 1) + kpts = torch.cat([kpts[:, :, :2] / kpt_wh, kpts[:, :, 2:]], dim=2) # normalize x, y; keep conf + parts.append(kpts.reshape(b, -1, a)) + else: + parts.append(det[:, 4:]) # class scores (+ mask coefficients / angle) + det = torch.cat(parts, dim=1) + return (det, *y[1:]) if isinstance(y, (tuple, list)) else det + + +def torch2litert( + model: torch.nn.Module, + im: torch.Tensor, + file: Path, + quantize: int | str | None, + calibration_dataset: torch.utils.data.DataLoader | None, + metadata: dict | None, + prefix: str, +) -> Path: + """Export a PyTorch model to LiteRT format using litert_torch, with optional INT8 quantization. + + Three INT8 schemes are supported via ``quantize``: ``8`` applies static INT8 (int8 weights + int8 activations) and + ``'w8a16'`` applies static INT8 weights with int16 activations, both requiring a ``calibration_dataset``; + ``'w8a32'`` applies dynamic/weight-only INT8 (int8 weights + FP32 activations) and needs no calibration. + ``None``/``32`` exports FP32. FP16 is not exported as a separate model: LiteRT runs an FP32 model in FP16 at runtime + via the GPU delegate (FP16 by default) or the XNNPACK ``FORCE_FP16`` flag on ARM. + + Args: + model (torch.nn.Module): The PyTorch model to export. + im (torch.Tensor): Example input tensor for tracing. + file (Path | str): Source model file path used to derive output directory. + quantize (int | str | None): Quantization scheme: ``8`` (static INT8), ``'w8a16'`` (static int8 weights + int16 + activations), ``'w8a32'`` (dynamic INT8), or ``None``/``32`` (FP32). + calibration_dataset (DataLoader | None): Calibration dataloader for static quantization, as returned by + ``get_int8_calibration_dataloader``. Required when ``quantize`` is ``8`` or ``'w8a16'``. + metadata (dict | None): Optional metadata embedded in the ``.tflite`` as a ``metadata.json`` entry. + prefix (str): Prefix for log messages. + + Returns: + (Path): Path to the exported ``.tflite`` file with metadata embedded as a ``metadata.json`` entry. + """ + from ultralytics.utils.checks import check_requirements + + check_requirements(("litert-torch>=0.9.0", "ai-edge-litert>=2.1.4")) + import litert_torch + + static_int8 = quantize == 8 + static_int16 = quantize == "w8a16" + dynamic_int8 = quantize == "w8a32" + LOGGER.info(f"\n{prefix} starting export with litert_torch {litert_torch.__version__}...") + file = Path(file) + quant_tag = "_int8" if static_int8 else "_w8a16" if static_int16 else "_w8a32" if dynamic_int8 else "" + + # Normalize coordinate channels to [0, 1] so INT8 quantization preserves scores (denormalized in LiteRTBackend). + # End-to-end models output post-NMS pixel coordinates in FP32 (no scale collapse), so they are left as-is. + meta = metadata or {} + task = meta.get("task") + if task in {"detect", "segment", "pose", "obb"} and not meta.get("end2end", False): + model = _NormalizeCoords( + model, int(im.shape[2]), int(im.shape[3]), task, len(meta.get("names", {})), meta.get("kpt_shape") + ) + + edge_model = litert_torch.convert(model, (im,)) + tflite_file = file.with_name(f"{file.stem}{quant_tag}.tflite") + edge_model.export(tflite_file) + + if static_int8 or static_int16 or dynamic_int8: + check_requirements("ai-edge-quantizer>=0.6.0") + from ai_edge_quantizer import qtyping, quantizer, recipe + + qt = quantizer.Quantizer(str(tflite_file)) + if static_int8 or static_int16: # static schemes calibrate over representative images + act = "int8" if static_int8 else "int16" + LOGGER.info(f"{prefix} applying static quantization (int8 weights + {act} activations)...") + calib_samples = [] + for batch in calibration_dataset: + imgs = batch["img"].cpu().float() / 255.0 + # litert-torch traces a fixed batch; feed whole batches matching im's batch dim (skip a partial tail). + if imgs.shape[0] == im.shape[0]: + calib_samples.append({"args_0": imgs.numpy()}) + assert calib_samples, ( + f"No calibration batches of size {im.shape[0]} were collected; the calibration dataset needs at least " + f"'batch={im.shape[0]}' images for static LiteRT quantization (reduce 'batch' or add more 'data')." + ) + qt.load_quantization_recipe(recipe.static_wi8_ai8() if static_int8 else recipe.static_wi8_ai16()) + # Keep FP32 graph input/output (weights/activations stay int8/int16 internally): matches the historical + # onnx2tf "fp32 in/out" contract that downstream consumers (LiteRT GPU delegate, on-device runtimes) expect, + # and avoids forcing every consumer to (de)quantize at the boundary. Must run after load_quantization_recipe. + for op in (qtyping.TFLOperationName.INPUT, qtyping.TFLOperationName.OUTPUT): + qt.update_quantization_recipe( + regex=".*", operation_name=op, algorithm_key=recipe.AlgorithmName.NO_QUANTIZE + ) + result = qt.calibrate({"serving_default": calib_samples}) + qt.quantize(calibration_result=result).export_model(str(tflite_file), overwrite=True) + else: # dynamic / weight-only INT8: int8 weights, FP32 activations, no calibration needed + LOGGER.info(f"{prefix} applying dynamic INT8 quantization (int8 weights + FP32 activations)...") + qt.load_quantization_recipe(recipe.dynamic_wi8_afp32()) + qt.quantize().export_model(str(tflite_file), overwrite=True) + + # Embed metadata as a JSON entry appended to the .tflite (zip-tolerant flatbuffer), so the model is a single + # self-contained file that LiteRTBackend reads back at load time. + with zipfile.ZipFile(tflite_file, "a", zipfile.ZIP_DEFLATED) as zf: + zf.writestr("metadata.json", json.dumps(metadata or {})) + return tflite_file diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/mnn.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/mnn.py new file mode 100644 index 0000000000000000000000000000000000000000..5cbce56e871f1add15e9ac405c635c1e36fc542b --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/mnn.py @@ -0,0 +1,61 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import json +from pathlib import Path + +from ultralytics.utils import LOGGER + + +def onnx2mnn( + onnx_file: str, + output_file: Path | str, + quantize: int | str | None = None, + metadata: dict | None = None, + prefix: str = "", +) -> str: + """Convert an ONNX model to MNN format. + + Args: + onnx_file (str): Path to the source ONNX file. + output_file (Path | str): Path to save the exported MNN model. + quantize (int | str | None): Precision scheme, e.g. 16 for FP16 or 8 for INT8 weights. + metadata (dict | None): Optional metadata embedded via ``--bizCode``. + prefix (str): Prefix for log messages. + + Returns: + (str): Path to the exported ``.mnn`` file. + """ + from ultralytics.utils.checks import check_requirements + from ultralytics.utils.torch_utils import TORCH_1_10 + + assert TORCH_1_10, "MNN export requires torch>=1.10.0 to avoid segmentation faults" + assert Path(onnx_file).exists(), f"failed to export ONNX file: {onnx_file}" + + check_requirements("MNN>=2.9.6") + import MNN + from MNN.tools import mnnconvert + + LOGGER.info(f"\n{prefix} starting export with MNN {MNN.version()}...") + mnn_args = [ + "", + "-f", + "ONNX", + "--modelFile", + onnx_file, + "--MNNModel", + str(output_file), + "--bizCode", + json.dumps(metadata or {}), + ] + if quantize == 8: + mnn_args.extend(("--weightQuantBits", "8")) + if quantize == 16: + mnn_args.append("--fp16") + mnnconvert.convert(mnn_args) + # Remove scratch file created during model convert optimize + convert_scratch = Path(output_file).parent / ".__convert_external_data.bin" + if convert_scratch.exists(): + convert_scratch.unlink() + return str(output_file) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/ncnn.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/ncnn.py new file mode 100644 index 0000000000000000000000000000000000000000..b055fe6f456ad55a02a94f5ac0033735d1fcb601 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/ncnn.py @@ -0,0 +1,67 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from pathlib import Path + +import torch + +from ultralytics.utils import LOGGER, YAML + + +def torch2ncnn( + model: torch.nn.Module, + im: torch.Tensor, + output_dir: Path | str, + quantize: int | str | None = None, + metadata: dict | None = None, + device: torch.device | None = None, + prefix: str = "", +) -> str: + """Export a PyTorch model to NCNN format using PNNX. + + Args: + model (torch.nn.Module): The PyTorch model to export. + im (torch.Tensor): Example input tensor for tracing. + output_dir (Path | str): Directory to save the exported NCNN model. + quantize (int | str | None): Precision scheme, e.g. 16 for FP16. + metadata (dict | None): Optional metadata saved as ``metadata.yaml``. + device (torch.device | None): Device the model lives on. + prefix (str): Prefix for log messages. + + Returns: + (str): Path to the exported ``_ncnn_model`` directory. + """ + from ultralytics.utils.checks import check_requirements + + check_requirements("ncnn", cmds="--no-deps") # no deps to avoid installing opencv-python + check_requirements("pnnx") + import ncnn + import pnnx + + LOGGER.info(f"\n{prefix} starting export with NCNN {ncnn.__version__} and PNNX {pnnx.__version__}...") + output_dir = Path(output_dir) + + ncnn_args = dict( + ncnnparam=(output_dir / "model.ncnn.param").as_posix(), + ncnnbin=(output_dir / "model.ncnn.bin").as_posix(), + ncnnpy=(output_dir / "model_ncnn.py").as_posix(), + ) + pnnx_args = dict( + ptpath=(output_dir / "model.pt").as_posix(), + pnnxparam=(output_dir / "model.pnnx.param").as_posix(), + pnnxbin=(output_dir / "model.pnnx.bin").as_posix(), + pnnxpy=(output_dir / "model_pnnx.py").as_posix(), + pnnxonnx=(output_dir / "model.pnnx.onnx").as_posix(), + ) + + output_dir.mkdir(parents=True, exist_ok=True) # make ncnn_model directory + device_type = device.type if device is not None else "cpu" + pnnx.export(model, inputs=im, **ncnn_args, **pnnx_args, fp16=quantize == 16, device=device_type) + + for f_debug in ("debug.bin", "debug.param", "debug2.bin", "debug2.param", *pnnx_args.values()): + Path(f_debug).unlink(missing_ok=True) + + if metadata: + YAML.save(output_dir / "metadata.yaml", metadata) # add metadata.yaml + return str(output_dir) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/onnx.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/onnx.py new file mode 100644 index 0000000000000000000000000000000000000000..bda0d6a60c1ec132ce03406cda58b58fd21a4a6b --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/onnx.py @@ -0,0 +1,55 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from pathlib import Path + +import numpy as np + +from ultralytics.utils import LOGGER + + +def onnx_calibration_reader(dataset, transform_fn, input_name: str = "images", batch: int = 0): + """Create an ONNX Runtime calibration data reader from an Ultralytics calibration dataloader. + + `batch` is the graph's static batch dimension (0 for dynamic-batch models): calibration datasets smaller than the + export batch yield undersized batches that static graphs reject, so samples are tiled up to exactly `batch`. + """ + from onnxruntime.quantization import CalibrationDataReader + + class _CalibrationReader(CalibrationDataReader): + def __init__(self): + """Materialize calibration inputs as `{input_name: float32_NCHW}` dicts.""" + self.samples = [] + for b in dataset: + im = transform_fn(b) + if batch and im.shape[0] != batch: # tile up to the static batch dimension + im = np.tile(im, (-(-batch // im.shape[0]), 1, 1, 1))[:batch] + self.samples.append({input_name: im}) + self.iterator = iter(self.samples) + + def get_next(self): + """Return the next calibration sample, or None when exhausted.""" + return next(self.iterator, None) + + def rewind(self): + """Reset the iterator for an additional calibration pass.""" + self.iterator = iter(self.samples) + + return _CalibrationReader() + + +def onnx_int8_quantize( + onnx_file, + output_file, + dataset, + transform_fn, + input_name: str = "images", + batch: int = 0, + prefix: str = "", +) -> str: + """Quantize an ONNX model to INT8 using ONNX Runtime static quantization.""" + from onnxruntime.quantization import quantize_static + + onnx_file, output_file = Path(onnx_file), Path(output_file) + LOGGER.info(f"{prefix} quantizing INT8 with ONNX Runtime...") + quantize_static(str(onnx_file), str(output_file), onnx_calibration_reader(dataset, transform_fn, input_name, batch)) + return str(output_file) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/openvino.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/openvino.py new file mode 100644 index 0000000000000000000000000000000000000000..1a95b82e394bf9651be0debfe6b44225e4a7f9cc --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/openvino.py @@ -0,0 +1,64 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import torch + +from ultralytics.utils import LOGGER + + +def torch2openvino( + model: torch.nn.Module, + im: torch.Tensor | list[torch.Tensor] | tuple[torch.Tensor, ...], + output_dir: Path | str | None = None, + dynamic: bool = False, + quantize: int | str | None = None, + calibration_dataset: Any | None = None, + ignored_scope: dict | None = None, + prefix: str = "", +) -> Any: + """Export a PyTorch model to OpenVINO format with optional INT8 quantization. + + Args: + model (torch.nn.Module): The model to export (may be NMS-wrapped). + im (torch.Tensor | list[torch.Tensor] | tuple[torch.Tensor, ...]): Example input tensor(s) for tracing. + output_dir (Path | str | None): Directory to save the exported OpenVINO model. + dynamic (bool): Whether to use dynamic input shapes. + quantize (int | str | None): Precision scheme, e.g. 16 for FP16 or 8 for INT8. + calibration_dataset (nncf.Dataset | None): Dataset for INT8 calibration (required when ``quantize=8``). + ignored_scope (dict | None): Kwargs passed to ``nncf.IgnoredScope`` for head patterns. + prefix (str): Prefix for log messages. + + Returns: + (ov.Model): The converted OpenVINO model. + """ + import openvino as ov + + LOGGER.info(f"\n{prefix} starting export with openvino {ov.__version__}...") + + input_shape = [i.shape for i in im] if isinstance(im, (list, tuple)) else im.shape + # Hand OpenVINO an already-traced ScriptModule (torchscript/coreml exports trace the same way), not a raw + # nn.Module, so it doesn't re-trace internally with check_trace=True - that re-trace-and-diff sanity check is + # non-deterministic on NMS models and fails with "Graphs differed across invocations!". check_trace=False skips + # the same check on our own trace. + ts = torch.jit.trace(model, im, strict=False, check_trace=False) + ov_model = ov.convert_model(ts, input=None if dynamic else input_shape, example_input=im) + if quantize == 8: + import nncf + + ov_model = nncf.quantize( + model=ov_model, + calibration_dataset=calibration_dataset, + preset=nncf.QuantizationPreset.MIXED, + ignored_scope=ignored_scope, + ) + + if output_dir is not None: + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + output_file = output_dir / "model.xml" + ov.save_model(ov_model, output_file, compress_to_fp16=quantize == 16) + return ov_model diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/paddle.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/paddle.py new file mode 100644 index 0000000000000000000000000000000000000000..7d3f46701946f8bb8c9f16f2bcb98e28aad95116 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/paddle.py @@ -0,0 +1,53 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from pathlib import Path + +import torch + +from ultralytics.utils import ARM64, IS_JETSON, LOGGER, YAML + + +def torch2paddle( + model: torch.nn.Module, + im: torch.Tensor, + output_dir: Path | str, + metadata: dict | None = None, + prefix: str = "", +) -> str: + """Export a PyTorch model to PaddlePaddle format using X2Paddle. + + Args: + model (torch.nn.Module): The PyTorch model to export. + im (torch.Tensor): Example input tensor for tracing. + output_dir (Path | str): Directory to save the exported PaddlePaddle model. + metadata (dict | None): Optional metadata saved as ``metadata.yaml``. + prefix (str): Prefix for log messages. + + Returns: + (str): Path to the exported ``_paddle_model`` directory. + """ + assert not IS_JETSON, "Jetson Paddle exports not supported yet" + from ultralytics.utils.checks import check_requirements + + check_requirements( + ( + "paddlepaddle-gpu>=3.0.0,<3.3.0" # pin <3.3.0 https://github.com/PaddlePaddle/Paddle/issues/77340 + if torch.cuda.is_available() + else "paddlepaddle==3.0.0" # pin 3.0.0 for ARM64 + if ARM64 + else "paddlepaddle>=3.0.0,<3.3.0", # pin <3.3.0 https://github.com/PaddlePaddle/Paddle/issues/77340 + "x2paddle", + ) + ) + + import x2paddle + from x2paddle.convert import pytorch2paddle + + LOGGER.info(f"\n{prefix} starting export with X2Paddle {x2paddle.__version__}...") + + pytorch2paddle(module=model, save_dir=output_dir, jit_type="trace", input_examples=[im]) # export + if metadata: + YAML.save(Path(output_dir) / "metadata.yaml", metadata) # add metadata.yaml + return str(output_dir) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/qnn.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/qnn.py new file mode 100644 index 0000000000000000000000000000000000000000..898065fe7f419c2f18b5ba019ca90b3ccf3e0d22 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/qnn.py @@ -0,0 +1,163 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from pathlib import Path + +from ultralytics.utils import LOGGER, WINDOWS +from ultralytics.utils.checks import check_requirements + + +def qnn_library_paths() -> tuple[str | None, str]: + """Resolve the QNN Execution Provider and HTP backend library paths for the installed onnxruntime-qnn build. + + onnxruntime-qnn ships two ways: plugin builds expose an `onnxruntime_qnn` helper module, while monolithic builds + expose `QNNExecutionProvider` directly and bundle the QNN backend libraries in `onnxruntime/capi`. + + Returns: + (tuple[str | None, str]): `(ep_library_path, htp_backend_path)`. `ep_library_path` is `None` when QNN is already + built into ONNX Runtime and does not need `register_execution_provider_library`. + """ + try: + import onnxruntime_qnn as qnn_ep + + return qnn_ep.get_library_path(), qnn_ep.get_qnn_htp_path() + except ImportError: + import onnxruntime + + capi = Path(onnxruntime.__file__).parent / "capi" + if "QNNExecutionProvider" in onnxruntime.get_available_providers(): + ep_lib = None + else: + ep_lib = capi / ("onnxruntime_providers_qnn.dll" if WINDOWS else "libonnxruntime_providers_qnn.so") + htp_lib = "QnnHtp.dll" if WINDOWS else "libQnnHtp.so" + return str(ep_lib) if ep_lib else None, str(capi / htp_lib) + + +def onnx2qnn( + onnx_file: str | Path, + output_file: Path | str, + dataset, + transform_fn, + name: str = "73", + metadata: dict | None = None, + batch: int = 0, + prefix: str = "", +) -> str: + """Convert an ONNX model to a Qualcomm QNN context binary using the ONNX Runtime QNN Execution Provider. + + The conversion runs entirely on the host with no Qualcomm account or cloud upload. The model is quantized with ONNX + Runtime's QNN QDQ flow to 16-bit activations and 8-bit weights (the recommended accuracy/performance balance for the + Hexagon NPU), then the `onnxruntime-qnn` Execution Provider — which bundles the Qualcomm AI Runtime (QAIRT) + libraries — compiles the quantized graph into a QNN context binary embedded in `_qnn.onnx`. No inference is + run. + + Args: + onnx_file (str | Path): Path to the source ONNX file (already exported). + output_file (Path | str): Path to save the exported QNN ONNX context-binary model. + dataset (DataLoader): Calibration dataloader (from `Exporter.get_int8_calibration_dataloader`) used for INT8 + quantization. + transform_fn (Callable): Preprocessing transform (`Exporter._transform_fn`) converting a calibration item to a + normalized `float32` NCHW array. + name (str): Target Hexagon Tensor Processor (HTP) architecture version, e.g. `"73"` (Snapdragon 8 Gen 2), `"75"` + (8 Gen 3), `"79"` (8 Elite). Finalizes the graph for the target chip when exporting on a host without a + Snapdragon NPU. + metadata (dict | None): Ultralytics model metadata ensured present in the context model's `metadata_props` (ONNX + Runtime normally carries the source model's metadata through, but this is not a documented guarantee). + batch (int): Static batch dimension of the ONNX graph used to tile undersized calibration batches, or 0 for + dynamic-batch models. + prefix (str): Prefix for log messages. + + Returns: + (str): Path to the exported `*_qnn.onnx` file. + + Notes: + `onnxruntime-qnn` wheels may expose QNN either as a plugin library or as a built-in ONNX Runtime provider. + """ + check_requirements("onnxruntime-qnn") + import onnxruntime as ort + from onnxruntime.quantization import QuantType, quantize + from onnxruntime.quantization.execution_providers.qnn import get_qnn_qdq_config + from onnxruntime.quantization.shape_inference import quant_pre_process + + from ultralytics.utils.export.onnx import onnx_calibration_reader + + ep_library, htp_backend = qnn_library_paths() + + onnx_file = Path(onnx_file) + ctx_file = Path(output_file) + ctx_file.parent.mkdir(parents=True, exist_ok=True) + pre_file = ctx_file.with_name(f"{onnx_file.stem}_qnn_preprocessed.onnx") + qdq_file = ctx_file.with_name(f"{onnx_file.stem}_qnn_qdq.onnx") + + LOGGER.info(f"\n{prefix} starting W8A16 quantization and export with ONNX Runtime QNN (HTP arch {name})...") + import onnx + + dims = [d.dim_value for d in onnx.load(str(onnx_file)).graph.input[0].type.tensor_type.shape.dim] + if len(dims) == 4 and dims[3] in {1, 3} and dims[1] not in {1, 3}: # channel-last graph (QNNModel export) + nchw_transform = transform_fn + transform_fn = lambda data_item: nchw_transform(data_item).transpose(0, 2, 3, 1) # noqa: E731 + try: + quant_pre_process(str(onnx_file), str(pre_file)) + # 16-bit activations + 8-bit weights is the ORT-recommended accuracy/perf balance for the HTP backend + qdq_config = get_qnn_qdq_config( + str(pre_file), + onnx_calibration_reader(dataset, transform_fn, batch=batch), + activation_type=QuantType.QUInt16, + weight_type=QuantType.QUInt8, + ) + quantize(str(pre_file), str(qdq_file), qdq_config) + + # Register the QNN EP, then compile the quantized graph to a context binary during session init (no inference + # run). htp_arch targets the chip so the graph finalizes offline on a host without an NPU, and the + # shared-memory allocator is disabled (no device present). ONNX Runtime's htp_arch parser accepts + # 68/69/73/75/81 but not 79 (invalid values only log a warning, leaving the graph untargeted), so v79 + # (Snapdragon 8 Elite) is targeted via its SoC model instead. + ep_name = "QNNExecutionProvider" + ep_options = { + "backend_path": htp_backend, + "htp_graph_finalization_optimization_mode": "3", + "enable_htp_shared_memory_allocator": "0", + } + if name == "79": + ep_options["soc_model"] = "69" # SM8750 (Snapdragon 8 Elite) -> HTP v79 + else: + ep_options["htp_arch"] = name + options = ort.SessionOptions() + options.add_session_config_entry("ep.context_enable", "1") + options.add_session_config_entry("ep.context_file_path", str(ctx_file)) + options.add_session_config_entry("ep.context_embed_mode", "1") + if ep_library: + ort.register_execution_provider_library(ep_name, ep_library) + try: + if ep_library: + devices = [d for d in ort.get_ep_devices() if d.ep_name == ep_name] + if not devices: + raise RuntimeError("QNN EP registered but no QNN devices were found by ONNX Runtime.") + options.add_provider_for_devices(devices, ep_options) + ort.InferenceSession(str(qdq_file), sess_options=options) + else: + ort.InferenceSession( + str(qdq_file), sess_options=options, providers=[ep_name], provider_options=[ep_options] + ) + finally: + if ep_library: + ort.unregister_execution_provider_library(ep_name) + finally: + for f in (pre_file, qdq_file): # remove quantization intermediates; the context binary is self-contained + f.unlink(missing_ok=True) + + if not ctx_file.exists(): + raise RuntimeError(f"QNN context binary was not generated at {ctx_file}. See {prefix} logs for details.") + + if metadata: # ensure Ultralytics metadata is present in the context model (usually preserved by ONNX Runtime) + import onnx + + ctx_model = onnx.load(str(ctx_file)) + existing = {p.key for p in ctx_model.metadata_props} + if missing := {k: v for k, v in metadata.items() if str(k) not in existing}: + for k, v in missing.items(): + entry = ctx_model.metadata_props.add() + entry.key, entry.value = str(k), str(v) + onnx.save(ctx_model, str(ctx_file)) + return str(ctx_file) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/rknn.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/rknn.py new file mode 100644 index 0000000000000000000000000000000000000000..71cf3d45146778eeab501bb5ac1ff25195bf8afc --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/rknn.py @@ -0,0 +1,82 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from pathlib import Path + +from ultralytics.utils import IS_COLAB, LOGGER, YAML + + +def _check_rknn_return(ret, name: str): + """Raise a RuntimeError if an RKNN API call failed.""" + if ret not in {0, None}: + raise RuntimeError(f"RKNN {name} failed with return code {ret}.") + + +def onnx2rknn( + onnx_file: str, + output_dir: Path | str, + name: str = "rk3588", + quantize: int | str | None = None, + dataset: Path | str | None = None, + metadata: dict | None = None, + prefix: str = "", +) -> str: + """Export an ONNX model to RKNN format for Rockchip NPUs with optional INT8 quantization. + + Args: + onnx_file (str): Path to the source ONNX file (already exported, opset <=19). + output_dir (Path | str): Directory to save the exported RKNN model. + name (str): Target platform name (e.g. ``"rk3588"``). + quantize (int | str | None): Precision scheme, 8 for INT8 or 16/None for floating-point builds. + dataset (Path | str | None): Path to the generated RKNN Toolkit calibration image-list file, required when + ``quantize=8``. Users should pass YOLO dataset YAMLs to ``export(data=...)``; ``export_rknn()`` converts + them to this internal image-path list. + metadata (dict | None): Metadata saved as ``metadata.yaml``. + prefix (str): Prefix for log messages. + + Returns: + (str): Path to the exported ``_rknn_model`` directory. + """ + use_int8 = quantize == 8 + if name in {"rv1103", "rv1106", "rv1103b", "rv1106b"} and not use_int8: + raise ValueError( + f"Rockchip target '{name}' requires quantize=8. Use a target that supports floating-point builds " + f"(e.g. rk2118, rk3562, rk3566, rk3568, rk3576, rk3588, rv1126b) or export with quantize=8." + ) + if use_int8: + if not dataset: + raise ValueError("RKNN INT8 export requires a generated calibration image-list file.") + dataset = Path(dataset) + if not dataset.is_file(): + raise ValueError(f"Generated RKNN INT8 calibration image-list file not found: {dataset}") + + from ultralytics.utils.checks import check_requirements + + LOGGER.info(f"\n{prefix} starting export with rknn-toolkit2...") + check_requirements("rknn-toolkit2>=2.3.2") + check_requirements("onnx<1.19.0") # fix AttributeError: module 'onnx' has no attribute 'mapping' + + if IS_COLAB: + # Prevent 'exit' from closing the notebook https://github.com/airockchip/rknn-toolkit2/issues/259 + import builtins + + builtins.exit = lambda: None + + from rknn.api import RKNN + + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + rknn = RKNN(verbose=False) + config = {"mean_values": [[0, 0, 0]], "std_values": [[255, 255, 255]], "target_platform": name} + _check_rknn_return(rknn.config(**config), "config") + _check_rknn_return(rknn.load_onnx(model=onnx_file), "load_onnx") + build_kwargs = {"do_quantization": use_int8} + if use_int8: + build_kwargs["dataset"] = str(dataset) + _check_rknn_return(rknn.build(**build_kwargs), "build") + _check_rknn_return(rknn.export_rknn(str(output_dir / f"{Path(onnx_file).stem}-{name}.rknn")), "export_rknn") + if metadata: + YAML.save(output_dir / "metadata.yaml", metadata) + return str(output_dir) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/tensorflow.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/tensorflow.py new file mode 100644 index 0000000000000000000000000000000000000000..ad2baaa433defc43b4632a0a158838e69a1e44d3 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/tensorflow.py @@ -0,0 +1,287 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from functools import partial +from pathlib import Path + +import numpy as np +import torch + +from ultralytics.nn.modules import Detect, Pose, Pose26 +from ultralytics.utils import LINUX, LOGGER, MACOS +from ultralytics.utils.checks import ( + IS_PYTHON_MINIMUM_3_13, + check_apt_requirements, + check_requirements, + check_version, + is_sudo_available, +) +from ultralytics.utils.downloads import attempt_download_asset +from ultralytics.utils.tal import make_anchors + + +def tf_wrapper(model: torch.nn.Module) -> torch.nn.Module: + """A wrapper for TensorFlow export compatibility (TF-specific handling is now in head modules).""" + for m in model.modules(): + if not isinstance(m, Detect): + continue + import types + + m._get_decode_boxes = types.MethodType(_tf_decode_boxes, m) + if isinstance(m, Pose): + m.kpts_decode = types.MethodType(partial(_tf_kpts_decode, is_pose26=type(m) is Pose26), m) + return model + + +def _tf_decode_boxes(self, x: dict[str, torch.Tensor]) -> torch.Tensor: + """Decode bounding boxes for TensorFlow export.""" + shape = x["feats"][0].shape # BCHW + boxes = x["boxes"] + if self.format != "imx" and (self.dynamic or self.shape != shape): + self.anchors, self.strides = (a.transpose(0, 1) for a in make_anchors(x["feats"], self.stride, 0.5)) + self.shape = shape + grid_h, grid_w = shape[2:4] + grid_size = torch.tensor([grid_w, grid_h, grid_w, grid_h], device=boxes.device).reshape(1, 4, 1) + norm = self.strides / (self.stride[0] * grid_size) + dbox = self.decode_bboxes(self.dfl(boxes) * norm, self.anchors.unsqueeze(0) * norm[:, :2]) + return dbox + + +def _tf_kpts_decode(self, kpts: torch.Tensor, is_pose26: bool = False) -> torch.Tensor: + """Decode keypoints for TensorFlow export.""" + ndim = self.kpt_shape[1] + bs = kpts.shape[0] + # Precompute normalization factor to increase numerical stability + y = kpts.view(bs, *self.kpt_shape, -1) + grid_h, grid_w = self.shape[2:4] + grid_size = torch.tensor([grid_w, grid_h], device=y.device).reshape(1, 2, 1) + norm = self.strides / (self.stride[0] * grid_size) + a = ((y[:, :, :2] + self.anchors) if is_pose26 else (y[:, :, :2] * 2.0 + (self.anchors - 0.5))) * norm + if ndim == 3: + a = torch.cat((a, y[:, :, 2:3].sigmoid()), 2) + return a.view(bs, self.nk, -1) + + +def onnx2saved_model( + onnx_file: str, + output_dir: Path | str, + quantize: int | str | None = None, + images: np.ndarray | None = None, + disable_group_convolution: bool = False, + prefix: str = "", +): + """Convert an ONNX model to TensorFlow SavedModel format using onnx2tf. + + Args: + onnx_file (str): ONNX file path. + output_dir (Path | str): Output directory path for the SavedModel. + quantize (int | str | None): Precision scheme, 8 for INT8. + images (np.ndarray | None, optional): Calibration images for INT8 quantization in BHWC format. + disable_group_convolution (bool, optional): Disable group convolution optimization. Defaults to False. + prefix (str, optional): Logging prefix. Defaults to "". + + Returns: + (keras.Model): Converted Keras model. + + Notes: + - Auto-installs tensorflow, onnx2tf, and all required dependencies if not present. + - Downloads calibration data if INT8 quantization is enabled. + - Removes temporary files and renames quantized models after conversion. + """ + cuda = torch.cuda.is_available() + try: + import tensorflow as tf + except ImportError: + check_requirements("tensorflow>2.19.0" if IS_PYTHON_MINIMUM_3_13 else "tensorflow>=2.0.0,<=2.19.0") + import tensorflow as tf + check_requirements( + f"onnx2tf{'>=2.3.0,<2.3.16' if IS_PYTHON_MINIMUM_3_13 else '>=1.26.3,<1.29.0'}", # pin to avoid h5py build issues on aarch64 + cmds="--no-deps", + ) + check_requirements( + ( + f"tf_keras{'>2.19.0' if IS_PYTHON_MINIMUM_3_13 else '<=2.19.0'}", # required by 'onnx2tf' package + "sng4onnx>=1.0.1", # required by 'onnx2tf' package + "onnx_graphsurgeon>=0.3.26", # required by 'onnx2tf' package + "ai-edge-litert>=1.2.0" + (",<1.4.0" if MACOS else ""), # required by 'onnx2tf' package + "onnx>=1.12.0,<2.0.0", + f"onnx2tf{'>=2.3.0,<2.3.16' if IS_PYTHON_MINIMUM_3_13 else '>=1.26.3,<1.29.0'}", + "onnxslim>=0.1.82", + "onnxruntime-gpu" if cuda else "onnxruntime", + "protobuf>=6.31.1,<7.0.0" + if IS_PYTHON_MINIMUM_3_13 + else "protobuf>=5", # TF>2.19 (Python 3.13) needs protobuf>=6.31.1; cap <7 to match TF gencode and avoid PaddlePaddle segfault + ), + cmds="--extra-index-url https://pypi.ngc.nvidia.com", # onnx_graphsurgeon only on NVIDIA + ) + + LOGGER.info(f"\n{prefix} starting export with tensorflow {tf.__version__}...") + check_version( + tf.__version__, + ">=2.0.0", + name="tensorflow", + verbose=True, + msg="https://github.com/ultralytics/ultralytics/issues/5161", + ) + + output_dir = Path(output_dir) + use_int8 = quantize == 8 + # Pre-download calibration file to fix https://github.com/PINTO0309/onnx2tf/issues/545 + onnx2tf_file = Path("calibration_image_sample_data_20x128x128x3_float32.npy") + if not onnx2tf_file.exists(): + attempt_download_asset(f"{onnx2tf_file}.zip", unzip=True, delete=True) + np_data = None + if use_int8: + tmp_file = output_dir / "tmp_tflite_int8_calibration_images.npy" # int8 calibration images file + if images is not None: + output_dir.mkdir(parents=True, exist_ok=True) + np.save(str(tmp_file), images) # BHWC + np_data = [["images", tmp_file, [[[[0, 0, 0]]]], [[[[255, 255, 255]]]]]] + + # Patch onnx.helper for onnx_graphsurgeon compatibility with ONNX>=1.17 + # The float32_to_bfloat16 function was removed in ONNX 1.17, but onnx_graphsurgeon still uses it + import onnx.helper + + if not hasattr(onnx.helper, "float32_to_bfloat16"): + import struct + + def float32_to_bfloat16(fval): + """Convert float32 to bfloat16 (truncates lower 16 bits of mantissa).""" + ival = struct.unpack("=I", struct.pack("=f", fval))[0] + return ival >> 16 + + onnx.helper.float32_to_bfloat16 = float32_to_bfloat16 + + import importlib + import inspect + import pathlib + + import onnx2tf.ops.TopK as _t + + _path = pathlib.Path(inspect.getfile(_t)) + _text = _path.read_text() + _patched = _text.replace( + "k_tensor = int(k_tensor)", + "k_tensor = int(k_tensor.squeeze()) if hasattr(k_tensor, 'squeeze') else int(k_tensor)", + ) + if _patched != _text: # write only when unpatched; site-packages may be read-only (pre-patched containers) + try: + _path.write_text(_patched) + importlib.reload(_t) + except OSError as e: # read-only install: continue unpatched, only TopK-containing models are affected + LOGGER.warning(f"{prefix} unable to apply onnx2tf TopK patch: {e}") + import onnx2tf # scoped for after ONNX export for reduced conflict during import + + LOGGER.info(f"{prefix} starting TFLite export with onnx2tf {onnx2tf.__version__}...") + keras_model = onnx2tf.convert( + input_onnx_file_path=onnx_file, + output_folder_path=str(output_dir), + not_use_onnxsim=True, + verbosity="error", # note INT8-FP16 activation bug https://github.com/ultralytics/ultralytics/issues/15873 + output_integer_quantized_tflite=use_int8, + custom_input_op_name_np_data_path=np_data, + enable_batchmatmul_unfold=not use_int8, # fix lower no. of detected objects on GPU delegate + output_signaturedefs=True, # fix error with Attention block group convolution + disable_group_convolution=disable_group_convolution, # fix error with group convolution + ) + + # Remove/rename TFLite models + if use_int8: + tmp_file.unlink(missing_ok=True) + for file in output_dir.rglob("*_dynamic_range_quant.tflite"): + file.rename(file.with_name(file.stem.replace("_dynamic_range_quant", "_int8") + file.suffix)) + for file in output_dir.rglob("*_integer_quant_with_int16_act.tflite"): + file.unlink() # delete extra fp16 activation TFLite files + return keras_model + + +def keras2pb(keras_model, output_file: Path | str, prefix: str = "") -> str: + """Convert a Keras model to TensorFlow GraphDef (.pb) format. + + Args: + keras_model (keras.Model): Keras model to convert to frozen graph format. + output_file (Path | str): Output file path (suffix will be changed to .pb). + prefix (str, optional): Logging prefix. Defaults to "". + + Returns: + (str): Path to the exported ``.pb`` file. + + Notes: + Creates a frozen graph by converting variables to constants for inference optimization. + """ + import tensorflow as tf + from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2 + + LOGGER.info(f"\n{prefix} starting export with tensorflow {tf.__version__}...") + m = tf.function(lambda x: keras_model(x)) # full model + m = m.get_concrete_function(tf.TensorSpec(keras_model.inputs[0].shape, keras_model.inputs[0].dtype)) + frozen_func = convert_variables_to_constants_v2(m) + frozen_func.graph.as_graph_def() + output_file = Path(output_file) + tf.io.write_graph( + graph_or_graph_def=frozen_func.graph, logdir=str(output_file.parent), name=output_file.name, as_text=False + ) + return str(output_file) + + +def tflite2edgetpu(tflite_file: str | Path, output_dir: str | Path, prefix: str = "") -> str: + """Convert a TensorFlow Lite model to Edge TPU format using the Edge TPU compiler. + + Args: + tflite_file (str | Path): Path to the input TensorFlow Lite (.tflite) model file. + output_dir (str | Path): Output directory path for the compiled Edge TPU model. + prefix (str, optional): Logging prefix. Defaults to "". + + Returns: + (str): Path to the exported Edge TPU model file. + + Notes: + Auto-installs the Edge TPU compiler if not found. The function compiles the TFLite model + for optimal performance on Google's Edge TPU hardware accelerator. + """ + import shlex + import subprocess + + # Install Edge TPU compiler if not found + check_cmd = "edgetpu_compiler --version" + help_url = "https://coral.ai/docs/edgetpu/compiler/" + assert LINUX, f"export only supported on Linux. See {help_url}" + if subprocess.run(check_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True).returncode != 0: + LOGGER.info(f"\n{prefix} export requires Edge TPU compiler. Attempting install from {help_url}") + sudo = "sudo " if is_sudo_available() else "" + for c in ( + f"{sudo}mkdir -p /etc/apt/keyrings", + f"curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | {sudo}gpg --no-tty --dearmor -o /etc/apt/keyrings/google.gpg", + f'echo "deb [signed-by=/etc/apt/keyrings/google.gpg] https://packages.cloud.google.com/apt coral-edgetpu-stable main" | {sudo}tee /etc/apt/sources.list.d/coral-edgetpu.list', + ): + subprocess.run(c, shell=True, check=True) + check_apt_requirements(["edgetpu-compiler"]) + + ver = subprocess.run(check_cmd, shell=True, capture_output=True, check=True).stdout.decode().rsplit(maxsplit=1)[-1] + LOGGER.info(f"\n{prefix} starting export with Edge TPU compiler {ver}...") + + cmd = [ + "edgetpu_compiler", + "--out_dir", + str(output_dir), + "--show_operations", + "--search_delegate", + "--delegate_search_step", + "30", + "--timeout_sec", + "180", + str(tflite_file), + ] # argv list avoids shell metacharacter issues in output_dir/tflite_file paths + LOGGER.info(f"{prefix} running '{shlex.join(cmd)}'") + subprocess.run(cmd) + return str(Path(output_dir) / f"{Path(tflite_file).stem}_edgetpu.tflite") + + +def gd_outputs(gd): + """Return TensorFlow GraphDef model output node names.""" + name_list, input_list = [], [] + for node in gd.node: # tensorflow.core.framework.node_def_pb2.NodeDef + name_list.append(node.name) + input_list.extend(node.input) + return sorted(f"{x}:0" for x in list(set(name_list) - set(input_list)) if not x.startswith("NoOp")) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/export/torchscript.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/torchscript.py new file mode 100644 index 0000000000000000000000000000000000000000..077b69f075e30f813eaa4ef3e664e41dc09486a7 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/export/torchscript.py @@ -0,0 +1,46 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import json +from pathlib import Path + +import torch + +from ultralytics.utils import LOGGER, TORCH_VERSION + + +def torch2torchscript( + model: torch.nn.Module, + im: torch.Tensor, + output_file: Path | str, + optimize: bool = False, + metadata: dict | None = None, + prefix: str = "", +) -> str: + """Export a PyTorch model to TorchScript format. + + Args: + model (torch.nn.Module): The PyTorch model to export (may be NMS-wrapped). + im (torch.Tensor): Example input tensor for tracing. + output_file (Path | str): Path to save the exported TorchScript model. + optimize (bool): Whether to optimize for mobile deployment. + metadata (dict | None): Optional metadata to embed in the TorchScript archive. + prefix (str): Prefix for log messages. + + Returns: + (str): Path to the exported ``.torchscript`` file. + """ + LOGGER.info(f"\n{prefix} starting export with torch {TORCH_VERSION}...") + + output_file = str(output_file) + ts = torch.jit.trace(model, im, strict=False) + extra_files = {"config.txt": json.dumps(metadata or {})} # torch._C.ExtraFilesMap() + if optimize: # https://pytorch.org/tutorials/recipes/mobile_interpreter.html + LOGGER.info(f"{prefix} optimizing for mobile...") + from torch.utils.mobile_optimizer import optimize_for_mobile + + optimize_for_mobile(ts)._save_for_lite_interpreter(output_file, _extra_files=extra_files) + else: + ts.save(output_file, _extra_files=extra_files) + return output_file diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/files.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/files.py new file mode 100644 index 0000000000000000000000000000000000000000..a58083a29dd87f56ad4d5357399ea09e3fbd64e2 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/files.py @@ -0,0 +1,219 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import contextlib +import glob +import os +import shutil +import tempfile +from contextlib import contextmanager +from datetime import datetime +from pathlib import Path + + +class WorkingDirectory(contextlib.ContextDecorator): + """A context manager and decorator for temporarily changing the working directory. + + This class allows for the temporary change of the working directory using a context manager or decorator. It ensures + that the original working directory is restored after the context or decorated function completes. + + Attributes: + dir (Path | str): The new directory to switch to. + cwd (Path): The original current working directory before the switch. + + Methods: + __enter__: Changes the current directory to the specified directory. + __exit__: Restores the original working directory on context exit. + + Examples: + Using as a context manager: + >>> with WorkingDirectory("/path/to/new/dir"): + ... # Perform operations in the new directory + ... pass + + Using as a decorator: + >>> @WorkingDirectory("/path/to/new/dir") + ... def some_function(): + ... # Perform operations in the new directory + ... pass + """ + + def __init__(self, new_dir: str | Path): + """Initialize the WorkingDirectory context manager with the target directory.""" + self.dir = new_dir # new dir + self.cwd = Path.cwd().resolve() # current dir + + def __enter__(self): + """Change the current working directory to the specified directory upon entering the context.""" + os.chdir(self.dir) + + def __exit__(self, exc_type, exc_val, exc_tb): + """Restore the original working directory when exiting the context.""" + os.chdir(self.cwd) + + +@contextmanager +def spaces_in_path(path: str | Path): + """Context manager to handle paths with spaces in their names. + + If a path contains spaces, it replaces them with underscores, copies the file/directory to the new path, executes + the context code block, then copies the file/directory back to its original location. + + Args: + path (str | Path): The original path that may contain spaces. + + Yields: + (Path | str): Temporary path with any spaces replaced by underscores. + + Examples: + >>> with spaces_in_path("/path/with spaces") as new_path: + ... # Your code here + ... pass + """ + # If path has spaces, replace them with underscores + if " " in str(path): + string = isinstance(path, str) # input type + path = Path(path) + + # Create a temporary directory and construct the new path + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_path = Path(tmp_dir) / path.name.replace(" ", "_") + + # Copy file/directory + if path.is_dir(): + shutil.copytree(path, tmp_path) + elif path.is_file(): + tmp_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(path, tmp_path) + + try: + # Yield the temporary path + yield str(tmp_path) if string else tmp_path + + finally: + # Copy file/directory back + if tmp_path.is_dir(): + shutil.copytree(tmp_path, path, dirs_exist_ok=True) + elif tmp_path.is_file(): + shutil.copy2(tmp_path, path) # Copy back the file + + else: + # If there are no spaces, just yield the original path + yield path + + +def increment_path(path: str | Path, exist_ok: bool = False, sep: str = "-", mkdir: bool = False) -> Path: + """Increment a file or directory path, i.e., runs/exp --> runs/exp{sep}2, runs/exp{sep}3, ... etc. + + If the path exists and `exist_ok` is not True, the path will be incremented by appending a number and `sep` to the + end of the path. If the path is a file, the file extension will be preserved. If the path is a directory, the number + will be appended directly to the end of the path. + + Args: + path (str | Path): Path to increment. + exist_ok (bool, optional): If True, the path will not be incremented and returned as-is. + sep (str, optional): Separator to use between the path and the incrementation number. + mkdir (bool, optional): Create a directory if it does not exist. + + Returns: + (Path): Incremented path. + + Examples: + Increment a directory path: + >>> from pathlib import Path + >>> path = Path("runs/exp") + >>> new_path = increment_path(path) + >>> print(new_path) + runs/exp-2 + + Increment a file path: + >>> path = Path("runs/exp/results.txt") + >>> new_path = increment_path(path) + >>> print(new_path) + runs/exp/results-2.txt + """ + path = Path(path) # os-agnostic + if path.exists() and not exist_ok: + path, suffix = (path.with_suffix(""), path.suffix) if path.is_file() else (path, "") + + # Method 1 + for n in range(2, 9999): + p = f"{path}{sep}{n}{suffix}" # increment path + if not os.path.exists(p): + break + path = Path(p) + + if mkdir: + path.mkdir(parents=True, exist_ok=True) # make directory + + return path + + +def file_age(path: str | Path = __file__) -> int: + """Return days since the last modification of the specified file.""" + dt = datetime.now() - datetime.fromtimestamp(Path(path).stat().st_mtime) # delta + return dt.days # + dt.seconds / 86400 # fractional days + + +def file_date(path: str | Path = __file__) -> str: + """Return the file modification date in 'YYYY-M-D' format.""" + t = datetime.fromtimestamp(Path(path).stat().st_mtime) + return f"{t.year}-{t.month}-{t.day}" + + +def file_size(path: str | Path) -> float: + """Return the size of a file or directory in mebibytes (MiB).""" + if isinstance(path, (str, Path)): + mb = 1 << 20 # bytes to MiB (1024 ** 2) + path = Path(path) + if path.is_file(): + return path.stat().st_size / mb + elif path.is_dir(): + return sum(f.stat().st_size for f in path.glob("**/*") if f.is_file()) / mb + return 0.0 + + +def get_latest_run(search_dir: str = ".") -> str: + """Return the path to the most recent 'last.pt' file in the specified directory for resuming training.""" + last_list = glob.glob(f"{search_dir}/**/last*.pt", recursive=True) + return max(last_list, key=os.path.getctime) if last_list else "" + + +def update_models(model_names: tuple = ("yolo26n.pt",), source_dir: Path = Path("."), update_names: bool = False): + """Update and re-save specified YOLO models in an 'updated_models' subdirectory. + + Args: + model_names (tuple, optional): Model filenames to update. + source_dir (Path, optional): Directory containing models and target subdirectory. + update_names (bool, optional): Update model names from a data YAML. + + Examples: + Update specified YOLO models and save them in 'updated_models' subdirectory: + >>> from ultralytics.utils.files import update_models + >>> model_names = ("yolo26n.pt", "yolo11s.pt") + >>> update_models(model_names, source_dir=Path("/models"), update_names=True) + """ + from ultralytics import YOLO + from ultralytics.nn.autobackend import default_class_names + from ultralytics.utils import LOGGER + + target_dir = source_dir / "updated_models" + target_dir.mkdir(parents=True, exist_ok=True) # Ensure target directory exists + + for model_name in model_names: + model_path = source_dir / model_name + LOGGER.info(f"Loading model from {model_path}") + + # Load model + model = YOLO(model_path) + model.half() + if update_names: # update model names from a dataset YAML + model.model.names = default_class_names("coco8.yaml") + + # Define new save path + save_path = target_dir / model_name + + # Save model using model.save() + LOGGER.info(f"Re-saving {model_name} model to {save_path}") + model.save(save_path) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/git.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/git.py new file mode 100644 index 0000000000000000000000000000000000000000..6c8ad1e5d90511bb59025afac5300cdfb7a21712 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/git.py @@ -0,0 +1,171 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import zlib +from functools import cached_property +from pathlib import Path + + +class GitRepo: + """Represent a local Git repository and expose branch, commit, and remote metadata. + + This class discovers the repository root by searching for a .git entry from the given path upward, resolves the + actual .git directory (including worktrees), and reads Git metadata directly from on-disk files. It does not invoke + the git binary and therefore works in restricted environments. All metadata properties are resolved lazily and + cached; construct a new instance to refresh state. + + Attributes: + root (Path | None): Repository root directory containing the .git entry; None if not in a repository. + gitdir (Path | None): Resolved .git directory path; handles worktrees; None if unresolved. + refdir (Path | None): Directory containing shared refs, objects, and config; None if unresolved. + head (str | None): Raw contents of HEAD; a SHA for detached HEAD or "ref: " for branch heads. + is_repo (bool): Whether the provided path resides inside a Git repository. + branch (str | None): Current branch name when HEAD points to a branch; None for detached HEAD or non-repo. + commit (str | None): Current commit SHA for HEAD; None if not determinable. + message (str | None): Current commit subject from a loose object; None if not determinable. + origin (str | None): URL of the "origin" remote as read from gitdir/config; None if unset or unavailable. + + Examples: + Initialize from the current working directory and read metadata + >>> from pathlib import Path + >>> repo = GitRepo(Path.cwd()) + >>> repo.is_repo + True + >>> repo.branch, repo.commit[:7], repo.origin + ('main', '1a2b3c4', 'https://example.com/owner/repo.git') + + Notes: + - Resolves metadata by reading files: HEAD, packed-refs, config, and objects; no subprocess calls are used. + - Caches properties on first access using cached_property; recreate the object to reflect repository changes. + """ + + def __init__(self, path: Path = Path(__file__).resolve()): + """Initialize a Git repository context by discovering the repository root from a starting path. + + Args: + path (Path, optional): File or directory path used as the starting point to locate the repository root. + """ + self.root = self._find_root(path) + self.gitdir = self._gitdir(self.root) if self.root else None + self.refdir = self._refdir(self.gitdir) + + @staticmethod + def _find_root(p: Path) -> Path | None: + """Return repo root or None.""" + return next((d for d in [p, *list(p.parents)] if (d / ".git").exists()), None) + + @staticmethod + def _gitdir(root: Path) -> Path | None: + """Resolve actual .git directory (handles worktrees).""" + g = root / ".git" + if g.is_dir(): + return g + if g.is_file(): + t = g.read_text(errors="ignore").strip() + if t.startswith("gitdir:"): + return (root / t.split(":", 1)[1].strip()).resolve() + return None + + @staticmethod + def _refdir(gitdir: Path | None) -> Path | None: + """Resolve directory containing refs, objects, and config.""" + p = gitdir / "commondir" if gitdir else None + if s := GitRepo._read(p): + d = Path(s) + return (gitdir / d).resolve() if not d.is_absolute() else d + return gitdir + + @staticmethod + def _read(p: Path | None) -> str | None: + """Read and strip file if exists.""" + return p.read_text(errors="ignore").strip() if p and p.exists() else None + + @cached_property + def head(self) -> str | None: + """HEAD file contents.""" + return self._read(self.gitdir / "HEAD" if self.gitdir else None) + + def _ref_commit(self, ref: str) -> str | None: + """Commit for ref (handles packed-refs).""" + rf = self.refdir / ref + if s := self._read(rf): + return s + pf = self.refdir / "packed-refs" + b = pf.read_bytes().splitlines() if pf.exists() else [] + tgt = ref.encode() + for line in b: + if line[:1] in (b"#", b"^") or b" " not in line: + continue + sha, name = line.split(b" ", 1) + if name.strip() == tgt: + return sha.decode() + return None + + def _commit_subject(self, commit: str) -> str | None: + """Commit subject from loose object or None.""" + obj = self.refdir / "objects" / commit[:2] / commit[2:] + if not obj.exists(): + return None + data = zlib.decompress(obj.read_bytes()) + if b"\0" not in data: + return None + kind, body = data.split(b"\0", 1) + if not kind.startswith(b"commit ") or b"\n\n" not in body: + return None + subject = body.split(b"\n\n", 1)[1].splitlines()[0].decode(errors="replace").strip() + return subject or None + + @property + def is_repo(self) -> bool: + """True if inside a git repo.""" + return self.gitdir is not None + + @cached_property + def branch(self) -> str | None: + """Current branch or None.""" + if not self.is_repo or not self.head or not self.head.startswith("ref: "): + return None + ref = self.head[5:].strip() + return ref[len("refs/heads/") :] if ref.startswith("refs/heads/") else ref + + @cached_property + def commit(self) -> str | None: + """Current commit SHA or None.""" + if not self.is_repo or not self.head: + return None + return self._ref_commit(self.head[5:].strip()) if self.head.startswith("ref: ") else self.head + + @cached_property + def message(self) -> str | None: + """Current commit subject or None.""" + if not self.is_repo or not self.commit: + return None + return self._commit_subject(self.commit) + + @cached_property + def origin(self) -> str | None: + """Origin URL or None.""" + if not self.is_repo: + return None + cfg = self.refdir / "config" + remote, url = None, None + for s in (self._read(cfg) or "").splitlines(): + t = s.strip() + if t.startswith("[") and t.endswith("]"): + remote = t.lower() + elif t.lower().startswith("url =") and remote == '[remote "origin"]': + url = t.split("=", 1)[1].strip() + break + return url + + +if __name__ == "__main__": + import time + + g = GitRepo() + if g.is_repo: + t0 = time.perf_counter() + print(f"repo={g.root}\nbranch={g.branch}\ncommit={g.commit}\nmessage={g.message}\norigin={g.origin}") + dt = (time.perf_counter() - t0) * 1000 + print(f"\n⏱️ Profiling: total {dt:.3f} ms") diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/instance.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/instance.py new file mode 100644 index 0000000000000000000000000000000000000000..b56decd573e7653afe9011eb620d7bba3c35da6c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/instance.py @@ -0,0 +1,498 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +from collections import abc +from itertools import repeat +from numbers import Number + +import numpy as np + +from .ops import ltwh2xywh, ltwh2xyxy, resample_segments, xywh2ltwh, xywh2xyxy, xyxy2ltwh, xyxy2xywh + + +def _ntuple(n): + """Create a function that converts input to n-tuple by repeating singleton values.""" + + def parse(x): + """Parse input to return n-tuple by repeating singleton values n times.""" + return x if isinstance(x, abc.Iterable) else tuple(repeat(x, n)) + + return parse + + +to_2tuple = _ntuple(2) +to_4tuple = _ntuple(4) + +# `xyxy` means left top and right bottom +# `xywh` means center x, center y and width, height(YOLO format) +# `ltwh` means left top and width, height(COCO format) +_formats = ["xyxy", "xywh", "ltwh"] + +__all__ = ("Bboxes", "Instances") # tuple or list + + +class Bboxes: + """A class for handling bounding boxes in multiple formats. + + The class supports various bounding box formats like 'xyxy', 'xywh', and 'ltwh' and provides methods for format + conversion, scaling, and area calculation. Bounding box data should be provided as numpy arrays. + + Attributes: + bboxes (np.ndarray): The bounding boxes stored in a 2D numpy array with shape (N, 4). + format (str): The format of the bounding boxes ('xyxy', 'xywh', or 'ltwh'). + + Methods: + convert: Convert bounding box format from one type to another. + areas: Calculate the area of bounding boxes. + mul: Multiply bounding box coordinates by scale factor(s). + add: Add offset to bounding box coordinates. + concatenate: Concatenate multiple Bboxes objects. + + Examples: + Create bounding boxes in YOLO format + >>> bboxes = Bboxes(np.array([[100, 50, 150, 100]]), format="xywh") + >>> bboxes.convert("xyxy") + >>> print(bboxes.areas()) + [15000] + + Notes: + This class does not handle normalization or denormalization of bounding boxes. + """ + + def __init__(self, bboxes: np.ndarray, format: str = "xyxy") -> None: + """Initialize the Bboxes class with bounding box data in a specified format. + + Args: + bboxes (np.ndarray): Array of bounding boxes with shape (N, 4) or (4,). + format (str): Format of the bounding boxes, one of 'xyxy', 'xywh', or 'ltwh'. + """ + assert format in _formats, f"Invalid bounding box format: {format}, format must be one of {_formats}" + bboxes = bboxes[None, :] if bboxes.ndim == 1 else bboxes + assert bboxes.ndim == 2 + assert bboxes.shape[1] == 4 + self.bboxes = bboxes + self.format = format + + def convert(self, format: str) -> None: + """Convert bounding box format from one type to another. + + Args: + format (str): Target format for conversion, one of 'xyxy', 'xywh', or 'ltwh'. + """ + assert format in _formats, f"Invalid bounding box format: {format}, format must be one of {_formats}" + if self.format == format: + return + elif self.format == "xyxy": + func = xyxy2xywh if format == "xywh" else xyxy2ltwh + elif self.format == "xywh": + func = xywh2xyxy if format == "xyxy" else xywh2ltwh + else: + func = ltwh2xyxy if format == "xyxy" else ltwh2xywh + self.bboxes = func(self.bboxes) + self.format = format + + def areas(self) -> np.ndarray: + """Calculate the area of bounding boxes.""" + return ( + (self.bboxes[:, 2] - self.bboxes[:, 0]) * (self.bboxes[:, 3] - self.bboxes[:, 1]) # format xyxy + if self.format == "xyxy" + else self.bboxes[:, 3] * self.bboxes[:, 2] # format xywh or ltwh + ) + + def mul(self, scale: int | tuple | list) -> None: + """Multiply bounding box coordinates by scale factor(s). + + Args: + scale (int | tuple | list): Scale factor(s) for four coordinates. If int, the same scale is applied to all + coordinates. + """ + if isinstance(scale, Number): + scale = to_4tuple(scale) + assert isinstance(scale, (tuple, list)) + assert len(scale) == 4 + self.bboxes[:, 0] *= scale[0] + self.bboxes[:, 1] *= scale[1] + self.bboxes[:, 2] *= scale[2] + self.bboxes[:, 3] *= scale[3] + + def add(self, offset: int | tuple | list) -> None: + """Add offset to bounding box coordinates. + + Args: + offset (int | tuple | list): Offset(s) for four coordinates. If int, the same offset is applied to all + coordinates. + """ + if isinstance(offset, Number): + offset = to_4tuple(offset) + assert isinstance(offset, (tuple, list)) + assert len(offset) == 4 + self.bboxes[:, 0] += offset[0] + self.bboxes[:, 1] += offset[1] + self.bboxes[:, 2] += offset[2] + self.bboxes[:, 3] += offset[3] + + def __len__(self) -> int: + """Return the number of bounding boxes.""" + return len(self.bboxes) + + @classmethod + def concatenate(cls, boxes_list: list[Bboxes], axis: int = 0) -> Bboxes: + """Concatenate a list of Bboxes objects into a single Bboxes object. + + Args: + boxes_list (list[Bboxes]): A list of Bboxes objects to concatenate. + axis (int, optional): The axis along which to concatenate the bounding boxes. + + Returns: + (Bboxes): A new Bboxes object containing the concatenated bounding boxes. + + Notes: + The input should be a list or tuple of Bboxes objects. + """ + assert isinstance(boxes_list, (list, tuple)) + if not boxes_list: + return cls(np.empty(0)) + assert all(isinstance(box, Bboxes) for box in boxes_list) + + if len(boxes_list) == 1: + return boxes_list[0] + return cls(np.concatenate([b.bboxes for b in boxes_list], axis=axis)) + + def __getitem__(self, index: int | np.ndarray | slice) -> Bboxes: + """Retrieve a specific bounding box or a set of bounding boxes using indexing. + + Args: + index (int | slice | np.ndarray): The index, slice, or boolean array to select the desired bounding boxes. + + Returns: + (Bboxes): A new Bboxes object containing the selected bounding boxes. + + Notes: + When using boolean indexing, make sure to provide a boolean array with the same length as the number of + bounding boxes. + """ + if isinstance(index, int): + return Bboxes(self.bboxes[index].reshape(1, -1)) + b = self.bboxes[index] + assert b.ndim == 2, f"Indexing on Bboxes with {index} failed to return a matrix!" + return Bboxes(b) + + +class Instances: + """Container for bounding boxes, segments, and keypoints of detected objects in an image. + + This class provides a unified interface for handling different types of object annotations including bounding boxes, + segmentation masks, and keypoints. It supports various operations like scaling, normalization, clipping, and format + conversion. + + Attributes: + _bboxes (Bboxes): Internal object for handling bounding box operations. + keypoints (np.ndarray): Keypoints with shape (N, 17, 3) in format (x, y, visible). + normalized (bool): Flag indicating whether the bounding box coordinates are normalized. + segments (np.ndarray): Segments array with shape (N, M, 2) after resampling. + + Methods: + convert_bbox: Convert bounding box format. + scale: Scale coordinates by given factors. + denormalize: Convert normalized coordinates to absolute coordinates. + normalize: Convert absolute coordinates to normalized coordinates. + add_padding: Add padding to coordinates. + flipud: Flip coordinates vertically. + fliplr: Flip coordinates horizontally. + clip: Clip coordinates to stay within image boundaries. + remove_zero_area_boxes: Remove boxes with zero area. + update: Update instance variables. + concatenate: Concatenate multiple Instances objects. + + Examples: + Create instances with bounding boxes and segments + >>> instances = Instances( + ... bboxes=np.array([[10, 10, 30, 30], [20, 20, 40, 40]]), + ... segments=[np.array([[5, 5], [10, 10]]), np.array([[15, 15], [20, 20]])], + ... keypoints=np.array([[[5, 5, 1], [10, 10, 1]], [[15, 15, 1], [20, 20, 1]]]), + ... ) + """ + + def __init__( + self, + bboxes: np.ndarray, + segments: np.ndarray = None, + keypoints: np.ndarray = None, + bbox_format: str = "xywh", + normalized: bool = True, + ) -> None: + """Initialize the Instances object with bounding boxes, segments, and keypoints. + + Args: + bboxes (np.ndarray): Bounding boxes with shape (N, 4). + segments (np.ndarray, optional): Segmentation masks. + keypoints (np.ndarray, optional): Keypoints with shape (N, 17, 3) in format (x, y, visible). + bbox_format (str): Format of bboxes. + normalized (bool): Whether the coordinates are normalized. + """ + self._bboxes = Bboxes(bboxes=bboxes, format=bbox_format) + self.keypoints = keypoints + self.normalized = normalized + self.segments = segments + + def convert_bbox(self, format: str) -> None: + """Convert bounding box format. + + Args: + format (str): Target format for conversion, one of 'xyxy', 'xywh', or 'ltwh'. + """ + self._bboxes.convert(format=format) + + @property + def bbox_areas(self) -> np.ndarray: + """Calculate the area of bounding boxes.""" + return self._bboxes.areas() + + def scale(self, scale_w: float, scale_h: float, bbox_only: bool = False): + """Scale coordinates by given factors. + + Args: + scale_w (float): Scale factor for width. + scale_h (float): Scale factor for height. + bbox_only (bool, optional): Whether to scale only bounding boxes. + """ + self._bboxes.mul(scale=(scale_w, scale_h, scale_w, scale_h)) + if bbox_only: + return + self.segments[..., 0] *= scale_w + self.segments[..., 1] *= scale_h + if self.keypoints is not None: + self.keypoints[..., 0] *= scale_w + self.keypoints[..., 1] *= scale_h + + def denormalize(self, w: int, h: int) -> None: + """Convert normalized coordinates to absolute coordinates. + + Args: + w (int): Image width. + h (int): Image height. + """ + if not self.normalized: + return + self._bboxes.mul(scale=(w, h, w, h)) + self.segments[..., 0] *= w + self.segments[..., 1] *= h + if self.keypoints is not None: + self.keypoints[..., 0] *= w + self.keypoints[..., 1] *= h + self.normalized = False + + def normalize(self, w: int, h: int) -> None: + """Convert absolute coordinates to normalized coordinates. + + Args: + w (int): Image width. + h (int): Image height. + """ + if self.normalized: + return + self._bboxes.mul(scale=(1 / w, 1 / h, 1 / w, 1 / h)) + self.segments[..., 0] /= w + self.segments[..., 1] /= h + if self.keypoints is not None: + self.keypoints[..., 0] /= w + self.keypoints[..., 1] /= h + self.normalized = True + + def add_padding(self, padw: int, padh: int) -> None: + """Add padding to coordinates. + + Args: + padw (int): Padding width. + padh (int): Padding height. + """ + assert not self.normalized, "you should add padding with absolute coordinates." + self._bboxes.add(offset=(padw, padh, padw, padh)) + self.segments[..., 0] += padw + self.segments[..., 1] += padh + if self.keypoints is not None: + self.keypoints[..., 0] += padw + self.keypoints[..., 1] += padh + + def __getitem__(self, index: int | np.ndarray | slice) -> Instances: + """Retrieve a specific instance or a set of instances using indexing. + + Args: + index (int | slice | np.ndarray): The index, slice, or boolean array to select the desired instances. + + Returns: + (Instances): A new Instances object containing the selected boxes, segments, and keypoints if present. + + Notes: + When using boolean indexing, make sure to provide a boolean array with the same length as the number of + instances. + """ + segments = self.segments[index] if len(self.segments) else self.segments + keypoints = self.keypoints[index] if self.keypoints is not None else None + bboxes = self.bboxes[index] + bbox_format = self._bboxes.format + return Instances( + bboxes=bboxes, + segments=segments, + keypoints=keypoints, + bbox_format=bbox_format, + normalized=self.normalized, + ) + + def flipud(self, h: int) -> None: + """Flip coordinates vertically. + + Args: + h (int): Image height. + """ + if self._bboxes.format == "xyxy": + y1 = self.bboxes[:, 1].copy() + y2 = self.bboxes[:, 3].copy() + self.bboxes[:, 1] = h - y2 + self.bboxes[:, 3] = h - y1 + else: + self.bboxes[:, 1] = h - self.bboxes[:, 1] + self.segments[..., 1] = h - self.segments[..., 1] + if self.keypoints is not None: + self.keypoints[..., 1] = h - self.keypoints[..., 1] + + def fliplr(self, w: int) -> None: + """Flip coordinates horizontally. + + Args: + w (int): Image width. + """ + if self._bboxes.format == "xyxy": + x1 = self.bboxes[:, 0].copy() + x2 = self.bboxes[:, 2].copy() + self.bboxes[:, 0] = w - x2 + self.bboxes[:, 2] = w - x1 + else: + self.bboxes[:, 0] = w - self.bboxes[:, 0] + self.segments[..., 0] = w - self.segments[..., 0] + if self.keypoints is not None: + self.keypoints[..., 0] = w - self.keypoints[..., 0] + + def clip(self, w: int, h: int) -> None: + """Clip coordinates to stay within image boundaries. + + Args: + w (int): Image width. + h (int): Image height. + """ + ori_format = self._bboxes.format + self.convert_bbox(format="xyxy") + self.bboxes[:, [0, 2]] = self.bboxes[:, [0, 2]].clip(0, w) + self.bboxes[:, [1, 3]] = self.bboxes[:, [1, 3]].clip(0, h) + if ori_format != "xyxy": + self.convert_bbox(format=ori_format) + self.segments[..., 0] = self.segments[..., 0].clip(0, w) + self.segments[..., 1] = self.segments[..., 1].clip(0, h) + if self.keypoints is not None: + # Set out of bounds visibility to zero + self.keypoints[..., 2][ + (self.keypoints[..., 0] < 0) + | (self.keypoints[..., 0] > w) + | (self.keypoints[..., 1] < 0) + | (self.keypoints[..., 1] > h) + ] = 0.0 + self.keypoints[..., 0] = self.keypoints[..., 0].clip(0, w) + self.keypoints[..., 1] = self.keypoints[..., 1].clip(0, h) + + def remove_zero_area_boxes(self) -> np.ndarray: + """Remove zero-area boxes, i.e. after clipping some boxes may have zero width or height. + + Returns: + (np.ndarray): Boolean array indicating which boxes were kept. + """ + good = self.bbox_areas > 0 + if not all(good): + self._bboxes = self._bboxes[good] + if self.segments is not None and len(self.segments): + self.segments = self.segments[good] + if self.keypoints is not None: + self.keypoints = self.keypoints[good] + return good + + def update(self, bboxes: np.ndarray, segments: np.ndarray = None, keypoints: np.ndarray = None): + """Update instance variables. + + Args: + bboxes (np.ndarray): New bounding boxes. + segments (np.ndarray, optional): New segments. + keypoints (np.ndarray, optional): New keypoints. + """ + self._bboxes = Bboxes(bboxes, format=self._bboxes.format) + if segments is not None: + self.segments = segments + if keypoints is not None: + self.keypoints = keypoints + + def __len__(self) -> int: + """Return the number of instances.""" + return len(self.bboxes) + + @classmethod + def concatenate(cls, instances_list: list[Instances], axis=0) -> Instances: + """Concatenate a list of Instances objects into a single Instances object. + + Args: + instances_list (list[Instances]): A list of Instances objects to concatenate. + axis (int, optional): The axis along which the arrays will be concatenated. + + Returns: + (Instances): A new Instances object containing the concatenated bounding boxes, segments, and keypoints if + present. + + Notes: + The `Instances` objects in the list should have the same properties, such as the format of the bounding + boxes, whether keypoints are present, and if the coordinates are normalized. + """ + assert isinstance(instances_list, (list, tuple)) + if not instances_list: + return cls(np.empty(0)) + assert all(isinstance(instance, Instances) for instance in instances_list) + + if len(instances_list) == 1: + return instances_list[0] + + use_keypoint = instances_list[0].keypoints is not None + bbox_format = instances_list[0]._bboxes.format + normalized = instances_list[0].normalized + + cat_boxes = np.concatenate([ins.bboxes for ins in instances_list], axis=axis) + seg_len = [b.segments.shape[1] for b in instances_list] + if len(frozenset(seg_len)) > 1: # resample segments if there's different length + max_len = max(seg_len) + cat_segments = np.concatenate( + [ + resample_segments(list(b.segments), max_len) + if len(b.segments) + else np.zeros((0, max_len, 2), dtype=np.float32) # re-generating empty segments + for b in instances_list + ], + axis=axis, + ) + else: + cat_segments = np.concatenate([b.segments for b in instances_list], axis=axis) + cat_keypoints = np.concatenate([b.keypoints for b in instances_list], axis=axis) if use_keypoint else None + return cls(cat_boxes, cat_segments, cat_keypoints, bbox_format, normalized) + + @property + def bboxes(self) -> np.ndarray: + """Return bounding boxes.""" + return self._bboxes.bboxes + + def __repr__(self) -> str: + """Return a string representation of the Instances object.""" + # Map private to public names and include direct attributes + attr_map = {"_bboxes": "bboxes"} + parts = [] + for key, value in self.__dict__.items(): + name = attr_map.get(key, key) + if name == "bboxes": + value = self.bboxes # Use the property + if value is not None: + parts.append(f"{name}={value!r}") + return "Instances({})".format("\n".join(parts)) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/logger.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..4607f894fbb5c90facade0df8993df0cf68219cd --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/logger.py @@ -0,0 +1,678 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +import json +import logging +import plistlib +import shutil +import subprocess +import sys +import threading +import time +from datetime import datetime +from pathlib import Path + +from ultralytics.utils import LINUX, LOGGER, MACOS, RANK, WINDOWS + + +class ConsoleLogger: + """Console output capture with batched streaming to file, API, or custom callback. + + Captures stdout/stderr output and streams it with intelligent deduplication and configurable batching. + + Attributes: + destination (str | Path | None): Target destination for streaming (URL, Path, or None for callback-only). + batch_size (int): Number of lines to batch before flushing (default: 1 for immediate). + flush_interval (float): Seconds between automatic flushes (default: 5.0). + on_flush (callable | None): Optional callback function called with batched content on flush. + active (bool): Whether console capture is currently active. + + Examples: + File logging (immediate): + >>> logger = ConsoleLogger("training.log") + >>> logger.start_capture() + >>> print("This will be logged") + >>> logger.stop_capture() + + API streaming with batching: + >>> logger = ConsoleLogger("https://api.example.com/logs", batch_size=10) + >>> logger.start_capture() + + Custom callback with batching: + >>> def my_handler(content, line_count, chunk_id): + ... print(f"Received {line_count} lines") + >>> logger = ConsoleLogger(on_flush=my_handler, batch_size=5) + >>> logger.start_capture() + """ + + def __init__(self, destination=None, batch_size=1, flush_interval=5.0, on_flush=None): + """Initialize console logger with optional batching. + + Args: + destination (str | Path | None): API endpoint URL (http/https), local file path, or None. + batch_size (int): Lines to accumulate before flush (1 = immediate, higher = batched). + flush_interval (float): Max seconds between flushes when batching. + on_flush (callable | None): Callback(content: str, line_count: int, chunk_id: int) for custom handling. + """ + self.destination = destination + self.is_api = isinstance(destination, str) and destination.startswith(("http://", "https://")) + if destination is not None and not self.is_api: + self.destination = Path(destination) + + # Batching configuration + self.batch_size = max(1, batch_size) + self.flush_interval = flush_interval + self.on_flush = on_flush + + # Console capture state + self.original_stdout = sys.stdout + self.original_stderr = sys.stderr + self.active = False + self._log_handler = None # Track handler for cleanup + + # Buffer for batching + self.buffer = [] + self.buffer_lock = threading.Lock() + self.flush_thread = None + self.chunk_id = 0 + + # Deduplication state + self.last_line = "" + self.last_time = 0.0 + self.last_progress_line = "" # Track progress sequence key for deduplication + self.last_was_progress = False # Track if last line was a progress bar + + def start_capture(self): + """Start capturing console output and redirect stdout/stderr. + + Notes: + In DDP training, only activates on rank 0/-1 to prevent duplicate logging. + """ + if self.active or RANK not in {-1, 0}: + return + + self.active = True + sys.stdout = self._ConsoleCapture(self.original_stdout, self._queue_log) + sys.stderr = self._ConsoleCapture(self.original_stderr, self._queue_log) + + # Hook Ultralytics logger + try: + self._log_handler = self._LogHandler(self._queue_log) + logging.getLogger("ultralytics").addHandler(self._log_handler) + except Exception: + pass + + # Start background flush thread for batched mode + if self.batch_size > 1: + self.flush_thread = threading.Thread(target=self._flush_worker, daemon=True) + self.flush_thread.start() + + def stop_capture(self): + """Stop capturing console output and flush remaining buffer.""" + if not self.active: + return + + self.active = False + sys.stdout = self.original_stdout + sys.stderr = self.original_stderr + + # Remove logging handler to prevent memory leak + if self._log_handler: + try: + logging.getLogger("ultralytics").removeHandler(self._log_handler) + except Exception: + pass + self._log_handler = None + + # Final flush + self._flush_buffer() + + def _queue_log(self, text): + """Queue console text with deduplication and timestamp processing.""" + if not self.active: + return + + current_time = time.time() + + # Handle carriage returns and strip ANSI clear-line codes (TQDM writes "\r\033[K" interactively) + if "\r" in text: + text = text.split("\r")[-1] + text = text.replace("\x1b[K", "") + + lines = text.split("\n") + if lines and lines[-1] == "": + lines.pop() + + for line in lines: + line = line.rstrip() + + # Skip lines with only thin progress bars (partial progress) + if "─" in line: # Has thin lines but no thick lines + continue + + # Only show 100% completion lines for progress bars + if " ━━" in line: + is_complete = "100%" in line + + # Skip ALL non-complete progress lines + if not is_complete: + continue + + # Extract sequence key to deduplicate multiple 100% lines for same sequence + parts = line.split() + seq_key = "" + if parts: + # Check for epoch pattern (X/Y at start) + if "/" in parts[0] and parts[0].replace("/", "").isdigit(): + seq_key = parts[0] # e.g., "1/3" + elif parts[0] == "Class" and len(parts) > 1: + seq_key = f"{parts[0]}_{parts[1]}" # e.g., "Class_train:" or "Class_val:" + elif parts[0] in ("train:", "val:"): + seq_key = parts[0] # Phase identifier + + # Skip if we already showed 100% for this sequence + if seq_key and self.last_progress_line == f"{seq_key}:done": + continue + + # Mark this sequence as done + if seq_key: + self.last_progress_line = f"{seq_key}:done" + + self.last_was_progress = True + else: + # Skip empty line after progress bar + if not line and self.last_was_progress: + self.last_was_progress = False + continue + self.last_was_progress = False + + # General deduplication + if line == self.last_line and current_time - self.last_time < 0.1: + continue + + self.last_line = line + self.last_time = current_time + + # Add timestamp if needed + if not line.startswith("[20"): + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + line = f"[{timestamp}] {line}" + + # Add to buffer and check if flush needed + should_flush = False + with self.buffer_lock: + self.buffer.append(line) + if len(self.buffer) >= self.batch_size: + should_flush = True + + # Flush outside lock to avoid deadlock + if should_flush: + self._flush_buffer() + + def _flush_worker(self): + """Background worker that flushes buffer periodically.""" + while self.active: + time.sleep(self.flush_interval) + if self.active: + self._flush_buffer() + + def _flush_buffer(self): + """Flush buffered lines to destination and/or callback.""" + with self.buffer_lock: + if not self.buffer: + return + lines = self.buffer.copy() + self.buffer.clear() + self.chunk_id += 1 + chunk_id = self.chunk_id # Capture under lock to avoid race + + content = "\n".join(lines) + line_count = len(lines) + + # Call custom callback if provided + if self.on_flush: + try: + self.on_flush(content, line_count, chunk_id) + except Exception: + pass # Silently ignore callback errors to avoid flooding stderr + + # Write to destination (file or API) + if self.destination is not None: + self._write_destination(content) + + def _write_destination(self, content): + """Write content to file or API destination.""" + try: + if self.is_api: + import requests + + payload = {"timestamp": datetime.now().isoformat(), "message": content} + requests.post(str(self.destination), json=payload, timeout=5) + else: + self.destination.parent.mkdir(parents=True, exist_ok=True) + with self.destination.open("a", encoding="utf-8") as f: + f.write(content + "\n") + except Exception as e: + print(f"Console logger write error: {e}", file=self.original_stderr) + + class _ConsoleCapture: + """Lightweight stdout/stderr capture.""" + + __slots__ = ("callback", "original") + + def __init__(self, original, callback): + """Initialize a stream wrapper that redirects writes to a callback while preserving the original.""" + self.original = original + self.callback = callback + + def write(self, text): + """Write text to the original stream and forward it to the capture callback.""" + self.original.write(text) + self.callback(text) + + def flush(self): + """Flush the wrapped stream to propagate buffered output promptly during console capture.""" + self.original.flush() + + def isatty(self): + """Delegate isatty check to the original stream.""" + return self.original.isatty() + + class _LogHandler(logging.Handler): + """Lightweight logging handler.""" + + __slots__ = ("callback",) + + def __init__(self, callback): + """Initialize a lightweight logging.Handler that forwards log records to the provided callback.""" + super().__init__() + self.callback = callback + + def emit(self, record): + """Format and forward LogRecord messages to the capture callback for unified log streaming.""" + self.callback(self.format(record) + "\n") + + +class _DriveInfo: + """Resolve mounted storage paths backed by local drives. + + This helper keeps platform-specific drive discovery isolated from SystemLogger metric collection. It uses fast + psutil mount discovery first and falls back to native OS commands only when multiple visible mounts need + disambiguation. + + Examples: + >>> logger = SystemLogger(all_drives=True) + >>> logger.mounts + ['/'] + """ + + @staticmethod + def mounts(psutil, all_drives=False): + """Get mounted paths to monitor.""" + partitions = [p for p in psutil.disk_partitions(all=False) if p.mountpoint] + if not all_drives: + return [_DriveInfo._current_mount(partitions)] + + mounts = [ + p.mountpoint for p in partitions if Path(p.mountpoint).is_dir() and "dontbrowse" not in p.opts.split(",") + ] + if len(mounts) <= 1: + return _DriveInfo._sort(mounts) or [_DriveInfo._current_mount(partitions)] + + for getter in ( + _DriveInfo._macos_mounts if MACOS else None, + _DriveInfo._linux_mounts if LINUX else None, + _DriveInfo._windows_mounts if WINDOWS else None, + ): + if getter: + try: + if platform_mounts := getter(partitions): + return _DriveInfo._sort(platform_mounts) + except (json.JSONDecodeError, OSError, plistlib.InvalidFileException, subprocess.SubprocessError): + pass + return _DriveInfo._sort(mounts) + + @staticmethod + def _sort(mounts): + """Sort mounted paths with root first, excluding boot/firmware partitions like /boot and /boot/efi.""" + mounts = {m for m in mounts if not (m + "/").startswith(("/boot/", "/efi/"))} + return sorted(mounts, key=lambda mount: (mount != "/", mount)) + + @staticmethod + def _current_mount(partitions): + """Get the mounted filesystem backing the current working directory.""" + try: + cwd = Path.cwd().resolve() + except OSError: + return "C:\\" if WINDOWS else "/" + matches = [] + for partition in partitions: + try: + mount = Path(partition.mountpoint).resolve() + except OSError: + continue + if cwd == mount or cwd.is_relative_to(mount): + matches.append(partition.mountpoint) + return max(matches, key=len, default=Path.cwd().anchor or "/") + + @staticmethod + def _macos_mounts(partitions): + """Get user-visible macOS mounts backed by physical disks.""" + disk_info = plistlib.loads(subprocess.check_output(["diskutil", "list", "-plist", "physical"], timeout=5)) + physical_devices = set(disk_info.get("WholeDisks", [])) + for disk in disk_info.get("AllDisksAndPartitions", []): + physical_devices.add(disk.get("DeviceIdentifier", "")) + physical_devices.update(p.get("DeviceIdentifier", "") for p in disk.get("Partitions", [])) + + mounts, volume_groups = [], set() + for partition in partitions: + if partition.mountpoint != "/" and "dontbrowse" in partition.opts.split(","): + continue + info = plistlib.loads( + subprocess.check_output( + ["diskutil", "info", "-plist", partition.mountpoint], + stderr=subprocess.DEVNULL, + timeout=5, + ) + ) + devices = {info.get("DeviceIdentifier", "")} + devices.update(s.get("APFSPhysicalStore", "") for s in info.get("APFSPhysicalStores", [])) + if not devices & physical_devices: + continue + group = info.get("APFSVolumeGroupID") or info.get("APFSContainerReference") or partition.mountpoint + if group in volume_groups: + continue + volume_groups.add(group) + mounts.append(partition.mountpoint) + return mounts + + @staticmethod + def _linux_mounts(_partitions): + """Get Linux mounts backed by physical block devices.""" + block_info = json.loads( + subprocess.check_output( + ["lsblk", "--json", "--output", "NAME,TYPE,MOUNTPOINT,MOUNTPOINTS"], text=True, timeout=5 + ) + ) + mounts = [] + + def visit(block, physical=False): + physical = physical or block.get("type") == "disk" + if physical: + values = block.get("mountpoints") or [block.get("mountpoint")] + if isinstance(values, str): + values = [values] + mounts.extend(m for m in values if isinstance(m, str) and m.startswith("/") and Path(m).is_dir()) + for child in block.get("children", []): + visit(child, physical) + + for block in block_info.get("blockdevices", []): + visit(block) + return mounts + + @staticmethod + def _windows_mounts(_partitions): + """Get Windows fixed local drive mounts.""" + output = subprocess.check_output( + [ + "powershell", + "-NoProfile", + "-Command", + "Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' | Select-Object -ExpandProperty DeviceID", + ], + text=True, + timeout=5, + ) + return [f"{drive}\\" for drive in (line.strip() for line in output.splitlines()) if drive] + + +class SystemLogger: + """Log dynamic system metrics for training monitoring. + + Captures real-time system metrics including CPU, RAM, disk I/O, network I/O, and NVIDIA GPU statistics for training + performance monitoring and analysis. + + Attributes: + pynvml: NVIDIA pynvml module instance if successfully imported, None otherwise. + nvidia_initialized (bool): Whether NVIDIA GPU monitoring is available and initialized. + net_start: Initial network I/O counters for calculating cumulative usage. + disk_start: Initial disk I/O counters for calculating cumulative usage. + + Examples: + Basic usage (single drive): + >>> logger = SystemLogger() + >>> metrics = logger.get_metrics() + >>> print(f"CPU: {metrics['cpu']}%, RAM: {metrics['ram']}%") + >>> for disk in metrics["disk"]: + ... print(f"{disk['mount']}: {disk['used_gb']}/{disk['total_gb']} GB") + + Monitor all drives: + >>> logger = SystemLogger(all_drives=True) + >>> metrics = logger.get_metrics() + >>> for disk in metrics["disk"]: + ... print(f"{disk['mount']}: {disk['used_gb']}/{disk['total_gb']} GB") + + Training loop integration: + >>> system_logger = SystemLogger() + >>> for epoch in range(epochs): + ... # Training code here + ... metrics = system_logger.get_metrics() + ... # Log to database/file + """ + + def __init__(self, all_drives=False): + """Initialize the system logger. + + Args: + all_drives (bool): If True, monitor all mounted drives. If False, monitor the current drive. + """ + import psutil # scoped as slow import + + self.pynvml = None + self.nvidia_initialized = self._init_nvidia() + self.net_start = psutil.net_io_counters() + self.disk_start = psutil.disk_io_counters() + self.mounts = _DriveInfo.mounts(psutil, all_drives) + + # For rate calculation + self._prev_net = self.net_start + self._prev_disk = self.disk_start + self._prev_time = time.time() + + def _init_nvidia(self): + """Initialize NVIDIA GPU monitoring with pynvml.""" + if MACOS: + return False + + try: + import pynvml # scoped as slow import + + self.pynvml = pynvml + pynvml.nvmlInit() + return True + except Exception as e: + import torch + + if torch.cuda.is_available(): + LOGGER.warning(f"SystemLogger NVML init failed: {e}") + return False + + def get_metrics(self, rates=False): + """Get current system metrics including CPU, RAM, disk, network, and GPU usage. + + Collects comprehensive system metrics including CPU usage, RAM usage, disk usage, disk I/O statistics, network + I/O statistics, and GPU metrics (if available). + + Example output (rates=False, default): + ```python + { + "cpu": 45.2, + "ram": 78.9, + "disk": [{"mount": "/", "used_gb": 256.8, "total_gb": 512.0}], + "disk_io": {"read_mb": 156.7, "write_mb": 89.3}, + "network": {"recv_mb": 157.2, "sent_mb": 89.1}, + "gpus": { + "0": {"usage": 95.6, "memory": 85.4, "temp": 72, "power": 285}, + "1": {"usage": 94.1, "memory": 82.7, "temp": 70, "power": 278}, + }, + } + ``` + + Example output (rates=True): + ```python + { + "cpu": 45.2, + "ram": 78.9, + "disk": [{"mount": "/", "used_gb": 256.8, "total_gb": 512.0}], + "disk_io": {"read_mbs": 12.5, "write_mbs": 8.3}, + "network": {"recv_mbs": 5.2, "sent_mbs": 1.1}, + "gpus": { + "0": {"usage": 95.6, "memory": 85.4, "temp": 72, "power": 285}, + }, + } + ``` + + Args: + rates (bool): If True, return disk/network as MB/s rates instead of cumulative MB. + + Returns: + (dict): Metrics dictionary with cpu, ram, disk, network, and gpus keys. + + Examples: + >>> logger = SystemLogger() + >>> logger.get_metrics()["cpu"] # CPU percentage + >>> logger.get_metrics(rates=True)["network"]["recv_mbs"] # MB/s download rate + """ + import psutil # scoped as slow import + + net = psutil.net_io_counters() + disk_io = psutil.disk_io_counters() + memory = psutil.virtual_memory() + now = time.time() + + # Calculate elapsed time since last call + elapsed = max(0.1, now - self._prev_time) # Avoid division by zero + + if rates: + disk_io_metrics = { + "read_mbs": round(max(0, (disk_io.read_bytes - self._prev_disk.read_bytes) / 1e6 / elapsed), 3), + "write_mbs": round(max(0, (disk_io.write_bytes - self._prev_disk.write_bytes) / 1e6 / elapsed), 3), + } + else: + disk_io_metrics = { + "read_mb": round((disk_io.read_bytes - self.disk_start.read_bytes) / 1e6, 3), + "write_mb": round((disk_io.write_bytes - self.disk_start.write_bytes) / 1e6, 3), + } + + disks = [] + for mounts in (self.mounts, ["C:\\" if WINDOWS else "/"]): + for mount in mounts: + try: + usage = shutil.disk_usage(mount) + disks.append( + { + "mount": mount, + "used_gb": round(usage.used / 1e9, 3), + "total_gb": round(usage.total / 1e9, 3), + } + ) + except (PermissionError, OSError): + continue # Skip inaccessible drives + if disks: + break + + metrics = { + "cpu": round(psutil.cpu_percent(), 3), + "ram": round(memory.percent, 3), + "disk": disks, + "disk_io": disk_io_metrics, + "gpus": {}, + } + + if rates: + metrics["network"] = { + "recv_mbs": round(max(0, (net.bytes_recv - self._prev_net.bytes_recv) / 1e6 / elapsed), 3), + "sent_mbs": round(max(0, (net.bytes_sent - self._prev_net.bytes_sent) / 1e6 / elapsed), 3), + } + else: + metrics["network"] = { + "recv_mb": round((net.bytes_recv - self.net_start.bytes_recv) / 1e6, 3), + "sent_mb": round((net.bytes_sent - self.net_start.bytes_sent) / 1e6, 3), + } + + # Always update previous values for accurate rate calculation on next call + self._prev_net = net + self._prev_disk = disk_io + self._prev_time = now + + # Add GPU metrics (NVIDIA only) + if self.nvidia_initialized: + metrics["gpus"].update(self._get_nvidia_metrics()) + + return metrics + + def _get_nvidia_metrics(self): + """Get NVIDIA GPU metrics including utilization, memory, temperature, and power.""" + gpus = {} + if not self.nvidia_initialized or not self.pynvml: + return gpus + try: + device_count = self.pynvml.nvmlDeviceGetCount() + for i in range(device_count): + handle = self.pynvml.nvmlDeviceGetHandleByIndex(i) + util = self.pynvml.nvmlDeviceGetUtilizationRates(handle) + memory = self.pynvml.nvmlDeviceGetMemoryInfo(handle) + temp = self.pynvml.nvmlDeviceGetTemperature(handle, self.pynvml.NVML_TEMPERATURE_GPU) + power = self.pynvml.nvmlDeviceGetPowerUsage(handle) // 1000 + + gpus[str(i)] = { + "usage": round(util.gpu, 3), + "memory": round((memory.used / memory.total) * 100, 3), + "temp": temp, + "power": power, + } + except Exception: + pass + return gpus + + +if __name__ == "__main__": + print("SystemLogger Real-time Metrics Monitor") + print("Press Ctrl+C to stop\n") + + logger = SystemLogger(all_drives=True) + + try: + while True: + metrics = logger.get_metrics() + + # Clear screen (works on most terminals) + print("\033[H\033[J", end="", flush=True) + + # Display system metrics + print(f"CPU: {metrics['cpu']:5.1f}%") + print(f"RAM: {metrics['ram']:5.1f}%") + print(f"Net Recv: {metrics['network']['recv_mb']:9.1f} MB") + print(f"Net Sent: {metrics['network']['sent_mb']:9.1f} MB") + + # Display disk metrics + print("\nDisk Metrics:") + for disk in metrics["disk"]: + print(f" {disk['mount']}: {disk['used_gb']:.1f}/{disk['total_gb']:.1f} GB") + + # Display GPU metrics if available + if metrics["gpus"]: + print("\nGPU Metrics:") + for gpu_id, gpu_data in metrics["gpus"].items(): + print( + f" GPU {gpu_id}: {gpu_data['usage']:3}% | " + f"Mem: {gpu_data['memory']:5.1f}% | " + f"Temp: {gpu_data['temp']:2}°C | " + f"Power: {gpu_data['power']:3}W" + ) + else: + print("\nGPU: No NVIDIA GPUs detected") + + time.sleep(1) + + except KeyboardInterrupt: + print("\n\nStopped monitoring.") diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/loss.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/loss.py new file mode 100644 index 0000000000000000000000000000000000000000..730f48e1cea16307e1d25e554892a958015e4252 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/loss.py @@ -0,0 +1,1393 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import math +from pathlib import Path +from typing import Any + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from ultralytics.utils.metrics import CITYSCAPES_WEIGHT, OKS_SIGMA, RLE_WEIGHT +from ultralytics.utils.ops import crop_mask, xywh2xyxy, xyxy2xywh +from ultralytics.utils.tal import RotatedTaskAlignedAssigner, TaskAlignedAssigner, dist2bbox, dist2rbox, make_anchors +from ultralytics.utils.torch_utils import autocast + +from .metrics import bbox_iou, probiou +from .tal import bbox2dist, rbox2dist + + +class VarifocalLoss(nn.Module): + """Varifocal loss by Zhang et al. + + Implements the Varifocal Loss function for addressing class imbalance in object detection by focusing on + hard-to-classify examples and balancing positive/negative samples. + + Attributes: + gamma (float): The focusing parameter that controls how much the loss focuses on hard-to-classify examples. + alpha (float): The balancing factor used to address class imbalance. + + References: + https://arxiv.org/abs/2008.13367 + """ + + def __init__(self, gamma: float = 2.0, alpha: float = 0.75): + """Initialize the VarifocalLoss class with focusing and balancing parameters.""" + super().__init__() + self.gamma = gamma + self.alpha = alpha + + def forward(self, pred_score: torch.Tensor, gt_score: torch.Tensor, label: torch.Tensor) -> torch.Tensor: + """Compute varifocal loss between predictions and ground truth.""" + weight = self.alpha * pred_score.sigmoid().pow(self.gamma) * (1 - label) + gt_score * label + with autocast(enabled=False): + loss = ( + (F.binary_cross_entropy_with_logits(pred_score.float(), gt_score.float(), reduction="none") * weight) + .mean(1) + .sum() + ) + return loss + + +class FocalLoss(nn.Module): + """Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5). + + Implements the Focal Loss function for addressing class imbalance by down-weighting easy examples and focusing on + hard negatives during training. + + Attributes: + gamma (float): The focusing parameter that controls how much the loss focuses on hard-to-classify examples. + alpha (torch.Tensor): The balancing factor used to address class imbalance. + """ + + def __init__(self, gamma: float = 1.5, alpha: float = 0.25): + """Initialize FocalLoss class with focusing and balancing parameters.""" + super().__init__() + self.gamma = gamma + self.alpha = torch.tensor(alpha) + + def forward(self, pred: torch.Tensor, label: torch.Tensor) -> torch.Tensor: + """Calculate focal loss with modulating factors for class imbalance.""" + loss = F.binary_cross_entropy_with_logits(pred, label, reduction="none") + # p_t = torch.exp(-loss) + # loss *= self.alpha * (1.000001 - p_t) ** self.gamma # non-zero power for gradient stability + + # TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py + pred_prob = pred.sigmoid() # prob from logits + p_t = label * pred_prob + (1 - label) * (1 - pred_prob) + modulating_factor = (1.0 - p_t) ** self.gamma + loss *= modulating_factor + if (self.alpha > 0).any(): + self.alpha = self.alpha.to(device=pred.device, dtype=pred.dtype) + alpha_factor = label * self.alpha + (1 - label) * (1 - self.alpha) + loss *= alpha_factor + return loss.mean(1).sum() + + +class DFLoss(nn.Module): + """Criterion class for computing Distribution Focal Loss (DFL).""" + + def __init__(self, reg_max: int = 16) -> None: + """Initialize the DFL module with regularization maximum.""" + super().__init__() + self.reg_max = reg_max + + def __call__(self, pred_dist: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + """Return sum of left and right DFL losses from https://arxiv.org/abs/2006.04388.""" + target = target.clamp_(0, self.reg_max - 1 - 0.01) + tl = target.long() # target left + tr = tl + 1 # target right + wl = tr - target # weight left + wr = 1 - wl # weight right + return ( + F.cross_entropy(pred_dist, tl.view(-1), reduction="none").view(tl.shape) * wl + + F.cross_entropy(pred_dist, tr.view(-1), reduction="none").view(tl.shape) * wr + ).mean(-1, keepdim=True) + + +class BboxLoss(nn.Module): + """Criterion class for computing training losses for bounding boxes.""" + + def __init__(self, reg_max: int = 16): + """Initialize the BboxLoss module with regularization maximum and DFL settings.""" + super().__init__() + self.dfl_loss = DFLoss(reg_max) if reg_max > 1 else None + + def forward( + self, + pred_dist: torch.Tensor, + pred_bboxes: torch.Tensor, + anchor_points: torch.Tensor, + target_bboxes: torch.Tensor, + target_scores: torch.Tensor, + target_scores_sum: torch.Tensor, + fg_mask: torch.Tensor, + imgsz: torch.Tensor, + stride: torch.Tensor, + ) -> tuple[torch.Tensor, torch.Tensor]: + """Compute IoU and DFL losses for bounding boxes.""" + weight = target_scores.sum(-1)[fg_mask].unsqueeze(-1) + iou = bbox_iou(pred_bboxes[fg_mask], target_bboxes[fg_mask], xywh=False, CIoU=True) + loss_iou = ((1.0 - iou) * weight).sum() / target_scores_sum + + # DFL loss + if self.dfl_loss: + target_ltrb = bbox2dist(anchor_points, target_bboxes, self.dfl_loss.reg_max - 1) + loss_dfl = self.dfl_loss(pred_dist[fg_mask].view(-1, self.dfl_loss.reg_max), target_ltrb[fg_mask]) * weight + loss_dfl = loss_dfl.sum() / target_scores_sum + else: + target_ltrb = bbox2dist(anchor_points, target_bboxes) + # normalize ltrb by image size + target_ltrb = target_ltrb * stride + target_ltrb[..., 0::2] /= imgsz[1] + target_ltrb[..., 1::2] /= imgsz[0] + pred_dist = pred_dist * stride + pred_dist[..., 0::2] /= imgsz[1] + pred_dist[..., 1::2] /= imgsz[0] + loss_dfl = ( + F.l1_loss(pred_dist[fg_mask], target_ltrb[fg_mask], reduction="none").mean(-1, keepdim=True) * weight + ) + loss_dfl = loss_dfl.sum() / target_scores_sum + + return loss_iou, loss_dfl + + +class RLELoss(nn.Module): + """Residual Log-Likelihood Estimation Loss. + + Attributes: + size_average (bool): Option to average the loss by the batch_size. + use_target_weight (bool): Option to use weighted loss. + residual (bool): Option to add L1 loss and let the flow learn the residual error distribution. + + References: + https://arxiv.org/abs/2107.11291 + https://github.com/open-mmlab/mmpose/blob/main/mmpose/models/losses/regression_loss.py + """ + + def __init__(self, use_target_weight: bool = True, size_average: bool = True, residual: bool = True): + """Initialize RLELoss with target weight and residual options. + + Args: + use_target_weight (bool): Whether to use target weights for loss calculation. + size_average (bool): Whether to average the loss over elements. + residual (bool): Whether to include residual log-likelihood term. + """ + super().__init__() + self.size_average = size_average + self.use_target_weight = use_target_weight + self.residual = residual + + def forward( + self, sigma: torch.Tensor, log_phi: torch.Tensor, error: torch.Tensor, target_weight: torch.Tensor = None + ) -> torch.Tensor: + """ + Args: + sigma (torch.Tensor): Output sigma, shape (N, D). + log_phi (torch.Tensor): Output log_phi, shape (N). + error (torch.Tensor): Error, shape (N, D). + target_weight (torch.Tensor): Weights across different joint types, shape (N). + """ + log_sigma = torch.log(sigma) + loss = log_sigma - log_phi.unsqueeze(1) + + if self.residual: + loss += torch.log(sigma * 2) + torch.abs(error) + + if self.use_target_weight: + assert target_weight is not None, "'target_weight' should not be None when 'use_target_weight' is True." + if target_weight.dim() == 1: + target_weight = target_weight.unsqueeze(1) + loss *= target_weight + + if self.size_average: + loss /= len(loss) + + return loss.sum() + + +class RotatedBboxLoss(BboxLoss): + """Criterion class for computing training losses for rotated bounding boxes.""" + + def __init__(self, reg_max: int): + """Initialize the RotatedBboxLoss module with regularization maximum and DFL settings.""" + super().__init__(reg_max) + + def forward( + self, + pred_dist: torch.Tensor, + pred_bboxes: torch.Tensor, + anchor_points: torch.Tensor, + target_bboxes: torch.Tensor, + target_scores: torch.Tensor, + target_scores_sum: torch.Tensor, + fg_mask: torch.Tensor, + imgsz: torch.Tensor, + stride: torch.Tensor, + ) -> tuple[torch.Tensor, torch.Tensor]: + """Compute IoU and DFL losses for rotated bounding boxes.""" + weight = target_scores.sum(-1)[fg_mask].unsqueeze(-1) + iou = probiou(pred_bboxes[fg_mask], target_bboxes[fg_mask]) + loss_iou = ((1.0 - iou) * weight).sum() / target_scores_sum + + # DFL loss + if self.dfl_loss: + target_ltrb = rbox2dist( + target_bboxes[..., :4], anchor_points, target_bboxes[..., 4:5], reg_max=self.dfl_loss.reg_max - 1 + ) + loss_dfl = self.dfl_loss(pred_dist[fg_mask].view(-1, self.dfl_loss.reg_max), target_ltrb[fg_mask]) * weight + loss_dfl = loss_dfl.sum() / target_scores_sum + else: + target_ltrb = rbox2dist(target_bboxes[..., :4], anchor_points, target_bboxes[..., 4:5]) + target_ltrb = target_ltrb * stride + target_ltrb[..., 0::2] /= imgsz[1] + target_ltrb[..., 1::2] /= imgsz[0] + pred_dist = pred_dist * stride + pred_dist[..., 0::2] /= imgsz[1] + pred_dist[..., 1::2] /= imgsz[0] + loss_dfl = ( + F.l1_loss(pred_dist[fg_mask], target_ltrb[fg_mask], reduction="none").mean(-1, keepdim=True) * weight + ) + loss_dfl = loss_dfl.sum() / target_scores_sum + + return loss_iou, loss_dfl + + +class MultiChannelDiceLoss(nn.Module): + """Criterion class for computing multi-channel Dice losses.""" + + def __init__(self, smooth: float = 1e-6, reduction: str = "mean"): + """Initialize MultiChannelDiceLoss with smoothing and reduction options. + + Args: + smooth (float): Smoothing factor to avoid division by zero. + reduction (str): Reduction method ('mean', 'sum', or 'none'). + """ + super().__init__() + self.smooth = smooth + self.reduction = reduction + + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + """Calculate multi-channel Dice loss between predictions and targets.""" + assert pred.size() == target.size(), "the size of predict and target must be equal." + + pred = pred.sigmoid() + intersection = (pred * target).sum(dim=(2, 3)) + union = pred.sum(dim=(2, 3)) + target.sum(dim=(2, 3)) + dice = (2.0 * intersection + self.smooth) / (union + self.smooth) + dice_loss = 1.0 - dice + dice_loss = dice_loss.mean(dim=1) + + if self.reduction == "mean": + return dice_loss.mean() + elif self.reduction == "sum": + return dice_loss.sum() + else: + return dice_loss + + +class BCEDiceLoss(nn.Module): + """Criterion class for computing combined BCE and Dice losses.""" + + def __init__(self, weight_bce: float = 0.5, weight_dice: float = 0.5): + """Initialize BCEDiceLoss with BCE and Dice weight factors. + + Args: + weight_bce (float): Weight factor for BCE loss component. + weight_dice (float): Weight factor for Dice loss component. + """ + super().__init__() + self.weight_bce = weight_bce + self.weight_dice = weight_dice + self.bce = nn.BCEWithLogitsLoss() + self.dice = MultiChannelDiceLoss(smooth=1) + + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + """Calculate combined BCE and Dice loss between predictions and targets.""" + _, _, mask_h, mask_w = pred.shape + if tuple(target.shape[-2:]) != (mask_h, mask_w): # downsample to the same size as pred + target = F.interpolate(target, (mask_h, mask_w), mode="nearest") + return self.weight_bce * self.bce(pred, target) + self.weight_dice * self.dice(pred, target) + + +class KeypointLoss(nn.Module): + """Criterion class for computing keypoint losses.""" + + def __init__(self, sigmas: torch.Tensor) -> None: + """Initialize the KeypointLoss class with keypoint sigmas.""" + super().__init__() + self.sigmas = sigmas + + def forward( + self, pred_kpts: torch.Tensor, gt_kpts: torch.Tensor, kpt_mask: torch.Tensor, area: torch.Tensor + ) -> torch.Tensor: + """Calculate keypoint loss factor and Euclidean distance loss for keypoints.""" + d = (pred_kpts[..., 0] - gt_kpts[..., 0]).pow(2) + (pred_kpts[..., 1] - gt_kpts[..., 1]).pow(2) + kpt_loss_factor = kpt_mask.shape[1] / (torch.sum(kpt_mask != 0, dim=1) + 1e-9) + # e = d / (2 * (area * self.sigmas) ** 2 + 1e-9) # from formula + e = d / ((2 * self.sigmas).pow(2) * (area + 1e-9) * 2) # from cocoeval + return (kpt_loss_factor.view(-1, 1) * ((1 - torch.exp(-e)) * kpt_mask)).mean() + + +class v8DetectionLoss: + """Criterion class for computing training losses for YOLOv8 object detection.""" + + def __init__( + self, model: torch.nn.Module, tal_topk: int = 10, tal_topk2: int | None = None + ): # model must be de-paralleled + """Initialize v8DetectionLoss with model parameters and task-aligned assignment settings.""" + device = next(model.parameters()).device # get model device + h = model.args # hyperparameters + + m = model.model[-1] # Detect() module + self.bce = nn.BCEWithLogitsLoss(reduction="none") + self.hyp = h + self.stride = m.stride # model strides + self.nc = m.nc # number of classes + self.no = m.nc + m.reg_max * 4 + self.reg_max = m.reg_max + self.device = device + + self.use_dfl = m.reg_max > 1 + + # Class weights for handling imbalanced datasets + self.class_weights = getattr(model, "class_weights", None) + if self.class_weights is not None: + self.class_weights = self.class_weights.to(device).view(1, 1, -1) + + self.assigner = TaskAlignedAssigner( + topk=tal_topk, + num_classes=self.nc, + alpha=0.5, + beta=6.0, + stride=self.stride.tolist(), + topk2=tal_topk2, + ) + self.bbox_loss = BboxLoss(m.reg_max).to(device) + self.proj = torch.arange(m.reg_max, dtype=torch.float, device=device) + + def preprocess(self, targets: torch.Tensor, batch_size: int, scale_tensor: torch.Tensor) -> torch.Tensor: + """Preprocess targets by converting to tensor format and scaling coordinates.""" + nl, ne = targets.shape + if nl == 0: + out = torch.zeros(batch_size, 0, ne - 1, device=self.device) + else: + batch_idx = targets[:, 0].long() # image index + _, counts = batch_idx.unique(return_counts=True) + counts = counts.to(dtype=torch.int32) + out = torch.zeros(batch_size, counts.max(), ne - 1, device=self.device) + offsets = torch.zeros(batch_size + 1, dtype=torch.long, device=self.device) + offsets.scatter_add_(0, batch_idx + 1, torch.ones_like(batch_idx)) + offsets = offsets.cumsum(0) + within_idx = torch.arange(nl, device=self.device) - offsets[batch_idx] + out[batch_idx, within_idx] = targets[:, 1:] + out[..., 1:5] = xywh2xyxy(out[..., 1:5].mul_(scale_tensor)) + return out + + def bbox_decode(self, anchor_points: torch.Tensor, pred_dist: torch.Tensor) -> torch.Tensor: + """Decode predicted object bounding box coordinates from anchor points and distribution.""" + if self.use_dfl: + b, a, c = pred_dist.shape # batch, anchors, channels + pred_dist = pred_dist.view(b, a, 4, c // 4).softmax(3).matmul(self.proj.type(pred_dist.dtype)) + # pred_dist = pred_dist.view(b, a, c // 4, 4).transpose(2,3).softmax(3).matmul(self.proj.type(pred_dist.dtype)) + # pred_dist = (pred_dist.view(b, a, c // 4, 4).softmax(2) * self.proj.type(pred_dist.dtype).view(1, 1, -1, 1)).sum(2) + return dist2bbox(pred_dist, anchor_points, xywh=False) + + def get_assigned_targets_and_loss(self, preds: dict[str, torch.Tensor], batch: dict[str, Any]) -> tuple: + """Calculate the sum of the loss for box, cls and dfl multiplied by batch size and return foreground mask and + target indices. + """ + loss = torch.zeros(3, device=self.device) # box, cls, dfl + pred_distri, pred_scores = ( + preds["boxes"].permute(0, 2, 1).contiguous(), + preds["scores"].permute(0, 2, 1).contiguous(), + ) + anchor_points, stride_tensor = make_anchors(preds["feats"], self.stride, 0.5) + + dtype = pred_scores.dtype + batch_size = pred_scores.shape[0] + imgsz = torch.tensor(preds["feats"][0].shape[2:], device=self.device, dtype=dtype) * self.stride[0] + + # Targets + targets = torch.cat((batch["batch_idx"].view(-1, 1), batch["cls"].view(-1, 1), batch["bboxes"]), 1) + targets = self.preprocess(targets.to(self.device), batch_size, scale_tensor=imgsz[[1, 0, 1, 0]]) + gt_labels, gt_bboxes = targets.split((1, 4), 2) # cls, xyxy + mask_gt = gt_bboxes.sum(2, keepdim=True).gt_(0.0) + + # Pboxes + pred_bboxes = self.bbox_decode(anchor_points, pred_distri) # xyxy, (b, h*w, 4) + + _, target_bboxes, target_scores, fg_mask, target_gt_idx = self.assigner( + pred_scores.detach().sigmoid(), + (pred_bboxes.detach() * stride_tensor).type(gt_bboxes.dtype), + anchor_points * stride_tensor, + gt_labels, + gt_bboxes, + mask_gt, + ) + + target_scores_sum = max(target_scores.sum(), 1) + + # Cls loss with optional class weighting + bce_loss = self.bce(pred_scores, target_scores.to(dtype)) # (bs, num_anchors, nc) + if self.class_weights is not None: + bce_loss *= self.class_weights + loss[1] = bce_loss.sum() / target_scores_sum # BCE + + # Bbox loss + if fg_mask.sum(): + loss[0], loss[2] = self.bbox_loss( + pred_distri, + pred_bboxes, + anchor_points, + target_bboxes / stride_tensor, + target_scores, + target_scores_sum, + fg_mask, + imgsz, + stride_tensor, + ) + + loss[0] *= self.hyp.box # box gain + loss[1] *= self.hyp.cls # cls gain + loss[2] *= self.hyp.dfl # dfl gain + return ( + (fg_mask, target_gt_idx, target_bboxes, anchor_points, stride_tensor), + loss, + loss.detach(), + ) # loss(box, cls, dfl) + + def parse_output( + self, preds: dict[str, torch.Tensor] | tuple[torch.Tensor, dict[str, torch.Tensor]] + ) -> torch.Tensor: + """Parse model predictions to extract features.""" + return preds[1] if isinstance(preds, tuple) else preds + + def __call__( + self, + preds: dict[str, torch.Tensor] | tuple[torch.Tensor, dict[str, torch.Tensor]], + batch: dict[str, torch.Tensor], + ) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the sum of the loss for box, cls and dfl multiplied by batch size.""" + return self.loss(self.parse_output(preds), batch) + + def loss(self, preds: dict[str, torch.Tensor], batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate detection loss using assigned targets.""" + batch_size = preds["boxes"].shape[0] + loss, loss_detach = self.get_assigned_targets_and_loss(preds, batch)[1:] + return loss * batch_size, loss_detach + + +class v8SegmentationLoss(v8DetectionLoss): + """Criterion class for computing training losses for YOLOv8 segmentation.""" + + def __init__( + self, model: torch.nn.Module, tal_topk: int = 10, tal_topk2: int | None = None + ): # model must be de-paralleled + """Initialize the v8SegmentationLoss class with model parameters and mask overlap setting.""" + super().__init__(model, tal_topk, tal_topk2) + self.overlap = model.args.overlap_mask + self.bcedice_loss = BCEDiceLoss(weight_bce=0.5, weight_dice=0.5) + + def loss(self, preds: dict[str, torch.Tensor], batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate and return the combined loss for detection and segmentation.""" + pred_masks, proto = preds["mask_coefficient"].permute(0, 2, 1).contiguous(), preds["proto"] + loss = torch.zeros(5, device=self.device) # box, seg, cls, dfl, semantic + if isinstance(proto, tuple) and len(proto) == 2: + proto, pred_semantic = proto + else: + pred_semantic = None + (fg_mask, target_gt_idx, target_bboxes, _, _), det_loss, _ = self.get_assigned_targets_and_loss(preds, batch) + # NOTE: re-assign index for consistency for now. Need to be removed in the future. + loss[0], loss[2], loss[3] = det_loss[0], det_loss[1], det_loss[2] + + batch_size, _, mask_h, mask_w = proto.shape # batch size, number of masks, mask height, mask width + if fg_mask.sum(): + # Masks loss + masks = batch["masks"].to(self.device).float() + if tuple(masks.shape[-2:]) != (mask_h, mask_w): # downsample + # masks = F.interpolate(masks[None], (mask_h, mask_w), mode="nearest")[0] + proto = F.interpolate(proto, masks.shape[-2:], mode="bilinear", align_corners=False) + + imgsz = ( + torch.tensor(preds["feats"][0].shape[2:], device=self.device, dtype=pred_masks.dtype) * self.stride[0] + ) + loss[1] = self.calculate_segmentation_loss( + fg_mask, + masks, + target_gt_idx, + target_bboxes, + batch["batch_idx"].view(-1, 1), + proto, + pred_masks, + imgsz, + ) + if pred_semantic is not None: + sem_masks = batch["sem_masks"].to(self.device) # NxHxW + sem_masks = F.one_hot(sem_masks.long(), num_classes=self.nc).permute(0, 3, 1, 2).float() # NxCxHxW + + if self.overlap: + mask_zero = masks == 0 # NxHxW + sem_masks[mask_zero.unsqueeze(1).expand_as(sem_masks)] = 0 + else: + batch_idx = batch["batch_idx"].view(-1) # [total_instances] + for i in range(batch_size): + instance_mask_i = masks[batch_idx == i] # [num_instances_i, H, W] + if len(instance_mask_i) == 0: + continue + sem_masks[i, :, instance_mask_i.sum(dim=0) == 0] = 0 + + loss[4] = self.bcedice_loss(pred_semantic, sem_masks) + loss[4] *= self.hyp.box # seg gain + + # WARNING: lines below prevent Multi-GPU DDP 'unused gradient' PyTorch errors, do not remove + else: + loss[1] += (proto * 0).sum() + (pred_masks * 0).sum() # inf sums may lead to nan loss + if pred_semantic is not None: + loss[4] += (pred_semantic * 0).sum() + + loss[1] *= self.hyp.box # seg gain + return loss * batch_size, loss.detach() # loss(box, seg, cls, dfl, semantic) + + @staticmethod + def single_mask_loss( + gt_mask: torch.Tensor, pred: torch.Tensor, proto: torch.Tensor, xyxy: torch.Tensor, area: torch.Tensor + ) -> torch.Tensor: + """Compute the instance segmentation loss for a single image. + + Args: + gt_mask (torch.Tensor): Ground truth mask of shape (N, H, W), where N is the number of objects. + pred (torch.Tensor): Predicted mask coefficients of shape (N, 32). + proto (torch.Tensor): Prototype masks of shape (32, H, W). + xyxy (torch.Tensor): Ground truth bounding boxes in xyxy format, normalized to [0, 1], of shape (N, 4). + area (torch.Tensor): Area of each ground truth bounding box of shape (N,). + + Returns: + (torch.Tensor): The calculated mask loss for a single image. + + Notes: + The function uses the equation pred_mask = torch.einsum('in,nhw->ihw', pred, proto) to produce the + predicted masks from the prototype masks and predicted mask coefficients. + """ + pred_mask = torch.einsum("in,nhw->ihw", pred, proto) # (n, 32) @ (32, 80, 80) -> (n, 80, 80) + loss = F.binary_cross_entropy_with_logits(pred_mask, gt_mask, reduction="none") + return (crop_mask(loss, xyxy).mean(dim=(1, 2)) / area).sum() + + def calculate_segmentation_loss( + self, + fg_mask: torch.Tensor, + masks: torch.Tensor, + target_gt_idx: torch.Tensor, + target_bboxes: torch.Tensor, + batch_idx: torch.Tensor, + proto: torch.Tensor, + pred_masks: torch.Tensor, + imgsz: torch.Tensor, + ) -> torch.Tensor: + """Calculate the loss for instance segmentation. + + Args: + fg_mask (torch.Tensor): A binary tensor of shape (BS, N_anchors) indicating which anchors are positive. + masks (torch.Tensor): Ground truth masks of shape (BS, H, W) if `overlap` is False, otherwise (BS, ?, H, W). + target_gt_idx (torch.Tensor): Indexes of ground truth objects for each anchor of shape (BS, N_anchors). + target_bboxes (torch.Tensor): Ground truth bounding boxes for each anchor of shape (BS, N_anchors, 4). + batch_idx (torch.Tensor): Batch indices of shape (N_labels_in_batch, 1). + proto (torch.Tensor): Prototype masks of shape (BS, 32, H, W). + pred_masks (torch.Tensor): Predicted masks for each anchor of shape (BS, N_anchors, 32). + imgsz (torch.Tensor): Size of the input image as a tensor of shape (2), i.e., (H, W). + + Returns: + (torch.Tensor): The calculated loss for instance segmentation. + + Notes: + The batch loss can be computed for improved speed at higher memory usage. + For example, pred_mask can be computed as follows: + pred_mask = torch.einsum('in,nhw->ihw', pred, proto) # (i, 32) @ (32, 160, 160) -> (i, 160, 160) + """ + _, _, mask_h, mask_w = proto.shape + loss = 0 + + # Normalize to 0-1 + target_bboxes_normalized = target_bboxes / imgsz[[1, 0, 1, 0]] + + # Areas of target bboxes + marea = xyxy2xywh(target_bboxes_normalized)[..., 2:].prod(2) + + # Normalize to mask size + mxyxy = target_bboxes_normalized * torch.tensor([mask_w, mask_h, mask_w, mask_h], device=proto.device) + + for i, single_i in enumerate(zip(fg_mask, target_gt_idx, pred_masks, proto, mxyxy, marea, masks)): + fg_mask_i, target_gt_idx_i, pred_masks_i, proto_i, mxyxy_i, marea_i, masks_i = single_i + if fg_mask_i.any(): + mask_idx = target_gt_idx_i[fg_mask_i] + if self.overlap: + gt_mask = masks_i == (mask_idx + 1).view(-1, 1, 1) + gt_mask = gt_mask.float() + else: + gt_mask = masks[batch_idx.view(-1) == i][mask_idx] + + loss += self.single_mask_loss( + gt_mask, pred_masks_i[fg_mask_i], proto_i, mxyxy_i[fg_mask_i], marea_i[fg_mask_i] + ) + + # WARNING: lines below prevents Multi-GPU DDP 'unused gradient' PyTorch errors, do not remove + else: + loss += (proto * 0).sum() + (pred_masks * 0).sum() # inf sums may lead to nan loss + + return loss / fg_mask.sum() + + +class v8PoseLoss(v8DetectionLoss): + """Criterion class for computing training losses for YOLOv8 pose estimation.""" + + def __init__(self, model: torch.nn.Module, tal_topk: int = 10, tal_topk2: int = 10): # model must be de-paralleled + """Initialize v8PoseLoss with model parameters and keypoint-specific loss functions.""" + super().__init__(model, tal_topk, tal_topk2) + self.kpt_shape = model.model[-1].kpt_shape + self.bce_pose = nn.BCEWithLogitsLoss() + is_pose = self.kpt_shape == [17, 3] + nkpt = self.kpt_shape[0] # number of keypoints + sigmas = torch.from_numpy(OKS_SIGMA).to(self.device) if is_pose else torch.ones(nkpt, device=self.device) / nkpt + self.keypoint_loss = KeypointLoss(sigmas=sigmas) + + def loss(self, preds: dict[str, torch.Tensor], batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the total loss and detach it for pose estimation.""" + pred_kpts = preds["kpts"].permute(0, 2, 1).contiguous() + loss = torch.zeros(5, device=self.device) # box, kpt_location, kpt_visibility, cls, dfl + (fg_mask, target_gt_idx, target_bboxes, anchor_points, stride_tensor), det_loss, _ = ( + self.get_assigned_targets_and_loss(preds, batch) + ) + # NOTE: re-assign index for consistency for now. Need to be removed in the future. + loss[0], loss[3], loss[4] = det_loss[0], det_loss[1], det_loss[2] + + batch_size = pred_kpts.shape[0] + imgsz = torch.tensor(preds["feats"][0].shape[2:], device=self.device, dtype=pred_kpts.dtype) * self.stride[0] + + # Pboxes + pred_kpts = self.kpts_decode(anchor_points, pred_kpts.view(batch_size, -1, *self.kpt_shape)) # (b, h*w, 17, 3) + + # Keypoint loss + if fg_mask.sum(): + keypoints = batch["keypoints"].to(self.device).float().clone() + keypoints[..., 0] *= imgsz[1] + keypoints[..., 1] *= imgsz[0] + + loss[1], loss[2] = self.calculate_keypoints_loss( + fg_mask, + target_gt_idx, + keypoints, + batch["batch_idx"].view(-1, 1), + stride_tensor, + target_bboxes, + pred_kpts, + ) + + loss[1] *= self.hyp.pose # pose gain + loss[2] *= self.hyp.kobj # kobj gain + + return loss * batch_size, loss.detach() # loss(box, pose, kobj, cls, dfl) + + @staticmethod + def kpts_decode(anchor_points: torch.Tensor, pred_kpts: torch.Tensor) -> torch.Tensor: + """Decode predicted keypoints to image coordinates.""" + y = pred_kpts.clone() + y[..., :2] *= 2.0 + y[..., 0] += anchor_points[:, [0]] - 0.5 + y[..., 1] += anchor_points[:, [1]] - 0.5 + return y + + def _select_target_keypoints( + self, + keypoints: torch.Tensor, + batch_idx: torch.Tensor, + target_gt_idx: torch.Tensor, + masks: torch.Tensor, + ) -> torch.Tensor: + """Select target keypoints for each anchor based on batch index and target ground truth index. + + Args: + keypoints (torch.Tensor): Ground truth keypoints, shape (N_kpts_in_batch, N_kpts_per_object, kpts_dim). + batch_idx (torch.Tensor): Batch index tensor for keypoints, shape (N_kpts_in_batch, 1). + target_gt_idx (torch.Tensor): Index tensor mapping anchors to ground truth objects, shape (BS, N_anchors). + masks (torch.Tensor): Binary mask tensor indicating object presence, shape (BS, N_anchors). + + Returns: + (torch.Tensor): Selected keypoints tensor, shape (BS, N_anchors, N_kpts_per_object, kpts_dim). + """ + batch_idx = batch_idx.flatten() + batch_size = len(masks) + + # Find the maximum number of keypoints in a single image + max_kpts = torch.unique(batch_idx, return_counts=True)[1].max() + + # Create a tensor to hold batched keypoints + batched_keypoints = torch.zeros( + (batch_size, max_kpts, keypoints.shape[1], keypoints.shape[2]), device=keypoints.device + ) + + # Vectorized fill: compute within-batch position for each keypoint using cumulative offsets + batch_idx_long = batch_idx.long() + offsets = torch.zeros(batch_size + 1, dtype=torch.long, device=keypoints.device) + offsets.scatter_add_(0, batch_idx_long + 1, torch.ones_like(batch_idx_long)) + offsets = offsets.cumsum(0) + within_idx = torch.arange(len(batch_idx), device=keypoints.device) - offsets[batch_idx_long] + batched_keypoints[batch_idx_long, within_idx] = keypoints + + # Expand dimensions of target_gt_idx to match the shape of batched_keypoints + target_gt_idx_expanded = target_gt_idx.unsqueeze(-1).unsqueeze(-1) + + # Use target_gt_idx_expanded to select keypoints from batched_keypoints + selected_keypoints = batched_keypoints.gather( + 1, target_gt_idx_expanded.expand(-1, -1, keypoints.shape[1], keypoints.shape[2]) + ) + + return selected_keypoints + + def calculate_keypoints_loss( + self, + masks: torch.Tensor, + target_gt_idx: torch.Tensor, + keypoints: torch.Tensor, + batch_idx: torch.Tensor, + stride_tensor: torch.Tensor, + target_bboxes: torch.Tensor, + pred_kpts: torch.Tensor, + ) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the keypoints loss for the model. + + This function calculates the keypoints loss and keypoints object loss for a given batch. The keypoints loss is + based on the difference between the predicted keypoints and ground truth keypoints. The keypoints object loss is + a binary classification loss that classifies whether a keypoint is present or not. + + Args: + masks (torch.Tensor): Binary mask tensor indicating object presence, shape (BS, N_anchors). + target_gt_idx (torch.Tensor): Index tensor mapping anchors to ground truth objects, shape (BS, N_anchors). + keypoints (torch.Tensor): Ground truth keypoints, shape (N_kpts_in_batch, N_kpts_per_object, kpts_dim). + batch_idx (torch.Tensor): Batch index tensor for keypoints, shape (N_kpts_in_batch, 1). + stride_tensor (torch.Tensor): Stride tensor for anchors, shape (N_anchors, 1). + target_bboxes (torch.Tensor): Ground truth boxes in (x1, y1, x2, y2) format, shape (BS, N_anchors, 4). + pred_kpts (torch.Tensor): Predicted keypoints, shape (BS, N_anchors, N_kpts_per_object, kpts_dim). + + Returns: + kpts_loss (torch.Tensor): The keypoints loss. + kpts_obj_loss (torch.Tensor): The keypoints object loss. + """ + # Select target keypoints using helper method + selected_keypoints = self._select_target_keypoints(keypoints, batch_idx, target_gt_idx, masks) + + # Divide coordinates by stride + selected_keypoints[..., :2] /= stride_tensor.view(1, -1, 1, 1) + + kpts_loss = 0 + kpts_obj_loss = 0 + + if masks.any(): + target_bboxes /= stride_tensor + gt_kpt = selected_keypoints[masks] + area = xyxy2xywh(target_bboxes[masks])[:, 2:].prod(1, keepdim=True) + pred_kpt = pred_kpts[masks] + kpt_mask = gt_kpt[..., 2] != 0 if gt_kpt.shape[-1] == 3 else torch.full_like(gt_kpt[..., 0], True) + kpts_loss = self.keypoint_loss(pred_kpt, gt_kpt, kpt_mask, area) # pose loss + + if pred_kpt.shape[-1] == 3: + kpts_obj_loss = self.bce_pose(pred_kpt[..., 2], kpt_mask.float()) # keypoint obj loss + + return kpts_loss, kpts_obj_loss + + +class PoseLoss26(v8PoseLoss): + """Criterion class for computing training losses for YOLO26 pose estimation with RLE loss support.""" + + def __init__( + self, model: torch.nn.Module, tal_topk: int = 10, tal_topk2: int | None = None + ): # model must be de-paralleled + """Initialize PoseLoss26 with model parameters and keypoint-specific loss functions including RLE loss.""" + super().__init__(model, tal_topk, tal_topk2) + is_pose = self.kpt_shape == [17, 3] + nkpt = self.kpt_shape[0] # number of keypoints + self.rle_loss = None + self.flow_model = model.model[-1].flow_model if hasattr(model.model[-1], "flow_model") else None + if self.flow_model is not None: + self.rle_loss = RLELoss(use_target_weight=True).to(self.device) + self.target_weights = ( + torch.from_numpy(RLE_WEIGHT).to(self.device) if is_pose else torch.ones(nkpt, device=self.device) + ) + + def loss(self, preds: dict[str, torch.Tensor], batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the total loss and detach it for pose estimation.""" + pred_kpts = preds["kpts"].permute(0, 2, 1).contiguous() + loss = torch.zeros( + 6 if self.rle_loss else 5, device=self.device + ) # box, kpt_location, kpt_visibility, cls, dfl[, rle] + (fg_mask, target_gt_idx, target_bboxes, anchor_points, stride_tensor), det_loss, _ = ( + self.get_assigned_targets_and_loss(preds, batch) + ) + # NOTE: re-assign index for consistency for now. Need to be removed in the future. + loss[0], loss[3], loss[4] = det_loss[0], det_loss[1], det_loss[2] + + batch_size = pred_kpts.shape[0] + imgsz = torch.tensor(preds["feats"][0].shape[2:], device=self.device, dtype=pred_kpts.dtype) * self.stride[0] + + pred_kpts = pred_kpts.view(batch_size, -1, *self.kpt_shape) # (b, h*w, 17, 3) + + if self.rle_loss and preds.get("kpts_sigma", None) is not None: + pred_sigma = preds["kpts_sigma"].permute(0, 2, 1).contiguous() + pred_sigma = pred_sigma.view(batch_size, -1, self.kpt_shape[0], 2) # (b, h*w, 17, 2) + pred_kpts = torch.cat([pred_kpts, pred_sigma], dim=-1) # (b, h*w, 17, 5) + + pred_kpts = self.kpts_decode(anchor_points, pred_kpts) + + # Keypoint loss + if fg_mask.sum(): + keypoints = batch["keypoints"].to(self.device).float().clone() + keypoints[..., 0] *= imgsz[1] + keypoints[..., 1] *= imgsz[0] + + keypoints_loss = self.calculate_keypoints_loss( + fg_mask, + target_gt_idx, + keypoints, + batch["batch_idx"].view(-1, 1), + stride_tensor, + target_bboxes, + pred_kpts, + ) + loss[1] = keypoints_loss[0] + loss[2] = keypoints_loss[1] + if self.rle_loss is not None: + loss[5] = keypoints_loss[2] + + loss[1] *= self.hyp.pose # pose gain + loss[2] *= self.hyp.kobj # kobj gain + if self.rle_loss is not None: + loss[5] *= self.hyp.rle # rle gain + + return loss * batch_size, loss.detach() # loss(box, kpt_location, kpt_visibility, cls, dfl[, rle]) + + @staticmethod + def kpts_decode(anchor_points: torch.Tensor, pred_kpts: torch.Tensor) -> torch.Tensor: + """Decode predicted keypoints to image coordinates.""" + y = pred_kpts.clone() + y[..., 0] += anchor_points[:, [0]] + y[..., 1] += anchor_points[:, [1]] + return y + + def calculate_rle_loss(self, pred_kpt: torch.Tensor, gt_kpt: torch.Tensor, kpt_mask: torch.Tensor) -> torch.Tensor: + """Calculate the RLE (Residual Log-likelihood Estimation) loss for keypoints. + + Args: + pred_kpt (torch.Tensor): Predicted kpts with sigma, shape (N, num_keypoints, kpts_dim) where kpts_dim >= 4. + gt_kpt (torch.Tensor): Ground truth keypoints, shape (N, num_keypoints, kpts_dim). + kpt_mask (torch.Tensor): Mask for valid keypoints, shape (N, num_keypoints). + + Returns: + (torch.Tensor): The RLE loss. + """ + if not kpt_mask.any(): + return pred_kpt[..., :0].sum() + + pred_kpt_visible = pred_kpt[kpt_mask] + gt_kpt_visible = gt_kpt[kpt_mask] + pred_coords = pred_kpt_visible[:, 0:2] + pred_sigma = pred_kpt_visible[:, -2:] + gt_coords = gt_kpt_visible[:, 0:2] + + target_weights = self.target_weights.unsqueeze(0).repeat(kpt_mask.shape[0], 1) + target_weights = target_weights[kpt_mask] + + pred_sigma = pred_sigma.sigmoid() + error = (pred_coords - gt_coords) / (pred_sigma + 1e-9) + if not error.numel(): + return pred_kpt[..., :0].sum() + + # Filter out NaN and Inf values to prevent MultivariateNormal validation errors + valid_mask = ~(torch.isnan(error) | torch.isinf(error)).any(dim=-1) + if not valid_mask.any(): + return pred_kpt[..., :0].sum() + + error = error[valid_mask] + error = error.clamp(-100, 100) # Prevent numerical instability + pred_sigma = pred_sigma[valid_mask] + target_weights = target_weights[valid_mask] + + log_phi = self.flow_model.log_prob(error) + + return self.rle_loss(pred_sigma, log_phi, error, target_weights) + + def calculate_keypoints_loss( + self, + masks: torch.Tensor, + target_gt_idx: torch.Tensor, + keypoints: torch.Tensor, + batch_idx: torch.Tensor, + stride_tensor: torch.Tensor, + target_bboxes: torch.Tensor, + pred_kpts: torch.Tensor, + ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """Calculate the keypoints loss for the model. + + This function calculates the keypoints loss and keypoints object loss for a given batch. The keypoints loss is + based on the difference between the predicted keypoints and ground truth keypoints. The keypoints object loss is + a binary classification loss that classifies whether a keypoint is present or not. + + Args: + masks (torch.Tensor): Binary mask tensor indicating object presence, shape (BS, N_anchors). + target_gt_idx (torch.Tensor): Index tensor mapping anchors to ground truth objects, shape (BS, N_anchors). + keypoints (torch.Tensor): Ground truth keypoints, shape (N_kpts_in_batch, N_kpts_per_object, kpts_dim). + batch_idx (torch.Tensor): Batch index tensor for keypoints, shape (N_kpts_in_batch, 1). + stride_tensor (torch.Tensor): Stride tensor for anchors, shape (N_anchors, 1). + target_bboxes (torch.Tensor): Ground truth boxes in (x1, y1, x2, y2) format, shape (BS, N_anchors, 4). + pred_kpts (torch.Tensor): Predicted keypoints, shape (BS, N_anchors, N_kpts_per_object, kpts_dim). + + Returns: + kpts_loss (torch.Tensor): The keypoints loss. + kpts_obj_loss (torch.Tensor): The keypoints object loss. + rle_loss (torch.Tensor): The RLE loss. + """ + # Select target keypoints using inherited helper method + selected_keypoints = self._select_target_keypoints(keypoints, batch_idx, target_gt_idx, masks) + + # Divide coordinates by stride + selected_keypoints[..., :2] /= stride_tensor.view(1, -1, 1, 1) + + kpts_loss = 0 + kpts_obj_loss = 0 + rle_loss = 0 + + if masks.any(): + target_bboxes /= stride_tensor + gt_kpt = selected_keypoints[masks] + area = xyxy2xywh(target_bboxes[masks])[:, 2:].prod(1, keepdim=True) + pred_kpt = pred_kpts[masks] + kpt_mask = gt_kpt[..., 2] != 0 if gt_kpt.shape[-1] == 3 else torch.full_like(gt_kpt[..., 0], True) + kpts_loss = self.keypoint_loss(pred_kpt, gt_kpt, kpt_mask, area) # pose loss + + if self.rle_loss is not None and (pred_kpt.shape[-1] == 4 or pred_kpt.shape[-1] == 5): + rle_loss = self.calculate_rle_loss(pred_kpt, gt_kpt, kpt_mask) + rle_loss = rle_loss.clamp(min=0) + if pred_kpt.shape[-1] == 3 or pred_kpt.shape[-1] == 5: + kpts_obj_loss = self.bce_pose(pred_kpt[..., 2], kpt_mask.float()) # keypoint obj loss + + return kpts_loss, kpts_obj_loss, rle_loss + + +class v8ClassificationLoss: + """Criterion class for computing training losses for classification.""" + + def __call__(self, preds: Any, batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Compute the classification loss between predictions and true labels.""" + preds = preds[1] if isinstance(preds, (list, tuple)) else preds + loss = F.cross_entropy(preds, batch["cls"], reduction="mean") + return loss, loss.detach() + + +class v8OBBLoss(v8DetectionLoss): + """Calculates losses for object detection, classification, and box distribution in rotated YOLO models.""" + + def __init__(self, model: torch.nn.Module, tal_topk=10, tal_topk2: int | None = None): + """Initialize v8OBBLoss with model, assigner, and rotated bbox loss; model must be de-paralleled.""" + super().__init__(model, tal_topk=tal_topk) + self.assigner = RotatedTaskAlignedAssigner( + topk=tal_topk, + num_classes=self.nc, + alpha=0.5, + beta=6.0, + stride=self.stride.tolist(), + topk2=tal_topk2, + ) + self.bbox_loss = RotatedBboxLoss(self.reg_max).to(self.device) + + def preprocess(self, targets: torch.Tensor, batch_size: int, scale_tensor: torch.Tensor) -> torch.Tensor: + """Preprocess targets for oriented bounding box detection.""" + if targets.shape[0] == 0: + out = torch.zeros(batch_size, 0, 6, device=self.device) + else: + batch_idx = targets[:, 0].long() # image index + _, counts = batch_idx.unique(return_counts=True) + counts = counts.to(dtype=torch.int32) + out = torch.zeros(batch_size, counts.max(), 6, device=self.device) + packed_targets = targets[:, 1:].clone() + packed_targets[:, 1:5].mul_(scale_tensor) + offsets = torch.zeros(batch_size + 1, dtype=torch.long, device=self.device) + offsets.scatter_add_(0, batch_idx + 1, torch.ones_like(batch_idx)) + offsets = offsets.cumsum(0) + within_idx = torch.arange(len(targets), device=self.device) - offsets[batch_idx] + out[batch_idx, within_idx] = packed_targets + return out + + def loss(self, preds: dict[str, torch.Tensor], batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate and return the loss for oriented bounding box detection.""" + loss = torch.zeros(4, device=self.device) # box, cls, dfl, angle + pred_distri, pred_scores, pred_angle = ( + preds["boxes"].permute(0, 2, 1).contiguous(), + preds["scores"].permute(0, 2, 1).contiguous(), + preds["angle"].permute(0, 2, 1).contiguous(), + ) + anchor_points, stride_tensor = make_anchors(preds["feats"], self.stride, 0.5) + batch_size = pred_angle.shape[0] # batch size + + dtype = pred_scores.dtype + imgsz = torch.tensor(preds["feats"][0].shape[2:], device=self.device, dtype=dtype) * self.stride[0] + + # targets + try: + batch_idx = batch["batch_idx"].view(-1, 1) + targets = torch.cat((batch_idx, batch["cls"].view(-1, 1), batch["bboxes"].view(-1, 5)), 1) + rw, rh = targets[:, 4] * float(imgsz[1]), targets[:, 5] * float(imgsz[0]) + targets = targets[(rw >= 2) & (rh >= 2)] # filter rboxes of tiny size to stabilize training + targets = self.preprocess(targets.to(self.device), batch_size, scale_tensor=imgsz[[1, 0, 1, 0]]) + gt_labels, gt_bboxes = targets.split((1, 5), 2) # cls, xywhr + mask_gt = gt_bboxes.sum(2, keepdim=True).gt_(0.0) + wh = gt_bboxes[..., 2:4] + wh.masked_fill_((wh < self.stride[0]) & mask_gt.bool(), self.assigner.stride_val) + except RuntimeError as e: + raise TypeError( + "ERROR ❌ OBB dataset incorrectly formatted or not a OBB dataset.\n" + "This error can occur when incorrectly training a 'OBB' model on a 'detect' dataset, " + "i.e. 'yolo train model=yolo26n-obb.pt data=dota8.yaml'.\nVerify your dataset is a " + "correctly formatted 'OBB' dataset using 'data=dota8.yaml' " + "as an example.\nSee https://docs.ultralytics.com/datasets/obb/ for help." + ) from e + + # Pboxes + pred_bboxes = self.bbox_decode(anchor_points, pred_distri, pred_angle) # xywhr, (b, h*w, 5) + + bboxes_for_assigner = pred_bboxes.clone().detach() + # Only the first four elements need to be scaled + bboxes_for_assigner[..., :4] *= stride_tensor + _, target_bboxes, target_scores, fg_mask, _ = self.assigner( + pred_scores.detach().sigmoid(), + bboxes_for_assigner.type(gt_bboxes.dtype), + anchor_points * stride_tensor, + gt_labels, + gt_bboxes, + mask_gt, + ) + + target_scores_sum = max(target_scores.sum(), 1) + + # Cls loss + # loss[1] = self.varifocal_loss(pred_scores, target_scores, target_labels) / target_scores_sum # VFL way + bce_loss = self.bce(pred_scores, target_scores.to(dtype)) # BCE + if self.class_weights is not None: + bce_loss *= self.class_weights + loss[1] = bce_loss.sum() / target_scores_sum + + # Bbox loss + if fg_mask.sum(): + target_bboxes[..., :4] /= stride_tensor + loss[0], loss[2] = self.bbox_loss( + pred_distri, + pred_bboxes, + anchor_points, + target_bboxes, + target_scores, + target_scores_sum, + fg_mask, + imgsz, + stride_tensor, + ) + weight = target_scores.sum(-1)[fg_mask] + loss[3] = self.calculate_angle_loss( + pred_bboxes, target_bboxes, fg_mask, weight, target_scores_sum + ) # angle loss + else: + loss[0] += (pred_angle * 0).sum() + + loss[0] *= self.hyp.box # box gain + loss[1] *= self.hyp.cls # cls gain + loss[2] *= self.hyp.dfl # dfl gain + loss[3] *= self.hyp.angle # angle gain + + return loss * batch_size, loss.detach() # loss(box, cls, dfl, angle) + + def bbox_decode( + self, anchor_points: torch.Tensor, pred_dist: torch.Tensor, pred_angle: torch.Tensor + ) -> torch.Tensor: + """Decode predicted object bounding box coordinates from anchor points and distribution. + + Args: + anchor_points (torch.Tensor): Anchor points, (h*w, 2). + pred_dist (torch.Tensor): Predicted rotated distance, (bs, h*w, 4). + pred_angle (torch.Tensor): Predicted angle, (bs, h*w, 1). + + Returns: + (torch.Tensor): Predicted rotated bounding boxes with angles, (bs, h*w, 5). + """ + if self.use_dfl: + b, a, c = pred_dist.shape # batch, anchors, channels + pred_dist = pred_dist.view(b, a, 4, c // 4).softmax(3).matmul(self.proj.type(pred_dist.dtype)) + return torch.cat((dist2rbox(pred_dist, pred_angle, anchor_points), pred_angle), dim=-1) + + def calculate_angle_loss(self, pred_bboxes, target_bboxes, fg_mask, weight, target_scores_sum, lambda_val=3): + """Calculate oriented angle loss. + + Args: + pred_bboxes (torch.Tensor): Predicted bounding boxes with shape [N, 5] (x, y, w, h, theta). + target_bboxes (torch.Tensor): Target bounding boxes with shape [N, 5] (x, y, w, h, theta). + fg_mask (torch.Tensor): Foreground mask indicating valid predictions. + weight (torch.Tensor): Loss weights for each prediction. + target_scores_sum (torch.Tensor): Sum of target scores for normalization. + lambda_val (int): Controls the sensitivity to aspect ratio. + + Returns: + (torch.Tensor): The calculated angle loss. + """ + w_gt = target_bboxes[..., 2] + h_gt = target_bboxes[..., 3] + pred_theta = pred_bboxes[..., 4] + target_theta = target_bboxes[..., 4] + + log_ar = torch.log((w_gt + 1e-9) / (h_gt + 1e-9)) + scale_weight = torch.exp(-(log_ar**2) / (lambda_val**2)) + + delta_theta = pred_theta - target_theta + delta_theta_wrapped = delta_theta - torch.round(delta_theta / math.pi) * math.pi + ang_loss = torch.sin(2 * delta_theta_wrapped[fg_mask]) ** 2 + + ang_loss = scale_weight[fg_mask] * ang_loss + ang_loss = ang_loss * weight + + return ang_loss.sum() / target_scores_sum + + +class E2EDetectLoss: + """Criterion class for computing training losses for end-to-end detection.""" + + def __init__(self, model: torch.nn.Module): + """Initialize E2EDetectLoss with one-to-many and one-to-one detection losses using the provided model.""" + self.one2many = v8DetectionLoss(model, tal_topk=10) + self.one2one = v8DetectionLoss(model, tal_topk=1) + + def __call__(self, preds: Any, batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the sum of the loss for box, cls and dfl multiplied by batch size.""" + preds = preds[1] if isinstance(preds, tuple) else preds + one2many = preds["one2many"] + loss_one2many = self.one2many(one2many, batch) + one2one = preds["one2one"] + loss_one2one = self.one2one(one2one, batch) + return loss_one2many[0] + loss_one2one[0], loss_one2many[1] + loss_one2one[1] + + +class E2ELoss: + """Criterion class for computing training losses for end-to-end detection.""" + + def __init__(self, model: torch.nn.Module, loss_fn=v8DetectionLoss): + """Initialize E2ELoss with one-to-many and one-to-one detection losses using the provided model.""" + self.one2many = loss_fn(model, tal_topk=10) + self.one2one = loss_fn(model, tal_topk=7, tal_topk2=1) + self.updates = 0 + self.total = 1.0 + # init gain + self.o2m = 0.8 + self.o2o = self.total - self.o2m + self.o2m_copy = self.o2m + # final gain + self.final_o2m = 0.1 + + def __call__(self, preds: Any, batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the sum of the loss for box, cls and dfl multiplied by batch size.""" + preds = self.one2many.parse_output(preds) + one2many, one2one = preds["one2many"], preds["one2one"] + loss_one2many = self.one2many.loss(one2many, batch) + loss_one2one = self.one2one.loss(one2one, batch) + return loss_one2many[0] * self.o2m + loss_one2one[0] * self.o2o, loss_one2one[1] + + def update(self) -> None: + """Update the weights for one-to-many and one-to-one losses based on the decay schedule.""" + self.updates += 1 + self.o2m = self.decay(self.updates) + self.o2o = max(self.total - self.o2m, 0) + + def decay(self, x) -> float: + """Calculate the decayed weight for one-to-many loss based on the current update step.""" + return max(1 - x / max(self.one2one.hyp.epochs - 1, 1), 0) * (self.o2m_copy - self.final_o2m) + self.final_o2m + + +class TVPDetectLoss: + """Criterion class for computing training losses for text-visual prompt detection.""" + + def __init__(self, model: torch.nn.Module, tal_topk=10, tal_topk2: int | None = None): + """Initialize TVPDetectLoss with task-prompt and visual-prompt criteria using the provided model.""" + self.vp_criterion = v8DetectionLoss(model, tal_topk, tal_topk2) + # NOTE: store following info as it's changeable in __call__ + self.hyp = self.vp_criterion.hyp + self.ori_nc = self.vp_criterion.nc + self.ori_no = self.vp_criterion.no + self.ori_reg_max = self.vp_criterion.reg_max + + def parse_output(self, preds) -> dict[str, torch.Tensor]: + """Parse model predictions to extract features.""" + return self.vp_criterion.parse_output(preds) + + def __call__(self, preds: Any, batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the loss for text-visual prompt detection.""" + return self.loss(self.parse_output(preds), batch) + + def loss(self, preds: dict[str, torch.Tensor], batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the loss for text-visual prompt detection.""" + if self.ori_nc == preds["scores"].shape[1]: + loss = torch.zeros(3, device=self.vp_criterion.device, requires_grad=True) + return loss, loss.detach() + + preds["scores"] = self._get_vp_features(preds) + vp_loss = self.vp_criterion(preds, batch) + box_loss = vp_loss[0][1] + return box_loss, vp_loss[1] + + def _get_vp_features(self, preds: dict[str, torch.Tensor]) -> list[torch.Tensor]: + """Extract visual-prompt features from the model output.""" + scores = preds["scores"] + vnc = scores.shape[1] + + self.vp_criterion.nc = vnc + self.vp_criterion.no = vnc + self.vp_criterion.reg_max * 4 + self.vp_criterion.assigner.num_classes = vnc + return scores + + +class TVPSegmentLoss(TVPDetectLoss): + """Criterion class for computing training losses for text-visual prompt segmentation.""" + + def __init__(self, model: torch.nn.Module, tal_topk=10): + """Initialize TVPSegmentLoss with task-prompt and visual-prompt criteria using the provided model.""" + super().__init__(model) + self.vp_criterion = v8SegmentationLoss(model, tal_topk) + self.hyp = self.vp_criterion.hyp + + def __call__(self, preds: Any, batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the loss for text-visual prompt segmentation.""" + return self.loss(self.parse_output(preds), batch) + + def loss(self, preds: Any, batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: + """Calculate the loss for text-visual prompt segmentation.""" + if self.ori_nc == preds["scores"].shape[1]: + loss = torch.zeros(4, device=self.vp_criterion.device, requires_grad=True) + return loss, loss.detach() + + preds["scores"] = self._get_vp_features(preds) + vp_loss = self.vp_criterion(preds, batch) + cls_loss = vp_loss[0][2] + return cls_loss, vp_loss[1] + + +class SemanticSegmentationLoss(nn.Module): + """Loss function for semantic segmentation using cross-entropy and Dice terms. + + Attributes: + nc (int): Number of semantic classes. + ce (nn.CrossEntropyLoss): Cross-entropy loss with ignore_index=255. + """ + + def __init__(self, model: torch.nn.Module): + """Initialize semantic segmentation loss. + + Args: + model (torch.nn.Module): Model containing the SemanticSegment head. + """ + super().__init__() + m = model.model[-1] + self.nc = m.nc + self.device = next(model.parameters()).device + self.dtype = next(model.parameters()).dtype + data_name = Path(str(getattr(model.args, "data", "") or "")).stem.lower() + self.use_cityscapes_weight = data_name in {"cityscapes", "cityscapes8"} and self.nc == len(CITYSCAPES_WEIGHT) + weight = getattr(model, "class_weights", None) # cls_pw frequency weights, else hardcoded Cityscapes + if weight is None and self.use_cityscapes_weight: + weight = torch.from_numpy(CITYSCAPES_WEIGHT) + weight = None if weight is None else weight.to(device=self.device, dtype=self.dtype) + if self.nc == 1: + self.ce = nn.BCEWithLogitsLoss() # binary: class weighting intentionally unsupported + else: + self.ce = nn.CrossEntropyLoss(ignore_index=255).to(device=self.device, dtype=self.dtype) + if weight is not None: + # Non-persistent: weight is a deterministic constant, no need to serialize into ckpt state_dict. + self.ce.register_buffer("weight", weight, persistent=False) + + def _resize_masks(self, masks, target_shape): + """Resize masks to match prediction spatial dimensions.""" + if masks.shape[1:] != target_shape: + return ( + F.interpolate(masks.float().unsqueeze(1), size=target_shape, mode="nearest").squeeze(1).to(torch.int32) + ) + return masks + + def _ce_loss(self, preds, masks): + """Compute cross-entropy on flattened pixels to avoid the CUDA nll_loss2d path.""" + if self.nc == 1: + flat = masks.reshape(-1) + valid = flat != 255 + logits = preds.reshape(-1)[valid] + target = flat[valid].float() + else: + logits = preds.permute(0, 2, 3, 1).reshape(-1, self.nc) + target = masks.reshape(-1).long() + return self.ce(logits, target) + + def _dice_loss(self, preds, masks): + """Compute Dice loss excluding ignore pixels.""" + if self.nc == 1: + return self._binary_dice_loss(preds, masks) + flat_target = masks.reshape(-1) + valid = flat_target != 255 + if not valid.any(): + return preds.sum() * 0 + + pred_soft = F.softmax(preds, dim=1) + target = flat_target[valid].long() + flat_pred = pred_soft.float().permute(0, 2, 3, 1).reshape(-1, self.nc)[valid] + intersection = torch.zeros(self.nc, device=preds.device, dtype=torch.float32) + intersection.scatter_add_(0, target, flat_pred.gather(1, target[:, None]).squeeze(1)) + pred_sum = flat_pred.sum(dim=0) + target_sum = torch.bincount(target, minlength=self.nc).to(device=preds.device, dtype=torch.float32) + cardinality = pred_sum + target_sum + return (1.0 - (2.0 * intersection + 1.0) / (cardinality + 1.0)).mean() + + def _binary_dice_loss(self, preds, masks): + """Compute Dice loss for single-class (binary) segmentation. + + Pixels with value 255 are excluded from Dice terms to match BCE valid-pixel filtering. + """ + valid = (masks != 255).float() + pred_soft = preds.squeeze(1).sigmoid() + target = (masks == 1).float() + intersection = (pred_soft * target * valid).sum() + cardinality = ((pred_soft + target) * valid).sum() + return 1.0 - (2.0 * intersection + 1.0) / (cardinality + 1.0) + + def forward(self, preds, batch): + """Compute semantic segmentation loss with optional auxiliary loss. + + Args: + preds (torch.Tensor | tuple): Main logits [B, nc, H', W'], or (main, aux) tuple. + batch (dict): Batch dict with 'semantic_mask' [B, H, W] containing class IDs (255=ignore). + + Returns: + (tuple[torch.Tensor, torch.Tensor]): (total_loss * batch_size, detached loss items [ce, dice, aux]). + """ + # Unpack auxiliary logits when present. + aux_logits = None + if isinstance(preds, tuple): + preds, aux_logits = preds + + masks = batch["semantic_mask"].to(preds.device) + if preds.shape[2:] != masks.shape[1:]: + preds = F.interpolate(preds, size=masks.shape[1:], mode="bilinear", align_corners=False) + + # Main cross-entropy and Dice loss. + ce_loss = self._ce_loss(preds, masks) + dice_loss = self._dice_loss(preds, masks) + total = ce_loss + dice_loss + + # Auxiliary cross-entropy loss. Match ce_loss dtype so torch.stack below succeeds under AMP. + aux_loss = torch.tensor(0.0, device=preds.device, dtype=ce_loss.dtype) + if aux_logits is not None: + if aux_logits.shape[2:] != masks.shape[1:]: + aux_logits = F.interpolate(aux_logits, size=masks.shape[1:], mode="bilinear", align_corners=False) + aux_loss = self._ce_loss(aux_logits, masks) * 0.4 + total += aux_loss + + loss_items = torch.stack([ce_loss, dice_loss, aux_loss]).detach() + return total * preds.shape[0], loss_items diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/metrics.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/metrics.py new file mode 100644 index 0000000000000000000000000000000000000000..3a0ed6b1735b94e1c525eb245fcf14442989a0f6 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/metrics.py @@ -0,0 +1,1891 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license +"""Model validation metrics.""" + +from __future__ import annotations + +import math +import warnings +from collections import defaultdict +from pathlib import Path +from typing import Any + +import numpy as np +import torch + +from ultralytics.utils import LOGGER, DataExportMixin, SimpleClass, TryExcept, checks, plt_settings +from ultralytics.utils.plotting import colors + +OKS_SIGMA = ( + np.array( + [0.26, 0.25, 0.25, 0.35, 0.35, 0.79, 0.79, 0.72, 0.72, 0.62, 0.62, 1.07, 1.07, 0.87, 0.87, 0.89, 0.89], + dtype=np.float32, + ) + / 10.0 +) +RLE_WEIGHT = np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.2, 1.2, 1.5, 1.5, 1.0, 1.0, 1.2, 1.2, 1.5, 1.5]) +CITYSCAPES_WEIGHT = np.array( + [ + 0.8373, + 0.918, + 0.866, + 1.0345, + 1.0166, + 0.9969, + 0.9754, + 1.0489, + 0.8786, + 1.0023, + 0.9539, + 0.9843, + 1.1116, + 0.9037, + 1.0865, + 1.0955, + 1.0865, + 1.1529, + 1.0507, + ] +) + + +def bbox_ioa(box1: np.ndarray, box2: np.ndarray, iou: bool = False, eps: float = 1e-7) -> np.ndarray: + """Calculate the intersection over box2 area given box1 and box2. + + Args: + box1 (np.ndarray): A numpy array of shape (N, 4) representing N bounding boxes in x1y1x2y2 format. + box2 (np.ndarray): A numpy array of shape (M, 4) representing M bounding boxes in x1y1x2y2 format. + iou (bool, optional): Calculate the standard IoU if True else return inter_area/box2_area. + eps (float, optional): A small value to avoid division by zero. + + Returns: + (np.ndarray): A numpy array of shape (N, M) representing the intersection over box2 area. + """ + # Get the coordinates of bounding boxes + b1_x1, b1_y1, b1_x2, b1_y2 = box1.T + b2_x1, b2_y1, b2_x2, b2_y2 = box2.T + + # Intersection area + inter_area = (np.minimum(b1_x2[:, None], b2_x2) - np.maximum(b1_x1[:, None], b2_x1)).clip(0) * ( + np.minimum(b1_y2[:, None], b2_y2) - np.maximum(b1_y1[:, None], b2_y1) + ).clip(0) + + # Box2 area + area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + if iou: + box1_area = (b1_x2 - b1_x1) * (b1_y2 - b1_y1) + area = area + box1_area[:, None] - inter_area + + # Intersection over box2 area + return inter_area / (area + eps) + + +def box_iou(box1: torch.Tensor, box2: torch.Tensor, eps: float = 1e-7) -> torch.Tensor: + """Calculate intersection-over-union (IoU) of boxes. + + Args: + box1 (torch.Tensor): A tensor of shape (N, 4) representing N bounding boxes in (x1, y1, x2, y2) format. + box2 (torch.Tensor): A tensor of shape (M, 4) representing M bounding boxes in (x1, y1, x2, y2) format. + eps (float, optional): A small value to avoid division by zero. + + Returns: + (torch.Tensor): An NxM tensor containing the pairwise IoU values for every element in box1 and box2. + + References: + https://github.com/pytorch/vision/blob/main/torchvision/ops/boxes.py + """ + # NOTE: Need .float() to get accurate iou values + # inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2) + (a1, a2), (b1, b2) = box1.float().unsqueeze(1).chunk(2, 2), box2.float().unsqueeze(0).chunk(2, 2) + inter = (torch.min(a2, b2) - torch.max(a1, b1)).clamp_(0).prod(2) + + # IoU = inter / (area1 + area2 - inter) + return inter / ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter + eps) + + +def bbox_iou( + box1: torch.Tensor, + box2: torch.Tensor, + xywh: bool = True, + GIoU: bool = False, + DIoU: bool = False, + CIoU: bool = False, + eps: float = 1e-7, +) -> torch.Tensor: + """Calculate the Intersection over Union (IoU) between bounding boxes. + + This function supports various shapes for `box1` and `box2` as long as the last dimension is 4. For instance, you + may pass tensors shaped like (4,), (N, 4), (B, N, 4), or (B, N, 1, 4). Internally, the code will split the last + dimension into (x, y, w, h) if `xywh=True`, or (x1, y1, x2, y2) if `xywh=False`. + + Args: + box1 (torch.Tensor): A tensor representing one or more bounding boxes, with the last dimension being 4. + box2 (torch.Tensor): A tensor representing one or more bounding boxes, with the last dimension being 4. + xywh (bool, optional): If True, input boxes are in (x, y, w, h) format. If False, input boxes are in (x1, y1, + x2, y2) format. + GIoU (bool, optional): If True, calculate Generalized IoU. + DIoU (bool, optional): If True, calculate Distance IoU. + CIoU (bool, optional): If True, calculate Complete IoU. + eps (float, optional): A small value to avoid division by zero. + + Returns: + (torch.Tensor): IoU, GIoU, DIoU, or CIoU values depending on the specified flags. + """ + # Get the coordinates of bounding boxes + if xywh: # transform from xywh to xyxy + (x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1) + w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2 + b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_ + b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_ + else: # x1, y1, x2, y2 = box1 + b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1) + b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1) + w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps + w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps + + # Intersection area + inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp_(0) * ( + b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1) + ).clamp_(0) + + # Union Area + union = w1 * h1 + w2 * h2 - inter + eps + + # IoU + iou = inter / union + if CIoU or DIoU or GIoU: + cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width + ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height + if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1 + c2 = cw.pow(2) + ch.pow(2) + eps # convex diagonal squared + rho2 = ( + (b2_x1 + b2_x2 - b1_x1 - b1_x2).pow(2) + (b2_y1 + b2_y2 - b1_y1 - b1_y2).pow(2) + ) / 4 # center dist**2 + if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47 + v = (4 / math.pi**2) * ((w2 / h2).atan() - (w1 / h1).atan()).pow(2) + with torch.no_grad(): + alpha = v / (v - iou + (1 + eps)) + return iou - (rho2 / c2 + v * alpha) # CIoU + return iou - rho2 / c2 # DIoU + c_area = cw * ch + eps # convex area + return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf + return iou # IoU + + +def mask_iou(mask1: torch.Tensor, mask2: torch.Tensor, eps: float = 1e-7) -> torch.Tensor: + """Calculate masks IoU. + + Args: + mask1 (torch.Tensor): A tensor of shape (N, n) where N is the number of ground truth objects and n is the + product of image width and height. + mask2 (torch.Tensor): A tensor of shape (M, n) where M is the number of predicted objects and n is the product + of image width and height. + eps (float, optional): A small value to avoid division by zero. + + Returns: + (torch.Tensor): A tensor of shape (N, M) representing masks IoU. + """ + intersection = torch.matmul(mask1, mask2.T).clamp_(0) + union = (mask1.sum(1)[:, None] + mask2.sum(1)[None]) - intersection # (area1 + area2) - intersection + return intersection / (union + eps) + + +def kpt_iou( + kpt1: torch.Tensor, kpt2: torch.Tensor, area: torch.Tensor, sigma: list[float], eps: float = 1e-7 +) -> torch.Tensor: + """Calculate Object Keypoint Similarity (OKS). + + Args: + kpt1 (torch.Tensor): A tensor of shape (N, 17, 3) representing ground truth keypoints. + kpt2 (torch.Tensor): A tensor of shape (M, 17, 3) representing predicted keypoints. + area (torch.Tensor): A tensor of shape (N,) representing areas from ground truth. + sigma (list[float]): A list containing 17 values representing keypoint scales. + eps (float, optional): A small value to avoid division by zero. + + Returns: + (torch.Tensor): A tensor of shape (N, M) representing keypoint similarities. + """ + d = (kpt1[:, None, :, 0] - kpt2[..., 0]).pow(2) + (kpt1[:, None, :, 1] - kpt2[..., 1]).pow(2) # (N, M, 17) + sigma = torch.tensor(sigma, device=kpt1.device, dtype=kpt1.dtype) # (17, ) + kpt_mask = kpt1[..., 2] != 0 # (N, 17) + e = d / ((2 * sigma).pow(2) * (area[:, None, None] + eps) * 2) # from cocoeval + # e = d / ((area[None, :, None] + eps) * sigma) ** 2 / 2 # from formula + return ((-e).exp() * kpt_mask[:, None]).sum(-1) / (kpt_mask.sum(-1)[:, None] + eps) + + +def _get_covariance_matrix(boxes: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """Generate covariance matrix from oriented bounding boxes. + + Args: + boxes (torch.Tensor): A tensor of shape (N, 5) representing rotated bounding boxes, with xywhr format. + + Returns: + (tuple[torch.Tensor, torch.Tensor, torch.Tensor]): Covariance matrix components (a, b, c) where the covariance + matrix is [[a, c], [c, b]], each of shape (N, 1). + """ + # Gaussian bounding boxes, ignore the center points (the first two columns) because they are not needed here. + gbbs = torch.cat((boxes[:, 2:4].pow(2) / 12, boxes[:, 4:]), dim=-1) + a, b, c = gbbs.split(1, dim=-1) + cos = c.cos() + sin = c.sin() + cos2 = cos.pow(2) + sin2 = sin.pow(2) + return a * cos2 + b * sin2, a * sin2 + b * cos2, (a - b) * cos * sin + + +def probiou(obb1: torch.Tensor, obb2: torch.Tensor, CIoU: bool = False, eps: float = 1e-7) -> torch.Tensor: + """Calculate probabilistic IoU between oriented bounding boxes. + + Args: + obb1 (torch.Tensor): Ground truth OBBs, shape (N, 5), format xywhr. + obb2 (torch.Tensor): Predicted OBBs, shape (N, 5), format xywhr. + CIoU (bool, optional): If True, calculate CIoU. + eps (float, optional): Small value to avoid division by zero. + + Returns: + (torch.Tensor): OBB similarities, shape (N,). + + Notes: + OBB format: [center_x, center_y, width, height, rotation_angle]. + + References: + https://arxiv.org/pdf/2106.06072v1.pdf + """ + x1, y1 = obb1[..., :2].split(1, dim=-1) + x2, y2 = obb2[..., :2].split(1, dim=-1) + a1, b1, c1 = _get_covariance_matrix(obb1) + a2, b2, c2 = _get_covariance_matrix(obb2) + + t1 = ( + ((a1 + a2) * (y1 - y2).pow(2) + (b1 + b2) * (x1 - x2).pow(2)) / ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2) + eps) + ) * 0.25 + t2 = (((c1 + c2) * (x2 - x1) * (y1 - y2)) / ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2) + eps)) * 0.5 + t3 = ( + ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2)) + / (4 * ((a1 * b1 - c1.pow(2)).clamp_(0) * (a2 * b2 - c2.pow(2)).clamp_(0)).sqrt() + eps) + + eps + ).log() * 0.5 + bd = (t1 + t2 + t3).clamp(eps, 100.0) + hd = (1.0 - (-bd).exp() + eps).sqrt() + iou = 1 - hd + if CIoU: # only include the wh aspect ratio part + w1, h1 = obb1[..., 2:4].split(1, dim=-1) + w2, h2 = obb2[..., 2:4].split(1, dim=-1) + v = (4 / math.pi**2) * ((w2 / h2).atan() - (w1 / h1).atan()).pow(2) + with torch.no_grad(): + alpha = v / (v - iou + (1 + eps)) + return iou - v * alpha # CIoU + return iou + + +def batch_probiou(obb1: torch.Tensor | np.ndarray, obb2: torch.Tensor | np.ndarray, eps: float = 1e-7) -> torch.Tensor: + """Calculate the probabilistic IoU between oriented bounding boxes. + + Args: + obb1 (torch.Tensor | np.ndarray): A tensor of shape (N, 5) representing ground truth obbs, with xywhr format. + obb2 (torch.Tensor | np.ndarray): A tensor of shape (M, 5) representing predicted obbs, with xywhr format. + eps (float, optional): A small value to avoid division by zero. + + Returns: + (torch.Tensor): A tensor of shape (N, M) representing obb similarities. + + References: + https://arxiv.org/pdf/2106.06072v1.pdf + """ + obb1 = torch.from_numpy(obb1) if isinstance(obb1, np.ndarray) else obb1 + obb2 = torch.from_numpy(obb2) if isinstance(obb2, np.ndarray) else obb2 + + x1, y1 = obb1[..., :2].split(1, dim=-1) + x2, y2 = (x.squeeze(-1)[None] for x in obb2[..., :2].split(1, dim=-1)) + a1, b1, c1 = _get_covariance_matrix(obb1) + a2, b2, c2 = (x.squeeze(-1)[None] for x in _get_covariance_matrix(obb2)) + + t1 = ( + ((a1 + a2) * (y1 - y2).pow(2) + (b1 + b2) * (x1 - x2).pow(2)) / ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2) + eps) + ) * 0.25 + t2 = (((c1 + c2) * (x2 - x1) * (y1 - y2)) / ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2) + eps)) * 0.5 + t3 = ( + ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2)) + / (4 * ((a1 * b1 - c1.pow(2)).clamp_(0) * (a2 * b2 - c2.pow(2)).clamp_(0)).sqrt() + eps) + + eps + ).log() * 0.5 + bd = (t1 + t2 + t3).clamp(eps, 100.0) + hd = (1.0 - (-bd).exp() + eps).sqrt() + return 1 - hd + + +def smooth_bce(eps: float = 0.1) -> tuple[float, float]: + """Compute smoothed positive and negative Binary Cross-Entropy targets. + + Args: + eps (float, optional): The epsilon value for label smoothing. + + Returns: + pos (float): Positive label smoothing BCE target. + neg (float): Negative label smoothing BCE target. + + References: + https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441 + """ + return 1.0 - 0.5 * eps, 0.5 * eps + + +class ConfusionMatrix(DataExportMixin): + """A class for calculating and updating a confusion matrix for object detection and classification tasks. + + Attributes: + task (str): The type of task, one of 'detect', 'classify', 'semantic', or 'obb'. + matrix (np.ndarray): The confusion matrix, with dimensions depending on the task. + nc (int): The number of classes. + names (dict[int, str]): The names of the classes, used as labels on the plot. + matches (dict | None): Contains the indices of ground truths and predictions categorized into TP, FP and FN. + """ + + def __init__(self, names: dict[int, str] | None = None, task: str = "detect", save_matches: bool = False): + """Initialize a ConfusionMatrix instance. + + Args: + names (dict[int, str], optional): Names of classes, used as labels on the plot. + task (str, optional): Type of task, one of 'detect', 'classify', 'semantic', or 'obb'. + save_matches (bool, optional): Save the indices of GTs, TPs, FPs, FNs for visualization. + """ + names = names if names is not None else {} + self.task = task + self.nc = len(names) # number of classes + self.matrix = ( + np.zeros((self.nc, self.nc)) + if self.task in {"classify", "semantic"} + else np.zeros((self.nc + 1, self.nc + 1)) + ) + self.names = names # name of classes + self.matches = {} if save_matches else None + + def _append_matches(self, mtype: str, batch: dict[str, Any], idx: int) -> None: + """Append the matches to TP, FP, FN or GT list for the last batch. + + This method updates the matches dictionary by appending specific batch data to the appropriate match type (True + Positive, False Positive, or False Negative). + + Args: + mtype (str): Match type identifier ('TP', 'FP', 'FN' or 'GT'). + batch (dict[str, Any]): Batch data containing detection results with keys like 'bboxes', 'cls', 'conf', + 'keypoints', 'masks'. + idx (int): Index of the specific detection to append from the batch. + + Notes: + For masks, handles both overlap and non-overlap cases. When masks.max() > 1.0, it indicates + overlap_mask=True with shape (1, H, W), otherwise uses direct indexing. + """ + if self.matches is None: + return + for k, v in batch.items(): + if k in {"bboxes", "cls", "conf", "keypoints"}: + self.matches[mtype][k] += v[[idx]] + elif k == "masks": + # NOTE: masks.max() > 1.0 means overlap_mask=True with (1, H, W) shape + self.matches[mtype][k] += [v[0] == idx + 1] if v.max() > 1.0 else [v[idx]] + + def process_cls_preds(self, preds: list[torch.Tensor], targets: list[torch.Tensor]) -> None: + """Update confusion matrix for classification task. + + Args: + preds (list[torch.Tensor]): Predicted class labels. + targets (list[torch.Tensor]): Ground truth class labels. + """ + preds, targets = torch.cat(preds)[:, 0], torch.cat(targets) + for p, t in zip(preds.cpu().numpy(), targets.cpu().numpy()): + self.matrix[p][t] += 1 + + def process_batch( + self, + detections: dict[str, torch.Tensor], + batch: dict[str, Any], + conf: float = 0.25, + iou_thres: float = 0.45, + ) -> None: + """Update confusion matrix for object detection task. + + Args: + detections (dict[str, torch.Tensor]): Dictionary containing detected bounding boxes and their associated + information. Should contain 'cls', 'conf', and 'bboxes' keys, where 'bboxes' can be Array[N, 4] for + regular boxes or Array[N, 5] for OBB with angle. + batch (dict[str, Any]): Batch dictionary containing ground truth data with 'bboxes' (Array[M, 4]| Array[M, + 5]) and 'cls' (Array[M]) keys, where M is the number of ground truth objects. + conf (float, optional): Confidence threshold for detections. + iou_thres (float, optional): IoU threshold for matching detections to ground truth. + """ + gt_cls, gt_bboxes = batch["cls"], batch["bboxes"] + if self.matches is not None: # only if visualization is enabled + self.matches = {k: defaultdict(list) for k in {"TP", "FP", "FN", "GT"}} + for i in range(gt_cls.shape[0]): + self._append_matches("GT", batch, i) # store GT + is_obb = gt_bboxes.shape[1] == 5 # check if boxes contains angle for OBB + conf = 0.25 if conf in {None, 0.01 if is_obb else 0.001} else conf # apply 0.25 if default val conf is passed + no_pred = detections["cls"].shape[0] == 0 + if gt_cls.shape[0] == 0: # Check if labels is empty + if not no_pred: + detections = {k: detections[k][detections["conf"] > conf] for k in detections} + detection_classes = detections["cls"].int().tolist() + for i, dc in enumerate(detection_classes): + self.matrix[dc, self.nc] += 1 # FP + self._append_matches("FP", detections, i) + return + if no_pred: + gt_classes = gt_cls.int().tolist() + for i, gc in enumerate(gt_classes): + self.matrix[self.nc, gc] += 1 # FN + self._append_matches("FN", batch, i) + return + + detections = {k: detections[k][detections["conf"] > conf] for k in detections} + gt_classes = gt_cls.int().tolist() + detection_classes = detections["cls"].int().tolist() + bboxes = detections["bboxes"] + iou = batch_probiou(gt_bboxes, bboxes) if is_obb else box_iou(gt_bboxes, bboxes) + + x = torch.where(iou > iou_thres) + if x[0].shape[0]: + matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy() + if x[0].shape[0] > 1: + matches = matches[matches[:, 2].argsort()[::-1]] + matches = matches[np.unique(matches[:, 1], return_index=True)[1]] + matches = matches[matches[:, 2].argsort()[::-1]] + matches = matches[np.unique(matches[:, 0], return_index=True)[1]] + else: + matches = np.zeros((0, 3)) + + n = matches.shape[0] > 0 + m0, m1, _ = matches.transpose().astype(int) + for i, gc in enumerate(gt_classes): + j = m0 == i + if n and sum(j) == 1: + dc = detection_classes[m1[j].item()] + self.matrix[dc, gc] += 1 # TP if class is correct else both an FP and an FN + if dc == gc: + self._append_matches("TP", detections, m1[j].item()) + else: + self._append_matches("FP", detections, m1[j].item()) + self._append_matches("FN", batch, i) + else: + self.matrix[self.nc, gc] += 1 # FN + self._append_matches("FN", batch, i) + + for i, dc in enumerate(detection_classes): + if not any(m1 == i): + self.matrix[dc, self.nc] += 1 # FP + self._append_matches("FP", detections, i) + + def matrix(self): + """Return the confusion matrix.""" + return self.matrix + + def tp_fp(self) -> tuple[np.ndarray, np.ndarray]: + """Return true positives and false positives. + + Returns: + tp (np.ndarray): True positives. + fp (np.ndarray): False positives. + """ + tp = self.matrix.diagonal() # true positives + fp = self.matrix.sum(1) - tp # false positives + # fn = self.matrix.sum(0) - tp # false negatives (missed detections) + return (tp, fp) if self.task == "classify" else (tp[:-1], fp[:-1]) # remove background class if task=detect + + def plot_matches( + self, img: torch.Tensor, im_file: str, save_dir: Path, show_labels: bool = True, show_conf: bool = True + ) -> None: + """Plot grid of GT, TP, FP, FN for each image. + + Args: + img (torch.Tensor): Image to plot onto. + im_file (str): Image filename to save visualizations. + save_dir (Path): Location to save the visualizations to. + show_labels (bool): Whether to display class labels in the visualization. + show_conf (bool): Whether to display confidence values in the visualization. + """ + if not self.matches: + return + from .ops import xyxy2xywh + from .plotting import plot_images + + # Create batch of 4 (GT, TP, FP, FN) + labels = defaultdict(list) + for i, mtype in enumerate(["GT", "FP", "TP", "FN"]): + mbatch = self.matches[mtype] + if "conf" not in mbatch: + mbatch["conf"] = torch.tensor([1.0] * len(mbatch["bboxes"]), device=img.device) + mbatch["batch_idx"] = torch.ones(len(mbatch["bboxes"]), device=img.device) * i + for k in mbatch: + labels[k] += mbatch[k] + + labels = {k: torch.stack(v, 0) if len(v) else torch.empty(0) for k, v in labels.items()} + if self.task != "obb" and labels["bboxes"].shape[0]: + labels["bboxes"] = xyxy2xywh(labels["bboxes"]) + (save_dir / "visualizations").mkdir(parents=True, exist_ok=True) + plot_images( + labels, + img.repeat(4, 1, 1, 1), + paths=["Ground Truth", "False Positives", "True Positives", "False Negatives"], + fname=save_dir / "visualizations" / Path(im_file).name, + names=self.names, + max_subplots=4, + conf_thres=0.001, + show_labels=show_labels, + show_conf=show_conf, + ) + + @TryExcept(msg="ConfusionMatrix plot failure") + @plt_settings() + def plot(self, normalize: bool = True, save_dir: str = "", on_plot=None): + """Plot the confusion matrix using matplotlib and save it to a file. + + Args: + normalize (bool, optional): Whether to normalize the confusion matrix. + save_dir (str, optional): Directory where the plot will be saved. + on_plot (callable, optional): An optional callback to pass plots path and data when they are rendered. + """ + import matplotlib.pyplot as plt # scope for faster 'import ultralytics' + + array = self.matrix / ((self.matrix.sum(0).reshape(1, -1) + 1e-9) if normalize else 1) # normalize columns + array[array < 0.005] = np.nan # don't annotate (would appear as 0.00) + + fig, ax = plt.subplots(1, 1, figsize=(12, 9)) + names, n = list(self.names.values()), self.nc + if self.nc >= 100: # downsample for large class count + k = max(2, self.nc // 60) # step size for downsampling, always > 1 + keep_idx = slice(None, None, k) # create slice instead of array + names = names[keep_idx] # slice class names + array = array[keep_idx, :][:, keep_idx] # slice matrix rows and cols + n = (self.nc + k - 1) // k # number of retained classes + nc = n if self.task == "classify" else n + 1 # adjust for background if needed + ticklabels = "auto" + if 0 < nc < 99: + ticklabels = names if self.task in {"classify", "semantic"} else [*names, "background"] + xy_ticks = np.arange(len(ticklabels)) if ticklabels != "auto" else np.arange(nc) + tick_fontsize = max(6, 15 - 0.1 * nc) # Minimum size is 6 + label_fontsize = max(6, 12 - 0.1 * nc) + title_fontsize = max(6, 12 - 0.1 * nc) + btm = max(0.1, 0.25 - 0.001 * nc) # Minimum value is 0.1 + with warnings.catch_warnings(): + warnings.simplefilter("ignore") # suppress empty matrix RuntimeWarning: All-NaN slice encountered + im = ax.imshow(array, cmap="Blues", vmin=0.0, interpolation="none") + ax.xaxis.set_label_position("bottom") + if nc < 30: # Add score for each cell of confusion matrix + color_threshold = 0.45 * (1 if normalize else np.nanmax(array)) # text color threshold + for i, row in enumerate(array[:nc]): + for j, val in enumerate(row[:nc]): + val = array[i, j] + if np.isnan(val): + continue + ax.text( + j, + i, + f"{val:.2f}" if normalize else f"{int(val)}", + ha="center", + va="center", + fontsize=10, + color="white" if val > color_threshold else "black", + ) + cbar = fig.colorbar(im, ax=ax, fraction=0.046, pad=0.05) + title = "Confusion Matrix" + " Normalized" * normalize + ax.set_xlabel("True", fontsize=label_fontsize, labelpad=10) + ax.set_ylabel("Predicted", fontsize=label_fontsize, labelpad=10) + ax.set_title(title, fontsize=title_fontsize, pad=20) + ax.set_xticks(xy_ticks) + ax.set_yticks(xy_ticks) + ax.tick_params(axis="x", bottom=True, top=False, labelbottom=True, labeltop=False) + ax.tick_params(axis="y", left=True, right=False, labelleft=True, labelright=False) + if ticklabels != "auto": + ax.set_xticklabels(ticklabels, fontsize=tick_fontsize, rotation=90, ha="center") + ax.set_yticklabels(ticklabels, fontsize=tick_fontsize) + for s in {"left", "right", "bottom", "top", "outline"}: + if s != "outline": + ax.spines[s].set_visible(False) # Confusion matrix plot don't have outline + cbar.ax.spines[s].set_visible(False) + fig.subplots_adjust(left=0, right=0.84, top=0.94, bottom=btm) # Adjust layout to ensure equal margins + plot_fname = Path(save_dir) / f"{title.lower().replace(' ', '_')}.png" + fig.savefig(plot_fname, dpi=250) + plt.close(fig) + if on_plot: + on_plot(plot_fname, {"type": "confusion_matrix", "matrix": self.matrix.tolist()}) + + def print(self): + """Print the confusion matrix to the console.""" + for i in range(self.matrix.shape[0]): + LOGGER.info(" ".join(map(str, self.matrix[i]))) + + def summary(self, normalize: bool = False, decimals: int = 5) -> list[dict[str, float]]: + """Generate a summarized representation of the confusion matrix as a list of dictionaries, with optional + normalization. This is useful for exporting the matrix to various formats such as CSV, XML, HTML, JSON, + or SQL. + + Args: + normalize (bool): Whether to normalize the confusion matrix values. + decimals (int): Number of decimal places to round the output values to. + + Returns: + (list[dict[str, float]]): A list of dictionaries, each representing one predicted class with corresponding + values for all actual classes. + + Examples: + >>> results = model.val(data="coco8.yaml", plots=True) + >>> cm_dict = results.confusion_matrix.summary(normalize=True, decimals=5) + >>> print(cm_dict) + """ + import re + + names = ( + list(self.names.values()) + if self.task in {"classify", "semantic"} + else [*list(self.names.values()), "background"] + ) + clean_names, seen = [], set() + for name in names: + clean_name = re.sub(r"[^a-zA-Z0-9_]", "_", name) + original_clean = clean_name + counter = 1 + while clean_name.lower() in seen: + clean_name = f"{original_clean}_{counter}" + counter += 1 + seen.add(clean_name.lower()) + clean_names.append(clean_name) + array = (self.matrix / ((self.matrix.sum(0).reshape(1, -1) + 1e-9) if normalize else 1)).round(decimals) + return [ + dict({"Predicted": clean_names[i]}, **{clean_names[j]: array[i, j] for j in range(len(clean_names))}) + for i in range(len(clean_names)) + ] + + +def smooth(y: np.ndarray, f: float = 0.05) -> np.ndarray: + """Box filter of fraction f.""" + nf = round(len(y) * f * 2) // 2 + 1 # number of filter elements (must be odd) + p = np.ones(nf // 2) # ones padding + yp = np.concatenate((p * y[0], y, p * y[-1]), 0) # y padded + return np.convolve(yp, np.ones(nf) / nf, mode="valid") # y-smoothed + + +@plt_settings() +def plot_pr_curve( + px: np.ndarray, + py: np.ndarray, + ap: np.ndarray, + save_dir: Path = Path("pr_curve.png"), + names: dict[int, str] | None = None, + on_plot=None, +): + """Plot precision-recall curve. + + Args: + px (np.ndarray): X values for the PR curve. + py (np.ndarray): Y values for the PR curve. + ap (np.ndarray): Average precision values. + save_dir (Path, optional): Path to save the plot. + names (dict[int, str], optional): Dictionary mapping class indices to class names. + on_plot (callable, optional): Function to call after plot is saved. + """ + import matplotlib.pyplot as plt # scope for faster 'import ultralytics' + + names = names if names is not None else {} + fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True) + py = np.stack(py, axis=1) + + if 0 < len(names) < 21: # display per-class legend if < 21 classes + for i, y in enumerate(py.T): + ax.plot(px, y, linewidth=1, label=f"{names[i]} {ap[i, 0]:.3f}") # plot(recall, precision) + else: + ax.plot(px, py, linewidth=1, color="gray") # plot(recall, precision) + + ax.plot(px, py.mean(1), linewidth=3, color="blue", label=f"all classes {ap[:, 0].mean():.3f} mAP@0.5") + ax.set_xlabel("Recall") + ax.set_ylabel("Precision") + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + ax.legend(bbox_to_anchor=(1.04, 1), loc="upper left") + ax.set_title("Precision-Recall Curve") + fig.savefig(save_dir, dpi=250) + plt.close(fig) + if on_plot: + # Pass PR curve data for interactive plotting (class names stored at model level) + # Transpose py to match other curves: y[class][point] format + on_plot(save_dir, {"type": "pr_curve", "x": px.tolist(), "y": py.T.tolist(), "ap": ap.tolist()}) + + +@plt_settings() +def plot_mc_curve( + px: np.ndarray, + py: np.ndarray, + save_dir: Path = Path("mc_curve.png"), + names: dict[int, str] | None = None, + xlabel: str = "Confidence", + ylabel: str = "Metric", + on_plot=None, +): + """Plot metric-confidence curve. + + Args: + px (np.ndarray): X values for the metric-confidence curve. + py (np.ndarray): Y values for the metric-confidence curve. + save_dir (Path, optional): Path to save the plot. + names (dict[int, str], optional): Dictionary mapping class indices to class names. + xlabel (str, optional): X-axis label. + ylabel (str, optional): Y-axis label. + on_plot (callable, optional): Function to call after plot is saved. + """ + import matplotlib.pyplot as plt # scope for faster 'import ultralytics' + + names = names if names is not None else {} + fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True) + + if 0 < len(names) < 21: # display per-class legend if < 21 classes + for i, y in enumerate(py): + ax.plot(px, y, linewidth=1, label=f"{names[i]}") # plot(confidence, metric) + else: + ax.plot(px, py.T, linewidth=1, color="gray") # plot(confidence, metric) + + y = smooth(py.mean(0), 0.1) + ax.plot(px, y, linewidth=3, color="blue", label=f"all classes {y.max():.2f} at {px[y.argmax()]:.3f}") + ax.set_xlabel(xlabel) + ax.set_ylabel(ylabel) + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + ax.legend(bbox_to_anchor=(1.04, 1), loc="upper left") + ax.set_title(f"{ylabel}-Confidence Curve") + fig.savefig(save_dir, dpi=250) + plt.close(fig) + if on_plot: + # Pass metric-confidence curve data for interactive plotting (class names stored at model level) + on_plot(save_dir, {"type": f"{ylabel.lower()}_curve", "x": px.tolist(), "y": py.tolist()}) + + +def compute_ap(recall: list[float], precision: list[float]) -> tuple[float, np.ndarray, np.ndarray]: + """Compute the average precision (AP) given the recall and precision curves. + + Args: + recall (list[float]): The recall curve. + precision (list[float]): The precision curve. + + Returns: + ap (float): Average precision. + mpre (np.ndarray): Precision envelope curve. + mrec (np.ndarray): Modified recall curve with sentinel values added at the beginning and end. + """ + # Append sentinel values to beginning and end + mrec = np.concatenate(([0.0], recall, [recall[-1] if len(recall) else 1.0], [1.0])) + mpre = np.concatenate(([1.0], precision, [0.0], [0.0])) + + # Compute the precision envelope + mpre = np.flip(np.maximum.accumulate(np.flip(mpre))) + + # Integrate area under curve + method = "interp" # methods: 'continuous', 'interp' + if method == "interp": + x = np.linspace(0, 1, 101) # 101-point interp (COCO) + func = np.trapezoid if checks.check_version(np.__version__, ">=2.0") else np.trapz # np.trapz deprecated + ap = func(np.interp(x, mrec, mpre), x) # integrate + else: # 'continuous' + i = np.where(mrec[1:] != mrec[:-1])[0] # points where x-axis (recall) changes + ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve + + return ap, mpre, mrec + + +def ap_per_class( + tp: np.ndarray, + conf: np.ndarray, + pred_cls: np.ndarray, + target_cls: np.ndarray, + plot: bool = False, + on_plot=None, + save_dir: Path = Path(), + names: dict[int, str] | None = None, + eps: float = 1e-16, + prefix: str = "", +) -> tuple: + """Compute the average precision per class for object detection evaluation. + + Args: + tp (np.ndarray): Binary array indicating whether the detection is correct (True) or not (False). + conf (np.ndarray): Array of confidence scores of the detections. + pred_cls (np.ndarray): Array of predicted classes of the detections. + target_cls (np.ndarray): Array of true classes of the targets. + plot (bool, optional): Whether to plot PR curves or not. + on_plot (callable, optional): A callback to pass plots path and data when they are rendered. + save_dir (Path, optional): Directory to save the PR curves. + names (dict[int, str], optional): Dictionary of class names to plot PR curves. + eps (float, optional): A small value to avoid division by zero. + prefix (str, optional): A prefix string for saving the plot files. + + Returns: + tp (np.ndarray): True positive counts at threshold given by max F1 metric for each class. + fp (np.ndarray): False positive counts at threshold given by max F1 metric for each class. + p (np.ndarray): Precision values at threshold given by max F1 metric for each class. + r (np.ndarray): Recall values at threshold given by max F1 metric for each class. + f1 (np.ndarray): F1-score values at threshold given by max F1 metric for each class. + ap (np.ndarray): Average precision for each class at different IoU thresholds. + unique_classes (np.ndarray): An array of unique classes that have data. + p_curve (np.ndarray): Precision curves for each class. + r_curve (np.ndarray): Recall curves for each class. + f1_curve (np.ndarray): F1-score curves for each class. + x (np.ndarray): X-axis values for the curves. + prec_values (np.ndarray): Precision values at mAP@0.5 for each class. + """ + names = names if names is not None else {} + # Sort by objectness + i = np.argsort(-conf) + tp, conf, pred_cls = tp[i], conf[i], pred_cls[i] + + # Find unique classes + unique_classes, nt = np.unique(target_cls, return_counts=True) + nc = unique_classes.shape[0] # number of classes, number of detections + + # Create Precision-Recall curve and compute AP for each class + x, prec_values = np.linspace(0, 1, 1000), [] + + # Average precision, precision and recall curves + ap, p_curve, r_curve = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000)) + for ci, c in enumerate(unique_classes): + i = pred_cls == c + n_l = nt[ci] # number of labels + n_p = i.sum() # number of predictions + if n_p == 0 or n_l == 0: + continue + + # Accumulate FPs and TPs + fpc = (1 - tp[i]).cumsum(0) + tpc = tp[i].cumsum(0) + + # Recall + recall = tpc / (n_l + eps) # recall curve + r_curve[ci] = np.interp(-x, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases + + # Precision + precision = tpc / (tpc + fpc) # precision curve + p_curve[ci] = np.interp(-x, -conf[i], precision[:, 0], left=1) # p at pr_score + + # AP from recall-precision curve + for j in range(tp.shape[1]): + ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j]) + if j == 0: + prec_values.append(np.interp(x, mrec, mpre)) # precision at mAP@0.5 + + prec_values = np.array(prec_values) if prec_values else np.zeros((1, 1000)) # (nc, 1000) + + # Compute F1 (harmonic mean of precision and recall) + f1_curve = 2 * p_curve * r_curve / (p_curve + r_curve + eps) + names = {i: names[k] for i, k in enumerate(unique_classes) if k in names} # dict: only classes that have data + if plot: + plot_pr_curve(x, prec_values, ap, save_dir / f"{prefix}PR_curve.png", names, on_plot=on_plot) + plot_mc_curve(x, f1_curve, save_dir / f"{prefix}F1_curve.png", names, ylabel="F1", on_plot=on_plot) + plot_mc_curve(x, p_curve, save_dir / f"{prefix}P_curve.png", names, ylabel="Precision", on_plot=on_plot) + plot_mc_curve(x, r_curve, save_dir / f"{prefix}R_curve.png", names, ylabel="Recall", on_plot=on_plot) + + i = smooth(f1_curve.mean(0), 0.1).argmax() # max F1 index + p, r, f1 = p_curve[:, i], r_curve[:, i], f1_curve[:, i] # max-F1 precision, recall, F1 values + tp = (r * nt).round() # true positives + fp = (tp / (p + eps) - tp).round() # false positives + return tp, fp, p, r, f1, ap, unique_classes.astype(int), p_curve, r_curve, f1_curve, x, prec_values + + +class Metric(SimpleClass): + """Class for computing evaluation metrics for Ultralytics YOLO models. + + Attributes: + p (list): Precision for each class. Shape: (nc,). + r (list): Recall for each class. Shape: (nc,). + f1 (list): F1 score for each class. Shape: (nc,). + all_ap (list): AP scores for all classes and all IoU thresholds. Shape: (nc, 10). + ap_class_index (list): Index of class for each AP score. Shape: (nc,). + nc (int): Number of classes. + + Methods: + ap50: AP at IoU threshold of 0.5 for all classes. + ap: AP at IoU thresholds from 0.5 to 0.95 for all classes. + mp: Mean precision of all classes. + mr: Mean recall of all classes. + map50: Mean AP at IoU threshold of 0.5 for all classes. + map75: Mean AP at IoU threshold of 0.75 for all classes. + map: Mean AP at IoU thresholds from 0.5 to 0.95 for all classes. + mean_results: Mean of results, returns mp, mr, map50, map. + class_result: Class-aware result, returns p[i], r[i], ap50[i], ap[i]. + maps: mAP of each class. + fitness: Model fitness as a weighted combination of metrics. + update: Update metric attributes with new evaluation results. + curves: Provides a list of curves for accessing specific metrics like precision, recall, F1, etc. + curves_results: Provide a list of results for accessing specific metrics like precision, recall, F1, etc. + """ + + def __init__(self) -> None: + """Initialize a Metric instance for computing evaluation metrics for the YOLO model.""" + self.p = [] # (nc, ) + self.r = [] # (nc, ) + self.f1 = [] # (nc, ) + self.all_ap = [] # (nc, 10) + self.ap_class_index = [] # (nc, ) + self.nc = 0 + self.image_metrics = {} + + @property + def ap50(self) -> np.ndarray | list: + """Return the Average Precision (AP) at an IoU threshold of 0.5 for all classes. + + Returns: + (np.ndarray | list): Array of shape (nc,) with AP50 values per class, or an empty list if not available. + """ + return self.all_ap[:, 0] if len(self.all_ap) else [] + + @property + def ap(self) -> np.ndarray | list: + """Return the Average Precision (AP) at an IoU threshold of 0.5-0.95 for all classes. + + Returns: + (np.ndarray | list): Array of shape (nc,) with AP50-95 values per class, or an empty list if not available. + """ + return self.all_ap.mean(1) if len(self.all_ap) else [] + + @property + def mp(self) -> float: + """Return the Mean Precision of all classes. + + Returns: + (float): The mean precision of all classes. + """ + return self.p.mean() if len(self.p) else 0.0 + + @property + def mr(self) -> float: + """Return the Mean Recall of all classes. + + Returns: + (float): The mean recall of all classes. + """ + return self.r.mean() if len(self.r) else 0.0 + + @property + def map50(self) -> float: + """Return the mean Average Precision (mAP) at an IoU threshold of 0.5. + + Returns: + (float): The mAP at an IoU threshold of 0.5. + """ + return self.all_ap[:, 0].mean() if len(self.all_ap) else 0.0 + + @property + def map75(self) -> float: + """Return the mean Average Precision (mAP) at an IoU threshold of 0.75. + + Returns: + (float): The mAP at an IoU threshold of 0.75. + """ + return self.all_ap[:, 5].mean() if len(self.all_ap) else 0.0 + + @property + def map(self) -> float: + """Return the mean Average Precision (mAP) over IoU thresholds of 0.5 - 0.95 in steps of 0.05. + + Returns: + (float): The mAP over IoU thresholds of 0.5 - 0.95 in steps of 0.05. + """ + return self.all_ap.mean() if len(self.all_ap) else 0.0 + + def mean_results(self) -> list[float]: + """Return mean of results, mp, mr, map50, map.""" + return [self.mp, self.mr, self.map50, self.map] + + def class_result(self, i: int) -> tuple[float, float, float, float]: + """Return class-aware result, p[i], r[i], ap50[i], ap[i].""" + return self.p[i], self.r[i], self.ap50[i], self.ap[i] + + @property + def maps(self) -> np.ndarray: + """Return mAP of each class.""" + maps = np.zeros(self.nc) + self.map + for i, c in enumerate(self.ap_class_index): + maps[c] = self.ap[i] + return maps + + def fitness(self) -> float: + """Return model fitness as a weighted combination of metrics.""" + w = [0.0, 0.0, 0.0, 1.0] # weights for [P, R, mAP@0.5, mAP@0.5:0.95] + return float((np.nan_to_num(np.array(self.mean_results())) * w).sum()) + + def update(self, results: tuple): + """Update the evaluation metrics with a new set of results. + + Args: + results (tuple): A tuple containing evaluation metrics: + - p (list): Precision for each class. + - r (list): Recall for each class. + - f1 (list): F1 score for each class. + - all_ap (list): AP scores for all classes and all IoU thresholds. + - ap_class_index (list): Index of class for each AP score. + - p_curve (list): Precision curve for each class. + - r_curve (list): Recall curve for each class. + - f1_curve (list): F1 curve for each class. + - px (list): X values for the curves. + - prec_values (list): Precision values for each class. + """ + ( + self.p, + self.r, + self.f1, + self.all_ap, + self.ap_class_index, + self.p_curve, + self.r_curve, + self.f1_curve, + self.px, + self.prec_values, + ) = results + + def clear_image_metrics(self) -> None: + """Clear stored per-image metrics from the current validation run.""" + self.image_metrics.clear() + + @property + def curves(self) -> list: + """Return a list of curves for accessing specific metrics curves.""" + return [] + + @property + def curves_results(self) -> list[list]: + """Return a list of curves results for accessing specific metrics curves.""" + return [ + [self.px, self.prec_values, "Recall", "Precision"], + [self.px, self.f1_curve, "Confidence", "F1"], + [self.px, self.p_curve, "Confidence", "Precision"], + [self.px, self.r_curve, "Confidence", "Recall"], + ] + + def update_image_metrics(self, tp: np.ndarray, target_cls: np.ndarray, pred_cls: np.ndarray, im_name: str) -> None: + """Update per-image precision, recall, F1, TP, FP, and FN at IoU threshold 0.5. + + Args: + tp (np.ndarray): True positive array of shape (num_preds, num_iou_thresholds), where the first column (IoU + >= 0.5) is used. + target_cls (np.ndarray): Ground truth class labels for the image. + pred_cls (np.ndarray): Predicted class labels for the image. + im_name (str): The image filename used as the per-image key. + """ + # Use the default IoU=0.5 column to match the validator's image-level matching policy. + tp = int(tp[:, 0].sum()) + num_preds = pred_cls.shape[0] + num_targets = target_cls.shape[0] + fp = num_preds - tp + fn = num_targets - tp + if num_preds == 0 and num_targets == 0: + # Empty-GT image with no predictions is a trivially correct call, so report a perfect score rather than + # zeroing out P/R/F1 by the standard 0/0 fallback below. + precision = recall = f1 = 1.0 + else: + precision = tp / num_preds if num_preds else 0.0 + recall = tp / num_targets if num_targets else 0.0 + denom = precision + recall + f1 = 2 * precision * recall / denom if denom else 0.0 + self.image_metrics[im_name] = { + "precision": float(precision), + "recall": float(recall), + "f1": float(f1), + "tp": int(tp), + "fp": int(fp), + "fn": int(fn), + } + + +class DetMetrics(SimpleClass, DataExportMixin): + """Utility class for computing detection metrics such as precision, recall, and mean average precision (mAP). + + Attributes: + names (dict[int, str]): A dictionary of class names. + box (Metric): An instance of the Metric class for storing detection results. + speed (dict[str, float]): A dictionary for storing execution times of different parts of the detection process. + stats (dict[str, list]): A dictionary containing lists for true positives, confidence scores, predicted classes, + target classes, and target images. + nt_per_class: Number of targets per class. + nt_per_image: Number of targets per image. + + Methods: + update_stats: Update statistics by appending new values to existing stat collections. + process: Process predicted results for object detection and update metrics. + clear_stats: Clear the stored statistics. + keys: Return a list of keys for accessing specific metrics. + mean_results: Calculate mean of detected objects & return precision, recall, mAP50, and mAP50-95. + class_result: Return the result of evaluating the performance of an object detection model on a specific class. + maps: Return mean Average Precision (mAP) scores per class. + fitness: Return the fitness of box object. + ap_class_index: Return the average precision index per class. + results_dict: Return dictionary of computed performance metrics and statistics. + curves: Return a list of curves for accessing specific metrics curves. + curves_results: Return a list of computed performance metrics and statistics. + summary: Generate a summarized representation of per-class detection metrics as a list of dictionaries. + """ + + def __init__(self, names: dict[int, str] | None = None) -> None: + """Initialize a DetMetrics instance with class names. + + Args: + names (dict[int, str], optional): Dictionary of class names. + """ + self.names = names if names is not None else {} + self.box = Metric() + self.speed = {"preprocess": 0.0, "inference": 0.0, "loss": 0.0, "postprocess": 0.0} + self.stats = dict(tp=[], conf=[], pred_cls=[], target_cls=[], target_img=[]) + self.nt_per_class = None + self.nt_per_image = None + + def update_stats(self, stat: dict[str, Any]) -> None: + """Update statistics by appending new values to existing stat collections. + + Args: + stat (dict[str, Any]): Dictionary containing new statistical values to append. Keys should match existing + keys in self.stats. + """ + for k in self.stats.keys(): + self.stats[k].append(stat[k]) + self.box.update_image_metrics(stat["tp"], stat["target_cls"], stat["pred_cls"], stat["im_name"]) + + def process(self, save_dir: Path = Path("."), plot: bool = False, on_plot=None) -> dict[str, np.ndarray]: + """Process predicted results for object detection and update metrics. + + Args: + save_dir (Path): Directory to save plots. Defaults to Path("."). + plot (bool): Whether to plot precision-recall curves. Defaults to False. + on_plot (callable, optional): Function to call after plots are generated. Defaults to None. + + Returns: + (dict[str, np.ndarray]): Dictionary containing concatenated statistics arrays. + """ + stats = {k: np.concatenate(v, 0) for k, v in self.stats.items()} # to numpy + if not stats: + return stats + results = ap_per_class( + stats["tp"], + stats["conf"], + stats["pred_cls"], + stats["target_cls"], + plot=plot, + save_dir=save_dir, + names=self.names, + on_plot=on_plot, + prefix="Box", + )[2:] + self.box.nc = len(self.names) + self.box.update(results) + self.nt_per_class = np.bincount(stats["target_cls"].astype(int), minlength=len(self.names)) + self.nt_per_image = np.bincount(stats["target_img"].astype(int), minlength=len(self.names)) + return stats + + def clear_stats(self): + """Clear the stored statistics.""" + for v in self.stats.values(): + v.clear() + + def clear_image_metrics(self) -> None: + """Clear stored per-image metrics.""" + self.box.clear_image_metrics() + + @property + def keys(self) -> list[str]: + """Return a list of keys for accessing specific metrics.""" + return ["metrics/precision(B)", "metrics/recall(B)", "metrics/mAP50(B)", "metrics/mAP50-95(B)"] + + def mean_results(self) -> list[float]: + """Calculate mean of detected objects & return precision, recall, mAP50, and mAP50-95.""" + return self.box.mean_results() + + def class_result(self, i: int) -> tuple[float, float, float, float]: + """Return the result of evaluating the performance of an object detection model on a specific class.""" + return self.box.class_result(i) + + @property + def maps(self) -> np.ndarray: + """Return mean Average Precision (mAP) scores per class.""" + return self.box.maps + + @property + def fitness(self) -> float: + """Return the fitness of box object.""" + return self.box.fitness() + + @property + def ap_class_index(self) -> list: + """Return the average precision index per class.""" + return self.box.ap_class_index + + @property + def results_dict(self) -> dict[str, float]: + """Return dictionary of computed performance metrics and statistics.""" + keys = [*self.keys, "fitness"] + values = ((float(x) if hasattr(x, "item") else x) for x in ([*self.mean_results(), self.fitness])) + return dict(zip(keys, values)) + + @property + def curves(self) -> list[str]: + """Return a list of curves for accessing specific metrics curves.""" + return ["Precision-Recall(B)", "F1-Confidence(B)", "Precision-Confidence(B)", "Recall-Confidence(B)"] + + @property + def curves_results(self) -> list[list]: + """Return a list of computed performance metrics and statistics.""" + return self.box.curves_results + + def summary(self, normalize: bool = True, decimals: int = 5) -> list[dict[str, Any]]: + """Generate a summarized representation of per-class detection metrics as a list of dictionaries. Includes + shared scalar metrics (mAP, mAP50, mAP75) alongside precision, recall, and F1-score for each class. + + Args: + normalize (bool): For Detect metrics, everything is normalized by default [0-1]. + decimals (int): Number of decimal places to round the metrics values to. + + Returns: + (list[dict[str, Any]]): A list of dictionaries, each representing one class with corresponding metric + values. + + Examples: + >>> results = model.val(data="coco8.yaml") + >>> detection_summary = results.summary() + >>> print(detection_summary) + """ + per_class = { + "Box-P": self.box.p, + "Box-R": self.box.r, + "Box-F1": self.box.f1, + } + return [ + { + "Class": self.names[self.ap_class_index[i]], + "Images": self.nt_per_image[self.ap_class_index[i]], + "Instances": self.nt_per_class[self.ap_class_index[i]], + **{k: round(v[i], decimals) for k, v in per_class.items()}, + "mAP50": round(self.class_result(i)[2], decimals), + "mAP50-95": round(self.class_result(i)[3], decimals), + } + for i in range(len(per_class["Box-P"])) + ] + + +class SegmentMetrics(DetMetrics): + """Calculate and aggregate detection and segmentation metrics over a given set of classes. + + Attributes: + names (dict[int, str]): Dictionary of class names. + box (Metric): An instance of the Metric class for storing detection results. + seg (Metric): An instance of the Metric class to calculate mask segmentation metrics. + speed (dict[str, float]): A dictionary for storing execution times of different parts of the detection process. + stats (dict[str, list]): A dictionary containing lists for true positives, confidence scores, predicted classes, + target classes, and target images. + nt_per_class: Number of targets per class. + nt_per_image: Number of targets per image. + + Methods: + process: Process the detection and segmentation metrics over the given set of predictions. + keys: Return a list of keys for accessing metrics. + mean_results: Return the mean metrics for bounding box and segmentation results. + class_result: Return classification results for a specified class index. + maps: Return mAP scores for object detection and segmentation models. + fitness: Return the fitness score for both segmentation and bounding box models. + curves: Return a list of curves for accessing specific metrics curves. + curves_results: Provide a list of computed performance metrics and statistics. + summary: Generate a summarized representation of per-class segmentation metrics as a list of dictionaries. + """ + + def __init__(self, names: dict[int, str] | None = None) -> None: + """Initialize a SegmentMetrics instance with class names. + + Args: + names (dict[int, str], optional): Dictionary of class names. + """ + DetMetrics.__init__(self, names) + self.seg = Metric() + self.stats["tp_m"] = [] # add additional stats for masks + + def update_stats(self, stat: dict[str, Any]) -> None: + """Update statistics by appending new values to existing stat collections. + + Args: + stat (dict[str, Any]): Dictionary containing new statistical values to append. Keys should match existing + keys in self.stats. + """ + super().update_stats(stat) # update box stats + self.seg.update_image_metrics(stat["tp_m"], stat["target_cls"], stat["pred_cls"], stat["im_name"]) + + def clear_image_metrics(self) -> None: + """Clear stored per-image metrics.""" + super().clear_image_metrics() + self.seg.clear_image_metrics() + + def process(self, save_dir: Path = Path("."), plot: bool = False, on_plot=None) -> dict[str, np.ndarray]: + """Process the detection and segmentation metrics over the given set of predictions. + + Args: + save_dir (Path): Directory to save plots. Defaults to Path("."). + plot (bool): Whether to plot precision-recall curves. Defaults to False. + on_plot (callable, optional): Function to call after plots are generated. Defaults to None. + + Returns: + (dict[str, np.ndarray]): Dictionary containing concatenated statistics arrays. + """ + stats = DetMetrics.process(self, save_dir, plot, on_plot=on_plot) # process box stats + results_mask = ap_per_class( + stats["tp_m"], + stats["conf"], + stats["pred_cls"], + stats["target_cls"], + plot=plot, + on_plot=on_plot, + save_dir=save_dir, + names=self.names, + prefix="Mask", + )[2:] + self.seg.nc = len(self.names) + self.seg.update(results_mask) + return stats + + @property + def keys(self) -> list[str]: + """Return a list of keys for accessing metrics.""" + return [ + *DetMetrics.keys.fget(self), + "metrics/precision(M)", + "metrics/recall(M)", + "metrics/mAP50(M)", + "metrics/mAP50-95(M)", + ] + + def mean_results(self) -> list[float]: + """Return the mean metrics for bounding box and segmentation results.""" + return DetMetrics.mean_results(self) + self.seg.mean_results() + + def class_result(self, i: int) -> list[float]: + """Return classification results for a specified class index.""" + return DetMetrics.class_result(self, i) + self.seg.class_result(i) + + @property + def maps(self) -> np.ndarray: + """Return mAP scores for object detection and segmentation models.""" + return DetMetrics.maps.fget(self) + self.seg.maps + + @property + def fitness(self) -> float: + """Return the fitness score for both segmentation and bounding box models.""" + return self.seg.fitness() + DetMetrics.fitness.fget(self) + + @property + def curves(self) -> list[str]: + """Return a list of curves for accessing specific metrics curves.""" + return [ + *DetMetrics.curves.fget(self), + "Precision-Recall(M)", + "F1-Confidence(M)", + "Precision-Confidence(M)", + "Recall-Confidence(M)", + ] + + @property + def curves_results(self) -> list[list]: + """Return a list of computed performance metrics and statistics.""" + return DetMetrics.curves_results.fget(self) + self.seg.curves_results + + def summary(self, normalize: bool = True, decimals: int = 5) -> list[dict[str, Any]]: + """Generate a summarized representation of per-class segmentation metrics as a list of dictionaries. Includes + both box and mask scalar metrics (mAP, mAP50, mAP75) alongside precision, recall, and F1-score for + each class. + + Args: + normalize (bool): For Segment metrics, everything is normalized by default [0-1]. + decimals (int): Number of decimal places to round the metrics values to. + + Returns: + (list[dict[str, Any]]): A list of dictionaries, each representing one class with corresponding metric + values. + + Examples: + >>> results = model.val(data="coco8-seg.yaml") + >>> seg_summary = results.summary(decimals=4) + >>> print(seg_summary) + """ + per_class = { + "Mask-P": self.seg.p, + "Mask-R": self.seg.r, + "Mask-F1": self.seg.f1, + } + summary = DetMetrics.summary(self, normalize, decimals) # get box summary + for i, s in enumerate(summary): + s.update({**{k: round(v[i], decimals) for k, v in per_class.items()}}) + return summary + + +class PoseMetrics(DetMetrics): + """Calculate and aggregate detection and pose metrics over a given set of classes. + + Attributes: + names (dict[int, str]): Dictionary of class names. + pose (Metric): An instance of the Metric class to calculate pose metrics. + box (Metric): An instance of the Metric class for storing detection results. + speed (dict[str, float]): A dictionary for storing execution times of different parts of the detection process. + stats (dict[str, list]): A dictionary containing lists for true positives, confidence scores, predicted classes, + target classes, and target images. + nt_per_class: Number of targets per class. + nt_per_image: Number of targets per image. + + Methods: + process: Process the detection and pose metrics over the given set of predictions. + keys: Return a list of keys for accessing metrics. + mean_results: Return the mean results of box and pose. + class_result: Return the class-wise detection results for a specific class i. + maps: Return the mean average precision (mAP) per class for both box and pose detections. + fitness: Return combined fitness score for pose and box detection. + curves: Return a list of curves for accessing specific metrics curves. + curves_results: Provide a list of computed performance metrics and statistics. + summary: Generate a summarized representation of per-class pose metrics as a list of dictionaries. + """ + + def __init__(self, names: dict[int, str] | None = None) -> None: + """Initialize the PoseMetrics class with class names. + + Args: + names (dict[int, str], optional): Dictionary of class names. + """ + super().__init__(names) + self.pose = Metric() + self.stats["tp_p"] = [] # add additional stats for pose + + def update_stats(self, stat: dict[str, Any]) -> None: + """Update statistics by appending new values to existing stat collections. + + Args: + stat (dict[str, Any]): Dictionary containing new statistical values to append. Keys should match existing + keys in self.stats. + """ + super().update_stats(stat) # update box stats + self.pose.update_image_metrics(stat["tp_p"], stat["target_cls"], stat["pred_cls"], stat["im_name"]) + + def clear_image_metrics(self) -> None: + """Clear stored per-image metrics.""" + super().clear_image_metrics() + self.pose.clear_image_metrics() + + def process(self, save_dir: Path = Path("."), plot: bool = False, on_plot=None) -> dict[str, np.ndarray]: + """Process the detection and pose metrics over the given set of predictions. + + Args: + save_dir (Path): Directory to save plots. Defaults to Path("."). + plot (bool): Whether to plot precision-recall curves. Defaults to False. + on_plot (callable, optional): Function to call after plots are generated. + + Returns: + (dict[str, np.ndarray]): Dictionary containing concatenated statistics arrays. + """ + stats = DetMetrics.process(self, save_dir, plot, on_plot=on_plot) # process box stats + results_pose = ap_per_class( + stats["tp_p"], + stats["conf"], + stats["pred_cls"], + stats["target_cls"], + plot=plot, + on_plot=on_plot, + save_dir=save_dir, + names=self.names, + prefix="Pose", + )[2:] + self.pose.nc = len(self.names) + self.pose.update(results_pose) + return stats + + @property + def keys(self) -> list[str]: + """Return a list of evaluation metric keys.""" + return [ + *DetMetrics.keys.fget(self), + "metrics/precision(P)", + "metrics/recall(P)", + "metrics/mAP50(P)", + "metrics/mAP50-95(P)", + ] + + def mean_results(self) -> list[float]: + """Return the mean results of box and pose.""" + return DetMetrics.mean_results(self) + self.pose.mean_results() + + def class_result(self, i: int) -> list[float]: + """Return the class-wise detection results for a specific class i.""" + return DetMetrics.class_result(self, i) + self.pose.class_result(i) + + @property + def maps(self) -> np.ndarray: + """Return the mean average precision (mAP) per class for both box and pose detections.""" + return DetMetrics.maps.fget(self) + self.pose.maps + + @property + def fitness(self) -> float: + """Return combined fitness score for pose and box detection.""" + return self.pose.fitness() + DetMetrics.fitness.fget(self) + + @property + def curves(self) -> list[str]: + """Return a list of curves for accessing specific metrics curves.""" + return [ + *DetMetrics.curves.fget(self), + "Precision-Recall(B)", + "F1-Confidence(B)", + "Precision-Confidence(B)", + "Recall-Confidence(B)", + "Precision-Recall(P)", + "F1-Confidence(P)", + "Precision-Confidence(P)", + "Recall-Confidence(P)", + ] + + @property + def curves_results(self) -> list[list]: + """Return a list of computed performance metrics and statistics.""" + return DetMetrics.curves_results.fget(self) + self.pose.curves_results + + def summary(self, normalize: bool = True, decimals: int = 5) -> list[dict[str, Any]]: + """Generate a summarized representation of per-class pose metrics as a list of dictionaries. Includes both box + and pose scalar metrics (mAP, mAP50, mAP75) alongside precision, recall, and F1-score for each class. + + Args: + normalize (bool): For Pose metrics, everything is normalized by default [0-1]. + decimals (int): Number of decimal places to round the metrics values to. + + Returns: + (list[dict[str, Any]]): A list of dictionaries, each representing one class with corresponding metric + values. + + Examples: + >>> results = model.val(data="coco8-pose.yaml") + >>> pose_summary = results.summary(decimals=4) + >>> print(pose_summary) + """ + per_class = { + "Pose-P": self.pose.p, + "Pose-R": self.pose.r, + "Pose-F1": self.pose.f1, + } + summary = DetMetrics.summary(self, normalize, decimals) # get box summary + for i, s in enumerate(summary): + s.update({**{k: round(v[i], decimals) for k, v in per_class.items()}}) + return summary + + +class ClassifyMetrics(SimpleClass, DataExportMixin): + """Class for computing classification metrics including top-1 and top-5 accuracy. + + Attributes: + top1 (float): The top-1 accuracy. + top5 (float): The top-5 accuracy. + speed (dict[str, float]): A dictionary containing the time taken for each step in the pipeline. + + Methods: + process: Process target classes and predicted classes to compute metrics. + fitness: Return mean of top-1 and top-5 accuracies as fitness score. + results_dict: Return a dictionary with model's performance metrics and fitness score. + keys: Return a list of keys for the results_dict property. + curves: Return a list of curves for accessing specific metrics curves. + curves_results: Provide a list of computed performance metrics and statistics. + summary: Generate a single-row summary of classification metrics (Top-1 and Top-5 accuracy). + """ + + def __init__(self) -> None: + """Initialize a ClassifyMetrics instance.""" + self.top1 = 0 + self.top5 = 0 + self.speed = {"preprocess": 0.0, "inference": 0.0, "loss": 0.0, "postprocess": 0.0} + + def process(self, targets: torch.Tensor, pred: torch.Tensor): + """Process target classes and predicted classes to compute metrics. + + Args: + targets (torch.Tensor): Target classes. + pred (torch.Tensor): Predicted classes. + """ + pred, targets = torch.cat(pred), torch.cat(targets) + correct = (targets[:, None] == pred).float() + acc = torch.stack((correct[:, 0], correct.max(1).values), dim=1) # (top1, top5) accuracy + self.top1, self.top5 = acc.mean(0).tolist() + + @property + def fitness(self) -> float: + """Return mean of top-1 and top-5 accuracies as fitness score.""" + return (self.top1 + self.top5) / 2 + + @property + def results_dict(self) -> dict[str, float]: + """Return a dictionary with model's performance metrics and fitness score.""" + return dict(zip([*self.keys, "fitness"], [self.top1, self.top5, self.fitness])) + + @property + def keys(self) -> list[str]: + """Return a list of keys for the results_dict property.""" + return ["metrics/accuracy_top1", "metrics/accuracy_top5"] + + @property + def curves(self) -> list: + """Return a list of curves for accessing specific metrics curves.""" + return [] + + @property + def curves_results(self) -> list: + """Return a list of curves results for accessing specific metrics curves.""" + return [] + + def summary(self, normalize: bool = True, decimals: int = 5) -> list[dict[str, float]]: + """Generate a single-row summary of classification metrics (Top-1 and Top-5 accuracy). + + Args: + normalize (bool): For Classify metrics, everything is normalized by default [0-1]. + decimals (int): Number of decimal places to round the metrics values to. + + Returns: + (list[dict[str, float]]): A list with one dictionary containing Top-1 and Top-5 classification accuracy. + + Examples: + >>> results = model.val(data="imagenet10") + >>> classify_summary = results.summary(decimals=4) + >>> print(classify_summary) + """ + return [{"top1_acc": round(self.top1, decimals), "top5_acc": round(self.top5, decimals)}] + + +class OBBMetrics(DetMetrics): + """Metrics for evaluating oriented bounding box (OBB) detection. + + Attributes: + names (dict[int, str]): Dictionary of class names. + box (Metric): An instance of the Metric class for storing detection results. + speed (dict[str, float]): A dictionary for storing execution times of different parts of the detection process. + stats (dict[str, list]): A dictionary containing lists for true positives, confidence scores, predicted classes, + target classes, and target images. + nt_per_class: Number of targets per class. + nt_per_image: Number of targets per image. + + References: + https://arxiv.org/pdf/2106.06072.pdf + """ + + def __init__(self, names: dict[int, str] | None = None) -> None: + """Initialize an OBBMetrics instance with class names. + + Args: + names (dict[int, str], optional): Dictionary of class names. + """ + DetMetrics.__init__(self, names) + + +class SemanticMetrics(SimpleClass, DataExportMixin): + """Metrics for semantic segmentation, including mIoU, pixel accuracy, and per-class IoU. + + Attributes: + names (dict): Class names mapping. + nc (int): Number of classes. + cm_nc (int): Confusion matrix side length (2 for binary segmentation, else nc). + device (torch.device | None): Device used for confusion matrix accumulation. + matrix (torch.Tensor | None): Accumulated confusion matrix of shape (cm_nc, cm_nc). + speed (dict): Processing speed statistics. + nt_per_image (np.ndarray): Number of images containing each class. + nt_per_class (np.ndarray): Number of pixels per class. + _miou (float): Cached mean IoU. + _pixel_accuracy (float): Cached pixel accuracy. + _per_class_iou (np.ndarray): Cached per-class IoU values. + _per_class_pixel_acc (np.ndarray): Cached per-class pixel accuracy. + """ + + def __init__(self, names: dict[int, str] | None = None) -> None: + """Initialize semantic segmentation metrics. + + Args: + names (dict, optional): Dictionary mapping class indices to names. + """ + self.names = names or {} + self.nc = len(self.names) + self.cm_nc = 2 if self.nc == 1 else self.nc + self.matrix = None + self.speed = {"preprocess": 0.0, "inference": 0.0, "loss": 0.0, "postprocess": 0.0} + self.nt_per_image = np.zeros(self.nc, dtype=np.int32) + self._miou = 0.0 + self._pixel_accuracy = 0.0 + self._per_class_iou = np.zeros(self.nc, dtype=np.float32) + self._per_class_pixel_acc = np.zeros(self.nc, dtype=np.float32) + self.nt_per_class = np.zeros(self.nc, dtype=np.int32) + + def update_stats(self, preds: torch.Tensor, targets: torch.Tensor) -> None: + """Accumulate confusion matrix from predictions and targets. + + Args: + preds (torch.Tensor): Predicted class IDs [B, H, W]. + targets (torch.Tensor): Ground truth class IDs [B, H, W]. + """ + if self.matrix is None: + self.matrix = torch.zeros((self.cm_nc, self.cm_nc), device=preds.device, dtype=torch.float32) + + valid = (targets != 255) & (preds >= 0) & (preds < self.cm_nc) & (targets >= 0) & (targets < self.cm_nc) + hist = torch.bincount(self.cm_nc * targets[valid] + preds[valid], minlength=self.cm_nc**2).reshape( + self.cm_nc, self.cm_nc + ) + self.matrix += hist.to(self.matrix.dtype) + + present = torch.zeros((targets.shape[0], self.cm_nc), dtype=torch.bool, device=targets.device) + batch_idx = torch.arange(targets.shape[0], device=targets.device).view(-1, 1, 1).expand_as(targets) + present[batch_idx[valid], targets[valid].long()] = True + if self.nc == 1: + self.nt_per_image[0] += int(present[:, 1].sum()) + else: + self.nt_per_image += present[:, : self.nc].sum(0).cpu().numpy() + + def process(self, save_dir: Path = Path("."), plot: bool = False, on_plot: callable | None = None) -> None: + """Compute final metrics from accumulated confusion matrix. + + Args: + save_dir (Path): Directory to save plots. Defaults to Path('.'). + plot (bool): Whether to plot IoU bars and confusion matrix. Defaults to False. + on_plot (callable, optional): Function to call after plots are generated. Defaults to None. + """ + if self.matrix is None: + return + + intersection = torch.diagonal(self.matrix) + union = self.matrix.sum(1) + self.matrix.sum(0) - intersection + iou = torch.where(union > 0, intersection / union, torch.zeros_like(intersection, dtype=torch.float32)) + row_sum = self.matrix.sum(1) + pa = intersection / (row_sum + 1e-10) + + if self.nc == 1: + self._miou = float(iou[1].item()) + self._per_class_iou = iou[1:].cpu().numpy() + self._per_class_pixel_acc = pa[1:].cpu().numpy() + self.nt_per_class = np.array([row_sum[1].item()], dtype=np.int32) + else: + # Average IoU only over classes present in the ground truth; classes with no GT pixels (absent + # from the val set or removed by the `classes` filter) are excluded. + present = row_sum > 0 + self._miou = float(iou[present].mean().item()) if present.any() else 0.0 + self._per_class_iou = iou.cpu().numpy() + self._per_class_pixel_acc = pa.cpu().numpy() + self.nt_per_class = row_sum[: self.nc].cpu().numpy().astype(np.int32) + + self._pixel_accuracy = float((intersection.sum() / (self.matrix.sum() + 1e-10)).item()) + + if plot: + self._plot_iou_bars(save_dir, on_plot) + + def clear_stats(self): + """Clear accumulated statistics.""" + self.matrix = None + self.nt_per_image.fill(0) + + @plt_settings() + def _plot_iou_bars(self, save_dir, on_plot): + """Plot per-class IoU bar chart. + + Args: + save_dir (Path | str): Directory to save the plot. + on_plot (callable, optional): Function to call after plot is saved. + """ + import matplotlib.pyplot as plt + + fig, ax = plt.subplots(1, 1, figsize=(10, 6), tight_layout=True) + names = list(self.names.values()) if self.names else [str(i) for i in range(self.nc)] + x = np.arange(self.nc) + bars = ax.bar(x, self._per_class_iou, color=[list(c / 255.0 for c in colors(i, False)) for i in range(self.nc)]) + ax.set_xlabel("Class") + ax.set_ylabel("IoU") + ax.set_title("Per-Class IoU") + ax.set_ylim(0, 1) + if 0 < len(names) < 30: + ax.set_xticks(x) + ax.set_xticklabels(names, rotation=90, fontsize=10) + for bar in bars: + height = bar.get_height() + ax.text(bar.get_x() + bar.get_width() / 2.0, height, f"{height:.3f}", ha="center", va="bottom", fontsize=8) + fname = Path(save_dir) / "iou_bar_chart.png" + plt.savefig(fname, dpi=250) + plt.close(fig) + if on_plot: + on_plot(fname) + + @property + def miou(self): + """Return mean IoU (foreground IoU only for binary segmentation).""" + return self._miou + + @property + def pixel_accuracy(self): + """Return overall pixel accuracy.""" + return self._pixel_accuracy + + @property + def per_class_iou(self): + """Return per-class IoU values (foreground IoU only for binary segmentation).""" + return self._per_class_iou + + @property + def per_class_pixel_accuracy(self): + """Return per-class pixel accuracy (diagonal / row sum for each class).""" + return self._per_class_pixel_acc + + @property + def fitness(self): + """Return model fitness as mean IoU.""" + return self.miou + + @property + def keys(self): + """Return metric keys for logging.""" + return ["metrics/mIoU", "metrics/pixel_acc"] + + def mean_results(self): + """Return mean results for logging.""" + return [self.miou, self.pixel_accuracy] + + def class_result(self, i: int) -> list[float]: + """Return the result of evaluating the performance on a specific class.""" + if self._per_class_iou is None or len(self._per_class_iou) == 0: + return [0.0, 0.0] + c = self.ap_class_index[i] + return [float(self._per_class_iou[c]), float(self._per_class_pixel_acc[c])] + + @property + def ap_class_index(self): + """Return the indices of classes present in the ground truth for per-class reporting.""" + return [i for i in range(self.nc) if self.nt_per_class[i] > 0] + + @property + def results_dict(self): + """Return results dictionary.""" + return dict(zip([*self.keys, "fitness"], [*self.mean_results(), self.fitness])) + + @property + def curves(self): + """Return an empty list because semantic segmentation has no PR curves.""" + return [] + + @property + def curves_results(self): + """Return empty list (no PR curve results).""" + return [] + + def summary(self, normalize: bool = True, decimals: int = 5) -> list[dict]: + """Generate a per-class summary of semantic segmentation metrics, with global mIoU and pixel accuracy on each + row. + + Args: + normalize (bool): For semantic metrics, values are already in [0, 1]. + decimals (int): Number of decimal places to round the metric values to. + + Returns: + (list[dict]): A list of dictionaries, one per class, with per-class IoU and shared scalars. + """ + miou = round(self.miou, decimals) + pixel_acc = round(self.pixel_accuracy, decimals) + per_class = self.per_class_iou + names = self.names or {i: str(i) for i in range(len(per_class))} + return [ + { + "Class": names.get(c, str(c)), + "Images": int(self.nt_per_image[c]), + "Pixels": int(self.nt_per_class[c]), + "IoU": round(float(per_class[c]), decimals), + "mIoU": miou, + "pixel_acc": pixel_acc, + } + for c in self.ap_class_index + ] diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/nms.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/nms.py new file mode 100644 index 0000000000000000000000000000000000000000..471917e06370d5552edfa3e20c70effb44f72029 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/nms.py @@ -0,0 +1,338 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +import sys +import time + +import torch + +from ultralytics.utils import LOGGER +from ultralytics.utils.metrics import batch_probiou, box_iou +from ultralytics.utils.ops import xywh2xyxy + + +def non_max_suppression( + prediction, + conf_thres: float = 0.25, + iou_thres: float = 0.45, + classes=None, + agnostic: bool = False, + multi_label: bool = False, + labels=(), + max_det: int = 300, + nc: int = 0, # number of classes (optional) + max_time_img: float = 0.05, + max_nms: int = 30000, + max_wh: int = 7680, + rotated: bool = False, + end2end: bool = False, + return_idxs: bool = False, +): + """Perform non-maximum suppression (NMS) on prediction results. + + Applies NMS to filter overlapping bounding boxes based on confidence and IoU thresholds. Supports multiple detection + formats including standard boxes, rotated boxes, and masks. + + Args: + prediction (torch.Tensor): Predictions with shape (batch_size, num_classes + 4 + num_masks, num_boxes) + containing boxes, classes, and optional masks. + conf_thres (float): Confidence threshold for filtering detections. Valid values are between 0.0 and 1.0. + iou_thres (float): IoU threshold for NMS filtering. Valid values are between 0.0 and 1.0. + classes (list[int], optional): List of class indices to consider. If None, all classes are considered. + agnostic (bool): Whether to perform class-agnostic NMS. + multi_label (bool): Whether each box can have multiple labels. + labels (list[torch.Tensor]): A priori labels for each image. + max_det (int): Maximum number of detections to keep per image. + nc (int): Number of classes. Indices after this are considered masks. + max_time_img (float): Maximum time in seconds for processing one image. + max_nms (int): Maximum number of boxes for NMS. + max_wh (int): Maximum box width and height in pixels. + rotated (bool): Whether to handle Oriented Bounding Boxes (OBB). + end2end (bool): Whether the model is end-to-end and doesn't require NMS. + return_idxs (bool): Whether to return the indices of kept detections. + + Returns: + (list[torch.Tensor] | tuple[list[torch.Tensor], list[torch.Tensor]]): List of detections per image with shape + (num_boxes, 6 + num_masks) containing (x1, y1, x2, y2, confidence, class, mask1, mask2, ...). If + return_idxs=True, returns a tuple of (output, keepi) where keepi contains indices of kept detections. + """ + # Checks + assert 0 <= conf_thres <= 1, f"Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0" + assert 0 <= iou_thres <= 1, f"Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0" + if isinstance(prediction, (list, tuple)): # YOLOv8 model in validation mode, output = (inference_out, loss_out) + prediction = prediction[0] # select only inference output + if classes is not None: + classes = torch.tensor(classes, device=prediction.device) + + if prediction.shape[-1] == 6 or end2end: # end-to-end model (BNC, i.e. 1,300,6) + output = [pred[pred[:, 4] > conf_thres][:max_det] for pred in prediction] + if classes is not None: + output = [pred[(pred[:, 5:6] == classes).any(1)] for pred in output] + return output + + bs = prediction.shape[0] # batch size (BCN, i.e. 1,84,6300) + nc = nc or (prediction.shape[1] - 4) # number of classes + extra = prediction.shape[1] - nc - 4 # number of extra info + mi = 4 + nc # mask start index + xc = prediction[:, 4:mi].amax(1) > conf_thres # candidates + xinds = torch.arange(prediction.shape[-1], device=prediction.device).expand(bs, -1)[..., None] # to track idxs + + # Settings + # min_wh = 2 # (pixels) minimum box width and height + time_limit = 2.0 + max_time_img * bs # seconds to quit after + multi_label &= nc > 1 # multiple labels per box (adds 0.5ms/img) + + prediction = prediction.transpose(-1, -2) # shape(1,84,6300) to shape(1,6300,84) + if not rotated: + prediction[..., :4] = xywh2xyxy(prediction[..., :4]) # xywh to xyxy + + t = time.time() + output = [torch.zeros((0, 6 + extra), device=prediction.device)] * bs + keepi = [torch.zeros((0, 1), device=prediction.device)] * bs # to store the kept idxs + for xi, (x, xk) in enumerate(zip(prediction, xinds)): # image index, (preds, preds indices) + # Apply constraints + # x[((x[:, 2:4] < min_wh) | (x[:, 2:4] > max_wh)).any(1), 4] = 0 # width-height + filt = xc[xi] # confidence + x = x[filt] + if return_idxs: + xk = xk[filt] + + # Cat apriori labels if autolabelling + if labels and len(labels[xi]) and not rotated: + lb = labels[xi] + v = torch.zeros((len(lb), nc + extra + 4), device=x.device) + v[:, :4] = xywh2xyxy(lb[:, 1:5]) # box + v[range(len(lb)), lb[:, 0].long() + 4] = 1.0 # cls + x = torch.cat((x, v), 0) + + # If none remain process next image + if not x.shape[0]: + continue + + # Detections matrix nx6 (xyxy, conf, cls) + box, cls, mask = x.split((4, nc, extra), 1) + + if multi_label: + i, j = torch.where(cls > conf_thres) + x = torch.cat((box[i], x[i, 4 + j, None], j[:, None].float(), mask[i]), 1) + if return_idxs: + xk = xk[i] + else: # best class only + conf, j = cls.max(1, keepdim=True) + filt = conf.view(-1) > conf_thres + x = torch.cat((box, conf, j.float(), mask), 1)[filt] + if return_idxs: + xk = xk[filt] + + # Filter by class + if classes is not None: + filt = (x[:, 5:6] == classes).any(1) + x = x[filt] + if return_idxs: + xk = xk[filt] + + # Check shape + n = x.shape[0] # number of boxes + if not n: # no boxes + continue + if n > max_nms: # excess boxes + filt = x[:, 4].argsort(descending=True)[:max_nms] # sort by confidence and remove excess boxes + x = x[filt] + if return_idxs: + xk = xk[filt] + + c = x[:, 5:6] * (0 if agnostic else max_wh) # classes + scores = x[:, 4] # scores + if rotated: + boxes = torch.cat((x[:, :2] + c, x[:, 2:4], x[:, -1:]), dim=-1) # xywhr + i = TorchNMS.fast_nms(boxes, scores, iou_thres, iou_func=batch_probiou) + else: + boxes = x[:, :4] + c # boxes (offset by class) + # Speed strategy: torchvision for val or already loaded (faster), TorchNMS for predict (lower latency) + if "torchvision" in sys.modules: + import torchvision # scope as slow import + + i = torchvision.ops.nms(boxes, scores, iou_thres) + else: + i = TorchNMS.nms(boxes, scores, iou_thres) + i = i[:max_det] # limit detections + + output[xi] = x[i] + if return_idxs: + keepi[xi] = xk[i].view(-1) + if (time.time() - t) > time_limit: + LOGGER.warning(f"NMS time limit {time_limit:.3f}s exceeded") + break # time limit exceeded + + return (output, keepi) if return_idxs else output + + +class TorchNMS: + """Ultralytics custom NMS implementation optimized for YOLO. + + This class provides static methods for performing non-maximum suppression (NMS) operations on bounding boxes, + including standard NMS, fast NMS, and batched NMS for multi-class scenarios. + + Methods: + fast_nms: Fast-NMS using upper triangular matrix operations. + nms: Optimized NMS with early termination that matches torchvision behavior exactly. + batched_nms: Batched NMS for class-aware suppression. + + Examples: + Perform standard NMS on boxes and scores + >>> boxes = torch.tensor([[0, 0, 10, 10], [5, 5, 15, 15]]) + >>> scores = torch.tensor([0.9, 0.8]) + >>> keep = TorchNMS.nms(boxes, scores, 0.5) + """ + + @staticmethod + def fast_nms( + boxes: torch.Tensor, + scores: torch.Tensor, + iou_threshold: float, + use_triu: bool = True, + iou_func=box_iou, + exit_early: bool = True, + ) -> torch.Tensor: + """Fast-NMS implementation from https://arxiv.org/pdf/1904.02689 using upper triangular matrix operations. + + Args: + boxes (torch.Tensor): Bounding boxes with shape (N, 4) in xyxy format. + scores (torch.Tensor): Confidence scores with shape (N,). + iou_threshold (float): IoU threshold for suppression. + use_triu (bool): Whether to use torch.triu operator for upper triangular matrix operations. + iou_func (callable): Function to compute IoU between boxes. + exit_early (bool): Whether to exit early if there are no boxes. + + Returns: + (torch.Tensor): Indices of boxes to keep after NMS. + + Examples: + Apply NMS to a set of boxes + >>> boxes = torch.tensor([[0, 0, 10, 10], [5, 5, 15, 15]]) + >>> scores = torch.tensor([0.9, 0.8]) + >>> keep = TorchNMS.fast_nms(boxes, scores, 0.5) + """ + if boxes.numel() == 0 and exit_early: + return torch.empty((0,), dtype=torch.int64, device=boxes.device) + + sorted_idx = torch.argsort(scores, descending=True) + boxes = boxes[sorted_idx] + ious = iou_func(boxes, boxes) + if use_triu: + ious = ious.triu_(diagonal=1) + # NOTE: handle the case when len(boxes) hence exportable by eliminating if-else condition + pick = torch.nonzero((ious >= iou_threshold).sum(0) <= 0).squeeze_(-1) + else: + n = boxes.shape[0] + row_idx = torch.arange(n, device=boxes.device).view(-1, 1).expand(-1, n) + col_idx = torch.arange(n, device=boxes.device).view(1, -1).expand(n, -1) + upper_mask = row_idx < col_idx + ious = ious * upper_mask + # Zeroing these scores ensures the additional indices would not affect the final results + scores_ = scores[sorted_idx] + scores_[~((ious >= iou_threshold).sum(0) <= 0)] = 0 + scores[sorted_idx] = scores_ # update original tensor for NMSModel + # NOTE: return indices with fixed length to avoid TFLite reshape error + pick = torch.topk(scores_, scores_.shape[0]).indices + return sorted_idx[pick] + + @staticmethod + def nms(boxes: torch.Tensor, scores: torch.Tensor, iou_threshold: float) -> torch.Tensor: + """Optimized NMS with early termination that matches torchvision behavior exactly. + + Args: + boxes (torch.Tensor): Bounding boxes with shape (N, 4) in xyxy format. + scores (torch.Tensor): Confidence scores with shape (N,). + iou_threshold (float): IoU threshold for suppression. + + Returns: + (torch.Tensor): Indices of boxes to keep after NMS. + + Examples: + Apply NMS to a set of boxes + >>> boxes = torch.tensor([[0, 0, 10, 10], [5, 5, 15, 15]]) + >>> scores = torch.tensor([0.9, 0.8]) + >>> keep = TorchNMS.nms(boxes, scores, 0.5) + """ + if boxes.numel() == 0: + return torch.empty((0,), dtype=torch.int64, device=boxes.device) + + # Pre-allocate and extract coordinates once + x1, y1, x2, y2 = boxes.unbind(1) + areas = (x2 - x1) * (y2 - y1) + + # Sort by scores descending + order = scores.argsort(0, descending=True) + + # Pre-allocate keep list with maximum possible size + keep = torch.zeros(order.numel(), dtype=torch.int64, device=boxes.device) + keep_idx = 0 + while order.numel() > 0: + i = order[0] + keep[keep_idx] = i + keep_idx += 1 + + if order.numel() == 1: + break + # Vectorized IoU calculation for remaining boxes + rest = order[1:] + xx1 = torch.maximum(x1[i], x1[rest]) + yy1 = torch.maximum(y1[i], y1[rest]) + xx2 = torch.minimum(x2[i], x2[rest]) + yy2 = torch.minimum(y2[i], y2[rest]) + + # Fast intersection and IoU + w = (xx2 - xx1).clamp_(min=0) + h = (yy2 - yy1).clamp_(min=0) + inter = w * h + # Early exit: skip IoU calculation if no intersection + if inter.sum() == 0: + # No overlaps with current box, keep all remaining boxes + order = rest + continue + iou = inter / (areas[i] + areas[rest] - inter) + # Keep boxes with IoU <= threshold + order = rest[iou <= iou_threshold] + + return keep[:keep_idx] + + @staticmethod + def batched_nms( + boxes: torch.Tensor, + scores: torch.Tensor, + idxs: torch.Tensor, + iou_threshold: float, + use_fast_nms: bool = False, + ) -> torch.Tensor: + """Batched NMS for class-aware suppression. + + Args: + boxes (torch.Tensor): Bounding boxes with shape (N, 4) in xyxy format. + scores (torch.Tensor): Confidence scores with shape (N,). + idxs (torch.Tensor): Class indices with shape (N,). + iou_threshold (float): IoU threshold for suppression. + use_fast_nms (bool): Whether to use the Fast-NMS implementation. + + Returns: + (torch.Tensor): Indices of boxes to keep after NMS. + + Examples: + Apply batched NMS across multiple classes + >>> boxes = torch.tensor([[0, 0, 10, 10], [5, 5, 15, 15]]) + >>> scores = torch.tensor([0.9, 0.8]) + >>> idxs = torch.tensor([0, 1]) + >>> keep = TorchNMS.batched_nms(boxes, scores, idxs, 0.5) + """ + if boxes.numel() == 0: + return torch.empty((0,), dtype=torch.int64, device=boxes.device) + + # Strategy: offset boxes by class index to prevent cross-class suppression + max_coordinate = boxes.max() + offsets = idxs.to(boxes) * (max_coordinate + 1) + boxes_for_nms = boxes + offsets[:, None] + + return ( + TorchNMS.fast_nms(boxes_for_nms, scores, iou_threshold) + if use_fast_nms + else TorchNMS.nms(boxes_for_nms, scores, iou_threshold) + ) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/ops.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/ops.py new file mode 100644 index 0000000000000000000000000000000000000000..45cd277fc5e0b3d72268fd26d1980d30b6f5b7dc --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/ops.py @@ -0,0 +1,781 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import contextlib +import math +import re +import time + +import cv2 +import numpy as np +import torch +import torch.nn.functional as F + +from ultralytics.utils import NOT_MACOS14 + + +class Profile(contextlib.ContextDecorator): + """Ultralytics Profile class for timing code execution. + + Use as a decorator with @Profile() or as a context manager with 'with Profile():'. Provides accurate timing + measurements with CUDA synchronization support for GPU operations. + + Attributes: + t (float): Accumulated time in seconds. + device (torch.device): Device used for model inference. + cuda (bool): Whether CUDA is being used for timing synchronization. + + Examples: + Use as a context manager to time code execution + >>> with Profile() as dt: + ... pass # slow operation here + >>> str(dt).startswith("Elapsed time is ") + True + + Use as a decorator to time function execution + >>> @Profile() + ... def slow_function(): + ... time.sleep(0.1) + """ + + def __init__(self, t: float = 0.0, device: torch.device | None = None): + """Initialize the Profile class. + + Args: + t (float): Initial accumulated time in seconds. + device (torch.device, optional): Device used for model inference to enable CUDA synchronization. + """ + self.t = t + self.device = device + self.cuda = bool(device and str(device).startswith("cuda")) + + def __enter__(self): + """Start timing.""" + self.start = self.time() + return self + + def __exit__(self, type, value, traceback): + """Stop timing.""" + self.dt = self.time() - self.start # delta-time + self.t += self.dt # accumulate dt + + def __str__(self): + """Return a human-readable string representing the accumulated elapsed time.""" + return f"Elapsed time is {self.t} s" + + def time(self): + """Get current time with CUDA synchronization if applicable.""" + if self.cuda: + torch.cuda.synchronize(self.device) + return time.perf_counter() + + +def segment2box(segment: np.ndarray, width: int = 640, height: int = 640) -> np.ndarray: + """Convert segment coordinates to bounding box coordinates. + + Converts a single segment label to a box label by finding the minimum and maximum x and y coordinates. Applies + inside-image constraint and clips coordinates when necessary. + + Args: + segment (np.ndarray): Segment coordinates in format (N, 2) where N is number of points. + width (int): Width of the image in pixels. + height (int): Height of the image in pixels. + + Returns: + (np.ndarray): Bounding box coordinates in xyxy format [x1, y1, x2, y2]. + """ + x, y = segment.T # segment xy + # Clip coordinates if 3 out of 4 sides are outside the image + if np.array([x.min() < 0, y.min() < 0, x.max() > width, y.max() > height]).sum() >= 3: + x = x.clip(0, width) + y = y.clip(0, height) + inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height) + x = x[inside] + y = y[inside] + return ( + np.array([x.min(), y.min(), x.max(), y.max()], dtype=segment.dtype) + if any(x) + else np.zeros(4, dtype=segment.dtype) + ) # xyxy + + +def scale_boxes( + img1_shape: tuple[int, int], + boxes: torch.Tensor | np.ndarray, + img0_shape: tuple[int, int], + ratio_pad: tuple | None = None, + padding: bool = True, + xywh: bool = False, +) -> torch.Tensor | np.ndarray: + """Rescale bounding boxes from one image shape to another. + + Rescales bounding boxes from img1_shape to img0_shape, accounting for padding and aspect ratio changes. Supports + both xyxy and xywh box formats. + + Args: + img1_shape (tuple[int, int]): Shape of the source image (height, width). + boxes (torch.Tensor | np.ndarray): Bounding boxes to rescale in format (N, 4). + img0_shape (tuple[int, int]): Shape of the target image (height, width). + ratio_pad (tuple, optional): Tuple of (ratio, pad) for scaling. If None, calculated from image shapes. + padding (bool): Whether boxes are based on YOLO-style augmented images with padding. + xywh (bool): Whether box format is xywh (True) or xyxy (False). + + Returns: + (torch.Tensor | np.ndarray): Rescaled bounding boxes in the same format as input. + """ + if ratio_pad is None: # calculate from img0_shape + gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new + pad_x = round((img1_shape[1] - round(img0_shape[1] * gain)) / 2 - 0.1) + pad_y = round((img1_shape[0] - round(img0_shape[0] * gain)) / 2 - 0.1) + else: + gain = ratio_pad[0][0] + pad_x, pad_y = ratio_pad[1] + + if padding: + boxes[..., 0] -= pad_x # x padding + boxes[..., 1] -= pad_y # y padding + if not xywh: + boxes[..., 2] -= pad_x # x padding + boxes[..., 3] -= pad_y # y padding + boxes[..., :4] /= gain + return boxes if xywh else clip_boxes(boxes, img0_shape) + + +def make_divisible(x: int, divisor): + """Return the smallest number >= x that is divisible by the given divisor. + + Args: + x (int): The number to make divisible. + divisor (int | torch.Tensor): The divisor. + + Returns: + (int): The smallest number >= x divisible by the divisor. + """ + if isinstance(divisor, torch.Tensor): + divisor = int(divisor.max()) # to int + return math.ceil(x / divisor) * divisor + + +def clip_boxes(boxes, shape): + """Clip bounding boxes to image boundaries. + + Args: + boxes (torch.Tensor | np.ndarray): Bounding boxes to clip. + shape (tuple): Image shape as HWC or HW (supports both). + + Returns: + (torch.Tensor | np.ndarray): Clipped bounding boxes. + """ + h, w = shape[:2] # supports both HWC or HW shapes + if isinstance(boxes, torch.Tensor): # faster individually + if NOT_MACOS14: + boxes[..., 0].clamp_(0, w) # x1 + boxes[..., 1].clamp_(0, h) # y1 + boxes[..., 2].clamp_(0, w) # x2 + boxes[..., 3].clamp_(0, h) # y2 + else: # Apple macOS14 MPS bug https://github.com/ultralytics/ultralytics/pull/21878 + boxes[..., 0] = boxes[..., 0].clamp(0, w) + boxes[..., 1] = boxes[..., 1].clamp(0, h) + boxes[..., 2] = boxes[..., 2].clamp(0, w) + boxes[..., 3] = boxes[..., 3].clamp(0, h) + else: # np.array (faster grouped) + boxes[..., [0, 2]] = boxes[..., [0, 2]].clip(0, w) # x1, x2 + boxes[..., [1, 3]] = boxes[..., [1, 3]].clip(0, h) # y1, y2 + return boxes + + +def clip_coords(coords, shape): + """Clip line coordinates to image boundaries. + + Args: + coords (torch.Tensor | np.ndarray): Line coordinates to clip. + shape (tuple): Image shape as HWC or HW (supports both). + + Returns: + (torch.Tensor | np.ndarray): Clipped coordinates. + """ + h, w = shape[:2] # supports both HWC or HW shapes + if isinstance(coords, torch.Tensor): + if NOT_MACOS14: + coords[..., 0].clamp_(0, w) # x + coords[..., 1].clamp_(0, h) # y + else: # Apple macOS14 MPS bug https://github.com/ultralytics/ultralytics/pull/21878 + coords[..., 0] = coords[..., 0].clamp(0, w) + coords[..., 1] = coords[..., 1].clamp(0, h) + else: # np.array + coords[..., 0] = coords[..., 0].clip(0, w) # x + coords[..., 1] = coords[..., 1].clip(0, h) # y + return coords + + +def xyxy2xywh(x): + """Convert bounding box coordinates from (x1, y1, x2, y2) format to (x, y, width, height) format where (x1, y1) is + the top-left corner and (x2, y2) is the bottom-right corner. + + Args: + x (np.ndarray | torch.Tensor): Input bounding box coordinates in (x1, y1, x2, y2) format. + + Returns: + (np.ndarray | torch.Tensor): Bounding box coordinates in (x, y, width, height) format. + """ + assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}" + y = empty_like(x) # faster than clone/copy + x1, y1, x2, y2 = x[..., 0], x[..., 1], x[..., 2], x[..., 3] + y[..., 0] = (x1 + x2) / 2 # x center + y[..., 1] = (y1 + y2) / 2 # y center + y[..., 2] = x2 - x1 # width + y[..., 3] = y2 - y1 # height + return y + + +def xywh2xyxy(x): + """Convert bounding box coordinates from (x, y, width, height) format to (x1, y1, x2, y2) format where (x1, y1) is + the top-left corner and (x2, y2) is the bottom-right corner. Note: ops per 2 channels faster than per channel. + + Args: + x (np.ndarray | torch.Tensor): Input bounding box coordinates in (x, y, width, height) format. + + Returns: + (np.ndarray | torch.Tensor): Bounding box coordinates in (x1, y1, x2, y2) format. + """ + assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}" + y = empty_like(x) # faster than clone/copy + xy = x[..., :2] # centers + wh = x[..., 2:] / 2 # half width-height + y[..., :2] = xy - wh # top left xy + y[..., 2:] = xy + wh # bottom right xy + return y + + +def xywhn2xyxy(x, w: int = 640, h: int = 640, padw: int = 0, padh: int = 0): + """Convert normalized bounding box coordinates to pixel coordinates. + + Args: + x (np.ndarray | torch.Tensor): Normalized bounding box coordinates in (x, y, w, h) format. + w (int): Image width in pixels. + h (int): Image height in pixels. + padw (int): Padding width in pixels. + padh (int): Padding height in pixels. + + Returns: + (np.ndarray | torch.Tensor): Bounding box coordinates in (x1, y1, x2, y2) format. + """ + assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}" + y = empty_like(x) # faster than clone/copy + xc, yc, xw, xh = x[..., 0], x[..., 1], x[..., 2], x[..., 3] + half_w, half_h = xw / 2, xh / 2 + y[..., 0] = w * (xc - half_w) + padw # top left x + y[..., 1] = h * (yc - half_h) + padh # top left y + y[..., 2] = w * (xc + half_w) + padw # bottom right x + y[..., 3] = h * (yc + half_h) + padh # bottom right y + return y + + +def xyxy2xywhn(x, w: int = 640, h: int = 640, clip: bool = False, eps: float = 0.0): + """Convert bounding box coordinates from (x1, y1, x2, y2) format to normalized (x, y, width, height) format. x, y, + width and height are normalized to image dimensions. + + Args: + x (np.ndarray | torch.Tensor): Input bounding box coordinates in (x1, y1, x2, y2) format. + w (int): Image width in pixels. + h (int): Image height in pixels. + clip (bool): Whether to clip boxes to image boundaries. + eps (float): Minimum value for box width and height. + + Returns: + (np.ndarray | torch.Tensor): Normalized bounding box coordinates in (x, y, width, height) format. + """ + if clip: + x = clip_boxes(x, (h - eps, w - eps)) + assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}" + y = empty_like(x) # faster than clone/copy + x1, y1, x2, y2 = x[..., 0], x[..., 1], x[..., 2], x[..., 3] + y[..., 0] = ((x1 + x2) / 2) / w # x center + y[..., 1] = ((y1 + y2) / 2) / h # y center + y[..., 2] = (x2 - x1) / w # width + y[..., 3] = (y2 - y1) / h # height + return y + + +def xywh2ltwh(x): + """Convert bounding box format from [x, y, w, h] to [x1, y1, w, h] where x1, y1 are top-left coordinates. + + Args: + x (np.ndarray | torch.Tensor): Input bounding box coordinates in xywh format. + + Returns: + (np.ndarray | torch.Tensor): Bounding box coordinates in ltwh format. + """ + y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) + y[..., 0] = x[..., 0] - x[..., 2] / 2 # top left x + y[..., 1] = x[..., 1] - x[..., 3] / 2 # top left y + return y + + +def xyxy2ltwh(x): + """Convert bounding boxes from [x1, y1, x2, y2] to [x1, y1, w, h] format. + + Args: + x (np.ndarray | torch.Tensor): Input bounding box coordinates in xyxy format. + + Returns: + (np.ndarray | torch.Tensor): Bounding box coordinates in ltwh format. + """ + y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) + y[..., 2] = x[..., 2] - x[..., 0] # width + y[..., 3] = x[..., 3] - x[..., 1] # height + return y + + +def ltwh2xywh(x): + """Convert bounding boxes from [x1, y1, w, h] to [x, y, w, h] where xy1=top-left, xy=center. + + Args: + x (np.ndarray | torch.Tensor): Input bounding box coordinates. + + Returns: + (np.ndarray | torch.Tensor): Bounding box coordinates in xywh format. + """ + y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) + y[..., 0] = x[..., 0] + x[..., 2] / 2 # center x + y[..., 1] = x[..., 1] + x[..., 3] / 2 # center y + return y + + +def xyxyxyxy2xywhr(x): + """Convert batched Oriented Bounding Boxes (OBB) from [xy1, xy2, xy3, xy4] to [xywh, rotation] format. + + Args: + x (np.ndarray | torch.Tensor): Input box corners with shape (N, 8) in [xy1, xy2, xy3, xy4] format. + + Returns: + (np.ndarray | torch.Tensor): Converted data in [cx, cy, w, h, rotation] format with shape (N, 5). Rotation + values are in radians from [-pi/4, 3pi/4). + """ + is_torch = isinstance(x, torch.Tensor) + points = x.cpu().numpy() if is_torch else x + points = points.reshape(len(x), -1, 2) + rboxes = [] + for pts in points: + # NOTE: Use cv2.minAreaRect to get accurate xywhr, + # especially some objects are cut off by augmentations in dataloader. + (cx, cy), (w, h), angle = cv2.minAreaRect(pts) + # convert angle to radian and normalize to [-pi/4, 3pi/4) + theta = angle / 180 * np.pi + if w < h: + w, h = h, w + theta += np.pi / 2 + while theta >= 3 * np.pi / 4: + theta -= np.pi + while theta < -np.pi / 4: + theta += np.pi + rboxes.append([cx, cy, w, h, theta]) + return torch.tensor(rboxes, device=x.device, dtype=x.dtype) if is_torch else np.asarray(rboxes) + + +def xywhr2xyxyxyxy(x): + """Convert batched Oriented Bounding Boxes (OBB) from [xywh, rotation] to [xy1, xy2, xy3, xy4] format. + + Args: + x (np.ndarray | torch.Tensor): Boxes in [cx, cy, w, h, rotation] format with shape (N, 5) or (B, N, 5). Rotation + values should be in radians from [-pi/4, 3pi/4). + + Returns: + (np.ndarray | torch.Tensor): Converted corner points with shape (N, 4, 2) or (B, N, 4, 2). + """ + cos, sin, cat, stack = ( + (torch.cos, torch.sin, torch.cat, torch.stack) + if isinstance(x, torch.Tensor) + else (np.cos, np.sin, np.concatenate, np.stack) + ) + + ctr = x[..., :2] + w, h, angle = (x[..., i : i + 1] for i in range(2, 5)) + cos_value, sin_value = cos(angle), sin(angle) + vec1 = [w / 2 * cos_value, w / 2 * sin_value] + vec2 = [-h / 2 * sin_value, h / 2 * cos_value] + vec1 = cat(vec1, -1) + vec2 = cat(vec2, -1) + pt1 = ctr + vec1 + vec2 + pt2 = ctr + vec1 - vec2 + pt3 = ctr - vec1 - vec2 + pt4 = ctr - vec1 + vec2 + return stack([pt1, pt2, pt3, pt4], -2) + + +def ltwh2xyxy(x): + """Convert bounding box from [x1, y1, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right. + + Args: + x (np.ndarray | torch.Tensor): Input bounding box coordinates. + + Returns: + (np.ndarray | torch.Tensor): Bounding box coordinates in xyxy format. + """ + y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) + y[..., 2] = x[..., 2] + x[..., 0] # x2 + y[..., 3] = x[..., 3] + x[..., 1] # y2 + return y + + +def segments2boxes(segments): + """Convert segment coordinates to bounding box labels in xywh format. + + Args: + segments (list): List of segments where each segment is a list of points, each point is [x, y] coordinates. + + Returns: + (np.ndarray): Bounding box coordinates in xywh format. + """ + boxes = [] + for s in segments: + x, y = s.T # segment xy + boxes.append([x.min(), y.min(), x.max(), y.max()]) # cls, xyxy + return xyxy2xywh(np.array(boxes)) # cls, xywh + + +def resample_segments(segments, n: int = 1000): + """Resample segments to n points each using linear interpolation. + + Args: + segments (list): List of (N, 2) arrays where N is the number of points in each segment. + n (int): Number of points to resample each segment to. + + Returns: + (list): Resampled segments with n points each. + """ + for i, s in enumerate(segments): + if len(s) == n: + continue + s = np.concatenate((s, s[0:1, :]), axis=0) + x = np.linspace(0, len(s) - 1, n - len(s) if len(s) < n else n) + xp = np.arange(len(s)) + x = np.insert(x, np.searchsorted(x, xp), xp) if len(s) < n else x + segments[i] = ( + np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)], dtype=np.float32).reshape(2, -1).T + ) # segment xy + return segments + + +def crop_mask(masks: torch.Tensor, boxes: torch.Tensor) -> torch.Tensor: + """Crop masks to bounding box regions. + + Args: + masks (torch.Tensor): Masks with shape (N, H, W). + boxes (torch.Tensor): Bounding box coordinates with shape (N, 4) in xyxy pixel format. + + Returns: + (torch.Tensor): Cropped masks. + """ + if boxes.device != masks.device: + boxes = boxes.to(masks.device) + _, h, w = masks.shape + x1, y1, x2, y2 = torch.chunk(boxes[:, :, None], 4, 1) # each shape (n,1,1) + r = torch.arange(w, device=masks.device, dtype=x1.dtype)[None, None, :] # columns (1,1,w) + c = torch.arange(h, device=masks.device, dtype=x1.dtype)[None, :, None] # rows (1,h,1) + # Apply the column and row masks separately and in place: the box region is separable, so this avoids ever + # materializing the full (n, h, w) boolean grid the combined product would build, and has no per-mask Python loop. + masks *= (r >= x1) * (r < x2) # zero columns outside the box + masks *= (c >= y1) * (c < y2) # zero rows outside the box + return masks + + +def process_mask(protos, masks_in, bboxes, shape, upsample: bool = False): + """Apply masks to bounding boxes using mask head output. + + Args: + protos (torch.Tensor): Mask prototypes with shape (mask_dim, mask_h, mask_w). + masks_in (torch.Tensor): Mask coefficients with shape (N, mask_dim) where N is number of masks after NMS. + bboxes (torch.Tensor): Bounding boxes with shape (N, 4) where N is number of masks after NMS. + shape (tuple): Input image size as (height, width). + upsample (bool): Whether to upsample masks to original image size. + + Returns: + (torch.Tensor): A binary mask tensor of shape [n, h, w], where n is the number of masks after NMS. When + upsample=True h and w match the input image size; otherwise they are the prototype mask resolution. + """ + c, mh, mw = protos.shape # CHW + masks = (masks_in @ protos.float().view(c, -1)).view(-1, mh, mw) # NHW + + width_ratio = mw / shape[1] + height_ratio = mh / shape[0] + ratios = torch.tensor([[width_ratio, height_ratio, width_ratio, height_ratio]], device=bboxes.device) + + masks = crop_mask(masks, boxes=bboxes * ratios) # NHW + if upsample: + masks = F.interpolate(masks[None], shape, mode="bilinear")[0] # NHW + return masks.gt_(0.0).byte() + + +def process_mask_native(protos, masks_in, bboxes, shape): + """Apply masks to bounding boxes using mask head output with native upsampling. + + Args: + protos (torch.Tensor): Mask prototypes with shape (mask_dim, mask_h, mask_w). + masks_in (torch.Tensor): Mask coefficients with shape (N, mask_dim) where N is number of masks after NMS. + bboxes (torch.Tensor): Bounding boxes with shape (N, 4) where N is number of masks after NMS. + shape (tuple): Input image size as (height, width). + + Returns: + (torch.Tensor): Binary mask tensor with shape (N, H, W). + """ + c, mh, mw = protos.shape # CHW + coeffs = masks_in @ protos.float().view(c, -1) # (N, mh*mw) prototype-resolution mask logits + h, w = shape + # Upsampling all N masks at once allocates an N*H*W float intermediate (~9 GB on a large image with many + # detections), which OOMs the worker. Upsample in chunks bounded by a pixel budget, thresholding each chunk to + # uint8 immediately so the float intermediate stays small, then crop the assembled uint8 stack. + step = max(1, 32_000_000 // (h * w)) + masks = [ + scale_masks(coeffs[i : i + step].view(-1, mh, mw)[None], shape)[0].gt_(0.0).byte() + for i in range(0, coeffs.shape[0], step) + ] + return crop_mask(torch.cat(masks), bboxes) + + +def scale_masks( + masks: torch.Tensor, + shape: tuple[int, int], + ratio_pad: tuple[tuple[int, int], tuple[int, int]] | None = None, + padding: bool = True, + mode: str = "bilinear", +) -> torch.Tensor: + """Rescale segment masks to target shape. + + Args: + masks (torch.Tensor): Masks with shape (N, C, H, W). + shape (tuple[int, int]): Target height and width as (height, width). + ratio_pad (tuple, optional): Ratio and padding values as ((ratio_h, ratio_w), (pad_w, pad_h)). + padding (bool): Whether masks are based on YOLO-style augmented images with padding. + mode (str): Interpolation mode, e.g. 'bilinear' for logits or 'nearest' for integer class maps. + + Returns: + (torch.Tensor): Rescaled masks. + """ + im1_h, im1_w = masks.shape[2:] + im0_h, im0_w = shape[:2] + if im1_h == im0_h and im1_w == im0_w: + return masks + + if ratio_pad is None: # calculate from im0_shape + gain = min(im1_h / im0_h, im1_w / im0_w) # gain = old / new + pad_w, pad_h = (im1_w - round(im0_w * gain)), (im1_h - round(im0_h * gain)) # wh padding + if padding: + pad_w /= 2 + pad_h /= 2 + else: + pad_w, pad_h = ratio_pad[1] + top, left = (round(pad_h - 0.1), round(pad_w - 0.1)) if padding else (0, 0) + bottom = im1_h - round(pad_h + 0.1) + right = im1_w - round(pad_w + 0.1) + return F.interpolate(masks[..., top:bottom, left:right].float(), shape, mode=mode) # NCHW masks + + +def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None, normalize: bool = False, padding: bool = True): + """Rescale segment coordinates from img1_shape to img0_shape. + + Args: + img1_shape (tuple): Source image shape as HWC or HW (supports both). + coords (torch.Tensor): Coordinates to scale with shape (N, 2). + img0_shape (tuple): Image 0 shape as HWC or HW (supports both). + ratio_pad (tuple, optional): Ratio and padding values as ((ratio_h, ratio_w), (pad_w, pad_h)). + normalize (bool): Whether to normalize coordinates to range [0, 1]. + padding (bool): Whether coordinates are based on YOLO-style augmented images with padding. + + Returns: + (torch.Tensor): Scaled coordinates. + """ + img0_h, img0_w = img0_shape[:2] # supports both HWC or HW shapes + if ratio_pad is None: # calculate from img0_shape + img1_h, img1_w = img1_shape[:2] # supports both HWC or HW shapes + gain = min(img1_h / img0_h, img1_w / img0_w) # gain = old / new + pad = (img1_w - round(img0_w * gain)) / 2, (img1_h - round(img0_h * gain)) / 2 # wh padding + else: + gain = ratio_pad[0][0] + pad = ratio_pad[1] + + if padding: + coords[..., 0] -= pad[0] # x padding + coords[..., 1] -= pad[1] # y padding + coords[..., 0] /= gain + coords[..., 1] /= gain + coords = clip_coords(coords, img0_shape) + if normalize: + coords[..., 0] /= img0_w # width + coords[..., 1] /= img0_h # height + return coords + + +def regularize_rboxes(rboxes): + """Regularize rotated bounding boxes to range [0, pi/2). + + Args: + rboxes (torch.Tensor): Input rotated boxes with shape (N, 5) in xywhr format. + + Returns: + (torch.Tensor): Regularized rotated boxes. + """ + x, y, w, h, t = rboxes.unbind(dim=-1) + # Swap edge if t >= pi/2 while not being symmetrically opposite + swap = t % math.pi >= math.pi / 2 + w_ = torch.where(swap, h, w) + h_ = torch.where(swap, w, h) + t = t % (math.pi / 2) + return torch.stack([x, y, w_, h_, t], dim=-1) # regularized boxes + + +def masks2segments(masks: np.ndarray | torch.Tensor, strategy: str = "all") -> list[np.ndarray]: + """Convert masks to segments using contour detection. + + Args: + masks (np.ndarray | torch.Tensor): Binary masks with shape (N, H, W). + strategy (str): Segmentation strategy, either 'all' or 'largest'. + + Returns: + (list): List of segment masks as float32 arrays. + """ + from ultralytics.data.converter import merge_multi_segment + + masks = masks.astype("uint8") if isinstance(masks, np.ndarray) else masks.byte().cpu().numpy() + segments = [] + for x in np.ascontiguousarray(masks): + c = cv2.findContours(x, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] + if c: + if strategy == "all": # merge and concatenate all segments + c = ( + np.concatenate(merge_multi_segment([x.reshape(-1, 2) for x in c])) + if len(c) > 1 + else c[0].reshape(-1, 2) + ) + elif strategy == "largest": # select largest segment + c = np.array(c[np.array([len(x) for x in c]).argmax()]).reshape(-1, 2) + else: + c = np.zeros((0, 2)) # no segments found + segments.append(c.astype("float32")) + return segments + + +def convert_torch2numpy_batch(batch: torch.Tensor) -> np.ndarray: + """Convert a batch of FP32 torch tensors to NumPy uint8 arrays, changing from BCHW to BHWC layout. + + Args: + batch (torch.Tensor): Input tensor batch with shape (Batch, Channels, Height, Width) and dtype torch.float32. + + Returns: + (np.ndarray): Output NumPy array batch with shape (Batch, Height, Width, Channels) and dtype uint8. + """ + return (batch.permute(0, 2, 3, 1).contiguous() * 255).clamp(0, 255).byte().cpu().numpy() + + +def clean_str(s): + """Clean a string by replacing special characters with '_' character. + + Args: + s (str): A string needing special characters replaced. + + Returns: + (str): A string with special characters replaced by an underscore _. + """ + return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨`><+]", repl="_", string=s) + + +def empty_like(x): + """Create empty torch.Tensor or np.ndarray with same shape and dtype as input.""" + return torch.empty_like(x, dtype=x.dtype) if isinstance(x, torch.Tensor) else np.empty_like(x, dtype=x.dtype) + + +_assignment_solver = None # resolved once on first call: SciPy's solver if installed, else the NumPy fallback + + +def linear_sum_assignment(cost_matrix): + """Solve the rectangular linear sum assignment problem (minimum-cost one-to-one matching). + + Uses `scipy.optimize.linear_sum_assignment` when SciPy is installed (faster compiled C++ solver), and otherwise + falls back to an equivalent pure-NumPy implementation of the same modified Jonker-Volgenant shortest augmenting path + algorithm (Crouse 2016). This keeps SciPy out of Ultralytics' required dependencies while preserving its speed when + present. SciPy is imported lazily so it never slows `import ultralytics`. For a rectangular matrix only min(rows, + columns) entries are matched. + + The NumPy fallback expects finite costs: an `inf` entry marks a forbidden assignment (matching SciPy), while `NaN` + is not rejected (SciPy raises), so callers must sanitize NaN upstream (e.g. the RT-DETR matcher zeros NaN/inf + beforehand). The two backends may return a different equal-cost assignment under exact ties, but the total cost is + identical. + + The NumPy fallback is validated against SciPy with exact optimal-cost parity across ~6.9k randomized cases (every + shape including empty/tall/wide, ties, negatives, IoU- and RT-DETR-style matrices, `maximize` via negation, + torch-tensor input) plus ~2k independent brute-force global-optimum checks. SciPy's compiled inner loop is faster, + but at the call-site sizes (smaller dimension = object count) the fallback runs in well under a millisecond: + + cost matrix NumPy SciPy + 300 x 20 0.2ms 0.02ms + 300 x 80 0.6ms 0.1ms + 300 x 300 28ms 1.5ms + + Args: + cost_matrix (np.ndarray | torch.Tensor): Cost matrix with shape (N, M) and finite values. + + Returns: + row_ind (np.ndarray): Row indices of the optimal assignment, sorted ascending, with length min(N, M). + col_ind (np.ndarray): Column indices matched to each row in row_ind. + + Examples: + >>> cost = np.array([[4, 1, 3], [2, 0, 5], [3, 2, 2]], dtype=float) + >>> row_ind, col_ind = linear_sum_assignment(cost) + >>> float(cost[row_ind, col_ind].sum()) + 5.0 + """ + global _assignment_solver + if _assignment_solver is None: # resolve the backend once, then reuse it on every later call + try: + from scipy.optimize import linear_sum_assignment as solver # faster compiled C++ solver when installed + + _assignment_solver = solver + except ImportError: + _assignment_solver = _linear_sum_assignment_numpy + return _assignment_solver(np.asarray(cost_matrix, dtype=np.float64)) + + +def _linear_sum_assignment_numpy(a): + """Solve the rectangular linear sum assignment problem with NumPy (Jonker-Volgenant SciPy-free fallback). + + Args: + a (np.ndarray): Cost matrix of shape (N, M) with dtype float64 and finite values. + + Returns: + row_ind (np.ndarray): Row indices of the optimal assignment, sorted ascending, with length min(N, M). + col_ind (np.ndarray): Column indices matched to each row in row_ind. + """ + n, m = a.shape + if n == 0 or m == 0: + return np.empty(0, dtype=np.intp), np.empty(0, dtype=np.intp) + transposed = n > m + if transposed: + a, n, m = a.T, m, n # ensure rows <= columns + u, v = np.zeros(n + 1), np.zeros(m + 1) # row and column dual potentials + p, way = np.zeros(m + 1, np.intp), np.zeros(m + 1, np.intp) # column->row matches and path pointers + for i in range(1, n + 1): + p[0], j0 = i, 0 + minv, used = np.full(m + 1, np.inf), np.zeros(m + 1, bool) + while True: # grow a shortest augmenting path from row i + used[j0] = True + i0 = p[j0] + cur = a[i0 - 1] - u[i0] - v[1:] + improve = (~used[1:]) & (cur < minv[1:]) + minv[1:][improve], way[1:][improve] = cur[improve], j0 + j1 = int(np.argmin(np.where(used[1:], np.inf, minv[1:]))) + 1 + delta = minv[j1] + u[p[used]] += delta + v[used] -= delta + minv[~used] -= delta + j0 = j1 + if p[j0] == 0: + break + while j0: # augment along the path + p[j0] = p[way[j0]] + j0 = way[j0] + cols = np.nonzero(p[1:])[0] + rows = p[1:][cols] - 1 + row_ind, col_ind = (cols, rows) if transposed else (rows, cols) + order = np.argsort(row_ind, kind="stable") # match scipy's row-sorted output + return row_ind[order].astype(np.intp), col_ind[order].astype(np.intp) diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/patches.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/patches.py new file mode 100644 index 0000000000000000000000000000000000000000..6206d8307f1218643a3e984037e21120932e0677 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/patches.py @@ -0,0 +1,266 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license +"""Monkey patches to update/extend functionality of existing functions.""" + +from __future__ import annotations + +import time +from contextlib import contextmanager +from copy import copy +from pathlib import Path +from typing import Any + +import cv2 +import numpy as np +import torch +from PIL import Image + +# OpenCV Multilanguage-friendly functions ------------------------------------------------------------------------------ +_imshow = cv2.imshow # copy to avoid recursion errors + + +def imread(filename: str, flags: int = cv2.IMREAD_COLOR) -> np.ndarray | None: + """Read an image from a file with multilanguage filename support. + + Args: + filename (str): Path to the file to read. + flags (int, optional): Flag that can take values of cv2.IMREAD_*. Controls how the image is read. + + Returns: + (np.ndarray | None): The read image array, or None if reading fails. + + Examples: + >>> img = imread("path/to/image.jpg") + >>> img = imread("path/to/image.jpg", cv2.IMREAD_GRAYSCALE) + """ + try: + file_bytes = np.fromfile(filename, np.uint8) + except (FileNotFoundError, OSError): + return None + if filename.endswith((".tiff", ".tif")): + success, frames = cv2.imdecodemulti(file_bytes, cv2.IMREAD_UNCHANGED) + if success: + # Handle multi-frame TIFFs and color images + return frames[0] if len(frames) == 1 and frames[0].ndim == 3 else np.stack(frames, axis=2) + return None + else: + im = cv2.imdecode(file_bytes, flags) + # Fallback for formats OpenCV imdecode may not support (AVIF, HEIC, HEIF) + if im is None and filename.lower().endswith((".avif", ".heic", ".heif")): + im = _imread_pil(filename, flags) + return im[..., None] if im is not None and im.ndim == 2 else im # Always ensure 3 dimensions + + +# PIL patches --------------------------------------------------------------------------------------------------------- +_image_open = Image.open # copy to avoid recursion errors +_pil_plugins_registered = False + + +def image_open(filename, *args, **kwargs): + """Open an image with PIL, lazily registering the HEIF plugin on first failure. + + This monkey-patches PIL.Image.open to add HEIC/HEIF support via pi-heif (lightweight, decode-only), avoiding the + ~800ms startup cost of importing the package unless actually needed. AVIF is supported natively by Pillow 12+ and + does not require a plugin. + + Args: + filename (str): Path to the image file. + *args (Any): Additional positional arguments passed to PIL.Image.open. + **kwargs (Any): Additional keyword arguments passed to PIL.Image.open. + + Returns: + (PIL.Image.Image): The opened PIL image. + """ + global _pil_plugins_registered + if _pil_plugins_registered: + return _image_open(filename, *args, **kwargs) + try: + return _image_open(filename, *args, **kwargs) + except Exception: + from ultralytics.utils.checks import check_requirements + + check_requirements("pi-heif") + from pi_heif import register_heif_opener + + register_heif_opener() + _pil_plugins_registered = True + return _image_open(filename, *args, **kwargs) + + +Image.open = image_open # apply patch + + +def _imread_pil(filename: str, flags: int = cv2.IMREAD_COLOR) -> np.ndarray | None: + """Read an image using PIL as fallback for formats not supported by OpenCV. + + Args: + filename (str): Path to the file to read. + flags (int, optional): OpenCV imread flags (used to determine grayscale conversion). + + Returns: + (np.ndarray | None): The read image array in BGR format, or None if reading fails. + """ + try: + with Image.open(filename) as img: + if flags == cv2.IMREAD_GRAYSCALE: + return np.asarray(img.convert("L")) + return cv2.cvtColor(np.asarray(img.convert("RGB")), cv2.COLOR_RGB2BGR) + except Exception: + return None + + +def imwrite(filename: str, img: np.ndarray, params: list[int] | None = None) -> bool: + """Write an image to a file with multilanguage filename support. + + Args: + filename (str): Path to the file to write. + img (np.ndarray): Image to write. + params (list[int], optional): Additional parameters for image encoding. + + Returns: + (bool): True if the file was written successfully, False otherwise. + + Examples: + >>> import numpy as np + >>> img = np.zeros((100, 100, 3), dtype=np.uint8) # Create a black image + >>> success = imwrite("output.jpg", img) # Write image to file + >>> print(success) + True + """ + try: + cv2.imencode(Path(filename).suffix, img, params)[1].tofile(filename) + return True + except Exception: + return False + + +def imshow(winname: str, mat: np.ndarray) -> None: + """Display an image in the specified window with multilanguage window name support. + + This function is a wrapper around OpenCV's imshow function that displays an image in a named window. It handles + multilanguage window names by encoding them properly for OpenCV compatibility. + + Args: + winname (str): Name of the window where the image will be displayed. If a window with this name already exists, + the image will be displayed in that window. + mat (np.ndarray): Image to be shown. Should be a valid numpy array representing an image. + + Examples: + >>> import numpy as np + >>> img = np.zeros((300, 300, 3), dtype=np.uint8) # Create a black image + >>> img[:100, :100] = [255, 0, 0] # Add a blue square + >>> imshow("Example Window", img) # Display the image + """ + _imshow(winname.encode("unicode_escape").decode(), mat) + + +# PyTorch functions ---------------------------------------------------------------------------------------------------- +_torch_save = torch.save + + +def torch_load(*args, **kwargs): + """Load a PyTorch model with updated arguments to avoid warnings. + + This function wraps torch.load and adds the 'weights_only' argument for PyTorch 1.13.0+ to prevent warnings. + + Args: + *args (Any): Variable length argument list to pass to torch.load. + **kwargs (Any): Arbitrary keyword arguments to pass to torch.load. + + Returns: + (Any): The loaded PyTorch object. + + Notes: + For PyTorch versions 1.13 and above, this function automatically sets `weights_only=False` if the argument is + not provided, to avoid deprecation warnings. + """ + from ultralytics.utils.torch_utils import TORCH_1_13 + + if TORCH_1_13 and "weights_only" not in kwargs: + kwargs["weights_only"] = False + + return torch.load(*args, **kwargs) + + +def torch_save(*args, **kwargs): + """Save PyTorch objects with retry mechanism for robustness. + + This function wraps torch.save with 3 retries and exponential backoff in case of save failures, which can occur due + to device flushing delays or antivirus scanning. + + Args: + *args (Any): Positional arguments to pass to torch.save. + **kwargs (Any): Keyword arguments to pass to torch.save. + + Examples: + >>> model = torch.nn.Linear(10, 1) + >>> torch_save(model.state_dict(), "model.pt") + """ + for i in range(4): # 3 retries + try: + return _torch_save(*args, **kwargs) + except RuntimeError as e: # Unable to save, possibly waiting for device to flush or antivirus scan + if i == 3: + raise e + time.sleep((2**i) / 2) # Exponential backoff: 0.5s, 1.0s, 2.0s + + +@contextmanager +def arange_patch(dynamic: bool = False, quantize: int | str | None = None, fmt: str = ""): + """Workaround for ONNX torch.arange incompatibility with FP16. + + https://github.com/pytorch/pytorch/issues/148041. + """ + if dynamic and quantize == 16 and fmt == "onnx": + func = torch.arange + + def arange(*args, dtype=None, **kwargs): + """Wrap torch.arange to cast dtype after creation instead of passing it directly.""" + return func(*args, **kwargs).to(dtype) # cast to dtype instead of passing dtype + + torch.arange = arange # patch + yield + torch.arange = func # unpatch + else: + yield + + +@contextmanager +def onnx_export_patch(): + """Workaround for ONNX export issues in PyTorch 2.9+ with Dynamo enabled.""" + from ultralytics.utils.torch_utils import TORCH_2_9 + + if TORCH_2_9: + func = torch.onnx.export + + def torch_export(*args, **kwargs): + """Export model to ONNX format with Dynamo disabled for compatibility.""" + return func(*args, **kwargs, dynamo=False) + + torch.onnx.export = torch_export # patch + yield + torch.onnx.export = func # unpatch + else: + yield + + +@contextmanager +def override_configs(args, overrides: dict[str, Any] | None = None): + """Context manager to temporarily override configurations in args. + + Args: + args (IterableSimpleNamespace): Original configuration arguments. + overrides (dict[str, Any] | None): Dictionary of overrides to apply. + + Yields: + (IterableSimpleNamespace): Configuration arguments with overrides applied. + """ + if overrides: + original_args = copy(args) + for key, value in overrides.items(): + setattr(args, key, value) + try: + yield args + finally: + args.__dict__.update(original_args.__dict__) + else: + yield args diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/plotting.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/plotting.py new file mode 100644 index 0000000000000000000000000000000000000000..643e33606eb4a0305ea9190c3910120ad9833822 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/plotting.py @@ -0,0 +1,1150 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import math +from collections.abc import Callable +from pathlib import Path +from typing import Any + +import cv2 +import numpy as np +import torch +from PIL import Image, ImageDraw, ImageFont +from PIL import __version__ as pil_version + +from ultralytics.utils import IS_COLAB, IS_KAGGLE, LOGGER, TryExcept, ops, plt_settings, threaded +from ultralytics.utils.checks import check_font, check_version, is_ascii +from ultralytics.utils.files import increment_path + + +def _gaussian_filter1d(y, sigma: int = 3, truncate: float = 4.0) -> np.ndarray: + """Smooth a 1D array with a Gaussian kernel (NumPy replacement for scipy.ndimage.gaussian_filter1d). + + Args: + y (np.ndarray): Input 1D array to smooth. + sigma (int): Standard deviation of the Gaussian kernel. + truncate (float): Truncate the kernel at this many standard deviations. + + Returns: + (np.ndarray): Smoothed 1D array with the same length as the input. + """ + y = np.asarray(y, dtype=float) + radius = int(truncate * sigma + 0.5) + kernel = np.exp(-0.5 * (np.arange(-radius, radius + 1) / sigma) ** 2) + kernel /= kernel.sum() + # scipy 'reflect' boundary mode is equivalent to NumPy 'symmetric' + return np.convolve(np.pad(y, radius, mode="symmetric"), kernel, mode="valid") + + +class Colors: + """Ultralytics color palette for visualization and plotting. + + This class provides methods to work with the Ultralytics color palette, including converting hex color codes to RGB + values and accessing predefined color schemes for object detection and pose estimation. + + ## Ultralytics Color Palette + + | Index | Color | HEX | RGB | + |-------|-------------------------------------------------------------------|-----------|-------------------| + | 0 | | `#042aff` | (4, 42, 255) | + | 1 | | `#0bdbeb` | (11, 219, 235) | + | 2 | | `#f3f3f3` | (243, 243, 243) | + | 3 | | `#00dfb7` | (0, 223, 183) | + | 4 | | `#111f68` | (17, 31, 104) | + | 5 | | `#ff6fdd` | (255, 111, 221) | + | 6 | | `#ff444f` | (255, 68, 79) | + | 7 | | `#cced00` | (204, 237, 0) | + | 8 | | `#00f344` | (0, 243, 68) | + | 9 | | `#bd00ff` | (189, 0, 255) | + | 10 | | `#00b4ff` | (0, 180, 255) | + | 11 | | `#dd00ba` | (221, 0, 186) | + | 12 | | `#00ffff` | (0, 255, 255) | + | 13 | | `#26c000` | (38, 192, 0) | + | 14 | | `#01ffb3` | (1, 255, 179) | + | 15 | | `#7d24ff` | (125, 36, 255) | + | 16 | | `#7b0068` | (123, 0, 104) | + | 17 | | `#ff1b6c` | (255, 27, 108) | + | 18 | | `#fc6d2f` | (252, 109, 47) | + | 19 | | `#a2ff0b` | (162, 255, 11) | + + ## Pose Color Palette + + | Index | Color | HEX | RGB | + |-------|-------------------------------------------------------------------|-----------|-------------------| + | 0 | | `#ff8000` | (255, 128, 0) | + | 1 | | `#ff9933` | (255, 153, 51) | + | 2 | | `#ffb266` | (255, 178, 102) | + | 3 | | `#e6e600` | (230, 230, 0) | + | 4 | | `#ff99ff` | (255, 153, 255) | + | 5 | | `#99ccff` | (153, 204, 255) | + | 6 | | `#ff66ff` | (255, 102, 255) | + | 7 | | `#ff33ff` | (255, 51, 255) | + | 8 | | `#66b2ff` | (102, 178, 255) | + | 9 | | `#3399ff` | (51, 153, 255) | + | 10 | | `#ff9999` | (255, 153, 153) | + | 11 | | `#ff6666` | (255, 102, 102) | + | 12 | | `#ff3333` | (255, 51, 51) | + | 13 | | `#99ff99` | (153, 255, 153) | + | 14 | | `#66ff66` | (102, 255, 102) | + | 15 | | `#33ff33` | (51, 255, 51) | + | 16 | | `#00ff00` | (0, 255, 0) | + | 17 | | `#0000ff` | (0, 0, 255) | + | 18 | | `#ff0000` | (255, 0, 0) | + | 19 | | `#ffffff` | (255, 255, 255) | + + !!! note "Ultralytics Brand Colors" + + For Ultralytics brand colors see [https://www.ultralytics.com/brand](https://www.ultralytics.com/brand). + Please use the official Ultralytics colors for all marketing materials. + + Attributes: + palette (list[tuple]): List of RGB color tuples for general use. + n (int): The number of colors in the palette. + pose_palette (np.ndarray): A specific color palette array for pose estimation with dtype np.uint8. + + Examples: + >>> from ultralytics.utils.plotting import Colors + >>> colors = Colors() + >>> colors(5, True) # Returns BGR format: (221, 111, 255) + >>> colors(5, False) # Returns RGB format: (255, 111, 221) + """ + + def __init__(self): + """Initialize the Ultralytics color palette from a fixed list of hex color codes.""" + hexs = ( + "042AFF", + "0BDBEB", + "F3F3F3", + "00DFB7", + "111F68", + "FF6FDD", + "FF444F", + "CCED00", + "00F344", + "BD00FF", + "00B4FF", + "DD00BA", + "00FFFF", + "26C000", + "01FFB3", + "7D24FF", + "7B0068", + "FF1B6C", + "FC6D2F", + "A2FF0B", + ) + self.palette = [self.hex2rgb(f"#{c}") for c in hexs] + self.n = len(self.palette) + self.pose_palette = np.array( + [ + [255, 128, 0], + [255, 153, 51], + [255, 178, 102], + [230, 230, 0], + [255, 153, 255], + [153, 204, 255], + [255, 102, 255], + [255, 51, 255], + [102, 178, 255], + [51, 153, 255], + [255, 153, 153], + [255, 102, 102], + [255, 51, 51], + [153, 255, 153], + [102, 255, 102], + [51, 255, 51], + [0, 255, 0], + [0, 0, 255], + [255, 0, 0], + [255, 255, 255], + ], + dtype=np.uint8, + ) + + def __call__(self, i: int | torch.Tensor, bgr: bool = False) -> tuple: + """Return a color from the palette by index. + + Args: + i (int | torch.Tensor): Color index. + bgr (bool, optional): Whether to return BGR format instead of RGB. + + Returns: + (tuple): RGB or BGR color tuple. + """ + c = self.palette[int(i) % self.n] + return (c[2], c[1], c[0]) if bgr else c + + @staticmethod + def hex2rgb(h: str) -> tuple: + """Convert hex color codes to RGB values (i.e. default PIL order).""" + return tuple(int(h[1 + i : 1 + i + 2], 16) for i in (0, 2, 4)) + + +colors = Colors() # create instance for 'from utils.plots import colors' + + +class Annotator: + """Ultralytics Annotator for train/val mosaics and JPGs and predictions annotations. + + Attributes: + im (Image.Image | np.ndarray): The image to annotate. + pil (bool): Whether to use PIL or cv2 for drawing annotations. + font (ImageFont.truetype | ImageFont.load_default): Font used for text annotations. + lw (int): Line width for drawing. + skeleton (list[list[int]]): Skeleton structure for keypoints. + limb_color (np.ndarray): Color palette for limbs. + kpt_color (np.ndarray): Color palette for keypoints. + dark_colors (set): Set of colors considered dark for text contrast. + light_colors (set): Set of colors considered light for text contrast. + + Examples: + >>> from ultralytics.utils.plotting import Annotator + >>> im0 = cv2.imread("test.png") + >>> annotator = Annotator(im0, line_width=10) + >>> annotator.box_label([10, 10, 100, 100], "person", (255, 0, 0)) + """ + + def __init__( + self, + im, + line_width: int | None = None, + font_size: int | None = None, + font: str = "Arial.ttf", + pil: bool = False, + example: str = "abc", + ): + """Initialize the Annotator class with image and line width along with color palette for keypoints and limbs.""" + non_ascii = not is_ascii(example) # non-latin labels, i.e. asian, arabic, cyrillic + input_is_pil = isinstance(im, Image.Image) + self.pil = pil or non_ascii or input_is_pil + self.lw = line_width or max(round(sum(im.size if input_is_pil else im.shape) / 2 * 0.003), 2) + if not input_is_pil: + if im.shape[2] == 1: # handle grayscale + im = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR) + elif im.shape[2] == 2: # handle 2-channel images + im = np.ascontiguousarray(np.dstack((im, np.zeros_like(im[..., :1])))) + elif im.shape[2] > 3: # multispectral + im = np.ascontiguousarray(im[..., :3]) + if self.pil: # use PIL + self.im = im if input_is_pil else Image.fromarray(im) # stay in BGR since color palette is in BGR + if self.im.mode not in {"RGB", "RGBA"}: # multispectral + self.im = self.im.convert("RGB") + self.draw = ImageDraw.Draw(self.im, "RGBA") + try: + font = check_font("Arial.Unicode.ttf" if non_ascii else font) + size = font_size or max(round(sum(self.im.size) / 2 * 0.035), 12) + self.font = ImageFont.truetype(str(font), size) + except Exception: + self.font = ImageFont.load_default() + # Deprecation fix for w, h = getsize(string) -> _, _, w, h = getbox(string) + if check_version(pil_version, "9.2.0"): + self.font.getsize = lambda x: self.font.getbbox(x)[2:4] # text width, height + else: # use cv2 + assert im.data.contiguous, "Image not contiguous. Apply np.ascontiguousarray(im) to Annotator input images." + self.im = im if im.flags.writeable else im.copy() + self.tf = max(self.lw - 1, 1) # font thickness + self.sf = self.lw / 3 # font scale + # Pose + self.skeleton = [ + [16, 14], + [14, 12], + [17, 15], + [15, 13], + [12, 13], + [6, 12], + [7, 13], + [6, 7], + [6, 8], + [7, 9], + [8, 10], + [9, 11], + [2, 3], + [1, 2], + [1, 3], + [2, 4], + [3, 5], + [4, 6], + [5, 7], + ] + + self.limb_color = colors.pose_palette[[9, 9, 9, 9, 7, 7, 7, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16]] + self.kpt_color = colors.pose_palette[[16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9]] + self.dark_colors = { + (235, 219, 11), + (243, 243, 243), + (183, 223, 0), + (221, 111, 255), + (0, 237, 204), + (68, 243, 0), + (255, 255, 0), + (179, 255, 1), + (11, 255, 162), + } + self.light_colors = { + (255, 42, 4), + (79, 68, 255), + (255, 0, 189), + (255, 180, 0), + (186, 0, 221), + (0, 192, 38), + (255, 36, 125), + (104, 0, 123), + (108, 27, 255), + (47, 109, 252), + (104, 31, 17), + } + + def get_txt_color(self, color: tuple = (128, 128, 128), txt_color: tuple = (255, 255, 255)) -> tuple: + """Assign text color based on background color. + + Args: + color (tuple, optional): The background color of the rectangle for text. + txt_color (tuple, optional): The fallback color of the text. + + Returns: + (tuple): Text color for label. + + Examples: + >>> from ultralytics.utils.plotting import Annotator + >>> im0 = cv2.imread("test.png") + >>> annotator = Annotator(im0, line_width=10) + >>> annotator.get_txt_color(color=(104, 31, 17)) # return (255, 255, 255) + """ + if color in self.dark_colors: + return 104, 31, 17 + elif color in self.light_colors: + return 255, 255, 255 + else: + return txt_color + + def box_label(self, box, label: str = "", color: tuple = (128, 128, 128), txt_color: tuple = (255, 255, 255)): + """Draw a bounding box on an image with a given label. + + Args: + box (tuple): The bounding box coordinates (x1, y1, x2, y2). + label (str, optional): The text label to be displayed. + color (tuple, optional): The background color of the rectangle. + txt_color (tuple, optional): The color of the text. + + Examples: + >>> from ultralytics.utils.plotting import Annotator + >>> im0 = cv2.imread("test.png") + >>> annotator = Annotator(im0, line_width=10) + >>> annotator.box_label(box=[10, 20, 30, 40], label="person") + """ + txt_color = self.get_txt_color(color, txt_color) + if isinstance(box, torch.Tensor): + box = box.tolist() + + multi_points = isinstance(box[0], list) # multiple points with shape (n, 2) + p1 = [int(b) for b in box[0]] if multi_points else (int(box[0]), int(box[1])) + if self.pil: + self.draw.polygon( + [tuple(b) for b in box], width=self.lw, outline=color + ) if multi_points else self.draw.rectangle(box, width=self.lw, outline=color) + if label: + w, h = self.font.getsize(label) # text width, height + outside = p1[1] >= h # label fits outside box + if p1[0] > self.im.size[0] - w: # size is (w, h), check if label extend beyond right side of image + p1 = self.im.size[0] - w, p1[1] + self.draw.rectangle( + (p1[0], p1[1] - h if outside else p1[1], p1[0] + w + 1, p1[1] + 1 if outside else p1[1] + h + 1), + fill=color, + ) + # self.draw.text([box[0], box[1]], label, fill=txt_color, font=self.font, anchor='ls') # for PIL>8.0 + self.draw.text((p1[0], p1[1] - h if outside else p1[1]), label, fill=txt_color, font=self.font) + else: # cv2 + cv2.polylines( + self.im, [np.asarray(box, dtype=int)], True, color, self.lw + ) if multi_points else cv2.rectangle( + self.im, p1, (int(box[2]), int(box[3])), color, thickness=self.lw, lineType=cv2.LINE_AA + ) + if label: + w, h = cv2.getTextSize(label, 0, fontScale=self.sf, thickness=self.tf)[0] # text width, height + h += 3 # add pixels to pad text + outside = p1[1] >= h # label fits outside box + if p1[0] > self.im.shape[1] - w: # shape is (h, w), check if label extend beyond right side of image + p1 = self.im.shape[1] - w, p1[1] + p2 = p1[0] + w, p1[1] - h if outside else p1[1] + h + cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA) # filled + cv2.putText( + self.im, + label, + (p1[0], p1[1] - 2 if outside else p1[1] + h - 1), + 0, + self.sf, + txt_color, + thickness=self.tf, + lineType=cv2.LINE_AA, + ) + + def masks(self, masks, colors, im_gpu: torch.Tensor = None, alpha: float = 0.5, retina_masks: bool = False): + """Plot masks on image. + + Args: + masks (torch.Tensor | np.ndarray): Predicted masks with shape [n, h, w]. + colors (list[list[int]]): Colors for predicted masks, [[r, g, b] * n]. + im_gpu (torch.Tensor | None): Image on GPU with shape [3, h, w], range [0, 1]. + alpha (float, optional): Mask transparency: 0.0 fully transparent, 1.0 opaque. + retina_masks (bool, optional): Whether to use high resolution masks or not. + """ + if self.pil: + # Convert to numpy first + self.im = np.asarray(self.im).copy() + if im_gpu is None: + assert isinstance(masks, np.ndarray), "`masks` must be a np.ndarray if `im_gpu` is not provided." + overlay = self.im.copy() + for i, mask in enumerate(masks): + overlay[mask.astype(bool)] = colors[i] + self.im = cv2.addWeighted(self.im, 1 - alpha, overlay, alpha, 0) + else: + assert isinstance(masks, torch.Tensor), "'masks' must be a torch.Tensor if 'im_gpu' is provided." + if len(masks) == 0: + self.im[:] = im_gpu.permute(1, 2, 0).contiguous().cpu().numpy() * 255 + return + if im_gpu.device != masks.device: + im_gpu = im_gpu.to(masks.device) + + ih, iw = self.im.shape[:2] + if not retina_masks: + # Use scale_masks to properly remove padding and upsample, convert bool to float first + masks = ops.scale_masks(masks[None].float(), (ih, iw))[0] > 0.5 + # Convert original BGR image to RGB tensor + im_gpu = ( + torch.from_numpy(self.im).to(masks.device).permute(2, 0, 1).flip(0).contiguous().float() / 255.0 + ) + + colors = torch.tensor(colors, device=masks.device, dtype=torch.float32) / 255.0 # shape(n,3) + colors = colors[:, None, None] # shape(n,1,1,3) + masks = masks.unsqueeze(3) # shape(n,h,w,1) + masks_color = masks * (colors * alpha) # shape(n,h,w,3) + inv_alpha_masks = (1 - masks * alpha).cumprod(0) # shape(n,h,w,1) + mcs = masks_color.max(dim=0).values # shape(h,w,3) + + im_gpu = im_gpu.flip(dims=[0]).permute(1, 2, 0).contiguous() # shape(h,w,3) + im_gpu = im_gpu * inv_alpha_masks[-1] + mcs + self.im[:] = (im_gpu * 255).byte().cpu().numpy() + if self.pil: + # Convert im back to PIL and update draw + self.fromarray(self.im) + + def semantic_mask(self, mask, alpha: float = 0.5, ignore_index: int = 255): + """Plot a semantic segmentation mask on the image. + + Args: + mask (np.ndarray): Semantic mask with shape [h, w] containing integer class indices. + alpha (float, optional): Mask transparency: 0.0 fully transparent, 1.0 opaque. + ignore_index (int, optional): Class index to ignore (e.g., 255 for void/ignore). + """ + if self.pil: + # Convert to numpy first + self.im = np.asarray(self.im).copy() + overlay = np.zeros_like(self.im) + for cls_id in np.unique(mask): + if cls_id == ignore_index: + continue + overlay[mask == cls_id] = colors(int(cls_id), True) + self.im = cv2.addWeighted(self.im, 1 - alpha, overlay, alpha, 0) + if self.pil: + # Convert im back to PIL and update draw + self.fromarray(self.im) + + def kpts( + self, + kpts, + shape: tuple = (640, 640), + radius: int | None = None, + kpt_line: bool = True, + conf_thres: float = 0.25, + kpt_color: tuple | None = None, + ): + """Plot keypoints on the image. + + Args: + kpts (torch.Tensor): Keypoints, shape [17, 3] (x, y, confidence). + shape (tuple, optional): Image shape (h, w). + radius (int, optional): Keypoint radius. + kpt_line (bool, optional): Draw lines between keypoints. + conf_thres (float, optional): Confidence threshold. + kpt_color (tuple, optional): Keypoint color. + + Notes: + - `kpt_line=True` currently only supports human pose plotting. + - Modifies self.im in-place. + - If self.pil is True, converts image to numpy array and back to PIL. + """ + radius = radius if radius is not None else self.lw + if self.pil: + # Convert to numpy first + self.im = np.asarray(self.im).copy() + nkpt, ndim = kpts.shape + is_pose = nkpt == 17 and ndim in {2, 3} + kpt_line &= is_pose # `kpt_line=True` for now only supports human pose plotting + for i, k in enumerate(kpts): + color_k = kpt_color or (self.kpt_color[i].tolist() if is_pose else colors(i)) + x_coord, y_coord = k[0], k[1] + if x_coord % shape[1] != 0 and y_coord % shape[0] != 0: + if len(k) == 3: + conf = k[2] + if conf < conf_thres: + continue + cv2.circle(self.im, (int(x_coord), int(y_coord)), radius, color_k, -1, lineType=cv2.LINE_AA) + + if kpt_line: + ndim = kpts.shape[-1] + for i, sk in enumerate(self.skeleton): + pos1 = (int(kpts[(sk[0] - 1), 0]), int(kpts[(sk[0] - 1), 1])) + pos2 = (int(kpts[(sk[1] - 1), 0]), int(kpts[(sk[1] - 1), 1])) + if ndim == 3: + conf1 = kpts[(sk[0] - 1), 2] + conf2 = kpts[(sk[1] - 1), 2] + if conf1 < conf_thres or conf2 < conf_thres: + continue + if pos1[0] % shape[1] == 0 or pos1[1] % shape[0] == 0 or pos1[0] < 0 or pos1[1] < 0: + continue + if pos2[0] % shape[1] == 0 or pos2[1] % shape[0] == 0 or pos2[0] < 0 or pos2[1] < 0: + continue + cv2.line( + self.im, + pos1, + pos2, + kpt_color or self.limb_color[i].tolist(), + thickness=int(np.ceil(self.lw / 2)), + lineType=cv2.LINE_AA, + ) + if self.pil: + # Convert im back to PIL and update draw + self.fromarray(self.im) + + def rectangle(self, xy, fill=None, outline=None, width: int = 1): + """Add rectangle to image (PIL-only).""" + self.draw.rectangle(xy, fill, outline, width) + + def text(self, xy, text: str, txt_color: tuple = (255, 255, 255), anchor: str = "top", box_color: tuple = ()): + """Add text to an image using PIL or cv2. + + Args: + xy (list[int]): Top-left coordinates for text placement. + text (str): Text to be drawn. + txt_color (tuple, optional): Text color. + anchor (str, optional): Text anchor position ('top' or 'bottom'). + box_color (tuple, optional): Box background color with optional alpha. + """ + if self.pil: + w, h = self.font.getsize(text) + if anchor == "bottom": # start y from font bottom + xy[1] += 1 - h + for line in text.split("\n"): + if box_color: + # Draw rectangle for each line + w, h = self.font.getsize(line) + self.draw.rectangle((xy[0], xy[1], xy[0] + w + 1, xy[1] + h + 1), fill=box_color) + self.draw.text(xy, line, fill=txt_color, font=self.font) + xy[1] += h + else: + if box_color: + w, h = cv2.getTextSize(text, 0, fontScale=self.sf, thickness=self.tf)[0] + h += 3 # add pixels to pad text + outside = xy[1] >= h # label fits outside box + p2 = xy[0] + w, xy[1] - h if outside else xy[1] + h + cv2.rectangle(self.im, xy, p2, box_color, -1, cv2.LINE_AA) # filled + cv2.putText(self.im, text, xy, 0, self.sf, txt_color, thickness=self.tf, lineType=cv2.LINE_AA) + + def fromarray(self, im): + """Update `self.im` from a NumPy array or PIL image.""" + self.im = im if isinstance(im, Image.Image) else Image.fromarray(im) + self.draw = ImageDraw.Draw(self.im) + + def result(self, pil=False): + """Return annotated image as array or PIL image.""" + im = np.asarray(self.im) # self.im is in BGR + return Image.fromarray(im[..., ::-1]) if pil else im + + def show(self, title: str | None = None): + """Show the annotated image.""" + im = Image.fromarray(np.asarray(self.im)[..., ::-1]) # Convert BGR NumPy array to RGB PIL Image + if IS_COLAB or IS_KAGGLE: # cannot use IS_JUPYTER as it runs for all IPython environments + try: + display(im) # noqa - display() function only available in ipython environments + except ImportError as e: + LOGGER.warning(f"Unable to display image in Jupyter notebooks: {e}") + else: + im.show(title=title) + + def save(self, filename: str = "image.jpg"): + """Save the annotated image to 'filename'.""" + cv2.imwrite(filename, np.asarray(self.im)) + + @staticmethod + def get_bbox_dimension(bbox: tuple | list): + """Calculate the dimensions and area of a bounding box. + + Args: + bbox (tuple | list): Bounding box coordinates in the format (x_min, y_min, x_max, y_max). + + Returns: + width (float): Width of the bounding box. + height (float): Height of the bounding box. + area (float): Area enclosed by the bounding box. + + Examples: + >>> from ultralytics.utils.plotting import Annotator + >>> im0 = cv2.imread("test.png") + >>> annotator = Annotator(im0, line_width=10) + >>> annotator.get_bbox_dimension(bbox=[10, 20, 30, 40]) + """ + x_min, y_min, x_max, y_max = bbox + width = x_max - x_min + height = y_max - y_min + return width, height, width * height + + +@TryExcept() +@plt_settings() +def plot_labels(boxes, cls, names=(), save_dir=Path(""), on_plot=None): + """Plot training labels including class histograms and box statistics. + + Args: + boxes (np.ndarray): Bounding box coordinates in format [x, y, width, height]. + cls (np.ndarray): Class indices. + names (dict, optional): Dictionary mapping class indices to class names. + save_dir (Path, optional): Directory to save the plot. + on_plot (Callable, optional): Function to call after plot is saved. + """ + import matplotlib.pyplot as plt # scope for faster 'import ultralytics' + import polars + from matplotlib.colors import LinearSegmentedColormap + + # Plot dataset labels + LOGGER.info(f"Plotting labels to {save_dir / 'labels.jpg'}... ") + nc = int(cls.max() + 1) # number of classes + boxes = boxes[:1000000] # limit to 1M boxes + x = polars.DataFrame(boxes, schema=["x", "y", "width", "height"]) + + # Matplotlib labels + subplot_3_4_color = LinearSegmentedColormap.from_list("white_blue", ["white", "blue"]) + ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)[1].ravel() + y = ax[0].hist(cls, bins=np.linspace(0, nc, nc + 1) - 0.5, rwidth=0.8) + for i in range(nc): + y[2].patches[i].set_color([x / 255 for x in colors(i)]) + ax[0].set_ylabel("instances") + if 0 < len(names) < 30: + ax[0].set_xticks(range(len(names))) + ax[0].set_xticklabels(list(names.values()), rotation=90, fontsize=10) + ax[0].bar_label(y[2]) + else: + ax[0].set_xlabel("classes") + boxes = np.column_stack([0.5 - boxes[:, 2:4] / 2, 0.5 + boxes[:, 2:4] / 2]) * 1000 + img = Image.fromarray(np.ones((1000, 1000, 3), dtype=np.uint8) * 255) + for class_id, box in zip(cls[:500], boxes[:500]): + ImageDraw.Draw(img).rectangle(box.tolist(), width=1, outline=colors(class_id)) # plot + ax[1].imshow(img) + ax[1].axis("off") + + ax[2].hist2d(x["x"], x["y"], bins=50, cmap=subplot_3_4_color) + ax[2].set_xlabel("x") + ax[2].set_ylabel("y") + ax[3].hist2d(x["width"], x["height"], bins=50, cmap=subplot_3_4_color) + ax[3].set_xlabel("width") + ax[3].set_ylabel("height") + for a in {0, 1, 2, 3}: + for s in {"top", "right", "left", "bottom"}: + ax[a].spines[s].set_visible(False) + + fname = save_dir / "labels.jpg" + plt.savefig(fname, dpi=200) + plt.close() + if on_plot: + on_plot(fname) + + +def save_one_box( + xyxy, + im, + file: Path = Path("im.jpg"), + gain: float = 1.02, + pad: int = 10, + square: bool = False, + BGR: bool = False, + save: bool = True, +): + """Save image crop as {file} with crop size multiple {gain} and {pad} pixels. Save and/or return crop. + + This function takes a bounding box and an image, and then saves a cropped portion of the image according to the + bounding box. Optionally, the crop can be squared, and the function allows for gain and padding adjustments to the + bounding box. + + Args: + xyxy (torch.Tensor | list): A tensor or list representing the bounding box in xyxy format. + im (np.ndarray): The input image. + file (Path, optional): The path where the cropped image will be saved. + gain (float, optional): A multiplicative factor to increase the size of the bounding box. + pad (int, optional): The number of pixels to add to the width and height of the bounding box. + square (bool, optional): If True, the bounding box will be transformed into a square. + BGR (bool, optional): If True, the image will be returned in BGR format, otherwise in RGB. + save (bool, optional): If True, the cropped image will be saved to disk. + + Returns: + (np.ndarray): The cropped image. + + Examples: + >>> from ultralytics.utils.plotting import save_one_box + >>> xyxy = [50, 50, 150, 150] + >>> im = cv2.imread("image.jpg") + >>> cropped_im = save_one_box(xyxy, im, file="cropped.jpg", square=True) + """ + if not isinstance(xyxy, torch.Tensor): # may be list + xyxy = torch.stack(xyxy) + b = ops.xyxy2xywh(xyxy.view(-1, 4)) # boxes + if square: + b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # attempt rectangle to square + b[:, 2:] = b[:, 2:] * gain + pad # box wh * gain + pad + xyxy = ops.xywh2xyxy(b).long() + xyxy = ops.clip_boxes(xyxy, im.shape) + grayscale = im.shape[2] == 1 # grayscale image + crop = im[int(xyxy[0, 1]) : int(xyxy[0, 3]), int(xyxy[0, 0]) : int(xyxy[0, 2]), :: (1 if BGR or grayscale else -1)] + if save: + file.parent.mkdir(parents=True, exist_ok=True) # make directory + f = str(increment_path(file).with_suffix(".jpg")) + # cv2.imwrite(f, crop) # save BGR, https://github.com/ultralytics/yolov5/issues/7007 chroma subsampling issue + crop = crop.squeeze(-1) if grayscale else crop[..., ::-1] if BGR else crop + Image.fromarray(crop).save(f, quality=95, subsampling=0) # save RGB + return crop + + +@threaded +def plot_images( + labels: dict[str, Any], + images: torch.Tensor | np.ndarray = np.zeros((0, 3, 640, 640), dtype=np.float32), + paths: list[str] | None = None, + fname: str = "images.jpg", + names: dict[int, str] | None = None, + on_plot: Callable | None = None, + max_size: int = 1920, + max_subplots: int = 16, + save: bool = True, + conf_thres: float = 0.25, + show_labels: bool = True, + show_conf: bool = True, +) -> np.ndarray | None: + """Plot image grid with labels, bounding boxes, masks, and keypoints. + + Args: + labels (dict[str, Any]): Dictionary containing detection data with keys like 'cls', 'bboxes', 'conf', 'masks', + 'keypoints', 'batch_idx', 'img'. + images (torch.Tensor | np.ndarray): Batch of images to plot. Shape: (batch_size, channels, height, width). + paths (list[str] | None): List of file paths for each image in the batch. + fname (str): Output filename for the plotted image grid. + names (dict[int, str] | None): Dictionary mapping class indices to class names. + on_plot (Callable | None): Callback function to be called after saving the plot. + max_size (int): Maximum size of the output image grid. + max_subplots (int): Maximum number of subplots in the image grid. + save (bool): Whether to save the plotted image grid to a file. + conf_thres (float): Confidence threshold for displaying detections. + show_labels (bool): Whether to display class labels. + show_conf (bool): Whether to display confidence values. + + Returns: + (np.ndarray | None): Plotted image grid as a numpy array if save is False, None otherwise. + + Notes: + This function supports both tensor and numpy array inputs. It will automatically + convert tensor inputs to numpy arrays for processing. + + Channel Support: + - 1 channel: Grayscale + - 2 channels: Third channel added as zeros + - 3 channels: Used as-is (standard RGB) + - 4+ channels: Cropped to first 3 channels + """ + for k in {"cls", "bboxes", "conf", "masks", "keypoints", "batch_idx", "images", "semantic_mask"}: + if k not in labels: + continue + if k == "cls" and labels[k].ndim == 2: + labels[k] = labels[k].squeeze(1) # squeeze if shape is (n, 1) + if isinstance(labels[k], torch.Tensor): + labels[k] = labels[k].cpu().numpy() + + cls = labels.get("cls", np.zeros(0, dtype=np.int64)) + batch_idx = labels.get("batch_idx", np.zeros(cls.shape, dtype=np.int64)) + bboxes = labels.get("bboxes", np.zeros(0, dtype=np.float32)) + confs = labels.get("conf", None) + masks = labels.get("masks", np.zeros(0, dtype=np.uint8)) + kpts = labels.get("keypoints", np.zeros(0, dtype=np.float32)) + semantic_masks = labels.get("semantic_mask", np.zeros(0, dtype=np.int64)) + images = labels.get("img", images) # default to input images + + if len(images) and isinstance(images, torch.Tensor): + images = images.cpu().float().numpy() + + # Handle 2-ch and n-ch images + c = images.shape[1] + if c == 2: + zero = np.zeros_like(images[:, :1]) + images = np.concatenate((images, zero), axis=1) # pad 2-ch with a black channel + elif c > 3: + images = images[:, :3] # crop multispectral images to first 3 channels + + bs, _, h, w = images.shape # batch size, _, height, width + bs = min(bs, max_subplots) # limit plot images + ns = np.ceil(bs**0.5) # number of subplots (square) + if np.max(images[0]) <= 1: + images *= 255 # de-normalise (optional) + + # Build Image + mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8) # init + for i in range(bs): + x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin + mosaic[y : y + h, x : x + w, :] = images[i].transpose(1, 2, 0) + + # Resize (optional) + scale = max_size / ns / max(h, w) + if scale < 1: + h = math.ceil(scale * h) + w = math.ceil(scale * w) + mosaic = cv2.resize(mosaic, tuple(int(x * ns) for x in (w, h))) + + # Annotate + fs = int((h + w) * ns * 0.01) # font size + fs = max(fs, 18) # ensure that the font size is large enough to be easily readable. + annotator = Annotator(mosaic, line_width=round(fs / 10), font_size=fs, pil=True, example=str(names)) + for i in range(bs): + x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin + annotator.rectangle([x, y, x + w, y + h], None, (255, 255, 255), width=2) # borders + if paths: + annotator.text([x + 5, y + 5], text=Path(paths[i]).name[:40], txt_color=(220, 220, 220)) # filenames + if len(cls) > 0: + idx = batch_idx == i + classes = cls[idx].astype("int") + labels = confs is None + conf = confs[idx] if confs is not None else None # check for confidence presence (label vs pred) + + if len(bboxes): + boxes = bboxes[idx] + if len(boxes): + if boxes[:, :4].max() <= 1.1: # if normalized with tolerance 0.1 + boxes[..., [0, 2]] *= w # scale to pixels + boxes[..., [1, 3]] *= h + elif scale < 1: # absolute coords need scale if image scales + boxes[..., :4] *= scale + boxes[..., 0] += x + boxes[..., 1] += y + is_obb = boxes.shape[-1] == 5 # xywhr + boxes = ops.xywhr2xyxyxyxy(boxes) if is_obb else ops.xywh2xyxy(boxes) + for j, box in enumerate(boxes.astype(np.int64).tolist()): + c = classes[j] + color = colors(c) + c = names.get(c, c) if names else c + if labels or conf[j] > conf_thres: + conf_text = f"{conf[j]:.1f}" if conf is not None else "" + label = f"{c}" if show_labels else "" + label += f" {conf_text}".strip() if show_conf else "" + annotator.box_label(box, label, color=color) + + elif len(classes): + for c in classes: + color = colors(c) + c = names.get(c, c) if names else c + label = f"{c}" if labels else f"{c} {conf[0]:.1f}" + annotator.text([x, y], label, txt_color=color, box_color=(64, 64, 64, 128)) + + # Plot keypoints + if len(kpts): + kpts_ = kpts[idx].copy() + if len(kpts_): + if kpts_[..., 0].max() <= 1.01 or kpts_[..., 1].max() <= 1.01: # if normalized with tolerance .01 + kpts_[..., 0] *= w # scale to pixels + kpts_[..., 1] *= h + elif scale < 1: # absolute coords need scale if image scales + kpts_ *= scale + kpts_[..., 0] += x + kpts_[..., 1] += y + for j in range(len(kpts_)): + if labels or conf[j] > conf_thres: + annotator.kpts(kpts_[j], conf_thres=conf_thres) + + # Plot masks + if len(masks): + if idx.shape[0] == masks.shape[0] and masks.max() <= 1: # overlap_mask=False + image_masks = masks[idx] + else: # overlap_mask=True + image_masks = masks[[i]] # (1, 640, 640) + nl = idx.sum() + index = np.arange(1, nl + 1).reshape((nl, 1, 1)) + image_masks = (image_masks == index).astype(np.float32) + + im = np.asarray(annotator.im).copy() + for j in range(len(image_masks)): + if labels or conf[j] > conf_thres: + color = colors(classes[j]) + mh, mw = image_masks[j].shape + if mh != h or mw != w: + mask = image_masks[j].astype(np.uint8) + mask = cv2.resize(mask, (w, h)) + mask = mask.astype(bool) + else: + mask = image_masks[j].astype(bool) + try: + im[y : y + h, x : x + w, :][mask] = ( + im[y : y + h, x : x + w, :][mask] * 0.4 + np.array(color) * 0.6 + ) + except Exception: + pass + annotator.fromarray(im) + + # Plot semantic masks + if len(semantic_masks) and i < len(semantic_masks): + mask = semantic_masks[i] + mh, mw = mask.shape + if mh != h or mw != w: + mask = cv2.resize(mask.astype(np.uint8), (w, h), interpolation=cv2.INTER_NEAREST) + im = np.asarray(annotator.im).copy() + sub_annotator = Annotator(np.ascontiguousarray(im[y : y + h, x : x + w]), line_width=1, pil=False) + sub_annotator.semantic_mask(mask, alpha=0.4) + im[y : y + h, x : x + w] = sub_annotator.im + annotator.fromarray(im) + if not save: + return np.asarray(annotator.im) + annotator.im.save(fname) # save + if on_plot: + on_plot(fname) + + +@plt_settings() +def plot_results(file: str = "path/to/results.csv", dir: str = "", on_plot: Callable | None = None): + """Plot training results from a results CSV file. The function supports various types of data including instance + segmentation, semantic segmentation, pose estimation, and classification. Plots are saved as 'results.png' in + the directory where the CSV is located. + + Args: + file (str, optional): Path to the CSV file containing the training results. + dir (str, optional): Directory where the CSV file is located if 'file' is not provided. + on_plot (Callable, optional): Callback function to be executed after plotting. Takes filename as an argument. + + Examples: + >>> from ultralytics.utils.plotting import plot_results + >>> plot_results("path/to/results.csv") + """ + import matplotlib.pyplot as plt # scope for faster 'import ultralytics' + import polars as pl + + save_dir = Path(file).parent if file else Path(dir) + files = list(save_dir.glob("results*.csv")) + assert len(files), f"No results.csv files found in {save_dir.resolve()}, nothing to plot." + + loss_keys, metric_keys = [], [] + fig, ax = None, None + for i, f in enumerate(files): + try: + data = pl.read_csv(f, infer_schema_length=None) + if i == 0: + for c in data.columns: + if "loss" in c: + loss_keys.append(c) + elif "metric" in c: + metric_keys.append(c) + loss_mid, metric_mid = len(loss_keys) // 2, len(metric_keys) // 2 + columns = ( + loss_keys[:loss_mid] + metric_keys[:metric_mid] + loss_keys[loss_mid:] + metric_keys[metric_mid:] + ) + fig, ax = plt.subplots(2, len(columns) // 2, figsize=(len(columns) + 2, 6), tight_layout=True) + ax = ax.ravel() + x = data.select(data.columns[0]).to_numpy().flatten() + for i, j in enumerate(columns): + y = data.select(j).to_numpy().flatten().astype("float") + ax[i].plot(x, y, marker=".", label=f.stem, linewidth=2, markersize=8) # actual results + ax[i].plot(x, _gaussian_filter1d(y, sigma=3), ":", label="smooth", linewidth=2) # smoothing line + ax[i].set_title(j, fontsize=12) + except Exception as e: + LOGGER.error(f"Plotting error for {f}: {e}") + if ax is not None: + ax[1].legend() + fname = save_dir / "results.png" + fig.savefig(fname, dpi=200) + plt.close() + if on_plot: + on_plot(fname) + + +@plt_settings() +def plot_multitrain_results(scores: dict, key: str = "fitness", save_dir=Path()): + """Plot per-dataset metrics from a multi-dataset training run as a bar chart with the cross-dataset mean. + + Args: + scores (dict): Mapping of dataset name to its scalar metric value. + key (str): Name of the plotted metric, used as the y-axis label. + save_dir (str | Path): Directory to save the 'multitrain_results.png' figure. + + Returns: + (Path): Path to the saved figure. + + Examples: + >>> from ultralytics.utils.plotting import plot_multitrain_results + >>> plot_multitrain_results({"coco8": 0.61, "dota8": 0.48}, key="metrics/mAP50-95(B)") + """ + import matplotlib.pyplot as plt # scope for faster 'import ultralytics' + + mean = sum(scores.values()) / len(scores) + fig, ax = plt.subplots(figsize=(max(6.0, len(scores) * 0.45), 5), tight_layout=True) + ax.bar(range(len(scores)), list(scores.values()), color="#042AFF") + ax.axhline(mean, color="orange", linestyle="--", label=f"mean = {mean:.3f}") + ax.set_xticks(range(len(scores))) + ax.set_xticklabels(list(scores), rotation=90) + ax.set_ylabel(key) + ax.set_title(f"MultiTrainer results across {len(scores)} datasets") + ax.legend() + fname = Path(save_dir) / "multitrain_results.png" + fig.savefig(fname, dpi=200) + plt.close(fig) + return fname + + +def plt_color_scatter(v, f, bins: int = 20, cmap: str = "viridis", alpha: float = 0.8, edgecolors: str = "none"): + """Plot a scatter plot with points colored based on a 2D histogram. + + Args: + v (array-like): Values for the x-axis. + f (array-like): Values for the y-axis. + bins (int, optional): Number of bins for the histogram. + cmap (str, optional): Colormap for the scatter plot. + alpha (float, optional): Alpha for the scatter plot. + edgecolors (str, optional): Edge colors for the scatter plot. + + Examples: + >>> v = np.random.rand(100) + >>> f = np.random.rand(100) + >>> plt_color_scatter(v, f) + """ + import matplotlib.pyplot as plt # scope for faster 'import ultralytics' + + # Calculate 2D histogram and corresponding colors + hist, xedges, yedges = np.histogram2d(v, f, bins=bins) + colors = [ + hist[ + min(np.digitize(v[i], xedges, right=True) - 1, hist.shape[0] - 1), + min(np.digitize(f[i], yedges, right=True) - 1, hist.shape[1] - 1), + ] + for i in range(len(v)) + ] + + # Scatter plot + plt.scatter(v, f, c=colors, cmap=cmap, alpha=alpha, edgecolors=edgecolors) + + +@plt_settings() +def plot_tune_results(results_file: str = "tune_results.ndjson", exclude_zero_fitness_points: bool = True): + """Plot the evolution results stored in a tuning NDJSON file. + + Args: + results_file (str, optional): Path to the NDJSON file containing the tuning results. + exclude_zero_fitness_points (bool, optional): Don't include points with zero fitness in tuning plots. + + Examples: + >>> plot_tune_results("path/to/tune_results.ndjson") + """ + import json + + import matplotlib.pyplot as plt # scope for faster 'import ultralytics' + + def _save_one_file(file): + """Save one matplotlib plot to 'file'.""" + plt.savefig(file, dpi=200) + plt.close() + LOGGER.info(f"Saved {file}") + + results_file = Path(results_file) + with open(results_file, encoding="utf-8") as f: + records = [json.loads(line) for line in f if line.strip()] + if not records: + return + + keys = list(records[0].get("hyperparameters", {})) + x = np.array( + [[r.get("fitness", 0.0)] + [r.get("hyperparameters", {}).get(k, np.nan) for k in keys] for r in records], + dtype=float, + ) + len(x) + all_fitness = x[:, 0] # fitness + zero_mask = slice(None) + if exclude_zero_fitness_points: + zero_mask = all_fitness > 0 # exclude zero-fitness points + x, all_fitness = x[zero_mask], all_fitness[zero_mask] + if len(all_fitness) == 0: + LOGGER.warning("No valid fitness values to plot (all iterations may have failed)") + return + fitness = all_fitness.copy() + # Iterative sigma rejection on lower bound only + for _ in range(3): # max 3 iterations + mean, std = fitness.mean(), fitness.std() + lower_bound = mean - 3 * std + mask = fitness >= lower_bound + if mask.all(): # no more outliers + break + x, fitness = x[mask], fitness[mask] + j = np.argmax(fitness) # max fitness index + n = math.ceil(len(keys) ** 0.5) # columns and rows in plot + plt.figure(figsize=(10, 10), tight_layout=True) + for i, k in enumerate(keys): + v = x[:, i + 1] + mu = v[j] # best single result + plt.subplot(n, n, i + 1) + plt_color_scatter(v, fitness, cmap="viridis", alpha=0.8, edgecolors="none") + plt.plot(mu, fitness.max(), "k+", markersize=15) + plt.title(f"{k} = {mu:.3g}", fontdict={"size": 9}) # limit to 40 characters + plt.tick_params(axis="both", labelsize=8) # Set axis label size to 8 + if i % n != 0: + plt.yticks([]) + _save_one_file(results_file.with_name("tune_scatter_plots.png")) + + # Fitness vs iteration + x = range(1, len(all_fitness) + 1) + plt.figure(figsize=(10, 6), tight_layout=True) + for dataset in sorted({k for r in records for k in r.get("datasets", {})}): + y = np.array([r.get("datasets", {}).get(dataset, {}).get("fitness", np.nan) for r in records], dtype=float) + if exclude_zero_fitness_points and not isinstance(zero_mask, slice): + y = y[zero_mask] + plt.plot(x, y, "o", markersize=5, alpha=0.8, label=dataset) + plt.plot(x, _gaussian_filter1d(all_fitness, sigma=3), ":", color="0.35", label="smoothed mean", linewidth=2) + plt.title("Fitness vs Iteration") + plt.xlabel("Iteration") + plt.ylabel("Fitness") + plt.grid(True) + plt.legend() + _save_one_file(results_file.with_name("tune_fitness.png")) + + +@plt_settings() +def feature_visualization(x, module_type: str, stage: int, n: int = 32, save_dir: Path = Path("runs/detect/exp")): + """Visualize feature maps of a given model module during inference. + + Args: + x (torch.Tensor): Features to be visualized. + module_type (str): Module type. + stage (int): Module stage within the model. + n (int, optional): Maximum number of feature maps to plot. + save_dir (Path, optional): Directory to save results. + """ + import matplotlib.pyplot as plt # scope for faster 'import ultralytics' + + for m in {"Detect", "Segment", "Pose", "Classify", "OBB", "RTDETRDecoder"}: # all model heads + if m in module_type: + return + if isinstance(x, torch.Tensor): + _, channels, height, width = x.shape # batch, channels, height, width + if height > 1 and width > 1: + f = save_dir / f"stage{stage}_{module_type.rsplit('.', 1)[-1]}_features.png" # filename + + blocks = torch.chunk(x[0].cpu(), channels, dim=0) # select batch index 0, block by channels + n = min(n, channels) # number of plots + _, ax = plt.subplots(math.ceil(n / 8), 8, tight_layout=True) # n/8 rows x 8 cols + ax = ax.ravel() + plt.subplots_adjust(wspace=0.05, hspace=0.05) + for i in range(n): + ax[i].imshow(blocks[i].squeeze()) # cmap='gray' + ax[i].axis("off") + + LOGGER.info(f"Saving {f}... ({n}/{channels})") + plt.savefig(f, dpi=300, bbox_inches="tight") + plt.close() + np.save(str(f.with_suffix(".npy")), x[0].cpu().numpy()) # npy save diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/tal.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/tal.py new file mode 100644 index 0000000000000000000000000000000000000000..d41c7a012de4c18c5edae854e9add265ef727c8e --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/tal.py @@ -0,0 +1,495 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import torch +import torch.nn as nn + +from . import LOGGER +from .metrics import bbox_iou, probiou +from .ops import xywh2xyxy, xywhr2xyxyxyxy, xyxy2xywh +from .torch_utils import TORCH_1_11 + + +class TaskAlignedAssigner(nn.Module): + """A task-aligned assigner for object detection. + + This class assigns ground-truth (gt) objects to anchors based on the task-aligned metric, which combines both + classification and localization information. + + Attributes: + topk (int): The number of top candidates to consider. + topk2 (int): Secondary topk value for additional filtering. + num_classes (int): The number of object classes. + alpha (float): The alpha parameter for the classification component of the task-aligned metric. + beta (float): The beta parameter for the localization component of the task-aligned metric. + stride (list): List of stride values for different feature levels. + stride_val (int): The stride value used for select_candidates_in_gts. + eps (float): A small value to prevent division by zero. + """ + + def __init__( + self, + topk: int = 13, + num_classes: int = 80, + alpha: float = 1.0, + beta: float = 6.0, + stride: list | None = None, + eps: float = 1e-9, + topk2=None, + ): + """Initialize a TaskAlignedAssigner object with customizable hyperparameters. + + Args: + topk (int, optional): The number of top candidates to consider. + num_classes (int, optional): The number of object classes. + alpha (float, optional): The alpha parameter for the classification component of the task-aligned metric. + beta (float, optional): The beta parameter for the localization component of the task-aligned metric. + stride (list, optional): List of stride values for different feature levels. + eps (float, optional): A small value to prevent division by zero. + topk2 (int, optional): Secondary topk value for additional filtering. + """ + super().__init__() + self.topk = topk + self.topk2 = topk2 or topk + self.num_classes = num_classes + self.alpha = alpha + self.beta = beta + self.stride = stride if stride is not None else [8, 16, 32] + self.stride_val = self.stride[1] if len(self.stride) > 1 else self.stride[0] + self.eps = eps + + @torch.no_grad() + def forward(self, pd_scores, pd_bboxes, anc_points, gt_labels, gt_bboxes, mask_gt): + """Compute the task-aligned assignment. + + Args: + pd_scores (torch.Tensor): Predicted classification scores with shape (bs, num_total_anchors, num_classes). + pd_bboxes (torch.Tensor): Predicted bounding boxes with shape (bs, num_total_anchors, 4). + anc_points (torch.Tensor): Anchor points with shape (num_total_anchors, 2). + gt_labels (torch.Tensor): Ground truth labels with shape (bs, n_max_boxes, 1). + gt_bboxes (torch.Tensor): Ground truth boxes with shape (bs, n_max_boxes, 4). + mask_gt (torch.Tensor): Mask for valid ground truth boxes with shape (bs, n_max_boxes, 1). + + Returns: + target_labels (torch.Tensor): Target labels with shape (bs, num_total_anchors). + target_bboxes (torch.Tensor): Target bounding boxes with shape (bs, num_total_anchors, 4). + target_scores (torch.Tensor): Target scores with shape (bs, num_total_anchors, num_classes). + fg_mask (torch.Tensor): Foreground mask with shape (bs, num_total_anchors). + target_gt_idx (torch.Tensor): Target ground truth indices with shape (bs, num_total_anchors). + + References: + https://github.com/Nioolek/PPYOLOE_pytorch/blob/master/ppyoloe/assigner/tal_assigner.py + """ + self.bs = pd_scores.shape[0] + self.n_max_boxes = gt_bboxes.shape[1] + device = gt_bboxes.device + + if self.n_max_boxes == 0: + return ( + torch.full_like(pd_scores[..., 0], self.num_classes), + torch.zeros_like(pd_bboxes), + torch.zeros_like(pd_scores), + torch.zeros_like(pd_scores[..., 0]), + torch.zeros_like(pd_scores[..., 0]), + ) + + try: + return self._forward(pd_scores, pd_bboxes, anc_points, gt_labels, gt_bboxes, mask_gt) + except RuntimeError as e: + if "out of memory" in str(e).lower(): + # Move tensors to CPU, compute, then move back to original device + LOGGER.warning("CUDA OutOfMemoryError in TaskAlignedAssigner, using CPU") + cpu_tensors = [t.cpu() for t in (pd_scores, pd_bboxes, anc_points, gt_labels, gt_bboxes, mask_gt)] + result = self._forward(*cpu_tensors) + return tuple(t.to(device) for t in result) + raise + + def _forward(self, pd_scores, pd_bboxes, anc_points, gt_labels, gt_bboxes, mask_gt): + """Compute the task-aligned assignment. + + Args: + pd_scores (torch.Tensor): Predicted classification scores with shape (bs, num_total_anchors, num_classes). + pd_bboxes (torch.Tensor): Predicted bounding boxes with shape (bs, num_total_anchors, 4). + anc_points (torch.Tensor): Anchor points with shape (num_total_anchors, 2). + gt_labels (torch.Tensor): Ground truth labels with shape (bs, n_max_boxes, 1). + gt_bboxes (torch.Tensor): Ground truth boxes with shape (bs, n_max_boxes, 4). + mask_gt (torch.Tensor): Mask for valid ground truth boxes with shape (bs, n_max_boxes, 1). + + Returns: + target_labels (torch.Tensor): Target labels with shape (bs, num_total_anchors). + target_bboxes (torch.Tensor): Target bounding boxes with shape (bs, num_total_anchors, 4). + target_scores (torch.Tensor): Target scores with shape (bs, num_total_anchors, num_classes). + fg_mask (torch.Tensor): Foreground mask with shape (bs, num_total_anchors). + target_gt_idx (torch.Tensor): Target ground truth indices with shape (bs, num_total_anchors). + """ + mask_pos, align_metric, overlaps = self.get_pos_mask( + pd_scores, pd_bboxes, gt_labels, gt_bboxes, anc_points, mask_gt + ) + + target_gt_idx, fg_mask, mask_pos = self.select_highest_overlaps( + mask_pos, overlaps, self.n_max_boxes, align_metric + ) + + # Assigned target + target_labels, target_bboxes, target_scores = self.get_targets(gt_labels, gt_bboxes, target_gt_idx, fg_mask) + + # Normalize + align_metric *= mask_pos + pos_align_metrics = align_metric.amax(dim=-1, keepdim=True) # b, max_num_obj + pos_overlaps = (overlaps * mask_pos).amax(dim=-1, keepdim=True) # b, max_num_obj + norm_align_metric = (align_metric * pos_overlaps / (pos_align_metrics + self.eps)).amax(-2).unsqueeze(-1) + target_scores = target_scores * norm_align_metric + + return target_labels, target_bboxes, target_scores, fg_mask.bool(), target_gt_idx + + def get_pos_mask(self, pd_scores, pd_bboxes, gt_labels, gt_bboxes, anc_points, mask_gt): + """Get positive mask for each ground truth box. + + Args: + pd_scores (torch.Tensor): Predicted classification scores with shape (bs, num_total_anchors, num_classes). + pd_bboxes (torch.Tensor): Predicted bounding boxes with shape (bs, num_total_anchors, 4). + gt_labels (torch.Tensor): Ground truth labels with shape (bs, n_max_boxes, 1). + gt_bboxes (torch.Tensor): Ground truth boxes with shape (bs, n_max_boxes, 4). + anc_points (torch.Tensor): Anchor points with shape (num_total_anchors, 2). + mask_gt (torch.Tensor): Mask for valid ground truth boxes with shape (bs, n_max_boxes, 1). + + Returns: + mask_pos (torch.Tensor): Positive mask with shape (bs, max_num_obj, h*w). + align_metric (torch.Tensor): Alignment metric with shape (bs, max_num_obj, h*w). + overlaps (torch.Tensor): Overlaps between predicted vs ground truth boxes with shape (bs, max_num_obj, h*w). + """ + mask_in_gts = self.select_candidates_in_gts(anc_points, gt_bboxes, mask_gt) + # Get anchor_align metric, (b, max_num_obj, h*w) + align_metric, overlaps = self.get_box_metrics(pd_scores, pd_bboxes, gt_labels, gt_bboxes, mask_in_gts * mask_gt) + # Get topk_metric mask, (b, max_num_obj, h*w) + mask_topk = self.select_topk_candidates(align_metric, topk_mask=mask_gt.expand(-1, -1, self.topk).bool()) + # Merge all mask to a final mask, (b, max_num_obj, h*w) + mask_pos = mask_topk * mask_in_gts * mask_gt + + return mask_pos, align_metric, overlaps + + def get_box_metrics(self, pd_scores, pd_bboxes, gt_labels, gt_bboxes, mask_gt): + """Compute alignment metric given predicted and ground truth bounding boxes. + + Args: + pd_scores (torch.Tensor): Predicted classification scores with shape (bs, num_total_anchors, num_classes). + pd_bboxes (torch.Tensor): Predicted bounding boxes with shape (bs, num_total_anchors, 4). + gt_labels (torch.Tensor): Ground truth labels with shape (bs, n_max_boxes, 1). + gt_bboxes (torch.Tensor): Ground truth boxes with shape (bs, n_max_boxes, 4). + mask_gt (torch.Tensor): Mask for valid ground truth boxes with shape (bs, n_max_boxes, h*w). + + Returns: + align_metric (torch.Tensor): Alignment metric combining classification and localization. + overlaps (torch.Tensor): IoU overlaps between predicted and ground truth boxes. + """ + na = pd_bboxes.shape[-2] + mask_gt = mask_gt.bool() # b, max_num_obj, h*w + overlaps = torch.zeros([self.bs, self.n_max_boxes, na], dtype=pd_bboxes.dtype, device=pd_bboxes.device) + bbox_scores = torch.zeros([self.bs, self.n_max_boxes, na], dtype=pd_scores.dtype, device=pd_scores.device) + + ind = torch.zeros([2, self.bs, self.n_max_boxes], dtype=torch.long) # 2, b, max_num_obj + ind[0] = torch.arange(end=self.bs).view(-1, 1).expand(-1, self.n_max_boxes) # b, max_num_obj + ind[1] = gt_labels.squeeze(-1) # b, max_num_obj + # Get the scores of each grid for each gt cls + bbox_scores[mask_gt] = pd_scores[ind[0], :, ind[1]][mask_gt] # b, max_num_obj, h*w + + # (b, max_num_obj, 1, 4), (b, 1, h*w, 4) + pd_boxes = pd_bboxes.unsqueeze(1).expand(-1, self.n_max_boxes, -1, -1)[mask_gt] + gt_boxes = gt_bboxes.unsqueeze(2).expand(-1, -1, na, -1)[mask_gt] + overlaps[mask_gt] = self.iou_calculation(gt_boxes, pd_boxes) + + align_metric = bbox_scores.pow(self.alpha) * overlaps.pow(self.beta) + return align_metric, overlaps + + def iou_calculation(self, gt_bboxes, pd_bboxes): + """Calculate IoU for horizontal bounding boxes. + + Args: + gt_bboxes (torch.Tensor): Ground truth boxes. + pd_bboxes (torch.Tensor): Predicted boxes. + + Returns: + (torch.Tensor): IoU values between each pair of boxes. + """ + return bbox_iou(gt_bboxes, pd_bboxes, xywh=False, CIoU=True).squeeze(-1).clamp_(0) + + def select_topk_candidates(self, metrics, topk_mask=None): + """Select the top-k candidates based on the given metrics. + + Args: + metrics (torch.Tensor): A tensor of shape (b, max_num_obj, h*w), where b is the batch size, max_num_obj is + the maximum number of objects, and h*w represents the total number of anchor points. + topk_mask (torch.Tensor, optional): An optional boolean tensor of shape (b, max_num_obj, topk), where topk + is the number of top candidates to consider. If not provided, the top-k values are automatically + computed based on the given metrics. + + Returns: + (torch.Tensor): A tensor of shape (b, max_num_obj, h*w) containing the selected top-k candidates. + """ + # (b, max_num_obj, topk) + topk_metrics, topk_idxs = torch.topk(metrics, self.topk, dim=-1, largest=True) + if topk_mask is None: + topk_mask = (topk_metrics.max(-1, keepdim=True)[0] > self.eps).expand_as(topk_idxs) + # (b, max_num_obj, topk) + topk_idxs.masked_fill_(~topk_mask, 0) + + # (b, max_num_obj, topk, h*w) -> (b, max_num_obj, h*w) + count_tensor = torch.zeros(metrics.shape, dtype=torch.int8, device=topk_idxs.device) + ones = torch.ones_like(topk_idxs[:, :, :1], dtype=torch.int8, device=topk_idxs.device) + for k in range(self.topk): + # Expand topk_idxs for each value of k and add 1 at the specified positions + count_tensor.scatter_add_(-1, topk_idxs[:, :, k : k + 1], ones) + # Filter invalid bboxes + count_tensor.masked_fill_(count_tensor > 1, 0) + + return count_tensor.to(metrics.dtype) + + def get_targets(self, gt_labels, gt_bboxes, target_gt_idx, fg_mask): + """Compute target labels, target bounding boxes, and target scores for the positive anchor points. + + Args: + gt_labels (torch.Tensor): Ground truth labels of shape (b, max_num_obj, 1), where b is the batch size and + max_num_obj is the maximum number of objects. + gt_bboxes (torch.Tensor): Ground truth bounding boxes of shape (b, max_num_obj, 4). + target_gt_idx (torch.Tensor): Indices of the assigned ground truth objects for positive anchor points, with + shape (b, h*w), where h*w is the total number of anchor points. + fg_mask (torch.Tensor): A boolean tensor of shape (b, h*w) indicating the positive (foreground) anchor + points. + + Returns: + target_labels (torch.Tensor): Target labels for positive anchor points with shape (b, h*w). + target_bboxes (torch.Tensor): Target bounding boxes for positive anchor points with shape (b, h*w, 4). + target_scores (torch.Tensor): Target scores for positive anchor points with shape (b, h*w, num_classes). + """ + # Assigned target labels, (b, 1) + batch_ind = torch.arange(end=self.bs, dtype=torch.int64, device=gt_labels.device)[..., None] + target_gt_idx = target_gt_idx + batch_ind * self.n_max_boxes # (b, h*w) + target_labels = gt_labels.long().flatten()[target_gt_idx] # (b, h*w) + + # Assigned target boxes, (b, max_num_obj, 4) -> (b, h*w, 4) + target_bboxes = gt_bboxes.view(-1, gt_bboxes.shape[-1])[target_gt_idx] + + # Assigned target scores + target_labels.clamp_(0) + + # 10x faster than F.one_hot() + target_scores = torch.zeros( + (target_labels.shape[0], target_labels.shape[1], self.num_classes), + dtype=torch.int64, + device=target_labels.device, + ) # (b, h*w, 80) + target_scores.scatter_(2, target_labels.unsqueeze(-1), 1) + + fg_scores_mask = fg_mask[:, :, None].repeat(1, 1, self.num_classes) # (b, h*w, 80) + target_scores = torch.where(fg_scores_mask > 0, target_scores, 0) + + return target_labels, target_bboxes, target_scores + + def select_candidates_in_gts(self, xy_centers, gt_bboxes, mask_gt, eps=1e-9): + """Select positive anchor centers within ground truth bounding boxes. + + Args: + xy_centers (torch.Tensor): Anchor center coordinates, shape (h*w, 2). + gt_bboxes (torch.Tensor): Ground truth bounding boxes, shape (b, n_boxes, 4). + mask_gt (torch.Tensor): Mask for valid ground truth boxes, shape (b, n_boxes, 1). + eps (float, optional): Small value for numerical stability. + + Returns: + (torch.Tensor): Boolean mask of positive anchors, shape (b, n_boxes, h*w). + + Notes: + - b: batch size, n_boxes: number of ground truth boxes, h: height, w: width. + - Bounding box format: [x_min, y_min, x_max, y_max]. + """ + gt_bboxes_xywh = xyxy2xywh(gt_bboxes) + wh_mask = gt_bboxes_xywh[..., 2:] < self.stride[0] # the smallest stride + gt_bboxes_xywh[..., 2:] = torch.where( + (wh_mask * mask_gt).bool(), + torch.tensor(self.stride_val, dtype=gt_bboxes_xywh.dtype, device=gt_bboxes_xywh.device), + gt_bboxes_xywh[..., 2:], + ) + gt_bboxes = xywh2xyxy(gt_bboxes_xywh) + + n_anchors = xy_centers.shape[0] + bs, n_boxes, _ = gt_bboxes.shape + lt, rb = gt_bboxes.view(-1, 1, 4).chunk(2, 2) # left-top, right-bottom + bbox_deltas = torch.cat((xy_centers[None] - lt, rb - xy_centers[None]), dim=2).view(bs, n_boxes, n_anchors, -1) + return bbox_deltas.amin(3).gt_(eps) + + def select_highest_overlaps(self, mask_pos, overlaps, n_max_boxes, align_metric): + """Select anchor boxes with highest IoU when assigned to multiple ground truths. + + Args: + mask_pos (torch.Tensor): Positive mask, shape (b, n_max_boxes, h*w). + overlaps (torch.Tensor): IoU overlaps, shape (b, n_max_boxes, h*w). + n_max_boxes (int): Maximum number of ground truth boxes. + align_metric (torch.Tensor): Alignment metric for selecting best matches. + + Returns: + target_gt_idx (torch.Tensor): Indices of assigned ground truths, shape (b, h*w). + fg_mask (torch.Tensor): Foreground mask, shape (b, h*w). + mask_pos (torch.Tensor): Updated positive mask, shape (b, n_max_boxes, h*w). + """ + # Convert (b, n_max_boxes, h*w) -> (b, h*w) + fg_mask = mask_pos.sum(-2) + if fg_mask.max() > 1: # one anchor is assigned to multiple gt_bboxes + mask_multi_gts = (fg_mask.unsqueeze(1) > 1).expand(-1, n_max_boxes, -1) # (b, n_max_boxes, h*w) + + max_overlaps_idx = overlaps.argmax(1) # (b, h*w) + is_max_overlaps = torch.zeros(mask_pos.shape, dtype=mask_pos.dtype, device=mask_pos.device) + is_max_overlaps.scatter_(1, max_overlaps_idx.unsqueeze(1), 1) + mask_pos = torch.where(mask_multi_gts, is_max_overlaps, mask_pos).float() # (b, n_max_boxes, h*w) + + fg_mask = mask_pos.sum(-2) + + if self.topk2 != self.topk: + align_metric = align_metric * mask_pos # update overlaps + # (b, n_max_boxes, topk2) + max_overlaps_idx = torch.topk(align_metric, self.topk2, dim=-1, largest=True).indices + topk_idx = torch.zeros(mask_pos.shape, dtype=mask_pos.dtype, device=mask_pos.device) # update mask_pos + topk_idx.scatter_(-1, max_overlaps_idx, 1.0) + mask_pos *= topk_idx + fg_mask = mask_pos.sum(-2) + # Find each grid serve which gt(index) + target_gt_idx = mask_pos.argmax(-2) # (b, h*w) + return target_gt_idx, fg_mask, mask_pos + + +class RotatedTaskAlignedAssigner(TaskAlignedAssigner): + """Assigns ground-truth objects to rotated bounding boxes using a task-aligned metric.""" + + def iou_calculation(self, gt_bboxes, pd_bboxes): + """Calculate IoU for rotated bounding boxes.""" + return probiou(gt_bboxes, pd_bboxes).squeeze(-1).clamp_(0) + + def select_candidates_in_gts(self, xy_centers, gt_bboxes, mask_gt): + """Select the positive anchor center in gt for rotated bounding boxes. + + Args: + xy_centers (torch.Tensor): Anchor center coordinates with shape (h*w, 2). + gt_bboxes (torch.Tensor): Ground truth bounding boxes with shape (b, n_boxes, 5). + mask_gt (torch.Tensor): Mask for valid ground truth boxes with shape (b, n_boxes, 1). + + Returns: + (torch.Tensor): Boolean mask of positive anchors with shape (b, n_boxes, h*w). + """ + gt_bboxes_clone = gt_bboxes.clone() + wh_mask = gt_bboxes_clone[..., 2:4] < self.stride[0] + gt_bboxes_clone[..., 2:4] = torch.where( + (wh_mask * mask_gt).bool(), + torch.tensor(self.stride_val, dtype=gt_bboxes_clone.dtype, device=gt_bboxes_clone.device), + gt_bboxes_clone[..., 2:4], + ) + + # (b, n_boxes, 5) --> (b, n_boxes, 4, 2) + corners = xywhr2xyxyxyxy(gt_bboxes_clone) + # (b, n_boxes, 1, 2) + a, b, _, d = corners.split(1, dim=-2) + ab = b - a + ad = d - a + + # (b, n_boxes, h*w, 2) + ap = xy_centers - a + norm_ab = (ab * ab).sum(dim=-1) + norm_ad = (ad * ad).sum(dim=-1) + ap_dot_ab = (ap * ab).sum(dim=-1) + ap_dot_ad = (ap * ad).sum(dim=-1) + return (ap_dot_ab >= 0) & (ap_dot_ab <= norm_ab) & (ap_dot_ad >= 0) & (ap_dot_ad <= norm_ad) # is_in_box + + +def make_anchors(feats, strides, grid_cell_offset=0.5): + """Generate anchors from features.""" + anchor_points, stride_tensor = [], [] + assert feats is not None + dtype, device = feats[0].dtype, feats[0].device + for i in range(len(feats)): # use len(feats) to avoid TracerWarning from iterating over strides tensor + stride = strides[i] + h, w = feats[i].shape[2:] if isinstance(feats, list) else (int(feats[i][0]), int(feats[i][1])) + sx = torch.arange(end=w, device=device, dtype=dtype) + grid_cell_offset # shift x + sy = torch.arange(end=h, device=device, dtype=dtype) + grid_cell_offset # shift y + sy, sx = torch.meshgrid(sy, sx, indexing="ij") if TORCH_1_11 else torch.meshgrid(sy, sx) + anchor_points.append(torch.stack((sx, sy), -1).view(-1, 2)) + stride_tensor.append(torch.full((h * w, 1), stride, dtype=dtype, device=device)) + return torch.cat(anchor_points), torch.cat(stride_tensor) + + +def dist2bbox(distance, anchor_points, xywh=True, dim=-1): + """Transform distance(ltrb) to box(xywh or xyxy).""" + lt, rb = distance.chunk(2, dim) + x1y1 = anchor_points - lt + x2y2 = anchor_points + rb + if xywh: + c_xy = (x1y1 + x2y2) / 2 + wh = x2y2 - x1y1 + return torch.cat([c_xy, wh], dim) # xywh bbox + return torch.cat((x1y1, x2y2), dim) # xyxy bbox + + +def bbox2dist(anchor_points: torch.Tensor, bbox: torch.Tensor, reg_max: int | None = None) -> torch.Tensor: + """Transform bbox(xyxy) to dist(ltrb).""" + x1y1, x2y2 = bbox.chunk(2, -1) + dist = torch.cat((anchor_points - x1y1, x2y2 - anchor_points), -1) + if reg_max is not None: + dist = dist.clamp_(0, reg_max - 0.01) # dist (lt, rb) + return dist + + +def dist2rbox(pred_dist, pred_angle, anchor_points, dim=-1): + """Decode predicted rotated bounding box coordinates from anchor points and distribution. + + Args: + pred_dist (torch.Tensor): Predicted rotated distance with shape (bs, h*w, 4). + pred_angle (torch.Tensor): Predicted angle with shape (bs, h*w, 1). + anchor_points (torch.Tensor): Anchor points with shape (h*w, 2). + dim (int, optional): Dimension along which to split. + + Returns: + (torch.Tensor): Predicted rotated bounding boxes with shape (bs, h*w, 4). + """ + lt, rb = pred_dist.split(2, dim=dim) + cos, sin = torch.cos(pred_angle), torch.sin(pred_angle) + # (bs, h*w, 1) + xf, yf = ((rb - lt) / 2).split(1, dim=dim) + x, y = xf * cos - yf * sin, xf * sin + yf * cos + xy = torch.cat([x, y], dim=dim) + anchor_points + return torch.cat([xy, lt + rb], dim=dim) + + +def rbox2dist( + target_bboxes: torch.Tensor, + anchor_points: torch.Tensor, + target_angle: torch.Tensor, + dim: int = -1, + reg_max: int | None = None, +): + """Transform rotated bounding box (xywh) to distance (ltrb). This is the inverse of dist2rbox. + + Args: + target_bboxes (torch.Tensor): Target rotated bounding boxes with shape (bs, h*w, 4), format [x, y, w, h]. + anchor_points (torch.Tensor): Anchor points with shape (h*w, 2). + target_angle (torch.Tensor): Target angle with shape (bs, h*w, 1). + dim (int, optional): Dimension along which to split. + reg_max (int, optional): Maximum regression value for clamping. + + Returns: + (torch.Tensor): Rotated distance with shape (bs, h*w, 4), format [l, t, r, b]. + """ + xy, wh = target_bboxes.split(2, dim=dim) + offset = xy - anchor_points # (bs, h*w, 2) + offset_x, offset_y = offset.split(1, dim=dim) + cos, sin = torch.cos(target_angle), torch.sin(target_angle) + xf = offset_x * cos + offset_y * sin + yf = -offset_x * sin + offset_y * cos + + w, h = wh.split(1, dim=dim) + target_l = w / 2 - xf + target_t = h / 2 - yf + target_r = w / 2 + xf + target_b = h / 2 + yf + + dist = torch.cat([target_l, target_t, target_r, target_b], dim=dim) + if reg_max is not None: + dist = dist.clamp_(0, reg_max - 0.01) + + return dist diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/torch_utils.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/torch_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..868a669bbf418f2b4c467bd92214223037392919 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/torch_utils.py @@ -0,0 +1,1038 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import functools +import gc +import math +import os +import random +import time +from contextlib import contextmanager +from copy import deepcopy +from datetime import datetime +from pathlib import Path +from typing import Any + +import numpy as np +import torch +import torch.distributed as dist +import torch.nn as nn +import torch.nn.functional as F + +from ultralytics import __version__ +from ultralytics.utils import ( + DEFAULT_CFG_DICT, + DEFAULT_CFG_KEYS, + LOGGER, + NUM_THREADS, + PYTHON_VERSION, + TORCH_VERSION, + TORCHVISION_VERSION, + WINDOWS, + colorstr, +) +from ultralytics.utils.checks import check_version +from ultralytics.utils.cpu import CPUInfo +from ultralytics.utils.patches import torch_load + +# Version checks (all default to version>=min_version) +TORCH_1_9 = check_version(TORCH_VERSION, "1.9.0") +TORCH_1_10 = check_version(TORCH_VERSION, "1.10.0") +TORCH_1_11 = check_version(TORCH_VERSION, "1.11.0") +TORCH_1_13 = check_version(TORCH_VERSION, "1.13.0") +TORCH_2_0 = check_version(TORCH_VERSION, "2.0.0") +TORCH_2_1 = check_version(TORCH_VERSION, "2.1.0") +TORCH_2_3 = check_version(TORCH_VERSION, "2.3.0") +TORCH_2_4 = check_version(TORCH_VERSION, "2.4.0") +TORCH_2_8 = check_version(TORCH_VERSION, "2.8.0") +TORCH_2_9 = check_version(TORCH_VERSION, "2.9.0") +TORCH_2_10 = check_version(TORCH_VERSION, "2.10.0") +TORCH_2_12 = check_version(TORCH_VERSION, "2.12.0") +TORCHVISION_0_10 = check_version(TORCHVISION_VERSION, "0.10.0") +TORCHVISION_0_11 = check_version(TORCHVISION_VERSION, "0.11.0") +TORCHVISION_0_13 = check_version(TORCHVISION_VERSION, "0.13.0") +TORCHVISION_0_18 = check_version(TORCHVISION_VERSION, "0.18.0") +if WINDOWS and check_version(TORCH_VERSION, "==2.4.0"): # reject version 2.4.0 on Windows + LOGGER.warning( + "Known issue with torch==2.4.0 on Windows with CPU, recommend upgrading to torch>=2.4.1 to resolve " + "https://github.com/ultralytics/ultralytics/issues/15049" + ) + + +@contextmanager +def torch_distributed_zero_first(local_rank: int): + """Ensure all processes in distributed training wait for the local master (rank 0) to complete a task first.""" + initialized = dist.is_available() and dist.is_initialized() + use_ids = initialized and dist.get_backend() == "nccl" + + if initialized and local_rank not in {-1, 0}: + dist.barrier(device_ids=[local_rank]) if use_ids else dist.barrier() + yield + if initialized and local_rank == 0: + dist.barrier(device_ids=[local_rank]) if use_ids else dist.barrier() + + +def smart_inference_mode(): + """Apply torch.inference_mode() decorator if torch>=1.10.0, else torch.no_grad() decorator.""" + + def decorate(fn): + """Apply appropriate torch decorator for inference mode based on torch version.""" + if TORCH_1_9 and torch.is_inference_mode_enabled(): + return fn # already in inference_mode, act as a pass-through + else: + return (torch.inference_mode if TORCH_1_10 else torch.no_grad)()(fn) + + return decorate + + +def autocast(enabled: bool, device: str = "cuda"): + """Get the appropriate autocast context manager based on PyTorch version and AMP setting. + + This function returns a context manager for automatic mixed precision (AMP) training that is compatible with both + older and newer versions of PyTorch. It handles the differences in the autocast API between PyTorch versions. + + Args: + enabled (bool): Whether to enable automatic mixed precision. + device (str, optional): The device to use for autocast. + + Returns: + (torch.amp.autocast): The appropriate autocast context manager. + + Examples: + >>> with autocast(enabled=True): + ... # Your mixed precision operations here + ... pass + + Notes: + - For PyTorch versions 1.13 and newer, it uses `torch.amp.autocast`. + - For older versions, it uses `torch.cuda.amp.autocast`. + """ + if TORCH_1_13: + return torch.amp.autocast(device, enabled=enabled) + else: + return torch.cuda.amp.autocast(enabled) + + +@functools.lru_cache +def get_cpu_info(): + """Return a string with system CPU information, i.e. 'Apple M2'.""" + from ultralytics.utils import PERSISTENT_CACHE # avoid circular import error + + if "cpu_info" not in PERSISTENT_CACHE: + try: + PERSISTENT_CACHE["cpu_info"] = CPUInfo.name() + except Exception: + pass + return PERSISTENT_CACHE.get("cpu_info", "unknown") + + +@functools.lru_cache +def get_gpu_info(index): + """Return a string with system GPU information, i.e. 'Tesla T4, 15102MiB'.""" + properties = torch.cuda.get_device_properties(index) + return f"{properties.name}, {properties.total_memory / (1 << 20):.0f}MiB" + + +def select_device(device="", newline=False, verbose=True): + """Select the appropriate PyTorch device based on the provided arguments. + + The function takes a string specifying the device or a torch.device object and returns a torch.device object + representing the selected device. The function also validates the number of available devices and raises an + exception if the requested device(s) are not available. + + Args: + device (str | torch.device, optional): Device string or torch.device object. Options include 'cpu', 'cuda', '0', + '0,1,2,3', 'mps', 'npu', 'npu:0', or '-1' for auto-select. Defaults to auto-selecting the first available + GPU, or CPU if no GPU is available. + newline (bool, optional): If True, adds a newline at the end of the log string. + verbose (bool, optional): If True, logs the device information. + + Returns: + (torch.device): Selected device. + + Examples: + >>> select_device("cuda:0") + device(type='cuda', index=0) + + >>> select_device("cpu") + device(type='cpu') + + Notes: + Sets the 'CUDA_VISIBLE_DEVICES' environment variable for specifying which GPUs to use. + """ + if isinstance(device, torch.device) or str(device).startswith(("tpu", "intel", "vulkan")): + return device + + s = f"Ultralytics {__version__} 🚀 Python-{PYTHON_VERSION} torch-{TORCH_VERSION} " + device = str(device).lower() + for remove in "cuda:", "none", "(", ")", "[", "]", "'", " ": + device = device.replace(remove, "") # to string, 'cuda:0' -> '0' and '(0, 1)' -> '0,1' + + # Huawei Ascend NPU + if device.startswith("npu"): + try: + import torch_npu # noqa + except ImportError: + raise ValueError(f"Invalid NPU 'device={device}'. Install 'torch_npu' at https://github.com/Ascend/pytorch") + + if not hasattr(torch, "npu") or not torch.npu.is_available(): + raise ValueError(f"Invalid NPU 'device={device}' requested. Ascend NPU is not available.") + + # Parse 'npu' or 'npu:N' (multi-NPU not yet supported) + suffix = device[3:] + if suffix == "": + idx = 0 + elif suffix.startswith(":") and suffix[1:].isdigit(): + idx = int(suffix[1:]) + else: + raise ValueError(f"Invalid NPU 'device={device}' format. Use 'npu' or 'npu:0'.") + + n = torch.npu.device_count() + if idx >= n: + raise ValueError(f"Invalid NPU 'device={device}' requested. Only {n} NPU(s) available.") + + torch.npu.set_device(idx) + if verbose: + LOGGER.info(f"{s}NPU:{idx} ({torch.npu.get_device_name(idx)})\n") + return torch.device(f"npu:{idx}") + + # Auto-select GPUs + if "-1" in device: + from ultralytics.utils.autodevice import GPUInfo + + # Replace each -1 with a selected GPU or remove it + parts = device.split(",") + selected = GPUInfo().select_idle_gpu(count=parts.count("-1"), min_memory_fraction=0.2) + for i in range(len(parts)): + if parts[i] == "-1": + parts[i] = str(selected.pop(0)) if selected else "" + device = ",".join(p for p in parts if p) + + cpu = device == "cpu" + mps = device in {"mps", "mps:0"} # Apple Metal Performance Shaders (MPS) + if cpu or mps: + os.environ["CUDA_VISIBLE_DEVICES"] = "" # force torch.cuda.is_available() = False + elif device: # non-cpu device requested + if device == "cuda": + device = "0" + if "," in device: + device = ",".join([x for x in device.split(",") if x]) # remove sequential commas, i.e. "0,,1" -> "0,1" + visible = os.environ.get("CUDA_VISIBLE_DEVICES", None) + os.environ["CUDA_VISIBLE_DEVICES"] = device # set environment variable - must be before assert is_available() + if not (torch.cuda.is_available() and torch.cuda.device_count() >= len(device.split(","))): + LOGGER.info(s) + install = ( + "See https://pytorch.org/get-started/locally/ for up-to-date torch install instructions if no " + "CUDA devices are seen by torch.\n" + if torch.cuda.device_count() == 0 + else "" + ) + raise ValueError( + f"Invalid CUDA 'device={device}' requested." + f" Use 'device=cpu' or pass valid CUDA device(s) if available," + f" i.e. 'device=0' or 'device=0,1,2,3' for Multi-GPU.\n" + f"\ntorch.cuda.is_available(): {torch.cuda.is_available()}" + f"\ntorch.cuda.device_count(): {torch.cuda.device_count()}" + f"\nos.environ['CUDA_VISIBLE_DEVICES']: {visible}\n" + f"{install}" + ) + + if not cpu and not mps and torch.cuda.is_available(): # prefer GPU if available + devices = device.split(",") if device else "0" # i.e. "0,1" -> ["0", "1"] + space = " " * len(s) + for i, d in enumerate(devices): + s += f"{'' if i == 0 else space}CUDA:{d} ({get_gpu_info(i)})\n" # bytes to MB + arg = "cuda:0" + elif mps and TORCH_2_0 and torch.backends.mps.is_available(): + # Prefer MPS if available + s += f"MPS ({get_cpu_info()})\n" + arg = "mps" + else: # revert to CPU + s += f"CPU ({get_cpu_info()})\n" + arg = "cpu" + + if arg in {"cpu", "mps"}: + torch.set_num_threads(NUM_THREADS) # reset OMP_NUM_THREADS for cpu training + if verbose: + LOGGER.info(s if newline else s.rstrip()) + return torch.device(arg) + + +def time_sync(): + """Return PyTorch-accurate time.""" + if torch.cuda.is_available(): + torch.cuda.synchronize() + return time.time() + + +def fuse_conv_and_bn(conv, bn): + """Fuse Conv2d and BatchNorm2d layers for inference optimization. + + Args: + conv (nn.Conv2d): Convolutional layer to fuse. + bn (nn.BatchNorm2d): Batch normalization layer to fuse. + + Returns: + (nn.Conv2d): The fused convolutional layer with gradients disabled. + + Examples: + >>> conv = nn.Conv2d(3, 16, 3) + >>> bn = nn.BatchNorm2d(16) + >>> fused_conv = fuse_conv_and_bn(conv, bn) + """ + # Compute fused weights + w_conv = conv.weight.view(conv.out_channels, -1) + w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps + bn.running_var))) + conv.weight.data = torch.mm(w_bn, w_conv).view(conv.weight.shape) + + # Compute fused bias + b_conv = ( + torch.zeros(conv.out_channels, device=conv.weight.device, dtype=conv.weight.dtype) + if conv.bias is None + else conv.bias + ) + b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps)) + fused_bias = torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn + + if conv.bias is None: + conv.register_parameter("bias", nn.Parameter(fused_bias)) + else: + conv.bias.data = fused_bias + + return conv.requires_grad_(False) + + +def fuse_deconv_and_bn(deconv, bn): + """Fuse ConvTranspose2d and BatchNorm2d layers for inference optimization. + + Args: + deconv (nn.ConvTranspose2d): Transposed convolutional layer to fuse. + bn (nn.BatchNorm2d): Batch normalization layer to fuse. + + Returns: + (nn.ConvTranspose2d): The fused transposed convolutional layer with gradients disabled. + + Examples: + >>> deconv = nn.ConvTranspose2d(16, 3, 3) + >>> bn = nn.BatchNorm2d(3) + >>> fused_deconv = fuse_deconv_and_bn(deconv, bn) + """ + if isinstance(bn, nn.Identity): # ConvTranspose(bn=False) leaves bn as nn.Identity, nothing to fuse + return deconv.requires_grad_(False) + # Compute fused weights + w_deconv = deconv.weight.view(deconv.out_channels, -1) + w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps + bn.running_var))) + deconv.weight.data = torch.mm(w_bn, w_deconv).view(deconv.weight.shape) + + # Compute fused bias + b_conv = ( + torch.zeros(deconv.out_channels, device=deconv.weight.device, dtype=deconv.weight.dtype) + if deconv.bias is None + else deconv.bias + ) + b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps)) + fused_bias = torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn + + if deconv.bias is None: + deconv.register_parameter("bias", nn.Parameter(fused_bias)) + else: + deconv.bias.data = fused_bias + + return deconv.requires_grad_(False) + + +def model_info(model, detailed=False, verbose=True, imgsz=640): + """Print and return detailed model information layer by layer. + + Args: + model (nn.Module): Model to analyze. + detailed (bool, optional): Whether to print detailed layer information. + verbose (bool, optional): Whether to print model information. + imgsz (int | list, optional): Input image size. + + Returns: + (tuple): Tuple containing: + - n_l (int): Number of layers. + - n_p (int): Number of parameters. + - n_g (int): Number of gradients. + - flops (float): GFLOPs. + """ + if not verbose: + return + n_p = get_num_params(model) # number of parameters + n_g = get_num_gradients(model) # number of gradients + layers = __import__("collections").OrderedDict((n, m) for n, m in model.named_modules() if len(m._modules) == 0) + n_l = len(layers) # number of layers + if detailed: + h = f"{'layer':>5}{'name':>40}{'type':>20}{'gradient':>10}{'parameters':>12}{'shape':>20}{'mu':>10}{'sigma':>10}" + LOGGER.info(h) + for i, (mn, m) in enumerate(layers.items()): + mn = mn.replace("module_list.", "") + mt = m.__class__.__name__ + if len(m._parameters): + for pn, p in m.named_parameters(): + LOGGER.info( + f"{i:>5g}{f'{mn}.{pn}':>40}{mt:>20}{p.requires_grad!r:>10}{p.numel():>12g}{list(p.shape)!s:>20}{p.mean():>10.3g}{p.std():>10.3g}{str(p.dtype).replace('torch.', ''):>15}" + ) + else: # layers with no learnable params + LOGGER.info(f"{i:>5g}{mn:>40}{mt:>20}{False!r:>10}{0:>12g}{[]!s:>20}{'-':>10}{'-':>10}{'-':>15}") + + flops = get_flops(model, imgsz) # imgsz may be int or list, i.e. imgsz=640 or imgsz=[640, 320] + fused = " (fused)" if getattr(model, "is_fused", lambda: False)() else "" + fs = f", {flops:.1f} GFLOPs" if flops else "" + yaml_file = getattr(model, "yaml_file", "") or getattr(model, "yaml", {}).get("yaml_file", "") + model_name = Path(yaml_file).stem.replace("yolo", "YOLO") or "Model" + LOGGER.info(f"{model_name} summary{fused}: {n_l:,} layers, {n_p:,} parameters, {n_g:,} gradients{fs}") + return n_l, n_p, n_g, flops + + +def get_num_params(model): + """Return the total number of parameters in a YOLO model.""" + return sum(x.numel() for x in model.parameters()) + + +def get_num_gradients(model): + """Return the total number of parameters with gradients in a YOLO model.""" + return sum(x.numel() for x in model.parameters() if x.requires_grad) + + +def model_info_for_loggers(trainer): + """Return model info dict with useful model information. + + Args: + trainer (ultralytics.engine.trainer.BaseTrainer): The trainer object containing model and validation data. + + Returns: + (dict): Dictionary containing model parameters, GFLOPs, and inference speeds. + + Examples: + YOLOv8n info for loggers + >>> results = { + ... "model/parameters": 3151904, + ... "model/GFLOPs": 8.746, + ... "model/speed_ONNX(ms)": 41.244, + ... "model/speed_TensorRT(ms)": 3.211, + ... "model/speed_PyTorch(ms)": 18.755, + ... } + """ + if trainer.args.profile: # profile ONNX and TensorRT times + from ultralytics.utils.benchmarks import ProfileModels + + results = ProfileModels([trainer.last], device=trainer.device).run()[0] + results.pop("model/name") + else: # only return PyTorch times from most recent validation + results = { + "model/parameters": get_num_params(trainer.model), + "model/GFLOPs": round(get_flops(trainer.model), 3), + } + results["model/speed_PyTorch(ms)"] = round(trainer.validator.speed["inference"], 3) + return results + + +def get_flops(model, imgsz=640): + """Calculate FLOPs (floating point operations) for a model in GFLOPs. + + Attempts two calculation methods: first with a stride-based tensor for efficiency, then falls back to full image + size if needed (e.g., for RTDETR models). Returns 0.0 if thop library is unavailable or calculation fails. + + Args: + model (nn.Module): The model to calculate FLOPs for. + imgsz (int | list, optional): Input image size. + + Returns: + (float): The model's GFLOPs (billions of floating point operations). + """ + try: + import thop + except ImportError: + thop = None # conda support without 'ultralytics-thop' installed + + if not thop: + return 0.0 # if not installed return 0.0 GFLOPs + + try: + model = unwrap_model(model) + p = next(model.parameters()) + if not isinstance(imgsz, list): + imgsz = [imgsz, imgsz] # expand if int/float + try: + # Method 1: Use stride-based input tensor + stride = max(int(model.stride.max()), 32) if hasattr(model, "stride") else 32 # max stride + im = torch.empty((1, p.shape[1], stride, stride), device=p.device, dtype=p.dtype) # input image in BCHW + flops = thop.profile(deepcopy(model), inputs=[im], verbose=False)[0] / 1e9 * 2 # stride GFLOPs + return flops * imgsz[0] / stride * imgsz[1] / stride # imgsz GFLOPs + except Exception: + # Method 2: Use actual image size (required for RTDETR models) + im = torch.empty((1, p.shape[1], *imgsz), device=p.device, dtype=p.dtype) # input image in BCHW format + return thop.profile(deepcopy(model), inputs=[im], verbose=False)[0] / 1e9 * 2 # imgsz GFLOPs + except Exception: + return 0.0 + + +def get_flops_with_torch_profiler(model, imgsz=640): + """Compute model FLOPs using torch profiler (alternative to thop package, but 2-10x slower). + + Args: + model (nn.Module): The model to calculate FLOPs for. + imgsz (int | list, optional): Input image size. + + Returns: + (float): The model's GFLOPs (billions of floating point operations). + """ + if not TORCH_2_0: # torch profiler implemented in torch>=2.0 + return 0.0 + model = unwrap_model(model) + p = next(model.parameters()) + if not isinstance(imgsz, list): + imgsz = [imgsz, imgsz] # expand if int/float + try: + # Use stride size for input tensor + stride = (max(int(model.stride.max()), 32) if hasattr(model, "stride") else 32) * 2 # max stride + im = torch.empty((1, p.shape[1], stride, stride), device=p.device, dtype=p.dtype) # input image in BCHW + with torch.profiler.profile(with_flops=True) as prof: + model(im) + flops = sum(x.flops for x in prof.key_averages()) / 1e9 + flops = flops * imgsz[0] / stride * imgsz[1] / stride # 640x640 GFLOPs + except Exception: + # Use actual image size for input tensor (i.e. required for RTDETR models) + im = torch.empty((1, p.shape[1], *imgsz), device=p.device, dtype=p.dtype) # input image in BCHW format + with torch.profiler.profile(with_flops=True) as prof: + model(im) + flops = sum(x.flops for x in prof.key_averages()) / 1e9 + return flops + + +def initialize_weights(model): + """Initialize model weights, biases, and module settings to default values.""" + for m in model.modules(): + t = type(m) + if t is nn.Conv2d: + pass # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') + elif t is nn.BatchNorm2d: + m.eps = 1e-3 + m.momentum = 0.03 + elif t in {nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6, nn.SiLU}: + m.inplace = True + + +def scale_img(img, ratio=1.0, same_shape=False, gs=32): + """Scale and pad an image tensor, optionally maintaining aspect ratio and padding to gs multiple. + + Args: + img (torch.Tensor): Input image tensor. + ratio (float, optional): Scaling ratio. + same_shape (bool, optional): Whether to maintain the same shape. + gs (int, optional): Grid size for padding. + + Returns: + (torch.Tensor): Scaled and padded image tensor. + """ + if ratio == 1.0: + return img + h, w = img.shape[2:] + s = (int(h * ratio), int(w * ratio)) # new size + img = F.interpolate(img, size=s, mode="bilinear", align_corners=False) # resize + if not same_shape: # pad/crop img + h, w = (math.ceil(x * ratio / gs) * gs for x in (h, w)) + return F.pad(img, [0, w - s[1], 0, h - s[0]], value=0.447) # value = imagenet mean + + +def copy_attr(a, b, include=(), exclude=()): + """Copy attributes from object 'b' to object 'a', with options to include/exclude certain attributes. + + Args: + a (Any): Destination object to copy attributes to. + b (Any): Source object to copy attributes from. + include (tuple, optional): Attributes to include. If empty, all attributes are included. + exclude (tuple, optional): Attributes to exclude. + """ + for k, v in b.__dict__.items(): + if (len(include) and k not in include) or k.startswith("_") or k in exclude: + continue + else: + setattr(a, k, v) + + +def intersect_dicts(da, db, exclude=()): + """Return a dictionary of intersecting keys with matching shapes, excluding 'exclude' keys, using da values. + + Args: + da (dict): First dictionary. + db (dict): Second dictionary. + exclude (tuple, optional): Keys to exclude. + + Returns: + (dict): Dictionary of intersecting keys with matching shapes. + """ + return {k: v for k, v in da.items() if k in db and all(x not in k for x in exclude) and v.shape == db[k].shape} + + +def is_parallel(model): + """Return True if model is of type DP or DDP. + + Args: + model (nn.Module): Model to check. + + Returns: + (bool): True if model is DataParallel or DistributedDataParallel. + """ + return isinstance(model, (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel)) + + +def unwrap_model(m: nn.Module) -> nn.Module: + """Unwrap compiled and parallel models to get the base model. + + Args: + m (nn.Module): A model that may be wrapped by torch.compile (._orig_mod) or parallel wrappers such as + DataParallel/DistributedDataParallel (.module). + + Returns: + (nn.Module): The unwrapped base model without compile or parallel wrappers. + """ + while True: + if hasattr(m, "_orig_mod") and isinstance(m._orig_mod, nn.Module): + m = m._orig_mod + elif hasattr(m, "module") and isinstance(m.module, nn.Module): + m = m.module + else: + return m + + +def one_cycle(y1=0.0, y2=1.0, steps=100): + """Return a lambda function for sinusoidal ramp from y1 to y2 https://arxiv.org/pdf/1812.01187.pdf. + + Args: + y1 (float, optional): Initial value. + y2 (float, optional): Final value. + steps (int, optional): Number of steps. + + Returns: + (function): Lambda function for computing the sinusoidal ramp. + """ + return lambda x: max((1 - math.cos(x * math.pi / steps)) / 2, 0) * (y2 - y1) + y1 + + +def init_seeds(seed=0, deterministic=False): + """Initialize random number generator (RNG) seeds https://pytorch.org/docs/stable/notes/randomness.html. + + Args: + seed (int, optional): Random seed. + deterministic (bool, optional): Whether to set deterministic algorithms. + """ + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.cuda.manual_seed_all(seed) # for Multi-GPU, exception safe + # torch.backends.cudnn.benchmark = True # AutoBatch problem https://github.com/ultralytics/yolov5/issues/9287 + if deterministic: + if TORCH_2_0: + torch.use_deterministic_algorithms(True, warn_only=True) # warn if deterministic is not possible + torch.backends.cudnn.deterministic = True + os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8" + os.environ["PYTHONHASHSEED"] = str(seed) + else: + LOGGER.warning("Upgrade to torch>=2.0.0 for deterministic training.") + else: + unset_deterministic() + + +def unset_deterministic(): + """Unset all the configurations applied for deterministic training.""" + torch.use_deterministic_algorithms(False) + torch.backends.cudnn.deterministic = False + os.environ.pop("CUBLAS_WORKSPACE_CONFIG", None) + os.environ.pop("PYTHONHASHSEED", None) + + +class ModelEMA: + """Updated Exponential Moving Average (EMA) implementation. + + Keeps a moving average of everything in the model state_dict (parameters and buffers). For EMA details see + References. + + To disable EMA set the `enabled` attribute to `False`. + + Attributes: + ema (nn.Module): Copy of the model in evaluation mode. + updates (int): Number of EMA updates. + decay (function): Decay function that determines the EMA weight. + enabled (bool): Whether EMA is enabled. + + References: + - https://github.com/rwightman/pytorch-image-models + - https://www.tensorflow.org/api_docs/python/tf/train/ExponentialMovingAverage + """ + + def __init__(self, model, decay=0.9999, tau=2000, updates=0): + """Initialize EMA for 'model' with given arguments. + + Args: + model (nn.Module): Model to create EMA for. + decay (float, optional): Maximum EMA decay rate. + tau (int, optional): EMA decay time constant. + updates (int, optional): Initial number of updates. + """ + self.ema = deepcopy(unwrap_model(model)).eval() # FP32 EMA + if hasattr(self.ema, "teacher_model"): + # DistillationModel: strip the teacher so the EMA does not carry a full duplicate copy. + self.ema.teacher_model = None + self.updates = updates # number of EMA updates + self.decay = lambda x: decay * (1 - math.exp(-x / tau)) # decay exponential ramp (to help early epochs) + for p in self.ema.parameters(): + p.requires_grad_(False) + self.enabled = True + + def update(self, model): + """Update EMA parameters. + + Args: + model (nn.Module): Model to update EMA from. + """ + if self.enabled: + self.updates += 1 + d = self.decay(self.updates) + + msd = unwrap_model(model).state_dict() # model state_dict + for k, v in self.ema.state_dict().items(): + if v.dtype.is_floating_point: # true for FP16 and FP32 + v *= d + v += (1 - d) * msd[k].detach() + # assert v.dtype == msd[k].dtype == torch.float32, f'{k}: EMA {v.dtype}, model {msd[k].dtype}' + + def update_attr(self, model, include=(), exclude=("process_group", "reducer")): + """Copy attributes from model to EMA, with options to include/exclude certain attributes. + + Args: + model (nn.Module): Model to copy attributes from. + include (tuple, optional): Attributes to include. + exclude (tuple, optional): Attributes to exclude. + """ + if self.enabled: + copy_attr(self.ema, model, include, exclude) + + +def strip_optimizer(f: str | Path = "best.pt", s: str = "", updates: dict[str, Any] | None = None) -> dict[str, Any]: + """Strip optimizer from 'f' to finalize training, optionally save as 's'. + + Args: + f (str | Path): File path to model to strip the optimizer from. + s (str, optional): File path to save the model with stripped optimizer to. If not provided, 'f' will be + overwritten. + updates (dict, optional): A dictionary of updates to overlay onto the checkpoint before saving. + + Returns: + (dict): The combined checkpoint dictionary. + + Examples: + >>> from pathlib import Path + >>> from ultralytics.utils.torch_utils import strip_optimizer + >>> for f in Path("path/to/model/checkpoints").rglob("*.pt"): + ... strip_optimizer(f) + """ + try: + x = torch_load(f, map_location=torch.device("cpu")) + assert isinstance(x, dict), "checkpoint is not a Python dictionary" + assert "model" in x, "'model' missing from checkpoint" + except Exception as e: + LOGGER.warning(f"Skipping {f}, not a valid Ultralytics model: {e}") + return {} + + metadata = { + "date": datetime.now().isoformat(), + "version": __version__, + "license": "AGPL-3.0 License (https://ultralytics.com/license)", + "docs": "https://docs.ultralytics.com", + } + + # Update model + if x.get("ema"): + x["model"] = x["ema"] # replace model with EMA + + # Unwrap DistillationModel to save only the student model + from ultralytics.nn.distill_model import DistillationModel + + if isinstance(x["model"], DistillationModel): + x["model"]._remove_feature_hooks() + x["model"] = x["model"].student_model + + if hasattr(x["model"], "args"): + x["model"].args = dict(x["model"].args) # convert from IterableSimpleNamespace to dict + if hasattr(x["model"], "criterion"): + x["model"].criterion = None # strip loss criterion + x["model"].half() # to FP16 + for p in x["model"].parameters(): + p.requires_grad = False + + # Update other keys + args = {**DEFAULT_CFG_DICT, **x.get("train_args", {})} # combine args + for k in "optimizer", "best_fitness", "ema", "updates", "scaler": # keys + x[k] = None + x["epoch"] = -1 + x["train_args"] = {k: v for k, v in args.items() if k in DEFAULT_CFG_KEYS} # strip non-default keys + # x['model'].args = x['train_args'] + + # Save + combined = {**metadata, **x, **(updates or {})} + torch.save(combined, s or f) # combine dicts (prefer to the right) + mb = os.path.getsize(s or f) / 1e6 # file size + LOGGER.info(f"Optimizer stripped from {f},{f' saved as {s},' if s else ''} {mb:.1f}MB") + return combined + + +def convert_optimizer_state_dict_to_fp16(state_dict): + """Convert the state_dict of a given optimizer to FP16, focusing on the 'state' key for tensor conversions. + + Args: + state_dict (dict): Optimizer state dictionary. + + Returns: + (dict): Converted optimizer state dictionary with FP16 tensors. + """ + for state in state_dict["state"].values(): + for k, v in state.items(): + if k not in {"step", "exp_avg_sq"} and isinstance(v, torch.Tensor) and v.dtype is torch.float32: + state[k] = v.half() + + return state_dict + + +@contextmanager +def cuda_memory_usage(device=None): + """Monitor and manage CUDA memory usage. + + This function checks if CUDA is available and, if so, empties the CUDA cache to free up unused memory. It then + yields a dictionary containing memory usage information, which can be updated by the caller. Finally, it updates the + dictionary with the amount of memory reserved by CUDA on the specified device. + + Args: + device (torch.device, optional): The CUDA device to query memory usage for. + + Yields: + (dict): A dictionary with a key 'memory' initialized to 0, which will be updated with the reserved memory. + """ + cuda_info = dict(memory=0) + if torch.cuda.is_available(): + torch.cuda.empty_cache() + try: + yield cuda_info + finally: + cuda_info["memory"] = torch.cuda.memory_reserved(device) + else: + yield cuda_info + + +def profile_ops(input, ops, n=10, device=None, max_num_obj=0): + """Ultralytics speed, memory and FLOPs profiler. + + Args: + input (torch.Tensor | list): Input tensor(s) to profile. + ops (nn.Module | list): Model or list of operations to profile. + n (int, optional): Number of iterations to average. + device (str | torch.device, optional): Device to profile on. + max_num_obj (int, optional): Maximum number of objects for simulation. + + Returns: + (list): Profile results for each operation. + + Examples: + >>> from ultralytics.utils.torch_utils import profile_ops + >>> input = torch.randn(16, 3, 640, 640) + >>> m1 = lambda x: x * torch.sigmoid(x) + >>> m2 = nn.SiLU() + >>> profile_ops(input, [m1, m2], n=100) # profile over 100 iterations + """ + try: + import thop + except ImportError: + thop = None # conda support without 'ultralytics-thop' installed + + results = [] + if not isinstance(device, torch.device): + device = select_device(device) + LOGGER.info( + f"{'Params':>12s}{'GFLOPs':>12s}{'GPU_mem (GB)':>14s}{'forward (ms)':>14s}{'backward (ms)':>14s}" + f"{'input':>24s}{'output':>24s}" + ) + gc.collect() # attempt to free unused memory + torch.cuda.empty_cache() + for x in input if isinstance(input, list) else [input]: + x = x.to(device) + x.requires_grad = True + for m in ops if isinstance(ops, list) else [ops]: + m = m.to(device) if hasattr(m, "to") else m # device + m = m.half() if hasattr(m, "half") and isinstance(x, torch.Tensor) and x.dtype is torch.float16 else m + tf, tb, t = 0, 0, [0, 0, 0] # dt forward, backward + try: + flops = thop.profile(deepcopy(m), inputs=[x], verbose=False)[0] / 1e9 * 2 if thop else 0 # GFLOPs + except Exception: + flops = 0 + + try: + mem = 0 + for _ in range(n): + with cuda_memory_usage(device) as cuda_info: + t[0] = time_sync() + y = m(x) + t[1] = time_sync() + try: + (sum(yi.sum() for yi in y) if isinstance(y, list) else y).sum().backward() + t[2] = time_sync() + except Exception: # no backward method + # print(e) # for debug + t[2] = float("nan") + mem += cuda_info["memory"] / 1e9 # (GB) + tf += (t[1] - t[0]) * 1000 / n # ms per op forward + tb += (t[2] - t[1]) * 1000 / n # ms per op backward + if max_num_obj: # simulate training with predictions per image grid (for AutoBatch) + with cuda_memory_usage(device) as cuda_info: + torch.randn( + x.shape[0], + max_num_obj, + int(sum((x.shape[-1] / s) * (x.shape[-2] / s) for s in m.stride.tolist())), + device=device, + dtype=torch.float32, + ) + mem += cuda_info["memory"] / 1e9 # (GB) + s_in, s_out = (tuple(x.shape) if isinstance(x, torch.Tensor) else "list" for x in (x, y)) # shapes + p = sum(x.numel() for x in m.parameters()) if isinstance(m, nn.Module) else 0 # parameters + LOGGER.info(f"{p:12}{flops:12.4g}{mem:>14.3f}{tf:14.4g}{tb:14.4g}{s_in!s:>24s}{s_out!s:>24s}") + results.append([p, flops, mem, tf, tb, s_in, s_out]) + except Exception as e: + LOGGER.info(e) + results.append(None) + finally: + gc.collect() # attempt to free unused memory + torch.cuda.empty_cache() + return results + + +class EarlyStopping: + """Early stopping class that stops training when a specified number of epochs have passed without improvement. + + Attributes: + best_fitness (float): Best fitness value observed. + best_epoch (int): Epoch where best fitness was observed. + patience (int): Number of epochs to wait after fitness stops improving before stopping. + possible_stop (bool): Flag indicating if stopping may occur next epoch. + """ + + def __init__(self, patience=50): + """Initialize early stopping object. + + Args: + patience (int, optional): Number of epochs to wait after fitness stops improving before stopping. + """ + self.best_fitness = 0.0 # i.e. mAP + self.best_epoch = 0 + self.patience = patience or float("inf") # epochs to wait after fitness stops improving to stop + self.possible_stop = False # possible stop may occur next epoch + + def __call__(self, epoch, fitness): + """Check whether to stop training. + + Args: + epoch (int): Current epoch of training. + fitness (float): Fitness value of current epoch. + + Returns: + (bool): True if training should stop, False otherwise. + """ + if fitness is None: # check if fitness=None (happens when val=False) + return False + + if fitness > self.best_fitness or self.best_fitness == 0: # allow for early zero-fitness stage of training + self.best_epoch = epoch + self.best_fitness = fitness + delta = epoch - self.best_epoch # epochs without improvement + self.possible_stop = delta >= (self.patience - 1) # possible stop may occur next epoch + stop = delta >= self.patience # stop training if patience exceeded + if stop: + prefix = colorstr("EarlyStopping: ") + LOGGER.info( + f"{prefix}Training stopped early as no improvement observed in last {self.patience} epochs. " + f"Best results observed at epoch {self.best_epoch}, best model saved as best.pt.\n" + f"To update EarlyStopping(patience={self.patience}) pass a new patience value, " + f"i.e. `patience=300` or use `patience=0` to disable EarlyStopping." + ) + return stop + + +def attempt_compile( + model: torch.nn.Module, + device: torch.device, + imgsz: int = 640, + use_autocast: bool = False, + warmup: bool = False, + mode: bool | str = "default", +) -> torch.nn.Module: + """Compile a model with torch.compile and optionally warm up the graph to reduce first-iteration latency. + + This utility attempts to compile the provided model using the inductor backend. If compilation is unavailable or + fails, the original model is returned unchanged. An optional warmup performs a single forward pass on a dummy input + to prime the compiled graph and measure compile/warmup time. + + Args: + model (torch.nn.Module): Model to compile. + device (torch.device): Inference device used for warmup and autocast decisions. + imgsz (int, optional): Square input size to create a dummy tensor with shape (1, 3, imgsz, imgsz) for warmup. + use_autocast (bool, optional): Whether to run warmup under autocast on CUDA or MPS devices. + warmup (bool, optional): Whether to execute a single dummy forward pass to warm up the compiled model. + mode (bool | str, optional): torch.compile mode. True → "default", False → no compile, or a string like + "default", "reduce-overhead", "max-autotune-no-cudagraphs". + + Returns: + (torch.nn.Module): Compiled model if compilation succeeds, otherwise the original unmodified model. + + Examples: + >>> device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + >>> # Try to compile and warm up a model with a 640x640 input + >>> model = attempt_compile(model, device=device, imgsz=640, use_autocast=True, warmup=True) + + Notes: + - If the current PyTorch build does not provide torch.compile, the function returns the input model immediately. + - Warmup runs under torch.inference_mode and may use torch.autocast for CUDA/MPS to align compute precision. + - CUDA devices are synchronized after warmup to account for asynchronous kernel execution. + """ + if not hasattr(torch, "compile") or not mode: + return model + + if mode is True: + mode = "default" + prefix = colorstr("compile:") + LOGGER.info(f"{prefix} starting torch.compile with '{mode}' mode...") + if mode == "max-autotune": + LOGGER.warning(f"{prefix} mode='{mode}' not recommended, using mode='max-autotune-no-cudagraphs' instead") + mode = "max-autotune-no-cudagraphs" + t0 = time.perf_counter() + try: + model = torch.compile(model, mode=mode, backend="inductor") + except Exception as e: + LOGGER.warning(f"{prefix} torch.compile failed, continuing uncompiled: {e}") + return model + t_compile = time.perf_counter() - t0 + + t_warm = 0.0 + if warmup: + # Use a single dummy tensor to build the graph shape state and reduce first-iteration latency + dummy = torch.zeros(1, 3, imgsz, imgsz, device=device) + if use_autocast and device.type == "cuda": + dummy = dummy.half() + t1 = time.perf_counter() + with torch.inference_mode(): + if use_autocast and device.type in {"cuda", "mps"}: + with torch.autocast(device.type): + _ = model(dummy) + else: + _ = model(dummy) + if device.type == "cuda": + torch.cuda.synchronize(device) + t_warm = time.perf_counter() - t1 + + total = t_compile + t_warm + if warmup: + LOGGER.info(f"{prefix} complete in {total:.1f}s (compile {t_compile:.1f}s + warmup {t_warm:.1f}s)") + else: + LOGGER.info(f"{prefix} compile complete in {t_compile:.1f}s (no warmup)") + return model diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/tqdm.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/tqdm.py new file mode 100644 index 0000000000000000000000000000000000000000..c89c52962a0bd5c22cee98f39878179e44662d99 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/tqdm.py @@ -0,0 +1,443 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import os +import sys +import time +from functools import lru_cache +from typing import IO, Any + + +@lru_cache(maxsize=1) +def is_noninteractive_console() -> bool: + """Check for known non-interactive console environments.""" + return "GITHUB_ACTIONS" in os.environ or "RUNPOD_POD_ID" in os.environ + + +class TQDM: + """Lightweight zero-dependency progress bar for Ultralytics. + + Provides clean, rich-style progress bars suitable for various environments including Weights & Biases, console + outputs, and other logging systems. Features zero external dependencies, clean single-line output, rich-style + progress bars with Unicode block characters, context manager support, iterator protocol support, and dynamic + description updates. + + Attributes: + iterable (Any): Iterable to wrap with progress bar. + desc (str): Prefix description for the progress bar. + total (int | None): Expected number of iterations. + disable (bool): Whether to disable the progress bar. + unit (str): String for units of iteration. + unit_scale (bool): Auto-scale units flag. + unit_divisor (int): Divisor for unit scaling. + leave (bool): Whether to leave the progress bar after completion. + mininterval (float): Minimum time interval between updates. + initial (int): Initial counter value. + n (int): Current iteration count. + closed (bool): Whether the progress bar is closed. + bar_format (str | None): Custom bar format string. + file (IO[str]): Output file stream. + + Methods: + update: Update progress by n steps. + set_description: Set or update the description. + set_postfix: Set postfix for the progress bar. + close: Close the progress bar and clean up. + refresh: Refresh the progress bar display. + clear: Clear the progress bar from display. + write: Write a message without breaking the progress bar. + + Examples: + Basic usage with iterator: + >>> for i in TQDM(range(100)): + ... time.sleep(0.01) + + With custom description: + >>> pbar = TQDM(range(100), desc="Processing") + >>> for i in pbar: + ... pbar.set_description(f"Processing item {i}") + + Context manager usage: + >>> with TQDM(total=100, unit="B", unit_scale=True) as pbar: + ... for i in range(100): + ... pbar.update(1) + + Manual updates: + >>> pbar = TQDM(total=100, desc="Training") + >>> for epoch in range(100): + ... # Do work + ... pbar.update(1) + >>> pbar.close() + """ + + # Constants + MIN_RATE_CALC_INTERVAL = 0.01 # Minimum time interval for rate calculation + RATE_SMOOTHING_FACTOR = 0.3 # Factor for exponential smoothing of rates + MAX_SMOOTHED_RATE = 1000000 # Maximum rate to apply smoothing to + NONINTERACTIVE_MIN_INTERVAL = 60.0 # Minimum interval for non-interactive environments + + def __init__( + self, + iterable: Any = None, + desc: str | None = None, + total: int | None = None, + leave: bool = True, + file: IO[str] | None = None, + mininterval: float = 0.1, + disable: bool | None = None, + unit: str = "it", + unit_scale: bool = True, + unit_divisor: int = 1000, + bar_format: str | None = None, # kept for API compatibility; not used for formatting + initial: int = 0, + **kwargs, + ) -> None: + """Initialize the TQDM progress bar with specified configuration options. + + Args: + iterable (Any, optional): Iterable to wrap with progress bar. + desc (str, optional): Prefix description for the progress bar. + total (int, optional): Expected number of iterations. + leave (bool, optional): Whether to leave the progress bar after completion. + file (IO[str], optional): Output file stream for progress display. + mininterval (float, optional): Minimum time interval between updates (default 0.1s, 60s in GitHub Actions). + disable (bool, optional): Whether to disable the progress bar. Auto-detected if None. + unit (str, optional): String for units of iteration (default "it" for items). + unit_scale (bool, optional): Auto-scale units for bytes/data units. + unit_divisor (int, optional): Divisor for unit scaling (default 1000). + bar_format (str, optional): Custom bar format string. + initial (int, optional): Initial counter value. + **kwargs (Any): Additional keyword arguments for compatibility (ignored). + """ + # Disable if not verbose + if disable is None: + try: + from ultralytics.utils import LOGGER, VERBOSE + + disable = not VERBOSE or LOGGER.getEffectiveLevel() > 20 + except ImportError: + disable = False + + self.iterable = iterable + self.desc = desc or "" + self.total = total or (len(iterable) if hasattr(iterable, "__len__") else None) or None # prevent total=0 + self.disable = disable + self.unit = unit + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + self.leave = leave + self.noninteractive = is_noninteractive_console() + self.mininterval = max(mininterval, self.NONINTERACTIVE_MIN_INTERVAL) if self.noninteractive else mininterval + self.initial = initial + + # Kept for API compatibility (unused for f-string formatting) + self.bar_format = bar_format + + self.file = file or sys.stdout + + # Internal state + self.n = self.initial + self.last_print_n = self.initial + self.last_print_t = time.time() + self.start_t = time.time() + self.last_rate = 0.0 + self.closed = False + self.is_bytes = unit_scale and unit in {"B", "bytes"} + self.scales = ( + [(1073741824, "GB/s"), (1048576, "MB/s"), (1024, "KB/s")] + if self.is_bytes + else [(1e9, f"G{self.unit}/s"), (1e6, f"M{self.unit}/s"), (1e3, f"K{self.unit}/s")] + ) + + if not self.disable and self.total and not self.noninteractive: + self._display() + + def _format_rate(self, rate: float) -> str: + """Format rate with units, switching between it/s and s/it for readability.""" + if rate <= 0: + return "" + + inv_rate = 1 / rate if rate else None + + # Use s/it format when inv_rate > 1 (i.e., rate < 1 it/s) for better readability + if inv_rate and inv_rate > 1: + return f"{inv_rate:.1f}s/B" if self.is_bytes else f"{inv_rate:.1f}s/{self.unit}" + + # Use it/s format for fast iterations + fallback = f"{rate:.1f}B/s" if self.is_bytes else f"{rate:.1f}{self.unit}/s" + return next((f"{rate / t:.1f}{u}" for t, u in self.scales if rate >= t), fallback) + + def _format_num(self, num: int | float) -> str: + """Format number with optional unit scaling.""" + if not self.unit_scale or not self.is_bytes: + return str(num) + + for unit in ("", "K", "M", "G", "T"): + if abs(num) < self.unit_divisor: + return f"{num:3.1f}{unit}B" if unit else f"{num:.0f}B" + num /= self.unit_divisor + return f"{num:.1f}PB" + + @staticmethod + def _format_time(seconds: float) -> str: + """Format time duration.""" + if seconds < 60: + return f"{seconds:.1f}s" + elif seconds < 3600: + return f"{int(seconds // 60)}:{seconds % 60:02.0f}" + else: + h, m = int(seconds // 3600), int((seconds % 3600) // 60) + return f"{h}:{m:02d}:{seconds % 60:02.0f}" + + def _generate_bar(self, width: int = 12) -> str: + """Generate progress bar.""" + if self.total is None: + return "━" * width if self.closed else "─" * width + + frac = min(1.0, self.n / self.total) + filled = int(frac * width) + bar = "━" * filled + "─" * (width - filled) + if filled < width and frac * width - filled > 0.5: + bar = f"{bar[:filled]}╸{bar[filled + 1 :]}" + return bar + + def _should_update(self, dt: float, dn: int) -> bool: + """Check if display should update.""" + if self.noninteractive: + return False + return (self.total is not None and self.n >= self.total) or (dt >= self.mininterval) + + def _display(self, final: bool = False) -> None: + """Display progress bar.""" + if self.disable or (self.closed and not final): + return + + current_time = time.time() + dt = current_time - self.last_print_t + dn = self.n - self.last_print_n + + if not final and not self._should_update(dt, dn): + return + + # Calculate rate (avoid crazy numbers) + if dt > self.MIN_RATE_CALC_INTERVAL: + rate = dn / dt if dt else 0.0 + # Smooth rate for reasonable values, use raw rate for very high values + if rate < self.MAX_SMOOTHED_RATE: + self.last_rate = self.RATE_SMOOTHING_FACTOR * rate + (1 - self.RATE_SMOOTHING_FACTOR) * self.last_rate + rate = self.last_rate + else: + rate = self.last_rate + + # At completion, use overall rate + if self.total and self.n >= self.total: + overall_elapsed = current_time - self.start_t + if overall_elapsed > 0: + rate = self.n / overall_elapsed + + # Update counters + self.last_print_n = self.n + self.last_print_t = current_time + elapsed = current_time - self.start_t + + # Remaining time + remaining_str = "" + if self.total and 0 < self.n < self.total and elapsed > 0: + est_rate = rate or (self.n / elapsed) + remaining_str = f"<{self._format_time((self.total - self.n) / est_rate)}" + + # Numbers and percent (floor so 100% only shows at true completion) + if self.total: + percent = int(self.n / self.total * 100) + n_str = self._format_num(self.n) + t_str = self._format_num(self.total) + if self.is_bytes and n_str[-2] == t_str[-2]: # Collapse suffix only when identical (e.g. "5.4/5.4MB") + n_str = n_str.rstrip("KMGTPB") + else: + percent = 0.0 + n_str, t_str = self._format_num(self.n), "?" + + elapsed_str = self._format_time(elapsed) + rate_str = self._format_rate(rate) or (self._format_rate(self.n / elapsed) if elapsed > 0 else "") + + bar = self._generate_bar() + + # Compose progress line via f-strings (two shapes: with/without total) + if self.total: + if self.is_bytes and self.n >= self.total: + # Completed bytes: show only final size + progress_str = f"{self.desc}: {percent:.0f}% {bar} {t_str} {rate_str} {elapsed_str}" + else: + progress_str = ( + f"{self.desc}: {percent:.0f}% {bar} {n_str}/{t_str} {rate_str} {elapsed_str}{remaining_str}" + ) + else: + progress_str = f"{self.desc}: {bar} {n_str} {rate_str} {elapsed_str}" + + # Write to output + try: + if self.noninteractive: + # In non-interactive environments, avoid carriage return which creates empty lines + self.file.write(progress_str) + else: + # In interactive terminals, use carriage return and clear line for updating display + self.file.write(f"\r\033[K{progress_str}") + self.file.flush() + except Exception: + pass + + def update(self, n: int = 1) -> None: + """Update progress by n steps.""" + if not self.disable and not self.closed: + self.n += n + self._display() + + def set_description(self, desc: str | None) -> None: + """Set description.""" + self.desc = desc or "" + if not self.disable: + self._display() + + def set_postfix(self, **kwargs: Any) -> None: + """Set postfix (appends to description).""" + if kwargs: + postfix = ", ".join(f"{k}={v}" for k, v in kwargs.items()) + base_desc = self.desc.split(" | ")[0] if " | " in self.desc else self.desc + self.set_description(f"{base_desc} | {postfix}") + + def close(self) -> None: + """Close progress bar.""" + if self.closed: + return + + self.closed = True + + if not self.disable: + # Final display + if self.total and self.n >= self.total: + self.n = self.total + if self.n != self.last_print_n: # Skip if 100% already shown + self._display(final=True) + else: + self._display(final=True) + + # Cleanup + if self.leave: + self.file.write("\n") + else: + self.file.write("\r\033[K") + + try: + self.file.flush() + except Exception: + pass + + def __enter__(self) -> TQDM: + """Enter context manager.""" + return self + + def __exit__(self, *args: Any) -> None: + """Exit context manager and close progress bar.""" + self.close() + + def __iter__(self) -> Any: + """Iterate over the wrapped iterable with progress updates.""" + if self.iterable is None: + raise TypeError("'NoneType' object is not iterable") + + try: + for item in self.iterable: + yield item + self.update(1) + finally: + self.close() + + def __del__(self) -> None: + """Destructor to ensure cleanup.""" + try: + self.close() + except Exception: + pass + + def refresh(self) -> None: + """Refresh display.""" + if not self.disable: + self._display() + + def clear(self) -> None: + """Clear progress bar.""" + if not self.disable: + try: + self.file.write("\r\033[K") + self.file.flush() + except Exception: + pass + + @staticmethod + def write(s: str, file: IO[str] | None = None, end: str = "\n") -> None: + """Static method to write without breaking progress bar.""" + file = file or sys.stdout + try: + file.write(s + end) + file.flush() + except Exception: + pass + + +if __name__ == "__main__": + import time + + print("1. Basic progress bar with known total:") + for i in TQDM(range(3), desc="Known total"): + time.sleep(0.05) + + print("\n2. Manual updates with known total:") + pbar = TQDM(total=300, desc="Manual updates", unit="files") + for i in range(300): + time.sleep(0.03) + pbar.update(1) + if i % 10 == 9: + pbar.set_description(f"Processing batch {i // 10 + 1}") + pbar.close() + + print("\n3. Progress bar with unknown total:") + pbar = TQDM(desc="Unknown total", unit="items") + for i in range(25): + time.sleep(0.08) + pbar.update(1) + if i % 5 == 4: + pbar.set_postfix(processed=i + 1, status="OK") + pbar.close() + + print("\n4. Context manager with unknown total:") + with TQDM(desc="Processing stream", unit="B", unit_scale=True, unit_divisor=1024) as pbar: + for i in range(30): + time.sleep(0.1) + pbar.update(1024 * 1024 * i) # Simulate processing MB of data + + print("\n5. Iterator with unknown length:") + + def data_stream(): + """Simulate a data stream of unknown length.""" + import random + + for i in range(random.randint(10, 20)): + yield f"data_chunk_{i}" + + for chunk in TQDM(data_stream(), desc="Stream processing", unit="chunks"): + time.sleep(0.1) + + print("\n6. File processing simulation (unknown size):") + + def process_files(): + """Simulate processing files of unknown count.""" + return [f"file_{i}.txt" for i in range(18)] + + pbar = TQDM(desc="Scanning files", unit="files") + files = process_files() + for i, filename in enumerate(files): + time.sleep(0.06) + pbar.update(1) + pbar.set_description(f"Processing {filename}") + pbar.close() diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/triton.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/triton.py new file mode 100644 index 0000000000000000000000000000000000000000..a6263694ab2975571b956b6afd57075756f95406 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/triton.py @@ -0,0 +1,112 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import ast +from urllib.parse import urlsplit + +import numpy as np + + +class TritonRemoteModel: + """Client for interacting with a remote Triton Inference Server model. + + This class provides a convenient interface for sending inference requests to a Triton Inference Server and + processing the responses. Supports both HTTP and gRPC communication protocols. + + Attributes: + endpoint (str): The name of the model on the Triton server. + url (str): The URL of the Triton server. + triton_client: The Triton client (either HTTP or gRPC). + InferInput: The input class for the Triton client. + InferRequestedOutput: The output request class for the Triton client. + input_formats (list[str]): The data types of the model inputs. + np_input_formats (list[type]): The numpy data types of the model inputs. + input_names (list[str]): The names of the model inputs. + output_names (list[str]): The names of the model outputs. + metadata: The metadata associated with the model. + + Methods: + __call__: Call the model with the given inputs and return the outputs. + + Examples: + Initialize a Triton client with HTTP + >>> model = TritonRemoteModel(url="localhost:8000", endpoint="yolov8", scheme="http") + + Make inference with numpy arrays + >>> outputs = model(np.random.rand(1, 3, 640, 640).astype(np.float32)) + """ + + def __init__(self, url: str, endpoint: str = "", scheme: str = ""): + """Initialize the TritonRemoteModel for interacting with a remote Triton Inference Server. + + Arguments may be provided individually or parsed from a collective 'url' argument of the form + ://// + + Args: + url (str): The URL of the Triton server. + endpoint (str, optional): The name of the model on the Triton server. + scheme (str, optional): The communication scheme ('http' or 'grpc'). + """ + if not endpoint and not scheme: # Parse all args from URL string + splits = urlsplit(url) + endpoint = splits.path.strip("/").split("/", 1)[0] + scheme = splits.scheme + url = splits.netloc + + self.endpoint = endpoint + self.url = url + + # Choose the Triton client based on the communication scheme + if scheme == "http": + import tritonclient.http as client + + self.triton_client = client.InferenceServerClient(url=self.url, verbose=False, ssl=False) + config = self.triton_client.get_model_config(endpoint) + else: + import tritonclient.grpc as client + + self.triton_client = client.InferenceServerClient(url=self.url, verbose=False, ssl=False) + config = self.triton_client.get_model_config(endpoint, as_json=True)["config"] + + # Sort output names alphabetically, i.e. 'output0', 'output1', etc. + config["output"] = sorted(config["output"], key=lambda x: x.get("name")) + + # Define model attributes + type_map = {"TYPE_FP32": np.float32, "TYPE_FP16": np.float16, "TYPE_UINT8": np.uint8} + self.InferRequestedOutput = client.InferRequestedOutput + self.InferInput = client.InferInput + self.input_formats = [x["data_type"] for x in config["input"]] + self.np_input_formats = [type_map[x] for x in self.input_formats] + self.input_names = [x["name"] for x in config["input"]] + self.output_names = [x["name"] for x in config["output"]] + self.metadata = ast.literal_eval(config.get("parameters", {}).get("metadata", {}).get("string_value", "None")) + + def __call__(self, *inputs: np.ndarray) -> list[np.ndarray]: + """Call the model with the given inputs and return inference results. + + Args: + *inputs (np.ndarray): Input data to the model. Each array should match the expected shape and type for the + corresponding model input. + + Returns: + (list[np.ndarray]): Model outputs cast to the dtype of the first input. Each element in the list corresponds + to one of the model's output tensors. + + Examples: + >>> model = TritonRemoteModel(url="localhost:8000", endpoint="yolov8", scheme="http") + >>> outputs = model(np.random.rand(1, 3, 640, 640).astype(np.float32)) + """ + infer_inputs = [] + input_format = inputs[0].dtype + for i, x in enumerate(inputs): + if x.dtype != self.np_input_formats[i]: + x = x.astype(self.np_input_formats[i]) + infer_input = self.InferInput(self.input_names[i], [*x.shape], self.input_formats[i].replace("TYPE_", "")) + infer_input.set_data_from_numpy(x) + infer_inputs.append(infer_input) + + infer_outputs = [self.InferRequestedOutput(output_name) for output_name in self.output_names] + outputs = self.triton_client.infer(model_name=self.endpoint, inputs=infer_inputs, outputs=infer_outputs) + + return [outputs.as_numpy(output_name).astype(input_format) for output_name in self.output_names] diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/tuner.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/tuner.py new file mode 100644 index 0000000000000000000000000000000000000000..b20d7dc80b38546e25ac0f7ad3ba12d2a8c06b3a --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/tuner.py @@ -0,0 +1,515 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from __future__ import annotations + +import numpy as np + +from ultralytics.cfg import TASK2DATA, TASK2METRIC, get_cfg, get_save_dir +from ultralytics.utils import DEFAULT_CFG, DEFAULT_CFG_DICT, LOGGER, NUM_THREADS, checks, colorstr + +RAY_SEARCH_ALG_REQUIREMENTS = { + "random": None, + "ax": "ax-platform", + "bayesopt": "bayesian-optimization==1.4.3", + "bohb": ["hpbandster", "ConfigSpace"], + "hebo": "HEBO>=0.2.0", + "hyperopt": "hyperopt", + "nevergrad": "nevergrad", + "optuna": "optuna", + "zoopt": "zoopt", +} + + +def _sanitize_tune_value(value: dict): + """Convert NumPy-backed Tune values into native Python types for YAML serialization. + + Args: + value (dict): The value to convert. Can be a dict, list, tuple, NumPy scalar, or NumPy array. + + Returns: + The converted value with NumPy types replaced by native Python types. + """ + if isinstance(value, dict): + return {k: _sanitize_tune_value(v) for k, v in value.items()} + if isinstance(value, list): + return [_sanitize_tune_value(v) for v in value] + if isinstance(value, tuple): + return tuple(_sanitize_tune_value(v) for v in value) + if isinstance(value, np.generic): + return value.item() + if isinstance(value, np.ndarray): + return value.tolist() + return value + + +def _get_ray_search_alg_kind(search_alg): + """Return the normalized Ray Tune search algorithm kind for known searcher objects. + + Args: + search_alg (str | ray.tune.search.Searcher): The search algorithm to identify. Can be None, a string, or a Ray + Tune searcher object. + + Returns: + str | None: The normalized search algorithm name, or None if not recognized. + """ + if search_alg is None: + return None + if isinstance(search_alg, str): + normalized = search_alg.strip().lower() + return normalized or None + + cls = search_alg.__class__ + module, name = cls.__module__, cls.__name__ + if name == "AxSearch" and module.startswith("ray.tune.search.ax"): + return "ax" + if name == "TuneBOHB" and module.startswith("ray.tune.search.bohb"): + return "bohb" + if name == "ZOOptSearch" and module.startswith("ray.tune.search.zoopt"): + return "zoopt" + return None + + +def _validate_ax_search_space(space): + """Validate that a Tune search space can be consumed by Ax. + + Args: + space (dict): The hyperparameter search space to validate. + + Returns: + list: The converted Ax parameters. + + Raises: + ImportError: If the required 'ax-platform' package is not installed. + """ + checks.check_requirements(RAY_SEARCH_ALG_REQUIREMENTS["ax"]) + + from ray.tune.search.ax.ax_search import AxSearch + + return AxSearch.convert_search_space(space) + + +def _create_ax_search(space, task): + """Create an Ax searcher with an initialized experiment. + + Args: + space (dict): The hyperparameter search space. + task (str): The task type (e.g., 'detect', 'segment', 'classify'). + + Returns: + AxSearch (ray.tune.search.Searcher): The configured Ax search algorithm. + + Raises: + ImportError: If required Ax packages are not installed. + """ + parameters = _validate_ax_search_space(space) + + from ax.service.ax_client import AxClient + from ax.service.utils.instantiation import ObjectiveProperties + from ray.tune.search.ax.ax_search import AxSearch + + ax_client = AxClient() + ax_client.create_experiment( + parameters=parameters, + objectives={TASK2METRIC[task]: ObjectiveProperties(minimize=False)}, + ) + return AxSearch(ax_client=ax_client) + + +def _convert_bohb_search_space(space): + """Convert a Tune search space into BOHB-compatible ConfigSpace and fixed-only Tune param_space. + + Args: + space (dict): The hyperparameter search space. + + Returns: + (tuple): A tuple containing the ConfigSpace object and a dict of fixed parameters. + + Raises: + ValueError: If the search space contains grid search parameters or unsupported samplers. + ImportError: If required BOHB packages are not installed. + """ + checks.check_requirements(RAY_SEARCH_ALG_REQUIREMENTS["bohb"]) + + import ConfigSpace + from ray.tune.search.sample import Categorical, Float, Integer, LogUniform, Quantized, Uniform + from ray.tune.search.variant_generator import parse_spec_vars + from ray.tune.utils import flatten_dict + + resolved_space = flatten_dict(space, prevent_delimiter=True) + resolved_vars, domain_vars, grid_vars = parse_spec_vars(resolved_space) + if grid_vars: + raise ValueError("Grid search parameters cannot be automatically converted to a TuneBOHB search space.") + + cs = ConfigSpace.ConfigurationSpace() + for path, domain in domain_vars: + par = "/".join(str(p) for p in path) + sampler = domain.get_sampler() + if isinstance(sampler, Quantized): + raise ValueError("TuneBOHB does not support quantized search spaces with the current ConfigSpace version.") + + if isinstance(domain, Float) and isinstance(sampler, (Uniform, LogUniform)): + cs.add( + ConfigSpace.UniformFloatHyperparameter( + par, lower=domain.lower, upper=domain.upper, log=isinstance(sampler, LogUniform) + ) + ) + elif isinstance(domain, Integer) and isinstance(sampler, (Uniform, LogUniform)): + upper = domain.upper - 1 # Tune integer search spaces are exclusive on the upper bound + cs.add( + ConfigSpace.UniformIntegerHyperparameter( + par, lower=domain.lower, upper=upper, log=isinstance(sampler, LogUniform) + ) + ) + elif isinstance(domain, Categorical) and isinstance(sampler, Uniform): + cs.add(ConfigSpace.CategoricalHyperparameter(par, choices=domain.categories)) + else: + raise ValueError( + f"TuneBOHB does not support parameters of type {type(domain).__name__} " + f"with sampler type {type(domain.sampler).__name__}." + ) + + fixed_param_space = {"/".join(str(p) for p in path): value for path, value in resolved_vars} + return cs, fixed_param_space + + +def _create_bohb_search(space, task): + """Create a BOHB searcher using a ConfigSpace definition compatible with current ConfigSpace versions. + + Args: + space (dict): The hyperparameter search space. + task (str): The task type (e.g., 'detect', 'segment', 'classify'). + + Returns: + (tuple): A tuple containing the TuneBOHB searcher and fixed parameter space dict. + + Raises: + ImportError: If required BOHB packages are not installed. + """ + cs, fixed_param_space = _convert_bohb_search_space(space) + + from ray.tune.search.bohb.bohb_search import TuneBOHB + + return TuneBOHB(space=cs, metric=TASK2METRIC[task], mode="max"), fixed_param_space + + +def _create_nevergrad_search(task): + """Create a Nevergrad searcher with a default optimizer. + + Args: + task (str): The task type (e.g., 'detect', 'segment', 'classify'). + + Returns: + (NevergradSearch): The configured Nevergrad search algorithm. + + Raises: + ImportError: If the 'nevergrad' package is not installed. + """ + checks.check_requirements(RAY_SEARCH_ALG_REQUIREMENTS["nevergrad"]) + + import nevergrad as ng + from ray.tune.search.nevergrad import NevergradSearch + + return NevergradSearch(optimizer=ng.optimizers.OnePlusOne, metric=TASK2METRIC[task], mode="max") + + +def _convert_zoopt_search_space(space): + """Convert a Tune search space into ZOOpt-compatible dimensions and fixed-only Tune param_space. + + Args: + space (dict): The hyperparameter search space. + + Returns: + (tuple): A tuple containing the ZOOpt dimension dict and fixed parameter space dict. + + Raises: + ImportError: If the 'zoopt' package is not installed. + """ + checks.check_requirements(RAY_SEARCH_ALG_REQUIREMENTS["zoopt"]) + + from ray.tune.search.variant_generator import parse_spec_vars + from ray.tune.search.zoopt import ZOOptSearch + from ray.tune.utils import flatten_dict + + resolved_space = flatten_dict(space, prevent_delimiter=True) + resolved_vars, _, _ = parse_spec_vars(resolved_space) + fixed_param_space = {"/".join(str(p) for p in path): value for path, value in resolved_vars} + dim_dict = ZOOptSearch.convert_search_space(space) + return dim_dict, fixed_param_space + + +def _create_zoopt_search(space, task, iterations): + """Create a ZOOpt searcher with required budget and converted search space. + + Args: + space (dict): The hyperparameter search space. + task (str): The task type (e.g., 'detect', 'segment', 'classify'). + iterations (int): The maximum number of trials (budget) for ZOOpt. + + Returns: + (tuple): A tuple containing the ZOOptSearch searcher and fixed parameter space dict. + + Raises: + ImportError: If the 'zoopt' package is not installed. + """ + dim_dict, fixed_param_space = _convert_zoopt_search_space(space) + + from ray.tune.search.zoopt import ZOOptSearch + + return ZOOptSearch( + algo="asracos", budget=iterations, dim_dict=dim_dict, metric=TASK2METRIC[task], mode="max" + ), fixed_param_space + + +def _resolve_ray_search_alg(search_alg, task, space, iterations): + """Resolve search algorithms and normalize Tune param_space for known Ray Tune searchers. + + Args: + search_alg (str | object | None): The search algorithm to use. Can be a string name, a pre-instantiated Ray Tune + searcher object, or None for default behavior. + task (str): The task type (e.g., 'detect', 'segment', 'classify'). + space (dict): The hyperparameter search space. + iterations (int): The maximum number of trials to run. + + Returns: + (tuple): A tuple containing (resolved_search_alg, tuner_param_space, resolved_search_alg_kind). + - resolved_search_alg: The configured searcher or None. + - tuner_param_space: The normalized parameter space for the tuner. + - resolved_search_alg_kind: The normalized algorithm name or None. + + Raises: + ValueError: If an unsupported search_alg string is provided. + ModuleNotFoundError: If required dependencies for the chosen algorithm are not installed. + """ + if search_alg is None: + return None, space, None + + normalized = _get_ray_search_alg_kind(search_alg) + if isinstance(search_alg, str): + if not normalized: + return None, space, None + if normalized not in RAY_SEARCH_ALG_REQUIREMENTS: + supported = ", ".join(sorted(RAY_SEARCH_ALG_REQUIREMENTS)) + raise ValueError(f"Unsupported Ray Tune search_alg '{search_alg}'. Supported values: {supported}.") + if normalized == "random": + return None, space, normalized + + try: + if normalized == "ax": + if isinstance(search_alg, str): + return _create_ax_search(space, task), {}, normalized + _validate_ax_search_space(space) + return search_alg, {}, normalized + if normalized == "bohb": + if isinstance(search_alg, str): + resolved_search_alg, tuner_param_space = _create_bohb_search(space, task) + else: + _, tuner_param_space = _convert_bohb_search_space(space) + resolved_search_alg = search_alg + return resolved_search_alg, tuner_param_space, normalized + if normalized == "nevergrad": + return _create_nevergrad_search(task), space, normalized + if normalized == "zoopt": + if isinstance(search_alg, str): + resolved_search_alg, tuner_param_space = _create_zoopt_search(space, task, iterations) + else: + _, tuner_param_space = _convert_zoopt_search_space(space) + resolved_search_alg = search_alg + return resolved_search_alg, tuner_param_space, normalized + if not isinstance(search_alg, str): + return search_alg, space, None + + requirements = RAY_SEARCH_ALG_REQUIREMENTS[normalized] + if requirements: + checks.check_requirements(requirements) + + from ray.tune.search import create_searcher + + return create_searcher(normalized, metric=TASK2METRIC[task], mode="max"), space, normalized + except (ImportError, ModuleNotFoundError) as e: + raise ModuleNotFoundError( + f"Ray Tune search_alg '{search_alg}' requires additional dependencies. Original error: {e}" + ) from e + + +def run_ray_tune( + model, + space: dict | None = None, + grace_period: int = 10, + gpu_per_trial: int | None = None, + iterations: int = 10, + search_alg=None, + **train_args, +): + """Run hyperparameter tuning using Ray Tune. + + Args: + model (YOLO): Model to run the tuner on. + space (dict, optional): The hyperparameter search space. If not provided, uses default space. + grace_period (int, optional): The grace period in epochs of the ASHA scheduler. + gpu_per_trial (int, optional): The number of GPUs to allocate per trial. + iterations (int, optional): The maximum number of trials to run. + search_alg (str | ray.tune.search.Searcher | ray.tune.search.SearchAlgorithm, optional): Search algorithm to + use. Strings are resolved to supported Ray Tune searchers. Pre-instantiated objects are reused, and known + searchers with special Tune param_space requirements are normalized automatically. + **train_args (Any): Additional arguments to pass to the `train()` method. + + Returns: + (ray.tune.ResultGrid): A ResultGrid containing the results of the hyperparameter search. + + Examples: + >>> from ultralytics import YOLO + >>> model = YOLO("yolo26n.pt") # Load a YOLO26n model + + Start tuning hyperparameters for YOLO26n training on the COCO8 dataset + >>> result_grid = model.tune(data="coco8.yaml", use_ray=True) + """ + LOGGER.info("💡 Learn about RayTune at https://docs.ultralytics.com/integrations/ray-tune") + try: + checks.check_requirements("ray[tune]", constrain=["pydantic>=2.0,<2.12"]) + + import ray + from ray import tune + from ray.tune import RunConfig + from ray.tune.schedulers import ASHAScheduler, HyperBandForBOHB + except ImportError: + raise ModuleNotFoundError('Ray Tune required but not found. To install run: pip install "ray[tune]"') + + try: + import wandb + + assert hasattr(wandb, "__version__") + except (ImportError, AssertionError): + wandb = False + + checks.check_version(ray.__version__, ">=2.0.0", "ray") + default_space = { + # 'optimizer': tune.choice(['SGD', 'Adam', 'AdamW', 'NAdam', 'RAdam', 'RMSProp']), + "lr0": tune.uniform(1e-5, 1e-2), # initial learning rate (i.e. SGD=1E-2, Adam=1E-3) + "lrf": tune.uniform(0.01, 1.0), # final OneCycleLR learning rate (lr0 * lrf) + "momentum": tune.uniform(0.7, 0.98), # SGD momentum/Adam beta1 + "weight_decay": tune.uniform(0.0, 0.001), # optimizer weight decay + "warmup_epochs": tune.uniform(0.0, 5.0), # warmup epochs (fractions ok) + "warmup_momentum": tune.uniform(0.0, 0.95), # warmup initial momentum + "box": tune.uniform(1.0, 20.0), # box loss gain + "cls": tune.uniform(0.1, 4.0), # cls loss gain (scale with pixels) + "cls_pw": tune.uniform(0.0, 1.0), # cls power weight (scale with pixels) + "dfl": tune.uniform(0.4, 12.0), # dfl loss gain + "hsv_h": tune.uniform(0.0, 0.1), # image HSV-Hue augmentation (fraction) + "hsv_s": tune.uniform(0.0, 0.9), # image HSV-Saturation augmentation (fraction) + "hsv_v": tune.uniform(0.0, 0.9), # image HSV-Value augmentation (fraction) + "degrees": tune.uniform(0.0, 45.0), # image rotation (+/- deg) + "translate": tune.uniform(0.0, 0.9), # image translation (+/- fraction) + "scale": tune.uniform(0.0, 0.95), # image scale (+/- gain) + "shear": tune.uniform(0.0, 10.0), # image shear (+/- deg) + "perspective": tune.uniform(0.0, 0.001), # image perspective (+/- fraction), range 0-0.001 + "flipud": tune.uniform(0.0, 1.0), # image flip up-down (probability) + "fliplr": tune.uniform(0.0, 1.0), # image flip left-right (probability) + "bgr": tune.uniform(0.0, 1.0), # swap RGB↔BGR channels (probability) + "mosaic": tune.uniform(0.0, 1.0), # image mosaic (probability) + "mixup": tune.uniform(0.0, 1.0), # image mixup (probability) + "cutmix": tune.uniform(0.0, 1.0), # image cutmix (probability) + "copy_paste": tune.uniform(0.0, 1.0), # segment copy-paste (probability) + "close_mosaic": tune.randint(0, 11), # close dataloader mosaic (epochs) + } + + # Put the model in ray store + task = model.task + model_in_store = ray.put(model) + base_name = train_args.get("name", "tune") + + def _tune(config): + """Train the YOLO model with the specified hyperparameters and return results.""" + model_to_train = ray.get(model_in_store) # get the model from ray store for tuning + model_to_train.trainer = None + model_to_train.reset_callbacks() + config = _sanitize_tune_value(dict(config)) + config.update(train_args) + + # Set trial-specific name for W&B logging + try: + if hasattr(tune, "get_context"): + trial_id = tune.get_context().get_trial_id() # Ray ≥2.7, get current trial ID (e.g., "tune_c1c1ce99") + else: + trial_id = tune.get_trial_id() # Ray <2.7 + trial_suffix = trial_id.split("_")[-1] if "_" in trial_id else trial_id + config["name"] = f"{base_name}_{trial_suffix}" + except Exception: + # Not in Ray Tune context or error getting trial ID, use base name + config["name"] = base_name + + results = model_to_train.train(**config) + return results.results_dict + + # Get search space + if not space and not train_args.get("resume"): + space = default_space + LOGGER.warning("Search space not provided, using default search space.") + + # Get dataset + data = train_args.get("data", TASK2DATA[task]) + space["data"] = data + if "data" not in train_args: + LOGGER.warning(f'Data not provided, using default "data={data}".') + + resolved_search_alg, tuner_param_space, resolved_search_alg_kind = _resolve_ray_search_alg( + search_alg, task, space, iterations + ) + + # Define the trainable function with allocated resources + trainable_with_resources = tune.with_resources(_tune, {"cpu": NUM_THREADS, "gpu": gpu_per_trial or 0}) + + # Define the scheduler for hyperparameter search + max_t = train_args.get("epochs") or DEFAULT_CFG_DICT["epochs"] or 100 + scheduler = ASHAScheduler( + time_attr="epoch", + metric=TASK2METRIC[task], + mode="max", + max_t=max_t, + grace_period=min(grace_period, max_t), + reduction_factor=3, + ) + if resolved_search_alg_kind == "bohb": + scheduler = HyperBandForBOHB( + time_attr="epoch", + metric=TASK2METRIC[task], + mode="max", + max_t=max_t, + reduction_factor=3, + ) + + # Create the Ray Tune hyperparameter search tuner + tune_dir = get_save_dir( + get_cfg( + DEFAULT_CFG, + {**train_args, **{"exist_ok": train_args.pop("resume", False)}}, # resume w/ same tune_dir + ), + name=train_args.pop("name", "tune"), # runs/{task}/{tune_dir} + ) # must be absolute dir + tune_dir.mkdir(parents=True, exist_ok=True) + if tune.Tuner.can_restore(tune_dir): + LOGGER.info(f"{colorstr('Tuner: ')} Resuming tuning run {tune_dir}...") + tuner = tune.Tuner.restore(str(tune_dir), trainable=trainable_with_resources, resume_errored=True) + else: + tuner = tune.Tuner( + trainable_with_resources, + param_space=tuner_param_space, + tune_config=tune.TuneConfig( + search_alg=resolved_search_alg, + scheduler=scheduler, + num_samples=iterations, + trial_name_creator=lambda trial: f"{trial.trainable_name}_{trial.trial_id}", + trial_dirname_creator=lambda trial: f"{trial.trainable_name}_{trial.trial_id}", + ), + run_config=RunConfig(storage_path=tune_dir.parent, name=tune_dir.name), + ) + + # Run the hyperparameter search + tuner.fit() + + # Get the results of the hyperparameter search + results = tuner.get_results() + + # Shut down Ray to clean up workers + ray.shutdown() + + return results diff --git a/.venv/lib/python3.14/site-packages/ultralytics/utils/uploads.py b/.venv/lib/python3.14/site-packages/ultralytics/utils/uploads.py new file mode 100644 index 0000000000000000000000000000000000000000..95ee82b8da9c6f1f884de2e3a1c532071260cc67 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics/utils/uploads.py @@ -0,0 +1,115 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license +"""Upload utilities for Ultralytics, mirroring downloads.py patterns.""" + +from __future__ import annotations + +import os +from pathlib import Path +from time import sleep + +from ultralytics.utils import LOGGER, TQDM + + +class _ProgressReader: + """File wrapper that reports read progress for upload monitoring.""" + + def __init__(self, file_path, pbar): + self.file = open(file_path, "rb") + self.pbar = pbar + self._size = os.path.getsize(file_path) + + def read(self, size=-1): + """Read data and update progress bar.""" + data = self.file.read(size) + if data and self.pbar: + self.pbar.update(len(data)) + return data + + def __len__(self): + """Return file size for Content-Length header.""" + return self._size + + def close(self): + """Close the file.""" + self.file.close() + + +def safe_upload( + file: str | Path, + url: str, + headers: dict | None = None, + retry: int = 2, + timeout: int = 600, + progress: bool = False, +) -> bool: + """Upload a file to a URL with retry logic and optional progress bar. + + Args: + file (str | Path): Path to the file to upload. + url (str): The URL endpoint to upload the file to (e.g., signed GCS URL). + headers (dict, optional): Additional headers to include in the request. + retry (int, optional): Number of retry attempts on failure (default: 2 for 3 total attempts). + timeout (int, optional): Request timeout in seconds. + progress (bool, optional): Whether to display a progress bar during upload. + + Returns: + (bool): True if upload succeeded, False otherwise. + + Examples: + >>> from ultralytics.utils.uploads import safe_upload + >>> success = safe_upload("model.pt", "https://storage.googleapis.com/...", progress=True) + """ + import requests + + file = Path(file) + if not file.exists(): + raise FileNotFoundError(f"File not found: {file}") + + file_size = file.stat().st_size + desc = f"Uploading {file.name}" + + # Prepare headers (Content-Length set automatically from file size) + upload_headers = {"Content-Type": "application/octet-stream"} + if headers: + upload_headers.update(headers) + + last_error = None + for attempt in range(retry + 1): + pbar = None + reader = None + try: + if progress: + pbar = TQDM(total=file_size, desc=desc, unit="B", unit_scale=True, unit_divisor=1024) + reader = _ProgressReader(file, pbar) + + r = requests.put(url, data=reader, headers=upload_headers, timeout=timeout) + r.raise_for_status() + reader.close() + reader = None # Prevent double-close in finally + if pbar: + pbar.close() + pbar = None + LOGGER.info(f"Uploaded {file.name} ✅") + return True + + except requests.exceptions.HTTPError as e: + status = e.response.status_code if e.response is not None else 0 + if 400 <= status < 500 and status not in {408, 429}: + LOGGER.warning(f"{desc} failed: {status} {getattr(e.response, 'reason', '')}") + return False + last_error = f"HTTP {status}" + except Exception as e: + last_error = str(e) + finally: + if reader: + reader.close() + if pbar: + pbar.close() + + if attempt < retry: + wait_time = 2 ** (attempt + 1) + LOGGER.warning(f"{desc} failed ({last_error}), retrying {attempt + 1}/{retry} in {wait_time}s...") + sleep(wait_time) + + LOGGER.warning(f"{desc} failed after {retry + 1} attempts: {last_error}") + return False diff --git a/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/INSTALLER b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/METADATA b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..42519c1ebcad915587377a5c1468d15d56cad0eb --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/METADATA @@ -0,0 +1,259 @@ +Metadata-Version: 2.4 +Name: ultralytics-thop +Version: 2.0.20 +Summary: Ultralytics THOP package for fast computation of PyTorch model FLOPs and parameters. +Author-email: Ligeng Zhu +Maintainer-email: Ultralytics +License: AGPL-3.0 +Project-URL: Homepage, https://ultralytics.com +Project-URL: Source, https://github.com/ultralytics/thop +Project-URL: Documentation, https://docs.ultralytics.com +Project-URL: Bug Reports, https://github.com/ultralytics/thop/issues +Project-URL: Changelog, https://github.com/ultralytics/thop/releases +Keywords: FLOPs,PyTorch,Model Analysis +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Education +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Software Development +Classifier: Topic :: Scientific/Engineering +Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence +Classifier: Topic :: Scientific/Engineering :: Image Recognition +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: MacOS +Classifier: Operating System :: Microsoft :: Windows +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: numpy +Requires-Dist: torch +Dynamic: license-file + +Ultralytics logo + +# 🚀 THOP: PyTorch-OpCounter + +Welcome to the [THOP](https://github.com/ultralytics/thop) repository, your comprehensive solution for profiling [PyTorch](https://pytorch.org/) models by computing the number of Multiply-Accumulate Operations (MACs) and parameters. Developed by Ultralytics, this tool is essential for [deep learning](https://www.ultralytics.com/glossary/deep-learning-dl) practitioners aiming to evaluate model efficiency and performance, crucial aspects discussed in our [model training tips guide](https://docs.ultralytics.com/guides/model-training-tips/). + +[![Ultralytics Actions](https://github.com/ultralytics/thop/actions/workflows/format.yml/badge.svg)](https://github.com/ultralytics/thop/actions/workflows/format.yml) +[![Ultralytics Discord](https://img.shields.io/discord/1089800235347353640?logo=discord&logoColor=white&label=Discord&color=blue)](https://discord.com/invite/ultralytics) +[![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com/) +[![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://reddit.com/r/ultralytics) + +## 📄 Description + +THOP offers an intuitive API designed to profile PyTorch models by calculating the total number of MACs and parameters. This functionality is vital for assessing the computational efficiency and memory footprint of deep learning models, helping developers optimize performance for deployment, especially on [edge devices](https://www.ultralytics.com/glossary/edge-ai). Understanding these metrics is key to selecting the right model architecture, a topic explored in our [model comparison pages](https://docs.ultralytics.com/compare/). + +## 📦 Installation + +Get started with THOP quickly by installing it via pip: + +[![PyPI - Version](https://img.shields.io/pypi/v/ultralytics-thop?logo=pypi&logoColor=white)](https://pypi.org/project/ultralytics-thop/) [![Downloads](https://static.pepy.tech/badge/ultralytics-thop)](https://clickpy.clickhouse.com/dashboard/ultralytics-thop) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ultralytics-thop?logo=python&logoColor=gold)](https://pypi.org/project/ultralytics-thop/) + +```bash +pip install ultralytics-thop +``` + +Alternatively, for the latest features and updates, install directly from the GitHub repository: + +```bash +pip install --upgrade git+https://github.com/ultralytics/thop.git +``` + +This ensures you have the most recent version, incorporating the latest improvements and bug fixes. + +## 🛠️ How to Use + +### Basic Usage + +Profiling a standard PyTorch model like [ResNet50](https://docs.pytorch.org/vision/main/models/generated/torchvision.models.resnet50.html) is straightforward. Import the necessary libraries, load your model and a sample input tensor, then use the `profile` function: + +```python +import torch +from torchvision.models import resnet50 # Example model + +from thop import profile # Import the profile function from THOP + +# Load a pre-trained model (e.g., ResNet50) +model = resnet50() + +# Create a dummy input tensor matching the model's expected input shape +dummy_input = torch.randn(1, 3, 224, 224) + +# Profile the model +macs, params = profile(model, inputs=(dummy_input,)) + +print(f"MACs: {macs}, Parameters: {params}") +# Expected output: MACs: 4139975680.0, Parameters: 25557032.0 +``` + +### Define Custom Rules for Third-Party Modules + +If your model includes custom or third-party modules not natively supported by THOP, you can define custom profiling rules using the `custom_ops` argument. This allows for accurate profiling even with complex or non-standard architectures, which is useful when working with models like those found in the [Ultralytics models section](https://docs.ultralytics.com/models/). + +```python +import torch +import torch.nn as nn + +from thop import profile + + +# Define your custom module +class YourCustomModule(nn.Module): + def __init__(self): + super().__init__() + # Define layers, e.g., a convolution + self.conv = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1) + + def forward(self, x): + return self.conv(x) + + +# Define a custom counting function for your module +# This function should calculate and return the MACs for the module's operations +def count_your_custom_module(module, x, y): + # Example: Calculate MACs for the conv layer + # Note: This is a simplified example. Real calculations depend on the module's specifics. + # MACs = output_height * output_width * kernel_height * kernel_width * in_channels * out_channels + # For simplicity, we'll just assign a placeholder value or use a helper if available + # In a real scenario, you'd implement the precise MAC calculation here. + # For nn.Conv2d, THOP usually handles it, but this demonstrates the concept. + macs = 0 # Placeholder: Implement actual MAC calculation based on module logic + # You might need access to module properties like kernel_size, stride, padding, channels etc. + # Example for a Conv2d layer (simplified): + if isinstance(module, nn.Conv2d): + _, _, H, W = y.shape # Output shape + k_h, k_w = module.kernel_size + in_c = module.in_channels + out_c = module.out_channels + groups = module.groups + macs = (k_h * k_w * in_c * out_c * H * W) / groups + module.total_ops += torch.DoubleTensor([macs]) # Accumulate MACs + + +# Instantiate a model containing your custom module +model = YourCustomModule() # Or a larger model incorporating this module + +# Create a dummy input +dummy_input = torch.randn(1, 3, 224, 224) + +# Profile the model, providing the custom operation mapping +macs, params = profile(model, inputs=(dummy_input,), custom_ops={YourCustomModule: count_your_custom_module}) + +print(f"Custom MACs: {macs}, Parameters: {params}") +# Expected output: Custom MACs: 87457792.0, Parameters: 1792.0 +``` + +### Improve Output Readability + +For clearer and more interpretable results, use the `thop.clever_format` function. This formats the raw MACs and parameter counts into human-readable strings (e.g., GigaMACs, MegaParams). This formatting helps in quickly understanding the scale of computational resources required, similar to the metrics provided in our [Ultralytics YOLOv8 documentation](https://docs.ultralytics.com/models/yolov8/). + +```python +import torch +from torchvision.models import resnet50 + +from thop import clever_format, profile + +model = resnet50() +dummy_input = torch.randn(1, 3, 224, 224) +macs, params = profile(model, inputs=(dummy_input,)) + +# Format the numbers into a readable format (e.g., 4.14 GMac, 25.56 MParams) +macs_readable, params_readable = clever_format([macs, params], "%.3f") + +print(f"Formatted MACs: {macs_readable}, Formatted Parameters: {params_readable}") +# Expected output: Formatted MACs: 4.140G, Formatted Parameters: 25.557M +``` + +## 📊 Results of Recent Models + +The table below showcases the parameters and MACs for several popular [computer vision](https://www.ultralytics.com/glossary/computer-vision-cv) models, profiled using THOP. These benchmarks provide a comparative overview of model complexity and computational cost. You can reproduce these results by running the script located at `benchmark/evaluate_famous_models.py` in this repository. Comparing these metrics is essential for tasks like selecting models for [object detection](https://www.ultralytics.com/glossary/object-detection) or [image classification](https://www.ultralytics.com/glossary/image-classification). For more comparisons, see our [model comparison section](https://docs.ultralytics.com/compare/). + + + + + + +
+ +| Model | Params(M) | MACs(G) | +| ---------------- | --------- | ------- | +| alexnet | 61.10 | 0.77 | +| vgg11 | 132.86 | 7.74 | +| vgg11_bn | 132.87 | 7.77 | +| vgg13 | 133.05 | 11.44 | +| vgg13_bn | 133.05 | 11.49 | +| vgg16 | 138.36 | 15.61 | +| vgg16_bn | 138.37 | 15.66 | +| vgg19 | 143.67 | 19.77 | +| vgg19_bn | 143.68 | 19.83 | +| resnet18 | 11.69 | 1.82 | +| resnet34 | 21.80 | 3.68 | +| resnet50 | 25.56 | 4.14 | +| resnet101 | 44.55 | 7.87 | +| resnet152 | 60.19 | 11.61 | +| wide_resnet101_2 | 126.89 | 22.84 | +| wide_resnet50_2 | 68.88 | 11.46 | + + + +| Model | Params(M) | MACs(G) | +| ------------------ | --------- | ------- | +| resnext50_32x4d | 25.03 | 4.29 | +| resnext101_32x8d | 88.79 | 16.54 | +| densenet121 | 7.98 | 2.90 | +| densenet161 | 28.68 | 7.85 | +| densenet169 | 14.15 | 3.44 | +| densenet201 | 20.01 | 4.39 | +| squeezenet1_0 | 1.25 | 0.82 | +| squeezenet1_1 | 1.24 | 0.35 | +| mnasnet0_5 | 2.22 | 0.14 | +| mnasnet0_75 | 3.17 | 0.24 | +| mnasnet1_0 | 4.38 | 0.34 | +| mnasnet1_3 | 6.28 | 0.53 | +| mobilenet_v2 | 3.50 | 0.33 | +| shufflenet_v2_x0_5 | 1.37 | 0.05 | +| shufflenet_v2_x1_0 | 2.28 | 0.15 | +| shufflenet_v2_x1_5 | 3.50 | 0.31 | +| shufflenet_v2_x2_0 | 7.39 | 0.60 | +| inception_v3 | 27.16 | 5.75 | + +
+ +## 🙌 Contribute + +We actively welcome and encourage community contributions to make THOP even better! Whether it's adding support for new [PyTorch layers](https://docs.pytorch.org/docs/stable/nn.html), improving existing calculations, enhancing documentation, or fixing bugs, your input is valuable. Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing/) for detailed instructions on how to participate. Together, we can ensure THOP remains a state-of-the-art tool for the [machine learning](https://www.ultralytics.com/glossary/machine-learning-ml) community. Don't hesitate to share your feedback and suggestions! + +## 📜 License + +THOP is distributed under the [AGPL-3.0 License](https://www.gnu.org/licenses/agpl-3.0.en.html). This license promotes open collaboration and sharing of improvements. For complete details, please refer to the [LICENSE](https://github.com/ultralytics/thop/blob/main/LICENSE) file included in the repository. Understanding the license is important before integrating THOP into your projects, especially for commercial applications which may require an [Enterprise License](https://www.ultralytics.com/license). + +## 📧 Contact + +Encountered a bug or have a feature request? Please submit an issue through our [GitHub Issues](https://github.com/ultralytics/thop/issues) page. For general discussions, questions, and community support, join the vibrant Ultralytics community on our [Discord server](https://discord.com/invite/ultralytics). We look forward to hearing from you and collaborating! + +
+
+ Ultralytics GitHub + space + Ultralytics LinkedIn + space + Ultralytics Twitter + space + Ultralytics YouTube + space + Ultralytics TikTok + space + Ultralytics BiliBili + space + Ultralytics Discord +
diff --git a/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/RECORD b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..40e695f0ebff0a109bafc85ecde003a001f93cca --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/RECORD @@ -0,0 +1,22 @@ +thop/__init__.py,sha256=RoqynNLPcoz96dWzlglHw9H3B3tERqol9g_lxR5_pC0,293 +thop/__pycache__/__init__.cpython-314.pyc,, +thop/__pycache__/fx_profile.cpython-314.pyc,, +thop/__pycache__/profile.cpython-314.pyc,, +thop/__pycache__/rnn_hooks.cpython-314.pyc,, +thop/__pycache__/utils.cpython-314.pyc,, +thop/fx_profile.py,sha256=iB8zmoj-dQFKJTO66EOye7DSqY6Ens9rlmbo1D4O5YA,8407 +thop/profile.py,sha256=QCqJfGPe5ivwJVBilJhJdEpCuwXQ3JG1iE9A4VJ6_Pk,8379 +thop/rnn_hooks.py,sha256=JKZ2eSCvIKvhvCDqM4oWPZjmBkdyJ4R2Q7XSn63lsX0,6503 +thop/utils.py,sha256=IwFJQ1v-SLyhm-313Li535R6fhtomkm8Fem1Kfe6G_U,1484 +thop/vision/__init__.py,sha256=lm6MckFYCPTbqIoX7w0s_daxdjNeBeKW6DXppv1-QUM,70 +thop/vision/__pycache__/__init__.cpython-314.pyc,, +thop/vision/__pycache__/basic_hooks.cpython-314.pyc,, +thop/vision/__pycache__/calc_func.cpython-314.pyc,, +thop/vision/basic_hooks.py,sha256=mXDIZL3O2UirrZWtZSOVit19OTHTtWPLdeBtV8zXZYA,5479 +thop/vision/calc_func.py,sha256=2-LcoFg8ODy8EMb-xEsgLVPigLTki3dXnAg58dxVq80,4530 +ultralytics_thop-2.0.20.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +ultralytics_thop-2.0.20.dist-info/METADATA,sha256=TTbCMveWZu-qZ7Iw2ig--uSihIxcMWQdiStl2vzpvOI,14997 +ultralytics_thop-2.0.20.dist-info/RECORD,, +ultralytics_thop-2.0.20.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91 +ultralytics_thop-2.0.20.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523 +ultralytics_thop-2.0.20.dist-info/top_level.txt,sha256=HQ7D0gSvDJ31CNR-f0EuXNVve05RYBmwyIkHQKiEhU8,5 diff --git a/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/WHEEL b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..14a883f292bc96b20c2b76a3081991f2676523a9 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/licenses/LICENSE b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..0ad25db4bd1d86c452db3f9602ccdbe172438f52 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/licenses/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/top_level.txt b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..656868771c58fe743a290d2a6b10765d643b8592 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/ultralytics_thop-2.0.20.dist-info/top_level.txt @@ -0,0 +1 @@ +thop diff --git a/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/INSTALLER b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/METADATA b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..9c7a4703f89e94e7c3391dc4e71ed9d1931e8936 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/METADATA @@ -0,0 +1,163 @@ +Metadata-Version: 2.4 +Name: urllib3 +Version: 2.7.0 +Summary: HTTP library with thread-safe connection pooling, file post, and more. +Project-URL: Changelog, https://github.com/urllib3/urllib3/blob/main/CHANGES.rst +Project-URL: Documentation, https://urllib3.readthedocs.io +Project-URL: Code, https://github.com/urllib3/urllib3 +Project-URL: Issue tracker, https://github.com/urllib3/urllib3/issues +Author-email: Andrey Petrov +Maintainer-email: Seth Michael Larson , Quentin Pradet , Illia Volochii +License-Expression: MIT +License-File: LICENSE.txt +Keywords: filepost,http,httplib,https,pooling,ssl,threadsafe,urllib +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Libraries +Requires-Python: >=3.10 +Provides-Extra: brotli +Requires-Dist: brotli>=1.2.0; (platform_python_implementation == 'CPython') and extra == 'brotli' +Requires-Dist: brotlicffi>=1.2.0.0; (platform_python_implementation != 'CPython') and extra == 'brotli' +Provides-Extra: h2 +Requires-Dist: h2<5,>=4; extra == 'h2' +Provides-Extra: socks +Requires-Dist: pysocks!=1.5.7,<2.0,>=1.5.6; extra == 'socks' +Provides-Extra: zstd +Requires-Dist: backports-zstd>=1.0.0; (python_version < '3.14') and extra == 'zstd' +Description-Content-Type: text/markdown + +

+ +![urllib3](https://github.com/urllib3/urllib3/raw/main/docs/_static/banner_github.svg) + +

+ +

+ PyPI Version + Python Versions + Join our Discord + Coverage Status + Build Status on GitHub + Documentation Status
+ OpenSSF Scorecard + SLSA 3 + CII Best Practices +

+ +urllib3 is a powerful, *user-friendly* HTTP client for Python. +urllib3 brings many critical features that are missing from the Python +standard libraries: + +- Thread safety. +- Connection pooling. +- Client-side SSL/TLS verification. +- File uploads with multipart encoding. +- Helpers for retrying requests and dealing with HTTP redirects. +- Support for gzip, deflate, brotli, and zstd encoding. +- Proxy support for HTTP and SOCKS. +- 100% test coverage. + +... and many more features, but most importantly: Our maintainers have a 15+ +year track record of maintaining urllib3 with the highest code standards and +attention to security and safety. + +[Much of the Python ecosystem already uses urllib3](https://urllib3.readthedocs.io/en/stable/#who-uses) +and you should too. + + +## Installing + +urllib3 can be installed with [pip](https://pip.pypa.io): + +```bash +$ python -m pip install urllib3 +``` + +Alternatively, you can grab the latest source code from [GitHub](https://github.com/urllib3/urllib3): + +```bash +$ git clone https://github.com/urllib3/urllib3.git +$ cd urllib3 +$ pip install . +``` + +## Getting Started + +urllib3 is easy to use: + +```python3 +>>> import urllib3 +>>> resp = urllib3.request("GET", "http://httpbin.org/robots.txt") +>>> resp.status +200 +>>> resp.data +b"User-agent: *\nDisallow: /deny\n" +``` + +urllib3 has usage and reference documentation at [urllib3.readthedocs.io](https://urllib3.readthedocs.io). + + +## Community + +urllib3 has a [community Discord channel](https://discord.gg/urllib3) for asking questions and +collaborating with other contributors. Drop by and say hello 👋 + + +## Contributing + +urllib3 happily accepts contributions. Please see our +[contributing documentation](https://urllib3.readthedocs.io/en/latest/contributing.html) +for some tips on getting started. + + +## Security Disclosures + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure with maintainers. + + +## Maintainers + +Meet our maintainers since 2008: + +- Current Lead: [@illia-v](https://github.com/illia-v) (Illia Volochii) +- [@sethmlarson](https://github.com/sethmlarson) (Seth M. Larson) +- [@pquentin](https://github.com/pquentin) (Quentin Pradet) +- [@theacodes](https://github.com/theacodes) (Thea Flowers) +- [@haikuginger](https://github.com/haikuginger) (Jess Shapiro) +- [@lukasa](https://github.com/lukasa) (Cory Benfield) +- [@sigmavirus24](https://github.com/sigmavirus24) (Ian Stapleton Cordasco) +- [@shazow](https://github.com/shazow) (Andrey Petrov) + +👋 + + +## Sponsorship + +If your company benefits from this library, please consider [sponsoring its +development](https://urllib3.readthedocs.io/en/latest/sponsors.html). + + +## For Enterprise + +Professional support for urllib3 is available as part of the [Tidelift +Subscription][1]. Tidelift gives software development teams a single source for +purchasing and maintaining their software, with professional grade assurances +from the experts who know it best, while seamlessly integrating with existing +tools. + +[1]: https://tidelift.com/subscription/pkg/pypi-urllib3?utm_source=pypi-urllib3&utm_medium=referral&utm_campaign=readme diff --git a/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/RECORD b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..d59be14366abf88b23da670140ad505348d40364 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/RECORD @@ -0,0 +1,79 @@ +urllib3-2.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +urllib3-2.7.0.dist-info/METADATA,sha256=ZhR4VtMLu2vtAcbOMybQ9I2E7V8oasF3YzoUhrgurOU,6852 +urllib3-2.7.0.dist-info/RECORD,, +urllib3-2.7.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87 +urllib3-2.7.0.dist-info/licenses/LICENSE.txt,sha256=Ew46ZNX91dCWp1JpRjSn2d8oRGnehuVzIQAmgEHj1oY,1093 +urllib3/__init__.py,sha256=JMo1tg1nIV1AeJ2vENC_Txfl0e5h6Gzl9DGVk1rWRbo,6979 +urllib3/__pycache__/__init__.cpython-314.pyc,, +urllib3/__pycache__/_base_connection.cpython-314.pyc,, +urllib3/__pycache__/_collections.cpython-314.pyc,, +urllib3/__pycache__/_request_methods.cpython-314.pyc,, +urllib3/__pycache__/_version.cpython-314.pyc,, +urllib3/__pycache__/connection.cpython-314.pyc,, +urllib3/__pycache__/connectionpool.cpython-314.pyc,, +urllib3/__pycache__/exceptions.cpython-314.pyc,, +urllib3/__pycache__/fields.cpython-314.pyc,, +urllib3/__pycache__/filepost.cpython-314.pyc,, +urllib3/__pycache__/poolmanager.cpython-314.pyc,, +urllib3/__pycache__/response.cpython-314.pyc,, +urllib3/_base_connection.py,sha256=HzcSEHexgDrRUr60jNniB2KQjdw97SedD5luRfHtCXg,5580 +urllib3/_collections.py,sha256=aOVm2mKilvuvT1efAGtkA7pi65C1NC3HJfpFxvYBS8A,17522 +urllib3/_request_methods.py,sha256=gCeF85SO_UU4WoPwYHIoz_tw-eM_EVOkLFp8OFsC7DA,9931 +urllib3/_version.py,sha256=-gMrKhsPiOj0XzUezVFa59d1S6QgQiKgQT5gGgyM8-w,520 +urllib3/connection.py,sha256=Zos3qxKDW9-GQ6aVqfBQVRM5soB0IjHxY_xXWpJaFTI,42786 +urllib3/connectionpool.py,sha256=sGFnddXYwlx7KC4JCP1gKvdNGLNK-YTCJDdGACGj3Y8,44164 +urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +urllib3/contrib/__pycache__/__init__.cpython-314.pyc,, +urllib3/contrib/__pycache__/pyopenssl.cpython-314.pyc,, +urllib3/contrib/__pycache__/socks.cpython-314.pyc,, +urllib3/contrib/emscripten/__init__.py,sha256=wyXve8rmqX7s2KqRQBxD5Wl48jzWPn5-1u_XoQBELVc,836 +urllib3/contrib/emscripten/__pycache__/__init__.cpython-314.pyc,, +urllib3/contrib/emscripten/__pycache__/connection.cpython-314.pyc,, +urllib3/contrib/emscripten/__pycache__/fetch.cpython-314.pyc,, +urllib3/contrib/emscripten/__pycache__/request.cpython-314.pyc,, +urllib3/contrib/emscripten/__pycache__/response.cpython-314.pyc,, +urllib3/contrib/emscripten/connection.py,sha256=giElsBoUsKVURbZzb8GCrJmqW23Xnvj2aNyQVF42slg,8960 +urllib3/contrib/emscripten/emscripten_fetch_worker.js,sha256=z1k3zZ4_hDKd3-tN7wzz8LHjHC2pxN_uu8B3k9D9A3c,3677 +urllib3/contrib/emscripten/fetch.py,sha256=5xcd--viFxZd2nBy0aK73dtJ9Tsh1yYZU_SUXwnwibk,23520 +urllib3/contrib/emscripten/request.py,sha256=mL28szy1KvE3NJhWor5jNmarp8gwplDU-7gwGZY5g0Q,566 +urllib3/contrib/emscripten/response.py,sha256=CDpY0GFoluR3IxECkM2gH5uDfYDM0EL1FWr7B76wtgM,9719 +urllib3/contrib/pyopenssl.py,sha256=XZY-QzyT7s8d43qisA4o-eSZYBeIkPyBMZhtj2kkLZs,19734 +urllib3/contrib/socks.py,sha256=rSuklfha4-vto-8qSLhSPmf5lmRPIYuqdAxKe9bg2RA,7639 +urllib3/exceptions.py,sha256=hQPHoqo4yw46esNSVkciVQXVUgAxWAglsh6MbyQta-Q,9945 +urllib3/fields.py,sha256=aGLFAVZpVU-FbJlllve4Ahg0-pH9ZZBQ2Iz7lfpkjkM,10801 +urllib3/filepost.py,sha256=U8eNZ-mpKKHhrlbHEEiTxxgK16IejhEa7uz42yqA_dI,2388 +urllib3/http2/__init__.py,sha256=xzrASH7R5ANRkPJOot5lGnATOq3KKuyXzI42rcnwmqs,1741 +urllib3/http2/__pycache__/__init__.cpython-314.pyc,, +urllib3/http2/__pycache__/connection.cpython-314.pyc,, +urllib3/http2/__pycache__/probe.cpython-314.pyc,, +urllib3/http2/connection.py,sha256=bHMH6fNvatwXPrKqrcn74yA3pUWcqPDppnK1LcKCbP8,12578 +urllib3/http2/probe.py,sha256=nnAkqbhAakOiF75rz7W0udZ38Eeh_uD8fjV74N73FEI,3014 +urllib3/poolmanager.py,sha256=c0rh0rcUC1t5tDGuUeGIgAzlErasPOwWwLiB67lX8pM,23895 +urllib3/py.typed,sha256=UaCuPFa3H8UAakbt-5G8SPacldTOGvJv18pPjUJ5gDY,93 +urllib3/response.py,sha256=9SX4BkkdoLgsx1ne6hpt-5PncrnDzPzeqC1DaShSc-M,53219 +urllib3/util/__init__.py,sha256=-qeS0QceivazvBEKDNFCAI-6ACcdDOE4TMvo7SLNlAQ,1001 +urllib3/util/__pycache__/__init__.cpython-314.pyc,, +urllib3/util/__pycache__/connection.cpython-314.pyc,, +urllib3/util/__pycache__/proxy.cpython-314.pyc,, +urllib3/util/__pycache__/request.cpython-314.pyc,, +urllib3/util/__pycache__/response.cpython-314.pyc,, +urllib3/util/__pycache__/retry.cpython-314.pyc,, +urllib3/util/__pycache__/ssl_.cpython-314.pyc,, +urllib3/util/__pycache__/ssl_match_hostname.cpython-314.pyc,, +urllib3/util/__pycache__/ssltransport.cpython-314.pyc,, +urllib3/util/__pycache__/timeout.cpython-314.pyc,, +urllib3/util/__pycache__/url.cpython-314.pyc,, +urllib3/util/__pycache__/util.cpython-314.pyc,, +urllib3/util/__pycache__/wait.cpython-314.pyc,, +urllib3/util/connection.py,sha256=JjO722lzHlzLXPTkr9ZWBdhseXnMVjMSb1DJLVrXSnQ,4444 +urllib3/util/proxy.py,sha256=seP8-Q5B6bB0dMtwPj-YcZZQ30vHuLqRu-tI0JZ2fzs,1148 +urllib3/util/request.py,sha256=itpnC8ug7D4nVfDmGUCRMlgkARUQ13r_XMxSnzTwmpE,8363 +urllib3/util/response.py,sha256=vQE639uoEhj1vpjEdxu5lNIhJCSUZkd7pqllUI0BZOA,3374 +urllib3/util/retry.py,sha256=2YnSX-_FecMShD61Mx5s68J0_btUHZrrc_BkFVRS1P4,19577 +urllib3/util/ssl_.py,sha256=Oqe3rIhUU3e3GVgZob2hxmR8Q0ZDhrhESPlPP_GLaVQ,17742 +urllib3/util/ssl_match_hostname.py,sha256=Ft44KJzTzGMmKff_ZXP91li2V4WhmvEy16PKBcv4vZk,5479 +urllib3/util/ssltransport.py,sha256=Ez4O8pR_vT8dan_FvqBYS6dgDfBXEMfVfrzcdUoWfi4,8847 +urllib3/util/timeout.py,sha256=4eT1FVeZZU7h7mYD1Jq2OXNe4fxekdNvhoWUkZusRpA,10346 +urllib3/util/url.py,sha256=WRh-TMYXosmgp8m8lT4H5spoHw5yUjlcMCfU53AkoAs,15205 +urllib3/util/util.py,sha256=j3lbZK1jPyiwD34T8IgJzdWEZVT-4E-0vYIJi9UjeNA,1146 +urllib3/util/wait.py,sha256=_ph8IrUR3sqPqi0OopQgJUlH4wzkGeM5CiyA7XGGtmI,4423 diff --git a/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/WHEEL b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..b1b94fd58e7e9ed0ef3449473bc48de68afcc3fe --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.29.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/licenses/LICENSE.txt b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..e6183d0276b26c5b87aecccf8d0d5bcd7b1148d4 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3-2.7.0.dist-info/licenses/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2008-2020 Andrey Petrov and contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.venv/lib/python3.14/site-packages/urllib3/__init__.py b/.venv/lib/python3.14/site-packages/urllib3/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3fe782c8a45bbabcf240f3cac4303ac12b0ec274 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/__init__.py @@ -0,0 +1,211 @@ +""" +Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more +""" + +from __future__ import annotations + +# Set default logging handler to avoid "No handler found" warnings. +import logging +import sys +import typing +import warnings +from logging import NullHandler + +from . import exceptions +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from ._version import __version__ +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url +from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import BaseHTTPResponse, HTTPResponse +from .util.request import make_headers +from .util.retry import Retry +from .util.timeout import Timeout + +# Ensure that Python is compiled with OpenSSL 1.1.1+ +# If the 'ssl' module isn't available at all that's +# fine, we only care if the module is available. +try: + import ssl +except ImportError: + pass +else: + if not ssl.OPENSSL_VERSION.startswith("OpenSSL "): # Defensive: + warnings.warn( + "urllib3 v2 only supports OpenSSL 1.1.1+, currently " + f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " + "See: https://github.com/urllib3/urllib3/issues/3020", + exceptions.NotOpenSSLWarning, + ) + elif ssl.OPENSSL_VERSION_INFO < (1, 1, 1): # Defensive: + raise ImportError( + "urllib3 v2 only supports OpenSSL 1.1.1+, currently " + f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " + "See: https://github.com/urllib3/urllib3/issues/2168" + ) + +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = __version__ + +__all__ = ( + "HTTPConnectionPool", + "HTTPHeaderDict", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "make_headers", + "proxy_from_url", + "request", + "BaseHTTPResponse", +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger( + level: int = logging.DEBUG, +) -> logging.StreamHandler[typing.TextIO]: + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug("Added a stderr logging handler to logger: %s", __name__) + return handler + + +# ... Clean up. +del NullHandler + + +# All warning filters *must* be appended unless you're really certain that they +# shouldn't be: otherwise, it's very hard for users to use most Python +# mechanisms to silence them. +# SecurityWarning's always go off by default. +warnings.simplefilter("always", exceptions.SecurityWarning, append=True) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) + + +def disable_warnings(category: type[Warning] = exceptions.HTTPWarning) -> None: + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter("ignore", category) + + +_DEFAULT_POOL = PoolManager() + + +def request( + method: str, + url: str, + *, + body: _TYPE_BODY | None = None, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + preload_content: bool | None = True, + decode_content: bool | None = True, + redirect: bool | None = True, + retries: Retry | bool | int | None = None, + timeout: Timeout | float | int | None = 3, + json: typing.Any | None = None, +) -> BaseHTTPResponse: + """ + A convenience, top-level request method. It uses a module-global ``PoolManager`` instance. + Therefore, its side effects could be shared across dependencies relying on it. + To avoid side effects create a new ``PoolManager`` instance and use it instead. + The method does not accept low-level ``**urlopen_kw`` keyword arguments. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param fields: + Data to encode and send in the request body. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. + + :param bool preload_content: + If True, the response's body will be preloaded into memory. + + :param bool decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param json: + Data to encode and send as JSON with UTF-encoded in the request body. + The ``"Content-Type"`` header will be set to ``"application/json"`` + unless specified otherwise. + """ + + return _DEFAULT_POOL.request( + method, + url, + body=body, + fields=fields, + headers=headers, + preload_content=preload_content, + decode_content=decode_content, + redirect=redirect, + retries=retries, + timeout=timeout, + json=json, + ) + + +if sys.platform == "emscripten": + from .contrib.emscripten import inject_into_urllib3 # noqa: 401 + + inject_into_urllib3() diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9998f9738355d551a0507cbce01fd4262e111011 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_base_connection.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_base_connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34f33bd9f8796a6bfa05fd66f98ba15d90c3e326 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_base_connection.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_collections.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_collections.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fd2732254e3cc89d17daf57daacf8de4bfd45c2 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_collections.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_request_methods.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_request_methods.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35141e34c7fb439f0345cc2c6a3bd4a6b1c43424 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_request_methods.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_version.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_version.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..276930efdb4670087a8d04f87c39cf4fad519b82 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_version.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connection.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61e4829421068551c27e5556a63854f6504caf96 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connection.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connectionpool.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connectionpool.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac94883bf2ee261928e82918e4eb92115475a521 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connectionpool.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/exceptions.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/exceptions.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b710fd351e224e564d8cddffadb9157d761d299 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/exceptions.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/fields.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/fields.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05e9cb9cf235ac38e862516a9bef12ee7412c906 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/fields.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/filepost.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/filepost.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a93710a4a64669b48536ec553ab4fb9bcd31901c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/filepost.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/poolmanager.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/poolmanager.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8045f73082f3e50a3644fd5fff188fb1475be833 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/poolmanager.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/__pycache__/response.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/response.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aada3dac097c40df7e7550f155fb105fcb6e33f0 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/__pycache__/response.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/_base_connection.py b/.venv/lib/python3.14/site-packages/urllib3/_base_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..992ec1657a9d0a98354b56b2a55d54b75db0eaa3 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/_base_connection.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import typing + +from .util.connection import _TYPE_SOCKET_OPTIONS +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT +from .util.url import Url + +_TYPE_BODY = typing.Union[ + bytes, typing.IO[typing.Any], typing.Iterable[bytes | str], str +] + + +class ProxyConfig(typing.NamedTuple): + ssl_context: ssl.SSLContext | None + use_forwarding_for_https: bool + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None + + +class _ResponseOptions(typing.NamedTuple): + # TODO: Remove this in favor of a better + # HTTP request/response lifecycle tracking. + request_method: str + request_url: str + preload_content: bool + decode_content: bool + enforce_content_length: bool + + +if typing.TYPE_CHECKING: + import ssl + from typing import Protocol + + from .response import BaseHTTPResponse + + class BaseHTTPConnection(Protocol): + default_port: typing.ClassVar[int] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + host: str + port: int + timeout: None | ( + float + ) # Instance doesn't store _DEFAULT_TIMEOUT, must be resolved. + blocksize: int + source_address: tuple[str, int] | None + socket_options: _TYPE_SOCKET_OPTIONS | None + + proxy: Url | None + proxy_config: ProxyConfig | None + + is_verified: bool + proxy_is_verified: bool | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + socket_options: _TYPE_SOCKET_OPTIONS | None = ..., + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: ... + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: ... + + def connect(self) -> None: ... + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + # We know *at least* botocore is depending on the order of the + # first 3 parameters so to be safe we only mark the later ones + # as keyword-only to ensure we have space to extend. + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: ... + + def getresponse(self) -> BaseHTTPResponse: ... + + def close(self) -> None: ... + + @property + def is_closed(self) -> bool: + """Whether the connection either is brand new or has been previously closed. + If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` + properties must be False. + """ + + @property + def is_connected(self) -> bool: + """Whether the connection is actively connected to any origin (proxy or target)""" + + @property + def has_connected_to_proxy(self) -> bool: + """Whether the connection has successfully connected to its proxy. + This returns False if no proxy is in use. Used to determine whether + errors are coming from the proxy layer or from tunnelling to the target origin. + """ + + class BaseHTTPSConnection(BaseHTTPConnection, Protocol): + default_port: typing.ClassVar[int] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + # Certificate verification methods + cert_reqs: int | str | None + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None + ssl_context: ssl.SSLContext | None + + # Trusted CAs + ca_certs: str | None + ca_cert_dir: str | None + ca_cert_data: None | str | bytes + + # TLS version + ssl_minimum_version: int | None + ssl_maximum_version: int | None + ssl_version: int | str | None # Deprecated + + # Client certificates + cert_file: str | None + key_file: str | None + key_password: str | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: _TYPE_SOCKET_OPTIONS | None = ..., + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: ... diff --git a/.venv/lib/python3.14/site-packages/urllib3/_collections.py b/.venv/lib/python3.14/site-packages/urllib3/_collections.py new file mode 100644 index 0000000000000000000000000000000000000000..ee9ca662b625ce6b0a4743d05a186301b9a30ee6 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/_collections.py @@ -0,0 +1,486 @@ +from __future__ import annotations + +import typing +from collections import OrderedDict +from enum import Enum, auto +from threading import RLock + +if typing.TYPE_CHECKING: + # We can only import Protocol if TYPE_CHECKING because it's a development + # dependency, and is not available at runtime. + from typing import Protocol + + from typing_extensions import Self + + class HasGettableStringKeys(Protocol): + def keys(self) -> typing.Iterator[str]: ... + + def __getitem__(self, key: str) -> str: ... + + +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] + + +# Key type +_KT = typing.TypeVar("_KT") +# Value type +_VT = typing.TypeVar("_VT") +# Default type +_DT = typing.TypeVar("_DT") + +ValidHTTPHeaderSource = typing.Union[ + "HTTPHeaderDict", + typing.Mapping[str, str], + typing.Iterable[tuple[str, str]], + "HasGettableStringKeys", +] + + +class _Sentinel(Enum): + not_passed = auto() + + +def ensure_can_construct_http_header_dict( + potential: object, +) -> ValidHTTPHeaderSource | None: + if isinstance(potential, HTTPHeaderDict): + return potential + elif isinstance(potential, typing.Mapping): + # Full runtime checking of the contents of a Mapping is expensive, so for the + # purposes of typechecking, we assume that any Mapping is the right shape. + return typing.cast(typing.Mapping[str, str], potential) + elif isinstance(potential, typing.Iterable): + # Similarly to Mapping, full runtime checking of the contents of an Iterable is + # expensive, so for the purposes of typechecking, we assume that any Iterable + # is the right shape. + return typing.cast(typing.Iterable[tuple[str, str]], potential) + elif hasattr(potential, "keys") and hasattr(potential, "__getitem__"): + return typing.cast("HasGettableStringKeys", potential) + else: + return None + + +class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT, _VT]): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + _container: typing.OrderedDict[_KT, _VT] + _maxsize: int + dispose_func: typing.Callable[[_VT], None] | None + lock: RLock + + def __init__( + self, + maxsize: int = 10, + dispose_func: typing.Callable[[_VT], None] | None = None, + ) -> None: + super().__init__() + self._maxsize = maxsize + self.dispose_func = dispose_func + self._container = OrderedDict() + self.lock = RLock() + + def __getitem__(self, key: _KT) -> _VT: + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key: _KT, value: _VT) -> None: + evicted_item = None + with self.lock: + # Possibly evict the existing value of 'key' + try: + # If the key exists, we'll overwrite it, which won't change the + # size of the pool. Because accessing a key should move it to + # the end of the eviction line, we pop it out first. + evicted_item = key, self._container.pop(key) + self._container[key] = value + except KeyError: + # When the key does not exist, we insert the value first so that + # evicting works in all cases, including when self._maxsize is 0 + self._container[key] = value + if len(self._container) > self._maxsize: + # If we didn't evict an existing value, and we've hit our maximum + # size, then we have to evict the least recently used item from + # the beginning of the container. + evicted_item = self._container.popitem(last=False) + + # After releasing the lock on the pool, dispose of any evicted value. + if evicted_item is not None and self.dispose_func: + _, evicted_value = evicted_item + self.dispose_func(evicted_value) + + def __delitem__(self, key: _KT) -> None: + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self) -> int: + with self.lock: + return len(self._container) + + def __iter__(self) -> typing.NoReturn: + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) + + def clear(self) -> None: + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(self._container.values()) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self) -> set[_KT]: # type: ignore[override] + with self.lock: + return set(self._container.keys()) + + +class HTTPHeaderDictItemView(set[tuple[str, str]]): + """ + HTTPHeaderDict is unusual for a Mapping[str, str] in that it has two modes of + address. + + If we directly try to get an item with a particular name, we will get a string + back that is the concatenated version of all the values: + + >>> d['X-Header-Name'] + 'Value1, Value2, Value3' + + However, if we iterate over an HTTPHeaderDict's items, we will optionally combine + these values based on whether combine=True was called when building up the dictionary + + >>> d = HTTPHeaderDict({"A": "1", "B": "foo"}) + >>> d.add("A", "2", combine=True) + >>> d.add("B", "bar") + >>> list(d.items()) + [ + ('A', '1, 2'), + ('B', 'foo'), + ('B', 'bar'), + ] + + This class conforms to the interface required by the MutableMapping ABC while + also giving us the nonstandard iteration behavior we want; items with duplicate + keys, ordered by time of first insertion. + """ + + _headers: HTTPHeaderDict + + def __init__(self, headers: HTTPHeaderDict) -> None: + self._headers = headers + + def __len__(self) -> int: + return len(list(self._headers.iteritems())) + + def __iter__(self) -> typing.Iterator[tuple[str, str]]: + return self._headers.iteritems() + + def __contains__(self, item: object) -> bool: + if isinstance(item, tuple) and len(item) == 2: + passed_key, passed_val = item + if isinstance(passed_key, str) and isinstance(passed_val, str): + return self._headers._has_value_for_header(passed_key, passed_val) + return False + + +class HTTPHeaderDict(typing.MutableMapping[str, str]): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + _container: typing.MutableMapping[str, list[str]] + + def __init__(self, headers: ValidHTTPHeaderSource | None = None, **kwargs: str): + super().__init__() + self._container = {} # 'dict' is insert-ordered + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key: str, val: str) -> None: + # avoid a bytes/str comparison by decoding before httplib + if isinstance(key, bytes): + key = key.decode("latin-1") + self._container[key.lower()] = [key, val] + + def __getitem__(self, key: str) -> str: + if isinstance(key, bytes): + key = key.decode("latin-1") + val = self._container[key.lower()] + return ", ".join(val[1:]) + + def __delitem__(self, key: str) -> None: + if isinstance(key, bytes): + key = key.decode("latin-1") + del self._container[key.lower()] + + def __contains__(self, key: object) -> bool: + if isinstance(key, bytes): + key = key.decode("latin-1") + if isinstance(key, str): + return key.lower() in self._container + return False + + def setdefault(self, key: str, default: str = "") -> str: + return super().setdefault(key, default) + + def __eq__(self, other: object) -> bool: + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return False + else: + other_as_http_header_dict = type(self)(maybe_constructable) + + return {k.lower(): v for k, v in self.itermerged()} == { + k.lower(): v for k, v in other_as_http_header_dict.itermerged() + } + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def __len__(self) -> int: + return len(self._container) + + def __iter__(self) -> typing.Iterator[str]: + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def discard(self, key: str) -> None: + try: + del self[key] + except KeyError: + pass + + def add(self, key: str, val: str, *, combine: bool = False) -> None: + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + If this is called with combine=True, instead of adding a new header value + as a distinct item during iteration, this will instead append the value to + any existing header value with a comma. If no existing header value exists + for the key, then the value will simply be added, ignoring the combine parameter. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + >>> list(headers.items()) + [('foo', 'bar'), ('foo', 'baz')] + >>> headers.add('foo', 'quz', combine=True) + >>> list(headers.items()) + [('foo', 'bar, baz, quz')] + """ + # avoid a bytes/str comparison by decoding before httplib + if isinstance(key, bytes): + key = key.decode("latin-1") + key_lower = key.lower() + new_vals = [key, val] + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + # if there are values here, then there is at least the initial + # key/value pair + assert len(vals) >= 2 + if combine: + vals[-1] = vals[-1] + ", " + val + else: + vals.append(val) + + def extend(self, *args: ValidHTTPHeaderSource, **kwargs: str) -> None: + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError( + f"extend() takes at most 1 positional arguments ({len(args)} given)" + ) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, typing.Mapping): + for key, val in other.items(): + self.add(key, val) + elif isinstance(other, typing.Iterable): + for key, value in other: + self.add(key, value) + elif hasattr(other, "keys") and hasattr(other, "__getitem__"): + # THIS IS NOT A TYPESAFE BRANCH + # In this branch, the object has a `keys` attr but is not a Mapping or any of + # the other types indicated in the method signature. We do some stuff with + # it as though it partially implements the Mapping interface, but we're not + # doing that stuff safely AT ALL. + for key in other.keys(): + self.add(key, other[key]) + + for key, value in kwargs.items(): + self.add(key, value) + + @typing.overload + def getlist(self, key: str) -> list[str]: ... + + @typing.overload + def getlist(self, key: str, default: _DT) -> list[str] | _DT: ... + + def getlist( + self, key: str, default: _Sentinel | _DT = _Sentinel.not_passed + ) -> list[str] | _DT: + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + if isinstance(key, bytes): + key = key.decode("latin-1") + try: + vals = self._container[key.lower()] + except KeyError: + if default is _Sentinel.not_passed: + # _DT is unbound; empty list is instance of List[str] + return [] + # _DT is bound; default is instance of _DT + return default + else: + # _DT may or may not be bound; vals[1:] is instance of List[str], which + # meets our external interface requirement of `Union[List[str], _DT]`. + return vals[1:] + + def _prepare_for_method_change(self) -> Self: + """ + Remove content-specific header fields before changing the request + method to GET or HEAD according to RFC 9110, Section 15.4. + """ + content_specific_headers = [ + "Content-Encoding", + "Content-Language", + "Content-Location", + "Content-Type", + "Content-Length", + "Digest", + "Last-Modified", + ] + for header in content_specific_headers: + self.discard(header) + return self + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self) -> str: + return f"{type(self).__name__}({dict(self.itermerged())})" + + def _copy_from(self, other: HTTPHeaderDict) -> None: + for key in other: + val = other.getlist(key) + self._container[key.lower()] = [key, *val] + + def copy(self) -> Self: + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self) -> typing.Iterator[tuple[str, str]]: + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self) -> typing.Iterator[tuple[str, str]]: + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ", ".join(val[1:]) + + def items(self) -> HTTPHeaderDictItemView: # type: ignore[override] + return HTTPHeaderDictItemView(self) + + def _has_value_for_header(self, header_name: str, potential_value: str) -> bool: + if header_name in self: + return potential_value in self._container[header_name.lower()][1:] + return False + + def __ior__(self, other: object) -> HTTPHeaderDict: + # Supports extending a header dict in-place using operator |= + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + self.extend(maybe_constructable) + return self + + def __or__(self, other: object) -> Self: + # Supports merging header dicts using operator | + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + result = self.copy() + result.extend(maybe_constructable) + return result + + def __ror__(self, other: object) -> Self: + # Supports merging header dicts using operator | when other is on left side + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + result = type(self)(maybe_constructable) + result.extend(self) + return result diff --git a/.venv/lib/python3.14/site-packages/urllib3/_request_methods.py b/.venv/lib/python3.14/site-packages/urllib3/_request_methods.py new file mode 100644 index 0000000000000000000000000000000000000000..297c271bf401c1cb48c6225f8822e78f58c3ca56 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/_request_methods.py @@ -0,0 +1,278 @@ +from __future__ import annotations + +import json as _json +import typing +from urllib.parse import urlencode + +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .response import BaseHTTPResponse + +__all__ = ["RequestMethods"] + +_TYPE_ENCODE_URL_FIELDS = typing.Union[ + typing.Sequence[tuple[str, typing.Union[str, bytes]]], + typing.Mapping[str, typing.Union[str, bytes]], +] + + +class RequestMethods: + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`urllib3.HTTPConnectionPool` and + :class:`urllib3.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} + + def __init__(self, headers: typing.Mapping[str, str] | None = None) -> None: + self.headers = headers or {} + + def urlopen( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + encode_multipart: bool = True, + multipart_boundary: str | None = None, + **kw: typing.Any, + ) -> BaseHTTPResponse: # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + json: typing.Any | None = None, + **urlopen_kw: typing.Any, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param fields: + Data to encode and send in the URL or request body, depending on ``method``. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param json: + Data to encode and send as JSON with UTF-encoded in the request body. + The ``"Content-Type"`` header will be set to ``"application/json"`` + unless specified otherwise. + """ + method = method.upper() + + if json is not None and body is not None: + raise TypeError( + "request got values for both 'body' and 'json' parameters which are mutually exclusive" + ) + + if json is not None: + if headers is None: + headers = self.headers + + if not ("content-type" in map(str.lower, headers.keys())): + headers = HTTPHeaderDict(headers) + headers["Content-Type"] = "application/json" + + body = _json.dumps(json, separators=(",", ":"), ensure_ascii=False).encode( + "utf-8" + ) + + if body is not None: + urlopen_kw["body"] = body + + if method in self._encode_url_methods: + return self.request_encode_url( + method, + url, + fields=fields, # type: ignore[arg-type] + headers=headers, + **urlopen_kw, + ) + else: + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + + def request_encode_url( + self, + method: str, + url: str, + fields: _TYPE_ENCODE_URL_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + **urlopen_kw: str, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param fields: + Data to encode and send in the URL. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + """ + if headers is None: + headers = self.headers + + extra_kw: dict[str, typing.Any] = {"headers": headers} + extra_kw.update(urlopen_kw) + + if fields: + url += "?" + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body( + self, + method: str, + url: str, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + encode_multipart: bool = True, + multipart_boundary: str | None = None, + **urlopen_kw: str, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :func:`urllib3.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :func:`urllib.parse.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimic behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param fields: + Data to encode and send in the request body. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param encode_multipart: + If True, encode the ``fields`` using the multipart/form-data MIME + format. + + :param multipart_boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + if headers is None: + headers = self.headers + + extra_kw: dict[str, typing.Any] = {"headers": HTTPHeaderDict(headers)} + body: bytes | str + + if fields: + if "body" in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one." + ) + + if encode_multipart: + body, content_type = encode_multipart_formdata( + fields, boundary=multipart_boundary + ) + else: + body, content_type = ( + urlencode(fields), # type: ignore[arg-type] + "application/x-www-form-urlencoded", + ) + + extra_kw["body"] = body + extra_kw["headers"].setdefault("Content-Type", content_type) + + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/.venv/lib/python3.14/site-packages/urllib3/_version.py b/.venv/lib/python3.14/site-packages/urllib3/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..bfed03985b436e778ef0caa2703c8a6774dda483 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/_version.py @@ -0,0 +1,24 @@ +# file generated by vcs-versioning +# don't change, don't track in version control +from __future__ import annotations + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +version: str +__version__: str +__version_tuple__: tuple[int | str, ...] +version_tuple: tuple[int | str, ...] +commit_id: str | None +__commit_id__: str | None + +__version__ = version = '2.7.0' +__version_tuple__ = version_tuple = (2, 7, 0) + +__commit_id__ = commit_id = None diff --git a/.venv/lib/python3.14/site-packages/urllib3/connection.py b/.venv/lib/python3.14/site-packages/urllib3/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..84e1dab9452d18ad0a2020c55f1966f4920f56c2 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/connection.py @@ -0,0 +1,1099 @@ +from __future__ import annotations + +import datetime +import http.client +import logging +import os +import re +import socket +import sys +import threading +import typing +import warnings +from http.client import HTTPConnection as _HTTPConnection +from http.client import HTTPException as HTTPException # noqa: F401 +from http.client import ResponseNotReady +from socket import timeout as SocketTimeout + +if typing.TYPE_CHECKING: + from .response import HTTPResponse + from .util.ssl_ import _TYPE_PEER_CERT_RET_DICT + from .util.ssltransport import SSLTransport + +from ._collections import HTTPHeaderDict +from .http2 import probe as http2_probe +from .util.response import assert_header_parsing +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT, Timeout +from .util.util import to_str +from .util.wait import wait_for_read + +try: # Compiled with SSL? + import ssl + + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): + ssl = None # type: ignore[assignment] + + class BaseSSLError(BaseException): # type: ignore[no-redef] + pass + + +from ._base_connection import _TYPE_BODY +from ._base_connection import ProxyConfig as ProxyConfig +from ._base_connection import _ResponseOptions as _ResponseOptions +from ._version import __version__ +from .exceptions import ( + ConnectTimeoutError, + HeaderParsingError, + NameResolutionError, + NewConnectionError, + ProxyError, + SystemTimeWarning, +) +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection, ssl_ +from .util.request import body_to_chunks +from .util.ssl_ import assert_fingerprint as _assert_fingerprint +from .util.ssl_ import ( + create_urllib3_context, + is_ipaddress, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .util.ssl_match_hostname import CertificateError, match_hostname +from .util.url import Url + +# Not a no-op, we're adding this to the namespace so it can be imported. +ConnectionError = ConnectionError +BrokenPipeError = BrokenPipeError + + +log = logging.getLogger(__name__) + +port_by_scheme = {"http": 80, "https": 443} + +# When it comes time to update this value as a part of regular maintenance +# (ie test_recent_date is failing) update it to ~6 months before the current date. +RECENT_DATE = datetime.date(2025, 1, 1) + +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") + + +class HTTPConnection(_HTTPConnection): + """ + Based on :class:`http.client.HTTPConnection` but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass: + + .. code-block:: python + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port: typing.ClassVar[int] = port_by_scheme["http"] # type: ignore[misc] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options: typing.ClassVar[connection._TYPE_SOCKET_OPTIONS] = [ + (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + ] + + #: Whether this connection verifies the host's certificate. + is_verified: bool = False + + #: Whether this proxy connection verified the proxy host's certificate. + # If no proxy is currently connected to the value will be ``None``. + proxy_is_verified: bool | None = None + + blocksize: int + source_address: tuple[str, int] | None + socket_options: connection._TYPE_SOCKET_OPTIONS | None + + _has_connected_to_proxy: bool + _response_options: _ResponseOptions | None + _tunnel_host: str | None + _tunnel_port: int | None + _tunnel_scheme: str | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: None | ( + connection._TYPE_SOCKET_OPTIONS + ) = default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: + super().__init__( + host=host, + port=port, + timeout=Timeout.resolve_default_timeout(timeout), + source_address=source_address, + blocksize=blocksize, + ) + self.socket_options = socket_options + self.proxy = proxy + self.proxy_config = proxy_config + + self._has_connected_to_proxy = False + self._response_options = None + self._tunnel_host: str | None = None + self._tunnel_port: int | None = None + self._tunnel_scheme: str | None = None + + def __str__(self) -> str: + return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" + + def __repr__(self) -> str: + return f"<{self} at {id(self):#x}>" + + @property + def host(self) -> str: + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip(".") + + @host.setter + def host(self, value: str) -> None: + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + + def _new_conn(self) -> socket.socket: + """Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + try: + sock = connection.create_connection( + (self._dns_host, self.port), + self.timeout, + source_address=self.source_address, + socket_options=self.socket_options, + ) + except socket.gaierror as e: + raise NameResolutionError(self.host, self, e) from e + except SocketTimeout as e: + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + + except OSError as e: + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + sys.audit("http.client.connect", self, self.host, self.port) + + return sock + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + if scheme not in ("http", "https"): + raise ValueError( + f"Invalid proxy scheme for tunneling: {scheme!r}, must be either 'http' or 'https'" + ) + super().set_tunnel(host, port=port, headers=headers) + self._tunnel_scheme = scheme + + if sys.version_info < (3, 11, 9) or ((3, 12) <= sys.version_info < (3, 12, 3)): + # Taken from python/cpython#100986 which was backported in 3.11.9 and 3.12.3. + # When using connection_from_host, host will come without brackets. + def _wrap_ipv6(self, ip: bytes) -> bytes: + if b":" in ip and ip[0] != b"["[0]: + return b"[" + ip + b"]" + return ip + + if sys.version_info < (3, 11, 9): + # `_tunnel` copied from 3.11.13 backporting + # https://github.com/python/cpython/commit/0d4026432591d43185568dd31cef6a034c4b9261 + # and https://github.com/python/cpython/commit/6fbc61070fda2ffb8889e77e3b24bca4249ab4d1 + def _tunnel(self) -> None: + _MAXLINE = http.client._MAXLINE # type: ignore[attr-defined] + connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( # type: ignore[str-format] + self._wrap_ipv6(self._tunnel_host.encode("ascii")), # type: ignore[union-attr] + self._tunnel_port, + ) + headers = [connect] + for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] + headers.append(f"{header}: {value}\r\n".encode("latin-1")) + headers.append(b"\r\n") + # Making a single send() call instead of one per line encourages + # the host OS to use a more optimal packet size instead of + # potentially emitting a series of small packets. + self.send(b"".join(headers)) + del headers + + response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] + try: + (version, code, message) = response._read_status() # type: ignore[attr-defined] + + if code != http.HTTPStatus.OK: + self.close() + raise OSError( + f"Tunnel connection failed: {code} {message.strip()}" + ) + while True: + line = response.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise http.client.LineTooLong("header line") + if not line: + # for sites which EOF without sending a trailer + break + if line in (b"\r\n", b"\n", b""): + break + + if self.debuglevel > 0: + print("header:", line.decode()) + finally: + response.close() + + elif (3, 12) <= sys.version_info < (3, 12, 3): + # `_tunnel` copied from 3.12.11 backporting + # https://github.com/python/cpython/commit/23aef575c7629abcd4aaf028ebd226fb41a4b3c8 + def _tunnel(self) -> None: # noqa: F811 + connect = b"CONNECT %s:%d HTTP/1.1\r\n" % ( # type: ignore[str-format] + self._wrap_ipv6(self._tunnel_host.encode("idna")), # type: ignore[union-attr] + self._tunnel_port, + ) + headers = [connect] + for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] + headers.append(f"{header}: {value}\r\n".encode("latin-1")) + headers.append(b"\r\n") + # Making a single send() call instead of one per line encourages + # the host OS to use a more optimal packet size instead of + # potentially emitting a series of small packets. + self.send(b"".join(headers)) + del headers + + response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] + try: + (version, code, message) = response._read_status() # type: ignore[attr-defined] + + self._raw_proxy_headers = http.client._read_headers(response.fp) # type: ignore[attr-defined] + + if self.debuglevel > 0: + for header in self._raw_proxy_headers: + print("header:", header.decode()) + + if code != http.HTTPStatus.OK: + self.close() + raise OSError( + f"Tunnel connection failed: {code} {message.strip()}" + ) + + finally: + response.close() + + def connect(self) -> None: + self.sock = self._new_conn() + if self._tunnel_host: + # If we're tunneling it means we're connected to our proxy. + self._has_connected_to_proxy = True + + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + + # If there's a proxy to be connected to we are fully connected. + # This is set twice (once above and here) due to forwarding proxies + # not using tunnelling. + self._has_connected_to_proxy = bool(self.proxy) + + if self._has_connected_to_proxy: + self.proxy_is_verified = False + + @property + def is_closed(self) -> bool: + return self.sock is None + + @property + def is_connected(self) -> bool: + if self.sock is None: + return False + return not wait_for_read(self.sock, timeout=0.0) + + @property + def has_connected_to_proxy(self) -> bool: + return self._has_connected_to_proxy + + @property + def proxy_is_forwarding(self) -> bool: + """ + Return True if a forwarding proxy is configured, else return False + """ + return bool(self.proxy) and self._tunnel_host is None + + @property + def proxy_is_tunneling(self) -> bool: + """ + Return True if a tunneling proxy is configured, else return False + """ + return self._tunnel_host is not None + + def close(self) -> None: + try: + super().close() + finally: + # Reset all stateful properties so connection + # can be re-used without leaking prior configs. + self.sock = None + self.is_verified = False + self.proxy_is_verified = None + self._has_connected_to_proxy = False + self._response_options = None + self._tunnel_host = None + self._tunnel_port = None + self._tunnel_scheme = None + + def putrequest( + self, + method: str, + url: str, + skip_host: bool = False, + skip_accept_encoding: bool = False, + ) -> None: + """""" + # Empty docstring because the indentation of CPython's implementation + # is broken but we don't want this method in our documentation. + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + f"Method cannot contain non-token characters {method!r} (found at least {match.group()!r})" + ) + + return super().putrequest( + method, url, skip_host=skip_host, skip_accept_encoding=skip_accept_encoding + ) + + def putheader(self, header: str, *values: str) -> None: # type: ignore[override] + """""" + if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): + super().putheader(header, *values) + elif to_str(header.lower()) not in SKIPPABLE_HEADERS: + skippable_headers = "', '".join( + [str.title(header) for header in sorted(SKIPPABLE_HEADERS)] + ) + raise ValueError( + f"urllib3.util.SKIP_HEADER only supports '{skippable_headers}'" + ) + + # `request` method's signature intentionally violates LSP. + # urllib3's API is different from `http.client.HTTPConnection` and the subclassing is only incidental. + def request( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: + # Update the inner socket's timeout value to send the request. + # This only triggers if the connection is re-used. + if self.sock is not None: + self.sock.settimeout(self.timeout) + + # Store these values to be fed into the HTTPResponse + # object later. TODO: Remove this in favor of a real + # HTTP lifecycle mechanism. + + # We have to store these before we call .request() + # because sometimes we can still salvage a response + # off the wire even if we aren't able to completely + # send the request body. + self._response_options = _ResponseOptions( + request_method=method, + request_url=url, + preload_content=preload_content, + decode_content=decode_content, + enforce_content_length=enforce_content_length, + ) + + if headers is None: + headers = {} + header_keys = frozenset(to_str(k.lower()) for k in headers) + skip_accept_encoding = "accept-encoding" in header_keys + skip_host = "host" in header_keys + self.putrequest( + method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host + ) + + # Transform the body into an iterable of sendall()-able chunks + # and detect if an explicit Content-Length is doable. + chunks_and_cl = body_to_chunks(body, method=method, blocksize=self.blocksize) + chunks = chunks_and_cl.chunks + content_length = chunks_and_cl.content_length + + # When chunked is explicit set to 'True' we respect that. + if chunked: + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") + else: + # Detect whether a framing mechanism is already in use. If so + # we respect that value, otherwise we pick chunked vs content-length + # depending on the type of 'body'. + if "content-length" in header_keys: + chunked = False + elif "transfer-encoding" in header_keys: + chunked = True + + # Otherwise we go off the recommendation of 'body_to_chunks()'. + else: + chunked = False + if content_length is None: + if chunks is not None: + chunked = True + self.putheader("Transfer-Encoding", "chunked") + else: + self.putheader("Content-Length", str(content_length)) + + # Now that framing headers are out of the way we send all the other headers. + if "user-agent" not in header_keys: + self.putheader("User-Agent", _get_default_user_agent()) + for header, value in headers.items(): + self.putheader(header, value) + self.endheaders() + + # If we're given a body we start sending that in chunks. + if chunks is not None: + for chunk in chunks: + # Sending empty chunks isn't allowed for TE: chunked + # as it indicates the end of the body. + if not chunk: + continue + if isinstance(chunk, str): + chunk = chunk.encode("utf-8") + if chunked: + self.send(b"%x\r\n%b\r\n" % (len(chunk), chunk)) + else: + self.send(chunk) + + # Regardless of whether we have a body or not, if we're in + # chunked mode we want to send an explicit empty chunk. + if chunked: + self.send(b"0\r\n\r\n") + + def request_chunked( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + ) -> None: + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + warnings.warn( + "HTTPConnection.request_chunked() is deprecated and will be removed " + "in urllib3 v3.0. Instead use HTTPConnection.request(..., chunked=True).", + category=FutureWarning, + stacklevel=2, + ) + self.request(method, url, body=body, headers=headers, chunked=True) + + def getresponse( # type: ignore[override] + self, + ) -> HTTPResponse: + """ + Get the response from the server. + + If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by the response_class variable. + + If a request has not been sent or if a previous response has not be handled, ResponseNotReady is raised. If the HTTP response indicates that the connection should be closed, then it will be closed before the response is returned. When the connection is closed, the underlying socket is closed. + """ + # Raise the same error as http.client.HTTPConnection + if self._response_options is None: + raise ResponseNotReady() + + # Reset this attribute for being used again. + resp_options = self._response_options + self._response_options = None + + # Since the connection's timeout value may have been updated + # we need to set the timeout on the socket. + self.sock.settimeout(self.timeout) + + # This is needed here to avoid circular import errors + from .response import HTTPResponse + + # Save a reference to the shutdown function before ownership is passed + # to httplib_response + # TODO should we implement it everywhere? + _shutdown = getattr(self.sock, "shutdown", None) + + # Get the response from http.client.HTTPConnection + httplib_response = super().getresponse() + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: + log.warning( + "Failed to parse headers (url=%s): %s", + _url_from_connection(self, resp_options.request_url), + hpe, + exc_info=True, + ) + + headers = HTTPHeaderDict(httplib_response.msg.items()) + + response = HTTPResponse( + body=httplib_response, + headers=headers, + status=httplib_response.status, + version=httplib_response.version, + version_string=getattr(self, "_http_vsn_str", "HTTP/?"), + reason=httplib_response.reason, + preload_content=resp_options.preload_content, + decode_content=resp_options.decode_content, + original_response=httplib_response, + enforce_content_length=resp_options.enforce_content_length, + request_method=resp_options.request_method, + request_url=resp_options.request_url, + sock_shutdown=_shutdown, + ) + return response + + +class HTTPSConnection(HTTPConnection): + """ + Many of the parameters to this constructor are passed to the underlying SSL + socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. + """ + + default_port = port_by_scheme["https"] # type: ignore[misc] + + cert_reqs: int | str | None = None + ca_certs: str | None = None + ca_cert_dir: str | None = None + ca_cert_data: None | str | bytes = None + ssl_version: int | str | None = None + ssl_minimum_version: int | None = None + ssl_maximum_version: int | None = None + assert_fingerprint: str | None = None + _connect_callback: typing.Callable[..., None] | None = None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: None | ( + connection._TYPE_SOCKET_OPTIONS + ) = HTTPConnection.default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: + super().__init__( + host, + port=port, + timeout=timeout, + source_address=source_address, + blocksize=blocksize, + socket_options=socket_options, + proxy=proxy, + proxy_config=proxy_config, + ) + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + # cert_reqs depends on ssl_context so calculate last. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + self.cert_reqs = cert_reqs + self._connect_callback = None + + def set_cert( + self, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ) -> None: + """ + This method should only be called once, before the connection is used. + """ + warnings.warn( + "HTTPSConnection.set_cert() is deprecated and will be removed " + "in urllib3 v3.0. Instead provide the parameters to the " + "HTTPSConnection constructor.", + category=FutureWarning, + stacklevel=2, + ) + + # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also + # have an SSLContext object in which case we'll use its verify_mode. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + def connect(self) -> None: + # Today we don't need to be doing this step before the /actual/ socket + # connection, however in the future we'll need to decide whether to + # create a new socket or re-use an existing "shared" socket as a part + # of the HTTP/2 handshake dance. + if self._tunnel_host is not None and self._tunnel_port is not None: + probe_http2_host = self._tunnel_host + probe_http2_port = self._tunnel_port + else: + probe_http2_host = self.host + probe_http2_port = self.port + + # Check if the target origin supports HTTP/2. + # If the value comes back as 'None' it means that the current thread + # is probing for HTTP/2 support. Otherwise, we're waiting for another + # probe to complete, or we get a value right away. + target_supports_http2: bool | None + if "h2" in ssl_.ALPN_PROTOCOLS: + target_supports_http2 = http2_probe.acquire_and_get( + host=probe_http2_host, port=probe_http2_port + ) + else: + # If HTTP/2 isn't going to be offered it doesn't matter if + # the target supports HTTP/2. Don't want to make a probe. + target_supports_http2 = False + + if self._connect_callback is not None: + self._connect_callback( + "before connect", + thread_id=threading.get_ident(), + target_supports_http2=target_supports_http2, + ) + + try: + sock: socket.socket | ssl.SSLSocket + self.sock = sock = self._new_conn() + server_hostname: str = self.host + tls_in_tls = False + + # Do we need to establish a tunnel? + if self.proxy_is_tunneling: + # We're tunneling to an HTTPS origin so need to do TLS-in-TLS. + if self._tunnel_scheme == "https": + # _connect_tls_proxy will verify and assign proxy_is_verified + self.sock = sock = self._connect_tls_proxy(self.host, sock) + tls_in_tls = True + elif self._tunnel_scheme == "http": + self.proxy_is_verified = False + + # If we're tunneling it means we're connected to our proxy. + self._has_connected_to_proxy = True + + self._tunnel() + # Override the host with the one we're requesting data from. + server_hostname = typing.cast(str, self._tunnel_host) + + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn( + ( + f"System time is way off (before {RECENT_DATE}). This will probably " + "lead to SSL verification errors" + ), + SystemTimeWarning, + ) + + # Remove trailing '.' from fqdn hostnames to allow certificate validation + server_hostname_rm_dot = server_hostname.rstrip(".") + + sock_and_verified = _ssl_wrap_socket_and_match_hostname( + sock=sock, + cert_reqs=self.cert_reqs, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + server_hostname=server_hostname_rm_dot, + ssl_context=self.ssl_context, + tls_in_tls=tls_in_tls, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + self.sock = sock_and_verified.socket + + # If an error occurs during connection/handshake we may need to release + # our lock so another connection can probe the origin. + except BaseException: + if self._connect_callback is not None: + self._connect_callback( + "after connect failure", + thread_id=threading.get_ident(), + target_supports_http2=target_supports_http2, + ) + + if target_supports_http2 is None: + http2_probe.set_and_release( + host=probe_http2_host, port=probe_http2_port, supports_http2=None + ) + raise + + # If this connection doesn't know if the origin supports HTTP/2 + # we report back to the HTTP/2 probe our result. + if target_supports_http2 is None: + supports_http2 = sock_and_verified.socket.selected_alpn_protocol() == "h2" + http2_probe.set_and_release( + host=probe_http2_host, + port=probe_http2_port, + supports_http2=supports_http2, + ) + + # Forwarding proxies can never have a verified target since + # the proxy is the one doing the verification. Should instead + # use a CONNECT tunnel in order to verify the target. + # See: https://github.com/urllib3/urllib3/issues/3267. + if self.proxy_is_forwarding: + self.is_verified = False + else: + self.is_verified = sock_and_verified.is_verified + + # If there's a proxy to be connected to we are fully connected. + # This is set twice (once above and here) due to forwarding proxies + # not using tunnelling. + self._has_connected_to_proxy = bool(self.proxy) + + # Set `self.proxy_is_verified` unless it's already set while + # establishing a tunnel. + if self._has_connected_to_proxy and self.proxy_is_verified is None: + self.proxy_is_verified = sock_and_verified.is_verified + + def _connect_tls_proxy(self, hostname: str, sock: socket.socket) -> ssl.SSLSocket: + """ + Establish a TLS connection to the proxy using the provided SSL context. + """ + # `_connect_tls_proxy` is called when self._tunnel_host is truthy. + proxy_config = typing.cast(ProxyConfig, self.proxy_config) + ssl_context = proxy_config.ssl_context + sock_and_verified = _ssl_wrap_socket_and_match_hostname( + sock, + cert_reqs=self.cert_reqs, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=hostname, + ssl_context=ssl_context, + assert_hostname=proxy_config.assert_hostname, + assert_fingerprint=proxy_config.assert_fingerprint, + # Features that aren't implemented for proxies yet: + cert_file=None, + key_file=None, + key_password=None, + tls_in_tls=False, + ) + self.proxy_is_verified = sock_and_verified.is_verified + return sock_and_verified.socket # type: ignore[return-value] + + +class _WrappedAndVerifiedSocket(typing.NamedTuple): + """ + Wrapped socket and whether the connection is + verified after the TLS handshake + """ + + socket: ssl.SSLSocket | SSLTransport + is_verified: bool + + +def _ssl_wrap_socket_and_match_hostname( + sock: socket.socket, + *, + cert_reqs: None | str | int, + ssl_version: None | str | int, + ssl_minimum_version: int | None, + ssl_maximum_version: int | None, + cert_file: str | None, + key_file: str | None, + key_password: str | None, + ca_certs: str | None, + ca_cert_dir: str | None, + ca_cert_data: None | str | bytes, + assert_hostname: None | str | typing.Literal[False], + assert_fingerprint: str | None, + server_hostname: str | None, + ssl_context: ssl.SSLContext | None, + tls_in_tls: bool = False, +) -> _WrappedAndVerifiedSocket: + """Logic for constructing an SSLContext from all TLS parameters, passing + that down into ssl_wrap_socket, and then doing certificate verification + either via hostname or fingerprint. This function exists to guarantee + that both proxies and targets have the same behavior when connecting via TLS. + """ + default_ssl_context = False + if ssl_context is None: + default_ssl_context = True + context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + ssl_minimum_version=ssl_minimum_version, + ssl_maximum_version=ssl_maximum_version, + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + else: + context = ssl_context + + context.verify_mode = resolve_cert_reqs(cert_reqs) + + # In some cases, we want to verify hostnames ourselves + if ( + # `ssl` can't verify fingerprints or alternate hostnames + assert_fingerprint + or assert_hostname + # assert_hostname can be set to False to disable hostname checking + or assert_hostname is False + # We still support OpenSSL 1.0.2, which prevents us from verifying + # hostnames easily: https://github.com/pyca/pyopenssl/pull/933 + or ssl_.IS_PYOPENSSL + or not ssl_.HAS_NEVER_CHECK_COMMON_NAME + ): + context.check_hostname = False + + # Try to load OS default certs if none are given. We need to do the hasattr() check + # for custom pyOpenSSL SSLContext objects because they don't support + # load_default_certs(). + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + + # Ensure that IPv6 addresses are in the proper format and don't have a + # scope ID. Python's SSL module fails to recognize scoped IPv6 addresses + # and interprets them as DNS hostnames. + if server_hostname is not None: + normalized = server_hostname.strip("[]") + if "%" in normalized: + normalized = normalized[: normalized.rfind("%")] + if is_ipaddress(normalized): + server_hostname = normalized + + ssl_sock = ssl_wrap_socket( + sock=sock, + keyfile=key_file, + certfile=cert_file, + key_password=key_password, + ca_certs=ca_certs, + ca_cert_dir=ca_cert_dir, + ca_cert_data=ca_cert_data, + server_hostname=server_hostname, + ssl_context=context, + tls_in_tls=tls_in_tls, + ) + + try: + if assert_fingerprint: + _assert_fingerprint( + ssl_sock.getpeercert(binary_form=True), assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not context.check_hostname + and assert_hostname is not False + ): + cert: _TYPE_PEER_CERT_RET_DICT = ssl_sock.getpeercert() # type: ignore[assignment] + + # Need to signal to our match_hostname whether to use 'commonName' or not. + # If we're using our own constructed SSLContext we explicitly set 'False' + # because PyPy hard-codes 'True' from SSLContext.hostname_checks_common_name. + if default_ssl_context: + hostname_checks_common_name = False + else: + hostname_checks_common_name = ( + getattr(context, "hostname_checks_common_name", False) or False + ) + + _match_hostname( + cert, + assert_hostname or server_hostname, # type: ignore[arg-type] + hostname_checks_common_name, + ) + + return _WrappedAndVerifiedSocket( + socket=ssl_sock, + is_verified=context.verify_mode == ssl.CERT_REQUIRED + or bool(assert_fingerprint), + ) + except BaseException: + ssl_sock.close() + raise + + +def _match_hostname( + cert: _TYPE_PEER_CERT_RET_DICT | None, + asserted_hostname: str, + hostname_checks_common_name: bool = False, +) -> None: + # Our upstream implementation of ssl.match_hostname() + # only applies this normalization to IP addresses so it doesn't + # match DNS SANs so we do the same thing! + stripped_hostname = asserted_hostname.strip("[]") + if is_ipaddress(stripped_hostname): + asserted_hostname = stripped_hostname + + try: + match_hostname(cert, asserted_hostname, hostname_checks_common_name) + except CertificateError as e: + log.warning( + "Certificate did not match expected hostname: %s. Certificate: %s", + asserted_hostname, + cert, + ) + # Add cert to exception and reraise so client code can inspect + # the cert when catching the exception, if they want to + e._peer_cert = cert # type: ignore[attr-defined] + raise + + +def _wrap_proxy_error(err: Exception, proxy_scheme: str | None) -> ProxyError: + # Look for the phrase 'wrong version number', if found + # then we should warn the user that we're very sure that + # this proxy is HTTP-only and they have a configuration issue. + error_normalized = " ".join(re.split("[^a-z]", str(err).lower())) + is_likely_http_proxy = ( + "wrong version number" in error_normalized + or "unknown protocol" in error_normalized + or "record layer failure" in error_normalized + ) + http_proxy_warning = ( + ". Your proxy appears to only use HTTP and not HTTPS, " + "try changing your proxy URL to be HTTP. See: " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#https-proxy-error-http-proxy" + ) + new_err = ProxyError( + f"Unable to connect to proxy" + f"{http_proxy_warning if is_likely_http_proxy and proxy_scheme == 'https' else ''}", + err, + ) + new_err.__cause__ = err + return new_err + + +def _get_default_user_agent() -> str: + return f"python-urllib3/{__version__}" + + +class DummyConnection: + """Used to detect a failed ConnectionCls import.""" + + +if not ssl: + HTTPSConnection = DummyConnection # type: ignore[misc, assignment] # noqa: F811 + + +VerifiedHTTPSConnection = HTTPSConnection + + +def _url_from_connection( + conn: HTTPConnection | HTTPSConnection, path: str | None = None +) -> str: + """Returns the URL from a given connection. This is mainly used for testing and logging.""" + + scheme = "https" if isinstance(conn, HTTPSConnection) else "http" + + return Url(scheme=scheme, host=conn.host, port=conn.port, path=path).url diff --git a/.venv/lib/python3.14/site-packages/urllib3/connectionpool.py b/.venv/lib/python3.14/site-packages/urllib3/connectionpool.py new file mode 100644 index 0000000000000000000000000000000000000000..70fbc5e725aee571654b1a58748537fa167b498d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/connectionpool.py @@ -0,0 +1,1191 @@ +from __future__ import annotations + +import errno +import logging +import queue +import sys +import typing +import warnings +import weakref +from socket import timeout as SocketTimeout +from types import TracebackType + +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from ._request_methods import RequestMethods +from .connection import ( + BaseSSLError, + BrokenPipeError, + DummyConnection, + HTTPConnection, + HTTPException, + HTTPSConnection, + ProxyConfig, + _wrap_proxy_error, +) +from .connection import port_by_scheme as port_by_scheme +from .exceptions import ( + ClosedPoolError, + EmptyPoolError, + FullPoolError, + HostChangedError, + InsecureRequestWarning, + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, +) +from .response import BaseHTTPResponse +from .util.connection import is_connection_dropped +from .util.proxy import connection_requires_http_tunnel +from .util.request import _TYPE_BODY_POSITION, set_file_position +from .util.retry import Retry +from .util.ssl_match_hostname import CertificateError +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_DEFAULT, Timeout +from .util.url import Url, _encode_target +from .util.url import _normalize_host as normalize_host +from .util.url import parse_url +from .util.util import to_str + +if typing.TYPE_CHECKING: + import ssl + + from typing_extensions import Self + + from ._base_connection import BaseHTTPConnection, BaseHTTPSConnection + +log = logging.getLogger(__name__) + +_TYPE_TIMEOUT = typing.Union[Timeout, float, _TYPE_DEFAULT, None] + + +# Pool objects +class ConnectionPool: + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + + .. note:: + ConnectionPool.urlopen() does not normalize or percent-encode target URIs + which is useful if your target server doesn't support percent-encoded + target URIs. + """ + + scheme: str | None = None + QueueCls = queue.LifoQueue + + def __init__(self, host: str, port: int | None = None) -> None: + if not host: + raise LocationValueError("No host specified.") + + self.host = _normalize_host(host, scheme=self.scheme) + self.port = port + + # This property uses 'normalize_host()' (not '_normalize_host()') + # to avoid removing square braces around IPv6 addresses. + # This value is sent to `HTTPConnection.set_tunnel()` if called + # because square braces are required for HTTP CONNECT tunneling. + self._tunnel_host = normalize_host(host, scheme=self.scheme).lower() + + def __str__(self) -> str: + return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> typing.Literal[False]: + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self) -> None: + """ + Close all pooled connections and disable the pool. + """ + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`http.client.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`http.client.HTTPConnection`. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.ProxyManager` + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.ProxyManager` + + :param \\**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = "http" + ConnectionCls: type[BaseHTTPConnection] | type[BaseHTTPSConnection] = HTTPConnection + + def __init__( + self, + host: str, + port: int | None = None, + timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, + maxsize: int = 1, + block: bool = False, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + _proxy: Url | None = None, + _proxy_headers: typing.Mapping[str, str] | None = None, + _proxy_config: ProxyConfig | None = None, + **conn_kw: typing.Any, + ): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool: queue.LifoQueue[typing.Any] | None = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + self.proxy_config = _proxy_config + + # Fill the queue up so that doing get() on it will block properly + for _ in range(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # Defaulting `socket_options` to an empty list avoids it defaulting to + # ``HTTPConnection.default_socket_options``. + self.conn_kw.setdefault("socket_options", []) + + self.conn_kw["proxy"] = self.proxy + self.conn_kw["proxy_config"] = self.proxy_config + + # Do not pass 'self' as callback to 'finalize'. + # Then the 'finalize' would keep an endless living (leak) to self. + # By just passing a reference to the pool allows the garbage collector + # to free self if nobody else has a reference to it. + pool = self.pool + + # Close all the HTTPConnections in the pool before the + # HTTPConnectionPool object is garbage collected. + weakref.finalize(self, _close_pool_connections, pool) + + def _new_conn(self) -> BaseHTTPConnection: + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTP connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "80", + ) + + conn = self.ConnectionCls( + host=self.host, + port=self.port, + timeout=self.timeout.connect_timeout, + **self.conn_kw, + ) + return conn + + def _get_conn(self, timeout: float | None = None) -> BaseHTTPConnection: + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + + if self.pool is None: + raise ClosedPoolError(self, "Pool is closed.") + + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") from None # Defensive: + + except queue.Empty: + if self.block: + raise EmptyPoolError( + self, + "Pool is empty and a new connection can't be opened due to blocking mode.", + ) from None + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.debug("Resetting dropped connection: %s", self.host) + conn.close() + + return conn or self._new_conn() + + def _put_conn(self, conn: BaseHTTPConnection | None) -> None: + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + if self.pool is not None: + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + if self.block: + # This should never happen if you got the conn from self._get_conn + raise FullPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) from None + + log.warning( + "Connection pool is full, discarding connection: %s. Connection pool size: %s", + self.host, + self.pool.qsize(), + ) + + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn: BaseHTTPConnection) -> None: + """ + Called right before a request is made, after the socket is created. + """ + + def _prepare_proxy(self, conn: BaseHTTPConnection) -> None: + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout: _TYPE_TIMEOUT) -> Timeout: + """Helper that always returns a :class:`urllib3.util.Timeout`""" + if timeout is _DEFAULT_TIMEOUT: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout( + self, + err: BaseSSLError | OSError | SocketTimeout, + url: str, + timeout_value: _TYPE_TIMEOUT | None, + ) -> None: + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={timeout_value})" + ) from err + + # See the above comment about EAGAIN in Python 3. + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={timeout_value})" + ) from err + + def _make_request( + self, + conn: BaseHTTPConnection, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | None = None, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + chunked: bool = False, + response_conn: BaseHTTPConnection | None = None, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> BaseHTTPResponse: + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param response_conn: + Set this to ``None`` if you will handle releasing the connection or + set the connection to have the response release it. + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout) + + try: + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # _validate_conn() starts the connection to an HTTPS proxy + # so we need to wrap errors with 'ProxyError' here too. + except ( + OSError, + NewConnectionError, + TimeoutError, + BaseSSLError, + CertificateError, + SSLError, + ) as e: + new_e: Exception = e + if isinstance(e, (BaseSSLError, CertificateError)): + new_e = SSLError(e) + # If the connection didn't successfully connect to it's proxy + # then there + if isinstance( + new_e, (OSError, NewConnectionError, TimeoutError, SSLError) + ) and (conn and conn.proxy and not conn.has_connected_to_proxy): + new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) + raise new_e + + # conn.request() calls http.client.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + try: + conn.request( + method, + url, + body=body, + headers=headers, + chunked=chunked, + preload_content=preload_content, + decode_content=decode_content, + enforce_content_length=enforce_content_length, + ) + + # We are swallowing BrokenPipeError (errno.EPIPE) since the server is + # legitimately able to close the connection after sending a valid response. + # With this behaviour, the received response is still readable. + except BrokenPipeError: + pass + except OSError as e: + # MacOS/Linux + # EPROTOTYPE and ECONNRESET are needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + # Condition changed later to emit ECONNRESET instead of only EPROTOTYPE. + if e.errno != errno.EPROTOTYPE and e.errno != errno.ECONNRESET: + raise + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + if not conn.is_closed: + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={read_timeout})" + ) + conn.timeout = read_timeout + + # Receive the response from the server + try: + response = conn.getresponse() + except (BaseSSLError, OSError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # Set properties that are used by the pooling layer. + response.retries = retries + response._connection = response_conn # type: ignore[attr-defined] + response._pool = self # type: ignore[attr-defined] + + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + response.version_string, + response.status, + response.length_remaining, + ) + + return response + + def close(self) -> None: + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + # Close all the HTTPConnections in the pool. + _close_pool_connections(old_pool) + + def is_same_host(self, url: str) -> bool: + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith("/"): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, _, host, port, *_ = parse_url(url) + scheme = scheme or "http" + if host is not None: + host = _normalize_host(host, scheme=scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + redirect: bool = True, + assert_same_host: bool = True, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + pool_timeout: int | None = None, + release_conn: bool | None = None, + chunked: bool = False, + body_pos: _TYPE_BODY_POSITION | None = None, + preload_content: bool = True, + decode_content: bool = True, + **response_kw: typing.Any, + ) -> BaseHTTPResponse: + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method + such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When ``False``, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param bool preload_content: + If True, the response's body will be preloaded into memory. + + :param bool decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of ``preload_content`` + which defaults to ``True``. + + :param bool chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param int body_pos: + Position to seek to in file-like body in the event of a retry or + redirect. Typically this won't need to be set because urllib3 will + auto-populate the value when needed. + """ + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + # URLs starting with / are inherently schemeless. + url = to_str(_encode_target(url)) + destination_scheme = None + else: + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme + url = to_str(parsed_url.url) + + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = preload_content + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + conn = None + + # Track whether `conn` needs to be released before + # returning/raising/recursing. Update this variable if necessary, and + # leave `release_conn` constant throughout the function. That way, if + # the function recurses, the original value of `release_conn` will be + # passed down into the recursive call, and its value will be respected. + # + # See issue #651 [1] for details. + # + # [1] + release_this_conn = release_conn + + http_tunnel_required = connection_requires_http_tunnel( + self.proxy, self.proxy_config, destination_scheme + ) + + # Merge the proxy headers. Only done when not using HTTP CONNECT. We + # have to copy the headers dict so we can safely change it without those + # changes being reflected in anyone else's copy. + if not http_tunnel_required: + headers = headers.copy() # type: ignore[attr-defined] + headers.update(self.proxy_headers) # type: ignore[union-attr] + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + # Keep track of whether we cleanly exited the except block. This + # ensures we do proper cleanup in finally. + clean_exit = False + + # Rewind body position, if needed. Record current position + # for future rewinds in the event of a redirect/retry. + body_pos = set_file_position(body, body_pos) + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout # type: ignore[assignment] + + # Is this a closed/new connection that requires CONNECT tunnelling? + if self.proxy is not None and http_tunnel_required and conn.is_closed: + try: + self._prepare_proxy(conn) + except (BaseSSLError, OSError, SocketTimeout) as e: + self._raise_timeout( + err=e, url=self.proxy.url, timeout_value=conn.timeout + ) + raise + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Make the request on the HTTPConnection object + response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + retries=retries, + response_conn=response_conn, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Everything went great! + clean_exit = True + + except EmptyPoolError: + # Didn't get a connection from the pool, no need to clean up + clean_exit = True + release_this_conn = False + raise + + except ( + TimeoutError, + HTTPException, + OSError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ProxyError, + ) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + new_e: Exception = e + if isinstance(e, (BaseSSLError, CertificateError)): + new_e = SSLError(e) + if isinstance( + new_e, + ( + OSError, + NewConnectionError, + TimeoutError, + SSLError, + HTTPException, + ), + ) and (conn and conn.proxy and not conn.has_connected_to_proxy): + new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) + elif isinstance(new_e, (OSError, HTTPException)): + new_e = ProtocolError("Connection aborted.", new_e) + + retries = retries.increment( + method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2] + ) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if not clean_exit: + # We hit some kind of exception, handled or otherwise. We need + # to throw the connection away unless explicitly told not to. + # Close the connection, set the variable to None, and make sure + # we put the None back in the pool to avoid leaking it. + if conn: + conn.close() + conn = None + release_this_conn = True + + if release_this_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning( + "Retrying (%r) after connection broken by '%r': %s", retries, err, url + ) + return self.urlopen( + method, + url, + body, + headers, + retries, + redirect, + assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + # Change the method according to RFC 9110, Section 15.4.4. + method = "GET" + # And lose the body not to transfer anything sensitive. + body = None + headers = HTTPHeaderDict(headers)._prepare_for_method_change() + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # self.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not self.is_same_host( + redirect_location + ): + new_headers = headers.copy() # type: ignore[union-attr] + for header in headers: + if header.lower() in retries.remove_headers_on_redirect: + new_headers.pop(header, None) + headers = new_headers + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep_for_retry(response) + log.debug("Redirecting %s -> %s", url, redirect_location) + return self.urlopen( + method, + redirect_location, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(response.headers.get("Retry-After")) + if retries.is_retry(method, response.status, has_retry_after): + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_status: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep(response) + log.debug("Retry: %s", url) + return self.urlopen( + method, + url, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` + is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + """ + + scheme = "https" + ConnectionCls: type[BaseHTTPSConnection] = HTTPSConnection + + def __init__( + self, + host: str, + port: int | None = None, + timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, + maxsize: int = 1, + block: bool = False, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + _proxy: Url | None = None, + _proxy_headers: typing.Mapping[str, str] | None = None, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + ssl_version: int | str | None = None, + ssl_minimum_version: ssl.TLSVersion | None = None, + ssl_maximum_version: ssl.TLSVersion | None = None, + assert_hostname: str | typing.Literal[False] | None = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + **conn_kw: typing.Any, + ) -> None: + super().__init__( + host, + port, + timeout, + maxsize, + block, + headers, + retries, + _proxy, + _proxy_headers, + **conn_kw, + ) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_proxy(self, conn: HTTPSConnection) -> None: # type: ignore[override] + """Establishes a tunnel connection through HTTP CONNECT.""" + if self.proxy and self.proxy.scheme == "https": + tunnel_scheme = "https" + else: + tunnel_scheme = "http" + + conn.set_tunnel( + scheme=tunnel_scheme, + host=self._tunnel_host, + port=self.port, + headers=self.proxy_headers, + ) + conn.connect() + + def _new_conn(self) -> BaseHTTPSConnection: + """ + Return a fresh :class:`urllib3.connection.HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTPS connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "443", + ) + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: # type: ignore[comparison-overlap] + raise ImportError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) + + actual_host: str = self.host + actual_port = self.port + if self.proxy is not None and self.proxy.host is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + return self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + **self.conn_kw, + ) + + def _validate_conn(self, conn: BaseHTTPConnection) -> None: + """ + Called right before a request is made, after the socket is created. + """ + super()._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if conn.is_closed: + conn.connect() + + # TODO revise this, see https://github.com/urllib3/urllib3/issues/2791 + if not conn.is_verified and not conn.proxy_is_verified: + warnings.warn( + ( + f"Unverified HTTPS request is being made to host '{conn.host}'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#tls-warnings" + ), + InsecureRequestWarning, + ) + + +def connection_from_url(url: str, **kw: typing.Any) -> HTTPConnectionPool: + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \\**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, _, host, port, *_ = parse_url(url) + scheme = scheme or "http" + port = port or port_by_scheme.get(scheme, 80) + if scheme == "https": + return HTTPSConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + else: + return HTTPConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + + +@typing.overload +def _normalize_host(host: None, scheme: str | None) -> None: ... + + +@typing.overload +def _normalize_host(host: str, scheme: str | None) -> str: ... + + +def _normalize_host(host: str | None, scheme: str | None) -> str | None: + """ + Normalize hosts for comparisons and use with sockets. + """ + + host = normalize_host(host, scheme) + + # httplib doesn't like it when we include brackets in IPv6 addresses + # Specifically, if we include brackets but also pass the port then + # httplib crazily doubles up the square brackets on the Host header. + # Instead, we need to make sure we never pass ``None`` as the port. + # However, for backward compatibility reasons we can't actually + # *assert* that. See http://bugs.python.org/issue28539 + if host and host.startswith("[") and host.endswith("]"): + host = host[1:-1] + return host + + +def _url_from_pool( + pool: HTTPConnectionPool | HTTPSConnectionPool, path: str | None = None +) -> str: + """Returns the URL from a given connection pool. This is mainly used for testing and logging.""" + return Url(scheme=pool.scheme, host=pool.host, port=pool.port, path=path).url + + +def _close_pool_connections(pool: queue.LifoQueue[typing.Any]) -> None: + """Drains a queue of connections and closes each one.""" + try: + while True: + conn = pool.get(block=False) + if conn: + conn.close() + except queue.Empty: + pass # Done. diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/__init__.py b/.venv/lib/python3.14/site-packages/urllib3/contrib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edd228405fe8b2d7e4d35ec4d32d983e21fd7396 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/pyopenssl.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/pyopenssl.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd095efda4ad58262bd99d7615f07e99c9170cb1 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/pyopenssl.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/socks.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/socks.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc98cc8f2f0da7cc0fe9a0a37df228c631cd9855 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/socks.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__init__.py b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e5b62b25e932566f7ae7599c1cedec2b8f30d95b --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__init__.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +import urllib3.connection + +from ...connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from .connection import EmscriptenHTTPConnection, EmscriptenHTTPSConnection + + +def inject_into_urllib3() -> None: + # override connection classes to use emscripten specific classes + # n.b. mypy complains about the overriding of classes below + # if it isn't ignored + HTTPConnectionPool.ConnectionCls = EmscriptenHTTPConnection + HTTPSConnectionPool.ConnectionCls = EmscriptenHTTPSConnection + urllib3.connection.HTTPConnection = EmscriptenHTTPConnection # type: ignore[misc,assignment] + urllib3.connection.HTTPSConnection = EmscriptenHTTPSConnection # type: ignore[misc,assignment] + urllib3.connection.VerifiedHTTPSConnection = EmscriptenHTTPSConnection # type: ignore[assignment] diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f55207374e348afd8270e1a69d7946126f45d22b Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/connection.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a82add40d7a8131f6d14a74a80a32237aaeb72cc Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/connection.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/fetch.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/fetch.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..941917b9ef280c89b3fd809fe2f9342ca1298660 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/fetch.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/request.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/request.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3175a318df625bd2cf90ddbb34f3529eb40e723 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/request.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/response.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/response.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bfe8f2a7b34d2a04c1b2ec5de4755ca282a4bc97 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__pycache__/response.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/connection.py b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..63f79dd3be803db09671c909f79316c3f65d6916 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/connection.py @@ -0,0 +1,260 @@ +from __future__ import annotations + +import os +import typing + +# use http.client.HTTPException for consistency with non-emscripten +from http.client import HTTPException as HTTPException # noqa: F401 +from http.client import ResponseNotReady + +from ..._base_connection import _TYPE_BODY +from ...connection import HTTPConnection, ProxyConfig, port_by_scheme +from ...exceptions import TimeoutError +from ...response import BaseHTTPResponse +from ...util.connection import _TYPE_SOCKET_OPTIONS +from ...util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT +from ...util.url import Url +from .fetch import _RequestError, _TimeoutError, send_request, send_streaming_request +from .request import EmscriptenRequest +from .response import EmscriptenHttpResponseWrapper, EmscriptenResponse + +if typing.TYPE_CHECKING: + from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection + + +class EmscriptenHTTPConnection: + default_port: typing.ClassVar[int] = port_by_scheme["http"] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + timeout: None | (float) + + host: str + port: int + blocksize: int + source_address: tuple[str, int] | None + socket_options: _TYPE_SOCKET_OPTIONS | None + + proxy: Url | None + proxy_config: ProxyConfig | None + + is_verified: bool = False + proxy_is_verified: bool | None = None + + response_class: type[BaseHTTPResponse] = EmscriptenHttpResponseWrapper + _response: EmscriptenResponse | None + + def __init__( + self, + host: str, + port: int = 0, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + socket_options: _TYPE_SOCKET_OPTIONS | None = None, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: + self.host = host + self.port = port + self.timeout = timeout if isinstance(timeout, float) else 0.0 + self.scheme = "http" + self._closed = True + self._response = None + # ignore these things because we don't + # have control over that stuff + self.proxy = None + self.proxy_config = None + self.blocksize = blocksize + self.source_address = None + self.socket_options = None + self.is_verified = False + + def set_tunnel( + self, + host: str, + port: int | None = 0, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + pass + + def connect(self) -> None: + pass + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + # We know *at least* botocore is depending on the order of the + # first 3 parameters so to be safe we only mark the later ones + # as keyword-only to ensure we have space to extend. + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: + self._closed = False + if url.startswith("/"): + if self.port is not None: + port = f":{self.port}" + else: + port = "" + # no scheme / host / port included, make a full url + url = f"{self.scheme}://{self.host}{port}{url}" + request = EmscriptenRequest( + url=url, + method=method, + timeout=self.timeout if self.timeout else 0, + decode_content=decode_content, + ) + request.set_body(body) + if headers: + for k, v in headers.items(): + request.set_header(k, v) + self._response = None + try: + if not preload_content: + self._response = send_streaming_request(request) + if self._response is None: + self._response = send_request(request) + except _TimeoutError as e: + raise TimeoutError(e.message) from e + except _RequestError as e: + raise HTTPException(e.message) from e + + def getresponse(self) -> BaseHTTPResponse: + if self._response is not None: + return EmscriptenHttpResponseWrapper( + internal_response=self._response, + url=self._response.request.url, + connection=self, + ) + else: + raise ResponseNotReady() + + def close(self) -> None: + self._closed = True + self._response = None + + @property + def is_closed(self) -> bool: + """Whether the connection either is brand new or has been previously closed. + If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` + properties must be False. + """ + return self._closed + + @property + def is_connected(self) -> bool: + """Whether the connection is actively connected to any origin (proxy or target)""" + return True + + @property + def has_connected_to_proxy(self) -> bool: + """Whether the connection has successfully connected to its proxy. + This returns False if no proxy is in use. Used to determine whether + errors are coming from the proxy layer or from tunnelling to the target origin. + """ + return False + + +class EmscriptenHTTPSConnection(EmscriptenHTTPConnection): + default_port = port_by_scheme["https"] + # all this is basically ignored, as browser handles https + cert_reqs: int | str | None = None + ca_certs: str | None = None + ca_cert_dir: str | None = None + ca_cert_data: None | str | bytes = None + cert_file: str | None + key_file: str | None + key_password: str | None + ssl_context: typing.Any | None + ssl_version: int | str | None = None + ssl_minimum_version: int | None = None + ssl_maximum_version: int | None = None + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None = None + + def __init__( + self, + host: str, + port: int = 0, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: ( + None | _TYPE_SOCKET_OPTIONS + ) = HTTPConnection.default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: typing.Any | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: + super().__init__( + host, + port=port, + timeout=timeout, + source_address=source_address, + blocksize=blocksize, + socket_options=socket_options, + proxy=proxy, + proxy_config=proxy_config, + ) + self.scheme = "https" + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + self.cert_reqs = None + + # The browser will automatically verify all requests. + # We have no control over that setting. + self.is_verified = True + + def set_cert( + self, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ) -> None: + pass + + +# verify that this class implements BaseHTTP(s) connection correctly +if typing.TYPE_CHECKING: + _supports_http_protocol: BaseHTTPConnection = EmscriptenHTTPConnection("", 0) + _supports_https_protocol: BaseHTTPSConnection = EmscriptenHTTPSConnection("", 0) diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js new file mode 100644 index 0000000000000000000000000000000000000000..faf141e1fa4113a0c14480d1681ddecb9678ced4 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js @@ -0,0 +1,110 @@ +let Status = { + SUCCESS_HEADER: -1, + SUCCESS_EOF: -2, + ERROR_TIMEOUT: -3, + ERROR_EXCEPTION: -4, +}; + +let connections = new Map(); +let nextConnectionID = 1; +const encoder = new TextEncoder(); + +self.addEventListener("message", async function (event) { + if (event.data.close) { + let connectionID = event.data.close; + connections.delete(connectionID); + return; + } else if (event.data.getMore) { + let connectionID = event.data.getMore; + let { curOffset, value, reader, intBuffer, byteBuffer } = + connections.get(connectionID); + // if we still have some in buffer, then just send it back straight away + if (!value || curOffset >= value.length) { + // read another buffer if required + try { + let readResponse = await reader.read(); + + if (readResponse.done) { + // read everything - clear connection and return + connections.delete(connectionID); + Atomics.store(intBuffer, 0, Status.SUCCESS_EOF); + Atomics.notify(intBuffer, 0); + // finished reading successfully + // return from event handler + return; + } + curOffset = 0; + connections.get(connectionID).value = readResponse.value; + value = readResponse.value; + } catch (error) { + console.log("Request exception:", error); + let errorBytes = encoder.encode(error.message); + let written = errorBytes.length; + byteBuffer.set(errorBytes); + intBuffer[1] = written; + Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); + Atomics.notify(intBuffer, 0); + } + } + + // send as much buffer as we can + let curLen = value.length - curOffset; + if (curLen > byteBuffer.length) { + curLen = byteBuffer.length; + } + byteBuffer.set(value.subarray(curOffset, curOffset + curLen), 0); + + Atomics.store(intBuffer, 0, curLen); // store current length in bytes + Atomics.notify(intBuffer, 0); + curOffset += curLen; + connections.get(connectionID).curOffset = curOffset; + + return; + } else { + // start fetch + let connectionID = nextConnectionID; + nextConnectionID += 1; + const intBuffer = new Int32Array(event.data.buffer); + const byteBuffer = new Uint8Array(event.data.buffer, 8); + try { + const response = await fetch(event.data.url, event.data.fetchParams); + // return the headers first via textencoder + var headers = []; + for (const pair of response.headers.entries()) { + headers.push([pair[0], pair[1]]); + } + let headerObj = { + headers: headers, + status: response.status, + connectionID, + }; + const headerText = JSON.stringify(headerObj); + let headerBytes = encoder.encode(headerText); + let written = headerBytes.length; + byteBuffer.set(headerBytes); + intBuffer[1] = written; + // make a connection + connections.set(connectionID, { + reader: response.body.getReader(), + intBuffer: intBuffer, + byteBuffer: byteBuffer, + value: undefined, + curOffset: 0, + }); + // set header ready + Atomics.store(intBuffer, 0, Status.SUCCESS_HEADER); + Atomics.notify(intBuffer, 0); + // all fetching after this goes through a new postmessage call with getMore + // this allows for parallel requests + } catch (error) { + console.log("Request exception:", error); + let errorBytes = encoder.encode(error.message); + let written = errorBytes.length; + byteBuffer.set(errorBytes); + intBuffer[1] = written; + Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); + Atomics.notify(intBuffer, 0); + } + } +}); +self.postMessage({ inited: true }); diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/fetch.py b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/fetch.py new file mode 100644 index 0000000000000000000000000000000000000000..612cfddc4c28d2f0edf47522278fa6d9b7906623 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/fetch.py @@ -0,0 +1,726 @@ +""" +Support for streaming http requests in emscripten. + +A few caveats - + +If your browser (or Node.js) has WebAssembly JavaScript Promise Integration enabled +https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md +*and* you launch pyodide using `pyodide.runPythonAsync`, this will fetch data using the +JavaScript asynchronous fetch api (wrapped via `pyodide.ffi.call_sync`). In this case +timeouts and streaming should just work. + +Otherwise, it uses a combination of XMLHttpRequest and a web-worker for streaming. + +This approach has several caveats: + +Firstly, you can't do streaming http in the main UI thread, because atomics.wait isn't allowed. +Streaming only works if you're running pyodide in a web worker. + +Secondly, this uses an extra web worker and SharedArrayBuffer to do the asynchronous fetch +operation, so it requires that you have crossOriginIsolation enabled, by serving over https +(or from localhost) with the two headers below set: + + Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp + +You can tell if cross origin isolation is successfully enabled by looking at the global crossOriginIsolated variable in +JavaScript console. If it isn't, streaming requests will fallback to XMLHttpRequest, i.e. getting the whole +request into a buffer and then returning it. it shows a warning in the JavaScript console in this case. + +Finally, the webworker which does the streaming fetch is created on initial import, but will only be started once +control is returned to javascript. Call `await wait_for_streaming_ready()` to wait for streaming fetch. + +NB: in this code, there are a lot of JavaScript objects. They are named js_* +to make it clear what type of object they are. +""" + +from __future__ import annotations + +import io +import json +from email.parser import Parser +from importlib.resources import files +from typing import TYPE_CHECKING, Any + +import js # type: ignore[import-not-found] +from pyodide.ffi import ( # type: ignore[import-not-found] + JsArray, + JsException, + JsProxy, + to_js, +) + +if TYPE_CHECKING: + from typing_extensions import Buffer + +from .request import EmscriptenRequest +from .response import EmscriptenResponse + +""" +There are some headers that trigger unintended CORS preflight requests. +See also https://github.com/koenvo/pyodide-http/issues/22 +""" +HEADERS_TO_IGNORE = ("user-agent",) + +SUCCESS_HEADER = -1 +SUCCESS_EOF = -2 +ERROR_TIMEOUT = -3 +ERROR_EXCEPTION = -4 + + +class _RequestError(Exception): + def __init__( + self, + message: str | None = None, + *, + request: EmscriptenRequest | None = None, + response: EmscriptenResponse | None = None, + ): + self.request = request + self.response = response + self.message = message + super().__init__(self.message) + + +class _StreamingError(_RequestError): + pass + + +class _TimeoutError(_RequestError): + pass + + +def _obj_from_dict(dict_val: dict[str, Any]) -> JsProxy: + return to_js(dict_val, dict_converter=js.Object.fromEntries) + + +class _ReadStream(io.RawIOBase): + def __init__( + self, + int_buffer: JsArray, + byte_buffer: JsArray, + timeout: float, + worker: JsProxy, + connection_id: int, + request: EmscriptenRequest, + ): + self.int_buffer = int_buffer + self.byte_buffer = byte_buffer + self.read_pos = 0 + self.read_len = 0 + self.connection_id = connection_id + self.worker = worker + self.timeout = int(1000 * timeout) if timeout > 0 else None + self.is_live = True + self._is_closed = False + self.request: EmscriptenRequest | None = request + + def __del__(self) -> None: + self.close() + + # this is compatible with _base_connection + def is_closed(self) -> bool: + return self._is_closed + + # for compatibility with RawIOBase + @property + def closed(self) -> bool: + return self.is_closed() + + def close(self) -> None: + if self.is_closed(): + return + self.read_len = 0 + self.read_pos = 0 + self.int_buffer = None + self.byte_buffer = None + self._is_closed = True + self.request = None + if self.is_live: + self.worker.postMessage(_obj_from_dict({"close": self.connection_id})) + self.is_live = False + super().close() + + def readable(self) -> bool: + return True + + def writable(self) -> bool: + return False + + def seekable(self) -> bool: + return False + + def readinto(self, byte_obj: Buffer) -> int: + if not self.int_buffer: + raise _StreamingError( + "No buffer for stream in _ReadStream.readinto", + request=self.request, + response=None, + ) + if self.read_len == 0: + # wait for the worker to send something + js.Atomics.store(self.int_buffer, 0, ERROR_TIMEOUT) + self.worker.postMessage(_obj_from_dict({"getMore": self.connection_id})) + if ( + js.Atomics.wait(self.int_buffer, 0, ERROR_TIMEOUT, self.timeout) + == "timed-out" + ): + raise _TimeoutError + data_len = self.int_buffer[0] + if data_len > 0: + self.read_len = data_len + self.read_pos = 0 + elif data_len == ERROR_EXCEPTION: + string_len = self.int_buffer[1] + # decode the error string + js_decoder = js.TextDecoder.new() + json_str = js_decoder.decode(self.byte_buffer.slice(0, string_len)) + raise _StreamingError( + f"Exception thrown in fetch: {json_str}", + request=self.request, + response=None, + ) + else: + # EOF, free the buffers and return zero + # and free the request + self.is_live = False + self.close() + return 0 + # copy from int32array to python bytes + ret_length = min(self.read_len, len(memoryview(byte_obj))) + subarray = self.byte_buffer.subarray( + self.read_pos, self.read_pos + ret_length + ).to_py() + memoryview(byte_obj)[0:ret_length] = subarray + self.read_len -= ret_length + self.read_pos += ret_length + return ret_length + + +class _StreamingFetcher: + def __init__(self) -> None: + # make web-worker and data buffer on startup + self.streaming_ready = False + streaming_worker_code = ( + files(__package__) + .joinpath("emscripten_fetch_worker.js") + .read_text(encoding="utf-8") + ) + js_data_blob = js.Blob.new( + to_js([streaming_worker_code], create_pyproxies=False), + _obj_from_dict({"type": "application/javascript"}), + ) + + def promise_resolver(js_resolve_fn: JsProxy, js_reject_fn: JsProxy) -> None: + def onMsg(e: JsProxy) -> None: + self.streaming_ready = True + js_resolve_fn(e) + + def onErr(e: JsProxy) -> None: + js_reject_fn(e) # Defensive: never happens in ci + + self.js_worker.onmessage = onMsg + self.js_worker.onerror = onErr + + js_data_url = js.URL.createObjectURL(js_data_blob) + self.js_worker = js.globalThis.Worker.new(js_data_url) + self.js_worker_ready_promise = js.globalThis.Promise.new(promise_resolver) + + def send(self, request: EmscriptenRequest) -> EmscriptenResponse: + headers = { + k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE + } + + body = request.body + fetch_data = {"headers": headers, "body": to_js(body), "method": request.method} + # start the request off in the worker + timeout = int(1000 * request.timeout) if request.timeout > 0 else None + js_shared_buffer = js.SharedArrayBuffer.new(1048576) + js_int_buffer = js.Int32Array.new(js_shared_buffer) + js_byte_buffer = js.Uint8Array.new(js_shared_buffer, 8) + + js.Atomics.store(js_int_buffer, 0, ERROR_TIMEOUT) + js.Atomics.notify(js_int_buffer, 0) + js_absolute_url = js.URL.new(request.url, js.location).href + self.js_worker.postMessage( + _obj_from_dict( + { + "buffer": js_shared_buffer, + "url": js_absolute_url, + "fetchParams": fetch_data, + } + ) + ) + # wait for the worker to send something + js.Atomics.wait(js_int_buffer, 0, ERROR_TIMEOUT, timeout) + if js_int_buffer[0] == ERROR_TIMEOUT: + raise _TimeoutError( + "Timeout connecting to streaming request", + request=request, + response=None, + ) + elif js_int_buffer[0] == SUCCESS_HEADER: + # got response + # header length is in second int of intBuffer + string_len = js_int_buffer[1] + # decode the rest to a JSON string + js_decoder = js.TextDecoder.new() + # this does a copy (the slice) because decode can't work on shared array + # for some silly reason + json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) + # get it as an object + response_obj = json.loads(json_str) + return EmscriptenResponse( + request=request, + status_code=response_obj["status"], + headers=response_obj["headers"], + body=_ReadStream( + js_int_buffer, + js_byte_buffer, + request.timeout, + self.js_worker, + response_obj["connectionID"], + request, + ), + ) + elif js_int_buffer[0] == ERROR_EXCEPTION: + string_len = js_int_buffer[1] + # decode the error string + js_decoder = js.TextDecoder.new() + json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) + raise _StreamingError( + f"Exception thrown in fetch: {json_str}", request=request, response=None + ) + else: + raise _StreamingError( + f"Unknown status from worker in fetch: {js_int_buffer[0]}", + request=request, + response=None, + ) + + +class _JSPIReadStream(io.RawIOBase): + """ + A read stream that uses pyodide.ffi.run_sync to read from a JavaScript fetch + response. This requires support for WebAssembly JavaScript Promise Integration + in the containing browser, and for pyodide to be launched via runPythonAsync. + + :param js_read_stream: + The JavaScript stream reader + + :param timeout: + Timeout in seconds + + :param request: + The request we're handling + + :param response: + The response this stream relates to + + :param js_abort_controller: + A JavaScript AbortController object, used for timeouts + """ + + def __init__( + self, + js_read_stream: Any, + timeout: float, + request: EmscriptenRequest, + response: EmscriptenResponse, + js_abort_controller: Any, # JavaScript AbortController for timeouts + ): + self.js_read_stream = js_read_stream + self.timeout = timeout + self._is_closed = False + self._is_done = False + self.request: EmscriptenRequest | None = request + self.response: EmscriptenResponse | None = response + self.current_buffer = None + self.current_buffer_pos = 0 + self.js_abort_controller = js_abort_controller + + def __del__(self) -> None: + self.close() + + # this is compatible with _base_connection + def is_closed(self) -> bool: + return self._is_closed + + # for compatibility with RawIOBase + @property + def closed(self) -> bool: + return self.is_closed() + + def close(self) -> None: + if self.is_closed(): + return + self.read_len = 0 + self.read_pos = 0 + self.js_read_stream.cancel() + self.js_read_stream = None + self._is_closed = True + self._is_done = True + self.request = None + self.response = None + super().close() + + def readable(self) -> bool: + return True + + def writable(self) -> bool: + return False + + def seekable(self) -> bool: + return False + + def _get_next_buffer(self) -> bool: + result_js = _run_sync_with_timeout( + self.js_read_stream.read(), + self.timeout, + self.js_abort_controller, + request=self.request, + response=self.response, + ) + if result_js.done: + self._is_done = True + return False + else: + self.current_buffer = result_js.value.to_py() + self.current_buffer_pos = 0 + return True + + def readinto(self, byte_obj: Buffer) -> int: + if self.current_buffer is None: + if not self._get_next_buffer() or self.current_buffer is None: + self.close() + return 0 + ret_length = min( + len(byte_obj), len(self.current_buffer) - self.current_buffer_pos + ) + byte_obj[0:ret_length] = self.current_buffer[ + self.current_buffer_pos : self.current_buffer_pos + ret_length + ] + self.current_buffer_pos += ret_length + if self.current_buffer_pos == len(self.current_buffer): + self.current_buffer = None + return ret_length + + +# check if we are in a worker or not +def is_in_browser_main_thread() -> bool: + return hasattr(js, "window") and hasattr(js, "self") and js.self == js.window + + +def is_cross_origin_isolated() -> bool: + return hasattr(js, "crossOriginIsolated") and js.crossOriginIsolated + + +def is_in_node() -> bool: + return ( + hasattr(js, "process") + and hasattr(js.process, "release") + and hasattr(js.process.release, "name") + and js.process.release.name == "node" + ) + + +def is_worker_available() -> bool: + return hasattr(js, "Worker") and hasattr(js, "Blob") + + +_fetcher: _StreamingFetcher | None = None + +if is_worker_available() and ( + (is_cross_origin_isolated() and not is_in_browser_main_thread()) + and (not is_in_node()) +): + _fetcher = _StreamingFetcher() +else: + _fetcher = None + + +NODE_JSPI_ERROR = ( + "urllib3 only works in Node.js with pyodide.runPythonAsync" + " and requires the flag --experimental-wasm-stack-switching in " + " versions of node <24." +) + + +def send_streaming_request(request: EmscriptenRequest) -> EmscriptenResponse | None: + if has_jspi(): + return send_jspi_request(request, True) + elif is_in_node(): + raise _RequestError( + message=NODE_JSPI_ERROR, + request=request, + response=None, + ) + + if _fetcher and streaming_ready(): + return _fetcher.send(request) + else: + _show_streaming_warning() + return None + + +_SHOWN_TIMEOUT_WARNING = False + + +def _show_timeout_warning() -> None: + global _SHOWN_TIMEOUT_WARNING + if not _SHOWN_TIMEOUT_WARNING: + _SHOWN_TIMEOUT_WARNING = True + message = "Warning: Timeout is not available on main browser thread" + js.console.warn(message) + + +_SHOWN_STREAMING_WARNING = False + + +def _show_streaming_warning() -> None: + global _SHOWN_STREAMING_WARNING + if not _SHOWN_STREAMING_WARNING: + _SHOWN_STREAMING_WARNING = True + message = "Can't stream HTTP requests because: \n" + if not is_cross_origin_isolated(): + message += " Page is not cross-origin isolated\n" + if is_in_browser_main_thread(): + message += " Python is running in main browser thread\n" + if not is_worker_available(): + message += " Worker or Blob classes are not available in this environment." # Defensive: this is always False in browsers that we test in + if streaming_ready() is False: + message += """ Streaming fetch worker isn't ready. If you want to be sure that streaming fetch +is working, you need to call: 'await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()`""" + from js import console + + console.warn(message) + + +def send_request(request: EmscriptenRequest) -> EmscriptenResponse: + if has_jspi(): + return send_jspi_request(request, False) + elif is_in_node(): + raise _RequestError( + message=NODE_JSPI_ERROR, + request=request, + response=None, + ) + try: + js_xhr = js.XMLHttpRequest.new() + + if not is_in_browser_main_thread(): + js_xhr.responseType = "arraybuffer" + if request.timeout: + js_xhr.timeout = int(request.timeout * 1000) + else: + js_xhr.overrideMimeType("text/plain; charset=ISO-8859-15") + if request.timeout: + # timeout isn't available on the main thread - show a warning in console + # if it is set + _show_timeout_warning() + + js_xhr.open(request.method, request.url, False) + for name, value in request.headers.items(): + if name.lower() not in HEADERS_TO_IGNORE: + js_xhr.setRequestHeader(name, value) + + js_xhr.send(to_js(request.body)) + + headers = dict(Parser().parsestr(js_xhr.getAllResponseHeaders())) + + if not is_in_browser_main_thread(): + body = js_xhr.response.to_py().tobytes() + else: + body = js_xhr.response.encode("ISO-8859-15") + return EmscriptenResponse( + status_code=js_xhr.status, headers=headers, body=body, request=request + ) + except JsException as err: + if err.name == "TimeoutError": + raise _TimeoutError(err.message, request=request) + elif err.name == "NetworkError": + raise _RequestError(err.message, request=request) + else: + # general http error + raise _RequestError(err.message, request=request) + + +def send_jspi_request( + request: EmscriptenRequest, streaming: bool +) -> EmscriptenResponse: + """ + Send a request using WebAssembly JavaScript Promise Integration + to wrap the asynchronous JavaScript fetch api (experimental). + + :param request: + Request to send + + :param streaming: + Whether to stream the response + + :return: The response object + :rtype: EmscriptenResponse + """ + timeout = request.timeout + js_abort_controller = js.AbortController.new() + headers = {k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE} + req_body = request.body + fetch_data = { + "headers": headers, + "body": to_js(req_body), + "method": request.method, + "signal": js_abort_controller.signal, + } + # Node.js returns the whole response (unlike opaqueredirect in browsers), + # so urllib3 can set `redirect: manual` to control redirects itself. + # https://stackoverflow.com/a/78524615 + if _is_node_js(): + fetch_data["redirect"] = "manual" + # Call JavaScript fetch (async api, returns a promise) + fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data)) + # Now suspend WebAssembly until we resolve that promise + # or time out. + response_js = _run_sync_with_timeout( + fetcher_promise_js, + timeout, + js_abort_controller, + request=request, + response=None, + ) + headers = {} + header_iter = response_js.headers.entries() + while True: + iter_value_js = header_iter.next() + if getattr(iter_value_js, "done", False): + break + else: + headers[str(iter_value_js.value[0])] = str(iter_value_js.value[1]) + status_code = response_js.status + body: bytes | io.RawIOBase = b"" + + response = EmscriptenResponse( + status_code=status_code, headers=headers, body=b"", request=request + ) + if streaming: + # get via inputstream + if response_js.body is not None: + # get a reader from the fetch response + body_stream_js = response_js.body.getReader() + body = _JSPIReadStream( + body_stream_js, timeout, request, response, js_abort_controller + ) + else: + # get directly via arraybuffer + # n.b. this is another async JavaScript call. + body = _run_sync_with_timeout( + response_js.arrayBuffer(), + timeout, + js_abort_controller, + request=request, + response=response, + ).to_py() + response.body = body + return response + + +def _run_sync_with_timeout( + promise: Any, + timeout: float, + js_abort_controller: Any, + request: EmscriptenRequest | None, + response: EmscriptenResponse | None, +) -> Any: + """ + Await a JavaScript promise synchronously with a timeout which is implemented + via the AbortController + + :param promise: + Javascript promise to await + + :param timeout: + Timeout in seconds + + :param js_abort_controller: + A JavaScript AbortController object, used on timeout + + :param request: + The request being handled + + :param response: + The response being handled (if it exists yet) + + :raises _TimeoutError: If the request times out + :raises _RequestError: If the request raises a JavaScript exception + + :return: The result of awaiting the promise. + """ + timer_id = None + if timeout > 0: + timer_id = js.setTimeout( + js_abort_controller.abort.bind(js_abort_controller), int(timeout * 1000) + ) + try: + from pyodide.ffi import run_sync + + # run_sync here uses WebAssembly JavaScript Promise Integration to + # suspend python until the JavaScript promise resolves. + return run_sync(promise) + except JsException as err: + if err.name == "AbortError": + raise _TimeoutError( + message="Request timed out", request=request, response=response + ) + else: + raise _RequestError(message=err.message, request=request, response=response) + finally: + if timer_id is not None: + js.clearTimeout(timer_id) + + +def has_jspi() -> bool: + """ + Return true if jspi can be used. + + This requires both browser support and also WebAssembly + to be in the correct state - i.e. that the javascript + call into python was async not sync. + + :return: True if jspi can be used. + :rtype: bool + """ + try: + from pyodide.ffi import can_run_sync, run_sync # noqa: F401 + + return bool(can_run_sync()) + except ImportError: + return False + + +def _is_node_js() -> bool: + """ + Check if we are in Node.js. + + :return: True if we are in Node.js. + :rtype: bool + """ + return ( + hasattr(js, "process") + and hasattr(js.process, "release") + # According to the Node.js documentation, the release name is always "node". + and js.process.release.name == "node" + ) + + +def streaming_ready() -> bool | None: + if _fetcher: + return _fetcher.streaming_ready + else: + return None # no fetcher, return None to signify that + + +async def wait_for_streaming_ready() -> bool: + if _fetcher: + await _fetcher.js_worker_ready_promise + return True + else: + return False diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/request.py b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/request.py new file mode 100644 index 0000000000000000000000000000000000000000..e692e692bd0d38f6a0677992a6993fc68050dff3 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/request.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from ..._base_connection import _TYPE_BODY + + +@dataclass +class EmscriptenRequest: + method: str + url: str + params: dict[str, str] | None = None + body: _TYPE_BODY | None = None + headers: dict[str, str] = field(default_factory=dict) + timeout: float = 0 + decode_content: bool = True + + def set_header(self, name: str, value: str) -> None: + self.headers[name.capitalize()] = value + + def set_body(self, body: _TYPE_BODY | None) -> None: + self.body = body diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/response.py b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/response.py new file mode 100644 index 0000000000000000000000000000000000000000..ec1e1dbe83722c5fb16290a852859252ae616826 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/response.py @@ -0,0 +1,281 @@ +from __future__ import annotations + +import json as _json +import logging +import typing +from contextlib import contextmanager +from dataclasses import dataclass +from http.client import HTTPException as HTTPException +from io import BytesIO, IOBase + +from ...exceptions import InvalidHeader, TimeoutError +from ...response import BaseHTTPResponse +from ...util.retry import Retry +from .request import EmscriptenRequest + +if typing.TYPE_CHECKING: + from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection + +log = logging.getLogger(__name__) + + +@dataclass +class EmscriptenResponse: + status_code: int + headers: dict[str, str] + body: IOBase | bytes + request: EmscriptenRequest + + +class EmscriptenHttpResponseWrapper(BaseHTTPResponse): + def __init__( + self, + internal_response: EmscriptenResponse, + url: str | None = None, + connection: BaseHTTPConnection | BaseHTTPSConnection | None = None, + ): + self._pool = None # set by pool class + self._body = None + self._uncached_read_occurred = False + self._response = internal_response + self._url = url + self._connection = connection + self._closed = False + super().__init__( + headers=internal_response.headers, + status=internal_response.status_code, + request_url=url, + version=0, + version_string="HTTP/?", + reason="", + decode_content=True, + ) + self.length_remaining = self._init_length(self._response.request.method) + self.length_is_certain = False + + @property + def url(self) -> str | None: + return self._url + + @url.setter + def url(self, url: str | None) -> None: + self._url = url + + @property + def connection(self) -> BaseHTTPConnection | BaseHTTPSConnection | None: + return self._connection + + @property + def retries(self) -> Retry | None: + return self._retries + + @retries.setter + def retries(self, retries: Retry | None) -> None: + # Override the request_url if retries has a redirect location. + self._retries = retries + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + while True: + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + else: + break + + def _init_length(self, request_method: str | None) -> int | None: + length: int | None + content_length: str | None = self.headers.get("content-length") + + if content_length is not None: + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = {int(val) for val in content_length.split(",")} + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % content_length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + else: # if content_length is None + length = None + + # Check for responses that shouldn't include a body + if ( + self.status in (204, 304) + or 100 <= self.status < 200 + or request_method == "HEAD" + ): + length = 0 + + return length + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, # ignored because browser decodes always + cache_content: bool = False, + ) -> bytes: + if ( + self._closed + or self._response is None + or (isinstance(self._response.body, IOBase) and self._response.body.closed) + ): + return b"" + + with self._error_catcher(): + # body has been preloaded as a string by XmlHttpRequest + if not isinstance(self._response.body, IOBase): + self.length_remaining = len(self._response.body) + self.length_is_certain = True + # wrap body in IOStream + self._response.body = BytesIO(self._response.body) + if amt is not None and amt >= 0: + # don't cache partial content + cache_content = False + data = self._response.body.read(amt) + self._uncached_read_occurred = True + else: # read all we can (and cache it) + data = self._response.body.read() + if cache_content and not self._uncached_read_occurred: + self._body = data + else: + self._uncached_read_occurred = True + if self.length_remaining is not None: + self.length_remaining = max(self.length_remaining - len(data), 0) + if len(data) == 0 or ( + self.length_is_certain and self.length_remaining == 0 + ): + # definitely finished reading, close response stream + self._response.body.close() + return typing.cast(bytes, data) + + def read_chunked( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> typing.Generator[bytes]: + # chunked is handled by browser + while True: + bytes = self.read(amt, decode_content) + if not bytes: + break + yield bytes + + def release_conn(self) -> None: + if not self._pool or not self._connection: + return None + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self) -> None: + self.close() + + @property + def data(self) -> bytes: + if self._body: + return self._body + else: + return self.read(cache_content=True) + + def json(self) -> typing.Any: + """ + Deserializes the body of the HTTP response as a Python object. + + The body of the HTTP response must be encoded using UTF-8, as per + `RFC 8529 Section 8.1 `_. + + To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to + your custom decoder instead. + + If the body of the HTTP response is not decodable to UTF-8, a + `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a + valid JSON document, a `json.JSONDecodeError` will be raised. + + Read more :ref:`here `. + + :returns: The body of the HTTP response as a Python object. + """ + data = self.data.decode("utf-8") + return _json.loads(data) + + def close(self) -> None: + if not self._closed: + if isinstance(self._response.body, IOBase): + self._response.body.close() + if self._connection: + self._connection.close() + self._connection = None + self._closed = True + + @contextmanager + def _error_catcher(self) -> typing.Generator[None]: + """ + Catch Emscripten specific exceptions thrown by fetch.py, + instead re-raising urllib3 variants, so that low-level exceptions + are not leaked in the high-level api. + + On exit, release the connection back to the pool. + """ + from .fetch import _RequestError, _TimeoutError # avoid circular import + + clean_exit = False + + try: + yield + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + except _TimeoutError as e: + raise TimeoutError(str(e)) + except _RequestError as e: + raise HTTPException(str(e)) + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now + if ( + isinstance(self._response.body, IOBase) + and not self._response.body.closed + ): + self._response.body.close() + # release the connection back to the pool + self.release_conn() + else: + # If we have read everything from the response stream, + # return the connection back to the pool. + if ( + isinstance(self._response.body, IOBase) + and self._response.body.closed + ): + self.release_conn() diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/pyopenssl.py b/.venv/lib/python3.14/site-packages/urllib3/contrib/pyopenssl.py new file mode 100644 index 0000000000000000000000000000000000000000..f06b8599920fdcf6b8a2f869141378709a30c84c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/contrib/pyopenssl.py @@ -0,0 +1,563 @@ +""" +Module for using pyOpenSSL as a TLS backend. This module was relevant before +the standard library ``ssl`` module supported SNI, but now that we've dropped +support for Python 2.7 all relevant Python versions support SNI so +**this module is no longer recommended**. + +This needs the following packages installed: + +* `pyOpenSSL`_ (tested with 19.0.0) +* `cryptography`_ (minimum 2.3, from pyopenssl) +* `idna`_ (minimum 2.1, from cryptography) + +However, pyOpenSSL depends on cryptography, so while we use all three directly here we +end up having relatively few packages required. + +You can install them with the following command: + +.. code-block:: bash + + $ python -m pip install pyopenssl cryptography idna + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this: + +.. code-block:: python + + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + +.. _pyopenssl: https://www.pyopenssl.org +.. _cryptography: https://cryptography.io +.. _idna: https://github.com/kjd/idna +""" + +from __future__ import annotations + +import OpenSSL.SSL # type: ignore[import-not-found] +from cryptography import x509 + +try: + from cryptography.x509 import UnsupportedExtension # type: ignore[attr-defined] +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): # type: ignore[no-redef] + pass + + +import logging +import ssl +import typing +from io import BytesIO +from socket import socket as socket_cls + +from .. import util + +if typing.TYPE_CHECKING: + from OpenSSL.crypto import X509 # type: ignore[import-not-found] + + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions: dict[int, int] = { + util.ssl_.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] + util.ssl_.PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + + +_stdlib_to_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} +_openssl_to_stdlib_verify = {v: k for k, v in _stdlib_to_openssl_verify.items()} + +# The SSLvX values are the most likely to be missing in the future +# but we check them all just to be sure. +_OP_NO_SSLv2_OR_SSLv3: int = getattr(OpenSSL.SSL, "OP_NO_SSLv2", 0) | getattr( + OpenSSL.SSL, "OP_NO_SSLv3", 0 +) +_OP_NO_TLSv1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1", 0) +_OP_NO_TLSv1_1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_1", 0) +_OP_NO_TLSv1_2: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_2", 0) +_OP_NO_TLSv1_3: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_3", 0) + +_openssl_to_ssl_minimum_version: dict[int, int] = { + ssl.TLSVersion.MINIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.TLSv1: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1, + ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1, + ssl.TLSVersion.TLSv1_3: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 + ), + ssl.TLSVersion.MAXIMUM_SUPPORTED: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 + ), +} +_openssl_to_ssl_maximum_version: dict[int, int] = { + ssl.TLSVersion.MINIMUM_SUPPORTED: ( + _OP_NO_SSLv2_OR_SSLv3 + | _OP_NO_TLSv1 + | _OP_NO_TLSv1_1 + | _OP_NO_TLSv1_2 + | _OP_NO_TLSv1_3 + ), + ssl.TLSVersion.TLSv1: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3 + ), + ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3, + ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_3, + ssl.TLSVersion.TLSv1_3: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.MAXIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, +} + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3() -> None: + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." + + _validate_dependencies_met() + + util.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.ssl_.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3() -> None: + "Undo monkey-patching by :func:`inject_into_urllib3`." + + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met() -> None: + """ + Verifies that PyOpenSSL's package-level dependencies have been met. + Throws `ImportError` if they are not met. + """ + # Method added in `cryptography==1.1`; not available in older versions + from cryptography.x509.extensions import Extensions + + if getattr(Extensions, "get_extension_for_class", None) is None: + raise ImportError( + "'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer." + ) + + # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 + # attribute is only present on those versions. + from OpenSSL.crypto import X509 + + x509 = X509() + if getattr(x509, "_x509", None) is None: + raise ImportError( + "'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer." + ) + + +def _dnsname_to_stdlib(name: str) -> str | None: + """ + Converts a dNSName SubjectAlternativeName field to the form used by the + standard library on the given Python version. + + Cryptography produces a dNSName as a unicode string that was idna-decoded + from ASCII bytes. We need to idna-encode that string to get it back, and + then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib + uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). + + If the name cannot be idna-encoded then we return None signalling that + the name given should be skipped. + """ + + def idna_encode(name: str) -> bytes | None: + """ + Borrowed wholesale from the Python Cryptography Project. It turns out + that we can't just safely call `idna.encode`: it can explode for + wildcard names. This avoids that problem. + """ + import idna + + try: + for prefix in ["*.", "."]: + if name.startswith(prefix): + name = name[len(prefix) :] + return prefix.encode("ascii") + idna.encode(name) + return idna.encode(name) + except idna.core.IDNAError: + return None + + # Don't send IPv6 addresses through the IDNA encoder. + if ":" in name: + return name + + encoded_name = idna_encode(name) + if encoded_name is None: + return None + return encoded_name.decode("utf-8") + + +def get_subj_alt_name(peer_cert: X509) -> list[tuple[str, str]]: + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + cert = peer_cert.to_cryptography() + + # We want to find the SAN extension. Ask Cryptography to locate it (it's + # faster than looping in Python) + try: + ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + except x509.ExtensionNotFound: + # No such extension, return the empty list. + return [] + except ( + x509.DuplicateExtension, + UnsupportedExtension, + x509.UnsupportedGeneralNameType, + UnicodeError, + ) as e: + # A problem has been found with the quality of the certificate. Assume + # no SAN field is present. + log.warning( + "A problem was encountered with the certificate that prevented " + "urllib3 from finding the SubjectAlternativeName field. This can " + "affect certificate validation. The error was %s", + e, + ) + return [] + + # We want to return dNSName and iPAddress fields. We need to cast the IPs + # back to strings because the match_hostname function wants them as + # strings. + # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 + # decoded. This is pretty frustrating, but that's what the standard library + # does with certificates, and so we need to attempt to do the same. + # We also want to skip over names which cannot be idna encoded. + names = [ + ("DNS", name) + for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + if name is not None + ] + names.extend( + ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) + ) + + return names + + +class WrappedSocket: + """API-compatibility wrapper for Python OpenSSL's Connection-class.""" + + def __init__( + self, + connection: OpenSSL.SSL.Connection, + socket: socket_cls, + suppress_ragged_eofs: bool = True, + ) -> None: + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._io_refs = 0 + self._closed = False + + def fileno(self) -> int: + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self) -> None: + if self._io_refs > 0: + self._io_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args: typing.Any, **kwargs: typing.Any) -> bytes: + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return b"" + else: + raise OSError(e.args[0], str(e)) from e + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b"" + else: + raise + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise TimeoutError("The read operation timed out") from e + else: + return self.recv(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"read error: {e!r}") from e + else: + return data # type: ignore[no-any-return] + + def recv_into(self, *args: typing.Any, **kwargs: typing.Any) -> int: + try: + return self.connection.recv_into(*args, **kwargs) # type: ignore[no-any-return] + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return 0 + else: + raise OSError(e.args[0], str(e)) from e + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise TimeoutError("The read operation timed out") from e + else: + return self.recv_into(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"read error: {e!r}") from e + + def settimeout(self, timeout: float) -> None: + return self.socket.settimeout(timeout) + + def _send_until_done(self, data: bytes) -> int: + while True: + try: + return self.connection.send(data) # type: ignore[no-any-return] + except OpenSSL.SSL.WantWriteError as e: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise TimeoutError() from e + continue + except OpenSSL.SSL.SysCallError as e: + raise OSError(e.args[0], str(e)) from e + + def sendall(self, data: bytes) -> None: + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done( + data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] + ) + total_sent += sent + + def shutdown(self, how: int) -> None: + try: + self.connection.shutdown() + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"shutdown error: {e!r}") from e + + def close(self) -> None: + self._closed = True + if self._io_refs <= 0: + self._real_close() + + def _real_close(self) -> None: + try: + return self.connection.close() # type: ignore[no-any-return] + except OpenSSL.SSL.Error: + return + + def getpeercert( + self, binary_form: bool = False + ) -> dict[str, list[typing.Any]] | None: + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 # type: ignore[no-any-return] + + if binary_form: + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) # type: ignore[no-any-return] + + return { + "subject": ((("commonName", x509.get_subject().CN),),), # type: ignore[dict-item] + "subjectAltName": get_subj_alt_name(x509), + } + + def version(self) -> str: + return self.connection.get_protocol_version_name() # type: ignore[no-any-return] + + def selected_alpn_protocol(self) -> str | None: + alpn_proto = self.connection.get_alpn_proto_negotiated() + return alpn_proto.decode() if alpn_proto else None + + +WrappedSocket.makefile = socket_cls.makefile # type: ignore[attr-defined] + + +class PyOpenSSLContext: + """ + I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible + for translating the interface of the standard library ``SSLContext`` object + to calls into PyOpenSSL. + """ + + def __init__(self, protocol: int) -> None: + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + self._minimum_version: int = ssl.TLSVersion.MINIMUM_SUPPORTED + self._maximum_version: int = ssl.TLSVersion.MAXIMUM_SUPPORTED + self._verify_flags: int = ssl.VERIFY_X509_TRUSTED_FIRST + + @property + def options(self) -> int: + return self._options + + @options.setter + def options(self, value: int) -> None: + self._options = value + self._set_ctx_options() + + @property + def verify_flags(self) -> int: + return self._verify_flags + + @verify_flags.setter + def verify_flags(self, value: int) -> None: + self._verify_flags = value + self._ctx.get_cert_store().set_flags(self._verify_flags) + + @property + def verify_mode(self) -> int: + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value: ssl.VerifyMode) -> None: + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) + + def set_default_verify_paths(self) -> None: + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers: bytes | str) -> None: + if isinstance(ciphers, str): + ciphers = ciphers.encode("utf-8") + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations( + self, + cafile: str | None = None, + capath: str | None = None, + cadata: bytes | None = None, + ) -> None: + if cafile is not None: + cafile = cafile.encode("utf-8") # type: ignore[assignment] + if capath is not None: + capath = capath.encode("utf-8") # type: ignore[assignment] + try: + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"unable to load trusted certificates: {e!r}") from e + + def load_cert_chain( + self, + certfile: str, + keyfile: str | None = None, + password: str | None = None, + ) -> None: + try: + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, bytes): + password = password.encode("utf-8") # type: ignore[assignment] + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"Unable to load certificate chain: {e!r}") from e + + def set_alpn_protocols(self, protocols: list[bytes | str]) -> None: + protocols = [util.util.to_bytes(p, "ascii") for p in protocols] + return self._ctx.set_alpn_protos(protocols) # type: ignore[no-any-return] + + def wrap_socket( + self, + sock: socket_cls, + server_side: bool = False, + do_handshake_on_connect: bool = True, + suppress_ragged_eofs: bool = True, + server_hostname: bytes | str | None = None, + ) -> WrappedSocket: + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + # If server_hostname is an IP, don't use it for SNI, per RFC6066 Section 3 + if server_hostname and not util.ssl_.is_ipaddress(server_hostname): + if isinstance(server_hostname, str): + server_hostname = server_hostname.encode("utf-8") + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(sock, sock.gettimeout()): + raise TimeoutError("select timed out") from e + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"bad handshake: {e!r}") from e + break + + return WrappedSocket(cnx, sock) + + def _set_ctx_options(self) -> None: + self._ctx.set_options( + self._options + | _openssl_to_ssl_minimum_version[self._minimum_version] + | _openssl_to_ssl_maximum_version[self._maximum_version] + ) + + @property + def minimum_version(self) -> int: + return self._minimum_version + + @minimum_version.setter + def minimum_version(self, minimum_version: int) -> None: + self._minimum_version = minimum_version + self._set_ctx_options() + + @property + def maximum_version(self) -> int: + return self._maximum_version + + @maximum_version.setter + def maximum_version(self, maximum_version: int) -> None: + self._maximum_version = maximum_version + self._set_ctx_options() + + +def _verify_callback( + cnx: OpenSSL.SSL.Connection, + x509: X509, + err_no: int, + err_depth: int, + return_code: int, +) -> bool: + return err_no == 0 diff --git a/.venv/lib/python3.14/site-packages/urllib3/contrib/socks.py b/.venv/lib/python3.14/site-packages/urllib3/contrib/socks.py new file mode 100644 index 0000000000000000000000000000000000000000..d37da8fc2049a7b3f7e18d2f64e79c205cfe3d3e --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/contrib/socks.py @@ -0,0 +1,228 @@ +""" +This module contains provisional support for SOCKS proxies from within +urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and +SOCKS5. To enable its functionality, either install PySocks or install this +module with the ``socks`` extra. + +The SOCKS implementation supports the full range of urllib3 features. It also +supports the following SOCKS features: + +- SOCKS4A (``proxy_url='socks4a://...``) +- SOCKS4 (``proxy_url='socks4://...``) +- SOCKS5 with remote DNS (``proxy_url='socks5h://...``) +- SOCKS5 with local DNS (``proxy_url='socks5://...``) +- Usernames and passwords for the SOCKS proxy + +.. note:: + It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in + your ``proxy_url`` to ensure that DNS resolution is done from the remote + server instead of client-side when connecting to a domain name. + +SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 +supports IPv4, IPv6, and domain names. + +When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` +will be sent as the ``userid`` section of the SOCKS request: + +.. code-block:: python + + proxy_url="socks4a://@proxy-host" + +When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion +of the ``proxy_url`` will be sent as the username/password to authenticate +with the proxy: + +.. code-block:: python + + proxy_url="socks5h://:@proxy-host" + +""" + +from __future__ import annotations + +try: + import socks # type: ignore[import-untyped] +except ImportError: + import warnings + + from ..exceptions import DependencyWarning + + warnings.warn( + ( + "SOCKS support in urllib3 requires the installation of optional " + "dependencies: specifically, PySocks. For more information, see " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html#socks-proxies" + ), + DependencyWarning, + ) + raise + +import typing +from socket import timeout as SocketTimeout + +from ..connection import HTTPConnection, HTTPSConnection +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from ..exceptions import ConnectTimeoutError, NewConnectionError +from ..poolmanager import PoolManager +from ..util.url import parse_url + +try: + import ssl +except ImportError: + ssl = None # type: ignore[assignment] + + +class _TYPE_SOCKS_OPTIONS(typing.TypedDict): + socks_version: int + proxy_host: str | None + proxy_port: str | None + username: str | None + password: str | None + rdns: bool + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + + def __init__( + self, + _socks_options: _TYPE_SOCKS_OPTIONS, + *args: typing.Any, + **kwargs: typing.Any, + ) -> None: + self._socks_options = _socks_options + super().__init__(*args, **kwargs) + + def _new_conn(self) -> socks.socksocket: + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw: dict[str, typing.Any] = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = socks.create_connection( + (self.host, self.port), + proxy_type=self._socks_options["socks_version"], + proxy_addr=self._socks_options["proxy_host"], + proxy_port=self._socks_options["proxy_port"], + proxy_username=self._socks_options["username"], + proxy_password=self._socks_options["password"], + proxy_rdns=self._socks_options["rdns"], + timeout=self.timeout, + **extra_kw, + ) + + except SocketTimeout as e: + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + + except socks.ProxyError as e: + # This is fragile as hell, but it seems to be the only way to raise + # useful errors here. + if e.socket_err: + error = e.socket_err + if isinstance(error, SocketTimeout): + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + else: + # Adding `from e` messes with coverage somehow, so it's omitted. + # See #2386. + raise NewConnectionError( + self, f"Failed to establish a new connection: {error}" + ) + else: # Defensive: see https://github.com/urllib3/urllib3/pull/3728#pullrequestreview-3816302703 + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + except OSError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + return conn + + +# We don't need to duplicate the Verified/Unverified distinction from +# urllib3/connection.py here because the HTTPSConnection will already have been +# correctly set to either the Verified or Unverified form by that module. This +# means the SOCKSHTTPSConnection will automatically be the correct type. +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): + pass + + +class SOCKSHTTPConnectionPool(HTTPConnectionPool): + ConnectionCls = SOCKSConnection + + +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): + ConnectionCls = SOCKSHTTPSConnection + + +class SOCKSProxyManager(PoolManager): + """ + A version of the urllib3 ProxyManager that routes connections via the + defined SOCKS proxy. + """ + + pool_classes_by_scheme = { + "http": SOCKSHTTPConnectionPool, + "https": SOCKSHTTPSConnectionPool, + } + + def __init__( + self, + proxy_url: str, + username: str | None = None, + password: str | None = None, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + **connection_pool_kw: typing.Any, + ): + parsed = parse_url(proxy_url) + + if username is None and password is None and parsed.auth is not None: + split = parsed.auth.split(":") + if len(split) == 2: + username, password = split + if parsed.scheme == "socks5": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = False + elif parsed.scheme == "socks5h": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = True + elif parsed.scheme == "socks4": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = False + elif parsed.scheme == "socks4a": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = True + else: + raise ValueError(f"Unable to determine SOCKS version from {proxy_url}") + + self.proxy_url = proxy_url + + socks_options = { + "socks_version": socks_version, + "proxy_host": parsed.host, + "proxy_port": parsed.port, + "username": username, + "password": password, + "rdns": rdns, + } + connection_pool_kw["_socks_options"] = socks_options + + super().__init__(num_pools, headers, **connection_pool_kw) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/.venv/lib/python3.14/site-packages/urllib3/exceptions.py b/.venv/lib/python3.14/site-packages/urllib3/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..3d7e9b93d307ffe7d54868122227a165cc98383c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/exceptions.py @@ -0,0 +1,335 @@ +from __future__ import annotations + +import socket +import typing +import warnings +from email.errors import MessageDefect +from http.client import IncompleteRead as httplib_IncompleteRead + +if typing.TYPE_CHECKING: + from .connection import HTTPConnection + from .connectionpool import ConnectionPool + from .response import HTTPResponse + from .util.retry import Retry + +# Base Exceptions + + +class HTTPError(Exception): + """Base exception used by this module.""" + + +class HTTPWarning(Warning): + """Base warning used by this module.""" + + +_TYPE_REDUCE_RESULT = tuple[typing.Callable[..., object], tuple[object, ...]] + + +class PoolError(HTTPError): + """Base exception for errors caused within a pool.""" + + def __init__(self, pool: ConnectionPool, message: str) -> None: + self.pool = pool + self._message = message + super().__init__(f"{pool}: {message}") + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self._message) + + +class RequestError(PoolError): + """Base exception for PoolErrors that have associated URLs.""" + + def __init__(self, pool: ConnectionPool, url: str | None, message: str) -> None: + self.url = url + super().__init__(pool, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self.url, self._message) + + +class SSLError(HTTPError): + """Raised when SSL certificate fails in an HTTPS connection.""" + + +class ProxyError(HTTPError): + """Raised when the connection to a proxy fails.""" + + # The original error is also available as __cause__. + original_error: Exception + + def __init__(self, message: str, error: Exception) -> None: + super().__init__(message, error) + self.original_error = error + + +class DecodeError(HTTPError): + """Raised when automatic decoding based on Content-Type fails.""" + + +class ProtocolError(HTTPError): + """Raised when something unexpected happens mid-request/response.""" + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param str url: The requested Url + :param reason: The underlying error + :type reason: :class:`Exception` + + """ + + def __init__( + self, pool: ConnectionPool, url: str | None, reason: Exception | None = None + ) -> None: + self.reason = reason + + message = f"Max retries exceeded with url: {url} (Caused by {reason!r})" + + super().__init__(pool, url, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self.url, self.reason) + + +class HostChangedError(RequestError): + """Raised when an existing pool gets a request for a foreign host.""" + + def __init__( + self, pool: ConnectionPool, url: str, retries: Retry | int = 3 + ) -> None: + message = f"Tried to open a foreign host with url: {url}" + super().__init__(pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """Raised when passing an invalid state to a timeout""" + + +class TimeoutError(HTTPError): + """Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + ` and :exc:`ConnectTimeoutErrors `. + """ + + +class ReadTimeoutError(TimeoutError, RequestError): + """Raised when a socket timeout occurs while receiving data from a server""" + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + """Raised when a socket timeout occurs while connecting to a server""" + + +class NewConnectionError(ConnectTimeoutError, HTTPError): + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" + + def __init__(self, conn: HTTPConnection, message: str) -> None: + self.conn = conn + self._message = message + super().__init__(f"{conn}: {message}") + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self._message) + + @property + def pool(self) -> HTTPConnection: + warnings.warn( + "The 'pool' property is deprecated and will be removed " + "in urllib3 v3.0. Use 'conn' instead.", + FutureWarning, + stacklevel=2, + ) + + return self.conn + + +class NameResolutionError(NewConnectionError): + """Raised when host name resolution fails.""" + + def __init__(self, host: str, conn: HTTPConnection, reason: socket.gaierror): + message = f"Failed to resolve '{host}' ({reason})" + self._host = host + self._reason = reason + super().__init__(conn, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (self._host, None, self._reason) + + +class EmptyPoolError(PoolError): + """Raised when a pool runs out of connections and no more are allowed.""" + + +class FullPoolError(PoolError): + """Raised when we try to add a connection to a full pool in blocking mode.""" + + +class ClosedPoolError(PoolError): + """Raised when a request enters a pool after the pool has been closed.""" + + +class LocationValueError(ValueError, HTTPError): + """Raised when there is something wrong with a given URL input.""" + + +class LocationParseError(LocationValueError): + """Raised when get_host or similar fails to parse the URL input.""" + + def __init__(self, location: str) -> None: + message = f"Failed to parse: {location}" + super().__init__(message) + + self.location = location + + +class URLSchemeUnknown(LocationValueError): + """Raised when a URL input has an unsupported scheme.""" + + def __init__(self, scheme: str): + message = f"Not supported URL scheme {scheme}" + super().__init__(message) + + self.scheme = scheme + + +class ResponseError(HTTPError): + """Used as a container for an error reason supplied in a MaxRetryError.""" + + GENERIC_ERROR = "too many error responses" + SPECIFIC_ERROR = "too many {status_code} error responses" + + +class SecurityWarning(HTTPWarning): + """Warned when performing security reducing actions""" + + +class InsecureRequestWarning(SecurityWarning): + """Warned when making an unverified HTTPS request.""" + + +class NotOpenSSLWarning(SecurityWarning): + """Warned when using unsupported SSL library""" + + +class SystemTimeWarning(SecurityWarning): + """Warned when system time is suspected to be wrong""" + + +class InsecurePlatformWarning(SecurityWarning): + """Warned when certain TLS/SSL configuration is not available on a platform.""" + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + + +class ResponseNotChunked(ProtocolError, ValueError): + """Response needs to be chunked in order to read it as chunks.""" + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be :class:`http.client.HTTPResponse` like + (have an fp attribute which returns raw chunks) for read_chunked(). + """ + + +class IncompleteRead(HTTPError, httplib_IncompleteRead): + """ + Response length doesn't match expected Content-Length + + Subclass of :class:`http.client.IncompleteRead` to allow int value + for ``partial`` to avoid creating large objects on streamed reads. + """ + + partial: int # type: ignore[assignment] + expected: int + + def __init__(self, partial: int, expected: int) -> None: + self.partial = partial + self.expected = expected + + def __repr__(self) -> str: + return "IncompleteRead(%i bytes read, %i more expected)" % ( + self.partial, + self.expected, + ) + + +class InvalidChunkLength(HTTPError, httplib_IncompleteRead): + """Invalid chunk length in a chunked response.""" + + def __init__(self, response: HTTPResponse, length: bytes) -> None: + self.partial: int = response.tell() # type: ignore[assignment] + self.expected: int | None = response.length_remaining + self.response = response + self.length = length + + def __repr__(self) -> str: + return "InvalidChunkLength(got length %r, %i bytes read)" % ( + self.length, + self.partial, + ) + + +class InvalidHeader(HTTPError): + """The header provided was somehow invalid.""" + + +class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): + """ProxyManager does not support the supplied scheme""" + + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme: str | None) -> None: + # 'localhost' is here because our URL parser parses + # localhost:8080 -> scheme=localhost, remove if we fix this. + if scheme == "localhost": + scheme = None + if scheme is None: + message = "Proxy URL had no scheme, should start with http:// or https://" + else: + message = f"Proxy URL had unsupported scheme {scheme}, should use http:// or https://" + super().__init__(message) + + +class ProxySchemeUnsupported(ValueError): + """Fetching HTTPS resources through HTTPS proxies is unsupported""" + + +class HeaderParsingError(HTTPError): + """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" + + def __init__( + self, defects: list[MessageDefect], unparsed_data: bytes | str | None + ) -> None: + message = f"{defects or 'Unknown'}, unparsed data: {unparsed_data!r}" + super().__init__(message) + + +class UnrewindableBodyError(HTTPError): + """urllib3 encountered an error when trying to rewind a body""" diff --git a/.venv/lib/python3.14/site-packages/urllib3/fields.py b/.venv/lib/python3.14/site-packages/urllib3/fields.py new file mode 100644 index 0000000000000000000000000000000000000000..fe68e177324bc67f145091e464556a69b89bcda7 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/fields.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import email.utils +import mimetypes +import typing + +_TYPE_FIELD_VALUE = typing.Union[str, bytes] +_TYPE_FIELD_VALUE_TUPLE = typing.Union[ + _TYPE_FIELD_VALUE, + tuple[str, _TYPE_FIELD_VALUE], + tuple[str, _TYPE_FIELD_VALUE, str], +] + + +def guess_content_type( + filename: str | None, default: str = "application/octet-stream" +) -> str: + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Helper function to format and quote a single header parameter using the + strategy defined in RFC 2231. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows + `RFC 2388 Section 4.4 `_. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :returns: + An RFC-2231-formatted unicode string. + + .. deprecated:: 2.0.0 + Will be removed in urllib3 v3.0. This is not valid for + ``multipart/form-data`` header parameters. + """ + import warnings + + warnings.warn( + "'format_header_param_rfc2231' is insecure, deprecated and will be " + "removed in urllib3 v3.0. This is not valid for " + "multipart/form-data header parameters.", + FutureWarning, + stacklevel=2, + ) + + if isinstance(value, bytes): + value = value.decode("utf-8") + + if not any(ch in value for ch in '"\\\r\n'): + result = f'{name}="{value}"' + try: + result.encode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + + value = email.utils.encode_rfc2231(value, "utf-8") + value = f"{name}*={value}" + + return value + + +def format_multipart_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Format and quote a single multipart header parameter. + + This follows the `WHATWG HTML Standard`_ as of 2021/06/10, matching + the behavior of current browser and curl versions. Values are + assumed to be UTF-8. The ``\\n``, ``\\r``, and ``"`` characters are + percent encoded. + + .. _WHATWG HTML Standard: + https://html.spec.whatwg.org/multipage/ + form-control-infrastructure.html#multipart-form-data + + :param name: + The name of the parameter, an ASCII-only ``str``. + :param value: + The value of the parameter, a ``str`` or UTF-8 encoded + ``bytes``. + :returns: + A string ``name="value"`` with the escaped value. + + .. versionchanged:: 2.0.0 + Matches the WHATWG HTML Standard as of 2021/06/10. Control + characters are no longer percent encoded. + + .. versionchanged:: 2.0.0 + Renamed from ``format_header_param_html5`` and + ``format_header_param``. The old names will be removed in + urllib3 v3.0. + """ + if isinstance(value, bytes): + value = value.decode("utf-8") + + # percent encode \n \r " + value = value.translate({10: "%0A", 13: "%0D", 34: "%22"}) + return f'{name}="{value}"' + + +def format_header_param_html5(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + .. deprecated:: 2.0.0 + Renamed to :func:`format_multipart_header_param`. Will be + removed in urllib3 v3.0. + """ + import warnings + + warnings.warn( + "'format_header_param_html5' has been renamed to " + "'format_multipart_header_param'. The old name will be " + "removed in urllib3 v3.0.", + FutureWarning, + stacklevel=2, + ) + return format_multipart_header_param(name, value) + + +def format_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + .. deprecated:: 2.0.0 + Renamed to :func:`format_multipart_header_param`. Will be + removed in urllib3 v3.0. + """ + import warnings + + warnings.warn( + "'format_header_param' has been renamed to " + "'format_multipart_header_param'. The old name will be " + "removed in urllib3 v3.0.", + FutureWarning, + stacklevel=2, + ) + return format_multipart_header_param(name, value) + + +class RequestField: + """ + A data container for request body parameters. + + :param name: + The name of this request field. Must be unicode. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. Must be unicode. + :param headers: + An optional dict-like object of headers to initially use for the field. + + .. versionchanged:: 2.0.0 + The ``header_formatter`` parameter is deprecated and will + be removed in urllib3 v3.0. + """ + + def __init__( + self, + name: str, + data: _TYPE_FIELD_VALUE, + filename: str | None = None, + headers: typing.Mapping[str, str] | None = None, + header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + ): + self._name = name + self._filename = filename + self.data = data + self.headers: dict[str, str | None] = {} + if headers: + self.headers = dict(headers) + + if header_formatter is not None: + import warnings + + warnings.warn( + "The 'header_formatter' parameter is deprecated and " + "will be removed in urllib3 v3.0.", + FutureWarning, + stacklevel=2, + ) + self.header_formatter = header_formatter + else: + self.header_formatter = format_multipart_header_param + + @classmethod + def from_tuples( + cls, + fieldname: str, + value: _TYPE_FIELD_VALUE_TUPLE, + header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + ) -> RequestField: + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + filename: str | None + content_type: str | None + data: _TYPE_FIELD_VALUE + + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls( + fieldname, data, filename=filename, header_formatter=header_formatter + ) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Override this method to change how each multipart header + parameter is formatted. By default, this calls + :func:`format_multipart_header_param`. + + :param name: + The name of the parameter, an ASCII-only ``str``. + :param value: + The value of the parameter, a ``str`` or UTF-8 encoded + ``bytes``. + + :meta public: + """ + return self.header_formatter(name, value) + + def _render_parts( + self, + header_parts: ( + dict[str, _TYPE_FIELD_VALUE | None] + | typing.Sequence[tuple[str, _TYPE_FIELD_VALUE | None]] + ), + ) -> str: + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + iterable: typing.Iterable[tuple[str, _TYPE_FIELD_VALUE | None]] + + parts = [] + if isinstance(header_parts, dict): + iterable = header_parts.items() + else: + iterable = header_parts + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return "; ".join(parts) + + def render_headers(self) -> str: + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append(f"{sort_key}: {self.headers[sort_key]}") + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append(f"{header_name}: {header_value}") + + lines.append("\r\n") + return "\r\n".join(lines) + + def make_multipart( + self, + content_disposition: str | None = None, + content_type: str | None = None, + content_location: str | None = None, + ) -> None: + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_disposition: + The 'Content-Disposition' of the request body. Defaults to 'form-data' + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + content_disposition = (content_disposition or "form-data") + "; ".join( + [ + "", + self._render_parts( + (("name", self._name), ("filename", self._filename)) + ), + ] + ) + + self.headers["Content-Disposition"] = content_disposition + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/.venv/lib/python3.14/site-packages/urllib3/filepost.py b/.venv/lib/python3.14/site-packages/urllib3/filepost.py new file mode 100644 index 0000000000000000000000000000000000000000..14f70b05b4778f91137e4a9e7059d7514aa44d28 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/filepost.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import binascii +import codecs +import os +import typing +from io import BytesIO + +from .fields import _TYPE_FIELD_VALUE_TUPLE, RequestField + +writer = codecs.lookup("utf-8")[3] + +_TYPE_FIELDS_SEQUENCE = typing.Sequence[ + typing.Union[tuple[str, _TYPE_FIELD_VALUE_TUPLE], RequestField] +] +_TYPE_FIELDS = typing.Union[ + _TYPE_FIELDS_SEQUENCE, + typing.Mapping[str, _TYPE_FIELD_VALUE_TUPLE], +] + + +def choose_boundary() -> str: + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + return binascii.hexlify(os.urandom(16)).decode() + + +def iter_field_objects(fields: _TYPE_FIELDS) -> typing.Iterable[RequestField]: + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + iterable: typing.Iterable[RequestField | tuple[str, _TYPE_FIELD_VALUE_TUPLE]] + + if isinstance(fields, typing.Mapping): + iterable = fields.items() + else: + iterable = fields + + for field in iterable: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def encode_multipart_formdata( + fields: _TYPE_FIELDS, boundary: str | None = None +) -> tuple[bytes, str]: + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + Values are processed by :func:`urllib3.fields.RequestField.from_tuples`. + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(f"--{boundary}\r\n".encode("latin-1")) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, str): + writer(body).write(data) + else: + body.write(data) + + body.write(b"\r\n") + + body.write(f"--{boundary}--\r\n".encode("latin-1")) + + content_type = f"multipart/form-data; boundary={boundary}" + + return body.getvalue(), content_type diff --git a/.venv/lib/python3.14/site-packages/urllib3/http2/__init__.py b/.venv/lib/python3.14/site-packages/urllib3/http2/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..133e1d8f237f6fddd557ae1c0e0cf738f7cc2748 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/http2/__init__.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from importlib.metadata import version + +__all__ = [ + "inject_into_urllib3", + "extract_from_urllib3", +] + +import typing + +orig_HTTPSConnection: typing.Any = None + + +def inject_into_urllib3() -> None: + # First check if h2 version is valid + h2_version = version("h2") + if not h2_version.startswith("4."): + raise ImportError( + "urllib3 v2 supports h2 version 4.x.x, currently " + f"the 'h2' module is compiled with {h2_version!r}. " + "See: https://github.com/urllib3/urllib3/issues/3290" + ) + + # Import here to avoid circular dependencies. + from .. import connection as urllib3_connection + from .. import util as urllib3_util + from ..connectionpool import HTTPSConnectionPool + from ..util import ssl_ as urllib3_util_ssl + from .connection import HTTP2Connection + + global orig_HTTPSConnection + orig_HTTPSConnection = urllib3_connection.HTTPSConnection + + HTTPSConnectionPool.ConnectionCls = HTTP2Connection + urllib3_connection.HTTPSConnection = HTTP2Connection # type: ignore[misc] + + # TODO: Offer 'http/1.1' as well, but for testing purposes this is handy. + urllib3_util.ALPN_PROTOCOLS = ["h2"] + urllib3_util_ssl.ALPN_PROTOCOLS = ["h2"] + + +def extract_from_urllib3() -> None: + from .. import connection as urllib3_connection + from .. import util as urllib3_util + from ..connectionpool import HTTPSConnectionPool + from ..util import ssl_ as urllib3_util_ssl + + HTTPSConnectionPool.ConnectionCls = orig_HTTPSConnection + urllib3_connection.HTTPSConnection = orig_HTTPSConnection # type: ignore[misc] + + urllib3_util.ALPN_PROTOCOLS = ["http/1.1"] + urllib3_util_ssl.ALPN_PROTOCOLS = ["http/1.1"] diff --git a/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d5b4e5d52ebb93dbf2c9d550d26224b6fcc0f8e Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/connection.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d308b322c92505676a3f1185809c59f5ee92b6ff Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/connection.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/probe.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/probe.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ae01901017f2f3cfc3e07dff1e48d3e164ea930 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/probe.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/http2/connection.py b/.venv/lib/python3.14/site-packages/urllib3/http2/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..0a026da0a8357e324ded47b82b24042713b9bf06 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/http2/connection.py @@ -0,0 +1,356 @@ +from __future__ import annotations + +import logging +import re +import threading +import types +import typing + +import h2.config +import h2.connection +import h2.events + +from .._base_connection import _TYPE_BODY +from .._collections import HTTPHeaderDict +from ..connection import HTTPSConnection, _get_default_user_agent +from ..exceptions import ConnectionError +from ..response import BaseHTTPResponse + +orig_HTTPSConnection = HTTPSConnection + +T = typing.TypeVar("T") + +log = logging.getLogger(__name__) + +RE_IS_LEGAL_HEADER_NAME = re.compile(rb"^[!#$%&'*+\-.^_`|~0-9a-z]+$") +RE_IS_ILLEGAL_HEADER_VALUE = re.compile(rb"[\0\x00\x0a\x0d\r\n]|^[ \r\n\t]|[ \r\n\t]$") + + +def _is_legal_header_name(name: bytes) -> bool: + """ + "An implementation that validates fields according to the definitions in Sections + 5.1 and 5.5 of [HTTP] only needs an additional check that field names do not + include uppercase characters." (https://httpwg.org/specs/rfc9113.html#n-field-validity) + + `http.client._is_legal_header_name` does not validate the field name according to the + HTTP 1.1 spec, so we do that here, in addition to checking for uppercase characters. + + This does not allow for the `:` character in the header name, so should not + be used to validate pseudo-headers. + """ + return bool(RE_IS_LEGAL_HEADER_NAME.match(name)) + + +def _is_illegal_header_value(value: bytes) -> bool: + """ + "A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed + (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position. A field + value MUST NOT start or end with an ASCII whitespace character (ASCII SP or HTAB, + 0x20 or 0x09)." (https://httpwg.org/specs/rfc9113.html#n-field-validity) + """ + return bool(RE_IS_ILLEGAL_HEADER_VALUE.search(value)) + + +class _LockedObject(typing.Generic[T]): + """ + A wrapper class that hides a specific object behind a lock. + The goal here is to provide a simple way to protect access to an object + that cannot safely be simultaneously accessed from multiple threads. The + intended use of this class is simple: take hold of it with a context + manager, which returns the protected object. + """ + + __slots__ = ( + "lock", + "_obj", + ) + + def __init__(self, obj: T): + self.lock = threading.RLock() + self._obj = obj + + def __enter__(self) -> T: + self.lock.acquire() + return self._obj + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + self.lock.release() + + +class HTTP2Connection(HTTPSConnection): + def __init__( + self, host: str, port: int | None = None, **kwargs: typing.Any + ) -> None: + self._h2_conn = self._new_h2_conn() + self._h2_stream: int | None = None + self._headers: list[tuple[bytes, bytes]] = [] + + if "proxy" in kwargs or "proxy_config" in kwargs: # Defensive: + raise NotImplementedError("Proxies aren't supported with HTTP/2") + + super().__init__(host, port, **kwargs) + + if self._tunnel_host is not None: + raise NotImplementedError("Tunneling isn't supported with HTTP/2") + + def _new_h2_conn(self) -> _LockedObject[h2.connection.H2Connection]: + config = h2.config.H2Configuration(client_side=True) + return _LockedObject(h2.connection.H2Connection(config=config)) + + def connect(self) -> None: + super().connect() + with self._h2_conn as conn: + conn.initiate_connection() + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + def putrequest( # type: ignore[override] + self, + method: str, + url: str, + **kwargs: typing.Any, + ) -> None: + """putrequest + This deviates from the HTTPConnection method signature since we never need to override + sending accept-encoding headers or the host header. + """ + if "skip_host" in kwargs: + raise NotImplementedError("`skip_host` isn't supported") + if "skip_accept_encoding" in kwargs: + raise NotImplementedError("`skip_accept_encoding` isn't supported") + + self._request_url = url or "/" + self._validate_path(url) # type: ignore[attr-defined] + + if ":" in self.host: + authority = f"[{self.host}]:{self.port or 443}" + else: + authority = f"{self.host}:{self.port or 443}" + + self._headers.append((b":scheme", b"https")) + self._headers.append((b":method", method.encode())) + self._headers.append((b":authority", authority.encode())) + self._headers.append((b":path", url.encode())) + + with self._h2_conn as conn: + self._h2_stream = conn.get_next_available_stream_id() + + def putheader(self, header: str | bytes, *values: str | bytes) -> None: # type: ignore[override] + # TODO SKIPPABLE_HEADERS from urllib3 are ignored. + header = header.encode() if isinstance(header, str) else header + header = header.lower() # A lot of upstream code uses capitalized headers. + if not _is_legal_header_name(header): + raise ValueError(f"Illegal header name {str(header)}") + + for value in values: + value = value.encode() if isinstance(value, str) else value + if _is_illegal_header_value(value): + raise ValueError(f"Illegal header value {str(value)}") + self._headers.append((header, value)) + + def endheaders(self, message_body: typing.Any = None) -> None: # type: ignore[override] + if self._h2_stream is None: + raise ConnectionError("Must call `putrequest` first.") + + with self._h2_conn as conn: + conn.send_headers( + stream_id=self._h2_stream, + headers=self._headers, + end_stream=(message_body is None), + ) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + self._headers = [] # Reset headers for the next request. + + def send(self, data: typing.Any) -> None: + """Send data to the server. + `data` can be: `str`, `bytes`, an iterable, or file-like objects + that support a .read() method. + """ + if self._h2_stream is None: + raise ConnectionError("Must call `putrequest` first.") + + with self._h2_conn as conn: + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + if hasattr(data, "read"): # file-like objects + while True: + chunk = data.read(self.blocksize) + if not chunk: + break + if isinstance(chunk, str): + chunk = chunk.encode() + conn.send_data(self._h2_stream, chunk, end_stream=False) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + conn.end_stream(self._h2_stream) + return + + if isinstance(data, str): # str -> bytes + data = data.encode() + + try: + if isinstance(data, bytes): + conn.send_data(self._h2_stream, data, end_stream=True) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + else: + for chunk in data: + conn.send_data(self._h2_stream, chunk, end_stream=False) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + conn.end_stream(self._h2_stream) + except TypeError: + raise TypeError( + "`data` should be str, bytes, iterable, or file. got %r" + % type(data) + ) + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + raise NotImplementedError( + "HTTP/2 does not support setting up a tunnel through a proxy" + ) + + def getresponse( # type: ignore[override] + self, + ) -> HTTP2Response: + status = None + data = bytearray() + with self._h2_conn as conn: + end_stream = False + while not end_stream: + # TODO: Arbitrary read value. + if received_data := self.sock.recv(65535): + events = conn.receive_data(received_data) + for event in events: + if isinstance(event, h2.events.ResponseReceived): + headers = HTTPHeaderDict() + for header, value in event.headers: + if header == b":status": + status = int(value.decode()) + else: + headers.add( + header.decode("ascii"), value.decode("ascii") + ) + + elif isinstance(event, h2.events.DataReceived): + data += event.data + conn.acknowledge_received_data( + event.flow_controlled_length, event.stream_id + ) + + elif isinstance(event, h2.events.StreamEnded): + end_stream = True + + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + assert status is not None + return HTTP2Response( + status=status, + headers=headers, + request_url=self._request_url, + data=bytes(data), + ) + + def request( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + *, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + **kwargs: typing.Any, + ) -> None: + """Send an HTTP/2 request""" + if "chunked" in kwargs: + # TODO this is often present from upstream. + # raise NotImplementedError("`chunked` isn't supported with HTTP/2") + pass + + if self.sock is not None: + self.sock.settimeout(self.timeout) + + self.putrequest(method, url) + + headers = headers or {} + for k, v in headers.items(): + if k.lower() == "transfer-encoding" and v == "chunked": + continue + else: + self.putheader(k, v) + + if b"user-agent" not in dict(self._headers): + self.putheader(b"user-agent", _get_default_user_agent()) + + if body: + self.endheaders(message_body=body) + self.send(body) + else: + self.endheaders() + + def close(self) -> None: + with self._h2_conn as conn: + try: + conn.close_connection() + if data := conn.data_to_send(): + self.sock.sendall(data) + except Exception: + pass + + # Reset all our HTTP/2 connection state. + self._h2_conn = self._new_h2_conn() + self._h2_stream = None + self._headers = [] + + super().close() + + +class HTTP2Response(BaseHTTPResponse): + # TODO: This is a woefully incomplete response object, but works for non-streaming. + def __init__( + self, + status: int, + headers: HTTPHeaderDict, + request_url: str, + data: bytes, + decode_content: bool = False, # TODO: support decoding + ) -> None: + super().__init__( + status=status, + headers=headers, + # Following CPython, we map HTTP versions to major * 10 + minor integers + version=20, + version_string="HTTP/2", + # No reason phrase in HTTP/2 + reason=None, + decode_content=decode_content, + request_url=request_url, + ) + self._data = data + self.length_remaining = 0 + + @property + def data(self) -> bytes: + return self._data + + def get_redirect_location(self) -> None: + return None + + def close(self) -> None: + pass diff --git a/.venv/lib/python3.14/site-packages/urllib3/http2/probe.py b/.venv/lib/python3.14/site-packages/urllib3/http2/probe.py new file mode 100644 index 0000000000000000000000000000000000000000..9ea900764f0885eafaac9454523417d86e33df2d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/http2/probe.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import threading + + +class _HTTP2ProbeCache: + __slots__ = ( + "_lock", + "_cache_locks", + "_cache_values", + ) + + def __init__(self) -> None: + self._lock = threading.Lock() + self._cache_locks: dict[tuple[str, int], threading.RLock] = {} + self._cache_values: dict[tuple[str, int], bool | None] = {} + + def acquire_and_get(self, host: str, port: int) -> bool | None: + # By the end of this block we know that + # _cache_[values,locks] is available. + value = None + with self._lock: + key = (host, port) + try: + value = self._cache_values[key] + # If it's a known value we return right away. + if value is not None: + return value + except KeyError: + self._cache_locks[key] = threading.RLock() + self._cache_values[key] = None + + # If the value is unknown, we acquire the lock to signal + # to the requesting thread that the probe is in progress + # or that the current thread needs to return their findings. + key_lock = self._cache_locks[key] + key_lock.acquire() + try: + # If the by the time we get the lock the value has been + # updated we want to return the updated value. + value = self._cache_values[key] + + # In case an exception like KeyboardInterrupt is raised here. + except BaseException as e: # Defensive: + assert not isinstance(e, KeyError) # KeyError shouldn't be possible. + key_lock.release() + raise + + return value + + def set_and_release( + self, host: str, port: int, supports_http2: bool | None + ) -> None: + key = (host, port) + key_lock = self._cache_locks[key] + with key_lock: # Uses an RLock, so can be locked again from same thread. + if supports_http2 is None and self._cache_values[key] is not None: + raise ValueError( + "Cannot reset HTTP/2 support for origin after value has been set." + ) # Defensive: not expected in normal usage + + self._cache_values[key] = supports_http2 + key_lock.release() + + def _values(self) -> dict[tuple[str, int], bool | None]: + """This function is for testing purposes only. Gets the current state of the probe cache""" + with self._lock: + return {k: v for k, v in self._cache_values.items()} + + def _reset(self) -> None: + """This function is for testing purposes only. Reset the cache values""" + with self._lock: + self._cache_locks = {} + self._cache_values = {} + + +_HTTP2_PROBE_CACHE = _HTTP2ProbeCache() + +set_and_release = _HTTP2_PROBE_CACHE.set_and_release +acquire_and_get = _HTTP2_PROBE_CACHE.acquire_and_get +_values = _HTTP2_PROBE_CACHE._values +_reset = _HTTP2_PROBE_CACHE._reset + +__all__ = [ + "set_and_release", + "acquire_and_get", +] diff --git a/.venv/lib/python3.14/site-packages/urllib3/poolmanager.py b/.venv/lib/python3.14/site-packages/urllib3/poolmanager.py new file mode 100644 index 0000000000000000000000000000000000000000..8f2c56745cdf5ee206767e22fd61c2fb6bc21e55 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/poolmanager.py @@ -0,0 +1,653 @@ +from __future__ import annotations + +import functools +import logging +import typing +import warnings +from types import TracebackType +from urllib.parse import urljoin + +from ._collections import HTTPHeaderDict, RecentlyUsedContainer +from ._request_methods import RequestMethods +from .connection import ProxyConfig +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme +from .exceptions import ( + LocationValueError, + MaxRetryError, + ProxySchemeUnknown, + URLSchemeUnknown, +) +from .response import BaseHTTPResponse +from .util.connection import _TYPE_SOCKET_OPTIONS +from .util.proxy import connection_requires_http_tunnel +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import Url, parse_url + +if typing.TYPE_CHECKING: + import ssl + + from typing_extensions import Self + +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ca_cert_data", + "ssl_version", + "ssl_minimum_version", + "ssl_maximum_version", + "ca_cert_dir", + "ssl_context", + "key_password", + "server_hostname", +) +# Default value for `blocksize` - a new parameter introduced to +# http.client.HTTPConnection & http.client.HTTPSConnection in Python 3.7 +_DEFAULT_BLOCKSIZE = 16384 + + +class PoolKey(typing.NamedTuple): + """ + All known keyword arguments that could be provided to the pool manager, its + pools, or the underlying connections. + + All custom key schemes should include the fields in this key at a minimum. + """ + + key_scheme: str + key_host: str + key_port: int | None + key_timeout: Timeout | float | int | None + key_retries: Retry | bool | int | None + key_block: bool | None + key_source_address: tuple[str, int] | None + key_key_file: str | None + key_key_password: str | None + key_cert_file: str | None + key_cert_reqs: str | None + key_ca_certs: str | None + key_ca_cert_data: str | bytes | None + key_ssl_version: int | str | None + key_ssl_minimum_version: ssl.TLSVersion | None + key_ssl_maximum_version: ssl.TLSVersion | None + key_ca_cert_dir: str | None + key_ssl_context: ssl.SSLContext | None + key_maxsize: int | None + key_headers: frozenset[tuple[str, str]] | None + key__proxy: Url | None + key__proxy_headers: frozenset[tuple[str, str]] | None + key__proxy_config: ProxyConfig | None + key_socket_options: _TYPE_SOCKET_OPTIONS | None + key__socks_options: frozenset[tuple[str, str]] | None + key_assert_hostname: bool | str | None + key_assert_fingerprint: str | None + key_server_hostname: str | None + key_blocksize: int | None + + +def _default_key_normalizer( + key_class: type[PoolKey], request_context: dict[str, typing.Any] +) -> PoolKey: + """ + Create a pool key out of a request context dictionary. + + According to RFC 3986, both the scheme and host are case-insensitive. + Therefore, this function normalizes both before constructing the pool + key for an HTTPS request. If you wish to change this behaviour, provide + alternate callables to ``key_fn_by_scheme``. + + :param key_class: + The class to use when constructing the key. This should be a namedtuple + with the ``scheme`` and ``host`` keys at a minimum. + :type key_class: namedtuple + :param request_context: + A dictionary-like object that contain the context for a request. + :type request_context: dict + + :return: A namedtuple that can be used as a connection pool key. + :rtype: PoolKey + """ + # Since we mutate the dictionary, make a copy first + context = request_context.copy() + context["scheme"] = context["scheme"].lower() + context["host"] = context["host"].lower() + + # These are both dictionaries and need to be transformed into frozensets + for key in ("headers", "_proxy_headers", "_socks_options"): + if key in context and context[key] is not None: + context[key] = frozenset(context[key].items()) + + # The socket_options key may be a list and needs to be transformed into a + # tuple. + socket_opts = context.get("socket_options") + if socket_opts is not None: + context["socket_options"] = tuple(socket_opts) + + # Map the kwargs to the names in the namedtuple - this is necessary since + # namedtuples can't have fields starting with '_'. + for key in list(context.keys()): + context["key_" + key] = context.pop(key) + + # Default to ``None`` for keys missing from the context + for field in key_class._fields: + if field not in context: + context[field] = None + + # Default key_blocksize to _DEFAULT_BLOCKSIZE if missing from the context + if context.get("key_blocksize") is None: + context["key_blocksize"] = _DEFAULT_BLOCKSIZE + + return key_class(**context) + + +#: A dictionary that maps a scheme to a callable that creates a pool key. +#: This can be used to alter the way pool keys are constructed, if desired. +#: Each PoolManager makes a copy of this dictionary so they can be configured +#: globally here, or individually on the instance. +key_fn_by_scheme = { + "http": functools.partial(_default_key_normalizer, PoolKey), + "https": functools.partial(_default_key_normalizer, PoolKey), +} + +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \\**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example: + + .. code-block:: python + + import urllib3 + + http = urllib3.PoolManager(num_pools=2) + + resp1 = http.request("GET", "https://google.com/") + resp2 = http.request("GET", "https://google.com/mail") + resp3 = http.request("GET", "https://yahoo.com/") + + print(len(http.pools)) + # 2 + + """ + + proxy: Url | None = None + proxy_config: ProxyConfig | None = None + + def __init__( + self, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + **connection_pool_kw: typing.Any, + ) -> None: + super().__init__(headers) + # PoolManager handles redirects itself in PoolManager.urlopen(). + # It always passes redirect=False to the underlying connection pool to + # suppress per-pool redirect handling. If the user supplied a non-Retry + # value (int/bool/etc) for retries and we let the pool normalize it + # while redirect=False, the resulting Retry object would have redirect + # handling disabled, which can interfere with PoolManager's own + # redirect logic. Normalize here so redirects remain governed solely by + # PoolManager logic. + if "retries" in connection_pool_kw: + retries = connection_pool_kw["retries"] + if not isinstance(retries, Retry): + retries = Retry.from_int(retries) + connection_pool_kw = connection_pool_kw.copy() + connection_pool_kw["retries"] = retries + self.connection_pool_kw = connection_pool_kw + + self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool] + self.pools = RecentlyUsedContainer(num_pools) + + # Locally set the pool classes and keys so other PoolManagers can + # override them. + self.pool_classes_by_scheme = pool_classes_by_scheme + self.key_fn_by_scheme = key_fn_by_scheme.copy() + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> typing.Literal[False]: + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool( + self, + scheme: str, + host: str, + port: int, + request_context: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + """ + Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and + any additional pool keyword arguments. + + If ``request_context`` is provided, it is provided as keyword arguments + to the pool class used. This method is used to actually create the + connection pools handed out by :meth:`connection_from_url` and + companion methods. It is intended to be overridden for customization. + """ + pool_cls: type[HTTPConnectionPool] = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Default blocksize to _DEFAULT_BLOCKSIZE if missing or explicitly + # set to 'None' in the request_context. + if request_context.get("blocksize") is None: + request_context["blocksize"] = _DEFAULT_BLOCKSIZE + + # Although the context has everything necessary to create the pool, + # this function has historically only used the scheme, host, and port + # in the positional args. When an API change is acceptable these can + # be removed. + for key in ("scheme", "host", "port"): + request_context.pop(key, None) + + if scheme == "http": + for kw in SSL_KEYWORDS: + request_context.pop(kw, None) + + return pool_cls(host, port, **request_context) + + def clear(self) -> None: + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host( + self, + host: str | None, + port: int | None = None, + scheme: str | None = "http", + pool_kwargs: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is + provided, it is merged with the instance's ``connection_pool_kw`` + variable and used to create the new connection pool, if one is + needed. + """ + + if not host: + raise LocationValueError("No host specified.") + + request_context = self._merge_pool_kwargs(pool_kwargs) + request_context["scheme"] = scheme or "http" + if not port: + port = port_by_scheme.get(request_context["scheme"].lower(), 80) + request_context["port"] = port + request_context["host"] = host + + return self.connection_from_context(request_context) + + def connection_from_context( + self, request_context: dict[str, typing.Any] + ) -> HTTPConnectionPool: + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. + + ``request_context`` must at least contain the ``scheme`` key and its + value must be a key in ``key_fn_by_scheme`` instance variable. + """ + if "strict" in request_context: + warnings.warn( + "The 'strict' parameter is no longer needed on Python 3+. " + "This will raise an error in urllib3 v3.0.", + FutureWarning, + ) + request_context.pop("strict") + + scheme = request_context["scheme"].lower() + pool_key_constructor = self.key_fn_by_scheme.get(scheme) + if not pool_key_constructor: + raise URLSchemeUnknown(scheme) + pool_key = pool_key_constructor(request_context) + + return self.connection_from_pool_key(pool_key, request_context=request_context) + + def connection_from_pool_key( + self, pool_key: PoolKey, request_context: dict[str, typing.Any] + ) -> HTTPConnectionPool: + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. + + ``pool_key`` should be a namedtuple that only contains immutable + objects. At a minimum it must have the ``scheme``, ``host``, and + ``port`` fields. + """ + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + scheme = request_context["scheme"] + host = request_context["host"] + port = request_context["port"] + pool = self._new_pool(scheme, host, port, request_context=request_context) + self.pools[pool_key] = pool + + return pool + + def connection_from_url( + self, url: str, pool_kwargs: dict[str, typing.Any] | None = None + ) -> HTTPConnectionPool: + """ + Similar to :func:`urllib3.connectionpool.connection_from_url`. + + If ``pool_kwargs`` is not provided and a new pool needs to be + constructed, ``self.connection_pool_kw`` is used to initialize + the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` + is provided, it is used instead. Note that if a new pool does not + need to be created for the request, the provided ``pool_kwargs`` are + not used. + """ + u = parse_url(url) + return self.connection_from_host( + u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs + ) + + def _merge_pool_kwargs( + self, override: dict[str, typing.Any] | None + ) -> dict[str, typing.Any]: + """ + Merge a dictionary of override values for self.connection_pool_kw. + + This does not modify self.connection_pool_kw and returns a new dict. + Any keys in the override dictionary with a value of ``None`` are + removed from the merged dictionary. + """ + base_pool_kwargs = self.connection_pool_kw.copy() + if override: + for key, value in override.items(): + if value is None: + try: + del base_pool_kwargs[key] + except KeyError: + pass + else: + base_pool_kwargs[key] = value + return base_pool_kwargs + + def _proxy_requires_url_absolute_form(self, parsed_url: Url) -> bool: + """ + Indicates if the proxy requires the complete destination URL in the + request. Normally this is only needed when not using an HTTP CONNECT + tunnel. + """ + if self.proxy is None: + return False + + return not connection_requires_http_tunnel( + self.proxy, self.proxy_config, parsed_url.scheme + ) + + def urlopen( # type: ignore[override] + self, method: str, url: str, redirect: bool = True, **kw: typing.Any + ) -> BaseHTTPResponse: + """ + Same as :meth:`urllib3.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + + if u.scheme is None: + warnings.warn( + "URLs without a scheme (ie 'https://') are deprecated and will raise an error " + "in urllib3 v3.0. To avoid this FutureWarning ensure all URLs " + "start with 'https://' or 'http://'. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2920", + category=FutureWarning, + stacklevel=2, + ) + + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw["assert_same_host"] = False + kw["redirect"] = False + + if "headers" not in kw: + kw["headers"] = self.headers + + if self._proxy_requires_url_absolute_form(u): + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + if response.status == 303: + # Change the method according to RFC 9110, Section 15.4.4. + method = "GET" + # And lose the body not to transfer anything sensitive. + kw["body"] = None + kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() + + retries = kw.get("retries", response.retries) + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # conn.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not conn.is_same_host( + redirect_location + ): + new_headers = kw["headers"].copy() + for header in kw["headers"]: + if header.lower() in retries.remove_headers_on_redirect: + new_headers.pop(header, None) + kw["headers"] = new_headers + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + kw["retries"] = retries + kw["redirect"] = redirect + + log.info("Redirecting %s -> %s", url, redirect_location) + + response.drain_conn() + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary containing headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + :param proxy_ssl_context: + The proxy SSL context is used to establish the TLS connection to the + proxy when using HTTPS proxies. + + :param use_forwarding_for_https: + (Defaults to False) If set to True will forward requests to the HTTPS + proxy to be made on behalf of the client instead of creating a TLS + tunnel via the CONNECT method. **Enabling this flag means that request + and response headers and content will be visible from the HTTPS proxy** + whereas tunneling keeps request and response headers and content + private. IP address, target hostname, SNI, and port are always visible + to an HTTPS proxy even when this flag is disabled. + + :param proxy_assert_hostname: + The hostname of the certificate to verify against. + + :param proxy_assert_fingerprint: + The fingerprint of the certificate to verify against. + + Example: + + .. code-block:: python + + import urllib3 + + proxy = urllib3.ProxyManager("https://localhost:3128/") + + resp1 = proxy.request("GET", "http://google.com/") + resp2 = proxy.request("GET", "http://httpbin.org/") + + # One pool was shared by both plain HTTP requests. + print(len(proxy.pools)) + # 1 + + resp3 = proxy.request("GET", "https://httpbin.org/") + resp4 = proxy.request("GET", "https://twitter.com/") + + # A separate pool was added for each HTTPS target. + print(len(proxy.pools)) + # 3 + + """ + + def __init__( + self, + proxy_url: str, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + proxy_headers: typing.Mapping[str, str] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + use_forwarding_for_https: bool = False, + proxy_assert_hostname: None | str | typing.Literal[False] = None, + proxy_assert_fingerprint: str | None = None, + **connection_pool_kw: typing.Any, + ) -> None: + if isinstance(proxy_url, HTTPConnectionPool): + str_proxy_url = f"{proxy_url.scheme}://{proxy_url.host}:{proxy_url.port}" + else: + str_proxy_url = proxy_url + proxy = parse_url(str_proxy_url) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + self.proxy_ssl_context = proxy_ssl_context + self.proxy_config = ProxyConfig( + proxy_ssl_context, + use_forwarding_for_https, + proxy_assert_hostname, + proxy_assert_fingerprint, + ) + + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + connection_pool_kw["_proxy_config"] = self.proxy_config + + super().__init__(num_pools, headers, **connection_pool_kw) + + def connection_from_host( + self, + host: str | None, + port: int | None = None, + scheme: str | None = "http", + pool_kwargs: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + if scheme == "https": + return super().connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs + ) + + return super().connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs # type: ignore[union-attr] + ) + + def _set_proxy_headers( + self, url: str, headers: typing.Mapping[str, str] | None = None + ) -> typing.Mapping[str, str]: + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {"Accept": "*/*"} + + netloc = parse_url(url).netloc + if netloc: + headers_["Host"] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen( # type: ignore[override] + self, method: str, url: str, redirect: bool = True, **kw: typing.Any + ) -> BaseHTTPResponse: + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): + # For connections using HTTP CONNECT, httplib sets the necessary + # headers on the CONNECT to the proxy. If we're not using CONNECT, + # we'll definitely need to set 'Host' at the very least. + headers = kw.get("headers", self.headers) + kw["headers"] = self._set_proxy_headers(url, headers) + + return super().urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url: str, **kw: typing.Any) -> ProxyManager: + return ProxyManager(proxy_url=url, **kw) diff --git a/.venv/lib/python3.14/site-packages/urllib3/py.typed b/.venv/lib/python3.14/site-packages/urllib3/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..5f3ea3d919363f08ab03edbc85b6099bc4df5647 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/py.typed @@ -0,0 +1,2 @@ +# Instruct type checkers to look for inline type annotations in this package. +# See PEP 561. diff --git a/.venv/lib/python3.14/site-packages/urllib3/response.py b/.venv/lib/python3.14/site-packages/urllib3/response.py new file mode 100644 index 0000000000000000000000000000000000000000..e9246b75e36215b7f956700aa4cb363e8423e526 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/response.py @@ -0,0 +1,1493 @@ +from __future__ import annotations + +import collections +import io +import json as _json +import logging +import socket +import sys +import typing +import warnings +import zlib +from contextlib import contextmanager +from http.client import HTTPMessage as _HttplibHTTPMessage +from http.client import HTTPResponse as _HttplibHTTPResponse +from socket import timeout as SocketTimeout + +if typing.TYPE_CHECKING: + from ._base_connection import BaseHTTPConnection + +try: + try: + import brotlicffi as brotli # type: ignore[import-not-found] + except ImportError: + import brotli # type: ignore[import-not-found] +except ImportError: + brotli = None + +from . import util +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from .connection import BaseSSLError, HTTPConnection, HTTPException +from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, + DependencyWarning, + HTTPError, + IncompleteRead, + InvalidChunkLength, + InvalidHeader, + ProtocolError, + ReadTimeoutError, + ResponseNotChunked, + SSLError, +) +from .util.response import is_fp_closed, is_response_to_head +from .util.retry import Retry + +if typing.TYPE_CHECKING: + from .connectionpool import HTTPConnectionPool + +log = logging.getLogger(__name__) + + +class ContentDecoder: + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + raise NotImplementedError() + + @property + def has_unconsumed_tail(self) -> bool: + raise NotImplementedError() + + def flush(self) -> bytes: + raise NotImplementedError() + + +class DeflateDecoder(ContentDecoder): + def __init__(self) -> None: + self._first_try = True + self._first_try_data = b"" + self._unfed_data = b"" + self._obj = zlib.decompressobj() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + data = self._unfed_data + data + self._unfed_data = b"" + if not data and not self._obj.unconsumed_tail: + return data + original_max_length = max_length + if original_max_length < 0: + max_length = 0 + elif original_max_length == 0: + # We should not pass 0 to the zlib decompressor because 0 is + # the default value that will make zlib decompress without a + # length limit. + # Data should be stored for subsequent calls. + self._unfed_data = data + return b"" + + # Subsequent calls always reuse `self._obj`. zlib requires + # passing the unconsumed tail if decompression is to continue. + if not self._first_try: + return self._obj.decompress( + self._obj.unconsumed_tail + data, max_length=max_length + ) + + # First call tries with RFC 1950 ZLIB format. + self._first_try_data += data + try: + decompressed = self._obj.decompress(data, max_length=max_length) + if decompressed: + self._first_try = False + self._first_try_data = b"" + return decompressed + # On failure, it falls back to RFC 1951 DEFLATE format. + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress( + self._first_try_data, max_length=original_max_length + ) + finally: + self._first_try_data = b"" + + @property + def has_unconsumed_tail(self) -> bool: + return bool(self._unfed_data) or ( + bool(self._obj.unconsumed_tail) and not self._first_try + ) + + def flush(self) -> bytes: + return self._obj.flush() + + +class GzipDecoderState: + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(ContentDecoder): + def __init__(self) -> None: + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + self._unconsumed_tail = b"" + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA: + return bytes(ret) + + if max_length == 0: + # We should not pass 0 to the zlib decompressor because 0 is + # the default value that will make zlib decompress without a + # length limit. + # Data should be stored for subsequent calls. + self._unconsumed_tail += data + return b"" + + # zlib requires passing the unconsumed tail to the subsequent + # call if decompression is to continue. + data = self._unconsumed_tail + data + if not data and self._obj.eof: + return bytes(ret) + + while True: + try: + ret += self._obj.decompress( + data, max_length=max(max_length - len(ret), 0) + ) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + self._unconsumed_tail = b"" + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + + self._unconsumed_tail = data = ( + self._obj.unconsumed_tail or self._obj.unused_data + ) + if max_length > 0 and len(ret) >= max_length: + break + + if not data: + return bytes(ret) + # When the end of a gzip member is reached, a new decompressor + # must be created for unused (possibly future) data. + if self._obj.eof: + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + return bytes(ret) + + @property + def has_unconsumed_tail(self) -> bool: + return bool(self._unconsumed_tail) + + def flush(self) -> bytes: + return self._obj.flush() + + +if brotli is not None: + + class BrotliDecoder(ContentDecoder): + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self) -> None: + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): + setattr(self, "_decompress", self._obj.decompress) + else: + setattr(self, "_decompress", self._obj.process) + + # Requires Brotli >= 1.2.0 for `output_buffer_limit`. + def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: + raise NotImplementedError() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + try: + if max_length > 0: + return self._decompress(data, output_buffer_limit=max_length) + else: + return self._decompress(data) + except TypeError: + # Fallback for Brotli/brotlicffi/brotlipy versions without + # the `output_buffer_limit` parameter. + warnings.warn( + "Brotli >= 1.2.0 is required to prevent decompression bombs.", + DependencyWarning, + ) + return self._decompress(data) + + @property + def has_unconsumed_tail(self) -> bool: + try: + return not self._obj.can_accept_more_data() + except AttributeError: + return False + + def flush(self) -> bytes: + if hasattr(self._obj, "flush"): + return self._obj.flush() # type: ignore[no-any-return] + return b"" + + +try: + if sys.version_info >= (3, 14): + from compression import zstd + else: + from backports import zstd +except ImportError: + HAS_ZSTD = False +else: + HAS_ZSTD = True + + class ZstdDecoder(ContentDecoder): + def __init__(self) -> None: + self._obj = zstd.ZstdDecompressor() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + if not data and not self.has_unconsumed_tail: + return b"" + if self._obj.eof: + data = self._obj.unused_data + data + self._obj = zstd.ZstdDecompressor() + part = self._obj.decompress(data, max_length=max_length) + length = len(part) + data_parts = [part] + # Every loop iteration is supposed to read data from a separate frame. + # The loop breaks when: + # - enough data is read; + # - no more unused data is available; + # - end of the last read frame has not been reached (i.e., + # more data has to be fed). + while ( + self._obj.eof + and self._obj.unused_data + and (max_length < 0 or length < max_length) + ): + unused_data = self._obj.unused_data + if not self._obj.needs_input: + self._obj = zstd.ZstdDecompressor() + part = self._obj.decompress( + unused_data, + max_length=(max_length - length) if max_length > 0 else -1, + ) + if part_length := len(part): + data_parts.append(part) + length += part_length + elif self._obj.needs_input: + break + return b"".join(data_parts) + + @property + def has_unconsumed_tail(self) -> bool: + return not (self._obj.needs_input or self._obj.eof) or bool( + self._obj.unused_data + ) + + def flush(self) -> bytes: + if not self._obj.eof: + raise DecodeError("Zstandard data is incomplete") + return b"" + + +class MultiDecoder(ContentDecoder): + """ + From RFC7231: + If one or more encodings have been applied to a representation, the + sender that applied the encodings MUST generate a Content-Encoding + header field that lists the content codings in the order in which + they were applied. + """ + + # Maximum allowed number of chained HTTP encodings in the + # Content-Encoding header. + max_decode_links = 5 + + def __init__(self, modes: str) -> None: + encodings = [m.strip() for m in modes.split(",")] + if len(encodings) > self.max_decode_links: + raise DecodeError( + "Too many content encodings in the chain: " + f"{len(encodings)} > {self.max_decode_links}" + ) + self._decoders = [_get_decoder(e) for e in encodings] + + def flush(self) -> bytes: + return self._decoders[0].flush() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + if max_length <= 0: + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + ret = bytearray() + # Every while loop iteration goes through all decoders once. + # It exits when enough data is read or no more data can be read. + # It is possible that the while loop iteration does not produce + # any data because we retrieve up to `max_length` from every + # decoder, and the amount of bytes may be insufficient for the + # next decoder to produce enough/any output. + while True: + any_data = False + for d in reversed(self._decoders): + data = d.decompress(data, max_length=max_length - len(ret)) + if data: + any_data = True + # We should not break when no data is returned because + # next decoders may produce data even with empty input. + ret += data + if not any_data or len(ret) >= max_length: + return bytes(ret) + data = b"" + + @property + def has_unconsumed_tail(self) -> bool: + return any(d.has_unconsumed_tail for d in self._decoders) + + +def _get_decoder(mode: str) -> ContentDecoder: + if "," in mode: + return MultiDecoder(mode) + + # According to RFC 9110 section 8.4.1.3, recipients should + # consider x-gzip equivalent to gzip + if mode in ("gzip", "x-gzip"): + return GzipDecoder() + + if brotli is not None and mode == "br": + return BrotliDecoder() + + if HAS_ZSTD and mode == "zstd": + return ZstdDecoder() + + return DeflateDecoder() + + +class BytesQueueBuffer: + """Memory-efficient bytes buffer + + To return decoded data in read() and still follow the BufferedIOBase API, we need a + buffer to always return the correct amount of bytes. + + This buffer should be filled using calls to put() + + Our maximum memory usage is determined by the sum of the size of: + + * self.buffer, which contains the full data + * the largest chunk that we will copy in get() + """ + + def __init__(self) -> None: + self.buffer: typing.Deque[bytes | memoryview[bytes]] = collections.deque() + self._size: int = 0 + + def __len__(self) -> int: + return self._size + + def put(self, data: bytes) -> None: + self.buffer.append(data) + self._size += len(data) + + def get(self, n: int) -> bytes: + if n == 0: + return b"" + elif not self.buffer: + raise RuntimeError("buffer is empty") + elif n < 0: + raise ValueError("n should be > 0") + + if len(self.buffer[0]) == n and isinstance(self.buffer[0], bytes): + self._size -= n + return self.buffer.popleft() + + fetched = 0 + ret = io.BytesIO() + while fetched < n: + remaining = n - fetched + chunk = self.buffer.popleft() + chunk_length = len(chunk) + if remaining < chunk_length: + chunk = memoryview(chunk) + left_chunk, right_chunk = chunk[:remaining], chunk[remaining:] + ret.write(left_chunk) + self.buffer.appendleft(right_chunk) + self._size -= remaining + break + else: + ret.write(chunk) + self._size -= chunk_length + fetched += chunk_length + + if not self.buffer: + break + + return ret.getvalue() + + def get_all(self) -> bytes: + buffer = self.buffer + if not buffer: + assert self._size == 0 + return b"" + if len(buffer) == 1: + result = buffer.pop() + if isinstance(result, memoryview): + result = result.tobytes() + else: + ret = io.BytesIO() + ret.writelines(buffer.popleft() for _ in range(len(buffer))) + result = ret.getvalue() + self._size = 0 + return result + + +class BaseHTTPResponse(io.IOBase): + CONTENT_DECODERS = ["gzip", "x-gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + if HAS_ZSTD: + CONTENT_DECODERS += ["zstd"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + DECODER_ERROR_CLASSES: tuple[type[Exception], ...] = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + if HAS_ZSTD: + DECODER_ERROR_CLASSES += (zstd.ZstdError,) + + def __init__( + self, + *, + headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, + status: int, + version: int, + version_string: str, + reason: str | None, + decode_content: bool, + request_url: str | None, + retries: Retry | None = None, + ) -> None: + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) # type: ignore[arg-type] + self.status = status + self.version = version + self.version_string = version_string + self.reason = reason + self.decode_content = decode_content + self._has_decoded_content = False + self._request_url: str | None = request_url + self.retries = retries + + self.chunked = False + tr_enc = self.headers.get("transfer-encoding", "").lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + self._decoder: ContentDecoder | None = None + self.length_remaining: int | None + + def get_redirect_location(self) -> str | None | typing.Literal[False]: + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get("location") + return False + + @property + def data(self) -> bytes: + raise NotImplementedError() + + def json(self) -> typing.Any: + """ + Deserializes the body of the HTTP response as a Python object. + + The body of the HTTP response must be encoded using UTF-8, as per + `RFC 8529 Section 8.1 `_. + + To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to + your custom decoder instead. + + If the body of the HTTP response is not decodable to UTF-8, a + `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a + valid JSON document, a `json.JSONDecodeError` will be raised. + + Read more :ref:`here `. + + :returns: The body of the HTTP response as a Python object. + """ + data = self.data.decode("utf-8") + return _json.loads(data) + + @property + def url(self) -> str | None: + raise NotImplementedError() + + @url.setter + def url(self, url: str | None) -> None: + raise NotImplementedError() + + @property + def connection(self) -> BaseHTTPConnection | None: + raise NotImplementedError() + + @property + def retries(self) -> Retry | None: + return self._retries + + @retries.setter + def retries(self, retries: Retry | None) -> None: + # Override the request_url if retries has a redirect location. + if retries is not None and retries.history: + self.url = retries.history[-1].redirect_location + self._retries = retries + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Iterator[bytes]: + raise NotImplementedError() + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, + cache_content: bool = False, + ) -> bytes: + raise NotImplementedError() + + def read1( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> bytes: + raise NotImplementedError() + + def read_chunked( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> typing.Iterator[bytes]: + raise NotImplementedError() + + def release_conn(self) -> None: + raise NotImplementedError() + + def drain_conn(self) -> None: + raise NotImplementedError() + + def shutdown(self) -> None: + raise NotImplementedError() + + def close(self) -> None: + raise NotImplementedError() + + def _init_decoder(self) -> None: + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get("content-encoding", "").lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] + if encodings: + self._decoder = _get_decoder(content_encoding) + + def _decode( + self, + data: bytes, + decode_content: bool | None, + flush_decoder: bool, + max_length: int | None = None, + ) -> bytes: + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + if self._has_decoded_content: + raise RuntimeError( + "Calling read(decode_content=False) is not supported after " + "read(decode_content=True) was called." + ) + return data + + if max_length is None or flush_decoder: + max_length = -1 + + try: + if self._decoder: + data = self._decoder.decompress(data, max_length=max_length) + self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, + e, + ) from e + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self) -> bytes: + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + return self._decoder.decompress(b"") + self._decoder.flush() + return b"" + + # Compatibility methods for `io` module + def readinto(self, b: bytearray | memoryview[int]) -> int: + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + # Methods used by dependent libraries + def getheaders(self) -> HTTPHeaderDict: + return self.headers + + def getheader(self, name: str, default: str | None = None) -> str | None: + return self.headers.get(name, default) + + # Compatibility method for http.cookiejar + def info(self) -> HTTPHeaderDict: + return self.headers + + def geturl(self) -> str | None: + return self.url + + +class HTTPResponse(BaseHTTPResponse): + """ + HTTP Response container. + + Backwards-compatible with :class:`http.client.HTTPResponse` but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in :class:`http.client.HTTPResponse`: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param original_response: + When this HTTPResponse wrapper is generated from an :class:`http.client.HTTPResponse` + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + + :param retries: + The retries contains the last :class:`~urllib3.util.retry.Retry` that + was used during the request. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + + def __init__( + self, + body: _TYPE_BODY = "", + headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, + status: int = 0, + version: int = 0, + version_string: str = "HTTP/?", + reason: str | None = None, + preload_content: bool = True, + decode_content: bool = True, + original_response: _HttplibHTTPResponse | None = None, + pool: HTTPConnectionPool | None = None, + connection: HTTPConnection | None = None, + msg: _HttplibHTTPMessage | None = None, + retries: Retry | None = None, + enforce_content_length: bool = True, + request_method: str | None = None, + request_url: str | None = None, + auto_close: bool = True, + sock_shutdown: typing.Callable[[int], None] | None = None, + ) -> None: + super().__init__( + headers=headers, + status=status, + version=version, + version_string=version_string, + reason=reason, + decode_content=decode_content, + request_url=request_url, + retries=retries, + ) + + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close + + self._body = None + self._uncached_read_occurred = False + self._fp: _HttplibHTTPResponse | None = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + + if body and isinstance(body, (str, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, "read"): + self._fp = body # type: ignore[assignment] + self._sock_shutdown = sock_shutdown + + # Are we using the chunked-style of transfer encoding? + self.chunk_left: int | None = None + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # Used to return the correct amount of bytes for partial read()s + self._decoded_buffer = BytesQueueBuffer() + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def release_conn(self) -> None: + if not self._pool or not self._connection: + return None + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self) -> None: + """ + Read and discard any remaining HTTP response data in the response connection. + + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: + self._raw_read() + except (HTTPError, OSError, BaseSSLError, HTTPException): + pass + if self._has_decoded_content: + # `_raw_read` skips decompression, so we should clean up the + # decoder to avoid keeping unnecessary data in memory. + self._decoded_buffer = BytesQueueBuffer() + self._decoder = None + + @property + def data(self) -> bytes: + # For backwards-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body # type: ignore[return-value] + + if self._fp: + return self.read(cache_content=True) + + return None # type: ignore[return-value] + + @property + def connection(self) -> HTTPConnection | None: + return self._connection + + def isclosed(self) -> bool: + return is_fp_closed(self._fp) + + def tell(self) -> int: + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:`HTTPResponse.read` + if bytes are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method: str | None) -> int | None: + """ + Set initial length value for Response content if available. + """ + length: int | None + content_length: str | None = self.headers.get("content-length") + + if content_length is not None: + if self.chunked: + # This Response will fail with an IncompleteRead if it can't be + # received as chunked. This method falls back to attempt reading + # the response before raising an exception. + log.warning( + "Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked." + ) + return None + + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = {int(val) for val in content_length.split(",")} + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % content_length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + else: # if content_length is None + length = None + + # Convert status to int for comparison + # In some cases, httplib returns a status of "_UNKNOWN" + try: + status = int(self.status) + except ValueError: + status = 0 + + # Check for responses that shouldn't include a body + if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD": + length = 0 + + return length + + @contextmanager + def _error_catcher(self) -> typing.Generator[None]: + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + clean_exit = False + + try: + try: + yield + + except SocketTimeout as e: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, "Read timed out.") from e # type: ignore[arg-type] + + except BaseSSLError as e: + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) from e + + except IncompleteRead as e: + if ( + e.expected is not None + and e.partial is not None + and e.expected == -e.partial + ): + arg = "Response may not contain content." + else: + arg = f"Connection broken: {e!r}" + raise ProtocolError(arg, e) from e + + except (HTTPException, OSError) as e: + raise ProtocolError(f"Connection broken: {e!r}", e) from e + + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now to ensure that the connection is + # released back to the pool. + if self._original_response: + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection: + self._connection.close() + + # If we hold the original response but it's closed now, we should + # return the connection back to the pool. + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def _fp_read( + self, + amt: int | None = None, + *, + read1: bool = False, + ) -> bytes: + """ + Read a response with the thought that reading the number of bytes + larger than can fit in a 32-bit int at a time via SSL in some + known cases leads to an overflow error that has to be prevented + if `amt` or `self.length_remaining` indicate that a problem may + happen. + + This happens to urllib3 injected with pyOpenSSL-backed SSL-support. + """ + assert self._fp + c_int_max = 2**31 - 1 + if ( + (amt and amt > c_int_max) + or ( + amt is None + and self.length_remaining + and self.length_remaining > c_int_max + ) + ) and util.IS_PYOPENSSL: + if read1: + return self._fp.read1(c_int_max) + buffer = io.BytesIO() + # Besides `max_chunk_amt` being a maximum chunk size, it + # affects memory overhead of reading a response by this + # method in CPython. + # `c_int_max` equal to 2 GiB - 1 byte is the actual maximum + # chunk size that does not lead to an overflow error, but + # 256 MiB is a compromise. + max_chunk_amt = 2**28 + while amt is None or amt != 0: + if amt is not None: + chunk_amt = min(amt, max_chunk_amt) + amt -= chunk_amt + else: + chunk_amt = max_chunk_amt + data = self._fp.read(chunk_amt) + if not data: + break + buffer.write(data) + del data # to reduce peak memory usage by `max_chunk_amt`. + return buffer.getvalue() + elif read1: + return self._fp.read1(amt) if amt is not None else self._fp.read1() + else: + # StringIO doesn't like amt=None + return self._fp.read(amt) if amt is not None else self._fp.read() + + def _raw_read( + self, + amt: int | None = None, + *, + read1: bool = False, + ) -> bytes: + """ + Reads `amt` of bytes from the socket. + """ + if self._fp is None: + return None # type: ignore[return-value] + + fp_closed = getattr(self._fp, "closed", False) + + with self._error_catcher(): + data = self._fp_read(amt, read1=read1) if not fp_closed else b"" + if amt is not None and amt != 0 and not data: + # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + if ( + self.enforce_content_length + and self.length_remaining is not None + and self.length_remaining != 0 + ): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) + elif read1 and ( + (amt != 0 and not data) or self.length_remaining == len(data) + ): + # All data has been read, but `self._fp.read1` in + # CPython 3.12 and older doesn't always close + # `http.client.HTTPResponse`, so we close it here. + # See https://github.com/python/cpython/issues/113199 + self._fp.close() + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + return data + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, + cache_content: bool = False, + ) -> bytes: + """ + Similar to :meth:`http.client.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if amt and amt < 0: + # Negative numbers and `None` should be treated the same. + amt = None + elif amt is not None: + cache_content = False + + if ( + self._decoder + and self._decoder.has_unconsumed_tail + and len(self._decoded_buffer) < amt + ): + decoded_data = self._decode( + b"", + decode_content, + flush_decoder=False, + max_length=amt - len(self._decoded_buffer), + ) + self._decoded_buffer.put(decoded_data) + if len(self._decoded_buffer) >= amt: + return self._decoded_buffer.get(amt) + + data = self._raw_read(amt) + if not cache_content: + self._uncached_read_occurred = True + + flush_decoder = amt is None or (amt != 0 and not data) + + if ( + not data + and len(self._decoded_buffer) == 0 + and not (self._decoder and self._decoder.has_unconsumed_tail) + ): + return data + + if amt is None: + data = self._decode(data, decode_content, flush_decoder) + # It's possible that there is buffered decoded data after a + # partial read. + if decode_content and len(self._decoded_buffer) > 0: + self._decoded_buffer.put(data) + data = self._decoded_buffer.get_all() + + if cache_content and not self._uncached_read_occurred: + self._body = data + else: + # do not waste memory on buffer when not decoding + if not decode_content: + if self._has_decoded_content: + raise RuntimeError( + "Calling read(decode_content=False) is not supported after " + "read(decode_content=True) was called." + ) + return data + + decoded_data = self._decode( + data, + decode_content, + flush_decoder, + max_length=amt - len(self._decoded_buffer), + ) + self._decoded_buffer.put(decoded_data) + + while len(self._decoded_buffer) < amt and data: + # TODO make sure to initially read enough data to get past the headers + # For example, the GZ file header takes 10 bytes, we don't want to read + # it one byte at a time + data = self._raw_read(amt) + decoded_data = self._decode( + data, + decode_content, + flush_decoder, + max_length=amt - len(self._decoded_buffer), + ) + self._decoded_buffer.put(decoded_data) + data = self._decoded_buffer.get(amt) + + return data + + def read1( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> bytes: + """ + Similar to ``http.client.HTTPResponse.read1`` and documented + in :meth:`io.BufferedReader.read1`, but with an additional parameter: + ``decode_content``. + + :param amt: + How much of the content to read. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if decode_content is None: + decode_content = self.decode_content + if amt and amt < 0: + # Negative numbers and `None` should be treated the same. + amt = None + # try and respond without going to the network + if self._has_decoded_content: + if not decode_content: + raise RuntimeError( + "Calling read1(decode_content=False) is not supported after " + "read1(decode_content=True) was called." + ) + if ( + self._decoder + and self._decoder.has_unconsumed_tail + and (amt is None or len(self._decoded_buffer) < amt) + ): + decoded_data = self._decode( + b"", + decode_content, + flush_decoder=False, + max_length=( + amt - len(self._decoded_buffer) if amt is not None else None + ), + ) + self._decoded_buffer.put(decoded_data) + if len(self._decoded_buffer) > 0: + if amt is None: + return self._decoded_buffer.get_all() + return self._decoded_buffer.get(amt) + if amt == 0: + return b"" + + # FIXME, this method's type doesn't say returning None is possible + data = self._raw_read(amt, read1=True) + self._uncached_read_occurred = True + if not decode_content or data is None: + return data + + self._init_decoder() + while True: + flush_decoder = not data + decoded_data = self._decode( + data, decode_content, flush_decoder, max_length=amt + ) + self._decoded_buffer.put(decoded_data) + if decoded_data or flush_decoder: + break + data = self._raw_read(8192, read1=True) + + if amt is None: + return self._decoded_buffer.get_all() + return self._decoded_buffer.get(amt) + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if amt == 0: + return + + if self.chunked and self.supports_chunked_reads(): + yield from self.read_chunked(amt, decode_content=decode_content) + else: + while ( + not is_fp_closed(self._fp) + or len(self._decoded_buffer) > 0 + or (self._decoder and self._decoder.has_unconsumed_tail) + ): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + # Overrides from io.IOBase + def readable(self) -> bool: + return True + + def shutdown(self) -> None: + if not self._sock_shutdown: + raise ValueError("Cannot shutdown socket as self._sock_shutdown is not set") + if self._connection is None: + raise RuntimeError( + "Cannot shutdown as connection has already been released to the pool" + ) + self._sock_shutdown(socket.SHUT_RD) + + def close(self) -> None: + self._sock_shutdown = None + + if not self.closed and self._fp: + self._fp.close() + + if self._connection: + self._connection.close() + + if not self.auto_close: + io.IOBase.close(self) + + @property + def closed(self) -> bool: + if not self.auto_close: + return io.IOBase.closed.__get__(self) # type: ignore[no-any-return] + elif self._fp is None: + return True + elif hasattr(self._fp, "isclosed"): + return self._fp.isclosed() + elif hasattr(self._fp, "closed"): + return self._fp.closed + else: + return True + + def fileno(self) -> int: + if self._fp is None: + raise OSError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise OSError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) + + def flush(self) -> None: + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): + return self._fp.flush() + + def supports_chunked_reads(self) -> bool: + """ + Checks if the underlying file-like object looks like a + :class:`http.client.HTTPResponse` object. We do this by testing for + the fp attribute. If it is present we assume it returns raw chunks as + processed by read_chunked(). + """ + return hasattr(self._fp, "fp") + + def _update_chunk_length(self) -> None: + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return None + line = self._fp.fp.readline() # type: ignore[union-attr] + line = line.split(b";", 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + self.close() + if line: + # Invalid chunked protocol response, abort. + raise InvalidChunkLength(self, line) from None + else: + # Truncated at start of next chunk + raise ProtocolError("Response ended prematurely") from None + + def _handle_chunk(self, amt: int | None) -> bytes: + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] + returned_chunk = chunk + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif self.chunk_left is not None and amt < self.chunk_left: + value = self._fp._safe_read(amt) # type: ignore[union-attr] + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) # type: ignore[union-attr] + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk # type: ignore[no-any-return] + + def read_chunked( + self, amt: int | None = None, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing." + ) + if not self.supports_chunked_reads(): + raise BodyNotHttplibCompatible( + "Body should be http.client.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks." + ) + + with self._error_catcher(): + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return None + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: # type: ignore[union-attr] + return None + + if amt == 0: + return + elif amt and amt < 0: + # Negative numbers and `None` should be treated the same, + # but httplib handles only `None` correctly. + amt = None + + while True: + # First, check if any data is left in the decoder's buffer. + if self._decoder and self._decoder.has_unconsumed_tail: + chunk = b"" + else: + self._update_chunk_length() + self._uncached_read_occurred = True + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode( + chunk, + decode_content=decode_content, + flush_decoder=False, + max_length=amt, + ) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while self._fp is not None: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b"\r\n": + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() + + @property + def url(self) -> str | None: + """ + Returns the URL that was the source of this response. + If the request that generated this response redirected, this method + will return the final redirect location. + """ + return self._request_url + + @url.setter + def url(self, url: str | None) -> None: + self._request_url = url + + def __iter__(self) -> typing.Iterator[bytes]: + buffer: list[bytes] = [] + for chunk in self.stream(decode_content=True): + if b"\n" in chunk: + chunks = chunk.split(b"\n") + yield b"".join(buffer) + chunks[0] + b"\n" + for x in chunks[1:-1]: + yield x + b"\n" + if chunks[-1]: + buffer = [chunks[-1]] + else: + buffer = [] + else: + buffer.append(chunk) + if buffer: + yield b"".join(buffer) diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__init__.py b/.venv/lib/python3.14/site-packages/urllib3/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..534126033c083203649022fa9b753a433f005556 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/__init__.py @@ -0,0 +1,42 @@ +# For backwards compatibility, provide imports that used to be here. +from __future__ import annotations + +from .connection import is_connection_dropped +from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers +from .response import is_fp_closed +from .retry import Retry +from .ssl_ import ( + ALPN_PROTOCOLS, + IS_PYOPENSSL, + SSLContext, + assert_fingerprint, + create_urllib3_context, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import Timeout +from .url import Url, parse_url +from .wait import wait_for_read, wait_for_write + +__all__ = ( + "IS_PYOPENSSL", + "SSLContext", + "ALPN_PROTOCOLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "create_urllib3_context", + "is_connection_dropped", + "is_fp_closed", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", + "SKIP_HEADER", + "SKIPPABLE_HEADERS", +) diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..783672fab3f6b583b30efd7885a83efbad05c9db Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/connection.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e60cde2466153ba44127e8f9d597826a539904d Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/connection.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/proxy.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/proxy.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..328059218eeb879ec5626556a152cff2d858459d Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/proxy.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/request.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/request.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4297201bda19d1b5fd0555eaa945bfa59846cc56 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/request.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/response.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/response.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7fb2e0bf0a0bde256ebeb8f7615a23d4107c1f46 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/response.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/retry.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/retry.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f45a36f8be6d751a3890fa6b57ff01f977fcb507 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/retry.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7052f53aa0b3bbd17f763064db8060b226fd7657 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42eff7c80853dcb8ad87d368303531ed42cc917a Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssltransport.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssltransport.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aae3af627b4df9860a219cf2fa1537dd24dc8f07 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssltransport.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/timeout.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/timeout.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8faf2abaf34051209c56db5d2bc26f14921198f7 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/timeout.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/url.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/url.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7bc953e519fc4215a620c1d749f6c5f28cfead03 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/url.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/util.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/util.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3022235cbf8b2f86e2591b4f90af1ebb67bff726 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/util.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/wait.cpython-314.pyc b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/wait.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a1b506a42f02f59a8cfda5224b80b6a6e535c06 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/wait.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/connection.py b/.venv/lib/python3.14/site-packages/urllib3/util/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..f92519ee9124e91e5da7d60ccc3f274312ed3514 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/connection.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import socket +import typing + +from ..exceptions import LocationParseError +from .timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT + +_TYPE_SOCKET_OPTIONS = list[tuple[int, int, typing.Union[int, bytes]]] + +if typing.TYPE_CHECKING: + from .._base_connection import BaseHTTPConnection + + +def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + :param conn: :class:`urllib3.connection.HTTPConnection` object. + """ + return not conn.is_connected + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +# One additional modification is that we avoid binding to IPv6 servers +# discovered in DNS if the system doesn't have IPv6 functionality. +def create_connection( + address: tuple[str, int], + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + socket_options: _TYPE_SOCKET_OPTIONS | None = None, +) -> socket.socket: + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`socket.getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith("["): + host = host.strip("[]") + err = None + + # Using the value from allowed_gai_family() in the context of getaddrinfo lets + # us select whether to work with IPv4 DNS records, IPv6 records, or both. + # The original create_connection function always returns all records. + family = allowed_gai_family() + + try: + host.encode("idna") + except UnicodeError: + raise LocationParseError(f"'{host}', label empty or too long") from None + + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + _set_socket_options(sock, socket_options) + + if timeout is not _DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + # Break explicitly a reference cycle + err = None + return sock + + except OSError as _: + err = _ + if sock is not None: + sock.close() + + if err is not None: + try: + raise err + finally: + # Break explicitly a reference cycle + err = None + else: + raise OSError("getaddrinfo returns an empty list") + + +def _set_socket_options( + sock: socket.socket, options: _TYPE_SOCKET_OPTIONS | None +) -> None: + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family() -> socket.AddressFamily: + """This function is designed to work in the context of + getaddrinfo, where family=socket.AF_UNSPEC is the default and + will perform a DNS search for both IPv6 and IPv4 records.""" + + family = socket.AF_INET + if HAS_IPV6: + family = socket.AF_UNSPEC + return family + + +def _has_ipv6(host: str) -> bool: + """Returns True if the system can bind an IPv6 address.""" + sock = None + has_ipv6 = False + + if socket.has_ipv6: + # has_ipv6 returns true if cPython was compiled with IPv6 support. + # It does not tell us if the system has IPv6 support enabled. To + # determine that we must bind to an IPv6 address. + # https://github.com/urllib3/urllib3/pull/611 + # https://bugs.python.org/issue658327 + try: + sock = socket.socket(socket.AF_INET6) + sock.bind((host, 0)) + has_ipv6 = True + except Exception: + pass + + if sock: + sock.close() + return has_ipv6 + + +HAS_IPV6 = _has_ipv6("::1") diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/proxy.py b/.venv/lib/python3.14/site-packages/urllib3/util/proxy.py new file mode 100644 index 0000000000000000000000000000000000000000..908fc6621d0afbed16bde2c1957a5cf28d3a84d8 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/proxy.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import typing + +from .url import Url + +if typing.TYPE_CHECKING: + from ..connection import ProxyConfig + + +def connection_requires_http_tunnel( + proxy_url: Url | None = None, + proxy_config: ProxyConfig | None = None, + destination_scheme: str | None = None, +) -> bool: + """ + Returns True if the connection requires an HTTP CONNECT through the proxy. + + :param URL proxy_url: + URL of the proxy. + :param ProxyConfig proxy_config: + Proxy configuration from poolmanager.py + :param str destination_scheme: + The scheme of the destination. (i.e https, http, etc) + """ + # If we're not using a proxy, no way to use a tunnel. + if proxy_url is None: + return False + + # HTTP destinations never require tunneling, we always forward. + if destination_scheme == "http": + return False + + # Support for forwarding with HTTPS proxies and HTTPS destinations. + if ( + proxy_url.scheme == "https" + and proxy_config + and proxy_config.use_forwarding_for_https + ): + return False + + # Otherwise always use a tunnel. + return True diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/request.py b/.venv/lib/python3.14/site-packages/urllib3/util/request.py new file mode 100644 index 0000000000000000000000000000000000000000..6c2372ba7e777826a4eb124ddfb54f0240b65d67 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/request.py @@ -0,0 +1,263 @@ +from __future__ import annotations + +import io +import sys +import typing +from base64 import b64encode +from enum import Enum + +from ..exceptions import UnrewindableBodyError +from .util import to_bytes + +if typing.TYPE_CHECKING: + from typing import Final + +# Pass as a value within ``headers`` to skip +# emitting some HTTP headers that are added automatically. +# The only headers that are supported are ``Accept-Encoding``, +# ``Host``, and ``User-Agent``. +SKIP_HEADER = "@@@SKIP_HEADER@@@" +SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) + +ACCEPT_ENCODING = "gzip,deflate" +try: + try: + import brotlicffi as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 + except ImportError: + import brotli as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",br" + +try: + if sys.version_info >= (3, 14): + from compression import zstd as _unused_module_zstd # noqa: F401 + else: + from backports import zstd as _unused_module_zstd # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",zstd" + + +class _TYPE_FAILEDTELL(Enum): + token = 0 + + +_FAILEDTELL: Final[_TYPE_FAILEDTELL] = _TYPE_FAILEDTELL.token + +_TYPE_BODY_POSITION = typing.Union[int, _TYPE_FAILEDTELL] + +# When sending a request with these methods we aren't expecting +# a body so don't need to set an explicit 'Content-Length: 0' +# The reason we do this in the negative instead of tracking methods +# which 'should' have a body is because unknown methods should be +# treated as if they were 'POST' which *does* expect a body. +_METHODS_NOT_EXPECTING_BODY = {"GET", "HEAD", "DELETE", "TRACE", "OPTIONS", "CONNECT"} + + +def make_headers( + keep_alive: bool | None = None, + accept_encoding: bool | list[str] | str | None = None, + user_agent: str | None = None, + basic_auth: str | None = None, + proxy_basic_auth: str | None = None, + disable_cache: bool | None = None, +) -> dict[str, str]: + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. If the dependencies for + Brotli (either the ``brotli`` or ``brotlicffi`` package) and/or + Zstandard (the ``backports.zstd`` package for Python before 3.14) + algorithms are installed, then their encodings are + included in the string ('br' and 'zstd', respectively). + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example: + + .. code-block:: python + + import urllib3 + + print(urllib3.util.make_headers(keep_alive=True, user_agent="Batman/1.0")) + # {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + print(urllib3.util.make_headers(accept_encoding=True)) + # {'accept-encoding': 'gzip,deflate'} + """ + headers: dict[str, str] = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ",".join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers["accept-encoding"] = accept_encoding + + if user_agent: + headers["user-agent"] = user_agent + + if keep_alive: + headers["connection"] = "keep-alive" + + if basic_auth: + headers["authorization"] = ( + f"Basic {b64encode(basic_auth.encode('latin-1')).decode()}" + ) + + if proxy_basic_auth: + headers["proxy-authorization"] = ( + f"Basic {b64encode(proxy_basic_auth.encode('latin-1')).decode()}" + ) + + if disable_cache: + headers["cache-control"] = "no-cache" + + return headers + + +def set_file_position( + body: typing.Any, pos: _TYPE_BODY_POSITION | None +) -> _TYPE_BODY_POSITION | None: + """ + If a position is provided, move file to that point. + Otherwise, we'll attempt to record a position for future use. + """ + if pos is not None: + rewind_body(body, pos) + elif getattr(body, "tell", None) is not None: + try: + pos = body.tell() + except OSError: + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body. + pos = _FAILEDTELL + + return pos + + +def rewind_body(body: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) -> None: + """ + Attempt to rewind body to a certain position. + Primarily used for request redirects and retries. + + :param body: + File-like object that supports seek. + + :param int pos: + Position to seek to in file. + """ + body_seek = getattr(body, "seek", None) + if body_seek is not None and isinstance(body_pos, int): + try: + body_seek(body_pos) + except OSError as e: + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) from e + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) + else: + raise ValueError( + f"body_pos must be of type integer, instead it was {type(body_pos)}." + ) + + +class ChunksAndContentLength(typing.NamedTuple): + chunks: typing.Iterable[bytes] | None + content_length: int | None + + +def body_to_chunks( + body: typing.Any | None, method: str, blocksize: int +) -> ChunksAndContentLength: + """Takes the HTTP request method, body, and blocksize and + transforms them into an iterable of chunks to pass to + socket.sendall() and an optional 'Content-Length' header. + + A 'Content-Length' of 'None' indicates the length of the body + can't be determined so should use 'Transfer-Encoding: chunked' + for framing instead. + """ + + chunks: typing.Iterable[bytes] | None + content_length: int | None + + # No body, we need to make a recommendation on 'Content-Length' + # based on whether that request method is expected to have + # a body or not. + if body is None: + chunks = None + if method.upper() not in _METHODS_NOT_EXPECTING_BODY: + content_length = 0 + else: + content_length = None + + # Bytes or strings become bytes + elif isinstance(body, (str, bytes)): + chunks = (to_bytes(body),) + content_length = len(chunks[0]) + + # File-like object, TODO: use seek() and tell() for length? + elif hasattr(body, "read"): + + def chunk_readable() -> typing.Iterable[bytes]: + encode = isinstance(body, io.TextIOBase) + while True: + datablock = body.read(blocksize) + if not datablock: + break + if encode: + datablock = datablock.encode("utf-8") + yield datablock + + chunks = chunk_readable() + content_length = None + + # Otherwise we need to start checking via duck-typing. + else: + try: + # Check if the body implements the buffer API. + mv = memoryview(body) + except TypeError: + try: + # Check if the body is an iterable + chunks = iter(body) + content_length = None + except TypeError: + raise TypeError( + f"'body' must be a bytes-like object, file-like " + f"object, or iterable. Instead was {body!r}" + ) from None + else: + # Since it implements the buffer API can be passed directly to socket.sendall() + chunks = (body,) + content_length = mv.nbytes + + return ChunksAndContentLength(chunks=chunks, content_length=content_length) diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/response.py b/.venv/lib/python3.14/site-packages/urllib3/util/response.py new file mode 100644 index 0000000000000000000000000000000000000000..0f4578696fa2e17a900c6890ec26d65e860b0b72 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/response.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import http.client as httplib +from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect + +from ..exceptions import HeaderParsingError + + +def is_fp_closed(obj: object) -> bool: + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check `isclosed()` first, in case Python3 doesn't set `closed`. + # GH Issue #928 + return obj.isclosed() # type: ignore[no-any-return, attr-defined] + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed # type: ignore[no-any-return, attr-defined] + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None # type: ignore[attr-defined] + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers: httplib.HTTPMessage) -> None: + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param http.client.HTTPMessage headers: Headers to verify. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError(f"expected httplib.Message, got {type(headers)}.") + + unparsed_data = None + + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = headers.get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 + + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in headers.defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response: httplib.HTTPResponse) -> bool: + """ + Checks whether the request of a response has been a HEAD-request. + + :param http.client.HTTPResponse response: + Response to check if the originating request + used 'HEAD' as a method. + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method_str = response._method # type: str # type: ignore[attr-defined] + return method_str.upper() == "HEAD" diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/retry.py b/.venv/lib/python3.14/site-packages/urllib3/util/retry.py new file mode 100644 index 0000000000000000000000000000000000000000..7649898e1d9930724a456c1c6fdecb66e078b4cc --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/retry.py @@ -0,0 +1,557 @@ +from __future__ import annotations + +import email +import logging +import random +import re +import time +import typing +from itertools import takewhile +from types import TracebackType + +from ..exceptions import ( + ConnectTimeoutError, + InvalidHeader, + MaxRetryError, + ProtocolError, + ProxyError, + ReadTimeoutError, + ResponseError, +) +from .util import reraise + +if typing.TYPE_CHECKING: + from typing_extensions import Self + + from ..connectionpool import ConnectionPool + from ..response import BaseHTTPResponse + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +class RequestHistory(typing.NamedTuple): + method: str | None + url: str | None + error: Exception | None + status: int | None + redirect_location: str | None + + +class Retry: + """Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool: + + .. code-block:: python + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request("GET", "https://example.com/") + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request("GET", "https://example.com/", retries=Retry(10)) + + Retries can be disabled by passing ``False``: + + .. code-block:: python + + response = http.request("GET", "https://example.com/", retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int status: + How many times to retry on bad status codes. + + These are retries made on responses, where status code matches + ``status_forcelist``. + + Set to ``0`` to fail on the first retry of this type. + + :param int other: + How many times to retry on other errors. + + Other errors are errors that are not connect, read, redirect or status errors. + These errors might be raised after the request was sent to the server, so the + request might have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + If ``total`` is not set, it's a good idea to set this to 0 to account + for unexpected edge cases and avoid infinite retry loops. + + :param Collection allowed_methods: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + idempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. + + Set to a ``None`` value to retry on any verb. + + :param Collection status_forcelist: + A set of integer HTTP status codes that we should force a retry on. + A retry is initiated if the request method is in ``allowed_methods`` + and the response status code is in ``status_forcelist``. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts after the second try + (most errors are resolved immediately by a second try without a + delay). urllib3 will sleep for:: + + {backoff factor} * (2 ** ({number of previous retries})) + + seconds. If `backoff_jitter` is non-zero, this sleep is extended by:: + + random.uniform(0, {backoff jitter}) + + seconds. For example, if the backoff_factor is 0.1, then :func:`Retry.sleep` will + sleep for [0.0s, 0.2s, 0.4s, 0.8s, ...] between retries. No backoff will ever + be longer than `backoff_max`. + + By default, backoff is disabled (factor set to 0). + + :param float backoff_max: + The maximum backoff time (in seconds) between retry attempts. + This value caps the computed backoff from `backoff_factor`. + + :param float backoff_jitter: + Random jitter amount (in seconds) added to the computed backoff. + Jitter is sampled uniformly from `0` to `backoff_jitter`. + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have + been exhausted. + + :param tuple history: The history of the request encountered during + each call to :meth:`~Retry.increment`. The list is in the order + the requests occurred. Each list item is of class :class:`RequestHistory`. + + :param bool respect_retry_after_header: + Whether to respect Retry-After header on status codes defined as + :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. + + :param Collection remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request. + + :param int retry_after_max: Number of seconds to allow as the maximum for + Retry-After headers. Defaults to :attr:`Retry.DEFAULT_RETRY_AFTER_MAX`. + Any Retry-After headers larger than this value will be limited to this + value. + """ + + #: Default methods to be used for ``allowed_methods`` + DEFAULT_ALLOWED_METHODS = frozenset( + ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + + #: Default status codes to be used for ``status_forcelist`` + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + #: Default headers to be used for ``remove_headers_on_redirect`` + DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset( + ["Cookie", "Authorization", "Proxy-Authorization"] + ) + + #: Default maximum backoff time. + DEFAULT_BACKOFF_MAX = 120 + + # This is undocumented in the RFC. Setting to 6 hours matches other popular libraries. + #: Default maximum allowed value for Retry-After headers in seconds + DEFAULT_RETRY_AFTER_MAX: typing.Final[int] = 21600 + + # Backward compatibility; assigned outside of the class. + DEFAULT: typing.ClassVar[Retry] + + def __init__( + self, + total: bool | int | None = 10, + connect: int | None = None, + read: int | None = None, + redirect: bool | int | None = None, + status: int | None = None, + other: int | None = None, + allowed_methods: typing.Collection[str] | None = DEFAULT_ALLOWED_METHODS, + status_forcelist: typing.Collection[int] | None = None, + backoff_factor: float = 0, + backoff_max: float = DEFAULT_BACKOFF_MAX, + raise_on_redirect: bool = True, + raise_on_status: bool = True, + history: tuple[RequestHistory, ...] | None = None, + respect_retry_after_header: bool = True, + remove_headers_on_redirect: typing.Collection[ + str + ] = DEFAULT_REMOVE_HEADERS_ON_REDIRECT, + backoff_jitter: float = 0.0, + retry_after_max: int = DEFAULT_RETRY_AFTER_MAX, + ) -> None: + self.total = total + self.connect = connect + self.read = read + self.status = status + self.other = other + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.allowed_methods = allowed_methods + self.backoff_factor = backoff_factor + self.backoff_max = backoff_max + self.retry_after_max = retry_after_max + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or () + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = frozenset( + h.lower() for h in remove_headers_on_redirect + ) + self.backoff_jitter = backoff_jitter + + def new(self, **kw: typing.Any) -> Self: + params = dict( + total=self.total, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + other=self.other, + allowed_methods=self.allowed_methods, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + backoff_max=self.backoff_max, + retry_after_max=self.retry_after_max, + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + history=self.history, + remove_headers_on_redirect=self.remove_headers_on_redirect, + respect_retry_after_header=self.respect_retry_after_header, + backoff_jitter=self.backoff_jitter, + ) + + params.update(kw) + return type(self)(**params) # type: ignore[arg-type] + + @classmethod + def from_int( + cls, + retries: Retry | bool | int | None, + redirect: bool | int | None = True, + default: Retry | bool | int | None = None, + ) -> Retry: + """Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r", retries, new_retries) + return new_retries + + def get_backoff_time(self) -> float: + """Formula for computing the current backoff + + :rtype: float + """ + # We want to consider only the last consecutive errors sequence (Ignore redirects). + consecutive_errors_len = len( + list( + takewhile(lambda x: x.redirect_location is None, reversed(self.history)) + ) + ) + if consecutive_errors_len <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) + if self.backoff_jitter != 0.0: + backoff_value += random.random() * self.backoff_jitter + return float(max(0, min(self.backoff_max, backoff_value))) + + def parse_retry_after(self, retry_after: str) -> float: + seconds: float + # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = int(retry_after) + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + raise InvalidHeader(f"Invalid Retry-After header: {retry_after}") + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + seconds = max(seconds, 0) + + # Check the seconds do not exceed the specified maximum + if seconds > self.retry_after_max: + seconds = self.retry_after_max + + return seconds + + def get_retry_after(self, response: BaseHTTPResponse) -> float | None: + """Get the value of Retry-After in seconds.""" + + retry_after = response.headers.get("Retry-After") + + if retry_after is None: + return None + + return self.parse_retry_after(retry_after) + + def sleep_for_retry(self, response: BaseHTTPResponse) -> bool: + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self) -> None: + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response: BaseHTTPResponse | None = None) -> None: + """Sleep between retry attempts. + + This method will respect a server's ``Retry-After`` response header + and sleep the duration of the time requested. If that is not present, it + will use an exponential backoff. By default, the backoff factor is 0 and + this method will return immediately. + """ + + if self.respect_retry_after_header and response: + slept = self.sleep_for_retry(response) + if slept: + return + + self._sleep_backoff() + + def _is_connection_error(self, err: Exception) -> bool: + """Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + if isinstance(err, ProxyError): + err = err.original_error + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err: Exception) -> bool: + """Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def _is_method_retryable(self, method: str) -> bool: + """Checks if a given HTTP method should be retried upon, depending if + it is included in the allowed_methods + """ + if self.allowed_methods and method.upper() not in self.allowed_methods: + return False + return True + + def is_retry( + self, method: str, status_code: int, has_retry_after: bool = False + ) -> bool: + """Is this method/status code retryable? (Based on allowlists and control + variables such as the number of total retries to allow, whether to + respect the Retry-After header, whether this header is present, and + whether the returned status code is on the list of status codes to + be retried upon on the presence of the aforementioned header) + """ + if not self._is_method_retryable(method): + return False + + if self.status_forcelist and status_code in self.status_forcelist: + return True + + return bool( + self.total + and self.respect_retry_after_header + and has_retry_after + and (status_code in self.RETRY_AFTER_STATUS_CODES) + ) + + def is_exhausted(self) -> bool: + """Are we out of retries?""" + retry_counts = [ + x + for x in ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) + if x + ] + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment( + self, + method: str | None = None, + url: str | None = None, + response: BaseHTTPResponse | None = None, + error: Exception | None = None, + _pool: ConnectionPool | None = None, + _stacktrace: TracebackType | None = None, + ) -> Self: + """Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.BaseHTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + connect = self.connect + read = self.read + redirect = self.redirect + status_count = self.status + other = self.other + cause = "unknown" + status = None + redirect_location = None + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False or method is None or not self._is_method_retryable(method): + raise reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + + elif error: + # Other retry? + if other is not None: + other -= 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = "too many redirects" + response_redirect_location = response.get_redirect_location() + if response_redirect_location: + redirect_location = response_redirect_location + status = response.status + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and the given method is in the allowed_methods + cause = ResponseError.GENERIC_ERROR + if response and response.status: + if status_count is not None: + status_count -= 1 + cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status) + status = response.status + + history = self.history + ( + RequestHistory(method, url, error, status, redirect_location), + ) + + new_retry = self.new( + total=total, + connect=connect, + read=read, + redirect=redirect, + status=status_count, + other=other, + history=history, + ) + + if new_retry.is_exhausted(): + reason = error or ResponseError(cause) + raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type] + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self) -> str: + return ( + f"{type(self).__name__}(total={self.total}, connect={self.connect}, " + f"read={self.read}, redirect={self.redirect}, status={self.status})" + ) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/ssl_.py b/.venv/lib/python3.14/site-packages/urllib3/util/ssl_.py new file mode 100644 index 0000000000000000000000000000000000000000..e66549a76c4b5821e639e5facfeb63dd1a39d543 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/ssl_.py @@ -0,0 +1,477 @@ +from __future__ import annotations + +import hashlib +import hmac +import os +import socket +import sys +import typing +import warnings +from binascii import unhexlify + +from ..exceptions import ProxySchemeUnsupported, SSLError +from .url import _BRACELESS_IPV6_ADDRZ_RE, _IPV4_RE + +SSLContext = None +SSLTransport = None +HAS_NEVER_CHECK_COMMON_NAME = False +IS_PYOPENSSL = False +ALPN_PROTOCOLS = ["http/1.1"] + +_TYPE_VERSION_INFO = tuple[int, int, int, str, int] + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = { + length: getattr(hashlib, algorithm, None) + for length, algorithm in ((32, "md5"), (40, "sha1"), (64, "sha256")) +} + + +def _is_has_never_check_common_name_reliable( + openssl_version: str, +) -> bool: + # As of May 2023, all released versions of LibreSSL fail to reject certificates with + # only common names, see https://github.com/urllib3/urllib3/pull/3024 + is_openssl = openssl_version.startswith("OpenSSL ") + + return is_openssl + + +if typing.TYPE_CHECKING: + from ssl import VerifyMode + from typing import TypedDict + + from .ssltransport import SSLTransport as SSLTransportType + + class _TYPE_PEER_CERT_RET_DICT(TypedDict, total=False): + subjectAltName: tuple[tuple[str, str], ...] + subject: tuple[tuple[tuple[str, str], ...], ...] + serialNumber: str + + +# Mapping from 'ssl.PROTOCOL_TLSX' to 'TLSVersion.X' +_SSL_VERSION_TO_TLS_VERSION: dict[int, int] = {} + +try: # Do we have ssl at all? + import ssl + from ssl import ( # type: ignore[assignment] + CERT_REQUIRED, + HAS_NEVER_CHECK_COMMON_NAME, + OP_NO_COMPRESSION, + OP_NO_TICKET, + OPENSSL_VERSION, + PROTOCOL_TLS, + PROTOCOL_TLS_CLIENT, + VERIFY_X509_PARTIAL_CHAIN, + VERIFY_X509_STRICT, + OP_NO_SSLv2, + OP_NO_SSLv3, + SSLContext, + TLSVersion, + ) + + PROTOCOL_SSLv23 = PROTOCOL_TLS + + # Setting SSLContext.hostname_checks_common_name = False didn't work with + # LibreSSL, check details in the used function. + if HAS_NEVER_CHECK_COMMON_NAME and not _is_has_never_check_common_name_reliable( + OPENSSL_VERSION, + ): # Defensive: + HAS_NEVER_CHECK_COMMON_NAME = False + + # Need to be careful here in case old TLS versions get + # removed in future 'ssl' module implementations. + for attr in ("TLSv1", "TLSv1_1", "TLSv1_2"): + try: + _SSL_VERSION_TO_TLS_VERSION[getattr(ssl, f"PROTOCOL_{attr}")] = getattr( + TLSVersion, attr + ) + except AttributeError: # Defensive: + continue + + from .ssltransport import SSLTransport # type: ignore[assignment] +except ImportError: + OP_NO_COMPRESSION = 0x20000 # type: ignore[assignment, misc] + OP_NO_TICKET = 0x4000 # type: ignore[assignment, misc] + OP_NO_SSLv2 = 0x1000000 # type: ignore[assignment, misc] + OP_NO_SSLv3 = 0x2000000 # type: ignore[assignment, misc] + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 # type: ignore[assignment, misc] + PROTOCOL_TLS_CLIENT = 16 # type: ignore[assignment, misc] + VERIFY_X509_PARTIAL_CHAIN = 0x80000 # type: ignore[assignment,misc] + VERIFY_X509_STRICT = 0x20 # type: ignore[assignment, misc] + + +_TYPE_PEER_CERT_RET = typing.Union["_TYPE_PEER_CERT_RET_DICT", bytes, None] + + +def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None: + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + if cert is None: + raise SSLError("No certificate for the peer.") + + fingerprint = fingerprint.replace(":", "").lower() + digest_length = len(fingerprint) + if digest_length not in HASHFUNC_MAP: + raise SSLError(f"Fingerprint of invalid length: {fingerprint}") + hashfunc = HASHFUNC_MAP.get(digest_length) + if hashfunc is None: + raise SSLError( + f"Hash function implementation unavailable for fingerprint length: {digest_length}" + ) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not hmac.compare_digest(cert_digest, fingerprint_bytes): + raise SSLError( + f'Fingerprints did not match. Expected "{fingerprint}", got "{cert_digest.hex()}"' + ) + + +def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode: + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_REQUIRED`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbreviation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_REQUIRED + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "CERT_" + candidate) + return res # type: ignore[no-any-return] + + return candidate # type: ignore[return-value] + + +def resolve_ssl_version(candidate: None | int | str) -> int: + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_TLS + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "PROTOCOL_" + candidate) + return typing.cast(int, res) + + return candidate + + +def create_urllib3_context( + ssl_version: int | None = None, + cert_reqs: int | None = None, + options: int | None = None, + ciphers: str | None = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + verify_flags: int | None = None, +) -> ssl.SSLContext: + """Creates and configures an :class:`ssl.SSLContext` instance for use with urllib3. + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + + This parameter is deprecated instead use 'ssl_minimum_version'. + :param ssl_minimum_version: + The minimum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. + :param ssl_maximum_version: + The maximum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. + Not recommended to set to anything other than 'ssl.TLSVersion.MAXIMUM_SUPPORTED' which is the + default value. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. + :param ciphers: + Which cipher suites to allow the server to select. Defaults to either system configured + ciphers if OpenSSL 1.1.1+, otherwise uses a secure default set of ciphers. + :param verify_flags: + The flags for certificate verification operations. These default to + ``ssl.VERIFY_X509_PARTIAL_CHAIN`` and ``ssl.VERIFY_X509_STRICT`` for Python 3.13+. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + if SSLContext is None: + raise TypeError("Can't create an SSLContext object without an ssl module") + + # This means 'ssl_version' was specified as an exact value. + if ssl_version not in (None, PROTOCOL_TLS, PROTOCOL_TLS_CLIENT): + # Disallow setting 'ssl_version' and 'ssl_minimum|maximum_version' + # to avoid conflicts. + if ssl_minimum_version is not None or ssl_maximum_version is not None: + raise ValueError( + "Can't specify both 'ssl_version' and either " + "'ssl_minimum_version' or 'ssl_maximum_version'" + ) + + # 'ssl_version' is deprecated and will be removed in the future. + else: + # Use 'ssl_minimum_version' and 'ssl_maximum_version' instead. + ssl_minimum_version = _SSL_VERSION_TO_TLS_VERSION.get( + ssl_version, TLSVersion.MINIMUM_SUPPORTED + ) + ssl_maximum_version = _SSL_VERSION_TO_TLS_VERSION.get( + ssl_version, TLSVersion.MAXIMUM_SUPPORTED + ) + + # This warning message is pushing users to use 'ssl_minimum_version' + # instead of both min/max. Best practice is to only set the minimum version and + # keep the maximum version to be it's default value: 'TLSVersion.MAXIMUM_SUPPORTED' + warnings.warn( + "'ssl_version' option is deprecated and will be " + "removed in urllib3 v3.0. Instead use 'ssl_minimum_version'", + category=FutureWarning, + stacklevel=2, + ) + + context = SSLContext(PROTOCOL_TLS_CLIENT) + if ssl_minimum_version is not None: + context.minimum_version = ssl_minimum_version + else: # pyOpenSSL defaults to 'MINIMUM_SUPPORTED' so explicitly set TLSv1.2 here + context.minimum_version = TLSVersion.TLSv1_2 + + if ssl_maximum_version is not None: + context.maximum_version = ssl_maximum_version + + # Unless we're given ciphers defer to either system ciphers in + # the case of OpenSSL 1.1.1+ or use our own secure default ciphers. + if ciphers: + context.set_ciphers(ciphers) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + # TLSv1.2 only. Unless set explicitly, do not request tickets. + # This may save some bandwidth on wire, and although the ticket is encrypted, + # there is a risk associated with it being on wire, + # if the server is not rotating its ticketing keys properly. + options |= OP_NO_TICKET + + context.options |= options + + if verify_flags is None: + verify_flags = 0 + # In Python 3.13+ ssl.create_default_context() sets VERIFY_X509_PARTIAL_CHAIN + # and VERIFY_X509_STRICT so we do the same + if sys.version_info >= (3, 13): + verify_flags |= VERIFY_X509_PARTIAL_CHAIN + verify_flags |= VERIFY_X509_STRICT + + context.verify_flags |= verify_flags + + # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is + # necessary for conditional client cert authentication with TLS 1.3. + # The attribute is None for OpenSSL <= 1.1.0 or does not exist when using + # an SSLContext created by pyOpenSSL. + if getattr(context, "post_handshake_auth", None) is not None: + context.post_handshake_auth = True + + # The order of the below lines setting verify_mode and check_hostname + # matter due to safe-guards SSLContext has to prevent an SSLContext with + # check_hostname=True, verify_mode=NONE/OPTIONAL. + # We always set 'check_hostname=False' for pyOpenSSL so we rely on our own + # 'ssl.match_hostname()' implementation. + if cert_reqs == ssl.CERT_REQUIRED and not IS_PYOPENSSL: + context.verify_mode = cert_reqs + context.check_hostname = True + else: + context.check_hostname = False + context.verify_mode = cert_reqs + + context.hostname_checks_common_name = False + + if "SSLKEYLOGFILE" in os.environ: + sslkeylogfile = os.path.expandvars(os.environ.get("SSLKEYLOGFILE")) + else: + sslkeylogfile = None + if sslkeylogfile: + context.keylog_filename = sslkeylogfile + + return context + + +@typing.overload +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = ..., + certfile: str | None = ..., + cert_reqs: int | None = ..., + ca_certs: str | None = ..., + server_hostname: str | None = ..., + ssl_version: int | None = ..., + ciphers: str | None = ..., + ssl_context: ssl.SSLContext | None = ..., + ca_cert_dir: str | None = ..., + key_password: str | None = ..., + ca_cert_data: None | str | bytes = ..., + tls_in_tls: typing.Literal[False] = ..., +) -> ssl.SSLSocket: ... + + +@typing.overload +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = ..., + certfile: str | None = ..., + cert_reqs: int | None = ..., + ca_certs: str | None = ..., + server_hostname: str | None = ..., + ssl_version: int | None = ..., + ciphers: str | None = ..., + ssl_context: ssl.SSLContext | None = ..., + ca_cert_dir: str | None = ..., + key_password: str | None = ..., + ca_cert_data: None | str | bytes = ..., + tls_in_tls: bool = ..., +) -> ssl.SSLSocket | SSLTransportType: ... + + +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = None, + certfile: str | None = None, + cert_reqs: int | None = None, + ca_certs: str | None = None, + server_hostname: str | None = None, + ssl_version: int | None = None, + ciphers: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_cert_dir: str | None = None, + key_password: str | None = None, + ca_cert_data: None | str | bytes = None, + tls_in_tls: bool = False, +) -> ssl.SSLSocket | SSLTransportType: + """ + All arguments except for server_hostname, ssl_context, tls_in_tls, ca_cert_data and + ca_cert_dir have the same meaning as they do when using + :func:`ssl.create_default_context`, :meth:`ssl.SSLContext.load_cert_chain`, + :meth:`ssl.SSLContext.set_ciphers` and :meth:`ssl.SSLContext.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + :param key_password: + Optional password if the keyfile is encrypted. + :param ca_cert_data: + Optional string containing CA certificates in PEM format suitable for + passing as the cadata parameter to SSLContext.load_verify_locations() + :param tls_in_tls: + Use SSLTransport to wrap the existing socket. + """ + context = ssl_context + if context is None: + # Note: This branch of code and all the variables in it are only used in tests. + # We should consider deprecating and removing this code. + context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) + + if ca_certs or ca_cert_dir or ca_cert_data: + try: + context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) + except OSError as e: + raise SSLError(e) from e + + elif ssl_context is None and hasattr(context, "load_default_certs"): + # try to load OS default certs; works well on Windows. + context.load_default_certs() + + # Attempt to detect if we get the goofy behavior of the + # keyfile being encrypted and OpenSSL asking for the + # passphrase via the terminal and instead error out. + if keyfile and key_password is None and _is_key_file_encrypted(keyfile): + raise SSLError("Client private key is encrypted, password is required") + + if certfile: + if key_password is None: + context.load_cert_chain(certfile, keyfile) + else: + context.load_cert_chain(certfile, keyfile, key_password) + + context.set_alpn_protocols(ALPN_PROTOCOLS) + + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname) + return ssl_sock + + +def is_ipaddress(hostname: str | bytes) -> bool: + """Detects whether the hostname given is an IPv4 or IPv6 address. + Also detects IPv6 addresses with Zone IDs. + + :param str hostname: Hostname to examine. + :return: True if the hostname is an IP address, False otherwise. + """ + if isinstance(hostname, bytes): + # IDN A-label bytes are ASCII compatible. + hostname = hostname.decode("ascii") + return bool(_IPV4_RE.match(hostname) or _BRACELESS_IPV6_ADDRZ_RE.match(hostname)) + + +def _is_key_file_encrypted(key_file: str) -> bool: + """Detects if a key file is encrypted or not.""" + with open(key_file) as f: + for line in f: + # Look for Proc-Type: 4,ENCRYPTED + if "ENCRYPTED" in line: + return True + + return False + + +def _ssl_wrap_socket_impl( + sock: socket.socket, + ssl_context: ssl.SSLContext, + tls_in_tls: bool, + server_hostname: str | None = None, +) -> ssl.SSLSocket | SSLTransportType: + if tls_in_tls: + if not SSLTransport: + # Import error, ssl is not available. + raise ProxySchemeUnsupported( + "TLS in TLS requires support for the 'ssl' module" + ) + + SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) + return SSLTransport(sock, ssl_context, server_hostname) + + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/ssl_match_hostname.py b/.venv/lib/python3.14/site-packages/urllib3/util/ssl_match_hostname.py new file mode 100644 index 0000000000000000000000000000000000000000..94994f25ae1570211c0b98353790f669ef4585fa --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/ssl_match_hostname.py @@ -0,0 +1,153 @@ +"""The match_hostname() function from Python 3.5, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html +# It is modified to remove commonName support. + +from __future__ import annotations + +import ipaddress +import re +import typing +from ipaddress import IPv4Address, IPv6Address + +if typing.TYPE_CHECKING: + from .ssl_ import _TYPE_PEER_CERT_RET_DICT + +__version__ = "3.5.0.1" + + +class CertificateError(ValueError): + pass + + +def _dnsname_match( + dn: typing.Any, hostname: str, max_wildcards: int = 1 +) -> typing.Match[str] | None | bool: + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r".") + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count("*") + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn) + ) + + # speed up common case w/o wildcards + if not wildcards: + return bool(dn.lower() == hostname.lower()) + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == "*": + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append("[^.]+") + elif leftmost.startswith("xn--") or hostname.startswith("xn--"): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) + return pat.match(hostname) + + +def _ipaddress_match(ipname: str, host_ip: IPv4Address | IPv6Address) -> bool: + """Exact matching of IP addresses. + + RFC 9110 section 4.3.5: "A reference identity of IP-ID contains the decoded + bytes of the IP address. An IP version 4 address is 4 octets, and an IP + version 6 address is 16 octets. [...] A reference identity of type IP-ID + matches if the address is identical to an iPAddress value of the + subjectAltName extension of the certificate." + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + # Divergence from upstream: ipaddress can't handle byte str + ip = ipaddress.ip_address(ipname.rstrip()) + return bool(ip.packed == host_ip.packed) + + +def match_hostname( + cert: _TYPE_PEER_CERT_RET_DICT | None, + hostname: str, + hostname_checks_common_name: bool = False, +) -> None: + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError( + "empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED" + ) + + try: + host_ip = ipaddress.ip_address(hostname) + except ValueError: + # Not an IP address (common case) + host_ip = None + dnsnames = [] + san: tuple[tuple[str, str], ...] = cert.get("subjectAltName", ()) + key: str + value: str + for key, value in san: + if key == "DNS": + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == "IP Address": + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + + # We only check 'commonName' if it's enabled and we're not verifying + # an IP address. IP addresses aren't valid within 'commonName'. + if hostname_checks_common_name and host_ip is None and not dnsnames: + for sub in cert.get("subject", ()): + for key, value in sub: + if key == "commonName": + if _dnsname_match(value, hostname): + return + dnsnames.append( + value + ) # Defensive: for older PyPy and OpenSSL versions + + if len(dnsnames) > 1: + raise CertificateError( + "hostname %r " + "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) + ) + elif len(dnsnames) == 1: + raise CertificateError(f"hostname {hostname!r} doesn't match {dnsnames[0]!r}") + else: + raise CertificateError("no appropriate subjectAltName fields were found") diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/ssltransport.py b/.venv/lib/python3.14/site-packages/urllib3/util/ssltransport.py new file mode 100644 index 0000000000000000000000000000000000000000..6d59bc3bce2489c3a0aa5bcb83b737dcf33c033b --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/ssltransport.py @@ -0,0 +1,271 @@ +from __future__ import annotations + +import io +import socket +import ssl +import typing + +from ..exceptions import ProxySchemeUnsupported + +if typing.TYPE_CHECKING: + from typing_extensions import Self + + from .ssl_ import _TYPE_PEER_CERT_RET, _TYPE_PEER_CERT_RET_DICT + + +_WriteBuffer = typing.Union[bytearray, memoryview] +_ReturnValue = typing.TypeVar("_ReturnValue") + +SSL_BLOCKSIZE = 16384 + + +class SSLTransport: + """ + The SSLTransport wraps an existing socket and establishes an SSL connection. + + Contrary to Python's implementation of SSLSocket, it allows you to chain + multiple TLS connections together. It's particularly useful if you need to + implement TLS within TLS. + + The class supports most of the socket API operations. + """ + + @staticmethod + def _validate_ssl_context_for_tls_in_tls(ssl_context: ssl.SSLContext) -> None: + """ + Raises a ProxySchemeUnsupported if the provided ssl_context can't be used + for TLS in TLS. + + The only requirement is that the ssl_context provides the 'wrap_bio' + methods. + """ + + if not hasattr(ssl_context, "wrap_bio"): + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) + + def __init__( + self, + socket: socket.socket, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + suppress_ragged_eofs: bool = True, + ) -> None: + """ + Create an SSLTransport around socket using the provided ssl_context. + """ + self.incoming = ssl.MemoryBIO() + self.outgoing = ssl.MemoryBIO() + + self.suppress_ragged_eofs = suppress_ragged_eofs + self.socket = socket + + self.sslobj = ssl_context.wrap_bio( + self.incoming, self.outgoing, server_hostname=server_hostname + ) + + # Perform initial handshake. + self._ssl_io_loop(self.sslobj.do_handshake) + + def __enter__(self) -> Self: + return self + + def __exit__(self, *_: typing.Any) -> None: + self.close() + + def fileno(self) -> int: + return self.socket.fileno() + + def read(self, len: int = 1024, buffer: typing.Any | None = None) -> int | bytes: + return self._wrap_ssl_read(len, buffer) + + def recv(self, buflen: int = 1024, flags: int = 0) -> int | bytes: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv") + return self._wrap_ssl_read(buflen) + + def recv_into( + self, + buffer: _WriteBuffer, + nbytes: int | None = None, + flags: int = 0, + ) -> None | int | bytes: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv_into") + if nbytes is None: + nbytes = len(buffer) + return self.read(nbytes, buffer) + + def sendall(self, data: bytes, flags: int = 0) -> None: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to sendall") + count = 0 + with memoryview(data) as view, view.cast("B") as byte_view: + amount = len(byte_view) + while count < amount: + v = self.send(byte_view[count:]) + count += v + + def send(self, data: bytes, flags: int = 0) -> int: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to send") + return self._ssl_io_loop(self.sslobj.write, data) + + def makefile( + self, + mode: str, + buffering: int | None = None, + *, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> typing.BinaryIO | typing.TextIO | socket.SocketIO: + """ + Python's httpclient uses makefile and buffered io when reading HTTP + messages and we need to support it. + + This is unfortunately a copy and paste of socket.py makefile with small + changes to point to the socket directly. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError(f"invalid mode {mode!r} (only r, w, b allowed)") + + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = socket.SocketIO(self, rawmode) # type: ignore[arg-type] + self.socket._io_refs += 1 # type: ignore[attr-defined] + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + buffer: typing.BinaryIO + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) # type: ignore[assignment] + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode # type: ignore[misc] + return text + + def unwrap(self) -> None: + self._ssl_io_loop(self.sslobj.unwrap) + + def close(self) -> None: + self.socket.close() + + @typing.overload + def getpeercert( + self, binary_form: typing.Literal[False] = ... + ) -> _TYPE_PEER_CERT_RET_DICT | None: ... + + @typing.overload + def getpeercert(self, binary_form: typing.Literal[True]) -> bytes | None: ... + + def getpeercert(self, binary_form: bool = False) -> _TYPE_PEER_CERT_RET: + return self.sslobj.getpeercert(binary_form) # type: ignore[return-value] + + def version(self) -> str | None: + return self.sslobj.version() + + def cipher(self) -> tuple[str, str, int] | None: + return self.sslobj.cipher() + + def selected_alpn_protocol(self) -> str | None: + return self.sslobj.selected_alpn_protocol() + + def shared_ciphers(self) -> list[tuple[str, str, int]] | None: + return self.sslobj.shared_ciphers() + + def compression(self) -> str | None: + return self.sslobj.compression() + + def settimeout(self, value: float | None) -> None: + self.socket.settimeout(value) + + def gettimeout(self) -> float | None: + return self.socket.gettimeout() + + def _decref_socketios(self) -> None: + self.socket._decref_socketios() # type: ignore[attr-defined] + + def _wrap_ssl_read(self, len: int, buffer: bytearray | None = None) -> int | bytes: + try: + return self._ssl_io_loop(self.sslobj.read, len, buffer) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: + return 0 # eof, return 0. + else: + raise + + # func is sslobj.do_handshake or sslobj.unwrap + @typing.overload + def _ssl_io_loop(self, func: typing.Callable[[], None]) -> None: ... + + # func is sslobj.write, arg1 is data + @typing.overload + def _ssl_io_loop(self, func: typing.Callable[[bytes], int], arg1: bytes) -> int: ... + + # func is sslobj.read, arg1 is len, arg2 is buffer + @typing.overload + def _ssl_io_loop( + self, + func: typing.Callable[[int, bytearray | None], bytes], + arg1: int, + arg2: bytearray | None, + ) -> bytes: ... + + def _ssl_io_loop( + self, + func: typing.Callable[..., _ReturnValue], + arg1: None | bytes | int = None, + arg2: bytearray | None = None, + ) -> _ReturnValue: + """Performs an I/O loop between incoming/outgoing and the socket.""" + should_loop = True + ret = None + + while should_loop: + errno = None + try: + if arg1 is None and arg2 is None: + ret = func() + elif arg2 is None: + ret = func(arg1) + else: + ret = func(arg1, arg2) + except ssl.SSLError as e: + if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): + # WANT_READ, and WANT_WRITE are expected, others are not. + raise e + errno = e.errno + + buf = self.outgoing.read() + self.socket.sendall(buf) + + if errno is None: + should_loop = False + elif errno == ssl.SSL_ERROR_WANT_READ: + buf = self.socket.recv(SSL_BLOCKSIZE) + if buf: + self.incoming.write(buf) + else: + self.incoming.write_eof() + return typing.cast(_ReturnValue, ret) diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/timeout.py b/.venv/lib/python3.14/site-packages/urllib3/util/timeout.py new file mode 100644 index 0000000000000000000000000000000000000000..4bb1be11d9cb06900dd82ecebd06aa6a7c5de916 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/timeout.py @@ -0,0 +1,275 @@ +from __future__ import annotations + +import time +import typing +from enum import Enum +from socket import getdefaulttimeout + +from ..exceptions import TimeoutStateError + +if typing.TYPE_CHECKING: + from typing import Final + + +class _TYPE_DEFAULT(Enum): + # This value should never be passed to socket.settimeout() so for safety we use a -1. + # socket.settimout() raises a ValueError for negative values. + token = -1 + + +_DEFAULT_TIMEOUT: Final[_TYPE_DEFAULT] = _TYPE_DEFAULT.token + +_TYPE_TIMEOUT = typing.Optional[typing.Union[float, _TYPE_DEFAULT]] + + +class Timeout: + """Timeout configuration. + + Timeouts can be defined as a default for a pool: + + .. code-block:: python + + import urllib3 + + timeout = urllib3.util.Timeout(connect=2.0, read=7.0) + + http = urllib3.PoolManager(timeout=timeout) + + resp = http.request("GET", "https://example.com/") + + print(resp.status) + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request("GET", "https://example.com/", timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``: + + .. code-block:: python + + no_timeout = Timeout(connect=None, read=None) + response = http.request("GET", "https://example.com/", timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: int, float, or None + + :param connect: + The maximum amount of time (in seconds) to wait for a connection + attempt to a server to succeed. Omitting the parameter will default the + connect timeout to the system default, probably `the global default + timeout in socket.py + `_. + None will set an infinite timeout for connection attempts. + + :type connect: int, float, or None + + :param read: + The maximum amount of time (in seconds) to wait between consecutive + read operations for a response from the server. Omitting the parameter + will default the read timeout to the system default, probably `the + global default timeout in socket.py + `_. + None will set an infinite timeout. + + :type read: int, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT + + def __init__( + self, + total: _TYPE_TIMEOUT = None, + connect: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + read: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + ) -> None: + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") + self._start_connect: float | None = None + + def __repr__(self) -> str: + return f"{type(self).__name__}(connect={self._connect!r}, read={self._read!r}, total={self.total!r})" + + # __str__ provided for backwards compatibility + __str__ = __repr__ + + @staticmethod + def resolve_default_timeout(timeout: _TYPE_TIMEOUT) -> float | None: + return getdefaulttimeout() if timeout is _DEFAULT_TIMEOUT else timeout + + @classmethod + def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT: + """Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If it is a numeric value less than or equal to + zero, or the type is not an integer, float, or None. + """ + if value is None or value is _DEFAULT_TIMEOUT: + return value + + if isinstance(value, bool): + raise ValueError( + "Timeout cannot be a boolean value. It must " + "be an int, float or None." + ) + try: + float(value) + except (TypeError, ValueError): + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) from None + + try: + if value <= 0: + raise ValueError( + "Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value) + ) + except TypeError: + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) from None + + return value + + @classmethod + def from_float(cls, timeout: _TYPE_TIMEOUT) -> Timeout: + """Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, :attr:`urllib3.util.Timeout.DEFAULT_TIMEOUT`, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self) -> Timeout: + """Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, total=self.total) + + def start_connect(self) -> float: + """Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = time.monotonic() + return self._start_connect + + def get_connect_duration(self) -> float: + """Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time in seconds. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError( + "Can't get connect duration for timer that has not started." + ) + return time.monotonic() - self._start_connect + + @property + def connect_timeout(self) -> _TYPE_TIMEOUT: + """Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is _DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) # type: ignore[type-var] + + @property + def read_timeout(self) -> float | None: + """Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if ( + self.total is not None + and self.total is not _DEFAULT_TIMEOUT + and self._read is not None + and self._read is not _DEFAULT_TIMEOUT + ): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), self._read)) + elif self.total is not None and self.total is not _DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self.resolve_default_timeout(self._read) diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/url.py b/.venv/lib/python3.14/site-packages/urllib3/util/url.py new file mode 100644 index 0000000000000000000000000000000000000000..db057f17be610174f30928748b5004dcbf6c501c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/url.py @@ -0,0 +1,469 @@ +from __future__ import annotations + +import re +import typing + +from ..exceptions import LocationParseError +from .util import to_str + +# We only want to normalize urls with an HTTP(S) scheme. +# urllib3 infers URLs without a scheme (None) to be http. +_NORMALIZABLE_SCHEMES = ("http", "https", None) + +# Almost all of these patterns were derived from the +# 'rfc3986' module: https://github.com/python-hyper/rfc3986 +_PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +_SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +_URI_RE = re.compile( + r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" + r"(?://([^\\/?#]*))?" + r"([^?#]*)" + r"(?:\?([^#]*))?" + r"(?:#(.*))?$", + re.UNICODE | re.DOTALL, +) + +_IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +_HEX_PAT = "[0-9A-Fa-f]{1,4}" +_LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=_HEX_PAT, ipv4=_IPV4_PAT) +_subs = {"hex": _HEX_PAT, "ls32": _LS32_PAT} +_variations = [ + # 6( h16 ":" ) ls32 + "(?:%(hex)s:){6}%(ls32)s", + # "::" 5( h16 ":" ) ls32 + "::(?:%(hex)s:){5}%(ls32)s", + # [ h16 ] "::" 4( h16 ":" ) ls32 + "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s", + # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s", + # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s", + # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s", + # [ *4( h16 ":" ) h16 ] "::" ls32 + "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s", + # [ *5( h16 ":" ) h16 ] "::" h16 + "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s", + # [ *6( h16 ":" ) h16 ] "::" + "(?:(?:%(hex)s:){0,6}%(hex)s)?::", +] + +_UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~" +_IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +_ZONE_ID_PAT = "(?:%25|%)(?:[" + _UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +_IPV6_ADDRZ_PAT = r"\[" + _IPV6_PAT + r"(?:" + _ZONE_ID_PAT + r")?\]" +_REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +_TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") + +_IPV4_RE = re.compile("^" + _IPV4_PAT + "$") +_IPV6_RE = re.compile("^" + _IPV6_PAT + "$") +_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT + "$") +_BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT[2:-2] + "$") +_ZONE_ID_RE = re.compile("(" + _ZONE_ID_PAT + r")\]$") + +_HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*?(|0|[1-9][0-9]{0,4}))?$") % ( + _REG_NAME_PAT, + _IPV4_PAT, + _IPV6_ADDRZ_PAT, +) +_HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) + +_UNRESERVED_CHARS = set( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" +) +_SUB_DELIM_CHARS = set("!$&'()*+,;=") +_USERINFO_CHARS = _UNRESERVED_CHARS | _SUB_DELIM_CHARS | {":"} +_PATH_CHARS = _USERINFO_CHARS | {"@", "/"} +_QUERY_CHARS = _FRAGMENT_CHARS = _PATH_CHARS | {"?"} + + +class Url( + typing.NamedTuple( + "Url", + [ + ("scheme", typing.Optional[str]), + ("auth", typing.Optional[str]), + ("host", typing.Optional[str]), + ("port", typing.Optional[int]), + ("path", typing.Optional[str]), + ("query", typing.Optional[str]), + ("fragment", typing.Optional[str]), + ], + ) +): + """ + Data structure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. Both the scheme and host are normalized as they are + both case-insensitive according to RFC 3986. + """ + + def __new__( # type: ignore[no-untyped-def] + cls, + scheme: str | None = None, + auth: str | None = None, + host: str | None = None, + port: int | None = None, + path: str | None = None, + query: str | None = None, + fragment: str | None = None, + ): + if path and not path.startswith("/"): + path = "/" + path + if scheme is not None: + scheme = scheme.lower() + return super().__new__(cls, scheme, auth, host, port, path, query, fragment) + + @property + def hostname(self) -> str | None: + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self) -> str: + """Absolute path including the query string.""" + uri = self.path or "/" + + if self.query is not None: + uri += "?" + self.query + + return uri + + @property + def authority(self) -> str | None: + """ + Authority component as defined in RFC 3986 3.2. + This includes userinfo (auth), host and port. + + i.e. + userinfo@host:port + """ + userinfo = self.auth + netloc = self.netloc + if netloc is None or userinfo is None: + return netloc + else: + return f"{userinfo}@{netloc}" + + @property + def netloc(self) -> str | None: + """ + Network location including host and port. + + If you need the equivalent of urllib.parse's ``netloc``, + use the ``authority`` property instead. + """ + if self.host is None: + return None + if self.port: + return f"{self.host}:{self.port}" + return self.host + + @property + def url(self) -> str: + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: + + .. code-block:: python + + import urllib3 + + U = urllib3.util.parse_url("https://google.com/mail/") + + print(U.url) + # "https://google.com/mail/" + + print( urllib3.util.Url("https", "username:password", + "host.com", 80, "/path", "query", "fragment" + ).url + ) + # "https://username:password@host.com:80/path?query#fragment" + """ + scheme, auth, host, port, path, query, fragment = self + url = "" + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + "://" + if auth is not None: + url += auth + "@" + if host is not None: + url += host + if port is not None: + url += ":" + str(port) + if path is not None: + url += path + if query is not None: + url += "?" + query + if fragment is not None: + url += "#" + fragment + + return url + + def __str__(self) -> str: + return self.url + + +@typing.overload +def _encode_invalid_chars( + component: str, allowed_chars: typing.Container[str] +) -> str: # Abstract + ... + + +@typing.overload +def _encode_invalid_chars( + component: None, allowed_chars: typing.Container[str] +) -> None: # Abstract + ... + + +def _encode_invalid_chars( + component: str | None, allowed_chars: typing.Container[str] +) -> str | None: + """Percent-encodes a URI component without reapplying + onto an already percent-encoded component. + """ + if component is None: + return component + + component = to_str(component) + + # Normalize existing percent-encoded bytes. + # Try to see if the component we're encoding is already percent-encoded + # so we can skip all '%' characters but still encode all others. + component, percent_encodings = _PERCENT_RE.subn( + lambda match: match.group(0).upper(), component + ) + + uri_bytes = component.encode("utf-8", "surrogatepass") + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") + encoded_component = bytearray() + + for i in range(0, len(uri_bytes)): + # Will return a single character bytestring + byte = uri_bytes[i : i + 1] + byte_ord = ord(byte) + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in allowed_chars + ): + encoded_component += byte + continue + encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) + + return encoded_component.decode() + + +def _remove_path_dot_segments(path: str) -> str: + # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code + segments = path.split("/") # Turn the path into a list of segments + output = [] # Initialize the variable to use to store output + + for segment in segments: + # '.' is the current directory, so ignore it, it is superfluous + if segment == ".": + continue + # Anything other than '..', should be appended to the output + if segment != "..": + output.append(segment) + # In this case segment == '..', if we can, we should pop the last + # element + elif output: + output.pop() + + # If the path starts with '/' and the output is empty or the first string + # is non-empty + if path.startswith("/") and (not output or output[0]): + output.insert(0, "") + + # If the path starts with '/.' or '/..' ensure we add one more empty + # string to add a trailing '/' + if path.endswith(("/.", "/..")): + output.append("") + + return "/".join(output) + + +@typing.overload +def _normalize_host(host: None, scheme: str | None) -> None: ... + + +@typing.overload +def _normalize_host(host: str, scheme: str | None) -> str: ... + + +def _normalize_host(host: str | None, scheme: str | None) -> str | None: + if host: + if scheme in _NORMALIZABLE_SCHEMES: + is_ipv6 = _IPV6_ADDRZ_RE.match(host) + if is_ipv6: + # IPv6 hosts of the form 'a::b%zone' are encoded in a URL as + # such per RFC 6874: 'a::b%25zone'. Unquote the ZoneID + # separator as necessary to return a valid RFC 4007 scoped IP. + match = _ZONE_ID_RE.search(host) + if match: + start, end = match.span(1) + zone_id = host[start:end] + + if zone_id.startswith("%25") and zone_id != "%25": + zone_id = zone_id[3:] + else: + zone_id = zone_id[1:] + zone_id = _encode_invalid_chars(zone_id, _UNRESERVED_CHARS) + return f"{host[:start].lower()}%{zone_id}{host[end:]}" + else: + return host.lower() + elif not _IPV4_RE.match(host): + return to_str( + b".".join([_idna_encode(label) for label in host.split(".")]), + "ascii", + ) + return host + + +def _idna_encode(name: str) -> bytes: + if not name.isascii(): + try: + import idna + except ImportError: + raise LocationParseError( + "Unable to parse URL without the 'idna' module" + ) from None + + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + raise LocationParseError( + f"Name '{name}' is not a valid IDNA label" + ) from None + + return name.lower().encode("ascii") + + +def _encode_target(target: str) -> str: + """Percent-encodes a request target so that there are no invalid characters + + Pre-condition for this function is that 'target' must start with '/'. + If that is the case then _TARGET_RE will always produce a match. + """ + match = _TARGET_RE.match(target) + if not match: # Defensive: + raise LocationParseError(f"{target!r} is not a valid request URI") + + path, query = match.groups() + encoded_target = _encode_invalid_chars(path, _PATH_CHARS) + if query is not None: + query = _encode_invalid_chars(query, _QUERY_CHARS) + encoded_target += "?" + query + return encoded_target + + +def parse_url(url: str) -> Url: + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + This parser is RFC 3986 and RFC 6874 compliant. + + The parser logic and helper functions are based heavily on + work done in the ``rfc3986`` module. + + :param str url: URL to parse into a :class:`.Url` namedtuple. + + Partly backwards-compatible with :mod:`urllib.parse`. + + Example: + + .. code-block:: python + + import urllib3 + + print( urllib3.util.parse_url('http://google.com/mail/')) + # Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + + print( urllib3.util.parse_url('google.com:80')) + # Url(scheme=None, host='google.com', port=80, path=None, ...) + + print( urllib3.util.parse_url('/foo?bar')) + # Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + if not url: + # Empty + return Url() + + source_url = url + if not _SCHEME_RE.search(url): + url = "//" + url + + scheme: str | None + authority: str | None + auth: str | None + host: str | None + port: str | None + port_int: int | None + path: str | None + query: str | None + fragment: str | None + + try: + scheme, authority, path, query, fragment = _URI_RE.match(url).groups() # type: ignore[union-attr] + normalize_uri = scheme is None or scheme.lower() in _NORMALIZABLE_SCHEMES + + if scheme: + scheme = scheme.lower() + + if authority: + auth, _, host_port = authority.rpartition("@") + auth = auth or None + host, port = _HOST_PORT_RE.match(host_port).groups() # type: ignore[union-attr] + if auth and normalize_uri: + auth = _encode_invalid_chars(auth, _USERINFO_CHARS) + if port == "": + port = None + else: + auth, host, port = None, None, None + + if port is not None: + port_int = int(port) + if not (0 <= port_int <= 65535): + raise LocationParseError(url) + else: + port_int = None + + host = _normalize_host(host, scheme) + + if normalize_uri and path: + path = _remove_path_dot_segments(path) + path = _encode_invalid_chars(path, _PATH_CHARS) + if normalize_uri and query: + query = _encode_invalid_chars(query, _QUERY_CHARS) + if normalize_uri and fragment: + fragment = _encode_invalid_chars(fragment, _FRAGMENT_CHARS) + + except (ValueError, AttributeError) as e: + raise LocationParseError(source_url) from e + + # For the sake of backwards compatibility we put empty + # string values for path if there are any defined values + # beyond the path in the URL. + # TODO: Remove this when we break backwards compatibility. + if not path: + if query is not None or fragment is not None: + path = "" + else: + path = None + + return Url( + scheme=scheme, + auth=auth, + host=host, + port=port_int, + path=path, + query=query, + fragment=fragment, + ) diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/util.py b/.venv/lib/python3.14/site-packages/urllib3/util/util.py new file mode 100644 index 0000000000000000000000000000000000000000..35c77e4025842f548565334a3c04cba90f9283d6 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/util.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import typing +from types import TracebackType + + +def to_bytes( + x: str | bytes, encoding: str | None = None, errors: str | None = None +) -> bytes: + if isinstance(x, bytes): + return x + elif not isinstance(x, str): + raise TypeError(f"not expecting type {type(x).__name__}") + if encoding or errors: + return x.encode(encoding or "utf-8", errors=errors or "strict") + return x.encode() + + +def to_str( + x: str | bytes, encoding: str | None = None, errors: str | None = None +) -> str: + if isinstance(x, str): + return x + elif not isinstance(x, bytes): + raise TypeError(f"not expecting type {type(x).__name__}") + if encoding or errors: + return x.decode(encoding or "utf-8", errors=errors or "strict") + return x.decode() + + +def reraise( + tp: type[BaseException] | None, + value: BaseException, + tb: TracebackType | None = None, +) -> typing.NoReturn: + try: + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None # type: ignore[assignment] + tb = None diff --git a/.venv/lib/python3.14/site-packages/urllib3/util/wait.py b/.venv/lib/python3.14/site-packages/urllib3/util/wait.py new file mode 100644 index 0000000000000000000000000000000000000000..aeca0c7ad5b232eeb1ad9c43d315bd1d74eaed9a --- /dev/null +++ b/.venv/lib/python3.14/site-packages/urllib3/util/wait.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +import select +import socket +from functools import partial + +__all__ = ["wait_for_read", "wait_for_write"] + + +# How should we wait on sockets? +# +# There are two types of APIs you can use for waiting on sockets: the fancy +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like +# select/poll. The stateful APIs are more efficient when you have a lots of +# sockets to keep track of, because you can set them up once and then use them +# lots of times. But we only ever want to wait on a single socket at a time +# and don't want to keep track of state, so the stateless APIs are actually +# more efficient. So we want to use select() or poll(). +# +# Now, how do we choose between select() and poll()? On traditional Unixes, +# select() has a strange calling convention that makes it slow, or fail +# altogether, for high-numbered file descriptors. The point of poll() is to fix +# that, so on Unixes, we prefer poll(). +# +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper +# for it), but that's OK, because on Windows, select() doesn't have this +# strange calling convention; plain select() works fine. +# +# So: on Windows we use select(), and everywhere else we use poll(). We also +# fall back to select() in case poll() is somehow broken or missing. + + +def select_wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + rcheck = [] + wcheck = [] + if read: + rcheck.append(sock) + if write: + wcheck.append(sock) + # When doing a non-blocking connect, most systems signal success by + # marking the socket writable. Windows, though, signals success by marked + # it as "exceptional". We paper over the difference by checking the write + # sockets for both conditions. (The stdlib selectors module does the same + # thing.) + fn = partial(select.select, rcheck, wcheck, wcheck) + rready, wready, xready = fn(timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + mask = 0 + if read: + mask |= select.POLLIN + if write: + mask |= select.POLLOUT + poll_obj = select.poll() + poll_obj.register(sock, mask) + + # For some reason, poll() takes timeout in milliseconds + def do_poll(t: float | None) -> list[tuple[int, int]]: + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(do_poll(timeout)) + + +def _have_working_poll() -> bool: + # Apparently some systems have a select.poll that fails as soon as you try + # to use it, either due to strange configuration or broken monkeypatching + # from libraries like eventlet/greenlet. + try: + poll_obj = select.poll() + poll_obj.poll(0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + # We delay choosing which implementation to use until the first time we're + # called. We could do it at import time, but then we might make the wrong + # decision if someone goes wild with monkeypatching select.poll after + # we're imported. + global wait_for_socket + if _have_working_poll(): + wait_for_socket = poll_wait_for_socket + elif hasattr(select, "select"): + wait_for_socket = select_wait_for_socket + return wait_for_socket(sock, read, write, timeout) + + +def wait_for_read(sock: socket.socket, timeout: float | None = None) -> bool: + """Waits for reading to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, read=True, timeout=timeout) + + +def wait_for_write(sock: socket.socket, timeout: float | None = None) -> bool: + """Waits for writing to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/INSTALLER b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/METADATA b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..390a4581fe76a6a34757c0ee517c85ec69adc377 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/METADATA @@ -0,0 +1,191 @@ +Metadata-Version: 2.4 +Name: uvicorn +Version: 0.49.0 +Summary: The lightning-fast ASGI server. +Project-URL: Changelog, https://uvicorn.dev/release-notes +Project-URL: Funding, https://github.com/sponsors/encode +Project-URL: Homepage, https://uvicorn.dev/ +Project-URL: Source, https://github.com/Kludex/uvicorn +Author-email: Tom Christie +Maintainer-email: Marcelo Trylesinski +License-Expression: BSD-3-Clause +License-File: LICENSE.md +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.10 +Requires-Dist: click>=7.0 +Requires-Dist: h11>=0.8 +Requires-Dist: typing-extensions>=4.0; python_version < '3.11' +Provides-Extra: standard +Requires-Dist: colorama>=0.4; (sys_platform == 'win32') and extra == 'standard' +Requires-Dist: httptools>=0.8.0; extra == 'standard' +Requires-Dist: python-dotenv>=0.13; extra == 'standard' +Requires-Dist: pyyaml>=5.1; extra == 'standard' +Requires-Dist: uvloop>=0.15.1; (sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')) and extra == 'standard' +Requires-Dist: watchfiles>=0.20; extra == 'standard' +Requires-Dist: websockets>=10.4; extra == 'standard' +Description-Content-Type: text/markdown + +

+ uvicorn +

+ +

+An ASGI web server, for Python. +

+ +--- + +[![Build Status](https://github.com/Kludex/uvicorn/workflows/Test%20Suite/badge.svg)](https://github.com/Kludex/uvicorn/actions) +[![Package version](https://badge.fury.io/py/uvicorn.svg)](https://pypi.python.org/pypi/uvicorn) +[![Supported Python Version](https://img.shields.io/pypi/pyversions/uvicorn.svg?color=%2334D058)](https://pypi.org/project/uvicorn) +[![Discord](https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/RxKUF5JuHs) + +--- + +**Documentation**: [https://uvicorn.dev](https://uvicorn.dev) + +**Source Code**: [https://www.github.com/Kludex/uvicorn](https://www.github.com/Kludex/uvicorn) + +--- + +Uvicorn is an ASGI web server implementation for Python. + +Until recently Python has lacked a minimal low-level server/application interface for +async frameworks. The [ASGI specification][asgi] fills this gap, and means we're now able to +start building a common set of tooling usable across all async frameworks. + +Uvicorn supports HTTP/1.1 and WebSockets. + +## Quickstart + +Install using `pip`: + +```shell +$ pip install uvicorn +``` + +This will install uvicorn with minimal (pure Python) dependencies. + +```shell +$ pip install 'uvicorn[standard]' +``` + +This will install uvicorn with "Cython-based" dependencies (where possible) and other "optional extras". + +In this context, "Cython-based" means the following: + +- the event loop `uvloop` will be installed and used if possible. +- the http protocol will be handled by `httptools` if possible. + +Moreover, "optional extras" means that: + +- the websocket protocol will be handled by `websockets` (should you want to use `wsproto` you'd need to install it manually) if possible. +- the `--reload` flag in development mode will use `watchfiles`. +- windows users will have `colorama` installed for the colored logs. +- `python-dotenv` will be installed should you want to use the `--env-file` option. +- `PyYAML` will be installed to allow you to provide a `.yaml` file to `--log-config`, if desired. + +Create an application, in `example.py`: + +```python +async def app(scope, receive, send): + assert scope['type'] == 'http' + + await send({ + 'type': 'http.response.start', + 'status': 200, + 'headers': [ + (b'content-type', b'text/plain'), + ], + }) + await send({ + 'type': 'http.response.body', + 'body': b'Hello, world!', + }) +``` + +Run the server: + +```shell +$ uvicorn example:app +``` + +--- + +## Why ASGI? + +Most well established Python Web frameworks started out as WSGI-based frameworks. + +WSGI applications are a single, synchronous callable that takes a request and returns a response. +This doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections, +which WSGI doesn't support well. + +Having an async concurrency model also allows for options such as lightweight background tasks, +and can be less of a limiting factor for endpoints that have long periods being blocked on network +I/O such as dealing with slow HTTP requests. + +--- + +## Alternative ASGI servers + +A strength of the ASGI protocol is that it decouples the server implementation +from the application framework. This allows for an ecosystem of interoperating +webservers and application frameworks. + +### Daphne + +The first ASGI server implementation, originally developed to power Django Channels, is [the Daphne webserver][daphne]. + +It is run widely in production, and supports HTTP/1.1, HTTP/2, and WebSockets. + +Any of the example applications given here can equally well be run using `daphne` instead. + +``` +$ pip install daphne +$ daphne app:App +``` + +### Hypercorn + +[Hypercorn][hypercorn] was initially part of the Quart web framework, before +being separated out into a standalone ASGI server. + +Hypercorn supports HTTP/1.1, HTTP/2, and WebSockets. + +It also supports [the excellent `trio` async framework][trio], as an alternative to `asyncio`. + +``` +$ pip install hypercorn +$ hypercorn app:App +``` + +### Mangum + +[Mangum][mangum] is an adapter for using ASGI applications with AWS Lambda & API Gateway. + +### Granian + +[Granian][granian] is an ASGI compatible Rust HTTP server which supports HTTP/2, TLS and WebSockets. + +--- + +

Uvicorn is BSD licensed code.
Designed & crafted with care.

— 🦄 —

+ +[asgi]: https://asgi.readthedocs.io/en/latest/ +[daphne]: https://github.com/django/daphne +[hypercorn]: https://github.com/pgjones/hypercorn +[trio]: https://trio.readthedocs.io +[mangum]: https://github.com/jordaneremieff/mangum +[granian]: https://github.com/emmett-framework/granian diff --git a/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/RECORD b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..82dd7e25b8c18cfc21b6de7beca14588565c135d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/RECORD @@ -0,0 +1,89 @@ +../../../bin/uvicorn,sha256=QN5BtAoYDE0CyO9uBgmEQhyau-kbE0CYsYR__8oZCx8,210 +uvicorn-0.49.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +uvicorn-0.49.0.dist-info/METADATA,sha256=xOeLMlH-BitEF0pF64qdEHEly7vgYuVBJ2wDxQ6RFDs,6738 +uvicorn-0.49.0.dist-info/RECORD,, +uvicorn-0.49.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn-0.49.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87 +uvicorn-0.49.0.dist-info/entry_points.txt,sha256=FW1w-hkc9QgwaGoovMvm0ZY73w_NcycWdGAUfDsNGxw,46 +uvicorn-0.49.0.dist-info/licenses/LICENSE.md,sha256=7-Gs8-YvuZwoiw7HPlp3O3Jo70Mg_nV-qZQhTktjw3E,1526 +uvicorn/__init__.py,sha256=dODKUIVij4EJeg9czHoZ1OATfCe3I_5KJK6LiQIMhZA,147 +uvicorn/__main__.py,sha256=DQizy6nKP0ywhPpnCHgmRDYIMfcqZKVEzNIWQZjqtVQ,62 +uvicorn/__pycache__/__init__.cpython-314.pyc,, +uvicorn/__pycache__/__main__.cpython-314.pyc,, +uvicorn/__pycache__/_compat.cpython-314.pyc,, +uvicorn/__pycache__/_subprocess.cpython-314.pyc,, +uvicorn/__pycache__/_types.cpython-314.pyc,, +uvicorn/__pycache__/config.cpython-314.pyc,, +uvicorn/__pycache__/importer.cpython-314.pyc,, +uvicorn/__pycache__/logging.cpython-314.pyc,, +uvicorn/__pycache__/main.cpython-314.pyc,, +uvicorn/__pycache__/server.cpython-314.pyc,, +uvicorn/__pycache__/workers.cpython-314.pyc,, +uvicorn/_compat.py,sha256=6X49c9ovzmMHir2S3syinOQMzSPBcp8MNuI9ZQrbYiU,2916 +uvicorn/_subprocess.py,sha256=fQ4JTajuXay6mVc8KQNGfXu6JHoab46aS6kyIMWJMPk,2776 +uvicorn/_types.py,sha256=JDM6E-hqZ6o_cSZvjDULjAv77PFH2pLe_Vgqshe8qH4,7677 +uvicorn/config.py,sha256=UhYkFgqeVqj38FZb6vwIn3qyVWYjr7u9eJfNP2LPhfc,23995 +uvicorn/importer.py,sha256=nRt0QQ3qpi264-n_mR0l55C2ddM8nowTNzT1jsWaam8,1128 +uvicorn/lifespan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/lifespan/__pycache__/__init__.cpython-314.pyc,, +uvicorn/lifespan/__pycache__/off.cpython-314.pyc,, +uvicorn/lifespan/__pycache__/on.cpython-314.pyc,, +uvicorn/lifespan/off.py,sha256=nfI6qHAUo_8-BEXMBKoHQ9wUbsXrPaXLCbDSS0vKSr8,332 +uvicorn/lifespan/on.py,sha256=iETPFj6SFhPDPcdeGxfa43LKImJU7MI5i7QUUepBcPE,5173 +uvicorn/logging.py,sha256=IBPSo2qRBWJRj5C2nN7I1civFAj9jKZ5Azxk0JpPB98,4235 +uvicorn/loops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/loops/__pycache__/__init__.cpython-314.pyc,, +uvicorn/loops/__pycache__/asyncio.cpython-314.pyc,, +uvicorn/loops/__pycache__/auto.cpython-314.pyc,, +uvicorn/loops/__pycache__/uvloop.cpython-314.pyc,, +uvicorn/loops/asyncio.py,sha256=a8wO4fB4pCk-RLICq7CnGr_cxQI7MBLJj-Go4UQldD8,333 +uvicorn/loops/auto.py,sha256=pdTsMcqkbE8fvdNgLs1y4c6hsuN598mZOSOlC0a23S4,566 +uvicorn/loops/uvloop.py,sha256=Ca-TL-W8tbdEW5uvL_GrMih7G8e_t_vhNevi2NKYjEU,236 +uvicorn/main.py,sha256=UdyHBAwxHz9_NYB3uzrn8XVnpL2PrX4JrPulJiFtWx8,18785 +uvicorn/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/middleware/__pycache__/__init__.cpython-314.pyc,, +uvicorn/middleware/__pycache__/asgi2.cpython-314.pyc,, +uvicorn/middleware/__pycache__/message_logger.cpython-314.pyc,, +uvicorn/middleware/__pycache__/proxy_headers.cpython-314.pyc,, +uvicorn/middleware/__pycache__/wsgi.cpython-314.pyc,, +uvicorn/middleware/asgi2.py,sha256=YQrQNm3RehFts3mzk3k4yw8aD8Egtj0tRS3N45YkQa0,394 +uvicorn/middleware/message_logger.py,sha256=IHEZUSnFNaMFUFdwtZO3AuFATnYcSor-gVtOjbCzt8M,2859 +uvicorn/middleware/proxy_headers.py,sha256=2CEkCr_CsQYcNzZdnqkha8WB7vH6oX_S2sePHCOJZHc,7078 +uvicorn/middleware/wsgi.py,sha256=VnbnkdCL3_ctLWzk1j39xvxysuoAB3kSYmi8TsOLpNs,7125 +uvicorn/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/protocols/__pycache__/__init__.cpython-314.pyc,, +uvicorn/protocols/__pycache__/utils.cpython-314.pyc,, +uvicorn/protocols/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/protocols/http/__pycache__/__init__.cpython-314.pyc,, +uvicorn/protocols/http/__pycache__/auto.cpython-314.pyc,, +uvicorn/protocols/http/__pycache__/flow_control.cpython-314.pyc,, +uvicorn/protocols/http/__pycache__/h11_impl.cpython-314.pyc,, +uvicorn/protocols/http/__pycache__/httptools_impl.cpython-314.pyc,, +uvicorn/protocols/http/auto.py,sha256=YfXGyzWTaaE2p_jkTPWrJCXsxEaQnC3NK0-G7Wgmnls,403 +uvicorn/protocols/http/flow_control.py,sha256=050WVg31EvPOkHwynCoMP1zXFl_vO3U4durlc5vyp4U,1701 +uvicorn/protocols/http/h11_impl.py,sha256=K7SHLVsYORBde6cUGNwDt9rbS2vPfUbLkzbVOnEYeDk,21161 +uvicorn/protocols/http/httptools_impl.py,sha256=zJN4yQl26yzPqdyjYOFYimssb_Z8JfURMbJy8sqxLI0,22314 +uvicorn/protocols/utils.py,sha256=1hKdDCmo_GtGrK1zZfa70jL7Hr3mDE8RnL69O4rHQRg,2058 +uvicorn/protocols/websockets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +uvicorn/protocols/websockets/__pycache__/__init__.cpython-314.pyc,, +uvicorn/protocols/websockets/__pycache__/auto.cpython-314.pyc,, +uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-314.pyc,, +uvicorn/protocols/websockets/__pycache__/websockets_sansio_impl.cpython-314.pyc,, +uvicorn/protocols/websockets/__pycache__/wsproto_impl.cpython-314.pyc,, +uvicorn/protocols/websockets/auto.py,sha256=pAEMIsyXQ12CL6ffaEJQ2cQEC8yAr8sr7bC0H6hzBfs,590 +uvicorn/protocols/websockets/websockets_impl.py,sha256=O2EW1zGQmcB65-kbxkA0XnU-sbSbPSsUKvzh8N-QQoQ,14849 +uvicorn/protocols/websockets/websockets_sansio_impl.py,sha256=KnCKcpF7VEpu5wxudtk0u7SmwY2O3dk52Q8rrQ614Bs,20198 +uvicorn/protocols/websockets/wsproto_impl.py,sha256=JKmDJJ-tcb9PVvNkpP_wz-RsvonfwmSbDKWEX7GSNM4,19538 +uvicorn/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +uvicorn/server.py,sha256=fXk3__HBf1g-oh_4_OXIpb8tnTyafxuWRiEet6SQEvM,13368 +uvicorn/supervisors/__init__.py,sha256=wT8eOEIqT1yWQgytZtv5taWMul7xoTIY0xm1m4oyPTw,507 +uvicorn/supervisors/__pycache__/__init__.cpython-314.pyc,, +uvicorn/supervisors/__pycache__/basereload.cpython-314.pyc,, +uvicorn/supervisors/__pycache__/multiprocess.cpython-314.pyc,, +uvicorn/supervisors/__pycache__/statreload.cpython-314.pyc,, +uvicorn/supervisors/__pycache__/watchfilesreload.cpython-314.pyc,, +uvicorn/supervisors/basereload.py,sha256=mYOPopNS6fVTrqfQ4HzclH7Xw-VVQy7ZDSINv5_lu04,4018 +uvicorn/supervisors/multiprocess.py,sha256=_lU92Bnf_IqibFmmNoojY3J3fR6LzSBa76BpmcSMfHc,7580 +uvicorn/supervisors/statreload.py,sha256=XPaEtVXaasII1kkG32UUG8oD7-fS0FwyXkQLF9QYPbA,1548 +uvicorn/supervisors/watchfilesreload.py,sha256=u-MuYm0LsZMN670u_TtSWiMBUA2z4j1sIe3O2A67Ep4,2923 +uvicorn/workers.py,sha256=lMCubn1Wi-4h2jxrXNMmhOJZy7ZP8QxbZbP3_2O1iHE,3873 diff --git a/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/REQUESTED b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/WHEEL b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..b1b94fd58e7e9ed0ef3449473bc48de68afcc3fe --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.29.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/entry_points.txt b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..4b00fcb60173c5497fb24839da1837e8bdbfd98c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +uvicorn = uvicorn.main:main diff --git a/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/licenses/LICENSE.md b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..a6bba14552c7673a7db57a5750ddb06508264273 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn-0.49.0.dist-info/licenses/LICENSE.md @@ -0,0 +1,27 @@ +Copyright © 2017-present, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__init__.py b/.venv/lib/python3.14/site-packages/uvicorn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2a2760ac715e8c9bc0a4942223e7d5b090c75d9f --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/__init__.py @@ -0,0 +1,5 @@ +from uvicorn.config import Config +from uvicorn.main import Server, main, run + +__version__ = "0.49.0" +__all__ = ["main", "run", "Config", "Server"] diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__main__.py b/.venv/lib/python3.14/site-packages/uvicorn/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..8a1dc979a39e36ce81e987843391479f125d73f3 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/__main__.py @@ -0,0 +1,4 @@ +import uvicorn + +if __name__ == "__main__": + uvicorn.main() diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f309d0b7d056e91ecf1998c59ebf47f117afd02 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/__main__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/__main__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb06b8b126c4799acb17c4382dcd391cc5b226b6 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/__main__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/_compat.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/_compat.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f4f82df448bc33de47ebd2c3e820a3332e7cb3b Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/_compat.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/_subprocess.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/_subprocess.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..affe0f001707037f26472c875425bca966ab72c9 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/_subprocess.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/_types.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/_types.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e935cf3be7f7c46f269daaa7742fcc943a501736 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/_types.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/config.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99052f2c1d4ef099a708adfddaef13bfa9930001 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/config.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/importer.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/importer.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..39be5c8483fed989f40722fa0cfd238570886c98 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/importer.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/logging.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/logging.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdece7df30d08cf02b2f647e019a0c868aaa32f7 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/logging.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/main.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/main.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff4f937a140e104d87ccace6423e24d87866c07a Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/main.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/server.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/server.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82bfcdb200bff865ac449141b3ebe2b588d1fc73 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/server.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/workers.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/workers.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e00554c3774f4e560295fcb2d189ffdf56cb8aa Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/__pycache__/workers.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/_compat.py b/.venv/lib/python3.14/site-packages/uvicorn/_compat.py new file mode 100644 index 0000000000000000000000000000000000000000..118a19ac0c43e518a24cf1108bfbb9dde507bd0c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/_compat.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import asyncio +import sys +from collections.abc import Callable, Coroutine +from typing import Any, TypeVar + +__all__ = ["asyncio_run", "iscoroutinefunction"] + +if sys.version_info >= (3, 14): + from inspect import iscoroutinefunction +else: + from asyncio import iscoroutinefunction + +_T = TypeVar("_T") + +if sys.version_info >= (3, 12): + asyncio_run = asyncio.run +elif sys.version_info >= (3, 11): + + def asyncio_run( + main: Coroutine[Any, Any, _T], + *, + debug: bool = False, + loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None, + ) -> _T: + # asyncio.run from Python 3.12 + # https://docs.python.org/3/license.html#psf-license + with asyncio.Runner(debug=debug, loop_factory=loop_factory) as runner: + return runner.run(main) + +else: + # modified version of asyncio.run from Python 3.10 to add loop_factory kwarg + # https://docs.python.org/3/license.html#psf-license + def asyncio_run( + main: Coroutine[Any, Any, _T], + *, + debug: bool = False, + loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None, + ) -> _T: + try: + asyncio.get_running_loop() + except RuntimeError: + pass + else: + raise RuntimeError("asyncio.run() cannot be called from a running event loop") + + if not asyncio.iscoroutine(main): + raise ValueError(f"a coroutine was expected, got {main!r}") + + if loop_factory is None: + loop = asyncio.new_event_loop() + else: + loop = loop_factory() + try: + if loop_factory is None: + asyncio.set_event_loop(loop) + if debug is not None: + loop.set_debug(debug) + return loop.run_until_complete(main) + finally: + try: + _cancel_all_tasks(loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + loop.run_until_complete(loop.shutdown_default_executor()) + finally: + if loop_factory is None: + asyncio.set_event_loop(None) + loop.close() + + def _cancel_all_tasks(loop: asyncio.AbstractEventLoop) -> None: + to_cancel = asyncio.all_tasks(loop) + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) diff --git a/.venv/lib/python3.14/site-packages/uvicorn/_subprocess.py b/.venv/lib/python3.14/site-packages/uvicorn/_subprocess.py new file mode 100644 index 0000000000000000000000000000000000000000..c084ec90b6a16c9e4824808d22283642e2757ded --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/_subprocess.py @@ -0,0 +1,84 @@ +""" +Some light wrappers around Python's multiprocessing, to deal with cleanly +starting child processes. +""" + +from __future__ import annotations + +import multiprocessing +import os +import sys +from collections.abc import Callable +from multiprocessing.context import SpawnProcess +from socket import socket + +from uvicorn.config import Config + +multiprocessing.allow_connection_pickling() +spawn = multiprocessing.get_context("spawn") + + +def get_subprocess( + config: Config, + target: Callable[..., None], + sockets: list[socket], +) -> SpawnProcess: + """ + Called in the parent process, to instantiate a new child process instance. + The child is not yet started at this point. + + * config - The Uvicorn configuration instance. + * target - A callable that accepts a list of sockets. In practice this will + be the `Server.run()` method. + * sockets - A list of sockets to pass to the server. Sockets are bound once + by the parent process, and then passed to the child processes. + """ + # We pass across the stdin fileno, and reopen it in the child process. + # This is required for some debugging environments. + try: + stdin_fileno = sys.stdin.fileno() + # The `sys.stdin` can be `None`, see https://docs.python.org/3/library/sys.html#sys.__stdin__. + except (AttributeError, OSError): + stdin_fileno = None + + kwargs = { + "config": config, + "target": target, + "sockets": sockets, + "stdin_fileno": stdin_fileno, + } + + return spawn.Process(target=subprocess_started, kwargs=kwargs) + + +def subprocess_started( + config: Config, + target: Callable[..., None], + sockets: list[socket], + stdin_fileno: int | None, +) -> None: + """ + Called when the child process starts. + + * config - The Uvicorn configuration instance. + * target - A callable that accepts a list of sockets. In practice this will + be the `Server.run()` method. + * sockets - A list of sockets to pass to the server. Sockets are bound once + by the parent process, and then passed to the child processes. + * stdin_fileno - The file number of sys.stdin, so that it can be reattached + to the child process. + """ + # Re-open stdin. + if stdin_fileno is not None: + sys.stdin = os.fdopen(stdin_fileno) # pragma: full coverage + + # Logging needs to be setup again for each child. + config.configure_logging() + + try: + # Now we can call into `Server.run(sockets=sockets)` + target(sockets=sockets) + except KeyboardInterrupt: # pragma: no cover + # suppress the exception to avoid a traceback from subprocess.Popen + # the parent already expects us to end, so no vital information is lost + pass diff --git a/.venv/lib/python3.14/site-packages/uvicorn/_types.py b/.venv/lib/python3.14/site-packages/uvicorn/_types.py new file mode 100644 index 0000000000000000000000000000000000000000..822445a7b58bb5b0726a9be205e8855a958dcdcf --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/_types.py @@ -0,0 +1,274 @@ +""" +Copyright (c) Django Software Foundation and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from __future__ import annotations + +import sys +import types +from collections.abc import Awaitable, Callable, Iterable, MutableMapping +from typing import Any, Literal, Protocol, TypedDict + +if sys.version_info >= (3, 11): # pragma: py-lt-311 + from typing import NotRequired +else: # pragma: py-gte-311 + from typing_extensions import NotRequired + +# WSGI +Environ = MutableMapping[str, Any] +ExcInfo = tuple[type[BaseException], BaseException, types.TracebackType | None] +StartResponse = Callable[[str, Iterable[tuple[str, str]], ExcInfo | None], None] +WSGIApp = Callable[[Environ, StartResponse], Iterable[bytes] | BaseException] + + +# ASGI +class ASGIVersions(TypedDict): + spec_version: str + version: Literal["2.0"] | Literal["3.0"] + + +class HTTPScope(TypedDict): + type: Literal["http"] + asgi: ASGIVersions + http_version: str + method: str + scheme: str + path: str + raw_path: bytes + query_string: bytes + root_path: str + headers: Iterable[tuple[bytes, bytes]] + client: tuple[str, int] | None + server: tuple[str, int | None] | None + state: NotRequired[dict[str, Any]] + extensions: NotRequired[dict[str, dict[object, object]]] + + +class WebSocketScope(TypedDict): + type: Literal["websocket"] + asgi: ASGIVersions + http_version: str + scheme: str + path: str + raw_path: bytes + query_string: bytes + root_path: str + headers: Iterable[tuple[bytes, bytes]] + client: tuple[str, int] | None + server: tuple[str, int | None] | None + subprotocols: Iterable[str] + state: NotRequired[dict[str, Any]] + extensions: NotRequired[dict[str, dict[object, object]]] + + +class LifespanScope(TypedDict): + type: Literal["lifespan"] + asgi: ASGIVersions + state: NotRequired[dict[str, Any]] + + +WWWScope = HTTPScope | WebSocketScope +Scope = HTTPScope | WebSocketScope | LifespanScope + + +class HTTPRequestEvent(TypedDict): + type: Literal["http.request"] + body: bytes + more_body: bool + + +class HTTPResponseDebugEvent(TypedDict): + type: Literal["http.response.debug"] + info: dict[str, object] + + +class HTTPResponseStartEvent(TypedDict): + type: Literal["http.response.start"] + status: int + headers: NotRequired[Iterable[tuple[bytes, bytes]]] + trailers: NotRequired[bool] + + +class HTTPResponseBodyEvent(TypedDict): + type: Literal["http.response.body"] + body: bytes + more_body: NotRequired[bool] + + +class HTTPResponseTrailersEvent(TypedDict): + type: Literal["http.response.trailers"] + headers: Iterable[tuple[bytes, bytes]] + more_trailers: bool + + +class HTTPServerPushEvent(TypedDict): + type: Literal["http.response.push"] + path: str + headers: Iterable[tuple[bytes, bytes]] + + +class HTTPDisconnectEvent(TypedDict): + type: Literal["http.disconnect"] + + +class WebSocketConnectEvent(TypedDict): + type: Literal["websocket.connect"] + + +class WebSocketAcceptEvent(TypedDict): + type: Literal["websocket.accept"] + subprotocol: NotRequired[str | None] + headers: NotRequired[Iterable[tuple[bytes, bytes]]] + + +class _WebSocketReceiveEventBytes(TypedDict): + type: Literal["websocket.receive"] + bytes: bytes + text: NotRequired[None] + + +class _WebSocketReceiveEventText(TypedDict): + type: Literal["websocket.receive"] + bytes: NotRequired[None] + text: str + + +WebSocketReceiveEvent = _WebSocketReceiveEventBytes | _WebSocketReceiveEventText + + +class _WebSocketSendEventBytes(TypedDict): + type: Literal["websocket.send"] + bytes: bytes + text: NotRequired[None] + + +class _WebSocketSendEventText(TypedDict): + type: Literal["websocket.send"] + bytes: NotRequired[None] + text: str + + +WebSocketSendEvent = _WebSocketSendEventBytes | _WebSocketSendEventText + + +class WebSocketResponseStartEvent(TypedDict): + type: Literal["websocket.http.response.start"] + status: int + headers: Iterable[tuple[bytes, bytes]] + + +class WebSocketResponseBodyEvent(TypedDict): + type: Literal["websocket.http.response.body"] + body: bytes + more_body: NotRequired[bool] + + +class WebSocketDisconnectEvent(TypedDict): + type: Literal["websocket.disconnect"] + code: int + reason: NotRequired[str | None] + + +class WebSocketCloseEvent(TypedDict): + type: Literal["websocket.close"] + code: NotRequired[int] + reason: NotRequired[str | None] + + +class LifespanStartupEvent(TypedDict): + type: Literal["lifespan.startup"] + + +class LifespanShutdownEvent(TypedDict): + type: Literal["lifespan.shutdown"] + + +class LifespanStartupCompleteEvent(TypedDict): + type: Literal["lifespan.startup.complete"] + + +class LifespanStartupFailedEvent(TypedDict): + type: Literal["lifespan.startup.failed"] + message: str + + +class LifespanShutdownCompleteEvent(TypedDict): + type: Literal["lifespan.shutdown.complete"] + + +class LifespanShutdownFailedEvent(TypedDict): + type: Literal["lifespan.shutdown.failed"] + message: str + + +WebSocketEvent = WebSocketReceiveEvent | WebSocketDisconnectEvent | WebSocketConnectEvent + + +ASGIReceiveEvent = ( + HTTPRequestEvent + | HTTPDisconnectEvent + | WebSocketConnectEvent + | WebSocketReceiveEvent + | WebSocketDisconnectEvent + | LifespanStartupEvent + | LifespanShutdownEvent +) + + +ASGISendEvent = ( + HTTPResponseStartEvent + | HTTPResponseBodyEvent + | HTTPResponseTrailersEvent + | HTTPServerPushEvent + | HTTPDisconnectEvent + | WebSocketAcceptEvent + | WebSocketSendEvent + | WebSocketResponseStartEvent + | WebSocketResponseBodyEvent + | WebSocketCloseEvent + | LifespanStartupCompleteEvent + | LifespanStartupFailedEvent + | LifespanShutdownCompleteEvent + | LifespanShutdownFailedEvent +) + + +ASGIReceiveCallable = Callable[[], Awaitable[ASGIReceiveEvent]] +ASGISendCallable = Callable[[ASGISendEvent], Awaitable[None]] + + +class ASGI2Protocol(Protocol): + def __init__(self, scope: Scope) -> None: ... # pragma: no cover + + async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ... # pragma: no cover + + +ASGI2Application = type[ASGI2Protocol] +ASGI3Application = Callable[[Scope, ASGIReceiveCallable, ASGISendCallable], Awaitable[None]] +ASGIApplication = ASGI2Application | ASGI3Application diff --git a/.venv/lib/python3.14/site-packages/uvicorn/config.py b/.venv/lib/python3.14/site-packages/uvicorn/config.py new file mode 100644 index 0000000000000000000000000000000000000000..c17ba60a4d37040fb50a94ca3f53aaaba0732c9d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/config.py @@ -0,0 +1,588 @@ +from __future__ import annotations + +import asyncio +import inspect +import json +import logging +import logging.config +import os +import socket +import ssl +import sys +from collections.abc import Awaitable, Callable +from configparser import RawConfigParser +from pathlib import Path +from typing import IO, Any, Literal + +import click + +from uvicorn._compat import iscoroutinefunction +from uvicorn._types import ASGIApplication +from uvicorn.importer import ImportFromStringError, import_from_string +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.middleware.asgi2 import ASGI2Middleware +from uvicorn.middleware.message_logger import MessageLoggerMiddleware +from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware +from uvicorn.middleware.wsgi import WSGIMiddleware + +HTTPProtocolType = Literal["auto", "h11", "httptools"] +WSProtocolType = Literal["auto", "none", "websockets", "websockets-sansio", "wsproto"] +LifespanType = Literal["auto", "on", "off"] +LoopFactoryType = Literal["none", "auto", "asyncio", "uvloop"] +InterfaceType = Literal["auto", "asgi3", "asgi2", "wsgi"] + +LOG_LEVELS: dict[str, int] = { + "critical": logging.CRITICAL, + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, + "debug": logging.DEBUG, + "trace": TRACE_LOG_LEVEL, +} +HTTP_PROTOCOLS: dict[str, str] = { + "auto": "uvicorn.protocols.http.auto:AutoHTTPProtocol", + "h11": "uvicorn.protocols.http.h11_impl:H11Protocol", + "httptools": "uvicorn.protocols.http.httptools_impl:HttpToolsProtocol", +} +WS_PROTOCOLS: dict[str, str | None] = { + "auto": "uvicorn.protocols.websockets.auto:AutoWebSocketsProtocol", + "none": None, + "websockets": "uvicorn.protocols.websockets.websockets_impl:WebSocketProtocol", + "websockets-sansio": "uvicorn.protocols.websockets.websockets_sansio_impl:WebSocketsSansIOProtocol", + "wsproto": "uvicorn.protocols.websockets.wsproto_impl:WSProtocol", +} +LIFESPAN: dict[str, str] = { + "auto": "uvicorn.lifespan.on:LifespanOn", + "on": "uvicorn.lifespan.on:LifespanOn", + "off": "uvicorn.lifespan.off:LifespanOff", +} +LOOP_FACTORIES: dict[str, str | None] = { + "none": None, + "auto": "uvicorn.loops.auto:auto_loop_factory", + "asyncio": "uvicorn.loops.asyncio:asyncio_loop_factory", + "uvloop": "uvicorn.loops.uvloop:uvloop_loop_factory", +} +INTERFACES: list[InterfaceType] = ["auto", "asgi3", "asgi2", "wsgi"] + +SSL_PROTOCOL_VERSION: int = ssl.PROTOCOL_TLS_SERVER + +LOGGING_CONFIG: dict[str, Any] = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "default": { + "()": "uvicorn.logging.DefaultFormatter", + "fmt": "%(levelprefix)s %(message)s", + "use_colors": None, + }, + "access": { + "()": "uvicorn.logging.AccessFormatter", + "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501 + }, + }, + "handlers": { + "default": { + "formatter": "default", + "class": "logging.StreamHandler", + "stream": "ext://sys.stderr", + }, + "access": { + "formatter": "access", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + }, + }, + "loggers": { + "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False}, + "uvicorn.error": {"level": "INFO"}, + "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False}, + }, +} + +logger = logging.getLogger("uvicorn.error") + + +def create_ssl_context( + certfile: str | os.PathLike[str], + keyfile: str | os.PathLike[str] | None, + password: str | None, + ssl_version: int, + cert_reqs: int, + ca_certs: str | os.PathLike[str] | None, + ciphers: str | None, +) -> ssl.SSLContext: + ctx = ssl.SSLContext(ssl_version) + get_password = (lambda: password) if password else None + ctx.load_cert_chain(certfile, keyfile, get_password) + ctx.verify_mode = ssl.VerifyMode(cert_reqs) + if ca_certs: + ctx.load_verify_locations(ca_certs) + if ciphers: + ctx.set_ciphers(ciphers) + return ctx + + +def is_dir(path: Path) -> bool: + try: + if not path.is_absolute(): + path = path.resolve() + return path.is_dir() + except OSError: # pragma: full coverage + return False + + +def resolve_reload_patterns(patterns_list: list[str], directories_list: list[str]) -> tuple[list[str], list[Path]]: + directories: list[Path] = list(set(map(Path, directories_list.copy()))) + patterns: list[str] = patterns_list.copy() + + current_working_directory = Path.cwd() + for pattern in patterns_list: + # Special case for the .* pattern, otherwise this would only match + # hidden directories which is probably undesired + if pattern == ".*": + continue # pragma: py-not-linux + patterns.append(pattern) + if is_dir(Path(pattern)): + directories.append(Path(pattern)) + else: + for match in current_working_directory.glob(pattern): + if is_dir(match): + directories.append(match) + + directories = list(set(directories)) + directories = list(map(Path, directories)) + directories = list(map(lambda x: x.resolve(), directories)) + directories = list({reload_path for reload_path in directories if is_dir(reload_path)}) + + children = [] + for j in range(len(directories)): + for k in range(j + 1, len(directories)): # pragma: full coverage + if directories[j] in directories[k].parents: + children.append(directories[k]) + elif directories[k] in directories[j].parents: + children.append(directories[j]) + + directories = list(set(directories).difference(set(children))) + + return list(set(patterns)), directories + + +def _normalize_dirs(dirs: list[str] | str | None) -> list[str]: + if dirs is None: + return [] + if isinstance(dirs, str): + return [dirs] + return list(set(dirs)) + + +class Config: + def __init__( + self, + app: ASGIApplication | Callable[..., Any] | str, + host: str = "127.0.0.1", + port: int = 8000, + uds: str | None = None, + fd: int | None = None, + loop: LoopFactoryType | str = "auto", + http: type[asyncio.Protocol] | HTTPProtocolType | str = "auto", + ws: type[asyncio.Protocol] | WSProtocolType | str = "auto", + ws_max_size: int = 16 * 1024 * 1024, + ws_max_queue: int = 32, + ws_ping_interval: float | None = 20.0, + ws_ping_timeout: float | None = 20.0, + ws_per_message_deflate: bool = True, + lifespan: LifespanType = "auto", + env_file: str | os.PathLike[str] | None = None, + log_config: dict[str, Any] | str | os.PathLike[str] | RawConfigParser | IO[Any] | None = LOGGING_CONFIG, + log_level: str | int | None = None, + access_log: bool = True, + use_colors: bool | None = None, + interface: InterfaceType = "auto", + reload: bool = False, + reload_dirs: list[str] | str | None = None, + reload_delay: float = 0.25, + reload_includes: list[str] | str | None = None, + reload_excludes: list[str] | str | None = None, + workers: int | None = None, + proxy_headers: bool = True, + server_header: bool = True, + date_header: bool = True, + forwarded_allow_ips: list[str] | str | None = None, + root_path: str = "", + limit_concurrency: int | None = None, + limit_max_requests: int | None = None, + limit_max_requests_jitter: int = 0, + backlog: int = 2048, + timeout_keep_alive: int = 5, + timeout_notify: int = 30, + timeout_graceful_shutdown: int | None = None, + timeout_worker_healthcheck: int = 5, + callback_notify: Callable[..., Awaitable[None]] | None = None, + ssl_keyfile: str | os.PathLike[str] | None = None, + ssl_certfile: str | os.PathLike[str] | None = None, + ssl_keyfile_password: str | None = None, + ssl_version: int = SSL_PROTOCOL_VERSION, + ssl_cert_reqs: int = ssl.CERT_NONE, + ssl_ca_certs: str | os.PathLike[str] | None = None, + ssl_ciphers: str | None = None, + ssl_context_factory: Callable[[Config, Callable[[], ssl.SSLContext]], ssl.SSLContext] | None = None, + headers: list[tuple[str, str]] | None = None, + factory: bool = False, + h11_max_incomplete_event_size: int | None = None, + reset_contextvars: bool = False, + ): + self.app = app + self.host = host + self.port = port + self.uds = uds + self.fd = fd + self.loop = loop + self.http = http + self.ws = ws + self.ws_max_size = ws_max_size + self.ws_max_queue = ws_max_queue + self.ws_ping_interval = ws_ping_interval + self.ws_ping_timeout = ws_ping_timeout + self.ws_per_message_deflate = ws_per_message_deflate + self.lifespan = lifespan + self.log_config = log_config + self.log_level = log_level + self.access_log = access_log + self.use_colors = use_colors + self.interface = interface + self.reload = reload + self.reload_delay = reload_delay + self.workers = workers or 1 + self.proxy_headers = proxy_headers + self.server_header = server_header + self.date_header = date_header + self.root_path = root_path + self.limit_concurrency = limit_concurrency + self.limit_max_requests = limit_max_requests + self.limit_max_requests_jitter = limit_max_requests_jitter + self.backlog = backlog + self.timeout_keep_alive = timeout_keep_alive + self.timeout_notify = timeout_notify + self.timeout_graceful_shutdown = timeout_graceful_shutdown + self.timeout_worker_healthcheck = timeout_worker_healthcheck + self.callback_notify = callback_notify + self.ssl_keyfile = ssl_keyfile + self.ssl_certfile = ssl_certfile + self.ssl_keyfile_password = ssl_keyfile_password + self.ssl_version = ssl_version + self.ssl_cert_reqs = ssl_cert_reqs + self.ssl_ca_certs = ssl_ca_certs + self.ssl_ciphers = ssl_ciphers + self.ssl_context_factory = ssl_context_factory + self.headers: list[tuple[str, str]] = headers or [] + self.encoded_headers: list[tuple[bytes, bytes]] = [] + self.factory = factory + self.h11_max_incomplete_event_size = h11_max_incomplete_event_size + self.reset_contextvars = reset_contextvars + + self.loaded = False + self.configure_logging() + + self.reload_dirs: list[Path] = [] + self.reload_dirs_excludes: list[Path] = [] + self.reload_includes: list[str] = [] + self.reload_excludes: list[str] = [] + + if (reload_dirs or reload_includes or reload_excludes) and not self.should_reload: + logger.warning( + "Current configuration will not reload as not all conditions are met, please refer to documentation." + ) + + if self.should_reload: + reload_dirs = _normalize_dirs(reload_dirs) + reload_includes = _normalize_dirs(reload_includes) + reload_excludes = _normalize_dirs(reload_excludes) + + self.reload_includes, self.reload_dirs = resolve_reload_patterns(reload_includes, reload_dirs) + + self.reload_excludes, self.reload_dirs_excludes = resolve_reload_patterns(reload_excludes, []) + + reload_dirs_tmp = self.reload_dirs.copy() + + for directory in self.reload_dirs_excludes: + for reload_directory in reload_dirs_tmp: + if directory == reload_directory or directory in reload_directory.parents: + try: + self.reload_dirs.remove(reload_directory) + except ValueError: # pragma: full coverage + pass + + for pattern in self.reload_excludes: + if pattern in self.reload_includes: + self.reload_includes.remove(pattern) # pragma: full coverage + + if not self.reload_dirs: + if reload_dirs: + logger.warning( + "Provided reload directories %s did not contain valid " + + "directories, watching current working directory.", + reload_dirs, + ) + self.reload_dirs = [Path.cwd()] + + logger.info( + "Will watch for changes in these directories: %s", + sorted(list(map(str, self.reload_dirs))), + ) + + if env_file is not None: + from dotenv import load_dotenv + + logger.info("Loading environment from '%s'", env_file) + load_dotenv(dotenv_path=env_file) + + if workers is None and "WEB_CONCURRENCY" in os.environ: + self.workers = int(os.environ["WEB_CONCURRENCY"]) + + self.forwarded_allow_ips: list[str] | str + if forwarded_allow_ips is None: + self.forwarded_allow_ips = os.environ.get("FORWARDED_ALLOW_IPS", "127.0.0.1") + else: + self.forwarded_allow_ips = forwarded_allow_ips # pragma: full coverage + + if self.reload and self.workers > 1: + logger.warning('"workers" flag is ignored when reloading is enabled.') + + @property + def asgi_version(self) -> Literal["2.0", "3.0"]: + mapping: dict[str, Literal["2.0", "3.0"]] = { + "asgi2": "2.0", + "asgi3": "3.0", + "wsgi": "3.0", + } + return mapping[self.interface] + + @property + def is_ssl(self) -> bool: + return bool(self.ssl_keyfile or self.ssl_certfile or self.ssl_context_factory) + + @property + def use_subprocess(self) -> bool: + return bool(self.reload or self.workers > 1) + + def configure_logging(self) -> None: + logging.addLevelName(TRACE_LOG_LEVEL, "TRACE") + + if self.log_config is not None: + if isinstance(self.log_config, os.PathLike): + self.log_config = os.fspath(self.log_config) + + if isinstance(self.log_config, dict): + if self.use_colors in (True, False): + self.log_config["formatters"]["default"]["use_colors"] = self.use_colors + self.log_config["formatters"]["access"]["use_colors"] = self.use_colors + logging.config.dictConfig(self.log_config) + elif isinstance(self.log_config, str) and self.log_config.endswith(".json"): + with open(self.log_config) as file: + loaded_config = json.load(file) + logging.config.dictConfig(loaded_config) + elif isinstance(self.log_config, str) and self.log_config.endswith((".yaml", ".yml")): + try: + import yaml + except ImportError as e: + raise ImportError( + "Install the PyYAML package or uvicorn[standard] to use `--log-config` with YAML files." + ) from e + + with open(self.log_config) as file: + loaded_config = yaml.safe_load(file) + logging.config.dictConfig(loaded_config) + else: + # See the note about fileConfig() here: + # https://docs.python.org/3/library/logging.config.html#configuration-file-format + logging.config.fileConfig(self.log_config, disable_existing_loggers=False) + + if self.log_level is not None: + if isinstance(self.log_level, str): + log_level = LOG_LEVELS[self.log_level.lower()] + else: + log_level = self.log_level + logging.getLogger("uvicorn.error").setLevel(log_level) + logging.getLogger("uvicorn.access").setLevel(log_level) + logging.getLogger("uvicorn.asgi").setLevel(log_level) + if self.access_log is False: + logging.getLogger("uvicorn.access").handlers = [] + logging.getLogger("uvicorn.access").propagate = False + + def load_app(self) -> Any: + """Import the app and return it. Exits on failure.""" + try: + return import_from_string(self.app) + except ImportFromStringError as exc: + logger.error("Error loading ASGI app. %s" % exc) + sys.exit(1) + + def load(self) -> None: + assert not self.loaded + + if self.ssl_context_factory is not None: + + def default_factory() -> ssl.SSLContext: + if not self.ssl_certfile: + raise RuntimeError( + "`default_ssl_context_factory()` requires `ssl_certfile` to be set on `Config`. " + "Either pass `ssl_certfile` (and optionally `ssl_keyfile`) or build the `SSLContext` " + "directly inside `ssl_context_factory` without calling the default factory." + ) + return create_ssl_context( + keyfile=self.ssl_keyfile, + certfile=self.ssl_certfile, + password=self.ssl_keyfile_password, + ssl_version=self.ssl_version, + cert_reqs=self.ssl_cert_reqs, + ca_certs=self.ssl_ca_certs, + ciphers=self.ssl_ciphers, + ) + + context = self.ssl_context_factory(self, default_factory) + if not isinstance(context, ssl.SSLContext): + raise TypeError(f"`ssl_context_factory` must return an `ssl.SSLContext`, got {type(context).__name__}") + self.ssl: ssl.SSLContext | None = context + elif self.is_ssl: + assert self.ssl_certfile + self.ssl = create_ssl_context( + keyfile=self.ssl_keyfile, + certfile=self.ssl_certfile, + password=self.ssl_keyfile_password, + ssl_version=self.ssl_version, + cert_reqs=self.ssl_cert_reqs, + ca_certs=self.ssl_ca_certs, + ciphers=self.ssl_ciphers, + ) + else: + self.ssl = None + + encoded_headers = [(key.lower().encode("latin1"), value.encode("latin1")) for key, value in self.headers] + self.encoded_headers = ( + [(b"server", b"uvicorn")] + encoded_headers + if b"server" not in dict(encoded_headers) and self.server_header + else encoded_headers + ) + + if isinstance(self.http, str): + http_protocol_class = import_from_string(HTTP_PROTOCOLS.get(self.http, self.http)) + self.http_protocol_class: type[asyncio.Protocol] = http_protocol_class + else: + self.http_protocol_class = self.http + + if isinstance(self.ws, str): + ws_protocol_class = import_from_string(WS_PROTOCOLS.get(self.ws, self.ws)) + self.ws_protocol_class: type[asyncio.Protocol] | None = ws_protocol_class + else: + self.ws_protocol_class = self.ws + + self.lifespan_class = import_from_string(LIFESPAN[self.lifespan]) + + self.loaded_app = self.load_app() + + try: + self.loaded_app = self.loaded_app() + except TypeError as exc: + if self.factory: + logger.error("Error loading ASGI app factory: %s", exc) + sys.exit(1) + else: + if not self.factory: + logger.warning( + "ASGI app factory detected. Using it, but please consider setting the --factory flag explicitly." + ) + + if self.interface == "auto": + if inspect.isclass(self.loaded_app): + use_asgi_3 = hasattr(self.loaded_app, "__await__") + elif inspect.isfunction(self.loaded_app): + use_asgi_3 = iscoroutinefunction(self.loaded_app) + else: + call = getattr(self.loaded_app, "__call__", None) + use_asgi_3 = iscoroutinefunction(call) + self.interface = "asgi3" if use_asgi_3 else "asgi2" + + if self.interface == "wsgi": + self.loaded_app = WSGIMiddleware(self.loaded_app) + self.ws_protocol_class = None + elif self.interface == "asgi2": + self.loaded_app = ASGI2Middleware(self.loaded_app) + + if logger.getEffectiveLevel() <= TRACE_LOG_LEVEL: + self.loaded_app = MessageLoggerMiddleware(self.loaded_app) + if self.proxy_headers: + self.loaded_app = ProxyHeadersMiddleware(self.loaded_app, trusted_hosts=self.forwarded_allow_ips) + + self.loaded = True + + def setup_event_loop(self) -> None: + raise AttributeError( + "The `setup_event_loop` method was replaced by `get_loop_factory` in uvicorn 0.36.0.\n" + "None of those methods are supposed to be used directly. If you are doing it, please let me know here: " + "https://github.com/Kludex/uvicorn/discussions/2706. Thank you, and sorry for the inconvenience." + ) + + def get_loop_factory(self) -> Callable[[], asyncio.AbstractEventLoop] | None: + if self.loop in LOOP_FACTORIES: + loop_factory: Callable[..., Any] | None = import_from_string(LOOP_FACTORIES[self.loop]) + else: + try: + return import_from_string(self.loop) + except ImportFromStringError as exc: + logger.error("Error loading custom loop setup function. %s" % exc) + sys.exit(1) + if loop_factory is None: + return None + return loop_factory(use_subprocess=self.use_subprocess) + + def bind_socket(self) -> socket.socket: + logger_args: list[str | int] + if self.uds is not None: # pragma: py-win32 + path = self.uds + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + sock.bind(path) + uds_perms = 0o666 + os.chmod(self.uds, uds_perms) + except OSError as exc: # pragma: full coverage + logger.error(exc) + sys.exit(1) + + message = "Uvicorn running on unix socket %s (Press CTRL+C to quit)" + sock_name_format = "%s" + color_message = "Uvicorn running on " + click.style(sock_name_format, bold=True) + " (Press CTRL+C to quit)" + logger_args = [self.uds] + elif self.fd is not None: # pragma: py-win32 + sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM) + message = "Uvicorn running on socket %s (Press CTRL+C to quit)" + fd_name_format = "%s" + color_message = "Uvicorn running on " + click.style(fd_name_format, bold=True) + " (Press CTRL+C to quit)" + logger_args = [sock.getsockname()] + else: + family = socket.AF_INET + addr_format = "%s://%s:%d" + + if self.host and ":" in self.host: # pragma: full coverage + # It's an IPv6 address. + family = socket.AF_INET6 + addr_format = "%s://[%s]:%d" + + sock = socket.socket(family=family) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + sock.bind((self.host, self.port)) + except OSError as exc: # pragma: full coverage + logger.error(exc) + sys.exit(1) + + message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)" + color_message = "Uvicorn running on " + click.style(addr_format, bold=True) + " (Press CTRL+C to quit)" + protocol_name = "https" if self.is_ssl else "http" + logger_args = [protocol_name, self.host, sock.getsockname()[1]] + logger.info(message, *logger_args, extra={"color_message": color_message}) + sock.set_inheritable(True) + return sock + + @property + def should_reload(self) -> bool: + return isinstance(self.app, str) and self.reload diff --git a/.venv/lib/python3.14/site-packages/uvicorn/importer.py b/.venv/lib/python3.14/site-packages/uvicorn/importer.py new file mode 100644 index 0000000000000000000000000000000000000000..f77520ee106deeedee30a48005479806ff9a19e2 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/importer.py @@ -0,0 +1,34 @@ +import importlib +from typing import Any + + +class ImportFromStringError(Exception): + pass + + +def import_from_string(import_str: Any) -> Any: + if not isinstance(import_str, str): + return import_str + + module_str, _, attrs_str = import_str.partition(":") + if not module_str or not attrs_str: + message = 'Import string "{import_str}" must be in format ":".' + raise ImportFromStringError(message.format(import_str=import_str)) + + try: + module = importlib.import_module(module_str) + except ModuleNotFoundError as exc: + if exc.name != module_str: + raise exc from None + message = 'Could not import module "{module_str}".' + raise ImportFromStringError(message.format(module_str=module_str)) + + instance = module + try: + for attr_str in attrs_str.split("."): + instance = getattr(instance, attr_str) + except AttributeError: + message = 'Attribute "{attrs_str}" not found in module "{module_str}".' + raise ImportFromStringError(message.format(attrs_str=attrs_str, module_str=module_str)) + + return instance diff --git a/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__init__.py b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b47b0a8194e7243e5c60d248aa88eaeacc74d149 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__pycache__/off.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__pycache__/off.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db26370fe44d37917e50758e0d6bfaab7ab9fcf1 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__pycache__/off.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__pycache__/on.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__pycache__/on.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..85561ad236c18d69f5c9d2ef4205d132c51a2537 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/__pycache__/on.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/lifespan/off.py b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/off.py new file mode 100644 index 0000000000000000000000000000000000000000..74554b1e2a149c37131168fbe283f3e2476a8f75 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/off.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from typing import Any + +from uvicorn import Config + + +class LifespanOff: + def __init__(self, config: Config) -> None: + self.should_exit = False + self.state: dict[str, Any] = {} + + async def startup(self) -> None: + pass + + async def shutdown(self) -> None: + pass diff --git a/.venv/lib/python3.14/site-packages/uvicorn/lifespan/on.py b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/on.py new file mode 100644 index 0000000000000000000000000000000000000000..71ac39472e9e07025f25c62b074b1d44df39cf8e --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/lifespan/on.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import asyncio +import logging +from asyncio import Queue +from typing import Any + +from uvicorn import Config +from uvicorn._types import ( + LifespanScope, + LifespanShutdownCompleteEvent, + LifespanShutdownEvent, + LifespanShutdownFailedEvent, + LifespanStartupCompleteEvent, + LifespanStartupEvent, + LifespanStartupFailedEvent, +) + +LifespanReceiveMessage = LifespanStartupEvent | LifespanShutdownEvent +LifespanSendMessage = ( + LifespanStartupFailedEvent + | LifespanShutdownFailedEvent + | LifespanStartupCompleteEvent + | LifespanShutdownCompleteEvent +) + + +STATE_TRANSITION_ERROR = "Got invalid state transition on lifespan protocol." + + +class LifespanOn: + def __init__(self, config: Config) -> None: + if not config.loaded: + config.load() + + self.config = config + self.logger = logging.getLogger("uvicorn.error") + self.startup_event = asyncio.Event() + self.shutdown_event = asyncio.Event() + self.receive_queue: Queue[LifespanReceiveMessage] = asyncio.Queue() + self.error_occurred = False + self.startup_failed = False + self.shutdown_failed = False + self.should_exit = False + self.state: dict[str, Any] = {} + + async def startup(self) -> None: + self.logger.info("Waiting for application startup.") + + loop = asyncio.get_event_loop() + main_lifespan_task = loop.create_task(self.main()) # noqa: F841 + # Keep a hard reference to prevent garbage collection + # See https://github.com/Kludex/uvicorn/pull/972 + startup_event: LifespanStartupEvent = {"type": "lifespan.startup"} + await self.receive_queue.put(startup_event) + await self.startup_event.wait() + + if self.startup_failed or (self.error_occurred and self.config.lifespan == "on"): + self.logger.error("Application startup failed. Exiting.") + self.should_exit = True + else: + self.logger.info("Application startup complete.") + + async def shutdown(self) -> None: + if self.error_occurred: + return + self.logger.info("Waiting for application shutdown.") + shutdown_event: LifespanShutdownEvent = {"type": "lifespan.shutdown"} + await self.receive_queue.put(shutdown_event) + await self.shutdown_event.wait() + + if self.shutdown_failed or (self.error_occurred and self.config.lifespan == "on"): + self.logger.error("Application shutdown failed. Exiting.") + self.should_exit = True + else: + self.logger.info("Application shutdown complete.") + + async def main(self) -> None: + try: + app = self.config.loaded_app + scope: LifespanScope = { + "type": "lifespan", + "asgi": {"version": self.config.asgi_version, "spec_version": "2.0"}, + "state": self.state, + } + await app(scope, self.receive, self.send) + except BaseException as exc: + self.asgi = None + self.error_occurred = True + if self.startup_failed or self.shutdown_failed: + return + if self.config.lifespan == "auto": + msg = "ASGI 'lifespan' protocol appears unsupported." + self.logger.info(msg) + else: + msg = "Exception in 'lifespan' protocol\n" + self.logger.error(msg, exc_info=exc) + finally: + self.startup_event.set() + self.shutdown_event.set() + + async def send(self, message: LifespanSendMessage) -> None: + assert message["type"] in ( + "lifespan.startup.complete", + "lifespan.startup.failed", + "lifespan.shutdown.complete", + "lifespan.shutdown.failed", + ) + + if message["type"] == "lifespan.startup.complete": + assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR + assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR + self.startup_event.set() + + elif message["type"] == "lifespan.startup.failed": + assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR + assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR + self.startup_event.set() + self.startup_failed = True + if message.get("message"): + self.logger.error(message["message"]) + + elif message["type"] == "lifespan.shutdown.complete": + assert self.startup_event.is_set(), STATE_TRANSITION_ERROR + assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR + self.shutdown_event.set() + + elif message["type"] == "lifespan.shutdown.failed": + assert self.startup_event.is_set(), STATE_TRANSITION_ERROR + assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR + self.shutdown_event.set() + self.shutdown_failed = True + if message.get("message"): + self.logger.error(message["message"]) + + async def receive(self) -> LifespanReceiveMessage: + return await self.receive_queue.get() diff --git a/.venv/lib/python3.14/site-packages/uvicorn/logging.py b/.venv/lib/python3.14/site-packages/uvicorn/logging.py new file mode 100644 index 0000000000000000000000000000000000000000..bd16115311a2c4deb4f5de0131d5cdfb14049980 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/logging.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +import http +import logging +import sys +from copy import copy +from typing import Literal + +import click + +TRACE_LOG_LEVEL = 5 + + +class ColourizedFormatter(logging.Formatter): + """ + A custom log formatter class that: + + * Outputs the LOG_LEVEL with an appropriate color. + * If a log call includes an `extra={"color_message": ...}` it will be used + for formatting the output, instead of the plain text message. + """ + + level_name_colors = { + TRACE_LOG_LEVEL: lambda level_name: click.style(str(level_name), fg="blue"), + logging.DEBUG: lambda level_name: click.style(str(level_name), fg="cyan"), + logging.INFO: lambda level_name: click.style(str(level_name), fg="green"), + logging.WARNING: lambda level_name: click.style(str(level_name), fg="yellow"), + logging.ERROR: lambda level_name: click.style(str(level_name), fg="red"), + logging.CRITICAL: lambda level_name: click.style(str(level_name), fg="bright_red"), + } + + def __init__( + self, + fmt: str | None = None, + datefmt: str | None = None, + style: Literal["%", "{", "$"] = "%", + use_colors: bool | None = None, + ): + if use_colors in (True, False): + self.use_colors = use_colors + else: + self.use_colors = sys.stdout.isatty() + super().__init__(fmt=fmt, datefmt=datefmt, style=style) + + def color_level_name(self, level_name: str, level_no: int) -> str: + def default(level_name: str) -> str: + return str(level_name) # pragma: no cover + + func = self.level_name_colors.get(level_no, default) + return func(level_name) + + def should_use_colors(self) -> bool: + return True # pragma: no cover + + def formatMessage(self, record: logging.LogRecord) -> str: + recordcopy = copy(record) + levelname = recordcopy.levelname + separator = " " * (8 - len(recordcopy.levelname)) + if self.use_colors: + levelname = self.color_level_name(levelname, recordcopy.levelno) + if "color_message" in recordcopy.__dict__: + recordcopy.msg = recordcopy.__dict__["color_message"] + recordcopy.__dict__["message"] = recordcopy.getMessage() + recordcopy.__dict__["levelprefix"] = levelname + ":" + separator + return super().formatMessage(recordcopy) + + +class DefaultFormatter(ColourizedFormatter): + def should_use_colors(self) -> bool: + return sys.stderr.isatty() # pragma: no cover + + +class AccessFormatter(ColourizedFormatter): + status_code_colours = { + 1: lambda code: click.style(str(code), fg="bright_white"), + 2: lambda code: click.style(str(code), fg="green"), + 3: lambda code: click.style(str(code), fg="yellow"), + 4: lambda code: click.style(str(code), fg="red"), + 5: lambda code: click.style(str(code), fg="bright_red"), + } + + def get_status_code(self, status_code: int) -> str: + try: + status_phrase = http.HTTPStatus(status_code).phrase + except ValueError: + status_phrase = "" + status_and_phrase = f"{status_code} {status_phrase}" + if self.use_colors: + + def default(code: int) -> str: + return status_and_phrase # pragma: no cover + + func = self.status_code_colours.get(status_code // 100, default) + return func(status_and_phrase) + return status_and_phrase + + def formatMessage(self, record: logging.LogRecord) -> str: + recordcopy = copy(record) + ( + client_addr, + method, + full_path, + http_version, + status_code, + ) = recordcopy.args # type: ignore[misc] + status_code = self.get_status_code(int(status_code)) # type: ignore[arg-type] + request_line = f"{method} {full_path} HTTP/{http_version}" + if self.use_colors: + request_line = click.style(request_line, bold=True) + recordcopy.__dict__.update( + { + "client_addr": client_addr, + "request_line": request_line, + "status_code": status_code, + } + ) + return super().formatMessage(recordcopy) diff --git a/.venv/lib/python3.14/site-packages/uvicorn/loops/__init__.py b/.venv/lib/python3.14/site-packages/uvicorn/loops/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68ad6aab00a28d019ab06b4da53be920c8f603d2 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/asyncio.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/asyncio.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..759bc30c407828ced80b419e6ed564d037ef6ebb Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/asyncio.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/auto.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/auto.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6caa1da5ca45279c5b0422cd02b58093a6a5f65f Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/auto.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/uvloop.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/uvloop.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb41671f0af5e48d4c6345f5eba865591f3df412 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/loops/__pycache__/uvloop.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/loops/asyncio.py b/.venv/lib/python3.14/site-packages/uvicorn/loops/asyncio.py new file mode 100644 index 0000000000000000000000000000000000000000..ad6121ee086b0c3b2fc2cb8dd378134725c1edf6 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/loops/asyncio.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +import asyncio +import sys +from collections.abc import Callable + + +def asyncio_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: + if sys.platform == "win32" and not use_subprocess: + return asyncio.ProactorEventLoop + return asyncio.SelectorEventLoop diff --git a/.venv/lib/python3.14/site-packages/uvicorn/loops/auto.py b/.venv/lib/python3.14/site-packages/uvicorn/loops/auto.py new file mode 100644 index 0000000000000000000000000000000000000000..190839905566f0c0e59c6119604887c326078115 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/loops/auto.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable + + +def auto_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: + try: + import uvloop # noqa + except ImportError: # pragma: no cover + from uvicorn.loops.asyncio import asyncio_loop_factory as loop_factory + + return loop_factory(use_subprocess=use_subprocess) + else: # pragma: no cover + from uvicorn.loops.uvloop import uvloop_loop_factory + + return uvloop_loop_factory(use_subprocess=use_subprocess) diff --git a/.venv/lib/python3.14/site-packages/uvicorn/loops/uvloop.py b/.venv/lib/python3.14/site-packages/uvicorn/loops/uvloop.py new file mode 100644 index 0000000000000000000000000000000000000000..c6692c58f5c63501b2ad0f3d3c1d6b2336d88aad --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/loops/uvloop.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable + +import uvloop + + +def uvloop_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: + return uvloop.new_event_loop diff --git a/.venv/lib/python3.14/site-packages/uvicorn/main.py b/.venv/lib/python3.14/site-packages/uvicorn/main.py new file mode 100644 index 0000000000000000000000000000000000000000..0b8eb0eada15c177b994fbd03c2ea2f3dbaffd67 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/main.py @@ -0,0 +1,644 @@ +from __future__ import annotations + +import asyncio +import logging +import os +import platform +import ssl +import sys +import warnings +from collections.abc import Callable +from configparser import RawConfigParser +from typing import IO, Any, get_args + +import click + +import uvicorn +from uvicorn._types import ASGIApplication +from uvicorn.config import ( + INTERFACES, + LIFESPAN, + LOG_LEVELS, + LOGGING_CONFIG, + SSL_PROTOCOL_VERSION, + Config, + HTTPProtocolType, + InterfaceType, + LifespanType, + LoopFactoryType, + WSProtocolType, +) +from uvicorn.server import Server +from uvicorn.supervisors import ChangeReload, Multiprocess + +LEVEL_CHOICES = click.Choice(list(LOG_LEVELS.keys())) +LIFESPAN_CHOICES = click.Choice(list(LIFESPAN.keys())) +INTERFACE_CHOICES = click.Choice(INTERFACES) + + +def _metavar_from_type(_type: Any) -> str: + return f"[{'|'.join(key for key in get_args(_type) if key != 'none')}]" + + +STARTUP_FAILURE = 3 + +logger = logging.getLogger("uvicorn.error") + + +def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + click.echo( + "Running uvicorn {version} with {py_implementation} {py_version} on {system}".format( # noqa: UP032 + version=uvicorn.__version__, + py_implementation=platform.python_implementation(), + py_version=platform.python_version(), + system=platform.system(), + ) + ) + ctx.exit() + + +@click.command(context_settings={"auto_envvar_prefix": "UVICORN"}) +@click.argument("app", envvar="UVICORN_APP") +@click.option( + "--host", + type=str, + default="127.0.0.1", + help="Bind socket to this host.", + show_default=True, +) +@click.option( + "--port", + type=int, + default=8000, + help="Bind socket to this port. If 0, an available port will be picked.", + show_default=True, +) +@click.option("--uds", type=str, default=None, help="Bind to a UNIX domain socket.") +@click.option("--fd", type=int, default=None, help="Bind to socket from this file descriptor.") +@click.option("--reload", is_flag=True, default=False, help="Enable auto-reload.") +@click.option( + "--reload-dir", + "reload_dirs", + multiple=True, + help="Set reload directories explicitly, instead of using the current working directory.", + type=click.Path(exists=True), +) +@click.option( + "--reload-include", + "reload_includes", + multiple=True, + help="Set glob patterns to include while watching for files. Includes '*.py' " + "by default; these defaults can be overridden with `--reload-exclude`. " + "This option has no effect unless watchfiles is installed.", +) +@click.option( + "--reload-exclude", + "reload_excludes", + multiple=True, + help="Set glob patterns to exclude while watching for files. Includes " + "'.*, .py[cod], .sw.*, ~*' by default; these defaults can be overridden " + "with `--reload-include`. This option has no effect unless watchfiles is " + "installed.", +) +@click.option( + "--reload-delay", + type=float, + default=0.25, + show_default=True, + help="Delay between previous and next check if application needs to be. Defaults to 0.25s.", +) +@click.option( + "--workers", + default=None, + type=int, + help="Number of worker processes. Defaults to the $WEB_CONCURRENCY environment" + " variable if available, or 1. Not valid with --reload.", +) +@click.option( + "--loop", + type=str, + metavar=_metavar_from_type(LoopFactoryType), + default="auto", + help="Event loop factory implementation.", + show_default=True, +) +@click.option( + "--http", + type=str, + metavar=_metavar_from_type(HTTPProtocolType), + default="auto", + help="HTTP protocol implementation.", + show_default=True, +) +@click.option( + "--ws", + type=str, + metavar=_metavar_from_type(WSProtocolType), + default="auto", + help="WebSocket protocol implementation.", + show_default=True, +) +@click.option( + "--ws-max-size", + type=int, + default=16777216, + help="WebSocket max size message in bytes", + show_default=True, +) +@click.option( + "--ws-max-queue", + type=int, + default=32, + help="The maximum length of the WebSocket message queue.", + show_default=True, +) +@click.option( + "--ws-ping-interval", + type=float, + default=20.0, + help="WebSocket ping interval in seconds.", + show_default=True, +) +@click.option( + "--ws-ping-timeout", + type=float, + default=20.0, + help="WebSocket ping timeout in seconds.", + show_default=True, +) +@click.option( + "--ws-per-message-deflate", + type=bool, + default=True, + help="WebSocket per-message-deflate compression", + show_default=True, +) +@click.option( + "--lifespan", + type=LIFESPAN_CHOICES, + default="auto", + help="Lifespan implementation.", + show_default=True, +) +@click.option( + "--interface", + type=INTERFACE_CHOICES, + default="auto", + help="Select ASGI3, ASGI2, or WSGI as the application interface.", + show_default=True, +) +@click.option( + "--env-file", + type=click.Path(exists=True), + default=None, + help="Environment configuration file.", + show_default=True, +) +@click.option( + "--log-config", + type=click.Path(exists=True), + default=None, + help="Logging configuration file. Supported formats: .ini, .json, .yaml.", + show_default=True, +) +@click.option( + "--log-level", + type=LEVEL_CHOICES, + default=None, + help="Log level. [default: info]", + show_default=True, +) +@click.option( + "--access-log/--no-access-log", + is_flag=True, + default=True, + help="Enable/Disable access log.", +) +@click.option( + "--use-colors/--no-use-colors", + is_flag=True, + default=None, + help="Enable/Disable colorized logging.", +) +@click.option( + "--proxy-headers/--no-proxy-headers", + is_flag=True, + default=True, + help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For to populate url scheme and remote address info.", +) +@click.option( + "--server-header/--no-server-header", + is_flag=True, + default=True, + help="Enable/Disable default Server header.", +) +@click.option( + "--date-header/--no-date-header", + is_flag=True, + default=True, + help="Enable/Disable default Date header.", +) +@click.option( + "--forwarded-allow-ips", + type=str, + default=None, + help="Comma separated list of IP Addresses, IP Networks, or literals " + "(e.g. UNIX Socket path) to trust with proxy headers. Defaults to the " + "$FORWARDED_ALLOW_IPS environment variable if available, or '127.0.0.1'. " + "The literal '*' means trust everything.", +) +@click.option( + "--root-path", + type=str, + default="", + help="Set the ASGI 'root_path' for applications submounted below a given URL path.", +) +@click.option( + "--limit-concurrency", + type=int, + default=None, + help="Maximum number of concurrent connections or tasks to allow, before issuing HTTP 503 responses.", +) +@click.option( + "--backlog", + type=int, + default=2048, + help="Maximum number of connections to hold in backlog", +) +@click.option( + "--limit-max-requests", + type=int, + default=None, + help="Maximum number of requests to service before terminating the process.", +) +@click.option( + "--limit-max-requests-jitter", + type=int, + default=0, + help="Maximum jitter to add to limit_max_requests." + " Staggers worker restarts to avoid all workers restarting simultaneously.", + show_default=True, +) +@click.option( + "--timeout-keep-alive", + type=int, + default=5, + help="Close Keep-Alive connections if no new data is received within this timeout (in seconds).", + show_default=True, +) +@click.option( + "--timeout-graceful-shutdown", + type=int, + default=None, + help="Maximum number of seconds to wait for graceful shutdown.", +) +@click.option( + "--timeout-worker-healthcheck", + type=int, + default=5, + help="Maximum number of seconds to wait for a worker to respond to a healthcheck.", + show_default=True, +) +@click.option("--ssl-keyfile", type=str, default=None, help="SSL key file", show_default=True) +@click.option( + "--ssl-certfile", + type=str, + default=None, + help="SSL certificate file", + show_default=True, +) +@click.option( + "--ssl-keyfile-password", + type=str, + default=None, + help="SSL keyfile password", + show_default=True, +) +@click.option( + "--ssl-version", + type=int, + default=int(SSL_PROTOCOL_VERSION), + help="SSL version to use (see stdlib ssl module's)", + show_default=True, +) +@click.option( + "--ssl-cert-reqs", + type=int, + default=int(ssl.CERT_NONE), + help="Whether client certificate is required (see stdlib ssl module's)", + show_default=True, +) +@click.option( + "--ssl-ca-certs", + type=str, + default=None, + help="CA certificates file", + show_default=True, +) +@click.option( + "--ssl-ciphers", + type=str, + default=None, + help="Ciphers to use (see stdlib ssl module's). Defaults to OpenSSL's safe defaults.", + show_default=True, +) +@click.option( + "--header", + "headers", + multiple=True, + help="Specify custom default HTTP response headers as a Name:Value pair", +) +@click.option( + "--version", + is_flag=True, + callback=print_version, + expose_value=False, + is_eager=True, + help="Display the uvicorn version and exit.", +) +@click.option( + "--app-dir", + default="", + show_default=True, + help="Look for APP in the specified directory, by adding this to the PYTHONPATH." + " Defaults to the current working directory.", +) +@click.option( + "--h11-max-incomplete-event-size", + "h11_max_incomplete_event_size", + type=int, + default=None, + help="For h11, the maximum number of bytes to buffer of an incomplete event.", +) +@click.option( + "--reset-contextvars", + is_flag=True, + default=False, + help="Run each ASGI request in a fresh contextvars.Context. Hides context set in the lifespan.", + show_default=True, +) +@click.option( + "--factory", + is_flag=True, + default=False, + help="Treat APP as an application factory, i.e. a () -> callable.", + show_default=True, +) +def main( + app: str, + host: str, + port: int, + uds: str, + fd: int, + loop: LoopFactoryType | str, + http: HTTPProtocolType | str, + ws: WSProtocolType | str, + ws_max_size: int, + ws_max_queue: int, + ws_ping_interval: float, + ws_ping_timeout: float, + ws_per_message_deflate: bool, + lifespan: LifespanType, + interface: InterfaceType, + reload: bool, + reload_dirs: list[str], + reload_includes: list[str], + reload_excludes: list[str], + reload_delay: float, + workers: int, + env_file: str, + log_config: str, + log_level: str, + access_log: bool, + proxy_headers: bool, + server_header: bool, + date_header: bool, + forwarded_allow_ips: str, + root_path: str, + limit_concurrency: int, + backlog: int, + limit_max_requests: int, + limit_max_requests_jitter: int, + timeout_keep_alive: int, + timeout_graceful_shutdown: int | None, + timeout_worker_healthcheck: int, + ssl_keyfile: str, + ssl_certfile: str, + ssl_keyfile_password: str, + ssl_version: int, + ssl_cert_reqs: int, + ssl_ca_certs: str, + ssl_ciphers: str | None, + headers: list[str], + use_colors: bool, + app_dir: str, + h11_max_incomplete_event_size: int | None, + reset_contextvars: bool, + factory: bool, +) -> None: + run( + app, + host=host, + port=port, + uds=uds, + fd=fd, + loop=loop, + http=http, + ws=ws, + ws_max_size=ws_max_size, + ws_max_queue=ws_max_queue, + ws_ping_interval=ws_ping_interval, + ws_ping_timeout=ws_ping_timeout, + ws_per_message_deflate=ws_per_message_deflate, + lifespan=lifespan, + env_file=env_file, + log_config=LOGGING_CONFIG if log_config is None else log_config, + log_level=log_level, + access_log=access_log, + interface=interface, + reload=reload, + reload_dirs=reload_dirs or None, + reload_includes=reload_includes or None, + reload_excludes=reload_excludes or None, + reload_delay=reload_delay, + workers=workers, + proxy_headers=proxy_headers, + server_header=server_header, + date_header=date_header, + forwarded_allow_ips=forwarded_allow_ips, + root_path=root_path, + limit_concurrency=limit_concurrency, + backlog=backlog, + limit_max_requests=limit_max_requests, + limit_max_requests_jitter=limit_max_requests_jitter, + timeout_keep_alive=timeout_keep_alive, + timeout_graceful_shutdown=timeout_graceful_shutdown, + timeout_worker_healthcheck=timeout_worker_healthcheck, + ssl_keyfile=ssl_keyfile, + ssl_certfile=ssl_certfile, + ssl_keyfile_password=ssl_keyfile_password, + ssl_version=ssl_version, + ssl_cert_reqs=ssl_cert_reqs, + ssl_ca_certs=ssl_ca_certs, + ssl_ciphers=ssl_ciphers, + headers=[header.split(":", 1) for header in headers], # type: ignore[misc] + use_colors=use_colors, + factory=factory, + app_dir=app_dir, + h11_max_incomplete_event_size=h11_max_incomplete_event_size, + reset_contextvars=reset_contextvars, + ) + + +def run( + app: ASGIApplication | Callable[..., Any] | str, + *, + host: str = "127.0.0.1", + port: int = 8000, + uds: str | None = None, + fd: int | None = None, + loop: LoopFactoryType | str = "auto", + http: type[asyncio.Protocol] | HTTPProtocolType | str = "auto", + ws: type[asyncio.Protocol] | WSProtocolType | str = "auto", + ws_max_size: int = 16777216, + ws_max_queue: int = 32, + ws_ping_interval: float | None = 20.0, + ws_ping_timeout: float | None = 20.0, + ws_per_message_deflate: bool = True, + lifespan: LifespanType = "auto", + interface: InterfaceType = "auto", + reload: bool = False, + reload_dirs: list[str] | str | None = None, + reload_includes: list[str] | str | None = None, + reload_excludes: list[str] | str | None = None, + reload_delay: float = 0.25, + workers: int | None = None, + env_file: str | os.PathLike[str] | None = None, + log_config: dict[str, Any] | str | os.PathLike[str] | RawConfigParser | IO[Any] | None = LOGGING_CONFIG, + log_level: str | int | None = None, + access_log: bool = True, + proxy_headers: bool = True, + server_header: bool = True, + date_header: bool = True, + forwarded_allow_ips: list[str] | str | None = None, + root_path: str = "", + limit_concurrency: int | None = None, + backlog: int = 2048, + limit_max_requests: int | None = None, + limit_max_requests_jitter: int = 0, + timeout_keep_alive: int = 5, + timeout_graceful_shutdown: int | None = None, + timeout_worker_healthcheck: int = 5, + ssl_keyfile: str | os.PathLike[str] | None = None, + ssl_certfile: str | os.PathLike[str] | None = None, + ssl_keyfile_password: str | None = None, + ssl_version: int = SSL_PROTOCOL_VERSION, + ssl_cert_reqs: int = ssl.CERT_NONE, + ssl_ca_certs: str | os.PathLike[str] | None = None, + ssl_ciphers: str | None = None, + ssl_context_factory: Callable[[Config, Callable[[], ssl.SSLContext]], ssl.SSLContext] | None = None, + headers: list[tuple[str, str]] | None = None, + use_colors: bool | None = None, + app_dir: str | None = None, + factory: bool = False, + h11_max_incomplete_event_size: int | None = None, + reset_contextvars: bool = False, +) -> None: + if app_dir is not None: + sys.path.insert(0, app_dir) + + config = Config( + app, + host=host, + port=port, + uds=uds, + fd=fd, + loop=loop, + http=http, + ws=ws, + ws_max_size=ws_max_size, + ws_max_queue=ws_max_queue, + ws_ping_interval=ws_ping_interval, + ws_ping_timeout=ws_ping_timeout, + ws_per_message_deflate=ws_per_message_deflate, + lifespan=lifespan, + interface=interface, + reload=reload, + reload_dirs=reload_dirs, + reload_includes=reload_includes, + reload_excludes=reload_excludes, + reload_delay=reload_delay, + workers=workers, + env_file=env_file, + log_config=log_config, + log_level=log_level, + access_log=access_log, + proxy_headers=proxy_headers, + server_header=server_header, + date_header=date_header, + forwarded_allow_ips=forwarded_allow_ips, + root_path=root_path, + limit_concurrency=limit_concurrency, + backlog=backlog, + limit_max_requests=limit_max_requests, + limit_max_requests_jitter=limit_max_requests_jitter, + timeout_keep_alive=timeout_keep_alive, + timeout_graceful_shutdown=timeout_graceful_shutdown, + timeout_worker_healthcheck=timeout_worker_healthcheck, + ssl_keyfile=ssl_keyfile, + ssl_certfile=ssl_certfile, + ssl_keyfile_password=ssl_keyfile_password, + ssl_version=ssl_version, + ssl_cert_reqs=ssl_cert_reqs, + ssl_ca_certs=ssl_ca_certs, + ssl_ciphers=ssl_ciphers, + ssl_context_factory=ssl_context_factory, + headers=headers, + use_colors=use_colors, + factory=factory, + h11_max_incomplete_event_size=h11_max_incomplete_event_size, + reset_contextvars=reset_contextvars, + ) + if (config.reload or config.workers > 1) and not isinstance(app, str): + logger = logging.getLogger("uvicorn.error") + logger.warning("You must pass the application as an import string to enable 'reload' or 'workers'.") + sys.exit(1) + + config.load_app() + server = Server(config=config) + + try: + if config.should_reload: + sock = config.bind_socket() + ChangeReload(config, target=server.run, sockets=[sock]).run() + elif config.workers > 1: + sock = config.bind_socket() + Multiprocess(config, target=server.run, sockets=[sock]).run() + else: + server.run() + except KeyboardInterrupt: # pragma: full coverage + pass + finally: + if config.uds and os.path.exists(config.uds): + os.remove(config.uds) # pragma: py-win32 + + if not server.started and not config.should_reload and config.workers == 1: + sys.exit(STARTUP_FAILURE) + + +def __getattr__(name: str) -> Any: + if name == "ServerState": + warnings.warn( + "uvicorn.main.ServerState is deprecated, use uvicorn.server.ServerState instead.", + DeprecationWarning, + ) + from uvicorn.server import ServerState + + return ServerState + raise AttributeError(f"module {__name__} has no attribute {name}") + + +if __name__ == "__main__": + main() # pragma: no cover diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/__init__.py b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6e99207376ea29af27b520c268eb8ba73ff89a9 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/asgi2.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/asgi2.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c26411b87b5b73741f617e6769418fd6a34684ba Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/asgi2.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/message_logger.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/message_logger.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d3217860f8b0f4c2de5f5d17fc4cd376dde0709 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/message_logger.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/proxy_headers.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/proxy_headers.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2c393a03f0f32143f2b0cd015caf67b12f3c711 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/proxy_headers.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/wsgi.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/wsgi.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7eeb71b038f7cb40b35e14ec336111bce2da48b0 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/middleware/__pycache__/wsgi.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/asgi2.py b/.venv/lib/python3.14/site-packages/uvicorn/middleware/asgi2.py new file mode 100644 index 0000000000000000000000000000000000000000..4e15d1599bab04ac38f56cf271d2ff9b1b9984c8 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/middleware/asgi2.py @@ -0,0 +1,15 @@ +from uvicorn._types import ( + ASGI2Application, + ASGIReceiveCallable, + ASGISendCallable, + Scope, +) + + +class ASGI2Middleware: + def __init__(self, app: "ASGI2Application"): + self.app = app + + async def __call__(self, scope: "Scope", receive: "ASGIReceiveCallable", send: "ASGISendCallable") -> None: + instance = self.app(scope) + await instance(receive, send) diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/message_logger.py b/.venv/lib/python3.14/site-packages/uvicorn/middleware/message_logger.py new file mode 100644 index 0000000000000000000000000000000000000000..0174bcce6d48de946214d59bcae5543d38d89c3a --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/middleware/message_logger.py @@ -0,0 +1,87 @@ +import logging +from typing import Any + +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveCallable, + ASGIReceiveEvent, + ASGISendCallable, + ASGISendEvent, + WWWScope, +) +from uvicorn.logging import TRACE_LOG_LEVEL + +PLACEHOLDER_FORMAT = { + "body": "<{length} bytes>", + "bytes": "<{length} bytes>", + "text": "<{length} chars>", + "headers": "<...>", +} + + +def message_with_placeholders(message: Any) -> Any: + """ + Return an ASGI message, with any body-type content omitted and replaced + with a placeholder. + """ + new_message = message.copy() + for attr in PLACEHOLDER_FORMAT.keys(): + if message.get(attr) is not None: + content = message[attr] + placeholder = PLACEHOLDER_FORMAT[attr].format(length=len(content)) + new_message[attr] = placeholder + return new_message + + +class MessageLoggerMiddleware: + def __init__(self, app: "ASGI3Application"): + self.task_counter = 0 + self.app = app + self.logger = logging.getLogger("uvicorn.asgi") + + def trace(message: Any, *args: Any, **kwargs: Any) -> None: + self.logger.log(TRACE_LOG_LEVEL, message, *args, **kwargs) + + self.logger.trace = trace # type: ignore + + async def __call__( + self, + scope: "WWWScope", + receive: "ASGIReceiveCallable", + send: "ASGISendCallable", + ) -> None: + self.task_counter += 1 + + task_counter = self.task_counter + client = scope.get("client") + prefix = "%s:%d - ASGI" % (client[0], client[1]) if client else "ASGI" + + async def inner_receive() -> "ASGIReceiveEvent": + message = await receive() + logged_message = message_with_placeholders(message) + log_text = "%s [%d] Receive %s" + self.logger.trace( # type: ignore + log_text, prefix, task_counter, logged_message + ) + return message + + async def inner_send(message: "ASGISendEvent") -> None: + logged_message = message_with_placeholders(message) + log_text = "%s [%d] Send %s" + self.logger.trace( # type: ignore + log_text, prefix, task_counter, logged_message + ) + await send(message) + + logged_scope = message_with_placeholders(scope) + log_text = "%s [%d] Started scope=%s" + self.logger.trace(log_text, prefix, task_counter, logged_scope) # type: ignore + try: + await self.app(scope, inner_receive, inner_send) + except BaseException as exc: + log_text = "%s [%d] Raised exception" + self.logger.trace(log_text, prefix, task_counter) # type: ignore + raise exc from None + else: + log_text = "%s [%d] Completed" + self.logger.trace(log_text, prefix, task_counter) # type: ignore diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/proxy_headers.py b/.venv/lib/python3.14/site-packages/uvicorn/middleware/proxy_headers.py new file mode 100644 index 0000000000000000000000000000000000000000..f1944694c75a94eb3acfdcca060370ebcc5f3701 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/middleware/proxy_headers.py @@ -0,0 +1,180 @@ +from __future__ import annotations + +import ipaddress + +from uvicorn._types import ASGI3Application, ASGIReceiveCallable, ASGISendCallable, Scope + + +class ProxyHeadersMiddleware: + """Middleware for handling known proxy headers + + This middleware can be used when a known proxy is fronting the application, + and is trusted to be properly setting the `X-Forwarded-Proto` and + `X-Forwarded-For` headers with the connecting client information. + + Modifies the `client` and `scheme` information so that they reference + the connecting client, rather that the connecting proxy. + + References: + - + - + """ + + def __init__(self, app: ASGI3Application, trusted_hosts: list[str] | str = "127.0.0.1") -> None: + self.app = app + self.trusted_hosts = _TrustedHosts(trusted_hosts) + + async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: + if scope["type"] == "lifespan": + return await self.app(scope, receive, send) + + client_addr = scope.get("client") + client_host = client_addr[0] if client_addr else None + + if client_host in self.trusted_hosts: + x_forwarded_proto_value: bytes | None = None + x_forwarded_for_values: list[bytes] = [] + for name, value in scope["headers"]: + if name == b"x-forwarded-proto": + x_forwarded_proto_value = value + elif name == b"x-forwarded-for": + x_forwarded_for_values.append(value) + + if x_forwarded_proto_value is not None: + x_forwarded_proto = x_forwarded_proto_value.decode("latin1").strip() + + if x_forwarded_proto in {"http", "https", "ws", "wss"}: + if scope["type"] == "websocket": + scope["scheme"] = x_forwarded_proto.replace("http", "ws") + else: + scope["scheme"] = x_forwarded_proto + + if x_forwarded_for_values: + x_forwarded_for = b", ".join(x_forwarded_for_values).decode("latin1") + host, port = self.trusted_hosts.get_trusted_client_address(x_forwarded_for) + + if host: + # If the x-forwarded-for header is empty then host is an empty string. + # Only set the client if we actually got something usable. + # See: https://github.com/Kludex/uvicorn/issues/1068 + scope["client"] = (host, port) + + return await self.app(scope, receive, send) + + +def _parse_raw_hosts(value: str) -> list[str]: + return [item.strip() for item in value.split(",")] + + +def _parse_host_port(value: str) -> tuple[str, int]: + """Parse a forwarded host value into host and optional port. + + Accepts bare IPs, IPv4 `host:port`, and bracketed IPv6 `[host]:port`. + Any unrecognized or malformed value is treated conservatively and returned + without a port so trust checks do not silently normalize arbitrary input. + """ + + if value.startswith("["): + bracket_end = value.find("]") + if bracket_end == -1: + return value, 0 + + host = value[1:bracket_end] + remainder = value[bracket_end + 1 :] + if not remainder: + return host, 0 + if not remainder.startswith(":"): + return value, 0 + + try: + return host, int(remainder[1:]) + except ValueError: + return host, 0 + + if value.count(":") == 1: + host, port = value.rsplit(":", 1) + try: + return host, int(port) + except ValueError: + return value, 0 + + return value, 0 + + +class _TrustedHosts: + """Container for trusted hosts and networks""" + + def __init__(self, trusted_hosts: list[str] | str) -> None: + self.always_trust: bool = trusted_hosts in ("*", ["*"]) + + self.trusted_literals: set[str] = set() + self.trusted_hosts: set[ipaddress.IPv4Address | ipaddress.IPv6Address] = set() + self.trusted_networks: set[ipaddress.IPv4Network | ipaddress.IPv6Network] = set() + + # Notes: + # - We separate hosts from literals as there are many ways to write + # an IPv6 Address so we need to compare by object. + # - We don't convert IP Address to single host networks (e.g. /32 / 128) as + # it more efficient to do an address lookup in a set than check for + # membership in each network. + # - We still allow literals as it might be possible that we receive a + # something that isn't an IP Address e.g. a unix socket. + + if not self.always_trust: + if isinstance(trusted_hosts, str): + trusted_hosts = _parse_raw_hosts(trusted_hosts) + + for host in trusted_hosts: + # Note: because we always convert invalid IP types to literals it + # is not possible for the user to know they provided a malformed IP + # type - this may lead to unexpected / difficult to debug behaviour. + + if "/" in host: + # Looks like a network + try: + self.trusted_networks.add(ipaddress.ip_network(host)) + except ValueError: + # Was not a valid IP Network + self.trusted_literals.add(host) + else: + try: + self.trusted_hosts.add(ipaddress.ip_address(host)) + except ValueError: + # Was not a valid IP Address + self.trusted_literals.add(host) + + def __contains__(self, host: str | None) -> bool: + if self.always_trust: + return True + + if not host: + return False + + try: + ip = ipaddress.ip_address(host) + if ip in self.trusted_hosts: + return True + return any(ip in net for net in self.trusted_networks) + + except ValueError: + return host in self.trusted_literals + + def get_trusted_client_address(self, x_forwarded_for: str) -> tuple[str, int]: + """Extract the client address from x_forwarded_for header. + + In general this is the first "untrusted" host in the forwarded for list. + """ + x_forwarded_for_hosts = _parse_raw_hosts(x_forwarded_for) + + if self.always_trust: + return _parse_host_port(x_forwarded_for_hosts[0]) + + # Note: each proxy appends to the header list so check it in reverse order + for host_port in reversed(x_forwarded_for_hosts): + host, port = _parse_host_port(host_port) + if host not in self: + return host, port + + # All hosts are trusted meaning that the client was also a trusted proxy + # See https://github.com/Kludex/uvicorn/issues/1068#issuecomment-855371576 + return _parse_host_port(x_forwarded_for_hosts[0]) diff --git a/.venv/lib/python3.14/site-packages/uvicorn/middleware/wsgi.py b/.venv/lib/python3.14/site-packages/uvicorn/middleware/wsgi.py new file mode 100644 index 0000000000000000000000000000000000000000..b2e3cb77d421085947741430e8b5451821eb66c7 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/middleware/wsgi.py @@ -0,0 +1,199 @@ +from __future__ import annotations + +import asyncio +import concurrent.futures +import io +import sys +import warnings +from collections import deque +from collections.abc import Iterable + +from uvicorn._types import ( + ASGIReceiveCallable, + ASGIReceiveEvent, + ASGISendCallable, + ASGISendEvent, + Environ, + ExcInfo, + HTTPRequestEvent, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + HTTPScope, + StartResponse, + WSGIApp, +) + + +def build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: io.BytesIO) -> Environ: + """ + Builds a scope and request message into a WSGI environ object. + """ + script_name = scope.get("root_path", "").encode("utf8").decode("latin1") + path_info = scope["path"].encode("utf8").decode("latin1") + if path_info.startswith(script_name): + path_info = path_info[len(script_name) :] + environ = { + "REQUEST_METHOD": scope["method"], + "SCRIPT_NAME": script_name, + "PATH_INFO": path_info, + "QUERY_STRING": scope["query_string"].decode("ascii"), + "SERVER_PROTOCOL": "HTTP/%s" % scope["http_version"], + "wsgi.version": (1, 0), + "wsgi.url_scheme": scope.get("scheme", "http"), + "wsgi.input": body, + "wsgi.errors": sys.stdout, + "wsgi.multithread": True, + "wsgi.multiprocess": True, + "wsgi.run_once": False, + } + + # Get server name and port - required in WSGI, not in ASGI + server = scope.get("server") + if server is None: + server = ("localhost", 80) + environ["SERVER_NAME"] = server[0] + environ["SERVER_PORT"] = server[1] + + # Get client IP address + client = scope.get("client") + if client is not None: + environ["REMOTE_ADDR"] = client[0] + + # Go through headers and make them into environ entries + for name, value in scope.get("headers", []): + name_str: str = name.decode("latin1") + if name_str == "content-length": + corrected_name = "CONTENT_LENGTH" + elif name_str == "content-type": + corrected_name = "CONTENT_TYPE" + else: + corrected_name = "HTTP_%s" % name_str.upper().replace("-", "_") + # HTTPbis say only ASCII chars are allowed in headers, but we latin1 + # just in case + value_str: str = value.decode("latin1") + if corrected_name in environ: + corrected_name_environ = environ[corrected_name] + assert isinstance(corrected_name_environ, str) + value_str = corrected_name_environ + "," + value_str + environ[corrected_name] = value_str + return environ + + +class _WSGIMiddleware: + def __init__(self, app: WSGIApp, workers: int = 10): + warnings.warn( + "Uvicorn's native WSGI implementation is deprecated, you should switch to a2wsgi (`pip install a2wsgi`).", + DeprecationWarning, + ) + self.app = app + self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers) + + async def __call__( + self, + scope: HTTPScope, + receive: ASGIReceiveCallable, + send: ASGISendCallable, + ) -> None: + assert scope["type"] == "http" + instance = WSGIResponder(self.app, self.executor, scope) + await instance(receive, send) + + +class WSGIResponder: + def __init__( + self, + app: WSGIApp, + executor: concurrent.futures.ThreadPoolExecutor, + scope: HTTPScope, + ): + self.app = app + self.executor = executor + self.scope = scope + self.status = None + self.response_headers = None + self.send_event = asyncio.Event() + self.send_queue: deque[ASGISendEvent | None] = deque() + self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + self.response_started = False + self.exc_info: ExcInfo | None = None + + async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: + message: HTTPRequestEvent = await receive() # type: ignore[assignment] + body = io.BytesIO(message.get("body", b"")) + more_body = message.get("more_body", False) + if more_body: + body.seek(0, io.SEEK_END) + while more_body: + body_message: HTTPRequestEvent = ( + await receive() # type: ignore[assignment] + ) + body.write(body_message.get("body", b"")) + more_body = body_message.get("more_body", False) + body.seek(0) + environ = build_environ(self.scope, message, body) + self.loop = asyncio.get_event_loop() + wsgi = self.loop.run_in_executor(self.executor, self.wsgi, environ, self.start_response) + sender = self.loop.create_task(self.sender(send)) + try: + await asyncio.wait_for(wsgi, None) + finally: + self.send_queue.append(None) + self.send_event.set() + await asyncio.wait_for(sender, None) + if self.exc_info is not None: + raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2]) + + async def sender(self, send: ASGISendCallable) -> None: + while True: + if self.send_queue: + message = self.send_queue.popleft() + if message is None: + return + await send(message) + else: # pragma: no cover + await self.send_event.wait() + self.send_event.clear() + + def start_response( + self, + status: str, + response_headers: Iterable[tuple[str, str]], + exc_info: ExcInfo | None = None, + ) -> None: + self.exc_info = exc_info + if not self.response_started: + self.response_started = True + status_code_str, _ = status.split(" ", 1) + status_code = int(status_code_str) + headers = [(name.encode("ascii"), value.encode("ascii")) for name, value in response_headers] + http_response_start_event: HTTPResponseStartEvent = { + "type": "http.response.start", + "status": status_code, + "headers": headers, + } + self.send_queue.append(http_response_start_event) + self.loop.call_soon_threadsafe(self.send_event.set) + + def wsgi(self, environ: Environ, start_response: StartResponse) -> None: + for chunk in self.app(environ, start_response): # type: ignore + response_body: HTTPResponseBodyEvent = { + "type": "http.response.body", + "body": chunk, + "more_body": True, + } + self.send_queue.append(response_body) + self.loop.call_soon_threadsafe(self.send_event.set) + + empty_body: HTTPResponseBodyEvent = { + "type": "http.response.body", + "body": b"", + "more_body": False, + } + self.send_queue.append(empty_body) + self.loop.call_soon_threadsafe(self.send_event.set) + + +try: + from a2wsgi import WSGIMiddleware +except ModuleNotFoundError: # pragma: no cover + WSGIMiddleware = _WSGIMiddleware # type: ignore[misc, assignment] diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/__init__.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f25e4744bcc298bc45906391886aab7b9777959f Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/__pycache__/utils.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/__pycache__/utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebe7a696bc9075218662c6c2b6e56ee2f440d3af Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/__pycache__/utils.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__init__.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..867075d994523ef635107bff00759b85b4fe665a Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/auto.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/auto.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bcc49e43707c0a2d4dc0e5a9f207d3a8e2c4892c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/auto.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/flow_control.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/flow_control.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c73eeb934c32d4890260be5a35bd652a4658de6c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/flow_control.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/h11_impl.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/h11_impl.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..062e29aa0c1c9449264382041edbf6b32c0e36b6 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/h11_impl.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/httptools_impl.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/httptools_impl.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67d6caba7d932ae56f8c4175fb0fd900ecb9ffba Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/__pycache__/httptools_impl.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/auto.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/auto.py new file mode 100644 index 0000000000000000000000000000000000000000..a14bec144a97a5e3718a768abe3b6a9e7e93d2c1 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/auto.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import asyncio + +AutoHTTPProtocol: type[asyncio.Protocol] +try: + import httptools # noqa +except ImportError: # pragma: no cover + from uvicorn.protocols.http.h11_impl import H11Protocol + + AutoHTTPProtocol = H11Protocol +else: # pragma: no cover + from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol + + AutoHTTPProtocol = HttpToolsProtocol diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/flow_control.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/flow_control.py new file mode 100644 index 0000000000000000000000000000000000000000..2d1b5fa2d3366fd6584181a26c8569a0207900cc --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/flow_control.py @@ -0,0 +1,54 @@ +import asyncio + +from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope + +CLOSE_HEADER = (b"connection", b"close") + +HIGH_WATER_LIMIT = 65536 + + +class FlowControl: + def __init__(self, transport: asyncio.Transport) -> None: + self._transport = transport + self.read_paused = False + self.write_paused = False + self._is_writable_event = asyncio.Event() + self._is_writable_event.set() + + async def drain(self) -> None: + await self._is_writable_event.wait() # pragma: full coverage + + def pause_reading(self) -> None: + if not self.read_paused: + self.read_paused = True + self._transport.pause_reading() + + def resume_reading(self) -> None: + if self.read_paused: + self.read_paused = False + self._transport.resume_reading() + + def pause_writing(self) -> None: + if not self.write_paused: # pragma: full coverage + self.write_paused = True + self._is_writable_event.clear() + + def resume_writing(self) -> None: + if self.write_paused: # pragma: full coverage + self.write_paused = False + self._is_writable_event.set() + + +async def service_unavailable(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: + await send( + { + "type": "http.response.start", + "status": 503, + "headers": [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"content-length", b"19"), + (b"connection", b"close"), + ], + } + ) + await send({"type": "http.response.body", "body": b"Service Unavailable", "more_body": False}) diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/h11_impl.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/h11_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..cb68ea0eca449756f860cfea3996ecd158624f8f --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/h11_impl.py @@ -0,0 +1,544 @@ +from __future__ import annotations + +import asyncio +import contextvars +import http +import logging +import sys +from collections.abc import Callable +from typing import Any, Literal +from urllib.parse import unquote + +import h11 +from h11._connection import DEFAULT_MAX_INCOMPLETE_EVENT_SIZE + +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveEvent, + ASGISendEvent, + HTTPRequestEvent, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + HTTPScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.http.flow_control import CLOSE_HEADER, HIGH_WATER_LIMIT, FlowControl, service_unavailable +from uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl +from uvicorn.server import ServerState + + +def _get_status_phrase(status_code: int) -> bytes: + try: + return http.HTTPStatus(status_code).phrase.encode() + except ValueError: + return b"" + + +STATUS_PHRASES = {status_code: _get_status_phrase(status_code) for status_code in range(100, 600)} + + +class H11Protocol(asyncio.Protocol): + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() + + self.config = config + self.app = config.loaded_app + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.access_logger = logging.getLogger("uvicorn.access") + self.access_log = self.access_logger.hasHandlers() + self.conn = h11.Connection( + h11.SERVER, + config.h11_max_incomplete_event_size + if config.h11_max_incomplete_event_size is not None + else DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, + ) + self.ws_protocol_class = config.ws_protocol_class + self.root_path = config.root_path + self.limit_concurrency = config.limit_concurrency + self.app_state = app_state + + # Timeouts + self.timeout_keep_alive_task: asyncio.TimerHandle | None = None + self.timeout_keep_alive = config.timeout_keep_alive + + # Shared server state + self.server_state = server_state + self.connections = server_state.connections + self.tasks = server_state.tasks + + # Per-connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.flow: FlowControl = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["http", "https"] | None = None + + # Per-request state + self.scope: HTTPScope = None # type: ignore[assignment] + self.headers: list[tuple[bytes, bytes]] = None # type: ignore[assignment] + self.cycle: RequestResponseCycle = None # type: ignore[assignment] + + # Protocol interface + def connection_made( # type: ignore[override] + self, transport: asyncio.Transport + ) -> None: + self.connections.add(self) + + self.transport = transport + self.flow = FlowControl(transport) + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "https" if is_ssl(transport) else "http" + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.connections.discard(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection lost", prefix) + + if self.cycle and not self.cycle.response_complete: + self.cycle.disconnected = True + if self.conn.our_state != h11.ERROR: + event = h11.ConnectionClosed() + try: + self.conn.send(event) + except h11.LocalProtocolError: + # Premature client disconnect + pass + + if self.cycle is not None: + self.cycle.message_event.set() + if self.flow is not None: + self.flow.resume_writing() + if exc is None: + self.transport.close() + self._unset_keepalive_if_required() + + def eof_received(self) -> None: + pass + + def _unset_keepalive_if_required(self) -> None: + if self.timeout_keep_alive_task is not None: + self.timeout_keep_alive_task.cancel() + self.timeout_keep_alive_task = None + + def _get_upgrade(self) -> bytes | None: + connection = [] + upgrade = None + for name, value in self.headers: + if name == b"connection": + connection = [token.lower().strip() for token in value.split(b",")] + if name == b"upgrade": + upgrade = value.lower() + if b"upgrade" in connection: + return upgrade + return None + + def _should_upgrade_to_ws(self) -> bool: + if self.ws_protocol_class is None: + return False + return True + + def _unsupported_upgrade_warning(self) -> None: + msg = "Unsupported upgrade request." + self.logger.warning(msg) + if not self._should_upgrade_to_ws(): + msg = "No supported WebSocket library detected. Please use \"pip install 'uvicorn[standard]'\", or install 'websockets' or 'wsproto' manually." # noqa: E501 + self.logger.warning(msg) + + def _should_upgrade(self) -> bool: + upgrade = self._get_upgrade() + if upgrade == b"websocket" and self._should_upgrade_to_ws(): + return True + if upgrade is not None: + self._unsupported_upgrade_warning() + return False + + def data_received(self, data: bytes) -> None: + self._unset_keepalive_if_required() + + self.conn.receive_data(data) + self.handle_events() + + def handle_events(self) -> None: + while True: + try: + event = self.conn.next_event() + except h11.RemoteProtocolError: + msg = "Invalid HTTP request received." + self.logger.warning(msg) + self.send_400_response(msg) + return + + if event is h11.NEED_DATA: + break + + elif event is h11.PAUSED: + # This case can occur in HTTP pipelining, so we need to + # stop reading any more data, and ensure that at the end + # of the active request/response cycle we handle any + # events that have been buffered up. + self.flow.pause_reading() + break + + elif isinstance(event, h11.Request): + self.headers = [(key.lower(), value) for key, value in event.headers] + raw_path, _, query_string = event.target.partition(b"?") + path = unquote(raw_path.decode("ascii")) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + raw_path + self.scope = { + "type": "http", + "asgi": {"version": self.config.asgi_version, "spec_version": "2.3"}, + "http_version": event.http_version.decode("ascii"), + "server": self.server, + "client": self.client, + "scheme": self.scheme, # type: ignore[typeddict-item] + "method": event.method.decode("ascii"), + "root_path": self.root_path, + "path": full_path, + "raw_path": full_raw_path, + "query_string": query_string, + "headers": self.headers, + "state": self.app_state.copy(), + } + if self._should_upgrade(): + self.handle_websocket_upgrade(event) + return + + # Handle 503 responses when 'limit_concurrency' is exceeded. + if self.limit_concurrency is not None and ( + len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency + ): + app = service_unavailable + message = "Exceeded concurrency limit." + self.logger.warning(message) + else: + app = self.app + + # When starting to process a request, disable the keep-alive + # timeout. Normally we disable this when receiving data from + # client and set back when finishing processing its request. + # However, for pipelined requests processing finishes after + # already receiving the next request and thus the timer may + # be set here, which we don't want. + self._unset_keepalive_if_required() + + self.cycle = RequestResponseCycle( + scope=self.scope, + conn=self.conn, + transport=self.transport, + flow=self.flow, + logger=self.logger, + access_logger=self.access_logger, + access_log=self.access_log, + default_headers=self.server_state.default_headers, + message_event=asyncio.Event(), + on_response=self.on_response_complete, + ) + if self.config.reset_contextvars: + # Opt-in workaround for https://github.com/python/cpython/issues/140947: + # asyncio can leak context vars between tasks. Hides context set in the + # lifespan or by external instrumentation. + if sys.version_info >= (3, 11): # pragma: py-lt-311 + task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context()) + else: # pragma: py-gte-311 + task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app)) + else: + task = self.loop.create_task(self.cycle.run_asgi(app)) + task.add_done_callback(self.tasks.discard) + self.tasks.add(task) + + elif isinstance(event, h11.Data): + if self.conn.our_state is h11.DONE: + continue + self.cycle.body += event.data + if len(self.cycle.body) > HIGH_WATER_LIMIT: + self.flow.pause_reading() + self.cycle.message_event.set() + + elif isinstance(event, h11.EndOfMessage): + if self.conn.our_state is h11.DONE: + self.transport.resume_reading() + self.conn.start_next_cycle() + continue + self.cycle.more_body = False + self.cycle.message_event.set() + if self.conn.their_state == h11.MUST_CLOSE: + break + + def handle_websocket_upgrade(self, event: h11.Request) -> None: + if self.logger.level <= TRACE_LOG_LEVEL: # pragma: full coverage + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sUpgrading to WebSocket", prefix) + + self.connections.discard(self) + output = [event.method, b" ", event.target, b" HTTP/1.1\r\n"] + for name, value in self.headers: + output += [name, b": ", value, b"\r\n"] + output.append(b"\r\n") + protocol = self.ws_protocol_class( # type: ignore[call-arg, misc] + config=self.config, + server_state=self.server_state, + app_state=self.app_state, + ) + protocol.connection_made(self.transport) + protocol.data_received(b"".join(output)) + self.transport.set_protocol(protocol) + + def send_400_response(self, msg: str) -> None: + reason = STATUS_PHRASES[400] + headers: list[tuple[bytes, bytes]] = [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"connection", b"close"), + ] + event = h11.Response(status_code=400, headers=headers, reason=reason) + output = self.conn.send(event) + self.transport.write(output) + + output = self.conn.send(event=h11.Data(data=msg.encode("ascii"))) + self.transport.write(output) + + output = self.conn.send(event=h11.EndOfMessage()) + self.transport.write(output) + + self.transport.close() + + def on_response_complete(self) -> None: + self.server_state.total_requests += 1 + + if self.transport.is_closing(): + return + + # Set a short Keep-Alive timeout. + self._unset_keepalive_if_required() + + self.timeout_keep_alive_task = self.loop.call_later(self.timeout_keep_alive, self.timeout_keep_alive_handler) + + # Unpause data reads if needed. + self.flow.resume_reading() + + # Unblock any pipelined events. + if self.conn.our_state is h11.DONE and self.conn.their_state is h11.DONE: + self.conn.start_next_cycle() + self.handle_events() + + def shutdown(self) -> None: + """ + Called by the server to commence a graceful shutdown. + """ + if self.cycle is None or self.cycle.response_complete: + event = h11.ConnectionClosed() + self.conn.send(event) + self.transport.close() + else: + self.cycle.keep_alive = False + + def pause_writing(self) -> None: + """ + Called by the transport when the write buffer exceeds the high water mark. + """ + self.flow.pause_writing() # pragma: full coverage + + def resume_writing(self) -> None: + """ + Called by the transport when the write buffer drops below the low water mark. + """ + self.flow.resume_writing() # pragma: full coverage + + def timeout_keep_alive_handler(self) -> None: + """ + Called on a keep-alive connection if no new data is received after a short + delay. + """ + if not self.transport.is_closing(): + event = h11.ConnectionClosed() + self.conn.send(event) + self.transport.close() + + +class RequestResponseCycle: + def __init__( + self, + scope: HTTPScope, + conn: h11.Connection, + transport: asyncio.Transport, + flow: FlowControl, + logger: logging.Logger, + access_logger: logging.Logger, + access_log: bool, + default_headers: list[tuple[bytes, bytes]], + message_event: asyncio.Event, + on_response: Callable[..., None], + ) -> None: + self.scope = scope + self.conn = conn + self.transport = transport + self.flow = flow + self.logger = logger + self.access_logger = access_logger + self.access_log = access_log + self.default_headers = default_headers + self.message_event = message_event + self.on_response = on_response + + # Connection state + self.disconnected = False + self.keep_alive = True + self.waiting_for_100_continue = conn.they_are_waiting_for_100_continue + + # Request state + self.body = bytearray() + self.more_body = True + + # Response state + self.response_started = False + self.response_complete = False + + # ASGI exception wrapper + async def run_asgi(self, app: ASGI3Application) -> None: + try: + result = await app( # type: ignore[func-returns-value] + self.scope, self.receive, self.send + ) + except BaseException as exc: + msg = "Exception in ASGI application\n" + self.logger.error(msg, exc_info=exc) + if not self.response_started: + await self.send_500_response() + else: + self.transport.close() + else: + if result is not None: + msg = "ASGI callable should return None, but returned '%s'." + self.logger.error(msg, result) + self.transport.close() + elif not self.response_started and not self.disconnected: + msg = "ASGI callable returned without starting response." + self.logger.error(msg) + await self.send_500_response() + elif not self.response_complete and not self.disconnected: + msg = "ASGI callable returned without completing response." + self.logger.error(msg) + self.transport.close() + finally: + self.on_response = lambda: None + + async def send_500_response(self) -> None: + response_start_event: HTTPResponseStartEvent = { + "type": "http.response.start", + "status": 500, + "headers": [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"connection", b"close"), + ], + } + await self.send(response_start_event) + response_body_event: HTTPResponseBodyEvent = { + "type": "http.response.body", + "body": b"Internal Server Error", + "more_body": False, + } + await self.send(response_body_event) + + # ASGI interface + async def send(self, message: ASGISendEvent) -> None: + if self.flow.write_paused and not self.disconnected: + await self.flow.drain() # pragma: full coverage + + if self.disconnected: + return # pragma: full coverage + + if not self.response_started: + # Sending response status line and headers + if message["type"] != "http.response.start": + raise RuntimeError(f"Expected ASGI message 'http.response.start', but got '{message['type']}'.") + + self.response_started = True + self.waiting_for_100_continue = False + + status = message["status"] + headers = self.default_headers + list(message.get("headers", [])) + + if CLOSE_HEADER in self.scope["headers"] and CLOSE_HEADER not in headers: + headers = headers + [CLOSE_HEADER] + + if self.access_log: + self.access_logger.info( + '%s - "%s %s HTTP/%s" %d', + get_client_addr(self.scope), + self.scope["method"], + get_path_with_query_string(self.scope), + self.scope["http_version"], + status, + ) + + # Write response status line and headers + reason = STATUS_PHRASES[status] + response = h11.Response(status_code=status, headers=headers, reason=reason) + output = self.conn.send(event=response) + self.transport.write(output) + + elif not self.response_complete: + # Sending response body + if message["type"] != "http.response.body": + raise RuntimeError(f"Expected ASGI message 'http.response.body', but got '{message['type']}'.") + + body = message.get("body", b"") + more_body = message.get("more_body", False) + + # Write response body + data = b"" if self.scope["method"] == "HEAD" else body + output = self.conn.send(event=h11.Data(data=data)) + self.transport.write(output) + + # Handle response completion + if not more_body: + self.response_complete = True + self.message_event.set() + output = self.conn.send(event=h11.EndOfMessage()) + self.transport.write(output) + + else: + # Response already sent + raise RuntimeError(f"Unexpected ASGI message '{message['type']}' sent, after response already completed.") + + if self.response_complete: + if self.conn.our_state is h11.MUST_CLOSE or not self.keep_alive: + self.conn.send(event=h11.ConnectionClosed()) + self.transport.close() + self.on_response() + + async def receive(self) -> ASGIReceiveEvent: + if self.waiting_for_100_continue and not self.transport.is_closing(): + headers: list[tuple[str, str]] = [] + event = h11.InformationalResponse(status_code=100, headers=headers, reason="Continue") + output = self.conn.send(event=event) + self.transport.write(output) + self.waiting_for_100_continue = False + + if not self.disconnected and not self.response_complete: + self.flow.resume_reading() + await self.message_event.wait() + self.message_event.clear() + + if self.disconnected or self.response_complete: + return {"type": "http.disconnect"} + + message: HTTPRequestEvent = {"type": "http.request", "body": bytes(self.body), "more_body": self.more_body} + self.body = bytearray() + return message diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/httptools_impl.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/httptools_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..de9c6fc66826923fa67b59dc6e46ea97ee25b6d2 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/httptools_impl.py @@ -0,0 +1,576 @@ +from __future__ import annotations + +import asyncio +import contextvars +import http +import logging +import re +import sys +import urllib +from asyncio.events import TimerHandle +from collections import deque +from collections.abc import Callable +from typing import Any, Literal + +import httptools + +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveEvent, + ASGISendEvent, + HTTPRequestEvent, + HTTPScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.http.flow_control import CLOSE_HEADER, HIGH_WATER_LIMIT, FlowControl, service_unavailable +from uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl +from uvicorn.server import ServerState + +HEADER_RE = re.compile(b'[\x00-\x1f\x7f()<>@,;:\\[\\]={} \t\\\\"]') +HEADER_VALUE_RE = re.compile(b"[\x00-\x08\x0a-\x1f\x7f]") + + +def _get_status_line(status_code: int) -> bytes: + try: + phrase = http.HTTPStatus(status_code).phrase.encode() + except ValueError: + phrase = b"" + return b"".join([b"HTTP/1.1 ", str(status_code).encode(), b" ", phrase, b"\r\n"]) + + +STATUS_LINE = {status_code: _get_status_line(status_code) for status_code in range(100, 600)} + + +class HttpToolsProtocol(asyncio.Protocol): + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() + + self.config = config + self.app = config.loaded_app + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.access_logger = logging.getLogger("uvicorn.access") + self.access_log = self.access_logger.hasHandlers() + self.parser = httptools.HttpRequestParser(self) + + try: + # Enable dangerous leniencies to allow server to a response on the first request from a pipelined request. + self.parser.set_dangerous_leniencies(lenient_data_after_close=True) + except AttributeError: # pragma: no cover + # httptools < 0.6.3 + pass + + self.ws_protocol_class = config.ws_protocol_class + self.root_path = config.root_path + self.limit_concurrency = config.limit_concurrency + self.app_state = app_state + + # Timeouts + self.timeout_keep_alive_task: TimerHandle | None = None + self.timeout_keep_alive = config.timeout_keep_alive + + # Global state + self.server_state = server_state + self.connections = server_state.connections + self.tasks = server_state.tasks + + # Per-connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.flow: FlowControl = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["http", "https"] | None = None + self.pipeline: deque[tuple[RequestResponseCycle, ASGI3Application]] = deque() + + # Per-request state + self.scope: HTTPScope = None # type: ignore[assignment] + self.headers: list[tuple[bytes, bytes]] = None # type: ignore[assignment] + self.expect_100_continue = False + self.cycle: RequestResponseCycle = None # type: ignore[assignment] + + # Protocol interface + def connection_made( # type: ignore[override] + self, transport: asyncio.Transport + ) -> None: + self.connections.add(self) + + self.transport = transport + self.flow = FlowControl(transport) + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "https" if is_ssl(transport) else "http" + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.connections.discard(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection lost", prefix) + + if self.cycle and not self.cycle.response_complete: + self.cycle.disconnected = True + if self.cycle is not None: + self.cycle.message_event.set() + if self.flow is not None: + self.flow.resume_writing() + if exc is None: + self.transport.close() + self._unset_keepalive_if_required() + + self.parser = None # type: ignore[assignment] + + def eof_received(self) -> None: + pass + + def _unset_keepalive_if_required(self) -> None: + if self.timeout_keep_alive_task is not None: + self.timeout_keep_alive_task.cancel() + self.timeout_keep_alive_task = None + + def _get_upgrade(self) -> bytes | None: + connection = [] + upgrade = None + for name, value in self.headers: + if name == b"connection": + connection = [token.lower().strip() for token in value.split(b",")] + if name == b"upgrade": + upgrade = value.lower() + if b"upgrade" in connection: + return upgrade + return None # pragma: full coverage + + def _should_upgrade_to_ws(self) -> bool: + if self.ws_protocol_class is None: + return False + return True + + def _unsupported_upgrade_warning(self) -> None: + self.logger.warning("Unsupported upgrade request.") + if not self._should_upgrade_to_ws(): + msg = "No supported WebSocket library detected. Please use \"pip install 'uvicorn[standard]'\", or install 'websockets' or 'wsproto' manually." # noqa: E501 + self.logger.warning(msg) + + def _should_upgrade(self) -> bool: + upgrade = self._get_upgrade() + return upgrade == b"websocket" and self._should_upgrade_to_ws() + + def data_received(self, data: bytes) -> None: + self._unset_keepalive_if_required() + + try: + self.parser.feed_data(data) + except httptools.HttpParserError: + msg = "Invalid HTTP request received." + self.logger.warning(msg) + self.send_400_response(msg) + return + except httptools.HttpParserUpgrade: + if self._should_upgrade(): + self.handle_websocket_upgrade() + else: + self._unsupported_upgrade_warning() + + def handle_websocket_upgrade(self) -> None: + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sUpgrading to WebSocket", prefix) + + self.connections.discard(self) + method = self.scope["method"].encode() + output = [method, b" ", self.url, b" HTTP/1.1\r\n"] + for name, value in self.scope["headers"]: + output += [name, b": ", value, b"\r\n"] + output.append(b"\r\n") + protocol = self.ws_protocol_class( # type: ignore[call-arg, misc] + config=self.config, + server_state=self.server_state, + app_state=self.app_state, + ) + protocol.connection_made(self.transport) + protocol.data_received(b"".join(output)) + self.transport.set_protocol(protocol) + + def send_400_response(self, msg: str) -> None: + content = [STATUS_LINE[400]] + for name, value in self.server_state.default_headers: + content.extend([name, b": ", value, b"\r\n"]) # pragma: full coverage + content.extend( + [ + b"content-type: text/plain; charset=utf-8\r\n", + b"content-length: " + str(len(msg)).encode("ascii") + b"\r\n", + b"connection: close\r\n", + b"\r\n", + msg.encode("ascii"), + ] + ) + self.transport.write(b"".join(content)) + self.transport.close() + + def on_message_begin(self) -> None: + self.url = b"" + self.expect_100_continue = False + self.headers = [] + self.scope = { # type: ignore[typeddict-item] + "type": "http", + "asgi": {"version": self.config.asgi_version, "spec_version": "2.3"}, + "http_version": "1.1", + "server": self.server, + "client": self.client, + "scheme": self.scheme, # type: ignore[typeddict-item] + "root_path": self.root_path, + "headers": self.headers, + "state": self.app_state.copy(), + } + + # Parser callbacks + def on_url(self, url: bytes) -> None: + self.url += url + + def on_header(self, name: bytes, value: bytes) -> None: + name = name.lower() + if name == b"expect" and value.lower() == b"100-continue": + self.expect_100_continue = True + self.headers.append((name, value)) + + def on_headers_complete(self) -> None: + http_version = self.parser.get_http_version() + method = self.parser.get_method() + self.scope["method"] = method.decode("ascii") + if http_version != "1.1": + self.scope["http_version"] = http_version + if self.parser.should_upgrade() and self._should_upgrade(): + return + parsed_url = httptools.parse_url(self.url) + raw_path = parsed_url.path + path = raw_path.decode("ascii") + if "%" in path: + path = urllib.parse.unquote(path) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + raw_path + self.scope["path"] = full_path + self.scope["raw_path"] = full_raw_path + self.scope["query_string"] = parsed_url.query or b"" + + # Handle 503 responses when 'limit_concurrency' is exceeded. + if self.limit_concurrency is not None and ( + len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency + ): + app = service_unavailable + message = "Exceeded concurrency limit." + self.logger.warning(message) + else: + app = self.app + + existing_cycle = self.cycle + self.cycle = RequestResponseCycle( + scope=self.scope, + transport=self.transport, + flow=self.flow, + logger=self.logger, + access_logger=self.access_logger, + access_log=self.access_log, + default_headers=self.server_state.default_headers, + message_event=asyncio.Event(), + expect_100_continue=self.expect_100_continue, + keep_alive=http_version != "1.0", + on_response=self.on_response_complete, + ) + if existing_cycle is None or existing_cycle.response_complete: + # Standard case - start processing the request. + self._start_asgi_task(self.cycle, app) + else: + # Pipelined HTTP requests need to be queued up. + self.flow.pause_reading() + self.pipeline.appendleft((self.cycle, app)) + + def _start_asgi_task(self, cycle: RequestResponseCycle, app: ASGI3Application) -> None: + if self.config.reset_contextvars: + # Opt-in workaround for https://github.com/python/cpython/issues/140947: + # asyncio can leak context vars between tasks. Hides context set in the + # lifespan or by external instrumentation. + if sys.version_info >= (3, 11): # pragma: py-lt-311 + task = self.loop.create_task(cycle.run_asgi(app), context=contextvars.Context()) + else: # pragma: py-gte-311 + task = contextvars.Context().run(self.loop.create_task, cycle.run_asgi(app)) + else: + task = self.loop.create_task(cycle.run_asgi(app)) + task.add_done_callback(self.tasks.discard) + self.tasks.add(task) + + def on_body(self, body: bytes) -> None: + if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete: + return + self.cycle.body += body + if len(self.cycle.body) > HIGH_WATER_LIMIT: + self.flow.pause_reading() + self.cycle.message_event.set() + + def on_message_complete(self) -> None: + if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete: + return + self.cycle.more_body = False + self.cycle.message_event.set() + + def on_response_complete(self) -> None: + # Callback for pipelined HTTP requests to be started. + self.server_state.total_requests += 1 + + if self.transport.is_closing(): + return + + self._unset_keepalive_if_required() + + # Unpause data reads if needed. + self.flow.resume_reading() + + # Unblock any pipelined events. If there are none, arm the + # Keep-Alive timeout instead. + if self.pipeline: + cycle, app = self.pipeline.pop() + self._start_asgi_task(cycle, app) + else: + self.timeout_keep_alive_task = self.loop.call_later( + self.timeout_keep_alive, self.timeout_keep_alive_handler + ) + + def shutdown(self) -> None: + """ + Called by the server to commence a graceful shutdown. + """ + if self.cycle is None or self.cycle.response_complete: + self.transport.close() + else: + self.cycle.keep_alive = False + + def pause_writing(self) -> None: + """ + Called by the transport when the write buffer exceeds the high water mark. + """ + self.flow.pause_writing() # pragma: full coverage + + def resume_writing(self) -> None: + """ + Called by the transport when the write buffer drops below the low water mark. + """ + self.flow.resume_writing() # pragma: full coverage + + def timeout_keep_alive_handler(self) -> None: + """ + Called on a keep-alive connection if no new data is received after a short + delay. + """ + if not self.transport.is_closing(): + self.transport.close() + + +class RequestResponseCycle: + def __init__( + self, + scope: HTTPScope, + transport: asyncio.Transport, + flow: FlowControl, + logger: logging.Logger, + access_logger: logging.Logger, + access_log: bool, + default_headers: list[tuple[bytes, bytes]], + message_event: asyncio.Event, + expect_100_continue: bool, + keep_alive: bool, + on_response: Callable[..., None], + ): + self.scope = scope + self.transport = transport + self.flow = flow + self.logger = logger + self.access_logger = access_logger + self.access_log = access_log + self.default_headers = default_headers + self.message_event = message_event + self.on_response = on_response + + # Connection state + self.disconnected = False + self.keep_alive = keep_alive + self.waiting_for_100_continue = expect_100_continue + + # Request state + self.body = bytearray() + self.more_body = True + + # Response state + self.response_started = False + self.response_complete = False + self.chunked_encoding: bool | None = None + self.expected_content_length = 0 + + # ASGI exception wrapper + async def run_asgi(self, app: ASGI3Application) -> None: + try: + result = await app( # type: ignore[func-returns-value] + self.scope, self.receive, self.send + ) + except BaseException as exc: + msg = "Exception in ASGI application\n" + self.logger.error(msg, exc_info=exc) + if not self.response_started: + await self.send_500_response() + else: + self.transport.close() + else: + if result is not None: + msg = "ASGI callable should return None, but returned '%s'." + self.logger.error(msg, result) + self.transport.close() + elif not self.response_started and not self.disconnected: + msg = "ASGI callable returned without starting response." + self.logger.error(msg) + await self.send_500_response() + elif not self.response_complete and not self.disconnected: + msg = "ASGI callable returned without completing response." + self.logger.error(msg) + self.transport.close() + finally: + self.on_response = lambda: None + + async def send_500_response(self) -> None: + await self.send( + { + "type": "http.response.start", + "status": 500, + "headers": [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"content-length", b"21"), + (b"connection", b"close"), + ], + } + ) + await self.send({"type": "http.response.body", "body": b"Internal Server Error", "more_body": False}) + + # ASGI interface + async def send(self, message: ASGISendEvent) -> None: + if self.flow.write_paused and not self.disconnected: + await self.flow.drain() # pragma: full coverage + + if self.disconnected: + return # pragma: full coverage + + if not self.response_started: + # Sending response status line and headers + if message["type"] != "http.response.start": + raise RuntimeError(f"Expected ASGI message 'http.response.start', but got '{message['type']}'.") + + self.response_started = True + self.waiting_for_100_continue = False + + status_code = message["status"] + headers = self.default_headers + list(message.get("headers", [])) + + if CLOSE_HEADER in self.scope["headers"] and CLOSE_HEADER not in headers: + headers = headers + [CLOSE_HEADER] + + if self.access_log: + self.access_logger.info( + '%s - "%s %s HTTP/%s" %d', + get_client_addr(self.scope), + self.scope["method"], + get_path_with_query_string(self.scope), + self.scope["http_version"], + status_code, + ) + + # Write response status line and headers + content = [STATUS_LINE[status_code]] + + for name, value in headers: + if HEADER_RE.search(name): + raise RuntimeError("Invalid HTTP header name.") # pragma: full coverage + if HEADER_VALUE_RE.search(value): + raise RuntimeError("Invalid HTTP header value.") + + name = name.lower() + if name == b"content-length" and self.chunked_encoding is None: + self.expected_content_length = int(value.decode()) + self.chunked_encoding = False + elif name == b"transfer-encoding" and value.lower() == b"chunked": + self.expected_content_length = 0 + self.chunked_encoding = True + elif name == b"connection" and value.lower() == b"close": + self.keep_alive = False + content.extend([name, b": ", value, b"\r\n"]) + + if self.chunked_encoding is None and self.scope["method"] != "HEAD" and status_code not in (204, 304): + # Neither content-length nor transfer-encoding specified + self.chunked_encoding = True + content.append(b"transfer-encoding: chunked\r\n") + + content.append(b"\r\n") + self.transport.write(b"".join(content)) + + elif not self.response_complete: + # Sending response body + if message["type"] != "http.response.body": + raise RuntimeError(f"Expected ASGI message 'http.response.body', but got '{message['type']}'.") + + body = message.get("body", b"") + more_body = message.get("more_body", False) + + # Write response body + if self.scope["method"] == "HEAD": + self.expected_content_length = 0 + elif self.chunked_encoding: + if body: + content = [b"%x\r\n" % len(body), body, b"\r\n"] + else: + content = [] + if not more_body: + content.append(b"0\r\n\r\n") + self.transport.write(b"".join(content)) + else: + num_bytes = len(body) + if num_bytes > self.expected_content_length: + raise RuntimeError("Response content longer than Content-Length") + else: + self.expected_content_length -= num_bytes + self.transport.write(body) + + # Handle response completion + if not more_body: + if self.expected_content_length != 0: + raise RuntimeError("Response content shorter than Content-Length") + self.response_complete = True + self.message_event.set() + if not self.keep_alive: + self.transport.close() + self.on_response() + + else: + # Response already sent + raise RuntimeError(f"Unexpected ASGI message '{message['type']}' sent, after response already completed.") + + async def receive(self) -> ASGIReceiveEvent: + if self.waiting_for_100_continue and not self.transport.is_closing(): + self.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n") + self.waiting_for_100_continue = False + + if not self.disconnected and not self.response_complete: + self.flow.resume_reading() + await self.message_event.wait() + self.message_event.clear() + + if self.disconnected or self.response_complete: + return {"type": "http.disconnect"} + message: HTTPRequestEvent = {"type": "http.request", "body": bytes(self.body), "more_body": self.more_body} + self.body = bytearray() + return message diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/utils.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..a9a89065c70d7a7905d4b44bc3c385a492cc0d70 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/protocols/utils.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import asyncio +import socket +import urllib.parse + +from uvicorn._types import WWWScope + + +class ClientDisconnected(OSError): ... + + +def get_remote_addr(transport: asyncio.Transport) -> tuple[str, int] | None: + socket_info: socket.socket | None = transport.get_extra_info("socket") + if socket_info is not None: + try: + info = socket_info.getpeername() + return (str(info[0]), int(info[1])) if isinstance(info, tuple) else None + except OSError: # pragma: no cover + # This case appears to inconsistently occur with uvloop + # bound to a unix domain socket. + return None + + info = transport.get_extra_info("peername") + if info is not None and isinstance(info, list | tuple) and len(info) == 2: + return (str(info[0]), int(info[1])) + return None + + +def get_local_addr(transport: asyncio.Transport) -> tuple[str, int | None] | None: + socket_info: socket.socket | None = transport.get_extra_info("socket") + if socket_info is not None: + info = socket_info.getsockname() + if isinstance(info, tuple): + return (str(info[0]), int(info[1])) + if isinstance(info, str): + return (info, None) + return None + info = transport.get_extra_info("sockname") + if info is not None and isinstance(info, list | tuple) and len(info) == 2: + return (str(info[0]), int(info[1])) + if isinstance(info, str): + return (info, None) + return None + + +def is_ssl(transport: asyncio.Transport) -> bool: + return bool(transport.get_extra_info("sslcontext")) + + +def get_client_addr(scope: WWWScope) -> str: + client = scope.get("client") + if not client: + return "" + return "%s:%d" % client + + +def get_path_with_query_string(scope: WWWScope) -> str: + path_with_query_string = urllib.parse.quote(scope["path"]) + if scope["query_string"]: + path_with_query_string = "{}?{}".format(path_with_query_string, scope["query_string"].decode("ascii")) + return path_with_query_string diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__init__.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46e18c7881c2fb55b190a653abe81d9645a7f989 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/auto.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/auto.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..830e295940782a385253024f8dbd9f9b55b172c4 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/auto.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48e2253108c2d2c185d668cf355cd16b18e1eca8 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_sansio_impl.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_sansio_impl.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..168d6f9db051bdde8939781f436d0106bc4e0140 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_sansio_impl.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/wsproto_impl.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/wsproto_impl.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bed60f6d807917d90b595d6d72ed7a26492f2d5 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/__pycache__/wsproto_impl.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/auto.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/auto.py new file mode 100644 index 0000000000000000000000000000000000000000..7180ad39788c6fb14e15ae3fda379aba01c332f8 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/auto.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable + +AutoWebSocketsProtocol: Callable[..., asyncio.Protocol] | None +try: + import websockets # noqa +except ImportError: # pragma: no cover + try: + import wsproto # noqa + except ImportError: + AutoWebSocketsProtocol = None + else: + from uvicorn.protocols.websockets.wsproto_impl import WSProtocol + + AutoWebSocketsProtocol = WSProtocol +else: + from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol + + AutoWebSocketsProtocol = WebSocketProtocol diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/websockets_impl.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/websockets_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..e4139391a2d199e2f2d1f67c232693774875f57a --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/websockets_impl.py @@ -0,0 +1,372 @@ +from __future__ import annotations + +import asyncio +import http +import logging +from collections.abc import Sequence +from typing import Any, Literal, cast +from urllib.parse import unquote + +import websockets +import websockets.legacy.handshake +from websockets.datastructures import Headers +from websockets.exceptions import ConnectionClosed +from websockets.extensions.base import ServerExtensionFactory +from websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory +from websockets.legacy.server import HTTPResponse +from websockets.server import WebSocketServerProtocol +from websockets.typing import Subprotocol + +from uvicorn._types import ( + ASGI3Application, + ASGISendEvent, + WebSocketConnectEvent, + WebSocketDisconnectEvent, + WebSocketReceiveEvent, + WebSocketScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.utils import ( + ClientDisconnected, + get_client_addr, + get_local_addr, + get_path_with_query_string, + get_remote_addr, + is_ssl, +) +from uvicorn.server import ServerState + + +class Server: + closing = False + + def register(self, ws: WebSocketServerProtocol) -> None: + pass + + def unregister(self, ws: WebSocketServerProtocol) -> None: + pass + + def is_serving(self) -> bool: + return not self.closing + + +class WebSocketProtocol(WebSocketServerProtocol): + extra_headers: list[tuple[str, str]] + logger: logging.Logger | logging.LoggerAdapter[Any] + + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ): + if not config.loaded: + config.load() + + self.config = config + self.app = cast(ASGI3Application, config.loaded_app) + self.loop = _loop or asyncio.get_event_loop() + self.root_path = config.root_path + self.app_state = app_state + + # Shared server state + self.connections = server_state.connections + self.tasks = server_state.tasks + + # Connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["wss", "ws"] = None # type: ignore[assignment] + + # Connection events + self.scope: WebSocketScope + self.handshake_started_event = asyncio.Event() + self.handshake_completed_event = asyncio.Event() + self.closed_event = asyncio.Event() + self.initial_response: HTTPResponse | None = None + self.connect_sent = False + self.lost_connection_before_handshake = False + self.accepted_subprotocol: Subprotocol | None = None + + self.ws_server: Server = Server() # type: ignore[assignment] + + extensions: list[ServerExtensionFactory] = [] + if self.config.ws_per_message_deflate: + extensions.append(ServerPerMessageDeflateFactory()) + + super().__init__( + ws_handler=self.ws_handler, + ws_server=self.ws_server, # type: ignore[arg-type] + max_size=self.config.ws_max_size, + max_queue=self.config.ws_max_queue, + ping_interval=self.config.ws_ping_interval, + ping_timeout=self.config.ws_ping_timeout, + extensions=extensions, + logger=logging.getLogger("uvicorn.error"), + ) + self.server_header = None + self.extra_headers = [ + (name.decode("latin-1"), value.decode("latin-1")) for name, value in server_state.default_headers + ] + + def connection_made( # type: ignore[override] + self, transport: asyncio.Transport + ) -> None: + self.connections.add(self) + self.transport = transport + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "wss" if is_ssl(transport) else "ws" + + if self.logger.isEnabledFor(TRACE_LOG_LEVEL): + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection made", prefix) + + super().connection_made(transport) + + def connection_lost(self, exc: Exception | None) -> None: + self.connections.remove(self) + + if self.logger.isEnabledFor(TRACE_LOG_LEVEL): + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix) + + self.lost_connection_before_handshake = not self.handshake_completed_event.is_set() + self.handshake_completed_event.set() + super().connection_lost(exc) + if exc is None: + self.transport.close() + + def shutdown(self) -> None: + self.ws_server.closing = True + if self.handshake_completed_event.is_set(): + self.fail_connection(1012) + else: + self.send_500_response() + self.transport.close() + + def on_task_complete(self, task: asyncio.Task[None]) -> None: + self.tasks.discard(task) + + async def process_request(self, path: str, request_headers: Headers) -> HTTPResponse | None: + """ + This hook is called to determine if the websocket should return + an HTTP response and close. + + Our behavior here is to start the ASGI application, and then wait + for either `accept` or `close` in order to determine if we should + close the connection. + """ + path_portion, _, query_string = path.partition("?") + + websockets.legacy.handshake.check_request(request_headers) + + subprotocols: list[str] = [] + for header in request_headers.get_all("Sec-WebSocket-Protocol"): + subprotocols.extend([token.strip() for token in header.split(",")]) + + asgi_headers = [ + (name.encode("ascii"), value.encode("ascii", errors="surrogateescape")) + for name, value in request_headers.raw_items() + ] + path = unquote(path_portion) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + path_portion.encode("ascii") + + self.scope = { + "type": "websocket", + "asgi": {"version": self.config.asgi_version, "spec_version": "2.4"}, + "http_version": "1.1", + "scheme": self.scheme, + "server": self.server, + "client": self.client, + "root_path": self.root_path, + "path": full_path, + "raw_path": full_raw_path, + "query_string": query_string.encode("ascii"), + "headers": asgi_headers, + "subprotocols": subprotocols, + "state": self.app_state.copy(), + "extensions": {"websocket.http.response": {}}, + } + task = self.loop.create_task(self.run_asgi()) + task.add_done_callback(self.on_task_complete) + self.tasks.add(task) + await self.handshake_started_event.wait() + return self.initial_response + + def process_subprotocol( + self, headers: Headers, available_subprotocols: Sequence[Subprotocol] | None + ) -> Subprotocol | None: + """ + We override the standard 'process_subprotocol' behavior here so that + we return whatever subprotocol is sent in the 'accept' message. + """ + return self.accepted_subprotocol + + def send_500_response(self) -> None: + msg = b"Internal Server Error" + content = [ + b"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=utf-8\r\n", + b"content-length: " + str(len(msg)).encode("ascii") + b"\r\n", + b"connection: close\r\n", + b"\r\n", + msg, + ] + self.transport.write(b"".join(content)) + # Allow handler task to terminate cleanly, as websockets doesn't cancel it by + # itself (see https://github.com/Kludex/uvicorn/issues/920) + self.handshake_started_event.set() + + async def ws_handler(self, protocol: WebSocketServerProtocol, path: str) -> Any: # type: ignore[override] + """ + This is the main handler function for the 'websockets' implementation + to call into. We just wait for close then return, and instead allow + 'send' and 'receive' events to drive the flow. + """ + self.handshake_completed_event.set() + await self.wait_closed() + + async def run_asgi(self) -> None: + """ + Wrapper around the ASGI callable, handling exceptions and unexpected + termination states. + """ + try: + result = await self.app(self.scope, self.asgi_receive, self.asgi_send) # type: ignore[func-returns-value] + except ClientDisconnected: # pragma: full coverage + self.closed_event.set() + except BaseException: + self.closed_event.set() + self.logger.exception("Exception in ASGI application\n") + if not self.handshake_started_event.is_set(): + self.send_500_response() + else: + await self.handshake_completed_event.wait() + else: + self.closed_event.set() + if not self.handshake_started_event.is_set(): + self.logger.error("ASGI callable returned without sending handshake.") + self.send_500_response() + elif result is not None: + self.logger.error("ASGI callable should return None, but returned '%s'.", result) + await self.handshake_completed_event.wait() + self.transport.close() + + async def asgi_send(self, message: ASGISendEvent) -> None: + if not self.handshake_started_event.is_set(): + if message["type"] == "websocket.accept": + self.logger.info( + '%s - "WebSocket %s" [accepted]', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + self.initial_response = None + self.accepted_subprotocol = cast(Subprotocol | None, message.get("subprotocol")) + if "headers" in message: + self.extra_headers.extend( + # ASGI spec requires bytes + # But for compatibility we need to convert it to strings + (name.decode("latin-1"), value.decode("latin-1")) + for name, value in message["headers"] + ) + self.handshake_started_event.set() + + elif message["type"] == "websocket.close": + self.logger.info( + '%s - "WebSocket %s" 403', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + self.initial_response = (http.HTTPStatus.FORBIDDEN, [], b"") + self.handshake_started_event.set() + self.closed_event.set() + + elif message["type"] == "websocket.http.response.start": + self.logger.info( + '%s - "WebSocket %s" %d', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + message["status"], + ) + # websockets requires the status to be an enum. look it up. + status = http.HTTPStatus(message["status"]) + headers = [ + (name.decode("latin-1"), value.decode("latin-1")) for name, value in message.get("headers", []) + ] + self.initial_response = (status, headers, b"") + self.handshake_started_event.set() + + else: + raise RuntimeError( + "Expected ASGI message 'websocket.accept', 'websocket.close', " + f"or 'websocket.http.response.start' but got '{message['type']}'." + ) + + elif not self.closed_event.is_set() and self.initial_response is None: + await self.handshake_completed_event.wait() + + try: + if message["type"] == "websocket.send": + bytes_data = message.get("bytes") + text_data = message.get("text") + data = text_data if bytes_data is None else bytes_data + await self.send(data) # type: ignore[arg-type] + + elif message["type"] == "websocket.close": + code = message.get("code", 1000) + reason = message.get("reason", "") or "" + await self.close(code, reason) + self.closed_event.set() + + else: + raise RuntimeError( + f"Expected ASGI message 'websocket.send' or 'websocket.close', but got '{message['type']}'." + ) + except ConnectionClosed as exc: + raise ClientDisconnected from exc + + elif self.initial_response is not None: + if message["type"] == "websocket.http.response.body": + body = self.initial_response[2] + message["body"] + self.initial_response = self.initial_response[:2] + (body,) + if not message.get("more_body", False): + self.closed_event.set() + else: + raise RuntimeError(f"Expected ASGI message 'websocket.http.response.body' but got '{message['type']}'.") + + else: + raise RuntimeError( + f"Unexpected ASGI message '{message['type']}', after sending 'websocket.close' " + "or response already completed." + ) + + async def asgi_receive(self) -> WebSocketDisconnectEvent | WebSocketConnectEvent | WebSocketReceiveEvent: + if not self.connect_sent: + self.connect_sent = True + return {"type": "websocket.connect"} + + await self.handshake_completed_event.wait() + + if self.lost_connection_before_handshake: + # If the handshake failed or the app closed before handshake completion, + # use 1006 Abnormal Closure. + return {"type": "websocket.disconnect", "code": 1006} + + if self.closed_event.is_set(): + return {"type": "websocket.disconnect", "code": 1005} + + try: + data = await self.recv() + except ConnectionClosed: + self.closed_event.set() + if self.ws_server.closing: + return {"type": "websocket.disconnect", "code": 1012} + return {"type": "websocket.disconnect", "code": self.close_code or 1005, "reason": self.close_reason} + + if isinstance(data, str): + return {"type": "websocket.receive", "text": data} + return {"type": "websocket.receive", "bytes": data} diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/websockets_sansio_impl.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/websockets_sansio_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..036b6ee01e801a04c83b57d9577e72ec6905922e --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/websockets_sansio_impl.py @@ -0,0 +1,477 @@ +from __future__ import annotations + +import asyncio +import logging +import random +import struct +import sys +from asyncio import TimerHandle +from asyncio.transports import BaseTransport, Transport +from http import HTTPStatus +from typing import Any, Literal, cast +from urllib.parse import unquote + +from websockets.exceptions import InvalidState +from websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory +from websockets.frames import Frame, Opcode +from websockets.http11 import Request +from websockets.server import ServerProtocol + +from uvicorn._types import ( + ASGIReceiveEvent, + ASGISendEvent, + WebSocketScope, +) +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.utils import ( + ClientDisconnected, + get_client_addr, + get_local_addr, + get_path_with_query_string, + get_remote_addr, + is_ssl, +) +from uvicorn.server import ServerState + +if sys.version_info >= (3, 11): # pragma: no cover + from typing import assert_never +else: # pragma: no cover + from typing_extensions import assert_never + + +class WebSocketsSansIOProtocol(asyncio.Protocol): + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() # pragma: no cover + + self.config = config + self.app = config.loaded_app + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.root_path = config.root_path + self.app_state = app_state + + # Shared server state + self.connections = server_state.connections + self.tasks = server_state.tasks + self.default_headers = server_state.default_headers + + # Connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["wss", "ws"] = None # type: ignore[assignment] + + # WebSocket state + self.queue: asyncio.Queue[ASGIReceiveEvent] = asyncio.Queue() + self.handshake_initiated = False + self.handshake_complete = False + self.close_sent = False + self.initial_response: tuple[int, list[tuple[str, str]], bytes] | None = None + + extensions = [] + if self.config.ws_per_message_deflate: + extensions = [ + ServerPerMessageDeflateFactory( + server_max_window_bits=12, + client_max_window_bits=12, + compress_settings={"memLevel": 5}, + ) + ] + self.conn = ServerProtocol( + extensions=extensions, + max_size=self.config.ws_max_size, + logger=logging.getLogger("uvicorn.error"), + ) + + self.read_paused = False + self.writable = asyncio.Event() + self.writable.set() + + # Keepalive state + self.ping_interval = config.ws_ping_interval + self.ping_timeout = config.ws_ping_timeout + self.ping_timer: TimerHandle | None = None + self.pong_timer: TimerHandle | None = None + self.pending_ping_payload: bytes | None = None + self.ping_sent_at: float = 0.0 + self.last_ping_rtt: float = 0.0 + + # Buffers + self.bytes = bytearray() + + def connection_made(self, transport: BaseTransport) -> None: + """Called when a connection is made.""" + transport = cast(Transport, transport) + self.connections.add(self) + self.transport = transport + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "wss" if is_ssl(transport) else "ws" + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.stop_keepalive() + code = 1005 if self.handshake_complete else 1006 + self.queue.put_nowait({"type": "websocket.disconnect", "code": code}) + self.connections.remove(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix) + + self.handshake_complete = True + if exc is None: + self.transport.close() + + def eof_received(self) -> None: + pass + + def shutdown(self) -> None: + self.stop_keepalive() + if self.handshake_complete: + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1012}) + self.conn.send_close(1012) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + else: + self.send_500_response() + self.transport.close() + + def data_received(self, data: bytes) -> None: + self.conn.receive_data(data) + if self.conn.parser_exc is not None: # pragma: no cover + self.handle_parser_exception() + return + self.handle_events() + + def handle_events(self) -> None: + for event in self.conn.events_received(): + if isinstance(event, Request): + self.handle_connect(event) + if isinstance(event, Frame): + if event.opcode == Opcode.CONT: + self.handle_cont(event) # pragma: no cover + elif event.opcode == Opcode.TEXT: + self.handle_text(event) + elif event.opcode == Opcode.BINARY: + self.handle_bytes(event) + elif event.opcode == Opcode.PING: + self.handle_ping() + elif event.opcode == Opcode.PONG: + self.handle_pong(event) + elif event.opcode == Opcode.CLOSE: + self.handle_close(event) + else: + assert_never(event.opcode) # pragma: no cover + + # Event handlers + + def handle_connect(self, event: Request) -> None: + self.request = event + self.response = self.conn.accept(event) + self.handshake_initiated = True + if self.response.status_code != 101: + self.handshake_complete = True + self.close_sent = True + self.conn.send_response(self.response) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.transport.close() + return + + headers = [ + (key.encode("ascii"), value.encode("ascii", errors="surrogateescape")) + for key, value in event.headers.raw_items() + ] + raw_path, _, query_string = event.path.partition("?") + self.scope: WebSocketScope = { + "type": "websocket", + "asgi": {"version": self.config.asgi_version, "spec_version": "2.4"}, + "http_version": "1.1", + "scheme": self.scheme, + "server": self.server, + "client": self.client, + "root_path": self.root_path, + "path": self.root_path + unquote(raw_path), + "raw_path": self.root_path.encode("ascii") + raw_path.encode("ascii"), + "query_string": query_string.encode("ascii"), + "headers": headers, + "subprotocols": event.headers.get_all("Sec-WebSocket-Protocol"), + "state": self.app_state.copy(), + "extensions": {"websocket.http.response": {}}, + } + self.queue.put_nowait({"type": "websocket.connect"}) + task = self.loop.create_task(self.run_asgi()) + task.add_done_callback(self.on_task_complete) + self.tasks.add(task) + + def handle_cont(self, event: Frame) -> None: + self.bytes.extend(event.data) + if event.fin: + self.send_receive_event_to_app() + + def handle_text(self, event: Frame) -> None: + self.bytes = bytearray(event.data) + self.curr_msg_data_type: Literal["text", "bytes"] = "text" + if event.fin: + self.send_receive_event_to_app() + + def handle_bytes(self, event: Frame) -> None: + self.bytes = bytearray(event.data) + self.curr_msg_data_type = "bytes" + if event.fin: + self.send_receive_event_to_app() + + def send_receive_event_to_app(self) -> None: + if self.curr_msg_data_type == "text": + try: + self.queue.put_nowait({"type": "websocket.receive", "text": self.bytes.decode()}) + except UnicodeDecodeError: # pragma: no cover + self.logger.exception("Invalid UTF-8 sequence received from client.") + self.conn.send_close(1007) + self.handle_parser_exception() + return + else: + self.queue.put_nowait({"type": "websocket.receive", "bytes": bytes(self.bytes)}) + if not self.read_paused: + self.read_paused = True + self.transport.pause_reading() + + def handle_ping(self) -> None: + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + + def handle_pong(self, event: Frame) -> None: + # Ignore unsolicited pongs and stale pongs whose payload doesn't match the ping currently in flight + if self.pending_ping_payload is None or bytes(event.data) != self.pending_ping_payload: + return # pragma: no cover + + self.last_ping_rtt = self.loop.time() - self.ping_sent_at + self.pending_ping_payload = None + # The peer answered in time; cancel the pong deadline and chain the next ping. This `schedule_ping()` call is + # what keeps the keepalive loop running when ping_timeout is set. When ping_timeout is None the next ping is + # already scheduled by `send_keepalive_ping`, so we must not schedule a duplicate here. + if self.pong_timer is not None: + self.pong_timer.cancel() + self.pong_timer = None + self.schedule_ping() + + def start_keepalive(self) -> None: + if self.ping_interval is not None and self.ping_interval > 0: + self.schedule_ping() + + def stop_keepalive(self) -> None: + if self.ping_timer is not None: + self.ping_timer.cancel() + self.ping_timer = None + if self.pong_timer is not None: # pragma: no cover + self.pong_timer.cancel() + self.pong_timer = None + self.pending_ping_payload = None + + def schedule_ping(self) -> None: + assert self.ping_interval is not None + delay = max(0.0, self.ping_interval - self.last_ping_rtt) + self.ping_timer = self.loop.call_later(delay, self.send_keepalive_ping) + + def send_keepalive_ping(self) -> None: + self.ping_timer = None + if self.close_sent or self.transport.is_closing(): # pragma: no cover + return + # Random 4-byte payload identifies this ping; `handle_pong` uses it to ignore stale or unsolicited pongs. + # See https://github.com/python-websockets/websockets/blob/4d229bf9f583d593aa103287aee0a77c9fbc3a79/src/websockets/asyncio/connection.py#L624 + self.pending_ping_payload = struct.pack("!I", random.getrandbits(32)) + self.ping_sent_at = self.loop.time() + self.conn.send_ping(self.pending_ping_payload) + self.transport.write(b"".join(self.conn.data_to_send())) + if self.ping_timeout is not None: + self.pong_timer = self.loop.call_later(self.ping_timeout, self.keepalive_timeout) + else: # pragma: no cover + self.schedule_ping() + + def keepalive_timeout(self) -> None: + self.pong_timer = None + self.pending_ping_payload = None + if self.close_sent or self.transport.is_closing(): # pragma: no cover + return + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket keepalive ping timeout", prefix) + self.conn.fail(1011, "keepalive ping timeout") + self.transport.write(b"".join(self.conn.data_to_send())) + self.close_sent = True + self.transport.close() + + def handle_close(self, event: Frame) -> None: + if not self.close_sent and not self.transport.is_closing(): + assert self.conn.close_rcvd is not None + code = self.conn.close_rcvd.code + reason = self.conn.close_rcvd.reason + self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason}) + + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.transport.close() + + def handle_parser_exception(self) -> None: # pragma: no cover + assert self.conn.close_sent is not None + code = self.conn.close_sent.code + reason = self.conn.close_sent.reason + self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason}) + + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.close_sent = True + self.transport.close() + + def on_task_complete(self, task: asyncio.Task[None]) -> None: + self.tasks.discard(task) + + async def run_asgi(self) -> None: + try: + result = await self.app(self.scope, self.receive, self.send) + except ClientDisconnected: + pass # pragma: full coverage + except BaseException: + self.logger.exception("Exception in ASGI application\n") + self.send_500_response() + else: + if not self.handshake_complete: + self.logger.error("ASGI callable returned without completing handshake.") + self.send_500_response() + elif result is not None: + self.logger.error("ASGI callable should return None, but returned '%s'.", result) + self.transport.close() + + def send_500_response(self) -> None: + if self.initial_response or self.handshake_complete: + return + response = self.conn.reject(500, "Internal Server Error") + self.conn.send_response(response) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + + async def send(self, message: ASGISendEvent) -> None: + await self.writable.wait() + + if not self.handshake_complete and self.initial_response is None: + if message["type"] == "websocket.accept": + self.logger.info( + '%s - "WebSocket %s" [accepted]', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + headers = [ + (name.decode("latin-1").lower(), value.decode("latin-1")) + for name, value in (self.default_headers + list(message.get("headers", []))) + ] + accepted_subprotocol = message.get("subprotocol") + if accepted_subprotocol: + headers.append(("Sec-WebSocket-Protocol", accepted_subprotocol)) + self.response.headers.update(headers) + + if not self.transport.is_closing(): + self.handshake_complete = True + self.conn.send_response(self.response) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.start_keepalive() + + elif message["type"] == "websocket.close": + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006}) + self.logger.info( + '%s - "WebSocket %s" 403', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + response = self.conn.reject(HTTPStatus.FORBIDDEN, "") + self.conn.send_response(response) + output = self.conn.data_to_send() + self.close_sent = True + self.handshake_complete = True + self.transport.write(b"".join(output)) + self.transport.close() + elif message["type"] == "websocket.http.response.start" and self.initial_response is None: + if not (100 <= message["status"] < 600): + raise RuntimeError("Invalid HTTP status code '%d' in response." % message["status"]) + self.logger.info( + '%s - "WebSocket %s" %d', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + message["status"], + ) + headers = [ + (name.decode("latin-1"), value.decode("latin-1")) + for name, value in list(message.get("headers", [])) + ] + self.initial_response = (message["status"], headers, b"") + else: + raise RuntimeError( + "Expected ASGI message 'websocket.accept', 'websocket.close' " + f"or 'websocket.http.response.start' but got '{message['type']}'." + ) + + elif not self.close_sent and self.initial_response is None: + try: + if message["type"] == "websocket.send": + bytes_data = message.get("bytes") + text_data = message.get("text") + if bytes_data is not None: + self.conn.send_binary(bytes_data) + elif text_data is not None: + self.conn.send_text(text_data.encode()) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + + elif message["type"] == "websocket.close": + if not self.transport.is_closing(): + code = message.get("code", 1000) + reason = message.get("reason", "") or "" + self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason}) + self.conn.send_close(code, reason) + output = self.conn.data_to_send() + self.transport.write(b"".join(output)) + self.close_sent = True + self.transport.close() + else: + raise RuntimeError( + f"Expected ASGI message 'websocket.send' or 'websocket.close', but got '{message['type']}'." + ) + except InvalidState: + raise ClientDisconnected() + elif self.initial_response is not None: + if message["type"] == "websocket.http.response.body": + body = self.initial_response[2] + message["body"] + self.initial_response = self.initial_response[:2] + (body,) + if not message.get("more_body", False): + response = self.conn.reject(self.initial_response[0], body.decode()) + response.headers.update(self.initial_response[1]) + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006}) + self.conn.send_response(response) + output = self.conn.data_to_send() + self.close_sent = True + self.transport.write(b"".join(output)) + self.transport.close() + else: # pragma: no cover + raise RuntimeError(f"Expected ASGI message 'websocket.http.response.body' but got '{message['type']}'.") + + else: + raise RuntimeError(f"Unexpected ASGI message '{message['type']}', after sending 'websocket.close'.") + + async def receive(self) -> ASGIReceiveEvent: + message = await self.queue.get() + if self.read_paused and self.queue.empty(): + self.read_paused = False + self.transport.resume_reading() + return message diff --git a/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/wsproto_impl.py b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/wsproto_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..adca63f9df595983d4f9d43d395a8b5042269110 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/protocols/websockets/wsproto_impl.py @@ -0,0 +1,458 @@ +from __future__ import annotations + +import asyncio +import logging +import random +import struct +from asyncio import TimerHandle +from io import BytesIO, StringIO +from typing import Any, Literal, cast +from urllib.parse import unquote + +import wsproto +from wsproto import ConnectionType, events +from wsproto.connection import ConnectionState +from wsproto.extensions import Extension, PerMessageDeflate +from wsproto.utilities import LocalProtocolError, RemoteProtocolError + +from uvicorn._types import ASGI3Application, ASGISendEvent, WebSocketEvent, WebSocketReceiveEvent, WebSocketScope +from uvicorn.config import Config +from uvicorn.logging import TRACE_LOG_LEVEL +from uvicorn.protocols.utils import ( + ClientDisconnected, + get_client_addr, + get_local_addr, + get_path_with_query_string, + get_remote_addr, + is_ssl, +) +from uvicorn.server import ServerState + + +class FrameTooLargeError(Exception): + """Raised when accumulated websocket message bytes exceed `ws_max_size`.""" + + +class WebsocketBuffer: + def __init__(self, max_length: int) -> None: + self.value: BytesIO | StringIO | None = None + self.length = 0 + self.max_length = max_length + + def extend(self, event: events.TextMessage | events.BytesMessage) -> None: + if self.value is None: + self.value = StringIO() if isinstance(event, events.TextMessage) else BytesIO() + self.value.write(event.data) # type: ignore[arg-type] + # `ws_max_size` is a byte budget, so count UTF-8 bytes for text. + self.length += len(event.data.encode()) if isinstance(event, events.TextMessage) else len(event.data) + if self.length > self.max_length: + raise FrameTooLargeError + + def clear(self) -> None: + self.value = None + self.length = 0 + + def to_message(self) -> WebSocketReceiveEvent: + if isinstance(self.value, StringIO): + return {"type": "websocket.receive", "text": self.value.getvalue()} + assert isinstance(self.value, BytesIO) + return {"type": "websocket.receive", "bytes": self.value.getvalue()} + + +class WSProtocol(asyncio.Protocol): + def __init__( + self, + config: Config, + server_state: ServerState, + app_state: dict[str, Any], + _loop: asyncio.AbstractEventLoop | None = None, + ) -> None: + if not config.loaded: + config.load() # pragma: full coverage + + self.config = config + self.app = cast(ASGI3Application, config.loaded_app) + self.loop = _loop or asyncio.get_event_loop() + self.logger = logging.getLogger("uvicorn.error") + self.root_path = config.root_path + self.app_state = app_state + + # Shared server state + self.connections = server_state.connections + self.tasks = server_state.tasks + self.default_headers = server_state.default_headers + + # Connection state + self.transport: asyncio.Transport = None # type: ignore[assignment] + self.server: tuple[str, int | None] | None = None + self.client: tuple[str, int] | None = None + self.scheme: Literal["wss", "ws"] = None # type: ignore[assignment] + + # WebSocket state + self.queue: asyncio.Queue[WebSocketEvent] = asyncio.Queue() + self.handshake_complete = False + self.close_sent = False + + # Rejection state + self.response_started = False + + self.conn = wsproto.WSConnection(connection_type=ConnectionType.SERVER) + + self.read_paused = False + self.writable = asyncio.Event() + self.writable.set() + + # Keepalive state + self.ping_interval = config.ws_ping_interval + self.ping_timeout = config.ws_ping_timeout + self.ping_timer: TimerHandle | None = None + self.pong_timer: TimerHandle | None = None + self.pending_ping_payload: bytes | None = None + self.ping_sent_at: float = 0.0 + self.last_ping_rtt: float = 0.0 + + # Buffer + self.buffer = WebsocketBuffer(self.config.ws_max_size) + + # Protocol interface + + def connection_made(self, transport: asyncio.Transport) -> None: # type: ignore[override] + self.connections.add(self) + self.transport = transport + self.server = get_local_addr(transport) + self.client = get_remote_addr(transport) + self.scheme = "wss" if is_ssl(transport) else "ws" + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection made", prefix) + + def connection_lost(self, exc: Exception | None) -> None: + self.stop_keepalive() + code = 1005 if self.handshake_complete else 1006 + self.queue.put_nowait({"type": "websocket.disconnect", "code": code}) + self.connections.remove(self) + + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix) + + self.handshake_complete = True + if exc is None: + self.transport.close() + + def eof_received(self) -> None: + pass + + def data_received(self, data: bytes) -> None: + try: + self.conn.receive_data(data) + except RemoteProtocolError as err: + # TODO: Remove `type: ignore` when wsproto fixes the type annotation. + self.transport.write(self.conn.send(err.event_hint)) # type: ignore[arg-type] # noqa: E501 + self.transport.close() + else: + self.handle_events() + + def handle_events(self) -> None: + for event in self.conn.events(): + if self.close_sent: + return + if isinstance(event, events.Request): + self.handle_connect(event) + elif isinstance(event, (events.TextMessage, events.BytesMessage)): + self.handle_message(event) + elif isinstance(event, events.CloseConnection): + self.handle_close(event) + elif isinstance(event, events.Ping): + self.handle_ping(event) + elif isinstance(event, events.Pong): + self.handle_pong(event) + + def pause_writing(self) -> None: + """ + Called by the transport when the write buffer exceeds the high water mark. + """ + self.writable.clear() # pragma: full coverage + + def resume_writing(self) -> None: + """ + Called by the transport when the write buffer drops below the low water mark. + """ + self.writable.set() # pragma: full coverage + + def shutdown(self) -> None: + self.stop_keepalive() + if self.handshake_complete: + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1012}) + output = self.conn.send(wsproto.events.CloseConnection(code=1012)) + self.transport.write(output) + else: + self.send_500_response() + self.transport.close() + + def on_task_complete(self, task: asyncio.Task[None]) -> None: + self.tasks.discard(task) + + # Event handlers + + def handle_connect(self, event: events.Request) -> None: + headers = [(b"host", event.host.encode())] + headers += [(key.lower(), value) for key, value in event.extra_headers] + raw_path, _, query_string = event.target.partition("?") + path = unquote(raw_path) + full_path = self.root_path + path + full_raw_path = self.root_path.encode("ascii") + raw_path.encode("ascii") + self.scope: WebSocketScope = { + "type": "websocket", + "asgi": {"version": self.config.asgi_version, "spec_version": "2.4"}, + "http_version": "1.1", + "scheme": self.scheme, + "server": self.server, + "client": self.client, + "root_path": self.root_path, + "path": full_path, + "raw_path": full_raw_path, + "query_string": query_string.encode("ascii"), + "headers": headers, + "subprotocols": event.subprotocols, + "state": self.app_state.copy(), + "extensions": {"websocket.http.response": {}}, + } + self.queue.put_nowait({"type": "websocket.connect"}) + task = self.loop.create_task(self.run_asgi()) + task.add_done_callback(self.on_task_complete) + self.tasks.add(task) + + def handle_message(self, event: events.TextMessage | events.BytesMessage) -> None: + try: + self.buffer.extend(event) + except FrameTooLargeError: + self.close_sent = True + reason = f"Message exceeds the maximum size ({self.config.ws_max_size} bytes)" + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1009, "reason": reason}) + if not self.transport.is_closing(): + self.transport.write(self.conn.send(wsproto.events.CloseConnection(code=1009, reason=reason))) + self.transport.close() + return + if event.message_finished: + self.queue.put_nowait(self.buffer.to_message()) + self.buffer.clear() + if not self.read_paused: + self.read_paused = True + self.transport.pause_reading() + + def handle_close(self, event: events.CloseConnection) -> None: + if self.conn.state == ConnectionState.REMOTE_CLOSING: + self.transport.write(self.conn.send(event.response())) + self.queue.put_nowait({"type": "websocket.disconnect", "code": event.code, "reason": event.reason}) + self.transport.close() + + def handle_ping(self, event: events.Ping) -> None: + self.transport.write(self.conn.send(event.response())) + + def handle_pong(self, event: events.Pong) -> None: + # Ignore unsolicited pongs and stale pongs whose payload doesn't match the ping currently in flight. + if self.pending_ping_payload is None or bytes(event.payload) != self.pending_ping_payload: + return # pragma: no cover + + self.last_ping_rtt = self.loop.time() - self.ping_sent_at + self.pending_ping_payload = None + # The peer answered in time; cancel the pong deadline and chain the next ping. This `schedule_ping()` call is + # what keeps the keepalive loop running when ping_timeout is set. When ping_timeout is None the next ping is + # already scheduled by `send_keepalive_ping`, so we must not schedule a duplicate here. + if self.pong_timer is not None: + self.pong_timer.cancel() + self.pong_timer = None + self.schedule_ping() + + def start_keepalive(self) -> None: + if self.ping_interval is not None and self.ping_interval > 0: + self.schedule_ping() + + def stop_keepalive(self) -> None: + if self.ping_timer is not None: + self.ping_timer.cancel() + self.ping_timer = None + if self.pong_timer is not None: # pragma: no cover + self.pong_timer.cancel() + self.pong_timer = None + self.pending_ping_payload = None + + def schedule_ping(self) -> None: + assert self.ping_interval is not None + delay = max(0.0, self.ping_interval - self.last_ping_rtt) + self.ping_timer = self.loop.call_later(delay, self.send_keepalive_ping) + + def send_keepalive_ping(self) -> None: + self.ping_timer = None + if self.close_sent or self.transport.is_closing(): # pragma: no cover + return + # Random 4-byte payload identifies this ping; `handle_pong` uses it to ignore stale or unsolicited pongs. + self.pending_ping_payload = struct.pack("!I", random.getrandbits(32)) + self.ping_sent_at = self.loop.time() + self.transport.write(self.conn.send(wsproto.events.Ping(payload=self.pending_ping_payload))) + if self.ping_timeout is not None: + self.pong_timer = self.loop.call_later(self.ping_timeout, self.keepalive_timeout) + else: # pragma: no cover + self.schedule_ping() + + def keepalive_timeout(self) -> None: + self.pong_timer = None + self.pending_ping_payload = None + if self.close_sent or self.transport.is_closing(): # pragma: no cover + return + if self.logger.level <= TRACE_LOG_LEVEL: + prefix = "%s:%d - " % self.client if self.client else "" + self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket keepalive ping timeout", prefix) + reason = "keepalive ping timeout" + self.transport.write(self.conn.send(wsproto.events.CloseConnection(code=1011, reason=reason))) + self.close_sent = True + self.transport.close() + + def send_500_response(self) -> None: + if self.response_started or self.handshake_complete: + return # we cannot send responses anymore + headers: list[tuple[bytes, bytes]] = [ + (b"content-type", b"text/plain; charset=utf-8"), + (b"connection", b"close"), + (b"content-length", b"21"), + ] + output = self.conn.send(wsproto.events.RejectConnection(status_code=500, headers=headers, has_body=True)) + output += self.conn.send(wsproto.events.RejectData(data=b"Internal Server Error")) + self.transport.write(output) + + async def run_asgi(self) -> None: + try: + result = await self.app(self.scope, self.receive, self.send) # type: ignore[func-returns-value] + except ClientDisconnected: + pass # pragma: full coverage + except BaseException: + self.logger.exception("Exception in ASGI application\n") + self.send_500_response() + else: + if not self.handshake_complete: + self.logger.error("ASGI callable returned without completing handshake.") + self.send_500_response() + elif result is not None: + self.logger.error("ASGI callable should return None, but returned '%s'.", result) + self.transport.close() + + async def send(self, message: ASGISendEvent) -> None: + await self.writable.wait() + + if not self.handshake_complete: + if message["type"] == "websocket.accept": + self.logger.info( + '%s - "WebSocket %s" [accepted]', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + subprotocol = message.get("subprotocol") + extra_headers = self.default_headers + list(message.get("headers", [])) + extensions: list[Extension] = [] + if self.config.ws_per_message_deflate: + extensions.append(PerMessageDeflate()) + if not self.transport.is_closing(): + self.handshake_complete = True + output = self.conn.send( + wsproto.events.AcceptConnection( + subprotocol=subprotocol, + extensions=extensions, + extra_headers=extra_headers, + ) + ) + self.transport.write(output) + self.start_keepalive() + + elif message["type"] == "websocket.close": + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006}) + self.logger.info( + '%s - "WebSocket %s" 403', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + ) + self.handshake_complete = True + self.close_sent = True + event = events.RejectConnection(status_code=403, headers=[]) + output = self.conn.send(event) + self.transport.write(output) + self.transport.close() + + elif message["type"] == "websocket.http.response.start": + # ensure status code is in the valid range + if not (100 <= message["status"] < 600): + msg = "Invalid HTTP status code '%d' in response." + raise RuntimeError(msg % message["status"]) + self.logger.info( + '%s - "WebSocket %s" %d', + get_client_addr(self.scope), + get_path_with_query_string(self.scope), + message["status"], + ) + self.handshake_complete = True + event = events.RejectConnection( + status_code=message["status"], + headers=list(message["headers"]), + has_body=True, + ) + output = self.conn.send(event) + self.transport.write(output) + self.response_started = True + + else: + raise RuntimeError( + "Expected ASGI message 'websocket.accept', 'websocket.close' " + f"or 'websocket.http.response.start' but got '{message['type']}'." + ) + + elif not self.close_sent and not self.response_started: + try: + if message["type"] == "websocket.send": + bytes_data = message.get("bytes") + text_data = message.get("text") + data = text_data if bytes_data is None else bytes_data + output = self.conn.send(wsproto.events.Message(data=data)) # type: ignore + if not self.transport.is_closing(): + self.transport.write(output) + + elif message["type"] == "websocket.close": + self.close_sent = True + code = message.get("code", 1000) + reason = message.get("reason", "") or "" + self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason}) + output = self.conn.send(wsproto.events.CloseConnection(code=code, reason=reason)) + if not self.transport.is_closing(): + self.transport.write(output) + self.transport.close() + + else: + raise RuntimeError( + f"Expected ASGI message 'websocket.send' or 'websocket.close', but got '{message['type']}'." + ) + except LocalProtocolError as exc: + raise ClientDisconnected from exc + elif self.response_started: + if message["type"] == "websocket.http.response.body": + body_finished = not message.get("more_body", False) + reject_data = events.RejectData(data=message["body"], body_finished=body_finished) + output = self.conn.send(reject_data) + self.transport.write(output) + + if body_finished: + self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006}) + self.close_sent = True + self.transport.close() + + else: + raise RuntimeError(f"Expected ASGI message 'websocket.http.response.body' but got '{message['type']}'.") + + else: + raise RuntimeError(f"Unexpected ASGI message '{message['type']}', after sending 'websocket.close'.") + + async def receive(self) -> WebSocketEvent: + message = await self.queue.get() + if self.read_paused and self.queue.empty(): + self.read_paused = False + self.transport.resume_reading() + return message diff --git a/.venv/lib/python3.14/site-packages/uvicorn/py.typed b/.venv/lib/python3.14/site-packages/uvicorn/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/py.typed @@ -0,0 +1 @@ + diff --git a/.venv/lib/python3.14/site-packages/uvicorn/server.py b/.venv/lib/python3.14/site-packages/uvicorn/server.py new file mode 100644 index 0000000000000000000000000000000000000000..938a6aaa905035fdd0b4af66133a89cefacd73f5 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/server.py @@ -0,0 +1,346 @@ +from __future__ import annotations + +import asyncio +import contextlib +import functools +import logging +import os +import platform +import random +import signal +import socket +import sys +import threading +import time +from collections.abc import Generator, Sequence +from email.utils import formatdate +from types import FrameType +from typing import TYPE_CHECKING, TypeAlias + +import click + +from uvicorn._compat import asyncio_run +from uvicorn.config import Config + +if TYPE_CHECKING: + from uvicorn.protocols.http.h11_impl import H11Protocol + from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol + from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol + from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol + from uvicorn.protocols.websockets.wsproto_impl import WSProtocol + + Protocols: TypeAlias = H11Protocol | HttpToolsProtocol | WSProtocol | WebSocketProtocol | WebSocketsSansIOProtocol + +HANDLED_SIGNALS = ( + signal.SIGINT, # Unix signal 2. Sent by Ctrl+C. + signal.SIGTERM, # Unix signal 15. Sent by `kill `. +) +if sys.platform == "win32": # pragma: py-not-win32 + HANDLED_SIGNALS += (signal.SIGBREAK,) # Windows signal 21. Sent by Ctrl+Break. + +logger = logging.getLogger("uvicorn.error") + + +class ServerState: + """ + Shared servers state that is available between all protocol instances. + """ + + def __init__(self) -> None: + self.total_requests = 0 + self.connections: set[Protocols] = set() + self.tasks: set[asyncio.Task[None]] = set() + self.default_headers: list[tuple[bytes, bytes]] = [] + + +class Server: + def __init__(self, config: Config) -> None: + self.config = config + self.server_state = ServerState() + + self.started = False + self.should_exit = False + self.force_exit = False + self.last_notified = 0.0 + + self._captured_signals: list[int] = [] + + @functools.cached_property + def limit_max_requests(self) -> int | None: + if self.config.limit_max_requests is None: + return None + return self.config.limit_max_requests + random.randint(0, self.config.limit_max_requests_jitter) + + def run(self, sockets: list[socket.socket] | None = None) -> None: + return asyncio_run(self.serve(sockets=sockets), loop_factory=self.config.get_loop_factory()) + + async def serve(self, sockets: list[socket.socket] | None = None) -> None: + with self.capture_signals(): + await self._serve(sockets) + + async def _serve(self, sockets: list[socket.socket] | None = None) -> None: + process_id = os.getpid() + + config = self.config + if not config.loaded: + config.load() + + self.lifespan = config.lifespan_class(config) + + message = "Started server process [%d]" + color_message = "Started server process [" + click.style("%d", fg="cyan") + "]" + logger.info(message, process_id, extra={"color_message": color_message}) + + await self.startup(sockets=sockets) + if not self.should_exit: + await self.main_loop() + if self.started: + await self.shutdown(sockets=sockets) + + message = "Finished server process [%d]" + color_message = "Finished server process [" + click.style("%d", fg="cyan") + "]" + logger.info(message, process_id, extra={"color_message": color_message}) + + async def startup(self, sockets: list[socket.socket] | None = None) -> None: + await self.lifespan.startup() + if self.lifespan.should_exit: + self.should_exit = True + return + + config = self.config + + def create_protocol( + _loop: asyncio.AbstractEventLoop | None = None, + ) -> asyncio.Protocol: + return config.http_protocol_class( # type: ignore[call-arg] + config=config, + server_state=self.server_state, + app_state=self.lifespan.state, + _loop=_loop, + ) + + loop = asyncio.get_running_loop() + + listeners: Sequence[socket.SocketType] + if sockets is not None: # pragma: full coverage + # Explicitly passed a list of open sockets. + # We use this when the server is run from a Gunicorn worker. + + def _share_socket( + sock: socket.SocketType, + ) -> socket.SocketType: # pragma py-not-win32 + # Windows requires the socket be explicitly shared across + # multiple workers (processes). + from socket import fromshare # type: ignore[attr-defined] + + sock_data = sock.share(os.getpid()) # type: ignore[attr-defined] + return fromshare(sock_data) + + self.servers: list[asyncio.base_events.Server] = [] + for sock in sockets: + is_windows = platform.system() == "Windows" + if config.workers > 1 and is_windows: # pragma: py-not-win32 + sock = _share_socket(sock) # type: ignore[assignment] + server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog) + self.servers.append(server) + listeners = sockets + + elif config.fd is not None: # pragma: py-win32 + # Use an existing socket, from a file descriptor. + sock = socket.fromfd(config.fd, socket.AF_UNIX, socket.SOCK_STREAM) + server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog) + assert server.sockets is not None # mypy + listeners = server.sockets + self.servers = [server] + + elif config.uds is not None: # pragma: py-win32 + # Create a socket using UNIX domain socket. + uds_perms = 0o666 + if os.path.exists(config.uds): + uds_perms = os.stat(config.uds).st_mode # pragma: full coverage + server = await loop.create_unix_server( + create_protocol, path=config.uds, ssl=config.ssl, backlog=config.backlog + ) + os.chmod(config.uds, uds_perms) + assert server.sockets is not None # mypy + listeners = server.sockets + self.servers = [server] + + else: + # Standard case. Create a socket from a host/port pair. + try: + server = await loop.create_server( + create_protocol, + host=config.host, + port=config.port, + ssl=config.ssl, + backlog=config.backlog, + ) + except OSError as exc: + logger.error(exc) + await self.lifespan.shutdown() + sys.exit(1) + + assert server.sockets is not None + listeners = server.sockets + self.servers = [server] + + if sockets is None: + self._log_started_message(listeners) + else: + # We're most likely running multiple workers, so a message has already been + # logged by `config.bind_socket()`. + pass # pragma: full coverage + + self.started = True + + def _log_started_message(self, listeners: Sequence[socket.SocketType]) -> None: + config = self.config + + if config.fd is not None: # pragma: py-win32 + sock = listeners[0] + logger.info( + "Uvicorn running on socket %s (Press CTRL+C to quit)", + sock.getsockname(), + ) + + elif config.uds is not None: # pragma: py-win32 + logger.info("Uvicorn running on unix socket %s (Press CTRL+C to quit)", config.uds) + + else: + addr_format = "%s://%s:%d" + host = "0.0.0.0" if config.host is None else config.host + if ":" in host: + # It's an IPv6 address. + addr_format = "%s://[%s]:%d" + + port = config.port + if port == 0: + port = listeners[0].getsockname()[1] + + protocol_name = "https" if config.ssl else "http" + message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)" + color_message = "Uvicorn running on " + click.style(addr_format, bold=True) + " (Press CTRL+C to quit)" + logger.info( + message, + protocol_name, + host, + port, + extra={"color_message": color_message}, + ) + + async def main_loop(self) -> None: + counter = 0 + should_exit = await self.on_tick(counter) + while not should_exit: + counter += 1 + counter = counter % 864000 + await asyncio.sleep(0.1) + should_exit = await self.on_tick(counter) + + async def on_tick(self, counter: int) -> bool: + # Update the default headers, once per second. + if counter % 10 == 0: + current_time = time.time() + current_date = formatdate(current_time, usegmt=True).encode() + + if self.config.date_header: + date_header = [(b"date", current_date)] + else: + date_header = [] + + self.server_state.default_headers = date_header + self.config.encoded_headers + + # Callback to `callback_notify` once every `timeout_notify` seconds. + if self.config.callback_notify is not None: + if current_time - self.last_notified > self.config.timeout_notify: # pragma: full coverage + self.last_notified = current_time + await self.config.callback_notify() + + # Determine if we should exit. + if self.should_exit: + return True + + max_requests = self.limit_max_requests + if max_requests is not None and self.server_state.total_requests >= max_requests: + logger.info("Maximum request limit of %d exceeded. Terminating process.", max_requests) + return True + + return False + + async def shutdown(self, sockets: list[socket.socket] | None = None) -> None: + logger.info("Shutting down") + + # Stop accepting new connections. + for server in self.servers: + server.close() + for sock in sockets or []: + sock.close() # pragma: full coverage + + # Request shutdown on all existing connections. + for connection in list(self.server_state.connections): + connection.shutdown() + await asyncio.sleep(0.1) + + # When 3.10 is not supported anymore, use `async with asyncio.timeout(...):`. + try: + await asyncio.wait_for( + self._wait_tasks_to_complete(), + timeout=self.config.timeout_graceful_shutdown, + ) + except asyncio.TimeoutError: + logger.error( + "Cancel %s running task(s), timeout graceful shutdown exceeded", + len(self.server_state.tasks), + ) + for t in self.server_state.tasks: + t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded") + + # Send the lifespan shutdown event, and wait for application shutdown. + if not self.force_exit: + await self.lifespan.shutdown() + + async def _wait_tasks_to_complete(self) -> None: + # Wait for existing connections to finish sending responses. + if self.server_state.connections and not self.force_exit: + msg = "Waiting for connections to close. (CTRL+C to force quit)" + logger.info(msg) + while self.server_state.connections and not self.force_exit: + await asyncio.sleep(0.1) + + # Wait for existing tasks to complete. + if self.server_state.tasks and not self.force_exit: + msg = "Waiting for background tasks to complete. (CTRL+C to force quit)" + logger.info(msg) + while self.server_state.tasks and not self.force_exit: + await asyncio.sleep(0.1) + + for server in self.servers: + await server.wait_closed() + + @contextlib.contextmanager + def capture_signals(self) -> Generator[None, None, None]: + # Signals can only be listened to from the main thread. + if threading.current_thread() is not threading.main_thread(): + yield + return + # always use signal.signal, even if loop.add_signal_handler is available + # this allows to restore previous signal handlers later on + original_handlers = {sig: signal.signal(sig, self.handle_exit) for sig in HANDLED_SIGNALS} + try: + yield + finally: + for sig, handler in original_handlers.items(): + signal.signal(sig, handler) + # If we did gracefully shut down due to a signal, try to + # trigger the expected behaviour now; multiple signals would be + # done LIFO, see https://stackoverflow.com/questions/48434964 + for captured_signal in reversed(self._captured_signals): + signal.raise_signal(captured_signal) + + def handle_exit(self, sig: int, frame: FrameType | None) -> None: + self._captured_signals.append(sig) + if self.should_exit and sig == signal.SIGINT: + self.force_exit = True # pragma: full coverage + else: + self.should_exit = True diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__init__.py b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cfceb6b94ce36dad006b1f81b816d45ce70ab3d6 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__init__.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from uvicorn.supervisors.basereload import BaseReload +from uvicorn.supervisors.multiprocess import Multiprocess + +if TYPE_CHECKING: + ChangeReload: type[BaseReload] +else: + try: + from uvicorn.supervisors.watchfilesreload import WatchFilesReload as ChangeReload + except ImportError: # pragma: no cover + from uvicorn.supervisors.statreload import StatReload as ChangeReload + +__all__ = ["Multiprocess", "ChangeReload"] diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..170bc5955180539f39bf95b224ff5267c75840d4 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/basereload.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/basereload.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f617b2ece8549f30228d7d7cf181681e70802aba Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/basereload.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/multiprocess.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/multiprocess.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a2d1768bae108cb7a5ead624fc31cab22fc0f47 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/multiprocess.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/statreload.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/statreload.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..904ebaddd50a2ebf1f786e94c550b27c6eaa4088 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/statreload.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/watchfilesreload.cpython-314.pyc b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/watchfilesreload.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be100ddb6a6c951de782f7bf0fc967e74502283e Binary files /dev/null and b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/__pycache__/watchfilesreload.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/basereload.py b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/basereload.py new file mode 100644 index 0000000000000000000000000000000000000000..66e34f53ffe62df65284d914bbf7d44a268b4917 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/basereload.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +import logging +import os +import signal +import sys +import threading +from collections.abc import Callable, Iterator +from pathlib import Path +from socket import socket +from types import FrameType + +import click + +from uvicorn._subprocess import get_subprocess +from uvicorn.config import Config + +HANDLED_SIGNALS = ( + signal.SIGINT, # Unix signal 2. Sent by Ctrl+C. + signal.SIGTERM, # Unix signal 15. Sent by `kill `. +) + +logger = logging.getLogger("uvicorn.error") + + +class BaseReload: + def __init__( + self, + config: Config, + target: Callable[[list[socket] | None], None], + sockets: list[socket], + ) -> None: + self.config = config + self.target = target + self.sockets = sockets + self.should_exit = threading.Event() + self.pid = os.getpid() + self.is_restarting = False + self.reloader_name: str | None = None + + def signal_handler(self, sig: int, frame: FrameType | None) -> None: # pragma: full coverage + """ + A signal handler that is registered with the parent process. + """ + if sys.platform == "win32" and self.is_restarting: + self.is_restarting = False + else: + self.should_exit.set() + + def run(self) -> None: + self.startup() + for changes in self: + if changes: + logger.warning( + "%s detected changes in %s. Reloading...", + self.reloader_name, + ", ".join(map(_display_path, changes)), + ) + self.restart() + + self.shutdown() + + def pause(self) -> None: + if self.should_exit.wait(self.config.reload_delay): + raise StopIteration() + + def __iter__(self) -> Iterator[list[Path] | None]: + return self + + def __next__(self) -> list[Path] | None: + return self.should_restart() + + def startup(self) -> None: + message = f"Started reloader process [{self.pid}] using {self.reloader_name}" + color_message = "Started reloader process [{}] using {}".format( + click.style(str(self.pid), fg="cyan", bold=True), + click.style(str(self.reloader_name), fg="cyan", bold=True), + ) + logger.info(message, extra={"color_message": color_message}) + + for sig in HANDLED_SIGNALS: + signal.signal(sig, self.signal_handler) + + self.process = get_subprocess(config=self.config, target=self.target, sockets=self.sockets) + self.process.start() + + def restart(self) -> None: + if sys.platform == "win32": # pragma: py-not-win32 + self.is_restarting = True + assert self.process.pid is not None + os.kill(self.process.pid, signal.CTRL_C_EVENT) + + # This is a workaround to ensure the Ctrl+C event is processed + sys.stdout.write(" ") # This has to be a non-empty string + sys.stdout.flush() + else: # pragma: py-win32 + self.process.terminate() + self.process.join() + + self.process = get_subprocess(config=self.config, target=self.target, sockets=self.sockets) + self.process.start() + + def shutdown(self) -> None: + if sys.platform == "win32": + self.should_exit.set() # pragma: py-not-win32 + else: + self.process.terminate() # pragma: py-win32 + self.process.join() + + for sock in self.sockets: + sock.close() + + message = f"Stopping reloader process [{str(self.pid)}]" + color_message = "Stopping reloader process [{}]".format(click.style(str(self.pid), fg="cyan", bold=True)) + logger.info(message, extra={"color_message": color_message}) + + def should_restart(self) -> list[Path] | None: + raise NotImplementedError("Reload strategies should override should_restart()") + + +def _display_path(path: Path) -> str: + try: + return f"'{path.relative_to(Path.cwd())}'" + except ValueError: + return f"'{path}'" diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/multiprocess.py b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/multiprocess.py new file mode 100644 index 0000000000000000000000000000000000000000..3ad98afab2bd5f6d6d729b7d3ad7c50ed5c6b941 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/multiprocess.py @@ -0,0 +1,223 @@ +from __future__ import annotations + +import logging +import os +import signal +import threading +from collections.abc import Callable +from multiprocessing import Pipe +from socket import socket +from typing import Any + +import click + +from uvicorn._subprocess import get_subprocess +from uvicorn.config import Config + +SIGNALS = { + getattr(signal, f"SIG{x}"): x + for x in "INT TERM BREAK HUP QUIT TTIN TTOU USR1 USR2 WINCH".split() + if hasattr(signal, f"SIG{x}") +} + +logger = logging.getLogger("uvicorn.error") + + +class Process: + def __init__( + self, + config: Config, + target: Callable[[list[socket] | None], None], + sockets: list[socket], + ) -> None: + self.real_target = target + + self.parent_conn, self.child_conn = Pipe() + self.process = get_subprocess(config, self.target, sockets) + + def ping(self, timeout: float = 5) -> bool: + self.parent_conn.send(b"ping") + if self.parent_conn.poll(timeout): + self.parent_conn.recv() + return True + return False + + def pong(self) -> None: + self.child_conn.recv() + self.child_conn.send(b"pong") + + def always_pong(self) -> None: + while True: + self.pong() + + def target(self, sockets: list[socket] | None = None) -> Any: # pragma: no cover + if os.name == "nt": # pragma: py-not-win32 + # Windows doesn't support SIGTERM, so we use SIGBREAK instead. + # And then we raise SIGTERM when SIGBREAK is received. + # https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/signal?view=msvc-170 + signal.signal( + signal.SIGBREAK, # type: ignore[attr-defined] + lambda sig, frame: signal.raise_signal(signal.SIGTERM), + ) + + threading.Thread(target=self.always_pong, daemon=True).start() + return self.real_target(sockets) + + def is_alive(self, timeout: float = 5) -> bool: + if not self.process.is_alive(): + return False # pragma: full coverage + + return self.ping(timeout) + + def start(self) -> None: + self.process.start() + + def terminate(self) -> None: + if self.process.exitcode is None: # Process is still running + assert self.process.pid is not None + if os.name == "nt": # pragma: py-not-win32 + # Windows doesn't support SIGTERM. + # So send SIGBREAK, and then in process raise SIGTERM. + os.kill(self.process.pid, signal.CTRL_BREAK_EVENT) # type: ignore[attr-defined] + else: + os.kill(self.process.pid, signal.SIGTERM) + logger.info(f"Terminated child process [{self.process.pid}]") + + self.parent_conn.close() + self.child_conn.close() + + def kill(self) -> None: + # In Windows, the method will call `TerminateProcess` to kill the process. + # In Unix, the method will send SIGKILL to the process. + self.process.kill() + + def join(self) -> None: + logger.info(f"Waiting for child process [{self.process.pid}]") + self.process.join() + + @property + def pid(self) -> int | None: + return self.process.pid + + +class Multiprocess: + def __init__( + self, + config: Config, + target: Callable[[list[socket] | None], None], + sockets: list[socket], + ) -> None: + self.config = config + self.target = target + self.sockets = sockets + + self.processes_num = config.workers + self.processes: list[Process] = [] + + self.should_exit = threading.Event() + + self.signal_queue: list[int] = [] + for sig in SIGNALS: + signal.signal(sig, lambda sig, frame: self.signal_queue.append(sig)) + + def init_processes(self) -> None: + for _ in range(self.processes_num): + process = Process(self.config, self.target, self.sockets) + process.start() + self.processes.append(process) + + def terminate_all(self) -> None: + for process in self.processes: + process.terminate() + + def join_all(self) -> None: + for process in self.processes: + process.join() + + def restart_all(self) -> None: + for idx, process in enumerate(self.processes): + process.terminate() + process.join() + new_process = Process(self.config, self.target, self.sockets) + new_process.start() + self.processes[idx] = new_process + + def run(self) -> None: + message = f"Started parent process [{os.getpid()}]" + color_message = "Started parent process [{}]".format(click.style(str(os.getpid()), fg="cyan", bold=True)) + logger.info(message, extra={"color_message": color_message}) + + self.init_processes() + + while not self.should_exit.wait(0.5): + self.handle_signals() + self.keep_subprocess_alive() + + self.terminate_all() + self.join_all() + + message = f"Stopping parent process [{os.getpid()}]" + color_message = "Stopping parent process [{}]".format(click.style(str(os.getpid()), fg="cyan", bold=True)) + logger.info(message, extra={"color_message": color_message}) + + def keep_subprocess_alive(self) -> None: + if self.should_exit.is_set(): + return # parent process is exiting, no need to keep subprocess alive + + for idx, process in enumerate(self.processes): + if process.is_alive(timeout=self.config.timeout_worker_healthcheck): + continue + + process.kill() # process is hung, kill it + process.join() + + if self.should_exit.is_set(): + return # pragma: full coverage + + logger.info(f"Child process [{process.pid}] died") + process = Process(self.config, self.target, self.sockets) + process.start() + self.processes[idx] = process + + def handle_signals(self) -> None: + for sig in tuple(self.signal_queue): + self.signal_queue.remove(sig) + sig_name = SIGNALS[sig] + sig_handler = getattr(self, f"handle_{sig_name.lower()}", None) + if sig_handler is not None: + sig_handler() + else: # pragma: no cover + logger.debug(f"Received signal {sig_name}, but no handler is defined for it.") + + def handle_int(self) -> None: + logger.info("Received SIGINT, exiting.") + self.should_exit.set() + + def handle_term(self) -> None: + logger.info("Received SIGTERM, exiting.") + self.should_exit.set() + + def handle_break(self) -> None: # pragma: py-not-win32 + logger.info("Received SIGBREAK, exiting.") + self.should_exit.set() + + def handle_hup(self) -> None: # pragma: py-win32 + logger.info("Received SIGHUP, restarting processes.") + self.restart_all() + + def handle_ttin(self) -> None: # pragma: py-win32 + logger.info("Received SIGTTIN, increasing the number of processes.") + self.processes_num += 1 + process = Process(self.config, self.target, self.sockets) + process.start() + self.processes.append(process) + + def handle_ttou(self) -> None: # pragma: py-win32 + logger.info("Received SIGTTOU, decreasing number of processes.") + if self.processes_num <= 1: + logger.info("Already reached one process, cannot decrease the number of processes anymore.") + return + self.processes_num -= 1 + process = self.processes.pop() + process.terminate() + process.join() diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/statreload.py b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/statreload.py new file mode 100644 index 0000000000000000000000000000000000000000..28693f9cc6d14ed8ea98518a78162e8c84f51c72 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/statreload.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import logging +from collections.abc import Callable, Iterator +from pathlib import Path +from socket import socket + +from uvicorn.config import Config +from uvicorn.supervisors.basereload import BaseReload + +logger = logging.getLogger("uvicorn.error") + + +class StatReload(BaseReload): + def __init__( + self, + config: Config, + target: Callable[[list[socket] | None], None], + sockets: list[socket], + ) -> None: + super().__init__(config, target, sockets) + self.reloader_name = "StatReload" + self.mtimes: dict[Path, float] = {} + + if config.reload_excludes or config.reload_includes: + logger.warning("--reload-include and --reload-exclude have no effect unless watchfiles is installed.") + + def should_restart(self) -> list[Path] | None: + self.pause() + + for file in self.iter_py_files(): + try: + mtime = file.stat().st_mtime + except OSError: # pragma: nocover + continue + + old_time = self.mtimes.get(file) + if old_time is None: + self.mtimes[file] = mtime + continue + elif mtime > old_time: + return [file] + return None + + def restart(self) -> None: + self.mtimes = {} + return super().restart() + + def iter_py_files(self) -> Iterator[Path]: + for reload_dir in self.config.reload_dirs: + for path in list(reload_dir.rglob("*.py")): + yield path.resolve() diff --git a/.venv/lib/python3.14/site-packages/uvicorn/supervisors/watchfilesreload.py b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/watchfilesreload.py new file mode 100644 index 0000000000000000000000000000000000000000..600990595274dbc649176247c0c15840e59e0105 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/supervisors/watchfilesreload.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from collections.abc import Callable +from pathlib import Path +from socket import socket + +from watchfiles import watch + +from uvicorn.config import Config +from uvicorn.supervisors.basereload import BaseReload + + +class FileFilter: + def __init__(self, config: Config): + default_includes = ["*.py"] + self.includes = [default for default in default_includes if default not in config.reload_excludes] + self.includes.extend(config.reload_includes) + self.includes = list(set(self.includes)) + + default_excludes = [".*", ".py[cod]", ".sw.*", "~*"] + self.excludes = [default for default in default_excludes if default not in config.reload_includes] + self.exclude_dirs = [] + for e in config.reload_excludes: + p = Path(e) + try: + is_dir = p.is_dir() + except OSError: # pragma: no cover + # gets raised on Windows for values like "*.py" + is_dir = False + + if is_dir: + self.exclude_dirs.append(p) + else: + self.excludes.append(e) # pragma: full coverage + self.excludes = list(set(self.excludes)) + + def __call__(self, path: Path) -> bool: + for include_pattern in self.includes: + if path.match(include_pattern): + if str(path).endswith(include_pattern): + return True # pragma: full coverage + + for exclude_dir in self.exclude_dirs: + if exclude_dir in path.parents: + return False + + for exclude_pattern in self.excludes: + if path.match(exclude_pattern): + return False # pragma: full coverage + + return True + return False + + +class WatchFilesReload(BaseReload): + def __init__( + self, + config: Config, + target: Callable[[list[socket] | None], None], + sockets: list[socket], + ) -> None: + super().__init__(config, target, sockets) + self.reloader_name = "WatchFiles" + self.reload_dirs: list[Path] = [] + for directory in config.reload_dirs: + self.reload_dirs.append(directory) + + self.watch_filter = FileFilter(config) + self.watcher = watch( + *self.reload_dirs, + watch_filter=None, + stop_event=self.should_exit, + # using yield_on_timeout here mostly to make sure tests don't + # hang forever, won't affect the class's behavior + yield_on_timeout=True, + ignore_permission_denied=True, + ) + + def should_restart(self) -> list[Path] | None: + self.pause() + + changes = next(self.watcher) + if changes: + unique_paths = {Path(c[1]) for c in changes} + return [p for p in unique_paths if self.watch_filter(p)] + return None diff --git a/.venv/lib/python3.14/site-packages/uvicorn/workers.py b/.venv/lib/python3.14/site-packages/uvicorn/workers.py new file mode 100644 index 0000000000000000000000000000000000000000..827d091c0f1693a43ae030b55e6ff79f7a552192 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/uvicorn/workers.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +import asyncio +import logging +import signal +import sys +import warnings +from typing import Any + +from gunicorn.arbiter import Arbiter +from gunicorn.workers.base import Worker + +from uvicorn._compat import asyncio_run +from uvicorn.config import Config +from uvicorn.server import Server + +warnings.warn( + "The `uvicorn.workers` module is deprecated. Please use `uvicorn-worker` package instead.\n" + "For more details, see https://github.com/Kludex/uvicorn-worker.", + DeprecationWarning, +) + + +class UvicornWorker(Worker): + """ + A worker class for Gunicorn that interfaces with an ASGI consumer callable, + rather than a WSGI callable. + """ + + CONFIG_KWARGS: dict[str, Any] = {"loop": "auto", "http": "auto"} + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + logger = logging.getLogger("uvicorn.error") + logger.handlers = self.log.error_log.handlers + logger.setLevel(self.log.error_log.level) + logger.propagate = False + + logger = logging.getLogger("uvicorn.access") + logger.handlers = self.log.access_log.handlers + logger.setLevel(self.log.access_log.level) + logger.propagate = False + + config_kwargs: dict = { + "app": None, + "log_config": None, + "timeout_keep_alive": self.cfg.keepalive, + "timeout_notify": self.timeout, + "callback_notify": self.callback_notify, + "limit_max_requests": self.max_requests, + "forwarded_allow_ips": self.cfg.forwarded_allow_ips, + } + + if self.cfg.is_ssl: + ssl_kwargs = { + "ssl_keyfile": self.cfg.ssl_options.get("keyfile"), + "ssl_certfile": self.cfg.ssl_options.get("certfile"), + "ssl_keyfile_password": self.cfg.ssl_options.get("password"), + "ssl_version": self.cfg.ssl_options.get("ssl_version"), + "ssl_cert_reqs": self.cfg.ssl_options.get("cert_reqs"), + "ssl_ca_certs": self.cfg.ssl_options.get("ca_certs"), + "ssl_ciphers": self.cfg.ssl_options.get("ciphers"), + } + config_kwargs.update(ssl_kwargs) + + if self.cfg.settings["backlog"].value: + config_kwargs["backlog"] = self.cfg.settings["backlog"].value + + config_kwargs.update(self.CONFIG_KWARGS) + + self.config = Config(**config_kwargs) + + def init_signals(self) -> None: + # Reset signals so Gunicorn doesn't swallow subprocess return codes + # other signals are set up by Server.install_signal_handlers() + # See: https://github.com/Kludex/uvicorn/issues/894 + for s in self.SIGNALS: + signal.signal(s, signal.SIG_DFL) + + signal.signal(signal.SIGUSR1, self.handle_usr1) + # Don't let SIGUSR1 disturb active requests by interrupting system calls + signal.siginterrupt(signal.SIGUSR1, False) + + def _install_sigquit_handler(self) -> None: + """Install a SIGQUIT handler on workers. + + - https://github.com/Kludex/uvicorn/issues/1116 + - https://github.com/benoitc/gunicorn/issues/2604 + """ + + loop = asyncio.get_running_loop() + loop.add_signal_handler(signal.SIGQUIT, self.handle_exit, signal.SIGQUIT, None) + + async def _serve(self) -> None: + self.config.app = self.wsgi + server = Server(config=self.config) + self._install_sigquit_handler() + await server.serve(sockets=self.sockets) + if not server.started: + sys.exit(Arbiter.WORKER_BOOT_ERROR) + + def run(self) -> None: + return asyncio_run(self._serve(), loop_factory=self.config.get_loop_factory()) + + async def callback_notify(self) -> None: + self.notify() + + +class UvicornH11Worker(UvicornWorker): + CONFIG_KWARGS = {"loop": "asyncio", "http": "h11"} diff --git a/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/INSTALLER b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/METADATA b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..53b147e4b45cc4ec0502fd8388f8c65f6e89c97c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/METADATA @@ -0,0 +1,179 @@ +Metadata-Version: 2.4 +Name: websockets +Version: 16.0 +Summary: An implementation of the WebSocket Protocol (RFC 6455 & 7692) +Author-email: Aymeric Augustin +License-Expression: BSD-3-Clause +Project-URL: Homepage, https://github.com/python-websockets/websockets +Project-URL: Changelog, https://websockets.readthedocs.io/en/stable/project/changelog.html +Project-URL: Documentation, https://websockets.readthedocs.io/ +Project-URL: Funding, https://tidelift.com/subscription/pkg/pypi-websockets?utm_source=pypi-websockets&utm_medium=referral&utm_campaign=readme +Project-URL: Tracker, https://github.com/python-websockets/websockets/issues +Keywords: WebSocket +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +Dynamic: description +Dynamic: description-content-type +Dynamic: license-file + +.. image:: logo/horizontal.svg + :width: 480px + :alt: websockets + +|licence| |version| |pyversions| |tests| |docs| |openssf| + +.. |licence| image:: https://img.shields.io/pypi/l/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |version| image:: https://img.shields.io/pypi/v/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |pyversions| image:: https://img.shields.io/pypi/pyversions/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |tests| image:: https://img.shields.io/github/checks-status/python-websockets/websockets/main?label=tests + :target: https://github.com/python-websockets/websockets/actions/workflows/tests.yml + +.. |docs| image:: https://img.shields.io/readthedocs/websockets.svg + :target: https://websockets.readthedocs.io/ + +.. |openssf| image:: https://bestpractices.coreinfrastructure.org/projects/6475/badge + :target: https://bestpractices.coreinfrastructure.org/projects/6475 + +What is ``websockets``? +----------------------- + +websockets is a library for building WebSocket_ servers and clients in Python +with a focus on correctness, simplicity, robustness, and performance. + +.. _WebSocket: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API + +Built on top of ``asyncio``, Python's standard asynchronous I/O framework, the +default implementation provides an elegant coroutine-based API. + +An implementation on top of ``threading`` and a Sans-I/O implementation are also +available. + +`Documentation is available on Read the Docs. `_ + +.. copy-pasted because GitHub doesn't support the include directive + +Here's an echo server with the ``asyncio`` API: + +.. code:: python + + #!/usr/bin/env python + + import asyncio + from websockets.asyncio.server import serve + + async def echo(websocket): + async for message in websocket: + await websocket.send(message) + + async def main(): + async with serve(echo, "localhost", 8765) as server: + await server.serve_forever() + + asyncio.run(main()) + +Here's how a client sends and receives messages with the ``threading`` API: + +.. code:: python + + #!/usr/bin/env python + + from websockets.sync.client import connect + + def hello(): + with connect("ws://localhost:8765") as websocket: + websocket.send("Hello world!") + message = websocket.recv() + print(f"Received: {message}") + + hello() + + +Does that look good? + +`Get started with the tutorial! `_ + +Why should I use ``websockets``? +-------------------------------- + +The development of ``websockets`` is shaped by four principles: + +1. **Correctness**: ``websockets`` is heavily tested for compliance with + :rfc:`6455`. Continuous integration fails under 100% branch coverage. + +2. **Simplicity**: all you need to understand is ``msg = await ws.recv()`` and + ``await ws.send(msg)``. ``websockets`` takes care of managing connections + so you can focus on your application. + +3. **Robustness**: ``websockets`` is built for production. For example, it was + the only library to `handle backpressure correctly`_ before the issue + became widely known in the Python community. + +4. **Performance**: memory usage is optimized and configurable. A C extension + accelerates expensive operations. It's pre-compiled for Linux, macOS and + Windows and packaged in the wheel format for each system and Python version. + +Documentation is a first class concern in the project. Head over to `Read the +Docs`_ and see for yourself. + +.. _Read the Docs: https://websockets.readthedocs.io/ +.. _handle backpressure correctly: https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/#websocket-servers + +Why shouldn't I use ``websockets``? +----------------------------------- + +* If you prefer callbacks over coroutines: ``websockets`` was created to + provide the best coroutine-based API to manage WebSocket connections in + Python. Pick another library for a callback-based API. + +* If you're looking for a mixed HTTP / WebSocket library: ``websockets`` aims + at being an excellent implementation of :rfc:`6455`: The WebSocket Protocol + and :rfc:`7692`: Compression Extensions for WebSocket. Its support for HTTP + is minimal — just enough for an HTTP health check. + + If you want to do both in the same server, look at HTTP + WebSocket servers + that build on top of ``websockets`` to support WebSocket connections, like + uvicorn_ or Sanic_. + +.. _uvicorn: https://www.uvicorn.org/ +.. _Sanic: https://sanic.dev/en/ + +What else? +---------- + +Bug reports, patches and suggestions are welcome! + +To report a security vulnerability, please use the `Tidelift security +contact`_. Tidelift will coordinate the fix and disclosure. + +.. _Tidelift security contact: https://tidelift.com/security + +For anything else, please open an issue_ or send a `pull request`_. + +.. _issue: https://github.com/python-websockets/websockets/issues/new +.. _pull request: https://github.com/python-websockets/websockets/compare/ + +Participants must uphold the `Contributor Covenant code of conduct`_. + +.. _Contributor Covenant code of conduct: https://github.com/python-websockets/websockets/blob/main/CODE_OF_CONDUCT.md + +``websockets`` is released under the `BSD license`_. + +.. _BSD license: https://github.com/python-websockets/websockets/blob/main/LICENSE diff --git a/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/RECORD b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..3f972fb0372d460072f0dd0bdb21ab026d95727d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/RECORD @@ -0,0 +1,108 @@ +../../../bin/websockets,sha256=u07euH2S0gvPw791O8QrujvQkl-L8nii1FMZYpJ8Xx8,212 +websockets-16.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +websockets-16.0.dist-info/METADATA,sha256=JcDvWo8DVSw5uoDAFbk9N8fJXuRJvnrcLXVBFyBjwN8,6799 +websockets-16.0.dist-info/RECORD,, +websockets-16.0.dist-info/WHEEL,sha256=2Id6qreet5t4wZv58bZfijJ58qrc2xkw6OVvWfeqxV0,136 +websockets-16.0.dist-info/entry_points.txt,sha256=Dnhn4dm5EsI4ZMAsHldGF6CwBXZrGXnR7cnK2-XR7zY,51 +websockets-16.0.dist-info/licenses/LICENSE,sha256=PWoMBQ2L7FL6utUC5F-yW9ArytvXDeo01Ee2oP9Obag,1514 +websockets-16.0.dist-info/top_level.txt,sha256=CMpdKklxKsvZgCgyltxUWOHibZXZ1uYIVpca9xsQ8Hk,11 +websockets/__init__.py,sha256=AC2Hq92uSc_WOo9_xvITpGshJ7Dy0Md5m2_ywsdSt_Y,7058 +websockets/__main__.py,sha256=wu5N2wk8mvBgyvr2ghmQf4prezAe0_i-p123VVreyYc,62 +websockets/__pycache__/__init__.cpython-314.pyc,, +websockets/__pycache__/__main__.cpython-314.pyc,, +websockets/__pycache__/auth.cpython-314.pyc,, +websockets/__pycache__/cli.cpython-314.pyc,, +websockets/__pycache__/client.cpython-314.pyc,, +websockets/__pycache__/connection.cpython-314.pyc,, +websockets/__pycache__/datastructures.cpython-314.pyc,, +websockets/__pycache__/exceptions.cpython-314.pyc,, +websockets/__pycache__/frames.cpython-314.pyc,, +websockets/__pycache__/headers.cpython-314.pyc,, +websockets/__pycache__/http.cpython-314.pyc,, +websockets/__pycache__/http11.cpython-314.pyc,, +websockets/__pycache__/imports.cpython-314.pyc,, +websockets/__pycache__/protocol.cpython-314.pyc,, +websockets/__pycache__/proxy.cpython-314.pyc,, +websockets/__pycache__/server.cpython-314.pyc,, +websockets/__pycache__/streams.cpython-314.pyc,, +websockets/__pycache__/typing.cpython-314.pyc,, +websockets/__pycache__/uri.cpython-314.pyc,, +websockets/__pycache__/utils.cpython-314.pyc,, +websockets/__pycache__/version.cpython-314.pyc,, +websockets/asyncio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +websockets/asyncio/__pycache__/__init__.cpython-314.pyc,, +websockets/asyncio/__pycache__/async_timeout.cpython-314.pyc,, +websockets/asyncio/__pycache__/client.cpython-314.pyc,, +websockets/asyncio/__pycache__/compatibility.cpython-314.pyc,, +websockets/asyncio/__pycache__/connection.cpython-314.pyc,, +websockets/asyncio/__pycache__/messages.cpython-314.pyc,, +websockets/asyncio/__pycache__/router.cpython-314.pyc,, +websockets/asyncio/__pycache__/server.cpython-314.pyc,, +websockets/asyncio/async_timeout.py,sha256=N-6Mubyiaoh66PAXGvCzhgxCM-7V2XiRnH32Xi6J6TE,8971 +websockets/asyncio/client.py,sha256=e4xlgtzb3v29M2vN-UDiyoUtThg--d5GqKg3lt2pDdE,30850 +websockets/asyncio/compatibility.py,sha256=gkenDDhzNbm6_iXV5Edvbvp6uHZYdrTvGNjt8P_JtyQ,786 +websockets/asyncio/connection.py,sha256=87RdVURijJk8V-ShWAWfTEyhW5Z1YUXKV8ezUzxt5L0,49099 +websockets/asyncio/messages.py,sha256=u2M5WKY9xPyw8G3nKoXfdO5K41hrTnf4MdizVHzgdM4,11129 +websockets/asyncio/router.py,sha256=S-69vszK-SqUCcZbXXPOnux-eH2fTHYC2JNh7tOtmmA,7520 +websockets/asyncio/server.py,sha256=wQ9oBc0WBOIzbXKDYJ8UhXRTeoXrSfLu6CWCrUl-vck,37941 +websockets/auth.py,sha256=U_Jwmn59ZRQ6EecpOvMizQCG_ZbAvgUf1ik7haZRC3c,568 +websockets/cli.py,sha256=YnegH59z93JxSVIGiXiWhR3ktgI6k1_pf_BRLanxKrQ,5336 +websockets/client.py,sha256=fljI5k5oQ-Sfm53MCoyTlr2jFtOOIuO13H9bbtpBPes,13789 +websockets/connection.py,sha256=OLiMVkNd25_86sB8Q7CrCwBoXy9nA0OCgdgLRA8WUR8,323 +websockets/datastructures.py,sha256=Uq2CpjmXak9_pPWcOqh36rzJMo8eCi2lVPTFWDvK5sA,5518 +websockets/exceptions.py,sha256=bgaMdqQGGZosAEULeCB30XW2YnwomWa3c8YOrEfeOoY,12859 +websockets/extensions/__init__.py,sha256=QkZsxaJVllVSp1uhdD5uPGibdbx_091GrVVfS5LXcpw,98 +websockets/extensions/__pycache__/__init__.cpython-314.pyc,, +websockets/extensions/__pycache__/base.cpython-314.pyc,, +websockets/extensions/__pycache__/permessage_deflate.cpython-314.pyc,, +websockets/extensions/base.py,sha256=JNfyk543C7VuPH0QOobiqKoGrzjJILje6sz5ILvOPl4,2903 +websockets/extensions/permessage_deflate.py,sha256=AkuhkAKFo5lqJQMXnckbSs9b2KBBrOFsE1DHIcbLL3k,25770 +websockets/frames.py,sha256=5IK4GZpl8ukr0bZ_UA_jjjztK09yYQAl9m5NVmGLiK0,12889 +websockets/headers.py,sha256=yQnPljVZwV1_V-pOSRKNLG_u827wFC1h72cciojcQ8M,16046 +websockets/http.py,sha256=T1tNLmbkFCneXQ6qepBmsVVDXyP9i500IVzTJTeBMR4,659 +websockets/http11.py,sha256=T8ai5BcBGkV0n9It63oDeNpmtQMyg8Cpav5rf_yT0r4,15619 +websockets/imports.py,sha256=T_B9TUmHoceKMQ-PNphdQQAH2XdxAxwSQNeQEgqILkE,2795 +websockets/legacy/__init__.py,sha256=wQ5zRIENGUS_5eKNAX9CRE7x1TwKapKimrQFFWN9Sxs,276 +websockets/legacy/__pycache__/__init__.cpython-314.pyc,, +websockets/legacy/__pycache__/auth.cpython-314.pyc,, +websockets/legacy/__pycache__/client.cpython-314.pyc,, +websockets/legacy/__pycache__/exceptions.cpython-314.pyc,, +websockets/legacy/__pycache__/framing.cpython-314.pyc,, +websockets/legacy/__pycache__/handshake.cpython-314.pyc,, +websockets/legacy/__pycache__/http.cpython-314.pyc,, +websockets/legacy/__pycache__/protocol.cpython-314.pyc,, +websockets/legacy/__pycache__/server.cpython-314.pyc,, +websockets/legacy/auth.py,sha256=DcQcCSeVeP93JcH8vFWE0HIJL-X-f23LZ0DsJpav1So,6531 +websockets/legacy/client.py,sha256=fV2mbiU9rciXhJfAEKVSm0GztJDUbDpRQ-K5EMbkuQ0,26815 +websockets/legacy/exceptions.py,sha256=ViEjpoT09fzx_Zqf0aNGDVtRDNjXaOw0gdCta3LkjFc,1924 +websockets/legacy/framing.py,sha256=r9P1wiXv_1XuAVQw8SOPkuE9d4eZ0r_JowAkz9-WV4w,6366 +websockets/legacy/handshake.py,sha256=2Nzr5AN2xvDC5EdNP-kB3lOcrAaUNlYuj_-hr_jv7pM,5285 +websockets/legacy/http.py,sha256=cOCQmDWhIKQmm8UWGXPW7CDZg03wjogCsb0LP9oetNQ,7061 +websockets/legacy/protocol.py,sha256=ajtVXDb-lEm9BN0NF3iEaTI_b1q5fBCKTB9wvUoGOxY,63632 +websockets/legacy/server.py,sha256=7mwY-yD0ljNF93oPYumTWD7OIVbCWtaEOw1FFJBhIAM,45251 +websockets/protocol.py,sha256=vTqjPIg2HmO-bSxsczuEmWMxPTxPXU1hmVUjqnahV44,27247 +websockets/proxy.py,sha256=oFrbEYtasYWv-WDcniObD9nBR5Q5qkHpyCVLngx7WMQ,4969 +websockets/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +websockets/server.py,sha256=E4SWBA8WZRmAOpsUm-oCqacBGZre9e0iDmDIrfpV21Q,21790 +websockets/speedups.c,sha256=u_dncR4M38EX6He_fzb1TY6D3Hke67ZpoHLLhZZ0hvQ,5920 +websockets/speedups.cpython-314-darwin.so,sha256=MOBcHHScPaBFfvjYPtAs_BqhVVs6kJ8EizUVcvrVz4A,51376 +websockets/speedups.pyi,sha256=unjvBNg-uW4c7z-9OW4WiSzZk_QH2bLEcjYAMuoSgBI,102 +websockets/streams.py,sha256=pXqga7ttjuF6lChWYiWLSfUlt3FCaQpEX1ae_jvcCeQ,4071 +websockets/sync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +websockets/sync/__pycache__/__init__.cpython-314.pyc,, +websockets/sync/__pycache__/client.cpython-314.pyc,, +websockets/sync/__pycache__/connection.cpython-314.pyc,, +websockets/sync/__pycache__/messages.cpython-314.pyc,, +websockets/sync/__pycache__/router.cpython-314.pyc,, +websockets/sync/__pycache__/server.cpython-314.pyc,, +websockets/sync/__pycache__/utils.cpython-314.pyc,, +websockets/sync/client.py,sha256=_2Erytw1f3f9O_u2jLtS1oNV4HsHUi_h3lGvT9ZEaDQ,22108 +websockets/sync/connection.py,sha256=1pJYEMRHLWIN7538vJcIeFVnvSXVrD0n1xrfX7wDNSc,41868 +websockets/sync/messages.py,sha256=yZV1zhY07ZD0vRF5b1yDa7ug0rbA5UDOCCCQmWwAcds,12858 +websockets/sync/router.py,sha256=BqKSAKNZYtRWiOxol9qYeyfgyXRrMNJ6FrTTZLNcXMg,7172 +websockets/sync/server.py,sha256=s07HNK_2s1kLN62Uqc77uvND0z7C0YTXGePsCiBtXaE,27655 +websockets/sync/utils.py,sha256=TtW-ncYFvJmiSW2gO86ngE2BVsnnBdL-4H88kWNDYbg,1107 +websockets/typing.py,sha256=A6xh4m65pRzKAbuOs0kFuGhL4DWIIko-ppS4wvJVc0Q,1946 +websockets/uri.py,sha256=2fFMw-AbKJ5HVHNCuw1Rx1MnkCkNWRpogxWhhM30EU4,3125 +websockets/utils.py,sha256=AwhS4UmlbKv7meAaR7WNbUqD5JFoStOP1bAyo9sRMus,1197 +websockets/version.py,sha256=IhaztWxysdY-pd-0nOubnnPduvySSvdoBwrQdJKtZ2g,3202 diff --git a/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/WHEEL b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..d4a23dfe488a2c86d9c4be4974885d4896c22972 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: false +Tag: cp314-cp314-macosx_11_0_arm64 +Generator: delocate 0.13.0 + diff --git a/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/entry_points.txt b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..60cd61ca0074591bf4dd607f11ee22310a3daab4 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +websockets = websockets.cli:main diff --git a/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/licenses/LICENSE b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..5d61ece22a75a759aed8e52af280eca28d35d6bf --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/licenses/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) Aymeric Augustin and contributors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/top_level.txt b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..14774b465e97f655dbcaa60d97c8a9aa72e7d51b --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets-16.0.dist-info/top_level.txt @@ -0,0 +1 @@ +websockets diff --git a/.venv/lib/python3.14/site-packages/websockets/__init__.py b/.venv/lib/python3.14/site-packages/websockets/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f90aff5b958ab3a19ecbb3e5ccad676e3719d9ab --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/__init__.py @@ -0,0 +1,236 @@ +from __future__ import annotations + +# Importing the typing module would conflict with websockets.typing. +from typing import TYPE_CHECKING + +from .imports import lazy_import +from .version import version as __version__ # noqa: F401 + + +__all__ = [ + # .asyncio.client + "connect", + "unix_connect", + "ClientConnection", + # .asyncio.router + "route", + "unix_route", + "Router", + # .asyncio.server + "basic_auth", + "broadcast", + "serve", + "unix_serve", + "ServerConnection", + "Server", + # .client + "ClientProtocol", + # .datastructures + "Headers", + "HeadersLike", + "MultipleValuesError", + # .exceptions + "ConcurrencyError", + "ConnectionClosed", + "ConnectionClosedError", + "ConnectionClosedOK", + "DuplicateParameter", + "InvalidHandshake", + "InvalidHeader", + "InvalidHeaderFormat", + "InvalidHeaderValue", + "InvalidMessage", + "InvalidOrigin", + "InvalidParameterName", + "InvalidParameterValue", + "InvalidProxy", + "InvalidProxyMessage", + "InvalidProxyStatus", + "InvalidState", + "InvalidStatus", + "InvalidUpgrade", + "InvalidURI", + "NegotiationError", + "PayloadTooBig", + "ProtocolError", + "ProxyError", + "SecurityError", + "WebSocketException", + # .frames + "Close", + "CloseCode", + "Frame", + "Opcode", + # .http11 + "Request", + "Response", + # .protocol + "Protocol", + "Side", + "State", + # .server + "ServerProtocol", + # .typing + "Data", + "ExtensionName", + "ExtensionParameter", + "LoggerLike", + "StatusLike", + "Origin", + "Subprotocol", +] + +# When type checking, import non-deprecated aliases eagerly. Else, import on demand. +if TYPE_CHECKING: + from .asyncio.client import ClientConnection, connect, unix_connect + from .asyncio.router import Router, route, unix_route + from .asyncio.server import ( + Server, + ServerConnection, + basic_auth, + broadcast, + serve, + unix_serve, + ) + from .client import ClientProtocol + from .datastructures import Headers, HeadersLike, MultipleValuesError + from .exceptions import ( + ConcurrencyError, + ConnectionClosed, + ConnectionClosedError, + ConnectionClosedOK, + DuplicateParameter, + InvalidHandshake, + InvalidHeader, + InvalidHeaderFormat, + InvalidHeaderValue, + InvalidMessage, + InvalidOrigin, + InvalidParameterName, + InvalidParameterValue, + InvalidProxy, + InvalidProxyMessage, + InvalidProxyStatus, + InvalidState, + InvalidStatus, + InvalidUpgrade, + InvalidURI, + NegotiationError, + PayloadTooBig, + ProtocolError, + ProxyError, + SecurityError, + WebSocketException, + ) + from .frames import Close, CloseCode, Frame, Opcode + from .http11 import Request, Response + from .protocol import Protocol, Side, State + from .server import ServerProtocol + from .typing import ( + Data, + ExtensionName, + ExtensionParameter, + LoggerLike, + Origin, + StatusLike, + Subprotocol, + ) +else: + lazy_import( + globals(), + aliases={ + # .asyncio.client + "connect": ".asyncio.client", + "unix_connect": ".asyncio.client", + "ClientConnection": ".asyncio.client", + # .asyncio.router + "route": ".asyncio.router", + "unix_route": ".asyncio.router", + "Router": ".asyncio.router", + # .asyncio.server + "basic_auth": ".asyncio.server", + "broadcast": ".asyncio.server", + "serve": ".asyncio.server", + "unix_serve": ".asyncio.server", + "ServerConnection": ".asyncio.server", + "Server": ".asyncio.server", + # .client + "ClientProtocol": ".client", + # .datastructures + "Headers": ".datastructures", + "HeadersLike": ".datastructures", + "MultipleValuesError": ".datastructures", + # .exceptions + "ConcurrencyError": ".exceptions", + "ConnectionClosed": ".exceptions", + "ConnectionClosedError": ".exceptions", + "ConnectionClosedOK": ".exceptions", + "DuplicateParameter": ".exceptions", + "InvalidHandshake": ".exceptions", + "InvalidHeader": ".exceptions", + "InvalidHeaderFormat": ".exceptions", + "InvalidHeaderValue": ".exceptions", + "InvalidMessage": ".exceptions", + "InvalidOrigin": ".exceptions", + "InvalidParameterName": ".exceptions", + "InvalidParameterValue": ".exceptions", + "InvalidProxy": ".exceptions", + "InvalidProxyMessage": ".exceptions", + "InvalidProxyStatus": ".exceptions", + "InvalidState": ".exceptions", + "InvalidStatus": ".exceptions", + "InvalidUpgrade": ".exceptions", + "InvalidURI": ".exceptions", + "NegotiationError": ".exceptions", + "PayloadTooBig": ".exceptions", + "ProtocolError": ".exceptions", + "ProxyError": ".exceptions", + "SecurityError": ".exceptions", + "WebSocketException": ".exceptions", + # .frames + "Close": ".frames", + "CloseCode": ".frames", + "Frame": ".frames", + "Opcode": ".frames", + # .http11 + "Request": ".http11", + "Response": ".http11", + # .protocol + "Protocol": ".protocol", + "Side": ".protocol", + "State": ".protocol", + # .server + "ServerProtocol": ".server", + # .typing + "Data": ".typing", + "ExtensionName": ".typing", + "ExtensionParameter": ".typing", + "LoggerLike": ".typing", + "Origin": ".typing", + "StatusLike": ".typing", + "Subprotocol": ".typing", + }, + deprecated_aliases={ + # deprecated in 9.0 - 2021-09-01 + "framing": ".legacy", + "handshake": ".legacy", + "parse_uri": ".uri", + "WebSocketURI": ".uri", + # deprecated in 14.0 - 2024-11-09 + # .legacy.auth + "BasicAuthWebSocketServerProtocol": ".legacy.auth", + "basic_auth_protocol_factory": ".legacy.auth", + # .legacy.client + "WebSocketClientProtocol": ".legacy.client", + # .legacy.exceptions + "AbortHandshake": ".legacy.exceptions", + "InvalidStatusCode": ".legacy.exceptions", + "RedirectHandshake": ".legacy.exceptions", + "WebSocketProtocolError": ".legacy.exceptions", + # .legacy.protocol + "WebSocketCommonProtocol": ".legacy.protocol", + # .legacy.server + "WebSocketServer": ".legacy.server", + "WebSocketServerProtocol": ".legacy.server", + }, + ) diff --git a/.venv/lib/python3.14/site-packages/websockets/__main__.py b/.venv/lib/python3.14/site-packages/websockets/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..2f05ddc225577125ac018702cfe4de55a1aacd71 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/__main__.py @@ -0,0 +1,5 @@ +from .cli import main + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8206588bd53956f58ea46a7bc7834d71753e892d Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/__main__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/__main__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1fcd72ae9ea2451fef9e2afb9046d9a6f8335c02 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/__main__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/auth.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/auth.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a5679cde751da4af177af1dc4783264d87b316d Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/auth.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/cli.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/cli.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f6829191052c5d1adf341b818912818ae9525f3 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/cli.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/client.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/client.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0bef57c9575d3aa7af08279b9a1ec0a4c5f7697f Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/client.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/connection.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ba08cb2ad695acafb5f6851e082b527c761b9cd Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/connection.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/datastructures.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/datastructures.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b16f89778863f07075e29bf3824b6a4fa557402 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/datastructures.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/exceptions.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/exceptions.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4bbd51ce231dddcae91e6187b27605997cba8ee Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/exceptions.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/frames.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/frames.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..936aa6e2ecdea0b45ace5ccdd861e41fbf740af1 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/frames.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/headers.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/headers.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b37a13b127cb891585df2f0efbe4c9b69f2bcdf1 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/headers.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/http.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/http.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a3f5c747e250b41ea5bf27ba0de25f2858c1caf Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/http.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/http11.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/http11.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e316116da0431ad5963b3dbbbe8b84da1fc54dbc Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/http11.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/imports.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/imports.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ddf62afff62ad51d016ea08c845e2818f32a08ee Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/imports.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/protocol.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/protocol.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7653a6f17f3f9c0f7e11a3d1115325bb6c9fdd5f Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/protocol.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/proxy.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/proxy.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d263d3b3cf3aabd6f471815be70c5e912fdf4a8b Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/proxy.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/server.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/server.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38d0f2e1008749f397896f48737467ad3c82686c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/server.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/streams.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/streams.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c77287a258dc2b04990a3209bc8d06b78ed4f9fb Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/streams.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/typing.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/typing.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52f161527ccce86346bc5311fc2c64a5f33a5d5a Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/typing.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/uri.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/uri.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2bf7f4bcf4f5dded71d1c24f40eb423eb355aee6 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/uri.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/utils.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb96f700916e50b67de0af6deb841aeb3d5c4483 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/utils.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/__pycache__/version.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/__pycache__/version.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f3b577750ca7b88df6e8b09724789db91ffe35c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/__pycache__/version.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/__init__.py b/.venv/lib/python3.14/site-packages/websockets/asyncio/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8173da8777d626b72967de921efa4e18e5ea12f Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/async_timeout.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/async_timeout.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d63c881700dcc3564ec08f11ee7e51f8249d6f4d Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/async_timeout.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/client.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/client.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..910497e9399deb92a409c2b658555a596c626a77 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/client.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/compatibility.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/compatibility.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..281fcdf8c17446385e6b3d937b9a33bad9036d6e Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/compatibility.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/connection.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21e1678696f500b45484c7863a12679f70258421 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/connection.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/messages.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/messages.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11accad434b1e1950ef13308225078bfd7a4c125 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/messages.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/router.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/router.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e84b1a869a6c67c1e36d2541e00639648427d571 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/router.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/server.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/server.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bb29592dc1d7daacdfe0c84207a72981628db8c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/asyncio/__pycache__/server.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/async_timeout.py b/.venv/lib/python3.14/site-packages/websockets/asyncio/async_timeout.py new file mode 100644 index 0000000000000000000000000000000000000000..6ffa899695637829dd5d3c7b58c68683000fc35d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/asyncio/async_timeout.py @@ -0,0 +1,282 @@ +# From https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py +# Licensed under the Apache License (Apache-2.0) + +import asyncio +import enum +import sys +import warnings +from types import TracebackType +from typing import Optional, Type + + +if sys.version_info >= (3, 11): + from typing import final +else: + # From https://github.com/python/typing_extensions/blob/main/src/typing_extensions.py + # Licensed under the Python Software Foundation License (PSF-2.0) + + # @final exists in 3.8+, but we backport it for all versions + # before 3.11 to keep support for the __final__ attribute. + # See https://bugs.python.org/issue46342 + def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. The decorator + sets the ``__final__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + """ + try: + f.__final__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return f + + # End https://github.com/python/typing_extensions/blob/main/src/typing_extensions.py + + +if sys.version_info >= (3, 11): + + def _uncancel_task(task: "asyncio.Task[object]") -> None: + task.uncancel() + +else: + + def _uncancel_task(task: "asyncio.Task[object]") -> None: + pass + + +__version__ = "4.0.3" + + +__all__ = ("timeout", "timeout_at", "Timeout") + + +def timeout(delay: Optional[float]) -> "Timeout": + """timeout context manager. + + Useful in cases when you want to apply timeout logic around block + of code or in cases when asyncio.wait_for is not suitable. For example: + + >>> async with timeout(0.001): + ... async with aiohttp.get('https://github.com') as r: + ... await r.text() + + + delay - value in seconds or None to disable timeout logic + """ + loop = asyncio.get_running_loop() + if delay is not None: + deadline = loop.time() + delay # type: Optional[float] + else: + deadline = None + return Timeout(deadline, loop) + + +def timeout_at(deadline: Optional[float]) -> "Timeout": + """Schedule the timeout at absolute time. + + deadline argument points on the time in the same clock system + as loop.time(). + + Please note: it is not POSIX time but a time with + undefined starting base, e.g. the time of the system power on. + + >>> async with timeout_at(loop.time() + 10): + ... async with aiohttp.get('https://github.com') as r: + ... await r.text() + + + """ + loop = asyncio.get_running_loop() + return Timeout(deadline, loop) + + +class _State(enum.Enum): + INIT = "INIT" + ENTER = "ENTER" + TIMEOUT = "TIMEOUT" + EXIT = "EXIT" + + +@final +class Timeout: + # Internal class, please don't instantiate it directly + # Use timeout() and timeout_at() public factories instead. + # + # Implementation note: `async with timeout()` is preferred + # over `with timeout()`. + # While technically the Timeout class implementation + # doesn't need to be async at all, + # the `async with` statement explicitly points that + # the context manager should be used from async function context. + # + # This design allows to avoid many silly misusages. + # + # TimeoutError is raised immediately when scheduled + # if the deadline is passed. + # The purpose is to time out as soon as possible + # without waiting for the next await expression. + + __slots__ = ("_deadline", "_loop", "_state", "_timeout_handler", "_task") + + def __init__( + self, deadline: Optional[float], loop: asyncio.AbstractEventLoop + ) -> None: + self._loop = loop + self._state = _State.INIT + + self._task: Optional["asyncio.Task[object]"] = None + self._timeout_handler = None # type: Optional[asyncio.Handle] + if deadline is None: + self._deadline = None # type: Optional[float] + else: + self.update(deadline) + + def __enter__(self) -> "Timeout": + warnings.warn( + "with timeout() is deprecated, use async with timeout() instead", + DeprecationWarning, + stacklevel=2, + ) + self._do_enter() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + self._do_exit(exc_type) + return None + + async def __aenter__(self) -> "Timeout": + self._do_enter() + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + self._do_exit(exc_type) + return None + + @property + def expired(self) -> bool: + """Is timeout expired during execution?""" + return self._state == _State.TIMEOUT + + @property + def deadline(self) -> Optional[float]: + return self._deadline + + def reject(self) -> None: + """Reject scheduled timeout if any.""" + # cancel is maybe better name but + # task.cancel() raises CancelledError in asyncio world. + if self._state not in (_State.INIT, _State.ENTER): + raise RuntimeError(f"invalid state {self._state.value}") + self._reject() + + def _reject(self) -> None: + self._task = None + if self._timeout_handler is not None: + self._timeout_handler.cancel() + self._timeout_handler = None + + def shift(self, delay: float) -> None: + """Advance timeout on delay seconds. + + The delay can be negative. + + Raise RuntimeError if shift is called when deadline is not scheduled + """ + deadline = self._deadline + if deadline is None: + raise RuntimeError("cannot shift timeout if deadline is not scheduled") + self.update(deadline + delay) + + def update(self, deadline: float) -> None: + """Set deadline to absolute value. + + deadline argument points on the time in the same clock system + as loop.time(). + + If new deadline is in the past the timeout is raised immediately. + + Please note: it is not POSIX time but a time with + undefined starting base, e.g. the time of the system power on. + """ + if self._state == _State.EXIT: + raise RuntimeError("cannot reschedule after exit from context manager") + if self._state == _State.TIMEOUT: + raise RuntimeError("cannot reschedule expired timeout") + if self._timeout_handler is not None: + self._timeout_handler.cancel() + self._deadline = deadline + if self._state != _State.INIT: + self._reschedule() + + def _reschedule(self) -> None: + assert self._state == _State.ENTER + deadline = self._deadline + if deadline is None: + return + + now = self._loop.time() + if self._timeout_handler is not None: + self._timeout_handler.cancel() + + self._task = asyncio.current_task() + if deadline <= now: + self._timeout_handler = self._loop.call_soon(self._on_timeout) + else: + self._timeout_handler = self._loop.call_at(deadline, self._on_timeout) + + def _do_enter(self) -> None: + if self._state != _State.INIT: + raise RuntimeError(f"invalid state {self._state.value}") + self._state = _State.ENTER + self._reschedule() + + def _do_exit(self, exc_type: Optional[Type[BaseException]]) -> None: + if exc_type is asyncio.CancelledError and self._state == _State.TIMEOUT: + assert self._task is not None + _uncancel_task(self._task) + self._timeout_handler = None + self._task = None + raise asyncio.TimeoutError + # timeout has not expired + self._state = _State.EXIT + self._reject() + return None + + def _on_timeout(self) -> None: + assert self._task is not None + self._task.cancel() + self._state = _State.TIMEOUT + # drop the reference early + self._timeout_handler = None + + +# End https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/client.py b/.venv/lib/python3.14/site-packages/websockets/asyncio/client.py new file mode 100644 index 0000000000000000000000000000000000000000..05947f3a07e99e3b02fe3e5fe24be3a01bc58092 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/asyncio/client.py @@ -0,0 +1,804 @@ +from __future__ import annotations + +import asyncio +import logging +import os +import socket +import ssl as ssl_module +import traceback +import urllib.parse +from collections.abc import AsyncIterator, Generator, Sequence +from types import TracebackType +from typing import Any, Callable, Literal, cast + +from ..client import ClientProtocol, backoff +from ..datastructures import HeadersLike +from ..exceptions import ( + InvalidMessage, + InvalidProxyMessage, + InvalidProxyStatus, + InvalidStatus, + ProxyError, + SecurityError, +) +from ..extensions.base import ClientExtensionFactory +from ..extensions.permessage_deflate import enable_client_permessage_deflate +from ..headers import validate_subprotocols +from ..http11 import USER_AGENT, Response +from ..protocol import CONNECTING, Event +from ..proxy import Proxy, get_proxy, parse_proxy, prepare_connect_request +from ..streams import StreamReader +from ..typing import LoggerLike, Origin, Subprotocol +from ..uri import WebSocketURI, parse_uri +from .compatibility import TimeoutError, asyncio_timeout +from .connection import Connection + + +__all__ = ["connect", "unix_connect", "ClientConnection"] + +MAX_REDIRECTS = int(os.environ.get("WEBSOCKETS_MAX_REDIRECTS", "10")) + + +class ClientConnection(Connection): + """ + :mod:`asyncio` implementation of a WebSocket client connection. + + :class:`ClientConnection` provides :meth:`recv` and :meth:`send` coroutines + for receiving and sending messages. + + It supports asynchronous iteration to receive messages:: + + async for message in websocket: + await process(message) + + The iterator exits normally when the connection is closed with code + 1000 (OK) or 1001 (going away) or without a close code. It raises a + :exc:`~websockets.exceptions.ConnectionClosedError` when the connection is + closed with any other code. + + The ``ping_interval``, ``ping_timeout``, ``close_timeout``, ``max_queue``, + and ``write_limit`` arguments have the same meaning as in :func:`connect`. + + Args: + protocol: Sans-I/O connection. + + """ + + def __init__( + self, + protocol: ClientProtocol, + *, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + max_queue: int | None | tuple[int | None, int | None] = 16, + write_limit: int | tuple[int, int | None] = 2**15, + ) -> None: + self.protocol: ClientProtocol + super().__init__( + protocol, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_queue=max_queue, + write_limit=write_limit, + ) + self.response_rcvd: asyncio.Future[None] = self.loop.create_future() + + async def handshake( + self, + additional_headers: HeadersLike | None = None, + user_agent_header: str | None = USER_AGENT, + ) -> None: + """ + Perform the opening handshake. + + """ + async with self.send_context(expected_state=CONNECTING): + self.request = self.protocol.connect() + if additional_headers is not None: + self.request.headers.update(additional_headers) + if user_agent_header is not None: + self.request.headers.setdefault("User-Agent", user_agent_header) + self.protocol.send_request(self.request) + + await asyncio.wait( + [self.response_rcvd, self.connection_lost_waiter], + return_when=asyncio.FIRST_COMPLETED, + ) + + # self.protocol.handshake_exc is set when the connection is lost before + # receiving a response, when the response cannot be parsed, or when the + # response fails the handshake. + + if self.protocol.handshake_exc is not None: + raise self.protocol.handshake_exc + + def process_event(self, event: Event) -> None: + """ + Process one incoming event. + + """ + # First event - handshake response. + if self.response is None: + assert isinstance(event, Response) + self.response = event + self.response_rcvd.set_result(None) + # Later events - frames. + else: + super().process_event(event) + + +def process_exception(exc: Exception) -> Exception | None: + """ + Determine whether a connection error is retryable or fatal. + + When reconnecting automatically with ``async for ... in connect(...)``, if a + connection attempt fails, :func:`process_exception` is called to determine + whether to retry connecting or to raise the exception. + + This function defines the default behavior, which is to retry on: + + * :exc:`EOFError`, :exc:`OSError`, :exc:`asyncio.TimeoutError`: network + errors; + * :exc:`~websockets.exceptions.InvalidStatus` when the status code is 500, + 502, 503, or 504: server or proxy errors. + + All other exceptions are considered fatal. + + You can change this behavior with the ``process_exception`` argument of + :func:`connect`. + + Return :obj:`None` if the exception is retryable i.e. when the error could + be transient and trying to reconnect with the same parameters could succeed. + The exception will be logged at the ``INFO`` level. + + Return an exception, either ``exc`` or a new exception, if the exception is + fatal i.e. when trying to reconnect will most likely produce the same error. + That exception will be raised, breaking out of the retry loop. + + """ + # This catches python-socks' ProxyConnectionError and ProxyTimeoutError. + # Remove asyncio.TimeoutError when dropping Python < 3.11. + if isinstance(exc, (OSError, TimeoutError, asyncio.TimeoutError)): + return None + if isinstance(exc, InvalidMessage) and isinstance(exc.__cause__, EOFError): + return None + if isinstance(exc, InvalidStatus) and exc.response.status_code in [ + 500, # Internal Server Error + 502, # Bad Gateway + 503, # Service Unavailable + 504, # Gateway Timeout + ]: + return None + return exc + + +# This is spelled in lower case because it's exposed as a callable in the API. +class connect: + """ + Connect to the WebSocket server at ``uri``. + + This coroutine returns a :class:`ClientConnection` instance, which you can + use to send and receive messages. + + :func:`connect` may be used as an asynchronous context manager:: + + from websockets.asyncio.client import connect + + async with connect(...) as websocket: + ... + + The connection is closed automatically when exiting the context. + + :func:`connect` can be used as an infinite asynchronous iterator to + reconnect automatically on errors:: + + async for websocket in connect(...): + try: + ... + except websockets.exceptions.ConnectionClosed: + continue + + If the connection fails with a transient error, it is retried with + exponential backoff. If it fails with a fatal error, the exception is + raised, breaking out of the loop. + + The connection is closed automatically after each iteration of the loop. + + Args: + uri: URI of the WebSocket server. + origin: Value of the ``Origin`` header, for servers that require it. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + additional_headers (HeadersLike | None): Arbitrary HTTP headers to add + to the handshake request. + user_agent_header: Value of the ``User-Agent`` request header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. + Setting it to :obj:`None` removes the header. + proxy: If a proxy is configured, it is used by default. Set ``proxy`` + to :obj:`None` to disable the proxy or to the address of a proxy + to override the system configuration. See the :doc:`proxy docs + <../../topics/proxies>` for details. + process_exception: When reconnecting automatically, tell whether an + error is transient or fatal. The default behavior is defined by + :func:`process_exception`. Refer to its documentation for details. + open_timeout: Timeout for opening the connection in seconds. + :obj:`None` disables the timeout. + ping_interval: Interval between keepalive pings in seconds. + :obj:`None` disables keepalive. + ping_timeout: Timeout for keepalive pings in seconds. + :obj:`None` disables timeouts. + close_timeout: Timeout for closing the connection in seconds. + :obj:`None` disables the timeout. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. You may pass a ``(max_message_size, + max_fragment_size)`` tuple to set different limits for messages and + fragments when you expect long messages sent in short fragments. + max_queue: High-water mark of the buffer where frames are received. + It defaults to 16 frames. The low-water mark defaults to ``max_queue + // 4``. You may pass a ``(high, low)`` tuple to set the high-water + and low-water marks. If you want to disable flow control entirely, + you may set it to ``None``, although that's a bad idea. + write_limit: High-water mark of write buffer in bytes. It is passed to + :meth:`~asyncio.WriteTransport.set_write_buffer_limits`. It defaults + to 32 KiB. You may pass a ``(high, low)`` tuple to set the + high-water and low-water marks. + logger: Logger for this client. + It defaults to ``logging.getLogger("websockets.client")``. + See the :doc:`logging guide <../../topics/logging>` for details. + create_connection: Factory for the :class:`ClientConnection` managing + the connection. Set it to a wrapper or a subclass to customize + connection handling. + + Any other keyword arguments are passed to the event loop's + :meth:`~asyncio.loop.create_connection` method. + + For example: + + * You can set ``ssl`` to a :class:`~ssl.SSLContext` to enforce TLS settings. + When connecting to a ``wss://`` URI, if ``ssl`` isn't provided, a TLS + context is created with :func:`~ssl.create_default_context`. + + * You can set ``server_hostname`` to override the host name from ``uri`` in + the TLS handshake. + + * You can set ``host`` and ``port`` to connect to a different host and port + from those found in ``uri``. This only changes the destination of the TCP + connection. The host name from ``uri`` is still used in the TLS handshake + for secure connections and in the ``Host`` header. + + * You can set ``sock`` to provide a preexisting TCP socket. You may call + :func:`socket.create_connection` (not to be confused with the event loop's + :meth:`~asyncio.loop.create_connection` method) to create a suitable + client socket and customize it. + + When using a proxy: + + * Prefix keyword arguments with ``proxy_`` for configuring TLS between the + client and an HTTPS proxy: ``proxy_ssl``, ``proxy_server_hostname``, + ``proxy_ssl_handshake_timeout``, and ``proxy_ssl_shutdown_timeout``. + * Use the standard keyword arguments for configuring TLS between the proxy + and the WebSocket server: ``ssl``, ``server_hostname``, + ``ssl_handshake_timeout``, and ``ssl_shutdown_timeout``. + * Other keyword arguments are used only for connecting to the proxy. + + Raises: + InvalidURI: If ``uri`` isn't a valid WebSocket URI. + InvalidProxy: If ``proxy`` isn't a valid proxy. + OSError: If the TCP connection fails. + InvalidHandshake: If the opening handshake fails. + TimeoutError: If the opening handshake times out. + + """ + + def __init__( + self, + uri: str, + *, + # WebSocket + origin: Origin | None = None, + extensions: Sequence[ClientExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + compression: str | None = "deflate", + # HTTP + additional_headers: HeadersLike | None = None, + user_agent_header: str | None = USER_AGENT, + proxy: str | Literal[True] | None = True, + process_exception: Callable[[Exception], Exception | None] = process_exception, + # Timeouts + open_timeout: float | None = 10, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + # Limits + max_size: int | None | tuple[int | None, int | None] = 2**20, + max_queue: int | None | tuple[int | None, int | None] = 16, + write_limit: int | tuple[int, int | None] = 2**15, + # Logging + logger: LoggerLike | None = None, + # Escape hatch for advanced customization + create_connection: type[ClientConnection] | None = None, + # Other keyword arguments are passed to loop.create_connection + **kwargs: Any, + ) -> None: + self.uri = uri + + if subprotocols is not None: + validate_subprotocols(subprotocols) + + if compression == "deflate": + extensions = enable_client_permessage_deflate(extensions) + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + if logger is None: + logger = logging.getLogger("websockets.client") + + if create_connection is None: + create_connection = ClientConnection + + def protocol_factory(uri: WebSocketURI) -> ClientConnection: + # This is a protocol in the Sans-I/O implementation of websockets. + protocol = ClientProtocol( + uri, + origin=origin, + extensions=extensions, + subprotocols=subprotocols, + max_size=max_size, + logger=logger, + ) + # This is a connection in websockets and a protocol in asyncio. + connection = create_connection( + protocol, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_queue=max_queue, + write_limit=write_limit, + ) + return connection + + self.proxy = proxy + self.protocol_factory = protocol_factory + self.additional_headers = additional_headers + self.user_agent_header = user_agent_header + self.process_exception = process_exception + self.open_timeout = open_timeout + self.logger = logger + self.connection_kwargs = kwargs + + async def create_connection(self) -> ClientConnection: + """Create TCP or Unix connection.""" + loop = asyncio.get_running_loop() + kwargs = self.connection_kwargs.copy() + + ws_uri = parse_uri(self.uri) + + proxy = self.proxy + if kwargs.get("unix", False): + proxy = None + if kwargs.get("sock") is not None: + proxy = None + if proxy is True: + proxy = get_proxy(ws_uri) + + def factory() -> ClientConnection: + return self.protocol_factory(ws_uri) + + if ws_uri.secure: + kwargs.setdefault("ssl", True) + kwargs.setdefault("server_hostname", ws_uri.host) + if kwargs.get("ssl") is None: + raise ValueError("ssl=None is incompatible with a wss:// URI") + else: + if kwargs.get("ssl") is not None: + raise ValueError("ssl argument is incompatible with a ws:// URI") + + if kwargs.pop("unix", False): + _, connection = await loop.create_unix_connection(factory, **kwargs) + elif proxy is not None: + proxy_parsed = parse_proxy(proxy) + if proxy_parsed.scheme[:5] == "socks": + # Connect to the server through the proxy. + sock = await connect_socks_proxy( + proxy_parsed, + ws_uri, + local_addr=kwargs.pop("local_addr", None), + ) + # Initialize WebSocket connection via the proxy. + _, connection = await loop.create_connection( + factory, + sock=sock, + **kwargs, + ) + elif proxy_parsed.scheme[:4] == "http": + # Split keyword arguments between the proxy and the server. + all_kwargs, proxy_kwargs, kwargs = kwargs, {}, {} + for key, value in all_kwargs.items(): + if key.startswith("ssl") or key == "server_hostname": + kwargs[key] = value + elif key.startswith("proxy_"): + proxy_kwargs[key[6:]] = value + else: + proxy_kwargs[key] = value + # Validate the proxy_ssl argument. + if proxy_parsed.scheme == "https": + proxy_kwargs.setdefault("ssl", True) + if proxy_kwargs.get("ssl") is None: + raise ValueError( + "proxy_ssl=None is incompatible with an https:// proxy" + ) + else: + if proxy_kwargs.get("ssl") is not None: + raise ValueError( + "proxy_ssl argument is incompatible with an http:// proxy" + ) + # Connect to the server through the proxy. + transport = await connect_http_proxy( + proxy_parsed, + ws_uri, + user_agent_header=self.user_agent_header, + **proxy_kwargs, + ) + # Initialize WebSocket connection via the proxy. + connection = factory() + transport.set_protocol(connection) + ssl = kwargs.pop("ssl", None) + if ssl is True: + ssl = ssl_module.create_default_context() + if ssl is not None: + new_transport = await loop.start_tls( + transport, connection, ssl, **kwargs + ) + assert new_transport is not None # help mypy + transport = new_transport + connection.connection_made(transport) + else: + raise AssertionError("unsupported proxy") + else: + # Connect to the server directly. + if kwargs.get("sock") is None: + kwargs.setdefault("host", ws_uri.host) + kwargs.setdefault("port", ws_uri.port) + # Initialize WebSocket connection. + _, connection = await loop.create_connection(factory, **kwargs) + return connection + + def process_redirect(self, exc: Exception) -> Exception | str: + """ + Determine whether a connection error is a redirect that can be followed. + + Return the new URI if it's a valid redirect. Else, return an exception. + + """ + if not ( + isinstance(exc, InvalidStatus) + and exc.response.status_code + in [ + 300, # Multiple Choices + 301, # Moved Permanently + 302, # Found + 303, # See Other + 307, # Temporary Redirect + 308, # Permanent Redirect + ] + and "Location" in exc.response.headers + ): + return exc + + old_ws_uri = parse_uri(self.uri) + new_uri = urllib.parse.urljoin(self.uri, exc.response.headers["Location"]) + new_ws_uri = parse_uri(new_uri) + + # If connect() received a socket, it is closed and cannot be reused. + if self.connection_kwargs.get("sock") is not None: + return ValueError( + f"cannot follow redirect to {new_uri} with a preexisting socket" + ) + + # TLS downgrade is forbidden. + if old_ws_uri.secure and not new_ws_uri.secure: + return SecurityError(f"cannot follow redirect to non-secure URI {new_uri}") + + # Apply restrictions to cross-origin redirects. + if ( + old_ws_uri.secure != new_ws_uri.secure + or old_ws_uri.host != new_ws_uri.host + or old_ws_uri.port != new_ws_uri.port + ): + # Cross-origin redirects on Unix sockets don't quite make sense. + if self.connection_kwargs.get("unix", False): + return ValueError( + f"cannot follow cross-origin redirect to {new_uri} " + f"with a Unix socket" + ) + + # Cross-origin redirects when host and port are overridden are ill-defined. + if ( + self.connection_kwargs.get("host") is not None + or self.connection_kwargs.get("port") is not None + ): + return ValueError( + f"cannot follow cross-origin redirect to {new_uri} " + f"with an explicit host or port" + ) + + return new_uri + + # ... = await connect(...) + + def __await__(self) -> Generator[Any, None, ClientConnection]: + # Create a suitable iterator by calling __await__ on a coroutine. + return self.__await_impl__().__await__() + + async def __await_impl__(self) -> ClientConnection: + try: + async with asyncio_timeout(self.open_timeout): + for _ in range(MAX_REDIRECTS): + self.connection = await self.create_connection() + try: + await self.connection.handshake( + self.additional_headers, + self.user_agent_header, + ) + except asyncio.CancelledError: + self.connection.transport.abort() + raise + except Exception as exc: + # Always close the connection even though keep-alive is + # the default in HTTP/1.1 because create_connection ties + # opening the network connection with initializing the + # protocol. In the current design of connect(), there is + # no easy way to reuse the network connection that works + # in every case nor to reinitialize the protocol. + self.connection.transport.abort() + + uri_or_exc = self.process_redirect(exc) + # Response is a valid redirect; follow it. + if isinstance(uri_or_exc, str): + self.uri = uri_or_exc + continue + # Response isn't a valid redirect; raise the exception. + if uri_or_exc is exc: + raise + else: + raise uri_or_exc from exc + + else: + self.connection.start_keepalive() + return self.connection + else: + raise SecurityError(f"more than {MAX_REDIRECTS} redirects") + + except TimeoutError as exc: + # Re-raise exception with an informative error message. + raise TimeoutError("timed out during opening handshake") from exc + + # ... = yield from connect(...) - remove when dropping Python < 3.11 + + __iter__ = __await__ + + # async with connect(...) as ...: ... + + async def __aenter__(self) -> ClientConnection: + return await self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self.connection.close() + + # async for ... in connect(...): + + async def __aiter__(self) -> AsyncIterator[ClientConnection]: + delays: Generator[float] | None = None + while True: + try: + async with self as protocol: + yield protocol + except Exception as exc: + # Determine whether the exception is retryable or fatal. + # The API of process_exception is "return an exception or None"; + # "raise an exception" is also supported because it's a frequent + # mistake. It isn't documented in order to keep the API simple. + try: + new_exc = self.process_exception(exc) + except Exception as raised_exc: + new_exc = raised_exc + + # The connection failed with a fatal error. + # Raise the exception and exit the loop. + if new_exc is exc: + raise + if new_exc is not None: + raise new_exc from exc + + # The connection failed with a retryable error. + # Start or continue backoff and reconnect. + if delays is None: + delays = backoff() + delay = next(delays) + self.logger.info( + "connect failed; reconnecting in %.1f seconds: %s", + delay, + traceback.format_exception_only(exc)[0].strip(), + ) + await asyncio.sleep(delay) + continue + + else: + # The connection succeeded. Reset backoff. + delays = None + + +def unix_connect( + path: str | None = None, + uri: str | None = None, + **kwargs: Any, +) -> connect: + """ + Connect to a WebSocket server listening on a Unix socket. + + This function accepts the same keyword arguments as :func:`connect`. + + It's only available on Unix. + + It's mainly useful for debugging servers listening on Unix sockets. + + Args: + path: File system path to the Unix socket. + uri: URI of the WebSocket server. ``uri`` defaults to + ``ws://localhost/`` or, when a ``ssl`` argument is provided, to + ``wss://localhost/``. + + """ + if uri is None: + if kwargs.get("ssl") is None: + uri = "ws://localhost/" + else: + uri = "wss://localhost/" + return connect(uri=uri, unix=True, path=path, **kwargs) + + +try: + from python_socks import ProxyType + from python_socks.async_.asyncio import Proxy as SocksProxy + +except ImportError: + + async def connect_socks_proxy( + proxy: Proxy, + ws_uri: WebSocketURI, + **kwargs: Any, + ) -> socket.socket: + raise ImportError("connecting through a SOCKS proxy requires python-socks") + +else: + SOCKS_PROXY_TYPES = { + "socks5h": ProxyType.SOCKS5, + "socks5": ProxyType.SOCKS5, + "socks4a": ProxyType.SOCKS4, + "socks4": ProxyType.SOCKS4, + } + + SOCKS_PROXY_RDNS = { + "socks5h": True, + "socks5": False, + "socks4a": True, + "socks4": False, + } + + async def connect_socks_proxy( + proxy: Proxy, + ws_uri: WebSocketURI, + **kwargs: Any, + ) -> socket.socket: + """Connect via a SOCKS proxy and return the socket.""" + socks_proxy = SocksProxy( + SOCKS_PROXY_TYPES[proxy.scheme], + proxy.host, + proxy.port, + proxy.username, + proxy.password, + SOCKS_PROXY_RDNS[proxy.scheme], + ) + # connect() is documented to raise OSError. + # socks_proxy.connect() doesn't raise TimeoutError; it gets canceled. + # Wrap other exceptions in ProxyError, a subclass of InvalidHandshake. + try: + return await socks_proxy.connect(ws_uri.host, ws_uri.port, **kwargs) + except OSError: + raise + except Exception as exc: + raise ProxyError("failed to connect to SOCKS proxy") from exc + + +class HTTPProxyConnection(asyncio.Protocol): + def __init__( + self, + ws_uri: WebSocketURI, + proxy: Proxy, + user_agent_header: str | None = None, + ): + self.ws_uri = ws_uri + self.proxy = proxy + self.user_agent_header = user_agent_header + + self.reader = StreamReader() + self.parser = Response.parse( + self.reader.read_line, + self.reader.read_exact, + self.reader.read_to_eof, + proxy=True, + ) + + loop = asyncio.get_running_loop() + self.response: asyncio.Future[Response] = loop.create_future() + + def run_parser(self) -> None: + try: + next(self.parser) + except StopIteration as exc: + response = exc.value + if 200 <= response.status_code < 300: + self.response.set_result(response) + else: + self.response.set_exception(InvalidProxyStatus(response)) + except Exception as exc: + proxy_exc = InvalidProxyMessage( + "did not receive a valid HTTP response from proxy" + ) + proxy_exc.__cause__ = exc + self.response.set_exception(proxy_exc) + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + transport = cast(asyncio.Transport, transport) + self.transport = transport + self.transport.write( + prepare_connect_request(self.proxy, self.ws_uri, self.user_agent_header) + ) + + def data_received(self, data: bytes) -> None: + self.reader.feed_data(data) + self.run_parser() + + def eof_received(self) -> None: + self.reader.feed_eof() + self.run_parser() + + def connection_lost(self, exc: Exception | None) -> None: + self.reader.feed_eof() + if exc is not None: + self.response.set_exception(exc) + + +async def connect_http_proxy( + proxy: Proxy, + ws_uri: WebSocketURI, + user_agent_header: str | None = None, + **kwargs: Any, +) -> asyncio.Transport: + transport, protocol = await asyncio.get_running_loop().create_connection( + lambda: HTTPProxyConnection(ws_uri, proxy, user_agent_header), + proxy.host, + proxy.port, + **kwargs, + ) + + try: + # This raises exceptions if the connection to the proxy fails. + await protocol.response + except Exception: + transport.close() + raise + + return transport diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/compatibility.py b/.venv/lib/python3.14/site-packages/websockets/asyncio/compatibility.py new file mode 100644 index 0000000000000000000000000000000000000000..e17000069d530bdde8de5194e0d8257a5c5d1770 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/asyncio/compatibility.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +import sys + + +__all__ = ["TimeoutError", "aiter", "anext", "asyncio_timeout", "asyncio_timeout_at"] + + +if sys.version_info[:2] >= (3, 11): + TimeoutError = TimeoutError + aiter = aiter + anext = anext + from asyncio import ( + timeout as asyncio_timeout, # noqa: F401 + timeout_at as asyncio_timeout_at, # noqa: F401 + ) + +else: # Python < 3.11 + from asyncio import TimeoutError + + def aiter(async_iterable): + return type(async_iterable).__aiter__(async_iterable) + + async def anext(async_iterator): + return await type(async_iterator).__anext__(async_iterator) + + from .async_timeout import ( + timeout as asyncio_timeout, # noqa: F401 + timeout_at as asyncio_timeout_at, # noqa: F401 + ) diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/connection.py b/.venv/lib/python3.14/site-packages/websockets/asyncio/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..205a2be50871cbc2cb85144421587a57a58bc725 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/asyncio/connection.py @@ -0,0 +1,1247 @@ +from __future__ import annotations + +import asyncio +import collections +import contextlib +import logging +import random +import struct +import sys +import traceback +import uuid +from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Iterable, Mapping +from types import TracebackType +from typing import Any, Literal, cast, overload + +from ..exceptions import ( + ConcurrencyError, + ConnectionClosed, + ConnectionClosedOK, + ProtocolError, +) +from ..frames import DATA_OPCODES, CloseCode, Frame, Opcode +from ..http11 import Request, Response +from ..protocol import CLOSED, OPEN, Event, Protocol, State +from ..typing import BytesLike, Data, DataLike, LoggerLike, Subprotocol +from .compatibility import ( + TimeoutError, + aiter, + anext, + asyncio_timeout, + asyncio_timeout_at, +) +from .messages import Assembler + + +__all__ = ["Connection"] + + +class Connection(asyncio.Protocol): + """ + :mod:`asyncio` implementation of a WebSocket connection. + + :class:`Connection` provides APIs shared between WebSocket servers and + clients. + + You shouldn't use it directly. Instead, use + :class:`~websockets.asyncio.client.ClientConnection` or + :class:`~websockets.asyncio.server.ServerConnection`. + + """ + + def __init__( + self, + protocol: Protocol, + *, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + max_queue: int | None | tuple[int | None, int | None] = 16, + write_limit: int | tuple[int, int | None] = 2**15, + ) -> None: + self.protocol = protocol + self.ping_interval = ping_interval + self.ping_timeout = ping_timeout + self.close_timeout = close_timeout + if isinstance(max_queue, int) or max_queue is None: + self.max_queue_high, self.max_queue_low = max_queue, None + else: + self.max_queue_high, self.max_queue_low = max_queue + if isinstance(write_limit, int): + self.write_limit_high, self.write_limit_low = write_limit, None + else: + self.write_limit_high, self.write_limit_low = write_limit + + # Inject reference to this instance in the protocol's logger. + self.protocol.logger = logging.LoggerAdapter( + self.protocol.logger, + {"websocket": self}, + ) + + # Copy attributes from the protocol for convenience. + self.id: uuid.UUID = self.protocol.id + """Unique identifier of the connection. Useful in logs.""" + self.logger: LoggerLike = self.protocol.logger + """Logger for this connection.""" + self.debug = self.protocol.debug + + # HTTP handshake request and response. + self.request: Request | None = None + """Opening handshake request.""" + self.response: Response | None = None + """Opening handshake response.""" + + # Event loop running this connection. + self.loop = asyncio.get_running_loop() + + # Assembler turning frames into messages and serializing reads. + self.recv_messages: Assembler # initialized in connection_made + + # Deadline for the closing handshake. + self.close_deadline: float | None = None + + # Whether we are busy sending a fragmented message. + self.send_in_progress: asyncio.Future[None] | None = None + + # Mapping of ping IDs to pong waiters, in chronological order. + self.pending_pings: dict[bytes, tuple[asyncio.Future[float], float]] = {} + + self.latency: float = 0.0 + """ + Latency of the connection, in seconds. + + Latency is defined as the round-trip time of the connection. It is + measured by sending a Ping frame and waiting for a matching Pong frame. + Before the first measurement, :attr:`latency` is ``0.0``. + + By default, websockets enables a :ref:`keepalive ` mechanism + that sends Ping frames automatically at regular intervals. You can also + send Ping frames and measure latency with :meth:`ping`. + """ + + # Task that sends keepalive pings. None when ping_interval is None. + self.keepalive_task: asyncio.Task[None] | None = None + + # Exception raised while reading from the connection, to be chained to + # ConnectionClosed in order to show why the TCP connection dropped. + self.recv_exc: BaseException | None = None + + # Completed when the TCP connection is closed and the WebSocket + # connection state becomes CLOSED. + self.connection_lost_waiter: asyncio.Future[None] = self.loop.create_future() + + # Adapted from asyncio.FlowControlMixin. + self.paused: bool = False + self.drain_waiters: collections.deque[asyncio.Future[None]] = ( + collections.deque() + ) + + # Public attributes + + @property + def local_address(self) -> Any: + """ + Local address of the connection. + + For IPv4 connections, this is a ``(host, port)`` tuple. + + The format of the address depends on the address family. + See :meth:`~socket.socket.getsockname`. + + """ + return self.transport.get_extra_info("sockname") + + @property + def remote_address(self) -> Any: + """ + Remote address of the connection. + + For IPv4 connections, this is a ``(host, port)`` tuple. + + The format of the address depends on the address family. + See :meth:`~socket.socket.getpeername`. + + """ + return self.transport.get_extra_info("peername") + + @property + def state(self) -> State: + """ + State of the WebSocket connection, defined in :rfc:`6455`. + + This attribute is provided for completeness. Typical applications + shouldn't check its value. Instead, they should call :meth:`~recv` or + :meth:`send` and handle :exc:`~websockets.exceptions.ConnectionClosed` + exceptions. + + """ + return self.protocol.state + + @property + def subprotocol(self) -> Subprotocol | None: + """ + Subprotocol negotiated during the opening handshake. + + :obj:`None` if no subprotocol was negotiated. + + """ + return self.protocol.subprotocol + + @property + def close_code(self) -> int | None: + """ + State of the WebSocket connection, defined in :rfc:`6455`. + + This attribute is provided for completeness. Typical applications + shouldn't check its value. Instead, they should inspect attributes + of :exc:`~websockets.exceptions.ConnectionClosed` exceptions. + + """ + return self.protocol.close_code + + @property + def close_reason(self) -> str | None: + """ + State of the WebSocket connection, defined in :rfc:`6455`. + + This attribute is provided for completeness. Typical applications + shouldn't check its value. Instead, they should inspect attributes + of :exc:`~websockets.exceptions.ConnectionClosed` exceptions. + + """ + return self.protocol.close_reason + + # Public methods + + async def __aenter__(self) -> Connection: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + if exc_type is None: + await self.close() + else: + await self.close(CloseCode.INTERNAL_ERROR) + + async def __aiter__(self) -> AsyncIterator[Data]: + """ + Iterate on incoming messages. + + The iterator calls :meth:`recv` and yields messages asynchronously in an + infinite loop. + + It exits when the connection is closed normally. It raises a + :exc:`~websockets.exceptions.ConnectionClosedError` exception after a + protocol error or a network failure. + + """ + try: + while True: + yield await self.recv() + except ConnectionClosedOK: + return + + @overload + async def recv(self, decode: Literal[True]) -> str: ... + + @overload + async def recv(self, decode: Literal[False]) -> bytes: ... + + @overload + async def recv(self, decode: bool | None = None) -> Data: ... + + async def recv(self, decode: bool | None = None) -> Data: + """ + Receive the next message. + + When the connection is closed, :meth:`recv` raises + :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it raises + :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal closure + and :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol + error or a network failure. This is how you detect the end of the + message stream. + + Canceling :meth:`recv` is safe. There's no risk of losing data. The next + invocation of :meth:`recv` will return the next message. + + This makes it possible to enforce a timeout by wrapping :meth:`recv` in + :func:`~asyncio.timeout` or :func:`~asyncio.wait_for`. + + When the message is fragmented, :meth:`recv` waits until all fragments + are received, reassembles them, and returns the whole message. + + Args: + decode: Set this flag to override the default behavior of returning + :class:`str` or :class:`bytes`. See below for details. + + Returns: + A string (:class:`str`) for a Text_ frame or a bytestring + (:class:`bytes`) for a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + You may override this behavior with the ``decode`` argument: + + * Set ``decode=False`` to disable UTF-8 decoding of Text_ frames and + return a bytestring (:class:`bytes`). This improves performance + when decoding isn't needed, for example if the message contains + JSON and you're using a JSON library that expects a bytestring. + * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames and + return strings (:class:`str`). This may be useful for servers that + send binary frames instead of text frames. + + Raises: + ConnectionClosed: When the connection is closed. + ConcurrencyError: If two coroutines call :meth:`recv` or + :meth:`recv_streaming` concurrently. + + """ + try: + return await self.recv_messages.get(decode) + except EOFError: + pass + # fallthrough + except ConcurrencyError: + raise ConcurrencyError( + "cannot call recv while another coroutine " + "is already running recv or recv_streaming" + ) from None + except UnicodeDecodeError as exc: + async with self.send_context(): + self.protocol.fail( + CloseCode.INVALID_DATA, + f"{exc.reason} at position {exc.start}", + ) + # fallthrough + + # Wait for the protocol state to be CLOSED before accessing close_exc. + await asyncio.shield(self.connection_lost_waiter) + raise self.protocol.close_exc from self.recv_exc + + @overload + def recv_streaming(self, decode: Literal[True]) -> AsyncIterator[str]: ... + + @overload + def recv_streaming(self, decode: Literal[False]) -> AsyncIterator[bytes]: ... + + @overload + def recv_streaming(self, decode: bool | None = None) -> AsyncIterator[Data]: ... + + async def recv_streaming(self, decode: bool | None = None) -> AsyncIterator[Data]: + """ + Receive the next message frame by frame. + + This method is designed for receiving fragmented messages. It returns an + asynchronous iterator that yields each fragment as it is received. This + iterator must be fully consumed. Else, future calls to :meth:`recv` or + :meth:`recv_streaming` will raise + :exc:`~websockets.exceptions.ConcurrencyError`, making the connection + unusable. + + :meth:`recv_streaming` raises the same exceptions as :meth:`recv`. + + Canceling :meth:`recv_streaming` before receiving the first frame is + safe. Canceling it after receiving one or more frames leaves the + iterator in a partially consumed state, making the connection unusable. + Instead, you should close the connection with :meth:`close`. + + Args: + decode: Set this flag to override the default behavior of returning + :class:`str` or :class:`bytes`. See below for details. + + Returns: + An iterator of strings (:class:`str`) for a Text_ frame or + bytestrings (:class:`bytes`) for a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + You may override this behavior with the ``decode`` argument: + + * Set ``decode=False`` to disable UTF-8 decoding of Text_ frames and + yield bytestrings (:class:`bytes`). This improves performance + when decoding isn't needed. + * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames and + yield strings (:class:`str`). This may be useful for servers that + send binary frames instead of text frames. + + Raises: + ConnectionClosed: When the connection is closed. + ConcurrencyError: If two coroutines call :meth:`recv` or + :meth:`recv_streaming` concurrently. + + """ + try: + async for frame in self.recv_messages.get_iter(decode): + yield frame + return + except EOFError: + pass + # fallthrough + except ConcurrencyError: + raise ConcurrencyError( + "cannot call recv_streaming while another coroutine " + "is already running recv or recv_streaming" + ) from None + except UnicodeDecodeError as exc: + async with self.send_context(): + self.protocol.fail( + CloseCode.INVALID_DATA, + f"{exc.reason} at position {exc.start}", + ) + # fallthrough + + # Wait for the protocol state to be CLOSED before accessing close_exc. + await asyncio.shield(self.connection_lost_waiter) + raise self.protocol.close_exc from self.recv_exc + + async def send( + self, + message: DataLike | Iterable[DataLike] | AsyncIterable[DataLike], + text: bool | None = None, + ) -> None: + """ + Send a message. + + A string (:class:`str`) is sent as a Text_ frame. A bytestring or + bytes-like object (:class:`bytes`, :class:`bytearray`, or + :class:`memoryview`) is sent as a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + You may override this behavior with the ``text`` argument: + + * Set ``text=True`` to send an UTF-8 bytestring or bytes-like object + (:class:`bytes`, :class:`bytearray`, or :class:`memoryview`) in a + Text_ frame. This improves performance when the message is already + UTF-8 encoded, for example if the message contains JSON and you're + using a JSON library that produces a bytestring. + * Set ``text=False`` to send a string (:class:`str`) in a Binary_ + frame. This may be useful for servers that expect binary frames + instead of text frames. + + :meth:`send` also accepts an iterable or asynchronous iterable of + strings, bytestrings, or bytes-like objects to enable fragmentation_. + Each item is treated as a message fragment and sent in its own frame. + All items must be of the same type, or else :meth:`send` will raise a + :exc:`TypeError` and the connection will be closed. + + .. _fragmentation: https://datatracker.ietf.org/doc/html/rfc6455#section-5.4 + + :meth:`send` rejects dict-like objects because this is often an error. + (If you really want to send the keys of a dict-like object as fragments, + call its :meth:`~dict.keys` method and pass the result to :meth:`send`.) + + Canceling :meth:`send` is discouraged. Instead, you should close the + connection with :meth:`close`. Indeed, there are only two situations + where :meth:`send` may yield control to the event loop and then get + canceled; in both cases, :meth:`close` has the same effect and the + effect is more obvious: + + 1. The write buffer is full. If you don't want to wait until enough + data is sent, your only alternative is to close the connection. + :meth:`close` will likely time out then abort the TCP connection. + 2. ``message`` is an asynchronous iterator that yields control. + Stopping in the middle of a fragmented message will cause a + protocol error and the connection will be closed. + + When the connection is closed, :meth:`send` raises + :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it + raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal + connection closure and + :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol + error or a network failure. + + Args: + message: Message to send. + + Raises: + ConnectionClosed: When the connection is closed. + TypeError: If ``message`` doesn't have a supported type. + + """ + # While sending a fragmented message, prevent sending other messages + # until all fragments are sent. + while self.send_in_progress is not None: + await asyncio.shield(self.send_in_progress) + + # Unfragmented message -- this case must be handled first because + # strings and bytes-like objects are iterable. + + if isinstance(message, str): + async with self.send_context(): + if text is False: + self.protocol.send_binary(message.encode()) + else: + self.protocol.send_text(message.encode()) + + elif isinstance(message, BytesLike): + async with self.send_context(): + if text is True: + self.protocol.send_text(message) + else: + self.protocol.send_binary(message) + + # Catch a common mistake -- passing a dict to send(). + + elif isinstance(message, Mapping): + raise TypeError("data is a dict-like object") + + # Fragmented message -- regular iterator. + + elif isinstance(message, Iterable): + chunks = iter(message) + try: + chunk = next(chunks) + except StopIteration: + return + + assert self.send_in_progress is None + self.send_in_progress = self.loop.create_future() + try: + # First fragment. + if isinstance(chunk, str): + async with self.send_context(): + if text is False: + self.protocol.send_binary(chunk.encode(), fin=False) + else: + self.protocol.send_text(chunk.encode(), fin=False) + encode = True + elif isinstance(chunk, BytesLike): + async with self.send_context(): + if text is True: + self.protocol.send_text(chunk, fin=False) + else: + self.protocol.send_binary(chunk, fin=False) + encode = False + else: + raise TypeError("iterable must contain bytes or str") + + # Other fragments + for chunk in chunks: + if isinstance(chunk, str) and encode: + async with self.send_context(): + self.protocol.send_continuation(chunk.encode(), fin=False) + elif isinstance(chunk, BytesLike) and not encode: + async with self.send_context(): + self.protocol.send_continuation(chunk, fin=False) + else: + raise TypeError("iterable must contain uniform types") + + # Final fragment. + async with self.send_context(): + self.protocol.send_continuation(b"", fin=True) + + except Exception: + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + async with self.send_context(): + self.protocol.fail( + CloseCode.INTERNAL_ERROR, + "error in fragmented message", + ) + raise + + finally: + self.send_in_progress.set_result(None) + self.send_in_progress = None + + # Fragmented message -- async iterator. + + elif isinstance(message, AsyncIterable): + achunks = aiter(message) + try: + chunk = await anext(achunks) + except StopAsyncIteration: + return + + assert self.send_in_progress is None + self.send_in_progress = self.loop.create_future() + try: + # First fragment. + if isinstance(chunk, str): + if text is False: + async with self.send_context(): + self.protocol.send_binary(chunk.encode(), fin=False) + else: + async with self.send_context(): + self.protocol.send_text(chunk.encode(), fin=False) + encode = True + elif isinstance(chunk, BytesLike): + if text is True: + async with self.send_context(): + self.protocol.send_text(chunk, fin=False) + else: + async with self.send_context(): + self.protocol.send_binary(chunk, fin=False) + encode = False + else: + raise TypeError("async iterable must contain bytes or str") + + # Other fragments + async for chunk in achunks: + if isinstance(chunk, str) and encode: + async with self.send_context(): + self.protocol.send_continuation(chunk.encode(), fin=False) + elif isinstance(chunk, BytesLike) and not encode: + async with self.send_context(): + self.protocol.send_continuation(chunk, fin=False) + else: + raise TypeError("async iterable must contain uniform types") + + # Final fragment. + async with self.send_context(): + self.protocol.send_continuation(b"", fin=True) + + except Exception: + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + async with self.send_context(): + self.protocol.fail( + CloseCode.INTERNAL_ERROR, + "error in fragmented message", + ) + raise + + finally: + self.send_in_progress.set_result(None) + self.send_in_progress = None + + else: + raise TypeError("data must be str, bytes, iterable, or async iterable") + + async def close( + self, + code: CloseCode | int = CloseCode.NORMAL_CLOSURE, + reason: str = "", + ) -> None: + """ + Perform the closing handshake. + + :meth:`close` waits for the other end to complete the handshake and + for the TCP connection to terminate. + + :meth:`close` is idempotent: it doesn't do anything once the + connection is closed. + + Args: + code: WebSocket close code. + reason: WebSocket close reason. + + """ + try: + # The context manager takes care of waiting for the TCP connection + # to terminate after calling a method that sends a close frame. + async with self.send_context(): + if self.send_in_progress is not None: + self.protocol.fail( + CloseCode.INTERNAL_ERROR, + "close during fragmented message", + ) + else: + self.protocol.send_close(code, reason) + except ConnectionClosed: + # Ignore ConnectionClosed exceptions raised from send_context(). + # They mean that the connection is closed, which was the goal. + pass + + async def wait_closed(self) -> None: + """ + Wait until the connection is closed. + + :meth:`wait_closed` waits for the closing handshake to complete and for + the TCP connection to terminate. + + """ + await asyncio.shield(self.connection_lost_waiter) + + async def ping(self, data: DataLike | None = None) -> Awaitable[float]: + """ + Send a Ping_. + + .. _Ping: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2 + + A ping may serve as a keepalive or as a check that the remote endpoint + received all messages up to this point + + Args: + data: Payload of the ping. A :class:`str` will be encoded to UTF-8. + If ``data`` is :obj:`None`, the payload is four random bytes. + + Returns: + A future that will be completed when the corresponding pong is + received. You can ignore it if you don't intend to wait. The result + of the future is the latency of the connection in seconds. + + :: + + pong_received = await ws.ping() + # only if you want to wait for the corresponding pong + latency = await pong_received + + Raises: + ConnectionClosed: When the connection is closed. + ConcurrencyError: If another ping was sent with the same data and + the corresponding pong wasn't received yet. + + """ + if isinstance(data, BytesLike): + data = bytes(data) + elif isinstance(data, str): + data = data.encode() + elif data is not None: + raise TypeError("data must be str or bytes-like") + + async with self.send_context(): + # Protect against duplicates if a payload is explicitly set. + if data in self.pending_pings: + raise ConcurrencyError("already waiting for a pong with the same data") + + # Generate a unique random payload otherwise. + while data is None or data in self.pending_pings: + data = struct.pack("!I", random.getrandbits(32)) + + pong_received = self.loop.create_future() + ping_timestamp = self.loop.time() + # The event loop's default clock is time.monotonic(). Its resolution + # is a bit low on Windows (~16ms). This is improved in Python 3.13. + self.pending_pings[data] = (pong_received, ping_timestamp) + self.protocol.send_ping(data) + return pong_received + + async def pong(self, data: DataLike = b"") -> None: + """ + Send a Pong_. + + .. _Pong: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3 + + An unsolicited pong may serve as a unidirectional heartbeat. + + Args: + data: Payload of the pong. A :class:`str` will be encoded to UTF-8. + + Raises: + ConnectionClosed: When the connection is closed. + + """ + if isinstance(data, BytesLike): + data = bytes(data) + elif isinstance(data, str): + data = data.encode() + else: + raise TypeError("data must be str or bytes-like") + + async with self.send_context(): + self.protocol.send_pong(data) + + # Private methods + + def process_event(self, event: Event) -> None: + """ + Process one incoming event. + + This method is overridden in subclasses to handle the handshake. + + """ + assert isinstance(event, Frame) + if event.opcode in DATA_OPCODES: + self.recv_messages.put(event) + + if event.opcode is Opcode.PONG: + self.acknowledge_pings(bytes(event.data)) + + def acknowledge_pings(self, data: bytes) -> None: + """ + Acknowledge pings when receiving a pong. + + """ + # Ignore unsolicited pong. + if data not in self.pending_pings: + return + + pong_timestamp = self.loop.time() + + # Sending a pong for only the most recent ping is legal. + # Acknowledge all previous pings too in that case. + ping_id = None + ping_ids = [] + for ping_id, (pong_received, ping_timestamp) in self.pending_pings.items(): + ping_ids.append(ping_id) + latency = pong_timestamp - ping_timestamp + if not pong_received.done(): + pong_received.set_result(latency) + if ping_id == data: + self.latency = latency + break + else: + raise AssertionError("solicited pong not found in pings") + + # Remove acknowledged pings from self.pending_pings. + for ping_id in ping_ids: + del self.pending_pings[ping_id] + + def terminate_pending_pings(self) -> None: + """ + Raise ConnectionClosed in pending pings when the connection is closed. + + """ + assert self.protocol.state is CLOSED + exc = self.protocol.close_exc + + for pong_received, _ping_timestamp in self.pending_pings.values(): + if not pong_received.done(): + pong_received.set_exception(exc) + # If the exception is never retrieved, it will be logged when ping + # is garbage-collected. This is confusing for users. + # Given that ping is done (with an exception), canceling it does + # nothing, but it prevents logging the exception. + pong_received.cancel() + + self.pending_pings.clear() + + async def keepalive(self) -> None: + """ + Send a Ping frame and wait for a Pong frame at regular intervals. + + """ + assert self.ping_interval is not None + latency = 0.0 + try: + while True: + # If self.ping_timeout > latency > self.ping_interval, + # pings will be sent immediately after receiving pongs. + # The period will be longer than self.ping_interval. + await asyncio.sleep(self.ping_interval - latency) + + # This cannot raise ConnectionClosed when the connection is + # closing because ping(), via send_context(), waits for the + # connection to be closed before raising ConnectionClosed. + # However, connection_lost() cancels keepalive_task before + # it gets a chance to resume excuting. + pong_received = await self.ping() + if self.debug: + self.logger.debug("% sent keepalive ping") + + if self.ping_timeout is not None: + try: + async with asyncio_timeout(self.ping_timeout): + # connection_lost cancels keepalive immediately + # after setting a ConnectionClosed exception on + # pong_received. A CancelledError is raised here, + # not a ConnectionClosed exception. + latency = await pong_received + if self.debug: + self.logger.debug("% received keepalive pong") + except asyncio.TimeoutError: + if self.debug: + self.logger.debug("- timed out waiting for keepalive pong") + async with self.send_context(): + self.protocol.fail( + CloseCode.INTERNAL_ERROR, + "keepalive ping timeout", + ) + raise AssertionError( + "send_context() should wait for connection_lost(), " + "which cancels keepalive()" + ) + except Exception: + self.logger.error("keepalive ping failed", exc_info=True) + + def start_keepalive(self) -> None: + """ + Run :meth:`keepalive` in a task, unless keepalive is disabled. + + """ + if self.ping_interval is not None: + self.keepalive_task = self.loop.create_task(self.keepalive()) + + @contextlib.asynccontextmanager + async def send_context( + self, + *, + expected_state: State = OPEN, # CONNECTING during the opening handshake + ) -> AsyncIterator[None]: + """ + Create a context for writing to the connection from user code. + + On entry, :meth:`send_context` checks that the connection is open; on + exit, it writes outgoing data to the socket:: + + async with self.send_context(): + self.protocol.send_text(message.encode()) + + When the connection isn't open on entry, when the connection is expected + to close on exit, or when an unexpected error happens, terminating the + connection, :meth:`send_context` waits until the connection is closed + then raises :exc:`~websockets.exceptions.ConnectionClosed`. + + """ + # Should we wait until the connection is closed? + wait_for_close = False + # Should we close the transport and raise ConnectionClosed? + raise_close_exc = False + # What exception should we chain ConnectionClosed to? + original_exc: BaseException | None = None + + if self.protocol.state is expected_state: + # Let the caller interact with the protocol. + try: + yield + except (ProtocolError, ConcurrencyError): + # The protocol state wasn't changed. Exit immediately. + raise + except Exception as exc: + self.logger.error("unexpected internal error", exc_info=True) + # This branch should never run. It's a safety net in case of + # bugs. Since we don't know what happened, we will close the + # connection and raise the exception to the caller. + wait_for_close = False + raise_close_exc = True + original_exc = exc + else: + # Check if the connection is expected to close soon. + if self.protocol.close_expected(): + wait_for_close = True + # Set the close deadline based on the close timeout. + # Since we tested earlier that protocol.state is OPEN + # (or CONNECTING), self.close_deadline is still None. + assert self.close_deadline is None + if self.close_timeout is not None: + self.close_deadline = self.loop.time() + self.close_timeout + # Write outgoing data to the socket with flow control. + try: + self.send_data() + await self.drain() + except Exception as exc: + if self.debug: + self.logger.debug( + "! error while sending data", + exc_info=True, + ) + # While the only expected exception here is OSError, + # other exceptions would be treated identically. + wait_for_close = False + raise_close_exc = True + original_exc = exc + + else: # self.protocol.state is not expected_state + # Minor layering violation: we assume that the connection + # will be closing soon if it isn't in the expected state. + wait_for_close = True + # Calculate close_deadline if it wasn't set yet. + if self.close_deadline is None: + if self.close_timeout is not None: + self.close_deadline = self.loop.time() + self.close_timeout + raise_close_exc = True + + # If the connection is expected to close soon and the close timeout + # elapses, close the socket to terminate the connection. + if wait_for_close: + try: + async with asyncio_timeout_at(self.close_deadline): + await asyncio.shield(self.connection_lost_waiter) + except TimeoutError: + # There's no risk of overwriting another error because + # original_exc is never set when wait_for_close is True. + assert original_exc is None + original_exc = TimeoutError("timed out while closing connection") + # Set recv_exc before closing the transport in order to get + # proper exception reporting. + raise_close_exc = True + self.set_recv_exc(original_exc) + + # If an error occurred, close the transport to terminate the connection and + # raise an exception. + if raise_close_exc: + self.transport.abort() + # Wait for the protocol state to be CLOSED before accessing close_exc. + await asyncio.shield(self.connection_lost_waiter) + raise self.protocol.close_exc from original_exc + + def send_data(self) -> None: + """ + Send outgoing data. + + """ + for data in self.protocol.data_to_send(): + if data: + self.transport.write(data) + else: + # Half-close the TCP connection when possible i.e. no TLS. + if self.transport.can_write_eof(): + if self.debug: + self.logger.debug("x half-closing TCP connection") + # write_eof() doesn't document which exceptions it raises. + # OSError is plausible. uvloop can raise RuntimeError here. + try: + self.transport.write_eof() + except Exception: # pragma: no cover + pass + # Else, close the TCP connection. + else: # pragma: no cover + if self.debug: + self.logger.debug("x closing TCP connection") + self.transport.close() + + def set_recv_exc(self, exc: BaseException | None) -> None: + """ + Set recv_exc, if not set yet. + + This method must be called only from connection callbacks. + + """ + if self.recv_exc is None: + self.recv_exc = exc + + # asyncio.Protocol methods + + # Connection callbacks + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + transport = cast(asyncio.Transport, transport) + self.recv_messages = Assembler( + self.max_queue_high, + self.max_queue_low, + pause=transport.pause_reading, + resume=transport.resume_reading, + ) + transport.set_write_buffer_limits( + self.write_limit_high, + self.write_limit_low, + ) + self.transport = transport + + def connection_lost(self, exc: Exception | None) -> None: + # Calling protocol.receive_eof() is safe because it's idempotent. + # This guarantees that the protocol state becomes CLOSED. + self.protocol.receive_eof() + assert self.protocol.state is CLOSED + + self.set_recv_exc(exc) + + # Abort recv() and pending pings with a ConnectionClosed exception. + self.recv_messages.close() + self.terminate_pending_pings() + + if self.keepalive_task is not None: + self.keepalive_task.cancel() + + # If self.connection_lost_waiter isn't pending, that's a bug, because: + # - it's set only here in connection_lost() which is called only once; + # - it must never be canceled. + self.connection_lost_waiter.set_result(None) + + # Adapted from asyncio.streams.FlowControlMixin + if self.paused: # pragma: no cover + self.paused = False + for waiter in self.drain_waiters: + if not waiter.done(): + if exc is None: + waiter.set_result(None) + else: + waiter.set_exception(exc) + + # Flow control callbacks + + def pause_writing(self) -> None: # pragma: no cover + # Adapted from asyncio.streams.FlowControlMixin + assert not self.paused + self.paused = True + + def resume_writing(self) -> None: # pragma: no cover + # Adapted from asyncio.streams.FlowControlMixin + assert self.paused + self.paused = False + for waiter in self.drain_waiters: + if not waiter.done(): + waiter.set_result(None) + + async def drain(self) -> None: # pragma: no cover + # We don't check if the connection is closed because we call drain() + # immediately after write() and write() would fail in that case. + + # Adapted from asyncio.streams.StreamWriter + # Yield to the event loop so that connection_lost() may be called. + if self.transport.is_closing(): + await asyncio.sleep(0) + + # Adapted from asyncio.streams.FlowControlMixin + if self.paused: + waiter = self.loop.create_future() + self.drain_waiters.append(waiter) + try: + await waiter + finally: + self.drain_waiters.remove(waiter) + + # Streaming protocol callbacks + + def data_received(self, data: bytes) -> None: + # Feed incoming data to the protocol. + self.protocol.receive_data(data) + + # This isn't expected to raise an exception. + events = self.protocol.events_received() + + # Write outgoing data to the transport. + try: + self.send_data() + except Exception as exc: + if self.debug: + self.logger.debug("! error while sending data", exc_info=True) + self.set_recv_exc(exc) + + # If needed, set the close deadline based on the close timeout. + if self.protocol.close_expected(): + if self.close_deadline is None: + if self.close_timeout is not None: + self.close_deadline = self.loop.time() + self.close_timeout + + # If self.send_data raised an exception, then events are lost. + # Given that automatic responses write small amounts of data, + # this should be uncommon, so we don't handle the edge case. + + for event in events: + # This isn't expected to raise an exception. + self.process_event(event) + + def eof_received(self) -> None: + # Feed the end of the data stream to the protocol. + self.protocol.receive_eof() + + # This isn't expected to raise an exception. + events = self.protocol.events_received() + + # There is no error handling because send_data() can only write + # the end of the data stream and it handles errors by itself. + self.send_data() + + # This code path is triggered when receiving an HTTP response + # without a Content-Length header. This is the only case where + # reading until EOF generates an event; all other events have + # a known length. Ignore for coverage measurement because tests + # are in test_client.py rather than test_connection.py. + for event in events: # pragma: no cover + # This isn't expected to raise an exception. + self.process_event(event) + + # The WebSocket protocol has its own closing handshake: endpoints close + # the TCP or TLS connection after sending and receiving a close frame. + # As a consequence, they never need to write after receiving EOF, so + # there's no reason to keep the transport open by returning True. + # Besides, that doesn't work on TLS connections. + + +# broadcast() is defined in the connection module even though it's primarily +# used by servers and documented in the server module because it works with +# client connections too and because it's easier to test together with the +# Connection class. + + +def broadcast( + connections: Iterable[Connection], + message: DataLike, + raise_exceptions: bool = False, +) -> None: + """ + Broadcast a message to several WebSocket connections. + + A string (:class:`str`) is sent as a Text_ frame. A bytestring or bytes-like + object (:class:`bytes`, :class:`bytearray`, or :class:`memoryview`) is sent + as a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + :func:`broadcast` pushes the message synchronously to all connections even + if their write buffers are overflowing. There's no backpressure. + + If you broadcast messages faster than a connection can handle them, messages + will pile up in its write buffer until the connection times out. Keep + ``ping_interval`` and ``ping_timeout`` low to prevent excessive memory usage + from slow connections. + + Unlike :meth:`~websockets.asyncio.connection.Connection.send`, + :func:`broadcast` doesn't support sending fragmented messages. Indeed, + fragmentation is useful for sending large messages without buffering them in + memory, while :func:`broadcast` buffers one copy per connection as fast as + possible. + + :func:`broadcast` skips connections that aren't open in order to avoid + errors on connections where the closing handshake is in progress. + + :func:`broadcast` ignores failures to write the message on some connections. + It continues writing to other connections. On Python 3.11 and above, you may + set ``raise_exceptions`` to :obj:`True` to record failures and raise all + exceptions in a :pep:`654` :exc:`ExceptionGroup`. + + While :func:`broadcast` makes more sense for servers, it works identically + with clients, if you have a use case for opening connections to many servers + and broadcasting a message to them. + + Args: + websockets: WebSocket connections to which the message will be sent. + message: Message to send. + raise_exceptions: Whether to raise an exception in case of failures. + + Raises: + TypeError: If ``message`` doesn't have a supported type. + + """ + if isinstance(message, str): + send_method = "send_text" + message = message.encode() + elif isinstance(message, BytesLike): + send_method = "send_binary" + else: + raise TypeError("data must be str or bytes") + + if raise_exceptions: + if sys.version_info[:2] < (3, 11): # pragma: no cover + raise ValueError("raise_exceptions requires at least Python 3.11") + exceptions: list[Exception] = [] + + for connection in connections: + exception: Exception + + if connection.protocol.state is not OPEN: + continue + + if connection.send_in_progress is not None: + if raise_exceptions: + exception = ConcurrencyError("sending a fragmented message") + exceptions.append(exception) + else: + connection.logger.warning( + "skipped broadcast: sending a fragmented message", + ) + continue + + try: + # Call connection.protocol.send_text or send_binary. + # Either way, message is already converted to bytes. + getattr(connection.protocol, send_method)(message) + connection.send_data() + except Exception as write_exception: + if raise_exceptions: + exception = RuntimeError("failed to write message") + exception.__cause__ = write_exception + exceptions.append(exception) + else: + connection.logger.warning( + "skipped broadcast: failed to write message: %s", + traceback.format_exception_only(write_exception)[0].strip(), + ) + + if raise_exceptions and exceptions: + raise ExceptionGroup("skipped broadcast", exceptions) + + +# Pretend that broadcast is actually defined in the server module. +broadcast.__module__ = "websockets.asyncio.server" diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/messages.py b/.venv/lib/python3.14/site-packages/websockets/asyncio/messages.py new file mode 100644 index 0000000000000000000000000000000000000000..f27cb2e7c8724434652f15be0a7efbdd49cf8951 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/asyncio/messages.py @@ -0,0 +1,316 @@ +from __future__ import annotations + +import asyncio +import codecs +import collections +from collections.abc import AsyncIterator, Iterable +from typing import Any, Callable, Generic, Literal, TypeVar, overload + +from ..exceptions import ConcurrencyError +from ..frames import OP_BINARY, OP_CONT, OP_TEXT, Frame +from ..typing import Data + + +__all__ = ["Assembler"] + +UTF8Decoder = codecs.getincrementaldecoder("utf-8") + +T = TypeVar("T") + + +class SimpleQueue(Generic[T]): + """ + Simplified version of :class:`asyncio.Queue`. + + Provides only the subset of functionality needed by :class:`Assembler`. + + """ + + def __init__(self) -> None: + self.loop = asyncio.get_running_loop() + self.get_waiter: asyncio.Future[None] | None = None + self.queue: collections.deque[T] = collections.deque() + + def __len__(self) -> int: + return len(self.queue) + + def put(self, item: T) -> None: + """Put an item into the queue.""" + self.queue.append(item) + if self.get_waiter is not None and not self.get_waiter.done(): + self.get_waiter.set_result(None) + + async def get(self, block: bool = True) -> T: + """Remove and return an item from the queue, waiting if necessary.""" + if not self.queue: + if not block: + raise EOFError("stream of frames ended") + assert self.get_waiter is None, "cannot call get() concurrently" + self.get_waiter = self.loop.create_future() + try: + await self.get_waiter + finally: + self.get_waiter.cancel() + self.get_waiter = None + return self.queue.popleft() + + def reset(self, items: Iterable[T]) -> None: + """Put back items into an empty, idle queue.""" + assert self.get_waiter is None, "cannot reset() while get() is running" + assert not self.queue, "cannot reset() while queue isn't empty" + self.queue.extend(items) + + def abort(self) -> None: + """Close the queue, raising EOFError in get() if necessary.""" + if self.get_waiter is not None and not self.get_waiter.done(): + self.get_waiter.set_exception(EOFError("stream of frames ended")) + + +class Assembler: + """ + Assemble messages from frames. + + :class:`Assembler` expects only data frames. The stream of frames must + respect the protocol; if it doesn't, the behavior is undefined. + + Args: + pause: Called when the buffer of frames goes above the high water mark; + should pause reading from the network. + resume: Called when the buffer of frames goes below the low water mark; + should resume reading from the network. + + """ + + def __init__( + self, + high: int | None = None, + low: int | None = None, + pause: Callable[[], Any] = lambda: None, + resume: Callable[[], Any] = lambda: None, + ) -> None: + # Queue of incoming frames. + self.frames: SimpleQueue[Frame] = SimpleQueue() + + # We cannot put a hard limit on the size of the queue because a single + # call to Protocol.data_received() could produce thousands of frames, + # which must be buffered. Instead, we pause reading when the buffer goes + # above the high limit and we resume when it goes under the low limit. + if high is not None and low is None: + low = high // 4 + if high is None and low is not None: + high = low * 4 + if high is not None and low is not None: + if low < 0: + raise ValueError("low must be positive or equal to zero") + if high < low: + raise ValueError("high must be greater than or equal to low") + self.high, self.low = high, low + self.pause = pause + self.resume = resume + self.paused = False + + # This flag prevents concurrent calls to get() by user code. + self.get_in_progress = False + + # This flag marks the end of the connection. + self.closed = False + + @overload + async def get(self, decode: Literal[True]) -> str: ... + + @overload + async def get(self, decode: Literal[False]) -> bytes: ... + + @overload + async def get(self, decode: bool | None = None) -> Data: ... + + async def get(self, decode: bool | None = None) -> Data: + """ + Read the next message. + + :meth:`get` returns a single :class:`str` or :class:`bytes`. + + If the message is fragmented, :meth:`get` waits until the last frame is + received, then it reassembles the message and returns it. To receive + messages frame by frame, use :meth:`get_iter` instead. + + Args: + decode: :obj:`False` disables UTF-8 decoding of text frames and + returns :class:`bytes`. :obj:`True` forces UTF-8 decoding of + binary frames and returns :class:`str`. + + Raises: + EOFError: If the stream of frames has ended. + UnicodeDecodeError: If a text frame contains invalid UTF-8. + ConcurrencyError: If two coroutines run :meth:`get` or + :meth:`get_iter` concurrently. + + """ + if self.get_in_progress: + raise ConcurrencyError("get() or get_iter() is already running") + self.get_in_progress = True + + # Locking with get_in_progress prevents concurrent execution + # until get() fetches a complete message or is canceled. + + try: + # Fetch the first frame. + frame = await self.frames.get(not self.closed) + self.maybe_resume() + assert frame.opcode is OP_TEXT or frame.opcode is OP_BINARY + if decode is None: + decode = frame.opcode is OP_TEXT + frames = [frame] + + # Fetch subsequent frames for fragmented messages. + while not frame.fin: + try: + frame = await self.frames.get(not self.closed) + except asyncio.CancelledError: + # Put frames already received back into the queue + # so that future calls to get() can return them. + self.frames.reset(frames) + raise + self.maybe_resume() + assert frame.opcode is OP_CONT + frames.append(frame) + + finally: + self.get_in_progress = False + + # This converts frame.data to bytes when it's a bytearray. + data = b"".join(frame.data for frame in frames) + if decode: + return data.decode() + else: + return data + + @overload + def get_iter(self, decode: Literal[True]) -> AsyncIterator[str]: ... + + @overload + def get_iter(self, decode: Literal[False]) -> AsyncIterator[bytes]: ... + + @overload + def get_iter(self, decode: bool | None = None) -> AsyncIterator[Data]: ... + + async def get_iter(self, decode: bool | None = None) -> AsyncIterator[Data]: + """ + Stream the next message. + + Iterating the return value of :meth:`get_iter` asynchronously yields a + :class:`str` or :class:`bytes` for each frame in the message. + + The iterator must be fully consumed before calling :meth:`get_iter` or + :meth:`get` again. Else, :exc:`ConcurrencyError` is raised. + + This method only makes sense for fragmented messages. If messages aren't + fragmented, use :meth:`get` instead. + + Args: + decode: :obj:`False` disables UTF-8 decoding of text frames and + returns :class:`bytes`. :obj:`True` forces UTF-8 decoding of + binary frames and returns :class:`str`. + + Raises: + EOFError: If the stream of frames has ended. + UnicodeDecodeError: If a text frame contains invalid UTF-8. + ConcurrencyError: If two coroutines run :meth:`get` or + :meth:`get_iter` concurrently. + + """ + if self.get_in_progress: + raise ConcurrencyError("get() or get_iter() is already running") + self.get_in_progress = True + + # Locking with get_in_progress prevents concurrent execution + # until get_iter() fetches a complete message or is canceled. + + # If get_iter() raises an exception e.g. in decoder.decode(), + # get_in_progress remains set and the connection becomes unusable. + + # Yield the first frame. + try: + frame = await self.frames.get(not self.closed) + except asyncio.CancelledError: + self.get_in_progress = False + raise + self.maybe_resume() + assert frame.opcode is OP_TEXT or frame.opcode is OP_BINARY + if decode is None: + decode = frame.opcode is OP_TEXT + if decode: + decoder = UTF8Decoder() + yield decoder.decode(frame.data, frame.fin) + else: + # Convert to bytes when frame.data is a bytearray. + yield bytes(frame.data) + + # Yield subsequent frames for fragmented messages. + while not frame.fin: + # We cannot handle asyncio.CancelledError because we don't buffer + # previous fragments — we're streaming them. Canceling get_iter() + # here will leave the assembler in a stuck state. Future calls to + # get() or get_iter() will raise ConcurrencyError. + frame = await self.frames.get(not self.closed) + self.maybe_resume() + assert frame.opcode is OP_CONT + if decode: + yield decoder.decode(frame.data, frame.fin) + else: + # Convert to bytes when frame.data is a bytearray. + yield bytes(frame.data) + + self.get_in_progress = False + + def put(self, frame: Frame) -> None: + """ + Add ``frame`` to the next message. + + Raises: + EOFError: If the stream of frames has ended. + + """ + if self.closed: + raise EOFError("stream of frames ended") + + self.frames.put(frame) + self.maybe_pause() + + def maybe_pause(self) -> None: + """Pause the writer if queue is above the high water mark.""" + # Skip if flow control is disabled. + if self.high is None: + return + + # Check for "> high" to support high = 0. + if len(self.frames) > self.high and not self.paused: + self.paused = True + self.pause() + + def maybe_resume(self) -> None: + """Resume the writer if queue is below the low water mark.""" + # Skip if flow control is disabled. + if self.low is None: + return + + # Check for "<= low" to support low = 0. + if len(self.frames) <= self.low and self.paused: + self.paused = False + self.resume() + + def close(self) -> None: + """ + End the stream of frames. + + Calling :meth:`close` concurrently with :meth:`get`, :meth:`get_iter`, + or :meth:`put` is safe. They will raise :exc:`EOFError`. + + """ + if self.closed: + return + + self.closed = True + + # Unblock get() or get_iter(). + self.frames.abort() diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/router.py b/.venv/lib/python3.14/site-packages/websockets/asyncio/router.py new file mode 100644 index 0000000000000000000000000000000000000000..49d2a40feee03ebcda5e05dc43eebeb27ccecab7 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/asyncio/router.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import http +import ssl as ssl_module +import urllib.parse +from typing import Any, Awaitable, Callable, Literal + +from ..http11 import Request, Response +from .server import Server, ServerConnection, serve + + +__all__ = ["route", "unix_route", "Router"] + + +try: + from werkzeug.exceptions import NotFound + from werkzeug.routing import Map, RequestRedirect + +except ImportError: + + def route( + url_map: Map, + *args: Any, + server_name: str | None = None, + ssl: ssl_module.SSLContext | Literal[True] | None = None, + create_router: type[Router] | None = None, + **kwargs: Any, + ) -> Awaitable[Server]: + raise ImportError("route() requires werkzeug") + + def unix_route( + url_map: Map, + path: str | None = None, + **kwargs: Any, + ) -> Awaitable[Server]: + raise ImportError("unix_route() requires werkzeug") + +else: + + def route( + url_map: Map, + *args: Any, + server_name: str | None = None, + ssl: ssl_module.SSLContext | Literal[True] | None = None, + create_router: type[Router] | None = None, + **kwargs: Any, + ) -> Awaitable[Server]: + """ + Create a WebSocket server dispatching connections to different handlers. + + This feature requires the third-party library `werkzeug`_: + + .. code-block:: console + + $ pip install werkzeug + + .. _werkzeug: https://werkzeug.palletsprojects.com/ + + :func:`route` accepts the same arguments as + :func:`~websockets.sync.server.serve`, except as described below. + + The first argument is a :class:`werkzeug.routing.Map` that maps URL patterns + to connection handlers. In addition to the connection, handlers receive + parameters captured in the URL as keyword arguments. + + Here's an example:: + + + from websockets.asyncio.router import route + from werkzeug.routing import Map, Rule + + async def channel_handler(websocket, channel_id): + ... + + url_map = Map([ + Rule("/channel/", endpoint=channel_handler), + ... + ]) + + # set this future to exit the server + stop = asyncio.get_running_loop().create_future() + + async with route(url_map, ...) as server: + await stop + + + Refer to the documentation of :mod:`werkzeug.routing` for details. + + If you define redirects with ``Rule(..., redirect_to=...)`` in the URL map, + when the server runs behind a reverse proxy that modifies the ``Host`` + header or terminates TLS, you need additional configuration: + + * Set ``server_name`` to the name of the server as seen by clients. When + not provided, websockets uses the value of the ``Host`` header. + + * Set ``ssl=True`` to generate ``wss://`` URIs without enabling TLS. + Under the hood, this bind the URL map with a ``url_scheme`` of + ``wss://`` instead of ``ws://``. + + There is no need to specify ``websocket=True`` in each rule. It is added + automatically. + + Args: + url_map: Mapping of URL patterns to connection handlers. + server_name: Name of the server as seen by clients. If :obj:`None`, + websockets uses the value of the ``Host`` header. + ssl: Configuration for enabling TLS on the connection. Set it to + :obj:`True` if a reverse proxy terminates TLS connections. + create_router: Factory for the :class:`Router` dispatching requests to + handlers. Set it to a wrapper or a subclass to customize routing. + + """ + url_scheme = "ws" if ssl is None else "wss" + if ssl is not True and ssl is not None: + kwargs["ssl"] = ssl + + if create_router is None: + create_router = Router + + router = create_router(url_map, server_name, url_scheme) + + _process_request: ( + Callable[ + [ServerConnection, Request], + Awaitable[Response | None] | Response | None, + ] + | None + ) = kwargs.pop("process_request", None) + if _process_request is None: + process_request: Callable[ + [ServerConnection, Request], + Awaitable[Response | None] | Response | None, + ] = router.route_request + else: + + async def process_request( + connection: ServerConnection, request: Request + ) -> Response | None: + response = _process_request(connection, request) + if isinstance(response, Awaitable): + response = await response + if response is not None: + return response + return router.route_request(connection, request) + + return serve(router.handler, *args, process_request=process_request, **kwargs) + + def unix_route( + url_map: Map, + path: str | None = None, + **kwargs: Any, + ) -> Awaitable[Server]: + """ + Create a WebSocket Unix server dispatching connections to different handlers. + + :func:`unix_route` combines the behaviors of :func:`route` and + :func:`~websockets.asyncio.server.unix_serve`. + + Args: + url_map: Mapping of URL patterns to connection handlers. + path: File system path to the Unix socket. + + """ + return route(url_map, unix=True, path=path, **kwargs) + + +class Router: + """WebSocket router supporting :func:`route`.""" + + def __init__( + self, + url_map: Map, + server_name: str | None = None, + url_scheme: str = "ws", + ) -> None: + self.url_map = url_map + self.server_name = server_name + self.url_scheme = url_scheme + for rule in self.url_map.iter_rules(): + rule.websocket = True + + def get_server_name(self, connection: ServerConnection, request: Request) -> str: + if self.server_name is None: + return request.headers["Host"] + else: + return self.server_name + + def redirect(self, connection: ServerConnection, url: str) -> Response: + response = connection.respond(http.HTTPStatus.FOUND, f"Found at {url}") + response.headers["Location"] = url + return response + + def not_found(self, connection: ServerConnection) -> Response: + return connection.respond(http.HTTPStatus.NOT_FOUND, "Not Found") + + def route_request( + self, connection: ServerConnection, request: Request + ) -> Response | None: + """Route incoming request.""" + url_map_adapter = self.url_map.bind( + server_name=self.get_server_name(connection, request), + url_scheme=self.url_scheme, + ) + try: + parsed = urllib.parse.urlparse(request.path) + handler, kwargs = url_map_adapter.match( + path_info=parsed.path, + query_args=parsed.query, + ) + except RequestRedirect as redirect: + return self.redirect(connection, redirect.new_url) + except NotFound: + return self.not_found(connection) + connection.handler, connection.handler_kwargs = handler, kwargs + return None + + async def handler(self, connection: ServerConnection) -> None: + """Handle a connection.""" + return await connection.handler(connection, **connection.handler_kwargs) diff --git a/.venv/lib/python3.14/site-packages/websockets/asyncio/server.py b/.venv/lib/python3.14/site-packages/websockets/asyncio/server.py new file mode 100644 index 0000000000000000000000000000000000000000..ef9bd807f102e05bbc3de212768bfed1679d8764 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/asyncio/server.py @@ -0,0 +1,997 @@ +from __future__ import annotations + +import asyncio +import hmac +import http +import logging +import re +import socket +import sys +from collections.abc import Awaitable, Generator, Iterable, Sequence +from types import TracebackType +from typing import Any, Callable, Mapping, cast + +from ..exceptions import InvalidHeader +from ..extensions.base import ServerExtensionFactory +from ..extensions.permessage_deflate import enable_server_permessage_deflate +from ..frames import CloseCode +from ..headers import ( + build_www_authenticate_basic, + parse_authorization_basic, + validate_subprotocols, +) +from ..http11 import SERVER, Request, Response +from ..protocol import CONNECTING, OPEN, Event +from ..server import ServerProtocol +from ..typing import LoggerLike, Origin, StatusLike, Subprotocol +from .compatibility import asyncio_timeout +from .connection import Connection, broadcast + + +__all__ = [ + "broadcast", + "serve", + "unix_serve", + "ServerConnection", + "Server", + "basic_auth", +] + + +class ServerConnection(Connection): + """ + :mod:`asyncio` implementation of a WebSocket server connection. + + :class:`ServerConnection` provides :meth:`recv` and :meth:`send` methods for + receiving and sending messages. + + It supports asynchronous iteration to receive messages:: + + async for message in websocket: + await process(message) + + The iterator exits normally when the connection is closed with code + 1000 (OK) or 1001 (going away) or without a close code. It raises a + :exc:`~websockets.exceptions.ConnectionClosedError` when the connection is + closed with any other code. + + The ``ping_interval``, ``ping_timeout``, ``close_timeout``, ``max_queue``, + and ``write_limit`` arguments have the same meaning as in :func:`serve`. + + Args: + protocol: Sans-I/O connection. + server: Server that manages this connection. + + """ + + def __init__( + self, + protocol: ServerProtocol, + server: Server, + *, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + max_queue: int | None | tuple[int | None, int | None] = 16, + write_limit: int | tuple[int, int | None] = 2**15, + ) -> None: + self.protocol: ServerProtocol + super().__init__( + protocol, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_queue=max_queue, + write_limit=write_limit, + ) + self.server = server + self.request_rcvd: asyncio.Future[None] = self.loop.create_future() + self.username: str # see basic_auth() + self.handler: Callable[[ServerConnection], Awaitable[None]] # see route() + self.handler_kwargs: Mapping[str, Any] # see route() + + def respond(self, status: StatusLike, text: str) -> Response: + """ + Create a plain text HTTP response. + + ``process_request`` and ``process_response`` may call this method to + return an HTTP response instead of performing the WebSocket opening + handshake. + + You can modify the response before returning it, for example by changing + HTTP headers. + + Args: + status: HTTP status code. + text: HTTP response body; it will be encoded to UTF-8. + + Returns: + HTTP response to send to the client. + + """ + return self.protocol.reject(status, text) + + async def handshake( + self, + process_request: ( + Callable[ + [ServerConnection, Request], + Awaitable[Response | None] | Response | None, + ] + | None + ) = None, + process_response: ( + Callable[ + [ServerConnection, Request, Response], + Awaitable[Response | None] | Response | None, + ] + | None + ) = None, + server_header: str | None = SERVER, + ) -> None: + """ + Perform the opening handshake. + + """ + await asyncio.wait( + [self.request_rcvd, self.connection_lost_waiter], + return_when=asyncio.FIRST_COMPLETED, + ) + + if self.request is not None: + async with self.send_context(expected_state=CONNECTING): + response = None + + if process_request is not None: + try: + response = process_request(self, self.request) + if isinstance(response, Awaitable): + response = await response + except Exception as exc: + self.protocol.handshake_exc = exc + response = self.protocol.reject( + http.HTTPStatus.INTERNAL_SERVER_ERROR, + ( + "Failed to open a WebSocket connection.\n" + "See server log for more information.\n" + ), + ) + + if response is None: + if self.server.is_serving(): + self.response = self.protocol.accept(self.request) + else: + self.response = self.protocol.reject( + http.HTTPStatus.SERVICE_UNAVAILABLE, + "Server is shutting down.\n", + ) + else: + assert isinstance(response, Response) # help mypy + self.response = response + + if server_header: + self.response.headers["Server"] = server_header + + response = None + + if process_response is not None: + try: + response = process_response(self, self.request, self.response) + if isinstance(response, Awaitable): + response = await response + except Exception as exc: + self.protocol.handshake_exc = exc + response = self.protocol.reject( + http.HTTPStatus.INTERNAL_SERVER_ERROR, + ( + "Failed to open a WebSocket connection.\n" + "See server log for more information.\n" + ), + ) + + if response is not None: + assert isinstance(response, Response) # help mypy + self.response = response + + self.protocol.send_response(self.response) + + # self.protocol.handshake_exc is set when the connection is lost before + # receiving a request, when the request cannot be parsed, or when the + # handshake fails, including when process_request or process_response + # raises an exception. + + # It isn't set when process_request or process_response sends an HTTP + # response that rejects the handshake. + + if self.protocol.handshake_exc is not None: + raise self.protocol.handshake_exc + + def process_event(self, event: Event) -> None: + """ + Process one incoming event. + + """ + # First event - handshake request. + if self.request is None: + assert isinstance(event, Request) + self.request = event + self.request_rcvd.set_result(None) + # Later events - frames. + else: + super().process_event(event) + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + super().connection_made(transport) + self.server.start_connection_handler(self) + + +class Server: + """ + WebSocket server returned by :func:`serve`. + + This class mirrors the API of :class:`asyncio.Server`. + + It keeps track of WebSocket connections in order to close them properly + when shutting down. + + Args: + handler: Connection handler. It receives the WebSocket connection, + which is a :class:`ServerConnection`, in argument. + process_request: Intercept the request during the opening handshake. + Return an HTTP response to force the response. Return :obj:`None` to + continue normally. When you force an HTTP 101 Continue response, the + handshake is successful. Else, the connection is aborted. + ``process_request`` may be a function or a coroutine. + process_response: Intercept the response during the opening handshake. + Modify the response or return a new HTTP response to force the + response. Return :obj:`None` to continue normally. When you force an + HTTP 101 Continue response, the handshake is successful. Else, the + connection is aborted. ``process_response`` may be a function or a + coroutine. + server_header: Value of the ``Server`` response header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. Setting it to + :obj:`None` removes the header. + open_timeout: Timeout for opening connections in seconds. + :obj:`None` disables the timeout. + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.server")``. + See the :doc:`logging guide <../../topics/logging>` for details. + + """ + + def __init__( + self, + handler: Callable[[ServerConnection], Awaitable[None]], + *, + process_request: ( + Callable[ + [ServerConnection, Request], + Awaitable[Response | None] | Response | None, + ] + | None + ) = None, + process_response: ( + Callable[ + [ServerConnection, Request, Response], + Awaitable[Response | None] | Response | None, + ] + | None + ) = None, + server_header: str | None = SERVER, + open_timeout: float | None = 10, + logger: LoggerLike | None = None, + ) -> None: + self.loop = asyncio.get_running_loop() + self.handler = handler + self.process_request = process_request + self.process_response = process_response + self.server_header = server_header + self.open_timeout = open_timeout + if logger is None: + logger = logging.getLogger("websockets.server") + self.logger = logger + + # Keep track of active connections. + self.handlers: dict[ServerConnection, asyncio.Task[None]] = {} + + # Task responsible for closing the server and terminating connections. + self.close_task: asyncio.Task[None] | None = None + + # Completed when the server is closed and connections are terminated. + self.closed_waiter: asyncio.Future[None] = self.loop.create_future() + + @property + def connections(self) -> set[ServerConnection]: + """ + Set of active connections. + + This property contains all connections that completed the opening + handshake successfully and didn't start the closing handshake yet. + It can be useful in combination with :func:`~broadcast`. + + """ + return {connection for connection in self.handlers if connection.state is OPEN} + + def wrap(self, server: asyncio.Server) -> None: + """ + Attach to a given :class:`asyncio.Server`. + + Since :meth:`~asyncio.loop.create_server` doesn't support injecting a + custom ``Server`` class, the easiest solution that doesn't rely on + private :mod:`asyncio` APIs is to: + + - instantiate a :class:`Server` + - give the protocol factory a reference to that instance + - call :meth:`~asyncio.loop.create_server` with the factory + - attach the resulting :class:`asyncio.Server` with this method + + """ + self.server = server + for sock in server.sockets: + if sock.family == socket.AF_INET: + name = "%s:%d" % sock.getsockname() + elif sock.family == socket.AF_INET6: + name = "[%s]:%d" % sock.getsockname()[:2] + elif sock.family == socket.AF_UNIX: + name = sock.getsockname() + # In the unlikely event that someone runs websockets over a + # protocol other than IP or Unix sockets, avoid crashing. + else: # pragma: no cover + name = str(sock.getsockname()) + self.logger.info("server listening on %s", name) + + async def conn_handler(self, connection: ServerConnection) -> None: + """ + Handle the lifecycle of a WebSocket connection. + + Since this method doesn't have a caller that can handle exceptions, + it attempts to log relevant ones. + + It guarantees that the TCP connection is closed before exiting. + + """ + try: + async with asyncio_timeout(self.open_timeout): + try: + await connection.handshake( + self.process_request, + self.process_response, + self.server_header, + ) + except asyncio.CancelledError: + connection.transport.abort() + raise + except Exception: + connection.logger.error("opening handshake failed", exc_info=True) + connection.transport.abort() + return + + if connection.protocol.state is not OPEN: + # process_request or process_response rejected the handshake. + connection.transport.abort() + return + + try: + connection.start_keepalive() + await self.handler(connection) + except Exception: + connection.logger.error("connection handler failed", exc_info=True) + await connection.close(CloseCode.INTERNAL_ERROR) + else: + await connection.close() + + except TimeoutError: + # When the opening handshake times out, there's nothing to log. + pass + + except Exception: # pragma: no cover + # Don't leak connections on unexpected errors. + connection.transport.abort() + + finally: + # Registration is tied to the lifecycle of conn_handler() because + # the server waits for connection handlers to terminate, even if + # all connections are already closed. + del self.handlers[connection] + + def start_connection_handler(self, connection: ServerConnection) -> None: + """ + Register a connection with this server. + + """ + # The connection must be registered in self.handlers immediately. + # If it was registered in conn_handler(), a race condition could + # happen when closing the server after scheduling conn_handler() + # but before it starts executing. + self.handlers[connection] = self.loop.create_task(self.conn_handler(connection)) + + def close( + self, + close_connections: bool = True, + code: CloseCode | int = CloseCode.GOING_AWAY, + reason: str = "", + ) -> None: + """ + Close the server. + + * Close the underlying :class:`asyncio.Server`. + * When ``close_connections`` is :obj:`True`, which is the default, close + existing connections. Specifically: + + * Reject opening WebSocket connections with an HTTP 503 (service + unavailable) error. This happens when the server accepted the TCP + connection but didn't complete the opening handshake before closing. + * Close open WebSocket connections with code 1001 (going away). + ``code`` and ``reason`` can be customized, for example to use code + 1012 (service restart). + + * Wait until all connection handlers terminate. + + :meth:`close` is idempotent. + + """ + if self.close_task is None: + self.close_task = self.get_loop().create_task( + self._close(close_connections, code, reason) + ) + + async def _close( + self, + close_connections: bool = True, + code: CloseCode | int = CloseCode.GOING_AWAY, + reason: str = "", + ) -> None: + """ + Implementation of :meth:`close`. + + This calls :meth:`~asyncio.Server.close` on the underlying + :class:`asyncio.Server` object to stop accepting new connections and + then closes open connections. + + """ + self.logger.info("server closing") + + # Stop accepting new connections. + self.server.close() + + # Wait until all accepted connections reach connection_made() and call + # register(). See https://github.com/python/cpython/issues/79033 for + # details. This workaround can be removed when dropping Python < 3.11. + await asyncio.sleep(0) + + # After server.close(), handshake() closes OPENING connections with an + # HTTP 503 error. + + if close_connections: + # Close OPEN connections with code 1001 by default. + close_tasks = [ + asyncio.create_task(connection.close(code, reason)) + for connection in self.handlers + if connection.protocol.state is not CONNECTING + ] + # asyncio.wait doesn't accept an empty first argument. + if close_tasks: + await asyncio.wait(close_tasks) + + # Wait until all TCP connections are closed. + await self.server.wait_closed() + + # Wait until all connection handlers terminate. + # asyncio.wait doesn't accept an empty first argument. + if self.handlers: + await asyncio.wait(self.handlers.values()) + + # Tell wait_closed() to return. + self.closed_waiter.set_result(None) + + self.logger.info("server closed") + + async def wait_closed(self) -> None: + """ + Wait until the server is closed. + + When :meth:`wait_closed` returns, all TCP connections are closed and + all connection handlers have returned. + + To ensure a fast shutdown, a connection handler should always be + awaiting at least one of: + + * :meth:`~ServerConnection.recv`: when the connection is closed, + it raises :exc:`~websockets.exceptions.ConnectionClosedOK`; + * :meth:`~ServerConnection.wait_closed`: when the connection is + closed, it returns. + + Then the connection handler is immediately notified of the shutdown; + it can clean up and exit. + + """ + await asyncio.shield(self.closed_waiter) + + def get_loop(self) -> asyncio.AbstractEventLoop: + """ + See :meth:`asyncio.Server.get_loop`. + + """ + return self.server.get_loop() + + def is_serving(self) -> bool: # pragma: no cover + """ + See :meth:`asyncio.Server.is_serving`. + + """ + return self.server.is_serving() + + async def start_serving(self) -> None: # pragma: no cover + """ + See :meth:`asyncio.Server.start_serving`. + + Typical use:: + + server = await serve(..., start_serving=False) + # perform additional setup here... + # ... then start the server + await server.start_serving() + + """ + await self.server.start_serving() + + async def serve_forever(self) -> None: # pragma: no cover + """ + See :meth:`asyncio.Server.serve_forever`. + + Typical use:: + + server = await serve(...) + # this coroutine doesn't return + # canceling it stops the server + await server.serve_forever() + + This is an alternative to using :func:`serve` as an asynchronous context + manager. Shutdown is triggered by canceling :meth:`serve_forever` + instead of exiting a :func:`serve` context. + + """ + await self.server.serve_forever() + + @property + def sockets(self) -> tuple[socket.socket, ...]: + """ + See :attr:`asyncio.Server.sockets`. + + """ + return self.server.sockets + + async def __aenter__(self) -> Server: # pragma: no cover + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: # pragma: no cover + self.close() + await self.wait_closed() + + +# This is spelled in lower case because it's exposed as a callable in the API. +class serve: + """ + Create a WebSocket server listening on ``host`` and ``port``. + + Whenever a client connects, the server creates a :class:`ServerConnection`, + performs the opening handshake, and delegates to the ``handler`` coroutine. + + The handler receives the :class:`ServerConnection` instance, which you can + use to send and receive messages. + + Once the handler completes, either normally or with an exception, the server + performs the closing handshake and closes the connection. + + This coroutine returns a :class:`Server` whose API mirrors + :class:`asyncio.Server`. Treat it as an asynchronous context manager to + ensure that the server will be closed:: + + from websockets.asyncio.server import serve + + def handler(websocket): + ... + + # set this future to exit the server + stop = asyncio.get_running_loop().create_future() + + async with serve(handler, host, port): + await stop + + Alternatively, call :meth:`~Server.serve_forever` to serve requests and + cancel it to stop the server:: + + server = await serve(handler, host, port) + await server.serve_forever() + + Args: + handler: Connection handler. It receives the WebSocket connection, + which is a :class:`ServerConnection`, in argument. + host: Network interfaces the server binds to. + See :meth:`~asyncio.loop.create_server` for details. + port: TCP port the server listens on. + See :meth:`~asyncio.loop.create_server` for details. + origins: Acceptable values of the ``Origin`` header, for defending + against Cross-Site WebSocket Hijacking attacks. Values can be + :class:`str` to test for an exact match or regular expressions + compiled by :func:`re.compile` to test against a pattern. Include + :obj:`None` in the list if the lack of an origin is acceptable. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + select_subprotocol: Callback for selecting a subprotocol among + those supported by the client and the server. It receives a + :class:`ServerConnection` (not a + :class:`~websockets.server.ServerProtocol`!) instance and a list of + subprotocols offered by the client. Other than the first argument, + it has the same behavior as the + :meth:`ServerProtocol.select_subprotocol + ` method. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + process_request: Intercept the request during the opening handshake. + Return an HTTP response to force the response or :obj:`None` to + continue normally. When you force an HTTP 101 Continue response, the + handshake is successful. Else, the connection is aborted. + ``process_request`` may be a function or a coroutine. + process_response: Intercept the response during the opening handshake. + Return an HTTP response to force the response or :obj:`None` to + continue normally. When you force an HTTP 101 Continue response, the + handshake is successful. Else, the connection is aborted. + ``process_response`` may be a function or a coroutine. + server_header: Value of the ``Server`` response header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. Setting it to + :obj:`None` removes the header. + open_timeout: Timeout for opening connections in seconds. + :obj:`None` disables the timeout. + ping_interval: Interval between keepalive pings in seconds. + :obj:`None` disables keepalive. + ping_timeout: Timeout for keepalive pings in seconds. + :obj:`None` disables timeouts. + close_timeout: Timeout for closing connections in seconds. + :obj:`None` disables the timeout. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. You may pass a ``(max_message_size, + max_fragment_size)`` tuple to set different limits for messages and + fragments when you expect long messages sent in short fragments. + max_queue: High-water mark of the buffer where frames are received. + It defaults to 16 frames. The low-water mark defaults to ``max_queue + // 4``. You may pass a ``(high, low)`` tuple to set the high-water + and low-water marks. If you want to disable flow control entirely, + you may set it to ``None``, although that's a bad idea. + write_limit: High-water mark of write buffer in bytes. It is passed to + :meth:`~asyncio.WriteTransport.set_write_buffer_limits`. It defaults + to 32 KiB. You may pass a ``(high, low)`` tuple to set the + high-water and low-water marks. + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.server")``. See the + :doc:`logging guide <../../topics/logging>` for details. + create_connection: Factory for the :class:`ServerConnection` managing + the connection. Set it to a wrapper or a subclass to customize + connection handling. + + Any other keyword arguments are passed to the event loop's + :meth:`~asyncio.loop.create_server` method. + + For example: + + * You can set ``ssl`` to a :class:`~ssl.SSLContext` to enable TLS. + + * You can set ``sock`` to provide a preexisting TCP socket. You may call + :func:`socket.create_server` (not to be confused with the event loop's + :meth:`~asyncio.loop.create_server` method) to create a suitable server + socket and customize it. + + * You can set ``start_serving`` to ``False`` to start accepting connections + only after you call :meth:`~Server.start_serving()` or + :meth:`~Server.serve_forever()`. + + """ + + def __init__( + self, + handler: Callable[[ServerConnection], Awaitable[None]], + host: str | None = None, + port: int | None = None, + *, + # WebSocket + origins: Sequence[Origin | re.Pattern[str] | None] | None = None, + extensions: Sequence[ServerExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + select_subprotocol: ( + Callable[ + [ServerConnection, Sequence[Subprotocol]], + Subprotocol | None, + ] + | None + ) = None, + compression: str | None = "deflate", + # HTTP + process_request: ( + Callable[ + [ServerConnection, Request], + Awaitable[Response | None] | Response | None, + ] + | None + ) = None, + process_response: ( + Callable[ + [ServerConnection, Request, Response], + Awaitable[Response | None] | Response | None, + ] + | None + ) = None, + server_header: str | None = SERVER, + # Timeouts + open_timeout: float | None = 10, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + # Limits + max_size: int | None | tuple[int | None, int | None] = 2**20, + max_queue: int | None | tuple[int | None, int | None] = 16, + write_limit: int | tuple[int, int | None] = 2**15, + # Logging + logger: LoggerLike | None = None, + # Escape hatch for advanced customization + create_connection: type[ServerConnection] | None = None, + # Other keyword arguments are passed to loop.create_server + **kwargs: Any, + ) -> None: + if subprotocols is not None: + validate_subprotocols(subprotocols) + + if compression == "deflate": + extensions = enable_server_permessage_deflate(extensions) + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + if create_connection is None: + create_connection = ServerConnection + + self.server = Server( + handler, + process_request=process_request, + process_response=process_response, + server_header=server_header, + open_timeout=open_timeout, + logger=logger, + ) + + if kwargs.get("ssl") is not None: + kwargs.setdefault("ssl_handshake_timeout", open_timeout) + if sys.version_info[:2] >= (3, 11): # pragma: no branch + kwargs.setdefault("ssl_shutdown_timeout", close_timeout) + + def factory() -> ServerConnection: + """ + Create an asyncio protocol for managing a WebSocket connection. + + """ + # Create a closure to give select_subprotocol access to connection. + protocol_select_subprotocol: ( + Callable[ + [ServerProtocol, Sequence[Subprotocol]], + Subprotocol | None, + ] + | None + ) = None + if select_subprotocol is not None: + + def protocol_select_subprotocol( + protocol: ServerProtocol, + subprotocols: Sequence[Subprotocol], + ) -> Subprotocol | None: + # mypy doesn't know that select_subprotocol is immutable. + assert select_subprotocol is not None + # Ensure this function is only used in the intended context. + assert protocol is connection.protocol + return select_subprotocol(connection, subprotocols) + + # This is a protocol in the Sans-I/O implementation of websockets. + protocol = ServerProtocol( + origins=origins, + extensions=extensions, + subprotocols=subprotocols, + select_subprotocol=protocol_select_subprotocol, + max_size=max_size, + logger=logger, + ) + # This is a connection in websockets and a protocol in asyncio. + connection = create_connection( + protocol, + self.server, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_queue=max_queue, + write_limit=write_limit, + ) + return connection + + loop = asyncio.get_running_loop() + if kwargs.pop("unix", False): + self.create_server = loop.create_unix_server(factory, **kwargs) + else: + # mypy cannot tell that kwargs must provide sock when port is None. + self.create_server = loop.create_server(factory, host, port, **kwargs) # type: ignore[arg-type] + + # async with serve(...) as ...: ... + + async def __aenter__(self) -> Server: + return await self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + self.server.close() + await self.server.wait_closed() + + # ... = await serve(...) + + def __await__(self) -> Generator[Any, None, Server]: + # Create a suitable iterator by calling __await__ on a coroutine. + return self.__await_impl__().__await__() + + async def __await_impl__(self) -> Server: + server = await self.create_server + self.server.wrap(server) + return self.server + + # ... = yield from serve(...) - remove when dropping Python < 3.11 + + __iter__ = __await__ + + +def unix_serve( + handler: Callable[[ServerConnection], Awaitable[None]], + path: str | None = None, + **kwargs: Any, +) -> Awaitable[Server]: + """ + Create a WebSocket server listening on a Unix socket. + + This function is identical to :func:`serve`, except the ``host`` and + ``port`` arguments are replaced by ``path``. It's only available on Unix. + + It's useful for deploying a server behind a reverse proxy such as nginx. + + Args: + handler: Connection handler. It receives the WebSocket connection, + which is a :class:`ServerConnection`, in argument. + path: File system path to the Unix socket. + + """ + return serve(handler, unix=True, path=path, **kwargs) + + +def is_credentials(credentials: Any) -> bool: + try: + username, password = credentials + except (TypeError, ValueError): + return False + else: + return isinstance(username, str) and isinstance(password, str) + + +def basic_auth( + realm: str = "", + credentials: tuple[str, str] | Iterable[tuple[str, str]] | None = None, + check_credentials: Callable[[str, str], Awaitable[bool] | bool] | None = None, +) -> Callable[[ServerConnection, Request], Awaitable[Response | None]]: + """ + Factory for ``process_request`` to enforce HTTP Basic Authentication. + + :func:`basic_auth` is designed to integrate with :func:`serve` as follows:: + + from websockets.asyncio.server import basic_auth, serve + + async with serve( + ..., + process_request=basic_auth( + realm="my dev server", + credentials=("hello", "iloveyou"), + ), + ): + + If authentication succeeds, the connection's ``username`` attribute is set. + If it fails, the server responds with an HTTP 401 Unauthorized status. + + One of ``credentials`` or ``check_credentials`` must be provided; not both. + + Args: + realm: Scope of protection. It should contain only ASCII characters + because the encoding of non-ASCII characters is undefined. Refer to + section 2.2 of :rfc:`7235` for details. + credentials: Hard coded authorized credentials. It can be a + ``(username, password)`` pair or a list of such pairs. + check_credentials: Function or coroutine that verifies credentials. + It receives ``username`` and ``password`` arguments and returns + whether they're valid. + Raises: + TypeError: If ``credentials`` or ``check_credentials`` is wrong. + ValueError: If ``credentials`` and ``check_credentials`` are both + provided or both not provided. + + """ + if (credentials is None) == (check_credentials is None): + raise ValueError("provide either credentials or check_credentials") + + if credentials is not None: + if is_credentials(credentials): + credentials_list = [cast(tuple[str, str], credentials)] + elif isinstance(credentials, Iterable): + credentials_list = list(cast(Iterable[tuple[str, str]], credentials)) + if not all(is_credentials(item) for item in credentials_list): + raise TypeError(f"invalid credentials argument: {credentials}") + else: + raise TypeError(f"invalid credentials argument: {credentials}") + + credentials_dict = dict(credentials_list) + + def check_credentials(username: str, password: str) -> bool: + try: + expected_password = credentials_dict[username] + except KeyError: + return False + return hmac.compare_digest(expected_password, password) + + assert check_credentials is not None # help mypy + + async def process_request( + connection: ServerConnection, + request: Request, + ) -> Response | None: + """ + Perform HTTP Basic Authentication. + + If it succeeds, set the connection's ``username`` attribute and return + :obj:`None`. If it fails, return an HTTP 401 Unauthorized responss. + + """ + try: + authorization = request.headers["Authorization"] + except KeyError: + response = connection.respond( + http.HTTPStatus.UNAUTHORIZED, + "Missing credentials\n", + ) + response.headers["WWW-Authenticate"] = build_www_authenticate_basic(realm) + return response + + try: + username, password = parse_authorization_basic(authorization) + except InvalidHeader: + response = connection.respond( + http.HTTPStatus.UNAUTHORIZED, + "Unsupported credentials\n", + ) + response.headers["WWW-Authenticate"] = build_www_authenticate_basic(realm) + return response + + valid_credentials = check_credentials(username, password) + if isinstance(valid_credentials, Awaitable): + valid_credentials = await valid_credentials + + if not valid_credentials: + response = connection.respond( + http.HTTPStatus.UNAUTHORIZED, + "Invalid credentials\n", + ) + response.headers["WWW-Authenticate"] = build_www_authenticate_basic(realm) + return response + + connection.username = username + return None + + return process_request diff --git a/.venv/lib/python3.14/site-packages/websockets/auth.py b/.venv/lib/python3.14/site-packages/websockets/auth.py new file mode 100644 index 0000000000000000000000000000000000000000..15b70a3727b2eb3202fc87173ad2fc8b742cf72c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/auth.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import warnings + + +with warnings.catch_warnings(): + # Suppress redundant DeprecationWarning raised by websockets.legacy. + warnings.filterwarnings("ignore", category=DeprecationWarning) + from .legacy.auth import * + from .legacy.auth import __all__ # noqa: F401 + + +warnings.warn( # deprecated in 14.0 - 2024-11-09 + "websockets.auth, an alias for websockets.legacy.auth, is deprecated; " + "see https://websockets.readthedocs.io/en/stable/howto/upgrade.html " + "for upgrade instructions", + DeprecationWarning, +) diff --git a/.venv/lib/python3.14/site-packages/websockets/cli.py b/.venv/lib/python3.14/site-packages/websockets/cli.py new file mode 100644 index 0000000000000000000000000000000000000000..e084b62a9ac250a7b20a1b7a7a18fe10e53db3a6 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/cli.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import argparse +import asyncio +import os +import sys +from typing import Generator + +from .asyncio.client import ClientConnection, connect +from .asyncio.messages import SimpleQueue +from .exceptions import ConnectionClosed +from .frames import Close +from .streams import StreamReader +from .version import version as websockets_version + + +__all__ = ["main"] + + +def print_during_input(string: str) -> None: + sys.stdout.write( + # Save cursor position + "\N{ESC}7" + # Add a new line + "\N{LINE FEED}" + # Move cursor up + "\N{ESC}[A" + # Insert blank line, scroll last line down + "\N{ESC}[L" + # Print string in the inserted blank line + f"{string}\N{LINE FEED}" + # Restore cursor position + "\N{ESC}8" + # Move cursor down + "\N{ESC}[B" + ) + sys.stdout.flush() + + +def print_over_input(string: str) -> None: + sys.stdout.write( + # Move cursor to beginning of line + "\N{CARRIAGE RETURN}" + # Delete current line + "\N{ESC}[K" + # Print string + f"{string}\N{LINE FEED}" + ) + sys.stdout.flush() + + +class ReadLines(asyncio.Protocol): + def __init__(self) -> None: + self.reader = StreamReader() + self.messages: SimpleQueue[str] = SimpleQueue() + + def parse(self) -> Generator[None, None, None]: + while True: + sys.stdout.write("> ") + sys.stdout.flush() + line = yield from self.reader.read_line(sys.maxsize) + self.messages.put(line.decode().rstrip("\r\n")) + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + self.parser = self.parse() + next(self.parser) + + def data_received(self, data: bytes) -> None: + self.reader.feed_data(data) + next(self.parser) + + def eof_received(self) -> None: + self.reader.feed_eof() + # next(self.parser) isn't useful and would raise EOFError. + + def connection_lost(self, exc: Exception | None) -> None: + self.reader.discard() + self.messages.abort() + + +async def print_incoming_messages(websocket: ClientConnection) -> None: + async for message in websocket: + if isinstance(message, str): + print_during_input("< " + message) + else: + print_during_input("< (binary) " + message.hex()) + + +async def send_outgoing_messages( + websocket: ClientConnection, + messages: SimpleQueue[str], +) -> None: + while True: + try: + message = await messages.get() + except EOFError: + break + try: + await websocket.send(message) + except ConnectionClosed: # pragma: no cover + break + + +async def interactive_client(uri: str) -> None: + try: + websocket = await connect(uri) + except Exception as exc: + print(f"Failed to connect to {uri}: {exc}.") + sys.exit(1) + else: + print(f"Connected to {uri}.") + + loop = asyncio.get_running_loop() + transport, protocol = await loop.connect_read_pipe(ReadLines, sys.stdin) + incoming = asyncio.create_task( + print_incoming_messages(websocket), + ) + outgoing = asyncio.create_task( + send_outgoing_messages(websocket, protocol.messages), + ) + try: + await asyncio.wait( + [incoming, outgoing], + # Clean up and exit when the server closes the connection + # or the user enters EOT (^D), whichever happens first. + return_when=asyncio.FIRST_COMPLETED, + ) + # asyncio.run() cancels the main task when the user triggers SIGINT (^C). + # https://docs.python.org/3/library/asyncio-runner.html#handling-keyboard-interruption + # Clean up and exit without re-raising CancelledError to prevent Python + # from raising KeyboardInterrupt and displaying a stack track. + except asyncio.CancelledError: # pragma: no cover + pass + finally: + incoming.cancel() + outgoing.cancel() + transport.close() + + await websocket.close() + assert websocket.close_code is not None and websocket.close_reason is not None + close_status = Close(websocket.close_code, websocket.close_reason) + print_over_input(f"Connection closed: {close_status}.") + + +def main(argv: list[str] | None = None) -> None: + parser = argparse.ArgumentParser( + prog="websockets", + description="Interactive WebSocket client.", + add_help=False, + ) + group = parser.add_mutually_exclusive_group() + group.add_argument("--version", action="store_true") + group.add_argument("uri", metavar="", nargs="?") + args = parser.parse_args(argv) + + if args.version: + print(f"websockets {websockets_version}") + return + + if args.uri is None: + parser.print_usage() + sys.exit(2) + + # Enable VT100 to support ANSI escape codes in Command Prompt on Windows. + # See https://github.com/python/cpython/issues/74261 for why this works. + if sys.platform == "win32": + os.system("") + + try: + import readline # noqa: F401 + except ImportError: # readline isn't available on all platforms + pass + + # Remove the try/except block when dropping Python < 3.11. + try: + asyncio.run(interactive_client(args.uri)) + except KeyboardInterrupt: # pragma: no cover + pass diff --git a/.venv/lib/python3.14/site-packages/websockets/client.py b/.venv/lib/python3.14/site-packages/websockets/client.py new file mode 100644 index 0000000000000000000000000000000000000000..0fbcda60ccfc6646ed4b81d0f9681d0c6b391510 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/client.py @@ -0,0 +1,391 @@ +from __future__ import annotations + +import os +import random +import warnings +from collections.abc import Generator, Sequence +from typing import Any + +from .datastructures import Headers, MultipleValuesError +from .exceptions import ( + InvalidHandshake, + InvalidHeader, + InvalidHeaderValue, + InvalidMessage, + InvalidStatus, + InvalidUpgrade, + NegotiationError, +) +from .extensions import ClientExtensionFactory, Extension +from .headers import ( + build_authorization_basic, + build_extension, + build_host, + build_subprotocol, + parse_connection, + parse_extension, + parse_subprotocol, + parse_upgrade, +) +from .http11 import Request, Response +from .imports import lazy_import +from .protocol import CLIENT, CONNECTING, OPEN, Protocol, State +from .typing import ( + ConnectionOption, + ExtensionHeader, + LoggerLike, + Origin, + Subprotocol, + UpgradeProtocol, +) +from .uri import WebSocketURI +from .utils import accept_key, generate_key + + +__all__ = ["ClientProtocol"] + + +class ClientProtocol(Protocol): + """ + Sans-I/O implementation of a WebSocket client connection. + + Args: + uri: URI of the WebSocket server, parsed + with :func:`~websockets.uri.parse_uri`. + origin: Value of the ``Origin`` header. This is useful when connecting + to a server that validates the ``Origin`` header to defend against + Cross-Site WebSocket Hijacking attacks. + extensions: List of supported extensions, in order in which they + should be tried. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + state: Initial state of the WebSocket connection. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. You may pass a ``(max_message_size, + max_fragment_size)`` tuple to set different limits for messages and + fragments when you expect long messages sent in short fragments. + logger: Logger for this connection; + defaults to ``logging.getLogger("websockets.client")``; + see the :doc:`logging guide <../../topics/logging>` for details. + + """ + + def __init__( + self, + uri: WebSocketURI, + *, + origin: Origin | None = None, + extensions: Sequence[ClientExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + state: State = CONNECTING, + max_size: int | None | tuple[int | None, int | None] = 2**20, + logger: LoggerLike | None = None, + ) -> None: + super().__init__( + side=CLIENT, + state=state, + max_size=max_size, + logger=logger, + ) + self.uri = uri + self.origin = origin + self.available_extensions = extensions + self.available_subprotocols = subprotocols + self.key = generate_key() + + def connect(self) -> Request: + """ + Create a handshake request to open a connection. + + You must send the handshake request with :meth:`send_request`. + + You can modify it before sending it, for example to add HTTP headers. + + Returns: + WebSocket handshake request event to send to the server. + + """ + headers = Headers() + headers["Host"] = build_host(self.uri.host, self.uri.port, self.uri.secure) + if self.uri.user_info: + headers["Authorization"] = build_authorization_basic(*self.uri.user_info) + if self.origin is not None: + headers["Origin"] = self.origin + headers["Upgrade"] = "websocket" + headers["Connection"] = "Upgrade" + headers["Sec-WebSocket-Key"] = self.key + headers["Sec-WebSocket-Version"] = "13" + if self.available_extensions is not None: + headers["Sec-WebSocket-Extensions"] = build_extension( + [ + (extension_factory.name, extension_factory.get_request_params()) + for extension_factory in self.available_extensions + ] + ) + if self.available_subprotocols is not None: + headers["Sec-WebSocket-Protocol"] = build_subprotocol( + self.available_subprotocols + ) + return Request(self.uri.resource_name, headers) + + def process_response(self, response: Response) -> None: + """ + Check a handshake response. + + Args: + request: WebSocket handshake response received from the server. + + Raises: + InvalidHandshake: If the handshake response is invalid. + + """ + + if response.status_code != 101: + raise InvalidStatus(response) + + headers = response.headers + + connection: list[ConnectionOption] = sum( + [parse_connection(value) for value in headers.get_all("Connection")], [] + ) + if not any(value.lower() == "upgrade" for value in connection): + raise InvalidUpgrade( + "Connection", ", ".join(connection) if connection else None + ) + + upgrade: list[UpgradeProtocol] = sum( + [parse_upgrade(value) for value in headers.get_all("Upgrade")], [] + ) + # For compatibility with non-strict implementations, ignore case when + # checking the Upgrade header. It's supposed to be 'WebSocket'. + if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"): + raise InvalidUpgrade("Upgrade", ", ".join(upgrade) if upgrade else None) + + try: + s_w_accept = headers["Sec-WebSocket-Accept"] + except KeyError: + raise InvalidHeader("Sec-WebSocket-Accept") from None + except MultipleValuesError: + raise InvalidHeader("Sec-WebSocket-Accept", "multiple values") from None + if s_w_accept != accept_key(self.key): + raise InvalidHeaderValue("Sec-WebSocket-Accept", s_w_accept) + + self.extensions = self.process_extensions(headers) + self.subprotocol = self.process_subprotocol(headers) + + def process_extensions(self, headers: Headers) -> list[Extension]: + """ + Handle the Sec-WebSocket-Extensions HTTP response header. + + Check that each extension is supported, as well as its parameters. + + :rfc:`6455` leaves the rules up to the specification of each + extension. + + To provide this level of flexibility, for each extension accepted by + the server, we check for a match with each extension available in the + client configuration. If no match is found, an exception is raised. + + If several variants of the same extension are accepted by the server, + it may be configured several times, which won't make sense in general. + Extensions must implement their own requirements. For this purpose, + the list of previously accepted extensions is provided. + + Other requirements, for example related to mandatory extensions or the + order of extensions, may be implemented by overriding this method. + + Args: + headers: WebSocket handshake response headers. + + Returns: + List of accepted extensions. + + Raises: + InvalidHandshake: To abort the handshake. + + """ + accepted_extensions: list[Extension] = [] + + extensions = headers.get_all("Sec-WebSocket-Extensions") + + if extensions: + if self.available_extensions is None: + raise NegotiationError("no extensions supported") + + parsed_extensions: list[ExtensionHeader] = sum( + [parse_extension(header_value) for header_value in extensions], [] + ) + + for name, response_params in parsed_extensions: + for extension_factory in self.available_extensions: + # Skip non-matching extensions based on their name. + if extension_factory.name != name: + continue + + # Skip non-matching extensions based on their params. + try: + extension = extension_factory.process_response_params( + response_params, accepted_extensions + ) + except NegotiationError: + continue + + # Add matching extension to the final list. + accepted_extensions.append(extension) + + # Break out of the loop once we have a match. + break + + # If we didn't break from the loop, no extension in our list + # matched what the server sent. Fail the connection. + else: + raise NegotiationError( + f"Unsupported extension: " + f"name = {name}, params = {response_params}" + ) + + return accepted_extensions + + def process_subprotocol(self, headers: Headers) -> Subprotocol | None: + """ + Handle the Sec-WebSocket-Protocol HTTP response header. + + If provided, check that it contains exactly one supported subprotocol. + + Args: + headers: WebSocket handshake response headers. + + Returns: + Subprotocol, if one was selected. + + """ + subprotocol: Subprotocol | None = None + + subprotocols = headers.get_all("Sec-WebSocket-Protocol") + + if subprotocols: + if self.available_subprotocols is None: + raise NegotiationError("no subprotocols supported") + + parsed_subprotocols: Sequence[Subprotocol] = sum( + [parse_subprotocol(header_value) for header_value in subprotocols], [] + ) + if len(parsed_subprotocols) > 1: + raise InvalidHeader( + "Sec-WebSocket-Protocol", + f"multiple values: {', '.join(parsed_subprotocols)}", + ) + + subprotocol = parsed_subprotocols[0] + if subprotocol not in self.available_subprotocols: + raise NegotiationError(f"unsupported subprotocol: {subprotocol}") + + return subprotocol + + def send_request(self, request: Request) -> None: + """ + Send a handshake request to the server. + + Args: + request: WebSocket handshake request event. + + """ + if self.debug: + self.logger.debug("> GET %s HTTP/1.1", request.path) + for key, value in request.headers.raw_items(): + self.logger.debug("> %s: %s", key, value) + + self.writes.append(request.serialize()) + + def parse(self) -> Generator[None]: + if self.state is CONNECTING: + try: + response = yield from Response.parse( + self.reader.read_line, + self.reader.read_exact, + self.reader.read_to_eof, + ) + except Exception as exc: + self.handshake_exc = InvalidMessage( + "did not receive a valid HTTP response" + ) + self.handshake_exc.__cause__ = exc + self.send_eof() + self.parser = self.discard() + next(self.parser) # start coroutine + yield + + if self.debug: + code, phrase = response.status_code, response.reason_phrase + self.logger.debug("< HTTP/1.1 %d %s", code, phrase) + for key, value in response.headers.raw_items(): + self.logger.debug("< %s: %s", key, value) + if response.body: + self.logger.debug("< [body] (%d bytes)", len(response.body)) + + try: + self.process_response(response) + except InvalidHandshake as exc: + response._exception = exc + self.events.append(response) + self.handshake_exc = exc + self.send_eof() + self.parser = self.discard() + next(self.parser) # start coroutine + yield + + assert self.state is CONNECTING + self.state = OPEN + self.events.append(response) + + yield from super().parse() + + +class ClientConnection(ClientProtocol): + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn( # deprecated in 11.0 - 2023-04-02 + "ClientConnection was renamed to ClientProtocol", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) + + +BACKOFF_INITIAL_DELAY = float(os.environ.get("WEBSOCKETS_BACKOFF_INITIAL_DELAY", "5")) +BACKOFF_MIN_DELAY = float(os.environ.get("WEBSOCKETS_BACKOFF_MIN_DELAY", "3.1")) +BACKOFF_MAX_DELAY = float(os.environ.get("WEBSOCKETS_BACKOFF_MAX_DELAY", "90.0")) +BACKOFF_FACTOR = float(os.environ.get("WEBSOCKETS_BACKOFF_FACTOR", "1.618")) + + +def backoff( + initial_delay: float = BACKOFF_INITIAL_DELAY, + min_delay: float = BACKOFF_MIN_DELAY, + max_delay: float = BACKOFF_MAX_DELAY, + factor: float = BACKOFF_FACTOR, +) -> Generator[float]: + """ + Generate a series of backoff delays between reconnection attempts. + + Yields: + How many seconds to wait before retrying to connect. + + """ + # Add a random initial delay between 0 and 5 seconds. + # See 7.2.3. Recovering from Abnormal Closure in RFC 6455. + yield random.random() * initial_delay + delay = min_delay + while delay < max_delay: + yield delay + delay *= factor + while True: + yield max_delay + + +lazy_import( + globals(), + deprecated_aliases={ + # deprecated in 14.0 - 2024-11-09 + "WebSocketClientProtocol": ".legacy.client", + "connect": ".legacy.client", + "unix_connect": ".legacy.client", + }, +) diff --git a/.venv/lib/python3.14/site-packages/websockets/connection.py b/.venv/lib/python3.14/site-packages/websockets/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..5e78e34479224d0332b165badd67a8933e0c73db --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/connection.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +import warnings + +from .protocol import SEND_EOF, Protocol as Connection, Side, State # noqa: F401 + + +warnings.warn( # deprecated in 11.0 - 2023-04-02 + "websockets.connection was renamed to websockets.protocol " + "and Connection was renamed to Protocol", + DeprecationWarning, +) diff --git a/.venv/lib/python3.14/site-packages/websockets/datastructures.py b/.venv/lib/python3.14/site-packages/websockets/datastructures.py new file mode 100644 index 0000000000000000000000000000000000000000..6d5d66d9aef166965dc66a592ef5298ad2a21da3 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/datastructures.py @@ -0,0 +1,183 @@ +from __future__ import annotations + +from collections.abc import Iterable, Iterator, Mapping, MutableMapping +from typing import Any, Protocol + + +__all__ = [ + "Headers", + "HeadersLike", + "MultipleValuesError", +] + + +class MultipleValuesError(LookupError): + """ + Exception raised when :class:`Headers` has multiple values for a key. + + """ + + def __str__(self) -> str: + # Implement the same logic as KeyError_str in Objects/exceptions.c. + if len(self.args) == 1: + return repr(self.args[0]) + return super().__str__() + + +class Headers(MutableMapping[str, str]): + """ + Efficient data structure for manipulating HTTP headers. + + A :class:`list` of ``(name, values)`` is inefficient for lookups. + + A :class:`dict` doesn't suffice because header names are case-insensitive + and multiple occurrences of headers with the same name are possible. + + :class:`Headers` stores HTTP headers in a hybrid data structure to provide + efficient insertions and lookups while preserving the original data. + + In order to account for multiple values with minimal hassle, + :class:`Headers` follows this logic: + + - When getting a header with ``headers[name]``: + - if there's no value, :exc:`KeyError` is raised; + - if there's exactly one value, it's returned; + - if there's more than one value, :exc:`MultipleValuesError` is raised. + + - When setting a header with ``headers[name] = value``, the value is + appended to the list of values for that header. + + - When deleting a header with ``del headers[name]``, all values for that + header are removed (this is slow). + + Other methods for manipulating headers are consistent with this logic. + + As long as no header occurs multiple times, :class:`Headers` behaves like + :class:`dict`, except keys are lower-cased to provide case-insensitivity. + + Two methods support manipulating multiple values explicitly: + + - :meth:`get_all` returns a list of all values for a header; + - :meth:`raw_items` returns an iterator of ``(name, values)`` pairs. + + """ + + __slots__ = ["_dict", "_list"] + + # Like dict, Headers accepts an optional "mapping or iterable" argument. + def __init__(self, *args: HeadersLike, **kwargs: str) -> None: + self._dict: dict[str, list[str]] = {} + self._list: list[tuple[str, str]] = [] + self.update(*args, **kwargs) + + def __str__(self) -> str: + return "".join(f"{key}: {value}\r\n" for key, value in self._list) + "\r\n" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self._list!r})" + + def copy(self) -> Headers: + copy = self.__class__() + copy._dict = self._dict.copy() + copy._list = self._list.copy() + return copy + + def serialize(self) -> bytes: + # Since headers only contain ASCII characters, we can keep this simple. + return str(self).encode() + + # Collection methods + + def __contains__(self, key: object) -> bool: + return isinstance(key, str) and key.lower() in self._dict + + def __iter__(self) -> Iterator[str]: + return iter(self._dict) + + def __len__(self) -> int: + return len(self._dict) + + # MutableMapping methods + + def __getitem__(self, key: str) -> str: + value = self._dict[key.lower()] + if len(value) == 1: + return value[0] + else: + raise MultipleValuesError(key) + + def __setitem__(self, key: str, value: str) -> None: + self._dict.setdefault(key.lower(), []).append(value) + self._list.append((key, value)) + + def __delitem__(self, key: str) -> None: + key_lower = key.lower() + self._dict.__delitem__(key_lower) + # This is inefficient. Fortunately deleting HTTP headers is uncommon. + self._list = [(k, v) for k, v in self._list if k.lower() != key_lower] + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Headers): + return NotImplemented + return self._dict == other._dict + + def clear(self) -> None: + """ + Remove all headers. + + """ + self._dict = {} + self._list = [] + + def update(self, *args: HeadersLike, **kwargs: str) -> None: + """ + Update from a :class:`Headers` instance and/or keyword arguments. + + """ + args = tuple( + arg.raw_items() if isinstance(arg, Headers) else arg for arg in args + ) + super().update(*args, **kwargs) + + # Methods for handling multiple values + + def get_all(self, key: str) -> list[str]: + """ + Return the (possibly empty) list of all values for a header. + + Args: + key: Header name. + + """ + return self._dict.get(key.lower(), []) + + def raw_items(self) -> Iterator[tuple[str, str]]: + """ + Return an iterator of all values as ``(name, value)`` pairs. + + """ + return iter(self._list) + + +# copy of _typeshed.SupportsKeysAndGetItem. +class SupportsKeysAndGetItem(Protocol): # pragma: no cover + """ + Dict-like types with ``keys() -> str`` and ``__getitem__(key: str) -> str`` methods. + + """ + + def keys(self) -> Iterable[str]: ... + + def __getitem__(self, key: str) -> str: ... + + +HeadersLike = ( + Headers | Mapping[str, str] | Iterable[tuple[str, str]] | SupportsKeysAndGetItem +) +""" +Types accepted where :class:`Headers` is expected. + +In addition to :class:`Headers` itself, this includes dict-like types where both +keys and values are :class:`str`. + +""" diff --git a/.venv/lib/python3.14/site-packages/websockets/exceptions.py b/.venv/lib/python3.14/site-packages/websockets/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..a88deaa66f6acf8d487d36fc0777c29016d0903d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/exceptions.py @@ -0,0 +1,473 @@ +""" +:mod:`websockets.exceptions` defines the following hierarchy of exceptions. + +* :exc:`WebSocketException` + * :exc:`ConnectionClosed` + * :exc:`ConnectionClosedOK` + * :exc:`ConnectionClosedError` + * :exc:`InvalidURI` + * :exc:`InvalidProxy` + * :exc:`InvalidHandshake` + * :exc:`SecurityError` + * :exc:`ProxyError` + * :exc:`InvalidProxyMessage` + * :exc:`InvalidProxyStatus` + * :exc:`InvalidMessage` + * :exc:`InvalidStatus` + * :exc:`InvalidStatusCode` (legacy) + * :exc:`InvalidHeader` + * :exc:`InvalidHeaderFormat` + * :exc:`InvalidHeaderValue` + * :exc:`InvalidOrigin` + * :exc:`InvalidUpgrade` + * :exc:`NegotiationError` + * :exc:`DuplicateParameter` + * :exc:`InvalidParameterName` + * :exc:`InvalidParameterValue` + * :exc:`AbortHandshake` (legacy) + * :exc:`RedirectHandshake` (legacy) + * :exc:`ProtocolError` (Sans-I/O) + * :exc:`PayloadTooBig` (Sans-I/O) + * :exc:`InvalidState` (Sans-I/O) + * :exc:`ConcurrencyError` + +""" + +from __future__ import annotations + +import warnings + +from .imports import lazy_import + + +__all__ = [ + "WebSocketException", + "ConnectionClosed", + "ConnectionClosedOK", + "ConnectionClosedError", + "InvalidURI", + "InvalidProxy", + "InvalidHandshake", + "SecurityError", + "ProxyError", + "InvalidProxyMessage", + "InvalidProxyStatus", + "InvalidMessage", + "InvalidStatus", + "InvalidHeader", + "InvalidHeaderFormat", + "InvalidHeaderValue", + "InvalidOrigin", + "InvalidUpgrade", + "NegotiationError", + "DuplicateParameter", + "InvalidParameterName", + "InvalidParameterValue", + "ProtocolError", + "PayloadTooBig", + "InvalidState", + "ConcurrencyError", +] + + +class WebSocketException(Exception): + """ + Base class for all exceptions defined by websockets. + + """ + + +class ConnectionClosed(WebSocketException): + """ + Raised when trying to interact with a closed connection. + + Attributes: + rcvd: If a close frame was received, its code and reason are available + in ``rcvd.code`` and ``rcvd.reason``. + sent: If a close frame was sent, its code and reason are available + in ``sent.code`` and ``sent.reason``. + rcvd_then_sent: If close frames were received and sent, this attribute + tells in which order this happened, from the perspective of this + side of the connection. + + """ + + def __init__( + self, + rcvd: frames.Close | None, + sent: frames.Close | None, + rcvd_then_sent: bool | None = None, + ) -> None: + self.rcvd = rcvd + self.sent = sent + self.rcvd_then_sent = rcvd_then_sent + assert (self.rcvd_then_sent is None) == (self.rcvd is None or self.sent is None) + + def __str__(self) -> str: + if self.rcvd is None: + if self.sent is None: + return "no close frame received or sent" + else: + return f"sent {self.sent}; no close frame received" + else: + if self.sent is None: + return f"received {self.rcvd}; no close frame sent" + else: + if self.rcvd_then_sent: + return f"received {self.rcvd}; then sent {self.sent}" + else: + return f"sent {self.sent}; then received {self.rcvd}" + + # code and reason attributes are provided for backwards-compatibility + + @property + def code(self) -> int: + warnings.warn( # deprecated in 13.1 - 2024-09-21 + "ConnectionClosed.code is deprecated; " + "use Protocol.close_code or ConnectionClosed.rcvd.code", + DeprecationWarning, + ) + if self.rcvd is None: + return frames.CloseCode.ABNORMAL_CLOSURE + return self.rcvd.code + + @property + def reason(self) -> str: + warnings.warn( # deprecated in 13.1 - 2024-09-21 + "ConnectionClosed.reason is deprecated; " + "use Protocol.close_reason or ConnectionClosed.rcvd.reason", + DeprecationWarning, + ) + if self.rcvd is None: + return "" + return self.rcvd.reason + + +class ConnectionClosedOK(ConnectionClosed): + """ + Like :exc:`ConnectionClosed`, when the connection terminated properly. + + A close code with code 1000 (OK) or 1001 (going away) or without a code was + received and sent. + + """ + + +class ConnectionClosedError(ConnectionClosed): + """ + Like :exc:`ConnectionClosed`, when the connection terminated with an error. + + A close frame with a code other than 1000 (OK) or 1001 (going away) was + received or sent, or the closing handshake didn't complete properly. + + """ + + +class InvalidURI(WebSocketException): + """ + Raised when connecting to a URI that isn't a valid WebSocket URI. + + """ + + def __init__(self, uri: str, msg: str) -> None: + self.uri = uri + self.msg = msg + + def __str__(self) -> str: + return f"{self.uri} isn't a valid URI: {self.msg}" + + +class InvalidProxy(WebSocketException): + """ + Raised when connecting via a proxy that isn't valid. + + """ + + def __init__(self, proxy: str, msg: str) -> None: + self.proxy = proxy + self.msg = msg + + def __str__(self) -> str: + return f"{self.proxy} isn't a valid proxy: {self.msg}" + + +class InvalidHandshake(WebSocketException): + """ + Base class for exceptions raised when the opening handshake fails. + + """ + + +class SecurityError(InvalidHandshake): + """ + Raised when a handshake request or response breaks a security rule. + + Security limits can be configured with :doc:`environment variables + <../reference/variables>`. + + """ + + +class ProxyError(InvalidHandshake): + """ + Raised when failing to connect to a proxy. + + """ + + +class InvalidProxyMessage(ProxyError): + """ + Raised when an HTTP proxy response is malformed. + + """ + + +class InvalidProxyStatus(ProxyError): + """ + Raised when an HTTP proxy rejects the connection. + + """ + + def __init__(self, response: http11.Response) -> None: + self.response = response + + def __str__(self) -> str: + return f"proxy rejected connection: HTTP {self.response.status_code:d}" + + +class InvalidMessage(InvalidHandshake): + """ + Raised when a handshake request or response is malformed. + + """ + + +class InvalidStatus(InvalidHandshake): + """ + Raised when a handshake response rejects the WebSocket upgrade. + + """ + + def __init__(self, response: http11.Response) -> None: + self.response = response + + def __str__(self) -> str: + return ( + f"server rejected WebSocket connection: HTTP {self.response.status_code:d}" + ) + + +class InvalidHeader(InvalidHandshake): + """ + Raised when an HTTP header doesn't have a valid format or value. + + """ + + def __init__(self, name: str, value: str | None = None) -> None: + self.name = name + self.value = value + + def __str__(self) -> str: + if self.value is None: + return f"missing {self.name} header" + elif self.value == "": + return f"empty {self.name} header" + else: + return f"invalid {self.name} header: {self.value}" + + +class InvalidHeaderFormat(InvalidHeader): + """ + Raised when an HTTP header cannot be parsed. + + The format of the header doesn't match the grammar for that header. + + """ + + def __init__(self, name: str, error: str, header: str, pos: int) -> None: + super().__init__(name, f"{error} at {pos} in {header}") + + +class InvalidHeaderValue(InvalidHeader): + """ + Raised when an HTTP header has a wrong value. + + The format of the header is correct but the value isn't acceptable. + + """ + + +class InvalidOrigin(InvalidHeader): + """ + Raised when the Origin header in a request isn't allowed. + + """ + + def __init__(self, origin: str | None) -> None: + super().__init__("Origin", origin) + + +class InvalidUpgrade(InvalidHeader): + """ + Raised when the Upgrade or Connection header isn't correct. + + """ + + +class NegotiationError(InvalidHandshake): + """ + Raised when negotiating an extension or a subprotocol fails. + + """ + + +class DuplicateParameter(NegotiationError): + """ + Raised when a parameter name is repeated in an extension header. + + """ + + def __init__(self, name: str) -> None: + self.name = name + + def __str__(self) -> str: + return f"duplicate parameter: {self.name}" + + +class InvalidParameterName(NegotiationError): + """ + Raised when a parameter name in an extension header is invalid. + + """ + + def __init__(self, name: str) -> None: + self.name = name + + def __str__(self) -> str: + return f"invalid parameter name: {self.name}" + + +class InvalidParameterValue(NegotiationError): + """ + Raised when a parameter value in an extension header is invalid. + + """ + + def __init__(self, name: str, value: str | None) -> None: + self.name = name + self.value = value + + def __str__(self) -> str: + if self.value is None: + return f"missing value for parameter {self.name}" + elif self.value == "": + return f"empty value for parameter {self.name}" + else: + return f"invalid value for parameter {self.name}: {self.value}" + + +class ProtocolError(WebSocketException): + """ + Raised when receiving or sending a frame that breaks the protocol. + + The Sans-I/O implementation raises this exception when: + + * receiving or sending a frame that contains invalid data; + * receiving or sending an invalid sequence of frames. + + """ + + +class PayloadTooBig(WebSocketException): + """ + Raised when parsing a frame with a payload that exceeds the maximum size. + + The Sans-I/O layer uses this exception internally. It doesn't bubble up to + the I/O layer. + + The :meth:`~websockets.extensions.Extension.decode` method of extensions + must raise :exc:`PayloadTooBig` if decoding a frame would exceed the limit. + + """ + + def __init__( + self, + size_or_message: int | None | str, + max_size: int | None = None, + current_size: int | None = None, + ) -> None: + if isinstance(size_or_message, str): + assert max_size is None + assert current_size is None + warnings.warn( # deprecated in 14.0 - 2024-11-09 + "PayloadTooBig(message) is deprecated; " + "change to PayloadTooBig(size, max_size)", + DeprecationWarning, + ) + self.message: str | None = size_or_message + else: + self.message = None + self.size: int | None = size_or_message + assert max_size is not None + self.max_size: int = max_size + self.current_size: int | None = None + self.set_current_size(current_size) + + def __str__(self) -> str: + if self.message is not None: + return self.message + else: + message = "frame " + if self.size is not None: + message += f"with {self.size} bytes " + if self.current_size is not None: + message += f"after reading {self.current_size} bytes " + message += f"exceeds limit of {self.max_size} bytes" + return message + + def set_current_size(self, current_size: int | None) -> None: + assert self.current_size is None + if current_size is not None: + self.max_size += current_size + self.current_size = current_size + + +class InvalidState(WebSocketException, AssertionError): + """ + Raised when sending a frame is forbidden in the current state. + + Specifically, the Sans-I/O layer raises this exception when: + + * sending a data frame to a connection in a state other + :attr:`~websockets.protocol.State.OPEN`; + * sending a control frame to a connection in a state other than + :attr:`~websockets.protocol.State.OPEN` or + :attr:`~websockets.protocol.State.CLOSING`. + + """ + + +class ConcurrencyError(WebSocketException, RuntimeError): + """ + Raised when receiving or sending messages concurrently. + + WebSocket is a connection-oriented protocol. Reads must be serialized; so + must be writes. However, reading and writing concurrently is possible. + + """ + + +# At the bottom to break import cycles created by type annotations. +from . import frames, http11 # noqa: E402 + + +lazy_import( + globals(), + deprecated_aliases={ + # deprecated in 14.0 - 2024-11-09 + "AbortHandshake": ".legacy.exceptions", + "InvalidStatusCode": ".legacy.exceptions", + "RedirectHandshake": ".legacy.exceptions", + "WebSocketProtocolError": ".legacy.exceptions", + }, +) diff --git a/.venv/lib/python3.14/site-packages/websockets/extensions/__init__.py b/.venv/lib/python3.14/site-packages/websockets/extensions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..02838b98a5335322daad566de9c0d9d0843fc49a --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/extensions/__init__.py @@ -0,0 +1,4 @@ +from .base import * + + +__all__ = ["Extension", "ClientExtensionFactory", "ServerExtensionFactory"] diff --git a/.venv/lib/python3.14/site-packages/websockets/extensions/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/extensions/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90a01854484cc8c73bded798ad7496bf54cba544 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/extensions/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/extensions/__pycache__/base.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/extensions/__pycache__/base.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d02db859209fcf901fab94d608249de4ca648d64 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/extensions/__pycache__/base.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/extensions/__pycache__/permessage_deflate.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/extensions/__pycache__/permessage_deflate.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25d7cd1f54532c67d59ddca029e927e632eb7d92 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/extensions/__pycache__/permessage_deflate.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/extensions/base.py b/.venv/lib/python3.14/site-packages/websockets/extensions/base.py new file mode 100644 index 0000000000000000000000000000000000000000..2fdc59f0fdae4d28fdcf18b6f9edf8d62ad22f01 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/extensions/base.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +from collections.abc import Sequence + +from ..frames import Frame +from ..typing import ExtensionName, ExtensionParameter + + +__all__ = ["Extension", "ClientExtensionFactory", "ServerExtensionFactory"] + + +class Extension: + """ + Base class for extensions. + + """ + + name: ExtensionName + """Extension identifier.""" + + def decode(self, frame: Frame, *, max_size: int | None = None) -> Frame: + """ + Decode an incoming frame. + + Args: + frame: Incoming frame. + max_size: Maximum payload size in bytes. + + Returns: + Decoded frame. + + Raises: + PayloadTooBig: If decoding the payload exceeds ``max_size``. + + """ + raise NotImplementedError + + def encode(self, frame: Frame) -> Frame: + """ + Encode an outgoing frame. + + Args: + frame: Outgoing frame. + + Returns: + Encoded frame. + + """ + raise NotImplementedError + + +class ClientExtensionFactory: + """ + Base class for client-side extension factories. + + """ + + name: ExtensionName + """Extension identifier.""" + + def get_request_params(self) -> Sequence[ExtensionParameter]: + """ + Build parameters to send to the server for this extension. + + Returns: + Parameters to send to the server. + + """ + raise NotImplementedError + + def process_response_params( + self, + params: Sequence[ExtensionParameter], + accepted_extensions: Sequence[Extension], + ) -> Extension: + """ + Process parameters received from the server. + + Args: + params: Parameters received from the server for this extension. + accepted_extensions: List of previously accepted extensions. + + Returns: + An extension instance. + + Raises: + NegotiationError: If parameters aren't acceptable. + + """ + raise NotImplementedError + + +class ServerExtensionFactory: + """ + Base class for server-side extension factories. + + """ + + name: ExtensionName + """Extension identifier.""" + + def process_request_params( + self, + params: Sequence[ExtensionParameter], + accepted_extensions: Sequence[Extension], + ) -> tuple[list[ExtensionParameter], Extension]: + """ + Process parameters received from the client. + + Args: + params: Parameters received from the client for this extension. + accepted_extensions: List of previously accepted extensions. + + Returns: + To accept the offer, parameters to send to the client for this + extension and an extension instance. + + Raises: + NegotiationError: To reject the offer, if parameters received from + the client aren't acceptable. + + """ + raise NotImplementedError diff --git a/.venv/lib/python3.14/site-packages/websockets/extensions/permessage_deflate.py b/.venv/lib/python3.14/site-packages/websockets/extensions/permessage_deflate.py new file mode 100644 index 0000000000000000000000000000000000000000..2bc63d79929321007578d3e1d96338f099fad085 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/extensions/permessage_deflate.py @@ -0,0 +1,699 @@ +from __future__ import annotations + +import zlib +from collections.abc import Sequence +from typing import Any, Literal + +from .. import frames +from ..exceptions import ( + DuplicateParameter, + InvalidParameterName, + InvalidParameterValue, + NegotiationError, + PayloadTooBig, + ProtocolError, +) +from ..typing import BytesLike, ExtensionName, ExtensionParameter +from .base import ClientExtensionFactory, Extension, ServerExtensionFactory + + +__all__ = [ + "PerMessageDeflate", + "ClientPerMessageDeflateFactory", + "enable_client_permessage_deflate", + "ServerPerMessageDeflateFactory", + "enable_server_permessage_deflate", +] + +_EMPTY_UNCOMPRESSED_BLOCK = b"\x00\x00\xff\xff" + +_MAX_WINDOW_BITS_VALUES = [str(bits) for bits in range(8, 16)] + + +class PerMessageDeflate(Extension): + """ + Per-Message Deflate extension. + + """ + + name = ExtensionName("permessage-deflate") + + def __init__( + self, + remote_no_context_takeover: bool, + local_no_context_takeover: bool, + remote_max_window_bits: int, + local_max_window_bits: int, + compress_settings: dict[Any, Any] | None = None, + ) -> None: + """ + Configure the Per-Message Deflate extension. + + """ + if compress_settings is None: + compress_settings = {} + + assert remote_no_context_takeover in [False, True] + assert local_no_context_takeover in [False, True] + assert 8 <= remote_max_window_bits <= 15 + assert 8 <= local_max_window_bits <= 15 + assert "wbits" not in compress_settings + + self.remote_no_context_takeover = remote_no_context_takeover + self.local_no_context_takeover = local_no_context_takeover + self.remote_max_window_bits = remote_max_window_bits + self.local_max_window_bits = local_max_window_bits + self.compress_settings = compress_settings + + if not self.remote_no_context_takeover: + self.decoder = zlib.decompressobj(wbits=-self.remote_max_window_bits) + + if not self.local_no_context_takeover: + self.encoder = zlib.compressobj( + wbits=-self.local_max_window_bits, + **self.compress_settings, + ) + + # To handle continuation frames properly, we must keep track of + # whether that initial frame was encoded. + self.decode_cont_data = False + # There's no need for self.encode_cont_data because we always encode + # outgoing frames, so it would always be True. + + def __repr__(self) -> str: + return ( + f"PerMessageDeflate(" + f"remote_no_context_takeover={self.remote_no_context_takeover}, " + f"local_no_context_takeover={self.local_no_context_takeover}, " + f"remote_max_window_bits={self.remote_max_window_bits}, " + f"local_max_window_bits={self.local_max_window_bits})" + ) + + def decode( + self, + frame: frames.Frame, + *, + max_size: int | None = None, + ) -> frames.Frame: + """ + Decode an incoming frame. + + """ + # Skip control frames. + if frame.opcode in frames.CTRL_OPCODES: + return frame + + # Handle continuation data frames: + # - skip if the message isn't encoded + # - reset "decode continuation data" flag if it's a final frame + if frame.opcode is frames.OP_CONT: + if not self.decode_cont_data: + return frame + if frame.fin: + self.decode_cont_data = False + + # Handle text and binary data frames: + # - skip if the message isn't encoded + # - unset the rsv1 flag on the first frame of a compressed message + # - set "decode continuation data" flag if it's a non-final frame + else: + if not frame.rsv1: + return frame + if not frame.fin: + self.decode_cont_data = True + + # Re-initialize per-message decoder. + if self.remote_no_context_takeover: + self.decoder = zlib.decompressobj(wbits=-self.remote_max_window_bits) + + # Uncompress data. Protect against zip bombs by preventing zlib from + # decompressing more than max_length bytes (except when the limit is + # disabled with max_size = None). + data: BytesLike + if frame.fin and len(frame.data) < 2044: + # Profiling shows that appending four bytes, which makes a copy, is + # faster than calling decompress() again when data is less than 2kB. + data = bytes(frame.data) + _EMPTY_UNCOMPRESSED_BLOCK + else: + data = frame.data + max_length = 0 if max_size is None else max_size + try: + data = self.decoder.decompress(data, max_length) + if self.decoder.unconsumed_tail: + assert max_size is not None # help mypy + raise PayloadTooBig(None, max_size) + if frame.fin and len(frame.data) >= 2044: + # This cannot generate additional data. + self.decoder.decompress(_EMPTY_UNCOMPRESSED_BLOCK) + except zlib.error as exc: + raise ProtocolError("decompression failed") from exc + + # Allow garbage collection of the decoder if it won't be reused. + if frame.fin and self.remote_no_context_takeover: + del self.decoder + + return frames.Frame( + frame.opcode, + data, + frame.fin, + # Unset the rsv1 flag on the first frame of a compressed message. + False, + frame.rsv2, + frame.rsv3, + ) + + def encode(self, frame: frames.Frame) -> frames.Frame: + """ + Encode an outgoing frame. + + """ + # Skip control frames. + if frame.opcode in frames.CTRL_OPCODES: + return frame + + # Since we always encode messages, there's no "encode continuation + # data" flag similar to "decode continuation data" at this time. + + if frame.opcode is not frames.OP_CONT: + # Re-initialize per-message decoder. + if self.local_no_context_takeover: + self.encoder = zlib.compressobj( + wbits=-self.local_max_window_bits, + **self.compress_settings, + ) + + # Compress data. + data: BytesLike + data = self.encoder.compress(frame.data) + self.encoder.flush(zlib.Z_SYNC_FLUSH) + if frame.fin: + # Sync flush generates between 5 or 6 bytes, ending with the bytes + # 0x00 0x00 0xff 0xff, which must be removed. + assert data[-4:] == _EMPTY_UNCOMPRESSED_BLOCK + # Making a copy is faster than memoryview(a)[:-4] until 2kB. + if len(data) < 2048: + data = data[:-4] + else: + data = memoryview(data)[:-4] + + # Allow garbage collection of the encoder if it won't be reused. + if frame.fin and self.local_no_context_takeover: + del self.encoder + + return frames.Frame( + frame.opcode, + data, + frame.fin, + # Set the rsv1 flag on the first frame of a compressed message. + frame.opcode is not frames.OP_CONT, + frame.rsv2, + frame.rsv3, + ) + + +def _build_parameters( + server_no_context_takeover: bool, + client_no_context_takeover: bool, + server_max_window_bits: int | None, + client_max_window_bits: int | Literal[True] | None, +) -> list[ExtensionParameter]: + """ + Build a list of ``(name, value)`` pairs for some compression parameters. + + """ + params: list[ExtensionParameter] = [] + if server_no_context_takeover: + params.append(("server_no_context_takeover", None)) + if client_no_context_takeover: + params.append(("client_no_context_takeover", None)) + if server_max_window_bits: + params.append(("server_max_window_bits", str(server_max_window_bits))) + if client_max_window_bits is True: # only in handshake requests + params.append(("client_max_window_bits", None)) + elif client_max_window_bits: + params.append(("client_max_window_bits", str(client_max_window_bits))) + return params + + +def _extract_parameters( + params: Sequence[ExtensionParameter], *, is_server: bool +) -> tuple[bool, bool, int | None, int | Literal[True] | None]: + """ + Extract compression parameters from a list of ``(name, value)`` pairs. + + If ``is_server`` is :obj:`True`, ``client_max_window_bits`` may be + provided without a value. This is only allowed in handshake requests. + + """ + server_no_context_takeover: bool = False + client_no_context_takeover: bool = False + server_max_window_bits: int | None = None + client_max_window_bits: int | Literal[True] | None = None + + for name, value in params: + if name == "server_no_context_takeover": + if server_no_context_takeover: + raise DuplicateParameter(name) + if value is None: + server_no_context_takeover = True + else: + raise InvalidParameterValue(name, value) + + elif name == "client_no_context_takeover": + if client_no_context_takeover: + raise DuplicateParameter(name) + if value is None: + client_no_context_takeover = True + else: + raise InvalidParameterValue(name, value) + + elif name == "server_max_window_bits": + if server_max_window_bits is not None: + raise DuplicateParameter(name) + if value in _MAX_WINDOW_BITS_VALUES: + server_max_window_bits = int(value) + else: + raise InvalidParameterValue(name, value) + + elif name == "client_max_window_bits": + if client_max_window_bits is not None: + raise DuplicateParameter(name) + if is_server and value is None: # only in handshake requests + client_max_window_bits = True + elif value in _MAX_WINDOW_BITS_VALUES: + client_max_window_bits = int(value) + else: + raise InvalidParameterValue(name, value) + + else: + raise InvalidParameterName(name) + + return ( + server_no_context_takeover, + client_no_context_takeover, + server_max_window_bits, + client_max_window_bits, + ) + + +class ClientPerMessageDeflateFactory(ClientExtensionFactory): + """ + Client-side extension factory for the Per-Message Deflate extension. + + Parameters behave as described in `section 7.1 of RFC 7692`_. + + .. _section 7.1 of RFC 7692: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1 + + Set them to :obj:`True` to include them in the negotiation offer without a + value or to an integer value to include them with this value. + + Args: + server_no_context_takeover: Prevent server from using context takeover. + client_no_context_takeover: Prevent client from using context takeover. + server_max_window_bits: Maximum size of the server's LZ77 sliding window + in bits, between 8 and 15. + client_max_window_bits: Maximum size of the client's LZ77 sliding window + in bits, between 8 and 15, or :obj:`True` to indicate support without + setting a limit. + compress_settings: Additional keyword arguments for :func:`zlib.compressobj`, + excluding ``wbits``. + + """ + + name = ExtensionName("permessage-deflate") + + def __init__( + self, + server_no_context_takeover: bool = False, + client_no_context_takeover: bool = False, + server_max_window_bits: int | None = None, + client_max_window_bits: int | Literal[True] | None = True, + compress_settings: dict[str, Any] | None = None, + ) -> None: + """ + Configure the Per-Message Deflate extension factory. + + """ + if not (server_max_window_bits is None or 8 <= server_max_window_bits <= 15): + raise ValueError("server_max_window_bits must be between 8 and 15") + if not ( + client_max_window_bits is None + or client_max_window_bits is True + or 8 <= client_max_window_bits <= 15 + ): + raise ValueError("client_max_window_bits must be between 8 and 15") + if compress_settings is not None and "wbits" in compress_settings: + raise ValueError( + "compress_settings must not include wbits, " + "set client_max_window_bits instead" + ) + + self.server_no_context_takeover = server_no_context_takeover + self.client_no_context_takeover = client_no_context_takeover + self.server_max_window_bits = server_max_window_bits + self.client_max_window_bits = client_max_window_bits + self.compress_settings = compress_settings + + def get_request_params(self) -> Sequence[ExtensionParameter]: + """ + Build request parameters. + + """ + return _build_parameters( + self.server_no_context_takeover, + self.client_no_context_takeover, + self.server_max_window_bits, + self.client_max_window_bits, + ) + + def process_response_params( + self, + params: Sequence[ExtensionParameter], + accepted_extensions: Sequence[Extension], + ) -> PerMessageDeflate: + """ + Process response parameters. + + Return an extension instance. + + """ + if any(other.name == self.name for other in accepted_extensions): + raise NegotiationError(f"received duplicate {self.name}") + + # Request parameters are available in instance variables. + + # Load response parameters in local variables. + ( + server_no_context_takeover, + client_no_context_takeover, + server_max_window_bits, + client_max_window_bits, + ) = _extract_parameters(params, is_server=False) + + # After comparing the request and the response, the final + # configuration must be available in the local variables. + + # server_no_context_takeover + # + # Req. Resp. Result + # ------ ------ -------------------------------------------------- + # False False False + # False True True + # True False Error! + # True True True + + if self.server_no_context_takeover: + if not server_no_context_takeover: + raise NegotiationError("expected server_no_context_takeover") + + # client_no_context_takeover + # + # Req. Resp. Result + # ------ ------ -------------------------------------------------- + # False False False + # False True True + # True False True - must change value + # True True True + + if self.client_no_context_takeover: + if not client_no_context_takeover: + client_no_context_takeover = True + + # server_max_window_bits + + # Req. Resp. Result + # ------ ------ -------------------------------------------------- + # None None None + # None 8≤M≤15 M + # 8≤N≤15 None Error! + # 8≤N≤15 8≤M≤N M + # 8≤N≤15 N self.server_max_window_bits: + raise NegotiationError("unsupported server_max_window_bits") + + # client_max_window_bits + + # Req. Resp. Result + # ------ ------ -------------------------------------------------- + # None None None + # None 8≤M≤15 Error! + # True None None + # True 8≤M≤15 M + # 8≤N≤15 None N - must change value + # 8≤N≤15 8≤M≤N M + # 8≤N≤15 N self.client_max_window_bits: + raise NegotiationError("unsupported client_max_window_bits") + + return PerMessageDeflate( + server_no_context_takeover, # remote_no_context_takeover + client_no_context_takeover, # local_no_context_takeover + server_max_window_bits or 15, # remote_max_window_bits + client_max_window_bits or 15, # local_max_window_bits + self.compress_settings, + ) + + +def enable_client_permessage_deflate( + extensions: Sequence[ClientExtensionFactory] | None, +) -> Sequence[ClientExtensionFactory]: + """ + Enable Per-Message Deflate with default settings in client extensions. + + If the extension is already present, perhaps with non-default settings, + the configuration isn't changed. + + """ + if extensions is None: + extensions = [] + if not any( + extension_factory.name == ClientPerMessageDeflateFactory.name + for extension_factory in extensions + ): + extensions = list(extensions) + [ + ClientPerMessageDeflateFactory( + compress_settings={"memLevel": 5}, + ) + ] + return extensions + + +class ServerPerMessageDeflateFactory(ServerExtensionFactory): + """ + Server-side extension factory for the Per-Message Deflate extension. + + Parameters behave as described in `section 7.1 of RFC 7692`_. + + .. _section 7.1 of RFC 7692: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1 + + Set them to :obj:`True` to include them in the negotiation offer without a + value or to an integer value to include them with this value. + + Args: + server_no_context_takeover: Prevent server from using context takeover. + client_no_context_takeover: Prevent client from using context takeover. + server_max_window_bits: Maximum size of the server's LZ77 sliding window + in bits, between 8 and 15. + client_max_window_bits: Maximum size of the client's LZ77 sliding window + in bits, between 8 and 15. + compress_settings: Additional keyword arguments for :func:`zlib.compressobj`, + excluding ``wbits``. + require_client_max_window_bits: Do not enable compression at all if + client doesn't advertise support for ``client_max_window_bits``; + the default behavior is to enable compression without enforcing + ``client_max_window_bits``. + + """ + + name = ExtensionName("permessage-deflate") + + def __init__( + self, + server_no_context_takeover: bool = False, + client_no_context_takeover: bool = False, + server_max_window_bits: int | None = None, + client_max_window_bits: int | None = None, + compress_settings: dict[str, Any] | None = None, + require_client_max_window_bits: bool = False, + ) -> None: + """ + Configure the Per-Message Deflate extension factory. + + """ + if not (server_max_window_bits is None or 8 <= server_max_window_bits <= 15): + raise ValueError("server_max_window_bits must be between 8 and 15") + if not (client_max_window_bits is None or 8 <= client_max_window_bits <= 15): + raise ValueError("client_max_window_bits must be between 8 and 15") + if compress_settings is not None and "wbits" in compress_settings: + raise ValueError( + "compress_settings must not include wbits, " + "set server_max_window_bits instead" + ) + if client_max_window_bits is None and require_client_max_window_bits: + raise ValueError( + "require_client_max_window_bits is enabled, " + "but client_max_window_bits isn't configured" + ) + + self.server_no_context_takeover = server_no_context_takeover + self.client_no_context_takeover = client_no_context_takeover + self.server_max_window_bits = server_max_window_bits + self.client_max_window_bits = client_max_window_bits + self.compress_settings = compress_settings + self.require_client_max_window_bits = require_client_max_window_bits + + def process_request_params( + self, + params: Sequence[ExtensionParameter], + accepted_extensions: Sequence[Extension], + ) -> tuple[list[ExtensionParameter], PerMessageDeflate]: + """ + Process request parameters. + + Return response params and an extension instance. + + """ + if any(other.name == self.name for other in accepted_extensions): + raise NegotiationError(f"skipped duplicate {self.name}") + + # Load request parameters in local variables. + ( + server_no_context_takeover, + client_no_context_takeover, + server_max_window_bits, + client_max_window_bits, + ) = _extract_parameters(params, is_server=True) + + # Configuration parameters are available in instance variables. + + # After comparing the request and the configuration, the response must + # be available in the local variables. + + # server_no_context_takeover + # + # Config Req. Resp. + # ------ ------ -------------------------------------------------- + # False False False + # False True True + # True False True - must change value to True + # True True True + + if self.server_no_context_takeover: + if not server_no_context_takeover: + server_no_context_takeover = True + + # client_no_context_takeover + # + # Config Req. Resp. + # ------ ------ -------------------------------------------------- + # False False False + # False True True (or False) + # True False True - must change value to True + # True True True (or False) + + if self.client_no_context_takeover: + if not client_no_context_takeover: + client_no_context_takeover = True + + # server_max_window_bits + + # Config Req. Resp. + # ------ ------ -------------------------------------------------- + # None None None + # None 8≤M≤15 M + # 8≤N≤15 None N - must change value + # 8≤N≤15 8≤M≤N M + # 8≤N≤15 N self.server_max_window_bits: + server_max_window_bits = self.server_max_window_bits + + # client_max_window_bits + + # Config Req. Resp. + # ------ ------ -------------------------------------------------- + # None None None + # None True None - must change value + # None 8≤M≤15 M (or None) + # 8≤N≤15 None None or Error! + # 8≤N≤15 True N - must change value + # 8≤N≤15 8≤M≤N M (or None) + # 8≤N≤15 N Sequence[ServerExtensionFactory]: + """ + Enable Per-Message Deflate with default settings in server extensions. + + If the extension is already present, perhaps with non-default settings, + the configuration isn't changed. + + """ + if extensions is None: + extensions = [] + if not any( + ext_factory.name == ServerPerMessageDeflateFactory.name + for ext_factory in extensions + ): + extensions = list(extensions) + [ + ServerPerMessageDeflateFactory( + server_max_window_bits=12, + client_max_window_bits=12, + compress_settings={"memLevel": 5}, + ) + ] + return extensions diff --git a/.venv/lib/python3.14/site-packages/websockets/frames.py b/.venv/lib/python3.14/site-packages/websockets/frames.py new file mode 100644 index 0000000000000000000000000000000000000000..7716e7a2b2896d598b4f3137c55a6e5e565201cb --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/frames.py @@ -0,0 +1,431 @@ +from __future__ import annotations + +import dataclasses +import enum +import io +import os +import secrets +import struct +from collections.abc import Generator, Sequence +from typing import Callable + +from .exceptions import PayloadTooBig, ProtocolError +from .typing import BytesLike + + +try: + from .speedups import apply_mask +except ImportError: + from .utils import apply_mask + + +__all__ = [ + "Opcode", + "OP_CONT", + "OP_TEXT", + "OP_BINARY", + "OP_CLOSE", + "OP_PING", + "OP_PONG", + "DATA_OPCODES", + "CTRL_OPCODES", + "CloseCode", + "Frame", + "Close", +] + + +class Opcode(enum.IntEnum): + """Opcode values for WebSocket frames.""" + + CONT, TEXT, BINARY = 0x00, 0x01, 0x02 + CLOSE, PING, PONG = 0x08, 0x09, 0x0A + + +OP_CONT = Opcode.CONT +OP_TEXT = Opcode.TEXT +OP_BINARY = Opcode.BINARY +OP_CLOSE = Opcode.CLOSE +OP_PING = Opcode.PING +OP_PONG = Opcode.PONG + +DATA_OPCODES = OP_CONT, OP_TEXT, OP_BINARY +CTRL_OPCODES = OP_CLOSE, OP_PING, OP_PONG + + +class CloseCode(enum.IntEnum): + """Close code values for WebSocket close frames.""" + + NORMAL_CLOSURE = 1000 + GOING_AWAY = 1001 + PROTOCOL_ERROR = 1002 + UNSUPPORTED_DATA = 1003 + # 1004 is reserved + NO_STATUS_RCVD = 1005 + ABNORMAL_CLOSURE = 1006 + INVALID_DATA = 1007 + POLICY_VIOLATION = 1008 + MESSAGE_TOO_BIG = 1009 + MANDATORY_EXTENSION = 1010 + INTERNAL_ERROR = 1011 + SERVICE_RESTART = 1012 + TRY_AGAIN_LATER = 1013 + BAD_GATEWAY = 1014 + TLS_HANDSHAKE = 1015 + + +# See https://www.iana.org/assignments/websocket/websocket.xhtml +CLOSE_CODE_EXPLANATIONS: dict[int, str] = { + CloseCode.NORMAL_CLOSURE: "OK", + CloseCode.GOING_AWAY: "going away", + CloseCode.PROTOCOL_ERROR: "protocol error", + CloseCode.UNSUPPORTED_DATA: "unsupported data", + CloseCode.NO_STATUS_RCVD: "no status received [internal]", + CloseCode.ABNORMAL_CLOSURE: "abnormal closure [internal]", + CloseCode.INVALID_DATA: "invalid frame payload data", + CloseCode.POLICY_VIOLATION: "policy violation", + CloseCode.MESSAGE_TOO_BIG: "message too big", + CloseCode.MANDATORY_EXTENSION: "mandatory extension", + CloseCode.INTERNAL_ERROR: "internal error", + CloseCode.SERVICE_RESTART: "service restart", + CloseCode.TRY_AGAIN_LATER: "try again later", + CloseCode.BAD_GATEWAY: "bad gateway", + CloseCode.TLS_HANDSHAKE: "TLS handshake failure [internal]", +} + + +# Close code that are allowed in a close frame. +# Using a set optimizes `code in EXTERNAL_CLOSE_CODES`. +EXTERNAL_CLOSE_CODES = { + CloseCode.NORMAL_CLOSURE, + CloseCode.GOING_AWAY, + CloseCode.PROTOCOL_ERROR, + CloseCode.UNSUPPORTED_DATA, + CloseCode.INVALID_DATA, + CloseCode.POLICY_VIOLATION, + CloseCode.MESSAGE_TOO_BIG, + CloseCode.MANDATORY_EXTENSION, + CloseCode.INTERNAL_ERROR, + CloseCode.SERVICE_RESTART, + CloseCode.TRY_AGAIN_LATER, + CloseCode.BAD_GATEWAY, +} + + +OK_CLOSE_CODES = { + CloseCode.NORMAL_CLOSURE, + CloseCode.GOING_AWAY, + CloseCode.NO_STATUS_RCVD, +} + + +@dataclasses.dataclass +class Frame: + """ + WebSocket frame. + + Attributes: + opcode: Opcode. + data: Payload data. + fin: FIN bit. + rsv1: RSV1 bit. + rsv2: RSV2 bit. + rsv3: RSV3 bit. + + Only these fields are needed. The MASK bit, payload length and masking-key + are handled on the fly when parsing and serializing frames. + + """ + + opcode: Opcode + data: BytesLike + fin: bool = True + rsv1: bool = False + rsv2: bool = False + rsv3: bool = False + + # Configure if you want to see more in logs. Should be a multiple of 3. + MAX_LOG_SIZE = int(os.environ.get("WEBSOCKETS_MAX_LOG_SIZE", "75")) + + def __str__(self) -> str: + """ + Return a human-readable representation of a frame. + + """ + coding = None + length = f"{len(self.data)} byte{'' if len(self.data) == 1 else 's'}" + non_final = "" if self.fin else "continued" + + if self.opcode is OP_TEXT: + # Decoding only the beginning and the end is needlessly hard. + # Decode the entire payload then elide later if necessary. + data = repr(bytes(self.data).decode()) + elif self.opcode is OP_BINARY: + # We'll show at most the first 16 bytes and the last 8 bytes. + # Encode just what we need, plus two dummy bytes to elide later. + binary = self.data + if len(binary) > self.MAX_LOG_SIZE // 3: + cut = (self.MAX_LOG_SIZE // 3 - 1) // 3 # by default cut = 8 + binary = b"".join([binary[: 2 * cut], b"\x00\x00", binary[-cut:]]) + data = " ".join(f"{byte:02x}" for byte in binary) + elif self.opcode is OP_CLOSE: + data = str(Close.parse(self.data)) + elif self.data: + # We don't know if a Continuation frame contains text or binary. + # Ping and Pong frames could contain UTF-8. + # Attempt to decode as UTF-8 and display it as text; fallback to + # binary. If self.data is a memoryview, it has no decode() method, + # which raises AttributeError. + try: + data = repr(bytes(self.data).decode()) + coding = "text" + except (UnicodeDecodeError, AttributeError): + binary = self.data + if len(binary) > self.MAX_LOG_SIZE // 3: + cut = (self.MAX_LOG_SIZE // 3 - 1) // 3 # by default cut = 8 + binary = b"".join([binary[: 2 * cut], b"\x00\x00", binary[-cut:]]) + data = " ".join(f"{byte:02x}" for byte in binary) + coding = "binary" + else: + data = "''" + + if len(data) > self.MAX_LOG_SIZE: + cut = self.MAX_LOG_SIZE // 3 - 1 # by default cut = 24 + data = data[: 2 * cut] + "..." + data[-cut:] + + metadata = ", ".join(filter(None, [coding, length, non_final])) + + return f"{self.opcode.name} {data} [{metadata}]" + + @classmethod + def parse( + cls, + read_exact: Callable[[int], Generator[None, None, bytes | bytearray]], + *, + mask: bool, + max_size: int | None = None, + extensions: Sequence[extensions.Extension] | None = None, + ) -> Generator[None, None, Frame]: + """ + Parse a WebSocket frame. + + This is a generator-based coroutine. + + Args: + read_exact: Generator-based coroutine that reads the requested + bytes or raises an exception if there isn't enough data. + mask: Whether the frame should be masked i.e. whether the read + happens on the server side. + max_size: Maximum payload size in bytes. + extensions: List of extensions, applied in reverse order. + + Raises: + EOFError: If the connection is closed without a full WebSocket frame. + PayloadTooBig: If the frame's payload size exceeds ``max_size``. + ProtocolError: If the frame contains incorrect values. + + """ + # Read the header. + data = yield from read_exact(2) + head1, head2 = struct.unpack("!BB", data) + + # While not Pythonic, this is marginally faster than calling bool(). + fin = True if head1 & 0b10000000 else False + rsv1 = True if head1 & 0b01000000 else False + rsv2 = True if head1 & 0b00100000 else False + rsv3 = True if head1 & 0b00010000 else False + + try: + opcode = Opcode(head1 & 0b00001111) + except ValueError as exc: + raise ProtocolError("invalid opcode") from exc + + if (True if head2 & 0b10000000 else False) != mask: + raise ProtocolError("incorrect masking") + + length = head2 & 0b01111111 + if length == 126: + data = yield from read_exact(2) + (length,) = struct.unpack("!H", data) + elif length == 127: + data = yield from read_exact(8) + (length,) = struct.unpack("!Q", data) + if max_size is not None and length > max_size: + raise PayloadTooBig(length, max_size) + if mask: + mask_bytes = yield from read_exact(4) + + # Read the data. + data = yield from read_exact(length) + if mask: + data = apply_mask(data, mask_bytes) + + frame = cls(opcode, data, fin, rsv1, rsv2, rsv3) + + if extensions is None: + extensions = [] + for extension in reversed(extensions): + frame = extension.decode(frame, max_size=max_size) + + frame.check() + + return frame + + def serialize( + self, + *, + mask: bool, + extensions: Sequence[extensions.Extension] | None = None, + ) -> bytes: + """ + Serialize a WebSocket frame. + + Args: + mask: Whether the frame should be masked i.e. whether the write + happens on the client side. + extensions: List of extensions, applied in order. + + Raises: + ProtocolError: If the frame contains incorrect values. + + """ + self.check() + + if extensions is None: + extensions = [] + for extension in extensions: + self = extension.encode(self) + + output = io.BytesIO() + + # Prepare the header. + head1 = ( + (0b10000000 if self.fin else 0) + | (0b01000000 if self.rsv1 else 0) + | (0b00100000 if self.rsv2 else 0) + | (0b00010000 if self.rsv3 else 0) + | self.opcode + ) + + head2 = 0b10000000 if mask else 0 + + length = len(self.data) + if length < 126: + output.write(struct.pack("!BB", head1, head2 | length)) + elif length < 65536: + output.write(struct.pack("!BBH", head1, head2 | 126, length)) + else: + output.write(struct.pack("!BBQ", head1, head2 | 127, length)) + + if mask: + mask_bytes = secrets.token_bytes(4) + output.write(mask_bytes) + + # Prepare the data. + data: BytesLike + if mask: + data = apply_mask(self.data, mask_bytes) + else: + data = self.data + output.write(data) + + return output.getvalue() + + def check(self) -> None: + """ + Check that reserved bits and opcode have acceptable values. + + Raises: + ProtocolError: If a reserved bit or the opcode is invalid. + + """ + if self.rsv1 or self.rsv2 or self.rsv3: + raise ProtocolError("reserved bits must be 0") + + if self.opcode in CTRL_OPCODES: + if len(self.data) > 125: + raise ProtocolError("control frame too long") + if not self.fin: + raise ProtocolError("fragmented control frame") + + +@dataclasses.dataclass +class Close: + """ + Code and reason for WebSocket close frames. + + Attributes: + code: Close code. + reason: Close reason. + + """ + + code: CloseCode | int + reason: str + + def __str__(self) -> str: + """ + Return a human-readable representation of a close code and reason. + + """ + if 3000 <= self.code < 4000: + explanation = "registered" + elif 4000 <= self.code < 5000: + explanation = "private use" + else: + explanation = CLOSE_CODE_EXPLANATIONS.get(self.code, "unknown") + result = f"{self.code} ({explanation})" + + if self.reason: + result = f"{result} {self.reason}" + + return result + + @classmethod + def parse(cls, data: BytesLike) -> Close: + """ + Parse the payload of a close frame. + + Args: + data: Payload of the close frame. + + Raises: + ProtocolError: If data is ill-formed. + UnicodeDecodeError: If the reason isn't valid UTF-8. + + """ + if isinstance(data, memoryview): + raise AssertionError("only compressed outgoing frames use memoryview") + if len(data) >= 2: + (code,) = struct.unpack("!H", data[:2]) + reason = data[2:].decode() + close = cls(code, reason) + close.check() + return close + elif len(data) == 0: + return cls(CloseCode.NO_STATUS_RCVD, "") + else: + raise ProtocolError("close frame too short") + + def serialize(self) -> bytes: + """ + Serialize the payload of a close frame. + + """ + self.check() + return struct.pack("!H", self.code) + self.reason.encode() + + def check(self) -> None: + """ + Check that the close code has a valid value for a close frame. + + Raises: + ProtocolError: If the close code is invalid. + + """ + if not (self.code in EXTERNAL_CLOSE_CODES or 3000 <= self.code < 5000): + raise ProtocolError("invalid status code") + + +# At the bottom to break import cycles created by type annotations. +from . import extensions # noqa: E402 diff --git a/.venv/lib/python3.14/site-packages/websockets/headers.py b/.venv/lib/python3.14/site-packages/websockets/headers.py new file mode 100644 index 0000000000000000000000000000000000000000..e05ff5b4c34fcd3b3021eb6456e9b2d71dbe0222 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/headers.py @@ -0,0 +1,586 @@ +from __future__ import annotations + +import base64 +import binascii +import ipaddress +import re +from collections.abc import Sequence +from typing import Callable, TypeVar, cast + +from .exceptions import InvalidHeaderFormat, InvalidHeaderValue +from .typing import ( + ConnectionOption, + ExtensionHeader, + ExtensionName, + ExtensionParameter, + Subprotocol, + UpgradeProtocol, +) + + +__all__ = [ + "build_host", + "parse_connection", + "parse_upgrade", + "parse_extension", + "build_extension", + "parse_subprotocol", + "build_subprotocol", + "validate_subprotocols", + "build_www_authenticate_basic", + "parse_authorization_basic", + "build_authorization_basic", +] + + +T = TypeVar("T") + + +def build_host( + host: str, + port: int, + secure: bool, + *, + always_include_port: bool = False, +) -> str: + """ + Build a ``Host`` header. + + """ + # https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2 + # IPv6 addresses must be enclosed in brackets. + try: + address = ipaddress.ip_address(host) + except ValueError: + # host is a hostname + pass + else: + # host is an IP address + if address.version == 6: + host = f"[{host}]" + + if always_include_port or port != (443 if secure else 80): + host = f"{host}:{port}" + + return host + + +# To avoid a dependency on a parsing library, we implement manually the ABNF +# described in https://datatracker.ietf.org/doc/html/rfc6455#section-9.1 and +# https://datatracker.ietf.org/doc/html/rfc7230#appendix-B. + + +def peek_ahead(header: str, pos: int) -> str | None: + """ + Return the next character from ``header`` at the given position. + + Return :obj:`None` at the end of ``header``. + + We never need to peek more than one character ahead. + + """ + return None if pos == len(header) else header[pos] + + +_OWS_re = re.compile(r"[\t ]*") + + +def parse_OWS(header: str, pos: int) -> int: + """ + Parse optional whitespace from ``header`` at the given position. + + Return the new position. + + The whitespace itself isn't returned because it isn't significant. + + """ + # There's always a match, possibly empty, whose content doesn't matter. + match = _OWS_re.match(header, pos) + assert match is not None + return match.end() + + +_token_re = re.compile(r"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+") + + +def parse_token(header: str, pos: int, header_name: str) -> tuple[str, int]: + """ + Parse a token from ``header`` at the given position. + + Return the token value and the new position. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + match = _token_re.match(header, pos) + if match is None: + raise InvalidHeaderFormat(header_name, "expected token", header, pos) + return match.group(), match.end() + + +_quoted_string_re = re.compile( + r'"(?:[\x09\x20-\x21\x23-\x5b\x5d-\x7e]|\\[\x09\x20-\x7e\x80-\xff])*"' +) + + +_unquote_re = re.compile(r"\\([\x09\x20-\x7e\x80-\xff])") + + +def parse_quoted_string(header: str, pos: int, header_name: str) -> tuple[str, int]: + """ + Parse a quoted string from ``header`` at the given position. + + Return the unquoted value and the new position. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + match = _quoted_string_re.match(header, pos) + if match is None: + raise InvalidHeaderFormat(header_name, "expected quoted string", header, pos) + return _unquote_re.sub(r"\1", match.group()[1:-1]), match.end() + + +_quotable_re = re.compile(r"[\x09\x20-\x7e\x80-\xff]*") + + +_quote_re = re.compile(r"([\x22\x5c])") + + +def build_quoted_string(value: str) -> str: + """ + Format ``value`` as a quoted string. + + This is the reverse of :func:`parse_quoted_string`. + + """ + match = _quotable_re.fullmatch(value) + if match is None: + raise ValueError("invalid characters for quoted-string encoding") + return '"' + _quote_re.sub(r"\\\1", value) + '"' + + +def parse_list( + parse_item: Callable[[str, int, str], tuple[T, int]], + header: str, + pos: int, + header_name: str, +) -> list[T]: + """ + Parse a comma-separated list from ``header`` at the given position. + + This is appropriate for parsing values with the following grammar: + + 1#item + + ``parse_item`` parses one item. + + ``header`` is assumed not to start or end with whitespace. + + (This function is designed for parsing an entire header value and + :func:`~websockets.http.read_headers` strips whitespace from values.) + + Return a list of items. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + # Per https://datatracker.ietf.org/doc/html/rfc7230#section-7, "a recipient + # MUST parse and ignore a reasonable number of empty list elements"; + # hence while loops that remove extra delimiters. + + # Remove extra delimiters before the first item. + while peek_ahead(header, pos) == ",": + pos = parse_OWS(header, pos + 1) + + items = [] + while True: + # Loop invariant: a item starts at pos in header. + item, pos = parse_item(header, pos, header_name) + items.append(item) + pos = parse_OWS(header, pos) + + # We may have reached the end of the header. + if pos == len(header): + break + + # There must be a delimiter after each element except the last one. + if peek_ahead(header, pos) == ",": + pos = parse_OWS(header, pos + 1) + else: + raise InvalidHeaderFormat(header_name, "expected comma", header, pos) + + # Remove extra delimiters before the next item. + while peek_ahead(header, pos) == ",": + pos = parse_OWS(header, pos + 1) + + # We may have reached the end of the header. + if pos == len(header): + break + + # Since we only advance in the header by one character with peek_ahead() + # or with the end position of a regex match, we can't overshoot the end. + assert pos == len(header) + + return items + + +def parse_connection_option( + header: str, pos: int, header_name: str +) -> tuple[ConnectionOption, int]: + """ + Parse a Connection option from ``header`` at the given position. + + Return the protocol value and the new position. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + item, pos = parse_token(header, pos, header_name) + return cast(ConnectionOption, item), pos + + +def parse_connection(header: str) -> list[ConnectionOption]: + """ + Parse a ``Connection`` header. + + Return a list of HTTP connection options. + + Args + header: value of the ``Connection`` header. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + return parse_list(parse_connection_option, header, 0, "Connection") + + +_protocol_re = re.compile( + r"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+(?:/[-!#$%&\'*+.^_`|~0-9a-zA-Z]+)?" +) + + +def parse_upgrade_protocol( + header: str, pos: int, header_name: str +) -> tuple[UpgradeProtocol, int]: + """ + Parse an Upgrade protocol from ``header`` at the given position. + + Return the protocol value and the new position. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + match = _protocol_re.match(header, pos) + if match is None: + raise InvalidHeaderFormat(header_name, "expected protocol", header, pos) + return cast(UpgradeProtocol, match.group()), match.end() + + +def parse_upgrade(header: str) -> list[UpgradeProtocol]: + """ + Parse an ``Upgrade`` header. + + Return a list of HTTP protocols. + + Args: + header: Value of the ``Upgrade`` header. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + return parse_list(parse_upgrade_protocol, header, 0, "Upgrade") + + +def parse_extension_item_param( + header: str, pos: int, header_name: str +) -> tuple[ExtensionParameter, int]: + """ + Parse a single extension parameter from ``header`` at the given position. + + Return a ``(name, value)`` pair and the new position. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + # Extract parameter name. + name, pos = parse_token(header, pos, header_name) + pos = parse_OWS(header, pos) + # Extract parameter value, if there is one. + value: str | None = None + if peek_ahead(header, pos) == "=": + pos = parse_OWS(header, pos + 1) + if peek_ahead(header, pos) == '"': + pos_before = pos # for proper error reporting below + value, pos = parse_quoted_string(header, pos, header_name) + # https://datatracker.ietf.org/doc/html/rfc6455#section-9.1 says: + # the value after quoted-string unescaping MUST conform to + # the 'token' ABNF. + if _token_re.fullmatch(value) is None: + raise InvalidHeaderFormat( + header_name, "invalid quoted header content", header, pos_before + ) + else: + value, pos = parse_token(header, pos, header_name) + pos = parse_OWS(header, pos) + + return (name, value), pos + + +def parse_extension_item( + header: str, pos: int, header_name: str +) -> tuple[ExtensionHeader, int]: + """ + Parse an extension definition from ``header`` at the given position. + + Return an ``(extension name, parameters)`` pair, where ``parameters`` is a + list of ``(name, value)`` pairs, and the new position. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + # Extract extension name. + name, pos = parse_token(header, pos, header_name) + pos = parse_OWS(header, pos) + # Extract all parameters. + parameters = [] + while peek_ahead(header, pos) == ";": + pos = parse_OWS(header, pos + 1) + parameter, pos = parse_extension_item_param(header, pos, header_name) + parameters.append(parameter) + return (cast(ExtensionName, name), parameters), pos + + +def parse_extension(header: str) -> list[ExtensionHeader]: + """ + Parse a ``Sec-WebSocket-Extensions`` header. + + Return a list of WebSocket extensions and their parameters in this format:: + + [ + ( + 'extension name', + [ + ('parameter name', 'parameter value'), + .... + ] + ), + ... + ] + + Parameter values are :obj:`None` when no value is provided. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + return parse_list(parse_extension_item, header, 0, "Sec-WebSocket-Extensions") + + +parse_extension_list = parse_extension # alias for backwards compatibility + + +def build_extension_item( + name: ExtensionName, parameters: Sequence[ExtensionParameter] +) -> str: + """ + Build an extension definition. + + This is the reverse of :func:`parse_extension_item`. + + """ + return "; ".join( + [cast(str, name)] + + [ + # Quoted strings aren't necessary because values are always tokens. + name if value is None else f"{name}={value}" + for name, value in parameters + ] + ) + + +def build_extension(extensions: Sequence[ExtensionHeader]) -> str: + """ + Build a ``Sec-WebSocket-Extensions`` header. + + This is the reverse of :func:`parse_extension`. + + """ + return ", ".join( + build_extension_item(name, parameters) for name, parameters in extensions + ) + + +build_extension_list = build_extension # alias for backwards compatibility + + +def parse_subprotocol_item( + header: str, pos: int, header_name: str +) -> tuple[Subprotocol, int]: + """ + Parse a subprotocol from ``header`` at the given position. + + Return the subprotocol value and the new position. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + item, pos = parse_token(header, pos, header_name) + return cast(Subprotocol, item), pos + + +def parse_subprotocol(header: str) -> list[Subprotocol]: + """ + Parse a ``Sec-WebSocket-Protocol`` header. + + Return a list of WebSocket subprotocols. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + return parse_list(parse_subprotocol_item, header, 0, "Sec-WebSocket-Protocol") + + +parse_subprotocol_list = parse_subprotocol # alias for backwards compatibility + + +def build_subprotocol(subprotocols: Sequence[Subprotocol]) -> str: + """ + Build a ``Sec-WebSocket-Protocol`` header. + + This is the reverse of :func:`parse_subprotocol`. + + """ + return ", ".join(subprotocols) + + +build_subprotocol_list = build_subprotocol # alias for backwards compatibility + + +def validate_subprotocols(subprotocols: Sequence[Subprotocol]) -> None: + """ + Validate that ``subprotocols`` is suitable for :func:`build_subprotocol`. + + """ + if not isinstance(subprotocols, Sequence): + raise TypeError("subprotocols must be a list") + if isinstance(subprotocols, str): + raise TypeError("subprotocols must be a list, not a str") + for subprotocol in subprotocols: + if not _token_re.fullmatch(subprotocol): + raise ValueError(f"invalid subprotocol: {subprotocol}") + + +def build_www_authenticate_basic(realm: str) -> str: + """ + Build a ``WWW-Authenticate`` header for HTTP Basic Auth. + + Args: + realm: Identifier of the protection space. + + """ + # https://datatracker.ietf.org/doc/html/rfc7617#section-2 + realm = build_quoted_string(realm) + charset = build_quoted_string("UTF-8") + return f"Basic realm={realm}, charset={charset}" + + +_token68_re = re.compile(r"[A-Za-z0-9-._~+/]+=*") + + +def parse_token68(header: str, pos: int, header_name: str) -> tuple[str, int]: + """ + Parse a token68 from ``header`` at the given position. + + Return the token value and the new position. + + Raises: + InvalidHeaderFormat: On invalid inputs. + + """ + match = _token68_re.match(header, pos) + if match is None: + raise InvalidHeaderFormat(header_name, "expected token68", header, pos) + return match.group(), match.end() + + +def parse_end(header: str, pos: int, header_name: str) -> None: + """ + Check that parsing reached the end of header. + + """ + if pos < len(header): + raise InvalidHeaderFormat(header_name, "trailing data", header, pos) + + +def parse_authorization_basic(header: str) -> tuple[str, str]: + """ + Parse an ``Authorization`` header for HTTP Basic Auth. + + Return a ``(username, password)`` tuple. + + Args: + header: Value of the ``Authorization`` header. + + Raises: + InvalidHeaderFormat: On invalid inputs. + InvalidHeaderValue: On unsupported inputs. + + """ + # https://datatracker.ietf.org/doc/html/rfc7235#section-2.1 + # https://datatracker.ietf.org/doc/html/rfc7617#section-2 + scheme, pos = parse_token(header, 0, "Authorization") + if scheme.lower() != "basic": + raise InvalidHeaderValue( + "Authorization", + f"unsupported scheme: {scheme}", + ) + if peek_ahead(header, pos) != " ": + raise InvalidHeaderFormat( + "Authorization", "expected space after scheme", header, pos + ) + pos += 1 + basic_credentials, pos = parse_token68(header, pos, "Authorization") + parse_end(header, pos, "Authorization") + + try: + user_pass = base64.b64decode(basic_credentials.encode()).decode() + except binascii.Error: + raise InvalidHeaderValue( + "Authorization", + "expected base64-encoded credentials", + ) from None + try: + username, password = user_pass.split(":", 1) + except ValueError: + raise InvalidHeaderValue( + "Authorization", + "expected username:password credentials", + ) from None + + return username, password + + +def build_authorization_basic(username: str, password: str) -> str: + """ + Build an ``Authorization`` header for HTTP Basic Auth. + + This is the reverse of :func:`parse_authorization_basic`. + + """ + # https://datatracker.ietf.org/doc/html/rfc7617#section-2 + assert ":" not in username + user_pass = f"{username}:{password}" + basic_credentials = base64.b64encode(user_pass.encode()).decode() + return "Basic " + basic_credentials diff --git a/.venv/lib/python3.14/site-packages/websockets/http.py b/.venv/lib/python3.14/site-packages/websockets/http.py new file mode 100644 index 0000000000000000000000000000000000000000..0d860e5379404c12f8fb4177ca4fcb6764b86f3b --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/http.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +import warnings + +from .datastructures import Headers, MultipleValuesError # noqa: F401 + + +with warnings.catch_warnings(): + # Suppress redundant DeprecationWarning raised by websockets.legacy. + warnings.filterwarnings("ignore", category=DeprecationWarning) + from .legacy.http import read_request, read_response # noqa: F401 + + +warnings.warn( # deprecated in 9.0 - 2021-09-01 + "Headers and MultipleValuesError were moved " + "from websockets.http to websockets.datastructures" + "and read_request and read_response were moved " + "from websockets.http to websockets.legacy.http", + DeprecationWarning, +) diff --git a/.venv/lib/python3.14/site-packages/websockets/http11.py b/.venv/lib/python3.14/site-packages/websockets/http11.py new file mode 100644 index 0000000000000000000000000000000000000000..5af73eb0cf58fa4246e9a274a2b1ef36f301eb9c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/http11.py @@ -0,0 +1,438 @@ +from __future__ import annotations + +import dataclasses +import os +import re +import sys +import warnings +from collections.abc import Generator +from typing import Callable + +from .datastructures import Headers +from .exceptions import SecurityError +from .version import version as websockets_version + + +__all__ = [ + "SERVER", + "USER_AGENT", + "Request", + "Response", +] + + +PYTHON_VERSION = "{}.{}".format(*sys.version_info) + +# User-Agent header for HTTP requests. +USER_AGENT = os.environ.get( + "WEBSOCKETS_USER_AGENT", + f"Python/{PYTHON_VERSION} websockets/{websockets_version}", +) + +# Server header for HTTP responses. +SERVER = os.environ.get( + "WEBSOCKETS_SERVER", + f"Python/{PYTHON_VERSION} websockets/{websockets_version}", +) + +# Maximum total size of headers is around 128 * 8 KiB = 1 MiB. +MAX_NUM_HEADERS = int(os.environ.get("WEBSOCKETS_MAX_NUM_HEADERS", "128")) + +# Limit request line and header lines. 8KiB is the most common default +# configuration of popular HTTP servers. +MAX_LINE_LENGTH = int(os.environ.get("WEBSOCKETS_MAX_LINE_LENGTH", "8192")) + +# Support for HTTP response bodies is intended to read an error message +# returned by a server. It isn't designed to perform large file transfers. +MAX_BODY_SIZE = int(os.environ.get("WEBSOCKETS_MAX_BODY_SIZE", "1_048_576")) # 1 MiB + + +def d(value: bytes | bytearray) -> str: + """ + Decode a bytestring for interpolating into an error message. + + """ + return value.decode(errors="backslashreplace") + + +# See https://datatracker.ietf.org/doc/html/rfc7230#appendix-B. + +# Regex for validating header names. + +_token_re = re.compile(rb"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+") + +# Regex for validating header values. + +# We don't attempt to support obsolete line folding. + +# Include HTAB (\x09), SP (\x20), VCHAR (\x21-\x7e), obs-text (\x80-\xff). + +# The ABNF is complicated because it attempts to express that optional +# whitespace is ignored. We strip whitespace and don't revalidate that. + +# See also https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189 + +_value_re = re.compile(rb"[\x09\x20-\x7e\x80-\xff]*") + + +@dataclasses.dataclass +class Request: + """ + WebSocket handshake request. + + Attributes: + path: Request path, including optional query. + headers: Request headers. + """ + + path: str + headers: Headers + # body isn't useful is the context of this library. + + _exception: Exception | None = None + + @property + def exception(self) -> Exception | None: # pragma: no cover + warnings.warn( # deprecated in 10.3 - 2022-04-17 + "Request.exception is deprecated; use ServerProtocol.handshake_exc instead", + DeprecationWarning, + ) + return self._exception + + @classmethod + def parse( + cls, + read_line: Callable[[int], Generator[None, None, bytes | bytearray]], + ) -> Generator[None, None, Request]: + """ + Parse a WebSocket handshake request. + + This is a generator-based coroutine. + + The request path isn't URL-decoded or validated in any way. + + The request path and headers are expected to contain only ASCII + characters. Other characters are represented with surrogate escapes. + + :meth:`parse` doesn't attempt to read the request body because + WebSocket handshake requests don't have one. If the request contains a + body, it may be read from the data stream after :meth:`parse` returns. + + Args: + read_line: Generator-based coroutine that reads a LF-terminated + line or raises an exception if there isn't enough data + + Raises: + EOFError: If the connection is closed without a full HTTP request. + SecurityError: If the request exceeds a security limit. + ValueError: If the request isn't well formatted. + + """ + # https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.1 + + # Parsing is simple because fixed values are expected for method and + # version and because path isn't checked. Since WebSocket software tends + # to implement HTTP/1.1 strictly, there's little need for lenient parsing. + + try: + request_line = yield from parse_line(read_line) + except EOFError as exc: + raise EOFError("connection closed while reading HTTP request line") from exc + + try: + method, raw_path, protocol = request_line.split(b" ", 2) + except ValueError: # not enough values to unpack (expected 3, got 1-2) + raise ValueError(f"invalid HTTP request line: {d(request_line)}") from None + if protocol != b"HTTP/1.1": + raise ValueError( + f"unsupported protocol; expected HTTP/1.1: {d(request_line)}" + ) + if method != b"GET": + raise ValueError(f"unsupported HTTP method; expected GET; got {d(method)}") + path = raw_path.decode("ascii", "surrogateescape") + + headers = yield from parse_headers(read_line) + + # https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3 + + if "Transfer-Encoding" in headers: + raise NotImplementedError("transfer codings aren't supported") + + if "Content-Length" in headers: + # Some devices send a Content-Length header with a value of 0. + # This raises ValueError if Content-Length isn't an integer too. + if int(headers["Content-Length"]) != 0: + raise ValueError("unsupported request body") + + return cls(path, headers) + + def serialize(self) -> bytes: + """ + Serialize a WebSocket handshake request. + + """ + # Since the request line and headers only contain ASCII characters, + # we can keep this simple. + request = f"GET {self.path} HTTP/1.1\r\n".encode() + request += self.headers.serialize() + return request + + +@dataclasses.dataclass +class Response: + """ + WebSocket handshake response. + + Attributes: + status_code: Response code. + reason_phrase: Response reason. + headers: Response headers. + body: Response body. + + """ + + status_code: int + reason_phrase: str + headers: Headers + body: bytes | bytearray = b"" + + _exception: Exception | None = None + + @property + def exception(self) -> Exception | None: # pragma: no cover + warnings.warn( # deprecated in 10.3 - 2022-04-17 + "Response.exception is deprecated; " + "use ClientProtocol.handshake_exc instead", + DeprecationWarning, + ) + return self._exception + + @classmethod + def parse( + cls, + read_line: Callable[[int], Generator[None, None, bytes | bytearray]], + read_exact: Callable[[int], Generator[None, None, bytes | bytearray]], + read_to_eof: Callable[[int], Generator[None, None, bytes | bytearray]], + proxy: bool = False, + ) -> Generator[None, None, Response]: + """ + Parse a WebSocket handshake response. + + This is a generator-based coroutine. + + The reason phrase and headers are expected to contain only ASCII + characters. Other characters are represented with surrogate escapes. + + Args: + read_line: Generator-based coroutine that reads a LF-terminated + line or raises an exception if there isn't enough data. + read_exact: Generator-based coroutine that reads the requested + bytes or raises an exception if there isn't enough data. + read_to_eof: Generator-based coroutine that reads until the end + of the stream. + + Raises: + EOFError: If the connection is closed without a full HTTP response. + SecurityError: If the response exceeds a security limit. + LookupError: If the response isn't well formatted. + ValueError: If the response isn't well formatted. + + """ + # https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + + try: + status_line = yield from parse_line(read_line) + except EOFError as exc: + raise EOFError("connection closed while reading HTTP status line") from exc + + try: + protocol, raw_status_code, raw_reason = status_line.split(b" ", 2) + except ValueError: # not enough values to unpack (expected 3, got 1-2) + raise ValueError(f"invalid HTTP status line: {d(status_line)}") from None + if proxy: # some proxies still use HTTP/1.0 + if protocol not in [b"HTTP/1.1", b"HTTP/1.0"]: + raise ValueError( + f"unsupported protocol; expected HTTP/1.1 or HTTP/1.0: " + f"{d(status_line)}" + ) + else: + if protocol != b"HTTP/1.1": + raise ValueError( + f"unsupported protocol; expected HTTP/1.1: {d(status_line)}" + ) + try: + status_code = int(raw_status_code) + except ValueError: # invalid literal for int() with base 10 + raise ValueError( + f"invalid status code; expected integer; got {d(raw_status_code)}" + ) from None + if not 100 <= status_code < 600: + raise ValueError( + f"invalid status code; expected 100–599; got {d(raw_status_code)}" + ) + if not _value_re.fullmatch(raw_reason): + raise ValueError(f"invalid HTTP reason phrase: {d(raw_reason)}") + reason = raw_reason.decode("ascii", "surrogateescape") + + headers = yield from parse_headers(read_line) + + body: bytes | bytearray + if proxy: + body = b"" + else: + body = yield from read_body( + status_code, headers, read_line, read_exact, read_to_eof + ) + + return cls(status_code, reason, headers, body) + + def serialize(self) -> bytes: + """ + Serialize a WebSocket handshake response. + + """ + # Since the status line and headers only contain ASCII characters, + # we can keep this simple. + response = f"HTTP/1.1 {self.status_code} {self.reason_phrase}\r\n".encode() + response += self.headers.serialize() + response += self.body + return response + + +def parse_line( + read_line: Callable[[int], Generator[None, None, bytes | bytearray]], +) -> Generator[None, None, bytes | bytearray]: + """ + Parse a single line. + + CRLF is stripped from the return value. + + Args: + read_line: Generator-based coroutine that reads a LF-terminated line + or raises an exception if there isn't enough data. + + Raises: + EOFError: If the connection is closed without a CRLF. + SecurityError: If the response exceeds a security limit. + + """ + try: + line = yield from read_line(MAX_LINE_LENGTH) + except RuntimeError: + raise SecurityError("line too long") + # Not mandatory but safe - https://datatracker.ietf.org/doc/html/rfc7230#section-3.5 + if not line.endswith(b"\r\n"): + raise EOFError("line without CRLF") + return line[:-2] + + +def parse_headers( + read_line: Callable[[int], Generator[None, None, bytes | bytearray]], +) -> Generator[None, None, Headers]: + """ + Parse HTTP headers. + + Non-ASCII characters are represented with surrogate escapes. + + Args: + read_line: Generator-based coroutine that reads a LF-terminated line + or raises an exception if there isn't enough data. + + Raises: + EOFError: If the connection is closed without complete headers. + SecurityError: If the request exceeds a security limit. + ValueError: If the request isn't well formatted. + + """ + # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 + + # We don't attempt to support obsolete line folding. + + headers = Headers() + for _ in range(MAX_NUM_HEADERS + 1): + try: + line = yield from parse_line(read_line) + except EOFError as exc: + raise EOFError("connection closed while reading HTTP headers") from exc + if line == b"": + break + + try: + raw_name, raw_value = line.split(b":", 1) + except ValueError: # not enough values to unpack (expected 2, got 1) + raise ValueError(f"invalid HTTP header line: {d(line)}") from None + if not _token_re.fullmatch(raw_name): + raise ValueError(f"invalid HTTP header name: {d(raw_name)}") + raw_value = raw_value.strip(b" \t") + if not _value_re.fullmatch(raw_value): + raise ValueError(f"invalid HTTP header value: {d(raw_value)}") + + name = raw_name.decode("ascii") # guaranteed to be ASCII at this point + value = raw_value.decode("ascii", "surrogateescape") + headers[name] = value + + else: + raise SecurityError("too many HTTP headers") + + return headers + + +def read_body( + status_code: int, + headers: Headers, + read_line: Callable[[int], Generator[None, None, bytes | bytearray]], + read_exact: Callable[[int], Generator[None, None, bytes | bytearray]], + read_to_eof: Callable[[int], Generator[None, None, bytes | bytearray]], +) -> Generator[None, None, bytes | bytearray]: + # https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3 + + # Since websockets only does GET requests (no HEAD, no CONNECT), all + # responses except 1xx, 204, and 304 include a message body. + if 100 <= status_code < 200 or status_code == 204 or status_code == 304: + return b"" + + # MultipleValuesError is sufficiently unlikely that we don't attempt to + # handle it when accessing headers. Instead we document that its parent + # class, LookupError, may be raised. + # Conversions from str to int are protected by sys.set_int_max_str_digits.. + + elif (coding := headers.get("Transfer-Encoding")) is not None: + if coding != "chunked": + raise NotImplementedError(f"transfer coding {coding} isn't supported") + + body = b"" + while True: + chunk_size_line = yield from parse_line(read_line) + raw_chunk_size = chunk_size_line.split(b";", 1)[0] + # Set a lower limit than default_max_str_digits; 1 EB is plenty. + if len(raw_chunk_size) > 15: + str_chunk_size = raw_chunk_size.decode(errors="backslashreplace") + raise SecurityError(f"chunk too large: 0x{str_chunk_size} bytes") + chunk_size = int(raw_chunk_size, 16) + if chunk_size == 0: + break + if len(body) + chunk_size > MAX_BODY_SIZE: + raise SecurityError( + f"chunk too large: {chunk_size} bytes after {len(body)} bytes" + ) + body += yield from read_exact(chunk_size) + if (yield from read_exact(2)) != b"\r\n": + raise ValueError("chunk without CRLF") + # Read the trailer. + yield from parse_headers(read_line) + return body + + elif (raw_content_length := headers.get("Content-Length")) is not None: + # Set a lower limit than default_max_str_digits; 1 EiB is plenty. + if len(raw_content_length) > 18: + raise SecurityError(f"body too large: {raw_content_length} bytes") + content_length = int(raw_content_length) + if content_length > MAX_BODY_SIZE: + raise SecurityError(f"body too large: {content_length} bytes") + return (yield from read_exact(content_length)) + + else: + try: + return (yield from read_to_eof(MAX_BODY_SIZE)) + except RuntimeError: + raise SecurityError(f"body too large: over {MAX_BODY_SIZE} bytes") diff --git a/.venv/lib/python3.14/site-packages/websockets/imports.py b/.venv/lib/python3.14/site-packages/websockets/imports.py new file mode 100644 index 0000000000000000000000000000000000000000..c63fb212ec602ae6ec75fe1b86a29fb2e11334df --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/imports.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +import warnings +from collections.abc import Iterable +from typing import Any + + +__all__ = ["lazy_import"] + + +def import_name(name: str, source: str, namespace: dict[str, Any]) -> Any: + """ + Import ``name`` from ``source`` in ``namespace``. + + There are two use cases: + + - ``name`` is an object defined in ``source``; + - ``name`` is a submodule of ``source``. + + Neither :func:`__import__` nor :func:`~importlib.import_module` does + exactly this. :func:`__import__` is closer to the intended behavior. + + """ + level = 0 + while source[level] == ".": + level += 1 + assert level < len(source), "importing from parent isn't supported" + module = __import__(source[level:], namespace, None, [name], level) + return getattr(module, name) + + +def lazy_import( + namespace: dict[str, Any], + aliases: dict[str, str] | None = None, + deprecated_aliases: dict[str, str] | None = None, +) -> None: + """ + Provide lazy, module-level imports. + + Typical use:: + + __getattr__, __dir__ = lazy_import( + globals(), + aliases={ + "": "", + ... + }, + deprecated_aliases={ + ..., + } + ) + + This function defines ``__getattr__`` and ``__dir__`` per :pep:`562`. + + """ + if aliases is None: + aliases = {} + if deprecated_aliases is None: + deprecated_aliases = {} + + namespace_set = set(namespace) + aliases_set = set(aliases) + deprecated_aliases_set = set(deprecated_aliases) + + assert not namespace_set & aliases_set, "namespace conflict" + assert not namespace_set & deprecated_aliases_set, "namespace conflict" + assert not aliases_set & deprecated_aliases_set, "namespace conflict" + + package = namespace["__name__"] + + def __getattr__(name: str) -> Any: + assert aliases is not None # mypy cannot figure this out + try: + source = aliases[name] + except KeyError: + pass + else: + return import_name(name, source, namespace) + + assert deprecated_aliases is not None # mypy cannot figure this out + try: + source = deprecated_aliases[name] + except KeyError: + pass + else: + warnings.warn( + f"{package}.{name} is deprecated", + DeprecationWarning, + stacklevel=2, + ) + return import_name(name, source, namespace) + + raise AttributeError(f"module {package!r} has no attribute {name!r}") + + namespace["__getattr__"] = __getattr__ + + def __dir__() -> Iterable[str]: + return sorted(namespace_set | aliases_set | deprecated_aliases_set) + + namespace["__dir__"] = __dir__ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__init__.py b/.venv/lib/python3.14/site-packages/websockets/legacy/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ad9aa25064f626754bda8a8bb149d974002a064e --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/legacy/__init__.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +import warnings + + +warnings.warn( # deprecated in 14.0 - 2024-11-09 + "websockets.legacy is deprecated; " + "see https://websockets.readthedocs.io/en/stable/howto/upgrade.html " + "for upgrade instructions", + DeprecationWarning, +) diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c07a39b580934b456126ff791df72aa1cca0007c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/auth.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/auth.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ef2c94994dad34b2e50183cb1318ba5178a9ffb Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/auth.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/client.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/client.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8455e932d49e4068a2e0f124050beb68d8bdf130 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/client.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/exceptions.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/exceptions.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3327f1daeaa418c3b18a2014147999c6befa0456 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/exceptions.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/framing.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/framing.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b7a1fb32b712470f9b55b1d957bf142124616ae Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/framing.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/handshake.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/handshake.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..512764be1e14fc49218f183516f160c33e42127b Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/handshake.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/http.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/http.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..917dc9e5d13bca865cd1f60fe3406a4535e9f7df Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/http.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/protocol.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/protocol.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..79ddd6b9bd2980ce8320135a85bf279e4cbda608 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/protocol.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/server.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/server.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd077a89f74511e05deff763690a0baec5b8f1cf Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/legacy/__pycache__/server.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/auth.py b/.venv/lib/python3.14/site-packages/websockets/legacy/auth.py new file mode 100644 index 0000000000000000000000000000000000000000..a262fcd791bc66a1ad0ee9389faed1c62c69e2be --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/legacy/auth.py @@ -0,0 +1,190 @@ +from __future__ import annotations + +import functools +import hmac +import http +from collections.abc import Awaitable, Iterable +from typing import Any, Callable, cast + +from ..datastructures import Headers +from ..exceptions import InvalidHeader +from ..headers import build_www_authenticate_basic, parse_authorization_basic +from .server import HTTPResponse, WebSocketServerProtocol + + +__all__ = ["BasicAuthWebSocketServerProtocol", "basic_auth_protocol_factory"] + +Credentials = tuple[str, str] + + +def is_credentials(value: Any) -> bool: + try: + username, password = value + except (TypeError, ValueError): + return False + else: + return isinstance(username, str) and isinstance(password, str) + + +class BasicAuthWebSocketServerProtocol(WebSocketServerProtocol): + """ + WebSocket server protocol that enforces HTTP Basic Auth. + + """ + + realm: str = "" + """ + Scope of protection. + + If provided, it should contain only ASCII characters because the + encoding of non-ASCII characters is undefined. + """ + + username: str | None = None + """Username of the authenticated user.""" + + def __init__( + self, + *args: Any, + realm: str | None = None, + check_credentials: Callable[[str, str], Awaitable[bool]] | None = None, + **kwargs: Any, + ) -> None: + if realm is not None: + self.realm = realm # shadow class attribute + self._check_credentials = check_credentials + super().__init__(*args, **kwargs) + + async def check_credentials(self, username: str, password: str) -> bool: + """ + Check whether credentials are authorized. + + This coroutine may be overridden in a subclass, for example to + authenticate against a database or an external service. + + Args: + username: HTTP Basic Auth username. + password: HTTP Basic Auth password. + + Returns: + :obj:`True` if the handshake should continue; + :obj:`False` if it should fail with an HTTP 401 error. + + """ + if self._check_credentials is not None: + return await self._check_credentials(username, password) + + return False + + async def process_request( + self, + path: str, + request_headers: Headers, + ) -> HTTPResponse | None: + """ + Check HTTP Basic Auth and return an HTTP 401 response if needed. + + """ + try: + authorization = request_headers["Authorization"] + except KeyError: + return ( + http.HTTPStatus.UNAUTHORIZED, + [("WWW-Authenticate", build_www_authenticate_basic(self.realm))], + b"Missing credentials\n", + ) + + try: + username, password = parse_authorization_basic(authorization) + except InvalidHeader: + return ( + http.HTTPStatus.UNAUTHORIZED, + [("WWW-Authenticate", build_www_authenticate_basic(self.realm))], + b"Unsupported credentials\n", + ) + + if not await self.check_credentials(username, password): + return ( + http.HTTPStatus.UNAUTHORIZED, + [("WWW-Authenticate", build_www_authenticate_basic(self.realm))], + b"Invalid credentials\n", + ) + + self.username = username + + return await super().process_request(path, request_headers) + + +def basic_auth_protocol_factory( + realm: str | None = None, + credentials: Credentials | Iterable[Credentials] | None = None, + check_credentials: Callable[[str, str], Awaitable[bool]] | None = None, + create_protocol: Callable[..., BasicAuthWebSocketServerProtocol] | None = None, +) -> Callable[..., BasicAuthWebSocketServerProtocol]: + """ + Protocol factory that enforces HTTP Basic Auth. + + :func:`basic_auth_protocol_factory` is designed to integrate with + :func:`~websockets.legacy.server.serve` like this:: + + serve( + ..., + create_protocol=basic_auth_protocol_factory( + realm="my dev server", + credentials=("hello", "iloveyou"), + ) + ) + + Args: + realm: Scope of protection. It should contain only ASCII characters + because the encoding of non-ASCII characters is undefined. + Refer to section 2.2 of :rfc:`7235` for details. + credentials: Hard coded authorized credentials. It can be a + ``(username, password)`` pair or a list of such pairs. + check_credentials: Coroutine that verifies credentials. + It receives ``username`` and ``password`` arguments + and returns a :class:`bool`. One of ``credentials`` or + ``check_credentials`` must be provided but not both. + create_protocol: Factory that creates the protocol. By default, this + is :class:`BasicAuthWebSocketServerProtocol`. It can be replaced + by a subclass. + Raises: + TypeError: If the ``credentials`` or ``check_credentials`` argument is + wrong. + + """ + if (credentials is None) == (check_credentials is None): + raise TypeError("provide either credentials or check_credentials") + + if credentials is not None: + if is_credentials(credentials): + credentials_list = [cast(Credentials, credentials)] + elif isinstance(credentials, Iterable): + credentials_list = list(cast(Iterable[Credentials], credentials)) + if not all(is_credentials(item) for item in credentials_list): + raise TypeError(f"invalid credentials argument: {credentials}") + else: + raise TypeError(f"invalid credentials argument: {credentials}") + + credentials_dict = dict(credentials_list) + + async def check_credentials(username: str, password: str) -> bool: + try: + expected_password = credentials_dict[username] + except KeyError: + return False + return hmac.compare_digest(expected_password, password) + + if create_protocol is None: + create_protocol = BasicAuthWebSocketServerProtocol + + # Help mypy and avoid this error: "type[BasicAuthWebSocketServerProtocol] | + # Callable[..., BasicAuthWebSocketServerProtocol]" not callable [misc] + create_protocol = cast( + Callable[..., BasicAuthWebSocketServerProtocol], create_protocol + ) + return functools.partial( + create_protocol, + realm=realm, + check_credentials=check_credentials, + ) diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/client.py b/.venv/lib/python3.14/site-packages/websockets/legacy/client.py new file mode 100644 index 0000000000000000000000000000000000000000..575c84519c35b3aa25481a5a3abf45b9719df630 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/legacy/client.py @@ -0,0 +1,703 @@ +from __future__ import annotations + +import asyncio +import functools +import logging +import os +import random +import traceback +import urllib.parse +import warnings +from collections.abc import AsyncIterator, Generator, Sequence +from types import TracebackType +from typing import Any, Callable, cast + +from ..asyncio.compatibility import asyncio_timeout +from ..datastructures import Headers, HeadersLike +from ..exceptions import ( + InvalidHeader, + InvalidHeaderValue, + InvalidMessage, + NegotiationError, + SecurityError, +) +from ..extensions import ClientExtensionFactory, Extension +from ..extensions.permessage_deflate import enable_client_permessage_deflate +from ..headers import ( + build_authorization_basic, + build_extension, + build_host, + build_subprotocol, + parse_extension, + parse_subprotocol, + validate_subprotocols, +) +from ..http11 import USER_AGENT +from ..typing import ExtensionHeader, LoggerLike, Origin, Subprotocol +from ..uri import WebSocketURI, parse_uri +from .exceptions import InvalidStatusCode, RedirectHandshake +from .handshake import build_request, check_response +from .http import read_response +from .protocol import WebSocketCommonProtocol + + +__all__ = ["connect", "unix_connect", "WebSocketClientProtocol"] + + +class WebSocketClientProtocol(WebSocketCommonProtocol): + """ + WebSocket client connection. + + :class:`WebSocketClientProtocol` provides :meth:`recv` and :meth:`send` + coroutines for receiving and sending messages. + + It supports asynchronous iteration to receive messages:: + + async for message in websocket: + await process(message) + + The iterator exits normally when the connection is closed with close code + 1000 (OK) or 1001 (going away) or without a close code. It raises + a :exc:`~websockets.exceptions.ConnectionClosedError` when the connection + is closed with any other code. + + See :func:`connect` for the documentation of ``logger``, ``origin``, + ``extensions``, ``subprotocols``, ``extra_headers``, and + ``user_agent_header``. + + See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the + documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``, + ``max_size``, ``max_queue``, ``read_limit``, and ``write_limit``. + + """ + + is_client = True + side = "client" + + def __init__( + self, + *, + logger: LoggerLike | None = None, + origin: Origin | None = None, + extensions: Sequence[ClientExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + extra_headers: HeadersLike | None = None, + user_agent_header: str | None = USER_AGENT, + **kwargs: Any, + ) -> None: + if logger is None: + logger = logging.getLogger("websockets.client") + super().__init__(logger=logger, **kwargs) + self.origin = origin + self.available_extensions = extensions + self.available_subprotocols = subprotocols + self.extra_headers = extra_headers + self.user_agent_header = user_agent_header + + def write_http_request(self, path: str, headers: Headers) -> None: + """ + Write request line and headers to the HTTP request. + + """ + self.path = path + self.request_headers = headers + + if self.debug: + self.logger.debug("> GET %s HTTP/1.1", path) + for key, value in headers.raw_items(): + self.logger.debug("> %s: %s", key, value) + + # Since the path and headers only contain ASCII characters, + # we can keep this simple. + request = f"GET {path} HTTP/1.1\r\n" + request += str(headers) + + self.transport.write(request.encode()) + + async def read_http_response(self) -> tuple[int, Headers]: + """ + Read status line and headers from the HTTP response. + + If the response contains a body, it may be read from ``self.reader`` + after this coroutine returns. + + Raises: + InvalidMessage: If the HTTP message is malformed or isn't an + HTTP/1.1 GET response. + + """ + try: + status_code, reason, headers = await read_response(self.reader) + except Exception as exc: + raise InvalidMessage("did not receive a valid HTTP response") from exc + + if self.debug: + self.logger.debug("< HTTP/1.1 %d %s", status_code, reason) + for key, value in headers.raw_items(): + self.logger.debug("< %s: %s", key, value) + + self.response_headers = headers + + return status_code, self.response_headers + + @staticmethod + def process_extensions( + headers: Headers, + available_extensions: Sequence[ClientExtensionFactory] | None, + ) -> list[Extension]: + """ + Handle the Sec-WebSocket-Extensions HTTP response header. + + Check that each extension is supported, as well as its parameters. + + Return the list of accepted extensions. + + Raise :exc:`~websockets.exceptions.InvalidHandshake` to abort the + connection. + + :rfc:`6455` leaves the rules up to the specification of each + :extension. + + To provide this level of flexibility, for each extension accepted by + the server, we check for a match with each extension available in the + client configuration. If no match is found, an exception is raised. + + If several variants of the same extension are accepted by the server, + it may be configured several times, which won't make sense in general. + Extensions must implement their own requirements. For this purpose, + the list of previously accepted extensions is provided. + + Other requirements, for example related to mandatory extensions or the + order of extensions, may be implemented by overriding this method. + + """ + accepted_extensions: list[Extension] = [] + + header_values = headers.get_all("Sec-WebSocket-Extensions") + + if header_values: + if available_extensions is None: + raise NegotiationError("no extensions supported") + + parsed_header_values: list[ExtensionHeader] = sum( + [parse_extension(header_value) for header_value in header_values], [] + ) + + for name, response_params in parsed_header_values: + for extension_factory in available_extensions: + # Skip non-matching extensions based on their name. + if extension_factory.name != name: + continue + + # Skip non-matching extensions based on their params. + try: + extension = extension_factory.process_response_params( + response_params, accepted_extensions + ) + except NegotiationError: + continue + + # Add matching extension to the final list. + accepted_extensions.append(extension) + + # Break out of the loop once we have a match. + break + + # If we didn't break from the loop, no extension in our list + # matched what the server sent. Fail the connection. + else: + raise NegotiationError( + f"Unsupported extension: " + f"name = {name}, params = {response_params}" + ) + + return accepted_extensions + + @staticmethod + def process_subprotocol( + headers: Headers, available_subprotocols: Sequence[Subprotocol] | None + ) -> Subprotocol | None: + """ + Handle the Sec-WebSocket-Protocol HTTP response header. + + Check that it contains exactly one supported subprotocol. + + Return the selected subprotocol. + + """ + subprotocol: Subprotocol | None = None + + header_values = headers.get_all("Sec-WebSocket-Protocol") + + if header_values: + if available_subprotocols is None: + raise NegotiationError("no subprotocols supported") + + parsed_header_values: Sequence[Subprotocol] = sum( + [parse_subprotocol(header_value) for header_value in header_values], [] + ) + + if len(parsed_header_values) > 1: + raise InvalidHeaderValue( + "Sec-WebSocket-Protocol", + f"multiple values: {', '.join(parsed_header_values)}", + ) + + subprotocol = parsed_header_values[0] + + if subprotocol not in available_subprotocols: + raise NegotiationError(f"unsupported subprotocol: {subprotocol}") + + return subprotocol + + async def handshake( + self, + wsuri: WebSocketURI, + origin: Origin | None = None, + available_extensions: Sequence[ClientExtensionFactory] | None = None, + available_subprotocols: Sequence[Subprotocol] | None = None, + extra_headers: HeadersLike | None = None, + ) -> None: + """ + Perform the client side of the opening handshake. + + Args: + wsuri: URI of the WebSocket server. + origin: Value of the ``Origin`` header. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + extra_headers: Arbitrary HTTP headers to add to the handshake request. + + Raises: + InvalidHandshake: If the handshake fails. + + """ + request_headers = Headers() + + request_headers["Host"] = build_host(wsuri.host, wsuri.port, wsuri.secure) + + if wsuri.user_info: + request_headers["Authorization"] = build_authorization_basic( + *wsuri.user_info + ) + + if origin is not None: + request_headers["Origin"] = origin + + key = build_request(request_headers) + + if available_extensions is not None: + extensions_header = build_extension( + [ + (extension_factory.name, extension_factory.get_request_params()) + for extension_factory in available_extensions + ] + ) + request_headers["Sec-WebSocket-Extensions"] = extensions_header + + if available_subprotocols is not None: + protocol_header = build_subprotocol(available_subprotocols) + request_headers["Sec-WebSocket-Protocol"] = protocol_header + + if self.extra_headers is not None: + request_headers.update(self.extra_headers) + + if self.user_agent_header: + request_headers.setdefault("User-Agent", self.user_agent_header) + + self.write_http_request(wsuri.resource_name, request_headers) + + status_code, response_headers = await self.read_http_response() + if status_code in (301, 302, 303, 307, 308): + if "Location" not in response_headers: + raise InvalidHeader("Location") + raise RedirectHandshake(response_headers["Location"]) + elif status_code != 101: + raise InvalidStatusCode(status_code, response_headers) + + check_response(response_headers, key) + + self.extensions = self.process_extensions( + response_headers, available_extensions + ) + + self.subprotocol = self.process_subprotocol( + response_headers, available_subprotocols + ) + + self.connection_open() + + +class Connect: + """ + Connect to the WebSocket server at ``uri``. + + Awaiting :func:`connect` yields a :class:`WebSocketClientProtocol` which + can then be used to send and receive messages. + + :func:`connect` can be used as a asynchronous context manager:: + + async with connect(...) as websocket: + ... + + The connection is closed automatically when exiting the context. + + :func:`connect` can be used as an infinite asynchronous iterator to + reconnect automatically on errors:: + + async for websocket in connect(...): + try: + ... + except websockets.exceptions.ConnectionClosed: + continue + + The connection is closed automatically after each iteration of the loop. + + If an error occurs while establishing the connection, :func:`connect` + retries with exponential backoff. The backoff delay starts at three + seconds and increases up to one minute. + + If an error occurs in the body of the loop, you can handle the exception + and :func:`connect` will reconnect with the next iteration; or you can + let the exception bubble up and break out of the loop. This lets you + decide which errors trigger a reconnection and which errors are fatal. + + Args: + uri: URI of the WebSocket server. + create_protocol: Factory for the :class:`asyncio.Protocol` managing + the connection. It defaults to :class:`WebSocketClientProtocol`. + Set it to a wrapper or a subclass to customize connection handling. + logger: Logger for this client. + It defaults to ``logging.getLogger("websockets.client")``. + See the :doc:`logging guide <../../topics/logging>` for details. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + origin: Value of the ``Origin`` header, for servers that require it. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + extra_headers: Arbitrary HTTP headers to add to the handshake request. + user_agent_header: Value of the ``User-Agent`` request header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. + Setting it to :obj:`None` removes the header. + open_timeout: Timeout for opening the connection in seconds. + :obj:`None` disables the timeout. + + See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the + documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``, + ``max_size``, ``max_queue``, ``read_limit``, and ``write_limit``. + + Any other keyword arguments are passed the event loop's + :meth:`~asyncio.loop.create_connection` method. + + For example: + + * You can set ``ssl`` to a :class:`~ssl.SSLContext` to enforce TLS + settings. When connecting to a ``wss://`` URI, if ``ssl`` isn't + provided, a TLS context is created + with :func:`~ssl.create_default_context`. + + * You can set ``host`` and ``port`` to connect to a different host and + port from those found in ``uri``. This only changes the destination of + the TCP connection. The host name from ``uri`` is still used in the TLS + handshake for secure connections and in the ``Host`` header. + + Raises: + InvalidURI: If ``uri`` isn't a valid WebSocket URI. + OSError: If the TCP connection fails. + InvalidHandshake: If the opening handshake fails. + ~asyncio.TimeoutError: If the opening handshake times out. + + """ + + MAX_REDIRECTS_ALLOWED = int(os.environ.get("WEBSOCKETS_MAX_REDIRECTS", "10")) + + def __init__( + self, + uri: str, + *, + create_protocol: Callable[..., WebSocketClientProtocol] | None = None, + logger: LoggerLike | None = None, + compression: str | None = "deflate", + origin: Origin | None = None, + extensions: Sequence[ClientExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + extra_headers: HeadersLike | None = None, + user_agent_header: str | None = USER_AGENT, + open_timeout: float | None = 10, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = None, + max_size: int | None = 2**20, + max_queue: int | None = 2**5, + read_limit: int = 2**16, + write_limit: int = 2**16, + **kwargs: Any, + ) -> None: + # Backwards compatibility: close_timeout used to be called timeout. + timeout: float | None = kwargs.pop("timeout", None) + if timeout is None: + timeout = 10 + else: + warnings.warn("rename timeout to close_timeout", DeprecationWarning) + # If both are specified, timeout is ignored. + if close_timeout is None: + close_timeout = timeout + + # Backwards compatibility: create_protocol used to be called klass. + klass: type[WebSocketClientProtocol] | None = kwargs.pop("klass", None) + if klass is None: + klass = WebSocketClientProtocol + else: + warnings.warn("rename klass to create_protocol", DeprecationWarning) + # If both are specified, klass is ignored. + if create_protocol is None: + create_protocol = klass + + # Backwards compatibility: recv() used to return None on closed connections + legacy_recv: bool = kwargs.pop("legacy_recv", False) + + # Backwards compatibility: the loop parameter used to be supported. + _loop: asyncio.AbstractEventLoop | None = kwargs.pop("loop", None) + if _loop is None: + loop = asyncio.get_event_loop() + else: + loop = _loop + warnings.warn("remove loop argument", DeprecationWarning) + + wsuri = parse_uri(uri) + if wsuri.secure: + kwargs.setdefault("ssl", True) + elif kwargs.get("ssl") is not None: + raise ValueError( + "connect() received a ssl argument for a ws:// URI, " + "use a wss:// URI to enable TLS" + ) + + if compression == "deflate": + extensions = enable_client_permessage_deflate(extensions) + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + if subprotocols is not None: + validate_subprotocols(subprotocols) + + # Help mypy and avoid this error: "type[WebSocketClientProtocol] | + # Callable[..., WebSocketClientProtocol]" not callable [misc] + create_protocol = cast(Callable[..., WebSocketClientProtocol], create_protocol) + factory = functools.partial( + create_protocol, + logger=logger, + origin=origin, + extensions=extensions, + subprotocols=subprotocols, + extra_headers=extra_headers, + user_agent_header=user_agent_header, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_size=max_size, + max_queue=max_queue, + read_limit=read_limit, + write_limit=write_limit, + host=wsuri.host, + port=wsuri.port, + secure=wsuri.secure, + legacy_recv=legacy_recv, + loop=_loop, + ) + + if kwargs.pop("unix", False): + path: str | None = kwargs.pop("path", None) + create_connection = functools.partial( + loop.create_unix_connection, factory, path, **kwargs + ) + else: + host: str | None + port: int | None + if kwargs.get("sock") is None: + host, port = wsuri.host, wsuri.port + else: + # If sock is given, host and port shouldn't be specified. + host, port = None, None + if kwargs.get("ssl"): + kwargs.setdefault("server_hostname", wsuri.host) + # If host and port are given, override values from the URI. + host = kwargs.pop("host", host) + port = kwargs.pop("port", port) + create_connection = functools.partial( + loop.create_connection, factory, host, port, **kwargs + ) + + self.open_timeout = open_timeout + if logger is None: + logger = logging.getLogger("websockets.client") + self.logger = logger + + # This is a coroutine function. + self._create_connection = create_connection + self._uri = uri + self._wsuri = wsuri + + def handle_redirect(self, uri: str) -> None: + # Update the state of this instance to connect to a new URI. + old_uri = self._uri + old_wsuri = self._wsuri + new_uri = urllib.parse.urljoin(old_uri, uri) + new_wsuri = parse_uri(new_uri) + + # Forbid TLS downgrade. + if old_wsuri.secure and not new_wsuri.secure: + raise SecurityError("redirect from WSS to WS") + + same_origin = ( + old_wsuri.secure == new_wsuri.secure + and old_wsuri.host == new_wsuri.host + and old_wsuri.port == new_wsuri.port + ) + + # Rewrite secure, host, and port for cross-origin redirects. + # This preserves connection overrides with the host and port + # arguments if the redirect points to the same host and port. + if not same_origin: + factory = self._create_connection.args[0] + # Support TLS upgrade. + if not old_wsuri.secure and new_wsuri.secure: + factory.keywords["secure"] = True + self._create_connection.keywords.setdefault("ssl", True) + # Replace secure, host, and port arguments of the protocol factory. + factory = functools.partial( + factory.func, + *factory.args, + **dict(factory.keywords, host=new_wsuri.host, port=new_wsuri.port), + ) + # Replace secure, host, and port arguments of create_connection. + self._create_connection = functools.partial( + self._create_connection.func, + *(factory, new_wsuri.host, new_wsuri.port), + **self._create_connection.keywords, + ) + + # Set the new WebSocket URI. This suffices for same-origin redirects. + self._uri = new_uri + self._wsuri = new_wsuri + + # async for ... in connect(...): + + BACKOFF_INITIAL = float(os.environ.get("WEBSOCKETS_BACKOFF_INITIAL_DELAY", "5")) + BACKOFF_MIN = float(os.environ.get("WEBSOCKETS_BACKOFF_MIN_DELAY", "3.1")) + BACKOFF_MAX = float(os.environ.get("WEBSOCKETS_BACKOFF_MAX_DELAY", "90.0")) + BACKOFF_FACTOR = float(os.environ.get("WEBSOCKETS_BACKOFF_FACTOR", "1.618")) + + async def __aiter__(self) -> AsyncIterator[WebSocketClientProtocol]: + backoff_delay = self.BACKOFF_MIN / self.BACKOFF_FACTOR + while True: + try: + async with self as protocol: + yield protocol + except Exception as exc: + # Add a random initial delay between 0 and 5 seconds. + # See 7.2.3. Recovering from Abnormal Closure in RFC 6455. + if backoff_delay == self.BACKOFF_MIN: + initial_delay = random.random() * self.BACKOFF_INITIAL + self.logger.info( + "connect failed; reconnecting in %.1f seconds: %s", + initial_delay, + traceback.format_exception_only(exc)[0].strip(), + ) + await asyncio.sleep(initial_delay) + else: + self.logger.info( + "connect failed again; retrying in %d seconds: %s", + int(backoff_delay), + traceback.format_exception_only(exc)[0].strip(), + ) + await asyncio.sleep(int(backoff_delay)) + # Increase delay with truncated exponential backoff. + backoff_delay = backoff_delay * self.BACKOFF_FACTOR + backoff_delay = min(backoff_delay, self.BACKOFF_MAX) + continue + else: + # Connection succeeded - reset backoff delay + backoff_delay = self.BACKOFF_MIN + + # async with connect(...) as ...: + + async def __aenter__(self) -> WebSocketClientProtocol: + return await self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self.protocol.close() + + # ... = await connect(...) + + def __await__(self) -> Generator[Any, None, WebSocketClientProtocol]: + # Create a suitable iterator by calling __await__ on a coroutine. + return self.__await_impl__().__await__() + + async def __await_impl__(self) -> WebSocketClientProtocol: + async with asyncio_timeout(self.open_timeout): + for _redirects in range(self.MAX_REDIRECTS_ALLOWED): + _transport, protocol = await self._create_connection() + try: + await protocol.handshake( + self._wsuri, + origin=protocol.origin, + available_extensions=protocol.available_extensions, + available_subprotocols=protocol.available_subprotocols, + extra_headers=protocol.extra_headers, + ) + except RedirectHandshake as exc: + protocol.fail_connection() + await protocol.wait_closed() + self.handle_redirect(exc.uri) + # Avoid leaking a connected socket when the handshake fails. + except (Exception, asyncio.CancelledError): + protocol.fail_connection() + await protocol.wait_closed() + raise + else: + self.protocol = protocol + return protocol + else: + raise SecurityError("too many redirects") + + # ... = yield from connect(...) - remove when dropping Python < 3.11 + + __iter__ = __await__ + + +connect = Connect + + +def unix_connect( + path: str | None = None, + uri: str = "ws://localhost/", + **kwargs: Any, +) -> Connect: + """ + Similar to :func:`connect`, but for connecting to a Unix socket. + + This function builds upon the event loop's + :meth:`~asyncio.loop.create_unix_connection` method. + + It is only available on Unix. + + It's mainly useful for debugging servers listening on Unix sockets. + + Args: + path: File system path to the Unix socket. + uri: URI of the WebSocket server; the host is used in the TLS + handshake for secure connections and in the ``Host`` header. + + """ + return connect(uri=uri, path=path, unix=True, **kwargs) diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/exceptions.py b/.venv/lib/python3.14/site-packages/websockets/legacy/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..29a2525b4e73b788f773682ce0b88e13eafc6e26 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/legacy/exceptions.py @@ -0,0 +1,71 @@ +import http + +from .. import datastructures +from ..exceptions import ( + InvalidHandshake, + # InvalidMessage was incorrectly moved here in versions 14.0 and 14.1. + InvalidMessage, # noqa: F401 + ProtocolError as WebSocketProtocolError, # noqa: F401 +) +from ..typing import StatusLike + + +class InvalidStatusCode(InvalidHandshake): + """ + Raised when a handshake response status code is invalid. + + """ + + def __init__(self, status_code: int, headers: datastructures.Headers) -> None: + self.status_code = status_code + self.headers = headers + + def __str__(self) -> str: + return f"server rejected WebSocket connection: HTTP {self.status_code}" + + +class AbortHandshake(InvalidHandshake): + """ + Raised to abort the handshake on purpose and return an HTTP response. + + This exception is an implementation detail. + + The public API is + :meth:`~websockets.legacy.server.WebSocketServerProtocol.process_request`. + + Attributes: + status (~http.HTTPStatus): HTTP status code. + headers (Headers): HTTP response headers. + body (bytes): HTTP response body. + """ + + def __init__( + self, + status: StatusLike, + headers: datastructures.HeadersLike, + body: bytes = b"", + ) -> None: + # If a user passes an int instead of an HTTPStatus, fix it automatically. + self.status = http.HTTPStatus(status) + self.headers = datastructures.Headers(headers) + self.body = body + + def __str__(self) -> str: + return ( + f"HTTP {self.status:d}, {len(self.headers)} headers, {len(self.body)} bytes" + ) + + +class RedirectHandshake(InvalidHandshake): + """ + Raised when a handshake gets redirected. + + This exception is an implementation detail. + + """ + + def __init__(self, uri: str) -> None: + self.uri = uri + + def __str__(self) -> str: + return f"redirect to {self.uri}" diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/framing.py b/.venv/lib/python3.14/site-packages/websockets/legacy/framing.py new file mode 100644 index 0000000000000000000000000000000000000000..452d2fb34dc45577401321ce76d6a030d2e49597 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/legacy/framing.py @@ -0,0 +1,224 @@ +from __future__ import annotations + +import struct +from collections.abc import Awaitable, Sequence +from typing import Any, Callable, NamedTuple + +from .. import extensions, frames +from ..exceptions import PayloadTooBig, ProtocolError +from ..typing import BytesLike, DataLike + + +try: + from ..speedups import apply_mask +except ImportError: + from ..utils import apply_mask + + +class Frame(NamedTuple): + fin: bool + opcode: frames.Opcode + data: BytesLike + rsv1: bool = False + rsv2: bool = False + rsv3: bool = False + + @property + def new_frame(self) -> frames.Frame: + return frames.Frame( + self.opcode, + self.data, + self.fin, + self.rsv1, + self.rsv2, + self.rsv3, + ) + + def __str__(self) -> str: + return str(self.new_frame) + + def check(self) -> None: + return self.new_frame.check() + + @classmethod + async def read( + cls, + reader: Callable[[int], Awaitable[bytes]], + *, + mask: bool, + max_size: int | None = None, + extensions: Sequence[extensions.Extension] | None = None, + ) -> Frame: + """ + Read a WebSocket frame. + + Args: + reader: Coroutine that reads exactly the requested number of + bytes, unless the end of file is reached. + mask: Whether the frame should be masked i.e. whether the read + happens on the server side. + max_size: Maximum payload size in bytes. + extensions: List of extensions, applied in reverse order. + + Raises: + PayloadTooBig: If the frame exceeds ``max_size``. + ProtocolError: If the frame contains incorrect values. + + """ + + # Read the header. + data = await reader(2) + head1, head2 = struct.unpack("!BB", data) + + # While not Pythonic, this is marginally faster than calling bool(). + fin = True if head1 & 0b10000000 else False + rsv1 = True if head1 & 0b01000000 else False + rsv2 = True if head1 & 0b00100000 else False + rsv3 = True if head1 & 0b00010000 else False + + try: + opcode = frames.Opcode(head1 & 0b00001111) + except ValueError as exc: + raise ProtocolError("invalid opcode") from exc + + if (True if head2 & 0b10000000 else False) != mask: + raise ProtocolError("incorrect masking") + + length = head2 & 0b01111111 + if length == 126: + data = await reader(2) + (length,) = struct.unpack("!H", data) + elif length == 127: + data = await reader(8) + (length,) = struct.unpack("!Q", data) + if max_size is not None and length > max_size: + raise PayloadTooBig(length, max_size) + if mask: + mask_bits = await reader(4) + + # Read the data. + data = await reader(length) + if mask: + data = apply_mask(data, mask_bits) + + new_frame = frames.Frame(opcode, data, fin, rsv1, rsv2, rsv3) + + if extensions is None: + extensions = [] + for extension in reversed(extensions): + new_frame = extension.decode(new_frame, max_size=max_size) + + new_frame.check() + + return cls( + new_frame.fin, + new_frame.opcode, + new_frame.data, + new_frame.rsv1, + new_frame.rsv2, + new_frame.rsv3, + ) + + def write( + self, + write: Callable[[bytes], Any], + *, + mask: bool, + extensions: Sequence[extensions.Extension] | None = None, + ) -> None: + """ + Write a WebSocket frame. + + Args: + frame: Frame to write. + write: Function that writes bytes. + mask: Whether the frame should be masked i.e. whether the write + happens on the client side. + extensions: List of extensions, applied in order. + + Raises: + ProtocolError: If the frame contains incorrect values. + + """ + # The frame is written in a single call to write in order to prevent + # TCP fragmentation. See #68 for details. This also makes it safe to + # send frames concurrently from multiple coroutines. + write(self.new_frame.serialize(mask=mask, extensions=extensions)) + + +def prepare_data(data: DataLike) -> tuple[int, BytesLike]: + """ + Convert a string or byte-like object to an opcode and a bytes-like object. + + This function is designed for data frames. + + If ``data`` is a :class:`str`, return ``OP_TEXT`` and a :class:`bytes` + object encoding ``data`` in UTF-8. + + If ``data`` is a bytes-like object, return ``OP_BINARY`` and a bytes-like + object. + + Raises: + TypeError: If ``data`` doesn't have a supported type. + + """ + if isinstance(data, str): + return frames.Opcode.TEXT, data.encode() + elif isinstance(data, BytesLike): + return frames.Opcode.BINARY, data + else: + raise TypeError("data must be str or bytes-like") + + +def prepare_ctrl(data: DataLike) -> bytes: + """ + Convert a string or byte-like object to bytes. + + This function is designed for ping and pong frames. + + If ``data`` is a :class:`str`, return a :class:`bytes` object encoding + ``data`` in UTF-8. + + If ``data`` is a bytes-like object, return a :class:`bytes` object. + + Raises: + TypeError: If ``data`` doesn't have a supported type. + + """ + if isinstance(data, str): + return data.encode() + elif isinstance(data, BytesLike): + return bytes(data) + else: + raise TypeError("data must be str or bytes-like") + + +# Backwards compatibility with previously documented public APIs +encode_data = prepare_ctrl + +# Backwards compatibility with previously documented public APIs +from ..frames import Close # noqa: E402 F401, I001 + + +def parse_close(data: bytes) -> tuple[int, str]: + """ + Parse the payload from a close frame. + + Returns: + Close code and reason. + + Raises: + ProtocolError: If data is ill-formed. + UnicodeDecodeError: If the reason isn't valid UTF-8. + + """ + close = Close.parse(data) + return close.code, close.reason + + +def serialize_close(code: int, reason: str) -> bytes: + """ + Serialize the payload for a close frame. + + """ + return Close(code, reason).serialize() diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/handshake.py b/.venv/lib/python3.14/site-packages/websockets/legacy/handshake.py new file mode 100644 index 0000000000000000000000000000000000000000..6a7157c010720733ca42d363cd810c354bf9d221 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/legacy/handshake.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +import base64 +import binascii + +from ..datastructures import Headers, MultipleValuesError +from ..exceptions import InvalidHeader, InvalidHeaderValue, InvalidUpgrade +from ..headers import parse_connection, parse_upgrade +from ..typing import ConnectionOption, UpgradeProtocol +from ..utils import accept_key as accept, generate_key + + +__all__ = ["build_request", "check_request", "build_response", "check_response"] + + +def build_request(headers: Headers) -> str: + """ + Build a handshake request to send to the server. + + Update request headers passed in argument. + + Args: + headers: Handshake request headers. + + Returns: + ``key`` that must be passed to :func:`check_response`. + + """ + key = generate_key() + headers["Upgrade"] = "websocket" + headers["Connection"] = "Upgrade" + headers["Sec-WebSocket-Key"] = key + headers["Sec-WebSocket-Version"] = "13" + return key + + +def check_request(headers: Headers) -> str: + """ + Check a handshake request received from the client. + + This function doesn't verify that the request is an HTTP/1.1 or higher GET + request and doesn't perform ``Host`` and ``Origin`` checks. These controls + are usually performed earlier in the HTTP request handling code. They're + the responsibility of the caller. + + Args: + headers: Handshake request headers. + + Returns: + ``key`` that must be passed to :func:`build_response`. + + Raises: + InvalidHandshake: If the handshake request is invalid. + Then, the server must return a 400 Bad Request error. + + """ + connection: list[ConnectionOption] = sum( + [parse_connection(value) for value in headers.get_all("Connection")], [] + ) + + if not any(value.lower() == "upgrade" for value in connection): + raise InvalidUpgrade("Connection", ", ".join(connection)) + + upgrade: list[UpgradeProtocol] = sum( + [parse_upgrade(value) for value in headers.get_all("Upgrade")], [] + ) + + # For compatibility with non-strict implementations, ignore case when + # checking the Upgrade header. The RFC always uses "websocket", except + # in section 11.2. (IANA registration) where it uses "WebSocket". + if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"): + raise InvalidUpgrade("Upgrade", ", ".join(upgrade)) + + try: + s_w_key = headers["Sec-WebSocket-Key"] + except KeyError as exc: + raise InvalidHeader("Sec-WebSocket-Key") from exc + except MultipleValuesError as exc: + raise InvalidHeader("Sec-WebSocket-Key", "multiple values") from exc + + try: + raw_key = base64.b64decode(s_w_key.encode(), validate=True) + except binascii.Error as exc: + raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key) from exc + if len(raw_key) != 16: + raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key) + + try: + s_w_version = headers["Sec-WebSocket-Version"] + except KeyError as exc: + raise InvalidHeader("Sec-WebSocket-Version") from exc + except MultipleValuesError as exc: + raise InvalidHeader("Sec-WebSocket-Version", "multiple values") from exc + + if s_w_version != "13": + raise InvalidHeaderValue("Sec-WebSocket-Version", s_w_version) + + return s_w_key + + +def build_response(headers: Headers, key: str) -> None: + """ + Build a handshake response to send to the client. + + Update response headers passed in argument. + + Args: + headers: Handshake response headers. + key: Returned by :func:`check_request`. + + """ + headers["Upgrade"] = "websocket" + headers["Connection"] = "Upgrade" + headers["Sec-WebSocket-Accept"] = accept(key) + + +def check_response(headers: Headers, key: str) -> None: + """ + Check a handshake response received from the server. + + This function doesn't verify that the response is an HTTP/1.1 or higher + response with a 101 status code. These controls are the responsibility of + the caller. + + Args: + headers: Handshake response headers. + key: Returned by :func:`build_request`. + + Raises: + InvalidHandshake: If the handshake response is invalid. + + """ + connection: list[ConnectionOption] = sum( + [parse_connection(value) for value in headers.get_all("Connection")], [] + ) + + if not any(value.lower() == "upgrade" for value in connection): + raise InvalidUpgrade("Connection", " ".join(connection)) + + upgrade: list[UpgradeProtocol] = sum( + [parse_upgrade(value) for value in headers.get_all("Upgrade")], [] + ) + + # For compatibility with non-strict implementations, ignore case when + # checking the Upgrade header. The RFC always uses "websocket", except + # in section 11.2. (IANA registration) where it uses "WebSocket". + if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"): + raise InvalidUpgrade("Upgrade", ", ".join(upgrade)) + + try: + s_w_accept = headers["Sec-WebSocket-Accept"] + except KeyError as exc: + raise InvalidHeader("Sec-WebSocket-Accept") from exc + except MultipleValuesError as exc: + raise InvalidHeader("Sec-WebSocket-Accept", "multiple values") from exc + + if s_w_accept != accept(key): + raise InvalidHeaderValue("Sec-WebSocket-Accept", s_w_accept) diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/http.py b/.venv/lib/python3.14/site-packages/websockets/legacy/http.py new file mode 100644 index 0000000000000000000000000000000000000000..a7c8a927e177d36f6a54bcc293dc853e2e15e736 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/legacy/http.py @@ -0,0 +1,201 @@ +from __future__ import annotations + +import asyncio +import os +import re + +from ..datastructures import Headers +from ..exceptions import SecurityError + + +__all__ = ["read_request", "read_response"] + +MAX_NUM_HEADERS = int(os.environ.get("WEBSOCKETS_MAX_NUM_HEADERS", "128")) +MAX_LINE_LENGTH = int(os.environ.get("WEBSOCKETS_MAX_LINE_LENGTH", "8192")) + + +def d(value: bytes) -> str: + """ + Decode a bytestring for interpolating into an error message. + + """ + return value.decode(errors="backslashreplace") + + +# See https://datatracker.ietf.org/doc/html/rfc7230#appendix-B. + +# Regex for validating header names. + +_token_re = re.compile(rb"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+") + +# Regex for validating header values. + +# We don't attempt to support obsolete line folding. + +# Include HTAB (\x09), SP (\x20), VCHAR (\x21-\x7e), obs-text (\x80-\xff). + +# The ABNF is complicated because it attempts to express that optional +# whitespace is ignored. We strip whitespace and don't revalidate that. + +# See also https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189 + +_value_re = re.compile(rb"[\x09\x20-\x7e\x80-\xff]*") + + +async def read_request(stream: asyncio.StreamReader) -> tuple[str, Headers]: + """ + Read an HTTP/1.1 GET request and return ``(path, headers)``. + + ``path`` isn't URL-decoded or validated in any way. + + ``path`` and ``headers`` are expected to contain only ASCII characters. + Other characters are represented with surrogate escapes. + + :func:`read_request` doesn't attempt to read the request body because + WebSocket handshake requests don't have one. If the request contains a + body, it may be read from ``stream`` after this coroutine returns. + + Args: + stream: Input to read the request from. + + Raises: + EOFError: If the connection is closed without a full HTTP request. + SecurityError: If the request exceeds a security limit. + ValueError: If the request isn't well formatted. + + """ + # https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.1 + + # Parsing is simple because fixed values are expected for method and + # version and because path isn't checked. Since WebSocket software tends + # to implement HTTP/1.1 strictly, there's little need for lenient parsing. + + try: + request_line = await read_line(stream) + except EOFError as exc: + raise EOFError("connection closed while reading HTTP request line") from exc + + try: + method, raw_path, version = request_line.split(b" ", 2) + except ValueError: # not enough values to unpack (expected 3, got 1-2) + raise ValueError(f"invalid HTTP request line: {d(request_line)}") from None + + if method != b"GET": + raise ValueError(f"unsupported HTTP method: {d(method)}") + if version != b"HTTP/1.1": + raise ValueError(f"unsupported HTTP version: {d(version)}") + path = raw_path.decode("ascii", "surrogateescape") + + headers = await read_headers(stream) + + return path, headers + + +async def read_response(stream: asyncio.StreamReader) -> tuple[int, str, Headers]: + """ + Read an HTTP/1.1 response and return ``(status_code, reason, headers)``. + + ``reason`` and ``headers`` are expected to contain only ASCII characters. + Other characters are represented with surrogate escapes. + + :func:`read_request` doesn't attempt to read the response body because + WebSocket handshake responses don't have one. If the response contains a + body, it may be read from ``stream`` after this coroutine returns. + + Args: + stream: Input to read the response from. + + Raises: + EOFError: If the connection is closed without a full HTTP response. + SecurityError: If the response exceeds a security limit. + ValueError: If the response isn't well formatted. + + """ + # https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + + # As in read_request, parsing is simple because a fixed value is expected + # for version, status_code is a 3-digit number, and reason can be ignored. + + try: + status_line = await read_line(stream) + except EOFError as exc: + raise EOFError("connection closed while reading HTTP status line") from exc + + try: + version, raw_status_code, raw_reason = status_line.split(b" ", 2) + except ValueError: # not enough values to unpack (expected 3, got 1-2) + raise ValueError(f"invalid HTTP status line: {d(status_line)}") from None + + if version != b"HTTP/1.1": + raise ValueError(f"unsupported HTTP version: {d(version)}") + try: + status_code = int(raw_status_code) + except ValueError: # invalid literal for int() with base 10 + raise ValueError(f"invalid HTTP status code: {d(raw_status_code)}") from None + if not 100 <= status_code < 1000: + raise ValueError(f"unsupported HTTP status code: {d(raw_status_code)}") + if not _value_re.fullmatch(raw_reason): + raise ValueError(f"invalid HTTP reason phrase: {d(raw_reason)}") + reason = raw_reason.decode() + + headers = await read_headers(stream) + + return status_code, reason, headers + + +async def read_headers(stream: asyncio.StreamReader) -> Headers: + """ + Read HTTP headers from ``stream``. + + Non-ASCII characters are represented with surrogate escapes. + + """ + # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 + + # We don't attempt to support obsolete line folding. + + headers = Headers() + for _ in range(MAX_NUM_HEADERS + 1): + try: + line = await read_line(stream) + except EOFError as exc: + raise EOFError("connection closed while reading HTTP headers") from exc + if line == b"": + break + + try: + raw_name, raw_value = line.split(b":", 1) + except ValueError: # not enough values to unpack (expected 2, got 1) + raise ValueError(f"invalid HTTP header line: {d(line)}") from None + if not _token_re.fullmatch(raw_name): + raise ValueError(f"invalid HTTP header name: {d(raw_name)}") + raw_value = raw_value.strip(b" \t") + if not _value_re.fullmatch(raw_value): + raise ValueError(f"invalid HTTP header value: {d(raw_value)}") + + name = raw_name.decode("ascii") # guaranteed to be ASCII at this point + value = raw_value.decode("ascii", "surrogateescape") + headers[name] = value + + else: + raise SecurityError("too many HTTP headers") + + return headers + + +async def read_line(stream: asyncio.StreamReader) -> bytes: + """ + Read a single line from ``stream``. + + CRLF is stripped from the return value. + + """ + # Security: this is bounded by the StreamReader's limit (default = 32 KiB). + line = await stream.readline() + # Security: this guarantees header values are small (hard-coded = 8 KiB) + if len(line) > MAX_LINE_LENGTH: + raise SecurityError("line too long") + # Not mandatory but safe - https://datatracker.ietf.org/doc/html/rfc7230#section-3.5 + if not line.endswith(b"\r\n"): + raise EOFError("line without CRLF") + return line[:-2] diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/protocol.py b/.venv/lib/python3.14/site-packages/websockets/legacy/protocol.py new file mode 100644 index 0000000000000000000000000000000000000000..ab4ec6bcec9ee886a97815239376dff0d166d8f9 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/legacy/protocol.py @@ -0,0 +1,1635 @@ +from __future__ import annotations + +import asyncio +import codecs +import collections +import logging +import random +import ssl +import struct +import sys +import time +import traceback +import uuid +import warnings +from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Iterable, Mapping +from typing import Any, Callable, Deque, cast + +from ..asyncio.compatibility import asyncio_timeout +from ..datastructures import Headers +from ..exceptions import ( + ConnectionClosed, + ConnectionClosedError, + ConnectionClosedOK, + InvalidState, + PayloadTooBig, + ProtocolError, +) +from ..extensions import Extension +from ..frames import ( + OK_CLOSE_CODES, + OP_BINARY, + OP_CLOSE, + OP_CONT, + OP_PING, + OP_PONG, + OP_TEXT, + Close, + CloseCode, + Opcode, +) +from ..protocol import State +from ..typing import BytesLike, Data, DataLike, LoggerLike, Subprotocol +from .framing import Frame, prepare_ctrl, prepare_data + + +__all__ = ["WebSocketCommonProtocol"] + + +# In order to ensure consistency, the code always checks the current value of +# WebSocketCommonProtocol.state before assigning a new value and never yields +# between the check and the assignment. + + +class WebSocketCommonProtocol(asyncio.Protocol): + """ + WebSocket connection. + + :class:`WebSocketCommonProtocol` provides APIs shared between WebSocket + servers and clients. You shouldn't use it directly. Instead, use + :class:`~websockets.legacy.client.WebSocketClientProtocol` or + :class:`~websockets.legacy.server.WebSocketServerProtocol`. + + This documentation focuses on low-level details that aren't covered in the + documentation of :class:`~websockets.legacy.client.WebSocketClientProtocol` + and :class:`~websockets.legacy.server.WebSocketServerProtocol` for the sake + of simplicity. + + Once the connection is open, a Ping_ frame is sent every ``ping_interval`` + seconds. This serves as a keepalive. It helps keeping the connection open, + especially in the presence of proxies with short timeouts on inactive + connections. Set ``ping_interval`` to :obj:`None` to disable this behavior. + + .. _Ping: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2 + + If the corresponding Pong_ frame isn't received within ``ping_timeout`` + seconds, the connection is considered unusable and is closed with code 1011. + This ensures that the remote endpoint remains responsive. Set + ``ping_timeout`` to :obj:`None` to disable this behavior. + + .. _Pong: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3 + + See the discussion of :doc:`keepalive <../../topics/keepalive>` for details. + + The ``close_timeout`` parameter defines a maximum wait time for completing + the closing handshake and terminating the TCP connection. For legacy + reasons, :meth:`close` completes in at most ``5 * close_timeout`` seconds + for clients and ``4 * close_timeout`` for servers. + + ``close_timeout`` is a parameter of the protocol because websockets usually + calls :meth:`close` implicitly upon exit: + + * on the client side, when using :func:`~websockets.legacy.client.connect` + as a context manager; + * on the server side, when the connection handler terminates. + + To apply a timeout to any other API, wrap it in :func:`~asyncio.timeout` or + :func:`~asyncio.wait_for`. + + The ``max_size`` parameter enforces the maximum size for incoming messages + in bytes. The default value is 1 MiB. If a larger message is received, + :meth:`recv` will raise :exc:`~websockets.exceptions.ConnectionClosedError` + and the connection will be closed with code 1009. + + The ``max_queue`` parameter sets the maximum length of the queue that + holds incoming messages. The default value is ``32``. Messages are added + to an in-memory queue when they're received; then :meth:`recv` pops from + that queue. In order to prevent excessive memory consumption when + messages are received faster than they can be processed, the queue must + be bounded. If the queue fills up, the protocol stops processing incoming + data until :meth:`recv` is called. In this situation, various receive + buffers (at least in :mod:`asyncio` and in the OS) will fill up, then the + TCP receive window will shrink, slowing down transmission to avoid packet + loss. + + Since Python can use up to 4 bytes of memory to represent a single + character, each connection may use up to ``4 * max_size * max_queue`` + bytes of memory to store incoming messages. By default, this is 128 MiB. + You may want to lower the limits, depending on your application's + requirements. + + The ``read_limit`` argument sets the high-water limit of the buffer for + incoming bytes. The low-water limit is half the high-water limit. The + default value is 64 KiB, half of asyncio's default (based on the current + implementation of :class:`~asyncio.StreamReader`). + + The ``write_limit`` argument sets the high-water limit of the buffer for + outgoing bytes. The low-water limit is a quarter of the high-water limit. + The default value is 64 KiB, equal to asyncio's default (based on the + current implementation of ``FlowControlMixin``). + + See the discussion of :doc:`memory usage <../../topics/memory>` for details. + + Args: + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.protocol")``. + See the :doc:`logging guide <../../topics/logging>` for details. + ping_interval: Interval between keepalive pings in seconds. + :obj:`None` disables keepalive. + ping_timeout: Timeout for keepalive pings in seconds. + :obj:`None` disables timeouts. + close_timeout: Timeout for closing the connection in seconds. + For legacy reasons, the actual timeout is 4 or 5 times larger. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. + max_queue: Maximum number of incoming messages in receive buffer. + :obj:`None` disables the limit. + read_limit: High-water mark of read buffer in bytes. + write_limit: High-water mark of write buffer in bytes. + + """ + + # There are only two differences between the client-side and server-side + # behavior: masking the payload and closing the underlying TCP connection. + # Set is_client = True/False and side = "client"/"server" to pick a side. + is_client: bool + side: str = "undefined" + + def __init__( + self, + *, + logger: LoggerLike | None = None, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = None, + max_size: int | None = 2**20, + max_queue: int | None = 2**5, + read_limit: int = 2**16, + write_limit: int = 2**16, + # The following arguments are kept only for backwards compatibility. + host: str | None = None, + port: int | None = None, + secure: bool | None = None, + legacy_recv: bool = False, + loop: asyncio.AbstractEventLoop | None = None, + timeout: float | None = None, + ) -> None: + if legacy_recv: # pragma: no cover + warnings.warn("legacy_recv is deprecated", DeprecationWarning) + + # Backwards compatibility: close_timeout used to be called timeout. + if timeout is None: + timeout = 10 + else: + warnings.warn("rename timeout to close_timeout", DeprecationWarning) + # If both are specified, timeout is ignored. + if close_timeout is None: + close_timeout = timeout + + # Backwards compatibility: the loop parameter used to be supported. + if loop is None: + loop = asyncio.get_event_loop() + else: + warnings.warn("remove loop argument", DeprecationWarning) + + self.ping_interval = ping_interval + self.ping_timeout = ping_timeout + self.close_timeout = close_timeout + self.max_size = max_size + self.max_queue = max_queue + self.read_limit = read_limit + self.write_limit = write_limit + + # Unique identifier. For logs. + self.id: uuid.UUID = uuid.uuid4() + """Unique identifier of the connection. Useful in logs.""" + + # Logger or LoggerAdapter for this connection. + if logger is None: + logger = logging.getLogger("websockets.protocol") + self.logger: LoggerLike = logging.LoggerAdapter(logger, {"websocket": self}) + """Logger for this connection.""" + + # Track if DEBUG is enabled. Shortcut logging calls if it isn't. + self.debug = logger.isEnabledFor(logging.DEBUG) + + self.loop = loop + + self._host = host + self._port = port + self._secure = secure + self.legacy_recv = legacy_recv + + # Configure read buffer limits. The high-water limit is defined by + # ``self.read_limit``. The ``limit`` argument controls the line length + # limit and half the buffer limit of :class:`~asyncio.StreamReader`. + # That's why it must be set to half of ``self.read_limit``. + self.reader = asyncio.StreamReader(limit=read_limit // 2, loop=loop) + + # Copied from asyncio.FlowControlMixin + self._paused = False + self._drain_waiter: asyncio.Future[None] | None = None + + # This class implements the data transfer and closing handshake, which + # are shared between the client-side and the server-side. + # Subclasses implement the opening handshake and, on success, execute + # :meth:`connection_open` to change the state to OPEN. + self.state = State.CONNECTING + if self.debug: + self.logger.debug("= connection is CONNECTING") + + # HTTP protocol parameters. + self.path: str + """Path of the opening handshake request.""" + self.request_headers: Headers + """Opening handshake request headers.""" + self.response_headers: Headers + """Opening handshake response headers.""" + + # WebSocket protocol parameters. + self.extensions: list[Extension] = [] + self.subprotocol: Subprotocol | None = None + """Subprotocol, if one was negotiated.""" + + # Close code and reason, set when a close frame is sent or received. + self.close_rcvd: Close | None = None + self.close_sent: Close | None = None + self.close_rcvd_then_sent: bool | None = None + + # Completed when the connection state becomes CLOSED. Translates the + # :meth:`connection_lost` callback to a :class:`~asyncio.Future` + # that can be awaited. (Other :class:`~asyncio.Protocol` callbacks are + # translated by ``self.stream_reader``). + self.connection_lost_waiter: asyncio.Future[None] = loop.create_future() + + # Queue of received messages. + self.messages: Deque[Data] = collections.deque() + self._pop_message_waiter: asyncio.Future[None] | None = None + self._put_message_waiter: asyncio.Future[None] | None = None + + # Protect sending fragmented messages. + self._fragmented_message_waiter: asyncio.Future[None] | None = None + + # Mapping of ping IDs to pong waiters, in chronological order. + self.pings: dict[bytes, tuple[asyncio.Future[float], float]] = {} + + self.latency: float = 0 + """ + Latency of the connection, in seconds. + + Latency is defined as the round-trip time of the connection. It is + measured by sending a Ping frame and waiting for a matching Pong frame. + Before the first measurement, :attr:`latency` is ``0``. + + By default, websockets enables a :ref:`keepalive ` mechanism + that sends Ping frames automatically at regular intervals. You can also + send Ping frames and measure latency with :meth:`ping`. + """ + + # Task running the data transfer. + self.transfer_data_task: asyncio.Task[None] + + # Exception that occurred during data transfer, if any. + self.transfer_data_exc: BaseException | None = None + + # Task sending keepalive pings. + self.keepalive_ping_task: asyncio.Task[None] + + # Task closing the TCP connection. + self.close_connection_task: asyncio.Task[None] + + # Copied from asyncio.FlowControlMixin + async def _drain_helper(self) -> None: # pragma: no cover + if self.connection_lost_waiter.done(): + raise ConnectionResetError("Connection lost") + if not self._paused: + return + waiter = self._drain_waiter + assert waiter is None or waiter.cancelled() + waiter = self.loop.create_future() + self._drain_waiter = waiter + await waiter + + # Copied from asyncio.StreamWriter + async def _drain(self) -> None: # pragma: no cover + if self.reader is not None: + exc = self.reader.exception() + if exc is not None: + raise exc + if self.transport is not None: + if self.transport.is_closing(): + # Yield to the event loop so connection_lost() may be + # called. Without this, _drain_helper() would return + # immediately, and code that calls + # write(...); yield from drain() + # in a loop would never call connection_lost(), so it + # would not see an error when the socket is closed. + await asyncio.sleep(0) + await self._drain_helper() + + def connection_open(self) -> None: + """ + Callback when the WebSocket opening handshake completes. + + Enter the OPEN state and start the data transfer phase. + + """ + # 4.1. The WebSocket Connection is Established. + assert self.state is State.CONNECTING + self.state = State.OPEN + if self.debug: + self.logger.debug("= connection is OPEN") + # Start the task that receives incoming WebSocket messages. + self.transfer_data_task = self.loop.create_task(self.transfer_data()) + # Start the task that sends pings at regular intervals. + self.keepalive_ping_task = self.loop.create_task(self.keepalive_ping()) + # Start the task that eventually closes the TCP connection. + self.close_connection_task = self.loop.create_task(self.close_connection()) + + @property + def host(self) -> str | None: + alternative = "remote_address" if self.is_client else "local_address" + warnings.warn(f"use {alternative}[0] instead of host", DeprecationWarning) + return self._host + + @property + def port(self) -> int | None: + alternative = "remote_address" if self.is_client else "local_address" + warnings.warn(f"use {alternative}[1] instead of port", DeprecationWarning) + return self._port + + @property + def secure(self) -> bool | None: + warnings.warn("don't use secure", DeprecationWarning) + return self._secure + + # Public API + + @property + def local_address(self) -> Any: + """ + Local address of the connection. + + For IPv4 connections, this is a ``(host, port)`` tuple. + + The format of the address depends on the address family; + see :meth:`~socket.socket.getsockname`. + + :obj:`None` if the TCP connection isn't established yet. + + """ + try: + transport = self.transport + except AttributeError: + return None + else: + return transport.get_extra_info("sockname") + + @property + def remote_address(self) -> Any: + """ + Remote address of the connection. + + For IPv4 connections, this is a ``(host, port)`` tuple. + + The format of the address depends on the address family; + see :meth:`~socket.socket.getpeername`. + + :obj:`None` if the TCP connection isn't established yet. + + """ + try: + transport = self.transport + except AttributeError: + return None + else: + return transport.get_extra_info("peername") + + @property + def open(self) -> bool: + """ + :obj:`True` when the connection is open; :obj:`False` otherwise. + + This attribute may be used to detect disconnections. However, this + approach is discouraged per the EAFP_ principle. Instead, you should + handle :exc:`~websockets.exceptions.ConnectionClosed` exceptions. + + .. _EAFP: https://docs.python.org/3/glossary.html#term-eafp + + """ + return self.state is State.OPEN and not self.transfer_data_task.done() + + @property + def closed(self) -> bool: + """ + :obj:`True` when the connection is closed; :obj:`False` otherwise. + + Be aware that both :attr:`open` and :attr:`closed` are :obj:`False` + during the opening and closing sequences. + + """ + return self.state is State.CLOSED + + @property + def close_code(self) -> int | None: + """ + WebSocket close code, defined in `section 7.1.5 of RFC 6455`_. + + .. _section 7.1.5 of RFC 6455: + https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5 + + :obj:`None` if the connection isn't closed yet. + + """ + if self.state is not State.CLOSED: + return None + elif self.close_rcvd is None: + return CloseCode.ABNORMAL_CLOSURE + else: + return self.close_rcvd.code + + @property + def close_reason(self) -> str | None: + """ + WebSocket close reason, defined in `section 7.1.6 of RFC 6455`_. + + .. _section 7.1.6 of RFC 6455: + https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6 + + :obj:`None` if the connection isn't closed yet. + + """ + if self.state is not State.CLOSED: + return None + elif self.close_rcvd is None: + return "" + else: + return self.close_rcvd.reason + + async def __aiter__(self) -> AsyncIterator[Data]: + """ + Iterate on incoming messages. + + The iterator exits normally when the connection is closed with the close + code 1000 (OK) or 1001 (going away) or without a close code. + + It raises a :exc:`~websockets.exceptions.ConnectionClosedError` + exception when the connection is closed with any other code. + + """ + try: + while True: + yield await self.recv() + except ConnectionClosedOK: + return + + async def recv(self) -> Data: + """ + Receive the next message. + + When the connection is closed, :meth:`recv` raises + :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it raises + :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal + connection closure and + :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol + error or a network failure. This is how you detect the end of the + message stream. + + Canceling :meth:`recv` is safe. There's no risk of losing the next + message. The next invocation of :meth:`recv` will return it. + + This makes it possible to enforce a timeout by wrapping :meth:`recv` in + :func:`~asyncio.timeout` or :func:`~asyncio.wait_for`. + + Returns: + A string (:class:`str`) for a Text_ frame. A bytestring + (:class:`bytes`) for a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + Raises: + ConnectionClosed: When the connection is closed. + RuntimeError: If two coroutines call :meth:`recv` concurrently. + + """ + if self._pop_message_waiter is not None: + raise RuntimeError( + "cannot call recv while another coroutine " + "is already waiting for the next message" + ) + + # Don't await self.ensure_open() here: + # - messages could be available in the queue even if the connection + # is closed; + # - messages could be received before the closing frame even if the + # connection is closing. + + # Wait until there's a message in the queue (if necessary) or the + # connection is closed. + while len(self.messages) <= 0: + pop_message_waiter: asyncio.Future[None] = self.loop.create_future() + self._pop_message_waiter = pop_message_waiter + try: + # If asyncio.wait() is canceled, it doesn't cancel + # pop_message_waiter and self.transfer_data_task. + await asyncio.wait( + [pop_message_waiter, self.transfer_data_task], + return_when=asyncio.FIRST_COMPLETED, + ) + finally: + self._pop_message_waiter = None + + # If asyncio.wait(...) exited because self.transfer_data_task + # completed before receiving a new message, raise a suitable + # exception (or return None if legacy_recv is enabled). + if not pop_message_waiter.done(): + if self.legacy_recv: + return None # type: ignore + else: + # Wait until the connection is closed to raise + # ConnectionClosed with the correct code and reason. + await self.ensure_open() + + # Pop a message from the queue. + message = self.messages.popleft() + + # Notify transfer_data(). + if self._put_message_waiter is not None: + self._put_message_waiter.set_result(None) + self._put_message_waiter = None + + return message + + async def send( + self, + message: DataLike | Iterable[DataLike] | AsyncIterable[DataLike], + ) -> None: + """ + Send a message. + + A string (:class:`str`) is sent as a Text_ frame. A bytestring or + bytes-like object (:class:`bytes`, :class:`bytearray`, or + :class:`memoryview`) is sent as a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + :meth:`send` also accepts an iterable or an asynchronous iterable of + strings, bytestrings, or bytes-like objects to enable fragmentation_. + Each item is treated as a message fragment and sent in its own frame. + All items must be of the same type, or else :meth:`send` will raise a + :exc:`TypeError` and the connection will be closed. + + .. _fragmentation: https://datatracker.ietf.org/doc/html/rfc6455#section-5.4 + + :meth:`send` rejects dict-like objects because this is often an error. + (If you want to send the keys of a dict-like object as fragments, call + its :meth:`~dict.keys` method and pass the result to :meth:`send`.) + + Canceling :meth:`send` is discouraged. Instead, you should close the + connection with :meth:`close`. Indeed, there are only two situations + where :meth:`send` may yield control to the event loop and then get + canceled; in both cases, :meth:`close` has the same effect and is + more clear: + + 1. The write buffer is full. If you don't want to wait until enough + data is sent, your only alternative is to close the connection. + :meth:`close` will likely time out then abort the TCP connection. + 2. ``message`` is an asynchronous iterator that yields control. + Stopping in the middle of a fragmented message will cause a + protocol error and the connection will be closed. + + When the connection is closed, :meth:`send` raises + :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it + raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal + connection closure and + :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol + error or a network failure. + + Args: + message: Message to send. + + Raises: + ConnectionClosed: When the connection is closed. + TypeError: If ``message`` doesn't have a supported type. + + """ + await self.ensure_open() + + # While sending a fragmented message, prevent sending other messages + # until all fragments are sent. + while self._fragmented_message_waiter is not None: + await asyncio.shield(self._fragmented_message_waiter) + + # Unfragmented message -- this case must be handled first because + # strings and bytes-like objects are iterable. + + if isinstance(message, (str, bytes, bytearray, memoryview)): + opcode, data = prepare_data(message) + await self.write_frame(True, opcode, data) + + # Catch a common mistake -- passing a dict to send(). + + elif isinstance(message, Mapping): + raise TypeError("data is a dict-like object") + + # Fragmented message -- regular iterator. + + elif isinstance(message, Iterable): + iter_message = iter(message) + try: + fragment = next(iter_message) + except StopIteration: + return + opcode, data = prepare_data(fragment) + + self._fragmented_message_waiter = self.loop.create_future() + try: + # First fragment. + await self.write_frame(False, opcode, data) + + # Other fragments. + for fragment in iter_message: + confirm_opcode, data = prepare_data(fragment) + if confirm_opcode != opcode: + raise TypeError("data contains inconsistent types") + await self.write_frame(False, OP_CONT, data) + + # Final fragment. + await self.write_frame(True, OP_CONT, b"") + + except (Exception, asyncio.CancelledError): + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + self.fail_connection(CloseCode.INTERNAL_ERROR) + raise + + finally: + self._fragmented_message_waiter.set_result(None) + self._fragmented_message_waiter = None + + # Fragmented message -- asynchronous iterator + + elif isinstance(message, AsyncIterable): + # Implement aiter_message = aiter(message) without aiter + # Work around https://github.com/python/mypy/issues/5738 + aiter_message = cast( + Callable[[AsyncIterable[DataLike]], AsyncIterator[DataLike]], + type(message).__aiter__, + )(message) + try: + # Implement fragment = anext(aiter_message) without anext + # Work around https://github.com/python/mypy/issues/5738 + fragment = await cast( + Callable[[AsyncIterator[DataLike]], Awaitable[DataLike]], + type(aiter_message).__anext__, + )(aiter_message) + except StopAsyncIteration: + return + opcode, data = prepare_data(fragment) + + self._fragmented_message_waiter = self.loop.create_future() + try: + # First fragment. + await self.write_frame(False, opcode, data) + + # Other fragments. + async for fragment in aiter_message: + confirm_opcode, data = prepare_data(fragment) + if confirm_opcode != opcode: + raise TypeError("data contains inconsistent types") + await self.write_frame(False, OP_CONT, data) + + # Final fragment. + await self.write_frame(True, OP_CONT, b"") + + except (Exception, asyncio.CancelledError): + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + self.fail_connection(CloseCode.INTERNAL_ERROR) + raise + + finally: + self._fragmented_message_waiter.set_result(None) + self._fragmented_message_waiter = None + + else: + raise TypeError("data must be str, bytes-like, or iterable") + + async def close( + self, + code: int = CloseCode.NORMAL_CLOSURE, + reason: str = "", + ) -> None: + """ + Perform the closing handshake. + + :meth:`close` waits for the other end to complete the handshake and + for the TCP connection to terminate. As a consequence, there's no need + to await :meth:`wait_closed` after :meth:`close`. + + :meth:`close` is idempotent: it doesn't do anything once the + connection is closed. + + Wrapping :func:`close` in :func:`~asyncio.create_task` is safe, given + that errors during connection termination aren't particularly useful. + + Canceling :meth:`close` is discouraged. If it takes too long, you can + set a shorter ``close_timeout``. If you don't want to wait, let the + Python process exit, then the OS will take care of closing the TCP + connection. + + Args: + code: WebSocket close code. + reason: WebSocket close reason. + + """ + try: + async with asyncio_timeout(self.close_timeout): + await self.write_close_frame(Close(code, reason)) + except asyncio.TimeoutError: + # If the close frame cannot be sent because the send buffers + # are full, the closing handshake won't complete anyway. + # Fail the connection to shut down faster. + self.fail_connection() + + # If no close frame is received within the timeout, asyncio_timeout() + # cancels the data transfer task and raises TimeoutError. + + # If close() is called multiple times concurrently and one of these + # calls hits the timeout, the data transfer task will be canceled. + # Other calls will receive a CancelledError here. + + try: + # If close() is canceled during the wait, self.transfer_data_task + # is canceled before the timeout elapses. + async with asyncio_timeout(self.close_timeout): + await self.transfer_data_task + except (asyncio.TimeoutError, asyncio.CancelledError): + pass + + # Wait for the close connection task to close the TCP connection. + await asyncio.shield(self.close_connection_task) + + async def wait_closed(self) -> None: + """ + Wait until the connection is closed. + + This coroutine is identical to the :attr:`closed` attribute, except it + can be awaited. + + This can make it easier to detect connection termination, regardless + of its cause, in tasks that interact with the WebSocket connection. + + """ + await asyncio.shield(self.connection_lost_waiter) + + async def ping(self, data: DataLike | None = None) -> Awaitable[float]: + """ + Send a Ping_. + + .. _Ping: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2 + + A ping may serve as a keepalive, as a check that the remote endpoint + received all messages up to this point, or to measure :attr:`latency`. + + Canceling :meth:`ping` is discouraged. If :meth:`ping` doesn't return + immediately, it means the write buffer is full. If you don't want to + wait, you should close the connection. + + Canceling the :class:`~asyncio.Future` returned by :meth:`ping` has no + effect. + + Args: + data: Payload of the ping. A string will be encoded to UTF-8. + If ``data`` is :obj:`None`, the payload is four random bytes. + + Returns: + A future that will be completed when the corresponding pong is + received. You can ignore it if you don't intend to wait. The result + of the future is the latency of the connection in seconds. + + :: + + pong_waiter = await ws.ping() + # only if you want to wait for the corresponding pong + latency = await pong_waiter + + Raises: + ConnectionClosed: When the connection is closed. + RuntimeError: If another ping was sent with the same data and + the corresponding pong wasn't received yet. + + """ + await self.ensure_open() + + if data is not None: + data = prepare_ctrl(data) + + # Protect against duplicates if a payload is explicitly set. + if data in self.pings: + raise RuntimeError("already waiting for a pong with the same data") + + # Generate a unique random payload otherwise. + while data is None or data in self.pings: + data = struct.pack("!I", random.getrandbits(32)) + + pong_waiter = self.loop.create_future() + # Resolution of time.monotonic() may be too low on Windows. + ping_timestamp = time.perf_counter() + self.pings[data] = (pong_waiter, ping_timestamp) + + await self.write_frame(True, OP_PING, data) + + return asyncio.shield(pong_waiter) + + async def pong(self, data: DataLike = b"") -> None: + """ + Send a Pong_. + + .. _Pong: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3 + + An unsolicited pong may serve as a unidirectional heartbeat. + + Canceling :meth:`pong` is discouraged. If :meth:`pong` doesn't return + immediately, it means the write buffer is full. If you don't want to + wait, you should close the connection. + + Args: + data: Payload of the pong. A string will be encoded to UTF-8. + + Raises: + ConnectionClosed: When the connection is closed. + + """ + await self.ensure_open() + + data = prepare_ctrl(data) + + await self.write_frame(True, OP_PONG, data) + + # Private methods - no guarantees. + + def connection_closed_exc(self) -> ConnectionClosed: + exc: ConnectionClosed + if ( + self.close_rcvd is not None + and self.close_rcvd.code in OK_CLOSE_CODES + and self.close_sent is not None + and self.close_sent.code in OK_CLOSE_CODES + ): + exc = ConnectionClosedOK( + self.close_rcvd, + self.close_sent, + self.close_rcvd_then_sent, + ) + else: + exc = ConnectionClosedError( + self.close_rcvd, + self.close_sent, + self.close_rcvd_then_sent, + ) + # Chain to the exception that terminated data transfer, if any. + exc.__cause__ = self.transfer_data_exc + return exc + + async def ensure_open(self) -> None: + """ + Check that the WebSocket connection is open. + + Raise :exc:`~websockets.exceptions.ConnectionClosed` if it isn't. + + """ + # Handle cases from most common to least common for performance. + if self.state is State.OPEN: + # If self.transfer_data_task exited without a closing handshake, + # self.close_connection_task may be closing the connection, going + # straight from OPEN to CLOSED. + if self.transfer_data_task.done(): + await asyncio.shield(self.close_connection_task) + raise self.connection_closed_exc() + else: + return + + if self.state is State.CLOSED: + raise self.connection_closed_exc() + + if self.state is State.CLOSING: + # If we started the closing handshake, wait for its completion to + # get the proper close code and reason. self.close_connection_task + # will complete within 4 or 5 * close_timeout after close(). The + # CLOSING state also occurs when failing the connection. In that + # case self.close_connection_task will complete even faster. + await asyncio.shield(self.close_connection_task) + raise self.connection_closed_exc() + + # Control may only reach this point in buggy third-party subclasses. + assert self.state is State.CONNECTING + raise InvalidState("WebSocket connection isn't established yet") + + async def transfer_data(self) -> None: + """ + Read incoming messages and put them in a queue. + + This coroutine runs in a task until the closing handshake is started. + + """ + try: + while True: + message = await self.read_message() + + # Exit the loop when receiving a close frame. + if message is None: + break + + # Wait until there's room in the queue (if necessary). + if self.max_queue is not None: + while len(self.messages) >= self.max_queue: + self._put_message_waiter = self.loop.create_future() + try: + await asyncio.shield(self._put_message_waiter) + finally: + self._put_message_waiter = None + + # Put the message in the queue. + self.messages.append(message) + + # Notify recv(). + if self._pop_message_waiter is not None: + self._pop_message_waiter.set_result(None) + self._pop_message_waiter = None + + except asyncio.CancelledError as exc: + self.transfer_data_exc = exc + # If fail_connection() cancels this task, avoid logging the error + # twice and failing the connection again. + raise + + except ProtocolError as exc: + self.transfer_data_exc = exc + self.fail_connection(CloseCode.PROTOCOL_ERROR) + + except (ConnectionError, TimeoutError, EOFError, ssl.SSLError) as exc: + # Reading data with self.reader.readexactly may raise: + # - most subclasses of ConnectionError if the TCP connection + # breaks, is reset, or is aborted; + # - TimeoutError if the TCP connection times out; + # - IncompleteReadError, a subclass of EOFError, if fewer + # bytes are available than requested; + # - ssl.SSLError if the other side infringes the TLS protocol. + self.transfer_data_exc = exc + self.fail_connection(CloseCode.ABNORMAL_CLOSURE) + + except UnicodeDecodeError as exc: + self.transfer_data_exc = exc + self.fail_connection(CloseCode.INVALID_DATA) + + except PayloadTooBig as exc: + self.transfer_data_exc = exc + self.fail_connection(CloseCode.MESSAGE_TOO_BIG) + + except Exception as exc: + # This shouldn't happen often because exceptions expected under + # regular circumstances are handled above. If it does, consider + # catching and handling more exceptions. + self.logger.error("data transfer failed", exc_info=True) + + self.transfer_data_exc = exc + self.fail_connection(CloseCode.INTERNAL_ERROR) + + async def read_message(self) -> Data | None: + """ + Read a single message from the connection. + + Re-assemble data frames if the message is fragmented. + + Return :obj:`None` when the closing handshake is started. + + """ + frame = await self.read_data_frame(max_size=self.max_size) + + # A close frame was received. + if frame is None: + return None + + if frame.opcode == OP_TEXT: + text = True + elif frame.opcode == OP_BINARY: + text = False + else: # frame.opcode == OP_CONT + raise ProtocolError("unexpected opcode") + + # Shortcut for the common case - no fragmentation + if frame.fin: + if isinstance(frame.data, memoryview): + raise AssertionError("only compressed outgoing frames use memoryview") + return frame.data.decode() if text else bytes(frame.data) + + # 5.4. Fragmentation + fragments: list[DataLike] = [] + max_size = self.max_size + if text: + decoder_factory = codecs.getincrementaldecoder("utf-8") + decoder = decoder_factory(errors="strict") + if max_size is None: + + def append(frame: Frame) -> None: + nonlocal fragments + fragments.append(decoder.decode(frame.data, frame.fin)) + + else: + + def append(frame: Frame) -> None: + nonlocal fragments, max_size + fragments.append(decoder.decode(frame.data, frame.fin)) + assert isinstance(max_size, int) + max_size -= len(frame.data) + + else: + if max_size is None: + + def append(frame: Frame) -> None: + nonlocal fragments + fragments.append(frame.data) + + else: + + def append(frame: Frame) -> None: + nonlocal fragments, max_size + fragments.append(frame.data) + assert isinstance(max_size, int) + max_size -= len(frame.data) + + append(frame) + + while not frame.fin: + frame = await self.read_data_frame(max_size=max_size) + if frame is None: + raise ProtocolError("incomplete fragmented message") + if frame.opcode != OP_CONT: + raise ProtocolError("unexpected opcode") + append(frame) + + return ("" if text else b"").join(fragments) + + async def read_data_frame(self, max_size: int | None) -> Frame | None: + """ + Read a single data frame from the connection. + + Process control frames received before the next data frame. + + Return :obj:`None` if a close frame is encountered before any data frame. + + """ + # 6.2. Receiving Data + while True: + frame = await self.read_frame(max_size) + + # 5.5. Control Frames + if frame.opcode == OP_CLOSE: + # 7.1.5. The WebSocket Connection Close Code + # 7.1.6. The WebSocket Connection Close Reason + self.close_rcvd = Close.parse(frame.data) + if self.close_sent is not None: + self.close_rcvd_then_sent = False + try: + # Echo the original data instead of re-serializing it with + # Close.serialize() because that fails when the close frame + # is empty and Close.parse() synthesizes a 1005 close code. + await self.write_close_frame(self.close_rcvd, frame.data) + except ConnectionClosed: + # Connection closed before we could echo the close frame. + pass + return None + + elif frame.opcode == OP_PING: + # Answer pings, unless connection is CLOSING. + if self.state is State.OPEN: + try: + await self.pong(frame.data) + except ConnectionClosed: + # Connection closed while draining write buffer. + pass + + elif frame.opcode == OP_PONG: + if frame.data in self.pings: + pong_timestamp = time.perf_counter() + # Sending a pong for only the most recent ping is legal. + # Acknowledge all previous pings too in that case. + ping_id = None + ping_ids = [] + for ping_id, (pong_waiter, ping_timestamp) in self.pings.items(): + ping_ids.append(ping_id) + if not pong_waiter.done(): + pong_waiter.set_result(pong_timestamp - ping_timestamp) + if ping_id == frame.data: + self.latency = pong_timestamp - ping_timestamp + break + else: + raise AssertionError("solicited pong not found in pings") + # Remove acknowledged pings from self.pings. + for ping_id in ping_ids: + del self.pings[ping_id] + + # 5.6. Data Frames + else: + return frame + + async def read_frame(self, max_size: int | None) -> Frame: + """ + Read a single frame from the connection. + + """ + frame = await Frame.read( + self.reader.readexactly, + mask=not self.is_client, + max_size=max_size, + extensions=self.extensions, + ) + if self.debug: + self.logger.debug("< %s", frame) + return frame + + def write_frame_sync(self, fin: bool, opcode: int, data: BytesLike) -> None: + frame = Frame(fin, Opcode(opcode), data) + if self.debug: + self.logger.debug("> %s", frame) + frame.write( + self.transport.write, + mask=self.is_client, + extensions=self.extensions, + ) + + async def drain(self) -> None: + try: + # Handle flow control automatically. + await self._drain() + except ConnectionError: + # Terminate the connection if the socket died. + self.fail_connection() + # Wait until the connection is closed to raise ConnectionClosed + # with the correct code and reason. + await self.ensure_open() + + async def write_frame( + self, fin: bool, opcode: int, data: BytesLike, *, _state: int = State.OPEN + ) -> None: + # Defensive assertion for protocol compliance. + if self.state is not _state: # pragma: no cover + raise InvalidState( + f"Cannot write to a WebSocket in the {self.state.name} state" + ) + self.write_frame_sync(fin, opcode, data) + await self.drain() + + async def write_close_frame( + self, close: Close, data: BytesLike | None = None + ) -> None: + """ + Write a close frame if and only if the connection state is OPEN. + + This dedicated coroutine must be used for writing close frames to + ensure that at most one close frame is sent on a given connection. + + """ + # Test and set the connection state before sending the close frame to + # avoid sending two frames in case of concurrent calls. + if self.state is State.OPEN: + # 7.1.3. The WebSocket Closing Handshake is Started + self.state = State.CLOSING + if self.debug: + self.logger.debug("= connection is CLOSING") + + self.close_sent = close + if self.close_rcvd is not None: + self.close_rcvd_then_sent = True + if data is None: + data = close.serialize() + + # 7.1.2. Start the WebSocket Closing Handshake + await self.write_frame(True, OP_CLOSE, data, _state=State.CLOSING) + + async def keepalive_ping(self) -> None: + """ + Send a Ping frame and wait for a Pong frame at regular intervals. + + This coroutine exits when the connection terminates and one of the + following happens: + + - :meth:`ping` raises :exc:`ConnectionClosed`, or + - :meth:`close_connection` cancels :attr:`keepalive_ping_task`. + + """ + if self.ping_interval is None: + return + + try: + while True: + await asyncio.sleep(self.ping_interval) + + if self.debug: + self.logger.debug("% sending keepalive ping") + pong_waiter = await self.ping() + + if self.ping_timeout is not None: + try: + async with asyncio_timeout(self.ping_timeout): + # Raises CancelledError if the connection is closed, + # when close_connection() cancels keepalive_ping(). + # Raises ConnectionClosed if the connection is lost, + # when connection_lost() calls abort_pings(). + await pong_waiter + if self.debug: + self.logger.debug("% received keepalive pong") + except asyncio.TimeoutError: + if self.debug: + self.logger.debug("- timed out waiting for keepalive pong") + self.fail_connection( + CloseCode.INTERNAL_ERROR, + "keepalive ping timeout", + ) + break + + except ConnectionClosed: + pass + + except Exception: + self.logger.error("keepalive ping failed", exc_info=True) + + async def close_connection(self) -> None: + """ + 7.1.1. Close the WebSocket Connection + + When the opening handshake succeeds, :meth:`connection_open` starts + this coroutine in a task. It waits for the data transfer phase to + complete then it closes the TCP connection cleanly. + + When the opening handshake fails, :meth:`fail_connection` does the + same. There's no data transfer phase in that case. + + """ + try: + # Wait for the data transfer phase to complete. + if hasattr(self, "transfer_data_task"): + try: + await self.transfer_data_task + except asyncio.CancelledError: + pass + + # Cancel the keepalive ping task. + if hasattr(self, "keepalive_ping_task"): + self.keepalive_ping_task.cancel() + + # A client should wait for a TCP close from the server. + if self.is_client and hasattr(self, "transfer_data_task"): + if await self.wait_for_connection_lost(): + return + if self.debug: + self.logger.debug("- timed out waiting for TCP close") + + # Half-close the TCP connection if possible (when there's no TLS). + if self.transport.can_write_eof(): + if self.debug: + self.logger.debug("x half-closing TCP connection") + # write_eof() doesn't document which exceptions it raises. + # "[Errno 107] Transport endpoint is not connected" happens + # but it isn't completely clear under which circumstances. + # uvloop can raise RuntimeError here. + try: + self.transport.write_eof() + except (OSError, RuntimeError): # pragma: no cover + pass + + if await self.wait_for_connection_lost(): + return + if self.debug: + self.logger.debug("- timed out waiting for TCP close") + + finally: + # The try/finally ensures that the transport never remains open, + # even if this coroutine is canceled (for example). + await self.close_transport() + + async def close_transport(self) -> None: + """ + Close the TCP connection. + + """ + # If connection_lost() was called, the TCP connection is closed. + # However, if TLS is enabled, the transport still needs closing. + # Else asyncio complains: ResourceWarning: unclosed transport. + if self.connection_lost_waiter.done() and self.transport.is_closing(): + return + + # Close the TCP connection. Buffers are flushed asynchronously. + if self.debug: + self.logger.debug("x closing TCP connection") + self.transport.close() + + if await self.wait_for_connection_lost(): + return + if self.debug: + self.logger.debug("- timed out waiting for TCP close") + + # Abort the TCP connection. Buffers are discarded. + if self.debug: + self.logger.debug("x aborting TCP connection") + self.transport.abort() + + # connection_lost() is called quickly after aborting. + await self.wait_for_connection_lost() + + async def wait_for_connection_lost(self) -> bool: + """ + Wait until the TCP connection is closed or ``self.close_timeout`` elapses. + + Return :obj:`True` if the connection is closed and :obj:`False` + otherwise. + + """ + if not self.connection_lost_waiter.done(): + try: + async with asyncio_timeout(self.close_timeout): + await asyncio.shield(self.connection_lost_waiter) + except asyncio.TimeoutError: + pass + # Re-check self.connection_lost_waiter.done() synchronously because + # connection_lost() could run between the moment the timeout occurs + # and the moment this coroutine resumes running. + return self.connection_lost_waiter.done() + + def fail_connection( + self, + code: int = CloseCode.ABNORMAL_CLOSURE, + reason: str = "", + ) -> None: + """ + 7.1.7. Fail the WebSocket Connection + + This requires: + + 1. Stopping all processing of incoming data, which means canceling + :attr:`transfer_data_task`. The close code will be 1006 unless a + close frame was received earlier. + + 2. Sending a close frame with an appropriate code if the opening + handshake succeeded and the other side is likely to process it. + + 3. Closing the connection. :meth:`close_connection` takes care of + this once :attr:`transfer_data_task` exits after being canceled. + + (The specification describes these steps in the opposite order.) + + """ + if self.debug: + self.logger.debug("! failing connection with code %d", code) + + # Cancel transfer_data_task if the opening handshake succeeded. + # cancel() is idempotent and ignored if the task is done already. + if hasattr(self, "transfer_data_task"): + self.transfer_data_task.cancel() + + # Send a close frame when the state is OPEN (a close frame was already + # sent if it's CLOSING), except when failing the connection because of + # an error reading from or writing to the network. + # Don't send a close frame if the connection is broken. + if code != CloseCode.ABNORMAL_CLOSURE and self.state is State.OPEN: + close = Close(code, reason) + + # Write the close frame without draining the write buffer. + + # Keeping fail_connection() synchronous guarantees it can't + # get stuck and simplifies the implementation of the callers. + # Not drainig the write buffer is acceptable in this context. + + # This duplicates a few lines of code from write_close_frame(). + + self.state = State.CLOSING + if self.debug: + self.logger.debug("= connection is CLOSING") + + # If self.close_rcvd was set, the connection state would be + # CLOSING. Therefore self.close_rcvd isn't set and we don't + # have to set self.close_rcvd_then_sent. + assert self.close_rcvd is None + self.close_sent = close + + self.write_frame_sync(True, OP_CLOSE, close.serialize()) + + # Start close_connection_task if the opening handshake didn't succeed. + if not hasattr(self, "close_connection_task"): + self.close_connection_task = self.loop.create_task(self.close_connection()) + + def abort_pings(self) -> None: + """ + Raise ConnectionClosed in pending keepalive pings. + + They'll never receive a pong once the connection is closed. + + """ + assert self.state is State.CLOSED + exc = self.connection_closed_exc() + + for pong_waiter, _ping_timestamp in self.pings.values(): + pong_waiter.set_exception(exc) + # If the exception is never retrieved, it will be logged when ping + # is garbage-collected. This is confusing for users. + # Given that ping is done (with an exception), canceling it does + # nothing, but it prevents logging the exception. + pong_waiter.cancel() + + # asyncio.Protocol methods + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + """ + Configure write buffer limits. + + The high-water limit is defined by ``self.write_limit``. + + The low-water limit currently defaults to ``self.write_limit // 4`` in + :meth:`~asyncio.WriteTransport.set_write_buffer_limits`, which should + be all right for reasonable use cases of this library. + + This is the earliest point where we can get hold of the transport, + which means it's the best point for configuring it. + + """ + transport = cast(asyncio.Transport, transport) + transport.set_write_buffer_limits(self.write_limit) + self.transport = transport + + # Copied from asyncio.StreamReaderProtocol + self.reader.set_transport(transport) + + def connection_lost(self, exc: Exception | None) -> None: + """ + 7.1.4. The WebSocket Connection is Closed. + + """ + self.state = State.CLOSED + if self.debug: + self.logger.debug("= connection is CLOSED") + + self.abort_pings() + + # If self.connection_lost_waiter isn't pending, that's a bug, because: + # - it's set only here in connection_lost() which is called only once; + # - it must never be canceled. + self.connection_lost_waiter.set_result(None) + + if True: # pragma: no cover + # Copied from asyncio.StreamReaderProtocol + if self.reader is not None: + if exc is None: + self.reader.feed_eof() + else: + self.reader.set_exception(exc) + + # Copied from asyncio.FlowControlMixin + # Wake up the writer if currently paused. + if not self._paused: + return + waiter = self._drain_waiter + if waiter is None: + return + self._drain_waiter = None + if waiter.done(): + return + if exc is None: + waiter.set_result(None) + else: + waiter.set_exception(exc) + + def pause_writing(self) -> None: # pragma: no cover + assert not self._paused + self._paused = True + + def resume_writing(self) -> None: # pragma: no cover + assert self._paused + self._paused = False + + waiter = self._drain_waiter + if waiter is not None: + self._drain_waiter = None + if not waiter.done(): + waiter.set_result(None) + + def data_received(self, data: bytes) -> None: + self.reader.feed_data(data) + + def eof_received(self) -> None: + """ + Close the transport after receiving EOF. + + The WebSocket protocol has its own closing handshake: endpoints close + the TCP or TLS connection after sending and receiving a close frame. + + As a consequence, they never need to write after receiving EOF, so + there's no reason to keep the transport open by returning :obj:`True`. + + Besides, that doesn't work on TLS connections. + + """ + self.reader.feed_eof() + + +# broadcast() is defined in the protocol module even though it's primarily +# used by servers and documented in the server module because it works with +# client connections too and because it's easier to test together with the +# WebSocketCommonProtocol class. + + +def broadcast( + websockets: Iterable[WebSocketCommonProtocol], + message: DataLike, + raise_exceptions: bool = False, +) -> None: + """ + Broadcast a message to several WebSocket connections. + + A string (:class:`str`) is sent as a Text_ frame. A bytestring or bytes-like + object (:class:`bytes`, :class:`bytearray`, or :class:`memoryview`) is sent + as a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + :func:`broadcast` pushes the message synchronously to all connections even + if their write buffers are overflowing. There's no backpressure. + + If you broadcast messages faster than a connection can handle them, messages + will pile up in its write buffer until the connection times out. Keep + ``ping_interval`` and ``ping_timeout`` low to prevent excessive memory usage + from slow connections. + + Unlike :meth:`~websockets.legacy.protocol.WebSocketCommonProtocol.send`, + :func:`broadcast` doesn't support sending fragmented messages. Indeed, + fragmentation is useful for sending large messages without buffering them in + memory, while :func:`broadcast` buffers one copy per connection as fast as + possible. + + :func:`broadcast` skips connections that aren't open in order to avoid + errors on connections where the closing handshake is in progress. + + :func:`broadcast` ignores failures to write the message on some connections. + It continues writing to other connections. On Python 3.11 and above, you may + set ``raise_exceptions`` to :obj:`True` to record failures and raise all + exceptions in a :pep:`654` :exc:`ExceptionGroup`. + + While :func:`broadcast` makes more sense for servers, it works identically + with clients, if you have a use case for opening connections to many servers + and broadcasting a message to them. + + Args: + websockets: WebSocket connections to which the message will be sent. + message: Message to send. + raise_exceptions: Whether to raise an exception in case of failures. + + Raises: + TypeError: If ``message`` doesn't have a supported type. + + """ + if not isinstance(message, (str, bytes, bytearray, memoryview)): + raise TypeError("data must be str or bytes-like") + + if raise_exceptions: + if sys.version_info[:2] < (3, 11): # pragma: no cover + raise ValueError("raise_exceptions requires at least Python 3.11") + exceptions = [] + + opcode, data = prepare_data(message) + + for websocket in websockets: + if websocket.state is not State.OPEN: + continue + + if websocket._fragmented_message_waiter is not None: + if raise_exceptions: + exception = RuntimeError("sending a fragmented message") + exceptions.append(exception) + else: + websocket.logger.warning( + "skipped broadcast: sending a fragmented message", + ) + continue + + try: + websocket.write_frame_sync(True, opcode, data) + except Exception as write_exception: + if raise_exceptions: + exception = RuntimeError("failed to write message") + exception.__cause__ = write_exception + exceptions.append(exception) + else: + websocket.logger.warning( + "skipped broadcast: failed to write message: %s", + traceback.format_exception_only(write_exception)[0].strip(), + ) + + if raise_exceptions and exceptions: + raise ExceptionGroup("skipped broadcast", exceptions) + + +# Pretend that broadcast is actually defined in the server module. +broadcast.__module__ = "websockets.legacy.server" diff --git a/.venv/lib/python3.14/site-packages/websockets/legacy/server.py b/.venv/lib/python3.14/site-packages/websockets/legacy/server.py new file mode 100644 index 0000000000000000000000000000000000000000..d2a69c716c34083020033b089a7d4156fbc1e93e --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/legacy/server.py @@ -0,0 +1,1191 @@ +from __future__ import annotations + +import asyncio +import email.utils +import functools +import http +import inspect +import logging +import socket +import warnings +from collections.abc import Awaitable, Generator, Iterable, Sequence +from types import TracebackType +from typing import Any, Callable, cast + +from ..asyncio.compatibility import asyncio_timeout +from ..datastructures import Headers, HeadersLike, MultipleValuesError +from ..exceptions import ( + InvalidHandshake, + InvalidHeader, + InvalidMessage, + InvalidOrigin, + InvalidUpgrade, + NegotiationError, +) +from ..extensions import Extension, ServerExtensionFactory +from ..extensions.permessage_deflate import enable_server_permessage_deflate +from ..headers import ( + build_extension, + parse_extension, + parse_subprotocol, + validate_subprotocols, +) +from ..http11 import SERVER +from ..protocol import State +from ..typing import ExtensionHeader, LoggerLike, Origin, StatusLike, Subprotocol +from .exceptions import AbortHandshake +from .handshake import build_response, check_request +from .http import read_request +from .protocol import WebSocketCommonProtocol, broadcast + + +__all__ = [ + "broadcast", + "serve", + "unix_serve", + "WebSocketServerProtocol", + "WebSocketServer", +] + + +HeadersLikeOrCallable = HeadersLike | Callable[[str, Headers], HeadersLike] + +HTTPResponse = tuple[StatusLike, HeadersLike, bytes] + + +class WebSocketServerProtocol(WebSocketCommonProtocol): + """ + WebSocket server connection. + + :class:`WebSocketServerProtocol` provides :meth:`recv` and :meth:`send` + coroutines for receiving and sending messages. + + It supports asynchronous iteration to receive messages:: + + async for message in websocket: + await process(message) + + The iterator exits normally when the connection is closed with close code + 1000 (OK) or 1001 (going away) or without a close code. It raises + a :exc:`~websockets.exceptions.ConnectionClosedError` when the connection + is closed with any other code. + + You may customize the opening handshake in a subclass by + overriding :meth:`process_request` or :meth:`select_subprotocol`. + + Args: + ws_server: WebSocket server that created this connection. + + See :func:`serve` for the documentation of ``ws_handler``, ``logger``, ``origins``, + ``extensions``, ``subprotocols``, ``extra_headers``, and ``server_header``. + + See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the + documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``, + ``max_size``, ``max_queue``, ``read_limit``, and ``write_limit``. + + """ + + is_client = False + side = "server" + + def __init__( + self, + # The version that accepts the path in the second argument is deprecated. + ws_handler: ( + Callable[[WebSocketServerProtocol], Awaitable[Any]] + | Callable[[WebSocketServerProtocol, str], Awaitable[Any]] + ), + ws_server: WebSocketServer, + *, + logger: LoggerLike | None = None, + origins: Sequence[Origin | None] | None = None, + extensions: Sequence[ServerExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + extra_headers: HeadersLikeOrCallable | None = None, + server_header: str | None = SERVER, + process_request: ( + Callable[[str, Headers], Awaitable[HTTPResponse | None]] | None + ) = None, + select_subprotocol: ( + Callable[[Sequence[Subprotocol], Sequence[Subprotocol]], Subprotocol] | None + ) = None, + open_timeout: float | None = 10, + **kwargs: Any, + ) -> None: + if logger is None: + logger = logging.getLogger("websockets.server") + super().__init__(logger=logger, **kwargs) + # For backwards compatibility with 6.0 or earlier. + if origins is not None and "" in origins: + warnings.warn("use None instead of '' in origins", DeprecationWarning) + origins = [None if origin == "" else origin for origin in origins] + # For backwards compatibility with 10.0 or earlier. Done here in + # addition to serve to trigger the deprecation warning on direct + # use of WebSocketServerProtocol. + self.ws_handler = remove_path_argument(ws_handler) + self.ws_server = ws_server + self.origins = origins + self.available_extensions = extensions + self.available_subprotocols = subprotocols + self.extra_headers = extra_headers + self.server_header = server_header + self._process_request = process_request + self._select_subprotocol = select_subprotocol + self.open_timeout = open_timeout + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + """ + Register connection and initialize a task to handle it. + + """ + super().connection_made(transport) + # Register the connection with the server before creating the handler + # task. Registering at the beginning of the handler coroutine would + # create a race condition between the creation of the task, which + # schedules its execution, and the moment the handler starts running. + self.ws_server.register(self) + self.handler_task = self.loop.create_task(self.handler()) + + async def handler(self) -> None: + """ + Handle the lifecycle of a WebSocket connection. + + Since this method doesn't have a caller able to handle exceptions, it + attempts to log relevant ones and guarantees that the TCP connection is + closed before exiting. + + """ + try: + try: + async with asyncio_timeout(self.open_timeout): + await self.handshake( + origins=self.origins, + available_extensions=self.available_extensions, + available_subprotocols=self.available_subprotocols, + extra_headers=self.extra_headers, + ) + except asyncio.TimeoutError: # pragma: no cover + raise + except ConnectionError: + raise + except Exception as exc: + if isinstance(exc, AbortHandshake): + status, headers, body = exc.status, exc.headers, exc.body + elif isinstance(exc, InvalidOrigin): + if self.debug: + self.logger.debug("! invalid origin", exc_info=True) + status, headers, body = ( + http.HTTPStatus.FORBIDDEN, + Headers(), + f"Failed to open a WebSocket connection: {exc}.\n".encode(), + ) + elif isinstance(exc, InvalidUpgrade): + if self.debug: + self.logger.debug("! invalid upgrade", exc_info=True) + status, headers, body = ( + http.HTTPStatus.UPGRADE_REQUIRED, + Headers([("Upgrade", "websocket")]), + ( + f"Failed to open a WebSocket connection: {exc}.\n" + f"\n" + f"You cannot access a WebSocket server directly " + f"with a browser. You need a WebSocket client.\n" + ).encode(), + ) + elif isinstance(exc, InvalidHandshake): + if self.debug: + self.logger.debug("! invalid handshake", exc_info=True) + exc_chain = cast(BaseException, exc) + exc_str = f"{exc_chain}" + while exc_chain.__cause__ is not None: + exc_chain = exc_chain.__cause__ + exc_str += f"; {exc_chain}" + status, headers, body = ( + http.HTTPStatus.BAD_REQUEST, + Headers(), + f"Failed to open a WebSocket connection: {exc_str}.\n".encode(), + ) + else: + self.logger.error("opening handshake failed", exc_info=True) + status, headers, body = ( + http.HTTPStatus.INTERNAL_SERVER_ERROR, + Headers(), + ( + b"Failed to open a WebSocket connection.\n" + b"See server log for more information.\n" + ), + ) + + headers.setdefault("Date", email.utils.formatdate(usegmt=True)) + if self.server_header: + headers.setdefault("Server", self.server_header) + + headers.setdefault("Content-Length", str(len(body))) + headers.setdefault("Content-Type", "text/plain") + headers.setdefault("Connection", "close") + + self.write_http_response(status, headers, body) + self.logger.info( + "connection rejected (%d %s)", status.value, status.phrase + ) + await self.close_transport() + return + + try: + await self.ws_handler(self) + except Exception: + self.logger.error("connection handler failed", exc_info=True) + if not self.closed: + self.fail_connection(1011) + raise + + try: + await self.close() + except ConnectionError: + raise + except Exception: + self.logger.error("closing handshake failed", exc_info=True) + raise + + except Exception: + # Last-ditch attempt to avoid leaking connections on errors. + try: + self.transport.close() + except Exception: # pragma: no cover + pass + + finally: + # Unregister the connection with the server when the handler task + # terminates. Registration is tied to the lifecycle of the handler + # task because the server waits for tasks attached to registered + # connections before terminating. + self.ws_server.unregister(self) + self.logger.info("connection closed") + + async def read_http_request(self) -> tuple[str, Headers]: + """ + Read request line and headers from the HTTP request. + + If the request contains a body, it may be read from ``self.reader`` + after this coroutine returns. + + Raises: + InvalidMessage: If the HTTP message is malformed or isn't an + HTTP/1.1 GET request. + + """ + try: + path, headers = await read_request(self.reader) + except asyncio.CancelledError: # pragma: no cover + raise + except Exception as exc: + raise InvalidMessage("did not receive a valid HTTP request") from exc + + if self.debug: + self.logger.debug("< GET %s HTTP/1.1", path) + for key, value in headers.raw_items(): + self.logger.debug("< %s: %s", key, value) + + self.path = path + self.request_headers = headers + + return path, headers + + def write_http_response( + self, status: http.HTTPStatus, headers: Headers, body: bytes | None = None + ) -> None: + """ + Write status line and headers to the HTTP response. + + This coroutine is also able to write a response body. + + """ + self.response_headers = headers + + if self.debug: + self.logger.debug("> HTTP/1.1 %d %s", status.value, status.phrase) + for key, value in headers.raw_items(): + self.logger.debug("> %s: %s", key, value) + if body is not None: + self.logger.debug("> [body] (%d bytes)", len(body)) + + # Since the status line and headers only contain ASCII characters, + # we can keep this simple. + response = f"HTTP/1.1 {status.value} {status.phrase}\r\n" + response += str(headers) + + self.transport.write(response.encode()) + + if body is not None: + self.transport.write(body) + + async def process_request( + self, path: str, request_headers: Headers + ) -> HTTPResponse | None: + """ + Intercept the HTTP request and return an HTTP response if appropriate. + + You may override this method in a :class:`WebSocketServerProtocol` + subclass, for example: + + * to return an HTTP 200 OK response on a given path; then a load + balancer can use this path for a health check; + * to authenticate the request and return an HTTP 401 Unauthorized or an + HTTP 403 Forbidden when authentication fails. + + You may also override this method with the ``process_request`` + argument of :func:`serve` and :class:`WebSocketServerProtocol`. This + is equivalent, except ``process_request`` won't have access to the + protocol instance, so it can't store information for later use. + + :meth:`process_request` is expected to complete quickly. If it may run + for a long time, then it should await :meth:`wait_closed` and exit if + :meth:`wait_closed` completes, or else it could prevent the server + from shutting down. + + Args: + path: Request path, including optional query string. + request_headers: Request headers. + + Returns: + tuple[StatusLike, HeadersLike, bytes] | None: :obj:`None` to + continue the WebSocket handshake normally. + + An HTTP response, represented by a 3-uple of the response status, + headers, and body, to abort the WebSocket handshake and return + that HTTP response instead. + + """ + if self._process_request is not None: + response = self._process_request(path, request_headers) + if isinstance(response, Awaitable): + return await response + else: + # For backwards compatibility with 7.0. + warnings.warn( + "declare process_request as a coroutine", DeprecationWarning + ) + return response + return None + + @staticmethod + def process_origin( + headers: Headers, origins: Sequence[Origin | None] | None = None + ) -> Origin | None: + """ + Handle the Origin HTTP request header. + + Args: + headers: Request headers. + origins: Optional list of acceptable origins. + + Raises: + InvalidOrigin: If the origin isn't acceptable. + + """ + # "The user agent MUST NOT include more than one Origin header field" + # per https://datatracker.ietf.org/doc/html/rfc6454#section-7.3. + try: + origin = headers.get("Origin") + except MultipleValuesError as exc: + raise InvalidHeader("Origin", "multiple values") from exc + if origin is not None: + origin = cast(Origin, origin) + if origins is not None: + if origin not in origins: + raise InvalidOrigin(origin) + return origin + + @staticmethod + def process_extensions( + headers: Headers, + available_extensions: Sequence[ServerExtensionFactory] | None, + ) -> tuple[str | None, list[Extension]]: + """ + Handle the Sec-WebSocket-Extensions HTTP request header. + + Accept or reject each extension proposed in the client request. + Negotiate parameters for accepted extensions. + + Return the Sec-WebSocket-Extensions HTTP response header and the list + of accepted extensions. + + :rfc:`6455` leaves the rules up to the specification of each + :extension. + + To provide this level of flexibility, for each extension proposed by + the client, we check for a match with each extension available in the + server configuration. If no match is found, the extension is ignored. + + If several variants of the same extension are proposed by the client, + it may be accepted several times, which won't make sense in general. + Extensions must implement their own requirements. For this purpose, + the list of previously accepted extensions is provided. + + This process doesn't allow the server to reorder extensions. It can + only select a subset of the extensions proposed by the client. + + Other requirements, for example related to mandatory extensions or the + order of extensions, may be implemented by overriding this method. + + Args: + headers: Request headers. + extensions: Optional list of supported extensions. + + Raises: + InvalidHandshake: To abort the handshake with an HTTP 400 error. + + """ + response_header_value: str | None = None + + extension_headers: list[ExtensionHeader] = [] + accepted_extensions: list[Extension] = [] + + header_values = headers.get_all("Sec-WebSocket-Extensions") + + if header_values and available_extensions: + parsed_header_values: list[ExtensionHeader] = sum( + [parse_extension(header_value) for header_value in header_values], [] + ) + + for name, request_params in parsed_header_values: + for ext_factory in available_extensions: + # Skip non-matching extensions based on their name. + if ext_factory.name != name: + continue + + # Skip non-matching extensions based on their params. + try: + response_params, extension = ext_factory.process_request_params( + request_params, accepted_extensions + ) + except NegotiationError: + continue + + # Add matching extension to the final list. + extension_headers.append((name, response_params)) + accepted_extensions.append(extension) + + # Break out of the loop once we have a match. + break + + # If we didn't break from the loop, no extension in our list + # matched what the client sent. The extension is declined. + + # Serialize extension header. + if extension_headers: + response_header_value = build_extension(extension_headers) + + return response_header_value, accepted_extensions + + # Not @staticmethod because it calls self.select_subprotocol() + def process_subprotocol( + self, headers: Headers, available_subprotocols: Sequence[Subprotocol] | None + ) -> Subprotocol | None: + """ + Handle the Sec-WebSocket-Protocol HTTP request header. + + Return Sec-WebSocket-Protocol HTTP response header, which is the same + as the selected subprotocol. + + Args: + headers: Request headers. + available_subprotocols: Optional list of supported subprotocols. + + Raises: + InvalidHandshake: To abort the handshake with an HTTP 400 error. + + """ + subprotocol: Subprotocol | None = None + + header_values = headers.get_all("Sec-WebSocket-Protocol") + + if header_values and available_subprotocols: + parsed_header_values: list[Subprotocol] = sum( + [parse_subprotocol(header_value) for header_value in header_values], [] + ) + + subprotocol = self.select_subprotocol( + parsed_header_values, available_subprotocols + ) + + return subprotocol + + def select_subprotocol( + self, + client_subprotocols: Sequence[Subprotocol], + server_subprotocols: Sequence[Subprotocol], + ) -> Subprotocol | None: + """ + Pick a subprotocol among those supported by the client and the server. + + If several subprotocols are available, select the preferred subprotocol + by giving equal weight to the preferences of the client and the server. + + If no subprotocol is available, proceed without a subprotocol. + + You may provide a ``select_subprotocol`` argument to :func:`serve` or + :class:`WebSocketServerProtocol` to override this logic. For example, + you could reject the handshake if the client doesn't support a + particular subprotocol, rather than accept the handshake without that + subprotocol. + + Args: + client_subprotocols: List of subprotocols offered by the client. + server_subprotocols: List of subprotocols available on the server. + + Returns: + Selected subprotocol, if a common subprotocol was found. + + :obj:`None` to continue without a subprotocol. + + """ + if self._select_subprotocol is not None: + return self._select_subprotocol(client_subprotocols, server_subprotocols) + + subprotocols = set(client_subprotocols) & set(server_subprotocols) + if not subprotocols: + return None + return sorted( + subprotocols, + key=lambda p: client_subprotocols.index(p) + server_subprotocols.index(p), + )[0] + + async def handshake( + self, + origins: Sequence[Origin | None] | None = None, + available_extensions: Sequence[ServerExtensionFactory] | None = None, + available_subprotocols: Sequence[Subprotocol] | None = None, + extra_headers: HeadersLikeOrCallable | None = None, + ) -> str: + """ + Perform the server side of the opening handshake. + + Args: + origins: List of acceptable values of the Origin HTTP header; + include :obj:`None` if the lack of an origin is acceptable. + extensions: List of supported extensions, in order in which they + should be tried. + subprotocols: List of supported subprotocols, in order of + decreasing preference. + extra_headers: Arbitrary HTTP headers to add to the response when + the handshake succeeds. + + Returns: + path of the URI of the request. + + Raises: + InvalidHandshake: If the handshake fails. + + """ + path, request_headers = await self.read_http_request() + + # Hook for customizing request handling, for example checking + # authentication or treating some paths as plain HTTP endpoints. + early_response_awaitable = self.process_request(path, request_headers) + if isinstance(early_response_awaitable, Awaitable): + early_response = await early_response_awaitable + else: + # For backwards compatibility with 7.0. + warnings.warn("declare process_request as a coroutine", DeprecationWarning) + early_response = early_response_awaitable + + # The connection may drop while process_request is running. + if self.state is State.CLOSED: + # This subclass of ConnectionError is silently ignored in handler(). + raise BrokenPipeError("connection closed during opening handshake") + + # Change the response to a 503 error if the server is shutting down. + if not self.ws_server.is_serving(): + early_response = ( + http.HTTPStatus.SERVICE_UNAVAILABLE, + [], + b"Server is shutting down.\n", + ) + + if early_response is not None: + raise AbortHandshake(*early_response) + + key = check_request(request_headers) + + self.origin = self.process_origin(request_headers, origins) + + extensions_header, self.extensions = self.process_extensions( + request_headers, available_extensions + ) + + protocol_header = self.subprotocol = self.process_subprotocol( + request_headers, available_subprotocols + ) + + response_headers = Headers() + + build_response(response_headers, key) + + if extensions_header is not None: + response_headers["Sec-WebSocket-Extensions"] = extensions_header + + if protocol_header is not None: + response_headers["Sec-WebSocket-Protocol"] = protocol_header + + if callable(extra_headers): + extra_headers = extra_headers(path, self.request_headers) + if extra_headers is not None: + response_headers.update(extra_headers) + + response_headers.setdefault("Date", email.utils.formatdate(usegmt=True)) + if self.server_header is not None: + response_headers.setdefault("Server", self.server_header) + + self.write_http_response(http.HTTPStatus.SWITCHING_PROTOCOLS, response_headers) + + self.logger.info("connection open") + + self.connection_open() + + return path + + +class WebSocketServer: + """ + WebSocket server returned by :func:`serve`. + + This class mirrors the API of :class:`~asyncio.Server`. + + It keeps track of WebSocket connections in order to close them properly + when shutting down. + + Args: + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.server")``. + See the :doc:`logging guide <../../topics/logging>` for details. + + """ + + def __init__(self, logger: LoggerLike | None = None) -> None: + if logger is None: + logger = logging.getLogger("websockets.server") + self.logger = logger + + # Keep track of active connections. + self.websockets: set[WebSocketServerProtocol] = set() + + # Task responsible for closing the server and terminating connections. + self.close_task: asyncio.Task[None] | None = None + + # Completed when the server is closed and connections are terminated. + self.closed_waiter: asyncio.Future[None] + + def wrap(self, server: asyncio.base_events.Server) -> None: + """ + Attach to a given :class:`~asyncio.Server`. + + Since :meth:`~asyncio.loop.create_server` doesn't support injecting a + custom ``Server`` class, the easiest solution that doesn't rely on + private :mod:`asyncio` APIs is to: + + - instantiate a :class:`WebSocketServer` + - give the protocol factory a reference to that instance + - call :meth:`~asyncio.loop.create_server` with the factory + - attach the resulting :class:`~asyncio.Server` with this method + + """ + self.server = server + for sock in server.sockets: + if sock.family == socket.AF_INET: + name = "%s:%d" % sock.getsockname() + elif sock.family == socket.AF_INET6: + name = "[%s]:%d" % sock.getsockname()[:2] + elif sock.family == socket.AF_UNIX: + name = sock.getsockname() + # In the unlikely event that someone runs websockets over a + # protocol other than IP or Unix sockets, avoid crashing. + else: # pragma: no cover + name = str(sock.getsockname()) + self.logger.info("server listening on %s", name) + + # Initialized here because we need a reference to the event loop. + # This could be moved back to __init__ now that Python < 3.10 isn't + # supported anymore, but I'm not taking that risk in legacy code. + self.closed_waiter = server.get_loop().create_future() + + def register(self, protocol: WebSocketServerProtocol) -> None: + """ + Register a connection with this server. + + """ + self.websockets.add(protocol) + + def unregister(self, protocol: WebSocketServerProtocol) -> None: + """ + Unregister a connection with this server. + + """ + self.websockets.remove(protocol) + + def close(self, close_connections: bool = True) -> None: + """ + Close the server. + + * Close the underlying :class:`~asyncio.Server`. + * When ``close_connections`` is :obj:`True`, which is the default, + close existing connections. Specifically: + + * Reject opening WebSocket connections with an HTTP 503 (service + unavailable) error. This happens when the server accepted the TCP + connection but didn't complete the opening handshake before closing. + * Close open WebSocket connections with close code 1001 (going away). + + * Wait until all connection handlers terminate. + + :meth:`close` is idempotent. + + """ + if self.close_task is None: + self.close_task = self.get_loop().create_task( + self._close(close_connections) + ) + + async def _close(self, close_connections: bool) -> None: + """ + Implementation of :meth:`close`. + + This calls :meth:`~asyncio.Server.close` on the underlying + :class:`~asyncio.Server` object to stop accepting new connections and + then closes open connections with close code 1001. + + """ + self.logger.info("server closing") + + # Stop accepting new connections. + self.server.close() + + # Wait until all accepted connections reach connection_made() and call + # register(). See https://github.com/python/cpython/issues/79033 for + # details. This workaround can be removed when dropping Python < 3.11. + await asyncio.sleep(0) + + if close_connections: + # Close OPEN connections with close code 1001. After server.close(), + # handshake() closes OPENING connections with an HTTP 503 error. + close_tasks = [ + asyncio.create_task(websocket.close(1001)) + for websocket in self.websockets + if websocket.state is not State.CONNECTING + ] + # asyncio.wait doesn't accept an empty first argument. + if close_tasks: + await asyncio.wait(close_tasks) + + # Wait until all TCP connections are closed. + await self.server.wait_closed() + + # Wait until all connection handlers terminate. + # asyncio.wait doesn't accept an empty first argument. + if self.websockets: + await asyncio.wait( + [websocket.handler_task for websocket in self.websockets] + ) + + # Tell wait_closed() to return. + self.closed_waiter.set_result(None) + + self.logger.info("server closed") + + async def wait_closed(self) -> None: + """ + Wait until the server is closed. + + When :meth:`wait_closed` returns, all TCP connections are closed and + all connection handlers have returned. + + To ensure a fast shutdown, a connection handler should always be + awaiting at least one of: + + * :meth:`~WebSocketServerProtocol.recv`: when the connection is closed, + it raises :exc:`~websockets.exceptions.ConnectionClosedOK`; + * :meth:`~WebSocketServerProtocol.wait_closed`: when the connection is + closed, it returns. + + Then the connection handler is immediately notified of the shutdown; + it can clean up and exit. + + """ + await asyncio.shield(self.closed_waiter) + + def get_loop(self) -> asyncio.AbstractEventLoop: + """ + See :meth:`asyncio.Server.get_loop`. + + """ + return self.server.get_loop() + + def is_serving(self) -> bool: + """ + See :meth:`asyncio.Server.is_serving`. + + """ + return self.server.is_serving() + + async def start_serving(self) -> None: # pragma: no cover + """ + See :meth:`asyncio.Server.start_serving`. + + Typical use:: + + server = await serve(..., start_serving=False) + # perform additional setup here... + # ... then start the server + await server.start_serving() + + """ + await self.server.start_serving() + + async def serve_forever(self) -> None: # pragma: no cover + """ + See :meth:`asyncio.Server.serve_forever`. + + Typical use:: + + server = await serve(...) + # this coroutine doesn't return + # canceling it stops the server + await server.serve_forever() + + This is an alternative to using :func:`serve` as an asynchronous context + manager. Shutdown is triggered by canceling :meth:`serve_forever` + instead of exiting a :func:`serve` context. + + """ + await self.server.serve_forever() + + @property + def sockets(self) -> Iterable[socket.socket]: + """ + See :attr:`asyncio.Server.sockets`. + + """ + return self.server.sockets + + async def __aenter__(self) -> WebSocketServer: # pragma: no cover + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: # pragma: no cover + self.close() + await self.wait_closed() + + +class Serve: + """ + Start a WebSocket server listening on ``host`` and ``port``. + + Whenever a client connects, the server creates a + :class:`WebSocketServerProtocol`, performs the opening handshake, and + delegates to the connection handler, ``ws_handler``. + + The handler receives the :class:`WebSocketServerProtocol` and uses it to + send and receive messages. + + Once the handler completes, either normally or with an exception, the + server performs the closing handshake and closes the connection. + + Awaiting :func:`serve` yields a :class:`WebSocketServer`. This object + provides a :meth:`~WebSocketServer.close` method to shut down the server:: + + # set this future to exit the server + stop = asyncio.get_running_loop().create_future() + + server = await serve(...) + await stop + server.close() + await server.wait_closed() + + :func:`serve` can be used as an asynchronous context manager. Then, the + server is shut down automatically when exiting the context:: + + # set this future to exit the server + stop = asyncio.get_running_loop().create_future() + + async with serve(...): + await stop + + Args: + ws_handler: Connection handler. It receives the WebSocket connection, + which is a :class:`WebSocketServerProtocol`, in argument. + host: Network interfaces the server binds to. + See :meth:`~asyncio.loop.create_server` for details. + port: TCP port the server listens on. + See :meth:`~asyncio.loop.create_server` for details. + create_protocol: Factory for the :class:`asyncio.Protocol` managing + the connection. It defaults to :class:`WebSocketServerProtocol`. + Set it to a wrapper or a subclass to customize connection handling. + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.server")``. + See the :doc:`logging guide <../../topics/logging>` for details. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + origins: Acceptable values of the ``Origin`` header, for defending + against Cross-Site WebSocket Hijacking attacks. Include :obj:`None` + in the list if the lack of an origin is acceptable. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + extra_headers (HeadersLike | Callable[[str, Headers] | HeadersLike]): + Arbitrary HTTP headers to add to the response. This can be + a :data:`~websockets.datastructures.HeadersLike` or a callable + taking the request path and headers in arguments and returning + a :data:`~websockets.datastructures.HeadersLike`. + server_header: Value of the ``Server`` response header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. + Setting it to :obj:`None` removes the header. + process_request (Callable[[str, Headers], \ + Awaitable[tuple[StatusLike, HeadersLike, bytes] | None]] | None): + Intercept HTTP request before the opening handshake. + See :meth:`~WebSocketServerProtocol.process_request` for details. + select_subprotocol: Select a subprotocol supported by the client. + See :meth:`~WebSocketServerProtocol.select_subprotocol` for details. + open_timeout: Timeout for opening connections in seconds. + :obj:`None` disables the timeout. + + See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the + documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``, + ``max_size``, ``max_queue``, ``read_limit``, and ``write_limit``. + + Any other keyword arguments are passed the event loop's + :meth:`~asyncio.loop.create_server` method. + + For example: + + * You can set ``ssl`` to a :class:`~ssl.SSLContext` to enable TLS. + + * You can set ``sock`` to a :obj:`~socket.socket` that you created + outside of websockets. + + Returns: + WebSocket server. + + """ + + def __init__( + self, + # The version that accepts the path in the second argument is deprecated. + ws_handler: ( + Callable[[WebSocketServerProtocol], Awaitable[Any]] + | Callable[[WebSocketServerProtocol, str], Awaitable[Any]] + ), + host: str | Sequence[str] | None = None, + port: int | None = None, + *, + create_protocol: Callable[..., WebSocketServerProtocol] | None = None, + logger: LoggerLike | None = None, + compression: str | None = "deflate", + origins: Sequence[Origin | None] | None = None, + extensions: Sequence[ServerExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + extra_headers: HeadersLikeOrCallable | None = None, + server_header: str | None = SERVER, + process_request: ( + Callable[[str, Headers], Awaitable[HTTPResponse | None]] | None + ) = None, + select_subprotocol: ( + Callable[[Sequence[Subprotocol], Sequence[Subprotocol]], Subprotocol] | None + ) = None, + open_timeout: float | None = 10, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = None, + max_size: int | None = 2**20, + max_queue: int | None = 2**5, + read_limit: int = 2**16, + write_limit: int = 2**16, + **kwargs: Any, + ) -> None: + # Backwards compatibility: close_timeout used to be called timeout. + timeout: float | None = kwargs.pop("timeout", None) + if timeout is None: + timeout = 10 + else: + warnings.warn("rename timeout to close_timeout", DeprecationWarning) + # If both are specified, timeout is ignored. + if close_timeout is None: + close_timeout = timeout + + # Backwards compatibility: create_protocol used to be called klass. + klass: type[WebSocketServerProtocol] | None = kwargs.pop("klass", None) + if klass is None: + klass = WebSocketServerProtocol + else: + warnings.warn("rename klass to create_protocol", DeprecationWarning) + # If both are specified, klass is ignored. + if create_protocol is None: + create_protocol = klass + + # Backwards compatibility: recv() used to return None on closed connections + legacy_recv: bool = kwargs.pop("legacy_recv", False) + + # Backwards compatibility: the loop parameter used to be supported. + _loop: asyncio.AbstractEventLoop | None = kwargs.pop("loop", None) + if _loop is None: + loop = asyncio.get_event_loop() + else: + loop = _loop + warnings.warn("remove loop argument", DeprecationWarning) + + ws_server = WebSocketServer(logger=logger) + + secure = kwargs.get("ssl") is not None + + if compression == "deflate": + extensions = enable_server_permessage_deflate(extensions) + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + if subprotocols is not None: + validate_subprotocols(subprotocols) + + # Help mypy and avoid this error: "type[WebSocketServerProtocol] | + # Callable[..., WebSocketServerProtocol]" not callable [misc] + create_protocol = cast(Callable[..., WebSocketServerProtocol], create_protocol) + factory = functools.partial( + create_protocol, + # For backwards compatibility with 10.0 or earlier. Done here in + # addition to WebSocketServerProtocol to trigger the deprecation + # warning once per serve() call rather than once per connection. + remove_path_argument(ws_handler), + ws_server, + host=host, + port=port, + secure=secure, + open_timeout=open_timeout, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_size=max_size, + max_queue=max_queue, + read_limit=read_limit, + write_limit=write_limit, + loop=_loop, + legacy_recv=legacy_recv, + origins=origins, + extensions=extensions, + subprotocols=subprotocols, + extra_headers=extra_headers, + server_header=server_header, + process_request=process_request, + select_subprotocol=select_subprotocol, + logger=logger, + ) + + if kwargs.pop("unix", False): + path: str | None = kwargs.pop("path", None) + # unix_serve(path) must not specify host and port parameters. + assert host is None and port is None + create_server = functools.partial( + loop.create_unix_server, factory, path, **kwargs + ) + else: + create_server = functools.partial( + loop.create_server, factory, host, port, **kwargs + ) + + # This is a coroutine function. + self._create_server = create_server + self.ws_server = ws_server + + # async with serve(...) + + async def __aenter__(self) -> WebSocketServer: + return await self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + self.ws_server.close() + await self.ws_server.wait_closed() + + # await serve(...) + + def __await__(self) -> Generator[Any, None, WebSocketServer]: + # Create a suitable iterator by calling __await__ on a coroutine. + return self.__await_impl__().__await__() + + async def __await_impl__(self) -> WebSocketServer: + server = await self._create_server() + self.ws_server.wrap(server) + return self.ws_server + + # yield from serve(...) - remove when dropping Python < 3.11 + + __iter__ = __await__ + + +serve = Serve + + +def unix_serve( + # The version that accepts the path in the second argument is deprecated. + ws_handler: ( + Callable[[WebSocketServerProtocol], Awaitable[Any]] + | Callable[[WebSocketServerProtocol, str], Awaitable[Any]] + ), + path: str | None = None, + **kwargs: Any, +) -> Serve: + """ + Start a WebSocket server listening on a Unix socket. + + This function is identical to :func:`serve`, except the ``host`` and + ``port`` arguments are replaced by ``path``. It is only available on Unix. + + Unrecognized keyword arguments are passed the event loop's + :meth:`~asyncio.loop.create_unix_server` method. + + It's useful for deploying a server behind a reverse proxy such as nginx. + + Args: + path: File system path to the Unix socket. + + """ + return serve(ws_handler, path=path, unix=True, **kwargs) + + +def remove_path_argument( + ws_handler: ( + Callable[[WebSocketServerProtocol], Awaitable[Any]] + | Callable[[WebSocketServerProtocol, str], Awaitable[Any]] + ), +) -> Callable[[WebSocketServerProtocol], Awaitable[Any]]: + try: + inspect.signature(ws_handler).bind(None) + except TypeError: + try: + inspect.signature(ws_handler).bind(None, "") + except TypeError: # pragma: no cover + # ws_handler accepts neither one nor two arguments; leave it alone. + pass + else: + # ws_handler accepts two arguments; activate backwards compatibility. + warnings.warn("remove second argument of ws_handler", DeprecationWarning) + + async def _ws_handler(websocket: WebSocketServerProtocol) -> Any: + return await cast( + Callable[[WebSocketServerProtocol, str], Awaitable[Any]], + ws_handler, + )(websocket, websocket.path) + + return _ws_handler + + return cast( + Callable[[WebSocketServerProtocol], Awaitable[Any]], + ws_handler, + ) diff --git a/.venv/lib/python3.14/site-packages/websockets/protocol.py b/.venv/lib/python3.14/site-packages/websockets/protocol.py new file mode 100644 index 0000000000000000000000000000000000000000..66ebbe3ffb302225e625f027dc79d0590e85793d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/protocol.py @@ -0,0 +1,768 @@ +from __future__ import annotations + +import enum +import logging +import uuid +from collections.abc import Generator + +from .exceptions import ( + ConnectionClosed, + ConnectionClosedError, + ConnectionClosedOK, + InvalidState, + PayloadTooBig, + ProtocolError, +) +from .extensions import Extension +from .frames import ( + OK_CLOSE_CODES, + OP_BINARY, + OP_CLOSE, + OP_CONT, + OP_PING, + OP_PONG, + OP_TEXT, + Close, + CloseCode, + Frame, +) +from .http11 import Request, Response +from .streams import StreamReader +from .typing import BytesLike, LoggerLike, Origin, Subprotocol + + +__all__ = [ + "Protocol", + "Side", + "State", + "SEND_EOF", +] + +Event = Request | Response | Frame +"""Events that :meth:`~Protocol.events_received` may return.""" + + +class Side(enum.IntEnum): + """A WebSocket connection is either a server or a client.""" + + SERVER, CLIENT = range(2) + + +SERVER = Side.SERVER +CLIENT = Side.CLIENT + + +class State(enum.IntEnum): + """A WebSocket connection is in one of these four states.""" + + CONNECTING, OPEN, CLOSING, CLOSED = range(4) + + +CONNECTING = State.CONNECTING +OPEN = State.OPEN +CLOSING = State.CLOSING +CLOSED = State.CLOSED + + +SEND_EOF = b"" +"""Sentinel signaling that the TCP connection must be half-closed.""" + + +class Protocol: + """ + Sans-I/O implementation of a WebSocket connection. + + Args: + side: :attr:`~Side.CLIENT` or :attr:`~Side.SERVER`. + state: Initial state of the WebSocket connection. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. You may pass a ``(max_message_size, + max_fragment_size)`` tuple to set different limits for messages and + fragments when you expect long messages sent in short fragments. + logger: Logger for this connection; depending on ``side``, + defaults to ``logging.getLogger("websockets.client")`` + or ``logging.getLogger("websockets.server")``; + see the :doc:`logging guide <../../topics/logging>` for details. + + """ + + def __init__( + self, + side: Side, + *, + state: State = OPEN, + max_size: tuple[int | None, int | None] | int | None = 2**20, + logger: LoggerLike | None = None, + ) -> None: + # Unique identifier. For logs. + self.id: uuid.UUID = uuid.uuid4() + """Unique identifier of the connection. Useful in logs.""" + + # Logger or LoggerAdapter for this connection. + if logger is None: + logger = logging.getLogger(f"websockets.{side.name.lower()}") + self.logger: LoggerLike = logger + """Logger for this connection.""" + + # Track if DEBUG is enabled. Shortcut logging calls if it isn't. + self.debug = logger.isEnabledFor(logging.DEBUG) + + # Connection side. CLIENT or SERVER. + self.side = side + + # Connection state. Initially OPEN because subclasses handle CONNECTING. + self.state = state + + # Maximum size of incoming messages in bytes. + if isinstance(max_size, int) or max_size is None: + self.max_message_size, self.max_fragment_size = max_size, None + else: + self.max_message_size, self.max_fragment_size = max_size + + # Current size of incoming message in bytes. Only set while reading a + # fragmented message i.e. a data frames with the FIN bit not set. + self.current_size: int | None = None + + # True while sending a fragmented message i.e. a data frames with the + # FIN bit not set. + self.expect_continuation_frame = False + + # WebSocket protocol parameters. + self.origin: Origin | None = None + self.extensions: list[Extension] = [] + self.subprotocol: Subprotocol | None = None + + # Close code and reason, set when a close frame is sent or received. + self.close_rcvd: Close | None = None + self.close_sent: Close | None = None + self.close_rcvd_then_sent: bool | None = None + + # Track if an exception happened during the handshake. + self.handshake_exc: Exception | None = None + """ + Exception to raise if the opening handshake failed. + + :obj:`None` if the opening handshake succeeded. + + """ + + # Track if send_eof() was called. + self.eof_sent = False + + # Parser state. + self.reader = StreamReader() + self.events: list[Event] = [] + self.writes: list[bytes] = [] + self.parser = self.parse() + next(self.parser) # start coroutine + self.parser_exc: Exception | None = None + + @property + def state(self) -> State: + """ + State of the WebSocket connection. + + Defined in 4.1_, 4.2_, 7.1.3_, and 7.1.4_ of :rfc:`6455`. + + .. _4.1: https://datatracker.ietf.org/doc/html/rfc6455#section-4.1 + .. _4.2: https://datatracker.ietf.org/doc/html/rfc6455#section-4.2 + .. _7.1.3: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.3 + .. _7.1.4: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4 + + """ + return self._state + + @state.setter + def state(self, state: State) -> None: + if self.debug: + self.logger.debug("= connection is %s", state.name) + self._state = state + + @property + def close_code(self) -> int | None: + """ + WebSocket close code received from the remote endpoint. + + Defined in 7.1.5_ of :rfc:`6455`. + + .. _7.1.5: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5 + + :obj:`None` if the connection isn't closed yet. + + """ + if self.state is not CLOSED: + return None + elif self.close_rcvd is None: + return CloseCode.ABNORMAL_CLOSURE + else: + return self.close_rcvd.code + + @property + def close_reason(self) -> str | None: + """ + WebSocket close reason received from the remote endpoint. + + Defined in 7.1.6_ of :rfc:`6455`. + + .. _7.1.6: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6 + + :obj:`None` if the connection isn't closed yet. + + """ + if self.state is not CLOSED: + return None + elif self.close_rcvd is None: + return "" + else: + return self.close_rcvd.reason + + @property + def close_exc(self) -> ConnectionClosed: + """ + Exception to raise when trying to interact with a closed connection. + + Don't raise this exception while the connection :attr:`state` + is :attr:`~websockets.protocol.State.CLOSING`; wait until + it's :attr:`~websockets.protocol.State.CLOSED`. + + Indeed, the exception includes the close code and reason, which are + known only once the connection is closed. + + Raises: + AssertionError: If the connection isn't closed yet. + + """ + assert self.state is CLOSED, "connection isn't closed yet" + exc_type: type[ConnectionClosed] + if ( + self.close_rcvd is not None + and self.close_sent is not None + and self.close_rcvd.code in OK_CLOSE_CODES + and self.close_sent.code in OK_CLOSE_CODES + ): + exc_type = ConnectionClosedOK + else: + exc_type = ConnectionClosedError + exc: ConnectionClosed = exc_type( + self.close_rcvd, + self.close_sent, + self.close_rcvd_then_sent, + ) + # Chain to the exception raised in the parser, if any. + exc.__cause__ = self.parser_exc + return exc + + # Public methods for receiving data. + + def receive_data(self, data: bytes | bytearray) -> None: + """ + Receive data from the network. + + After calling this method: + + - You must call :meth:`data_to_send` and send this data to the network. + - You should call :meth:`events_received` and process resulting events. + + Raises: + EOFError: If :meth:`receive_eof` was called earlier. + + """ + self.reader.feed_data(data) + next(self.parser) + + def receive_eof(self) -> None: + """ + Receive the end of the data stream from the network. + + After calling this method: + + - You must call :meth:`data_to_send` and send this data to the network; + it will return ``[b""]``, signaling the end of the stream, or ``[]``. + - You aren't expected to call :meth:`events_received`; it won't return + any new events. + + :meth:`receive_eof` is idempotent. + + """ + if self.reader.eof: + return + self.reader.feed_eof() + next(self.parser) + + # Public methods for sending events. + + def send_continuation(self, data: BytesLike, fin: bool) -> None: + """ + Send a `Continuation frame`_. + + .. _Continuation frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + Parameters: + data: payload containing the same kind of data + as the initial frame. + fin: FIN bit; set it to :obj:`True` if this is the last frame + of a fragmented message and to :obj:`False` otherwise. + + Raises: + ProtocolError: If a fragmented message isn't in progress. + + """ + if not self.expect_continuation_frame: + raise ProtocolError("unexpected continuation frame") + if self._state is not OPEN: + raise InvalidState(f"connection is {self.state.name.lower()}") + self.expect_continuation_frame = not fin + self.send_frame(Frame(OP_CONT, data, fin)) + + def send_text(self, data: BytesLike, fin: bool = True) -> None: + """ + Send a `Text frame`_. + + .. _Text frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + Parameters: + data: payload containing text encoded with UTF-8. + fin: FIN bit; set it to :obj:`False` if this is the first frame of + a fragmented message. + + Raises: + ProtocolError: If a fragmented message is in progress. + + """ + if self.expect_continuation_frame: + raise ProtocolError("expected a continuation frame") + if self._state is not OPEN: + raise InvalidState(f"connection is {self.state.name.lower()}") + self.expect_continuation_frame = not fin + self.send_frame(Frame(OP_TEXT, data, fin)) + + def send_binary(self, data: BytesLike, fin: bool = True) -> None: + """ + Send a `Binary frame`_. + + .. _Binary frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + Parameters: + data: payload containing arbitrary binary data. + fin: FIN bit; set it to :obj:`False` if this is the first frame of + a fragmented message. + + Raises: + ProtocolError: If a fragmented message is in progress. + + """ + if self.expect_continuation_frame: + raise ProtocolError("expected a continuation frame") + if self._state is not OPEN: + raise InvalidState(f"connection is {self.state.name.lower()}") + self.expect_continuation_frame = not fin + self.send_frame(Frame(OP_BINARY, data, fin)) + + def send_close(self, code: CloseCode | int | None = None, reason: str = "") -> None: + """ + Send a `Close frame`_. + + .. _Close frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.1 + + Parameters: + code: close code. + reason: close reason. + + Raises: + ProtocolError: If the code isn't valid or if a reason is provided + without a code. + + """ + # While RFC 6455 doesn't rule out sending more than one close Frame, + # websockets is conservative in what it sends and doesn't allow that. + if self._state is not OPEN: + raise InvalidState(f"connection is {self.state.name.lower()}") + if code is None: + if reason != "": + raise ProtocolError("cannot send a reason without a code") + close = Close(CloseCode.NO_STATUS_RCVD, "") + data = b"" + else: + close = Close(code, reason) + data = close.serialize() + # 7.1.3. The WebSocket Closing Handshake is Started + self.send_frame(Frame(OP_CLOSE, data)) + # Since the state is OPEN, no close frame was received yet. + # As a consequence, self.close_rcvd_then_sent remains None. + assert self.close_rcvd is None + self.close_sent = close + self.state = CLOSING + + def send_ping(self, data: BytesLike) -> None: + """ + Send a `Ping frame`_. + + .. _Ping frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2 + + Parameters: + data: payload containing arbitrary binary data. + + """ + # RFC 6455 allows control frames after starting the closing handshake. + if self._state is not OPEN and self._state is not CLOSING: + raise InvalidState(f"connection is {self.state.name.lower()}") + self.send_frame(Frame(OP_PING, data)) + + def send_pong(self, data: BytesLike) -> None: + """ + Send a `Pong frame`_. + + .. _Pong frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3 + + Parameters: + data: payload containing arbitrary binary data. + + """ + # RFC 6455 allows control frames after starting the closing handshake. + if self._state is not OPEN and self._state is not CLOSING: + raise InvalidState(f"connection is {self.state.name.lower()}") + self.send_frame(Frame(OP_PONG, data)) + + def fail(self, code: CloseCode | int, reason: str = "") -> None: + """ + `Fail the WebSocket connection`_. + + .. _Fail the WebSocket connection: + https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.7 + + Parameters: + code: close code + reason: close reason + + Raises: + ProtocolError: If the code isn't valid. + """ + # 7.1.7. Fail the WebSocket Connection + + # Send a close frame when the state is OPEN (a close frame was already + # sent if it's CLOSING), except when failing the connection because + # of an error reading from or writing to the network. + if self.state is OPEN: + if code != CloseCode.ABNORMAL_CLOSURE: + close = Close(code, reason) + data = close.serialize() + self.send_frame(Frame(OP_CLOSE, data)) + self.close_sent = close + # If recv_messages() raised an exception upon receiving a close + # frame but before echoing it, then close_rcvd is not None even + # though the state is OPEN. This happens when the connection is + # closed while receiving a fragmented message. + if self.close_rcvd is not None: + self.close_rcvd_then_sent = True + self.state = CLOSING + + # When failing the connection, a server closes the TCP connection + # without waiting for the client to complete the handshake, while a + # client waits for the server to close the TCP connection, possibly + # after sending a close frame that the client will ignore. + if self.side is SERVER and not self.eof_sent: + self.send_eof() + + # 7.1.7. Fail the WebSocket Connection "An endpoint MUST NOT continue + # to attempt to process data(including a responding Close frame) from + # the remote endpoint after being instructed to _Fail the WebSocket + # Connection_." + self.parser = self.discard() + next(self.parser) # start coroutine + + # Public method for getting incoming events after receiving data. + + def events_received(self) -> list[Event]: + """ + Fetch events generated from data received from the network. + + Call this method immediately after any of the ``receive_*()`` methods. + + Process resulting events, likely by passing them to the application. + + Returns: + Events read from the connection. + """ + events, self.events = self.events, [] + return events + + # Public method for getting outgoing data after receiving data or sending events. + + def data_to_send(self) -> list[bytes]: + """ + Obtain data to send to the network. + + Call this method immediately after any of the ``receive_*()``, + ``send_*()``, or :meth:`fail` methods. + + Write resulting data to the connection. + + The empty bytestring :data:`~websockets.protocol.SEND_EOF` signals + the end of the data stream. When you receive it, half-close the TCP + connection. + + Returns: + Data to write to the connection. + + """ + writes, self.writes = self.writes, [] + return writes + + def close_expected(self) -> bool: + """ + Tell if the TCP connection is expected to close soon. + + Call this method immediately after any of the ``receive_*()``, + ``send_close()``, or :meth:`fail` methods. + + If it returns :obj:`True`, schedule closing the TCP connection after a + short timeout if the other side hasn't already closed it. + + Returns: + Whether the TCP connection is expected to close soon. + + """ + # During the opening handshake, when our state is CONNECTING, we expect + # a TCP close if and only if the hansdake fails. When it does, we start + # the TCP closing handshake by sending EOF with send_eof(). + + # Once the opening handshake completes successfully, we expect a TCP + # close if and only if we sent a close frame, meaning that our state + # progressed to CLOSING: + + # * Normal closure: once we send a close frame, we expect a TCP close: + # server waits for client to complete the TCP closing handshake; + # client waits for server to initiate the TCP closing handshake. + + # * Abnormal closure: we always send a close frame and the same logic + # applies, except on EOFError where we don't send a close frame + # because we already received the TCP close, so we don't expect it. + + # If our state is CLOSED, we already received a TCP close so we don't + # expect it anymore. + + # Micro-optimization: put the most common case first + if self.state is OPEN: + return False + if self.state is CLOSING: + return True + if self.state is CLOSED: + return False + assert self.state is CONNECTING + return self.eof_sent + + # Private methods for receiving data. + + def parse(self) -> Generator[None]: + """ + Parse incoming data into frames. + + :meth:`receive_data` and :meth:`receive_eof` run this generator + coroutine until it needs more data or reaches EOF. + + :meth:`parse` never raises an exception. Instead, it sets the + :attr:`parser_exc` and yields control. + + """ + try: + while True: + if (yield from self.reader.at_eof()): + if self.debug: + self.logger.debug("< EOF") + # If the WebSocket connection is closed cleanly, with a + # closing handhshake, recv_frame() substitutes parse() + # with discard(). This branch is reached only when the + # connection isn't closed cleanly. + raise EOFError("unexpected end of stream") + + max_size = None + + if self.max_message_size is not None: + if self.current_size is None: + max_size = self.max_message_size + else: + max_size = self.max_message_size - self.current_size + + if self.max_fragment_size is not None: + if max_size is None: + max_size = self.max_fragment_size + else: + max_size = min(max_size, self.max_fragment_size) + + # During a normal closure, execution ends here on the next + # iteration of the loop after receiving a close frame. At + # this point, recv_frame() replaced parse() by discard(). + frame = yield from Frame.parse( + self.reader.read_exact, + mask=self.side is SERVER, + max_size=max_size, + extensions=self.extensions, + ) + + if self.debug: + self.logger.debug("< %s", frame) + + self.recv_frame(frame) + + except ProtocolError as exc: + self.fail(CloseCode.PROTOCOL_ERROR, str(exc)) + self.parser_exc = exc + + except EOFError as exc: + self.fail(CloseCode.ABNORMAL_CLOSURE, str(exc)) + self.parser_exc = exc + + except UnicodeDecodeError as exc: + self.fail(CloseCode.INVALID_DATA, f"{exc.reason} at position {exc.start}") + self.parser_exc = exc + + except PayloadTooBig as exc: + exc.set_current_size(self.current_size) + self.fail(CloseCode.MESSAGE_TOO_BIG, str(exc)) + self.parser_exc = exc + + except Exception as exc: + self.logger.error("parser failed", exc_info=True) + # Don't include exception details, which may be security-sensitive. + self.fail(CloseCode.INTERNAL_ERROR) + self.parser_exc = exc + + # During an abnormal closure, execution ends here after catching an + # exception. At this point, fail() replaced parse() by discard(). + yield + raise AssertionError("parse() shouldn't step after error") + + def discard(self) -> Generator[None]: + """ + Discard incoming data. + + This coroutine replaces :meth:`parse`: + + - after receiving a close frame, during a normal closure (1.4); + - after sending a close frame, during an abnormal closure (7.1.7). + + """ + # After the opening handshake completes, the server closes the TCP + # connection in the same circumstances where discard() replaces parse(). + # The client closes it when it receives EOF from the server or times + # out. (The latter case cannot be handled in this Sans-I/O layer.) + assert (self.side is SERVER or self.state is CONNECTING) == (self.eof_sent) + while not (yield from self.reader.at_eof()): + self.reader.discard() + if self.debug: + self.logger.debug("< EOF") + # A server closes the TCP connection immediately, while a client + # waits for the server to close the TCP connection. + if self.side is CLIENT and self.state is not CONNECTING: + self.send_eof() + self.state = CLOSED + # If discard() completes normally, execution ends here. + yield + # Once the reader reaches EOF, its feed_data/eof() methods raise an + # error, so our receive_data/eof() methods don't step the generator. + raise AssertionError("discard() shouldn't step after EOF") + + def recv_frame(self, frame: Frame) -> None: + """ + Process an incoming frame. + + """ + if frame.opcode is OP_TEXT or frame.opcode is OP_BINARY: + if self.current_size is not None: + raise ProtocolError("expected a continuation frame") + if not frame.fin: + self.current_size = len(frame.data) + + elif frame.opcode is OP_CONT: + if self.current_size is None: + raise ProtocolError("unexpected continuation frame") + if frame.fin: + self.current_size = None + else: + self.current_size += len(frame.data) + + elif frame.opcode is OP_PING: + # 5.5.2. Ping: "Upon receipt of a Ping frame, an endpoint MUST + # send a Pong frame in response" + pong_frame = Frame(OP_PONG, frame.data) + self.send_frame(pong_frame) + + elif frame.opcode is OP_PONG: + # 5.5.3 Pong: "A response to an unsolicited Pong frame is not + # expected." + pass + + elif frame.opcode is OP_CLOSE: + # 7.1.5. The WebSocket Connection Close Code + # 7.1.6. The WebSocket Connection Close Reason + self.close_rcvd = Close.parse(frame.data) + if self.state is CLOSING: + assert self.close_sent is not None + self.close_rcvd_then_sent = False + + if self.current_size is not None: + raise ProtocolError("incomplete fragmented message") + + # 5.5.1 Close: "If an endpoint receives a Close frame and did + # not previously send a Close frame, the endpoint MUST send a + # Close frame in response. (When sending a Close frame in + # response, the endpoint typically echos the status code it + # received.)" + + if self.state is OPEN: + # Echo the original data instead of re-serializing it with + # Close.serialize() because that fails when the close frame + # is empty and Close.parse() synthesizes a 1005 close code. + # The rest is identical to send_close(). + self.send_frame(Frame(OP_CLOSE, frame.data)) + self.close_sent = self.close_rcvd + self.close_rcvd_then_sent = True + self.state = CLOSING + + # 7.1.2. Start the WebSocket Closing Handshake: "Once an + # endpoint has both sent and received a Close control frame, + # that endpoint SHOULD _Close the WebSocket Connection_" + + # A server closes the TCP connection immediately, while a client + # waits for the server to close the TCP connection. + if self.side is SERVER: + self.send_eof() + + # 1.4. Closing Handshake: "after receiving a control frame + # indicating the connection should be closed, a peer discards + # any further data received." + # RFC 6455 allows reading Ping and Pong frames after a Close frame. + # However, that doesn't seem useful; websockets doesn't support it. + self.parser = self.discard() + next(self.parser) # start coroutine + + else: + # This can't happen because Frame.parse() validates opcodes. + raise AssertionError(f"unexpected opcode: {frame.opcode:02x}") + + self.events.append(frame) + + # Private methods for sending events. + + def send_frame(self, frame: Frame) -> None: + if self.debug: + self.logger.debug("> %s", frame) + self.writes.append( + frame.serialize( + mask=self.side is CLIENT, + extensions=self.extensions, + ) + ) + + def send_eof(self) -> None: + assert not self.eof_sent + self.eof_sent = True + if self.debug: + self.logger.debug("> EOF") + self.writes.append(SEND_EOF) diff --git a/.venv/lib/python3.14/site-packages/websockets/proxy.py b/.venv/lib/python3.14/site-packages/websockets/proxy.py new file mode 100644 index 0000000000000000000000000000000000000000..a343b37bcda58da54d81002e4a2d3b9fe6f08f76 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/proxy.py @@ -0,0 +1,150 @@ +from __future__ import annotations + +import dataclasses +import urllib.parse +import urllib.request + +from .datastructures import Headers +from .exceptions import InvalidProxy +from .headers import build_authorization_basic, build_host +from .http11 import USER_AGENT +from .uri import DELIMS, WebSocketURI + + +__all__ = ["get_proxy", "parse_proxy", "Proxy"] + + +@dataclasses.dataclass +class Proxy: + """ + Proxy address. + + Attributes: + scheme: ``"socks5h"``, ``"socks5"``, ``"socks4a"``, ``"socks4"``, + ``"https"``, or ``"http"``. + host: Normalized to lower case. + port: Always set even if it's the default. + username: Available when the proxy address contains `User Information`_. + password: Available when the proxy address contains `User Information`_. + + .. _User Information: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 + + """ + + scheme: str + host: str + port: int + username: str | None = None + password: str | None = None + + @property + def user_info(self) -> tuple[str, str] | None: + if self.username is None: + return None + assert self.password is not None + return (self.username, self.password) + + +def parse_proxy(proxy: str) -> Proxy: + """ + Parse and validate a proxy. + + Args: + proxy: proxy. + + Returns: + Parsed proxy. + + Raises: + InvalidProxy: If ``proxy`` isn't a valid proxy. + + """ + parsed = urllib.parse.urlparse(proxy) + if parsed.scheme not in ["socks5h", "socks5", "socks4a", "socks4", "https", "http"]: + raise InvalidProxy(proxy, f"scheme {parsed.scheme} isn't supported") + if parsed.hostname is None: + raise InvalidProxy(proxy, "hostname isn't provided") + if parsed.path not in ["", "/"]: + raise InvalidProxy(proxy, "path is meaningless") + if parsed.query != "": + raise InvalidProxy(proxy, "query is meaningless") + if parsed.fragment != "": + raise InvalidProxy(proxy, "fragment is meaningless") + + scheme = parsed.scheme + host = parsed.hostname + port = parsed.port or (443 if parsed.scheme == "https" else 80) + username = parsed.username + password = parsed.password + # urllib.parse.urlparse accepts URLs with a username but without a + # password. This doesn't make sense for HTTP Basic Auth credentials. + if username is not None and password is None: + raise InvalidProxy(proxy, "username provided without password") + + try: + proxy.encode("ascii") + except UnicodeEncodeError: + # Input contains non-ASCII characters. + # It must be an IRI. Convert it to a URI. + host = host.encode("idna").decode() + if username is not None: + assert password is not None + username = urllib.parse.quote(username, safe=DELIMS) + password = urllib.parse.quote(password, safe=DELIMS) + + return Proxy(scheme, host, port, username, password) + + +def get_proxy(uri: WebSocketURI) -> str | None: + """ + Return the proxy to use for connecting to the given WebSocket URI, if any. + + """ + if urllib.request.proxy_bypass(f"{uri.host}:{uri.port}"): + return None + + # According to the _Proxy Usage_ section of RFC 6455, use a SOCKS5 proxy if + # available, else favor the proxy for HTTPS connections over the proxy for + # HTTP connections. + + # The priority of a proxy for WebSocket connections is unspecified. We give + # it the highest priority. This makes it easy to configure a specific proxy + # for websockets. + + # getproxies() may return SOCKS proxies as {"socks": "http://host:port"} or + # as {"https": "socks5h://host:port"} depending on whether they're declared + # in the operating system or in environment variables. + + proxies = urllib.request.getproxies() + if uri.secure: + schemes = ["wss", "socks", "https"] + else: + schemes = ["ws", "socks", "https", "http"] + + for scheme in schemes: + proxy = proxies.get(scheme) + if proxy is not None: + if scheme == "socks" and proxy.startswith("http://"): + proxy = "socks5h://" + proxy[7:] + return proxy + else: + return None + + +def prepare_connect_request( + proxy: Proxy, + ws_uri: WebSocketURI, + user_agent_header: str | None = USER_AGENT, +) -> bytes: + host = build_host(ws_uri.host, ws_uri.port, ws_uri.secure, always_include_port=True) + headers = Headers() + headers["Host"] = build_host(ws_uri.host, ws_uri.port, ws_uri.secure) + if user_agent_header is not None: + headers["User-Agent"] = user_agent_header + if proxy.username is not None: + assert proxy.password is not None # enforced by parse_proxy() + headers["Proxy-Authorization"] = build_authorization_basic( + proxy.username, proxy.password + ) + # We cannot use the Request class because it supports only GET requests. + return f"CONNECT {host} HTTP/1.1\r\n".encode() + headers.serialize() diff --git a/.venv/lib/python3.14/site-packages/websockets/py.typed b/.venv/lib/python3.14/site-packages/websockets/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/websockets/server.py b/.venv/lib/python3.14/site-packages/websockets/server.py new file mode 100644 index 0000000000000000000000000000000000000000..de2c63548f09e92c27bed63340e11e5e213ea137 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/server.py @@ -0,0 +1,589 @@ +from __future__ import annotations + +import base64 +import binascii +import email.utils +import http +import re +import warnings +from collections.abc import Generator, Sequence +from typing import Any, Callable, cast + +from .datastructures import Headers, MultipleValuesError +from .exceptions import ( + InvalidHandshake, + InvalidHeader, + InvalidHeaderValue, + InvalidMessage, + InvalidOrigin, + InvalidUpgrade, + NegotiationError, +) +from .extensions import Extension, ServerExtensionFactory +from .headers import ( + build_extension, + parse_connection, + parse_extension, + parse_subprotocol, + parse_upgrade, +) +from .http11 import Request, Response +from .imports import lazy_import +from .protocol import CONNECTING, OPEN, SERVER, Protocol, State +from .typing import ( + ConnectionOption, + ExtensionHeader, + LoggerLike, + Origin, + StatusLike, + Subprotocol, + UpgradeProtocol, +) +from .utils import accept_key + + +__all__ = ["ServerProtocol"] + + +class ServerProtocol(Protocol): + """ + Sans-I/O implementation of a WebSocket server connection. + + Args: + origins: Acceptable values of the ``Origin`` header. Values can be + :class:`str` to test for an exact match or regular expressions + compiled by :func:`re.compile` to test against a pattern. Include + :obj:`None` in the list if the lack of an origin is acceptable. + This is useful for defending against Cross-Site WebSocket + Hijacking attacks. + extensions: List of supported extensions, in order in which they + should be tried. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + select_subprotocol: Callback for selecting a subprotocol among + those supported by the client and the server. It has the same + signature as the :meth:`select_subprotocol` method, including a + :class:`ServerProtocol` instance as first argument. + state: Initial state of the WebSocket connection. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. You may pass a ``(max_message_size, + max_fragment_size)`` tuple to set different limits for messages and + fragments when you expect long messages sent in short fragments. + logger: Logger for this connection; + defaults to ``logging.getLogger("websockets.server")``; + see the :doc:`logging guide <../../topics/logging>` for details. + + """ + + def __init__( + self, + *, + origins: Sequence[Origin | re.Pattern[str] | None] | None = None, + extensions: Sequence[ServerExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + select_subprotocol: ( + Callable[ + [ServerProtocol, Sequence[Subprotocol]], + Subprotocol | None, + ] + | None + ) = None, + state: State = CONNECTING, + max_size: int | None | tuple[int | None, int | None] = 2**20, + logger: LoggerLike | None = None, + ) -> None: + super().__init__( + side=SERVER, + state=state, + max_size=max_size, + logger=logger, + ) + self.origins = origins + self.available_extensions = extensions + self.available_subprotocols = subprotocols + if select_subprotocol is not None: + # Bind select_subprotocol then shadow self.select_subprotocol. + # Use setattr to work around https://github.com/python/mypy/issues/2427. + setattr( + self, + "select_subprotocol", + select_subprotocol.__get__(self, self.__class__), + ) + + def accept(self, request: Request) -> Response: + """ + Create a handshake response to accept the connection. + + If the handshake request is valid and the handshake successful, + :meth:`accept` returns an HTTP response with status code 101. + + Else, it returns an HTTP response with another status code. This rejects + the connection, like :meth:`reject` would. + + You must send the handshake response with :meth:`send_response`. + + You may modify the response before sending it, typically by adding HTTP + headers. + + Args: + request: WebSocket handshake request received from the client. + + Returns: + WebSocket handshake response or HTTP response to send to the client. + + """ + try: + ( + accept_header, + extensions_header, + protocol_header, + ) = self.process_request(request) + except InvalidOrigin as exc: + request._exception = exc + self.handshake_exc = exc + if self.debug: + self.logger.debug("! invalid origin", exc_info=True) + return self.reject( + http.HTTPStatus.FORBIDDEN, + f"Failed to open a WebSocket connection: {exc}.\n", + ) + except InvalidUpgrade as exc: + request._exception = exc + self.handshake_exc = exc + if self.debug: + self.logger.debug("! invalid upgrade", exc_info=True) + response = self.reject( + http.HTTPStatus.UPGRADE_REQUIRED, + ( + f"Failed to open a WebSocket connection: {exc}.\n" + f"\n" + f"You cannot access a WebSocket server directly " + f"with a browser. You need a WebSocket client.\n" + ), + ) + response.headers["Upgrade"] = "websocket" + return response + except InvalidHandshake as exc: + request._exception = exc + self.handshake_exc = exc + if self.debug: + self.logger.debug("! invalid handshake", exc_info=True) + exc_chain = cast(BaseException, exc) + exc_str = f"{exc_chain}" + while exc_chain.__cause__ is not None: + exc_chain = exc_chain.__cause__ + exc_str += f"; {exc_chain}" + return self.reject( + http.HTTPStatus.BAD_REQUEST, + f"Failed to open a WebSocket connection: {exc_str}.\n", + ) + except Exception as exc: + # Handle exceptions raised by user-provided select_subprotocol and + # unexpected errors. + request._exception = exc + self.handshake_exc = exc + self.logger.error("opening handshake failed", exc_info=True) + return self.reject( + http.HTTPStatus.INTERNAL_SERVER_ERROR, + ( + "Failed to open a WebSocket connection.\n" + "See server log for more information.\n" + ), + ) + + headers = Headers() + headers["Date"] = email.utils.formatdate(usegmt=True) + headers["Upgrade"] = "websocket" + headers["Connection"] = "Upgrade" + headers["Sec-WebSocket-Accept"] = accept_header + if extensions_header is not None: + headers["Sec-WebSocket-Extensions"] = extensions_header + if protocol_header is not None: + headers["Sec-WebSocket-Protocol"] = protocol_header + return Response(101, "Switching Protocols", headers) + + def process_request( + self, + request: Request, + ) -> tuple[str, str | None, str | None]: + """ + Check a handshake request and negotiate extensions and subprotocol. + + This function doesn't verify that the request is an HTTP/1.1 or higher + GET request and doesn't check the ``Host`` header. These controls are + usually performed earlier in the HTTP request handling code. They're + the responsibility of the caller. + + Args: + request: WebSocket handshake request received from the client. + + Returns: + ``Sec-WebSocket-Accept``, ``Sec-WebSocket-Extensions``, and + ``Sec-WebSocket-Protocol`` headers for the handshake response. + + Raises: + InvalidHandshake: If the handshake request is invalid; + then the server must return 400 Bad Request error. + + """ + headers = request.headers + + connection: list[ConnectionOption] = sum( + [parse_connection(value) for value in headers.get_all("Connection")], [] + ) + if not any(value.lower() == "upgrade" for value in connection): + raise InvalidUpgrade( + "Connection", ", ".join(connection) if connection else None + ) + + upgrade: list[UpgradeProtocol] = sum( + [parse_upgrade(value) for value in headers.get_all("Upgrade")], [] + ) + # For compatibility with non-strict implementations, ignore case when + # checking the Upgrade header. The RFC always uses "websocket", except + # in section 11.2. (IANA registration) where it uses "WebSocket". + if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"): + raise InvalidUpgrade("Upgrade", ", ".join(upgrade) if upgrade else None) + + try: + key = headers["Sec-WebSocket-Key"] + except KeyError: + raise InvalidHeader("Sec-WebSocket-Key") from None + except MultipleValuesError: + raise InvalidHeader("Sec-WebSocket-Key", "multiple values") from None + try: + raw_key = base64.b64decode(key.encode(), validate=True) + except binascii.Error as exc: + raise InvalidHeaderValue("Sec-WebSocket-Key", key) from exc + if len(raw_key) != 16: + raise InvalidHeaderValue("Sec-WebSocket-Key", key) + accept_header = accept_key(key) + + try: + version = headers["Sec-WebSocket-Version"] + except KeyError: + raise InvalidHeader("Sec-WebSocket-Version") from None + except MultipleValuesError: + raise InvalidHeader("Sec-WebSocket-Version", "multiple values") from None + if version != "13": + raise InvalidHeaderValue("Sec-WebSocket-Version", version) + + self.origin = self.process_origin(headers) + extensions_header, self.extensions = self.process_extensions(headers) + protocol_header = self.subprotocol = self.process_subprotocol(headers) + + return (accept_header, extensions_header, protocol_header) + + def process_origin(self, headers: Headers) -> Origin | None: + """ + Handle the Origin HTTP request header. + + Args: + headers: WebSocket handshake request headers. + + Returns: + origin, if it is acceptable. + + Raises: + InvalidHandshake: If the Origin header is invalid. + InvalidOrigin: If the origin isn't acceptable. + + """ + # "The user agent MUST NOT include more than one Origin header field" + # per https://datatracker.ietf.org/doc/html/rfc6454#section-7.3. + try: + origin = headers.get("Origin") + except MultipleValuesError: + raise InvalidHeader("Origin", "multiple values") from None + if origin is not None: + origin = cast(Origin, origin) + if self.origins is not None: + for origin_or_regex in self.origins: + if origin_or_regex == origin or ( + isinstance(origin_or_regex, re.Pattern) + and origin is not None + and origin_or_regex.fullmatch(origin) is not None + ): + break + else: + raise InvalidOrigin(origin) + return origin + + def process_extensions( + self, + headers: Headers, + ) -> tuple[str | None, list[Extension]]: + """ + Handle the Sec-WebSocket-Extensions HTTP request header. + + Accept or reject each extension proposed in the client request. + Negotiate parameters for accepted extensions. + + Per :rfc:`6455`, negotiation rules are defined by the specification of + each extension. + + To provide this level of flexibility, for each extension proposed by + the client, we check for a match with each extension available in the + server configuration. If no match is found, the extension is ignored. + + If several variants of the same extension are proposed by the client, + it may be accepted several times, which won't make sense in general. + Extensions must implement their own requirements. For this purpose, + the list of previously accepted extensions is provided. + + This process doesn't allow the server to reorder extensions. It can + only select a subset of the extensions proposed by the client. + + Other requirements, for example related to mandatory extensions or the + order of extensions, may be implemented by overriding this method. + + Args: + headers: WebSocket handshake request headers. + + Returns: + ``Sec-WebSocket-Extensions`` HTTP response header and list of + accepted extensions. + + Raises: + InvalidHandshake: If the Sec-WebSocket-Extensions header is invalid. + + """ + response_header_value: str | None = None + + extension_headers: list[ExtensionHeader] = [] + accepted_extensions: list[Extension] = [] + + header_values = headers.get_all("Sec-WebSocket-Extensions") + + if header_values and self.available_extensions: + parsed_header_values: list[ExtensionHeader] = sum( + [parse_extension(header_value) for header_value in header_values], [] + ) + + for name, request_params in parsed_header_values: + for ext_factory in self.available_extensions: + # Skip non-matching extensions based on their name. + if ext_factory.name != name: + continue + + # Skip non-matching extensions based on their params. + try: + response_params, extension = ext_factory.process_request_params( + request_params, accepted_extensions + ) + except NegotiationError: + continue + + # Add matching extension to the final list. + extension_headers.append((name, response_params)) + accepted_extensions.append(extension) + + # Break out of the loop once we have a match. + break + + # If we didn't break from the loop, no extension in our list + # matched what the client sent. The extension is declined. + + # Serialize extension header. + if extension_headers: + response_header_value = build_extension(extension_headers) + + return response_header_value, accepted_extensions + + def process_subprotocol(self, headers: Headers) -> Subprotocol | None: + """ + Handle the Sec-WebSocket-Protocol HTTP request header. + + Args: + headers: WebSocket handshake request headers. + + Returns: + Subprotocol, if one was selected; this is also the value of the + ``Sec-WebSocket-Protocol`` response header. + + Raises: + InvalidHandshake: If the Sec-WebSocket-Subprotocol header is invalid. + + """ + subprotocols: Sequence[Subprotocol] = sum( + [ + parse_subprotocol(header_value) + for header_value in headers.get_all("Sec-WebSocket-Protocol") + ], + [], + ) + return self.select_subprotocol(subprotocols) + + def select_subprotocol( + self, + subprotocols: Sequence[Subprotocol], + ) -> Subprotocol | None: + """ + Pick a subprotocol among those offered by the client. + + If several subprotocols are supported by both the client and the server, + pick the first one in the list declared the server. + + If the server doesn't support any subprotocols, continue without a + subprotocol, regardless of what the client offers. + + If the server supports at least one subprotocol and the client doesn't + offer any, abort the handshake with an HTTP 400 error. + + You provide a ``select_subprotocol`` argument to :class:`ServerProtocol` + to override this logic. For example, you could accept the connection + even if client doesn't offer a subprotocol, rather than reject it. + + Here's how to negotiate the ``chat`` subprotocol if the client supports + it and continue without a subprotocol otherwise:: + + def select_subprotocol(protocol, subprotocols): + if "chat" in subprotocols: + return "chat" + + Args: + subprotocols: List of subprotocols offered by the client. + + Returns: + Selected subprotocol, if a common subprotocol was found. + + :obj:`None` to continue without a subprotocol. + + Raises: + NegotiationError: Custom implementations may raise this exception + to abort the handshake with an HTTP 400 error. + + """ + # Server doesn't offer any subprotocols. + if not self.available_subprotocols: # None or empty list + return None + + # Server offers at least one subprotocol but client doesn't offer any. + if not subprotocols: + raise NegotiationError("missing subprotocol") + + # Server and client both offer subprotocols. Look for a shared one. + proposed_subprotocols = set(subprotocols) + for subprotocol in self.available_subprotocols: + if subprotocol in proposed_subprotocols: + return subprotocol + + # No common subprotocol was found. + raise NegotiationError( + "invalid subprotocol; expected one of " + + ", ".join(self.available_subprotocols) + ) + + def reject(self, status: StatusLike, text: str) -> Response: + """ + Create a handshake response to reject the connection. + + A short plain text response is the best fallback when failing to + establish a WebSocket connection. + + You must send the handshake response with :meth:`send_response`. + + You may modify the response before sending it, for example by changing + HTTP headers. + + Args: + status: HTTP status code. + text: HTTP response body; it will be encoded to UTF-8. + + Returns: + HTTP response to send to the client. + + """ + # If status is an int instead of an HTTPStatus, fix it automatically. + status = http.HTTPStatus(status) + body = text.encode() + headers = Headers( + [ + ("Date", email.utils.formatdate(usegmt=True)), + ("Connection", "close"), + ("Content-Length", str(len(body))), + ("Content-Type", "text/plain; charset=utf-8"), + ] + ) + return Response(status.value, status.phrase, headers, body) + + def send_response(self, response: Response) -> None: + """ + Send a handshake response to the client. + + Args: + response: WebSocket handshake response event to send. + + """ + if self.debug: + code, phrase = response.status_code, response.reason_phrase + self.logger.debug("> HTTP/1.1 %d %s", code, phrase) + for key, value in response.headers.raw_items(): + self.logger.debug("> %s: %s", key, value) + if response.body: + self.logger.debug("> [body] (%d bytes)", len(response.body)) + + self.writes.append(response.serialize()) + + if response.status_code == 101: + assert self.state is CONNECTING + self.state = OPEN + self.logger.info("connection open") + + else: + self.logger.info( + "connection rejected (%d %s)", + response.status_code, + response.reason_phrase, + ) + + self.send_eof() + self.parser = self.discard() + next(self.parser) # start coroutine + + def parse(self) -> Generator[None]: + if self.state is CONNECTING: + try: + request = yield from Request.parse( + self.reader.read_line, + ) + except Exception as exc: + self.handshake_exc = InvalidMessage( + "did not receive a valid HTTP request" + ) + self.handshake_exc.__cause__ = exc + self.send_eof() + self.parser = self.discard() + next(self.parser) # start coroutine + yield + + if self.debug: + self.logger.debug("< GET %s HTTP/1.1", request.path) + for key, value in request.headers.raw_items(): + self.logger.debug("< %s: %s", key, value) + + self.events.append(request) + + yield from super().parse() + + +class ServerConnection(ServerProtocol): + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn( # deprecated in 11.0 - 2023-04-02 + "ServerConnection was renamed to ServerProtocol", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) + + +lazy_import( + globals(), + deprecated_aliases={ + # deprecated in 14.0 - 2024-11-09 + "WebSocketServer": ".legacy.server", + "WebSocketServerProtocol": ".legacy.server", + "broadcast": ".legacy.server", + "serve": ".legacy.server", + "unix_serve": ".legacy.server", + }, +) diff --git a/.venv/lib/python3.14/site-packages/websockets/speedups.c b/.venv/lib/python3.14/site-packages/websockets/speedups.c new file mode 100644 index 0000000000000000000000000000000000000000..f14ba3b9740a9f7ea901471c2f524f008c7c8ef5 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/speedups.c @@ -0,0 +1,229 @@ +/* C implementation of performance sensitive functions. */ + +#define PY_SSIZE_T_CLEAN +#include +#include /* uint8_t, uint32_t, uint64_t */ + +#if __ARM_NEON +#include +#elif __SSE2__ +#include +#endif + +static const Py_ssize_t MASK_LEN = 4; + +/* Similar to PyBytes_AsStringAndSize, but accepts more types */ + +static int +_PyBytesLike_AsStringAndSize(PyObject *obj, PyObject **tmp, char **buffer, Py_ssize_t *length) +{ + // This supports bytes, bytearrays, and memoryview objects, + // which are common data structures for handling byte streams. + // If *tmp isn't NULL, the caller gets a new reference. + if (PyBytes_Check(obj)) + { + *tmp = NULL; + *buffer = PyBytes_AS_STRING(obj); + *length = PyBytes_GET_SIZE(obj); + } + else if (PyByteArray_Check(obj)) + { + *tmp = NULL; + *buffer = PyByteArray_AS_STRING(obj); + *length = PyByteArray_GET_SIZE(obj); + } + else if (PyMemoryView_Check(obj)) + { + *tmp = PyMemoryView_GetContiguous(obj, PyBUF_READ, 'C'); + if (*tmp == NULL) + { + return -1; + } + Py_buffer *mv_buf; + mv_buf = PyMemoryView_GET_BUFFER(*tmp); + *buffer = mv_buf->buf; + *length = mv_buf->len; + } + else + { + PyErr_Format( + PyExc_TypeError, + "expected a bytes-like object, %.200s found", + Py_TYPE(obj)->tp_name); + return -1; + } + + return 0; +} + +/* C implementation of websockets.utils.apply_mask */ + +static PyObject * +apply_mask(PyObject *self, PyObject *args, PyObject *kwds) +{ + + // In order to support various bytes-like types, accept any Python object. + + static char *kwlist[] = {"data", "mask", NULL}; + PyObject *input_obj; + PyObject *mask_obj; + + // A pointer to a char * + length will be extracted from the data and mask + // arguments, possibly via a Py_buffer. + + PyObject *input_tmp = NULL; + char *input; + Py_ssize_t input_len; + PyObject *mask_tmp = NULL; + char *mask; + Py_ssize_t mask_len; + + // Initialize a PyBytesObject then get a pointer to the underlying char * + // in order to avoid an extra memory copy in PyBytes_FromStringAndSize. + + PyObject *result = NULL; + char *output; + + // Other variables. + + Py_ssize_t i = 0; + + // Parse inputs. + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "OO", kwlist, &input_obj, &mask_obj)) + { + goto exit; + } + + if (_PyBytesLike_AsStringAndSize(input_obj, &input_tmp, &input, &input_len) == -1) + { + goto exit; + } + + if (_PyBytesLike_AsStringAndSize(mask_obj, &mask_tmp, &mask, &mask_len) == -1) + { + goto exit; + } + + if (mask_len != MASK_LEN) + { + PyErr_SetString(PyExc_ValueError, "mask must contain 4 bytes"); + goto exit; + } + + // Create output. + + result = PyBytes_FromStringAndSize(NULL, input_len); + if (result == NULL) + { + goto exit; + } + + // Since we just created result, we don't need error checks. + output = PyBytes_AS_STRING(result); + + // Perform the masking operation. + + // Apparently GCC cannot figure out the following optimizations by itself. + + // We need a new scope for MSVC 2010 (non C99 friendly) + { +#if __ARM_NEON + + // With NEON support, XOR by blocks of 16 bytes = 128 bits. + + Py_ssize_t input_len_128 = input_len & ~15; + uint8x16_t mask_128 = vreinterpretq_u8_u32(vdupq_n_u32(*(uint32_t *)mask)); + + for (; i < input_len_128; i += 16) + { + uint8x16_t in_128 = vld1q_u8((uint8_t *)(input + i)); + uint8x16_t out_128 = veorq_u8(in_128, mask_128); + vst1q_u8((uint8_t *)(output + i), out_128); + } + +#elif __SSE2__ + + // With SSE2 support, XOR by blocks of 16 bytes = 128 bits. + + // Since we cannot control the 16-bytes alignment of input and output + // buffers, we rely on loadu/storeu rather than load/store. + + Py_ssize_t input_len_128 = input_len & ~15; + __m128i mask_128 = _mm_set1_epi32(*(uint32_t *)mask); + + for (; i < input_len_128; i += 16) + { + __m128i in_128 = _mm_loadu_si128((__m128i *)(input + i)); + __m128i out_128 = _mm_xor_si128(in_128, mask_128); + _mm_storeu_si128((__m128i *)(output + i), out_128); + } + +#else + + // Without SSE2 support, XOR by blocks of 8 bytes = 64 bits. + + // We assume the memory allocator aligns everything on 8 bytes boundaries. + + Py_ssize_t input_len_64 = input_len & ~7; + uint32_t mask_32 = *(uint32_t *)mask; + uint64_t mask_64 = ((uint64_t)mask_32 << 32) | (uint64_t)mask_32; + + for (; i < input_len_64; i += 8) + { + *(uint64_t *)(output + i) = *(uint64_t *)(input + i) ^ mask_64; + } + +#endif + } + + // XOR the remainder of the input byte by byte. + + for (; i < input_len; i++) + { + output[i] = input[i] ^ mask[i & (MASK_LEN - 1)]; + } + +exit: + Py_XDECREF(input_tmp); + Py_XDECREF(mask_tmp); + return result; + +} + +static PyMethodDef speedups_methods[] = { + { + "apply_mask", + (PyCFunction)apply_mask, + METH_VARARGS | METH_KEYWORDS, + "Apply masking to the data of a WebSocket message.", + }, + {NULL, NULL, 0, NULL}, /* Sentinel */ +}; + +static struct PyModuleDef speedups_module = { + PyModuleDef_HEAD_INIT, + "websocket.speedups", /* m_name */ + "C implementation of performance sensitive functions.", + /* m_doc */ + -1, /* m_size */ + speedups_methods, /* m_methods */ + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit_speedups(void) +{ + PyObject *m = PyModule_Create(&speedups_module); + if (m == NULL) { + return NULL; + } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; +} diff --git a/.venv/lib/python3.14/site-packages/websockets/speedups.cpython-314-darwin.so b/.venv/lib/python3.14/site-packages/websockets/speedups.cpython-314-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..920257ce31ae3145af54693044c0342d401705b3 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/speedups.cpython-314-darwin.so differ diff --git a/.venv/lib/python3.14/site-packages/websockets/speedups.pyi b/.venv/lib/python3.14/site-packages/websockets/speedups.pyi new file mode 100644 index 0000000000000000000000000000000000000000..ffd6c3e07e451e712b53df7b7cf36b61d3a0bf1d --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/speedups.pyi @@ -0,0 +1,3 @@ +from .typing import BytesLike + +def apply_mask(data: BytesLike, mask: bytes | bytearray) -> bytes: ... diff --git a/.venv/lib/python3.14/site-packages/websockets/streams.py b/.venv/lib/python3.14/site-packages/websockets/streams.py new file mode 100644 index 0000000000000000000000000000000000000000..309ce152ddb5d6407a29bc06e1edbe0a273e45e3 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/streams.py @@ -0,0 +1,151 @@ +from __future__ import annotations + +from collections.abc import Generator + + +class StreamReader: + """ + Generator-based stream reader. + + This class doesn't support concurrent calls to :meth:`read_line`, + :meth:`read_exact`, or :meth:`read_to_eof`. Make sure calls are + serialized. + + """ + + def __init__(self) -> None: + self.buffer = bytearray() + self.eof = False + + def read_line(self, m: int) -> Generator[None, None, bytearray]: + """ + Read a LF-terminated line from the stream. + + This is a generator-based coroutine. + + The return value includes the LF character. + + Args: + m: Maximum number bytes to read; this is a security limit. + + Raises: + EOFError: If the stream ends without a LF. + RuntimeError: If the stream ends in more than ``m`` bytes. + + """ + n = 0 # number of bytes to read + p = 0 # number of bytes without a newline + while True: + n = self.buffer.find(b"\n", p) + 1 + if n > 0: + break + p = len(self.buffer) + if p > m: + raise RuntimeError(f"read {p} bytes, expected no more than {m} bytes") + if self.eof: + raise EOFError(f"stream ends after {p} bytes, before end of line") + yield + if n > m: + raise RuntimeError(f"read {n} bytes, expected no more than {m} bytes") + r = self.buffer[:n] + del self.buffer[:n] + return r + + def read_exact(self, n: int) -> Generator[None, None, bytearray]: + """ + Read a given number of bytes from the stream. + + This is a generator-based coroutine. + + Args: + n: How many bytes to read. + + Raises: + EOFError: If the stream ends in less than ``n`` bytes. + + """ + assert n >= 0 + while len(self.buffer) < n: + if self.eof: + p = len(self.buffer) + raise EOFError(f"stream ends after {p} bytes, expected {n} bytes") + yield + r = self.buffer[:n] + del self.buffer[:n] + return r + + def read_to_eof(self, m: int) -> Generator[None, None, bytearray]: + """ + Read all bytes from the stream. + + This is a generator-based coroutine. + + Args: + m: Maximum number bytes to read; this is a security limit. + + Raises: + RuntimeError: If the stream ends in more than ``m`` bytes. + + """ + while not self.eof: + p = len(self.buffer) + if p > m: + raise RuntimeError(f"read {p} bytes, expected no more than {m} bytes") + yield + r = self.buffer[:] + del self.buffer[:] + return r + + def at_eof(self) -> Generator[None, None, bool]: + """ + Tell whether the stream has ended and all data was read. + + This is a generator-based coroutine. + + """ + while True: + if self.buffer: + return False + if self.eof: + return True + # When all data was read but the stream hasn't ended, we can't + # tell if until either feed_data() or feed_eof() is called. + yield + + def feed_data(self, data: bytes | bytearray) -> None: + """ + Write data to the stream. + + :meth:`feed_data` cannot be called after :meth:`feed_eof`. + + Args: + data: Data to write. + + Raises: + EOFError: If the stream has ended. + + """ + if self.eof: + raise EOFError("stream ended") + self.buffer += data + + def feed_eof(self) -> None: + """ + End the stream. + + :meth:`feed_eof` cannot be called more than once. + + Raises: + EOFError: If the stream has ended. + + """ + if self.eof: + raise EOFError("stream ended") + self.eof = True + + def discard(self) -> None: + """ + Discard all buffered data, but don't end the stream. + + """ + del self.buffer[:] diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/__init__.py b/.venv/lib/python3.14/site-packages/websockets/sync/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c67156246d8e8aa0e1664afb1c0d2dcf7cc37842 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/client.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/client.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cf0f58610a97969ec8d2ff1625646a7adce209c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/client.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/connection.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e74fd958aac143e7738cd7cb633ded2eea65a5a Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/connection.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/messages.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/messages.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5e692c1afd4c3d17155860cee6e83a7bb6cb3ba Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/messages.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/router.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/router.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fdd16c3d7ef24144290fe5c183a44ad7abfc784 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/router.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/server.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/server.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ae883ef91c239e128ba5b8106aa72c5acf0b951 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/server.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/utils.cpython-314.pyc b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bd37de2c22aff44e4567932c38b2c8262c518f2 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/websockets/sync/__pycache__/utils.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/client.py b/.venv/lib/python3.14/site-packages/websockets/sync/client.py new file mode 100644 index 0000000000000000000000000000000000000000..b3fff44eee51ca2961f40c0994d7078c0670a9da --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/sync/client.py @@ -0,0 +1,633 @@ +from __future__ import annotations + +import socket +import ssl as ssl_module +import threading +import warnings +from collections.abc import Sequence +from typing import Any, Callable, Literal, TypeVar, cast + +from ..client import ClientProtocol +from ..datastructures import HeadersLike +from ..exceptions import InvalidProxyMessage, InvalidProxyStatus, ProxyError +from ..extensions.base import ClientExtensionFactory +from ..extensions.permessage_deflate import enable_client_permessage_deflate +from ..headers import validate_subprotocols +from ..http11 import USER_AGENT, Response +from ..protocol import CONNECTING, Event +from ..proxy import Proxy, get_proxy, parse_proxy, prepare_connect_request +from ..streams import StreamReader +from ..typing import BytesLike, LoggerLike, Origin, Subprotocol +from ..uri import WebSocketURI, parse_uri +from .connection import Connection +from .utils import Deadline + + +__all__ = ["connect", "unix_connect", "ClientConnection"] + + +class ClientConnection(Connection): + """ + :mod:`threading` implementation of a WebSocket client connection. + + :class:`ClientConnection` provides :meth:`recv` and :meth:`send` methods for + receiving and sending messages. + + It supports iteration to receive messages:: + + for message in websocket: + process(message) + + The iterator exits normally when the connection is closed with code + 1000 (OK) or 1001 (going away) or without a close code. It raises a + :exc:`~websockets.exceptions.ConnectionClosedError` when the connection is + closed with any other code. + + The ``ping_interval``, ``ping_timeout``, ``close_timeout``, and + ``max_queue`` arguments have the same meaning as in :func:`connect`. + + Args: + socket: Socket connected to a WebSocket server. + protocol: Sans-I/O connection. + + """ + + def __init__( + self, + socket: socket.socket, + protocol: ClientProtocol, + *, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + max_queue: int | None | tuple[int | None, int | None] = 16, + ) -> None: + self.protocol: ClientProtocol + self.response_rcvd = threading.Event() + super().__init__( + socket, + protocol, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_queue=max_queue, + ) + + def handshake( + self, + additional_headers: HeadersLike | None = None, + user_agent_header: str | None = USER_AGENT, + timeout: float | None = None, + ) -> None: + """ + Perform the opening handshake. + + """ + with self.send_context(expected_state=CONNECTING): + self.request = self.protocol.connect() + if additional_headers is not None: + self.request.headers.update(additional_headers) + if user_agent_header is not None: + self.request.headers.setdefault("User-Agent", user_agent_header) + self.protocol.send_request(self.request) + + if not self.response_rcvd.wait(timeout): + raise TimeoutError("timed out while waiting for handshake response") + + # self.protocol.handshake_exc is set when the connection is lost before + # receiving a response, when the response cannot be parsed, or when the + # response fails the handshake. + + if self.protocol.handshake_exc is not None: + raise self.protocol.handshake_exc + + def process_event(self, event: Event) -> None: + """ + Process one incoming event. + + """ + # First event - handshake response. + if self.response is None: + assert isinstance(event, Response) + self.response = event + self.response_rcvd.set() + # Later events - frames. + else: + super().process_event(event) + + def recv_events(self) -> None: + """ + Read incoming data from the socket and process events. + + """ + try: + super().recv_events() + finally: + # If the connection is closed during the handshake, unblock it. + self.response_rcvd.set() + + +def connect( + uri: str, + *, + # TCP/TLS + sock: socket.socket | None = None, + ssl: ssl_module.SSLContext | None = None, + server_hostname: str | None = None, + # WebSocket + origin: Origin | None = None, + extensions: Sequence[ClientExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + compression: str | None = "deflate", + # HTTP + additional_headers: HeadersLike | None = None, + user_agent_header: str | None = USER_AGENT, + proxy: str | Literal[True] | None = True, + proxy_ssl: ssl_module.SSLContext | None = None, + proxy_server_hostname: str | None = None, + # Timeouts + open_timeout: float | None = 10, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + # Limits + max_size: int | None | tuple[int | None, int | None] = 2**20, + max_queue: int | None | tuple[int | None, int | None] = 16, + # Logging + logger: LoggerLike | None = None, + # Escape hatch for advanced customization + create_connection: type[ClientConnection] | None = None, + **kwargs: Any, +) -> ClientConnection: + """ + Connect to the WebSocket server at ``uri``. + + This function returns a :class:`ClientConnection` instance, which you can + use to send and receive messages. + + :func:`connect` may be used as a context manager:: + + from websockets.sync.client import connect + + with connect(...) as websocket: + ... + + The connection is closed automatically when exiting the context. + + Args: + uri: URI of the WebSocket server. + sock: Preexisting TCP socket. ``sock`` overrides the host and port + from ``uri``. You may call :func:`socket.create_connection` to + create a suitable TCP socket. + ssl: Configuration for enabling TLS on the connection. + server_hostname: Host name for the TLS handshake. ``server_hostname`` + overrides the host name from ``uri``. + origin: Value of the ``Origin`` header, for servers that require it. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + additional_headers (HeadersLike | None): Arbitrary HTTP headers to add + to the handshake request. + user_agent_header: Value of the ``User-Agent`` request header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. + Setting it to :obj:`None` removes the header. + proxy: If a proxy is configured, it is used by default. Set ``proxy`` + to :obj:`None` to disable the proxy or to the address of a proxy + to override the system configuration. See the :doc:`proxy docs + <../../topics/proxies>` for details. + proxy_ssl: Configuration for enabling TLS on the proxy connection. + proxy_server_hostname: Host name for the TLS handshake with the proxy. + ``proxy_server_hostname`` overrides the host name from ``proxy``. + open_timeout: Timeout for opening the connection in seconds. + :obj:`None` disables the timeout. + ping_interval: Interval between keepalive pings in seconds. + :obj:`None` disables keepalive. + ping_timeout: Timeout for keepalive pings in seconds. + :obj:`None` disables timeouts. + close_timeout: Timeout for closing the connection in seconds. + :obj:`None` disables the timeout. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. You may pass a ``(max_message_size, + max_fragment_size)`` tuple to set different limits for messages and + fragments when you expect long messages sent in short fragments. + max_queue: High-water mark of the buffer where frames are received. + It defaults to 16 frames. The low-water mark defaults to ``max_queue + // 4``. You may pass a ``(high, low)`` tuple to set the high-water + and low-water marks. If you want to disable flow control entirely, + you may set it to ``None``, although that's a bad idea. + logger: Logger for this client. + It defaults to ``logging.getLogger("websockets.client")``. + See the :doc:`logging guide <../../topics/logging>` for details. + create_connection: Factory for the :class:`ClientConnection` managing + the connection. Set it to a wrapper or a subclass to customize + connection handling. + + Any other keyword arguments are passed to :func:`~socket.create_connection`. + + Raises: + InvalidURI: If ``uri`` isn't a valid WebSocket URI. + OSError: If the TCP connection fails. + InvalidHandshake: If the opening handshake fails. + TimeoutError: If the opening handshake times out. + + """ + + # Process parameters + + # Backwards compatibility: ssl used to be called ssl_context. + if ssl is None and "ssl_context" in kwargs: + ssl = kwargs.pop("ssl_context") + warnings.warn( # deprecated in 13.0 - 2024-08-20 + "ssl_context was renamed to ssl", + DeprecationWarning, + ) + + ws_uri = parse_uri(uri) + if not ws_uri.secure and ssl is not None: + raise ValueError("ssl argument is incompatible with a ws:// URI") + + # Private APIs for unix_connect() + unix: bool = kwargs.pop("unix", False) + path: str | None = kwargs.pop("path", None) + + if unix: + if path is None and sock is None: + raise ValueError("missing path argument") + elif path is not None and sock is not None: + raise ValueError("path and sock arguments are incompatible") + + if subprotocols is not None: + validate_subprotocols(subprotocols) + + if compression == "deflate": + extensions = enable_client_permessage_deflate(extensions) + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + if unix: + proxy = None + if sock is not None: + proxy = None + if proxy is True: + proxy = get_proxy(ws_uri) + + # Calculate timeouts on the TCP, TLS, and WebSocket handshakes. + # The TCP and TLS timeouts must be set on the socket, then removed + # to avoid conflicting with the WebSocket timeout in handshake(). + deadline = Deadline(open_timeout) + + if create_connection is None: + create_connection = ClientConnection + + try: + # Connect socket + + if sock is None: + if unix: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.settimeout(deadline.timeout()) + assert path is not None # mypy cannot figure this out + sock.connect(path) + elif proxy is not None: + proxy_parsed = parse_proxy(proxy) + if proxy_parsed.scheme[:5] == "socks": + # Connect to the server through the proxy. + sock = connect_socks_proxy( + proxy_parsed, + ws_uri, + deadline, + # websockets is consistent with the socket module while + # python_socks is consistent across implementations. + local_addr=kwargs.pop("source_address", None), + ) + elif proxy_parsed.scheme[:4] == "http": + # Validate the proxy_ssl argument. + if proxy_parsed.scheme != "https" and proxy_ssl is not None: + raise ValueError( + "proxy_ssl argument is incompatible with an http:// proxy" + ) + # Connect to the server through the proxy. + sock = connect_http_proxy( + proxy_parsed, + ws_uri, + deadline, + user_agent_header=user_agent_header, + ssl=proxy_ssl, + server_hostname=proxy_server_hostname, + **kwargs, + ) + else: + raise AssertionError("unsupported proxy") + else: + kwargs.setdefault("timeout", deadline.timeout()) + sock = socket.create_connection( + (ws_uri.host, ws_uri.port), + **kwargs, + ) + sock.settimeout(None) + + # Disable Nagle algorithm + + if not unix: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) + + # Initialize TLS wrapper and perform TLS handshake + + if ws_uri.secure: + if ssl is None: + ssl = ssl_module.create_default_context() + if server_hostname is None: + server_hostname = ws_uri.host + sock.settimeout(deadline.timeout()) + if proxy_ssl is None: + sock = ssl.wrap_socket(sock, server_hostname=server_hostname) + else: + sock_2 = SSLSSLSocket(sock, ssl, server_hostname=server_hostname) + # Let's pretend that sock is a socket, even though it isn't. + sock = cast(socket.socket, sock_2) + sock.settimeout(None) + + # Initialize WebSocket protocol + + protocol = ClientProtocol( + ws_uri, + origin=origin, + extensions=extensions, + subprotocols=subprotocols, + max_size=max_size, + logger=logger, + ) + + # Initialize WebSocket connection + + connection = create_connection( + sock, + protocol, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_queue=max_queue, + ) + except Exception: + if sock is not None: + sock.close() + raise + + try: + connection.handshake( + additional_headers, + user_agent_header, + deadline.timeout(), + ) + except Exception: + connection.close_socket() + connection.recv_events_thread.join() + raise + + connection.start_keepalive() + return connection + + +def unix_connect( + path: str | None = None, + uri: str | None = None, + **kwargs: Any, +) -> ClientConnection: + """ + Connect to a WebSocket server listening on a Unix socket. + + This function accepts the same keyword arguments as :func:`connect`. + + It's only available on Unix. + + It's mainly useful for debugging servers listening on Unix sockets. + + Args: + path: File system path to the Unix socket. + uri: URI of the WebSocket server. ``uri`` defaults to + ``ws://localhost/`` or, when a ``ssl`` is provided, to + ``wss://localhost/``. + + """ + if uri is None: + # Backwards compatibility: ssl used to be called ssl_context. + if kwargs.get("ssl") is None and kwargs.get("ssl_context") is None: + uri = "ws://localhost/" + else: + uri = "wss://localhost/" + return connect(uri=uri, unix=True, path=path, **kwargs) + + +try: + from python_socks import ProxyType + from python_socks.sync import Proxy as SocksProxy + +except ImportError: + + def connect_socks_proxy( + proxy: Proxy, + ws_uri: WebSocketURI, + deadline: Deadline, + **kwargs: Any, + ) -> socket.socket: + raise ImportError("connecting through a SOCKS proxy requires python-socks") + +else: + SOCKS_PROXY_TYPES = { + "socks5h": ProxyType.SOCKS5, + "socks5": ProxyType.SOCKS5, + "socks4a": ProxyType.SOCKS4, + "socks4": ProxyType.SOCKS4, + } + + SOCKS_PROXY_RDNS = { + "socks5h": True, + "socks5": False, + "socks4a": True, + "socks4": False, + } + + def connect_socks_proxy( + proxy: Proxy, + ws_uri: WebSocketURI, + deadline: Deadline, + **kwargs: Any, + ) -> socket.socket: + """Connect via a SOCKS proxy and return the socket.""" + socks_proxy = SocksProxy( + SOCKS_PROXY_TYPES[proxy.scheme], + proxy.host, + proxy.port, + proxy.username, + proxy.password, + SOCKS_PROXY_RDNS[proxy.scheme], + ) + kwargs.setdefault("timeout", deadline.timeout()) + # connect() is documented to raise OSError and TimeoutError. + # Wrap other exceptions in ProxyError, a subclass of InvalidHandshake. + try: + return socks_proxy.connect(ws_uri.host, ws_uri.port, **kwargs) + except (OSError, TimeoutError, socket.timeout): + raise + except Exception as exc: + raise ProxyError("failed to connect to SOCKS proxy") from exc + + +def read_connect_response(sock: socket.socket, deadline: Deadline) -> Response: + reader = StreamReader() + parser = Response.parse( + reader.read_line, + reader.read_exact, + reader.read_to_eof, + proxy=True, + ) + try: + while True: + sock.settimeout(deadline.timeout()) + data = sock.recv(4096) + if data: + reader.feed_data(data) + else: + reader.feed_eof() + next(parser) + except StopIteration as exc: + assert isinstance(exc.value, Response) # help mypy + response = exc.value + if 200 <= response.status_code < 300: + return response + else: + raise InvalidProxyStatus(response) + except socket.timeout: + raise TimeoutError("timed out while connecting to HTTP proxy") + except Exception as exc: + raise InvalidProxyMessage( + "did not receive a valid HTTP response from proxy" + ) from exc + finally: + sock.settimeout(None) + + +def connect_http_proxy( + proxy: Proxy, + ws_uri: WebSocketURI, + deadline: Deadline, + *, + user_agent_header: str | None = None, + ssl: ssl_module.SSLContext | None = None, + server_hostname: str | None = None, + **kwargs: Any, +) -> socket.socket: + # Connect socket + + kwargs.setdefault("timeout", deadline.timeout()) + sock = socket.create_connection((proxy.host, proxy.port), **kwargs) + + # Initialize TLS wrapper and perform TLS handshake + + if proxy.scheme == "https": + if ssl is None: + ssl = ssl_module.create_default_context() + if server_hostname is None: + server_hostname = proxy.host + sock.settimeout(deadline.timeout()) + sock = ssl.wrap_socket(sock, server_hostname=server_hostname) + sock.settimeout(None) + + # Send CONNECT request to the proxy and read response. + + sock.sendall(prepare_connect_request(proxy, ws_uri, user_agent_header)) + try: + read_connect_response(sock, deadline) + except Exception: + sock.close() + raise + + return sock + + +T = TypeVar("T") +F = TypeVar("F", bound=Callable[..., T]) + + +class SSLSSLSocket: + """ + Socket-like object providing TLS-in-TLS. + + Only methods that are used by websockets are implemented. + + """ + + recv_bufsize = 65536 + + def __init__( + self, + sock: socket.socket, + ssl_context: ssl_module.SSLContext, + server_hostname: str | None = None, + ) -> None: + self.incoming = ssl_module.MemoryBIO() + self.outgoing = ssl_module.MemoryBIO() + self.ssl_socket = sock + self.ssl_object = ssl_context.wrap_bio( + self.incoming, + self.outgoing, + server_hostname=server_hostname, + ) + self.run_io(self.ssl_object.do_handshake) + + def run_io(self, func: Callable[..., T], *args: Any) -> T: + while True: + want_read = False + want_write = False + try: + result = func(*args) + except ssl_module.SSLWantReadError: + want_read = True + except ssl_module.SSLWantWriteError: # pragma: no cover + want_write = True + + # Write outgoing data in all cases. + data = self.outgoing.read() + if data: + self.ssl_socket.sendall(data) + + # Read incoming data and retry on SSLWantReadError. + if want_read: + data = self.ssl_socket.recv(self.recv_bufsize) + if data: + self.incoming.write(data) + else: + self.incoming.write_eof() + continue + # Retry after writing outgoing data on SSLWantWriteError. + if want_write: # pragma: no cover + continue + # Return result if no error happened. + return result + + def recv(self, buflen: int) -> bytes: + try: + return self.run_io(self.ssl_object.read, buflen) + except ssl_module.SSLEOFError: + return b"" # always ignore ragged EOFs + + def send(self, data: BytesLike) -> int: + return self.run_io(self.ssl_object.write, data) + + def sendall(self, data: BytesLike) -> None: + # adapted from ssl_module.SSLSocket.sendall() + count = 0 + with memoryview(data) as view, view.cast("B") as byte_view: + amount = len(byte_view) + while count < amount: + count += self.send(byte_view[count:]) + + # recv_into(), recvfrom(), recvfrom_into(), sendto(), unwrap(), and the + # flags argument aren't implemented because websockets doesn't need them. + + def __getattr__(self, name: str) -> Any: + return getattr(self.ssl_socket, name) diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/connection.py b/.venv/lib/python3.14/site-packages/websockets/sync/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..665f478ac9377174ca5c912f5939f81bdd8db3d7 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/sync/connection.py @@ -0,0 +1,1078 @@ +from __future__ import annotations + +import contextlib +import logging +import random +import socket +import struct +import threading +import time +import uuid +from collections.abc import Iterable, Iterator, Mapping +from types import TracebackType +from typing import Any, Literal, overload + +from ..exceptions import ( + ConcurrencyError, + ConnectionClosed, + ConnectionClosedOK, + ProtocolError, +) +from ..frames import DATA_OPCODES, CloseCode, Frame, Opcode +from ..http11 import Request, Response +from ..protocol import CLOSED, OPEN, Event, Protocol, State +from ..typing import BytesLike, Data, DataLike, LoggerLike, Subprotocol +from .messages import Assembler +from .utils import Deadline + + +__all__ = ["Connection"] + + +class Connection: + """ + :mod:`threading` implementation of a WebSocket connection. + + :class:`Connection` provides APIs shared between WebSocket servers and + clients. + + You shouldn't use it directly. Instead, use + :class:`~websockets.sync.client.ClientConnection` or + :class:`~websockets.sync.server.ServerConnection`. + + """ + + recv_bufsize = 65536 + + def __init__( + self, + socket: socket.socket, + protocol: Protocol, + *, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + max_queue: int | None | tuple[int | None, int | None] = 16, + ) -> None: + self.socket = socket + self.protocol = protocol + self.ping_interval = ping_interval + self.ping_timeout = ping_timeout + self.close_timeout = close_timeout + if isinstance(max_queue, int) or max_queue is None: + max_queue_high, max_queue_low = max_queue, None + else: + max_queue_high, max_queue_low = max_queue + + # Inject reference to this instance in the protocol's logger. + self.protocol.logger = logging.LoggerAdapter( + self.protocol.logger, + {"websocket": self}, + ) + + # Copy attributes from the protocol for convenience. + self.id: uuid.UUID = self.protocol.id + """Unique identifier of the connection. Useful in logs.""" + self.logger: LoggerLike = self.protocol.logger + """Logger for this connection.""" + self.debug = self.protocol.debug + + # HTTP handshake request and response. + self.request: Request | None = None + """Opening handshake request.""" + self.response: Response | None = None + """Opening handshake response.""" + + # Mutex serializing interactions with the protocol. + self.protocol_mutex = threading.Lock() + + # Lock stopping reads when the assembler buffer is full. + self.recv_flow_control = threading.Lock() + + # Assembler turning frames into messages and serializing reads. + self.recv_messages = Assembler( + max_queue_high, + max_queue_low, + pause=self.recv_flow_control.acquire, + resume=self.recv_flow_control.release, + ) + + # Deadline for the closing handshake. + self.close_deadline: Deadline | None = None + + # Whether we are busy sending a fragmented message. + self.send_in_progress = False + + # Mapping of ping IDs to pong waiters, in chronological order. + self.pending_pings: dict[bytes, tuple[threading.Event, float, bool]] = {} + + self.latency: float = 0.0 + """ + Latency of the connection, in seconds. + + Latency is defined as the round-trip time of the connection. It is + measured by sending a Ping frame and waiting for a matching Pong frame. + Before the first measurement, :attr:`latency` is ``0.0``. + + By default, websockets enables a :ref:`keepalive ` mechanism + that sends Ping frames automatically at regular intervals. You can also + send Ping frames and measure latency with :meth:`ping`. + """ + + # Thread that sends keepalive pings. None when ping_interval is None. + self.keepalive_thread: threading.Thread | None = None + + # Exception raised while reading from the connection, to be chained to + # ConnectionClosed in order to show why the TCP connection dropped. + self.recv_exc: BaseException | None = None + + # Receiving events from the socket. This thread is marked as daemon to + # allow creating a connection in a non-daemon thread and using it in a + # daemon thread. This mustn't prevent the interpreter from exiting. + self.recv_events_thread = threading.Thread( + target=self.recv_events, + daemon=True, + ) + + # Start recv_events only after all attributes are initialized. + self.recv_events_thread.start() + + # Public attributes + + @property + def local_address(self) -> Any: + """ + Local address of the connection. + + For IPv4 connections, this is a ``(host, port)`` tuple. + + The format of the address depends on the address family. + See :meth:`~socket.socket.getsockname`. + + """ + return self.socket.getsockname() + + @property + def remote_address(self) -> Any: + """ + Remote address of the connection. + + For IPv4 connections, this is a ``(host, port)`` tuple. + + The format of the address depends on the address family. + See :meth:`~socket.socket.getpeername`. + + """ + return self.socket.getpeername() + + @property + def state(self) -> State: + """ + State of the WebSocket connection, defined in :rfc:`6455`. + + This attribute is provided for completeness. Typical applications + shouldn't check its value. Instead, they should call :meth:`~recv` or + :meth:`send` and handle :exc:`~websockets.exceptions.ConnectionClosed` + exceptions. + + """ + return self.protocol.state + + @property + def subprotocol(self) -> Subprotocol | None: + """ + Subprotocol negotiated during the opening handshake. + + :obj:`None` if no subprotocol was negotiated. + + """ + return self.protocol.subprotocol + + @property + def close_code(self) -> int | None: + """ + State of the WebSocket connection, defined in :rfc:`6455`. + + This attribute is provided for completeness. Typical applications + shouldn't check its value. Instead, they should inspect attributes + of :exc:`~websockets.exceptions.ConnectionClosed` exceptions. + + """ + return self.protocol.close_code + + @property + def close_reason(self) -> str | None: + """ + State of the WebSocket connection, defined in :rfc:`6455`. + + This attribute is provided for completeness. Typical applications + shouldn't check its value. Instead, they should inspect attributes + of :exc:`~websockets.exceptions.ConnectionClosed` exceptions. + + """ + return self.protocol.close_reason + + # Public methods + + def __enter__(self) -> Connection: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + if exc_type is None: + self.close() + else: + self.close(CloseCode.INTERNAL_ERROR) + + def __iter__(self) -> Iterator[Data]: + """ + Iterate on incoming messages. + + The iterator calls :meth:`recv` and yields messages in an infinite loop. + + It exits when the connection is closed normally. It raises a + :exc:`~websockets.exceptions.ConnectionClosedError` exception after a + protocol error or a network failure. + + """ + try: + while True: + yield self.recv() + except ConnectionClosedOK: + return + + # This overload structure is required to avoid the error: + # "parameter without a default follows parameter with a default" + + @overload + def recv(self, timeout: float | None, decode: Literal[True]) -> str: ... + + @overload + def recv(self, timeout: float | None, decode: Literal[False]) -> bytes: ... + + @overload + def recv(self, timeout: float | None = None, *, decode: Literal[True]) -> str: ... + + @overload + def recv( + self, timeout: float | None = None, *, decode: Literal[False] + ) -> bytes: ... + + @overload + def recv( + self, timeout: float | None = None, decode: bool | None = None + ) -> Data: ... + + def recv(self, timeout: float | None = None, decode: bool | None = None) -> Data: + """ + Receive the next message. + + When the connection is closed, :meth:`recv` raises + :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it raises + :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal closure + and :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol + error or a network failure. This is how you detect the end of the + message stream. + + If ``timeout`` is :obj:`None`, block until a message is received. If + ``timeout`` is set, wait up to ``timeout`` seconds for a message to be + received and return it, else raise :exc:`TimeoutError`. If ``timeout`` + is ``0`` or negative, check if a message has been received already and + return it, else raise :exc:`TimeoutError`. + + When the message is fragmented, :meth:`recv` waits until all fragments + are received, reassembles them, and returns the whole message. + + Args: + timeout: Timeout for receiving a message in seconds. + decode: Set this flag to override the default behavior of returning + :class:`str` or :class:`bytes`. See below for details. + + Returns: + A string (:class:`str`) for a Text_ frame or a bytestring + (:class:`bytes`) for a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + You may override this behavior with the ``decode`` argument: + + * Set ``decode=False`` to disable UTF-8 decoding of Text_ frames and + return a bytestring (:class:`bytes`). This improves performance + when decoding isn't needed, for example if the message contains + JSON and you're using a JSON library that expects a bytestring. + * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames and + return strings (:class:`str`). This may be useful for servers that + send binary frames instead of text frames. + + Raises: + ConnectionClosed: When the connection is closed. + ConcurrencyError: If two threads call :meth:`recv` or + :meth:`recv_streaming` concurrently. + + """ + try: + return self.recv_messages.get(timeout, decode) + except EOFError: + pass + # fallthrough + except ConcurrencyError: + raise ConcurrencyError( + "cannot call recv while another thread " + "is already running recv or recv_streaming" + ) from None + except UnicodeDecodeError as exc: + with self.send_context(): + self.protocol.fail( + CloseCode.INVALID_DATA, + f"{exc.reason} at position {exc.start}", + ) + # fallthrough + + # Wait for the protocol state to be CLOSED before accessing close_exc. + self.recv_events_thread.join() + raise self.protocol.close_exc from self.recv_exc + + @overload + def recv_streaming(self, decode: Literal[True]) -> Iterator[str]: ... + + @overload + def recv_streaming(self, decode: Literal[False]) -> Iterator[bytes]: ... + + @overload + def recv_streaming(self, decode: bool | None = None) -> Iterator[Data]: ... + + def recv_streaming(self, decode: bool | None = None) -> Iterator[Data]: + """ + Receive the next message frame by frame. + + This method is designed for receiving fragmented messages. It returns an + iterator that yields each fragment as it is received. This iterator must + be fully consumed. Else, future calls to :meth:`recv` or + :meth:`recv_streaming` will raise + :exc:`~websockets.exceptions.ConcurrencyError`, making the connection + unusable. + + :meth:`recv_streaming` raises the same exceptions as :meth:`recv`. + + Args: + decode: Set this flag to override the default behavior of returning + :class:`str` or :class:`bytes`. See below for details. + + Returns: + An iterator of strings (:class:`str`) for a Text_ frame or + bytestrings (:class:`bytes`) for a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + You may override this behavior with the ``decode`` argument: + + * Set ``decode=False`` to disable UTF-8 decoding of Text_ frames and + yield bytestrings (:class:`bytes`). This improves performance + when decoding isn't needed. + * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames and + yield strings (:class:`str`). This may be useful for servers that + send binary frames instead of text frames. + + Raises: + ConnectionClosed: When the connection is closed. + ConcurrencyError: If two threads call :meth:`recv` or + :meth:`recv_streaming` concurrently. + + """ + try: + yield from self.recv_messages.get_iter(decode) + return + except EOFError: + pass + # fallthrough + except ConcurrencyError: + raise ConcurrencyError( + "cannot call recv_streaming while another thread " + "is already running recv or recv_streaming" + ) from None + except UnicodeDecodeError as exc: + with self.send_context(): + self.protocol.fail( + CloseCode.INVALID_DATA, + f"{exc.reason} at position {exc.start}", + ) + # fallthrough + + # Wait for the protocol state to be CLOSED before accessing close_exc. + self.recv_events_thread.join() + raise self.protocol.close_exc from self.recv_exc + + def send( + self, + message: DataLike | Iterable[DataLike], + text: bool | None = None, + ) -> None: + """ + Send a message. + + A string (:class:`str`) is sent as a Text_ frame. A bytestring or + bytes-like object (:class:`bytes`, :class:`bytearray`, or + :class:`memoryview`) is sent as a Binary_ frame. + + .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + You may override this behavior with the ``text`` argument: + + * Set ``text=True`` to send an UTF-8 bytestring or bytes-like object + (:class:`bytes`, :class:`bytearray`, or :class:`memoryview`) in a + Text_ frame. This improves performance when the message is already + UTF-8 encoded, for example if the message contains JSON and you're + using a JSON library that produces a bytestring. + * Set ``text=False`` to send a string (:class:`str`) in a Binary_ + frame. This may be useful for servers that expect binary frames + instead of text frames. + + :meth:`send` also accepts an iterable of strings, bytestrings, or + bytes-like objects to enable fragmentation_. Each item is treated as a + message fragment and sent in its own frame. All items must be of the + same type, or else :meth:`send` will raise a :exc:`TypeError` and the + connection will be closed. + + .. _fragmentation: https://datatracker.ietf.org/doc/html/rfc6455#section-5.4 + + :meth:`send` rejects dict-like objects because this is often an error. + (If you really want to send the keys of a dict-like object as fragments, + call its :meth:`~dict.keys` method and pass the result to :meth:`send`.) + + When the connection is closed, :meth:`send` raises + :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it + raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal + connection closure and + :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol + error or a network failure. + + Args: + message: Message to send. + + Raises: + ConnectionClosed: When the connection is closed. + ConcurrencyError: If the connection is sending a fragmented message. + TypeError: If ``message`` doesn't have a supported type. + + """ + # Unfragmented message -- this case must be handled first because + # strings and bytes-like objects are iterable. + + if isinstance(message, str): + with self.send_context(): + if self.send_in_progress: + raise ConcurrencyError( + "cannot call send while another thread is already running send" + ) + if text is False: + self.protocol.send_binary(message.encode()) + else: + self.protocol.send_text(message.encode()) + + elif isinstance(message, BytesLike): + with self.send_context(): + if self.send_in_progress: + raise ConcurrencyError( + "cannot call send while another thread is already running send" + ) + if text is True: + self.protocol.send_text(message) + else: + self.protocol.send_binary(message) + + # Catch a common mistake -- passing a dict to send(). + + elif isinstance(message, Mapping): + raise TypeError("data is a dict-like object") + + # Fragmented message -- regular iterator. + + elif isinstance(message, Iterable): + chunks = iter(message) + try: + chunk = next(chunks) + except StopIteration: + return + + try: + # First fragment. + if isinstance(chunk, str): + with self.send_context(): + if self.send_in_progress: + raise ConcurrencyError( + "cannot call send while another thread " + "is already running send" + ) + self.send_in_progress = True + if text is False: + self.protocol.send_binary(chunk.encode(), fin=False) + else: + self.protocol.send_text(chunk.encode(), fin=False) + encode = True + elif isinstance(chunk, BytesLike): + with self.send_context(): + if self.send_in_progress: + raise ConcurrencyError( + "cannot call send while another thread " + "is already running send" + ) + self.send_in_progress = True + if text is True: + self.protocol.send_text(chunk, fin=False) + else: + self.protocol.send_binary(chunk, fin=False) + encode = False + else: + raise TypeError("iterable must contain bytes or str") + + # Other fragments + for chunk in chunks: + if isinstance(chunk, str) and encode: + with self.send_context(): + assert self.send_in_progress + self.protocol.send_continuation(chunk.encode(), fin=False) + elif isinstance(chunk, BytesLike) and not encode: + with self.send_context(): + assert self.send_in_progress + self.protocol.send_continuation(chunk, fin=False) + else: + raise TypeError("iterable must contain uniform types") + + # Final fragment. + with self.send_context(): + self.protocol.send_continuation(b"", fin=True) + self.send_in_progress = False + + except ConcurrencyError: + # We didn't start sending a fragmented message. + # The connection is still usable. + raise + + except Exception: + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + with self.send_context(): + self.protocol.fail( + CloseCode.INTERNAL_ERROR, + "error in fragmented message", + ) + raise + + else: + raise TypeError("data must be str, bytes, or iterable") + + def close( + self, + code: CloseCode | int = CloseCode.NORMAL_CLOSURE, + reason: str = "", + ) -> None: + """ + Perform the closing handshake. + + :meth:`close` waits for the other end to complete the handshake and + for the TCP connection to terminate. + + :meth:`close` is idempotent: it doesn't do anything once the + connection is closed. + + Args: + code: WebSocket close code. + reason: WebSocket close reason. + + """ + try: + # The context manager takes care of waiting for the TCP connection + # to terminate after calling a method that sends a close frame. + with self.send_context(): + if self.send_in_progress: + self.protocol.fail( + CloseCode.INTERNAL_ERROR, + "close during fragmented message", + ) + else: + self.protocol.send_close(code, reason) + except ConnectionClosed: + # Ignore ConnectionClosed exceptions raised from send_context(). + # They mean that the connection is closed, which was the goal. + pass + + def ping( + self, + data: DataLike | None = None, + ack_on_close: bool = False, + ) -> threading.Event: + """ + Send a Ping_. + + .. _Ping: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2 + + A ping may serve as a keepalive or as a check that the remote endpoint + received all messages up to this point + + Args: + data: Payload of the ping. A :class:`str` will be encoded to UTF-8. + If ``data`` is :obj:`None`, the payload is four random bytes. + ack_on_close: when this option is :obj:`True`, the event will also + be set when the connection is closed. While this avoids getting + stuck waiting for a pong that will never arrive, it requires + checking that the state of the connection is still ``OPEN`` to + confirm that a pong was received, rather than the connection + being closed. + + Returns: + An event that will be set when the corresponding pong is received. + You can ignore it if you don't intend to wait. + + :: + + pong_received = ws.ping() + # only if you want to wait for the corresponding pong + pong_received.wait() + + Raises: + ConnectionClosed: When the connection is closed. + ConcurrencyError: If another ping was sent with the same data and + the corresponding pong wasn't received yet. + + """ + if isinstance(data, BytesLike): + data = bytes(data) + elif isinstance(data, str): + data = data.encode() + elif data is not None: + raise TypeError("data must be str or bytes-like") + + with self.send_context(): + # Protect against duplicates if a payload is explicitly set. + if data in self.pending_pings: + raise ConcurrencyError("already waiting for a pong with the same data") + + # Generate a unique random payload otherwise. + while data is None or data in self.pending_pings: + data = struct.pack("!I", random.getrandbits(32)) + + pong_received = threading.Event() + ping_timestamp = time.monotonic() + self.pending_pings[data] = (pong_received, ping_timestamp, ack_on_close) + self.protocol.send_ping(data) + return pong_received + + def pong(self, data: DataLike = b"") -> None: + """ + Send a Pong_. + + .. _Pong: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3 + + An unsolicited pong may serve as a unidirectional heartbeat. + + Args: + data: Payload of the pong. A :class:`str` will be encoded to UTF-8. + + Raises: + ConnectionClosed: When the connection is closed. + + """ + if isinstance(data, BytesLike): + data = bytes(data) + elif isinstance(data, str): + data = data.encode() + else: + raise TypeError("data must be str or bytes-like") + + with self.send_context(): + self.protocol.send_pong(data) + + # Private methods + + def process_event(self, event: Event) -> None: + """ + Process one incoming event. + + This method is overridden in subclasses to handle the handshake. + + """ + assert isinstance(event, Frame) + if event.opcode in DATA_OPCODES: + self.recv_messages.put(event) + + if event.opcode is Opcode.PONG: + self.acknowledge_pings(bytes(event.data)) + + def acknowledge_pings(self, data: bytes) -> None: + """ + Acknowledge pings when receiving a pong. + + """ + with self.protocol_mutex: + # Ignore unsolicited pong. + if data not in self.pending_pings: + return + + pong_timestamp = time.monotonic() + + # Sending a pong for only the most recent ping is legal. + # Acknowledge all previous pings too in that case. + ping_id = None + ping_ids = [] + for ping_id, ( + pong_received, + ping_timestamp, + _ack_on_close, + ) in self.pending_pings.items(): + ping_ids.append(ping_id) + pong_received.set() + if ping_id == data: + self.latency = pong_timestamp - ping_timestamp + break + else: + raise AssertionError("solicited pong not found in pings") + + # Remove acknowledged pings from self.pending_pings. + for ping_id in ping_ids: + del self.pending_pings[ping_id] + + def terminate_pending_pings(self) -> None: + """ + Acknowledge pending pings when the connection is closed. + + """ + assert self.protocol_mutex.locked() + assert self.protocol.state is CLOSED + + for pong_received, _ping_timestamp, ack_on_close in self.pending_pings.values(): + if ack_on_close: + pong_received.set() + + self.pending_pings.clear() + + def keepalive(self) -> None: + """ + Send a Ping frame and wait for a Pong frame at regular intervals. + + """ + assert self.ping_interval is not None + try: + while True: + # If self.ping_timeout > self.latency > self.ping_interval, + # pings will be sent immediately after receiving pongs. + # The period will be longer than self.ping_interval. + self.recv_events_thread.join(self.ping_interval - self.latency) + if not self.recv_events_thread.is_alive(): + break + + try: + pong_received = self.ping(ack_on_close=True) + except ConnectionClosed: + break + if self.debug: + self.logger.debug("% sent keepalive ping") + + if self.ping_timeout is not None: + if pong_received.wait(self.ping_timeout): + if self.debug: + self.logger.debug("% received keepalive pong") + else: + if self.debug: + self.logger.debug("- timed out waiting for keepalive pong") + with self.send_context(): + self.protocol.fail( + CloseCode.INTERNAL_ERROR, + "keepalive ping timeout", + ) + break + except Exception: + self.logger.error("keepalive ping failed", exc_info=True) + + def start_keepalive(self) -> None: + """ + Run :meth:`keepalive` in a thread, unless keepalive is disabled. + + """ + if self.ping_interval is not None: + # This thread is marked as daemon like self.recv_events_thread. + self.keepalive_thread = threading.Thread( + target=self.keepalive, + daemon=True, + ) + self.keepalive_thread.start() + + def recv_events(self) -> None: + """ + Read incoming data from the socket and process events. + + Run this method in a thread as long as the connection is alive. + + ``recv_events()`` exits immediately when ``self.socket`` is closed. + + """ + try: + while True: + try: + # If the assembler buffer is full, block until it drains. + with self.recv_flow_control: + pass + if self.close_deadline is not None: + self.socket.settimeout(self.close_deadline.timeout()) + data = self.socket.recv(self.recv_bufsize) + except Exception as exc: + if self.debug: + self.logger.debug( + "! error while receiving data", + exc_info=True, + ) + # When the closing handshake is initiated by our side, + # recv() may block until send_context() closes the socket. + # In that case, send_context() already set recv_exc. + # Calling set_recv_exc() avoids overwriting it. + with self.protocol_mutex: + self.set_recv_exc(exc) + break + + if data == b"": + break + + # Acquire the connection lock. + with self.protocol_mutex: + # Feed incoming data to the protocol. + self.protocol.receive_data(data) + + # This isn't expected to raise an exception. + events = self.protocol.events_received() + + # Write outgoing data to the socket. + try: + self.send_data() + except Exception as exc: + if self.debug: + self.logger.debug( + "! error while sending data", + exc_info=True, + ) + # Similarly to the above, avoid overriding an exception + # set by send_context(), in case of a race condition + # i.e. send_context() closes the socket after recv() + # returns above but before send_data() calls send(). + self.set_recv_exc(exc) + break + + # If needed, set the close deadline based on the close timeout. + if self.protocol.close_expected(): + if self.close_deadline is None: + self.close_deadline = Deadline(self.close_timeout) + + # Unlock conn_mutex before processing events. Else, the + # application can't send messages in response to events. + + # If self.send_data raised an exception, then events are lost. + # Given that automatic responses write small amounts of data, + # this should be uncommon, so we don't handle the edge case. + + for event in events: + # This isn't expected to raise an exception. + self.process_event(event) + + # Breaking out of the while True: ... loop means that we believe + # that the socket doesn't work anymore. + + with self.protocol_mutex: + # Feed the end of the data stream to the protocol. + self.protocol.receive_eof() + + # This isn't expected to raise an exception. + events = self.protocol.events_received() + + # There is no error handling because send_data() can only write + # the end of the data stream and it handles errors by itself. + self.send_data() + + # This code path is triggered when receiving an HTTP response + # without a Content-Length header. This is the only case where + # reading until EOF generates an event; all other events have + # a known length. Ignore for coverage measurement because tests + # are in test_client.py rather than test_connection.py. + for event in events: # pragma: no cover + # This isn't expected to raise an exception. + self.process_event(event) + + except Exception as exc: + # This branch should never run. It's a safety net in case of bugs. + self.logger.error("unexpected internal error", exc_info=True) + with self.protocol_mutex: + self.set_recv_exc(exc) + finally: + # This isn't expected to raise an exception. + self.close_socket() + + @contextlib.contextmanager + def send_context( + self, + *, + expected_state: State = OPEN, # CONNECTING during the opening handshake + ) -> Iterator[None]: + """ + Create a context for writing to the connection from user code. + + On entry, :meth:`send_context` acquires the connection lock and checks + that the connection is open; on exit, it writes outgoing data to the + socket and releases the connection lock:: + + with self.send_context(): + self.protocol.send_text(message.encode()) + + When the connection isn't open on entry, when the connection is expected + to close on exit, or when an unexpected error happens, terminating the + connection, :meth:`send_context` waits until the connection is closed + then raises :exc:`~websockets.exceptions.ConnectionClosed`. + + """ + # Should we wait until the connection is closed? + wait_for_close = False + # Should we close the socket and raise ConnectionClosed? + raise_close_exc = False + # What exception should we chain ConnectionClosed to? + original_exc: BaseException | None = None + + # Acquire the protocol lock. + with self.protocol_mutex: + if self.protocol.state is expected_state: + # Let the caller interact with the protocol. + try: + yield + except (ProtocolError, ConcurrencyError): + # The protocol state wasn't changed. Exit immediately. + raise + except Exception as exc: + self.logger.error("unexpected internal error", exc_info=True) + # This branch should never run. It's a safety net in case of + # bugs. Since we don't know what happened, we will close the + # connection and raise the exception to the caller. + wait_for_close = False + raise_close_exc = True + original_exc = exc + else: + # Check if the connection is expected to close soon. + if self.protocol.close_expected(): + wait_for_close = True + # Set the close deadline based on the close timeout. + # Since we tested earlier that protocol.state is OPEN + # (or CONNECTING) and we didn't release protocol_mutex, + # self.close_deadline is still None. + assert self.close_deadline is None + self.close_deadline = Deadline(self.close_timeout) + # Write outgoing data to the socket. + try: + self.send_data() + except Exception as exc: + if self.debug: + self.logger.debug( + "! error while sending data", + exc_info=True, + ) + # While the only expected exception here is OSError, + # other exceptions would be treated identically. + wait_for_close = False + raise_close_exc = True + original_exc = exc + + else: # self.protocol.state is not expected_state + # Minor layering violation: we assume that the connection + # will be closing soon if it isn't in the expected state. + wait_for_close = True + # Calculate close_deadline if it wasn't set yet. + if self.close_deadline is None: + self.close_deadline = Deadline(self.close_timeout) + raise_close_exc = True + + # To avoid a deadlock, release the connection lock by exiting the + # context manager before waiting for recv_events() to terminate. + + # If the connection is expected to close soon and the close timeout + # elapses, close the socket to terminate the connection. + if wait_for_close: + # Thread.join() returns immediately if timeout is negative. + assert self.close_deadline is not None + timeout = self.close_deadline.timeout(raise_if_elapsed=False) + self.recv_events_thread.join(timeout) + if self.recv_events_thread.is_alive(): + # There's no risk of overwriting another error because + # original_exc is never set when wait_for_close is True. + assert original_exc is None + original_exc = TimeoutError("timed out while closing connection") + # Set recv_exc before closing the socket in order to get + # proper exception reporting. + raise_close_exc = True + with self.protocol_mutex: + self.set_recv_exc(original_exc) + + # If an error occurred, close the socket to terminate the connection and + # raise an exception. + if raise_close_exc: + self.close_socket() + # Wait for the protocol state to be CLOSED before accessing close_exc. + self.recv_events_thread.join() + raise self.protocol.close_exc from original_exc + + def send_data(self) -> None: + """ + Send outgoing data. + + This method requires holding protocol_mutex. + + """ + assert self.protocol_mutex.locked() + for data in self.protocol.data_to_send(): + if data: + if self.close_deadline is not None: + self.socket.settimeout(self.close_deadline.timeout()) + self.socket.sendall(data) + else: + try: + self.socket.shutdown(socket.SHUT_WR) + except OSError: # socket already closed + pass + + def set_recv_exc(self, exc: BaseException | None) -> None: + """ + Set recv_exc, if not set yet. + + This method requires holding protocol_mutex and must be called only from + the thread running recv_events(). + + """ + assert self.protocol_mutex.locked() + if self.recv_exc is None: + self.recv_exc = exc + + def close_socket(self) -> None: + """ + Shutdown and close socket. Close message assembler. + + Calling close_socket() guarantees that recv_events() terminates. Indeed, + recv_events() may block only on socket.recv() or on recv_messages.put(). + + """ + # shutdown() is required to interrupt recv() on Linux. + try: + self.socket.shutdown(socket.SHUT_RDWR) + except OSError: # socket already closed + pass + self.socket.close() + + # Calling protocol.receive_eof() is safe because it's idempotent. + # This guarantees that the protocol state becomes CLOSED. + with self.protocol_mutex: + self.protocol.receive_eof() + assert self.protocol.state is CLOSED + + # Abort recv() with a ConnectionClosed exception. + self.recv_messages.close() + + # Acknowledge pings sent with the ack_on_close option. + self.terminate_pending_pings() diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/messages.py b/.venv/lib/python3.14/site-packages/websockets/sync/messages.py new file mode 100644 index 0000000000000000000000000000000000000000..d95519f63ecfb20e681de13ed6bf3481c966d2d3 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/sync/messages.py @@ -0,0 +1,348 @@ +from __future__ import annotations + +import codecs +import queue +import threading +from typing import Any, Callable, Iterable, Iterator, Literal, overload + +from ..exceptions import ConcurrencyError +from ..frames import OP_BINARY, OP_CONT, OP_TEXT, Frame +from ..typing import Data +from .utils import Deadline + + +__all__ = ["Assembler"] + +UTF8Decoder = codecs.getincrementaldecoder("utf-8") + + +class Assembler: + """ + Assemble messages from frames. + + :class:`Assembler` expects only data frames. The stream of frames must + respect the protocol; if it doesn't, the behavior is undefined. + + Args: + pause: Called when the buffer of frames goes above the high water mark; + should pause reading from the network. + resume: Called when the buffer of frames goes below the low water mark; + should resume reading from the network. + + """ + + def __init__( + self, + high: int | None = None, + low: int | None = None, + pause: Callable[[], Any] = lambda: None, + resume: Callable[[], Any] = lambda: None, + ) -> None: + # Serialize reads and writes -- except for reads via synchronization + # primitives provided by the threading and queue modules. + self.mutex = threading.Lock() + + # Queue of incoming frames. + self.frames: queue.SimpleQueue[Frame | None] = queue.SimpleQueue() + + # We cannot put a hard limit on the size of the queue because a single + # call to Protocol.data_received() could produce thousands of frames, + # which must be buffered. Instead, we pause reading when the buffer goes + # above the high limit and we resume when it goes under the low limit. + if high is not None and low is None: + low = high // 4 + if high is None and low is not None: + high = low * 4 + if high is not None and low is not None: + if low < 0: + raise ValueError("low must be positive or equal to zero") + if high < low: + raise ValueError("high must be greater than or equal to low") + self.high, self.low = high, low + self.pause = pause + self.resume = resume + self.paused = False + + # This flag prevents concurrent calls to get() by user code. + self.get_in_progress = False + + # This flag marks the end of the connection. + self.closed = False + + def get_next_frame(self, timeout: float | None = None) -> Frame: + # Helper to factor out the logic for getting the next frame from the + # queue, while handling timeouts and reaching the end of the stream. + if self.closed: + try: + frame = self.frames.get(block=False) + except queue.Empty: + raise EOFError("stream of frames ended") from None + else: + try: + # Check for a frame that's already received if timeout <= 0. + # SimpleQueue.get() doesn't support negative timeout values. + if timeout is not None and timeout <= 0: + frame = self.frames.get(block=False) + else: + frame = self.frames.get(block=True, timeout=timeout) + except queue.Empty: + raise TimeoutError(f"timed out in {timeout:.1f}s") from None + if frame is None: + raise EOFError("stream of frames ended") + return frame + + def reset_queue(self, frames: Iterable[Frame]) -> None: + # Helper to put frames back into the queue after they were fetched. + # This happens only when the queue is empty. However, by the time + # we acquire self.mutex, put() may have added items in the queue. + # Therefore, we must handle the case where the queue is not empty. + frame: Frame | None + with self.mutex: + queued = [] + try: + while True: + queued.append(self.frames.get(block=False)) + except queue.Empty: + pass + for frame in frames: + self.frames.put(frame) + # This loop runs only when a race condition occurs. + for frame in queued: # pragma: no cover + self.frames.put(frame) + + # This overload structure is required to avoid the error: + # "parameter without a default follows parameter with a default" + + @overload + def get(self, timeout: float | None, decode: Literal[True]) -> str: ... + + @overload + def get(self, timeout: float | None, decode: Literal[False]) -> bytes: ... + + @overload + def get(self, timeout: float | None = None, *, decode: Literal[True]) -> str: ... + + @overload + def get(self, timeout: float | None = None, *, decode: Literal[False]) -> bytes: ... + + @overload + def get(self, timeout: float | None = None, decode: bool | None = None) -> Data: ... + + def get(self, timeout: float | None = None, decode: bool | None = None) -> Data: + """ + Read the next message. + + :meth:`get` returns a single :class:`str` or :class:`bytes`. + + If the message is fragmented, :meth:`get` waits until the last frame is + received, then it reassembles the message and returns it. To receive + messages frame by frame, use :meth:`get_iter` instead. + + Args: + timeout: If a timeout is provided and elapses before a complete + message is received, :meth:`get` raises :exc:`TimeoutError`. + decode: :obj:`False` disables UTF-8 decoding of text frames and + returns :class:`bytes`. :obj:`True` forces UTF-8 decoding of + binary frames and returns :class:`str`. + + Raises: + EOFError: If the stream of frames has ended. + UnicodeDecodeError: If a text frame contains invalid UTF-8. + ConcurrencyError: If two coroutines run :meth:`get` or + :meth:`get_iter` concurrently. + TimeoutError: If a timeout is provided and elapses before a + complete message is received. + + """ + with self.mutex: + if self.get_in_progress: + raise ConcurrencyError("get() or get_iter() is already running") + self.get_in_progress = True + + # Locking with get_in_progress prevents concurrent execution + # until get() fetches a complete message or times out. + + try: + deadline = Deadline(timeout) + + # Fetch the first frame. + frame = self.get_next_frame(deadline.timeout(raise_if_elapsed=False)) + with self.mutex: + self.maybe_resume() + assert frame.opcode is OP_TEXT or frame.opcode is OP_BINARY + if decode is None: + decode = frame.opcode is OP_TEXT + frames = [frame] + + # Fetch subsequent frames for fragmented messages. + while not frame.fin: + try: + frame = self.get_next_frame( + deadline.timeout(raise_if_elapsed=False) + ) + except TimeoutError: + # Put frames already received back into the queue + # so that future calls to get() can return them. + self.reset_queue(frames) + raise + with self.mutex: + self.maybe_resume() + assert frame.opcode is OP_CONT + frames.append(frame) + + finally: + self.get_in_progress = False + + # This converts frame.data to bytes when it's a bytearray. + data = b"".join(frame.data for frame in frames) + if decode: + return data.decode() + else: + return data + + @overload + def get_iter(self, decode: Literal[True]) -> Iterator[str]: ... + + @overload + def get_iter(self, decode: Literal[False]) -> Iterator[bytes]: ... + + @overload + def get_iter(self, decode: bool | None = None) -> Iterator[Data]: ... + + def get_iter(self, decode: bool | None = None) -> Iterator[Data]: + """ + Stream the next message. + + Iterating the return value of :meth:`get_iter` yields a :class:`str` or + :class:`bytes` for each frame in the message. + + The iterator must be fully consumed before calling :meth:`get_iter` or + :meth:`get` again. Else, :exc:`ConcurrencyError` is raised. + + This method only makes sense for fragmented messages. If messages aren't + fragmented, use :meth:`get` instead. + + Args: + decode: :obj:`False` disables UTF-8 decoding of text frames and + returns :class:`bytes`. :obj:`True` forces UTF-8 decoding of + binary frames and returns :class:`str`. + + Raises: + EOFError: If the stream of frames has ended. + UnicodeDecodeError: If a text frame contains invalid UTF-8. + ConcurrencyError: If two coroutines run :meth:`get` or + :meth:`get_iter` concurrently. + + """ + with self.mutex: + if self.get_in_progress: + raise ConcurrencyError("get() or get_iter() is already running") + self.get_in_progress = True + + # Locking with get_in_progress prevents concurrent execution + # until get_iter() fetches a complete message or times out. + + # If get_iter() raises an exception e.g. in decoder.decode(), + # get_in_progress remains set and the connection becomes unusable. + + # Yield the first frame. + frame = self.get_next_frame() + with self.mutex: + self.maybe_resume() + assert frame.opcode is OP_TEXT or frame.opcode is OP_BINARY + if decode is None: + decode = frame.opcode is OP_TEXT + if decode: + decoder = UTF8Decoder() + yield decoder.decode(frame.data, frame.fin) + else: + # Convert to bytes when frame.data is a bytearray. + yield bytes(frame.data) + + # Yield subsequent frames for fragmented messages. + while not frame.fin: + frame = self.get_next_frame() + with self.mutex: + self.maybe_resume() + assert frame.opcode is OP_CONT + if decode: + yield decoder.decode(frame.data, frame.fin) + else: + # Convert to bytes when frame.data is a bytearray. + yield bytes(frame.data) + + self.get_in_progress = False + + def put(self, frame: Frame) -> None: + """ + Add ``frame`` to the next message. + + Raises: + EOFError: If the stream of frames has ended. + + """ + with self.mutex: + if self.closed: + raise EOFError("stream of frames ended") + + self.frames.put(frame) + self.maybe_pause() + + # put() and get/get_iter() call maybe_pause() and maybe_resume() while + # holding self.mutex. This guarantees that the calls interleave properly. + # Specifically, it prevents a race condition where maybe_resume() would + # run before maybe_pause(), leaving the connection incorrectly paused. + + # A race condition is possible when get/get_iter() call self.frames.get() + # without holding self.mutex. However, it's harmless — and even beneficial! + # It can only result in popping an item from the queue before maybe_resume() + # runs and skipping a pause() - resume() cycle that would otherwise occur. + + def maybe_pause(self) -> None: + """Pause the writer if queue is above the high water mark.""" + # Skip if flow control is disabled. + if self.high is None: + return + + assert self.mutex.locked() + + # Check for "> high" to support high = 0. + if self.frames.qsize() > self.high and not self.paused: + self.paused = True + self.pause() + + def maybe_resume(self) -> None: + """Resume the writer if queue is below the low water mark.""" + # Skip if flow control is disabled. + if self.low is None: + return + + assert self.mutex.locked() + + # Check for "<= low" to support low = 0. + if self.frames.qsize() <= self.low and self.paused: + self.paused = False + self.resume() + + def close(self) -> None: + """ + End the stream of frames. + + Calling :meth:`close` concurrently with :meth:`get`, :meth:`get_iter`, + or :meth:`put` is safe. They will raise :exc:`EOFError`. + + """ + with self.mutex: + if self.closed: + return + + self.closed = True + + if self.get_in_progress: + # Unblock get() or get_iter(). + self.frames.put(None) + + if self.paused: + # Unblock recv_events(). + self.paused = False + self.resume() diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/router.py b/.venv/lib/python3.14/site-packages/websockets/sync/router.py new file mode 100644 index 0000000000000000000000000000000000000000..1c35e8aaea909b3dd1a16f9346cd94f5063aa17c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/sync/router.py @@ -0,0 +1,213 @@ +from __future__ import annotations + +import http +import ssl as ssl_module +import urllib.parse +from typing import Any, Callable, Literal + +from ..http11 import Request, Response +from .server import Server, ServerConnection, serve + + +__all__ = ["route", "unix_route", "Router"] + + +try: + from werkzeug.exceptions import NotFound + from werkzeug.routing import Map, RequestRedirect + +except ImportError: + + def route( + url_map: Map, + *args: Any, + server_name: str | None = None, + ssl: ssl_module.SSLContext | Literal[True] | None = None, + create_router: type[Router] | None = None, + **kwargs: Any, + ) -> Server: + raise ImportError("route() requires werkzeug") + + def unix_route( + url_map: Map, + path: str | None = None, + **kwargs: Any, + ) -> Server: + raise ImportError("unix_route() requires werkzeug") + +else: + + def route( + url_map: Map, + *args: Any, + server_name: str | None = None, + ssl: ssl_module.SSLContext | Literal[True] | None = None, + create_router: type[Router] | None = None, + **kwargs: Any, + ) -> Server: + """ + Create a WebSocket server dispatching connections to different handlers. + + This feature requires the third-party library `werkzeug`_: + + .. code-block:: console + + $ pip install werkzeug + + .. _werkzeug: https://werkzeug.palletsprojects.com/ + + :func:`route` accepts the same arguments as + :func:`~websockets.sync.server.serve`, except as described below. + + The first argument is a :class:`werkzeug.routing.Map` that maps URL patterns + to connection handlers. In addition to the connection, handlers receive + parameters captured in the URL as keyword arguments. + + Here's an example:: + + + from websockets.sync.router import route + from werkzeug.routing import Map, Rule + + def channel_handler(websocket, channel_id): + ... + + url_map = Map([ + Rule("/channel/", endpoint=channel_handler), + ... + ]) + + with route(url_map, ...) as server: + server.serve_forever() + + Refer to the documentation of :mod:`werkzeug.routing` for details. + + If you define redirects with ``Rule(..., redirect_to=...)`` in the URL map, + when the server runs behind a reverse proxy that modifies the ``Host`` + header or terminates TLS, you need additional configuration: + + * Set ``server_name`` to the name of the server as seen by clients. When + not provided, websockets uses the value of the ``Host`` header. + + * Set ``ssl=True`` to generate ``wss://`` URIs without enabling TLS. + Under the hood, this bind the URL map with a ``url_scheme`` of + ``wss://`` instead of ``ws://``. + + There is no need to specify ``websocket=True`` in each rule. It is added + automatically. + + Args: + url_map: Mapping of URL patterns to connection handlers. + server_name: Name of the server as seen by clients. If :obj:`None`, + websockets uses the value of the ``Host`` header. + ssl: Configuration for enabling TLS on the connection. Set it to + :obj:`True` if a reverse proxy terminates TLS connections. + create_router: Factory for the :class:`Router` dispatching requests to + handlers. Set it to a wrapper or a subclass to customize routing. + + """ + url_scheme = "ws" if ssl is None else "wss" + if ssl is not True and ssl is not None: + kwargs["ssl"] = ssl + + if create_router is None: + create_router = Router + + router = create_router(url_map, server_name, url_scheme) + + _process_request: ( + Callable[ + [ServerConnection, Request], + Response | None, + ] + | None + ) = kwargs.pop("process_request", None) + if _process_request is None: + process_request: Callable[ + [ServerConnection, Request], + Response | None, + ] = router.route_request + else: + + def process_request( + connection: ServerConnection, request: Request + ) -> Response | None: + response = _process_request(connection, request) + if response is not None: + return response + return router.route_request(connection, request) + + return serve(router.handler, *args, process_request=process_request, **kwargs) + + def unix_route( + url_map: Map, + path: str | None = None, + **kwargs: Any, + ) -> Server: + """ + Create a WebSocket Unix server dispatching connections to different handlers. + + :func:`unix_route` combines the behaviors of :func:`route` and + :func:`~websockets.sync.server.unix_serve`. + + Args: + url_map: Mapping of URL patterns to connection handlers. + path: File system path to the Unix socket. + + """ + return route(url_map, unix=True, path=path, **kwargs) + + +class Router: + """WebSocket router supporting :func:`route`.""" + + def __init__( + self, + url_map: Map, + server_name: str | None = None, + url_scheme: str = "ws", + ) -> None: + self.url_map = url_map + self.server_name = server_name + self.url_scheme = url_scheme + for rule in self.url_map.iter_rules(): + rule.websocket = True + + def get_server_name(self, connection: ServerConnection, request: Request) -> str: + if self.server_name is None: + return request.headers["Host"] + else: + return self.server_name + + def redirect(self, connection: ServerConnection, url: str) -> Response: + response = connection.respond(http.HTTPStatus.FOUND, f"Found at {url}") + response.headers["Location"] = url + return response + + def not_found(self, connection: ServerConnection) -> Response: + return connection.respond(http.HTTPStatus.NOT_FOUND, "Not Found") + + def route_request( + self, connection: ServerConnection, request: Request + ) -> Response | None: + """Route incoming request.""" + url_map_adapter = self.url_map.bind( + server_name=self.get_server_name(connection, request), + url_scheme=self.url_scheme, + ) + try: + parsed = urllib.parse.urlparse(request.path) + handler, kwargs = url_map_adapter.match( + path_info=parsed.path, + query_args=parsed.query, + ) + except RequestRedirect as redirect: + return self.redirect(connection, redirect.new_url) + except NotFound: + return self.not_found(connection) + connection.handler, connection.handler_kwargs = handler, kwargs + return None + + def handler(self, connection: ServerConnection) -> None: + """Handle a connection.""" + return connection.handler(connection, **connection.handler_kwargs) diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/server.py b/.venv/lib/python3.14/site-packages/websockets/sync/server.py new file mode 100644 index 0000000000000000000000000000000000000000..ffd82fbad36e10e2f7a6a96cbcfba432b7ee780c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/sync/server.py @@ -0,0 +1,765 @@ +from __future__ import annotations + +import hmac +import http +import logging +import os +import re +import selectors +import socket +import ssl as ssl_module +import sys +import threading +import warnings +from collections.abc import Iterable, Sequence +from types import TracebackType +from typing import Any, Callable, Mapping, cast + +from ..exceptions import InvalidHeader +from ..extensions.base import ServerExtensionFactory +from ..extensions.permessage_deflate import enable_server_permessage_deflate +from ..frames import CloseCode +from ..headers import ( + build_www_authenticate_basic, + parse_authorization_basic, + validate_subprotocols, +) +from ..http11 import SERVER, Request, Response +from ..protocol import CONNECTING, OPEN, Event +from ..server import ServerProtocol +from ..typing import LoggerLike, Origin, StatusLike, Subprotocol +from .connection import Connection +from .utils import Deadline + + +__all__ = ["serve", "unix_serve", "ServerConnection", "Server", "basic_auth"] + + +class ServerConnection(Connection): + """ + :mod:`threading` implementation of a WebSocket server connection. + + :class:`ServerConnection` provides :meth:`recv` and :meth:`send` methods for + receiving and sending messages. + + It supports iteration to receive messages:: + + for message in websocket: + process(message) + + The iterator exits normally when the connection is closed with code + 1000 (OK) or 1001 (going away) or without a close code. It raises a + :exc:`~websockets.exceptions.ConnectionClosedError` when the connection is + closed with any other code. + + The ``ping_interval``, ``ping_timeout``, ``close_timeout``, and + ``max_queue`` arguments have the same meaning as in :func:`serve`. + + Args: + socket: Socket connected to a WebSocket client. + protocol: Sans-I/O connection. + + """ + + def __init__( + self, + socket: socket.socket, + protocol: ServerProtocol, + *, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + max_queue: int | None | tuple[int | None, int | None] = 16, + ) -> None: + self.protocol: ServerProtocol + self.request_rcvd = threading.Event() + super().__init__( + socket, + protocol, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_queue=max_queue, + ) + self.username: str # see basic_auth() + self.handler: Callable[[ServerConnection], None] # see route() + self.handler_kwargs: Mapping[str, Any] # see route() + + def respond(self, status: StatusLike, text: str) -> Response: + """ + Create a plain text HTTP response. + + ``process_request`` and ``process_response`` may call this method to + return an HTTP response instead of performing the WebSocket opening + handshake. + + You can modify the response before returning it, for example by changing + HTTP headers. + + Args: + status: HTTP status code. + text: HTTP response body; it will be encoded to UTF-8. + + Returns: + HTTP response to send to the client. + + """ + return self.protocol.reject(status, text) + + def handshake( + self, + process_request: ( + Callable[ + [ServerConnection, Request], + Response | None, + ] + | None + ) = None, + process_response: ( + Callable[ + [ServerConnection, Request, Response], + Response | None, + ] + | None + ) = None, + server_header: str | None = SERVER, + timeout: float | None = None, + ) -> None: + """ + Perform the opening handshake. + + """ + if not self.request_rcvd.wait(timeout): + raise TimeoutError("timed out while waiting for handshake request") + + if self.request is not None: + with self.send_context(expected_state=CONNECTING): + response = None + + if process_request is not None: + try: + response = process_request(self, self.request) + except Exception as exc: + self.protocol.handshake_exc = exc + response = self.protocol.reject( + http.HTTPStatus.INTERNAL_SERVER_ERROR, + ( + "Failed to open a WebSocket connection.\n" + "See server log for more information.\n" + ), + ) + + if response is None: + self.response = self.protocol.accept(self.request) + else: + self.response = response + + if server_header: + self.response.headers["Server"] = server_header + + response = None + + if process_response is not None: + try: + response = process_response(self, self.request, self.response) + except Exception as exc: + self.protocol.handshake_exc = exc + response = self.protocol.reject( + http.HTTPStatus.INTERNAL_SERVER_ERROR, + ( + "Failed to open a WebSocket connection.\n" + "See server log for more information.\n" + ), + ) + + if response is not None: + self.response = response + + self.protocol.send_response(self.response) + + # self.protocol.handshake_exc is set when the connection is lost before + # receiving a request, when the request cannot be parsed, or when the + # handshake fails, including when process_request or process_response + # raises an exception. + + # It isn't set when process_request or process_response sends an HTTP + # response that rejects the handshake. + + if self.protocol.handshake_exc is not None: + raise self.protocol.handshake_exc + + def process_event(self, event: Event) -> None: + """ + Process one incoming event. + + """ + # First event - handshake request. + if self.request is None: + assert isinstance(event, Request) + self.request = event + self.request_rcvd.set() + # Later events - frames. + else: + super().process_event(event) + + def recv_events(self) -> None: + """ + Read incoming data from the socket and process events. + + """ + try: + super().recv_events() + finally: + # If the connection is closed during the handshake, unblock it. + self.request_rcvd.set() + + +class Server: + """ + WebSocket server returned by :func:`serve`. + + This class mirrors the API of :class:`~socketserver.BaseServer`, notably the + :meth:`~socketserver.BaseServer.serve_forever` and + :meth:`~socketserver.BaseServer.shutdown` methods, as well as the context + manager protocol. + + Args: + socket: Server socket listening for new connections. + handler: Handler for one connection. Receives the socket and address + returned by :meth:`~socket.socket.accept`. + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.server")``. + See the :doc:`logging guide <../../topics/logging>` for details. + + """ + + def __init__( + self, + socket: socket.socket, + handler: Callable[[socket.socket, Any], None], + logger: LoggerLike | None = None, + ) -> None: + self.socket = socket + self.handler = handler + if logger is None: + logger = logging.getLogger("websockets.server") + self.logger = logger + if sys.platform != "win32": + self.shutdown_watcher, self.shutdown_notifier = os.pipe() + + def serve_forever(self) -> None: + """ + See :meth:`socketserver.BaseServer.serve_forever`. + + This method doesn't return. Calling :meth:`shutdown` from another thread + stops the server. + + Typical use:: + + with serve(...) as server: + server.serve_forever() + + """ + poller = selectors.DefaultSelector() + try: + poller.register(self.socket, selectors.EVENT_READ) + except ValueError: # pragma: no cover + # If shutdown() is called before poller.register(), + # the socket is closed and poller.register() raises + # ValueError: Invalid file descriptor: -1 + return + if sys.platform != "win32": + poller.register(self.shutdown_watcher, selectors.EVENT_READ) + + while True: + poller.select() + try: + # If the socket is closed, this will raise an exception and exit + # the loop. So we don't need to check the return value of select(). + sock, addr = self.socket.accept() + except OSError: + break + # Since there isn't a mechanism for tracking connections and waiting + # for them to terminate, we cannot use daemon threads, or else all + # connections would be terminate brutally when closing the server. + thread = threading.Thread(target=self.handler, args=(sock, addr)) + thread.start() + + def shutdown(self) -> None: + """ + See :meth:`socketserver.BaseServer.shutdown`. + + """ + self.socket.close() + if sys.platform != "win32": + os.write(self.shutdown_notifier, b"x") + + def fileno(self) -> int: + """ + See :meth:`socketserver.BaseServer.fileno`. + + """ + return self.socket.fileno() + + def __enter__(self) -> Server: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + self.shutdown() + + +def __getattr__(name: str) -> Any: + if name == "WebSocketServer": + warnings.warn( # deprecated in 13.0 - 2024-08-20 + "WebSocketServer was renamed to Server", + DeprecationWarning, + ) + return Server + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +def serve( + handler: Callable[[ServerConnection], None], + host: str | None = None, + port: int | None = None, + *, + # TCP/TLS + sock: socket.socket | None = None, + ssl: ssl_module.SSLContext | None = None, + # WebSocket + origins: Sequence[Origin | re.Pattern[str] | None] | None = None, + extensions: Sequence[ServerExtensionFactory] | None = None, + subprotocols: Sequence[Subprotocol] | None = None, + select_subprotocol: ( + Callable[ + [ServerConnection, Sequence[Subprotocol]], + Subprotocol | None, + ] + | None + ) = None, + compression: str | None = "deflate", + # HTTP + process_request: ( + Callable[ + [ServerConnection, Request], + Response | None, + ] + | None + ) = None, + process_response: ( + Callable[ + [ServerConnection, Request, Response], + Response | None, + ] + | None + ) = None, + server_header: str | None = SERVER, + # Timeouts + open_timeout: float | None = 10, + ping_interval: float | None = 20, + ping_timeout: float | None = 20, + close_timeout: float | None = 10, + # Limits + max_size: int | None | tuple[int | None, int | None] = 2**20, + max_queue: int | None | tuple[int | None, int | None] = 16, + # Logging + logger: LoggerLike | None = None, + # Escape hatch for advanced customization + create_connection: type[ServerConnection] | None = None, + **kwargs: Any, +) -> Server: + """ + Create a WebSocket server listening on ``host`` and ``port``. + + Whenever a client connects, the server creates a :class:`ServerConnection`, + performs the opening handshake, and delegates to the ``handler``. + + The handler receives the :class:`ServerConnection` instance, which you can + use to send and receive messages. + + Once the handler completes, either normally or with an exception, the server + performs the closing handshake and closes the connection. + + This function returns a :class:`Server` whose API mirrors + :class:`~socketserver.BaseServer`. Treat it as a context manager to ensure + that it will be closed and call :meth:`~Server.serve_forever` to serve + requests:: + + from websockets.sync.server import serve + + def handler(websocket): + ... + + with serve(handler, ...) as server: + server.serve_forever() + + Args: + handler: Connection handler. It receives the WebSocket connection, + which is a :class:`ServerConnection`, in argument. + host: Network interfaces the server binds to. + See :func:`~socket.create_server` for details. + port: TCP port the server listens on. + See :func:`~socket.create_server` for details. + sock: Preexisting TCP socket. ``sock`` replaces ``host`` and ``port``. + You may call :func:`socket.create_server` to create a suitable TCP + socket. + ssl: Configuration for enabling TLS on the connection. + origins: Acceptable values of the ``Origin`` header, for defending + against Cross-Site WebSocket Hijacking attacks. Values can be + :class:`str` to test for an exact match or regular expressions + compiled by :func:`re.compile` to test against a pattern. Include + :obj:`None` in the list if the lack of an origin is acceptable. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + select_subprotocol: Callback for selecting a subprotocol among + those supported by the client and the server. It receives a + :class:`ServerConnection` (not a + :class:`~websockets.server.ServerProtocol`!) instance and a list of + subprotocols offered by the client. Other than the first argument, + it has the same behavior as the + :meth:`ServerProtocol.select_subprotocol + ` method. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + process_request: Intercept the request during the opening handshake. + Return an HTTP response to force the response. Return :obj:`None` to + continue normally. When you force an HTTP 101 Continue response, the + handshake is successful. Else, the connection is aborted. + process_response: Intercept the response during the opening handshake. + Modify the response or return a new HTTP response to force the + response. Return :obj:`None` to continue normally. When you force an + HTTP 101 Continue response, the handshake is successful. Else, the + connection is aborted. + server_header: Value of the ``Server`` response header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. Setting it to + :obj:`None` removes the header. + open_timeout: Timeout for opening connections in seconds. + :obj:`None` disables the timeout. + ping_interval: Interval between keepalive pings in seconds. + :obj:`None` disables keepalive. + ping_timeout: Timeout for keepalive pings in seconds. + :obj:`None` disables timeouts. + close_timeout: Timeout for closing connections in seconds. + :obj:`None` disables the timeout. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. You may pass a ``(max_message_size, + max_fragment_size)`` tuple to set different limits for messages and + fragments when you expect long messages sent in short fragments. + max_queue: High-water mark of the buffer where frames are received. + It defaults to 16 frames. The low-water mark defaults to ``max_queue + // 4``. You may pass a ``(high, low)`` tuple to set the high-water + and low-water marks. If you want to disable flow control entirely, + you may set it to ``None``, although that's a bad idea. + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.server")``. See the + :doc:`logging guide <../../topics/logging>` for details. + create_connection: Factory for the :class:`ServerConnection` managing + the connection. Set it to a wrapper or a subclass to customize + connection handling. + + Any other keyword arguments are passed to :func:`~socket.create_server`. + + """ + + # Process parameters + + # Backwards compatibility: ssl used to be called ssl_context. + if ssl is None and "ssl_context" in kwargs: + ssl = kwargs.pop("ssl_context") + warnings.warn( # deprecated in 13.0 - 2024-08-20 + "ssl_context was renamed to ssl", + DeprecationWarning, + ) + + if subprotocols is not None: + validate_subprotocols(subprotocols) + + if compression == "deflate": + extensions = enable_server_permessage_deflate(extensions) + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + if create_connection is None: + create_connection = ServerConnection + + # Bind socket and listen + + # Private APIs for unix_connect() + unix: bool = kwargs.pop("unix", False) + path: str | None = kwargs.pop("path", None) + + if sock is None: + if unix: + if path is None: + raise ValueError("missing path argument") + kwargs.setdefault("family", socket.AF_UNIX) + sock = socket.create_server(path, **kwargs) + else: + sock = socket.create_server((host, port), **kwargs) + else: + if path is not None: + raise ValueError("path and sock arguments are incompatible") + + # Initialize TLS wrapper + + if ssl is not None: + sock = ssl.wrap_socket( + sock, + server_side=True, + # Delay TLS handshake until after we set a timeout on the socket. + do_handshake_on_connect=False, + ) + + # Define request handler + + def conn_handler(sock: socket.socket, addr: Any) -> None: + # Calculate timeouts on the TLS and WebSocket handshakes. + # The TLS timeout must be set on the socket, then removed + # to avoid conflicting with the WebSocket timeout in handshake(). + deadline = Deadline(open_timeout) + + try: + # Disable Nagle algorithm + + if not unix: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) + + # Perform TLS handshake + + if ssl is not None: + sock.settimeout(deadline.timeout()) + # mypy cannot figure this out + assert isinstance(sock, ssl_module.SSLSocket) + sock.do_handshake() + sock.settimeout(None) + + # Create a closure to give select_subprotocol access to connection. + protocol_select_subprotocol: ( + Callable[ + [ServerProtocol, Sequence[Subprotocol]], + Subprotocol | None, + ] + | None + ) = None + if select_subprotocol is not None: + + def protocol_select_subprotocol( + protocol: ServerProtocol, + subprotocols: Sequence[Subprotocol], + ) -> Subprotocol | None: + # mypy doesn't know that select_subprotocol is immutable. + assert select_subprotocol is not None + # Ensure this function is only used in the intended context. + assert protocol is connection.protocol + return select_subprotocol(connection, subprotocols) + + # Initialize WebSocket protocol + + protocol = ServerProtocol( + origins=origins, + extensions=extensions, + subprotocols=subprotocols, + select_subprotocol=protocol_select_subprotocol, + max_size=max_size, + logger=logger, + ) + + # Initialize WebSocket connection + + assert create_connection is not None # help mypy + connection = create_connection( + sock, + protocol, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_queue=max_queue, + ) + except Exception: + sock.close() + return + + try: + try: + connection.handshake( + process_request, + process_response, + server_header, + deadline.timeout(), + ) + except TimeoutError: + connection.close_socket() + connection.recv_events_thread.join() + return + except Exception: + connection.logger.error("opening handshake failed", exc_info=True) + connection.close_socket() + connection.recv_events_thread.join() + return + + assert connection.protocol.state is OPEN + try: + connection.start_keepalive() + handler(connection) + except Exception: + connection.logger.error("connection handler failed", exc_info=True) + connection.close(CloseCode.INTERNAL_ERROR) + else: + connection.close() + + except Exception: # pragma: no cover + # Don't leak sockets on unexpected errors. + sock.close() + + # Initialize server + + return Server(sock, conn_handler, logger) + + +def unix_serve( + handler: Callable[[ServerConnection], None], + path: str | None = None, + **kwargs: Any, +) -> Server: + """ + Create a WebSocket server listening on a Unix socket. + + This function accepts the same keyword arguments as :func:`serve`. + + It's only available on Unix. + + It's useful for deploying a server behind a reverse proxy such as nginx. + + Args: + handler: Connection handler. It receives the WebSocket connection, + which is a :class:`ServerConnection`, in argument. + path: File system path to the Unix socket. + + """ + return serve(handler, unix=True, path=path, **kwargs) + + +def is_credentials(credentials: Any) -> bool: + try: + username, password = credentials + except (TypeError, ValueError): + return False + else: + return isinstance(username, str) and isinstance(password, str) + + +def basic_auth( + realm: str = "", + credentials: tuple[str, str] | Iterable[tuple[str, str]] | None = None, + check_credentials: Callable[[str, str], bool] | None = None, +) -> Callable[[ServerConnection, Request], Response | None]: + """ + Factory for ``process_request`` to enforce HTTP Basic Authentication. + + :func:`basic_auth` is designed to integrate with :func:`serve` as follows:: + + from websockets.sync.server import basic_auth, serve + + with serve( + ..., + process_request=basic_auth( + realm="my dev server", + credentials=("hello", "iloveyou"), + ), + ): + + If authentication succeeds, the connection's ``username`` attribute is set. + If it fails, the server responds with an HTTP 401 Unauthorized status. + + One of ``credentials`` or ``check_credentials`` must be provided; not both. + + Args: + realm: Scope of protection. It should contain only ASCII characters + because the encoding of non-ASCII characters is undefined. Refer to + section 2.2 of :rfc:`7235` for details. + credentials: Hard coded authorized credentials. It can be a + ``(username, password)`` pair or a list of such pairs. + check_credentials: Function that verifies credentials. + It receives ``username`` and ``password`` arguments and returns + whether they're valid. + Raises: + TypeError: If ``credentials`` or ``check_credentials`` is wrong. + ValueError: If ``credentials`` and ``check_credentials`` are both + provided or both not provided. + + """ + if (credentials is None) == (check_credentials is None): + raise ValueError("provide either credentials or check_credentials") + + if credentials is not None: + if is_credentials(credentials): + credentials_list = [cast(tuple[str, str], credentials)] + elif isinstance(credentials, Iterable): + credentials_list = list(cast(Iterable[tuple[str, str]], credentials)) + if not all(is_credentials(item) for item in credentials_list): + raise TypeError(f"invalid credentials argument: {credentials}") + else: + raise TypeError(f"invalid credentials argument: {credentials}") + + credentials_dict = dict(credentials_list) + + def check_credentials(username: str, password: str) -> bool: + try: + expected_password = credentials_dict[username] + except KeyError: + return False + return hmac.compare_digest(expected_password, password) + + assert check_credentials is not None # help mypy + + def process_request( + connection: ServerConnection, + request: Request, + ) -> Response | None: + """ + Perform HTTP Basic Authentication. + + If it succeeds, set the connection's ``username`` attribute and return + :obj:`None`. If it fails, return an HTTP 401 Unauthorized responss. + + """ + try: + authorization = request.headers["Authorization"] + except KeyError: + response = connection.respond( + http.HTTPStatus.UNAUTHORIZED, + "Missing credentials\n", + ) + response.headers["WWW-Authenticate"] = build_www_authenticate_basic(realm) + return response + + try: + username, password = parse_authorization_basic(authorization) + except InvalidHeader: + response = connection.respond( + http.HTTPStatus.UNAUTHORIZED, + "Unsupported credentials\n", + ) + response.headers["WWW-Authenticate"] = build_www_authenticate_basic(realm) + return response + + if not check_credentials(username, password): + response = connection.respond( + http.HTTPStatus.UNAUTHORIZED, + "Invalid credentials\n", + ) + response.headers["WWW-Authenticate"] = build_www_authenticate_basic(realm) + return response + + connection.username = username + return None + + return process_request diff --git a/.venv/lib/python3.14/site-packages/websockets/sync/utils.py b/.venv/lib/python3.14/site-packages/websockets/sync/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..00bce2cc6bb19fa280a6ef2b3481403e6f6ba74f --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/sync/utils.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import time + + +__all__ = ["Deadline"] + + +class Deadline: + """ + Manage timeouts across multiple steps. + + Args: + timeout: Time available in seconds or :obj:`None` if there is no limit. + + """ + + def __init__(self, timeout: float | None) -> None: + self.deadline: float | None + if timeout is None: + self.deadline = None + else: + self.deadline = time.monotonic() + timeout + + def timeout(self, *, raise_if_elapsed: bool = True) -> float | None: + """ + Calculate a timeout from a deadline. + + Args: + raise_if_elapsed: Whether to raise :exc:`TimeoutError` + if the deadline lapsed. + + Raises: + TimeoutError: If the deadline lapsed. + + Returns: + Time left in seconds or :obj:`None` if there is no limit. + + """ + if self.deadline is None: + return None + timeout = self.deadline - time.monotonic() + if raise_if_elapsed and timeout <= 0: + raise TimeoutError("timed out") + return timeout diff --git a/.venv/lib/python3.14/site-packages/websockets/typing.py b/.venv/lib/python3.14/site-packages/websockets/typing.py new file mode 100644 index 0000000000000000000000000000000000000000..69b1a8d372837b0a297177f8353a335375cfbd49 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/typing.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import http +import logging +from typing import TYPE_CHECKING, Any, NewType, Sequence + + +__all__ = [ + "Data", + "LoggerLike", + "StatusLike", + "Origin", + "Subprotocol", + "ExtensionName", + "ExtensionParameter", +] + + +# Public types used in the signature of public APIs + +Data = str | bytes +"""Types supported in a WebSocket message: +:class:`str` for a Text_ frame, :class:`bytes` for a Binary_ frame. + +.. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 +.. _Binary : https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + +""" + +BytesLike = bytes | bytearray | memoryview +"""Types accepted where :class:`bytes` is expected.""" + +DataLike = str | bytes | bytearray | memoryview +"""Types accepted where :class:`Data` is expected.""" + +if TYPE_CHECKING: + LoggerLike = logging.Logger | logging.LoggerAdapter[Any] + """Types accepted where a :class:`~logging.Logger` is expected.""" +else: # remove this branch when dropping support for Python < 3.11 + LoggerLike = logging.Logger | logging.LoggerAdapter + """Types accepted where a :class:`~logging.Logger` is expected.""" + + +StatusLike = http.HTTPStatus | int +""" +Types accepted where an :class:`~http.HTTPStatus` is expected.""" + + +Origin = NewType("Origin", str) +"""Value of a ``Origin`` header.""" + + +Subprotocol = NewType("Subprotocol", str) +"""Subprotocol in a ``Sec-WebSocket-Protocol`` header.""" + + +ExtensionName = NewType("ExtensionName", str) +"""Name of a WebSocket extension.""" + +ExtensionParameter = tuple[str, str | None] +"""Parameter of a WebSocket extension.""" + + +# Private types + +ExtensionHeader = tuple[ExtensionName, Sequence[ExtensionParameter]] +"""Extension in a ``Sec-WebSocket-Extensions`` header.""" + + +ConnectionOption = NewType("ConnectionOption", str) +"""Connection option in a ``Connection`` header.""" + + +UpgradeProtocol = NewType("UpgradeProtocol", str) +"""Upgrade protocol in an ``Upgrade`` header.""" diff --git a/.venv/lib/python3.14/site-packages/websockets/uri.py b/.venv/lib/python3.14/site-packages/websockets/uri.py new file mode 100644 index 0000000000000000000000000000000000000000..f85e16810b8e43bbd348871ef661b2644ea34240 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/uri.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import dataclasses +import urllib.parse + +from .exceptions import InvalidURI + + +__all__ = ["parse_uri", "WebSocketURI"] + + +# All characters from the gen-delims and sub-delims sets in RFC 3987. +DELIMS = ":/?#[]@!$&'()*+,;=" + + +@dataclasses.dataclass +class WebSocketURI: + """ + WebSocket URI. + + Attributes: + secure: :obj:`True` for a ``wss`` URI, :obj:`False` for a ``ws`` URI. + host: Normalized to lower case. + port: Always set even if it's the default. + path: May be empty. + query: May be empty if the URI doesn't include a query component. + username: Available when the URI contains `User Information`_. + password: Available when the URI contains `User Information`_. + + .. _User Information: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 + + """ + + secure: bool + host: str + port: int + path: str + query: str + username: str | None = None + password: str | None = None + + @property + def resource_name(self) -> str: + if self.path: + resource_name = self.path + else: + resource_name = "/" + if self.query: + resource_name += "?" + self.query + return resource_name + + @property + def user_info(self) -> tuple[str, str] | None: + if self.username is None: + return None + assert self.password is not None + return (self.username, self.password) + + +def parse_uri(uri: str) -> WebSocketURI: + """ + Parse and validate a WebSocket URI. + + Args: + uri: WebSocket URI. + + Returns: + Parsed WebSocket URI. + + Raises: + InvalidURI: If ``uri`` isn't a valid WebSocket URI. + + """ + parsed = urllib.parse.urlparse(uri) + if parsed.scheme not in ["ws", "wss"]: + raise InvalidURI(uri, "scheme isn't ws or wss") + if parsed.hostname is None: + raise InvalidURI(uri, "hostname isn't provided") + if parsed.fragment != "": + raise InvalidURI(uri, "fragment identifier is meaningless") + + secure = parsed.scheme == "wss" + host = parsed.hostname + port = parsed.port or (443 if secure else 80) + path = parsed.path + query = parsed.query + username = parsed.username + password = parsed.password + # urllib.parse.urlparse accepts URLs with a username but without a + # password. This doesn't make sense for HTTP Basic Auth credentials. + if username is not None and password is None: + raise InvalidURI(uri, "username provided without password") + + try: + uri.encode("ascii") + except UnicodeEncodeError: + # Input contains non-ASCII characters. + # It must be an IRI. Convert it to a URI. + host = host.encode("idna").decode() + path = urllib.parse.quote(path, safe=DELIMS) + query = urllib.parse.quote(query, safe=DELIMS) + if username is not None: + assert password is not None + username = urllib.parse.quote(username, safe=DELIMS) + password = urllib.parse.quote(password, safe=DELIMS) + + return WebSocketURI(secure, host, port, path, query, username, password) diff --git a/.venv/lib/python3.14/site-packages/websockets/utils.py b/.venv/lib/python3.14/site-packages/websockets/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..b2a90e52b5a88e2a086fdff088ace214fdf23bb9 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/utils.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import base64 +import hashlib +import secrets +import sys + +from .typing import BytesLike + + +__all__ = ["accept_key", "apply_mask"] + + +GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + +def generate_key() -> str: + """ + Generate a random key for the Sec-WebSocket-Key header. + + """ + key = secrets.token_bytes(16) + return base64.b64encode(key).decode() + + +def accept_key(key: str) -> str: + """ + Compute the value of the Sec-WebSocket-Accept header. + + Args: + key: Value of the Sec-WebSocket-Key header. + + """ + sha1 = hashlib.sha1((key + GUID).encode()).digest() + return base64.b64encode(sha1).decode() + + +def apply_mask(data: BytesLike, mask: bytes | bytearray) -> bytes: + """ + Apply masking to the data of a WebSocket message. + + Args: + data: Data to mask. + mask: 4-bytes mask. + + """ + if len(mask) != 4: + raise ValueError("mask must contain 4 bytes") + + data_int = int.from_bytes(data, sys.byteorder) + mask_repeated = mask * (len(data) // 4) + mask[: len(data) % 4] + mask_int = int.from_bytes(mask_repeated, sys.byteorder) + return (data_int ^ mask_int).to_bytes(len(data), sys.byteorder) diff --git a/.venv/lib/python3.14/site-packages/websockets/version.py b/.venv/lib/python3.14/site-packages/websockets/version.py new file mode 100644 index 0000000000000000000000000000000000000000..dde52b6b0b1db8c0c3763ff537c23b7ce718b60c --- /dev/null +++ b/.venv/lib/python3.14/site-packages/websockets/version.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +import importlib.metadata + + +__all__ = ["tag", "version", "commit"] + + +# ========= =========== =================== +# release development +# ========= =========== =================== +# tag X.Y X.Y (upcoming) +# version X.Y X.Y.dev1+g5678cde +# commit X.Y 5678cde +# ========= =========== =================== + + +# When tagging a release, set `released = True`. +# After tagging a release, set `released = False` and increment `tag`. + +released = True + +tag = version = commit = "16.0" + + +if not released: # pragma: no cover + import pathlib + import re + import subprocess + + def get_version(tag: str) -> str: + # Since setup.py executes the contents of src/websockets/version.py, + # __file__ can point to either of these two files. + file_path = pathlib.Path(__file__) + root_dir = file_path.parents[0 if file_path.name == "setup.py" else 2] + + # Read version from package metadata if it is installed. + try: + version = importlib.metadata.version("websockets") + except ImportError: + pass + else: + # Check that this file belongs to the installed package. + files = importlib.metadata.files("websockets") + if files: + version_files = [f for f in files if f.name == file_path.name] + if version_files: + version_file = version_files[0] + if version_file.locate() == file_path: + return version + + # Read version from git if available. + try: + description = subprocess.run( + ["git", "describe", "--dirty", "--tags", "--long"], + capture_output=True, + cwd=root_dir, + timeout=1, + check=True, + text=True, + ).stdout.strip() + # subprocess.run raises FileNotFoundError if git isn't on $PATH. + except ( + FileNotFoundError, + subprocess.CalledProcessError, + subprocess.TimeoutExpired, + ): + pass + else: + description_re = r"[0-9.]+-([0-9]+)-(g[0-9a-f]{7,}(?:-dirty)?)" + match = re.fullmatch(description_re, description) + if match is None: + raise ValueError(f"Unexpected git description: {description}") + distance, remainder = match.groups() + remainder = remainder.replace("-", ".") # required by PEP 440 + return f"{tag}.dev{distance}+{remainder}" + + # Avoid crashing if the development version cannot be determined. + return f"{tag}.dev0+gunknown" + + version = get_version(tag) + + def get_commit(tag: str, version: str) -> str: + # Extract commit from version, falling back to tag if not available. + version_re = r"[0-9.]+\.dev[0-9]+\+g([0-9a-f]{7,}|unknown)(?:\.dirty)?" + match = re.fullmatch(version_re, version) + if match is None: + raise ValueError(f"Unexpected version: {version}") + (commit,) = match.groups() + return tag if commit == "unknown" else commit + + commit = get_commit(tag, version) diff --git a/.venv/lib/python3.14/site-packages/yaml/__init__.py b/.venv/lib/python3.14/site-packages/yaml/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d58f0891737def7f38e5d86dde2dbf9be0c13dce --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/__init__.py @@ -0,0 +1,390 @@ + +from .error import * + +from .tokens import * +from .events import * +from .nodes import * + +from .loader import * +from .dumper import * + +__version__ = '6.0.3' +try: + from .cyaml import * + __with_libyaml__ = True +except ImportError: + __with_libyaml__ = False + +import io + +#------------------------------------------------------------------------------ +# XXX "Warnings control" is now deprecated. Leaving in the API function to not +# break code that uses it. +#------------------------------------------------------------------------------ +def warnings(settings=None): + if settings is None: + return {} + +#------------------------------------------------------------------------------ +def scan(stream, Loader=Loader): + """ + Scan a YAML stream and produce scanning tokens. + """ + loader = Loader(stream) + try: + while loader.check_token(): + yield loader.get_token() + finally: + loader.dispose() + +def parse(stream, Loader=Loader): + """ + Parse a YAML stream and produce parsing events. + """ + loader = Loader(stream) + try: + while loader.check_event(): + yield loader.get_event() + finally: + loader.dispose() + +def compose(stream, Loader=Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding representation tree. + """ + loader = Loader(stream) + try: + return loader.get_single_node() + finally: + loader.dispose() + +def compose_all(stream, Loader=Loader): + """ + Parse all YAML documents in a stream + and produce corresponding representation trees. + """ + loader = Loader(stream) + try: + while loader.check_node(): + yield loader.get_node() + finally: + loader.dispose() + +def load(stream, Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + """ + loader = Loader(stream) + try: + return loader.get_single_data() + finally: + loader.dispose() + +def load_all(stream, Loader): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + """ + loader = Loader(stream) + try: + while loader.check_data(): + yield loader.get_data() + finally: + loader.dispose() + +def full_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load(stream, FullLoader) + +def full_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load_all(stream, FullLoader) + +def safe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load(stream, SafeLoader) + +def safe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load_all(stream, SafeLoader) + +def unsafe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load(stream, UnsafeLoader) + +def unsafe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load_all(stream, UnsafeLoader) + +def emit(events, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + """ + Emit YAML parsing events into a stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + stream = io.StringIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + try: + for event in events: + dumper.emit(event) + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize_all(nodes, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + """ + Serialize a sequence of representation trees into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end) + try: + dumper.open() + for node in nodes: + dumper.serialize(node) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize(node, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a representation tree into a YAML stream. + If stream is None, return the produced string instead. + """ + return serialize_all([node], stream, Dumper=Dumper, **kwds) + +def dump_all(documents, stream=None, Dumper=Dumper, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + """ + Serialize a sequence of Python objects into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, default_style=default_style, + default_flow_style=default_flow_style, + canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys) + try: + dumper.open() + for data in documents: + dumper.represent(data) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def dump(data, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a Python object into a YAML stream. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=Dumper, **kwds) + +def safe_dump_all(documents, stream=None, **kwds): + """ + Serialize a sequence of Python objects into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all(documents, stream, Dumper=SafeDumper, **kwds) + +def safe_dump(data, stream=None, **kwds): + """ + Serialize a Python object into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=SafeDumper, **kwds) + +def add_implicit_resolver(tag, regexp, first=None, + Loader=None, Dumper=Dumper): + """ + Add an implicit scalar detector. + If an implicit scalar value matches the given regexp, + the corresponding tag is assigned to the scalar. + first is a sequence of possible initial characters or None. + """ + if Loader is None: + loader.Loader.add_implicit_resolver(tag, regexp, first) + loader.FullLoader.add_implicit_resolver(tag, regexp, first) + loader.UnsafeLoader.add_implicit_resolver(tag, regexp, first) + else: + Loader.add_implicit_resolver(tag, regexp, first) + Dumper.add_implicit_resolver(tag, regexp, first) + +def add_path_resolver(tag, path, kind=None, Loader=None, Dumper=Dumper): + """ + Add a path based resolver for the given tag. + A path is a list of keys that forms a path + to a node in the representation tree. + Keys can be string values, integers, or None. + """ + if Loader is None: + loader.Loader.add_path_resolver(tag, path, kind) + loader.FullLoader.add_path_resolver(tag, path, kind) + loader.UnsafeLoader.add_path_resolver(tag, path, kind) + else: + Loader.add_path_resolver(tag, path, kind) + Dumper.add_path_resolver(tag, path, kind) + +def add_constructor(tag, constructor, Loader=None): + """ + Add a constructor for the given tag. + Constructor is a function that accepts a Loader instance + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_constructor(tag, constructor) + loader.FullLoader.add_constructor(tag, constructor) + loader.UnsafeLoader.add_constructor(tag, constructor) + else: + Loader.add_constructor(tag, constructor) + +def add_multi_constructor(tag_prefix, multi_constructor, Loader=None): + """ + Add a multi-constructor for the given tag prefix. + Multi-constructor is called for a node if its tag starts with tag_prefix. + Multi-constructor accepts a Loader instance, a tag suffix, + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_multi_constructor(tag_prefix, multi_constructor) + loader.FullLoader.add_multi_constructor(tag_prefix, multi_constructor) + loader.UnsafeLoader.add_multi_constructor(tag_prefix, multi_constructor) + else: + Loader.add_multi_constructor(tag_prefix, multi_constructor) + +def add_representer(data_type, representer, Dumper=Dumper): + """ + Add a representer for the given type. + Representer is a function accepting a Dumper instance + and an instance of the given data type + and producing the corresponding representation node. + """ + Dumper.add_representer(data_type, representer) + +def add_multi_representer(data_type, multi_representer, Dumper=Dumper): + """ + Add a representer for the given type. + Multi-representer is a function accepting a Dumper instance + and an instance of the given data type or subtype + and producing the corresponding representation node. + """ + Dumper.add_multi_representer(data_type, multi_representer) + +class YAMLObjectMetaclass(type): + """ + The metaclass for YAMLObject. + """ + def __init__(cls, name, bases, kwds): + super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds) + if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None: + if isinstance(cls.yaml_loader, list): + for loader in cls.yaml_loader: + loader.add_constructor(cls.yaml_tag, cls.from_yaml) + else: + cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) + + cls.yaml_dumper.add_representer(cls, cls.to_yaml) + +class YAMLObject(metaclass=YAMLObjectMetaclass): + """ + An object that can dump itself to a YAML stream + and load itself from a YAML stream. + """ + + __slots__ = () # no direct instantiation, so allow immutable subclasses + + yaml_loader = [Loader, FullLoader, UnsafeLoader] + yaml_dumper = Dumper + + yaml_tag = None + yaml_flow_style = None + + @classmethod + def from_yaml(cls, loader, node): + """ + Convert a representation node to a Python object. + """ + return loader.construct_yaml_object(node, cls) + + @classmethod + def to_yaml(cls, dumper, data): + """ + Convert a Python object to a representation node. + """ + return dumper.represent_yaml_object(cls.yaml_tag, data, cls, + flow_style=cls.yaml_flow_style) + diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/__init__.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..909570f5b8d60e3108dbdfa754b0c3cf6e2e2832 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/__init__.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/composer.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/composer.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6444ff818b696f9c1e0b57b6096fa0d84f006229 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/composer.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/constructor.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/constructor.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce0237b497b05a691630eea40a30e008af437984 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/constructor.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/cyaml.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/cyaml.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2466502733dc9724ce5f10022f3c0bebb0ff2c3 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/cyaml.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/dumper.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/dumper.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be7a48156cdf8d068d7d3831862300e00f4e4af5 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/dumper.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/emitter.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/emitter.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4389c21aa12afe1e80cef21aa606dc9a14a4b41a Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/emitter.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/error.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/error.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82684ad7cd994bc3c8cbac77f9571080b77d1531 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/error.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/events.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/events.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73589236e85d7ca4b1a61eccfb209552dedf1207 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/events.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/loader.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/loader.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50c80b441237f1bcfd99420f86854fed0adbc4ad Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/loader.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/nodes.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/nodes.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8419743409d45a84468918c5f08008430fd1ab6f Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/nodes.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/parser.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/parser.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b255137cf9e7651da3f068e79d5934bcb67b51b Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/parser.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/reader.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/reader.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..719a57eab1c49a5268746ac33d1a630b48bd9b1f Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/reader.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/representer.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/representer.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4fb762c3d034abfa3db21abe29b1c01fb4aa96c Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/representer.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/resolver.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/resolver.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6eab9daf997545033df6021ad3856bee2cdc9a32 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/resolver.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/scanner.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/scanner.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b9cc45754947ae89f28d2e4c39fa5fa5474f5c6 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/scanner.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/serializer.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/serializer.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71ffed13c791b987fb781cc31e3fea20f4bd2eae Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/serializer.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/__pycache__/tokens.cpython-314.pyc b/.venv/lib/python3.14/site-packages/yaml/__pycache__/tokens.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb809addaf77d908aa780b7de78b280b342dc1f7 Binary files /dev/null and b/.venv/lib/python3.14/site-packages/yaml/__pycache__/tokens.cpython-314.pyc differ diff --git a/.venv/lib/python3.14/site-packages/yaml/_yaml.cpython-314-darwin.so b/.venv/lib/python3.14/site-packages/yaml/_yaml.cpython-314-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..6ac673ca4a58e4166c74a9fedbf1cf43a6c65616 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/_yaml.cpython-314-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35b28ba8dc677e24bcd7c1fc1706b1960f173bb97497c3573a527b45aed12107 +size 381736 diff --git a/.venv/lib/python3.14/site-packages/yaml/composer.py b/.venv/lib/python3.14/site-packages/yaml/composer.py new file mode 100644 index 0000000000000000000000000000000000000000..6d15cb40e3b4198819c91c6f8d8b32807fcf53b2 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/composer.py @@ -0,0 +1,139 @@ + +__all__ = ['Composer', 'ComposerError'] + +from .error import MarkedYAMLError +from .events import * +from .nodes import * + +class ComposerError(MarkedYAMLError): + pass + +class Composer: + + def __init__(self): + self.anchors = {} + + def check_node(self): + # Drop the STREAM-START event. + if self.check_event(StreamStartEvent): + self.get_event() + + # If there are more documents available? + return not self.check_event(StreamEndEvent) + + def get_node(self): + # Get the root node of the next document. + if not self.check_event(StreamEndEvent): + return self.compose_document() + + def get_single_node(self): + # Drop the STREAM-START event. + self.get_event() + + # Compose a document if the stream is not empty. + document = None + if not self.check_event(StreamEndEvent): + document = self.compose_document() + + # Ensure that the stream contains no more documents. + if not self.check_event(StreamEndEvent): + event = self.get_event() + raise ComposerError("expected a single document in the stream", + document.start_mark, "but found another document", + event.start_mark) + + # Drop the STREAM-END event. + self.get_event() + + return document + + def compose_document(self): + # Drop the DOCUMENT-START event. + self.get_event() + + # Compose the root node. + node = self.compose_node(None, None) + + # Drop the DOCUMENT-END event. + self.get_event() + + self.anchors = {} + return node + + def compose_node(self, parent, index): + if self.check_event(AliasEvent): + event = self.get_event() + anchor = event.anchor + if anchor not in self.anchors: + raise ComposerError(None, None, "found undefined alias %r" + % anchor, event.start_mark) + return self.anchors[anchor] + event = self.peek_event() + anchor = event.anchor + if anchor is not None: + if anchor in self.anchors: + raise ComposerError("found duplicate anchor %r; first occurrence" + % anchor, self.anchors[anchor].start_mark, + "second occurrence", event.start_mark) + self.descend_resolver(parent, index) + if self.check_event(ScalarEvent): + node = self.compose_scalar_node(anchor) + elif self.check_event(SequenceStartEvent): + node = self.compose_sequence_node(anchor) + elif self.check_event(MappingStartEvent): + node = self.compose_mapping_node(anchor) + self.ascend_resolver() + return node + + def compose_scalar_node(self, anchor): + event = self.get_event() + tag = event.tag + if tag is None or tag == '!': + tag = self.resolve(ScalarNode, event.value, event.implicit) + node = ScalarNode(tag, event.value, + event.start_mark, event.end_mark, style=event.style) + if anchor is not None: + self.anchors[anchor] = node + return node + + def compose_sequence_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(SequenceNode, None, start_event.implicit) + node = SequenceNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + index = 0 + while not self.check_event(SequenceEndEvent): + node.value.append(self.compose_node(node, index)) + index += 1 + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + + def compose_mapping_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(MappingNode, None, start_event.implicit) + node = MappingNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + while not self.check_event(MappingEndEvent): + #key_event = self.peek_event() + item_key = self.compose_node(node, None) + #if item_key in node.value: + # raise ComposerError("while composing a mapping", start_event.start_mark, + # "found duplicate key", key_event.start_mark) + item_value = self.compose_node(node, item_key) + #node.value[item_key] = item_value + node.value.append((item_key, item_value)) + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + diff --git a/.venv/lib/python3.14/site-packages/yaml/constructor.py b/.venv/lib/python3.14/site-packages/yaml/constructor.py new file mode 100644 index 0000000000000000000000000000000000000000..619acd3070a4845c653fcf22a626e05158035bc2 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/constructor.py @@ -0,0 +1,748 @@ + +__all__ = [ + 'BaseConstructor', + 'SafeConstructor', + 'FullConstructor', + 'UnsafeConstructor', + 'Constructor', + 'ConstructorError' +] + +from .error import * +from .nodes import * + +import collections.abc, datetime, base64, binascii, re, sys, types + +class ConstructorError(MarkedYAMLError): + pass + +class BaseConstructor: + + yaml_constructors = {} + yaml_multi_constructors = {} + + def __init__(self): + self.constructed_objects = {} + self.recursive_objects = {} + self.state_generators = [] + self.deep_construct = False + + def check_data(self): + # If there are more documents available? + return self.check_node() + + def check_state_key(self, key): + """Block special attributes/methods from being set in a newly created + object, to prevent user-controlled methods from being called during + deserialization""" + if self.get_state_keys_blacklist_regexp().match(key): + raise ConstructorError(None, None, + "blacklisted key '%s' in instance state found" % (key,), None) + + def get_data(self): + # Construct and return the next document. + if self.check_node(): + return self.construct_document(self.get_node()) + + def get_single_data(self): + # Ensure that the stream contains a single document and construct it. + node = self.get_single_node() + if node is not None: + return self.construct_document(node) + return None + + def construct_document(self, node): + data = self.construct_object(node) + while self.state_generators: + state_generators = self.state_generators + self.state_generators = [] + for generator in state_generators: + for dummy in generator: + pass + self.constructed_objects = {} + self.recursive_objects = {} + self.deep_construct = False + return data + + def construct_object(self, node, deep=False): + if node in self.constructed_objects: + return self.constructed_objects[node] + if deep: + old_deep = self.deep_construct + self.deep_construct = True + if node in self.recursive_objects: + raise ConstructorError(None, None, + "found unconstructable recursive node", node.start_mark) + self.recursive_objects[node] = None + constructor = None + tag_suffix = None + if node.tag in self.yaml_constructors: + constructor = self.yaml_constructors[node.tag] + else: + for tag_prefix in self.yaml_multi_constructors: + if tag_prefix is not None and node.tag.startswith(tag_prefix): + tag_suffix = node.tag[len(tag_prefix):] + constructor = self.yaml_multi_constructors[tag_prefix] + break + else: + if None in self.yaml_multi_constructors: + tag_suffix = node.tag + constructor = self.yaml_multi_constructors[None] + elif None in self.yaml_constructors: + constructor = self.yaml_constructors[None] + elif isinstance(node, ScalarNode): + constructor = self.__class__.construct_scalar + elif isinstance(node, SequenceNode): + constructor = self.__class__.construct_sequence + elif isinstance(node, MappingNode): + constructor = self.__class__.construct_mapping + if tag_suffix is None: + data = constructor(self, node) + else: + data = constructor(self, tag_suffix, node) + if isinstance(data, types.GeneratorType): + generator = data + data = next(generator) + if self.deep_construct: + for dummy in generator: + pass + else: + self.state_generators.append(generator) + self.constructed_objects[node] = data + del self.recursive_objects[node] + if deep: + self.deep_construct = old_deep + return data + + def construct_scalar(self, node): + if not isinstance(node, ScalarNode): + raise ConstructorError(None, None, + "expected a scalar node, but found %s" % node.id, + node.start_mark) + return node.value + + def construct_sequence(self, node, deep=False): + if not isinstance(node, SequenceNode): + raise ConstructorError(None, None, + "expected a sequence node, but found %s" % node.id, + node.start_mark) + return [self.construct_object(child, deep=deep) + for child in node.value] + + def construct_mapping(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + mapping = {} + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + if not isinstance(key, collections.abc.Hashable): + raise ConstructorError("while constructing a mapping", node.start_mark, + "found unhashable key", key_node.start_mark) + value = self.construct_object(value_node, deep=deep) + mapping[key] = value + return mapping + + def construct_pairs(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + pairs = [] + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + value = self.construct_object(value_node, deep=deep) + pairs.append((key, value)) + return pairs + + @classmethod + def add_constructor(cls, tag, constructor): + if not 'yaml_constructors' in cls.__dict__: + cls.yaml_constructors = cls.yaml_constructors.copy() + cls.yaml_constructors[tag] = constructor + + @classmethod + def add_multi_constructor(cls, tag_prefix, multi_constructor): + if not 'yaml_multi_constructors' in cls.__dict__: + cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy() + cls.yaml_multi_constructors[tag_prefix] = multi_constructor + +class SafeConstructor(BaseConstructor): + + def construct_scalar(self, node): + if isinstance(node, MappingNode): + for key_node, value_node in node.value: + if key_node.tag == 'tag:yaml.org,2002:value': + return self.construct_scalar(value_node) + return super().construct_scalar(node) + + def flatten_mapping(self, node): + merge = [] + index = 0 + while index < len(node.value): + key_node, value_node = node.value[index] + if key_node.tag == 'tag:yaml.org,2002:merge': + del node.value[index] + if isinstance(value_node, MappingNode): + self.flatten_mapping(value_node) + merge.extend(value_node.value) + elif isinstance(value_node, SequenceNode): + submerge = [] + for subnode in value_node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing a mapping", + node.start_mark, + "expected a mapping for merging, but found %s" + % subnode.id, subnode.start_mark) + self.flatten_mapping(subnode) + submerge.append(subnode.value) + submerge.reverse() + for value in submerge: + merge.extend(value) + else: + raise ConstructorError("while constructing a mapping", node.start_mark, + "expected a mapping or list of mappings for merging, but found %s" + % value_node.id, value_node.start_mark) + elif key_node.tag == 'tag:yaml.org,2002:value': + key_node.tag = 'tag:yaml.org,2002:str' + index += 1 + else: + index += 1 + if merge: + node.value = merge + node.value + + def construct_mapping(self, node, deep=False): + if isinstance(node, MappingNode): + self.flatten_mapping(node) + return super().construct_mapping(node, deep=deep) + + def construct_yaml_null(self, node): + self.construct_scalar(node) + return None + + bool_values = { + 'yes': True, + 'no': False, + 'true': True, + 'false': False, + 'on': True, + 'off': False, + } + + def construct_yaml_bool(self, node): + value = self.construct_scalar(node) + return self.bool_values[value.lower()] + + def construct_yaml_int(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '') + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '0': + return 0 + elif value.startswith('0b'): + return sign*int(value[2:], 2) + elif value.startswith('0x'): + return sign*int(value[2:], 16) + elif value[0] == '0': + return sign*int(value, 8) + elif ':' in value: + digits = [int(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*int(value) + + inf_value = 1e300 + while inf_value != inf_value*inf_value: + inf_value *= inf_value + nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99). + + def construct_yaml_float(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '').lower() + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '.inf': + return sign*self.inf_value + elif value == '.nan': + return self.nan_value + elif ':' in value: + digits = [float(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0.0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*float(value) + + def construct_yaml_binary(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + timestamp_regexp = re.compile( + r'''^(?P[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)?$''', re.X) + + def construct_yaml_timestamp(self, node): + value = self.construct_scalar(node) + match = self.timestamp_regexp.match(node.value) + values = match.groupdict() + year = int(values['year']) + month = int(values['month']) + day = int(values['day']) + if not values['hour']: + return datetime.date(year, month, day) + hour = int(values['hour']) + minute = int(values['minute']) + second = int(values['second']) + fraction = 0 + tzinfo = None + if values['fraction']: + fraction = values['fraction'][:6] + while len(fraction) < 6: + fraction += '0' + fraction = int(fraction) + if values['tz_sign']: + tz_hour = int(values['tz_hour']) + tz_minute = int(values['tz_minute'] or 0) + delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) + if values['tz_sign'] == '-': + delta = -delta + tzinfo = datetime.timezone(delta) + elif values['tz']: + tzinfo = datetime.timezone.utc + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=tzinfo) + + def construct_yaml_omap(self, node): + # Note: we do not check for duplicate keys, because it's too + # CPU-expensive. + omap = [] + yield omap + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + omap.append((key, value)) + + def construct_yaml_pairs(self, node): + # Note: the same code as `construct_yaml_omap`. + pairs = [] + yield pairs + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + pairs.append((key, value)) + + def construct_yaml_set(self, node): + data = set() + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_str(self, node): + return self.construct_scalar(node) + + def construct_yaml_seq(self, node): + data = [] + yield data + data.extend(self.construct_sequence(node)) + + def construct_yaml_map(self, node): + data = {} + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_object(self, node, cls): + data = cls.__new__(cls) + yield data + if hasattr(data, '__setstate__'): + state = self.construct_mapping(node, deep=True) + data.__setstate__(state) + else: + state = self.construct_mapping(node) + data.__dict__.update(state) + + def construct_undefined(self, node): + raise ConstructorError(None, None, + "could not determine a constructor for the tag %r" % node.tag, + node.start_mark) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:null', + SafeConstructor.construct_yaml_null) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:bool', + SafeConstructor.construct_yaml_bool) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:int', + SafeConstructor.construct_yaml_int) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:float', + SafeConstructor.construct_yaml_float) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:binary', + SafeConstructor.construct_yaml_binary) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:timestamp', + SafeConstructor.construct_yaml_timestamp) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:omap', + SafeConstructor.construct_yaml_omap) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:pairs', + SafeConstructor.construct_yaml_pairs) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:set', + SafeConstructor.construct_yaml_set) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:str', + SafeConstructor.construct_yaml_str) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:seq', + SafeConstructor.construct_yaml_seq) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:map', + SafeConstructor.construct_yaml_map) + +SafeConstructor.add_constructor(None, + SafeConstructor.construct_undefined) + +class FullConstructor(SafeConstructor): + # 'extend' is blacklisted because it is used by + # construct_python_object_apply to add `listitems` to a newly generate + # python instance + def get_state_keys_blacklist(self): + return ['^extend$', '^__.*__$'] + + def get_state_keys_blacklist_regexp(self): + if not hasattr(self, 'state_keys_blacklist_regexp'): + self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')') + return self.state_keys_blacklist_regexp + + def construct_python_str(self, node): + return self.construct_scalar(node) + + def construct_python_unicode(self, node): + return self.construct_scalar(node) + + def construct_python_bytes(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + def construct_python_long(self, node): + return self.construct_yaml_int(node) + + def construct_python_complex(self, node): + return complex(self.construct_scalar(node)) + + def construct_python_tuple(self, node): + return tuple(self.construct_sequence(node)) + + def find_python_module(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python module", mark, + "expected non-empty name appended to the tag", mark) + if unsafe: + try: + __import__(name) + except ImportError as exc: + raise ConstructorError("while constructing a Python module", mark, + "cannot find module %r (%s)" % (name, exc), mark) + if name not in sys.modules: + raise ConstructorError("while constructing a Python module", mark, + "module %r is not imported" % name, mark) + return sys.modules[name] + + def find_python_name(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python object", mark, + "expected non-empty name appended to the tag", mark) + if '.' in name: + module_name, object_name = name.rsplit('.', 1) + else: + module_name = 'builtins' + object_name = name + if unsafe: + try: + __import__(module_name) + except ImportError as exc: + raise ConstructorError("while constructing a Python object", mark, + "cannot find module %r (%s)" % (module_name, exc), mark) + if module_name not in sys.modules: + raise ConstructorError("while constructing a Python object", mark, + "module %r is not imported" % module_name, mark) + module = sys.modules[module_name] + if not hasattr(module, object_name): + raise ConstructorError("while constructing a Python object", mark, + "cannot find %r in the module %r" + % (object_name, module.__name__), mark) + return getattr(module, object_name) + + def construct_python_name(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python name", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_name(suffix, node.start_mark) + + def construct_python_module(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python module", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_module(suffix, node.start_mark) + + def make_python_instance(self, suffix, node, + args=None, kwds=None, newobj=False, unsafe=False): + if not args: + args = [] + if not kwds: + kwds = {} + cls = self.find_python_name(suffix, node.start_mark) + if not (unsafe or isinstance(cls, type)): + raise ConstructorError("while constructing a Python instance", node.start_mark, + "expected a class, but found %r" % type(cls), + node.start_mark) + if newobj and isinstance(cls, type): + return cls.__new__(cls, *args, **kwds) + else: + return cls(*args, **kwds) + + def set_python_instance_state(self, instance, state, unsafe=False): + if hasattr(instance, '__setstate__'): + instance.__setstate__(state) + else: + slotstate = {} + if isinstance(state, tuple) and len(state) == 2: + state, slotstate = state + if hasattr(instance, '__dict__'): + if not unsafe and state: + for key in state.keys(): + self.check_state_key(key) + instance.__dict__.update(state) + elif state: + slotstate.update(state) + for key, value in slotstate.items(): + if not unsafe: + self.check_state_key(key) + setattr(instance, key, value) + + def construct_python_object(self, suffix, node): + # Format: + # !!python/object:module.name { ... state ... } + instance = self.make_python_instance(suffix, node, newobj=True) + yield instance + deep = hasattr(instance, '__setstate__') + state = self.construct_mapping(node, deep=deep) + self.set_python_instance_state(instance, state) + + def construct_python_object_apply(self, suffix, node, newobj=False): + # Format: + # !!python/object/apply # (or !!python/object/new) + # args: [ ... arguments ... ] + # kwds: { ... keywords ... } + # state: ... state ... + # listitems: [ ... listitems ... ] + # dictitems: { ... dictitems ... } + # or short format: + # !!python/object/apply [ ... arguments ... ] + # The difference between !!python/object/apply and !!python/object/new + # is how an object is created, check make_python_instance for details. + if isinstance(node, SequenceNode): + args = self.construct_sequence(node, deep=True) + kwds = {} + state = {} + listitems = [] + dictitems = {} + else: + value = self.construct_mapping(node, deep=True) + args = value.get('args', []) + kwds = value.get('kwds', {}) + state = value.get('state', {}) + listitems = value.get('listitems', []) + dictitems = value.get('dictitems', {}) + instance = self.make_python_instance(suffix, node, args, kwds, newobj) + if state: + self.set_python_instance_state(instance, state) + if listitems: + instance.extend(listitems) + if dictitems: + for key in dictitems: + instance[key] = dictitems[key] + return instance + + def construct_python_object_new(self, suffix, node): + return self.construct_python_object_apply(suffix, node, newobj=True) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/none', + FullConstructor.construct_yaml_null) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bool', + FullConstructor.construct_yaml_bool) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/str', + FullConstructor.construct_python_str) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/unicode', + FullConstructor.construct_python_unicode) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bytes', + FullConstructor.construct_python_bytes) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/int', + FullConstructor.construct_yaml_int) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/long', + FullConstructor.construct_python_long) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/float', + FullConstructor.construct_yaml_float) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/complex', + FullConstructor.construct_python_complex) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/list', + FullConstructor.construct_yaml_seq) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/tuple', + FullConstructor.construct_python_tuple) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/dict', + FullConstructor.construct_yaml_map) + +FullConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/name:', + FullConstructor.construct_python_name) + +class UnsafeConstructor(FullConstructor): + + def find_python_module(self, name, mark): + return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True) + + def find_python_name(self, name, mark): + return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True) + + def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False): + return super(UnsafeConstructor, self).make_python_instance( + suffix, node, args, kwds, newobj, unsafe=True) + + def set_python_instance_state(self, instance, state): + return super(UnsafeConstructor, self).set_python_instance_state( + instance, state, unsafe=True) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/module:', + UnsafeConstructor.construct_python_module) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object:', + UnsafeConstructor.construct_python_object) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/new:', + UnsafeConstructor.construct_python_object_new) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/apply:', + UnsafeConstructor.construct_python_object_apply) + +# Constructor is same as UnsafeConstructor. Need to leave this in place in case +# people have extended it directly. +class Constructor(UnsafeConstructor): + pass diff --git a/.venv/lib/python3.14/site-packages/yaml/cyaml.py b/.venv/lib/python3.14/site-packages/yaml/cyaml.py new file mode 100644 index 0000000000000000000000000000000000000000..0c21345879b298bb8668201bebe7d289586b17f9 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/cyaml.py @@ -0,0 +1,101 @@ + +__all__ = [ + 'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader', + 'CBaseDumper', 'CSafeDumper', 'CDumper' +] + +from yaml._yaml import CParser, CEmitter + +from .constructor import * + +from .serializer import * +from .representer import * + +from .resolver import * + +class CBaseLoader(CParser, BaseConstructor, BaseResolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class CSafeLoader(CParser, SafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class CFullLoader(CParser, FullConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class CUnsafeLoader(CParser, UnsafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + UnsafeConstructor.__init__(self) + Resolver.__init__(self) + +class CLoader(CParser, Constructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + Constructor.__init__(self) + Resolver.__init__(self) + +class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CSafeDumper(CEmitter, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CDumper(CEmitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/.venv/lib/python3.14/site-packages/yaml/dumper.py b/.venv/lib/python3.14/site-packages/yaml/dumper.py new file mode 100644 index 0000000000000000000000000000000000000000..6aadba551f3836b02f4752277f4b3027073defad --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/dumper.py @@ -0,0 +1,62 @@ + +__all__ = ['BaseDumper', 'SafeDumper', 'Dumper'] + +from .emitter import * +from .serializer import * +from .representer import * +from .resolver import * + +class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class Dumper(Emitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/.venv/lib/python3.14/site-packages/yaml/emitter.py b/.venv/lib/python3.14/site-packages/yaml/emitter.py new file mode 100644 index 0000000000000000000000000000000000000000..a664d011162af69184df2f8e59ab7feec818f7c7 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/emitter.py @@ -0,0 +1,1137 @@ + +# Emitter expects events obeying the following grammar: +# stream ::= STREAM-START document* STREAM-END +# document ::= DOCUMENT-START node DOCUMENT-END +# node ::= SCALAR | sequence | mapping +# sequence ::= SEQUENCE-START node* SEQUENCE-END +# mapping ::= MAPPING-START (node node)* MAPPING-END + +__all__ = ['Emitter', 'EmitterError'] + +from .error import YAMLError +from .events import * + +class EmitterError(YAMLError): + pass + +class ScalarAnalysis: + def __init__(self, scalar, empty, multiline, + allow_flow_plain, allow_block_plain, + allow_single_quoted, allow_double_quoted, + allow_block): + self.scalar = scalar + self.empty = empty + self.multiline = multiline + self.allow_flow_plain = allow_flow_plain + self.allow_block_plain = allow_block_plain + self.allow_single_quoted = allow_single_quoted + self.allow_double_quoted = allow_double_quoted + self.allow_block = allow_block + +class Emitter: + + DEFAULT_TAG_PREFIXES = { + '!' : '!', + 'tag:yaml.org,2002:' : '!!', + } + + def __init__(self, stream, canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + + # The stream should have the methods `write` and possibly `flush`. + self.stream = stream + + # Encoding can be overridden by STREAM-START. + self.encoding = None + + # Emitter is a state machine with a stack of states to handle nested + # structures. + self.states = [] + self.state = self.expect_stream_start + + # Current event and the event queue. + self.events = [] + self.event = None + + # The current indentation level and the stack of previous indents. + self.indents = [] + self.indent = None + + # Flow level. + self.flow_level = 0 + + # Contexts. + self.root_context = False + self.sequence_context = False + self.mapping_context = False + self.simple_key_context = False + + # Characteristics of the last emitted character: + # - current position. + # - is it a whitespace? + # - is it an indention character + # (indentation space, '-', '?', or ':')? + self.line = 0 + self.column = 0 + self.whitespace = True + self.indention = True + + # Whether the document requires an explicit document indicator + self.open_ended = False + + # Formatting details. + self.canonical = canonical + self.allow_unicode = allow_unicode + self.best_indent = 2 + if indent and 1 < indent < 10: + self.best_indent = indent + self.best_width = 80 + if width and width > self.best_indent*2: + self.best_width = width + self.best_line_break = '\n' + if line_break in ['\r', '\n', '\r\n']: + self.best_line_break = line_break + + # Tag prefixes. + self.tag_prefixes = None + + # Prepared anchor and tag. + self.prepared_anchor = None + self.prepared_tag = None + + # Scalar analysis and style. + self.analysis = None + self.style = None + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def emit(self, event): + self.events.append(event) + while not self.need_more_events(): + self.event = self.events.pop(0) + self.state() + self.event = None + + # In some cases, we wait for a few next events before emitting. + + def need_more_events(self): + if not self.events: + return True + event = self.events[0] + if isinstance(event, DocumentStartEvent): + return self.need_events(1) + elif isinstance(event, SequenceStartEvent): + return self.need_events(2) + elif isinstance(event, MappingStartEvent): + return self.need_events(3) + else: + return False + + def need_events(self, count): + level = 0 + for event in self.events[1:]: + if isinstance(event, (DocumentStartEvent, CollectionStartEvent)): + level += 1 + elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)): + level -= 1 + elif isinstance(event, StreamEndEvent): + level = -1 + if level < 0: + return False + return (len(self.events) < count+1) + + def increase_indent(self, flow=False, indentless=False): + self.indents.append(self.indent) + if self.indent is None: + if flow: + self.indent = self.best_indent + else: + self.indent = 0 + elif not indentless: + self.indent += self.best_indent + + # States. + + # Stream handlers. + + def expect_stream_start(self): + if isinstance(self.event, StreamStartEvent): + if self.event.encoding and not hasattr(self.stream, 'encoding'): + self.encoding = self.event.encoding + self.write_stream_start() + self.state = self.expect_first_document_start + else: + raise EmitterError("expected StreamStartEvent, but got %s" + % self.event) + + def expect_nothing(self): + raise EmitterError("expected nothing, but got %s" % self.event) + + # Document handlers. + + def expect_first_document_start(self): + return self.expect_document_start(first=True) + + def expect_document_start(self, first=False): + if isinstance(self.event, DocumentStartEvent): + if (self.event.version or self.event.tags) and self.open_ended: + self.write_indicator('...', True) + self.write_indent() + if self.event.version: + version_text = self.prepare_version(self.event.version) + self.write_version_directive(version_text) + self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy() + if self.event.tags: + handles = sorted(self.event.tags.keys()) + for handle in handles: + prefix = self.event.tags[handle] + self.tag_prefixes[prefix] = handle + handle_text = self.prepare_tag_handle(handle) + prefix_text = self.prepare_tag_prefix(prefix) + self.write_tag_directive(handle_text, prefix_text) + implicit = (first and not self.event.explicit and not self.canonical + and not self.event.version and not self.event.tags + and not self.check_empty_document()) + if not implicit: + self.write_indent() + self.write_indicator('---', True) + if self.canonical: + self.write_indent() + self.state = self.expect_document_root + elif isinstance(self.event, StreamEndEvent): + if self.open_ended: + self.write_indicator('...', True) + self.write_indent() + self.write_stream_end() + self.state = self.expect_nothing + else: + raise EmitterError("expected DocumentStartEvent, but got %s" + % self.event) + + def expect_document_end(self): + if isinstance(self.event, DocumentEndEvent): + self.write_indent() + if self.event.explicit: + self.write_indicator('...', True) + self.write_indent() + self.flush_stream() + self.state = self.expect_document_start + else: + raise EmitterError("expected DocumentEndEvent, but got %s" + % self.event) + + def expect_document_root(self): + self.states.append(self.expect_document_end) + self.expect_node(root=True) + + # Node handlers. + + def expect_node(self, root=False, sequence=False, mapping=False, + simple_key=False): + self.root_context = root + self.sequence_context = sequence + self.mapping_context = mapping + self.simple_key_context = simple_key + if isinstance(self.event, AliasEvent): + self.expect_alias() + elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)): + self.process_anchor('&') + self.process_tag() + if isinstance(self.event, ScalarEvent): + self.expect_scalar() + elif isinstance(self.event, SequenceStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_sequence(): + self.expect_flow_sequence() + else: + self.expect_block_sequence() + elif isinstance(self.event, MappingStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_mapping(): + self.expect_flow_mapping() + else: + self.expect_block_mapping() + else: + raise EmitterError("expected NodeEvent, but got %s" % self.event) + + def expect_alias(self): + if self.event.anchor is None: + raise EmitterError("anchor is not specified for alias") + self.process_anchor('*') + self.state = self.states.pop() + + def expect_scalar(self): + self.increase_indent(flow=True) + self.process_scalar() + self.indent = self.indents.pop() + self.state = self.states.pop() + + # Flow sequence handlers. + + def expect_flow_sequence(self): + self.write_indicator('[', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_sequence_item + + def expect_first_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator(']', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + def expect_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator(']', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + # Flow mapping handlers. + + def expect_flow_mapping(self): + self.write_indicator('{', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_mapping_key + + def expect_first_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator('}', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator('}', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + def expect_flow_mapping_value(self): + if self.canonical or self.column > self.best_width: + self.write_indent() + self.write_indicator(':', True) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + # Block sequence handlers. + + def expect_block_sequence(self): + indentless = (self.mapping_context and not self.indention) + self.increase_indent(flow=False, indentless=indentless) + self.state = self.expect_first_block_sequence_item + + def expect_first_block_sequence_item(self): + return self.expect_block_sequence_item(first=True) + + def expect_block_sequence_item(self, first=False): + if not first and isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + self.write_indicator('-', True, indention=True) + self.states.append(self.expect_block_sequence_item) + self.expect_node(sequence=True) + + # Block mapping handlers. + + def expect_block_mapping(self): + self.increase_indent(flow=False) + self.state = self.expect_first_block_mapping_key + + def expect_first_block_mapping_key(self): + return self.expect_block_mapping_key(first=True) + + def expect_block_mapping_key(self, first=False): + if not first and isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + if self.check_simple_key(): + self.states.append(self.expect_block_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True, indention=True) + self.states.append(self.expect_block_mapping_value) + self.expect_node(mapping=True) + + def expect_block_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + def expect_block_mapping_value(self): + self.write_indent() + self.write_indicator(':', True, indention=True) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + # Checkers. + + def check_empty_sequence(self): + return (isinstance(self.event, SequenceStartEvent) and self.events + and isinstance(self.events[0], SequenceEndEvent)) + + def check_empty_mapping(self): + return (isinstance(self.event, MappingStartEvent) and self.events + and isinstance(self.events[0], MappingEndEvent)) + + def check_empty_document(self): + if not isinstance(self.event, DocumentStartEvent) or not self.events: + return False + event = self.events[0] + return (isinstance(event, ScalarEvent) and event.anchor is None + and event.tag is None and event.implicit and event.value == '') + + def check_simple_key(self): + length = 0 + if isinstance(self.event, NodeEvent) and self.event.anchor is not None: + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + length += len(self.prepared_anchor) + if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \ + and self.event.tag is not None: + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(self.event.tag) + length += len(self.prepared_tag) + if isinstance(self.event, ScalarEvent): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + length += len(self.analysis.scalar) + return (length < 128 and (isinstance(self.event, AliasEvent) + or (isinstance(self.event, ScalarEvent) + and not self.analysis.empty and not self.analysis.multiline) + or self.check_empty_sequence() or self.check_empty_mapping())) + + # Anchor, Tag, and Scalar processors. + + def process_anchor(self, indicator): + if self.event.anchor is None: + self.prepared_anchor = None + return + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + if self.prepared_anchor: + self.write_indicator(indicator+self.prepared_anchor, True) + self.prepared_anchor = None + + def process_tag(self): + tag = self.event.tag + if isinstance(self.event, ScalarEvent): + if self.style is None: + self.style = self.choose_scalar_style() + if ((not self.canonical or tag is None) and + ((self.style == '' and self.event.implicit[0]) + or (self.style != '' and self.event.implicit[1]))): + self.prepared_tag = None + return + if self.event.implicit[0] and tag is None: + tag = '!' + self.prepared_tag = None + else: + if (not self.canonical or tag is None) and self.event.implicit: + self.prepared_tag = None + return + if tag is None: + raise EmitterError("tag is not specified") + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(tag) + if self.prepared_tag: + self.write_indicator(self.prepared_tag, True) + self.prepared_tag = None + + def choose_scalar_style(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.event.style == '"' or self.canonical: + return '"' + if not self.event.style and self.event.implicit[0]: + if (not (self.simple_key_context and + (self.analysis.empty or self.analysis.multiline)) + and (self.flow_level and self.analysis.allow_flow_plain + or (not self.flow_level and self.analysis.allow_block_plain))): + return '' + if self.event.style and self.event.style in '|>': + if (not self.flow_level and not self.simple_key_context + and self.analysis.allow_block): + return self.event.style + if not self.event.style or self.event.style == '\'': + if (self.analysis.allow_single_quoted and + not (self.simple_key_context and self.analysis.multiline)): + return '\'' + return '"' + + def process_scalar(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.style is None: + self.style = self.choose_scalar_style() + split = (not self.simple_key_context) + #if self.analysis.multiline and split \ + # and (not self.style or self.style in '\'\"'): + # self.write_indent() + if self.style == '"': + self.write_double_quoted(self.analysis.scalar, split) + elif self.style == '\'': + self.write_single_quoted(self.analysis.scalar, split) + elif self.style == '>': + self.write_folded(self.analysis.scalar) + elif self.style == '|': + self.write_literal(self.analysis.scalar) + else: + self.write_plain(self.analysis.scalar, split) + self.analysis = None + self.style = None + + # Analyzers. + + def prepare_version(self, version): + major, minor = version + if major != 1: + raise EmitterError("unsupported YAML version: %d.%d" % (major, minor)) + return '%d.%d' % (major, minor) + + def prepare_tag_handle(self, handle): + if not handle: + raise EmitterError("tag handle must not be empty") + if handle[0] != '!' or handle[-1] != '!': + raise EmitterError("tag handle must start and end with '!': %r" % handle) + for ch in handle[1:-1]: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the tag handle: %r" + % (ch, handle)) + return handle + + def prepare_tag_prefix(self, prefix): + if not prefix: + raise EmitterError("tag prefix must not be empty") + chunks = [] + start = end = 0 + if prefix[0] == '!': + end = 1 + while end < len(prefix): + ch = prefix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?!:@&=+$,_.~*\'()[]': + end += 1 + else: + if start < end: + chunks.append(prefix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ord(ch)) + if start < end: + chunks.append(prefix[start:end]) + return ''.join(chunks) + + def prepare_tag(self, tag): + if not tag: + raise EmitterError("tag must not be empty") + if tag == '!': + return tag + handle = None + suffix = tag + prefixes = sorted(self.tag_prefixes.keys()) + for prefix in prefixes: + if tag.startswith(prefix) \ + and (prefix == '!' or len(prefix) < len(tag)): + handle = self.tag_prefixes[prefix] + suffix = tag[len(prefix):] + chunks = [] + start = end = 0 + while end < len(suffix): + ch = suffix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.~*\'()[]' \ + or (ch == '!' and handle != '!'): + end += 1 + else: + if start < end: + chunks.append(suffix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ch) + if start < end: + chunks.append(suffix[start:end]) + suffix_text = ''.join(chunks) + if handle: + return '%s%s' % (handle, suffix_text) + else: + return '!<%s>' % suffix_text + + def prepare_anchor(self, anchor): + if not anchor: + raise EmitterError("anchor must not be empty") + for ch in anchor: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the anchor: %r" + % (ch, anchor)) + return anchor + + def analyze_scalar(self, scalar): + + # Empty scalar is a special case. + if not scalar: + return ScalarAnalysis(scalar=scalar, empty=True, multiline=False, + allow_flow_plain=False, allow_block_plain=True, + allow_single_quoted=True, allow_double_quoted=True, + allow_block=False) + + # Indicators and special characters. + block_indicators = False + flow_indicators = False + line_breaks = False + special_characters = False + + # Important whitespace combinations. + leading_space = False + leading_break = False + trailing_space = False + trailing_break = False + break_space = False + space_break = False + + # Check document indicators. + if scalar.startswith('---') or scalar.startswith('...'): + block_indicators = True + flow_indicators = True + + # First character or preceded by a whitespace. + preceded_by_whitespace = True + + # Last character or followed by a whitespace. + followed_by_whitespace = (len(scalar) == 1 or + scalar[1] in '\0 \t\r\n\x85\u2028\u2029') + + # The previous character is a space. + previous_space = False + + # The previous character is a break. + previous_break = False + + index = 0 + while index < len(scalar): + ch = scalar[index] + + # Check for indicators. + if index == 0: + # Leading indicators are special characters. + if ch in '#,[]{}&*!|>\'\"%@`': + flow_indicators = True + block_indicators = True + if ch in '?:': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '-' and followed_by_whitespace: + flow_indicators = True + block_indicators = True + else: + # Some indicators cannot appear within a scalar as well. + if ch in ',?[]{}': + flow_indicators = True + if ch == ':': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '#' and preceded_by_whitespace: + flow_indicators = True + block_indicators = True + + # Check for line breaks, special, and unicode characters. + if ch in '\n\x85\u2028\u2029': + line_breaks = True + if not (ch == '\n' or '\x20' <= ch <= '\x7E'): + if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD' + or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF': + unicode_characters = True + if not self.allow_unicode: + special_characters = True + else: + special_characters = True + + # Detect important whitespace combinations. + if ch == ' ': + if index == 0: + leading_space = True + if index == len(scalar)-1: + trailing_space = True + if previous_break: + break_space = True + previous_space = True + previous_break = False + elif ch in '\n\x85\u2028\u2029': + if index == 0: + leading_break = True + if index == len(scalar)-1: + trailing_break = True + if previous_space: + space_break = True + previous_space = False + previous_break = True + else: + previous_space = False + previous_break = False + + # Prepare for the next character. + index += 1 + preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029') + followed_by_whitespace = (index+1 >= len(scalar) or + scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029') + + # Let's decide what styles are allowed. + allow_flow_plain = True + allow_block_plain = True + allow_single_quoted = True + allow_double_quoted = True + allow_block = True + + # Leading and trailing whitespaces are bad for plain scalars. + if (leading_space or leading_break + or trailing_space or trailing_break): + allow_flow_plain = allow_block_plain = False + + # We do not permit trailing spaces for block scalars. + if trailing_space: + allow_block = False + + # Spaces at the beginning of a new line are only acceptable for block + # scalars. + if break_space: + allow_flow_plain = allow_block_plain = allow_single_quoted = False + + # Spaces followed by breaks, as well as special character are only + # allowed for double quoted scalars. + if space_break or special_characters: + allow_flow_plain = allow_block_plain = \ + allow_single_quoted = allow_block = False + + # Although the plain scalar writer supports breaks, we never emit + # multiline plain scalars. + if line_breaks: + allow_flow_plain = allow_block_plain = False + + # Flow indicators are forbidden for flow plain scalars. + if flow_indicators: + allow_flow_plain = False + + # Block indicators are forbidden for block plain scalars. + if block_indicators: + allow_block_plain = False + + return ScalarAnalysis(scalar=scalar, + empty=False, multiline=line_breaks, + allow_flow_plain=allow_flow_plain, + allow_block_plain=allow_block_plain, + allow_single_quoted=allow_single_quoted, + allow_double_quoted=allow_double_quoted, + allow_block=allow_block) + + # Writers. + + def flush_stream(self): + if hasattr(self.stream, 'flush'): + self.stream.flush() + + def write_stream_start(self): + # Write BOM if needed. + if self.encoding and self.encoding.startswith('utf-16'): + self.stream.write('\uFEFF'.encode(self.encoding)) + + def write_stream_end(self): + self.flush_stream() + + def write_indicator(self, indicator, need_whitespace, + whitespace=False, indention=False): + if self.whitespace or not need_whitespace: + data = indicator + else: + data = ' '+indicator + self.whitespace = whitespace + self.indention = self.indention and indention + self.column += len(data) + self.open_ended = False + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_indent(self): + indent = self.indent or 0 + if not self.indention or self.column > indent \ + or (self.column == indent and not self.whitespace): + self.write_line_break() + if self.column < indent: + self.whitespace = True + data = ' '*(indent-self.column) + self.column = indent + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_line_break(self, data=None): + if data is None: + data = self.best_line_break + self.whitespace = True + self.indention = True + self.line += 1 + self.column = 0 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_version_directive(self, version_text): + data = '%%YAML %s' % version_text + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + def write_tag_directive(self, handle_text, prefix_text): + data = '%%TAG %s %s' % (handle_text, prefix_text) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + # Scalar streams. + + def write_single_quoted(self, text, split=True): + self.write_indicator('\'', True) + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch is None or ch != ' ': + if start+1 == end and self.column > self.best_width and split \ + and start != 0 and end != len(text): + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'': + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch == '\'': + data = '\'\'' + self.column += 2 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + 1 + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + self.write_indicator('\'', False) + + ESCAPE_REPLACEMENTS = { + '\0': '0', + '\x07': 'a', + '\x08': 'b', + '\x09': 't', + '\x0A': 'n', + '\x0B': 'v', + '\x0C': 'f', + '\x0D': 'r', + '\x1B': 'e', + '\"': '\"', + '\\': '\\', + '\x85': 'N', + '\xA0': '_', + '\u2028': 'L', + '\u2029': 'P', + } + + def write_double_quoted(self, text, split=True): + self.write_indicator('"', True) + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \ + or not ('\x20' <= ch <= '\x7E' + or (self.allow_unicode + and ('\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD'))): + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + if ch in self.ESCAPE_REPLACEMENTS: + data = '\\'+self.ESCAPE_REPLACEMENTS[ch] + elif ch <= '\xFF': + data = '\\x%02X' % ord(ch) + elif ch <= '\uFFFF': + data = '\\u%04X' % ord(ch) + else: + data = '\\U%08X' % ord(ch) + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end+1 + if 0 < end < len(text)-1 and (ch == ' ' or start >= end) \ + and self.column+(end-start) > self.best_width and split: + data = text[start:end]+'\\' + if start < end: + start = end + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_indent() + self.whitespace = False + self.indention = False + if text[start] == ' ': + data = '\\' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + end += 1 + self.write_indicator('"', False) + + def determine_block_hints(self, text): + hints = '' + if text: + if text[0] in ' \n\x85\u2028\u2029': + hints += str(self.best_indent) + if text[-1] not in '\n\x85\u2028\u2029': + hints += '-' + elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029': + hints += '+' + return hints + + def write_folded(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('>'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + leading_space = True + spaces = False + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if not leading_space and ch is not None and ch != ' ' \ + and text[start] == '\n': + self.write_line_break() + leading_space = (ch == ' ') + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + elif spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width: + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + spaces = (ch == ' ') + end += 1 + + def write_literal(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('|'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + else: + if ch is None or ch in '\n\x85\u2028\u2029': + data = text[start:end] + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + + def write_plain(self, text, split=True): + if self.root_context: + self.open_ended = True + if not text: + return + if not self.whitespace: + data = ' ' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.whitespace = False + self.indention = False + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width and split: + self.write_indent() + self.whitespace = False + self.indention = False + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + self.whitespace = False + self.indention = False + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 diff --git a/.venv/lib/python3.14/site-packages/yaml/error.py b/.venv/lib/python3.14/site-packages/yaml/error.py new file mode 100644 index 0000000000000000000000000000000000000000..b796b4dc519512c4825ff539a2e6aa20f4d370d0 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/error.py @@ -0,0 +1,75 @@ + +__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError'] + +class Mark: + + def __init__(self, name, index, line, column, buffer, pointer): + self.name = name + self.index = index + self.line = line + self.column = column + self.buffer = buffer + self.pointer = pointer + + def get_snippet(self, indent=4, max_length=75): + if self.buffer is None: + return None + head = '' + start = self.pointer + while start > 0 and self.buffer[start-1] not in '\0\r\n\x85\u2028\u2029': + start -= 1 + if self.pointer-start > max_length/2-1: + head = ' ... ' + start += 5 + break + tail = '' + end = self.pointer + while end < len(self.buffer) and self.buffer[end] not in '\0\r\n\x85\u2028\u2029': + end += 1 + if end-self.pointer > max_length/2-1: + tail = ' ... ' + end -= 5 + break + snippet = self.buffer[start:end] + return ' '*indent + head + snippet + tail + '\n' \ + + ' '*(indent+self.pointer-start+len(head)) + '^' + + def __str__(self): + snippet = self.get_snippet() + where = " in \"%s\", line %d, column %d" \ + % (self.name, self.line+1, self.column+1) + if snippet is not None: + where += ":\n"+snippet + return where + +class YAMLError(Exception): + pass + +class MarkedYAMLError(YAMLError): + + def __init__(self, context=None, context_mark=None, + problem=None, problem_mark=None, note=None): + self.context = context + self.context_mark = context_mark + self.problem = problem + self.problem_mark = problem_mark + self.note = note + + def __str__(self): + lines = [] + if self.context is not None: + lines.append(self.context) + if self.context_mark is not None \ + and (self.problem is None or self.problem_mark is None + or self.context_mark.name != self.problem_mark.name + or self.context_mark.line != self.problem_mark.line + or self.context_mark.column != self.problem_mark.column): + lines.append(str(self.context_mark)) + if self.problem is not None: + lines.append(self.problem) + if self.problem_mark is not None: + lines.append(str(self.problem_mark)) + if self.note is not None: + lines.append(self.note) + return '\n'.join(lines) + diff --git a/.venv/lib/python3.14/site-packages/yaml/events.py b/.venv/lib/python3.14/site-packages/yaml/events.py new file mode 100644 index 0000000000000000000000000000000000000000..f79ad389cb6c9517e391dcd25534866bc9ccd36a --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/events.py @@ -0,0 +1,86 @@ + +# Abstract classes. + +class Event(object): + def __init__(self, start_mark=None, end_mark=None): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in ['anchor', 'tag', 'implicit', 'value'] + if hasattr(self, key)] + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +class NodeEvent(Event): + def __init__(self, anchor, start_mark=None, end_mark=None): + self.anchor = anchor + self.start_mark = start_mark + self.end_mark = end_mark + +class CollectionStartEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None, + flow_style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class CollectionEndEvent(Event): + pass + +# Implementations. + +class StreamStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndEvent(Event): + pass + +class DocumentStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None, version=None, tags=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + self.version = version + self.tags = tags + +class DocumentEndEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + +class AliasEvent(NodeEvent): + pass + +class ScalarEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, value, + start_mark=None, end_mark=None, style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class SequenceStartEvent(CollectionStartEvent): + pass + +class SequenceEndEvent(CollectionEndEvent): + pass + +class MappingStartEvent(CollectionStartEvent): + pass + +class MappingEndEvent(CollectionEndEvent): + pass + diff --git a/.venv/lib/python3.14/site-packages/yaml/loader.py b/.venv/lib/python3.14/site-packages/yaml/loader.py new file mode 100644 index 0000000000000000000000000000000000000000..e90c11224c38e559cdf0cb205f0692ebd4fb8681 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/loader.py @@ -0,0 +1,63 @@ + +__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader'] + +from .reader import * +from .scanner import * +from .parser import * +from .composer import * +from .constructor import * +from .resolver import * + +class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) + +# UnsafeLoader is the same as Loader (which is and was always unsafe on +# untrusted input). Use of either Loader or UnsafeLoader should be rare, since +# FullLoad should be able to load almost all YAML safely. Loader is left intact +# to ensure backwards compatibility. +class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) diff --git a/.venv/lib/python3.14/site-packages/yaml/nodes.py b/.venv/lib/python3.14/site-packages/yaml/nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..c4f070c41e1fb1bc01af27d69329e92dded38908 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/nodes.py @@ -0,0 +1,49 @@ + +class Node(object): + def __init__(self, tag, value, start_mark, end_mark): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + value = self.value + #if isinstance(value, list): + # if len(value) == 0: + # value = '' + # elif len(value) == 1: + # value = '<1 item>' + # else: + # value = '<%d items>' % len(value) + #else: + # if len(value) > 75: + # value = repr(value[:70]+u' ... ') + # else: + # value = repr(value) + value = repr(value) + return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value) + +class ScalarNode(Node): + id = 'scalar' + def __init__(self, tag, value, + start_mark=None, end_mark=None, style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class CollectionNode(Node): + def __init__(self, tag, value, + start_mark=None, end_mark=None, flow_style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class SequenceNode(CollectionNode): + id = 'sequence' + +class MappingNode(CollectionNode): + id = 'mapping' + diff --git a/.venv/lib/python3.14/site-packages/yaml/parser.py b/.venv/lib/python3.14/site-packages/yaml/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..13a5995d292045d0f865a99abf692bd35dc87814 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/parser.py @@ -0,0 +1,589 @@ + +# The following YAML grammar is LL(1) and is parsed by a recursive descent +# parser. +# +# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +# implicit_document ::= block_node DOCUMENT-END* +# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +# block_node_or_indentless_sequence ::= +# ALIAS +# | properties (block_content | indentless_block_sequence)? +# | block_content +# | indentless_block_sequence +# block_node ::= ALIAS +# | properties block_content? +# | block_content +# flow_node ::= ALIAS +# | properties flow_content? +# | flow_content +# properties ::= TAG ANCHOR? | ANCHOR TAG? +# block_content ::= block_collection | flow_collection | SCALAR +# flow_content ::= flow_collection | SCALAR +# block_collection ::= block_sequence | block_mapping +# flow_collection ::= flow_sequence | flow_mapping +# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +# indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +# block_mapping ::= BLOCK-MAPPING_START +# ((KEY block_node_or_indentless_sequence?)? +# (VALUE block_node_or_indentless_sequence?)?)* +# BLOCK-END +# flow_sequence ::= FLOW-SEQUENCE-START +# (flow_sequence_entry FLOW-ENTRY)* +# flow_sequence_entry? +# FLOW-SEQUENCE-END +# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# flow_mapping ::= FLOW-MAPPING-START +# (flow_mapping_entry FLOW-ENTRY)* +# flow_mapping_entry? +# FLOW-MAPPING-END +# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# +# FIRST sets: +# +# stream: { STREAM-START } +# explicit_document: { DIRECTIVE DOCUMENT-START } +# implicit_document: FIRST(block_node) +# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_sequence: { BLOCK-SEQUENCE-START } +# block_mapping: { BLOCK-MAPPING-START } +# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY } +# indentless_sequence: { ENTRY } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_sequence: { FLOW-SEQUENCE-START } +# flow_mapping: { FLOW-MAPPING-START } +# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } +# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } + +__all__ = ['Parser', 'ParserError'] + +from .error import MarkedYAMLError +from .tokens import * +from .events import * +from .scanner import * + +class ParserError(MarkedYAMLError): + pass + +class Parser: + # Since writing a recursive-descendant parser is a straightforward task, we + # do not give many comments here. + + DEFAULT_TAGS = { + '!': '!', + '!!': 'tag:yaml.org,2002:', + } + + def __init__(self): + self.current_event = None + self.yaml_version = None + self.tag_handles = {} + self.states = [] + self.marks = [] + self.state = self.parse_stream_start + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def check_event(self, *choices): + # Check the type of the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + if self.current_event is not None: + if not choices: + return True + for choice in choices: + if isinstance(self.current_event, choice): + return True + return False + + def peek_event(self): + # Get the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + return self.current_event + + def get_event(self): + # Get the next event and proceed further. + if self.current_event is None: + if self.state: + self.current_event = self.state() + value = self.current_event + self.current_event = None + return value + + # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END + # implicit_document ::= block_node DOCUMENT-END* + # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + + def parse_stream_start(self): + + # Parse the stream start. + token = self.get_token() + event = StreamStartEvent(token.start_mark, token.end_mark, + encoding=token.encoding) + + # Prepare the next state. + self.state = self.parse_implicit_document_start + + return event + + def parse_implicit_document_start(self): + + # Parse an implicit document. + if not self.check_token(DirectiveToken, DocumentStartToken, + StreamEndToken): + self.tag_handles = self.DEFAULT_TAGS + token = self.peek_token() + start_mark = end_mark = token.start_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=False) + + # Prepare the next state. + self.states.append(self.parse_document_end) + self.state = self.parse_block_node + + return event + + else: + return self.parse_document_start() + + def parse_document_start(self): + + # Parse any extra document end indicators. + while self.check_token(DocumentEndToken): + self.get_token() + + # Parse an explicit document. + if not self.check_token(StreamEndToken): + token = self.peek_token() + start_mark = token.start_mark + version, tags = self.process_directives() + if not self.check_token(DocumentStartToken): + raise ParserError(None, None, + "expected '', but found %r" + % self.peek_token().id, + self.peek_token().start_mark) + token = self.get_token() + end_mark = token.end_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=True, version=version, tags=tags) + self.states.append(self.parse_document_end) + self.state = self.parse_document_content + else: + # Parse the end of the stream. + token = self.get_token() + event = StreamEndEvent(token.start_mark, token.end_mark) + assert not self.states + assert not self.marks + self.state = None + return event + + def parse_document_end(self): + + # Parse the document end. + token = self.peek_token() + start_mark = end_mark = token.start_mark + explicit = False + if self.check_token(DocumentEndToken): + token = self.get_token() + end_mark = token.end_mark + explicit = True + event = DocumentEndEvent(start_mark, end_mark, + explicit=explicit) + + # Prepare the next state. + self.state = self.parse_document_start + + return event + + def parse_document_content(self): + if self.check_token(DirectiveToken, + DocumentStartToken, DocumentEndToken, StreamEndToken): + event = self.process_empty_scalar(self.peek_token().start_mark) + self.state = self.states.pop() + return event + else: + return self.parse_block_node() + + def process_directives(self): + self.yaml_version = None + self.tag_handles = {} + while self.check_token(DirectiveToken): + token = self.get_token() + if token.name == 'YAML': + if self.yaml_version is not None: + raise ParserError(None, None, + "found duplicate YAML directive", token.start_mark) + major, minor = token.value + if major != 1: + raise ParserError(None, None, + "found incompatible YAML document (version 1.* is required)", + token.start_mark) + self.yaml_version = token.value + elif token.name == 'TAG': + handle, prefix = token.value + if handle in self.tag_handles: + raise ParserError(None, None, + "duplicate tag handle %r" % handle, + token.start_mark) + self.tag_handles[handle] = prefix + if self.tag_handles: + value = self.yaml_version, self.tag_handles.copy() + else: + value = self.yaml_version, None + for key in self.DEFAULT_TAGS: + if key not in self.tag_handles: + self.tag_handles[key] = self.DEFAULT_TAGS[key] + return value + + # block_node_or_indentless_sequence ::= ALIAS + # | properties (block_content | indentless_block_sequence)? + # | block_content + # | indentless_block_sequence + # block_node ::= ALIAS + # | properties block_content? + # | block_content + # flow_node ::= ALIAS + # | properties flow_content? + # | flow_content + # properties ::= TAG ANCHOR? | ANCHOR TAG? + # block_content ::= block_collection | flow_collection | SCALAR + # flow_content ::= flow_collection | SCALAR + # block_collection ::= block_sequence | block_mapping + # flow_collection ::= flow_sequence | flow_mapping + + def parse_block_node(self): + return self.parse_node(block=True) + + def parse_flow_node(self): + return self.parse_node() + + def parse_block_node_or_indentless_sequence(self): + return self.parse_node(block=True, indentless_sequence=True) + + def parse_node(self, block=False, indentless_sequence=False): + if self.check_token(AliasToken): + token = self.get_token() + event = AliasEvent(token.value, token.start_mark, token.end_mark) + self.state = self.states.pop() + else: + anchor = None + tag = None + start_mark = end_mark = tag_mark = None + if self.check_token(AnchorToken): + token = self.get_token() + start_mark = token.start_mark + end_mark = token.end_mark + anchor = token.value + if self.check_token(TagToken): + token = self.get_token() + tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + elif self.check_token(TagToken): + token = self.get_token() + start_mark = tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + if self.check_token(AnchorToken): + token = self.get_token() + end_mark = token.end_mark + anchor = token.value + if tag is not None: + handle, suffix = tag + if handle is not None: + if handle not in self.tag_handles: + raise ParserError("while parsing a node", start_mark, + "found undefined tag handle %r" % handle, + tag_mark) + tag = self.tag_handles[handle]+suffix + else: + tag = suffix + #if tag == '!': + # raise ParserError("while parsing a node", start_mark, + # "found non-specific tag '!'", tag_mark, + # "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.") + if start_mark is None: + start_mark = end_mark = self.peek_token().start_mark + event = None + implicit = (tag is None or tag == '!') + if indentless_sequence and self.check_token(BlockEntryToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark) + self.state = self.parse_indentless_sequence_entry + else: + if self.check_token(ScalarToken): + token = self.get_token() + end_mark = token.end_mark + if (token.plain and tag is None) or tag == '!': + implicit = (True, False) + elif tag is None: + implicit = (False, True) + else: + implicit = (False, False) + event = ScalarEvent(anchor, tag, implicit, token.value, + start_mark, end_mark, style=token.style) + self.state = self.states.pop() + elif self.check_token(FlowSequenceStartToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_sequence_first_entry + elif self.check_token(FlowMappingStartToken): + end_mark = self.peek_token().end_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_mapping_first_key + elif block and self.check_token(BlockSequenceStartToken): + end_mark = self.peek_token().start_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_sequence_first_entry + elif block and self.check_token(BlockMappingStartToken): + end_mark = self.peek_token().start_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_mapping_first_key + elif anchor is not None or tag is not None: + # Empty scalars are allowed even if a tag or an anchor is + # specified. + event = ScalarEvent(anchor, tag, (implicit, False), '', + start_mark, end_mark) + self.state = self.states.pop() + else: + if block: + node = 'block' + else: + node = 'flow' + token = self.peek_token() + raise ParserError("while parsing a %s node" % node, start_mark, + "expected the node content, but found %r" % token.id, + token.start_mark) + return event + + # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END + + def parse_block_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_sequence_entry() + + def parse_block_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, BlockEndToken): + self.states.append(self.parse_block_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_block_sequence_entry + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block collection", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + # indentless_sequence ::= (BLOCK-ENTRY block_node?)+ + + def parse_indentless_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, + KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_indentless_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_indentless_sequence_entry + return self.process_empty_scalar(token.end_mark) + token = self.peek_token() + event = SequenceEndEvent(token.start_mark, token.start_mark) + self.state = self.states.pop() + return event + + # block_mapping ::= BLOCK-MAPPING_START + # ((KEY block_node_or_indentless_sequence?)? + # (VALUE block_node_or_indentless_sequence?)?)* + # BLOCK-END + + def parse_block_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_mapping_key() + + def parse_block_mapping_key(self): + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_value) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_value + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block mapping", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_block_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_key) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_block_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + # flow_sequence ::= FLOW-SEQUENCE-START + # (flow_sequence_entry FLOW-ENTRY)* + # flow_sequence_entry? + # FLOW-SEQUENCE-END + # flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + # + # Note that while production rules for both flow_sequence_entry and + # flow_mapping_entry are equal, their interpretations are different. + # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?` + # generate an inline mapping (set syntax). + + def parse_flow_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_sequence_entry(first=True) + + def parse_flow_sequence_entry(self, first=False): + if not self.check_token(FlowSequenceEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow sequence", self.marks[-1], + "expected ',' or ']', but got %r" % token.id, token.start_mark) + + if self.check_token(KeyToken): + token = self.peek_token() + event = MappingStartEvent(None, None, True, + token.start_mark, token.end_mark, + flow_style=True) + self.state = self.parse_flow_sequence_entry_mapping_key + return event + elif not self.check_token(FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry) + return self.parse_flow_node() + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_sequence_entry_mapping_key(self): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_value + return self.process_empty_scalar(token.end_mark) + + def parse_flow_sequence_entry_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_end) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_end + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_sequence_entry_mapping_end + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_sequence_entry_mapping_end(self): + self.state = self.parse_flow_sequence_entry + token = self.peek_token() + return MappingEndEvent(token.start_mark, token.start_mark) + + # flow_mapping ::= FLOW-MAPPING-START + # (flow_mapping_entry FLOW-ENTRY)* + # flow_mapping_entry? + # FLOW-MAPPING-END + # flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + + def parse_flow_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_mapping_key(first=True) + + def parse_flow_mapping_key(self, first=False): + if not self.check_token(FlowMappingEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow mapping", self.marks[-1], + "expected ',' or '}', but got %r" % token.id, token.start_mark) + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_value + return self.process_empty_scalar(token.end_mark) + elif not self.check_token(FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_empty_value) + return self.parse_flow_node() + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_key) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_mapping_empty_value(self): + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(self.peek_token().start_mark) + + def process_empty_scalar(self, mark): + return ScalarEvent(None, None, (True, False), '', mark, mark) + diff --git a/.venv/lib/python3.14/site-packages/yaml/reader.py b/.venv/lib/python3.14/site-packages/yaml/reader.py new file mode 100644 index 0000000000000000000000000000000000000000..774b0219b5932a0ee1c27e637371de5ba8d9cb16 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/reader.py @@ -0,0 +1,185 @@ +# This module contains abstractions for the input stream. You don't have to +# looks further, there are no pretty code. +# +# We define two classes here. +# +# Mark(source, line, column) +# It's just a record and its only use is producing nice error messages. +# Parser does not use it for any other purposes. +# +# Reader(source, data) +# Reader determines the encoding of `data` and converts it to unicode. +# Reader provides the following methods and attributes: +# reader.peek(length=1) - return the next `length` characters +# reader.forward(length=1) - move the current position to `length` characters. +# reader.index - the number of the current character. +# reader.line, stream.column - the line and the column of the current character. + +__all__ = ['Reader', 'ReaderError'] + +from .error import YAMLError, Mark + +import codecs, re + +class ReaderError(YAMLError): + + def __init__(self, name, position, character, encoding, reason): + self.name = name + self.character = character + self.position = position + self.encoding = encoding + self.reason = reason + + def __str__(self): + if isinstance(self.character, bytes): + return "'%s' codec can't decode byte #x%02x: %s\n" \ + " in \"%s\", position %d" \ + % (self.encoding, ord(self.character), self.reason, + self.name, self.position) + else: + return "unacceptable character #x%04x: %s\n" \ + " in \"%s\", position %d" \ + % (self.character, self.reason, + self.name, self.position) + +class Reader(object): + # Reader: + # - determines the data encoding and converts it to a unicode string, + # - checks if characters are in allowed range, + # - adds '\0' to the end. + + # Reader accepts + # - a `bytes` object, + # - a `str` object, + # - a file-like object with its `read` method returning `str`, + # - a file-like object with its `read` method returning `unicode`. + + # Yeah, it's ugly and slow. + + def __init__(self, stream): + self.name = None + self.stream = None + self.stream_pointer = 0 + self.eof = True + self.buffer = '' + self.pointer = 0 + self.raw_buffer = None + self.raw_decode = None + self.encoding = None + self.index = 0 + self.line = 0 + self.column = 0 + if isinstance(stream, str): + self.name = "" + self.check_printable(stream) + self.buffer = stream+'\0' + elif isinstance(stream, bytes): + self.name = "" + self.raw_buffer = stream + self.determine_encoding() + else: + self.stream = stream + self.name = getattr(stream, 'name', "") + self.eof = False + self.raw_buffer = None + self.determine_encoding() + + def peek(self, index=0): + try: + return self.buffer[self.pointer+index] + except IndexError: + self.update(index+1) + return self.buffer[self.pointer+index] + + def prefix(self, length=1): + if self.pointer+length >= len(self.buffer): + self.update(length) + return self.buffer[self.pointer:self.pointer+length] + + def forward(self, length=1): + if self.pointer+length+1 >= len(self.buffer): + self.update(length+1) + while length: + ch = self.buffer[self.pointer] + self.pointer += 1 + self.index += 1 + if ch in '\n\x85\u2028\u2029' \ + or (ch == '\r' and self.buffer[self.pointer] != '\n'): + self.line += 1 + self.column = 0 + elif ch != '\uFEFF': + self.column += 1 + length -= 1 + + def get_mark(self): + if self.stream is None: + return Mark(self.name, self.index, self.line, self.column, + self.buffer, self.pointer) + else: + return Mark(self.name, self.index, self.line, self.column, + None, None) + + def determine_encoding(self): + while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2): + self.update_raw() + if isinstance(self.raw_buffer, bytes): + if self.raw_buffer.startswith(codecs.BOM_UTF16_LE): + self.raw_decode = codecs.utf_16_le_decode + self.encoding = 'utf-16-le' + elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE): + self.raw_decode = codecs.utf_16_be_decode + self.encoding = 'utf-16-be' + else: + self.raw_decode = codecs.utf_8_decode + self.encoding = 'utf-8' + self.update(1) + + NON_PRINTABLE = re.compile('[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]') + def check_printable(self, data): + match = self.NON_PRINTABLE.search(data) + if match: + character = match.group() + position = self.index+(len(self.buffer)-self.pointer)+match.start() + raise ReaderError(self.name, position, ord(character), + 'unicode', "special characters are not allowed") + + def update(self, length): + if self.raw_buffer is None: + return + self.buffer = self.buffer[self.pointer:] + self.pointer = 0 + while len(self.buffer) < length: + if not self.eof: + self.update_raw() + if self.raw_decode is not None: + try: + data, converted = self.raw_decode(self.raw_buffer, + 'strict', self.eof) + except UnicodeDecodeError as exc: + character = self.raw_buffer[exc.start] + if self.stream is not None: + position = self.stream_pointer-len(self.raw_buffer)+exc.start + else: + position = exc.start + raise ReaderError(self.name, position, character, + exc.encoding, exc.reason) + else: + data = self.raw_buffer + converted = len(data) + self.check_printable(data) + self.buffer += data + self.raw_buffer = self.raw_buffer[converted:] + if self.eof: + self.buffer += '\0' + self.raw_buffer = None + break + + def update_raw(self, size=4096): + data = self.stream.read(size) + if self.raw_buffer is None: + self.raw_buffer = data + else: + self.raw_buffer += data + self.stream_pointer += len(data) + if not data: + self.eof = True diff --git a/.venv/lib/python3.14/site-packages/yaml/representer.py b/.venv/lib/python3.14/site-packages/yaml/representer.py new file mode 100644 index 0000000000000000000000000000000000000000..808ca06dfbd60c9a23eb079151b74a82ef688749 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/representer.py @@ -0,0 +1,389 @@ + +__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', + 'RepresenterError'] + +from .error import * +from .nodes import * + +import datetime, copyreg, types, base64, collections + +class RepresenterError(YAMLError): + pass + +class BaseRepresenter: + + yaml_representers = {} + yaml_multi_representers = {} + + def __init__(self, default_style=None, default_flow_style=False, sort_keys=True): + self.default_style = default_style + self.sort_keys = sort_keys + self.default_flow_style = default_flow_style + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent(self, data): + node = self.represent_data(data) + self.serialize(node) + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent_data(self, data): + if self.ignore_aliases(data): + self.alias_key = None + else: + self.alias_key = id(data) + if self.alias_key is not None: + if self.alias_key in self.represented_objects: + node = self.represented_objects[self.alias_key] + #if node is None: + # raise RepresenterError("recursive objects are not allowed: %r" % data) + return node + #self.represented_objects[alias_key] = None + self.object_keeper.append(data) + data_types = type(data).__mro__ + if data_types[0] in self.yaml_representers: + node = self.yaml_representers[data_types[0]](self, data) + else: + for data_type in data_types: + if data_type in self.yaml_multi_representers: + node = self.yaml_multi_representers[data_type](self, data) + break + else: + if None in self.yaml_multi_representers: + node = self.yaml_multi_representers[None](self, data) + elif None in self.yaml_representers: + node = self.yaml_representers[None](self, data) + else: + node = ScalarNode(None, str(data)) + #if alias_key is not None: + # self.represented_objects[alias_key] = node + return node + + @classmethod + def add_representer(cls, data_type, representer): + if not 'yaml_representers' in cls.__dict__: + cls.yaml_representers = cls.yaml_representers.copy() + cls.yaml_representers[data_type] = representer + + @classmethod + def add_multi_representer(cls, data_type, representer): + if not 'yaml_multi_representers' in cls.__dict__: + cls.yaml_multi_representers = cls.yaml_multi_representers.copy() + cls.yaml_multi_representers[data_type] = representer + + def represent_scalar(self, tag, value, style=None): + if style is None: + style = self.default_style + node = ScalarNode(tag, value, style=style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + return node + + def represent_sequence(self, tag, sequence, flow_style=None): + value = [] + node = SequenceNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + for item in sequence: + node_item = self.represent_data(item) + if not (isinstance(node_item, ScalarNode) and not node_item.style): + best_style = False + value.append(node_item) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def represent_mapping(self, tag, mapping, flow_style=None): + value = [] + node = MappingNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + if hasattr(mapping, 'items'): + mapping = list(mapping.items()) + if self.sort_keys: + try: + mapping = sorted(mapping) + except TypeError: + pass + for item_key, item_value in mapping: + node_key = self.represent_data(item_key) + node_value = self.represent_data(item_value) + if not (isinstance(node_key, ScalarNode) and not node_key.style): + best_style = False + if not (isinstance(node_value, ScalarNode) and not node_value.style): + best_style = False + value.append((node_key, node_value)) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def ignore_aliases(self, data): + return False + +class SafeRepresenter(BaseRepresenter): + + def ignore_aliases(self, data): + if data is None: + return True + if isinstance(data, tuple) and data == (): + return True + if isinstance(data, (str, bytes, bool, int, float)): + return True + + def represent_none(self, data): + return self.represent_scalar('tag:yaml.org,2002:null', 'null') + + def represent_str(self, data): + return self.represent_scalar('tag:yaml.org,2002:str', data) + + def represent_binary(self, data): + if hasattr(base64, 'encodebytes'): + data = base64.encodebytes(data).decode('ascii') + else: + data = base64.encodestring(data).decode('ascii') + return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|') + + def represent_bool(self, data): + if data: + value = 'true' + else: + value = 'false' + return self.represent_scalar('tag:yaml.org,2002:bool', value) + + def represent_int(self, data): + return self.represent_scalar('tag:yaml.org,2002:int', str(data)) + + inf_value = 1e300 + while repr(inf_value) != repr(inf_value*inf_value): + inf_value *= inf_value + + def represent_float(self, data): + if data != data or (data == 0.0 and data == 1.0): + value = '.nan' + elif data == self.inf_value: + value = '.inf' + elif data == -self.inf_value: + value = '-.inf' + else: + value = repr(data).lower() + # Note that in some cases `repr(data)` represents a float number + # without the decimal parts. For instance: + # >>> repr(1e17) + # '1e17' + # Unfortunately, this is not a valid float representation according + # to the definition of the `!!float` tag. We fix this by adding + # '.0' before the 'e' symbol. + if '.' not in value and 'e' in value: + value = value.replace('e', '.0e', 1) + return self.represent_scalar('tag:yaml.org,2002:float', value) + + def represent_list(self, data): + #pairs = (len(data) > 0 and isinstance(data, list)) + #if pairs: + # for item in data: + # if not isinstance(item, tuple) or len(item) != 2: + # pairs = False + # break + #if not pairs: + return self.represent_sequence('tag:yaml.org,2002:seq', data) + #value = [] + #for item_key, item_value in data: + # value.append(self.represent_mapping(u'tag:yaml.org,2002:map', + # [(item_key, item_value)])) + #return SequenceNode(u'tag:yaml.org,2002:pairs', value) + + def represent_dict(self, data): + return self.represent_mapping('tag:yaml.org,2002:map', data) + + def represent_set(self, data): + value = {} + for key in data: + value[key] = None + return self.represent_mapping('tag:yaml.org,2002:set', value) + + def represent_date(self, data): + value = data.isoformat() + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_datetime(self, data): + value = data.isoformat(' ') + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_yaml_object(self, tag, data, cls, flow_style=None): + if hasattr(data, '__getstate__'): + state = data.__getstate__() + else: + state = data.__dict__.copy() + return self.represent_mapping(tag, state, flow_style=flow_style) + + def represent_undefined(self, data): + raise RepresenterError("cannot represent an object", data) + +SafeRepresenter.add_representer(type(None), + SafeRepresenter.represent_none) + +SafeRepresenter.add_representer(str, + SafeRepresenter.represent_str) + +SafeRepresenter.add_representer(bytes, + SafeRepresenter.represent_binary) + +SafeRepresenter.add_representer(bool, + SafeRepresenter.represent_bool) + +SafeRepresenter.add_representer(int, + SafeRepresenter.represent_int) + +SafeRepresenter.add_representer(float, + SafeRepresenter.represent_float) + +SafeRepresenter.add_representer(list, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(tuple, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(dict, + SafeRepresenter.represent_dict) + +SafeRepresenter.add_representer(set, + SafeRepresenter.represent_set) + +SafeRepresenter.add_representer(datetime.date, + SafeRepresenter.represent_date) + +SafeRepresenter.add_representer(datetime.datetime, + SafeRepresenter.represent_datetime) + +SafeRepresenter.add_representer(None, + SafeRepresenter.represent_undefined) + +class Representer(SafeRepresenter): + + def represent_complex(self, data): + if data.imag == 0.0: + data = '%r' % data.real + elif data.real == 0.0: + data = '%rj' % data.imag + elif data.imag > 0: + data = '%r+%rj' % (data.real, data.imag) + else: + data = '%r%rj' % (data.real, data.imag) + return self.represent_scalar('tag:yaml.org,2002:python/complex', data) + + def represent_tuple(self, data): + return self.represent_sequence('tag:yaml.org,2002:python/tuple', data) + + def represent_name(self, data): + name = '%s.%s' % (data.__module__, data.__name__) + return self.represent_scalar('tag:yaml.org,2002:python/name:'+name, '') + + def represent_module(self, data): + return self.represent_scalar( + 'tag:yaml.org,2002:python/module:'+data.__name__, '') + + def represent_object(self, data): + # We use __reduce__ API to save the data. data.__reduce__ returns + # a tuple of length 2-5: + # (function, args, state, listitems, dictitems) + + # For reconstructing, we calls function(*args), then set its state, + # listitems, and dictitems if they are not None. + + # A special case is when function.__name__ == '__newobj__'. In this + # case we create the object with args[0].__new__(*args). + + # Another special case is when __reduce__ returns a string - we don't + # support it. + + # We produce a !!python/object, !!python/object/new or + # !!python/object/apply node. + + cls = type(data) + if cls in copyreg.dispatch_table: + reduce = copyreg.dispatch_table[cls](data) + elif hasattr(data, '__reduce_ex__'): + reduce = data.__reduce_ex__(2) + elif hasattr(data, '__reduce__'): + reduce = data.__reduce__() + else: + raise RepresenterError("cannot represent an object", data) + reduce = (list(reduce)+[None]*5)[:5] + function, args, state, listitems, dictitems = reduce + args = list(args) + if state is None: + state = {} + if listitems is not None: + listitems = list(listitems) + if dictitems is not None: + dictitems = dict(dictitems) + if function.__name__ == '__newobj__': + function = args[0] + args = args[1:] + tag = 'tag:yaml.org,2002:python/object/new:' + newobj = True + else: + tag = 'tag:yaml.org,2002:python/object/apply:' + newobj = False + function_name = '%s.%s' % (function.__module__, function.__name__) + if not args and not listitems and not dictitems \ + and isinstance(state, dict) and newobj: + return self.represent_mapping( + 'tag:yaml.org,2002:python/object:'+function_name, state) + if not listitems and not dictitems \ + and isinstance(state, dict) and not state: + return self.represent_sequence(tag+function_name, args) + value = {} + if args: + value['args'] = args + if state or not isinstance(state, dict): + value['state'] = state + if listitems: + value['listitems'] = listitems + if dictitems: + value['dictitems'] = dictitems + return self.represent_mapping(tag+function_name, value) + + def represent_ordered_dict(self, data): + # Provide uniform representation across different Python versions. + data_type = type(data) + tag = 'tag:yaml.org,2002:python/object/apply:%s.%s' \ + % (data_type.__module__, data_type.__name__) + items = [[key, value] for key, value in data.items()] + return self.represent_sequence(tag, [items]) + +Representer.add_representer(complex, + Representer.represent_complex) + +Representer.add_representer(tuple, + Representer.represent_tuple) + +Representer.add_multi_representer(type, + Representer.represent_name) + +Representer.add_representer(collections.OrderedDict, + Representer.represent_ordered_dict) + +Representer.add_representer(types.FunctionType, + Representer.represent_name) + +Representer.add_representer(types.BuiltinFunctionType, + Representer.represent_name) + +Representer.add_representer(types.ModuleType, + Representer.represent_module) + +Representer.add_multi_representer(object, + Representer.represent_object) + diff --git a/.venv/lib/python3.14/site-packages/yaml/resolver.py b/.venv/lib/python3.14/site-packages/yaml/resolver.py new file mode 100644 index 0000000000000000000000000000000000000000..3522bdaaf6358110b608f4e6503b9d314c82d887 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/resolver.py @@ -0,0 +1,227 @@ + +__all__ = ['BaseResolver', 'Resolver'] + +from .error import * +from .nodes import * + +import re + +class ResolverError(YAMLError): + pass + +class BaseResolver: + + DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str' + DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq' + DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map' + + yaml_implicit_resolvers = {} + yaml_path_resolvers = {} + + def __init__(self): + self.resolver_exact_paths = [] + self.resolver_prefix_paths = [] + + @classmethod + def add_implicit_resolver(cls, tag, regexp, first): + if not 'yaml_implicit_resolvers' in cls.__dict__: + implicit_resolvers = {} + for key in cls.yaml_implicit_resolvers: + implicit_resolvers[key] = cls.yaml_implicit_resolvers[key][:] + cls.yaml_implicit_resolvers = implicit_resolvers + if first is None: + first = [None] + for ch in first: + cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp)) + + @classmethod + def add_path_resolver(cls, tag, path, kind=None): + # Note: `add_path_resolver` is experimental. The API could be changed. + # `new_path` is a pattern that is matched against the path from the + # root to the node that is being considered. `node_path` elements are + # tuples `(node_check, index_check)`. `node_check` is a node class: + # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None` + # matches any kind of a node. `index_check` could be `None`, a boolean + # value, a string value, or a number. `None` and `False` match against + # any _value_ of sequence and mapping nodes. `True` matches against + # any _key_ of a mapping node. A string `index_check` matches against + # a mapping value that corresponds to a scalar key which content is + # equal to the `index_check` value. An integer `index_check` matches + # against a sequence value with the index equal to `index_check`. + if not 'yaml_path_resolvers' in cls.__dict__: + cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy() + new_path = [] + for element in path: + if isinstance(element, (list, tuple)): + if len(element) == 2: + node_check, index_check = element + elif len(element) == 1: + node_check = element[0] + index_check = True + else: + raise ResolverError("Invalid path element: %s" % element) + else: + node_check = None + index_check = element + if node_check is str: + node_check = ScalarNode + elif node_check is list: + node_check = SequenceNode + elif node_check is dict: + node_check = MappingNode + elif node_check not in [ScalarNode, SequenceNode, MappingNode] \ + and not isinstance(node_check, str) \ + and node_check is not None: + raise ResolverError("Invalid node checker: %s" % node_check) + if not isinstance(index_check, (str, int)) \ + and index_check is not None: + raise ResolverError("Invalid index checker: %s" % index_check) + new_path.append((node_check, index_check)) + if kind is str: + kind = ScalarNode + elif kind is list: + kind = SequenceNode + elif kind is dict: + kind = MappingNode + elif kind not in [ScalarNode, SequenceNode, MappingNode] \ + and kind is not None: + raise ResolverError("Invalid node kind: %s" % kind) + cls.yaml_path_resolvers[tuple(new_path), kind] = tag + + def descend_resolver(self, current_node, current_index): + if not self.yaml_path_resolvers: + return + exact_paths = {} + prefix_paths = [] + if current_node: + depth = len(self.resolver_prefix_paths) + for path, kind in self.resolver_prefix_paths[-1]: + if self.check_resolver_prefix(depth, path, kind, + current_node, current_index): + if len(path) > depth: + prefix_paths.append((path, kind)) + else: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + for path, kind in self.yaml_path_resolvers: + if not path: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + prefix_paths.append((path, kind)) + self.resolver_exact_paths.append(exact_paths) + self.resolver_prefix_paths.append(prefix_paths) + + def ascend_resolver(self): + if not self.yaml_path_resolvers: + return + self.resolver_exact_paths.pop() + self.resolver_prefix_paths.pop() + + def check_resolver_prefix(self, depth, path, kind, + current_node, current_index): + node_check, index_check = path[depth-1] + if isinstance(node_check, str): + if current_node.tag != node_check: + return + elif node_check is not None: + if not isinstance(current_node, node_check): + return + if index_check is True and current_index is not None: + return + if (index_check is False or index_check is None) \ + and current_index is None: + return + if isinstance(index_check, str): + if not (isinstance(current_index, ScalarNode) + and index_check == current_index.value): + return + elif isinstance(index_check, int) and not isinstance(index_check, bool): + if index_check != current_index: + return + return True + + def resolve(self, kind, value, implicit): + if kind is ScalarNode and implicit[0]: + if value == '': + resolvers = self.yaml_implicit_resolvers.get('', []) + else: + resolvers = self.yaml_implicit_resolvers.get(value[0], []) + wildcard_resolvers = self.yaml_implicit_resolvers.get(None, []) + for tag, regexp in resolvers + wildcard_resolvers: + if regexp.match(value): + return tag + implicit = implicit[1] + if self.yaml_path_resolvers: + exact_paths = self.resolver_exact_paths[-1] + if kind in exact_paths: + return exact_paths[kind] + if None in exact_paths: + return exact_paths[None] + if kind is ScalarNode: + return self.DEFAULT_SCALAR_TAG + elif kind is SequenceNode: + return self.DEFAULT_SEQUENCE_TAG + elif kind is MappingNode: + return self.DEFAULT_MAPPING_TAG + +class Resolver(BaseResolver): + pass + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:bool', + re.compile(r'''^(?:yes|Yes|YES|no|No|NO + |true|True|TRUE|false|False|FALSE + |on|On|ON|off|Off|OFF)$''', re.X), + list('yYnNtTfFoO')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:float', + re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? + |\.[0-9][0-9_]*(?:[eE][-+][0-9]+)? + |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* + |[-+]?\.(?:inf|Inf|INF) + |\.(?:nan|NaN|NAN))$''', re.X), + list('-+0123456789.')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:int', + re.compile(r'''^(?:[-+]?0b[0-1_]+ + |[-+]?0[0-7_]+ + |[-+]?(?:0|[1-9][0-9_]*) + |[-+]?0x[0-9a-fA-F_]+ + |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), + list('-+0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:merge', + re.compile(r'^(?:<<)$'), + ['<']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:null', + re.compile(r'''^(?: ~ + |null|Null|NULL + | )$''', re.X), + ['~', 'n', 'N', '']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:timestamp', + re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] + |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]? + (?:[Tt]|[ \t]+)[0-9][0-9]? + :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)? + (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X), + list('0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:value', + re.compile(r'^(?:=)$'), + ['=']) + +# The following resolver is only for documentation purposes. It cannot work +# because plain scalars cannot start with '!', '&', or '*'. +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:yaml', + re.compile(r'^(?:!|&|\*)$'), + list('!&*')) + diff --git a/.venv/lib/python3.14/site-packages/yaml/scanner.py b/.venv/lib/python3.14/site-packages/yaml/scanner.py new file mode 100644 index 0000000000000000000000000000000000000000..de925b07f1eaec33c9c305a8a69f9eb7ac5983c5 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/scanner.py @@ -0,0 +1,1435 @@ + +# Scanner produces tokens of the following types: +# STREAM-START +# STREAM-END +# DIRECTIVE(name, value) +# DOCUMENT-START +# DOCUMENT-END +# BLOCK-SEQUENCE-START +# BLOCK-MAPPING-START +# BLOCK-END +# FLOW-SEQUENCE-START +# FLOW-MAPPING-START +# FLOW-SEQUENCE-END +# FLOW-MAPPING-END +# BLOCK-ENTRY +# FLOW-ENTRY +# KEY +# VALUE +# ALIAS(value) +# ANCHOR(value) +# TAG(value) +# SCALAR(value, plain, style) +# +# Read comments in the Scanner code for more details. +# + +__all__ = ['Scanner', 'ScannerError'] + +from .error import MarkedYAMLError +from .tokens import * + +class ScannerError(MarkedYAMLError): + pass + +class SimpleKey: + # See below simple keys treatment. + + def __init__(self, token_number, required, index, line, column, mark): + self.token_number = token_number + self.required = required + self.index = index + self.line = line + self.column = column + self.mark = mark + +class Scanner: + + def __init__(self): + """Initialize the scanner.""" + # It is assumed that Scanner and Reader will have a common descendant. + # Reader do the dirty work of checking for BOM and converting the + # input data to Unicode. It also adds NUL to the end. + # + # Reader supports the following methods + # self.peek(i=0) # peek the next i-th character + # self.prefix(l=1) # peek the next l characters + # self.forward(l=1) # read the next l characters and move the pointer. + + # Had we reached the end of the stream? + self.done = False + + # The number of unclosed '{' and '['. `flow_level == 0` means block + # context. + self.flow_level = 0 + + # List of processed tokens that are not yet emitted. + self.tokens = [] + + # Add the STREAM-START token. + self.fetch_stream_start() + + # Number of tokens that were emitted through the `get_token` method. + self.tokens_taken = 0 + + # The current indentation level. + self.indent = -1 + + # Past indentation levels. + self.indents = [] + + # Variables related to simple keys treatment. + + # A simple key is a key that is not denoted by the '?' indicator. + # Example of simple keys: + # --- + # block simple key: value + # ? not a simple key: + # : { flow simple key: value } + # We emit the KEY token before all keys, so when we find a potential + # simple key, we try to locate the corresponding ':' indicator. + # Simple keys should be limited to a single line and 1024 characters. + + # Can a simple key start at the current position? A simple key may + # start: + # - at the beginning of the line, not counting indentation spaces + # (in block context), + # - after '{', '[', ',' (in the flow context), + # - after '?', ':', '-' (in the block context). + # In the block context, this flag also signifies if a block collection + # may start at the current position. + self.allow_simple_key = True + + # Keep track of possible simple keys. This is a dictionary. The key + # is `flow_level`; there can be no more that one possible simple key + # for each level. The value is a SimpleKey record: + # (token_number, required, index, line, column, mark) + # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow), + # '[', or '{' tokens. + self.possible_simple_keys = {} + + # Public methods. + + def check_token(self, *choices): + # Check if the next token is one of the given types. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + if not choices: + return True + for choice in choices: + if isinstance(self.tokens[0], choice): + return True + return False + + def peek_token(self): + # Return the next token, but do not delete if from the queue. + # Return None if no more tokens. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + return self.tokens[0] + else: + return None + + def get_token(self): + # Return the next token. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + self.tokens_taken += 1 + return self.tokens.pop(0) + + # Private methods. + + def need_more_tokens(self): + if self.done: + return False + if not self.tokens: + return True + # The current token may be a potential simple key, so we + # need to look further. + self.stale_possible_simple_keys() + if self.next_possible_simple_key() == self.tokens_taken: + return True + + def fetch_more_tokens(self): + + # Eat whitespaces and comments until we reach the next token. + self.scan_to_next_token() + + # Remove obsolete possible simple keys. + self.stale_possible_simple_keys() + + # Compare the current indentation and column. It may add some tokens + # and decrease the current indentation level. + self.unwind_indent(self.column) + + # Peek the next character. + ch = self.peek() + + # Is it the end of stream? + if ch == '\0': + return self.fetch_stream_end() + + # Is it a directive? + if ch == '%' and self.check_directive(): + return self.fetch_directive() + + # Is it the document start? + if ch == '-' and self.check_document_start(): + return self.fetch_document_start() + + # Is it the document end? + if ch == '.' and self.check_document_end(): + return self.fetch_document_end() + + # TODO: support for BOM within a stream. + #if ch == '\uFEFF': + # return self.fetch_bom() <-- issue BOMToken + + # Note: the order of the following checks is NOT significant. + + # Is it the flow sequence start indicator? + if ch == '[': + return self.fetch_flow_sequence_start() + + # Is it the flow mapping start indicator? + if ch == '{': + return self.fetch_flow_mapping_start() + + # Is it the flow sequence end indicator? + if ch == ']': + return self.fetch_flow_sequence_end() + + # Is it the flow mapping end indicator? + if ch == '}': + return self.fetch_flow_mapping_end() + + # Is it the flow entry indicator? + if ch == ',': + return self.fetch_flow_entry() + + # Is it the block entry indicator? + if ch == '-' and self.check_block_entry(): + return self.fetch_block_entry() + + # Is it the key indicator? + if ch == '?' and self.check_key(): + return self.fetch_key() + + # Is it the value indicator? + if ch == ':' and self.check_value(): + return self.fetch_value() + + # Is it an alias? + if ch == '*': + return self.fetch_alias() + + # Is it an anchor? + if ch == '&': + return self.fetch_anchor() + + # Is it a tag? + if ch == '!': + return self.fetch_tag() + + # Is it a literal scalar? + if ch == '|' and not self.flow_level: + return self.fetch_literal() + + # Is it a folded scalar? + if ch == '>' and not self.flow_level: + return self.fetch_folded() + + # Is it a single quoted scalar? + if ch == '\'': + return self.fetch_single() + + # Is it a double quoted scalar? + if ch == '\"': + return self.fetch_double() + + # It must be a plain scalar then. + if self.check_plain(): + return self.fetch_plain() + + # No? It's an error. Let's produce a nice error message. + raise ScannerError("while scanning for the next token", None, + "found character %r that cannot start any token" % ch, + self.get_mark()) + + # Simple keys treatment. + + def next_possible_simple_key(self): + # Return the number of the nearest possible simple key. Actually we + # don't need to loop through the whole dictionary. We may replace it + # with the following code: + # if not self.possible_simple_keys: + # return None + # return self.possible_simple_keys[ + # min(self.possible_simple_keys.keys())].token_number + min_token_number = None + for level in self.possible_simple_keys: + key = self.possible_simple_keys[level] + if min_token_number is None or key.token_number < min_token_number: + min_token_number = key.token_number + return min_token_number + + def stale_possible_simple_keys(self): + # Remove entries that are no longer possible simple keys. According to + # the YAML specification, simple keys + # - should be limited to a single line, + # - should be no longer than 1024 characters. + # Disabling this procedure will allow simple keys of any length and + # height (may cause problems if indentation is broken though). + for level in list(self.possible_simple_keys): + key = self.possible_simple_keys[level] + if key.line != self.line \ + or self.index-key.index > 1024: + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + del self.possible_simple_keys[level] + + def save_possible_simple_key(self): + # The next token may start a simple key. We check if it's possible + # and save its position. This function is called for + # ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'. + + # Check if a simple key is required at the current position. + required = not self.flow_level and self.indent == self.column + + # The next token might be a simple key. Let's save it's number and + # position. + if self.allow_simple_key: + self.remove_possible_simple_key() + token_number = self.tokens_taken+len(self.tokens) + key = SimpleKey(token_number, required, + self.index, self.line, self.column, self.get_mark()) + self.possible_simple_keys[self.flow_level] = key + + def remove_possible_simple_key(self): + # Remove the saved possible key position at the current flow level. + if self.flow_level in self.possible_simple_keys: + key = self.possible_simple_keys[self.flow_level] + + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + + del self.possible_simple_keys[self.flow_level] + + # Indentation functions. + + def unwind_indent(self, column): + + ## In flow context, tokens should respect indentation. + ## Actually the condition should be `self.indent >= column` according to + ## the spec. But this condition will prohibit intuitively correct + ## constructions such as + ## key : { + ## } + #if self.flow_level and self.indent > column: + # raise ScannerError(None, None, + # "invalid indentation or unclosed '[' or '{'", + # self.get_mark()) + + # In the flow context, indentation is ignored. We make the scanner less + # restrictive then specification requires. + if self.flow_level: + return + + # In block context, we may need to issue the BLOCK-END tokens. + while self.indent > column: + mark = self.get_mark() + self.indent = self.indents.pop() + self.tokens.append(BlockEndToken(mark, mark)) + + def add_indent(self, column): + # Check if we need to increase indentation. + if self.indent < column: + self.indents.append(self.indent) + self.indent = column + return True + return False + + # Fetchers. + + def fetch_stream_start(self): + # We always add STREAM-START as the first token and STREAM-END as the + # last token. + + # Read the token. + mark = self.get_mark() + + # Add STREAM-START. + self.tokens.append(StreamStartToken(mark, mark, + encoding=self.encoding)) + + + def fetch_stream_end(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + self.possible_simple_keys = {} + + # Read the token. + mark = self.get_mark() + + # Add STREAM-END. + self.tokens.append(StreamEndToken(mark, mark)) + + # The steam is finished. + self.done = True + + def fetch_directive(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Scan and add DIRECTIVE. + self.tokens.append(self.scan_directive()) + + def fetch_document_start(self): + self.fetch_document_indicator(DocumentStartToken) + + def fetch_document_end(self): + self.fetch_document_indicator(DocumentEndToken) + + def fetch_document_indicator(self, TokenClass): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. Note that there could not be a block collection + # after '---'. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Add DOCUMENT-START or DOCUMENT-END. + start_mark = self.get_mark() + self.forward(3) + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_start(self): + self.fetch_flow_collection_start(FlowSequenceStartToken) + + def fetch_flow_mapping_start(self): + self.fetch_flow_collection_start(FlowMappingStartToken) + + def fetch_flow_collection_start(self, TokenClass): + + # '[' and '{' may start a simple key. + self.save_possible_simple_key() + + # Increase the flow level. + self.flow_level += 1 + + # Simple keys are allowed after '[' and '{'. + self.allow_simple_key = True + + # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_end(self): + self.fetch_flow_collection_end(FlowSequenceEndToken) + + def fetch_flow_mapping_end(self): + self.fetch_flow_collection_end(FlowMappingEndToken) + + def fetch_flow_collection_end(self, TokenClass): + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Decrease the flow level. + self.flow_level -= 1 + + # No simple keys after ']' or '}'. + self.allow_simple_key = False + + # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_entry(self): + + # Simple keys are allowed after ','. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add FLOW-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(FlowEntryToken(start_mark, end_mark)) + + def fetch_block_entry(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a new entry? + if not self.allow_simple_key: + raise ScannerError(None, None, + "sequence entries are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-SEQUENCE-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockSequenceStartToken(mark, mark)) + + # It's an error for the block entry to occur in the flow context, + # but we let the parser detect this. + else: + pass + + # Simple keys are allowed after '-'. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add BLOCK-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(BlockEntryToken(start_mark, end_mark)) + + def fetch_key(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a key (not necessary a simple)? + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping keys are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-MAPPING-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after '?' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add KEY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(KeyToken(start_mark, end_mark)) + + def fetch_value(self): + + # Do we determine a simple key? + if self.flow_level in self.possible_simple_keys: + + # Add KEY. + key = self.possible_simple_keys[self.flow_level] + del self.possible_simple_keys[self.flow_level] + self.tokens.insert(key.token_number-self.tokens_taken, + KeyToken(key.mark, key.mark)) + + # If this key starts a new block mapping, we need to add + # BLOCK-MAPPING-START. + if not self.flow_level: + if self.add_indent(key.column): + self.tokens.insert(key.token_number-self.tokens_taken, + BlockMappingStartToken(key.mark, key.mark)) + + # There cannot be two simple keys one after another. + self.allow_simple_key = False + + # It must be a part of a complex key. + else: + + # Block context needs additional checks. + # (Do we really need them? They will be caught by the parser + # anyway.) + if not self.flow_level: + + # We are allowed to start a complex value if and only if + # we can start a simple key. + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping values are not allowed here", + self.get_mark()) + + # If this value starts a new block mapping, we need to add + # BLOCK-MAPPING-START. It will be detected as an error later by + # the parser. + if not self.flow_level: + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after ':' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add VALUE. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(ValueToken(start_mark, end_mark)) + + def fetch_alias(self): + + # ALIAS could be a simple key. + self.save_possible_simple_key() + + # No simple keys after ALIAS. + self.allow_simple_key = False + + # Scan and add ALIAS. + self.tokens.append(self.scan_anchor(AliasToken)) + + def fetch_anchor(self): + + # ANCHOR could start a simple key. + self.save_possible_simple_key() + + # No simple keys after ANCHOR. + self.allow_simple_key = False + + # Scan and add ANCHOR. + self.tokens.append(self.scan_anchor(AnchorToken)) + + def fetch_tag(self): + + # TAG could start a simple key. + self.save_possible_simple_key() + + # No simple keys after TAG. + self.allow_simple_key = False + + # Scan and add TAG. + self.tokens.append(self.scan_tag()) + + def fetch_literal(self): + self.fetch_block_scalar(style='|') + + def fetch_folded(self): + self.fetch_block_scalar(style='>') + + def fetch_block_scalar(self, style): + + # A simple key may follow a block scalar. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Scan and add SCALAR. + self.tokens.append(self.scan_block_scalar(style)) + + def fetch_single(self): + self.fetch_flow_scalar(style='\'') + + def fetch_double(self): + self.fetch_flow_scalar(style='"') + + def fetch_flow_scalar(self, style): + + # A flow scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after flow scalars. + self.allow_simple_key = False + + # Scan and add SCALAR. + self.tokens.append(self.scan_flow_scalar(style)) + + def fetch_plain(self): + + # A plain scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after plain scalars. But note that `scan_plain` will + # change this flag if the scan is finished at the beginning of the + # line. + self.allow_simple_key = False + + # Scan and add SCALAR. May change `allow_simple_key`. + self.tokens.append(self.scan_plain()) + + # Checkers. + + def check_directive(self): + + # DIRECTIVE: ^ '%' ... + # The '%' indicator is already checked. + if self.column == 0: + return True + + def check_document_start(self): + + # DOCUMENT-START: ^ '---' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '---' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_document_end(self): + + # DOCUMENT-END: ^ '...' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '...' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_block_entry(self): + + # BLOCK-ENTRY: '-' (' '|'\n') + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_key(self): + + # KEY(flow context): '?' + if self.flow_level: + return True + + # KEY(block context): '?' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_value(self): + + # VALUE(flow context): ':' + if self.flow_level: + return True + + # VALUE(block context): ':' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_plain(self): + + # A plain scalar may start with any non-space character except: + # '-', '?', ':', ',', '[', ']', '{', '}', + # '#', '&', '*', '!', '|', '>', '\'', '\"', + # '%', '@', '`'. + # + # It may also start with + # '-', '?', ':' + # if it is followed by a non-space character. + # + # Note that we limit the last rule to the block context (except the + # '-' character) because we want the flow context to be space + # independent. + ch = self.peek() + return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \ + or (self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029' + and (ch == '-' or (not self.flow_level and ch in '?:'))) + + # Scanners. + + def scan_to_next_token(self): + # We ignore spaces, line breaks and comments. + # If we find a line break in the block context, we set the flag + # `allow_simple_key` on. + # The byte order mark is stripped if it's the first character in the + # stream. We do not yet support BOM inside the stream as the + # specification requires. Any such mark will be considered as a part + # of the document. + # + # TODO: We need to make tab handling rules more sane. A good rule is + # Tabs cannot precede tokens + # BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END, + # KEY(block), VALUE(block), BLOCK-ENTRY + # So the checking code is + # if : + # self.allow_simple_keys = False + # We also need to add the check for `allow_simple_keys == True` to + # `unwind_indent` before issuing BLOCK-END. + # Scanners for block, flow, and plain scalars need to be modified. + + if self.index == 0 and self.peek() == '\uFEFF': + self.forward() + found = False + while not found: + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + if self.scan_line_break(): + if not self.flow_level: + self.allow_simple_key = True + else: + found = True + + def scan_directive(self): + # See the specification for details. + start_mark = self.get_mark() + self.forward() + name = self.scan_directive_name(start_mark) + value = None + if name == 'YAML': + value = self.scan_yaml_directive_value(start_mark) + end_mark = self.get_mark() + elif name == 'TAG': + value = self.scan_tag_directive_value(start_mark) + end_mark = self.get_mark() + else: + end_mark = self.get_mark() + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + self.scan_directive_ignored_line(start_mark) + return DirectiveToken(name, value, start_mark, end_mark) + + def scan_directive_name(self, start_mark): + # See the specification for details. + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + return value + + def scan_yaml_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + major = self.scan_yaml_directive_number(start_mark) + if self.peek() != '.': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or '.', but found %r" % self.peek(), + self.get_mark()) + self.forward() + minor = self.scan_yaml_directive_number(start_mark) + if self.peek() not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or ' ', but found %r" % self.peek(), + self.get_mark()) + return (major, minor) + + def scan_yaml_directive_number(self, start_mark): + # See the specification for details. + ch = self.peek() + if not ('0' <= ch <= '9'): + raise ScannerError("while scanning a directive", start_mark, + "expected a digit, but found %r" % ch, self.get_mark()) + length = 0 + while '0' <= self.peek(length) <= '9': + length += 1 + value = int(self.prefix(length)) + self.forward(length) + return value + + def scan_tag_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + handle = self.scan_tag_directive_handle(start_mark) + while self.peek() == ' ': + self.forward() + prefix = self.scan_tag_directive_prefix(start_mark) + return (handle, prefix) + + def scan_tag_directive_handle(self, start_mark): + # See the specification for details. + value = self.scan_tag_handle('directive', start_mark) + ch = self.peek() + if ch != ' ': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_tag_directive_prefix(self, start_mark): + # See the specification for details. + value = self.scan_tag_uri('directive', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_directive_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a comment or a line break, but found %r" + % ch, self.get_mark()) + self.scan_line_break() + + def scan_anchor(self, TokenClass): + # The specification does not restrict characters for anchors and + # aliases. This may lead to problems, for instance, the document: + # [ *alias, value ] + # can be interpreted in two ways, as + # [ "value" ] + # and + # [ *alias , "value" ] + # Therefore we restrict aliases to numbers and ASCII letters. + start_mark = self.get_mark() + indicator = self.peek() + if indicator == '*': + name = 'alias' + else: + name = 'anchor' + self.forward() + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \t\r\n\x85\u2028\u2029?:,]}%@`': + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + end_mark = self.get_mark() + return TokenClass(value, start_mark, end_mark) + + def scan_tag(self): + # See the specification for details. + start_mark = self.get_mark() + ch = self.peek(1) + if ch == '<': + handle = None + self.forward(2) + suffix = self.scan_tag_uri('tag', start_mark) + if self.peek() != '>': + raise ScannerError("while parsing a tag", start_mark, + "expected '>', but found %r" % self.peek(), + self.get_mark()) + self.forward() + elif ch in '\0 \t\r\n\x85\u2028\u2029': + handle = None + suffix = '!' + self.forward() + else: + length = 1 + use_handle = False + while ch not in '\0 \r\n\x85\u2028\u2029': + if ch == '!': + use_handle = True + break + length += 1 + ch = self.peek(length) + handle = '!' + if use_handle: + handle = self.scan_tag_handle('tag', start_mark) + else: + handle = '!' + self.forward() + suffix = self.scan_tag_uri('tag', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a tag", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + value = (handle, suffix) + end_mark = self.get_mark() + return TagToken(value, start_mark, end_mark) + + def scan_block_scalar(self, style): + # See the specification for details. + + if style == '>': + folded = True + else: + folded = False + + chunks = [] + start_mark = self.get_mark() + + # Scan the header. + self.forward() + chomping, increment = self.scan_block_scalar_indicators(start_mark) + self.scan_block_scalar_ignored_line(start_mark) + + # Determine the indentation level and go to the first non-empty line. + min_indent = self.indent+1 + if min_indent < 1: + min_indent = 1 + if increment is None: + breaks, max_indent, end_mark = self.scan_block_scalar_indentation() + indent = max(min_indent, max_indent) + else: + indent = min_indent+increment-1 + breaks, end_mark = self.scan_block_scalar_breaks(indent) + line_break = '' + + # Scan the inner part of the block scalar. + while self.column == indent and self.peek() != '\0': + chunks.extend(breaks) + leading_non_space = self.peek() not in ' \t' + length = 0 + while self.peek(length) not in '\0\r\n\x85\u2028\u2029': + length += 1 + chunks.append(self.prefix(length)) + self.forward(length) + line_break = self.scan_line_break() + breaks, end_mark = self.scan_block_scalar_breaks(indent) + if self.column == indent and self.peek() != '\0': + + # Unfortunately, folding rules are ambiguous. + # + # This is the folding according to the specification: + + if folded and line_break == '\n' \ + and leading_non_space and self.peek() not in ' \t': + if not breaks: + chunks.append(' ') + else: + chunks.append(line_break) + + # This is Clark Evans's interpretation (also in the spec + # examples): + # + #if folded and line_break == '\n': + # if not breaks: + # if self.peek() not in ' \t': + # chunks.append(' ') + # else: + # chunks.append(line_break) + #else: + # chunks.append(line_break) + else: + break + + # Chomp the tail. + if chomping is not False: + chunks.append(line_break) + if chomping is True: + chunks.extend(breaks) + + # We are done. + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + def scan_block_scalar_indicators(self, start_mark): + # See the specification for details. + chomping = None + increment = None + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + elif ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected chomping or indentation indicators, but found %r" + % ch, self.get_mark()) + return chomping, increment + + def scan_block_scalar_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected a comment or a line break, but found %r" % ch, + self.get_mark()) + self.scan_line_break() + + def scan_block_scalar_indentation(self): + # See the specification for details. + chunks = [] + max_indent = 0 + end_mark = self.get_mark() + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() != ' ': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + else: + self.forward() + if self.column > max_indent: + max_indent = self.column + return chunks, max_indent, end_mark + + def scan_block_scalar_breaks(self, indent): + # See the specification for details. + chunks = [] + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + while self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + return chunks, end_mark + + def scan_flow_scalar(self, style): + # See the specification for details. + # Note that we loose indentation rules for quoted scalars. Quoted + # scalars don't need to adhere indentation because " and ' clearly + # mark the beginning and the end of them. Therefore we are less + # restrictive then the specification requires. We only need to check + # that document separators are not included in scalars. + if style == '"': + double = True + else: + double = False + chunks = [] + start_mark = self.get_mark() + quote = self.peek() + self.forward() + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + while self.peek() != quote: + chunks.extend(self.scan_flow_scalar_spaces(double, start_mark)) + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + self.forward() + end_mark = self.get_mark() + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + ESCAPE_REPLACEMENTS = { + '0': '\0', + 'a': '\x07', + 'b': '\x08', + 't': '\x09', + '\t': '\x09', + 'n': '\x0A', + 'v': '\x0B', + 'f': '\x0C', + 'r': '\x0D', + 'e': '\x1B', + ' ': '\x20', + '\"': '\"', + '\\': '\\', + '/': '/', + 'N': '\x85', + '_': '\xA0', + 'L': '\u2028', + 'P': '\u2029', + } + + ESCAPE_CODES = { + 'x': 2, + 'u': 4, + 'U': 8, + } + + def scan_flow_scalar_non_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + length = 0 + while self.peek(length) not in '\'\"\\\0 \t\r\n\x85\u2028\u2029': + length += 1 + if length: + chunks.append(self.prefix(length)) + self.forward(length) + ch = self.peek() + if not double and ch == '\'' and self.peek(1) == '\'': + chunks.append('\'') + self.forward(2) + elif (double and ch == '\'') or (not double and ch in '\"\\'): + chunks.append(ch) + self.forward() + elif double and ch == '\\': + self.forward() + ch = self.peek() + if ch in self.ESCAPE_REPLACEMENTS: + chunks.append(self.ESCAPE_REPLACEMENTS[ch]) + self.forward() + elif ch in self.ESCAPE_CODES: + length = self.ESCAPE_CODES[ch] + self.forward() + for k in range(length): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "expected escape sequence of %d hexadecimal numbers, but found %r" % + (length, self.peek(k)), self.get_mark()) + code = int(self.prefix(length), 16) + chunks.append(chr(code)) + self.forward(length) + elif ch in '\r\n\x85\u2028\u2029': + self.scan_line_break() + chunks.extend(self.scan_flow_scalar_breaks(double, start_mark)) + else: + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "found unknown escape character %r" % ch, self.get_mark()) + else: + return chunks + + def scan_flow_scalar_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + length = 0 + while self.peek(length) in ' \t': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch == '\0': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected end of stream", self.get_mark()) + elif ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + breaks = self.scan_flow_scalar_breaks(double, start_mark) + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + else: + chunks.append(whitespaces) + return chunks + + def scan_flow_scalar_breaks(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + # Instead of checking indentation, we check for document + # separators. + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected document separator", self.get_mark()) + while self.peek() in ' \t': + self.forward() + if self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + else: + return chunks + + def scan_plain(self): + # See the specification for details. + # We add an additional restriction for the flow context: + # plain scalars in the flow context cannot contain ',' or '?'. + # We also keep track of the `allow_simple_key` flag here. + # Indentation rules are loosed for the flow context. + chunks = [] + start_mark = self.get_mark() + end_mark = start_mark + indent = self.indent+1 + # We allow zero indentation for scalars, but then we need to check for + # document separators at the beginning of the line. + #if indent == 0: + # indent = 1 + spaces = [] + while True: + length = 0 + if self.peek() == '#': + break + while True: + ch = self.peek(length) + if ch in '\0 \t\r\n\x85\u2028\u2029' \ + or (ch == ':' and + self.peek(length+1) in '\0 \t\r\n\x85\u2028\u2029' + + (u',[]{}' if self.flow_level else u''))\ + or (self.flow_level and ch in ',?[]{}'): + break + length += 1 + if length == 0: + break + self.allow_simple_key = False + chunks.extend(spaces) + chunks.append(self.prefix(length)) + self.forward(length) + end_mark = self.get_mark() + spaces = self.scan_plain_spaces(indent, start_mark) + if not spaces or self.peek() == '#' \ + or (not self.flow_level and self.column < indent): + break + return ScalarToken(''.join(chunks), True, start_mark, end_mark) + + def scan_plain_spaces(self, indent, start_mark): + # See the specification for details. + # The specification is really confusing about tabs in plain scalars. + # We just forbid them completely. Do not use tabs in YAML! + chunks = [] + length = 0 + while self.peek(length) in ' ': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + self.allow_simple_key = True + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + breaks = [] + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() == ' ': + self.forward() + else: + breaks.append(self.scan_line_break()) + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + elif whitespaces: + chunks.append(whitespaces) + return chunks + + def scan_tag_handle(self, name, start_mark): + # See the specification for details. + # For some strange reasons, the specification does not allow '_' in + # tag handles. I have allowed it anyway. + ch = self.peek() + if ch != '!': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length = 1 + ch = self.peek(length) + if ch != ' ': + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if ch != '!': + self.forward(length) + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length += 1 + value = self.prefix(length) + self.forward(length) + return value + + def scan_tag_uri(self, name, start_mark): + # See the specification for details. + # Note: we do not check if URI is well-formed. + chunks = [] + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.!~*\'()[]%': + if ch == '%': + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + chunks.append(self.scan_uri_escapes(name, start_mark)) + else: + length += 1 + ch = self.peek(length) + if length: + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + if not chunks: + raise ScannerError("while parsing a %s" % name, start_mark, + "expected URI, but found %r" % ch, self.get_mark()) + return ''.join(chunks) + + def scan_uri_escapes(self, name, start_mark): + # See the specification for details. + codes = [] + mark = self.get_mark() + while self.peek() == '%': + self.forward() + for k in range(2): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected URI escape sequence of 2 hexadecimal numbers, but found %r" + % self.peek(k), self.get_mark()) + codes.append(int(self.prefix(2), 16)) + self.forward(2) + try: + value = bytes(codes).decode('utf-8') + except UnicodeDecodeError as exc: + raise ScannerError("while scanning a %s" % name, start_mark, str(exc), mark) + return value + + def scan_line_break(self): + # Transforms: + # '\r\n' : '\n' + # '\r' : '\n' + # '\n' : '\n' + # '\x85' : '\n' + # '\u2028' : '\u2028' + # '\u2029 : '\u2029' + # default : '' + ch = self.peek() + if ch in '\r\n\x85': + if self.prefix(2) == '\r\n': + self.forward(2) + else: + self.forward() + return '\n' + elif ch in '\u2028\u2029': + self.forward() + return ch + return '' diff --git a/.venv/lib/python3.14/site-packages/yaml/serializer.py b/.venv/lib/python3.14/site-packages/yaml/serializer.py new file mode 100644 index 0000000000000000000000000000000000000000..fe911e67ae7a739abb491fbbc6834b9c37bbda4b --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/serializer.py @@ -0,0 +1,111 @@ + +__all__ = ['Serializer', 'SerializerError'] + +from .error import YAMLError +from .events import * +from .nodes import * + +class SerializerError(YAMLError): + pass + +class Serializer: + + ANCHOR_TEMPLATE = 'id%03d' + + def __init__(self, encoding=None, + explicit_start=None, explicit_end=None, version=None, tags=None): + self.use_encoding = encoding + self.use_explicit_start = explicit_start + self.use_explicit_end = explicit_end + self.use_version = version + self.use_tags = tags + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + self.closed = None + + def open(self): + if self.closed is None: + self.emit(StreamStartEvent(encoding=self.use_encoding)) + self.closed = False + elif self.closed: + raise SerializerError("serializer is closed") + else: + raise SerializerError("serializer is already opened") + + def close(self): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif not self.closed: + self.emit(StreamEndEvent()) + self.closed = True + + #def __del__(self): + # self.close() + + def serialize(self, node): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif self.closed: + raise SerializerError("serializer is closed") + self.emit(DocumentStartEvent(explicit=self.use_explicit_start, + version=self.use_version, tags=self.use_tags)) + self.anchor_node(node) + self.serialize_node(node, None, None) + self.emit(DocumentEndEvent(explicit=self.use_explicit_end)) + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + + def anchor_node(self, node): + if node in self.anchors: + if self.anchors[node] is None: + self.anchors[node] = self.generate_anchor(node) + else: + self.anchors[node] = None + if isinstance(node, SequenceNode): + for item in node.value: + self.anchor_node(item) + elif isinstance(node, MappingNode): + for key, value in node.value: + self.anchor_node(key) + self.anchor_node(value) + + def generate_anchor(self, node): + self.last_anchor_id += 1 + return self.ANCHOR_TEMPLATE % self.last_anchor_id + + def serialize_node(self, node, parent, index): + alias = self.anchors[node] + if node in self.serialized_nodes: + self.emit(AliasEvent(alias)) + else: + self.serialized_nodes[node] = True + self.descend_resolver(parent, index) + if isinstance(node, ScalarNode): + detected_tag = self.resolve(ScalarNode, node.value, (True, False)) + default_tag = self.resolve(ScalarNode, node.value, (False, True)) + implicit = (node.tag == detected_tag), (node.tag == default_tag) + self.emit(ScalarEvent(alias, node.tag, implicit, node.value, + style=node.style)) + elif isinstance(node, SequenceNode): + implicit = (node.tag + == self.resolve(SequenceNode, node.value, True)) + self.emit(SequenceStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + index = 0 + for item in node.value: + self.serialize_node(item, node, index) + index += 1 + self.emit(SequenceEndEvent()) + elif isinstance(node, MappingNode): + implicit = (node.tag + == self.resolve(MappingNode, node.value, True)) + self.emit(MappingStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + for key, value in node.value: + self.serialize_node(key, node, None) + self.serialize_node(value, node, key) + self.emit(MappingEndEvent()) + self.ascend_resolver() + diff --git a/.venv/lib/python3.14/site-packages/yaml/tokens.py b/.venv/lib/python3.14/site-packages/yaml/tokens.py new file mode 100644 index 0000000000000000000000000000000000000000..4d0b48a394ac8c019b401516a12f688df361cf90 --- /dev/null +++ b/.venv/lib/python3.14/site-packages/yaml/tokens.py @@ -0,0 +1,104 @@ + +class Token(object): + def __init__(self, start_mark, end_mark): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in self.__dict__ + if not key.endswith('_mark')] + attributes.sort() + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +#class BOMToken(Token): +# id = '' + +class DirectiveToken(Token): + id = '' + def __init__(self, name, value, start_mark, end_mark): + self.name = name + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class DocumentStartToken(Token): + id = '' + +class DocumentEndToken(Token): + id = '' + +class StreamStartToken(Token): + id = '' + def __init__(self, start_mark=None, end_mark=None, + encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndToken(Token): + id = '' + +class BlockSequenceStartToken(Token): + id = '' + +class BlockMappingStartToken(Token): + id = '' + +class BlockEndToken(Token): + id = '' + +class FlowSequenceStartToken(Token): + id = '[' + +class FlowMappingStartToken(Token): + id = '{' + +class FlowSequenceEndToken(Token): + id = ']' + +class FlowMappingEndToken(Token): + id = '}' + +class KeyToken(Token): + id = '?' + +class ValueToken(Token): + id = ':' + +class BlockEntryToken(Token): + id = '-' + +class FlowEntryToken(Token): + id = ',' + +class AliasToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class AnchorToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class TagToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class ScalarToken(Token): + id = '' + def __init__(self, value, plain, start_mark, end_mark, style=None): + self.value = value + self.plain = plain + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + diff --git a/.venv/pyvenv.cfg b/.venv/pyvenv.cfg new file mode 100644 index 0000000000000000000000000000000000000000..93d4a9710c494d889192f1f80e136357096fa794 --- /dev/null +++ b/.venv/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /opt/homebrew/opt/python@3.14/bin +include-system-site-packages = false +version = 3.14.3 +executable = /opt/homebrew/Cellar/python@3.14/3.14.3_1/Frameworks/Python.framework/Versions/3.14/bin/python3.14 +command = /opt/homebrew/opt/python@3.14/bin/python3.14 -m venv /Users/emreyoleri/Desktop/smart-traffic-vision/.venv diff --git a/.venv/share/jupyter/nbextensions/pydeck/extensionRequires.js b/.venv/share/jupyter/nbextensions/pydeck/extensionRequires.js new file mode 100644 index 0000000000000000000000000000000000000000..f663cc192edcb23360ed281390a083352c743434 --- /dev/null +++ b/.venv/share/jupyter/nbextensions/pydeck/extensionRequires.js @@ -0,0 +1,15 @@ +/* eslint-disable */ +define(function() { + 'use strict'; + requirejs.config({ + map: { + '*': { + '@deck.gl/jupyter-widget': 'nbextensions/pydeck/index' + } + } + }); + // Export the required load_ipython_extension function + return { + load_ipython_extension: function() {} + }; +}); diff --git a/.venv/share/jupyter/nbextensions/pydeck/index.js b/.venv/share/jupyter/nbextensions/pydeck/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d5fa43d7bea5b2e7d0d0aad29df7de12b1052b26 --- /dev/null +++ b/.venv/share/jupyter/nbextensions/pydeck/index.js @@ -0,0 +1,8915 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if (typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if (typeof define === 'function' && define.amd) define([], factory); + else { + var a = factory(); + for (var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; +}})(globalThis, function () { +"use strict";var __exports__=(()=>{var Cwe=Object.create;var iM=Object.defineProperty;var Mwe=Object.getOwnPropertyDescriptor;var Iwe=Object.getOwnPropertyNames;var Pwe=Object.getPrototypeOf,Rwe=Object.prototype.hasOwnProperty;var Bwe=(r,e,t)=>e in r?iM(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var qY=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+r+'" is not supported')});var Nt=(r,e)=>()=>(r&&(e=r(r=0)),e);var Vi=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),Wa=(r,e)=>{for(var t in e)iM(r,t,{get:e[t],enumerable:!0})},$Y=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Iwe(e))!Rwe.call(r,n)&&n!==t&&iM(r,n,{get:()=>e[n],enumerable:!(i=Mwe(e,n))||i.enumerable});return r};var Mf=(r,e,t)=>(t=r!=null?Cwe(Pwe(r)):{},$Y(e||!r||!r.__esModule?iM(t,"default",{value:r,enumerable:!0}):t,r)),XY=r=>$Y(iM({},"__esModule",{value:!0}),r);var rn=(r,e,t)=>(Bwe(r,typeof e!="symbol"?e+"":e,t),t);var HL,Nwe,WL,zwe,YY,Q5=Nt(()=>{HL=globalThis,Nwe=globalThis.document||{},WL=globalThis.process||{},zwe=globalThis.console,YY=globalThis.navigator||{}});function qL(r){if(typeof window<"u"&&window.process?.type==="renderer"||typeof process<"u"&&Boolean(process.versions?.electron))return!0;let e=typeof navigator<"u"&&navigator.userAgent,t=r||e;return Boolean(t&&t.indexOf("Electron")>=0)}var J5=Nt(()=>{});function rp(){return!(typeof process=="object"&&String(process)==="[object process]"&&!process?.browser)||qL()}var e8=Nt(()=>{J5()});function t8(r){return!r&&!rp()?"Node":qL(r)?"Electron":(r||YY.userAgent||"").indexOf("Edge")>-1?"Edge":globalThis.chrome?"Chrome":globalThis.safari?"Safari":globalThis.mozInnerScreenX?"Firefox":"Unknown"}var KY=Nt(()=>{e8();J5();Q5()});var r8,_v=Nt(()=>{Q5();e8();KY();r8="4.1.1"});function M2(r,e){if(!r)throw new Error(e||"Assertion failed")}var i8=Nt(()=>{});function n8(r){if(!r)return 0;let e;switch(typeof r){case"number":e=r;break;case"object":e=r.logLevel||r.priority||0;break;default:return 0}return M2(Number.isFinite(e)&&e>=0),e}function QY(r){let{logLevel:e,message:t}=r;r.logLevel=n8(e);let i=r.args?Array.from(r.args):[];for(;i.length&&i.shift()!==t;);switch(typeof e){case"string":case"function":t!==void 0&&i.unshift(t),r.message=e;break;case"object":Object.assign(r,e);break;default:}typeof r.message=="function"&&(r.message=r.message());let n=typeof r.message;return M2(n==="string"||n==="object"),Object.assign(r,{args:i},r.opts)}var JY=Nt(()=>{i8()});var _b,$L,eK=Nt(()=>{JY();_b=()=>{},$L=class{constructor({level:e=0}={}){this.userData={},this._onceCache=new Set,this._level=e}set level(e){this.setLevel(e)}get level(){return this.getLevel()}setLevel(e){return this._level=e,this}getLevel(){return this._level}warn(e,...t){return this._log("warn",0,e,t,{once:!0})}error(e,...t){return this._log("error",0,e,t)}log(e,t,...i){return this._log("log",e,t,i)}info(e,t,...i){return this._log("info",e,t,i)}once(e,t,...i){return this._log("once",e,t,i,{once:!0})}_log(e,t,i,n,o={}){let s=QY({logLevel:t,message:i,args:this._buildArgs(t,i,n),opts:o});return this._createLogFunction(e,s,o)}_buildArgs(e,t,i){return[e,t,...i]}_createLogFunction(e,t,i){if(!this._shouldLog(t.logLevel))return _b;let n=this._getOnceTag(i.tag??t.tag??t.message);if((i.once||t.once)&&n!==void 0){if(this._onceCache.has(n))return _b;this._onceCache.add(n)}return this._emit(e,t)}_shouldLog(e){return this.getLevel()>=n8(e)}_getOnceTag(e){if(e!==void 0)try{return typeof e=="string"?e:String(e)}catch{return}}}});function Vwe(r){try{let e=window[r],t="__storage_test__";return e.setItem(t,t),e.removeItem(t),e}catch{return null}}var XL,tK=Nt(()=>{XL=class{constructor(e,t,i="sessionStorage"){this.storage=Vwe(i),this.id=e,this.config=t,this._loadConfiguration()}getConfiguration(){return this.config}setConfiguration(e){if(Object.assign(this.config,e),this.storage){let t=JSON.stringify(this.config);this.storage.setItem(this.id,t)}}_loadConfiguration(){let e={};if(this.storage){let t=this.storage.getItem(this.id);e=t?JSON.parse(t):{}}return Object.assign(this.config,e),this}}});function rK(r){let e;return r<10?e=`${r.toFixed(2)}ms`:r<100?e=`${r.toFixed(1)}ms`:r<1e3?e=`${r.toFixed(0)}ms`:e=`${(r/1e3).toFixed(2)}s`,e}function iK(r,e=8){let t=Math.max(e-r.length,0);return`${" ".repeat(t)}${r}`}var nK=Nt(()=>{});function oK(r){return typeof r!="string"?r:(r=r.toUpperCase(),ZL[r]||ZL.WHITE)}function sK(r,e,t){return!rp&&typeof r=="string"&&(e&&(r=`\x1B[${oK(e)}m${r}\x1B[39m`),t&&(r=`\x1B[${oK(t)+jwe}m${r}\x1B[49m`)),r}var ZL,jwe,aK=Nt(()=>{_v();(function(r){r[r.BLACK=30]="BLACK",r[r.RED=31]="RED",r[r.GREEN=32]="GREEN",r[r.YELLOW=33]="YELLOW",r[r.BLUE=34]="BLUE",r[r.MAGENTA=35]="MAGENTA",r[r.CYAN=36]="CYAN",r[r.WHITE=37]="WHITE",r[r.BRIGHT_BLACK=90]="BRIGHT_BLACK",r[r.BRIGHT_RED=91]="BRIGHT_RED",r[r.BRIGHT_GREEN=92]="BRIGHT_GREEN",r[r.BRIGHT_YELLOW=93]="BRIGHT_YELLOW",r[r.BRIGHT_BLUE=94]="BRIGHT_BLUE",r[r.BRIGHT_MAGENTA=95]="BRIGHT_MAGENTA",r[r.BRIGHT_CYAN=96]="BRIGHT_CYAN",r[r.BRIGHT_WHITE=97]="BRIGHT_WHITE"})(ZL||(ZL={}));jwe=10});function lK(r,e=["constructor"]){let t=Object.getPrototypeOf(r),i=Object.getOwnPropertyNames(t),n=r;for(let o of i){let s=n[o];typeof s=="function"&&(e.find(c=>o===c)||(n[o]=s.bind(r)))}}var cK=Nt(()=>{});function I2(){let r;if(rp()&&HL.performance)r=HL?.performance?.now?.();else if("hrtime"in WL){let e=WL?.hrtime?.();r=e[0]*1e3+e[1]/1e6}else r=Date.now();return r}var uK=Nt(()=>{_v()});function Gwe(r,e,t){if(typeof e=="string"){let i=t.time?iK(rK(t.total)):"";e=t.time?`${r}: ${i} ${e}`:`${r}: ${e}`,e=sK(e,t.color,t.background)}return e}function Hwe(r){for(let e in r)for(let t in r[e])return t||"untitled";return"empty"}var P2,o8,ip,s8=Nt(()=>{_v();eK();tK();nK();aK();cK();i8();uK();P2={debug:rp()&&console.debug||console.log,log:console.log,info:console.info,warn:console.warn,error:console.error},o8={enabled:!0,level:0},ip=class extends $L{constructor({id:e}={id:""}){super({level:0}),this.VERSION=r8,this._startTs=I2(),this._deltaTs=I2(),this.userData={},this.LOG_THROTTLE_TIMEOUT=0,this.id=e,this.userData={},this._storage=new XL(`__probe-${this.id}__`,{[this.id]:o8}),this.timeStamp(`${this.id} started`),lK(this),Object.seal(this)}isEnabled(){return this._getConfiguration().enabled}getLevel(){return this._getConfiguration().level}getTotal(){return Number((I2()-this._startTs).toPrecision(10))}getDelta(){return Number((I2()-this._deltaTs).toPrecision(10))}set priority(e){this.level=e}get priority(){return this.level}getPriority(){return this.level}enable(e=!0){return this._updateConfiguration({enabled:e}),this}setLevel(e){return this._updateConfiguration({level:e}),this}get(e){return this._getConfiguration()[e]}set(e,t){this._updateConfiguration({[e]:t})}settings(){console.table?console.table(this._storage.config):console.log(this._storage.config)}assert(e,t){if(!e)throw new Error(t||"Assertion failed")}warn(e,...t){return this._log("warn",0,e,t,{method:P2.warn,once:!0})}error(e,...t){return this._log("error",0,e,t,{method:P2.error})}deprecated(e,t){return this.warn(`\`${e}\` is deprecated and will be removed in a later version. Use \`${t}\` instead`)}removed(e,t){return this.error(`\`${e}\` has been removed. Use \`${t}\` instead`)}probe(e,t,...i){return this._log("log",e,t,i,{method:P2.log,time:!0,once:!0})}log(e,t,...i){return this._log("log",e,t,i,{method:P2.debug})}info(e,t,...i){return this._log("info",e,t,i,{method:console.info})}once(e,t,...i){return this._log("once",e,t,i,{method:P2.debug||P2.info,once:!0})}table(e,t,i){return t?this._log("table",e,t,i&&[i]||[],{method:console.table||_b,tag:Hwe(t)}):_b}time(e,t){return this._log("time",e,t,[],{method:console.time?console.time:console.info})}timeEnd(e,t){return this._log("time",e,t,[],{method:console.timeEnd?console.timeEnd:console.info})}timeStamp(e,t){return this._log("time",e,t,[],{method:console.timeStamp||_b})}group(e,t,i={collapsed:!1}){let n=(i.collapsed?console.groupCollapsed:console.group)||console.info;return this._log("group",e,t,[],{method:n})}groupCollapsed(e,t,i={}){return this.group(e,t,Object.assign({},i,{collapsed:!0}))}groupEnd(e){return this._log("groupEnd",e,"",[],{method:console.groupEnd||_b})}withGroup(e,t,i){this.group(e,t)();try{i()}finally{this.groupEnd(e)()}}trace(){console.trace&&console.trace()}_shouldLog(e){return this.isEnabled()&&super._shouldLog(e)}_emit(e,t){let i=t.method;M2(i),t.total=this.getTotal(),t.delta=this.getDelta(),this._deltaTs=I2();let n=Gwe(this.id,t.message,t);return i.bind(console,n,...t.args)}_getConfiguration(){return this._storage.config[this.id]||this._updateConfiguration(o8),this._storage.config[this.id]}_updateConfiguration(e){let t=this._storage.config[this.id]||{...o8};this._storage.setConfiguration({[this.id]:{...t,...e}})}};ip.VERSION=r8});var hK=Nt(()=>{globalThis.probe={}});var a8,R2=Nt(()=>{s8();s8();hK();a8=new ip({id:"@probe.gl/log"})});function fM(){let r;if(typeof window<"u"&&window.performance)r=window.performance.now();else if(typeof process<"u"&&process.hrtime){let e=process.hrtime();r=e[0]*1e3+e[1]/1e6}else r=Date.now();return r}var C8=Nt(()=>{});var wb,M8=Nt(()=>{C8();wb=class{constructor(e,t){this.sampleSize=1,this.time=0,this.count=0,this.samples=0,this.lastTiming=0,this.lastSampleTime=0,this.lastSampleCount=0,this._count=0,this._time=0,this._samples=0,this._startTime=0,this._timerPending=!1,this.name=e,this.type=t,this.reset()}reset(){return this.time=0,this.count=0,this.samples=0,this.lastTiming=0,this.lastSampleTime=0,this.lastSampleCount=0,this._count=0,this._time=0,this._samples=0,this._startTime=0,this._timerPending=!1,this}setSampleSize(e){return this.sampleSize=e,this}incrementCount(){return this.addCount(1),this}decrementCount(){return this.subtractCount(1),this}addCount(e){return this._count+=e,this._samples++,this._checkSampling(),this}subtractCount(e){return this._count-=e,this._samples++,this._checkSampling(),this}addTime(e){return this._time+=e,this.lastTiming=e,this._samples++,this._checkSampling(),this}timeStart(){return this._startTime=fM(),this._timerPending=!0,this}timeEnd(){return this._timerPending?(this.addTime(fM()-this._startTime),this._timerPending=!1,this._checkSampling(),this):this}getSampleAverageCount(){return this.sampleSize>0?this.lastSampleCount/this.sampleSize:0}getSampleAverageTime(){return this.sampleSize>0?this.lastSampleTime/this.sampleSize:0}getSampleHz(){return this.lastSampleTime>0?this.sampleSize/(this.lastSampleTime/1e3):0}getAverageCount(){return this.samples>0?this.count/this.samples:0}getAverageTime(){return this.samples>0?this.time/this.samples:0}getHz(){return this.time>0?this.samples/(this.time/1e3):0}_checkSampling(){this._samples===this.sampleSize&&(this.lastSampleTime=this._time,this.lastSampleCount=this._count,this.count+=this._count,this.time+=this._time,this.samples+=this._samples,this._time=0,this._count=0,this._samples=0)}}});var Ed,CK=Nt(()=>{M8();Ed=class{constructor(e){this.stats={},this.id=e.id,this.stats={},this._initializeStats(e.stats),Object.seal(this)}get(e,t="count"){return this._getOrCreate({name:e,type:t})}get size(){return Object.keys(this.stats).length}reset(){for(let e of Object.values(this.stats))e.reset();return this}forEach(e){for(let t of Object.values(this.stats))e(t)}getTable(){let e={};return this.forEach(t=>{e[t.name]={time:t.time||0,count:t.count||0,average:t.getAverageTime()||0,hz:t.getHz()||0}}),e}_initializeStats(e=[]){e.forEach(t=>this._getOrCreate(t))}_getOrCreate(e){let{name:t,type:i}=e,n=this.stats[t];return n||(e instanceof wb?n=e:n=new wb(t,i),this.stats[t]=n),n}}});var Tb=Nt(()=>{CK();M8();C8()});var jA,yee=Nt(()=>{(function(r){r[r.DEPTH_BUFFER_BIT=256]="DEPTH_BUFFER_BIT",r[r.STENCIL_BUFFER_BIT=1024]="STENCIL_BUFFER_BIT",r[r.COLOR_BUFFER_BIT=16384]="COLOR_BUFFER_BIT",r[r.POINTS=0]="POINTS",r[r.LINES=1]="LINES",r[r.LINE_LOOP=2]="LINE_LOOP",r[r.LINE_STRIP=3]="LINE_STRIP",r[r.TRIANGLES=4]="TRIANGLES",r[r.TRIANGLE_STRIP=5]="TRIANGLE_STRIP",r[r.TRIANGLE_FAN=6]="TRIANGLE_FAN",r[r.ZERO=0]="ZERO",r[r.ONE=1]="ONE",r[r.SRC_COLOR=768]="SRC_COLOR",r[r.ONE_MINUS_SRC_COLOR=769]="ONE_MINUS_SRC_COLOR",r[r.SRC_ALPHA=770]="SRC_ALPHA",r[r.ONE_MINUS_SRC_ALPHA=771]="ONE_MINUS_SRC_ALPHA",r[r.DST_ALPHA=772]="DST_ALPHA",r[r.ONE_MINUS_DST_ALPHA=773]="ONE_MINUS_DST_ALPHA",r[r.DST_COLOR=774]="DST_COLOR",r[r.ONE_MINUS_DST_COLOR=775]="ONE_MINUS_DST_COLOR",r[r.SRC_ALPHA_SATURATE=776]="SRC_ALPHA_SATURATE",r[r.CONSTANT_COLOR=32769]="CONSTANT_COLOR",r[r.ONE_MINUS_CONSTANT_COLOR=32770]="ONE_MINUS_CONSTANT_COLOR",r[r.CONSTANT_ALPHA=32771]="CONSTANT_ALPHA",r[r.ONE_MINUS_CONSTANT_ALPHA=32772]="ONE_MINUS_CONSTANT_ALPHA",r[r.FUNC_ADD=32774]="FUNC_ADD",r[r.FUNC_SUBTRACT=32778]="FUNC_SUBTRACT",r[r.FUNC_REVERSE_SUBTRACT=32779]="FUNC_REVERSE_SUBTRACT",r[r.BLEND_EQUATION=32777]="BLEND_EQUATION",r[r.BLEND_EQUATION_RGB=32777]="BLEND_EQUATION_RGB",r[r.BLEND_EQUATION_ALPHA=34877]="BLEND_EQUATION_ALPHA",r[r.BLEND_DST_RGB=32968]="BLEND_DST_RGB",r[r.BLEND_SRC_RGB=32969]="BLEND_SRC_RGB",r[r.BLEND_DST_ALPHA=32970]="BLEND_DST_ALPHA",r[r.BLEND_SRC_ALPHA=32971]="BLEND_SRC_ALPHA",r[r.BLEND_COLOR=32773]="BLEND_COLOR",r[r.ARRAY_BUFFER_BINDING=34964]="ARRAY_BUFFER_BINDING",r[r.ELEMENT_ARRAY_BUFFER_BINDING=34965]="ELEMENT_ARRAY_BUFFER_BINDING",r[r.LINE_WIDTH=2849]="LINE_WIDTH",r[r.ALIASED_POINT_SIZE_RANGE=33901]="ALIASED_POINT_SIZE_RANGE",r[r.ALIASED_LINE_WIDTH_RANGE=33902]="ALIASED_LINE_WIDTH_RANGE",r[r.CULL_FACE_MODE=2885]="CULL_FACE_MODE",r[r.FRONT_FACE=2886]="FRONT_FACE",r[r.DEPTH_RANGE=2928]="DEPTH_RANGE",r[r.DEPTH_WRITEMASK=2930]="DEPTH_WRITEMASK",r[r.DEPTH_CLEAR_VALUE=2931]="DEPTH_CLEAR_VALUE",r[r.DEPTH_FUNC=2932]="DEPTH_FUNC",r[r.STENCIL_CLEAR_VALUE=2961]="STENCIL_CLEAR_VALUE",r[r.STENCIL_FUNC=2962]="STENCIL_FUNC",r[r.STENCIL_FAIL=2964]="STENCIL_FAIL",r[r.STENCIL_PASS_DEPTH_FAIL=2965]="STENCIL_PASS_DEPTH_FAIL",r[r.STENCIL_PASS_DEPTH_PASS=2966]="STENCIL_PASS_DEPTH_PASS",r[r.STENCIL_REF=2967]="STENCIL_REF",r[r.STENCIL_VALUE_MASK=2963]="STENCIL_VALUE_MASK",r[r.STENCIL_WRITEMASK=2968]="STENCIL_WRITEMASK",r[r.STENCIL_BACK_FUNC=34816]="STENCIL_BACK_FUNC",r[r.STENCIL_BACK_FAIL=34817]="STENCIL_BACK_FAIL",r[r.STENCIL_BACK_PASS_DEPTH_FAIL=34818]="STENCIL_BACK_PASS_DEPTH_FAIL",r[r.STENCIL_BACK_PASS_DEPTH_PASS=34819]="STENCIL_BACK_PASS_DEPTH_PASS",r[r.STENCIL_BACK_REF=36003]="STENCIL_BACK_REF",r[r.STENCIL_BACK_VALUE_MASK=36004]="STENCIL_BACK_VALUE_MASK",r[r.STENCIL_BACK_WRITEMASK=36005]="STENCIL_BACK_WRITEMASK",r[r.VIEWPORT=2978]="VIEWPORT",r[r.SCISSOR_BOX=3088]="SCISSOR_BOX",r[r.COLOR_CLEAR_VALUE=3106]="COLOR_CLEAR_VALUE",r[r.COLOR_WRITEMASK=3107]="COLOR_WRITEMASK",r[r.UNPACK_ALIGNMENT=3317]="UNPACK_ALIGNMENT",r[r.PACK_ALIGNMENT=3333]="PACK_ALIGNMENT",r[r.MAX_TEXTURE_SIZE=3379]="MAX_TEXTURE_SIZE",r[r.MAX_VIEWPORT_DIMS=3386]="MAX_VIEWPORT_DIMS",r[r.SUBPIXEL_BITS=3408]="SUBPIXEL_BITS",r[r.RED_BITS=3410]="RED_BITS",r[r.GREEN_BITS=3411]="GREEN_BITS",r[r.BLUE_BITS=3412]="BLUE_BITS",r[r.ALPHA_BITS=3413]="ALPHA_BITS",r[r.DEPTH_BITS=3414]="DEPTH_BITS",r[r.STENCIL_BITS=3415]="STENCIL_BITS",r[r.POLYGON_OFFSET_UNITS=10752]="POLYGON_OFFSET_UNITS",r[r.POLYGON_OFFSET_FACTOR=32824]="POLYGON_OFFSET_FACTOR",r[r.TEXTURE_BINDING_2D=32873]="TEXTURE_BINDING_2D",r[r.SAMPLE_BUFFERS=32936]="SAMPLE_BUFFERS",r[r.SAMPLES=32937]="SAMPLES",r[r.SAMPLE_COVERAGE_VALUE=32938]="SAMPLE_COVERAGE_VALUE",r[r.SAMPLE_COVERAGE_INVERT=32939]="SAMPLE_COVERAGE_INVERT",r[r.COMPRESSED_TEXTURE_FORMATS=34467]="COMPRESSED_TEXTURE_FORMATS",r[r.VENDOR=7936]="VENDOR",r[r.RENDERER=7937]="RENDERER",r[r.VERSION=7938]="VERSION",r[r.IMPLEMENTATION_COLOR_READ_TYPE=35738]="IMPLEMENTATION_COLOR_READ_TYPE",r[r.IMPLEMENTATION_COLOR_READ_FORMAT=35739]="IMPLEMENTATION_COLOR_READ_FORMAT",r[r.BROWSER_DEFAULT_WEBGL=37444]="BROWSER_DEFAULT_WEBGL",r[r.STATIC_DRAW=35044]="STATIC_DRAW",r[r.STREAM_DRAW=35040]="STREAM_DRAW",r[r.DYNAMIC_DRAW=35048]="DYNAMIC_DRAW",r[r.ARRAY_BUFFER=34962]="ARRAY_BUFFER",r[r.ELEMENT_ARRAY_BUFFER=34963]="ELEMENT_ARRAY_BUFFER",r[r.BUFFER_SIZE=34660]="BUFFER_SIZE",r[r.BUFFER_USAGE=34661]="BUFFER_USAGE",r[r.CURRENT_VERTEX_ATTRIB=34342]="CURRENT_VERTEX_ATTRIB",r[r.VERTEX_ATTRIB_ARRAY_ENABLED=34338]="VERTEX_ATTRIB_ARRAY_ENABLED",r[r.VERTEX_ATTRIB_ARRAY_SIZE=34339]="VERTEX_ATTRIB_ARRAY_SIZE",r[r.VERTEX_ATTRIB_ARRAY_STRIDE=34340]="VERTEX_ATTRIB_ARRAY_STRIDE",r[r.VERTEX_ATTRIB_ARRAY_TYPE=34341]="VERTEX_ATTRIB_ARRAY_TYPE",r[r.VERTEX_ATTRIB_ARRAY_NORMALIZED=34922]="VERTEX_ATTRIB_ARRAY_NORMALIZED",r[r.VERTEX_ATTRIB_ARRAY_POINTER=34373]="VERTEX_ATTRIB_ARRAY_POINTER",r[r.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING=34975]="VERTEX_ATTRIB_ARRAY_BUFFER_BINDING",r[r.CULL_FACE=2884]="CULL_FACE",r[r.FRONT=1028]="FRONT",r[r.BACK=1029]="BACK",r[r.FRONT_AND_BACK=1032]="FRONT_AND_BACK",r[r.BLEND=3042]="BLEND",r[r.DEPTH_TEST=2929]="DEPTH_TEST",r[r.DITHER=3024]="DITHER",r[r.POLYGON_OFFSET_FILL=32823]="POLYGON_OFFSET_FILL",r[r.SAMPLE_ALPHA_TO_COVERAGE=32926]="SAMPLE_ALPHA_TO_COVERAGE",r[r.SAMPLE_COVERAGE=32928]="SAMPLE_COVERAGE",r[r.SCISSOR_TEST=3089]="SCISSOR_TEST",r[r.STENCIL_TEST=2960]="STENCIL_TEST",r[r.NO_ERROR=0]="NO_ERROR",r[r.INVALID_ENUM=1280]="INVALID_ENUM",r[r.INVALID_VALUE=1281]="INVALID_VALUE",r[r.INVALID_OPERATION=1282]="INVALID_OPERATION",r[r.OUT_OF_MEMORY=1285]="OUT_OF_MEMORY",r[r.CONTEXT_LOST_WEBGL=37442]="CONTEXT_LOST_WEBGL",r[r.CW=2304]="CW",r[r.CCW=2305]="CCW",r[r.DONT_CARE=4352]="DONT_CARE",r[r.FASTEST=4353]="FASTEST",r[r.NICEST=4354]="NICEST",r[r.GENERATE_MIPMAP_HINT=33170]="GENERATE_MIPMAP_HINT",r[r.BYTE=5120]="BYTE",r[r.UNSIGNED_BYTE=5121]="UNSIGNED_BYTE",r[r.SHORT=5122]="SHORT",r[r.UNSIGNED_SHORT=5123]="UNSIGNED_SHORT",r[r.INT=5124]="INT",r[r.UNSIGNED_INT=5125]="UNSIGNED_INT",r[r.FLOAT=5126]="FLOAT",r[r.DOUBLE=5130]="DOUBLE",r[r.DEPTH_COMPONENT=6402]="DEPTH_COMPONENT",r[r.ALPHA=6406]="ALPHA",r[r.RGB=6407]="RGB",r[r.RGBA=6408]="RGBA",r[r.LUMINANCE=6409]="LUMINANCE",r[r.LUMINANCE_ALPHA=6410]="LUMINANCE_ALPHA",r[r.UNSIGNED_SHORT_4_4_4_4=32819]="UNSIGNED_SHORT_4_4_4_4",r[r.UNSIGNED_SHORT_5_5_5_1=32820]="UNSIGNED_SHORT_5_5_5_1",r[r.UNSIGNED_SHORT_5_6_5=33635]="UNSIGNED_SHORT_5_6_5",r[r.FRAGMENT_SHADER=35632]="FRAGMENT_SHADER",r[r.VERTEX_SHADER=35633]="VERTEX_SHADER",r[r.COMPILE_STATUS=35713]="COMPILE_STATUS",r[r.DELETE_STATUS=35712]="DELETE_STATUS",r[r.LINK_STATUS=35714]="LINK_STATUS",r[r.VALIDATE_STATUS=35715]="VALIDATE_STATUS",r[r.ATTACHED_SHADERS=35717]="ATTACHED_SHADERS",r[r.ACTIVE_ATTRIBUTES=35721]="ACTIVE_ATTRIBUTES",r[r.ACTIVE_UNIFORMS=35718]="ACTIVE_UNIFORMS",r[r.MAX_VERTEX_ATTRIBS=34921]="MAX_VERTEX_ATTRIBS",r[r.MAX_VERTEX_UNIFORM_VECTORS=36347]="MAX_VERTEX_UNIFORM_VECTORS",r[r.MAX_VARYING_VECTORS=36348]="MAX_VARYING_VECTORS",r[r.MAX_COMBINED_TEXTURE_IMAGE_UNITS=35661]="MAX_COMBINED_TEXTURE_IMAGE_UNITS",r[r.MAX_VERTEX_TEXTURE_IMAGE_UNITS=35660]="MAX_VERTEX_TEXTURE_IMAGE_UNITS",r[r.MAX_TEXTURE_IMAGE_UNITS=34930]="MAX_TEXTURE_IMAGE_UNITS",r[r.MAX_FRAGMENT_UNIFORM_VECTORS=36349]="MAX_FRAGMENT_UNIFORM_VECTORS",r[r.SHADER_TYPE=35663]="SHADER_TYPE",r[r.SHADING_LANGUAGE_VERSION=35724]="SHADING_LANGUAGE_VERSION",r[r.CURRENT_PROGRAM=35725]="CURRENT_PROGRAM",r[r.NEVER=512]="NEVER",r[r.LESS=513]="LESS",r[r.EQUAL=514]="EQUAL",r[r.LEQUAL=515]="LEQUAL",r[r.GREATER=516]="GREATER",r[r.NOTEQUAL=517]="NOTEQUAL",r[r.GEQUAL=518]="GEQUAL",r[r.ALWAYS=519]="ALWAYS",r[r.KEEP=7680]="KEEP",r[r.REPLACE=7681]="REPLACE",r[r.INCR=7682]="INCR",r[r.DECR=7683]="DECR",r[r.INVERT=5386]="INVERT",r[r.INCR_WRAP=34055]="INCR_WRAP",r[r.DECR_WRAP=34056]="DECR_WRAP",r[r.NEAREST=9728]="NEAREST",r[r.LINEAR=9729]="LINEAR",r[r.NEAREST_MIPMAP_NEAREST=9984]="NEAREST_MIPMAP_NEAREST",r[r.LINEAR_MIPMAP_NEAREST=9985]="LINEAR_MIPMAP_NEAREST",r[r.NEAREST_MIPMAP_LINEAR=9986]="NEAREST_MIPMAP_LINEAR",r[r.LINEAR_MIPMAP_LINEAR=9987]="LINEAR_MIPMAP_LINEAR",r[r.TEXTURE_MAG_FILTER=10240]="TEXTURE_MAG_FILTER",r[r.TEXTURE_MIN_FILTER=10241]="TEXTURE_MIN_FILTER",r[r.TEXTURE_WRAP_S=10242]="TEXTURE_WRAP_S",r[r.TEXTURE_WRAP_T=10243]="TEXTURE_WRAP_T",r[r.TEXTURE_2D=3553]="TEXTURE_2D",r[r.TEXTURE=5890]="TEXTURE",r[r.TEXTURE_CUBE_MAP=34067]="TEXTURE_CUBE_MAP",r[r.TEXTURE_BINDING_CUBE_MAP=34068]="TEXTURE_BINDING_CUBE_MAP",r[r.TEXTURE_CUBE_MAP_POSITIVE_X=34069]="TEXTURE_CUBE_MAP_POSITIVE_X",r[r.TEXTURE_CUBE_MAP_NEGATIVE_X=34070]="TEXTURE_CUBE_MAP_NEGATIVE_X",r[r.TEXTURE_CUBE_MAP_POSITIVE_Y=34071]="TEXTURE_CUBE_MAP_POSITIVE_Y",r[r.TEXTURE_CUBE_MAP_NEGATIVE_Y=34072]="TEXTURE_CUBE_MAP_NEGATIVE_Y",r[r.TEXTURE_CUBE_MAP_POSITIVE_Z=34073]="TEXTURE_CUBE_MAP_POSITIVE_Z",r[r.TEXTURE_CUBE_MAP_NEGATIVE_Z=34074]="TEXTURE_CUBE_MAP_NEGATIVE_Z",r[r.MAX_CUBE_MAP_TEXTURE_SIZE=34076]="MAX_CUBE_MAP_TEXTURE_SIZE",r[r.TEXTURE0=33984]="TEXTURE0",r[r.ACTIVE_TEXTURE=34016]="ACTIVE_TEXTURE",r[r.REPEAT=10497]="REPEAT",r[r.CLAMP_TO_EDGE=33071]="CLAMP_TO_EDGE",r[r.MIRRORED_REPEAT=33648]="MIRRORED_REPEAT",r[r.TEXTURE_WIDTH=4096]="TEXTURE_WIDTH",r[r.TEXTURE_HEIGHT=4097]="TEXTURE_HEIGHT",r[r.FLOAT_VEC2=35664]="FLOAT_VEC2",r[r.FLOAT_VEC3=35665]="FLOAT_VEC3",r[r.FLOAT_VEC4=35666]="FLOAT_VEC4",r[r.INT_VEC2=35667]="INT_VEC2",r[r.INT_VEC3=35668]="INT_VEC3",r[r.INT_VEC4=35669]="INT_VEC4",r[r.BOOL=35670]="BOOL",r[r.BOOL_VEC2=35671]="BOOL_VEC2",r[r.BOOL_VEC3=35672]="BOOL_VEC3",r[r.BOOL_VEC4=35673]="BOOL_VEC4",r[r.FLOAT_MAT2=35674]="FLOAT_MAT2",r[r.FLOAT_MAT3=35675]="FLOAT_MAT3",r[r.FLOAT_MAT4=35676]="FLOAT_MAT4",r[r.SAMPLER_2D=35678]="SAMPLER_2D",r[r.SAMPLER_CUBE=35680]="SAMPLER_CUBE",r[r.LOW_FLOAT=36336]="LOW_FLOAT",r[r.MEDIUM_FLOAT=36337]="MEDIUM_FLOAT",r[r.HIGH_FLOAT=36338]="HIGH_FLOAT",r[r.LOW_INT=36339]="LOW_INT",r[r.MEDIUM_INT=36340]="MEDIUM_INT",r[r.HIGH_INT=36341]="HIGH_INT",r[r.FRAMEBUFFER=36160]="FRAMEBUFFER",r[r.RENDERBUFFER=36161]="RENDERBUFFER",r[r.RGBA4=32854]="RGBA4",r[r.RGB5_A1=32855]="RGB5_A1",r[r.RGB565=36194]="RGB565",r[r.DEPTH_COMPONENT16=33189]="DEPTH_COMPONENT16",r[r.STENCIL_INDEX=6401]="STENCIL_INDEX",r[r.STENCIL_INDEX8=36168]="STENCIL_INDEX8",r[r.DEPTH_STENCIL=34041]="DEPTH_STENCIL",r[r.RENDERBUFFER_WIDTH=36162]="RENDERBUFFER_WIDTH",r[r.RENDERBUFFER_HEIGHT=36163]="RENDERBUFFER_HEIGHT",r[r.RENDERBUFFER_INTERNAL_FORMAT=36164]="RENDERBUFFER_INTERNAL_FORMAT",r[r.RENDERBUFFER_RED_SIZE=36176]="RENDERBUFFER_RED_SIZE",r[r.RENDERBUFFER_GREEN_SIZE=36177]="RENDERBUFFER_GREEN_SIZE",r[r.RENDERBUFFER_BLUE_SIZE=36178]="RENDERBUFFER_BLUE_SIZE",r[r.RENDERBUFFER_ALPHA_SIZE=36179]="RENDERBUFFER_ALPHA_SIZE",r[r.RENDERBUFFER_DEPTH_SIZE=36180]="RENDERBUFFER_DEPTH_SIZE",r[r.RENDERBUFFER_STENCIL_SIZE=36181]="RENDERBUFFER_STENCIL_SIZE",r[r.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE=36048]="FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE",r[r.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME=36049]="FRAMEBUFFER_ATTACHMENT_OBJECT_NAME",r[r.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL=36050]="FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL",r[r.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE=36051]="FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE",r[r.COLOR_ATTACHMENT0=36064]="COLOR_ATTACHMENT0",r[r.DEPTH_ATTACHMENT=36096]="DEPTH_ATTACHMENT",r[r.STENCIL_ATTACHMENT=36128]="STENCIL_ATTACHMENT",r[r.DEPTH_STENCIL_ATTACHMENT=33306]="DEPTH_STENCIL_ATTACHMENT",r[r.NONE=0]="NONE",r[r.FRAMEBUFFER_COMPLETE=36053]="FRAMEBUFFER_COMPLETE",r[r.FRAMEBUFFER_INCOMPLETE_ATTACHMENT=36054]="FRAMEBUFFER_INCOMPLETE_ATTACHMENT",r[r.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT=36055]="FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT",r[r.FRAMEBUFFER_INCOMPLETE_DIMENSIONS=36057]="FRAMEBUFFER_INCOMPLETE_DIMENSIONS",r[r.FRAMEBUFFER_UNSUPPORTED=36061]="FRAMEBUFFER_UNSUPPORTED",r[r.FRAMEBUFFER_BINDING=36006]="FRAMEBUFFER_BINDING",r[r.RENDERBUFFER_BINDING=36007]="RENDERBUFFER_BINDING",r[r.READ_FRAMEBUFFER=36008]="READ_FRAMEBUFFER",r[r.DRAW_FRAMEBUFFER=36009]="DRAW_FRAMEBUFFER",r[r.MAX_RENDERBUFFER_SIZE=34024]="MAX_RENDERBUFFER_SIZE",r[r.INVALID_FRAMEBUFFER_OPERATION=1286]="INVALID_FRAMEBUFFER_OPERATION",r[r.UNPACK_FLIP_Y_WEBGL=37440]="UNPACK_FLIP_Y_WEBGL",r[r.UNPACK_PREMULTIPLY_ALPHA_WEBGL=37441]="UNPACK_PREMULTIPLY_ALPHA_WEBGL",r[r.UNPACK_COLORSPACE_CONVERSION_WEBGL=37443]="UNPACK_COLORSPACE_CONVERSION_WEBGL",r[r.READ_BUFFER=3074]="READ_BUFFER",r[r.UNPACK_ROW_LENGTH=3314]="UNPACK_ROW_LENGTH",r[r.UNPACK_SKIP_ROWS=3315]="UNPACK_SKIP_ROWS",r[r.UNPACK_SKIP_PIXELS=3316]="UNPACK_SKIP_PIXELS",r[r.PACK_ROW_LENGTH=3330]="PACK_ROW_LENGTH",r[r.PACK_SKIP_ROWS=3331]="PACK_SKIP_ROWS",r[r.PACK_SKIP_PIXELS=3332]="PACK_SKIP_PIXELS",r[r.TEXTURE_BINDING_3D=32874]="TEXTURE_BINDING_3D",r[r.UNPACK_SKIP_IMAGES=32877]="UNPACK_SKIP_IMAGES",r[r.UNPACK_IMAGE_HEIGHT=32878]="UNPACK_IMAGE_HEIGHT",r[r.MAX_3D_TEXTURE_SIZE=32883]="MAX_3D_TEXTURE_SIZE",r[r.MAX_ELEMENTS_VERTICES=33e3]="MAX_ELEMENTS_VERTICES",r[r.MAX_ELEMENTS_INDICES=33001]="MAX_ELEMENTS_INDICES",r[r.MAX_TEXTURE_LOD_BIAS=34045]="MAX_TEXTURE_LOD_BIAS",r[r.MAX_FRAGMENT_UNIFORM_COMPONENTS=35657]="MAX_FRAGMENT_UNIFORM_COMPONENTS",r[r.MAX_VERTEX_UNIFORM_COMPONENTS=35658]="MAX_VERTEX_UNIFORM_COMPONENTS",r[r.MAX_ARRAY_TEXTURE_LAYERS=35071]="MAX_ARRAY_TEXTURE_LAYERS",r[r.MIN_PROGRAM_TEXEL_OFFSET=35076]="MIN_PROGRAM_TEXEL_OFFSET",r[r.MAX_PROGRAM_TEXEL_OFFSET=35077]="MAX_PROGRAM_TEXEL_OFFSET",r[r.MAX_VARYING_COMPONENTS=35659]="MAX_VARYING_COMPONENTS",r[r.FRAGMENT_SHADER_DERIVATIVE_HINT=35723]="FRAGMENT_SHADER_DERIVATIVE_HINT",r[r.RASTERIZER_DISCARD=35977]="RASTERIZER_DISCARD",r[r.VERTEX_ARRAY_BINDING=34229]="VERTEX_ARRAY_BINDING",r[r.MAX_VERTEX_OUTPUT_COMPONENTS=37154]="MAX_VERTEX_OUTPUT_COMPONENTS",r[r.MAX_FRAGMENT_INPUT_COMPONENTS=37157]="MAX_FRAGMENT_INPUT_COMPONENTS",r[r.MAX_SERVER_WAIT_TIMEOUT=37137]="MAX_SERVER_WAIT_TIMEOUT",r[r.MAX_ELEMENT_INDEX=36203]="MAX_ELEMENT_INDEX",r[r.RED=6403]="RED",r[r.RGB8=32849]="RGB8",r[r.RGBA8=32856]="RGBA8",r[r.RGB10_A2=32857]="RGB10_A2",r[r.TEXTURE_3D=32879]="TEXTURE_3D",r[r.TEXTURE_WRAP_R=32882]="TEXTURE_WRAP_R",r[r.TEXTURE_MIN_LOD=33082]="TEXTURE_MIN_LOD",r[r.TEXTURE_MAX_LOD=33083]="TEXTURE_MAX_LOD",r[r.TEXTURE_BASE_LEVEL=33084]="TEXTURE_BASE_LEVEL",r[r.TEXTURE_MAX_LEVEL=33085]="TEXTURE_MAX_LEVEL",r[r.TEXTURE_COMPARE_MODE=34892]="TEXTURE_COMPARE_MODE",r[r.TEXTURE_COMPARE_FUNC=34893]="TEXTURE_COMPARE_FUNC",r[r.SRGB=35904]="SRGB",r[r.SRGB8=35905]="SRGB8",r[r.SRGB8_ALPHA8=35907]="SRGB8_ALPHA8",r[r.COMPARE_REF_TO_TEXTURE=34894]="COMPARE_REF_TO_TEXTURE",r[r.RGBA32F=34836]="RGBA32F",r[r.RGB32F=34837]="RGB32F",r[r.RGBA16F=34842]="RGBA16F",r[r.RGB16F=34843]="RGB16F",r[r.TEXTURE_2D_ARRAY=35866]="TEXTURE_2D_ARRAY",r[r.TEXTURE_BINDING_2D_ARRAY=35869]="TEXTURE_BINDING_2D_ARRAY",r[r.R11F_G11F_B10F=35898]="R11F_G11F_B10F",r[r.RGB9_E5=35901]="RGB9_E5",r[r.RGBA32UI=36208]="RGBA32UI",r[r.RGB32UI=36209]="RGB32UI",r[r.RGBA16UI=36214]="RGBA16UI",r[r.RGB16UI=36215]="RGB16UI",r[r.RGBA8UI=36220]="RGBA8UI",r[r.RGB8UI=36221]="RGB8UI",r[r.RGBA32I=36226]="RGBA32I",r[r.RGB32I=36227]="RGB32I",r[r.RGBA16I=36232]="RGBA16I",r[r.RGB16I=36233]="RGB16I",r[r.RGBA8I=36238]="RGBA8I",r[r.RGB8I=36239]="RGB8I",r[r.RED_INTEGER=36244]="RED_INTEGER",r[r.RGB_INTEGER=36248]="RGB_INTEGER",r[r.RGBA_INTEGER=36249]="RGBA_INTEGER",r[r.R8=33321]="R8",r[r.RG8=33323]="RG8",r[r.R16F=33325]="R16F",r[r.R32F=33326]="R32F",r[r.RG16F=33327]="RG16F",r[r.RG32F=33328]="RG32F",r[r.R8I=33329]="R8I",r[r.R8UI=33330]="R8UI",r[r.R16I=33331]="R16I",r[r.R16UI=33332]="R16UI",r[r.R32I=33333]="R32I",r[r.R32UI=33334]="R32UI",r[r.RG8I=33335]="RG8I",r[r.RG8UI=33336]="RG8UI",r[r.RG16I=33337]="RG16I",r[r.RG16UI=33338]="RG16UI",r[r.RG32I=33339]="RG32I",r[r.RG32UI=33340]="RG32UI",r[r.R8_SNORM=36756]="R8_SNORM",r[r.RG8_SNORM=36757]="RG8_SNORM",r[r.RGB8_SNORM=36758]="RGB8_SNORM",r[r.RGBA8_SNORM=36759]="RGBA8_SNORM",r[r.RGB10_A2UI=36975]="RGB10_A2UI",r[r.TEXTURE_IMMUTABLE_FORMAT=37167]="TEXTURE_IMMUTABLE_FORMAT",r[r.TEXTURE_IMMUTABLE_LEVELS=33503]="TEXTURE_IMMUTABLE_LEVELS",r[r.UNSIGNED_INT_2_10_10_10_REV=33640]="UNSIGNED_INT_2_10_10_10_REV",r[r.UNSIGNED_INT_10F_11F_11F_REV=35899]="UNSIGNED_INT_10F_11F_11F_REV",r[r.UNSIGNED_INT_5_9_9_9_REV=35902]="UNSIGNED_INT_5_9_9_9_REV",r[r.FLOAT_32_UNSIGNED_INT_24_8_REV=36269]="FLOAT_32_UNSIGNED_INT_24_8_REV",r[r.UNSIGNED_INT_24_8=34042]="UNSIGNED_INT_24_8",r[r.HALF_FLOAT=5131]="HALF_FLOAT",r[r.RG=33319]="RG",r[r.RG_INTEGER=33320]="RG_INTEGER",r[r.INT_2_10_10_10_REV=36255]="INT_2_10_10_10_REV",r[r.CURRENT_QUERY=34917]="CURRENT_QUERY",r[r.QUERY_RESULT=34918]="QUERY_RESULT",r[r.QUERY_RESULT_AVAILABLE=34919]="QUERY_RESULT_AVAILABLE",r[r.ANY_SAMPLES_PASSED=35887]="ANY_SAMPLES_PASSED",r[r.ANY_SAMPLES_PASSED_CONSERVATIVE=36202]="ANY_SAMPLES_PASSED_CONSERVATIVE",r[r.MAX_DRAW_BUFFERS=34852]="MAX_DRAW_BUFFERS",r[r.DRAW_BUFFER0=34853]="DRAW_BUFFER0",r[r.DRAW_BUFFER1=34854]="DRAW_BUFFER1",r[r.DRAW_BUFFER2=34855]="DRAW_BUFFER2",r[r.DRAW_BUFFER3=34856]="DRAW_BUFFER3",r[r.DRAW_BUFFER4=34857]="DRAW_BUFFER4",r[r.DRAW_BUFFER5=34858]="DRAW_BUFFER5",r[r.DRAW_BUFFER6=34859]="DRAW_BUFFER6",r[r.DRAW_BUFFER7=34860]="DRAW_BUFFER7",r[r.DRAW_BUFFER8=34861]="DRAW_BUFFER8",r[r.DRAW_BUFFER9=34862]="DRAW_BUFFER9",r[r.DRAW_BUFFER10=34863]="DRAW_BUFFER10",r[r.DRAW_BUFFER11=34864]="DRAW_BUFFER11",r[r.DRAW_BUFFER12=34865]="DRAW_BUFFER12",r[r.DRAW_BUFFER13=34866]="DRAW_BUFFER13",r[r.DRAW_BUFFER14=34867]="DRAW_BUFFER14",r[r.DRAW_BUFFER15=34868]="DRAW_BUFFER15",r[r.MAX_COLOR_ATTACHMENTS=36063]="MAX_COLOR_ATTACHMENTS",r[r.COLOR_ATTACHMENT1=36065]="COLOR_ATTACHMENT1",r[r.COLOR_ATTACHMENT2=36066]="COLOR_ATTACHMENT2",r[r.COLOR_ATTACHMENT3=36067]="COLOR_ATTACHMENT3",r[r.COLOR_ATTACHMENT4=36068]="COLOR_ATTACHMENT4",r[r.COLOR_ATTACHMENT5=36069]="COLOR_ATTACHMENT5",r[r.COLOR_ATTACHMENT6=36070]="COLOR_ATTACHMENT6",r[r.COLOR_ATTACHMENT7=36071]="COLOR_ATTACHMENT7",r[r.COLOR_ATTACHMENT8=36072]="COLOR_ATTACHMENT8",r[r.COLOR_ATTACHMENT9=36073]="COLOR_ATTACHMENT9",r[r.COLOR_ATTACHMENT10=36074]="COLOR_ATTACHMENT10",r[r.COLOR_ATTACHMENT11=36075]="COLOR_ATTACHMENT11",r[r.COLOR_ATTACHMENT12=36076]="COLOR_ATTACHMENT12",r[r.COLOR_ATTACHMENT13=36077]="COLOR_ATTACHMENT13",r[r.COLOR_ATTACHMENT14=36078]="COLOR_ATTACHMENT14",r[r.COLOR_ATTACHMENT15=36079]="COLOR_ATTACHMENT15",r[r.SAMPLER_3D=35679]="SAMPLER_3D",r[r.SAMPLER_2D_SHADOW=35682]="SAMPLER_2D_SHADOW",r[r.SAMPLER_2D_ARRAY=36289]="SAMPLER_2D_ARRAY",r[r.SAMPLER_2D_ARRAY_SHADOW=36292]="SAMPLER_2D_ARRAY_SHADOW",r[r.SAMPLER_CUBE_SHADOW=36293]="SAMPLER_CUBE_SHADOW",r[r.INT_SAMPLER_2D=36298]="INT_SAMPLER_2D",r[r.INT_SAMPLER_3D=36299]="INT_SAMPLER_3D",r[r.INT_SAMPLER_CUBE=36300]="INT_SAMPLER_CUBE",r[r.INT_SAMPLER_2D_ARRAY=36303]="INT_SAMPLER_2D_ARRAY",r[r.UNSIGNED_INT_SAMPLER_2D=36306]="UNSIGNED_INT_SAMPLER_2D",r[r.UNSIGNED_INT_SAMPLER_3D=36307]="UNSIGNED_INT_SAMPLER_3D",r[r.UNSIGNED_INT_SAMPLER_CUBE=36308]="UNSIGNED_INT_SAMPLER_CUBE",r[r.UNSIGNED_INT_SAMPLER_2D_ARRAY=36311]="UNSIGNED_INT_SAMPLER_2D_ARRAY",r[r.MAX_SAMPLES=36183]="MAX_SAMPLES",r[r.SAMPLER_BINDING=35097]="SAMPLER_BINDING",r[r.PIXEL_PACK_BUFFER=35051]="PIXEL_PACK_BUFFER",r[r.PIXEL_UNPACK_BUFFER=35052]="PIXEL_UNPACK_BUFFER",r[r.PIXEL_PACK_BUFFER_BINDING=35053]="PIXEL_PACK_BUFFER_BINDING",r[r.PIXEL_UNPACK_BUFFER_BINDING=35055]="PIXEL_UNPACK_BUFFER_BINDING",r[r.COPY_READ_BUFFER=36662]="COPY_READ_BUFFER",r[r.COPY_WRITE_BUFFER=36663]="COPY_WRITE_BUFFER",r[r.COPY_READ_BUFFER_BINDING=36662]="COPY_READ_BUFFER_BINDING",r[r.COPY_WRITE_BUFFER_BINDING=36663]="COPY_WRITE_BUFFER_BINDING",r[r.FLOAT_MAT2x3=35685]="FLOAT_MAT2x3",r[r.FLOAT_MAT2x4=35686]="FLOAT_MAT2x4",r[r.FLOAT_MAT3x2=35687]="FLOAT_MAT3x2",r[r.FLOAT_MAT3x4=35688]="FLOAT_MAT3x4",r[r.FLOAT_MAT4x2=35689]="FLOAT_MAT4x2",r[r.FLOAT_MAT4x3=35690]="FLOAT_MAT4x3",r[r.UNSIGNED_INT_VEC2=36294]="UNSIGNED_INT_VEC2",r[r.UNSIGNED_INT_VEC3=36295]="UNSIGNED_INT_VEC3",r[r.UNSIGNED_INT_VEC4=36296]="UNSIGNED_INT_VEC4",r[r.UNSIGNED_NORMALIZED=35863]="UNSIGNED_NORMALIZED",r[r.SIGNED_NORMALIZED=36764]="SIGNED_NORMALIZED",r[r.VERTEX_ATTRIB_ARRAY_INTEGER=35069]="VERTEX_ATTRIB_ARRAY_INTEGER",r[r.VERTEX_ATTRIB_ARRAY_DIVISOR=35070]="VERTEX_ATTRIB_ARRAY_DIVISOR",r[r.TRANSFORM_FEEDBACK_BUFFER_MODE=35967]="TRANSFORM_FEEDBACK_BUFFER_MODE",r[r.MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS=35968]="MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS",r[r.TRANSFORM_FEEDBACK_VARYINGS=35971]="TRANSFORM_FEEDBACK_VARYINGS",r[r.TRANSFORM_FEEDBACK_BUFFER_START=35972]="TRANSFORM_FEEDBACK_BUFFER_START",r[r.TRANSFORM_FEEDBACK_BUFFER_SIZE=35973]="TRANSFORM_FEEDBACK_BUFFER_SIZE",r[r.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN=35976]="TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN",r[r.MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS=35978]="MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS",r[r.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS=35979]="MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS",r[r.INTERLEAVED_ATTRIBS=35980]="INTERLEAVED_ATTRIBS",r[r.SEPARATE_ATTRIBS=35981]="SEPARATE_ATTRIBS",r[r.TRANSFORM_FEEDBACK_BUFFER=35982]="TRANSFORM_FEEDBACK_BUFFER",r[r.TRANSFORM_FEEDBACK_BUFFER_BINDING=35983]="TRANSFORM_FEEDBACK_BUFFER_BINDING",r[r.TRANSFORM_FEEDBACK=36386]="TRANSFORM_FEEDBACK",r[r.TRANSFORM_FEEDBACK_PAUSED=36387]="TRANSFORM_FEEDBACK_PAUSED",r[r.TRANSFORM_FEEDBACK_ACTIVE=36388]="TRANSFORM_FEEDBACK_ACTIVE",r[r.TRANSFORM_FEEDBACK_BINDING=36389]="TRANSFORM_FEEDBACK_BINDING",r[r.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING=33296]="FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING",r[r.FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE=33297]="FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE",r[r.FRAMEBUFFER_ATTACHMENT_RED_SIZE=33298]="FRAMEBUFFER_ATTACHMENT_RED_SIZE",r[r.FRAMEBUFFER_ATTACHMENT_GREEN_SIZE=33299]="FRAMEBUFFER_ATTACHMENT_GREEN_SIZE",r[r.FRAMEBUFFER_ATTACHMENT_BLUE_SIZE=33300]="FRAMEBUFFER_ATTACHMENT_BLUE_SIZE",r[r.FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE=33301]="FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE",r[r.FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE=33302]="FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE",r[r.FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE=33303]="FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE",r[r.FRAMEBUFFER_DEFAULT=33304]="FRAMEBUFFER_DEFAULT",r[r.DEPTH24_STENCIL8=35056]="DEPTH24_STENCIL8",r[r.DRAW_FRAMEBUFFER_BINDING=36006]="DRAW_FRAMEBUFFER_BINDING",r[r.READ_FRAMEBUFFER_BINDING=36010]="READ_FRAMEBUFFER_BINDING",r[r.RENDERBUFFER_SAMPLES=36011]="RENDERBUFFER_SAMPLES",r[r.FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER=36052]="FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER",r[r.FRAMEBUFFER_INCOMPLETE_MULTISAMPLE=36182]="FRAMEBUFFER_INCOMPLETE_MULTISAMPLE",r[r.UNIFORM_BUFFER=35345]="UNIFORM_BUFFER",r[r.UNIFORM_BUFFER_BINDING=35368]="UNIFORM_BUFFER_BINDING",r[r.UNIFORM_BUFFER_START=35369]="UNIFORM_BUFFER_START",r[r.UNIFORM_BUFFER_SIZE=35370]="UNIFORM_BUFFER_SIZE",r[r.MAX_VERTEX_UNIFORM_BLOCKS=35371]="MAX_VERTEX_UNIFORM_BLOCKS",r[r.MAX_FRAGMENT_UNIFORM_BLOCKS=35373]="MAX_FRAGMENT_UNIFORM_BLOCKS",r[r.MAX_COMBINED_UNIFORM_BLOCKS=35374]="MAX_COMBINED_UNIFORM_BLOCKS",r[r.MAX_UNIFORM_BUFFER_BINDINGS=35375]="MAX_UNIFORM_BUFFER_BINDINGS",r[r.MAX_UNIFORM_BLOCK_SIZE=35376]="MAX_UNIFORM_BLOCK_SIZE",r[r.MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS=35377]="MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS",r[r.MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS=35379]="MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS",r[r.UNIFORM_BUFFER_OFFSET_ALIGNMENT=35380]="UNIFORM_BUFFER_OFFSET_ALIGNMENT",r[r.ACTIVE_UNIFORM_BLOCKS=35382]="ACTIVE_UNIFORM_BLOCKS",r[r.UNIFORM_TYPE=35383]="UNIFORM_TYPE",r[r.UNIFORM_SIZE=35384]="UNIFORM_SIZE",r[r.UNIFORM_BLOCK_INDEX=35386]="UNIFORM_BLOCK_INDEX",r[r.UNIFORM_OFFSET=35387]="UNIFORM_OFFSET",r[r.UNIFORM_ARRAY_STRIDE=35388]="UNIFORM_ARRAY_STRIDE",r[r.UNIFORM_MATRIX_STRIDE=35389]="UNIFORM_MATRIX_STRIDE",r[r.UNIFORM_IS_ROW_MAJOR=35390]="UNIFORM_IS_ROW_MAJOR",r[r.UNIFORM_BLOCK_BINDING=35391]="UNIFORM_BLOCK_BINDING",r[r.UNIFORM_BLOCK_DATA_SIZE=35392]="UNIFORM_BLOCK_DATA_SIZE",r[r.UNIFORM_BLOCK_ACTIVE_UNIFORMS=35394]="UNIFORM_BLOCK_ACTIVE_UNIFORMS",r[r.UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES=35395]="UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES",r[r.UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER=35396]="UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER",r[r.UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER=35398]="UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER",r[r.OBJECT_TYPE=37138]="OBJECT_TYPE",r[r.SYNC_CONDITION=37139]="SYNC_CONDITION",r[r.SYNC_STATUS=37140]="SYNC_STATUS",r[r.SYNC_FLAGS=37141]="SYNC_FLAGS",r[r.SYNC_FENCE=37142]="SYNC_FENCE",r[r.SYNC_GPU_COMMANDS_COMPLETE=37143]="SYNC_GPU_COMMANDS_COMPLETE",r[r.UNSIGNALED=37144]="UNSIGNALED",r[r.SIGNALED=37145]="SIGNALED",r[r.ALREADY_SIGNALED=37146]="ALREADY_SIGNALED",r[r.TIMEOUT_EXPIRED=37147]="TIMEOUT_EXPIRED",r[r.CONDITION_SATISFIED=37148]="CONDITION_SATISFIED",r[r.WAIT_FAILED=37149]="WAIT_FAILED",r[r.SYNC_FLUSH_COMMANDS_BIT=1]="SYNC_FLUSH_COMMANDS_BIT",r[r.COLOR=6144]="COLOR",r[r.DEPTH=6145]="DEPTH",r[r.STENCIL=6146]="STENCIL",r[r.MIN=32775]="MIN",r[r.MAX=32776]="MAX",r[r.DEPTH_COMPONENT24=33190]="DEPTH_COMPONENT24",r[r.STREAM_READ=35041]="STREAM_READ",r[r.STREAM_COPY=35042]="STREAM_COPY",r[r.STATIC_READ=35045]="STATIC_READ",r[r.STATIC_COPY=35046]="STATIC_COPY",r[r.DYNAMIC_READ=35049]="DYNAMIC_READ",r[r.DYNAMIC_COPY=35050]="DYNAMIC_COPY",r[r.DEPTH_COMPONENT32F=36012]="DEPTH_COMPONENT32F",r[r.DEPTH32F_STENCIL8=36013]="DEPTH32F_STENCIL8",r[r.INVALID_INDEX=4294967295]="INVALID_INDEX",r[r.TIMEOUT_IGNORED=-1]="TIMEOUT_IGNORED",r[r.MAX_CLIENT_WAIT_TIMEOUT_WEBGL=37447]="MAX_CLIENT_WAIT_TIMEOUT_WEBGL",r[r.UNMASKED_VENDOR_WEBGL=37445]="UNMASKED_VENDOR_WEBGL",r[r.UNMASKED_RENDERER_WEBGL=37446]="UNMASKED_RENDERER_WEBGL",r[r.MAX_TEXTURE_MAX_ANISOTROPY_EXT=34047]="MAX_TEXTURE_MAX_ANISOTROPY_EXT",r[r.TEXTURE_MAX_ANISOTROPY_EXT=34046]="TEXTURE_MAX_ANISOTROPY_EXT",r[r.R16_EXT=33322]="R16_EXT",r[r.RG16_EXT=33324]="RG16_EXT",r[r.RGB16_EXT=32852]="RGB16_EXT",r[r.RGBA16_EXT=32859]="RGBA16_EXT",r[r.R16_SNORM_EXT=36760]="R16_SNORM_EXT",r[r.RG16_SNORM_EXT=36761]="RG16_SNORM_EXT",r[r.RGB16_SNORM_EXT=36762]="RGB16_SNORM_EXT",r[r.RGBA16_SNORM_EXT=36763]="RGBA16_SNORM_EXT",r[r.COMPRESSED_RGB_S3TC_DXT1_EXT=33776]="COMPRESSED_RGB_S3TC_DXT1_EXT",r[r.COMPRESSED_RGBA_S3TC_DXT1_EXT=33777]="COMPRESSED_RGBA_S3TC_DXT1_EXT",r[r.COMPRESSED_RGBA_S3TC_DXT3_EXT=33778]="COMPRESSED_RGBA_S3TC_DXT3_EXT",r[r.COMPRESSED_RGBA_S3TC_DXT5_EXT=33779]="COMPRESSED_RGBA_S3TC_DXT5_EXT",r[r.COMPRESSED_SRGB_S3TC_DXT1_EXT=35916]="COMPRESSED_SRGB_S3TC_DXT1_EXT",r[r.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT=35917]="COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT",r[r.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT=35918]="COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT",r[r.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT=35919]="COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT",r[r.COMPRESSED_RED_RGTC1_EXT=36283]="COMPRESSED_RED_RGTC1_EXT",r[r.COMPRESSED_SIGNED_RED_RGTC1_EXT=36284]="COMPRESSED_SIGNED_RED_RGTC1_EXT",r[r.COMPRESSED_RED_GREEN_RGTC2_EXT=36285]="COMPRESSED_RED_GREEN_RGTC2_EXT",r[r.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT=36286]="COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT",r[r.COMPRESSED_RGBA_BPTC_UNORM_EXT=36492]="COMPRESSED_RGBA_BPTC_UNORM_EXT",r[r.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT=36493]="COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT",r[r.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT=36494]="COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT",r[r.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT=36495]="COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT",r[r.COMPRESSED_R11_EAC=37488]="COMPRESSED_R11_EAC",r[r.COMPRESSED_SIGNED_R11_EAC=37489]="COMPRESSED_SIGNED_R11_EAC",r[r.COMPRESSED_RG11_EAC=37490]="COMPRESSED_RG11_EAC",r[r.COMPRESSED_SIGNED_RG11_EAC=37491]="COMPRESSED_SIGNED_RG11_EAC",r[r.COMPRESSED_RGB8_ETC2=37492]="COMPRESSED_RGB8_ETC2",r[r.COMPRESSED_RGBA8_ETC2_EAC=37493]="COMPRESSED_RGBA8_ETC2_EAC",r[r.COMPRESSED_SRGB8_ETC2=37494]="COMPRESSED_SRGB8_ETC2",r[r.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC=37495]="COMPRESSED_SRGB8_ALPHA8_ETC2_EAC",r[r.COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2=37496]="COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2",r[r.COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2=37497]="COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2",r[r.COMPRESSED_RGB_PVRTC_4BPPV1_IMG=35840]="COMPRESSED_RGB_PVRTC_4BPPV1_IMG",r[r.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG=35842]="COMPRESSED_RGBA_PVRTC_4BPPV1_IMG",r[r.COMPRESSED_RGB_PVRTC_2BPPV1_IMG=35841]="COMPRESSED_RGB_PVRTC_2BPPV1_IMG",r[r.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG=35843]="COMPRESSED_RGBA_PVRTC_2BPPV1_IMG",r[r.COMPRESSED_RGB_ETC1_WEBGL=36196]="COMPRESSED_RGB_ETC1_WEBGL",r[r.COMPRESSED_RGB_ATC_WEBGL=35986]="COMPRESSED_RGB_ATC_WEBGL",r[r.COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL=35986]="COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL",r[r.COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL=34798]="COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL",r[r.COMPRESSED_RGBA_ASTC_4x4_KHR=37808]="COMPRESSED_RGBA_ASTC_4x4_KHR",r[r.COMPRESSED_RGBA_ASTC_5x4_KHR=37809]="COMPRESSED_RGBA_ASTC_5x4_KHR",r[r.COMPRESSED_RGBA_ASTC_5x5_KHR=37810]="COMPRESSED_RGBA_ASTC_5x5_KHR",r[r.COMPRESSED_RGBA_ASTC_6x5_KHR=37811]="COMPRESSED_RGBA_ASTC_6x5_KHR",r[r.COMPRESSED_RGBA_ASTC_6x6_KHR=37812]="COMPRESSED_RGBA_ASTC_6x6_KHR",r[r.COMPRESSED_RGBA_ASTC_8x5_KHR=37813]="COMPRESSED_RGBA_ASTC_8x5_KHR",r[r.COMPRESSED_RGBA_ASTC_8x6_KHR=37814]="COMPRESSED_RGBA_ASTC_8x6_KHR",r[r.COMPRESSED_RGBA_ASTC_8x8_KHR=37815]="COMPRESSED_RGBA_ASTC_8x8_KHR",r[r.COMPRESSED_RGBA_ASTC_10x5_KHR=37816]="COMPRESSED_RGBA_ASTC_10x5_KHR",r[r.COMPRESSED_RGBA_ASTC_10x6_KHR=37817]="COMPRESSED_RGBA_ASTC_10x6_KHR",r[r.COMPRESSED_RGBA_ASTC_10x8_KHR=37818]="COMPRESSED_RGBA_ASTC_10x8_KHR",r[r.COMPRESSED_RGBA_ASTC_10x10_KHR=37819]="COMPRESSED_RGBA_ASTC_10x10_KHR",r[r.COMPRESSED_RGBA_ASTC_12x10_KHR=37820]="COMPRESSED_RGBA_ASTC_12x10_KHR",r[r.COMPRESSED_RGBA_ASTC_12x12_KHR=37821]="COMPRESSED_RGBA_ASTC_12x12_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR=37840]="COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR=37841]="COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR=37842]="COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR=37843]="COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR=37844]="COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR=37845]="COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR=37846]="COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR=37847]="COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR=37848]="COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR=37849]="COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR=37850]="COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR=37851]="COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR=37852]="COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR",r[r.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR=37853]="COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR",r[r.QUERY_COUNTER_BITS_EXT=34916]="QUERY_COUNTER_BITS_EXT",r[r.CURRENT_QUERY_EXT=34917]="CURRENT_QUERY_EXT",r[r.QUERY_RESULT_EXT=34918]="QUERY_RESULT_EXT",r[r.QUERY_RESULT_AVAILABLE_EXT=34919]="QUERY_RESULT_AVAILABLE_EXT",r[r.TIME_ELAPSED_EXT=35007]="TIME_ELAPSED_EXT",r[r.TIMESTAMP_EXT=36392]="TIMESTAMP_EXT",r[r.GPU_DISJOINT_EXT=36795]="GPU_DISJOINT_EXT",r[r.COMPLETION_STATUS_KHR=37297]="COMPLETION_STATUS_KHR",r[r.DEPTH_CLAMP_EXT=34383]="DEPTH_CLAMP_EXT",r[r.FIRST_VERTEX_CONVENTION_WEBGL=36429]="FIRST_VERTEX_CONVENTION_WEBGL",r[r.LAST_VERTEX_CONVENTION_WEBGL=36430]="LAST_VERTEX_CONVENTION_WEBGL",r[r.PROVOKING_VERTEX_WEBL=36431]="PROVOKING_VERTEX_WEBL",r[r.POLYGON_MODE_WEBGL=2880]="POLYGON_MODE_WEBGL",r[r.POLYGON_OFFSET_LINE_WEBGL=10754]="POLYGON_OFFSET_LINE_WEBGL",r[r.LINE_WEBGL=6913]="LINE_WEBGL",r[r.FILL_WEBGL=6914]="FILL_WEBGL",r[r.MAX_CLIP_DISTANCES_WEBGL=3378]="MAX_CLIP_DISTANCES_WEBGL",r[r.MAX_CULL_DISTANCES_WEBGL=33529]="MAX_CULL_DISTANCES_WEBGL",r[r.MAX_COMBINED_CLIP_AND_CULL_DISTANCES_WEBGL=33530]="MAX_COMBINED_CLIP_AND_CULL_DISTANCES_WEBGL",r[r.CLIP_DISTANCE0_WEBGL=12288]="CLIP_DISTANCE0_WEBGL",r[r.CLIP_DISTANCE1_WEBGL=12289]="CLIP_DISTANCE1_WEBGL",r[r.CLIP_DISTANCE2_WEBGL=12290]="CLIP_DISTANCE2_WEBGL",r[r.CLIP_DISTANCE3_WEBGL=12291]="CLIP_DISTANCE3_WEBGL",r[r.CLIP_DISTANCE4_WEBGL=12292]="CLIP_DISTANCE4_WEBGL",r[r.CLIP_DISTANCE5_WEBGL=12293]="CLIP_DISTANCE5_WEBGL",r[r.CLIP_DISTANCE6_WEBGL=12294]="CLIP_DISTANCE6_WEBGL",r[r.CLIP_DISTANCE7_WEBGL=12295]="CLIP_DISTANCE7_WEBGL",r[r.POLYGON_OFFSET_CLAMP_EXT=36379]="POLYGON_OFFSET_CLAMP_EXT",r[r.LOWER_LEFT_EXT=36001]="LOWER_LEFT_EXT",r[r.UPPER_LEFT_EXT=36002]="UPPER_LEFT_EXT",r[r.NEGATIVE_ONE_TO_ONE_EXT=37726]="NEGATIVE_ONE_TO_ONE_EXT",r[r.ZERO_TO_ONE_EXT=37727]="ZERO_TO_ONE_EXT",r[r.CLIP_ORIGIN_EXT=37724]="CLIP_ORIGIN_EXT",r[r.CLIP_DEPTH_MODE_EXT=37725]="CLIP_DEPTH_MODE_EXT",r[r.SRC1_COLOR_WEBGL=35065]="SRC1_COLOR_WEBGL",r[r.SRC1_ALPHA_WEBGL=34185]="SRC1_ALPHA_WEBGL",r[r.ONE_MINUS_SRC1_COLOR_WEBGL=35066]="ONE_MINUS_SRC1_COLOR_WEBGL",r[r.ONE_MINUS_SRC1_ALPHA_WEBGL=35067]="ONE_MINUS_SRC1_ALPHA_WEBGL",r[r.MAX_DUAL_SOURCE_DRAW_BUFFERS_WEBGL=35068]="MAX_DUAL_SOURCE_DRAW_BUFFERS_WEBGL",r[r.MIRROR_CLAMP_TO_EDGE_EXT=34627]="MIRROR_CLAMP_TO_EDGE_EXT"})(jA||(jA={}))});var QV=Nt(()=>{yee()});var Eee=Vi((ej,tj)=>{(function(r,e){typeof ej=="object"&&typeof tj<"u"?tj.exports=e():typeof define=="function"&&define.amd?define(e):(r=r||self,r.mapboxgl=e())})(ej,function(){"use strict";var r,e,t;function i(n,o){if(!r)r=o;else if(!e)e=o;else{var s="var sharedChunk = {}; ("+r+")(sharedChunk); ("+e+")(sharedChunk);",c={};r(c),t=o(c),typeof window<"u"&&(t.workerUrl=window.URL.createObjectURL(new Blob([s],{type:"text/javascript"})))}}return i(["exports"],function(n){"use strict";function o(u,p){return u(p={exports:{}},p.exports),p.exports}var s=c;function c(u,p,_,R){this.cx=3*u,this.bx=3*(_-u)-this.cx,this.ax=1-this.cx-this.bx,this.cy=3*p,this.by=3*(R-p)-this.cy,this.ay=1-this.cy-this.by,this.p1x=u,this.p1y=R,this.p2x=_,this.p2y=R}c.prototype.sampleCurveX=function(u){return((this.ax*u+this.bx)*u+this.cx)*u},c.prototype.sampleCurveY=function(u){return((this.ay*u+this.by)*u+this.cy)*u},c.prototype.sampleCurveDerivativeX=function(u){return(3*this.ax*u+2*this.bx)*u+this.cx},c.prototype.solveCurveX=function(u,p){var _,R,j,$,J;for(p===void 0&&(p=1e-6),j=u,J=0;J<8;J++){if($=this.sampleCurveX(j)-u,Math.abs($)(R=1))return R;for(;_$?_=j:R=j,j=.5*(R-_)+_}return j},c.prototype.solve=function(u,p){return this.sampleCurveY(this.solveCurveX(u,p))};var f=g;function g(u,p){this.x=u,this.y=p}g.prototype={clone:function(){return new g(this.x,this.y)},add:function(u){return this.clone()._add(u)},sub:function(u){return this.clone()._sub(u)},multByPoint:function(u){return this.clone()._multByPoint(u)},divByPoint:function(u){return this.clone()._divByPoint(u)},mult:function(u){return this.clone()._mult(u)},div:function(u){return this.clone()._div(u)},rotate:function(u){return this.clone()._rotate(u)},rotateAround:function(u,p){return this.clone()._rotateAround(u,p)},matMult:function(u){return this.clone()._matMult(u)},unit:function(){return this.clone()._unit()},perp:function(){return this.clone()._perp()},round:function(){return this.clone()._round()},mag:function(){return Math.sqrt(this.x*this.x+this.y*this.y)},equals:function(u){return this.x===u.x&&this.y===u.y},dist:function(u){return Math.sqrt(this.distSqr(u))},distSqr:function(u){var p=u.x-this.x,_=u.y-this.y;return p*p+_*_},angle:function(){return Math.atan2(this.y,this.x)},angleTo:function(u){return Math.atan2(this.y-u.y,this.x-u.x)},angleWith:function(u){return this.angleWithSep(u.x,u.y)},angleWithSep:function(u,p){return Math.atan2(this.x*p-this.y*u,this.x*u+this.y*p)},_matMult:function(u){var p=u[2]*this.x+u[3]*this.y;return this.x=u[0]*this.x+u[1]*this.y,this.y=p,this},_add:function(u){return this.x+=u.x,this.y+=u.y,this},_sub:function(u){return this.x-=u.x,this.y-=u.y,this},_mult:function(u){return this.x*=u,this.y*=u,this},_div:function(u){return this.x/=u,this.y/=u,this},_multByPoint:function(u){return this.x*=u.x,this.y*=u.y,this},_divByPoint:function(u){return this.x/=u.x,this.y/=u.y,this},_unit:function(){return this._div(this.mag()),this},_perp:function(){var u=this.y;return this.y=this.x,this.x=-u,this},_rotate:function(u){var p=Math.cos(u),_=Math.sin(u),R=_*this.x+p*this.y;return this.x=p*this.x-_*this.y,this.y=R,this},_rotateAround:function(u,p){var _=Math.cos(u),R=Math.sin(u),j=p.y+R*(this.x-p.x)+_*(this.y-p.y);return this.x=p.x+_*(this.x-p.x)-R*(this.y-p.y),this.y=j,this},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}},g.convert=function(u){return u instanceof g?u:Array.isArray(u)?new g(u[0],u[1]):u};var y=typeof self<"u"?self:{},M=Math.pow(2,53)-1;function F(u,p,_,R){var j=new s(u,p,_,R);return function($){return j.solve($)}}var k=F(.25,.1,.25,1);function U(u,p,_){return Math.min(_,Math.max(p,u))}function K(u,p,_){var R=_-p,j=((u-p)%R+R)%R+p;return j===p?_:j}function ee(u){for(var p=[],_=arguments.length-1;_-- >0;)p[_]=arguments[_+1];for(var R=0,j=p;R>p/4).toString(16):([1e7]+-[1e3]+-4e3+-8e3+-1e11).replace(/[018]/g,u)}()}function Ce(u){return!!u&&/^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u)}function Oe(u,p){u.forEach(function(_){p[_]&&(p[_]=p[_].bind(p))})}function ne(u,p){return u.indexOf(p,u.length-p.length)!==-1}function He(u,p,_){var R={};for(var j in u)R[j]=p.call(_||this,u[j],j,u);return R}function We(u,p,_){var R={};for(var j in u)p.call(_||this,u[j],j,u)&&(R[j]=u[j]);return R}function Se(u){return Array.isArray(u)?u.map(Se):typeof u=="object"&&u?He(u,Se):u}var ve={};function Qe(u){ve[u]||(typeof console<"u"&&console.warn(u),ve[u]=!0)}function wt(u,p,_){return(_.y-u.y)*(p.x-u.x)>(p.y-u.y)*(_.x-u.x)}function et(u){for(var p=0,_=0,R=u.length,j=R-1,$=void 0,J=void 0;_@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)(?:\=(?:([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)|(?:\"((?:[^"\\]|\\.)*)\")))?/g,function(R,j,$,J){var le=$||J;return p[j]=!le||le.toLowerCase(),""}),p["max-age"]){var _=parseInt(p["max-age"],10);isNaN(_)?delete p["max-age"]:p["max-age"]=_}return p}var Dr=null;function hr(u){if(Dr==null){var p=u.navigator?u.navigator.userAgent:null;Dr=!!u.safari||!(!p||!(/\b(iPad|iPhone|iPod)\b/.test(p)||p.match("Safari")&&!p.match("Chrome")))}return Dr}function mi(u){try{var p=y[u];return p.setItem("_mapbox_test_",1),p.removeItem("_mapbox_test_"),!0}catch{return!1}}var bi,un,Yr,Xi,xi=y.performance&&y.performance.now?y.performance.now.bind(y.performance):Date.now.bind(Date),ii=y.requestAnimationFrame||y.mozRequestAnimationFrame||y.webkitRequestAnimationFrame||y.msRequestAnimationFrame,Hn=y.cancelAnimationFrame||y.mozCancelAnimationFrame||y.webkitCancelAnimationFrame||y.msCancelAnimationFrame,Ve={now:xi,frame:function(u){var p=ii(u);return{cancel:function(){return Hn(p)}}},getImageData:function(u,p){p===void 0&&(p=0);var _=y.document.createElement("canvas"),R=_.getContext("2d");if(!R)throw new Error("failed to create canvas 2d context");return _.width=u.width,_.height=u.height,R.drawImage(u,0,0,u.width,u.height),R.getImageData(-p,-p,u.width+2*p,u.height+2*p)},resolveURL:function(u){return bi||(bi=y.document.createElement("a")),bi.href=u,bi.href},hardwareConcurrency:y.navigator&&y.navigator.hardwareConcurrency||4,get devicePixelRatio(){return y.devicePixelRatio},get prefersReducedMotion(){return!!y.matchMedia&&(un==null&&(un=y.matchMedia("(prefers-reduced-motion: reduce)")),un.matches)}},nt={API_URL:"https://api.mapbox.com",get EVENTS_URL(){return this.API_URL?this.API_URL.indexOf("https://api.mapbox.cn")===0?"https://events.mapbox.cn/events/v2":this.API_URL.indexOf("https://api.mapbox.com")===0?"https://events.mapbox.com/events/v2":null:null},FEEDBACK_URL:"https://apps.mapbox.com/feedback",REQUIRE_ACCESS_TOKEN:!0,ACCESS_TOKEN:null,MAX_PARALLEL_IMAGE_REQUESTS:16},Ft={supported:!1,testSupport:function(u){!mr&&Xi&&(Xr?zr(u):Yr=u)}},mr=!1,Xr=!1;function zr(u){var p=u.createTexture();u.bindTexture(u.TEXTURE_2D,p);try{if(u.texImage2D(u.TEXTURE_2D,0,u.RGBA,u.RGBA,u.UNSIGNED_BYTE,Xi),u.isContextLost())return;Ft.supported=!0}catch{}u.deleteTexture(p),mr=!0}y.document&&((Xi=y.document.createElement("img")).onload=function(){Yr&&zr(Yr),Yr=null,Xr=!0},Xi.onerror=function(){mr=!0,Yr=null},Xi.src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA=");var ln="01",Nn=function(u,p){this._transformRequestFn=u,this._customAccessToken=p,this._createSkuToken()};function Co(u){return u.indexOf("mapbox:")===0}Nn.prototype._createSkuToken=function(){var u=function(){for(var p="",_=0;_<10;_++)p+="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(62*Math.random())];return{token:["1",ln,p].join(""),tokenExpiresAt:Date.now()+432e5}}();this._skuToken=u.token,this._skuTokenExpiresAt=u.tokenExpiresAt},Nn.prototype._isSkuTokenExpired=function(){return Date.now()>this._skuTokenExpiresAt},Nn.prototype.transformRequest=function(u,p){return this._transformRequestFn&&this._transformRequestFn(u,p)||{url:u}},Nn.prototype.normalizeStyleURL=function(u,p){if(!Co(u))return u;var _=zs(u);return _.path="/styles/v1"+_.path,this._makeAPIURL(_,this._customAccessToken||p)},Nn.prototype.normalizeGlyphsURL=function(u,p){if(!Co(u))return u;var _=zs(u);return _.path="/fonts/v1"+_.path,this._makeAPIURL(_,this._customAccessToken||p)},Nn.prototype.normalizeSourceURL=function(u,p){if(!Co(u))return u;var _=zs(u);return _.path="/v4/"+_.authority+".json",_.params.push("secure"),this._makeAPIURL(_,this._customAccessToken||p)},Nn.prototype.normalizeSpriteURL=function(u,p,_,R){var j=zs(u);return Co(u)?(j.path="/styles/v1"+j.path+"/sprite"+p+_,this._makeAPIURL(j,this._customAccessToken||R)):(j.path+=""+p+_,Ba(j))},Nn.prototype.normalizeTileURL=function(u,p){if(this._isSkuTokenExpired()&&this._createSkuToken(),u&&!Co(u))return u;var _=zs(u);_.path=_.path.replace(/(\.(png|jpg)\d*)(?=$)/,(Ve.devicePixelRatio>=2||p===512?"@2x":"")+(Ft.supported?".webp":"$1")),_.path=_.path.replace(/^.+\/v4\//,"/"),_.path="/v4"+_.path;var R=this._customAccessToken||function(j){for(var $=0,J=j;$=0&&u.params.splice(j,1)}if(R.path!=="/"&&(u.path=""+R.path+u.path),!nt.REQUIRE_ACCESS_TOKEN)return Ba(u);if(!(p=p||nt.ACCESS_TOKEN))throw new Error("An API access token is required to use Mapbox GL. "+_);if(p[0]==="s")throw new Error("Use a public access token (pk.*) with Mapbox GL, not a secret access token (sk.*). "+_);return u.params=u.params.filter(function($){return $.indexOf("access_token")===-1}),u.params.push("access_token="+p),Ba(u)};var Go=/^((https?:)?\/\/)?([^\/]+\.)?mapbox\.c(n|om)(\/|\?|$)/i;function is(u){return Go.test(u)}var mo=/^(\w+):\/\/([^/?]*)(\/[^?]+)?\??(.+)?/;function zs(u){var p=u.match(mo);if(!p)throw new Error("Unable to parse URL object");return{protocol:p[1],authority:p[2],path:p[3]||"/",params:p[4]?p[4].split("&"):[]}}function Ba(u){var p=u.params.length?"?"+u.params.join("&"):"";return u.protocol+"://"+u.authority+u.path+p}function ds(u){if(!u)return null;var p=u.split(".");if(!p||p.length!==3)return null;try{return JSON.parse(decodeURIComponent(y.atob(p[1]).split("").map(function(_){return"%"+("00"+_.charCodeAt(0).toString(16)).slice(-2)}).join("")))}catch{return null}}var so=function(u){this.type=u,this.anonId=null,this.eventData={},this.queue=[],this.pendingRequest=null};so.prototype.getStorageKey=function(u){var p,_=ds(nt.ACCESS_TOKEN);return p=_&&_.u?y.btoa(encodeURIComponent(_.u).replace(/%([0-9A-F]{2})/g,function(R,j){return String.fromCharCode(Number("0x"+j))})):nt.ACCESS_TOKEN||"",u?"mapbox.eventData."+u+":"+p:"mapbox.eventData:"+p},so.prototype.fetchEventData=function(){var u=mi("localStorage"),p=this.getStorageKey(),_=this.getStorageKey("uuid");if(u)try{var R=y.localStorage.getItem(p);R&&(this.eventData=JSON.parse(R));var j=y.localStorage.getItem(_);j&&(this.anonId=j)}catch{Qe("Unable to read from LocalStorage")}},so.prototype.saveEventData=function(){var u=mi("localStorage"),p=this.getStorageKey(),_=this.getStorageKey("uuid");if(u)try{y.localStorage.setItem(_,this.anonId),Object.keys(this.eventData).length>=1&&y.localStorage.setItem(p,JSON.stringify(this.eventData))}catch{Qe("Unable to write to LocalStorage")}},so.prototype.processRequests=function(u){},so.prototype.postEvent=function(u,p,_,R){var j=this;if(nt.EVENTS_URL){var $=zs(nt.EVENTS_URL);$.params.push("access_token="+(R||nt.ACCESS_TOKEN||""));var J={event:this.type,created:new Date(u).toISOString(),sdkIdentifier:"mapbox-gl-js",sdkVersion:"1.13.3",skuId:ln,userId:this.anonId},le=p?ee(J,p):J,ye={url:Ba($),headers:{"Content-Type":"text/plain"},body:JSON.stringify([le])};this.pendingRequest=ns(ye,function(Me){j.pendingRequest=null,_(Me),j.saveEventData(),j.processRequests(R)})}},so.prototype.queueRequest=function(u,p){this.queue.push(u),this.processRequests(p)};var ps,ws,Yt=function(u){function p(){u.call(this,"map.load"),this.success={},this.skuToken=""}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.postMapLoadEvent=function(_,R,j,$){this.skuToken=j;var J=!(!$&&!nt.ACCESS_TOKEN),le=Array.isArray(_)&&_.some(function(ye){return Co(ye)||is(ye)});nt.EVENTS_URL&&J&&le&&this.queueRequest({id:R,timestamp:Date.now()},$)},p.prototype.processRequests=function(_){var R=this;if(!this.pendingRequest&&this.queue.length!==0){var j=this.queue.shift(),$=j.id,J=j.timestamp;$&&this.success[$]||(this.anonId||this.fetchEventData(),Ce(this.anonId)||(this.anonId=me()),this.postEvent(J,{skuToken:this.skuToken},function(le){le||$&&(R.success[$]=!0)},_))}},p}(so),Fe=new(function(u){function p(_){u.call(this,"appUserTurnstile"),this._customAccessToken=_}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.postTurnstileEvent=function(_,R){nt.EVENTS_URL&&nt.ACCESS_TOKEN&&Array.isArray(_)&&_.some(function(j){return Co(j)||is(j)})&&this.queueRequest(Date.now(),R)},p.prototype.processRequests=function(_){var R=this;if(!this.pendingRequest&&this.queue.length!==0){this.anonId&&this.eventData.lastSuccess&&this.eventData.tokenU||this.fetchEventData();var j=ds(nt.ACCESS_TOKEN),$=j?j.u:nt.ACCESS_TOKEN,J=$!==this.eventData.tokenU;Ce(this.anonId)||(this.anonId=me(),J=!0);var le=this.queue.shift();if(this.eventData.lastSuccess){var ye=new Date(this.eventData.lastSuccess),Me=new Date(le),Le=(le-this.eventData.lastSuccess)/864e5;J=J||Le>=1||Le<-1||ye.getDate()!==Me.getDate()}else J=!0;if(!J)return this.processRequests();this.postEvent(le,{"enabled.telemetry":!1},function(ze){ze||(R.eventData.lastSuccess=le,R.eventData.tokenU=$)},_)}},p}(so)),Ne=Fe.postTurnstileEvent.bind(Fe),rt=new Yt,Vt=rt.postMapLoadEvent.bind(rt),Ht=500,yr=50;function fr(){y.caches&&!ps&&(ps=y.caches.open("mapbox-tiles"))}function nr(u){var p=u.indexOf("?");return p<0?u:u.slice(0,p)}var xr,ti=1/0;function Jr(){return xr==null&&(xr=y.OffscreenCanvas&&new y.OffscreenCanvas(1,1).getContext("2d")&&typeof y.createImageBitmap=="function"),xr}var Wi={Unknown:"Unknown",Style:"Style",Source:"Source",Tile:"Tile",Glyphs:"Glyphs",SpriteImage:"SpriteImage",SpriteJSON:"SpriteJSON",Image:"Image"};typeof Object.freeze=="function"&&Object.freeze(Wi);var fn=function(u){function p(_,R,j){R===401&&is(j)&&(_+=": you may have provided an invalid Mapbox access token. See https://www.mapbox.com/api-documentation/#access-tokens-and-token-scopes"),u.call(this,_),this.status=R,this.url=j,this.name=this.constructor.name,this.message=_}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.toString=function(){return this.name+": "+this.message+" ("+this.status+"): "+this.url},p}(Error),Wn=xt()?function(){return self.worker&&self.worker.referrer}:function(){return(y.location.protocol==="blob:"?y.parent:y).location.href},bs,go,wr=function(u,p){if(!(/^file:/.test(_=u.url)||/^file:/.test(Wn())&&!/^\w+:/.test(_))){if(y.fetch&&y.Request&&y.AbortController&&y.Request.prototype.hasOwnProperty("signal"))return function(R,j){var $,J=new y.AbortController,le=new y.Request(R.url,{method:R.method||"GET",body:R.body,credentials:R.credentials,headers:R.headers,referrer:Wn(),signal:J.signal}),ye=!1,Me=!1,Le=($=le.url).indexOf("sku=")>0&&is($);R.type==="json"&&le.headers.set("Accept","application/json");var ze=function(lt,It,Dt){if(!Me){if(lt&<.message!=="SecurityError"&&Qe(lt),It&&Dt)return Ye(It);var tr=Date.now();y.fetch(le).then(function(qt){if(qt.ok){var ur=Le?qt.clone():null;return Ye(qt,ur,tr)}return j(new fn(qt.statusText,qt.status,R.url))}).catch(function(qt){qt.code!==20&&j(new Error(qt.message))})}},Ye=function(lt,It,Dt){(R.type==="arrayBuffer"?lt.arrayBuffer():R.type==="json"?lt.json():lt.text()).then(function(tr){Me||(It&&Dt&&function(qt,ur,Tr){if(fr(),ps){var Nr={status:ur.status,statusText:ur.statusText,headers:new y.Headers};ur.headers.forEach(function(ri,Ei){return Nr.headers.set(Ei,ri)});var Vr=sr(ur.headers.get("Cache-Control")||"");Vr["no-store"]||(Vr["max-age"]&&Nr.headers.set("Expires",new Date(Tr+1e3*Vr["max-age"]).toUTCString()),new Date(Nr.headers.get("Expires")).getTime()-Tr<42e4||function(ri,Ei){if(ws===void 0)try{new Response(new ReadableStream),ws=!0}catch{ws=!1}ws?Ei(ri.body):ri.blob().then(Ei)}(ur,function(ri){var Ei=new y.Response(ri,Nr);fr(),ps&&ps.then(function(Ui){return Ui.put(nr(qt.url),Ei)}).catch(function(Ui){return Qe(Ui.message)})}))}}(le,It,Dt),ye=!0,j(null,tr,lt.headers.get("Cache-Control"),lt.headers.get("Expires")))}).catch(function(tr){Me||j(new Error(tr.message))})};return Le?function(lt,It){if(fr(),!ps)return It(null);var Dt=nr(lt.url);ps.then(function(tr){tr.match(Dt).then(function(qt){var ur=function(Tr){if(!Tr)return!1;var Nr=new Date(Tr.headers.get("Expires")||0),Vr=sr(Tr.headers.get("Cache-Control")||"");return Nr>Date.now()&&!Vr["no-cache"]}(qt);tr.delete(Dt),ur&&tr.put(Dt,qt.clone()),It(null,qt,ur)}).catch(It)}).catch(It)}(le,ze):ze(null,null),{cancel:function(){Me=!0,ye||J.abort()}}}(u,p);if(xt()&&self.worker&&self.worker.actor)return self.worker.actor.send("getResource",u,p,void 0,!0)}var _;return function(R,j){var $=new y.XMLHttpRequest;for(var J in $.open(R.method||"GET",R.url,!0),R.type==="arrayBuffer"&&($.responseType="arraybuffer"),R.headers)$.setRequestHeader(J,R.headers[J]);return R.type==="json"&&($.responseType="text",$.setRequestHeader("Accept","application/json")),$.withCredentials=R.credentials==="include",$.onerror=function(){j(new Error($.statusText))},$.onload=function(){if(($.status>=200&&$.status<300||$.status===0)&&$.response!==null){var le=$.response;if(R.type==="json")try{le=JSON.parse($.response)}catch(ye){return j(ye)}j(null,le,$.getResponseHeader("Cache-Control"),$.getResponseHeader("Expires"))}else j(new fn($.statusText,$.status,R.url))},$.send(R.body),{cancel:function(){return $.abort()}}}(u,p)},Us=function(u,p){return wr(ee(u,{type:"arrayBuffer"}),p)},ns=function(u,p){return wr(ee(u,{method:"POST"}),p)},Be="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=";bs=[],go=0;var Xe=function(u,p){if(Ft.supported&&(u.headers||(u.headers={}),u.headers.accept="image/webp,*/*"),go>=nt.MAX_PARALLEL_IMAGE_REQUESTS){var _={requestParameters:u,callback:p,cancelled:!1,cancel:function(){this.cancelled=!0}};return bs.push(_),_}go++;var R=!1,j=function(){if(!R)for(R=!0,go--;bs.length&&go0||this._oneTimeListeners&&this._oneTimeListeners[u]&&this._oneTimeListeners[u].length>0||this._eventedParent&&this._eventedParent.listens(u)},Jt.prototype.setEventedParent=function(u,p){return this._eventedParent=u,this._eventedParentData=p,this};var ht={$version:8,$root:{version:{required:!0,type:"enum",values:[8]},name:{type:"string"},metadata:{type:"*"},center:{type:"array",value:"number"},zoom:{type:"number"},bearing:{type:"number",default:0,period:360,units:"degrees"},pitch:{type:"number",default:0,units:"degrees"},light:{type:"light"},sources:{required:!0,type:"sources"},sprite:{type:"string"},glyphs:{type:"string"},transition:{type:"transition"},layers:{required:!0,type:"array",value:"layer"}},sources:{"*":{type:"source"}},source:["source_vector","source_raster","source_raster_dem","source_geojson","source_video","source_image"],source_vector:{type:{required:!0,type:"enum",values:{vector:{}}},url:{type:"string"},tiles:{type:"array",value:"string"},bounds:{type:"array",value:"number",length:4,default:[-180,-85.051129,180,85.051129]},scheme:{type:"enum",values:{xyz:{},tms:{}},default:"xyz"},minzoom:{type:"number",default:0},maxzoom:{type:"number",default:22},attribution:{type:"string"},promoteId:{type:"promoteId"},volatile:{type:"boolean",default:!1},"*":{type:"*"}},source_raster:{type:{required:!0,type:"enum",values:{raster:{}}},url:{type:"string"},tiles:{type:"array",value:"string"},bounds:{type:"array",value:"number",length:4,default:[-180,-85.051129,180,85.051129]},minzoom:{type:"number",default:0},maxzoom:{type:"number",default:22},tileSize:{type:"number",default:512,units:"pixels"},scheme:{type:"enum",values:{xyz:{},tms:{}},default:"xyz"},attribution:{type:"string"},volatile:{type:"boolean",default:!1},"*":{type:"*"}},source_raster_dem:{type:{required:!0,type:"enum",values:{"raster-dem":{}}},url:{type:"string"},tiles:{type:"array",value:"string"},bounds:{type:"array",value:"number",length:4,default:[-180,-85.051129,180,85.051129]},minzoom:{type:"number",default:0},maxzoom:{type:"number",default:22},tileSize:{type:"number",default:512,units:"pixels"},attribution:{type:"string"},encoding:{type:"enum",values:{terrarium:{},mapbox:{}},default:"mapbox"},volatile:{type:"boolean",default:!1},"*":{type:"*"}},source_geojson:{type:{required:!0,type:"enum",values:{geojson:{}}},data:{type:"*"},maxzoom:{type:"number",default:18},attribution:{type:"string"},buffer:{type:"number",default:128,maximum:512,minimum:0},filter:{type:"*"},tolerance:{type:"number",default:.375},cluster:{type:"boolean",default:!1},clusterRadius:{type:"number",default:50,minimum:0},clusterMaxZoom:{type:"number"},clusterMinPoints:{type:"number"},clusterProperties:{type:"*"},lineMetrics:{type:"boolean",default:!1},generateId:{type:"boolean",default:!1},promoteId:{type:"promoteId"}},source_video:{type:{required:!0,type:"enum",values:{video:{}}},urls:{required:!0,type:"array",value:"string"},coordinates:{required:!0,type:"array",length:4,value:{type:"array",length:2,value:"number"}}},source_image:{type:{required:!0,type:"enum",values:{image:{}}},url:{required:!0,type:"string"},coordinates:{required:!0,type:"array",length:4,value:{type:"array",length:2,value:"number"}}},layer:{id:{type:"string",required:!0},type:{type:"enum",values:{fill:{},line:{},symbol:{},circle:{},heatmap:{},"fill-extrusion":{},raster:{},hillshade:{},background:{}},required:!0},metadata:{type:"*"},source:{type:"string"},"source-layer":{type:"string"},minzoom:{type:"number",minimum:0,maximum:24},maxzoom:{type:"number",minimum:0,maximum:24},filter:{type:"filter"},layout:{type:"layout"},paint:{type:"paint"}},layout:["layout_fill","layout_line","layout_circle","layout_heatmap","layout_fill-extrusion","layout_symbol","layout_raster","layout_hillshade","layout_background"],layout_background:{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_fill:{"fill-sort-key":{type:"number",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_circle:{"circle-sort-key":{type:"number",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_heatmap:{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},"layout_fill-extrusion":{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_line:{"line-cap":{type:"enum",values:{butt:{},round:{},square:{}},default:"butt",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"line-join":{type:"enum",values:{bevel:{},round:{},miter:{}},default:"miter",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"line-miter-limit":{type:"number",default:2,requires:[{"line-join":"miter"}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"line-round-limit":{type:"number",default:1.05,requires:[{"line-join":"round"}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"line-sort-key":{type:"number",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_symbol:{"symbol-placement":{type:"enum",values:{point:{},line:{},"line-center":{}},default:"point",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"symbol-spacing":{type:"number",default:250,minimum:1,units:"pixels",requires:[{"symbol-placement":"line"}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"symbol-avoid-edges":{type:"boolean",default:!1,expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"symbol-sort-key":{type:"number",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"symbol-z-order":{type:"enum",values:{auto:{},"viewport-y":{},source:{}},default:"auto",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-allow-overlap":{type:"boolean",default:!1,requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-ignore-placement":{type:"boolean",default:!1,requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-optional":{type:"boolean",default:!1,requires:["icon-image","text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-rotation-alignment":{type:"enum",values:{map:{},viewport:{},auto:{}},default:"auto",requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-size":{type:"number",default:1,minimum:0,units:"factor of the original icon size",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-text-fit":{type:"enum",values:{none:{},width:{},height:{},both:{}},default:"none",requires:["icon-image","text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-text-fit-padding":{type:"array",value:"number",length:4,default:[0,0,0,0],units:"pixels",requires:["icon-image","text-field",{"icon-text-fit":["both","width","height"]}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"icon-image":{type:"resolvedImage",tokens:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-rotate":{type:"number",default:0,period:360,units:"degrees",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-padding":{type:"number",default:2,minimum:0,units:"pixels",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"icon-keep-upright":{type:"boolean",default:!1,requires:["icon-image",{"icon-rotation-alignment":"map"},{"symbol-placement":["line","line-center"]}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-offset":{type:"array",value:"number",length:2,default:[0,0],requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-anchor":{type:"enum",values:{center:{},left:{},right:{},top:{},bottom:{},"top-left":{},"top-right":{},"bottom-left":{},"bottom-right":{}},default:"center",requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-pitch-alignment":{type:"enum",values:{map:{},viewport:{},auto:{}},default:"auto",requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-pitch-alignment":{type:"enum",values:{map:{},viewport:{},auto:{}},default:"auto",requires:["text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-rotation-alignment":{type:"enum",values:{map:{},viewport:{},auto:{}},default:"auto",requires:["text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-field":{type:"formatted",default:"",tokens:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-font":{type:"array",value:"string",default:["Open Sans Regular","Arial Unicode MS Regular"],requires:["text-field"],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-size":{type:"number",default:16,minimum:0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-max-width":{type:"number",default:10,minimum:0,units:"ems",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-line-height":{type:"number",default:1.2,units:"ems",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"text-letter-spacing":{type:"number",default:0,units:"ems",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-justify":{type:"enum",values:{auto:{},left:{},center:{},right:{}},default:"center",requires:["text-field"],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-radial-offset":{type:"number",units:"ems",default:0,requires:["text-field"],"property-type":"data-driven",expression:{interpolated:!0,parameters:["zoom","feature"]}},"text-variable-anchor":{type:"array",value:"enum",values:{center:{},left:{},right:{},top:{},bottom:{},"top-left":{},"top-right":{},"bottom-left":{},"bottom-right":{}},requires:["text-field",{"symbol-placement":["point"]}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-anchor":{type:"enum",values:{center:{},left:{},right:{},top:{},bottom:{},"top-left":{},"top-right":{},"bottom-left":{},"bottom-right":{}},default:"center",requires:["text-field",{"!":"text-variable-anchor"}],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-max-angle":{type:"number",default:45,units:"degrees",requires:["text-field",{"symbol-placement":["line","line-center"]}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"text-writing-mode":{type:"array",value:"enum",values:{horizontal:{},vertical:{}},requires:["text-field",{"symbol-placement":["point"]}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-rotate":{type:"number",default:0,period:360,units:"degrees",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-padding":{type:"number",default:2,minimum:0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"text-keep-upright":{type:"boolean",default:!0,requires:["text-field",{"text-rotation-alignment":"map"},{"symbol-placement":["line","line-center"]}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-transform":{type:"enum",values:{none:{},uppercase:{},lowercase:{}},default:"none",requires:["text-field"],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-offset":{type:"array",value:"number",units:"ems",length:2,default:[0,0],requires:["text-field",{"!":"text-radial-offset"}],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-allow-overlap":{type:"boolean",default:!1,requires:["text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-ignore-placement":{type:"boolean",default:!1,requires:["text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-optional":{type:"boolean",default:!1,requires:["text-field","icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_raster:{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_hillshade:{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},filter:{type:"array",value:"*"},filter_operator:{type:"enum",values:{"==":{},"!=":{},">":{},">=":{},"<":{},"<=":{},in:{},"!in":{},all:{},any:{},none:{},has:{},"!has":{},within:{}}},geometry_type:{type:"enum",values:{Point:{},LineString:{},Polygon:{}}},function:{expression:{type:"expression"},stops:{type:"array",value:"function_stop"},base:{type:"number",default:1,minimum:0},property:{type:"string",default:"$zoom"},type:{type:"enum",values:{identity:{},exponential:{},interval:{},categorical:{}},default:"exponential"},colorSpace:{type:"enum",values:{rgb:{},lab:{},hcl:{}},default:"rgb"},default:{type:"*",required:!1}},function_stop:{type:"array",minimum:0,maximum:24,value:["number","color"],length:2},expression:{type:"array",value:"*",minimum:1},light:{anchor:{type:"enum",default:"viewport",values:{map:{},viewport:{}},"property-type":"data-constant",transition:!1,expression:{interpolated:!1,parameters:["zoom"]}},position:{type:"array",default:[1.15,210,30],length:3,value:"number","property-type":"data-constant",transition:!0,expression:{interpolated:!0,parameters:["zoom"]}},color:{type:"color","property-type":"data-constant",default:"#ffffff",expression:{interpolated:!0,parameters:["zoom"]},transition:!0},intensity:{type:"number","property-type":"data-constant",default:.5,minimum:0,maximum:1,expression:{interpolated:!0,parameters:["zoom"]},transition:!0}},paint:["paint_fill","paint_line","paint_circle","paint_heatmap","paint_fill-extrusion","paint_symbol","paint_raster","paint_hillshade","paint_background"],paint_fill:{"fill-antialias":{type:"boolean",default:!0,expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"fill-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-color":{type:"color",default:"#000000",transition:!0,requires:[{"!":"fill-pattern"}],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-outline-color":{type:"color",transition:!0,requires:[{"!":"fill-pattern"},{"fill-antialias":!0}],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"fill-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["fill-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"fill-pattern":{type:"resolvedImage",transition:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"cross-faded-data-driven"}},"paint_fill-extrusion":{"fill-extrusion-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"fill-extrusion-color":{type:"color",default:"#000000",transition:!0,requires:[{"!":"fill-extrusion-pattern"}],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-extrusion-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"fill-extrusion-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["fill-extrusion-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"fill-extrusion-pattern":{type:"resolvedImage",transition:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"cross-faded-data-driven"},"fill-extrusion-height":{type:"number",default:0,minimum:0,units:"meters",transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-extrusion-base":{type:"number",default:0,minimum:0,units:"meters",transition:!0,requires:["fill-extrusion-height"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-extrusion-vertical-gradient":{type:"boolean",default:!0,transition:!1,expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"}},paint_line:{"line-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-color":{type:"color",default:"#000000",transition:!0,requires:[{"!":"line-pattern"}],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"line-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["line-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"line-width":{type:"number",default:1,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-gap-width":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-offset":{type:"number",default:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-blur":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-dasharray":{type:"array",value:"number",minimum:0,transition:!0,units:"line widths",requires:[{"!":"line-pattern"}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"cross-faded"},"line-pattern":{type:"resolvedImage",transition:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"cross-faded-data-driven"},"line-gradient":{type:"color",transition:!1,requires:[{"!":"line-dasharray"},{"!":"line-pattern"},{source:"geojson",has:{lineMetrics:!0}}],expression:{interpolated:!0,parameters:["line-progress"]},"property-type":"color-ramp"}},paint_circle:{"circle-radius":{type:"number",default:5,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-color":{type:"color",default:"#000000",transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-blur":{type:"number",default:0,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"circle-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["circle-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"circle-pitch-scale":{type:"enum",values:{map:{},viewport:{}},default:"map",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"circle-pitch-alignment":{type:"enum",values:{map:{},viewport:{}},default:"viewport",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"circle-stroke-width":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-stroke-color":{type:"color",default:"#000000",transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-stroke-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"}},paint_heatmap:{"heatmap-radius":{type:"number",default:30,minimum:1,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"heatmap-weight":{type:"number",default:1,minimum:0,transition:!1,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"heatmap-intensity":{type:"number",default:1,minimum:0,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"heatmap-color":{type:"color",default:["interpolate",["linear"],["heatmap-density"],0,"rgba(0, 0, 255, 0)",.1,"royalblue",.3,"cyan",.5,"lime",.7,"yellow",1,"red"],transition:!1,expression:{interpolated:!0,parameters:["heatmap-density"]},"property-type":"color-ramp"},"heatmap-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"}},paint_symbol:{"icon-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-color":{type:"color",default:"#000000",transition:!0,requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-halo-color":{type:"color",default:"rgba(0, 0, 0, 0)",transition:!0,requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-halo-width":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-halo-blur":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"icon-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["icon-image","icon-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-color":{type:"color",default:"#000000",transition:!0,overridable:!0,requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-halo-color":{type:"color",default:"rgba(0, 0, 0, 0)",transition:!0,requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-halo-width":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-halo-blur":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"text-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["text-field","text-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"}},paint_raster:{"raster-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-hue-rotate":{type:"number",default:0,period:360,transition:!0,units:"degrees",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-brightness-min":{type:"number",default:0,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-brightness-max":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-saturation":{type:"number",default:0,minimum:-1,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-contrast":{type:"number",default:0,minimum:-1,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-resampling":{type:"enum",values:{linear:{},nearest:{}},default:"linear",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"raster-fade-duration":{type:"number",default:300,minimum:0,transition:!1,units:"milliseconds",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"}},paint_hillshade:{"hillshade-illumination-direction":{type:"number",default:335,minimum:0,maximum:359,transition:!1,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-illumination-anchor":{type:"enum",values:{map:{},viewport:{}},default:"viewport",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-exaggeration":{type:"number",default:.5,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-shadow-color":{type:"color",default:"#000000",transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-highlight-color":{type:"color",default:"#FFFFFF",transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-accent-color":{type:"color",default:"#000000",transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"}},paint_background:{"background-color":{type:"color",default:"#000000",transition:!0,requires:[{"!":"background-pattern"}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"background-pattern":{type:"resolvedImage",transition:!0,expression:{interpolated:!1,parameters:["zoom"]},"property-type":"cross-faded"},"background-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"}},transition:{duration:{type:"number",default:300,minimum:0,units:"milliseconds"},delay:{type:"number",default:0,minimum:0,units:"milliseconds"}},"property-type":{"data-driven":{type:"property-type"},"cross-faded":{type:"property-type"},"cross-faded-data-driven":{type:"property-type"},"color-ramp":{type:"property-type"},"data-constant":{type:"property-type"},constant:{type:"property-type"}},promoteId:{"*":{type:"string"}}},Ot=function(u,p,_,R){this.message=(u?u+": ":"")+_,R&&(this.identifier=R),p!=null&&p.__line__&&(this.line=p.__line__)};function Ct(u){var p=u.value;return p?[new Ot(u.key,p,"constants have been deprecated as of v8")]:[]}function ei(u){for(var p=[],_=arguments.length-1;_-- >0;)p[_]=arguments[_+1];for(var R=0,j=p;R":u.itemType.kind==="value"?"array":"array<"+p+">"}return u.kind}var Al=[fi,pr,Di,Mi,Ii,Ls,Ni,No(zi),co];function uo(u,p){if(p.kind==="error")return null;if(u.kind==="array"){if(p.kind==="array"&&(p.N===0&&p.itemType.kind==="value"||!uo(u.itemType,p.itemType))&&(typeof u.N!="number"||u.N===p.N))return null}else{if(u.kind===p.kind)return null;if(u.kind==="value"){for(var _=0,R=Al;_255?255:le}function j(le){return R(le[le.length-1]==="%"?parseFloat(le)/100*255:parseInt(le))}function $(le){return(ye=le[le.length-1]==="%"?parseFloat(le)/100:parseFloat(le))<0?0:ye>1?1:ye;var ye}function J(le,ye,Me){return Me<0?Me+=1:Me>1&&(Me-=1),6*Me<1?le+(ye-le)*Me*6:2*Me<1?ye:3*Me<2?le+(ye-le)*(2/3-Me)*6:le}try{p.parseCSSColor=function(le){var ye,Me=le.replace(/ /g,"").toLowerCase();if(Me in _)return _[Me].slice();if(Me[0]==="#")return Me.length===4?(ye=parseInt(Me.substr(1),16))>=0&&ye<=4095?[(3840&ye)>>4|(3840&ye)>>8,240&ye|(240&ye)>>4,15&ye|(15&ye)<<4,1]:null:Me.length===7&&(ye=parseInt(Me.substr(1),16))>=0&&ye<=16777215?[(16711680&ye)>>16,(65280&ye)>>8,255&ye,1]:null;var Le=Me.indexOf("("),ze=Me.indexOf(")");if(Le!==-1&&ze+1===Me.length){var Ye=Me.substr(0,Le),lt=Me.substr(Le+1,ze-(Le+1)).split(","),It=1;switch(Ye){case"rgba":if(lt.length!==4)return null;It=$(lt.pop());case"rgb":return lt.length!==3?null:[j(lt[0]),j(lt[1]),j(lt[2]),It];case"hsla":if(lt.length!==4)return null;It=$(lt.pop());case"hsl":if(lt.length!==3)return null;var Dt=(parseFloat(lt[0])%360+360)%360/360,tr=$(lt[1]),qt=$(lt[2]),ur=qt<=.5?qt*(tr+1):qt+tr-qt*tr,Tr=2*qt-ur;return[R(255*J(Tr,ur,Dt+1/3)),R(255*J(Tr,ur,Dt)),R(255*J(Tr,ur,Dt-1/3)),It];default:return null}}return null}}catch{}}).parseCSSColor,cs=function(u,p,_,R){R===void 0&&(R=1),this.r=u,this.g=p,this.b=_,this.a=R};cs.parse=function(u){if(u){if(u instanceof cs)return u;if(typeof u=="string"){var p=ho(u);if(p)return new cs(p[0]/255*p[3],p[1]/255*p[3],p[2]/255*p[3],p[3])}}},cs.prototype.toString=function(){var u=this.toArray(),p=u[1],_=u[2],R=u[3];return"rgba("+Math.round(u[0])+","+Math.round(p)+","+Math.round(_)+","+R+")"},cs.prototype.toArray=function(){var u=this.a;return u===0?[0,0,0,0]:[255*this.r/u,255*this.g/u,255*this.b/u,u]},cs.black=new cs(0,0,0,1),cs.white=new cs(1,1,1,1),cs.transparent=new cs(0,0,0,0),cs.red=new cs(1,0,0,1);var _l=function(u,p,_){this.sensitivity=u?p?"variant":"case":p?"accent":"base",this.locale=_,this.collator=new Intl.Collator(this.locale?this.locale:[],{sensitivity:this.sensitivity,usage:"search"})};_l.prototype.compare=function(u,p){return this.collator.compare(u,p)},_l.prototype.resolvedLocale=function(){return new Intl.Collator(this.locale?this.locale:[]).resolvedOptions().locale};var zc=function(u,p,_,R,j){this.text=u,this.image=p,this.scale=_,this.fontStack=R,this.textColor=j},yl=function(u){this.sections=u};yl.fromString=function(u){return new yl([new zc(u,null,null,null,null)])},yl.prototype.isEmpty=function(){return this.sections.length===0||!this.sections.some(function(u){return u.text.length!==0||u.image&&u.image.name.length!==0})},yl.factory=function(u){return u instanceof yl?u:yl.fromString(u)},yl.prototype.toString=function(){return this.sections.length===0?"":this.sections.map(function(u){return u.text}).join("")},yl.prototype.serialize=function(){for(var u=["format"],p=0,_=this.sections;p<_.length;p+=1){var R=_[p];if(R.image)u.push(["image",R.image.name]);else{u.push(R.text);var j={};R.fontStack&&(j["text-font"]=["literal",R.fontStack.split(",")]),R.scale&&(j["font-scale"]=R.scale),R.textColor&&(j["text-color"]=["rgba"].concat(R.textColor.toArray())),u.push(j)}}return u};var Ya=function(u){this.name=u.name,this.available=u.available};function md(u,p,_,R){return typeof u=="number"&&u>=0&&u<=255&&typeof p=="number"&&p>=0&&p<=255&&typeof _=="number"&&_>=0&&_<=255?R===void 0||typeof R=="number"&&R>=0&&R<=1?null:"Invalid rgba value ["+[u,p,_,R].join(", ")+"]: 'a' must be between 0 and 1.":"Invalid rgba value ["+(typeof R=="number"?[u,p,_,R]:[u,p,_]).join(", ")+"]: 'r', 'g', and 'b' must be between 0 and 255."}function os(u){if(u===null||typeof u=="string"||typeof u=="boolean"||typeof u=="number"||u instanceof cs||u instanceof _l||u instanceof yl||u instanceof Ya)return!0;if(Array.isArray(u)){for(var p=0,_=u;p<_.length;p+=1)if(!os(_[p]))return!1;return!0}if(typeof u=="object"){for(var R in u)if(!os(u[R]))return!1;return!0}return!1}function la(u){if(u===null)return fi;if(typeof u=="string")return Di;if(typeof u=="boolean")return Mi;if(typeof u=="number")return pr;if(u instanceof cs)return Ii;if(u instanceof _l)return po;if(u instanceof yl)return Ls;if(u instanceof Ya)return co;if(Array.isArray(u)){for(var p,_=u.length,R=0,j=u;R2){var le=u[1];if(typeof le!="string"||!(le in eu)||le==="object")return p.error('The item type argument of "array" must be one of string, number, boolean',1);$=eu[le],R++}else $=zi;if(u.length>3){if(u[2]!==null&&(typeof u[2]!="number"||u[2]<0||u[2]!==Math.floor(u[2])))return p.error('The length argument to "array" must be a positive integer literal',2);J=u[2],R++}_=No($,J)}else _=eu[j];for(var ye=[];R1)&&p.push(R)}}return p.concat(this.args.map(function(j){return j.serialize()}))};var Tc=function(u){this.type=Ls,this.sections=u};Tc.parse=function(u,p){if(u.length<2)return p.error("Expected at least one argument.");var _=u[1];if(!Array.isArray(_)&&typeof _=="object")return p.error("First argument must be an image or text section.");for(var R=[],j=!1,$=1;$<=u.length-1;++$){var J=u[$];if(j&&typeof J=="object"&&!Array.isArray(J)){j=!1;var le=null;if(J["font-scale"]&&!(le=p.parse(J["font-scale"],1,pr)))return null;var ye=null;if(J["text-font"]&&!(ye=p.parse(J["text-font"],1,No(Di))))return null;var Me=null;if(J["text-color"]&&!(Me=p.parse(J["text-color"],1,Ii)))return null;var Le=R[R.length-1];Le.scale=le,Le.font=ye,Le.textColor=Me}else{var ze=p.parse(u[$],1,zi);if(!ze)return null;var Ye=ze.type.kind;if(Ye!=="string"&&Ye!=="value"&&Ye!=="null"&&Ye!=="resolvedImage")return p.error("Formatted text type must be 'string', 'value', 'image' or 'null'.");j=!0,R.push({content:ze,scale:null,font:null,textColor:null})}}return new Tc(R)},Tc.prototype.evaluate=function(u){return new yl(this.sections.map(function(p){var _=p.content.evaluate(u);return la(_)===co?new zc("",_,null,null,null):new zc(mf(_),null,p.scale?p.scale.evaluate(u):null,p.font?p.font.evaluate(u).join(","):null,p.textColor?p.textColor.evaluate(u):null)}))},Tc.prototype.eachChild=function(u){for(var p=0,_=this.sections;p<_.length;p+=1){var R=_[p];u(R.content),R.scale&&u(R.scale),R.font&&u(R.font),R.textColor&&u(R.textColor)}},Tc.prototype.outputDefined=function(){return!1},Tc.prototype.serialize=function(){for(var u=["format"],p=0,_=this.sections;p<_.length;p+=1){var R=_[p];u.push(R.content.serialize());var j={};R.scale&&(j["font-scale"]=R.scale.serialize()),R.font&&(j["text-font"]=R.font.serialize()),R.textColor&&(j["text-color"]=R.textColor.serialize()),u.push(j)}return u};var Uc=function(u){this.type=co,this.input=u};Uc.parse=function(u,p){if(u.length!==2)return p.error("Expected two arguments.");var _=p.parse(u[1],1,Di);return _?new Uc(_):p.error("No image name provided.")},Uc.prototype.evaluate=function(u){var p=this.input.evaluate(u),_=Ya.fromString(p);return _&&u.availableImages&&(_.available=u.availableImages.indexOf(p)>-1),_},Uc.prototype.eachChild=function(u){u(this.input)},Uc.prototype.outputDefined=function(){return!1},Uc.prototype.serialize=function(){return["image",this.input.serialize()]};var pm={"to-boolean":Mi,"to-color":Ii,"to-number":pr,"to-string":Di},pu=function(u,p){this.type=u,this.args=p};pu.parse=function(u,p){if(u.length<2)return p.error("Expected at least one argument.");var _=u[0];if((_==="to-boolean"||_==="to-string")&&u.length!==2)return p.error("Expected one argument.");for(var R=pm[_],j=[],$=1;$4?"Invalid rbga value "+JSON.stringify(p)+": expected an array containing either three or four numeric values.":md(p[0],p[1],p[2],p[3])))return new cs(p[0]/255,p[1]/255,p[2]/255,p[3])}throw new Dl(_||"Could not parse color from value '"+(typeof p=="string"?p:String(JSON.stringify(p)))+"'")}if(this.type.kind==="number"){for(var J=null,le=0,ye=this.args;le=p[2]||u[1]<=p[1]||u[3]>=p[3])}function gf(u,p){var _=(180+u[0])/360,R=(180-180/Math.PI*Math.log(Math.tan(Math.PI/4+u[1]*Math.PI/360)))/360,j=Math.pow(2,p.z);return[Math.round(_*j*8192),Math.round(R*j*8192)]}function gd(u,p,_){return p[1]>u[1]!=_[1]>u[1]&&u[0]<(_[0]-p[0])*(u[1]-p[1])/(_[1]-p[1])+p[0]}function Gh(u,p){for(var _,R,j,$,J,le,ye,Me=!1,Le=0,ze=p.length;Le0&&le<0||J<0&&le>0}function Sp(u,p,_){for(var R=0,j=_;R_[2]){var j=.5*R,$=u[0]-_[0]>j?-R:_[0]-u[0]>j?R:0;$===0&&($=u[0]-_[2]>j?-R:_[2]-u[0]>j?R:0),u[0]+=$}Sc(p,u)}function Zn(u,p,_,R){for(var j=8192*Math.pow(2,R.z),$=[8192*R.x,8192*R.y],J=[],le=0,ye=u;le=0)return!1;var _=!0;return u.eachChild(function(R){_&&!vl(R,p)&&(_=!1)}),_}ec.parse=function(u,p){if(u.length!==2)return p.error("'within' expression requires exactly one argument, but found "+(u.length-1)+" instead.");if(os(u[1])){var _=u[1];if(_.type==="FeatureCollection")for(var R=0;R<_.features.length;++R){var j=_.features[R].geometry.type;if(j==="Polygon"||j==="MultiPolygon")return new ec(_,_.features[R].geometry)}else if(_.type==="Feature"){var $=_.geometry.type;if($==="Polygon"||$==="MultiPolygon")return new ec(_,_.geometry)}else if(_.type==="Polygon"||_.type==="MultiPolygon")return new ec(_,_)}return p.error("'within' expression requires valid geojson object that contains polygon geometry type.")},ec.prototype.evaluate=function(u){if(u.geometry()!=null&&u.canonicalID()!=null){if(u.geometryType()==="Point")return function(p,_){var R=[1/0,1/0,-1/0,-1/0],j=[1/0,1/0,-1/0,-1/0],$=p.canonicalID();if(_.type==="Polygon"){var J=Ql(_.coordinates,j,$),le=Zn(p.geometry(),R,j,$);if(!pc(R,j))return!1;for(var ye=0,Me=le;yep))throw new Dl("Input is not a number.");$=J-1}return 0}tu.prototype.parse=function(u,p,_,R,j){return j===void 0&&(j={}),p?this.concat(p,_,R)._parse(u,j):this._parse(u,j)},tu.prototype._parse=function(u,p){function _(Me,Le,ze){return ze==="assert"?new ca(Le,[Me]):ze==="coerce"?new pu(Le,[Me]):Me}if(u!==null&&typeof u!="string"&&typeof u!="boolean"&&typeof u!="number"||(u=["literal",u]),Array.isArray(u)){if(u.length===0)return this.error('Expected an array with at least one element. If you wanted a literal array, use ["literal", []].');var R=u[0];if(typeof R!="string")return this.error("Expression name must be a string, but found "+typeof R+' instead. If you wanted a literal array, use ["literal", [...]].',0),null;var j=this.registry[R];if(j){var $=j.parse(u,this);if(!$)return null;if(this.expectedType){var J=this.expectedType,le=$.type;if(J.kind!=="string"&&J.kind!=="number"&&J.kind!=="boolean"&&J.kind!=="object"&&J.kind!=="array"||le.kind!=="value")if(J.kind!=="color"&&J.kind!=="formatted"&&J.kind!=="resolvedImage"||le.kind!=="value"&&le.kind!=="string"){if(this.checkSubtype(J,le))return null}else $=_($,J,p.typeAnnotation||"coerce");else $=_($,J,p.typeAnnotation||"assert")}if(!($ instanceof Ll)&&$.type.kind!=="resolvedImage"&&function Me(Le){if(Le instanceof Pu)return Me(Le.boundExpression);if(Le instanceof Ia&&Le.name==="error"||Le instanceof Iu||Le instanceof ec)return!1;var ze=Le instanceof pu||Le instanceof ca,Ye=!0;return Le.eachChild(function(lt){Ye=ze?Ye&&Me(lt):Ye&< instanceof Ll}),!!Ye&&sl(Le)&&vl(Le,["zoom","heatmap-density","line-progress","accumulated","is-supported-script"])}($)){var ye=new jh;try{$=new Ll($.type,$.evaluate(ye))}catch(Me){return this.error(Me.message),null}}return $}return this.error('Unknown expression "'+R+'". If you wanted a literal array, use ["literal", [...]].',0)}return this.error(u===void 0?"'undefined' value invalid. Use null instead.":typeof u=="object"?'Bare objects invalid. Use ["literal", {...}] instead.':"Expected an array, but found "+typeof u+" instead.")},tu.prototype.concat=function(u,p,_){var R=typeof u=="number"?this.path.concat(u):this.path,j=_?this.scope.concat(_):this.scope;return new tu(this.registry,R,p||null,j,this.errors)},tu.prototype.error=function(u){for(var p=[],_=arguments.length-1;_-- >0;)p[_]=arguments[_+1];var R=""+this.key+p.map(function(j){return"["+j+"]"}).join("");this.errors.push(new si(R,u))},tu.prototype.checkSubtype=function(u,p){var _=uo(u,p);return _&&this.error(_),_};var xl=function(u,p,_){this.type=u,this.input=p,this.labels=[],this.outputs=[];for(var R=0,j=_;R=J)return p.error('Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.',ye);var Le=p.parse(le,Me,j);if(!Le)return null;j=j||Le.type,R.push([J,Le])}return new xl(j,_,R)},xl.prototype.evaluate=function(u){var p=this.labels,_=this.outputs;if(p.length===1)return _[0].evaluate(u);var R=this.input.evaluate(u);if(R<=p[0])return _[0].evaluate(u);var j=p.length;return R>=p[j-1]?_[j-1].evaluate(u):_[La(p,R)].evaluate(u)},xl.prototype.eachChild=function(u){u(this.input);for(var p=0,_=this.outputs;p<_.length;p+=1)u(_[p])},xl.prototype.outputDefined=function(){return this.outputs.every(function(u){return u.outputDefined()})},xl.prototype.serialize=function(){for(var u=["step",this.input.serialize()],p=0;p0&&u.push(this.labels[p]),u.push(this.outputs[p].serialize());return u};var Wh=Object.freeze({__proto__:null,number:Ws,color:function(u,p,_){return new cs(Ws(u.r,p.r,_),Ws(u.g,p.g,_),Ws(u.b,p.b,_),Ws(u.a,p.a,_))},array:function(u,p,_){return u.map(function(R,j){return Ws(R,p[j],_)})}}),Ec=6/29*3*(6/29),Hf=Math.PI/180,qh=180/Math.PI;function Gd(u){return u>.008856451679035631?Math.pow(u,1/3):u/Ec+4/29}function Cp(u){return u>6/29?u*u*u:Ec*(u-4/29)}function Af(u){return 255*(u<=.0031308?12.92*u:1.055*Math.pow(u,1/2.4)-.055)}function ra(u){return(u/=255)<=.04045?u/12.92:Math.pow((u+.055)/1.055,2.4)}function mh(u){var p=ra(u.r),_=ra(u.g),R=ra(u.b),j=Gd((.4124564*p+.3575761*_+.1804375*R)/.95047),$=Gd((.2126729*p+.7151522*_+.072175*R)/1);return{l:116*$-16,a:500*(j-$),b:200*($-Gd((.0193339*p+.119192*_+.9503041*R)/1.08883)),alpha:u.a}}function ms(u){var p=(u.l+16)/116,_=isNaN(u.a)?p:p+u.a/500,R=isNaN(u.b)?p:p-u.b/200;return p=1*Cp(p),_=.95047*Cp(_),R=1.08883*Cp(R),new cs(Af(3.2404542*_-1.5371385*p-.4985314*R),Af(-.969266*_+1.8760108*p+.041556*R),Af(.0556434*_-.2040259*p+1.0572252*R),u.alpha)}function Ao(u,p,_){var R=p-u;return u+_*(R>180||R<-180?R-360*Math.round(R/360):R)}var $h={forward:mh,reverse:ms,interpolate:function(u,p,_){return{l:Ws(u.l,p.l,_),a:Ws(u.a,p.a,_),b:Ws(u.b,p.b,_),alpha:Ws(u.alpha,p.alpha,_)}}},us={forward:function(u){var p=mh(u),_=p.l,R=p.a,j=p.b,$=Math.atan2(j,R)*qh;return{h:$<0?$+360:$,c:Math.sqrt(R*R+j*j),l:_,alpha:u.a}},reverse:function(u){var p=u.h*Hf,_=u.c;return ms({l:u.l,a:Math.cos(p)*_,b:Math.sin(p)*_,alpha:u.alpha})},interpolate:function(u,p,_){return{h:Ao(u.h,p.h,_),c:Ws(u.c,p.c,_),l:Ws(u.l,p.l,_),alpha:Ws(u.alpha,p.alpha,_)}}},Hd=Object.freeze({__proto__:null,lab:$h,hcl:us}),q=function(u,p,_,R,j){this.type=u,this.operator=p,this.interpolation=_,this.input=R,this.labels=[],this.outputs=[];for(var $=0,J=j;$1}))return p.error("Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.",1);R={name:"cubic-bezier",controlPoints:le}}if(u.length-1<4)return p.error("Expected at least 4 arguments, but found only "+(u.length-1)+".");if((u.length-1)%2!=0)return p.error("Expected an even number of arguments.");if(!(j=p.parse(j,2,pr)))return null;var ye=[],Me=null;_==="interpolate-hcl"||_==="interpolate-lab"?Me=Ii:p.expectedType&&p.expectedType.kind!=="value"&&(Me=p.expectedType);for(var Le=0;Le<$.length;Le+=2){var ze=$[Le],Ye=$[Le+1],lt=Le+3,It=Le+4;if(typeof ze!="number")return p.error('Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.',lt);if(ye.length&&ye[ye.length-1][0]>=ze)return p.error('Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.',lt);var Dt=p.parse(Ye,It,Me);if(!Dt)return null;Me=Me||Dt.type,ye.push([ze,Dt])}return Me.kind==="number"||Me.kind==="color"||Me.kind==="array"&&Me.itemType.kind==="number"&&typeof Me.N=="number"?new q(Me,_,R,j,ye):p.error("Type "+wo(Me)+" is not interpolatable.")},q.prototype.evaluate=function(u){var p=this.labels,_=this.outputs;if(p.length===1)return _[0].evaluate(u);var R=this.input.evaluate(u);if(R<=p[0])return _[0].evaluate(u);var j=p.length;if(R>=p[j-1])return _[j-1].evaluate(u);var $=La(p,R),J=q.interpolationFactor(this.interpolation,R,p[$],p[$+1]),le=_[$].evaluate(u),ye=_[$+1].evaluate(u);return this.operator==="interpolate"?Wh[this.type.kind.toLowerCase()](le,ye,J):this.operator==="interpolate-hcl"?us.reverse(us.interpolate(us.forward(le),us.forward(ye),J)):$h.reverse($h.interpolate($h.forward(le),$h.forward(ye),J))},q.prototype.eachChild=function(u){u(this.input);for(var p=0,_=this.outputs;p<_.length;p+=1)u(_[p])},q.prototype.outputDefined=function(){return this.outputs.every(function(u){return u.outputDefined()})},q.prototype.serialize=function(){var u;u=this.interpolation.name==="linear"?["linear"]:this.interpolation.name==="exponential"?this.interpolation.base===1?["linear"]:["exponential",this.interpolation.base]:["cubic-bezier"].concat(this.interpolation.controlPoints);for(var p=[this.operator,u,this.input.serialize()],_=0;_=_.length)throw new Dl("Array index out of bounds: "+p+" > "+(_.length-1)+".");if(p!==Math.floor(p))throw new Dl("Array index must be an integer, but found "+p+" instead.");return _[p]},Ge.prototype.eachChild=function(u){u(this.index),u(this.input)},Ge.prototype.outputDefined=function(){return!1},Ge.prototype.serialize=function(){return["at",this.index.serialize(),this.input.serialize()]};var qe=function(u,p){this.type=Mi,this.needle=u,this.haystack=p};qe.parse=function(u,p){if(u.length!==3)return p.error("Expected 2 arguments, but found "+(u.length-1)+" instead.");var _=p.parse(u[1],1,zi),R=p.parse(u[2],2,zi);return _&&R?Qn(_.type,[Mi,Di,pr,fi,zi])?new qe(_,R):p.error("Expected first argument to be of type boolean, string, number or null, but found "+wo(_.type)+" instead"):null},qe.prototype.evaluate=function(u){var p=this.needle.evaluate(u),_=this.haystack.evaluate(u);if(!_)return!1;if(!Ds(p,["boolean","string","number","null"]))throw new Dl("Expected first argument to be of type boolean, string, number or null, but found "+wo(la(p))+" instead.");if(!Ds(_,["string","array"]))throw new Dl("Expected second argument to be of type array or string, but found "+wo(la(_))+" instead.");return _.indexOf(p)>=0},qe.prototype.eachChild=function(u){u(this.needle),u(this.haystack)},qe.prototype.outputDefined=function(){return!0},qe.prototype.serialize=function(){return["in",this.needle.serialize(),this.haystack.serialize()]};var ot=function(u,p,_){this.type=pr,this.needle=u,this.haystack=p,this.fromIndex=_};ot.parse=function(u,p){if(u.length<=2||u.length>=5)return p.error("Expected 3 or 4 arguments, but found "+(u.length-1)+" instead.");var _=p.parse(u[1],1,zi),R=p.parse(u[2],2,zi);if(!_||!R)return null;if(!Qn(_.type,[Mi,Di,pr,fi,zi]))return p.error("Expected first argument to be of type boolean, string, number or null, but found "+wo(_.type)+" instead");if(u.length===4){var j=p.parse(u[3],3,pr);return j?new ot(_,R,j):null}return new ot(_,R)},ot.prototype.evaluate=function(u){var p=this.needle.evaluate(u),_=this.haystack.evaluate(u);if(!Ds(p,["boolean","string","number","null"]))throw new Dl("Expected first argument to be of type boolean, string, number or null, but found "+wo(la(p))+" instead.");if(!Ds(_,["string","array"]))throw new Dl("Expected second argument to be of type array or string, but found "+wo(la(_))+" instead.");if(this.fromIndex){var R=this.fromIndex.evaluate(u);return _.indexOf(p,R)}return _.indexOf(p)},ot.prototype.eachChild=function(u){u(this.needle),u(this.haystack),this.fromIndex&&u(this.fromIndex)},ot.prototype.outputDefined=function(){return!1},ot.prototype.serialize=function(){if(this.fromIndex!=null&&this.fromIndex!==void 0){var u=this.fromIndex.serialize();return["index-of",this.needle.serialize(),this.haystack.serialize(),u]}return["index-of",this.needle.serialize(),this.haystack.serialize()]};var rr=function(u,p,_,R,j,$){this.inputType=u,this.type=p,this.input=_,this.cases=R,this.outputs=j,this.otherwise=$};rr.parse=function(u,p){if(u.length<5)return p.error("Expected at least 4 arguments, but found only "+(u.length-1)+".");if(u.length%2!=1)return p.error("Expected an even number of arguments.");var _,R;p.expectedType&&p.expectedType.kind!=="value"&&(R=p.expectedType);for(var j={},$=[],J=2;JNumber.MAX_SAFE_INTEGER)return Me.error("Branch labels must be integers no larger than "+Number.MAX_SAFE_INTEGER+".");if(typeof Ye=="number"&&Math.floor(Ye)!==Ye)return Me.error("Numeric branch labels must be integer values.");if(_){if(Me.checkSubtype(_,la(Ye)))return null}else _=la(Ye);if(j[String(Ye)]!==void 0)return Me.error("Branch labels must be unique.");j[String(Ye)]=$.length}var lt=p.parse(ye,J,R);if(!lt)return null;R=R||lt.type,$.push(lt)}var It=p.parse(u[1],1,zi);if(!It)return null;var Dt=p.parse(u[u.length-1],u.length-1,R);return Dt?It.type.kind!=="value"&&p.concat(1).checkSubtype(_,It.type)?null:new rr(_,R,It,j,$,Dt):null},rr.prototype.evaluate=function(u){var p=this.input.evaluate(u);return(la(p)===this.inputType&&this.outputs[this.cases[p]]||this.otherwise).evaluate(u)},rr.prototype.eachChild=function(u){u(this.input),this.outputs.forEach(u),u(this.otherwise)},rr.prototype.outputDefined=function(){return this.outputs.every(function(u){return u.outputDefined()})&&this.otherwise.outputDefined()},rr.prototype.serialize=function(){for(var u=this,p=["match",this.input.serialize()],_=[],R={},j=0,$=Object.keys(this.cases).sort();j<$.length;j+=1){var J=$[j];(ze=R[this.cases[J]])===void 0?(R[this.cases[J]]=_.length,_.push([this.cases[J],[J]])):_[ze][1].push(J)}for(var le=function(lt){return u.inputType.kind==="number"?Number(lt):lt},ye=0,Me=_;ye=5)return p.error("Expected 3 or 4 arguments, but found "+(u.length-1)+" instead.");var _=p.parse(u[1],1,zi),R=p.parse(u[2],2,pr);if(!_||!R)return null;if(!Qn(_.type,[No(zi),Di,zi]))return p.error("Expected first argument to be of type array or string, but found "+wo(_.type)+" instead");if(u.length===4){var j=p.parse(u[3],3,pr);return j?new cr(_.type,_,R,j):null}return new cr(_.type,_,R)},cr.prototype.evaluate=function(u){var p=this.input.evaluate(u),_=this.beginIndex.evaluate(u);if(!Ds(p,["string","array"]))throw new Dl("Expected first argument to be of type array or string, but found "+wo(la(p))+" instead.");if(this.endIndex){var R=this.endIndex.evaluate(u);return p.slice(_,R)}return p.slice(_)},cr.prototype.eachChild=function(u){u(this.input),u(this.beginIndex),this.endIndex&&u(this.endIndex)},cr.prototype.outputDefined=function(){return!1},cr.prototype.serialize=function(){if(this.endIndex!=null&&this.endIndex!==void 0){var u=this.endIndex.serialize();return["slice",this.input.serialize(),this.beginIndex.serialize(),u]}return["slice",this.input.serialize(),this.beginIndex.serialize()]};var Bi=vr("==",function(u,p,_){return p===_},li),gn=vr("!=",function(u,p,_){return p!==_},function(u,p,_,R){return!li(0,p,_,R)}),gs=vr("<",function(u,p,_){return p<_},function(u,p,_,R){return R.compare(p,_)<0}),Io=vr(">",function(u,p,_){return p>_},function(u,p,_,R){return R.compare(p,_)>0}),bl=vr("<=",function(u,p,_){return p<=_},function(u,p,_,R){return R.compare(p,_)<=0}),al=vr(">=",function(u,p,_){return p>=_},function(u,p,_,R){return R.compare(p,_)>=0}),Da=function(u,p,_,R,j){this.type=Di,this.number=u,this.locale=p,this.currency=_,this.minFractionDigits=R,this.maxFractionDigits=j};Da.parse=function(u,p){if(u.length!==3)return p.error("Expected two arguments.");var _=p.parse(u[1],1,pr);if(!_)return null;var R=u[2];if(typeof R!="object"||Array.isArray(R))return p.error("NumberFormat options argument must be an object.");var j=null;if(R.locale&&!(j=p.parse(R.locale,1,Di)))return null;var $=null;if(R.currency&&!($=p.parse(R.currency,1,Di)))return null;var J=null;if(R["min-fraction-digits"]&&!(J=p.parse(R["min-fraction-digits"],1,pr)))return null;var le=null;return R["max-fraction-digits"]&&!(le=p.parse(R["max-fraction-digits"],1,pr))?null:new Da(_,j,$,J,le)},Da.prototype.evaluate=function(u){return new Intl.NumberFormat(this.locale?this.locale.evaluate(u):[],{style:this.currency?"currency":"decimal",currency:this.currency?this.currency.evaluate(u):void 0,minimumFractionDigits:this.minFractionDigits?this.minFractionDigits.evaluate(u):void 0,maximumFractionDigits:this.maxFractionDigits?this.maxFractionDigits.evaluate(u):void 0}).format(this.number.evaluate(u))},Da.prototype.eachChild=function(u){u(this.number),this.locale&&u(this.locale),this.currency&&u(this.currency),this.minFractionDigits&&u(this.minFractionDigits),this.maxFractionDigits&&u(this.maxFractionDigits)},Da.prototype.outputDefined=function(){return!1},Da.prototype.serialize=function(){var u={};return this.locale&&(u.locale=this.locale.serialize()),this.currency&&(u.currency=this.currency.serialize()),this.minFractionDigits&&(u["min-fraction-digits"]=this.minFractionDigits.serialize()),this.maxFractionDigits&&(u["max-fraction-digits"]=this.maxFractionDigits.serialize()),["number-format",this.number.serialize(),u]};var Ka=function(u){this.type=pr,this.input=u};Ka.parse=function(u,p){if(u.length!==2)return p.error("Expected 1 argument, but found "+(u.length-1)+" instead.");var _=p.parse(u[1],1);return _?_.type.kind!=="array"&&_.type.kind!=="string"&&_.type.kind!=="value"?p.error("Expected argument of type string or array, but found "+wo(_.type)+" instead."):new Ka(_):null},Ka.prototype.evaluate=function(u){var p=this.input.evaluate(u);if(typeof p=="string"||Array.isArray(p))return p.length;throw new Dl("Expected value to be of type string or array, but found "+wo(la(p))+" instead.")},Ka.prototype.eachChild=function(u){u(this.input)},Ka.prototype.outputDefined=function(){return!1},Ka.prototype.serialize=function(){var u=["length"];return this.eachChild(function(p){u.push(p.serialize())}),u};var Yn={"==":Bi,"!=":gn,">":Io,"<":gs,">=":al,"<=":bl,array:ca,at:Ge,boolean:ca,case:Ut,coalesce:pe,collator:Iu,format:Tc,image:Uc,in:qe,"index-of":ot,interpolate:q,"interpolate-hcl":q,"interpolate-lab":q,length:Ka,let:be,literal:Ll,match:rr,number:ca,"number-format":Da,object:ca,slice:cr,step:xl,string:ca,"to-boolean":pu,"to-color":pu,"to-number":pu,"to-string":pu,var:Pu,within:ec};function Er(u,p){var _=p[0],R=p[1],j=p[2],$=p[3];_=_.evaluate(u),R=R.evaluate(u),j=j.evaluate(u);var J=$?$.evaluate(u):1,le=md(_,R,j,J);if(le)throw new Dl(le);return new cs(_/255*J,R/255*J,j/255*J,J)}function Hr(u,p){return u in p}function wi(u,p){var _=p[u];return _===void 0?null:_}function ni(u){return{type:u}}function b(u){return{result:"success",value:u}}function Po(u){return{result:"error",value:u}}function _o(u){return u["property-type"]==="data-driven"||u["property-type"]==="cross-faded-data-driven"}function Ze(u){return!!u.expression&&u.expression.parameters.indexOf("zoom")>-1}function Is(u){return!!u.expression&&u.expression.interpolated}function ao(u){return u instanceof Number?"number":u instanceof String?"string":u instanceof Boolean?"boolean":Array.isArray(u)?"array":u===null?"null":typeof u}function Ps(u){return typeof u=="object"&&u!==null&&!Array.isArray(u)}function Rs(u){return u}function Ho(u,p,_){return u!==void 0?u:p!==void 0?p:_!==void 0?_:void 0}function Ks(u,p,_,R,j){return Ho(typeof _===j?R[_]:void 0,u.default,p.default)}function Ts(u,p,_){if(ao(_)!=="number")return Ho(u.default,p.default);var R=u.stops.length;if(R===1||_<=u.stops[0][0])return u.stops[0][1];if(_>=u.stops[R-1][0])return u.stops[R-1][1];var j=La(u.stops.map(function($){return $[0]}),_);return u.stops[j][1]}function Os(u,p,_){var R=u.base!==void 0?u.base:1;if(ao(_)!=="number")return Ho(u.default,p.default);var j=u.stops.length;if(j===1||_<=u.stops[0][0])return u.stops[0][1];if(_>=u.stops[j-1][0])return u.stops[j-1][1];var $=La(u.stops.map(function(ze){return ze[0]}),_),J=function(ze,Ye,lt,It){var Dt=It-lt,tr=ze-lt;return Dt===0?0:Ye===1?tr/Dt:(Math.pow(Ye,tr)-1)/(Math.pow(Ye,Dt)-1)}(_,R,u.stops[$][0],u.stops[$+1][0]),le=u.stops[$][1],ye=u.stops[$+1][1],Me=Wh[p.type]||Rs;if(u.colorSpace&&u.colorSpace!=="rgb"){var Le=Hd[u.colorSpace];Me=function(ze,Ye){return Le.reverse(Le.interpolate(Le.forward(ze),Le.forward(Ye),J))}}return typeof le.evaluate=="function"?{evaluate:function(){for(var ze=[],Ye=arguments.length;Ye--;)ze[Ye]=arguments[Ye];var lt=le.evaluate.apply(void 0,ze),It=ye.evaluate.apply(void 0,ze);if(lt!==void 0&&It!==void 0)return Me(lt,It,J)}}:Me(le,ye,J)}function gh(u,p,_){return p.type==="color"?_=cs.parse(_):p.type==="formatted"?_=yl.fromString(_.toString()):p.type==="resolvedImage"?_=Ya.fromString(_.toString()):ao(_)===p.type||p.type==="enum"&&p.values[_]||(_=void 0),Ho(_,u.default,p.default)}Ia.register(Yn,{error:[{kind:"error"},[Di],function(u,p){throw new Dl(p[0].evaluate(u))}],typeof:[Di,[zi],function(u,p){return wo(la(p[0].evaluate(u)))}],"to-rgba":[No(pr,4),[Ii],function(u,p){return p[0].evaluate(u).toArray()}],rgb:[Ii,[pr,pr,pr],Er],rgba:[Ii,[pr,pr,pr,pr],Er],has:{type:Mi,overloads:[[[Di],function(u,p){return Hr(p[0].evaluate(u),u.properties())}],[[Di,Ni],function(u,p){var _=p[1];return Hr(p[0].evaluate(u),_.evaluate(u))}]]},get:{type:zi,overloads:[[[Di],function(u,p){return wi(p[0].evaluate(u),u.properties())}],[[Di,Ni],function(u,p){var _=p[1];return wi(p[0].evaluate(u),_.evaluate(u))}]]},"feature-state":[zi,[Di],function(u,p){return wi(p[0].evaluate(u),u.featureState||{})}],properties:[Ni,[],function(u){return u.properties()}],"geometry-type":[Di,[],function(u){return u.geometryType()}],id:[zi,[],function(u){return u.id()}],zoom:[pr,[],function(u){return u.globals.zoom}],"heatmap-density":[pr,[],function(u){return u.globals.heatmapDensity||0}],"line-progress":[pr,[],function(u){return u.globals.lineProgress||0}],accumulated:[zi,[],function(u){return u.globals.accumulated===void 0?null:u.globals.accumulated}],"+":[pr,ni(pr),function(u,p){for(var _=0,R=0,j=p;R":[Mi,[Di,zi],function(u,p){var _=p[0],R=p[1],j=u.properties()[_.value],$=R.value;return typeof j==typeof $&&j>$}],"filter-id->":[Mi,[zi],function(u,p){var _=p[0],R=u.id(),j=_.value;return typeof R==typeof j&&R>j}],"filter-<=":[Mi,[Di,zi],function(u,p){var _=p[0],R=p[1],j=u.properties()[_.value],$=R.value;return typeof j==typeof $&&j<=$}],"filter-id-<=":[Mi,[zi],function(u,p){var _=p[0],R=u.id(),j=_.value;return typeof R==typeof j&&R<=j}],"filter->=":[Mi,[Di,zi],function(u,p){var _=p[0],R=p[1],j=u.properties()[_.value],$=R.value;return typeof j==typeof $&&j>=$}],"filter-id->=":[Mi,[zi],function(u,p){var _=p[0],R=u.id(),j=_.value;return typeof R==typeof j&&R>=j}],"filter-has":[Mi,[zi],function(u,p){return p[0].value in u.properties()}],"filter-has-id":[Mi,[],function(u){return u.id()!==null&&u.id()!==void 0}],"filter-type-in":[Mi,[No(Di)],function(u,p){return p[0].value.indexOf(u.geometryType())>=0}],"filter-id-in":[Mi,[No(zi)],function(u,p){return p[0].value.indexOf(u.id())>=0}],"filter-in-small":[Mi,[Di,No(zi)],function(u,p){var _=p[0];return p[1].value.indexOf(u.properties()[_.value])>=0}],"filter-in-large":[Mi,[Di,No(zi)],function(u,p){var _=p[0],R=p[1];return function(j,$,J,le){for(;J<=le;){var ye=J+le>>1;if($[ye]===j)return!0;$[ye]>j?le=ye-1:J=ye+1}return!1}(u.properties()[_.value],R.value,0,R.value.length-1)}],all:{type:Mi,overloads:[[[Mi,Mi],function(u,p){var _=p[1];return p[0].evaluate(u)&&_.evaluate(u)}],[ni(Mi),function(u,p){for(var _=0,R=p;_0&&typeof u[0]=="string"&&u[0]in Yn}function mc(u,p){var _=new tu(Yn,[],p?function(j){var $={color:Ii,string:Di,number:pr,enum:Di,boolean:Mi,formatted:Ls,resolvedImage:co};return j.type==="array"?No($[j.value]||zi,j.length):$[j.type]}(p):void 0),R=_.parse(u,void 0,void 0,void 0,p&&p.type==="string"?{typeAnnotation:"coerce"}:void 0);return R?b(new Ss(R,p)):Po(_.errors)}Ss.prototype.evaluateWithoutErrorHandling=function(u,p,_,R,j,$){return this._evaluator.globals=u,this._evaluator.feature=p,this._evaluator.featureState=_,this._evaluator.canonical=R,this._evaluator.availableImages=j||null,this._evaluator.formattedSection=$,this.expression.evaluate(this._evaluator)},Ss.prototype.evaluate=function(u,p,_,R,j,$){this._evaluator.globals=u,this._evaluator.feature=p||null,this._evaluator.featureState=_||null,this._evaluator.canonical=R,this._evaluator.availableImages=j||null,this._evaluator.formattedSection=$||null;try{var J=this.expression.evaluate(this._evaluator);if(J==null||typeof J=="number"&&J!=J)return this._defaultValue;if(this._enumValues&&!(J in this._enumValues))throw new Dl("Expected value to be one of "+Object.keys(this._enumValues).map(function(le){return JSON.stringify(le)}).join(", ")+", but found "+JSON.stringify(J)+" instead.");return J}catch(le){return this._warningHistory[le.message]||(this._warningHistory[le.message]=!0,typeof console<"u"&&console.warn(le.message)),this._defaultValue}};var Qa=function(u,p){this.kind=u,this._styleExpression=p,this.isStateDependent=u!=="constant"&&!Ol(p.expression)};Qa.prototype.evaluateWithoutErrorHandling=function(u,p,_,R,j,$){return this._styleExpression.evaluateWithoutErrorHandling(u,p,_,R,j,$)},Qa.prototype.evaluate=function(u,p,_,R,j,$){return this._styleExpression.evaluate(u,p,_,R,j,$)};var Ru=function(u,p,_,R){this.kind=u,this.zoomStops=_,this._styleExpression=p,this.isStateDependent=u!=="camera"&&!Ol(p.expression),this.interpolationType=R};function gc(u,p){if((u=mc(u,p)).result==="error")return u;var _=u.value.expression,R=sl(_);if(!R&&!_o(p))return Po([new si("","data expressions not supported")]);var j=vl(_,["zoom"]);if(!j&&!Ze(p))return Po([new si("","zoom expressions not supported")]);var $=function J(le){var ye=null;if(le instanceof be)ye=J(le.result);else if(le instanceof pe)for(var Me=0,Le=le.args;MeR.maximum?[new Ot(p,_,_+" is greater than the maximum value "+R.maximum)]:[]}function dt(u){var p,_,R,j=u.valueSpec,$=ui(u.value.type),J={},le=$!=="categorical"&&u.value.property===void 0,ye=!le,Me=ao(u.value.stops)==="array"&&ao(u.value.stops[0])==="array"&&ao(u.value.stops[0][0])==="object",Le=Fl({key:u.key,value:u.value,valueSpec:u.styleSpec.function,style:u.style,styleSpec:u.styleSpec,objectElementValidators:{stops:function(lt){if($==="identity")return[new Ot(lt.key,lt.value,'identity function may not have a "stops" property')];var It=[],Dt=lt.value;return It=It.concat(Ac({key:lt.key,value:Dt,valueSpec:lt.valueSpec,style:lt.style,styleSpec:lt.styleSpec,arrayElementValidator:ze})),ao(Dt)==="array"&&Dt.length===0&&It.push(new Ot(lt.key,Dt,"array must have at least one stop")),It},default:function(lt){return Gi({key:lt.key,value:lt.value,valueSpec:j,style:lt.style,styleSpec:lt.styleSpec})}}});return $==="identity"&&le&&Le.push(new Ot(u.key,u.value,'missing required property "property"')),$==="identity"||u.value.stops||Le.push(new Ot(u.key,u.value,'missing required property "stops"')),$==="exponential"&&u.valueSpec.expression&&!Is(u.valueSpec)&&Le.push(new Ot(u.key,u.value,"exponential functions not supported")),u.styleSpec.$version>=8&&(ye&&!_o(u.valueSpec)?Le.push(new Ot(u.key,u.value,"property functions not supported")):le&&!Ze(u.valueSpec)&&Le.push(new Ot(u.key,u.value,"zoom functions not supported"))),$!=="categorical"&&!Me||u.value.property!==void 0||Le.push(new Ot(u.key,u.value,'"property" property is required')),Le;function ze(lt){var It=[],Dt=lt.value,tr=lt.key;if(ao(Dt)!=="array")return[new Ot(tr,Dt,"array expected, "+ao(Dt)+" found")];if(Dt.length!==2)return[new Ot(tr,Dt,"array length 2 expected, length "+Dt.length+" found")];if(Me){if(ao(Dt[0])!=="object")return[new Ot(tr,Dt,"object expected, "+ao(Dt[0])+" found")];if(Dt[0].zoom===void 0)return[new Ot(tr,Dt,"object stop key must have zoom")];if(Dt[0].value===void 0)return[new Ot(tr,Dt,"object stop key must have value")];if(R&&R>ui(Dt[0].zoom))return[new Ot(tr,Dt[0].zoom,"stop zoom values must appear in ascending order")];ui(Dt[0].zoom)!==R&&(R=ui(Dt[0].zoom),_=void 0,J={}),It=It.concat(Fl({key:tr+"[0]",value:Dt[0],valueSpec:{zoom:{}},style:lt.style,styleSpec:lt.styleSpec,objectElementValidators:{zoom:qn,value:Ye}}))}else It=It.concat(Ye({key:tr+"[0]",value:Dt[0],valueSpec:{},style:lt.style,styleSpec:lt.styleSpec},Dt));return Ah(Gr(Dt[1]))?It.concat([new Ot(tr+"[1]",Dt[1],"expressions are not allowed in function stops.")]):It.concat(Gi({key:tr+"[1]",value:Dt[1],valueSpec:j,style:lt.style,styleSpec:lt.styleSpec}))}function Ye(lt,It){var Dt=ao(lt.value),tr=ui(lt.value),qt=lt.value!==null?lt.value:It;if(p){if(Dt!==p)return[new Ot(lt.key,qt,Dt+" stop domain type must match previous stop domain type "+p)]}else p=Dt;if(Dt!=="number"&&Dt!=="string"&&Dt!=="boolean")return[new Ot(lt.key,qt,"stop domain value must be a number, string, or boolean")];if(Dt!=="number"&&$!=="categorical"){var ur="number expected, "+Dt+" found";return _o(j)&&$===void 0&&(ur+='\nIf you intended to use a categorical function, specify `"type": "categorical"`.'),[new Ot(lt.key,qt,ur)]}return $!=="categorical"||Dt!=="number"||isFinite(tr)&&Math.floor(tr)===tr?$!=="categorical"&&Dt==="number"&&_!==void 0&&tr<_?[new Ot(lt.key,qt,"stop domain values must appear in ascending order")]:(_=tr,$==="categorical"&&tr in J?[new Ot(lt.key,qt,"stop domain values must be unique")]:(J[tr]=!0,[])):[new Ot(lt.key,qt,"integer expected, found "+tr)]}}function Fn(u){var p=(u.expressionContext==="property"?gc:mc)(Gr(u.value),u.valueSpec);if(p.result==="error")return p.value.map(function(R){return new Ot(""+u.key+R.key,u.value,R.message)});var _=p.value.expression||p.value._styleExpression.expression;if(u.expressionContext==="property"&&u.propertyKey==="text-font"&&!_.outputDefined())return[new Ot(u.key,u.value,'Invalid data expression for "'+u.propertyKey+'". Output values must be contained as literals within the expression.')];if(u.expressionContext==="property"&&u.propertyType==="layout"&&!Ol(_))return[new Ot(u.key,u.value,'"feature-state" data expressions are not supported with layout properties.')];if(u.expressionContext==="filter"&&!Ol(_))return[new Ot(u.key,u.value,'"feature-state" data expressions are not supported with filters.')];if(u.expressionContext&&u.expressionContext.indexOf("cluster")===0){if(!vl(_,["zoom","feature-state"]))return[new Ot(u.key,u.value,'"zoom" and "feature-state" expressions are not supported with cluster properties.')];if(u.expressionContext==="cluster-initial"&&!sl(_))return[new Ot(u.key,u.value,"Feature data expressions are not supported with initial expression part of cluster properties.")]}return[]}function Xh(u){var p=u.key,_=u.value,R=u.valueSpec,j=[];return Array.isArray(R.values)?R.values.indexOf(ui(_))===-1&&j.push(new Ot(p,_,"expected one of ["+R.values.join(", ")+"], "+JSON.stringify(_)+" found")):Object.keys(R.values).indexOf(ui(_))===-1&&j.push(new Ot(p,_,"expected one of ["+Object.keys(R.values).join(", ")+"], "+JSON.stringify(_)+" found")),j}function _f(u){if(u===!0||u===!1)return!0;if(!Array.isArray(u)||u.length===0)return!1;switch(u[0]){case"has":return u.length>=2&&u[1]!=="$id"&&u[1]!=="$type";case"in":return u.length>=3&&(typeof u[1]!="string"||Array.isArray(u[2]));case"!in":case"!has":case"none":return!1;case"==":case"!=":case">":case">=":case"<":case"<=":return u.length!==3||Array.isArray(u[1])||Array.isArray(u[2]);case"any":case"all":for(var p=0,_=u.slice(1);p<_.length;p+=1){var R=_[p];if(!_f(R)&&typeof R!="boolean")return!1}return!0;default:return!0}}_h.deserialize=function(u){return new _h(u._parameters,u._specification)},_h.serialize=function(u){return{_parameters:u._parameters,_specification:u._specification}};var Zh={type:"boolean",default:!1,transition:!1,"property-type":"data-driven",expression:{interpolated:!1,parameters:["zoom","feature"]}};function Wd(u){if(u==null)return{filter:function(){return!0},needGeometry:!1};_f(u)||(u=Je(u));var p=mc(u,Zh);if(p.result==="error")throw new Error(p.value.map(function(_){return _.key+": "+_.message}).join(", "));return{filter:function(_,R,j){return p.value.evaluate(_,R,{},j)},needGeometry:function _(R){if(!Array.isArray(R))return!1;if(R[0]==="within")return!0;for(var j=1;jp?1:0}function Je(u){if(!u)return!0;var p,_=u[0];return u.length<=1?_!=="any":_==="=="?yf(u[1],u[2],"=="):_==="!="?qf(yf(u[1],u[2],"==")):_==="<"||_===">"||_==="<="||_===">="?yf(u[1],u[2],_):_==="any"?(p=u.slice(1),["any"].concat(p.map(Je))):_==="all"?["all"].concat(u.slice(1).map(Je)):_==="none"?["all"].concat(u.slice(1).map(Je).map(qf)):_==="in"?Bu(u[1],u.slice(2)):_==="!in"?qf(Bu(u[1],u.slice(2))):_==="has"?Wf(u[1]):_==="!has"?qf(Wf(u[1])):_!=="within"||u}function yf(u,p,_){switch(u){case"$type":return["filter-type-"+_,p];case"$id":return["filter-id-"+_,p];default:return["filter-"+_,u,p]}}function Bu(u,p){if(p.length===0)return!1;switch(u){case"$type":return["filter-type-in",["literal",p]];case"$id":return["filter-id-in",["literal",p]];default:return p.length>200&&!p.some(function(_){return typeof _!=typeof p[0]})?["filter-in-large",u,["literal",p.sort(Ad)]]:["filter-in-small",u,["literal",p]]}}function Wf(u){switch(u){case"$type":return!0;case"$id":return["filter-has-id"];default:return["filter-has",u]}}function qf(u){return["!",u]}function Bt(u){return _f(Gr(u.value))?Fn(ei({},u,{expressionContext:"filter",valueSpec:{value:"boolean"}})):function p(_){var R=_.value,j=_.key;if(ao(R)!=="array")return[new Ot(j,R,"array expected, "+ao(R)+" found")];var $,J=_.styleSpec,le=[];if(R.length<1)return[new Ot(j,R,"filter array must have at least 1 element")];switch(le=le.concat(Xh({key:j+"[0]",value:R[0],valueSpec:J.filter_operator,style:_.style,styleSpec:_.styleSpec})),ui(R[0])){case"<":case"<=":case">":case">=":R.length>=2&&ui(R[1])==="$type"&&le.push(new Ot(j,R,'"$type" cannot be use with operator "'+R[0]+'"'));case"==":case"!=":R.length!==3&&le.push(new Ot(j,R,'filter array for operator "'+R[0]+'" must have 3 elements'));case"in":case"!in":R.length>=2&&($=ao(R[1]))!=="string"&&le.push(new Ot(j+"[1]",R[1],"string expected, "+$+" found"));for(var ye=2;ye=Le[lt+0]&&R>=Le[lt+1])?(J[Ye]=!0,$.push(Me[Ye])):J[Ye]=!1}}},Sl.prototype._forEachCell=function(u,p,_,R,j,$,J,le){for(var ye=this._convertToCellCoord(u),Me=this._convertToCellCoord(p),Le=this._convertToCellCoord(_),ze=this._convertToCellCoord(R),Ye=ye;Ye<=Le;Ye++)for(var lt=Me;lt<=ze;lt++){var It=this.d*lt+Ye;if((!le||le(this._convertFromCellCoord(Ye),this._convertFromCellCoord(lt),this._convertFromCellCoord(Ye+1),this._convertFromCellCoord(lt+1)))&&j.call(this,u,p,_,R,It,$,J,le))return}},Sl.prototype._convertFromCellCoord=function(u){return(u-this.padding)/this.scale},Sl.prototype._convertToCellCoord=function(u){return Math.max(0,Math.min(this.d-1,Math.floor(u*this.scale)+this.padding))},Sl.prototype.toArrayBuffer=function(){if(this.arrayBuffer)return this.arrayBuffer;for(var u=this.cells,p=3+this.cells.length+1+1,_=0,R=0;R=0)){var Le=u[Me];ye[Me]=Pa[le].shallow.indexOf(Me)>=0?Le:ru(Le,p)}u instanceof Error&&(ye.message=u.message)}if(ye.$name)throw new Error("$name property is reserved for worker serialization logic.");return le!=="Object"&&(ye.$name=le),ye}throw new Error("can't serialize object of type "+typeof u)}function kl(u){if(u==null||typeof u=="boolean"||typeof u=="number"||typeof u=="string"||u instanceof Boolean||u instanceof Number||u instanceof String||u instanceof Date||u instanceof RegExp||qd(u)||yd(u)||ArrayBuffer.isView(u)||u instanceof _d)return u;if(Array.isArray(u))return u.map(kl);if(typeof u=="object"){var p=u.$name||"Object",_=Pa[p].klass;if(!_)throw new Error("can't deserialize unregistered class "+p);if(_.deserialize)return _.deserialize(u);for(var R=Object.create(_.prototype),j=0,$=Object.keys(u);j<$.length;j+=1){var J=$[j];if(J!=="$name"){var le=u[J];R[J]=Pa[p].shallow.indexOf(J)>=0?le:kl(le)}}return R}throw new Error("can't deserialize object of type "+typeof u)}var Mp=function(){this.first=!0};Mp.prototype.update=function(u,p){var _=Math.floor(u);return this.first?(this.first=!1,this.lastIntegerZoom=_,this.lastIntegerZoomTime=0,this.lastZoom=u,this.lastFloorZoom=_,!0):(this.lastFloorZoom>_?(this.lastIntegerZoom=_+1,this.lastIntegerZoomTime=p):this.lastFloorZoom<_&&(this.lastIntegerZoom=_,this.lastIntegerZoomTime=p),u!==this.lastZoom&&(this.lastZoom=u,this.lastFloorZoom=_,!0))};var ki={"Latin-1 Supplement":function(u){return u>=128&&u<=255},Arabic:function(u){return u>=1536&&u<=1791},"Arabic Supplement":function(u){return u>=1872&&u<=1919},"Arabic Extended-A":function(u){return u>=2208&&u<=2303},"Hangul Jamo":function(u){return u>=4352&&u<=4607},"Unified Canadian Aboriginal Syllabics":function(u){return u>=5120&&u<=5759},Khmer:function(u){return u>=6016&&u<=6143},"Unified Canadian Aboriginal Syllabics Extended":function(u){return u>=6320&&u<=6399},"General Punctuation":function(u){return u>=8192&&u<=8303},"Letterlike Symbols":function(u){return u>=8448&&u<=8527},"Number Forms":function(u){return u>=8528&&u<=8591},"Miscellaneous Technical":function(u){return u>=8960&&u<=9215},"Control Pictures":function(u){return u>=9216&&u<=9279},"Optical Character Recognition":function(u){return u>=9280&&u<=9311},"Enclosed Alphanumerics":function(u){return u>=9312&&u<=9471},"Geometric Shapes":function(u){return u>=9632&&u<=9727},"Miscellaneous Symbols":function(u){return u>=9728&&u<=9983},"Miscellaneous Symbols and Arrows":function(u){return u>=11008&&u<=11263},"CJK Radicals Supplement":function(u){return u>=11904&&u<=12031},"Kangxi Radicals":function(u){return u>=12032&&u<=12255},"Ideographic Description Characters":function(u){return u>=12272&&u<=12287},"CJK Symbols and Punctuation":function(u){return u>=12288&&u<=12351},Hiragana:function(u){return u>=12352&&u<=12447},Katakana:function(u){return u>=12448&&u<=12543},Bopomofo:function(u){return u>=12544&&u<=12591},"Hangul Compatibility Jamo":function(u){return u>=12592&&u<=12687},Kanbun:function(u){return u>=12688&&u<=12703},"Bopomofo Extended":function(u){return u>=12704&&u<=12735},"CJK Strokes":function(u){return u>=12736&&u<=12783},"Katakana Phonetic Extensions":function(u){return u>=12784&&u<=12799},"Enclosed CJK Letters and Months":function(u){return u>=12800&&u<=13055},"CJK Compatibility":function(u){return u>=13056&&u<=13311},"CJK Unified Ideographs Extension A":function(u){return u>=13312&&u<=19903},"Yijing Hexagram Symbols":function(u){return u>=19904&&u<=19967},"CJK Unified Ideographs":function(u){return u>=19968&&u<=40959},"Yi Syllables":function(u){return u>=40960&&u<=42127},"Yi Radicals":function(u){return u>=42128&&u<=42191},"Hangul Jamo Extended-A":function(u){return u>=43360&&u<=43391},"Hangul Syllables":function(u){return u>=44032&&u<=55215},"Hangul Jamo Extended-B":function(u){return u>=55216&&u<=55295},"Private Use Area":function(u){return u>=57344&&u<=63743},"CJK Compatibility Ideographs":function(u){return u>=63744&&u<=64255},"Arabic Presentation Forms-A":function(u){return u>=64336&&u<=65023},"Vertical Forms":function(u){return u>=65040&&u<=65055},"CJK Compatibility Forms":function(u){return u>=65072&&u<=65103},"Small Form Variants":function(u){return u>=65104&&u<=65135},"Arabic Presentation Forms-B":function(u){return u>=65136&&u<=65279},"Halfwidth and Fullwidth Forms":function(u){return u>=65280&&u<=65519}};function Zo(u){for(var p=0,_=u;p<_.length;p+=1)if(vd(_[p].charCodeAt(0)))return!0;return!1}function vd(u){return!(u!==746&&u!==747&&(u<4352||!(ki["Bopomofo Extended"](u)||ki.Bopomofo(u)||ki["CJK Compatibility Forms"](u)&&!(u>=65097&&u<=65103)||ki["CJK Compatibility Ideographs"](u)||ki["CJK Compatibility"](u)||ki["CJK Radicals Supplement"](u)||ki["CJK Strokes"](u)||!(!ki["CJK Symbols and Punctuation"](u)||u>=12296&&u<=12305||u>=12308&&u<=12319||u===12336)||ki["CJK Unified Ideographs Extension A"](u)||ki["CJK Unified Ideographs"](u)||ki["Enclosed CJK Letters and Months"](u)||ki["Hangul Compatibility Jamo"](u)||ki["Hangul Jamo Extended-A"](u)||ki["Hangul Jamo Extended-B"](u)||ki["Hangul Jamo"](u)||ki["Hangul Syllables"](u)||ki.Hiragana(u)||ki["Ideographic Description Characters"](u)||ki.Kanbun(u)||ki["Kangxi Radicals"](u)||ki["Katakana Phonetic Extensions"](u)||ki.Katakana(u)&&u!==12540||!(!ki["Halfwidth and Fullwidth Forms"](u)||u===65288||u===65289||u===65293||u>=65306&&u<=65310||u===65339||u===65341||u===65343||u>=65371&&u<=65503||u===65507||u>=65512&&u<=65519)||!(!ki["Small Form Variants"](u)||u>=65112&&u<=65118||u>=65123&&u<=65126)||ki["Unified Canadian Aboriginal Syllabics"](u)||ki["Unified Canadian Aboriginal Syllabics Extended"](u)||ki["Vertical Forms"](u)||ki["Yijing Hexagram Symbols"](u)||ki["Yi Syllables"](u)||ki["Yi Radicals"](u))))}function Jm(u){return!(vd(u)||function(p){return!!(ki["Latin-1 Supplement"](p)&&(p===167||p===169||p===174||p===177||p===188||p===189||p===190||p===215||p===247)||ki["General Punctuation"](p)&&(p===8214||p===8224||p===8225||p===8240||p===8241||p===8251||p===8252||p===8258||p===8263||p===8264||p===8265||p===8273)||ki["Letterlike Symbols"](p)||ki["Number Forms"](p)||ki["Miscellaneous Technical"](p)&&(p>=8960&&p<=8967||p>=8972&&p<=8991||p>=8996&&p<=9e3||p===9003||p>=9085&&p<=9114||p>=9150&&p<=9165||p===9167||p>=9169&&p<=9179||p>=9186&&p<=9215)||ki["Control Pictures"](p)&&p!==9251||ki["Optical Character Recognition"](p)||ki["Enclosed Alphanumerics"](p)||ki["Geometric Shapes"](p)||ki["Miscellaneous Symbols"](p)&&!(p>=9754&&p<=9759)||ki["Miscellaneous Symbols and Arrows"](p)&&(p>=11026&&p<=11055||p>=11088&&p<=11097||p>=11192&&p<=11243)||ki["CJK Symbols and Punctuation"](p)||ki.Katakana(p)||ki["Private Use Area"](p)||ki["CJK Compatibility Forms"](p)||ki["Small Form Variants"](p)||ki["Halfwidth and Fullwidth Forms"](p)||p===8734||p===8756||p===8757||p>=9984&&p<=10087||p>=10102&&p<=10131||p===65532||p===65533)}(u))}function Zu(u){return u>=1424&&u<=2303||ki["Arabic Presentation Forms-A"](u)||ki["Arabic Presentation Forms-B"](u)}function Mc(u,p){return!(!p&&Zu(u)||u>=2304&&u<=3583||u>=3840&&u<=4255||ki.Khmer(u))}function eg(u){for(var p=0,_=u;p<_.length;p+=1)if(Zu(_[p].charCodeAt(0)))return!0;return!1}var tg=null,ua="unavailable",mu=null,Ic=function(u){u&&typeof u=="string"&&u.indexOf("NetworkError")>-1&&(ua="error"),tg&&tg(u)};function yh(){$d.fire(new Ke("pluginStateChange",{pluginStatus:ua,pluginURL:mu}))}var $d=new Jt,$f=function(){return ua},Yu=function(){if(ua!=="deferred"||!mu)throw new Error("rtl-text-plugin cannot be downloaded unless a pluginURL is specified");ua="loading",yh(),mu&&Us({url:mu},function(u){u?Ic(u):(ua="loaded",yh())})},gu={applyArabicShaping:null,processBidirectionalText:null,processStyledBidirectionalText:null,isLoaded:function(){return ua==="loaded"||gu.applyArabicShaping!=null},isLoading:function(){return ua==="loading"},setState:function(u){ua=u.pluginStatus,mu=u.pluginURL},isParsed:function(){return gu.applyArabicShaping!=null&&gu.processBidirectionalText!=null&&gu.processStyledBidirectionalText!=null},getPluginURL:function(){return mu}},So=function(u,p){this.zoom=u,p?(this.now=p.now,this.fadeDuration=p.fadeDuration,this.zoomHistory=p.zoomHistory,this.transition=p.transition):(this.now=0,this.fadeDuration=0,this.zoomHistory=new Mp,this.transition={})};So.prototype.isSupportedScript=function(u){return function(p,_){for(var R=0,j=p;Rthis.zoomHistory.lastIntegerZoom?{fromScale:2,toScale:1,t:p+(1-p)*_}:{fromScale:.5,toScale:1,t:1-(1-_)*p}};var Lu=function(u,p){this.property=u,this.value=p,this.expression=function(_,R){if(Ps(_))return new _h(_,R);if(Ah(_)){var j=gc(_,R);if(j.result==="error")throw new Error(j.value.map(function(J){return J.key+": "+J.message}).join(", "));return j.value}var $=_;return typeof _=="string"&&R.type==="color"&&($=cs.parse(_)),{kind:"constant",evaluate:function(){return $}}}(p===void 0?u.specification.default:p,u.specification)};Lu.prototype.isDataDriven=function(){return this.expression.kind==="source"||this.expression.kind==="composite"},Lu.prototype.possiblyEvaluate=function(u,p,_){return this.property.possiblyEvaluate(this,u,p,_)};var Nl=function(u){this.property=u,this.value=new Lu(u,void 0)};Nl.prototype.transitioned=function(u,p){return new xf(this.property,this.value,p,ee({},u.transition,this.transition),u.now)},Nl.prototype.untransitioned=function(){return new xf(this.property,this.value,null,{},0)};var ha=function(u){this._properties=u,this._values=Object.create(u.defaultTransitionablePropertyValues)};ha.prototype.getValue=function(u){return Se(this._values[u].value.value)},ha.prototype.setValue=function(u,p){this._values.hasOwnProperty(u)||(this._values[u]=new Nl(this._values[u].property)),this._values[u].value=new Lu(this._values[u].property,p===null?void 0:Se(p))},ha.prototype.getTransition=function(u){return Se(this._values[u].transition)},ha.prototype.setTransition=function(u,p){this._values.hasOwnProperty(u)||(this._values[u]=new Nl(this._values[u].property)),this._values[u].transition=Se(p)||void 0},ha.prototype.serialize=function(){for(var u={},p=0,_=Object.keys(this._values);p<_.length;p+=1){var R=_[p],j=this.getValue(R);j!==void 0&&(u[R]=j);var $=this.getTransition(R);$!==void 0&&(u[R+"-transition"]=$)}return u},ha.prototype.transitioned=function(u,p){for(var _=new Xf(this._properties),R=0,j=Object.keys(this._values);Rthis.end)return this.prior=null,j;if(this.value.isDataDriven())return this.prior=null,j;if(R=1)return 1;var ye=le*le,Me=ye*le;return 4*(le<.5?Me:3*(le-ye)+Me-.75)}(J))}return j};var Xf=function(u){this._properties=u,this._values=Object.create(u.defaultTransitioningPropertyValues)};Xf.prototype.possiblyEvaluate=function(u,p,_){for(var R=new Ku(this._properties),j=0,$=Object.keys(this._values);j<$.length;j+=1){var J=$[j];R._values[J]=this._values[J].possiblyEvaluate(u,p,_)}return R},Xf.prototype.hasTransition=function(){for(var u=0,p=Object.keys(this._values);u$.zoomHistory.lastIntegerZoom?{from:_,to:R}:{from:j,to:R}},p.prototype.interpolate=function(_){return _},p}(nn),Vc=function(u){this.specification=u};Vc.prototype.possiblyEvaluate=function(u,p,_,R){if(u.value!==void 0){if(u.expression.kind==="constant"){var j=u.expression.evaluate(p,null,{},_,R);return this._calculate(j,j,j,p)}return this._calculate(u.expression.evaluate(new So(Math.floor(p.zoom-1),p)),u.expression.evaluate(new So(Math.floor(p.zoom),p)),u.expression.evaluate(new So(Math.floor(p.zoom+1),p)),p)}},Vc.prototype._calculate=function(u,p,_,R){return R.zoom>R.zoomHistory.lastIntegerZoom?{from:u,to:p}:{from:_,to:p}},Vc.prototype.interpolate=function(u){return u};var Au=function(u){this.specification=u};Au.prototype.possiblyEvaluate=function(u,p,_,R){return!!u.expression.evaluate(p,null,{},_,R)},Au.prototype.interpolate=function(){return!1};var Cl=function(u){for(var p in this.properties=u,this.defaultPropertyValues={},this.defaultTransitionablePropertyValues={},this.defaultTransitioningPropertyValues={},this.defaultPossiblyEvaluatedValues={},this.overridableProperties=[],u){var _=u[p];_.specification.overridable&&this.overridableProperties.push(p);var R=this.defaultPropertyValues[p]=new Lu(_,void 0),j=this.defaultTransitionablePropertyValues[p]=new Nl(_);this.defaultTransitioningPropertyValues[p]=j.untransitioned(),this.defaultPossiblyEvaluatedValues[p]=R.possiblyEvaluate({})}};Yi("DataDrivenProperty",nn),Yi("DataConstantProperty",An),Yi("CrossFadedDataDrivenProperty",Xd),Yi("CrossFadedProperty",Vc),Yi("ColorRampProperty",Au);var Yh=function(u){function p(_,R){if(u.call(this),this.id=_.id,this.type=_.type,this._featureFilter={filter:function(){return!0},needGeometry:!1},_.type!=="custom"&&(this.metadata=(_=_).metadata,this.minzoom=_.minzoom,this.maxzoom=_.maxzoom,_.type!=="background"&&(this.source=_.source,this.sourceLayer=_["source-layer"],this.filter=_.filter),R.layout&&(this._unevaluatedLayout=new Pc(R.layout)),R.paint)){for(var j in this._transitionablePaint=new ha(R.paint),_.paint)this.setPaintProperty(j,_.paint[j],{validate:!1});for(var $ in _.layout)this.setLayoutProperty($,_.layout[$],{validate:!1});this._transitioningPaint=this._transitionablePaint.untransitioned(),this.paint=new Ku(R.paint)}}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.getCrossfadeParameters=function(){return this._crossfadeParameters},p.prototype.getLayoutProperty=function(_){return _==="visibility"?this.visibility:this._unevaluatedLayout.getValue(_)},p.prototype.setLayoutProperty=function(_,R,j){j===void 0&&(j={}),R!=null&&this._validate(Tl,"layers."+this.id+".layout."+_,_,R,j)||(_!=="visibility"?this._unevaluatedLayout.setValue(_,R):this.visibility=R)},p.prototype.getPaintProperty=function(_){return ne(_,"-transition")?this._transitionablePaint.getTransition(_.slice(0,-11)):this._transitionablePaint.getValue(_)},p.prototype.setPaintProperty=function(_,R,j){if(j===void 0&&(j={}),R!=null&&this._validate(tc,"layers."+this.id+".paint."+_,_,R,j))return!1;if(ne(_,"-transition"))return this._transitionablePaint.setTransition(_.slice(0,-11),R||void 0),!1;var $=this._transitionablePaint._values[_],J=$.property.specification["property-type"]==="cross-faded-data-driven",le=$.value.isDataDriven(),ye=$.value;this._transitionablePaint.setValue(_,R),this._handleSpecialPaintPropertyUpdate(_);var Me=this._transitionablePaint._values[_].value;return Me.isDataDriven()||le||J||this._handleOverridablePaintPropertyUpdate(_,ye,Me)},p.prototype._handleSpecialPaintPropertyUpdate=function(_){},p.prototype._handleOverridablePaintPropertyUpdate=function(_,R,j){return!1},p.prototype.isHidden=function(_){return!!(this.minzoom&&_=this.maxzoom)||this.visibility==="none"},p.prototype.updateTransitions=function(_){this._transitioningPaint=this._transitionablePaint.transitioned(_,this._transitioningPaint)},p.prototype.hasTransition=function(){return this._transitioningPaint.hasTransition()},p.prototype.recalculate=function(_,R){_.getCrossfadeParameters&&(this._crossfadeParameters=_.getCrossfadeParameters()),this._unevaluatedLayout&&(this.layout=this._unevaluatedLayout.possiblyEvaluate(_,void 0,R)),this.paint=this._transitioningPaint.possiblyEvaluate(_,void 0,R)},p.prototype.serialize=function(){var _={id:this.id,type:this.type,source:this.source,"source-layer":this.sourceLayer,metadata:this.metadata,minzoom:this.minzoom,maxzoom:this.maxzoom,filter:this.filter,layout:this._unevaluatedLayout&&this._unevaluatedLayout.serialize(),paint:this._transitionablePaint&&this._transitionablePaint.serialize()};return this.visibility&&(_.layout=_.layout||{},_.layout.visibility=this.visibility),We(_,function(R,j){return!(R===void 0||j==="layout"&&!Object.keys(R).length||j==="paint"&&!Object.keys(R).length)})},p.prototype._validate=function(_,R,j,$,J){return J===void 0&&(J={}),(!J||J.validate!==!1)&&ll(this,_.call(wl,{key:R,layerType:this.type,objectKey:j,value:$,styleSpec:ht,style:{glyphs:!0,sprite:!0}}))},p.prototype.is3D=function(){return!1},p.prototype.isTileClipped=function(){return!1},p.prototype.hasOffscreenPass=function(){return!1},p.prototype.resize=function(){},p.prototype.isStateDependent=function(){for(var _ in this.paint._values){var R=this.paint.get(_);if(R instanceof El&&_o(R.property.specification)&&(R.value.kind==="source"||R.value.kind==="composite")&&R.value.isStateDependent)return!0}return!1},p}(Jt),rg={Int8:Int8Array,Uint8:Uint8Array,Int16:Int16Array,Uint16:Uint16Array,Int32:Int32Array,Uint32:Uint32Array,Float32:Float32Array},ja=function(u,p){this._structArray=u,this._pos1=p*this.size,this._pos2=this._pos1/2,this._pos4=this._pos1/4,this._pos8=this._pos1/8},As=function(){this.isTransferred=!1,this.capacity=-1,this.resize(0)};function fa(u,p){p===void 0&&(p=1);var _=0,R=0;return{members:u.map(function(j){var $=rg[j.type].BYTES_PER_ELEMENT,J=_=Ip(_,Math.max(p,$)),le=j.components||1;return R=Math.max(R,$),_+=$*le,{name:j.name,type:j.type,components:le,offset:J}}),size:Ip(_,Math.max(R,p)),alignment:p}}function Ip(u,p){return Math.ceil(u/p)*p}As.serialize=function(u,p){return u._trim(),p&&(u.isTransferred=!0,p.push(u.arrayBuffer)),{length:u.length,arrayBuffer:u.arrayBuffer}},As.deserialize=function(u){var p=Object.create(this.prototype);return p.arrayBuffer=u.arrayBuffer,p.length=u.length,p.capacity=u.arrayBuffer.byteLength/p.bytesPerElement,p._refreshViews(),p},As.prototype._trim=function(){this.length!==this.capacity&&(this.capacity=this.length,this.arrayBuffer=this.arrayBuffer.slice(0,this.length*this.bytesPerElement),this._refreshViews())},As.prototype.clear=function(){this.length=0},As.prototype.resize=function(u){this.reserve(u),this.length=u},As.prototype.reserve=function(u){if(u>this.capacity){this.capacity=Math.max(u,Math.floor(5*this.capacity),128),this.arrayBuffer=new ArrayBuffer(this.capacity*this.bytesPerElement);var p=this.uint8;this._refreshViews(),p&&this.uint8.set(p)}},As.prototype._refreshViews=function(){throw new Error("_refreshViews() must be implemented by each concrete StructArray layout")};var Kh=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R){var j=this.length;return this.resize(j+1),this.emplace(j,_,R)},p.prototype.emplace=function(_,R,j){var $=2*_;return this.int16[$+0]=R,this.int16[$+1]=j,_},p}(As);Kh.prototype.bytesPerElement=4,Yi("StructArrayLayout2i4",Kh);var Pp=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$){var J=this.length;return this.resize(J+1),this.emplace(J,_,R,j,$)},p.prototype.emplace=function(_,R,j,$,J){var le=4*_;return this.int16[le+0]=R,this.int16[le+1]=j,this.int16[le+2]=$,this.int16[le+3]=J,_},p}(As);Pp.prototype.bytesPerElement=8,Yi("StructArrayLayout4i8",Pp);var _c=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$,J,le){var ye=this.length;return this.resize(ye+1),this.emplace(ye,_,R,j,$,J,le)},p.prototype.emplace=function(_,R,j,$,J,le,ye){var Me=6*_;return this.int16[Me+0]=R,this.int16[Me+1]=j,this.int16[Me+2]=$,this.int16[Me+3]=J,this.int16[Me+4]=le,this.int16[Me+5]=ye,_},p}(As);_c.prototype.bytesPerElement=12,Yi("StructArrayLayout2i4i12",_c);var Rc=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$,J,le){var ye=this.length;return this.resize(ye+1),this.emplace(ye,_,R,j,$,J,le)},p.prototype.emplace=function(_,R,j,$,J,le,ye){var Me=4*_,Le=8*_;return this.int16[Me+0]=R,this.int16[Me+1]=j,this.uint8[Le+4]=$,this.uint8[Le+5]=J,this.uint8[Le+6]=le,this.uint8[Le+7]=ye,_},p}(As);Rc.prototype.bytesPerElement=8,Yi("StructArrayLayout2i4ub8",Rc);var vh=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R){var j=this.length;return this.resize(j+1),this.emplace(j,_,R)},p.prototype.emplace=function(_,R,j){var $=2*_;return this.float32[$+0]=R,this.float32[$+1]=j,_},p}(As);vh.prototype.bytesPerElement=8,Yi("StructArrayLayout2f8",vh);var _u=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$,J,le,ye,Me,Le,ze){var Ye=this.length;return this.resize(Ye+1),this.emplace(Ye,_,R,j,$,J,le,ye,Me,Le,ze)},p.prototype.emplace=function(_,R,j,$,J,le,ye,Me,Le,ze,Ye){var lt=10*_;return this.uint16[lt+0]=R,this.uint16[lt+1]=j,this.uint16[lt+2]=$,this.uint16[lt+3]=J,this.uint16[lt+4]=le,this.uint16[lt+5]=ye,this.uint16[lt+6]=Me,this.uint16[lt+7]=Le,this.uint16[lt+8]=ze,this.uint16[lt+9]=Ye,_},p}(As);_u.prototype.bytesPerElement=20,Yi("StructArrayLayout10ui20",_u);var Zf=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt){var It=this.length;return this.resize(It+1),this.emplace(It,_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt)},p.prototype.emplace=function(_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt,It){var Dt=12*_;return this.int16[Dt+0]=R,this.int16[Dt+1]=j,this.int16[Dt+2]=$,this.int16[Dt+3]=J,this.uint16[Dt+4]=le,this.uint16[Dt+5]=ye,this.uint16[Dt+6]=Me,this.uint16[Dt+7]=Le,this.int16[Dt+8]=ze,this.int16[Dt+9]=Ye,this.int16[Dt+10]=lt,this.int16[Dt+11]=It,_},p}(As);Zf.prototype.bytesPerElement=24,Yi("StructArrayLayout4i4ui4i24",Zf);var Zd=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j){var $=this.length;return this.resize($+1),this.emplace($,_,R,j)},p.prototype.emplace=function(_,R,j,$){var J=3*_;return this.float32[J+0]=R,this.float32[J+1]=j,this.float32[J+2]=$,_},p}(As);Zd.prototype.bytesPerElement=12,Yi("StructArrayLayout3f12",Zd);var xd=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_){var R=this.length;return this.resize(R+1),this.emplace(R,_)},p.prototype.emplace=function(_,R){return this.uint32[1*_+0]=R,_},p}(As);xd.prototype.bytesPerElement=4,Yi("StructArrayLayout1ul4",xd);var mm=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$,J,le,ye,Me,Le){var ze=this.length;return this.resize(ze+1),this.emplace(ze,_,R,j,$,J,le,ye,Me,Le)},p.prototype.emplace=function(_,R,j,$,J,le,ye,Me,Le,ze){var Ye=10*_,lt=5*_;return this.int16[Ye+0]=R,this.int16[Ye+1]=j,this.int16[Ye+2]=$,this.int16[Ye+3]=J,this.int16[Ye+4]=le,this.int16[Ye+5]=ye,this.uint32[lt+3]=Me,this.uint16[Ye+8]=Le,this.uint16[Ye+9]=ze,_},p}(As);mm.prototype.bytesPerElement=20,Yi("StructArrayLayout6i1ul2ui20",mm);var xh=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$,J,le){var ye=this.length;return this.resize(ye+1),this.emplace(ye,_,R,j,$,J,le)},p.prototype.emplace=function(_,R,j,$,J,le,ye){var Me=6*_;return this.int16[Me+0]=R,this.int16[Me+1]=j,this.int16[Me+2]=$,this.int16[Me+3]=J,this.int16[Me+4]=le,this.int16[Me+5]=ye,_},p}(As);xh.prototype.bytesPerElement=12,Yi("StructArrayLayout2i2i2i12",xh);var Qu=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$,J){var le=this.length;return this.resize(le+1),this.emplace(le,_,R,j,$,J)},p.prototype.emplace=function(_,R,j,$,J,le){var ye=4*_,Me=8*_;return this.float32[ye+0]=R,this.float32[ye+1]=j,this.float32[ye+2]=$,this.int16[Me+6]=J,this.int16[Me+7]=le,_},p}(As);Qu.prototype.bytesPerElement=16,Yi("StructArrayLayout2f1f2i16",Qu);var Yd=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$){var J=this.length;return this.resize(J+1),this.emplace(J,_,R,j,$)},p.prototype.emplace=function(_,R,j,$,J){var le=12*_,ye=3*_;return this.uint8[le+0]=R,this.uint8[le+1]=j,this.float32[ye+1]=$,this.float32[ye+2]=J,_},p}(As);Yd.prototype.bytesPerElement=12,Yi("StructArrayLayout2ub2f12",Yd);var Qh=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j){var $=this.length;return this.resize($+1),this.emplace($,_,R,j)},p.prototype.emplace=function(_,R,j,$){var J=3*_;return this.uint16[J+0]=R,this.uint16[J+1]=j,this.uint16[J+2]=$,_},p}(As);Qh.prototype.bytesPerElement=6,Yi("StructArrayLayout3ui6",Qh);var bh=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt,It,Dt,tr,qt,ur){var Tr=this.length;return this.resize(Tr+1),this.emplace(Tr,_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt,It,Dt,tr,qt,ur)},p.prototype.emplace=function(_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt,It,Dt,tr,qt,ur,Tr){var Nr=24*_,Vr=12*_,ri=48*_;return this.int16[Nr+0]=R,this.int16[Nr+1]=j,this.uint16[Nr+2]=$,this.uint16[Nr+3]=J,this.uint32[Vr+2]=le,this.uint32[Vr+3]=ye,this.uint32[Vr+4]=Me,this.uint16[Nr+10]=Le,this.uint16[Nr+11]=ze,this.uint16[Nr+12]=Ye,this.float32[Vr+7]=lt,this.float32[Vr+8]=It,this.uint8[ri+36]=Dt,this.uint8[ri+37]=tr,this.uint8[ri+38]=qt,this.uint32[Vr+10]=ur,this.int16[Nr+22]=Tr,_},p}(As);bh.prototype.bytesPerElement=48,Yi("StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48",bh);var Rp=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt,It,Dt,tr,qt,ur,Tr,Nr,Vr,ri,Ei,Ui,Cn,Ki,Bn,Ji,Wo){var Xn=this.length;return this.resize(Xn+1),this.emplace(Xn,_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt,It,Dt,tr,qt,ur,Tr,Nr,Vr,ri,Ei,Ui,Cn,Ki,Bn,Ji,Wo)},p.prototype.emplace=function(_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt,It,Dt,tr,qt,ur,Tr,Nr,Vr,ri,Ei,Ui,Cn,Ki,Bn,Ji,Wo,Xn){var pn=34*_,Jo=17*_;return this.int16[pn+0]=R,this.int16[pn+1]=j,this.int16[pn+2]=$,this.int16[pn+3]=J,this.int16[pn+4]=le,this.int16[pn+5]=ye,this.int16[pn+6]=Me,this.int16[pn+7]=Le,this.uint16[pn+8]=ze,this.uint16[pn+9]=Ye,this.uint16[pn+10]=lt,this.uint16[pn+11]=It,this.uint16[pn+12]=Dt,this.uint16[pn+13]=tr,this.uint16[pn+14]=qt,this.uint16[pn+15]=ur,this.uint16[pn+16]=Tr,this.uint16[pn+17]=Nr,this.uint16[pn+18]=Vr,this.uint16[pn+19]=ri,this.uint16[pn+20]=Ei,this.uint16[pn+21]=Ui,this.uint16[pn+22]=Cn,this.uint32[Jo+12]=Ki,this.float32[Jo+13]=Bn,this.float32[Jo+14]=Ji,this.float32[Jo+15]=Wo,this.float32[Jo+16]=Xn,_},p}(As);Rp.prototype.bytesPerElement=68,Yi("StructArrayLayout8i15ui1ul4f68",Rp);var bf=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_){var R=this.length;return this.resize(R+1),this.emplace(R,_)},p.prototype.emplace=function(_,R){return this.float32[1*_+0]=R,_},p}(As);bf.prototype.bytesPerElement=4,Yi("StructArrayLayout1f4",bf);var Du=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j){var $=this.length;return this.resize($+1),this.emplace($,_,R,j)},p.prototype.emplace=function(_,R,j,$){var J=3*_;return this.int16[J+0]=R,this.int16[J+1]=j,this.int16[J+2]=$,_},p}(As);Du.prototype.bytesPerElement=6,Yi("StructArrayLayout3i6",Du);var Bp=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j){var $=this.length;return this.resize($+1),this.emplace($,_,R,j)},p.prototype.emplace=function(_,R,j,$){var J=4*_;return this.uint32[2*_+0]=R,this.uint16[J+2]=j,this.uint16[J+3]=$,_},p}(As);Bp.prototype.bytesPerElement=8,Yi("StructArrayLayout1ul2ui8",Bp);var Kd=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R){var j=this.length;return this.resize(j+1),this.emplace(j,_,R)},p.prototype.emplace=function(_,R,j){var $=2*_;return this.uint16[$+0]=R,this.uint16[$+1]=j,_},p}(As);Kd.prototype.bytesPerElement=4,Yi("StructArrayLayout2ui4",Kd);var Yf=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_){var R=this.length;return this.resize(R+1),this.emplace(R,_)},p.prototype.emplace=function(_,R){return this.uint16[1*_+0]=R,_},p}(As);Yf.prototype.bytesPerElement=2,Yi("StructArrayLayout1ui2",Yf);var Jh=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._refreshViews=function(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)},p.prototype.emplaceBack=function(_,R,j,$){var J=this.length;return this.resize(J+1),this.emplace(J,_,R,j,$)},p.prototype.emplace=function(_,R,j,$,J){var le=4*_;return this.float32[le+0]=R,this.float32[le+1]=j,this.float32[le+2]=$,this.float32[le+3]=J,_},p}(As);Jh.prototype.bytesPerElement=16,Yi("StructArrayLayout4f16",Jh);var w=function(u){function p(){u.apply(this,arguments)}u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p;var _={anchorPointX:{configurable:!0},anchorPointY:{configurable:!0},x1:{configurable:!0},y1:{configurable:!0},x2:{configurable:!0},y2:{configurable:!0},featureIndex:{configurable:!0},sourceLayerIndex:{configurable:!0},bucketIndex:{configurable:!0},anchorPoint:{configurable:!0}};return _.anchorPointX.get=function(){return this._structArray.int16[this._pos2+0]},_.anchorPointY.get=function(){return this._structArray.int16[this._pos2+1]},_.x1.get=function(){return this._structArray.int16[this._pos2+2]},_.y1.get=function(){return this._structArray.int16[this._pos2+3]},_.x2.get=function(){return this._structArray.int16[this._pos2+4]},_.y2.get=function(){return this._structArray.int16[this._pos2+5]},_.featureIndex.get=function(){return this._structArray.uint32[this._pos4+3]},_.sourceLayerIndex.get=function(){return this._structArray.uint16[this._pos2+8]},_.bucketIndex.get=function(){return this._structArray.uint16[this._pos2+9]},_.anchorPoint.get=function(){return new f(this.anchorPointX,this.anchorPointY)},Object.defineProperties(p.prototype,_),p}(ja);w.prototype.size=20;var I=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.get=function(_){return new w(this,_)},p}(mm);Yi("CollisionBoxArray",I);var S=function(u){function p(){u.apply(this,arguments)}u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p;var _={anchorX:{configurable:!0},anchorY:{configurable:!0},glyphStartIndex:{configurable:!0},numGlyphs:{configurable:!0},vertexStartIndex:{configurable:!0},lineStartIndex:{configurable:!0},lineLength:{configurable:!0},segment:{configurable:!0},lowerSize:{configurable:!0},upperSize:{configurable:!0},lineOffsetX:{configurable:!0},lineOffsetY:{configurable:!0},writingMode:{configurable:!0},placedOrientation:{configurable:!0},hidden:{configurable:!0},crossTileID:{configurable:!0},associatedIconIndex:{configurable:!0}};return _.anchorX.get=function(){return this._structArray.int16[this._pos2+0]},_.anchorY.get=function(){return this._structArray.int16[this._pos2+1]},_.glyphStartIndex.get=function(){return this._structArray.uint16[this._pos2+2]},_.numGlyphs.get=function(){return this._structArray.uint16[this._pos2+3]},_.vertexStartIndex.get=function(){return this._structArray.uint32[this._pos4+2]},_.lineStartIndex.get=function(){return this._structArray.uint32[this._pos4+3]},_.lineLength.get=function(){return this._structArray.uint32[this._pos4+4]},_.segment.get=function(){return this._structArray.uint16[this._pos2+10]},_.lowerSize.get=function(){return this._structArray.uint16[this._pos2+11]},_.upperSize.get=function(){return this._structArray.uint16[this._pos2+12]},_.lineOffsetX.get=function(){return this._structArray.float32[this._pos4+7]},_.lineOffsetY.get=function(){return this._structArray.float32[this._pos4+8]},_.writingMode.get=function(){return this._structArray.uint8[this._pos1+36]},_.placedOrientation.get=function(){return this._structArray.uint8[this._pos1+37]},_.placedOrientation.set=function(R){this._structArray.uint8[this._pos1+37]=R},_.hidden.get=function(){return this._structArray.uint8[this._pos1+38]},_.hidden.set=function(R){this._structArray.uint8[this._pos1+38]=R},_.crossTileID.get=function(){return this._structArray.uint32[this._pos4+10]},_.crossTileID.set=function(R){this._structArray.uint32[this._pos4+10]=R},_.associatedIconIndex.get=function(){return this._structArray.int16[this._pos2+22]},Object.defineProperties(p.prototype,_),p}(ja);S.prototype.size=48;var L=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.get=function(_){return new S(this,_)},p}(bh);Yi("PlacedSymbolArray",L);var Y=function(u){function p(){u.apply(this,arguments)}u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p;var _={anchorX:{configurable:!0},anchorY:{configurable:!0},rightJustifiedTextSymbolIndex:{configurable:!0},centerJustifiedTextSymbolIndex:{configurable:!0},leftJustifiedTextSymbolIndex:{configurable:!0},verticalPlacedTextSymbolIndex:{configurable:!0},placedIconSymbolIndex:{configurable:!0},verticalPlacedIconSymbolIndex:{configurable:!0},key:{configurable:!0},textBoxStartIndex:{configurable:!0},textBoxEndIndex:{configurable:!0},verticalTextBoxStartIndex:{configurable:!0},verticalTextBoxEndIndex:{configurable:!0},iconBoxStartIndex:{configurable:!0},iconBoxEndIndex:{configurable:!0},verticalIconBoxStartIndex:{configurable:!0},verticalIconBoxEndIndex:{configurable:!0},featureIndex:{configurable:!0},numHorizontalGlyphVertices:{configurable:!0},numVerticalGlyphVertices:{configurable:!0},numIconVertices:{configurable:!0},numVerticalIconVertices:{configurable:!0},useRuntimeCollisionCircles:{configurable:!0},crossTileID:{configurable:!0},textBoxScale:{configurable:!0},textOffset0:{configurable:!0},textOffset1:{configurable:!0},collisionCircleDiameter:{configurable:!0}};return _.anchorX.get=function(){return this._structArray.int16[this._pos2+0]},_.anchorY.get=function(){return this._structArray.int16[this._pos2+1]},_.rightJustifiedTextSymbolIndex.get=function(){return this._structArray.int16[this._pos2+2]},_.centerJustifiedTextSymbolIndex.get=function(){return this._structArray.int16[this._pos2+3]},_.leftJustifiedTextSymbolIndex.get=function(){return this._structArray.int16[this._pos2+4]},_.verticalPlacedTextSymbolIndex.get=function(){return this._structArray.int16[this._pos2+5]},_.placedIconSymbolIndex.get=function(){return this._structArray.int16[this._pos2+6]},_.verticalPlacedIconSymbolIndex.get=function(){return this._structArray.int16[this._pos2+7]},_.key.get=function(){return this._structArray.uint16[this._pos2+8]},_.textBoxStartIndex.get=function(){return this._structArray.uint16[this._pos2+9]},_.textBoxEndIndex.get=function(){return this._structArray.uint16[this._pos2+10]},_.verticalTextBoxStartIndex.get=function(){return this._structArray.uint16[this._pos2+11]},_.verticalTextBoxEndIndex.get=function(){return this._structArray.uint16[this._pos2+12]},_.iconBoxStartIndex.get=function(){return this._structArray.uint16[this._pos2+13]},_.iconBoxEndIndex.get=function(){return this._structArray.uint16[this._pos2+14]},_.verticalIconBoxStartIndex.get=function(){return this._structArray.uint16[this._pos2+15]},_.verticalIconBoxEndIndex.get=function(){return this._structArray.uint16[this._pos2+16]},_.featureIndex.get=function(){return this._structArray.uint16[this._pos2+17]},_.numHorizontalGlyphVertices.get=function(){return this._structArray.uint16[this._pos2+18]},_.numVerticalGlyphVertices.get=function(){return this._structArray.uint16[this._pos2+19]},_.numIconVertices.get=function(){return this._structArray.uint16[this._pos2+20]},_.numVerticalIconVertices.get=function(){return this._structArray.uint16[this._pos2+21]},_.useRuntimeCollisionCircles.get=function(){return this._structArray.uint16[this._pos2+22]},_.crossTileID.get=function(){return this._structArray.uint32[this._pos4+12]},_.crossTileID.set=function(R){this._structArray.uint32[this._pos4+12]=R},_.textBoxScale.get=function(){return this._structArray.float32[this._pos4+13]},_.textOffset0.get=function(){return this._structArray.float32[this._pos4+14]},_.textOffset1.get=function(){return this._structArray.float32[this._pos4+15]},_.collisionCircleDiameter.get=function(){return this._structArray.float32[this._pos4+16]},Object.defineProperties(p.prototype,_),p}(ja);Y.prototype.size=68;var ie=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.get=function(_){return new Y(this,_)},p}(Rp);Yi("SymbolInstanceArray",ie);var ae=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.getoffsetX=function(_){return this.float32[1*_+0]},p}(bf);Yi("GlyphOffsetArray",ae);var ge=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.getx=function(_){return this.int16[3*_+0]},p.prototype.gety=function(_){return this.int16[3*_+1]},p.prototype.gettileUnitDistanceFromAnchor=function(_){return this.int16[3*_+2]},p}(Du);Yi("SymbolLineVertexArray",ge);var Pe=function(u){function p(){u.apply(this,arguments)}u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p;var _={featureIndex:{configurable:!0},sourceLayerIndex:{configurable:!0},bucketIndex:{configurable:!0}};return _.featureIndex.get=function(){return this._structArray.uint32[this._pos4+0]},_.sourceLayerIndex.get=function(){return this._structArray.uint16[this._pos2+2]},_.bucketIndex.get=function(){return this._structArray.uint16[this._pos2+3]},Object.defineProperties(p.prototype,_),p}(ja);Pe.prototype.size=8;var Ue=function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.get=function(_){return new Pe(this,_)},p}(Bp);Yi("FeatureIndexArray",Ue);var ct=fa([{name:"a_pos",components:2,type:"Int16"}],4).members,ft=function(u){u===void 0&&(u=[]),this.segments=u};function gt(u,p){return 256*(u=U(Math.floor(u),0,255))+U(Math.floor(p),0,255)}ft.prototype.prepareSegment=function(u,p,_,R){var j=this.segments[this.segments.length-1];return u>ft.MAX_VERTEX_ARRAY_LENGTH&&Qe("Max vertices per segment is "+ft.MAX_VERTEX_ARRAY_LENGTH+": bucket requested "+u),(!j||j.vertexLength+u>ft.MAX_VERTEX_ARRAY_LENGTH||j.sortKey!==R)&&(j={vertexOffset:p.length,primitiveOffset:_.length,vertexLength:0,primitiveLength:0},R!==void 0&&(j.sortKey=R),this.segments.push(j)),j},ft.prototype.get=function(){return this.segments},ft.prototype.destroy=function(){for(var u=0,p=this.segments;u>>16)*le&65535)<<16)&4294967295)<<15|Me>>>17))*ye+(((Me>>>16)*ye&65535)<<16)&4294967295)<<13|$>>>19))+((5*($>>>16)&65535)<<16)&4294967295))+((58964+(J>>>16)&65535)<<16);switch(Me=0,R){case 3:Me^=(255&p.charCodeAt(Le+2))<<16;case 2:Me^=(255&p.charCodeAt(Le+1))<<8;case 1:$^=Me=(65535&(Me=(Me=(65535&(Me^=255&p.charCodeAt(Le)))*le+(((Me>>>16)*le&65535)<<16)&4294967295)<<15|Me>>>17))*ye+(((Me>>>16)*ye&65535)<<16)&4294967295}return $^=p.length,$=2246822507*(65535&($^=$>>>16))+((2246822507*($>>>16)&65535)<<16)&4294967295,$=3266489909*(65535&($^=$>>>13))+((3266489909*($>>>16)&65535)<<16)&4294967295,($^=$>>>16)>>>0}}),Wt=o(function(u){u.exports=function(p,_){for(var R,j=p.length,$=_^j,J=0;j>=4;)R=1540483477*(65535&(R=255&p.charCodeAt(J)|(255&p.charCodeAt(++J))<<8|(255&p.charCodeAt(++J))<<16|(255&p.charCodeAt(++J))<<24))+((1540483477*(R>>>16)&65535)<<16),$=1540483477*(65535&$)+((1540483477*($>>>16)&65535)<<16)^(R=1540483477*(65535&(R^=R>>>24))+((1540483477*(R>>>16)&65535)<<16)),j-=4,++J;switch(j){case 3:$^=(255&p.charCodeAt(J+2))<<16;case 2:$^=(255&p.charCodeAt(J+1))<<8;case 1:$=1540483477*(65535&($^=255&p.charCodeAt(J)))+((1540483477*($>>>16)&65535)<<16)}return $=1540483477*(65535&($^=$>>>13))+((1540483477*($>>>16)&65535)<<16),($^=$>>>15)>>>0}}),Et=Pt,Zt=Wt;Et.murmur3=Pt,Et.murmur2=Zt;var ir=function(){this.ids=[],this.positions=[],this.indexed=!1};ir.prototype.add=function(u,p,_,R){this.ids.push(kr(u)),this.positions.push(p,_,R)},ir.prototype.getPositions=function(u){for(var p=kr(u),_=0,R=this.ids.length-1;_>1;this.ids[j]>=p?R=j:_=j+1}for(var $=[];this.ids[_]===p;)$.push({index:this.positions[3*_],start:this.positions[3*_+1],end:this.positions[3*_+2]}),_++;return $},ir.serialize=function(u,p){var _=new Float64Array(u.ids),R=new Uint32Array(u.positions);return function j($,J,le,ye){for(;le>1],Le=le-1,ze=ye+1;;){do Le++;while($[Le]Me);if(Le>=ze)break;Br($,Le,ze),Br(J,3*Le,3*ze),Br(J,3*Le+1,3*ze+1),Br(J,3*Le+2,3*ze+2)}ze-leJ.x+1||yeJ.y+1)&&Qe("Geometry exceeds allowed extent, reduce your vector tile buffer size")}return _}function Ta(u,p){return{type:u.type,id:u.id,properties:u.properties,geometry:p?Vs(u):[]}}function en(u,p,_,R,j){u.emplaceBack(2*p+(R+1)/2,2*_+(j+1)/2)}var _n=function(u){this.zoom=u.zoom,this.overscaling=u.overscaling,this.layers=u.layers,this.layerIds=this.layers.map(function(p){return p.id}),this.index=u.index,this.hasPattern=!1,this.layoutVertexArray=new Kh,this.indexArray=new Qh,this.segments=new ft,this.programConfigurations=new Rn(u.layers,u.zoom),this.stateDependentLayerIds=this.layers.filter(function(p){return p.isStateDependent()}).map(function(p){return p.id})};function Ju(u,p){for(var _=0;_1){if(Hc(u,p))return!0;for(var R=0;R1?_:_.sub(p)._mult(j)._add(p))}function Ou(u,p){for(var _,R,j,$=!1,J=0;Jp.y!=(j=_[ye]).y>p.y&&p.x<(j.x-R.x)*(p.y-R.y)/(j.y-R.y)+R.x&&($=!$);return $}function iu(u,p){for(var _=!1,R=0,j=u.length-1;Rp.y!=J.y>p.y&&p.x<(J.x-$.x)*(p.y-$.y)/(J.y-$.y)+$.x&&(_=!_)}return _}function Sa(u,p,_){var R=_[0],j=_[2];if(u.xj.x&&p.x>j.x||u.yj.y&&p.y>j.y)return!1;var $=wt(u,p,_[0]);return $!==wt(u,p,_[1])||$!==wt(u,p,_[2])||$!==wt(u,p,_[3])}function ia(u,p,_){var R=p.paint.get(u).value;return R.kind==="constant"?R.value:_.programConfigurations.get(p.id).getMaxValue(u)}function zl(u){return Math.sqrt(u[0]*u[0]+u[1]*u[1])}function eh(u,p,_,R,j){if(!p[0]&&!p[1])return u;var $=f.convert(p)._mult(j);_==="viewport"&&$._rotate(-R);for(var J=[],le=0;le=8192||Le<0||Le>=8192)){var ze=this.segments.prepareSegment(4,this.layoutVertexArray,this.indexArray,u.sortKey),Ye=ze.vertexLength;en(this.layoutVertexArray,Me,Le,-1,-1),en(this.layoutVertexArray,Me,Le,1,-1),en(this.layoutVertexArray,Me,Le,1,1),en(this.layoutVertexArray,Me,Le,-1,1),this.indexArray.emplaceBack(Ye,Ye+1,Ye+2),this.indexArray.emplaceBack(Ye,Ye+3,Ye+2),ze.vertexLength+=4,ze.primitiveLength+=2}}this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length,u,_,{},R)},Yi("CircleBucket",_n,{omit:["layers"]});var ef=new Cl({"circle-sort-key":new nn(ht.layout_circle["circle-sort-key"])}),Ul={paint:new Cl({"circle-radius":new nn(ht.paint_circle["circle-radius"]),"circle-color":new nn(ht.paint_circle["circle-color"]),"circle-blur":new nn(ht.paint_circle["circle-blur"]),"circle-opacity":new nn(ht.paint_circle["circle-opacity"]),"circle-translate":new An(ht.paint_circle["circle-translate"]),"circle-translate-anchor":new An(ht.paint_circle["circle-translate-anchor"]),"circle-pitch-scale":new An(ht.paint_circle["circle-pitch-scale"]),"circle-pitch-alignment":new An(ht.paint_circle["circle-pitch-alignment"]),"circle-stroke-width":new nn(ht.paint_circle["circle-stroke-width"]),"circle-stroke-color":new nn(ht.paint_circle["circle-stroke-color"]),"circle-stroke-opacity":new nn(ht.paint_circle["circle-stroke-opacity"])}),layout:ef},_s=typeof Float32Array<"u"?Float32Array:Array;function yu(u){return u[0]=1,u[1]=0,u[2]=0,u[3]=0,u[4]=0,u[5]=1,u[6]=0,u[7]=0,u[8]=0,u[9]=0,u[10]=1,u[11]=0,u[12]=0,u[13]=0,u[14]=0,u[15]=1,u}function Il(u,p,_){var R=p[0],j=p[1],$=p[2],J=p[3],le=p[4],ye=p[5],Me=p[6],Le=p[7],ze=p[8],Ye=p[9],lt=p[10],It=p[11],Dt=p[12],tr=p[13],qt=p[14],ur=p[15],Tr=_[0],Nr=_[1],Vr=_[2],ri=_[3];return u[0]=Tr*R+Nr*le+Vr*ze+ri*Dt,u[1]=Tr*j+Nr*ye+Vr*Ye+ri*tr,u[2]=Tr*$+Nr*Me+Vr*lt+ri*qt,u[3]=Tr*J+Nr*Le+Vr*It+ri*ur,u[4]=(Tr=_[4])*R+(Nr=_[5])*le+(Vr=_[6])*ze+(ri=_[7])*Dt,u[5]=Tr*j+Nr*ye+Vr*Ye+ri*tr,u[6]=Tr*$+Nr*Me+Vr*lt+ri*qt,u[7]=Tr*J+Nr*Le+Vr*It+ri*ur,u[8]=(Tr=_[8])*R+(Nr=_[9])*le+(Vr=_[10])*ze+(ri=_[11])*Dt,u[9]=Tr*j+Nr*ye+Vr*Ye+ri*tr,u[10]=Tr*$+Nr*Me+Vr*lt+ri*qt,u[11]=Tr*J+Nr*Le+Vr*It+ri*ur,u[12]=(Tr=_[12])*R+(Nr=_[13])*le+(Vr=_[14])*ze+(ri=_[15])*Dt,u[13]=Tr*j+Nr*ye+Vr*Ye+ri*tr,u[14]=Tr*$+Nr*Me+Vr*lt+ri*qt,u[15]=Tr*J+Nr*Le+Vr*It+ri*ur,u}Math.hypot||(Math.hypot=function(){for(var u=arguments,p=0,_=arguments.length;_--;)p+=u[_]*u[_];return Math.sqrt(p)});var tf,Kg=Il;function Lp(u,p,_){var R=p[0],j=p[1],$=p[2],J=p[3];return u[0]=_[0]*R+_[4]*j+_[8]*$+_[12]*J,u[1]=_[1]*R+_[5]*j+_[9]*$+_[13]*J,u[2]=_[2]*R+_[6]*j+_[10]*$+_[14]*J,u[3]=_[3]*R+_[7]*j+_[11]*$+_[15]*J,u}tf=new _s(3),_s!=Float32Array&&(tf[0]=0,tf[1]=0,tf[2]=0),function(){var u=new _s(4);_s!=Float32Array&&(u[0]=0,u[1]=0,u[2]=0,u[3]=0)}();var th=(function(){var u=new _s(2);_s!=Float32Array&&(u[0]=0,u[1]=0)}(),function(u){function p(_){u.call(this,_,Ul)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.createBucket=function(_){return new _n(_)},p.prototype.queryRadius=function(_){var R=_;return ia("circle-radius",this,R)+ia("circle-stroke-width",this,R)+zl(this.paint.get("circle-translate"))},p.prototype.queryIntersectsFeature=function(_,R,j,$,J,le,ye,Me){for(var Le=eh(_,this.paint.get("circle-translate"),this.paint.get("circle-translate-anchor"),le.angle,ye),ze=this.paint.get("circle-radius").evaluate(R,j)+this.paint.get("circle-stroke-width").evaluate(R,j),Ye=this.paint.get("circle-pitch-alignment")==="map",lt=Ye?Le:function(Ei,Ui){return Ei.map(function(Cn){return Kf(Cn,Ui)})}(Le,Me),It=Ye?ze*ye:ze,Dt=0,tr=$;Dtu.width||j.height>u.height||_.x>u.width-j.width||_.y>u.height-j.height)throw new RangeError("out of range source coordinates for image copy");if(j.width>p.width||j.height>p.height||R.x>p.width-j.width||R.y>p.height-j.height)throw new RangeError("out of range destination coordinates for image copy");for(var J=u.data,le=p.data,ye=0;ye80*_){R=$=u[0],j=J=u[1];for(var It=_;It$&&($=le),ye>J&&(J=ye);Me=(Me=Math.max($-R,J-j))!==0?1/Me:0}return P(Ye,lt,_,R,j,Me),lt}function m(u,p,_,R,j){var $,J;if(j===Cr(u,p,_,R)>0)for($=p;$<_;$+=R)J=zt($,u[$],u[$+1],J);else for($=_-R;$>=p;$-=R)J=zt($,u[$],u[$+1],J);return J&&vt(J,J.next)&&(jt(J),J=J.next),J}function T(u,p){if(!u)return u;p||(p=u);var _,R=u;do if(_=!1,R.steiner||!vt(R,R.next)&&At(R.prev,R,R.next)!==0)R=R.next;else{if(jt(R),(R=p=R.prev)===R.next)break;_=!0}while(_||R!==p);return p}function P(u,p,_,R,j,$,J){if(u){!J&&$&&function(Le,ze,Ye,lt){var It=Le;do It.z===null&&(It.z=Ie(It.x,It.y,ze,Ye,lt)),It.prevZ=It.prev,It.nextZ=It.next,It=It.next;while(It!==Le);It.prevZ.nextZ=null,It.prevZ=null,function(Dt){var tr,qt,ur,Tr,Nr,Vr,ri,Ei,Ui=1;do{for(qt=Dt,Dt=null,Nr=null,Vr=0;qt;){for(Vr++,ur=qt,ri=0,tr=0;tr0||Ei>0&&ur;)ri!==0&&(Ei===0||!ur||qt.z<=ur.z)?(Tr=qt,qt=qt.nextZ,ri--):(Tr=ur,ur=ur.nextZ,Ei--),Nr?Nr.nextZ=Tr:Dt=Tr,Tr.prevZ=Nr,Nr=Tr;qt=ur}Nr.nextZ=null,Ui*=2}while(Vr>1)}(It)}(u,R,j,$);for(var le,ye,Me=u;u.prev!==u.next;)if(le=u.prev,ye=u.next,$?W(u,R,j,$):V(u))p.push(le.i/_),p.push(u.i/_),p.push(ye.i/_),jt(u),u=ye.next,Me=ye.next;else if((u=ye)===Me){J?J===1?P(u=te(T(u),p,_),p,_,R,j,$,2):J===2&&se(u,p,_,R,j,$):P(T(u),p,_,R,j,$,1);break}}}function V(u){var p=u.prev,_=u,R=u.next;if(At(p,_,R)>=0)return!1;for(var j=u.next.next;j!==u.prev;){if(tt(p.x,p.y,_.x,_.y,R.x,R.y,j.x,j.y)&&At(j.prev,j,j.next)>=0)return!1;j=j.next}return!0}function W(u,p,_,R){var j=u.prev,$=u,J=u.next;if(At(j,$,J)>=0)return!1;for(var le=j.x>$.x?j.x>J.x?j.x:J.x:$.x>J.x?$.x:J.x,ye=j.y>$.y?j.y>J.y?j.y:J.y:$.y>J.y?$.y:J.y,Me=Ie(j.x<$.x?j.x=Me&&Ye&&Ye.z<=Le;){if(ze!==u.prev&&ze!==u.next&&tt(j.x,j.y,$.x,$.y,J.x,J.y,ze.x,ze.y)&&At(ze.prev,ze,ze.next)>=0||(ze=ze.prevZ,Ye!==u.prev&&Ye!==u.next&&tt(j.x,j.y,$.x,$.y,J.x,J.y,Ye.x,Ye.y)&&At(Ye.prev,Ye,Ye.next)>=0))return!1;Ye=Ye.nextZ}for(;ze&&ze.z>=Me;){if(ze!==u.prev&&ze!==u.next&&tt(j.x,j.y,$.x,$.y,J.x,J.y,ze.x,ze.y)&&At(ze.prev,ze,ze.next)>=0)return!1;ze=ze.prevZ}for(;Ye&&Ye.z<=Le;){if(Ye!==u.prev&&Ye!==u.next&&tt(j.x,j.y,$.x,$.y,J.x,J.y,Ye.x,Ye.y)&&At(Ye.prev,Ye,Ye.next)>=0)return!1;Ye=Ye.nextZ}return!0}function te(u,p,_){var R=u;do{var j=R.prev,$=R.next.next;!vt(j,$)&&Lt(j,R,R.next,$)&&Xt(j,$)&&Xt($,j)&&(p.push(j.i/_),p.push(R.i/_),p.push($.i/_),jt(R),jt(R.next),R=u=$),R=R.next}while(R!==u);return T(R)}function se(u,p,_,R,j,$){var J=u;do{for(var le=J.next.next;le!==J.prev;){if(J.i!==le.i&&yt(J,le)){var ye=Rt(J,le);return J=T(J,J.next),ye=T(ye,ye.next),P(J,p,_,R,j,$),void P(ye,p,_,R,j,$)}le=le.next}J=J.next}while(J!==u)}function fe(u,p){return u.x-p.x}function _e(u,p){if(p=function(R,j){var $,J=j,le=R.x,ye=R.y,Me=-1/0;do{if(ye<=J.y&&ye>=J.next.y&&J.next.y!==J.y){var Le=J.x+(ye-J.y)*(J.next.x-J.x)/(J.next.y-J.y);if(Le<=le&&Le>Me){if(Me=Le,Le===le){if(ye===J.y)return J;if(ye===J.next.y)return J.next}$=J.x=J.x&&J.x>=lt&&le!==J.x&&tt(ye$.x||J.x===$.x&&Re($,J)))&&($=J,Dt=ze)),J=J.next;while(J!==Ye);return $}(u,p)){var _=Rt(p,u);T(p,p.next),T(_,_.next)}}function Re(u,p){return At(u.prev,u,p.prev)<0&&At(p.next,u,u.next)<0}function Ie(u,p,_,R,j){return(u=1431655765&((u=858993459&((u=252645135&((u=16711935&((u=32767*(u-_)*j)|u<<8))|u<<4))|u<<2))|u<<1))|(p=1431655765&((p=858993459&((p=252645135&((p=16711935&((p=32767*(p-R)*j)|p<<8))|p<<4))|p<<2))|p<<1))<<1}function je(u){var p=u,_=u;do(p.x<_.x||p.x===_.x&&p.y<_.y)&&(_=p),p=p.next;while(p!==u);return _}function tt(u,p,_,R,j,$,J,le){return(j-J)*(p-le)-(u-J)*($-le)>=0&&(u-J)*(R-le)-(_-J)*(p-le)>=0&&(_-J)*($-le)-(j-J)*(R-le)>=0}function yt(u,p){return u.next.i!==p.i&&u.prev.i!==p.i&&!function(_,R){var j=_;do{if(j.i!==_.i&&j.next.i!==_.i&&j.i!==R.i&&j.next.i!==R.i&&Lt(j,j.next,_,R))return!0;j=j.next}while(j!==_);return!1}(u,p)&&(Xt(u,p)&&Xt(p,u)&&function(_,R){var j=_,$=!1,J=(_.x+R.x)/2,le=(_.y+R.y)/2;do j.y>le!=j.next.y>le&&j.next.y!==j.y&&J<(j.next.x-j.x)*(le-j.y)/(j.next.y-j.y)+j.x&&($=!$),j=j.next;while(j!==_);return $}(u,p)&&(At(u.prev,u,p.prev)||At(u,p.prev,p))||vt(u,p)&&At(u.prev,u,u.next)>0&&At(p.prev,p,p.next)>0)}function At(u,p,_){return(p.y-u.y)*(_.x-p.x)-(p.x-u.x)*(_.y-p.y)}function vt(u,p){return u.x===p.x&&u.y===p.y}function Lt(u,p,_,R){var j=Mt(At(u,p,_)),$=Mt(At(u,p,R)),J=Mt(At(_,R,u)),le=Mt(At(_,R,p));return j!==$&&J!==le||!(j!==0||!Kt(u,_,p))||!($!==0||!Kt(u,R,p))||!(J!==0||!Kt(_,u,R))||!(le!==0||!Kt(_,p,R))}function Kt(u,p,_){return p.x<=Math.max(u.x,_.x)&&p.x>=Math.min(u.x,_.x)&&p.y<=Math.max(u.y,_.y)&&p.y>=Math.min(u.y,_.y)}function Mt(u){return u>0?1:u<0?-1:0}function Xt(u,p){return At(u.prev,u,u.next)<0?At(u,p,u.next)>=0&&At(u,u.prev,p)>=0:At(u,p,u.prev)<0||At(u,u.next,p)<0}function Rt(u,p){var _=new br(u.i,u.x,u.y),R=new br(p.i,p.x,p.y),j=u.next,$=p.prev;return u.next=p,p.prev=u,_.next=j,j.prev=_,R.next=_,_.prev=R,$.next=R,R.prev=$,R}function zt(u,p,_,R){var j=new br(u,p,_);return R?(j.next=R.next,j.prev=R,R.next.prev=j,R.next=j):(j.prev=j,j.next=j),j}function jt(u){u.next.prev=u.prev,u.prev.next=u.next,u.prevZ&&(u.prevZ.nextZ=u.nextZ),u.nextZ&&(u.nextZ.prevZ=u.prevZ)}function br(u,p,_){this.i=u,this.x=p,this.y=_,this.prev=null,this.next=null,this.z=null,this.prevZ=null,this.nextZ=null,this.steiner=!1}function Cr(u,p,_,R){for(var j=0,$=p,J=_-R;$<_;$+=R)j+=(u[J]-u[$])*(u[$+1]+u[J+1]),J=$;return j}function Pr(u,p,_,R,j){(function $(J,le,ye,Me,Le){for(;Me>ye;){if(Me-ye>600){var ze=Me-ye+1,Ye=le-ye+1,lt=Math.log(ze),It=.5*Math.exp(2*lt/3),Dt=.5*Math.sqrt(lt*It*(ze-It)/ze)*(Ye-ze/2<0?-1:1);$(J,le,Math.max(ye,Math.floor(le-Ye*It/ze+Dt)),Math.min(Me,Math.floor(le+(ze-Ye)*It/ze+Dt)),Le)}var tr=J[le],qt=ye,ur=Me;for(Ir(J,ye,le),Le(J[Me],tr)>0&&Ir(J,ye,Me);qt0;)ur--}Le(J[ye],tr)===0?Ir(J,ye,ur):Ir(J,++ur,Me),ur<=le&&(ye=ur+1),le<=ur&&(Me=ur-1)}})(u,p,_||0,R||u.length-1,j||Ai)}function Ir(u,p,_){var R=u[p];u[p]=u[_],u[_]=R}function Ai(u,p){return up?1:0}function _i(u,p){var _=u.length;if(_<=1)return[u];for(var R,j,$=[],J=0;J<_;J++){var le=et(u[J]);le!==0&&(u[J].area=Math.abs(le),j===void 0&&(j=le<0),j===le<0?(R&&$.push(R),R=[u[J]]):R.push(u[J]))}if(R&&$.push(R),p>1)for(var ye=0;ye<$.length;ye++)$[ye].length<=p||(Pr($[ye],p,1,$[ye].length-1,hi),$[ye]=$[ye].slice(0,p));return $}function hi(u,p){return p.area-u.area}function hn(u,p,_){for(var R=_.patternDependencies,j=!1,$=0,J=p;$0&&_.holes.push(R+=u[j-1].length)}return _},Qg.default=z;var $r=function(u){this.zoom=u.zoom,this.overscaling=u.overscaling,this.layers=u.layers,this.layerIds=this.layers.map(function(p){return p.id}),this.index=u.index,this.hasPattern=!1,this.patternFeatures=[],this.layoutVertexArray=new Kh,this.indexArray=new Qh,this.indexArray2=new Kd,this.programConfigurations=new Rn(u.layers,u.zoom),this.segments=new ft,this.segments2=new ft,this.stateDependentLayerIds=this.layers.filter(function(p){return p.isStateDependent()}).map(function(p){return p.id})};$r.prototype.populate=function(u,p,_){this.hasPattern=hn("fill",this.layers,p);for(var R=this.layers[0].layout.get("fill-sort-key"),j=[],$=0,J=u;$>3}if(j--,R===1||R===2)$+=u.readSVarint(),J+=u.readSVarint(),R===1&&(p&&le.push(p),p=[]),p.push(new f($,J));else{if(R!==7)throw new Error("unknown command "+R);p&&p.push(p[0].clone())}}return p&&le.push(p),le},qs.prototype.bbox=function(){var u=this._pbf;u.pos=this._geometry;for(var p=u.readVarint()+u.pos,_=1,R=0,j=0,$=0,J=1/0,le=-1/0,ye=1/0,Me=-1/0;u.pos>3}if(R--,_===1||_===2)(j+=u.readSVarint())le&&(le=j),($+=u.readSVarint())Me&&(Me=$);else if(_!==7)throw new Error("unknown command "+_)}return[J,ye,le,Me]},qs.prototype.toGeoJSON=function(u,p,_){var R,j,$=this.extent*Math.pow(2,_),J=this.extent*u,le=this.extent*p,ye=this.loadGeometry(),Me=qs.types[this.type];function Le(lt){for(var It=0;It>3;j=J===1?R.readString():J===2?R.readFloat():J===3?R.readDouble():J===4?R.readVarint64():J===5?R.readVarint():J===6?R.readSVarint():J===7?R.readBoolean():null}return j}(_))}function E(u,p,_){if(u===3){var R=new Ea(_,_.readVarint()+_.pos);R.length&&(p[R.name]=R)}}v.prototype.feature=function(u){if(u<0||u>=this._features.length)throw new Error("feature index out of bounds");this._pbf.pos=this._features[u];var p=this._pbf.readVarint()+this._pbf.pos;return new jl(this._pbf,p,this.extent,this._keys,this._values)};var B={VectorTile:function(u,p){this.layers=u.readFields(E,{},p)},VectorTileFeature:jl,VectorTileLayer:Ea},N=B.VectorTileFeature.types,H=Math.pow(2,13);function X(u,p,_,R,j,$,J,le){u.emplaceBack(p,_,2*Math.floor(R*H)+J,j*H*2,$*H*2,Math.round(le))}var Q=function(u){this.zoom=u.zoom,this.overscaling=u.overscaling,this.layers=u.layers,this.layerIds=this.layers.map(function(p){return p.id}),this.index=u.index,this.hasPattern=!1,this.layoutVertexArray=new _c,this.indexArray=new Qh,this.programConfigurations=new Rn(u.layers,u.zoom),this.segments=new ft,this.stateDependentLayerIds=this.layers.filter(function(p){return p.isStateDependent()}).map(function(p){return p.id})};function ue(u,p){return u.x===p.x&&(u.x<0||u.x>8192)||u.y===p.y&&(u.y<0||u.y>8192)}Q.prototype.populate=function(u,p,_){this.features=[],this.hasPattern=hn("fill-extrusion",this.layers,p);for(var R=0,j=u;R8192})||Jo.every(function(xo){return xo.y<0})||Jo.every(function(xo){return xo.y>8192})))for(var Dt=0,tr=0;tr=1){var ur=It[tr-1];if(!ue(qt,ur)){ze.vertexLength+4>ft.MAX_VERTEX_ARRAY_LENGTH&&(ze=this.segments.prepareSegment(4,this.layoutVertexArray,this.indexArray));var Tr=qt.sub(ur)._perp()._unit(),Nr=ur.dist(qt);Dt+Nr>32768&&(Dt=0),X(this.layoutVertexArray,qt.x,qt.y,Tr.x,Tr.y,0,0,Dt),X(this.layoutVertexArray,qt.x,qt.y,Tr.x,Tr.y,0,1,Dt),X(this.layoutVertexArray,ur.x,ur.y,Tr.x,Tr.y,0,0,Dt+=Nr),X(this.layoutVertexArray,ur.x,ur.y,Tr.x,Tr.y,0,1,Dt);var Vr=ze.vertexLength;this.indexArray.emplaceBack(Vr,Vr+2,Vr+1),this.indexArray.emplaceBack(Vr+1,Vr+2,Vr+3),ze.vertexLength+=4,ze.primitiveLength+=2}}}}if(ze.vertexLength+ye>ft.MAX_VERTEX_ARRAY_LENGTH&&(ze=this.segments.prepareSegment(ye,this.layoutVertexArray,this.indexArray)),N[u.type]==="Polygon"){for(var ri=[],Ei=[],Ui=ze.vertexLength,Cn=0,Ki=le;Cn=2&&u[ye-1].equals(u[ye-2]);)ye--;for(var Me=0;Me0;if(Ei&&qt>Me){var Cn=Le.dist(lt);if(Cn>2*ze){var Ki=Le.sub(Le.sub(lt)._mult(ze/Cn)._round());this.updateDistance(lt,Ki),this.addCurrentVertex(Ki,Dt,0,0,Ye),lt=Ki}}var Bn=lt&&It,Ji=Bn?_:le?"butt":R;if(Bn&&Ji==="round"&&(Vr<$?Ji="miter":Vr<=2&&(Ji="fakeround")),Ji==="miter"&&Vr>j&&(Ji="bevel"),Ji==="bevel"&&(Vr>2&&(Ji="flipbevel"),Vr100)ur=tr.mult(-1);else{var Wo=Vr*Dt.add(tr).mag()/Dt.sub(tr).mag();ur._perp()._mult(Wo*(Ui?-1:1))}this.addCurrentVertex(Le,ur,0,0,Ye),this.addCurrentVertex(Le,ur.mult(-1),0,0,Ye)}else if(Ji==="bevel"||Ji==="fakeround"){var Xn=-Math.sqrt(Vr*Vr-1),pn=Ui?Xn:0,Jo=Ui?0:Xn;if(lt&&this.addCurrentVertex(Le,Dt,pn,Jo,Ye),Ji==="fakeround")for(var xo=Math.round(180*ri/Math.PI/20),es=1;es2*ze){var fl=Le.add(It.sub(Le)._mult(ze/Wl)._round());this.updateDistance(Le,fl),this.addCurrentVertex(fl,tr,0,0,Ye),Le=fl}}}}},_r.prototype.addCurrentVertex=function(u,p,_,R,j,$){$===void 0&&($=!1);var J=p.y*R-p.x,le=-p.y-p.x*R;this.addHalfVertex(u,p.x+p.y*_,p.y-p.x*_,$,!1,_,j),this.addHalfVertex(u,J,le,$,!0,-R,j),this.distance>qr/2&&this.totalDistance===0&&(this.distance=0,this.addCurrentVertex(u,p,_,R,j,$))},_r.prototype.addHalfVertex=function(u,p,_,R,j,$,J){var le=.5*(this.lineClips?this.scaledDistance*(qr-1):this.scaledDistance);this.layoutVertexArray.emplaceBack((u.x<<1)+(R?1:0),(u.y<<1)+(j?1:0),Math.round(63*p)+128,Math.round(63*_)+128,1+($===0?0:$<0?-1:1)|(63&le)<<2,le>>6),this.lineClips&&this.layoutVertexArray2.emplaceBack((this.scaledDistance-this.lineClips.start)/(this.lineClips.end-this.lineClips.start),this.lineClipsArray.length);var ye=J.vertexLength++;this.e1>=0&&this.e2>=0&&(this.indexArray.emplaceBack(this.e1,this.e2,ye),J.primitiveLength++),j?this.e2=ye:this.e1=ye},_r.prototype.updateScaledDistance=function(){this.scaledDistance=this.lineClips?this.lineClips.start+(this.lineClips.end-this.lineClips.start)*this.distance/this.totalDistance:this.distance},_r.prototype.updateDistance=function(u,p){this.distance+=u.dist(p),this.updateScaledDistance()},Yi("LineBucket",_r,{omit:["layers","patternFeatures"]});var Sr=new Cl({"line-cap":new An(ht.layout_line["line-cap"]),"line-join":new nn(ht.layout_line["line-join"]),"line-miter-limit":new An(ht.layout_line["line-miter-limit"]),"line-round-limit":new An(ht.layout_line["line-round-limit"]),"line-sort-key":new nn(ht.layout_line["line-sort-key"])}),or={paint:new Cl({"line-opacity":new nn(ht.paint_line["line-opacity"]),"line-color":new nn(ht.paint_line["line-color"]),"line-translate":new An(ht.paint_line["line-translate"]),"line-translate-anchor":new An(ht.paint_line["line-translate-anchor"]),"line-width":new nn(ht.paint_line["line-width"]),"line-gap-width":new nn(ht.paint_line["line-gap-width"]),"line-offset":new nn(ht.paint_line["line-offset"]),"line-blur":new nn(ht.paint_line["line-blur"]),"line-dasharray":new Vc(ht.paint_line["line-dasharray"]),"line-pattern":new Xd(ht.paint_line["line-pattern"]),"line-gradient":new Au(ht.paint_line["line-gradient"])}),layout:Sr},Pi=new(function(u){function p(){u.apply(this,arguments)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.possiblyEvaluate=function(_,R){return R=new So(Math.floor(R.zoom),{now:R.now,fadeDuration:R.fadeDuration,zoomHistory:R.zoomHistory,transition:R.transition}),u.prototype.possiblyEvaluate.call(this,_,R)},p.prototype.evaluate=function(_,R,j,$){return R=ee({},R,{zoom:Math.floor(R.zoom)}),u.prototype.evaluate.call(this,_,R,j,$)},p}(nn))(or.paint.properties["line-width"].specification);Pi.useIntegerZoom=!0;var En=function(u){function p(_){u.call(this,_,or),this.gradientVersion=0}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype._handleSpecialPaintPropertyUpdate=function(_){_==="line-gradient"&&(this.stepInterpolant=this._transitionablePaint._values["line-gradient"].value.expression._styleExpression.expression instanceof xl,this.gradientVersion=(this.gradientVersion+1)%M)},p.prototype.gradientExpression=function(){return this._transitionablePaint._values["line-gradient"].value.expression},p.prototype.recalculate=function(_,R){u.prototype.recalculate.call(this,_,R),this.paint._values["line-floorwidth"]=Pi.possiblyEvaluate(this._transitioningPaint._values["line-width"].value,_)},p.prototype.createBucket=function(_){return new _r(_)},p.prototype.queryRadius=function(_){var R=_,j=wn(ia("line-width",this,R),ia("line-gap-width",this,R)),$=ia("line-offset",this,R);return j/2+Math.abs($)+zl(this.paint.get("line-translate"))},p.prototype.queryIntersectsFeature=function(_,R,j,$,J,le,ye){var Me=eh(_,this.paint.get("line-translate"),this.paint.get("line-translate-anchor"),le.angle,ye),Le=ye/2*wn(this.paint.get("line-width").evaluate(R,j),this.paint.get("line-gap-width").evaluate(R,j)),ze=this.paint.get("line-offset").evaluate(R,j);return ze&&($=function(Ye,lt){for(var It=[],Dt=new f(0,0),tr=0;tr=3){for(var qt=0;qt0?p+2*u:u}var vo=fa([{name:"a_pos_offset",components:4,type:"Int16"},{name:"a_data",components:4,type:"Uint16"},{name:"a_pixeloffset",components:4,type:"Int16"}],4),Ro=fa([{name:"a_projected_pos",components:3,type:"Float32"}],4),Yo=(fa([{name:"a_fade_opacity",components:1,type:"Uint32"}],4),fa([{name:"a_placed",components:2,type:"Uint8"},{name:"a_shift",components:2,type:"Float32"}])),ro=(fa([{type:"Int16",name:"anchorPointX"},{type:"Int16",name:"anchorPointY"},{type:"Int16",name:"x1"},{type:"Int16",name:"y1"},{type:"Int16",name:"x2"},{type:"Int16",name:"y2"},{type:"Uint32",name:"featureIndex"},{type:"Uint16",name:"sourceLayerIndex"},{type:"Uint16",name:"bucketIndex"}]),fa([{name:"a_pos",components:2,type:"Int16"},{name:"a_anchor_pos",components:2,type:"Int16"},{name:"a_extrude",components:2,type:"Int16"}],4)),kn=fa([{name:"a_pos",components:2,type:"Float32"},{name:"a_radius",components:1,type:"Float32"},{name:"a_flags",components:2,type:"Int16"}],4);function Oi(u,p,_){return u.sections.forEach(function(R){R.text=function(j,$,J){var le=$.layout.get("text-transform").evaluate(J,{});return le==="uppercase"?j=j.toLocaleUpperCase():le==="lowercase"&&(j=j.toLocaleLowerCase()),gu.applyArabicShaping&&(j=gu.applyArabicShaping(j)),j}(R.text,p,_)}),u}fa([{name:"triangle",components:3,type:"Uint16"}]),fa([{type:"Int16",name:"anchorX"},{type:"Int16",name:"anchorY"},{type:"Uint16",name:"glyphStartIndex"},{type:"Uint16",name:"numGlyphs"},{type:"Uint32",name:"vertexStartIndex"},{type:"Uint32",name:"lineStartIndex"},{type:"Uint32",name:"lineLength"},{type:"Uint16",name:"segment"},{type:"Uint16",name:"lowerSize"},{type:"Uint16",name:"upperSize"},{type:"Float32",name:"lineOffsetX"},{type:"Float32",name:"lineOffsetY"},{type:"Uint8",name:"writingMode"},{type:"Uint8",name:"placedOrientation"},{type:"Uint8",name:"hidden"},{type:"Uint32",name:"crossTileID"},{type:"Int16",name:"associatedIconIndex"}]),fa([{type:"Int16",name:"anchorX"},{type:"Int16",name:"anchorY"},{type:"Int16",name:"rightJustifiedTextSymbolIndex"},{type:"Int16",name:"centerJustifiedTextSymbolIndex"},{type:"Int16",name:"leftJustifiedTextSymbolIndex"},{type:"Int16",name:"verticalPlacedTextSymbolIndex"},{type:"Int16",name:"placedIconSymbolIndex"},{type:"Int16",name:"verticalPlacedIconSymbolIndex"},{type:"Uint16",name:"key"},{type:"Uint16",name:"textBoxStartIndex"},{type:"Uint16",name:"textBoxEndIndex"},{type:"Uint16",name:"verticalTextBoxStartIndex"},{type:"Uint16",name:"verticalTextBoxEndIndex"},{type:"Uint16",name:"iconBoxStartIndex"},{type:"Uint16",name:"iconBoxEndIndex"},{type:"Uint16",name:"verticalIconBoxStartIndex"},{type:"Uint16",name:"verticalIconBoxEndIndex"},{type:"Uint16",name:"featureIndex"},{type:"Uint16",name:"numHorizontalGlyphVertices"},{type:"Uint16",name:"numVerticalGlyphVertices"},{type:"Uint16",name:"numIconVertices"},{type:"Uint16",name:"numVerticalIconVertices"},{type:"Uint16",name:"useRuntimeCollisionCircles"},{type:"Uint32",name:"crossTileID"},{type:"Float32",name:"textBoxScale"},{type:"Float32",components:2,name:"textOffset"},{type:"Float32",name:"collisionCircleDiameter"}]),fa([{type:"Float32",name:"offsetX"}]),fa([{type:"Int16",name:"x"},{type:"Int16",name:"y"},{type:"Int16",name:"tileUnitDistanceFromAnchor"}]);var Ko={"!":"\uFE15","#":"\uFF03",$:"\uFF04","%":"\uFF05","&":"\uFF06","(":"\uFE35",")":"\uFE36","*":"\uFF0A","+":"\uFF0B",",":"\uFE10","-":"\uFE32",".":"\u30FB","/":"\uFF0F",":":"\uFE13",";":"\uFE14","<":"\uFE3F","=":"\uFF1D",">":"\uFE40","?":"\uFE16","@":"\uFF20","[":"\uFE47","\\":"\uFF3C","]":"\uFE48","^":"\uFF3E",_:"\uFE33","`":"\uFF40","{":"\uFE37","|":"\u2015","}":"\uFE38","~":"\uFF5E","\xA2":"\uFFE0","\xA3":"\uFFE1","\xA5":"\uFFE5","\xA6":"\uFFE4","\xAC":"\uFFE2","\xAF":"\uFFE3","\u2013":"\uFE32","\u2014":"\uFE31","\u2018":"\uFE43","\u2019":"\uFE44","\u201C":"\uFE41","\u201D":"\uFE42","\u2026":"\uFE19","\u2027":"\u30FB","\u20A9":"\uFFE6","\u3001":"\uFE11","\u3002":"\uFE12","\u3008":"\uFE3F","\u3009":"\uFE40","\u300A":"\uFE3D","\u300B":"\uFE3E","\u300C":"\uFE41","\u300D":"\uFE42","\u300E":"\uFE43","\u300F":"\uFE44","\u3010":"\uFE3B","\u3011":"\uFE3C","\u3014":"\uFE39","\u3015":"\uFE3A","\u3016":"\uFE17","\u3017":"\uFE18","\uFF01":"\uFE15","\uFF08":"\uFE35","\uFF09":"\uFE36","\uFF0C":"\uFE10","\uFF0D":"\uFE32","\uFF0E":"\u30FB","\uFF1A":"\uFE13","\uFF1B":"\uFE14","\uFF1C":"\uFE3F","\uFF1E":"\uFE40","\uFF1F":"\uFE16","\uFF3B":"\uFE47","\uFF3D":"\uFE48","\uFF3F":"\uFE33","\uFF5B":"\uFE37","\uFF5C":"\u2015","\uFF5D":"\uFE38","\uFF5F":"\uFE35","\uFF60":"\uFE36","\uFF61":"\uFE12","\uFF62":"\uFE41","\uFF63":"\uFE42"},Ca=function(u,p,_,R,j){var $,J,le=8*j-R-1,ye=(1<>1,Le=-7,ze=_?j-1:0,Ye=_?-1:1,lt=u[p+ze];for(ze+=Ye,$=lt&(1<<-Le)-1,lt>>=-Le,Le+=le;Le>0;$=256*$+u[p+ze],ze+=Ye,Le-=8);for(J=$&(1<<-Le)-1,$>>=-Le,Le+=R;Le>0;J=256*J+u[p+ze],ze+=Ye,Le-=8);if($===0)$=1-Me;else{if($===ye)return J?NaN:1/0*(lt?-1:1);J+=Math.pow(2,R),$-=Me}return(lt?-1:1)*J*Math.pow(2,$-R)},Qo=function(u,p,_,R,j,$){var J,le,ye,Me=8*$-j-1,Le=(1<>1,Ye=j===23?Math.pow(2,-24)-Math.pow(2,-77):0,lt=R?0:$-1,It=R?1:-1,Dt=p<0||p===0&&1/p<0?1:0;for(p=Math.abs(p),isNaN(p)||p===1/0?(le=isNaN(p)?1:0,J=Le):(J=Math.floor(Math.log(p)/Math.LN2),p*(ye=Math.pow(2,-J))<1&&(J--,ye*=2),(p+=J+ze>=1?Ye/ye:Ye*Math.pow(2,1-ze))*ye>=2&&(J++,ye/=2),J+ze>=Le?(le=0,J=Le):J+ze>=1?(le=(p*ye-1)*Math.pow(2,j),J+=ze):(le=p*Math.pow(2,ze-1)*Math.pow(2,j),J=0));j>=8;u[_+lt]=255&le,lt+=It,le/=256,j-=8);for(J=J<0;u[_+lt]=255&J,lt+=It,J/=256,Me-=8);u[_+lt-It]|=128*Dt},va=Bo;function Bo(u){this.buf=ArrayBuffer.isView&&ArrayBuffer.isView(u)?u:new Uint8Array(u||0),this.pos=0,this.type=0,this.length=this.buf.length}Bo.Varint=0,Bo.Fixed64=1,Bo.Bytes=2,Bo.Fixed32=5;var Gx=typeof TextDecoder>"u"?null:new TextDecoder("utf8");function Sd(u){return u.type===Bo.Bytes?u.readVarint()+u.pos:u.pos+1}function Jg(u,p,_){return _?4294967296*p+(u>>>0):4294967296*(p>>>0)+(u>>>0)}function Hx(u,p,_){var R=p<=16383?1:p<=2097151?2:p<=268435455?3:Math.floor(Math.log(p)/(7*Math.LN2));_.realloc(R);for(var j=_.pos-1;j>=u;j--)_.buf[j+R]=_.buf[j]}function x5(u,p){for(var _=0;_>>8,u[_+2]=p>>>16,u[_+3]=p>>>24}function SC(u,p){return(u[p]|u[p+1]<<8|u[p+2]<<16)+(u[p+3]<<24)}function fL(u,p,_){u===1&&_.readMessage(E5,p)}function E5(u,p,_){if(u===3){var R=_.readMessage(EC,{}),j=R.width,$=R.height,J=R.left,le=R.top,ye=R.advance;p.push({id:R.id,bitmap:new Fu({width:j+6,height:$+6},R.bitmap),metrics:{width:j,height:$,left:J,top:le,advance:ye}})}}function EC(u,p,_){u===1?p.id=_.readVarint():u===2?p.bitmap=_.readBytes():u===3?p.width=_.readVarint():u===4?p.height=_.readVarint():u===5?p.left=_.readSVarint():u===6?p.top=_.readSVarint():u===7&&(p.advance=_.readVarint())}function CC(u){for(var p=0,_=0,R=0,j=u;R=0;Ye--){var lt=J[Ye];if(!(ze.w>lt.w||ze.h>lt.h)){if(ze.x=lt.x,ze.y=lt.y,ye=Math.max(ye,ze.y+ze.h),le=Math.max(le,ze.x+ze.w),ze.w===lt.w&&ze.h===lt.h){var It=J.pop();Ye>3,$=this.pos;this.type=7&R,u(j,p,this),this.pos===$&&this.skip(R)}return p},readMessage:function(u,p){return this.readFields(u,p,this.readVarint()+this.pos)},readFixed32:function(){var u=Jw(this.buf,this.pos);return this.pos+=4,u},readSFixed32:function(){var u=SC(this.buf,this.pos);return this.pos+=4,u},readFixed64:function(){var u=Jw(this.buf,this.pos)+4294967296*Jw(this.buf,this.pos+4);return this.pos+=8,u},readSFixed64:function(){var u=Jw(this.buf,this.pos)+4294967296*SC(this.buf,this.pos+4);return this.pos+=8,u},readFloat:function(){var u=Ca(this.buf,this.pos,!0,23,4);return this.pos+=4,u},readDouble:function(){var u=Ca(this.buf,this.pos,!0,52,8);return this.pos+=8,u},readVarint:function(u){var p,_,R=this.buf;return p=127&(_=R[this.pos++]),_<128?p:(p|=(127&(_=R[this.pos++]))<<7,_<128?p:(p|=(127&(_=R[this.pos++]))<<14,_<128?p:(p|=(127&(_=R[this.pos++]))<<21,_<128?p:function(j,$,J){var le,ye,Me=J.buf;if(le=(112&(ye=Me[J.pos++]))>>4,ye<128||(le|=(127&(ye=Me[J.pos++]))<<3,ye<128)||(le|=(127&(ye=Me[J.pos++]))<<10,ye<128)||(le|=(127&(ye=Me[J.pos++]))<<17,ye<128)||(le|=(127&(ye=Me[J.pos++]))<<24,ye<128)||(le|=(1&(ye=Me[J.pos++]))<<31,ye<128))return Jg(j,le,$);throw new Error("Expected varint not more than 10 bytes")}(p|=(15&(_=R[this.pos]))<<28,u,this))))},readVarint64:function(){return this.readVarint(!0)},readSVarint:function(){var u=this.readVarint();return u%2==1?(u+1)/-2:u/2},readBoolean:function(){return Boolean(this.readVarint())},readString:function(){var u=this.readVarint()+this.pos,p=this.pos;return this.pos=u,u-p>=12&&Gx?function(_,R,j){return Gx.decode(_.subarray(R,j))}(this.buf,p,u):function(_,R,j){for(var $="",J=R;J239?4:Le>223?3:Le>191?2:1;if(J+Ye>j)break;Ye===1?Le<128&&(ze=Le):Ye===2?(192&(le=_[J+1]))==128&&(ze=(31&Le)<<6|63&le)<=127&&(ze=null):Ye===3?(ye=_[J+2],(192&(le=_[J+1]))==128&&(192&ye)==128&&((ze=(15&Le)<<12|(63&le)<<6|63&ye)<=2047||ze>=55296&&ze<=57343)&&(ze=null)):Ye===4&&(ye=_[J+2],Me=_[J+3],(192&(le=_[J+1]))==128&&(192&ye)==128&&(192&Me)==128&&((ze=(15&Le)<<18|(63&le)<<12|(63&ye)<<6|63&Me)<=65535||ze>=1114112)&&(ze=null)),ze===null?(ze=65533,Ye=1):ze>65535&&(ze-=65536,$+=String.fromCharCode(ze>>>10&1023|55296),ze=56320|1023&ze),$+=String.fromCharCode(ze),J+=Ye}return $}(this.buf,p,u)},readBytes:function(){var u=this.readVarint()+this.pos,p=this.buf.subarray(this.pos,u);return this.pos=u,p},readPackedVarint:function(u,p){if(this.type!==Bo.Bytes)return u.push(this.readVarint(p));var _=Sd(this);for(u=u||[];this.pos<_;)u.push(this.readVarint(p));return u},readPackedSVarint:function(u){if(this.type!==Bo.Bytes)return u.push(this.readSVarint());var p=Sd(this);for(u=u||[];this.pos127;);else if(p===Bo.Bytes)this.pos=this.readVarint()+this.pos;else if(p===Bo.Fixed32)this.pos+=4;else{if(p!==Bo.Fixed64)throw new Error("Unimplemented type: "+p);this.pos+=8}},writeTag:function(u,p){this.writeVarint(u<<3|p)},realloc:function(u){for(var p=this.length||16;p268435455||u<0?function(p,_){var R,j;if(p>=0?(R=p%4294967296|0,j=p/4294967296|0):(j=~(-p/4294967296),4294967295^(R=~(-p%4294967296))?R=R+1|0:(R=0,j=j+1|0)),p>=18446744073709552e3||p<-18446744073709552e3)throw new Error("Given varint doesn't fit into 10 bytes");_.realloc(10),function($,J,le){le.buf[le.pos++]=127&$|128,$>>>=7,le.buf[le.pos++]=127&$|128,$>>>=7,le.buf[le.pos++]=127&$|128,$>>>=7,le.buf[le.pos++]=127&$|128,le.buf[le.pos]=127&($>>>=7)}(R,0,_),function($,J){var le=(7&$)<<4;J.buf[J.pos++]|=le|(($>>>=3)?128:0),$&&(J.buf[J.pos++]=127&$|(($>>>=7)?128:0),$&&(J.buf[J.pos++]=127&$|(($>>>=7)?128:0),$&&(J.buf[J.pos++]=127&$|(($>>>=7)?128:0),$&&(J.buf[J.pos++]=127&$|(($>>>=7)?128:0),$&&(J.buf[J.pos++]=127&$)))))}(j,_)}(u,this):(this.realloc(4),this.buf[this.pos++]=127&u|(u>127?128:0),u<=127||(this.buf[this.pos++]=127&(u>>>=7)|(u>127?128:0),u<=127||(this.buf[this.pos++]=127&(u>>>=7)|(u>127?128:0),u<=127||(this.buf[this.pos++]=u>>>7&127))))},writeSVarint:function(u){this.writeVarint(u<0?2*-u-1:2*u)},writeBoolean:function(u){this.writeVarint(Boolean(u))},writeString:function(u){u=String(u),this.realloc(4*u.length),this.pos++;var p=this.pos;this.pos=function(R,j,$){for(var J,le,ye=0;ye55295&&J<57344){if(!le){J>56319||ye+1===j.length?(R[$++]=239,R[$++]=191,R[$++]=189):le=J;continue}if(J<56320){R[$++]=239,R[$++]=191,R[$++]=189,le=J;continue}J=le-55296<<10|J-56320|65536,le=null}else le&&(R[$++]=239,R[$++]=191,R[$++]=189,le=null);J<128?R[$++]=J:(J<2048?R[$++]=J>>6|192:(J<65536?R[$++]=J>>12|224:(R[$++]=J>>18|240,R[$++]=J>>12&63|128),R[$++]=J>>6&63|128),R[$++]=63&J|128)}return $}(this.buf,u,this.pos);var _=this.pos-p;_>=128&&Hx(p,_,this),this.pos=p-1,this.writeVarint(_),this.pos+=_},writeFloat:function(u){this.realloc(4),Qo(this.buf,u,this.pos,!0,23,4),this.pos+=4},writeDouble:function(u){this.realloc(8),Qo(this.buf,u,this.pos,!0,52,8),this.pos+=8},writeBytes:function(u){var p=u.length;this.writeVarint(p),this.realloc(p);for(var _=0;_=128&&Hx(_,R,this),this.pos=_-1,this.writeVarint(R),this.pos+=R},writeMessage:function(u,p,_){this.writeTag(u,Bo.Bytes),this.writeRawMessage(p,_)},writePackedVarint:function(u,p){p.length&&this.writeMessage(u,x5,p)},writePackedSVarint:function(u,p){p.length&&this.writeMessage(u,b5,p)},writePackedBoolean:function(u,p){p.length&&this.writeMessage(u,S5,p)},writePackedFloat:function(u,p){p.length&&this.writeMessage(u,w5,p)},writePackedDouble:function(u,p){p.length&&this.writeMessage(u,T5,p)},writePackedFixed32:function(u,p){p.length&&this.writeMessage(u,cL,p)},writePackedSFixed32:function(u,p){p.length&&this.writeMessage(u,TC,p)},writePackedFixed64:function(u,p){p.length&&this.writeMessage(u,uL,p)},writePackedSFixed64:function(u,p){p.length&&this.writeMessage(u,hL,p)},writeBytesField:function(u,p){this.writeTag(u,Bo.Bytes),this.writeBytes(p)},writeFixed32Field:function(u,p){this.writeTag(u,Bo.Fixed32),this.writeFixed32(p)},writeSFixed32Field:function(u,p){this.writeTag(u,Bo.Fixed32),this.writeSFixed32(p)},writeFixed64Field:function(u,p){this.writeTag(u,Bo.Fixed64),this.writeFixed64(p)},writeSFixed64Field:function(u,p){this.writeTag(u,Bo.Fixed64),this.writeSFixed64(p)},writeVarintField:function(u,p){this.writeTag(u,Bo.Varint),this.writeVarint(p)},writeSVarintField:function(u,p){this.writeTag(u,Bo.Varint),this.writeSVarint(p)},writeStringField:function(u,p){this.writeTag(u,Bo.Bytes),this.writeString(p)},writeFloatField:function(u,p){this.writeTag(u,Bo.Fixed32),this.writeFloat(p)},writeDoubleField:function(u,p){this.writeTag(u,Bo.Fixed64),this.writeDouble(p)},writeBooleanField:function(u,p){this.writeVarintField(u,Boolean(p))}};var ev=function(u,p){var _=p.pixelRatio,R=p.version,j=p.stretchX,$=p.stretchY,J=p.content;this.paddedRect=u,this.pixelRatio=_,this.stretchX=j,this.stretchY=$,this.content=J,this.version=R},e0={tl:{configurable:!0},br:{configurable:!0},tlbr:{configurable:!0},displaySize:{configurable:!0}};e0.tl.get=function(){return[this.paddedRect.x+1,this.paddedRect.y+1]},e0.br.get=function(){return[this.paddedRect.x+this.paddedRect.w-1,this.paddedRect.y+this.paddedRect.h-1]},e0.tlbr.get=function(){return this.tl.concat(this.br)},e0.displaySize.get=function(){return[(this.paddedRect.w-2)/this.pixelRatio,(this.paddedRect.h-2)/this.pixelRatio]},Object.defineProperties(ev.prototype,e0);var rh=function(u,p){var _={},R={};this.haveRenderCallbacks=[];var j=[];this.addImages(u,_,j),this.addImages(p,R,j);var $=CC(j),J=new Vl({width:$.w||1,height:$.h||1});for(var le in u){var ye=u[le],Me=_[le].paddedRect;Vl.copy(ye.data,J,{x:0,y:0},{x:Me.x+1,y:Me.y+1},ye.data)}for(var Le in p){var ze=p[Le],Ye=R[Le].paddedRect,lt=Ye.x+1,It=Ye.y+1,Dt=ze.data.width,tr=ze.data.height;Vl.copy(ze.data,J,{x:0,y:0},{x:lt,y:It},ze.data),Vl.copy(ze.data,J,{x:0,y:tr-1},{x:lt,y:It-1},{width:Dt,height:1}),Vl.copy(ze.data,J,{x:0,y:0},{x:lt,y:It+tr},{width:Dt,height:1}),Vl.copy(ze.data,J,{x:Dt-1,y:0},{x:lt-1,y:It},{width:1,height:tr}),Vl.copy(ze.data,J,{x:0,y:0},{x:lt+Dt,y:It},{width:1,height:tr})}this.image=J,this.iconPositions=_,this.patternPositions=R};rh.prototype.addImages=function(u,p,_){for(var R in u){var j=u[R],$={x:0,y:0,w:j.data.width+2,h:j.data.height+2};_.push($),p[R]=new ev($,j),j.hasRenderCallback&&this.haveRenderCallbacks.push(R)}},rh.prototype.patchUpdatedImages=function(u,p){for(var _ in u.dispatchRenderCallbacks(this.haveRenderCallbacks),u.updatedImages)this.patchUpdatedImage(this.iconPositions[_],u.getImage(_),p),this.patchUpdatedImage(this.patternPositions[_],u.getImage(_),p)},rh.prototype.patchUpdatedImage=function(u,p,_){if(u&&p&&u.version!==p.version){u.version=p.version;var R=u.tl;_.update(p.data,void 0,{x:R[0],y:R[1]})}},Yi("ImagePosition",ev),Yi("ImageAtlas",rh);var Qf={horizontal:1,vertical:2,horizontalOnly:3},J0=function(){this.scale=1,this.fontStack="",this.imageName=null};J0.forText=function(u,p){var _=new J0;return _.scale=u||1,_.fontStack=p,_},J0.forImage=function(u){var p=new J0;return p.imageName=u,p};var wh=function(){this.text="",this.sectionIndex=[],this.sections=[],this.imageSectionID=null};function Wx(u,p,_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt,It,Dt){var tr,qt=wh.fromFeature(u,j);ze===Qf.vertical&&qt.verticalizePunctuation();var ur=gu.processBidirectionalText,Tr=gu.processStyledBidirectionalText;if(ur&&qt.sections.length===1){tr=[];for(var Nr=0,Vr=ur(qt.toString(),t2(qt,Me,$,p,R,lt,It));Nr0&&Jd>dl&&(dl=Jd)}else{var tb=xo[$s.fontStack],D_=tb&&tb[ed];if(D_&&D_.rect)lg=D_.rect,oc=D_.metrics;else{var lv=Jo[$s.fontStack],rb=lv&&lv[ed];if(!rb)continue;oc=rb.metrics}Qd=24*(Bs-$s.scale)}L_?(pn.verticalizable=!0,Na.push({glyph:ed,imageName:Op,x:ou,y:ih+Qd,vertical:L_,scale:$s.scale,fontStack:$s.fontStack,sectionIndex:Dp,metrics:oc,rect:lg}),ou+=sv*$s.scale+fl):(Na.push({glyph:ed,imageName:Op,x:ou,y:ih+Qd,vertical:L_,scale:$s.scale,fontStack:$s.fontStack,sectionIndex:Dp,metrics:oc,rect:lg}),ou+=oc.advance*$s.scale+fl)}Na.length!==0&&(su=Math.max(ou-fl,su),C5(Na,0,Na.length-1,ka,dl)),ou=0;var ib=js*Bs+dl;xu.lineOffset=Math.max(dl,nf),ih+=ib,nc=Math.max(ib,nc),++au}else ih+=js,++au}var cg,iA=ih- -17,O_=RC(hl),Fp=O_.horizontalAlign,F_=O_.verticalAlign;(function(cv,nb,of,ug,nA,ob,uv,hv,sb){var ym,k_=(nb-of)*nA;ym=ob!==uv?-hv*ug- -17:(-ug*sb+.5)*uv;for(var oA=0,N_=cv;oA=0&&R>=u&&e2[this.text.charCodeAt(R)];R--)_--;this.text=this.text.substring(u,_),this.sectionIndex=this.sectionIndex.slice(u,_)},wh.prototype.substring=function(u,p){var _=new wh;return _.text=this.text.substring(u,p),_.sectionIndex=this.sectionIndex.slice(u,p),_.sections=this.sections,_},wh.prototype.toString=function(){return this.text},wh.prototype.getMaxScale=function(){var u=this;return this.sectionIndex.reduce(function(p,_){return Math.max(p,u.sections[_].scale)},0)},wh.prototype.addTextSection=function(u,p){this.text+=u.text,this.sections.push(J0.forText(u.scale,u.fontStack||p));for(var _=this.sections.length-1,R=0;R=63743?null:++this.imageSectionID:(this.imageSectionID=57344,this.imageSectionID)};var e2={9:!0,10:!0,11:!0,12:!0,13:!0,32:!0},Jf={};function MC(u,p,_,R,j,$){if(p.imageName){var J=R[p.imageName];return J?J.displaySize[0]*p.scale*24/$+j:0}var le=_[p.fontStack],ye=le&&le[u];return ye?ye.metrics.advance*p.scale+j:0}function dL(u,p,_,R){var j=Math.pow(u-p,2);return R?u=0,ze=0,Ye=0;Ye-_/2;){if(--J<0)return!1;le-=u[J].dist($),$=u[J]}le+=u[J].dist(u[J+1]),J++;for(var ye=[],Me=0;le<_/2;){var Le=u[J],ze=u[J+1];if(!ze)return!1;var Ye=u[J-1].angleTo(Le)-Le.angleTo(ze);for(Ye=Math.abs((Ye+3*Math.PI)%(2*Math.PI)-Math.PI),ye.push({distance:le,angleDelta:Ye}),Me+=Ye;le-ye[0].distance>R;)Me-=ye.shift().angleDelta;if(Me>j)return!1;J++,le+=Le.dist(ze)}return!0}function gL(u){for(var p=0,_=0;_Me){var It=(Me-ye)/lt,Dt=Ws(ze.x,Ye.x,It),tr=Ws(ze.y,Ye.y,It),qt=new t0(Dt,tr,Ye.angleTo(ze),Le);return qt._round(),!J||mL(u,qt,le,J,p)?qt:void 0}ye+=lt}}function I5(u,p,_,R,j,$,J,le,ye){var Me=AL(R,$,J),Le=_L(R,j),ze=Le*J,Ye=u[0].x===0||u[0].x===ye||u[0].y===0||u[0].y===ye;return p-ze

=0&&es=0&&Mn=0&&Ki+Ei<=Ui){var js=new t0(es,Mn,Jo,Ji);js._round(),qt&&!mL(It,js,Tr,qt,ur)||Bn.push(js)}}Cn+=pn}return Vr||Bn.length||Nr||(Bn=lt(It,Cn/2,tr,qt,ur,Tr,Nr,!0,ri)),Bn}(u,Ye?p/2*le%p:(Le/2+2*$)*J*le%p,p,Me,_,ze,Ye,!1,ye)}function yL(u,p,_,R,j){for(var $=[],J=0;J=R&&ze.x>=R||(Le.x>=R?Le=new f(R,Le.y+(R-Le.x)/(ze.x-Le.x)*(ze.y-Le.y))._round():ze.x>=R&&(ze=new f(R,Le.y+(R-Le.x)/(ze.x-Le.x)*(ze.y-Le.y))._round()),Le.y>=j&&ze.y>=j||(Le.y>=j?Le=new f(Le.x+(j-Le.y)/(ze.y-Le.y)*(ze.x-Le.x),j)._round():ze.y>=j&&(ze=new f(Le.x+(j-Le.y)/(ze.y-Le.y)*(ze.x-Le.x),j)._round()),ye&&Le.equals(ye[ye.length-1])||$.push(ye=[Le]),ye.push(ze)))))}return $}function r2(u,p,_,R){var j=[],$=u.image,J=$.pixelRatio,le=$.paddedRect.w-2,ye=$.paddedRect.h-2,Me=u.right-u.left,Le=u.bottom-u.top,ze=$.stretchX||[[0,le]],Ye=$.stretchY||[[0,ye]],lt=function(es,Mn){return es+Mn[1]-Mn[0]},It=ze.reduce(lt,0),Dt=Ye.reduce(lt,0),tr=le-It,qt=ye-Dt,ur=0,Tr=It,Nr=0,Vr=Dt,ri=0,Ei=tr,Ui=0,Cn=qt;if($.content&&R){var Ki=$.content;ur=i2(ze,0,Ki[0]),Nr=i2(Ye,0,Ki[1]),Tr=i2(ze,Ki[0],Ki[2]),Vr=i2(Ye,Ki[1],Ki[3]),ri=Ki[0]-ur,Ui=Ki[1]-Nr,Ei=Ki[2]-Ki[0]-Tr,Cn=Ki[3]-Ki[1]-Vr}var Bn=function(es,Mn,js,hl){var Hl=tA(es.stretch-ur,Tr,Me,u.left),Wl=n2(es.fixed-ri,Ei,es.stretch,It),fl=tA(Mn.stretch-Nr,Vr,Le,u.top),ic=n2(Mn.fixed-Ui,Cn,Mn.stretch,Dt),nu=tA(js.stretch-ur,Tr,Me,u.left),ou=n2(js.fixed-ri,Ei,js.stretch,It),ih=tA(hl.stretch-Nr,Vr,Le,u.top),su=n2(hl.fixed-Ui,Cn,hl.stretch,Dt),nc=new f(Hl,fl),ka=new f(nu,fl),au=new f(nu,ih),hs=new f(Hl,ih),Ra=new f(Wl/J,ic/J),Js=new f(ou/J,su/J),Bs=p*Math.PI/180;if(Bs){var nf=Math.sin(Bs),xu=Math.cos(Bs),Na=[xu,-nf,nf,xu];nc._matMult(Na),ka._matMult(Na),hs._matMult(Na),au._matMult(Na)}var dl=es.stretch+es.fixed,ku=Mn.stretch+Mn.fixed;return{tl:nc,tr:ka,bl:hs,br:au,tex:{x:$.paddedRect.x+1+dl,y:$.paddedRect.y+1+ku,w:js.stretch+js.fixed-dl,h:hl.stretch+hl.fixed-ku},writingMode:void 0,glyphOffset:[0,0],sectionIndex:0,pixelOffsetTL:Ra,pixelOffsetBR:Js,minFontScaleX:Ei/J/Me,minFontScaleY:Cn/J/Le,isSDF:_}};if(R&&($.stretchX||$.stretchY))for(var Ji=LC(ze,tr,It),Wo=LC(Ye,qt,Dt),Xn=0;Xn0&&(lt=Math.max(10,lt),this.circleDiameter=lt)}else{var It=$.top*J-le,Dt=$.bottom*J+le,tr=$.left*J-le,qt=$.right*J+le,ur=$.collisionPadding;if(ur&&(tr-=ur[0]*J,It-=ur[1]*J,qt+=ur[2]*J,Dt+=ur[3]*J),Me){var Tr=new f(tr,It),Nr=new f(qt,It),Vr=new f(tr,Dt),ri=new f(qt,Dt),Ei=Me*Math.PI/180;Tr._rotate(Ei),Nr._rotate(Ei),Vr._rotate(Ei),ri._rotate(Ei),tr=Math.min(Tr.x,Nr.x,Vr.x,ri.x),qt=Math.max(Tr.x,Nr.x,Vr.x,ri.x),It=Math.min(Tr.y,Nr.y,Vr.y,ri.y),Dt=Math.max(Tr.y,Nr.y,Vr.y,ri.y)}u.emplaceBack(p.x,p.y,tr,It,qt,Dt,_,R,j)}this.boxEndIndex=u.length},Am=function(u,p){if(u===void 0&&(u=[]),p===void 0&&(p=vL),this.data=u,this.length=this.data.length,this.compare=p,this.length>0)for(var _=(this.length>>1)-1;_>=0;_--)this._down(_)};function vL(u,p){return up?1:0}function o2(u,p,_){p===void 0&&(p=1),_===void 0&&(_=!1);for(var R=1/0,j=1/0,$=-1/0,J=-1/0,le=u[0],ye=0;ye$)&&($=Me.x),(!ye||Me.y>J)&&(J=Me.y)}var Le=Math.min($-R,J-j),ze=Le/2,Ye=new Am([],s2);if(Le===0)return new f(R,j);for(var lt=R;lt<$;lt+=Le)for(var It=j;ItDt.d||!Dt.d)&&(Dt=qt,_&&console.log("found best %d after %d probes",Math.round(1e4*qt.d)/1e4,tr)),qt.max-Dt.d<=p||(Ye.push(new og(qt.p.x-(ze=qt.h/2),qt.p.y-ze,ze,u)),Ye.push(new og(qt.p.x+ze,qt.p.y-ze,ze,u)),Ye.push(new og(qt.p.x-ze,qt.p.y+ze,ze,u)),Ye.push(new og(qt.p.x+ze,qt.p.y+ze,ze,u)),tr+=4)}return _&&(console.log("num probes: "+tr),console.log("best distance: "+Dt.d)),Dt.p}function s2(u,p){return p.max-u.max}function og(u,p,_,R){this.p=new f(u,p),this.h=_,this.d=function(j,$){for(var J=!1,le=1/0,ye=0;ye<$.length;ye++)for(var Me=$[ye],Le=0,ze=Me.length,Ye=ze-1;Lej.y!=It.y>j.y&&j.x<(It.x-lt.x)*(j.y-lt.y)/(It.y-lt.y)+lt.x&&(J=!J),le=Math.min(le,Td(j,lt,It))}return(J?1:-1)*Math.sqrt(le)}(this.p,R),this.max=this.d+this.h*Math.SQRT2}Am.prototype.push=function(u){this.data.push(u),this.length++,this._up(this.length-1)},Am.prototype.pop=function(){if(this.length!==0){var u=this.data[0],p=this.data.pop();return this.length--,this.length>0&&(this.data[0]=p,this._down(0)),u}},Am.prototype.peek=function(){return this.data[0]},Am.prototype._up=function(u){for(var p=this.data,_=this.compare,R=p[u];u>0;){var j=u-1>>1,$=p[j];if(_(R,$)>=0)break;p[u]=$,u=j}p[u]=R},Am.prototype._down=function(u){for(var p=this.data,_=this.compare,R=this.length>>1,j=p[u];u=0)break;p[u]=J,u=$}p[u]=j};var a2=Number.POSITIVE_INFINITY;function DC(u,p){return p[1]!==a2?function(_,R,j){var $=0,J=0;switch(R=Math.abs(R),j=Math.abs(j),_){case"top-right":case"top-left":case"top":J=j-7;break;case"bottom-right":case"bottom-left":case"bottom":J=7-j}switch(_){case"top-right":case"bottom-right":case"right":$=-R;break;case"top-left":case"bottom-left":case"left":$=R}return[$,J]}(u,p[0],p[1]):function(_,R){var j=0,$=0;R<0&&(R=0);var J=R/Math.sqrt(2);switch(_){case"top-right":case"top-left":$=J-7;break;case"bottom-right":case"bottom-left":$=7-J;break;case"bottom":$=7-R;break;case"top":$=R-7}switch(_){case"top-right":case"bottom-right":j=-J;break;case"top-left":case"bottom-left":j=J;break;case"left":j=R;break;case"right":j=-R}return[j,$]}(u,p[0])}function S_(u){switch(u){case"right":case"top-right":case"bottom-right":return"right";case"left":case"top-left":case"bottom-left":return"left"}return"center"}function l2(u,p,_,R,j,$,J,le,ye,Me,Le,ze,Ye,lt,It){var Dt=function(Nr,Vr,ri,Ei,Ui,Cn,Ki,Bn){for(var Ji=Ei.layout.get("text-rotate").evaluate(Cn,{})*Math.PI/180,Wo=[],Xn=0,pn=Vr.positionedLines;Xn32640&&Qe(u.layerIds[0]+': Value for "text-size" is >= 255. Reduce your "text-size".'):tr.kind==="composite"&&((qt=[128*lt.compositeTextSizes[0].evaluate(J,{},It),128*lt.compositeTextSizes[1].evaluate(J,{},It)])[0]>32640||qt[1]>32640)&&Qe(u.layerIds[0]+': Value for "text-size" is >= 255. Reduce your "text-size".'),u.addSymbols(u.text,Dt,qt,le,$,J,Me,p,ye.lineStartIndex,ye.lineLength,Ye,It);for(var ur=0,Tr=Le;ur=0;J--)if(R.dist($[J])<_)return!0}else j[p]=[];return j[p].push(R),!1}var tv=B.VectorTileFeature.types,Xx=[{name:"a_fade_opacity",components:1,type:"Uint8",offset:0}];function u2(u,p,_,R,j,$,J,le,ye,Me,Le,ze,Ye){var lt=le?Math.min(32640,Math.round(le[0])):0,It=le?Math.min(32640,Math.round(le[1])):0;u.emplaceBack(p,_,Math.round(32*R),Math.round(32*j),$,J,(lt<<1)+(ye?1:0),It,16*Me,16*Le,256*ze,256*Ye)}function h2(u,p,_){u.emplaceBack(p.x,p.y,_),u.emplaceBack(p.x,p.y,_),u.emplaceBack(p.x,p.y,_),u.emplaceBack(p.x,p.y,_)}function C_(u){for(var p=0,_=u.sections;p<_.length;p+=1)if(eg(_[p].text))return!0;return!1}var rv=function(u){this.layoutVertexArray=new Zf,this.indexArray=new Qh,this.programConfigurations=u,this.segments=new ft,this.dynamicLayoutVertexArray=new Zd,this.opacityVertexArray=new xd,this.placedSymbolArray=new L};rv.prototype.isEmpty=function(){return this.layoutVertexArray.length===0&&this.indexArray.length===0&&this.dynamicLayoutVertexArray.length===0&&this.opacityVertexArray.length===0},rv.prototype.upload=function(u,p,_,R){this.isEmpty()||(_&&(this.layoutVertexBuffer=u.createVertexBuffer(this.layoutVertexArray,vo.members),this.indexBuffer=u.createIndexBuffer(this.indexArray,p),this.dynamicLayoutVertexBuffer=u.createVertexBuffer(this.dynamicLayoutVertexArray,Ro.members,!0),this.opacityVertexBuffer=u.createVertexBuffer(this.opacityVertexArray,Xx,!0),this.opacityVertexBuffer.itemSize=1),(_||R)&&this.programConfigurations.upload(u))},rv.prototype.destroy=function(){this.layoutVertexBuffer&&(this.layoutVertexBuffer.destroy(),this.indexBuffer.destroy(),this.programConfigurations.destroy(),this.segments.destroy(),this.dynamicLayoutVertexBuffer.destroy(),this.opacityVertexBuffer.destroy())},Yi("SymbolBuffers",rv);var iv=function(u,p,_){this.layoutVertexArray=new u,this.layoutAttributes=p,this.indexArray=new _,this.segments=new ft,this.collisionVertexArray=new Yd};iv.prototype.upload=function(u){this.layoutVertexBuffer=u.createVertexBuffer(this.layoutVertexArray,this.layoutAttributes),this.indexBuffer=u.createIndexBuffer(this.indexArray),this.collisionVertexBuffer=u.createVertexBuffer(this.collisionVertexArray,Yo.members,!0)},iv.prototype.destroy=function(){this.layoutVertexBuffer&&(this.layoutVertexBuffer.destroy(),this.indexBuffer.destroy(),this.segments.destroy(),this.collisionVertexBuffer.destroy())},Yi("CollisionBuffers",iv);var Qs=function(u){this.collisionBoxArray=u.collisionBoxArray,this.zoom=u.zoom,this.overscaling=u.overscaling,this.layers=u.layers,this.layerIds=this.layers.map(function($){return $.id}),this.index=u.index,this.pixelRatio=u.pixelRatio,this.sourceLayerIndex=u.sourceLayerIndex,this.hasPattern=!1,this.hasRTLText=!1,this.sortKeyRanges=[],this.collisionCircleArray=[],this.placementInvProjMatrix=yu([]),this.placementViewportMatrix=yu([]);var p=this.layers[0]._unevaluatedLayout._values;this.textSizeData=qx(this.zoom,p["text-size"]),this.iconSizeData=qx(this.zoom,p["icon-size"]);var _=this.layers[0].layout,R=_.get("symbol-sort-key"),j=_.get("symbol-z-order");this.canOverlap=_.get("text-allow-overlap")||_.get("icon-allow-overlap")||_.get("text-ignore-placement")||_.get("icon-ignore-placement"),this.sortFeaturesByKey=j!=="viewport-y"&&R.constantOr(1)!==void 0,this.sortFeaturesByY=(j==="viewport-y"||j==="auto"&&!this.sortFeaturesByKey)&&this.canOverlap,_.get("symbol-placement")==="point"&&(this.writingModes=_.get("text-writing-mode").map(function($){return Qf[$]})),this.stateDependentLayerIds=this.layers.filter(function($){return $.isStateDependent()}).map(function($){return $.id}),this.sourceID=u.sourceID};Qs.prototype.createArrays=function(){this.text=new rv(new Rn(this.layers,this.zoom,function(u){return/^text/.test(u)})),this.icon=new rv(new Rn(this.layers,this.zoom,function(u){return/^icon/.test(u)})),this.glyphOffsetArray=new ae,this.lineVertexArray=new ge,this.symbolInstances=new ie},Qs.prototype.calculateGlyphDependencies=function(u,p,_,R,j){for(var $=0;$0)&&($.value.kind!=="constant"||$.value.value.length>0),Me=le.value.kind!=="constant"||!!le.value.value||Object.keys(le.parameters).length>0,Le=j.get("symbol-sort-key");if(this.features=[],ye||Me){for(var ze=p.iconDependencies,Ye=p.glyphDependencies,lt=p.availableImages,It=new So(this.zoom),Dt=0,tr=u;Dt=0;for(var Jo=0,xo=Ui.sections;Jo=0;le--)$[le]={x:p[le].x,y:p[le].y,tileUnitDistanceFromAnchor:j},le>0&&(j+=p[le-1].dist(p[le]));for(var ye=0;ye0},Qs.prototype.hasIconData=function(){return this.icon.segments.get().length>0},Qs.prototype.hasDebugData=function(){return this.textCollisionBox&&this.iconCollisionBox},Qs.prototype.hasTextCollisionBoxData=function(){return this.hasDebugData()&&this.textCollisionBox.segments.get().length>0},Qs.prototype.hasIconCollisionBoxData=function(){return this.hasDebugData()&&this.iconCollisionBox.segments.get().length>0},Qs.prototype.addIndicesForPlacedSymbol=function(u,p){for(var _=u.placedSymbolArray.get(p),R=_.vertexStartIndex+4*_.numGlyphs,j=_.vertexStartIndex;j1||this.icon.segments.get().length>1)){this.symbolInstanceIndexes=this.getSortedSymbolIndexes(u),this.sortedAngle=u,this.text.indexArray.clear(),this.icon.indexArray.clear(),this.featureSortOrder=[];for(var _=0,R=this.symbolInstanceIndexes;_=0&&le.indexOf($)===J&&p.addIndicesForPlacedSymbol(p.text,$)}),j.verticalPlacedTextSymbolIndex>=0&&this.addIndicesForPlacedSymbol(this.text,j.verticalPlacedTextSymbolIndex),j.placedIconSymbolIndex>=0&&this.addIndicesForPlacedSymbol(this.icon,j.placedIconSymbolIndex),j.verticalPlacedIconSymbolIndex>=0&&this.addIndicesForPlacedSymbol(this.icon,j.verticalPlacedIconSymbolIndex)}this.text.indexBuffer&&this.text.indexBuffer.updateData(this.text.indexArray),this.icon.indexBuffer&&this.icon.indexBuffer.updateData(this.icon.indexArray)}},Yi("SymbolBucket",Qs,{omit:["layers","collisionBoxArray","features","compareText"]}),Qs.MAX_GLYPHS=65535,Qs.addDynamicAttributes=h2;var OC=new Cl({"symbol-placement":new An(ht.layout_symbol["symbol-placement"]),"symbol-spacing":new An(ht.layout_symbol["symbol-spacing"]),"symbol-avoid-edges":new An(ht.layout_symbol["symbol-avoid-edges"]),"symbol-sort-key":new nn(ht.layout_symbol["symbol-sort-key"]),"symbol-z-order":new An(ht.layout_symbol["symbol-z-order"]),"icon-allow-overlap":new An(ht.layout_symbol["icon-allow-overlap"]),"icon-ignore-placement":new An(ht.layout_symbol["icon-ignore-placement"]),"icon-optional":new An(ht.layout_symbol["icon-optional"]),"icon-rotation-alignment":new An(ht.layout_symbol["icon-rotation-alignment"]),"icon-size":new nn(ht.layout_symbol["icon-size"]),"icon-text-fit":new An(ht.layout_symbol["icon-text-fit"]),"icon-text-fit-padding":new An(ht.layout_symbol["icon-text-fit-padding"]),"icon-image":new nn(ht.layout_symbol["icon-image"]),"icon-rotate":new nn(ht.layout_symbol["icon-rotate"]),"icon-padding":new An(ht.layout_symbol["icon-padding"]),"icon-keep-upright":new An(ht.layout_symbol["icon-keep-upright"]),"icon-offset":new nn(ht.layout_symbol["icon-offset"]),"icon-anchor":new nn(ht.layout_symbol["icon-anchor"]),"icon-pitch-alignment":new An(ht.layout_symbol["icon-pitch-alignment"]),"text-pitch-alignment":new An(ht.layout_symbol["text-pitch-alignment"]),"text-rotation-alignment":new An(ht.layout_symbol["text-rotation-alignment"]),"text-field":new nn(ht.layout_symbol["text-field"]),"text-font":new nn(ht.layout_symbol["text-font"]),"text-size":new nn(ht.layout_symbol["text-size"]),"text-max-width":new nn(ht.layout_symbol["text-max-width"]),"text-line-height":new An(ht.layout_symbol["text-line-height"]),"text-letter-spacing":new nn(ht.layout_symbol["text-letter-spacing"]),"text-justify":new nn(ht.layout_symbol["text-justify"]),"text-radial-offset":new nn(ht.layout_symbol["text-radial-offset"]),"text-variable-anchor":new An(ht.layout_symbol["text-variable-anchor"]),"text-anchor":new nn(ht.layout_symbol["text-anchor"]),"text-max-angle":new An(ht.layout_symbol["text-max-angle"]),"text-writing-mode":new An(ht.layout_symbol["text-writing-mode"]),"text-rotate":new nn(ht.layout_symbol["text-rotate"]),"text-padding":new An(ht.layout_symbol["text-padding"]),"text-keep-upright":new An(ht.layout_symbol["text-keep-upright"]),"text-transform":new nn(ht.layout_symbol["text-transform"]),"text-offset":new nn(ht.layout_symbol["text-offset"]),"text-allow-overlap":new An(ht.layout_symbol["text-allow-overlap"]),"text-ignore-placement":new An(ht.layout_symbol["text-ignore-placement"]),"text-optional":new An(ht.layout_symbol["text-optional"])}),f2={paint:new Cl({"icon-opacity":new nn(ht.paint_symbol["icon-opacity"]),"icon-color":new nn(ht.paint_symbol["icon-color"]),"icon-halo-color":new nn(ht.paint_symbol["icon-halo-color"]),"icon-halo-width":new nn(ht.paint_symbol["icon-halo-width"]),"icon-halo-blur":new nn(ht.paint_symbol["icon-halo-blur"]),"icon-translate":new An(ht.paint_symbol["icon-translate"]),"icon-translate-anchor":new An(ht.paint_symbol["icon-translate-anchor"]),"text-opacity":new nn(ht.paint_symbol["text-opacity"]),"text-color":new nn(ht.paint_symbol["text-color"],{runtimeType:Ii,getOverride:function(u){return u.textColor},hasOverride:function(u){return!!u.textColor}}),"text-halo-color":new nn(ht.paint_symbol["text-halo-color"]),"text-halo-width":new nn(ht.paint_symbol["text-halo-width"]),"text-halo-blur":new nn(ht.paint_symbol["text-halo-blur"]),"text-translate":new An(ht.paint_symbol["text-translate"]),"text-translate-anchor":new An(ht.paint_symbol["text-translate-anchor"])}),layout:OC},M_=function(u){this.type=u.property.overrides?u.property.overrides.runtimeType:fi,this.defaultValue=u};M_.prototype.evaluate=function(u){if(u.formattedSection){var p=this.defaultValue.property.overrides;if(p&&p.hasOverride(u.formattedSection))return p.getOverride(u.formattedSection)}return u.feature&&u.featureState?this.defaultValue.evaluate(u.feature,u.featureState):this.defaultValue.property.specification.default},M_.prototype.eachChild=function(u){this.defaultValue.isConstant()||u(this.defaultValue.value._styleExpression.expression)},M_.prototype.outputDefined=function(){return!1},M_.prototype.serialize=function(){return null},Yi("FormatSectionOverride",M_,{omit:["defaultValue"]});var P5=function(u){function p(_){u.call(this,_,f2)}return u&&(p.__proto__=u),(p.prototype=Object.create(u&&u.prototype)).constructor=p,p.prototype.recalculate=function(_,R){if(u.prototype.recalculate.call(this,_,R),this.layout.get("icon-rotation-alignment")==="auto"&&(this.layout._values["icon-rotation-alignment"]=this.layout.get("symbol-placement")!=="point"?"map":"viewport"),this.layout.get("text-rotation-alignment")==="auto"&&(this.layout._values["text-rotation-alignment"]=this.layout.get("symbol-placement")!=="point"?"map":"viewport"),this.layout.get("text-pitch-alignment")==="auto"&&(this.layout._values["text-pitch-alignment"]=this.layout.get("text-rotation-alignment")),this.layout.get("icon-pitch-alignment")==="auto"&&(this.layout._values["icon-pitch-alignment"]=this.layout.get("icon-rotation-alignment")),this.layout.get("symbol-placement")==="point"){var j=this.layout.get("text-writing-mode");if(j){for(var $=[],J=0,le=j;J",targetMapId:R,sourceMapId:$.mapId})}}},sg.prototype.receive=function(u){var p=u.data,_=p.id;if(_&&(!p.targetMapId||this.mapId===p.targetMapId))if(p.type===""){delete this.tasks[_];var R=this.cancelCallbacks[_];delete this.cancelCallbacks[_],R&&R()}else xt()||p.mustQueue?(this.tasks[_]=p,this.taskQueue.push(_),this.invoker.trigger()):this.processTask(_,p)},sg.prototype.process=function(){if(this.taskQueue.length){var u=this.taskQueue.shift(),p=this.tasks[u];delete this.tasks[u],this.taskQueue.length&&this.invoker.trigger(),p&&this.processTask(u,p)}},sg.prototype.processTask=function(u,p){var _=this;if(p.type===""){var R=this.callbacks[u];delete this.callbacks[u],R&&(p.error?R(kl(p.error)):R(null,kl(p.data)))}else{var j=!1,$=hr(this.globalScope)?void 0:[],J=p.hasCallback?function(Le,ze){j=!0,delete _.cancelCallbacks[u],_.target.postMessage({id:u,type:"",sourceMapId:_.mapId,error:Le?ru(Le):null,data:ru(ze,$)},$)}:function(Le){j=!0},le=null,ye=kl(p.data);if(this.parent[p.type])le=this.parent[p.type](p.sourceMapId,ye,J);else if(this.parent.getWorkerSource){var Me=p.type.split(".");le=this.parent.getWorkerSource(p.sourceMapId,Me[0],ye.source)[Me[1]](ye,J)}else J(new Error("Could not find function "+p.type));!j&&le&&le.cancel&&(this.cancelCallbacks[u]=le.cancel)}},sg.prototype.remove=function(){this.invoker.remove(),this.target.removeEventListener("message",this.receive,!1)};var Wc=function(u,p){u&&(p?this.setSouthWest(u).setNorthEast(p):u.length===4?this.setSouthWest([u[0],u[1]]).setNorthEast([u[2],u[3]]):this.setSouthWest(u[0]).setNorthEast(u[1]))};Wc.prototype.setNorthEast=function(u){return this._ne=u instanceof ul?new ul(u.lng,u.lat):ul.convert(u),this},Wc.prototype.setSouthWest=function(u){return this._sw=u instanceof ul?new ul(u.lng,u.lat):ul.convert(u),this},Wc.prototype.extend=function(u){var p,_,R=this._sw,j=this._ne;if(u instanceof ul)p=u,_=u;else{if(!(u instanceof Wc))return Array.isArray(u)?u.length===4||u.every(Array.isArray)?this.extend(Wc.convert(u)):this.extend(ul.convert(u)):this;if(_=u._ne,!(p=u._sw)||!_)return this}return R||j?(R.lng=Math.min(p.lng,R.lng),R.lat=Math.min(p.lat,R.lat),j.lng=Math.max(_.lng,j.lng),j.lat=Math.max(_.lat,j.lat)):(this._sw=new ul(p.lng,p.lat),this._ne=new ul(_.lng,_.lat)),this},Wc.prototype.getCenter=function(){return new ul((this._sw.lng+this._ne.lng)/2,(this._sw.lat+this._ne.lat)/2)},Wc.prototype.getSouthWest=function(){return this._sw},Wc.prototype.getNorthEast=function(){return this._ne},Wc.prototype.getNorthWest=function(){return new ul(this.getWest(),this.getNorth())},Wc.prototype.getSouthEast=function(){return new ul(this.getEast(),this.getSouth())},Wc.prototype.getWest=function(){return this._sw.lng},Wc.prototype.getSouth=function(){return this._sw.lat},Wc.prototype.getEast=function(){return this._ne.lng},Wc.prototype.getNorth=function(){return this._ne.lat},Wc.prototype.toArray=function(){return[this._sw.toArray(),this._ne.toArray()]},Wc.prototype.toString=function(){return"LngLatBounds("+this._sw.toString()+", "+this._ne.toString()+")"},Wc.prototype.isEmpty=function(){return!(this._sw&&this._ne)},Wc.prototype.contains=function(u){var p=ul.convert(u),_=p.lng,R=p.lat,j=this._sw.lng<=_&&_<=this._ne.lng;return this._sw.lng>this._ne.lng&&(j=this._sw.lng>=_&&_>=this._ne.lng),this._sw.lat<=R&&R<=this._ne.lat&&j},Wc.convert=function(u){return!u||u instanceof Wc?u:new Wc(u)};var ul=function(u,p){if(isNaN(u)||isNaN(p))throw new Error("Invalid LngLat object: ("+u+", "+p+")");if(this.lng=+u,this.lat=+p,this.lat>90||this.lat<-90)throw new Error("Invalid LngLat latitude value: must be between -90 and 90")};ul.prototype.wrap=function(){return new ul(K(this.lng,-180,180),this.lat)},ul.prototype.toArray=function(){return[this.lng,this.lat]},ul.prototype.toString=function(){return"LngLat("+this.lng+", "+this.lat+")"},ul.prototype.distanceTo=function(u){var p=Math.PI/180,_=this.lat*p,R=u.lat*p,j=Math.sin(_)*Math.sin(R)+Math.cos(_)*Math.cos(R)*Math.cos((u.lng-this.lng)*p);return 63710088e-1*Math.acos(Math.min(j,1))},ul.prototype.toBounds=function(u){u===void 0&&(u=0);var p=360*u/40075017,_=p/Math.cos(Math.PI/180*this.lat);return new Wc(new ul(this.lng-_,this.lat-p),new ul(this.lng+_,this.lat+p))},ul.convert=function(u){if(u instanceof ul)return u;if(Array.isArray(u)&&(u.length===2||u.length===3))return new ul(Number(u[0]),Number(u[1]));if(!Array.isArray(u)&&typeof u=="object"&&u!==null)return new ul(Number("lng"in u?u.lng:u.lon),Number(u.lat));throw new Error("`LngLatLike` argument must be specified as a LngLat instance, an object {lng: , lat: }, an object {lon: , lat: }, or an array of [, ]")};var kC=2*Math.PI*63710088e-1;function NC(u){return kC*Math.cos(u*Math.PI/180)}function TL(u){return(180+u)/360}function SL(u){return(180-180/Math.PI*Math.log(Math.tan(Math.PI/4+u*Math.PI/360)))/360}function nv(u,p){return u/NC(p)}function p2(u){return 360/Math.PI*Math.atan(Math.exp((180-360*u)*Math.PI/180))-90}var P_=function(u,p,_){_===void 0&&(_=0),this.x=+u,this.y=+p,this.z=+_};P_.fromLngLat=function(u,p){p===void 0&&(p=0);var _=ul.convert(u);return new P_(TL(_.lng),SL(_.lat),nv(p,_.lat))},P_.prototype.toLngLat=function(){return new ul(360*this.x-180,p2(this.y))},P_.prototype.toAltitude=function(){return this.z*NC(p2(this.y))},P_.prototype.meterInMercatorCoordinateUnits=function(){return 1/kC*(u=p2(this.y),1/Math.cos(u*Math.PI/180));var u};var R_=function(u,p,_){this.z=u,this.x=p,this.y=_,this.key=Qx(0,u,u,p,_)};R_.prototype.equals=function(u){return this.z===u.z&&this.x===u.x&&this.y===u.y},R_.prototype.url=function(u,p){var _,R,j,$,J,le=(R=this.y,j=this.z,$=d2(256*(_=this.x),256*(R=Math.pow(2,j)-R-1),j),J=d2(256*(_+1),256*(R+1),j),$[0]+","+$[1]+","+J[0]+","+J[1]),ye=function(Me,Le,ze){for(var Ye,lt="",It=Me;It>0;It--)lt+=(Le&(Ye=1<this.canonical.z?new qc(u,this.wrap,this.canonical.z,this.canonical.x,this.canonical.y):new qc(u,this.wrap,u,this.canonical.x>>p,this.canonical.y>>p)},qc.prototype.calculateScaledKey=function(u,p){var _=this.canonical.z-u;return u>this.canonical.z?Qx(this.wrap*+p,u,this.canonical.z,this.canonical.x,this.canonical.y):Qx(this.wrap*+p,u,u,this.canonical.x>>_,this.canonical.y>>_)},qc.prototype.isChildOf=function(u){if(u.wrap!==this.wrap)return!1;var p=this.canonical.z-u.canonical.z;return u.overscaledZ===0||u.overscaledZ>p&&u.canonical.y===this.canonical.y>>p},qc.prototype.children=function(u){if(this.overscaledZ>=u)return[new qc(this.overscaledZ+1,this.wrap,this.canonical.z,this.canonical.x,this.canonical.y)];var p=this.canonical.z+1,_=2*this.canonical.x,R=2*this.canonical.y;return[new qc(p,this.wrap,p,_,R),new qc(p,this.wrap,p,_+1,R),new qc(p,this.wrap,p,_,R+1),new qc(p,this.wrap,p,_+1,R+1)]},qc.prototype.isLessThan=function(u){return this.wrapu.wrap)&&(this.overscaledZu.overscaledZ)&&(this.canonical.xu.canonical.x)&&this.canonical.y=this.dim+1||p<-1||p>=this.dim+1)throw new RangeError("out of range source coordinates for DEM data");return(p+1)*this.stride+(u+1)},ag.prototype._unpackMapbox=function(u,p,_){return(256*u*256+256*p+_)/10-1e4},ag.prototype._unpackTerrarium=function(u,p,_){return 256*u+p+_/256-32768},ag.prototype.getPixels=function(){return new Vl({width:this.stride,height:this.stride},new Uint8Array(this.data.buffer))},ag.prototype.backfillBorder=function(u,p,_){if(this.dim!==u.dim)throw new Error("dem dimension mismatch");var R=p*this.dim,j=p*this.dim+this.dim,$=_*this.dim,J=_*this.dim+this.dim;switch(p){case-1:R=j-1;break;case 1:j=R+1}switch(_){case-1:$=J-1;break;case 1:J=$+1}for(var le=-p*this.dim,ye=-_*this.dim,Me=$;Me=0&&Le[3]>=0&&le.insert(J,Le[0],Le[1],Le[2],Le[3])}},r0.prototype.loadVTLayers=function(){return this.vtLayers||(this.vtLayers=new B.VectorTile(new va(this.rawTileData)).layers,this.sourceLayerCoder=new ov(this.vtLayers?Object.keys(this.vtLayers).sort():["_geojsonTileLayer"])),this.vtLayers},r0.prototype.query=function(u,p,_,R){var j=this;this.loadVTLayers();for(var $=u.params||{},J=8192/u.tileSize/u.scale,le=Wd($.filter),ye=u.queryGeometry,Me=u.queryPadding*J,Le=CL(ye),ze=this.grid.query(Le.minX-Me,Le.minY-Me,Le.maxX+Me,Le.maxY+Me),Ye=CL(u.cameraQueryGeometry),lt=this.grid3D.query(Ye.minX-Me,Ye.minY-Me,Ye.maxX+Me,Ye.maxY+Me,function(Nr,Vr,ri,Ei){return function(Ui,Cn,Ki,Bn,Ji){for(var Wo=0,Xn=Ui;Wo=pn.x&&Ji>=pn.y)return!0}var Jo=[new f(Cn,Ki),new f(Cn,Ji),new f(Bn,Ji),new f(Bn,Ki)];if(Ui.length>2){for(var xo=0,es=Jo;xo=0)return!0;return!1}($,ze)){var Ye=this.sourceLayerCoder.decode(_),lt=this.vtLayers[Ye].feature(R);if(j.needGeometry){var It=Ta(lt,!0);if(!j.filter(new So(this.tileID.overscaledZ),It,this.tileID.canonical))return}else if(!j.filter(new So(this.tileID.overscaledZ),lt))return;for(var Dt=this.getId(lt,Ye),tr=0;trR)j=!1;else if(p)if(this.expirationTimeyr&&(u.getActor().send("enforceCacheSizeLimit",Ht),ti=0)},n.clamp=U,n.clearTileCache=function(u){var p=y.caches.delete("mapbox-tiles");u&&p.catch(u).then(function(){return u()})},n.clipLine=yL,n.clone=function(u){var p=new _s(16);return p[0]=u[0],p[1]=u[1],p[2]=u[2],p[3]=u[3],p[4]=u[4],p[5]=u[5],p[6]=u[6],p[7]=u[7],p[8]=u[8],p[9]=u[9],p[10]=u[10],p[11]=u[11],p[12]=u[12],p[13]=u[13],p[14]=u[14],p[15]=u[15],p},n.clone$1=Se,n.clone$2=function(u){var p=new _s(3);return p[0]=u[0],p[1]=u[1],p[2]=u[2],p},n.collisionCircleLayout=kn,n.config=nt,n.create=function(){var u=new _s(16);return _s!=Float32Array&&(u[1]=0,u[2]=0,u[3]=0,u[4]=0,u[6]=0,u[7]=0,u[8]=0,u[9]=0,u[11]=0,u[12]=0,u[13]=0,u[14]=0),u[0]=1,u[5]=1,u[10]=1,u[15]=1,u},n.create$1=function(){var u=new _s(9);return _s!=Float32Array&&(u[1]=0,u[2]=0,u[3]=0,u[5]=0,u[6]=0,u[7]=0),u[0]=1,u[4]=1,u[8]=1,u},n.create$2=function(){var u=new _s(4);return _s!=Float32Array&&(u[1]=0,u[2]=0),u[0]=1,u[3]=1,u},n.createCommonjsModule=o,n.createExpression=mc,n.createLayout=fa,n.createStyleLayer=function(u){return u.type==="custom"?new Zx(u):new O5[u.type](u)},n.cross=function(u,p,_){var R=p[0],j=p[1],$=p[2],J=_[0],le=_[1],ye=_[2];return u[0]=j*ye-$*le,u[1]=$*J-R*ye,u[2]=R*le-j*J,u},n.deepEqual=function u(p,_){if(Array.isArray(p)){if(!Array.isArray(_)||p.length!==_.length)return!1;for(var R=0;R0&&($=1/Math.sqrt($)),u[0]=p[0]*$,u[1]=p[1]*$,u[2]=p[2]*$,u},n.number=Ws,n.offscreenCanvasSupported=Jr,n.ortho=function(u,p,_,R,j,$,J){var le=1/(p-_),ye=1/(R-j),Me=1/($-J);return u[0]=-2*le,u[1]=0,u[2]=0,u[3]=0,u[4]=0,u[5]=-2*ye,u[6]=0,u[7]=0,u[8]=0,u[9]=0,u[10]=2*Me,u[11]=0,u[12]=(p+_)*le,u[13]=(j+R)*ye,u[14]=(J+$)*Me,u[15]=1,u},n.parseGlyphPBF=function(u){return new va(u).readFields(fL,[])},n.pbf=va,n.performSymbolLayout=function(u,p,_,R,j,$,J){u.createArrays(),u.tilePixelRatio=8192/(512*u.overscaling),u.compareText={},u.iconsNeedLinear=!1;var le=u.layers[0].layout,ye=u.layers[0]._unevaluatedLayout._values,Me={};if(u.textSizeData.kind==="composite"){var Le=u.textSizeData,ze=Le.maxZoom;Me.compositeTextSizes=[ye["text-size"].possiblyEvaluate(new So(Le.minZoom),J),ye["text-size"].possiblyEvaluate(new So(ze),J)]}if(u.iconSizeData.kind==="composite"){var Ye=u.iconSizeData,lt=Ye.maxZoom;Me.compositeIconSizes=[ye["icon-size"].possiblyEvaluate(new So(Ye.minZoom),J),ye["icon-size"].possiblyEvaluate(new So(lt),J)]}Me.layoutTextSize=ye["text-size"].possiblyEvaluate(new So(u.zoom+1),J),Me.layoutIconSize=ye["icon-size"].possiblyEvaluate(new So(u.zoom+1),J),Me.textMaxSize=ye["text-size"].possiblyEvaluate(new So(18));for(var It=24*le.get("text-line-height"),Dt=le.get("text-rotation-alignment")==="map"&&le.get("symbol-placement")!=="point",tr=le.get("text-keep-upright"),qt=le.get("text-size"),ur=function(){var Vr=Nr[Tr],ri=le.get("text-font").evaluate(Vr,{},J).join(","),Ei=qt.evaluate(Vr,{},J),Ui=Me.layoutTextSize.evaluate(Vr,{},J),Cn=Me.layoutIconSize.evaluate(Vr,{},J),Ki={horizontal:{},vertical:void 0},Bn=Vr.text,Ji=[0,0];if(Bn){var Wo=Bn.toString(),Xn=24*le.get("text-letter-spacing").evaluate(Vr,{},J),pn=function(hs){for(var Ra=0,Js=hs;Ra=8192||xm.y<0||xm.y>=8192||function(ea,el,zu,i0,td,Uu,U_,fg,ab,bm,n0,g2,A2,UC,fv,_2,VC,y2,sf,kp,pl,wm,lb,Np,RL){var jC,lA,V_,cA,o0,j_=ea.addToLineVertexArray(el,zu),GC=0,G_=0,v2=0,HC=0,WC=-1,x2=-1,zp={},cb=Et(""),dv=0,ub=0;if(fg._unevaluatedLayout.getValue("text-radial-offset")===void 0?(dv=(jC=fg.layout.get("text-offset").evaluate(pl,{},Np).map(function(dg){return 24*dg}))[0],ub=jC[1]):(dv=24*fg.layout.get("text-radial-offset").evaluate(pl,{},Np),ub=a2),ea.allowVerticalPlacement&&i0.vertical){var b2=fg.layout.get("text-rotate").evaluate(pl,{},Np)+90;cA=new rc(ab,el,bm,n0,g2,i0.vertical,A2,UC,fv,b2),U_&&(o0=new rc(ab,el,bm,n0,g2,U_,VC,y2,fv,b2))}if(td){var hb=fg.layout.get("icon-rotate").evaluate(pl,{}),qC=fg.layout.get("icon-text-fit")!=="none",$C=r2(td,hb,lb,qC),w2=U_?r2(U_,hb,lb,qC):void 0;V_=new rc(ab,el,bm,n0,g2,td,VC,y2,!1,hb),GC=4*$C.length;var XC=ea.iconSizeData,pv=null;XC.kind==="source"?(pv=[128*fg.layout.get("icon-size").evaluate(pl,{})])[0]>32640&&Qe(ea.layerIds[0]+': Value for "icon-size" is >= 255. Reduce your "icon-size".'):XC.kind==="composite"&&((pv=[128*wm.compositeIconSizes[0].evaluate(pl,{},Np),128*wm.compositeIconSizes[1].evaluate(pl,{},Np)])[0]>32640||pv[1]>32640)&&Qe(ea.layerIds[0]+': Value for "icon-size" is >= 255. Reduce your "icon-size".'),ea.addSymbols(ea.icon,$C,pv,kp,sf,pl,!1,el,j_.lineStartIndex,j_.lineLength,-1,Np),WC=ea.icon.placedSymbolArray.length-1,w2&&(G_=4*w2.length,ea.addSymbols(ea.icon,w2,pv,kp,sf,pl,Qf.vertical,el,j_.lineStartIndex,j_.lineLength,-1,Np),x2=ea.icon.placedSymbolArray.length-1)}for(var BL in i0.horizontal){var fb=i0.horizontal[BL];if(!lA){cb=Et(fb.text);var F5=fg.layout.get("text-rotate").evaluate(pl,{},Np);lA=new rc(ab,el,bm,n0,g2,fb,A2,UC,fv,F5)}var LL=fb.positionedLines.length===1;if(v2+=l2(ea,el,fb,Uu,fg,fv,pl,_2,j_,i0.vertical?Qf.horizontal:Qf.horizontalOnly,LL?Object.keys(i0.horizontal):[BL],zp,WC,wm,Np),LL)break}i0.vertical&&(HC+=l2(ea,el,i0.vertical,Uu,fg,fv,pl,_2,j_,Qf.vertical,["vertical"],zp,x2,wm,Np));var ZC=lA?lA.boxStartIndex:ea.collisionBoxArray.length,DL=lA?lA.boxEndIndex:ea.collisionBoxArray.length,k5=cA?cA.boxStartIndex:ea.collisionBoxArray.length,N5=cA?cA.boxEndIndex:ea.collisionBoxArray.length,OL=V_?V_.boxStartIndex:ea.collisionBoxArray.length,db=V_?V_.boxEndIndex:ea.collisionBoxArray.length,FL=o0?o0.boxStartIndex:ea.collisionBoxArray.length,z5=o0?o0.boxEndIndex:ea.collisionBoxArray.length,Up=-1,mv=function(dg,KC){return dg&&dg.circleDiameter?Math.max(dg.circleDiameter,KC):KC};Up=mv(lA,Up),Up=mv(cA,Up),Up=mv(V_,Up);var YC=(Up=mv(o0,Up))>-1?1:0;YC&&(Up*=RL/24),ea.glyphOffsetArray.length>=Qs.MAX_GLYPHS&&Qe("Too many glyphs being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907"),pl.sortKey!==void 0&&ea.addToSortKeyRanges(ea.symbolInstances.length,pl.sortKey),ea.symbolInstances.emplaceBack(el.x,el.y,zp.right>=0?zp.right:-1,zp.center>=0?zp.center:-1,zp.left>=0?zp.left:-1,zp.vertical||-1,WC,x2,cb,ZC,DL,k5,N5,OL,db,FL,z5,bm,v2,HC,GC,G_,YC,0,A2,dv,ub,Up)}(hs,xm,vm,Js,Bs,nf,Qd,hs.layers[0],hs.collisionBoxArray,Ra.index,Ra.sourceLayerIndex,hs.index,L_,tb,rb,ku,_m,D_,ib,lg,Ra,xu,$s,Dp,Na)};if(cg==="line")for(var F_=0,cv=yL(Ra.geometry,0,0,8192,8192);F_1){var sb=M5(hv,lv,Js.vertical||Op,Bs,24,av);sb&&Fp(hv,sb)}}else if(Ra.type==="Polygon")for(var ym=0,k_=_i(Ra.geometry,0);ym=No.maxzoom||No.visibility!=="none"&&(y(co,this.zoom,it),(Gr[No.id]=No.createBucket({index:ht.bucketLayerIDs.length,layers:co,zoom:this.zoom,pixelRatio:this.pixelRatio,overscaling:this.overscaling,collisionBoxArray:this.collisionBoxArray,sourceLayerIndex:Di,sourceID:this.source})).populate(Mi,si,this.tileID.canonical),ht.bucketLayerIDs.push(co.map(function(Ds){return Ds.id})))}}}var wo=n.mapObject(si.glyphDependencies,function(Ds){return Object.keys(Ds).map(Number)});Object.keys(wo).length?mt.send("getGlyphs",{uid:this.uid,stacks:wo},function(Ds,ho){Ot||(Ot=Ds,Ct=ho,Qn.call($t))}):Ct={};var Al=Object.keys(si.iconDependencies);Al.length?mt.send("getImages",{icons:Al,source:this.source,tileID:this.tileID,type:"icons"},function(Ds,ho){Ot||(Ot=Ds,ei=ho,Qn.call($t))}):ei={};var uo=Object.keys(si.patternDependencies);function Qn(){if(Ot)return Ke(Ot);if(Ct&&ei&&ui){var Ds=new f(Ct),ho=new n.ImageAtlas(ei,ui);for(var cs in Gr){var _l=Gr[cs];_l instanceof n.SymbolBucket?(y(_l.layers,this.zoom,it),n.performSymbolLayout(_l,Ct,Ds.positions,ei,ho.iconPositions,this.showCollisionBoxes,this.tileID.canonical)):_l.hasPattern&&(_l instanceof n.LineBucket||_l instanceof n.FillBucket||_l instanceof n.FillExtrusionBucket)&&(y(_l.layers,this.zoom,it),_l.addFeatures(si,this.tileID.canonical,ho.patternPositions))}this.status="done",Ke(null,{buckets:n.values(Gr).filter(function(zc){return!zc.isEmpty()}),featureIndex:ht,collisionBoxArray:this.collisionBoxArray,glyphAtlasImage:Ds.image,imageAtlas:ho,glyphMap:this.returnDependencies?Ct:null,iconMap:this.returnDependencies?ei:null,glyphPositions:this.returnDependencies?Ds.positions:null})}}uo.length?mt.send("getImages",{icons:uo,source:this.source,tileID:this.tileID,type:"patterns"},function(Ds,ho){Ot||(Ot=Ds,ui=ho,Qn.call($t))}):ui={},Qn.call(this)};var F=function(Be,Xe,it,mt){this.actor=Be,this.layerIndex=Xe,this.availableImages=it,this.loadVectorData=mt||M,this.loading={},this.loaded={}};F.prototype.loadTile=function(Be,Xe){var it=this,mt=Be.uid;this.loading||(this.loading={});var Ke=!!(Be&&Be.request&&Be.request.collectResourceTiming)&&new n.RequestPerformance(Be.request),$t=this.loading[mt]=new g(Be);$t.abort=this.loadVectorData(Be,function(Jt,ht){if(delete it.loading[mt],Jt||!ht)return $t.status="done",it.loaded[mt]=$t,Xe(Jt);var Ot=ht.rawData,Ct={};ht.expires&&(Ct.expires=ht.expires),ht.cacheControl&&(Ct.cacheControl=ht.cacheControl);var ei={};if(Ke){var ui=Ke.finish();ui&&(ei.resourceTiming=JSON.parse(JSON.stringify(ui)))}$t.vectorTile=ht.vectorTile,$t.parse(ht.vectorTile,it.layerIndex,it.availableImages,it.actor,function(Gr,si){if(Gr||!si)return Xe(Gr);Xe(null,n.extend({rawTileData:Ot.slice(0)},si,Ct,ei))}),it.loaded=it.loaded||{},it.loaded[mt]=$t})},F.prototype.reloadTile=function(Be,Xe){var it=this,mt=this.loaded,Ke=Be.uid,$t=this;if(mt&&mt[Ke]){var Jt=mt[Ke];Jt.showCollisionBoxes=Be.showCollisionBoxes;var ht=function(Ot,Ct){var ei=Jt.reloadCallback;ei&&(delete Jt.reloadCallback,Jt.parse(Jt.vectorTile,$t.layerIndex,it.availableImages,$t.actor,ei)),Xe(Ot,Ct)};Jt.status==="parsing"?Jt.reloadCallback=ht:Jt.status==="done"&&(Jt.vectorTile?Jt.parse(Jt.vectorTile,this.layerIndex,this.availableImages,this.actor,ht):ht())}},F.prototype.abortTile=function(Be,Xe){var it=this.loading,mt=Be.uid;it&&it[mt]&&it[mt].abort&&(it[mt].abort(),delete it[mt]),Xe()},F.prototype.removeTile=function(Be,Xe){var it=this.loaded,mt=Be.uid;it&&it[mt]&&delete it[mt],Xe()};var k=n.window.ImageBitmap,U=function(){this.loaded={}};function K(Be,Xe){if(Be.length!==0){ee(Be[0],Xe);for(var it=1;it=Math.abs(ht)?it-Ot+ht:ht-Ot+it,it=Ot}it+mt>=0!=!!Xe&&Be.reverse()}U.prototype.loadTile=function(Be,Xe){var it=Be.uid,mt=Be.encoding,Ke=Be.rawImageData,$t=k&&Ke instanceof k?this.getImageData(Ke):Ke,Jt=new n.DEMData(it,$t,mt);this.loaded=this.loaded||{},this.loaded[it]=Jt,Xe(null,Jt)},U.prototype.getImageData=function(Be){this.offscreenCanvas&&this.offscreenCanvasContext||(this.offscreenCanvas=new OffscreenCanvas(Be.width,Be.height),this.offscreenCanvasContext=this.offscreenCanvas.getContext("2d")),this.offscreenCanvas.width=Be.width,this.offscreenCanvas.height=Be.height,this.offscreenCanvasContext.drawImage(Be,0,0,Be.width,Be.height);var Xe=this.offscreenCanvasContext.getImageData(-1,-1,Be.width+2,Be.height+2);return this.offscreenCanvasContext.clearRect(0,0,this.offscreenCanvas.width,this.offscreenCanvas.height),new n.RGBAImage({width:Xe.width,height:Xe.height},Xe.data)},U.prototype.removeTile=function(Be){var Xe=this.loaded,it=Be.uid;Xe&&Xe[it]&&delete Xe[it]};var he=n.vectorTile.VectorTileFeature.prototype.toGeoJSON,xe=function(Be){this._feature=Be,this.extent=n.EXTENT,this.type=Be.type,this.properties=Be.tags,"id"in Be&&!isNaN(Be.id)&&(this.id=parseInt(Be.id,10))};xe.prototype.loadGeometry=function(){if(this._feature.type===1){for(var Be=[],Xe=0,it=this._feature.geometry;Xe>31}function Dr(Be,Xe){for(var it=Be.loadGeometry(),mt=Be.type,Ke=0,$t=0,Jt=it.length,ht=0;ht>1;(function Di(Mi,Ii,Ni,zi,po,Ls){for(;po>zi;){if(po-zi>600){var co=po-zi+1,No=Ni-zi+1,wo=Math.log(co),Al=.5*Math.exp(2*wo/3),uo=.5*Math.sqrt(wo*Al*(co-Al)/co)*(No-co/2<0?-1:1);Di(Mi,Ii,Ni,Math.max(zi,Math.floor(Ni-No*Al/co+uo)),Math.min(po,Math.floor(Ni+(co-No)*Al/co+uo)),Ls)}var Qn=Ii[2*Ni+Ls],Ds=zi,ho=po;for(mi(Mi,Ii,zi,Ni),Ii[2*po+Ls]>Qn&&mi(Mi,Ii,zi,po);DsQn;)ho--}Ii[2*zi+Ls]===Qn?mi(Mi,Ii,zi,ho):mi(Mi,Ii,++ho,po),ho<=Ni&&(zi=ho+1),Ni<=ho&&(po=ho-1)}})(ei,ui,pr,si,Or,fi%2),Ct(ei,ui,Gr,si,pr-1,fi+1),Ct(ei,ui,Gr,pr+1,Or,fi+1)}})(Jt,ht,mt,0,Jt.length-1,0)};xi.prototype.range=function(Be,Xe,it,mt){return function(Ke,$t,Jt,ht,Ot,Ct,ei){for(var ui,Gr,si=[0,Ke.length-1,0],Or=[];si.length;){var fi=si.pop(),pr=si.pop(),Di=si.pop();if(pr-Di<=ei)for(var Mi=Di;Mi<=pr;Mi++)Gr=$t[2*Mi+1],(ui=$t[2*Mi])>=Jt&&ui<=Ot&&Gr>=ht&&Gr<=Ct&&Or.push(Ke[Mi]);else{var Ii=Math.floor((Di+pr)/2);Gr=$t[2*Ii+1],(ui=$t[2*Ii])>=Jt&&ui<=Ot&&Gr>=ht&&Gr<=Ct&&Or.push(Ke[Ii]);var Ni=(fi+1)%2;(fi===0?Jt<=ui:ht<=Gr)&&(si.push(Di),si.push(Ii-1),si.push(Ni)),(fi===0?Ot>=ui:Ct>=Gr)&&(si.push(Ii+1),si.push(pr),si.push(Ni))}}return Or}(this.ids,this.coords,Be,Xe,it,mt,this.nodeSize)},xi.prototype.within=function(Be,Xe,it){return function(mt,Ke,$t,Jt,ht,Ot){for(var Ct=[0,mt.length-1,0],ei=[],ui=ht*ht;Ct.length;){var Gr=Ct.pop(),si=Ct.pop(),Or=Ct.pop();if(si-Or<=Ot)for(var fi=Or;fi<=si;fi++)un(Ke[2*fi],Ke[2*fi+1],$t,Jt)<=ui&&ei.push(mt[fi]);else{var pr=Math.floor((Or+si)/2),Di=Ke[2*pr],Mi=Ke[2*pr+1];un(Di,Mi,$t,Jt)<=ui&&ei.push(mt[pr]);var Ii=(Gr+1)%2;(Gr===0?$t-ht<=Di:Jt-ht<=Mi)&&(Ct.push(Or),Ct.push(pr-1),Ct.push(Ii)),(Gr===0?$t+ht>=Di:Jt+ht>=Mi)&&(Ct.push(pr+1),Ct.push(si),Ct.push(Ii))}}return ei}(this.ids,this.coords,Be,Xe,it,this.nodeSize)};var ii={minZoom:0,maxZoom:16,minPoints:2,radius:40,extent:512,nodeSize:64,log:!1,generateId:!1,reduce:null,map:function(Be){return Be}},Hn=function(Be){this.options=ln(Object.create(ii),Be),this.trees=new Array(this.options.maxZoom+1)};function Ve(Be,Xe,it,mt,Ke){return{x:Be,y:Xe,zoom:1/0,id:it,parentId:-1,numPoints:mt,properties:Ke}}function nt(Be,Xe){var it=Be.geometry.coordinates,mt=it[1];return{x:Xr(it[0]),y:zr(mt),zoom:1/0,index:Xe,parentId:-1}}function Ft(Be){return{type:"Feature",id:Be.id,properties:mr(Be),geometry:{type:"Point",coordinates:[(mt=Be.x,360*(mt-.5)),(Xe=Be.y,it=(180-360*Xe)*Math.PI/180,360*Math.atan(Math.exp(it))/Math.PI-90)]}};var Xe,it,mt}function mr(Be){var Xe=Be.numPoints,it=Xe>=1e4?Math.round(Xe/1e3)+"k":Xe>=1e3?Math.round(Xe/100)/10+"k":Xe;return ln(ln({},Be.properties),{cluster:!0,cluster_id:Be.id,point_count:Xe,point_count_abbreviated:it})}function Xr(Be){return Be/360+.5}function zr(Be){var Xe=Math.sin(Be*Math.PI/180),it=.5-.25*Math.log((1+Xe)/(1-Xe))/Math.PI;return it<0?0:it>1?1:it}function ln(Be,Xe){for(var it in Xe)Be[it]=Xe[it];return Be}function Nn(Be){return Be.x}function Co(Be){return Be.y}function Go(Be,Xe,it,mt,Ke,$t){var Jt=Ke-it,ht=$t-mt;if(Jt!==0||ht!==0){var Ot=((Be-it)*Jt+(Xe-mt)*ht)/(Jt*Jt+ht*ht);Ot>1?(it=Ke,mt=$t):Ot>0&&(it+=Jt*Ot,mt+=ht*Ot)}return(Jt=Be-it)*Jt+(ht=Xe-mt)*ht}function is(Be,Xe,it,mt){var Ke={id:Be===void 0?null:Be,type:Xe,geometry:it,tags:mt,minX:1/0,minY:1/0,maxX:-1/0,maxY:-1/0};return function($t){var Jt=$t.geometry,ht=$t.type;if(ht==="Point"||ht==="MultiPoint"||ht==="LineString")mo($t,Jt);else if(ht==="Polygon"||ht==="MultiLineString")for(var Ot=0;Ot0&&(Jt+=mt?(Ke*Ct-Ot*$t)/2:Math.sqrt(Math.pow(Ot-Ke,2)+Math.pow(Ct-$t,2))),Ke=Ot,$t=Ct}var ei=Xe.length-3;Xe[2]=1,function ui(Gr,si,Or,fi){for(var pr,Di=fi,Mi=Or-si>>1,Ii=Or-si,Ni=Gr[si],zi=Gr[si+1],po=Gr[Or],Ls=Gr[Or+1],co=si+3;coDi)pr=co,Di=No;else if(No===Di){var wo=Math.abs(co-Mi);wofi&&(pr-si>3&&ui(Gr,si,pr,fi),Gr[pr+2]=Di,Or-pr>3&&ui(Gr,pr,Or,fi))}(Xe,0,ei,it),Xe[ei+2]=1,Xe.size=Math.abs(Jt),Xe.start=0,Xe.end=Xe.size}function so(Be,Xe,it,mt){for(var Ke=0;Ke1?1:it}function Yt(Be,Xe,it,mt,Ke,$t,Jt,ht){if(mt/=Xe,$t>=(it/=Xe)&&Jt=mt)return null;for(var Ot=[],Ct=0;Ct=it&&Or=mt)){var fi=[];if(Gr==="Point"||Gr==="MultiPoint")Fe(ui,fi,it,mt,Ke);else if(Gr==="LineString")Ne(ui,fi,it,mt,Ke,!1,ht.lineMetrics);else if(Gr==="MultiLineString")Vt(ui,fi,it,mt,Ke,!1);else if(Gr==="Polygon")Vt(ui,fi,it,mt,Ke,!0);else if(Gr==="MultiPolygon")for(var pr=0;pr=it&&Jt<=mt&&(Xe.push(Be[$t]),Xe.push(Be[$t+1]),Xe.push(Be[$t+2]))}}function Ne(Be,Xe,it,mt,Ke,$t,Jt){for(var ht,Ot,Ct=rt(Be),ei=Ke===0?yr:fr,ui=Be.start,Gr=0;Grit&&(Ot=ei(Ct,si,Or,pr,Di,it),Jt&&(Ct.start=ui+ht*Ot)):Mi>mt?Ii=it&&(Ot=ei(Ct,si,Or,pr,Di,it),Ni=!0),Ii>mt&&Mi<=mt&&(Ot=ei(Ct,si,Or,pr,Di,mt),Ni=!0),!$t&&Ni&&(Jt&&(Ct.end=ui+ht*Ot),Xe.push(Ct),Ct=rt(Be)),Jt&&(ui+=ht)}var zi=Be.length-3;si=Be[zi],Or=Be[zi+1],fi=Be[zi+2],(Mi=Ke===0?si:Or)>=it&&Mi<=mt&&Ht(Ct,si,Or,fi),zi=Ct.length-3,$t&&zi>=3&&(Ct[zi]!==Ct[0]||Ct[zi+1]!==Ct[1])&&Ht(Ct,Ct[0],Ct[1],Ct[2]),Ct.length&&Xe.push(Ct)}function rt(Be){var Xe=[];return Xe.size=Be.size,Xe.start=Be.start,Xe.end=Be.end,Xe}function Vt(Be,Xe,it,mt,Ke,$t){for(var Jt=0;JtJt.maxX&&(Jt.maxX=ei),ui>Jt.maxY&&(Jt.maxY=ui)}return Jt}function fn(Be,Xe,it,mt){var Ke=Xe.geometry,$t=Xe.type,Jt=[];if($t==="Point"||$t==="MultiPoint")for(var ht=0;ht0&&Xe.size<(Ke?Jt:mt))it.numPoints+=Xe.length/3;else{for(var ht=[],Ot=0;OtJt)&&(it.numSimplified++,ht.push(Xe[Ot]),ht.push(Xe[Ot+1])),it.numPoints++;Ke&&function(Ct,ei){for(var ui=0,Gr=0,si=Ct.length,Or=si-2;Gr0===ei)for(Gr=0,si=Ct.length;Gr24)throw new Error("maxZoom should be in the 0-24 range");if(Xe.promoteId&&Xe.generateId)throw new Error("promoteId and generateId cannot be used together.");var mt=function(Ke,$t){var Jt=[];if(Ke.type==="FeatureCollection")for(var ht=0;ht=mt;Ct--){var ei=+Date.now();ht=this._cluster(ht,Ct),this.trees[Ct]=new xi(ht,Nn,Co,$t,Float32Array),it&&console.log("z%d: %d clusters in %dms",Ct,ht.length,+Date.now()-ei)}return it&&console.timeEnd("total time"),this},Hn.prototype.getClusters=function(Be,Xe){var it=((Be[0]+180)%360+360)%360-180,mt=Math.max(-90,Math.min(90,Be[1])),Ke=Be[2]===180?180:((Be[2]+180)%360+360)%360-180,$t=Math.max(-90,Math.min(90,Be[3]));if(Be[2]-Be[0]>=360)it=-180,Ke=180;else if(it>Ke){var Jt=this.getClusters([it,mt,180,$t],Xe),ht=this.getClusters([-180,mt,Ke,$t],Xe);return Jt.concat(ht)}for(var Ot=this.trees[this._limitZoom(Xe)],Ct=[],ei=0,ui=Ot.range(Xr(it),zr($t),Xr(Ke),zr(mt));eiXe&&(Gr+=fi.numPoints||1)}if(Gr>=$t){for(var pr=Ot.x*ui,Di=Ot.y*ui,Mi=Ke&&ui>1?this._map(Ot,!0):null,Ii=(ht<<5)+(Xe+1)+this.points.length,Ni=0,zi=ei;Ni1)for(var co=0,No=ei;co>5},Hn.prototype._getOriginZoom=function(Be){return(Be-this.points.length)%32},Hn.prototype._map=function(Be,Xe){if(Be.numPoints)return Xe?ln({},Be.properties):Be.properties;var it=this.points[Be.index].properties,mt=this.options.map(it);return Xe&&mt===it?ln({},mt):mt},bs.prototype.options={maxZoom:14,indexMaxZoom:5,indexMaxPoints:1e5,tolerance:3,extent:4096,buffer:64,lineMetrics:!1,promoteId:null,generateId:!1,debug:0},bs.prototype.splitTile=function(Be,Xe,it,mt,Ke,$t,Jt){for(var ht=[Be,Xe,it,mt],Ot=this.options,Ct=Ot.debug;ht.length;){mt=ht.pop(),it=ht.pop(),Xe=ht.pop(),Be=ht.pop();var ei=1<1&&console.time("creation"),Gr=this.tiles[ui]=Wi(Be,Xe,it,mt,Ot),this.tileCoords.push({z:Xe,x:it,y:mt}),Ct)){Ct>1&&(console.log("tile z%d-%d-%d (features: %d, points: %d, simplified: %d)",Xe,it,mt,Gr.numFeatures,Gr.numPoints,Gr.numSimplified),console.timeEnd("creation"));var si="z"+Xe;this.stats[si]=(this.stats[si]||0)+1,this.total++}if(Gr.source=Be,Ke){if(Xe===Ot.maxZoom||Xe===Ke)continue;var Or=1<1&&console.time("clipping");var fi,pr,Di,Mi,Ii,Ni,zi=.5*Ot.buffer/Ot.extent,po=.5-zi,Ls=.5+zi,co=1+zi;fi=pr=Di=Mi=null,Ii=Yt(Be,ei,it-zi,it+Ls,0,Gr.minX,Gr.maxX,Ot),Ni=Yt(Be,ei,it+po,it+co,0,Gr.minX,Gr.maxX,Ot),Be=null,Ii&&(fi=Yt(Ii,ei,mt-zi,mt+Ls,1,Gr.minY,Gr.maxY,Ot),pr=Yt(Ii,ei,mt+po,mt+co,1,Gr.minY,Gr.maxY,Ot),Ii=null),Ni&&(Di=Yt(Ni,ei,mt-zi,mt+Ls,1,Gr.minY,Gr.maxY,Ot),Mi=Yt(Ni,ei,mt+po,mt+co,1,Gr.minY,Gr.maxY,Ot),Ni=null),Ct>1&&console.timeEnd("clipping"),ht.push(fi||[],Xe+1,2*it,2*mt),ht.push(pr||[],Xe+1,2*it,2*mt+1),ht.push(Di||[],Xe+1,2*it+1,2*mt),ht.push(Mi||[],Xe+1,2*it+1,2*mt+1)}}},bs.prototype.getTile=function(Be,Xe,it){var mt=this.options,Ke=mt.extent,$t=mt.debug;if(Be<0||Be>24)return null;var Jt=1<1&&console.log("drilling down to z%d-%d-%d",Be,Xe,it);for(var Ot,Ct=Be,ei=Xe,ui=it;!Ot&&Ct>0;)Ct--,ei=Math.floor(ei/2),ui=Math.floor(ui/2),Ot=this.tiles[go(Ct,ei,ui)];return Ot&&Ot.source?($t>1&&console.log("found parent tile z%d-%d-%d",Ct,ei,ui),$t>1&&console.time("drilling down"),this.splitTile(Ot.source,Ct,ei,ui,Be,Xe,it),$t>1&&console.timeEnd("drilling down"),this.tiles[ht]?ti(this.tiles[ht],Ke):null):null};var Us=function(Be){function Xe(it,mt,Ke,$t){Be.call(this,it,mt,Ke,wr),$t&&(this.loadGeoJSON=$t)}return Be&&(Xe.__proto__=Be),(Xe.prototype=Object.create(Be&&Be.prototype)).constructor=Xe,Xe.prototype.loadData=function(it,mt){this._pendingCallback&&this._pendingCallback(null,{abandoned:!0}),this._pendingCallback=mt,this._pendingLoadDataParams=it,this._state&&this._state!=="Idle"?this._state="NeedsLoadData":(this._state="Coalescing",this._loadData())},Xe.prototype._loadData=function(){var it=this;if(this._pendingCallback&&this._pendingLoadDataParams){var mt=this._pendingCallback,Ke=this._pendingLoadDataParams;delete this._pendingCallback,delete this._pendingLoadDataParams;var $t=!!(Ke&&Ke.request&&Ke.request.collectResourceTiming)&&new n.RequestPerformance(Ke.request);this.loadGeoJSON(Ke,function(Jt,ht){if(Jt||!ht)return mt(Jt);if(typeof ht!="object")return mt(new Error("Input data given to '"+Ke.source+"' is not a valid GeoJSON object."));(function Gr(si,Or){var fi,pr=si&&si.type;if(pr==="FeatureCollection")for(fi=0;fi"u"||typeof document>"u"?"not a browser":Array.prototype&&Array.prototype.every&&Array.prototype.filter&&Array.prototype.forEach&&Array.prototype.indexOf&&Array.prototype.lastIndexOf&&Array.prototype.map&&Array.prototype.some&&Array.prototype.reduce&&Array.prototype.reduceRight&&Array.isArray?Function.prototype&&Function.prototype.bind?Object.keys&&Object.create&&Object.getPrototypeOf&&Object.getOwnPropertyNames&&Object.isSealed&&Object.isFrozen&&Object.isExtensible&&Object.getOwnPropertyDescriptor&&Object.defineProperty&&Object.defineProperties&&Object.seal&&Object.freeze&&Object.preventExtensions?"JSON"in window&&"parse"in JSON&&"stringify"in JSON?function(){if(!("Worker"in window&&"Blob"in window&&"URL"in window))return!1;var ae,ge,Pe=new Blob([""],{type:"text/javascript"}),Ue=URL.createObjectURL(Pe);try{ge=new Worker(Ue),ae=!0}catch{ae=!1}return ge&&ge.terminate(),URL.revokeObjectURL(Ue),ae}()?"Uint8ClampedArray"in window?ArrayBuffer.isView?function(){var ae=document.createElement("canvas");ae.width=ae.height=1;var ge=ae.getContext("2d");if(!ge)return!1;var Pe=ge.getImageData(0,0,1,1);return Pe&&Pe.width===ae.width}()?(L[ie=Y&&Y.failIfMajorPerformanceCaveat]===void 0&&(L[ie]=function(ae){var ge=function(Ue){var ct=document.createElement("canvas"),ft=Object.create(I.webGLContextAttributes);return ft.failIfMajorPerformanceCaveat=Ue,ct.probablySupportsContext?ct.probablySupportsContext("webgl",ft)||ct.probablySupportsContext("experimental-webgl",ft):ct.supportsContext?ct.supportsContext("webgl",ft)||ct.supportsContext("experimental-webgl",ft):ct.getContext("webgl",ft)||ct.getContext("experimental-webgl",ft)}(ae);if(!ge)return!1;var Pe=ge.createShader(ge.VERTEX_SHADER);return!(!Pe||ge.isContextLost())&&(ge.shaderSource(Pe,"void main() {}"),ge.compileShader(Pe),ge.getShaderParameter(Pe,ge.COMPILE_STATUS)===!0)}(ie)),L[ie]?void 0:"insufficient WebGL support"):"insufficient Canvas/getImageData support":"insufficient ArrayBuffer support":"insufficient Uint8ClampedArray support":"insufficient worker support":"insufficient JSON support":"insufficient Object support":"insufficient Function support":"insufficent Array support";var ie}w.exports?w.exports=I:window&&(window.mapboxgl=window.mapboxgl||{},window.mapboxgl.supported=I,window.mapboxgl.notSupportedReason=S);var L={};I.webGLContextAttributes={antialias:!1,alpha:!0,stencil:!0,depth:!0}}),s={create:function(w,I,S){var L=n.window.document.createElement(w);return I!==void 0&&(L.className=I),S&&S.appendChild(L),L},createNS:function(w,I){return n.window.document.createElementNS(w,I)}},c=n.window.document&&n.window.document.documentElement.style;function f(w){if(!c)return w[0];for(var I=0;I=0?0:w.button},s.remove=function(w){w.parentNode&&w.parentNode.removeChild(w)};var ee=function(w){function I(){w.call(this),this.images={},this.updatedImages={},this.callbackDispatchedThisFrame={},this.loaded=!1,this.requestors=[],this.patterns={},this.atlasImage=new n.RGBAImage({width:1,height:1}),this.dirty=!0}return w&&(I.__proto__=w),(I.prototype=Object.create(w&&w.prototype)).constructor=I,I.prototype.isLoaded=function(){return this.loaded},I.prototype.setLoaded=function(S){if(this.loaded!==S&&(this.loaded=S,S)){for(var L=0,Y=this.requestors;L=0?1.2:1))}function Oe(w,I,S,L,Y,ie,ae){for(var ge=0;ge65535)Pe(new Error("glyphs > 65535 not supported"));else if(ft.ranges[ut])Pe(null,{stack:Ue,id:ct,glyph:gt});else{var Pt=ft.requests[ut];Pt||(Pt=ft.requests[ut]=[],He.loadGlyphRange(Ue,ut,S.url,S.requestManager,function(Wt,Et){if(Et){for(var Zt in Et)S._doesCharSupportLocalGlyph(+Zt)||(ft.glyphs[+Zt]=Et[+Zt]);ft.ranges[ut]=!0}for(var ir=0,dr=Pt;ir1&&(ge=w[++ae]);var Ue=Math.abs(Pe-ge.left),ct=Math.abs(Pe-ge.right),ft=Math.min(Ue,ct),gt=void 0,ut=Y/S*(L+1);if(ge.isDash){var Pt=L-Math.abs(ut);gt=Math.sqrt(ft*ft+Pt*Pt)}else gt=L-Math.sqrt(ft*ft+ut*ut);this.data[ie+Pe]=Math.max(0,Math.min(255,gt+128))}},Qe.prototype.addRegularDash=function(w){for(var I=w.length-1;I>=0;--I){var S=w[I],L=w[I+1];S.zeroLength?w.splice(I,1):L&&L.isDash===S.isDash&&(L.left=S.left,w.splice(I,1))}var Y=w[0],ie=w[w.length-1];Y.isDash===ie.isDash&&(Y.left=ie.left-this.width,ie.right=Y.right+this.width);for(var ae=this.width*this.nextRow,ge=0,Pe=w[ge],Ue=0;Ue1&&(Pe=w[++ge]);var ct=Math.abs(Ue-Pe.left),ft=Math.abs(Ue-Pe.right),gt=Math.min(ct,ft);this.data[ae+Ue]=Math.max(0,Math.min(255,(Pe.isDash?gt:-gt)+128))}},Qe.prototype.addDash=function(w,I){var S=I?7:0,L=2*S+1;if(this.nextRow+L>this.height)return n.warnOnce("LineAtlas out of space"),null;for(var Y=0,ie=0;ie=S&&w.x=L&&w.y0&&(Ue[new n.OverscaledTileID(S.overscaledZ,ae,L.z,ie,L.y-1).key]={backfilled:!1},Ue[new n.OverscaledTileID(S.overscaledZ,S.wrap,L.z,L.x,L.y-1).key]={backfilled:!1},Ue[new n.OverscaledTileID(S.overscaledZ,Pe,L.z,ge,L.y-1).key]={backfilled:!1}),L.y+10&&(Y.resourceTiming=S._resourceTiming,S._resourceTiming=[]),S.fire(new n.Event("data",Y))}})},I.prototype.onAdd=function(S){this.map=S,this.load()},I.prototype.setData=function(S){var L=this;return this._data=S,this.fire(new n.Event("dataloading",{dataType:"source"})),this._updateWorkerData(function(Y){if(Y)L.fire(new n.ErrorEvent(Y));else{var ie={dataType:"source",sourceDataType:"content"};L._collectResourceTiming&&L._resourceTiming&&L._resourceTiming.length>0&&(ie.resourceTiming=L._resourceTiming,L._resourceTiming=[]),L.fire(new n.Event("data",ie))}}),this},I.prototype.getClusterExpansionZoom=function(S,L){return this.actor.send("geojson.getClusterExpansionZoom",{clusterId:S,source:this.id},L),this},I.prototype.getClusterChildren=function(S,L){return this.actor.send("geojson.getClusterChildren",{clusterId:S,source:this.id},L),this},I.prototype.getClusterLeaves=function(S,L,Y,ie){return this.actor.send("geojson.getClusterLeaves",{source:this.id,clusterId:S,limit:L,offset:Y},ie),this},I.prototype._updateWorkerData=function(S){var L=this;this._loaded=!1;var Y=n.extend({},this.workerOptions),ie=this._data;typeof ie=="string"?(Y.request=this.map._requestManager.transformRequest(n.browser.resolveURL(ie),n.ResourceType.Source),Y.request.collectResourceTiming=this._collectResourceTiming):Y.data=JSON.stringify(ie),this.actor.send(this.type+".loadData",Y,function(ae,ge){L._removed||ge&&ge.abandoned||(L._loaded=!0,ge&&ge.resourceTiming&&ge.resourceTiming[L.id]&&(L._resourceTiming=ge.resourceTiming[L.id].slice(0)),L.actor.send(L.type+".coalesce",{source:Y.source},null),S(ae))})},I.prototype.loaded=function(){return this._loaded},I.prototype.loadTile=function(S,L){var Y=this,ie=S.actor?"reloadTile":"loadTile";S.actor=this.actor,S.request=this.actor.send(ie,{type:this.type,uid:S.uid,tileID:S.tileID,zoom:S.tileID.overscaledZ,maxZoom:this.maxzoom,tileSize:this.tileSize,source:this.id,pixelRatio:n.browser.devicePixelRatio,showCollisionBoxes:this.map.showCollisionBoxes,promoteId:this.promoteId},function(ae,ge){return delete S.request,S.unloadVectorData(),S.aborted?L(null):ae?L(ae):(S.loadVectorData(ge,Y.map.painter,ie==="reloadTile"),L(null))})},I.prototype.abortTile=function(S){S.request&&(S.request.cancel(),delete S.request),S.aborted=!0},I.prototype.unloadTile=function(S){S.unloadVectorData(),this.actor.send("removeTile",{uid:S.uid,type:this.type,source:this.id})},I.prototype.onRemove=function(){this._removed=!0,this.actor.send("removeSource",{type:this.type,source:this.id})},I.prototype.serialize=function(){return n.extend({},this._options,{type:this.type,data:this._data})},I.prototype.hasTransition=function(){return!1},I}(n.Evented),bi=n.createLayout([{name:"a_pos",type:"Int16",components:2},{name:"a_texture_pos",type:"Int16",components:2}]),un=function(w){function I(S,L,Y,ie){w.call(this),this.id=S,this.dispatcher=Y,this.coordinates=L.coordinates,this.type="image",this.minzoom=0,this.maxzoom=22,this.tileSize=512,this.tiles={},this._loaded=!1,this.setEventedParent(ie),this.options=L}return w&&(I.__proto__=w),(I.prototype=Object.create(w&&w.prototype)).constructor=I,I.prototype.load=function(S,L){var Y=this;this._loaded=!1,this.fire(new n.Event("dataloading",{dataType:"source"})),this.url=this.options.url,n.getImage(this.map._requestManager.transformRequest(this.url,n.ResourceType.Image),function(ie,ae){Y._loaded=!0,ie?Y.fire(new n.ErrorEvent(ie)):ae&&(Y.image=ae,S&&(Y.coordinates=S),L&&L(),Y._finishLoading())})},I.prototype.loaded=function(){return this._loaded},I.prototype.updateImage=function(S){var L=this;return this.image&&S.url?(this.options.url=S.url,this.load(S.coordinates,function(){L.texture=null}),this):this},I.prototype._finishLoading=function(){this.map&&(this.setCoordinates(this.coordinates),this.fire(new n.Event("data",{dataType:"source",sourceDataType:"metadata"})))},I.prototype.onAdd=function(S){this.map=S,this.load()},I.prototype.setCoordinates=function(S){var L=this;this.coordinates=S;var Y=S.map(n.MercatorCoordinate.fromLngLat);this.tileID=function(ae){for(var ge=1/0,Pe=1/0,Ue=-1/0,ct=-1/0,ft=0,gt=ae;ftL.end(0)?this.fire(new n.ErrorEvent(new n.ValidationError("sources."+this.id,null,"Playback for this video can be set only between the "+L.start(0)+" and "+L.end(0)+"-second mark."))):this.video.currentTime=S}},I.prototype.getVideo=function(){return this.video},I.prototype.onAdd=function(S){this.map||(this.map=S,this.load(),this.video&&(this.video.play(),this.setCoordinates(this.coordinates)))},I.prototype.prepare=function(){if(!(Object.keys(this.tiles).length===0||this.video.readyState<2)){var S=this.map.painter.context,L=S.gl;for(var Y in this.boundsBuffer||(this.boundsBuffer=S.createVertexBuffer(this._boundsArray,bi.members)),this.boundsSegments||(this.boundsSegments=n.SegmentVector.simpleSegment(0,0,4,2)),this.texture?this.video.paused||(this.texture.bind(L.LINEAR,L.CLAMP_TO_EDGE),L.texSubImage2D(L.TEXTURE_2D,0,0,0,L.RGBA,L.UNSIGNED_BYTE,this.video)):(this.texture=new n.Texture(S,this.video,L.RGBA),this.texture.bind(L.LINEAR,L.CLAMP_TO_EDGE)),this.tiles){var ie=this.tiles[Y];ie.state!=="loaded"&&(ie.state="loaded",ie.texture=this.texture)}}},I.prototype.serialize=function(){return{type:"video",urls:this.urls,coordinates:this.coordinates}},I.prototype.hasTransition=function(){return this.video&&!this.video.paused},I}(un),Xi=function(w){function I(S,L,Y,ie){w.call(this,S,L,Y,ie),L.coordinates?Array.isArray(L.coordinates)&&L.coordinates.length===4&&!L.coordinates.some(function(ae){return!Array.isArray(ae)||ae.length!==2||ae.some(function(ge){return typeof ge!="number"})})||this.fire(new n.ErrorEvent(new n.ValidationError("sources."+S,null,'"coordinates" property must be an array of 4 longitude/latitude array pairs'))):this.fire(new n.ErrorEvent(new n.ValidationError("sources."+S,null,'missing required property "coordinates"'))),L.animate&&typeof L.animate!="boolean"&&this.fire(new n.ErrorEvent(new n.ValidationError("sources."+S,null,'optional "animate" property must be a boolean value'))),L.canvas?typeof L.canvas=="string"||L.canvas instanceof n.window.HTMLCanvasElement||this.fire(new n.ErrorEvent(new n.ValidationError("sources."+S,null,'"canvas" must be either a string representing the ID of the canvas element from which to read, or an HTMLCanvasElement instance'))):this.fire(new n.ErrorEvent(new n.ValidationError("sources."+S,null,'missing required property "canvas"'))),this.options=L,this.animate=L.animate===void 0||L.animate}return w&&(I.__proto__=w),(I.prototype=Object.create(w&&w.prototype)).constructor=I,I.prototype.load=function(){this._loaded=!0,this.canvas||(this.canvas=this.options.canvas instanceof n.window.HTMLCanvasElement?this.options.canvas:n.window.document.getElementById(this.options.canvas)),this.width=this.canvas.width,this.height=this.canvas.height,this._hasInvalidDimensions()?this.fire(new n.ErrorEvent(new Error("Canvas dimensions cannot be less than or equal to zero."))):(this.play=function(){this._playing=!0,this.map.triggerRepaint()},this.pause=function(){this._playing&&(this.prepare(),this._playing=!1)},this._finishLoading())},I.prototype.getCanvas=function(){return this.canvas},I.prototype.onAdd=function(S){this.map=S,this.load(),this.canvas&&this.animate&&this.play()},I.prototype.onRemove=function(){this.pause()},I.prototype.prepare=function(){var S=!1;if(this.canvas.width!==this.width&&(this.width=this.canvas.width,S=!0),this.canvas.height!==this.height&&(this.height=this.canvas.height,S=!0),!this._hasInvalidDimensions()&&Object.keys(this.tiles).length!==0){var L=this.map.painter.context,Y=L.gl;for(var ie in this.boundsBuffer||(this.boundsBuffer=L.createVertexBuffer(this._boundsArray,bi.members)),this.boundsSegments||(this.boundsSegments=n.SegmentVector.simpleSegment(0,0,4,2)),this.texture?(S||this._playing)&&this.texture.update(this.canvas,{premultiply:!0}):this.texture=new n.Texture(L,this.canvas,Y.RGBA,{premultiply:!0}),this.tiles){var ae=this.tiles[ie];ae.state!=="loaded"&&(ae.state="loaded",ae.texture=this.texture)}}},I.prototype.serialize=function(){return{type:"canvas",coordinates:this.coordinates}},I.prototype.hasTransition=function(){return this._playing},I.prototype._hasInvalidDimensions=function(){for(var S=0,L=[this.canvas.width,this.canvas.height];Sthis.max){var ae=this._getAndRemoveByKey(this.order[0]);ae&&this.onRemove(ae)}return this},nt.prototype.has=function(w){return w.wrapped().key in this.data},nt.prototype.getAndRemove=function(w){return this.has(w)?this._getAndRemoveByKey(w.wrapped().key):null},nt.prototype._getAndRemoveByKey=function(w){var I=this.data[w].shift();return I.timeout&&clearTimeout(I.timeout),this.data[w].length===0&&delete this.data[w],this.order.splice(this.order.indexOf(w),1),I.value},nt.prototype.getByKey=function(w){var I=this.data[w];return I?I[0].value:null},nt.prototype.get=function(w){return this.has(w)?this.data[w.wrapped().key][0].value:null},nt.prototype.remove=function(w,I){if(!this.has(w))return this;var S=w.wrapped().key,L=I===void 0?0:this.data[S].indexOf(I),Y=this.data[S][L];return this.data[S].splice(L,1),Y.timeout&&clearTimeout(Y.timeout),this.data[S].length===0&&delete this.data[S],this.onRemove(Y.value),this.order.splice(this.order.indexOf(S),1),this},nt.prototype.setMaxSize=function(w){for(this.max=w;this.order.length>this.max;){var I=this._getAndRemoveByKey(this.order[0]);I&&this.onRemove(I)}return this},nt.prototype.filter=function(w){var I=[];for(var S in this.data)for(var L=0,Y=this.data[S];L1||(Math.abs(ct)>1&&(Math.abs(ct+gt)===1?ct+=gt:Math.abs(ct-gt)===1&&(ct-=gt)),Ue.dem&&Pe.dem&&(Pe.dem.backfillBorder(Ue.dem,ct,ft),Pe.neighboringTiles&&Pe.neighboringTiles[ut]&&(Pe.neighboringTiles[ut].backfilled=!0)))}},I.prototype.getTile=function(S){return this.getTileByID(S.key)},I.prototype.getTileByID=function(S){return this._tiles[S]},I.prototype._retainLoadedChildren=function(S,L,Y,ie){for(var ae in this._tiles){var ge=this._tiles[ae];if(!(ie[ae]||!ge.hasData()||ge.tileID.overscaledZ<=L||ge.tileID.overscaledZ>Y)){for(var Pe=ge.tileID;ge&&ge.tileID.overscaledZ>L+1;){var Ue=ge.tileID.scaledTo(ge.tileID.overscaledZ-1);(ge=this._tiles[Ue.key])&&ge.hasData()&&(Pe=Ue)}for(var ct=Pe;ct.overscaledZ>L;)if(S[(ct=ct.scaledTo(ct.overscaledZ-1)).key]){ie[Pe.key]=Pe;break}}}},I.prototype.findLoadedParent=function(S,L){if(S.key in this._loadedParentTiles){var Y=this._loadedParentTiles[S.key];return Y&&Y.tileID.overscaledZ>=L?Y:null}for(var ie=S.overscaledZ-1;ie>=L;ie--){var ae=S.scaledTo(ie),ge=this._getLoadedTile(ae);if(ge)return ge}},I.prototype._getLoadedTile=function(S){var L=this._tiles[S.key];return L&&L.hasData()?L:this._cache.getByKey(S.wrapped().key)},I.prototype.updateCacheSize=function(S){var L=Math.ceil(S.width/this._source.tileSize)+1,Y=Math.ceil(S.height/this._source.tileSize)+1,ie=Math.floor(L*Y*5),ae=typeof this._maxTileCacheSize=="number"?Math.min(this._maxTileCacheSize,ie):ie;this._cache.setMaxSize(ae)},I.prototype.handleWrapJump=function(S){var L=Math.round((S-(this._prevLng===void 0?S:this._prevLng))/360);if(this._prevLng=S,L){var Y={};for(var ie in this._tiles){var ae=this._tiles[ie];ae.tileID=ae.tileID.unwrapTo(ae.tileID.wrap+L),Y[ae.tileID.key]=ae}for(var ge in this._tiles=Y,this._timers)clearTimeout(this._timers[ge]),delete this._timers[ge];for(var Pe in this._tiles)this._setTileReloadTimer(Pe,this._tiles[Pe])}},I.prototype.update=function(S){var L=this;if(this.transform=S,this._sourceLoaded&&!this._paused){var Y;this.updateCacheSize(S),this.handleWrapJump(this.transform.center.lng),this._coveredTiles={},this.used?this._source.tileID?Y=S.getVisibleUnwrappedCoordinates(this._source.tileID).map(function(ai){return new n.OverscaledTileID(ai.canonical.z,ai.wrap,ai.canonical.z,ai.canonical.x,ai.canonical.y)}):(Y=S.coveringTiles({tileSize:this._source.tileSize,minzoom:this._source.minzoom,maxzoom:this._source.maxzoom,roundZoom:this._source.roundZoom,reparseOverscaled:this._source.reparseOverscaled}),this._source.hasTile&&(Y=Y.filter(function(ai){return L._source.hasTile(ai)}))):Y=[];var ie=S.coveringZoomLevel(this._source),ae=Math.max(ie-I.maxOverzooming,this._source.minzoom),ge=Math.max(ie+I.maxUnderzooming,this._source.minzoom),Pe=this._updateRetainedTiles(Y,ie);if(ei(this._source.type)){for(var Ue={},ct={},ft=0,gt=Object.keys(Pe);ftthis._source.maxzoom){var Zt=Wt.children(this._source.maxzoom)[0],ir=this.getTile(Zt);if(ir&&ir.hasData()){Y[Zt.key]=Zt;continue}}else{var dr=Wt.children(this._source.maxzoom);if(Y[dr[0].key]&&Y[dr[1].key]&&Y[dr[2].key]&&Y[dr[3].key])continue}for(var kr=Et.wasRequested(),Br=Wt.overscaledZ-1;Br>=ae;--Br){var Wr=Wt.scaledTo(Br);if(ie[Wr.key]||(ie[Wr.key]=!0,!(Et=this.getTile(Wr))&&kr&&(Et=this._addTile(Wr)),Et&&(Y[Wr.key]=Wr,kr=Et.wasRequested(),Et.hasData())))break}}}return Y},I.prototype._updateLoadedParentTileCache=function(){for(var S in this._loadedParentTiles={},this._tiles){for(var L=[],Y=void 0,ie=this._tiles[S].tileID;ie.overscaledZ>0;){if(ie.key in this._loadedParentTiles){Y=this._loadedParentTiles[ie.key];break}L.push(ie.key);var ae=ie.scaledTo(ie.overscaledZ-1);if(Y=this._getLoadedTile(ae))break;ie=ae}for(var ge=0,Pe=L;ge0||(L.hasData()&&L.state!=="reloading"?this._cache.add(L.tileID,L,L.getExpiryTimeout()):(L.aborted=!0,this._abortTile(L),this._unloadTile(L))))},I.prototype.clearTiles=function(){for(var S in this._shouldReloadOnResume=!1,this._paused=!1,this._tiles)this._removeTile(S);this._cache.reset()},I.prototype.tilesIn=function(S,L,Y){var ie=this,ae=[],ge=this.transform;if(!ge)return ae;for(var Pe=Y?ge.getCameraQueryGeometry(S):S,Ue=S.map(function(Br){return ge.pointCoordinate(Br)}),ct=Pe.map(function(Br){return ge.pointCoordinate(Br)}),ft=this.getIds(),gt=1/0,ut=1/0,Pt=-1/0,Wt=-1/0,Et=0,Zt=ct;Et=0&&$i[1].y+Si>=0){var bn=Ue.map(function(xn){return ai.getTilePoint(xn)}),Tn=ct.map(function(xn){return ai.getTilePoint(xn)});ae.push({tile:Wr,tileID:ai,queryGeometry:bn,cameraQueryGeometry:Tn,scale:Ci})}}},kr=0;kr=n.browser.now())return!0}return!1},I.prototype.setFeatureState=function(S,L,Y){this._state.updateState(S=S||"_geojsonTileLayer",L,Y)},I.prototype.removeFeatureState=function(S,L,Y){this._state.removeFeatureState(S=S||"_geojsonTileLayer",L,Y)},I.prototype.getFeatureState=function(S,L){return this._state.getState(S=S||"_geojsonTileLayer",L)},I.prototype.setDependencies=function(S,L,Y){var ie=this._tiles[S];ie&&ie.setDependencies(L,Y)},I.prototype.reloadTilesForDependencies=function(S,L){for(var Y in this._tiles)this._tiles[Y].hasDependency(S,L)&&this._reloadTile(Y,"reloading");this._cache.filter(function(ie){return!ie.hasDependency(S,L)})},I}(n.Evented);function Ct(w,I){var S=Math.abs(2*w.wrap)-+(w.wrap<0),L=Math.abs(2*I.wrap)-+(I.wrap<0);return w.overscaledZ-I.overscaledZ||L-S||I.canonical.y-w.canonical.y||I.canonical.x-w.canonical.x}function ei(w){return w==="raster"||w==="image"||w==="video"}function ui(){return new n.window.Worker(Jh.workerUrl)}Ot.maxOverzooming=10,Ot.maxUnderzooming=3;var Gr="mapboxgl_preloaded_worker_pool",si=function(){this.active={}};si.prototype.acquire=function(w){if(!this.workers)for(this.workers=[];this.workers.length0?(L-ie)/ae:0;return this.points[Y].mult(1-ge).add(this.points[I].mult(ge))};var uo=function(w,I,S){var L=this.boxCells=[],Y=this.circleCells=[];this.xCellCount=Math.ceil(w/S),this.yCellCount=Math.ceil(I/S);for(var ie=0;ie=-I[0]&&S<=I[0]&&L>=-I[1]&&L<=I[1]}function zc(w,I,S,L,Y,ie,ae,ge){var Pe=L?w.textSizeData:w.iconSizeData,Ue=n.evaluateSizeForZoom(Pe,S.transform.zoom),ct=[256/S.width*2+1,256/S.height*2+1],ft=L?w.text.dynamicLayoutVertexArray:w.icon.dynamicLayoutVertexArray;ft.clear();for(var gt=w.lineVertexArray,ut=L?w.text.placedSymbolArray:w.icon.placedSymbolArray,Pt=S.transform.width/S.transform.height,Wt=!1,Et=0;EtMath.abs(S.x-I.x)*L?{useVertical:!0}:(w===n.WritingMode.vertical?I.yS.x)?{needsFlipping:!0}:null}function md(w,I,S,L,Y,ie,ae,ge,Pe,Ue,ct,ft,gt,ut){var Pt,Wt=I/24,Et=w.lineOffsetX*Wt,Zt=w.lineOffsetY*Wt;if(w.numGlyphs>1){var ir=w.glyphStartIndex+w.numGlyphs,dr=w.lineStartIndex,kr=w.lineStartIndex+w.lineLength,Br=yl(Wt,ge,Et,Zt,S,ct,ft,w,Pe,ie,gt);if(!Br)return{notEnoughRoom:!0};var Wr=ho(Br.first.point,ae).point,ai=ho(Br.last.point,ae).point;if(L&&!S){var Ci=Ya(w.writingMode,Wr,ai,ut);if(Ci)return Ci}Pt=[Br.first];for(var Si=w.glyphStartIndex+1;Si0?xn.point:os(ft,Tn,$i,1,Y),qi=Ya(w.writingMode,$i,zo,ut);if(qi)return qi}var Sn=la(Wt*ge.getoffsetX(w.glyphStartIndex),Et,Zt,S,ct,ft,w.segment,w.lineStartIndex,w.lineStartIndex+w.lineLength,Pe,ie,gt);if(!Sn)return{notEnoughRoom:!0};Pt=[Sn]}for(var $n=0,Hi=Pt;$n0?1:-1,Pt=0;L&&(ut*=-1,Pt=Math.PI),ut<0&&(Pt+=Math.PI);for(var Wt=ut>0?ge+ae:ge+ae+1,Et=Y,Zt=Y,ir=0,dr=0,kr=Math.abs(gt),Br=[];ir+dr<=kr;){if((Wt+=ut)=Pe)return null;if(Zt=Et,Br.push(Et),(Et=ft[Wt])===void 0){var Wr=new n.Point(Ue.getx(Wt),Ue.gety(Wt)),ai=ho(Wr,ct);if(ai.signedDistanceFromCamera>0)Et=ft[Wt]=ai.point;else{var Ci=Wt-ut;Et=os(ir===0?ie:new n.Point(Ue.getx(Ci),Ue.gety(Ci)),Wr,Zt,kr-ir+1,ct)}}ir+=dr,dr=Zt.dist(Et)}var Si=(kr-ir)/dr,$i=Et.sub(Zt),bn=$i.mult(Si)._add(Zt);bn._add($i._unit()._perp()._mult(S*ut));var Tn=Pt+Math.atan2(Et.y-Zt.y,Et.x-Zt.x);return Br.push(bn),{point:bn,angle:Tn,path:Br}}uo.prototype.keysLength=function(){return this.boxKeys.length+this.circleKeys.length},uo.prototype.insert=function(w,I,S,L,Y){this._forEachCell(I,S,L,Y,this._insertBoxCell,this.boxUid++),this.boxKeys.push(w),this.bboxes.push(I),this.bboxes.push(S),this.bboxes.push(L),this.bboxes.push(Y)},uo.prototype.insertCircle=function(w,I,S,L){this._forEachCell(I-L,S-L,I+L,S+L,this._insertCircleCell,this.circleUid++),this.circleKeys.push(w),this.circles.push(I),this.circles.push(S),this.circles.push(L)},uo.prototype._insertBoxCell=function(w,I,S,L,Y,ie){this.boxCells[Y].push(ie)},uo.prototype._insertCircleCell=function(w,I,S,L,Y,ie){this.circleCells[Y].push(ie)},uo.prototype._query=function(w,I,S,L,Y,ie){if(S<0||w>this.width||L<0||I>this.height)return!Y&&[];var ae=[];if(w<=0&&I<=0&&this.width<=S&&this.height<=L){if(Y)return!0;for(var ge=0;ge0:ae},uo.prototype._queryCircle=function(w,I,S,L,Y){var ie=w-S,ae=w+S,ge=I-S,Pe=I+S;if(ae<0||ie>this.width||Pe<0||ge>this.height)return!L&&[];var Ue=[];return this._forEachCell(ie,ge,ae,Pe,this._queryCellCircle,Ue,{hitTest:L,circle:{x:w,y:I,radius:S},seenUids:{box:{},circle:{}}},Y),L?Ue.length>0:Ue},uo.prototype.query=function(w,I,S,L,Y){return this._query(w,I,S,L,!1,Y)},uo.prototype.hitTest=function(w,I,S,L,Y){return this._query(w,I,S,L,!0,Y)},uo.prototype.hitTestCircle=function(w,I,S,L){return this._queryCircle(w,I,S,!0,L)},uo.prototype._queryCell=function(w,I,S,L,Y,ie,ae,ge){var Pe=ae.seenUids,Ue=this.boxCells[Y];if(Ue!==null)for(var ct=this.bboxes,ft=0,gt=Ue;ft=ct[Pt+0]&&L>=ct[Pt+1]&&(!ge||ge(this.boxKeys[ut]))){if(ae.hitTest)return ie.push(!0),!0;ie.push({key:this.boxKeys[ut],x1:ct[Pt],y1:ct[Pt+1],x2:ct[Pt+2],y2:ct[Pt+3]})}}}var Wt=this.circleCells[Y];if(Wt!==null)for(var Et=this.circles,Zt=0,ir=Wt;Ztae*ae+ge*ge},uo.prototype._circleAndRectCollide=function(w,I,S,L,Y,ie,ae){var ge=(ie-L)/2,Pe=Math.abs(w-(L+ge));if(Pe>ge+S)return!1;var Ue=(ae-Y)/2,ct=Math.abs(I-(Y+Ue));if(ct>Ue+S)return!1;if(Pe<=ge||ct<=Ue)return!0;var ft=Pe-ge,gt=ct-Ue;return ft*ft+gt*gt<=S*S};var mf=new Float32Array([-1/0,-1/0,0,-1/0,-1/0,0,-1/0,-1/0,0,-1/0,-1/0,0]);function Ll(w,I){for(var S=0;S=1;zo--)xn.push(bn.path[zo]);for(var qi=1;qi0){for(var Gn=xn[0].clone(),eo=xn[0].clone(),Dn=1;Dn=Ci.x&&eo.x<=Si.x&&Gn.y>=Ci.y&&eo.y<=Si.y?[xn]:eo.xSi.x||eo.ySi.y?[]:n.clipLine([xn],Ci.x,Ci.y,Si.x,Si.y)}for(var Rn=0,Ml=Hi;Rn=this.screenRightBoundary||L<100||I>this.screenBottomBoundary},eu.prototype.isInsideGrid=function(w,I,S,L){return S>=0&&w=0&&I0?(this.prevPlacement&&this.prevPlacement.variableOffsets[ft.crossTileID]&&this.prevPlacement.placements[ft.crossTileID]&&this.prevPlacement.placements[ft.crossTileID].text&&(Wt=this.prevPlacement.variableOffsets[ft.crossTileID].anchor),this.variableOffsets[ft.crossTileID]={textOffset:Et,width:S,height:L,anchor:w,textBoxScale:Y,prevAnchor:Wt},this.markUsedJustification(gt,w,ft,ut),gt.allowVerticalPlacement&&(this.markUsedOrientation(gt,ut,ft),this.placedOrientations[ft.crossTileID]=ut),{shift:Zt,placedGlyphBoxes:ir}):void 0},Sc.prototype.placeLayerBucketPart=function(w,I,S){var L=this,Y=w.parameters,ie=Y.bucket,ae=Y.layout,ge=Y.posMatrix,Pe=Y.textLabelPlaneMatrix,Ue=Y.labelToScreenMatrix,ct=Y.textPixelRatio,ft=Y.holdingForFade,gt=Y.collisionBoxArray,ut=Y.partiallyEvaluatedTextSize,Pt=Y.collisionGroup,Wt=ae.get("text-optional"),Et=ae.get("icon-optional"),Zt=ae.get("text-allow-overlap"),ir=ae.get("icon-allow-overlap"),dr=ae.get("text-rotation-alignment")==="map",kr=ae.get("text-pitch-alignment")==="map",Br=ae.get("icon-text-fit")!=="none",Wr=ae.get("symbol-z-order")==="viewport-y",ai=Zt&&(ir||!ie.hasIconData()||Et),Ci=ir&&(Zt||!ie.hasTextData()||Wt);!ie.collisionArrays&>&&ie.deserializeCollisionBoxes(gt);var Si=function(qi,Sn){if(!I[qi.crossTileID])if(ft)L.placements[qi.crossTileID]=new pm(!1,!1,!1);else{var $n,Hi=!1,Gn=!1,eo=!0,Dn=null,Rn={box:null,offscreen:null},Ml={box:null,offscreen:null},Ga=null,yc=null,cl=0,Vs=0,Ta=0;Sn.textFeatureIndex?cl=Sn.textFeatureIndex:qi.useRuntimeCollisionCircles&&(cl=qi.featureIndex),Sn.verticalTextFeatureIndex&&(Vs=Sn.verticalTextFeatureIndex);var en=Sn.textBox;if(en){var _n=function(_s){var yu=n.WritingMode.horizontal;if(ie.allowVerticalPlacement&&!_s&&L.prevPlacement){var Il=L.prevPlacement.placedOrientations[qi.crossTileID];Il&&(L.placedOrientations[qi.crossTileID]=Il,L.markUsedOrientation(ie,yu=Il,qi))}return yu},Ju=function(_s,yu){if(ie.allowVerticalPlacement&&qi.numVerticalGlyphVertices>0&&Sn.verticalTextBox)for(var Il=0,tf=ie.writingModes;Il0&&(jc=jc.filter(function(_s){return _s!==Gc.anchor})).unshift(Gc.anchor)}var Fa=function(_s,yu,Il){for(var tf=_s.x2-_s.x1,Kg=_s.y2-_s.y1,Lp=qi.textBoxScale,th=Br&&!ir?yu:null,Kf={box:[],offscreen:!1},wf=Zt?2*jc.length:jc.length,rf=0;rf=jc.length,qi,ie,Il,th);if(Tf&&(Kf=Tf.placedGlyphBoxes)&&Kf.box&&Kf.box.length){Hi=!0,Dn=Tf.shift;break}}return Kf};Ju(function(){return Fa(en,Sn.iconBox,n.WritingMode.horizontal)},function(){var _s=Sn.verticalTextBox;return ie.allowVerticalPlacement&&!(Rn&&Rn.box&&Rn.box.length)&&qi.numVerticalGlyphVertices>0&&_s?Fa(_s,Sn.verticalIconBox,n.WritingMode.vertical):{box:null,offscreen:null}}),Rn&&(Hi=Rn.box,eo=Rn.offscreen);var Hc=_n(Rn&&Rn.box);if(!Hi&&L.prevPlacement){var bd=L.prevPlacement.variableOffsets[qi.crossTileID];bd&&(L.variableOffsets[qi.crossTileID]=bd,L.markUsedJustification(ie,bd.anchor,qi,Hc))}}else{var wd=function(_s,yu){var Il=L.collisionIndex.placeCollisionBox(_s,Zt,ct,ge,Pt.predicate);return Il&&Il.box&&Il.box.length&&(L.markUsedOrientation(ie,yu,qi),L.placedOrientations[qi.crossTileID]=yu),Il};Ju(function(){return wd(en,n.WritingMode.horizontal)},function(){var _s=Sn.verticalTextBox;return ie.allowVerticalPlacement&&qi.numVerticalGlyphVertices>0&&_s?wd(_s,n.WritingMode.vertical):{box:null,offscreen:null}}),_n(Rn&&Rn.box&&Rn.box.length)}}if(Hi=($n=Rn)&&$n.box&&$n.box.length>0,eo=$n&&$n.offscreen,qi.useRuntimeCollisionCircles){var Td=ie.text.placedSymbolArray.get(qi.centerJustifiedTextSymbolIndex),Ou=n.evaluateSizeForFeature(ie.textSizeData,ut,Td),iu=ae.get("text-padding");Ga=L.collisionIndex.placeCollisionCircles(Zt,Td,ie.lineVertexArray,ie.glyphOffsetArray,Ou,ge,Pe,Ue,S,kr,Pt.predicate,qi.collisionCircleDiameter,iu),Hi=Zt||Ga.circles.length>0&&!Ga.collisionDetected,eo=eo&&Ga.offscreen}if(Sn.iconFeatureIndex&&(Ta=Sn.iconFeatureIndex),Sn.iconBox){var Sa=function(_s){var yu=Br&&Dn?Iu(_s,Dn.x,Dn.y,dr,kr,L.transform.angle):_s;return L.collisionIndex.placeCollisionBox(yu,ir,ct,ge,Pt.predicate)};Gn=Ml&&Ml.box&&Ml.box.length&&Sn.verticalIconBox?(yc=Sa(Sn.verticalIconBox)).box.length>0:(yc=Sa(Sn.iconBox)).box.length>0,eo=eo&&yc.offscreen}var ia=Wt||qi.numHorizontalGlyphVertices===0&&qi.numVerticalGlyphVertices===0,zl=Et||qi.numIconVertices===0;if(ia||zl?zl?ia||(Gn=Gn&&Hi):Hi=Gn&&Hi:Gn=Hi=Gn&&Hi,Hi&&$n&&$n.box&&L.collisionIndex.insertCollisionBox($n.box,ae.get("text-ignore-placement"),ie.bucketInstanceId,Ml&&Ml.box&&Vs?Vs:cl,Pt.ID),Gn&&yc&&L.collisionIndex.insertCollisionBox(yc.box,ae.get("icon-ignore-placement"),ie.bucketInstanceId,Ta,Pt.ID),Ga&&(Hi&&L.collisionIndex.insertCollisionCircles(Ga.circles,ae.get("text-ignore-placement"),ie.bucketInstanceId,cl,Pt.ID),S)){var eh=ie.bucketInstanceId,ef=L.collisionCircleArrays[eh];ef===void 0&&(ef=L.collisionCircleArrays[eh]=new pu);for(var Ul=0;Ul=0;--bn){var Tn=$i[bn];Si(ie.symbolInstances.get(Tn),ie.collisionArrays[Tn])}else for(var xn=w.symbolInstanceStart;xn=0&&(w.text.placedSymbolArray.get(ge).crossTileID=Y>=0&&ge!==Y?0:S.crossTileID)}},Sc.prototype.markUsedOrientation=function(w,I,S){for(var L=I===n.WritingMode.horizontal||I===n.WritingMode.horizontalOnly?I:0,Y=I===n.WritingMode.vertical?I:0,ie=0,ae=[S.leftJustifiedTextSymbolIndex,S.centerJustifiedTextSymbolIndex,S.rightJustifiedTextSymbolIndex];ie0,Ci=L.placedOrientations[ir.crossTileID],Si=Ci===n.WritingMode.vertical,$i=Ci===n.WritingMode.horizontal||Ci===n.WritingMode.horizontalOnly;if(dr>0||kr>0){var bn=Ep(Wr.text);ut(w.text,dr,Si?Ql:bn),ut(w.text,kr,$i?Ql:bn);var Tn=Wr.text.isHidden();[ir.rightJustifiedTextSymbolIndex,ir.centerJustifiedTextSymbolIndex,ir.leftJustifiedTextSymbolIndex].forEach(function(Rn){Rn>=0&&(w.text.placedSymbolArray.get(Rn).hidden=Tn||Si?1:0)}),ir.verticalPlacedTextSymbolIndex>=0&&(w.text.placedSymbolArray.get(ir.verticalPlacedTextSymbolIndex).hidden=Tn||$i?1:0);var xn=L.variableOffsets[ir.crossTileID];xn&&L.markUsedJustification(w,xn.anchor,ir,Ci);var zo=L.placedOrientations[ir.crossTileID];zo&&(L.markUsedJustification(w,"left",ir,zo),L.markUsedOrientation(w,zo,ir))}if(ai){var qi=Ep(Wr.icon),Sn=!(ft&&ir.verticalPlacedIconSymbolIndex&&Si);ir.placedIconSymbolIndex>=0&&(ut(w.icon,ir.numIconVertices,Sn?qi:Ql),w.icon.placedSymbolArray.get(ir.placedIconSymbolIndex).hidden=Wr.icon.isHidden()),ir.verticalPlacedIconSymbolIndex>=0&&(ut(w.icon,ir.numVerticalIconVertices,Sn?Ql:qi),w.icon.placedSymbolArray.get(ir.verticalPlacedIconSymbolIndex).hidden=Wr.icon.isHidden())}if(w.hasIconCollisionBoxData()||w.hasTextCollisionBoxData()){var $n=w.collisionArrays[Zt];if($n){var Hi=new n.Point(0,0);if($n.textBox||$n.verticalTextBox){var Gn=!0;if(Pe){var eo=L.variableOffsets[Br];eo?(Hi=Ia(eo.anchor,eo.width,eo.height,eo.textOffset,eo.textBoxScale),Ue&&Hi._rotate(ct?L.transform.angle:-L.transform.angle)):Gn=!1}$n.textBox&&pc(w.textCollisionBox.collisionVertexArray,Wr.text.placed,!Gn||Si,Hi.x,Hi.y),$n.verticalTextBox&&pc(w.textCollisionBox.collisionVertexArray,Wr.text.placed,!Gn||$i,Hi.x,Hi.y)}var Dn=Boolean(!$i&&$n.verticalIconBox);$n.iconBox&&pc(w.iconCollisionBox.collisionVertexArray,Wr.icon.placed,Dn,ft?Hi.x:0,ft?Hi.y:0),$n.verticalIconBox&&pc(w.iconCollisionBox.collisionVertexArray,Wr.icon.placed,!Dn,ft?Hi.x:0,ft?Hi.y:0)}}},Wt=0;Wtw},Sc.prototype.setStale=function(){this.stale=!0};var gf=Math.pow(2,25),gd=Math.pow(2,24),Gh=Math.pow(2,17),Hh=Math.pow(2,16),Mo=Math.pow(2,9),Sp=Math.pow(2,8),Qm=Math.pow(2,1);function Ep(w){if(w.opacity===0&&!w.placed)return 0;if(w.opacity===1&&w.placed)return 4294967295;var I=w.placed?1:0,S=Math.floor(127*w.opacity);return S*gf+I*gd+S*Gh+I*Hh+S*Mo+I*Sp+S*Qm+I}var Ql=0,Jl=function(w){this._sortAcrossTiles=w.layout.get("symbol-z-order")!=="viewport-y"&&w.layout.get("symbol-sort-key").constantOr(1)!==void 0,this._currentTileIndex=0,this._currentPartIndex=0,this._seenCrossTileIDs={},this._bucketParts=[]};Jl.prototype.continuePlacement=function(w,I,S,L,Y){for(var ie=this._bucketParts;this._currentTileIndex2};this._currentPlacementIndex>=0;){var ae=I[w[this._currentPlacementIndex]],ge=this.placement.collisionIndex.transform.zoom;if(ae.type==="symbol"&&(!ae.minzoom||ae.minzoom<=ge)&&(!ae.maxzoom||ae.maxzoom>ge)){if(this._inProgressLayer||(this._inProgressLayer=new Jl(ae)),this._inProgressLayer.continuePlacement(S[ae.source],this.placement,this._showCollisionBoxes,ae,ie))return;delete this._inProgressLayer}this._currentPlacementIndex--}this._done=!0},_a.prototype.commit=function(w){return this.placement.commit(w),this.placement};var Zn=512/n.EXTENT/2,Hs=function(w,I,S){this.tileID=w,this.indexedSymbolInstances={},this.bucketInstanceId=S;for(var L=0;Lw.overscaledZ)for(var ge in ae){var Pe=ae[ge];Pe.tileID.isChildOf(w)&&Pe.findMatches(I.symbolInstances,w,Y)}else{var Ue=ae[w.scaledTo(Number(ie)).key];Ue&&Ue.findMatches(I.symbolInstances,w,Y)}}for(var ct=0;ct1?"@2x":"",ft=n.getJSON(ie.transformRequest(ie.normalizeSpriteURL(Y,ct,".json"),n.ResourceType.SpriteJSON),function(Pt,Wt){ft=null,Ue||(Ue=Pt,ge=Wt,ut())}),gt=n.getImage(ie.transformRequest(ie.normalizeSpriteURL(Y,ct,".png"),n.ResourceType.SpriteImage),function(Pt,Wt){gt=null,Ue||(Ue=Pt,Pe=Wt,ut())});function ut(){if(Ue)ae(Ue);else if(ge&&Pe){var Pt=n.browser.getImageData(Pe),Wt={};for(var Et in ge){var Zt=ge[Et],ir=Zt.width,dr=Zt.height,kr=Zt.x,Br=Zt.y,Wr=Zt.sdf,ai=Zt.pixelRatio,Ci=Zt.stretchX,Si=Zt.stretchY,$i=Zt.content,bn=new n.RGBAImage({width:ir,height:dr});n.RGBAImage.copy(Pt,bn,{x:kr,y:Br},{x:0,y:0},{width:ir,height:dr}),Wt[Et]={data:bn,pixelRatio:ai,sdf:Wr,stretchX:Ci,stretchY:Si,content:$i}}ae(null,Wt)}}return{cancel:function(){ft&&(ft.cancel(),ft=null),gt&&(gt.cancel(),gt=null)}}}(S,this.map._requestManager,function(Y,ie){if(L._spriteRequest=null,Y)L.fire(new n.ErrorEvent(Y));else if(ie)for(var ae in ie)L.imageManager.addImage(ae,ie[ae]);L.imageManager.setLoaded(!0),L._availableImages=L.imageManager.listImages(),L.dispatcher.broadcast("setImages",L._availableImages),L.fire(new n.Event("data",{dataType:"style"}))})},I.prototype._validateLayer=function(S){var L=this.sourceCaches[S.source];if(L){var Y=S.sourceLayer;if(Y){var ie=L.getSource();(ie.type==="geojson"||ie.vectorLayerIds&&ie.vectorLayerIds.indexOf(Y)===-1)&&this.fire(new n.ErrorEvent(new Error('Source layer "'+Y+'" does not exist on source "'+ie.id+'" as specified by style layer "'+S.id+'"')))}}},I.prototype.loaded=function(){if(!this._loaded||Object.keys(this._updatedSources).length)return!1;for(var S in this.sourceCaches)if(!this.sourceCaches[S].loaded())return!1;return!!this.imageManager.isLoaded()},I.prototype._serializeLayers=function(S){for(var L=[],Y=0,ie=S;Y0)throw new Error("Unimplemented: "+ie.map(function(ae){return ae.command}).join(", ")+".");return Y.forEach(function(ae){ae.command!=="setTransition"&&L[ae.command].apply(L,ae.args)}),this.stylesheet=S,!0},I.prototype.addImage=function(S,L){if(this.getImage(S))return this.fire(new n.ErrorEvent(new Error("An image with this name already exists.")));this.imageManager.addImage(S,L),this._afterImageUpdated(S)},I.prototype.updateImage=function(S,L){this.imageManager.updateImage(S,L)},I.prototype.getImage=function(S){return this.imageManager.getImage(S)},I.prototype.removeImage=function(S){if(!this.getImage(S))return this.fire(new n.ErrorEvent(new Error("No image with this name exists.")));this.imageManager.removeImage(S),this._afterImageUpdated(S)},I.prototype._afterImageUpdated=function(S){this._availableImages=this.imageManager.listImages(),this._changedImages[S]=!0,this._changed=!0,this.dispatcher.broadcast("setImages",this._availableImages),this.fire(new n.Event("data",{dataType:"style"}))},I.prototype.listImages=function(){return this._checkLoaded(),this.imageManager.listImages()},I.prototype.addSource=function(S,L,Y){var ie=this;if(Y===void 0&&(Y={}),this._checkLoaded(),this.sourceCaches[S]!==void 0)throw new Error("There is already a source with this ID");if(!L.type)throw new Error("The type property must be defined, but only the following properties were given: "+Object.keys(L).join(", ")+".");if(!(["vector","raster","geojson","video","image"].indexOf(L.type)>=0&&this._validate(n.validateStyle.source,"sources."+S,L,null,Y))){this.map&&this.map._collectResourceTiming&&(L.collectResourceTiming=!0);var ae=this.sourceCaches[S]=new Ot(S,L,this.dispatcher);ae.style=this,ae.setEventedParent(this,function(){return{isSourceLoaded:ie.loaded(),source:ae.serialize(),sourceId:S}}),ae.onAdd(this.map),this._changed=!0}},I.prototype.removeSource=function(S){if(this._checkLoaded(),this.sourceCaches[S]===void 0)throw new Error("There is no source with this ID");for(var L in this._layers)if(this._layers[L].source===S)return this.fire(new n.ErrorEvent(new Error('Source "'+S+'" cannot be removed while layer "'+L+'" is using it.')));var Y=this.sourceCaches[S];delete this.sourceCaches[S],delete this._updatedSources[S],Y.fire(new n.Event("data",{sourceDataType:"metadata",dataType:"source",sourceId:S})),Y.setEventedParent(null),Y.clearTiles(),Y.onRemove&&Y.onRemove(this.map),this._changed=!0},I.prototype.setGeoJSONSourceData=function(S,L){this._checkLoaded(),this.sourceCaches[S].getSource().setData(L),this._changed=!0},I.prototype.getSource=function(S){return this.sourceCaches[S]&&this.sourceCaches[S].getSource()},I.prototype.addLayer=function(S,L,Y){Y===void 0&&(Y={}),this._checkLoaded();var ie=S.id;if(this.getLayer(ie))this.fire(new n.ErrorEvent(new Error('Layer with id "'+ie+'" already exists on this map')));else{var ae;if(S.type==="custom"){if(vl(this,n.validateCustomStyleLayer(S)))return;ae=n.createStyleLayer(S)}else{if(typeof S.source=="object"&&(this.addSource(ie,S.source),S=n.clone$1(S),S=n.extend(S,{source:ie})),this._validate(n.validateStyle.layer,"layers."+ie,S,{arrayIndex:-1},Y))return;ae=n.createStyleLayer(S),this._validateLayer(ae),ae.setEventedParent(this,{layer:{id:ie}}),this._serializedLayers[ae.id]=ae.serialize()}var ge=L?this._order.indexOf(L):this._order.length;if(L&&ge===-1)this.fire(new n.ErrorEvent(new Error('Layer with id "'+L+'" does not exist on this map.')));else{if(this._order.splice(ge,0,ie),this._layerOrderChanged=!0,this._layers[ie]=ae,this._removedLayers[ie]&&ae.source&&ae.type!=="custom"){var Pe=this._removedLayers[ie];delete this._removedLayers[ie],Pe.type!==ae.type?this._updatedSources[ae.source]="clear":(this._updatedSources[ae.source]="reload",this.sourceCaches[ae.source].pause())}this._updateLayer(ae),ae.onAdd&&ae.onAdd(this.map)}}},I.prototype.moveLayer=function(S,L){if(this._checkLoaded(),this._changed=!0,this._layers[S]){if(S!==L){var Y=this._order.indexOf(S);this._order.splice(Y,1);var ie=L?this._order.indexOf(L):this._order.length;L&&ie===-1?this.fire(new n.ErrorEvent(new Error('Layer with id "'+L+'" does not exist on this map.'))):(this._order.splice(ie,0,S),this._layerOrderChanged=!0)}}else this.fire(new n.ErrorEvent(new Error("The layer '"+S+"' does not exist in the map's style and cannot be moved.")))},I.prototype.removeLayer=function(S){this._checkLoaded();var L=this._layers[S];if(L){L.setEventedParent(null);var Y=this._order.indexOf(S);this._order.splice(Y,1),this._layerOrderChanged=!0,this._changed=!0,this._removedLayers[S]=L,delete this._layers[S],delete this._serializedLayers[S],delete this._updatedLayers[S],delete this._updatedPaintProps[S],L.onRemove&&L.onRemove(this.map)}else this.fire(new n.ErrorEvent(new Error("The layer '"+S+"' does not exist in the map's style and cannot be removed.")))},I.prototype.getLayer=function(S){return this._layers[S]},I.prototype.hasLayer=function(S){return S in this._layers},I.prototype.setLayerZoomRange=function(S,L,Y){this._checkLoaded();var ie=this.getLayer(S);ie?ie.minzoom===L&&ie.maxzoom===Y||(L!=null&&(ie.minzoom=L),Y!=null&&(ie.maxzoom=Y),this._updateLayer(ie)):this.fire(new n.ErrorEvent(new Error("The layer '"+S+"' does not exist in the map's style and cannot have zoom extent.")))},I.prototype.setFilter=function(S,L,Y){Y===void 0&&(Y={}),this._checkLoaded();var ie=this.getLayer(S);if(ie){if(!n.deepEqual(ie.filter,L))return L==null?(ie.filter=void 0,void this._updateLayer(ie)):void(this._validate(n.validateStyle.filter,"layers."+ie.id+".filter",L,null,Y)||(ie.filter=n.clone$1(L),this._updateLayer(ie)))}else this.fire(new n.ErrorEvent(new Error("The layer '"+S+"' does not exist in the map's style and cannot be filtered.")))},I.prototype.getFilter=function(S){return n.clone$1(this.getLayer(S).filter)},I.prototype.setLayoutProperty=function(S,L,Y,ie){ie===void 0&&(ie={}),this._checkLoaded();var ae=this.getLayer(S);ae?n.deepEqual(ae.getLayoutProperty(L),Y)||(ae.setLayoutProperty(L,Y,ie),this._updateLayer(ae)):this.fire(new n.ErrorEvent(new Error("The layer '"+S+"' does not exist in the map's style and cannot be styled.")))},I.prototype.getLayoutProperty=function(S,L){var Y=this.getLayer(S);if(Y)return Y.getLayoutProperty(L);this.fire(new n.ErrorEvent(new Error("The layer '"+S+"' does not exist in the map's style.")))},I.prototype.setPaintProperty=function(S,L,Y,ie){ie===void 0&&(ie={}),this._checkLoaded();var ae=this.getLayer(S);ae?n.deepEqual(ae.getPaintProperty(L),Y)||(ae.setPaintProperty(L,Y,ie)&&this._updateLayer(ae),this._changed=!0,this._updatedPaintProps[S]=!0):this.fire(new n.ErrorEvent(new Error("The layer '"+S+"' does not exist in the map's style and cannot be styled.")))},I.prototype.getPaintProperty=function(S,L){return this.getLayer(S).getPaintProperty(L)},I.prototype.setFeatureState=function(S,L){this._checkLoaded();var Y=S.source,ie=S.sourceLayer,ae=this.sourceCaches[Y];if(ae!==void 0){var ge=ae.getSource().type;ge==="geojson"&&ie?this.fire(new n.ErrorEvent(new Error("GeoJSON sources cannot have a sourceLayer parameter."))):ge!=="vector"||ie?(S.id===void 0&&this.fire(new n.ErrorEvent(new Error("The feature id parameter must be provided."))),ae.setFeatureState(ie,S.id,L)):this.fire(new n.ErrorEvent(new Error("The sourceLayer parameter must be provided for vector source types.")))}else this.fire(new n.ErrorEvent(new Error("The source '"+Y+"' does not exist in the map's style.")))},I.prototype.removeFeatureState=function(S,L){this._checkLoaded();var Y=S.source,ie=this.sourceCaches[Y];if(ie!==void 0){var ae=ie.getSource().type,ge=ae==="vector"?S.sourceLayer:void 0;ae!=="vector"||ge?L&&typeof S.id!="string"&&typeof S.id!="number"?this.fire(new n.ErrorEvent(new Error("A feature id is required to remove its specific state property."))):ie.removeFeatureState(ge,S.id,L):this.fire(new n.ErrorEvent(new Error("The sourceLayer parameter must be provided for vector source types.")))}else this.fire(new n.ErrorEvent(new Error("The source '"+Y+"' does not exist in the map's style.")))},I.prototype.getFeatureState=function(S){this._checkLoaded();var L=S.source,Y=S.sourceLayer,ie=this.sourceCaches[L];if(ie!==void 0){if(ie.getSource().type!=="vector"||Y)return S.id===void 0&&this.fire(new n.ErrorEvent(new Error("The feature id parameter must be provided."))),ie.getFeatureState(Y,S.id);this.fire(new n.ErrorEvent(new Error("The sourceLayer parameter must be provided for vector source types.")))}else this.fire(new n.ErrorEvent(new Error("The source '"+L+"' does not exist in the map's style.")))},I.prototype.getTransition=function(){return n.extend({duration:300,delay:0},this.stylesheet&&this.stylesheet.transition)},I.prototype.serialize=function(){return n.filterObject({version:this.stylesheet.version,name:this.stylesheet.name,metadata:this.stylesheet.metadata,light:this.stylesheet.light,center:this.stylesheet.center,zoom:this.stylesheet.zoom,bearing:this.stylesheet.bearing,pitch:this.stylesheet.pitch,sprite:this.stylesheet.sprite,glyphs:this.stylesheet.glyphs,transition:this.stylesheet.transition,sources:n.mapObject(this.sourceCaches,function(S){return S.serialize()}),layers:this._serializeLayers(this._order)},function(S){return S!==void 0})},I.prototype._updateLayer=function(S){this._updatedLayers[S.id]=!0,S.source&&!this._updatedSources[S.source]&&this.sourceCaches[S.source].getSource().type!=="raster"&&(this._updatedSources[S.source]="reload",this.sourceCaches[S.source].pause()),this._changed=!0},I.prototype._flattenAndSortRenderedFeatures=function(S){for(var L=this,Y=function(Ci){return L._layers[Ci].type==="fill-extrusion"},ie={},ae=[],ge=this._order.length-1;ge>=0;ge--){var Pe=this._order[ge];if(Y(Pe)){ie[Pe]=ge;for(var Ue=0,ct=S;Ue=0;Wt--){var Et=this._order[Wt];if(Y(Et))for(var Zt=ae.length-1;Zt>=0;Zt--){var ir=ae[Zt].feature;if(ie[ir.layer.id] 0.5) {gl_FragColor=vec4(0.0,0.0,1.0,0.5)*alpha;}if (v_notUsed > 0.5) {gl_FragColor*=.1;}}","attribute vec2 a_pos;attribute vec2 a_anchor_pos;attribute vec2 a_extrude;attribute vec2 a_placed;attribute vec2 a_shift;uniform mat4 u_matrix;uniform vec2 u_extrude_scale;uniform float u_camera_to_center_distance;varying float v_placed;varying float v_notUsed;void main() {vec4 projectedPoint=u_matrix*vec4(a_anchor_pos,0,1);highp float camera_to_anchor_distance=projectedPoint.w;highp float collision_perspective_ratio=clamp(0.5+0.5*(u_camera_to_center_distance/camera_to_anchor_distance),0.0,4.0);gl_Position=u_matrix*vec4(a_pos,0.0,1.0);gl_Position.xy+=(a_extrude+a_shift)*u_extrude_scale*gl_Position.w*collision_perspective_ratio;v_placed=a_placed.x;v_notUsed=a_placed.y;}"),mh=vr("varying float v_radius;varying vec2 v_extrude;varying float v_perspective_ratio;varying float v_collision;void main() {float alpha=0.5*min(v_perspective_ratio,1.0);float stroke_radius=0.9*max(v_perspective_ratio,1.0);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);gl_FragColor=color*alpha*opacity_t;}","attribute vec2 a_pos;attribute float a_radius;attribute vec2 a_flags;uniform mat4 u_matrix;uniform mat4 u_inv_matrix;uniform vec2 u_viewport_size;uniform float u_camera_to_center_distance;varying float v_radius;varying vec2 v_extrude;varying float v_perspective_ratio;varying float v_collision;vec3 toTilePosition(vec2 screenPos) {vec4 rayStart=u_inv_matrix*vec4(screenPos,-1.0,1.0);vec4 rayEnd =u_inv_matrix*vec4(screenPos, 1.0,1.0);rayStart.xyz/=rayStart.w;rayEnd.xyz /=rayEnd.w;highp float t=(0.0-rayStart.z)/(rayEnd.z-rayStart.z);return mix(rayStart.xyz,rayEnd.xyz,t);}void main() {vec2 quadCenterPos=a_pos;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;vec3 tilePos=toTilePosition(quadCenterPos);vec4 clipPos=u_matrix*vec4(tilePos,1.0);highp float camera_to_anchor_distance=clipPos.w;highp float collision_perspective_ratio=clamp(0.5+0.5*(u_camera_to_center_distance/camera_to_anchor_distance),0.0,4.0);float padding_factor=1.2;v_radius=radius;v_extrude=quadVertexExtent*padding_factor;v_perspective_ratio=collision_perspective_ratio;v_collision=collision;gl_Position=vec4(clipPos.xyz/clipPos.w,1.0)+vec4(quadVertexExtent*padding_factor/u_viewport_size*2.0,0.0,0.0);}"),ms=vr("uniform highp vec4 u_color;uniform sampler2D u_overlay;varying vec2 v_uv;void main() {vec4 overlay_color=texture2D(u_overlay,v_uv);gl_FragColor=mix(u_color,overlay_color,overlay_color.a);}","attribute vec2 a_pos;varying vec2 v_uv;uniform mat4 u_matrix;uniform float u_overlay_scale;void main() {v_uv=a_pos/8192.0;gl_Position=u_matrix*vec4(a_pos*u_overlay_scale,0,1);}"),Ao=vr(`#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float opacity +gl_FragColor=color*opacity; +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,`attribute vec2 a_pos;uniform mat4 u_matrix; +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float opacity +gl_Position=u_matrix*vec4(a_pos,0,1);}`),$h=vr(`varying vec2 v_pos; +#pragma mapbox: define highp vec4 outline_color +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 outline_color +#pragma mapbox: initialize lowp float opacity +float dist=length(v_pos-gl_FragCoord.xy);float alpha=1.0-smoothstep(0.0,1.0,dist);gl_FragColor=outline_color*(alpha*opacity); +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,`attribute vec2 a_pos;uniform mat4 u_matrix;uniform vec2 u_world;varying vec2 v_pos; +#pragma mapbox: define highp vec4 outline_color +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 outline_color +#pragma mapbox: initialize lowp float opacity +gl_Position=u_matrix*vec4(a_pos,0,1);v_pos=(gl_Position.xy/gl_Position.w+1.0)/2.0*u_world;}`),us=vr(`uniform vec2 u_texsize;uniform sampler2D u_image;uniform float u_fade;varying vec2 v_pos_a;varying vec2 v_pos_b;varying vec2 v_pos; +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +void main() { +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +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;vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(pattern_tl_a/u_texsize,pattern_br_a/u_texsize,imagecoord);vec4 color1=texture2D(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=texture2D(u_image,pos2);float dist=length(v_pos-gl_FragCoord.xy);float alpha=1.0-smoothstep(0.0,1.0,dist);gl_FragColor=mix(color1,color2,u_fade)*alpha*opacity; +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,`uniform mat4 u_matrix;uniform vec2 u_world;uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform vec3 u_scale;attribute vec2 a_pos;varying vec2 v_pos_a;varying vec2 v_pos_b;varying vec2 v_pos; +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +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 tileRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;gl_Position=u_matrix*vec4(a_pos,0,1);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;v_pos_a=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,fromScale*display_size_a,tileRatio,a_pos);v_pos_b=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,toScale*display_size_b,tileRatio,a_pos);v_pos=(gl_Position.xy/gl_Position.w+1.0)/2.0*u_world;}`),Hd=vr(`uniform vec2 u_texsize;uniform float u_fade;uniform sampler2D u_image;varying vec2 v_pos_a;varying vec2 v_pos_b; +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +void main() { +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +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;vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(pattern_tl_a/u_texsize,pattern_br_a/u_texsize,imagecoord);vec4 color1=texture2D(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=texture2D(u_image,pos2);gl_FragColor=mix(color1,color2,u_fade)*opacity; +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,`uniform mat4 u_matrix;uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform vec3 u_scale;attribute vec2 a_pos;varying vec2 v_pos_a;varying vec2 v_pos_b; +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +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=u_matrix*vec4(a_pos,0,1);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);}`),q=vr(`varying vec4 v_color;void main() {gl_FragColor=v_color; +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,`uniform mat4 u_matrix;uniform vec3 u_lightcolor;uniform lowp vec3 u_lightpos;uniform lowp float u_lightintensity;uniform float u_vertical_gradient;uniform lowp float u_opacity;attribute vec2 a_pos;attribute vec4 a_normal_ed;varying vec4 v_color; +#pragma mapbox: define highp float base +#pragma mapbox: define highp float height +#pragma mapbox: define highp vec4 color +void main() { +#pragma mapbox: initialize highp float base +#pragma mapbox: initialize highp float height +#pragma mapbox: initialize highp vec4 color +vec3 normal=a_normal_ed.xyz;base=max(0.0,base);height=max(0.0,height);float t=mod(normal.x,2.0);gl_Position=u_matrix*vec4(a_pos,t > 0.0 ? height : base,1);float 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;float directional=clamp(dot(normal/16384.0,u_lightpos),0.0,1.0);directional=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;}`),oe=vr(`uniform vec2 u_texsize;uniform float u_fade;uniform sampler2D u_image;varying vec2 v_pos_a;varying vec2 v_pos_b;varying vec4 v_lighting; +#pragma mapbox: define lowp float base +#pragma mapbox: define lowp float height +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float base +#pragma mapbox: initialize lowp float height +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +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;vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(pattern_tl_a/u_texsize,pattern_br_a/u_texsize,imagecoord);vec4 color1=texture2D(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=texture2D(u_image,pos2);vec4 mixedColor=mix(color1,color2,u_fade);gl_FragColor=mixedColor*v_lighting; +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,`uniform mat4 u_matrix;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 vec3 u_lightcolor;uniform lowp vec3 u_lightpos;uniform lowp float u_lightintensity;attribute vec2 a_pos;attribute vec4 a_normal_ed;varying vec2 v_pos_a;varying vec2 v_pos_b;varying vec4 v_lighting; +#pragma mapbox: define lowp float base +#pragma mapbox: define lowp float height +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float base +#pragma mapbox: initialize lowp float height +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +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 tileRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;vec3 normal=a_normal_ed.xyz;float edgedistance=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;base=max(0.0,base);height=max(0.0,height);float t=mod(normal.x,2.0);float z=t > 0.0 ? height : base;gl_Position=u_matrix*vec4(a_pos,z,1);vec2 pos=normal.x==1.0 && normal.y==0.0 && normal.z==16384.0 +? a_pos +: vec2(edgedistance,z*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;}`),pe=vr(`#ifdef GL_ES +precision highp float; +#endif +uniform sampler2D u_image;varying vec2 v_pos;uniform vec2 u_dimension;uniform float u_zoom;uniform vec4 u_unpack;float getElevation(vec2 coord,float bias) {vec4 data=texture2D(u_image,coord)*255.0;data.a=-1.0;return dot(data,u_unpack)/4.0;}void main() {vec2 epsilon=1.0/u_dimension;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))/pow(2.0,exaggeration+(19.2562-u_zoom));gl_FragColor=clamp(vec4(deriv.x/2.0+0.5,deriv.y/2.0+0.5,1.0,1.0),0.0,1.0); +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,"uniform mat4 u_matrix;uniform vec2 u_dimension;attribute vec2 a_pos;attribute vec2 a_texture_pos;varying 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;}"),be=vr(`uniform sampler2D u_image;varying vec2 v_pos;uniform vec2 u_latrange;uniform vec2 u_light;uniform vec4 u_shadow;uniform vec4 u_highlight;uniform vec4 u_accent; +#define PI 3.141592653589793 +void main() {vec4 pixel=texture2D(u_image,v_pos);vec2 deriv=((pixel.rg*2.0)-1.0);float scaleFactor=cos(radians((u_latrange[0]-u_latrange[1])*(1.0-v_pos.y)+u_latrange[1]));float slope=atan(1.25*length(deriv)/scaleFactor);float aspect=deriv.x !=0.0 ? atan(deriv.y,-deriv.x) : PI/2.0*(deriv.y > 0.0 ? 1.0 :-1.0);float intensity=u_light.x;float azimuth=u_light.y+PI;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_shadow,u_highlight,shade)*sin(scaledSlope)*clamp(intensity*2.0,0.0,1.0);gl_FragColor=accent_color*(1.0-shade_color.a)+shade_color; +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,"uniform mat4 u_matrix;attribute vec2 a_pos;attribute vec2 a_texture_pos;varying vec2 v_pos;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);v_pos=a_texture_pos/8192.0;}"),Ge=vr(`uniform lowp float u_device_pixel_ratio;varying vec2 v_width2;varying vec2 v_normal;varying float v_gamma_scale; +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +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);gl_FragColor=color*(alpha*opacity); +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,` +#define scale 0.015873016 +attribute vec2 a_pos_normal;attribute vec4 a_data;uniform mat4 u_matrix;uniform mediump float u_ratio;uniform vec2 u_units_to_pixels;uniform lowp float u_device_pixel_ratio;varying vec2 v_normal;varying vec2 v_width2;varying float v_gamma_scale;varying highp float v_linesofar; +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float gapwidth +#pragma mapbox: define lowp float offset +#pragma mapbox: define mediump float width +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float gapwidth +#pragma mapbox: initialize lowp float offset +#pragma mapbox: initialize mediump float width +float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;v_linesofar=(floor(a_data.z/4.0)+a_data.w*64.0)*2.0;vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;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);vec4 projected_extrude=u_matrix*vec4(dist/u_ratio,0.0,0.0);gl_Position=u_matrix*vec4(pos+offset2/u_ratio,0.0,1.0)+projected_extrude;float extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length(projected_extrude.xy/gl_Position.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;v_width2=vec2(outset,inset);}`),qe=vr(`uniform lowp float u_device_pixel_ratio;uniform sampler2D u_image;varying vec2 v_width2;varying vec2 v_normal;varying float v_gamma_scale;varying highp vec2 v_uv; +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +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=texture2D(u_image,v_uv);gl_FragColor=color*(alpha*opacity); +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,` +#define scale 0.015873016 +attribute vec2 a_pos_normal;attribute vec4 a_data;attribute float a_uv_x;attribute float a_split_index;uniform mat4 u_matrix;uniform mediump float u_ratio;uniform lowp float u_device_pixel_ratio;uniform vec2 u_units_to_pixels;uniform float u_image_height;varying vec2 v_normal;varying vec2 v_width2;varying float v_gamma_scale;varying highp vec2 v_uv; +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float gapwidth +#pragma mapbox: define lowp float offset +#pragma mapbox: define mediump float width +void main() { +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float gapwidth +#pragma mapbox: initialize lowp float offset +#pragma mapbox: initialize mediump float width +float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;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=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;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);vec4 projected_extrude=u_matrix*vec4(dist/u_ratio,0.0,0.0);gl_Position=u_matrix*vec4(pos+offset2/u_ratio,0.0,1.0)+projected_extrude;float extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length(projected_extrude.xy/gl_Position.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;v_width2=vec2(outset,inset);}`),ot=vr(`uniform lowp float u_device_pixel_ratio;uniform vec2 u_texsize;uniform float u_fade;uniform mediump vec3 u_scale;uniform sampler2D u_image;varying vec2 v_normal;varying vec2 v_width2;varying float v_linesofar;varying float v_gamma_scale;varying float v_width; +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +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;vec2 pattern_size_a=vec2(display_size_a.x*fromScale/tileZoomRatio,display_size_a.y);vec2 pattern_size_b=vec2(display_size_b.x*toScale/tileZoomRatio,display_size_b.y);float aspect_a=display_size_a.y/v_width;float aspect_b=display_size_b.y/v_width;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);float x_a=mod(v_linesofar/pattern_size_a.x*aspect_a,1.0);float x_b=mod(v_linesofar/pattern_size_b.x*aspect_b,1.0);float y=0.5*v_normal.y+0.5;vec2 texel_size=1.0/u_texsize;vec2 pos_a=mix(pattern_tl_a*texel_size-texel_size,pattern_br_a*texel_size+texel_size,vec2(x_a,y));vec2 pos_b=mix(pattern_tl_b*texel_size-texel_size,pattern_br_b*texel_size+texel_size,vec2(x_b,y));vec4 color=mix(texture2D(u_image,pos_a),texture2D(u_image,pos_b),u_fade);gl_FragColor=color*alpha*opacity; +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,` +#define scale 0.015873016 +#define LINE_DISTANCE_SCALE 2.0 +attribute vec2 a_pos_normal;attribute vec4 a_data;uniform mat4 u_matrix;uniform vec2 u_units_to_pixels;uniform mediump float u_ratio;uniform lowp float u_device_pixel_ratio;varying vec2 v_normal;varying vec2 v_width2;varying float v_linesofar;varying float v_gamma_scale;varying float v_width; +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float offset +#pragma mapbox: define mediump float gapwidth +#pragma mapbox: define mediump float width +#pragma mapbox: define lowp float floorwidth +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float offset +#pragma mapbox: initialize mediump float gapwidth +#pragma mapbox: initialize mediump float width +#pragma mapbox: initialize lowp float floorwidth +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;float a_linesofar=(floor(a_data.z/4.0)+a_data.w*64.0)*LINE_DISTANCE_SCALE;vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;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);vec4 projected_extrude=u_matrix*vec4(dist/u_ratio,0.0,0.0);gl_Position=u_matrix*vec4(pos+offset2/u_ratio,0.0,1.0)+projected_extrude;float extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length(projected_extrude.xy/gl_Position.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;v_linesofar=a_linesofar;v_width2=vec2(outset,inset);v_width=floorwidth;}`),rr=vr(`uniform lowp float u_device_pixel_ratio;uniform sampler2D u_image;uniform float u_sdfgamma;uniform float u_mix;varying vec2 v_normal;varying vec2 v_width2;varying vec2 v_tex_a;varying vec2 v_tex_b;varying float v_gamma_scale; +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float width +#pragma mapbox: define lowp float floorwidth +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float width +#pragma mapbox: initialize lowp float floorwidth +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);float sdfdist_a=texture2D(u_image,v_tex_a).a;float sdfdist_b=texture2D(u_image,v_tex_b).a;float sdfdist=mix(sdfdist_a,sdfdist_b,u_mix);alpha*=smoothstep(0.5-u_sdfgamma/floorwidth,0.5+u_sdfgamma/floorwidth,sdfdist);gl_FragColor=color*(alpha*opacity); +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,` +#define scale 0.015873016 +#define LINE_DISTANCE_SCALE 2.0 +attribute vec2 a_pos_normal;attribute vec4 a_data;uniform mat4 u_matrix;uniform mediump float u_ratio;uniform lowp float u_device_pixel_ratio;uniform vec2 u_patternscale_a;uniform float u_tex_y_a;uniform vec2 u_patternscale_b;uniform float u_tex_y_b;uniform vec2 u_units_to_pixels;varying vec2 v_normal;varying vec2 v_width2;varying vec2 v_tex_a;varying vec2 v_tex_b;varying float v_gamma_scale; +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float gapwidth +#pragma mapbox: define lowp float offset +#pragma mapbox: define mediump float width +#pragma mapbox: define lowp float floorwidth +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float gapwidth +#pragma mapbox: initialize lowp float offset +#pragma mapbox: initialize mediump float width +#pragma mapbox: initialize lowp float floorwidth +float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;float a_linesofar=(floor(a_data.z/4.0)+a_data.w*64.0)*LINE_DISTANCE_SCALE;vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;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);vec4 projected_extrude=u_matrix*vec4(dist/u_ratio,0.0,0.0);gl_Position=u_matrix*vec4(pos+offset2/u_ratio,0.0,1.0)+projected_extrude;float extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length(projected_extrude.xy/gl_Position.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;v_tex_a=vec2(a_linesofar*u_patternscale_a.x/floorwidth,normal.y*u_patternscale_a.y+u_tex_y_a);v_tex_b=vec2(a_linesofar*u_patternscale_b.x/floorwidth,normal.y*u_patternscale_b.y+u_tex_y_b);v_width2=vec2(outset,inset);}`),Ut=vr(`uniform float u_fade_t;uniform float u_opacity;uniform sampler2D u_image0;uniform sampler2D u_image1;varying vec2 v_pos0;varying vec2 v_pos1;uniform float u_brightness_low;uniform float u_brightness_high;uniform float u_saturation_factor;uniform float u_contrast_factor;uniform vec3 u_spin_weights;void main() {vec4 color0=texture2D(u_image0,v_pos0);vec4 color1=texture2D(u_image1,v_pos1);if (color0.a > 0.0) {color0.rgb=color0.rgb/color0.a;}if (color1.a > 0.0) {color1.rgb=color1.rgb/color1.a;}vec4 color=mix(color0,color1,u_fade_t);color.a*=u_opacity;vec3 rgb=color.rgb;rgb=vec3(dot(rgb,u_spin_weights.xyz),dot(rgb,u_spin_weights.zxy),dot(rgb,u_spin_weights.yzx));float average=(color.r+color.g+color.b)/3.0;rgb+=(average-rgb)*u_saturation_factor;rgb=(rgb-0.5)*u_contrast_factor+0.5;vec3 u_high_vec=vec3(u_brightness_low,u_brightness_low,u_brightness_low);vec3 u_low_vec=vec3(u_brightness_high,u_brightness_high,u_brightness_high);gl_FragColor=vec4(mix(u_high_vec,u_low_vec,rgb)*color.a,color.a); +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,"uniform mat4 u_matrix;uniform vec2 u_tl_parent;uniform float u_scale_parent;uniform float u_buffer_scale;attribute vec2 a_pos;attribute vec2 a_texture_pos;varying vec2 v_pos0;varying vec2 v_pos1;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);v_pos0=(((a_texture_pos/8192.0)-0.5)/u_buffer_scale )+0.5;v_pos1=(v_pos0*u_scale_parent)+u_tl_parent;}"),cr=vr(`uniform sampler2D u_texture;varying vec2 v_tex;varying float v_fade_opacity; +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize lowp float opacity +lowp float alpha=opacity*v_fade_opacity;gl_FragColor=texture2D(u_texture,v_tex)*alpha; +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,`const float PI=3.141592653589793;attribute vec4 a_pos_offset;attribute vec4 a_data;attribute vec4 a_pixeloffset;attribute vec3 a_projected_pos;attribute float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform highp float u_camera_to_center_distance;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform float u_fade_change;uniform mat4 u_matrix;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform vec2 u_texsize;varying vec2 v_tex;varying float v_fade_opacity; +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize lowp float opacity +vec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);vec2 a_pxoffset=a_pixeloffset.xy;vec2 a_minFontScale=a_pixeloffset.zw/256.0;highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}vec4 projectedPoint=u_matrix*vec4(a_pos,0,1);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ? +camera_to_anchor_distance/u_camera_to_center_distance : +u_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(0.5+0.5*distance_ratio,0.0,4.0);size*=perspective_ratio;float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=u_matrix*vec4(a_pos+vec2(1,0),0,1);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy,0.0,1.0);gl_Position=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*max(a_minFontScale,fontScale)+a_pxoffset/16.0),0.0,1.0);v_tex=a_tex/u_texsize;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;v_fade_opacity=max(0.0,min(1.0,fade_opacity[0]+fade_change));}`),Fr=vr(`#define SDF_PX 8.0 +uniform bool u_is_halo;uniform sampler2D u_texture;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;uniform bool u_is_text;varying vec2 v_data0;varying vec3 v_data1; +#pragma mapbox: define highp vec4 fill_color +#pragma mapbox: define highp vec4 halo_color +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float halo_width +#pragma mapbox: define lowp float halo_blur +void main() { +#pragma mapbox: initialize highp vec4 fill_color +#pragma mapbox: initialize highp vec4 halo_color +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float halo_width +#pragma mapbox: initialize lowp float halo_blur +float EDGE_GAMMA=0.105/u_device_pixel_ratio;vec2 tex=v_data0.xy;float gamma_scale=v_data1.x;float size=v_data1.y;float fade_opacity=v_data1[2];float fontScale=u_is_text ? size/24.0 : size;lowp vec4 color=fill_color;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float buff=(256.0-64.0)/256.0;if (u_is_halo) {color=halo_color;gamma=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);buff=(6.0-halo_width/fontScale)/SDF_PX;}lowp float dist=texture2D(u_texture,tex).a;highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(buff-gamma_scaled,buff+gamma_scaled,dist);gl_FragColor=color*(alpha*opacity*fade_opacity); +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,`const float PI=3.141592653589793;attribute vec4 a_pos_offset;attribute vec4 a_data;attribute vec4 a_pixeloffset;attribute vec3 a_projected_pos;attribute float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform mat4 u_matrix;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform highp float u_camera_to_center_distance;uniform float u_fade_change;uniform vec2 u_texsize;varying vec2 v_data0;varying vec3 v_data1; +#pragma mapbox: define highp vec4 fill_color +#pragma mapbox: define highp vec4 halo_color +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float halo_width +#pragma mapbox: define lowp float halo_blur +void main() { +#pragma mapbox: initialize highp vec4 fill_color +#pragma mapbox: initialize highp vec4 halo_color +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float halo_width +#pragma mapbox: initialize lowp float halo_blur +vec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);vec2 a_pxoffset=a_pixeloffset.xy;highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}vec4 projectedPoint=u_matrix*vec4(a_pos,0,1);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ? +camera_to_anchor_distance/u_camera_to_center_distance : +u_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(0.5+0.5*distance_ratio,0.0,4.0);size*=perspective_ratio;float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=u_matrix*vec4(a_pos+vec2(1,0),0,1);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy,0.0,1.0);gl_Position=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*fontScale+a_pxoffset),0.0,1.0);float gamma_scale=gl_Position.w;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float interpolated_fade_opacity=max(0.0,min(1.0,fade_opacity[0]+fade_change));v_data0=a_tex/u_texsize;v_data1=vec3(gamma_scale,size,interpolated_fade_opacity);}`),li=vr(`#define SDF_PX 8.0 +#define SDF 1.0 +#define ICON 0.0 +uniform bool u_is_halo;uniform sampler2D u_texture;uniform sampler2D u_texture_icon;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;varying vec4 v_data0;varying vec4 v_data1; +#pragma mapbox: define highp vec4 fill_color +#pragma mapbox: define highp vec4 halo_color +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float halo_width +#pragma mapbox: define lowp float halo_blur +void main() { +#pragma mapbox: initialize highp vec4 fill_color +#pragma mapbox: initialize highp vec4 halo_color +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float halo_width +#pragma mapbox: initialize lowp float halo_blur +float fade_opacity=v_data1[2];if (v_data1.w==ICON) {vec2 tex_icon=v_data0.zw;lowp float alpha=opacity*fade_opacity;gl_FragColor=texture2D(u_texture_icon,tex_icon)*alpha; +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +return;}vec2 tex=v_data0.xy;float EDGE_GAMMA=0.105/u_device_pixel_ratio;float gamma_scale=v_data1.x;float size=v_data1.y;float fontScale=size/24.0;lowp vec4 color=fill_color;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float buff=(256.0-64.0)/256.0;if (u_is_halo) {color=halo_color;gamma=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);buff=(6.0-halo_width/fontScale)/SDF_PX;}lowp float dist=texture2D(u_texture,tex).a;highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(buff-gamma_scaled,buff+gamma_scaled,dist);gl_FragColor=color*(alpha*opacity*fade_opacity); +#ifdef OVERDRAW_INSPECTOR +gl_FragColor=vec4(1.0); +#endif +}`,`const float PI=3.141592653589793;attribute vec4 a_pos_offset;attribute vec4 a_data;attribute vec3 a_projected_pos;attribute float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform mat4 u_matrix;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform highp float u_camera_to_center_distance;uniform float u_fade_change;uniform vec2 u_texsize;uniform vec2 u_texsize_icon;varying vec4 v_data0;varying vec4 v_data1; +#pragma mapbox: define highp vec4 fill_color +#pragma mapbox: define highp vec4 halo_color +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float halo_width +#pragma mapbox: define lowp float halo_blur +void main() { +#pragma mapbox: initialize highp vec4 fill_color +#pragma mapbox: initialize highp vec4 halo_color +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float halo_width +#pragma mapbox: initialize lowp float halo_blur +vec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);float is_sdf=a_size[0]-2.0*a_size_min;highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}vec4 projectedPoint=u_matrix*vec4(a_pos,0,1);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ? +camera_to_anchor_distance/u_camera_to_center_distance : +u_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(0.5+0.5*distance_ratio,0.0,4.0);size*=perspective_ratio;float fontScale=size/24.0;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=u_matrix*vec4(a_pos+vec2(1,0),0,1);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy,0.0,1.0);gl_Position=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*fontScale),0.0,1.0);float gamma_scale=gl_Position.w;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float interpolated_fade_opacity=max(0.0,min(1.0,fade_opacity[0]+fade_change));v_data0.xy=a_tex/u_texsize;v_data0.zw=a_tex/u_texsize_icon;v_data1=vec4(gamma_scale,size,interpolated_fade_opacity,is_sdf);}`);function vr(w,I){var S=/#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g,L=I.match(/attribute ([\w]+) ([\w]+)/g),Y=w.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g),ie=I.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g),ae=ie?ie.concat(Y):Y,ge={};return{fragmentSource:w=w.replace(S,function(Pe,Ue,ct,ft,gt){return ge[gt]=!0,Ue==="define"?` +#ifndef HAS_UNIFORM_u_`+gt+` +varying `+ct+" "+ft+" "+gt+`; +#else +uniform `+ct+" "+ft+" u_"+gt+`; +#endif +`:` +#ifdef HAS_UNIFORM_u_`+gt+` + `+ct+" "+ft+" "+gt+" = u_"+gt+`; +#endif +`}),vertexSource:I=I.replace(S,function(Pe,Ue,ct,ft,gt){var ut=ft==="float"?"vec2":"vec4",Pt=gt.match(/color/)?"color":ut;return ge[gt]?Ue==="define"?` +#ifndef HAS_UNIFORM_u_`+gt+` +uniform lowp float u_`+gt+`_t; +attribute `+ct+" "+ut+" a_"+gt+`; +varying `+ct+" "+ft+" "+gt+`; +#else +uniform `+ct+" "+ft+" u_"+gt+`; +#endif +`:Pt==="vec4"?` +#ifndef HAS_UNIFORM_u_`+gt+` + `+gt+" = a_"+gt+`; +#else + `+ct+" "+ft+" "+gt+" = u_"+gt+`; +#endif +`:` +#ifndef HAS_UNIFORM_u_`+gt+` + `+gt+" = unpack_mix_"+Pt+"(a_"+gt+", u_"+gt+`_t); +#else + `+ct+" "+ft+" "+gt+" = u_"+gt+`; +#endif +`:Ue==="define"?` +#ifndef HAS_UNIFORM_u_`+gt+` +uniform lowp float u_`+gt+`_t; +attribute `+ct+" "+ut+" a_"+gt+`; +#else +uniform `+ct+" "+ft+" u_"+gt+`; +#endif +`:Pt==="vec4"?` +#ifndef HAS_UNIFORM_u_`+gt+` + `+ct+" "+ft+" "+gt+" = a_"+gt+`; +#else + `+ct+" "+ft+" "+gt+" = u_"+gt+`; +#endif +`:` +#ifndef HAS_UNIFORM_u_`+gt+` + `+ct+" "+ft+" "+gt+" = unpack_mix_"+Pt+"(a_"+gt+", u_"+gt+`_t); +#else + `+ct+" "+ft+" "+gt+" = u_"+gt+`; +#endif +`}),staticAttributes:L,staticUniforms:ae}}var Bi=Object.freeze({__proto__:null,prelude:Wh,background:Ec,backgroundPattern:Hf,circle:qh,clippingMask:Gd,heatmap:Cp,heatmapTexture:Af,collisionBox:ra,collisionCircle:mh,debug:ms,fill:Ao,fillOutline:$h,fillOutlinePattern:us,fillPattern:Hd,fillExtrusion:q,fillExtrusionPattern:oe,hillshadePrepare:pe,hillshade:be,line:Ge,lineGradient:qe,linePattern:ot,lineSDF:rr,raster:Ut,symbolIcon:cr,symbolSDF:Fr,symbolTextAndIcon:li}),gn=function(){this.boundProgram=null,this.boundLayoutVertexBuffer=null,this.boundPaintVertexBuffers=[],this.boundIndexBuffer=null,this.boundVertexOffset=null,this.boundDynamicVertexBuffer=null,this.vao=null};function gs(w){for(var I=[],S=0;S>16,ge>>16],u_pixel_coord_lower:[65535&ae,65535&ge]}}Io.prototype.draw=function(w,I,S,L,Y,ie,ae,ge,Pe,Ue,ct,ft,gt,ut,Pt,Wt){var Et,Zt=w.gl;if(!this.failedToCreate){for(var ir in w.program.set(this.program),w.setDepthMode(S),w.setStencilMode(L),w.setColorMode(Y),w.setCullFace(ie),this.fixedUniforms)this.fixedUniforms[ir].set(ae[ir]);ut&&ut.setUniforms(w,this.binderUniforms,ft,{zoom:gt});for(var dr=(Et={},Et[Zt.LINES]=2,Et[Zt.TRIANGLES]=3,Et[Zt.LINE_STRIP]=1,Et)[I],kr=0,Br=ct.get();kr0?1-1/(1.001-ae):-ae),u_contrast_factor:(ie=Y.paint.get("raster-contrast"),ie>0?1/(1-ie):1+ie),u_spin_weights:Os(Y.paint.get("raster-hue-rotate"))};var ie,ae};function Os(w){w*=Math.PI/180;var I=Math.sin(w),S=Math.cos(w);return[(2*S+1)/3,(-Math.sqrt(3)*I-S+1)/3,(Math.sqrt(3)*I-S+1)/3]}var gh,Ss=function(w,I,S,L,Y,ie,ae,ge,Pe,Ue){var ct=Y.transform;return{u_is_size_zoom_constant:+(w==="constant"||w==="source"),u_is_size_feature_constant:+(w==="constant"||w==="camera"),u_size_t:I?I.uSizeT:0,u_size:I?I.uSize:0,u_camera_to_center_distance:ct.cameraToCenterDistance,u_pitch:ct.pitch/360*2*Math.PI,u_rotate_symbol:+S,u_aspect_ratio:ct.width/ct.height,u_fade_change:Y.options.fadeDuration?Y.symbolFadeChange:1,u_matrix:ie,u_label_plane_matrix:ae,u_coord_matrix:ge,u_is_text:+Pe,u_pitch_with_map:+L,u_texsize:Ue,u_texture:0}},Ah=function(w,I,S,L,Y,ie,ae,ge,Pe,Ue,ct){var ft=Y.transform;return n.extend(Ss(w,I,S,L,Y,ie,ae,ge,Pe,Ue),{u_gamma_scale:L?Math.cos(ft._pitch)*ft.cameraToCenterDistance:1,u_device_pixel_ratio:n.browser.devicePixelRatio,u_is_halo:+ct})},mc=function(w,I,S,L,Y,ie,ae,ge,Pe,Ue){return n.extend(Ah(w,I,S,L,Y,ie,ae,ge,!0,Pe,!0),{u_texsize_icon:Ue,u_texture_icon:1})},Qa=function(w,I,S){return{u_matrix:w,u_opacity:I,u_color:S}},Ru=function(w,I,S,L,Y,ie){return n.extend(function(ae,ge,Pe,Ue){var ct=Pe.imageManager.getPattern(ae.from.toString()),ft=Pe.imageManager.getPattern(ae.to.toString()),gt=Pe.imageManager.getPixelSize(),ut=gt.width,Pt=gt.height,Wt=Math.pow(2,Ue.tileID.overscaledZ),Et=Ue.tileSize*Math.pow(2,Pe.transform.tileZoom)/Wt,Zt=Et*(Ue.tileID.canonical.x+Ue.tileID.wrap*Wt),ir=Et*Ue.tileID.canonical.y;return{u_image:0,u_pattern_tl_a:ct.tl,u_pattern_br_a:ct.br,u_pattern_tl_b:ft.tl,u_pattern_br_b:ft.br,u_texsize:[ut,Pt],u_mix:ge.t,u_pattern_size_a:ct.displaySize,u_pattern_size_b:ft.displaySize,u_scale_a:ge.fromScale,u_scale_b:ge.toScale,u_tile_units_to_pixels:1/ca(Ue,1,Pe.transform.tileZoom),u_pixel_coord_upper:[Zt>>16,ir>>16],u_pixel_coord_lower:[65535&Zt,65535&ir]}}(L,ie,S,Y),{u_matrix:w,u_opacity:I})},gc={fillExtrusion:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_lightpos:new n.Uniform3f(w,I.u_lightpos),u_lightintensity:new n.Uniform1f(w,I.u_lightintensity),u_lightcolor:new n.Uniform3f(w,I.u_lightcolor),u_vertical_gradient:new n.Uniform1f(w,I.u_vertical_gradient),u_opacity:new n.Uniform1f(w,I.u_opacity)}},fillExtrusionPattern:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_lightpos:new n.Uniform3f(w,I.u_lightpos),u_lightintensity:new n.Uniform1f(w,I.u_lightintensity),u_lightcolor:new n.Uniform3f(w,I.u_lightcolor),u_vertical_gradient:new n.Uniform1f(w,I.u_vertical_gradient),u_height_factor:new n.Uniform1f(w,I.u_height_factor),u_image:new n.Uniform1i(w,I.u_image),u_texsize:new n.Uniform2f(w,I.u_texsize),u_pixel_coord_upper:new n.Uniform2f(w,I.u_pixel_coord_upper),u_pixel_coord_lower:new n.Uniform2f(w,I.u_pixel_coord_lower),u_scale:new n.Uniform3f(w,I.u_scale),u_fade:new n.Uniform1f(w,I.u_fade),u_opacity:new n.Uniform1f(w,I.u_opacity)}},fill:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix)}},fillPattern:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_image:new n.Uniform1i(w,I.u_image),u_texsize:new n.Uniform2f(w,I.u_texsize),u_pixel_coord_upper:new n.Uniform2f(w,I.u_pixel_coord_upper),u_pixel_coord_lower:new n.Uniform2f(w,I.u_pixel_coord_lower),u_scale:new n.Uniform3f(w,I.u_scale),u_fade:new n.Uniform1f(w,I.u_fade)}},fillOutline:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_world:new n.Uniform2f(w,I.u_world)}},fillOutlinePattern:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_world:new n.Uniform2f(w,I.u_world),u_image:new n.Uniform1i(w,I.u_image),u_texsize:new n.Uniform2f(w,I.u_texsize),u_pixel_coord_upper:new n.Uniform2f(w,I.u_pixel_coord_upper),u_pixel_coord_lower:new n.Uniform2f(w,I.u_pixel_coord_lower),u_scale:new n.Uniform3f(w,I.u_scale),u_fade:new n.Uniform1f(w,I.u_fade)}},circle:function(w,I){return{u_camera_to_center_distance:new n.Uniform1f(w,I.u_camera_to_center_distance),u_scale_with_map:new n.Uniform1i(w,I.u_scale_with_map),u_pitch_with_map:new n.Uniform1i(w,I.u_pitch_with_map),u_extrude_scale:new n.Uniform2f(w,I.u_extrude_scale),u_device_pixel_ratio:new n.Uniform1f(w,I.u_device_pixel_ratio),u_matrix:new n.UniformMatrix4f(w,I.u_matrix)}},collisionBox:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_camera_to_center_distance:new n.Uniform1f(w,I.u_camera_to_center_distance),u_pixels_to_tile_units:new n.Uniform1f(w,I.u_pixels_to_tile_units),u_extrude_scale:new n.Uniform2f(w,I.u_extrude_scale),u_overscale_factor:new n.Uniform1f(w,I.u_overscale_factor)}},collisionCircle:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_inv_matrix:new n.UniformMatrix4f(w,I.u_inv_matrix),u_camera_to_center_distance:new n.Uniform1f(w,I.u_camera_to_center_distance),u_viewport_size:new n.Uniform2f(w,I.u_viewport_size)}},debug:function(w,I){return{u_color:new n.UniformColor(w,I.u_color),u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_overlay:new n.Uniform1i(w,I.u_overlay),u_overlay_scale:new n.Uniform1f(w,I.u_overlay_scale)}},clippingMask:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix)}},heatmap:function(w,I){return{u_extrude_scale:new n.Uniform1f(w,I.u_extrude_scale),u_intensity:new n.Uniform1f(w,I.u_intensity),u_matrix:new n.UniformMatrix4f(w,I.u_matrix)}},heatmapTexture:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_world:new n.Uniform2f(w,I.u_world),u_image:new n.Uniform1i(w,I.u_image),u_color_ramp:new n.Uniform1i(w,I.u_color_ramp),u_opacity:new n.Uniform1f(w,I.u_opacity)}},hillshade:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_image:new n.Uniform1i(w,I.u_image),u_latrange:new n.Uniform2f(w,I.u_latrange),u_light:new n.Uniform2f(w,I.u_light),u_shadow:new n.UniformColor(w,I.u_shadow),u_highlight:new n.UniformColor(w,I.u_highlight),u_accent:new n.UniformColor(w,I.u_accent)}},hillshadePrepare:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_image:new n.Uniform1i(w,I.u_image),u_dimension:new n.Uniform2f(w,I.u_dimension),u_zoom:new n.Uniform1f(w,I.u_zoom),u_unpack:new n.Uniform4f(w,I.u_unpack)}},line:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_ratio:new n.Uniform1f(w,I.u_ratio),u_device_pixel_ratio:new n.Uniform1f(w,I.u_device_pixel_ratio),u_units_to_pixels:new n.Uniform2f(w,I.u_units_to_pixels)}},lineGradient:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_ratio:new n.Uniform1f(w,I.u_ratio),u_device_pixel_ratio:new n.Uniform1f(w,I.u_device_pixel_ratio),u_units_to_pixels:new n.Uniform2f(w,I.u_units_to_pixels),u_image:new n.Uniform1i(w,I.u_image),u_image_height:new n.Uniform1f(w,I.u_image_height)}},linePattern:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_texsize:new n.Uniform2f(w,I.u_texsize),u_ratio:new n.Uniform1f(w,I.u_ratio),u_device_pixel_ratio:new n.Uniform1f(w,I.u_device_pixel_ratio),u_image:new n.Uniform1i(w,I.u_image),u_units_to_pixels:new n.Uniform2f(w,I.u_units_to_pixels),u_scale:new n.Uniform3f(w,I.u_scale),u_fade:new n.Uniform1f(w,I.u_fade)}},lineSDF:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_ratio:new n.Uniform1f(w,I.u_ratio),u_device_pixel_ratio:new n.Uniform1f(w,I.u_device_pixel_ratio),u_units_to_pixels:new n.Uniform2f(w,I.u_units_to_pixels),u_patternscale_a:new n.Uniform2f(w,I.u_patternscale_a),u_patternscale_b:new n.Uniform2f(w,I.u_patternscale_b),u_sdfgamma:new n.Uniform1f(w,I.u_sdfgamma),u_image:new n.Uniform1i(w,I.u_image),u_tex_y_a:new n.Uniform1f(w,I.u_tex_y_a),u_tex_y_b:new n.Uniform1f(w,I.u_tex_y_b),u_mix:new n.Uniform1f(w,I.u_mix)}},raster:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_tl_parent:new n.Uniform2f(w,I.u_tl_parent),u_scale_parent:new n.Uniform1f(w,I.u_scale_parent),u_buffer_scale:new n.Uniform1f(w,I.u_buffer_scale),u_fade_t:new n.Uniform1f(w,I.u_fade_t),u_opacity:new n.Uniform1f(w,I.u_opacity),u_image0:new n.Uniform1i(w,I.u_image0),u_image1:new n.Uniform1i(w,I.u_image1),u_brightness_low:new n.Uniform1f(w,I.u_brightness_low),u_brightness_high:new n.Uniform1f(w,I.u_brightness_high),u_saturation_factor:new n.Uniform1f(w,I.u_saturation_factor),u_contrast_factor:new n.Uniform1f(w,I.u_contrast_factor),u_spin_weights:new n.Uniform3f(w,I.u_spin_weights)}},symbolIcon:function(w,I){return{u_is_size_zoom_constant:new n.Uniform1i(w,I.u_is_size_zoom_constant),u_is_size_feature_constant:new n.Uniform1i(w,I.u_is_size_feature_constant),u_size_t:new n.Uniform1f(w,I.u_size_t),u_size:new n.Uniform1f(w,I.u_size),u_camera_to_center_distance:new n.Uniform1f(w,I.u_camera_to_center_distance),u_pitch:new n.Uniform1f(w,I.u_pitch),u_rotate_symbol:new n.Uniform1i(w,I.u_rotate_symbol),u_aspect_ratio:new n.Uniform1f(w,I.u_aspect_ratio),u_fade_change:new n.Uniform1f(w,I.u_fade_change),u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_label_plane_matrix:new n.UniformMatrix4f(w,I.u_label_plane_matrix),u_coord_matrix:new n.UniformMatrix4f(w,I.u_coord_matrix),u_is_text:new n.Uniform1i(w,I.u_is_text),u_pitch_with_map:new n.Uniform1i(w,I.u_pitch_with_map),u_texsize:new n.Uniform2f(w,I.u_texsize),u_texture:new n.Uniform1i(w,I.u_texture)}},symbolSDF:function(w,I){return{u_is_size_zoom_constant:new n.Uniform1i(w,I.u_is_size_zoom_constant),u_is_size_feature_constant:new n.Uniform1i(w,I.u_is_size_feature_constant),u_size_t:new n.Uniform1f(w,I.u_size_t),u_size:new n.Uniform1f(w,I.u_size),u_camera_to_center_distance:new n.Uniform1f(w,I.u_camera_to_center_distance),u_pitch:new n.Uniform1f(w,I.u_pitch),u_rotate_symbol:new n.Uniform1i(w,I.u_rotate_symbol),u_aspect_ratio:new n.Uniform1f(w,I.u_aspect_ratio),u_fade_change:new n.Uniform1f(w,I.u_fade_change),u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_label_plane_matrix:new n.UniformMatrix4f(w,I.u_label_plane_matrix),u_coord_matrix:new n.UniformMatrix4f(w,I.u_coord_matrix),u_is_text:new n.Uniform1i(w,I.u_is_text),u_pitch_with_map:new n.Uniform1i(w,I.u_pitch_with_map),u_texsize:new n.Uniform2f(w,I.u_texsize),u_texture:new n.Uniform1i(w,I.u_texture),u_gamma_scale:new n.Uniform1f(w,I.u_gamma_scale),u_device_pixel_ratio:new n.Uniform1f(w,I.u_device_pixel_ratio),u_is_halo:new n.Uniform1i(w,I.u_is_halo)}},symbolTextAndIcon:function(w,I){return{u_is_size_zoom_constant:new n.Uniform1i(w,I.u_is_size_zoom_constant),u_is_size_feature_constant:new n.Uniform1i(w,I.u_is_size_feature_constant),u_size_t:new n.Uniform1f(w,I.u_size_t),u_size:new n.Uniform1f(w,I.u_size),u_camera_to_center_distance:new n.Uniform1f(w,I.u_camera_to_center_distance),u_pitch:new n.Uniform1f(w,I.u_pitch),u_rotate_symbol:new n.Uniform1i(w,I.u_rotate_symbol),u_aspect_ratio:new n.Uniform1f(w,I.u_aspect_ratio),u_fade_change:new n.Uniform1f(w,I.u_fade_change),u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_label_plane_matrix:new n.UniformMatrix4f(w,I.u_label_plane_matrix),u_coord_matrix:new n.UniformMatrix4f(w,I.u_coord_matrix),u_is_text:new n.Uniform1i(w,I.u_is_text),u_pitch_with_map:new n.Uniform1i(w,I.u_pitch_with_map),u_texsize:new n.Uniform2f(w,I.u_texsize),u_texsize_icon:new n.Uniform2f(w,I.u_texsize_icon),u_texture:new n.Uniform1i(w,I.u_texture),u_texture_icon:new n.Uniform1i(w,I.u_texture_icon),u_gamma_scale:new n.Uniform1f(w,I.u_gamma_scale),u_device_pixel_ratio:new n.Uniform1f(w,I.u_device_pixel_ratio),u_is_halo:new n.Uniform1i(w,I.u_is_halo)}},background:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_opacity:new n.Uniform1f(w,I.u_opacity),u_color:new n.UniformColor(w,I.u_color)}},backgroundPattern:function(w,I){return{u_matrix:new n.UniformMatrix4f(w,I.u_matrix),u_opacity:new n.Uniform1f(w,I.u_opacity),u_image:new n.Uniform1i(w,I.u_image),u_pattern_tl_a:new n.Uniform2f(w,I.u_pattern_tl_a),u_pattern_br_a:new n.Uniform2f(w,I.u_pattern_br_a),u_pattern_tl_b:new n.Uniform2f(w,I.u_pattern_tl_b),u_pattern_br_b:new n.Uniform2f(w,I.u_pattern_br_b),u_texsize:new n.Uniform2f(w,I.u_texsize),u_mix:new n.Uniform1f(w,I.u_mix),u_pattern_size_a:new n.Uniform2f(w,I.u_pattern_size_a),u_pattern_size_b:new n.Uniform2f(w,I.u_pattern_size_b),u_scale_a:new n.Uniform1f(w,I.u_scale_a),u_scale_b:new n.Uniform1f(w,I.u_scale_b),u_pixel_coord_upper:new n.Uniform2f(w,I.u_pixel_coord_upper),u_pixel_coord_lower:new n.Uniform2f(w,I.u_pixel_coord_lower),u_tile_units_to_pixels:new n.Uniform1f(w,I.u_tile_units_to_pixels)}}};function _h(w,I,S,L,Y,ie,ae){for(var ge=w.context,Pe=ge.gl,Ue=w.useProgram("collisionBox"),ct=[],ft=0,gt=0,ut=0;ut0){var kr=n.create(),Br=Zt;n.mul(kr,Et.placementInvProjMatrix,w.transform.glCoordMatrix),n.mul(kr,kr,Et.placementViewportMatrix),ct.push({circleArray:dr,circleOffset:gt,transform:Br,invTransform:kr}),gt=ft+=dr.length/4}ir&&Ue.draw(ge,Pe.LINES,mt.disabled,Ke.disabled,w.colorModeForRenderPass(),Jt.disabled,ni(Zt,w.transform,Wt),S.id,ir.layoutVertexBuffer,ir.indexBuffer,ir.segments,null,w.transform.zoom,null,null,ir.collisionVertexBuffer)}}if(ae&&ct.length){var Wr=w.useProgram("collisionCircle"),ai=new n.StructArrayLayout2f1f2i16;ai.resize(4*ft),ai._trim();for(var Ci=0,Si=0,$i=ct;Si<$i.length;Si+=1)for(var bn=$i[Si],Tn=0;Tn=0&&(Pt[Et.associatedIconIndex]={shiftedAnchor:ai,angle:Ci})}else Ll(Et.numGlyphs,gt)}if(ct){ut.clear();for(var $i=w.icon.placedSymbolArray,bn=0;bn<$i.length;bn++){var Tn=$i.get(bn);if(Tn.hidden)Ll(Tn.numGlyphs,ut);else{var xn=Pt[bn];if(xn)for(var zo=0;zo0){var ae=n.browser.now(),ge=(ae-w.timeAdded)/ie,Pe=I?(ae-I.timeAdded)/ie:-1,Ue=S.getSource(),ct=Y.coveringZoomLevel({tileSize:Ue.tileSize,roundZoom:Ue.roundZoom}),ft=!I||Math.abs(I.tileID.overscaledZ-ct)>Math.abs(w.tileID.overscaledZ-ct),gt=ft&&w.refreshedUponExpiration?1:n.clamp(ft?ge:1-Pe,0,1);return w.refreshedUponExpiration&&ge>=1&&(w.refreshedUponExpiration=!1),I?{opacity:1,mix:1-gt}:{opacity:gt,mix:0}}return{opacity:1,mix:0}}var yf=new n.Color(1,0,0,1),Bu=new n.Color(0,1,0,1),Wf=new n.Color(0,0,1,1),qf=new n.Color(1,0,1,1),Bt=new n.Color(0,1,1,1);function ar(w,I,S,L){Ur(w,0,I+S/2,w.transform.width,S,L)}function Rr(w,I,S,L){Ur(w,I-S/2,0,S,w.transform.height,L)}function Ur(w,I,S,L,Y,ie){var ae=w.context,ge=ae.gl;ge.enable(ge.SCISSOR_TEST),ge.scissor(I*n.browser.devicePixelRatio,S*n.browser.devicePixelRatio,L*n.browser.devicePixelRatio,Y*n.browser.devicePixelRatio),ae.clear({color:ie}),ge.disable(ge.SCISSOR_TEST)}function ci(w,I,S){var L=w.context,Y=L.gl,ie=S.posMatrix,ae=w.useProgram("debug"),ge=mt.disabled,Pe=Ke.disabled,Ue=w.colorModeForRenderPass();L.activeTexture.set(Y.TEXTURE0),w.emptyTexture.bind(Y.LINEAR,Y.CLAMP_TO_EDGE),ae.draw(L,Y.LINE_STRIP,ge,Pe,Ue,Jt.disabled,Po(ie,n.Color.red),"$debug",w.debugBuffer,w.tileBorderIndexBuffer,w.debugSegments);var ct=I.getTileByID(S.key).latestRawTileData,ft=Math.floor((ct&&ct.byteLength||0)/1024),gt=I.getTile(S).tileSize,ut=512/Math.min(gt,512)*(S.overscaledZ/w.transform.zoom)*.5,Pt=S.canonical.toString();S.overscaledZ!==S.canonical.z&&(Pt+=" => "+S.overscaledZ),function(Wt,Et){Wt.initDebugOverlayCanvas();var Zt=Wt.debugOverlayCanvas,ir=Wt.context.gl,dr=Wt.debugOverlayCanvas.getContext("2d");dr.clearRect(0,0,Zt.width,Zt.height),dr.shadowColor="white",dr.shadowBlur=2,dr.lineWidth=1.5,dr.strokeStyle="white",dr.textBaseline="top",dr.font="bold 36px Open Sans, sans-serif",dr.fillText(Et,5,5),dr.strokeText(Et,5,5),Wt.debugOverlayTexture.update(Zt),Wt.debugOverlayTexture.bind(ir.LINEAR,ir.CLAMP_TO_EDGE)}(w,Pt+" "+ft+"kb"),ae.draw(L,Y.TRIANGLES,ge,Pe,$t.alphaBlended,Jt.disabled,Po(ie,n.Color.transparent,ut),"$debug",w.debugBuffer,w.quadTriangleIndexBuffer,w.debugSegments)}var Li={symbol:function(w,I,S,L,Y){if(w.renderPass==="translucent"){var ie=Ke.disabled,ae=w.colorModeForRenderPass();S.layout.get("text-variable-anchor")&&function(ge,Pe,Ue,ct,ft,gt,ut){for(var Pt=Pe.transform,Wt=ft==="map",Et=gt==="map",Zt=0,ir=ge;Zt256&&this.clearStencil(),S.setColorMode($t.disabled),S.setDepthMode(mt.disabled);var Y=this.useProgram("clippingMask");this._tileClippingMaskIDs={};for(var ie=0,ae=I;ie256&&this.clearStencil();var w=this.nextStencilID++,I=this.context.gl;return new Ke({func:I.NOTEQUAL,mask:255},w,255,I.KEEP,I.KEEP,I.REPLACE)},yi.prototype.stencilModeForClipping=function(w){var I=this.context.gl;return new Ke({func:I.EQUAL,mask:255},this._tileClippingMaskIDs[w.key],0,I.KEEP,I.KEEP,I.REPLACE)},yi.prototype.stencilConfigForOverlap=function(w){var I,S=this.context.gl,L=w.sort(function(Pe,Ue){return Ue.overscaledZ-Pe.overscaledZ}),Y=L[L.length-1].overscaledZ,ie=L[0].overscaledZ-Y+1;if(ie>1){this.currentStencilSource=void 0,this.nextStencilID+ie>256&&this.clearStencil();for(var ae={},ge=0;ge=0;this.currentLayer--){var dr=this.style._layers[L[this.currentLayer]],kr=Y[dr.source],Br=Ue[dr.source];this._renderTileClippingMasks(dr,Br),this.renderLayer(this,kr,dr,Br)}for(this.renderPass="translucent",this.currentLayer=0;this.currentLayer0?I.pop():null},yi.prototype.isPatternMissing=function(w){if(!w)return!1;if(!w.from||!w.to)return!0;var I=this.imageManager.getPattern(w.from.toString()),S=this.imageManager.getPattern(w.to.toString());return!I||!S},yi.prototype.useProgram=function(w,I){this.cache=this.cache||{};var S=""+w+(I?I.cacheKey:"")+(this._showOverdrawInspector?"/overdraw":"");return this.cache[S]||(this.cache[S]=new Io(this.context,w,Bi[w],I,gc[w],this._showOverdrawInspector)),this.cache[S]},yi.prototype.setCustomLayerDefaults=function(){this.context.unbindVAO(),this.context.cullFace.setDefault(),this.context.activeTexture.setDefault(),this.context.pixelStoreUnpack.setDefault(),this.context.pixelStoreUnpackPremultiplyAlpha.setDefault(),this.context.pixelStoreUnpackFlipY.setDefault()},yi.prototype.setBaseState=function(){var w=this.context.gl;this.context.cullFace.set(!1),this.context.viewport.set([0,0,this.width,this.height]),this.context.blendEquation.set(w.FUNC_ADD)},yi.prototype.initDebugOverlayCanvas=function(){this.debugOverlayCanvas==null&&(this.debugOverlayCanvas=n.window.document.createElement("canvas"),this.debugOverlayCanvas.width=512,this.debugOverlayCanvas.height=512,this.debugOverlayTexture=new n.Texture(this.context,this.debugOverlayCanvas,this.context.gl.RGBA))},yi.prototype.destroy=function(){this.emptyTexture.destroy(),this.debugOverlayTexture&&this.debugOverlayTexture.destroy()};var Pn=function(w,I){this.points=w,this.planes=I};Pn.fromInvProjectionMatrix=function(w,I,S){var L=Math.pow(2,S),Y=[[-1,1,-1,1],[1,1,-1,1],[1,-1,-1,1],[-1,-1,-1,1],[-1,1,1,1],[1,1,1,1],[1,-1,1,1],[-1,-1,1,1]].map(function(ae){return n.transformMat4([],ae,w)}).map(function(ae){return n.scale$1([],ae,1/ae[3]/I*L)}),ie=[[0,1,2],[6,5,4],[0,3,7],[2,1,5],[3,2,6],[0,4,5]].map(function(ae){var ge=n.sub([],Y[ae[0]],Y[ae[1]]),Pe=n.sub([],Y[ae[2]],Y[ae[1]]),Ue=n.normalize([],n.cross([],ge,Pe)),ct=-n.dot(Ue,Y[ae[1]]);return Ue.concat(ct)});return new Pn(Y,ie)};var sn=function(w,I){this.min=w,this.max=I,this.center=n.scale$2([],n.add([],this.min,this.max),.5)};sn.prototype.quadrant=function(w){for(var I=[w%2==0,w<2],S=n.clone$2(this.min),L=n.clone$2(this.max),Y=0;Y=0;if(ie===0)return 0;ie!==I.length&&(S=!1)}if(S)return 2;for(var ge=0;ge<3;ge++){for(var Pe=Number.MAX_VALUE,Ue=-Number.MAX_VALUE,ct=0;ctthis.max[ge]-this.min[ge])return 0}return 1};var To=function(w,I,S,L){if(w===void 0&&(w=0),I===void 0&&(I=0),S===void 0&&(S=0),L===void 0&&(L=0),isNaN(w)||w<0||isNaN(I)||I<0||isNaN(S)||S<0||isNaN(L)||L<0)throw new Error("Invalid value for edge-insets, top, bottom, left and right must all be numbers");this.top=w,this.bottom=I,this.left=S,this.right=L};To.prototype.interpolate=function(w,I,S){return I.top!=null&&w.top!=null&&(this.top=n.number(w.top,I.top,S)),I.bottom!=null&&w.bottom!=null&&(this.bottom=n.number(w.bottom,I.bottom,S)),I.left!=null&&w.left!=null&&(this.left=n.number(w.left,I.left,S)),I.right!=null&&w.right!=null&&(this.right=n.number(w.right,I.right,S)),this},To.prototype.getCenter=function(w,I){var S=n.clamp((this.left+w-this.right)/2,0,w),L=n.clamp((this.top+I-this.bottom)/2,0,I);return new n.Point(S,L)},To.prototype.equals=function(w){return this.top===w.top&&this.bottom===w.bottom&&this.left===w.left&&this.right===w.right},To.prototype.clone=function(){return new To(this.top,this.bottom,this.left,this.right)},To.prototype.toJSON=function(){return{top:this.top,bottom:this.bottom,left:this.left,right:this.right}};var Gi=function(w,I,S,L,Y){this.tileSize=512,this.maxValidLatitude=85.051129,this._renderWorldCopies=Y===void 0||Y,this._minZoom=w||0,this._maxZoom=I||22,this._minPitch=S??0,this._maxPitch=L??60,this.setMaxBounds(),this.width=0,this.height=0,this._center=new n.LngLat(0,0),this.zoom=0,this.angle=0,this._fov=.6435011087932844,this._pitch=0,this._unmodified=!0,this._edgeInsets=new To,this._posMatrixCache={},this._alignedPosMatrixCache={}},Ti={minZoom:{configurable:!0},maxZoom:{configurable:!0},minPitch:{configurable:!0},maxPitch:{configurable:!0},renderWorldCopies:{configurable:!0},worldSize:{configurable:!0},centerOffset:{configurable:!0},size:{configurable:!0},bearing:{configurable:!0},pitch:{configurable:!0},fov:{configurable:!0},zoom:{configurable:!0},center:{configurable:!0},padding:{configurable:!0},centerPoint:{configurable:!0},unmodified:{configurable:!0},point:{configurable:!0}};Gi.prototype.clone=function(){var w=new Gi(this._minZoom,this._maxZoom,this._minPitch,this.maxPitch,this._renderWorldCopies);return w.tileSize=this.tileSize,w.latRange=this.latRange,w.width=this.width,w.height=this.height,w._center=this._center,w.zoom=this.zoom,w.angle=this.angle,w._fov=this._fov,w._pitch=this._pitch,w._unmodified=this._unmodified,w._edgeInsets=this._edgeInsets.clone(),w._calcMatrices(),w},Ti.minZoom.get=function(){return this._minZoom},Ti.minZoom.set=function(w){this._minZoom!==w&&(this._minZoom=w,this.zoom=Math.max(this.zoom,w))},Ti.maxZoom.get=function(){return this._maxZoom},Ti.maxZoom.set=function(w){this._maxZoom!==w&&(this._maxZoom=w,this.zoom=Math.min(this.zoom,w))},Ti.minPitch.get=function(){return this._minPitch},Ti.minPitch.set=function(w){this._minPitch!==w&&(this._minPitch=w,this.pitch=Math.max(this.pitch,w))},Ti.maxPitch.get=function(){return this._maxPitch},Ti.maxPitch.set=function(w){this._maxPitch!==w&&(this._maxPitch=w,this.pitch=Math.min(this.pitch,w))},Ti.renderWorldCopies.get=function(){return this._renderWorldCopies},Ti.renderWorldCopies.set=function(w){w===void 0?w=!0:w===null&&(w=!1),this._renderWorldCopies=w},Ti.worldSize.get=function(){return this.tileSize*this.scale},Ti.centerOffset.get=function(){return this.centerPoint._sub(this.size._div(2))},Ti.size.get=function(){return new n.Point(this.width,this.height)},Ti.bearing.get=function(){return-this.angle/Math.PI*180},Ti.bearing.set=function(w){var I=-n.wrap(w,-180,180)*Math.PI/180;this.angle!==I&&(this._unmodified=!1,this.angle=I,this._calcMatrices(),this.rotationMatrix=n.create$2(),n.rotate(this.rotationMatrix,this.rotationMatrix,this.angle))},Ti.pitch.get=function(){return this._pitch/Math.PI*180},Ti.pitch.set=function(w){var I=n.clamp(w,this.minPitch,this.maxPitch)/180*Math.PI;this._pitch!==I&&(this._unmodified=!1,this._pitch=I,this._calcMatrices())},Ti.fov.get=function(){return this._fov/Math.PI*180},Ti.fov.set=function(w){w=Math.max(.01,Math.min(60,w)),this._fov!==w&&(this._unmodified=!1,this._fov=w/180*Math.PI,this._calcMatrices())},Ti.zoom.get=function(){return this._zoom},Ti.zoom.set=function(w){var I=Math.min(Math.max(w,this.minZoom),this.maxZoom);this._zoom!==I&&(this._unmodified=!1,this._zoom=I,this.scale=this.zoomScale(I),this.tileZoom=Math.floor(I),this.zoomFraction=I-this.tileZoom,this._constrain(),this._calcMatrices())},Ti.center.get=function(){return this._center},Ti.center.set=function(w){w.lat===this._center.lat&&w.lng===this._center.lng||(this._unmodified=!1,this._center=w,this._constrain(),this._calcMatrices())},Ti.padding.get=function(){return this._edgeInsets.toJSON()},Ti.padding.set=function(w){this._edgeInsets.equals(w)||(this._unmodified=!1,this._edgeInsets.interpolate(this._edgeInsets,w,1),this._calcMatrices())},Ti.centerPoint.get=function(){return this._edgeInsets.getCenter(this.width,this.height)},Gi.prototype.isPaddingEqual=function(w){return this._edgeInsets.equals(w)},Gi.prototype.interpolatePadding=function(w,I,S){this._unmodified=!1,this._edgeInsets.interpolate(w,I,S),this._constrain(),this._calcMatrices()},Gi.prototype.coveringZoomLevel=function(w){var I=(w.roundZoom?Math.round:Math.floor)(this.zoom+this.scaleZoom(this.tileSize/w.tileSize));return Math.max(0,I)},Gi.prototype.getVisibleUnwrappedCoordinates=function(w){var I=[new n.UnwrappedTileID(0,w)];if(this._renderWorldCopies)for(var S=this.pointCoordinate(new n.Point(0,0)),L=this.pointCoordinate(new n.Point(this.width,0)),Y=this.pointCoordinate(new n.Point(this.width,this.height)),ie=this.pointCoordinate(new n.Point(0,this.height)),ae=Math.floor(Math.min(S.x,L.x,Y.x,ie.x)),ge=Math.floor(Math.max(S.x,L.x,Y.x,ie.x)),Pe=ae-1;Pe<=ge+1;Pe++)Pe!==0&&I.push(new n.UnwrappedTileID(Pe,w));return I},Gi.prototype.coveringTiles=function(w){var I=this.coveringZoomLevel(w),S=I;if(w.minzoom!==void 0&&Iw.maxzoom&&(I=w.maxzoom);var L=n.MercatorCoordinate.fromLngLat(this.center),Y=Math.pow(2,I),ie=[Y*L.x,Y*L.y,0],ae=Pn.fromInvProjectionMatrix(this.invProjMatrix,this.worldSize,I),ge=w.minzoom||0;this.pitch<=60&&this._edgeInsets.top<.1&&(ge=I);var Pe=function(Si){return{aabb:new sn([Si*Y,0,0],[(Si+1)*Y,Y,0]),zoom:0,x:0,y:0,wrap:Si,fullyVisible:!1}},Ue=[],ct=[],ft=I,gt=w.reparseOverscaled?S:I;if(this._renderWorldCopies)for(var ut=1;ut<=3;ut++)Ue.push(Pe(-ut)),Ue.push(Pe(ut));for(Ue.push(Pe(0));Ue.length>0;){var Pt=Ue.pop(),Wt=Pt.x,Et=Pt.y,Zt=Pt.fullyVisible;if(!Zt){var ir=Pt.aabb.intersects(ae);if(ir===0)continue;Zt=ir===2}var dr=Pt.aabb.distanceX(ie),kr=Pt.aabb.distanceY(ie),Br=Math.max(Math.abs(dr),Math.abs(kr));if(Pt.zoom===ft||Br>3+(1<=ge)ct.push({tileID:new n.OverscaledTileID(Pt.zoom===ft?gt:Pt.zoom,Pt.wrap,Pt.zoom,Wt,Et),distanceSq:n.sqrLen([ie[0]-.5-Wt,ie[1]-.5-Et])});else for(var Wr=0;Wr<4;Wr++){var ai=(Wt<<1)+Wr%2,Ci=(Et<<1)+(Wr>>1);Ue.push({aabb:Pt.aabb.quadrant(Wr),zoom:Pt.zoom+1,x:ai,y:Ci,wrap:Pt.wrap,fullyVisible:Zt})}}return ct.sort(function(Si,$i){return Si.distanceSq-$i.distanceSq}).map(function(Si){return Si.tileID})},Gi.prototype.resize=function(w,I){this.width=w,this.height=I,this.pixelsToGLUnits=[2/w,-2/I],this._constrain(),this._calcMatrices()},Ti.unmodified.get=function(){return this._unmodified},Gi.prototype.zoomScale=function(w){return Math.pow(2,w)},Gi.prototype.scaleZoom=function(w){return Math.log(w)/Math.LN2},Gi.prototype.project=function(w){var I=n.clamp(w.lat,-this.maxValidLatitude,this.maxValidLatitude);return new n.Point(n.mercatorXfromLng(w.lng)*this.worldSize,n.mercatorYfromLat(I)*this.worldSize)},Gi.prototype.unproject=function(w){return new n.MercatorCoordinate(w.x/this.worldSize,w.y/this.worldSize).toLngLat()},Ti.point.get=function(){return this.project(this.center)},Gi.prototype.setLocationAtPoint=function(w,I){var S=this.pointCoordinate(I),L=this.pointCoordinate(this.centerPoint),Y=this.locationCoordinate(w),ie=new n.MercatorCoordinate(Y.x-(S.x-L.x),Y.y-(S.y-L.y));this.center=this.coordinateLocation(ie),this._renderWorldCopies&&(this.center=this.center.wrap())},Gi.prototype.locationPoint=function(w){return this.coordinatePoint(this.locationCoordinate(w))},Gi.prototype.pointLocation=function(w){return this.coordinateLocation(this.pointCoordinate(w))},Gi.prototype.locationCoordinate=function(w){return n.MercatorCoordinate.fromLngLat(w)},Gi.prototype.coordinateLocation=function(w){return w.toLngLat()},Gi.prototype.pointCoordinate=function(w){var I=[w.x,w.y,0,1],S=[w.x,w.y,1,1];n.transformMat4(I,I,this.pixelMatrixInverse),n.transformMat4(S,S,this.pixelMatrixInverse);var L=I[3],Y=S[3],ie=I[1]/L,ae=S[1]/Y,ge=I[2]/L,Pe=S[2]/Y,Ue=ge===Pe?0:(0-ge)/(Pe-ge);return new n.MercatorCoordinate(n.number(I[0]/L,S[0]/Y,Ue)/this.worldSize,n.number(ie,ae,Ue)/this.worldSize)},Gi.prototype.coordinatePoint=function(w){var I=[w.x*this.worldSize,w.y*this.worldSize,0,1];return n.transformMat4(I,I,this.pixelMatrix),new n.Point(I[0]/I[3],I[1]/I[3])},Gi.prototype.getBounds=function(){return new n.LngLatBounds().extend(this.pointLocation(new n.Point(0,0))).extend(this.pointLocation(new n.Point(this.width,0))).extend(this.pointLocation(new n.Point(this.width,this.height))).extend(this.pointLocation(new n.Point(0,this.height)))},Gi.prototype.getMaxBounds=function(){return this.latRange&&this.latRange.length===2&&this.lngRange&&this.lngRange.length===2?new n.LngLatBounds([this.lngRange[0],this.latRange[0]],[this.lngRange[1],this.latRange[1]]):null},Gi.prototype.setMaxBounds=function(w){w?(this.lngRange=[w.getWest(),w.getEast()],this.latRange=[w.getSouth(),w.getNorth()],this._constrain()):(this.lngRange=null,this.latRange=[-this.maxValidLatitude,this.maxValidLatitude])},Gi.prototype.calculatePosMatrix=function(w,I){I===void 0&&(I=!1);var S=w.key,L=I?this._alignedPosMatrixCache:this._posMatrixCache;if(L[S])return L[S];var Y=w.canonical,ie=this.worldSize/this.zoomScale(Y.z),ae=Y.x+Math.pow(2,Y.z)*w.wrap,ge=n.identity(new Float64Array(16));return n.translate(ge,ge,[ae*ie,Y.y*ie,0]),n.scale(ge,ge,[ie/n.EXTENT,ie/n.EXTENT,1]),n.multiply(ge,I?this.alignedProjMatrix:this.projMatrix,ge),L[S]=new Float32Array(ge),L[S]},Gi.prototype.customLayerMatrix=function(){return this.mercatorMatrix.slice()},Gi.prototype._constrain=function(){if(this.center&&this.width&&this.height&&!this._constraining){this._constraining=!0;var w,I,S,L,Y=-90,ie=90,ae=-180,ge=180,Pe=this.size,Ue=this._unmodified;if(this.latRange){var ct=this.latRange;Y=n.mercatorYfromLat(ct[1])*this.worldSize,w=(ie=n.mercatorYfromLat(ct[0])*this.worldSize)-Yie&&(L=ie-Wt)}if(this.lngRange){var Et=gt.x,Zt=Pe.x/2;Et-Ztge&&(S=ge-Zt)}S===void 0&&L===void 0||(this.center=this.unproject(new n.Point(S!==void 0?S:gt.x,L!==void 0?L:gt.y))),this._unmodified=Ue,this._constraining=!1}},Gi.prototype._calcMatrices=function(){if(this.height){var w=this.centerOffset;this.cameraToCenterDistance=.5/Math.tan(this._fov/2)*this.height;var I=Math.PI/2+this._pitch,S=this._fov*(.5+w.y/this.height),L=Math.sin(S)*this.cameraToCenterDistance/Math.sin(n.clamp(Math.PI-I-S,.01,Math.PI-.01)),Y=this.point,ie=Y.x,ae=Y.y,ge=1.01*(Math.cos(Math.PI/2-this._pitch)*L+this.cameraToCenterDistance),Pe=this.height/50,Ue=new Float64Array(16);n.perspective(Ue,this._fov,this.width/this.height,Pe,ge),Ue[8]=2*-w.x/this.width,Ue[9]=2*w.y/this.height,n.scale(Ue,Ue,[1,-1,1]),n.translate(Ue,Ue,[0,0,-this.cameraToCenterDistance]),n.rotateX(Ue,Ue,this._pitch),n.rotateZ(Ue,Ue,this.angle),n.translate(Ue,Ue,[-ie,-ae,0]),this.mercatorMatrix=n.scale([],Ue,[this.worldSize,this.worldSize,this.worldSize]),n.scale(Ue,Ue,[1,1,n.mercatorZfromAltitude(1,this.center.lat)*this.worldSize,1]),this.projMatrix=Ue,this.invProjMatrix=n.invert([],this.projMatrix);var ct=this.width%2/2,ft=this.height%2/2,gt=Math.cos(this.angle),ut=Math.sin(this.angle),Pt=ie-Math.round(ie)+gt*ct+ut*ft,Wt=ae-Math.round(ae)+gt*ft+ut*ct,Et=new Float64Array(Ue);if(n.translate(Et,Et,[Pt>.5?Pt-1:Pt,Wt>.5?Wt-1:Wt,0]),this.alignedProjMatrix=Et,Ue=n.create(),n.scale(Ue,Ue,[this.width/2,-this.height/2,1]),n.translate(Ue,Ue,[1,-1,0]),this.labelPlaneMatrix=Ue,Ue=n.create(),n.scale(Ue,Ue,[1,-1,1]),n.translate(Ue,Ue,[-1,-1,0]),n.scale(Ue,Ue,[2/this.width,2/this.height,1]),this.glCoordMatrix=Ue,this.pixelMatrix=n.multiply(new Float64Array(16),this.labelPlaneMatrix,this.projMatrix),!(Ue=n.invert(new Float64Array(16),this.pixelMatrix)))throw new Error("failed to invert matrix");this.pixelMatrixInverse=Ue,this._posMatrixCache={},this._alignedPosMatrixCache={}}},Gi.prototype.maxPitchScaleFactor=function(){if(!this.pixelMatrixInverse)return 1;var w=this.pointCoordinate(new n.Point(0,0)),I=[w.x*this.worldSize,w.y*this.worldSize,0,1];return n.transformMat4(I,I,this.pixelMatrix)[3]/this.cameraToCenterDistance},Gi.prototype.getCameraPoint=function(){var w=Math.tan(this._pitch)*(this.cameraToCenterDistance||1);return this.centerPoint.add(new n.Point(0,w))},Gi.prototype.getCameraQueryGeometry=function(w){var I=this.getCameraPoint();if(w.length===1)return[w[0],I];for(var S=I.x,L=I.y,Y=I.x,ie=I.y,ae=0,ge=w;ae=3&&!w.some(function(S){return isNaN(S)})){var I=this._map.dragRotate.isEnabled()&&this._map.touchZoomRotate.isEnabled()?+(w[3]||0):this._map.getBearing();return this._map.jumpTo({center:[+w[2],+w[1]],zoom:+w[0],bearing:I,pitch:+(w[4]||0)}),!0}return!1},Kn.prototype._updateHashUnthrottled=function(){var w=n.window.location.href.replace(/(#.+)?$/,this.getHashString());try{n.window.history.replaceState(n.window.history.state,null,w)}catch{}};var zn={linearity:.3,easing:n.bezier(0,0,.3,1)},Fs=n.extend({deceleration:2500,maxSpeed:1400},zn),wl=n.extend({deceleration:20,maxSpeed:1400},zn),yo=n.extend({deceleration:1e3,maxSpeed:360},zn),tc=n.extend({deceleration:1e3,maxSpeed:90},zn),Tl=function(w){this._map=w,this.clear()};function ll(w,I){(!w.duration||w.duration0&&I-w[0].time>160;)w.shift()},Tl.prototype._onMoveEnd=function(w){if(this._drainInertiaBuffer(),!(this._inertiaBuffer.length<2)){for(var I={zoom:0,bearing:0,pitch:0,pan:new n.Point(0,0),pinchAround:void 0,around:void 0},S=0,L=this._inertiaBuffer;S=this._clickTolerance||this._map.fire(new Sl(w.type,this._map,w))},Pa.prototype.dblclick=function(w){return this._firePreventable(new Sl(w.type,this._map,w))},Pa.prototype.mouseover=function(w){this._map.fire(new Sl(w.type,this._map,w))},Pa.prototype.mouseout=function(w){this._map.fire(new Sl(w.type,this._map,w))},Pa.prototype.touchstart=function(w){return this._firePreventable(new _d(w.type,this._map,w))},Pa.prototype.touchmove=function(w){this._map.fire(new _d(w.type,this._map,w))},Pa.prototype.touchend=function(w){this._map.fire(new _d(w.type,this._map,w))},Pa.prototype.touchcancel=function(w){this._map.fire(new _d(w.type,this._map,w))},Pa.prototype._firePreventable=function(w){if(this._map.fire(w),w.defaultPrevented)return{}},Pa.prototype.isEnabled=function(){return!0},Pa.prototype.isActive=function(){return!1},Pa.prototype.enable=function(){},Pa.prototype.disable=function(){};var Yi=function(w){this._map=w};Yi.prototype.reset=function(){this._delayContextMenu=!1,delete this._contextMenuEvent},Yi.prototype.mousemove=function(w){this._map.fire(new Sl(w.type,this._map,w))},Yi.prototype.mousedown=function(){this._delayContextMenu=!0},Yi.prototype.mouseup=function(){this._delayContextMenu=!1,this._contextMenuEvent&&(this._map.fire(new Sl("contextmenu",this._map,this._contextMenuEvent)),delete this._contextMenuEvent)},Yi.prototype.contextmenu=function(w){this._delayContextMenu?this._contextMenuEvent=w:this._map.fire(new Sl(w.type,this._map,w)),this._map.listens("contextmenu")&&w.preventDefault()},Yi.prototype.isEnabled=function(){return!0},Yi.prototype.isActive=function(){return!1},Yi.prototype.enable=function(){},Yi.prototype.disable=function(){};var Cc=function(w,I){this._map=w,this._el=w.getCanvasContainer(),this._container=w.getContainer(),this._clickTolerance=I.clickTolerance||1};function qd(w,I){for(var S={},L=0;Lthis.numTouches)&&(this.aborted=!0),this.aborted||(this.startTime===void 0&&(this.startTime=w.timeStamp),S.length===this.numTouches&&(this.centroid=function(L){for(var Y=new n.Point(0,0),ie=0,ae=L;ie30)&&(this.aborted=!0)}}},yd.prototype.touchend=function(w,I,S){if((!this.centroid||w.timeStamp-this.startTime>500)&&(this.aborted=!0),S.length===0){var L=!this.aborted&&this.centroid;if(this.reset(),L)return L}};var ru=function(w){this.singleTap=new yd(w),this.numTaps=w.numTaps,this.reset()};ru.prototype.reset=function(){this.lastTime=1/0,delete this.lastTap,this.count=0,this.singleTap.reset()},ru.prototype.touchstart=function(w,I,S){this.singleTap.touchstart(w,I,S)},ru.prototype.touchmove=function(w,I,S){this.singleTap.touchmove(w,I,S)},ru.prototype.touchend=function(w,I,S){var L=this.singleTap.touchend(w,I,S);if(L){var Y=w.timeStamp-this.lastTime<500,ie=!this.lastTap||this.lastTap.dist(L)<30;if(Y&&ie||this.reset(),this.count++,this.lastTime=w.timeStamp,this.lastTap=L,this.count===this.numTaps)return this.reset(),L}};var kl=function(){this._zoomIn=new ru({numTouches:1,numTaps:2}),this._zoomOut=new ru({numTouches:2,numTaps:1}),this.reset()};kl.prototype.reset=function(){this._active=!1,this._zoomIn.reset(),this._zoomOut.reset()},kl.prototype.touchstart=function(w,I,S){this._zoomIn.touchstart(w,I,S),this._zoomOut.touchstart(w,I,S)},kl.prototype.touchmove=function(w,I,S){this._zoomIn.touchmove(w,I,S),this._zoomOut.touchmove(w,I,S)},kl.prototype.touchend=function(w,I,S){var L=this,Y=this._zoomIn.touchend(w,I,S),ie=this._zoomOut.touchend(w,I,S);return Y?(this._active=!0,w.preventDefault(),setTimeout(function(){return L.reset()},0),{cameraAnimation:function(ae){return ae.easeTo({duration:300,zoom:ae.getZoom()+1,around:ae.unproject(Y)},{originalEvent:w})}}):ie?(this._active=!0,w.preventDefault(),setTimeout(function(){return L.reset()},0),{cameraAnimation:function(ae){return ae.easeTo({duration:300,zoom:ae.getZoom()-1,around:ae.unproject(ie)},{originalEvent:w})}}):void 0},kl.prototype.touchcancel=function(){this.reset()},kl.prototype.enable=function(){this._enabled=!0},kl.prototype.disable=function(){this._enabled=!1,this.reset()},kl.prototype.isEnabled=function(){return this._enabled},kl.prototype.isActive=function(){return this._active};var Mp={0:1,2:2},ki=function(w){this.reset(),this._clickTolerance=w.clickTolerance||1};ki.prototype.blur=function(){this.reset()},ki.prototype.reset=function(){this._active=!1,this._moved=!1,delete this._lastPoint,delete this._eventButton},ki.prototype._correctButton=function(w,I){return!1},ki.prototype._move=function(w,I){return{}},ki.prototype.mousedown=function(w,I){if(!this._lastPoint){var S=s.mouseButton(w);this._correctButton(w,S)&&(this._lastPoint=I,this._eventButton=S)}},ki.prototype.mousemoveWindow=function(w,I){var S=this._lastPoint;if(S){if(w.preventDefault(),function(L,Y){var ie=Mp[Y];return L.buttons===void 0||(L.buttons&ie)!==ie}(w,this._eventButton))this.reset();else if(this._moved||!(I.dist(S)0&&(this._active=!0);var L=qd(S,I),Y=new n.Point(0,0),ie=new n.Point(0,0),ae=0;for(var ge in L){var Pe=L[ge],Ue=this._touches[ge];Ue&&(Y._add(Pe),ie._add(Pe.sub(Ue)),ae++,L[ge]=Pe)}if(this._touches=L,!(aeMath.abs(w.x)}var $d=function(w){function I(){w.apply(this,arguments)}return w&&(I.__proto__=w),(I.prototype=Object.create(w&&w.prototype)).constructor=I,I.prototype.reset=function(){w.prototype.reset.call(this),this._valid=void 0,delete this._firstMove,delete this._lastPoints},I.prototype._start=function(S){this._lastPoints=S,yh(S[0].sub(S[1]))&&(this._valid=!1)},I.prototype._move=function(S,L,Y){var ie=S[0].sub(this._lastPoints[0]),ae=S[1].sub(this._lastPoints[1]);if(this._valid=this.gestureBeginsVertically(ie,ae,Y.timeStamp),this._valid)return this._lastPoints=S,this._active=!0,{pitchDelta:(ie.y+ae.y)/2*-.5}},I.prototype.gestureBeginsVertically=function(S,L,Y){if(this._valid!==void 0)return this._valid;var ie=S.mag()>=2,ae=L.mag()>=2;if(ie||ae){if(!ie||!ae)return this._firstMove===void 0&&(this._firstMove=Y),Y-this._firstMove<100&&void 0;var ge=S.y>0==L.y>0;return yh(S)&&yh(L)&&ge}},I}(Mc),$f={panStep:100,bearingStep:15,pitchStep:10},Yu=function(){var w=$f;this._panStep=w.panStep,this._bearingStep=w.bearingStep,this._pitchStep=w.pitchStep,this._rotationDisabled=!1};function gu(w){return w*(2-w)}Yu.prototype.blur=function(){this.reset()},Yu.prototype.reset=function(){this._active=!1},Yu.prototype.keydown=function(w){var I=this;if(!(w.altKey||w.ctrlKey||w.metaKey)){var S=0,L=0,Y=0,ie=0,ae=0;switch(w.keyCode){case 61:case 107:case 171:case 187:S=1;break;case 189:case 109:case 173:S=-1;break;case 37:w.shiftKey?L=-1:(w.preventDefault(),ie=-1);break;case 39:w.shiftKey?L=1:(w.preventDefault(),ie=1);break;case 38:w.shiftKey?Y=1:(w.preventDefault(),ae=-1);break;case 40:w.shiftKey?Y=-1:(w.preventDefault(),ae=1);break;default:return}return this._rotationDisabled&&(L=0,Y=0),{cameraAnimation:function(ge){var Pe=ge.getZoom();ge.easeTo({duration:300,easeId:"keyboardHandler",easing:gu,zoom:S?Math.round(Pe)+S*(w.shiftKey?2:1):Pe,bearing:ge.getBearing()+L*I._bearingStep,pitch:ge.getPitch()+Y*I._pitchStep,offset:[-ie*I._panStep,-ae*I._panStep],center:ge.getCenter()},{originalEvent:w})}}}},Yu.prototype.enable=function(){this._enabled=!0},Yu.prototype.disable=function(){this._enabled=!1,this.reset()},Yu.prototype.isEnabled=function(){return this._enabled},Yu.prototype.isActive=function(){return this._active},Yu.prototype.disableRotation=function(){this._rotationDisabled=!0},Yu.prototype.enableRotation=function(){this._rotationDisabled=!1};var So=function(w,I){this._map=w,this._el=w.getCanvasContainer(),this._handler=I,this._delta=0,this._defaultZoomRate=.01,this._wheelZoomRate=1/450,n.bindAll(["_onTimeout"],this)};So.prototype.setZoomRate=function(w){this._defaultZoomRate=w},So.prototype.setWheelZoomRate=function(w){this._wheelZoomRate=w},So.prototype.isEnabled=function(){return!!this._enabled},So.prototype.isActive=function(){return!!this._active||this._finishTimeout!==void 0},So.prototype.isZooming=function(){return!!this._zooming},So.prototype.enable=function(w){this.isEnabled()||(this._enabled=!0,this._aroundCenter=w&&w.around==="center")},So.prototype.disable=function(){this.isEnabled()&&(this._enabled=!1)},So.prototype.wheel=function(w){if(this.isEnabled()){var I=w.deltaMode===n.window.WheelEvent.DOM_DELTA_LINE?40*w.deltaY:w.deltaY,S=n.browser.now(),L=S-(this._lastWheelEventTime||0);this._lastWheelEventTime=S,I!==0&&I%4.000244140625==0?this._type="wheel":I!==0&&Math.abs(I)<4?this._type="trackpad":L>400?(this._type=null,this._lastValue=I,this._timeout=setTimeout(this._onTimeout,40,w)):this._type||(this._type=Math.abs(L*I)<200?"trackpad":"wheel",this._timeout&&(clearTimeout(this._timeout),this._timeout=null,I+=this._lastValue)),w.shiftKey&&I&&(I/=4),this._type&&(this._lastWheelEvent=w,this._delta-=I,this._active||this._start(w)),w.preventDefault()}},So.prototype._onTimeout=function(w){this._type="wheel",this._delta-=this._lastValue,this._active||this._start(w)},So.prototype._start=function(w){if(this._delta){this._frameId&&(this._frameId=null),this._active=!0,this.isZooming()||(this._zooming=!0),this._finishTimeout&&(clearTimeout(this._finishTimeout),delete this._finishTimeout);var I=s.mousePos(this._el,w);this._around=n.LngLat.convert(this._aroundCenter?this._map.getCenter():this._map.unproject(I)),this._aroundPoint=this._map.transform.locationPoint(this._around),this._frameId||(this._frameId=!0,this._handler._triggerRenderFrame())}},So.prototype.renderFrame=function(){var w=this;if(this._frameId&&(this._frameId=null,this.isActive())){var I=this._map.transform;if(this._delta!==0){var S=this._type==="wheel"&&Math.abs(this._delta)>4.000244140625?this._wheelZoomRate:this._defaultZoomRate,L=2/(1+Math.exp(-Math.abs(this._delta*S)));this._delta<0&&L!==0&&(L=1/L);var Y=typeof this._targetZoom=="number"?I.zoomScale(this._targetZoom):I.scale;this._targetZoom=Math.min(I.maxZoom,Math.max(I.minZoom,I.scaleZoom(Y*L))),this._type==="wheel"&&(this._startZoom=I.zoom,this._easing=this._smoothOutEasing(200)),this._delta=0}var ie,ae=typeof this._targetZoom=="number"?this._targetZoom:I.zoom,ge=this._startZoom,Pe=this._easing,Ue=!1;if(this._type==="wheel"&&ge&&Pe){var ct=Math.min((n.browser.now()-this._lastWheelEventTime)/200,1),ft=Pe(ct);ie=n.number(ge,ae,ft),ct<1?this._frameId||(this._frameId=!0):Ue=!0}else ie=ae,Ue=!0;return this._active=!0,Ue&&(this._active=!1,this._finishTimeout=setTimeout(function(){w._zooming=!1,w._handler._triggerRenderFrame(),delete w._targetZoom,delete w._finishTimeout},200)),{noInertia:!0,needsRenderFrame:!Ue,zoomDelta:ie-I.zoom,around:this._aroundPoint,originalEvent:this._lastWheelEvent}}},So.prototype._smoothOutEasing=function(w){var I=n.ease;if(this._prevEase){var S=this._prevEase,L=(n.browser.now()-S.start)/S.duration,Y=S.easing(L+.01)-S.easing(L),ie=.27/Math.sqrt(Y*Y+1e-4)*.01,ae=Math.sqrt(.0729-ie*ie);I=n.bezier(ie,ae,.25,1)}return this._prevEase={start:n.browser.now(),duration:w,easing:I},I},So.prototype.blur=function(){this.reset()},So.prototype.reset=function(){this._active=!1};var Lu=function(w,I){this._clickZoom=w,this._tapZoom=I};Lu.prototype.enable=function(){this._clickZoom.enable(),this._tapZoom.enable()},Lu.prototype.disable=function(){this._clickZoom.disable(),this._tapZoom.disable()},Lu.prototype.isEnabled=function(){return this._clickZoom.isEnabled()&&this._tapZoom.isEnabled()},Lu.prototype.isActive=function(){return this._clickZoom.isActive()||this._tapZoom.isActive()};var Nl=function(){this.reset()};Nl.prototype.reset=function(){this._active=!1},Nl.prototype.blur=function(){this.reset()},Nl.prototype.dblclick=function(w,I){return w.preventDefault(),{cameraAnimation:function(S){S.easeTo({duration:300,zoom:S.getZoom()+(w.shiftKey?-1:1),around:S.unproject(I)},{originalEvent:w})}}},Nl.prototype.enable=function(){this._enabled=!0},Nl.prototype.disable=function(){this._enabled=!1,this.reset()},Nl.prototype.isEnabled=function(){return this._enabled},Nl.prototype.isActive=function(){return this._active};var ha=function(){this._tap=new ru({numTouches:1,numTaps:1}),this.reset()};ha.prototype.reset=function(){this._active=!1,delete this._swipePoint,delete this._swipeTouch,delete this._tapTime,this._tap.reset()},ha.prototype.touchstart=function(w,I,S){this._swipePoint||(this._tapTime&&w.timeStamp-this._tapTime>500&&this.reset(),this._tapTime?S.length>0&&(this._swipePoint=I[0],this._swipeTouch=S[0].identifier):this._tap.touchstart(w,I,S))},ha.prototype.touchmove=function(w,I,S){if(this._tapTime){if(this._swipePoint){if(S[0].identifier!==this._swipeTouch)return;var L=I[0],Y=L.y-this._swipePoint.y;return this._swipePoint=L,w.preventDefault(),this._active=!0,{zoomDelta:Y/128}}}else this._tap.touchmove(w,I,S)},ha.prototype.touchend=function(w,I,S){this._tapTime?this._swipePoint&&S.length===0&&this.reset():this._tap.touchend(w,I,S)&&(this._tapTime=w.timeStamp)},ha.prototype.touchcancel=function(){this.reset()},ha.prototype.enable=function(){this._enabled=!0},ha.prototype.disable=function(){this._enabled=!1,this.reset()},ha.prototype.isEnabled=function(){return this._enabled},ha.prototype.isActive=function(){return this._active};var xf=function(w,I,S){this._el=w,this._mousePan=I,this._touchPan=S};xf.prototype.enable=function(w){this._inertiaOptions=w||{},this._mousePan.enable(),this._touchPan.enable(),this._el.classList.add("mapboxgl-touch-drag-pan")},xf.prototype.disable=function(){this._mousePan.disable(),this._touchPan.disable(),this._el.classList.remove("mapboxgl-touch-drag-pan")},xf.prototype.isEnabled=function(){return this._mousePan.isEnabled()&&this._touchPan.isEnabled()},xf.prototype.isActive=function(){return this._mousePan.isActive()||this._touchPan.isActive()};var Xf=function(w,I,S){this._pitchWithRotate=w.pitchWithRotate,this._mouseRotate=I,this._mousePitch=S};Xf.prototype.enable=function(){this._mouseRotate.enable(),this._pitchWithRotate&&this._mousePitch.enable()},Xf.prototype.disable=function(){this._mouseRotate.disable(),this._mousePitch.disable()},Xf.prototype.isEnabled=function(){return this._mouseRotate.isEnabled()&&(!this._pitchWithRotate||this._mousePitch.isEnabled())},Xf.prototype.isActive=function(){return this._mouseRotate.isActive()||this._mousePitch.isActive()};var Pc=function(w,I,S,L){this._el=w,this._touchZoom=I,this._touchRotate=S,this._tapDragZoom=L,this._rotationDisabled=!1,this._enabled=!0};Pc.prototype.enable=function(w){this._touchZoom.enable(w),this._rotationDisabled||this._touchRotate.enable(w),this._tapDragZoom.enable(),this._el.classList.add("mapboxgl-touch-zoom-rotate")},Pc.prototype.disable=function(){this._touchZoom.disable(),this._touchRotate.disable(),this._tapDragZoom.disable(),this._el.classList.remove("mapboxgl-touch-zoom-rotate")},Pc.prototype.isEnabled=function(){return this._touchZoom.isEnabled()&&(this._rotationDisabled||this._touchRotate.isEnabled())&&this._tapDragZoom.isEnabled()},Pc.prototype.isActive=function(){return this._touchZoom.isActive()||this._touchRotate.isActive()||this._tapDragZoom.isActive()},Pc.prototype.disableRotation=function(){this._rotationDisabled=!0,this._touchRotate.disable()},Pc.prototype.enableRotation=function(){this._rotationDisabled=!1,this._touchZoom.isEnabled()&&this._touchRotate.enable()};var El=function(w){return w.zoom||w.drag||w.pitch||w.rotate},Ku=function(w){function I(){w.apply(this,arguments)}return w&&(I.__proto__=w),(I.prototype=Object.create(w&&w.prototype)).constructor=I,I}(n.Event);function An(w){return w.panDelta&&w.panDelta.mag()||w.zoomDelta||w.bearingDelta||w.pitchDelta}var nn=function(w,I){this._map=w,this._el=this._map.getCanvasContainer(),this._handlers=[],this._handlersById={},this._changes=[],this._inertia=new Tl(w),this._bearingSnap=I.bearingSnap,this._previousActiveHandlers={},this._eventsInProgress={},this._addDefaultHandlers(I),n.bindAll(["handleEvent","handleWindowEvent"],this);var S=this._el;this._listeners=[[S,"touchstart",{passive:!0}],[S,"touchmove",{passive:!1}],[S,"touchend",void 0],[S,"touchcancel",void 0],[S,"mousedown",void 0],[S,"mousemove",void 0],[S,"mouseup",void 0],[n.window.document,"mousemove",{capture:!0}],[n.window.document,"mouseup",void 0],[S,"mouseover",void 0],[S,"mouseout",void 0],[S,"dblclick",void 0],[S,"click",void 0],[S,"keydown",{capture:!1}],[S,"keyup",void 0],[S,"wheel",{passive:!1}],[S,"contextmenu",void 0],[n.window,"blur",void 0]];for(var L=0,Y=this._listeners;Lae?Math.min(2,Wr):Math.max(.5,Wr),bn=Math.pow($i,1-Ci),Tn=ie.unproject(kr.add(Br.mult(Ci*bn)).mult(Si));ie.setLocationAtPoint(ie.renderWorldCopies?Tn.wrap():Tn,Wt)}Y._fireMoveEvents(L)},function(Ci){Y._afterEase(L,Ci)},S),this},I.prototype._prepareEase=function(S,L,Y){Y===void 0&&(Y={}),this._moving=!0,L||Y.moving||this.fire(new n.Event("movestart",S)),this._zooming&&!Y.zooming&&this.fire(new n.Event("zoomstart",S)),this._rotating&&!Y.rotating&&this.fire(new n.Event("rotatestart",S)),this._pitching&&!Y.pitching&&this.fire(new n.Event("pitchstart",S))},I.prototype._fireMoveEvents=function(S){this.fire(new n.Event("move",S)),this._zooming&&this.fire(new n.Event("zoom",S)),this._rotating&&this.fire(new n.Event("rotate",S)),this._pitching&&this.fire(new n.Event("pitch",S))},I.prototype._afterEase=function(S,L){if(!this._easeId||!L||this._easeId!==L){delete this._easeId;var Y=this._zooming,ie=this._rotating,ae=this._pitching;this._moving=!1,this._zooming=!1,this._rotating=!1,this._pitching=!1,this._padding=!1,Y&&this.fire(new n.Event("zoomend",S)),ie&&this.fire(new n.Event("rotateend",S)),ae&&this.fire(new n.Event("pitchend",S)),this.fire(new n.Event("moveend",S))}},I.prototype.flyTo=function(S,L){var Y=this;if(!S.essential&&n.browser.prefersReducedMotion){var ie=n.pick(S,["center","zoom","bearing","pitch","around"]);return this.jumpTo(ie,L)}this.stop(),S=n.extend({offset:[0,0],speed:1.2,curve:1.42,easing:n.ease},S);var ae=this.transform,ge=this.getZoom(),Pe=this.getBearing(),Ue=this.getPitch(),ct=this.getPadding(),ft="zoom"in S?n.clamp(+S.zoom,ae.minZoom,ae.maxZoom):ge,gt="bearing"in S?this._normalizeBearing(S.bearing,Pe):Pe,ut="pitch"in S?+S.pitch:Ue,Pt="padding"in S?S.padding:ae.padding,Wt=ae.zoomScale(ft-ge),Et=n.Point.convert(S.offset),Zt=ae.centerPoint.add(Et),ir=ae.pointLocation(Zt),dr=n.LngLat.convert(S.center||ir);this._normalizeCenter(dr);var kr=ae.project(ir),Br=ae.project(dr).sub(kr),Wr=S.curve,ai=Math.max(ae.width,ae.height),Ci=ai/Wt,Si=Br.mag();if("minZoom"in S){var $i=n.clamp(Math.min(S.minZoom,ge,ft),ae.minZoom,ae.maxZoom),bn=ai/ae.zoomScale($i-ge);Wr=Math.sqrt(bn/Si*2)}var Tn=Wr*Wr;function xn(Dn){var Rn=(Ci*Ci-ai*ai+(Dn?-1:1)*Tn*Tn*Si*Si)/(2*(Dn?Ci:ai)*Tn*Si);return Math.log(Math.sqrt(Rn*Rn+1)-Rn)}function zo(Dn){return(Math.exp(Dn)-Math.exp(-Dn))/2}function qi(Dn){return(Math.exp(Dn)+Math.exp(-Dn))/2}var Sn=xn(0),$n=function(Dn){return qi(Sn)/qi(Sn+Wr*Dn)},Hi=function(Dn){return ai*((qi(Sn)*(zo(Rn=Sn+Wr*Dn)/qi(Rn))-zo(Sn))/Tn)/Si;var Rn},Gn=(xn(1)-Sn)/Wr;if(Math.abs(Si)<1e-6||!isFinite(Gn)){if(Math.abs(ai-Ci)<1e-6)return this.easeTo(S,L);var eo=CiS.maxDuration&&(S.duration=0),this._zooming=!0,this._rotating=Pe!==gt,this._pitching=ut!==Ue,this._padding=!ae.isPaddingEqual(Pt),this._prepareEase(L,!1),this._ease(function(Dn){var Rn=Dn*Gn,Ml=1/$n(Rn);ae.zoom=Dn===1?ft:ge+ae.scaleZoom(Ml),Y._rotating&&(ae.bearing=n.number(Pe,gt,Dn)),Y._pitching&&(ae.pitch=n.number(Ue,ut,Dn)),Y._padding&&(ae.interpolatePadding(ct,Pt,Dn),Zt=ae.centerPoint.add(Et));var Ga=Dn===1?dr:ae.unproject(kr.add(Br.mult(Hi(Rn))).mult(Ml));ae.setLocationAtPoint(ae.renderWorldCopies?Ga.wrap():Ga,Zt),Y._fireMoveEvents(L)},function(){return Y._afterEase(L)},S),this},I.prototype.isEasing=function(){return!!this._easeFrameId},I.prototype.stop=function(){return this._stop()},I.prototype._stop=function(S,L){if(this._easeFrameId&&(this._cancelRenderFrame(this._easeFrameId),delete this._easeFrameId,delete this._onEaseFrame),this._onEaseEnd){var Y=this._onEaseEnd;delete this._onEaseEnd,Y.call(this,L)}if(!S){var ie=this.handlers;ie&&ie.stop(!1)}return this},I.prototype._ease=function(S,L,Y){Y.animate===!1||Y.duration===0?(S(1),L()):(this._easeStart=n.browser.now(),this._easeOptions=Y,this._onEaseFrame=S,this._onEaseEnd=L,this._easeFrameId=this._requestRenderFrame(this._renderFrameCallback))},I.prototype._renderFrameCallback=function(){var S=Math.min((n.browser.now()-this._easeStart)/this._easeOptions.duration,1);this._onEaseFrame(this._easeOptions.easing(S)),S<1?this._easeFrameId=this._requestRenderFrame(this._renderFrameCallback):this.stop()},I.prototype._normalizeBearing=function(S,L){S=n.wrap(S,-180,180);var Y=Math.abs(S-L);return Math.abs(S-360-L)180?-360:Y<-180?360:0}},I}(n.Evented),Vc=function(w){w===void 0&&(w={}),this.options=w,n.bindAll(["_toggleAttribution","_updateEditLink","_updateData","_updateCompact"],this)};Vc.prototype.getDefaultPosition=function(){return"bottom-right"},Vc.prototype.onAdd=function(w){var I=this.options&&this.options.compact;return this._map=w,this._container=s.create("div","mapboxgl-ctrl mapboxgl-ctrl-attrib"),this._compactButton=s.create("button","mapboxgl-ctrl-attrib-button",this._container),this._compactButton.addEventListener("click",this._toggleAttribution),this._setElementTitle(this._compactButton,"ToggleAttribution"),this._innerContainer=s.create("div","mapboxgl-ctrl-attrib-inner",this._container),this._innerContainer.setAttribute("role","list"),I&&this._container.classList.add("mapboxgl-compact"),this._updateAttributions(),this._updateEditLink(),this._map.on("styledata",this._updateData),this._map.on("sourcedata",this._updateData),this._map.on("moveend",this._updateEditLink),I===void 0&&(this._map.on("resize",this._updateCompact),this._updateCompact()),this._container},Vc.prototype.onRemove=function(){s.remove(this._container),this._map.off("styledata",this._updateData),this._map.off("sourcedata",this._updateData),this._map.off("moveend",this._updateEditLink),this._map.off("resize",this._updateCompact),this._map=void 0,this._attribHTML=void 0},Vc.prototype._setElementTitle=function(w,I){var S=this._map._getUIString("AttributionControl."+I);w.title=S,w.setAttribute("aria-label",S)},Vc.prototype._toggleAttribution=function(){this._container.classList.contains("mapboxgl-compact-show")?(this._container.classList.remove("mapboxgl-compact-show"),this._compactButton.setAttribute("aria-pressed","false")):(this._container.classList.add("mapboxgl-compact-show"),this._compactButton.setAttribute("aria-pressed","true"))},Vc.prototype._updateEditLink=function(){var w=this._editLink;w||(w=this._editLink=this._container.querySelector(".mapbox-improve-map"));var I=[{key:"owner",value:this.styleOwner},{key:"id",value:this.styleId},{key:"access_token",value:this._map._requestManager._customAccessToken||n.config.ACCESS_TOKEN}];if(w){var S=I.reduce(function(L,Y,ie){return Y.value&&(L+=Y.key+"="+Y.value+(ie=0)return!1;return!0})).join(" | ");ae!==this._attribHTML&&(this._attribHTML=ae,w.length?(this._innerContainer.innerHTML=ae,this._container.classList.remove("mapboxgl-attrib-empty")):this._container.classList.add("mapboxgl-attrib-empty"),this._editLink=null)}},Vc.prototype._updateCompact=function(){this._map.getCanvasContainer().offsetWidth<=640?this._container.classList.add("mapboxgl-compact"):this._container.classList.remove("mapboxgl-compact","mapboxgl-compact-show")};var Au=function(){n.bindAll(["_updateLogo"],this),n.bindAll(["_updateCompact"],this)};Au.prototype.onAdd=function(w){this._map=w,this._container=s.create("div","mapboxgl-ctrl");var I=s.create("a","mapboxgl-ctrl-logo");return I.target="_blank",I.rel="noopener nofollow",I.href="https://www.mapbox.com/",I.setAttribute("aria-label",this._map._getUIString("LogoControl.Title")),I.setAttribute("rel","noopener nofollow"),this._container.appendChild(I),this._container.style.display="none",this._map.on("sourcedata",this._updateLogo),this._updateLogo(),this._map.on("resize",this._updateCompact),this._updateCompact(),this._container},Au.prototype.onRemove=function(){s.remove(this._container),this._map.off("sourcedata",this._updateLogo),this._map.off("resize",this._updateCompact)},Au.prototype.getDefaultPosition=function(){return"bottom-left"},Au.prototype._updateLogo=function(w){w&&w.sourceDataType!=="metadata"||(this._container.style.display=this._logoRequired()?"block":"none")},Au.prototype._logoRequired=function(){if(this._map.style){var w=this._map.style.sourceCaches;for(var I in w)if(w[I].getSource().mapbox_logo)return!0;return!1}},Au.prototype._updateCompact=function(){var w=this._container.children;if(w.length){var I=w[0];this._map.getCanvasContainer().offsetWidth<250?I.classList.add("mapboxgl-compact"):I.classList.remove("mapboxgl-compact")}};var Cl=function(){this._queue=[],this._id=0,this._cleared=!1,this._currentlyRunning=!1};Cl.prototype.add=function(w){var I=++this._id;return this._queue.push({callback:w,id:I,cancelled:!1}),I},Cl.prototype.remove=function(w){for(var I=this._currentlyRunning,S=0,L=I?this._queue.concat(I):this._queue;SL.maxZoom)throw new Error("maxZoom must be greater than or equal to minZoom");if(L.minPitch!=null&&L.maxPitch!=null&&L.minPitch>L.maxPitch)throw new Error("maxPitch must be greater than or equal to minPitch");if(L.minPitch!=null&&L.minPitch<0)throw new Error("minPitch must be greater than or equal to 0");if(L.maxPitch!=null&&L.maxPitch>60)throw new Error("maxPitch must be less than or equal to 60");var ie=new Gi(L.minZoom,L.maxZoom,L.minPitch,L.maxPitch,L.renderWorldCopies);if(w.call(this,ie,L),this._interactive=L.interactive,this._maxTileCacheSize=L.maxTileCacheSize,this._failIfMajorPerformanceCaveat=L.failIfMajorPerformanceCaveat,this._preserveDrawingBuffer=L.preserveDrawingBuffer,this._antialias=L.antialias,this._trackResize=L.trackResize,this._bearingSnap=L.bearingSnap,this._refreshExpiredTiles=L.refreshExpiredTiles,this._fadeDuration=L.fadeDuration,this._crossSourceCollisions=L.crossSourceCollisions,this._crossFadingFactor=1,this._collectResourceTiming=L.collectResourceTiming,this._renderTaskQueue=new Cl,this._controls=[],this._mapId=n.uniqueId(),this._locale=n.extend({},Yh,L.locale),this._clickTolerance=L.clickTolerance,this._requestManager=new n.RequestManager(L.transformRequest,L.accessToken),typeof L.container=="string"){if(this._container=n.window.document.getElementById(L.container),!this._container)throw new Error("Container '"+L.container+"' not found.")}else{if(!(L.container instanceof ja))throw new Error("Invalid type: 'container' must be a String or HTMLElement.");this._container=L.container}if(L.maxBounds&&this.setMaxBounds(L.maxBounds),n.bindAll(["_onWindowOnline","_onWindowResize","_onMapScroll","_contextLost","_contextRestored"],this),this._setupContainer(),this._setupPainter(),this.painter===void 0)throw new Error("Failed to initialize WebGL.");this.on("move",function(){return Y._update(!1)}),this.on("moveend",function(){return Y._update(!1)}),this.on("zoom",function(){return Y._update(!0)}),n.window!==void 0&&(n.window.addEventListener("online",this._onWindowOnline,!1),n.window.addEventListener("resize",this._onWindowResize,!1),n.window.addEventListener("orientationchange",this._onWindowResize,!1)),this.handlers=new nn(this,L),this._hash=L.hash&&new Kn(typeof L.hash=="string"&&L.hash||void 0).addTo(this),this._hash&&this._hash._onHashChange()||(this.jumpTo({center:L.center,zoom:L.zoom,bearing:L.bearing,pitch:L.pitch}),L.bounds&&(this.resize(),this.fitBounds(L.bounds,n.extend({},L.fitBoundsOptions,{duration:0})))),this.resize(),this._localIdeographFontFamily=L.localIdeographFontFamily,L.style&&this.setStyle(L.style,{localIdeographFontFamily:L.localIdeographFontFamily}),L.attributionControl&&this.addControl(new Vc({customAttribution:L.customAttribution})),this.addControl(new Au,L.logoPosition),this.on("style.load",function(){Y.transform.unmodified&&Y.jumpTo(Y.style.stylesheet)}),this.on("data",function(ae){Y._update(ae.dataType==="style"),Y.fire(new n.Event(ae.dataType+"data",ae))}),this.on("dataloading",function(ae){Y.fire(new n.Event(ae.dataType+"dataloading",ae))})}w&&(I.__proto__=w),(I.prototype=Object.create(w&&w.prototype)).constructor=I;var S={showTileBoundaries:{configurable:!0},showPadding:{configurable:!0},showCollisionBoxes:{configurable:!0},showOverdrawInspector:{configurable:!0},repaint:{configurable:!0},vertices:{configurable:!0},version:{configurable:!0}};return I.prototype._getMapId=function(){return this._mapId},I.prototype.addControl=function(L,Y){if(Y===void 0&&(Y=L.getDefaultPosition?L.getDefaultPosition():"top-right"),!L||!L.onAdd)return this.fire(new n.ErrorEvent(new Error("Invalid argument to map.addControl(). Argument must be a control with onAdd and onRemove methods.")));var ie=L.onAdd(this);this._controls.push(L);var ae=this._controlPositions[Y];return Y.indexOf("bottom")!==-1?ae.insertBefore(ie,ae.firstChild):ae.appendChild(ie),this},I.prototype.removeControl=function(L){if(!L||!L.onRemove)return this.fire(new n.ErrorEvent(new Error("Invalid argument to map.removeControl(). Argument must be a control with onAdd and onRemove methods.")));var Y=this._controls.indexOf(L);return Y>-1&&this._controls.splice(Y,1),L.onRemove(this),this},I.prototype.hasControl=function(L){return this._controls.indexOf(L)>-1},I.prototype.resize=function(L){var Y=this._containerDimensions(),ie=Y[0],ae=Y[1];if(ie===this.transform.width&&ae===this.transform.height)return this;this._resizeCanvas(ie,ae),this.transform.resize(ie,ae),this.painter.resize(ie,ae);var ge=!this._moving;return ge&&this.fire(new n.Event("movestart",L)).fire(new n.Event("move",L)),this.fire(new n.Event("resize",L)),ge&&this.fire(new n.Event("moveend",L)),this},I.prototype.getBounds=function(){return this.transform.getBounds()},I.prototype.getMaxBounds=function(){return this.transform.getMaxBounds()},I.prototype.setMaxBounds=function(L){return this.transform.setMaxBounds(n.LngLatBounds.convert(L)),this._update()},I.prototype.setMinZoom=function(L){if((L=L??-2)>=-2&&L<=this.transform.maxZoom)return this.transform.minZoom=L,this._update(),this.getZoom()=this.transform.minZoom)return this.transform.maxZoom=L,this._update(),this.getZoom()>L&&this.setZoom(L),this;throw new Error("maxZoom must be greater than the current minZoom")},I.prototype.getMaxZoom=function(){return this.transform.maxZoom},I.prototype.setMinPitch=function(L){if((L=L??0)<0)throw new Error("minPitch must be greater than or equal to 0");if(L>=0&&L<=this.transform.maxPitch)return this.transform.minPitch=L,this._update(),this.getPitch()60)throw new Error("maxPitch must be less than or equal to 60");if(L>=this.transform.minPitch)return this.transform.maxPitch=L,this._update(),this.getPitch()>L&&this.setPitch(L),this;throw new Error("maxPitch must be greater than the current minPitch")},I.prototype.getMaxPitch=function(){return this.transform.maxPitch},I.prototype.getRenderWorldCopies=function(){return this.transform.renderWorldCopies},I.prototype.setRenderWorldCopies=function(L){return this.transform.renderWorldCopies=L,this._update()},I.prototype.project=function(L){return this.transform.locationPoint(n.LngLat.convert(L))},I.prototype.unproject=function(L){return this.transform.pointLocation(n.Point.convert(L))},I.prototype.isMoving=function(){return this._moving||this.handlers.isMoving()},I.prototype.isZooming=function(){return this._zooming||this.handlers.isZooming()},I.prototype.isRotating=function(){return this._rotating||this.handlers.isRotating()},I.prototype._createDelegatedListener=function(L,Y,ie){var ae,ge=this;if(L==="mouseenter"||L==="mouseover"){var Pe=!1;return{layer:Y,listener:ie,delegates:{mousemove:function(ct){var ft=ge.getLayer(Y)?ge.queryRenderedFeatures(ct.point,{layers:[Y]}):[];ft.length?Pe||(Pe=!0,ie.call(ge,new Sl(L,ge,ct.originalEvent,{features:ft}))):Pe=!1},mouseout:function(){Pe=!1}}}}if(L==="mouseleave"||L==="mouseout"){var Ue=!1;return{layer:Y,listener:ie,delegates:{mousemove:function(ct){(ge.getLayer(Y)?ge.queryRenderedFeatures(ct.point,{layers:[Y]}):[]).length?Ue=!0:Ue&&(Ue=!1,ie.call(ge,new Sl(L,ge,ct.originalEvent)))},mouseout:function(ct){Ue&&(Ue=!1,ie.call(ge,new Sl(L,ge,ct.originalEvent)))}}}}return{layer:Y,listener:ie,delegates:(ae={},ae[L]=function(ct){var ft=ge.getLayer(Y)?ge.queryRenderedFeatures(ct.point,{layers:[Y]}):[];ft.length&&(ct.features=ft,ie.call(ge,ct),delete ct.features)},ae)}},I.prototype.on=function(L,Y,ie){if(ie===void 0)return w.prototype.on.call(this,L,Y);var ae=this._createDelegatedListener(L,Y,ie);for(var ge in this._delegatedListeners=this._delegatedListeners||{},this._delegatedListeners[L]=this._delegatedListeners[L]||[],this._delegatedListeners[L].push(ae),ae.delegates)this.on(ge,ae.delegates[ge]);return this},I.prototype.once=function(L,Y,ie){if(ie===void 0)return w.prototype.once.call(this,L,Y);var ae=this._createDelegatedListener(L,Y,ie);for(var ge in ae.delegates)this.once(ge,ae.delegates[ge]);return this},I.prototype.off=function(L,Y,ie){var ae=this;return ie===void 0?w.prototype.off.call(this,L,Y):(this._delegatedListeners&&this._delegatedListeners[L]&&function(ge){for(var Pe=ge[L],Ue=0;Ue180;){var ae=S.locationPoint(w);if(ae.x>=0&&ae.y>=0&&ae.x<=S.width&&ae.y<=S.height)break;w.lng>S.center.lng?w.lng-=360:w.lng+=360}return w}Rc.prototype.down=function(w,I){this.mouseRotate.mousedown(w,I),this.mousePitch&&this.mousePitch.mousedown(w,I),s.disableDrag()},Rc.prototype.move=function(w,I){var S=this.map,L=this.mouseRotate.mousemoveWindow(w,I);if(L&&L.bearingDelta&&S.setBearing(S.getBearing()+L.bearingDelta),this.mousePitch){var Y=this.mousePitch.mousemoveWindow(w,I);Y&&Y.pitchDelta&&S.setPitch(S.getPitch()+Y.pitchDelta)}},Rc.prototype.off=function(){var w=this.element;s.removeEventListener(w,"mousedown",this.mousedown),s.removeEventListener(w,"touchstart",this.touchstart,{passive:!1}),s.removeEventListener(w,"touchmove",this.touchmove),s.removeEventListener(w,"touchend",this.touchend),s.removeEventListener(w,"touchcancel",this.reset),this.offTemp()},Rc.prototype.offTemp=function(){s.enableDrag(),s.removeEventListener(n.window,"mousemove",this.mousemove),s.removeEventListener(n.window,"mouseup",this.mouseup)},Rc.prototype.mousedown=function(w){this.down(n.extend({},w,{ctrlKey:!0,preventDefault:function(){return w.preventDefault()}}),s.mousePos(this.element,w)),s.addEventListener(n.window,"mousemove",this.mousemove),s.addEventListener(n.window,"mouseup",this.mouseup)},Rc.prototype.mousemove=function(w){this.move(w,s.mousePos(this.element,w))},Rc.prototype.mouseup=function(w){this.mouseRotate.mouseupWindow(w),this.mousePitch&&this.mousePitch.mouseupWindow(w),this.offTemp()},Rc.prototype.touchstart=function(w){w.targetTouches.length!==1?this.reset():(this._startPos=this._lastPos=s.touchPos(this.element,w.targetTouches)[0],this.down({type:"mousedown",button:0,ctrlKey:!0,preventDefault:function(){return w.preventDefault()}},this._startPos))},Rc.prototype.touchmove=function(w){w.targetTouches.length!==1?this.reset():(this._lastPos=s.touchPos(this.element,w.targetTouches)[0],this.move({preventDefault:function(){return w.preventDefault()}},this._lastPos))},Rc.prototype.touchend=function(w){w.targetTouches.length===0&&this._startPos&&this._lastPos&&this._startPos.dist(this._lastPos)=L}this._isDragging&&(this._pos=S.point.sub(this._positionDelta),this._lngLat=this._map.unproject(this._pos),this.setLngLat(this._lngLat),this._element.style.pointerEvents="none",this._state==="pending"&&(this._state="active",this.fire(new n.Event("dragstart"))),this.fire(new n.Event("drag")))},I.prototype._onUp=function(){this._element.style.pointerEvents="auto",this._positionDelta=null,this._pointerdownPos=null,this._isDragging=!1,this._map.off("mousemove",this._onMove),this._map.off("touchmove",this._onMove),this._state==="active"&&this.fire(new n.Event("dragend")),this._state="inactive"},I.prototype._addDragHandler=function(S){this._element.contains(S.originalEvent.target)&&(S.preventDefault(),this._positionDelta=S.point.sub(this._pos).add(this._offset),this._pointerdownPos=S.point,this._state="pending",this._map.on("mousemove",this._onMove),this._map.on("touchmove",this._onMove),this._map.once("mouseup",this._onUp),this._map.once("touchend",this._onUp))},I.prototype.setDraggable=function(S){return this._draggable=!!S,this._map&&(S?(this._map.on("mousedown",this._addDragHandler),this._map.on("touchstart",this._addDragHandler)):(this._map.off("mousedown",this._addDragHandler),this._map.off("touchstart",this._addDragHandler))),this},I.prototype.isDraggable=function(){return this._draggable},I.prototype.setRotation=function(S){return this._rotation=S||0,this._update(),this},I.prototype.getRotation=function(){return this._rotation},I.prototype.setRotationAlignment=function(S){return this._rotationAlignment=S||"auto",this._update(),this},I.prototype.getRotationAlignment=function(){return this._rotationAlignment},I.prototype.setPitchAlignment=function(S){return this._pitchAlignment=S&&S!=="auto"?S:this._rotationAlignment,this._update(),this},I.prototype.getPitchAlignment=function(){return this._pitchAlignment},I}(n.Evented),mm={positionOptions:{enableHighAccuracy:!1,maximumAge:0,timeout:6e3},fitBoundsOptions:{maxZoom:15},trackUserLocation:!1,showAccuracyCircle:!0,showUserLocation:!0},xh=0,Qu=!1,Yd=function(w){function I(S){w.call(this),this.options=n.extend({},mm,S),n.bindAll(["_onSuccess","_onError","_onZoom","_finish","_setupUI","_updateCamera","_updateMarker"],this)}return w&&(I.__proto__=w),(I.prototype=Object.create(w&&w.prototype)).constructor=I,I.prototype.onAdd=function(S){var L;return this._map=S,this._container=s.create("div","mapboxgl-ctrl mapboxgl-ctrl-group"),L=this._setupUI,Zd!==void 0?L(Zd):n.window.navigator.permissions!==void 0?n.window.navigator.permissions.query({name:"geolocation"}).then(function(Y){L(Zd=Y.state!=="denied")}):L(Zd=!!n.window.navigator.geolocation),this._container},I.prototype.onRemove=function(){this._geolocationWatchID!==void 0&&(n.window.navigator.geolocation.clearWatch(this._geolocationWatchID),this._geolocationWatchID=void 0),this.options.showUserLocation&&this._userLocationDotMarker&&this._userLocationDotMarker.remove(),this.options.showAccuracyCircle&&this._accuracyCircleMarker&&this._accuracyCircleMarker.remove(),s.remove(this._container),this._map.off("zoom",this._onZoom),this._map=void 0,xh=0,Qu=!1},I.prototype._isOutOfMapMaxBounds=function(S){var L=this._map.getMaxBounds(),Y=S.coords;return L&&(Y.longitudeL.getEast()||Y.latitudeL.getNorth())},I.prototype._setErrorState=function(){switch(this._watchState){case"WAITING_ACTIVE":this._watchState="ACTIVE_ERROR",this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-active"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-active-error");break;case"ACTIVE_LOCK":this._watchState="ACTIVE_ERROR",this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-active"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-active-error"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-waiting");break;case"BACKGROUND":this._watchState="BACKGROUND_ERROR",this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-background"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-background-error"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-waiting")}},I.prototype._onSuccess=function(S){if(this._map){if(this._isOutOfMapMaxBounds(S))return this._setErrorState(),this.fire(new n.Event("outofmaxbounds",S)),this._updateMarker(),void this._finish();if(this.options.trackUserLocation)switch(this._lastKnownPosition=S,this._watchState){case"WAITING_ACTIVE":case"ACTIVE_LOCK":case"ACTIVE_ERROR":this._watchState="ACTIVE_LOCK",this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-waiting"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-active-error"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-active");break;case"BACKGROUND":case"BACKGROUND_ERROR":this._watchState="BACKGROUND",this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-waiting"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-background-error"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-background")}this.options.showUserLocation&&this._watchState!=="OFF"&&this._updateMarker(S),this.options.trackUserLocation&&this._watchState!=="ACTIVE_LOCK"||this._updateCamera(S),this.options.showUserLocation&&this._dotElement.classList.remove("mapboxgl-user-location-dot-stale"),this.fire(new n.Event("geolocate",S)),this._finish()}},I.prototype._updateCamera=function(S){var L=new n.LngLat(S.coords.longitude,S.coords.latitude),Y=S.coords.accuracy,ie=this._map.getBearing(),ae=n.extend({bearing:ie},this.options.fitBoundsOptions);this._map.fitBounds(L.toBounds(Y),ae,{geolocateSource:!0})},I.prototype._updateMarker=function(S){if(S){var L=new n.LngLat(S.coords.longitude,S.coords.latitude);this._accuracyCircleMarker.setLngLat(L).addTo(this._map),this._userLocationDotMarker.setLngLat(L).addTo(this._map),this._accuracy=S.coords.accuracy,this.options.showUserLocation&&this.options.showAccuracyCircle&&this._updateCircleRadius()}else this._userLocationDotMarker.remove(),this._accuracyCircleMarker.remove()},I.prototype._updateCircleRadius=function(){var S=this._map._container.clientHeight/2,L=this._map.unproject([0,S]),Y=this._map.unproject([1,S]),ie=L.distanceTo(Y),ae=Math.ceil(2*this._accuracy/ie);this._circleElement.style.width=ae+"px",this._circleElement.style.height=ae+"px"},I.prototype._onZoom=function(){this.options.showUserLocation&&this.options.showAccuracyCircle&&this._updateCircleRadius()},I.prototype._onError=function(S){if(this._map){if(this.options.trackUserLocation)if(S.code===1){this._watchState="OFF",this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-waiting"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-active"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-active-error"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-background"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-background-error"),this._geolocateButton.disabled=!0;var L=this._map._getUIString("GeolocateControl.LocationNotAvailable");this._geolocateButton.title=L,this._geolocateButton.setAttribute("aria-label",L),this._geolocationWatchID!==void 0&&this._clearWatch()}else{if(S.code===3&&Qu)return;this._setErrorState()}this._watchState!=="OFF"&&this.options.showUserLocation&&this._dotElement.classList.add("mapboxgl-user-location-dot-stale"),this.fire(new n.Event("error",S)),this._finish()}},I.prototype._finish=function(){this._timeoutId&&clearTimeout(this._timeoutId),this._timeoutId=void 0},I.prototype._setupUI=function(S){var L=this;if(this._container.addEventListener("contextmenu",function(ae){return ae.preventDefault()}),this._geolocateButton=s.create("button","mapboxgl-ctrl-geolocate",this._container),s.create("span","mapboxgl-ctrl-icon",this._geolocateButton).setAttribute("aria-hidden",!0),this._geolocateButton.type="button",S===!1){n.warnOnce("Geolocation support is not available so the GeolocateControl will be disabled.");var Y=this._map._getUIString("GeolocateControl.LocationNotAvailable");this._geolocateButton.disabled=!0,this._geolocateButton.title=Y,this._geolocateButton.setAttribute("aria-label",Y)}else{var ie=this._map._getUIString("GeolocateControl.FindMyLocation");this._geolocateButton.title=ie,this._geolocateButton.setAttribute("aria-label",ie)}this.options.trackUserLocation&&(this._geolocateButton.setAttribute("aria-pressed","false"),this._watchState="OFF"),this.options.showUserLocation&&(this._dotElement=s.create("div","mapboxgl-user-location-dot"),this._userLocationDotMarker=new xd(this._dotElement),this._circleElement=s.create("div","mapboxgl-user-location-accuracy-circle"),this._accuracyCircleMarker=new xd({element:this._circleElement,pitchAlignment:"map"}),this.options.trackUserLocation&&(this._watchState="OFF"),this._map.on("zoom",this._onZoom)),this._geolocateButton.addEventListener("click",this.trigger.bind(this)),this._setup=!0,this.options.trackUserLocation&&this._map.on("movestart",function(ae){ae.geolocateSource||L._watchState!=="ACTIVE_LOCK"||ae.originalEvent&&ae.originalEvent.type==="resize"||(L._watchState="BACKGROUND",L._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-background"),L._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-active"),L.fire(new n.Event("trackuserlocationend")))})},I.prototype.trigger=function(){if(!this._setup)return n.warnOnce("Geolocate control triggered before added to a map"),!1;if(this.options.trackUserLocation){switch(this._watchState){case"OFF":this._watchState="WAITING_ACTIVE",this.fire(new n.Event("trackuserlocationstart"));break;case"WAITING_ACTIVE":case"ACTIVE_LOCK":case"ACTIVE_ERROR":case"BACKGROUND_ERROR":xh--,Qu=!1,this._watchState="OFF",this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-waiting"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-active"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-active-error"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-background"),this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-background-error"),this.fire(new n.Event("trackuserlocationend"));break;case"BACKGROUND":this._watchState="ACTIVE_LOCK",this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-background"),this._lastKnownPosition&&this._updateCamera(this._lastKnownPosition),this.fire(new n.Event("trackuserlocationstart"))}switch(this._watchState){case"WAITING_ACTIVE":this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-waiting"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-active");break;case"ACTIVE_LOCK":this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-active");break;case"ACTIVE_ERROR":this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-waiting"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-active-error");break;case"BACKGROUND":this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-background");break;case"BACKGROUND_ERROR":this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-waiting"),this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-background-error")}if(this._watchState==="OFF"&&this._geolocationWatchID!==void 0)this._clearWatch();else if(this._geolocationWatchID===void 0){var S;this._geolocateButton.classList.add("mapboxgl-ctrl-geolocate-waiting"),this._geolocateButton.setAttribute("aria-pressed","true"),++xh>1?(S={maximumAge:6e5,timeout:0},Qu=!0):(S=this.options.positionOptions,Qu=!1),this._geolocationWatchID=n.window.navigator.geolocation.watchPosition(this._onSuccess,this._onError,S)}}else n.window.navigator.geolocation.getCurrentPosition(this._onSuccess,this._onError,this.options.positionOptions),this._timeoutId=setTimeout(this._finish,1e4);return!0},I.prototype._clearWatch=function(){n.window.navigator.geolocation.clearWatch(this._geolocationWatchID),this._geolocationWatchID=void 0,this._geolocateButton.classList.remove("mapboxgl-ctrl-geolocate-waiting"),this._geolocateButton.setAttribute("aria-pressed","false"),this.options.showUserLocation&&this._updateMarker(null)},I}(n.Evented),Qh={maxWidth:100,unit:"metric"},bh=function(w){this.options=n.extend({},Qh,w),n.bindAll(["_onMove","setUnit"],this)};function Rp(w,I,S){var L=S&&S.maxWidth||100,Y=w._container.clientHeight/2,ie=w.unproject([0,Y]),ae=w.unproject([L,Y]),ge=ie.distanceTo(ae);if(S&&S.unit==="imperial"){var Pe=3.2808*ge;Pe>5280?bf(I,L,Pe/5280,w._getUIString("ScaleControl.Miles")):bf(I,L,Pe,w._getUIString("ScaleControl.Feet"))}else S&&S.unit==="nautical"?bf(I,L,ge/1852,w._getUIString("ScaleControl.NauticalMiles")):ge>=1e3?bf(I,L,ge/1e3,w._getUIString("ScaleControl.Kilometers")):bf(I,L,ge,w._getUIString("ScaleControl.Meters"))}function bf(w,I,S,L){var Y,ie,ae,ge=(Y=S,(ie=Math.pow(10,(""+Math.floor(Y)).length-1))*(ae=(ae=Y/ie)>=10?10:ae>=5?5:ae>=3?3:ae>=2?2:ae>=1?1:function(Pe){var Ue=Math.pow(10,Math.ceil(-Math.log(Pe)/Math.LN10));return Math.round(Pe*Ue)/Ue}(ae)));w.style.width=I*(ge/S)+"px",w.innerHTML=ge+" "+L}bh.prototype.getDefaultPosition=function(){return"bottom-left"},bh.prototype._onMove=function(){Rp(this._map,this._container,this.options)},bh.prototype.onAdd=function(w){return this._map=w,this._container=s.create("div","mapboxgl-ctrl mapboxgl-ctrl-scale",w.getContainer()),this._map.on("move",this._onMove),this._onMove(),this._container},bh.prototype.onRemove=function(){s.remove(this._container),this._map.off("move",this._onMove),this._map=void 0},bh.prototype.setUnit=function(w){this.options.unit=w,Rp(this._map,this._container,this.options)};var Du=function(w){this._fullscreen=!1,w&&w.container&&(w.container instanceof n.window.HTMLElement?this._container=w.container:n.warnOnce("Full screen control 'container' must be a DOM element.")),n.bindAll(["_onClickFullscreen","_changeIcon"],this),"onfullscreenchange"in n.window.document?this._fullscreenchange="fullscreenchange":"onmozfullscreenchange"in n.window.document?this._fullscreenchange="mozfullscreenchange":"onwebkitfullscreenchange"in n.window.document?this._fullscreenchange="webkitfullscreenchange":"onmsfullscreenchange"in n.window.document&&(this._fullscreenchange="MSFullscreenChange")};Du.prototype.onAdd=function(w){return this._map=w,this._container||(this._container=this._map.getContainer()),this._controlContainer=s.create("div","mapboxgl-ctrl mapboxgl-ctrl-group"),this._checkFullscreenSupport()?this._setupUI():(this._controlContainer.style.display="none",n.warnOnce("This device does not support fullscreen mode.")),this._controlContainer},Du.prototype.onRemove=function(){s.remove(this._controlContainer),this._map=null,n.window.document.removeEventListener(this._fullscreenchange,this._changeIcon)},Du.prototype._checkFullscreenSupport=function(){return!!(n.window.document.fullscreenEnabled||n.window.document.mozFullScreenEnabled||n.window.document.msFullscreenEnabled||n.window.document.webkitFullscreenEnabled)},Du.prototype._setupUI=function(){var w=this._fullscreenButton=s.create("button","mapboxgl-ctrl-fullscreen",this._controlContainer);s.create("span","mapboxgl-ctrl-icon",w).setAttribute("aria-hidden",!0),w.type="button",this._updateTitle(),this._fullscreenButton.addEventListener("click",this._onClickFullscreen),n.window.document.addEventListener(this._fullscreenchange,this._changeIcon)},Du.prototype._updateTitle=function(){var w=this._getTitle();this._fullscreenButton.setAttribute("aria-label",w),this._fullscreenButton.title=w},Du.prototype._getTitle=function(){return this._map._getUIString(this._isFullscreen()?"FullscreenControl.Exit":"FullscreenControl.Enter")},Du.prototype._isFullscreen=function(){return this._fullscreen},Du.prototype._changeIcon=function(){(n.window.document.fullscreenElement||n.window.document.mozFullScreenElement||n.window.document.webkitFullscreenElement||n.window.document.msFullscreenElement)===this._container!==this._fullscreen&&(this._fullscreen=!this._fullscreen,this._fullscreenButton.classList.toggle("mapboxgl-ctrl-shrink"),this._fullscreenButton.classList.toggle("mapboxgl-ctrl-fullscreen"),this._updateTitle())},Du.prototype._onClickFullscreen=function(){this._isFullscreen()?n.window.document.exitFullscreen?n.window.document.exitFullscreen():n.window.document.mozCancelFullScreen?n.window.document.mozCancelFullScreen():n.window.document.msExitFullscreen?n.window.document.msExitFullscreen():n.window.document.webkitCancelFullScreen&&n.window.document.webkitCancelFullScreen():this._container.requestFullscreen?this._container.requestFullscreen():this._container.mozRequestFullScreen?this._container.mozRequestFullScreen():this._container.msRequestFullscreen?this._container.msRequestFullscreen():this._container.webkitRequestFullscreen&&this._container.webkitRequestFullscreen()};var Bp={closeButton:!0,closeOnClick:!0,focusAfterOpen:!0,className:"",maxWidth:"240px"},Kd=["a[href]","[tabindex]:not([tabindex='-1'])","[contenteditable]:not([contenteditable='false'])","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])"].join(", "),Yf=function(w){function I(S){w.call(this),this.options=n.extend(Object.create(Bp),S),n.bindAll(["_update","_onClose","remove","_onMouseMove","_onMouseUp","_onDrag"],this)}return w&&(I.__proto__=w),(I.prototype=Object.create(w&&w.prototype)).constructor=I,I.prototype.addTo=function(S){return this._map&&this.remove(),this._map=S,this.options.closeOnClick&&this._map.on("click",this._onClose),this.options.closeOnMove&&this._map.on("move",this._onClose),this._map.on("remove",this.remove),this._update(),this._focusFirstElement(),this._trackPointer?(this._map.on("mousemove",this._onMouseMove),this._map.on("mouseup",this._onMouseUp),this._container&&this._container.classList.add("mapboxgl-popup-track-pointer"),this._map._canvasContainer.classList.add("mapboxgl-track-pointer")):this._map.on("move",this._update),this.fire(new n.Event("open")),this},I.prototype.isOpen=function(){return!!this._map},I.prototype.remove=function(){return this._content&&s.remove(this._content),this._container&&(s.remove(this._container),delete this._container),this._map&&(this._map.off("move",this._update),this._map.off("move",this._onClose),this._map.off("click",this._onClose),this._map.off("remove",this.remove),this._map.off("mousemove",this._onMouseMove),this._map.off("mouseup",this._onMouseUp),this._map.off("drag",this._onDrag),delete this._map),this.fire(new n.Event("close")),this},I.prototype.getLngLat=function(){return this._lngLat},I.prototype.setLngLat=function(S){return this._lngLat=n.LngLat.convert(S),this._pos=null,this._trackPointer=!1,this._update(),this._map&&(this._map.on("move",this._update),this._map.off("mousemove",this._onMouseMove),this._container&&this._container.classList.remove("mapboxgl-popup-track-pointer"),this._map._canvasContainer.classList.remove("mapboxgl-track-pointer")),this},I.prototype.trackPointer=function(){return this._trackPointer=!0,this._pos=null,this._update(),this._map&&(this._map.off("move",this._update),this._map.on("mousemove",this._onMouseMove),this._map.on("drag",this._onDrag),this._container&&this._container.classList.add("mapboxgl-popup-track-pointer"),this._map._canvasContainer.classList.add("mapboxgl-track-pointer")),this},I.prototype.getElement=function(){return this._container},I.prototype.setText=function(S){return this.setDOMContent(n.window.document.createTextNode(S))},I.prototype.setHTML=function(S){var L,Y=n.window.document.createDocumentFragment(),ie=n.window.document.createElement("body");for(ie.innerHTML=S;L=ie.firstChild;)Y.appendChild(L);return this.setDOMContent(Y)},I.prototype.getMaxWidth=function(){return this._container&&this._container.style.maxWidth},I.prototype.setMaxWidth=function(S){return this.options.maxWidth=S,this._update(),this},I.prototype.setDOMContent=function(S){if(this._content)for(;this._content.hasChildNodes();)this._content.firstChild&&this._content.removeChild(this._content.firstChild);else this._content=s.create("div","mapboxgl-popup-content",this._container);return this._content.appendChild(S),this._createCloseButton(),this._update(),this._focusFirstElement(),this},I.prototype.addClassName=function(S){this._container&&this._container.classList.add(S)},I.prototype.removeClassName=function(S){this._container&&this._container.classList.remove(S)},I.prototype.setOffset=function(S){return this.options.offset=S,this._update(),this},I.prototype.toggleClassName=function(S){if(this._container)return this._container.classList.toggle(S)},I.prototype._createCloseButton=function(){this.options.closeButton&&(this._closeButton=s.create("button","mapboxgl-popup-close-button",this._content),this._closeButton.type="button",this._closeButton.setAttribute("aria-label","Close popup"),this._closeButton.innerHTML="×",this._closeButton.addEventListener("click",this._onClose))},I.prototype._onMouseUp=function(S){this._update(S.point)},I.prototype._onMouseMove=function(S){this._update(S.point)},I.prototype._onDrag=function(S){this._update(S.point)},I.prototype._update=function(S){var L=this;if(this._map&&(this._lngLat||this._trackPointer)&&this._content&&(this._container||(this._container=s.create("div","mapboxgl-popup",this._map.getContainer()),this._tip=s.create("div","mapboxgl-popup-tip",this._container),this._container.appendChild(this._content),this.options.className&&this.options.className.split(" ").forEach(function(ft){return L._container.classList.add(ft)}),this._trackPointer&&this._container.classList.add("mapboxgl-popup-track-pointer")),this.options.maxWidth&&this._container.style.maxWidth!==this.options.maxWidth&&(this._container.style.maxWidth=this.options.maxWidth),this._map.transform.renderWorldCopies&&!this._trackPointer&&(this._lngLat=vh(this._lngLat,this._pos,this._map.transform)),!this._trackPointer||S)){var Y=this._pos=this._trackPointer&&S?S:this._map.project(this._lngLat),ie=this.options.anchor,ae=function ft(gt){if(gt){if(typeof gt=="number"){var ut=Math.round(Math.sqrt(.5*Math.pow(gt,2)));return{center:new n.Point(0,0),top:new n.Point(0,gt),"top-left":new n.Point(ut,ut),"top-right":new n.Point(-ut,ut),bottom:new n.Point(0,-gt),"bottom-left":new n.Point(ut,-ut),"bottom-right":new n.Point(-ut,-ut),left:new n.Point(gt,0),right:new n.Point(-gt,0)}}if(gt instanceof n.Point||Array.isArray(gt)){var Pt=n.Point.convert(gt);return{center:Pt,top:Pt,"top-left":Pt,"top-right":Pt,bottom:Pt,"bottom-left":Pt,"bottom-right":Pt,left:Pt,right:Pt}}return{center:n.Point.convert(gt.center||[0,0]),top:n.Point.convert(gt.top||[0,0]),"top-left":n.Point.convert(gt["top-left"]||[0,0]),"top-right":n.Point.convert(gt["top-right"]||[0,0]),bottom:n.Point.convert(gt.bottom||[0,0]),"bottom-left":n.Point.convert(gt["bottom-left"]||[0,0]),"bottom-right":n.Point.convert(gt["bottom-right"]||[0,0]),left:n.Point.convert(gt.left||[0,0]),right:n.Point.convert(gt.right||[0,0])}}return ft(new n.Point(0,0))}(this.options.offset);if(!ie){var ge,Pe=this._container.offsetWidth,Ue=this._container.offsetHeight;ge=Y.y+ae.bottom.ythis._map.transform.height-Ue?["bottom"]:[],Y.xthis._map.transform.width-Pe/2&&ge.push("right"),ie=ge.length===0?"bottom":ge.join("-")}var ct=Y.add(ae[ie]).round();s.setTransform(this._container,_u[ie]+" translate("+ct.x+"px,"+ct.y+"px)"),Zf(this._container,ie,"popup")}},I.prototype._focusFirstElement=function(){if(this.options.focusAfterOpen&&this._container){var S=this._container.querySelector(Kd);S&&S.focus()}},I.prototype._onClose=function(){this.remove()},I}(n.Evented),Jh={version:n.version,supported:o,setRTLTextPlugin:n.setRTLTextPlugin,getRTLTextPluginStatus:n.getRTLTextPluginStatus,Map:Ip,NavigationControl:_c,GeolocateControl:Yd,AttributionControl:Vc,ScaleControl:bh,FullscreenControl:Du,Popup:Yf,Marker:xd,Style:xl,LngLat:n.LngLat,LngLatBounds:n.LngLatBounds,Point:n.Point,MercatorCoordinate:n.MercatorCoordinate,Evented:n.Evented,config:n.config,prewarm:function(){pr().acquire(Gr)},clearPrewarmedResources:function(){var w=Or;w&&(w.isPreloaded()&&w.numActive()===1?(w.release(Gr),Or=null):console.warn("Could not clear WebWorkers since there are active Map instances that still reference it. The pre-warmed WebWorker pool can only be cleared when all map instances have been removed with map.remove()"))},get accessToken(){return n.config.ACCESS_TOKEN},set accessToken(w){n.config.ACCESS_TOKEN=w},get baseApiUrl(){return n.config.API_URL},set baseApiUrl(w){n.config.API_URL=w},get workerCount(){return si.workerCount},set workerCount(w){si.workerCount=w},get maxParallelImageRequests(){return n.config.MAX_PARALLEL_IMAGE_REQUESTS},set maxParallelImageRequests(w){n.config.MAX_PARALLEL_IMAGE_REQUESTS=w},clearStorage:function(w){n.clearTileCache(w)},workerUrl:""};return Jh}),t})});function D3e(r,e){let t=r.stats,i=!1;for(let f of e)t[f]||(r.get(f),i=!0);let n=Object.keys(t).length,o=Pre.get(r);if(!i&&o?.orderedStatNames===e&&o.statCount===n)return;let s={},c=Rre.get(e);c||(c=new Set(e),Rre.set(e,c));for(let f of e)t[f]&&(s[f]=t[f]);for(let[f,g]of Object.entries(t))c.has(f)||(s[f]=g);for(let f of Object.keys(t))delete t[f];Object.assign(t,s),Pre.set(r,{orderedStatNames:e,statCount:n})}var B3e,L3e,Pre,Rre,gG,aF,AG=Nt(()=>{Tb();B3e="GPU Time and Memory",L3e=["Adapter","GPU","GPU Type","GPU Backend","Frame Rate","CPU Time","GPU Time","GPU Memory","Buffer Memory","Texture Memory","Referenced Buffer Memory","Referenced Texture Memory","Swap Chain Texture"],Pre=new WeakMap,Rre=new WeakMap,gG=class{stats=new Map;getStats(e){return this.get(e)}get(e){this.stats.has(e)||this.stats.set(e,new Ed({id:e}));let t=this.stats.get(e);return e===B3e&&D3e(t,L3e),t}},aF=new gG});var Lr,Rg=Nt(()=>{R2();Lr=new ip({id:"luma.gl"})});function WA(r="id"){_G[r]=_G[r]||1;let e=_G[r]++;return`${r}-${e}`}var _G,CT=Nt(()=>{_G={}});function U3e(r,e){let t={...e};for(let i in r)r[i]!==void 0&&(t[i]=r[i]);return t}function kre(r,e){let t=r.stats,i=!1;for(let f of e)t[f]||(r.get(f),i=!0);let n=Object.keys(t).length,o=Ore.get(r);if(!i&&o?.orderedStatNames===e&&o.statCount===n)return;let s={},c=Fre.get(e);c||(c=new Set(e),Fre.set(e,c));for(let f of e)t[f]&&(s[f]=t[f]);for(let[f,g]of Object.entries(t))c.has(f)||(s[f]=g);for(let f of Object.keys(t))delete t[f];Object.assign(t,s),Ore.set(r,{orderedStatNames:e,statCount:n})}function Nre(r){return r.type==="webgl"?z3e:N3e}function MI(r){let e=r.userData[O3e];return e?.enabled?e:null}function Nv(){return globalThis.performance?.now?.()??Date.now()}function V3e(r,e){let t=MI(r);if(!(!t||!t.activeDefaultFramebufferAcquireDepth))switch(t.transientCanvasResourceCreates=(t.transientCanvasResourceCreates||0)+1,e){case"Texture":t.transientCanvasTextureCreates=(t.transientCanvasTextureCreates||0)+1;break;case"TextureView":t.transientCanvasTextureViewCreates=(t.transientCanvasTextureViewCreates||0)+1;break;case"Sampler":t.transientCanvasSamplerCreates=(t.transientCanvasSamplerCreates||0)+1;break;case"Framebuffer":t.transientCanvasFramebufferCreates=(t.transientCanvasFramebufferCreates||0)+1;break;default:break}}function j3e(r){let e=Object.getPrototypeOf(r);for(;e;){let t=Object.getPrototypeOf(e);if(!t||t===$o.prototype)return G3e(e)||r[Symbol.toStringTag]||r.constructor.name;e=t}return r[Symbol.toStringTag]||r.constructor.name}function G3e(r){let e=Object.getOwnPropertyDescriptor(r,Symbol.toStringTag);return typeof e?.get=="function"?e.get.call(r):typeof e?.value=="string"?e.value:null}var O3e,Bre,Lre,Dre,F3e,k3e,N3e,z3e,Ore,Fre,$o,ad=Nt(()=>{CT();O3e="cpu-hotspot-profiler",Bre="GPU Resource Counts",Lre="Resource Counts",Dre="GPU Time and Memory",F3e=["Resources","Buffers","Textures","Samplers","TextureViews","Framebuffers","QuerySets","Shaders","RenderPipelines","ComputePipelines","PipelineLayouts","VertexArrays","RenderPasss","ComputePasss","CommandEncoders","CommandBuffers"],k3e=["Resources","Buffers","Textures","Samplers","TextureViews","Framebuffers","QuerySets","Shaders","RenderPipelines","SharedRenderPipelines","ComputePipelines","PipelineLayouts","VertexArrays","RenderPasss","ComputePasss","CommandEncoders","CommandBuffers"],N3e=F3e.flatMap(r=>[`${r} Created`,`${r} Active`]),z3e=k3e.flatMap(r=>[`${r} Created`,`${r} Active`]),Ore=new WeakMap,Fre=new WeakMap,$o=class{toString(){return`${this[Symbol.toStringTag]||this.constructor.name}:"${this.id}"`}id;props;userData={};_device;destroyed=!1;allocatedBytes=0;allocatedBytesName=null;_attachedResources=new Set;constructor(e,t,i){if(!e)throw new Error("no device");this._device=e,this.props=U3e(t,i);let n=this.props.id!=="undefined"?this.props.id:WA(this[Symbol.toStringTag]);this.props.id=n,this.id=n,this.userData=this.props.userData||{},this.addStats()}destroy(){this.destroyed||this.destroyResource()}delete(){return this.destroy(),this}getProps(){return this.props}attachResource(e){this._attachedResources.add(e)}detachResource(e){this._attachedResources.delete(e)}destroyAttachedResource(e){this._attachedResources.delete(e)&&e.destroy()}destroyAttachedResources(){for(let e of this._attachedResources)e.destroy();this._attachedResources=new Set}destroyResource(){this.destroyed||(this.destroyAttachedResources(),this.removeStats(),this.destroyed=!0)}removeStats(){let e=MI(this._device),t=e?Nv():0,i=[this._device.statsManager.getStats(Bre),this._device.statsManager.getStats(Lre)],n=Nre(this._device);for(let s of i)kre(s,n);let o=this.getStatsName();for(let s of i)s.get("Resources Active").decrementCount(),s.get(`${o}s Active`).decrementCount();e&&(e.statsBookkeepingCalls=(e.statsBookkeepingCalls||0)+1,e.statsBookkeepingTimeMs=(e.statsBookkeepingTimeMs||0)+(Nv()-t))}trackAllocatedMemory(e,t=this.getStatsName()){let i=MI(this._device),n=i?Nv():0,o=this._device.statsManager.getStats(Dre);this.allocatedBytes>0&&this.allocatedBytesName&&(o.get("GPU Memory").subtractCount(this.allocatedBytes),o.get(`${this.allocatedBytesName} Memory`).subtractCount(this.allocatedBytes)),o.get("GPU Memory").addCount(e),o.get(`${t} Memory`).addCount(e),i&&(i.statsBookkeepingCalls=(i.statsBookkeepingCalls||0)+1,i.statsBookkeepingTimeMs=(i.statsBookkeepingTimeMs||0)+(Nv()-n)),this.allocatedBytes=e,this.allocatedBytesName=t}trackReferencedMemory(e,t=this.getStatsName()){this.trackAllocatedMemory(e,`Referenced ${t}`)}trackDeallocatedMemory(e=this.getStatsName()){if(this.allocatedBytes===0){this.allocatedBytesName=null;return}let t=MI(this._device),i=t?Nv():0,n=this._device.statsManager.getStats(Dre);n.get("GPU Memory").subtractCount(this.allocatedBytes),n.get(`${this.allocatedBytesName||e} Memory`).subtractCount(this.allocatedBytes),t&&(t.statsBookkeepingCalls=(t.statsBookkeepingCalls||0)+1,t.statsBookkeepingTimeMs=(t.statsBookkeepingTimeMs||0)+(Nv()-i)),this.allocatedBytes=0,this.allocatedBytesName=null}trackDeallocatedReferencedMemory(e=this.getStatsName()){this.trackDeallocatedMemory(`Referenced ${e}`)}addStats(){let e=this.getStatsName(),t=MI(this._device),i=t?Nv():0,n=[this._device.statsManager.getStats(Bre),this._device.statsManager.getStats(Lre)],o=Nre(this._device);for(let s of n)kre(s,o);for(let s of n)s.get("Resources Created").incrementCount(),s.get("Resources Active").incrementCount(),s.get(`${e}s Created`).incrementCount(),s.get(`${e}s Active`).incrementCount();t&&(t.statsBookkeepingCalls=(t.statsBookkeepingCalls||0)+1,t.statsBookkeepingTimeMs=(t.statsBookkeepingTimeMs||0)+(Nv()-i)),V3e(this._device,e)}getStatsName(){return j3e(this)}};rn($o,"defaultProps",{id:"undefined",handle:void 0,userData:void 0})});var II,lo,lF=Nt(()=>{ad();II=class extends $o{get[Symbol.toStringTag](){return"Buffer"}usage;indexType;updateTimestamp;constructor(e,t){let i={...t};(t.usage||0)&II.INDEX&&!t.indexType&&(t.data instanceof Uint32Array?i.indexType="uint32":t.data instanceof Uint16Array?i.indexType="uint16":t.data instanceof Uint8Array&&(i.indexType="uint8")),delete i.data,super(e,i,II.defaultProps),this.usage=i.usage||0,this.indexType=i.indexType,this.updateTimestamp=e.incrementTimestamp()}clone(e){return this.device.createBuffer({...this.props,...e})}debugData=new ArrayBuffer(0);_setDebugData(e,t,i){let n=null,o;ArrayBuffer.isView(e)?(n=e,o=e.buffer):o=e;let s=Math.min(e?e.byteLength:i,II.DEBUG_DATA_MAX_LENGTH);if(o===null)this.debugData=new ArrayBuffer(s);else{let c=Math.min(n?.byteOffset||0,o.byteLength),f=Math.max(0,o.byteLength-c),g=Math.min(s,f);this.debugData=new Uint8Array(o,c,g).slice().buffer}}},lo=II;rn(lo,"INDEX",16),rn(lo,"VERTEX",32),rn(lo,"UNIFORM",64),rn(lo,"STORAGE",128),rn(lo,"INDIRECT",256),rn(lo,"QUERY_RESOLVE",512),rn(lo,"MAP_READ",1),rn(lo,"MAP_WRITE",2),rn(lo,"COPY_SRC",4),rn(lo,"COPY_DST",8),rn(lo,"DEBUG_DATA_MAX_LENGTH",32),rn(lo,"defaultProps",{...$o.defaultProps,usage:0,byteLength:0,byteOffset:0,data:null,indexType:"uint16",onMapped:void 0})});var vG,up,yG,cF=Nt(()=>{vG=class{getDataTypeInfo(e){let[t,i,n]=yG[e],o=e.includes("norm"),s=!o&&!e.startsWith("float"),c=e.startsWith("s");return{signedType:t,primitiveType:i,byteLength:n,normalized:o,integer:s,signed:c}}getNormalizedDataType(e){let t=e;switch(t){case"uint8":return"unorm8";case"sint8":return"snorm8";case"uint16":return"unorm16";case"sint16":return"snorm16";default:return t}}alignTo(e,t){switch(t){case 1:return e;case 2:return e+e%2;default:return e+(4-e%4)%4}}getDataType(e){let t=ArrayBuffer.isView(e)?e.constructor:e;if(t===Uint8ClampedArray)return"uint8";let i=Object.values(yG).find(n=>t===n[4]);if(!i)throw new Error(t.name);return i[0]}getTypedArrayConstructor(e){let[,,,,t]=yG[e];return t}},up=new vG,yG={uint8:["uint8","u32",1,!1,Uint8Array],sint8:["sint8","i32",1,!1,Int8Array],unorm8:["uint8","f32",1,!0,Uint8Array],snorm8:["sint8","f32",1,!0,Int8Array],uint16:["uint16","u32",2,!1,Uint16Array],sint16:["sint16","i32",2,!1,Int16Array],unorm16:["uint16","u32",2,!0,Uint16Array],snorm16:["sint16","i32",2,!0,Int16Array],float16:["float16","f16",2,!1,Uint16Array],float32:["float32","f32",4,!1,Float32Array],uint32:["uint32","u32",4,!1,Uint32Array],sint32:["sint32","i32",4,!1,Int32Array]}});var xG,ay,uF=Nt(()=>{cF();xG=class{getVertexFormatInfo(e){let t;e.endsWith("-webgl")&&(e.replace("-webgl",""),t=!0);let[i,n]=e.split("x"),o=i,s=n?parseInt(n):1,c=up.getDataTypeInfo(o),f={type:o,components:s,byteLength:c.byteLength*s,integer:c.integer,signed:c.signed,normalized:c.normalized};return t&&(f.webglOnly=!0),f}makeVertexFormat(e,t,i){let n=i?up.getNormalizedDataType(e):e;switch(n){case"unorm8":return t===1?"unorm8":t===3?"unorm8x3-webgl":`${n}x${t}`;case"snorm8":return t===1?"snorm8":t===3?"snorm8x3-webgl":`${n}x${t}`;case"uint8":case"sint8":if(t===1||t===3)throw new Error(`size: ${t}`);return`${n}x${t}`;case"uint16":return t===1?"uint16":t===3?"uint16x3-webgl":`${n}x${t}`;case"sint16":return t===1?"sint16":t===3?"sint16x3-webgl":`${n}x${t}`;case"unorm16":return t===1?"unorm16":t===3?"unorm16x3-webgl":`${n}x${t}`;case"snorm16":return t===1?"snorm16":t===3?"snorm16x3-webgl":`${n}x${t}`;case"float16":if(t===1||t===3)throw new Error(`size: ${t}`);return`${n}x${t}`;default:return t===1?n:`${n}x${t}`}}getVertexFormatFromAttribute(e,t,i){if(!t||t>4)throw new Error(`size ${t}`);let n=t,o=up.getDataType(e);return this.makeVertexFormat(o,n,i)}getCompatibleVertexFormat(e){let t;switch(e.primitiveType){case"f32":t="float32";break;case"i32":t="sint32";break;case"u32":t="uint32";break;case"f16":return e.components<=2?"float16x2":"float16x4"}return e.components===1?t:`${t}x${e.components}`}},ay=new xG});function CG(r){let e=Vre[r];if(!e)throw new Error(`Unsupported texture format ${r}`);return e}function Ure(){return Vre}var Ld,uc,qA,H3e,hF,bG,fF,wG,W3e,TG,zv,SG,EG,dF,zre,q3e,$3e,Vre,MG=Nt(()=>{Ld="texture-compression-bc",uc="texture-compression-astc",qA="texture-compression-etc2",H3e="texture-compression-etc1-webgl",hF="texture-compression-pvrtc-webgl",bG="texture-compression-atc-webgl",fF="float32-renderable-webgl",wG="float16-renderable-webgl",W3e="rgb9e5ufloat-renderable-webgl",TG="snorm8-renderable-webgl",zv="norm16-webgl",SG="norm16-renderable-webgl",EG="snorm16-renderable-webgl",dF="float32-filterable",zre="float16-filterable-webgl";q3e={r8unorm:{},rg8unorm:{},"rgb8unorm-webgl":{},rgba8unorm:{},"rgba8unorm-srgb":{},r8snorm:{render:TG},rg8snorm:{render:TG},"rgb8snorm-webgl":{},rgba8snorm:{render:TG},r8uint:{},rg8uint:{},rgba8uint:{},r8sint:{},rg8sint:{},rgba8sint:{},bgra8unorm:{},"bgra8unorm-srgb":{},r16unorm:{f:zv,render:SG},rg16unorm:{f:zv,render:SG},"rgb16unorm-webgl":{f:zv,render:!1},rgba16unorm:{f:zv,render:SG},r16snorm:{f:zv,render:EG},rg16snorm:{f:zv,render:EG},"rgb16snorm-webgl":{f:zv,render:!1},rgba16snorm:{f:zv,render:EG},r16uint:{},rg16uint:{},rgba16uint:{},r16sint:{},rg16sint:{},rgba16sint:{},r16float:{render:wG,filter:"float16-filterable-webgl"},rg16float:{render:wG,filter:zre},rgba16float:{render:wG,filter:zre},r32uint:{},rg32uint:{},rgba32uint:{},r32sint:{},rg32sint:{},rgba32sint:{},r32float:{render:fF,filter:dF},rg32float:{render:!1,filter:dF},"rgb32float-webgl":{render:fF,filter:dF},rgba32float:{render:fF,filter:dF},"rgba4unorm-webgl":{channels:"rgba",bitsPerChannel:[4,4,4,4],packed:!0},"rgb565unorm-webgl":{channels:"rgb",bitsPerChannel:[5,6,5,0],packed:!0},"rgb5a1unorm-webgl":{channels:"rgba",bitsPerChannel:[5,5,5,1],packed:!0},rgb9e5ufloat:{channels:"rgb",packed:!0,render:W3e},rg11b10ufloat:{channels:"rgb",bitsPerChannel:[11,11,10,0],packed:!0,p:1,render:fF},rgb10a2unorm:{channels:"rgba",bitsPerChannel:[10,10,10,2],packed:!0,p:1},rgb10a2uint:{channels:"rgba",bitsPerChannel:[10,10,10,2],packed:!0,p:1},stencil8:{attachment:"stencil",bitsPerChannel:[8,0,0,0],dataType:"uint8"},depth16unorm:{attachment:"depth",bitsPerChannel:[16,0,0,0],dataType:"uint16"},depth24plus:{attachment:"depth",bitsPerChannel:[24,0,0,0],dataType:"uint32"},depth32float:{attachment:"depth",bitsPerChannel:[32,0,0,0],dataType:"float32"},"depth24plus-stencil8":{attachment:"depth-stencil",bitsPerChannel:[24,8,0,0],packed:!0},"depth32float-stencil8":{attachment:"depth-stencil",bitsPerChannel:[32,8,0,0],packed:!0}},$3e={"bc1-rgb-unorm-webgl":{f:Ld},"bc1-rgb-unorm-srgb-webgl":{f:Ld},"bc1-rgba-unorm":{f:Ld},"bc1-rgba-unorm-srgb":{f:Ld},"bc2-rgba-unorm":{f:Ld},"bc2-rgba-unorm-srgb":{f:Ld},"bc3-rgba-unorm":{f:Ld},"bc3-rgba-unorm-srgb":{f:Ld},"bc4-r-unorm":{f:Ld},"bc4-r-snorm":{f:Ld},"bc5-rg-unorm":{f:Ld},"bc5-rg-snorm":{f:Ld},"bc6h-rgb-ufloat":{f:Ld},"bc6h-rgb-float":{f:Ld},"bc7-rgba-unorm":{f:Ld},"bc7-rgba-unorm-srgb":{f:Ld},"etc2-rgb8unorm":{f:qA},"etc2-rgb8unorm-srgb":{f:qA},"etc2-rgb8a1unorm":{f:qA},"etc2-rgb8a1unorm-srgb":{f:qA},"etc2-rgba8unorm":{f:qA},"etc2-rgba8unorm-srgb":{f:qA},"eac-r11unorm":{f:qA},"eac-r11snorm":{f:qA},"eac-rg11unorm":{f:qA},"eac-rg11snorm":{f:qA},"astc-4x4-unorm":{f:uc},"astc-4x4-unorm-srgb":{f:uc},"astc-5x4-unorm":{f:uc},"astc-5x4-unorm-srgb":{f:uc},"astc-5x5-unorm":{f:uc},"astc-5x5-unorm-srgb":{f:uc},"astc-6x5-unorm":{f:uc},"astc-6x5-unorm-srgb":{f:uc},"astc-6x6-unorm":{f:uc},"astc-6x6-unorm-srgb":{f:uc},"astc-8x5-unorm":{f:uc},"astc-8x5-unorm-srgb":{f:uc},"astc-8x6-unorm":{f:uc},"astc-8x6-unorm-srgb":{f:uc},"astc-8x8-unorm":{f:uc},"astc-8x8-unorm-srgb":{f:uc},"astc-10x5-unorm":{f:uc},"astc-10x5-unorm-srgb":{f:uc},"astc-10x6-unorm":{f:uc},"astc-10x6-unorm-srgb":{f:uc},"astc-10x8-unorm":{f:uc},"astc-10x8-unorm-srgb":{f:uc},"astc-10x10-unorm":{f:uc},"astc-10x10-unorm-srgb":{f:uc},"astc-12x10-unorm":{f:uc},"astc-12x10-unorm-srgb":{f:uc},"astc-12x12-unorm":{f:uc},"astc-12x12-unorm-srgb":{f:uc},"pvrtc-rgb4unorm-webgl":{f:hF},"pvrtc-rgba4unorm-webgl":{f:hF},"pvrtc-rgb2unorm-webgl":{f:hF},"pvrtc-rgba2unorm-webgl":{f:hF},"etc1-rbg-unorm-webgl":{f:H3e},"atc-rgb-unorm-webgl":{f:bG},"atc-rgba-unorm-webgl":{f:bG},"atc-rgbai-unorm-webgl":{f:bG}},Vre={...q3e,...$3e}});function Q3e({format:r,width:e,height:t,depth:i,byteAlignment:n}){let o=ld.getInfo(r),{bytesPerPixel:s,bytesPerBlock:c=s,blockWidth:f=1,blockHeight:g=1,compressed:y=!1}=o,M=y?Math.ceil(e/f):e,F=y?Math.ceil(t/g):t,k=M*c,U=Math.ceil(k/n)*n,K=F,ee=U*K*i;return{bytesPerPixel:s,bytesPerRow:U,rowsPerImage:K,depthOrArrayLayers:i,bytesPerImage:U*K,byteLength:ee}}function J3e(r){let e=CG(r),t={format:r,create:e.f??!0,render:e.render??!0,filter:e.filter??!0,blend:e.blend??!0,store:e.store??!0},i=jre(r),n=r.startsWith("depth")||r.startsWith("stencil"),o=i?.signed,s=i?.integer,c=i?.webgl,f=Boolean(i?.compressed);return t.render&&=!n&&!f,t.filter&&=!n&&!o&&!s&&!c,t}function jre(r){let e=eRe(r);if(ld.isCompressed(r)){e.channels="rgb",e.components=3,e.bytesPerPixel=1,e.srgb=!1,e.compressed=!0,e.bytesPerBlock=rRe(r);let i=tRe(r);i&&(e.blockWidth=i.blockWidth,e.blockHeight=i.blockHeight)}let t=e.packed?null:X3e.exec(r);if(t){let[,i,n,o,s,c]=t,f=`${o}${n}`,g=up.getDataTypeInfo(f),y=g.byteLength*8,M=i?.length??1,F=[y,M>=2?y:0,M>=3?y:0,M>=4?y:0];e={format:r,attachment:e.attachment,dataType:g.signedType,components:M,channels:i,integer:g.integer,signed:g.signed,normalized:g.normalized,bitsPerChannel:F,bytesPerPixel:g.byteLength*M,packed:e.packed,srgb:e.srgb},c==="-webgl"&&(e.webgl=!0),s==="-srgb"&&(e.srgb=!0)}return r.endsWith("-webgl")&&(e.webgl=!0),r.endsWith("-srgb")&&(e.srgb=!0),e}function eRe(r){let e=CG(r),t=e.bytesPerPixel||1,i=e.bitsPerChannel||[8,8,8,8];return delete e.bitsPerChannel,delete e.bytesPerPixel,delete e.f,delete e.render,delete e.filter,delete e.blend,delete e.store,{...e,format:r,attachment:e.attachment||"color",channels:e.channels||"r",components:e.components||e.channels?.length||1,bytesPerPixel:t,bitsPerChannel:i,dataType:e.dataType||"uint8",srgb:e.srgb??!1,packed:e.packed??!1,webgl:e.webgl??!1,integer:e.integer??!1,signed:e.signed??!1,normalized:e.normalized??!1,compressed:e.compressed??!1}}function tRe(r){let t=/.*-(\d+)x(\d+)-.*/.exec(r);if(t){let[,i,n]=t;return{blockWidth:Number(i),blockHeight:Number(n)}}return r.startsWith("bc")||r.startsWith("etc1")||r.startsWith("etc2")||r.startsWith("eac")||r.startsWith("atc")?{blockWidth:4,blockHeight:4}:r.startsWith("pvrtc-rgb4")||r.startsWith("pvrtc-rgba4")?{blockWidth:4,blockHeight:4}:r.startsWith("pvrtc-rgb2")||r.startsWith("pvrtc-rgba2")?{blockWidth:8,blockHeight:4}:null}function rRe(r){return r.startsWith("bc1")||r.startsWith("bc4")||r.startsWith("etc1")||r.startsWith("etc2-rgb8")||r.startsWith("etc2-rgb8a1")||r.startsWith("eac-r11")||r==="atc-rgb-unorm-webgl"?8:r.startsWith("bc2")||r.startsWith("bc3")||r.startsWith("bc5")||r.startsWith("bc6h")||r.startsWith("bc7")||r.startsWith("etc2-rgba8")||r.startsWith("eac-rg11")||r.startsWith("astc")||r==="atc-rgba-unorm-webgl"||r==="atc-rgbai-unorm-webgl"?16:r.startsWith("pvrtc")?8:16}var X3e,Z3e,Y3e,K3e,IG,ld,pF=Nt(()=>{cF();MG();X3e=/^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/,Z3e=["rgb","rgba","bgra"],Y3e=["depth","stencil"],K3e=["bc1","bc2","bc3","bc4","bc5","bc6","bc7","etc1","etc2","eac","atc","astc","pvrtc"],IG=class{isColor(e){return Z3e.some(t=>e.startsWith(t))}isDepthStencil(e){return Y3e.some(t=>e.startsWith(t))}isCompressed(e){return K3e.some(t=>e.startsWith(t))}getInfo(e){return jre(e)}getCapabilities(e){return J3e(e)}computeMemoryLayout(e){return Q3e(e)}},ld=new IG});function MT(r){return typeof ImageData<"u"&&r instanceof ImageData||typeof ImageBitmap<"u"&&r instanceof ImageBitmap||typeof HTMLImageElement<"u"&&r instanceof HTMLImageElement||typeof HTMLVideoElement<"u"&&r instanceof HTMLVideoElement||typeof VideoFrame<"u"&&r instanceof VideoFrame||typeof HTMLCanvasElement<"u"&&r instanceof HTMLCanvasElement||typeof OffscreenCanvas<"u"&&r instanceof OffscreenCanvas}function PI(r){if(typeof ImageData<"u"&&r instanceof ImageData||typeof ImageBitmap<"u"&&r instanceof ImageBitmap||typeof HTMLCanvasElement<"u"&&r instanceof HTMLCanvasElement||typeof OffscreenCanvas<"u"&&r instanceof OffscreenCanvas)return{width:r.width,height:r.height};if(typeof HTMLImageElement<"u"&&r instanceof HTMLImageElement)return{width:r.naturalWidth,height:r.naturalHeight};if(typeof HTMLVideoElement<"u"&&r instanceof HTMLVideoElement)return{width:r.videoWidth,height:r.videoHeight};if(typeof VideoFrame<"u"&&r instanceof VideoFrame)return{width:r.displayWidth,height:r.displayHeight};throw new Error("Unknown image type")}var PG=Nt(()=>{});function iRe(r,e){let t=RG(r),i=e.map(RG).filter(n=>n!==void 0);return[t,...i].filter(n=>n!==void 0)}function RG(r){if(r!==void 0){if(r===null||typeof r=="string"||typeof r=="number"||typeof r=="boolean")return r;if(r instanceof Error)return r.message;if(Array.isArray(r))return r.map(RG);if(typeof r=="object"){if(nRe(r)){let e=String(r);if(e!=="[object Object]")return e}return oRe(r)?sRe(r):r.constructor?.name||"Object"}return String(r)}}function nRe(r){return"toString"in r&&typeof r.toString=="function"&&r.toString!==Object.prototype.toString}function oRe(r){return"message"in r&&"type"in r}function sRe(r){let e=typeof r.type=="string"?r.type:"message",t=typeof r.message=="string"?r.message:"",i=typeof r.lineNum=="number"?r.lineNum:null,n=typeof r.linePos=="number"?r.linePos:null,o=i!==null&&n!==null?` @ ${i}:${n}`:i!==null?` @ ${i}`:"";return`${e}${o}: ${t}`.trim()}function aRe(r,e){return r!=null?Boolean(r):e!==void 0?e!=="production":!1}function lRe(){return aRe(Lr.get("debug"),cRe())}function cRe(){let r=globalThis.process;if(r?.env)return r.env.NODE_ENV}var RI,BI,BG,Qp,LG=Nt(()=>{AG();Rg();CT();lF();uF();pF();PG();MG();RI=class{};BI=class{features;disabledFeatures;constructor(e=[],t){this.features=new Set(e),this.disabledFeatures=t||{}}*[Symbol.iterator](){yield*this.features}has(e){return!this.disabledFeatures?.[e]&&this.features.has(e)}},BG=class{get[Symbol.toStringTag](){return"Device"}toString(){return`Device(${this.id})`}id;props;userData={};statsManager=aF;_factories={};timestamp=0;_reused=!1;_moduleData={};_textureCaps={};_debugGPUTimeQuery=null;constructor(e){this.props={...BG.defaultProps,...e},this.id=this.props.id||WA(this[Symbol.toStringTag].toLowerCase())}getVertexFormatInfo(e){return ay.getVertexFormatInfo(e)}isVertexFormatSupported(e){return!0}getTextureFormatInfo(e){return ld.getInfo(e)}getTextureFormatCapabilities(e){let t=this._textureCaps[e];if(!t){let i=this._getDeviceTextureFormatCapabilities(e);t=this._getDeviceSpecificTextureFormatCapabilities(i),this._textureCaps[e]=t}return t}getMipLevelCount(e,t,i=1){let n=Math.max(e,t,i);return 1+Math.floor(Math.log2(n))}isExternalImage(e){return MT(e)}getExternalImageSize(e){return PI(e)}isTextureFormatSupported(e){return this.getTextureFormatCapabilities(e).create}isTextureFormatFilterable(e){return this.getTextureFormatCapabilities(e).filter}isTextureFormatRenderable(e){return this.getTextureFormatCapabilities(e).render}isTextureFormatCompressed(e){return ld.isCompressed(e)}getSupportedCompressedTextureFormats(){let e=[];for(let t of Object.keys(Ure()))this.isTextureFormatCompressed(t)&&this.isTextureFormatSupported(t)&&e.push(t);return e}pushDebugGroup(e){this.commandEncoder.pushDebugGroup(e)}popDebugGroup(){this.commandEncoder?.popDebugGroup()}insertDebugMarker(e){this.commandEncoder?.insertDebugMarker(e)}loseDevice(){return!1}incrementTimestamp(){return this.timestamp++}reportError(e,t,...i){if(!this.props.onError(e,t)){let o=iRe(t,i);return Lr.error(this.type==="webgl"?"%cWebGL":"%cWebGPU","color: white; background: red; padding: 2px 6px; border-radius: 3px;",e.message,...o)}return()=>{}}debug(){if(this.props.debug)debugger;else{let e=`'Type luma.log.set({debug: true}) in console to enable debug breakpoints', +or create a device with the 'debug: true' prop.`;Lr.once(0,e)()}}getDefaultCanvasContext(){if(!this.canvasContext)throw new Error("Device has no default CanvasContext. See props.createCanvasContext");return this.canvasContext}createFence(){throw new Error("createFence() not implemented")}beginRenderPass(e){return this.commandEncoder.beginRenderPass(e)}beginComputePass(e){return this.commandEncoder.beginComputePass(e)}generateMipmapsWebGPU(e){throw new Error("not implemented")}_createSharedRenderPipelineWebGL(e){throw new Error("_createSharedRenderPipelineWebGL() not implemented")}_createBindGroupLayoutWebGPU(e,t){throw new Error("_createBindGroupLayoutWebGPU() not implemented")}_createBindGroupWebGPU(e,t,i,n,o){throw new Error("_createBindGroupWebGPU() not implemented")}_supportsDebugGPUTime(){return this.features.has("timestamp-query")&&Boolean(this.props.debug||this.props.debugGPUTime)}_enableDebugGPUTime(e=256){if(!this._supportsDebugGPUTime())return null;if(this._debugGPUTimeQuery)return this._debugGPUTimeQuery;try{this._debugGPUTimeQuery=this.createQuerySet({type:"timestamp",count:e}),this.commandEncoder=this.createCommandEncoder({id:this.commandEncoder.props.id,timeProfilingQuerySet:this._debugGPUTimeQuery})}catch{this._debugGPUTimeQuery=null}return this._debugGPUTimeQuery}_disableDebugGPUTime(){this._debugGPUTimeQuery&&(this.commandEncoder.getTimeProfilingQuerySet()===this._debugGPUTimeQuery&&(this.commandEncoder=this.createCommandEncoder({id:this.commandEncoder.props.id})),this._debugGPUTimeQuery.destroy(),this._debugGPUTimeQuery=null)}_isDebugGPUTimeEnabled(){return this._debugGPUTimeQuery!==null}getCanvasContext(){return this.getDefaultCanvasContext()}readPixelsToArrayWebGL(e,t){throw new Error("not implemented")}readPixelsToBufferWebGL(e,t){throw new Error("not implemented")}setParametersWebGL(e){throw new Error("not implemented")}getParametersWebGL(e){throw new Error("not implemented")}withParametersWebGL(e,t){throw new Error("not implemented")}clearWebGL(e){throw new Error("not implemented")}resetWebGL(){throw new Error("not implemented")}getModuleData(e){return this._moduleData[e]||={},this._moduleData[e]}static _getCanvasContextProps(e){return e.createCanvasContext===!0?{}:e.createCanvasContext}_getDeviceTextureFormatCapabilities(e){let t=ld.getCapabilities(e),i=o=>(typeof o=="string"?this.features.has(o):o)??!0,n=i(t.create);return{format:e,create:n,render:n&&i(t.render),filter:n&&i(t.filter),blend:n&&i(t.blend),store:n&&i(t.store)}}_normalizeBufferProps(e){(e instanceof ArrayBuffer||ArrayBuffer.isView(e))&&(e={data:e});let t={...e};if((e.usage||0)&lo.INDEX&&(e.indexType||(e.data instanceof Uint32Array?t.indexType="uint32":e.data instanceof Uint16Array?t.indexType="uint16":e.data instanceof Uint8Array&&(t.data=new Uint16Array(e.data),t.indexType="uint16")),!t.indexType))throw new Error("indices buffer content must be of type uint16 or uint32");return t}},Qp=BG;rn(Qp,"defaultProps",{id:null,powerPreference:"high-performance",failIfMajorPerformanceCaveat:!1,createCanvasContext:void 0,webgl:{},onError:(e,t)=>{},onResize:(e,t)=>{let[i,n]=e.getDevicePixelSize();Lr.log(1,`${e} resized => ${i}x${n}px`)()},onPositionChange:(e,t)=>{let[i,n]=e.getPosition();Lr.log(1,`${e} repositioned => ${i},${n}`)()},onVisibilityChange:e=>Lr.log(1,`${e} Visibility changed ${e.isVisible}`)(),onDevicePixelRatioChange:(e,t)=>Lr.log(1,`${e} DPR changed ${t.oldRatio} => ${e.devicePixelRatio}`)(),debug:lRe(),debugGPUTime:!1,debugShaders:Lr.get("debug-shaders")||void 0,debugFramebuffers:Boolean(Lr.get("debug-framebuffers")),debugFactories:Boolean(Lr.get("debug-factories")),debugWebGL:Boolean(Lr.get("debug-webgl")),debugSpectorJS:void 0,debugSpectorJSUrl:void 0,_reuseDevices:!1,_requestMaxLimits:!0,_cacheShaders:!0,_destroyShaders:!1,_cachePipelines:!0,_sharePipelines:!0,_destroyPipelines:!1,_initializeFeatures:!0,_disabledFeatures:{"compilation-status-async-webgl":!0},_handle:void 0})});var uRe,Gre,gF,mF,$A,Hre=Nt(()=>{LG();AG();Rg();uRe="set luma.log.level=1 (or higher) to trace rendering",Gre="No matching device found. Ensure `@luma.gl/webgl` and/or `@luma.gl/webgpu` modules are imported.",gF=class{stats=aF;log=Lr;VERSION="9.3.2";spector;preregisteredAdapters=new Map;constructor(){if(globalThis.luma){if(globalThis.luma.VERSION!==this.VERSION)throw Lr.error(`Found luma.gl ${globalThis.luma.VERSION} while initialzing ${this.VERSION}`)(),Lr.error("'yarn why @luma.gl/core' can help identify the source of the conflict")(),new Error("luma.gl - multiple versions detected: see console log");Lr.error("This version of luma.gl has already been initialized")()}Lr.log(1,`${this.VERSION} - ${uRe}`)(),globalThis.luma=this}async createDevice(e={}){let t={...gF.defaultProps,...e},i=this.selectAdapter(t.type,t.adapters);if(!i)throw new Error(Gre);return t.waitForPageLoad&&await i.pageLoaded,await i.create(t)}async attachDevice(e,t){let i=this._getTypeFromHandle(e,t.adapters),n=i&&this.selectAdapter(i,t.adapters);if(!n)throw new Error(Gre);return await n?.attach?.(e,t)}registerAdapters(e){for(let t of e)this.preregisteredAdapters.set(t.type,t)}getSupportedAdapters(e=[]){let t=this._getAdapterMap(e);return Array.from(t).map(([,i])=>i).filter(i=>i.isSupported?.()).map(i=>i.type)}getBestAvailableAdapterType(e=[]){let t=["webgpu","webgl","null"],i=this._getAdapterMap(e);for(let n of t)if(i.get(n)?.isSupported?.())return n;return null}selectAdapter(e,t=[]){let i=e;e==="best-available"&&(i=this.getBestAvailableAdapterType(t));let n=this._getAdapterMap(t);return i&&n.get(i)||null}enforceWebGL2(e=!0,t=[]){let n=this._getAdapterMap(t).get("webgl");n||Lr.warn("enforceWebGL2: webgl adapter not found")(),n?.enforceWebGL2?.(e)}setDefaultDeviceProps(e){Object.assign(gF.defaultProps,e)}_getAdapterMap(e=[]){let t=new Map(this.preregisteredAdapters);for(let i of e)t.set(i.type,i);return t}_getTypeFromHandle(e,t=[]){return e instanceof WebGL2RenderingContext?"webgl":typeof GPUDevice<"u"&&e instanceof GPUDevice||e?.queue?"webgpu":e===null?"null":(e instanceof WebGLRenderingContext?Lr.warn("WebGL1 is not supported",e)():Lr.warn("Unknown handle type",e)(),null)}},mF=gF;rn(mF,"defaultProps",{...Qp.defaultProps,type:"best-available",adapters:void 0,waitForPageLoad:!0});$A=new mF});function dRe(){return AF||(fRe()||typeof window>"u"?AF=Promise.resolve():AF=new Promise(r=>window.addEventListener("load",()=>r()))),AF}var LI,hRe,fRe,AF,Wre=Nt(()=>{_v();LI=class{get pageLoaded(){return dRe()}},hRe=rp()&&typeof document<"u",fRe=()=>hRe&&document.readyState==="complete",AF=null});var _F,qre=Nt(()=>{_F=class{props;_resizeObserver;_intersectionObserver;_observeDevicePixelRatioTimeout=null;_observeDevicePixelRatioMediaQuery=null;_handleDevicePixelRatioChange=()=>this._refreshDevicePixelRatio();_trackPositionInterval=null;_started=!1;get started(){return this._started}constructor(e){this.props=e}start(){if(!(this._started||!this.props.canvas)){this._started=!0,this._intersectionObserver||=new IntersectionObserver(e=>this.props.onIntersection(e)),this._resizeObserver||=new ResizeObserver(e=>this.props.onResize(e)),this._intersectionObserver.observe(this.props.canvas);try{this._resizeObserver.observe(this.props.canvas,{box:"device-pixel-content-box"})}catch{this._resizeObserver.observe(this.props.canvas,{box:"content-box"})}this._observeDevicePixelRatioTimeout=setTimeout(()=>this._refreshDevicePixelRatio(),0),this.props.trackPosition&&this._trackPosition()}}stop(){this._started&&(this._started=!1,this._observeDevicePixelRatioTimeout&&(clearTimeout(this._observeDevicePixelRatioTimeout),this._observeDevicePixelRatioTimeout=null),this._observeDevicePixelRatioMediaQuery&&(this._observeDevicePixelRatioMediaQuery.removeEventListener("change",this._handleDevicePixelRatioChange),this._observeDevicePixelRatioMediaQuery=null),this._trackPositionInterval&&(clearInterval(this._trackPositionInterval),this._trackPositionInterval=null),this._resizeObserver?.disconnect(),this._intersectionObserver?.disconnect())}_refreshDevicePixelRatio(){this._started&&(this.props.onDevicePixelRatioChange(),this._observeDevicePixelRatioMediaQuery?.removeEventListener("change",this._handleDevicePixelRatioChange),this._observeDevicePixelRatioMediaQuery=matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`),this._observeDevicePixelRatioMediaQuery.addEventListener("change",this._handleDevicePixelRatioChange,{once:!0}))}_trackPosition(e=100){this._trackPositionInterval||(this._trackPositionInterval=setInterval(()=>{this._started?this.props.onPositionChange():this._trackPositionInterval&&(clearInterval(this._trackPositionInterval),this._trackPositionInterval=null)},e))}}});function $re(){let r,e;return{promise:new Promise((i,n)=>{r=i,e=n}),resolve:r,reject:e}}var Xre=Nt(()=>{});function DG(r,e){if(!r){let t=new Error(e??"luma.gl assertion failed.");throw Error.captureStackTrace?.(t,DG),t}}function f1(r,e){return DG(r,e),r}var OG=Nt(()=>{});function pRe(r){if(typeof r=="string"){let e=document.getElementById(r);if(!e)throw new Error(`${r} is not an HTML element`);return e}return r||document.body}function mRe(r){let e=document.getElementById(r);if(!ly.isHTMLCanvas(e))throw new Error("Object is not a canvas element");return e}function gRe(r){let{width:e,height:t}=r,i=document.createElement("canvas");i.id=WA("lumagl-auto-created-canvas"),i.width=e||1,i.height=t||1,i.style.width=Number.isFinite(e)?`${e}px`:"100%",i.style.height=Number.isFinite(t)?`${t}px`:"100%",r?.visible||(i.style.visibility="hidden");let n=pRe(r?.container||null);return n.insertBefore(i,n.firstChild),i}function ARe(r,e,t,i,n){let o=r,s=Zre(o[0],e,t),c=Yre(o[1],e,i,n),f=Zre(o[0]+1,e,t),g=f===t-1?f:f-1;f=Yre(o[1]+1,e,i,n);let y;return n?(f=f===0?f:f+1,y=c,c=f):y=f===i-1?f:f-1,{x:s,y:c,width:Math.max(g-s+1,1),height:Math.max(y-c+1,1)}}function Zre(r,e,t){return Math.min(Math.round(r*e),t-1)}function Yre(r,e,t,i){return i?Math.max(0,t-1-Math.round(r*e)):Math.min(Math.round(r*e),t-1)}var DI,ly,FG=Nt(()=>{_v();qre();CT();Xre();OG();DI=class{static isHTMLCanvas(e){return typeof HTMLCanvasElement<"u"&&e instanceof HTMLCanvasElement}static isOffscreenCanvas(e){return typeof OffscreenCanvas<"u"&&e instanceof OffscreenCanvas}id;props;canvas;htmlCanvas;offscreenCanvas;type;initialized;isInitialized=!1;isVisible=!0;cssWidth;cssHeight;devicePixelRatio;devicePixelWidth;devicePixelHeight;drawingBufferWidth;drawingBufferHeight;_initializedResolvers=$re();_canvasObserver;_position=[0,0];destroyed=!1;_needsDrawingBufferResize=!0;toString(){return`${this[Symbol.toStringTag]}(${this.id})`}constructor(e){this.props={...DI.defaultProps,...e},e=this.props,this.initialized=this._initializedResolvers.promise,rp()?e.canvas?typeof e.canvas=="string"?this.canvas=mRe(e.canvas):this.canvas=e.canvas:this.canvas=gRe(e):this.canvas={width:e.width||1,height:e.height||1},DI.isHTMLCanvas(this.canvas)?(this.id=e.id||this.canvas.id,this.type="html-canvas",this.htmlCanvas=this.canvas):DI.isOffscreenCanvas(this.canvas)?(this.id=e.id||"offscreen-canvas",this.type="offscreen-canvas",this.offscreenCanvas=this.canvas):(this.id=e.id||"node-canvas-context",this.type="node"),this.cssWidth=this.htmlCanvas?.clientWidth||this.canvas.width,this.cssHeight=this.htmlCanvas?.clientHeight||this.canvas.height,this.devicePixelWidth=this.canvas.width,this.devicePixelHeight=this.canvas.height,this.drawingBufferWidth=this.canvas.width,this.drawingBufferHeight=this.canvas.height,this.devicePixelRatio=globalThis.devicePixelRatio||1,this._position=[0,0],this._canvasObserver=new _F({canvas:this.htmlCanvas,trackPosition:this.props.trackPosition,onResize:t=>this._handleResize(t),onIntersection:t=>this._handleIntersection(t),onDevicePixelRatioChange:()=>this._observeDevicePixelRatio(),onPositionChange:()=>this.updatePosition()})}destroy(){this.destroyed||(this.destroyed=!0,this._stopObservers(),this.device=null)}setProps(e){return"useDevicePixels"in e&&(this.props.useDevicePixels=e.useDevicePixels||!1,this._updateDrawingBufferSize()),this}getCurrentFramebuffer(e){return this._resizeDrawingBufferIfNeeded(),this._getCurrentFramebuffer(e)}getCSSSize(){return[this.cssWidth,this.cssHeight]}getPosition(){return this._position}getDevicePixelSize(){return[this.devicePixelWidth,this.devicePixelHeight]}getDrawingBufferSize(){return[this.drawingBufferWidth,this.drawingBufferHeight]}getMaxDrawingBufferSize(){let e=this.device.limits.maxTextureDimension2D;return[e,e]}setDrawingBufferSize(e,t){e=Math.floor(e),t=Math.floor(t),!(this.drawingBufferWidth===e&&this.drawingBufferHeight===t)&&(this.drawingBufferWidth=e,this.drawingBufferHeight=t,this._needsDrawingBufferResize=!0)}getDevicePixelRatio(){return typeof window<"u"&&window.devicePixelRatio||1}cssToDevicePixels(e,t=!0){let i=this.cssToDeviceRatio(),[n,o]=this.getDrawingBufferSize();return ARe(e,i,n,o,t)}getPixelSize(){return this.getDevicePixelSize()}getAspect(){let[e,t]=this.getDrawingBufferSize();return e>0&&t>0?e/t:1}cssToDeviceRatio(){try{let[e]=this.getDrawingBufferSize(),[t]=this.getCSSSize();return t?e/t:1}catch{return 1}}resize(e){this.setDrawingBufferSize(e.width,e.height)}_setAutoCreatedCanvasId(e){this.htmlCanvas?.id==="lumagl-auto-created-canvas"&&(this.htmlCanvas.id=e)}_startObservers(){this.destroyed||this._canvasObserver.start()}_stopObservers(){this._canvasObserver.stop()}_handleIntersection(e){if(this.destroyed)return;let t=e.find(n=>n.target===this.canvas);if(!t)return;let i=t.isIntersecting;this.isVisible!==i&&(this.isVisible=i,this.device.props.onVisibilityChange(this))}_handleResize(e){if(this.destroyed)return;let t=e.find(g=>g.target===this.canvas);if(!t)return;let i=f1(t.contentBoxSize?.[0]);this.cssWidth=i.inlineSize,this.cssHeight=i.blockSize;let n=this.getDevicePixelSize(),o=t.devicePixelContentBoxSize?.[0]?.inlineSize||i.inlineSize*devicePixelRatio,s=t.devicePixelContentBoxSize?.[0]?.blockSize||i.blockSize*devicePixelRatio,[c,f]=this.getMaxDrawingBufferSize();this.devicePixelWidth=Math.max(1,Math.min(o,c)),this.devicePixelHeight=Math.max(1,Math.min(s,f)),this._updateDrawingBufferSize(),this.device.props.onResize(this,{oldPixelSize:n})}_updateDrawingBufferSize(){if(this.props.autoResize)if(typeof this.props.useDevicePixels=="number"){let e=this.props.useDevicePixels;this.setDrawingBufferSize(this.cssWidth*e,this.cssHeight*e)}else this.props.useDevicePixels?this.setDrawingBufferSize(this.devicePixelWidth,this.devicePixelHeight):this.setDrawingBufferSize(this.cssWidth,this.cssHeight);this._initializedResolvers.resolve(),this.isInitialized=!0,this.updatePosition()}_resizeDrawingBufferIfNeeded(){this._needsDrawingBufferResize&&(this._needsDrawingBufferResize=!1,(this.drawingBufferWidth!==this.canvas.width||this.drawingBufferHeight!==this.canvas.height)&&(this.canvas.width=this.drawingBufferWidth,this.canvas.height=this.drawingBufferHeight,this._configureDevice()))}_observeDevicePixelRatio(){if(this.destroyed||!this._canvasObserver.started)return;let e=this.devicePixelRatio;this.devicePixelRatio=window.devicePixelRatio,this.updatePosition(),this.device.props.onDevicePixelRatioChange?.(this,{oldRatio:e})}updatePosition(){if(this.destroyed)return;let e=this.htmlCanvas?.getBoundingClientRect();if(e){let t=[e.left,e.top];if(this._position??=t,t[0]!==this._position[0]||t[1]!==this._position[1]){let n=this._position;this._position=t,this.device.props.onPositionChange?.(this,{oldPosition:n})}}}},ly=DI;rn(ly,"defaultProps",{id:void 0,canvas:null,width:800,height:600,useDevicePixels:!0,autoResize:!0,container:null,visible:!0,alphaMode:"opaque",colorSpace:"srgb",trackPosition:!1})});var IT,Kre=Nt(()=>{FG();IT=class extends ly{};rn(IT,"defaultProps",ly.defaultProps)});var OI,Qre=Nt(()=>{FG();OI=class extends ly{}});var yF,Dd,kG=Nt(()=>{ad();yF=class extends $o{get[Symbol.toStringTag](){return"Sampler"}constructor(e,t){t=yF.normalizeProps(e,t),super(e,t,yF.defaultProps)}static normalizeProps(e,t){return t}},Dd=yF;rn(Dd,"defaultProps",{...$o.defaultProps,type:"color-sampler",addressModeU:"clamp-to-edge",addressModeV:"clamp-to-edge",addressModeW:"clamp-to-edge",magFilter:"nearest",minFilter:"nearest",mipmapFilter:"none",lodMinClamp:0,lodMaxClamp:32,compare:"less-equal",maxAnisotropy:1})});var _Re,Fm,oo,NG=Nt(()=>{ad();kG();Rg();pF();_Re={"1d":"1d","2d":"2d","2d-array":"2d",cube:"2d","cube-array":"2d","3d":"3d"},Fm=class extends $o{dimension;baseDimension;format;width;height;depth;mipLevels;samples;byteAlignment;ready=Promise.resolve(this);isReady=!0;updateTimestamp;get[Symbol.toStringTag](){return"Texture"}toString(){return`Texture(${this.id},${this.format},${this.width}x${this.height})`}constructor(e,t,i){if(t=Fm.normalizeProps(e,t),super(e,t,Fm.defaultProps),this.dimension=this.props.dimension,this.baseDimension=_Re[this.dimension],this.format=this.props.format,this.width=this.props.width,this.height=this.props.height,this.depth=this.props.depth,this.mipLevels=this.props.mipLevels,this.samples=this.props.samples||1,this.dimension==="cube"&&(this.depth=6),this.props.width===void 0||this.props.height===void 0)if(e.isExternalImage(t.data)){let n=e.getExternalImageSize(t.data);this.width=n?.width||1,this.height=n?.height||1}else this.width=1,this.height=1,(this.props.width===void 0||this.props.height===void 0)&&Lr.warn(`${this} created with undefined width or height. This is deprecated. Use DynamicTexture instead.`)();this.byteAlignment=i?.byteAlignment||1,this.updateTimestamp=e.incrementTimestamp()}clone(e){return this.device.createTexture({...this.props,...e})}setSampler(e){this.sampler=e instanceof Dd?e:this.device.createSampler(e)}copyImageData(e){let{data:t,depth:i,...n}=e;this.writeData(t,{...n,depthOrArrayLayers:n.depthOrArrayLayers??i})}computeMemoryLayout(e={}){let t=this._normalizeTextureReadOptions(e),{width:i=this.width,height:n=this.height,depthOrArrayLayers:o=this.depth}=t,{format:s,byteAlignment:c}=this;return ld.computeMemoryLayout({format:s,width:i,height:n,depth:o,byteAlignment:c})}readBuffer(e,t){throw new Error("readBuffer not implemented")}readDataAsync(e){throw new Error("readBuffer not implemented")}writeBuffer(e,t){throw new Error("readBuffer not implemented")}writeData(e,t){throw new Error("readBuffer not implemented")}readDataSyncWebGL(e){throw new Error("readDataSyncWebGL not available")}generateMipmapsWebGL(){throw new Error("generateMipmapsWebGL not available")}static normalizeProps(e,t){let i={...t},{width:n,height:o}=i;return typeof n=="number"&&(i.width=Math.max(1,Math.ceil(n))),typeof o=="number"&&(i.height=Math.max(1,Math.ceil(o))),i}_initializeData(e){this.device.isExternalImage(e)?this.copyExternalImage({image:e,width:this.width,height:this.height,depth:this.depth,mipLevel:0,x:0,y:0,z:0,aspect:"all",colorSpace:"srgb",premultipliedAlpha:!1,flipY:!1}):e&&this.copyImageData({data:e,mipLevel:0,x:0,y:0,z:0,aspect:"all"})}_normalizeCopyImageDataOptions(e){let{data:t,depth:i,...n}=e,o=this._normalizeTextureWriteOptions({...n,depthOrArrayLayers:n.depthOrArrayLayers??i});return{data:t,depth:o.depthOrArrayLayers,...o}}_normalizeCopyExternalImageOptions(e){let t=Fm._omitUndefined(e),i=t.mipLevel??0,n=this._getMipLevelSize(i),o=this.device.getExternalImageSize(e.image),s={...Fm.defaultCopyExternalImageOptions,...n,...o,...t};return s.width=Math.min(s.width,n.width-s.x),s.height=Math.min(s.height,n.height-s.y),s.depth=Math.min(s.depth,n.depthOrArrayLayers-s.z),s}_normalizeTextureReadOptions(e){let t=Fm._omitUndefined(e),i=t.mipLevel??0,n=this._getMipLevelSize(i),o={...Fm.defaultTextureReadOptions,...n,...t};return o.width=Math.min(o.width,n.width-o.x),o.height=Math.min(o.height,n.height-o.y),o.depthOrArrayLayers=Math.min(o.depthOrArrayLayers,n.depthOrArrayLayers-o.z),o}_getSupportedColorReadOptions(e){let t=this._normalizeTextureReadOptions(e),i=ld.getInfo(this.format);switch(this._validateColorReadAspect(t),this._validateColorReadFormat(i),this.dimension){case"2d":case"cube":case"cube-array":case"2d-array":case"3d":return t;default:throw new Error(`${this} color readback does not support ${this.dimension} textures`)}}_validateColorReadAspect(e){if(e.aspect!=="all")throw new Error(`${this} color readback only supports aspect 'all'`)}_validateColorReadFormat(e){if(e.compressed)throw new Error(`${this} color readback does not support compressed formats (${this.format})`);switch(e.attachment){case"color":return;case"depth":throw new Error(`${this} color readback does not support depth formats (${this.format})`);case"stencil":throw new Error(`${this} color readback does not support stencil formats (${this.format})`);case"depth-stencil":throw new Error(`${this} color readback does not support depth-stencil formats (${this.format})`);default:throw new Error(`${this} color readback does not support format ${this.format}`)}}_normalizeTextureWriteOptions(e){let t=Fm._omitUndefined(e),i=t.mipLevel??0,n=this._getMipLevelSize(i),o={...Fm.defaultTextureWriteOptions,...n,...t};o.width=Math.min(o.width,n.width-o.x),o.height=Math.min(o.height,n.height-o.y),o.depthOrArrayLayers=Math.min(o.depthOrArrayLayers,n.depthOrArrayLayers-o.z);let s=ld.computeMemoryLayout({format:this.format,width:o.width,height:o.height,depth:o.depthOrArrayLayers,byteAlignment:this.byteAlignment}),c=s.bytesPerPixel*o.width;if(o.bytesPerRow=t.bytesPerRow??s.bytesPerRow,o.rowsPerImage=t.rowsPerImage??o.height,o.bytesPerRow>e),i=this.baseDimension==="1d"?1:Math.max(1,this.height>>e),n=this.dimension==="3d"?Math.max(1,this.depth>>e):this.depth;return{width:t,height:i,depthOrArrayLayers:n}}getAllocatedByteLength(){let e=0;for(let t=0;tt!==void 0))}},oo=Fm;rn(oo,"SAMPLE",4),rn(oo,"STORAGE",8),rn(oo,"RENDER",16),rn(oo,"COPY_SRC",1),rn(oo,"COPY_DST",2),rn(oo,"TEXTURE",4),rn(oo,"RENDER_ATTACHMENT",16),rn(oo,"defaultProps",{...$o.defaultProps,data:null,dimension:"2d",format:"rgba8unorm",usage:Fm.SAMPLE|Fm.RENDER|Fm.COPY_DST,width:void 0,height:void 0,depth:1,mipLevels:1,samples:void 0,sampler:{},view:void 0}),rn(oo,"defaultCopyDataOptions",{data:void 0,byteOffset:0,bytesPerRow:void 0,rowsPerImage:void 0,width:void 0,height:void 0,depthOrArrayLayers:void 0,depth:1,mipLevel:0,x:0,y:0,z:0,aspect:"all"}),rn(oo,"defaultCopyExternalImageOptions",{image:void 0,sourceX:0,sourceY:0,width:void 0,height:void 0,depth:1,mipLevel:0,x:0,y:0,z:0,aspect:"all",colorSpace:"srgb",premultipliedAlpha:!1,flipY:!1}),rn(oo,"defaultTextureReadOptions",{x:0,y:0,z:0,width:void 0,height:void 0,depthOrArrayLayers:1,mipLevel:0,aspect:"all"}),rn(oo,"defaultTextureWriteOptions",{byteOffset:0,bytesPerRow:void 0,rowsPerImage:void 0,x:0,y:0,z:0,width:void 0,height:void 0,depthOrArrayLayers:1,mipLevel:0,aspect:"all"})});var zG,Bg,Jre=Nt(()=>{ad();zG=class extends $o{get[Symbol.toStringTag](){return"TextureView"}constructor(e,t){super(e,t,zG.defaultProps)}},Bg=zG;rn(Bg,"defaultProps",{...$o.defaultProps,format:void 0,dimension:void 0,aspect:"all",baseMipLevel:0,mipLevelCount:void 0,baseArrayLayer:0,arrayLayerCount:void 0})});function eie(r,e,t){let i="",n=e.split(/\r?\n/),o=r.slice().sort((s,c)=>s.lineNum-c.lineNum);switch(t?.showSourceCode||"no"){case"all":let s=0;for(let c=1;c<=n.length;c++){let f=n[c-1],g=o[s];for(f&&g&&(i+=tie(f,c,t));o.length>s&&g.lineNum===c;){let y=o[s++];y&&(i+=UG(y,n,y.lineNum,{...t,inlineSource:!1}))}}for(;o.length>s;){let c=o[s++];c&&(i+=UG(c,[],0,{...t,inlineSource:!1}))}return i;case"issues":case"no":for(let c of r)i+=UG(c,n,c.lineNum,{inlineSource:t?.showSourceCode!=="no"});return i}}function UG(r,e,t,i){if(i?.inlineSource){let o=yRe(e,t),s=r.linePos>0?`${" ".repeat(r.linePos+5)}^^^ +`:"";return` +${o}${s}${r.type.toUpperCase()}: ${r.message} + +`}let n=r.type==="error"?"red":"orange";return i?.html?`

${r.type.toUpperCase()}: ${r.message}
`:`${r.type.toUpperCase()}: ${r.message}`}function yRe(r,e,t){let i="";for(let n=e-2;n<=e;n++){let o=r[n-1];o!==void 0&&(i+=tie(o,e,t))}return i}function tie(r,e,t){let i=t?.html?xRe(r):r;return`${vRe(String(e),4)}: ${i}${t?.html?"
":` +`}`}function vRe(r,e){let t="";for(let i=r.length;i",">").replaceAll('"',""").replaceAll("'","'")}var rie=Nt(()=>{});function bRe(r){return wRe(r.source)||r.id||WA(`unnamed ${r.stage}-shader`)}function wRe(r,e="unnamed"){return/#define[\s*]SHADER_NAME[\s*]([A-Za-z0-9_-]+)[\s*]/.exec(r)?.[1]??e}var VG,Uv,jG=Nt(()=>{ad();CT();rie();VG=class extends $o{get[Symbol.toStringTag](){return"Shader"}stage;source;compilationStatus="pending";constructor(e,t){t={...t,debugShaders:t.debugShaders||e.props.debugShaders||"errors"},super(e,{id:bRe(t),...t},VG.defaultProps),this.stage=this.props.stage,this.source=this.props.source}getCompilationInfoSync(){return null}getTranslatedSource(){return null}async debugShader(){let e=this.props.debugShaders;switch(e){case"never":return;case"errors":if(this.compilationStatus==="success")return;break;case"warnings":case"always":break}let t=await this.getCompilationInfo();e==="warnings"&&t?.length===0||this._displayShaderLog(t,this.id)}_displayShaderLog(e,t){if(typeof document>"u"||!document?.createElement)return;let i=t,n=`${this.stage} shader "${i}"`,o=eie(e,this.source,{showSourceCode:"all",html:!0}),s=this.getTranslatedSource(),c=document.createElement("div");c.innerHTML=`

Compilation error in ${n}

+
+
+ +
+
${o}
`,s&&(c.innerHTML+=`

Translated Source



${s}
`),c.style.top="0",c.style.left="0",c.style.background="white",c.style.position="fixed",c.style.zIndex="9999",c.style.maxWidth="100vw",c.style.maxHeight="100vh",c.style.overflowY="auto",document.body.appendChild(c),c.querySelector(".luma-compiler-log-error")?.scrollIntoView(),c.querySelector("button#close").onclick=()=>{c.remove()},c.querySelector("button#copy").onclick=()=>{navigator.clipboard.writeText(this.source)}}},Uv=VG;rn(Uv,"defaultProps",{...$o.defaultProps,language:"auto",stage:void 0,source:"",sourceMap:null,entryPoint:"main",debugShaders:void 0})});var GG,XA,iie=Nt(()=>{ad();NG();Rg();GG=class extends $o{get[Symbol.toStringTag](){return"Framebuffer"}width;height;constructor(e,t={}){super(e,t,GG.defaultProps),this.width=this.props.width,this.height=this.props.height}clone(e){let t=this.colorAttachments.map(n=>n.texture.clone(e)),i=this.depthStencilAttachment&&this.depthStencilAttachment.texture.clone(e);return this.device.createFramebuffer({...this.props,...e,colorAttachments:t,depthStencilAttachment:i})}resize(e){let t=!e;if(e){let[i,n]=Array.isArray(e)?e:[e.width,e.height];t=t||n!==this.height||i!==this.width,this.width=i,this.height=n}t&&(Lr.log(2,`Resizing framebuffer ${this.id} to ${this.width}x${this.height}`)(),this.resizeAttachments(this.width,this.height))}autoCreateAttachmentTextures(){if(this.props.colorAttachments.length===0&&!this.props.depthStencilAttachment)throw new Error("Framebuffer has noattachments");this.colorAttachments=this.props.colorAttachments.map((t,i)=>{if(typeof t=="string"){let n=this.createColorTexture(t,i);return this.attachResource(n),n.view}return t instanceof oo?t.view:t});let e=this.props.depthStencilAttachment;if(e)if(typeof e=="string"){let t=this.createDepthStencilTexture(e);this.attachResource(t),this.depthStencilAttachment=t.view}else e instanceof oo?this.depthStencilAttachment=e.view:this.depthStencilAttachment=e}createColorTexture(e,t){return this.device.createTexture({id:`${this.id}-color-attachment-${t}`,usage:oo.RENDER_ATTACHMENT,format:e,width:this.width,height:this.height,sampler:{magFilter:"linear",minFilter:"linear"}})}createDepthStencilTexture(e){return this.device.createTexture({id:`${this.id}-depth-stencil-attachment`,usage:oo.RENDER_ATTACHMENT,format:e,width:this.width,height:this.height})}resizeAttachments(e,t){if(this.colorAttachments.forEach((i,n)=>{let o=i.texture.clone({width:e,height:t});this.destroyAttachedResource(i),this.colorAttachments[n]=o.view,this.attachResource(o.view)}),this.depthStencilAttachment){let i=this.depthStencilAttachment.texture.clone({width:e,height:t});this.destroyAttachedResource(this.depthStencilAttachment),this.depthStencilAttachment=i.view,this.attachResource(i)}this.updateAttachments()}},XA=GG;rn(XA,"defaultProps",{...$o.defaultProps,width:1,height:1,colorAttachments:[],depthStencilAttachment:null})});var HG,km,WG=Nt(()=>{ad();HG=class extends $o{get[Symbol.toStringTag](){return"RenderPipeline"}shaderLayout;bufferLayout;linkStatus="pending";hash="";sharedRenderPipeline=null;get isPending(){return this.linkStatus==="pending"||this.vs.compilationStatus==="pending"||this.fs?.compilationStatus==="pending"}get isErrored(){return this.linkStatus==="error"||this.vs.compilationStatus==="error"||this.fs?.compilationStatus==="error"}constructor(e,t){super(e,t,HG.defaultProps),this.shaderLayout=this.props.shaderLayout,this.bufferLayout=this.props.bufferLayout||[],this.sharedRenderPipeline=this.props._sharedRenderPipeline||null}},km=HG;rn(km,"defaultProps",{...$o.defaultProps,vs:null,vertexEntryPoint:"vertexMain",vsConstants:{},fs:null,fragmentEntryPoint:"fragmentMain",fsConstants:{},shaderLayout:null,bufferLayout:[],topology:"triangle-list",colorAttachmentFormats:void 0,depthStencilAttachmentFormat:void 0,parameters:{},varyings:void 0,bufferMode:void 0,disableWarnings:!1,_sharedRenderPipeline:void 0,bindings:void 0,bindGroups:void 0})});var FI,nie=Nt(()=>{ad();FI=class extends $o{get[Symbol.toStringTag](){return"SharedRenderPipeline"}constructor(e,t){super(e,t,{...$o.defaultProps,handle:void 0,vs:void 0,fs:void 0,varyings:void 0,bufferMode:void 0})}}});var qG,PT,oie=Nt(()=>{ad();qG=class extends $o{get[Symbol.toStringTag](){return"ComputePipeline"}hash="";shaderLayout;constructor(e,t){super(e,t,qG.defaultProps),this.shaderLayout=t.shaderLayout}},PT=qG;rn(PT,"defaultProps",{...$o.defaultProps,shader:void 0,entryPoint:void 0,constants:{},shaderLayout:void 0})});var $G,RT,sie=Nt(()=>{oie();WG();Rg();CT();$G=class{static getDefaultPipelineFactory(e){let t=e.getModuleData("@luma.gl/core");return t.defaultPipelineFactory||=new $G(e),t.defaultPipelineFactory}device;_hashCounter=0;_hashes={};_renderPipelineCache={};_computePipelineCache={};_sharedRenderPipelineCache={};get[Symbol.toStringTag](){return"PipelineFactory"}toString(){return`PipelineFactory(${this.device.id})`}constructor(e){this.device=e}createRenderPipeline(e){if(!this.device.props._cachePipelines)return this.device.createRenderPipeline(e);let t={...km.defaultProps,...e},i=this._renderPipelineCache,n=this._hashRenderPipeline(t),o=i[n]?.resource;if(o)i[n].useCount++,this.device.props.debugFactories&&Lr.log(3,`${this}: ${i[n].resource} reused, count=${i[n].useCount}, (id=${e.id})`)();else{let s=this.device.type==="webgl"&&this.device.props._sharePipelines?this.createSharedRenderPipeline(t):void 0;o=this.device.createRenderPipeline({...t,id:t.id?`${t.id}-cached`:WA("unnamed-cached"),_sharedRenderPipeline:s}),o.hash=n,i[n]={resource:o,useCount:1},this.device.props.debugFactories&&Lr.log(3,`${this}: ${o} created, count=${i[n].useCount}`)()}return o}createComputePipeline(e){if(!this.device.props._cachePipelines)return this.device.createComputePipeline(e);let t={...PT.defaultProps,...e},i=this._computePipelineCache,n=this._hashComputePipeline(t),o=i[n]?.resource;return o?(i[n].useCount++,this.device.props.debugFactories&&Lr.log(3,`${this}: ${i[n].resource} reused, count=${i[n].useCount}, (id=${e.id})`)()):(o=this.device.createComputePipeline({...t,id:t.id?`${t.id}-cached`:void 0}),o.hash=n,i[n]={resource:o,useCount:1},this.device.props.debugFactories&&Lr.log(3,`${this}: ${o} created, count=${i[n].useCount}`)()),o}release(e){if(!this.device.props._cachePipelines){e.destroy();return}let t=this._getCache(e),i=e.hash;t[i].useCount--,t[i].useCount===0?(this._destroyPipeline(e),this.device.props.debugFactories&&Lr.log(3,`${this}: ${e} released and destroyed`)()):t[i].useCount<0?(Lr.error(`${this}: ${e} released, useCount < 0, resetting`)(),t[i].useCount=0):this.device.props.debugFactories&&Lr.log(3,`${this}: ${e} released, count=${t[i].useCount}`)()}createSharedRenderPipeline(e){let t=this._hashSharedRenderPipeline(e),i=this._sharedRenderPipelineCache[t];return i||(i={resource:this.device._createSharedRenderPipelineWebGL(e),useCount:0},this._sharedRenderPipelineCache[t]=i),i.useCount++,i.resource}releaseSharedRenderPipeline(e){if(!e.sharedRenderPipeline)return;let t=this._hashSharedRenderPipeline(e.sharedRenderPipeline.props),i=this._sharedRenderPipelineCache[t];i&&(i.useCount--,i.useCount===0&&(i.resource.destroy(),delete this._sharedRenderPipelineCache[t]))}_destroyPipeline(e){let t=this._getCache(e);return this.device.props._destroyPipelines?(delete t[e.hash],e.destroy(),e instanceof km&&this.releaseSharedRenderPipeline(e),!0):!1}_getCache(e){let t;if(e instanceof PT&&(t=this._computePipelineCache),e instanceof km&&(t=this._renderPipelineCache),!t)throw new Error(`${this}`);if(!t[e.hash])throw new Error(`${this}: ${e} matched incorrect entry`);return t}_hashComputePipeline(e){let{type:t}=this.device,i=this._getHash(e.shader.source),n=this._getHash(JSON.stringify(e.shaderLayout));return`${t}/C/${i}SL${n}`}_hashRenderPipeline(e){let t=e.vs?this._getHash(e.vs.source):0,i=e.fs?this._getHash(e.fs.source):0,n=this._getWebGLVaryingHash(e),o=this._getHash(JSON.stringify(e.shaderLayout)),s=this._getHash(JSON.stringify(e.bufferLayout)),{type:c}=this.device;switch(c){case"webgl":let f=this._getHash(JSON.stringify(e.parameters));return`${c}/R/${t}/${i}V${n}T${e.topology}P${f}SL${o}BL${s}`;case"webgpu":default:let g=this._getHash(JSON.stringify({vertexEntryPoint:e.vertexEntryPoint,fragmentEntryPoint:e.fragmentEntryPoint})),y=this._getHash(JSON.stringify(e.parameters)),M=this._getWebGPUAttachmentHash(e);return`${c}/R/${t}/${i}V${n}T${e.topology}EP${g}P${y}SL${o}BL${s}A${M}`}}_hashSharedRenderPipeline(e){let t=e.vs?this._getHash(e.vs.source):0,i=e.fs?this._getHash(e.fs.source):0,n=this._getWebGLVaryingHash(e);return`webgl/S/${t}/${i}V${n}`}_getHash(e){return this._hashes[e]===void 0&&(this._hashes[e]=this._hashCounter++),this._hashes[e]}_getWebGLVaryingHash(e){let{varyings:t=[],bufferMode:i=null}=e;return this._getHash(JSON.stringify({varyings:t,bufferMode:i}))}_getWebGPUAttachmentHash(e){let t=e.colorAttachmentFormats??[this.device.preferredColorFormat],i=e.parameters?.depthWriteEnabled?e.depthStencilAttachmentFormat||this.device.preferredDepthFormat:null;return this._getHash(JSON.stringify({colorAttachmentFormats:t,depthStencilAttachmentFormat:i}))}},RT=$G;rn(RT,"defaultProps",{...km.defaultProps})});var XG,BT,aie=Nt(()=>{jG();Rg();XG=class{static getDefaultShaderFactory(e){let t=e.getModuleData("@luma.gl/core");return t.defaultShaderFactory||=new XG(e),t.defaultShaderFactory}device;_cache={};get[Symbol.toStringTag](){return"ShaderFactory"}toString(){return`${this[Symbol.toStringTag]}(${this.device.id})`}constructor(e){this.device=e}createShader(e){if(!this.device.props._cacheShaders)return this.device.createShader(e);let t=this._hashShader(e),i=this._cache[t];if(i)i.useCount++,this.device.props.debugFactories&&Lr.log(3,`${this}: Reusing shader ${i.resource.id} count=${i.useCount}`)();else{let n=this.device.createShader({...e,id:e.id?`${e.id}-cached`:void 0});this._cache[t]=i={resource:n,useCount:1},this.device.props.debugFactories&&Lr.log(3,`${this}: Created new shader ${n.id}`)()}return i.resource}release(e){if(!this.device.props._cacheShaders){e.destroy();return}let t=this._hashShader(e),i=this._cache[t];if(i)if(i.useCount--,i.useCount===0)this.device.props._destroyShaders&&(delete this._cache[t],i.resource.destroy(),this.device.props.debugFactories&&Lr.log(3,`${this}: Releasing shader ${e.id}, destroyed`)());else{if(i.useCount<0)throw new Error(`ShaderFactory: Shader ${e.id} released too many times`);this.device.props.debugFactories&&Lr.log(3,`${this}: Releasing shader ${e.id} count=${i.useCount}`)()}}_hashShader(e){return`${e.stage}:${e.source}`}},BT=XG;rn(BT,"defaultProps",{...Uv.defaultProps})});function lie(r,e,t){let i=r.bindings.find(n=>n.name===e||`${n.name.toLocaleLowerCase()}uniforms`===e.toLocaleLowerCase());return!i&&!t?.ignoreWarnings&&Lr.warn(`Binding ${e} not set: Not found in shader layout.`)(),i||null}function kI(r,e){if(!e)return{};if(TRe(e))return Object.fromEntries(Object.entries(e).map(([n,o])=>[Number(n),{...o}]));let t={};for(let[i,n]of Object.entries(e)){let s=lie(r,i)?.group??0;t[s]||={},t[s][i]=n}return t}function vF(r){let e={};for(let t of Object.values(r))Object.assign(e,t);return e}function TRe(r){let e=Object.keys(r);return e.length>0&&e.every(t=>/^\d+$/.test(t))}var cie=Nt(()=>{Rg()});var d1,cy,uie=Nt(()=>{ad();d1=class extends $o{get[Symbol.toStringTag](){return"RenderPass"}constructor(e,t){t=d1.normalizeProps(e,t),super(e,t,d1.defaultProps)}static normalizeProps(e,t){return t}},cy=d1;rn(cy,"defaultClearColor",[0,0,0,1]),rn(cy,"defaultClearDepth",1),rn(cy,"defaultClearStencil",0),rn(cy,"defaultProps",{...$o.defaultProps,framebuffer:null,parameters:void 0,clearColor:d1.defaultClearColor,clearColors:void 0,clearDepth:d1.defaultClearDepth,clearStencil:d1.defaultClearStencil,depthReadOnly:!1,stencilReadOnly:!1,discard:!1,occlusionQuerySet:void 0,timestampQuerySet:void 0,beginTimestampIndex:void 0,endTimestampIndex:void 0})});var ZG,LT,hie=Nt(()=>{ad();ZG=class extends $o{get[Symbol.toStringTag](){return"CommandEncoder"}_timeProfilingQuerySet=null;_timeProfilingSlotCount=0;_gpuTimeMs;constructor(e,t){super(e,t,ZG.defaultProps),this._timeProfilingQuerySet=t.timeProfilingQuerySet??null,this._timeProfilingSlotCount=0,this._gpuTimeMs=void 0}async resolveTimeProfilingQuerySet(){if(this._gpuTimeMs=void 0,!this._timeProfilingQuerySet)return;let e=Math.floor(this._timeProfilingSlotCount/2);if(e<=0)return;let t=e*2,i=await this._timeProfilingQuerySet.readResults({firstQuery:0,queryCount:t}),n=0n;for(let o=0;o=this._timeProfilingQuerySet.props.count?t:(this._timeProfilingSlotCount+=2,{...t,timestampQuerySet:this._timeProfilingQuerySet,beginTimestampIndex:i,endTimestampIndex:i+1})}_supportsTimestampQueries(){return this.device.features.has("timestamp-query")}},LT=ZG;rn(LT,"defaultProps",{...$o.defaultProps,measureExecutionTime:void 0,timeProfilingQuerySet:void 0})});var YG,DT,fie=Nt(()=>{ad();YG=class extends $o{get[Symbol.toStringTag](){return"CommandBuffer"}constructor(e,t){super(e,t,YG.defaultProps)}},DT=YG;rn(DT,"defaultProps",{...$o.defaultProps})});function OT(r){let e=xF(r),t=MRe[e];if(!t)throw new Error(`Unsupported variable shader type: ${r}`);return t}function die(r){let e=pie(r),t=CRe[e];if(!t)throw new Error(`Unsupported attribute shader type: ${r}`);let[i,n]=t,o=i==="i32"||i==="u32",s=i!=="u32",c=ERe[i]*n;return{primitiveType:i,components:n,byteLength:c,integer:o,signed:s}}function SRe(r,e){return e===1?r:`vec${e}<${r}>`}function pie(r){return IRe[r]||r}function xF(r){return PRe[r]||r}var KG,QG,ERe,CRe,MRe,IRe,PRe,bF=Nt(()=>{KG=class{getVariableShaderTypeInfo(e){return OT(e)}getAttributeShaderTypeInfo(e){return die(e)}makeShaderAttributeType(e,t){return SRe(e,t)}resolveAttributeShaderTypeAlias(e){return pie(e)}resolveVariableShaderTypeAlias(e){return xF(e)}};QG=new KG,ERe={f32:4,f16:2,i32:4,u32:4},CRe={f32:["f32",1],"vec2":["f32",2],"vec3":["f32",3],"vec4":["f32",4],f16:["f16",1],"vec2":["f16",2],"vec3":["f16",3],"vec4":["f16",4],i32:["i32",1],"vec2":["i32",2],"vec3":["i32",3],"vec4":["i32",4],u32:["u32",1],"vec2":["u32",2],"vec3":["u32",3],"vec4":["u32",4]},MRe={f32:{type:"f32",components:1},f16:{type:"f16",components:1},i32:{type:"i32",components:1},u32:{type:"u32",components:1},"vec2":{type:"f32",components:2},"vec3":{type:"f32",components:3},"vec4":{type:"f32",components:4},"vec2":{type:"f16",components:2},"vec3":{type:"f16",components:3},"vec4":{type:"f16",components:4},"vec2":{type:"i32",components:2},"vec3":{type:"i32",components:3},"vec4":{type:"i32",components:4},"vec2":{type:"u32",components:2},"vec3":{type:"u32",components:3},"vec4":{type:"u32",components:4},"mat2x2":{type:"f32",components:4},"mat2x3":{type:"f32",components:6},"mat2x4":{type:"f32",components:8},"mat3x2":{type:"f32",components:6},"mat3x3":{type:"f32",components:9},"mat3x4":{type:"f32",components:12},"mat4x2":{type:"f32",components:8},"mat4x3":{type:"f32",components:12},"mat4x4":{type:"f32",components:16},"mat2x2":{type:"f16",components:4},"mat2x3":{type:"f16",components:6},"mat2x4":{type:"f16",components:8},"mat3x2":{type:"f16",components:6},"mat3x3":{type:"f16",components:9},"mat3x4":{type:"f16",components:12},"mat4x2":{type:"f16",components:8},"mat4x3":{type:"f16",components:12},"mat4x4":{type:"f16",components:16},"mat2x2":{type:"i32",components:4},"mat2x3":{type:"i32",components:6},"mat2x4":{type:"i32",components:8},"mat3x2":{type:"i32",components:6},"mat3x3":{type:"i32",components:9},"mat3x4":{type:"i32",components:12},"mat4x2":{type:"i32",components:8},"mat4x3":{type:"i32",components:12},"mat4x4":{type:"i32",components:16},"mat2x2":{type:"u32",components:4},"mat2x3":{type:"u32",components:6},"mat2x4":{type:"u32",components:8},"mat3x2":{type:"u32",components:6},"mat3x3":{type:"u32",components:9},"mat3x4":{type:"u32",components:12},"mat4x2":{type:"u32",components:8},"mat4x3":{type:"u32",components:12},"mat4x4":{type:"u32",components:16}},IRe={vec2i:"vec2",vec3i:"vec3",vec4i:"vec4",vec2u:"vec2",vec3u:"vec3",vec4u:"vec4",vec2f:"vec2",vec3f:"vec3",vec4f:"vec4",vec2h:"vec2",vec3h:"vec3",vec4h:"vec4"},PRe={vec2i:"vec2",vec3i:"vec3",vec4i:"vec4",vec2u:"vec2",vec3u:"vec3",vec4u:"vec4",vec2f:"vec2",vec3f:"vec3",vec4f:"vec4",vec2h:"vec2",vec3h:"vec3",vec4h:"vec4",mat2x2f:"mat2x2",mat2x3f:"mat2x3",mat2x4f:"mat2x4",mat3x2f:"mat3x2",mat3x3f:"mat3x3",mat3x4f:"mat3x4",mat4x2f:"mat4x2",mat4x3f:"mat4x3",mat4x4f:"mat4x4",mat2x2i:"mat2x2",mat2x3i:"mat2x3",mat2x4i:"mat2x4",mat3x2i:"mat3x2",mat3x3i:"mat3x3",mat3x4i:"mat3x4",mat4x2i:"mat4x2",mat4x3i:"mat4x3",mat4x4i:"mat4x4",mat2x2u:"mat2x2",mat2x3u:"mat2x3",mat2x4u:"mat2x4",mat3x2u:"mat3x2",mat3x3u:"mat3x3",mat3x4u:"mat3x4",mat4x2u:"mat4x2",mat4x3u:"mat4x3",mat4x4u:"mat4x4",mat2x2h:"mat2x2",mat2x3h:"mat2x3",mat2x4h:"mat2x4",mat3x2h:"mat3x2",mat3x3h:"mat3x3",mat3x4h:"mat3x4",mat4x2h:"mat4x2",mat4x3h:"mat4x3",mat4x4h:"mat4x4"}});function wF(r,e){let t={};for(let i of r.attributes){let n=RRe(r,e,i.name);n&&(t[i.name]=n)}return t}function mie(r,e,t=16){let i=wF(r,e),n=new Array(t).fill(null);for(let o of Object.values(i))n[o.location]=o;return n}function RRe(r,e,t){let i=BRe(r,t),n=LRe(e,t);if(!i)return null;let o=QG.getAttributeShaderTypeInfo(i.type),s=ay.getCompatibleVertexFormat(o),c=n?.vertexFormat||s,f=ay.getVertexFormatInfo(c);return{attributeName:n?.attributeName||i.name,bufferName:n?.bufferName||i.name,location:i.location,shaderType:i.type,primitiveType:o.primitiveType,shaderComponents:o.components,vertexFormat:c,bufferDataType:f.type,bufferComponents:f.components,normalized:f.normalized,integer:o.integer,stepMode:n?.stepMode||i.stepMode||"vertex",byteOffset:n?.byteOffset||0,byteStride:n?.byteStride||0}}function BRe(r,e){let t=r.attributes.find(i=>i.name===e);return t||Lr.warn(`shader layout attribute "${e}" not present in shader`),t||null}function LRe(r,e){DRe(r);let t=ORe(r,e);return t||(t=FRe(r,e),t)?t:(Lr.warn(`layout for attribute "${e}" not present in buffer layout`),null)}function DRe(r){for(let e of r)(e.attributes&&e.format||!e.attributes&&!e.format)&&Lr.warn(`BufferLayout ${name} must have either 'attributes' or 'format' field`)}function ORe(r,e){for(let t of r)if(t.format&&t.name===e)return{attributeName:t.name,bufferName:e,stepMode:t.stepMode,vertexFormat:t.format,byteOffset:0,byteStride:t.byteStride||0};return null}function FRe(r,e){for(let t of r){let i=t.byteStride;if(typeof t.byteStride!="number")for(let o of t.attributes||[]){let s=ay.getVertexFormatInfo(o.format);i+=s.byteLength}let n=t.attributes?.find(o=>o.attribute===e);if(n)return{attributeName:n.attribute,bufferName:t.name,stepMode:t.stepMode,vertexFormat:n.format,byteOffset:n.byteOffset,byteStride:i}}return null}var JG=Nt(()=>{Rg();bF();uF()});var eH,FT,gie=Nt(()=>{JG();ad();eH=class extends $o{get[Symbol.toStringTag](){return"VertexArray"}maxVertexAttributes;attributeInfos;indexBuffer=null;attributes;constructor(e,t){super(e,t,eH.defaultProps),this.maxVertexAttributes=e.limits.maxVertexAttributes,this.attributes=new Array(this.maxVertexAttributes).fill(null),this.attributeInfos=mie(t.shaderLayout,t.bufferLayout,this.maxVertexAttributes)}setConstantWebGL(e,t){this.device.reportError(new Error("constant attributes not supported"),this)()}},FT=eH;rn(FT,"defaultProps",{...$o.defaultProps,shaderLayout:void 0,bufferLayout:[]})});var tH,kT,Aie=Nt(()=>{ad();tH=class extends $o{get[Symbol.toStringTag](){return"TransformFeedback"}constructor(e,t){super(e,t,tH.defaultProps)}},kT=tH;rn(kT,"defaultProps",{...$o.defaultProps,layout:void 0,buffers:{}})});var rH,NT,_ie=Nt(()=>{ad();rH=class extends $o{get[Symbol.toStringTag](){return"QuerySet"}constructor(e,t){super(e,t,rH.defaultProps)}},NT=rH;rn(NT,"defaultProps",{...$o.defaultProps,type:void 0,count:void 0})});var iH,zT,yie=Nt(()=>{ad();iH=class extends $o{get[Symbol.toStringTag](){return"Fence"}constructor(e,t={}){super(e,t,iH.defaultProps)}},zT=iH;rn(zT,"defaultProps",{...$o.defaultProps})});function ZA(r,e){switch(e){case 1:return r;case 2:return r+r%2;default:return r+(4-r%4)%4}}function NI(r){let[,,,,e]=kRe[r];return e}var kRe,nH=Nt(()=>{kRe={uint8:["uint8","u32",1,!1,Uint8Array],sint8:["sint8","i32",1,!1,Int8Array],unorm8:["uint8","f32",1,!0,Uint8Array],snorm8:["sint8","f32",1,!0,Int8Array],uint16:["uint16","u32",2,!1,Uint16Array],sint16:["sint16","i32",2,!1,Int16Array],unorm16:["uint16","u32",2,!0,Uint16Array],snorm16:["sint16","i32",2,!0,Int16Array],float16:["float16","f16",2,!1,Uint16Array],float32:["float32","f32",4,!1,Float32Array],uint32:["uint32","u32",4,!1,Uint32Array],sint32:["sint32","i32",4,!1,Int32Array]}});function xie(r,e={}){let t={...r},i=e.layout??"std140",n={},o=0;for(let[s,c]of Object.entries(t))o=oH(n,s,c,o,i);return o=ZA(o,Vv(t,i)),{layout:i,byteLength:o*4,uniformTypes:t,fields:n}}function zI(r,e){let t=xF(r),i=OT(t),n=/^mat(\d)x(\d)<.+>$/.exec(t);if(n){let s=Number(n[1]),c=Number(n[2]),f=vie(c,t,i.type,e),g=zRe(f.size,f.alignment,e);return{alignment:f.alignment,size:s*g,components:s*c,columns:s,rows:c,columnStride:g,shaderType:t,type:i.type}}let o=/^vec(\d)<.+>$/.exec(t);return o?vie(Number(o[1]),t,i.type,e):{alignment:1,size:1,components:1,columns:1,rows:1,columnStride:1,shaderType:t,type:i.type}}function sH(r){return Boolean(r)&&typeof r=="object"&&!Array.isArray(r)}function oH(r,e,t,i,n){if(typeof t=="string"){let o=zI(t,n),s=ZA(i,o.alignment);return r[e]={offset:s,...o},s+o.size}if(Array.isArray(t)){if(Array.isArray(t[0]))throw new Error(`Nested arrays are not supported for ${e}`);let o=t[0],s=t[1],c=wie(o,n),f=ZA(i,Vv(t,n));for(let g=0;g{nH();bF()});function lH(r){return(!TF||TF.byteLength{});function VRe(r){return ArrayBuffer.isView(r)&&!(r instanceof DataView)}function UT(r){return Array.isArray(r)?r.length===0||typeof r[0]=="number":VRe(r)}var hH=Nt(()=>{});function jRe(r){return Boolean(r)&&typeof r=="object"&&!Array.isArray(r)&&!ArrayBuffer.isView(r)}function GRe(r,e,t){return Array.prototype.slice.call(r,e,t)}var SF,Sie=Nt(()=>{uH();hH();Rg();aH();SF=class{layout;constructor(e){this.layout=e}has(e){return Boolean(this.layout.fields[e])}get(e){let t=this.layout.fields[e];return t?{offset:t.offset,size:t.size}:void 0}getFlatUniformValues(e){let t={};for(let[i,n]of Object.entries(e)){let o=this.layout.uniformTypes[i];o?this._flattenCompositeValue(t,i,o,n):this.layout.fields[i]&&(t[i]=n)}return t}getData(e){let t=lH(this.layout.byteLength);new Uint8Array(t,0,this.layout.byteLength).fill(0);let i={i32:new Int32Array(t),u32:new Uint32Array(t),f32:new Float32Array(t),f16:new Uint16Array(t)},n=this.getFlatUniformValues(e);for(let[o,s]of Object.entries(n))this._writeLeafValue(i,o,s);return new Uint8Array(t,0,this.layout.byteLength)}_flattenCompositeValue(e,t,i,n){if(n!==void 0){if(typeof i=="string"||this.layout.fields[t]){e[t]=n;return}if(Array.isArray(i)){let o=i[0],s=i[1];if(Array.isArray(o))throw new Error(`Nested arrays are not supported for ${t}`);if(typeof o=="string"&&UT(n)){this._flattenPackedArray(e,t,o,s,n);return}if(!Array.isArray(n)){Lr.warn(`Unsupported uniform array value for ${t}:`,n)();return}for(let c=0;c=s.length)break;f===1?e[`${t}[${g}]`]=Number(s[y]):e[`${t}[${g}]`]=GRe(o,y,y+f)}}_writeLeafValue(e,t,i){let n=this.layout.fields[t];if(!n){Lr.warn(`Uniform ${t} not found in layout`)();return}let{type:o,components:s,columns:c,rows:f,offset:g,columnStride:y}=n,M=e[o];if(s===1){M[g]=Number(i);return}let F=i;if(c===1){for(let U=0;Uo)return!1;for(let s=0;s{hH();HRe=128});var EF,Iie=Nt(()=>{Mie();EF=class{name;uniforms={};modifiedUniforms={};modified=!0;bindingLayout={};needsRedraw="initialized";constructor(e){if(this.name=e?.name||"unnamed",e?.name&&e?.shaderLayout){let t=e?.shaderLayout.bindings?.find(n=>n.type==="uniform"&&n.name===e?.name);if(!t)throw new Error(e?.name);let i=t;for(let n of i.uniforms||[])this.bindingLayout[n.name]=n}}setUniforms(e){for(let[t,i]of Object.entries(e))this._setUniform(t,i),this.needsRedraw||this.setNeedsRedraw(`${this.name}.${t}=${i}`)}setNeedsRedraw(e){this.needsRedraw=this.needsRedraw||e}getAllUniforms(){return this.modifiedUniforms={},this.needsRedraw=!1,this.uniforms||{}}_setUniform(e,t){Eie(this.uniforms[e],t)||(this.uniforms[e]=Cie(t),this.modifiedUniforms[e]=!0,this.modified=!0)}}});function qRe(r){return r.type==="webgpu"?"wgsl-uniform":"std140"}var WRe,p1,Pie=Nt(()=>{lF();Rg();aH();Iie();Sie();WRe=1024,p1=class{device;uniformBlocks=new Map;shaderBlockLayouts=new Map;shaderBlockWriters=new Map;uniformBuffers=new Map;constructor(e,t){this.device=e;for(let[i,n]of Object.entries(t)){let o=i,s=xie(n.uniformTypes??{},{layout:n.layout??qRe(e)}),c=new SF(s);this.shaderBlockLayouts.set(o,s),this.shaderBlockWriters.set(o,c);let f=new EF({name:i});f.setUniforms(c.getFlatUniformValues(n.defaultUniforms||{})),this.uniformBlocks.set(o,f)}}destroy(){for(let e of this.uniformBuffers.values())e.destroy()}setUniforms(e){for(let[t,i]of Object.entries(e)){let n=t,s=this.shaderBlockWriters.get(n)?.getFlatUniformValues(i||{});this.uniformBlocks.get(n)?.setUniforms(s||{})}this.updateUniformBuffers()}getUniformBufferByteLength(e){let t=this.shaderBlockLayouts.get(e)?.byteLength||0;return Math.max(t,WRe)}getUniformBufferData(e){let t=this.uniformBlocks.get(e)?.getAllUniforms()||{};return this.shaderBlockWriters.get(e)?.getData(t)||new Uint8Array(0)}createUniformBuffer(e,t){t&&this.setUniforms(t);let i=this.getUniformBufferByteLength(e),n=this.device.createBuffer({usage:lo.UNIFORM|lo.COPY_DST,byteLength:i}),o=this.getUniformBufferData(e);return n.write(o),n}getManagedUniformBuffer(e){if(!this.uniformBuffers.get(e)){let t=this.getUniformBufferByteLength(e),i=this.device.createBuffer({usage:lo.UNIFORM|lo.COPY_DST,byteLength:t});this.uniformBuffers.set(e,i)}return this.uniformBuffers.get(e)}updateUniformBuffers(){let e=!1;for(let t of this.uniformBlocks.keys()){let i=this.updateUniformBuffer(t);e||=i}return e&&Lr.log(3,`UniformStore.updateUniformBuffers(): ${e}`)(),e}updateUniformBuffer(e){let t=this.uniformBlocks.get(e),i=this.uniformBuffers.get(e),n=!1;if(i&&t?.needsRedraw){n||=t.needsRedraw;let o=this.getUniformBufferData(e);i=this.uniformBuffers.get(e),i?.write(o);let s=this.uniformBlocks.get(e)?.getAllUniforms();Lr.log(4,`Writing to uniform buffer ${String(e)}`,o,s)()}return n}}});var Eo=Nt(()=>{Hre();Wre();LG();Kre();Qre();lF();NG();Jre();jG();kG();iie();WG();nie();sie();aie();uie();hie();fie();gie();Aie();_ie();yie();Pie();cF();nH();bF();uF();pF();PG();Rg();cie();OG();uH();JG()});function loe(r=!0){let e=HTMLCanvasElement.prototype;if(!r&&e.originalGetContext){e.getContext=e.originalGetContext,e.originalGetContext=void 0;return}e.originalGetContext=e.getContext,e.getContext=function(t,i){if(t==="webgl"||t==="experimental-webgl"){let n=this.originalGetContext("webgl2",i);return n instanceof HTMLElement&&xOe(n),n}return this.originalGetContext(t,i)}}function xOe(r){r.getExtension("EXT_color_buffer_float");let e={...AOe,WEBGL_disjoint_timer_query:r.getExtension("EXT_disjoint_timer_query_webgl2"),WEBGL_draw_buffers:_Oe(r),OES_vertex_array_object:yOe(r),ANGLE_instanced_arrays:vOe(r)},t=r.getExtension;r.getExtension=function(n){let o=t.call(r,n);return o||(n in e?e[n]:null)};let i=r.getSupportedExtensions;r.getSupportedExtensions=function(){return(i.apply(r)||[])?.concat(Object.keys(e))}}var AOe,_Oe,yOe,vOe,coe=Nt(()=>{AOe={WEBGL_depth_texture:{UNSIGNED_INT_24_8_WEBGL:34042},OES_element_index_uint:{},OES_texture_float:{},OES_texture_half_float:{HALF_FLOAT_OES:5131},EXT_color_buffer_float:{},OES_standard_derivatives:{FRAGMENT_SHADER_DERIVATIVE_HINT_OES:35723},EXT_frag_depth:{},EXT_blend_minmax:{MIN_EXT:32775,MAX_EXT:32776},EXT_shader_texture_lod:{}},_Oe=r=>({drawBuffersWEBGL(e){return r.drawBuffers(e)},COLOR_ATTACHMENT0_WEBGL:36064,COLOR_ATTACHMENT1_WEBGL:36065,COLOR_ATTACHMENT2_WEBGL:36066,COLOR_ATTACHMENT3_WEBGL:36067}),yOe=r=>({VERTEX_ARRAY_BINDING_OES:34229,createVertexArrayOES(){return r.createVertexArray()},deleteVertexArrayOES(e){return r.deleteVertexArray(e)},isVertexArrayOES(e){return r.isVertexArray(e)},bindVertexArrayOES(e){return r.bindVertexArray(e)}}),vOe=r=>({VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE:35070,drawArraysInstancedANGLE(...e){return r.drawArraysInstanced(...e)},drawElementsInstancedANGLE(...e){return r.drawElementsInstanced(...e)},vertexAttribDivisorANGLE(...e){return r.vertexAttribDivisor(...e)}})});async function yk(r,e){let t=document.getElementsByTagName("head")[0];if(!t)throw new Error("loadScript");let i=document.createElement("script");return i.setAttribute("type","text/javascript"),i.setAttribute("src",r),e&&(i.id=e),new Promise((n,o)=>{i.onload=n,i.onerror=s=>o(new Error(`Unable to load script '${r}': ${s}`)),t.appendChild(i)})}var r9=Nt(()=>{});function I1(r){let e=r.luma||{_polyfilled:!1,extensions:{},softwareRenderer:!1};return e._polyfilled??=!1,e.extensions||={},r.luma=e,e}var vk=Nt(()=>{});async function hoe(r){if(!globalThis.SPECTOR)try{await yk(r.debugSpectorJSUrl||xk.debugSpectorJSUrl)}catch(e){Lr.warn(String(e))}}function foe(r){if(r={...xk,...r},!r.debugSpectorJS)return null;if(!zm&&globalThis.SPECTOR&&!globalThis.luma?.spector){Lr.probe(bOe,"SPECTOR found and initialized. Start with `luma.spector.displayUI()`")();let{Spector:e}=globalThis.SPECTOR;zm=new e,globalThis.luma&&(globalThis.luma.spector=zm)}if(!zm)return null;if(uoe||(uoe=!0,zm.spyCanvases(),zm?.onCaptureStarted.add(e=>Lr.info("Spector capture started:",e)()),zm?.onCapture.add(e=>{Lr.info("Spector capture complete:",e)(),zm?.getResultUI(),zm?.resultView.display(),zm?.resultView.addCapture(e)})),r.gl){let e=r.gl,t=I1(e),i=t.device;zm?.startCapture(r.gl,500),t.device=i,new Promise(n=>setTimeout(n,2e3)).then(n=>{Lr.info("Spector capture stopped after 2 seconds")(),zm?.stopCapture()})}return zm}var bOe,zm,uoe,xk,i9=Nt(()=>{Eo();r9();vk();bOe=1,zm=null,uoe=!1,xk={debugSpectorJS:Lr.get("debug-spectorjs"),debugSpectorJSUrl:"https://cdn.jsdelivr.net/npm/spectorjs@0.9.30/dist/spector.bundle.js",gl:void 0}});function poe(r){return r.luma=r.luma||{},r.luma}async function moe(){rp()&&!globalThis.WebGLDebugUtils&&(globalThis.global=globalThis.global||globalThis,globalThis.global.module={},await yk(wOe))}function goe(r,e={}){return e.debugWebGL||e.traceWebGL?SOe(r,e):TOe(r)}function TOe(r){let e=poe(r);return e.realContext?e.realContext:r}function SOe(r,e){if(!globalThis.WebGLDebugUtils)return Lr.warn("webgl-debug not loaded")(),r;let t=poe(r);if(t.debugContext)return t.debugContext;globalThis.WebGLDebugUtils.init({...jA,...r});let i=globalThis.WebGLDebugUtils.makeDebugContext(r,EOe.bind(null,e),COe.bind(null,e));for(let s in jA)!(s in i)&&typeof jA[s]=="number"&&(i[s]=jA[s]);class n{}Object.setPrototypeOf(i,Object.getPrototypeOf(r)),Object.setPrototypeOf(n,i);let o=Object.create(n);return t.realContext=r,t.debugContext=o,o.luma=t,o.debug=!0,o}function doe(r,e){e=Array.from(e).map(i=>i===void 0?"undefined":i);let t=globalThis.WebGLDebugUtils.glFunctionArgsToString(r,e);return t=`${t.slice(0,100)}${t.length>100?"...":""}`,`gl.${r}(${t})`}function EOe(r,e,t,i){i=Array.from(i).map(c=>c===void 0?"undefined":c);let n=globalThis.WebGLDebugUtils.glEnumToString(e),o=globalThis.WebGLDebugUtils.glFunctionArgsToString(t,i),s=`${n} in gl.${t}(${o})`;Lr.error("%cWebGL","color: white; background: red; padding: 2px 6px; border-radius: 3px;",s)();debugger;throw new Error(s)}function COe(r,e,t){let i="";r.traceWebGL&&Lr.level>=1&&(i=doe(e,t),Lr.info(1,"%cWebGL","color: white; background: blue; padding: 2px 6px; border-radius: 3px;",i)());for(let n of t)if(n===void 0){i=i||doe(e,t);debugger}}var wOe,n9=Nt(()=>{Eo();QV();_v();r9();wOe="https://unpkg.com/webgl-debug@2.0.1/index.js"});function o9(r){return Array.isArray(r)||ArrayBuffer.isView(r)&&!(r instanceof DataView)}function Wu(r,e,t){return e[r]!==void 0?e[r]:t[r]}var bP,Nh,Aoe,Um,_oe,xP,yoe,voe,s9,i_,a9,xoe,l9=Nt(()=>{bP={[3042]:!1,[32773]:new Float32Array([0,0,0,0]),[32777]:32774,[34877]:32774,[32969]:1,[32968]:0,[32971]:1,[32970]:0,[3106]:new Float32Array([0,0,0,0]),[3107]:[!0,!0,!0,!0],[2884]:!1,[2885]:1029,[2929]:!1,[2931]:1,[2932]:513,[2928]:new Float32Array([0,1]),[2930]:!0,[3024]:!0,[35725]:null,[36006]:null,[36007]:null,[34229]:null,[34964]:null,[2886]:2305,[33170]:4352,[2849]:1,[32823]:!1,[32824]:0,[10752]:0,[32926]:!1,[32928]:!1,[32938]:1,[32939]:!1,[3089]:!1,[3088]:new Int32Array([0,0,1024,1024]),[2960]:!1,[2961]:0,[2968]:4294967295,[36005]:4294967295,[2962]:519,[2967]:0,[2963]:4294967295,[34816]:519,[36003]:0,[36004]:4294967295,[2964]:7680,[2965]:7680,[2966]:7680,[34817]:7680,[34818]:7680,[34819]:7680,[2978]:[0,0,1024,1024],[36389]:null,[36662]:null,[36663]:null,[35053]:null,[35055]:null,[35723]:4352,[36010]:null,[35977]:!1,[3333]:4,[3317]:4,[37440]:!1,[37441]:!1,[37443]:37444,[3330]:0,[3332]:0,[3331]:0,[3314]:0,[32878]:0,[3316]:0,[3315]:0,[32877]:0},Nh=(r,e,t)=>e?r.enable(t):r.disable(t),Aoe=(r,e,t)=>r.hint(t,e),Um=(r,e,t)=>r.pixelStorei(t,e),_oe=(r,e,t)=>{let i=t===36006?36009:36008;return r.bindFramebuffer(i,e)},xP=(r,e,t)=>{let n={[34964]:34962,[36662]:36662,[36663]:36663,[35053]:35051,[35055]:35052}[t];r.bindBuffer(n,e)};yoe={[3042]:Nh,[32773]:(r,e)=>r.blendColor(...e),[32777]:"blendEquation",[34877]:"blendEquation",[32969]:"blendFunc",[32968]:"blendFunc",[32971]:"blendFunc",[32970]:"blendFunc",[3106]:(r,e)=>r.clearColor(...e),[3107]:(r,e)=>r.colorMask(...e),[2884]:Nh,[2885]:(r,e)=>r.cullFace(e),[2929]:Nh,[2931]:(r,e)=>r.clearDepth(e),[2932]:(r,e)=>r.depthFunc(e),[2928]:(r,e)=>r.depthRange(...e),[2930]:(r,e)=>r.depthMask(e),[3024]:Nh,[35723]:Aoe,[35725]:(r,e)=>r.useProgram(e),[36007]:(r,e)=>r.bindRenderbuffer(36161,e),[36389]:(r,e)=>r.bindTransformFeedback?.(36386,e),[34229]:(r,e)=>r.bindVertexArray(e),[36006]:_oe,[36010]:_oe,[34964]:xP,[36662]:xP,[36663]:xP,[35053]:xP,[35055]:xP,[2886]:(r,e)=>r.frontFace(e),[33170]:Aoe,[2849]:(r,e)=>r.lineWidth(e),[32823]:Nh,[32824]:"polygonOffset",[10752]:"polygonOffset",[35977]:Nh,[32926]:Nh,[32928]:Nh,[32938]:"sampleCoverage",[32939]:"sampleCoverage",[3089]:Nh,[3088]:(r,e)=>r.scissor(...e),[2960]:Nh,[2961]:(r,e)=>r.clearStencil(e),[2968]:(r,e)=>r.stencilMaskSeparate(1028,e),[36005]:(r,e)=>r.stencilMaskSeparate(1029,e),[2962]:"stencilFuncFront",[2967]:"stencilFuncFront",[2963]:"stencilFuncFront",[34816]:"stencilFuncBack",[36003]:"stencilFuncBack",[36004]:"stencilFuncBack",[2964]:"stencilOpFront",[2965]:"stencilOpFront",[2966]:"stencilOpFront",[34817]:"stencilOpBack",[34818]:"stencilOpBack",[34819]:"stencilOpBack",[2978]:(r,e)=>r.viewport(...e),[34383]:Nh,[10754]:Nh,[12288]:Nh,[12289]:Nh,[12290]:Nh,[12291]:Nh,[12292]:Nh,[12293]:Nh,[12294]:Nh,[12295]:Nh,[3333]:Um,[3317]:Um,[37440]:Um,[37441]:Um,[37443]:Um,[3330]:Um,[3332]:Um,[3331]:Um,[3314]:Um,[32878]:Um,[3316]:Um,[3315]:Um,[32877]:Um,framebuffer:(r,e)=>{let t=e&&"handle"in e?e.handle:e;return r.bindFramebuffer(36160,t)},blend:(r,e)=>e?r.enable(3042):r.disable(3042),blendColor:(r,e)=>r.blendColor(...e),blendEquation:(r,e)=>{let t=typeof e=="number"?[e,e]:e;r.blendEquationSeparate(...t)},blendFunc:(r,e)=>{let t=e?.length===2?[...e,...e]:e;r.blendFuncSeparate(...t)},clearColor:(r,e)=>r.clearColor(...e),clearDepth:(r,e)=>r.clearDepth(e),clearStencil:(r,e)=>r.clearStencil(e),colorMask:(r,e)=>r.colorMask(...e),cull:(r,e)=>e?r.enable(2884):r.disable(2884),cullFace:(r,e)=>r.cullFace(e),depthTest:(r,e)=>e?r.enable(2929):r.disable(2929),depthFunc:(r,e)=>r.depthFunc(e),depthMask:(r,e)=>r.depthMask(e),depthRange:(r,e)=>r.depthRange(...e),dither:(r,e)=>e?r.enable(3024):r.disable(3024),derivativeHint:(r,e)=>{r.hint(35723,e)},frontFace:(r,e)=>r.frontFace(e),mipmapHint:(r,e)=>r.hint(33170,e),lineWidth:(r,e)=>r.lineWidth(e),polygonOffsetFill:(r,e)=>e?r.enable(32823):r.disable(32823),polygonOffset:(r,e)=>r.polygonOffset(...e),sampleCoverage:(r,e)=>r.sampleCoverage(e[0],e[1]||!1),scissorTest:(r,e)=>e?r.enable(3089):r.disable(3089),scissor:(r,e)=>r.scissor(...e),stencilTest:(r,e)=>e?r.enable(2960):r.disable(2960),stencilMask:(r,e)=>{e=o9(e)?e:[e,e];let[t,i]=e;r.stencilMaskSeparate(1028,t),r.stencilMaskSeparate(1029,i)},stencilFunc:(r,e)=>{e=o9(e)&&e.length===3?[...e,...e]:e;let[t,i,n,o,s,c]=e;r.stencilFuncSeparate(1028,t,i,n),r.stencilFuncSeparate(1029,o,s,c)},stencilOp:(r,e)=>{e=o9(e)&&e.length===3?[...e,...e]:e;let[t,i,n,o,s,c]=e;r.stencilOpSeparate(1028,t,i,n),r.stencilOpSeparate(1029,o,s,c)},viewport:(r,e)=>r.viewport(...e)};voe={blendEquation:(r,e,t)=>r.blendEquationSeparate(Wu(32777,e,t),Wu(34877,e,t)),blendFunc:(r,e,t)=>r.blendFuncSeparate(Wu(32969,e,t),Wu(32968,e,t),Wu(32971,e,t),Wu(32970,e,t)),polygonOffset:(r,e,t)=>r.polygonOffset(Wu(32824,e,t),Wu(10752,e,t)),sampleCoverage:(r,e,t)=>r.sampleCoverage(Wu(32938,e,t),Wu(32939,e,t)),stencilFuncFront:(r,e,t)=>r.stencilFuncSeparate(1028,Wu(2962,e,t),Wu(2967,e,t),Wu(2963,e,t)),stencilFuncBack:(r,e,t)=>r.stencilFuncSeparate(1029,Wu(34816,e,t),Wu(36003,e,t),Wu(36004,e,t)),stencilOpFront:(r,e,t)=>r.stencilOpSeparate(1028,Wu(2964,e,t),Wu(2965,e,t),Wu(2966,e,t)),stencilOpBack:(r,e,t)=>r.stencilOpSeparate(1029,Wu(34817,e,t),Wu(34818,e,t),Wu(34819,e,t))},s9={enable:(r,e)=>r({[e]:!0}),disable:(r,e)=>r({[e]:!1}),pixelStorei:(r,e,t)=>r({[e]:t}),hint:(r,e,t)=>r({[e]:t}),useProgram:(r,e)=>r({[35725]:e}),bindRenderbuffer:(r,e,t)=>r({[36007]:t}),bindTransformFeedback:(r,e,t)=>r({[36389]:t}),bindVertexArray:(r,e)=>r({[34229]:e}),bindFramebuffer:(r,e,t)=>{switch(e){case 36160:return r({[36006]:t,[36010]:t});case 36009:return r({[36006]:t});case 36008:return r({[36010]:t});default:return null}},bindBuffer:(r,e,t)=>{let i={[34962]:[34964],[36662]:[36662],[36663]:[36663],[35051]:[35053],[35052]:[35055]}[e];return i?r({[i]:t}):{valueChanged:!0}},blendColor:(r,e,t,i,n)=>r({[32773]:new Float32Array([e,t,i,n])}),blendEquation:(r,e)=>r({[32777]:e,[34877]:e}),blendEquationSeparate:(r,e,t)=>r({[32777]:e,[34877]:t}),blendFunc:(r,e,t)=>r({[32969]:e,[32968]:t,[32971]:e,[32970]:t}),blendFuncSeparate:(r,e,t,i,n)=>r({[32969]:e,[32968]:t,[32971]:i,[32970]:n}),clearColor:(r,e,t,i,n)=>r({[3106]:new Float32Array([e,t,i,n])}),clearDepth:(r,e)=>r({[2931]:e}),clearStencil:(r,e)=>r({[2961]:e}),colorMask:(r,e,t,i,n)=>r({[3107]:[e,t,i,n]}),cullFace:(r,e)=>r({[2885]:e}),depthFunc:(r,e)=>r({[2932]:e}),depthRange:(r,e,t)=>r({[2928]:new Float32Array([e,t])}),depthMask:(r,e)=>r({[2930]:e}),frontFace:(r,e)=>r({[2886]:e}),lineWidth:(r,e)=>r({[2849]:e}),polygonOffset:(r,e,t)=>r({[32824]:e,[10752]:t}),sampleCoverage:(r,e,t)=>r({[32938]:e,[32939]:t}),scissor:(r,e,t,i,n)=>r({[3088]:new Int32Array([e,t,i,n])}),stencilMask:(r,e)=>r({[2968]:e,[36005]:e}),stencilMaskSeparate:(r,e,t)=>r({[e===1028?2968:36005]:t}),stencilFunc:(r,e,t,i)=>r({[2962]:e,[2967]:t,[2963]:i,[34816]:e,[36003]:t,[36004]:i}),stencilFuncSeparate:(r,e,t,i,n)=>r({[e===1028?2962:34816]:t,[e===1028?2967:36003]:i,[e===1028?2963:36004]:n}),stencilOp:(r,e,t,i)=>r({[2964]:e,[2965]:t,[2966]:i,[34817]:e,[34818]:t,[34819]:i}),stencilOpSeparate:(r,e,t,i,n)=>r({[e===1028?2964:34817]:t,[e===1028?2965:34818]:i,[e===1028?2966:34819]:n}),viewport:(r,e,t,i,n)=>r({[2978]:[e,t,i,n]})},i_=(r,e)=>r.isEnabled(e),a9={[3042]:i_,[2884]:i_,[2929]:i_,[3024]:i_,[32823]:i_,[32926]:i_,[32928]:i_,[3089]:i_,[2960]:i_,[35977]:i_},xoe=new Set([34016,36388,36387,35983,35368,34965,35739,35738,3074,34853,34854,34855,34856,34857,34858,34859,34860,34861,34862,34863,34864,34865,34866,34867,34868,35097,32873,35869,32874,34068])});function B0(r,e){if(MOe(e))return;let t={};for(let n in e){let o=Number(n),s=yoe[n];s&&(typeof s=="string"?t[s]=!0:s(r,e[n],o))}let i=r.lumaState?.cache;if(i)for(let n in t){let o=voe[n];o(r,e,i)}}function bk(r,e=bP){if(typeof e=="number"){let n=e,o=a9[n];return o?o(r,n):r.getParameter(n)}let t=Array.isArray(e)?e:Object.keys(e),i={};for(let n of t){let o=a9[n];i[n]=o?o(r,Number(n)):r.getParameter(Number(n))}return i}function boe(r){B0(r,bP)}function MOe(r){for(let e in r)return!1;return!0}var nS=Nt(()=>{l9()});function Toe(r,e){if(r===e)return!0;if(woe(r)&&woe(e)&&r.length===e.length){for(let t=0;t{});function Eoe(r,e){let t=r[e].bind(r);r[e]=function(n){if(n===void 0||xoe.has(n))return t(n);let o=L0.get(r);return n in o.cache||(o.cache[n]=t(n)),o.enable?o.cache[n]:t(n)},Object.defineProperty(r[e],"name",{value:`${e}-from-cache`,configurable:!1})}function IOe(r,e,t){if(!r[e])return;let i=r[e].bind(r);r[e]=function(...o){let s=L0.get(r),{valueChanged:c,oldValue:f}=t(s._updateCache,...o);return c&&i(...o),f},Object.defineProperty(r[e],"name",{value:`${e}-to-cache`,configurable:!1})}function POe(r){let e=r.useProgram.bind(r);r.useProgram=function(i){let n=L0.get(r);n.program!==i&&(e(i),n.program=i)}}var L0,c9=Nt(()=>{nS();Soe();l9();L0=class{static get(e){return e.lumaState}gl;program=null;stateStack=[];enable=!0;cache=null;log;initialized=!1;constructor(e,t){this.gl=e,this.log=t?.log||(()=>{}),this._updateCache=this._updateCache.bind(this),Object.seal(this)}push(e={}){this.stateStack.push({})}pop(){let e=this.stateStack[this.stateStack.length-1];B0(this.gl,e),this.stateStack.pop()}trackState(e,t){if(this.cache=t?.copyState?bk(e):Object.assign({},bP),this.initialized)throw new Error("WebGLStateTracker");this.initialized=!0,this.gl.lumaState=this,POe(e);for(let i in s9){let n=s9[i];IOe(e,i,n)}Eoe(e,"getParameter"),Eoe(e,"isEnabled")}_updateCache(e){let t=!1,i,n=this.stateStack.length>0?this.stateStack[this.stateStack.length-1]:null;for(let o in e){let s=e[o],c=this.cache[o];Toe(s,c)||(t=!0,i=c,n&&!(o in n)&&(n[o]=c),this.cache[o]=s)}return{valueChanged:t,oldValue:i}}}});function Coe(r,e,t){let i="",n=f=>{let g=f.statusMessage;g&&(i||=g)};r.addEventListener("webglcontextcreationerror",n,!1);let o=t.failIfMajorPerformanceCaveat!==!0,s={preserveDrawingBuffer:!0,...t,failIfMajorPerformanceCaveat:!0},c=null;try{c||=r.getContext("webgl2",s),!c&&s.failIfMajorPerformanceCaveat&&(i||="Only software GPU is available. Set `failIfMajorPerformanceCaveat: false` to allow.");let f=!1;if(!c&&o&&(s.failIfMajorPerformanceCaveat=!1,c=r.getContext("webgl2",s),f=!0),c||(c=r.getContext("webgl",{}),c&&(c=null,i||="Your browser only supports WebGL1")),!c)throw i||="Your browser does not support WebGL",new Error(`Failed to create WebGL context: ${i}`);let g=I1(c);g.softwareRenderer=f;let{onContextLost:y,onContextRestored:M}=e;return r.addEventListener("webglcontextlost",F=>y(F),!1),r.addEventListener("webglcontextrestored",F=>M(F),!1),c}finally{r.removeEventListener("webglcontextcreationerror",n,!1)}}var Moe=Nt(()=>{vk()});function D0(r,e,t){return t[e]===void 0&&(t[e]=r.getExtension(e)||null),t[e]}var wP=Nt(()=>{});function Ioe(r,e){let t=r.getParameter(7936),i=r.getParameter(7937);D0(r,"WEBGL_debug_renderer_info",e);let n=e.WEBGL_debug_renderer_info,o=r.getParameter(n?n.UNMASKED_VENDOR_WEBGL:7936),s=r.getParameter(n?n.UNMASKED_RENDERER_WEBGL:7937),c=o||t,f=s||i,g=r.getParameter(7938),y=Poe(c,f),M=ROe(c,f),F=BOe(c,f);return{type:"webgl",gpu:y,gpuType:F,gpuBackend:M,vendor:c,renderer:f,version:g,shadingLanguage:"glsl",shadingLanguageVersion:300}}function Poe(r,e){return/NVIDIA/i.exec(r)||/NVIDIA/i.exec(e)?"nvidia":/INTEL/i.exec(r)||/INTEL/i.exec(e)?"intel":/Apple/i.exec(r)||/Apple/i.exec(e)?"apple":/AMD/i.exec(r)||/AMD/i.exec(e)||/ATI/i.exec(r)||/ATI/i.exec(e)?"amd":/SwiftShader/i.exec(r)||/SwiftShader/i.exec(e)?"software":"unknown"}function ROe(r,e){return/Metal/i.exec(r)||/Metal/i.exec(e)?"metal":/ANGLE/i.exec(r)||/ANGLE/i.exec(e)?"opengl":"unknown"}function BOe(r,e){if(/SwiftShader/i.exec(r)||/SwiftShader/i.exec(e))return"cpu";switch(Poe(r,e)){case"apple":return LOe(r,e)?"integrated":"unknown";case"intel":return"integrated";case"software":return"cpu";case"unknown":return"unknown";default:return"discrete"}}function LOe(r,e){return/Apple (M\d|A\d|GPU)/i.test(`${r} ${e}`)}var Roe=Nt(()=>{wP()});function wk(r){switch(r){case"uint8":return 5121;case"sint8":return 5120;case"unorm8":return 5121;case"snorm8":return 5120;case"uint16":return 5123;case"sint16":return 5122;case"unorm16":return 5123;case"snorm16":return 5122;case"uint32":return 5125;case"sint32":return 5124;case"float16":return 5131;case"float32":return 5126}throw new Error(String(r))}var u9=Nt(()=>{});function Doe(r){return r in Sk}function m9(r,e,t){return Ooe(r,e,t,new Set)}function Ooe(r,e,t,i){let n=Sk[e];if(!n||i.has(e))return!1;i.add(e);let o=(n.features||[]).every(s=>Ooe(r,s,t,i));return i.delete(e),o?(n.extensions||[]).every(s=>Boolean(D0(r,s,t))):!1}function Foe(r,e,t){let i=e.create,n=Ek[e.format];n?.gl===void 0&&(i=!1),n?.x&&(i=i&&Boolean(D0(r,n.x,t))),e.format==="stencil8"&&(i=!1);let o=n?.r===!1?!1:n?.r===void 0||m9(r,n.r,t),s=i&&e.render&&o&&VOe(r,e.format,t);return{format:e.format,create:i&&e.create,render:s,filter:i&&e.filter,blend:i&&e.blend,store:i&&e.store}}function VOe(r,e,t){let i=Ek[e],n=i?.gl;if(n===void 0||i?.x&&!D0(r,i.x,t))return!1;let o=r.getParameter(32873),s=r.getParameter(36006),c=r.createTexture(),f=r.createFramebuffer();if(!c||!f)return!1;let g=Number(0),y=Number(r.getError());for(;y!==g;)y=r.getError();let M=!1;try{if(r.bindTexture(3553,c),r.texStorage2D(3553,1,n,1,1),Number(r.getError())!==g)return!1;r.bindFramebuffer(36160,f),r.framebufferTexture2D(36160,36064,3553,c,0),M=Number(r.checkFramebufferStatus(36160))===Number(36053)&&Number(r.getError())===g}finally{r.bindFramebuffer(36160,s),r.deleteFramebuffer(f),r.bindTexture(3553,o),r.deleteTexture(c)}return M}function Ck(r){let e=Ek[r],t=GOe(r),i=ld.getInfo(r);return i.compressed&&(e.dataFormat=t),{internalFormat:t,format:e?.dataFormat||jOe(i.channels,i.integer,i.normalized,t),type:i.dataType?wk(i.dataType):e?.types?.[0]||5121,compressed:i.compressed||!1}}function koe(r){switch(ld.getInfo(r).attachment){case"depth":return 36096;case"stencil":return 36128;case"depth-stencil":return 33306;default:throw new Error(`Not a depth stencil format: ${r}`)}}function jOe(r,e,t,i){if(i===6408||i===6407)return i;switch(r){case"r":return e&&!t?36244:6403;case"rg":return e&&!t?33320:33319;case"rgb":return e&&!t?36248:6407;case"rgba":return e&&!t?36249:6408;case"bgra":throw new Error("bgra pixels not supported by WebGL");default:return 6408}}function GOe(r){let t=Ek[r]?.gl;if(t===void 0)throw new Error(`Unsupported texture format ${r}`);return t}var TP,SP,oS,sS,DOe,OOe,FOe,kOe,NOe,zOe,Boe,Loe,h9,f9,d9,p9,Tk,UOe,Sk,Ek,aS=Nt(()=>{Eo();wP();u9();TP="WEBGL_compressed_texture_s3tc",SP="WEBGL_compressed_texture_s3tc_srgb",oS="EXT_texture_compression_rgtc",sS="EXT_texture_compression_bptc",DOe="WEBGL_compressed_texture_etc",OOe="WEBGL_compressed_texture_astc",FOe="WEBGL_compressed_texture_etc1",kOe="WEBGL_compressed_texture_pvrtc",NOe="WEBGL_compressed_texture_atc",zOe="EXT_texture_norm16",Boe="EXT_render_snorm",Loe="EXT_color_buffer_float",h9="snorm8-renderable-webgl",f9="norm16-renderable-webgl",d9="snorm16-renderable-webgl",p9="float16-renderable-webgl",Tk="float32-renderable-webgl",UOe="rgb9e5ufloat-renderable-webgl",Sk={"float32-renderable-webgl":{extensions:[Loe]},"float16-renderable-webgl":{extensions:["EXT_color_buffer_half_float"]},"rgb9e5ufloat-renderable-webgl":{extensions:["WEBGL_render_shared_exponent"]},"snorm8-renderable-webgl":{extensions:[Boe]},"norm16-webgl":{extensions:[zOe]},"norm16-renderable-webgl":{features:["norm16-webgl"]},"snorm16-renderable-webgl":{features:["norm16-webgl"],extensions:[Boe]},"float32-filterable":{extensions:["OES_texture_float_linear"]},"float16-filterable-webgl":{extensions:["OES_texture_half_float_linear"]},"texture-filterable-anisotropic-webgl":{extensions:["EXT_texture_filter_anisotropic"]},"texture-blend-float-webgl":{extensions:["EXT_float_blend"]},"texture-compression-bc":{extensions:[TP,SP,oS,sS]},"texture-compression-bc5-webgl":{extensions:[oS]},"texture-compression-bc7-webgl":{extensions:[sS]},"texture-compression-etc2":{extensions:[DOe]},"texture-compression-astc":{extensions:[OOe]},"texture-compression-etc1-webgl":{extensions:[FOe]},"texture-compression-pvrtc-webgl":{extensions:[kOe]},"texture-compression-atc-webgl":{extensions:[NOe]}};Ek={r8unorm:{gl:33321,rb:!0},r8snorm:{gl:36756,r:h9},r8uint:{gl:33330,rb:!0},r8sint:{gl:33329,rb:!0},rg8unorm:{gl:33323,rb:!0},rg8snorm:{gl:36757,r:h9},rg8uint:{gl:33336,rb:!0},rg8sint:{gl:33335,rb:!0},r16uint:{gl:33332,rb:!0},r16sint:{gl:33331,rb:!0},r16float:{gl:33325,rb:!0,r:p9},r16unorm:{gl:33322,rb:!0,r:f9},r16snorm:{gl:36760,r:d9},"rgba4unorm-webgl":{gl:32854,rb:!0},"rgb565unorm-webgl":{gl:36194,rb:!0},"rgb5a1unorm-webgl":{gl:32855,rb:!0},"rgb8unorm-webgl":{gl:32849},"rgb8snorm-webgl":{gl:36758},rgba8unorm:{gl:32856},"rgba8unorm-srgb":{gl:35907},rgba8snorm:{gl:36759,r:h9},rgba8uint:{gl:36220},rgba8sint:{gl:36238},bgra8unorm:{},"bgra8unorm-srgb":{},rg16uint:{gl:33338},rg16sint:{gl:33337},rg16float:{gl:33327,rb:!0,r:p9},rg16unorm:{gl:33324,r:f9},rg16snorm:{gl:36761,r:d9},r32uint:{gl:33334,rb:!0},r32sint:{gl:33333,rb:!0},r32float:{gl:33326,r:Tk},rgb9e5ufloat:{gl:35901,r:UOe},rg11b10ufloat:{gl:35898,rb:!0},rgb10a2unorm:{gl:32857,rb:!0},rgb10a2uint:{gl:36975,rb:!0},"rgb16unorm-webgl":{gl:32852,r:!1},"rgb16snorm-webgl":{gl:36762,r:!1},rg32uint:{gl:33340,rb:!0},rg32sint:{gl:33339,rb:!0},rg32float:{gl:33328,rb:!0,r:Tk},rgba16uint:{gl:36214,rb:!0},rgba16sint:{gl:36232,rb:!0},rgba16float:{gl:34842,r:p9},rgba16unorm:{gl:32859,rb:!0,r:f9},rgba16snorm:{gl:36763,r:d9},"rgb32float-webgl":{gl:34837,x:Loe,r:Tk,dataFormat:6407,types:[5126]},rgba32uint:{gl:36208,rb:!0},rgba32sint:{gl:36226,rb:!0},rgba32float:{gl:34836,rb:!0,r:Tk},stencil8:{gl:36168,rb:!0},depth16unorm:{gl:33189,dataFormat:6402,types:[5123],rb:!0},depth24plus:{gl:33190,dataFormat:6402,types:[5125]},depth32float:{gl:36012,dataFormat:6402,types:[5126],rb:!0},"depth24plus-stencil8":{gl:35056,rb:!0,depthTexture:!0,dataFormat:34041,types:[34042]},"depth32float-stencil8":{gl:36013,dataFormat:34041,types:[36269],rb:!0},"bc1-rgb-unorm-webgl":{gl:33776,x:TP},"bc1-rgb-unorm-srgb-webgl":{gl:35916,x:SP},"bc1-rgba-unorm":{gl:33777,x:TP},"bc1-rgba-unorm-srgb":{gl:35916,x:SP},"bc2-rgba-unorm":{gl:33778,x:TP},"bc2-rgba-unorm-srgb":{gl:35918,x:SP},"bc3-rgba-unorm":{gl:33779,x:TP},"bc3-rgba-unorm-srgb":{gl:35919,x:SP},"bc4-r-unorm":{gl:36283,x:oS},"bc4-r-snorm":{gl:36284,x:oS},"bc5-rg-unorm":{gl:36285,x:oS},"bc5-rg-snorm":{gl:36286,x:oS},"bc6h-rgb-ufloat":{gl:36495,x:sS},"bc6h-rgb-float":{gl:36494,x:sS},"bc7-rgba-unorm":{gl:36492,x:sS},"bc7-rgba-unorm-srgb":{gl:36493,x:sS},"etc2-rgb8unorm":{gl:37492},"etc2-rgb8unorm-srgb":{gl:37494},"etc2-rgb8a1unorm":{gl:37496},"etc2-rgb8a1unorm-srgb":{gl:37497},"etc2-rgba8unorm":{gl:37493},"etc2-rgba8unorm-srgb":{gl:37495},"eac-r11unorm":{gl:37488},"eac-r11snorm":{gl:37489},"eac-rg11unorm":{gl:37490},"eac-rg11snorm":{gl:37491},"astc-4x4-unorm":{gl:37808},"astc-4x4-unorm-srgb":{gl:37840},"astc-5x4-unorm":{gl:37809},"astc-5x4-unorm-srgb":{gl:37841},"astc-5x5-unorm":{gl:37810},"astc-5x5-unorm-srgb":{gl:37842},"astc-6x5-unorm":{gl:37811},"astc-6x5-unorm-srgb":{gl:37843},"astc-6x6-unorm":{gl:37812},"astc-6x6-unorm-srgb":{gl:37844},"astc-8x5-unorm":{gl:37813},"astc-8x5-unorm-srgb":{gl:37845},"astc-8x6-unorm":{gl:37814},"astc-8x6-unorm-srgb":{gl:37846},"astc-8x8-unorm":{gl:37815},"astc-8x8-unorm-srgb":{gl:37847},"astc-10x5-unorm":{gl:37816},"astc-10x5-unorm-srgb":{gl:37848},"astc-10x6-unorm":{gl:37817},"astc-10x6-unorm-srgb":{gl:37849},"astc-10x8-unorm":{gl:37818},"astc-10x8-unorm-srgb":{gl:37850},"astc-10x10-unorm":{gl:37819},"astc-10x10-unorm-srgb":{gl:37851},"astc-12x10-unorm":{gl:37820},"astc-12x10-unorm-srgb":{gl:37852},"astc-12x12-unorm":{gl:37821},"astc-12x12-unorm-srgb":{gl:37853},"pvrtc-rgb4unorm-webgl":{gl:35840},"pvrtc-rgba4unorm-webgl":{gl:35842},"pvrtc-rgb2unorm-webgl":{gl:35841},"pvrtc-rgba2unorm-webgl":{gl:35843},"etc1-rbg-unorm-webgl":{gl:36196},"atc-rgb-unorm-webgl":{gl:35986},"atc-rgba-unorm-webgl":{gl:35986},"atc-rgbai-unorm-webgl":{gl:34798}}});var Noe,Mk,zoe=Nt(()=>{Eo();wP();aS();Noe={"depth-clip-control":"EXT_depth_clamp","timestamp-query":"EXT_disjoint_timer_query_webgl2","compilation-status-async-webgl":"KHR_parallel_shader_compile","polygon-mode-webgl":"WEBGL_polygon_mode","provoking-vertex-webgl":"WEBGL_provoking_vertex","shader-clip-cull-distance-webgl":"WEBGL_clip_cull_distance","shader-noperspective-interpolation-webgl":"NV_shader_noperspective_interpolation","shader-conservative-depth-webgl":"EXT_conservative_depth"},Mk=class extends BI{gl;extensions;testedFeatures=new Set;constructor(e,t,i){super([],i),this.gl=e,this.extensions=t,D0(e,"EXT_color_buffer_float",t)}*[Symbol.iterator](){let e=this.getFeatures();for(let t of e)this.has(t)&&(yield t);return[]}has(e){return this.disabledFeatures?.[e]?!1:(this.testedFeatures.has(e)||(this.testedFeatures.add(e),Doe(e)&&m9(this.gl,e,this.extensions)&&this.features.add(e),this.getWebGLFeature(e)&&this.features.add(e)),this.features.has(e))}initializeFeatures(){let e=this.getFeatures().filter(t=>t!=="polygon-mode-webgl");for(let t of e)this.has(t)}getFeatures(){return[...Object.keys(Noe),...Object.keys(Sk)]}getWebGLFeature(e){let t=Noe[e];return Boolean(typeof t=="string"?D0(this.gl,t,this.extensions):t)}}});var Ik,Uoe=Nt(()=>{Eo();Ik=class extends RI{get maxTextureDimension1D(){return 0}get maxTextureDimension2D(){return this.getParameter(3379)}get maxTextureDimension3D(){return this.getParameter(32883)}get maxTextureArrayLayers(){return this.getParameter(35071)}get maxBindGroups(){return 0}get maxDynamicUniformBuffersPerPipelineLayout(){return 0}get maxDynamicStorageBuffersPerPipelineLayout(){return 0}get maxSampledTexturesPerShaderStage(){return this.getParameter(35660)}get maxSamplersPerShaderStage(){return this.getParameter(35661)}get maxStorageBuffersPerShaderStage(){return 0}get maxStorageTexturesPerShaderStage(){return 0}get maxUniformBuffersPerShaderStage(){return this.getParameter(35375)}get maxUniformBufferBindingSize(){return this.getParameter(35376)}get maxStorageBufferBindingSize(){return 0}get minUniformBufferOffsetAlignment(){return this.getParameter(35380)}get minStorageBufferOffsetAlignment(){return 0}get maxVertexBuffers(){return 16}get maxVertexAttributes(){return this.getParameter(34921)}get maxVertexBufferArrayStride(){return 2048}get maxInterStageShaderVariables(){return this.getParameter(35659)}get maxComputeWorkgroupStorageSize(){return 0}get maxComputeInvocationsPerWorkgroup(){return 0}get maxComputeWorkgroupSizeX(){return 0}get maxComputeWorkgroupSizeY(){return 0}get maxComputeWorkgroupSizeZ(){return 0}get maxComputeWorkgroupsPerDimension(){return 0}gl;limits={};constructor(e){super(),this.gl=e}getParameter(e){return this.limits[e]===void 0&&(this.limits[e]=this.gl.getParameter(e)),this.limits[e]||0}}});function HOe(r){return r<34069?r+34069:r}function WOe(r){switch(r){case 36053:return"success";case 36054:return"Mismatched attachments";case 36055:return"No attachments";case 36057:return"Height/width mismatch";case 36061:return"Unsupported or split attachments";case 36182:return"Samples mismatch";default:return`${r}`}}var n_,Pk=Nt(()=>{Eo();aS();n_=class extends XA{device;gl;handle;colorAttachments=[];depthStencilAttachment=null;constructor(e,t){super(e,t);let i=t.handle===null;this.device=e,this.gl=e.gl,this.handle=this.props.handle||i?this.props.handle:this.gl.createFramebuffer(),i||(e._setWebGLDebugMetadata(this.handle,this,{spector:this.props}),this.autoCreateAttachmentTextures(),this.updateAttachments())}destroy(){super.destroy(),!this.destroyed&&this.handle!==null&&this.gl.deleteFramebuffer(this.handle)}updateAttachments(){let e=this.gl.bindFramebuffer(36160,this.handle);for(let t=0;t{Eo();Pk();Rk=class extends IT{device;handle=null;_framebuffer=null;get[Symbol.toStringTag](){return"WebGLCanvasContext"}constructor(e,t){super(t),this.device=e,this._setAutoCreatedCanvasId(`${this.device.id}-canvas`),this._configureDevice()}_configureDevice(){(this.drawingBufferWidth!==this._framebuffer?.width||this.drawingBufferHeight!==this._framebuffer?.height)&&this._framebuffer?.resize([this.drawingBufferWidth,this.drawingBufferHeight])}_getCurrentFramebuffer(){return this._framebuffer||=new n_(this.device,{id:"canvas-context-framebuffer",handle:null,width:this.drawingBufferWidth,height:this.drawingBufferHeight}),this._framebuffer}}});var Bk,joe=Nt(()=>{Eo();Bk=class extends OI{device;handle=null;context2d;get[Symbol.toStringTag](){return"WebGLPresentationContext"}constructor(e,t={}){super(t),this.device=e;let i=`${this[Symbol.toStringTag]}(${this.id})`;if(!this.device.getDefaultCanvasContext().offscreenCanvas)throw new Error(`${i}: WebGL PresentationContext requires the default CanvasContext canvas to be an OffscreenCanvas`);let o=this.canvas.getContext("2d");if(!o)throw new Error(`${i}: Failed to create 2d presentation context`);this.context2d=o,this._setAutoCreatedCanvasId(`${this.device.id}-presentation-canvas`),this._configureDevice(),this._startObservers()}present(){this._resizeDrawingBufferIfNeeded(),this.device.submit();let e=this.device.getDefaultCanvasContext(),[t,i]=e.getDrawingBufferSize();if(!(this.drawingBufferWidth===0||this.drawingBufferHeight===0||t===0||i===0||e.canvas.width===0||e.canvas.height===0)){if(t!==this.drawingBufferWidth||i!==this.drawingBufferHeight||e.canvas.width!==this.drawingBufferWidth||e.canvas.height!==this.drawingBufferHeight)throw new Error(`${this[Symbol.toStringTag]}(${this.id}): Default canvas context size ${t}x${i} does not match presentation size ${this.drawingBufferWidth}x${this.drawingBufferHeight}`);this.context2d.clearRect(0,0,this.drawingBufferWidth,this.drawingBufferHeight),this.context2d.drawImage(e.canvas,0,0)}}_configureDevice(){}_getCurrentFramebuffer(e){let t=this.device.getDefaultCanvasContext();return t.setDrawingBufferSize(this.drawingBufferWidth,this.drawingBufferHeight),t.getCurrentFramebuffer(e)}}});function Goe(r="id"){g9[r]=g9[r]||1;let e=g9[r]++;return`${r}-${e}`}var g9,Hoe=Nt(()=>{g9={}});function qOe(r){return r&lo.INDEX?34963:r&lo.VERTEX?34962:r&lo.UNIFORM?35345:34962}function $Oe(r){return r&lo.INDEX||r&lo.VERTEX?35044:r&lo.UNIFORM?35048:35044}var O0,Lk=Nt(()=>{Eo();O0=class extends lo{device;gl;handle;glTarget;glUsage;glIndexType=5123;byteLength=0;bytesUsed=0;constructor(e,t={}){super(e,t),this.device=e,this.gl=this.device.gl;let i=typeof t=="object"?t.handle:void 0;this.handle=i||this.gl.createBuffer(),e._setWebGLDebugMetadata(this.handle,this,{spector:{...this.props,data:typeof this.props.data}}),this.glTarget=qOe(this.props.usage),this.glUsage=$Oe(this.props.usage),this.glIndexType=this.props.indexType==="uint32"?5125:5123,t.data?this._initWithData(t.data,t.byteOffset,t.byteLength):this._initWithByteLength(t.byteLength||0)}destroy(){!this.destroyed&&this.handle&&(this.removeStats(),this.props.handle?this.trackDeallocatedReferencedMemory("Buffer"):(this.trackDeallocatedMemory(),this.gl.deleteBuffer(this.handle)),this.destroyed=!0,this.handle=null)}_initWithData(e,t=0,i=e.byteLength+t){let n=this.glTarget;this.gl.bindBuffer(n,this.handle),this.gl.bufferData(n,i,this.glUsage),this.gl.bufferSubData(n,t,e),this.gl.bindBuffer(n,null),this.bytesUsed=i,this.byteLength=i,this._setDebugData(e,t,i),this.props.handle?this.trackReferencedMemory(i,"Buffer"):this.trackAllocatedMemory(i)}_initWithByteLength(e){let t=e;e===0&&(t=new Float32Array(0));let i=this.glTarget;return this.gl.bindBuffer(i,this.handle),this.gl.bufferData(i,t,this.glUsage),this.gl.bindBuffer(i,null),this.bytesUsed=e,this.byteLength=e,this._setDebugData(null,0,e),this.props.handle?this.trackReferencedMemory(e,"Buffer"):this.trackAllocatedMemory(e),this}write(e,t=0){let i=ArrayBuffer.isView(e)?e:new Uint8Array(e),n=0,o=void 0,s=36663;this.gl.bindBuffer(s,this.handle),n!==0||o!==void 0?this.gl.bufferSubData(s,t,i,n,o):this.gl.bufferSubData(s,t,i),this.gl.bindBuffer(s,null),this._setDebugData(e,t,e.byteLength)}async mapAndWriteAsync(e,t=0,i=this.byteLength-t){let n=new ArrayBuffer(i);await e(n,"copied"),this.write(n,t)}async readAsync(e=0,t){return this.readSyncWebGL(e,t)}async mapAndReadAsync(e,t=0,i){let n=await this.readAsync(t,i);return await e(n.buffer,"copied")}readSyncWebGL(e=0,t){t=t??this.byteLength-e;let i=new Uint8Array(t),n=0;return this.gl.bindBuffer(36662,this.handle),this.gl.getBufferSubData(36662,e,i,n,t),this.gl.bindBuffer(36662,null),this._setDebugData(i,e,t),i}}});function Woe(r){let e=r.split(/\r?\n/),t=[];for(let i of e){if(i.length<=1)continue;let n=i.trim(),o=i.split(":"),s=o[0]?.trim();if(o.length===2){let[k,U]=o;if(!k||!U){t.push({message:n,type:Dk(s||"info"),lineNum:0,linePos:0});continue}t.push({message:U.trim(),type:Dk(k),lineNum:0,linePos:0});continue}let[c,f,g,...y]=o;if(!c||!f||!g){t.push({message:o.slice(1).join(":").trim()||n,type:Dk(s||"info"),lineNum:0,linePos:0});continue}let M=parseInt(g,10);Number.isNaN(M)&&(M=0);let F=parseInt(f,10);Number.isNaN(F)&&(F=0),t.push({message:y.join(":").trim(),type:Dk(c),lineNum:M,linePos:F})}return t}function Dk(r){let e=["warning","error","info"],t=r.toLowerCase();return e.includes(t)?t:"info"}var qoe=Nt(()=>{});var Ok,$oe=Nt(()=>{Eo();qoe();Ok=class extends Uv{device;handle;constructor(e,t){switch(super(e,t),this.device=e,this.props.stage){case"vertex":this.handle=this.props.handle||this.device.gl.createShader(35633);break;case"fragment":this.handle=this.props.handle||this.device.gl.createShader(35632);break;default:throw new Error(this.props.stage)}e._setWebGLDebugMetadata(this.handle,this,{spector:this.props});let i=this._compile(this.source);i&&typeof i.catch=="function"&&i.catch(()=>{this.compilationStatus="error"})}destroy(){this.handle&&(this.removeStats(),this.device.gl.deleteShader(this.handle),this.destroyed=!0,this.handle.destroyed=!0)}get asyncCompilationStatus(){return this._waitForCompilationComplete().then(()=>(this._getCompilationStatus(),this.compilationStatus))}async getCompilationInfo(){return await this._waitForCompilationComplete(),this.getCompilationInfoSync()}getCompilationInfoSync(){let e=this.device.gl.getShaderInfoLog(this.handle);return e?Woe(e):[]}getTranslatedSource(){return this.device.getExtension("WEBGL_debug_shaders").WEBGL_debug_shaders?.getTranslatedShaderSource(this.handle)||null}_compile(e){e=e.startsWith("#version ")?e:`#version 300 es +${e}`;let{gl:t}=this.device;if(t.shaderSource(this.handle,e),t.compileShader(this.handle),!this.device.props.debug){this.compilationStatus="pending";return}if(!this.device.features.has("compilation-status-async-webgl")){if(this._getCompilationStatus(),this.debugShader(),this.compilationStatus==="error")throw new Error(`GLSL compilation errors in ${this.props.stage} shader ${this.props.id}`);return}return Lr.once(1,"Shader compilation is asynchronous")(),this._waitForCompilationComplete().then(()=>{Lr.info(2,`Shader ${this.id} - async compilation complete: ${this.compilationStatus}`)(),this._getCompilationStatus(),this.debugShader()})}async _waitForCompilationComplete(){let e=async n=>await new Promise(o=>setTimeout(o,n));if(!this.device.features.has("compilation-status-async-webgl")){await e(10);return}let{gl:i}=this.device;for(;;){if(i.getShaderParameter(this.handle,37297))return;await e(10)}}_getCompilationStatus(){this.compilationStatus=this.device.gl.getShaderParameter(this.handle,35713)?"success":"error"}}});function Zoe(r,e,t,i){if(KOe(e))return i(r);let n=r;n.pushState();try{return XOe(r,e),B0(n.gl,t),i(r)}finally{n.popState()}}function XOe(r,e){let t=r,{gl:i}=t;if(e.cullMode)switch(e.cullMode){case"none":i.disable(2884);break;case"front":i.enable(2884),i.cullFace(1028);break;case"back":i.enable(2884),i.cullFace(1029);break}if(e.frontFace&&i.frontFace(P1("frontFace",e.frontFace,{ccw:2305,cw:2304})),e.unclippedDepth&&r.features.has("depth-clip-control")&&i.enable(34383),e.depthBias!==void 0&&(i.enable(32823),i.polygonOffset(e.depthBias,e.depthBiasSlopeScale||0)),e.provokingVertex&&r.features.has("provoking-vertex-webgl")){let o=t.getExtension("WEBGL_provoking_vertex").WEBGL_provoking_vertex,s=P1("provokingVertex",e.provokingVertex,{first:36429,last:36430});o?.provokingVertexWEBGL(s)}if((e.polygonMode||e.polygonOffsetLine)&&r.features.has("polygon-mode-webgl")){if(e.polygonMode){let o=t.getExtension("WEBGL_polygon_mode").WEBGL_polygon_mode,s=P1("polygonMode",e.polygonMode,{fill:6914,line:6913});o?.polygonModeWEBGL(1028,s),o?.polygonModeWEBGL(1029,s)}e.polygonOffsetLine&&i.enable(10754)}if(r.features.has("shader-clip-cull-distance-webgl")&&(e.clipDistance0&&i.enable(12288),e.clipDistance1&&i.enable(12289),e.clipDistance2&&i.enable(12290),e.clipDistance3&&i.enable(12291),e.clipDistance4&&i.enable(12292),e.clipDistance5&&i.enable(12293),e.clipDistance6&&i.enable(12294),e.clipDistance7&&i.enable(12295)),e.depthWriteEnabled!==void 0&&i.depthMask(YOe("depthWriteEnabled",e.depthWriteEnabled)),e.depthCompare&&(e.depthCompare!=="always"?i.enable(2929):i.disable(2929),i.depthFunc(kk("depthCompare",e.depthCompare))),e.clearDepth!==void 0&&i.clearDepth(e.clearDepth),e.stencilWriteMask){let n=e.stencilWriteMask;i.stencilMaskSeparate(1028,n),i.stencilMaskSeparate(1029,n)}if(e.stencilReadMask&&Lr.warn("stencilReadMask not supported under WebGL"),e.stencilCompare){let n=e.stencilReadMask||4294967295,o=kk("depthCompare",e.stencilCompare);e.stencilCompare!=="always"?i.enable(2960):i.disable(2960),i.stencilFuncSeparate(1028,o,0,n),i.stencilFuncSeparate(1029,o,0,n)}if(e.stencilPassOperation&&e.stencilFailOperation&&e.stencilDepthFailOperation){let n=A9("stencilPassOperation",e.stencilPassOperation),o=A9("stencilFailOperation",e.stencilFailOperation),s=A9("stencilDepthFailOperation",e.stencilDepthFailOperation);i.stencilOpSeparate(1028,o,s,n),i.stencilOpSeparate(1029,o,s,n)}switch(e.blend){case!0:i.enable(3042);break;case!1:i.disable(3042);break;default:}if(e.blendColorOperation||e.blendAlphaOperation){let n=Xoe("blendColorOperation",e.blendColorOperation||"add"),o=Xoe("blendAlphaOperation",e.blendAlphaOperation||"add");i.blendEquationSeparate(n,o);let s=Fk("blendColorSrcFactor",e.blendColorSrcFactor||"one"),c=Fk("blendColorDstFactor",e.blendColorDstFactor||"zero"),f=Fk("blendAlphaSrcFactor",e.blendAlphaSrcFactor||"one"),g=Fk("blendAlphaDstFactor",e.blendAlphaDstFactor||"zero");i.blendFuncSeparate(s,c,f,g)}}function kk(r,e){return P1(r,e,{never:512,less:513,equal:514,"less-equal":515,greater:516,"not-equal":517,"greater-equal":518,always:519})}function A9(r,e){return P1(r,e,{keep:7680,zero:0,replace:7681,invert:5386,"increment-clamp":7682,"decrement-clamp":7683,"increment-wrap":34055,"decrement-wrap":34056})}function Xoe(r,e){return P1(r,e,{add:32774,subtract:32778,"reverse-subtract":32779,min:32775,max:32776})}function Fk(r,e,t="color"){return P1(r,e,{one:1,zero:0,src:768,"one-minus-src":769,dst:774,"one-minus-dst":775,"src-alpha":770,"one-minus-src-alpha":771,"dst-alpha":772,"one-minus-dst-alpha":773,"src-alpha-saturated":776,constant:t==="color"?32769:32771,"one-minus-constant":t==="color"?32770:32772,src1:768,"one-minus-src1":769,"src1-alpha":770,"one-minus-src1-alpha":771})}function ZOe(r,e){return`Illegal parameter ${e} for ${r}`}function P1(r,e,t){if(!(e in t))throw new Error(ZOe(r,e));return t[e]}function YOe(r,e){return e}function KOe(r){let e=!0;for(let t in r){e=!1;break}return e}var _9=Nt(()=>{Eo();nS()});function Nk(r){let e={};return r.addressModeU&&(e[10242]=y9(r.addressModeU)),r.addressModeV&&(e[10243]=y9(r.addressModeV)),r.addressModeW&&(e[32882]=y9(r.addressModeW)),r.magFilter&&(e[10240]=v9(r.magFilter)),(r.minFilter||r.mipmapFilter)&&(e[10241]=QOe(r.minFilter||"linear",r.mipmapFilter)),r.lodMinClamp!==void 0&&(e[33082]=r.lodMinClamp),r.lodMaxClamp!==void 0&&(e[33083]=r.lodMaxClamp),r.type==="comparison-sampler"&&(e[34892]=34894),r.compare&&(e[34893]=kk("compare",r.compare)),r.maxAnisotropy&&(e[34046]=r.maxAnisotropy),e}function y9(r){switch(r){case"clamp-to-edge":return 33071;case"repeat":return 10497;case"mirror-repeat":return 33648}}function v9(r){switch(r){case"nearest":return 9728;case"linear":return 9729}}function QOe(r,e="none"){if(!e)return v9(r);switch(e){case"none":return v9(r);case"nearest":switch(r){case"nearest":return 9984;case"linear":return 9985}break;case"linear":switch(r){case"nearest":return 9986;case"linear":return 9987}}}var x9=Nt(()=>{_9()});var zk,Yoe=Nt(()=>{Eo();x9();zk=class extends Dd{device;handle;parameters;constructor(e,t){super(e,t),this.device=e,this.parameters=Nk(t),this.handle=t.handle||this.device.gl.createSampler(),this._setSamplerParameters(this.parameters)}destroy(){this.handle&&(this.device.gl.deleteSampler(this.handle),this.handle=void 0)}toString(){return`Sampler(${this.id},${JSON.stringify(this.props)})`}_setSamplerParameters(e){for(let[t,i]of Object.entries(e)){let n=Number(t);switch(n){case 33082:case 33083:this.device.gl.samplerParameterf(this.handle,n,i);break;default:this.device.gl.samplerParameteri(this.handle,n,i);break}}}}});function o_(r,e,t){if(JOe(e))return t(r);let{nocatch:i=!0}=e,n=L0.get(r);n.push(),B0(r,e);let o;if(i)o=t(r),n.pop();else try{o=t(r)}finally{n.pop()}return o}function JOe(r){for(let e in r)return!1;return!0}var Uk=Nt(()=>{nS();c9()});var s_,b9=Nt(()=>{Eo();s_=class extends Bg{device;gl;handle;texture;constructor(e,t){super(e,{...oo.defaultProps,...t}),this.device=e,this.gl=this.device.gl,this.handle=null,this.texture=t.texture}}});function Vk(r){return eFe[r]}var eFe,w9=Nt(()=>{eFe={[5124]:"sint32",[5125]:"uint32",[5122]:"sint16",[5123]:"uint16",[5120]:"sint8",[5121]:"uint8",[5126]:"float32",[5131]:"float16",[33635]:"uint16",[32819]:"uint16",[32820]:"uint16",[33640]:"uint32",[35899]:"uint32",[35902]:"uint32",[34042]:"uint32",[36269]:"uint32"}});function tFe(r,e=0){return e?new r.constructor(r.buffer,r.byteOffset+e,(r.byteLength-e)/r.BYTES_PER_ELEMENT):r}function rFe(r,e){if(e%r.BYTES_PER_ELEMENT!==0)throw new Error(`Texture byteOffset ${e} must align to typed array element size ${r.BYTES_PER_ELEMENT}`);return e/r.BYTES_PER_ELEMENT}function iFe(r){switch(r){case"1d":break;case"2d":return 3553;case"3d":return 32879;case"cube":return 34067;case"2d-array":return 35866;case"cube-array":break}throw new Error(r)}function jk(r,e,t){return e==="cube"?34069+t:r}var a_,Gk=Nt(()=>{Eo();aS();x9();Uk();b9();w9();a_=class extends oo{device;gl;handle;sampler=void 0;view;glTarget;glFormat;glType;glInternalFormat;compressed;_textureUnit=0;_framebuffer=null;_framebufferAttachmentKey=null;constructor(e,t){super(e,t,{byteAlignment:1}),this.device=e,this.gl=this.device.gl;let i=Ck(this.props.format);this.glTarget=iFe(this.props.dimension),this.glInternalFormat=i.internalFormat,this.glFormat=i.format,this.glType=i.type,this.compressed=i.compressed,this.handle=this.props.handle||this.gl.createTexture(),this.device._setWebGLDebugMetadata(this.handle,this,{spector:this.props}),this.gl.bindTexture(this.glTarget,this.handle);let{dimension:n,width:o,height:s,depth:c,mipLevels:f,glTarget:g,glInternalFormat:y}=this;if(!this.compressed)switch(n){case"2d":case"cube":this.gl.texStorage2D(g,f,y,o,s);break;case"2d-array":case"3d":this.gl.texStorage3D(g,f,y,o,s,c);break;default:throw new Error(n)}this.gl.bindTexture(this.glTarget,null),this._initializeData(t.data),this.props.handle?this.trackReferencedMemory(this.getAllocatedByteLength(),"Texture"):this.trackAllocatedMemory(this.getAllocatedByteLength(),"Texture"),this.setSampler(this.props.sampler),this.view=new s_(this.device,{...this.props,texture:this}),Object.seal(this)}destroy(){this.handle&&(this._framebuffer?.destroy(),this._framebuffer=null,this._framebufferAttachmentKey=null,this.removeStats(),this.props.handle?this.trackDeallocatedReferencedMemory("Texture"):(this.gl.deleteTexture(this.handle),this.trackDeallocatedMemory("Texture")),this.destroyed=!0)}createView(e){return new s_(this.device,{...e,texture:this})}setSampler(e={}){super.setSampler(e);let t=Nk(this.sampler.props);this._setSamplerParameters(t)}copyExternalImage(e){let t=this._normalizeCopyExternalImageOptions(e);if(t.sourceX||t.sourceY)throw new Error("WebGL does not support sourceX/sourceY)");let{glFormat:i,glType:n}=this,{image:o,depth:s,mipLevel:c,x:f,y:g,z:y,width:M,height:F}=t,k=jk(this.glTarget,this.dimension,y),U=t.flipY?{[37440]:!0}:{};return this.gl.bindTexture(this.glTarget,this.handle),o_(this.gl,U,()=>{switch(this.dimension){case"2d":case"cube":this.gl.texSubImage2D(k,c,f,g,M,F,i,n,o);break;case"2d-array":case"3d":this.gl.texSubImage3D(k,c,f,g,y,M,F,s,i,n,o);break;default:}}),this.gl.bindTexture(this.glTarget,null),{width:t.width,height:t.height}}copyImageData(e){super.copyImageData(e)}readBuffer(e={},t){if(!t)throw new Error(`${this} readBuffer requires a destination buffer`);let i=this._getSupportedColorReadOptions(e),n=e.byteOffset??0,o=this.computeMemoryLayout(i);if(t.byteLength{this.gl.readPixels(i.x,i.y,i.width,i.height,this.glFormat,this.glType,n+c)})}finally{this.gl.bindBuffer(35051,null)}return t}async readDataAsync(e={}){throw new Error(`${this} readDataAsync is deprecated; use readBuffer() with an explicit destination buffer or DynamicTexture.readAsync()`)}writeBuffer(e,t={}){let i=this._normalizeTextureWriteOptions(t),{width:n,height:o,depthOrArrayLayers:s,mipLevel:c,byteOffset:f,x:g,y,z:M}=i,{glFormat:F,glType:k,compressed:U}=this,K=jk(this.glTarget,this.dimension,M);if(U)throw new Error("writeBuffer for compressed textures is not implemented in WebGL");let{bytesPerPixel:ee}=this.device.getTextureFormatInfo(this.format),he=ee?i.bytesPerRow/ee:void 0,xe={[3317]:this.byteAlignment,...he!==void 0?{[3314]:he}:{},[32878]:i.rowsPerImage};this.gl.bindTexture(this.glTarget,this.handle),this.gl.bindBuffer(35052,e.handle),o_(this.gl,xe,()=>{switch(this.dimension){case"2d":case"cube":this.gl.texSubImage2D(K,c,g,y,n,o,F,k,f);break;case"2d-array":case"3d":this.gl.texSubImage3D(K,c,g,y,M,n,o,s,F,k,f);break;default:}}),this.gl.bindBuffer(35052,null),this.gl.bindTexture(this.glTarget,null)}writeData(e,t={}){let i=this._normalizeTextureWriteOptions(t),n=ArrayBuffer.isView(e)?e:new Uint8Array(e),{width:o,height:s,depthOrArrayLayers:c,mipLevel:f,x:g,y,z:M,byteOffset:F}=i,{glFormat:k,glType:U,compressed:K}=this,ee=jk(this.glTarget,this.dimension,M),he;if(!K){let{bytesPerPixel:He}=this.device.getTextureFormatInfo(this.format);He&&(he=i.bytesPerRow/He)}let xe=this.compressed?{}:{[3317]:this.byteAlignment,...he!==void 0?{[3314]:he}:{},[32878]:i.rowsPerImage},me=rFe(n,F),Ce=K?tFe(n,F):n,Oe=this._getMipLevelSize(f),ne=g===0&&y===0&&M===0&&o===Oe.width&&s===Oe.height&&c===Oe.depthOrArrayLayers;this.gl.bindTexture(this.glTarget,this.handle),this.gl.bindBuffer(35052,null),o_(this.gl,xe,()=>{switch(this.dimension){case"2d":case"cube":K?ne?this.gl.compressedTexImage2D(ee,f,k,o,s,0,Ce):this.gl.compressedTexSubImage2D(ee,f,g,y,o,s,k,Ce):this.gl.texSubImage2D(ee,f,g,y,o,s,k,U,n,me);break;case"2d-array":case"3d":K?ne?this.gl.compressedTexImage3D(ee,f,k,o,s,c,0,Ce):this.gl.compressedTexSubImage3D(ee,f,g,y,M,o,s,c,k,Ce):this.gl.texSubImage3D(ee,f,g,y,M,o,s,c,k,U,n,me);break;default:}}),this.gl.bindTexture(this.glTarget,null)}_getRowByteAlignment(e,t){return 1}_getFramebuffer(){return this._framebuffer||=this.device.createFramebuffer({id:`framebuffer-for-${this.id}`,width:this.width,height:this.height,colorAttachments:[this]}),this._framebuffer}readDataSyncWebGL(e={}){let t=this._getSupportedColorReadOptions(e),i=this.computeMemoryLayout(t),n=Vk(this.glType),o=NI(n),s=new o(i.byteLength/o.BYTES_PER_ELEMENT);return this._readColorTextureLayers(t,i,c=>{let f=new o(s.buffer,s.byteOffset+c,i.bytesPerImage/o.BYTES_PER_ELEMENT);this.gl.readPixels(t.x,t.y,t.width,t.height,this.glFormat,this.glType,f)}),s.buffer}_readColorTextureLayers(e,t,i){let n=this._getFramebuffer(),o=t.bytesPerRow/t.bytesPerPixel,s={[3333]:this.byteAlignment,...o!==e.width?{[3330]:o}:{}},c=this.gl.getParameter(3074),f=this.gl.bindFramebuffer(36160,n.handle);try{this.gl.readBuffer(36064),o_(this.gl,s,()=>{for(let g=0;g{});function Joe(r){switch(r){case"point-list":return 0;case"line-list":return 1;case"line-strip":return 3;case"triangle-list":return 4;case"triangle-strip":return 5;default:throw new Error(r)}}function ese(r){switch(r){case"point-list":return 0;case"line-list":return 1;case"line-strip":return 1;case"triangle-list":return 4;case"triangle-strip":return 4;default:throw new Error(r)}}var T9=Nt(()=>{});function nFe(r,e){let t={...r,attributes:r.attributes.map(i=>({...i})),bindings:r.bindings.map(i=>({...i}))};for(let i of e?.attributes||[]){let n=t.attributes.find(o=>o.name===i.name);n?(n.type=i.type||n.type,n.stepMode=i.stepMode||n.stepMode):Lr.warn(`shader layout attribute ${i.name} not present in shader`)}for(let i of e?.bindings||[]){let n=rse(t,i.name);if(!n){Lr.warn(`shader layout binding ${i.name} not present in shader`);continue}Object.assign(n,i)}return t}function rse(r,e){return r.bindings.find(t=>t.name===e||t.name===`${e}Uniforms`||`${t.name}Uniforms`===e)}function tse(r,e){return r[e]||r[`${e}Uniforms`]||r[e.replace(/Uniforms$/,"")]}var Hk,ise=Nt(()=>{Eo();_9();Qoe();Lk();Pk();Gk();b9();T9();Hk=class extends km{device;handle;vs;fs;introspectedLayout;bindings={};uniforms={};varyings=null;_uniformCount=0;_uniformSetters={};get[Symbol.toStringTag](){return"WEBGLRenderPipeline"}constructor(e,t){super(e,t),this.device=e;let i=this.sharedRenderPipeline||this.device._createSharedRenderPipelineWebGL(t);this.sharedRenderPipeline=i,this.handle=i.handle,this.vs=i.vs,this.fs=i.fs,this.linkStatus=i.linkStatus,this.introspectedLayout=i.introspectedLayout,this.device._setWebGLDebugMetadata(this.handle,this,{spector:{id:this.props.id}}),this.shaderLayout=t.shaderLayout?nFe(this.introspectedLayout,t.shaderLayout):this.introspectedLayout}destroy(){this.destroyed||(this.sharedRenderPipeline&&!this.props._sharedRenderPipeline&&this.sharedRenderPipeline.destroy(),this.destroyResource())}setBindings(e,t){let i=vF(kI(this.shaderLayout,e));for(let[n,o]of Object.entries(i)){let s=rse(this.shaderLayout,n);if(s){switch(o||Lr.warn(`Unsetting binding "${n}" in render pipeline "${this.id}"`)(),s.type){case"uniform":if(!(o instanceof O0)&&!(o.buffer instanceof O0))throw new Error("buffer value");break;case"texture":if(!(o instanceof s_||o instanceof a_||o instanceof n_))throw new Error(`${this} Bad texture binding for ${n}`);break;case"sampler":Lr.warn(`Ignoring sampler ${n}`)();break;default:throw new Error(s.type)}this.bindings[n]=o}else{let c=this.shaderLayout.bindings.map(f=>`"${f.name}"`).join(", ");t?.disableWarnings||Lr.warn(`No binding "${n}" in render pipeline "${this.id}", expected one of ${c}`,o)()}}}draw(e){this._syncLinkStatus();let t=e.bindGroups?vF(e.bindGroups):e.bindings||this.bindings,{renderPass:i,parameters:n=this.props.parameters,topology:o=this.props.topology,vertexArray:s,vertexCount:c,instanceCount:f,isInstanced:g=!1,firstVertex:y=0,transformFeedback:M,uniforms:F=this.uniforms}=e,k=Joe(o),U=Boolean(s.indexBuffer),K=s.indexBuffer?.glIndexType;if(this.linkStatus!=="success")return Lr.info(2,`RenderPipeline:${this.id}.draw() aborted - waiting for shader linking`)(),!1;if(!this._areTexturesRenderable(t))return Lr.info(2,`RenderPipeline:${this.id}.draw() aborted - textures not yet loaded`)(),!1;this.device.gl.useProgram(this.handle),s.bindBeforeRender(i),M&&M.begin(this.props.topology),this._applyBindings(t,{disableWarnings:this.props.disableWarnings}),this._applyUniforms(F);let ee=i;return Zoe(this.device,n,ee.glParameters,()=>{U&&g?this.device.gl.drawElementsInstanced(k,c||0,K,y,f||0):U?this.device.gl.drawElements(k,c||0,K,y):g?this.device.gl.drawArraysInstanced(k,y,c||0,f||0):this.device.gl.drawArrays(k,y,c||0),M&&M.end()}),s.unbindAfterRender(i),!0}_areTexturesRenderable(e){let t=!0;for(let i of this.shaderLayout.bindings)tse(e,i.name)||(Lr.warn(`Binding ${i.name} not found in ${this.id}`)(),t=!1);return t}_applyBindings(e,t){if(this._syncLinkStatus(),this.linkStatus!=="success")return;let{gl:i}=this.device;i.useProgram(this.handle);let n=0,o=0;for(let s of this.shaderLayout.bindings){let c=tse(e,s.name);if(!c)throw new Error(`No value for binding ${s.name} in ${this.id}`);switch(s.type){case"uniform":let{name:f}=s,g=i.getUniformBlockIndex(this.handle,f);if(g===4294967295)throw new Error(`Invalid uniform block name ${f}`);if(i.uniformBlockBinding(this.handle,g,o),c instanceof O0)i.bindBufferBase(35345,o,c.handle);else{let M=c;i.bindBufferRange(35345,o,M.buffer.handle,M.offset||0,M.size||M.buffer.byteLength-(M.offset||0))}o+=1;break;case"texture":if(!(c instanceof s_||c instanceof a_||c instanceof n_))throw new Error("texture");let y;if(c instanceof s_)y=c.texture;else if(c instanceof a_)y=c;else if(c instanceof n_&&c.colorAttachments[0]instanceof s_)Lr.warn("Passing framebuffer in texture binding may be deprecated. Use fbo.colorAttachments[0] instead")(),y=c.colorAttachments[0].texture;else throw new Error("No texture");i.activeTexture(33984+n),i.bindTexture(y.glTarget,y.handle),n+=1;break;case"sampler":break;case"storage":case"read-only-storage":throw new Error(`binding type '${s.type}' not supported in WebGL`)}}}_applyUniforms(e){for(let t of this.shaderLayout.uniforms||[]){let{name:i,location:n,type:o,textureUnit:s}=t,c=e[i]??s;c!==void 0&&Koe(this.device.gl,n,o,c)}}_syncLinkStatus(){this.linkStatus=this.sharedRenderPipeline.linkStatus}}});function nse(r){return sFe[r]}function Wk(r){return oFe[r]}function qk(r){return Boolean(sse[r])}function ose(r){return sse[r]}var oFe,sse,sFe,$k=Nt(()=>{oFe={[5126]:"f32",[35664]:"vec2",[35665]:"vec3",[35666]:"vec4",[5124]:"i32",[35667]:"vec2",[35668]:"vec3",[35669]:"vec4",[5125]:"u32",[36294]:"vec2",[36295]:"vec3",[36296]:"vec4",[35670]:"f32",[35671]:"vec2",[35672]:"vec3",[35673]:"vec4",[35674]:"mat2x2",[35685]:"mat2x3",[35686]:"mat2x4",[35687]:"mat3x2",[35675]:"mat3x3",[35688]:"mat3x4",[35689]:"mat4x2",[35690]:"mat4x3",[35676]:"mat4x4"},sse={[35678]:{viewDimension:"2d",sampleType:"float"},[35680]:{viewDimension:"cube",sampleType:"float"},[35679]:{viewDimension:"3d",sampleType:"float"},[35682]:{viewDimension:"3d",sampleType:"depth"},[36289]:{viewDimension:"2d-array",sampleType:"float"},[36292]:{viewDimension:"2d-array",sampleType:"depth"},[36293]:{viewDimension:"cube",sampleType:"float"},[36298]:{viewDimension:"2d",sampleType:"sint"},[36299]:{viewDimension:"3d",sampleType:"sint"},[36300]:{viewDimension:"cube",sampleType:"sint"},[36303]:{viewDimension:"2d-array",sampleType:"uint"},[36306]:{viewDimension:"2d",sampleType:"uint"},[36307]:{viewDimension:"3d",sampleType:"uint"},[36308]:{viewDimension:"cube",sampleType:"uint"},[36311]:{viewDimension:"2d-array",sampleType:"uint"}},sFe={uint8:5121,sint8:5120,unorm8:5121,snorm8:5120,uint16:5123,sint16:5122,unorm16:5123,snorm16:5122,uint32:5125,sint32:5124,float16:5131,float32:5126}});function ase(r,e){let t={attributes:[],bindings:[]};t.attributes=aFe(r,e);let i=uFe(r,e);for(let c of i){let f=c.uniforms.map(g=>({name:g.name,format:g.format,byteOffset:g.byteOffset,byteStride:g.byteStride,arrayLength:g.arrayLength}));t.bindings.push({type:"uniform",name:c.name,group:0,location:c.location,visibility:(c.vertex?1:0)&(c.fragment?2:0),minBindingSize:c.byteLength,uniforms:f})}let n=cFe(r,e),o=0;for(let c of n)if(qk(c.type)){let{viewDimension:f,sampleType:g}=ose(c.type);t.bindings.push({type:"texture",name:c.name,group:0,location:o,viewDimension:f,sampleType:g}),c.textureUnit=o,o+=1}n.length&&(t.uniforms=n);let s=lFe(r,e);return s?.length&&(t.varyings=s),t}function aFe(r,e){let t=[],i=r.getProgramParameter(e,35721);for(let n=0;n=0){let g=Wk(c),y=/instance/i.test(s)?"instance":"vertex";t.push({name:s,location:f,stepMode:y,type:g})}}return t.sort((n,o)=>n.location-o.location),t}function lFe(r,e){let t=[],i=r.getProgramParameter(e,35971);for(let n=0;nn.location-o.location),t}function cFe(r,e){let t=[],i=r.getProgramParameter(e,35718);for(let n=0;n1)for(let k=0;kr.getActiveUniformBlockParameter(e,o,s),i=[],n=r.getProgramParameter(e,35382);for(let o=0;oU.name.split(".")[0]).filter(U=>Boolean(U))),k=s.name.replace(/Uniforms$/,"");if(F.size===1&&!F.has(s.name)&&!F.has(k)){let[U]=F;Lr.warn(`Uniform block "${s.name}" uses GLSL instance "${U}". luma.gl binds uniform buffers by block name ("${s.name}") and alias ("${k}"). Prefer matching the instance name to one of those to avoid confusing silent mismatches.`)()}i.push(s)}return i.sort((o,s)=>o.location-s.location),i}function hFe(r){if(r[r.length-1]!=="]")return{name:r,length:1,isArray:!1};let t=/([^[]*)(\[[0-9]+\])?/.exec(r);return{name:f1(t?.[1],`Failed to parse GLSL uniform name ${r}`),length:t?.[2]?1:0,isArray:Boolean(t?.[2])}}var lse=Nt(()=>{Eo();$k()});var cse,Xk,use=Nt(()=>{Eo();lse();$k();cse=4,Xk=class extends FI{device;handle;vs;fs;introspectedLayout={attributes:[],bindings:[],uniforms:[]};linkStatus="pending";constructor(e,t){super(e,t),this.device=e,this.handle=t.handle||this.device.gl.createProgram(),this.vs=t.vs,this.fs=t.fs,t.varyings&&t.varyings.length>0&&this.device.gl.transformFeedbackVaryings(this.handle,t.varyings,t.bufferMode||35981),this._linkShaders(),Lr.time(3,`RenderPipeline ${this.id} - shaderLayout introspection`)(),this.introspectedLayout=ase(this.device.gl,this.handle),Lr.timeEnd(3,`RenderPipeline ${this.id} - shaderLayout introspection`)()}destroy(){this.destroyed||(this.device.gl.useProgram(null),this.device.gl.deleteProgram(this.handle),this.handle.destroyed=!0,this.destroyResource())}async _linkShaders(){let{gl:e}=this.device;if(e.attachShader(this.handle,this.vs.handle),e.attachShader(this.handle,this.fs.handle),Lr.time(cse,`linkProgram for ${this.id}`)(),e.linkProgram(this.handle),Lr.timeEnd(cse,`linkProgram for ${this.id}`)(),!this.device.features.has("compilation-status-async-webgl")){let i=this._getLinkStatus();this._reportLinkStatus(i);return}Lr.once(1,"RenderPipeline linking is asynchronous")(),await this._waitForLinkComplete(),Lr.info(2,`RenderPipeline ${this.id} - async linking complete: ${this.linkStatus}`)();let t=this._getLinkStatus();this._reportLinkStatus(t)}async _reportLinkStatus(e){switch(e){case"success":return;default:let t=e==="link-error"?"Link error":"Validation error";switch(this.vs.compilationStatus){case"error":throw this.vs.debugShader(),new Error(`${this} ${t} during compilation of ${this.vs}`);case"pending":await this.vs.asyncCompilationStatus,this.vs.debugShader();break;case"success":break}switch(this.fs?.compilationStatus){case"error":throw this.fs.debugShader(),new Error(`${this} ${t} during compilation of ${this.fs}`);case"pending":await this.fs.asyncCompilationStatus,this.fs.debugShader();break;case"success":break}let i=this.device.gl.getProgramInfoLog(this.handle);this.device.reportError(new Error(`${t} during ${e}: ${i}`),this)(),this.device.debug()}}_getLinkStatus(){let{gl:e}=this.device;return e.getProgramParameter(this.handle,35714)?(this._initializeSamplerUniforms(),e.validateProgram(this.handle),e.getProgramParameter(this.handle,35715)?(this.linkStatus="success","success"):(this.linkStatus="error","validation-error")):(this.linkStatus="error","link-error")}_initializeSamplerUniforms(){let{gl:e}=this.device;e.useProgram(this.handle);let t=0,i=e.getProgramParameter(this.handle,35718);for(let n=0;n1){let s=Int32Array.from({length:t.size},(c,f)=>n+f);return o.uniform1iv(e,s),n+t.size}return o.uniform1i(e,n),n+1}async _waitForLinkComplete(){let e=async n=>await new Promise(o=>setTimeout(o,n));if(!this.device.features.has("compilation-status-async-webgl")){await e(10);return}let{gl:i}=this.device;for(;;){if(i.getProgramParameter(this.handle,37297))return;await e(10)}}}});function fFe(r,e){let t=e.sourceBuffer,i=e.destinationBuffer;r.gl.bindBuffer(36662,t.handle),r.gl.bindBuffer(36663,i.handle),r.gl.copyBufferSubData(36662,36663,e.sourceOffset??0,e.destinationOffset??0,e.size),r.gl.bindBuffer(36662,null),r.gl.bindBuffer(36663,null)}function dFe(r,e){throw new Error("copyBufferToTexture is not supported in WebGL")}function pFe(r,e){let{sourceTexture:t,mipLevel:i=0,aspect:n="all",width:o=e.sourceTexture.width,height:s=e.sourceTexture.height,depthOrArrayLayers:c,origin:f=[0,0,0],destinationBuffer:g,byteOffset:y=0,bytesPerRow:M,rowsPerImage:F}=e;if(t instanceof oo){t.readBuffer({x:f[0]??0,y:f[1]??0,z:f[2]??0,width:o,height:s,depthOrArrayLayers:c,mipLevel:i,aspect:n,byteOffset:y},g);return}if(n!=="all")throw new Error("aspect not supported in WebGL");if(i!==0||c!==void 0||M||F)throw new Error("not implemented");let{framebuffer:k,destroyFramebuffer:U}=hse(t),K;try{let ee=g,he=o||k.width,xe=s||k.height,me=f1(k.colorAttachments[0]),Ce=Ck(me.texture.props.format),Oe=Ce.format,ne=Ce.type;r.gl.bindBuffer(35051,ee.handle),K=r.gl.bindFramebuffer(36160,k.handle),r.gl.readPixels(f[0],f[1],he,xe,Oe,ne,y)}finally{r.gl.bindBuffer(35051,null),K!==void 0&&r.gl.bindFramebuffer(36160,K),U&&k.destroy()}}function mFe(r,e){let{sourceTexture:t,destinationMipLevel:i=0,origin:n=[0,0],destinationOrigin:o=[0,0,0],destinationTexture:s}=e,{width:c=e.destinationTexture.width,height:f=e.destinationTexture.height}=e,{framebuffer:g,destroyFramebuffer:y}=hse(t),[M=0,F=0]=n,[k,U,K]=o,ee=r.gl.bindFramebuffer(36160,g.handle),he,xe;if(s instanceof a_)he=s,c=Number.isFinite(c)?c:he.width,f=Number.isFinite(f)?f:he.height,he._bind(0),xe=he.glTarget;else throw new Error("invalid destination");switch(xe){case 3553:case 34067:r.gl.copyTexSubImage2D(xe,i,k,U,M,F,c,f);break;case 35866:case 32879:r.gl.copyTexSubImage3D(xe,i,k,U,K,M,F,c,f);break;default:}he&&he._unbind(),r.gl.bindFramebuffer(36160,ee),y&&g.destroy()}function hse(r){if(r instanceof oo){let{width:e,height:t,id:i}=r;return{framebuffer:r.device.createFramebuffer({id:`framebuffer-for-${i}`,width:e,height:t,colorAttachments:[r]}),destroyFramebuffer:!0}}return{framebuffer:r,destroyFramebuffer:!1}}var Zk,fse=Nt(()=>{Eo();aS();Gk();Zk=class extends DT{device;handle=null;commands=[];constructor(e,t={}){super(e,t),this.device=e}_executeCommands(e=this.commands){for(let t of e)switch(t.name){case"copy-buffer-to-buffer":fFe(this.device,t.options);break;case"copy-buffer-to-texture":dFe(this.device,t.options);break;case"copy-texture-to-buffer":pFe(this.device,t.options);break;case"copy-texture-to-texture":mFe(this.device,t.options);break;default:throw new Error(t.name)}}}});var gFe,Yk,dse=Nt(()=>{Eo();Uk();nS();gFe=[1,2,4,8],Yk=class extends cy{device;handle=null;glParameters={};constructor(e,t){super(e,t),this.device=e;let i=this.props.framebuffer,n=!i||i.handle===null;n&&e.getDefaultCanvasContext()._resizeDrawingBufferIfNeeded();let o;if(!t?.parameters?.viewport)if(n){let[s,c]=e.getDefaultCanvasContext().getDrawingBufferSize();o=[0,0,s,c]}else{let{width:s,height:c}=i;o=[0,0,s,c]}if(this.device.pushState(),this.setParameters({viewport:o,...this.props.parameters}),n)this.device.gl.drawBuffers([1029]);else{let s=i.colorAttachments.map((c,f)=>36064+f);this.device.gl.drawBuffers(s)}this.clear(),this.props.timestampQuerySet&&this.props.beginTimestampIndex!==void 0&&this.props.timestampQuerySet.writeTimestamp(this.props.beginTimestampIndex)}end(){this.destroyed||(this.props.timestampQuerySet&&this.props.endTimestampIndex!==void 0&&this.props.timestampQuerySet.writeTimestamp(this.props.endTimestampIndex),this.device.popState(),this.destroy())}pushDebugGroup(e){}popDebugGroup(){}insertDebugMarker(e){}setParameters(e={}){let t={...this.glParameters};t.framebuffer=this.props.framebuffer||null,this.props.depthReadOnly&&(t.depthMask=!this.props.depthReadOnly),t.stencilMask=this.props.stencilReadOnly?0:1,t[35977]=this.props.discard,e.viewport&&(e.viewport.length>=6?(t.viewport=e.viewport.slice(0,4),t.depthRange=[e.viewport[4],e.viewport[5]]):t.viewport=e.viewport),e.scissorRect&&(t.scissorTest=!0,t.scissor=e.scissorRect),e.blendConstant&&(t.blendColor=e.blendConstant),e.stencilReference!==void 0&&(t[2967]=e.stencilReference,t[36003]=e.stencilReference),"colorMask"in e&&(t.colorMask=gFe.map(i=>Boolean(i&e.colorMask))),this.glParameters=t,B0(this.device.gl,t)}beginOcclusionQuery(e){this.props.occlusionQuerySet?.beginOcclusionQuery()}endOcclusionQuery(){this.props.occlusionQuerySet?.endOcclusionQuery()}clear(){let e={...this.glParameters},t=0;this.props.clearColors&&this.props.clearColors.forEach((i,n)=>{i&&this.clearColorBuffer(n,i)}),this.props.clearColor!==!1&&this.props.clearColors===void 0&&(t|=16384,e.clearColor=this.props.clearColor),this.props.clearDepth!==!1&&(t|=256,e.clearDepth=this.props.clearDepth),this.props.clearStencil!==!1&&(t|=1024,e.clearStencil=this.props.clearStencil),t!==0&&o_(this.device.gl,e,()=>{this.device.gl.clear(t)})}clearColorBuffer(e=0,t=[0,0,0,0]){o_(this.device.gl,{framebuffer:this.props.framebuffer},()=>{switch(t.constructor){case Int8Array:case Int16Array:case Int32Array:this.device.gl.clearBufferiv(6144,e,t);break;case Uint8Array:case Uint8ClampedArray:case Uint16Array:case Uint32Array:this.device.gl.clearBufferuiv(6144,e,t);break;case Float32Array:this.device.gl.clearBufferfv(6144,e,t);break;default:throw new Error("clearColorBuffer: color must be typed array")}})}}});var EP,pse=Nt(()=>{Eo();fse();dse();EP=class extends LT{device;handle=null;commandBuffer;constructor(e,t){super(e,t),this.device=e,this.commandBuffer=new Zk(e,{id:`${this.props.id}-command-buffer`})}destroy(){this.destroyResource()}finish(e){return e?.id&&this.commandBuffer.id!==e.id&&(this.commandBuffer.id=e.id,this.commandBuffer.props.id=e.id),this.destroy(),this.commandBuffer}beginRenderPass(e={}){return new Yk(this.device,this._applyTimeProfilingToPassProps(e))}beginComputePass(e={}){throw new Error("ComputePass not supported in WebGL")}copyBufferToBuffer(e){this.commandBuffer.commands.push({name:"copy-buffer-to-buffer",options:e})}copyBufferToTexture(e){this.commandBuffer.commands.push({name:"copy-buffer-to-texture",options:e})}copyTextureToBuffer(e){this.commandBuffer.commands.push({name:"copy-texture-to-buffer",options:e})}copyTextureToTexture(e){this.commandBuffer.commands.push({name:"copy-texture-to-texture",options:e})}pushDebugGroup(e){}popDebugGroup(){}insertDebugMarker(e){}resolveQuerySet(e,t,i){throw new Error("resolveQuerySet is not supported in WebGL")}writeTimestamp(e,t){e.writeTimestamp(t)}}});function mse(r){let{target:e,source:t,start:i=0,count:n=1}=r,o=t.length,s=n*o,c=0;for(let f=i;c{});function AFe(r){return Array.isArray(r)?new Float32Array(r):r}function _Fe(r,e){if(!r||!e||r.length!==e.length||r.constructor!==e.constructor)return!1;for(let t=0;t{Eo();_v();u9();gse();lS=class extends FT{get[Symbol.toStringTag](){return"VertexArray"}device;handle;buffer=null;bufferValue=null;static isConstantAttributeZeroSupported(e){return t8()==="Chrome"}constructor(e,t){super(e,t),this.device=e,this.handle=this.device.gl.createVertexArray()}destroy(){super.destroy(),this.buffer&&this.buffer?.destroy(),this.handle&&(this.device.gl.deleteVertexArray(this.handle),this.handle=void 0)}setIndexBuffer(e){let t=e;if(t&&t.glTarget!==34963)throw new Error("Use .setBuffer()");this.device.gl.bindVertexArray(this.handle),this.device.gl.bindBuffer(34963,t?t.handle:null),this.indexBuffer=t,this.device.gl.bindVertexArray(null)}setBuffer(e,t){let i=t;if(i.glTarget===34963)throw new Error("Use .setIndexBuffer()");let{size:n,type:o,stride:s,offset:c,normalized:f,integer:g,divisor:y}=this._getAccessor(e);this.device.gl.bindVertexArray(this.handle),this.device.gl.bindBuffer(34962,i.handle),g?this.device.gl.vertexAttribIPointer(e,n,o,s,c):this.device.gl.vertexAttribPointer(e,n,o,f,s,c),this.device.gl.bindBuffer(34962,null),this.device.gl.enableVertexAttribArray(e),this.device.gl.vertexAttribDivisor(e,y||0),this.attributes[e]=i,this.device.gl.bindVertexArray(null)}setConstantWebGL(e,t){this._enable(e,!1),this.attributes[e]=t}bindBeforeRender(){this.device.gl.bindVertexArray(this.handle),this._applyConstantAttributes()}unbindAfterRender(){this.device.gl.bindVertexArray(null)}_applyConstantAttributes(){for(let e=0;e{Eo();CP();T9();Kk=class extends kT{device;gl;handle;layout;buffers={};unusedBuffers={};bindOnUse=!0;_bound=!1;constructor(e,t){super(e,t),this.device=e,this.gl=e.gl,this.handle=this.props.handle||this.gl.createTransformFeedback(),this.layout=this.props.layout,t.buffers&&this.setBuffers(t.buffers),Object.seal(this)}destroy(){this.gl.deleteTransformFeedback(this.handle),super.destroy()}begin(e="point-list"){this.gl.bindTransformFeedback(36386,this.handle),this.bindOnUse&&this._bindBuffers(),this.gl.beginTransformFeedback(ese(e))}end(){this.gl.endTransformFeedback(),this.bindOnUse&&this._unbindBuffers(),this.gl.bindTransformFeedback(36386,null)}setBuffers(e){this.buffers={},this.unusedBuffers={},this.bind(()=>{for(let[t,i]of Object.entries(e))this.setBuffer(t,i)})}setBuffer(e,t){let i=this._getVaryingIndex(e),{buffer:n,byteLength:o,byteOffset:s}=this._getBufferRange(t);if(i<0){this.unusedBuffers[e]=n,Lr.warn(`${this.id} unusedBuffers varying buffer ${e}`)();return}this.buffers[i]={buffer:n,byteLength:o,byteOffset:s},this.bindOnUse||this._bindBuffer(i,n,s,o)}getBuffer(e){if(_se(e))return this.buffers[e]||null;let t=this._getVaryingIndex(e);return this.buffers[t]??null}bind(e=this.handle){if(typeof e!="function")return this.gl.bindTransformFeedback(36386,e),this;let t;return this._bound?t=e():(this.gl.bindTransformFeedback(36386,this.handle),this._bound=!0,t=e(),this._bound=!1,this.gl.bindTransformFeedback(36386,null)),t}unbind(){this.bind(null)}_getBufferRange(e){if(e instanceof O0)return{buffer:e,byteOffset:0,byteLength:e.byteLength};let{buffer:t,byteOffset:i=0,byteLength:n=e.buffer.byteLength}=e;return{buffer:t,byteOffset:i,byteLength:n}}_getVaryingIndex(e){if(_se(e))return Number(e);for(let t of this.layout.varyings||[])if(e===t.name)return t.location;return-1}_bindBuffers(){for(let[e,t]of Object.entries(this.buffers)){let{buffer:i,byteLength:n,byteOffset:o}=this._getBufferRange(t);this._bindBuffer(Number(e),i,o,n)}}_unbindBuffers(){for(let e in this.buffers)this.gl.bindBufferBase(35982,Number(e),null)}_bindBuffer(e,t,i=0,n){let o=t&&t.handle;!o||n===void 0?this.gl.bindBufferBase(35982,e,o):this.gl.bindBufferRange(35982,e,o,i,n)}}});var Qk,vse=Nt(()=>{Eo();Qk=class extends NT{device;handle;_timestampPairs=[];_pendingReads=new Set;_occlusionQuery=null;_occlusionActive=!1;get[Symbol.toStringTag](){return"QuerySet"}constructor(e,t){if(super(e,t),this.device=e,t.type==="timestamp"){if(t.count<2)throw new Error("Timestamp QuerySet requires at least two query slots");this._timestampPairs=new Array(Math.ceil(t.count/2)).fill(null).map(()=>({activeQuery:null,completedQueries:[]})),this.handle=null}else{if(t.count>1)throw new Error("WebGL occlusion QuerySet can only have one value");let i=this.device.gl.createQuery();if(!i)throw new Error("WebGL query not supported");this.handle=i}Object.seal(this)}destroy(){if(!this.destroyed){this.handle&&this.device.gl.deleteQuery(this.handle);for(let e of this._timestampPairs){e.activeQuery&&(this._cancelPendingQuery(e.activeQuery),this.device.gl.deleteQuery(e.activeQuery.handle));for(let t of e.completedQueries)this._cancelPendingQuery(t),this.device.gl.deleteQuery(t.handle)}this._occlusionQuery&&(this._cancelPendingQuery(this._occlusionQuery),this.device.gl.deleteQuery(this._occlusionQuery.handle));for(let e of Array.from(this._pendingReads))this._cancelPendingQuery(e);this.destroyResource()}}isResultAvailable(e){return this.props.type==="timestamp"?e===void 0?this._timestampPairs.some((t,i)=>this._isTimestampPairAvailable(i)):this._isTimestampPairAvailable(this._getTimestampPairIndex(e)):this._occlusionQuery?this._pollQueryAvailability(this._occlusionQuery):!1}async readResults(e){let t=e?.firstQuery||0,i=e?.queryCount||this.props.count-t;if(this._validateRange(t,i),this.props.type==="timestamp"){let n=new Array(i).fill(0n),o=Math.floor(t/2),s=Math.floor((t+i-1)/2);for(let c=o;c<=s;c++){let f=await this._consumeTimestampPairResult(c),g=c*2,y=g+1;g>=t&&g=t&&y=this.props.count||t<=e)throw new Error("Timestamp duration range is out of bounds");if(e%2!==0||t!==e+1)throw new Error("WebGL timestamp durations require adjacent even/odd query indices");let i=await this._consumeTimestampPairResult(this._getTimestampPairIndex(e));return Number(i)/1e6}beginOcclusionQuery(){if(this.props.type!=="occlusion")throw new Error("Occlusion queries require an occlusion QuerySet");if(!this.handle)throw new Error("WebGL occlusion query is not available");if(this._occlusionActive)throw new Error("Occlusion query is already active");this.device.gl.beginQuery(35887,this.handle),this._occlusionQuery={handle:this.handle,promise:null,result:null,disjoint:!1,cancelled:!1,pollRequestId:null,resolve:null,reject:null},this._occlusionActive=!0}endOcclusionQuery(){if(!this._occlusionActive)throw new Error("Occlusion query is not active");this.device.gl.endQuery(35887),this._occlusionActive=!1}writeTimestamp(e){if(this.props.type!=="timestamp")throw new Error("Timestamp writes require a timestamp QuerySet");let t=this._getTimestampPairIndex(e),i=this._timestampPairs[t];if(e%2===0){if(i.activeQuery)throw new Error("Timestamp query pair is already active");let n=this.device.gl.createQuery();if(!n)throw new Error("WebGL query not supported");let o={handle:n,promise:null,result:null,disjoint:!1,cancelled:!1,pollRequestId:null,resolve:null,reject:null};this.device.gl.beginQuery(35007,n),i.activeQuery=o;return}if(!i.activeQuery)throw new Error("Timestamp query pair was ended before it was started");this.device.gl.endQuery(35007),i.completedQueries.push(i.activeQuery),i.activeQuery=null}_validateRange(e,t){if(e<0||t<0||e+t>this.props.count)throw new Error("Query read range is out of bounds")}_getTimestampPairIndex(e){if(e<0||e>=this.props.count)throw new Error("Query index is out of bounds");return Math.floor(e/2)}_isTimestampPairAvailable(e){let t=this._timestampPairs[e];return!t||t.completedQueries.length===0?!1:this._pollQueryAvailability(t.completedQueries[0])}_pollQueryAvailability(e){if(e.cancelled||this.destroyed)return e.result=0n,!0;if(e.result!==null||e.disjoint)return!0;if(!this.device.gl.getQueryParameter(e.handle,34919))return!1;let i=Boolean(this.device.gl.getParameter(36795));return e.disjoint=i,e.result=i?0n:BigInt(this.device.gl.getQueryParameter(e.handle,34918)),!0}async _consumeTimestampPairResult(e){let t=this._timestampPairs[e];if(!t||t.completedQueries.length===0)throw new Error("Timestamp query pair has no completed result");let i=t.completedQueries.shift();try{return await this._consumeQueryResult(i)}finally{this.device.gl.deleteQuery(i.handle)}}_consumeQueryResult(e){return e.promise||(this._pendingReads.add(e),e.promise=new Promise((t,i)=>{e.resolve=t,e.reject=i;let n=()=>{if(e.pollRequestId=null,e.cancelled||this.destroyed){this._pendingReads.delete(e),e.promise=null,e.resolve=null,e.reject=null,t(0n);return}if(!this._pollQueryAvailability(e)){e.pollRequestId=this._requestAnimationFrame(n);return}this._pendingReads.delete(e),e.promise=null,e.resolve=null,e.reject=null,e.disjoint?i(new Error("GPU timestamp query was invalidated by a disjoint event")):t(e.result||0n)};n()})),e.promise}_cancelPendingQuery(e){if(this._pendingReads.delete(e),e.cancelled=!0,e.pollRequestId!==null&&(this._cancelAnimationFrame(e.pollRequestId),e.pollRequestId=null),e.resolve){let t=e.resolve;e.promise=null,e.resolve=null,e.reject=null,t(0n)}}_requestAnimationFrame(e){return requestAnimationFrame(e)}_cancelAnimationFrame(e){cancelAnimationFrame(e)}}});var Jk,xse=Nt(()=>{Eo();Jk=class extends zT{device;gl;handle;signaled;_signaled=!1;constructor(e,t={}){super(e,{}),this.device=e,this.gl=e.gl;let i=this.props.handle||this.gl.fenceSync(this.gl.SYNC_GPU_COMMANDS_COMPLETE,0);if(!i)throw new Error("Failed to create WebGL fence");this.handle=i,this.signaled=new Promise(n=>{let o=()=>{let s=this.gl.clientWaitSync(this.handle,0,0);s===this.gl.ALREADY_SIGNALED||s===this.gl.CONDITION_SATISFIED?(this._signaled=!0,n()):setTimeout(o,1)};o()})}isSignaled(){if(this._signaled)return!0;let e=this.gl.getSyncParameter(this.handle,this.gl.SYNC_STATUS);return this._signaled=e===this.gl.SIGNALED,this._signaled}destroy(){this.destroyed||this.gl.deleteSync(this.handle)}}});function S9(r){switch(r){case 6406:case 33326:case 6403:case 36244:return 1;case 33339:case 33340:case 33328:case 33320:case 33319:return 2;case 6407:case 36248:case 34837:return 3;case 6408:case 36249:case 34836:return 4;default:return 0}}function bse(r){switch(r){case 5121:return 1;case 33635:case 32819:case 32820:return 2;case 5126:return 4;default:return 0}}var wse=Nt(()=>{});function Tse(r,e){let{sourceX:t=0,sourceY:i=0,sourceAttachment:n=0}=e||{},{target:o=null,sourceWidth:s,sourceHeight:c,sourceDepth:f,sourceFormat:g,sourceType:y}=e||{},{framebuffer:M,deleteFramebuffer:F}=Ese(r),{gl:k,handle:U}=M;s||=M.width,c||=M.height;let K=M.colorAttachments[n]?.texture;if(!K)throw new Error(`Invalid framebuffer attachment ${n}`);f=K?.depth||1,g||=K?.glFormat||6408,y||=K?.glType||5121,o=vFe(o,y,g,s,c,f);let ee=up.getDataType(o);y=y||nse(ee);let he=k.bindFramebuffer(36160,U);return k.readBuffer(36064+n),k.readPixels(t,i,s,c,g,y,o),k.readBuffer(36064),k.bindFramebuffer(36160,he||null),F&&M.destroy(),o}function Sse(r,e){let{target:t,sourceX:i=0,sourceY:n=0,sourceFormat:o=6408,targetByteOffset:s=0}=e||{},{sourceWidth:c,sourceHeight:f,sourceType:g}=e||{},{framebuffer:y,deleteFramebuffer:M}=Ese(r);c=c||y.width,f=f||y.height;let F=y;g=g||5121;let k=t;if(!k){let K=S9(o),ee=bse(g),he=s+c*f*K*ee;k=F.device.createBuffer({byteLength:he})}let U=r.device.createCommandEncoder();return U.copyTextureToBuffer({sourceTexture:r,width:c,height:f,origin:[i,n],destinationBuffer:k,byteOffset:s}),U.destroy(),M&&y.destroy(),k}function Ese(r){return r instanceof XA?{framebuffer:r,deleteFramebuffer:!1}:{framebuffer:yFe(r),deleteFramebuffer:!0}}function yFe(r,e){let{device:t,width:i,height:n,id:o}=r;return t.createFramebuffer({...e,id:`framebuffer-for-${o}`,width:i,height:n,colorAttachments:[r]})}function vFe(r,e,t,i,n,o){if(r)return r;e||=5121;let s=Vk(e),c=up.getTypedArrayConstructor(s),f=S9(t);return new c(i*n*f)}var Cse=Nt(()=>{Eo();$k();wse();w9()});var E9={};Wa(E9,{WebGLDevice:()=>tm});function xFe(r,e,t){switch(t.length){case 1:r.gl.vertexAttrib1fv(e,t);break;case 2:r.gl.vertexAttrib2fv(e,t);break;case 3:r.gl.vertexAttrib3fv(e,t);break;case 4:r.gl.vertexAttrib4fv(e,t);break;default:}}function bFe(r,e,t){r.gl.vertexAttribI4iv(e,t)}function wFe(r,e,t){r.gl.vertexAttribI4uiv(e,t)}function TFe(r,e){if(!r||!e||r.length!==e.length||r.constructor!==e.constructor)return!1;for(let t=0;t{Eo();c9();Moe();vk();Roe();zoe();Uoe();Voe();joe();i9();n9();aS();Hoe();Lk();$oe();Yoe();Gk();Pk();ise();use();pse();Ase();yse();vse();xse();Cse();nS();Uk();wP();tm=class extends Qp{static getDeviceFromContext(e){return e?e.luma?.device??null:null}type="webgl";handle;features;limits;info;canvasContext;preferredColorFormat="rgba8unorm";preferredDepthFormat="depth24plus";commandEncoder;lost;_resolveContextLost;gl;_constants;extensions;_polyfilled=!1;spectorJS;get[Symbol.toStringTag](){return"WebGLDevice"}toString(){return`${this[Symbol.toStringTag]}(${this.id})`}isVertexFormatSupported(e){switch(e){case"unorm8x4-bgra":return!1;default:return!0}}constructor(e){super({...e,id:e.id||Goe("webgl-device")});let t=Qp._getCanvasContextProps(e);if(!t)throw new Error("WebGLDevice requires props.createCanvasContext to be set");let i=t.canvas?.gl??null,n=tm.getDeviceFromContext(i);if(n)throw new Error(`WebGL context already attached to device ${n.id}`);this.canvasContext=new Rk(this,t),this.lost=new Promise(y=>{this._resolveContextLost=y});let o={...e.webgl};t.alphaMode==="premultiplied"&&(o.premultipliedAlpha=!0),e.powerPreference!==void 0&&(o.powerPreference=e.powerPreference),e.failIfMajorPerformanceCaveat!==void 0&&(o.failIfMajorPerformanceCaveat=e.failIfMajorPerformanceCaveat);let c=this.props._handle||Coe(this.canvasContext.canvas,{onContextLost:y=>this._resolveContextLost?.({reason:"destroyed",message:"Entered sleep mode, or too many apps or browser tabs are using the GPU."}),onContextRestored:y=>console.log("WebGL context restored")},o);if(!c)throw new Error("WebGL context creation failed");if(n=tm.getDeviceFromContext(c),n){if(e._reuseDevices)return Lr.log(1,`Not creating a new Device, instead returning a reference to Device ${n.id} already attached to WebGL context`,n)(),this.canvasContext.destroy(),n._reused=!0,n;throw new Error(`WebGL context already attached to device ${n.id}`)}this.handle=c,this.gl=c,this.spectorJS=foe({...this.props,gl:this.handle});let f=I1(this.handle);f.device=this,f.extensions||(f.extensions={}),this.extensions=f.extensions,this.info=Ioe(this.gl,this.extensions),this.limits=new Ik(this.gl),this.features=new Mk(this.gl,this.extensions,this.props._disabledFeatures),this.props._initializeFeatures&&this.features.initializeFeatures(),new L0(this.gl,{log:(...y)=>Lr.log(1,...y)()}).trackState(this.gl,{copyState:!1}),(e.debug||e.debugWebGL)&&(this.gl=goe(this.gl,{debugWebGL:!0,traceWebGL:e.debugWebGL}),Lr.warn("WebGL debug mode activated. Performance reduced.")()),e.debugWebGL&&(Lr.level=Math.max(Lr.level,1)),this.commandEncoder=new EP(this,{id:`${this}-command-encoder`}),this.canvasContext._startObservers()}destroy(){if(this.commandEncoder?.destroy(),!this.props._reuseDevices&&!this._reused){let e=I1(this.handle);e.device=null}}get isLost(){return this.gl.isContextLost()}createCanvasContext(e){throw new Error("WebGL only supports a single canvas")}createPresentationContext(e){return new Bk(this,e||{})}createBuffer(e){let t=this._normalizeBufferProps(e);return new O0(this,t)}createTexture(e){return new a_(this,e)}createExternalTexture(e){throw new Error("createExternalTexture() not implemented")}createSampler(e){return new zk(this,e)}createShader(e){return new Ok(this,e)}createFramebuffer(e){return new n_(this,e)}createVertexArray(e){return new lS(this,e)}createTransformFeedback(e){return new Kk(this,e)}createQuerySet(e){return new Qk(this,e)}createFence(){return new Jk(this)}createRenderPipeline(e){return new Hk(this,e)}_createSharedRenderPipelineWebGL(e){return new Xk(this,e)}createComputePipeline(e){throw new Error("ComputePipeline not supported in WebGL")}createCommandEncoder(e={}){return new EP(this,e)}submit(e){let t=null;e||({submittedCommandEncoder:t,commandBuffer:e}=this._finalizeDefaultCommandEncoderForSubmit());try{e._executeCommands(),t&&t.resolveTimeProfilingQuerySet().then(()=>{this.commandEncoder._gpuTimeMs=t._gpuTimeMs}).catch(()=>{})}finally{e.destroy()}}_finalizeDefaultCommandEncoderForSubmit(){let e=this.commandEncoder,t=e.finish();return this.commandEncoder.destroy(),this.commandEncoder=this.createCommandEncoder({id:e.props.id,timeProfilingQuerySet:e.getTimeProfilingQuerySet()}),{submittedCommandEncoder:e,commandBuffer:t}}readPixelsToArrayWebGL(e,t){return Tse(e,t)}readPixelsToBufferWebGL(e,t){return Sse(e,t)}setParametersWebGL(e){B0(this.gl,e)}getParametersWebGL(e){return bk(this.gl,e)}withParametersWebGL(e,t){return o_(this.gl,e,t)}resetWebGL(){Lr.warn("WebGLDevice.resetWebGL is deprecated, use only for debugging")(),boe(this.gl)}_getDeviceSpecificTextureFormatCapabilities(e){return Foe(this.gl,e,this.extensions)}loseDevice(){let e=!1,i=this.getExtension("WEBGL_lose_context").WEBGL_lose_context;return i&&(e=!0,i.loseContext()),this._resolveContextLost?.({reason:"destroyed",message:"Application triggered context loss"}),e}pushState(){L0.get(this.gl).push()}popState(){L0.get(this.gl).pop()}getGLKey(e,t){let i=Number(e);for(let n in this.gl)if(this.gl[n]===i)return`GL.${n}`;return t?.emptyIfUnknown?"":String(e)}getGLKeys(e){let t={emptyIfUnknown:!0};return Object.entries(e).reduce((i,[n,o])=>(i[`${n}:${this.getGLKey(n,t)}`]=`${o}:${this.getGLKey(o,t)}`,i),{})}setConstantAttributeWebGL(e,t){let i=this.limits.maxVertexAttributes;this._constants=this._constants||new Array(i).fill(null);let n=this._constants[e];switch(n&&TFe(n,t)&&Lr.info(1,`setConstantAttributeWebGL(${e}) could have been skipped, value unchanged`)(),this._constants[e]=t,t.constructor){case Float32Array:xFe(this,e,t);break;case Int32Array:bFe(this,e,t);break;case Uint32Array:wFe(this,e,t);break;default:throw new Error("constant")}}getExtension(e){return D0(this.gl,e,this.extensions),this.extensions}_setWebGLDebugMetadata(e,t,i){e.luma=t;let n={props:i.spector,id:i.spector.id};e.__SPECTOR_Metadata=n}}});function SFe(r){return typeof WebGL2RenderingContext<"u"&&r instanceof WebGL2RenderingContext?!0:Boolean(r&&typeof r.createVertexArray=="function")}var MP,C9,IP,Mse=Nt(()=>{Eo();coe();i9();n9();MP=1,C9=class extends LI{type="webgl";constructor(){super(),Qp.defaultProps={...Qp.defaultProps,...xk}}enforceWebGL2(e){loe(e)}isSupported(){return typeof WebGL2RenderingContext<"u"}isDeviceHandle(e){return typeof WebGL2RenderingContext<"u"&&e instanceof WebGL2RenderingContext?!0:(typeof WebGLRenderingContext<"u"&&e instanceof WebGLRenderingContext&&Lr.warn("WebGL1 is not supported",e)(),!1)}async attach(e,t={}){let{WebGLDevice:i}=await Promise.resolve().then(()=>(eN(),E9));if(e instanceof i)return e;let n=i.getDeviceFromContext(e);if(n)return n;if(!SFe(e))throw new Error("Invalid WebGL2RenderingContext");let o=t.createCanvasContext===!0?{}:t.createCanvasContext;return new i({...t,_handle:e,createCanvasContext:{canvas:e.canvas,autoResize:!1,...o}})}async create(e={}){let{WebGLDevice:t}=await Promise.resolve().then(()=>(eN(),E9)),i=[];(e.debugWebGL||e.debug)&&i.push(moe()),e.debugSpectorJS&&i.push(hoe(e));let n=await Promise.allSettled(i);for(let o of n)o.status==="rejected"&&Lr.error(`Failed to initialize debug libraries ${o.reason}`)();try{let o=new t(e);Lr.groupCollapsed(MP,`WebGLDevice ${o.id} created`)();let s=`${o._reused?"Reusing":"Created"} device with WebGL2 ${o.props.debug?"debug ":""}context: ${o.info.vendor}, ${o.info.renderer} for canvas: ${o.canvasContext.id}`;return Lr.probe(MP,s)(),Lr.table(MP,o.info)(),o}finally{Lr.groupEnd(MP)(),Lr.info(MP,"%cWebGL call tracing: luma.log.set('debug-webgl') ","color: white; background: blue; padding: 2px 6px; border-radius: 3px;")()}}};IP=new C9});var CP=Nt(()=>{Mse();eN();Lk()});var yae=Vi((W9,q9)=>{(function(r,e){typeof W9=="object"&&typeof q9<"u"?q9.exports=e():typeof define=="function"&&define.amd?define(e):(r=typeof globalThis<"u"?globalThis:r||self,r.maplibregl=e())})(W9,function(){"use strict";var r={},e={};function t(n,o,s){if(e[n]=s,n==="index"){var c="var sharedModule = {}; ("+e.shared+")(sharedModule); ("+e.worker+")(sharedModule);",f={};return e.shared(f),e.index(r,f),typeof window<"u"&&r.setWorkerUrl(window.URL.createObjectURL(new Blob([c],{type:"text/javascript"}))),r}}t("shared",["exports"],function(n){"use strict";function o(d,a,h,A){return new(h||(h=Promise))(function(C,D){function O(re){try{Z(A.next(re))}catch(ce){D(ce)}}function G(re){try{Z(A.throw(re))}catch(ce){D(ce)}}function Z(re){var ce;re.done?C(re.value):(ce=re.value,ce instanceof h?ce:new h(function(de){de(ce)})).then(O,G)}Z((A=A.apply(d,a||[])).next())})}function s(d,a){this.x=d,this.y=a}function c(d){return d&&d.__esModule&&Object.prototype.hasOwnProperty.call(d,"default")?d.default:d}var f,g;typeof SuppressedError=="function"&&SuppressedError,s.prototype={clone(){return new s(this.x,this.y)},add(d){return this.clone()._add(d)},sub(d){return this.clone()._sub(d)},multByPoint(d){return this.clone()._multByPoint(d)},divByPoint(d){return this.clone()._divByPoint(d)},mult(d){return this.clone()._mult(d)},div(d){return this.clone()._div(d)},rotate(d){return this.clone()._rotate(d)},rotateAround(d,a){return this.clone()._rotateAround(d,a)},matMult(d){return this.clone()._matMult(d)},unit(){return this.clone()._unit()},perp(){return this.clone()._perp()},round(){return this.clone()._round()},mag(){return Math.sqrt(this.x*this.x+this.y*this.y)},equals(d){return this.x===d.x&&this.y===d.y},dist(d){return Math.sqrt(this.distSqr(d))},distSqr(d){let a=d.x-this.x,h=d.y-this.y;return a*a+h*h},angle(){return Math.atan2(this.y,this.x)},angleTo(d){return Math.atan2(this.y-d.y,this.x-d.x)},angleWith(d){return this.angleWithSep(d.x,d.y)},angleWithSep(d,a){return Math.atan2(this.x*a-this.y*d,this.x*d+this.y*a)},_matMult(d){let a=d[2]*this.x+d[3]*this.y;return this.x=d[0]*this.x+d[1]*this.y,this.y=a,this},_add(d){return this.x+=d.x,this.y+=d.y,this},_sub(d){return this.x-=d.x,this.y-=d.y,this},_mult(d){return this.x*=d,this.y*=d,this},_div(d){return this.x/=d,this.y/=d,this},_multByPoint(d){return this.x*=d.x,this.y*=d.y,this},_divByPoint(d){return this.x/=d.x,this.y/=d.y,this},_unit(){return this._div(this.mag()),this},_perp(){let d=this.y;return this.y=this.x,this.x=-d,this},_rotate(d){let a=Math.cos(d),h=Math.sin(d),A=h*this.x+a*this.y;return this.x=a*this.x-h*this.y,this.y=A,this},_rotateAround(d,a){let h=Math.cos(d),A=Math.sin(d),C=a.y+A*(this.x-a.x)+h*(this.y-a.y);return this.x=a.x+h*(this.x-a.x)-A*(this.y-a.y),this.y=C,this},_round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},constructor:s},s.convert=function(d){if(d instanceof s)return d;if(Array.isArray(d))return new s(+d[0],+d[1]);if(d.x!==void 0&&d.y!==void 0)return new s(+d.x,+d.y);throw new Error("Expected [x, y] or {x, y} point format")};var y=function(){if(g)return f;function d(a,h,A,C){this.cx=3*a,this.bx=3*(A-a)-this.cx,this.ax=1-this.cx-this.bx,this.cy=3*h,this.by=3*(C-h)-this.cy,this.ay=1-this.cy-this.by,this.p1x=a,this.p1y=h,this.p2x=A,this.p2y=C}return g=1,f=d,d.prototype={sampleCurveX:function(a){return((this.ax*a+this.bx)*a+this.cx)*a},sampleCurveY:function(a){return((this.ay*a+this.by)*a+this.cy)*a},sampleCurveDerivativeX:function(a){return(3*this.ax*a+2*this.bx)*a+this.cx},solveCurveX:function(a,h){if(h===void 0&&(h=1e-6),a<0)return 0;if(a>1)return 1;for(var A=a,C=0;C<8;C++){var D=this.sampleCurveX(A)-a;if(Math.abs(D)D?G=A:Z=A,A=.5*(Z-G)+G;return A},solve:function(a,h){return this.sampleCurveY(this.solveCurveX(a,h))}},f}(),M=c(y);let F,k;function U(){return F==null&&(F=typeof OffscreenCanvas<"u"&&new OffscreenCanvas(1,1).getContext("2d")&&typeof createImageBitmap=="function"),F}function K(){if(k==null&&(k=!1,U())){let a=new OffscreenCanvas(5,5).getContext("2d",{willReadFrequently:!0});if(a){for(let A=0;A<5*5;A++){let C=4*A;a.fillStyle=`rgb(${C},${C+1},${C+2})`,a.fillRect(A%5,Math.floor(A/5),1,1)}let h=a.getImageData(0,0,5,5).data;for(let A=0;A<5*5*4;A++)if(A%4!=3&&h[A]!==A){k=!0;break}}}return k||!1}var ee=1e-6,he=typeof Float32Array<"u"?Float32Array:Array;function xe(){var d=new he(9);return he!=Float32Array&&(d[1]=0,d[2]=0,d[3]=0,d[5]=0,d[6]=0,d[7]=0),d[0]=1,d[4]=1,d[8]=1,d}function me(d){return d[0]=1,d[1]=0,d[2]=0,d[3]=0,d[4]=0,d[5]=1,d[6]=0,d[7]=0,d[8]=0,d[9]=0,d[10]=1,d[11]=0,d[12]=0,d[13]=0,d[14]=0,d[15]=1,d}function Ce(){var d=new he(3);return he!=Float32Array&&(d[0]=0,d[1]=0,d[2]=0),d}function Oe(d){var a=d[0],h=d[1],A=d[2];return Math.sqrt(a*a+h*h+A*A)}function ne(d,a,h){var A=new he(3);return A[0]=d,A[1]=a,A[2]=h,A}function He(d,a,h){return d[0]=a[0]+h[0],d[1]=a[1]+h[1],d[2]=a[2]+h[2],d}function We(d,a,h){return d[0]=a[0]*h,d[1]=a[1]*h,d[2]=a[2]*h,d}function Se(d,a,h){var A=a[0],C=a[1],D=a[2],O=h[0],G=h[1],Z=h[2];return d[0]=C*Z-D*G,d[1]=D*O-A*Z,d[2]=A*G-C*O,d}var ve,Qe=Oe;function wt(d,a,h){var A=a[0],C=a[1],D=a[2],O=a[3];return d[0]=h[0]*A+h[4]*C+h[8]*D+h[12]*O,d[1]=h[1]*A+h[5]*C+h[9]*D+h[13]*O,d[2]=h[2]*A+h[6]*C+h[10]*D+h[14]*O,d[3]=h[3]*A+h[7]*C+h[11]*D+h[15]*O,d}function et(){var d=new he(4);return he!=Float32Array&&(d[0]=0,d[1]=0,d[2]=0),d[3]=1,d}function xt(d,a,h,A){var C=arguments.length>4&&arguments[4]!==void 0?arguments[4]:"zyx",D=Math.PI/360;a*=D,A*=D,h*=D;var O=Math.sin(a),G=Math.cos(a),Z=Math.sin(h),re=Math.cos(h),ce=Math.sin(A),de=Math.cos(A);switch(C){case"xyz":d[0]=O*re*de+G*Z*ce,d[1]=G*Z*de-O*re*ce,d[2]=G*re*ce+O*Z*de,d[3]=G*re*de-O*Z*ce;break;case"xzy":d[0]=O*re*de-G*Z*ce,d[1]=G*Z*de-O*re*ce,d[2]=G*re*ce+O*Z*de,d[3]=G*re*de+O*Z*ce;break;case"yxz":d[0]=O*re*de+G*Z*ce,d[1]=G*Z*de-O*re*ce,d[2]=G*re*ce-O*Z*de,d[3]=G*re*de+O*Z*ce;break;case"yzx":d[0]=O*re*de+G*Z*ce,d[1]=G*Z*de+O*re*ce,d[2]=G*re*ce-O*Z*de,d[3]=G*re*de-O*Z*ce;break;case"zxy":d[0]=O*re*de-G*Z*ce,d[1]=G*Z*de+O*re*ce,d[2]=G*re*ce+O*Z*de,d[3]=G*re*de-O*Z*ce;break;case"zyx":d[0]=O*re*de-G*Z*ce,d[1]=G*Z*de+O*re*ce,d[2]=G*re*ce-O*Z*de,d[3]=G*re*de+O*Z*ce;break;default:throw new Error("Unknown angle order "+C)}return d}function sr(){var d=new he(2);return he!=Float32Array&&(d[0]=0,d[1]=0),d}function Dr(d,a){var h=new he(2);return h[0]=d,h[1]=a,h}Ce(),ve=new he(4),he!=Float32Array&&(ve[0]=0,ve[1]=0,ve[2]=0,ve[3]=0),Ce(),ne(1,0,0),ne(0,1,0),et(),et(),xe(),sr();let hr=8192;function mi(d,a,h){return a*(hr/(d.tileSize*Math.pow(2,h-d.tileID.overscaledZ)))}function bi(d,a){return(d%a+a)%a}function un(d,a,h){return d*(1-h)+a*h}function Yr(d){if(d<=0)return 0;if(d>=1)return 1;let a=d*d,h=a*d;return 4*(d<.5?h:3*(d-a)+h-.75)}function Xi(d,a,h,A){let C=new M(d,a,h,A);return D=>C.solve(D)}let xi=Xi(.25,.1,.25,1);function ii(d,a,h){return Math.min(h,Math.max(a,d))}function Hn(d,a,h){let A=h-a,C=((d-a)%A+A)%A+a;return C===a?h:C}function Ve(d,...a){for(let h of a)for(let A in h)d[A]=h[A];return d}let nt=1;function Ft(d,a,h){let A={};for(let C in d)A[C]=a.call(this,d[C],C,d);return A}function mr(d,a,h){let A={};for(let C in d)a.call(this,d[C],C,d)&&(A[C]=d[C]);return A}function Xr(d){return Array.isArray(d)?d.map(Xr):typeof d=="object"&&d?Ft(d,Xr):d}let zr={};function ln(d){zr[d]||(typeof console<"u"&&console.warn(d),zr[d]=!0)}function Nn(d,a,h){return(h.y-d.y)*(a.x-d.x)>(a.y-d.y)*(h.x-d.x)}function Co(d){return typeof WorkerGlobalScope<"u"&&d!==void 0&&d instanceof WorkerGlobalScope}let Go=null;function is(d){return typeof ImageBitmap<"u"&&d instanceof ImageBitmap}let mo="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=";function zs(d,a,h,A,C){return o(this,void 0,void 0,function*(){if(typeof VideoFrame>"u")throw new Error("VideoFrame not supported");let D=new VideoFrame(d,{timestamp:0});try{let O=D?.format;if(!O||!O.startsWith("BGR")&&!O.startsWith("RGB"))throw new Error(`Unrecognized format ${O}`);let G=O.startsWith("BGR"),Z=new Uint8ClampedArray(A*C*4);if(yield D.copyTo(Z,function(re,ce,de,we,Ee){let De=4*Math.max(-ce,0),ke=(Math.max(0,de)-de)*we*4+De,$e=4*we,st=Math.max(0,ce),kt=Math.max(0,de);return{rect:{x:st,y:kt,width:Math.min(re.width,ce+we)-st,height:Math.min(re.height,de+Ee)-kt},layout:[{offset:ke,stride:$e}]}}(d,a,h,A,C)),G)for(let re=0;re{d.removeEventListener(a,h,A)}}}function ps(d){return d*Math.PI/180}function ws(d){return d/Math.PI*180}let Yt={touchstart:!0,touchmove:!0,touchmoveWindow:!0,touchend:!0,touchcancel:!0},Fe={dblclick:!0,click:!0,mouseover:!0,mouseout:!0,mousedown:!0,mousemove:!0,mousemoveWindow:!0,mouseup:!0,mouseupWindow:!0,contextmenu:!0,wheel:!0},Ne="AbortError";class rt extends Error{constructor(a=Ne){super(a instanceof Error?a.message:a),this.name=Ne,a instanceof Error&&a.stack&&(this.stack=a.stack)}}function Vt(d){return d.name===Ne}let Ht={MAX_PARALLEL_IMAGE_REQUESTS:16,MAX_PARALLEL_IMAGE_REQUESTS_PER_FRAME:8,MAX_TILE_CACHE_ZOOM_LEVELS:5,REGISTERED_PROTOCOLS:{},WORKER_URL:""};function yr(d){return Ht.REGISTERED_PROTOCOLS[d.substring(0,d.indexOf("://"))]}let fr="global-dispatcher";class nr extends Error{constructor(a,h,A,C){super(`AJAXError: ${h} (${a}): ${A}`),this.status=a,this.statusText=h,this.url=A,this.body=C}}let xr=()=>Co(self)?self.worker&&self.worker.referrer:(window.location.protocol==="blob:"?window.parent:window).location.href,ti=function(d,a){if(/:\/\//.test(d.url)&&!/^https?:|^file:/.test(d.url)){let A=yr(d.url);if(A)return A(d,a);if(Co(self)&&self.worker&&self.worker.actor)return self.worker.actor.sendAsync({type:"GR",data:d,targetMapId:fr},a)}if(!(/^file:/.test(h=d.url)||/^file:/.test(xr())&&!/^\w+:/.test(h))){if(fetch&&Request&&AbortController&&Object.prototype.hasOwnProperty.call(Request.prototype,"signal"))return function(A,C){return o(this,void 0,void 0,function*(){let D=new Request(A.url,{method:A.method||"GET",body:A.body,credentials:A.credentials,headers:A.headers,cache:A.cache,referrer:xr(),signal:C.signal}),O,G;A.type!=="json"||D.headers.has("Accept")||D.headers.set("Accept","application/json");try{O=yield fetch(D)}catch(re){throw Vt(re)?re:new nr(0,re.message,A.url,new Blob)}if(!O.ok){let re=yield O.blob();throw new nr(O.status,O.statusText,A.url,re)}G=A.type==="arrayBuffer"||A.type==="image"?O.arrayBuffer():A.type==="json"?O.json():O.text();let Z=yield G;return C.signal.throwIfAborted(),{data:Z,cacheControl:O.headers.get("Cache-Control"),expires:O.headers.get("Expires")}})}(d,a);if(Co(self)&&self.worker&&self.worker.actor)return self.worker.actor.sendAsync({type:"GR",data:d,mustQueue:!0,targetMapId:fr},a)}var h;return function(A,C){return new Promise((D,O)=>{var G;let Z=new XMLHttpRequest;Z.open(A.method||"GET",A.url,!0),A.type!=="arrayBuffer"&&A.type!=="image"||(Z.responseType="arraybuffer");for(let re in A.headers)Z.setRequestHeader(re,A.headers[re]);A.type==="json"&&(Z.responseType="text",!((G=A.headers)===null||G===void 0)&&G.Accept||Z.setRequestHeader("Accept","application/json")),Z.withCredentials=A.credentials==="include",Z.onerror=()=>{O(new Error(Z.statusText))},Z.onload=()=>{if(!C.signal.aborted)if((Z.status>=200&&Z.status<300||Z.status===0)&&Z.response!==null){let re=Z.response;if(A.type==="json")try{re=JSON.parse(Z.response)}catch(ce){return void O(ce)}D({data:re,cacheControl:Z.getResponseHeader("Cache-Control"),expires:Z.getResponseHeader("Expires")})}else{let re=new Blob([Z.response],{type:Z.getResponseHeader("Content-Type")});O(new nr(Z.status,Z.statusText,A.url,re))}},C.signal.addEventListener("abort",()=>{Z.abort(),O(new rt(C.signal.reason))}),Z.send(A.body)})}(d,a)};function Jr(d){if(!d||d.indexOf("://")<=0||d.indexOf("data:image/")===0||d.indexOf("blob:")===0)return!0;let a=new URL(d),h=window.location;return a.protocol===h.protocol&&a.host===h.host}function Wi(d,a,h){h[d]&&h[d].indexOf(a)!==-1||(h[d]=h[d]||[],h[d].push(a))}function fn(d,a,h){if(h&&h[d]){let A=h[d].indexOf(a);A!==-1&&h[d].splice(A,1)}}class Wn{constructor(a,h={}){Ve(this,h),this.type=a}}class bs extends Wn{constructor(a,h={}){super("error",Ve({error:a},h))}}class go{on(a,h){return this._listeners=this._listeners||{},Wi(a,h,this._listeners),{unsubscribe:()=>{this.off(a,h)}}}off(a,h){return fn(a,h,this._listeners),fn(a,h,this._oneTimeListeners),this}once(a,h){return h?(this._oneTimeListeners=this._oneTimeListeners||{},Wi(a,h,this._oneTimeListeners),this):new Promise(A=>this.once(a,A))}fire(a,h){typeof a=="string"&&(a=new Wn(a,h||{}));let A=a.type;if(this.listens(A)){a.target=this;let C=this._listeners&&this._listeners[A]?this._listeners[A].slice():[];for(let G of C)G.call(this,a);let D=this._oneTimeListeners&&this._oneTimeListeners[A]?this._oneTimeListeners[A].slice():[];for(let G of D)fn(A,G,this._oneTimeListeners),G.call(this,a);let O=this._eventedParent;O&&(Ve(a,typeof this._eventedParentData=="function"?this._eventedParentData():this._eventedParentData),O.fire(a))}else a instanceof bs&&console.error(a.error);return this}listens(a){return this._listeners&&this._listeners[a]&&this._listeners[a].length>0||this._oneTimeListeners&&this._oneTimeListeners[a]&&this._oneTimeListeners[a].length>0||this._eventedParent&&this._eventedParent.listens(a)}setEventedParent(a,h){return this._eventedParent=a,this._eventedParentData=h,this}}var wr={$version:8,$root:{version:{required:!0,type:"enum",values:[8]},name:{type:"string"},metadata:{type:"*"},center:{type:"array",value:"number"},centerAltitude:{type:"number"},zoom:{type:"number"},bearing:{type:"number",default:0,period:360,units:"degrees"},pitch:{type:"number",default:0,units:"degrees"},roll:{type:"number",default:0,units:"degrees"},state:{type:"state",default:{}},light:{type:"light"},sky:{type:"sky"},projection:{type:"projection"},terrain:{type:"terrain"},sources:{required:!0,type:"sources"},sprite:{type:"sprite"},glyphs:{type:"string"},"font-faces":{type:"array",value:"fontFaces"},transition:{type:"transition"},layers:{required:!0,type:"array",value:"layer"}},sources:{"*":{type:"source"}},source:["source_vector","source_raster","source_raster_dem","source_geojson","source_video","source_image"],source_vector:{type:{required:!0,type:"enum",values:{vector:{}}},url:{type:"string"},tiles:{type:"array",value:"string"},bounds:{type:"array",value:"number",length:4,default:[-180,-85.051129,180,85.051129]},scheme:{type:"enum",values:{xyz:{},tms:{}},default:"xyz"},minzoom:{type:"number",default:0},maxzoom:{type:"number",default:22},attribution:{type:"string"},promoteId:{type:"promoteId"},volatile:{type:"boolean",default:!1},encoding:{type:"enum",values:{mvt:{},mlt:{}},default:"mvt"},"*":{type:"*"}},source_raster:{type:{required:!0,type:"enum",values:{raster:{}}},url:{type:"string"},tiles:{type:"array",value:"string"},bounds:{type:"array",value:"number",length:4,default:[-180,-85.051129,180,85.051129]},minzoom:{type:"number",default:0},maxzoom:{type:"number",default:22},tileSize:{type:"number",default:512,units:"pixels"},scheme:{type:"enum",values:{xyz:{},tms:{}},default:"xyz"},attribution:{type:"string"},volatile:{type:"boolean",default:!1},"*":{type:"*"}},source_raster_dem:{type:{required:!0,type:"enum",values:{"raster-dem":{}}},url:{type:"string"},tiles:{type:"array",value:"string"},bounds:{type:"array",value:"number",length:4,default:[-180,-85.051129,180,85.051129]},minzoom:{type:"number",default:0},maxzoom:{type:"number",default:22},tileSize:{type:"number",default:512,units:"pixels"},attribution:{type:"string"},encoding:{type:"enum",values:{terrarium:{},mapbox:{},custom:{}},default:"mapbox"},redFactor:{type:"number",default:1},blueFactor:{type:"number",default:1},greenFactor:{type:"number",default:1},baseShift:{type:"number",default:0},volatile:{type:"boolean",default:!1},"*":{type:"*"}},source_geojson:{type:{required:!0,type:"enum",values:{geojson:{}}},data:{required:!0,type:"*"},maxzoom:{type:"number",default:18},attribution:{type:"string"},buffer:{type:"number",default:128,maximum:512,minimum:0},filter:{type:"*"},tolerance:{type:"number",default:.375},cluster:{type:"boolean",default:!1},clusterRadius:{type:"number",default:50,minimum:0},clusterMaxZoom:{type:"number"},clusterMinPoints:{type:"number"},clusterProperties:{type:"*"},lineMetrics:{type:"boolean",default:!1},generateId:{type:"boolean",default:!1},promoteId:{type:"promoteId"}},source_video:{type:{required:!0,type:"enum",values:{video:{}}},urls:{required:!0,type:"array",value:"string"},coordinates:{required:!0,type:"array",length:4,value:{type:"array",length:2,value:"number"}}},source_image:{type:{required:!0,type:"enum",values:{image:{}}},url:{required:!0,type:"string"},coordinates:{required:!0,type:"array",length:4,value:{type:"array",length:2,value:"number"}}},layer:{id:{type:"string",required:!0},type:{type:"enum",values:{fill:{},line:{},symbol:{},circle:{},heatmap:{},"fill-extrusion":{},raster:{},hillshade:{},"color-relief":{},background:{}},required:!0},metadata:{type:"*"},source:{type:"string"},"source-layer":{type:"string"},minzoom:{type:"number",minimum:0,maximum:24},maxzoom:{type:"number",minimum:0,maximum:24},filter:{type:"filter"},layout:{type:"layout"},paint:{type:"paint"}},layout:["layout_fill","layout_line","layout_circle","layout_heatmap","layout_fill-extrusion","layout_symbol","layout_raster","layout_hillshade","layout_color-relief","layout_background"],layout_background:{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_fill:{"fill-sort-key":{type:"number",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_circle:{"circle-sort-key":{type:"number",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_heatmap:{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},"layout_fill-extrusion":{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_line:{"line-cap":{type:"enum",values:{butt:{},round:{},square:{}},default:"butt",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"line-join":{type:"enum",values:{bevel:{},round:{},miter:{}},default:"miter",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"line-miter-limit":{type:"number",default:2,requires:[{"line-join":"miter"}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"line-round-limit":{type:"number",default:1.05,requires:[{"line-join":"round"}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"line-sort-key":{type:"number",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_symbol:{"symbol-placement":{type:"enum",values:{point:{},line:{},"line-center":{}},default:"point",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"symbol-spacing":{type:"number",default:250,minimum:1,units:"pixels",requires:[{"symbol-placement":"line"}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"symbol-avoid-edges":{type:"boolean",default:!1,expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"symbol-sort-key":{type:"number",expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"symbol-z-order":{type:"enum",values:{auto:{},"viewport-y":{},source:{}},default:"auto",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-allow-overlap":{type:"boolean",default:!1,requires:["icon-image",{"!":"icon-overlap"}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-overlap":{type:"enum",values:{never:{},always:{},cooperative:{}},requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-ignore-placement":{type:"boolean",default:!1,requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-optional":{type:"boolean",default:!1,requires:["icon-image","text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-rotation-alignment":{type:"enum",values:{map:{},viewport:{},auto:{}},default:"auto",requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-size":{type:"number",default:1,minimum:0,units:"factor of the original icon size",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-text-fit":{type:"enum",values:{none:{},width:{},height:{},both:{}},default:"none",requires:["icon-image","text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-text-fit-padding":{type:"array",value:"number",length:4,default:[0,0,0,0],units:"pixels",requires:["icon-image","text-field",{"icon-text-fit":["both","width","height"]}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"icon-image":{type:"resolvedImage",tokens:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-rotate":{type:"number",default:0,period:360,units:"degrees",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-padding":{type:"padding",default:[2],units:"pixels",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-keep-upright":{type:"boolean",default:!1,requires:["icon-image",{"icon-rotation-alignment":"map"},{"symbol-placement":["line","line-center"]}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"icon-offset":{type:"array",value:"number",length:2,default:[0,0],requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-anchor":{type:"enum",values:{center:{},left:{},right:{},top:{},bottom:{},"top-left":{},"top-right":{},"bottom-left":{},"bottom-right":{}},default:"center",requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"icon-pitch-alignment":{type:"enum",values:{map:{},viewport:{},auto:{}},default:"auto",requires:["icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-pitch-alignment":{type:"enum",values:{map:{},viewport:{},auto:{}},default:"auto",requires:["text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-rotation-alignment":{type:"enum",values:{map:{},viewport:{},"viewport-glyph":{},auto:{}},default:"auto",requires:["text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-field":{type:"formatted",default:"",tokens:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-font":{type:"array",value:"string",default:["Open Sans Regular","Arial Unicode MS Regular"],requires:["text-field"],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-size":{type:"number",default:16,minimum:0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-max-width":{type:"number",default:10,minimum:0,units:"ems",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-line-height":{type:"number",default:1.2,units:"ems",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"text-letter-spacing":{type:"number",default:0,units:"ems",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-justify":{type:"enum",values:{auto:{},left:{},center:{},right:{}},default:"center",requires:["text-field"],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-radial-offset":{type:"number",units:"ems",default:0,requires:["text-field"],"property-type":"data-driven",expression:{interpolated:!0,parameters:["zoom","feature"]}},"text-variable-anchor":{type:"array",value:"enum",values:{center:{},left:{},right:{},top:{},bottom:{},"top-left":{},"top-right":{},"bottom-left":{},"bottom-right":{}},requires:["text-field",{"symbol-placement":["point"]}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-variable-anchor-offset":{type:"variableAnchorOffsetCollection",requires:["text-field",{"symbol-placement":["point"]}],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-anchor":{type:"enum",values:{center:{},left:{},right:{},top:{},bottom:{},"top-left":{},"top-right":{},"bottom-left":{},"bottom-right":{}},default:"center",requires:["text-field",{"!":"text-variable-anchor"}],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-max-angle":{type:"number",default:45,units:"degrees",requires:["text-field",{"symbol-placement":["line","line-center"]}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"text-writing-mode":{type:"array",value:"enum",values:{horizontal:{},vertical:{}},requires:["text-field",{"symbol-placement":["point"]}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-rotate":{type:"number",default:0,period:360,units:"degrees",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-padding":{type:"number",default:2,minimum:0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"text-keep-upright":{type:"boolean",default:!0,requires:["text-field",{"text-rotation-alignment":"map"},{"symbol-placement":["line","line-center"]}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-transform":{type:"enum",values:{none:{},uppercase:{},lowercase:{}},default:"none",requires:["text-field"],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-offset":{type:"array",value:"number",units:"ems",length:2,default:[0,0],requires:["text-field",{"!":"text-radial-offset"}],expression:{interpolated:!0,parameters:["zoom","feature"]},"property-type":"data-driven"},"text-allow-overlap":{type:"boolean",default:!1,requires:["text-field",{"!":"text-overlap"}],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-overlap":{type:"enum",values:{never:{},always:{},cooperative:{}},requires:["text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-ignore-placement":{type:"boolean",default:!1,requires:["text-field"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-optional":{type:"boolean",default:!1,requires:["text-field","icon-image"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_raster:{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},layout_hillshade:{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},"layout_color-relief":{visibility:{type:"enum",values:{visible:{},none:{}},default:"visible","property-type":"constant"}},filter:{type:"array",value:"*"},filter_operator:{type:"enum",values:{"==":{},"!=":{},">":{},">=":{},"<":{},"<=":{},in:{},"!in":{},all:{},any:{},none:{},has:{},"!has":{}}},geometry_type:{type:"enum",values:{Point:{},LineString:{},Polygon:{}}},function:{expression:{type:"expression"},stops:{type:"array",value:"function_stop"},base:{type:"number",default:1,minimum:0},property:{type:"string",default:"$zoom"},type:{type:"enum",values:{identity:{},exponential:{},interval:{},categorical:{}},default:"exponential"},colorSpace:{type:"enum",values:{rgb:{},lab:{},hcl:{}},default:"rgb"},default:{type:"*",required:!1}},function_stop:{type:"array",minimum:0,maximum:24,value:["number","color"],length:2},expression:{type:"array",value:"*",minimum:1},light:{anchor:{type:"enum",default:"viewport",values:{map:{},viewport:{}},"property-type":"data-constant",transition:!1,expression:{interpolated:!1,parameters:["zoom"]}},position:{type:"array",default:[1.15,210,30],length:3,value:"number","property-type":"data-constant",transition:!0,expression:{interpolated:!0,parameters:["zoom"]}},color:{type:"color","property-type":"data-constant",default:"#ffffff",expression:{interpolated:!0,parameters:["zoom"]},transition:!0},intensity:{type:"number","property-type":"data-constant",default:.5,minimum:0,maximum:1,expression:{interpolated:!0,parameters:["zoom"]},transition:!0}},sky:{"sky-color":{type:"color","property-type":"data-constant",default:"#88C6FC",expression:{interpolated:!0,parameters:["zoom"]},transition:!0},"horizon-color":{type:"color","property-type":"data-constant",default:"#ffffff",expression:{interpolated:!0,parameters:["zoom"]},transition:!0},"fog-color":{type:"color","property-type":"data-constant",default:"#ffffff",expression:{interpolated:!0,parameters:["zoom"]},transition:!0},"fog-ground-blend":{type:"number","property-type":"data-constant",default:.5,minimum:0,maximum:1,expression:{interpolated:!0,parameters:["zoom"]},transition:!0},"horizon-fog-blend":{type:"number","property-type":"data-constant",default:.8,minimum:0,maximum:1,expression:{interpolated:!0,parameters:["zoom"]},transition:!0},"sky-horizon-blend":{type:"number","property-type":"data-constant",default:.8,minimum:0,maximum:1,expression:{interpolated:!0,parameters:["zoom"]},transition:!0},"atmosphere-blend":{type:"number","property-type":"data-constant",default:.8,minimum:0,maximum:1,expression:{interpolated:!0,parameters:["zoom"]},transition:!0}},terrain:{source:{type:"string",required:!0},exaggeration:{type:"number",minimum:0,default:1}},projection:{type:{type:"projectionDefinition",default:"mercator","property-type":"data-constant",transition:!1,expression:{interpolated:!0,parameters:["zoom"]}}},paint:["paint_fill","paint_line","paint_circle","paint_heatmap","paint_fill-extrusion","paint_symbol","paint_raster","paint_hillshade","paint_color-relief","paint_background"],paint_fill:{"fill-antialias":{type:"boolean",default:!0,expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"fill-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-color":{type:"color",default:"#000000",transition:!0,requires:[{"!":"fill-pattern"}],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-outline-color":{type:"color",transition:!0,requires:[{"!":"fill-pattern"},{"fill-antialias":!0}],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"fill-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["fill-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"fill-pattern":{type:"resolvedImage",transition:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"cross-faded-data-driven"}},"paint_fill-extrusion":{"fill-extrusion-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"fill-extrusion-color":{type:"color",default:"#000000",transition:!0,requires:[{"!":"fill-extrusion-pattern"}],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-extrusion-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"fill-extrusion-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["fill-extrusion-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"fill-extrusion-pattern":{type:"resolvedImage",transition:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"cross-faded-data-driven"},"fill-extrusion-height":{type:"number",default:0,minimum:0,units:"meters",transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-extrusion-base":{type:"number",default:0,minimum:0,units:"meters",transition:!0,requires:["fill-extrusion-height"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"fill-extrusion-vertical-gradient":{type:"boolean",default:!0,transition:!1,expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"}},paint_line:{"line-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-color":{type:"color",default:"#000000",transition:!0,requires:[{"!":"line-pattern"}],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"line-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["line-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"line-width":{type:"number",default:1,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-gap-width":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-offset":{type:"number",default:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-blur":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"line-dasharray":{type:"array",value:"number",minimum:0,transition:!0,units:"line widths",requires:[{"!":"line-pattern"}],expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"cross-faded-data-driven"},"line-pattern":{type:"resolvedImage",transition:!0,expression:{interpolated:!1,parameters:["zoom","feature"]},"property-type":"cross-faded-data-driven"},"line-gradient":{type:"color",transition:!1,requires:[{"!":"line-dasharray"},{"!":"line-pattern"},{source:"geojson",has:{lineMetrics:!0}}],expression:{interpolated:!0,parameters:["line-progress"]},"property-type":"color-ramp"}},paint_circle:{"circle-radius":{type:"number",default:5,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-color":{type:"color",default:"#000000",transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-blur":{type:"number",default:0,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"circle-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["circle-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"circle-pitch-scale":{type:"enum",values:{map:{},viewport:{}},default:"map",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"circle-pitch-alignment":{type:"enum",values:{map:{},viewport:{}},default:"viewport",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"circle-stroke-width":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-stroke-color":{type:"color",default:"#000000",transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"circle-stroke-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"}},paint_heatmap:{"heatmap-radius":{type:"number",default:30,minimum:1,transition:!0,units:"pixels",expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"heatmap-weight":{type:"number",default:1,minimum:0,transition:!1,expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"heatmap-intensity":{type:"number",default:1,minimum:0,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"heatmap-color":{type:"color",default:["interpolate",["linear"],["heatmap-density"],0,"rgba(0, 0, 255, 0)",.1,"royalblue",.3,"cyan",.5,"lime",.7,"yellow",1,"red"],transition:!1,expression:{interpolated:!0,parameters:["heatmap-density"]},"property-type":"color-ramp"},"heatmap-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"}},paint_symbol:{"icon-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-color":{type:"color",default:"#000000",transition:!0,requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-halo-color":{type:"color",default:"rgba(0, 0, 0, 0)",transition:!0,requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-halo-width":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-halo-blur":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"icon-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",requires:["icon-image"],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"icon-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["icon-image","icon-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"text-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-color":{type:"color",default:"#000000",transition:!0,overridable:!0,requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-halo-color":{type:"color",default:"rgba(0, 0, 0, 0)",transition:!0,requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-halo-width":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-halo-blur":{type:"number",default:0,minimum:0,transition:!0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom","feature","feature-state"]},"property-type":"data-driven"},"text-translate":{type:"array",value:"number",length:2,default:[0,0],transition:!0,units:"pixels",requires:["text-field"],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"text-translate-anchor":{type:"enum",values:{map:{},viewport:{}},default:"map",requires:["text-field","text-translate"],expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"}},paint_raster:{"raster-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-hue-rotate":{type:"number",default:0,period:360,transition:!0,units:"degrees",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-brightness-min":{type:"number",default:0,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-brightness-max":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-saturation":{type:"number",default:0,minimum:-1,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-contrast":{type:"number",default:0,minimum:-1,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"raster-resampling":{type:"enum",values:{linear:{},nearest:{}},default:"linear",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"raster-fade-duration":{type:"number",default:300,minimum:0,transition:!1,units:"milliseconds",expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"}},paint_hillshade:{"hillshade-illumination-direction":{type:"numberArray",default:335,minimum:0,maximum:359,transition:!1,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-illumination-altitude":{type:"numberArray",default:45,minimum:0,maximum:90,transition:!1,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-illumination-anchor":{type:"enum",values:{map:{},viewport:{}},default:"viewport",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-exaggeration":{type:"number",default:.5,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-shadow-color":{type:"colorArray",default:"#000000",transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-highlight-color":{type:"colorArray",default:"#FFFFFF",transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-accent-color":{type:"color",default:"#000000",transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"hillshade-method":{type:"enum",values:{standard:{},basic:{},combined:{},igor:{},multidirectional:{}},default:"standard",expression:{interpolated:!1,parameters:["zoom"]},"property-type":"data-constant"}},"paint_color-relief":{"color-relief-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"color-relief-color":{type:"color",transition:!1,expression:{interpolated:!0,parameters:["elevation"]},"property-type":"color-ramp"}},paint_background:{"background-color":{type:"color",default:"#000000",transition:!0,requires:[{"!":"background-pattern"}],expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"},"background-pattern":{type:"resolvedImage",transition:!0,expression:{interpolated:!1,parameters:["zoom"]},"property-type":"cross-faded"},"background-opacity":{type:"number",default:1,minimum:0,maximum:1,transition:!0,expression:{interpolated:!0,parameters:["zoom"]},"property-type":"data-constant"}},transition:{duration:{type:"number",default:300,minimum:0,units:"milliseconds"},delay:{type:"number",default:0,minimum:0,units:"milliseconds"}},"property-type":{"data-driven":{type:"property-type"},"cross-faded":{type:"property-type"},"cross-faded-data-driven":{type:"property-type"},"color-ramp":{type:"property-type"},"data-constant":{type:"property-type"},constant:{type:"property-type"}},promoteId:{"*":{type:"string"}}};let Us=["type","source","source-layer","minzoom","maxzoom","filter","layout"];function ns(d,a){let h={};for(let A in d)A!=="ref"&&(h[A]=d[A]);return Us.forEach(A=>{A in a&&(h[A]=a[A])}),h}function Be(d,a){if(Array.isArray(d)){if(!Array.isArray(a)||d.length!==a.length)return!1;for(let h=0;h`:d.itemType.kind==="value"?"array":`array<${a}>`}return d.kind}let Ds=[si,Or,fi,pr,Di,Mi,po,Ii,uo(Ni),Ls,No,co,wo,Al];function ho(d,a){if(a.kind==="error")return null;if(d.kind==="array"){if(a.kind==="array"&&(a.N===0&&a.itemType.kind==="value"||!ho(d.itemType,a.itemType))&&(typeof d.N!="number"||d.N===a.N))return null}else{if(d.kind===a.kind)return null;if(d.kind==="value"){for(let h of Ds)if(!ho(h,a))return null}}return`Expected ${Qn(d)} but found ${Qn(a)} instead.`}function cs(d,a){return a.some(h=>h.kind===d.kind)}function _l(d,a){return a.some(h=>h==="null"?d===null:h==="array"?Array.isArray(d):h==="object"?d&&!Array.isArray(d)&&typeof d=="object":h===typeof d)}function zc(d,a){return d.kind==="array"&&a.kind==="array"?d.itemType.kind===a.itemType.kind&&typeof d.N=="number":d.kind===a.kind}let yl=.96422,Ya=.82521,md=4/29,os=6/29,la=3*os*os,mf=os*os*os,Ll=Math.PI/180,Dl=180/Math.PI;function eu(d){return(d%=360)<0&&(d+=360),d}function ca([d,a,h,A]){let C,D,O=Uc((.2225045*(d=Tc(d))+.7168786*(a=Tc(a))+.0606169*(h=Tc(h)))/1);d===a&&a===h?C=D=O:(C=Uc((.4360747*d+.3850649*a+.1430804*h)/yl),D=Uc((.0139322*d+.0971045*a+.7141733*h)/Ya));let G=116*O-16;return[G<0?0:G,500*(C-O),200*(O-D),A]}function Tc(d){return d<=.04045?d/12.92:Math.pow((d+.055)/1.055,2.4)}function Uc(d){return d>mf?Math.pow(d,1/3):d/la+md}function pm([d,a,h,A]){let C=(d+16)/116,D=isNaN(a)?C:C+a/500,O=isNaN(h)?C:C-h/200;return C=1*Mu(C),D=yl*Mu(D),O=Ya*Mu(O),[pu(3.1338561*D-1.6168667*C-.4906146*O),pu(-.9787684*D+1.9161415*C+.033454*O),pu(.0719453*D-.2289914*C+1.4052427*O),A]}function pu(d){return(d=d<=.00304?12.92*d:1.055*Math.pow(d,1/2.4)-.055)<0?0:d>1?1:d}function Mu(d){return d>os?d*d*d:la*(d-md)}let jh=Object.hasOwn||function(d,a){return Object.prototype.hasOwnProperty.call(d,a)};function Ia(d,a){return jh(d,a)?d[a]:void 0}function Iu(d){return parseInt(d.padEnd(2,d),16)/255}function Sc(d,a){return pc(a?d/100:d,0,1)}function pc(d,a,h){return Math.min(Math.max(a,d),h)}function gf(d){return!d.some(Number.isNaN)}let gd={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]};function Gh(d,a,h){return d+h*(a-d)}function Hh(d,a,h){return d.map((A,C)=>Gh(A,a[C],h))}class Mo{constructor(a,h,A,C=1,D=!0){this.r=a,this.g=h,this.b=A,this.a=C,D||(this.r*=C,this.g*=C,this.b*=C,C||this.overwriteGetter("rgb",[a,h,A,C]))}static parse(a){if(a instanceof Mo)return a;if(typeof a!="string")return;let h=function(A){if((A=A.toLowerCase().trim())==="transparent")return[0,0,0,0];let C=Ia(gd,A);if(C){let[O,G,Z]=C;return[O/255,G/255,Z/255,1]}if(A.startsWith("#")&&/^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(A)){let O=A.length<6?1:2,G=1;return[Iu(A.slice(G,G+=O)),Iu(A.slice(G,G+=O)),Iu(A.slice(G,G+=O)),Iu(A.slice(G,G+O)||"ff")]}if(A.startsWith("rgb")){let O=A.match(/^rgba?\(\s*([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/);if(O){let[G,Z,re,ce,de,we,Ee,De,ke,$e,st,kt]=O,pt=[ce||" ",Ee||" ",$e].join("");if(pt===" "||pt===" /"||pt===",,"||pt===",,,"){let _t=[re,we,ke].join(""),Gt=_t==="%%%"?100:_t===""?255:0;if(Gt){let Qt=[pc(+Z/Gt,0,1),pc(+de/Gt,0,1),pc(+De/Gt,0,1),st?Sc(+st,kt):1];if(gf(Qt))return Qt}}return}}let D=A.match(/^hsla?\(\s*([\de.+-]+)(?:deg)?(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/);if(D){let[O,G,Z,re,ce,de,we,Ee,De]=D,ke=[Z||" ",ce||" ",we].join("");if(ke===" "||ke===" /"||ke===",,"||ke===",,,"){let $e=[+G,pc(+re,0,100),pc(+de,0,100),Ee?Sc(+Ee,De):1];if(gf($e))return function([st,kt,pt,_t]){function Gt(Qt){let Mr=(Qt+st/30)%12,oi=kt*Math.min(pt,1-pt);return pt-oi*Math.max(-1,Math.min(Mr-3,9-Mr,1))}return st=eu(st),kt/=100,pt/=100,[Gt(0),Gt(8),Gt(4),_t]}($e)}}}(a);return h?new Mo(...h,!1):void 0}get rgb(){let{r:a,g:h,b:A,a:C}=this,D=C||1/0;return this.overwriteGetter("rgb",[a/D,h/D,A/D,C])}get hcl(){return this.overwriteGetter("hcl",function(a){let[h,A,C,D]=ca(a),O=Math.sqrt(A*A+C*C);return[Math.round(1e4*O)?eu(Math.atan2(C,A)*Dl):NaN,O,h,D]}(this.rgb))}get lab(){return this.overwriteGetter("lab",ca(this.rgb))}overwriteGetter(a,h){return Object.defineProperty(this,a,{value:h}),h}toString(){let[a,h,A,C]=this.rgb;return`rgba(${[a,h,A].map(D=>Math.round(255*D)).join(",")},${C})`}static interpolate(a,h,A,C="rgb"){switch(C){case"rgb":{let[D,O,G,Z]=Hh(a.rgb,h.rgb,A);return new Mo(D,O,G,Z,!1)}case"hcl":{let[D,O,G,Z]=a.hcl,[re,ce,de,we]=h.hcl,Ee,De;if(isNaN(D)||isNaN(re))isNaN(D)?isNaN(re)?Ee=NaN:(Ee=re,G!==1&&G!==0||(De=ce)):(Ee=D,de!==1&&de!==0||(De=O));else{let pt=re-D;re>D&&pt>180?pt-=360:re180&&(pt+=360),Ee=D+A*pt}let[ke,$e,st,kt]=function([pt,_t,Gt,Qt]){return pt=isNaN(pt)?0:pt*Ll,pm([Gt,Math.cos(pt)*_t,Math.sin(pt)*_t,Qt])}([Ee,De??Gh(O,ce,A),Gh(G,de,A),Gh(Z,we,A)]);return new Mo(ke,$e,st,kt,!1)}case"lab":{let[D,O,G,Z]=pm(Hh(a.lab,h.lab,A));return new Mo(D,O,G,Z,!1)}}}}Mo.black=new Mo(0,0,0,1),Mo.white=new Mo(1,1,1,1),Mo.transparent=new Mo(0,0,0,0),Mo.red=new Mo(1,0,0,1);class Sp{constructor(a,h,A){this.sensitivity=a?h?"variant":"case":h?"accent":"base",this.locale=A,this.collator=new Intl.Collator(this.locale?this.locale:[],{sensitivity:this.sensitivity,usage:"search"})}compare(a,h){return this.collator.compare(a,h)}resolvedLocale(){return new Intl.Collator(this.locale?this.locale:[]).resolvedOptions().locale}}let Qm=["bottom","center","top"];class Ep{constructor(a,h,A,C,D,O){this.text=a,this.image=h,this.scale=A,this.fontStack=C,this.textColor=D,this.verticalAlign=O}}class Ql{constructor(a){this.sections=a}static fromString(a){return new Ql([new Ep(a,null,null,null,null,null)])}isEmpty(){return this.sections.length===0||!this.sections.some(a=>a.text.length!==0||a.image&&a.image.name.length!==0)}static factory(a){return a instanceof Ql?a:Ql.fromString(a)}toString(){return this.sections.length===0?"":this.sections.map(a=>a.text).join("")}}class Jl{constructor(a){this.values=a.slice()}static parse(a){if(a instanceof Jl)return a;if(typeof a=="number")return new Jl([a,a,a,a]);if(Array.isArray(a)&&!(a.length<1||a.length>4)){for(let h of a)if(typeof h!="number")return;switch(a.length){case 1:a=[a[0],a[0],a[0],a[0]];break;case 2:a=[a[0],a[1],a[0],a[1]];break;case 3:a=[a[0],a[1],a[2],a[1]]}return new Jl(a)}}toString(){return JSON.stringify(this.values)}static interpolate(a,h,A){return new Jl(Hh(a.values,h.values,A))}}class _a{constructor(a){this.values=a.slice()}static parse(a){if(a instanceof _a)return a;if(typeof a=="number")return new _a([a]);if(Array.isArray(a)){for(let h of a)if(typeof h!="number")return;return new _a(a)}}toString(){return JSON.stringify(this.values)}static interpolate(a,h,A){return new _a(Hh(a.values,h.values,A))}}class Zn{constructor(a){this.values=a.slice()}static parse(a){if(a instanceof Zn)return a;if(typeof a=="string"){let A=Mo.parse(a);return A?new Zn([A]):void 0}if(!Array.isArray(a))return;let h=[];for(let A of a){if(typeof A!="string")return;let C=Mo.parse(A);if(!C)return;h.push(C)}return new Zn(h)}toString(){return JSON.stringify(this.values)}static interpolate(a,h,A,C="rgb"){let D=[];if(a.values.length!=h.values.length)throw new Error(`colorArray: Arrays have mismatched length (${a.values.length} vs. ${h.values.length}), cannot interpolate.`);for(let O=0;O=0&&d<=255&&typeof a=="number"&&a>=0&&a<=255&&typeof h=="number"&&h>=0&&h<=255?A===void 0||typeof A=="number"&&A>=0&&A<=1?null:`Invalid rgba value [${[d,a,h,A].join(", ")}]: 'a' must be between 0 and 1.`:`Invalid rgba value [${(typeof A=="number"?[d,a,h,A]:[d,a,h]).join(", ")}]: 'r', 'g', and 'b' must be between 0 and 255.`}function tu(d){if(d===null||typeof d=="string"||typeof d=="boolean"||typeof d=="number"||d instanceof vl||d instanceof Mo||d instanceof Sp||d instanceof Ql||d instanceof Jl||d instanceof _a||d instanceof Zn||d instanceof sl||d instanceof Ol)return!0;if(Array.isArray(d)){for(let a of d)if(!tu(a))return!1;return!0}if(typeof d=="object"){for(let a in d)if(!tu(d[a]))return!1;return!0}return!1}function La(d){if(d===null)return si;if(typeof d=="string")return fi;if(typeof d=="boolean")return pr;if(typeof d=="number")return Or;if(d instanceof Mo)return Di;if(d instanceof vl)return Mi;if(d instanceof Sp)return zi;if(d instanceof Ql)return po;if(d instanceof Jl)return Ls;if(d instanceof _a)return No;if(d instanceof Zn)return co;if(d instanceof sl)return Al;if(d instanceof Ol)return wo;if(Array.isArray(d)){let a=d.length,h;for(let A of d){let C=La(A);if(h){if(h===C)continue;h=Ni;break}h=C}return uo(h||Ni,a)}return Ii}function xl(d){let a=typeof d;return d===null?"":a==="string"||a==="number"||a==="boolean"?String(d):d instanceof Mo||d instanceof vl||d instanceof Ql||d instanceof Jl||d instanceof _a||d instanceof Zn||d instanceof sl||d instanceof Ol?d.toString():JSON.stringify(d)}class Ws{constructor(a,h){this.type=a,this.value=h}static parse(a,h){if(a.length!==2)return h.error(`'literal' expression requires exactly one argument, but found ${a.length-1} instead.`);if(!tu(a[1]))return h.error("invalid value");let A=a[1],C=La(A),D=h.expectedType;return C.kind!=="array"||C.N!==0||!D||D.kind!=="array"||typeof D.N=="number"&&D.N!==0||(C=D),new Ws(C,A)}evaluate(){return this.value}eachChild(){}outputDefined(){return!0}}let Wh={string:fi,number:Or,boolean:pr,object:Ii};class Ec{constructor(a,h){this.type=a,this.args=h}static parse(a,h){if(a.length<2)return h.error("Expected at least one argument.");let A,C=1,D=a[0];if(D==="array"){let G,Z;if(a.length>2){let re=a[1];if(typeof re!="string"||!(re in Wh)||re==="object")return h.error('The item type argument of "array" must be one of string, number, boolean',1);G=Wh[re],C++}else G=Ni;if(a.length>3){if(a[2]!==null&&(typeof a[2]!="number"||a[2]<0||a[2]!==Math.floor(a[2])))return h.error('The length argument to "array" must be a positive integer literal',2);Z=a[2],C++}A=uo(G,Z)}else{if(!Wh[D])throw new Error(`Types doesn't contain name = ${D}`);A=Wh[D]}let O=[];for(;Ca.outputDefined())}}let Hf={"to-boolean":pr,"to-color":Di,"to-number":Or,"to-string":fi};class qh{constructor(a,h){this.type=a,this.args=h}static parse(a,h){if(a.length<2)return h.error("Expected at least one argument.");let A=a[0];if(!Hf[A])throw new Error(`Can't parse ${A} as it is not part of the known types`);if((A==="to-boolean"||A==="to-string")&&a.length!==2)return h.error("Expected one argument.");let C=Hf[A],D=[];for(let O=1;O4?`Invalid rgba value ${JSON.stringify(h)}: expected an array containing either three or four numeric values.`:Pu(h[0],h[1],h[2],h[3]),!A))return new Mo(h[0]/255,h[1]/255,h[2]/255,h[3])}throw new Hs(A||`Could not parse color from value '${typeof h=="string"?h:JSON.stringify(h)}'`)}case"padding":{let h;for(let A of this.args){h=A.evaluate(a);let C=Jl.parse(h);if(C)return C}throw new Hs(`Could not parse padding from value '${typeof h=="string"?h:JSON.stringify(h)}'`)}case"numberArray":{let h;for(let A of this.args){h=A.evaluate(a);let C=_a.parse(h);if(C)return C}throw new Hs(`Could not parse numberArray from value '${typeof h=="string"?h:JSON.stringify(h)}'`)}case"colorArray":{let h;for(let A of this.args){h=A.evaluate(a);let C=Zn.parse(h);if(C)return C}throw new Hs(`Could not parse colorArray from value '${typeof h=="string"?h:JSON.stringify(h)}'`)}case"variableAnchorOffsetCollection":{let h;for(let A of this.args){h=A.evaluate(a);let C=sl.parse(h);if(C)return C}throw new Hs(`Could not parse variableAnchorOffsetCollection from value '${typeof h=="string"?h:JSON.stringify(h)}'`)}case"number":{let h=null;for(let A of this.args){if(h=A.evaluate(a),h===null)return 0;let C=Number(h);if(!isNaN(C))return C}throw new Hs(`Could not convert ${JSON.stringify(h)} to number.`)}case"formatted":return Ql.fromString(xl(this.args[0].evaluate(a)));case"resolvedImage":return Ol.fromString(xl(this.args[0].evaluate(a)));case"projectionDefinition":return this.args[0].evaluate(a);default:return xl(this.args[0].evaluate(a))}}eachChild(a){this.args.forEach(a)}outputDefined(){return this.args.every(a=>a.outputDefined())}}let Gd=["Unknown","Point","LineString","Polygon"];class Cp{constructor(){this.globals=null,this.feature=null,this.featureState=null,this.formattedSection=null,this._parseColorCache=new Map,this.availableImages=null,this.canonical=null}id(){return this.feature&&"id"in this.feature?this.feature.id:null}geometryType(){return this.feature?typeof this.feature.type=="number"?Gd[this.feature.type]:this.feature.type:null}geometry(){return this.feature&&"geometry"in this.feature?this.feature.geometry:null}canonicalID(){return this.canonical}properties(){return this.feature&&this.feature.properties||{}}parseColor(a){let h=this._parseColorCache.get(a);return h||(h=Mo.parse(a),this._parseColorCache.set(a,h)),h}}class Af{constructor(a,h,A=[],C,D=new Gr,O=[]){this.registry=a,this.path=A,this.key=A.map(G=>`[${G}]`).join(""),this.scope=D,this.errors=O,this.expectedType=C,this._isConstant=h}parse(a,h,A,C,D={}){return h?this.concat(h,A,C)._parse(a,D):this._parse(a,D)}_parse(a,h){function A(C,D,O){return O==="assert"?new Ec(D,[C]):O==="coerce"?new qh(D,[C]):C}if(a!==null&&typeof a!="string"&&typeof a!="boolean"&&typeof a!="number"||(a=["literal",a]),Array.isArray(a)){if(a.length===0)return this.error('Expected an array with at least one element. If you wanted a literal array, use ["literal", []].');let C=a[0];if(typeof C!="string")return this.error(`Expression name must be a string, but found ${typeof C} instead. If you wanted a literal array, use ["literal", [...]].`,0),null;let D=this.registry[C];if(D){let O=D.parse(a,this);if(!O)return null;if(this.expectedType){let G=this.expectedType,Z=O.type;if(G.kind!=="string"&&G.kind!=="number"&&G.kind!=="boolean"&&G.kind!=="object"&&G.kind!=="array"||Z.kind!=="value"){if(G.kind==="projectionDefinition"&&["string","array"].includes(Z.kind)||["color","formatted","resolvedImage"].includes(G.kind)&&["value","string"].includes(Z.kind)||["padding","numberArray"].includes(G.kind)&&["value","number","array"].includes(Z.kind)||G.kind==="colorArray"&&["value","string","array"].includes(Z.kind)||G.kind==="variableAnchorOffsetCollection"&&["value","array"].includes(Z.kind))O=A(O,G,h.typeAnnotation||"coerce");else if(this.checkSubtype(G,Z))return null}else O=A(O,G,h.typeAnnotation||"assert")}if(!(O instanceof Ws)&&O.type.kind!=="resolvedImage"&&this._isConstant(O)){let G=new Cp;try{O=new Ws(O.type,O.evaluate(G))}catch(Z){return this.error(Z.message),null}}return O}return this.error(`Unknown expression "${C}". If you wanted a literal array, use ["literal", [...]].`,0)}return this.error(a===void 0?"'undefined' value invalid. Use null instead.":typeof a=="object"?'Bare objects invalid. Use ["literal", {...}] instead.':`Expected an array, but found ${typeof a} instead.`)}concat(a,h,A){let C=typeof a=="number"?this.path.concat(a):this.path,D=A?this.scope.concat(A):this.scope;return new Af(this.registry,this._isConstant,C,h||null,D,this.errors)}error(a,...h){let A=`${this.key}${h.map(C=>`[${C}]`).join("")}`;this.errors.push(new ui(A,a))}checkSubtype(a,h){let A=ho(a,h);return A&&this.error(A),A}}class ra{constructor(a,h){this.type=h.type,this.bindings=[].concat(a),this.result=h}evaluate(a){return this.result.evaluate(a)}eachChild(a){for(let h of this.bindings)a(h[1]);a(this.result)}static parse(a,h){if(a.length<4)return h.error(`Expected at least 3 arguments, but found ${a.length-1} instead.`);let A=[];for(let D=1;D=A.length)throw new Hs(`Array index out of bounds: ${h} > ${A.length-1}.`);if(h!==Math.floor(h))throw new Hs(`Array index must be an integer, but found ${h} instead.`);return A[h]}eachChild(a){a(this.index),a(this.input)}outputDefined(){return!1}}class Ao{constructor(a,h){this.type=pr,this.needle=a,this.haystack=h}static parse(a,h){if(a.length!==3)return h.error(`Expected 2 arguments, but found ${a.length-1} instead.`);let A=h.parse(a[1],1,Ni),C=h.parse(a[2],2,Ni);return A&&C?cs(A.type,[pr,fi,Or,si,Ni])?new Ao(A,C):h.error(`Expected first argument to be of type boolean, string, number or null, but found ${Qn(A.type)} instead`):null}evaluate(a){let h=this.needle.evaluate(a),A=this.haystack.evaluate(a);if(!A)return!1;if(!_l(h,["boolean","string","number","null"]))throw new Hs(`Expected first argument to be of type boolean, string, number or null, but found ${Qn(La(h))} instead.`);if(!_l(A,["string","array"]))throw new Hs(`Expected second argument to be of type array or string, but found ${Qn(La(A))} instead.`);return A.indexOf(h)>=0}eachChild(a){a(this.needle),a(this.haystack)}outputDefined(){return!0}}class $h{constructor(a,h,A){this.type=Or,this.needle=a,this.haystack=h,this.fromIndex=A}static parse(a,h){if(a.length<=2||a.length>=5)return h.error(`Expected 2 or 3 arguments, but found ${a.length-1} instead.`);let A=h.parse(a[1],1,Ni),C=h.parse(a[2],2,Ni);if(!A||!C)return null;if(!cs(A.type,[pr,fi,Or,si,Ni]))return h.error(`Expected first argument to be of type boolean, string, number or null, but found ${Qn(A.type)} instead`);if(a.length===4){let D=h.parse(a[3],3,Or);return D?new $h(A,C,D):null}return new $h(A,C)}evaluate(a){let h=this.needle.evaluate(a),A=this.haystack.evaluate(a);if(!_l(h,["boolean","string","number","null"]))throw new Hs(`Expected first argument to be of type boolean, string, number or null, but found ${Qn(La(h))} instead.`);let C;if(this.fromIndex&&(C=this.fromIndex.evaluate(a)),_l(A,["string"])){let D=A.indexOf(h,C);return D===-1?-1:[...A.slice(0,D)].length}if(_l(A,["array"]))return A.indexOf(h,C);throw new Hs(`Expected second argument to be of type array or string, but found ${Qn(La(A))} instead.`)}eachChild(a){a(this.needle),a(this.haystack),this.fromIndex&&a(this.fromIndex)}outputDefined(){return!1}}class us{constructor(a,h,A,C,D,O){this.inputType=a,this.type=h,this.input=A,this.cases=C,this.outputs=D,this.otherwise=O}static parse(a,h){if(a.length<5)return h.error(`Expected at least 4 arguments, but found only ${a.length-1}.`);if(a.length%2!=1)return h.error("Expected an even number of arguments.");let A,C;h.expectedType&&h.expectedType.kind!=="value"&&(C=h.expectedType);let D={},O=[];for(let re=2;reNumber.MAX_SAFE_INTEGER)return we.error(`Branch labels must be integers no larger than ${Number.MAX_SAFE_INTEGER}.`);if(typeof De=="number"&&Math.floor(De)!==De)return we.error("Numeric branch labels must be integer values.");if(A){if(we.checkSubtype(A,La(De)))return null}else A=La(De);if(D[String(De)]!==void 0)return we.error("Branch labels must be unique.");D[String(De)]=O.length}let Ee=h.parse(de,re,C);if(!Ee)return null;C=C||Ee.type,O.push(Ee)}let G=h.parse(a[1],1,Ni);if(!G)return null;let Z=h.parse(a[a.length-1],a.length-1,C);return Z?G.type.kind!=="value"&&h.concat(1).checkSubtype(A,G.type)?null:new us(A,C,G,D,O,Z):null}evaluate(a){let h=this.input.evaluate(a);return(La(h)===this.inputType&&this.outputs[this.cases[h]]||this.otherwise).evaluate(a)}eachChild(a){a(this.input),this.outputs.forEach(a),a(this.otherwise)}outputDefined(){return this.outputs.every(a=>a.outputDefined())&&this.otherwise.outputDefined()}}class Hd{constructor(a,h,A){this.type=a,this.branches=h,this.otherwise=A}static parse(a,h){if(a.length<4)return h.error(`Expected at least 3 arguments, but found only ${a.length-1}.`);if(a.length%2!=0)return h.error("Expected an odd number of arguments.");let A;h.expectedType&&h.expectedType.kind!=="value"&&(A=h.expectedType);let C=[];for(let O=1;Oh.outputDefined())&&this.otherwise.outputDefined()}}class q{constructor(a,h,A,C){this.type=a,this.input=h,this.beginIndex=A,this.endIndex=C}static parse(a,h){if(a.length<=2||a.length>=5)return h.error(`Expected 2 or 3 arguments, but found ${a.length-1} instead.`);let A=h.parse(a[1],1,Ni),C=h.parse(a[2],2,Or);if(!A||!C)return null;if(!cs(A.type,[uo(Ni),fi,Ni]))return h.error(`Expected first argument to be of type array or string, but found ${Qn(A.type)} instead`);if(a.length===4){let D=h.parse(a[3],3,Or);return D?new q(A.type,A,C,D):null}return new q(A.type,A,C)}evaluate(a){let h=this.input.evaluate(a),A=this.beginIndex.evaluate(a),C;if(this.endIndex&&(C=this.endIndex.evaluate(a)),_l(h,["string"]))return[...h].slice(A,C).join("");if(_l(h,["array"]))return h.slice(A,C);throw new Hs(`Expected first argument to be of type array or string, but found ${Qn(La(h))} instead.`)}eachChild(a){a(this.input),a(this.beginIndex),this.endIndex&&a(this.endIndex)}outputDefined(){return!1}}function oe(d,a){let h=d.length-1,A,C,D=0,O=h,G=0;for(;D<=O;)if(G=Math.floor((D+O)/2),A=d[G],C=d[G+1],A<=a){if(G===h||aa))throw new Hs("Input is not a number.");O=G-1}return 0}class pe{constructor(a,h,A){this.type=a,this.input=h,this.labels=[],this.outputs=[];for(let[C,D]of A)this.labels.push(C),this.outputs.push(D)}static parse(a,h){if(a.length-1<4)return h.error(`Expected at least 4 arguments, but found only ${a.length-1}.`);if((a.length-1)%2!=0)return h.error("Expected an even number of arguments.");let A=h.parse(a[1],1,Or);if(!A)return null;let C=[],D=null;h.expectedType&&h.expectedType.kind!=="value"&&(D=h.expectedType);for(let O=1;O=G)return h.error('Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.',re);let de=h.parse(Z,ce,D);if(!de)return null;D=D||de.type,C.push([G,de])}return new pe(D,A,C)}evaluate(a){let h=this.labels,A=this.outputs;if(h.length===1)return A[0].evaluate(a);let C=this.input.evaluate(a);if(C<=h[0])return A[0].evaluate(a);let D=h.length;return C>=h[D-1]?A[D-1].evaluate(a):A[oe(h,C)].evaluate(a)}eachChild(a){a(this.input);for(let h of this.outputs)a(h)}outputDefined(){return this.outputs.every(a=>a.outputDefined())}}function be(d){return d&&d.__esModule&&Object.prototype.hasOwnProperty.call(d,"default")?d.default:d}var Ge,qe,ot=function(){if(qe)return Ge;function d(a,h,A,C){this.cx=3*a,this.bx=3*(A-a)-this.cx,this.ax=1-this.cx-this.bx,this.cy=3*h,this.by=3*(C-h)-this.cy,this.ay=1-this.cy-this.by,this.p1x=a,this.p1y=h,this.p2x=A,this.p2y=C}return qe=1,Ge=d,d.prototype={sampleCurveX:function(a){return((this.ax*a+this.bx)*a+this.cx)*a},sampleCurveY:function(a){return((this.ay*a+this.by)*a+this.cy)*a},sampleCurveDerivativeX:function(a){return(3*this.ax*a+2*this.bx)*a+this.cx},solveCurveX:function(a,h){if(h===void 0&&(h=1e-6),a<0)return 0;if(a>1)return 1;for(var A=a,C=0;C<8;C++){var D=this.sampleCurveX(A)-a;if(Math.abs(D)D?G=A:Z=A,A=.5*(Z-G)+G;return A},solve:function(a,h){return this.sampleCurveY(this.solveCurveX(a,h))}},Ge}(),rr=be(ot);class Ut{constructor(a,h,A,C,D){this.type=a,this.operator=h,this.interpolation=A,this.input=C,this.labels=[],this.outputs=[];for(let[O,G]of D)this.labels.push(O),this.outputs.push(G)}static interpolationFactor(a,h,A,C){let D=0;if(a.name==="exponential")D=cr(h,a.base,A,C);else if(a.name==="linear")D=cr(h,1,A,C);else if(a.name==="cubic-bezier"){let O=a.controlPoints;D=new rr(O[0],O[1],O[2],O[3]).solve(cr(h,1,A,C))}return D}static parse(a,h){let[A,C,D,...O]=a;if(!Array.isArray(C)||C.length===0)return h.error("Expected an interpolation type expression.",1);if(C[0]==="linear")C={name:"linear"};else if(C[0]==="exponential"){let re=C[1];if(typeof re!="number")return h.error("Exponential interpolation requires a numeric base.",1,1);C={name:"exponential",base:re}}else{if(C[0]!=="cubic-bezier")return h.error(`Unknown interpolation type ${String(C[0])}`,1,0);{let re=C.slice(1);if(re.length!==4||re.some(ce=>typeof ce!="number"||ce<0||ce>1))return h.error("Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.",1);C={name:"cubic-bezier",controlPoints:re}}}if(a.length-1<4)return h.error(`Expected at least 4 arguments, but found only ${a.length-1}.`);if((a.length-1)%2!=0)return h.error("Expected an even number of arguments.");if(D=h.parse(D,2,Or),!D)return null;let G=[],Z=null;A!=="interpolate-hcl"&&A!=="interpolate-lab"||h.expectedType==co?h.expectedType&&h.expectedType.kind!=="value"&&(Z=h.expectedType):Z=Di;for(let re=0;re=ce)return h.error('Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.',we);let De=h.parse(de,Ee,Z);if(!De)return null;Z=Z||De.type,G.push([ce,De])}return zc(Z,Or)||zc(Z,Mi)||zc(Z,Di)||zc(Z,Ls)||zc(Z,No)||zc(Z,co)||zc(Z,Al)||zc(Z,uo(Or))?new Ut(Z,A,C,D,G):h.error(`Type ${Qn(Z)} is not interpolatable.`)}evaluate(a){let h=this.labels,A=this.outputs;if(h.length===1)return A[0].evaluate(a);let C=this.input.evaluate(a);if(C<=h[0])return A[0].evaluate(a);let D=h.length;if(C>=h[D-1])return A[D-1].evaluate(a);let O=oe(h,C),G=Ut.interpolationFactor(this.interpolation,C,h[O],h[O+1]),Z=A[O].evaluate(a),re=A[O+1].evaluate(a);switch(this.operator){case"interpolate":switch(this.type.kind){case"number":return Gh(Z,re,G);case"color":return Mo.interpolate(Z,re,G);case"padding":return Jl.interpolate(Z,re,G);case"colorArray":return Zn.interpolate(Z,re,G);case"numberArray":return _a.interpolate(Z,re,G);case"variableAnchorOffsetCollection":return sl.interpolate(Z,re,G);case"array":return Hh(Z,re,G);case"projectionDefinition":return vl.interpolate(Z,re,G)}case"interpolate-hcl":switch(this.type.kind){case"color":return Mo.interpolate(Z,re,G,"hcl");case"colorArray":return Zn.interpolate(Z,re,G,"hcl")}case"interpolate-lab":switch(this.type.kind){case"color":return Mo.interpolate(Z,re,G,"lab");case"colorArray":return Zn.interpolate(Z,re,G,"lab")}}}eachChild(a){a(this.input);for(let h of this.outputs)a(h)}outputDefined(){return this.outputs.every(a=>a.outputDefined())}}function cr(d,a,h,A){let C=A-h,D=d-h;return C===0?0:a===1?D/C:(Math.pow(a,D)-1)/(Math.pow(a,C)-1)}let Fr={color:Mo.interpolate,number:Gh,padding:Jl.interpolate,numberArray:_a.interpolate,colorArray:Zn.interpolate,variableAnchorOffsetCollection:sl.interpolate,array:Hh};class li{constructor(a,h){this.type=a,this.args=h}static parse(a,h){if(a.length<2)return h.error("Expected at least one argument.");let A=null,C=h.expectedType;C&&C.kind!=="value"&&(A=C);let D=[];for(let G of a.slice(1)){let Z=h.parse(G,1+D.length,A,void 0,{typeAnnotation:"omit"});if(!Z)return null;A=A||Z.type,D.push(Z)}if(!A)throw new Error("No output type");let O=C&&D.some(G=>ho(C,G.type));return new li(O?Ni:A,D)}evaluate(a){let h,A=null,C=0;for(let D of this.args)if(C++,A=D.evaluate(a),A&&A instanceof Ol&&!A.available&&(h||(h=A.name),A=null,C===this.args.length&&(A=h)),A!==null)break;return A}eachChild(a){this.args.forEach(a)}outputDefined(){return this.args.every(a=>a.outputDefined())}}function vr(d,a){return d==="=="||d==="!="?a.kind==="boolean"||a.kind==="string"||a.kind==="number"||a.kind==="null"||a.kind==="value":a.kind==="string"||a.kind==="number"||a.kind==="value"}function Bi(d,a,h,A){return A.compare(a,h)===0}function gn(d,a,h){let A=d!=="=="&&d!=="!=";return class _ae{constructor(D,O,G){this.type=pr,this.lhs=D,this.rhs=O,this.collator=G,this.hasUntypedArgument=D.type.kind==="value"||O.type.kind==="value"}static parse(D,O){if(D.length!==3&&D.length!==4)return O.error("Expected two or three arguments.");let G=D[0],Z=O.parse(D[1],1,Ni);if(!Z)return null;if(!vr(G,Z.type))return O.concat(1).error(`"${G}" comparisons are not supported for type '${Qn(Z.type)}'.`);let re=O.parse(D[2],2,Ni);if(!re)return null;if(!vr(G,re.type))return O.concat(2).error(`"${G}" comparisons are not supported for type '${Qn(re.type)}'.`);if(Z.type.kind!==re.type.kind&&Z.type.kind!=="value"&&re.type.kind!=="value")return O.error(`Cannot compare types '${Qn(Z.type)}' and '${Qn(re.type)}'.`);A&&(Z.type.kind==="value"&&re.type.kind!=="value"?Z=new Ec(re.type,[Z]):Z.type.kind!=="value"&&re.type.kind==="value"&&(re=new Ec(Z.type,[re])));let ce=null;if(D.length===4){if(Z.type.kind!=="string"&&re.type.kind!=="string"&&Z.type.kind!=="value"&&re.type.kind!=="value")return O.error("Cannot use collator to compare non-string types.");if(ce=O.parse(D[3],3,zi),!ce)return null}return new _ae(Z,re,ce)}evaluate(D){let O=this.lhs.evaluate(D),G=this.rhs.evaluate(D);if(A&&this.hasUntypedArgument){let Z=La(O),re=La(G);if(Z.kind!==re.kind||Z.kind!=="string"&&Z.kind!=="number")throw new Hs(`Expected arguments for "${d}" to be (string, string) or (number, number), but found (${Z.kind}, ${re.kind}) instead.`)}if(this.collator&&!A&&this.hasUntypedArgument){let Z=La(O),re=La(G);if(Z.kind!=="string"||re.kind!=="string")return a(D,O,G)}return this.collator?h(D,O,G,this.collator.evaluate(D)):a(D,O,G)}eachChild(D){D(this.lhs),D(this.rhs),this.collator&&D(this.collator)}outputDefined(){return!0}}}let gs=gn("==",function(d,a,h){return a===h},Bi),Io=gn("!=",function(d,a,h){return a!==h},function(d,a,h,A){return!Bi(0,a,h,A)}),bl=gn("<",function(d,a,h){return a",function(d,a,h){return a>h},function(d,a,h,A){return A.compare(a,h)>0}),Da=gn("<=",function(d,a,h){return a<=h},function(d,a,h,A){return A.compare(a,h)<=0}),Ka=gn(">=",function(d,a,h){return a>=h},function(d,a,h,A){return A.compare(a,h)>=0});class Yn{constructor(a,h,A){this.type=zi,this.locale=A,this.caseSensitive=a,this.diacriticSensitive=h}static parse(a,h){if(a.length!==2)return h.error("Expected one argument.");let A=a[1];if(typeof A!="object"||Array.isArray(A))return h.error("Collator options argument must be an object.");let C=h.parse(A["case-sensitive"]!==void 0&&A["case-sensitive"],1,pr);if(!C)return null;let D=h.parse(A["diacritic-sensitive"]!==void 0&&A["diacritic-sensitive"],1,pr);if(!D)return null;let O=null;return A.locale&&(O=h.parse(A.locale,1,fi),!O)?null:new Yn(C,D,O)}evaluate(a){return new Sp(this.caseSensitive.evaluate(a),this.diacriticSensitive.evaluate(a),this.locale?this.locale.evaluate(a):null)}eachChild(a){a(this.caseSensitive),a(this.diacriticSensitive),this.locale&&a(this.locale)}outputDefined(){return!1}}class Er{constructor(a,h,A,C,D){this.type=fi,this.number=a,this.locale=h,this.currency=A,this.minFractionDigits=C,this.maxFractionDigits=D}static parse(a,h){if(a.length!==3)return h.error("Expected two arguments.");let A=h.parse(a[1],1,Or);if(!A)return null;let C=a[2];if(typeof C!="object"||Array.isArray(C))return h.error("NumberFormat options argument must be an object.");let D=null;if(C.locale&&(D=h.parse(C.locale,1,fi),!D))return null;let O=null;if(C.currency&&(O=h.parse(C.currency,1,fi),!O))return null;let G=null;if(C["min-fraction-digits"]&&(G=h.parse(C["min-fraction-digits"],1,Or),!G))return null;let Z=null;return C["max-fraction-digits"]&&(Z=h.parse(C["max-fraction-digits"],1,Or),!Z)?null:new Er(A,D,O,G,Z)}evaluate(a){return new Intl.NumberFormat(this.locale?this.locale.evaluate(a):[],{style:this.currency?"currency":"decimal",currency:this.currency?this.currency.evaluate(a):void 0,minimumFractionDigits:this.minFractionDigits?this.minFractionDigits.evaluate(a):void 0,maximumFractionDigits:this.maxFractionDigits?this.maxFractionDigits.evaluate(a):void 0}).format(this.number.evaluate(a))}eachChild(a){a(this.number),this.locale&&a(this.locale),this.currency&&a(this.currency),this.minFractionDigits&&a(this.minFractionDigits),this.maxFractionDigits&&a(this.maxFractionDigits)}outputDefined(){return!1}}class Hr{constructor(a){this.type=po,this.sections=a}static parse(a,h){if(a.length<2)return h.error("Expected at least one argument.");let A=a[1];if(!Array.isArray(A)&&typeof A=="object")return h.error("First argument must be an image or text section.");let C=[],D=!1;for(let O=1;O<=a.length-1;++O){let G=a[O];if(D&&typeof G=="object"&&!Array.isArray(G)){D=!1;let Z=null;if(G["font-scale"]&&(Z=h.parse(G["font-scale"],1,Or),!Z))return null;let re=null;if(G["text-font"]&&(re=h.parse(G["text-font"],1,uo(fi)),!re))return null;let ce=null;if(G["text-color"]&&(ce=h.parse(G["text-color"],1,Di),!ce))return null;let de=null;if(G["vertical-align"]){if(typeof G["vertical-align"]=="string"&&!Qm.includes(G["vertical-align"]))return h.error(`'vertical-align' must be one of: 'bottom', 'center', 'top' but found '${G["vertical-align"]}' instead.`);if(de=h.parse(G["vertical-align"],1,fi),!de)return null}let we=C[C.length-1];we.scale=Z,we.font=re,we.textColor=ce,we.verticalAlign=de}else{let Z=h.parse(a[O],1,Ni);if(!Z)return null;let re=Z.type.kind;if(re!=="string"&&re!=="value"&&re!=="null"&&re!=="resolvedImage")return h.error("Formatted text type must be 'string', 'value', 'image' or 'null'.");D=!0,C.push({content:Z,scale:null,font:null,textColor:null,verticalAlign:null})}}return new Hr(C)}evaluate(a){return new Ql(this.sections.map(h=>{let A=h.content.evaluate(a);return La(A)===wo?new Ep("",A,null,null,null,h.verticalAlign?h.verticalAlign.evaluate(a):null):new Ep(xl(A),null,h.scale?h.scale.evaluate(a):null,h.font?h.font.evaluate(a).join(","):null,h.textColor?h.textColor.evaluate(a):null,h.verticalAlign?h.verticalAlign.evaluate(a):null)}))}eachChild(a){for(let h of this.sections)a(h.content),h.scale&&a(h.scale),h.font&&a(h.font),h.textColor&&a(h.textColor),h.verticalAlign&&a(h.verticalAlign)}outputDefined(){return!1}}class wi{constructor(a){this.type=wo,this.input=a}static parse(a,h){if(a.length!==2)return h.error("Expected two arguments.");let A=h.parse(a[1],1,fi);return A?new wi(A):h.error("No image name provided.")}evaluate(a){let h=this.input.evaluate(a),A=Ol.fromString(h);return A&&a.availableImages&&(A.available=a.availableImages.indexOf(h)>-1),A}eachChild(a){a(this.input)}outputDefined(){return!1}}class ni{constructor(a){this.type=Or,this.input=a}static parse(a,h){if(a.length!==2)return h.error(`Expected 1 argument, but found ${a.length-1} instead.`);let A=h.parse(a[1],1);return A?A.type.kind!=="array"&&A.type.kind!=="string"&&A.type.kind!=="value"?h.error(`Expected argument of type string or array, but found ${Qn(A.type)} instead.`):new ni(A):null}evaluate(a){let h=this.input.evaluate(a);if(typeof h=="string")return[...h].length;if(Array.isArray(h))return h.length;throw new Hs(`Expected value to be of type string or array, but found ${Qn(La(h))} instead.`)}eachChild(a){a(this.input)}outputDefined(){return!1}}let b=8192;function Po(d,a){let h=(180+d[0])/360,A=(180-180/Math.PI*Math.log(Math.tan(Math.PI/4+d[1]*Math.PI/360)))/360,C=Math.pow(2,a.z);return[Math.round(h*C*b),Math.round(A*C*b)]}function _o(d,a){let h=Math.pow(2,a.z);return[(C=(d[0]/b+a.x)/h,360*C-180),(A=(d[1]/b+a.y)/h,360/Math.PI*Math.atan(Math.exp((180-360*A)*Math.PI/180))-90)];var A,C}function Ze(d,a){d[0]=Math.min(d[0],a[0]),d[1]=Math.min(d[1],a[1]),d[2]=Math.max(d[2],a[0]),d[3]=Math.max(d[3],a[1])}function Is(d,a){return!(d[0]<=a[0]||d[2]>=a[2]||d[1]<=a[1]||d[3]>=a[3])}function ao(d,a,h){let A=d[0]-a[0],C=d[1]-a[1],D=d[0]-h[0],O=d[1]-h[1];return A*O-D*C==0&&A*D<=0&&C*O<=0}function Ps(d,a,h,A){return(C=[A[0]-h[0],A[1]-h[1]])[0]*(D=[a[0]-d[0],a[1]-d[1]])[1]-C[1]*D[0]!=0&&!(!gh(d,a,h,A)||!gh(h,A,d,a));var C,D}function Rs(d,a,h){for(let A of h)for(let C=0;C(C=d)[1]!=(O=G[Z+1])[1]>C[1]&&C[0]<(O[0]-D[0])*(C[1]-D[1])/(O[1]-D[1])+D[0]&&(A=!A)}var C,D,O;return A}function Ks(d,a){for(let h of a)if(Ho(d,h))return!0;return!1}function Ts(d,a){for(let h of d)if(!Ho(h,a))return!1;for(let h=0;h0&&G<0||O<0&&G>0}function Ss(d,a,h){let A=[];for(let C=0;Ch[2]){let C=.5*A,D=d[0]-h[0]>C?-A:h[0]-d[0]>C?A:0;D===0&&(D=d[0]-h[2]>C?-A:h[2]-d[0]>C?A:0),d[0]+=D}Ze(a,d)}function Qa(d,a,h,A){let C=Math.pow(2,A.z)*b,D=[A.x*b,A.y*b],O=[];for(let G of d)for(let Z of G){let re=[Z.x+D[0],Z.y+D[1]];mc(re,a,h,C),O.push(re)}return O}function Ru(d,a,h,A){let C=Math.pow(2,A.z)*b,D=[A.x*b,A.y*b],O=[];for(let Z of d){let re=[];for(let ce of Z){let de=[ce.x+D[0],ce.y+D[1]];Ze(a,de),re.push(de)}O.push(re)}if(a[2]-a[0]<=C/2){(G=a)[0]=G[1]=1/0,G[2]=G[3]=-1/0;for(let Z of O)for(let re of Z)mc(re,a,h,C)}var G;return O}class gc{constructor(a,h){this.type=pr,this.geojson=a,this.geometries=h}static parse(a,h){if(a.length!==2)return h.error(`'within' expression requires exactly one argument, but found ${a.length-1} instead.`);if(tu(a[1])){let A=a[1];if(A.type==="FeatureCollection"){let C=[];for(let D of A.features){let{type:O,coordinates:G}=D.geometry;O==="Polygon"&&C.push(G),O==="MultiPolygon"&&C.push(...G)}if(C.length)return new gc(A,{type:"MultiPolygon",coordinates:C})}else if(A.type==="Feature"){let C=A.geometry.type;if(C==="Polygon"||C==="MultiPolygon")return new gc(A,A.geometry)}else if(A.type==="Polygon"||A.type==="MultiPolygon")return new gc(A,A)}return h.error("'within' expression requires valid geojson object that contains polygon geometry type.")}evaluate(a){if(a.geometry()!=null&&a.canonicalID()!=null){if(a.geometryType()==="Point")return function(h,A){let C=[1/0,1/0,-1/0,-1/0],D=[1/0,1/0,-1/0,-1/0],O=h.canonicalID();if(A.type==="Polygon"){let G=Ss(A.coordinates,D,O),Z=Qa(h.geometry(),C,D,O);if(!Is(C,D))return!1;for(let re of Z)if(!Ho(re,G))return!1}if(A.type==="MultiPolygon"){let G=Ah(A.coordinates,D,O),Z=Qa(h.geometry(),C,D,O);if(!Is(C,D))return!1;for(let re of Z)if(!Ks(re,G))return!1}return!0}(a,this.geometries);if(a.geometryType()==="LineString")return function(h,A){let C=[1/0,1/0,-1/0,-1/0],D=[1/0,1/0,-1/0,-1/0],O=h.canonicalID();if(A.type==="Polygon"){let G=Ss(A.coordinates,D,O),Z=Ru(h.geometry(),C,D,O);if(!Is(C,D))return!1;for(let re of Z)if(!Ts(re,G))return!1}if(A.type==="MultiPolygon"){let G=Ah(A.coordinates,D,O),Z=Ru(h.geometry(),C,D,O);if(!Is(C,D))return!1;for(let re of Z)if(!Os(re,G))return!1}return!0}(a,this.geometries)}return!1}eachChild(){}outputDefined(){return!0}}let _h=class{constructor(d=[],a=(h,A)=>hA?1:0){if(this.data=d,this.length=this.data.length,this.compare=a,this.length>0)for(let h=(this.length>>1)-1;h>=0;h--)this._down(h)}push(d){this.data.push(d),this._up(this.length++)}pop(){if(this.length===0)return;let d=this.data[0],a=this.data.pop();return--this.length>0&&(this.data[0]=a,this._down(0)),d}peek(){return this.data[0]}_up(d){let{data:a,compare:h}=this,A=a[d];for(;d>0;){let C=d-1>>1,D=a[C];if(h(A,D)>=0)break;a[d]=D,d=C}a[d]=A}_down(d){let{data:a,compare:h}=this,A=this.length>>1,C=a[d];for(;d=0)break;a[d]=a[D],d=D}a[d]=C}};function Fl(d,a,h=0,A=d.length-1,C=qn){for(;A>h;){if(A-h>600){let Z=A-h+1,re=a-h+1,ce=Math.log(Z),de=.5*Math.exp(2*ce/3),we=.5*Math.sqrt(ce*de*(Z-de)/Z)*(re-Z/2<0?-1:1);Fl(d,a,Math.max(h,Math.floor(a-re*de/Z+we)),Math.min(A,Math.floor(a+(Z-re)*de/Z+we)),C)}let D=d[a],O=h,G=A;for(Ac(d,h,a),C(d[A],D)>0&&Ac(d,h,A);O0;)G--}C(d[h],D)===0?Ac(d,h,G):(G++,Ac(d,G,A)),G<=a&&(h=G+1),a<=G&&(A=G-1)}}function Ac(d,a,h){let A=d[a];d[a]=d[h],d[h]=A}function qn(d,a){return da?1:0}function dt(d,a){if(d.length<=1)return[d];let h=[],A,C;for(let D of d){let O=Xh(D);O!==0&&(D.area=Math.abs(O),C===void 0&&(C=O<0),C===O<0?(A&&h.push(A),A=[D]):A.push(D))}if(A&&h.push(A),a>1)for(let D=0;D1?(re=a[Z+1][0],ce=a[Z+1][1]):Ee>0&&(re+=de/this.kx*Ee,ce+=we/this.ky*Ee)),de=this.wrap(h[0]-re)*this.kx,we=(h[1]-ce)*this.ky;let De=de*de+we*we;De180;)a-=360;return a}}function Je(d,a){return a[0]-d[0]}function yf(d){return d[1]-d[0]+1}function Bu(d,a){return d[1]>=d[0]&&d[1]d[1])return[null,null];let h=yf(d);if(a){if(h===2)return[d,null];let C=Math.floor(h/2);return[[d[0],d[0]+C],[d[0]+C,d[1]]]}if(h===1)return[d,null];let A=Math.floor(h/2)-1;return[[d[0],d[0]+A],[d[0]+A+1,d[1]]]}function qf(d,a){if(!Bu(a,d.length))return[1/0,1/0,-1/0,-1/0];let h=[1/0,1/0,-1/0,-1/0];for(let A=a[0];A<=a[1];++A)Ze(h,d[A]);return h}function Bt(d){let a=[1/0,1/0,-1/0,-1/0];for(let h of d)for(let A of h)Ze(a,A);return a}function ar(d){return d[0]!==-1/0&&d[1]!==-1/0&&d[2]!==1/0&&d[3]!==1/0}function Rr(d,a,h){if(!ar(d)||!ar(a))return NaN;let A=0,C=0;return d[2]a[2]&&(A=d[0]-a[2]),d[1]>a[3]&&(C=d[1]-a[3]),d[3]=A)return A;if(Is(C,D)){if(To(d,a))return 0}else if(To(a,d))return 0;let O=1/0;for(let G of d)for(let Z=0,re=G.length,ce=re-1;Z0;){let Z=O.pop();if(Z[0]>=D)continue;let re=Z[1],ce=a?50:100;if(yf(re)<=ce){if(!Bu(re,d.length))return NaN;if(a){let de=sn(d,re,h,A);if(isNaN(de)||de===0)return de;D=Math.min(D,de)}else for(let de=re[0];de<=re[1];++de){let we=Pn(d[de],h,A);if(D=Math.min(D,we),D===0)return 0}}else{let de=Wf(re,a);Ti(O,D,A,d,G,de[0]),Ti(O,D,A,d,G,de[1])}}return D}function Fs(d,a,h,A,C,D=1/0){let O=Math.min(D,C.distance(d[0],h[0]));if(O===0)return O;let G=new _h([[0,[0,d.length-1],[0,h.length-1]]],Je);for(;G.length>0;){let Z=G.pop();if(Z[0]>=O)continue;let re=Z[1],ce=Z[2],de=a?50:100,we=A?50:100;if(yf(re)<=de&&yf(ce)<=we){if(!Bu(re,d.length)&&Bu(ce,h.length))return NaN;let Ee;if(a&&A)Ee=Li(d,re,h,ce,C),O=Math.min(O,Ee);else if(a&&!A){let De=d.slice(re[0],re[1]+1);for(let ke=ce[0];ke<=ce[1];++ke)if(Ee=Ur(h[ke],De,C),O=Math.min(O,Ee),O===0)return O}else if(!a&&A){let De=h.slice(ce[0],ce[1]+1);for(let ke=re[0];ke<=re[1];++ke)if(Ee=Ur(d[ke],De,C),O=Math.min(O,Ee),O===0)return O}else Ee=yi(d,re,h,ce,C),O=Math.min(O,Ee)}else{let Ee=Wf(re,a),De=Wf(ce,A);Kn(G,O,C,d,h,Ee[0],De[0]),Kn(G,O,C,d,h,Ee[0],De[1]),Kn(G,O,C,d,h,Ee[1],De[0]),Kn(G,O,C,d,h,Ee[1],De[1])}}return O}function wl(d){return d.type==="MultiPolygon"?d.coordinates.map(a=>({type:"Polygon",coordinates:a})):d.type==="MultiLineString"?d.coordinates.map(a=>({type:"LineString",coordinates:a})):d.type==="MultiPoint"?d.coordinates.map(a=>({type:"Point",coordinates:a})):[d]}class yo{constructor(a,h){this.type=Or,this.geojson=a,this.geometries=h}static parse(a,h){if(a.length!==2)return h.error(`'distance' expression requires exactly one argument, but found ${a.length-1} instead.`);if(tu(a[1])){let A=a[1];if(A.type==="FeatureCollection")return new yo(A,A.features.map(C=>wl(C.geometry)).flat());if(A.type==="Feature")return new yo(A,wl(A.geometry));if("type"in A&&"coordinates"in A)return new yo(A,wl(A))}return h.error("'distance' expression requires valid geojson object that contains polygon geometry type.")}evaluate(a){if(a.geometry()!=null&&a.canonicalID()!=null){if(a.geometryType()==="Point")return function(h,A){let C=h.geometry(),D=C.flat().map(Z=>_o([Z.x,Z.y],h.canonical));if(C.length===0)return NaN;let O=new Ad(D[0][1]),G=1/0;for(let Z of A){switch(Z.type){case"Point":G=Math.min(G,Fs(D,!1,[Z.coordinates],!1,O,G));break;case"LineString":G=Math.min(G,Fs(D,!1,Z.coordinates,!0,O,G));break;case"Polygon":G=Math.min(G,zn(D,!1,Z.coordinates,O,G))}if(G===0)return G}return G}(a,this.geometries);if(a.geometryType()==="LineString")return function(h,A){let C=h.geometry(),D=C.flat().map(Z=>_o([Z.x,Z.y],h.canonical));if(C.length===0)return NaN;let O=new Ad(D[0][1]),G=1/0;for(let Z of A){switch(Z.type){case"Point":G=Math.min(G,Fs(D,!0,[Z.coordinates],!1,O,G));break;case"LineString":G=Math.min(G,Fs(D,!0,Z.coordinates,!0,O,G));break;case"Polygon":G=Math.min(G,zn(D,!0,Z.coordinates,O,G))}if(G===0)return G}return G}(a,this.geometries);if(a.geometryType()==="Polygon")return function(h,A){let C=h.geometry();if(C.length===0||C[0].length===0)return NaN;let D=dt(C,0).map(Z=>Z.map(re=>re.map(ce=>_o([ce.x,ce.y],h.canonical)))),O=new Ad(D[0][0][0][1]),G=1/0;for(let Z of A)for(let re of D){switch(Z.type){case"Point":G=Math.min(G,zn([Z.coordinates],!1,re,O,G));break;case"LineString":G=Math.min(G,zn(Z.coordinates,!0,re,O,G));break;case"Polygon":G=Math.min(G,Gi(re,Z.coordinates,O,G))}if(G===0)return G}return G}(a,this.geometries)}return NaN}eachChild(){}outputDefined(){return!0}}class tc{constructor(a){this.type=Ni,this.key=a}static parse(a,h){if(a.length!==2)return h.error(`Expected 1 argument, but found ${a.length-1} instead.`);let A=a[1];return A==null?h.error("Global state property must be defined."):typeof A!="string"?h.error(`Global state property must be string, but found ${typeof a[1]} instead.`):new tc(A)}evaluate(a){var h;let A=(h=a.globals)===null||h===void 0?void 0:h.globalState;return A&&Object.keys(A).length!==0?Ia(A,this.key):null}eachChild(){}outputDefined(){return!1}}let Tl={"==":gs,"!=":Io,">":al,"<":bl,">=":Ka,"<=":Da,array:Ec,at:ms,boolean:Ec,case:Hd,coalesce:li,collator:Yn,format:Hr,image:wi,in:Ao,"index-of":$h,interpolate:Ut,"interpolate-hcl":Ut,"interpolate-lab":Ut,length:ni,let:ra,literal:Ws,match:us,number:Ec,"number-format":Er,object:Ec,slice:q,step:pe,string:Ec,"to-boolean":qh,"to-color":qh,"to-number":qh,"to-string":qh,var:mh,within:gc,distance:yo,"global-state":tc};class ll{constructor(a,h,A,C){this.name=a,this.type=h,this._evaluate=A,this.args=C}evaluate(a){return this._evaluate(a,this.args)}eachChild(a){this.args.forEach(a)}outputDefined(){return!1}static parse(a,h){let A=a[0],C=ll.definitions[A];if(!C)return h.error(`Unknown expression "${A}". If you wanted a literal array, use ["literal", [...]].`,0);let D=Array.isArray(C)?C[0]:C.type,O=Array.isArray(C)?[[C[1],C[2]]]:C.overloads,G=O.filter(([re])=>!Array.isArray(re)||re.length===a.length-1),Z=null;for(let[re,ce]of G){Z=new Af(h.registry,Pa,h.path,null,h.scope);let de=[],we=!1;for(let Ee=1;Ee{return we=de,Array.isArray(we)?`(${we.map(Qn).join(", ")})`:`(${Qn(we.type)}...)`;var we}).join(" | "),ce=[];for(let de=1;de{h=a?h&&Pa(A):h&&A instanceof Ws}),!!h&&Yi(d)&&qd(d,["zoom","heatmap-density","elevation","line-progress","accumulated","is-supported-script"])}function Yi(d){if(d instanceof ll&&(d.name==="get"&&d.args.length===1||d.name==="feature-state"||d.name==="has"&&d.args.length===1||d.name==="properties"||d.name==="geometry-type"||d.name==="id"||/^filter-/.test(d.name))||d instanceof gc||d instanceof yo)return!1;let a=!0;return d.eachChild(h=>{a&&!Yi(h)&&(a=!1)}),a}function Cc(d){if(d instanceof ll&&d.name==="feature-state")return!1;let a=!0;return d.eachChild(h=>{a&&!Cc(h)&&(a=!1)}),a}function qd(d,a){if(d instanceof ll&&a.indexOf(d.name)>=0)return!1;let h=!0;return d.eachChild(A=>{h&&!qd(A,a)&&(h=!1)}),h}function yd(d){return{result:"success",value:d}}function ru(d){return{result:"error",value:d}}function kl(d){return d["property-type"]==="data-driven"||d["property-type"]==="cross-faded-data-driven"}function Mp(d){return!!d.expression&&d.expression.parameters.indexOf("zoom")>-1}function ki(d){return!!d.expression&&d.expression.interpolated}function Zo(d){return d instanceof Number?"number":d instanceof String?"string":d instanceof Boolean?"boolean":Array.isArray(d)?"array":d===null?"null":typeof d}function vd(d){return typeof d=="object"&&d!==null&&!Array.isArray(d)&&La(d)===Ii}function Jm(d){return d}function Zu(d,a){let h=d.stops&&typeof d.stops[0][0]=="object",A=h||!(h||d.property!==void 0),C=d.type||(ki(a)?"exponential":"interval"),D=function(ce){switch(ce.type){case"color":return Mo.parse;case"padding":return Jl.parse;case"numberArray":return _a.parse;case"colorArray":return Zn.parse;default:return null}}(a);if(D&&((d=ei({},d)).stops&&(d.stops=d.stops.map(ce=>[ce[0],D(ce[1])])),d.default=D(d.default?d.default:a.default)),d.colorSpace&&(O=d.colorSpace)!=="rgb"&&O!=="hcl"&&O!=="lab")throw new Error(`Unknown color space: "${d.colorSpace}"`);var O;let G=function(ce){switch(ce){case"exponential":return ua;case"interval":return tg;case"categorical":return eg;case"identity":return mu;default:throw new Error(`Unknown function type "${ce}"`)}}(C),Z,re;if(C==="categorical"){Z=Object.create(null);for(let ce of d.stops)Z[ce[0]]=ce[1];re=typeof d.stops[0][0]}if(h){let ce={},de=[];for(let De=0;DeDe[0]),evaluate:({zoom:De},ke)=>ua({stops:we,base:d.base},a,De).evaluate(De,ke)}}if(A){let ce=C==="exponential"?{name:"exponential",base:d.base!==void 0?d.base:1}:null;return{kind:"camera",interpolationType:ce,interpolationFactor:Ut.interpolationFactor.bind(void 0,ce),zoomStops:d.stops.map(de=>de[0]),evaluate:({zoom:de})=>G(d,a,de,Z,re)}}return{kind:"source",evaluate(ce,de){let we=de&&de.properties?de.properties[d.property]:void 0;return we===void 0?Mc(d.default,a.default):G(d,a,we,Z,re)}}}function Mc(d,a,h){return d!==void 0?d:a!==void 0?a:h!==void 0?h:void 0}function eg(d,a,h,A,C){return Mc(typeof h===C?A[h]:void 0,d.default,a.default)}function tg(d,a,h){if(Zo(h)!=="number")return Mc(d.default,a.default);let A=d.stops.length;if(A===1||h<=d.stops[0][0])return d.stops[0][1];if(h>=d.stops[A-1][0])return d.stops[A-1][1];let C=oe(d.stops.map(D=>D[0]),h);return d.stops[C][1]}function ua(d,a,h){let A=d.base!==void 0?d.base:1;if(Zo(h)!=="number")return Mc(d.default,a.default);let C=d.stops.length;if(C===1||h<=d.stops[0][0])return d.stops[0][1];if(h>=d.stops[C-1][0])return d.stops[C-1][1];let D=oe(d.stops.map(ce=>ce[0]),h),O=function(ce,de,we,Ee){let De=Ee-we,ke=ce-we;return De===0?0:de===1?ke/De:(Math.pow(de,ke)-1)/(Math.pow(de,De)-1)}(h,A,d.stops[D][0],d.stops[D+1][0]),G=d.stops[D][1],Z=d.stops[D+1][1],re=Fr[a.type]||Jm;return typeof G.evaluate=="function"?{evaluate(...ce){let de=G.evaluate.apply(void 0,ce),we=Z.evaluate.apply(void 0,ce);if(de!==void 0&&we!==void 0)return re(de,we,O,d.colorSpace)}}:re(G,Z,O,d.colorSpace)}function mu(d,a,h){switch(a.type){case"color":h=Mo.parse(h);break;case"formatted":h=Ql.fromString(h.toString());break;case"resolvedImage":h=Ol.fromString(h.toString());break;case"padding":h=Jl.parse(h);break;case"colorArray":h=Zn.parse(h);break;case"numberArray":h=_a.parse(h);break;default:Zo(h)===a.type||a.type==="enum"&&a.values[h]||(h=void 0)}return Mc(h,d.default,a.default)}ll.register(Tl,{error:[{kind:"error"},[fi],(d,[a])=>{throw new Hs(a.evaluate(d))}],typeof:[fi,[Ni],(d,[a])=>Qn(La(a.evaluate(d)))],"to-rgba":[uo(Or,4),[Di],(d,[a])=>{let[h,A,C,D]=a.evaluate(d).rgb;return[255*h,255*A,255*C,D]}],rgb:[Di,[Or,Or,Or],Oa],rgba:[Di,[Or,Or,Or,Or],Oa],has:{type:pr,overloads:[[[fi],(d,[a])=>Sl(a.evaluate(d),d.properties())],[[fi,Ii],(d,[a,h])=>Sl(a.evaluate(d),h.evaluate(d))]]},get:{type:Ni,overloads:[[[fi],(d,[a])=>_d(a.evaluate(d),d.properties())],[[fi,Ii],(d,[a,h])=>_d(a.evaluate(d),h.evaluate(d))]]},"feature-state":[Ni,[fi],(d,[a])=>_d(a.evaluate(d),d.featureState||{})],properties:[Ii,[],d=>d.properties()],"geometry-type":[fi,[],d=>d.geometryType()],id:[Ni,[],d=>d.id()],zoom:[Or,[],d=>d.globals.zoom],"heatmap-density":[Or,[],d=>d.globals.heatmapDensity||0],elevation:[Or,[],d=>d.globals.elevation||0],"line-progress":[Or,[],d=>d.globals.lineProgress||0],accumulated:[Ni,[],d=>d.globals.accumulated===void 0?null:d.globals.accumulated],"+":[Or,vf(Or),(d,a)=>{let h=0;for(let A of a)h+=A.evaluate(d);return h}],"*":[Or,vf(Or),(d,a)=>{let h=1;for(let A of a)h*=A.evaluate(d);return h}],"-":{type:Or,overloads:[[[Or,Or],(d,[a,h])=>a.evaluate(d)-h.evaluate(d)],[[Or],(d,[a])=>-a.evaluate(d)]]},"/":[Or,[Or,Or],(d,[a,h])=>a.evaluate(d)/h.evaluate(d)],"%":[Or,[Or,Or],(d,[a,h])=>a.evaluate(d)%h.evaluate(d)],ln2:[Or,[],()=>Math.LN2],pi:[Or,[],()=>Math.PI],e:[Or,[],()=>Math.E],"^":[Or,[Or,Or],(d,[a,h])=>Math.pow(a.evaluate(d),h.evaluate(d))],sqrt:[Or,[Or],(d,[a])=>Math.sqrt(a.evaluate(d))],log10:[Or,[Or],(d,[a])=>Math.log(a.evaluate(d))/Math.LN10],ln:[Or,[Or],(d,[a])=>Math.log(a.evaluate(d))],log2:[Or,[Or],(d,[a])=>Math.log(a.evaluate(d))/Math.LN2],sin:[Or,[Or],(d,[a])=>Math.sin(a.evaluate(d))],cos:[Or,[Or],(d,[a])=>Math.cos(a.evaluate(d))],tan:[Or,[Or],(d,[a])=>Math.tan(a.evaluate(d))],asin:[Or,[Or],(d,[a])=>Math.asin(a.evaluate(d))],acos:[Or,[Or],(d,[a])=>Math.acos(a.evaluate(d))],atan:[Or,[Or],(d,[a])=>Math.atan(a.evaluate(d))],min:[Or,vf(Or),(d,a)=>Math.min(...a.map(h=>h.evaluate(d)))],max:[Or,vf(Or),(d,a)=>Math.max(...a.map(h=>h.evaluate(d)))],abs:[Or,[Or],(d,[a])=>Math.abs(a.evaluate(d))],round:[Or,[Or],(d,[a])=>{let h=a.evaluate(d);return h<0?-Math.round(-h):Math.round(h)}],floor:[Or,[Or],(d,[a])=>Math.floor(a.evaluate(d))],ceil:[Or,[Or],(d,[a])=>Math.ceil(a.evaluate(d))],"filter-==":[pr,[fi,Ni],(d,[a,h])=>d.properties()[a.value]===h.value],"filter-id-==":[pr,[Ni],(d,[a])=>d.id()===a.value],"filter-type-==":[pr,[fi],(d,[a])=>d.geometryType()===a.value],"filter-<":[pr,[fi,Ni],(d,[a,h])=>{let A=d.properties()[a.value],C=h.value;return typeof A==typeof C&&A{let h=d.id(),A=a.value;return typeof h==typeof A&&h":[pr,[fi,Ni],(d,[a,h])=>{let A=d.properties()[a.value],C=h.value;return typeof A==typeof C&&A>C}],"filter-id->":[pr,[Ni],(d,[a])=>{let h=d.id(),A=a.value;return typeof h==typeof A&&h>A}],"filter-<=":[pr,[fi,Ni],(d,[a,h])=>{let A=d.properties()[a.value],C=h.value;return typeof A==typeof C&&A<=C}],"filter-id-<=":[pr,[Ni],(d,[a])=>{let h=d.id(),A=a.value;return typeof h==typeof A&&h<=A}],"filter->=":[pr,[fi,Ni],(d,[a,h])=>{let A=d.properties()[a.value],C=h.value;return typeof A==typeof C&&A>=C}],"filter-id->=":[pr,[Ni],(d,[a])=>{let h=d.id(),A=a.value;return typeof h==typeof A&&h>=A}],"filter-has":[pr,[Ni],(d,[a])=>a.value in d.properties()],"filter-has-id":[pr,[],d=>d.id()!==null&&d.id()!==void 0],"filter-type-in":[pr,[uo(fi)],(d,[a])=>a.value.indexOf(d.geometryType())>=0],"filter-id-in":[pr,[uo(Ni)],(d,[a])=>a.value.indexOf(d.id())>=0],"filter-in-small":[pr,[fi,uo(Ni)],(d,[a,h])=>h.value.indexOf(d.properties()[a.value])>=0],"filter-in-large":[pr,[fi,uo(Ni)],(d,[a,h])=>function(A,C,D,O){for(;D<=O;){let G=D+O>>1;if(C[G]===A)return!0;C[G]>A?O=G-1:D=G+1}return!1}(d.properties()[a.value],h.value,0,h.value.length-1)],all:{type:pr,overloads:[[[pr,pr],(d,[a,h])=>a.evaluate(d)&&h.evaluate(d)],[vf(pr),(d,a)=>{for(let h of a)if(!h.evaluate(d))return!1;return!0}]]},any:{type:pr,overloads:[[[pr,pr],(d,[a,h])=>a.evaluate(d)||h.evaluate(d)],[vf(pr),(d,a)=>{for(let h of a)if(h.evaluate(d))return!0;return!1}]]},"!":[pr,[pr],(d,[a])=>!a.evaluate(d)],"is-supported-script":[pr,[fi],(d,[a])=>{let h=d.globals&&d.globals.isSupportedScript;return!h||h(a.evaluate(d))}],upcase:[fi,[fi],(d,[a])=>a.evaluate(d).toUpperCase()],downcase:[fi,[fi],(d,[a])=>a.evaluate(d).toLowerCase()],concat:[fi,vf(Ni),(d,a)=>a.map(h=>xl(h.evaluate(d))).join("")],"resolved-locale":[fi,[zi],(d,[a])=>a.evaluate(d).resolvedLocale()]});class Ic{constructor(a,h,A){this.expression=a,this._warningHistory={},this._evaluator=new Cp,this._defaultValue=h?function(C){if(C.type==="color"&&vd(C.default))return new Mo(0,0,0,0);switch(C.type){case"color":return Mo.parse(C.default)||null;case"padding":return Jl.parse(C.default)||null;case"numberArray":return _a.parse(C.default)||null;case"colorArray":return Zn.parse(C.default)||null;case"variableAnchorOffsetCollection":return sl.parse(C.default)||null;case"projectionDefinition":return vl.parse(C.default)||null;default:return C.default===void 0?null:C.default}}(h):null,this._enumValues=h&&h.type==="enum"?h.values:null,this._globalState=A}evaluateWithoutErrorHandling(a,h,A,C,D,O){return this._globalState&&(a=ha(a,this._globalState)),this._evaluator.globals=a,this._evaluator.feature=h,this._evaluator.featureState=A,this._evaluator.canonical=C,this._evaluator.availableImages=D||null,this._evaluator.formattedSection=O,this.expression.evaluate(this._evaluator)}evaluate(a,h,A,C,D,O){this._globalState&&(a=ha(a,this._globalState)),this._evaluator.globals=a,this._evaluator.feature=h||null,this._evaluator.featureState=A||null,this._evaluator.canonical=C,this._evaluator.availableImages=D||null,this._evaluator.formattedSection=O||null;try{let G=this.expression.evaluate(this._evaluator);if(G==null||typeof G=="number"&&G!=G)return this._defaultValue;if(this._enumValues&&!(G in this._enumValues))throw new Hs(`Expected value to be one of ${Object.keys(this._enumValues).map(Z=>JSON.stringify(Z)).join(", ")}, but found ${JSON.stringify(G)} instead.`);return G}catch(G){return this._warningHistory[G.message]||(this._warningHistory[G.message]=!0,typeof console<"u"&&console.warn(G.message)),this._defaultValue}}}function yh(d){return Array.isArray(d)&&d.length>0&&typeof d[0]=="string"&&d[0]in Tl}function $d(d,a,h){let A=new Af(Tl,Pa,[],a?function(D){let O={color:Di,string:fi,number:Or,enum:fi,boolean:pr,formatted:po,padding:Ls,numberArray:No,colorArray:co,projectionDefinition:Mi,resolvedImage:wo,variableAnchorOffsetCollection:Al};return D.type==="array"?uo(O[D.value]||Ni,D.length):O[D.type]}(a):void 0),C=A.parse(d,void 0,void 0,void 0,a&&a.type==="string"?{typeAnnotation:"coerce"}:void 0);return C?yd(new Ic(C,a,h)):ru(A.errors)}class $f{constructor(a,h,A){this.kind=a,this._styleExpression=h,this.isStateDependent=a!=="constant"&&!Cc(h.expression),this.globalStateRefs=Nl(h.expression),this._globalState=A}evaluateWithoutErrorHandling(a,h,A,C,D,O){return this._globalState&&(a=ha(a,this._globalState)),this._styleExpression.evaluateWithoutErrorHandling(a,h,A,C,D,O)}evaluate(a,h,A,C,D,O){return this._globalState&&(a=ha(a,this._globalState)),this._styleExpression.evaluate(a,h,A,C,D,O)}}class Yu{constructor(a,h,A,C,D){this.kind=a,this.zoomStops=A,this._styleExpression=h,this.isStateDependent=a!=="camera"&&!Cc(h.expression),this.globalStateRefs=Nl(h.expression),this.interpolationType=C,this._globalState=D}evaluateWithoutErrorHandling(a,h,A,C,D,O){return this._globalState&&(a=ha(a,this._globalState)),this._styleExpression.evaluateWithoutErrorHandling(a,h,A,C,D,O)}evaluate(a,h,A,C,D,O){return this._globalState&&(a=ha(a,this._globalState)),this._styleExpression.evaluate(a,h,A,C,D,O)}interpolationFactor(a,h,A){return this.interpolationType?Ut.interpolationFactor(this.interpolationType,a,h,A):0}}function gu(d,a,h){let A=$d(d,a,h);if(A.result==="error")return A;let C=A.value.expression,D=Yi(C);if(!D&&!kl(a))return ru([new ui("","data expressions not supported")]);let O=qd(C,["zoom"]);if(!O&&!Mp(a))return ru([new ui("","zoom expressions not supported")]);let G=Lu(C);return G||O?G instanceof ui?ru([G]):G instanceof Ut&&!ki(a)?ru([new ui("",'"interpolate" expressions cannot be used with this property')]):yd(G?new Yu(D?"camera":"composite",A.value,G.labels,G instanceof Ut?G.interpolation:void 0,h):new $f(D?"constant":"source",A.value,h)):ru([new ui("",'"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.')])}class So{constructor(a,h){this._parameters=a,this._specification=h,ei(this,Zu(this._parameters,this._specification))}static deserialize(a){return new So(a._parameters,a._specification)}static serialize(a){return{_parameters:a._parameters,_specification:a._specification}}}function Lu(d){let a=null;if(d instanceof ra)a=Lu(d.result);else if(d instanceof li){for(let h of d.args)if(a=Lu(h),a)break}else(d instanceof pe||d instanceof Ut)&&d.input instanceof ll&&d.input.name==="zoom"&&(a=d);return a instanceof ui||d.eachChild(h=>{let A=Lu(h);A instanceof ui?a=A:!a&&A?a=new ui("",'"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.'):a&&A&&a!==A&&(a=new ui("",'Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.'))}),a}function Nl(d,a=new Set){return d instanceof tc&&a.add(d.key),d.eachChild(h=>{Nl(h,a)}),a}function ha(d,a){let{zoom:h,heatmapDensity:A,elevation:C,lineProgress:D,isSupportedScript:O,accumulated:G}=d??{};return{zoom:h,heatmapDensity:A,elevation:C,lineProgress:D,isSupportedScript:O,accumulated:G,globalState:a}}function xf(d){if(d===!0||d===!1)return!0;if(!Array.isArray(d)||d.length===0)return!1;switch(d[0]){case"has":return d.length>=2&&d[1]!=="$id"&&d[1]!=="$type";case"in":return d.length>=3&&(typeof d[1]!="string"||Array.isArray(d[2]));case"!in":case"!has":case"none":return!1;case"==":case"!=":case">":case">=":case"<":case"<=":return d.length!==3||Array.isArray(d[1])||Array.isArray(d[2]);case"any":case"all":for(let a of d.slice(1))if(!xf(a)&&typeof a!="boolean")return!1;return!0;default:return!0}}let Xf={type:"boolean",default:!1,transition:!1,"property-type":"data-driven",expression:{interpolated:!1,parameters:["zoom","feature"]}};function Pc(d,a){if(d==null)return{filter:()=>!0,needGeometry:!1,getGlobalStateRefs:()=>new Set};xf(d)||(d=An(d));let h=$d(d,Xf,a);if(h.result==="error")throw new Error(h.value.map(A=>`${A.key}: ${A.message}`).join(", "));return{filter:(A,C,D)=>h.value.evaluate(A,C,{},D),needGeometry:Ku(d),getGlobalStateRefs:()=>Nl(h.value.expression)}}function El(d,a){return da?1:0}function Ku(d){if(!Array.isArray(d))return!1;if(d[0]==="within"||d[0]==="distance")return!0;for(let a=1;a"||a==="<="||a===">="?nn(d[1],d[2],a):a==="any"?(h=d.slice(1),["any"].concat(h.map(An))):a==="all"?["all"].concat(d.slice(1).map(An)):a==="none"?["all"].concat(d.slice(1).map(An).map(Au)):a==="in"?Xd(d[1],d.slice(2)):a==="!in"?Au(Xd(d[1],d.slice(2))):a==="has"?Vc(d[1]):a!=="!has"||Au(Vc(d[1]));var h}function nn(d,a,h){switch(d){case"$type":return[`filter-type-${h}`,a];case"$id":return[`filter-id-${h}`,a];default:return[`filter-${h}`,d,a]}}function Xd(d,a){if(a.length===0)return!1;switch(d){case"$type":return["filter-type-in",["literal",a]];case"$id":return["filter-id-in",["literal",a]];default:return a.length>200&&!a.some(h=>typeof h!=typeof a[0])?["filter-in-large",d,["literal",a.sort(El)]]:["filter-in-small",d,["literal",a]]}}function Vc(d){switch(d){case"$type":return!0;case"$id":return["filter-has-id"];default:return["filter-has",d]}}function Au(d){return["!",d]}function Cl(d){let a=typeof d;if(a==="number"||a==="boolean"||a==="string"||d==null)return JSON.stringify(d);if(Array.isArray(d)){let C="[";for(let D of d)C+=`${Cl(D)},`;return`${C}]`}let h=Object.keys(d).sort(),A="{";for(let C=0;CA.maximum?[new Ct(a,h,`${h} is greater than the maximum value ${A.maximum}`)]:[]}function Pp(d){let a=d.valueSpec,h=ja(d.value.type),A,C,D,O={},G=h!=="categorical"&&d.value.property===void 0,Z=!G,re=Zo(d.value.stops)==="array"&&Zo(d.value.stops[0])==="array"&&Zo(d.value.stops[0][0])==="object",ce=fa({key:d.key,value:d.value,valueSpec:d.styleSpec.function,validateSpec:d.validateSpec,style:d.style,styleSpec:d.styleSpec,objectElementValidators:{stops:function(Ee){if(h==="identity")return[new Ct(Ee.key,Ee.value,'identity function may not have a "stops" property')];let De=[],ke=Ee.value;return De=De.concat(Ip({key:Ee.key,value:ke,valueSpec:Ee.valueSpec,validateSpec:Ee.validateSpec,style:Ee.style,styleSpec:Ee.styleSpec,arrayElementValidator:de})),Zo(ke)==="array"&&ke.length===0&&De.push(new Ct(Ee.key,ke,"array must have at least one stop")),De},default:function(Ee){return Ee.validateSpec({key:Ee.key,value:Ee.value,valueSpec:a,validateSpec:Ee.validateSpec,style:Ee.style,styleSpec:Ee.styleSpec})}}});return h==="identity"&&G&&ce.push(new Ct(d.key,d.value,'missing required property "property"')),h==="identity"||d.value.stops||ce.push(new Ct(d.key,d.value,'missing required property "stops"')),h==="exponential"&&d.valueSpec.expression&&!ki(d.valueSpec)&&ce.push(new Ct(d.key,d.value,"exponential functions not supported")),d.styleSpec.$version>=8&&(Z&&!kl(d.valueSpec)?ce.push(new Ct(d.key,d.value,"property functions not supported")):G&&!Mp(d.valueSpec)&&ce.push(new Ct(d.key,d.value,"zoom functions not supported"))),h!=="categorical"&&!re||d.value.property!==void 0||ce.push(new Ct(d.key,d.value,'"property" property is required')),ce;function de(Ee){let De=[],ke=Ee.value,$e=Ee.key;if(Zo(ke)!=="array")return[new Ct($e,ke,`array expected, ${Zo(ke)} found`)];if(ke.length!==2)return[new Ct($e,ke,`array length 2 expected, length ${ke.length} found`)];if(re){if(Zo(ke[0])!=="object")return[new Ct($e,ke,`object expected, ${Zo(ke[0])} found`)];if(ke[0].zoom===void 0)return[new Ct($e,ke,"object stop key must have zoom")];if(ke[0].value===void 0)return[new Ct($e,ke,"object stop key must have value")];if(D&&D>ja(ke[0].zoom))return[new Ct($e,ke[0].zoom,"stop zoom values must appear in ascending order")];ja(ke[0].zoom)!==D&&(D=ja(ke[0].zoom),C=void 0,O={}),De=De.concat(fa({key:`${$e}[0]`,value:ke[0],valueSpec:{zoom:{}},validateSpec:Ee.validateSpec,style:Ee.style,styleSpec:Ee.styleSpec,objectElementValidators:{zoom:Kh,value:we}}))}else De=De.concat(we({key:`${$e}[0]`,value:ke[0],validateSpec:Ee.validateSpec,style:Ee.style,styleSpec:Ee.styleSpec},ke));return yh(As(ke[1]))?De.concat([new Ct(`${$e}[1]`,ke[1],"expressions are not allowed in function stops.")]):De.concat(Ee.validateSpec({key:`${$e}[1]`,value:ke[1],valueSpec:a,validateSpec:Ee.validateSpec,style:Ee.style,styleSpec:Ee.styleSpec}))}function we(Ee,De){let ke=Zo(Ee.value),$e=ja(Ee.value),st=Ee.value!==null?Ee.value:De;if(A){if(ke!==A)return[new Ct(Ee.key,st,`${ke} stop domain type must match previous stop domain type ${A}`)]}else A=ke;if(ke!=="number"&&ke!=="string"&&ke!=="boolean")return[new Ct(Ee.key,st,"stop domain value must be a number, string, or boolean")];if(ke!=="number"&&h!=="categorical"){let kt=`number expected, ${ke} found`;return kl(a)&&h===void 0&&(kt+='\nIf you intended to use a categorical function, specify `"type": "categorical"`.'),[new Ct(Ee.key,st,kt)]}return h!=="categorical"||ke!=="number"||isFinite($e)&&Math.floor($e)===$e?h!=="categorical"&&ke==="number"&&C!==void 0&&$enew Ct(`${d.key}${A.key}`,d.value,A.message));let h=a.value.expression||a.value._styleExpression.expression;if(d.expressionContext==="property"&&d.propertyKey==="text-font"&&!h.outputDefined())return[new Ct(d.key,d.value,`Invalid data expression for "${d.propertyKey}". Output values must be contained as literals within the expression.`)];if(d.expressionContext==="property"&&d.propertyType==="layout"&&!Cc(h))return[new Ct(d.key,d.value,'"feature-state" data expressions are not supported with layout properties.')];if(d.expressionContext==="filter"&&!Cc(h))return[new Ct(d.key,d.value,'"feature-state" data expressions are not supported with filters.')];if(d.expressionContext&&d.expressionContext.indexOf("cluster")===0){if(!qd(h,["zoom","feature-state"]))return[new Ct(d.key,d.value,'"zoom" and "feature-state" expressions are not supported with cluster properties.')];if(d.expressionContext==="cluster-initial"&&!Yi(h))return[new Ct(d.key,d.value,"Feature data expressions are not supported with initial expression part of cluster properties.")]}return[]}function Rc(d){let a=d.key,h=d.value,A=Zo(h);return A!=="string"?[new Ct(a,h,`color expected, ${A} found`)]:Mo.parse(String(h))?[]:[new Ct(a,h,`color expected, "${h}" found`)]}function vh(d){let a=d.key,h=d.value,A=d.valueSpec,C=[];return Array.isArray(A.values)?A.values.indexOf(ja(h))===-1&&C.push(new Ct(a,h,`expected one of [${A.values.join(", ")}], ${JSON.stringify(h)} found`)):Object.keys(A.values).indexOf(ja(h))===-1&&C.push(new Ct(a,h,`expected one of [${Object.keys(A.values).join(", ")}], ${JSON.stringify(h)} found`)),C}function _u(d){return xf(As(d.value))?_c(ei({},d,{expressionContext:"filter",valueSpec:{value:"boolean"}})):Zf(d)}function Zf(d){let a=d.value,h=d.key;if(Zo(a)!=="array")return[new Ct(h,a,`array expected, ${Zo(a)} found`)];let A=d.styleSpec,C,D=[];if(a.length<1)return[new Ct(h,a,"filter array must have at least 1 element")];switch(D=D.concat(vh({key:`${h}[0]`,value:a[0],valueSpec:A.filter_operator,style:d.style,styleSpec:d.styleSpec})),ja(a[0])){case"<":case"<=":case">":case">=":a.length>=2&&ja(a[1])==="$type"&&D.push(new Ct(h,a,`"$type" cannot be use with operator "${a[0]}"`));case"==":case"!=":a.length!==3&&D.push(new Ct(h,a,`filter array for operator "${a[0]}" must have 3 elements`));case"in":case"!in":a.length>=2&&(C=Zo(a[1]),C!=="string"&&D.push(new Ct(`${h}[1]`,a[1],`string expected, ${C} found`)));for(let O=2;O{re in h&&a.push(new Ct(A,h[re],`"${re}" is prohibited for ref layers`))}),C.layers.forEach(re=>{ja(re.id)===G&&(Z=re)}),Z?Z.ref?a.push(new Ct(A,h.ref,"ref cannot reference another ref layer")):O=ja(Z.type):a.push(new Ct(A,h.ref,`ref layer "${G}" not found`))}else if(O!=="background")if(h.source){let Z=C.sources&&C.sources[h.source],re=Z&&ja(Z.type);Z?re==="vector"&&O==="raster"?a.push(new Ct(A,h.source,`layer "${h.id}" requires a raster source`)):re!=="raster-dem"&&O==="hillshade"||re!=="raster-dem"&&O==="color-relief"?a.push(new Ct(A,h.source,`layer "${h.id}" requires a raster-dem source`)):re==="raster"&&O!=="raster"?a.push(new Ct(A,h.source,`layer "${h.id}" requires a vector source`)):re!=="vector"||h["source-layer"]?re==="raster-dem"&&O!=="hillshade"&&O!=="color-relief"?a.push(new Ct(A,h.source,"raster-dem source can only be used with layer type 'hillshade' or 'color-relief'.")):O!=="line"||!h.paint||!h.paint["line-gradient"]||re==="geojson"&&Z.lineMetrics||a.push(new Ct(A,h,`layer "${h.id}" specifies a line-gradient, which requires a GeoJSON source with \`lineMetrics\` enabled.`)):a.push(new Ct(A,h,`layer "${h.id}" must specify a "source-layer"`)):a.push(new Ct(A,h.source,`source "${h.source}" not found`))}else a.push(new Ct(A,h,'missing required property "source"'));return a=a.concat(fa({key:A,value:h,valueSpec:D.layer,style:d.style,styleSpec:d.styleSpec,validateSpec:d.validateSpec,objectElementValidators:{"*":()=>[],type:()=>d.validateSpec({key:`${A}.type`,value:h.type,valueSpec:D.layer.type,style:d.style,styleSpec:d.styleSpec,validateSpec:d.validateSpec,object:h,objectKey:"type"}),filter:_u,layout:Z=>fa({layer:h,key:Z.key,value:Z.value,style:Z.style,styleSpec:Z.styleSpec,validateSpec:Z.validateSpec,objectElementValidators:{"*":re=>mm(ei({layerType:O},re))}}),paint:Z=>fa({layer:h,key:Z.key,value:Z.value,style:Z.style,styleSpec:Z.styleSpec,validateSpec:Z.validateSpec,objectElementValidators:{"*":re=>xd(ei({layerType:O},re))}})}})),a}function Qu(d){let a=d.value,h=d.key,A=Zo(a);return A!=="string"?[new Ct(h,a,`string expected, ${A} found`)]:[]}let Yd={promoteId:function({key:d,value:a}){if(Zo(a)==="string")return Qu({key:d,value:a});{let h=[];for(let A in a)h.push(...Qu({key:`${d}.${A}`,value:a[A]}));return h}}};function Qh(d){let a=d.value,h=d.key,A=d.styleSpec,C=d.style,D=d.validateSpec;if(!a.type)return[new Ct(h,a,'"type" is required')];let O=ja(a.type),G;switch(O){case"vector":case"raster":return G=fa({key:h,value:a,valueSpec:A[`source_${O.replace("-","_")}`],style:d.style,styleSpec:A,objectElementValidators:Yd,validateSpec:D}),G;case"raster-dem":return G=function(Z){var re;let ce=(re=Z.sourceName)!==null&&re!==void 0?re:"",de=Z.value,we=Z.styleSpec,Ee=we.source_raster_dem,De=Z.style,ke=[],$e=Zo(de);if(de===void 0)return ke;if($e!=="object")return ke.push(new Ct("source_raster_dem",de,`object expected, ${$e} found`)),ke;let st=ja(de.encoding)==="custom",kt=["redFactor","greenFactor","blueFactor","baseShift"],pt=Z.value.encoding?`"${Z.value.encoding}"`:"Default";for(let _t in de)!st&&kt.includes(_t)?ke.push(new Ct(_t,de[_t],`In "${ce}": "${_t}" is only valid when "encoding" is set to "custom". ${pt} encoding found`)):Ee[_t]?ke=ke.concat(Z.validateSpec({key:_t,value:de[_t],valueSpec:Ee[_t],validateSpec:Z.validateSpec,style:De,styleSpec:we})):ke.push(new Ct(_t,de[_t],`unknown property "${_t}"`));return ke}({sourceName:h,value:a,style:d.style,styleSpec:A,validateSpec:D}),G;case"geojson":if(G=fa({key:h,value:a,valueSpec:A.source_geojson,style:C,styleSpec:A,validateSpec:D,objectElementValidators:Yd}),a.cluster)for(let Z in a.clusterProperties){let[re,ce]=a.clusterProperties[Z],de=typeof re=="string"?[re,["accumulated"],["get",Z]]:re;G.push(..._c({key:`${h}.${Z}.map`,value:ce,expressionContext:"cluster-map"})),G.push(..._c({key:`${h}.${Z}.reduce`,value:de,expressionContext:"cluster-reduce"}))}return G;case"video":return fa({key:h,value:a,valueSpec:A.source_video,style:C,validateSpec:D,styleSpec:A});case"image":return fa({key:h,value:a,valueSpec:A.source_image,style:C,validateSpec:D,styleSpec:A});case"canvas":return[new Ct(h,null,"Please use runtime APIs to add canvas sources, rather than including them in stylesheets.","source.canvas")];default:return vh({key:`${h}.type`,value:a.type,valueSpec:{values:["vector","raster","raster-dem","geojson","video","image"]}})}}function bh(d){let a=d.value,h=d.styleSpec,A=h.light,C=d.style,D=[],O=Zo(a);if(a===void 0)return D;if(O!=="object")return D=D.concat([new Ct("light",a,`object expected, ${O} found`)]),D;for(let G in a){let Z=G.match(/^(.*)-transition$/);D=D.concat(Z&&A[Z[1]]&&A[Z[1]].transition?d.validateSpec({key:G,value:a[G],valueSpec:h.transition,validateSpec:d.validateSpec,style:C,styleSpec:h}):A[G]?d.validateSpec({key:G,value:a[G],valueSpec:A[G],validateSpec:d.validateSpec,style:C,styleSpec:h}):[new Ct(G,a[G],`unknown property "${G}"`)])}return D}function Rp(d){let a=d.value,h=d.styleSpec,A=h.sky,C=d.style,D=Zo(a);if(a===void 0)return[];if(D!=="object")return[new Ct("sky",a,`object expected, ${D} found`)];let O=[];for(let G in a)O=O.concat(A[G]?d.validateSpec({key:G,value:a[G],valueSpec:A[G],style:C,styleSpec:h}):[new Ct(G,a[G],`unknown property "${G}"`)]);return O}function bf(d){let a=d.value,h=d.styleSpec,A=h.terrain,C=d.style,D=[],O=Zo(a);if(a===void 0)return D;if(O!=="object")return D=D.concat([new Ct("terrain",a,`object expected, ${O} found`)]),D;for(let G in a)D=D.concat(A[G]?d.validateSpec({key:G,value:a[G],valueSpec:A[G],validateSpec:d.validateSpec,style:C,styleSpec:h}):[new Ct(G,a[G],`unknown property "${G}"`)]);return D}function Du(d){let a=[],h=d.value,A=d.key;if(Array.isArray(h)){let C=[],D=[];for(let O in h)h[O].id&&C.includes(h[O].id)&&a.push(new Ct(A,h,`all the sprites' ids must be unique, but ${h[O].id} is duplicated`)),C.push(h[O].id),h[O].url&&D.includes(h[O].url)&&a.push(new Ct(A,h,`all the sprites' URLs must be unique, but ${h[O].url} is duplicated`)),D.push(h[O].url),a=a.concat(fa({key:`${A}[${O}]`,value:h[O],valueSpec:{id:{type:"string",required:!0},url:{type:"string",required:!0}},validateSpec:d.validateSpec}));return a}return Qu({key:A,value:h})}function Bp(d){return a=d.value,Boolean(a)&&a.constructor===Object?[]:[new Ct(d.key,d.value,`object expected, ${Zo(d.value)} found`)];var a}let Kd={"*":()=>[],array:Ip,boolean:function(d){let a=d.value,h=d.key,A=Zo(a);return A!=="boolean"?[new Ct(h,a,`boolean expected, ${A} found`)]:[]},number:Kh,color:Rc,constants:rg,enum:vh,filter:_u,function:Pp,layer:xh,object:fa,source:Qh,light:bh,sky:Rp,terrain:bf,projection:function(d){let a=d.value,h=d.styleSpec,A=h.projection,C=d.style,D=Zo(a);if(a===void 0)return[];if(D!=="object")return[new Ct("projection",a,`object expected, ${D} found`)];let O=[];for(let G in a)O=O.concat(A[G]?d.validateSpec({key:G,value:a[G],valueSpec:A[G],style:C,styleSpec:h}):[new Ct(G,a[G],`unknown property "${G}"`)]);return O},projectionDefinition:function(d){let a=d.key,h=d.value;h=h instanceof String?h.valueOf():h;let A=Zo(h);return A!=="array"||function(C){return Array.isArray(C)&&C.length===3&&typeof C[0]=="string"&&typeof C[1]=="string"&&typeof C[2]=="number"}(h)||function(C){return!!["interpolate","step","literal"].includes(C[0])}(h)?["array","string"].includes(A)?[]:[new Ct(a,h,`projection expected, invalid type "${A}" found`)]:[new Ct(a,h,`projection expected, invalid array ${JSON.stringify(h)} found`)]},string:Qu,formatted:function(d){return Qu(d).length===0?[]:_c(d)},resolvedImage:function(d){return Qu(d).length===0?[]:_c(d)},padding:function(d){let a=d.key,h=d.value;if(Zo(h)==="array"){if(h.length<1||h.length>4)return[new Ct(a,h,`padding requires 1 to 4 values; ${h.length} values found`)];let A={type:"number"},C=[];for(let D=0;D[]}})),d.constants&&(h=h.concat(rg({key:"constants",value:d.constants}))),S(h)}function I(d){return function(a){return d(Object.assign({},a,{validateSpec:Yf}))}}function S(d){return[].concat(d).sort((a,h)=>a.line-h.line)}function L(d){return function(...a){return S(d.apply(this,a))}}w.source=L(I(Qh)),w.sprite=L(I(Du)),w.glyphs=L(I(Jh)),w.light=L(I(bh)),w.sky=L(I(Rp)),w.terrain=L(I(bf)),w.state=L(I(Bp)),w.layer=L(I(xh)),w.filter=L(I(_u)),w.paintProperty=L(I(xd)),w.layoutProperty=L(I(mm));let Y=wr,ie=w,ae=ie.light,ge=ie.sky,Pe=ie.paintProperty,Ue=ie.layoutProperty;function ct(d,a){let h=!1;if(a&&a.length)for(let A of a)d.fire(new bs(new Error(A.message))),h=!0;return h}class ft{constructor(a,h,A){let C=this.cells=[];if(a instanceof ArrayBuffer){this.arrayBuffer=a;let O=new Int32Array(this.arrayBuffer);a=O[0],this.d=(h=O[1])+2*(A=O[2]);for(let Z=0;Z=de[De+0]&&C>=de[De+1])?(G[Ee]=!0,O.push(ce[Ee])):G[Ee]=!1}}}}_forEachCell(a,h,A,C,D,O,G,Z){let re=this._convertToCellCoord(a),ce=this._convertToCellCoord(h),de=this._convertToCellCoord(A),we=this._convertToCellCoord(C);for(let Ee=re;Ee<=de;Ee++)for(let De=ce;De<=we;De++){let ke=this.d*De+Ee;if((!Z||Z(this._convertFromCellCoord(Ee),this._convertFromCellCoord(De),this._convertFromCellCoord(Ee+1),this._convertFromCellCoord(De+1)))&&D.call(this,a,h,A,C,ke,O,G,Z))return}}_convertFromCellCoord(a){return(a-this.padding)/this.scale}_convertToCellCoord(a){return Math.max(0,Math.min(this.d-1,Math.floor(a*this.scale)+this.padding))}toArrayBuffer(){if(this.arrayBuffer)return this.arrayBuffer;let a=this.cells,h=3+this.cells.length+1+1,A=0;for(let O=0;O=0)continue;let O=d[D];C[D]=gt[h].shallow.indexOf(D)>=0?O:Zt(O,a)}d instanceof Error&&(C.message=d.message)}if(C.$name)throw new Error("$name property is reserved for worker serialization logic.");return h!=="Object"&&(C.$name=h),C}function ir(d){if(Et(d))return d;if(Array.isArray(d))return d.map(ir);if(typeof d!="object")throw new Error("can't deserialize object of type "+typeof d);let a=Wt(d)||"Object";if(!gt[a])throw new Error(`can't deserialize unregistered class ${a}`);let{klass:h}=gt[a];if(!h)throw new Error(`can't deserialize unregistered class ${a}`);if(h.deserialize)return h.deserialize(d);let A=Object.create(h.prototype);for(let C of Object.keys(d)){if(C==="$name")continue;let D=d[C];A[C]=gt[a].shallow.indexOf(C)>=0?D:ir(D)}return A}class dr{constructor(){this.first=!0}update(a,h){let A=Math.floor(a);return this.first?(this.first=!1,this.lastIntegerZoom=A,this.lastIntegerZoomTime=0,this.lastZoom=a,this.lastFloorZoom=A,!0):(this.lastFloorZoom>A?(this.lastIntegerZoom=A+1,this.lastIntegerZoomTime=h):this.lastFloorZoom{try{return new RegExp(`\\p{sc=${h}}`,"u").source}catch{return null}}).filter(h=>h);return new RegExp(a.join("|"),"u")}let $i=Si(["Arab","Dupl","Mong","Ougr","Syrc"]);function bn(d){return!$i.test(String.fromCodePoint(d))}function Tn(d){return!(Br(d)||(a=d,/[\xA7\xA9\xAE\xB1\xBC-\xBE\xD7\xF7\u2016\u2020\u2021\u2030\u2031\u203B\u203C\u2042\u2047-\u2049\u2051\u2100-\u218F\u221E\u2234\u2235\u2300-\u2307\u230C-\u231F\u2324-\u2328\u232B\u237D-\u239A\u23BE-\u23CD\u23CF\u23D1-\u23DB\u23E2-\u2422\u2424-\u24FF\u25A0-\u2619\u2620-\u2767\u2776-\u2793\u2B12-\u2B2F\u2B50-\u2B59\u2BB8-\u2BEB\u3000-\u303F\u30A0-\u30FF\uE000-\uF8FF\uFE30-\uFE6F\uFF00-\uFFEF\uFFFC\uFFFD]|[\uDB80-\uDBFF][\uDC00-\uDFFF]/gim.test(String.fromCodePoint(a))));var a}let xn=Si(["Adlm","Arab","Armi","Avst","Chrs","Cprt","Egyp","Elym","Gara","Hatr","Hebr","Hung","Khar","Lydi","Mand","Mani","Mend","Merc","Mero","Narb","Nbat","Nkoo","Orkh","Palm","Phli","Phlp","Phnx","Prti","Rohg","Samr","Sarb","Sogo","Syrc","Thaa","Todr","Yezi"]);function zo(d){return xn.test(String.fromCodePoint(d))}function qi(d,a){return!(!a&&zo(d)||/[\u0900-\u0DFF\u0F00-\u109F\u1780-\u17FF]/gim.test(String.fromCodePoint(d)))}function Sn(d){for(let a of d)if(zo(a.codePointAt(0)))return!0;return!1}let $n=new class{constructor(){this.TIMEOUT=5e3,this.applyArabicShaping=null,this.processBidirectionalText=null,this.processStyledBidirectionalText=null,this.pluginStatus="unavailable",this.pluginURL=null,this.loadScriptResolve=()=>{}}setState(d){this.pluginStatus=d.pluginStatus,this.pluginURL=d.pluginURL}getState(){return{pluginStatus:this.pluginStatus,pluginURL:this.pluginURL}}setMethods(d){if($n.isParsed())throw new Error("RTL text plugin already registered.");this.applyArabicShaping=d.applyArabicShaping,this.processBidirectionalText=d.processBidirectionalText,this.processStyledBidirectionalText=d.processStyledBidirectionalText,this.loadScriptResolve()}isParsed(){return this.applyArabicShaping!=null&&this.processBidirectionalText!=null&&this.processStyledBidirectionalText!=null}getRTLTextPluginStatus(){return this.pluginStatus}syncState(d,a){return o(this,void 0,void 0,function*(){if(this.isParsed())return this.getState();if(d.pluginStatus!=="loading")return this.setState(d),d;let h=d.pluginURL,A=new Promise(D=>{this.loadScriptResolve=D});a(h);let C=new Promise(D=>setTimeout(()=>D(),this.TIMEOUT));if(yield Promise.race([A,C]),this.isParsed()){let D={pluginStatus:"loaded",pluginURL:h};return this.setState(D),D}throw this.setState({pluginStatus:"error",pluginURL:""}),new Error(`RTL Text Plugin failed to import scripts from ${h}`)})}};class Hi{constructor(a,h){this.isSupportedScript=Gn,this.zoom=a,h?(this.now=h.now||0,this.fadeDuration=h.fadeDuration||0,this.zoomHistory=h.zoomHistory||new dr,this.transition=h.transition||{}):(this.now=0,this.fadeDuration=0,this.zoomHistory=new dr,this.transition={})}crossFadingFactor(){return this.fadeDuration===0?1:Math.min((this.now-this.zoomHistory.lastIntegerZoomTime)/this.fadeDuration,1)}getCrossfadeParameters(){let a=this.zoom,h=a-Math.floor(a),A=this.crossFadingFactor();return a>this.zoomHistory.lastIntegerZoom?{fromScale:2,toScale:1,t:h+(1-h)*A}:{fromScale:.5,toScale:1,t:1-(1-A)*h}}}function Gn(d){return function(a,h){for(let A of a)if(!qi(A.codePointAt(0),h))return!1;return!0}(d,$n.getRTLTextPluginStatus()==="loaded")}let eo="-transition";class Dn{constructor(a,h,A){this.property=a,this.value=h,this.expression=function(C,D,O){if(vd(C))return new So(C,D);if(yh(C)){let G=gu(C,D,O);if(G.result==="error")throw new Error(G.value.map(Z=>`${Z.key}: ${Z.message}`).join(", "));return G.value}{let G=C;return D.type==="color"&&typeof C=="string"?G=Mo.parse(C):D.type!=="padding"||typeof C!="number"&&!Array.isArray(C)?D.type!=="numberArray"||typeof C!="number"&&!Array.isArray(C)?D.type!=="colorArray"||typeof C!="string"&&!Array.isArray(C)?D.type==="variableAnchorOffsetCollection"&&Array.isArray(C)?G=sl.parse(C):D.type==="projectionDefinition"&&typeof C=="string"&&(G=vl.parse(C)):G=Zn.parse(C):G=_a.parse(C):G=Jl.parse(C),{globalStateRefs:new Set,_globalState:null,kind:"constant",evaluate:()=>G}}}(h===void 0?a.specification.default:h,a.specification,A)}isDataDriven(){return this.expression.kind==="source"||this.expression.kind==="composite"}getGlobalStateRefs(){return this.expression.globalStateRefs||new Set}possiblyEvaluate(a,h,A){return this.property.possiblyEvaluate(this,a,h,A)}}class Rn{constructor(a,h){this.property=a,this.value=new Dn(a,void 0,h)}transitioned(a,h){return new Ga(this.property,this.value,h,Ve({},a.transition,this.transition),a.now)}untransitioned(){return new Ga(this.property,this.value,null,{},0)}}class Ml{constructor(a,h){this._properties=a,this._values=Object.create(a.defaultTransitionablePropertyValues),this._globalState=h}getValue(a){return Xr(this._values[a].value.value)}setValue(a,h){Object.prototype.hasOwnProperty.call(this._values,a)||(this._values[a]=new Rn(this._values[a].property,this._globalState)),this._values[a].value=new Dn(this._values[a].property,h===null?void 0:Xr(h),this._globalState)}getTransition(a){return Xr(this._values[a].transition)}setTransition(a,h){Object.prototype.hasOwnProperty.call(this._values,a)||(this._values[a]=new Rn(this._values[a].property,this._globalState)),this._values[a].transition=Xr(h)||void 0}serialize(){let a={};for(let h of Object.keys(this._values)){let A=this.getValue(h);A!==void 0&&(a[h]=A);let C=this.getTransition(h);C!==void 0&&(a[`${h}${eo}`]=C)}return a}transitioned(a,h){let A=new yc(this._properties);for(let C of Object.keys(this._values))A._values[C]=this._values[C].transitioned(a,h._values[C]);return A}untransitioned(){let a=new yc(this._properties);for(let h of Object.keys(this._values))a._values[h]=this._values[h].untransitioned();return a}}class Ga{constructor(a,h,A,C,D){this.property=a,this.value=h,this.begin=D+C.delay||0,this.end=this.begin+C.duration||0,a.specification.transition&&(C.delay||C.duration)&&(this.prior=A)}possiblyEvaluate(a,h,A){let C=a.now||0,D=this.value.possiblyEvaluate(a,h,A),O=this.prior;if(O){if(C>this.end)return this.prior=null,D;if(this.value.isDataDriven())return this.prior=null,D;if(CC.zoomHistory.lastIntegerZoom?{from:a,to:h}:{from:A,to:h}}interpolate(a){return a}}class jc{constructor(a){this.specification=a}possiblyEvaluate(a,h,A,C){if(a.value!==void 0){if(a.expression.kind==="constant"){let D=a.expression.evaluate(h,null,{},A,C);return this._calculate(D,D,D,h)}return this._calculate(a.expression.evaluate(new Hi(Math.floor(h.zoom-1),h)),a.expression.evaluate(new Hi(Math.floor(h.zoom),h)),a.expression.evaluate(new Hi(Math.floor(h.zoom+1),h)),h)}}_calculate(a,h,A,C){return C.zoom>C.zoomHistory.lastIntegerZoom?{from:a,to:h}:{from:A,to:h}}interpolate(a){return a}}class Gc{constructor(a){this.specification=a}possiblyEvaluate(a,h,A,C){return!!a.expression.evaluate(h,null,{},A,C)}interpolate(){return!1}}class Fa{constructor(a){this.properties=a,this.defaultPropertyValues={},this.defaultTransitionablePropertyValues={},this.defaultTransitioningPropertyValues={},this.defaultPossiblyEvaluatedValues={},this.overridableProperties=[];for(let h in a){let A=a[h];A.specification.overridable&&this.overridableProperties.push(h);let C=this.defaultPropertyValues[h]=new Dn(A,void 0,void 0),D=this.defaultTransitionablePropertyValues[h]=new Rn(A,void 0);this.defaultTransitioningPropertyValues[h]=D.untransitioned(),this.defaultPossiblyEvaluatedValues[h]=C.possiblyEvaluate({})}}}ut("DataDrivenProperty",_n),ut("DataConstantProperty",en),ut("CrossFadedDataDrivenProperty",Ju),ut("CrossFadedProperty",jc),ut("ColorRampProperty",Gc);class Hc extends go{constructor(a,h,A){if(super(),this.id=a.id,this.type=a.type,this._globalState=A,this._featureFilter={filter:()=>!0,needGeometry:!1,getGlobalStateRefs:()=>new Set},a.type!=="custom"&&(this.metadata=a.metadata,this.minzoom=a.minzoom,this.maxzoom=a.maxzoom,a.type!=="background"&&(this.source=a.source,this.sourceLayer=a["source-layer"],this.filter=a.filter,this._featureFilter=Pc(a.filter,A)),h.layout&&(this._unevaluatedLayout=new cl(h.layout,A)),h.paint)){this._transitionablePaint=new Ml(h.paint,A);for(let C in a.paint)this.setPaintProperty(C,a.paint[C],{validate:!1});for(let C in a.layout)this.setLayoutProperty(C,a.layout[C],{validate:!1});this._transitioningPaint=this._transitionablePaint.untransitioned(),this.paint=new Ta(h.paint)}}setFilter(a){this.filter=a,this._featureFilter=Pc(a,this._globalState)}getCrossfadeParameters(){return this._crossfadeParameters}getLayoutProperty(a){return a==="visibility"?this.visibility:this._unevaluatedLayout.getValue(a)}getLayoutAffectingGlobalStateRefs(){let a=new Set;if(this._unevaluatedLayout)for(let h in this._unevaluatedLayout._values){let A=this._unevaluatedLayout._values[h];for(let C of A.getGlobalStateRefs())a.add(C)}for(let h of this._featureFilter.getGlobalStateRefs())a.add(h);return a}getPaintAffectingGlobalStateRefs(){var a;let h=new globalThis.Map;if(this._transitionablePaint)for(let A in this._transitionablePaint._values){let C=this._transitionablePaint._values[A].value;for(let D of C.getGlobalStateRefs()){let O=(a=h.get(D))!==null&&a!==void 0?a:[];O.push({name:A,value:C.value}),h.set(D,O)}}return h}setLayoutProperty(a,h,A={}){h!=null&&this._validate(Ue,`layers.${this.id}.layout.${a}`,a,h,A)||(a!=="visibility"?this._unevaluatedLayout.setValue(a,h):this.visibility=h)}getPaintProperty(a){return a.endsWith(eo)?this._transitionablePaint.getTransition(a.slice(0,-11)):this._transitionablePaint.getValue(a)}setPaintProperty(a,h,A={}){if(h!=null&&this._validate(Pe,`layers.${this.id}.paint.${a}`,a,h,A))return!1;if(a.endsWith(eo))return this._transitionablePaint.setTransition(a.slice(0,-11),h||void 0),!1;{let C=this._transitionablePaint._values[a],D=C.property.specification["property-type"]==="cross-faded-data-driven",O=C.value.isDataDriven(),G=C.value;this._transitionablePaint.setValue(a,h),this._handleSpecialPaintPropertyUpdate(a);let Z=this._transitionablePaint._values[a].value;return Z.isDataDriven()||O||D||this._handleOverridablePaintPropertyUpdate(a,G,Z)}}_handleSpecialPaintPropertyUpdate(a){}_handleOverridablePaintPropertyUpdate(a,h,A){return!1}isHidden(a,h=!1){return!!(this.minzoom&&a<(h?Math.floor(this.minzoom):this.minzoom))||!!(this.maxzoom&&a>=this.maxzoom)||this.visibility==="none"}updateTransitions(a){this._transitioningPaint=this._transitionablePaint.transitioned(a,this._transitioningPaint)}hasTransition(){return this._transitioningPaint.hasTransition()}recalculate(a,h){a.getCrossfadeParameters&&(this._crossfadeParameters=a.getCrossfadeParameters()),this._unevaluatedLayout&&(this.layout=this._unevaluatedLayout.possiblyEvaluate(a,void 0,h)),this.paint=this._transitioningPaint.possiblyEvaluate(a,void 0,h)}serialize(){let a={id:this.id,type:this.type,source:this.source,"source-layer":this.sourceLayer,metadata:this.metadata,minzoom:this.minzoom,maxzoom:this.maxzoom,filter:this.filter,layout:this._unevaluatedLayout&&this._unevaluatedLayout.serialize(),paint:this._transitionablePaint&&this._transitionablePaint.serialize()};return this.visibility&&(a.layout=a.layout||{},a.layout.visibility=this.visibility),mr(a,(h,A)=>!(h===void 0||A==="layout"&&!Object.keys(h).length||A==="paint"&&!Object.keys(h).length))}_validate(a,h,A,C,D={}){return(!D||D.validate!==!1)&&ct(this,a.call(ie,{key:h,layerType:this.type,objectKey:A,value:C,styleSpec:wr,style:{glyphs:!0,sprite:!0}}))}is3D(){return!1}isTileClipped(){return!1}hasOffscreenPass(){return!1}resize(){}isStateDependent(){for(let a in this.paint._values){let h=this.paint.get(a);if(h instanceof Vs&&kl(h.property.specification)&&(h.value.kind==="source"||h.value.kind==="composite")&&h.value.isStateDependent)return!0}return!1}}let bd;var wd={get paint(){return bd=bd||new Fa({"raster-opacity":new en(wr.paint_raster["raster-opacity"]),"raster-hue-rotate":new en(wr.paint_raster["raster-hue-rotate"]),"raster-brightness-min":new en(wr.paint_raster["raster-brightness-min"]),"raster-brightness-max":new en(wr.paint_raster["raster-brightness-max"]),"raster-saturation":new en(wr.paint_raster["raster-saturation"]),"raster-contrast":new en(wr.paint_raster["raster-contrast"]),"raster-resampling":new en(wr.paint_raster["raster-resampling"]),"raster-fade-duration":new en(wr.paint_raster["raster-fade-duration"])})}};class Td extends Hc{constructor(a,h){super(a,wd,h)}}let Ou={Int8:Int8Array,Uint8:Uint8Array,Int16:Int16Array,Uint16:Uint16Array,Int32:Int32Array,Uint32:Uint32Array,Float32:Float32Array};class iu{constructor(a,h){this._structArray=a,this._pos1=h*this.size,this._pos2=this._pos1/2,this._pos4=this._pos1/4,this._pos8=this._pos1/8}}class Sa{constructor(){this.isTransferred=!1,this.capacity=-1,this.resize(0)}static serialize(a,h){return a._trim(),h&&(a.isTransferred=!0,h.push(a.arrayBuffer)),{length:a.length,arrayBuffer:a.arrayBuffer}}static deserialize(a){let h=Object.create(this.prototype);return h.arrayBuffer=a.arrayBuffer,h.length=a.length,h.capacity=a.arrayBuffer.byteLength/h.bytesPerElement,h._refreshViews(),h}_trim(){this.length!==this.capacity&&(this.capacity=this.length,this.arrayBuffer=this.arrayBuffer.slice(0,this.length*this.bytesPerElement),this._refreshViews())}clear(){this.length=0}resize(a){this.reserve(a),this.length=a}reserve(a){if(a>this.capacity){this.capacity=Math.max(a,Math.floor(5*this.capacity),128),this.arrayBuffer=new ArrayBuffer(this.capacity*this.bytesPerElement);let h=this.uint8;this._refreshViews(),h&&this.uint8.set(h)}}_refreshViews(){throw new Error("_refreshViews() must be implemented by each concrete StructArray layout")}}function ia(d,a=1){let h=0,A=0;return{members:d.map(C=>{let D=Ou[C.type].BYTES_PER_ELEMENT,O=h=zl(h,Math.max(a,D)),G=C.components||1;return A=Math.max(A,D),h+=D*G,{name:C.name,type:C.type,components:G,offset:O}}),size:zl(h,Math.max(A,a)),alignment:a}}function zl(d,a){return Math.ceil(d/a)*a}class eh extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)}emplaceBack(a,h){let A=this.length;return this.resize(A+1),this.emplace(A,a,h)}emplace(a,h,A){let C=2*a;return this.int16[C+0]=h,this.int16[C+1]=A,a}}eh.prototype.bytesPerElement=4,ut("StructArrayLayout2i4",eh);class ef extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)}emplaceBack(a,h,A){let C=this.length;return this.resize(C+1),this.emplace(C,a,h,A)}emplace(a,h,A,C){let D=3*a;return this.int16[D+0]=h,this.int16[D+1]=A,this.int16[D+2]=C,a}}ef.prototype.bytesPerElement=6,ut("StructArrayLayout3i6",ef);class Ul extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)}emplaceBack(a,h,A,C){let D=this.length;return this.resize(D+1),this.emplace(D,a,h,A,C)}emplace(a,h,A,C,D){let O=4*a;return this.int16[O+0]=h,this.int16[O+1]=A,this.int16[O+2]=C,this.int16[O+3]=D,a}}Ul.prototype.bytesPerElement=8,ut("StructArrayLayout4i8",Ul);class _s extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O){let G=this.length;return this.resize(G+1),this.emplace(G,a,h,A,C,D,O)}emplace(a,h,A,C,D,O,G){let Z=6*a;return this.int16[Z+0]=h,this.int16[Z+1]=A,this.int16[Z+2]=C,this.int16[Z+3]=D,this.int16[Z+4]=O,this.int16[Z+5]=G,a}}_s.prototype.bytesPerElement=12,ut("StructArrayLayout2i4i12",_s);class yu extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O){let G=this.length;return this.resize(G+1),this.emplace(G,a,h,A,C,D,O)}emplace(a,h,A,C,D,O,G){let Z=4*a,re=8*a;return this.int16[Z+0]=h,this.int16[Z+1]=A,this.uint8[re+4]=C,this.uint8[re+5]=D,this.uint8[re+6]=O,this.uint8[re+7]=G,a}}yu.prototype.bytesPerElement=8,ut("StructArrayLayout2i4ub8",yu);class Il extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)}emplaceBack(a,h){let A=this.length;return this.resize(A+1),this.emplace(A,a,h)}emplace(a,h,A){let C=2*a;return this.float32[C+0]=h,this.float32[C+1]=A,a}}Il.prototype.bytesPerElement=8,ut("StructArrayLayout2f8",Il);class tf extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O,G,Z,re,ce){let de=this.length;return this.resize(de+1),this.emplace(de,a,h,A,C,D,O,G,Z,re,ce)}emplace(a,h,A,C,D,O,G,Z,re,ce,de){let we=10*a;return this.uint16[we+0]=h,this.uint16[we+1]=A,this.uint16[we+2]=C,this.uint16[we+3]=D,this.uint16[we+4]=O,this.uint16[we+5]=G,this.uint16[we+6]=Z,this.uint16[we+7]=re,this.uint16[we+8]=ce,this.uint16[we+9]=de,a}}tf.prototype.bytesPerElement=20,ut("StructArrayLayout10ui20",tf);class Kg extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O,G,Z){let re=this.length;return this.resize(re+1),this.emplace(re,a,h,A,C,D,O,G,Z)}emplace(a,h,A,C,D,O,G,Z,re){let ce=8*a;return this.uint16[ce+0]=h,this.uint16[ce+1]=A,this.uint16[ce+2]=C,this.uint16[ce+3]=D,this.uint16[ce+4]=O,this.uint16[ce+5]=G,this.uint16[ce+6]=Z,this.uint16[ce+7]=re,a}}Kg.prototype.bytesPerElement=16,ut("StructArrayLayout8ui16",Kg);class Lp extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O,G,Z,re,ce,de,we){let Ee=this.length;return this.resize(Ee+1),this.emplace(Ee,a,h,A,C,D,O,G,Z,re,ce,de,we)}emplace(a,h,A,C,D,O,G,Z,re,ce,de,we,Ee){let De=12*a;return this.int16[De+0]=h,this.int16[De+1]=A,this.int16[De+2]=C,this.int16[De+3]=D,this.uint16[De+4]=O,this.uint16[De+5]=G,this.uint16[De+6]=Z,this.uint16[De+7]=re,this.int16[De+8]=ce,this.int16[De+9]=de,this.int16[De+10]=we,this.int16[De+11]=Ee,a}}Lp.prototype.bytesPerElement=24,ut("StructArrayLayout4i4ui4i24",Lp);class th extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)}emplaceBack(a,h,A){let C=this.length;return this.resize(C+1),this.emplace(C,a,h,A)}emplace(a,h,A,C){let D=3*a;return this.float32[D+0]=h,this.float32[D+1]=A,this.float32[D+2]=C,a}}th.prototype.bytesPerElement=12,ut("StructArrayLayout3f12",th);class Kf extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer)}emplaceBack(a){let h=this.length;return this.resize(h+1),this.emplace(h,a)}emplace(a,h){return this.uint32[1*a+0]=h,a}}Kf.prototype.bytesPerElement=4,ut("StructArrayLayout1ul4",Kf);class wf extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O,G,Z,re){let ce=this.length;return this.resize(ce+1),this.emplace(ce,a,h,A,C,D,O,G,Z,re)}emplace(a,h,A,C,D,O,G,Z,re,ce){let de=10*a,we=5*a;return this.int16[de+0]=h,this.int16[de+1]=A,this.int16[de+2]=C,this.int16[de+3]=D,this.int16[de+4]=O,this.int16[de+5]=G,this.uint32[we+3]=Z,this.uint16[de+8]=re,this.uint16[de+9]=ce,a}}wf.prototype.bytesPerElement=20,ut("StructArrayLayout6i1ul2ui20",wf);class rf extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O){let G=this.length;return this.resize(G+1),this.emplace(G,a,h,A,C,D,O)}emplace(a,h,A,C,D,O,G){let Z=6*a;return this.int16[Z+0]=h,this.int16[Z+1]=A,this.int16[Z+2]=C,this.int16[Z+3]=D,this.int16[Z+4]=O,this.int16[Z+5]=G,a}}rf.prototype.bytesPerElement=12,ut("StructArrayLayout2i2i2i12",rf);class Tf extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D){let O=this.length;return this.resize(O+1),this.emplace(O,a,h,A,C,D)}emplace(a,h,A,C,D,O){let G=4*a,Z=8*a;return this.float32[G+0]=h,this.float32[G+1]=A,this.float32[G+2]=C,this.int16[Z+6]=D,this.int16[Z+7]=O,a}}Tf.prototype.bytesPerElement=16,ut("StructArrayLayout2f1f2i16",Tf);class ig extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O){let G=this.length;return this.resize(G+1),this.emplace(G,a,h,A,C,D,O)}emplace(a,h,A,C,D,O,G){let Z=16*a,re=4*a,ce=8*a;return this.uint8[Z+0]=h,this.uint8[Z+1]=A,this.float32[re+1]=C,this.float32[re+2]=D,this.int16[ce+6]=O,this.int16[ce+7]=G,a}}ig.prototype.bytesPerElement=16,ut("StructArrayLayout2ub2f2i16",ig);class Fu extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)}emplaceBack(a,h,A){let C=this.length;return this.resize(C+1),this.emplace(C,a,h,A)}emplace(a,h,A,C){let D=3*a;return this.uint16[D+0]=h,this.uint16[D+1]=A,this.uint16[D+2]=C,a}}Fu.prototype.bytesPerElement=6,ut("StructArrayLayout3ui6",Fu);class Vl extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O,G,Z,re,ce,de,we,Ee,De,ke,$e,st){let kt=this.length;return this.resize(kt+1),this.emplace(kt,a,h,A,C,D,O,G,Z,re,ce,de,we,Ee,De,ke,$e,st)}emplace(a,h,A,C,D,O,G,Z,re,ce,de,we,Ee,De,ke,$e,st,kt){let pt=24*a,_t=12*a,Gt=48*a;return this.int16[pt+0]=h,this.int16[pt+1]=A,this.uint16[pt+2]=C,this.uint16[pt+3]=D,this.uint32[_t+2]=O,this.uint32[_t+3]=G,this.uint32[_t+4]=Z,this.uint16[pt+10]=re,this.uint16[pt+11]=ce,this.uint16[pt+12]=de,this.float32[_t+7]=we,this.float32[_t+8]=Ee,this.uint8[Gt+36]=De,this.uint8[Gt+37]=ke,this.uint8[Gt+38]=$e,this.uint32[_t+10]=st,this.int16[pt+22]=kt,a}}Vl.prototype.bytesPerElement=48,ut("StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48",Vl);class b_ extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.int16=new Int16Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)}emplaceBack(a,h,A,C,D,O,G,Z,re,ce,de,we,Ee,De,ke,$e,st,kt,pt,_t,Gt,Qt,Mr,oi,Kr,gi,ji,vi){let Qr=this.length;return this.resize(Qr+1),this.emplace(Qr,a,h,A,C,D,O,G,Z,re,ce,de,we,Ee,De,ke,$e,st,kt,pt,_t,Gt,Qt,Mr,oi,Kr,gi,ji,vi)}emplace(a,h,A,C,D,O,G,Z,re,ce,de,we,Ee,De,ke,$e,st,kt,pt,_t,Gt,Qt,Mr,oi,Kr,gi,ji,vi,Qr){let jr=32*a,dn=16*a;return this.int16[jr+0]=h,this.int16[jr+1]=A,this.int16[jr+2]=C,this.int16[jr+3]=D,this.int16[jr+4]=O,this.int16[jr+5]=G,this.int16[jr+6]=Z,this.int16[jr+7]=re,this.uint16[jr+8]=ce,this.uint16[jr+9]=de,this.uint16[jr+10]=we,this.uint16[jr+11]=Ee,this.uint16[jr+12]=De,this.uint16[jr+13]=ke,this.uint16[jr+14]=$e,this.uint16[jr+15]=st,this.uint16[jr+16]=kt,this.uint16[jr+17]=pt,this.uint16[jr+18]=_t,this.uint16[jr+19]=Gt,this.uint16[jr+20]=Qt,this.uint16[jr+21]=Mr,this.uint16[jr+22]=oi,this.uint32[dn+12]=Kr,this.float32[dn+13]=gi,this.float32[dn+14]=ji,this.uint16[jr+30]=vi,this.uint16[jr+31]=Qr,a}}b_.prototype.bytesPerElement=64,ut("StructArrayLayout8i15ui1ul2f2ui64",b_);class ng extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)}emplaceBack(a){let h=this.length;return this.resize(h+1),this.emplace(h,a)}emplace(a,h){return this.float32[1*a+0]=h,a}}ng.prototype.bytesPerElement=4,ut("StructArrayLayout1f4",ng);class gm extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)}emplaceBack(a,h,A){let C=this.length;return this.resize(C+1),this.emplace(C,a,h,A)}emplace(a,h,A,C){let D=3*a;return this.uint16[6*a+0]=h,this.float32[D+1]=A,this.float32[D+2]=C,a}}gm.prototype.bytesPerElement=12,ut("StructArrayLayout1ui2f12",gm);class w_ extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint32=new Uint32Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)}emplaceBack(a,h,A){let C=this.length;return this.resize(C+1),this.emplace(C,a,h,A)}emplace(a,h,A,C){let D=4*a;return this.uint32[2*a+0]=h,this.uint16[D+2]=A,this.uint16[D+3]=C,a}}w_.prototype.bytesPerElement=8,ut("StructArrayLayout1ul2ui8",w_);class T_ extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)}emplaceBack(a,h){let A=this.length;return this.resize(A+1),this.emplace(A,a,h)}emplace(a,h,A){let C=2*a;return this.uint16[C+0]=h,this.uint16[C+1]=A,a}}T_.prototype.bytesPerElement=4,ut("StructArrayLayout2ui4",T_);class Q0 extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.uint16=new Uint16Array(this.arrayBuffer)}emplaceBack(a){let h=this.length;return this.resize(h+1),this.emplace(h,a)}emplace(a,h){return this.uint16[1*a+0]=h,a}}Q0.prototype.bytesPerElement=2,ut("StructArrayLayout1ui2",Q0);class Qg extends Sa{_refreshViews(){this.uint8=new Uint8Array(this.arrayBuffer),this.float32=new Float32Array(this.arrayBuffer)}emplaceBack(a,h,A,C){let D=this.length;return this.resize(D+1),this.emplace(D,a,h,A,C)}emplace(a,h,A,C,D){let O=4*a;return this.float32[O+0]=h,this.float32[O+1]=A,this.float32[O+2]=C,this.float32[O+3]=D,a}}Qg.prototype.bytesPerElement=16,ut("StructArrayLayout4f16",Qg);class z extends iu{get anchorPointX(){return this._structArray.int16[this._pos2+0]}get anchorPointY(){return this._structArray.int16[this._pos2+1]}get x1(){return this._structArray.int16[this._pos2+2]}get y1(){return this._structArray.int16[this._pos2+3]}get x2(){return this._structArray.int16[this._pos2+4]}get y2(){return this._structArray.int16[this._pos2+5]}get featureIndex(){return this._structArray.uint32[this._pos4+3]}get sourceLayerIndex(){return this._structArray.uint16[this._pos2+8]}get bucketIndex(){return this._structArray.uint16[this._pos2+9]}get anchorPoint(){return new s(this.anchorPointX,this.anchorPointY)}}z.prototype.size=20;class l extends wf{get(a){return new z(this,a)}}ut("CollisionBoxArray",l);class m extends iu{get anchorX(){return this._structArray.int16[this._pos2+0]}get anchorY(){return this._structArray.int16[this._pos2+1]}get glyphStartIndex(){return this._structArray.uint16[this._pos2+2]}get numGlyphs(){return this._structArray.uint16[this._pos2+3]}get vertexStartIndex(){return this._structArray.uint32[this._pos4+2]}get lineStartIndex(){return this._structArray.uint32[this._pos4+3]}get lineLength(){return this._structArray.uint32[this._pos4+4]}get segment(){return this._structArray.uint16[this._pos2+10]}get lowerSize(){return this._structArray.uint16[this._pos2+11]}get upperSize(){return this._structArray.uint16[this._pos2+12]}get lineOffsetX(){return this._structArray.float32[this._pos4+7]}get lineOffsetY(){return this._structArray.float32[this._pos4+8]}get writingMode(){return this._structArray.uint8[this._pos1+36]}get placedOrientation(){return this._structArray.uint8[this._pos1+37]}set placedOrientation(a){this._structArray.uint8[this._pos1+37]=a}get hidden(){return this._structArray.uint8[this._pos1+38]}set hidden(a){this._structArray.uint8[this._pos1+38]=a}get crossTileID(){return this._structArray.uint32[this._pos4+10]}set crossTileID(a){this._structArray.uint32[this._pos4+10]=a}get associatedIconIndex(){return this._structArray.int16[this._pos2+22]}}m.prototype.size=48;class T extends Vl{get(a){return new m(this,a)}}ut("PlacedSymbolArray",T);class P extends iu{get anchorX(){return this._structArray.int16[this._pos2+0]}get anchorY(){return this._structArray.int16[this._pos2+1]}get rightJustifiedTextSymbolIndex(){return this._structArray.int16[this._pos2+2]}get centerJustifiedTextSymbolIndex(){return this._structArray.int16[this._pos2+3]}get leftJustifiedTextSymbolIndex(){return this._structArray.int16[this._pos2+4]}get verticalPlacedTextSymbolIndex(){return this._structArray.int16[this._pos2+5]}get placedIconSymbolIndex(){return this._structArray.int16[this._pos2+6]}get verticalPlacedIconSymbolIndex(){return this._structArray.int16[this._pos2+7]}get key(){return this._structArray.uint16[this._pos2+8]}get textBoxStartIndex(){return this._structArray.uint16[this._pos2+9]}get textBoxEndIndex(){return this._structArray.uint16[this._pos2+10]}get verticalTextBoxStartIndex(){return this._structArray.uint16[this._pos2+11]}get verticalTextBoxEndIndex(){return this._structArray.uint16[this._pos2+12]}get iconBoxStartIndex(){return this._structArray.uint16[this._pos2+13]}get iconBoxEndIndex(){return this._structArray.uint16[this._pos2+14]}get verticalIconBoxStartIndex(){return this._structArray.uint16[this._pos2+15]}get verticalIconBoxEndIndex(){return this._structArray.uint16[this._pos2+16]}get featureIndex(){return this._structArray.uint16[this._pos2+17]}get numHorizontalGlyphVertices(){return this._structArray.uint16[this._pos2+18]}get numVerticalGlyphVertices(){return this._structArray.uint16[this._pos2+19]}get numIconVertices(){return this._structArray.uint16[this._pos2+20]}get numVerticalIconVertices(){return this._structArray.uint16[this._pos2+21]}get useRuntimeCollisionCircles(){return this._structArray.uint16[this._pos2+22]}get crossTileID(){return this._structArray.uint32[this._pos4+12]}set crossTileID(a){this._structArray.uint32[this._pos4+12]=a}get textBoxScale(){return this._structArray.float32[this._pos4+13]}get collisionCircleDiameter(){return this._structArray.float32[this._pos4+14]}get textAnchorOffsetStartIndex(){return this._structArray.uint16[this._pos2+30]}get textAnchorOffsetEndIndex(){return this._structArray.uint16[this._pos2+31]}}P.prototype.size=64;class V extends b_{get(a){return new P(this,a)}}ut("SymbolInstanceArray",V);class W extends ng{getoffsetX(a){return this.float32[1*a+0]}}ut("GlyphOffsetArray",W);class te extends ef{getx(a){return this.int16[3*a+0]}gety(a){return this.int16[3*a+1]}gettileUnitDistanceFromAnchor(a){return this.int16[3*a+2]}}ut("SymbolLineVertexArray",te);class se extends iu{get textAnchor(){return this._structArray.uint16[this._pos2+0]}get textOffset0(){return this._structArray.float32[this._pos4+1]}get textOffset1(){return this._structArray.float32[this._pos4+2]}}se.prototype.size=12;class fe extends gm{get(a){return new se(this,a)}}ut("TextAnchorOffsetArray",fe);class _e extends iu{get featureIndex(){return this._structArray.uint32[this._pos4+0]}get sourceLayerIndex(){return this._structArray.uint16[this._pos2+2]}get bucketIndex(){return this._structArray.uint16[this._pos2+3]}}_e.prototype.size=8;class Re extends w_{get(a){return new _e(this,a)}}ut("FeatureIndexArray",Re);class Ie extends eh{}class je extends eh{}class tt extends eh{}class yt extends _s{}class At extends yu{}class vt extends Il{}class Lt extends tf{}class Kt extends Kg{}class Mt extends Lp{}class Xt extends th{}class Rt extends Kf{}class zt extends rf{}class jt extends ig{}class br extends Fu{}class Cr extends T_{}let Pr=ia([{name:"a_pos",components:2,type:"Int16"}],4),{members:Ir}=Pr;class Ai{constructor(a=[]){this._forceNewSegmentOnNextPrepare=!1,this.segments=a}prepareSegment(a,h,A,C){let D=this.segments[this.segments.length-1];return a>Ai.MAX_VERTEX_ARRAY_LENGTH&&ln(`Max vertices per segment is ${Ai.MAX_VERTEX_ARRAY_LENGTH}: bucket requested ${a}. Consider using the \`fillLargeMeshArrays\` function if you require meshes with more than ${Ai.MAX_VERTEX_ARRAY_LENGTH} vertices.`),this._forceNewSegmentOnNextPrepare||!D||D.vertexLength+a>Ai.MAX_VERTEX_ARRAY_LENGTH||D.sortKey!==C?this.createNewSegment(h,A,C):D}createNewSegment(a,h,A){let C={vertexOffset:a.length,primitiveOffset:h.length,vertexLength:0,primitiveLength:0,vaos:{}};return A!==void 0&&(C.sortKey=A),this._forceNewSegmentOnNextPrepare=!1,this.segments.push(C),C}getOrCreateLatestSegment(a,h,A){return this.prepareSegment(0,a,h,A)}forceNewSegmentOnNextPrepare(){this._forceNewSegmentOnNextPrepare=!0}get(){return this.segments}destroy(){for(let a of this.segments)for(let h in a.vaos)a.vaos[h].destroy()}static simpleSegment(a,h,A,C){return new Ai([{vertexOffset:a,primitiveOffset:h,vertexLength:A,primitiveLength:C,vaos:{},sortKey:0}])}}function _i(d,a){return 256*(d=ii(Math.floor(d),0,255))+ii(Math.floor(a),0,255)}Ai.MAX_VERTEX_ARRAY_LENGTH=Math.pow(2,16)-1,ut("SegmentVector",Ai);let hi=ia([{name:"a_pattern_from",components:4,type:"Uint16"},{name:"a_pattern_to",components:4,type:"Uint16"},{name:"a_pixel_ratio_from",components:1,type:"Uint16"},{name:"a_pixel_ratio_to",components:1,type:"Uint16"}]),hn=ia([{name:"a_dasharray_from",components:4,type:"Uint16"},{name:"a_dasharray_to",components:4,type:"Uint16"}]);var fo,$r,di,na={exports:{}},Ja={exports:{}},ya={exports:{}},jl=function(){if(di)return na.exports;di=1;var d=(fo||(fo=1,Ja.exports=function(h,A){var C,D,O,G,Z,re,ce,de;for(D=h.length-(C=3&h.length),O=A,Z=3432918353,re=461845907,de=0;de>>16)*Z&65535)<<16)&4294967295)<<15|ce>>>17))*re+(((ce>>>16)*re&65535)<<16)&4294967295)<<13|O>>>19))+((5*(O>>>16)&65535)<<16)&4294967295))+((58964+(G>>>16)&65535)<<16);switch(ce=0,C){case 3:ce^=(255&h.charCodeAt(de+2))<<16;case 2:ce^=(255&h.charCodeAt(de+1))<<8;case 1:O^=ce=(65535&(ce=(ce=(65535&(ce^=255&h.charCodeAt(de)))*Z+(((ce>>>16)*Z&65535)<<16)&4294967295)<<15|ce>>>17))*re+(((ce>>>16)*re&65535)<<16)&4294967295}return O^=h.length,O=2246822507*(65535&(O^=O>>>16))+((2246822507*(O>>>16)&65535)<<16)&4294967295,O=3266489909*(65535&(O^=O>>>13))+((3266489909*(O>>>16)&65535)<<16)&4294967295,(O^=O>>>16)>>>0}),Ja.exports),a=($r||($r=1,ya.exports=function(h,A){for(var C,D=h.length,O=A^D,G=0;D>=4;)C=1540483477*(65535&(C=255&h.charCodeAt(G)|(255&h.charCodeAt(++G))<<8|(255&h.charCodeAt(++G))<<16|(255&h.charCodeAt(++G))<<24))+((1540483477*(C>>>16)&65535)<<16),O=1540483477*(65535&O)+((1540483477*(O>>>16)&65535)<<16)^(C=1540483477*(65535&(C^=C>>>24))+((1540483477*(C>>>16)&65535)<<16)),D-=4,++G;switch(D){case 3:O^=(255&h.charCodeAt(G+2))<<16;case 2:O^=(255&h.charCodeAt(G+1))<<8;case 1:O=1540483477*(65535&(O^=255&h.charCodeAt(G)))+((1540483477*(O>>>16)&65535)<<16)}return O=1540483477*(65535&(O^=O>>>13))+((1540483477*(O>>>16)&65535)<<16),(O^=O>>>15)>>>0}),ya.exports);return na.exports=d,na.exports.murmur3=d,na.exports.murmur2=a,na.exports}(),qs=c(jl);class Ha{constructor(){this.ids=[],this.positions=[],this.indexed=!1}add(a,h,A,C){this.ids.push(Gl(a)),this.positions.push(h,A,C)}getPositions(a){if(!this.indexed)throw new Error("Trying to get index, but feature positions are not indexed");let h=Gl(a),A=0,C=this.ids.length-1;for(;A>1;this.ids[O]>=h?C=O:A=O+1}let D=[];for(;this.ids[A]===h;)D.push({index:this.positions[3*A],start:this.positions[3*A+1],end:this.positions[3*A+2]}),A++;return D}static serialize(a,h){let A=new Float64Array(a.ids),C=new Uint32Array(a.positions);return Ea(A,C,0,A.length-1),h&&h.push(A.buffer,C.buffer),{ids:A,positions:C}}static deserialize(a){let h=new Ha;return h.ids=a.ids,h.positions=a.positions,h.indexed=!0,h}}function Gl(d){let a=+d;return!isNaN(a)&&a<=Number.MAX_SAFE_INTEGER?a:qs(String(d))}function Ea(d,a,h,A){for(;h>1],D=h-1,O=A+1;for(;;){do D++;while(d[D]C);if(D>=O)break;v(d,D,O),v(a,3*D,3*O),v(a,3*D+1,3*O+1),v(a,3*D+2,3*O+2)}O-h`u_${C}`),this.type=A}setUniform(a,h,A){a.set(A.constantOr(this.value))}getBinding(a,h,A){return this.type==="color"?new N(a,h):new E(a,h)}}class ue{constructor(a,h){this.uniformNames=h.map(A=>`u_${A}`),this.patternFrom=null,this.patternTo=null,this.pixelRatioFrom=1,this.pixelRatioTo=1}setConstantPatternPositions(a,h){this.pixelRatioFrom=h.pixelRatio,this.pixelRatioTo=a.pixelRatio,this.patternFrom=h.tlbr,this.patternTo=a.tlbr}setConstantDashPositions(a,h){this.dashTo=[0,a.y,a.height,a.width],this.dashFrom=[0,h.y,h.height,h.width]}setUniform(a,h,A,C){let D=null;C==="u_pattern_to"?D=this.patternTo:C==="u_pattern_from"?D=this.patternFrom:C==="u_dasharray_to"?D=this.dashTo:C==="u_dasharray_from"?D=this.dashFrom:C==="u_pixel_ratio_to"?D=this.pixelRatioTo:C==="u_pixel_ratio_from"&&(D=this.pixelRatioFrom),D!==null&&a.set(D)}getBinding(a,h,A){return A.substr(0,9)==="u_pattern"||A.substr(0,12)==="u_dasharray_"?new B(a,h):new E(a,h)}}class Ae{constructor(a,h,A,C){this.expression=a,this.type=A,this.maxValue=0,this.paintVertexAttributes=h.map(D=>({name:`a_${D}`,type:"Float32",components:A==="color"?2:1,offset:0})),this.paintVertexArray=new C}populatePaintArray(a,h,A){let C=this.paintVertexArray.length,D=this.expression.evaluate(new Hi(0,A),h,{},A.canonical,[],A.formattedSection);this.paintVertexArray.resize(a),this._setPaintValue(C,a,D)}updatePaintArray(a,h,A,C,D){let O=this.expression.evaluate(new Hi(0,D),A,C);this._setPaintValue(a,h,O)}_setPaintValue(a,h,A){if(this.type==="color"){let C=X(A);for(let D=a;D`u_${G}_t`),this.type=A,this.useIntegerZoom=C,this.zoom=D,this.maxValue=0,this.paintVertexAttributes=h.map(G=>({name:`a_${G}`,type:"Float32",components:A==="color"?4:2,offset:0})),this.paintVertexArray=new O}populatePaintArray(a,h,A){let C=this.expression.evaluate(new Hi(this.zoom,A),h,{},A.canonical,[],A.formattedSection),D=this.expression.evaluate(new Hi(this.zoom+1,A),h,{},A.canonical,[],A.formattedSection),O=this.paintVertexArray.length;this.paintVertexArray.resize(a),this._setPaintValue(O,a,C,D)}updatePaintArray(a,h,A,C,D){let O=this.expression.evaluate(new Hi(this.zoom,D),A,C),G=this.expression.evaluate(new Hi(this.zoom+1,D),A,C);this._setPaintValue(a,h,O,G)}_setPaintValue(a,h,A,C){if(this.type==="color"){let D=X(A),O=X(C);for(let G=a;G`#define HAS_UNIFORM_${C}`))}return a}getBinderAttributes(){let a=[];for(let h in this.binders){let A=this.binders[h];if(A instanceof Ae||A instanceof Te)for(let C=0;C!0){this.programConfigurations={};for(let C of a)this.programConfigurations[C.id]=new St(C,h,A);this.needsUpload=!1,this._featureMap=new Ha,this._bufferOffset=0}populatePaintArrays(a,h,A,C){for(let D in this.programConfigurations)this.programConfigurations[D].populatePaintArrays(a,h,C);h.id!==void 0&&this._featureMap.add(h.id,A,this._bufferOffset,a),this._bufferOffset=a,this.needsUpload=!0}updatePaintArrays(a,h,A,C){for(let D of A)this.needsUpload=this.programConfigurations[D.id].updatePaintArrays(a,this._featureMap,h,D,C)||this.needsUpload}get(a){return this.programConfigurations[a]}upload(a){if(this.needsUpload){for(let h in this.programConfigurations)this.programConfigurations[h].upload(a);this.needsUpload=!1}}destroy(){for(let a in this.programConfigurations)this.programConfigurations[a].destroy()}}function Ar(d,a){return{"text-opacity":["opacity"],"icon-opacity":["opacity"],"text-color":["fill_color"],"icon-color":["fill_color"],"text-halo-color":["halo_color"],"icon-halo-color":["halo_color"],"text-halo-blur":["halo_blur"],"icon-halo-blur":["halo_blur"],"text-halo-width":["halo_width"],"icon-halo-width":["halo_width"],"line-gap-width":["gapwidth"],"line-dasharray":["dasharray_to","dasharray_from"],"line-pattern":["pattern_to","pattern_from","pixel_ratio_to","pixel_ratio_from"],"fill-pattern":["pattern_to","pattern_from","pixel_ratio_to","pixel_ratio_from"],"fill-extrusion-pattern":["pattern_to","pattern_from","pixel_ratio_to","pixel_ratio_from"]}[d]||[d.replace(`${a}-`,"").replace(/-/g,"_")]}function qr(d,a,h){let A={color:{source:Il,composite:Qg},number:{source:ng,composite:Il}},C=function(D){return{"line-pattern":{source:Lt,composite:Lt},"fill-pattern":{source:Lt,composite:Lt},"fill-extrusion-pattern":{source:Lt,composite:Lt},"line-dasharray":{source:Kt,composite:Kt}}[D]}(d);return C&&C[h]||A[a][h]}ut("ConstantBinder",Q),ut("CrossFadedConstantBinder",ue),ut("SourceExpressionBinder",Ae),ut("CrossFadedPatternBinder",bt),ut("CrossFadedDasharrayBinder",Tt),ut("CompositeExpressionBinder",Te),ut("ProgramConfiguration",St,{omit:["_buffers"]}),ut("ProgramConfigurationSet",er);let _r=Math.pow(2,14)-1,Sr=-_r-1;function or(d){let a=hr/d.extent,h=d.loadGeometry();for(let A=0;AO.x+1||ZO.y+1)&&ln("Geometry exceeds allowed extent, reduce your vector tile buffer size")}}return h}function Pi(d,a){return{type:d.type,id:d.id,properties:d.properties,geometry:a?or(d):[]}}let En=-32768;function wn(d,a,h,A,C){d.emplaceBack(En+8*a+A,En+8*h+C)}class vo{constructor(a){this.zoom=a.zoom,this.overscaling=a.overscaling,this.layers=a.layers,this.layerIds=this.layers.map(h=>h.id),this.index=a.index,this.hasDependencies=!1,this.layoutVertexArray=new je,this.indexArray=new br,this.segments=new Ai,this.programConfigurations=new er(a.layers,a.zoom),this.stateDependentLayerIds=this.layers.filter(h=>h.isStateDependent()).map(h=>h.id)}populate(a,h,A){let C=this.layers[0],D=[],O=null,G=!1,Z=C.type==="heatmap";if(C.type==="circle"){let ce=C;O=ce.layout.get("circle-sort-key"),G=!O.isConstant(),Z=Z||ce.paint.get("circle-pitch-alignment")==="map"}let re=Z?h.subdivisionGranularity.circle:1;for(let{feature:ce,id:de,index:we,sourceLayerIndex:Ee}of a){let De=this.layers[0]._featureFilter.needGeometry,ke=Pi(ce,De);if(!this.layers[0]._featureFilter.filter(new Hi(this.zoom),ke,A))continue;let $e=G?O.evaluate(ke,{},A):void 0,st={id:de,properties:ce.properties,type:ce.type,sourceLayerIndex:Ee,index:we,geometry:De?ke.geometry:or(ce),patterns:{},sortKey:$e};D.push(st)}G&&D.sort((ce,de)=>ce.sortKey-de.sortKey);for(let ce of D){let{geometry:de,index:we,sourceLayerIndex:Ee}=ce,De=a[we].feature;this.addFeature(ce,de,we,A,re),h.featureIndex.insert(De,de,we,Ee,this.index)}}update(a,h,A){this.stateDependentLayers.length&&this.programConfigurations.updatePaintArrays(a,h,this.stateDependentLayers,{imagePositions:A})}isEmpty(){return this.layoutVertexArray.length===0}uploadPending(){return!this.uploaded||this.programConfigurations.needsUpload}upload(a){this.uploaded||(this.layoutVertexBuffer=a.createVertexBuffer(this.layoutVertexArray,Ir),this.indexBuffer=a.createIndexBuffer(this.indexArray)),this.programConfigurations.upload(a),this.uploaded=!0}destroy(){this.layoutVertexBuffer&&(this.layoutVertexBuffer.destroy(),this.indexBuffer.destroy(),this.programConfigurations.destroy(),this.segments.destroy())}addFeature(a,h,A,C,D=1){let O;switch(D){case 1:O=[0,7];break;case 3:O=[0,2,5,7];break;case 5:O=[0,1,3,4,6,7];break;case 7:O=[0,1,2,3,4,5,6,7];break;default:throw new Error(`Invalid circle bucket granularity: ${D}; valid values are 1, 3, 5, 7.`)}let G=O.length;for(let Z of h)for(let re of Z){let ce=re.x,de=re.y;if(ce<0||ce>=hr||de<0||de>=hr)continue;let we=this.segments.prepareSegment(G*G,this.layoutVertexArray,this.indexArray,a.sortKey),Ee=we.vertexLength;for(let De=0;De1){if(Oi(d,a))return!0;for(let A=0;A1?h:h.sub(a)._mult(C)._add(a))}function va(d,a){let h,A,C,D=!1;for(let O=0;Oa.y!=C.y>a.y&&a.x<(C.x-A.x)*(a.y-A.y)/(C.y-A.y)+A.x&&(D=!D)}return D}function Bo(d,a){let h=!1;for(let A=0,C=d.length-1;Aa.y!=O.y>a.y&&a.x<(O.x-D.x)*(a.y-D.y)/(O.y-D.y)+D.x&&(h=!h)}return h}function Gx(d,a,h){let A=h[0],C=h[2];if(d.xC.x&&a.x>C.x||d.yC.y&&a.y>C.y)return!1;let D=Nn(d,a,h[0]);return D!==Nn(d,a,h[1])||D!==Nn(d,a,h[2])||D!==Nn(d,a,h[3])}function Sd(d,a,h){let A=a.paint.get(d).value;return A.kind==="constant"?A.value:h.programConfigurations.get(a.id).getMaxValue(d)}function Jg(d){return Math.sqrt(d[0]*d[0]+d[1]*d[1])}function Hx(d,a,h,A,C){if(!a[0]&&!a[1])return d;let D=s.convert(a)._mult(C);h==="viewport"&&D._rotate(-A);let O=[];for(let G=0;GTC(pt,$e,st,kt))}(re,D,G,Z),De=ce),cL({queryGeometry:Ee,size:De,transform:D,unwrappedTileID:G,getElevation:Z,pitchAlignment:we,pitchScale:de},C)}}class SC extends vo{}let fL;ut("HeatmapBucket",SC,{omit:["layers"]});var E5={get paint(){return fL=fL||new Fa({"heatmap-radius":new _n(wr.paint_heatmap["heatmap-radius"]),"heatmap-weight":new _n(wr.paint_heatmap["heatmap-weight"]),"heatmap-intensity":new en(wr.paint_heatmap["heatmap-intensity"]),"heatmap-color":new Gc(wr.paint_heatmap["heatmap-color"]),"heatmap-opacity":new en(wr.paint_heatmap["heatmap-opacity"])})}};function EC(d,{width:a,height:h},A,C){if(C){if(C instanceof Uint8ClampedArray)C=new Uint8Array(C.buffer);else if(C.length!==a*h*A)throw new RangeError(`mismatched image size. expected: ${C.length} but got: ${a*h*A}`)}else C=new Uint8Array(a*h*A);return d.width=a,d.height=h,d.data=C,d}function CC(d,{width:a,height:h},A){if(a===d.width&&h===d.height)return;let C=EC({},{width:a,height:h},A);ev(d,C,{x:0,y:0},{x:0,y:0},{width:Math.min(d.width,a),height:Math.min(d.height,h)},A),d.width=a,d.height=h,d.data=C.data}function ev(d,a,h,A,C,D){if(C.width===0||C.height===0)return a;if(C.width>d.width||C.height>d.height||h.x>d.width-C.width||h.y>d.height-C.height)throw new RangeError("out of range source coordinates for image copy");if(C.width>a.width||C.height>a.height||A.x>a.width-C.width||A.y>a.height-C.height)throw new RangeError("out of range destination coordinates for image copy");let O=d.data,G=a.data;if(O===G)throw new Error("srcData equals dstData, so image is already copied");for(let Z=0;Z{a[d.evaluationKey]=Z;let re=d.expression.evaluate(a);C.setPixel(O/4/h,G/4,re)};if(d.clips)for(let O=0,G=0;Othis.max&&(this.max=de),de=this.dim+1||h<-1||h>=this.dim+1)throw new RangeError("out of range source coordinates for DEM data");return(h+1)*this.stride+(a+1)}unpack(a,h,A){return a*this.redFactor+h*this.greenFactor+A*this.blueFactor-this.baseShift}pack(a){return t2(a,this.getUnpackVector())}getPixels(){return new rh({width:this.stride,height:this.stride},new Uint8Array(this.data.buffer))}backfillBorder(a,h,A){if(this.dim!==a.dim)throw new Error("dem dimension mismatch");let C=h*this.dim,D=h*this.dim+this.dim,O=A*this.dim,G=A*this.dim+this.dim;switch(h){case-1:C=D-1;break;case 1:D=C+1}switch(A){case-1:O=G-1;break;case 1:G=O+1}let Z=-h*this.dim,re=-A*this.dim;for(let ce=O;ce0)for(let O=a;O=a;O-=A)D=DC(O/A|0,d[O],d[O+1],D);return D&&Am(D,D.next)&&(S_(D),D=D.next),D}function eA(d,a){if(!d)return d;a||(a=d);let h,A=d;do if(h=!1,A.steiner||!Am(A,A.next)&&rc(A.prev,A,A.next)!==0)A=A.next;else{if(S_(A),A=a=A.prev,A===A.next)break;h=!0}while(h||A!==a);return a}function $x(d,a,h,A,C,D,O){if(!d)return;!O&&D&&function(Z,re,ce,de){let we=Z;do we.z===0&&(we.z=r2(we.x,we.y,re,ce,de)),we.prevZ=we.prev,we.nextZ=we.next,we=we.next;while(we!==Z);we.prevZ.nextZ=null,we.prevZ=null,function(Ee){let De,ke=1;do{let $e,st=Ee;Ee=null;let kt=null;for(De=0;st;){De++;let pt=st,_t=0;for(let Qt=0;Qt0||Gt>0&&pt;)_t!==0&&(Gt===0||!pt||st.z<=pt.z)?($e=st,st=st.nextZ,_t--):($e=pt,pt=pt.nextZ,Gt--),kt?kt.nextZ=$e:Ee=$e,$e.prevZ=kt,kt=$e;st=pt}kt.nextZ=null,ke*=2}while(De>1)}(we)}(d,A,C,D);let G=d;for(;d.prev!==d.next;){let Z=d.prev,re=d.next;if(D?gL(d,A,C,D):mL(d))a.push(Z.i,d.i,re.i),S_(d),d=re.next,G=re.next;else if((d=re)===G){O?O===1?$x(d=AL(eA(d),a),a,h,A,C,D,2):O===2&&_L(d,a,h,A,C,D):$x(eA(d),a,h,A,C,D,1);break}}}function mL(d){let a=d.prev,h=d,A=d.next;if(rc(a,h,A)>=0)return!1;let C=a.x,D=h.x,O=A.x,G=a.y,Z=h.y,re=A.y,ce=Math.min(C,D,O),de=Math.min(G,Z,re),we=Math.max(C,D,O),Ee=Math.max(G,Z,re),De=A.next;for(;De!==a;){if(De.x>=ce&&De.x<=we&&De.y>=de&&De.y<=Ee&&tA(C,G,D,Z,O,re,De.x,De.y)&&rc(De.prev,De,De.next)>=0)return!1;De=De.next}return!0}function gL(d,a,h,A){let C=d.prev,D=d,O=d.next;if(rc(C,D,O)>=0)return!1;let G=C.x,Z=D.x,re=O.x,ce=C.y,de=D.y,we=O.y,Ee=Math.min(G,Z,re),De=Math.min(ce,de,we),ke=Math.max(G,Z,re),$e=Math.max(ce,de,we),st=r2(Ee,De,a,h,A),kt=r2(ke,$e,a,h,A),pt=d.prevZ,_t=d.nextZ;for(;pt&&pt.z>=st&&_t&&_t.z<=kt;){if(pt.x>=Ee&&pt.x<=ke&&pt.y>=De&&pt.y<=$e&&pt!==C&&pt!==O&&tA(G,ce,Z,de,re,we,pt.x,pt.y)&&rc(pt.prev,pt,pt.next)>=0||(pt=pt.prevZ,_t.x>=Ee&&_t.x<=ke&&_t.y>=De&&_t.y<=$e&&_t!==C&&_t!==O&&tA(G,ce,Z,de,re,we,_t.x,_t.y)&&rc(_t.prev,_t,_t.next)>=0))return!1;_t=_t.nextZ}for(;pt&&pt.z>=st;){if(pt.x>=Ee&&pt.x<=ke&&pt.y>=De&&pt.y<=$e&&pt!==C&&pt!==O&&tA(G,ce,Z,de,re,we,pt.x,pt.y)&&rc(pt.prev,pt,pt.next)>=0)return!1;pt=pt.prevZ}for(;_t&&_t.z<=kt;){if(_t.x>=Ee&&_t.x<=ke&&_t.y>=De&&_t.y<=$e&&_t!==C&&_t!==O&&tA(G,ce,Z,de,re,we,_t.x,_t.y)&&rc(_t.prev,_t,_t.next)>=0)return!1;_t=_t.nextZ}return!0}function AL(d,a){let h=d;do{let A=h.prev,C=h.next.next;!Am(A,C)&&vL(A,h,h.next,C)&&og(A,C)&&og(C,A)&&(a.push(A.i,h.i,C.i),S_(h),S_(h.next),h=d=C),h=h.next}while(h!==d);return eA(h)}function _L(d,a,h,A,C,D){let O=d;do{let G=O.next.next;for(;G!==O.prev;){if(O.i!==G.i&&n2(O,G)){let Z=a2(O,G);return O=eA(O,O.next),Z=eA(Z,Z.next),$x(O,a,h,A,C,D,0),void $x(Z,a,h,A,C,D,0)}G=G.next}O=O.next}while(O!==d)}function M5(d,a){let h=d.x-a.x;return h===0&&(h=d.y-a.y,h===0)&&(h=(d.next.y-d.y)/(d.next.x-d.x)-(a.next.y-a.y)/(a.next.x-a.x)),h}function I5(d,a){let h=function(C,D){let O=D,G=C.x,Z=C.y,re,ce=-1/0;if(Am(C,O))return O;do{if(Am(C,O.next))return O.next;if(Z<=O.y&&Z>=O.next.y&&O.next.y!==O.y){let ke=O.x+(Z-O.y)*(O.next.x-O.x)/(O.next.y-O.y);if(ke<=G&&ke>ce&&(ce=ke,re=O.x=O.x&&O.x>=we&&G!==O.x&&LC(Zre.x||O.x===re.x&&yL(re,O)))&&(re=O,De=ke)}O=O.next}while(O!==de);return re}(d,a);if(!h)return a;let A=a2(h,d);return eA(A,A.next),eA(h,h.next)}function yL(d,a){return rc(d.prev,d,a.prev)<0&&rc(a.next,d,d.next)<0}function r2(d,a,h,A,C){return(d=1431655765&((d=858993459&((d=252645135&((d=16711935&((d=(d-h)*C|0)|d<<8))|d<<4))|d<<2))|d<<1))|(a=1431655765&((a=858993459&((a=252645135&((a=16711935&((a=(a-A)*C|0)|a<<8))|a<<4))|a<<2))|a<<1))<<1}function i2(d){let a=d,h=d;do(a.x=(d-O)*(D-G)&&(d-O)*(A-G)>=(h-O)*(a-G)&&(h-O)*(D-G)>=(C-O)*(A-G)}function tA(d,a,h,A,C,D,O,G){return!(d===O&&a===G)&&LC(d,a,h,A,C,D,O,G)}function n2(d,a){return d.next.i!==a.i&&d.prev.i!==a.i&&!function(h,A){let C=h;do{if(C.i!==h.i&&C.next.i!==h.i&&C.i!==A.i&&C.next.i!==A.i&&vL(C,C.next,h,A))return!0;C=C.next}while(C!==h);return!1}(d,a)&&(og(d,a)&&og(a,d)&&function(h,A){let C=h,D=!1,O=(h.x+A.x)/2,G=(h.y+A.y)/2;do C.y>G!=C.next.y>G&&C.next.y!==C.y&&O<(C.next.x-C.x)*(G-C.y)/(C.next.y-C.y)+C.x&&(D=!D),C=C.next;while(C!==h);return D}(d,a)&&(rc(d.prev,d,a.prev)||rc(d,a.prev,a))||Am(d,a)&&rc(d.prev,d,d.next)>0&&rc(a.prev,a,a.next)>0)}function rc(d,a,h){return(a.y-d.y)*(h.x-a.x)-(a.x-d.x)*(h.y-a.y)}function Am(d,a){return d.x===a.x&&d.y===a.y}function vL(d,a,h,A){let C=s2(rc(d,a,h)),D=s2(rc(d,a,A)),O=s2(rc(h,A,d)),G=s2(rc(h,A,a));return C!==D&&O!==G||!(C!==0||!o2(d,h,a))||!(D!==0||!o2(d,A,a))||!(O!==0||!o2(h,d,A))||!(G!==0||!o2(h,a,A))}function o2(d,a,h){return a.x<=Math.max(d.x,h.x)&&a.x>=Math.min(d.x,h.x)&&a.y<=Math.max(d.y,h.y)&&a.y>=Math.min(d.y,h.y)}function s2(d){return d>0?1:d<0?-1:0}function og(d,a){return rc(d.prev,d,d.next)<0?rc(d,a,d.next)>=0&&rc(d,d.prev,a)>=0:rc(d,a,d.prev)<0||rc(d,d.next,a)<0}function a2(d,a){let h=l2(d.i,d.x,d.y),A=l2(a.i,a.x,a.y),C=d.next,D=a.prev;return d.next=a,a.prev=d,h.next=C,C.prev=h,A.next=h,h.prev=A,D.next=A,A.prev=D,A}function DC(d,a,h,A){let C=l2(d,a,h);return A?(C.next=A.next,C.prev=A,A.next.prev=C,A.next=C):(C.prev=C,C.next=C),C}function S_(d){d.next.prev=d.prev,d.prev.next=d.next,d.prevZ&&(d.prevZ.nextZ=d.nextZ),d.nextZ&&(d.nextZ.prevZ=d.prevZ)}function l2(d,a,h){return{i:d,x:a,y:h,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}class E_{constructor(a,h){if(h>a)throw new Error("Min granularity must not be greater than base granularity.");this._baseZoomGranularity=a,this._minGranularity=h}getGranularityForZoomLevel(a){return Math.max(Math.floor(this._baseZoomGranularity/(1<32767||h>32767)throw new Error("Vertex coordinates are out of signed 16 bit integer range.");let A=0|Math.round(a),C=0|Math.round(h),D=this._getKey(A,C);if(this._vertexDictionary.has(D))return this._vertexDictionary.get(D);let O=this._vertexBuffer.length/2;return this._vertexDictionary.set(D,O),this._vertexBuffer.push(A,C),O}_subdivideTrianglesScanline(a){if(this._granularity<2)return function(C,D){let O=[];for(let G=0;G0?(O.push(Z),O.push(ce),O.push(re)):(O.push(Z),O.push(re),O.push(ce))}return O}(this._vertexBuffer,a);let h=[],A=a.length;for(let C=0;C=1||Gt<=0)||st&&(reD)){de>=C&&de<=D&&O.push(A[(G+1)%3]);continue}!st&&_t>0&&O.push(this._vertexToIndex(Z+De*_t,re+ke*_t));let Qt=Z+De*Math.max(_t,0),Mr=Z+De*Math.min(Gt,1);$e||this._generateIntraEdgeVertices(O,Z,re,ce,de,Qt,Mr),!st&&Gt<1&&O.push(this._vertexToIndex(Z+De*Gt,re+ke*Gt)),(st||de>=C&&de<=D)&&O.push(A[(G+1)%3]),!st&&(de<=C||de>=D)&&this._generateInterEdgeVertices(O,Z,re,ce,de,we,Ee,Mr,C,D)}return O}_generateIntraEdgeVertices(a,h,A,C,D,O,G){let Z=C-h,re=D-A,ce=re===0,de=ce?Math.min(h,C):Math.min(O,G),we=ce?Math.max(h,C):Math.max(O,G),Ee=Math.floor(de/this._granularityCellSize)+1,De=Math.ceil(we/this._granularityCellSize)-1;if(ce?h=Ee;ke--){let $e=ke*this._granularityCellSize;a.push(this._vertexToIndex($e,A+re*($e-h)/Z))}}_generateInterEdgeVertices(a,h,A,C,D,O,G,Z,re,ce){let de=D-A,we=O-C,Ee=G-D,De=(re-D)/Ee,ke=(ce-D)/Ee,$e=Math.min(De,ke),st=Math.max(De,ke),kt=C+we*$e,pt=Math.floor(Math.min(kt,Z)/this._granularityCellSize)+1,_t=Math.ceil(Math.max(kt,Z)/this._granularityCellSize)-1,Gt=Z=1||st<=0){let oi=A-G,Kr=O+(h-O)*Math.min((re-G)/oi,(ce-G)/oi);pt=Math.floor(Math.min(Kr,Z)/this._granularityCellSize)+1,_t=Math.ceil(Math.max(Kr,Z)/this._granularityCellSize)-1,Gt=Z0?ce:re;if(Gt)for(let oi=pt;oi<=_t;oi++)a.push(this._vertexToIndex(oi*this._granularityCellSize,Mr));else for(let oi=_t;oi>=pt;oi--)a.push(this._vertexToIndex(oi*this._granularityCellSize,Mr))}_generateOutline(a){let h=[];for(let A of a){let C=C_(A,this._granularity,!0),D=this._pointArrayToIndices(C),O=[];for(let G=1;GD!=(O===tv)?(a.push(h),a.push(A),a.push(this._vertexToIndex(C,O)),a.push(A),a.push(this._vertexToIndex(D,O)),a.push(this._vertexToIndex(C,O))):(a.push(A),a.push(h),a.push(this._vertexToIndex(C,O)),a.push(this._vertexToIndex(D,O)),a.push(A),a.push(this._vertexToIndex(C,O)))}_fillPoles(a,h,A){let C=this._vertexBuffer,D=hr,O=a.length;for(let G=2;G80*de){$e=re[0],st=re[1];let pt=$e,_t=st;for(let Gt=de;Gtpt&&(pt=Qt),Mr>_t&&(_t=Mr)}kt=Math.max(pt-$e,_t-st),kt=kt!==0?32767/kt:0}return $x(De,ke,de,$e,st,kt,0),ke}(A,C),Z=this._convertIndices(A,G);D=this._subdivideTrianglesScanline(Z)}catch(G){console.error(G)}let O=[];return h&&(O=this._generateOutline(a)),this._ensureNoPoleVertices(),this._handlePoles(D),{verticesFlattened:this._vertexBuffer,indicesTriangles:D,indicesLineList:O}}_convertIndices(a,h){let A=[];for(let C=0;C0?(Math.floor(Mr/O)+1)*O:(Math.ceil(Mr/O)-1)*O,ji=_t>0?(Math.floor(oi/O)+1)*O:(Math.ceil(oi/O)-1)*O,vi=Math.abs(Mr-gi),Qr=Math.abs(oi-ji),jr=Math.abs(Mr-ke),dn=Math.abs(oi-$e),cn=st?vi/Gt:Number.POSITIVE_INFINITY,vn=kt?Qr/Qt:Number.POSITIVE_INFINITY;if((jr<=vi||!st)&&(dn<=Qr||!kt))break;if(cn=0?O-1:D-1,re=(G+1)%D,ce=d[2*a[Z]],de=d[2*a[re]],we=d[2*a[O]],Ee=d[2*a[O]+1],De=d[2*a[G]+1],ke=!1;if(cede)ke=!1;else{let $e=De-Ee,st=-(d[2*a[G]]-we),kt=Ee((de-we)*$e+(d[2*a[re]+1]-Ee)*st)*kt&&(ke=!0)}if(ke){let $e=a[Z],st=a[O],kt=a[G];$e!==st&&$e!==kt&&st!==kt&&h.push(kt,st,$e),O--,O<0&&(O=D-1)}else{let $e=a[re],st=a[O],kt=a[G];$e!==st&&$e!==kt&&st!==kt&&h.push(kt,st,$e),G++,G>=D&&(G=0)}if(Z===re)break}}function iv(d,a,h,A,C,D,O,G,Z){let re=C.length/2,ce=O&&G&&Z;if(reAi.MAX_VERTEX_ARRAY_LENGTH&&(_t=de.createNewSegment(we,Ee),pt=kt.count,gi=!0,ji=!0,vi=!0,Gt=0);let Qr=Qs(st,De,$e,kt,Mr,gi,_t),jr=Qs(st,De,$e,kt,oi,ji,_t),dn=Qs(st,De,$e,kt,Kr,vi,_t);Ee.emplaceBack(Gt+Qr-pt,Gt+jr-pt,Gt+dn-pt),_t.primitiveLength++}})(a,h,A,C,D,d),ce&&function(de,we,Ee,De,ke,$e){let st=[];for(let Qt=0;QtAi.MAX_VERTEX_ARRAY_LENGTH&&(_t=de.createNewSegment(we,Ee),pt=kt.count,ji=!0,vi=!0,Gt=0);let Qr=Qs(st,De,$e,kt,Kr,ji,_t),jr=Qs(st,De,$e,kt,gi,vi,_t);Ee.emplaceBack(Gt+Qr-pt,Gt+jr-pt),_t.primitiveLength++}}}(O,h,G,C,Z,d),a.forceNewSegmentOnNextPrepare(),O?.forceNewSegmentOnNextPrepare()}function Qs(d,a,h,A,C,D,O){if(D){let G=A.count;return h(a[2*C],a[2*C+1]),d[C]=A.count,A.count++,O.vertexLength++,G}return d[C]}class OC{constructor(a){this.zoom=a.zoom,this.overscaling=a.overscaling,this.layers=a.layers,this.layerIds=this.layers.map(h=>h.id),this.index=a.index,this.hasDependencies=!1,this.patternFeatures=[],this.layoutVertexArray=new tt,this.indexArray=new br,this.indexArray2=new Cr,this.programConfigurations=new er(a.layers,a.zoom),this.segments=new Ai,this.segments2=new Ai,this.stateDependentLayerIds=this.layers.filter(h=>h.isStateDependent()).map(h=>h.id)}populate(a,h,A){this.hasDependencies=t0("fill",this.layers,h);let C=this.layers[0].layout.get("fill-sort-key"),D=!C.isConstant(),O=[];for(let{feature:G,id:Z,index:re,sourceLayerIndex:ce}of a){let de=this.layers[0]._featureFilter.needGeometry,we=Pi(G,de);if(!this.layers[0]._featureFilter.filter(new Hi(this.zoom),we,A))continue;let Ee=D?C.evaluate(we,{},A,h.availableImages):void 0,De={id:Z,properties:G.properties,type:G.type,sourceLayerIndex:ce,index:re,geometry:de?we.geometry:or(G),patterns:{},sortKey:Ee};O.push(De)}D&&O.sort((G,Z)=>G.sortKey-Z.sortKey);for(let G of O){let{geometry:Z,index:re,sourceLayerIndex:ce}=G;if(this.hasDependencies){let de=qx("fill",this.layers,G,{zoom:this.zoom},h);this.patternFeatures.push(de)}else this.addFeature(G,Z,re,A,{},h.subdivisionGranularity);h.featureIndex.insert(a[re].feature,Z,re,ce,this.index)}}update(a,h,A){this.stateDependentLayers.length&&this.programConfigurations.updatePaintArrays(a,h,this.stateDependentLayers,{imagePositions:A})}addFeatures(a,h,A){for(let C of this.patternFeatures)this.addFeature(C,C.geometry,C.index,h,A,a.subdivisionGranularity)}isEmpty(){return this.layoutVertexArray.length===0}uploadPending(){return!this.uploaded||this.programConfigurations.needsUpload}upload(a){this.uploaded||(this.layoutVertexBuffer=a.createVertexBuffer(this.layoutVertexArray,pL),this.indexBuffer=a.createIndexBuffer(this.indexArray),this.indexBuffer2=a.createIndexBuffer(this.indexArray2)),this.programConfigurations.upload(a),this.uploaded=!0}destroy(){this.layoutVertexBuffer&&(this.layoutVertexBuffer.destroy(),this.indexBuffer.destroy(),this.indexBuffer2.destroy(),this.programConfigurations.destroy(),this.segments.destroy(),this.segments2.destroy())}addFeature(a,h,A,C,D,O){for(let G of dt(h,500)){let Z=h2(G,C,O.fill.getGranularityForZoomLevel(C.z)),re=this.layoutVertexArray;iv((ce,de)=>{re.emplaceBack(ce,de)},this.segments,this.layoutVertexArray,this.indexArray,Z.verticesFlattened,Z.indicesTriangles,this.segments2,this.indexArray2,Z.indicesLineList)}this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length,a,A,{imagePositions:D,canonical:C})}}let f2,M_;ut("FillBucket",OC,{omit:["layers","patternFeatures"]});var P5={get paint(){return M_=M_||new Fa({"fill-antialias":new en(wr.paint_fill["fill-antialias"]),"fill-opacity":new _n(wr.paint_fill["fill-opacity"]),"fill-color":new _n(wr.paint_fill["fill-color"]),"fill-outline-color":new _n(wr.paint_fill["fill-outline-color"]),"fill-translate":new en(wr.paint_fill["fill-translate"]),"fill-translate-anchor":new en(wr.paint_fill["fill-translate-anchor"]),"fill-pattern":new Ju(wr.paint_fill["fill-pattern"])})},get layout(){return f2=f2||new Fa({"fill-sort-key":new _n(wr.layout_fill["fill-sort-key"])})}};class R5 extends Hc{constructor(a,h){super(a,P5,h)}recalculate(a,h){super.recalculate(a,h);let A=this.paint._values["fill-outline-color"];A.value.kind==="constant"&&A.value.value===void 0&&(this.paint._values["fill-outline-color"]=this.paint._values["fill-color"])}createBucket(a){return new OC(a)}queryRadius(){return Jg(this.paint.get("fill-translate"))}queryIntersectsFeature({queryGeometry:a,geometry:h,transform:A,pixelsToTileUnits:C}){return ro(Hx(a,this.paint.get("fill-translate"),this.paint.get("fill-translate-anchor"),-A.bearingInRadians,C),h)}isTileClipped(){return!0}}let B5=ia([{name:"a_pos",components:2,type:"Int16"},{name:"a_normal_ed",components:4,type:"Int16"}],4),L5=ia([{name:"a_centroid",components:2,type:"Int16"}],4),{members:D5}=B5;class Zx{constructor(a,h,A,C,D){this.properties={},this.extent=A,this.type=0,this.id=void 0,this._pbf=a,this._geometry=-1,this._keys=C,this._values=D,a.readFields(O5,this,h)}loadGeometry(){let a=this._pbf;a.pos=this._geometry;let h=a.readVarint()+a.pos,A=[],C,D=1,O=0,G=0,Z=0;for(;a.pos>3}if(O--,D===1||D===2)G+=a.readSVarint(),Z+=a.readSVarint(),D===1&&(C&&A.push(C),C=[]),C&&C.push(new s(G,Z));else{if(D!==7)throw new Error(`unknown command ${D}`);C&&C.push(C[0].clone())}}return C&&A.push(C),A}bbox(){let a=this._pbf;a.pos=this._geometry;let h=a.readVarint()+a.pos,A=1,C=0,D=0,O=0,G=1/0,Z=-1/0,re=1/0,ce=-1/0;for(;a.pos>3}if(C--,A===1||A===2)D+=a.readSVarint(),O+=a.readSVarint(),DZ&&(Z=D),Oce&&(ce=O);else if(A!==7)throw new Error(`unknown command ${A}`)}return[G,re,Z,ce]}toGeoJSON(a,h,A){let C=this.extent*Math.pow(2,A),D=this.extent*a,O=this.extent*h,G=this.loadGeometry();function Z(we){return[360*(we.x+D)/C-180,360/Math.PI*Math.atan(Math.exp((1-2*(we.y+O)/C)*Math.PI))-90]}function re(we){return we.map(Z)}let ce;if(this.type===1){let we=[];for(let De of G)we.push(De[0]);let Ee=re(we);ce=we.length===1?{type:"Point",coordinates:Ee[0]}:{type:"MultiPoint",coordinates:Ee}}else if(this.type===2){let we=G.map(re);ce=we.length===1?{type:"LineString",coordinates:we[0]}:{type:"MultiLineString",coordinates:we}}else{if(this.type!==3)throw new Error("unknown feature type");{let we=FC(G),Ee=[];for(let De of we)Ee.push(De.map(re));ce=Ee.length===1?{type:"Polygon",coordinates:Ee[0]}:{type:"MultiPolygon",coordinates:Ee}}}let de={type:"Feature",geometry:ce,properties:this.properties};return this.id!=null&&(de.id=this.id),de}}function O5(d,a,h){d===1?a.id=h.readVarint():d===2?function(A,C){let D=A.readVarint()+A.pos;for(;A.pos=this._features.length)throw new Error("feature index out of bounds");this._pbf.pos=this._features[a];let h=this._pbf.readVarint()+this._pbf.pos;return new Zx(this._pbf,h,this.extent,this._keys,this._values)}}function wL(d,a,h){d===15?a.version=h.readVarint():d===1?a.name=h.readString():d===5?a.extent=h.readVarint():d===2?a._features.push(h.pos):d===3?a._keys.push(h.readString()):d===4&&a._values.push(function(A){let C=null,D=A.readVarint()+A.pos;for(;A.pos>3;C=O===1?A.readString():O===2?A.readFloat():O===3?A.readDouble():O===4?A.readVarint64():O===5?A.readVarint():O===6?A.readSVarint():O===7?A.readBoolean():null}if(C==null)throw new Error("unknown feature value");return C}(h))}class Yx{constructor(a,h){this.layers=a.readFields(I_,{},h)}}function I_(d,a,h){if(d===3){let A=new bL(h,h.readVarint()+h.pos);A.length&&(a[A.name]=A)}}let Kx=Math.pow(2,13);function sg(d,a,h,A,C,D,O,G){d.emplaceBack(a,h,2*Math.floor(A*Kx)+O,C*Kx*2,D*Kx*2,Math.round(G))}class d2{constructor(a){this.zoom=a.zoom,this.overscaling=a.overscaling,this.layers=a.layers,this.layerIds=this.layers.map(h=>h.id),this.index=a.index,this.hasDependencies=!1,this.layoutVertexArray=new yt,this.centroidVertexArray=new Ie,this.indexArray=new br,this.programConfigurations=new er(a.layers,a.zoom),this.segments=new Ai,this.stateDependentLayerIds=this.layers.filter(h=>h.isStateDependent()).map(h=>h.id)}populate(a,h,A){this.features=[],this.hasDependencies=t0("fill-extrusion",this.layers,h);for(let{feature:C,id:D,index:O,sourceLayerIndex:G}of a){let Z=this.layers[0]._featureFilter.needGeometry,re=Pi(C,Z);if(!this.layers[0]._featureFilter.filter(new Hi(this.zoom),re,A))continue;let ce={id:D,sourceLayerIndex:G,index:O,geometry:Z?re.geometry:or(C),properties:C.properties,type:C.type,patterns:{}};this.hasDependencies?this.features.push(qx("fill-extrusion",this.layers,ce,{zoom:this.zoom},h)):this.addFeature(ce,ce.geometry,O,A,{},h.subdivisionGranularity),h.featureIndex.insert(C,ce.geometry,O,G,this.index,!0)}}addFeatures(a,h,A){for(let C of this.features){let{geometry:D}=C;this.addFeature(C,D,C.index,h,A,a.subdivisionGranularity)}}update(a,h,A){this.stateDependentLayers.length&&this.programConfigurations.updatePaintArrays(a,h,this.stateDependentLayers,{imagePositions:A})}isEmpty(){return this.layoutVertexArray.length===0&&this.centroidVertexArray.length===0}uploadPending(){return!this.uploaded||this.programConfigurations.needsUpload}upload(a){this.uploaded||(this.layoutVertexBuffer=a.createVertexBuffer(this.layoutVertexArray,D5),this.centroidVertexBuffer=a.createVertexBuffer(this.centroidVertexArray,L5.members,!0),this.indexBuffer=a.createIndexBuffer(this.indexArray)),this.programConfigurations.upload(a),this.uploaded=!0}destroy(){this.layoutVertexBuffer&&(this.layoutVertexBuffer.destroy(),this.indexBuffer.destroy(),this.programConfigurations.destroy(),this.segments.destroy(),this.centroidVertexBuffer.destroy())}addFeature(a,h,A,C,D,O){for(let G of dt(h,500)){let Z={x:0,y:0,sampleCount:0},re=this.layoutVertexArray.length;this.processPolygon(Z,C,a,G,O);let ce=this.layoutVertexArray.length-re,de=Math.floor(Z.x/Z.sampleCount),we=Math.floor(Z.y/Z.sampleCount);for(let Ee=0;Ee{sg(ce,de,we,0,0,1,1,0)},this.segments,this.layoutVertexArray,this.indexArray,re.verticesFlattened,re.indicesTriangles)}_generateSideFaces(a,h){let A=0;for(let C=1;CAi.MAX_VERTEX_ARRAY_LENGTH&&(h.segment=this.segments.prepareSegment(4,this.layoutVertexArray,this.indexArray));let G=D.sub(O)._perp()._unit(),Z=O.dist(D);A+Z>32768&&(A=0),sg(this.layoutVertexArray,D.x,D.y,G.x,G.y,0,0,A),sg(this.layoutVertexArray,D.x,D.y,G.x,G.y,0,1,A),A+=Z,sg(this.layoutVertexArray,O.x,O.y,G.x,G.y,0,0,A),sg(this.layoutVertexArray,O.x,O.y,G.x,G.y,0,1,A);let re=h.segment.vertexLength;this.indexArray.emplaceBack(re,re+2,re+1),this.indexArray.emplaceBack(re+1,re+2,re+3),h.segment.vertexLength+=4,h.segment.primitiveLength+=2}}}function Wc(d,a){for(let h=0;hhr)||d.y===a.y&&(d.y<0||d.y>hr)}function kC(d){return d.every(a=>a.x<0)||d.every(a=>a.x>hr)||d.every(a=>a.y<0)||d.every(a=>a.y>hr)}let NC;ut("FillExtrusionBucket",d2,{omit:["layers","features"]});var TL={get paint(){return NC=NC||new Fa({"fill-extrusion-opacity":new en(wr["paint_fill-extrusion"]["fill-extrusion-opacity"]),"fill-extrusion-color":new _n(wr["paint_fill-extrusion"]["fill-extrusion-color"]),"fill-extrusion-translate":new en(wr["paint_fill-extrusion"]["fill-extrusion-translate"]),"fill-extrusion-translate-anchor":new en(wr["paint_fill-extrusion"]["fill-extrusion-translate-anchor"]),"fill-extrusion-pattern":new Ju(wr["paint_fill-extrusion"]["fill-extrusion-pattern"]),"fill-extrusion-height":new _n(wr["paint_fill-extrusion"]["fill-extrusion-height"]),"fill-extrusion-base":new _n(wr["paint_fill-extrusion"]["fill-extrusion-base"]),"fill-extrusion-vertical-gradient":new en(wr["paint_fill-extrusion"]["fill-extrusion-vertical-gradient"])})}};class SL extends Hc{constructor(a,h){super(a,TL,h)}createBucket(a){return new d2(a)}queryRadius(){return Jg(this.paint.get("fill-extrusion-translate"))}is3D(){return!0}queryIntersectsFeature({queryGeometry:a,feature:h,featureState:A,geometry:C,transform:D,pixelsToTileUnits:O,pixelPosMatrix:G}){let Z=Hx(a,this.paint.get("fill-extrusion-translate"),this.paint.get("fill-extrusion-translate-anchor"),-D.bearingInRadians,O),re=this.paint.get("fill-extrusion-height").evaluate(h,A),ce=this.paint.get("fill-extrusion-base").evaluate(h,A),de=function(Ee,De){let ke=[];for(let $e of Ee){let st=[$e.x,$e.y,0,1];wt(st,st,De),ke.push(new s(st[0]/st[3],st[1]/st[3]))}return ke}(Z,G),we=function(Ee,De,ke,$e){let st=[],kt=[],pt=$e[8]*De,_t=$e[9]*De,Gt=$e[10]*De,Qt=$e[11]*De,Mr=$e[8]*ke,oi=$e[9]*ke,Kr=$e[10]*ke,gi=$e[11]*ke;for(let ji of Ee){let vi=[],Qr=[];for(let jr of ji){let dn=jr.x,cn=jr.y,vn=$e[0]*dn+$e[4]*cn+$e[12],tn=$e[1]*dn+$e[5]*cn+$e[13],Vn=$e[2]*dn+$e[6]*cn+$e[14],da=$e[3]*dn+$e[7]*cn+$e[15],Gs=Vn+Gt,vc=da+Qt,Ef=vn+Mr,Vu=tn+oi,xc=Vn+Kr,sc=da+gi,za=new s((vn+pt)/vc,(tn+_t)/vc);za.z=Gs/vc,vi.push(za);let $c=new s(Ef/sc,Vu/sc);$c.z=xc/sc,Qr.push($c)}st.push(vi),kt.push(Qr)}return[st,kt]}(C,ce,re,G);return function(Ee,De,ke){let $e=1/0;ro(ke,De)&&($e=p2(ke,De[0]));for(let st=0;sth.id),this.index=a.index,this.hasDependencies=!1,this.patternFeatures=[],this.lineClipsArray=[],this.gradients={},this.layers.forEach(h=>{this.gradients[h.id]={}}),this.layoutVertexArray=new At,this.layoutVertexArray2=new vt,this.indexArray=new br,this.programConfigurations=new er(a.layers,a.zoom),this.segments=new Ai,this.maxLineLength=0,this.stateDependentLayerIds=this.layers.filter(h=>h.isStateDependent()).map(h=>h.id)}populate(a,h,A){this.hasDependencies=t0("line",this.layers,h)||this.hasLineDasharray(this.layers);let C=this.layers[0].layout.get("line-sort-key"),D=!C.isConstant(),O=[];for(let{feature:G,id:Z,index:re,sourceLayerIndex:ce}of a){let de=this.layers[0]._featureFilter.needGeometry,we=Pi(G,de);if(!this.layers[0]._featureFilter.filter(new Hi(this.zoom),we,A))continue;let Ee=D?C.evaluate(we,{},A):void 0,De={id:Z,properties:G.properties,type:G.type,sourceLayerIndex:ce,index:re,geometry:de?we.geometry:or(G),patterns:{},dashes:{},sortKey:Ee};O.push(De)}D&&O.sort((G,Z)=>G.sortKey-Z.sortKey);for(let G of O){let{geometry:Z,index:re,sourceLayerIndex:ce}=G;this.hasDependencies?(t0("line",this.layers,h)?qx("line",this.layers,G,{zoom:this.zoom},h):this.hasLineDasharray(this.layers)&&this.addLineDashDependencies(this.layers,G,this.zoom,h),this.patternFeatures.push(G)):this.addFeature(G,Z,re,A,{},{},h.subdivisionGranularity),h.featureIndex.insert(a[re].feature,Z,re,ce,this.index)}}update(a,h,A,C){this.stateDependentLayers.length&&this.programConfigurations.updatePaintArrays(a,h,this.stateDependentLayers,{imagePositions:A,dashPositions:C})}addFeatures(a,h,A,C){for(let D of this.patternFeatures)this.addFeature(D,D.geometry,D.index,h,A,C,a.subdivisionGranularity)}isEmpty(){return this.layoutVertexArray.length===0}uploadPending(){return!this.uploaded||this.programConfigurations.needsUpload}upload(a){this.uploaded||(this.layoutVertexArray2.length!==0&&(this.layoutVertexBuffer2=a.createVertexBuffer(this.layoutVertexArray2,qc)),this.layoutVertexBuffer=a.createVertexBuffer(this.layoutVertexArray,R_),this.indexBuffer=a.createIndexBuffer(this.indexArray)),this.programConfigurations.upload(a),this.uploaded=!0}destroy(){this.layoutVertexBuffer&&(this.layoutVertexBuffer.destroy(),this.indexBuffer.destroy(),this.programConfigurations.destroy(),this.segments.destroy())}lineFeatureClips(a){if(a.properties&&Object.prototype.hasOwnProperty.call(a.properties,"mapbox_clip_start")&&Object.prototype.hasOwnProperty.call(a.properties,"mapbox_clip_end"))return{start:+a.properties.mapbox_clip_start,end:+a.properties.mapbox_clip_end}}addFeature(a,h,A,C,D,O,G){let Z=this.layers[0].layout,re=Z.get("line-join").evaluate(a,{}),ce=Z.get("line-cap"),de=Z.get("line-miter-limit"),we=Z.get("line-round-limit");this.lineClips=this.lineFeatureClips(a);for(let Ee of h)this.addLine(Ee,a,re,ce,de,we,C,G);this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length,a,A,{imagePositions:D,dashPositions:O,canonical:C})}addLine(a,h,A,C,D,O,G,Z){if(this.distance=0,this.scaledDistance=0,this.totalDistance=0,a=C_(a,G?Z.line.getGranularityForZoomLevel(G.z):1),this.lineClips){this.lineClipsArray.push(this.lineClips);for(let pt=0;pt=2&&a[ce-1].equals(a[ce-2]);)ce--;let de=0;for(;de0;if(Kr&&pt>de){let Qr=De.dist(ke);if(Qr>2*we){let jr=De.sub(De.sub(ke)._mult(we/Qr)._round());this.updateDistance(ke,jr),this.addCurrentVertex(jr,st,0,0,Ee),ke=jr}}let ji=ke&&$e,vi=ji?A:re?"butt":C;if(ji&&vi==="round"&&(MrD&&(vi="bevel"),vi==="bevel"&&(Mr>2&&(vi="flipbevel"),Mr100)_t=kt.mult(-1);else{let Qr=Mr*st.add(kt).mag()/st.sub(kt).mag();_t._perp()._mult(Qr*(gi?-1:1))}this.addCurrentVertex(De,_t,0,0,Ee),this.addCurrentVertex(De,_t.mult(-1),0,0,Ee)}else if(vi==="bevel"||vi==="fakeround"){let Qr=-Math.sqrt(Mr*Mr-1),jr=gi?Qr:0,dn=gi?0:Qr;if(ke&&this.addCurrentVertex(De,st,jr,dn,Ee),vi==="fakeround"){let cn=Math.round(180*oi/Math.PI/20);for(let vn=1;vn2*we){let jr=De.add($e.sub(De)._mult(we/Qr)._round());this.updateDistance(De,jr),this.addCurrentVertex(jr,kt,0,0,Ee),De=jr}}}}addCurrentVertex(a,h,A,C,D,O=!1){let G=h.y*C-h.x,Z=-h.y-h.x*C;this.addHalfVertex(a,h.x+h.y*A,h.y-h.x*A,O,!1,A,D),this.addHalfVertex(a,G,Z,O,!0,-C,D),this.distance>ag/2&&this.totalDistance===0&&(this.distance=0,this.updateScaledDistance(),this.addCurrentVertex(a,h,A,C,D,O))}addHalfVertex({x:a,y:h},A,C,D,O,G,Z){let re=.5*(this.lineClips?this.scaledDistance*(ag-1):this.scaledDistance);this.layoutVertexArray.emplaceBack((a<<1)+(D?1:0),(h<<1)+(O?1:0),Math.round(63*A)+128,Math.round(63*C)+128,1+(G===0?0:G<0?-1:1)|(63&re)<<2,re>>6),this.lineClips&&this.layoutVertexArray2.emplaceBack((this.scaledDistance-this.lineClips.start)/(this.lineClips.end-this.lineClips.start),this.lineClipsArray.length);let ce=Z.vertexLength++;this.e1>=0&&this.e2>=0&&(this.indexArray.emplaceBack(this.e1,ce,this.e2),Z.primitiveLength++),O?this.e2=ce:this.e1=ce}updateScaledDistance(){this.scaledDistance=this.lineClips?this.lineClips.start+(this.lineClips.end-this.lineClips.start)*this.distance/this.totalDistance:this.distance}updateDistance(a,h){this.distance+=a.dist(h),this.updateScaledDistance()}hasLineDasharray(a){for(let h of a){let A=h.paint.get("line-dasharray");if(A&&!A.isConstant())return!0}return!1}addLineDashDependencies(a,h,A,C){for(let D of a){let O=D.paint.get("line-dasharray");if(!O||O.value.kind==="constant")continue;let G=D.layout.get("line-cap")==="round",Z={dasharray:O.value.evaluate({zoom:A-1},h,{}),round:G},re={dasharray:O.value.evaluate({zoom:A},h,{}),round:G},ce={dasharray:O.value.evaluate({zoom:A+1},h,{}),round:G},de=`${Z.dasharray.join(",")},${Z.round}`,we=`${re.dasharray.join(",")},${re.round}`,Ee=`${ce.dasharray.join(",")},${ce.round}`;C.dashDependencies[de]=Z,C.dashDependencies[we]=re,C.dashDependencies[Ee]=ce,h.dashes[D.id]={min:de,mid:we,max:Ee}}}}let Jx,m2;ut("LineBucket",ov,{omit:["layers","patternFeatures"]});var B_={get paint(){return m2=m2||new Fa({"line-opacity":new _n(wr.paint_line["line-opacity"]),"line-color":new _n(wr.paint_line["line-color"]),"line-translate":new en(wr.paint_line["line-translate"]),"line-translate-anchor":new en(wr.paint_line["line-translate-anchor"]),"line-width":new _n(wr.paint_line["line-width"]),"line-gap-width":new _n(wr.paint_line["line-gap-width"]),"line-offset":new _n(wr.paint_line["line-offset"]),"line-blur":new _n(wr.paint_line["line-blur"]),"line-dasharray":new Ju(wr.paint_line["line-dasharray"]),"line-pattern":new Ju(wr.paint_line["line-pattern"]),"line-gradient":new Gc(wr.paint_line["line-gradient"])})},get layout(){return Jx=Jx||new Fa({"line-cap":new en(wr.layout_line["line-cap"]),"line-join":new _n(wr.layout_line["line-join"]),"line-miter-limit":new en(wr.layout_line["line-miter-limit"]),"line-round-limit":new en(wr.layout_line["line-round-limit"]),"line-sort-key":new _n(wr.layout_line["line-sort-key"])})}};class r0 extends _n{possiblyEvaluate(a,h){return h=new Hi(Math.floor(h.zoom),{now:h.now,fadeDuration:h.fadeDuration,zoomHistory:h.zoomHistory,transition:h.transition}),super.possiblyEvaluate(a,h)}evaluate(a,h,A,C){return h=Ve({},h,{zoom:Math.floor(h.zoom)}),super.evaluate(a,h,A,C)}}let eb;class CL extends Hc{constructor(a,h){super(a,B_,h),this.gradientVersion=0,eb||(eb=new r0(B_.paint.properties["line-width"].specification),eb.useIntegerZoom=!0)}_handleSpecialPaintPropertyUpdate(a){if(a==="line-gradient"){let h=this.gradientExpression();this.stepInterpolant=!!function(A){return A._styleExpression!==void 0}(h)&&h._styleExpression.expression instanceof pe,this.gradientVersion=(this.gradientVersion+1)%Number.MAX_SAFE_INTEGER}}gradientExpression(){return this._transitionablePaint._values["line-gradient"].value.expression}recalculate(a,h){super.recalculate(a,h),this.paint._values["line-floorwidth"]=eb.possiblyEvaluate(this._transitioningPaint._values["line-width"].value,a)}createBucket(a){return new ov(a)}queryRadius(a){let h=a,A=ML(Sd("line-width",this,h),Sd("line-gap-width",this,h)),C=Sd("line-offset",this,h);return A/2+Math.abs(C)+Jg(this.paint.get("line-translate"))}queryIntersectsFeature({queryGeometry:a,feature:h,featureState:A,geometry:C,transform:D,pixelsToTileUnits:O}){let G=Hx(a,this.paint.get("line-translate"),this.paint.get("line-translate-anchor"),-D.bearingInRadians,O),Z=O/2*ML(this.paint.get("line-width").evaluate(h,A),this.paint.get("line-gap-width").evaluate(h,A)),re=this.paint.get("line-offset").evaluate(h,A);return re&&(C=function(ce,de){let we=[];for(let Ee=0;Ee=3){for(let ke=0;ke0?a+2*d:d}let vu=ia([{name:"a_pos_offset",components:4,type:"Int16"},{name:"a_data",components:4,type:"Uint16"},{name:"a_pixeloffset",components:4,type:"Int16"}],4),rA=ia([{name:"a_projected_pos",components:3,type:"Float32"}],4);ia([{name:"a_fade_opacity",components:1,type:"Uint32"}],4);let IL=ia([{name:"a_placed",components:2,type:"Uint8"},{name:"a_shift",components:2,type:"Float32"},{name:"a_box_real",components:2,type:"Int16"}]);ia([{type:"Int16",name:"anchorPointX"},{type:"Int16",name:"anchorPointY"},{type:"Int16",name:"x1"},{type:"Int16",name:"y1"},{type:"Int16",name:"x2"},{type:"Int16",name:"y2"},{type:"Uint32",name:"featureIndex"},{type:"Uint16",name:"sourceLayerIndex"},{type:"Uint16",name:"bucketIndex"}]);let u=ia([{name:"a_pos",components:2,type:"Int16"},{name:"a_anchor_pos",components:2,type:"Int16"},{name:"a_extrude",components:2,type:"Int16"}],4),p=ia([{name:"a_pos",components:2,type:"Float32"},{name:"a_radius",components:1,type:"Float32"},{name:"a_flags",components:2,type:"Int16"}],4);function _(d,a,h){return d.sections.forEach(A=>{A.text=function(C,D,O){let G=D.layout.get("text-transform").evaluate(O,{});return G==="uppercase"?C=C.toLocaleUpperCase():G==="lowercase"&&(C=C.toLocaleLowerCase()),$n.applyArabicShaping&&(C=$n.applyArabicShaping(C)),C}(A.text,a,h)}),d}ia([{name:"triangle",components:3,type:"Uint16"}]),ia([{type:"Int16",name:"anchorX"},{type:"Int16",name:"anchorY"},{type:"Uint16",name:"glyphStartIndex"},{type:"Uint16",name:"numGlyphs"},{type:"Uint32",name:"vertexStartIndex"},{type:"Uint32",name:"lineStartIndex"},{type:"Uint32",name:"lineLength"},{type:"Uint16",name:"segment"},{type:"Uint16",name:"lowerSize"},{type:"Uint16",name:"upperSize"},{type:"Float32",name:"lineOffsetX"},{type:"Float32",name:"lineOffsetY"},{type:"Uint8",name:"writingMode"},{type:"Uint8",name:"placedOrientation"},{type:"Uint8",name:"hidden"},{type:"Uint32",name:"crossTileID"},{type:"Int16",name:"associatedIconIndex"}]),ia([{type:"Int16",name:"anchorX"},{type:"Int16",name:"anchorY"},{type:"Int16",name:"rightJustifiedTextSymbolIndex"},{type:"Int16",name:"centerJustifiedTextSymbolIndex"},{type:"Int16",name:"leftJustifiedTextSymbolIndex"},{type:"Int16",name:"verticalPlacedTextSymbolIndex"},{type:"Int16",name:"placedIconSymbolIndex"},{type:"Int16",name:"verticalPlacedIconSymbolIndex"},{type:"Uint16",name:"key"},{type:"Uint16",name:"textBoxStartIndex"},{type:"Uint16",name:"textBoxEndIndex"},{type:"Uint16",name:"verticalTextBoxStartIndex"},{type:"Uint16",name:"verticalTextBoxEndIndex"},{type:"Uint16",name:"iconBoxStartIndex"},{type:"Uint16",name:"iconBoxEndIndex"},{type:"Uint16",name:"verticalIconBoxStartIndex"},{type:"Uint16",name:"verticalIconBoxEndIndex"},{type:"Uint16",name:"featureIndex"},{type:"Uint16",name:"numHorizontalGlyphVertices"},{type:"Uint16",name:"numVerticalGlyphVertices"},{type:"Uint16",name:"numIconVertices"},{type:"Uint16",name:"numVerticalIconVertices"},{type:"Uint16",name:"useRuntimeCollisionCircles"},{type:"Uint32",name:"crossTileID"},{type:"Float32",name:"textBoxScale"},{type:"Float32",name:"collisionCircleDiameter"},{type:"Uint16",name:"textAnchorOffsetStartIndex"},{type:"Uint16",name:"textAnchorOffsetEndIndex"}]),ia([{type:"Float32",name:"offsetX"}]),ia([{type:"Int16",name:"x"},{type:"Int16",name:"y"},{type:"Int16",name:"tileUnitDistanceFromAnchor"}]),ia([{type:"Uint16",name:"textAnchor"},{type:"Float32",components:2,name:"textOffset"}]);var R=24;let j={"!":"\uFE15","#":"\uFF03",$:"\uFF04","%":"\uFF05","&":"\uFF06","(":"\uFE35",")":"\uFE36","*":"\uFF0A","+":"\uFF0B",",":"\uFE10","-":"\uFE32",".":"\u30FB","/":"\uFF0F",":":"\uFE13",";":"\uFE14","<":"\uFE3F","=":"\uFF1D",">":"\uFE40","?":"\uFE16","@":"\uFF20","[":"\uFE47","\\":"\uFF3C","]":"\uFE48","^":"\uFF3E",_:"\uFE33","`":"\uFF40","{":"\uFE37","|":"\u2015","}":"\uFE38","~":"\uFF5E","\xA2":"\uFFE0","\xA3":"\uFFE1","\xA5":"\uFFE5","\xA6":"\uFFE4","\xAC":"\uFFE2","\xAF":"\uFFE3","\u2013":"\uFE32","\u2014":"\uFE31","\u2018":"\uFE43","\u2019":"\uFE44","\u201C":"\uFE41","\u201D":"\uFE42","\u2026":"\uFE19","\u22EF":"\uFE19","\u2027":"\u30FB","\u20A9":"\uFFE6","\u3001":"\uFE11","\u3002":"\uFE12","\u3008":"\uFE3F","\u3009":"\uFE40","\u300A":"\uFE3D","\u300B":"\uFE3E","\u300C":"\uFE41","\u300D":"\uFE42","\u300E":"\uFE43","\u300F":"\uFE44","\u3010":"\uFE3B","\u3011":"\uFE3C","\u3014":"\uFE39","\u3015":"\uFE3A","\u3016":"\uFE17","\u3017":"\uFE18","\uFF01":"\uFE15","\uFF08":"\uFE35","\uFF09":"\uFE36","\uFF0C":"\uFE10","\uFF0D":"\uFE32","\uFF0E":"\u30FB","\uFF1A":"\uFE13","\uFF1B":"\uFE14","\uFF1C":"\uFE3F","\uFF1E":"\uFE40","\uFF1F":"\uFE16","\uFF3B":"\uFE47","\uFF3D":"\uFE48","\uFF3F":"\uFE33","\uFF5B":"\uFE37","\uFF5C":"\u2015","\uFF5D":"\uFE38","\uFF5F":"\uFE35","\uFF60":"\uFE36","\uFF61":"\uFE12","\uFF62":"\uFE41","\uFF63":"\uFE42"},$={10:!0,32:!0,38:!0,41:!0,43:!0,45:!0,47:!0,173:!0,183:!0,8203:!0,8208:!0,8211:!0,8231:!0},J={40:!0};function le(d,a,h,A,C,D){if("fontStack"in a){let O=h[a.fontStack],G=O&&O[d];return G?G.metrics.advance*a.scale+C:0}{let O=A[a.imageName];return O?O.displaySize[0]*a.scale*R/D+C:0}}function ye(d,a,h,A){let C=Math.pow(d-a,2);return A?dMath.max(a,this.sections[h].scale),0)}getMaxImageSize(a){let h=0,A=0;for(let C=0;CA))}addImageSection(a){let h=a.image?a.image.name:"";if(h.length===0)return void ln("Can't add FormattedSection with an empty image.");let A=this.getNextImageSectionCharCode();A?(this.text+=String.fromCharCode(A),this.sections.push({scale:1,verticalAlign:a.verticalAlign||"bottom",imageName:h}),this.sectionIndex.push(this.sections.length-1)):ln("Reached maximum number of images 6401")}getNextImageSectionCharCode(){return this.imageSectionID?this.imageSectionID>=63743?null:++this.imageSectionID:(this.imageSectionID=57344,this.imageSectionID)}determineLineBreaks(a,h,A,C,D){let O=[],G=this.determineAverageLineWidth(a,h,A,C,D),Z=this.hasZeroWidthSpaces(),re=0,ce=0,de=this.text[Symbol.iterator](),we=de.next(),Ee=this.text[Symbol.iterator]();Ee.next();let De=Ee.next(),ke=this.text[Symbol.iterator]();ke.next(),ke.next();let $e=ke.next();for(;!we.done;){let st=this.getSection(ce),kt=we.value.codePointAt(0);if(Wr(kt)||(re+=le(kt,st,A,C,a,D)),!De.done){let pt=kr(kt),_t=De.value.codePointAt(0);($[kt]||pt||"imageName"in st||!$e.done&&J[_t])&&O.push(Le(ce+1,re,G,O,Me(kt,_t,pt&&Z),!1))}ce++,we=de.next(),De=Ee.next(),$e=ke.next()}return ze(Le(this.length(),re,G,O,0,!0))}determineAverageLineWidth(a,h,A,C,D){let O=0,G=0;for(let Z of this.text){let re=this.getSection(G);O+=le(Z.codePointAt(0),re,A,C,a,D),G++}return O/Math.max(1,Math.ceil(O/h))}}let lt=4294967296,It=1/lt,Dt=typeof TextDecoder>"u"?null:new TextDecoder("utf-8");class tr{constructor(a=new Uint8Array(16)){this.buf=ArrayBuffer.isView(a)?a:new Uint8Array(a),this.dataView=new DataView(this.buf.buffer),this.pos=0,this.type=0,this.length=this.buf.length}readFields(a,h,A=this.length){for(;this.pos>3,O=this.pos;this.type=7&C,a(D,h,this),this.pos===O&&this.skip(C)}return h}readMessage(a,h){return this.readFields(a,h,this.readVarint()+this.pos)}readFixed32(){let a=this.dataView.getUint32(this.pos,!0);return this.pos+=4,a}readSFixed32(){let a=this.dataView.getInt32(this.pos,!0);return this.pos+=4,a}readFixed64(){let a=this.dataView.getUint32(this.pos,!0)+this.dataView.getUint32(this.pos+4,!0)*lt;return this.pos+=8,a}readSFixed64(){let a=this.dataView.getUint32(this.pos,!0)+this.dataView.getInt32(this.pos+4,!0)*lt;return this.pos+=8,a}readFloat(){let a=this.dataView.getFloat32(this.pos,!0);return this.pos+=4,a}readDouble(){let a=this.dataView.getFloat64(this.pos,!0);return this.pos+=8,a}readVarint(a){let h=this.buf,A,C;return C=h[this.pos++],A=127&C,C<128?A:(C=h[this.pos++],A|=(127&C)<<7,C<128?A:(C=h[this.pos++],A|=(127&C)<<14,C<128?A:(C=h[this.pos++],A|=(127&C)<<21,C<128?A:(C=h[this.pos],A|=(15&C)<<28,function(D,O,G){let Z=G.buf,re,ce;if(ce=Z[G.pos++],re=(112&ce)>>4,ce<128||(ce=Z[G.pos++],re|=(127&ce)<<3,ce<128)||(ce=Z[G.pos++],re|=(127&ce)<<10,ce<128)||(ce=Z[G.pos++],re|=(127&ce)<<17,ce<128)||(ce=Z[G.pos++],re|=(127&ce)<<24,ce<128)||(ce=Z[G.pos++],re|=(1&ce)<<31,ce<128))return qt(D,re,O);throw new Error("Expected varint not more than 10 bytes")}(A,a,this)))))}readVarint64(){return this.readVarint(!0)}readSVarint(){let a=this.readVarint();return a%2==1?(a+1)/-2:a/2}readBoolean(){return Boolean(this.readVarint())}readString(){let a=this.readVarint()+this.pos,h=this.pos;return this.pos=a,a-h>=12&&Dt?Dt.decode(this.buf.subarray(h,a)):function(A,C,D){let O="",G=C;for(;G239?4:Z>223?3:Z>191?2:1;if(G+Ee>D)break;Ee===1?Z<128&&(we=Z):Ee===2?(re=A[G+1],(192&re)==128&&(we=(31&Z)<<6|63&re,we<=127&&(we=null))):Ee===3?(re=A[G+1],ce=A[G+2],(192&re)==128&&(192&ce)==128&&(we=(15&Z)<<12|(63&re)<<6|63&ce,(we<=2047||we>=55296&&we<=57343)&&(we=null))):Ee===4&&(re=A[G+1],ce=A[G+2],de=A[G+3],(192&re)==128&&(192&ce)==128&&(192&de)==128&&(we=(15&Z)<<18|(63&re)<<12|(63&ce)<<6|63&de,(we<=65535||we>=1114112)&&(we=null))),we===null?(we=65533,Ee=1):we>65535&&(we-=65536,O+=String.fromCharCode(we>>>10&1023|55296),we=56320|1023&we),O+=String.fromCharCode(we),G+=Ee}return O}(this.buf,h,a)}readBytes(){let a=this.readVarint()+this.pos,h=this.buf.subarray(this.pos,a);return this.pos=a,h}readPackedVarint(a=[],h){let A=this.readPackedEnd();for(;this.pos127;);else if(h===2)this.pos=this.readVarint()+this.pos;else if(h===5)this.pos+=4;else{if(h!==1)throw new Error(`Unimplemented type: ${h}`);this.pos+=8}}writeTag(a,h){this.writeVarint(a<<3|h)}realloc(a){let h=this.length||16;for(;h268435455||a<0?function(h,A){let C,D;if(h>=0?(C=h%4294967296|0,D=h/4294967296|0):(C=~(-h%4294967296),D=~(-h/4294967296),4294967295^C?C=C+1|0:(C=0,D=D+1|0)),h>=18446744073709552e3||h<-18446744073709552e3)throw new Error("Given varint doesn't fit into 10 bytes");A.realloc(10),function(O,G,Z){Z.buf[Z.pos++]=127&O|128,O>>>=7,Z.buf[Z.pos++]=127&O|128,O>>>=7,Z.buf[Z.pos++]=127&O|128,O>>>=7,Z.buf[Z.pos++]=127&O|128,Z.buf[Z.pos]=127&(O>>>=7)}(C,0,A),function(O,G){let Z=(7&O)<<4;G.buf[G.pos++]|=Z|((O>>>=3)?128:0),O&&(G.buf[G.pos++]=127&O|((O>>>=7)?128:0),O&&(G.buf[G.pos++]=127&O|((O>>>=7)?128:0),O&&(G.buf[G.pos++]=127&O|((O>>>=7)?128:0),O&&(G.buf[G.pos++]=127&O|((O>>>=7)?128:0),O&&(G.buf[G.pos++]=127&O)))))}(D,A)}(a,this):(this.realloc(4),this.buf[this.pos++]=127&a|(a>127?128:0),a<=127||(this.buf[this.pos++]=127&(a>>>=7)|(a>127?128:0),a<=127||(this.buf[this.pos++]=127&(a>>>=7)|(a>127?128:0),a<=127||(this.buf[this.pos++]=a>>>7&127))))}writeSVarint(a){this.writeVarint(a<0?2*-a-1:2*a)}writeBoolean(a){this.writeVarint(+a)}writeString(a){a=String(a),this.realloc(4*a.length),this.pos++;let h=this.pos;this.pos=function(C,D,O){for(let G,Z,re=0;re55295&&G<57344){if(!Z){G>56319||re+1===D.length?(C[O++]=239,C[O++]=191,C[O++]=189):Z=G;continue}if(G<56320){C[O++]=239,C[O++]=191,C[O++]=189,Z=G;continue}G=Z-55296<<10|G-56320|65536,Z=null}else Z&&(C[O++]=239,C[O++]=191,C[O++]=189,Z=null);G<128?C[O++]=G:(G<2048?C[O++]=G>>6|192:(G<65536?C[O++]=G>>12|224:(C[O++]=G>>18|240,C[O++]=G>>12&63|128),C[O++]=G>>6&63|128),C[O++]=63&G|128)}return O}(this.buf,a,this.pos);let A=this.pos-h;A>=128&&ur(h,A,this),this.pos=h-1,this.writeVarint(A),this.pos+=A}writeFloat(a){this.realloc(4),this.dataView.setFloat32(this.pos,a,!0),this.pos+=4}writeDouble(a){this.realloc(8),this.dataView.setFloat64(this.pos,a,!0),this.pos+=8}writeBytes(a){let h=a.length;this.writeVarint(h),this.realloc(h);for(let A=0;A=128&&ur(A,C,this),this.pos=A-1,this.writeVarint(C),this.pos+=C}writeMessage(a,h,A){this.writeTag(a,2),this.writeRawMessage(h,A)}writePackedVarint(a,h){h.length&&this.writeMessage(a,Tr,h)}writePackedSVarint(a,h){h.length&&this.writeMessage(a,Nr,h)}writePackedBoolean(a,h){h.length&&this.writeMessage(a,Ei,h)}writePackedFloat(a,h){h.length&&this.writeMessage(a,Vr,h)}writePackedDouble(a,h){h.length&&this.writeMessage(a,ri,h)}writePackedFixed32(a,h){h.length&&this.writeMessage(a,Ui,h)}writePackedSFixed32(a,h){h.length&&this.writeMessage(a,Cn,h)}writePackedFixed64(a,h){h.length&&this.writeMessage(a,Ki,h)}writePackedSFixed64(a,h){h.length&&this.writeMessage(a,Bn,h)}writeBytesField(a,h){this.writeTag(a,2),this.writeBytes(h)}writeFixed32Field(a,h){this.writeTag(a,5),this.writeFixed32(h)}writeSFixed32Field(a,h){this.writeTag(a,5),this.writeSFixed32(h)}writeFixed64Field(a,h){this.writeTag(a,1),this.writeFixed64(h)}writeSFixed64Field(a,h){this.writeTag(a,1),this.writeSFixed64(h)}writeVarintField(a,h){this.writeTag(a,0),this.writeVarint(h)}writeSVarintField(a,h){this.writeTag(a,0),this.writeSVarint(h)}writeStringField(a,h){this.writeTag(a,2),this.writeString(h)}writeFloatField(a,h){this.writeTag(a,5),this.writeFloat(h)}writeDoubleField(a,h){this.writeTag(a,1),this.writeDouble(h)}writeBooleanField(a,h){this.writeVarintField(a,+h)}}function qt(d,a,h){return h?4294967296*a+(d>>>0):4294967296*(a>>>0)+(d>>>0)}function ur(d,a,h){let A=a<=16383?1:a<=2097151?2:a<=268435455?3:Math.floor(Math.log(a)/(7*Math.LN2));h.realloc(A);for(let C=h.pos-1;C>=d;C--)h.buf[C+A]=h.buf[C]}function Tr(d,a){for(let h=0;hG.h-O.h);let A=[{x:0,y:0,w:Math.max(Math.ceil(Math.sqrt(a/.95)),h),h:1/0}],C=0,D=0;for(let O of d)for(let G=A.length-1;G>=0;G--){let Z=A[G];if(!(O.w>Z.w||O.h>Z.h)){if(O.x=Z.x,O.y=Z.y,D=Math.max(D,O.y+O.h),C=Math.max(C,O.x+O.w),O.w===Z.w&&O.h===Z.h){let re=A.pop();re&&Gke.toCodeUnitIndex(Mr));let Qt=kt(ke.toString(),st);for(let Mr of Qt){let oi=[...Mr].map(()=>0);$e.push(new Ye(Mr,ke.sections,oi))}}else if(pt){$e=[],st=st.map(Kr=>ke.toCodeUnitIndex(Kr));let Qt=0,Mr=[];for(let Kr of ke.text)Mr.push(...Array(Kr.length).fill(ke.sectionIndex[Qt])),Qt++;let oi=pt(ke.text,Mr,st);for(let Kr of oi){let gi=[],ji="";for(let vi of Kr[0])gi.push(Kr[1][ji.length]),ji+=vi;$e.push(new Ye(Kr[0],ke.sections,gi))}}else $e=function(Qt,Mr){let oi=[],Kr=0;for(let gi of Mr)oi.push(Qt.substring(Kr,gi)),Kr=gi;return Krre){let ce=Math.ceil(D/re);C*=ce/O,O=ce}return{x1:A,y1:C,x2:A+D,y2:C+O}}function su(d,a,h,A,C,D){let O=d.image,G;if(O.content){let $e=O.content,st=O.pixelRatio||1;G=[$e[0]/st,$e[1]/st,O.displaySize[0]-$e[2]/st,O.displaySize[1]-$e[3]/st]}let Z=a.left*D,re=a.right*D,ce,de,we,Ee;h==="width"||h==="both"?(Ee=C[0]+Z-A[3],de=C[0]+re+A[1]):(Ee=C[0]+(Z+re-O.displaySize[0])/2,de=Ee+O.displaySize[0]);let De=a.top*D,ke=a.bottom*D;return h==="height"||h==="both"?(ce=C[1]+De-A[0],we=C[1]+ke+A[2]):(ce=C[1]+(De+ke-O.displaySize[1])/2,we=ce+O.displaySize[1]),{image:O,top:ce,right:de,bottom:we,left:Ee,collisionPadding:G}}ut("ImagePosition",Jo),ut("ImageAtlas",xo),n.aA=void 0,(es=n.aA||(n.aA={}))[es.none=0]="none",es[es.horizontal=1]="horizontal",es[es.vertical=2]="vertical",es[es.horizontalOnly=3]="horizontalOnly";let nc=128,ka=32640;function au(d,a){let{expression:h}=a;if(h.kind==="constant")return{kind:"constant",layoutSize:h.evaluate(new Hi(d+1))};if(h.kind==="source")return{kind:"source"};{let{zoomStops:A,interpolationType:C}=h,D=0;for(;DO.id),this.index=a.index,this.pixelRatio=a.pixelRatio,this.sourceLayerIndex=a.sourceLayerIndex,this.hasDependencies=!1,this.hasRTLText=!1,this.sortKeyRanges=[],this.collisionCircleArray=[];let h=this.layers[0]._unevaluatedLayout._values;this.textSizeData=au(this.zoom,h["text-size"]),this.iconSizeData=au(this.zoom,h["icon-size"]);let A=this.layers[0].layout,C=A.get("symbol-sort-key"),D=A.get("symbol-z-order");this.canOverlap=hs(A,"text-overlap","text-allow-overlap")!=="never"||hs(A,"icon-overlap","icon-allow-overlap")!=="never"||A.get("text-ignore-placement")||A.get("icon-ignore-placement"),this.sortFeaturesByKey=D!=="viewport-y"&&!C.isConstant(),this.sortFeaturesByY=(D==="viewport-y"||D==="auto"&&!this.sortFeaturesByKey)&&this.canOverlap,A.get("symbol-placement")==="point"&&(this.writingModes=A.get("text-writing-mode").map(O=>n.aA[O])),this.stateDependentLayerIds=this.layers.filter(O=>O.isStateDependent()).map(O=>O.id),this.sourceID=a.sourceID}createArrays(){this.text=new xu(new er(this.layers,this.zoom,a=>/^text/.test(a))),this.icon=new xu(new er(this.layers,this.zoom,a=>/^icon/.test(a))),this.glyphOffsetArray=new W,this.lineVertexArray=new te,this.symbolInstances=new V,this.textAnchorOffsets=new fe}calculateGlyphDependencies(a,h,A,C,D){for(let O of a)if(h[O.codePointAt(0)]=!0,(A||C)&&D){let G=j[O];G&&(h[G.codePointAt(0)]=!0)}}populate(a,h,A){let C=this.layers[0],D=C.layout,O=D.get("text-font"),G=D.get("text-field"),Z=D.get("icon-image"),re=(G.value.kind!=="constant"||G.value.value instanceof Ql&&!G.value.value.isEmpty()||G.value.value.toString().length>0)&&(O.value.kind!=="constant"||O.value.value.length>0),ce=Z.value.kind!=="constant"||!!Z.value.value||Object.keys(Z.parameters).length>0,de=D.get("symbol-sort-key");if(this.features=[],!re&&!ce)return;let we=h.iconDependencies,Ee=h.glyphDependencies,De=h.availableImages,ke=new Hi(this.zoom);for(let{feature:$e,id:st,index:kt,sourceLayerIndex:pt}of a){let _t=C._featureFilter.needGeometry,Gt=Pi($e,_t);if(!C._featureFilter.filter(ke,Gt,A))continue;let Qt,Mr;if(_t||(Gt.geometry=or($e)),re){let Kr=C.getValueAndResolveTokens("text-field",Gt,A,De),gi=Ql.factory(Kr),ji=this.hasRTLText=this.hasRTLText||nf(gi);(!ji||$n.getRTLTextPluginStatus()==="unavailable"||ji&&$n.isParsed())&&(Qt=_(gi,C,Gt))}if(ce){let Kr=C.getValueAndResolveTokens("icon-image",Gt,A,De);Mr=Kr instanceof Ol?Kr:Ol.fromString(Kr)}if(!Qt&&!Mr)continue;let oi=this.sortFeaturesByKey?de.evaluate(Gt,{},A):void 0;if(this.features.push({id:st,text:Qt,icon:Mr,index:kt,sourceLayerIndex:pt,geometry:Gt.geometry,properties:$e.properties,type:Zx.types[$e.type],sortKey:oi}),Mr&&(we[Mr.name]=!0),Qt){let Kr=O.evaluate(Gt,{},A).join(","),gi=D.get("text-rotation-alignment")!=="viewport"&&D.get("symbol-placement")!=="point";this.allowVerticalPlacement=this.writingModes&&this.writingModes.indexOf(n.aA.vertical)>=0;for(let ji of Qt.sections)if(ji.image)we[ji.image.name]=!0;else{let vi=ai(Qt.toString()),Qr=ji.fontStack||Kr,jr=Ee[Qr]=Ee[Qr]||{};this.calculateGlyphDependencies(ji.text,jr,gi,this.allowVerticalPlacement,vi)}}}D.get("symbol-placement")==="line"&&(this.features=function($e){let st={},kt={},pt=[],_t=0;function Gt(Kr){pt.push($e[Kr]),_t++}function Qt(Kr,gi,ji){let vi=kt[Kr];return delete kt[Kr],kt[gi]=vi,pt[vi].geometry[0].pop(),pt[vi].geometry[0]=pt[vi].geometry[0].concat(ji[0]),vi}function Mr(Kr,gi,ji){let vi=st[gi];return delete st[gi],st[Kr]=vi,pt[vi].geometry[0].shift(),pt[vi].geometry[0]=ji[0].concat(pt[vi].geometry[0]),vi}function oi(Kr,gi,ji){let vi=ji?gi[0][gi[0].length-1]:gi[0][0];return`${Kr}:${vi.x}:${vi.y}`}for(let Kr=0;Kr<$e.length;Kr++){let gi=$e[Kr],ji=gi.geometry,vi=gi.text?gi.text.toString():null;if(!vi){Gt(Kr);continue}let Qr=oi(vi,ji),jr=oi(vi,ji,!0);if(Qr in kt&&jr in st&&kt[Qr]!==st[jr]){let dn=Mr(Qr,jr,ji),cn=Qt(Qr,jr,pt[dn].geometry);delete st[Qr],delete kt[jr],kt[oi(vi,pt[cn].geometry,!0)]=cn,pt[dn].geometry=null}else Qr in kt?Qt(Qr,jr,ji):jr in st?Mr(Qr,jr,ji):(Gt(Kr),st[Qr]=_t-1,kt[jr]=_t-1)}return pt.filter(Kr=>Kr.geometry)}(this.features)),this.sortFeaturesByKey&&this.features.sort(($e,st)=>$e.sortKey-st.sortKey)}update(a,h,A){this.stateDependentLayers.length&&(this.text.programConfigurations.updatePaintArrays(a,h,this.layers,{imagePositions:A}),this.icon.programConfigurations.updatePaintArrays(a,h,this.layers,{imagePositions:A}))}isEmpty(){return this.symbolInstances.length===0&&!this.hasRTLText}uploadPending(){return!this.uploaded||this.text.programConfigurations.needsUpload||this.icon.programConfigurations.needsUpload}upload(a){!this.uploaded&&this.hasDebugData()&&(this.textCollisionBox.upload(a),this.iconCollisionBox.upload(a)),this.text.upload(a,this.sortFeaturesByY,!this.uploaded,this.text.programConfigurations.needsUpload),this.icon.upload(a,this.sortFeaturesByY,!this.uploaded,this.icon.programConfigurations.needsUpload),this.uploaded=!0}destroyDebugData(){this.textCollisionBox.destroy(),this.iconCollisionBox.destroy()}destroy(){this.text.destroy(),this.icon.destroy(),this.hasDebugData()&&this.destroyDebugData()}addToLineVertexArray(a,h){let A=this.lineVertexArray.length;if(a.segment!==void 0){let C=a.dist(h[a.segment+1]),D=a.dist(h[a.segment]),O={};for(let G=a.segment+1;G=0;G--)O[G]={x:h[G].x,y:h[G].y,tileUnitDistanceFromAnchor:D},G>0&&(D+=h[G-1].dist(h[G]));for(let G=0;G0}hasIconData(){return this.icon.segments.get().length>0}hasDebugData(){return this.textCollisionBox&&this.iconCollisionBox}hasTextCollisionBoxData(){return this.hasDebugData()&&this.textCollisionBox.segments.get().length>0}hasIconCollisionBoxData(){return this.hasDebugData()&&this.iconCollisionBox.segments.get().length>0}addIndicesForPlacedSymbol(a,h){let A=a.placedSymbolArray.get(h),C=A.vertexStartIndex+4*A.numGlyphs;for(let D=A.vertexStartIndex;DC[G]-C[Z]||D[Z]-D[G]),O}addToSortKeyRanges(a,h){let A=this.sortKeyRanges[this.sortKeyRanges.length-1];A&&A.sortKey===h?A.symbolInstanceEnd=a+1:this.sortKeyRanges.push({sortKey:h,symbolInstanceStart:a,symbolInstanceEnd:a+1})}sortFeatures(a){if(this.sortFeaturesByY&&this.sortedAngle!==a&&!(this.text.segments.get().length>1||this.icon.segments.get().length>1)){this.symbolInstanceIndexes=this.getSortedSymbolIndexes(a),this.sortedAngle=a,this.text.indexArray.clear(),this.icon.indexArray.clear(),this.featureSortOrder=[];for(let h of this.symbolInstanceIndexes){let A=this.symbolInstances.get(h);this.featureSortOrder.push(A.featureIndex),[A.rightJustifiedTextSymbolIndex,A.centerJustifiedTextSymbolIndex,A.leftJustifiedTextSymbolIndex].forEach((C,D,O)=>{C>=0&&O.indexOf(C)===D&&this.addIndicesForPlacedSymbol(this.text,C)}),A.verticalPlacedTextSymbolIndex>=0&&this.addIndicesForPlacedSymbol(this.text,A.verticalPlacedTextSymbolIndex),A.placedIconSymbolIndex>=0&&this.addIndicesForPlacedSymbol(this.icon,A.placedIconSymbolIndex),A.verticalPlacedIconSymbolIndex>=0&&this.addIndicesForPlacedSymbol(this.icon,A.verticalPlacedIconSymbolIndex)}this.text.indexBuffer&&this.text.indexBuffer.updateData(this.text.indexArray),this.icon.indexBuffer&&this.icon.indexBuffer.updateData(this.icon.indexArray)}}}let ku,$s;ut("SymbolBucket",dl,{omit:["layers","collisionBoxArray","features","compareText"]}),dl.MAX_GLYPHS=65535,dl.addDynamicAttributes=Bs;var Dp={get paint(){return $s=$s||new Fa({"icon-opacity":new _n(wr.paint_symbol["icon-opacity"]),"icon-color":new _n(wr.paint_symbol["icon-color"]),"icon-halo-color":new _n(wr.paint_symbol["icon-halo-color"]),"icon-halo-width":new _n(wr.paint_symbol["icon-halo-width"]),"icon-halo-blur":new _n(wr.paint_symbol["icon-halo-blur"]),"icon-translate":new en(wr.paint_symbol["icon-translate"]),"icon-translate-anchor":new en(wr.paint_symbol["icon-translate-anchor"]),"text-opacity":new _n(wr.paint_symbol["text-opacity"]),"text-color":new _n(wr.paint_symbol["text-color"],{runtimeType:Di,getOverride:d=>d.textColor,hasOverride:d=>!!d.textColor}),"text-halo-color":new _n(wr.paint_symbol["text-halo-color"]),"text-halo-width":new _n(wr.paint_symbol["text-halo-width"]),"text-halo-blur":new _n(wr.paint_symbol["text-halo-blur"]),"text-translate":new en(wr.paint_symbol["text-translate"]),"text-translate-anchor":new en(wr.paint_symbol["text-translate-anchor"])})},get layout(){return ku=ku||new Fa({"symbol-placement":new en(wr.layout_symbol["symbol-placement"]),"symbol-spacing":new en(wr.layout_symbol["symbol-spacing"]),"symbol-avoid-edges":new en(wr.layout_symbol["symbol-avoid-edges"]),"symbol-sort-key":new _n(wr.layout_symbol["symbol-sort-key"]),"symbol-z-order":new en(wr.layout_symbol["symbol-z-order"]),"icon-allow-overlap":new en(wr.layout_symbol["icon-allow-overlap"]),"icon-overlap":new en(wr.layout_symbol["icon-overlap"]),"icon-ignore-placement":new en(wr.layout_symbol["icon-ignore-placement"]),"icon-optional":new en(wr.layout_symbol["icon-optional"]),"icon-rotation-alignment":new en(wr.layout_symbol["icon-rotation-alignment"]),"icon-size":new _n(wr.layout_symbol["icon-size"]),"icon-text-fit":new en(wr.layout_symbol["icon-text-fit"]),"icon-text-fit-padding":new en(wr.layout_symbol["icon-text-fit-padding"]),"icon-image":new _n(wr.layout_symbol["icon-image"]),"icon-rotate":new _n(wr.layout_symbol["icon-rotate"]),"icon-padding":new _n(wr.layout_symbol["icon-padding"]),"icon-keep-upright":new en(wr.layout_symbol["icon-keep-upright"]),"icon-offset":new _n(wr.layout_symbol["icon-offset"]),"icon-anchor":new _n(wr.layout_symbol["icon-anchor"]),"icon-pitch-alignment":new en(wr.layout_symbol["icon-pitch-alignment"]),"text-pitch-alignment":new en(wr.layout_symbol["text-pitch-alignment"]),"text-rotation-alignment":new en(wr.layout_symbol["text-rotation-alignment"]),"text-field":new _n(wr.layout_symbol["text-field"]),"text-font":new _n(wr.layout_symbol["text-font"]),"text-size":new _n(wr.layout_symbol["text-size"]),"text-max-width":new _n(wr.layout_symbol["text-max-width"]),"text-line-height":new en(wr.layout_symbol["text-line-height"]),"text-letter-spacing":new _n(wr.layout_symbol["text-letter-spacing"]),"text-justify":new _n(wr.layout_symbol["text-justify"]),"text-radial-offset":new _n(wr.layout_symbol["text-radial-offset"]),"text-variable-anchor":new en(wr.layout_symbol["text-variable-anchor"]),"text-variable-anchor-offset":new _n(wr.layout_symbol["text-variable-anchor-offset"]),"text-anchor":new _n(wr.layout_symbol["text-anchor"]),"text-max-angle":new en(wr.layout_symbol["text-max-angle"]),"text-writing-mode":new en(wr.layout_symbol["text-writing-mode"]),"text-rotate":new _n(wr.layout_symbol["text-rotate"]),"text-padding":new en(wr.layout_symbol["text-padding"]),"text-keep-upright":new en(wr.layout_symbol["text-keep-upright"]),"text-transform":new _n(wr.layout_symbol["text-transform"]),"text-offset":new _n(wr.layout_symbol["text-offset"]),"text-allow-overlap":new en(wr.layout_symbol["text-allow-overlap"]),"text-overlap":new en(wr.layout_symbol["text-overlap"]),"text-ignore-placement":new en(wr.layout_symbol["text-ignore-placement"]),"text-optional":new en(wr.layout_symbol["text-optional"])})}};class ed{constructor(a){if(a.property.overrides===void 0)throw new Error("overrides must be provided to instantiate FormatSectionOverride class");this.type=a.property.overrides?a.property.overrides.runtimeType:si,this.defaultValue=a}evaluate(a){if(a.formattedSection){let h=this.defaultValue.property.overrides;if(h&&h.hasOverride(a.formattedSection))return h.getOverride(a.formattedSection)}return a.feature&&a.featureState?this.defaultValue.evaluate(a.feature,a.featureState):this.defaultValue.property.specification.default}eachChild(a){this.defaultValue.isConstant()||a(this.defaultValue.value._styleExpression.expression)}outputDefined(){return!1}serialize(){return null}}ut("FormatSectionOverride",ed,{omit:["defaultValue"]});class Qd extends Hc{constructor(a,h){super(a,Dp,h)}recalculate(a,h){if(super.recalculate(a,h),this.layout.get("icon-rotation-alignment")==="auto"&&(this.layout._values["icon-rotation-alignment"]=this.layout.get("symbol-placement")!=="point"?"map":"viewport"),this.layout.get("text-rotation-alignment")==="auto"&&(this.layout._values["text-rotation-alignment"]=this.layout.get("symbol-placement")!=="point"?"map":"viewport"),this.layout.get("text-pitch-alignment")==="auto"&&(this.layout._values["text-pitch-alignment"]=this.layout.get("text-rotation-alignment")==="map"?"map":"viewport"),this.layout.get("icon-pitch-alignment")==="auto"&&(this.layout._values["icon-pitch-alignment"]=this.layout.get("icon-rotation-alignment")),this.layout.get("symbol-placement")==="point"){let A=this.layout.get("text-writing-mode");if(A){let C=[];for(let D of A)C.indexOf(D)<0&&C.push(D);this.layout._values["text-writing-mode"]=C}else this.layout._values["text-writing-mode"]=["horizontal"]}this._setPaintOverrides()}getValueAndResolveTokens(a,h,A,C){let D=this.layout.get(a).evaluate(h,{},A,C),O=this._unevaluatedLayout._values[a];return O.isDataDriven()||yh(O.value)||!D?D:function(G,Z){return Z.replace(/{([^{}]+)}/g,(re,ce)=>G&&ce in G?String(G[ce]):"")}(h.properties,D)}createBucket(a){return new dl(a)}queryRadius(){return 0}queryIntersectsFeature(){throw new Error("Should take a different path in FeatureIndex")}_setPaintOverrides(){for(let a of Dp.paint.overridableProperties){if(!Qd.hasPaintOverride(this.layout,a))continue;let h=this.paint.get(a),A=new ed(h),C=new Ic(A,h.property.specification),D=null;D=h.value.kind==="constant"||h.value.kind==="source"?new $f("source",C):new Yu("composite",C,h.value.zoomStops),this.paint._values[a]=new Vs(h.property,D,h.parameters)}}_handleOverridablePaintPropertyUpdate(a,h,A){return!(!this.layout||h.isDataDriven()||A.isDataDriven())&&Qd.hasPaintOverride(this.layout,a)}static hasPaintOverride(a,h){let A=a.get("text-field"),C=Dp.paint.properties[h],D=!1,O=G=>{for(let Z of G)if(C.overrides&&C.overrides.hasOverride(Z))return void(D=!0)};if(A.value.kind==="constant"&&A.value.value instanceof Ql)O(A.value.value.sections);else if(A.value.kind==="source"||A.value.kind==="composite"){let G=re=>{D||(re instanceof Ws&&La(re.value)===po?O(re.value.sections):re instanceof Hr?O(re.sections):re.eachChild(G))},Z=A.value;Z._styleExpression&&G(Z._styleExpression.expression)}return D}}let oc;var lg={get paint(){return oc=oc||new Fa({"background-color":new en(wr.paint_background["background-color"]),"background-pattern":new jc(wr.paint_background["background-pattern"]),"background-opacity":new en(wr.paint_background["background-opacity"])})}};class Op extends Hc{constructor(a,h){super(a,lg,h)}}class sv extends Hc{constructor(a,h){super(a,{},h),this.onAdd=A=>{this.implementation.onAdd&&this.implementation.onAdd(A,A.painter.context.gl)},this.onRemove=A=>{this.implementation.onRemove&&this.implementation.onRemove(A,A.painter.context.gl)},this.implementation=a}is3D(){return this.implementation.renderingMode==="3d"}hasOffscreenPass(){return this.implementation.prerender!==void 0}recalculate(){}updateTransitions(){}hasTransition(){return!1}serialize(){throw new Error("Custom layers cannot be serialized")}}class L_{constructor(a){this._methodToThrottle=a,this._triggered=!1,typeof MessageChannel<"u"&&(this._channel=new MessageChannel,this._channel.port2.onmessage=()=>{this._triggered=!1,this._methodToThrottle()})}trigger(){this._triggered||(this._triggered=!0,this._channel?this._channel.port1.postMessage(!0):setTimeout(()=>{this._triggered=!1,this._methodToThrottle()},0))}remove(){delete this._channel,this._methodToThrottle=()=>{}}}let av={once:!0},_m=63710088e-1;class Jd{constructor(a,h){if(isNaN(a)||isNaN(h))throw new Error(`Invalid LngLat object: (${a}, ${h})`);if(this.lng=+a,this.lat=+h,this.lat>90||this.lat<-90)throw new Error("Invalid LngLat latitude value: must be between -90 and 90")}wrap(){return new Jd(Hn(this.lng,-180,180),this.lat)}toArray(){return[this.lng,this.lat]}toString(){return`LngLat(${this.lng}, ${this.lat})`}distanceTo(a){let h=Math.PI/180,A=this.lat*h,C=a.lat*h,D=Math.sin(A)*Math.sin(C)+Math.cos(A)*Math.cos(C)*Math.cos((a.lng-this.lng)*h);return _m*Math.acos(Math.min(D,1))}static convert(a){if(a instanceof Jd)return a;if(Array.isArray(a)&&(a.length===2||a.length===3))return new Jd(Number(a[0]),Number(a[1]));if(!Array.isArray(a)&&typeof a=="object"&&a!==null)return new Jd(Number("lng"in a?a.lng:a.lon),Number(a.lat));throw new Error("`LngLatLike` argument must be specified as a LngLat instance, an object {lng: , lat: }, an object {lon: , lat: }, or an array of [, ]")}}let tb=2*Math.PI*_m;function D_(d){return tb*Math.cos(d*Math.PI/180)}function lv(d){return(180+d)/360}function rb(d){return(180-180/Math.PI*Math.log(Math.tan(Math.PI/4+d*Math.PI/360)))/360}function ib(d,a){return d/D_(a)}function cg(d){return 360*d-180}function iA(d){return 360/Math.PI*Math.atan(Math.exp((180-360*d)*Math.PI/180))-90}function O_(d,a){return d*D_(iA(a))}class Fp{constructor(a,h,A=0){this.x=+a,this.y=+h,this.z=+A}static fromLngLat(a,h=0){let A=Jd.convert(a);return new Fp(lv(A.lng),rb(A.lat),ib(h,A.lat))}toLngLat(){return new Jd(cg(this.x),iA(this.y))}toAltitude(){return O_(this.z,this.y)}meterInMercatorCoordinateUnits(){return 1/tb*(a=iA(this.y),1/Math.cos(a*Math.PI/180));var a}}function F_(d,a,h){var A=2*Math.PI*6378137/256/Math.pow(2,h);return[d*A-2*Math.PI*6378137/2,a*A-2*Math.PI*6378137/2]}class cv{constructor(a,h,A){if(!function(C,D,O){return!(C<0||C>25||O<0||O>=Math.pow(2,C)||D<0||D>=Math.pow(2,C))}(a,h,A))throw new Error(`x=${h}, y=${A}, z=${a} outside of bounds. 0<=x<${Math.pow(2,a)}, 0<=y<${Math.pow(2,a)} 0<=z<=25 `);this.z=a,this.x=h,this.y=A,this.key=ug(0,a,a,h,A)}equals(a){return this.z===a.z&&this.x===a.x&&this.y===a.y}url(a,h,A){let C=function(O,G,Z){var re=F_(256*O,256*(G=Math.pow(2,Z)-G-1),Z),ce=F_(256*(O+1),256*(G+1),Z);return re[0]+","+re[1]+","+ce[0]+","+ce[1]}(this.x,this.y,this.z),D=function(O,G,Z){let re,ce="";for(let de=O;de>0;de--)re=1<1?"@2x":"").replace(/{quadkey}/g,D).replace(/{bbox-epsg-3857}/g,C)}isChildOf(a){let h=this.z-a.z;return h>0&&a.x===this.x>>h&&a.y===this.y>>h}getTilePoint(a){let h=Math.pow(2,this.z);return new s((a.x*h-this.x)*hr,(a.y*h-this.y)*hr)}toString(){return`${this.z}/${this.x}/${this.y}`}}class nb{constructor(a,h){this.wrap=a,this.canonical=h,this.key=ug(a,h.z,h.z,h.x,h.y)}}class of{constructor(a,h,A,C,D){if(this.terrainRttPosMatrix32f=null,a= z; overscaledZ = ${a}; z = ${A}`);this.overscaledZ=a,this.wrap=h,this.canonical=new cv(A,+C,+D),this.key=ug(h,a,A,C,D)}clone(){return new of(this.overscaledZ,this.wrap,this.canonical.z,this.canonical.x,this.canonical.y)}equals(a){return this.overscaledZ===a.overscaledZ&&this.wrap===a.wrap&&this.canonical.equals(a.canonical)}scaledTo(a){if(a>this.overscaledZ)throw new Error(`targetZ > this.overscaledZ; targetZ = ${a}; overscaledZ = ${this.overscaledZ}`);let h=this.canonical.z-a;return a>this.canonical.z?new of(a,this.wrap,this.canonical.z,this.canonical.x,this.canonical.y):new of(a,this.wrap,a,this.canonical.x>>h,this.canonical.y>>h)}isOverscaled(){return this.overscaledZ>this.canonical.z}calculateScaledKey(a,h){if(a>this.overscaledZ)throw new Error(`targetZ > this.overscaledZ; targetZ = ${a}; overscaledZ = ${this.overscaledZ}`);let A=this.canonical.z-a;return a>this.canonical.z?ug(this.wrap*+h,a,this.canonical.z,this.canonical.x,this.canonical.y):ug(this.wrap*+h,a,a,this.canonical.x>>A,this.canonical.y>>A)}isChildOf(a){if(a.wrap!==this.wrap||this.overscaledZ-a.overscaledZ<=0)return!1;if(a.overscaledZ===0)return this.overscaledZ>0;let h=this.canonical.z-a.canonical.z;return!(h<0)&&a.canonical.x===this.canonical.x>>h&&a.canonical.y===this.canonical.y>>h}children(a){if(this.overscaledZ>=a)return[new of(this.overscaledZ+1,this.wrap,this.canonical.z,this.canonical.x,this.canonical.y)];let h=this.canonical.z+1,A=2*this.canonical.x,C=2*this.canonical.y;return[new of(h,this.wrap,h,A,C),new of(h,this.wrap,h,A+1,C),new of(h,this.wrap,h,A,C+1),new of(h,this.wrap,h,A+1,C+1)]}isLessThan(a){return this.wrapa.wrap)&&(this.overscaledZa.overscaledZ)&&(this.canonical.xa.canonical.x)&&this.canonical.yD.key===A);C>-1&&d.addOrUpdateProperties.splice(C,1)}return(d.removeAllProperties||a.removeAllProperties)&&(h.removeAllProperties=!0),(d.removeProperties||a.removeProperties)&&(h.removeProperties=[...d.removeProperties||[],...a.removeProperties||[]]),(d.addOrUpdateProperties||a.addOrUpdateProperties)&&(h.addOrUpdateProperties=[...d.addOrUpdateProperties||[],...a.addOrUpdateProperties||[]]),(d.newGeometry||a.newGeometry)&&(h.newGeometry=a.newGeometry||d.newGeometry),h}function uv(d){var a,h;if(!d)return{};let A={};return A.removeAll=d.removeAll,A.remove=new Set(d.remove||[]),A.add=new Map((a=d.add)===null||a===void 0?void 0:a.map(C=>[C.id,C])),A.update=new Map((h=d.update)===null||h===void 0?void 0:h.map(C=>[C.id,C])),A}ut("CanonicalTileID",cv),ut("OverscaledTileID",of,{omit:["terrainRttPosMatrix32f"]});class hv{constructor(a){this._stringToNumber={},this._numberToString=[];for(let h=0;h=this._numberToString.length)throw new Error(`Out of bounds. Index requested n=${a} can't be >= this._numberToString.length ${this._numberToString.length}`);return this._numberToString[a]}}class sb{constructor(a,h,A,C,D){this.type="Feature",this._vectorTileFeature=a,this._x=A,this._y=C,this._z=h,this.properties=a.properties,this.id=D}projectPoint(a,h,A,C){return[360*(a.x+h)/C-180,360/Math.PI*Math.atan(Math.exp((1-2*(a.y+A)/C)*Math.PI))-90]}projectLine(a,h,A,C){return a.map(D=>this.projectPoint(D,h,A,C))}get geometry(){if(this._geometry)return this._geometry;let a=this._vectorTileFeature,h=a.extent*Math.pow(2,this._z),A=a.extent*this._x,C=a.extent*this._y,D=a.loadGeometry();switch(a.type){case 1:{let O=[];for(let Z of D)O.push(Z[0]);let G=this.projectLine(O,A,C,h);this._geometry=O.length===1?{type:"Point",coordinates:G[0]}:{type:"MultiPoint",coordinates:G};break}case 2:{let O=D.map(G=>this.projectLine(G,A,C,h));this._geometry=O.length===1?{type:"LineString",coordinates:O[0]}:{type:"MultiLineString",coordinates:O};break}case 3:{let O=FC(D),G=[];for(let Z of O)G.push(Z.map(re=>this.projectLine(re,A,C,h)));this._geometry=G.length===1?{type:"Polygon",coordinates:G[0]}:{type:"MultiPolygon",coordinates:G};break}default:throw new Error(`unknown feature type: ${a.type}`)}return this._geometry}set geometry(a){this._geometry=a}toJSON(){let a={geometry:this.geometry};for(let h in this)h!=="_geometry"&&h!=="_vectorTileFeature"&&h!=="_x"&&h!=="_y"&&h!=="_z"&&(a[h]=this[h]);return a}}class ym{_name;dataBuffer;nullabilityBuffer;_size;constructor(a,h,A){this._name=a,this.dataBuffer=h,typeof A=="number"?this._size=A:(this.nullabilityBuffer=A,this._size=A.size())}getValue(a){return this.nullabilityBuffer&&!this.nullabilityBuffer.get(a)?null:this.getValueFromBuffer(a)}has(a){return this.nullabilityBuffer&&this.nullabilityBuffer.get(a)||!this.nullabilityBuffer}get name(){return this._name}get size(){return this._size}}class k_ extends ym{}class oA extends k_{getValueFromBuffer(a){return this.dataBuffer[a]}}class N_ extends k_{getValueFromBuffer(a){return this.dataBuffer[a]}}class sA extends ym{delta;constructor(a,h,A,C){super(a,h,C),this.delta=A}}class z_ extends sA{constructor(a,h,A,C){super(a,Int32Array.of(h),A,C)}getValueFromBuffer(a){return this.dataBuffer[0]+a*this.delta}}class aA extends ym{constructor(a,h,A){super(a,Int32Array.of(h),A)}getValueFromBuffer(a){return this.dataBuffer[0]}}class zC{_name;_geometryVector;_idVector;_propertyVectors;_extent;propertyVectorsMap;constructor(a,h,A,C,D=4096){this._name=a,this._geometryVector=h,this._idVector=A,this._propertyVectors=C,this._extent=D}get name(){return this._name}get idVector(){return this._idVector}get geometryVector(){return this._geometryVector}get propertyVectors(){return this._propertyVectors}getPropertyVector(a){return this.propertyVectorsMap||(this.propertyVectorsMap=new Map(this._propertyVectors.map(h=>[h.name,h]))),this.propertyVectorsMap.get(a)}*[Symbol.iterator](){let a=this.geometryVector[Symbol.iterator](),h=0;for(;h>4,Z<128||(Z=D[O.get()],O.increment(),G|=(127&Z)<<3,Z<128)||(Z=D[O.get()],O.increment(),G|=(127&Z)<<10,Z<128)||(Z=D[O.get()],O.increment(),G|=(127&Z)<<17,Z<128)||(Z=D[O.get()],O.increment(),G|=(127&Z)<<24,Z<128)||(Z=D[O.get()],O.increment(),G|=(1&Z)<<31,Z<128))return 4294967296*G+(C>>>0);throw new Error("Expected varint not more than 10 bytes")}(h,d,a)))))}function ab(d,a,h,A){throw new Error("FastPFor is not implemented yet.")}function bm(d){return d>>>1^-(1&d)}function n0(d){return d>>1n^-(1n&d)}function g2(d,a){let h=0n,A=0,C=a.get();for(;C=64)throw new Error("Varint too long")}return a.set(C),h}function A2(d,a,h){let A=new Int32Array(h),C=0;for(let D=0;D=4)for(let A=d[0];h>>1^-(1&d[0]),d[1]=d[1]>>>1^-(1&d[1]);let a=d.length/4*4,h=2;if(a>=4)for(;h>>1^-(1&A))+d[h-2],d[h+1]=(C>>>1^-(1&C))+d[h-1],d[h+2]=(D>>>1^-(1&D))+d[h],d[h+3]=(O>>>1^-(1&O))+d[h+1]}for(;h!=d.length;h+=2)d[h]=(d[h]>>>1^-(1&d[h]))+d[h-2],d[h+1]=(d[h+1]>>>1^-(1&d[h+1]))+d[h-1]}(function(d){d.NONE="NONE",d.DELTA="DELTA",d.COMPONENTWISE_DELTA="COMPONENTWISE_DELTA",d.RLE="RLE",d.MORTON="MORTON",d.PDE="PDE"})(Es||(Es={})),function(d){d.NONE="NONE",d.FAST_PFOR="FAST_PFOR",d.VARINT="VARINT",d.ALP="ALP"}(hg||(hg={})),function(d){d.PRESENT="PRESENT",d.DATA="DATA",d.OFFSET="OFFSET",d.LENGTH="LENGTH"}(Nu||(Nu={}));class y2{_dictionaryType;_offsetType;_lengthType;constructor(a,h,A){this._dictionaryType=a,this._offsetType=h,this._lengthType=A}get dictionaryType(){return this._dictionaryType}get offsetType(){return this._offsetType}get lengthType(){return this._lengthType}}function sf(d,a){let h=function(A,C){let D=A[C.get()],O=Object.values(Nu)[D>>4],G=null;switch(O){case Nu.DATA:G=new y2(Object.values(vm)[15&D]);break;case Nu.OFFSET:G=new y2(null,Object.values(xm)[15&D]);break;case Nu.LENGTH:G=new y2(null,null,Object.values(ea)[15&D])}C.increment();let Z=A[C.get()],re=Object.values(Es)[Z>>5],ce=Object.values(Es)[Z>>2&7],de=Object.values(hg)[3&Z];C.increment();let we=Uu(A,C,2),Ee=we[0];return{physicalStreamType:O,logicalStreamType:G,logicalLevelTechnique1:re,logicalLevelTechnique2:ce,physicalLevelTechnique:de,numValues:Ee,byteLength:we[1],decompressedCount:Ee}}(d,a);return h.logicalLevelTechnique1===Es.MORTON?function(A,C,D){let O=Uu(C,D,2);return{physicalStreamType:A.physicalStreamType,logicalStreamType:A.logicalStreamType,logicalLevelTechnique1:A.logicalLevelTechnique1,logicalLevelTechnique2:A.logicalLevelTechnique2,physicalLevelTechnique:A.physicalLevelTechnique,numValues:A.numValues,byteLength:A.byteLength,decompressedCount:A.decompressedCount,numBits:O[0],coordinateShift:O[1]}}(h,d,a):Es.RLE!==h.logicalLevelTechnique1&&Es.RLE!==h.logicalLevelTechnique2||hg.NONE===h.physicalLevelTechnique?h:function(A,C,D){let O=Uu(C,D,2);return{physicalStreamType:A.physicalStreamType,logicalStreamType:A.logicalStreamType,logicalLevelTechnique1:A.logicalLevelTechnique1,logicalLevelTechnique2:A.logicalLevelTechnique2,physicalLevelTechnique:A.physicalLevelTechnique,numValues:A.numValues,byteLength:A.byteLength,decompressedCount:O[1],runs:O[0],numRleValues:O[1]}}(h,d,a)}(function(d){d.NONE="NONE",d.SINGLE="SINGLE",d.SHARED="SHARED",d.VERTEX="VERTEX",d.MORTON="MORTON",d.FSST="FSST"})(vm||(vm={})),function(d){d.VERTEX="VERTEX",d.INDEX="INDEX",d.STRING="STRING",d.KEY="KEY"}(xm||(xm={})),function(d){d.VAR_BINARY="VAR_BINARY",d.GEOMETRIES="GEOMETRIES",d.PARTS="PARTS",d.RINGS="RINGS",d.TRIANGLES="TRIANGLES",d.SYMBOL="SYMBOL",d.DICTIONARY="DICTIONARY"}(ea||(ea={})),function(d){d[d.FLAT=0]="FLAT",d[d.CONST=1]="CONST",d[d.SEQUENCE=2]="SEQUENCE",d[d.DICTIONARY=3]="DICTIONARY",d[d.FSST_DICTIONARY=4]="FSST_DICTIONARY"}(el||(el={}));class kp{values;_size;constructor(a,h){this.values=a,this._size=h}get(a){let h=Math.floor(a/8);return(this.values[h]>>a%8&1)==1}set(a,h){let A=Math.floor(a/8);this.values[A]=this.values[A]|(h?1:0)<>a%8&1}size(){return this._size}getBuffer(){return this.values}}function pl(d,a,h,A,C){return function(D,O,G){switch(O.logicalLevelTechnique1){case Es.DELTA:return O.logicalLevelTechnique2===Es.RLE?function(Z,re,ce){let de=new Int32Array(ce),we=0,Ee=0;for(let De=0;De>>1^-(1&Z[0]);let re=Z.length/4*4,ce=1;if(re>=4)for(;ce>>1^-(1&de))+Z[ce-1],Z[ce+1]=(we>>>1^-(1&we))+Z[ce],Z[ce+2]=(Ee>>>1^-(1&Ee))+Z[ce+1],Z[ce+3]=(De>>>1^-(1&De))+Z[ce+2]}for(;ce!=Z.length;++ce)Z[ce]=(Z[ce]>>>1^-(1&Z[ce]))+Z[ce-1]}(D),D);case Es.RLE:return function(Z,re,ce){return ce?function(de,we,Ee){let De=new Int32Array(Ee),ke=0;for(let $e=0;$e>>1^-(1&kt),De.fill(kt,ke,ke+st),ke+=st}return De}(Z,re.runs,re.numRleValues):A2(Z,re.runs,re.numRleValues)}(D,O,G);case Es.MORTON:return _2(D),D;case Es.COMPONENTWISE_DELTA:return VC(D),D;case Es.NONE:return G&&function(Z){for(let re=0;re>>1^-(1&ce)}}(D),D;default:throw new Error(`The specified Logical level technique is not supported: ${O.logicalLevelTechnique1}`)}}(lb(d,a,h),h,A)}function wm(d,a,h){return function(A,C){if(C.logicalLevelTechnique1===Es.DELTA&&C.logicalLevelTechnique2===Es.NONE)return function(O){let G=new Int32Array(O.length+1);G[0]=0,G[1]=bm(O[0]);let Z=G[1],re=2;for(;re!=G.length;++re){let ce=O[re-1];Z+=ce>>>1^-(1&ce),G[re]=G[re-1]+Z}return G}(A);if(C.logicalLevelTechnique1===Es.RLE&&C.logicalLevelTechnique2===Es.NONE)return function(O,G,Z){let re=new Int32Array(Z+1);re[0]=0;let ce=1,de=re[0];for(let we=0;we>>1^-(1&De);for(let ke=ce;ke>1n^-(1n&G[0]);let Z=G.length/4*4,re=1;if(Z>=4)for(;re>1n^-(1n&ce))+G[re-1],G[re+1]=(de>>1n^-(1n&de))+G[re],G[re+2]=(we>>1n^-(1n&we))+G[re+1],G[re+3]=(Ee>>1n^-(1n&Ee))+G[re+2]}for(;re!=G.length;++re)G[re]=(G[re]>>1n^-(1n&G[re]))+G[re-1]}(C),C);case Es.RLE:return function(G,Z,re){return re?function(ce,de,we){let Ee=new BigInt64Array(we),De=0;for(let ke=0;ke>1n^-(1n&st),Ee.fill(st,De,De+$e),De+=$e}return Ee}(G,Z.runs,Z.numRleValues):UC(G,Z.runs,Z.numRleValues)}(C,D,O);case Es.NONE:return O&&function(G){for(let Z=0;Z>1n^-(1n&re)}}(C),C;default:throw new Error(`The specified Logical level technique is not supported: ${D.logicalLevelTechnique1}`)}}(U_(d,a,h.numValues),h,A)}function V_(d,a,h,A){let C=U_(d,a,h.numValues);if(C.length===1){let D=C[0];return A?n0(D):D}return A?function(D){return n0(D[1])}(C):function(D){return D[1]}(C)}function cA(d,a,h,A,C){return function(D,O,G,Z){switch(O.logicalLevelTechnique1){case Es.DELTA:return O.logicalLevelTechnique2===Es.RLE&&(D=A2(D,O.runs,O.numRleValues)),function(re,ce){let de=new Int32Array(re.size()),we=0;re.get(0)?(de[0]=re.get(0)?ce[0]>>>1^-(1&ce[0]):0,we=1):de[0]=0;let Ee=1;for(;Ee!=de.length;++Ee)de[Ee]=re.get(Ee)?de[Ee-1]+(ce[we]>>>1^-(1&ce[we++])):de[Ee-1];return de}(Z,D);case Es.RLE:return function(re,ce,de,we){let Ee=ce;return de?function(De,ke,$e){let st=new Int32Array(De.size()),kt=0;for(let pt=0;pt<$e;pt++){let _t=ke[pt],Gt=ke[pt+$e];Gt=Gt>>>1^-(1&Gt);for(let Qt=kt;Qt>>1^-(1&De)}else de[Ee]=0;return de}(Z,D):function(re,ce){let de=new Int32Array(re.size()),we=0,Ee=0;for(;Ee!=de.length;++Ee)de[Ee]=re.get(Ee)?ce[we++]:0;return de}(Z,D),D;default:throw new Error("The specified Logical level technique is not supported")}}(h.physicalLevelTechnique===hg.FAST_PFOR?ab():Uu(d,a,h.numValues),h,A,C)}function o0(d,a,h,A){let C=d.logicalLevelTechnique1;if(C===Es.RLE)return d.runs===1?el.CONST:el.FLAT;let D=a instanceof kp?a.size():a;if(C===Es.DELTA&&d.logicalLevelTechnique2===Es.RLE){let O=d.runs,G=2;if(d.numRleValues!==D)return el.FLAT;if(O===1)return el.SEQUENCE;if(O===2){let Z=A.get(),re;if(d.physicalLevelTechnique===hg.VARINT)re=Uu(h,A,4);else{let ce=A.get();re=new Int32Array(h.buffer,h.byteOffset+ce,4)}if(A.set(Z),re[2]===G&&re[3]===G)return el.SEQUENCE}}return d.numValues===1?el.CONST:el.FLAT}class j_ extends k_{getValueFromBuffer(a){return this.dataBuffer[a]}}class GC extends sA{constructor(a,h,A,C){super(a,BigInt64Array.of(h),A,C)}getValueFromBuffer(a){return this.dataBuffer[0]+BigInt(a)*this.delta}}class G_{_geometryOffsets;_partOffsets;_ringOffsets;constructor(a,h,A){this._geometryOffsets=a,this._partOffsets=h,this._ringOffsets=A}get geometryOffsets(){return this._geometryOffsets}get partOffsets(){return this._partOffsets}get ringOffsets(){return this._ringOffsets}}function v2(d,a,h){return{x:HC(d,a)-h,y:HC(d>>1,a)-h}}function HC(d,a){let h=0;for(let A=0;A>A;return h}(function(d){d[d.POINT=0]="POINT",d[d.LINESTRING=1]="LINESTRING",d[d.POLYGON=2]="POLYGON",d[d.MULTIPOINT=3]="MULTIPOINT",d[d.MULTILINESTRING=4]="MULTILINESTRING",d[d.MULTIPOLYGON=5]="MULTIPOLYGON"})(zu||(zu={})),function(d){d[d.POINT=0]="POINT",d[d.LINESTRING=1]="LINESTRING",d[d.POLYGON=2]="POLYGON"}(i0||(i0={})),function(d){d[d.MORTON=0]="MORTON",d[d.VEC_2=1]="VEC_2",d[d.VEC_3=2]="VEC_3"}(td||(td={}));class WC{createPoint(a){return[[a]]}createMultiPoint(a){return a.map(h=>[h])}createLineString(a){return[a]}createMultiLineString(a){return a}createPolygon(a,h){return[a].concat(h)}createMultiPolygon(a){return a.flat()}}function x2(d){let a=new Array(d.numGeometries),h=1,A=1,C=1,D=0,O=new WC,G=0,Z=0,re=d.mortonSettings,ce=d.topologyVector,de=ce.geometryOffsets,we=ce.partOffsets,Ee=ce.ringOffsets,De=d.vertexOffsets,ke=d.containsPolygonGeometry(),$e=d.vertexBuffer;for(let st=0;st0&&ke.push(ke[0]),we.push(ke)}a[ce]=we,D&&re++}break;case zu.MULTIPOLYGON:{let de=D[re]-D[re-1];re++;let we=[];for(let Ee=0;Ee0&&st.push(st[0]),we.push(st)}}a[ce]=we}}return a}[Symbol.iterator](){return null}}function pv(d,a,h,A,C,D){return new BL(d,a,h,A,C,D)}class BL extends XC{_numGeometries;_geometryType;constructor(a,h,A,C,D,O){super(A,C,D,O),this._numGeometries=a,this._geometryType=h}geometryType(a){return this._geometryType}get numGeometries(){return this._numGeometries}containsSingleGeometryType(){return!0}}function fb(d,a,h,A,C){return new F5(d,a,h,A,C)}class F5 extends XC{_geometryTypes;constructor(a,h,A,C,D){super(h,A,C,D),this._geometryTypes=a}geometryType(a){return this._geometryTypes[a]}get numGeometries(){return this._geometryTypes.length}containsSingleGeometryType(){return!1}}function LL(d,a,h,A,C){let D=sf(d,h),O=null,G=null,Z=null,re=null,ce=null,de=null,we=null,Ee=null;if(o0(D,A,d,h)===el.CONST){let ke=Np(d,h,D,!1);for(let $e=0;$eh?a[D++]:1);return A}function DL(d,a,h,A){let C=new Int32Array(a[a.length-1]+1),D=0;C[0]=D;let O=1,G=0;for(let Z=0;Z=12?z5.decode(d.subarray(a,h)):function(A,C,D){let O="",G=C;for(;G239?4:Z>223?3:Z>191?2:1;if(G+Ee>D)break;Ee===1?Z<128&&(we=Z):Ee===2?(re=A[G+1],(192&re)==128&&(we=(31&Z)<<6|63&re,we<=127&&(we=null))):Ee===3?(re=A[G+1],ce=A[G+2],(192&re)==128&&(192&ce)==128&&(we=(15&Z)<<12|(63&re)<<6|63&ce,(we<=2047||we>=55296&&we<=57343)&&(we=null))):Ee===4&&(re=A[G+1],ce=A[G+2],de=A[G+3],(192&re)==128&&(192&ce)==128&&(192&de)==128&&(we=(15&Z)<<18|(63&re)<<12|(63&ce)<<6|63&de,(we<=65535||we>=1114112)&&(we=null))),we===null?(we=65533,Ee=1):we>65535&&(we-=65536,O+=String.fromCharCode(we>>>10&1023|55296),we=56320|1023&we),O+=String.fromCharCode(we),G+=Ee}return O}(d,a,h)}class mv extends ym{offsetBuffer;constructor(a,h,A,C){super(a,A,C),this.offsetBuffer=h}}class YC extends mv{textEncoder;constructor(a,h,A,C){super(a,h,A,C??h.length-1),this.textEncoder=new TextEncoder}getValueFromBuffer(a){return Up(this.dataBuffer,this.offsetBuffer[a],this.offsetBuffer[a+1])}}class dg extends mv{indexBuffer;textEncoder;constructor(a,h,A,C,D){super(a,A,C,D??h.length),this.indexBuffer=h,this.indexBuffer=h,this.textEncoder=new TextEncoder}getValueFromBuffer(a){let h=this.indexBuffer[a];return Up(this.dataBuffer,this.offsetBuffer[h],this.offsetBuffer[h+1])}}class KC extends mv{indexBuffer;symbolOffsetBuffer;symbolTableBuffer;textEncoder;symbolLengthBuffer;lengthBuffer;decodedDictionary;constructor(a,h,A,C,D,O,G){super(a,A,C,G),this.indexBuffer=h,this.symbolOffsetBuffer=D,this.symbolTableBuffer=O,this.textEncoder=new TextEncoder}getValueFromBuffer(a){this.decodedDictionary==null&&(this.symbolLengthBuffer==null&&(this.symbolLengthBuffer=this.offsetToLengthBuffer(this.symbolOffsetBuffer),this.lengthBuffer=this.offsetToLengthBuffer(this.offsetBuffer)),this.decodedDictionary=function(A,C,D){let O=[],G=new Array(C.length).fill(0);for(let Z=1;Z>1n^-(1n&tn[0]):0n,da=1):Vn[0]=0n;let Gs=1;for(;Gs!=Vn.length;++Gs)Vn[Gs]=vn.get(Gs)?Vn[Gs-1]+(tn[da]>>1n^-(1n&tn[da++])):Vn[Gs-1];return Vn}(cn,Qr);case Es.RLE:return function(vn,tn,Vn,da){let Gs=tn;return Vn?function(vc,Ef,Vu){let xc=new BigInt64Array(vc.size()),sc=0;for(let za=0;za>1n^-(1n&lu);for(let ql=sc;ql>1n^-(1n&vc)}else Vn[Gs]=0n;return Vn}(cn,Qr):function(vn,tn){let Vn=new BigInt64Array(vn.size()),da=0,Gs=0;for(;Gs!=Vn.length;++Gs)Vn[Gs]=vn.get(Gs)?tn[da++]:0n;return Vn}(cn,Qr),Qr;default:throw new Error("The specified Logical level technique is not supported")}}(U_(oi,Kr,gi.numValues),gi,ji,vi)}(ke,$e,_t,Qt,kt):lA(ke,$e,_t,Qt);return new j_(st.name,Mr,kt)}if(Gt===el.SEQUENCE){let Mr=jC(ke,$e,_t);return new GC(st.name,Mr[0],Mr[1],_t.numRleValues)}{let Mr=V_(ke,$e,_t,Qt);return new OL(st.name,Mr,kt)}}(G,Z,de,De,ce);case 7:return function(ke,$e,st,kt){let pt=sf(ke,$e),_t=QC(kt)?function(Gt,Qt,Mr,oi){let Kr=Qt.get(),gi=Kr+oi*Float32Array.BYTES_PER_ELEMENT,ji=new Uint8Array(Gt.subarray(Kr,gi)).buffer,vi=new Float32Array(ji);Qt.set(gi);let Qr=Mr.size(),jr=new Float32Array(Qr),dn=0;for(let cn=0;cn>>0,A=!!(4&h),C=!!(2&h),D=Uu(d,a,1)[0]>>>0,O={};if(1&h&&(O.nullable=!0),C){let G={};if(A?(G.type="logicalType",G.logicalType=D):(G.type="physicalType",G.physicalType=D),8&h){let Z=Uu(d,a,1)[0]>>>0;G.children=new Array(Z);for(let re=0;re>>0,A=function(C){switch(C){case 0:case 1:case 2:case 3:{let D={};D.nullable=!!(1&C),D.columnScope=0;let O={};return O.physicalType=C>1?6:4,O.type="physicalType",D.scalarType=O,D.type="scalarType",D}case 4:{let D={nullable:!1,columnScope:0},O={type:"physicalType",physicalType:0};return D.type="complexType",D.complexType=O,D}case 30:{let D={nullable:!1,columnScope:0},O={type:"physicalType",physicalType:1};return D.type="complexType",D.complexType=O,D}default:return function(D){let O=null;switch(D){case 10:case 11:O=0;break;case 12:case 13:O=1;break;case 14:case 15:O=2;break;case 16:case 17:O=3;break;case 18:case 19:O=4;break;case 20:case 21:O=5;break;case 22:case 23:O=6;break;case 24:case 25:O=7;break;case 26:case 27:O=8;break;case 28:case 29:O=9;break;default:return null}let G={};G.nullable=!!(1&D),G.columnScope=0;let Z={type:"physicalType"};return Z.physicalType=O,G.type="scalarType",G.scalarType=Z,G}(C)}}(h);if(!A)throw new Error(`Unsupported column type code: ${h}`);if(function(C){return C>=10}(h)?A.name=gY(d,a):h>=0&&h<=3?A.name="id":h===4&&(A.name="geometry"),function(C){return C===30}(h)){let C=Uu(d,a,1)[0]>>>0,D=A.complexType;D.children=new Array(C);for(let O=0;O>>0,D=Uu(d,a,1)[0]>>>0;A.columns=new Array(D);for(let O=0;O=4)for(;Gt<_t-4;Gt+=4){let Qt=pt[Gt],Mr=pt[Gt+1],oi=pt[Gt+2],Kr=pt[Gt+3];pt[Gt]=(Qt%2==1?(Qt+1)/-2:Qt/2)+pt[Gt-1],pt[Gt+1]=(Mr%2==1?(Mr+1)/-2:Mr/2)+pt[Gt],pt[Gt+2]=(oi%2==1?(oi+1)/-2:oi/2)+pt[Gt+1],pt[Gt+3]=(Kr%2==1?(Kr+1)/-2:Kr/2)+pt[Gt+2]}for(;Gt!=pt.length;++Gt)pt[Gt]=(pt[Gt]%2==1?(pt[Gt]+1)/-2:pt[Gt]/2)+pt[Gt-1]}($e),$e;case Es.RLE:return function(pt,_t,Gt){return Gt?function(Qt,Mr,oi){let Kr=new Float64Array(oi),gi=0;for(let ji=0;ji>>0,re=O.get()+Z;if(re>A.length)throw new Error(`Block overruns tile: ${re} > ${A.length}`);if(Uu(A,O,1)[0]>>>0!=1){O.set(re);continue}let ce=X1e(A,O),de=ce[1],we=ce[0].featureTables[0],Ee=null,De=null,ke=[],$e=0;for(let kt of we.columns){let pt=kt.name;if(pt==="id"){let _t=null;if(kt.nullable){let Qt=sf(A,O),Mr=O.get(),oi=db(A,Qt.numValues,O);O.set(Mr+Qt.byteLength),_t=new kp(oi,Qt.numValues)}let Gt=sf(A,O);$e=Gt.decompressedCount,Ee=Z1e(A,kt,O,pt,Gt,_t??$e,D)}else if(pt==="geometry"){let _t=Uu(A,O,1)[0];if($e===0){let Gt=O.get();$e=sf(A,O).decompressedCount,O.set(Gt)}De=LL(A,_t,O,$e)}else{let _t=W1e(kt)?Uu(A,O,1)[0]:1;if(_t===0&&kt.type==="scalarType")continue;let Gt=H1e(A,O,kt,_t,$e);if(Gt)if(Array.isArray(Gt))for(let Qt of Gt)ke.push(Qt);else ke.push(Gt)}}let st=new zC(we.name,De,Ee,ke,de);G.push(st),O.set(re)}return G}(new Uint8Array(a));this.layers=h.reduce((A,C)=>Object.assign(Object.assign({},A),{[C.name]:new K1e(C)}),{})}}class pb{constructor(){this.minX=1/0,this.maxX=-1/0,this.minY=1/0,this.maxY=-1/0}extend(a){return this.minX=Math.min(this.minX,a.x),this.minY=Math.min(this.minY,a.y),this.maxX=Math.max(this.maxX,a.x),this.maxY=Math.max(this.maxY,a.y),this}expandBy(a){return this.minX-=a,this.minY-=a,this.maxX+=a,this.maxY+=a,(this.minX>this.maxX||this.minY>this.maxY)&&(this.minX=1/0,this.maxX=-1/0,this.minY=1/0,this.maxY=-1/0),this}shrinkBy(a){return this.expandBy(-a)}map(a){let h=new pb;return h.extend(a(new s(this.minX,this.minY))),h.extend(a(new s(this.maxX,this.minY))),h.extend(a(new s(this.minX,this.maxY))),h.extend(a(new s(this.maxX,this.maxY))),h}static fromPoints(a){let h=new pb;for(let A of a)h.extend(A);return h}contains(a){return a.x>=this.minX&&a.x<=this.maxX&&a.y>=this.minY&&a.y<=this.maxY}empty(){return this.minX>this.maxX}width(){return this.maxX-this.minX}height(){return this.maxY-this.minY}covers(a){return!this.empty()&&!a.empty()&&a.minX>=this.minX&&a.maxX<=this.maxX&&a.minY>=this.minY&&a.maxY<=this.maxY}intersects(a){return!this.empty()&&!a.empty()&&a.minX<=this.maxX&&a.maxX>=this.minX&&a.minY<=this.maxY&&a.maxY>=this.minY}}class Q1e{constructor(a,h){this.feature=a,this.type=a.type,this.properties=a.tags?a.tags:{},this.extent=h,"id"in a&&(typeof a.id=="string"?this.id=parseInt(a.id,10):typeof a.id!="number"||isNaN(a.id)||(this.id=a.id))}loadGeometry(){let a=[],h=this.feature.type===1?[this.feature.geometry]:this.feature.geometry;for(let A of h){let C=[];for(let D of A)C.push(new s(D[0],D[1]));a.push(C)}return a}}let JC="_geojsonTileLayer";function J1e(d,a){a.writeVarintField(15,d.version||1),a.writeStringField(1,d.name||""),a.writeVarintField(5,d.extent||4096);let h={keys:[],values:[],keycache:{},valuecache:{}};for(let D=0;D>31}function rwe(d,a){let h=d.loadGeometry(),A=d.type,C=0,D=0;for(let O of h){let G=1;A===1&&(G=O.length),a.writeVarint(U5(1,G));let Z=A===3?O.length-1:O.length;for(let re=0;re=0&&de[3]>=0&&Z.insert(G,de[0],de[1],de[2],de[3])}}loadVTLayers(){return this.vtLayers||(this.vtLayers=this.encoding!=="mlt"?new Yx(new tr(this.rawTileData)).layers:new _Y(this.rawTileData).layers,this.sourceLayerCoder=new hv(this.vtLayers?Object.keys(this.vtLayers).sort():[JC])),this.vtLayers}query(a,h,A,C){this.loadVTLayers();let D=a.params,O=hr/a.tileSize/a.scale,G=Pc(D.filter,D.globalState),Z=a.queryGeometry,re=a.queryPadding*O,ce=pb.fromPoints(Z),de=this.grid.query(ce.minX-re,ce.minY-re,ce.maxX+re,ce.maxY+re),we=pb.fromPoints(a.cameraQueryGeometry).expandBy(re),Ee=this.grid3D.query(we.minX,we.minY,we.maxX,we.maxY,($e,st,kt,pt)=>function(_t,Gt,Qt,Mr,oi){for(let gi of _t)if(Gt<=gi.x&&Qt<=gi.y&&Mr>=gi.x&&oi>=gi.y)return!0;let Kr=[new s(Gt,Qt),new s(Gt,oi),new s(Mr,oi),new s(Mr,Qt)];if(_t.length>2){for(let gi of Kr)if(Bo(_t,gi))return!0}for(let gi=0;gi<_t.length-1;gi++)if(Gx(_t[gi],_t[gi+1],Kr))return!0;return!1}(a.cameraQueryGeometry,$e-re,st-re,kt+re,pt+re));for(let $e of Ee)de.push($e);de.sort(nwe);let De={},ke;for(let $e=0;$e(pt||(pt=or(_t)),Gt.queryIntersectsFeature({queryGeometry:Z,feature:_t,featureState:Qt,geometry:pt,zoom:this.z,transform:a.transform,pixelsToTileUnits:O,pixelPosMatrix:a.pixelPosMatrix,unwrappedTileID:this.tileID.toUnwrapped(),getElevation:a.getElevation})))}return De}loadMatchingFeature(a,h,A,C,D,O,G,Z,re,ce,de){let we=this.bucketLayerIDs[h];if(O&&!we.some($e=>O.has($e)))return;let Ee=this.sourceLayerCoder.decode(A),De=this.vtLayers[Ee].feature(C);if(D.needGeometry){let $e=Pi(De,!0);if(!D.filter(new Hi(this.tileID.overscaledZ),$e,this.tileID.canonical))return}else if(!D.filter(new Hi(this.tileID.overscaledZ),De))return;let ke=this.getId(De,Ee);for(let $e=0;$e{let G=a instanceof Ta?a.get(O):null;return G&&G.evaluate?G.evaluate(h,A,C):G})}function nwe(d,a){return a-d}function bY(d,a,h,A,C){let D=[];for(let O=0;O=A&&de.x>=A||(ce.x>=A?ce=new s(A,ce.y+(A-ce.x)/(de.x-ce.x)*(de.y-ce.y))._round():de.x>=A&&(de=new s(A,ce.y+(A-ce.x)/(de.x-ce.x)*(de.y-ce.y))._round()),ce.y>=C&&de.y>=C||(ce.y>=C?ce=new s(ce.x+(C-ce.y)/(de.y-ce.y)*(de.x-ce.x),C)._round():de.y>=C&&(de=new s(ce.x+(C-ce.y)/(de.y-ce.y)*(de.x-ce.x),C)._round()),Z&&ce.equals(Z[Z.length-1])||(Z=[ce],D.push(Z)),Z.push(de)))))}}return D}function wY(d,a,h,A,C){switch(a){case 1:return function(D,O,G,Z){let re=[];for(let ce of D)for(let de of ce){let we=Z===0?de.x:de.y;we>=O&&we<=G&&re.push([de])}return re}(d,h,A,C);case 2:return TY(d,h,A,C,!1);case 3:return TY(d,h,A,C,!0)}return[]}function owe(d,a,h,A,C){let D=A===0?swe:awe,O=[],G=[];for(let ce=0;cea&&O.push(D(de,we,a)):Ee>h?De=a&&(O.push(D(de,we,a)),ke=!0),De>h&&Ee<=h&&(O.push(D(de,we,h)),ke=!0),!C&&ke&&(G.push(O),O=[])}let Z=d.length-1,re=A===0?d[Z].x:d[Z].y;return re>=a&&re<=h&&O.push(d[Z]),C&&O.length>0&&!O[0].equals(O[O.length-1])&&O.push(new s(O[0].x,O[0].y)),O.length>0&&G.push(O),G}function TY(d,a,h,A,C){let D=[];for(let O of d){let G=owe(O,a,h,A,C);G.length>0&&D.push(...G)}return D}function swe(d,a,h){return new s(h,d.y+(h-d.x)/(a.x-d.x)*(a.y-d.y))}function awe(d,a,h){return new s(d.x+(h-d.y)/(a.y-d.y)*(a.x-d.x),h)}ut("FeatureIndex",vY,{omit:["rawTileData","sourceLayerCoder"]});class gv extends s{constructor(a,h,A,C){super(a,h),this.angle=A,C!==void 0&&(this.segment=C)}clone(){return new gv(this.x,this.y,this.angle,this.segment)}}function SY(d,a,h,A,C){if(a.segment===void 0||h===0)return!0;let D=a,O=a.segment+1,G=0;for(;G>-h/2;){if(O--,O<0)return!1;G-=d[O].dist(D),D=d[O]}G+=d[O].dist(d[O+1]),O++;let Z=[],re=0;for(;GA;)re-=Z.shift().angleDelta;if(re>C)return!1;O++,G+=ce.dist(de)}return!0}function EY(d){let a=0;for(let h=0;hre){let De=(re-Z)/Ee,ke=Fr.number(de.x,we.x,De),$e=Fr.number(de.y,we.y,De),st=new gv(ke,$e,we.angleTo(de),ce);return st._round(),!O||SY(d,st,G,O,a)?st:void 0}Z+=Ee}}function cwe(d,a,h,A,C,D,O,G,Z){let re=CY(A,D,O),ce=MY(A,C),de=ce*O,we=d[0].x===0||d[0].x===Z||d[0].y===0||d[0].y===Z;return a-de=0&&_t=0&&Gt=0&&we+re<=ce){let Qt=new gv(_t,Gt,kt,De);Qt._round(),A&&!SY(d,Qt,D,A,C)||Ee.push(Qt)}}de+=st}return G||Ee.length||O||(Ee=IY(d,de/2,h,A,C,D,O,!0,Z)),Ee}function PY(d,a,h,A){let C=[],D=d.image,O=D.pixelRatio,G=D.paddedRect.w-2,Z=D.paddedRect.h-2,re={x1:d.left,y1:d.top,x2:d.right,y2:d.bottom},ce=D.stretchX||[[0,G]],de=D.stretchY||[[0,Z]],we=(jr,dn)=>jr+dn[1]-dn[0],Ee=ce.reduce(we,0),De=de.reduce(we,0),ke=G-Ee,$e=Z-De,st=0,kt=Ee,pt=0,_t=De,Gt=0,Qt=ke,Mr=0,oi=$e;if(D.content&&A){let jr=D.content,dn=jr[2]-jr[0],cn=jr[3]-jr[1];(D.textFitWidth||D.textFitHeight)&&(re=ih(d)),st=kL(ce,0,jr[0]),pt=kL(de,0,jr[1]),kt=kL(ce,jr[0],jr[2]),_t=kL(de,jr[1],jr[3]),Gt=jr[0]-st,Mr=jr[1]-pt,Qt=dn-kt,oi=cn-_t}let Kr=re.x1,gi=re.y1,ji=re.x2-Kr,vi=re.y2-gi,Qr=(jr,dn,cn,vn)=>{let tn=NL(jr.stretch-st,kt,ji,Kr),Vn=zL(jr.fixed-Gt,Qt,jr.stretch,Ee),da=NL(dn.stretch-pt,_t,vi,gi),Gs=zL(dn.fixed-Mr,oi,dn.stretch,De),vc=NL(cn.stretch-st,kt,ji,Kr),Ef=zL(cn.fixed-Gt,Qt,cn.stretch,Ee),Vu=NL(vn.stretch-pt,_t,vi,gi),xc=zL(vn.fixed-Mr,oi,vn.stretch,De),sc=new s(tn,da),za=new s(vc,da),$c=new s(vc,Vu),lu=new s(tn,Vu),ql=new s(Vn/O,Gs/O),ep=new s(Ef/O,xc/O),Cf=a*Math.PI/180;if(Cf){let Th=Math.sin(Cf),Bc=Math.cos(Cf),ju=[Bc,-Th,Th,Bc];sc._matMult(ju),za._matMult(ju),lu._matMult(ju),$c._matMult(ju)}let tp=jr.stretch+jr.fixed,pg=dn.stretch+dn.fixed;return{tl:sc,tr:za,bl:lu,br:$c,tex:{x:D.paddedRect.x+1+tp,y:D.paddedRect.y+1+pg,w:cn.stretch+cn.fixed-tp,h:vn.stretch+vn.fixed-pg},writingMode:void 0,glyphOffset:[0,0],sectionIndex:0,pixelOffsetTL:ql,pixelOffsetBR:ep,minFontScaleX:Qt/O/ji,minFontScaleY:oi/O/vi,isSDF:h}};if(A&&(D.stretchX||D.stretchY)){let jr=RY(ce,ke,Ee),dn=RY(de,$e,De);for(let cn=0;cn0&&(ke=Math.max(10,ke),this.circleDiameter=ke)}else{let we=!((de=O.image)===null||de===void 0)&&de.content&&(O.image.textFitWidth||O.image.textFitHeight)?ih(O):{x1:O.left,y1:O.top,x2:O.right,y2:O.bottom};we.y1=we.y1*G-Z[0],we.y2=we.y2*G+Z[2],we.x1=we.x1*G-Z[3],we.x2=we.x2*G+Z[1];let Ee=O.collisionPadding;if(Ee&&(we.x1-=Ee[0]*G,we.y1-=Ee[1]*G,we.x2+=Ee[2]*G,we.y2+=Ee[3]*G),ce){let De=new s(we.x1,we.y1),ke=new s(we.x2,we.y1),$e=new s(we.x1,we.y2),st=new s(we.x2,we.y2),kt=ce*Math.PI/180;De._rotate(kt),ke._rotate(kt),$e._rotate(kt),st._rotate(kt),we.x1=Math.min(De.x,ke.x,$e.x,st.x),we.x2=Math.max(De.x,ke.x,$e.x,st.x),we.y1=Math.min(De.y,ke.y,$e.y,st.y),we.y2=Math.max(De.y,ke.y,$e.y,st.y)}a.emplaceBack(h.x,h.y,we.x1,we.y1,we.x2,we.y2,A,C,D)}this.boxEndIndex=a.length}}class uwe{constructor(a=[],h=(A,C)=>AC?1:0){if(this.data=a,this.length=this.data.length,this.compare=h,this.length>0)for(let A=(this.length>>1)-1;A>=0;A--)this._down(A)}push(a){this.data.push(a),this._up(this.length++)}pop(){if(this.length===0)return;let a=this.data[0],h=this.data.pop();return--this.length>0&&(this.data[0]=h,this._down(0)),a}peek(){return this.data[0]}_up(a){let{data:h,compare:A}=this,C=h[a];for(;a>0;){let D=a-1>>1,O=h[D];if(A(C,O)>=0)break;h[a]=O,a=D}h[a]=C}_down(a){let{data:h,compare:A}=this,C=this.length>>1,D=h[a];for(;a=0)break;h[a]=h[O],a=O}h[a]=D}}function hwe(d,a=1,h=!1){let A=pb.fromPoints(d[0]),C=Math.min(A.width(),A.height()),D=C/2,O=new uwe([],fwe),{minX:G,minY:Z,maxX:re,maxY:ce}=A;if(C===0)return new s(G,Z);for(let Ee=G;Eede.d||!de.d)&&(de=Ee,h&&console.log("found best %d after %d probes",Math.round(1e4*Ee.d)/1e4,we)),Ee.max-de.d<=a||(D=Ee.h/2,O.push(new T2(Ee.p.x-D,Ee.p.y-D,D,d)),O.push(new T2(Ee.p.x+D,Ee.p.y-D,D,d)),O.push(new T2(Ee.p.x-D,Ee.p.y+D,D,d)),O.push(new T2(Ee.p.x+D,Ee.p.y+D,D,d)),we+=4)}return h&&(console.log(`num probes: ${we}`),console.log(`best distance: ${de.d}`)),de.p}function fwe(d,a){return a.max-d.max}function T2(d,a,h,A){this.p=new s(d,a),this.h=h,this.d=function(C,D){let O=!1,G=1/0;for(let Z=0;ZC.y!=De.y>C.y&&C.x<(De.x-Ee.x)*(C.y-Ee.y)/(De.y-Ee.y)+Ee.x&&(O=!O),G=Math.min(G,Qo(C,Ee,De))}}return(O?1:-1)*Math.sqrt(G)}(this.p,A),this.max=this.d+this.h*Math.SQRT2}var Sf;n.aQ=void 0,(Sf=n.aQ||(n.aQ={}))[Sf.center=1]="center",Sf[Sf.left=2]="left",Sf[Sf.right=3]="right",Sf[Sf.top=4]="top",Sf[Sf.bottom=5]="bottom",Sf[Sf["top-left"]=6]="top-left",Sf[Sf["top-right"]=7]="top-right",Sf[Sf["bottom-left"]=8]="bottom-left",Sf[Sf["bottom-right"]=9]="bottom-right";let V5=Number.POSITIVE_INFINITY;function BY(d,a){return a[1]!==V5?function(h,A,C){let D=0,O=0;switch(A=Math.abs(A),C=Math.abs(C),h){case"top-right":case"top-left":case"top":O=C-7;break;case"bottom-right":case"bottom-left":case"bottom":O=7-C}switch(h){case"top-right":case"bottom-right":case"right":D=-A;break;case"top-left":case"bottom-left":case"left":D=A}return[D,O]}(d,a[0],a[1]):function(h,A){let C=0,D=0;A<0&&(A=0);let O=A/Math.SQRT2;switch(h){case"top-right":case"top-left":D=O-7;break;case"bottom-right":case"bottom-left":D=7-O;break;case"bottom":D=7-A;break;case"top":D=A-7}switch(h){case"top-right":case"bottom-right":C=-O;break;case"top-left":case"bottom-left":C=O;break;case"left":C=A;break;case"right":C=-A}return[C,D]}(d,a[0])}function LY(d,a,h){var A;let C=d.layout,D=(A=C.get("text-variable-anchor-offset"))===null||A===void 0?void 0:A.evaluate(a,{},h);if(D){let G=D.values,Z=[];for(let re=0;rewe*R);ce.startsWith("top")?de[1]-=7:ce.startsWith("bottom")&&(de[1]+=7),Z[re+1]=de}return new sl(Z)}let O=C.get("text-variable-anchor");if(O){let G;G=d._unevaluatedLayout.getValue("text-radial-offset")!==void 0?[C.get("text-radial-offset").evaluate(a,{},h)*R,V5]:C.get("text-offset").evaluate(a,{},h).map(re=>re*R);let Z=[];for(let re of O)Z.push(re,BY(re,G));return new sl(Z)}return null}function j5(d){switch(d){case"right":case"top-right":case"bottom-right":return"right";case"left":case"top-left":case"bottom-left":return"left"}return"center"}function dwe(d,a,h,A,C,D,O,G,Z,re,ce,de){let we=D.textMaxSize.evaluate(a,{});we===void 0&&(we=O);let Ee=d.layers[0].layout,De=Ee.get("icon-offset").evaluate(a,{},ce),ke=OY(h.horizontal),$e=O/24,st=d.tilePixelRatio*$e,kt=d.tilePixelRatio*we/24,pt=d.tilePixelRatio*G,_t=d.tilePixelRatio*Ee.get("symbol-spacing"),Gt=Ee.get("text-padding")*d.tilePixelRatio,Qt=function(cn,vn,tn,Vn=1){let da=cn.get("icon-padding").evaluate(vn,{},tn),Gs=da&&da.values;return[Gs[0]*Vn,Gs[1]*Vn,Gs[2]*Vn,Gs[3]*Vn]}(Ee,a,ce,d.tilePixelRatio),Mr=Ee.get("text-max-angle")/180*Math.PI,oi=Ee.get("text-rotation-alignment")!=="viewport"&&Ee.get("symbol-placement")!=="point",Kr=Ee.get("icon-rotation-alignment")==="map"&&Ee.get("symbol-placement")!=="point",gi=Ee.get("symbol-placement"),ji=_t/2,vi=Ee.get("icon-text-fit"),Qr;A&&vi!=="none"&&(d.allowVerticalPlacement&&h.vertical&&(Qr=su(A,h.vertical,vi,Ee.get("icon-text-fit-padding"),De,$e)),ke&&(A=su(A,ke,vi,Ee.get("icon-text-fit-padding"),De,$e)));let jr=ce?de.line.getGranularityForZoomLevel(ce.z):1,dn=(cn,vn)=>{vn.x<0||vn.x>=hr||vn.y<0||vn.y>=hr||function(tn,Vn,da,Gs,vc,Ef,Vu,xc,sc,za,$c,lu,ql,ep,Cf,tp,pg,Th,Bc,ju,Xc,Sh,H_,s0,rM){let Av=tn.addToLineVertexArray(Vn,da),mb,S2,E2,C2,zY=0,UY=0,VY=0,jY=0,Y5=-1,K5=-1,W_={},GY=qs("");if(tn.allowVerticalPlacement&&Gs.vertical){let rd=xc.layout.get("text-rotate").evaluate(Xc,{},s0)+90;E2=new UL(sc,Vn,za,$c,lu,Gs.vertical,ql,ep,Cf,rd),Vu&&(C2=new UL(sc,Vn,za,$c,lu,Vu,pg,Th,Cf,rd))}if(vc){let rd=xc.layout.get("icon-rotate").evaluate(Xc,{}),Tm=xc.layout.get("icon-text-fit")!=="none",gb=PY(vc,rd,H_,Tm),l0=Vu?PY(Vu,rd,H_,Tm):void 0;S2=new UL(sc,Vn,za,$c,lu,vc,pg,Th,!1,rd),zY=4*gb.length;let Ab=tn.iconSizeData,uA=null;Ab.kind==="source"?(uA=[nc*xc.layout.get("icon-size").evaluate(Xc,{})],uA[0]>ka&&ln(`${tn.layerIds[0]}: Value for "icon-size" is >= 255. Reduce your "icon-size".`)):Ab.kind==="composite"&&(uA=[nc*Sh.compositeIconSizes[0].evaluate(Xc,{},s0),nc*Sh.compositeIconSizes[1].evaluate(Xc,{},s0)],(uA[0]>ka||uA[1]>ka)&&ln(`${tn.layerIds[0]}: Value for "icon-size" is >= 255. Reduce your "icon-size".`)),tn.addSymbols(tn.icon,gb,uA,ju,Bc,Xc,n.aA.none,Vn,Av.lineStartIndex,Av.lineLength,-1,s0),Y5=tn.icon.placedSymbolArray.length-1,l0&&(UY=4*l0.length,tn.addSymbols(tn.icon,l0,uA,ju,Bc,Xc,n.aA.vertical,Vn,Av.lineStartIndex,Av.lineLength,-1,s0),K5=tn.icon.placedSymbolArray.length-1)}let HY=Object.keys(Gs.horizontal);for(let rd of HY){let Tm=Gs.horizontal[rd];if(!mb){GY=qs(Tm.text);let l0=xc.layout.get("text-rotate").evaluate(Xc,{},s0);mb=new UL(sc,Vn,za,$c,lu,Tm,ql,ep,Cf,l0)}let gb=Tm.positionedLines.length===1;if(VY+=DY(tn,Vn,Tm,Ef,xc,Cf,Xc,tp,Av,Gs.vertical?n.aA.horizontal:n.aA.horizontalOnly,gb?HY:[rd],W_,Y5,Sh,s0),gb)break}Gs.vertical&&(jY+=DY(tn,Vn,Gs.vertical,Ef,xc,Cf,Xc,tp,Av,n.aA.vertical,["vertical"],W_,K5,Sh,s0));let gwe=mb?mb.boxStartIndex:tn.collisionBoxArray.length,Awe=mb?mb.boxEndIndex:tn.collisionBoxArray.length,_we=E2?E2.boxStartIndex:tn.collisionBoxArray.length,ywe=E2?E2.boxEndIndex:tn.collisionBoxArray.length,vwe=S2?S2.boxStartIndex:tn.collisionBoxArray.length,xwe=S2?S2.boxEndIndex:tn.collisionBoxArray.length,bwe=C2?C2.boxStartIndex:tn.collisionBoxArray.length,wwe=C2?C2.boxEndIndex:tn.collisionBoxArray.length,a0=-1,jL=(rd,Tm)=>rd&&rd.circleDiameter?Math.max(rd.circleDiameter,Tm):Tm;a0=jL(mb,a0),a0=jL(E2,a0),a0=jL(S2,a0),a0=jL(C2,a0);let WY=a0>-1?1:0;WY&&(a0*=rM/R),tn.glyphOffsetArray.length>=dl.MAX_GLYPHS&&ln("Too many glyphs being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907"),Xc.sortKey!==void 0&&tn.addToSortKeyRanges(tn.symbolInstances.length,Xc.sortKey);let Twe=LY(xc,Xc,s0),[Swe,Ewe]=function(rd,Tm){let gb=rd.length,l0=Tm?.values;if(l0?.length>0)for(let Ab=0;Ab=0?W_.right:-1,W_.center>=0?W_.center:-1,W_.left>=0?W_.left:-1,W_.vertical||-1,Y5,K5,GY,gwe,Awe,_we,ywe,vwe,xwe,bwe,wwe,za,VY,jY,zY,UY,WY,0,ql,a0,Swe,Ewe)}(d,vn,cn,h,A,C,Qr,d.layers[0],d.collisionBoxArray,a.index,a.sourceLayerIndex,d.index,st,[Gt,Gt,Gt,Gt],oi,Z,pt,Qt,Kr,De,a,D,re,ce,O)};if(gi==="line")for(let cn of bY(a.geometry,0,0,hr,hr)){let vn=C_(cn,jr),tn=cwe(vn,_t,Mr,h.vertical||ke,A,24,kt,d.overscaling,hr);for(let Vn of tn)ke&&pwe(d,ke.text,ji,Vn)||dn(vn,Vn)}else if(gi==="line-center"){for(let cn of a.geometry)if(cn.length>1){let vn=C_(cn,jr),tn=lwe(vn,Mr,h.vertical||ke,A,24,kt);tn&&dn(vn,tn)}}else if(a.type==="Polygon")for(let cn of dt(a.geometry,0)){let vn=hwe(cn,16);dn(C_(cn[0],jr,!0),new gv(vn.x,vn.y,0))}else if(a.type==="LineString")for(let cn of a.geometry){let vn=C_(cn,jr);dn(vn,new gv(vn[0].x,vn[0].y,0))}else if(a.type==="Point")for(let cn of a.geometry)for(let vn of cn)dn([vn],new gv(vn.x,vn.y,0))}function DY(d,a,h,A,C,D,O,G,Z,re,ce,de,we,Ee,De){let ke=function(kt,pt,_t,Gt,Qt,Mr,oi,Kr){let gi=Gt.layout.get("text-rotate").evaluate(Mr,{})*Math.PI/180,ji=[];for(let vi of pt.positionedLines)for(let Qr of vi.positionedGlyphs){if(!Qr.rect)continue;let jr=Qr.rect||{},dn=4,cn=!0,vn=1,tn=0,Vn=(Qt||Kr)&&Qr.vertical,da=Qr.metrics.advance*Qr.scale/2;if(Kr&&pt.verticalizable&&(tn=vi.lineOffset/2-(Qr.imageName?-(R-Qr.metrics.width*Qr.scale)/2:(Qr.scale-1)*R)),Qr.imageName){let Th=oi[Qr.imageName];cn=Th.sdf,vn=Th.pixelRatio,dn=1/vn}let Gs=Qt?[Qr.x+da,Qr.y]:[0,0],vc=Qt?[0,0]:[Qr.x+da+_t[0],Qr.y+_t[1]-tn],Ef=[0,0];Vn&&(Ef=vc,vc=[0,0]);let Vu=Qr.metrics.isDoubleResolution?2:1,xc=(Qr.metrics.left-dn)*Qr.scale-da+vc[0],sc=(-Qr.metrics.top-dn)*Qr.scale+vc[1],za=xc+jr.w/Vu*Qr.scale/vn,$c=sc+jr.h/Vu*Qr.scale/vn,lu=new s(xc,sc),ql=new s(za,sc),ep=new s(xc,$c),Cf=new s(za,$c);if(Vn){let Th=new s(-da,da- -17),Bc=-Math.PI/2,ju=12-da,Xc=new s(22-ju,-(Qr.imageName?ju:0)),Sh=new s(...Ef);lu._rotateAround(Bc,Th)._add(Xc)._add(Sh),ql._rotateAround(Bc,Th)._add(Xc)._add(Sh),ep._rotateAround(Bc,Th)._add(Xc)._add(Sh),Cf._rotateAround(Bc,Th)._add(Xc)._add(Sh)}if(gi){let Th=Math.sin(gi),Bc=Math.cos(gi),ju=[Bc,-Th,Th,Bc];lu._matMult(ju),ql._matMult(ju),ep._matMult(ju),Cf._matMult(ju)}let tp=new s(0,0),pg=new s(0,0);ji.push({tl:lu,tr:ql,bl:ep,br:Cf,tex:jr,writingMode:pt.writingMode,glyphOffset:Gs,sectionIndex:Qr.sectionIndex,isSDF:cn,pixelOffsetTL:tp,pixelOffsetBR:pg,minFontScaleX:0,minFontScaleY:0})}return ji}(0,h,G,C,D,O,A,d.allowVerticalPlacement),$e=d.textSizeData,st=null;$e.kind==="source"?(st=[nc*C.layout.get("text-size").evaluate(O,{})],st[0]>ka&&ln(`${d.layerIds[0]}: Value for "text-size" is >= 255. Reduce your "text-size".`)):$e.kind==="composite"&&(st=[nc*Ee.compositeTextSizes[0].evaluate(O,{},De),nc*Ee.compositeTextSizes[1].evaluate(O,{},De)],(st[0]>ka||st[1]>ka)&&ln(`${d.layerIds[0]}: Value for "text-size" is >= 255. Reduce your "text-size".`)),d.addSymbols(d.text,ke,st,G,D,O,re,a,Z.lineStartIndex,Z.lineLength,we,De);for(let kt of ce)de[kt]=d.text.placedSymbolArray.length-1;return 4*ke.length}function OY(d){for(let a in d)return d[a];return null}function pwe(d,a,h,A){let C=d.compareText;if(a in C){let D=C[a];for(let O=D.length-1;O>=0;O--)if(A.dist(D[O])>4;if(C!==1)throw new Error(`Got v${C} data when expected v1.`);let D=FY[15&A];if(!D)throw new Error("Unrecognized array type.");let[O]=new Uint16Array(a,2,1),[G]=new Uint32Array(a,4,1);return new G5(G,O,D,a)}constructor(a,h=64,A=Float64Array,C){if(isNaN(a)||a<0)throw new Error(`Unpexpected numItems value: ${a}.`);this.numItems=+a,this.nodeSize=Math.min(Math.max(+h,2),65535),this.ArrayType=A,this.IndexArrayType=a<65536?Uint16Array:Uint32Array;let D=FY.indexOf(this.ArrayType),O=2*a*this.ArrayType.BYTES_PER_ELEMENT,G=a*this.IndexArrayType.BYTES_PER_ELEMENT,Z=(8-G%8)%8;if(D<0)throw new Error(`Unexpected typed array class: ${A}.`);C&&C instanceof ArrayBuffer?(this.data=C,this.ids=new this.IndexArrayType(this.data,8,a),this.coords=new this.ArrayType(this.data,8+G+Z,2*a),this._pos=2*a,this._finished=!0):(this.data=new ArrayBuffer(8+O+G+Z),this.ids=new this.IndexArrayType(this.data,8,a),this.coords=new this.ArrayType(this.data,8+G+Z,2*a),this._pos=0,this._finished=!1,new Uint8Array(this.data,0,2).set([219,16+D]),new Uint16Array(this.data,2,1)[0]=h,new Uint32Array(this.data,4,1)[0]=a)}add(a,h){let A=this._pos>>1;return this.ids[A]=A,this.coords[this._pos++]=a,this.coords[this._pos++]=h,A}finish(){let a=this._pos>>1;if(a!==this.numItems)throw new Error(`Added ${a} items when expected ${this.numItems}.`);return H5(this.ids,this.coords,this.nodeSize,0,this.numItems-1,0),this._finished=!0,this}range(a,h,A,C){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");let{ids:D,coords:O,nodeSize:G}=this,Z=[0,D.length-1,0],re=[];for(;Z.length;){let ce=Z.pop()||0,de=Z.pop()||0,we=Z.pop()||0;if(de-we<=G){for(let $e=we;$e<=de;$e++){let st=O[2*$e],kt=O[2*$e+1];st>=a&&st<=A&&kt>=h&&kt<=C&&re.push(D[$e])}continue}let Ee=we+de>>1,De=O[2*Ee],ke=O[2*Ee+1];De>=a&&De<=A&&ke>=h&&ke<=C&&re.push(D[Ee]),(ce===0?a<=De:h<=ke)&&(Z.push(we),Z.push(Ee-1),Z.push(1-ce)),(ce===0?A>=De:C>=ke)&&(Z.push(Ee+1),Z.push(de),Z.push(1-ce))}return re}within(a,h,A){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");let{ids:C,coords:D,nodeSize:O}=this,G=[0,C.length-1,0],Z=[],re=A*A;for(;G.length;){let ce=G.pop()||0,de=G.pop()||0,we=G.pop()||0;if(de-we<=O){for(let $e=we;$e<=de;$e++)NY(D[2*$e],D[2*$e+1],a,h)<=re&&Z.push(C[$e]);continue}let Ee=we+de>>1,De=D[2*Ee],ke=D[2*Ee+1];NY(De,ke,a,h)<=re&&Z.push(C[Ee]),(ce===0?a-A<=De:h-A<=ke)&&(G.push(we),G.push(Ee-1),G.push(1-ce)),(ce===0?a+A>=De:h+A>=ke)&&(G.push(Ee+1),G.push(de),G.push(1-ce))}return Z}}function H5(d,a,h,A,C,D){if(C-A<=h)return;let O=A+C>>1;kY(d,a,O,A,C,D),H5(d,a,h,A,O-1,1-D),H5(d,a,h,O+1,C,1-D)}function kY(d,a,h,A,C,D){for(;C>A;){if(C-A>600){let re=C-A+1,ce=h-A+1,de=Math.log(re),we=.5*Math.exp(2*de/3),Ee=.5*Math.sqrt(de*we*(re-we)/re)*(ce-re/2<0?-1:1);kY(d,a,h,Math.max(A,Math.floor(h-ce*we/re+Ee)),Math.min(C,Math.floor(h+(re-ce)*we/re+Ee)),D)}let O=a[2*h+D],G=A,Z=C;for(eM(d,a,A,h),a[2*C+D]>O&&eM(d,a,A,C);GO;)Z--}a[2*A+D]===O?eM(d,a,A,Z):(Z++,eM(d,a,Z,C)),Z<=h&&(A=Z+1),h<=Z&&(C=Z-1)}}function eM(d,a,h,A){W5(d,h,A),W5(a,2*h,2*A),W5(a,2*h+1,2*A+1)}function W5(d,a,h){let A=d[a];d[a]=d[h],d[h]=A}function NY(d,a,h,A){let C=d-h,D=a-A;return C*C+D*D}var q5;n.cI=void 0,(q5=n.cI||(n.cI={})).create="create",q5.load="load",q5.fullLoad="fullLoad";let VL=null,tM=[],$5=1e3/60,X5="loadTime",Z5="fullLoadTime",mwe={mark(d){performance.mark(d)},frame(d){let a=d;VL!=null&&tM.push(a-VL),VL=a},clearMetrics(){VL=null,tM=[],performance.clearMeasures(X5),performance.clearMeasures(Z5);for(let d in n.cI)performance.clearMarks(n.cI[d])},getPerformanceMetrics(){performance.measure(X5,n.cI.create,n.cI.load),performance.measure(Z5,n.cI.create,n.cI.fullLoad);let d=performance.getEntriesByName(X5)[0].duration,a=performance.getEntriesByName(Z5)[0].duration,h=tM.length,A=1/(tM.reduce((D,O)=>D+O,0)/h/1e3),C=tM.filter(D=>D>$5).reduce((D,O)=>D+(O-$5)/$5,0);return{loadTime:d,fullLoadTime:a,fps:A,percentDroppedFrames:C/(h+C)*100,totalFrames:h}}};n.$=U,n.A=he,n.B=ct,n.C=ie,n.D=en,n.E=go,n.F=function([d,a,h]){return a+=90,a*=Math.PI/180,h*=Math.PI/180,{x:d*Math.cos(a)*Math.sin(h),y:d*Math.sin(a)*Math.sin(h),z:d*Math.cos(h)}},n.G=Fr,n.H=Hi,n.I=Jo,n.J=ge,n.K=function(d){if(Go==null){let a=d.navigator?d.navigator.userAgent:null;Go=!!d.safari||!(!a||!(/\b(iPad|iPhone|iPod)\b/.test(a)||a.match("Safari")&&!a.match("Chrome")))}return Go},n.L=class{constructor(d,a){this.target=d,this.mapId=a,this.resolveRejects={},this.tasks={},this.taskQueue=[],this.abortControllers={},this.messageHandlers={},this.invoker=new L_(()=>this.process()),this.subscription=so(this.target,"message",h=>this.receive(h),!1),this.globalScope=Co(self)?d:window}registerMessageHandler(d,a){this.messageHandlers[d]=a}unregisterMessageHandler(d){delete this.messageHandlers[d]}sendAsync(d,a){return new Promise((h,A)=>{let C=Math.round(1e18*Math.random()).toString(36).substring(0,10),D=a?so(a.signal,"abort",()=>{D?.unsubscribe(),delete this.resolveRejects[C];let Z={id:C,type:"",origin:location.origin,targetMapId:d.targetMapId,sourceMapId:this.mapId};this.target.postMessage(Z)},av):null;this.resolveRejects[C]={resolve:Z=>{D?.unsubscribe(),h(Z)},reject:Z=>{D?.unsubscribe(),A(Z)}};let O=[],G=Object.assign(Object.assign({},d),{id:C,sourceMapId:this.mapId,origin:location.origin,data:Zt(d.data,O)});this.target.postMessage(G,{transfer:O})})}receive(d){let a=d.data,h=a.id;if(!(a.origin!=="file://"&&location.origin!=="file://"&&a.origin!=="resource://android"&&location.origin!=="resource://android"&&a.origin!==location.origin||a.targetMapId&&this.mapId!==a.targetMapId)){if(a.type===""){delete this.tasks[h];let A=this.abortControllers[h];return delete this.abortControllers[h],void(A&&A.abort())}if(Co(self)||a.mustQueue)return this.tasks[h]=a,this.taskQueue.push(h),void this.invoker.trigger();this.processTask(h,a)}}process(){if(this.taskQueue.length===0)return;let d=this.taskQueue.shift(),a=this.tasks[d];delete this.tasks[d],this.taskQueue.length>0&&this.invoker.trigger(),a&&this.processTask(d,a)}processTask(d,a){return o(this,void 0,void 0,function*(){if(a.type===""){let C=this.resolveRejects[d];return delete this.resolveRejects[d],C?void(a.error?C.reject(ir(a.error)):C.resolve(ir(a.data))):void 0}if(!this.messageHandlers[a.type])return void this.completeTask(d,new Error(`Could not find a registered handler for ${a.type}, map ID: ${this.mapId}, available handlers: ${Object.keys(this.messageHandlers).join(", ")}`));let h=ir(a.data),A=new AbortController;this.abortControllers[d]=A;try{let C=yield this.messageHandlers[a.type](a.sourceMapId,h,A);this.completeTask(d,null,C)}catch(C){this.completeTask(d,C)}})}completeTask(d,a,h){let A=[];delete this.abortControllers[d];let C={id:d,type:"",sourceMapId:this.mapId,origin:location.origin,error:a?Zt(a):null,data:Zt(h,A)};this.target.postMessage(C,{transfer:A})}remove(){this.invoker.remove(),this.subscription.unsubscribe()}},n.M=fr,n.N=function(){var d=new he(16);return he!=Float32Array&&(d[1]=0,d[2]=0,d[3]=0,d[4]=0,d[6]=0,d[7]=0,d[8]=0,d[9]=0,d[11]=0,d[12]=0,d[13]=0,d[14]=0),d[0]=1,d[5]=1,d[10]=1,d[15]=1,d},n.O=function(d,a,h){var A,C,D,O,G,Z,re,ce,de,we,Ee,De,ke=h[0],$e=h[1],st=h[2];return a===d?(d[12]=a[0]*ke+a[4]*$e+a[8]*st+a[12],d[13]=a[1]*ke+a[5]*$e+a[9]*st+a[13],d[14]=a[2]*ke+a[6]*$e+a[10]*st+a[14],d[15]=a[3]*ke+a[7]*$e+a[11]*st+a[15]):(C=a[1],D=a[2],O=a[3],G=a[4],Z=a[5],re=a[6],ce=a[7],de=a[8],we=a[9],Ee=a[10],De=a[11],d[0]=A=a[0],d[1]=C,d[2]=D,d[3]=O,d[4]=G,d[5]=Z,d[6]=re,d[7]=ce,d[8]=de,d[9]=we,d[10]=Ee,d[11]=De,d[12]=A*ke+G*$e+de*st+a[12],d[13]=C*ke+Z*$e+we*st+a[13],d[14]=D*ke+re*$e+Ee*st+a[14],d[15]=O*ke+ce*$e+De*st+a[15]),d},n.P=s,n.Q=function(d,a,h){var A=h[0],C=h[1],D=h[2];return d[0]=a[0]*A,d[1]=a[1]*A,d[2]=a[2]*A,d[3]=a[3]*A,d[4]=a[4]*C,d[5]=a[5]*C,d[6]=a[6]*C,d[7]=a[7]*C,d[8]=a[8]*D,d[9]=a[9]*D,d[10]=a[10]*D,d[11]=a[11]*D,d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15],d},n.R=rh,n.S=function(d,a,h){var A=a[0],C=a[1],D=a[2],O=a[3],G=a[4],Z=a[5],re=a[6],ce=a[7],de=a[8],we=a[9],Ee=a[10],De=a[11],ke=a[12],$e=a[13],st=a[14],kt=a[15],pt=h[0],_t=h[1],Gt=h[2],Qt=h[3];return d[0]=pt*A+_t*G+Gt*de+Qt*ke,d[1]=pt*C+_t*Z+Gt*we+Qt*$e,d[2]=pt*D+_t*re+Gt*Ee+Qt*st,d[3]=pt*O+_t*ce+Gt*De+Qt*kt,d[4]=(pt=h[4])*A+(_t=h[5])*G+(Gt=h[6])*de+(Qt=h[7])*ke,d[5]=pt*C+_t*Z+Gt*we+Qt*$e,d[6]=pt*D+_t*re+Gt*Ee+Qt*st,d[7]=pt*O+_t*ce+Gt*De+Qt*kt,d[8]=(pt=h[8])*A+(_t=h[9])*G+(Gt=h[10])*de+(Qt=h[11])*ke,d[9]=pt*C+_t*Z+Gt*we+Qt*$e,d[10]=pt*D+_t*re+Gt*Ee+Qt*st,d[11]=pt*O+_t*ce+Gt*De+Qt*kt,d[12]=(pt=h[12])*A+(_t=h[13])*G+(Gt=h[14])*de+(Qt=h[15])*ke,d[13]=pt*C+_t*Z+Gt*we+Qt*$e,d[14]=pt*D+_t*re+Gt*Ee+Qt*st,d[15]=pt*O+_t*ce+Gt*De+Qt*kt,d},n.T=IC,n.U=function(d,a){let h={};for(let A=0;A0||((C=D.addOrUpdateProperties)===null||C===void 0?void 0:C.length)>0;if((G||Z)&&(O=Object.assign({},O),d.set(D.id,O),G&&(O.geometry=D.newGeometry),Z)){if(O.properties=D.removeAllProperties?{}:Object.assign({},O.properties||{}),D.removeProperties)for(let re of D.removeProperties)delete O.properties[re];if(D.addOrUpdateProperties)for(let{key:re,value:ce}of D.addOrUpdateProperties)O.properties[re]=ce}}},n.aB=function(d,{uSize:a,uSizeT:h},{lowerSize:A,upperSize:C}){return d.kind==="source"?A/nc:d.kind==="composite"?Fr.number(A/nc,C/nc,h):a},n.aC=function(d,a){var h=a[0],A=a[1],C=a[2],D=a[3],O=a[4],G=a[5],Z=a[6],re=a[7],ce=a[8],de=a[9],we=a[10],Ee=a[11],De=a[12],ke=a[13],$e=a[14],st=a[15],kt=h*G-A*O,pt=h*Z-C*O,_t=h*re-D*O,Gt=A*Z-C*G,Qt=A*re-D*G,Mr=C*re-D*Z,oi=ce*ke-de*De,Kr=ce*$e-we*De,gi=ce*st-Ee*De,ji=de*$e-we*ke,vi=de*st-Ee*ke,Qr=we*st-Ee*$e,jr=kt*Qr-pt*vi+_t*ji+Gt*gi-Qt*Kr+Mr*oi;return jr?(d[0]=(G*Qr-Z*vi+re*ji)*(jr=1/jr),d[1]=(C*vi-A*Qr-D*ji)*jr,d[2]=(ke*Mr-$e*Qt+st*Gt)*jr,d[3]=(we*Qt-de*Mr-Ee*Gt)*jr,d[4]=(Z*gi-O*Qr-re*Kr)*jr,d[5]=(h*Qr-C*gi+D*Kr)*jr,d[6]=($e*_t-De*Mr-st*pt)*jr,d[7]=(ce*Mr-we*_t+Ee*pt)*jr,d[8]=(O*vi-G*gi+re*oi)*jr,d[9]=(A*gi-h*vi-D*oi)*jr,d[10]=(De*Qt-ke*_t+st*kt)*jr,d[11]=(de*_t-ce*Qt-Ee*kt)*jr,d[12]=(G*Kr-O*ji-Z*oi)*jr,d[13]=(h*ji-A*Kr+C*oi)*jr,d[14]=(ke*pt-De*Gt-$e*kt)*jr,d[15]=(ce*Gt-de*pt+we*kt)*jr,d):null},n.aD=sr,n.aE=function(d){var a=d[0],h=d[1];return Math.sqrt(a*a+h*h)},n.aF=function(d){return d[0]=0,d[1]=0,d},n.aG=function(d,a,h){return d[0]=a[0]*h,d[1]=a[1]*h,d},n.aH=Bs,n.aI=wt,n.aJ=function(d,a,h,A){let C=a.y-d.y,D=a.x-d.x,O=A.y-h.y,G=A.x-h.x,Z=O*D-G*C;if(Z===0)return null;let re=(G*(d.y-h.y)-O*(d.x-h.x))/Z;return new s(d.x+re*D,d.y+re*C)},n.aK=bY,n.aL=Ro,n.aM=function(d){let a=1/0,h=1/0,A=-1/0,C=-1/0;for(let D of d)a=Math.min(a,D.x),h=Math.min(h,D.y),A=Math.max(A,D.x),C=Math.max(C,D.y);return[a,h,A,C]},n.aN=R,n.aO=mi,n.aP=function(d,a,h,A,C=!1){if(!h[0]&&!h[1])return[0,0];let D=C?A==="map"?-d.bearingInRadians:0:A==="viewport"?d.bearingInRadians:0;if(D){let O=Math.sin(D),G=Math.cos(D);h=[h[0]*G-h[1]*O,h[0]*O+h[1]*G]}return[C?h[0]:mi(a,h[0],d.zoom),C?h[1]:mi(a,h[1],d.zoom)]},n.aR=hs,n.aS=j5,n.aT=js,n.aU=G5,n.aV=ia,n.aW=c2,n.aX=Ie,n.aY=Ai,n.aZ=br,n.a_=ws,n.aa=JC,n.ab=Fp,n.ac=pb,n.ad=25,n.ae=cv,n.af=d=>{let a=window.document.createElement("video");return a.muted=!0,new Promise(h=>{a.onloadstart=()=>{h(a)};for(let A of d){let C=window.document.createElement("source");Jr(A)||(a.crossOrigin="Anonymous"),C.src=A,a.appendChild(C)}})},n.ag=Ct,n.ah=function(){return nt++},n.ai=l,n.aj=dl,n.ak=Pc,n.al=Pi,n.am=sb,n.an=function(d){let a={};if(d.replace(/(?:^|(?:\s*\,\s*))([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)(?:\=(?:([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)|(?:\"((?:[^"\\]|\\.)*)\")))?/g,(h,A,C,D)=>{let O=C||D;return a[A]=!O||O.toLowerCase(),""}),a["max-age"]){let h=parseInt(a["max-age"],10);isNaN(h)?delete a["max-age"]:a["max-age"]=h}return a},n.ao=ii,n.ap=85.051129,n.aq=ps,n.ar=function(d){return Math.pow(2,d)},n.as=me,n.at=ib,n.au=function(d){return Math.log(d)/Math.LN2},n.av=function(d){var a=d[0],h=d[1];return a*a+h*h},n.aw=function(d){if(!d.length)return new Set;let a=Math.max(...d.map(Z=>Z.canonical.z)),h=1/0,A=-1/0,C=1/0,D=-1/0,O=[];for(let Z of d){let{x:re,y:ce,z:de}=Z.canonical,we=Math.pow(2,a-de),Ee=re*we,De=ce*we;O.push({id:Z,x:Ee,y:De}),EeA&&(A=Ee),DeD&&(D=De)}let G=new Set;for(let Z of O)Z.x!==h&&Z.x!==A&&Z.y!==C&&Z.y!==D||G.add(Z.id);return G},n.ax=function(d,a){let h=Math.abs(2*d.wrap)-+(d.wrap<0),A=Math.abs(2*a.wrap)-+(a.wrap<0);return d.overscaledZ-a.overscaledZ||A-h||a.canonical.y-d.canonical.y||a.canonical.x-d.canonical.x},n.ay=class{constructor(d,a){this.max=d,this.onRemove=a,this.reset()}reset(){for(let d in this.data)for(let a of this.data[d])a.timeout&&clearTimeout(a.timeout),this.onRemove(a.value);return this.data={},this.order=[],this}add(d,a,h){let A=d.wrapped().key;this.data[A]===void 0&&(this.data[A]=[]);let C={value:a,timeout:void 0};if(h!==void 0&&(C.timeout=setTimeout(()=>{this.remove(d,C)},h)),this.data[A].push(C),this.order.push(A),this.order.length>this.max){let D=this._getAndRemoveByKey(this.order[0]);D&&this.onRemove(D)}return this}has(d){return d.wrapped().key in this.data}getAndRemove(d){return this.has(d)?this._getAndRemoveByKey(d.wrapped().key):null}_getAndRemoveByKey(d){let a=this.data[d].shift();return a.timeout&&clearTimeout(a.timeout),this.data[d].length===0&&delete this.data[d],this.order.splice(this.order.indexOf(d),1),a.value}getByKey(d){let a=this.data[d];return a?a[0].value:null}get(d){return this.has(d)?this.data[d.wrapped().key][0].value:null}remove(d,a){if(!this.has(d))return this;let h=d.wrapped().key,A=a===void 0?0:this.data[h].indexOf(a),C=this.data[h][A];return this.data[h].splice(A,1),C.timeout&&clearTimeout(C.timeout),this.data[h].length===0&&delete this.data[h],this.onRemove(C.value),this.order.splice(this.order.indexOf(h),1),this}setMaxSize(d){for(this.max=d;this.order.length>this.max;){let a=this._getAndRemoveByKey(this.order[0]);a&&this.onRemove(a)}return this}filter(d){let a=[];for(let h in this.data)for(let A of this.data[h])d(A.value)||a.push(A);for(let h of a)this.remove(h.value.tileID,h)}},n.az=function(d,a){let h=0,A=0;if(d.kind==="constant")A=d.layoutSize;else if(d.kind!=="source"){let{interpolationType:C,minZoom:D,maxZoom:O}=d,G=C?ii(Ut.interpolationFactor(C,a,D,O),0,1):0;d.kind==="camera"?A=Fr.number(d.minSize,d.maxSize,G):h=G}return{uSizeT:h,uSize:A}},n.b=is,n.b$=class extends x{constructor(d,a){super(d,a),this.current=0}set(d){this.current!==d&&(this.current=d,this.gl.uniform1i(this.location,d))}},n.b0=We,n.b1=He,n.b2=function(d){var a=new he(3);return a[0]=d[0],a[1]=d[1],a[2]=d[2],a},n.b3=function(d,a,h){return d[0]=a[0]-h[0],d[1]=a[1]-h[1],d[2]=a[2]-h[2],d},n.b4=function(d,a){var h=a[0],A=a[1],C=a[2],D=h*h+A*A+C*C;return D>0&&(D=1/Math.sqrt(D)),d[0]=a[0]*D,d[1]=a[1]*D,d[2]=a[2]*D,d},n.b5=Se,n.b6=function(d,a){return d[0]*a[0]+d[1]*a[1]+d[2]*a[2]},n.b7=function(d,a,h){return d[0]=a[0]*h[0],d[1]=a[1]*h[1],d[2]=a[2]*h[2],d[3]=a[3]*h[3],d},n.b8=Oe,n.b9=function(d,a,h){let A=a[0]*h[0]+a[1]*h[1]+a[2]*h[2];return A===0?null:(-(d[0]*h[0]+d[1]*h[1]+d[2]*h[2])-h[3])/A},n.bA=function(){return new Float64Array(3)},n.bB=function(d,a,h,A){return d[0]=a[0]+h[0]*A,d[1]=a[1]+h[1]*A,d[2]=a[2]+h[2]*A,d},n.bC=xt,n.bD=function(d,a,h){var A=h[0],C=h[1],D=h[2],O=h[3],G=a[0],Z=a[1],re=a[2],ce=C*re-D*Z,de=D*G-A*re,we=A*Z-C*G;return d[0]=G+O*(ce+=ce)+C*(we+=we)-D*(de+=de),d[1]=Z+O*de+D*ce-A*we,d[2]=re+O*we+A*de-C*ce,d},n.bE=function(d,a,h){let A=function(Z){var re=Z[3],ce=Z[4],de=Z[5],we=Z[6],Ee=Z[7],De=Z[8];return Z[0]*(De*ce-de*Ee)+Z[1]*(-De*re+de*we)+Z[2]*(Ee*re-ce*we)}([d[0],d[1],d[2],a[0],a[1],a[2],h[0],h[1],h[2]]);if(A===0)return null;let C=Se([],[a[0],a[1],a[2]],[h[0],h[1],h[2]]),D=Se([],[h[0],h[1],h[2]],[d[0],d[1],d[2]]),O=Se([],[d[0],d[1],d[2]],[a[0],a[1],a[2]]),G=We([],C,-d[3]);return He(G,G,We([],D,-a[3])),He(G,G,We([],O,-h[3])),We(G,G,1/A),G},n.bF=_m,n.bG=function(){return new Float64Array(4)},n.bH=function(d,a,h,A){var C=[],D=[];return C[0]=a[0]-h[0],C[1]=a[1]-h[1],C[2]=a[2]-h[2],D[0]=C[0]*Math.cos(A)-C[1]*Math.sin(A),D[1]=C[0]*Math.sin(A)+C[1]*Math.cos(A),D[2]=C[2],d[0]=D[0]+h[0],d[1]=D[1]+h[1],d[2]=D[2]+h[2],d},n.bI=function(d,a,h,A){var C=[],D=[];return C[0]=a[0]-h[0],C[1]=a[1]-h[1],C[2]=a[2]-h[2],D[0]=C[0],D[1]=C[1]*Math.cos(A)-C[2]*Math.sin(A),D[2]=C[1]*Math.sin(A)+C[2]*Math.cos(A),d[0]=D[0]+h[0],d[1]=D[1]+h[1],d[2]=D[2]+h[2],d},n.bJ=function(d,a,h,A){var C=[],D=[];return C[0]=a[0]-h[0],C[1]=a[1]-h[1],C[2]=a[2]-h[2],D[0]=C[2]*Math.sin(A)+C[0]*Math.cos(A),D[1]=C[1],D[2]=C[2]*Math.cos(A)-C[0]*Math.sin(A),d[0]=D[0]+h[0],d[1]=D[1]+h[1],d[2]=D[2]+h[2],d},n.bK=function(d,a,h){var A=Math.sin(h),C=Math.cos(h),D=a[0],O=a[1],G=a[2],Z=a[3],re=a[8],ce=a[9],de=a[10],we=a[11];return a!==d&&(d[4]=a[4],d[5]=a[5],d[6]=a[6],d[7]=a[7],d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]),d[0]=D*C-re*A,d[1]=O*C-ce*A,d[2]=G*C-de*A,d[3]=Z*C-we*A,d[8]=D*A+re*C,d[9]=O*A+ce*C,d[10]=G*A+de*C,d[11]=Z*A+we*C,d},n.bL=function(d,a){let h=bi(d,360),A=bi(a,360),C=A-h,D=A>h?C-360:C+360;return Math.abs(C)0?O:-O},n.bO=function(d,a){let h=bi(d,2*Math.PI),A=bi(a,2*Math.PI);return Math.min(Math.abs(h-A),Math.abs(h-A+2*Math.PI),Math.abs(h-A-2*Math.PI))},n.bP=function(){let d={},a=wr.$version;for(let h in wr.$root){let A=wr.$root[h];if(A.required){let C=null;C=h==="version"?a:A.type==="array"?[]:{},C!=null&&(d[h]=C)}}return d},n.bQ=xr,n.bR=dr,n.bS=function d(a,h){if(Array.isArray(a)){if(!Array.isArray(h)||a.length!==h.length)return!1;for(let A=0;Ad.type==="raster",n.bW=Xr,n.bX=function(d,a){if(!d)return[{command:"setStyle",args:[a]}];let h=[];try{if(!Be(d.version,a.version))return[{command:"setStyle",args:[a]}];Be(d.center,a.center)||h.push({command:"setCenter",args:[a.center]}),Be(d.state,a.state)||h.push({command:"setGlobalState",args:[a.state]}),Be(d.centerAltitude,a.centerAltitude)||h.push({command:"setCenterAltitude",args:[a.centerAltitude]}),Be(d.zoom,a.zoom)||h.push({command:"setZoom",args:[a.zoom]}),Be(d.bearing,a.bearing)||h.push({command:"setBearing",args:[a.bearing]}),Be(d.pitch,a.pitch)||h.push({command:"setPitch",args:[a.pitch]}),Be(d.roll,a.roll)||h.push({command:"setRoll",args:[a.roll]}),Be(d.sprite,a.sprite)||h.push({command:"setSprite",args:[a.sprite]}),Be(d.glyphs,a.glyphs)||h.push({command:"setGlyphs",args:[a.glyphs]}),Be(d.transition,a.transition)||h.push({command:"setTransition",args:[a.transition]}),Be(d.light,a.light)||h.push({command:"setLight",args:[a.light]}),Be(d.terrain,a.terrain)||h.push({command:"setTerrain",args:[a.terrain]}),Be(d.sky,a.sky)||h.push({command:"setSky",args:[a.sky]}),Be(d.projection,a.projection)||h.push({command:"setProjection",args:[a.projection]});let A={},C=[];(function(O,G,Z,re){let ce;for(ce in G=G||{},O=O||{})Object.prototype.hasOwnProperty.call(O,ce)&&(Object.prototype.hasOwnProperty.call(G,ce)||mt(ce,Z,re));for(ce in G)Object.prototype.hasOwnProperty.call(G,ce)&&(Object.prototype.hasOwnProperty.call(O,ce)?Be(O[ce],G[ce])||(O[ce].type==="geojson"&&G[ce].type==="geojson"&&$t(O,G,ce)?Xe(Z,{command:"setGeoJSONSourceData",args:[ce,G[ce].data]}):Ke(ce,G,Z,re)):it(ce,G,Z))})(d.sources,a.sources,C,A);let D=[];d.layers&&d.layers.forEach(O=>{"source"in O&&A[O.source]?h.push({command:"removeLayer",args:[O.id]}):D.push(O)}),h=h.concat(C),function(O,G,Z){G=G||[];let re=(O=O||[]).map(ht),ce=G.map(ht),de=O.reduce(Ot,{}),we=G.reduce(Ot,{}),Ee=re.slice(),De=Object.create(null),ke,$e,st,kt,pt;for(let _t=0,Gt=0;_tee?(C=Math.acos(D),O=Math.sin(C),G=Math.sin((1-A)*C)/O,Z=Math.sin(A*C)/O):(G=1-A,Z=A),d[0]=G*re+Z*Ee,d[1]=G*ce+Z*De,d[2]=G*de+Z*ke,d[3]=G*we+Z*$e,d},n.bo=function(d){let a=new Float64Array(9);(function(D,O){var G=O[0],Z=O[1],re=O[2],ce=O[3],de=G+G,we=Z+Z,Ee=re+re,De=G*de,ke=Z*de,$e=Z*we,st=re*de,kt=re*we,pt=re*Ee,_t=ce*de,Gt=ce*we,Qt=ce*Ee;D[0]=1-$e-pt,D[3]=ke-Qt,D[6]=st+Gt,D[1]=ke+Qt,D[4]=1-De-pt,D[7]=kt-_t,D[2]=st-Gt,D[5]=kt+_t,D[8]=1-De-$e})(a,d);let h=ws(-Math.asin(ii(a[2],-1,1))),A,C;return Math.hypot(a[5],a[8])<.001?(A=0,C=-ws(Math.atan2(a[3],a[4]))):(A=ws(a[5]===0&&a[8]===0?0:Math.atan2(a[5],a[8])),C=ws(a[1]===0&&a[0]===0?0:Math.atan2(a[1],a[0]))),{roll:A,pitch:h+90,bearing:C}},n.bp=function(d,a){return d.roll==a.roll&&d.pitch==a.pitch&&d.bearing==a.bearing},n.bq=Mo,n.br=E,n.bs=tv,n.bt=Xx,n.bu=E_,n.bv=un,n.bw=Yr,n.bx=vl,n.by=function(d,a,h,A,C){return un(A,C,ii((d-a)/(h-a),0,1))},n.bz=bi,n.c=Ht,n.c$=class{constructor(d){this._marks={start:[d.url,"start"].join("#"),end:[d.url,"end"].join("#"),measure:d.url.toString()},performance.mark(this._marks.start)}finish(){performance.mark(this._marks.end);let d=performance.getEntriesByName(this._marks.measure);return d.length===0&&(performance.measure(this._marks.measure,this._marks.start,this._marks.end),d=performance.getEntriesByName(this._marks.measure),performance.clearMarks(this._marks.start),performance.clearMarks(this._marks.end),performance.clearMeasures(this._marks.measure)),d}},n.c0=N,n.c1=class extends x{constructor(d,a){super(d,a),this.current=H}set(d){if(d[12]!==this.current[12]||d[0]!==this.current[0])return this.current=d,void this.gl.uniformMatrix4fv(this.location,!1,d);for(let a=1;a<16;a++)if(d[a]!==this.current[a]){this.current=d,this.gl.uniformMatrix4fv(this.location,!1,d);break}}},n.c2=B,n.c3=class extends x{constructor(d,a){super(d,a),this.current=[0,0,0]}set(d){d[0]===this.current[0]&&d[1]===this.current[1]&&d[2]===this.current[2]||(this.current=d,this.gl.uniform3f(this.location,d[0],d[1],d[2]))}},n.c4=class extends x{constructor(d,a){super(d,a),this.current=[0,0]}set(d){d[0]===this.current[0]&&d[1]===this.current[1]||(this.current=d,this.gl.uniform2f(this.location,d[0],d[1]))}},n.c5=xe,n.c6=function(d,a){var h=Math.sin(a),A=Math.cos(a);return d[0]=A,d[1]=h,d[2]=0,d[3]=-h,d[4]=A,d[5]=0,d[6]=0,d[7]=0,d[8]=1,d},n.c7=function(d,a,h){var A=a[0],C=a[1],D=a[2];return d[0]=A*h[0]+C*h[3]+D*h[6],d[1]=A*h[1]+C*h[4]+D*h[7],d[2]=A*h[2]+C*h[5]+D*h[8],d},n.c8=function(d,a,h,A,C,D,O){var G=1/(a-h),Z=1/(A-C),re=1/(D-O);return d[0]=-2*G,d[1]=0,d[2]=0,d[3]=0,d[4]=0,d[5]=-2*Z,d[6]=0,d[7]=0,d[8]=0,d[9]=0,d[10]=2*re,d[11]=0,d[12]=(a+h)*G,d[13]=(C+A)*Z,d[14]=(O+D)*re,d[15]=1,d},n.c9=class extends x{constructor(d,a){super(d,a),this.current=new Array}set(d){if(d!=this.current){this.current=d;let a=new Float32Array(4*d.length);for(let h=0;h25||A<0||A>=1||h<0||h>=1)},n.cF=function(d,a){return d[0]=a[0],d[1]=0,d[2]=0,d[3]=0,d[4]=0,d[5]=a[1],d[6]=0,d[7]=0,d[8]=0,d[9]=0,d[10]=a[2],d[11]=0,d[12]=0,d[13]=0,d[14]=0,d[15]=1,d},n.cG=class extends ef{},n.cH=mwe,n.cJ=nr,n.cK=function(d,a){Ht.REGISTERED_PROTOCOLS[d]=a},n.cL=function(d){delete Ht.REGISTERED_PROTOCOLS[d]},n.cM=function(d,a){let h={};for(let C=0;CQr*R)}let Kr=O?"center":h.get("text-justify").evaluate(re,{},d.canonical),gi=h.get("symbol-placement")==="point"?h.get("text-max-width").evaluate(re,{},d.canonical)*R:1/0,ji=()=>{d.bucket.allowVerticalPlacement&&ai(_t)&&(De.vertical=Mn(ke,d.glyphMap,d.glyphPositions,d.imagePositions,ce,gi,D,Mr,"left",Qt,st,n.aA.vertical,!0,we,de))};if(!O&&oi){let vi=new Set;if(Kr==="auto")for(let jr=0;jr=this.maxEntries){let h=this.map.keys().next().value;this.map.delete(h)}this.map.set(d,a)}clear(){this.map.clear()}},n.cY=Yx,n.cZ=tr,n.c_=_Y,n.ca=class extends x{constructor(d,a){super(d,a),this.current=new Array}set(d){if(d!=this.current){this.current=d;let a=new Float32Array(d);this.gl.uniform1fv(this.location,a)}}},n.cb=class extends Tf{},n.cc=p,n.cd=class extends Fu{},n.ce=J0,n.cf=function(d){return d<=1?1:Math.pow(2,Math.ceil(Math.log(d)/Math.LN2))},n.cg=Qf,n.ch=function(d,a,h){var A=a[0],C=a[1],D=a[2],O=h[3]*A+h[7]*C+h[11]*D+h[15];return d[0]=(h[0]*A+h[4]*C+h[8]*D+h[12])/(O=O||1),d[1]=(h[1]*A+h[5]*C+h[9]*D+h[13])/O,d[2]=(h[2]*A+h[6]*C+h[10]*D+h[14])/O,d},n.ci=class extends Ul{},n.cj=class extends Q0{},n.ck=function(d,a){return d[0]===a[0]&&d[1]===a[1]&&d[2]===a[2]&&d[3]===a[3]&&d[4]===a[4]&&d[5]===a[5]&&d[6]===a[6]&&d[7]===a[7]&&d[8]===a[8]&&d[9]===a[9]&&d[10]===a[10]&&d[11]===a[11]&&d[12]===a[12]&&d[13]===a[13]&&d[14]===a[14]&&d[15]===a[15]},n.cl=function(d,a){var h=d[0],A=d[1],C=d[2],D=d[3],O=d[4],G=d[5],Z=d[6],re=d[7],ce=d[8],de=d[9],we=d[10],Ee=d[11],De=d[12],ke=d[13],$e=d[14],st=d[15],kt=a[0],pt=a[1],_t=a[2],Gt=a[3],Qt=a[4],Mr=a[5],oi=a[6],Kr=a[7],gi=a[8],ji=a[9],vi=a[10],Qr=a[11],jr=a[12],dn=a[13],cn=a[14],vn=a[15];return Math.abs(h-kt)<=ee*Math.max(1,Math.abs(h),Math.abs(kt))&&Math.abs(A-pt)<=ee*Math.max(1,Math.abs(A),Math.abs(pt))&&Math.abs(C-_t)<=ee*Math.max(1,Math.abs(C),Math.abs(_t))&&Math.abs(D-Gt)<=ee*Math.max(1,Math.abs(D),Math.abs(Gt))&&Math.abs(O-Qt)<=ee*Math.max(1,Math.abs(O),Math.abs(Qt))&&Math.abs(G-Mr)<=ee*Math.max(1,Math.abs(G),Math.abs(Mr))&&Math.abs(Z-oi)<=ee*Math.max(1,Math.abs(Z),Math.abs(oi))&&Math.abs(re-Kr)<=ee*Math.max(1,Math.abs(re),Math.abs(Kr))&&Math.abs(ce-gi)<=ee*Math.max(1,Math.abs(ce),Math.abs(gi))&&Math.abs(de-ji)<=ee*Math.max(1,Math.abs(de),Math.abs(ji))&&Math.abs(we-vi)<=ee*Math.max(1,Math.abs(we),Math.abs(vi))&&Math.abs(Ee-Qr)<=ee*Math.max(1,Math.abs(Ee),Math.abs(Qr))&&Math.abs(De-jr)<=ee*Math.max(1,Math.abs(De),Math.abs(jr))&&Math.abs(ke-dn)<=ee*Math.max(1,Math.abs(ke),Math.abs(dn))&&Math.abs($e-cn)<=ee*Math.max(1,Math.abs($e),Math.abs(cn))&&Math.abs(st-vn)<=ee*Math.max(1,Math.abs(st),Math.abs(vn))},n.cm=function(d,a){return d[0]=a[0],d[1]=a[1],d[2]=a[2],d[3]=a[3],d[4]=a[4],d[5]=a[5],d[6]=a[6],d[7]=a[7],d[8]=a[8],d[9]=a[9],d[10]=a[10],d[11]=a[11],d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15],d},n.cn=d=>d.type==="symbol",n.co=d=>d.type==="circle",n.cp=d=>d.type==="heatmap",n.cq=d=>d.type==="line",n.cr=d=>d.type==="fill",n.cs=d=>d.type==="fill-extrusion",n.ct=d=>d.type==="hillshade",n.cu=d=>d.type==="color-relief",n.cv=d=>d.type==="background",n.cw=d=>d.type==="custom",n.cx=Xi,n.cy=function(d,a,h){let A=Dr(a.x-h.x,a.y-h.y),C=Dr(d.x-h.x,d.y-h.y),D=Math.atan2(A[0]*C[1]-A[1]*C[0],function(O,G){return O[0]*G[0]+O[1]*G[1]}(A,C));return ws(D)},n.cz=xi,n.d=Jr,n.d0=function(d,a,h,A,C){return o(this,void 0,void 0,function*(){if(K())try{return yield zs(d,a,h,A,C)}catch{}return function(D,O,G,Z,re){let ce=D.width,de=D.height;Ba&&ds||(Ba=new OffscreenCanvas(ce,de),ds=Ba.getContext("2d",{willReadFrequently:!0})),Ba.width=ce,Ba.height=de,ds.drawImage(D,0,0,ce,de);let we=ds.getImageData(O,G,Z,re);return ds.clearRect(0,0,ce,de),we.data}(d,a,h,A,C)})},n.d1=PC,n.d2=c,n.d3=class{constructor(d,a){this.layers={[JC]:this},this.name=JC,this.version=a?a.version:1,this.extent=a?a.extent:4096,this.length=d.length,this.features=d}feature(d){return new Q1e(this.features[d],this.extent)}},n.d4=$d,n.d5=$n,n.e=Ve,n.f=d=>o(void 0,void 0,void 0,function*(){if(d.byteLength===0)return createImageBitmap(new ImageData(1,1));let a=new Blob([new Uint8Array(d)],{type:"image/png"});try{return createImageBitmap(a)}catch(h){throw new Error(`Could not load image because of ${h.message}. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported.`)}}),n.g=yr,n.h=d=>new Promise((a,h)=>{let A=new Image;A.onload=()=>{a(A),URL.revokeObjectURL(A.src),A.onload=null,window.requestAnimationFrame(()=>{A.src=mo})},A.onerror=()=>h(new Error("Could not load image. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported."));let C=new Blob([new Uint8Array(d)],{type:"image/png"});A.src=d.byteLength?URL.createObjectURL(C):mo}),n.i=Co,n.j=(d,a)=>ti(Ve(d,{type:"json"}),a),n.k=bs,n.l=Wn,n.m=ti,n.n=(d,a)=>ti(Ve(d,{type:"arrayBuffer"}),a),n.o=function(d){return new tr(d).readFields(Ji,[])},n.p=pn,n.q=function(d){return/[\u02EA\u02EB\u1100-\u11FF\u2E80-\u2FDF\u3000-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFE10-\uFE1F\uFE30-\uFE4F\uFF00-\uFFEF]|\uD81B[\uDFE0-\uDFFF]|[\uD81C-\uD822\uD840-\uD868\uD86A-\uD86D\uD86F-\uD872\uD874-\uD879\uD880-\uD883\uD885-\uD88C][\uDC00-\uDFFF]|\uD823[\uDC00-\uDCD5\uDCFF-\uDD1E\uDD80-\uDDF2]|\uD82B[\uDFF0-\uDFFF]|\uD82C[\uDC00-\uDEFB]|\uD83C[\uDE00-\uDEFF]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEAD\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0\uDFF0-\uDFFF]|\uD87B[\uDC00-\uDE5D]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A\uDF50-\uDFFF]|\uD88D[\uDC00-\uDC79]/gim.test(String.fromCodePoint(d))},n.r=e0,n.s=so,n.t=Fa,n.u=wr,n.v=Y,n.w=ln,n.x=Ml,n.y=ae,n.z=eo}),t("worker",["./shared"],function(n){"use strict";class o{constructor(Fe,Ne){this.keyCache={},Fe&&this.replace(Fe,Ne)}replace(Fe,Ne){this._layerConfigs={},this._layers={},this.update(Fe,[],Ne)}update(Fe,Ne,rt){for(let Ht of Fe){this._layerConfigs[Ht.id]=Ht;let yr=this._layers[Ht.id]=n.bU(Ht,rt);yr._featureFilter=n.ak(yr.filter,rt),this.keyCache[Ht.id]&&delete this.keyCache[Ht.id]}for(let Ht of Ne)delete this.keyCache[Ht],delete this._layerConfigs[Ht],delete this._layers[Ht];this.familiesBySource={};let Vt=n.cM(Object.values(this._layerConfigs),this.keyCache);for(let Ht of Vt){let yr=Ht.map(Wi=>this._layers[Wi.id]),fr=yr[0];if(fr.visibility==="none")continue;let nr=fr.source||"",xr=this.familiesBySource[nr];xr||(xr=this.familiesBySource[nr]={});let ti=fr.sourceLayer||n.aa,Jr=xr[ti];Jr||(Jr=xr[ti]=[]),Jr.push(yr)}}}class s{constructor(Fe){let Ne={},rt=[];for(let fr in Fe){let nr=Fe[fr],xr=Ne[fr]={};for(let ti in nr){let Jr=nr[+ti];if(!Jr||Jr.bitmap.width===0||Jr.bitmap.height===0)continue;let Wi={x:0,y:0,w:Jr.bitmap.width+2,h:Jr.bitmap.height+2};rt.push(Wi),xr[ti]={rect:Wi,metrics:Jr.metrics}}}let{w:Vt,h:Ht}=n.p(rt),yr=new n.r({width:Vt||1,height:Ht||1});for(let fr in Fe){let nr=Fe[fr];for(let xr in nr){let ti=nr[+xr];if(!ti||ti.bitmap.width===0||ti.bitmap.height===0)continue;let Jr=Ne[fr][xr].rect;n.r.copy(ti.bitmap,yr,{x:0,y:0},{x:Jr.x+1,y:Jr.y+1},ti.bitmap)}}this.image=yr,this.positions=Ne}}n.cN("GlyphAtlas",s);class c{constructor(Fe){this.tileID=new n.a2(Fe.tileID.overscaledZ,Fe.tileID.wrap,Fe.tileID.canonical.z,Fe.tileID.canonical.x,Fe.tileID.canonical.y),this.uid=Fe.uid,this.zoom=Fe.zoom,this.pixelRatio=Fe.pixelRatio,this.tileSize=Fe.tileSize,this.source=Fe.source,this.overscaling=this.tileID.overscaleFactor(),this.showCollisionBoxes=Fe.showCollisionBoxes,this.collectResourceTiming=!!Fe.collectResourceTiming,this.returnDependencies=!!Fe.returnDependencies,this.promoteId=Fe.promoteId,this.inFlightDependencies=[]}parse(Fe,Ne,rt,Vt,Ht){return n._(this,void 0,void 0,function*(){this.status="parsing",this.data=Fe,this.collisionBoxArray=new n.ai;let yr=new n.cO(Object.keys(Fe.layers).sort()),fr=new n.cP(this.tileID,this.promoteId);fr.bucketLayerIDs=[];let nr={},xr={featureIndex:fr,iconDependencies:{},patternDependencies:{},glyphDependencies:{},dashDependencies:{},availableImages:rt,subdivisionGranularity:Ht},ti=Ne.familiesBySource[this.source];for(let $t in ti){let Jt=Fe.layers[$t];if(!Jt)continue;Jt.version===1&&n.w(`Vector tile source "${this.source}" layer "${$t}" does not use vector tile spec v2 and therefore may have some rendering errors.`);let ht=yr.encode($t),Ot=[];for(let Ct=0;Ctui.id)))}}let Jr=n.bZ(xr.glyphDependencies,$t=>Object.keys($t).map(Number));this.inFlightDependencies.forEach($t=>$t?.abort()),this.inFlightDependencies=[];let Wi=Promise.resolve({});if(Object.keys(Jr).length){let $t=new AbortController;this.inFlightDependencies.push($t),Wi=Vt.sendAsync({type:"GG",data:{stacks:Jr,source:this.source,tileID:this.tileID,type:"glyphs"}},$t)}let fn=Object.keys(xr.iconDependencies),Wn=Promise.resolve({});if(fn.length){let $t=new AbortController;this.inFlightDependencies.push($t),Wn=Vt.sendAsync({type:"GI",data:{icons:fn,source:this.source,tileID:this.tileID,type:"icons"}},$t)}let bs=Object.keys(xr.patternDependencies),go=Promise.resolve({});if(bs.length){let $t=new AbortController;this.inFlightDependencies.push($t),go=Vt.sendAsync({type:"GI",data:{icons:bs,source:this.source,tileID:this.tileID,type:"patterns"}},$t)}let wr=xr.dashDependencies,Us=Promise.resolve({});if(Object.keys(wr).length){let $t=new AbortController;this.inFlightDependencies.push($t),Us=Vt.sendAsync({type:"GDA",data:{dashes:wr}},$t)}let[ns,Be,Xe,it]=yield Promise.all([Wi,Wn,go,Us]),mt=new s(ns),Ke=new n.cQ(Be,Xe);for(let $t in nr){let Jt=nr[$t];Jt instanceof n.aj?(f(Jt.layers,this.zoom,rt),n.cR({bucket:Jt,glyphMap:ns,glyphPositions:mt.positions,imageMap:Be,imagePositions:Ke.iconPositions,showCollisionBoxes:this.showCollisionBoxes,canonical:this.tileID.canonical,subdivisionGranularity:xr.subdivisionGranularity})):Jt.hasDependencies&&(Jt instanceof n.cS||Jt instanceof n.cT||Jt instanceof n.cU)&&(f(Jt.layers,this.zoom,rt),Jt.addFeatures(xr,this.tileID.canonical,Ke.patternPositions,it))}return this.status="done",{buckets:Object.values(nr).filter($t=>!$t.isEmpty()),featureIndex:fr,collisionBoxArray:this.collisionBoxArray,glyphAtlasImage:mt.image,imageAtlas:Ke,dashPositions:it,glyphMap:this.returnDependencies?ns:null,iconMap:this.returnDependencies?Be:null,glyphPositions:this.returnDependencies?mt.positions:null}})}}function f(Yt,Fe,Ne){let rt=new n.H(Fe);for(let Vt of Yt)Vt.recalculate(rt,Ne)}class g{constructor(Fe,Ne,rt,Vt,Ht){this.type=Fe,this.properties=rt||{},this.extent=Ht,this.pointsArray=Ne,this.id=Vt}loadGeometry(){return this.pointsArray.map(Fe=>Fe.map(Ne=>new n.P(Ne.x,Ne.y)))}}class y{constructor(Fe,Ne,rt){this.version=2,this._myFeatures=Fe,this.name=Ne,this.length=Fe.length,this.extent=rt}feature(Fe){return this._myFeatures[Fe]}}class M{constructor(){this.layers={}}addLayer(Fe){this.layers[Fe.name]=Fe}}function F(Yt){let Fe=n.cV(Yt);return Fe.byteOffset===0&&Fe.byteLength===Fe.buffer.byteLength||(Fe=new Uint8Array(Fe)),{vectorTile:Yt,rawData:Fe.buffer}}function k(Yt,Fe,Ne){let{extent:rt}=Yt,Vt=Math.pow(2,Ne.z-Fe.z),Ht=(Ne.x-Fe.x*Vt)*rt,yr=(Ne.y-Fe.y*Vt)*rt,fr=[];for(let nr=0;nr0&&xr.addLayer(Wn)}let Jr=F(xr);return this.overzoomedTileResultCache.set(fr,Jr),Jr}reloadTile(Fe){return n._(this,void 0,void 0,function*(){let Ne=Fe.uid;if(!this.loaded||!this.loaded[Ne])throw new Error("Should not be trying to reload a tile that was never loaded or has been removed");let rt=this.loaded[Ne];if(rt.showCollisionBoxes=Fe.showCollisionBoxes,rt.status==="parsing"){let Vt=yield rt.parse(rt.vectorTile,this.layerIndex,this.availableImages,this.actor,Fe.subdivisionGranularity),Ht;if(this.fetching[Ne]){let{rawTileData:yr,cacheControl:fr,resourceTiming:nr}=this.fetching[Ne];delete this.fetching[Ne],Ht=n.e({rawTileData:yr.slice(0),encoding:Fe.encoding},Vt,fr,nr)}else Ht=Vt;return Ht}if(rt.status==="done"&&rt.vectorTile)return rt.parse(rt.vectorTile,this.layerIndex,this.availableImages,this.actor,Fe.subdivisionGranularity)})}abortTile(Fe){return n._(this,void 0,void 0,function*(){let Ne=this.loading,rt=Fe.uid;Ne&&Ne[rt]&&Ne[rt].abort&&(Ne[rt].abort.abort(),delete Ne[rt])})}removeTile(Fe){return n._(this,void 0,void 0,function*(){this.loaded&&this.loaded[Fe.uid]&&delete this.loaded[Fe.uid]})}}class K{constructor(){this.loaded={}}loadTile(Fe){return n._(this,void 0,void 0,function*(){let{uid:Ne,encoding:rt,rawImageData:Vt,redFactor:Ht,greenFactor:yr,blueFactor:fr,baseShift:nr}=Fe,xr=Vt.width+2,ti=Vt.height+2,Jr=n.b(Vt)?new n.R({width:xr,height:ti},yield n.d0(Vt,-1,-1,xr,ti)):Vt,Wi=new n.d1(Ne,Jr,rt,Ht,yr,fr,nr);return this.loaded=this.loaded||{},this.loaded[Ne]=Wi,Wi})}removeTile(Fe){let Ne=this.loaded,rt=Fe.uid;Ne&&Ne[rt]&&delete Ne[rt]}}var ee,he,xe=function(){if(he)return ee;function Yt(Ne,rt){if(Ne.length!==0){Fe(Ne[0],rt);for(var Vt=1;Vt=Math.abs(xr)?Vt-ti+xr:xr-ti+Vt,Vt=ti}Vt+Ht>=0!=!!rt&&Ne.reverse()}return he=1,ee=function Ne(rt,Vt){var Ht,yr=rt&&rt.type;if(yr==="FeatureCollection")for(Ht=0;HtYt},Oe=Math.fround||(ne=new Float32Array(1),Yt=>(ne[0]=+Yt,ne[0]));var ne;class He{constructor(Fe){this.options=Object.assign(Object.create(Ce),Fe),this.trees=new Array(this.options.maxZoom+1),this.stride=this.options.reduce?7:6,this.clusterProps=[]}load(Fe){let{log:Ne,minZoom:rt,maxZoom:Vt}=this.options;Ne&&console.time("total time");let Ht=`prepare ${Fe.length} points`;Ne&&console.time(Ht),this.points=Fe;let yr=[];for(let nr=0;nr=rt;nr--){let xr=+Date.now();fr=this.trees[nr]=this._createTree(this._cluster(fr,nr)),Ne&&console.log("z%d: %d clusters in %dms",nr,fr.numItems,+Date.now()-xr)}return Ne&&console.timeEnd("total time"),this}getClusters(Fe,Ne){let rt=((Fe[0]+180)%360+360)%360-180,Vt=Math.max(-90,Math.min(90,Fe[1])),Ht=Fe[2]===180?180:((Fe[2]+180)%360+360)%360-180,yr=Math.max(-90,Math.min(90,Fe[3]));if(Fe[2]-Fe[0]>=360)rt=-180,Ht=180;else if(rt>Ht){let Jr=this.getClusters([rt,Vt,180,yr],Ne),Wi=this.getClusters([-180,Vt,Ht,yr],Ne);return Jr.concat(Wi)}let fr=this.trees[this._limitZoom(Ne)],nr=fr.range(ve(rt),Qe(yr),ve(Ht),Qe(Vt)),xr=fr.data,ti=[];for(let Jr of nr){let Wi=this.stride*Jr;ti.push(xr[Wi+5]>1?We(xr,Wi,this.clusterProps):this.points[xr[Wi+3]])}return ti}getChildren(Fe){let Ne=this._getOriginId(Fe),rt=this._getOriginZoom(Fe),Vt="No cluster with the specified id.",Ht=this.trees[rt];if(!Ht)throw new Error(Vt);let yr=Ht.data;if(Ne*this.stride>=yr.length)throw new Error(Vt);let fr=this.options.radius/(this.options.extent*Math.pow(2,rt-1)),nr=Ht.within(yr[Ne*this.stride],yr[Ne*this.stride+1],fr),xr=[];for(let ti of nr){let Jr=ti*this.stride;yr[Jr+4]===Fe&&xr.push(yr[Jr+5]>1?We(yr,Jr,this.clusterProps):this.points[yr[Jr+3]])}if(xr.length===0)throw new Error(Vt);return xr}getLeaves(Fe,Ne,rt){let Vt=[];return this._appendLeaves(Vt,Fe,Ne=Ne||10,rt=rt||0,0),Vt}getTile(Fe,Ne,rt){let Vt=this.trees[this._limitZoom(Fe)],Ht=Math.pow(2,Fe),{extent:yr,radius:fr}=this.options,nr=fr/yr,xr=(rt-nr)/Ht,ti=(rt+1+nr)/Ht,Jr={features:[]};return this._addTileFeatures(Vt.range((Ne-nr)/Ht,xr,(Ne+1+nr)/Ht,ti),Vt.data,Ne,rt,Ht,Jr),Ne===0&&this._addTileFeatures(Vt.range(1-nr/Ht,xr,1,ti),Vt.data,Ht,rt,Ht,Jr),Ne===Ht-1&&this._addTileFeatures(Vt.range(0,xr,nr/Ht,ti),Vt.data,-1,rt,Ht,Jr),Jr.features.length?Jr:null}getClusterExpansionZoom(Fe){let Ne=this._getOriginZoom(Fe)-1;for(;Ne<=this.options.maxZoom;){let rt=this.getChildren(Fe);if(Ne++,rt.length!==1)break;Fe=rt[0].properties.cluster_id}return Ne}_appendLeaves(Fe,Ne,rt,Vt,Ht){let yr=this.getChildren(Ne);for(let fr of yr){let nr=fr.properties;if(nr&&nr.cluster?Ht+nr.point_count<=Vt?Ht+=nr.point_count:Ht=this._appendLeaves(Fe,nr.cluster_id,rt,Vt,Ht):Ht1,ti,Jr,Wi;if(xr)ti=Se(Ne,nr,this.clusterProps),Jr=Ne[nr],Wi=Ne[nr+1];else{let bs=this.points[Ne[nr+3]];ti=bs.properties;let[go,wr]=bs.geometry.coordinates;Jr=ve(go),Wi=Qe(wr)}let fn={type:1,geometry:[[Math.round(this.options.extent*(Jr*Ht-rt)),Math.round(this.options.extent*(Wi*Ht-Vt))]],tags:ti},Wn;Wn=xr||this.options.generateId?Ne[nr+3]:this.points[Ne[nr+3]].id,Wn!==void 0&&(fn.id=Wn),yr.features.push(fn)}}_limitZoom(Fe){return Math.max(this.options.minZoom,Math.min(Math.floor(+Fe),this.options.maxZoom+1))}_cluster(Fe,Ne){let{radius:rt,extent:Vt,reduce:Ht,minPoints:yr}=this.options,fr=rt/(Vt*Math.pow(2,Ne)),nr=Fe.data,xr=[],ti=this.stride;for(let Jr=0;JrNe&&(go+=nr[Us+5])}if(go>bs&&go>=yr){let wr,Us=Wi*bs,ns=fn*bs,Be=-1,Xe=(Jr/ti<<5)+(Ne+1)+this.points.length;for(let it of Wn){let mt=it*ti;if(nr[mt+2]<=Ne)continue;nr[mt+2]=Ne;let Ke=nr[mt+5];Us+=nr[mt]*Ke,ns+=nr[mt+1]*Ke,nr[mt+4]=Xe,Ht&&(wr||(wr=this._map(nr,Jr,!0),Be=this.clusterProps.length,this.clusterProps.push(wr)),Ht(wr,this._map(nr,mt)))}nr[Jr+4]=Xe,xr.push(Us/go,ns/go,1/0,Xe,-1,go),Ht&&xr.push(Be)}else{for(let wr=0;wr1)for(let wr of Wn){let Us=wr*ti;if(!(nr[Us+2]<=Ne)){nr[Us+2]=Ne;for(let ns=0;ns>5}_getOriginZoom(Fe){return(Fe-this.points.length)%32}_map(Fe,Ne,rt){if(Fe[Ne+5]>1){let yr=this.clusterProps[Fe[Ne+6]];return rt?Object.assign({},yr):yr}let Vt=this.points[Fe[Ne+3]].properties,Ht=this.options.map(Vt);return rt&&Ht===Vt?Object.assign({},Ht):Ht}}function We(Yt,Fe,Ne){return{type:"Feature",id:Yt[Fe+3],properties:Se(Yt,Fe,Ne),geometry:{type:"Point",coordinates:[(rt=Yt[Fe],360*(rt-.5)),wt(Yt[Fe+1])]}};var rt}function Se(Yt,Fe,Ne){let rt=Yt[Fe+5],Vt=rt>=1e4?`${Math.round(rt/1e3)}k`:rt>=1e3?Math.round(rt/100)/10+"k":rt,Ht=Yt[Fe+6],yr=Ht===-1?{}:Object.assign({},Ne[Ht]);return Object.assign(yr,{cluster:!0,cluster_id:Yt[Fe+3],point_count:rt,point_count_abbreviated:Vt})}function ve(Yt){return Yt/360+.5}function Qe(Yt){let Fe=Math.sin(Yt*Math.PI/180),Ne=.5-.25*Math.log((1+Fe)/(1-Fe))/Math.PI;return Ne<0?0:Ne>1?1:Ne}function wt(Yt){let Fe=(180-360*Yt)*Math.PI/180;return 360*Math.atan(Math.exp(Fe))/Math.PI-90}function et(Yt,Fe,Ne,rt){let Vt=rt,Ht=Fe+(Ne-Fe>>1),yr,fr=Ne-Fe,nr=Yt[Fe],xr=Yt[Fe+1],ti=Yt[Ne],Jr=Yt[Ne+1];for(let Wi=Fe+3;WiVt)yr=Wi,Vt=fn;else if(fn===Vt){let Wn=Math.abs(Wi-Ht);Wnrt&&(yr-Fe>3&&et(Yt,Fe,yr,rt),Yt[yr+2]=Vt,Ne-yr>3&&et(Yt,yr,Ne,rt))}function xt(Yt,Fe,Ne,rt,Vt,Ht){let yr=Vt-Ne,fr=Ht-rt;if(yr!==0||fr!==0){let nr=((Yt-Ne)*yr+(Fe-rt)*fr)/(yr*yr+fr*fr);nr>1?(Ne=Vt,rt=Ht):nr>0&&(Ne+=yr*nr,rt+=fr*nr)}return yr=Yt-Ne,fr=Fe-rt,yr*yr+fr*fr}function sr(Yt,Fe,Ne,rt){let Vt={id:Yt??null,type:Fe,geometry:Ne,tags:rt,minX:1/0,minY:1/0,maxX:-1/0,maxY:-1/0};if(Fe==="Point"||Fe==="MultiPoint"||Fe==="LineString")Dr(Vt,Ne);else if(Fe==="Polygon")Dr(Vt,Ne[0]);else if(Fe==="MultiLineString")for(let Ht of Ne)Dr(Vt,Ht);else if(Fe==="MultiPolygon")for(let Ht of Ne)Dr(Vt,Ht[0]);return Vt}function Dr(Yt,Fe){for(let Ne=0;Ne0&&(yr+=rt?(Vt*ti-xr*Ht)/2:Math.sqrt(Math.pow(xr-Vt,2)+Math.pow(ti-Ht,2))),Vt=xr,Ht=ti}let fr=Fe.length-3;Fe[2]=1,et(Fe,0,fr,Ne),Fe[fr+2]=1,Fe.size=Math.abs(yr),Fe.start=0,Fe.end=Fe.size}function un(Yt,Fe,Ne,rt){for(let Vt=0;Vt1?1:Ne}function xi(Yt,Fe,Ne,rt,Vt,Ht,yr,fr){if(rt/=Fe,Ht>=(Ne/=Fe)&&yr=rt)return null;let nr=[];for(let xr of Yt){let ti=xr.geometry,Jr=xr.type,Wi=Vt===0?xr.minX:xr.minY,fn=Vt===0?xr.maxX:xr.maxY;if(Wi>=Ne&&fn=rt)continue;let Wn=[];if(Jr==="Point"||Jr==="MultiPoint")ii(ti,Wn,Ne,rt,Vt);else if(Jr==="LineString")Hn(ti,Wn,Ne,rt,Vt,!1,fr.lineMetrics);else if(Jr==="MultiLineString")nt(ti,Wn,Ne,rt,Vt,!1);else if(Jr==="Polygon")nt(ti,Wn,Ne,rt,Vt,!0);else if(Jr==="MultiPolygon")for(let bs of ti){let go=[];nt(bs,go,Ne,rt,Vt,!0),go.length&&Wn.push(go)}if(Wn.length){if(fr.lineMetrics&&Jr==="LineString"){for(let bs of Wn)nr.push(sr(xr.id,Jr,bs,xr.tags));continue}Jr!=="LineString"&&Jr!=="MultiLineString"||(Wn.length===1?(Jr="LineString",Wn=Wn[0]):Jr="MultiLineString"),Jr!=="Point"&&Jr!=="MultiPoint"||(Jr=Wn.length===3?"Point":"MultiPoint"),nr.push(sr(xr.id,Jr,Wn,xr.tags))}}return nr.length?nr:null}function ii(Yt,Fe,Ne,rt,Vt){for(let Ht=0;Ht=Ne&&yr<=rt&&Ft(Fe,Yt[Ht],Yt[Ht+1],Yt[Ht+2])}}function Hn(Yt,Fe,Ne,rt,Vt,Ht,yr){let fr=Ve(Yt),nr=Vt===0?mr:Xr,xr,ti,Jr=Yt.start;for(let go=0;goNe&&(ti=nr(fr,wr,Us,Be,Xe,Ne),yr&&(fr.start=Jr+xr*ti)):it>rt?mt=Ne&&(ti=nr(fr,wr,Us,Be,Xe,Ne),Ke=!0),mt>rt&&it<=rt&&(ti=nr(fr,wr,Us,Be,Xe,rt),Ke=!0),!Ht&&Ke&&(yr&&(fr.end=Jr+xr*ti),Fe.push(fr),fr=Ve(Yt)),yr&&(Jr+=xr)}let Wi=Yt.length-3,fn=Yt[Wi],Wn=Yt[Wi+1],bs=Vt===0?fn:Wn;bs>=Ne&&bs<=rt&&Ft(fr,fn,Wn,Yt[Wi+2]),Wi=fr.length-3,Ht&&Wi>=3&&(fr[Wi]!==fr[0]||fr[Wi+1]!==fr[1])&&Ft(fr,fr[0],fr[1],fr[2]),fr.length&&Fe.push(fr)}function Ve(Yt){let Fe=[];return Fe.size=Yt.size,Fe.start=Yt.start,Fe.end=Yt.end,Fe}function nt(Yt,Fe,Ne,rt,Vt,Ht){for(let yr of Yt)Hn(yr,Fe,Ne,rt,Vt,Ht,!1)}function Ft(Yt,Fe,Ne,rt){Yt.push(Fe,Ne,rt)}function mr(Yt,Fe,Ne,rt,Vt,Ht){let yr=(Ht-Fe)/(rt-Fe);return Ft(Yt,Ht,Ne+(Vt-Ne)*yr,1),yr}function Xr(Yt,Fe,Ne,rt,Vt,Ht){let yr=(Ht-Ne)/(Vt-Ne);return Ft(Yt,Fe+(rt-Fe)*yr,Ht,1),yr}function zr(Yt,Fe){let Ne=[];for(let rt=0;rt0&&Fe.size<(Vt?yr:rt))return void(Ne.numPoints+=Fe.length/3);let fr=[];for(let nr=0;nryr)&&(Ne.numSimplified++,fr.push(Fe[nr],Fe[nr+1])),Ne.numPoints++;Vt&&function(nr,xr){let ti=0;for(let Jr=0,Wi=nr.length,fn=Wi-2;Jr0===xr)for(let Jr=0,Wi=nr.length;Jr24)throw new Error("maxZoom should be in the 0-24 range");if(Ne.promoteId&&Ne.generateId)throw new Error("promoteId and generateId cannot be used together.");let Vt=function(Ht,yr){let fr=[];if(Ht.type==="FeatureCollection")for(let nr=0;nr1&&console.time("creation"),fn=this.tiles[Wi]=Go(Fe,Ne,rt,Vt,xr),this.tileCoords.push({z:Ne,x:rt,y:Vt}),ti)){ti>1&&(console.log("tile z%d-%d-%d (features: %d, points: %d, simplified: %d)",Ne,rt,Vt,fn.numFeatures,fn.numPoints,fn.numSimplified),console.timeEnd("creation"));let Ke=`z${Ne}`;this.stats[Ke]=(this.stats[Ke]||0)+1,this.total++}if(fn.source=Fe,Ht==null){if(Ne===xr.indexMaxZoom||fn.numPoints<=xr.indexMaxPoints)continue}else{if(Ne===xr.maxZoom||Ne===Ht)continue;if(Ht!=null){let Ke=Ht-Ne;if(rt!==yr>>Ke||Vt!==fr>>Ke)continue}}if(fn.source=null,Fe.length===0)continue;ti>1&&console.time("clipping");let Wn=.5*xr.buffer/xr.extent,bs=.5-Wn,go=.5+Wn,wr=1+Wn,Us=null,ns=null,Be=null,Xe=null,it=xi(Fe,Jr,rt-Wn,rt+go,0,fn.minX,fn.maxX,xr),mt=xi(Fe,Jr,rt+bs,rt+wr,0,fn.minX,fn.maxX,xr);Fe=null,it&&(Us=xi(it,Jr,Vt-Wn,Vt+go,1,fn.minY,fn.maxY,xr),ns=xi(it,Jr,Vt+bs,Vt+wr,1,fn.minY,fn.maxY,xr),it=null),mt&&(Be=xi(mt,Jr,Vt-Wn,Vt+go,1,fn.minY,fn.maxY,xr),Xe=xi(mt,Jr,Vt+bs,Vt+wr,1,fn.minY,fn.maxY,xr),mt=null),ti>1&&console.timeEnd("clipping"),nr.push(Us||[],Ne+1,2*rt,2*Vt),nr.push(ns||[],Ne+1,2*rt,2*Vt+1),nr.push(Be||[],Ne+1,2*rt+1,2*Vt),nr.push(Xe||[],Ne+1,2*rt+1,2*Vt+1)}}getTile(Fe,Ne,rt){Fe=+Fe,Ne=+Ne,rt=+rt;let Vt=this.options,{extent:Ht,debug:yr}=Vt;if(Fe<0||Fe>24)return null;let fr=1<1&&console.log("drilling down to z%d-%d-%d",Fe,Ne,rt);let xr,ti=Fe,Jr=Ne,Wi=rt;for(;!xr&&ti>0;)ti--,Jr>>=1,Wi>>=1,xr=this.tiles[ds(ti,Jr,Wi)];return xr&&xr.source?(yr>1&&(console.log("found parent tile z%d-%d-%d",ti,Jr,Wi),console.time("drilling down")),this.splitTile(xr.source,ti,Jr,Wi,Fe,Ne,rt),yr>1&&console.timeEnd("drilling down"),this.tiles[nr]?Nn(this.tiles[nr],Ht):null):null}}function ds(Yt,Fe,Ne){return 32*((1<`${Ht.key}: ${Ht.message}`).join(", "));let Vt=Fe.features.filter(Ht=>rt.value.evaluate({zoom:0},Ht));return this._toFeatureCollection(Vt)}_toFeatureCollection(Fe){return{type:"FeatureCollection",features:Fe}}removeSource(Fe){return n._(this,void 0,void 0,function*(){this._pendingRequest&&this._pendingRequest.abort()})}getClusterExpansionZoom(Fe){return this._geoJSONIndex.getClusterExpansionZoom(Fe.clusterId)}getClusterChildren(Fe){return this._geoJSONIndex.getChildren(Fe.clusterId)}getClusterLeaves(Fe){return this._geoJSONIndex.getLeaves(Fe.clusterId,Fe.limit,Fe.offset)}}function ps(Yt,Fe){return Fe.cluster?new He(function({superclusterOptions:Ne,clusterProperties:rt}){if(!rt||!Ne)return Ne;let Vt={},Ht={},yr={accumulated:null,zoom:0},fr={properties:null},nr=Object.keys(rt);for(let xr of nr){let[ti,Jr]=rt[xr],Wi=n.d4(Jr),fn=n.d4(typeof ti=="string"?[ti,["accumulated"],["get",xr]]:ti);Vt[xr]=Wi.value,Ht[xr]=fn.value}return Ne.map=xr=>{fr.properties=xr;let ti={};for(let Jr of nr)ti[Jr]=Vt[Jr].evaluate(yr,fr);return ti},Ne.reduce=(xr,ti)=>{fr.properties=ti;for(let Jr of nr)yr.accumulated=xr[Jr],xr[Jr]=Ht[Jr].evaluate(yr,fr)},Ne}(Fe)).load(Yt.features):function(Ne,rt){return new Ba(Ne,rt)}(Yt,Fe.geojsonVtOptions)}class ws{constructor(Fe){this.self=Fe,this.actor=new n.L(Fe),this.layerIndexes={},this.availableImages={},this.workerSources={},this.demWorkerSources={},this.externalWorkerSourceTypes={},this.globalStates=new Map,this.self.registerWorkerSource=(Ne,rt)=>{if(this.externalWorkerSourceTypes[Ne])throw new Error(`Worker source with name "${Ne}" already registered.`);this.externalWorkerSourceTypes[Ne]=rt},this.self.addProtocol=n.cK,this.self.removeProtocol=n.cL,this.self.registerRTLTextPlugin=Ne=>{n.d5.setMethods(Ne)},this.actor.registerMessageHandler("LDT",(Ne,rt)=>this._getDEMWorkerSource(Ne,rt.source).loadTile(rt)),this.actor.registerMessageHandler("RDT",(Ne,rt)=>n._(this,void 0,void 0,function*(){this._getDEMWorkerSource(Ne,rt.source).removeTile(rt)})),this.actor.registerMessageHandler("GCEZ",(Ne,rt)=>n._(this,void 0,void 0,function*(){return this._getWorkerSource(Ne,rt.type,rt.source).getClusterExpansionZoom(rt)})),this.actor.registerMessageHandler("GCC",(Ne,rt)=>n._(this,void 0,void 0,function*(){return this._getWorkerSource(Ne,rt.type,rt.source).getClusterChildren(rt)})),this.actor.registerMessageHandler("GCL",(Ne,rt)=>n._(this,void 0,void 0,function*(){return this._getWorkerSource(Ne,rt.type,rt.source).getClusterLeaves(rt)})),this.actor.registerMessageHandler("LD",(Ne,rt)=>this._getWorkerSource(Ne,rt.type,rt.source).loadData(rt)),this.actor.registerMessageHandler("GD",(Ne,rt)=>this._getWorkerSource(Ne,rt.type,rt.source).getData()),this.actor.registerMessageHandler("LT",(Ne,rt)=>this._getWorkerSource(Ne,rt.type,rt.source).loadTile(rt)),this.actor.registerMessageHandler("RT",(Ne,rt)=>this._getWorkerSource(Ne,rt.type,rt.source).reloadTile(rt)),this.actor.registerMessageHandler("AT",(Ne,rt)=>this._getWorkerSource(Ne,rt.type,rt.source).abortTile(rt)),this.actor.registerMessageHandler("RMT",(Ne,rt)=>this._getWorkerSource(Ne,rt.type,rt.source).removeTile(rt)),this.actor.registerMessageHandler("RS",(Ne,rt)=>n._(this,void 0,void 0,function*(){if(!this.workerSources[Ne]||!this.workerSources[Ne][rt.type]||!this.workerSources[Ne][rt.type][rt.source])return;let Vt=this.workerSources[Ne][rt.type][rt.source];delete this.workerSources[Ne][rt.type][rt.source],Vt.removeSource!==void 0&&Vt.removeSource(rt)})),this.actor.registerMessageHandler("RM",Ne=>n._(this,void 0,void 0,function*(){delete this.layerIndexes[Ne],delete this.availableImages[Ne],delete this.workerSources[Ne],delete this.demWorkerSources[Ne],this.globalStates.delete(Ne)})),this.actor.registerMessageHandler("SR",(Ne,rt)=>n._(this,void 0,void 0,function*(){this.referrer=rt})),this.actor.registerMessageHandler("SRPS",(Ne,rt)=>this._syncRTLPluginState(Ne,rt)),this.actor.registerMessageHandler("IS",(Ne,rt)=>n._(this,void 0,void 0,function*(){this.self.importScripts(rt)})),this.actor.registerMessageHandler("SI",(Ne,rt)=>this._setImages(Ne,rt)),this.actor.registerMessageHandler("UL",(Ne,rt)=>n._(this,void 0,void 0,function*(){this._getLayerIndex(Ne).update(rt.layers,rt.removedIds,this._getGlobalState(Ne))})),this.actor.registerMessageHandler("UGS",(Ne,rt)=>n._(this,void 0,void 0,function*(){let Vt=this._getGlobalState(Ne);for(let Ht in rt)Vt[Ht]=rt[Ht]})),this.actor.registerMessageHandler("SL",(Ne,rt)=>n._(this,void 0,void 0,function*(){this._getLayerIndex(Ne).replace(rt,this._getGlobalState(Ne))}))}_getGlobalState(Fe){let Ne=this.globalStates.get(Fe);return Ne||(Ne={},this.globalStates.set(Fe,Ne)),Ne}_setImages(Fe,Ne){return n._(this,void 0,void 0,function*(){this.availableImages[Fe]=Ne;for(let rt in this.workerSources[Fe]){let Vt=this.workerSources[Fe][rt];for(let Ht in Vt)Vt[Ht].availableImages=Ne}})}_syncRTLPluginState(Fe,Ne){return n._(this,void 0,void 0,function*(){return yield n.d5.syncState(Ne,this.self.importScripts)})}_getAvailableImages(Fe){let Ne=this.availableImages[Fe];return Ne||(Ne=[]),Ne}_getLayerIndex(Fe){let Ne=this.layerIndexes[Fe];return Ne||(Ne=this.layerIndexes[Fe]=new o),Ne}_getWorkerSource(Fe,Ne,rt){if(this.workerSources[Fe]||(this.workerSources[Fe]={}),this.workerSources[Fe][Ne]||(this.workerSources[Fe][Ne]={}),!this.workerSources[Fe][Ne][rt]){let Vt={sendAsync:(Ht,yr)=>(Ht.targetMapId=Fe,this.actor.sendAsync(Ht,yr))};switch(Ne){case"vector":this.workerSources[Fe][Ne][rt]=new U(Vt,this._getLayerIndex(Fe),this._getAvailableImages(Fe));break;case"geojson":this.workerSources[Fe][Ne][rt]=new so(Vt,this._getLayerIndex(Fe),this._getAvailableImages(Fe));break;default:this.workerSources[Fe][Ne][rt]=new this.externalWorkerSourceTypes[Ne](Vt,this._getLayerIndex(Fe),this._getAvailableImages(Fe))}}return this.workerSources[Fe][Ne][rt]}_getDEMWorkerSource(Fe,Ne){return this.demWorkerSources[Fe]||(this.demWorkerSources[Fe]={}),this.demWorkerSources[Fe][Ne]||(this.demWorkerSources[Fe][Ne]=new K),this.demWorkerSources[Fe][Ne]}}return n.i(self)&&(self.worker=new ws(self)),ws}),t("index",["exports","./shared"],function(n,o){"use strict";var s="5.14.0";function c(){var z=new o.A(4);return o.A!=Float32Array&&(z[1]=0,z[2]=0),z[0]=1,z[3]=1,z}let f,g,y,M={frame(z,l,m){let T=requestAnimationFrame(V=>{P(),l(V)}),{unsubscribe:P}=o.s(z.signal,"abort",()=>{P(),cancelAnimationFrame(T),m(new o.a(z.signal.reason))},!1)},frameAsync(z){return new Promise((l,m)=>{this.frame(z,l,m)})},getImageData(z,l=0){return this.getImageCanvasContext(z).getImageData(-l,-l,z.width+2*l,z.height+2*l)},getImageCanvasContext(z){let l=window.document.createElement("canvas"),m=l.getContext("2d",{willReadFrequently:!0});if(!m)throw new Error("failed to create canvas 2d context");return l.width=z.width,l.height=z.height,m.drawImage(z,0,0,z.width,z.height),m},resolveURL:z=>(f||(f=document.createElement("a")),f.href=z,f.href),hardwareConcurrency:typeof navigator<"u"&&navigator.hardwareConcurrency||4,get prefersReducedMotion(){return y!==void 0?y:!!matchMedia&&(g==null&&(g=matchMedia("(prefers-reduced-motion: reduce)")),g.matches)},set prefersReducedMotion(z){y=z}},F=new class{constructor(){this._realTime=typeof performance<"u"&&performance&&performance.now?performance.now.bind(performance):Date.now.bind(Date),this._frozenAt=null}getCurrentTime(){return this._frozenAt!==null?this._frozenAt:this._realTime()}setNow(z){this._frozenAt=z}restoreNow(){this._frozenAt=null}isFrozen(){return this._frozenAt!==null}};function k(){return F.getCurrentTime()}class U{static testProp(l){if(!U.docStyle)return l[0];for(let m=0;m{window.removeEventListener("click",U.suppressClickInternal,!0)},0)}static getScale(l){let m=l.getBoundingClientRect();return{x:m.width/l.offsetWidth||1,y:m.height/l.offsetHeight||1,boundingClientRect:m}}static getPoint(l,m,T){let P=m.boundingClientRect;return new o.P((T.clientX-P.left)/m.x-l.clientLeft,(T.clientY-P.top)/m.y-l.clientTop)}static mousePos(l,m){let T=U.getScale(l);return U.getPoint(l,T,m)}static touchPos(l,m){let T=[],P=U.getScale(l);for(let V=0;V{ee&&Ce(ee),ee=null,me=!0},he.onerror=()=>{xe=!0,ee=null},he.src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA="),function(z){let l,m,T,P;z.resetRequestQueue=()=>{l=[],m=0,T=0,P={}},z.addThrottleControl=se=>{let fe=T++;return P[fe]=se,fe},z.removeThrottleControl=se=>{delete P[se],W()},z.getImage=(se,fe,_e=!0)=>new Promise((Re,Ie)=>{K.supported&&(se.headers||(se.headers={}),se.headers.accept="image/webp,*/*"),o.e(se,{type:"image"}),l.push({abortController:fe,requestParameters:se,supportImageRefresh:_e,state:"queued",onError:je=>{Ie(je)},onSuccess:je=>{Re(je)}}),W()});let V=se=>o._(this,void 0,void 0,function*(){se.state="running";let{requestParameters:fe,supportImageRefresh:_e,onError:Re,onSuccess:Ie,abortController:je}=se,tt=_e===!1&&!o.i(self)&&!o.g(fe.url)&&(!fe.headers||Object.keys(fe.headers).reduce((vt,Lt)=>vt&&Lt==="accept",!0));m++;let yt=tt?te(fe,je):o.m(fe,je);try{let vt=yield yt;delete se.abortController,se.state="completed",vt.data instanceof HTMLImageElement||o.b(vt.data)?Ie(vt):vt.data&&Ie({data:yield(At=vt.data,typeof createImageBitmap=="function"?o.f(At):o.h(At)),cacheControl:vt.cacheControl,expires:vt.expires})}catch(vt){delete se.abortController,Re(vt)}finally{m--,W()}var At}),W=()=>{let se=(()=>{for(let fe of Object.keys(P))if(P[fe]())return!0;return!1})()?o.c.MAX_PARALLEL_IMAGE_REQUESTS_PER_FRAME:o.c.MAX_PARALLEL_IMAGE_REQUESTS;for(let fe=m;fe0;fe++){let _e=l.shift();_e.abortController.signal.aborted?fe--:V(_e)}},te=(se,fe)=>new Promise((_e,Re)=>{let Ie=new Image,je=se.url,tt=se.credentials;tt&&tt==="include"?Ie.crossOrigin="use-credentials":(tt&&tt==="same-origin"||!o.d(je))&&(Ie.crossOrigin="anonymous"),fe.signal.addEventListener("abort",()=>{Ie.src="",Re(new o.a(fe.signal.reason))}),Ie.fetchPriority="high",Ie.onload=()=>{Ie.onerror=Ie.onload=null,_e({data:Ie})},Ie.onerror=()=>{Ie.onerror=Ie.onload=null,fe.signal.aborted||Re(new Error("Could not load image. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported."))},Ie.src=je})}(Oe||(Oe={})),Oe.resetRequestQueue();class ne{constructor(l){this._transformRequestFn=l??null}transformRequest(l,m){return this._transformRequestFn&&this._transformRequestFn(l,m)||{url:l}}setTransformRequest(l){this._transformRequestFn=l}}function He(z){let l=[];if(typeof z=="string")l.push({id:"default",url:z});else if(z&&z.length>0){let m=[];for(let{id:T,url:P}of z){let V=`${T}${P}`;m.indexOf(V)===-1&&(m.push(V),l.push({id:T,url:P}))}}return l}function We(z,l,m){try{let T=new URL(z);return T.pathname+=`${l}${m}`,T.toString()}catch{throw new Error(`Invalid sprite URL "${z}", must be absolute. Modify style specification directly or use TransformStyleFunction to correct the issue dynamically`)}}function Se(z){let{userImage:l}=z;return!!(l&&l.render&&l.render())&&(z.data.replace(new Uint8Array(l.data.buffer)),!0)}class ve extends o.E{constructor(){super(),this.images={},this.updatedImages={},this.callbackDispatchedThisFrame={},this.loaded=!1,this.requestors=[],this.patterns={},this.atlasImage=new o.R({width:1,height:1}),this.dirty=!0}destroy(){this.atlasTexture&&(this.atlasTexture.destroy(),this.atlasTexture=null);for(let l of Object.keys(this.images))this.removeImage(l);this.patterns={},this.atlasImage=new o.R({width:1,height:1}),this.dirty=!0}isLoaded(){return this.loaded}setLoaded(l){if(this.loaded!==l&&(this.loaded=l,l)){for(let{ids:m,promiseResolve:T}of this.requestors)T(this._getImagesForIds(m));this.requestors=[]}}getImage(l){let m=this.images[l];if(m&&!m.data&&m.spriteData){let T=m.spriteData;m.data=new o.R({width:T.width,height:T.height},T.context.getImageData(T.x,T.y,T.width,T.height).data),m.spriteData=null}return m}addImage(l,m){if(this.images[l])throw new Error(`Image id ${l} already exist, use updateImage instead`);this._validate(l,m)&&(this.images[l]=m)}_validate(l,m){let T=!0,P=m.data||m.spriteData;return this._validateStretch(m.stretchX,P&&P.width)||(this.fire(new o.k(new Error(`Image "${l}" has invalid "stretchX" value`))),T=!1),this._validateStretch(m.stretchY,P&&P.height)||(this.fire(new o.k(new Error(`Image "${l}" has invalid "stretchY" value`))),T=!1),this._validateContent(m.content,m)||(this.fire(new o.k(new Error(`Image "${l}" has invalid "content" value`))),T=!1),T}_validateStretch(l,m){if(!l)return!0;let T=0;for(let P of l){if(P[0]{let P=!0;if(!this.isLoaded())for(let V of l)this.images[V]||(P=!1);this.isLoaded()||P?m(this._getImagesForIds(l)):this.requestors.push({ids:l,promiseResolve:m})})}_getImagesForIds(l){let m={};for(let T of l){let P=this.getImage(T);P||(this.fire(new o.l("styleimagemissing",{id:T})),P=this.getImage(T)),P?m[T]={data:P.data.clone(),pixelRatio:P.pixelRatio,sdf:P.sdf,version:P.version,stretchX:P.stretchX,stretchY:P.stretchY,content:P.content,textFitWidth:P.textFitWidth,textFitHeight:P.textFitHeight,hasRenderCallback:Boolean(P.userImage&&P.userImage.render)}:o.w(`Image "${T}" could not be loaded. Please make sure you have added the image with map.addImage() or a "sprite" property in your style. You can provide missing images by listening for the "styleimagemissing" map event.`)}return m}getPixelSize(){let{width:l,height:m}=this.atlasImage;return{width:l,height:m}}getPattern(l){let m=this.patterns[l],T=this.getImage(l);if(!T)return null;if(m&&m.position.version===T.version)return m.position;if(m)m.position.version=T.version;else{let P={w:T.data.width+2,h:T.data.height+2,x:0,y:0},V=new o.I(P,T);this.patterns[l]={bin:P,position:V}}return this._updatePatternAtlas(),this.patterns[l].position}bind(l){let m=l.gl;this.atlasTexture?this.dirty&&(this.atlasTexture.update(this.atlasImage),this.dirty=!1):this.atlasTexture=new o.T(l,this.atlasImage,m.RGBA),this.atlasTexture.bind(m.LINEAR,m.CLAMP_TO_EDGE)}_updatePatternAtlas(){let l=[];for(let V in this.patterns)l.push(this.patterns[V].bin);let{w:m,h:T}=o.p(l),P=this.atlasImage;P.resize({width:m||1,height:T||1});for(let V in this.patterns){let{bin:W}=this.patterns[V],te=W.x+1,se=W.y+1,fe=this.getImage(V).data,_e=fe.width,Re=fe.height;o.R.copy(fe,P,{x:0,y:0},{x:te,y:se},{width:_e,height:Re}),o.R.copy(fe,P,{x:0,y:Re-1},{x:te,y:se-1},{width:_e,height:1}),o.R.copy(fe,P,{x:0,y:0},{x:te,y:se+Re},{width:_e,height:1}),o.R.copy(fe,P,{x:_e-1,y:0},{x:te-1,y:se},{width:1,height:Re}),o.R.copy(fe,P,{x:0,y:0},{x:te+_e,y:se},{width:1,height:Re})}this.dirty=!0}beginFrame(){this.callbackDispatchedThisFrame={}}dispatchRenderCallbacks(l){for(let m of l){if(this.callbackDispatchedThisFrame[m])continue;this.callbackDispatchedThisFrame[m]=!0;let T=this.getImage(m);T||o.w(`Image with ID: "${m}" was not found`),Se(T)&&this.updateImage(m,T)}}cloneImages(){let l={};for(let m in this.images){let T=this.images[m];l[m]=Object.assign(Object.assign({},T),{data:T.data?T.data.clone():null})}return l}}let Qe=1e20;function wt(z,l,m,T,P,V,W,te,se){for(let fe=l;fe-1);se++,V[se]=te,W[se]=fe,W[se+1]=Qe}for(let te=0,se=0;te/[-\w]+/.test(P)?P:`'${CSS.escape(P)}'`).join(",");return new sr.TinySDF({fontSize:48,buffer:6,radius:16,cutoff:.25,fontFamily:T,fontWeight:this._fontWeight(m[0]),fontStyle:this._fontStyle(m[0]),lang:this.lang})}_fontStyle(l){return/italic/i.test(l)?"italic":/oblique/i.test(l)?"oblique":"normal"}_fontWeight(l){let m={thin:100,hairline:100,"extra light":200,"ultra light":200,light:300,normal:400,regular:400,medium:500,semibold:600,demibold:600,bold:700,"extra bold":800,"ultra bold":800,black:900,heavy:900,"extra black":950,"ultra black":950},T;for(let[P,V]of Object.entries(m))new RegExp(`\\b${P}\\b`,"i").test(l)&&(T=`${V}`);return T}destroy(){for(let l in this.entries){let m=this.entries[l];m.tinySDF&&(m.tinySDF=null),m.ideographTinySDF&&(m.ideographTinySDF=null),m.glyphs={},m.requests={},m.ranges={}}this.entries={}}}sr.loadGlyphRange=function(z,l,m,T){return o._(this,void 0,void 0,function*(){let P=256*l,V=P+255,W=T.transformRequest(m.replace("{fontstack}",z).replace("{range}",`${P}-${V}`),"Glyphs"),te=yield o.n(W,new AbortController);if(!te||!te.data)throw new Error(`Could not load glyph range. range: ${l}, ${P}-${V}`);let se={};for(let fe of o.o(te.data))se[fe.id]=fe;return se})},sr.TinySDF=class{constructor({fontSize:z=24,buffer:l=3,radius:m=8,cutoff:T=.25,fontFamily:P="sans-serif",fontWeight:V="normal",fontStyle:W="normal",lang:te=null}={}){this.buffer=l,this.cutoff=T,this.radius=m,this.lang=te;let se=this.size=z+4*l,fe=this._createCanvas(se),_e=this.ctx=fe.getContext("2d",{willReadFrequently:!0});_e.font=`${W} ${V} ${z}px ${P}`,_e.textBaseline="alphabetic",_e.textAlign="left",_e.fillStyle="black",this.gridOuter=new Float64Array(se*se),this.gridInner=new Float64Array(se*se),this.f=new Float64Array(se),this.z=new Float64Array(se+1),this.v=new Uint16Array(se)}_createCanvas(z){let l=document.createElement("canvas");return l.width=l.height=z,l}draw(z){let{width:l,actualBoundingBoxAscent:m,actualBoundingBoxDescent:T,actualBoundingBoxLeft:P,actualBoundingBoxRight:V}=this.ctx.measureText(z),W=Math.ceil(m),te=Math.max(0,Math.min(this.size-this.buffer,Math.ceil(V-P))),se=Math.min(this.size-this.buffer,W+Math.ceil(T)),fe=te+2*this.buffer,_e=se+2*this.buffer,Re=Math.max(fe*_e,0),Ie=new Uint8ClampedArray(Re),je={data:Ie,width:fe,height:_e,glyphWidth:te,glyphHeight:se,glyphTop:W,glyphLeft:0,glyphAdvance:l};if(te===0||se===0)return je;let{ctx:tt,buffer:yt,gridInner:At,gridOuter:vt}=this;this.lang&&(tt.lang=this.lang),tt.clearRect(yt,yt,te,se),tt.fillText(z,yt,yt+W);let Lt=tt.getImageData(yt,yt,te,se);vt.fill(Qe,0,Re),At.fill(0,0,Re);for(let Kt=0;Kt0?zt*zt:0,At[Rt]=zt<0?zt*zt:0}}wt(vt,0,0,fe,_e,fe,this.f,this.v,this.z),wt(At,yt,yt,te,se,fe,this.f,this.v,this.z);for(let Kt=0;Kt1&&(se=l[++te]);let _e=Math.abs(fe-se.left),Re=Math.abs(fe-se.right),Ie=Math.min(_e,Re),je,tt=V/T*(P+1);if(se.isDash){let yt=P-Math.abs(tt);je=Math.sqrt(Ie*Ie+yt*yt)}else je=P-Math.sqrt(Ie*Ie+tt*tt);this.data[W+fe]=Math.max(0,Math.min(255,je+128))}}}addRegularDash(l){for(let te=l.length-1;te>=0;--te){let se=l[te],fe=l[te+1];se.zeroLength?l.splice(te,1):fe&&fe.isDash===se.isDash&&(fe.left=se.left,l.splice(te,1))}let m=l[0],T=l[l.length-1];m.isDash===T.isDash&&(m.left=T.left-this.width,T.right=m.right+this.width);let P=this.width*this.nextRow,V=0,W=l[V];for(let te=0;te1&&(W=l[++V]);let se=Math.abs(te-W.left),fe=Math.abs(te-W.right),_e=Math.min(se,fe);this.data[P+te]=Math.max(0,Math.min(255,(W.isDash?_e:-_e)+128))}}addDash(l,m){let T=m?7:0,P=2*T+1;if(this.nextRow+P>this.height)return o.w("LineAtlas out of space"),null;let V=0;for(let te=0;te{m.terminate()}),this.workers=null)}isPreloaded(){return!!this.active[Xi]}numActive(){return Object.keys(this.active).length}}let ii=Math.floor(M.hardwareConcurrency/2),Hn,Ve;function nt(){return Hn||(Hn=new xi),Hn}xi.workerCount=o.K(globalThis)?Math.max(Math.min(ii,3),1):1;class Ft{constructor(l,m){this.workerPool=l,this.actors=[],this.currentActor=0,this.id=m;let T=this.workerPool.acquire(m);for(let P=0;P{m.remove()}),this.actors=[],l&&this.workerPool.release(this.id)}registerMessageHandler(l,m){for(let T of this.actors)T.registerMessageHandler(l,m)}unregisterMessageHandler(l){for(let m of this.actors)m.unregisterMessageHandler(l)}}function mr(){return Ve||(Ve=new Ft(nt(),o.M),Ve.registerMessageHandler("GR",(z,l,m)=>o.m(l,m))),Ve}function Xr(z,l){let m=o.N();return o.O(m,m,[1,1,0]),o.Q(m,m,[.5*z.width,.5*z.height,1]),z.calculatePosMatrix?o.S(m,m,z.calculatePosMatrix(l.toUnwrapped())):m}function zr(z,l,m,T,P,V,W){var te;let se=function(Ie,je,tt){if(Ie)for(let yt of Ie){let At=je[yt];if(At&&At.source===tt&&At.type==="fill-extrusion")return!0}else for(let yt in je){let At=je[yt];if(At.source===tt&&At.type==="fill-extrusion")return!0}return!1}((te=P?.layers)!==null&&te!==void 0?te:null,l,z.id),fe=V.maxPitchScaleFactor(),_e=z.tilesIn(T,fe,se);_e.sort(ln);let Re=[];for(let Ie of _e)Re.push({wrappedTileID:Ie.tileID.wrapped().key,queryResults:Ie.tile.queryRenderedFeatures(l,m,z.getState(),Ie.queryGeometry,Ie.cameraQueryGeometry,Ie.scale,P,V,fe,Xr(V,Ie.tileID),W?(je,tt)=>W(Ie.tileID,je,tt):void 0)});return function(Ie,je){for(let tt in Ie)for(let yt of Ie[tt])Nn(yt,je);return Ie}(function(Ie){let je={},tt={};for(let yt of Ie){let At=yt.queryResults,vt=yt.wrappedTileID,Lt=tt[vt]=tt[vt]||{};for(let Kt in At){let Mt=At[Kt],Xt=Lt[Kt]=Lt[Kt]||{},Rt=je[Kt]=je[Kt]||[];for(let zt of Mt)Xt[zt.featureIndex]||(Xt[zt.featureIndex]=!0,Rt.push(zt))}}return je}(Re),z)}function ln(z,l){let m=z.tileID,T=l.tileID;return m.overscaledZ-T.overscaledZ||m.canonical.y-T.canonical.y||m.wrap-T.wrap||m.canonical.x-T.canonical.x}function Nn(z,l){let m=z.feature,T=l.getFeatureState(m.layer["source-layer"],m.id);m.source=m.layer.source,m.layer["source-layer"]&&(m.sourceLayer=m.layer["source-layer"]),m.state=T}function Co(z,l,m){return o._(this,void 0,void 0,function*(){let T=z;if(z.url?T=(yield o.j(l.transformRequest(z.url,"Source"),m)).data:yield M.frameAsync(m),!T)return null;let P=o.U(o.e(T,z),["tiles","minzoom","maxzoom","attribution","bounds","scheme","tileSize","encoding"]);return"vector_layers"in T&&T.vector_layers&&(P.vectorLayerIds=T.vector_layers.map(V=>V.id)),P})}class Go{constructor(l,m){l&&(m?this.setSouthWest(l).setNorthEast(m):Array.isArray(l)&&(l.length===4?this.setSouthWest([l[0],l[1]]).setNorthEast([l[2],l[3]]):this.setSouthWest(l[0]).setNorthEast(l[1])))}setNorthEast(l){return this._ne=l instanceof o.V?new o.V(l.lng,l.lat):o.V.convert(l),this}setSouthWest(l){return this._sw=l instanceof o.V?new o.V(l.lng,l.lat):o.V.convert(l),this}extend(l){let m=this._sw,T=this._ne,P,V;if(l instanceof o.V)P=l,V=l;else{if(!(l instanceof Go))return Array.isArray(l)?l.length===4||l.every(Array.isArray)?this.extend(Go.convert(l)):this.extend(o.V.convert(l)):l&&("lng"in l||"lon"in l)&&"lat"in l?this.extend(o.V.convert(l)):this;if(P=l._sw,V=l._ne,!P||!V)return this}return m||T?(m.lng=Math.min(P.lng,m.lng),m.lat=Math.min(P.lat,m.lat),T.lng=Math.max(V.lng,T.lng),T.lat=Math.max(V.lat,T.lat)):(this._sw=new o.V(P.lng,P.lat),this._ne=new o.V(V.lng,V.lat)),this}getCenter(){return new o.V((this._sw.lng+this._ne.lng)/2,(this._sw.lat+this._ne.lat)/2)}getSouthWest(){return this._sw}getNorthEast(){return this._ne}getNorthWest(){return new o.V(this.getWest(),this.getNorth())}getSouthEast(){return new o.V(this.getEast(),this.getSouth())}getWest(){return this._sw.lng}getSouth(){return this._sw.lat}getEast(){return this._ne.lng}getNorth(){return this._ne.lat}toArray(){return[this._sw.toArray(),this._ne.toArray()]}toString(){return`LngLatBounds(${this._sw.toString()}, ${this._ne.toString()})`}isEmpty(){return!(this._sw&&this._ne)}contains(l){let{lng:m,lat:T}=o.V.convert(l),P=this._sw.lng<=m&&m<=this._ne.lng;return this._sw.lng>this._ne.lng&&(P=this._sw.lng>=m&&m>=this._ne.lng),this._sw.lat<=T&&T<=this._ne.lat&&P}intersects(l){if(!((l=Go.convert(l)).getNorth()>=this.getSouth()&&l.getSouth()<=this.getNorth()))return!1;let m=o.W(this.getWest(),-180,180),T=o.W(this.getEast(),-180,180),P=o.W(l.getWest(),-180,180),V=o.W(l.getEast(),-180,180),W=m>=T,te=P>=V;return!(!W||!te)||(W?V>=m||P<=T:te?T>=P||m<=V:P<=T&&V>=m)}static convert(l){return l instanceof Go?l:l&&new Go(l)}static fromLngLat(l,m=0){let T=360*m/40075017,P=T/Math.cos(Math.PI/180*l.lat);return new Go(new o.V(l.lng-P,l.lat-T),new o.V(l.lng+P,l.lat+T))}adjustAntiMeridian(){let l=new o.V(this._sw.lng,this._sw.lat),m=new o.V(this._ne.lng,this._ne.lat);return new Go(l,l.lng>m.lng?new o.V(m.lng+360,m.lat):m)}}class is{constructor(l,m,T){this.bounds=Go.convert(this.validateBounds(l)),this.minzoom=m||0,this.maxzoom=T||24}validateBounds(l){return Array.isArray(l)&&l.length===4?[Math.max(-180,l[0]),Math.max(-90,l[1]),Math.min(180,l[2]),Math.min(90,l[3])]:[-180,-90,180,90]}contains(l){let m=Math.pow(2,l.z),T=Math.floor(o.Y(this.bounds.getWest())*m),P=Math.floor(o.X(this.bounds.getNorth())*m),V=Math.ceil(o.Y(this.bounds.getEast())*m),W=Math.ceil(o.X(this.bounds.getSouth())*m);return l.x>=T&&l.x=P&&l.y{this._options.tiles=l}),this}setUrl(l){return this.setSourceProperty(()=>{this.url=l,this._options.url=l}),this}onRemove(){this._tileJSONRequest&&(this._tileJSONRequest.abort(),this._tileJSONRequest=null)}serialize(){return o.e({},this._options)}loadTile(l){return o._(this,void 0,void 0,function*(){let m=l.tileID.canonical.url(this.tiles,this.map.getPixelRatio(),this.scheme),T={request:this.map._requestManager.transformRequest(m,"Tile"),uid:l.uid,tileID:l.tileID,zoom:l.tileID.overscaledZ,tileSize:this.tileSize*l.tileID.overscaleFactor(),type:this.type,source:this.id,pixelRatio:this.map.getPixelRatio(),showCollisionBoxes:this.map.showCollisionBoxes,promoteId:this.promoteId,subdivisionGranularity:this.map.style.projection.subdivisionGranularity,encoding:this.encoding,overzoomParameters:this._getOverzoomParameters(l)};T.request.collectResourceTiming=this._collectResourceTiming;let P="RT";if(l.actor&&l.state!=="expired"){if(l.state==="loading")return new Promise((V,W)=>{l.reloadPromise={resolve:V,reject:W}})}else l.actor=this.dispatcher.getActor(),P="LT";l.abortController=new AbortController;try{let V=yield l.actor.sendAsync({type:P,data:T},l.abortController);if(delete l.abortController,l.aborted)return;this._afterTileLoadWorkerResponse(l,V)}catch(V){if(delete l.abortController,l.aborted)return;if(V&&V.status!==404)throw V;this._afterTileLoadWorkerResponse(l,null)}})}_getOverzoomParameters(l){if(l.tileID.canonical.z<=this.maxzoom||this.map._zoomLevelsToOverscale===void 0)return;let m=l.tileID.scaledTo(this.maxzoom).canonical,T=m.url(this.tiles,this.map.getPixelRatio(),this.scheme);return{maxZoomTileID:m,overzoomRequest:this.map._requestManager.transformRequest(T,"Tile")}}_afterTileLoadWorkerResponse(l,m){if(m&&m.resourceTiming&&(l.resourceTiming=m.resourceTiming),m&&this.map._refreshExpiredTiles&&l.setExpiryData(m),l.loadVectorData(m,this.map.painter),l.reloadPromise){let T=l.reloadPromise;l.reloadPromise=null,this.loadTile(l).then(T.resolve).catch(T.reject)}}abortTile(l){return o._(this,void 0,void 0,function*(){l.abortController&&(l.abortController.abort(),delete l.abortController),l.actor&&(yield l.actor.sendAsync({type:"AT",data:{uid:l.uid,type:this.type,source:this.id}}))})}unloadTile(l){return o._(this,void 0,void 0,function*(){l.unloadVectorData(),l.actor&&(yield l.actor.sendAsync({type:"RMT",data:{uid:l.uid,type:this.type,source:this.id}}))})}hasTransition(){return!1}}class zs extends o.E{constructor(l,m,T,P){super(),this.id=l,this.dispatcher=T,this.setEventedParent(P),this.type="raster",this.minzoom=0,this.maxzoom=22,this.roundZoom=!0,this.scheme="xyz",this.tileSize=512,this._loaded=!1,this._options=o.e({type:"raster"},m),o.e(this,o.U(m,["url","scheme","tileSize"]))}load(){return o._(this,arguments,void 0,function*(l=!1){this._loaded=!1,this.fire(new o.l("dataloading",{dataType:"source"})),this._tileJSONRequest=new AbortController;try{let m=yield Co(this._options,this.map._requestManager,this._tileJSONRequest);this._tileJSONRequest=null,this._loaded=!0,m&&(o.e(this,m),m.bounds&&(this.tileBounds=new is(m.bounds,this.minzoom,this.maxzoom)),this.fire(new o.l("data",{dataType:"source",sourceDataType:"metadata"})),this.fire(new o.l("data",{dataType:"source",sourceDataType:"content",sourceDataChanged:l})))}catch(m){this._tileJSONRequest=null,this._loaded=!0,o.Z(m)||this.fire(new o.k(m))}})}loaded(){return this._loaded}onAdd(l){this.map=l,this.load()}onRemove(){this._tileJSONRequest&&(this._tileJSONRequest.abort(),this._tileJSONRequest=null)}setSourceProperty(l){this._tileJSONRequest&&(this._tileJSONRequest.abort(),this._tileJSONRequest=null),l(),this.load(!0)}setTiles(l){return this.setSourceProperty(()=>{this._options.tiles=l}),this}setUrl(l){return this.setSourceProperty(()=>{this.url=l,this._options.url=l}),this}serialize(){return o.e({},this._options)}hasTile(l){return!this.tileBounds||this.tileBounds.contains(l.canonical)}loadTile(l){return o._(this,void 0,void 0,function*(){let m=l.tileID.canonical.url(this.tiles,this.map.getPixelRatio(),this.scheme);l.abortController=new AbortController;try{let T=yield Oe.getImage(this.map._requestManager.transformRequest(m,"Tile"),l.abortController,this.map._refreshExpiredTiles);if(delete l.abortController,l.aborted)return void(l.state="unloaded");if(T&&T.data){this.map._refreshExpiredTiles&&(T.cacheControl||T.expires)&&l.setExpiryData({cacheControl:T.cacheControl,expires:T.expires});let P=this.map.painter.context,V=P.gl,W=T.data;l.texture=this.map.painter.getTileTexture(W.width),l.texture?l.texture.update(W,{useMipmap:!0}):(l.texture=new o.T(P,W,V.RGBA,{useMipmap:!0}),l.texture.bind(V.LINEAR,V.CLAMP_TO_EDGE,V.LINEAR_MIPMAP_NEAREST)),l.state="loaded"}}catch(T){if(delete l.abortController,l.aborted)l.state="unloaded";else if(T)throw l.state="errored",T}})}abortTile(l){return o._(this,void 0,void 0,function*(){l.abortController&&(l.abortController.abort(),delete l.abortController)})}unloadTile(l){return o._(this,void 0,void 0,function*(){l.texture&&this.map.painter.saveTileTexture(l.texture)})}hasTransition(){return!1}}class Ba extends zs{constructor(l,m,T,P){super(l,m,T,P),this.type="raster-dem",this.maxzoom=22,this._options=o.e({type:"raster-dem"},m),this.encoding=m.encoding||"mapbox",this.redFactor=m.redFactor,this.greenFactor=m.greenFactor,this.blueFactor=m.blueFactor,this.baseShift=m.baseShift}loadTile(l){return o._(this,void 0,void 0,function*(){let m=l.tileID.canonical.url(this.tiles,this.map.getPixelRatio(),this.scheme),T=this.map._requestManager.transformRequest(m,"Tile");l.neighboringTiles=this._getNeighboringTiles(l.tileID),l.abortController=new AbortController;try{let P=yield Oe.getImage(T,l.abortController,this.map._refreshExpiredTiles);if(delete l.abortController,l.aborted)return void(l.state="unloaded");if(P&&P.data){let V=P.data;this.map._refreshExpiredTiles&&(P.cacheControl||P.expires)&&l.setExpiryData({cacheControl:P.cacheControl,expires:P.expires});let W=o.b(V)&&o.$()?V:yield this.readImageNow(V),te={type:this.type,uid:l.uid,source:this.id,rawImageData:W,encoding:this.encoding,redFactor:this.redFactor,greenFactor:this.greenFactor,blueFactor:this.blueFactor,baseShift:this.baseShift};if(!l.actor||l.state==="expired"){l.actor=this.dispatcher.getActor();let se=yield l.actor.sendAsync({type:"LDT",data:te});l.dem=se,l.needsHillshadePrepare=!0,l.needsTerrainPrepare=!0,l.state="loaded"}}}catch(P){if(delete l.abortController,l.aborted)l.state="unloaded";else if(P)throw l.state="errored",P}})}readImageNow(l){return o._(this,void 0,void 0,function*(){if(typeof VideoFrame<"u"&&o.a0()){let m=l.width+2,T=l.height+2;try{return new o.R({width:m,height:T},yield o.a1(l,-1,-1,m,T))}catch{}}return M.getImageData(l,1)})}_getNeighboringTiles(l){let m=l.canonical,T=Math.pow(2,m.z),P=(m.x-1+T)%T,V=m.x===0?l.wrap-1:l.wrap,W=(m.x+1+T)%T,te=m.x+1===T?l.wrap+1:l.wrap,se={};return se[new o.a2(l.overscaledZ,V,m.z,P,m.y).key]={backfilled:!1},se[new o.a2(l.overscaledZ,te,m.z,W,m.y).key]={backfilled:!1},m.y>0&&(se[new o.a2(l.overscaledZ,V,m.z,P,m.y-1).key]={backfilled:!1},se[new o.a2(l.overscaledZ,l.wrap,m.z,m.x,m.y-1).key]={backfilled:!1},se[new o.a2(l.overscaledZ,te,m.z,W,m.y-1).key]={backfilled:!1}),m.y+1l.coordinates).flat(1/0):z.coordinates.flat(1/0)}function so(z){let l=new Go,m;switch(z.type){case"FeatureCollection":m=z.features.map(T=>ds(T.geometry)).flat(1/0);break;case"Feature":m=ds(z.geometry);break;default:m=ds(z)}if(m.length==0)return l;for(let T=0;T0&&o.e(W,{resourceTiming:V}),this.fire(new o.l("data",Object.assign(Object.assign({},W),{sourceDataType:"metadata"}))),this.fire(new o.l("data",Object.assign(Object.assign({},W),{sourceDataType:"content",shouldReloadTileOptions:this._getShouldReloadTileOptions(m)})))}catch(P){if(this._isUpdatingWorker=!1,this._removed)return void this.fire(new o.l("dataabort",{dataType:"source"}));this.fire(new o.k(P))}finally{this._hasPendingWorkerUpdate()&&this._updateWorkerData()}})}_applyDiff(l){let m=typeof this.promoteId=="string"?this.promoteId:void 0;if(!this._data.url&&!this._data.updateable){if(!o.a7(this._data.geojson,m))throw new Error(`GeoJSONSource "${this.id}": GeoJSON data is not compatible with updateData`);this._data={updateable:o.a8(this._data.geojson,m)}}this._data.updateable&&o.a9(this._data.updateable,l,m)}_getShouldReloadTileOptions(l){if(this._options.cluster||!l||l.removeAll)return;let{add:m=[],update:T=[],remove:P=[]}=l||{},V=new Set([...T.map(W=>W.id),...P]);for(let W of V.values())if(typeof W!="number"&&this.promoteId==null)return void o.w(`GeoJSONSource "${this.id}": updateData is slower when using string GeoJSON feature IDs. Consider using promoteId or numeric IDs for better performance.`);return{nextBounds:[...T.map(W=>W.newGeometry),...m.map(W=>W.geometry)].filter(Boolean).map(W=>so(W)),prevIds:V}}shouldReloadTile(l,{nextBounds:m,prevIds:T}){if(!l.latestFeatureIndex)return l.state!=="unloaded";let P=l.latestFeatureIndex.loadVTLayers();for(let se=0;se{this.texture=null}),this):this}_finishLoading(){this.map&&(this.setCoordinates(this.coordinates),this.fire(new o.l("data",{dataType:"source",sourceDataType:"metadata"})))}onAdd(l){this.map=l,this.load()}onRemove(){this._request&&(this._request.abort(),this._request=null)}setCoordinates(l){this.coordinates=l;let m=l.map(o.ab.fromLngLat);var T;return this.tileID=function(P){let V=o.ac.fromPoints(P),W=V.width(),te=V.height(),se=Math.max(W,te),fe=Math.max(0,Math.floor(-Math.log(se)/Math.LN2)),_e=Math.pow(2,fe);return new o.ae(fe,Math.floor((V.minX+V.maxX)/2*_e),Math.floor((V.minY+V.maxY)/2*_e))}(m),this.terrainTileRanges=this._getOverlappingTileRanges(m),this.minzoom=this.maxzoom=this.tileID.z,this.tileCoords=m.map(P=>this.tileID.getTilePoint(P)._round()),this.flippedWindingOrder=((T=this.tileCoords)[1].x-T[0].x)*(T[2].y-T[0].y)-(T[1].y-T[0].y)*(T[2].x-T[0].x)<0,this.fire(new o.l("data",{dataType:"source",sourceDataType:"content"})),this}prepare(){if(Object.keys(this.tiles).length===0||!this.image)return;let l=this.map.painter.context,m=l.gl;this.texture||(this.texture=new o.T(l,this.image,m.RGBA),this.texture.bind(m.LINEAR,m.CLAMP_TO_EDGE));let T=!1;for(let P in this.tiles){let V=this.tiles[P];V.state!=="loaded"&&(V.state="loaded",V.texture=this.texture,T=!0)}T&&this.fire(new o.l("data",{dataType:"source",sourceDataType:"idle",sourceId:this.id}))}loadTile(l){return o._(this,void 0,void 0,function*(){this.tileID&&this.tileID.equals(l.tileID.canonical)?(this.tiles[String(l.tileID.wrap)]=l,l.buckets={}):l.state="errored"})}serialize(){return{type:"image",url:this.options.url,coordinates:this.coordinates}}hasTransition(){return!1}_getOverlappingTileRanges(l){let{minX:m,minY:T,maxX:P,maxY:V}=o.ac.fromPoints(l),W={};for(let te=0;te<=o.ad;te++){let se=Math.pow(2,te),fe=Math.floor(m*se),_e=Math.floor(T*se),Re=Math.floor(P*se),Ie=Math.floor(V*se);W[te]={minTileX:fe,minTileY:_e,maxTileX:Re,maxTileY:Ie}}return W}}class Yt extends ws{constructor(l,m,T,P){super(l,m,T,P),this.roundZoom=!0,this.type="video",this.options=m}load(){return o._(this,void 0,void 0,function*(){this._loaded=!1;let l=this.options;this.urls=[];for(let m of l.urls)this.urls.push(this.map._requestManager.transformRequest(m,"Source").url);try{let m=yield o.af(this.urls);if(this._loaded=!0,!m)return;this.video=m,this.video.loop=!0,this.video.addEventListener("playing",()=>{this.map.triggerRepaint()}),this.map&&this.video.play(),this._finishLoading()}catch(m){this.fire(new o.k(m))}})}pause(){this.video&&this.video.pause()}play(){this.video&&this.video.play()}seek(l){if(this.video){let m=this.video.seekable;lm.end(0)?this.fire(new o.k(new o.ag(`sources.${this.id}`,null,`Playback for this video can be set only between the ${m.start(0)} and ${m.end(0)}-second mark.`))):this.video.currentTime=l}}getVideo(){return this.video}onAdd(l){this.map||(this.map=l,this.load(),this.video&&(this.video.play(),this.setCoordinates(this.coordinates)))}prepare(){if(Object.keys(this.tiles).length===0||this.video.readyState<2)return;let l=this.map.painter.context,m=l.gl;this.texture?this.video.paused||(this.texture.bind(m.LINEAR,m.CLAMP_TO_EDGE),m.texSubImage2D(m.TEXTURE_2D,0,0,0,m.RGBA,m.UNSIGNED_BYTE,this.video)):(this.texture=new o.T(l,this.video,m.RGBA),this.texture.bind(m.LINEAR,m.CLAMP_TO_EDGE));let T=!1;for(let P in this.tiles){let V=this.tiles[P];V.state!=="loaded"&&(V.state="loaded",V.texture=this.texture,T=!0)}T&&this.fire(new o.l("data",{dataType:"source",sourceDataType:"idle",sourceId:this.id}))}serialize(){return{type:"video",urls:this.urls,coordinates:this.coordinates}}hasTransition(){return this.video&&!this.video.paused}}class Fe extends ws{constructor(l,m,T,P){super(l,m,T,P),m.coordinates?Array.isArray(m.coordinates)&&m.coordinates.length===4&&!m.coordinates.some(V=>!Array.isArray(V)||V.length!==2||V.some(W=>typeof W!="number"))||this.fire(new o.k(new o.ag(`sources.${l}`,null,'"coordinates" property must be an array of 4 longitude/latitude array pairs'))):this.fire(new o.k(new o.ag(`sources.${l}`,null,'missing required property "coordinates"'))),m.animate&&typeof m.animate!="boolean"&&this.fire(new o.k(new o.ag(`sources.${l}`,null,'optional "animate" property must be a boolean value'))),m.canvas?typeof m.canvas=="string"||m.canvas instanceof HTMLCanvasElement||this.fire(new o.k(new o.ag(`sources.${l}`,null,'"canvas" must be either a string representing the ID of the canvas element from which to read, or an HTMLCanvasElement instance'))):this.fire(new o.k(new o.ag(`sources.${l}`,null,'missing required property "canvas"'))),this.options=m,this.animate=m.animate===void 0||m.animate}load(){return o._(this,void 0,void 0,function*(){this._loaded=!0,this.canvas||(this.canvas=this.options.canvas instanceof HTMLCanvasElement?this.options.canvas:document.getElementById(this.options.canvas)),this.width=this.canvas.width,this.height=this.canvas.height,this._hasInvalidDimensions()?this.fire(new o.k(new Error("Canvas dimensions cannot be less than or equal to zero."))):(this.play=function(){this._playing=!0,this.map.triggerRepaint()},this.pause=function(){this._playing&&(this.prepare(),this._playing=!1)},this._finishLoading())})}getCanvas(){return this.canvas}onAdd(l){this.map=l,this.load(),this.canvas&&this.animate&&this.play()}onRemove(){this.pause()}prepare(){let l=!1;if(this.canvas.width!==this.width&&(this.width=this.canvas.width,l=!0),this.canvas.height!==this.height&&(this.height=this.canvas.height,l=!0),this._hasInvalidDimensions()||Object.keys(this.tiles).length===0)return;let m=this.map.painter.context,T=m.gl;this.texture?(l||this._playing)&&this.texture.update(this.canvas,{premultiply:!0}):(this.texture=new o.T(m,this.canvas,T.RGBA,{premultiply:!0}),this.texture.bind(T.LINEAR,T.CLAMP_TO_EDGE));let P=!1;for(let V in this.tiles){let W=this.tiles[V];W.state!=="loaded"&&(W.state="loaded",W.texture=this.texture,P=!0)}P&&this.fire(new o.l("data",{dataType:"source",sourceDataType:"idle",sourceId:this.id}))}serialize(){return{type:"canvas",animate:this.animate,canvas:this.options.canvas,coordinates:this.coordinates}}hasTransition(){return this._playing}_hasInvalidDimensions(){for(let l of[this.canvas.width,this.canvas.height])if(isNaN(l)||l<=0)return!0;return!1}}let Ne={},rt=z=>{switch(z){case"geojson":return ps;case"image":return ws;case"raster":return zs;case"raster-dem":return Ba;case"vector":return mo;case"video":return Yt;case"canvas":return Fe}return Ne[z]},Vt="RTLPluginLoaded";class Ht extends o.E{constructor(){super(...arguments),this.status="unavailable",this.url=null,this.dispatcher=mr()}_syncState(l){return this.status=l,this.dispatcher.broadcast("SRPS",{pluginStatus:l,pluginURL:this.url}).catch(m=>{throw this.status="error",m})}getRTLTextPluginStatus(){return this.status}clearRTLTextPlugin(){this.status="unavailable",this.url=null}setRTLTextPlugin(l){return o._(this,arguments,void 0,function*(m,T=!1){if(this.url)throw new Error("setRTLTextPlugin cannot be called multiple times.");if(this.url=M.resolveURL(m),!this.url)throw new Error(`requested url ${m} is invalid`);if(this.status==="unavailable"){if(!T)return this._requestImport();this.status="deferred",this._syncState(this.status)}else if(this.status==="requested")return this._requestImport()})}_requestImport(){return o._(this,void 0,void 0,function*(){yield this._syncState("loading"),this.status="loaded",this.fire(new o.l(Vt))})}lazyLoad(){this.status==="unavailable"?this.status="requested":this.status==="deferred"&&this._requestImport()}}let yr=null;function fr(){return yr||(yr=new Ht),yr}var nr,xr;(function(z){z[z.Base=0]="Base",z[z.Parent=1]="Parent"})(nr||(nr={})),function(z){z[z.Departing=0]="Departing",z[z.Incoming=1]="Incoming"}(xr||(xr={}));class ti{constructor(l,m){this.timeAdded=0,this.fadeEndTime=0,this.fadeOpacity=1,this.tileID=l,this.uid=o.ah(),this.uses=0,this.tileSize=m,this.buckets={},this.expirationTime=null,this.queryPadding=0,this.hasSymbolBuckets=!1,this.hasRTLText=!1,this.dependencies={},this.rtt=[],this.rttCoords={},this.expiredRequestCount=0,this.state="loading"}isRenderable(l){return this.hasData()&&(!this.fadeEndTime||this.fadeOpacity>0)&&(l||!this.holdingForSymbolFade())}setCrossFadeLogic({fadingRole:l,fadingDirection:m,fadingParentID:T,fadeEndTime:P}){this.resetFadeLogic(),this.fadingRole=l,this.fadingDirection=m,this.fadingParentID=T,this.fadeEndTime=P}setSelfFadeLogic(l){this.resetFadeLogic(),this.selfFading=!0,this.fadeEndTime=l}resetFadeLogic(){this.fadingRole=null,this.fadingDirection=null,this.fadingParentID=null,this.selfFading=!1,this.timeAdded=k(),this.fadeEndTime=0,this.fadeOpacity=1}wasRequested(){return this.state==="errored"||this.state==="loaded"||this.state==="reloading"}clearTextures(l){this.demTexture&&l.saveTileTexture(this.demTexture),this.demTexture=null}loadVectorData(l,m,T){if(this.hasData()&&this.unloadVectorData(),this.state="loaded",l){l.featureIndex&&(this.latestFeatureIndex=l.featureIndex,l.rawTileData?(this.latestRawTileData=l.rawTileData,this.latestFeatureIndex.rawTileData=l.rawTileData,this.latestFeatureIndex.encoding=l.encoding):this.latestRawTileData&&(this.latestFeatureIndex.rawTileData=this.latestRawTileData,this.latestFeatureIndex.encoding=this.latestEncoding)),this.collisionBoxArray=l.collisionBoxArray,this.buckets=function(P,V){let W={};if(!V)return W;for(let te of P){let se=te.layerIds.map(fe=>V.getLayer(fe)).filter(Boolean);if(se.length!==0){te.layers=se,te.stateDependentLayerIds&&(te.stateDependentLayers=te.stateDependentLayerIds.map(fe=>se.filter(_e=>_e.id===fe)[0]));for(let fe of se)W[fe.id]=te}}return W}(l.buckets,m?.style),this.hasSymbolBuckets=!1;for(let P in this.buckets){let V=this.buckets[P];if(V instanceof o.aj){if(this.hasSymbolBuckets=!0,!T)break;V.justReloaded=!0}}if(this.hasRTLText=!1,this.hasSymbolBuckets)for(let P in this.buckets){let V=this.buckets[P];if(V instanceof o.aj&&V.hasRTLText){this.hasRTLText=!0,fr().lazyLoad();break}}this.queryPadding=0;for(let P in this.buckets){let V=this.buckets[P];this.queryPadding=Math.max(this.queryPadding,m.style.getLayer(P).queryRadius(V))}l.imageAtlas&&(this.imageAtlas=l.imageAtlas),l.glyphAtlasImage&&(this.glyphAtlasImage=l.glyphAtlasImage),this.dashPositions=l.dashPositions}else this.collisionBoxArray=new o.ai}unloadVectorData(){for(let l in this.buckets)this.buckets[l].destroy();this.buckets={},this.imageAtlasTexture&&this.imageAtlasTexture.destroy(),this.imageAtlas&&(this.imageAtlas=null),this.glyphAtlasTexture&&this.glyphAtlasTexture.destroy(),this.dashPositions&&(this.dashPositions=null),this.latestFeatureIndex=null,this.state="unloaded"}getBucket(l){return this.buckets[l.id]}upload(l){for(let T in this.buckets){let P=this.buckets[T];P.uploadPending()&&P.upload(l)}let m=l.gl;this.imageAtlas&&!this.imageAtlas.uploaded&&(this.imageAtlasTexture=new o.T(l,this.imageAtlas.image,m.RGBA),this.imageAtlas.uploaded=!0),this.glyphAtlasImage&&(this.glyphAtlasTexture=new o.T(l,this.glyphAtlasImage,m.ALPHA),this.glyphAtlasImage=null)}prepare(l){this.imageAtlas&&this.imageAtlas.patchUpdatedImages(l,this.imageAtlasTexture)}queryRenderedFeatures(l,m,T,P,V,W,te,se,fe,_e,Re){return this.latestFeatureIndex&&this.latestFeatureIndex.rawTileData?this.latestFeatureIndex.query({queryGeometry:P,cameraQueryGeometry:V,scale:W,tileSize:this.tileSize,pixelPosMatrix:_e,transform:se,params:te,queryPadding:this.queryPadding*fe,getElevation:Re},l,m,T):{}}querySourceFeatures(l,m){let T=this.latestFeatureIndex;if(!T||!T.rawTileData)return;let P=T.loadVTLayers(),V=m&&m.sourceLayer?m.sourceLayer:"",W=P[o.aa]||P[V];if(!W)return;let te=o.ak(m?.filter,m?.globalState),{z:se,x:fe,y:_e}=this.tileID.canonical,Re={z:se,x:fe,y:_e};for(let Ie=0;IeT)P=!1;else if(m)if(this.expirationTime({zoom:0,x:0,y:0,wrap:Xt,fullyVisible:!1}),Kt=[],Mt=[];if(z.renderWorldCopies&&W.allowWorldCopies())for(let Xt=1;Xt<=3;Xt++)Kt.push(Lt(-Xt)),Kt.push(Lt(Xt));for(Kt.push(Lt(0));Kt.length>0;){let Xt=Kt.pop(),Rt=Xt.x,zt=Xt.y,jt=Xt.fullyVisible,br={x:Rt,y:zt,z:Xt.zoom},Cr=W.getTileBoundingVolume(br,Xt.wrap,z.elevation,l);if(!jt){let _i=ns(m,Cr,T);if(_i===0)continue;jt=_i===2}let Pr=W.distanceToTile2d(P.x,P.y,br,Cr),Ir=se;te&&(Ir=(l.calculateTileZoom||it)(z.zoom+o.au(z.tileSize/l.tileSize),Pr,At,vt,z.fov)),Ir=(l.roundZoom?Math.round:Math.floor)(Ir),Ir=Math.max(0,Ir);let Ai=Math.min(Ir,_e);if(Xt.wrap=W.getWrap(V,br,Xt.wrap),Xt.zoom>=Ai){if(Xt.zoom>1),wrap:Xt.wrap,fullyVisible:jt})}return Mt.sort((Xt,Rt)=>Xt.distanceSq-Rt.distanceSq).map(Xt=>Xt.tileID)}let $t=o.ac.fromPoints([new o.P(0,0),new o.P(o.a5,o.a5)]);function Jt(z){return z==="raster"||z==="image"||z==="video"}function ht(z,l,m,T,P,V,W){if(!l.hasData())return!1;let{tileID:te,fadingRole:se,fadingDirection:fe,fadingParentID:_e}=l;if(se===nr.Base&&fe===xr.Incoming&&_e)return m[_e.key]=_e,!0;let Re=Math.max(te.overscaledZ-P,V);for(let Ie=te.overscaledZ-1;Ie>=Re;Ie--){let je=te.scaledTo(Ie),tt=z.getLoadedTile(je);if(tt)return l.setCrossFadeLogic({fadingRole:nr.Base,fadingDirection:xr.Incoming,fadingParentID:tt.tileID,fadeEndTime:T+W}),tt.setCrossFadeLogic({fadingRole:nr.Parent,fadingDirection:xr.Departing,fadeEndTime:T+W}),m[je.key]=je,!0}return!1}function Ot(z,l,m,T,P,V){if(!l.hasData())return!1;let W=l.tileID.children(P),te=Ct(z,l,W,m,T,P,V);if(te)return!0;for(let se of W)Ct(z,l,se.children(P),m,T,P,V)&&(te=!0);return te}function Ct(z,l,m,T,P,V,W){if(m[0].overscaledZ>=V)return!1;let te=!1;for(let se of m){let fe=z.getLoadedTile(se);if(!fe)continue;let{fadingRole:_e,fadingDirection:Re,fadingParentID:Ie}=fe;_e===nr.Base&&Re===xr.Departing&&Ie||(fe.setCrossFadeLogic({fadingRole:nr.Base,fadingDirection:xr.Departing,fadingParentID:l.tileID,fadeEndTime:P+W}),l.setCrossFadeLogic({fadingRole:nr.Parent,fadingDirection:xr.Incoming,fadeEndTime:P+W})),T[se.key]=se,te=!0}return te}function ei(z,l,m,T){let P=z.tileID;return!!z.selfFading||!z.hasData()&&!!l.has(P)&&(z.setSelfFadeLogic(m+T),!0)}function ui(z,l){var m;z.needsHillshadePrepare=!0,z.needsTerrainPrepare=!0;let T=l.tileID.canonical.x-z.tileID.canonical.x,P=l.tileID.canonical.y-z.tileID.canonical.y,V=Math.pow(2,z.tileID.canonical.z),W=l.tileID.key;T===0&&P===0||Math.abs(P)>1||(Math.abs(T)>1&&(Math.abs(T+V)===1?T+=V:Math.abs(T-V)===1&&(T-=V)),l.dem&&z.dem&&(z.dem.backfillBorder(l.dem,T,P),!((m=z.neighboringTiles)===null||m===void 0)&&m[W]&&(z.neighboringTiles[W].backfilled=!0)))}class Gr{constructor(){this._tiles={}}handleWrapJump(l){let m={};for(let T in this._tiles){let P=this._tiles[T];P.tileID=P.tileID.unwrapTo(P.tileID.wrap+l),m[P.tileID.key]=P}this._tiles=m}setFeatureState(l,m){for(let T in this._tiles)this._tiles[T].setFeatureState(l,m)}getAllTiles(){return Object.values(this._tiles)}getAllIds(l=!1){return l?Object.values(this._tiles).map(m=>m.tileID).sort(o.ax).map(m=>m.key):Object.keys(this._tiles)}getTileById(l){return this._tiles[l]}setTile(l,m){this._tiles[l]=m}deleteTileById(l){delete this._tiles[l]}getLoadedTile(l){let m=this.getTileById(l.key);return m?.hasData()?m:null}isIdRenderable(l,m=!1){var T;return(T=this.getTileById(l))===null||T===void 0?void 0:T.isRenderable(m)}getRenderableIds(l=0,m){let T=[];for(let P of this.getAllIds())this.isIdRenderable(P,m)&&T.push(this.getTileById(P));return m?T.sort((P,V)=>{let W=P.tileID,te=V.tileID,se=new o.P(W.canonical.x,W.canonical.y)._rotate(-l),fe=new o.P(te.canonical.x,te.canonical.y)._rotate(-l);return W.overscaledZ-te.overscaledZ||fe.y-se.y||fe.x-se.x}).map(P=>P.tileID.key):T.map(P=>P.tileID).sort(o.ax).map(P=>P.key)}}class si extends o.E{constructor(l,m,T){super(),this.id=l,this.dispatcher=T,this.on("data",P=>this._dataHandler(P)),this.on("dataloading",()=>{this._sourceErrored=!1}),this.on("error",()=>{this._sourceErrored=this._source.loaded()}),this._source=((P,V,W,te)=>{let se=new(rt(V.type))(P,V,W,te);if(se.id!==P)throw new Error(`Expected Source id to be ${P} instead of ${se.id}`);return se})(l,m,T,this),this._inViewTiles=new Gr,this._outOfViewCache=new o.ay(0,P=>this._unloadTile(P)),this._timers={},this._maxTileCacheSize=null,this._maxTileCacheZoomLevels=null,this._rasterFadeDuration=0,this._maxFadingAncestorLevels=5,this._state=new Jr,this._didEmitContent=!1,this._updated=!1}onAdd(l){this.map=l,this._maxTileCacheSize=l?l._maxTileCacheSize:null,this._maxTileCacheZoomLevels=l?l._maxTileCacheZoomLevels:null,this._source&&this._source.onAdd&&this._source.onAdd(l)}onRemove(l){for(let m of this._inViewTiles.getAllTiles())m.unloadVectorData();this.clearTiles(),this._source&&this._source.onRemove&&this._source.onRemove(l),this._inViewTiles=new Gr}loaded(){if(this._sourceErrored)return!0;if(!this._sourceLoaded||!this._source.loaded())return!1;if(!(this.used===void 0&&this.usedForTerrain===void 0||this.used||this.usedForTerrain))return!0;if(!this._updated)return!1;for(let l of this._inViewTiles.getAllTiles())if(l.state!=="loaded"&&l.state!=="errored")return!1;return!0}getSource(){return this._source}getState(){return this._state}pause(){this._paused=!0}resume(){if(!this._paused)return;let l=this._shouldReloadOnResume;this._paused=!1,this._shouldReloadOnResume=!1,l&&this.reload(),this.transform&&this.update(this.transform,this.terrain)}_loadTile(l,m,T){return o._(this,void 0,void 0,function*(){try{yield this._source.loadTile(l),this._tileLoaded(l,m,T)}catch(P){l.state="errored",P.status!==404?this._source.fire(new o.k(P,{tile:l})):this.update(this.transform,this.terrain)}})}_unloadTile(l){this._source.unloadTile&&this._source.unloadTile(l)}_abortTile(l){this._source.abortTile&&this._source.abortTile(l),this._source.fire(new o.l("dataabort",{tile:l,coord:l.tileID,dataType:"source"}))}serialize(){return this._source.serialize()}prepare(l){this._source.prepare&&this._source.prepare(),this._state.coalesceChanges(this._inViewTiles,this.map?this.map.painter:null);for(let m of this._inViewTiles.getAllTiles())m.upload(l),m.prepare(this.map.style.imageManager)}getIds(){return this._inViewTiles.getAllIds(!0)}getRenderableIds(l){var m;return this._inViewTiles.getRenderableIds((m=this.transform)===null||m===void 0?void 0:m.bearingInRadians,l)}hasRenderableParent(l){let m=l.overscaledZ-1;if(m>=this._source.minzoom){let T=this.getLoadedTile(l.scaledTo(m));if(T)return this._inViewTiles.isIdRenderable(T.tileID.key)}return!1}reload(l,m=void 0){if(this._paused)this._shouldReloadOnResume=!0;else{this._outOfViewCache.reset();for(let T of this._inViewTiles.getAllIds()){let P=this._inViewTiles.getTileById(T);m&&!this._source.shouldReloadTile(P,m)||(l?this._reloadTile(T,"expired"):P.state!=="errored"&&this._reloadTile(T,"reloading"))}}}_reloadTile(l,m){return o._(this,void 0,void 0,function*(){let T=this._inViewTiles.getTileById(l);T&&(T.state!=="loading"&&(T.state=m),yield this._loadTile(T,l,m))})}_tileLoaded(l,m,T){l.timeAdded=k(),l.selfFading&&(l.fadeEndTime=l.timeAdded+this._rasterFadeDuration),T==="expired"&&(l.refreshedUponExpiration=!0),this._setTileReloadTimer(m,l),this.getSource().type==="raster-dem"&&l.dem&&function(P,V){var W,te;let se=V.getRenderableIds();for(let fe of se){if(!P.neighboringTiles||!P.neighboringTiles[fe])continue;let _e=V.getTileById(fe);P.neighboringTiles[fe].backfilled||ui(P,_e),!((te=(W=_e.neighboringTiles)===null||W===void 0?void 0:W[P.tileID.key])===null||te===void 0)&&te.backfilled||ui(_e,P)}}(l,this._inViewTiles),this._state.initializeTileState(l,this.map?this.map.painter:null),l.aborted||this._source.fire(new o.l("data",{dataType:"source",tile:l,coord:l.tileID}))}getTile(l){return this.getTileByID(l.key)}getTileByID(l){return this._inViewTiles.getTileById(l)}_retainLoadedChildren(l,m){let T=this._getLoadedDescendents(m),P=new Set;for(let V of m){let W=T[V.key];if(!W?.length){P.add(V);continue}let te=V.overscaledZ+si.maxOverzooming,se=W.filter(Re=>Re.tileID.overscaledZ<=te);if(!se.length){P.add(V);continue}let fe=Math.min(...se.map(Re=>Re.tileID.overscaledZ)),_e=se.filter(Re=>Re.tileID.overscaledZ===fe).map(Re=>Re.tileID);for(let Re of _e)l[Re.key]=Re;this._areDescendentsComplete(_e,fe,V.overscaledZ)||P.add(V)}return P}_getLoadedDescendents(l){var m;let T={};for(let P of this._inViewTiles.getAllTiles().filter(V=>V.hasData()))for(let V of l)P.tileID.isChildOf(V)&&(T[m=V.key]||(T[m]=[])).push(P);return T}_areDescendentsComplete(l,m,T){return l.length===1&&l[0].isOverscaled()?l[0].overscaledZ===m:Math.pow(4,m-T)===l.length}getLoadedTile(l){return this._inViewTiles.getLoadedTile(l)}updateCacheSize(l){let m=Math.ceil(l.width/this._source.tileSize)+1,T=Math.ceil(l.height/this._source.tileSize)+1,P=Math.floor(m*T*(this._maxTileCacheZoomLevels===null?o.c.MAX_TILE_CACHE_ZOOM_LEVELS:this._maxTileCacheZoomLevels)),V=typeof this._maxTileCacheSize=="number"?Math.min(this._maxTileCacheSize,P):P;this._outOfViewCache.setMaxSize(V)}handleWrapJump(l){let m=Math.round((l-(this._prevLng===void 0?l:this._prevLng))/360);this._prevLng=l,m&&(this._inViewTiles.handleWrapJump(m),this._resetTileReloadTimers())}update(l,m){if(!this._sourceLoaded||this._paused)return;let T;this.transform=l,this.terrain=m,this.updateCacheSize(l),this.handleWrapJump(this.transform.center.lng),this.used||this.usedForTerrain?this._source.tileID?T=l.getVisibleUnwrappedCoordinates(this._source.tileID).map(se=>new o.a2(se.canonical.z,se.wrap,se.canonical.z,se.canonical.x,se.canonical.y)):(T=Ke(l,{tileSize:this.usedForTerrain?this.tileSize:this._source.tileSize,minzoom:this._source.minzoom,maxzoom:this._source.type==="vector"&&this.map._zoomLevelsToOverscale!==void 0?l.maxZoom-this.map._zoomLevelsToOverscale:this._source.maxzoom,roundZoom:!this.usedForTerrain&&this._source.roundZoom,reparseOverscaled:this._source.reparseOverscaled,terrain:m,calculateTileZoom:this._source.calculateTileZoom}),this._source.hasTile&&(T=T.filter(se=>this._source.hasTile(se)))):T=[],this.usedForTerrain&&(T=this._addTerrainIdealTiles(T));let P=T.length===0&&!this._updated&&this._didEmitContent;this._updated=!0,P&&this.fire(new o.l("data",{sourceDataType:"idle",dataType:"source",sourceId:this.id}));let V=mt(l,this._source),W=this._updateRetainedTiles(T,V),te=Jt(this._source.type);te&&this._rasterFadeDuration>0&&!m&&function(se,fe,_e,Re,Ie,je,tt){let yt=k(),At=o.aw(fe);for(let vt of fe){let Lt=se.getTileById(vt.key);Lt.fadingDirection!==xr.Departing&&Lt.fadeOpacity!==0||Lt.resetFadeLogic(),ht(se,Lt,_e,yt,Re,Ie,tt)||Ot(se,Lt,_e,yt,je,tt)||ei(Lt,At,yt,tt)||Lt.resetFadeLogic()}}(this._inViewTiles,T,W,this._maxFadingAncestorLevels,this._source.minzoom,this._source.maxzoom,this._rasterFadeDuration),te?this._cleanUpRasterTiles(W):this._cleanUpVectorTiles(W)}_cleanUpRasterTiles(l){for(let m of this._inViewTiles.getAllIds())l[m]||this._removeTile(m)}_cleanUpVectorTiles(l){for(let m of this._inViewTiles.getAllIds()){let T=this._inViewTiles.getTileById(m);l[m]?T.clearSymbolFadeHold():T.hasSymbolBuckets?T.holdingForSymbolFade()?T.symbolFadeFinished()&&this._removeTile(m):T.setSymbolHoldDuration(this.map._fadeDuration):this._removeTile(m)}}_addTerrainIdealTiles(l){let m=[];for(let T of l)if(T.canonical.z>this._source.minzoom){let P=T.scaledTo(T.canonical.z-1);m.push(P);let V=T.scaledTo(Math.max(this._source.minzoom,Math.min(T.canonical.z,5)));m.push(V)}return l.concat(m)}releaseSymbolFadeTiles(){for(let l of this._inViewTiles.getAllIds())this._inViewTiles.getTileById(l).holdingForSymbolFade()&&this._removeTile(l)}_updateRetainedTiles(l,m){var T;let P=new Set;for(let fe of l)this._addTile(fe).hasData()||P.add(fe);let V=l.reduce((fe,_e)=>(fe[_e.key]=_e,fe),{}),W=this._retainLoadedChildren(V,P),te={},se=Math.max(m-si.maxUnderzooming,this._source.minzoom);for(let fe of W){let _e=this._inViewTiles.getTileById(fe.key),Re=_e?.wasRequested();for(let Ie=fe.overscaledZ-1;Ie>=se;--Ie){let je=fe.scaledTo(Ie);if(te[je.key])break;if(te[je.key]=!0,_e=this.getTile(je),!_e&&Re&&(_e=this._addTile(je)),_e){let tt=_e.hasData();if((tt||!(!((T=this.map)===null||T===void 0)&&T.cancelPendingTileRequestsWhileZooming)||Re)&&(V[je.key]=je),Re=_e.wasRequested(),tt)break}}}return V}_addTile(l){let m=this._inViewTiles.getTileById(l.key);if(m)return m;m=this._outOfViewCache.getAndRemove(l),m&&(m.resetFadeLogic(),this._setTileReloadTimer(l.key,m),m.tileID=l,this._state.initializeTileState(m,this.map?this.map.painter:null));let T=m;return m||(m=new ti(l,this._source.tileSize*l.overscaleFactor()),this._loadTile(m,l.key,m.state)),m.uses++,this._inViewTiles.setTile(l.key,m),T||this._source.fire(new o.l("dataloading",{tile:m,coord:m.tileID,dataType:"source"})),m}_setTileReloadTimer(l,m){this._clearTileReloadTimer(l);let T=m.getExpiryTimeout();T&&(this._timers[l]=setTimeout(()=>{this._reloadTile(l,"expired"),delete this._timers[l]},T))}_clearTileReloadTimer(l){let m=this._timers[l];m&&(clearTimeout(m),delete this._timers[l])}_resetTileReloadTimers(){for(let l in this._timers)clearTimeout(this._timers[l]),delete this._timers[l];for(let l of this._inViewTiles.getAllIds()){let m=this._inViewTiles.getTileById(l);this._setTileReloadTimer(l,m)}}refreshTiles(l){for(let m of this._inViewTiles.getAllIds()){let T=this._inViewTiles.getTileById(m);(this._inViewTiles.isIdRenderable(m)||T.state=="errored")&&l.some(P=>P.equals(T.tileID.canonical))&&this._reloadTile(m,"expired")}}_removeTile(l){let m=this._inViewTiles.getTileById(l);m&&(m.uses--,this._inViewTiles.deleteTileById(l),this._clearTileReloadTimer(l),m.uses>0||(m.hasData()&&m.state!=="reloading"?this._outOfViewCache.add(m.tileID,m,m.getExpiryTimeout()):(m.aborted=!0,this._abortTile(m),this._unloadTile(m))))}_dataHandler(l){l.dataType==="source"&&(l.sourceDataType!=="metadata"?l.sourceDataType==="content"&&this._sourceLoaded&&!this._paused&&(this.reload(l.sourceDataChanged,l.shouldReloadTileOptions),this.transform&&this.update(this.transform,this.terrain),this._didEmitContent=!0):this._sourceLoaded=!0)}clearTiles(){this._shouldReloadOnResume=!1,this._paused=!1;for(let l of this._inViewTiles.getAllIds())this._removeTile(l);this._outOfViewCache.reset()}tilesIn(l,m,T){let P=[],V=this.transform;if(!V)return P;let W=V.getCoveringTilesDetailsProvider().allowWorldCopies(),te=T?V.getCameraQueryGeometry(l):l,se=je=>V.screenPointToMercatorCoordinate(je,this.terrain),fe=this.transformBbox(l,se,!W),_e=this.transformBbox(te,se,!W),Re=this.getIds(),Ie=o.ac.fromPoints(_e);for(let je=0;jeLt.getTilePoint(new o.ab(Mt.x,Mt.y)));if(Kt.expandBy(vt),Kt.intersects($t)){let Mt=fe.map(Rt=>Lt.getTilePoint(Rt)),Xt=_e.map(Rt=>Lt.getTilePoint(Rt));P.push({tile:tt,tileID:W?Lt:Lt.unwrapTo(0),queryGeometry:Mt,cameraQueryGeometry:Xt,scale:At})}}}return P}transformBbox(l,m,T){let P=l.map(m);if(T){let V=o.ac.fromPoints(l);V.shrinkBy(.001*Math.min(V.width(),V.height()));let W=V.map(m);o.ac.fromPoints(P).covers(W)||(P=P.map(te=>te.x>.5?new o.ab(te.x-1,te.y,te.z):te))}return P}getVisibleCoordinates(l){let m=this.getRenderableIds(l).map(T=>this._inViewTiles.getTileById(T).tileID);return this.transform&&this.transform.populateCache(m),m}hasTransition(){return!!this._source.hasTransition()||!(!Jt(this._source.type)||!function(l,m){if(m<=0)return!1;let T=k();for(let P of l.getAllTiles())if(P.fadeEndTime>=T)return!0;return!1}(this._inViewTiles,this._rasterFadeDuration))}setRasterFadeDuration(l){this._rasterFadeDuration=l}setFeatureState(l,m,T){this._state.updateState(l=l||o.aa,m,T)}removeFeatureState(l,m,T){this._state.removeFeatureState(l=l||o.aa,m,T)}getFeatureState(l,m){return this._state.getState(l=l||o.aa,m)}setDependencies(l,m,T){let P=this._inViewTiles.getTileById(l);P&&P.setDependencies(m,T)}reloadTilesForDependencies(l,m){for(let T of this._inViewTiles.getAllIds())this._inViewTiles.getTileById(T).hasDependency(l,m)&&this._reloadTile(T,"reloading");this._outOfViewCache.filter(T=>!T.hasDependency(l,m))}areTilesLoaded(){for(let l of this._inViewTiles.getAllTiles())if(l.state!=="loaded"&&l.state!=="errored")return!1;return!0}}si.maxUnderzooming=10,si.maxOverzooming=3;class Or{constructor(l,m){this.reset(l,m)}reset(l,m){this.points=l||[],this._distances=[0];for(let T=1;T0?(P-W)/te:0;return this.points[V].mult(1-se).add(this.points[m].mult(se))}}function fi(z,l){let m=!0;return z==="always"||z!=="never"&&l!=="never"||(m=!1),m}class pr{constructor(l,m,T){let P=this.boxCells=[],V=this.circleCells=[];this.xCellCount=Math.ceil(l/T),this.yCellCount=Math.ceil(m/T);for(let W=0;Wthis.width||P<0||m>this.height)return[];let se=[];if(l<=0&&m<=0&&this.width<=T&&this.height<=P){if(V)return[{key:null,x1:l,y1:m,x2:T,y2:P}];for(let fe=0;fe0}hitTestCircle(l,m,T,P,V){let W=l-T,te=l+T,se=m-T,fe=m+T;if(te<0||W>this.width||fe<0||se>this.height)return!1;let _e=[];return this._forEachCell(W,se,te,fe,this._queryCellCircle,_e,{hitTest:!0,overlapMode:P,circle:{x:l,y:m,radius:T},seenUids:{box:{},circle:{}}},V),_e.length>0}_queryCell(l,m,T,P,V,W,te,se){let{seenUids:fe,hitTest:_e,overlapMode:Re}=te,Ie=this.boxCells[V];if(Ie!==null){let tt=this.bboxes;for(let yt of Ie)if(!fe.box[yt]){fe.box[yt]=!0;let At=4*yt,vt=this.boxKeys[yt];if(l<=tt[At+2]&&m<=tt[At+3]&&T>=tt[At+0]&&P>=tt[At+1]&&(!se||se(vt))&&(!_e||!fi(Re,vt.overlapMode))&&(W.push({key:vt,x1:tt[At],y1:tt[At+1],x2:tt[At+2],y2:tt[At+3]}),_e))return!0}}let je=this.circleCells[V];if(je!==null){let tt=this.circles;for(let yt of je)if(!fe.circle[yt]){fe.circle[yt]=!0;let At=3*yt,vt=this.circleKeys[yt];if(this._circleAndRectCollide(tt[At],tt[At+1],tt[At+2],l,m,T,P)&&(!se||se(vt))&&(!_e||!fi(Re,vt.overlapMode))){let Lt=tt[At],Kt=tt[At+1],Mt=tt[At+2];if(W.push({key:vt,x1:Lt-Mt,y1:Kt-Mt,x2:Lt+Mt,y2:Kt+Mt}),_e)return!0}}}return!1}_queryCellCircle(l,m,T,P,V,W,te,se){let{circle:fe,seenUids:_e,overlapMode:Re}=te,Ie=this.boxCells[V];if(Ie!==null){let tt=this.bboxes;for(let yt of Ie)if(!_e.box[yt]){_e.box[yt]=!0;let At=4*yt,vt=this.boxKeys[yt];if(this._circleAndRectCollide(fe.x,fe.y,fe.radius,tt[At+0],tt[At+1],tt[At+2],tt[At+3])&&(!se||se(vt))&&!fi(Re,vt.overlapMode))return W.push(!0),!0}}let je=this.circleCells[V];if(je!==null){let tt=this.circles;for(let yt of je)if(!_e.circle[yt]){_e.circle[yt]=!0;let At=3*yt,vt=this.circleKeys[yt];if(this._circlesCollide(tt[At],tt[At+1],tt[At+2],fe.x,fe.y,fe.radius)&&(!se||se(vt))&&!fi(Re,vt.overlapMode))return W.push(!0),!0}}}_forEachCell(l,m,T,P,V,W,te,se){let fe=this._convertToXCellCoord(l),_e=this._convertToYCellCoord(m),Re=this._convertToXCellCoord(T),Ie=this._convertToYCellCoord(P);for(let je=fe;je<=Re;je++)for(let tt=_e;tt<=Ie;tt++)if(V.call(this,l,m,T,P,this.xCellCount*tt+je,W,te,se))return}_convertToXCellCoord(l){return Math.max(0,Math.min(this.xCellCount-1,Math.floor(l*this.xScale)))}_convertToYCellCoord(l){return Math.max(0,Math.min(this.yCellCount-1,Math.floor(l*this.yScale)))}_circlesCollide(l,m,T,P,V,W){let te=P-l,se=V-m,fe=T+W;return fe*fe>te*te+se*se}_circleAndRectCollide(l,m,T,P,V,W,te){let se=(W-P)/2,fe=Math.abs(l-(P+se));if(fe>se+T)return!1;let _e=(te-V)/2,Re=Math.abs(m-(V+_e));if(Re>_e+T)return!1;if(fe<=se||Re<=_e)return!0;let Ie=fe-se,je=Re-_e;return Ie*Ie+je*je<=T*T}}function Di(z,l,m){let T=o.N();if(!z){let{vecSouth:Re,vecEast:Ie}=Ii(l),je=c();je[0]=Ie[0],je[1]=Ie[1],je[2]=Re[0],je[3]=Re[1],P=je,(_e=(W=(V=je)[0])*(fe=V[3])-(se=V[2])*(te=V[1]))&&(P[0]=fe*(_e=1/_e),P[1]=-te*_e,P[2]=-se*_e,P[3]=W*_e),T[0]=je[0],T[1]=je[1],T[4]=je[2],T[5]=je[3]}var P,V,W,te,se,fe,_e;return o.Q(T,T,[1/m,1/m,1]),T}function Mi(z,l,m,T){if(z){let P=o.N();if(!l){let{vecSouth:V,vecEast:W}=Ii(m);P[0]=W[0],P[1]=W[1],P[4]=V[0],P[5]=V[1]}return o.Q(P,P,[T,T,1]),P}return m.pixelsToClipSpaceMatrix}function Ii(z){let l=Math.cos(z.rollInRadians),m=Math.sin(z.rollInRadians),T=Math.cos(z.pitchInRadians),P=Math.cos(z.bearingInRadians),V=Math.sin(z.bearingInRadians),W=o.aD();W[0]=-P*T*m-V*l,W[1]=-V*T*m+P*l;let te=o.aE(W);te<1e-9?o.aF(W):o.aG(W,W,1/te);let se=o.aD();se[0]=P*T*l-V*m,se[1]=V*T*l+P*m;let fe=o.aE(se);return fe<1e-9?o.aF(se):o.aG(se,se,1/fe),{vecEast:se,vecSouth:W}}function Ni(z,l,m,T){let P;T?(P=[z,l,T(z,l),1],o.aI(P,P,m)):(P=[z,l,0,1],md(P,P,m));let V=P[3];return{point:new o.P(P[0]/V,P[1]/V),signedDistanceFromCamera:V,isOccluded:!1}}function zi(z,l){return .5+z/l*.5}function po(z,l){return z.x>=-l[0]&&z.x<=l[0]&&z.y>=-l[1]&&z.y<=l[1]}function Ls(z,l,m,T,P,V,W,te,se,fe,_e,Re,Ie){let je=m?z.textSizeData:z.iconSizeData,tt=o.az(je,l.transform.zoom),yt=[256/l.width*2+1,256/l.height*2+1],At=m?z.text.dynamicLayoutVertexArray:z.icon.dynamicLayoutVertexArray;At.clear();let vt=z.lineVertexArray,Lt=m?z.text.placedSymbolArray:z.icon.placedSymbolArray,Kt=l.transform.width/l.transform.height,Mt=!1;for(let Xt=0;XtMath.abs(m.x-l.x)*T?{useVertical:!0}:(z===o.aA.vertical?l.ym.x)?{needsFlipping:!0}:null}function wo(z){let{projectionContext:l,pitchedLabelPlaneMatrixInverse:m,symbol:T,fontSize:P,flip:V,keepUpright:W,glyphOffsetArray:te,dynamicLayoutVertexArray:se,aspectRatio:fe,rotateToLine:_e}=z,Re=P/24,Ie=T.lineOffsetX*Re,je=T.lineOffsetY*Re,tt;if(T.numGlyphs>1){let yt=T.glyphStartIndex+T.numGlyphs,At=T.lineStartIndex,vt=T.lineStartIndex+T.lineLength,Lt=co(Re,te,Ie,je,V,T,_e,l);if(!Lt)return{notEnoughRoom:!0};let Kt=Ds(Lt.first.point.x,Lt.first.point.y,l,m),Mt=Ds(Lt.last.point.x,Lt.last.point.y,l,m);if(W&&!V){let Xt=No(T.writingMode,Kt,Mt,fe);if(Xt)return Xt}tt=[Lt.first];for(let Xt=T.glyphStartIndex+1;Xt0?Kt.point:Al(l.tileAnchorPoint,Lt,At,1,l),Xt=Ds(At.x,At.y,l,m),Rt=Ds(Mt.x,Mt.y,l,m),zt=No(T.writingMode,Xt,Rt,fe);if(zt)return zt}let yt=zc(Re*te.getoffsetX(T.glyphStartIndex),Ie,je,V,T.segment,T.lineStartIndex,T.lineStartIndex+T.lineLength,l,_e);if(!yt||l.projectionCache.anyProjectionOccluded)return{notEnoughRoom:!0};tt=[yt]}for(let yt of tt)o.aH(se,yt.point,yt.angle);return{}}function Al(z,l,m,T,P){let V=z.add(z.sub(l)._unit()),W=Qn(V.x,V.y,P).point,te=m.sub(W);return m.add(te._mult(T/te.mag()))}function uo(z,l,m){let T=l.projectionCache;if(T.projections[z])return T.projections[z];let P=new o.P(l.lineVertexArray.getx(z),l.lineVertexArray.gety(z)),V=Qn(P.x,P.y,l);if(V.signedDistanceFromCamera>0)return T.projections[z]=V.point,T.anyProjectionOccluded=T.anyProjectionOccluded||V.isOccluded,V.point;let W=z-m.direction;return Al(m.distanceFromAnchor===0?l.tileAnchorPoint:new o.P(l.lineVertexArray.getx(W),l.lineVertexArray.gety(W)),P,m.previousVertex,m.absOffsetX-m.distanceFromAnchor+1,l)}function Qn(z,l,m){let T=z+m.translation[0],P=l+m.translation[1],V;return m.pitchWithMap?(V=Ni(T,P,m.pitchedLabelPlaneMatrix,m.getElevation),V.isOccluded=!1):(V=m.transform.projectTileCoordinates(T,P,m.unwrappedTileID,m.getElevation),V.point.x=(.5*V.point.x+.5)*m.width,V.point.y=(.5*-V.point.y+.5)*m.height),V}function Ds(z,l,m,T){if(m.pitchWithMap){let P=[z,l,0,1];return o.aI(P,P,T),m.transform.projectTileCoordinates(P[0]/P[3],P[1]/P[3],m.unwrappedTileID,m.getElevation).point}return{x:z/m.width*2-1,y:1-l/m.height*2}}function ho(z,l,m){return m.transform.projectTileCoordinates(z,l,m.unwrappedTileID,m.getElevation)}function cs(z,l,m){return z._unit()._perp()._mult(l*m)}function _l(z,l,m,T,P,V,W,te,se){if(te.projectionCache.offsets[z])return te.projectionCache.offsets[z];let fe=m.add(l);if(z+se.direction=P)return te.projectionCache.offsets[z]=fe,fe;let _e=uo(z+se.direction,te,se),Re=cs(_e.sub(m),W,se.direction),Ie=m.add(Re),je=_e.add(Re);return te.projectionCache.offsets[z]=o.aJ(V,fe,Ie,je)||fe,te.projectionCache.offsets[z]}function zc(z,l,m,T,P,V,W,te,se){let fe=T?z-l:z+l,_e=fe>0?1:-1,Re=0;T&&(_e*=-1,Re=Math.PI),_e<0&&(Re+=Math.PI);let Ie,je=_e>0?V+P:V+P+1;te.projectionCache.cachedAnchorPoint?Ie=te.projectionCache.cachedAnchorPoint:(Ie=Qn(te.tileAnchorPoint.x,te.tileAnchorPoint.y,te).point,te.projectionCache.cachedAnchorPoint=Ie);let tt,yt,At=Ie,vt=Ie,Lt=0,Kt=0,Mt=Math.abs(fe),Xt=[],Rt;for(;Lt+Kt<=Mt;){if(je+=_e,je=W)return null;Lt+=Kt,vt=At,yt=tt;let br={absOffsetX:Mt,direction:_e,distanceFromAnchor:Lt,previousVertex:vt};if(At=uo(je,te,br),m===0)Xt.push(vt),Rt=At.sub(vt);else{let Cr,Pr=At.sub(vt);Cr=Pr.mag()===0?cs(uo(je+_e,te,br).sub(At),m,_e):cs(Pr,m,_e),yt||(yt=vt.add(Cr)),tt=_l(je,Cr,At,V,W,yt,m,te,br),Xt.push(yt),Rt=tt.sub(yt)}Kt=Rt.mag()}let zt=Rt._mult((Mt-Lt)/Kt)._add(yt||vt),jt=Re+Math.atan2(At.y-vt.y,At.x-vt.x);return Xt.push(zt),{point:zt,angle:se?jt:0,path:Xt}}let yl=new Float32Array([-1/0,-1/0,0,-1/0,-1/0,0,-1/0,-1/0,0,-1/0,-1/0,0]);function Ya(z,l){for(let m=0;m=1;fo--)_i.push(Ir.path[fo]);for(let fo=1;fo$r.signedDistanceFromCamera<=0)?[]:fo.map($r=>$r.point)}let hn=[];if(_i.length>0){let fo=_i[0].clone(),$r=_i[0].clone();for(let di=1;di<_i.length;di++)fo.x=Math.min(fo.x,_i[di].x),fo.y=Math.min(fo.y,_i[di].y),$r.x=Math.max($r.x,_i[di].x),$r.y=Math.max($r.y,_i[di].y);hn=fo.x>=br.x&&$r.x<=Cr.x&&fo.y>=br.y&&$r.y<=Cr.y?[_i]:$r.xCr.x||$r.yCr.y?[]:o.aK([_i],br.x,br.y,Cr.x,Cr.y)}for(let fo of hn){Pr.reset(fo,.25*jt);let $r=0;$r=Pr.length<=.5*jt?1:Math.ceil(Pr.paddedLength/hi)+1;for(let di=0;di<$r;di++){let na=di/Math.max($r-1,1),Ja=Pr.lerp(na),ya=Ja.x+os,jl=Ja.y+os;yt.push(ya,jl,jt,0);let qs=ya-jt,Ha=jl-jt,Gl=ya+jt,Ea=jl+jt;if(zt=zt&&this.isOffscreen(qs,Ha,Gl,Ea),Rt=Rt||this.isInsideGrid(qs,Ha,Gl,Ea),l!=="always"&&this.grid.hitTestCircle(ya,jl,jt,l,_e)&&(Xt=!0,!se))return{circles:[],offscreen:!1,collisionDetected:Xt}}}}return{circles:!se&&Xt||!Rt||vt{let se=Ni(te.x,te.y,W,V.getElevation),fe=V.transform.projectTileCoordinates(se.point.x,se.point.y,V.unwrappedTileID,V.getElevation);return fe.point.x=(.5*fe.point.x+.5)*V.width,fe.point.y=(.5*-fe.point.y+.5)*V.height,fe})}(l,m);return function(P){let V=0,W=0,te=0,se=0;for(let fe=0;feW&&(W=se,V=te));return P.slice(V,V+W)}(T)}queryRenderedSymbols(l){if(l.length===0||this.grid.keysLength()===0&&this.ignoredGrid.keysLength()===0)return{};let m=[],T=new o.ac;for(let Re of l){let Ie=new o.P(Re.x+os,Re.y+os);T.extend(Ie),m.push(Ie)}let{minX:P,minY:V,maxX:W,maxY:te}=T,se=this.grid.query(P,V,W,te).concat(this.ignoredGrid.query(P,V,W,te)),fe={},_e={};for(let Re of se){let Ie=Re.key;if(fe[Ie.bucketInstanceId]===void 0&&(fe[Ie.bucketInstanceId]={}),fe[Ie.bucketInstanceId][Ie.featureIndex])continue;let je=[new o.P(Re.x1,Re.y1),new o.P(Re.x2,Re.y1),new o.P(Re.x2,Re.y2),new o.P(Re.x1,Re.y2)];o.aL(m,je)&&(fe[Ie.bucketInstanceId][Ie.featureIndex]=!0,_e[Ie.bucketInstanceId]===void 0&&(_e[Ie.bucketInstanceId]=[]),_e[Ie.bucketInstanceId].push(Ie.featureIndex))}return _e}insertCollisionBox(l,m,T,P,V,W){(T?this.ignoredGrid:this.grid).insert({bucketInstanceId:P,featureIndex:V,collisionGroupID:W,overlapMode:m},l[0],l[1],l[2],l[3])}insertCollisionCircles(l,m,T,P,V,W){let te=T?this.ignoredGrid:this.grid,se={bucketInstanceId:P,featureIndex:V,collisionGroupID:W,overlapMode:m};for(let fe=0;fe=this.screenRightBoundary||Pthis.screenBottomBoundary}isInsideGrid(l,m,T,P){return T>=0&&l=0&&mthis.projectAndGetPerspectiveRatio(hi.x,hi.y,P,fe,Re));Ai=_i.some(hi=>!hi.isOccluded),Ir=_i.map(hi=>new o.P(hi.x,hi.y))}else Ai=!0;return{box:o.aM(Ir),allPointsOccluded:!Ai}}}class mf{constructor(l,m,T,P){this.opacity=l?Math.max(0,Math.min(1,l.opacity+(l.placed?m:-m))):P&&T?1:0,this.placed=T}isHidden(){return this.opacity===0&&!this.placed}}class Ll{constructor(l,m,T,P,V){this.text=new mf(l?l.text:null,m,T,V),this.icon=new mf(l?l.icon:null,m,P,V)}isHidden(){return this.text.isHidden()&&this.icon.isHidden()}}class Dl{constructor(l,m,T){this.text=l,this.icon=m,this.skipFade=T}}class eu{constructor(l,m,T,P,V){this.bucketInstanceId=l,this.featureIndex=m,this.sourceLayerIndex=T,this.bucketIndex=P,this.tileID=V}}class ca{constructor(l){this.crossSourceCollisions=l,this.maxGroupID=0,this.collisionGroups={}}get(l){if(this.crossSourceCollisions)return{ID:0,predicate:null};if(!this.collisionGroups[l]){let m=++this.maxGroupID;this.collisionGroups[l]={ID:m,predicate:T=>T.collisionGroupID===m}}return this.collisionGroups[l]}}function Tc(z,l,m,T,P){let{horizontalAlign:V,verticalAlign:W}=o.aT(z);return new o.P(-(V-.5)*l+T[0]*P,-(W-.5)*m+T[1]*P)}class Uc{constructor(l,m,T,P,V){this.transform=l.clone(),this.terrain=m,this.collisionIndex=new la(this.transform),this.placements={},this.opacities={},this.variableOffsets={},this.stale=!1,this.commitTime=0,this.fadeDuration=T,this.retainedQueryData={},this.collisionGroups=new ca(P),this.collisionCircleArrays={},this.collisionBoxArrays=new Map,this.prevPlacement=V,V&&(V.prevPlacement=void 0),this.placedOrientations={}}_getTerrainElevationFunc(l){let m=this.terrain;return m?(T,P)=>m.getElevation(l,T,P):null}getBucketParts(l,m,T,P){let V=T.getBucket(m),W=T.latestFeatureIndex;if(!V||!W||m.id!==V.layerIds[0])return;let te=T.collisionBoxArray,se=V.layers[0].layout,fe=V.layers[0].paint,_e=Math.pow(2,this.transform.zoom-T.tileID.overscaledZ),Re=T.tileSize/o.a5,Ie=T.tileID.toUnwrapped(),je=se.get("text-rotation-alignment")==="map",tt=o.aO(T,1,this.transform.zoom),yt=o.aP(this.collisionIndex.transform,T,fe.get("text-translate"),fe.get("text-translate-anchor")),At=o.aP(this.collisionIndex.transform,T,fe.get("icon-translate"),fe.get("icon-translate-anchor")),vt=Di(je,this.transform,tt);this.retainedQueryData[V.bucketInstanceId]=new eu(V.bucketInstanceId,W,V.sourceLayerIndex,V.index,T.tileID);let Lt={bucket:V,layout:se,translationText:yt,translationIcon:At,unwrappedTileID:Ie,pitchedLabelPlaneMatrix:vt,scale:_e,textPixelRatio:Re,holdingForFade:T.holdingForSymbolFade(),collisionBoxArray:te,partiallyEvaluatedTextSize:o.az(V.textSizeData,this.transform.zoom),collisionGroup:this.collisionGroups.get(V.sourceID)};if(P)for(let Kt of V.sortKeyRanges){let{sortKey:Mt,symbolInstanceStart:Xt,symbolInstanceEnd:Rt}=Kt;l.push({sortKey:Mt,symbolInstanceStart:Xt,symbolInstanceEnd:Rt,parameters:Lt})}else l.push({symbolInstanceStart:0,symbolInstanceEnd:V.symbolInstances.length,parameters:Lt})}attemptAnchorPlacement(l,m,T,P,V,W,te,se,fe,_e,Re,Ie,je,tt,yt,At,vt,Lt,Kt,Mt){let Xt=o.aQ[l.textAnchor],Rt=[l.textOffset0,l.textOffset1],zt=Tc(Xt,T,P,Rt,V),jt=this.collisionIndex.placeCollisionBox(m,Ie,se,fe,_e,te,W,At,Re.predicate,Kt,zt,Mt);if((!Lt||this.collisionIndex.placeCollisionBox(Lt,Ie,se,fe,_e,te,W,vt,Re.predicate,Kt,zt,Mt).placeable)&&jt.placeable){let br;if(this.prevPlacement&&this.prevPlacement.variableOffsets[je.crossTileID]&&this.prevPlacement.placements[je.crossTileID]&&this.prevPlacement.placements[je.crossTileID].text&&(br=this.prevPlacement.variableOffsets[je.crossTileID].anchor),je.crossTileID===0)throw new Error("symbolInstance.crossTileID can't be 0");return this.variableOffsets[je.crossTileID]={textOffset:Rt,width:T,height:P,anchor:Xt,textBoxScale:V,prevAnchor:br},this.markUsedJustification(tt,Xt,je,yt),tt.allowVerticalPlacement&&(this.markUsedOrientation(tt,yt,je),this.placedOrientations[je.crossTileID]=yt),{shift:zt,placedGlyphBoxes:jt}}}placeLayerBucketPart(l,m,T){let{bucket:P,layout:V,translationText:W,translationIcon:te,unwrappedTileID:se,pitchedLabelPlaneMatrix:fe,textPixelRatio:_e,holdingForFade:Re,collisionBoxArray:Ie,partiallyEvaluatedTextSize:je,collisionGroup:tt}=l.parameters,yt=V.get("text-optional"),At=V.get("icon-optional"),vt=o.aR(V,"text-overlap","text-allow-overlap"),Lt=vt==="always",Kt=o.aR(V,"icon-overlap","icon-allow-overlap"),Mt=Kt==="always",Xt=V.get("text-rotation-alignment")==="map",Rt=V.get("text-pitch-alignment")==="map",zt=V.get("icon-text-fit")!=="none",jt=V.get("symbol-z-order")==="viewport-y",br=Lt&&(Mt||!P.hasIconData()||At),Cr=Mt&&(Lt||!P.hasTextData()||yt);!P.collisionArrays&&Ie&&P.deserializeCollisionBoxes(Ie);let Pr=this.retainedQueryData[P.bucketInstanceId].tileID,Ir=this._getTerrainElevationFunc(Pr),Ai=this.transform.getFastPathSimpleProjectionMatrix(Pr),_i=(hi,hn,fo)=>{var $r,di;if(m[hi.crossTileID])return;if(Re)return void(this.placements[hi.crossTileID]=new Dl(!1,!1,!1));let na=!1,Ja=!1,ya=!0,jl=null,qs={box:null,placeable:!1,offscreen:null,occluded:!1},Ha={placeable:!1},Gl=null,Ea=null,v=null,x=0,E=0,B=0;hn.textFeatureIndex?x=hn.textFeatureIndex:hi.useRuntimeCollisionCircles&&(x=hi.featureIndex),hn.verticalTextFeatureIndex&&(E=hn.verticalTextFeatureIndex);let N=hn.textBox;if(N){let ue=bt=>{let Tt=o.aA.horizontal;if(P.allowVerticalPlacement&&!bt&&this.prevPlacement){let St=this.prevPlacement.placedOrientations[hi.crossTileID];St&&(this.placedOrientations[hi.crossTileID]=St,Tt=St,this.markUsedOrientation(P,Tt,hi))}return Tt},Ae=(bt,Tt)=>{if(P.allowVerticalPlacement&&hi.numVerticalGlyphVertices>0&&hn.verticalTextBox){for(let St of P.writingModes)if(St===o.aA.vertical?(qs=Tt(),Ha=qs):qs=bt(),qs&&qs.placeable)break}else qs=bt()},Te=hi.textAnchorOffsetStartIndex,at=hi.textAnchorOffsetEndIndex;if(at===Te){let bt=(Tt,St)=>{let er=this.collisionIndex.placeCollisionBox(Tt,vt,_e,Pr,se,Rt,Xt,W,tt.predicate,Ir,void 0,Ai);return er&&er.placeable&&(this.markUsedOrientation(P,St,hi),this.placedOrientations[hi.crossTileID]=St),er};Ae(()=>bt(N,o.aA.horizontal),()=>{let Tt=hn.verticalTextBox;return P.allowVerticalPlacement&&hi.numVerticalGlyphVertices>0&&Tt?bt(Tt,o.aA.vertical):{box:null,offscreen:null}}),ue(qs&&qs.placeable)}else{let bt=o.aQ[(di=($r=this.prevPlacement)===null||$r===void 0?void 0:$r.variableOffsets[hi.crossTileID])===null||di===void 0?void 0:di.anchor],Tt=(er,Ar,qr)=>{let _r=er.x2-er.x1,Sr=er.y2-er.y1,or=hi.textBoxScale,Pi=zt&&Kt==="never"?Ar:null,En=null,wn=vt==="never"?1:2,vo="never";bt&&wn++;for(let Ro=0;RoTt(N,hn.iconBox,o.aA.horizontal),()=>{let er=hn.verticalTextBox;return P.allowVerticalPlacement&&(!qs||!qs.placeable)&&hi.numVerticalGlyphVertices>0&&er?Tt(er,hn.verticalIconBox,o.aA.vertical):{box:null,occluded:!0,offscreen:null}}),qs&&(na=qs.placeable,ya=qs.offscreen);let St=ue(qs&&qs.placeable);if(!na&&this.prevPlacement){let er=this.prevPlacement.variableOffsets[hi.crossTileID];er&&(this.variableOffsets[hi.crossTileID]=er,this.markUsedJustification(P,er.anchor,hi,St))}}}if(Gl=qs,na=Gl&&Gl.placeable,ya=Gl&&Gl.offscreen,hi.useRuntimeCollisionCircles&&hi.centerJustifiedTextSymbolIndex>=0){let ue=P.text.placedSymbolArray.get(hi.centerJustifiedTextSymbolIndex),Ae=o.aB(P.textSizeData,je,ue),Te=V.get("text-padding");Ea=this.collisionIndex.placeCollisionCircles(vt,ue,P.lineVertexArray,P.glyphOffsetArray,Ae,se,fe,T,Rt,tt.predicate,hi.collisionCircleDiameter,Te,W,Ir),Ea.circles.length&&Ea.collisionDetected&&!T&&o.w("Collisions detected, but collision boxes are not shown"),na=Lt||Ea.circles.length>0&&!Ea.collisionDetected,ya=ya&&Ea.offscreen}if(hn.iconFeatureIndex&&(B=hn.iconFeatureIndex),hn.iconBox){let ue=Ae=>this.collisionIndex.placeCollisionBox(Ae,Kt,_e,Pr,se,Rt,Xt,te,tt.predicate,Ir,zt&&jl?jl:void 0,Ai);Ha&&Ha.placeable&&hn.verticalIconBox?(v=ue(hn.verticalIconBox),Ja=v.placeable):(v=ue(hn.iconBox),Ja=v.placeable),ya=ya&&v.offscreen}let H=yt||hi.numHorizontalGlyphVertices===0&&hi.numVerticalGlyphVertices===0,X=At||hi.numIconVertices===0;H||X?X?H||(Ja=Ja&&na):na=Ja&&na:Ja=na=Ja&&na;let Q=Ja&&v.placeable;if(na&&Gl.placeable&&this.collisionIndex.insertCollisionBox(Gl.box,vt,V.get("text-ignore-placement"),P.bucketInstanceId,Ha&&Ha.placeable&&E?E:x,tt.ID),Q&&this.collisionIndex.insertCollisionBox(v.box,Kt,V.get("icon-ignore-placement"),P.bucketInstanceId,B,tt.ID),Ea&&na&&this.collisionIndex.insertCollisionCircles(Ea.circles,vt,V.get("text-ignore-placement"),P.bucketInstanceId,x,tt.ID),T&&this.storeCollisionData(P.bucketInstanceId,fo,hn,Gl,v,Ea),hi.crossTileID===0)throw new Error("symbolInstance.crossTileID can't be 0");if(P.bucketInstanceId===0)throw new Error("bucket.bucketInstanceId can't be 0");this.placements[hi.crossTileID]=new Dl((na||br)&&!Gl?.occluded,(Ja||Cr)&&!v?.occluded,ya||P.justReloaded),m[hi.crossTileID]=!0};if(jt){if(l.symbolInstanceStart!==0)throw new Error("bucket.bucketInstanceId should be 0");let hi=P.getSortedSymbolIndexes(-this.transform.bearingInRadians);for(let hn=hi.length-1;hn>=0;--hn){let fo=hi[hn];_i(P.symbolInstances.get(fo),P.collisionArrays[fo],fo)}}else for(let hi=l.symbolInstanceStart;hi=0&&(l.text.placedSymbolArray.get(te).crossTileID=V>=0&&te!==V?0:T.crossTileID)}markUsedOrientation(l,m,T){let P=m===o.aA.horizontal||m===o.aA.horizontalOnly?m:0,V=m===o.aA.vertical?m:0,W=[T.leftJustifiedTextSymbolIndex,T.centerJustifiedTextSymbolIndex,T.rightJustifiedTextSymbolIndex];for(let te of W)l.text.placedSymbolArray.get(te).placedOrientation=P;T.verticalPlacedTextSymbolIndex&&(l.text.placedSymbolArray.get(T.verticalPlacedTextSymbolIndex).placedOrientation=V)}commit(l){this.commitTime=l,this.zoomAtLastRecencyCheck=this.transform.zoom;let m=this.prevPlacement,T=!1;this.prevZoomAdjustment=m?m.zoomAdjustment(this.transform.zoom):0;let P=m?m.symbolFadeChange(l):1,V=m?m.opacities:{},W=m?m.variableOffsets:{},te=m?m.placedOrientations:{};for(let se in this.placements){let fe=this.placements[se],_e=V[se];_e?(this.opacities[se]=new Ll(_e,P,fe.text,fe.icon),T=T||fe.text!==_e.text.placed||fe.icon!==_e.icon.placed):(this.opacities[se]=new Ll(null,P,fe.text,fe.icon,fe.skipFade),T=T||fe.text||fe.icon)}for(let se in V){let fe=V[se];if(!this.opacities[se]){let _e=new Ll(fe,P,!1,!1);_e.isHidden()||(this.opacities[se]=_e,T=T||fe.text.placed||fe.icon.placed)}}for(let se in W)this.variableOffsets[se]||!this.opacities[se]||this.opacities[se].isHidden()||(this.variableOffsets[se]=W[se]);for(let se in te)this.placedOrientations[se]||!this.opacities[se]||this.opacities[se].isHidden()||(this.placedOrientations[se]=te[se]);if(m&&m.lastPlacementChangeTime===void 0)throw new Error("Last placement time for previous placement is not defined");T?this.lastPlacementChangeTime=l:typeof this.lastPlacementChangeTime!="number"&&(this.lastPlacementChangeTime=m?m.lastPlacementChangeTime:l)}updateLayerOpacities(l,m){let T={};for(let P of m){let V=P.getBucket(l);V&&P.latestFeatureIndex&&l.id===V.layerIds[0]&&this.updateBucketOpacities(V,P.tileID,T,P.collisionBoxArray)}}updateBucketOpacities(l,m,T,P){l.hasTextData()&&(l.text.opacityVertexArray.clear(),l.text.hasVisibleVertices=!1),l.hasIconData()&&(l.icon.opacityVertexArray.clear(),l.icon.hasVisibleVertices=!1),l.hasIconCollisionBoxData()&&l.iconCollisionBox.collisionVertexArray.clear(),l.hasTextCollisionBoxData()&&l.textCollisionBox.collisionVertexArray.clear();let V=l.layers[0],W=V.layout,te=new Ll(null,0,!1,!1,!0),se=W.get("text-allow-overlap"),fe=W.get("icon-allow-overlap"),_e=V._unevaluatedLayout.hasValue("text-variable-anchor")||V._unevaluatedLayout.hasValue("text-variable-anchor-offset"),Re=W.get("text-rotation-alignment")==="map",Ie=W.get("text-pitch-alignment")==="map",je=W.get("icon-text-fit")!=="none",tt=new Ll(null,0,se&&(fe||!l.hasIconData()||W.get("icon-optional")),fe&&(se||!l.hasTextData()||W.get("text-optional")),!0);!l.collisionArrays&&P&&(l.hasIconCollisionBoxData()||l.hasTextCollisionBoxData())&&l.deserializeCollisionBoxes(P);let yt=(vt,Lt,Kt)=>{for(let Mt=0;Mt0,jt=this.placedOrientations[Lt.crossTileID],br=jt===o.aA.vertical,Cr=jt===o.aA.horizontal||jt===o.aA.horizontalOnly;if(Kt>0||Mt>0){let Ir=gf(Rt.text);yt(l.text,Kt,br?gd:Ir),yt(l.text,Mt,Cr?gd:Ir);let Ai=Rt.text.isHidden();[Lt.rightJustifiedTextSymbolIndex,Lt.centerJustifiedTextSymbolIndex,Lt.leftJustifiedTextSymbolIndex].forEach(hn=>{hn>=0&&(l.text.placedSymbolArray.get(hn).hidden=Ai||br?1:0)}),Lt.verticalPlacedTextSymbolIndex>=0&&(l.text.placedSymbolArray.get(Lt.verticalPlacedTextSymbolIndex).hidden=Ai||Cr?1:0);let _i=this.variableOffsets[Lt.crossTileID];_i&&this.markUsedJustification(l,_i.anchor,Lt,jt);let hi=this.placedOrientations[Lt.crossTileID];hi&&(this.markUsedJustification(l,"left",Lt,hi),this.markUsedOrientation(l,hi,Lt))}if(zt){let Ir=gf(Rt.icon),Ai=!(je&&Lt.verticalPlacedIconSymbolIndex&&br);Lt.placedIconSymbolIndex>=0&&(yt(l.icon,Lt.numIconVertices,Ai?Ir:gd),l.icon.placedSymbolArray.get(Lt.placedIconSymbolIndex).hidden=Rt.icon.isHidden()),Lt.verticalPlacedIconSymbolIndex>=0&&(yt(l.icon,Lt.numVerticalIconVertices,Ai?gd:Ir),l.icon.placedSymbolArray.get(Lt.verticalPlacedIconSymbolIndex).hidden=Rt.icon.isHidden())}let Pr=At&&At.has(vt)?At.get(vt):{text:null,icon:null};if(l.hasIconCollisionBoxData()||l.hasTextCollisionBoxData()){let Ir=l.collisionArrays[vt];if(Ir){let Ai=new o.P(0,0);if(Ir.textBox||Ir.verticalTextBox){let _i=!0;if(_e){let hi=this.variableOffsets[Xt];hi?(Ai=Tc(hi.anchor,hi.width,hi.height,hi.textOffset,hi.textBoxScale),Re&&Ai._rotate(Ie?-this.transform.bearingInRadians:this.transform.bearingInRadians)):_i=!1}if(Ir.textBox||Ir.verticalTextBox){let hi;Ir.textBox&&(hi=br),Ir.verticalTextBox&&(hi=Cr),pm(l.textCollisionBox.collisionVertexArray,Rt.text.placed,!_i||hi,Pr.text,Ai.x,Ai.y)}}if(Ir.iconBox||Ir.verticalIconBox){let _i=Boolean(!Cr&&Ir.verticalIconBox),hi;Ir.iconBox&&(hi=_i),Ir.verticalIconBox&&(hi=!_i),pm(l.iconCollisionBox.collisionVertexArray,Rt.icon.placed,hi,Pr.icon,je?Ai.x:0,je?Ai.y:0)}}}}if(l.sortFeatures(-this.transform.bearingInRadians),this.retainedQueryData[l.bucketInstanceId]&&(this.retainedQueryData[l.bucketInstanceId].featureSortOrder=l.featureSortOrder),l.hasTextData()&&l.text.opacityVertexBuffer&&l.text.opacityVertexBuffer.updateData(l.text.opacityVertexArray),l.hasIconData()&&l.icon.opacityVertexBuffer&&l.icon.opacityVertexBuffer.updateData(l.icon.opacityVertexArray),l.hasIconCollisionBoxData()&&l.iconCollisionBox.collisionVertexBuffer&&l.iconCollisionBox.collisionVertexBuffer.updateData(l.iconCollisionBox.collisionVertexArray),l.hasTextCollisionBoxData()&&l.textCollisionBox.collisionVertexBuffer&&l.textCollisionBox.collisionVertexBuffer.updateData(l.textCollisionBox.collisionVertexArray),l.text.opacityVertexArray.length!==l.text.layoutVertexArray.length/4)throw new Error(`bucket.text.opacityVertexArray.length (= ${l.text.opacityVertexArray.length}) !== bucket.text.layoutVertexArray.length (= ${l.text.layoutVertexArray.length}) / 4`);if(l.icon.opacityVertexArray.length!==l.icon.layoutVertexArray.length/4)throw new Error(`bucket.icon.opacityVertexArray.length (= ${l.icon.opacityVertexArray.length}) !== bucket.icon.layoutVertexArray.length (= ${l.icon.layoutVertexArray.length}) / 4`);l.bucketInstanceId in this.collisionCircleArrays&&(l.collisionCircleArray=this.collisionCircleArrays[l.bucketInstanceId],delete this.collisionCircleArrays[l.bucketInstanceId])}symbolFadeChange(l){return this.fadeDuration===0?1:(l-this.commitTime)/this.fadeDuration+this.prevZoomAdjustment}zoomAdjustment(l){return Math.max(0,(this.transform.zoom-l)/1.5)}hasTransitions(l){return this.stale||l-this.lastPlacementChangeTimel}setStale(){this.stale=!0}}function pm(z,l,m,T,P,V){T&&T.length!==0||(T=[0,0,0,0]);let W=T[0]-os,te=T[1]-os,se=T[2]-os,fe=T[3]-os;z.emplaceBack(l?1:0,m?1:0,P||0,V||0,W,te),z.emplaceBack(l?1:0,m?1:0,P||0,V||0,se,te),z.emplaceBack(l?1:0,m?1:0,P||0,V||0,se,fe),z.emplaceBack(l?1:0,m?1:0,P||0,V||0,W,fe)}let pu=Math.pow(2,25),Mu=Math.pow(2,24),jh=Math.pow(2,17),Ia=Math.pow(2,16),Iu=Math.pow(2,9),Sc=Math.pow(2,8),pc=Math.pow(2,1);function gf(z){if(z.opacity===0&&!z.placed)return 0;if(z.opacity===1&&z.placed)return 4294967295;let l=z.placed?1:0,m=Math.floor(127*z.opacity);return m*pu+l*Mu+m*jh+l*Ia+m*Iu+l*Sc+m*pc+l}let gd=0;class Gh{constructor(l){this._sortAcrossTiles=l.layout.get("symbol-z-order")!=="viewport-y"&&!l.layout.get("symbol-sort-key").isConstant(),this._currentTileIndex=0,this._currentPartIndex=0,this._seenCrossTileIDs={},this._bucketParts=[]}continuePlacement(l,m,T,P,V){let W=this._bucketParts;for(;this._currentTileIndexte.sortKey-se.sortKey));this._currentPartIndex!this._forceFullPlacement&&k()-P>2;for(;this._currentPlacementIndex>=0;){let W=m[l[this._currentPlacementIndex]],te=this.placement.collisionIndex.transform.zoom;if(W.type==="symbol"&&(!W.minzoom||W.minzoom<=te)&&(!W.maxzoom||W.maxzoom>te)){if(this._inProgressLayer||(this._inProgressLayer=new Gh(W)),this._inProgressLayer.continuePlacement(T[W.source],this.placement,this._showCollisionBoxes,W,V))return;delete this._inProgressLayer}this._currentPlacementIndex--}this._done=!0}commit(l){return this.placement.commit(l),this.placement}}let Mo=512/o.a5/2;class Sp{constructor(l,m,T){this.tileID=l,this.bucketInstanceId=T,this._symbolsByKey={};let P=new Map;for(let V=0;V({x:Math.floor(se.anchorX*Mo),y:Math.floor(se.anchorY*Mo)})),crossTileIDs:W.map(se=>se.crossTileID)};if(te.positions.length>128){let se=new o.aU(te.positions.length,16,Uint16Array);for(let{x:fe,y:_e}of te.positions)se.add(fe,_e);se.finish(),delete te.positions,te.index=se}this._symbolsByKey[V]=te}}getScaledCoordinates(l,m){let{x:T,y:P,z:V}=this.tileID.canonical,{x:W,y:te,z:se}=m.canonical,fe=Mo/Math.pow(2,se-V),_e=(te*o.a5+l.anchorY)*fe,Re=P*o.a5*Mo;return{x:Math.floor((W*o.a5+l.anchorX)*fe-T*o.a5*Mo),y:Math.floor(_e-Re)}}findMatches(l,m,T){let P=this.tileID.canonical.zl)}}class Qm{constructor(){this.maxCrossTileID=0}generate(){return++this.maxCrossTileID}}class Ep{constructor(){this.indexes={},this.usedCrossTileIDs={},this.lng=0}handleWrapJump(l){let m=Math.round((l-this.lng)/360);if(m!==0)for(let T in this.indexes){let P=this.indexes[T],V={};for(let W in P){let te=P[W];te.tileID=te.tileID.unwrapTo(te.tileID.wrap+m),V[te.tileID.key]=te}this.indexes[T]=V}this.lng=l}addBucket(l,m,T){if(this.indexes[l.overscaledZ]&&this.indexes[l.overscaledZ][l.key]){if(this.indexes[l.overscaledZ][l.key].bucketInstanceId===m.bucketInstanceId)return!1;this.removeBucketCrossTileIDs(l.overscaledZ,this.indexes[l.overscaledZ][l.key])}for(let V=0;Vl.overscaledZ)for(let te in W){let se=W[te];se.tileID.isChildOf(l)&&se.findMatches(m.symbolInstances,l,P)}else{let te=W[l.scaledTo(Number(V)).key];te&&te.findMatches(m.symbolInstances,l,P)}}for(let V=0;V{m[T]=!0});for(let T in this.layerIndexes)m[T]||delete this.layerIndexes[T]}}var Jl="void main() {fragColor=vec4(1.0);}";let _a={prelude:Zn(`#ifdef GL_ES +precision mediump float; +#else +#if !defined(lowp) +#define lowp +#endif +#if !defined(mediump) +#define mediump +#endif +#if !defined(highp) +#define highp +#endif +#endif +out highp vec4 fragColor;`,`#ifdef GL_ES +precision highp float; +#else +#if !defined(lowp) +#define lowp +#endif +#if !defined(mediump) +#define mediump +#endif +#if !defined(highp) +#define highp +#endif +#endif +vec2 unpack_float(const float packedValue) {int packedIntValue=int(packedValue);int v0=packedIntValue/256;return vec2(v0,packedIntValue-v0*256);}vec2 unpack_opacity(const float packedOpacity) {int intOpacity=int(packedOpacity)/2;return vec2(float(intOpacity)/127.0,mod(packedOpacity,2.0));}vec4 decode_color(const vec2 encodedColor) {return vec4(unpack_float(encodedColor[0])/255.0,unpack_float(encodedColor[1])/255.0 +);}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 +);} +#ifdef TERRAIN3D +uniform 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; +#endif +const 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) { +#ifdef TERRAIN3D +highp 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)); +#else +return 1.0; +#endif +}float calculate_visibility(vec4 pos) { +#ifdef TERRAIN3D +vec3 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; +#else +return 1.0; +#endif +}float ele(vec2 pos) { +#ifdef TERRAIN3D +vec4 rgb=(texture(u_terrain,pos)*255.0)*u_terrain_unpack;return rgb.r+rgb.g+rgb.b-u_terrain_unpack.a; +#else +return 0.0; +#endif +}float get_elevation(vec2 pos) { +#ifdef TERRAIN3D +#ifdef GLOBE +if ((pos.y <-32767.5) || (pos.y > 32766.5)) {return 0.0;} +#endif +vec2 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; +#else +return 0.0; +#endif +}const float PI=3.141592653589793;uniform mat4 u_projection_matrix;`),projectionMercator:Zn("","float projectLineThickness(float tileY) {return 1.0;}float projectCircleRadius(float tileY) {return 1.0;}vec4 projectTile(vec2 p) {vec4 result=u_projection_matrix*vec4(p,0.0,1.0);return result;}vec4 projectTile(vec2 p,vec2 rawPos) {vec4 result=u_projection_matrix*vec4(p,0.0,1.0);if (rawPos.y <-32767.5 || rawPos.y > 32766.5) {result.z=-10000000.0;}return result;}vec4 projectTileWithElevation(vec2 posInTile,float elevation) {return u_projection_matrix*vec4(posInTile,elevation,1.0);}vec4 projectTileFor3D(vec2 posInTile,float elevation) {return projectTileWithElevation(posInTile,elevation);}"),projectionGlobe:Zn("",`#define GLOBE_RADIUS 6371008.8 +uniform highp vec4 u_projection_tile_mercator_coords;uniform highp vec4 u_projection_clipping_plane;uniform highp float u_projection_transition;uniform mat4 u_projection_fallback_matrix;vec3 globeRotateVector(vec3 vec,vec2 angles) {vec3 axisRight=vec3(vec.z,0.0,-vec.x);vec3 axisUp=cross(axisRight,vec);axisRight=normalize(axisRight);axisUp=normalize(axisUp);vec2 t=tan(angles);return normalize(vec+axisRight*t.x+axisUp*t.y);}mat3 globeGetRotationMatrix(vec3 spherePos) {vec3 axisRight=vec3(spherePos.z,0.0,-spherePos.x);vec3 axisDown=cross(axisRight,spherePos);axisRight=normalize(axisRight);axisDown=normalize(axisDown);return mat3(axisRight,axisDown,spherePos +);}float circumferenceRatioAtTileY(float tileY) {float mercator_pos_y=u_projection_tile_mercator_coords.y+u_projection_tile_mercator_coords.w*tileY;float spherical_y=2.0*atan(exp(PI-(mercator_pos_y*PI*2.0)))-PI*0.5;return cos(spherical_y);}float projectLineThickness(float tileY) {float thickness=1.0/circumferenceRatioAtTileY(tileY); +if (u_projection_transition < 0.999) {return mix(1.0,thickness,u_projection_transition);} else {return thickness;}}vec3 projectToSphere(vec2 translatedPos,vec2 rawPos) {vec2 mercator_pos=u_projection_tile_mercator_coords.xy+u_projection_tile_mercator_coords.zw*translatedPos;vec2 spherical;spherical.x=mercator_pos.x*PI*2.0+PI;spherical.y=2.0*atan(exp(PI-(mercator_pos.y*PI*2.0)))-PI*0.5;float len=cos(spherical.y);vec3 pos=vec3(sin(spherical.x)*len,sin(spherical.y),cos(spherical.x)*len +);if (rawPos.y <-32767.5) {pos=vec3(0.0,1.0,0.0);}if (rawPos.y > 32766.5) {pos=vec3(0.0,-1.0,0.0);}return pos;}vec3 projectToSphere(vec2 posInTile) {return projectToSphere(posInTile,vec2(0.0,0.0));}float globeComputeClippingZ(vec3 spherePos) {return (1.0-(dot(spherePos,u_projection_clipping_plane.xyz)+u_projection_clipping_plane.w));}vec4 interpolateProjection(vec2 posInTile,vec3 spherePos,float elevation) {vec3 elevatedPos=spherePos*(1.0+elevation/GLOBE_RADIUS);vec4 globePosition=u_projection_matrix*vec4(elevatedPos,1.0);globePosition.z=globeComputeClippingZ(elevatedPos)*globePosition.w;if (u_projection_transition > 0.999) {return globePosition;}vec4 flatPosition=u_projection_fallback_matrix*vec4(posInTile,elevation,1.0);const float z_globeness_threshold=0.2;vec4 result=globePosition;result.z=mix(0.0,globePosition.z,clamp((u_projection_transition-z_globeness_threshold)/(1.0-z_globeness_threshold),0.0,1.0));result.xyw=mix(flatPosition.xyw,globePosition.xyw,u_projection_transition);if ((posInTile.y <-32767.5) || (posInTile.y > 32766.5)) {result=globePosition;const float poles_hidden_anim_percentage=0.02;result.z=mix(globePosition.z,100.0,pow(max((1.0-u_projection_transition)/poles_hidden_anim_percentage,0.0),8.0));}return result;}vec4 interpolateProjectionFor3D(vec2 posInTile,vec3 spherePos,float elevation) {vec3 elevatedPos=spherePos*(1.0+elevation/GLOBE_RADIUS);vec4 globePosition=u_projection_matrix*vec4(elevatedPos,1.0);if (u_projection_transition > 0.999) {return globePosition;}vec4 fallbackPosition=u_projection_fallback_matrix*vec4(posInTile,elevation,1.0);return mix(fallbackPosition,globePosition,u_projection_transition);}vec4 projectTile(vec2 posInTile) {return interpolateProjection(posInTile,projectToSphere(posInTile),0.0);}vec4 projectTile(vec2 posInTile,vec2 rawPos) {return interpolateProjection(posInTile,projectToSphere(posInTile,rawPos),0.0);}vec4 projectTileWithElevation(vec2 posInTile,float elevation) {return interpolateProjection(posInTile,projectToSphere(posInTile),elevation);}vec4 projectTileFor3D(vec2 posInTile,float elevation) {vec3 spherePos=projectToSphere(posInTile,posInTile);return interpolateProjectionFor3D(posInTile,spherePos,elevation);}`),background:Zn(`uniform vec4 u_color;uniform float u_opacity;void main() {fragColor=u_color*u_opacity; +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,"in vec2 a_pos;void main() {gl_Position=projectTile(a_pos);}"),backgroundPattern:Zn(`uniform vec2 u_pattern_tl_a;uniform vec2 u_pattern_br_a;uniform vec2 u_pattern_tl_b;uniform vec2 u_pattern_br_b;uniform vec2 u_texsize;uniform float u_mix;uniform float u_opacity;uniform sampler2D u_image;in vec2 v_pos_a;in vec2 v_pos_b;void main() {vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(u_pattern_tl_a/u_texsize,u_pattern_br_a/u_texsize,imagecoord);vec4 color1=texture(u_image,pos);vec2 imagecoord_b=mod(v_pos_b,1.0);vec2 pos2=mix(u_pattern_tl_b/u_texsize,u_pattern_br_b/u_texsize,imagecoord_b);vec4 color2=texture(u_image,pos2);fragColor=mix(color1,color2,u_mix)*u_opacity; +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,"uniform vec2 u_pattern_size_a;uniform vec2 u_pattern_size_b;uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform float u_scale_a;uniform float u_scale_b;uniform float u_tile_units_to_pixels;in vec2 a_pos;out vec2 v_pos_a;out vec2 v_pos_b;void main() {gl_Position=projectTile(a_pos);v_pos_a=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,u_scale_a*u_pattern_size_a,u_tile_units_to_pixels,a_pos);v_pos_b=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,u_scale_b*u_pattern_size_b,u_tile_units_to_pixels,a_pos);}"),circle:Zn(`in vec3 v_data;in float v_visibility; +#pragma mapbox: define highp vec4 color +#pragma mapbox: define mediump float radius +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define highp vec4 stroke_color +#pragma mapbox: define mediump float stroke_width +#pragma mapbox: define lowp float stroke_opacity +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize mediump float radius +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize highp vec4 stroke_color +#pragma mapbox: initialize mediump float stroke_width +#pragma mapbox: initialize lowp float stroke_opacity +vec2 extrude=v_data.xy;float extrude_length=length(extrude);float antialiased_blur=v_data.z;float opacity_t=smoothstep(0.0,antialiased_blur,extrude_length-1.0);float color_t=stroke_width < 0.01 ? 0.0 : smoothstep(antialiased_blur,0.0,extrude_length-radius/(radius+stroke_width));fragColor=v_visibility*opacity_t*mix(color*opacity,stroke_color*stroke_opacity,color_t);const float epsilon=0.5/255.0;if (fragColor.r < epsilon && fragColor.g < epsilon && fragColor.b < epsilon && fragColor.a < epsilon) {discard;} +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`uniform bool u_scale_with_map;uniform bool u_pitch_with_map;uniform vec2 u_extrude_scale;uniform highp float u_globe_extrude_scale;uniform lowp float u_device_pixel_ratio;uniform highp float u_camera_to_center_distance;uniform vec2 u_translate;in vec2 a_pos;out vec3 v_data;out float v_visibility; +#pragma mapbox: define highp vec4 color +#pragma mapbox: define mediump float radius +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define highp vec4 stroke_color +#pragma mapbox: define mediump float stroke_width +#pragma mapbox: define lowp float stroke_opacity +void main(void) { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize mediump float radius +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize highp vec4 stroke_color +#pragma mapbox: initialize mediump float stroke_width +#pragma mapbox: initialize lowp float stroke_opacity +vec2 pos_raw=a_pos+32768.0;vec2 extrude=vec2(mod(pos_raw,8.0)/7.0*2.0-1.0);vec2 circle_center=floor(pos_raw/8.0)+u_translate;float ele=get_elevation(circle_center);v_visibility=calculate_visibility(projectTileWithElevation(circle_center,ele));if (u_pitch_with_map) { +#ifdef GLOBE +vec3 center_vector=projectToSphere(circle_center); +#endif +float angle_scale=u_globe_extrude_scale;vec2 corner_position=circle_center;if (u_scale_with_map) {angle_scale*=(radius+stroke_width);corner_position+=extrude*u_extrude_scale*(radius+stroke_width);} else { +#ifdef GLOBE +vec4 projected_center=interpolateProjection(circle_center,center_vector,ele); +#else +vec4 projected_center=projectTileWithElevation(circle_center,ele); +#endif +corner_position+=extrude*u_extrude_scale*(radius+stroke_width)*(projected_center.w/u_camera_to_center_distance);angle_scale*=(radius+stroke_width)*(projected_center.w/u_camera_to_center_distance);} +#ifdef GLOBE +vec2 angles=extrude*angle_scale;vec3 corner_vector=globeRotateVector(center_vector,angles);gl_Position=interpolateProjection(corner_position,corner_vector,ele); +#else +gl_Position=projectTileWithElevation(corner_position,ele); +#endif +} else {gl_Position=projectTileWithElevation(circle_center,ele);if (gl_Position.z/gl_Position.w > 1.0) {gl_Position.xy=vec2(10000.0);}if (u_scale_with_map) {gl_Position.xy+=extrude*(radius+stroke_width)*u_extrude_scale*u_camera_to_center_distance;} else {gl_Position.xy+=extrude*(radius+stroke_width)*u_extrude_scale*gl_Position.w;}}float antialiasblur=-max(1.0/u_device_pixel_ratio/(radius+stroke_width),blur);v_data=vec3(extrude.x,extrude.y,antialiasblur);}`),clippingMask:Zn(Jl,"in vec2 a_pos;void main() {gl_Position=projectTile(a_pos);}"),heatmap:Zn(`uniform highp float u_intensity;in vec2 v_extrude; +#pragma mapbox: define highp float weight +#define GAUSS_COEF 0.3989422804014327 +void main() { +#pragma mapbox: initialize highp float weight +float d=-0.5*3.0*3.0*dot(v_extrude,v_extrude);float val=weight*u_intensity*GAUSS_COEF*exp(d);fragColor=vec4(val,1.0,1.0,1.0); +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`uniform float u_extrude_scale;uniform float u_opacity;uniform float u_intensity;uniform highp float u_globe_extrude_scale;in vec2 a_pos;out vec2 v_extrude; +#pragma mapbox: define highp float weight +#pragma mapbox: define mediump float radius +const highp float ZERO=1.0/255.0/16.0; +#define GAUSS_COEF 0.3989422804014327 +void main(void) { +#pragma mapbox: initialize highp float weight +#pragma mapbox: initialize mediump float radius +vec2 pos_raw=a_pos+32768.0;vec2 unscaled_extrude=vec2(mod(pos_raw,8.0)/7.0*2.0-1.0);float S=sqrt(-2.0*log(ZERO/weight/u_intensity/GAUSS_COEF))/3.0;v_extrude=S*unscaled_extrude;vec2 extrude=v_extrude*radius*u_extrude_scale;vec2 circle_center=floor(pos_raw/8.0); +#ifdef GLOBE +vec2 angles=v_extrude*radius*u_globe_extrude_scale;vec3 center_vector=projectToSphere(circle_center);vec3 corner_vector=globeRotateVector(center_vector,angles);gl_Position=interpolateProjection(circle_center+extrude,corner_vector,0.0); +#else +gl_Position=projectTileFor3D(circle_center+extrude,get_elevation(circle_center)); +#endif +}`),heatmapTexture:Zn(`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; +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(0.0); +#endif +}`,"uniform mat4 u_matrix;uniform vec2 u_world;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;}"),collisionBox:Zn("in float v_placed;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;}}","in vec2 a_anchor_pos;in vec2 a_placed;in vec2 a_box_real;uniform vec2 u_pixel_extrude_scale;out float v_placed;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;}"),collisionCircle:Zn("in float v_radius;in vec2 v_extrude;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;}","in vec2 a_pos;in float a_radius;in vec2 a_flags;uniform vec2 u_viewport_size;out float v_radius;out vec2 v_extrude;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);}"),colorRelief:Zn(`#ifdef GL_ES +precision highp float; +#endif +uniform 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 +{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)); +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,"uniform vec2 u_dimension;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;}}"),debug:Zn("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);}","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));}"),depth:Zn(Jl,`in vec2 a_pos;void main() { +#ifdef GLOBE +gl_Position=projectTileFor3D(a_pos,0.0); +#else +gl_Position=u_projection_matrix*vec4(a_pos,0.0,1.0); +#endif +}`),fill:Zn(`#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float opacity +fragColor=color*opacity; +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`uniform vec2 u_fill_translate;in vec2 a_pos; +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float opacity +gl_Position=projectTile(a_pos+u_fill_translate,a_pos);}`),fillOutline:Zn(`in vec2 v_pos; +#ifdef GLOBE +in float v_depth; +#endif +#pragma mapbox: define highp vec4 outline_color +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 outline_color +#pragma mapbox: initialize lowp float opacity +float dist=length(v_pos-gl_FragCoord.xy);float alpha=1.0-smoothstep(0.0,1.0,dist);fragColor=outline_color*(alpha*opacity); +#ifdef GLOBE +if (v_depth > 1.0) {discard;} +#endif +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`uniform vec2 u_world;uniform vec2 u_fill_translate;in vec2 a_pos;out vec2 v_pos; +#ifdef GLOBE +out float v_depth; +#endif +#pragma mapbox: define highp vec4 outline_color +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 outline_color +#pragma mapbox: initialize lowp float opacity +gl_Position=projectTile(a_pos+u_fill_translate,a_pos);v_pos=(gl_Position.xy/gl_Position.w+1.0)/2.0*u_world; +#ifdef GLOBE +v_depth=gl_Position.z/gl_Position.w; +#endif +}`),fillOutlinePattern:Zn(`uniform vec2 u_texsize;uniform sampler2D u_image;uniform float u_fade;in vec2 v_pos_a;in vec2 v_pos_b;in vec2 v_pos; +#ifdef GLOBE +in float v_depth; +#endif +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +void main() { +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +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;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);float dist=length(v_pos-gl_FragCoord.xy);float alpha=1.0-smoothstep(0.0,1.0,dist);fragColor=mix(color1,color2,u_fade)*alpha*opacity; +#ifdef GLOBE +if (v_depth > 1.0) {discard;} +#endif +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`uniform vec2 u_world;uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform vec3 u_scale;uniform vec2 u_fill_translate;in vec2 a_pos;out vec2 v_pos_a;out vec2 v_pos_b;out vec2 v_pos; +#ifdef GLOBE +out float v_depth; +#endif +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +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 tileRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;gl_Position=projectTile(a_pos+u_fill_translate,a_pos);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;v_pos_a=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,fromScale*display_size_a,tileRatio,a_pos);v_pos_b=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,toScale*display_size_b,tileRatio,a_pos);v_pos=(gl_Position.xy/gl_Position.w+1.0)/2.0*u_world; +#ifdef GLOBE +v_depth=gl_Position.z/gl_Position.w; +#endif +}`),fillPattern:Zn(`#ifdef GL_ES +precision highp float; +#endif +uniform vec2 u_texsize;uniform float u_fade;uniform sampler2D u_image;in vec2 v_pos_a;in vec2 v_pos_b; +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +void main() { +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +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;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; +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform vec3 u_scale;uniform vec2 u_fill_translate;in vec2 a_pos;out vec2 v_pos_a;out vec2 v_pos_b; +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +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);}`),fillExtrusion:Zn(`in vec4 v_color;void main() {fragColor=v_color; +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`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;in vec2 a_pos;in vec4 a_normal_ed; +#ifdef TERRAIN3D +in vec2 a_centroid; +#endif +out vec4 v_color; +#pragma mapbox: define highp float base +#pragma mapbox: define highp float height +#pragma mapbox: define highp vec4 color +void main() { +#pragma mapbox: initialize highp float base +#pragma mapbox: initialize highp float height +#pragma mapbox: initialize highp vec4 color +vec3 normal=a_normal_ed.xyz; +#ifdef TERRAIN3D +float height_terrain3d_offset=get_elevation(a_centroid);float base_terrain3d_offset=height_terrain3d_offset-(base > 0.0 ? 0.0 : 10.0); +#else +float height_terrain3d_offset=0.0;float base_terrain3d_offset=0.0; +#endif +base=max(0.0,base)+base_terrain3d_offset;height=max(0.0,height)+height_terrain3d_offset;float t=mod(normal.x,2.0);float elevation=t > 0.0 ? height : base;vec2 posInTile=a_pos+u_fill_translate; +#ifdef GLOBE +vec3 spherePos=projectToSphere(posInTile,a_pos);gl_Position=interpolateProjectionFor3D(posInTile,spherePos,elevation); +#else +gl_Position=u_projection_matrix*vec4(posInTile,elevation,1.0); +#endif +float 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); +#ifdef GLOBE +mat3 rotMatrix=globeGetRotationMatrix(spherePos);normalForLighting=rotMatrix*normalForLighting;directional=mix(directional,clamp(dot(normalForLighting,u_lightpos_globe),0.0,1.0),u_projection_transition); +#endif +directional=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;}`),fillExtrusionPattern:Zn(`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; +#pragma mapbox: define lowp float base +#pragma mapbox: define lowp float height +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float base +#pragma mapbox: initialize lowp float height +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +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;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; +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`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;in vec2 a_pos;in vec4 a_normal_ed; +#ifdef TERRAIN3D +in vec2 a_centroid; +#endif +#ifdef GLOBE +out vec3 v_sphere_pos; +#endif +out vec2 v_pos_a;out vec2 v_pos_b;out vec4 v_lighting; +#pragma mapbox: define lowp float base +#pragma mapbox: define lowp float height +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float base +#pragma mapbox: initialize lowp float height +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +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 tileRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;vec3 normal=a_normal_ed.xyz;float edgedistance=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; +#ifdef TERRAIN3D +float height_terrain3d_offset=get_elevation(a_centroid);float base_terrain3d_offset=height_terrain3d_offset-(base > 0.0 ? 0.0 : 10.0); +#else +float height_terrain3d_offset=0.0;float base_terrain3d_offset=0.0; +#endif +base=max(0.0,base)+base_terrain3d_offset;height=max(0.0,height)+height_terrain3d_offset;float t=mod(normal.x,2.0);float elevation=t > 0.0 ? height : base;vec2 posInTile=a_pos+u_fill_translate; +#ifdef GLOBE +vec3 spherePos=projectToSphere(posInTile,a_pos);vec3 elevatedPos=spherePos*(1.0+elevation/GLOBE_RADIUS);v_sphere_pos=elevatedPos;gl_Position=interpolateProjectionFor3D(posInTile,spherePos,elevation); +#else +gl_Position=u_projection_matrix*vec4(posInTile,elevation,1.0); +#endif +vec2 pos=normal.x==1.0 && normal.y==0.0 && normal.z==16384.0 +? a_pos +: 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;}`),hillshadePrepare:Zn(`#ifdef GL_ES +precision highp float; +#endif +uniform 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); +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,"uniform mat4 u_matrix;uniform vec2 u_dimension;in vec2 a_pos;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;}"),hillshade:Zn(`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]; +#define PI 3.141592653589793 +#define STANDARD 0 +#define COMBINED 1 +#define IGOR 2 +#define MULTIDIRECTIONAL 3 +#define BASIC 4 +float 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 +{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 +{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);} +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,"uniform mat4 u_matrix;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;}}"),line:Zn(`uniform lowp float u_device_pixel_ratio;in vec2 v_width2;in vec2 v_normal;in float v_gamma_scale; +#ifdef GLOBE +in float v_depth; +#endif +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +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); +#ifdef GLOBE +if (v_depth > 1.0) {discard;} +#endif +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,` +#define scale 0.015873016 +in vec2 a_pos_normal;in vec4 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;out vec2 v_width2;out float v_gamma_scale;out highp float v_linesofar; +#ifdef GLOBE +out float v_depth; +#endif +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float gapwidth +#pragma mapbox: define lowp float offset +#pragma mapbox: define mediump float width +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float gapwidth +#pragma mapbox: initialize lowp float offset +#pragma mapbox: initialize mediump float width +float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;v_linesofar=(floor(a_data.z/4.0)+a_data.w*64.0)*2.0;vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;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; +#ifdef GLOBE +v_depth=gl_Position.z/gl_Position.w; +#endif +#ifdef TERRAIN3D +v_gamma_scale=1.0; +#else +float 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; +#endif +v_width2=vec2(outset,inset);}`),lineGradient:Zn(`uniform lowp float u_device_pixel_ratio;uniform sampler2D u_image;in vec2 v_width2;in vec2 v_normal;in float v_gamma_scale;in highp vec2 v_uv; +#ifdef GLOBE +in float v_depth; +#endif +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +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); +#ifdef GLOBE +if (v_depth > 1.0) {discard;} +#endif +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,` +#define scale 0.015873016 +in vec2 a_pos_normal;in vec4 a_data;in float a_uv_x;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;out vec2 v_width2;out float v_gamma_scale;out highp vec2 v_uv; +#ifdef GLOBE +out float v_depth; +#endif +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float gapwidth +#pragma mapbox: define lowp float offset +#pragma mapbox: define mediump float width +void main() { +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float gapwidth +#pragma mapbox: initialize lowp float offset +#pragma mapbox: initialize mediump float width +float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;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=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;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; +#ifdef GLOBE +v_depth=gl_Position.z/gl_Position.w; +#endif +#ifdef TERRAIN3D +v_gamma_scale=1.0; +#else +float 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; +#endif +v_width2=vec2(outset,inset);}`),linePattern:Zn(`#ifdef GL_ES +precision highp float; +#endif +uniform lowp float u_device_pixel_ratio;uniform vec2 u_texsize;uniform float u_fade;uniform mediump vec3 u_scale;uniform sampler2D u_image;in vec2 v_normal;in vec2 v_width2;in float v_linesofar;in float v_gamma_scale;in float v_width; +#ifdef GLOBE +in float v_depth; +#endif +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +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;vec2 pattern_size_a=vec2(display_size_a.x*fromScale/tileZoomRatio,display_size_a.y);vec2 pattern_size_b=vec2(display_size_b.x*toScale/tileZoomRatio,display_size_b.y);float aspect_a=display_size_a.y/v_width;float aspect_b=display_size_b.y/v_width;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);float x_a=mod(v_linesofar/pattern_size_a.x*aspect_a,1.0);float x_b=mod(v_linesofar/pattern_size_b.x*aspect_b,1.0);float y=0.5*v_normal.y+0.5;vec2 texel_size=1.0/u_texsize;vec2 pos_a=mix(pattern_tl_a*texel_size-texel_size,pattern_br_a*texel_size+texel_size,vec2(x_a,y));vec2 pos_b=mix(pattern_tl_b*texel_size-texel_size,pattern_br_b*texel_size+texel_size,vec2(x_b,y));vec4 color=mix(texture(u_image,pos_a),texture(u_image,pos_b),u_fade);fragColor=color*alpha*opacity; +#ifdef GLOBE +if (v_depth > 1.0) {discard;} +#endif +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,` +#define scale 0.015873016 +#define LINE_DISTANCE_SCALE 2.0 +in vec2 a_pos_normal;in vec4 a_data;uniform vec2 u_translation;uniform vec2 u_units_to_pixels;uniform mediump float u_ratio;uniform lowp float u_device_pixel_ratio;out vec2 v_normal;out vec2 v_width2;out float v_linesofar;out float v_gamma_scale;out float v_width; +#ifdef GLOBE +out float v_depth; +#endif +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float offset +#pragma mapbox: define mediump float gapwidth +#pragma mapbox: define mediump float width +#pragma mapbox: define lowp float floorwidth +#pragma mapbox: define lowp vec4 pattern_from +#pragma mapbox: define lowp vec4 pattern_to +#pragma mapbox: define lowp float pixel_ratio_from +#pragma mapbox: define lowp float pixel_ratio_to +void main() { +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float offset +#pragma mapbox: initialize mediump float gapwidth +#pragma mapbox: initialize mediump float width +#pragma mapbox: initialize lowp float floorwidth +#pragma mapbox: initialize mediump vec4 pattern_from +#pragma mapbox: initialize mediump vec4 pattern_to +#pragma mapbox: initialize lowp float pixel_ratio_from +#pragma mapbox: initialize lowp float pixel_ratio_to +float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;float a_linesofar=(floor(a_data.z/4.0)+a_data.w*64.0)*LINE_DISTANCE_SCALE;vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;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; +#ifdef GLOBE +v_depth=gl_Position.z/gl_Position.w; +#endif +#ifdef TERRAIN3D +v_gamma_scale=1.0; +#else +float 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; +#endif +v_linesofar=a_linesofar;v_width2=vec2(outset,inset);v_width=floorwidth;}`),lineSDF:Zn(`uniform lowp float u_device_pixel_ratio;uniform lowp float u_lineatlas_width;uniform sampler2D u_image;uniform float u_mix;in vec2 v_normal;in vec2 v_width2;in vec2 v_tex_a;in vec2 v_tex_b;in float v_gamma_scale; +#ifdef GLOBE +in float v_depth; +#endif +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float width +#pragma mapbox: define lowp float floorwidth +#pragma mapbox: define mediump vec4 dasharray_from +#pragma mapbox: define mediump vec4 dasharray_to +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float width +#pragma mapbox: initialize lowp float floorwidth +#pragma mapbox: initialize mediump vec4 dasharray_from +#pragma mapbox: initialize mediump vec4 dasharray_to +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);float sdfdist_a=texture(u_image,v_tex_a).a;float sdfdist_b=texture(u_image,v_tex_b).a;float sdfdist=mix(sdfdist_a,sdfdist_b,u_mix);float sdfgamma=(u_lineatlas_width/256.0/u_device_pixel_ratio)/min(dasharray_from.w,dasharray_to.w);alpha*=smoothstep(0.5-sdfgamma/floorwidth,0.5+sdfgamma/floorwidth,sdfdist);fragColor=color*(alpha*opacity); +#ifdef GLOBE +if (v_depth > 1.0) {discard;} +#endif +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,` +#define scale 0.015873016 +#define LINE_DISTANCE_SCALE 2.0 +in vec2 a_pos_normal;in vec4 a_data;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_tileratio;uniform float u_crossfade_from;uniform float u_crossfade_to;uniform float u_lineatlas_height;out vec2 v_normal;out vec2 v_width2;out vec2 v_tex_a;out vec2 v_tex_b;out float v_gamma_scale; +#ifdef GLOBE +out float v_depth; +#endif +#pragma mapbox: define highp vec4 color +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float gapwidth +#pragma mapbox: define lowp float offset +#pragma mapbox: define mediump float width +#pragma mapbox: define lowp float floorwidth +#pragma mapbox: define mediump vec4 dasharray_from +#pragma mapbox: define mediump vec4 dasharray_to +void main() { +#pragma mapbox: initialize highp vec4 color +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float gapwidth +#pragma mapbox: initialize lowp float offset +#pragma mapbox: initialize mediump float width +#pragma mapbox: initialize lowp float floorwidth +#pragma mapbox: initialize mediump vec4 dasharray_from +#pragma mapbox: initialize mediump vec4 dasharray_to +float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;float a_linesofar=(floor(a_data.z/4.0)+a_data.w*64.0)*LINE_DISTANCE_SCALE;vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;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; +#ifdef GLOBE +v_depth=gl_Position.z/gl_Position.w; +#endif +#ifdef TERRAIN3D +v_gamma_scale=1.0; +#else +float 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; +#endif +float u_patternscale_a_x=u_tileratio/dasharray_from.w/u_crossfade_from;float u_patternscale_a_y=-dasharray_from.z/2.0/u_lineatlas_height;float u_patternscale_b_x=u_tileratio/dasharray_to.w/u_crossfade_to;float u_patternscale_b_y=-dasharray_to.z/2.0/u_lineatlas_height;v_tex_a=vec2(a_linesofar*u_patternscale_a_x/floorwidth,normal.y*u_patternscale_a_y+(float(dasharray_from.y)+0.5)/u_lineatlas_height);v_tex_b=vec2(a_linesofar*u_patternscale_b_x/floorwidth,normal.y*u_patternscale_b_y+(float(dasharray_to.y)+0.5)/u_lineatlas_height);v_width2=vec2(outset,inset);}`),lineGradientSDF:Zn(`uniform lowp float u_device_pixel_ratio;uniform sampler2D u_image;uniform sampler2D u_image_dash;uniform float u_mix;uniform lowp float u_lineatlas_width;in vec2 v_normal;in vec2 v_width2;in vec2 v_tex_a;in vec2 v_tex_b;in float v_gamma_scale;in highp vec2 v_uv; +#ifdef GLOBE +in float v_depth; +#endif +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float width +#pragma mapbox: define lowp float floorwidth +#pragma mapbox: define mediump vec4 dasharray_from +#pragma mapbox: define mediump vec4 dasharray_to +void main() { +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float width +#pragma mapbox: initialize lowp float floorwidth +#pragma mapbox: initialize mediump vec4 dasharray_from +#pragma mapbox: initialize mediump vec4 dasharray_to +float dist=length(v_normal)*v_width2.s;float blur2=(blur+1.0/u_device_pixel_ratio)*v_gamma_scale;float alpha=clamp(min(dist-(v_width2.t-blur2),v_width2.s-dist)/blur2,0.0,1.0);vec4 color=texture(u_image,v_uv);float sdfdist_a=texture(u_image_dash,v_tex_a).a;float sdfdist_b=texture(u_image_dash,v_tex_b).a;float sdfdist=mix(sdfdist_a,sdfdist_b,u_mix);float sdfgamma=(u_lineatlas_width/256.0)/min(dasharray_from.w,dasharray_to.w);float dash_alpha=smoothstep(0.5-sdfgamma/floorwidth,0.5+sdfgamma/floorwidth,sdfdist);fragColor=color*(alpha*dash_alpha*opacity); +#ifdef GLOBE +if (v_depth > 1.0) {discard;} +#endif +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,` +#define scale 0.015873016 +#define LINE_DISTANCE_SCALE 2.0 +in vec2 a_pos_normal;in vec4 a_data;in float a_uv_x;in float a_split_index;uniform vec2 u_translation;uniform mediump float u_ratio;uniform lowp float u_device_pixel_ratio;uniform vec2 u_units_to_pixels;uniform float u_image_height;uniform float u_tileratio;uniform float u_crossfade_from;uniform float u_crossfade_to;uniform float u_lineatlas_height;out vec2 v_normal;out vec2 v_width2;out float v_gamma_scale;out highp vec2 v_uv;out vec2 v_tex_a;out vec2 v_tex_b; +#ifdef GLOBE +out float v_depth; +#endif +#pragma mapbox: define lowp float blur +#pragma mapbox: define lowp float opacity +#pragma mapbox: define mediump float gapwidth +#pragma mapbox: define lowp float offset +#pragma mapbox: define mediump float width +#pragma mapbox: define lowp float floorwidth +#pragma mapbox: define mediump vec4 dasharray_from +#pragma mapbox: define mediump vec4 dasharray_to +void main() { +#pragma mapbox: initialize lowp float blur +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize mediump float gapwidth +#pragma mapbox: initialize lowp float offset +#pragma mapbox: initialize mediump float width +#pragma mapbox: initialize lowp float floorwidth +#pragma mapbox: initialize mediump vec4 dasharray_from +#pragma mapbox: initialize mediump vec4 dasharray_to +float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;float a_linesofar=(floor(a_data.z/4.0)+a_data.w*64.0)*LINE_DISTANCE_SCALE;float texel_height=1.0/u_image_height;float half_texel_height=0.5*texel_height;v_uv=vec2(a_uv_x,a_split_index*texel_height-half_texel_height);vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;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; +#ifdef GLOBE +v_depth=gl_Position.z/gl_Position.w; +#endif +#ifdef TERRAIN3D +v_gamma_scale=1.0; +#else +float 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; +#endif +float u_patternscale_a_x=u_tileratio/dasharray_from.w/u_crossfade_from;float u_patternscale_a_y=-dasharray_from.z/2.0/u_lineatlas_height;float u_patternscale_b_x=u_tileratio/dasharray_to.w/u_crossfade_to;float u_patternscale_b_y=-dasharray_to.z/2.0/u_lineatlas_height;v_tex_a=vec2(a_linesofar*u_patternscale_a_x/floorwidth,normal.y*u_patternscale_a_y+(float(dasharray_from.y)+0.5)/u_lineatlas_height);v_tex_b=vec2(a_linesofar*u_patternscale_b_x/floorwidth,normal.y*u_patternscale_b_y+(float(dasharray_to.y)+0.5)/u_lineatlas_height);v_width2=vec2(outset,inset);}`),raster:Zn(`uniform float u_fade_t;uniform float u_opacity;uniform sampler2D u_image0;uniform sampler2D u_image1;in vec2 v_pos0;in vec2 v_pos1;uniform float u_brightness_low;uniform float u_brightness_high;uniform float u_saturation_factor;uniform float u_contrast_factor;uniform vec3 u_spin_weights;void main() {vec4 color0=texture(u_image0,v_pos0);vec4 color1=texture(u_image1,v_pos1);if (color0.a > 0.0) {color0.rgb=color0.rgb/color0.a;}if (color1.a > 0.0) {color1.rgb=color1.rgb/color1.a;}vec4 color=mix(color0,color1,u_fade_t);color.a*=u_opacity;vec3 rgb=color.rgb;rgb=vec3(dot(rgb,u_spin_weights.xyz),dot(rgb,u_spin_weights.zxy),dot(rgb,u_spin_weights.yzx));float average=(color.r+color.g+color.b)/3.0;rgb+=(average-rgb)*u_saturation_factor;rgb=(rgb-0.5)*u_contrast_factor+0.5;vec3 u_high_vec=vec3(u_brightness_low,u_brightness_low,u_brightness_low);vec3 u_low_vec=vec3(u_brightness_high,u_brightness_high,u_brightness_high);fragColor=vec4(mix(u_high_vec,u_low_vec,rgb)*color.a,color.a); +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`uniform vec2 u_tl_parent;uniform float u_scale_parent;uniform float u_buffer_scale;uniform vec4 u_coords_top;uniform vec4 u_coords_bottom;in vec2 a_pos;out vec2 v_pos0;out vec2 v_pos1;void main() {vec2 fractionalPos=a_pos/8192.0;vec2 position=mix(mix(u_coords_top.xy,u_coords_top.zw,fractionalPos.x),mix(u_coords_bottom.xy,u_coords_bottom.zw,fractionalPos.x),fractionalPos.y);gl_Position=projectTile(position,position);v_pos0=((fractionalPos-0.5)/u_buffer_scale)+0.5; +#ifdef GLOBE +if (a_pos.y <-32767.5) {v_pos0.y=0.0;}if (a_pos.y > 32766.5) {v_pos0.y=1.0;} +#endif +v_pos1=(v_pos0*u_scale_parent)+u_tl_parent;}`),symbolIcon:Zn(`uniform sampler2D u_texture;in vec2 v_tex;in float v_fade_opacity; +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize lowp float opacity +lowp float alpha=opacity*v_fade_opacity;fragColor=texture(u_texture,v_tex)*alpha; +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`in vec4 a_pos_offset;in vec4 a_data;in vec4 a_pixeloffset;in vec3 a_projected_pos;in float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform highp float u_camera_to_center_distance;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform float u_fade_change;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform vec2 u_texsize;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform vec2 u_translation;uniform float u_pitched_scale;out vec2 v_tex;out float v_fade_opacity; +#pragma mapbox: define lowp float opacity +void main() { +#pragma mapbox: initialize lowp float opacity +vec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);vec2 a_pxoffset=a_pixeloffset.xy;vec2 a_minFontScale=a_pixeloffset.zw/256.0;float ele=get_elevation(a_pos);highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ? +camera_to_anchor_distance/u_camera_to_center_distance : +u_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(0.5+0.5*distance_ratio,0.0,4.0);size*=perspective_ratio;float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0; +#ifdef GLOBE +if(u_pitch_with_map) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);} +#endif +vec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*max(a_minFontScale,fontScale)+a_pxoffset/16.0)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}gl_Position=finalPos;v_tex=a_tex/u_texsize;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float visibility=calculate_visibility(projectedPoint);v_fade_opacity=max(0.0,min(visibility,fade_opacity[0]+fade_change));}`),symbolSDF:Zn(`#define SDF_PX 8.0 +uniform bool u_is_halo;uniform sampler2D u_texture;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;uniform bool u_is_text;in vec2 v_data0;in vec3 v_data1; +#pragma mapbox: define highp vec4 fill_color +#pragma mapbox: define highp vec4 halo_color +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float halo_width +#pragma mapbox: define lowp float halo_blur +void main() { +#pragma mapbox: initialize highp vec4 fill_color +#pragma mapbox: initialize highp vec4 halo_color +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float halo_width +#pragma mapbox: initialize lowp float halo_blur +float EDGE_GAMMA=0.105/u_device_pixel_ratio;vec2 tex=v_data0.xy;float gamma_scale=v_data1.x;float size=v_data1.y;float fade_opacity=v_data1[2];float fontScale=u_is_text ? size/24.0 : size;lowp vec4 color=fill_color;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float inner_edge=(256.0-64.0)/256.0;if (u_is_halo) {color=halo_color;gamma=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);inner_edge=inner_edge+gamma*gamma_scale;}lowp float dist=texture(u_texture,tex).a;highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(inner_edge-gamma_scaled,inner_edge+gamma_scaled,dist);if (u_is_halo) {lowp float halo_edge=(6.0-halo_width/fontScale)/SDF_PX;alpha=min(smoothstep(halo_edge-gamma_scaled,halo_edge+gamma_scaled,dist),1.0-alpha);}fragColor=color*(alpha*opacity*fade_opacity); +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`in vec4 a_pos_offset;in vec4 a_data;in vec4 a_pixeloffset;in vec3 a_projected_pos;in float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform highp float u_camera_to_center_distance;uniform float u_fade_change;uniform vec2 u_texsize;uniform vec2 u_translation;uniform float u_pitched_scale;out vec2 v_data0;out vec3 v_data1; +#pragma mapbox: define highp vec4 fill_color +#pragma mapbox: define highp vec4 halo_color +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float halo_width +#pragma mapbox: define lowp float halo_blur +void main() { +#pragma mapbox: initialize highp vec4 fill_color +#pragma mapbox: initialize highp vec4 halo_color +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float halo_width +#pragma mapbox: initialize lowp float halo_blur +vec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);vec2 a_pxoffset=a_pixeloffset.xy;float ele=get_elevation(a_pos);highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ? +camera_to_anchor_distance/u_camera_to_center_distance : +u_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(0.5+0.5*distance_ratio,0.0,4.0);size*=perspective_ratio;float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0; +#ifdef GLOBE +if(u_pitch_with_map) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);} +#endif +vec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*fontScale+a_pxoffset)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}float gamma_scale=finalPos.w;gl_Position=finalPos;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float visibility=calculate_visibility(projectedPoint);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float interpolated_fade_opacity=max(0.0,min(visibility,fade_opacity[0]+fade_change));v_data0=a_tex/u_texsize;v_data1=vec3(gamma_scale,size,interpolated_fade_opacity);}`),symbolTextAndIcon:Zn(`#define SDF_PX 8.0 +#define SDF 1.0 +#define ICON 0.0 +uniform bool u_is_halo;uniform sampler2D u_texture;uniform sampler2D u_texture_icon;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;in vec4 v_data0;in vec4 v_data1; +#pragma mapbox: define highp vec4 fill_color +#pragma mapbox: define highp vec4 halo_color +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float halo_width +#pragma mapbox: define lowp float halo_blur +void main() { +#pragma mapbox: initialize highp vec4 fill_color +#pragma mapbox: initialize highp vec4 halo_color +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float halo_width +#pragma mapbox: initialize lowp float halo_blur +float fade_opacity=v_data1[2];if (v_data1.w==ICON) {vec2 tex_icon=v_data0.zw;lowp float alpha=opacity*fade_opacity;fragColor=texture(u_texture_icon,tex_icon)*alpha; +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +return;}vec2 tex=v_data0.xy;float EDGE_GAMMA=0.105/u_device_pixel_ratio;float gamma_scale=v_data1.x;float size=v_data1.y;float fontScale=size/24.0;lowp vec4 color=fill_color;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float buff=(256.0-64.0)/256.0;if (u_is_halo) {color=halo_color;gamma=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);buff=(6.0-halo_width/fontScale)/SDF_PX;}lowp float dist=texture(u_texture,tex).a;highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(buff-gamma_scaled,buff+gamma_scaled,dist);fragColor=color*(alpha*opacity*fade_opacity); +#ifdef OVERDRAW_INSPECTOR +fragColor=vec4(1.0); +#endif +}`,`in vec4 a_pos_offset;in vec4 a_data;in vec3 a_projected_pos;in float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform highp float u_camera_to_center_distance;uniform float u_fade_change;uniform vec2 u_texsize;uniform vec2 u_texsize_icon;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform vec2 u_translation;uniform float u_pitched_scale;out vec4 v_data0;out vec4 v_data1; +#pragma mapbox: define highp vec4 fill_color +#pragma mapbox: define highp vec4 halo_color +#pragma mapbox: define lowp float opacity +#pragma mapbox: define lowp float halo_width +#pragma mapbox: define lowp float halo_blur +void main() { +#pragma mapbox: initialize highp vec4 fill_color +#pragma mapbox: initialize highp vec4 halo_color +#pragma mapbox: initialize lowp float opacity +#pragma mapbox: initialize lowp float halo_width +#pragma mapbox: initialize lowp float halo_blur +vec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);float is_sdf=a_size[0]-2.0*a_size_min;float ele=get_elevation(a_pos);highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ? +camera_to_anchor_distance/u_camera_to_center_distance : +u_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(0.5+0.5*distance_ratio,0.0,4.0);size*=perspective_ratio;float fontScale=size/24.0;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0; +#ifdef GLOBE +if(u_pitch_with_map && !u_is_along_line) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);} +#endif +vec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*fontScale)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}float gamma_scale=finalPos.w;gl_Position=finalPos;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float visibility=calculate_visibility(projectedPoint);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float interpolated_fade_opacity=max(0.0,min(visibility,fade_opacity[0]+fade_change));v_data0.xy=a_tex/u_texsize;v_data0.zw=a_tex/u_texsize_icon;v_data1=vec4(gamma_scale,size,interpolated_fade_opacity,is_sdf);}`),terrain:Zn("uniform sampler2D u_texture;uniform vec4 u_fog_color;uniform vec4 u_horizon_color;uniform float u_fog_ground_blend;uniform float u_fog_ground_blend_opacity;uniform float u_horizon_fog_blend;uniform bool u_is_globe_mode;in vec2 v_texture_pos;in float v_fog_depth;const float gamma=2.2;vec4 gammaToLinear(vec4 color) {return pow(color,vec4(gamma));}vec4 linearToGamma(vec4 color) {return pow(color,vec4(1.0/gamma));}void main() {vec4 surface_color=texture(u_texture,vec2(v_texture_pos.x,1.0-v_texture_pos.y));if (!u_is_globe_mode && v_fog_depth > u_fog_ground_blend) {vec4 surface_color_linear=gammaToLinear(surface_color);float blend_color=smoothstep(0.0,1.0,max((v_fog_depth-u_horizon_fog_blend)/(1.0-u_horizon_fog_blend),0.0));vec4 fog_horizon_color_linear=mix(gammaToLinear(u_fog_color),gammaToLinear(u_horizon_color),blend_color);float factor_fog=max(v_fog_depth-u_fog_ground_blend,0.0)/(1.0-u_fog_ground_blend);fragColor=linearToGamma(mix(surface_color_linear,fog_horizon_color_linear,pow(factor_fog,2.0)*u_fog_ground_blend_opacity));} else {fragColor=surface_color;}}","in vec3 a_pos3d;uniform mat4 u_fog_matrix;uniform float u_ele_delta;out vec2 v_texture_pos;out float v_fog_depth;void main() {float ele=get_elevation(a_pos3d.xy);float ele_delta=a_pos3d.z==1.0 ? u_ele_delta : 0.0;v_texture_pos=a_pos3d.xy/8192.0;gl_Position=projectTileFor3D(a_pos3d.xy,get_elevation(a_pos3d.xy)-ele_delta);vec4 pos=u_fog_matrix*vec4(a_pos3d.xy,ele,1.0);v_fog_depth=pos.z/pos.w*0.5+0.5;}"),terrainDepth:Zn("in float v_depth;const highp vec4 bitSh=vec4(256.*256.*256.,256.*256.,256.,1.);const highp vec4 bitMsk=vec4(0.,vec3(1./256.0));highp vec4 pack(highp float value) {highp vec4 comp=fract(value*bitSh);comp-=comp.xxyz*bitMsk;return comp;}void main() {fragColor=pack(v_depth);}","in vec3 a_pos3d;uniform float u_ele_delta;out float v_depth;void main() {float ele=get_elevation(a_pos3d.xy);float ele_delta=a_pos3d.z==1.0 ? u_ele_delta : 0.0;gl_Position=projectTileFor3D(a_pos3d.xy,ele-ele_delta);v_depth=gl_Position.z/gl_Position.w;}"),terrainCoords:Zn("precision mediump float;uniform sampler2D u_texture;uniform float u_terrain_coords_id;in vec2 v_texture_pos;void main() {vec4 rgba=texture(u_texture,v_texture_pos);fragColor=vec4(rgba.r,rgba.g,rgba.b,u_terrain_coords_id);}","in vec3 a_pos3d;uniform float u_ele_delta;out vec2 v_texture_pos;void main() {float ele=get_elevation(a_pos3d.xy);float ele_delta=a_pos3d.z==1.0 ? u_ele_delta : 0.0;v_texture_pos=a_pos3d.xy/8192.0;gl_Position=projectTileFor3D(a_pos3d.xy,ele-ele_delta);}"),projectionErrorMeasurement:Zn("in vec4 v_output_error_encoded;void main() {fragColor=v_output_error_encoded;}","in vec2 a_pos;uniform highp float u_input;uniform highp float u_output_expected;out vec4 v_output_error_encoded;void main() {float real_output=2.0*atan(exp(PI-(u_input*PI*2.0)))-PI*0.5;float error=real_output-u_output_expected;float abs_error=abs(error)*128.0;v_output_error_encoded.x=min(floor(abs_error*256.0),255.0)/255.0;abs_error-=v_output_error_encoded.x;v_output_error_encoded.y=min(floor(abs_error*65536.0),255.0)/255.0;abs_error-=v_output_error_encoded.x/255.0;v_output_error_encoded.z=min(floor(abs_error*16777216.0),255.0)/255.0;v_output_error_encoded.w=error >=0.0 ? 1.0 : 0.0;gl_Position=vec4(a_pos,0.0,1.0);}"),atmosphere:Zn(`in vec3 view_direction;uniform vec3 u_sun_pos;uniform vec3 u_globe_position;uniform float u_globe_radius;uniform float u_atmosphere_blend;/**Shader use from https:*Made some change to adapt to MapLibre Globe geometry*/const float PI=3.141592653589793;const int iSteps=5;const int jSteps=3;/*radius of the planet*/const float EARTH_RADIUS=6371e3;/*radius of the atmosphere*/const float ATMOS_RADIUS=6471e3;vec2 rsi(vec3 r0,vec3 rd,float sr) {float a=dot(rd,rd);float b=2.0*dot(rd,r0);float c=dot(r0,r0)-(sr*sr);float d=(b*b)-4.0*a*c;if (d < 0.0) return vec2(1e5,-1e5);return vec2((-b-sqrt(d))/(2.0*a),(-b+sqrt(d))/(2.0*a));}vec4 atmosphere(vec3 r,vec3 r0,vec3 pSun,float iSun,float rPlanet,float rAtmos,vec3 kRlh,float kMie,float shRlh,float shMie,float g) {pSun=normalize(pSun);r=normalize(r);vec2 p=rsi(r0,r,rAtmos);if (p.x > p.y) {return vec4(0.0,0.0,0.0,1.0);}if (p.x < 0.0) {p.x=0.0;}vec3 pos=r0+r*p.x;vec2 p2=rsi(r0,r,rPlanet);if (p2.x <=p2.y && p2.x > 0.0) {p.y=min(p.y,p2.x);}float iStepSize=(p.y-p.x)/float(iSteps);float iTime=p.x+iStepSize*0.5;vec3 totalRlh=vec3(0,0,0);vec3 totalMie=vec3(0,0,0);float iOdRlh=0.0;float iOdMie=0.0;float mu=dot(r,pSun);float mumu=mu*mu;float gg=g*g;float pRlh=3.0/(16.0*PI)*(1.0+mumu);float pMie=3.0/(8.0*PI)*((1.0-gg)*(mumu+1.0))/(pow(1.0+gg-2.0*mu*g,1.5)*(2.0+gg));for (int i=0; i < iSteps; i++) {vec3 iPos=r0+r*iTime;float iHeight=length(iPos)-rPlanet;float odStepRlh=exp(-iHeight/shRlh)*iStepSize;float odStepMie=exp(-iHeight/shMie)*iStepSize;iOdRlh+=odStepRlh;iOdMie+=odStepMie;float jStepSize=rsi(iPos,pSun,rAtmos).y/float(jSteps);float jTime=jStepSize*0.5;float jOdRlh=0.0;float jOdMie=0.0;for (int j=0; j < jSteps; j++) {vec3 jPos=iPos+pSun*jTime;float jHeight=length(jPos)-rPlanet;jOdRlh+=exp(-jHeight/shRlh)*jStepSize;jOdMie+=exp(-jHeight/shMie)*jStepSize;jTime+=jStepSize;}vec3 attn=exp(-(kMie*(iOdMie+jOdMie)+kRlh*(iOdRlh+jOdRlh)));totalRlh+=odStepRlh*attn;totalMie+=odStepMie*attn;iTime+=iStepSize;}float opacity=exp(-(length(kRlh)*length(totalRlh)+kMie*length(totalMie)));vec3 color=iSun*(pRlh*kRlh*totalRlh+pMie*kMie*totalMie);return vec4(color,opacity);}void main() {vec3 scale_camera_pos=-u_globe_position*EARTH_RADIUS/u_globe_radius;vec4 color=atmosphere(normalize(view_direction),scale_camera_pos,u_sun_pos,22.0,EARTH_RADIUS,ATMOS_RADIUS,vec3(5.5e-6,13.0e-6,22.4e-6),21e-6,8e3,1.2e3,0.758 +);color.rgb=1.0-exp(-1.0*color.rgb);color=pow(color,vec4(1.0/2.2));fragColor=vec4(color.rgb,1.0-color.a)*u_atmosphere_blend;}`,"in vec2 a_pos;uniform mat4 u_inv_proj_matrix;out vec3 view_direction;void main() {view_direction=(u_inv_proj_matrix*vec4(a_pos,0.0,1.0)).xyz;gl_Position=vec4(a_pos,0.0,1.0);}"),sky:Zn("uniform vec4 u_sky_color;uniform vec4 u_horizon_color;uniform vec2 u_horizon;uniform vec2 u_horizon_normal;uniform float u_sky_horizon_blend;uniform float u_sky_blend;void main() {float x=gl_FragCoord.x;float y=gl_FragCoord.y;float blend=(y-u_horizon.y)*u_horizon_normal.y+(x-u_horizon.x)*u_horizon_normal.x;if (blend > 0.0) {if (blend < u_sky_horizon_blend) {fragColor=mix(u_sky_color,u_horizon_color,pow(1.0-blend/u_sky_horizon_blend,2.0));} else {fragColor=u_sky_color;}}fragColor=mix(fragColor,vec4(vec3(0.0),0.0),u_sky_blend);}","in vec2 a_pos;void main() {gl_Position=vec4(a_pos,1.0,1.0);}")};function Zn(z,l){let m=/#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g,T=l.match(/in ([\w]+) ([\w]+)/g),P=z.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g),V=l.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g),W=V?V.concat(P):P,te={};return{fragmentSource:z=z.replace(m,(se,fe,_e,Re,Ie)=>(te[Ie]=!0,fe==="define"?` +#ifndef HAS_UNIFORM_u_${Ie} +in ${_e} ${Re} ${Ie}; +#else +uniform ${_e} ${Re} u_${Ie}; +#endif +`:` +#ifdef HAS_UNIFORM_u_${Ie} + ${_e} ${Re} ${Ie} = u_${Ie}; +#endif +`)),vertexSource:l=l.replace(m,(se,fe,_e,Re,Ie)=>{let je=Re==="float"?"vec2":"vec4",tt=Ie.match(/color/)?"color":je;return te[Ie]?fe==="define"?` +#ifndef HAS_UNIFORM_u_${Ie} +uniform lowp float u_${Ie}_t; +in ${_e} ${je} a_${Ie}; +out ${_e} ${Re} ${Ie}; +#else +uniform ${_e} ${Re} u_${Ie}; +#endif +`:tt==="vec4"?` +#ifndef HAS_UNIFORM_u_${Ie} + ${Ie} = a_${Ie}; +#else + ${_e} ${Re} ${Ie} = u_${Ie}; +#endif +`:` +#ifndef HAS_UNIFORM_u_${Ie} + ${Ie} = unpack_mix_${tt}(a_${Ie}, u_${Ie}_t); +#else + ${_e} ${Re} ${Ie} = u_${Ie}; +#endif +`:fe==="define"?` +#ifndef HAS_UNIFORM_u_${Ie} +uniform lowp float u_${Ie}_t; +in ${_e} ${je} a_${Ie}; +#else +uniform ${_e} ${Re} u_${Ie}; +#endif +`:tt==="vec4"?` +#ifndef HAS_UNIFORM_u_${Ie} + ${_e} ${Re} ${Ie} = a_${Ie}; +#else + ${_e} ${Re} ${Ie} = u_${Ie}; +#endif +`:` +#ifndef HAS_UNIFORM_u_${Ie} + ${_e} ${Re} ${Ie} = unpack_mix_${tt}(a_${Ie}, u_${Ie}_t); +#else + ${_e} ${Re} ${Ie} = u_${Ie}; +#endif +`}),staticAttributes:T,staticUniforms:W}}class Hs{constructor(l,m,T){this.vertexBuffer=l,this.indexBuffer=m,this.segments=T}destroy(){this.vertexBuffer.destroy(),this.indexBuffer.destroy(),this.segments.destroy(),this.vertexBuffer=null,this.indexBuffer=null,this.segments=null}}var ec=o.aV([{name:"a_pos",type:"Int16",components:2}]);let sl="#define PROJECTION_MERCATOR",Ol="mercator";class vl{constructor(){this._cachedMesh=null}get name(){return"mercator"}get useSubdivision(){return!1}get shaderVariantName(){return Ol}get shaderDefine(){return sl}get shaderPreludeCode(){return _a.projectionMercator}get vertexShaderPreludeCode(){return _a.projectionMercator.vertexSource}get subdivisionGranularity(){return o.aW.noSubdivision}get useGlobeControls(){return!1}get transitionState(){return 0}get latitudeErrorCorrectionRadians(){return 0}destroy(){}updateGPUdependent(l){}getMeshFromTileID(l,m,T,P,V){if(this._cachedMesh)return this._cachedMesh;let W=new o.aX;W.emplaceBack(0,0),W.emplaceBack(o.a5,0),W.emplaceBack(0,o.a5),W.emplaceBack(o.a5,o.a5);let te=l.createVertexBuffer(W,ec.members),se=o.aY.simpleSegment(0,0,4,2),fe=new o.aZ;fe.emplaceBack(1,0,2),fe.emplaceBack(1,2,3);let _e=l.createIndexBuffer(fe);return this._cachedMesh=new Hs(te,_e,se),this._cachedMesh}recalculate(){}hasTransition(){return!1}setErrorQueryLatitudeDegrees(l){}}class Pu{constructor(l=0,m=0,T=0,P=0){if(isNaN(l)||l<0||isNaN(m)||m<0||isNaN(T)||T<0||isNaN(P)||P<0)throw new Error("Invalid value for edge-insets, top, bottom, left and right must all be numbers");this.top=l,this.bottom=m,this.left=T,this.right=P}interpolate(l,m,T){return m.top!=null&&l.top!=null&&(this.top=o.G.number(l.top,m.top,T)),m.bottom!=null&&l.bottom!=null&&(this.bottom=o.G.number(l.bottom,m.bottom,T)),m.left!=null&&l.left!=null&&(this.left=o.G.number(l.left,m.left,T)),m.right!=null&&l.right!=null&&(this.right=o.G.number(l.right,m.right,T)),this}getCenter(l,m){let T=o.ao((this.left+l-this.right)/2,0,l),P=o.ao((this.top+m-this.bottom)/2,0,m);return new o.P(T,P)}equals(l){return this.top===l.top&&this.bottom===l.bottom&&this.left===l.left&&this.right===l.right}clone(){return new Pu(this.top,this.bottom,this.left,this.right)}toJSON(){return{top:this.top,bottom:this.bottom,left:this.left,right:this.right}}}function tu(z,l){if(!z.renderWorldCopies||z.lngRange)return;let m=l.lng-z.center.lng;l.lng+=m>180?-360:m<-180?360:0}function La(z){return Math.max(0,Math.floor(z))}class xl{constructor(l,m){var T;this.applyConstrain=(P,V)=>this._constrainOverride!==null?this._constrainOverride(P,V):this._callbacks.defaultConstrain(P,V),this._callbacks=l,this._tileSize=512,this._renderWorldCopies=m?.renderWorldCopies===void 0||!!m?.renderWorldCopies,this._minZoom=m?.minZoom||0,this._maxZoom=m?.maxZoom||22,this._minPitch=m?.minPitch==null?0:m?.minPitch,this._maxPitch=m?.maxPitch==null?60:m?.maxPitch,this._constrainOverride=(T=m?.constrainOverride)!==null&&T!==void 0?T:null,this.setMaxBounds(),this._width=0,this._height=0,this._center=new o.V(0,0),this._elevation=0,this._zoom=0,this._tileZoom=La(this._zoom),this._scale=o.ar(this._zoom),this._bearingInRadians=0,this._fovInRadians=.6435011087932844,this._pitchInRadians=0,this._rollInRadians=0,this._unmodified=!0,this._edgeInsets=new Pu,this._minElevationForCurrentTile=0,this._autoCalculateNearFarZ=!0}apply(l,m,T){this._constrainOverride=l.constrainOverride,this._latRange=l.latRange,this._lngRange=l.lngRange,this._width=l.width,this._height=l.height,this._center=l.center,this._elevation=l.elevation,this._minElevationForCurrentTile=l.minElevationForCurrentTile,this._zoom=l.zoom,this._tileZoom=La(this._zoom),this._scale=o.ar(this._zoom),this._bearingInRadians=l.bearingInRadians,this._fovInRadians=l.fovInRadians,this._pitchInRadians=l.pitchInRadians,this._rollInRadians=l.rollInRadians,this._unmodified=l.unmodified,this._edgeInsets=new Pu(l.padding.top,l.padding.bottom,l.padding.left,l.padding.right),this._minZoom=l.minZoom,this._maxZoom=l.maxZoom,this._minPitch=l.minPitch,this._maxPitch=l.maxPitch,this._renderWorldCopies=l.renderWorldCopies,this._cameraToCenterDistance=l.cameraToCenterDistance,this._nearZ=l.nearZ,this._farZ=l.farZ,this._autoCalculateNearFarZ=!T&&l.autoCalculateNearFarZ,m&&this.constrainInternal(),this._calcMatrices()}get pixelsToClipSpaceMatrix(){return this._pixelsToClipSpaceMatrix}get clipSpaceToPixelsMatrix(){return this._clipSpaceToPixelsMatrix}get minElevationForCurrentTile(){return this._minElevationForCurrentTile}setMinElevationForCurrentTile(l){this._minElevationForCurrentTile=l}get tileSize(){return this._tileSize}get tileZoom(){return this._tileZoom}get scale(){return this._scale}get width(){return this._width}get height(){return this._height}get bearingInRadians(){return this._bearingInRadians}get lngRange(){return this._lngRange}get latRange(){return this._latRange}get pixelsToGLUnits(){return this._pixelsToGLUnits}get minZoom(){return this._minZoom}setMinZoom(l){this._minZoom!==l&&(this._minZoom=l,this.setZoom(this.applyConstrain(this._center,this.zoom).zoom))}get maxZoom(){return this._maxZoom}setMaxZoom(l){this._maxZoom!==l&&(this._maxZoom=l,this.setZoom(this.applyConstrain(this._center,this.zoom).zoom))}get minPitch(){return this._minPitch}setMinPitch(l){this._minPitch!==l&&(this._minPitch=l,this.setPitch(Math.max(this.pitch,l)))}get maxPitch(){return this._maxPitch}setMaxPitch(l){this._maxPitch!==l&&(this._maxPitch=l,this.setPitch(Math.min(this.pitch,l)))}get renderWorldCopies(){return this._renderWorldCopies}setRenderWorldCopies(l){l===void 0?l=!0:l===null&&(l=!1),this._renderWorldCopies=l}get constrainOverride(){return this._constrainOverride}setConstrainOverride(l){l===void 0&&(l=null),this._constrainOverride!==l&&(this._constrainOverride=l,this.constrainInternal(),this._calcMatrices())}get worldSize(){return this._tileSize*this._scale}get centerOffset(){return this.centerPoint._sub(this.size._div(2))}get size(){return new o.P(this._width,this._height)}get bearing(){return this._bearingInRadians/Math.PI*180}setBearing(l){let m=o.W(l,-180,180)*Math.PI/180;var T,P,V,W,te,se,fe,_e,Re;this._bearingInRadians!==m&&(this._unmodified=!1,this._bearingInRadians=m,this._calcMatrices(),this._rotationMatrix=c(),T=this._rotationMatrix,V=-this._bearingInRadians,W=(P=this._rotationMatrix)[0],te=P[1],se=P[2],fe=P[3],_e=Math.sin(V),Re=Math.cos(V),T[0]=W*Re+se*_e,T[1]=te*Re+fe*_e,T[2]=W*-_e+se*Re,T[3]=te*-_e+fe*Re)}get rotationMatrix(){return this._rotationMatrix}get pitchInRadians(){return this._pitchInRadians}get pitch(){return this._pitchInRadians/Math.PI*180}setPitch(l){let m=o.ao(l,this.minPitch,this.maxPitch)/180*Math.PI;this._pitchInRadians!==m&&(this._unmodified=!1,this._pitchInRadians=m,this._calcMatrices())}get rollInRadians(){return this._rollInRadians}get roll(){return this._rollInRadians/Math.PI*180}setRoll(l){let m=l/180*Math.PI;this._rollInRadians!==m&&(this._unmodified=!1,this._rollInRadians=m,this._calcMatrices())}get fovInRadians(){return this._fovInRadians}get fov(){return o.a_(this._fovInRadians)}setFov(l){l=o.ao(l,.1,150),this.fov!==l&&(this._unmodified=!1,this._fovInRadians=o.aq(l),this._calcMatrices())}get zoom(){return this._zoom}setZoom(l){let m=this.applyConstrain(this._center,l).zoom;this._zoom!==m&&(this._unmodified=!1,this._zoom=m,this._tileZoom=Math.max(0,Math.floor(m)),this._scale=o.ar(m),this.constrainInternal(),this._calcMatrices())}get center(){return this._center}setCenter(l){l.lat===this._center.lat&&l.lng===this._center.lng||(this._unmodified=!1,this._center=l,this.constrainInternal(),this._calcMatrices())}get elevation(){return this._elevation}setElevation(l){l!==this._elevation&&(this._elevation=l,this.constrainInternal(),this._calcMatrices())}get padding(){return this._edgeInsets.toJSON()}setPadding(l){this._edgeInsets.equals(l)||(this._unmodified=!1,this._edgeInsets.interpolate(this._edgeInsets,l,1),this._calcMatrices())}get centerPoint(){return this._edgeInsets.getCenter(this._width,this._height)}get pixelsPerMeter(){return this._pixelPerMeter}get unmodified(){return this._unmodified}get cameraToCenterDistance(){return this._cameraToCenterDistance}get nearZ(){return this._nearZ}get farZ(){return this._farZ}get autoCalculateNearFarZ(){return this._autoCalculateNearFarZ}overrideNearFarZ(l,m){this._autoCalculateNearFarZ=!1,this._nearZ=l,this._farZ=m,this._calcMatrices()}clearNearFarZOverride(){this._autoCalculateNearFarZ=!0,this._calcMatrices()}isPaddingEqual(l){return this._edgeInsets.equals(l)}interpolatePadding(l,m,T){this._unmodified=!1,this._edgeInsets.interpolate(l,m,T),this.constrainInternal(),this._calcMatrices()}resize(l,m,T=!0){this._width=l,this._height=m,T&&this.constrainInternal(),this._calcMatrices()}getMaxBounds(){return this._latRange&&this._latRange.length===2&&this._lngRange&&this._lngRange.length===2?new Go([this._lngRange[0],this._latRange[0]],[this._lngRange[1],this._latRange[1]]):null}setMaxBounds(l){l?(this._lngRange=[l.getWest(),l.getEast()],this._latRange=[l.getSouth(),l.getNorth()],this.constrainInternal()):(this._lngRange=null,this._latRange=[-o.ap,o.ap])}getCameraQueryGeometry(l,m){if(m.length===1)return[m[0],l];{let{minX:T,minY:P,maxX:V,maxY:W}=o.ac.fromPoints(m).extend(l);return[new o.P(T,P),new o.P(V,P),new o.P(V,W),new o.P(T,W),new o.P(T,P)]}}constrainInternal(){if(!this.center||!this._width||!this._height||this._constraining)return;this._constraining=!0;let l=this._unmodified,{center:m,zoom:T}=this.applyConstrain(this.center,this.zoom);this.setCenter(m),this.setZoom(T),this._unmodified=l,this._constraining=!1}_calcMatrices(){if(this._width&&this._height){this._pixelsToGLUnits=[2/this._width,-2/this._height];let l=o.as(new Float64Array(16));o.Q(l,l,[this._width/2,-this._height/2,1]),o.O(l,l,[1,-1,0]),this._clipSpaceToPixelsMatrix=l,l=o.as(new Float64Array(16)),o.Q(l,l,[1,-1,1]),o.O(l,l,[-1,-1,0]),o.Q(l,l,[2/this._width,2/this._height,1]),this._pixelsToClipSpaceMatrix=l,this._cameraToCenterDistance=.5/Math.tan(this.fovInRadians/2)*this._height}this._callbacks.calcMatrices()}calculateCenterFromCameraLngLatAlt(l,m,T,P){let V=T!==void 0?T:this.bearing,W=P=P!==void 0?P:this.pitch,{distanceToCenter:te,clampedElevation:se}=this._distanceToCenterFromAltElevationPitch(m,this.elevation,W),{x:fe,y:_e}=Us(W,V),Re=o.ab.fromLngLat(l,m),Ie,je,tt=o.a$(1,Re.y),yt=0;do{if(yt+=1,yt>10)break;je=te/tt,Ie=new o.ab(Re.x+fe*je,Re.y+_e*je),tt=1/Ie.meterInMercatorCoordinateUnits()}while(Math.abs(te-je*tt)>1e-12);return{center:Ie.toLngLat(),elevation:se,zoom:o.au(this.height/2/Math.tan(this.fovInRadians/2)/je/this.tileSize)}}recalculateZoomAndCenter(l){if(this.elevation-l==0)return;let m=1/this.worldSize,T=o.at(1,this.center.lat)*this.worldSize,P=o.ab.fromLngLat(this.center,this.elevation),V=P.x/m,W=P.y/m,te=P.z/m,se=this.pitch,fe=this.bearing,{x:_e,y:Re,z:Ie}=Us(se,fe),je=this.cameraToCenterDistance,tt=V+je*-_e,yt=W+je*-Re,At=te+je*Ie,{distanceToCenter:vt,clampedElevation:Lt}=this._distanceToCenterFromAltElevationPitch(At/T,l,se),Kt=vt*T,Mt=new o.ab((tt+_e*Kt)*m,(yt+Re*Kt)*m,0).toLngLat(),Xt=o.at(1,Mt.lat),Rt=o.au(this.height/2/Math.tan(this.fovInRadians/2)/vt/Xt/this.tileSize);this._elevation=Lt,this._center=Mt,this.setZoom(Rt)}_distanceToCenterFromAltElevationPitch(l,m,T){let P=-Math.cos(o.aq(T)),V=l-m,W,te=m;return P*V>=0||Math.abs(P)<.1?(W=1e4,te=l+W*P):W=-V/P,{distanceToCenter:W,clampedElevation:te}}getCameraPoint(){let l=Math.tan(this.pitchInRadians)*(this.cameraToCenterDistance||1);return this.centerPoint.add(new o.P(l*Math.sin(this.rollInRadians),l*Math.cos(this.rollInRadians)))}getCameraAltitude(){return Math.cos(this.pitchInRadians)*this._cameraToCenterDistance/this._pixelPerMeter+this.elevation}getCameraLngLat(){let l=o.at(1,this.center.lat)*this.worldSize;return wr(this.center,this.elevation,this.pitch,this.bearing,this.cameraToCenterDistance/l).toLngLat()}getMercatorTileCoordinates(l){if(!l)return[0,0,1,1];let m=l.canonical.z>=0?1<this.max[0]||l.aabb.min[1]>this.max[1]||l.aabb.min[2]>this.max[2]||l.aabb.max[0]0?(m+=l[P]*this.min[P],T+=l[P]*this.max[P]):(T+=l[P]*this.min[P],m+=l[P]*this.max[P]);return m>=0?2:T<0?0:1}}class Wh{distanceToTile2d(l,m,T,P){let V=P.distanceX([l,m]),W=P.distanceY([l,m]);return Math.hypot(V,W)}getWrap(l,m,T){return T}getTileBoundingVolume(l,m,T,P){var V,W;let te=0,se=0;if(P?.terrain){let _e=new o.a2(l.z,m,l.z,l.x,l.y),Re=P.terrain.getMinMaxElevation(_e);te=(V=Re.minElevation)!==null&&V!==void 0?V:Math.min(0,T),se=(W=Re.maxElevation)!==null&&W!==void 0?W:Math.max(0,T)}let fe=1<P}allowWorldCopies(){return!0}prepareNextFrame(){}}class Ec{constructor(l,m,T){this.points=l,this.planes=m,this.aabb=T}static fromInvProjectionMatrix(l,m=1,T=0,P,V){let W=V?[[6,5,4],[0,1,2],[0,3,7],[2,1,5],[3,2,6],[0,4,5]]:[[0,1,2],[6,5,4],[0,3,7],[2,1,5],[3,2,6],[0,4,5]],te=Math.pow(2,T),se=[[-1,1,-1,1],[1,1,-1,1],[1,-1,-1,1],[-1,-1,-1,1],[-1,1,1,1],[1,1,1,1],[1,-1,1,1],[-1,-1,1,1]].map(Ie=>function(je,tt,yt,At){let vt=o.aI([],je,tt),Lt=1/vt[3]/yt*At;return o.b7(vt,vt,[Lt,Lt,1/vt[3],Lt])}(Ie,l,m,te));P&&function(Ie,je,tt,yt){let At=yt?4:0,vt=yt?0:4,Lt=0,Kt=[],Mt=[];for(let zt=0;zt<4;zt++){let jt=o.b3([],Ie[zt+vt],Ie[zt+At]),br=o.b8(jt);o.b0(jt,jt,1/br),Kt.push(br),Mt.push(jt)}for(let zt=0;zt<4;zt++){let jt=o.b9(Ie[zt+At],Mt[zt],tt);Lt=jt!==null&&jt>=0?Math.max(Lt,jt):Math.max(Lt,Kt[zt])}let Xt=function(zt,jt){let br=o.b3([],zt[jt[0]],zt[jt[1]]),Cr=o.b3([],zt[jt[2]],zt[jt[1]]),Pr=[0,0,0,0];return o.b4(Pr,o.b5([],br,Cr)),Pr[3]=-o.b6(Pr,zt[jt[0]]),Pr}(Ie,je),Rt=function(zt,jt){let br=o.ba(zt),Cr=o.bb([],zt,1/br),Pr=o.b3([],jt,o.b0([],Cr,o.b6(jt,Cr))),Ir=o.ba(Pr);if(Ir>0){let Ai=Math.sqrt(1-Cr[3]*Cr[3]),_i=o.b0([],Cr,-Cr[3]),hi=o.b1([],_i,o.b0([],Pr,Ai/Ir));return o.bc(jt,hi)}return null}(tt,Xt);if(Rt!==null){let zt=Rt/o.b6(Mt[0],Xt);Lt=Math.min(Lt,zt)}for(let zt=0;zt<4;zt++){let jt=Math.min(Lt,Kt[zt]);Ie[zt+vt]=[Ie[zt+At][0]+Mt[zt][0]*jt,Ie[zt+At][1]+Mt[zt][1]*jt,Ie[zt+At][2]+Mt[zt][2]*jt,1]}}(se,W[0],P,V);let fe=W.map(Ie=>{let je=o.b3([],se[Ie[0]],se[Ie[1]]),tt=o.b3([],se[Ie[2]],se[Ie[1]]),yt=o.b4([],o.b5([],je,tt)),At=-o.b6(yt,se[Ie[1]]);return yt.concat(At)}),_e=[Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY],Re=[Number.NEGATIVE_INFINITY,Number.NEGATIVE_INFINITY,Number.NEGATIVE_INFINITY];for(let Ie of se)for(let je=0;je<3;je++)_e[je]=Math.min(_e[je],Ie[je]),Re[je]=Math.max(Re[je],Ie[je]);return new Ec(se,fe,new Ws(_e,Re))}}class Hf{get pixelsToClipSpaceMatrix(){return this._helper.pixelsToClipSpaceMatrix}get clipSpaceToPixelsMatrix(){return this._helper.clipSpaceToPixelsMatrix}get pixelsToGLUnits(){return this._helper.pixelsToGLUnits}get centerOffset(){return this._helper.centerOffset}get size(){return this._helper.size}get rotationMatrix(){return this._helper.rotationMatrix}get centerPoint(){return this._helper.centerPoint}get pixelsPerMeter(){return this._helper.pixelsPerMeter}setMinZoom(l){this._helper.setMinZoom(l)}setMaxZoom(l){this._helper.setMaxZoom(l)}setMinPitch(l){this._helper.setMinPitch(l)}setMaxPitch(l){this._helper.setMaxPitch(l)}setRenderWorldCopies(l){this._helper.setRenderWorldCopies(l)}setBearing(l){this._helper.setBearing(l)}setPitch(l){this._helper.setPitch(l)}setRoll(l){this._helper.setRoll(l)}setFov(l){this._helper.setFov(l)}setZoom(l){this._helper.setZoom(l)}setCenter(l){this._helper.setCenter(l)}setElevation(l){this._helper.setElevation(l)}setMinElevationForCurrentTile(l){this._helper.setMinElevationForCurrentTile(l)}setPadding(l){this._helper.setPadding(l)}interpolatePadding(l,m,T){return this._helper.interpolatePadding(l,m,T)}isPaddingEqual(l){return this._helper.isPaddingEqual(l)}resize(l,m,T=!0){this._helper.resize(l,m,T)}getMaxBounds(){return this._helper.getMaxBounds()}setMaxBounds(l){this._helper.setMaxBounds(l)}setConstrainOverride(l){this._helper.setConstrainOverride(l)}overrideNearFarZ(l,m){this._helper.overrideNearFarZ(l,m)}clearNearFarZOverride(){this._helper.clearNearFarZOverride()}getCameraQueryGeometry(l){return this._helper.getCameraQueryGeometry(this.getCameraPoint(),l)}get tileSize(){return this._helper.tileSize}get tileZoom(){return this._helper.tileZoom}get scale(){return this._helper.scale}get worldSize(){return this._helper.worldSize}get width(){return this._helper.width}get height(){return this._helper.height}get lngRange(){return this._helper.lngRange}get latRange(){return this._helper.latRange}get minZoom(){return this._helper.minZoom}get maxZoom(){return this._helper.maxZoom}get zoom(){return this._helper.zoom}get center(){return this._helper.center}get minPitch(){return this._helper.minPitch}get maxPitch(){return this._helper.maxPitch}get pitch(){return this._helper.pitch}get pitchInRadians(){return this._helper.pitchInRadians}get roll(){return this._helper.roll}get rollInRadians(){return this._helper.rollInRadians}get bearing(){return this._helper.bearing}get bearingInRadians(){return this._helper.bearingInRadians}get fov(){return this._helper.fov}get fovInRadians(){return this._helper.fovInRadians}get elevation(){return this._helper.elevation}get minElevationForCurrentTile(){return this._helper.minElevationForCurrentTile}get padding(){return this._helper.padding}get unmodified(){return this._helper.unmodified}get renderWorldCopies(){return this._helper.renderWorldCopies}get cameraToCenterDistance(){return this._helper.cameraToCenterDistance}get constrainOverride(){return this._helper.constrainOverride}get nearZ(){return this._helper.nearZ}get farZ(){return this._helper.farZ}get autoCalculateNearFarZ(){return this._helper.autoCalculateNearFarZ}setTransitionState(l,m){}constructor(l){this._posMatrixCache=new Map,this._alignedPosMatrixCache=new Map,this._fogMatrixCacheF32=new Map,this.defaultConstrain=(m,T)=>{T=o.ao(+T,this.minZoom,this.maxZoom);let P={center:new o.V(m.lng,m.lat),zoom:T},V=this._helper._lngRange;if(!this._helper._renderWorldCopies&&V===null){let Mt=179.9999999999;V=[-Mt,Mt]}let W=this.tileSize*o.ar(P.zoom),te=0,se=W,fe=0,_e=W,Re=0,Ie=0,{x:je,y:tt}=this.size;if(this._helper._latRange){let Mt=this._helper._latRange;te=o.X(Mt[1])*W,se=o.X(Mt[0])*W,se-tese&&(Lt=se-Mt)}if(V){let Mt=(fe+_e)/2,Xt=yt;this._helper._renderWorldCopies&&(Xt=o.W(yt,Mt-W/2,Mt+W/2));let Rt=je/2;Xt-Rt_e&&(vt=_e-Rt)}if(vt!==void 0||Lt!==void 0){let Mt=new o.P(vt??yt,Lt??At);P.center=Wn(W,Mt).wrap()}return P},this.applyConstrain=(m,T)=>this._helper.applyConstrain(m,T),this._helper=new xl({calcMatrices:()=>{this._calcMatrices()},defaultConstrain:(m,T)=>this.defaultConstrain(m,T)},l),this._coveringTilesDetailsProvider=new Wh}clone(){let l=new Hf;return l.apply(this),l}apply(l,m,T){this._helper.apply(l,m,T)}get cameraPosition(){return this._cameraPosition}get projectionMatrix(){return this._projectionMatrix}get modelViewProjectionMatrix(){return this._viewProjMatrix}get inverseProjectionMatrix(){return this._invProjMatrix}get mercatorMatrix(){return this._mercatorMatrix}getVisibleUnwrappedCoordinates(l){let m=[new o.bd(0,l)];if(this._helper._renderWorldCopies){let T=this.screenPointToMercatorCoordinate(new o.P(0,0)),P=this.screenPointToMercatorCoordinate(new o.P(this._helper._width,0)),V=this.screenPointToMercatorCoordinate(new o.P(this._helper._width,this._helper._height)),W=this.screenPointToMercatorCoordinate(new o.P(0,this._helper._height)),te=Math.floor(Math.min(T.x,P.x,V.x,W.x)),se=Math.floor(Math.max(T.x,P.x,V.x,W.x)),fe=1;for(let _e=te-fe;_e<=se+fe;_e++)_e!==0&&m.push(new o.bd(_e,l))}return m}getCameraFrustum(){return Ec.fromInvProjectionMatrix(this._invViewProjMatrix,this.worldSize)}getClippingPlane(){return null}getCoveringTilesDetailsProvider(){return this._coveringTilesDetailsProvider}recalculateZoomAndCenter(l){let m=this.screenPointToLocation(this.centerPoint,l),T=l?l.getElevationForLngLatZoom(m,this._helper._tileZoom):0;this._helper.recalculateZoomAndCenter(T)}setLocationAtPoint(l,m){let T=o.at(this.elevation,this.center.lat),P=this.screenPointToMercatorCoordinateAtZ(m,T),V=this.screenPointToMercatorCoordinateAtZ(this.centerPoint,T),W=o.ab.fromLngLat(l),te=new o.ab(W.x-(P.x-V.x),W.y-(P.y-V.y));this.setCenter(te?.toLngLat()),this._helper._renderWorldCopies&&this.setCenter(this.center.wrap())}locationToScreenPoint(l,m){return m?this.coordinatePoint(o.ab.fromLngLat(l),m.getElevationForLngLat(l),this._pixelMatrix3D):this.coordinatePoint(o.ab.fromLngLat(l))}screenPointToLocation(l,m){var T;return(T=this.screenPointToMercatorCoordinate(l,m))===null||T===void 0?void 0:T.toLngLat()}screenPointToMercatorCoordinate(l,m){if(m){let T=m.pointCoordinate(l);if(T!=null)return T}return this.screenPointToMercatorCoordinateAtZ(l)}screenPointToMercatorCoordinateAtZ(l,m){let T=m||0,P=[l.x,l.y,0,1],V=[l.x,l.y,1,1];o.aI(P,P,this._pixelMatrixInverse),o.aI(V,V,this._pixelMatrixInverse);let W=P[3],te=V[3],se=P[1]/W,fe=V[1]/te,_e=P[2]/W,Re=V[2]/te,Ie=_e===Re?0:(T-_e)/(Re-_e);return new o.ab(o.G.number(P[0]/W,V[0]/te,Ie)/this.worldSize,o.G.number(se,fe,Ie)/this.worldSize,T)}coordinatePoint(l,m=0,T=this._pixelMatrix){let P=[l.x*this.worldSize,l.y*this.worldSize,m,1];return o.aI(P,P,T),new o.P(P[0]/P[3],P[1]/P[3])}getBounds(){let l=Math.max(0,this._helper._height/2-bs(this));return new Go().extend(this.screenPointToLocation(new o.P(0,l))).extend(this.screenPointToLocation(new o.P(this._helper._width,l))).extend(this.screenPointToLocation(new o.P(this._helper._width,this._helper._height))).extend(this.screenPointToLocation(new o.P(0,this._helper._height)))}isPointOnMapSurface(l,m){return m?m.pointCoordinate(l)!=null:l.y>this.height/2-bs(this)}calculatePosMatrix(l,m=!1,T){var P;let V=(P=l.key)!==null&&P!==void 0?P:o.be(l.wrap,l.canonical.z,l.canonical.z,l.canonical.x,l.canonical.y),W=m?this._alignedPosMatrixCache:this._posMatrixCache;if(W.has(V)){let fe=W.get(V);return T?fe.f32:fe.f64}let te=go(l,this.worldSize);o.S(te,m?this._alignedProjMatrix:this._viewProjMatrix,te);let se={f64:te,f32:new Float32Array(te)};return W.set(V,se),T?se.f32:se.f64}calculateFogMatrix(l){let m=l.key,T=this._fogMatrixCacheF32;if(T.has(m))return T.get(m);let P=go(l,this.worldSize);return o.S(P,this._fogMatrix,P),T.set(m,new Float32Array(P)),T.get(m)}calculateCenterFromCameraLngLatAlt(l,m,T,P){return this._helper.calculateCenterFromCameraLngLatAlt(l,m,T,P)}_calculateNearFarZIfNeeded(l,m,T){if(!this._helper.autoCalculateNearFarZ)return;let P=Math.min(this.elevation,this.minElevationForCurrentTile,this.getCameraAltitude()-100),V=l-P*this._helper._pixelPerMeter/Math.cos(m),W=P<0?V:l,te=Math.PI/2+this.pitchInRadians,se=o.aq(this.fov)*(Math.abs(Math.cos(o.aq(this.roll)))*this.height+Math.abs(Math.sin(o.aq(this.roll)))*this.width)/this.height*(.5+T.y/this.height),fe=Math.sin(se)*W/Math.sin(o.ao(Math.PI-te-se,.01,Math.PI-.01)),_e=bs(this),Re=Math.atan(_e/this._helper.cameraToCenterDistance),Ie=o.aq(.75),je=Re>Ie?2*Re*(.5+T.y/(2*_e)):Ie,tt=Math.sin(je)*W/Math.sin(o.ao(Math.PI-te-je,.01,Math.PI-.01)),yt=Math.min(fe,tt);this._helper._farZ=1.01*(Math.cos(Math.PI/2-m)*yt+W),this._helper._nearZ=this._helper._height/50}_calcMatrices(){if(!this._helper._height)return;let l=this.centerOffset,m=fn(this.worldSize,this.center),T=m.x,P=m.y;this._helper._pixelPerMeter=o.at(1,this.center.lat)*this.worldSize;let V=o.aq(Math.min(this.pitch,Wi)),W=Math.max(this._helper.cameraToCenterDistance/2,this._helper.cameraToCenterDistance+this._helper._elevation*this._helper._pixelPerMeter/Math.cos(V)),te;this._calculateNearFarZIfNeeded(W,V,l),te=new Float64Array(16),o.bf(te,this.fovInRadians,this._helper._width/this._helper._height,this._helper._nearZ,this._helper._farZ),this._invProjMatrix=new Float64Array(16),o.aC(this._invProjMatrix,te),te[8]=2*-l.x/this._helper._width,te[9]=2*l.y/this._helper._height,this._projectionMatrix=o.bg(te),o.Q(te,te,[1,-1,1]),o.O(te,te,[0,0,-this._helper.cameraToCenterDistance]),o.bh(te,te,-this.rollInRadians),o.bi(te,te,this.pitchInRadians),o.bh(te,te,-this.bearingInRadians),o.O(te,te,[-T,-P,0]),this._mercatorMatrix=o.Q([],te,[this.worldSize,this.worldSize,this.worldSize]),o.Q(te,te,[1,1,this._helper._pixelPerMeter]),this._pixelMatrix=o.S(new Float64Array(16),this.clipSpaceToPixelsMatrix,te),o.O(te,te,[0,0,-this.elevation]),this._viewProjMatrix=te,this._invViewProjMatrix=o.aC([],te);let se=[0,0,-1,1];o.aI(se,se,this._invViewProjMatrix),this._cameraPosition=[se[0]/se[3],se[1]/se[3],se[2]/se[3]],this._fogMatrix=new Float64Array(16),o.bf(this._fogMatrix,this.fovInRadians,this.width/this.height,W,this._helper._farZ),this._fogMatrix[8]=2*-l.x/this.width,this._fogMatrix[9]=2*l.y/this.height,o.Q(this._fogMatrix,this._fogMatrix,[1,-1,1]),o.O(this._fogMatrix,this._fogMatrix,[0,0,-this.cameraToCenterDistance]),o.bh(this._fogMatrix,this._fogMatrix,-this.rollInRadians),o.bi(this._fogMatrix,this._fogMatrix,this.pitchInRadians),o.bh(this._fogMatrix,this._fogMatrix,-this.bearingInRadians),o.O(this._fogMatrix,this._fogMatrix,[-T,-P,0]),o.Q(this._fogMatrix,this._fogMatrix,[1,1,this._helper._pixelPerMeter]),o.O(this._fogMatrix,this._fogMatrix,[0,0,-this.elevation]),this._pixelMatrix3D=o.S(new Float64Array(16),this.clipSpaceToPixelsMatrix,te);let fe=this._helper._width%2/2,_e=this._helper._height%2/2,Re=Math.cos(this.bearingInRadians),Ie=Math.sin(-this.bearingInRadians),je=T-Math.round(T)+Re*fe+Ie*_e,tt=P-Math.round(P)+Re*_e+Ie*fe,yt=new Float64Array(te);if(o.O(yt,yt,[je>.5?je-1:je,tt>.5?tt-1:tt,0]),this._alignedProjMatrix=yt,te=o.aC(new Float64Array(16),this._pixelMatrix),!te)throw new Error("failed to invert matrix");this._pixelMatrixInverse=te,this._clearMatrixCaches()}_clearMatrixCaches(){this._posMatrixCache.clear(),this._alignedPosMatrixCache.clear(),this._fogMatrixCacheF32.clear()}maxPitchScaleFactor(){if(!this._pixelMatrixInverse)return 1;let l=this.screenPointToMercatorCoordinate(new o.P(0,0)),m=[l.x*this.worldSize,l.y*this.worldSize,0,1];return o.aI(m,m,this._pixelMatrix)[3]/this._helper.cameraToCenterDistance}getCameraPoint(){return this._helper.getCameraPoint()}getCameraAltitude(){return this._helper.getCameraAltitude()}getCameraLngLat(){let l=o.at(1,this.center.lat)*this.worldSize;return wr(this.center,this.elevation,this.pitch,this.bearing,this._helper.cameraToCenterDistance/l).toLngLat()}lngLatToCameraDepth(l,m){let T=o.ab.fromLngLat(l),P=[T.x*this.worldSize,T.y*this.worldSize,m,1];return o.aI(P,P,this._viewProjMatrix),P[2]/P[3]}getProjectionData(l){let{overscaledTileID:m,aligned:T,applyTerrainMatrix:P}=l,V=this._helper.getMercatorTileCoordinates(m),W=m?this.calculatePosMatrix(m,T,!0):null,te;return te=m&&m.terrainRttPosMatrix32f&&P?m.terrainRttPosMatrix32f:W||o.bj(),{mainMatrix:te,tileMercatorCoords:V,clippingPlane:[0,0,0,0],projectionTransition:0,fallbackMatrix:te}}isLocationOccluded(l){return!1}getPixelScale(){return 1}getCircleRadiusCorrection(){return 1}getPitchedTextCorrection(l,m,T){return 1}transformLightDirection(l){return o.b2(l)}getRayDirectionFromPixel(l){throw new Error("Not implemented.")}projectTileCoordinates(l,m,T,P){let V=this.calculatePosMatrix(T),W;P?(W=[l,m,P(l,m),1],o.aI(W,W,V)):(W=[l,m,0,1],md(W,W,V));let te=W[3];return{point:new o.P(W[0]/te,W[1]/te),signedDistanceFromCamera:te,isOccluded:!1}}populateCache(l){for(let m of l)this.calculatePosMatrix(m)}getMatrixForModel(l,m){let T=o.ab.fromLngLat(l,m),P=T.meterInMercatorCoordinateUnits(),V=o.bk();return o.O(V,V,[T.x,T.y,T.z]),o.bh(V,V,Math.PI),o.bi(V,V,Math.PI/2),o.Q(V,V,[-P,P,P]),V}getProjectionDataForCustomLayer(l=!0){let m=new o.a2(0,0,0,0,0),T=this.getProjectionData({overscaledTileID:m,applyGlobeMatrix:l}),P=go(m,this.worldSize);o.S(P,this._viewProjMatrix,P),T.tileMercatorCoords=[0,0,1,1];let V=[o.a5,o.a5,this.worldSize/this._helper.pixelsPerMeter],W=o.bl();return o.Q(W,P,V),T.fallbackMatrix=W,T.mainMatrix=W,T}getFastPathSimpleProjectionMatrix(l){return this.calculatePosMatrix(l)}}function qh(){o.w("Map cannot fit within canvas with the given bounds, padding, and/or offset.")}function Gd(z){if(z.useSlerp)if(z.k<1){let l=o.bm(z.startEulerAngles.roll,z.startEulerAngles.pitch,z.startEulerAngles.bearing),m=o.bm(z.endEulerAngles.roll,z.endEulerAngles.pitch,z.endEulerAngles.bearing),T=new Float64Array(4);o.bn(T,l,m,z.k);let P=o.bo(T);z.tr.setRoll(P.roll),z.tr.setPitch(P.pitch),z.tr.setBearing(P.bearing)}else z.tr.setRoll(z.endEulerAngles.roll),z.tr.setPitch(z.endEulerAngles.pitch),z.tr.setBearing(z.endEulerAngles.bearing);else z.tr.setRoll(o.G.number(z.startEulerAngles.roll,z.endEulerAngles.roll,z.k)),z.tr.setPitch(o.G.number(z.startEulerAngles.pitch,z.endEulerAngles.pitch,z.k)),z.tr.setBearing(o.G.number(z.startEulerAngles.bearing,z.endEulerAngles.bearing,z.k))}function Cp(z,l,m,T,P){let V=P.padding,W=fn(P.worldSize,m.getNorthWest()),te=fn(P.worldSize,m.getNorthEast()),se=fn(P.worldSize,m.getSouthEast()),fe=fn(P.worldSize,m.getSouthWest()),_e=o.aq(-T),Re=W.rotate(_e),Ie=te.rotate(_e),je=se.rotate(_e),tt=fe.rotate(_e),yt=new o.P(Math.max(Re.x,Ie.x,tt.x,je.x),Math.max(Re.y,Ie.y,tt.y,je.y)),At=new o.P(Math.min(Re.x,Ie.x,tt.x,je.x),Math.min(Re.y,Ie.y,tt.y,je.y)),vt=yt.sub(At),Lt=(P.width-(V.left+V.right+l.left+l.right))/vt.x,Kt=(P.height-(V.top+V.bottom+l.top+l.bottom))/vt.y;if(Kt<0||Lt<0)return void qh();let Mt=Math.min(o.au(P.scale*Math.min(Lt,Kt)),z.maxZoom),Xt=o.P.convert(z.offset),Rt=new o.P((l.left-l.right)/2,(l.top-l.bottom)/2).rotate(o.aq(T)),zt=Xt.add(Rt).mult(P.scale/o.ar(Mt));return{center:Wn(P.worldSize,W.add(se).div(2).sub(zt)),zoom:Mt,bearing:T}}class Af{get useGlobeControls(){return!1}handlePanInertia(l,m){let T=l.mag(),P=Math.abs(bs(m));return{easingOffset:l.mult(Math.min(.75*P/T,1)),easingCenter:m.center}}handleMapControlsRollPitchBearingZoom(l,m){l.bearingDelta&&m.setBearing(m.bearing+l.bearingDelta),l.pitchDelta&&m.setPitch(m.pitch+l.pitchDelta),l.rollDelta&&m.setRoll(m.roll+l.rollDelta),l.zoomDelta&&m.setZoom(m.zoom+l.zoomDelta)}handleMapControlsPan(l,m,T){l.around.distSqr(m.centerPoint)<.01||m.setLocationAtPoint(T,l.around)}cameraForBoxAndBearing(l,m,T,P,V){return Cp(l,m,T,P,V)}handleJumpToCenterZoom(l,m){l.zoom!==(m.zoom!==void 0?+m.zoom:l.zoom)&&l.setZoom(+m.zoom),m.center!==void 0&&l.setCenter(o.V.convert(m.center))}handleEaseTo(l,m){let T=l.zoom,P=l.padding,V={roll:l.roll,pitch:l.pitch,bearing:l.bearing},W={roll:m.roll===void 0?l.roll:m.roll,pitch:m.pitch===void 0?l.pitch:m.pitch,bearing:m.bearing===void 0?l.bearing:m.bearing},te=m.zoom!==void 0,se=!l.isPaddingEqual(m.padding),fe=!1,_e=te?+m.zoom:l.zoom,Re=l.centerPoint.add(m.offsetAsPoint),Ie=l.screenPointToLocation(Re),{center:je,zoom:tt}=l.applyConstrain(o.V.convert(m.center||Ie),_e??T);tu(l,je);let yt=fn(l.worldSize,Ie),At=fn(l.worldSize,je).sub(yt),vt=o.ar(tt-T);return fe=tt!==T,{easeFunc:Lt=>{if(fe&&l.setZoom(o.G.number(T,tt,Lt)),o.bp(V,W)||Gd({startEulerAngles:V,endEulerAngles:W,tr:l,k:Lt,useSlerp:V.roll!=W.roll}),se&&(l.interpolatePadding(P,m.padding,Lt),Re=l.centerPoint.add(m.offsetAsPoint)),m.around)l.setLocationAtPoint(m.around,m.aroundPoint);else{let Kt=o.ar(l.zoom-T),Mt=tt>T?Math.min(2,vt):Math.max(.5,vt),Xt=Math.pow(Mt,1-Lt),Rt=Wn(l.worldSize,yt.add(At.mult(Lt*Xt)).mult(Kt));l.setLocationAtPoint(l.renderWorldCopies?Rt.wrap():Rt,Re)}},isZooming:fe,elevationCenter:je}}handleFlyTo(l,m){let T=m.zoom!==void 0,P=l.zoom,V=l.applyConstrain(o.V.convert(m.center||m.locationAtOffset),T?+m.zoom:P),W=V.center,te=V.zoom;tu(l,W);let se=fn(l.worldSize,m.locationAtOffset),fe=fn(l.worldSize,W).sub(se),_e=fe.mag(),Re=o.ar(te-P),Ie;if(m.minZoom!==void 0){let je=Math.min(+m.minZoom,P,te),tt=l.applyConstrain(W,je).zoom;Ie=o.ar(tt-P)}return{easeFunc:(je,tt,yt,At)=>{l.setZoom(je===1?te:P+o.au(tt));let vt=je===1?W:Wn(l.worldSize,se.add(fe.mult(yt)).mult(tt));l.setLocationAtPoint(l.renderWorldCopies?vt.wrap():vt,At)},scaleOfZoom:Re,targetCenter:W,scaleOfMinZoom:Ie,pixelPathLength:_e}}}class ra{constructor(l,m,T){this.blendFunction=l,this.blendColor=m,this.mask=T}}ra.Replace=[1,0],ra.disabled=new ra(ra.Replace,o.bq.transparent,[!1,!1,!1,!1]),ra.unblended=new ra(ra.Replace,o.bq.transparent,[!0,!0,!0,!0]),ra.alphaBlended=new ra([1,771],o.bq.transparent,[!0,!0,!0,!0]);let mh=2305;class ms{constructor(l,m,T){this.enable=l,this.mode=m,this.frontFace=T}}ms.disabled=new ms(!1,1029,mh),ms.backCCW=new ms(!0,1029,mh),ms.frontCCW=new ms(!0,1028,mh);class Ao{constructor(l,m,T){this.func=l,this.mask=m,this.range=T}}Ao.ReadOnly=!1,Ao.ReadWrite=!0,Ao.disabled=new Ao(519,Ao.ReadOnly,[0,1]);let $h=7680;class us{constructor(l,m,T,P,V,W){this.test=l,this.ref=m,this.mask=T,this.fail=P,this.depthFail=V,this.pass=W}}us.disabled=new us({func:519,mask:0},0,0,$h,$h,$h);let Hd=new WeakMap;function q(z){var l;if(Hd.has(z))return Hd.get(z);{let m=(l=z.getParameter(z.VERSION))===null||l===void 0?void 0:l.startsWith("WebGL 2.0");return Hd.set(z,m),m}}class oe{get awaitingQuery(){return!!this._readbackQueue}constructor(l){this._readbackWaitFrames=4,this._measureWaitFrames=6,this._texWidth=1,this._texHeight=1,this._measuredError=0,this._updateCount=0,this._lastReadbackFrame=-1e3,this._readbackQueue=null,this._cachedRenderContext=l;let m=l.context,T=m.gl;this._texFormat=T.RGBA,this._texType=T.UNSIGNED_BYTE;let P=new o.aX;P.emplaceBack(-1,-1),P.emplaceBack(2,-1),P.emplaceBack(-1,2);let V=new o.aZ;V.emplaceBack(0,1,2),this._fullscreenTriangle=new Hs(m.createVertexBuffer(P,ec.members),m.createIndexBuffer(V),o.aY.simpleSegment(0,0,P.length,V.length)),this._resultBuffer=new Uint8Array(4),m.activeTexture.set(T.TEXTURE1);let W=T.createTexture();T.bindTexture(T.TEXTURE_2D,W),T.texParameteri(T.TEXTURE_2D,T.TEXTURE_WRAP_S,T.CLAMP_TO_EDGE),T.texParameteri(T.TEXTURE_2D,T.TEXTURE_WRAP_T,T.CLAMP_TO_EDGE),T.texParameteri(T.TEXTURE_2D,T.TEXTURE_MIN_FILTER,T.NEAREST),T.texParameteri(T.TEXTURE_2D,T.TEXTURE_MAG_FILTER,T.NEAREST),T.texImage2D(T.TEXTURE_2D,0,this._texFormat,this._texWidth,this._texHeight,0,this._texFormat,this._texType,null),this._fbo=m.createFramebuffer(this._texWidth,this._texHeight,!1,!1),this._fbo.colorAttachment.set(W),q(T)&&(this._pbo=T.createBuffer(),T.bindBuffer(T.PIXEL_PACK_BUFFER,this._pbo),T.bufferData(T.PIXEL_PACK_BUFFER,4,T.STREAM_READ),T.bindBuffer(T.PIXEL_PACK_BUFFER,null))}destroy(){let l=this._cachedRenderContext.context.gl;this._fullscreenTriangle.destroy(),this._fbo.destroy(),l.deleteBuffer(this._pbo),this._fullscreenTriangle=null,this._fbo=null,this._pbo=null,this._resultBuffer=null}updateErrorLoop(l,m){let T=this._updateCount;return this._readbackQueue?T>=this._readbackQueue.frameNumberIssued+this._readbackWaitFrames&&this._tryReadback():T>=this._lastReadbackFrame+this._measureWaitFrames&&this._renderErrorTexture(l,m),this._updateCount++,this._measuredError}_bindFramebuffer(){let l=this._cachedRenderContext.context,m=l.gl;l.activeTexture.set(m.TEXTURE1),m.bindTexture(m.TEXTURE_2D,this._fbo.colorAttachment.get()),l.bindFramebuffer.set(this._fbo.framebuffer)}_renderErrorTexture(l,m){let T=this._cachedRenderContext.context,P=T.gl;if(this._bindFramebuffer(),T.viewport.set([0,0,this._texWidth,this._texHeight]),T.clear({color:o.bq.transparent}),this._cachedRenderContext.useProgram("projectionErrorMeasurement").draw(T,P.TRIANGLES,Ao.disabled,us.disabled,ra.unblended,ms.disabled,((V,W)=>({u_input:V,u_output_expected:W}))(l,m),null,null,"$clipping",this._fullscreenTriangle.vertexBuffer,this._fullscreenTriangle.indexBuffer,this._fullscreenTriangle.segments),this._pbo&&q(P)){P.bindBuffer(P.PIXEL_PACK_BUFFER,this._pbo),P.readBuffer(P.COLOR_ATTACHMENT0),P.readPixels(0,0,this._texWidth,this._texHeight,this._texFormat,this._texType,0),P.bindBuffer(P.PIXEL_PACK_BUFFER,null);let V=P.fenceSync(P.SYNC_GPU_COMMANDS_COMPLETE,0);P.flush(),this._readbackQueue={frameNumberIssued:this._updateCount,sync:V}}else this._readbackQueue={frameNumberIssued:this._updateCount,sync:null}}_tryReadback(){let l=this._cachedRenderContext.context.gl;if(this._pbo&&this._readbackQueue&&q(l)){let m=l.clientWaitSync(this._readbackQueue.sync,0,0);if(m===l.WAIT_FAILED)return o.w("WebGL2 clientWaitSync failed."),this._readbackQueue=null,void(this._lastReadbackFrame=this._updateCount);if(m===l.TIMEOUT_EXPIRED)return;l.bindBuffer(l.PIXEL_PACK_BUFFER,this._pbo),l.getBufferSubData(l.PIXEL_PACK_BUFFER,0,this._resultBuffer,0,4),l.bindBuffer(l.PIXEL_PACK_BUFFER,null)}else this._bindFramebuffer(),l.readPixels(0,0,this._texWidth,this._texHeight,this._texFormat,this._texType,this._resultBuffer);this._readbackQueue=null,this._measuredError=oe._parseRGBA8float(this._resultBuffer),this._lastReadbackFrame=this._updateCount}static _parseRGBA8float(l){let m=0;return m+=l[0]/256,m+=l[1]/65536,m+=l[2]/16777216,l[3]<127&&(m=-m),m/128}}let pe=o.a5/128;function be(z,l){let m=z.granularity!==void 0?Math.max(z.granularity,1):1,T=m+(z.generateBorders?2:0),P=m+(z.extendToNorthPole||z.generateBorders?1:0)+(z.extendToSouthPole||z.generateBorders?1:0),V=T+1,W=P+1,te=z.generateBorders?-1:0,se=z.generateBorders||z.extendToNorthPole?-1:0,fe=m+(z.generateBorders?1:0),_e=m+(z.generateBorders||z.extendToSouthPole?1:0),Re=V*W,Ie=T*P*6,je=V*W>65536;if(je&&l==="16bit")throw new Error("Granularity is too large and meshes would not fit inside 16 bit vertex indices.");let tt=je||l==="32bit",yt=new Int16Array(2*Re),At=0;for(let Kt=se;Kt<=_e;Kt++)for(let Mt=te;Mt<=fe;Mt++){let Xt=Mt/m*o.a5;Mt===-1&&(Xt=-pe),Mt===m+1&&(Xt=o.a5+pe);let Rt=Kt/m*o.a5;Kt===-1&&(Rt=z.extendToNorthPole?o.bs:-pe),Kt===m+1&&(Rt=z.extendToSouthPole?o.bt:o.a5+pe),yt[At++]=Xt,yt[At++]=Rt}let vt=tt?new Uint32Array(Ie):new Uint16Array(Ie),Lt=0;for(let Kt=0;Kt0}get latitudeErrorCorrectionRadians(){return this._verticalPerspectiveProjection.latitudeErrorCorrectionRadians}get currentProjection(){return this.useGlobeRendering?this._verticalPerspectiveProjection:this._mercatorProjection}get name(){return"globe"}get useSubdivision(){return this.currentProjection.useSubdivision}get shaderVariantName(){return this.currentProjection.shaderVariantName}get shaderDefine(){return this.currentProjection.shaderDefine}get shaderPreludeCode(){return this.currentProjection.shaderPreludeCode}get vertexShaderPreludeCode(){return this.currentProjection.vertexShaderPreludeCode}get subdivisionGranularity(){return this.currentProjection.subdivisionGranularity}get useGlobeControls(){return this.transitionState>0}destroy(){this._mercatorProjection.destroy(),this._verticalPerspectiveProjection.destroy()}updateGPUdependent(l){this._mercatorProjection.updateGPUdependent(l),this._verticalPerspectiveProjection.updateGPUdependent(l)}getMeshFromTileID(l,m,T,P,V){return this.currentProjection.getMeshFromTileID(l,m,T,P,V)}setProjection(l){this._transitionable.setValue("type",l?.type||"mercator")}updateTransitions(l){this._transitioning=this._transitionable.transitioned(l,this._transitioning)}hasTransition(){return this._transitioning.hasTransition()||this.currentProjection.hasTransition()}recalculate(l){this.properties=this._transitioning.possiblyEvaluate(l)}setErrorQueryLatitudeDegrees(l){this._verticalPerspectiveProjection.setErrorQueryLatitudeDegrees(l),this._mercatorProjection.setErrorQueryLatitudeDegrees(l)}}function Ut(z){let l=li(z.worldSize,z.center.lat);return 2*Math.PI*l}function cr(z,l,m,T,P){let V=1/(1<1e-6){let T=z[0]/m,P=Math.acos(z[2]/m),V=(T>0?P:-P)/Math.PI*180;return new o.V(o.W(V,-180,180),l)}return new o.V(0,l)}function Bi(z){return Math.cos(z*Math.PI/180)}function gn(z,l){let m=Bi(z),T=Bi(l);return o.au(T/m)}function gs(z,l){let m=z.rotate(l.bearingInRadians),T=l.zoom+gn(l.center.lat,0),P=o.bv(1/Bi(l.center.lat),1/Bi(Math.min(Math.abs(l.center.lat),60)),o.by(T,7,3,0,1)),V=360/Ut({worldSize:l.worldSize,center:{lat:l.center.lat}});return new o.V(l.center.lng-m.x*V*P,o.ao(l.center.lat+m.y*V,-o.ap,o.ap))}function Io(z){let l=.5*z,m=Math.sin(l),T=Math.cos(l);return Math.log(m+T)-Math.log(T-m)}function bl(z,l,m,T){let P=z.lat+m*T;if(Math.abs(m)>1){let V=(Math.sign(z.lat+m)!==Math.sign(z.lat)?-Math.abs(z.lat):Math.abs(z.lat))*Math.PI/180,W=Math.abs(z.lat+m)*Math.PI/180,te=Io(V+T*(W-V)),se=Io(V),fe=Io(W);return new o.V(z.lng+l*((te-se)/(fe-se)),P)}return new o.V(z.lng+l*T,P)}class al{constructor(l){this._cachePrevious=new Map,this._cache=new Map,this._hadAnyChanges=!1,this._boundingVolumeFactory=l}swapBuffers(){if(!this._hadAnyChanges)return;let l=this._cachePrevious;this._cachePrevious=this._cache,this._cache=l,this._cache.clear(),this._hadAnyChanges=!1}getTileBoundingVolume(l,m,T,P){let V=`${l.z}_${l.x}_${l.y}_${P?.terrain?"t":""}`,W=this._cache.get(V);if(W)return W;let te=this._cachePrevious.get(V);if(te)return this._cache.set(V,te),te;let se=this._boundingVolumeFactory(l,m,T,P);return this._cache.set(V,se),this._hadAnyChanges=!0,se}}class Da{constructor(l,m,T,P){this.min=T,this.max=P,this.points=l,this.planes=m}static fromAabb(l,m){let T=[];for(let P=0;P<8;P++)T.push([1&~P?l[0]:m[0],(P>>1&1)==1?m[1]:l[1],(P>>2&1)==1?m[2]:l[2]]);return new Da(T,[[-1,0,0,m[0]],[1,0,0,-l[0]],[0,-1,0,m[1]],[0,1,0,-l[1]],[0,0,-1,m[2]],[0,0,1,-l[2]]],l,m)}static fromCenterSizeAngles(l,m,T){let P=o.bC([],T[0],T[1],T[2]),V=o.bD([],[m[0],0,0],P),W=o.bD([],[0,m[1],0],P),te=o.bD([],[0,0,m[2]],P),se=[...l],fe=[...l];for(let Re=0;Re<8;Re++)for(let Ie=0;Ie<3;Ie++){let je=l[Ie]+V[Ie]*(1&~Re?-1:1)+W[Ie]*((Re>>1&1)==1?1:-1)+te[Ie]*((Re>>2&1)==1?1:-1);se[Ie]=Math.min(se[Ie],je),fe[Ie]=Math.max(fe[Ie],je)}let _e=[];for(let Re=0;Re<8;Re++){let Ie=[...l];o.b1(Ie,Ie,o.b0([],V,1&~Re?-1:1)),o.b1(Ie,Ie,o.b0([],W,(Re>>1&1)==1?1:-1)),o.b1(Ie,Ie,o.b0([],te,(Re>>2&1)==1?1:-1)),_e.push(Ie)}return new Da(_e,[[...V,-o.b6(V,_e[0])],[...W,-o.b6(W,_e[0])],[...te,-o.b6(te,_e[0])],[-V[0],-V[1],-V[2],-o.b6(V,_e[7])],[-W[0],-W[1],-W[2],-o.b6(W,_e[7])],[-te[0],-te[1],-te[2],-o.b6(te,_e[7])]],se,fe)}intersectsFrustum(l){let m=!0,T=this.points.length,P=this.planes.length,V=l.planes.length,W=l.points.length;for(let te=0;te=0&&fe++}if(fe===0)return 0;fe=0&&fe++}if(fe===0)return 0}return 1}intersectsPlane(l){let m=this.points.length,T=0;for(let P=0;P=0&&T++}return T===m?2:T===0?0:1}}function Ka(z,l,m){let T=z-l;return T<0?-T:Math.max(0,T-m)}function Yn(z,l,m,T,P){let V=z-m,W;return W=V<0?Math.min(-V,1+V-P):V>1?Math.min(Math.max(V-P,0),1-V):0,Math.max(W,Ka(l,T,P))}class Er{constructor(){this._boundingVolumeCache=new al(this._computeTileBoundingVolume)}prepareNextFrame(){this._boundingVolumeCache.swapBuffers()}distanceToTile2d(l,m,T,P){let V=1<4}allowWorldCopies(){return!1}getTileBoundingVolume(l,m,T,P){return this._boundingVolumeCache.getTileBoundingVolume(l,m,T,P)}_computeTileBoundingVolume(l,m,T,P){var V,W;let te=0,se=0;if(P?.terrain){let fe=new o.a2(l.z,m,l.z,l.x,l.y),_e=P.terrain.getMinMaxElevation(fe);te=(V=_e.minElevation)!==null&&V!==void 0?V:Math.min(0,T),se=(W=_e.maxElevation)!==null&&W!==void 0?W:Math.max(0,T)}if(te/=o.bF,se/=o.bF,te+=1,se+=1,l.z<=0)return Da.fromAabb([-se,-se,-se],[se,se,se]);if(l.z===1)return Da.fromAabb([l.x===0?-se:0,l.y===0?0:-se,-se],[l.x===0?0:se,l.y===0?se:0,se]);{let fe=[cr(0,0,l.x,l.y,l.z),cr(o.a5,0,l.x,l.y,l.z),cr(o.a5,o.a5,l.x,l.y,l.z),cr(0,o.a5,l.x,l.y,l.z)],_e=[];for(let Pr of fe)_e.push(o.b0([],Pr,se));if(se!==te)for(let Pr of fe)_e.push(o.b0([],Pr,te));l.y===0&&_e.push([0,1,0]),l.y===(1<=(1<{let P=o.ao(m.lat,-o.ap,o.ap),V=o.ao(+T,this.minZoom+gn(0,P),this.maxZoom);return{center:new o.V(m.lng,P),zoom:V}},this.applyConstrain=(m,T)=>this._helper.applyConstrain(m,T),this._helper=new xl({calcMatrices:()=>{this._calcMatrices()},defaultConstrain:(m,T)=>this.defaultConstrain(m,T)},l),this._coveringTilesDetailsProvider=new Er}clone(){let l=new wi;return l.apply(this),l}apply(l,m){this._globeLatitudeErrorCorrectionRadians=m||0,this._helper.apply(l)}get projectionMatrix(){return this._projectionMatrix}get modelViewProjectionMatrix(){return this._globeViewProjMatrixNoCorrection}get inverseProjectionMatrix(){return this._globeProjMatrixInverted}get cameraPosition(){let l=o.bA();return l[0]=this._cameraPosition[0],l[1]=this._cameraPosition[1],l[2]=this._cameraPosition[2],l}get cameraToCenterDistance(){return this._helper.cameraToCenterDistance}getProjectionData(l){let{overscaledTileID:m,applyGlobeMatrix:T}=l,P=this._helper.getMercatorTileCoordinates(m);return{mainMatrix:this._globeViewProjMatrix32f,tileMercatorCoords:P,clippingPlane:this._cachedClippingPlane,projectionTransition:T?1:0,fallbackMatrix:this._globeViewProjMatrix32f}}_computeClippingPlane(l){let m=this.pitchInRadians,T=this.cameraToCenterDistance/l,P=Math.sin(m)*T,V=Math.cos(m)*T+1,W=1/Math.sqrt(P*P+V*V)*1,te=-P,se=V,fe=Math.sqrt(te*te+se*se);te/=fe,se/=fe;let _e=[0,te,se];o.bH(_e,_e,[0,0,0],-this.bearingInRadians),o.bI(_e,_e,[0,0,0],-1*this.center.lat*Math.PI/180),o.bJ(_e,_e,[0,0,0],this.center.lng*Math.PI/180);let Re=1/o.b8(_e);return o.b0(_e,_e,Re),[..._e,-W*Re]}isLocationOccluded(l){return!this.isSurfacePointVisible(Fr(l))}transformLightDirection(l){let m=this._helper._center.lng*Math.PI/180,T=this._helper._center.lat*Math.PI/180,P=Math.cos(T),V=[Math.sin(m)*P,Math.sin(T),Math.cos(m)*P],W=[V[2],0,-V[0]],te=[0,0,0];o.b5(te,W,V),o.b4(W,W),o.b4(te,te);let se=[0,0,0];return o.b4(se,[W[0]*l[0]+te[0]*l[1]+V[0]*l[2],W[1]*l[0]+te[1]*l[1]+V[1]*l[2],W[2]*l[0]+te[2]*l[1]+V[2]*l[2]]),se}getPixelScale(){return 1/Math.cos(this._helper._center.lat*Math.PI/180)}getCircleRadiusCorrection(){return Math.cos(this._helper._center.lat*Math.PI/180)}getPitchedTextCorrection(l,m,T){let P=function(te,se,fe){let _e=1/(1<V&&(V=Ie),jete&&(te=je)}let _e=[fe.lng+W,fe.lat+se,fe.lng+V,fe.lat+te];return this.isSurfacePointOnScreen([0,1,0])&&(_e[3]=90,_e[0]=-180,_e[2]=180),this.isSurfacePointOnScreen([0,-1,0])&&(_e[1]=-90,_e[0]=-180,_e[2]=180),new Go(_e)}calculateCenterFromCameraLngLatAlt(l,m,T,P){return this._helper.calculateCenterFromCameraLngLatAlt(l,m,T,P)}setLocationAtPoint(l,m){let T=Fr(this.unprojectScreenPoint(m)),P=Fr(l),V=o.bA();o.bM(V);let W=o.bA();o.bJ(W,T,V,-this.center.lng*Math.PI/180),o.bI(W,W,V,this.center.lat*Math.PI/180);let te=P[0]*P[0]+P[2]*P[2],se=W[0]*W[0];if(te=-vt&&tt<=vt,Kt=At>=-vt&&At<=vt,Mt,Xt;if(Lt&&Kt){let br=this.center.lng*Math.PI/180,Cr=this.center.lat*Math.PI/180;o.bO(Re,br)+o.bO(tt,Cr)=0}isSurfacePointOnScreen(l){if(!this.isSurfacePointVisible(l))return!1;let m=o.bG();return o.aI(m,[...l,1],this._globeViewProjMatrixNoCorrection),m[0]/=m[3],m[1]/=m[3],m[2]/=m[3],m[0]>-1&&m[0]<1&&m[1]>-1&&m[1]<1&&m[2]>-1&&m[2]<1}rayPlanetIntersection(l,m){let T=o.b6(l,m),P=o.bA(),V=o.bA();o.b0(V,m,T),o.b3(P,l,V);let W=1-o.b6(P,P);if(W<0)return null;let te=o.b6(l,l)-1,se=-T+(T<0?1:-1)*Math.sqrt(W),fe=te/se,_e=se;return{tMin:Math.min(fe,_e),tMax:Math.max(fe,_e)}}unprojectScreenPoint(l){let m=this._cameraPosition,T=this.getRayDirectionFromPixel(l),P=this.rayPlanetIntersection(m,T);if(P){let _e=o.bA();o.b1(_e,m,[T[0]*P.tMin,T[1]*P.tMin,T[2]*P.tMin]);let Re=o.bA();return o.b4(Re,_e),vr(Re)}let V=this._cachedClippingPlane,W=V[0]*T[0]+V[1]*T[1]+V[2]*T[2],te=-o.bc(V,m)/W,se=o.bA();if(te>0)o.b1(se,m,[T[0]*te,T[1]*te,T[2]*te]);else{let _e=o.bA();o.b1(_e,m,[2*T[0],2*T[1],2*T[2]]);let Re=o.bc(this._cachedClippingPlane,_e);o.b3(se,_e,[this._cachedClippingPlane[0]*Re,this._cachedClippingPlane[1]*Re,this._cachedClippingPlane[2]*Re])}let fe=function(_e){let Re=o.bA();return Re[0]=_e[0]*-_e[3],Re[1]=_e[1]*-_e[3],Re[2]=_e[2]*-_e[3],{center:Re,radius:Math.sqrt(1-_e[3]*_e[3])}}(V);return vr(function(_e,Re,Ie){let je=o.bA();o.b3(je,Ie,_e);let tt=o.bA();return o.bB(tt,_e,je,Re/o.ba(je)),tt}(fe.center,fe.radius,se))}getMatrixForModel(l,m){let T=o.V.convert(l),P=1/o.bF,V=o.bk();return o.bK(V,V,T.lng/180*Math.PI),o.bi(V,V,-T.lat/180*Math.PI),o.O(V,V,[0,0,1+m/o.bF]),o.bi(V,V,.5*Math.PI),o.Q(V,V,[P,P,P]),V}getProjectionDataForCustomLayer(l=!0){let m=this.getProjectionData({overscaledTileID:new o.a2(0,0,0,0,0),applyGlobeMatrix:l});return m.tileMercatorCoords=[0,0,1,1],m}getFastPathSimpleProjectionMatrix(l){}}class ni{get pixelsToClipSpaceMatrix(){return this._helper.pixelsToClipSpaceMatrix}get clipSpaceToPixelsMatrix(){return this._helper.clipSpaceToPixelsMatrix}get pixelsToGLUnits(){return this._helper.pixelsToGLUnits}get centerOffset(){return this._helper.centerOffset}get size(){return this._helper.size}get rotationMatrix(){return this._helper.rotationMatrix}get centerPoint(){return this._helper.centerPoint}get pixelsPerMeter(){return this._helper.pixelsPerMeter}setMinZoom(l){this._helper.setMinZoom(l)}setMaxZoom(l){this._helper.setMaxZoom(l)}setMinPitch(l){this._helper.setMinPitch(l)}setMaxPitch(l){this._helper.setMaxPitch(l)}setRenderWorldCopies(l){this._helper.setRenderWorldCopies(l)}setBearing(l){this._helper.setBearing(l)}setPitch(l){this._helper.setPitch(l)}setRoll(l){this._helper.setRoll(l)}setFov(l){this._helper.setFov(l)}setZoom(l){this._helper.setZoom(l)}setCenter(l){this._helper.setCenter(l)}setElevation(l){this._helper.setElevation(l)}setMinElevationForCurrentTile(l){this._helper.setMinElevationForCurrentTile(l)}setPadding(l){this._helper.setPadding(l)}interpolatePadding(l,m,T){return this._helper.interpolatePadding(l,m,T)}isPaddingEqual(l){return this._helper.isPaddingEqual(l)}resize(l,m,T=!0){this._helper.resize(l,m,T)}getMaxBounds(){return this._helper.getMaxBounds()}setMaxBounds(l){this._helper.setMaxBounds(l)}setConstrainOverride(l){this._helper.setConstrainOverride(l)}overrideNearFarZ(l,m){this._helper.overrideNearFarZ(l,m)}clearNearFarZOverride(){this._helper.clearNearFarZOverride()}getCameraQueryGeometry(l){return this._helper.getCameraQueryGeometry(this.getCameraPoint(),l)}get tileSize(){return this._helper.tileSize}get tileZoom(){return this._helper.tileZoom}get scale(){return this._helper.scale}get worldSize(){return this._helper.worldSize}get width(){return this._helper.width}get height(){return this._helper.height}get lngRange(){return this._helper.lngRange}get latRange(){return this._helper.latRange}get minZoom(){return this._helper.minZoom}get maxZoom(){return this._helper.maxZoom}get zoom(){return this._helper.zoom}get center(){return this._helper.center}get minPitch(){return this._helper.minPitch}get maxPitch(){return this._helper.maxPitch}get pitch(){return this._helper.pitch}get pitchInRadians(){return this._helper.pitchInRadians}get roll(){return this._helper.roll}get rollInRadians(){return this._helper.rollInRadians}get bearing(){return this._helper.bearing}get bearingInRadians(){return this._helper.bearingInRadians}get fov(){return this._helper.fov}get fovInRadians(){return this._helper.fovInRadians}get elevation(){return this._helper.elevation}get minElevationForCurrentTile(){return this._helper.minElevationForCurrentTile}get padding(){return this._helper.padding}get unmodified(){return this._helper.unmodified}get renderWorldCopies(){return this._helper.renderWorldCopies}get cameraToCenterDistance(){return this._helper.cameraToCenterDistance}get constrainOverride(){return this._helper.constrainOverride}get nearZ(){return this._helper.nearZ}get farZ(){return this._helper.farZ}get autoCalculateNearFarZ(){return this._helper.autoCalculateNearFarZ}get isGlobeRendering(){return this._globeness>0}setTransitionState(l,m){this._globeness=l,this._globeLatitudeErrorCorrectionRadians=m,this._calcMatrices(),this._verticalPerspectiveTransform.getCoveringTilesDetailsProvider().prepareNextFrame(),this._mercatorTransform.getCoveringTilesDetailsProvider().prepareNextFrame()}get currentTransform(){return this.isGlobeRendering?this._verticalPerspectiveTransform:this._mercatorTransform}constructor(l){this._globeLatitudeErrorCorrectionRadians=0,this._globeness=1,this.defaultConstrain=(m,T)=>this.currentTransform.defaultConstrain(m,T),this.applyConstrain=(m,T)=>this._helper.applyConstrain(m,T),this._helper=new xl({calcMatrices:()=>{this._calcMatrices()},defaultConstrain:(m,T)=>this.defaultConstrain(m,T)},l),this._globeness=1,this._mercatorTransform=new Hf,this._verticalPerspectiveTransform=new wi}clone(){let l=new ni;return l._globeness=this._globeness,l._globeLatitudeErrorCorrectionRadians=this._globeLatitudeErrorCorrectionRadians,l.apply(this),l}apply(l){this._helper.apply(l),this._mercatorTransform.apply(this),this._verticalPerspectiveTransform.apply(this,this._globeLatitudeErrorCorrectionRadians)}get projectionMatrix(){return this.currentTransform.projectionMatrix}get modelViewProjectionMatrix(){return this.currentTransform.modelViewProjectionMatrix}get inverseProjectionMatrix(){return this.currentTransform.inverseProjectionMatrix}get cameraPosition(){return this.currentTransform.cameraPosition}getProjectionData(l){let m=this._mercatorTransform.getProjectionData(l),T=this._verticalPerspectiveTransform.getProjectionData(l);return{mainMatrix:this.isGlobeRendering?T.mainMatrix:m.mainMatrix,clippingPlane:T.clippingPlane,tileMercatorCoords:T.tileMercatorCoords,projectionTransition:l.applyGlobeMatrix?this._globeness:0,fallbackMatrix:m.fallbackMatrix}}isLocationOccluded(l){return this.currentTransform.isLocationOccluded(l)}transformLightDirection(l){return this.currentTransform.transformLightDirection(l)}getPixelScale(){return o.bv(this._mercatorTransform.getPixelScale(),this._verticalPerspectiveTransform.getPixelScale(),this._globeness)}getCircleRadiusCorrection(){return o.bv(this._mercatorTransform.getCircleRadiusCorrection(),this._verticalPerspectiveTransform.getCircleRadiusCorrection(),this._globeness)}getPitchedTextCorrection(l,m,T){let P=this._mercatorTransform.getPitchedTextCorrection(l,m,T),V=this._verticalPerspectiveTransform.getPitchedTextCorrection(l,m,T);return o.bv(P,V,this._globeness)}projectTileCoordinates(l,m,T,P){return this.currentTransform.projectTileCoordinates(l,m,T,P)}_calcMatrices(){this._helper._width&&this._helper._height&&(this._verticalPerspectiveTransform.apply(this,this._globeLatitudeErrorCorrectionRadians),this._helper._nearZ=this._verticalPerspectiveTransform.nearZ,this._helper._farZ=this._verticalPerspectiveTransform.farZ,this._mercatorTransform.apply(this,!0,this.isGlobeRendering),this._helper._nearZ=this._mercatorTransform.nearZ,this._helper._farZ=this._mercatorTransform.farZ)}calculateFogMatrix(l){return this.currentTransform.calculateFogMatrix(l)}getVisibleUnwrappedCoordinates(l){return this.currentTransform.getVisibleUnwrappedCoordinates(l)}getCameraFrustum(){return this.currentTransform.getCameraFrustum()}getClippingPlane(){return this.currentTransform.getClippingPlane()}getCoveringTilesDetailsProvider(){return this.currentTransform.getCoveringTilesDetailsProvider()}recalculateZoomAndCenter(l){this._mercatorTransform.recalculateZoomAndCenter(l),this._verticalPerspectiveTransform.recalculateZoomAndCenter(l)}maxPitchScaleFactor(){return this._mercatorTransform.maxPitchScaleFactor()}getCameraPoint(){return this._helper.getCameraPoint()}getCameraAltitude(){return this._helper.getCameraAltitude()}getCameraLngLat(){return this._helper.getCameraLngLat()}lngLatToCameraDepth(l,m){return this.currentTransform.lngLatToCameraDepth(l,m)}populateCache(l){this._mercatorTransform.populateCache(l),this._verticalPerspectiveTransform.populateCache(l)}getBounds(){return this.currentTransform.getBounds()}calculateCenterFromCameraLngLatAlt(l,m,T,P){return this._helper.calculateCenterFromCameraLngLatAlt(l,m,T,P)}setLocationAtPoint(l,m){if(!this.isGlobeRendering)return this._mercatorTransform.setLocationAtPoint(l,m),void this.apply(this._mercatorTransform);this._verticalPerspectiveTransform.setLocationAtPoint(l,m),this.apply(this._verticalPerspectiveTransform)}locationToScreenPoint(l,m){return this.currentTransform.locationToScreenPoint(l,m)}screenPointToMercatorCoordinate(l,m){return this.currentTransform.screenPointToMercatorCoordinate(l,m)}screenPointToLocation(l,m){return this.currentTransform.screenPointToLocation(l,m)}isPointOnMapSurface(l,m){return this.currentTransform.isPointOnMapSurface(l,m)}getRayDirectionFromPixel(l){return this._verticalPerspectiveTransform.getRayDirectionFromPixel(l)}getMatrixForModel(l,m){return this.currentTransform.getMatrixForModel(l,m)}getProjectionDataForCustomLayer(l=!0){let m=this._mercatorTransform.getProjectionDataForCustomLayer(l);if(!this.isGlobeRendering)return m;let T=this._verticalPerspectiveTransform.getProjectionDataForCustomLayer(l);return T.fallbackMatrix=m.mainMatrix,T}getFastPathSimpleProjectionMatrix(l){return this.currentTransform.getFastPathSimpleProjectionMatrix(l)}}class b{get useGlobeControls(){return!0}handlePanInertia(l,m){let T=gs(l,m);return Math.abs(T.lng-m.center.lng)>180&&(T.lng=m.center.lng+179.5*Math.sign(T.lng-m.center.lng)),{easingCenter:T,easingOffset:new o.P(0,0)}}handleMapControlsRollPitchBearingZoom(l,m){let T=l.around,P=m.screenPointToLocation(T);l.bearingDelta&&m.setBearing(m.bearing+l.bearingDelta),l.pitchDelta&&m.setPitch(m.pitch+l.pitchDelta),l.rollDelta&&m.setRoll(m.roll+l.rollDelta);let V=m.zoom;l.zoomDelta&&m.setZoom(m.zoom+l.zoomDelta);let W=m.zoom-V;if(W===0)return;let te=o.bL(m.center.lng,P.lng),se=te/(Math.abs(te/180)+1),fe=o.bL(m.center.lat,P.lat),_e=m.getRayDirectionFromPixel(T),Re=m.cameraPosition,Ie=-1*o.b6(Re,_e),je=o.bA();o.b1(je,Re,[_e[0]*Ie,_e[1]*Ie,_e[2]*Ie]);let tt=o.b8(je)-1,yt=Math.exp(.5*-Math.max(tt-.3,0)),At=li(m.worldSize,m.center.lat)/Math.min(m.width,m.height),vt=o.by(At,.9,.5,1,.25),Lt=(1-o.ar(-W))*Math.min(yt,vt),Kt=m.center.lat,Mt=m.zoom,Xt=new o.V(m.center.lng+se*Lt,o.ao(m.center.lat+fe*Lt,-o.ap,o.ap));m.setLocationAtPoint(P,T);let Rt=m.center,zt=o.by(Math.abs(te),45,85,0,1),jt=o.by(At,.75,.35,0,1),br=Math.pow(Math.max(zt,jt),.25),Cr=o.bL(Rt.lng,Xt.lng),Pr=o.bL(Rt.lat,Xt.lat);m.setCenter(new o.V(Rt.lng+Cr*br,Rt.lat+Pr*br).wrap()),m.setZoom(Mt+gn(Kt,m.center.lat))}handleMapControlsPan(l,m,T){if(!l.panDelta)return;let P=m.center.lat,V=m.zoom;m.setCenter(gs(l.panDelta,m).wrap()),m.setZoom(V+gn(P,m.center.lat))}cameraForBoxAndBearing(l,m,T,P,V){let W=Cp(l,m,T,P,V),te=m.left/V.width*2-1,se=(V.width-m.right)/V.width*2-1,fe=m.top/V.height*-2+1,_e=(V.height-m.bottom)/V.height*-2+1,Re=o.bL(T.getWest(),T.getEast())<0,Ie=Re?T.getEast():T.getWest(),je=Re?T.getWest():T.getEast(),tt=Math.max(T.getNorth(),T.getSouth()),yt=Math.min(T.getNorth(),T.getSouth()),At=Ie+.5*o.bL(Ie,je),vt=tt+.5*o.bL(tt,yt),Lt=V.clone();Lt.setCenter(W.center),Lt.setBearing(W.bearing),Lt.setPitch(0),Lt.setRoll(0),Lt.setZoom(W.zoom);let Kt=Lt.modelViewProjectionMatrix,Mt=[Fr(T.getNorthWest()),Fr(T.getNorthEast()),Fr(T.getSouthWest()),Fr(T.getSouthEast()),Fr(new o.V(je,vt)),Fr(new o.V(Ie,vt)),Fr(new o.V(At,tt)),Fr(new o.V(At,yt))],Xt=Fr(W.center),Rt=Number.POSITIVE_INFINITY;for(let zt of Mt)te<0&&(Rt=b.getLesserNonNegativeNonNull(Rt,b.solveVectorScale(zt,Xt,Kt,"x",te))),se>0&&(Rt=b.getLesserNonNegativeNonNull(Rt,b.solveVectorScale(zt,Xt,Kt,"x",se))),fe>0&&(Rt=b.getLesserNonNegativeNonNull(Rt,b.solveVectorScale(zt,Xt,Kt,"y",fe))),_e<0&&(Rt=b.getLesserNonNegativeNonNull(Rt,b.solveVectorScale(zt,Xt,Kt,"y",_e)));if(Number.isFinite(Rt)&&Rt!==0)return W.zoom=Lt.zoom+o.au(Rt),W;qh()}handleJumpToCenterZoom(l,m){let T=l.center.lat,P=l.applyConstrain(m.center?o.V.convert(m.center):l.center,l.zoom).center;l.setCenter(P.wrap());let V=m.zoom!==void 0?+m.zoom:l.zoom+gn(T,P.lat);l.zoom!==V&&l.setZoom(V)}handleEaseTo(l,m){let T=l.zoom,P=l.center,V=l.padding,W={roll:l.roll,pitch:l.pitch,bearing:l.bearing},te={roll:m.roll===void 0?l.roll:m.roll,pitch:m.pitch===void 0?l.pitch:m.pitch,bearing:m.bearing===void 0?l.bearing:m.bearing},se=m.zoom!==void 0,fe=!l.isPaddingEqual(m.padding),_e=!1,Re=m.center?o.V.convert(m.center):P,Ie=l.applyConstrain(Re,T).center;tu(l,Ie);let je=l.clone();je.setCenter(Ie),je.setZoom(se?+m.zoom:T+gn(P.lat,Re.lat)),je.setBearing(m.bearing);let tt=new o.P(o.ao(l.centerPoint.x+m.offsetAsPoint.x,0,l.width),o.ao(l.centerPoint.y+m.offsetAsPoint.y,0,l.height));je.setLocationAtPoint(Ie,tt);let yt=(m.offset&&m.offsetAsPoint.mag())>0?je.center:Ie,At=se?+m.zoom:T+gn(P.lat,yt.lat),vt=T+gn(P.lat,0),Lt=At+gn(yt.lat,0),Kt=o.bL(P.lng,yt.lng),Mt=o.bL(P.lat,yt.lat),Xt=o.ar(Lt-vt);return _e=At!==T,{easeFunc:Rt=>{if(o.bp(W,te)||Gd({startEulerAngles:W,endEulerAngles:te,tr:l,k:Rt,useSlerp:W.roll!=te.roll}),fe&&l.interpolatePadding(V,m.padding,Rt),m.around)o.w("Easing around a point is not supported under globe projection."),l.setLocationAtPoint(m.around,m.aroundPoint);else{let zt=Lt>vt?Math.min(2,Xt):Math.max(.5,Xt),jt=Math.pow(zt,1-Rt),br=bl(P,Kt,Mt,Rt*jt);l.setCenter(br.wrap())}if(_e){let zt=o.G.number(vt,Lt,Rt)+gn(0,l.center.lat);l.setZoom(zt)}},isZooming:_e,elevationCenter:yt}}handleFlyTo(l,m){let T=m.zoom!==void 0,P=l.center,V=l.zoom,W=l.padding,te=!l.isPaddingEqual(m.padding),se=l.applyConstrain(o.V.convert(m.center||m.locationAtOffset),V).center,fe=T?+m.zoom:l.zoom+gn(l.center.lat,se.lat),_e=l.clone();_e.setCenter(se),_e.setZoom(fe),_e.setBearing(m.bearing);let Re=new o.P(o.ao(l.centerPoint.x+m.offsetAsPoint.x,0,l.width),o.ao(l.centerPoint.y+m.offsetAsPoint.y,0,l.height));_e.setLocationAtPoint(se,Re);let Ie=_e.center;tu(l,Ie);let je=function(Mt,Xt,Rt){let zt=Fr(Xt),jt=Fr(Rt),br=o.b6(zt,jt),Cr=Math.acos(br),Pr=Ut(Mt);return Cr/(2*Math.PI)*Pr}(l,P,Ie),tt=V+gn(P.lat,0),yt=fe+gn(Ie.lat,0),At=o.ar(yt-tt),vt;if(typeof m.minZoom=="number"){let Mt=+m.minZoom+gn(Ie.lat,0),Xt=Math.min(Mt,tt,yt)+gn(0,Ie.lat),Rt=l.applyConstrain(Ie,Xt).zoom+gn(Ie.lat,0);vt=o.ar(Rt-tt)}let Lt=o.bL(P.lng,Ie.lng),Kt=o.bL(P.lat,Ie.lat);return{easeFunc:(Mt,Xt,Rt,zt)=>{let jt=bl(P,Lt,Kt,Rt);te&&l.interpolatePadding(W,m.padding,Mt);let br=Mt===1?Ie:jt;l.setCenter(br.wrap());let Cr=tt+o.au(Xt);l.setZoom(Mt===1?fe:Cr+gn(0,br.lat))},scaleOfZoom:At,targetCenter:Ie,scaleOfMinZoom:vt,pixelPathLength:je}}static solveVectorScale(l,m,T,P,V){let W=P==="x"?[T[0],T[4],T[8],T[12]]:[T[1],T[5],T[9],T[13]],te=[T[3],T[7],T[11],T[15]],se=l[0]*W[0]+l[1]*W[1]+l[2]*W[2],fe=l[0]*te[0]+l[1]*te[1]+l[2]*te[2],_e=m[0]*W[0]+m[1]*W[1]+m[2]*W[2],Re=m[0]*te[0]+m[1]*te[1]+m[2]*te[2];return _e+V*fe===se+V*Re||te[3]*(se-_e)+W[3]*(Re-fe)+se*Re==_e*fe?null:(_e+W[3]-V*Re-V*te[3])/(_e-se-V*Re+V*fe)}static getLesserNonNegativeNonNull(l,m){return m!==null&&m>=0&&mo.B(z,l&&l.filter(m=>m.identifier!=="source.canvas")),Ze=o.bP();class Is extends o.E{constructor(l,m={}){var T,P;super(),this._rtlPluginLoaded=()=>{for(let W in this.tileManagers){let te=this.tileManagers[W].getSource().type;te!=="vector"&&te!=="geojson"||this.tileManagers[W].reload()}},this.map=l,this.dispatcher=new Ft(nt(),l._getMapId()),this.dispatcher.registerMessageHandler("GG",(W,te)=>this.getGlyphs(W,te)),this.dispatcher.registerMessageHandler("GI",(W,te)=>this.getImages(W,te)),this.dispatcher.registerMessageHandler("GDA",(W,te)=>this.getDashes(W,te)),this.imageManager=new ve,this.imageManager.setEventedParent(this);let V=((T=l._container)===null||T===void 0?void 0:T.lang)||typeof document<"u"&&((P=document.documentElement)===null||P===void 0?void 0:P.lang)||void 0;this.glyphManager=new sr(l._requestManager,m.localIdeographFontFamily,V),this.lineAtlas=new Yr(256,512),this.crossTileSymbolIndex=new Ql,this._setInitialValues(),this._resetUpdates(),this.dispatcher.broadcast("SR",o.bQ()),fr().on(Vt,this._rtlPluginLoaded),this.on("data",W=>{if(W.dataType!=="source"||W.sourceDataType!=="metadata")return;let te=this.tileManagers[W.sourceId];if(!te)return;let se=te.getSource();if(se&&se.vectorLayerIds)for(let fe in this._layers){let _e=this._layers[fe];_e.source===se.id&&this._validateLayer(_e)}})}_setInitialValues(){var l;this._spritesImagesIds={},this._layers={},this._order=[],this.tileManagers={},this.zoomHistory=new o.bR,this._availableImages=[],this._globalState={},this._serializedLayers={},this.stylesheet=null,this.light=null,this.sky=null,this.projection&&(this.projection.destroy(),delete this.projection),this._loaded=!1,this._changed=!1,this._updatedLayers={},this._updatedSources={},this._changedImages={},this._glyphsDidChange=!1,this._updatedPaintProps={},this._layerOrderChanged=!1,this.crossTileSymbolIndex=new(((l=this.crossTileSymbolIndex)===null||l===void 0?void 0:l.constructor)||Object),this.pauseablePlacement=void 0,this.placement=void 0,this.z=0}setGlobalStateProperty(l,m){var T,P,V;this._checkLoaded();let W=m===null?(V=(P=(T=this.stylesheet.state)===null||T===void 0?void 0:T[l])===null||P===void 0?void 0:P.default)!==null&&V!==void 0?V:null:m;if(o.bS(W,this._globalState[l]))return this;this._globalState[l]=W,this._applyGlobalStateChanges([l])}getGlobalState(){return this._globalState}setGlobalState(l){this._checkLoaded();let m=[];for(let T in l)!o.bS(this._globalState[T],l[T].default)&&(m.push(T),this._globalState[T]=l[T].default);this._applyGlobalStateChanges(m)}_applyGlobalStateChanges(l){if(l.length===0)return;let m=new Set,T={};for(let P of l){T[P]=this._globalState[P];for(let V in this._layers){let W=this._layers[V],te=W.getLayoutAffectingGlobalStateRefs(),se=W.getPaintAffectingGlobalStateRefs();if(te.has(P)&&m.add(W.source),se.has(P))for(let{name:fe,value:_e}of se.get(P))this._updatePaintProperty(W,fe,_e)}}this.dispatcher.broadcast("UGS",T);for(let P in this.tileManagers)m.has(P)&&(this._reloadSource(P),this._changed=!0)}loadURL(l,m={},T){this.fire(new o.l("dataloading",{dataType:"style"})),m.validate=typeof m.validate!="boolean"||m.validate;let P=this.map._requestManager.transformRequest(l,"Style");this._loadStyleRequest=new AbortController;let V=this._loadStyleRequest;o.j(P,this._loadStyleRequest).then(W=>{this._loadStyleRequest=null,this._load(W.data,m,T)}).catch(W=>{this._loadStyleRequest=null,W&&!V.signal.aborted&&this.fire(new o.k(W))})}loadJSON(l,m={},T){this.fire(new o.l("dataloading",{dataType:"style"})),this._frameRequest=new AbortController,M.frameAsync(this._frameRequest).then(()=>{this._frameRequest=null,m.validate=m.validate!==!1,this._load(l,m,T)}).catch(()=>{})}loadEmpty(){this.fire(new o.l("dataloading",{dataType:"style"})),this._load(Ze,{validate:!1})}_load(l,m,T){var P,V;let W=m.transformStyle?m.transformStyle(T,l):l;if(!m.validate||!_o(this,o.C(W))){W=Object.assign({},W),this._loaded=!0,this.stylesheet=W;for(let te in W.sources)this.addSource(te,W.sources[te],{validate:!1});W.sprite?this._loadSprite(W.sprite):this.imageManager.setLoaded(!0),this.glyphManager.setURL(W.glyphs),this._createLayers(),this.light=new mi(this.stylesheet.light),this._setProjectionInternal(((P=this.stylesheet.projection)===null||P===void 0?void 0:P.type)||"mercator"),this.sky=new un(this.stylesheet.sky),this.map.setTerrain((V=this.stylesheet.terrain)!==null&&V!==void 0?V:null),this.fire(new o.l("data",{dataType:"style"})),this.fire(new o.l("style.load"))}}_createLayers(){var l,m,T;let P=o.bT(this.stylesheet.layers);this.setGlobalState((l=this.stylesheet.state)!==null&&l!==void 0?l:null),this.dispatcher.broadcast("SL",P),this._order=P.map(V=>V.id),this._layers={},this._serializedLayers=null;for(let V of P){let W=o.bU(V,this._globalState);if(W.setEventedParent(this,{layer:{id:V.id}}),this._layers[V.id]=W,o.bV(W)&&this.tileManagers[W.source]){let te=(T=(m=V.paint)===null||m===void 0?void 0:m["raster-fade-duration"])!==null&&T!==void 0?T:W.paint.get("raster-fade-duration");this.tileManagers[W.source].setRasterFadeDuration(te)}}}_loadSprite(l,m=!1,T=void 0){this.imageManager.setLoaded(!1);let P=new AbortController,V;this._spriteRequest=P,function(W,te,se,fe){return o._(this,void 0,void 0,function*(){let _e=He(W),Re=se>1?"@2x":"",Ie={},je={};for(let{id:tt,url:yt}of _e){let At=te.transformRequest(We(yt,Re,".json"),"SpriteJSON");Ie[tt]=o.j(At,fe);let vt=te.transformRequest(We(yt,Re,".png"),"SpriteImage");je[tt]=Oe.getImage(vt,fe)}return yield Promise.all([...Object.values(Ie),...Object.values(je)]),function(tt,yt){return o._(this,void 0,void 0,function*(){let At={};for(let vt in tt){At[vt]={};let Lt=M.getImageCanvasContext((yield yt[vt]).data),Kt=(yield tt[vt]).data;for(let Mt in Kt){let{width:Xt,height:Rt,x:zt,y:jt,sdf:br,pixelRatio:Cr,stretchX:Pr,stretchY:Ir,content:Ai,textFitWidth:_i,textFitHeight:hi}=Kt[Mt];At[vt][Mt]={data:null,pixelRatio:Cr,sdf:br,stretchX:Pr,stretchY:Ir,content:Ai,textFitWidth:_i,textFitHeight:hi,spriteData:{width:Xt,height:Rt,x:zt,y:jt,context:Lt}}}}return At})}(Ie,je)})}(l,this.map._requestManager,this.map.getPixelRatio(),this._spriteRequest).then(W=>{if(this._spriteRequest=null,W)for(let te in W){this._spritesImagesIds[te]=[];let se=this._spritesImagesIds[te]?this._spritesImagesIds[te].filter(fe=>!(fe in W)):[];for(let fe of se)this.imageManager.removeImage(fe),this._changedImages[fe]=!0;for(let fe in W[te]){let _e=te==="default"?fe:`${te}:${fe}`;this._spritesImagesIds[te].push(_e),_e in this.imageManager.images?this.imageManager.updateImage(_e,W[te][fe],!1):this.imageManager.addImage(_e,W[te][fe]),m&&(this._changedImages[_e]=!0)}}}).catch(W=>{this._spriteRequest=null,V=W,P.signal.aborted||this.fire(new o.k(V))}).finally(()=>{this.imageManager.setLoaded(!0),this._availableImages=this.imageManager.listImages(),m&&(this._changed=!0),this.dispatcher.broadcast("SI",this._availableImages),this.fire(new o.l("data",{dataType:"style"})),T&&T(V)})}_unloadSprite(){for(let l of Object.values(this._spritesImagesIds).flat())this.imageManager.removeImage(l),this._changedImages[l]=!0;this._spritesImagesIds={},this._availableImages=this.imageManager.listImages(),this._changed=!0,this.dispatcher.broadcast("SI",this._availableImages),this.fire(new o.l("data",{dataType:"style"}))}_validateLayer(l){let m=this.tileManagers[l.source];if(!m)return;let T=l.sourceLayer;if(!T)return;let P=m.getSource();(P.type==="geojson"||P.vectorLayerIds&&P.vectorLayerIds.indexOf(T)===-1)&&this.fire(new o.k(new Error(`Source layer "${T}" does not exist on source "${P.id}" as specified by style layer "${l.id}".`)))}loaded(){if(!this._loaded||Object.keys(this._updatedSources).length)return!1;for(let l in this.tileManagers)if(!this.tileManagers[l].loaded())return!1;return!!this.imageManager.isLoaded()}_serializeByIds(l,m=!1){let T=this._serializedAllLayers();if(!l||l.length===0)return Object.values(m?o.bW(T):T);let P=[];for(let V of l)if(T[V]){let W=m?o.bW(T[V]):T[V];P.push(W)}return P}_serializedAllLayers(){let l=this._serializedLayers;if(l)return l;l=this._serializedLayers={};let m=Object.keys(this._layers);for(let T of m){let P=this._layers[T];P.type!=="custom"&&(l[T]=P.serialize())}return l}hasTransitions(){var l,m,T;if(!((l=this.light)===null||l===void 0)&&l.hasTransition()||!((m=this.sky)===null||m===void 0)&&m.hasTransition()||!((T=this.projection)===null||T===void 0)&&T.hasTransition())return!0;for(let P in this.tileManagers)if(this.tileManagers[P].hasTransition())return!0;for(let P in this._layers)if(this._layers[P].hasTransition())return!0;return!1}_checkLoaded(){if(!this._loaded)throw new Error("Style is not done loading.")}update(l){if(!this._loaded)return;let m=this._changed;if(m){let P=Object.keys(this._updatedLayers),V=Object.keys(this._removedLayers);(P.length||V.length)&&this._updateWorkerLayers(P,V);for(let W in this._updatedSources){let te=this._updatedSources[W];if(te==="reload")this._reloadSource(W);else{if(te!=="clear")throw new Error(`Invalid action ${te}`);this._clearSource(W)}}this._updateTilesForChangedImages(),this._updateTilesForChangedGlyphs();for(let W in this._updatedPaintProps)this._layers[W].updateTransitions(l);this.light.updateTransitions(l),this.sky.updateTransitions(l),this._resetUpdates()}let T={};for(let P in this.tileManagers){let V=this.tileManagers[P];T[P]=V.used,V.used=!1}for(let P of this._order){let V=this._layers[P];V.recalculate(l,this._availableImages),!V.isHidden(l.zoom)&&V.source&&(this.tileManagers[V.source].used=!0)}for(let P in T){let V=this.tileManagers[P];!!T[P]!=!!V.used&&V.fire(new o.l("data",{sourceDataType:"visibility",dataType:"source",sourceId:P}))}this.light.recalculate(l),this.sky.recalculate(l),this.projection.recalculate(l),this.z=l.zoom,m&&this.fire(new o.l("data",{dataType:"style"}))}_updateTilesForChangedImages(){let l=Object.keys(this._changedImages);if(l.length){for(let m in this.tileManagers)this.tileManagers[m].reloadTilesForDependencies(["icons","patterns"],l);this._changedImages={}}}_updateTilesForChangedGlyphs(){if(this._glyphsDidChange){for(let l in this.tileManagers)this.tileManagers[l].reloadTilesForDependencies(["glyphs"],[""]);this._glyphsDidChange=!1}}_updateWorkerLayers(l,m){this.dispatcher.broadcast("UL",{layers:this._serializeByIds(l,!1),removedIds:m})}_resetUpdates(){this._changed=!1,this._updatedLayers={},this._removedLayers={},this._updatedSources={},this._updatedPaintProps={},this._changedImages={},this._glyphsDidChange=!1}setState(l,m={}){var T;this._checkLoaded();let P=this.serialize();if(l=m.transformStyle?m.transformStyle(P,l):l,((T=m.validate)===null||T===void 0||T)&&_o(this,o.C(l)))return!1;(l=o.bW(l)).layers=o.bT(l.layers);let V=o.bX(P,l),W=this._getOperationsToPerform(V);if(W.unimplemented.length>0)throw new Error(`Unimplemented: ${W.unimplemented.join(", ")}.`);if(W.operations.length===0)return!1;for(let te of W.operations)te();return this.stylesheet=l,this._serializedLayers=null,!0}_getOperationsToPerform(l){let m=[],T=[];for(let P of l)switch(P.command){case"setCenter":case"setZoom":case"setBearing":case"setPitch":case"setRoll":continue;case"addLayer":m.push(()=>this.addLayer.apply(this,P.args));break;case"removeLayer":m.push(()=>this.removeLayer.apply(this,P.args));break;case"setPaintProperty":m.push(()=>this.setPaintProperty.apply(this,P.args));break;case"setLayoutProperty":m.push(()=>this.setLayoutProperty.apply(this,P.args));break;case"setFilter":m.push(()=>this.setFilter.apply(this,P.args));break;case"addSource":m.push(()=>this.addSource.apply(this,P.args));break;case"removeSource":m.push(()=>this.removeSource.apply(this,P.args));break;case"setLayerZoomRange":m.push(()=>this.setLayerZoomRange.apply(this,P.args));break;case"setLight":m.push(()=>this.setLight.apply(this,P.args));break;case"setGeoJSONSourceData":m.push(()=>this.setGeoJSONSourceData.apply(this,P.args));break;case"setGlyphs":m.push(()=>this.setGlyphs.apply(this,P.args));break;case"setSprite":m.push(()=>this.setSprite.apply(this,P.args));break;case"setTerrain":m.push(()=>this.map.setTerrain.apply(this,P.args));break;case"setSky":m.push(()=>this.setSky.apply(this,P.args));break;case"setProjection":this.setProjection.apply(this,P.args);break;case"setGlobalState":m.push(()=>this.setGlobalState.apply(this,P.args));break;case"setTransition":m.push(()=>{});break;default:T.push(P.command)}return{operations:m,unimplemented:T}}addImage(l,m){if(this.getImage(l))return this.fire(new o.k(new Error(`An image named "${l}" already exists.`)));this.imageManager.addImage(l,m),this._afterImageUpdated(l)}updateImage(l,m){this.imageManager.updateImage(l,m)}getImage(l){return this.imageManager.getImage(l)}removeImage(l){if(!this.getImage(l))return this.fire(new o.k(new Error(`An image named "${l}" does not exist.`)));this.imageManager.removeImage(l),this._afterImageUpdated(l)}_afterImageUpdated(l){this._availableImages=this.imageManager.listImages(),this._changedImages[l]=!0,this._changed=!0,this.dispatcher.broadcast("SI",this._availableImages),this.fire(new o.l("data",{dataType:"style"}))}listImages(){return this._checkLoaded(),this.imageManager.listImages()}addSource(l,m,T={}){if(this._checkLoaded(),this.tileManagers[l]!==void 0)throw new Error(`Source "${l}" already exists.`);if(!m.type)throw new Error(`The type property must be defined, but only the following properties were given: ${Object.keys(m).join(", ")}.`);if(["vector","raster","geojson","video","image"].indexOf(m.type)>=0&&this._validate(o.C.source,`sources.${l}`,m,null,T))return;this.map&&this.map._collectResourceTiming&&(m.collectResourceTiming=!0);let P=this.tileManagers[l]=new si(l,m,this.dispatcher);P.style=this,P.setEventedParent(this,()=>({isSourceLoaded:P.loaded(),source:P.serialize(),sourceId:l})),P.onAdd(this.map),this._changed=!0}removeSource(l){if(this._checkLoaded(),this.tileManagers[l]===void 0)throw new Error("There is no source with this ID");for(let T in this._layers)if(this._layers[T].source===l)return this.fire(new o.k(new Error(`Source "${l}" cannot be removed while layer "${T}" is using it.`)));let m=this.tileManagers[l];delete this.tileManagers[l],delete this._updatedSources[l],m.fire(new o.l("data",{sourceDataType:"metadata",dataType:"source",sourceId:l})),m.setEventedParent(null),m.onRemove(this.map),this._changed=!0}setGeoJSONSourceData(l,m){if(this._checkLoaded(),this.tileManagers[l]===void 0)throw new Error(`There is no source with this ID=${l}`);let T=this.tileManagers[l].getSource();if(T.type!=="geojson")throw new Error(`geojsonSource.type is ${T.type}, which is !== 'geojson`);T.setData(m),this._changed=!0}getSource(l){return this.tileManagers[l]&&this.tileManagers[l].getSource()}addLayer(l,m,T={}){this._checkLoaded();let P=l.id;if(this.getLayer(P))return void this.fire(new o.k(new Error(`Layer "${P}" already exists on this map.`)));let V;if(l.type==="custom"){if(_o(this,o.bY(l)))return;V=o.bU(l,this._globalState)}else{if("source"in l&&typeof l.source=="object"&&(this.addSource(P,l.source),l=o.bW(l),l=o.e(l,{source:P})),this._validate(o.C.layer,`layers.${P}`,l,{arrayIndex:-1},T))return;V=o.bU(l,this._globalState),this._validateLayer(V),V.setEventedParent(this,{layer:{id:P}})}let W=m?this._order.indexOf(m):this._order.length;if(m&&W===-1)this.fire(new o.k(new Error(`Cannot add layer "${P}" before non-existing layer "${m}".`)));else{if(this._order.splice(W,0,P),this._layerOrderChanged=!0,this._layers[P]=V,this._removedLayers[P]&&V.source&&V.type!=="custom"){let te=this._removedLayers[P];delete this._removedLayers[P],te.type!==V.type?this._updatedSources[V.source]="clear":(this._updatedSources[V.source]="reload",this.tileManagers[V.source].pause())}this._updateLayer(V),V.onAdd&&V.onAdd(this.map)}}moveLayer(l,m){if(this._checkLoaded(),this._changed=!0,!this._layers[l])return void this.fire(new o.k(new Error(`The layer '${l}' does not exist in the map's style and cannot be moved.`)));if(l===m)return;let T=this._order.indexOf(l);this._order.splice(T,1);let P=m?this._order.indexOf(m):this._order.length;m&&P===-1?this.fire(new o.k(new Error(`Cannot move layer "${l}" before non-existing layer "${m}".`))):(this._order.splice(P,0,l),this._layerOrderChanged=!0)}removeLayer(l){this._checkLoaded();let m=this._layers[l];if(!m)return void this.fire(new o.k(new Error(`Cannot remove non-existing layer "${l}".`)));m.setEventedParent(null);let T=this._order.indexOf(l);this._order.splice(T,1),this._layerOrderChanged=!0,this._changed=!0,this._removedLayers[l]=m,delete this._layers[l],this._serializedLayers&&delete this._serializedLayers[l],delete this._updatedLayers[l],delete this._updatedPaintProps[l],m.onRemove&&m.onRemove(this.map)}getLayer(l){return this._layers[l]}getLayersOrder(){return[...this._order]}hasLayer(l){return l in this._layers}setLayerZoomRange(l,m,T){this._checkLoaded();let P=this.getLayer(l);P?P.minzoom===m&&P.maxzoom===T||(m!=null&&(P.minzoom=m),T!=null&&(P.maxzoom=T),this._updateLayer(P)):this.fire(new o.k(new Error(`Cannot set the zoom range of non-existing layer "${l}".`)))}setFilter(l,m,T={}){this._checkLoaded();let P=this.getLayer(l);if(P){if(!o.bS(P.filter,m))return m==null?(P.setFilter(void 0),void this._updateLayer(P)):void(this._validate(o.C.filter,`layers.${P.id}.filter`,m,null,T)||(P.setFilter(o.bW(m)),this._updateLayer(P)))}else this.fire(new o.k(new Error(`Cannot filter non-existing layer "${l}".`)))}getFilter(l){return o.bW(this.getLayer(l).filter)}setLayoutProperty(l,m,T,P={}){this._checkLoaded();let V=this.getLayer(l);V?o.bS(V.getLayoutProperty(m),T)||(V.setLayoutProperty(m,T,P),this._updateLayer(V)):this.fire(new o.k(new Error(`Cannot style non-existing layer "${l}".`)))}getLayoutProperty(l,m){let T=this.getLayer(l);if(T)return T.getLayoutProperty(m);this.fire(new o.k(new Error(`Cannot get style of non-existing layer "${l}".`)))}setPaintProperty(l,m,T,P={}){this._checkLoaded();let V=this.getLayer(l);V?o.bS(V.getPaintProperty(m),T)||this._updatePaintProperty(V,m,T,P):this.fire(new o.k(new Error(`Cannot style non-existing layer "${l}".`)))}_updatePaintProperty(l,m,T,P={}){l.setPaintProperty(m,T,P)&&this._updateLayer(l),o.bV(l)&&m==="raster-fade-duration"&&this.tileManagers[l.source].setRasterFadeDuration(T),this._changed=!0,this._updatedPaintProps[l.id]=!0,this._serializedLayers=null}getPaintProperty(l,m){return this.getLayer(l).getPaintProperty(m)}setFeatureState(l,m){this._checkLoaded();let T=l.source,P=l.sourceLayer,V=this.tileManagers[T];if(V===void 0)return void this.fire(new o.k(new Error(`The source '${T}' does not exist in the map's style.`)));let W=V.getSource().type;W==="geojson"&&P?this.fire(new o.k(new Error("GeoJSON sources cannot have a sourceLayer parameter."))):W!=="vector"||P?(l.id===void 0&&this.fire(new o.k(new Error("The feature id parameter must be provided."))),V.setFeatureState(P,l.id,m)):this.fire(new o.k(new Error("The sourceLayer parameter must be provided for vector source types.")))}removeFeatureState(l,m){this._checkLoaded();let T=l.source,P=this.tileManagers[T];if(P===void 0)return void this.fire(new o.k(new Error(`The source '${T}' does not exist in the map's style.`)));let V=P.getSource().type,W=V==="vector"?l.sourceLayer:void 0;V!=="vector"||W?m&&typeof l.id!="string"&&typeof l.id!="number"?this.fire(new o.k(new Error("A feature id is required to remove its specific state property."))):P.removeFeatureState(W,l.id,m):this.fire(new o.k(new Error("The sourceLayer parameter must be provided for vector source types.")))}getFeatureState(l){this._checkLoaded();let m=l.source,T=l.sourceLayer,P=this.tileManagers[m];if(P!==void 0)return P.getSource().type!=="vector"||T?(l.id===void 0&&this.fire(new o.k(new Error("The feature id parameter must be provided."))),P.getFeatureState(T,l.id)):void this.fire(new o.k(new Error("The sourceLayer parameter must be provided for vector source types.")));this.fire(new o.k(new Error(`The source '${m}' does not exist in the map's style.`)))}getTransition(){return o.e({duration:300,delay:0},this.stylesheet&&this.stylesheet.transition)}serialize(){if(!this._loaded)return;let l=o.bZ(this.tileManagers,V=>V.serialize()),m=this._serializeByIds(this._order,!0),T=this.map.getTerrain()||void 0,P=this.stylesheet;return o.b_({version:P.version,name:P.name,metadata:P.metadata,light:P.light,sky:P.sky,center:P.center,zoom:P.zoom,bearing:P.bearing,pitch:P.pitch,sprite:P.sprite,glyphs:P.glyphs,transition:P.transition,projection:P.projection,sources:l,layers:m,terrain:T},V=>V!==void 0)}_updateLayer(l){this._updatedLayers[l.id]=!0,l.source&&!this._updatedSources[l.source]&&this.tileManagers[l.source].getSource().type!=="raster"&&(this._updatedSources[l.source]="reload",this.tileManagers[l.source].pause()),this._serializedLayers=null,this._changed=!0}_flattenAndSortRenderedFeatures(l){let m=W=>this._layers[W].type==="fill-extrusion",T={},P=[];for(let W=this._order.length-1;W>=0;W--){let te=this._order[W];if(m(te)){T[te]=W;for(let se of l){let fe=se[te];if(fe)for(let _e of fe)P.push(_e)}}}P.sort((W,te)=>te.intersectionZ-W.intersectionZ);let V=[];for(let W=this._order.length-1;W>=0;W--){let te=this._order[W];if(m(te))for(let se=P.length-1;se>=0;se--){let fe=P[se].feature;if(T[fe.layer.id]this.map.terrain.getElevation(_e,Re,Ie):void 0));return this.placement&&V.push(function(fe,_e,Re,Ie,je,tt,yt){let At={},vt=tt.queryRenderedSymbols(Ie),Lt=[];for(let Kt of Object.keys(vt).map(Number))Lt.push(yt[Kt]);Lt.sort(ln);for(let Kt of Lt){let Mt=Kt.featureIndex.lookupSymbolFeatures(vt[Kt.bucketInstanceId],_e,Kt.bucketIndex,Kt.sourceLayerIndex,{filterSpec:je.filter,globalState:je.globalState},je.layers,je.availableImages,fe);for(let Xt in Mt){let Rt=At[Xt]=At[Xt]||[],zt=Mt[Xt];zt.sort((jt,br)=>{let Cr=Kt.featureSortOrder;if(Cr){let Pr=Cr.indexOf(jt.featureIndex);return Cr.indexOf(br.featureIndex)-Pr}return br.featureIndex-jt.featureIndex});for(let jt of zt)Rt.push(jt)}}return function(Kt,Mt,Xt){for(let Rt in Kt)for(let zt of Kt[Rt])Nn(zt,Xt[Mt[Rt].source]);return Kt}(At,fe,Re)}(this._layers,W,this.tileManagers,l,se,this.placement.collisionIndex,this.placement.retainedQueryData)),this._flattenAndSortRenderedFeatures(V)}querySourceFeatures(l,m){m?.filter&&this._validate(o.C.filter,"querySourceFeatures.filter",m.filter,null,m);let T=this.tileManagers[l];return T?function(P,V){let W=P.getRenderableIds().map(fe=>P.getTileByID(fe)),te=[],se={};for(let fe=0;feIe.getTileByID(je)).sort((je,tt)=>tt.tileID.overscaledZ-je.tileID.overscaledZ||(je.tileID.isLessThan(tt.tileID)?-1:1))}let Re=this.crossTileSymbolIndex.addLayer(_e,se[_e.source],l.center.lng);W=W||Re}if(this.crossTileSymbolIndex.pruneUnusedLayers(this._order),((V=V||this._layerOrderChanged||T===0)||!this.pauseablePlacement||this.pauseablePlacement.isDone()&&!this.placement.stillRecent(k(),l.zoom))&&(this.pauseablePlacement=new Hh(l,this.map.terrain,this._order,V,m,T,P,this.placement),this._layerOrderChanged=!1),this.pauseablePlacement.isDone()?this.placement.setStale():(this.pauseablePlacement.continuePlacement(this._order,this._layers,se),this.pauseablePlacement.isDone()&&(this.placement=this.pauseablePlacement.commit(k()),te=!0),W&&this.pauseablePlacement.placement.setStale()),te||W)for(let fe of this._order){let _e=this._layers[fe];_e.type==="symbol"&&this.placement.updateLayerOpacities(_e,se[_e.source])}return!this.pauseablePlacement.isDone()||this.placement.hasTransitions(k())}_releaseSymbolFadeTiles(){for(let l in this.tileManagers)this.tileManagers[l].releaseSymbolFadeTiles()}getImages(l,m){return o._(this,void 0,void 0,function*(){let T=yield this.imageManager.getImages(m.icons);this._updateTilesForChangedImages();let P=this.tileManagers[m.source];return P&&P.setDependencies(m.tileID.key,m.type,m.icons),T})}getGlyphs(l,m){return o._(this,void 0,void 0,function*(){let T=yield this.glyphManager.getGlyphs(m.stacks),P=this.tileManagers[m.source];return P&&P.setDependencies(m.tileID.key,m.type,[""]),T})}getGlyphsUrl(){return this.stylesheet.glyphs||null}setGlyphs(l,m={}){this._checkLoaded(),l&&this._validate(o.C.glyphs,"glyphs",l,null,m)||(this._glyphsDidChange=!0,this.stylesheet.glyphs=l,this.glyphManager.entries={},this.glyphManager.setURL(l))}getDashes(l,m){return o._(this,void 0,void 0,function*(){let T={};for(let[P,V]of Object.entries(m.dashes))T[P]=this.lineAtlas.getDash(V.dasharray,V.round);return T})}addSprite(l,m,T={},P){this._checkLoaded();let V=[{id:l,url:m}],W=[...He(this.stylesheet.sprite),...V];this._validate(o.C.sprite,"sprite",W,null,T)||(this.stylesheet.sprite=W,this._loadSprite(V,!0,P))}removeSprite(l){this._checkLoaded();let m=He(this.stylesheet.sprite);if(m.find(T=>T.id===l)){if(this._spritesImagesIds[l])for(let T of this._spritesImagesIds[l])this.imageManager.removeImage(T),this._changedImages[T]=!0;m.splice(m.findIndex(T=>T.id===l),1),this.stylesheet.sprite=m.length>0?m:void 0,delete this._spritesImagesIds[l],this._availableImages=this.imageManager.listImages(),this._changed=!0,this.dispatcher.broadcast("SI",this._availableImages),this.fire(new o.l("data",{dataType:"style"}))}else this.fire(new o.k(new Error(`Sprite "${l}" doesn't exists on this map.`)))}getSprite(){return He(this.stylesheet.sprite)}setSprite(l,m={},T){this._checkLoaded(),l&&this._validate(o.C.sprite,"sprite",l,null,m)||(this.stylesheet.sprite=l,l?this._loadSprite(l,!0,T):(this._unloadSprite(),T&&T(null)))}destroy(){this._frameRequest&&(this._frameRequest.abort(),this._frameRequest=null),this._loadStyleRequest&&(this._loadStyleRequest.abort(),this._loadStyleRequest=null),this._spriteRequest&&(this._spriteRequest.abort(),this._spriteRequest=null);for(let l in this.tileManagers){let m=this.tileManagers[l];m.setEventedParent(null),m.onRemove(this.map)}this.tileManagers={},this.imageManager&&(this.imageManager.setEventedParent(null),this.imageManager.destroy(),this._availableImages=[],this._spritesImagesIds={}),this.glyphManager&&this.glyphManager.destroy();for(let l in this._layers){let m=this._layers[l];m.setEventedParent(null),m.onRemove&&m.onRemove(this.map)}this._setInitialValues(),this.setEventedParent(null),this.dispatcher.unregisterMessageHandler("GG"),this.dispatcher.unregisterMessageHandler("GI"),this.dispatcher.unregisterMessageHandler("GDA"),this.dispatcher.remove(!0),this._listeners={},this._oneTimeListeners={}}}var ao=o.aV([{name:"a_pos",type:"Int16",components:2},{name:"a_texture_pos",type:"Int16",components:2}]);class Ps{constructor(){this.boundProgram=null,this.boundLayoutVertexBuffer=null,this.boundPaintVertexBuffers=[],this.boundIndexBuffer=null,this.boundVertexOffset=null,this.boundDynamicVertexBuffer=null,this.vao=null}bind(l,m,T,P,V,W,te,se,fe){this.context=l;let _e=this.boundPaintVertexBuffers.length!==P.length;for(let Re=0;!_e&&Re({u_texture:0,u_ele_delta:z,u_fog_matrix:l,u_fog_color:m?m.properties.get("fog-color"):o.bq.white,u_fog_ground_blend:m?m.properties.get("fog-ground-blend"):1,u_fog_ground_blend_opacity:P?0:m?m.calculateFogBlendOpacity(T):0,u_horizon_color:m?m.properties.get("horizon-color"):o.bq.white,u_horizon_fog_blend:m?m.properties.get("horizon-fog-blend"):1,u_is_globe_mode:P?1:0}),Ho={mainMatrix:"u_projection_matrix",tileMercatorCoords:"u_projection_tile_mercator_coords",clippingPlane:"u_projection_clipping_plane",projectionTransition:"u_projection_transition",fallbackMatrix:"u_projection_fallback_matrix"};function Ks(z){let l=[];for(let m=0;m({u_depth:new o.b$(Cr,Pr.u_depth),u_terrain:new o.b$(Cr,Pr.u_terrain),u_terrain_dim:new o.br(Cr,Pr.u_terrain_dim),u_terrain_matrix:new o.c1(Cr,Pr.u_terrain_matrix),u_terrain_unpack:new o.c2(Cr,Pr.u_terrain_unpack),u_terrain_exaggeration:new o.br(Cr,Pr.u_terrain_exaggeration)}))(l,br),this.projectionUniforms=((Cr,Pr)=>({u_projection_matrix:new o.c1(Cr,Pr.u_projection_matrix),u_projection_tile_mercator_coords:new o.c2(Cr,Pr.u_projection_tile_mercator_coords),u_projection_clipping_plane:new o.c2(Cr,Pr.u_projection_clipping_plane),u_projection_transition:new o.br(Cr,Pr.u_projection_transition),u_projection_fallback_matrix:new o.c1(Cr,Pr.u_projection_fallback_matrix)}))(l,br),this.binderUniforms=T?T.getUniforms(l,br):[]}draw(l,m,T,P,V,W,te,se,fe,_e,Re,Ie,je,tt,yt,At,vt,Lt,Kt){let Mt=l.gl;if(this.failedToCreate)return;if(l.program.set(this.program),l.setDepthMode(T),l.setStencilMode(P),l.setColorMode(V),l.setCullFace(W),se){l.activeTexture.set(Mt.TEXTURE2),Mt.bindTexture(Mt.TEXTURE_2D,se.depthTexture),l.activeTexture.set(Mt.TEXTURE3),Mt.bindTexture(Mt.TEXTURE_2D,se.texture);for(let Rt in this.terrainUniforms)this.terrainUniforms[Rt].set(se[Rt])}if(fe)for(let Rt in fe)this.projectionUniforms[Ho[Rt]].set(fe[Rt]);if(te)for(let Rt in this.fixedUniforms)this.fixedUniforms[Rt].set(te[Rt]);At&&At.setUniforms(l,this.binderUniforms,tt,{zoom:yt});let Xt=0;switch(m){case Mt.LINES:Xt=2;break;case Mt.TRIANGLES:Xt=3;break;case Mt.LINE_STRIP:Xt=1}for(let Rt of je.get()){let zt=Rt.vaos||(Rt.vaos={});(zt[_e]||(zt[_e]=new Ps)).bind(l,this,Re,At?At.getPaintVertexBuffers():[],Ie,Rt.vertexOffset,vt,Lt,Kt),Mt.drawElements(m,Rt.primitiveLength*Xt,Mt.UNSIGNED_SHORT,Rt.primitiveOffset*Xt*2)}}}function Os(z,l,m){let T=1/o.aO(m,1,l.transform.tileZoom),P=Math.pow(2,m.tileID.overscaledZ),V=m.tileSize*Math.pow(2,l.transform.tileZoom)/P,W=V*(m.tileID.canonical.x+m.tileID.wrap*P),te=V*m.tileID.canonical.y;return{u_image:0,u_texsize:m.imageAtlasTexture.size,u_scale:[T,z.fromScale,z.toScale],u_fade:z.t,u_pixel_coord_upper:[W>>16,te>>16],u_pixel_coord_lower:[65535&W,65535&te]}}let gh=(z,l,m,T)=>{let P=z.style.light,V=P.properties.get("position"),W=[V.x,V.y,V.z],te=o.c5();P.properties.get("anchor")==="viewport"&&o.c6(te,z.transform.bearingInRadians),o.c7(W,W,te);let se=z.transform.transformLightDirection(W),fe=P.properties.get("color");return{u_lightpos:W,u_lightpos_globe:se,u_lightintensity:P.properties.get("intensity"),u_lightcolor:[fe.r,fe.g,fe.b],u_vertical_gradient:+l,u_opacity:m,u_fill_translate:T}},Ss=(z,l,m,T,P,V,W)=>o.e(gh(z,l,m,T),Os(V,z,W),{u_height_factor:-Math.pow(2,P.overscaledZ)/W.tileSize/8}),Ah=(z,l,m,T)=>o.e(Os(l,z,m),{u_fill_translate:T}),mc=(z,l)=>({u_world:z,u_fill_translate:l}),Qa=(z,l,m,T,P)=>o.e(Ah(z,l,m,P),{u_world:T}),Ru=(z,l,m,T,P)=>{let V=z.transform,W,te,se=0;if(m.paint.get("circle-pitch-alignment")==="map"){let fe=o.aO(l,1,V.zoom);W=!0,te=[fe,fe],se=fe/(o.a5*Math.pow(2,l.tileID.overscaledZ))*2*Math.PI*P}else W=!1,te=V.pixelsToGLUnits;return{u_camera_to_center_distance:V.cameraToCenterDistance,u_scale_with_map:+(m.paint.get("circle-pitch-scale")==="map"),u_pitch_with_map:+W,u_device_pixel_ratio:z.pixelRatio,u_extrude_scale:te,u_globe_extrude_scale:se,u_translate:T}},gc=z=>({u_pixel_extrude_scale:[1/z.width,1/z.height]}),_h=z=>({u_viewport_size:[z.width,z.height]}),Fl=(z,l=1)=>({u_color:z,u_overlay:0,u_overlay_scale:l}),Ac=(z,l,m,T)=>{let P=o.aO(z,1,l)/(o.a5*Math.pow(2,z.tileID.overscaledZ))*2*Math.PI*T;return{u_extrude_scale:o.aO(z,1,l),u_intensity:m,u_globe_extrude_scale:P}},qn=(z,l,m,T)=>{let P=o.N();o.c8(P,0,z.width,z.height,0,0,1);let V=z.context.gl;return{u_matrix:P,u_world:[V.drawingBufferWidth,V.drawingBufferHeight],u_image:m,u_color_ramp:T,u_opacity:l.paint.get("heatmap-opacity")}},dt=(z,l,m)=>{let T=m.paint.get("hillshade-accent-color"),P;switch(m.paint.get("hillshade-method")){case"basic":P=4;break;case"combined":P=1;break;case"igor":P=2;break;case"multidirectional":P=3;break;default:P=0}let V=m.getIlluminationProperties();for(let W=0;W{let m=l.stride,T=o.N();return o.c8(T,0,o.a5,-o.a5,0,0,1),o.O(T,T,[0,-o.a5,0]),{u_matrix:T,u_image:1,u_dimension:[m,m],u_zoom:z.overscaledZ,u_unpack:l.getUnpackVector()}};function Xh(z,l){let m=Math.pow(2,l.canonical.z),T=l.canonical.y;return[new o.ab(0,T/m).toLngLat().lat,new o.ab(0,(T+1)/m).toLngLat().lat]}let _f=(z,l,m=0)=>({u_image:0,u_unpack:l.getUnpackVector(),u_dimension:[l.stride,l.stride],u_elevation_stops:1,u_color_stops:4,u_color_ramp_size:m,u_opacity:z.paint.get("color-relief-opacity")}),Zh=(z,l,m,T)=>{let P=z.transform;return{u_translation:Wf(z,l,m),u_ratio:T/o.aO(l,1,P.zoom),u_device_pixel_ratio:z.pixelRatio,u_units_to_pixels:[1/P.pixelsToGLUnits[0],1/P.pixelsToGLUnits[1]]}},Wd=(z,l,m,T,P)=>o.e(Zh(z,l,m,T),{u_image:0,u_image_height:P}),Ad=(z,l,m,T,P)=>{let V=z.transform,W=Bu(l,V);return{u_translation:Wf(z,l,m),u_texsize:l.imageAtlasTexture.size,u_ratio:T/o.aO(l,1,V.zoom),u_device_pixel_ratio:z.pixelRatio,u_image:0,u_scale:[W,P.fromScale,P.toScale],u_fade:P.t,u_units_to_pixels:[1/V.pixelsToGLUnits[0],1/V.pixelsToGLUnits[1]]}},Je=(z,l,m,T,P)=>{let V=Bu(l,z.transform);return o.e(Zh(z,l,m,T),{u_tileratio:V,u_crossfade_from:P.fromScale,u_crossfade_to:P.toScale,u_image:0,u_mix:P.t,u_lineatlas_width:z.lineAtlas.width,u_lineatlas_height:z.lineAtlas.height})},yf=(z,l,m,T,P,V)=>{let W=Bu(l,z.transform);return o.e(Zh(z,l,m,T),{u_image:0,u_image_height:V,u_tileratio:W,u_crossfade_from:P.fromScale,u_crossfade_to:P.toScale,u_image_dash:1,u_mix:P.t,u_lineatlas_width:z.lineAtlas.width,u_lineatlas_height:z.lineAtlas.height})};function Bu(z,l){return 1/o.aO(z,1,l.tileZoom)}function Wf(z,l,m){return o.aP(z.transform,l,m.paint.get("line-translate"),m.paint.get("line-translate-anchor"))}let qf=(z,l,m,T,P)=>{return{u_tl_parent:z,u_scale_parent:l,u_buffer_scale:1,u_fade_t:m.mix,u_opacity:m.opacity*T.paint.get("raster-opacity"),u_image0:0,u_image1:1,u_brightness_low:T.paint.get("raster-brightness-min"),u_brightness_high:T.paint.get("raster-brightness-max"),u_saturation_factor:(W=T.paint.get("raster-saturation"),W>0?1-1/(1.001-W):-W),u_contrast_factor:(V=T.paint.get("raster-contrast"),V>0?1/(1-V):1+V),u_spin_weights:Bt(T.paint.get("raster-hue-rotate")),u_coords_top:[P[0].x,P[0].y,P[1].x,P[1].y],u_coords_bottom:[P[3].x,P[3].y,P[2].x,P[2].y]};var V,W};function Bt(z){z*=Math.PI/180;let l=Math.sin(z),m=Math.cos(z);return[(2*m+1)/3,(-Math.sqrt(3)*l-m+1)/3,(Math.sqrt(3)*l-m+1)/3]}let ar=(z,l,m,T,P,V,W,te,se,fe,_e,Re,Ie)=>{let je=W.transform;return{u_is_size_zoom_constant:+(z==="constant"||z==="source"),u_is_size_feature_constant:+(z==="constant"||z==="camera"),u_size_t:l?l.uSizeT:0,u_size:l?l.uSize:0,u_camera_to_center_distance:je.cameraToCenterDistance,u_pitch:je.pitch/360*2*Math.PI,u_rotate_symbol:+m,u_aspect_ratio:je.width/je.height,u_fade_change:W.options.fadeDuration?W.symbolFadeChange:1,u_label_plane_matrix:te,u_coord_matrix:se,u_is_text:+_e,u_pitch_with_map:+T,u_is_along_line:P,u_is_variable_anchor:V,u_texsize:Re,u_texture:0,u_translation:fe,u_pitched_scale:Ie}},Rr=(z,l,m,T,P,V,W,te,se,fe,_e,Re,Ie,je)=>{let tt=W.transform;return o.e(ar(z,l,m,T,P,V,W,te,se,fe,_e,Re,je),{u_gamma_scale:T?Math.cos(tt.pitch*Math.PI/180)*tt.cameraToCenterDistance:1,u_device_pixel_ratio:W.pixelRatio,u_is_halo:1})},Ur=(z,l,m,T,P,V,W,te,se,fe,_e,Re,Ie)=>o.e(Rr(z,l,m,T,P,V,W,te,se,fe,!0,_e,0,Ie),{u_texsize_icon:Re,u_texture_icon:1}),ci=(z,l)=>({u_opacity:z,u_color:l}),Li=(z,l,m,T,P)=>o.e(function(V,W,te,se){let fe=te.imageManager.getPattern(V.from.toString()),_e=te.imageManager.getPattern(V.to.toString()),{width:Re,height:Ie}=te.imageManager.getPixelSize(),je=Math.pow(2,se.tileID.overscaledZ),tt=se.tileSize*Math.pow(2,te.transform.tileZoom)/je,yt=tt*(se.tileID.canonical.x+se.tileID.wrap*je),At=tt*se.tileID.canonical.y;return{u_image:0,u_pattern_tl_a:fe.tl,u_pattern_br_a:fe.br,u_pattern_tl_b:_e.tl,u_pattern_br_b:_e.br,u_texsize:[Re,Ie],u_mix:W.t,u_pattern_size_a:fe.displaySize,u_pattern_size_b:_e.displaySize,u_scale_a:W.fromScale,u_scale_b:W.toScale,u_tile_units_to_pixels:1/o.aO(se,1,te.transform.tileZoom),u_pixel_coord_upper:[yt>>16,At>>16],u_pixel_coord_lower:[65535&yt,65535&At]}}(m,P,l,T),{u_opacity:z}),yi=(z,l)=>{},Pn={fillExtrusion:(z,l)=>({u_lightpos:new o.c3(z,l.u_lightpos),u_lightpos_globe:new o.c3(z,l.u_lightpos_globe),u_lightintensity:new o.br(z,l.u_lightintensity),u_lightcolor:new o.c3(z,l.u_lightcolor),u_vertical_gradient:new o.br(z,l.u_vertical_gradient),u_opacity:new o.br(z,l.u_opacity),u_fill_translate:new o.c4(z,l.u_fill_translate)}),fillExtrusionPattern:(z,l)=>({u_lightpos:new o.c3(z,l.u_lightpos),u_lightpos_globe:new o.c3(z,l.u_lightpos_globe),u_lightintensity:new o.br(z,l.u_lightintensity),u_lightcolor:new o.c3(z,l.u_lightcolor),u_vertical_gradient:new o.br(z,l.u_vertical_gradient),u_height_factor:new o.br(z,l.u_height_factor),u_opacity:new o.br(z,l.u_opacity),u_fill_translate:new o.c4(z,l.u_fill_translate),u_image:new o.b$(z,l.u_image),u_texsize:new o.c4(z,l.u_texsize),u_pixel_coord_upper:new o.c4(z,l.u_pixel_coord_upper),u_pixel_coord_lower:new o.c4(z,l.u_pixel_coord_lower),u_scale:new o.c3(z,l.u_scale),u_fade:new o.br(z,l.u_fade)}),fill:(z,l)=>({u_fill_translate:new o.c4(z,l.u_fill_translate)}),fillPattern:(z,l)=>({u_image:new o.b$(z,l.u_image),u_texsize:new o.c4(z,l.u_texsize),u_pixel_coord_upper:new o.c4(z,l.u_pixel_coord_upper),u_pixel_coord_lower:new o.c4(z,l.u_pixel_coord_lower),u_scale:new o.c3(z,l.u_scale),u_fade:new o.br(z,l.u_fade),u_fill_translate:new o.c4(z,l.u_fill_translate)}),fillOutline:(z,l)=>({u_world:new o.c4(z,l.u_world),u_fill_translate:new o.c4(z,l.u_fill_translate)}),fillOutlinePattern:(z,l)=>({u_world:new o.c4(z,l.u_world),u_image:new o.b$(z,l.u_image),u_texsize:new o.c4(z,l.u_texsize),u_pixel_coord_upper:new o.c4(z,l.u_pixel_coord_upper),u_pixel_coord_lower:new o.c4(z,l.u_pixel_coord_lower),u_scale:new o.c3(z,l.u_scale),u_fade:new o.br(z,l.u_fade),u_fill_translate:new o.c4(z,l.u_fill_translate)}),circle:(z,l)=>({u_camera_to_center_distance:new o.br(z,l.u_camera_to_center_distance),u_scale_with_map:new o.b$(z,l.u_scale_with_map),u_pitch_with_map:new o.b$(z,l.u_pitch_with_map),u_extrude_scale:new o.c4(z,l.u_extrude_scale),u_device_pixel_ratio:new o.br(z,l.u_device_pixel_ratio),u_globe_extrude_scale:new o.br(z,l.u_globe_extrude_scale),u_translate:new o.c4(z,l.u_translate)}),collisionBox:(z,l)=>({u_pixel_extrude_scale:new o.c4(z,l.u_pixel_extrude_scale)}),collisionCircle:(z,l)=>({u_viewport_size:new o.c4(z,l.u_viewport_size)}),debug:(z,l)=>({u_color:new o.c0(z,l.u_color),u_overlay:new o.b$(z,l.u_overlay),u_overlay_scale:new o.br(z,l.u_overlay_scale)}),depth:yi,clippingMask:yi,heatmap:(z,l)=>({u_extrude_scale:new o.br(z,l.u_extrude_scale),u_intensity:new o.br(z,l.u_intensity),u_globe_extrude_scale:new o.br(z,l.u_globe_extrude_scale)}),heatmapTexture:(z,l)=>({u_matrix:new o.c1(z,l.u_matrix),u_world:new o.c4(z,l.u_world),u_image:new o.b$(z,l.u_image),u_color_ramp:new o.b$(z,l.u_color_ramp),u_opacity:new o.br(z,l.u_opacity)}),hillshade:(z,l)=>({u_image:new o.b$(z,l.u_image),u_latrange:new o.c4(z,l.u_latrange),u_exaggeration:new o.br(z,l.u_exaggeration),u_altitudes:new o.ca(z,l.u_altitudes),u_azimuths:new o.ca(z,l.u_azimuths),u_accent:new o.c0(z,l.u_accent),u_method:new o.b$(z,l.u_method),u_shadows:new o.c9(z,l.u_shadows),u_highlights:new o.c9(z,l.u_highlights)}),hillshadePrepare:(z,l)=>({u_matrix:new o.c1(z,l.u_matrix),u_image:new o.b$(z,l.u_image),u_dimension:new o.c4(z,l.u_dimension),u_zoom:new o.br(z,l.u_zoom),u_unpack:new o.c2(z,l.u_unpack)}),colorRelief:(z,l)=>({u_image:new o.b$(z,l.u_image),u_unpack:new o.c2(z,l.u_unpack),u_dimension:new o.c4(z,l.u_dimension),u_elevation_stops:new o.b$(z,l.u_elevation_stops),u_color_stops:new o.b$(z,l.u_color_stops),u_color_ramp_size:new o.b$(z,l.u_color_ramp_size),u_opacity:new o.br(z,l.u_opacity)}),line:(z,l)=>({u_translation:new o.c4(z,l.u_translation),u_ratio:new o.br(z,l.u_ratio),u_device_pixel_ratio:new o.br(z,l.u_device_pixel_ratio),u_units_to_pixels:new o.c4(z,l.u_units_to_pixels)}),lineGradient:(z,l)=>({u_translation:new o.c4(z,l.u_translation),u_ratio:new o.br(z,l.u_ratio),u_device_pixel_ratio:new o.br(z,l.u_device_pixel_ratio),u_units_to_pixels:new o.c4(z,l.u_units_to_pixels),u_image:new o.b$(z,l.u_image),u_image_height:new o.br(z,l.u_image_height)}),linePattern:(z,l)=>({u_translation:new o.c4(z,l.u_translation),u_texsize:new o.c4(z,l.u_texsize),u_ratio:new o.br(z,l.u_ratio),u_device_pixel_ratio:new o.br(z,l.u_device_pixel_ratio),u_image:new o.b$(z,l.u_image),u_units_to_pixels:new o.c4(z,l.u_units_to_pixels),u_scale:new o.c3(z,l.u_scale),u_fade:new o.br(z,l.u_fade)}),lineSDF:(z,l)=>({u_translation:new o.c4(z,l.u_translation),u_ratio:new o.br(z,l.u_ratio),u_device_pixel_ratio:new o.br(z,l.u_device_pixel_ratio),u_units_to_pixels:new o.c4(z,l.u_units_to_pixels),u_image:new o.b$(z,l.u_image),u_mix:new o.br(z,l.u_mix),u_tileratio:new o.br(z,l.u_tileratio),u_crossfade_from:new o.br(z,l.u_crossfade_from),u_crossfade_to:new o.br(z,l.u_crossfade_to),u_lineatlas_width:new o.br(z,l.u_lineatlas_width),u_lineatlas_height:new o.br(z,l.u_lineatlas_height)}),lineGradientSDF:(z,l)=>({u_translation:new o.c4(z,l.u_translation),u_ratio:new o.br(z,l.u_ratio),u_device_pixel_ratio:new o.br(z,l.u_device_pixel_ratio),u_units_to_pixels:new o.c4(z,l.u_units_to_pixels),u_image:new o.b$(z,l.u_image),u_image_height:new o.br(z,l.u_image_height),u_tileratio:new o.br(z,l.u_tileratio),u_crossfade_from:new o.br(z,l.u_crossfade_from),u_crossfade_to:new o.br(z,l.u_crossfade_to),u_image_dash:new o.b$(z,l.u_image_dash),u_mix:new o.br(z,l.u_mix),u_lineatlas_width:new o.br(z,l.u_lineatlas_width),u_lineatlas_height:new o.br(z,l.u_lineatlas_height)}),raster:(z,l)=>({u_tl_parent:new o.c4(z,l.u_tl_parent),u_scale_parent:new o.br(z,l.u_scale_parent),u_buffer_scale:new o.br(z,l.u_buffer_scale),u_fade_t:new o.br(z,l.u_fade_t),u_opacity:new o.br(z,l.u_opacity),u_image0:new o.b$(z,l.u_image0),u_image1:new o.b$(z,l.u_image1),u_brightness_low:new o.br(z,l.u_brightness_low),u_brightness_high:new o.br(z,l.u_brightness_high),u_saturation_factor:new o.br(z,l.u_saturation_factor),u_contrast_factor:new o.br(z,l.u_contrast_factor),u_spin_weights:new o.c3(z,l.u_spin_weights),u_coords_top:new o.c2(z,l.u_coords_top),u_coords_bottom:new o.c2(z,l.u_coords_bottom)}),symbolIcon:(z,l)=>({u_is_size_zoom_constant:new o.b$(z,l.u_is_size_zoom_constant),u_is_size_feature_constant:new o.b$(z,l.u_is_size_feature_constant),u_size_t:new o.br(z,l.u_size_t),u_size:new o.br(z,l.u_size),u_camera_to_center_distance:new o.br(z,l.u_camera_to_center_distance),u_pitch:new o.br(z,l.u_pitch),u_rotate_symbol:new o.b$(z,l.u_rotate_symbol),u_aspect_ratio:new o.br(z,l.u_aspect_ratio),u_fade_change:new o.br(z,l.u_fade_change),u_label_plane_matrix:new o.c1(z,l.u_label_plane_matrix),u_coord_matrix:new o.c1(z,l.u_coord_matrix),u_is_text:new o.b$(z,l.u_is_text),u_pitch_with_map:new o.b$(z,l.u_pitch_with_map),u_is_along_line:new o.b$(z,l.u_is_along_line),u_is_variable_anchor:new o.b$(z,l.u_is_variable_anchor),u_texsize:new o.c4(z,l.u_texsize),u_texture:new o.b$(z,l.u_texture),u_translation:new o.c4(z,l.u_translation),u_pitched_scale:new o.br(z,l.u_pitched_scale)}),symbolSDF:(z,l)=>({u_is_size_zoom_constant:new o.b$(z,l.u_is_size_zoom_constant),u_is_size_feature_constant:new o.b$(z,l.u_is_size_feature_constant),u_size_t:new o.br(z,l.u_size_t),u_size:new o.br(z,l.u_size),u_camera_to_center_distance:new o.br(z,l.u_camera_to_center_distance),u_pitch:new o.br(z,l.u_pitch),u_rotate_symbol:new o.b$(z,l.u_rotate_symbol),u_aspect_ratio:new o.br(z,l.u_aspect_ratio),u_fade_change:new o.br(z,l.u_fade_change),u_label_plane_matrix:new o.c1(z,l.u_label_plane_matrix),u_coord_matrix:new o.c1(z,l.u_coord_matrix),u_is_text:new o.b$(z,l.u_is_text),u_pitch_with_map:new o.b$(z,l.u_pitch_with_map),u_is_along_line:new o.b$(z,l.u_is_along_line),u_is_variable_anchor:new o.b$(z,l.u_is_variable_anchor),u_texsize:new o.c4(z,l.u_texsize),u_texture:new o.b$(z,l.u_texture),u_gamma_scale:new o.br(z,l.u_gamma_scale),u_device_pixel_ratio:new o.br(z,l.u_device_pixel_ratio),u_is_halo:new o.b$(z,l.u_is_halo),u_translation:new o.c4(z,l.u_translation),u_pitched_scale:new o.br(z,l.u_pitched_scale)}),symbolTextAndIcon:(z,l)=>({u_is_size_zoom_constant:new o.b$(z,l.u_is_size_zoom_constant),u_is_size_feature_constant:new o.b$(z,l.u_is_size_feature_constant),u_size_t:new o.br(z,l.u_size_t),u_size:new o.br(z,l.u_size),u_camera_to_center_distance:new o.br(z,l.u_camera_to_center_distance),u_pitch:new o.br(z,l.u_pitch),u_rotate_symbol:new o.b$(z,l.u_rotate_symbol),u_aspect_ratio:new o.br(z,l.u_aspect_ratio),u_fade_change:new o.br(z,l.u_fade_change),u_label_plane_matrix:new o.c1(z,l.u_label_plane_matrix),u_coord_matrix:new o.c1(z,l.u_coord_matrix),u_is_text:new o.b$(z,l.u_is_text),u_pitch_with_map:new o.b$(z,l.u_pitch_with_map),u_is_along_line:new o.b$(z,l.u_is_along_line),u_is_variable_anchor:new o.b$(z,l.u_is_variable_anchor),u_texsize:new o.c4(z,l.u_texsize),u_texsize_icon:new o.c4(z,l.u_texsize_icon),u_texture:new o.b$(z,l.u_texture),u_texture_icon:new o.b$(z,l.u_texture_icon),u_gamma_scale:new o.br(z,l.u_gamma_scale),u_device_pixel_ratio:new o.br(z,l.u_device_pixel_ratio),u_is_halo:new o.b$(z,l.u_is_halo),u_translation:new o.c4(z,l.u_translation),u_pitched_scale:new o.br(z,l.u_pitched_scale)}),background:(z,l)=>({u_opacity:new o.br(z,l.u_opacity),u_color:new o.c0(z,l.u_color)}),backgroundPattern:(z,l)=>({u_opacity:new o.br(z,l.u_opacity),u_image:new o.b$(z,l.u_image),u_pattern_tl_a:new o.c4(z,l.u_pattern_tl_a),u_pattern_br_a:new o.c4(z,l.u_pattern_br_a),u_pattern_tl_b:new o.c4(z,l.u_pattern_tl_b),u_pattern_br_b:new o.c4(z,l.u_pattern_br_b),u_texsize:new o.c4(z,l.u_texsize),u_mix:new o.br(z,l.u_mix),u_pattern_size_a:new o.c4(z,l.u_pattern_size_a),u_pattern_size_b:new o.c4(z,l.u_pattern_size_b),u_scale_a:new o.br(z,l.u_scale_a),u_scale_b:new o.br(z,l.u_scale_b),u_pixel_coord_upper:new o.c4(z,l.u_pixel_coord_upper),u_pixel_coord_lower:new o.c4(z,l.u_pixel_coord_lower),u_tile_units_to_pixels:new o.br(z,l.u_tile_units_to_pixels)}),terrain:(z,l)=>({u_texture:new o.b$(z,l.u_texture),u_ele_delta:new o.br(z,l.u_ele_delta),u_fog_matrix:new o.c1(z,l.u_fog_matrix),u_fog_color:new o.c0(z,l.u_fog_color),u_fog_ground_blend:new o.br(z,l.u_fog_ground_blend),u_fog_ground_blend_opacity:new o.br(z,l.u_fog_ground_blend_opacity),u_horizon_color:new o.c0(z,l.u_horizon_color),u_horizon_fog_blend:new o.br(z,l.u_horizon_fog_blend),u_is_globe_mode:new o.br(z,l.u_is_globe_mode)}),terrainDepth:(z,l)=>({u_ele_delta:new o.br(z,l.u_ele_delta)}),terrainCoords:(z,l)=>({u_texture:new o.b$(z,l.u_texture),u_terrain_coords_id:new o.br(z,l.u_terrain_coords_id),u_ele_delta:new o.br(z,l.u_ele_delta)}),projectionErrorMeasurement:(z,l)=>({u_input:new o.br(z,l.u_input),u_output_expected:new o.br(z,l.u_output_expected)}),atmosphere:(z,l)=>({u_sun_pos:new o.c3(z,l.u_sun_pos),u_atmosphere_blend:new o.br(z,l.u_atmosphere_blend),u_globe_position:new o.c3(z,l.u_globe_position),u_globe_radius:new o.br(z,l.u_globe_radius),u_inv_proj_matrix:new o.c1(z,l.u_inv_proj_matrix)}),sky:(z,l)=>({u_sky_color:new o.c0(z,l.u_sky_color),u_horizon_color:new o.c0(z,l.u_horizon_color),u_horizon:new o.c4(z,l.u_horizon),u_horizon_normal:new o.c4(z,l.u_horizon_normal),u_sky_horizon_blend:new o.br(z,l.u_sky_horizon_blend),u_sky_blend:new o.br(z,l.u_sky_blend)})};class sn{constructor(l,m,T){this.context=l;let P=l.gl;this.buffer=P.createBuffer(),this.dynamicDraw=Boolean(T),this.context.unbindVAO(),l.bindElementBuffer.set(this.buffer),P.bufferData(P.ELEMENT_ARRAY_BUFFER,m.arrayBuffer,this.dynamicDraw?P.DYNAMIC_DRAW:P.STATIC_DRAW),this.dynamicDraw||delete m.arrayBuffer}bind(){this.context.bindElementBuffer.set(this.buffer)}updateData(l){let m=this.context.gl;if(!this.dynamicDraw)throw new Error("Attempted to update data while not in dynamic mode.");this.context.unbindVAO(),this.bind(),m.bufferSubData(m.ELEMENT_ARRAY_BUFFER,0,l.arrayBuffer)}destroy(){this.buffer&&(this.context.gl.deleteBuffer(this.buffer),delete this.buffer)}}let To={Int8:"BYTE",Uint8:"UNSIGNED_BYTE",Int16:"SHORT",Uint16:"UNSIGNED_SHORT",Int32:"INT",Uint32:"UNSIGNED_INT",Float32:"FLOAT"};class Gi{constructor(l,m,T,P){this.length=m.length,this.attributes=T,this.itemSize=m.bytesPerElement,this.dynamicDraw=P,this.context=l;let V=l.gl;this.buffer=V.createBuffer(),l.bindVertexBuffer.set(this.buffer),V.bufferData(V.ARRAY_BUFFER,m.arrayBuffer,this.dynamicDraw?V.DYNAMIC_DRAW:V.STATIC_DRAW),this.dynamicDraw||delete m.arrayBuffer}bind(){this.context.bindVertexBuffer.set(this.buffer)}updateData(l){if(l.length!==this.length)throw new Error(`Length of new data is ${l.length}, which doesn't match current length of ${this.length}`);let m=this.context.gl;this.bind(),m.bufferSubData(m.ARRAY_BUFFER,0,l.arrayBuffer)}enableAttributes(l,m){for(let T=0;T0&&(fe.push({circleArray:Xt,circleOffset:Re,coord:Lt}),_e+=Xt.length/4,Re=_e),Mt&&se.draw(V,te.LINES,Ao.disabled,us.disabled,z.colorModeForRenderPass(),ms.disabled,gc(z.transform),z.style.map.terrain&&z.style.map.terrain.getTerrainData(Lt),W.getProjectionData({overscaledTileID:Lt,applyGlobeMatrix:!0,applyTerrainMatrix:!0}),m.id,Mt.layoutVertexBuffer,Mt.indexBuffer,Mt.segments,null,z.transform.zoom,null,null,Mt.collisionVertexBuffer)}if(!P||!fe.length)return;let Ie=z.useProgram("collisionCircle"),je=new o.cb;je.resize(4*_e),je._trim();let tt=0;for(let vt of fe)for(let Lt=0;Lt=0&&(yt[vt.associatedIconIndex]={shiftedAnchor:hi,angle:hn})}else Ya(vt.numGlyphs,je)}if(se){tt.clear();let At=z.icon.placedSymbolArray;for(let vt=0;vtz.style.map.terrain.getElevation(Cr,St,er):null,Tt=m.layout.get("text-rotation-alignment")==="map";Ls(Ir,z,P,v,x,vt,fe,Tt,Cr.toUnwrapped(),yt.width,yt.height,B,bt)}let Q=P&&zt||X,ue=Lt||Q?xf:vt?v:z.transform.clipSpaceToPixelsMatrix,Ae=hi&&m.paint.get(P?"text-halo-width":"icon-halo-width").constantOr(1)!==0,Te;Te=hi?Ir.iconsInText?Ur(hn.kind,di,Kt,vt,Lt,Q,z,ue,E,B,Ja,Ha,br):Rr(hn.kind,di,Kt,vt,Lt,Q,z,ue,E,B,P,Ja,0,br):ar(hn.kind,di,Kt,vt,Lt,Q,z,ue,E,B,P,Ja,br);let at={program:$r,buffers:Ai,uniformValues:Te,projectionData:N,atlasTexture:ya,atlasTextureIcon:Gl,atlasInterpolation:jl,atlasInterpolationIcon:qs,isSDF:hi,hasHalo:Ae};if(Mt&&Ir.canOverlap){Xt=!0;let bt=Ai.segments.get();for(let Tt of bt)jt.push({segments:new o.aY([Tt]),sortKey:Tt.sortKey,state:at,terrainData:na})}else jt.push({segments:Ai.segments,sortKey:0,state:at,terrainData:na})}Xt&&jt.sort((Cr,Pr)=>Cr.sortKey-Pr.sortKey);for(let Cr of jt){let Pr=Cr.state;if(je.activeTexture.set(tt.TEXTURE0),Pr.atlasTexture.bind(Pr.atlasInterpolation,tt.CLAMP_TO_EDGE),Pr.atlasTextureIcon&&(je.activeTexture.set(tt.TEXTURE1),Pr.atlasTextureIcon&&Pr.atlasTextureIcon.bind(Pr.atlasInterpolationIcon,tt.CLAMP_TO_EDGE)),Pr.isSDF){let Ir=Pr.uniformValues;Pr.hasHalo&&(Ir.u_is_halo=1,nn(Pr.buffers,Cr.segments,m,z,Pr.program,Rt,_e,Re,Ir,Pr.projectionData,Cr.terrainData)),Ir.u_is_halo=0}nn(Pr.buffers,Cr.segments,m,z,Pr.program,Rt,_e,Re,Pr.uniformValues,Pr.projectionData,Cr.terrainData)}}function nn(z,l,m,T,P,V,W,te,se,fe,_e){let Re=T.context;P.draw(Re,Re.gl.TRIANGLES,V,W,te,ms.backCCW,se,_e,fe,m.id,z.layoutVertexBuffer,z.indexBuffer,l,m.paint,T.transform.zoom,z.programConfigurations.get(m.id),z.dynamicLayoutVertexBuffer,z.opacityVertexBuffer)}function Xd(z,l,m,T,P){let V=z.context,W=V.gl,te=us.disabled,se=new ra([W.ONE,W.ONE],o.bq.transparent,[!0,!0,!0,!0]),fe=l.getBucket(m);if(!fe)return;let _e=T.key,Re=m.heatmapFbos.get(_e);Re||(Re=Au(V,l.tileSize,l.tileSize),m.heatmapFbos.set(_e,Re)),V.bindFramebuffer.set(Re.framebuffer),V.viewport.set([0,0,l.tileSize,l.tileSize]),V.clear({color:o.bq.transparent});let Ie=fe.programConfigurations.get(m.id),je=z.useProgram("heatmap",Ie,!P),tt=z.transform.getProjectionData({overscaledTileID:l.tileID,applyGlobeMatrix:!0,applyTerrainMatrix:!0}),yt=z.style.map.terrain.getTerrainData(T);je.draw(V,W.TRIANGLES,Ao.disabled,te,se,ms.disabled,Ac(l,z.transform.zoom,m.paint.get("heatmap-intensity"),1),yt,tt,m.id,fe.layoutVertexBuffer,fe.indexBuffer,fe.segments,m.paint,z.transform.zoom,Ie)}function Vc(z,l,m,T,P){let V=z.context,W=V.gl,te=z.transform;V.setColorMode(z.colorModeForRenderPass());let se=Cl(V,l),fe=m.key,_e=l.heatmapFbos.get(fe);if(!_e)return;V.activeTexture.set(W.TEXTURE0),W.bindTexture(W.TEXTURE_2D,_e.colorAttachment.get()),V.activeTexture.set(W.TEXTURE1),se.bind(W.LINEAR,W.CLAMP_TO_EDGE);let Re=te.getProjectionData({overscaledTileID:m,applyTerrainMatrix:P,applyGlobeMatrix:!T});z.useProgram("heatmapTexture").draw(V,W.TRIANGLES,Ao.disabled,us.disabled,z.colorModeForRenderPass(),ms.disabled,qn(z,l,0,1),null,Re,l.id,z.rasterBoundsBuffer,z.quadTriangleIndexBuffer,z.rasterBoundsSegments,l.paint,te.zoom),_e.destroy(),l.heatmapFbos.delete(fe)}function Au(z,l,m){var T,P;let V=z.gl,W=V.createTexture();V.bindTexture(V.TEXTURE_2D,W),V.texParameteri(V.TEXTURE_2D,V.TEXTURE_WRAP_S,V.CLAMP_TO_EDGE),V.texParameteri(V.TEXTURE_2D,V.TEXTURE_WRAP_T,V.CLAMP_TO_EDGE),V.texParameteri(V.TEXTURE_2D,V.TEXTURE_MIN_FILTER,V.LINEAR),V.texParameteri(V.TEXTURE_2D,V.TEXTURE_MAG_FILTER,V.LINEAR);let te=(T=z.HALF_FLOAT)!==null&&T!==void 0?T:V.UNSIGNED_BYTE,se=(P=z.RGBA16F)!==null&&P!==void 0?P:V.RGBA;V.texImage2D(V.TEXTURE_2D,0,se,l,m,0,V.RGBA,te,null);let fe=z.createFramebuffer(l,m,!1,!1);return fe.colorAttachment.set(W),fe}function Cl(z,l){return l.colorRampTexture||(l.colorRampTexture=new o.T(z,l.colorRamp,z.gl.RGBA)),l.colorRampTexture}function Yh(z,l,m,T,P,V,W,te){let se=256;if(P.stepInterpolant){let fe=l.getSource().maxzoom,_e=W.canonical.z===fe?Math.ceil(1<20&&Ie.texParameterf(Ie.TEXTURE_2D,Re.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT,Re.extTextureFilterAnisotropicMax);let _i=z.style.map.terrain&&z.style.map.terrain.getTerrainData(Rt),hi=tt.getProjectionData({overscaledTileID:Rt,aligned:vt,applyGlobeMatrix:!fe,applyTerrainMatrix:!0}),hn=qf(Ir,Pr,Ai.fadeMix,m,te),fo=yt.getMeshFromTileID(Re,Rt.canonical,V,W,"raster");je.draw(Re,Ie.TRIANGLES,zt,P?P[Rt.overscaledZ]:us.disabled,At,se?ms.frontCCW:ms.backCCW,hn,_i,hi,m.id,fo.vertexBuffer,fo.indexBuffer,fo.segments)}}function Zf(z,l,m,T){let P={parentTile:null,parentScaleBy:1,parentTopLeft:[0,0],fadeValues:{tileOpacity:1,parentTileOpacity:1,fadeMix:{opacity:1,mix:0}}};if(m===0||T)return P;if(z.fadingParentID){let V=l.getLoadedTile(z.fadingParentID);if(!V)return P;let W=Math.pow(2,V.tileID.overscaledZ-z.tileID.overscaledZ),te=[z.tileID.canonical.x*W%1,z.tileID.canonical.y*W%1],se=function(fe,_e,Re){let Ie=k(),je=(Ie-_e.timeAdded)/Re,tt=fe.fadingDirection===xr.Incoming,yt=o.ao((Ie-fe.timeAdded)/Re,0,1),At=o.ao(1-je,0,1),vt=tt?yt:At;return{tileOpacity:vt,parentTileOpacity:tt?At:yt,fadeMix:{opacity:1,mix:1-vt}}}(z,V,m);return{parentTile:V,parentScaleBy:W,parentTopLeft:te,fadeValues:se}}if(z.selfFading){let V=function(W,te){let se=(k()-W.timeAdded)/te,fe=o.ao(se,0,1);return{tileOpacity:fe,fadeMix:{opacity:fe,mix:0}}}(z,m);return{parentTile:null,parentScaleBy:1,parentTopLeft:[0,0],fadeValues:V}}return P}let Zd=new o.bq(1,0,0,1),xd=new o.bq(0,1,0,1),mm=new o.bq(0,0,1,1),xh=new o.bq(1,0,1,1),Qu=new o.bq(0,1,1,1);function Yd(z,l,m,T){bh(z,0,l+m/2,z.transform.width,m,T)}function Qh(z,l,m,T){bh(z,l-m/2,0,m,z.transform.height,T)}function bh(z,l,m,T,P,V){let W=z.context,te=W.gl;te.enable(te.SCISSOR_TEST),te.scissor(l*z.pixelRatio,m*z.pixelRatio,T*z.pixelRatio,P*z.pixelRatio),W.clear({color:V}),te.disable(te.SCISSOR_TEST)}function Rp(z,l,m){let T=z.context,P=T.gl,V=z.useProgram("debug"),W=Ao.disabled,te=us.disabled,se=z.colorModeForRenderPass(),fe="$debug",_e=z.style.map.terrain&&z.style.map.terrain.getTerrainData(m);T.activeTexture.set(P.TEXTURE0);let Re=l.getTileByID(m.key).latestRawTileData,Ie=Math.floor((Re&&Re.byteLength||0)/1024),je=l.getTile(m).tileSize,tt=512/Math.min(je,512)*(m.overscaledZ/z.transform.zoom)*.5,yt=m.canonical.toString();m.overscaledZ!==m.canonical.z&&(yt+=` => ${m.overscaledZ}`),function(vt,Lt){vt.initDebugOverlayCanvas();let Kt=vt.debugOverlayCanvas,Mt=vt.context.gl,Xt=vt.debugOverlayCanvas.getContext("2d");Xt.clearRect(0,0,Kt.width,Kt.height),Xt.shadowColor="white",Xt.shadowBlur=2,Xt.lineWidth=1.5,Xt.strokeStyle="white",Xt.textBaseline="top",Xt.font="bold 36px Open Sans, sans-serif",Xt.fillText(Lt,5,5),Xt.strokeText(Lt,5,5),vt.debugOverlayTexture.update(Kt),vt.debugOverlayTexture.bind(Mt.LINEAR,Mt.CLAMP_TO_EDGE)}(z,`${yt} ${Ie}kB`);let At=z.transform.getProjectionData({overscaledTileID:m,applyGlobeMatrix:!0,applyTerrainMatrix:!0});V.draw(T,P.TRIANGLES,W,te,ra.alphaBlended,ms.disabled,Fl(o.bq.transparent,tt),null,At,fe,z.debugBuffer,z.quadTriangleIndexBuffer,z.debugSegments),V.draw(T,P.LINE_STRIP,W,te,se,ms.disabled,Fl(o.bq.red),_e,At,fe,z.debugBuffer,z.tileBorderIndexBuffer,z.debugSegments)}function bf(z,l,m,T){let{isRenderingGlobe:P}=T,V=z.context,W=V.gl,te=z.transform,se=z.colorModeForRenderPass(),fe=z.getDepthModeFor3D(),_e=z.useProgram("terrain");V.bindFramebuffer.set(null),V.viewport.set([0,0,z.width,z.height]);for(let Re of m){let Ie=l.getTerrainMesh(Re.tileID),je=z.renderToTexture.getTexture(Re),tt=l.getTerrainData(Re.tileID);V.activeTexture.set(W.TEXTURE0),W.bindTexture(W.TEXTURE_2D,je.texture);let yt=l.getMeshFrameDelta(te.zoom),At=te.calculateFogMatrix(Re.tileID.toUnwrapped()),vt=Rs(yt,At,z.style.sky,te.pitch,P),Lt=te.getProjectionData({overscaledTileID:Re.tileID,applyTerrainMatrix:!1,applyGlobeMatrix:!0});_e.draw(V,W.TRIANGLES,fe,us.disabled,se,ms.backCCW,vt,tt,Lt,"terrain",Ie.vertexBuffer,Ie.indexBuffer,Ie.segments)}}function Du(z,l){if(!l.mesh){let m=new o.aX;m.emplaceBack(-1,-1),m.emplaceBack(1,-1),m.emplaceBack(1,1),m.emplaceBack(-1,1);let T=new o.aZ;T.emplaceBack(0,1,2),T.emplaceBack(0,2,3),l.mesh=new Hs(z.createVertexBuffer(m,ec.members),z.createIndexBuffer(T),o.aY.simpleSegment(0,0,m.length,T.length))}return l.mesh}class Bp{constructor(l,m){this.context=new Lu(l),this.transform=m,this._tileTextures={},this.terrainFacilitator={dirty:!0,matrix:o.as(new Float64Array(16)),renderTime:0},this.setup(),this.numSublayers=si.maxOverzooming+si.maxUnderzooming+1,this.depthEpsilon=1/Math.pow(2,16),this.crossTileSymbolIndex=new Ql}resize(l,m,T){if(this.width=Math.floor(l*T),this.height=Math.floor(m*T),this.pixelRatio=T,this.context.viewport.set([0,0,this.width,this.height]),this.style)for(let P of this.style._order)this.style._layers[P].resize()}setup(){let l=this.context,m=new o.aX;m.emplaceBack(0,0),m.emplaceBack(o.a5,0),m.emplaceBack(0,o.a5),m.emplaceBack(o.a5,o.a5),this.tileExtentBuffer=l.createVertexBuffer(m,ec.members),this.tileExtentSegments=o.aY.simpleSegment(0,0,4,2);let T=new o.aX;T.emplaceBack(0,0),T.emplaceBack(o.a5,0),T.emplaceBack(0,o.a5),T.emplaceBack(o.a5,o.a5),this.debugBuffer=l.createVertexBuffer(T,ec.members),this.debugSegments=o.aY.simpleSegment(0,0,4,5);let P=new o.ci;P.emplaceBack(0,0,0,0),P.emplaceBack(o.a5,0,o.a5,0),P.emplaceBack(0,o.a5,0,o.a5),P.emplaceBack(o.a5,o.a5,o.a5,o.a5),this.rasterBoundsBuffer=l.createVertexBuffer(P,ao.members),this.rasterBoundsSegments=o.aY.simpleSegment(0,0,4,2);let V=new o.aX;V.emplaceBack(0,0),V.emplaceBack(o.a5,0),V.emplaceBack(0,o.a5),V.emplaceBack(o.a5,o.a5),this.rasterBoundsBufferPosOnly=l.createVertexBuffer(V,ec.members),this.rasterBoundsSegmentsPosOnly=o.aY.simpleSegment(0,0,4,5);let W=new o.aX;W.emplaceBack(0,0),W.emplaceBack(1,0),W.emplaceBack(0,1),W.emplaceBack(1,1),this.viewportBuffer=l.createVertexBuffer(W,ec.members),this.viewportSegments=o.aY.simpleSegment(0,0,4,2);let te=new o.cj;te.emplaceBack(0),te.emplaceBack(1),te.emplaceBack(3),te.emplaceBack(2),te.emplaceBack(0),this.tileBorderIndexBuffer=l.createIndexBuffer(te);let se=new o.aZ;se.emplaceBack(1,0,2),se.emplaceBack(1,2,3),this.quadTriangleIndexBuffer=l.createIndexBuffer(se);let fe=this.context.gl;this.stencilClearMode=new us({func:fe.ALWAYS,mask:0},0,255,fe.ZERO,fe.ZERO,fe.ZERO),this.tileExtentMesh=new Hs(this.tileExtentBuffer,this.quadTriangleIndexBuffer,this.tileExtentSegments)}clearStencil(){let l=this.context,m=l.gl;this.nextStencilID=1,this.currentStencilSource=void 0;let T=o.N();o.c8(T,0,this.width,this.height,0,0,1),o.Q(T,T,[m.drawingBufferWidth,m.drawingBufferHeight,0]);let P={mainMatrix:T,tileMercatorCoords:[0,0,1,1],clippingPlane:[0,0,0,0],projectionTransition:0,fallbackMatrix:T};this.useProgram("clippingMask",null,!0).draw(l,m.TRIANGLES,Ao.disabled,this.stencilClearMode,ra.disabled,ms.disabled,null,null,P,"$clipping",this.viewportBuffer,this.quadTriangleIndexBuffer,this.viewportSegments)}_renderTileClippingMasks(l,m,T){if(this.currentStencilSource===l.source||!l.isTileClipped()||!m||!m.length)return;this.currentStencilSource=l.source,this.nextStencilID+m.length>256&&this.clearStencil();let P=this.context;P.setColorMode(ra.disabled),P.setDepthMode(Ao.disabled);let V={};for(let W of m)V[W.key]=this.nextStencilID++;this._renderTileMasks(V,m,T,!0),this._renderTileMasks(V,m,T,!1),this._tileClippingMaskIDs=V}_renderTileMasks(l,m,T,P){let V=this.context,W=V.gl,te=this.style.projection,se=this.transform,fe=this.useProgram("clippingMask");for(let _e of m){let Re=l[_e.key],Ie=this.style.map.terrain&&this.style.map.terrain.getTerrainData(_e),je=te.getMeshFromTileID(this.context,_e.canonical,P,!0,"stencil"),tt=se.getProjectionData({overscaledTileID:_e,applyGlobeMatrix:!T,applyTerrainMatrix:!0});fe.draw(V,W.TRIANGLES,Ao.disabled,new us({func:W.ALWAYS,mask:0},Re,255,W.KEEP,W.KEEP,W.REPLACE),ra.disabled,T?ms.disabled:ms.backCCW,null,Ie,tt,"$clipping",je.vertexBuffer,je.indexBuffer,je.segments)}}_renderTilesDepthBuffer(){let l=this.context,m=l.gl,T=this.style.projection,P=this.transform,V=this.useProgram("depth"),W=this.getDepthModeFor3D(),te=Ke(P,{tileSize:P.tileSize});for(let se of te){let fe=this.style.map.terrain&&this.style.map.terrain.getTerrainData(se),_e=T.getMeshFromTileID(this.context,se.canonical,!0,!0,"raster"),Re=P.getProjectionData({overscaledTileID:se,applyGlobeMatrix:!0,applyTerrainMatrix:!0});V.draw(l,m.TRIANGLES,W,us.disabled,ra.disabled,ms.backCCW,null,fe,Re,"$clipping",_e.vertexBuffer,_e.indexBuffer,_e.segments)}}stencilModeFor3D(){this.currentStencilSource=void 0,this.nextStencilID+1>256&&this.clearStencil();let l=this.nextStencilID++,m=this.context.gl;return new us({func:m.NOTEQUAL,mask:255},l,255,m.KEEP,m.KEEP,m.REPLACE)}stencilModeForClipping(l){let m=this.context.gl;return new us({func:m.EQUAL,mask:255},this._tileClippingMaskIDs[l.key],0,m.KEEP,m.KEEP,m.REPLACE)}getStencilConfigForOverlapAndUpdateStencilID(l){let m=this.context.gl,T=l.sort((W,te)=>te.overscaledZ-W.overscaledZ),P=T[T.length-1].overscaledZ,V=T[0].overscaledZ-P+1;if(V>1){this.currentStencilSource=void 0,this.nextStencilID+V>256&&this.clearStencil();let W={};for(let te=0;tete.overscaledZ-W.overscaledZ),P=T[T.length-1].overscaledZ,V=T[0].overscaledZ-P+1;if(this.clearStencil(),V>1){let W={},te={};for(let se=0;se0};for(let Ie in W){let je=W[Ie];je.used&&je.prepare(this.context),te[Ie]=je.getVisibleCoordinates(!1),se[Ie]=te[Ie].slice().reverse(),fe[Ie]=je.getVisibleCoordinates(!0).reverse()}this.opaquePassCutoff=1/0;for(let Ie=0;Iethis.useProgram(Ie)}),this.context.viewport.set([0,0,this.width,this.height]),this.context.bindFramebuffer.set(null),this.context.clear({color:m.showOverdrawInspector?o.bq.black:o.bq.transparent,depth:1}),this.clearStencil(),this.style.sky&&function(Ie,je){let tt=Ie.context,yt=tt.gl,At=((Rt,zt,jt)=>{let br=Math.cos(zt.rollInRadians),Cr=Math.sin(zt.rollInRadians),Pr=bs(zt),Ir=zt.getProjectionData({overscaledTileID:null,applyGlobeMatrix:!0,applyTerrainMatrix:!0}).projectionTransition;return{u_sky_color:Rt.properties.get("sky-color"),u_horizon_color:Rt.properties.get("horizon-color"),u_horizon:[(zt.width/2-Pr*Cr)*jt,(zt.height/2+Pr*br)*jt],u_horizon_normal:[-Cr,br],u_sky_horizon_blend:Rt.properties.get("sky-horizon-blend")*zt.height/2*jt,u_sky_blend:Ir}})(je,Ie.style.map.transform,Ie.pixelRatio),vt=new Ao(yt.LEQUAL,Ao.ReadWrite,[0,1]),Lt=us.disabled,Kt=Ie.colorModeForRenderPass(),Mt=Ie.useProgram("sky"),Xt=Du(tt,je);Mt.draw(tt,yt.TRIANGLES,vt,Lt,Kt,ms.disabled,At,null,void 0,"sky",Xt.vertexBuffer,Xt.indexBuffer,Xt.segments)}(this,this.style.sky),this._showOverdrawInspector=m.showOverdrawInspector,this.depthRangeFor3D=[0,1-(l._order.length+2)*this.numSublayers*this.depthEpsilon],!this.renderToTexture)for(this.renderPass="opaque",this.currentLayer=V.length-1;this.currentLayer>=0;this.currentLayer--){let Ie=this.style._layers[V[this.currentLayer]],je=W[Ie.source],tt=te[Ie.source];this._renderTileClippingMasks(Ie,tt,!1),this.renderLayer(this,je,Ie,tt,_e)}this.renderPass="translucent";let Re=!1;for(this.currentLayer=0;this.currentLayer({u_sun_pos:Ir,u_atmosphere_blend:Ai,u_globe_position:_i,u_globe_radius:hi,u_inv_proj_matrix:hn}))(Mt,Rt,[br[0],br[1],br[2]],zt,jt),Pr=Du(yt,je);vt.draw(yt,At.TRIANGLES,Lt,us.disabled,ra.alphaBlended,ms.disabled,Cr,null,null,"atmosphere",Pr.vertexBuffer,Pr.indexBuffer,Pr.segments)}(this,this.style.sky,this.style.light),this.options.showTileBoundaries){let Ie=function(je,tt){let yt=null,At=Object.values(je._layers).flatMap(Mt=>Mt.source&&!Mt.isHidden(tt)?[je.tileManagers[Mt.source]]:[]),vt=At.filter(Mt=>Mt.getSource().type==="vector"),Lt=At.filter(Mt=>Mt.getSource().type!=="vector"),Kt=Mt=>{(!yt||yt.getSource().maxzoomKt(Mt)),yt||Lt.forEach(Mt=>Kt(Mt)),yt}(this.style,this.transform.zoom);Ie&&function(je,tt,yt){for(let At=0;Atbr.getElevation(Ir,Ja,ya):null;El(_i,Cr,Pr,zt,jt,fo,di,hi,$r,o.aP(jt,Ai,Xt,Rt),Ir.toUnwrapped(),na)}}}(fe,W,se,te,se.layout.get("text-rotation-alignment"),se.layout.get("text-pitch-alignment"),se.paint.get("text-translate"),se.paint.get("text-translate-anchor"),_e),se.paint.get("icon-opacity").constantOr(1)!==0&&An(W,te,se,fe,!1,se.paint.get("icon-translate"),se.paint.get("icon-translate-anchor"),se.layout.get("icon-rotation-alignment"),se.layout.get("icon-pitch-alignment"),se.layout.get("icon-keep-upright"),je,tt,Ie),se.paint.get("text-opacity").constantOr(1)!==0&&An(W,te,se,fe,!0,se.paint.get("text-translate"),se.paint.get("text-translate-anchor"),se.layout.get("text-rotation-alignment"),se.layout.get("text-pitch-alignment"),se.layout.get("text-keep-upright"),je,tt,Ie),te.map.showCollisionBoxes&&(ha(W,te,se,fe,!0),ha(W,te,se,fe,!1))}(l,m,T,P,this.style.placement.variableOffsets,V):o.co(T)?function(W,te,se,fe,_e){if(W.renderPass!=="translucent")return;let{isRenderingToTexture:Re}=_e,Ie=se.paint.get("circle-opacity"),je=se.paint.get("circle-stroke-width"),tt=se.paint.get("circle-stroke-opacity"),yt=!se.layout.get("circle-sort-key").isConstant();if(Ie.constantOr(1)===0&&(je.constantOr(1)===0||tt.constantOr(1)===0))return;let At=W.context,vt=At.gl,Lt=W.transform,Kt=W.getDepthModeForSublayer(0,Ao.ReadOnly),Mt=us.disabled,Xt=W.colorModeForRenderPass(),Rt=[],zt=Lt.getCircleRadiusCorrection();for(let jt=0;jtjt.sortKey-br.sortKey);for(let jt of Rt){let{programConfiguration:br,program:Cr,layoutVertexBuffer:Pr,indexBuffer:Ir,uniformValues:Ai,terrainData:_i,projectionData:hi}=jt.state;Cr.draw(At,vt.TRIANGLES,Kt,Mt,Xt,ms.backCCW,Ai,_i,hi,se.id,Pr,Ir,jt.segments,se.paint,W.transform.zoom,br)}}(l,m,T,P,V):o.cp(T)?function(W,te,se,fe,_e){if(se.paint.get("heatmap-opacity")===0)return;let Re=W.context,{isRenderingToTexture:Ie,isRenderingGlobe:je}=_e;if(W.style.map.terrain){for(let tt of fe){let yt=te.getTile(tt);te.hasRenderableParent(tt)||(W.renderPass==="offscreen"?Xd(W,yt,se,tt,je):W.renderPass==="translucent"&&Vc(W,se,tt,Ie,je))}Re.viewport.set([0,0,W.width,W.height])}else W.renderPass==="offscreen"?function(tt,yt,At,vt){let Lt=tt.context,Kt=Lt.gl,Mt=tt.transform,Xt=us.disabled,Rt=new ra([Kt.ONE,Kt.ONE],o.bq.transparent,[!0,!0,!0,!0]);(function(zt,jt,br){let Cr=zt.gl;zt.activeTexture.set(Cr.TEXTURE1),zt.viewport.set([0,0,jt.width/4,jt.height/4]);let Pr=br.heatmapFbos.get(o.ce);Pr?(Cr.bindTexture(Cr.TEXTURE_2D,Pr.colorAttachment.get()),zt.bindFramebuffer.set(Pr.framebuffer)):(Pr=Au(zt,jt.width/4,jt.height/4),br.heatmapFbos.set(o.ce,Pr))})(Lt,tt,At),Lt.clear({color:o.bq.transparent});for(let zt=0;zt0?m.pop():null}isPatternMissing(l){if(!l)return!1;if(!l.from||!l.to)return!0;let m=this.imageManager.getPattern(l.from.toString()),T=this.imageManager.getPattern(l.to.toString());return!m||!T}useProgram(l,m,T=!1,P=[]){this.cache=this.cache||{};let V=!!this.style.map.terrain,W=this.style.projection,te=T?_a.projectionMercator:W.shaderPreludeCode,se=T?sl:W.shaderDefine,fe=l+(m?m.cacheKey:"")+`/${T?Ol:W.shaderVariantName}`+(this._showOverdrawInspector?"/overdraw":"")+(V?"/terrain":"")+(P?`/${P.join("/")}`:"");return this.cache[fe]||(this.cache[fe]=new Ts(this.context,_a[l],m,Pn[l],this._showOverdrawInspector,V,te,se,P)),this.cache[fe]}setCustomLayerDefaults(){this.context.unbindVAO(),this.context.cullFace.setDefault(),this.context.activeTexture.setDefault(),this.context.pixelStoreUnpack.setDefault(),this.context.pixelStoreUnpackPremultiplyAlpha.setDefault(),this.context.pixelStoreUnpackFlipY.setDefault()}setBaseState(){let l=this.context.gl;this.context.cullFace.set(!1),this.context.viewport.set([0,0,this.width,this.height]),this.context.blendEquation.set(l.FUNC_ADD)}initDebugOverlayCanvas(){this.debugOverlayCanvas==null&&(this.debugOverlayCanvas=document.createElement("canvas"),this.debugOverlayCanvas.width=512,this.debugOverlayCanvas.height=512,this.debugOverlayTexture=new o.T(this.context,this.debugOverlayCanvas,this.context.gl.RGBA))}destroy(){var l,m;if(this._tileTextures){for(let T in this._tileTextures){let P=this._tileTextures[T];if(P)for(let V of P)V.destroy()}this._tileTextures={}}if(this.tileExtentBuffer&&this.tileExtentBuffer.destroy(),this.debugBuffer&&this.debugBuffer.destroy(),this.rasterBoundsBuffer&&this.rasterBoundsBuffer.destroy(),this.rasterBoundsBufferPosOnly&&this.rasterBoundsBufferPosOnly.destroy(),this.viewportBuffer&&this.viewportBuffer.destroy(),this.tileBorderIndexBuffer&&this.tileBorderIndexBuffer.destroy(),this.quadTriangleIndexBuffer&&this.quadTriangleIndexBuffer.destroy(),this.tileExtentMesh&&((l=this.tileExtentMesh.vertexBuffer)===null||l===void 0||l.destroy()),this.tileExtentMesh&&((m=this.tileExtentMesh.indexBuffer)===null||m===void 0||m.destroy()),this.debugOverlayTexture&&this.debugOverlayTexture.destroy(),this.cache){for(let T in this.cache){let P=this.cache[T];P&&P.program&&this.context.gl.deleteProgram(P.program)}this.cache={}}this.context&&this.context.setDefault()}overLimit(){let{drawingBufferWidth:l,drawingBufferHeight:m}=this.context.gl;return this.width!==l||this.height!==m}}function Kd(z,l){let m,T=!1,P=null,V=null,W=()=>{P=null,T&&(z.apply(V,m),P=setTimeout(W,l),T=!1)};return(...te)=>(T=!0,V=this,m=te,P||W(),P)}class Yf{constructor(l){this._getCurrentHash=()=>{let m=window.location.hash.replace("#","");if(this._hashName){let T;return m.split("&").map(P=>P.split("=")).forEach(P=>{P[0]===this._hashName&&(T=P)}),(T&&T[1]||"").split("/")}return m.split("/")},this._onHashChange=()=>{let m=this._getCurrentHash();if(!this._isValidHash(m))return!1;let T=this._map.dragRotate.isEnabled()&&this._map.touchZoomRotate.isEnabled()?+(m[3]||0):this._map.getBearing();return this._map.jumpTo({center:[+m[2],+m[1]],zoom:+m[0],bearing:T,pitch:+(m[4]||0)}),!0},this._updateHashUnthrottled=()=>{let m=window.location.href.replace(/(#.*)?$/,this.getHashString());window.history.replaceState(window.history.state,null,m)},this._removeHash=()=>{let m=this._getCurrentHash();if(m.length===0)return;let T=m.join("/"),P=T;P.split("&").length>0&&(P=P.split("&")[0]),this._hashName&&(P=`${this._hashName}=${T}`);let V=window.location.hash.replace(P,"");V.startsWith("#&")?V=V.slice(0,1)+V.slice(2):V==="#"&&(V="");let W=window.location.href.replace(/(#.+)?$/,V);W=W.replace("&&","&"),window.history.replaceState(window.history.state,null,W)},this._updateHash=Kd(this._updateHashUnthrottled,300),this._hashName=l&&encodeURIComponent(l)}addTo(l){return this._map=l,addEventListener("hashchange",this._onHashChange,!1),this._map.on("moveend",this._updateHash),this}remove(){return removeEventListener("hashchange",this._onHashChange,!1),this._map.off("moveend",this._updateHash),clearTimeout(this._updateHash()),this._removeHash(),delete this._map,this}getHashString(l){let m=this._map.getCenter(),T=Math.round(100*this._map.getZoom())/100,P=Math.ceil((T*Math.LN2+Math.log(512/360/.5))/Math.LN10),V=Math.pow(10,P),W=Math.round(m.lng*V)/V,te=Math.round(m.lat*V)/V,se=this._map.getBearing(),fe=this._map.getPitch(),_e="";if(_e+=l?`/${W}/${te}/${T}`:`${T}/${te}/${W}`,(se||fe)&&(_e+="/"+Math.round(10*se)/10),fe&&(_e+=`/${Math.round(fe)}`),this._hashName){let Re=this._hashName,Ie=!1,je=window.location.hash.slice(1).split("&").map(tt=>{let yt=tt.split("=")[0];return yt===Re?(Ie=!0,`${yt}=${_e}`):tt}).filter(tt=>tt);return Ie||je.push(`${Re}=${_e}`),`#${je.join("&")}`}return`#${_e}`}_isValidHash(l){if(l.length<3||l.some(isNaN))return!1;try{new o.V(+l[2],+l[1])}catch{return!1}let m=+l[0],T=+(l[3]||0),P=+(l[4]||0);return m>=this._map.getMinZoom()&&m<=this._map.getMaxZoom()&&T>=-180&&T<=180&&P>=this._map.getMinPitch()&&P<=this._map.getMaxPitch()}}let Jh={linearity:.3,easing:o.cx(0,0,.3,1)},w=o.e({deceleration:2500,maxSpeed:1400},Jh),I=o.e({deceleration:20,maxSpeed:1400},Jh),S=o.e({deceleration:1e3,maxSpeed:360},Jh),L=o.e({deceleration:1e3,maxSpeed:90},Jh),Y=o.e({deceleration:1e3,maxSpeed:360},Jh);class ie{constructor(l){this._map=l,this.clear()}clear(){this._inertiaBuffer=[]}record(l){this._drainInertiaBuffer(),this._inertiaBuffer.push({time:k(),settings:l})}_drainInertiaBuffer(){let l=this._inertiaBuffer,m=k();for(;l.length>0&&m-l[0].time>160;)l.shift()}_onMoveEnd(l){if(this._drainInertiaBuffer(),this._inertiaBuffer.length<2)return;let m={zoom:0,bearing:0,pitch:0,roll:0,pan:new o.P(0,0),pinchAround:void 0,around:void 0};for(let{settings:V}of this._inertiaBuffer)m.zoom+=V.zoomDelta||0,m.bearing+=V.bearingDelta||0,m.pitch+=V.pitchDelta||0,m.roll+=V.rollDelta||0,V.panDelta&&m.pan._add(V.panDelta),V.around&&(m.around=V.around),V.pinchAround&&(m.pinchAround=V.pinchAround);let T=this._inertiaBuffer[this._inertiaBuffer.length-1].time-this._inertiaBuffer[0].time,P={};if(m.pan.mag()){let V=ge(m.pan.mag(),T,o.e({},w,l||{})),W=m.pan.mult(V.amount/m.pan.mag()),te=this._map.cameraHelper.handlePanInertia(W,this._map.transform);P.center=te.easingCenter,P.offset=te.easingOffset,ae(P,V)}if(m.zoom){let V=ge(m.zoom,T,I);P.zoom=this._map.transform.zoom+V.amount,ae(P,V)}if(m.bearing){let V=ge(m.bearing,T,S);P.bearing=this._map.transform.bearing+o.ao(V.amount,-179,179),ae(P,V)}if(m.pitch){let V=ge(m.pitch,T,L);P.pitch=this._map.transform.pitch+V.amount,ae(P,V)}if(m.roll){let V=ge(m.roll,T,Y);P.roll=this._map.transform.roll+o.ao(V.amount,-179,179),ae(P,V)}if(P.zoom||P.bearing){let V=m.pinchAround===void 0?m.around:m.pinchAround;P.around=V?this._map.unproject(V):this._map.getCenter()}return this.clear(),o.e(P,{noMoveStart:!0})}}function ae(z,l){(!z.duration||z.durationm.unproject(se)),te=V.reduce((se,fe,_e,Re)=>se.add(fe.div(Re.length)),new o.P(0,0));super(l,{points:V,point:te,lngLats:W,lngLat:m.unproject(te),originalEvent:T}),this._defaultPrevented=!1}}class ct extends o.l{preventDefault(){this._defaultPrevented=!0}get defaultPrevented(){return this._defaultPrevented}constructor(l,m,T){super(l,{originalEvent:T}),this._defaultPrevented=!1}}class ft{constructor(l,m){this._map=l,this._clickTolerance=m.clickTolerance}reset(){delete this._mousedownPos}wheel(l){return this._firePreventable(new ct(l.type,this._map,l))}mousedown(l,m){return this._mousedownPos=m,this._firePreventable(new Pe(l.type,this._map,l))}mouseup(l){this._map.fire(new Pe(l.type,this._map,l))}click(l,m){this._mousedownPos&&this._mousedownPos.dist(m)>=this._clickTolerance||this._map.fire(new Pe(l.type,this._map,l))}dblclick(l){return this._firePreventable(new Pe(l.type,this._map,l))}mouseover(l){this._map.fire(new Pe(l.type,this._map,l))}mouseout(l){this._map.fire(new Pe(l.type,this._map,l))}touchstart(l){return this._firePreventable(new Ue(l.type,this._map,l))}touchmove(l){this._map.fire(new Ue(l.type,this._map,l))}touchend(l){this._map.fire(new Ue(l.type,this._map,l))}touchcancel(l){this._map.fire(new Ue(l.type,this._map,l))}_firePreventable(l){if(this._map.fire(l),l.defaultPrevented)return{}}isEnabled(){return!0}isActive(){return!1}enable(){}disable(){}}class gt{constructor(l){this._map=l}reset(){this._delayContextMenu=!1,this._ignoreContextMenu=!0,delete this._contextMenuEvent}mousemove(l){this._map.fire(new Pe(l.type,this._map,l))}mousedown(){this._delayContextMenu=!0,this._ignoreContextMenu=!1}mouseup(){this._delayContextMenu=!1,this._contextMenuEvent&&(this._map.fire(new Pe("contextmenu",this._map,this._contextMenuEvent)),delete this._contextMenuEvent)}contextmenu(l){this._delayContextMenu?this._contextMenuEvent=l:this._ignoreContextMenu||this._map.fire(new Pe(l.type,this._map,l)),this._map.listens("contextmenu")&&l.preventDefault()}isEnabled(){return!0}isActive(){return!1}enable(){}disable(){}}class ut{constructor(l){this._map=l}get transform(){return this._map._requestedCameraState||this._map.transform}get center(){return{lng:this.transform.center.lng,lat:this.transform.center.lat}}get zoom(){return this.transform.zoom}get pitch(){return this.transform.pitch}get bearing(){return this.transform.bearing}unproject(l){return this.transform.screenPointToLocation(o.P.convert(l),this._map.terrain)}}class Pt{constructor(l,m){this._map=l,this._tr=new ut(l),this._el=l.getCanvasContainer(),this._container=l.getContainer(),this._clickTolerance=m.clickTolerance||1}isEnabled(){return!!this._enabled}isActive(){return!!this._active}enable(){this.isEnabled()||(this._enabled=!0)}disable(){this.isEnabled()&&(this._enabled=!1)}mousedown(l,m){this.isEnabled()&&l.shiftKey&&l.button===0&&(U.disableDrag(),this._startPos=this._lastPos=m,this._active=!0)}mousemoveWindow(l,m){if(!this._active)return;let T=m;if(this._lastPos.equals(T)||!this._box&&T.dist(this._startPos)V.fitScreenCoordinates(T,P,this._tr.bearing,{linear:!0})};this._fireEvent("boxzoomcancel",l)}keydown(l){this._active&&l.keyCode===27&&(this.reset(),this._fireEvent("boxzoomcancel",l))}reset(){this._active=!1,this._container.classList.remove("maplibregl-crosshair"),this._box&&(U.remove(this._box),this._box=null),U.enableDrag(),delete this._startPos,delete this._lastPos}_fireEvent(l,m){return this._map.fire(new o.l(l,{originalEvent:m}))}}function Wt(z,l){if(z.length!==l.length)throw new Error(`The number of touches and points are not equal - touches ${z.length}, points ${l.length}`);let m={};for(let T=0;Tthis.numTouches)&&(this.aborted=!0),this.aborted||(this.startTime===void 0&&(this.startTime=l.timeStamp),T.length===this.numTouches&&(this.centroid=function(P){let V=new o.P(0,0);for(let W of P)V._add(W);return V.div(P.length)}(m),this.touches=Wt(T,m)))}touchmove(l,m,T){if(this.aborted||!this.centroid)return;let P=Wt(T,m);for(let V in this.touches){let W=P[V];(!W||W.dist(this.touches[V])>30)&&(this.aborted=!0)}}touchend(l,m,T){if((!this.centroid||l.timeStamp-this.startTime>500)&&(this.aborted=!0),T.length===0){let P=!this.aborted&&this.centroid;if(this.reset(),P)return P}}}class Zt{constructor(l){this.singleTap=new Et(l),this.numTaps=l.numTaps,this.reset()}reset(){this.lastTime=1/0,delete this.lastTap,this.count=0,this.singleTap.reset()}touchstart(l,m,T){this.singleTap.touchstart(l,m,T)}touchmove(l,m,T){this.singleTap.touchmove(l,m,T)}touchend(l,m,T){let P=this.singleTap.touchend(l,m,T);if(P){let V=l.timeStamp-this.lastTime<500,W=!this.lastTap||this.lastTap.dist(P)<30;if(V&&W||this.reset(),this.count++,this.lastTime=l.timeStamp,this.lastTap=P,this.count===this.numTaps)return this.reset(),P}}}class ir{constructor(l){this._tr=new ut(l),this._zoomIn=new Zt({numTouches:1,numTaps:2}),this._zoomOut=new Zt({numTouches:2,numTaps:1}),this.reset()}reset(){this._active=!1,this._zoomIn.reset(),this._zoomOut.reset()}touchstart(l,m,T){this._zoomIn.touchstart(l,m,T),this._zoomOut.touchstart(l,m,T)}touchmove(l,m,T){this._zoomIn.touchmove(l,m,T),this._zoomOut.touchmove(l,m,T)}touchend(l,m,T){let P=this._zoomIn.touchend(l,m,T),V=this._zoomOut.touchend(l,m,T),W=this._tr;return P?(this._active=!0,l.preventDefault(),setTimeout(()=>this.reset(),0),{cameraAnimation:te=>te.easeTo({duration:300,zoom:W.zoom+1,around:W.unproject(P)},{originalEvent:l})}):V?(this._active=!0,l.preventDefault(),setTimeout(()=>this.reset(),0),{cameraAnimation:te=>te.easeTo({duration:300,zoom:W.zoom-1,around:W.unproject(V)},{originalEvent:l})}):void 0}touchcancel(){this.reset()}enable(){this._enabled=!0}disable(){this._enabled=!1,this.reset()}isEnabled(){return this._enabled}isActive(){return this._active}}class dr{constructor(l){this._enabled=!!l.enable,this._moveStateManager=l.moveStateManager,this._clickTolerance=l.clickTolerance||1,this._moveFunction=l.move,this._activateOnStart=!!l.activateOnStart,l.assignEvents(this),this.reset()}reset(l){this._active=!1,this._moved=!1,delete this._lastPoint,this._moveStateManager.endMove(l)}_move(...l){let m=this._moveFunction(...l);if(m.bearingDelta||m.pitchDelta||m.rollDelta||m.around||m.panDelta)return this._active=!0,m}dragStart(l,m){this.isEnabled()&&!this._lastPoint&&this._moveStateManager.isValidStartEvent(l)&&(this._moveStateManager.startMove(l),this._lastPoint=Array.isArray(m)?m[0]:m,this._activateOnStart&&this._lastPoint&&(this._active=!0))}dragMove(l,m){if(!this.isEnabled())return;let T=this._lastPoint;if(!T)return;if(l.preventDefault(),!this._moveStateManager.isValidMoveEvent(l))return void this.reset(l);let P=Array.isArray(m)?m[0]:m;return!this._moved&&P.dist(T)!0}),m=new Ci){this.mouseMoveStateManager=l,this.oneFingerTouchMoveStateManager=m}_executeRelevantHandler(l,m,T){return l instanceof MouseEvent?m(l):typeof TouchEvent<"u"&&l instanceof TouchEvent?T(l):void 0}startMove(l){this._executeRelevantHandler(l,m=>this.mouseMoveStateManager.startMove(m),m=>this.oneFingerTouchMoveStateManager.startMove(m))}endMove(l){this._executeRelevantHandler(l,m=>this.mouseMoveStateManager.endMove(m),m=>this.oneFingerTouchMoveStateManager.endMove(m))}isValidStartEvent(l){return this._executeRelevantHandler(l,m=>this.mouseMoveStateManager.isValidStartEvent(m),m=>this.oneFingerTouchMoveStateManager.isValidStartEvent(m))}isValidMoveEvent(l){return this._executeRelevantHandler(l,m=>this.mouseMoveStateManager.isValidMoveEvent(m),m=>this.oneFingerTouchMoveStateManager.isValidMoveEvent(m))}isValidEndEvent(l){return this._executeRelevantHandler(l,m=>this.mouseMoveStateManager.isValidEndEvent(m),m=>this.oneFingerTouchMoveStateManager.isValidEndEvent(m))}}let $i=z=>{z.mousedown=z.dragStart,z.mousemoveWindow=z.dragMove,z.mouseup=z.dragEnd,z.contextmenu=l=>{l.preventDefault()}};class bn{constructor(l,m){this._clickTolerance=l.clickTolerance||1,this._map=m,this.reset()}reset(){this._active=!1,this._touches={},this._sum=new o.P(0,0)}_shouldBePrevented(l){return l<(this._map.cooperativeGestures.isEnabled()?2:1)}touchstart(l,m,T){return this._calculateTransform(l,m,T)}touchmove(l,m,T){if(this._active){if(!this._shouldBePrevented(T.length))return l.preventDefault(),this._calculateTransform(l,m,T);this._map.cooperativeGestures.notifyGestureBlocked("touch_pan",l)}}touchend(l,m,T){this._calculateTransform(l,m,T),this._active&&this._shouldBePrevented(T.length)&&this.reset()}touchcancel(){this.reset()}_calculateTransform(l,m,T){T.length>0&&(this._active=!0);let P=Wt(T,m),V=new o.P(0,0),W=new o.P(0,0),te=0;for(let fe in P){let _e=P[fe],Re=this._touches[fe];Re&&(V._add(_e),W._add(_e.sub(Re)),te++,P[fe]=_e)}if(this._touches=P,this._shouldBePrevented(te)||!W.mag())return;let se=W.div(te);return this._sum._add(se),this._sum.mag()Math.abs(z.x)}class Gn extends Tn{constructor(l){super(),this._currentTouchCount=0,this._map=l}reset(){super.reset(),this._valid=void 0,delete this._firstMove,delete this._lastPoints}touchstart(l,m,T){super.touchstart(l,m,T),this._currentTouchCount=T.length}_start(l){this._lastPoints=l,Hi(l[0].sub(l[1]))&&(this._valid=!1)}_move(l,m,T){if(this._map.cooperativeGestures.isEnabled()&&this._currentTouchCount<3)return;let P=l[0].sub(this._lastPoints[0]),V=l[1].sub(this._lastPoints[1]);return this._valid=this.gestureBeginsVertically(P,V,T.timeStamp),this._valid?(this._lastPoints=l,this._active=!0,{pitchDelta:(P.y+V.y)/2*-.5}):void 0}gestureBeginsVertically(l,m,T){if(this._valid!==void 0)return this._valid;let P=l.mag()>=2,V=m.mag()>=2;if(!P&&!V)return;if(!P||!V)return this._firstMove===void 0&&(this._firstMove=T),T-this._firstMove<100&&void 0;let W=l.y>0==m.y>0;return Hi(l)&&Hi(m)&&W}}let eo={panStep:100,bearingStep:15,pitchStep:10};class Dn{constructor(l){this._tr=new ut(l);let m=eo;this._panStep=m.panStep,this._bearingStep=m.bearingStep,this._pitchStep=m.pitchStep,this._rotationDisabled=!1}reset(){this._active=!1}keydown(l){if(l.altKey||l.ctrlKey||l.metaKey)return;let m=0,T=0,P=0,V=0,W=0;switch(l.keyCode){case 61:case 107:case 171:case 187:m=1;break;case 189:case 109:case 173:m=-1;break;case 37:l.shiftKey?T=-1:(l.preventDefault(),V=-1);break;case 39:l.shiftKey?T=1:(l.preventDefault(),V=1);break;case 38:l.shiftKey?P=1:(l.preventDefault(),W=-1);break;case 40:l.shiftKey?P=-1:(l.preventDefault(),W=1);break;default:return}return this._rotationDisabled&&(T=0,P=0),{cameraAnimation:te=>{let se=this._tr;te.easeTo({duration:300,easeId:"keyboardHandler",easing:Rn,zoom:m?Math.round(se.zoom)+m*(l.shiftKey?2:1):se.zoom,bearing:se.bearing+T*this._bearingStep,pitch:se.pitch+P*this._pitchStep,offset:[-V*this._panStep,-W*this._panStep],center:se.center},{originalEvent:l})}}}enable(){this._enabled=!0}disable(){this._enabled=!1,this.reset()}isEnabled(){return this._enabled}isActive(){return this._active}disableRotation(){this._rotationDisabled=!0}enableRotation(){this._rotationDisabled=!1}}function Rn(z){return z*(2-z)}let Ml=4.000244140625,Ga=1/450;class yc{constructor(l,m){this._onTimeout=T=>{this._type="wheel",this._delta-=this._lastValue,this._active||this._start(T)},this._map=l,this._tr=new ut(l),this._triggerRenderFrame=m,this._delta=0,this._defaultZoomRate=.01,this._wheelZoomRate=Ga}setZoomRate(l){this._defaultZoomRate=l}setWheelZoomRate(l){this._wheelZoomRate=l}isEnabled(){return!!this._enabled}isActive(){return!!this._active||this._finishTimeout!==void 0}isZooming(){return!!this._zooming}enable(l){this.isEnabled()||(this._enabled=!0,this._aroundCenter=!!l&&l.around==="center")}disable(){this.isEnabled()&&(this._enabled=!1)}_shouldBePrevented(l){return!!this._map.cooperativeGestures.isEnabled()&&!(l.ctrlKey||this._map.cooperativeGestures.isBypassed(l))}wheel(l){if(!this.isEnabled())return;if(this._shouldBePrevented(l))return void this._map.cooperativeGestures.notifyGestureBlocked("wheel_zoom",l);let m=l.deltaMode===WheelEvent.DOM_DELTA_LINE?40*l.deltaY:l.deltaY,T=k(),P=T-(this._lastWheelEventTime||0);this._lastWheelEventTime=T,m!==0&&m%Ml==0?this._type="wheel":m!==0&&Math.abs(m)<4?this._type="trackpad":P>400?(this._type=null,this._lastValue=m,this._timeout=setTimeout(this._onTimeout,40,l)):this._type||(this._type=Math.abs(P*m)<200?"trackpad":"wheel",this._timeout&&(clearTimeout(this._timeout),this._timeout=null,m+=this._lastValue)),l.shiftKey&&m&&(m/=4),this._type&&(this._lastWheelEvent=l,this._delta-=m,this._active||this._start(l)),l.preventDefault()}_start(l){if(!this._delta)return;this._frameId&&(this._frameId=null),this._active=!0,this.isZooming()||(this._zooming=!0),this._finishTimeout&&(clearTimeout(this._finishTimeout),delete this._finishTimeout);let m=U.mousePos(this._map.getCanvas(),l),T=this._tr;this._aroundPoint=this._aroundCenter?T.transform.locationToScreenPoint(o.V.convert(T.center)):m,this._frameId||(this._frameId=!0,this._triggerRenderFrame())}renderFrame(){if(!this._frameId||(this._frameId=null,!this.isActive()))return;let l=this._tr.transform;if(typeof this._lastExpectedZoom=="number"){let te=l.zoom-this._lastExpectedZoom;typeof this._startZoom=="number"&&(this._startZoom+=te),typeof this._targetZoom=="number"&&(this._targetZoom+=te)}if(this._delta!==0){let te=this._type==="wheel"&&Math.abs(this._delta)>Ml?this._wheelZoomRate:this._defaultZoomRate,se=2/(1+Math.exp(-Math.abs(this._delta*te)));this._delta<0&&se!==0&&(se=1/se);let fe=typeof this._targetZoom!="number"?l.scale:o.ar(this._targetZoom);this._targetZoom=l.applyConstrain(l.getCameraLngLat(),o.au(fe*se)).zoom,this._type==="wheel"&&(this._startZoom=l.zoom,this._easing=this._smoothOutEasing(200)),this._delta=0}let m=typeof this._targetZoom!="number"?l.zoom:this._targetZoom,T=this._startZoom,P=this._easing,V,W=!1;if(this._type==="wheel"&&T&&P){let te=k()-this._lastWheelEventTime,se=Math.min((te+5)/200,1),fe=P(se);V=o.G.number(T,m,fe),se<1?this._frameId||(this._frameId=!0):W=!0}else V=m,W=!0;return this._active=!0,W&&(this._active=!1,this._finishTimeout=setTimeout(()=>{this._zooming=!1,this._triggerRenderFrame(),delete this._targetZoom,delete this._lastExpectedZoom,delete this._finishTimeout},200)),this._lastExpectedZoom=V,{noInertia:!0,needsRenderFrame:!W,zoomDelta:V-l.zoom,around:this._aroundPoint,originalEvent:this._lastWheelEvent}}_smoothOutEasing(l){let m=o.cz;if(this._prevEase){let T=this._prevEase,P=(k()-T.start)/T.duration,V=T.easing(P+.01)-T.easing(P),W=.27/Math.sqrt(V*V+1e-4)*.01,te=Math.sqrt(.0729-W*W);m=o.cx(W,te,.25,1)}return this._prevEase={start:k(),duration:l,easing:m},m}reset(){this._active=!1,this._zooming=!1,delete this._targetZoom,delete this._lastExpectedZoom,this._finishTimeout&&(clearTimeout(this._finishTimeout),delete this._finishTimeout)}}class cl{constructor(l,m){this._clickZoom=l,this._tapZoom=m}enable(){this._clickZoom.enable(),this._tapZoom.enable()}disable(){this._clickZoom.disable(),this._tapZoom.disable()}isEnabled(){return this._clickZoom.isEnabled()&&this._tapZoom.isEnabled()}isActive(){return this._clickZoom.isActive()||this._tapZoom.isActive()}}class Vs{constructor(l){this._tr=new ut(l),this.reset()}reset(){this._active=!1}dblclick(l,m){return l.preventDefault(),{cameraAnimation:T=>{T.easeTo({duration:300,zoom:this._tr.zoom+(l.shiftKey?-1:1),around:this._tr.unproject(m)},{originalEvent:l})}}}enable(){this._enabled=!0}disable(){this._enabled=!1,this.reset()}isEnabled(){return this._enabled}isActive(){return this._active}}class Ta{constructor(){this._tap=new Zt({numTouches:1,numTaps:1}),this.reset()}reset(){this._active=!1,delete this._swipePoint,delete this._swipeTouch,delete this._tapTime,delete this._tapPoint,this._tap.reset()}touchstart(l,m,T){if(!this._swipePoint)if(this._tapTime){let P=m[0],V=l.timeStamp-this._tapTime<500,W=this._tapPoint.dist(P)<30;V&&W?T.length>0&&(this._swipePoint=P,this._swipeTouch=T[0].identifier):this.reset()}else this._tap.touchstart(l,m,T)}touchmove(l,m,T){if(this._tapTime){if(this._swipePoint){if(T[0].identifier!==this._swipeTouch)return;let P=m[0],V=P.y-this._swipePoint.y;return this._swipePoint=P,l.preventDefault(),this._active=!0,{zoomDelta:V/128}}}else this._tap.touchmove(l,m,T)}touchend(l,m,T){if(this._tapTime)this._swipePoint&&T.length===0&&this.reset();else{let P=this._tap.touchend(l,m,T);P&&(this._tapTime=l.timeStamp,this._tapPoint=P)}}touchcancel(){this.reset()}enable(){this._enabled=!0}disable(){this._enabled=!1,this.reset()}isEnabled(){return this._enabled}isActive(){return this._active}}class en{constructor(l,m,T){this._el=l,this._mousePan=m,this._touchPan=T}enable(l){this._inertiaOptions=l||{},this._mousePan.enable(),this._touchPan.enable(),this._el.classList.add("maplibregl-touch-drag-pan")}disable(){this._mousePan.disable(),this._touchPan.disable(),this._el.classList.remove("maplibregl-touch-drag-pan")}isEnabled(){return this._mousePan.isEnabled()&&this._touchPan.isEnabled()}isActive(){return this._mousePan.isActive()||this._touchPan.isActive()}}class _n{constructor(l,m,T,P){this._pitchWithRotate=l.pitchWithRotate,this._rollEnabled=l.rollEnabled,this._mouseRotate=m,this._mousePitch=T,this._mouseRoll=P}enable(){this._mouseRotate.enable(),this._pitchWithRotate&&this._mousePitch.enable(),this._rollEnabled&&this._mouseRoll.enable()}disable(){this._mouseRotate.disable(),this._mousePitch.disable(),this._mouseRoll.disable()}isEnabled(){return this._mouseRotate.isEnabled()&&(!this._pitchWithRotate||this._mousePitch.isEnabled())&&(!this._rollEnabled||this._mouseRoll.isEnabled())}isActive(){return this._mouseRotate.isActive()||this._mousePitch.isActive()||this._mouseRoll.isActive()}}class Ju{constructor(l,m,T,P){this._el=l,this._touchZoom=m,this._touchRotate=T,this._tapDragZoom=P,this._rotationDisabled=!1,this._enabled=!0}enable(l){this._touchZoom.enable(l),this._rotationDisabled||this._touchRotate.enable(l),this._tapDragZoom.enable(),this._el.classList.add("maplibregl-touch-zoom-rotate")}disable(){this._touchZoom.disable(),this._touchRotate.disable(),this._tapDragZoom.disable(),this._el.classList.remove("maplibregl-touch-zoom-rotate")}isEnabled(){return this._touchZoom.isEnabled()&&(this._rotationDisabled||this._touchRotate.isEnabled())&&this._tapDragZoom.isEnabled()}isActive(){return this._touchZoom.isActive()||this._touchRotate.isActive()||this._tapDragZoom.isActive()}disableRotation(){this._rotationDisabled=!0,this._touchRotate.disable()}enableRotation(){this._rotationDisabled=!1,this._touchZoom.isEnabled()&&this._touchRotate.enable()}}class jc{constructor(l,m){this._bypassKey=navigator.userAgent.indexOf("Mac")!==-1?"metaKey":"ctrlKey",this._map=l,this._options=m,this._enabled=!1}isActive(){return!1}reset(){}_setupUI(){if(this._container)return;let l=this._map.getCanvasContainer();l.classList.add("maplibregl-cooperative-gestures"),this._container=U.create("div","maplibregl-cooperative-gesture-screen",l);let m=this._map._getUIString("CooperativeGesturesHandler.WindowsHelpText");this._bypassKey==="metaKey"&&(m=this._map._getUIString("CooperativeGesturesHandler.MacHelpText"));let T=this._map._getUIString("CooperativeGesturesHandler.MobileHelpText"),P=document.createElement("div");P.className="maplibregl-desktop-message",P.textContent=m,this._container.appendChild(P);let V=document.createElement("div");V.className="maplibregl-mobile-message",V.textContent=T,this._container.appendChild(V),this._container.setAttribute("aria-hidden","true")}_destroyUI(){this._container&&(U.remove(this._container),this._map.getCanvasContainer().classList.remove("maplibregl-cooperative-gestures")),delete this._container}enable(){this._setupUI(),this._enabled=!0}disable(){this._enabled=!1,this._destroyUI()}isEnabled(){return this._enabled}isBypassed(l){return l[this._bypassKey]}notifyGestureBlocked(l,m){this._enabled&&(this._map.fire(new o.l("cooperativegestureprevented",{gestureType:l,originalEvent:m})),this._container.classList.add("maplibregl-show"),setTimeout(()=>{this._container.classList.remove("maplibregl-show")},100))}}let Gc=z=>z.zoom||z.drag||z.roll||z.pitch||z.rotate;class Fa extends o.l{}function Hc(z){return z.panDelta&&z.panDelta.mag()||z.zoomDelta||z.bearingDelta||z.pitchDelta||z.rollDelta}class bd{constructor(l,m){this.handleWindowEvent=P=>{this.handleEvent(P,`${P.type}Window`)},this.handleEvent=(P,V)=>{if(P.type==="blur")return void this.stop(!0);this._updatingCamera=!0;let W=P.type==="renderFrame"?void 0:P,te={needsRenderFrame:!1},se={},fe={};for(let{handlerName:Ie,handler:je,allowed:tt}of this._handlers){if(!je.isEnabled())continue;let yt;if(this._blockedByActive(fe,tt,Ie))je.reset();else if(je[V||P.type]){if(o.cA(P,V||P.type)){let At=U.mousePos(this._map.getCanvas(),P);yt=je[V||P.type](P,At)}else if(o.cB(P,V||P.type)){let At=this._getMapTouches(P.touches),vt=U.touchPos(this._map.getCanvas(),At);yt=je[V||P.type](P,vt,At)}else o.cC(V||P.type)||(yt=je[V||P.type](P));this.mergeHandlerResult(te,se,yt,Ie,W),yt&&yt.needsRenderFrame&&this._triggerRenderFrame()}(yt||je.isActive())&&(fe[Ie]=je)}let _e={};for(let Ie in this._previousActiveHandlers)fe[Ie]||(_e[Ie]=W);this._previousActiveHandlers=fe,(Object.keys(_e).length||Hc(te))&&(this._changes.push([te,se,_e]),this._triggerRenderFrame()),(Object.keys(fe).length||Hc(te))&&this._map._stop(!0),this._updatingCamera=!1;let{cameraAnimation:Re}=te;Re&&(this._inertia.clear(),this._fireEvents({},{},!0),this._changes=[],Re(this._map))},this._map=l,this._el=this._map.getCanvasContainer(),this._handlers=[],this._handlersById={},this._changes=[],this._inertia=new ie(l),this._bearingSnap=m.bearingSnap,this._previousActiveHandlers={},this._eventsInProgress={},this._addDefaultHandlers(m);let T=this._el;this._listeners=[[T,"touchstart",{passive:!0}],[T,"touchmove",{passive:!1}],[T,"touchend",void 0],[T,"touchcancel",void 0],[T,"mousedown",void 0],[T,"mousemove",void 0],[T,"mouseup",void 0],[document,"mousemove",{capture:!0}],[document,"mouseup",void 0],[T,"mouseover",void 0],[T,"mouseout",void 0],[T,"dblclick",void 0],[T,"click",void 0],[T,"keydown",{capture:!1}],[T,"keyup",void 0],[T,"wheel",{passive:!1}],[T,"contextmenu",void 0],[window,"blur",void 0]];for(let[P,V,W]of this._listeners)U.addEventListener(P,V,P===document?this.handleWindowEvent:this.handleEvent,W)}destroy(){for(let[l,m,T]of this._listeners)U.removeEventListener(l,m,l===document?this.handleWindowEvent:this.handleEvent,T)}_addDefaultHandlers(l){let m=this._map,T=m.getCanvasContainer();this._add("mapEvent",new ft(m,l));let P=m.boxZoom=new Pt(m,l);this._add("boxZoom",P),l.interactive&&l.boxZoom&&P.enable();let V=m.cooperativeGestures=new jc(m,l.cooperativeGestures);this._add("cooperativeGestures",V),l.cooperativeGestures&&V.enable();let W=new ir(m),te=new Vs(m);m.doubleClickZoom=new cl(te,W),this._add("tapZoom",W),this._add("clickZoom",te),l.interactive&&l.doubleClickZoom&&m.doubleClickZoom.enable();let se=new Ta;this._add("tapDragZoom",se);let fe=m.touchPitch=new Gn(m);this._add("touchPitch",fe),l.interactive&&l.touchPitch&&m.touchPitch.enable(l.touchPitch);let _e=()=>m.project(m.getCenter()),Re=function({enable:Mt,clickTolerance:Xt,aroundCenter:Rt=!0,minPixelCenterThreshold:zt=100,rotateDegreesPerPixelMoved:jt=.8},br){let Cr=new ai({checkCorrectEvent:Pr=>U.mouseButton(Pr)===0&&Pr.ctrlKey||U.mouseButton(Pr)===2&&!Pr.ctrlKey});return new dr({clickTolerance:Xt,move:(Pr,Ir)=>{let Ai=br();if(Rt&&Math.abs(Ai.y-Pr.y)>zt)return{bearingDelta:o.cy(new o.P(Pr.x,Ir.y),Ir,Ai)};let _i=(Ir.x-Pr.x)*jt;return Rt&&Ir.yU.mouseButton(jt)===0&&jt.ctrlKey||U.mouseButton(jt)===2});return new dr({clickTolerance:Xt,move:(jt,br)=>({pitchDelta:(br.y-jt.y)*Rt}),moveStateManager:zt,enable:Mt,assignEvents:$i})}(l),je=function({enable:Mt,clickTolerance:Xt,rollDegreesPerPixelMoved:Rt=.3},zt){let jt=new ai({checkCorrectEvent:br=>U.mouseButton(br)===2&&br.ctrlKey});return new dr({clickTolerance:Xt,move:(br,Cr)=>{let Pr=zt(),Ir=(Cr.x-br.x)*Rt;return Cr.yU.mouseButton(zt)===0&&!zt.ctrlKey});return new dr({clickTolerance:Xt,move:(zt,jt)=>({around:jt,panDelta:jt.sub(zt)}),activateOnStart:!0,moveStateManager:Rt,enable:Mt,assignEvents:$i})}(l),yt=new bn(l,m);m.dragPan=new en(T,tt,yt),this._add("mousePan",tt),this._add("touchPan",yt,["touchZoom","touchRotate"]),l.interactive&&l.dragPan&&m.dragPan.enable(l.dragPan);let At=new $n,vt=new qi;m.touchZoomRotate=new Ju(T,vt,At,se),this._add("touchRotate",At,["touchPan","touchZoom"]),this._add("touchZoom",vt,["touchPan","touchRotate"]),l.interactive&&l.touchZoomRotate&&m.touchZoomRotate.enable(l.touchZoomRotate),this._add("blockableMapEvent",new gt(m));let Lt=m.scrollZoom=new yc(m,()=>this._triggerRenderFrame());this._add("scrollZoom",Lt,["mousePan"]),l.interactive&&l.scrollZoom&&m.scrollZoom.enable(l.scrollZoom);let Kt=m.keyboard=new Dn(m);this._add("keyboard",Kt),l.interactive&&l.keyboard&&m.keyboard.enable()}_add(l,m,T){this._handlers.push({handlerName:l,handler:m,allowed:T}),this._handlersById[l]=m}stop(l){if(!this._updatingCamera){for(let{handler:m}of this._handlers)m.reset();this._inertia.clear(),this._fireEvents({},{},l),this._changes=[]}}isActive(){for(let{handler:l}of this._handlers)if(l.isActive())return!0;return!1}isZooming(){return!!this._eventsInProgress.zoom||this._map.scrollZoom.isZooming()}isRotating(){return!!this._eventsInProgress.rotate}isMoving(){return Boolean(Gc(this._eventsInProgress))||this.isZooming()}_blockedByActive(l,m,T){for(let P in l)if(P!==T&&(!m||m.indexOf(P)<0))return!0;return!1}_getMapTouches(l){let m=[];for(let T of l)this._el.contains(T.target)&&m.push(T);return m}mergeHandlerResult(l,m,T,P,V){if(!T)return;o.e(l,T);let W={handlerName:P,originalEvent:T.originalEvent||V};T.zoomDelta!==void 0&&(m.zoom=W),T.panDelta!==void 0&&(m.drag=W),T.rollDelta!==void 0&&(m.roll=W),T.pitchDelta!==void 0&&(m.pitch=W),T.bearingDelta!==void 0&&(m.rotate=W)}_applyChanges(){let l={},m={},T={};for(let[P,V,W]of this._changes)P.panDelta&&(l.panDelta=(l.panDelta||new o.P(0,0))._add(P.panDelta)),P.zoomDelta&&(l.zoomDelta=(l.zoomDelta||0)+P.zoomDelta),P.bearingDelta&&(l.bearingDelta=(l.bearingDelta||0)+P.bearingDelta),P.pitchDelta&&(l.pitchDelta=(l.pitchDelta||0)+P.pitchDelta),P.rollDelta&&(l.rollDelta=(l.rollDelta||0)+P.rollDelta),P.around!==void 0&&(l.around=P.around),P.pinchAround!==void 0&&(l.pinchAround=P.pinchAround),P.noInertia&&(l.noInertia=P.noInertia),o.e(m,V),o.e(T,W);this._updateMapTransform(l,m,T),this._changes=[]}_updateMapTransform(l,m,T){let P=this._map,V=P._getTransformForUpdate(),W=P.terrain;if(!(Hc(l)||W&&this._terrainMovement))return this._fireEvents(m,T,!0);P._stop(!0);let{panDelta:te,zoomDelta:se,bearingDelta:fe,pitchDelta:_e,rollDelta:Re,around:Ie,pinchAround:je}=l;je!==void 0&&(Ie=je),Ie=Ie||P.transform.centerPoint,W&&!V.isPointOnMapSurface(Ie)&&(Ie=V.centerPoint);let tt={panDelta:te,zoomDelta:se,rollDelta:Re,pitchDelta:_e,bearingDelta:fe,around:Ie};this._map.cameraHelper.useGlobeControls&&!V.isPointOnMapSurface(Ie)&&(Ie=V.centerPoint);let yt=Ie.distSqr(V.centerPoint)<.01?V.center:V.screenPointToLocation(te?Ie.sub(te):Ie);this._handleMapControls({terrain:W,tr:V,deltasForHelper:tt,preZoomAroundLoc:yt,combinedEventsInProgress:m,panDelta:te}),P._applyUpdatedTransform(V),this._map._update(),l.noInertia||this._inertia.record(l),this._fireEvents(m,T,!0)}_handleMapControls({terrain:l,tr:m,deltasForHelper:T,preZoomAroundLoc:P,combinedEventsInProgress:V,panDelta:W}){let te=this._map.cameraHelper;if(te.handleMapControlsRollPitchBearingZoom(T,m),l)return te.useGlobeControls?(this._terrainMovement||!V.drag&&!V.zoom||(this._terrainMovement=!0,this._map._elevationFreeze=!0),void te.handleMapControlsPan(T,m,P)):this._terrainMovement||!V.drag&&!V.zoom?void(V.drag&&this._terrainMovement&&W?m.setCenter(m.screenPointToLocation(m.centerPoint.sub(W))):te.handleMapControlsPan(T,m,P)):(this._terrainMovement=!0,this._map._elevationFreeze=!0,void te.handleMapControlsPan(T,m,P));te.handleMapControlsPan(T,m,P)}_fireEvents(l,m,T){let P=Gc(this._eventsInProgress),V=Gc(l),W={};for(let Re in l){let{originalEvent:Ie}=l[Re];this._eventsInProgress[Re]||(W[`${Re}start`]=Ie),this._eventsInProgress[Re]=l[Re]}!P&&V&&this._fireEvent("movestart",V.originalEvent);for(let Re in W)this._fireEvent(Re,W[Re]);V&&this._fireEvent("move",V.originalEvent);for(let Re in l){let{originalEvent:Ie}=l[Re];this._fireEvent(Re,Ie)}let te={},se;for(let Re in this._eventsInProgress){let{handlerName:Ie,originalEvent:je}=this._eventsInProgress[Re];this._handlersById[Ie].isActive()||(delete this._eventsInProgress[Re],se=m[Ie]||je,te[`${Re}end`]=se)}for(let Re in te)this._fireEvent(Re,te[Re]);let fe=Gc(this._eventsInProgress),_e=(P||V)&&!fe;if(_e&&this._terrainMovement){this._map._elevationFreeze=!1,this._terrainMovement=!1;let Re=this._map._getTransformForUpdate();this._map.getCenterClampedToGround()&&Re.recalculateZoomAndCenter(this._map.terrain),this._map._applyUpdatedTransform(Re)}if(T&&_e){this._updatingCamera=!0;let Re=this._inertia._onMoveEnd(this._map.dragPan._inertiaOptions),Ie=je=>je!==0&&-this._bearingSnap{delete this._frameId,this.handleEvent(new Fa("renderFrame",{timeStamp:l})),this._applyChanges()})}_triggerRenderFrame(){this._frameId===void 0&&(this._frameId=this._requestFrame())}}class wd extends o.E{constructor(l,m,T){super(),this._renderFrameCallback=()=>{let P=Math.min((k()-this._easeStart)/this._easeOptions.duration,1);this._onEaseFrame(this._easeOptions.easing(P)),P<1&&this._easeFrameId?this._easeFrameId=this._requestRenderFrame(this._renderFrameCallback):this.stop()},this._moving=!1,this._zooming=!1,this.transform=l,this._bearingSnap=T.bearingSnap,this.cameraHelper=m,this.on("moveend",()=>{delete this._requestedCameraState})}migrateProjection(l,m){l.apply(this.transform),this.transform=l,this.cameraHelper=m}getCenter(){return new o.V(this.transform.center.lng,this.transform.center.lat)}setCenter(l,m){return this.jumpTo({center:l},m)}getCenterElevation(){return this.transform.elevation}setCenterElevation(l,m){return this.jumpTo({elevation:l},m),this}getCenterClampedToGround(){return this._centerClampedToGround}setCenterClampedToGround(l){this._centerClampedToGround=l}panBy(l,m,T){return l=o.P.convert(l).mult(-1),this.panTo(this.transform.center,o.e({offset:l},m),T)}panTo(l,m,T){return this.easeTo(o.e({center:l},m),T)}getZoom(){return this.transform.zoom}setZoom(l,m){return this.jumpTo({zoom:l},m),this}zoomTo(l,m,T){return this.easeTo(o.e({zoom:l},m),T)}zoomIn(l,m){return this.zoomTo(this.getZoom()+1,l,m),this}zoomOut(l,m){return this.zoomTo(this.getZoom()-1,l,m),this}getVerticalFieldOfView(){return this.transform.fov}setVerticalFieldOfView(l,m){return l!=this.transform.fov&&(this.transform.setFov(l),this.fire(new o.l("movestart",m)).fire(new o.l("move",m)).fire(new o.l("moveend",m))),this}getBearing(){return this.transform.bearing}setBearing(l,m){return this.jumpTo({bearing:l},m),this}getPadding(){return this.transform.padding}setPadding(l,m){return this.jumpTo({padding:l},m),this}rotateTo(l,m,T){return this.easeTo(o.e({bearing:l},m),T)}resetNorth(l,m){return this.rotateTo(0,o.e({duration:1e3},l),m),this}resetNorthPitch(l,m){return this.easeTo(o.e({bearing:0,pitch:0,roll:0,duration:1e3},l),m),this}snapToNorth(l,m){return Math.abs(this.getBearing()){yt.easeFunc(At),this.terrain&&!l.freezeElevation&&this._updateElevation(At),this._applyUpdatedTransform(T),this._fireMoveEvents(m)},At=>{this.terrain&&l.freezeElevation&&this._finalizeElevation(),this._afterEase(m,At)},l),this}_prepareEase(l,m,T={}){this._moving=!0,m||T.moving||this.fire(new o.l("movestart",l)),this._zooming&&!T.zooming&&this.fire(new o.l("zoomstart",l)),this._rotating&&!T.rotating&&this.fire(new o.l("rotatestart",l)),this._pitching&&!T.pitching&&this.fire(new o.l("pitchstart",l)),this._rolling&&!T.rolling&&this.fire(new o.l("rollstart",l))}_prepareElevation(l){this._elevationCenter=l,this._elevationStart=this.transform.elevation,this._elevationTarget=this.terrain.getElevationForLngLatZoom(l,this.transform.tileZoom),this._elevationFreeze=!0}_updateElevation(l){this._elevationStart!==void 0&&this._elevationCenter!==void 0||this._prepareElevation(this.transform.center),this.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._elevationCenter,this.transform.tileZoom));let m=this.terrain.getElevationForLngLatZoom(this._elevationCenter,this.transform.tileZoom);if(l<1&&m!==this._elevationTarget){let T=this._elevationTarget-this._elevationStart;this._elevationStart+=l*(T-(m-(T*l+this._elevationStart))/(1-l)),this._elevationTarget=m}this.transform.setElevation(o.G.number(this._elevationStart,this._elevationTarget,l))}_finalizeElevation(){this._elevationFreeze=!1,this.getCenterClampedToGround()&&this.transform.recalculateZoomAndCenter(this.terrain)}_getTransformForUpdate(){return this.transformCameraUpdate||this.terrain?(this._requestedCameraState||(this._requestedCameraState=this.transform.clone()),this._requestedCameraState):this.transform}_elevateCameraIfInsideTerrain(l){if(!this.terrain&&l.elevation>=0&&l.pitch<=90)return{};let m=l.getCameraLngLat(),T=l.getCameraAltitude(),P=this.terrain?this.terrain.getElevationForLngLatZoom(m,l.zoom):0;if(Tthis._elevateCameraIfInsideTerrain(P)),this.transformCameraUpdate&&m.push(P=>this.transformCameraUpdate(P)),!m.length)return;let T=l.clone();for(let P of m){let V=T.clone(),{center:W,zoom:te,roll:se,pitch:fe,bearing:_e,elevation:Re}=P(V);W&&V.setCenter(W),Re!==void 0&&V.setElevation(Re),te!==void 0&&V.setZoom(te),se!==void 0&&V.setRoll(se),fe!==void 0&&V.setPitch(fe),_e!==void 0&&V.setBearing(_e),T.apply(V)}this.transform.apply(T)}_fireMoveEvents(l){this.fire(new o.l("move",l)),this._zooming&&this.fire(new o.l("zoom",l)),this._rotating&&this.fire(new o.l("rotate",l)),this._pitching&&this.fire(new o.l("pitch",l)),this._rolling&&this.fire(new o.l("roll",l))}_afterEase(l,m){if(this._easeId&&m&&this._easeId===m)return;delete this._easeId;let T=this._zooming,P=this._rotating,V=this._pitching,W=this._rolling;this._moving=!1,this._zooming=!1,this._rotating=!1,this._pitching=!1,this._rolling=!1,this._padding=!1,T&&this.fire(new o.l("zoomend",l)),P&&this.fire(new o.l("rotateend",l)),V&&this.fire(new o.l("pitchend",l)),W&&this.fire(new o.l("rollend",l)),this.fire(new o.l("moveend",l))}flyTo(l,m){if(!l.essential&&M.prefersReducedMotion){let Ir=o.U(l,["center","zoom","bearing","pitch","roll","elevation","padding"]);return this.jumpTo(Ir,m)}this.stop(),l=o.e({offset:[0,0],speed:1.2,curve:1.42,easing:o.cz},l);let T=this._getTransformForUpdate(),P=T.bearing,V=T.pitch,W=T.roll,te=T.padding,se="bearing"in l?this._normalizeBearing(l.bearing,P):P,fe="pitch"in l?+l.pitch:V,_e="roll"in l?this._normalizeBearing(l.roll,W):W,Re="padding"in l?l.padding:T.padding,Ie=o.P.convert(l.offset),je=T.centerPoint.add(Ie),tt=T.screenPointToLocation(je),yt=this.cameraHelper.handleFlyTo(T,{bearing:se,pitch:fe,roll:_e,padding:Re,locationAtOffset:tt,offsetAsPoint:Ie,center:l.center,minZoom:l.minZoom,zoom:l.zoom}),At=l.curve,vt=Math.max(T.width,T.height),Lt=vt/yt.scaleOfZoom,Kt=yt.pixelPathLength;typeof yt.scaleOfMinZoom=="number"&&(At=Math.sqrt(vt/yt.scaleOfMinZoom/Kt*2));let Mt=At*At;function Xt(Ir){let Ai=(Lt*Lt-vt*vt+(Ir?-1:1)*Mt*Mt*Kt*Kt)/(2*(Ir?Lt:vt)*Mt*Kt);return Math.log(Math.sqrt(Ai*Ai+1)-Ai)}function Rt(Ir){return(Math.exp(Ir)-Math.exp(-Ir))/2}function zt(Ir){return(Math.exp(Ir)+Math.exp(-Ir))/2}let jt=Xt(!1),br=function(Ir){return zt(jt)/zt(jt+At*Ir)},Cr=function(Ir){return vt*((zt(jt)*(Rt(Ai=jt+At*Ir)/zt(Ai))-Rt(jt))/Mt)/Kt;var Ai},Pr=(Xt(!0)-jt)/At;if(Math.abs(Kt)<2e-6||!isFinite(Pr)){if(Math.abs(vt-Lt)<1e-6)return this.easeTo(l,m);let Ir=Lt0,br=Ai=>Math.exp(Ir*At*Ai)}return l.duration="duration"in l?+l.duration:1e3*Pr/("screenSpeed"in l?+l.screenSpeed/At:+l.speed),l.maxDuration&&l.duration>l.maxDuration&&(l.duration=0),this._zooming=!0,this._rotating=P!==se,this._pitching=fe!==V,this._rolling=_e!==W,this._padding=!T.isPaddingEqual(Re),this._prepareEase(m,!1),this.terrain&&this._prepareElevation(yt.targetCenter),this._ease(Ir=>{let Ai=Ir*Pr,_i=1/br(Ai),hi=Cr(Ai);this._rotating&&T.setBearing(o.G.number(P,se,Ir)),this._pitching&&T.setPitch(o.G.number(V,fe,Ir)),this._rolling&&T.setRoll(o.G.number(W,_e,Ir)),this._padding&&(T.interpolatePadding(te,Re,Ir),je=T.centerPoint.add(Ie)),yt.easeFunc(Ir,_i,hi,je),this.terrain&&!l.freezeElevation&&this._updateElevation(Ir),this._applyUpdatedTransform(T),this._fireMoveEvents(m)},()=>{this.terrain&&l.freezeElevation&&this._finalizeElevation(),this._afterEase(m)},l),this}isEasing(){return!!this._easeFrameId}stop(){return this._stop()}_stop(l,m){var T;if(this._easeFrameId&&(this._cancelRenderFrame(this._easeFrameId),delete this._easeFrameId,delete this._onEaseFrame),this._onEaseEnd){let P=this._onEaseEnd;delete this._onEaseEnd,P.call(this,m)}return l||(T=this.handlers)===null||T===void 0||T.stop(!1),this}_ease(l,m,T){T.animate===!1||T.duration===0?(l(1),m()):(this._easeStart=k(),this._easeOptions=T,this._onEaseFrame=l,this._onEaseEnd=m,this._easeFrameId=this._requestRenderFrame(this._renderFrameCallback))}_normalizeBearing(l,m){l=o.W(l,-180,180);let T=Math.abs(l-m);return Math.abs(l-360-m)MapLibre'};class Ou{constructor(l=Td){this._toggleAttribution=()=>{this._container.classList.contains("maplibregl-compact")&&(this._container.classList.contains("maplibregl-compact-show")?(this._container.setAttribute("open",""),this._container.classList.remove("maplibregl-compact-show")):(this._container.classList.add("maplibregl-compact-show"),this._container.removeAttribute("open")))},this._updateData=m=>{!m||m.sourceDataType!=="metadata"&&m.sourceDataType!=="visibility"&&m.dataType!=="style"&&m.type!=="terrain"||this._updateAttributions()},this._updateCompact=()=>{this._map.getCanvasContainer().offsetWidth<=640||this._compact?this._compact===!1?this._container.setAttribute("open",""):this._container.classList.contains("maplibregl-compact")||this._container.classList.contains("maplibregl-attrib-empty")||(this._container.setAttribute("open",""),this._container.classList.add("maplibregl-compact","maplibregl-compact-show")):(this._container.setAttribute("open",""),this._container.classList.contains("maplibregl-compact")&&this._container.classList.remove("maplibregl-compact","maplibregl-compact-show"))},this._updateCompactMinimize=()=>{this._container.classList.contains("maplibregl-compact")&&this._container.classList.contains("maplibregl-compact-show")&&this._container.classList.remove("maplibregl-compact-show")},this.options=l}getDefaultPosition(){return"bottom-right"}onAdd(l){return this._map=l,this._compact=this.options.compact,this._container=U.create("details","maplibregl-ctrl maplibregl-ctrl-attrib"),this._compactButton=U.create("summary","maplibregl-ctrl-attrib-button",this._container),this._compactButton.addEventListener("click",this._toggleAttribution),this._setElementTitle(this._compactButton,"ToggleAttribution"),this._innerContainer=U.create("div","maplibregl-ctrl-attrib-inner",this._container),this._updateAttributions(),this._updateCompact(),this._map.on("styledata",this._updateData),this._map.on("sourcedata",this._updateData),this._map.on("terrain",this._updateData),this._map.on("resize",this._updateCompact),this._map.on("drag",this._updateCompactMinimize),this._container}onRemove(){U.remove(this._container),this._map.off("styledata",this._updateData),this._map.off("sourcedata",this._updateData),this._map.off("terrain",this._updateData),this._map.off("resize",this._updateCompact),this._map.off("drag",this._updateCompactMinimize),this._map=void 0,this._compact=void 0,this._attribHTML=void 0}_setElementTitle(l,m){let T=this._map._getUIString(`AttributionControl.${m}`);l.title=T,l.setAttribute("aria-label",T)}_updateAttributions(){if(!this._map.style)return;let l=[];if(this.options.customAttribution&&(Array.isArray(this.options.customAttribution)?l=l.concat(this.options.customAttribution.map(P=>typeof P!="string"?"":P)):typeof this.options.customAttribution=="string"&&l.push(this.options.customAttribution)),this._map.style.stylesheet){let P=this._map.style.stylesheet;this.styleOwner=P.owner,this.styleId=P.id}let m=this._map.style.tileManagers;for(let P in m){let V=m[P];if(V.used||V.usedForTerrain){let W=V.getSource();W.attribution&&l.indexOf(W.attribution)<0&&l.push(W.attribution)}}l=l.filter(P=>String(P).trim()),l.sort((P,V)=>P.length-V.length),l=l.filter((P,V)=>{for(let W=V+1;W=0)return!1;return!0});let T=l.join(" | ");T!==this._attribHTML&&(this._attribHTML=T,l.length?(this._innerContainer.innerHTML=U.sanitize(T),this._container.classList.remove("maplibregl-attrib-empty")):this._container.classList.add("maplibregl-attrib-empty"),this._updateCompact(),this._editLink=null)}}class iu{constructor(l={}){this._updateCompact=()=>{let m=this._container.children;if(m.length){let T=m[0];this._map.getCanvasContainer().offsetWidth<=640||this._compact?this._compact!==!1&&T.classList.add("maplibregl-compact"):T.classList.remove("maplibregl-compact")}},this.options=l}getDefaultPosition(){return"bottom-left"}onAdd(l){this._map=l,this._compact=this.options&&this.options.compact,this._container=U.create("div","maplibregl-ctrl");let m=U.create("a","maplibregl-ctrl-logo");return m.target="_blank",m.rel="noopener nofollow",m.href="https://maplibre.org/",m.setAttribute("aria-label",this._map._getUIString("LogoControl.Title")),m.setAttribute("rel","noopener nofollow"),this._container.appendChild(m),this._container.style.display="block",this._map.on("resize",this._updateCompact),this._updateCompact(),this._container}onRemove(){U.remove(this._container),this._map.off("resize",this._updateCompact),this._map=void 0,this._compact=void 0}}class Sa{constructor(){this._queue=[],this._id=0,this._cleared=!1,this._currentlyRunning=!1}add(l){let m=++this._id;return this._queue.push({callback:l,id:m,cancelled:!1}),m}remove(l){let m=this._currentlyRunning,T=m?this._queue.concat(m):this._queue;for(let P of T)if(P.id===l)return void(P.cancelled=!0)}run(l=0){if(this._currentlyRunning)throw new Error("Attempting to run(), but is already running.");let m=this._currentlyRunning=this._queue;this._queue=[];for(let T of m)if(!T.cancelled&&(T.callback(l),this._cleared))break;this._cleared=!1,this._currentlyRunning=!1}clear(){this._currentlyRunning&&(this._cleared=!0),this._queue=[]}}var ia=o.aV([{name:"a_pos3d",type:"Int16",components:3}]);class zl extends o.E{constructor(l){super(),this._lastTilesetChange=k(),this.tileManager=l,this._tiles={},this._renderableTilesKeys=[],this._sourceTileCache={},this.minzoom=0,this.maxzoom=22,this.deltaZoom=1,this.tileSize=l._source.tileSize*2**this.deltaZoom,l.usedForTerrain=!0,l.tileSize=this.tileSize}destruct(){this.tileManager.usedForTerrain=!1,this.tileManager.tileSize=null}getSource(){return this.tileManager._source}update(l,m){this.tileManager.update(l,m),this._renderableTilesKeys=[];let T={};for(let P of Ke(l,{tileSize:this.tileSize,minzoom:this.minzoom,maxzoom:this.maxzoom,reparseOverscaled:!1,terrain:m,calculateTileZoom:this.tileManager._source.calculateTileZoom}))T[P.key]=!0,this._renderableTilesKeys.push(P.key),this._tiles[P.key]||(P.terrainRttPosMatrix32f=new Float64Array(16),o.c8(P.terrainRttPosMatrix32f,0,o.a5,o.a5,0,0,1),this._tiles[P.key]=new ti(P,this.tileSize),this._lastTilesetChange=k());for(let P in this._tiles)T[P]||delete this._tiles[P]}freeRtt(l){for(let m in this._tiles){let T=this._tiles[m];(!l||T.tileID.equals(l)||T.tileID.isChildOf(l)||l.isChildOf(T.tileID))&&(T.rtt=[])}}getRenderableTiles(){return this._renderableTilesKeys.map(l=>this.getTileByID(l))}getTileByID(l){return this._tiles[l]}getTerrainCoords(l,m){return m?this._getTerrainCoordsForTileRanges(l,m):this._getTerrainCoordsForRegularTile(l)}_getTerrainCoordsForRegularTile(l){let m={};for(let T of this._renderableTilesKeys){let P=this._tiles[T].tileID,V=l.clone(),W=o.bl();if(P.canonical.equals(l.canonical))o.c8(W,0,o.a5,o.a5,0,0,1);else if(P.canonical.isChildOf(l.canonical)){let te=P.canonical.z-l.canonical.z,se=P.canonical.x-(P.canonical.x>>te<>te<>te;o.c8(W,0,_e,_e,0,0,1),o.O(W,W,[-se*_e,-fe*_e,0])}else{if(!l.canonical.isChildOf(P.canonical))continue;{let te=l.canonical.z-P.canonical.z,se=l.canonical.x-(l.canonical.x>>te<>te<>te;o.c8(W,0,o.a5,o.a5,0,0,1),o.O(W,W,[se*_e,fe*_e,0]),o.Q(W,W,[1/2**te,1/2**te,0])}}V.terrainRttPosMatrix32f=new Float32Array(W),m[T]=V}return m}_getTerrainCoordsForTileRanges(l,m){let T={};for(let P of this._renderableTilesKeys){let V=this._tiles[P].tileID;if(!this._isWithinTileRanges(V,m))continue;let W=l.clone(),te=o.bl();if(V.canonical.z===l.canonical.z){let se=l.canonical.x-V.canonical.x,fe=l.canonical.y-V.canonical.y;o.c8(te,0,o.a5,o.a5,0,0,1),o.O(te,te,[se*o.a5,fe*o.a5,0])}else if(V.canonical.z>l.canonical.z){let se=V.canonical.z-l.canonical.z,fe=V.canonical.x-(V.canonical.x>>se<>se<>se),Ie=l.canonical.y-(V.canonical.y>>se),je=o.a5>>se;o.c8(te,0,je,je,0,0,1),o.O(te,te,[-fe*je+Re*o.a5,-_e*je+Ie*o.a5,0])}else{let se=l.canonical.z-V.canonical.z,fe=l.canonical.x-(l.canonical.x>>se<>se<>se)-V.canonical.x,Ie=(l.canonical.y>>se)-V.canonical.y,je=o.a5<T.maxzoom&&(P=T.maxzoom),P=T.minzoom&&!V?.dem;)V=this.findTileInCaches(l.scaledTo(P--).key);return V}findTileInCaches(l){let m=this.tileManager.getTileByID(l);return m||(m=this.tileManager._outOfViewCache.getByKey(l),m)}anyTilesAfterTime(l=Date.now()){return this._lastTilesetChange>=l}_isWithinTileRanges(l,m){return m[l.canonical.z]&&l.canonical.x>=m[l.canonical.z].minTileX&&l.canonical.x<=m[l.canonical.z].maxTileX&&l.canonical.y>=m[l.canonical.z].minTileY&&l.canonical.y<=m[l.canonical.z].maxTileY}}class eh{constructor(l,m,T){this._meshCache={},this.painter=l,this.tileManager=new zl(m),this.options=T,this.exaggeration=typeof T.exaggeration=="number"?T.exaggeration:1,this.qualityFactor=2,this.meshSize=128,this._demMatrixCache={},this.coordsIndex=[],this._coordsTextureSize=1024}getDEMElevation(l,m,T,P=o.a5){var V;if(!(m>=0&&m=0&&Tl.canonical.z&&(l.canonical.z>=P?V=l.canonical.z-P:o.w("cannot calculate elevation if elevation maxzoom > source.maxzoom"));let W=l.canonical.x-(l.canonical.x>>V<>V<>8<<4|V>>8,m[W+3]=0;let T=new o.R({width:this._coordsTextureSize,height:this._coordsTextureSize},new Uint8Array(m.buffer)),P=new o.T(l,T,l.gl.RGBA,{premultiply:!1});return P.bind(l.gl.NEAREST,l.gl.CLAMP_TO_EDGE),this._coordsTexture=P,P}pointCoordinate(l){this.painter.maybeDrawDepthAndCoords(!0);let m=new Uint8Array(4),T=this.painter.context,P=T.gl,V=Math.round(l.x*this.painter.pixelRatio/devicePixelRatio),W=Math.round(l.y*this.painter.pixelRatio/devicePixelRatio),te=Math.round(this.painter.height/devicePixelRatio);T.bindFramebuffer.set(this.getFramebuffer("coords").framebuffer),P.readPixels(V,te-W-1,1,1,P.RGBA,P.UNSIGNED_BYTE,m),T.bindFramebuffer.set(null);let se=m[0]+(m[2]>>4<<8),fe=m[1]+((15&m[2])<<8),_e=this.coordsIndex[255-m[3]],Re=_e&&this.tileManager.getTileByID(_e);if(!Re)return null;let Ie=this._coordsTextureSize,je=(1<0,P=T&&l.canonical.y===0,V=T&&l.canonical.y===(1<l.id!==m),this._recentlyUsed.push(l.id)}stampObject(l){l.stamp=++this._stamp}getOrCreateFreeObject(){for(let m of this._recentlyUsed)if(!this._objects[m].inUse)return this._objects[m];if(this._objects.length>=this._size)throw new Error("No free RenderPool available, call freeAllObjects() required!");let l=this._createObject(this._objects.length);return this._objects.push(l),l}freeObject(l){l.inUse=!1}freeAllObjects(){for(let l of this._objects)this.freeObject(l)}isFull(){return!(this._objects.length!l.inUse)===!1}}let Ul={background:!0,fill:!0,line:!0,raster:!0,hillshade:!0,"color-relief":!0};class _s{constructor(l,m){this.painter=l,this.terrain=m,this.pool=new ef(l.context,30,m.tileManager.tileSize*m.qualityFactor)}destruct(){this.pool.destruct()}getTexture(l){return this.pool.getObjectForId(l.rtt[this._stacks.length-1].id).texture}prepareForRender(l,m){this._stacks=[],this._prevType=null,this._rttTiles=[],this._renderableTiles=this.terrain.tileManager.getRenderableTiles(),this._renderableLayerIds=l._order.filter(T=>!l._layers[T].isHidden(m)),this._coordsAscending={};for(let T in l.tileManagers){this._coordsAscending[T]={};let P=l.tileManagers[T].getVisibleCoordinates(),V=l.tileManagers[T].getSource(),W=V instanceof ws?V.terrainTileRanges:null;for(let te of P){let se=this.terrain.tileManager.getTerrainCoords(te,W);for(let fe in se)this._coordsAscending[T][fe]||(this._coordsAscending[T][fe]=[]),this._coordsAscending[T][fe].push(se[fe])}}this._coordsAscendingStr={};for(let T of l._order){let P=l._layers[T],V=P.source;if(Ul[P.type]&&!this._coordsAscendingStr[V]){this._coordsAscendingStr[V]={};for(let W in this._coordsAscending[V])this._coordsAscendingStr[V][W]=this._coordsAscending[V][W].map(te=>te.key).sort().join()}}for(let T of this._renderableTiles)for(let P in this._coordsAscendingStr){let V=this._coordsAscendingStr[P][T.tileID.key];V&&V!==T.rttCoords[P]&&(T.rtt=[])}}renderLayer(l,m){if(l.isHidden(this.painter.transform.zoom))return!1;let T=Object.assign(Object.assign({},m),{isRenderingToTexture:!0}),P=l.type,V=this.painter,W=this._renderableLayerIds[this._renderableLayerIds.length-1]===l.id;if(Ul[P]&&(this._prevType&&Ul[this._prevType]||this._stacks.push([]),this._prevType=P,this._stacks[this._stacks.length-1].push(l.id),!W))return!0;if(Ul[this._prevType]||Ul[P]&&W){this._prevType=P;let te=this._stacks.length-1,se=this._stacks[te]||[];for(let fe of this._renderableTiles){if(this.pool.isFull()&&(bf(this.painter,this.terrain,this._rttTiles,T),this._rttTiles=[],this.pool.freeAllObjects()),this._rttTiles.push(fe),fe.rtt[te]){let Re=this.pool.getObjectForId(fe.rtt[te].id);if(Re.stamp===fe.rtt[te].stamp){this.pool.useObject(Re);continue}}let _e=this.pool.getOrCreateFreeObject();this.pool.useObject(_e),this.pool.stampObject(_e),fe.rtt[te]={id:_e.id,stamp:_e.stamp},V.context.bindFramebuffer.set(_e.fbo.framebuffer),V.context.clear({color:o.bq.transparent,stencil:0}),V.currentStencilSource=void 0;for(let Re=0;Re{this.startMove(V,U.mousePos(this.element,V)),U.addEventListener(window,"mousemove",this.mousemove),U.addEventListener(window,"mouseup",this.mouseup)},this.mousemove=V=>{this.move(V,U.mousePos(this.element,V))},this.mouseup=V=>{this._rotatePitchHandler.dragEnd(V),this.offTemp()},this.touchstart=V=>{V.targetTouches.length!==1?this.reset():(this._startPos=this._lastPos=U.touchPos(this.element,V.targetTouches)[0],this.startMove(V,this._startPos),U.addEventListener(window,"touchmove",this.touchmove,{passive:!1}),U.addEventListener(window,"touchend",this.touchend))},this.touchmove=V=>{V.targetTouches.length!==1?this.reset():(this._lastPos=U.touchPos(this.element,V.targetTouches)[0],this.move(V,this._lastPos))},this.touchend=V=>{V.targetTouches.length===0&&this._startPos&&this._lastPos&&this._startPos.dist(this._lastPos){this._rotatePitchHandler.reset(),delete this._startPos,delete this._lastPos,this.offTemp()},this._clickTolerance=10,this.element=m;let P=new Si;this._rotatePitchHandler=new dr({clickTolerance:3,move:(V,W)=>{let te=m.getBoundingClientRect(),se=new o.P((te.bottom-te.top)/2,(te.right-te.left)/2);return{bearingDelta:o.cy(new o.P(V.x,W.y),W,se),pitchDelta:T?-.5*(W.y-V.y):void 0}},moveStateManager:P,enable:!0,assignEvents:()=>{}}),this.map=l,U.addEventListener(m,"mousedown",this.mousedown),U.addEventListener(m,"touchstart",this.touchstart,{passive:!1}),U.addEventListener(m,"touchcancel",this.reset)}startMove(l,m){this._rotatePitchHandler.dragStart(l,m),U.disableDrag()}move(l,m){let T=this.map,{bearingDelta:P,pitchDelta:V}=this._rotatePitchHandler.dragMove(l,m)||{};P&&T.setBearing(T.getBearing()+P),V&&T.setPitch(T.getPitch()+V)}off(){let l=this.element;U.removeEventListener(l,"mousedown",this.mousedown),U.removeEventListener(l,"touchstart",this.touchstart,{passive:!1}),U.removeEventListener(window,"touchmove",this.touchmove,{passive:!1}),U.removeEventListener(window,"touchend",this.touchend),U.removeEventListener(l,"touchcancel",this.reset),this.offTemp()}offTemp(){U.enableDrag(),U.removeEventListener(window,"mousemove",this.mousemove),U.removeEventListener(window,"mouseup",this.mouseup),U.removeEventListener(window,"touchmove",this.touchmove,{passive:!1}),U.removeEventListener(window,"touchend",this.touchend)}}let th;function Kf(z,l,m,T=!1){if(T||!m.getCoveringTilesDetailsProvider().allowWorldCopies())return z?.wrap();let P=new o.V(z.lng,z.lat);if(z=new o.V(z.lng,z.lat),l){let V=new o.V(z.lng-360,z.lat),W=new o.V(z.lng+360,z.lat),te=m.locationToScreenPoint(z).distSqr(l);m.locationToScreenPoint(V).distSqr(l)180;){let V=m.locationToScreenPoint(z);if(V.x>=0&&V.y>=0&&V.x<=m.width&&V.y<=m.height)break;z.lng>m.center.lng?z.lng-=360:z.lng+=360}return z.lng!==P.lng&&m.isPointOnMapSurface(m.locationToScreenPoint(z))?z:P}let wf={center:"translate(-50%,-50%)",top:"translate(-50%,0)","top-left":"translate(0,0)","top-right":"translate(-100%,0)",bottom:"translate(-50%,-100%)","bottom-left":"translate(0,-100%)","bottom-right":"translate(-100%,-100%)",left:"translate(0,-50%)",right:"translate(-100%,-50%)"};function rf(z,l,m){let T=z.classList;for(let P in wf)T.remove(`maplibregl-${m}-anchor-${P}`);T.add(`maplibregl-${m}-anchor-${l}`)}class Tf extends o.E{constructor(l){if(super(),this._onKeyPress=m=>{let T=m.code,P=m.charCode||m.keyCode;T!=="Space"&&T!=="Enter"&&P!==32&&P!==13||this.togglePopup()},this._onMapClick=m=>{let T=m.originalEvent.target,P=this._element;this._popup&&(T===P||P.contains(T))&&this.togglePopup()},this._update=m=>{if(!this._map)return;let T=this._map.loaded()&&!this._map.isMoving();(m?.type==="terrain"||m?.type==="render"&&!T)&&this._map.once("render",this._update),this._lngLat=Kf(this._lngLat,this._flatPos,this._map.transform),this._flatPos=this._pos=this._map.project(this._lngLat)._add(this._offset),this._map.terrain&&(this._flatPos=this._map.transform.locationToScreenPoint(this._lngLat)._add(this._offset));let P="";this._rotationAlignment==="viewport"||this._rotationAlignment==="auto"?P=`rotateZ(${this._rotation}deg)`:this._rotationAlignment==="map"&&(P=`rotateZ(${this._rotation-this._map.getBearing()}deg)`);let V="";this._pitchAlignment==="viewport"||this._pitchAlignment==="auto"?V="rotateX(0deg)":this._pitchAlignment==="map"&&(V=`rotateX(${this._map.getPitch()}deg)`),this._subpixelPositioning||m&&m.type!=="moveend"||(this._pos=this._pos.round()),U.setTransform(this._element,`${wf[this._anchor]} translate(${this._pos.x}px, ${this._pos.y}px) ${V} ${P}`),M.frameAsync(new AbortController).then(()=>{this._updateOpacity(m&&m.type==="moveend")}).catch(()=>{})},this._onMove=m=>{if(!this._isDragging){let T=this._clickTolerance||this._map._clickTolerance;this._isDragging=m.point.dist(this._pointerdownPos)>=T}this._isDragging&&(this._pos=m.point.sub(this._positionDelta),this._lngLat=this._map.unproject(this._pos),this.setLngLat(this._lngLat),this._element.style.pointerEvents="none",this._state==="pending"&&(this._state="active",this.fire(new o.l("dragstart"))),this.fire(new o.l("drag")))},this._onUp=()=>{this._element.style.pointerEvents="auto",this._positionDelta=null,this._pointerdownPos=null,this._isDragging=!1,this._map.off("mousemove",this._onMove),this._map.off("touchmove",this._onMove),this._state==="active"&&this.fire(new o.l("dragend")),this._state="inactive"},this._addDragHandler=m=>{this._element.contains(m.originalEvent.target)&&(m.preventDefault(),this._positionDelta=m.point.sub(this._pos).add(this._offset),this._pointerdownPos=m.point,this._state="pending",this._map.on("mousemove",this._onMove),this._map.on("touchmove",this._onMove),this._map.once("mouseup",this._onUp),this._map.once("touchend",this._onUp))},this._anchor=l&&l.anchor||"center",this._color=l&&l.color||"#3FB1CE",this._scale=l&&l.scale||1,this._draggable=l&&l.draggable||!1,this._clickTolerance=l&&l.clickTolerance||0,this._subpixelPositioning=l&&l.subpixelPositioning||!1,this._isDragging=!1,this._state="inactive",this._rotation=l&&l.rotation||0,this._rotationAlignment=l&&l.rotationAlignment||"auto",this._pitchAlignment=l&&l.pitchAlignment&&l.pitchAlignment!=="auto"?l.pitchAlignment:this._rotationAlignment,this.setOpacity(l?.opacity,l?.opacityWhenCovered),l&&l.element)this._element=l.element,this._offset=o.P.convert(l&&l.offset||[0,0]);else{this._defaultMarker=!0,this._element=U.create("div");let m=U.createNS("http://www.w3.org/2000/svg","svg"),T=41,P=27;m.setAttributeNS(null,"display","block"),m.setAttributeNS(null,"height",`${T}px`),m.setAttributeNS(null,"width",`${P}px`),m.setAttributeNS(null,"viewBox",`0 0 ${P} ${T}`);let V=U.createNS("http://www.w3.org/2000/svg","g");V.setAttributeNS(null,"stroke","none"),V.setAttributeNS(null,"stroke-width","1"),V.setAttributeNS(null,"fill","none"),V.setAttributeNS(null,"fill-rule","evenodd");let W=U.createNS("http://www.w3.org/2000/svg","g");W.setAttributeNS(null,"fill-rule","nonzero");let te=U.createNS("http://www.w3.org/2000/svg","g");te.setAttributeNS(null,"transform","translate(3.0, 29.0)"),te.setAttributeNS(null,"fill","#000000");let se=[{rx:"10.5",ry:"5.25002273"},{rx:"10.5",ry:"5.25002273"},{rx:"9.5",ry:"4.77275007"},{rx:"8.5",ry:"4.29549936"},{rx:"7.5",ry:"3.81822308"},{rx:"6.5",ry:"3.34094679"},{rx:"5.5",ry:"2.86367051"},{rx:"4.5",ry:"2.38636864"}];for(let vt of se){let Lt=U.createNS("http://www.w3.org/2000/svg","ellipse");Lt.setAttributeNS(null,"opacity","0.04"),Lt.setAttributeNS(null,"cx","10.5"),Lt.setAttributeNS(null,"cy","5.80029008"),Lt.setAttributeNS(null,"rx",vt.rx),Lt.setAttributeNS(null,"ry",vt.ry),te.appendChild(Lt)}let fe=U.createNS("http://www.w3.org/2000/svg","g");fe.setAttributeNS(null,"fill",this._color);let _e=U.createNS("http://www.w3.org/2000/svg","path");_e.setAttributeNS(null,"d","M27,13.5 C27,19.074644 20.250001,27.000002 14.75,34.500002 C14.016665,35.500004 12.983335,35.500004 12.25,34.500002 C6.7499993,27.000002 0,19.222562 0,13.5 C0,6.0441559 6.0441559,0 13.5,0 C20.955844,0 27,6.0441559 27,13.5 Z"),fe.appendChild(_e);let Re=U.createNS("http://www.w3.org/2000/svg","g");Re.setAttributeNS(null,"opacity","0.25"),Re.setAttributeNS(null,"fill","#000000");let Ie=U.createNS("http://www.w3.org/2000/svg","path");Ie.setAttributeNS(null,"d","M13.5,0 C6.0441559,0 0,6.0441559 0,13.5 C0,19.222562 6.7499993,27 12.25,34.5 C13,35.522727 14.016664,35.500004 14.75,34.5 C20.250001,27 27,19.074644 27,13.5 C27,6.0441559 20.955844,0 13.5,0 Z M13.5,1 C20.415404,1 26,6.584596 26,13.5 C26,15.898657 24.495584,19.181431 22.220703,22.738281 C19.945823,26.295132 16.705119,30.142167 13.943359,33.908203 C13.743445,34.180814 13.612715,34.322738 13.5,34.441406 C13.387285,34.322738 13.256555,34.180814 13.056641,33.908203 C10.284481,30.127985 7.4148684,26.314159 5.015625,22.773438 C2.6163816,19.232715 1,15.953538 1,13.5 C1,6.584596 6.584596,1 13.5,1 Z"),Re.appendChild(Ie);let je=U.createNS("http://www.w3.org/2000/svg","g");je.setAttributeNS(null,"transform","translate(6.0, 7.0)"),je.setAttributeNS(null,"fill","#FFFFFF");let tt=U.createNS("http://www.w3.org/2000/svg","g");tt.setAttributeNS(null,"transform","translate(8.0, 8.0)");let yt=U.createNS("http://www.w3.org/2000/svg","circle");yt.setAttributeNS(null,"fill","#000000"),yt.setAttributeNS(null,"opacity","0.25"),yt.setAttributeNS(null,"cx","5.5"),yt.setAttributeNS(null,"cy","5.5"),yt.setAttributeNS(null,"r","5.4999962");let At=U.createNS("http://www.w3.org/2000/svg","circle");At.setAttributeNS(null,"fill","#FFFFFF"),At.setAttributeNS(null,"cx","5.5"),At.setAttributeNS(null,"cy","5.5"),At.setAttributeNS(null,"r","5.4999962"),tt.appendChild(yt),tt.appendChild(At),W.appendChild(te),W.appendChild(fe),W.appendChild(Re),W.appendChild(je),W.appendChild(tt),m.appendChild(W),m.setAttributeNS(null,"height",T*this._scale+"px"),m.setAttributeNS(null,"width",P*this._scale+"px"),this._element.appendChild(m),this._offset=o.P.convert(l&&l.offset||[0,-14])}if(this._element.classList.add("maplibregl-marker"),this._element.addEventListener("dragstart",m=>{m.preventDefault()}),this._element.addEventListener("mousedown",m=>{m.preventDefault()}),rf(this._element,this._anchor,"marker"),l&&l.className)for(let m of l.className.split(" "))this._element.classList.add(m);this._popup=null}addTo(l){return this.remove(),this._map=l,this._element.hasAttribute("aria-label")||this._element.setAttribute("aria-label",l._getUIString("Marker.Title")),this._element.hasAttribute("role")||this._element.setAttribute("role","button"),l.getCanvasContainer().appendChild(this._element),l.on("move",this._update),l.on("moveend",this._update),l.on("terrain",this._update),l.on("projectiontransition",this._update),this.setDraggable(this._draggable),this._update(),this._map.on("click",this._onMapClick),this}remove(){return this._opacityTimeout&&(clearTimeout(this._opacityTimeout),delete this._opacityTimeout),this._map&&(this._map.off("click",this._onMapClick),this._map.off("move",this._update),this._map.off("moveend",this._update),this._map.off("terrain",this._update),this._map.off("projectiontransition",this._update),this._map.off("mousedown",this._addDragHandler),this._map.off("touchstart",this._addDragHandler),this._map.off("mouseup",this._onUp),this._map.off("touchend",this._onUp),this._map.off("mousemove",this._onMove),this._map.off("touchmove",this._onMove),delete this._map),U.remove(this._element),this._popup&&this._popup.remove(),this}getLngLat(){return this._lngLat}setLngLat(l){return this._lngLat=o.V.convert(l),this._pos=null,this._popup&&this._popup.setLngLat(this._lngLat),this._update(),this}getElement(){return this._element}setPopup(l){if(this._popup&&(this._popup.remove(),this._popup=null,this._element.removeEventListener("keypress",this._onKeyPress),this._originalTabIndex||this._element.removeAttribute("tabindex")),l){if(!("offset"in l.options)){let P=Math.abs(13.5)/Math.SQRT2;l.options.offset=this._defaultMarker?{top:[0,0],"top-left":[0,0],"top-right":[0,0],bottom:[0,-38.1],"bottom-left":[P,-1*(38.1-13.5+P)],"bottom-right":[-P,-1*(38.1-13.5+P)],left:[13.5,-1*(38.1-13.5)],right:[-13.5,-1*(38.1-13.5)]}:this._offset}this._popup=l,this._originalTabIndex=this._element.getAttribute("tabindex"),this._originalTabIndex||this._element.setAttribute("tabindex","0"),this._element.addEventListener("keypress",this._onKeyPress)}return this}setSubpixelPositioning(l){return this._subpixelPositioning=l,this}getPopup(){return this._popup}togglePopup(){let l=this._popup;return this._element.style.opacity===this._opacityWhenCovered?this:l?(l.isOpen()?l.remove():(l.setLngLat(this._lngLat),l.addTo(this._map)),this):this}_updateOpacity(l=!1){var m,T;let P=(m=this._map)===null||m===void 0?void 0:m.terrain,V=this._map.transform.isLocationOccluded(this._lngLat);if(!P||V){let je=V?this._opacityWhenCovered:this._opacity;return void(this._element.style.opacity!==je&&(this._element.style.opacity=je))}if(l)this._opacityTimeout=null;else{if(this._opacityTimeout)return;this._opacityTimeout=setTimeout(()=>{this._opacityTimeout=null},100)}let W=this._map,te=W.terrain.depthAtPoint(this._pos),se=W.terrain.getElevationForLngLat(this._lngLat);if(W.transform.lngLatToCameraDepth(this._lngLat,se)-te<.006)return void(this._element.style.opacity=this._opacity);let fe=-this._offset.y/W.transform.pixelsPerMeter,_e=Math.sin(W.getPitch()*Math.PI/180)*fe,Re=W.terrain.depthAtPoint(new o.P(this._pos.x,this._pos.y-this._offset.y)),Ie=W.transform.lngLatToCameraDepth(this._lngLat,se+_e)-Re>.006;!((T=this._popup)===null||T===void 0)&&T.isOpen()&&Ie&&this._popup.remove(),this._element.style.opacity=Ie?this._opacityWhenCovered:this._opacity}getOffset(){return this._offset}setOffset(l){return this._offset=o.P.convert(l),this._update(),this}addClassName(l){this._element.classList.add(l)}removeClassName(l){this._element.classList.remove(l)}toggleClassName(l){return this._element.classList.toggle(l)}setDraggable(l){return this._draggable=!!l,this._map&&(l?(this._map.on("mousedown",this._addDragHandler),this._map.on("touchstart",this._addDragHandler)):(this._map.off("mousedown",this._addDragHandler),this._map.off("touchstart",this._addDragHandler))),this}isDraggable(){return this._draggable}setRotation(l){return this._rotation=l||0,this._update(),this}getRotation(){return this._rotation}setRotationAlignment(l){return this._rotationAlignment=l||"auto",this._update(),this}getRotationAlignment(){return this._rotationAlignment}setPitchAlignment(l){return this._pitchAlignment=l&&l!=="auto"?l:this._rotationAlignment,this._update(),this}getPitchAlignment(){return this._pitchAlignment}setOpacity(l,m){return(this._opacity===void 0||l===void 0&&m===void 0)&&(this._opacity="1",this._opacityWhenCovered="0.2"),l!==void 0&&(this._opacity=l),m!==void 0&&(this._opacityWhenCovered=m),this._map&&this._updateOpacity(!0),this}}let ig={positionOptions:{enableHighAccuracy:!1,maximumAge:0,timeout:6e3},fitBoundsOptions:{maxZoom:15},trackUserLocation:!1,showAccuracyCircle:!0,showUserLocation:!0},Fu=0,Vl=!1,b_={maxWidth:100,unit:"metric"};function ng(z,l,m){let T=m&&m.maxWidth||100,P=z._container.clientHeight/2,V=z._container.clientWidth/2,W=z.unproject([V-T/2,P]),te=z.unproject([V+T/2,P]),se=Math.round(z.project(te).x-z.project(W).x),fe=Math.min(T,se,z._container.clientWidth),_e=W.distanceTo(te);if(m&&m.unit==="imperial"){let Re=3.2808*_e;Re>5280?gm(l,fe,Re/5280,z._getUIString("ScaleControl.Miles")):gm(l,fe,Re,z._getUIString("ScaleControl.Feet"))}else m&&m.unit==="nautical"?gm(l,fe,_e/1852,z._getUIString("ScaleControl.NauticalMiles")):_e>=1e3?gm(l,fe,_e/1e3,z._getUIString("ScaleControl.Kilometers")):gm(l,fe,_e,z._getUIString("ScaleControl.Meters"))}function gm(z,l,m,T){let P=function(V){let W=Math.pow(10,`${Math.floor(V)}`.length-1),te=V/W;return te=te>=10?10:te>=5?5:te>=3?3:te>=2?2:te>=1?1:function(se){let fe=Math.pow(10,Math.ceil(-Math.log(se)/Math.LN10));return Math.round(se*fe)/fe}(te),W*te}(m);z.style.width=l*(P/m)+"px",z.innerHTML=`${P} ${T}`}let w_={closeButton:!0,closeOnClick:!0,focusAfterOpen:!0,className:"",maxWidth:"240px",subpixelPositioning:!1,locationOccludedOpacity:void 0},T_=["a[href]","[tabindex]:not([tabindex='-1'])","[contenteditable]:not([contenteditable='false'])","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])"].join(", ");function Q0(z){if(z){if(typeof z=="number"){let l=Math.round(Math.abs(z)/Math.SQRT2);return{center:new o.P(0,0),top:new o.P(0,z),"top-left":new o.P(l,l),"top-right":new o.P(-l,l),bottom:new o.P(0,-z),"bottom-left":new o.P(l,-l),"bottom-right":new o.P(-l,-l),left:new o.P(z,0),right:new o.P(-z,0)}}if(z instanceof o.P||Array.isArray(z)){let l=o.P.convert(z);return{center:l,top:l,"top-left":l,"top-right":l,bottom:l,"bottom-left":l,"bottom-right":l,left:l,right:l}}return{center:o.P.convert(z.center||[0,0]),top:o.P.convert(z.top||[0,0]),"top-left":o.P.convert(z["top-left"]||[0,0]),"top-right":o.P.convert(z["top-right"]||[0,0]),bottom:o.P.convert(z.bottom||[0,0]),"bottom-left":o.P.convert(z["bottom-left"]||[0,0]),"bottom-right":o.P.convert(z["bottom-right"]||[0,0]),left:o.P.convert(z.left||[0,0]),right:o.P.convert(z.right||[0,0])}}return Q0(new o.P(0,0))}let Qg=s;n.AJAXError=o.cJ,n.Event=o.l,n.Evented=o.E,n.LngLat=o.V,n.MercatorCoordinate=o.ab,n.Point=o.P,n.addProtocol=o.cK,n.config=o.c,n.removeProtocol=o.cL,n.AttributionControl=Ou,n.BoxZoomHandler=Pt,n.CanvasSource=Fe,n.CooperativeGesturesHandler=jc,n.DoubleClickZoomHandler=cl,n.DragPanHandler=en,n.DragRotateHandler=_n,n.EdgeInsets=Pu,n.FullscreenControl=class extends o.E{constructor(z={}){super(),this._onFullscreenChange=()=>{var l;let m=window.document.fullscreenElement||window.document.mozFullScreenElement||window.document.webkitFullscreenElement||window.document.msFullscreenElement;for(;!((l=m?.shadowRoot)===null||l===void 0)&&l.fullscreenElement;)m=m.shadowRoot.fullscreenElement;m===this._container!==this._fullscreen&&this._handleFullscreenChange()},this._onClickFullscreen=()=>{this._isFullscreen()?this._exitFullscreen():this._requestFullscreen()},this._fullscreen=!1,z&&z.container&&(z.container instanceof HTMLElement?this._container=z.container:o.w("Full screen control 'container' must be a DOM element.")),"onfullscreenchange"in document?this._fullscreenchange="fullscreenchange":"onmozfullscreenchange"in document?this._fullscreenchange="mozfullscreenchange":"onwebkitfullscreenchange"in document?this._fullscreenchange="webkitfullscreenchange":"onmsfullscreenchange"in document&&(this._fullscreenchange="MSFullscreenChange")}onAdd(z){return this._map=z,this._container||(this._container=this._map.getContainer()),this._controlContainer=U.create("div","maplibregl-ctrl maplibregl-ctrl-group"),this._setupUI(),this._controlContainer}onRemove(){U.remove(this._controlContainer),this._map=null,window.document.removeEventListener(this._fullscreenchange,this._onFullscreenChange)}_setupUI(){let z=this._fullscreenButton=U.create("button","maplibregl-ctrl-fullscreen",this._controlContainer);U.create("span","maplibregl-ctrl-icon",z).setAttribute("aria-hidden","true"),z.type="button",this._updateTitle(),this._fullscreenButton.addEventListener("click",this._onClickFullscreen),window.document.addEventListener(this._fullscreenchange,this._onFullscreenChange)}_updateTitle(){let z=this._getTitle();this._fullscreenButton.setAttribute("aria-label",z),this._fullscreenButton.title=z}_getTitle(){return this._map._getUIString(this._isFullscreen()?"FullscreenControl.Exit":"FullscreenControl.Enter")}_isFullscreen(){return this._fullscreen}_handleFullscreenChange(){this._fullscreen=!this._fullscreen,this._fullscreenButton.classList.toggle("maplibregl-ctrl-shrink"),this._fullscreenButton.classList.toggle("maplibregl-ctrl-fullscreen"),this._updateTitle(),this._fullscreen?(this.fire(new o.l("fullscreenstart")),this._prevCooperativeGesturesEnabled=this._map.cooperativeGestures.isEnabled(),this._map.cooperativeGestures.disable()):(this.fire(new o.l("fullscreenend")),this._prevCooperativeGesturesEnabled&&this._map.cooperativeGestures.enable())}_exitFullscreen(){window.document.exitFullscreen?window.document.exitFullscreen():window.document.mozCancelFullScreen?window.document.mozCancelFullScreen():window.document.msExitFullscreen?window.document.msExitFullscreen():window.document.webkitCancelFullScreen?window.document.webkitCancelFullScreen():this._togglePseudoFullScreen()}_requestFullscreen(){this._container.requestFullscreen?this._container.requestFullscreen():this._container.mozRequestFullScreen?this._container.mozRequestFullScreen():this._container.msRequestFullscreen?this._container.msRequestFullscreen():this._container.webkitRequestFullscreen?this._container.webkitRequestFullscreen():this._togglePseudoFullScreen()}_togglePseudoFullScreen(){this._container.classList.toggle("maplibregl-pseudo-fullscreen"),this._handleFullscreenChange(),this._map.resize()}},n.GeoJSONSource=ps,n.GeolocateControl=class extends o.E{constructor(z){super(),this._onSuccess=l=>{if(this._map){if(this._isOutOfMapMaxBounds(l))return this._setErrorState(),this.fire(new o.l("outofmaxbounds",l)),this._updateMarker(),void this._finish();if(this.options.trackUserLocation)switch(this._lastKnownPosition=l,this._watchState){case"WAITING_ACTIVE":case"ACTIVE_LOCK":case"ACTIVE_ERROR":this._watchState="ACTIVE_LOCK",this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active-error"),this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active");break;case"BACKGROUND":case"BACKGROUND_ERROR":this._watchState="BACKGROUND",this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background-error"),this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-background");break;default:throw new Error(`Unexpected watchState ${this._watchState}`)}this.options.showUserLocation&&this._watchState!=="OFF"&&this._updateMarker(l),this.options.trackUserLocation&&this._watchState!=="ACTIVE_LOCK"||this._updateCamera(l),this.options.showUserLocation&&this._dotElement.classList.remove("maplibregl-user-location-dot-stale"),this.fire(new o.l("geolocate",l)),this._finish()}},this._updateCamera=l=>{let m=new o.V(l.coords.longitude,l.coords.latitude),T=l.coords.accuracy,P=this._map.getBearing(),V=o.e({bearing:P},this.options.fitBoundsOptions),W=Go.fromLngLat(m,T);this._map.fitBounds(W,V,{geolocateSource:!0})},this._updateMarker=l=>{if(l){let m=new o.V(l.coords.longitude,l.coords.latitude);this._accuracyCircleMarker.setLngLat(m).addTo(this._map),this._userLocationDotMarker.setLngLat(m).addTo(this._map),this._accuracy=l.coords.accuracy,this._updateCircleRadiusIfNeeded()}else this._userLocationDotMarker.remove(),this._accuracyCircleMarker.remove()},this._onUpdate=()=>{this._updateCircleRadiusIfNeeded()},this._onError=l=>{if(this._map){if(l.code===1){this._watchState="OFF",this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active-error"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background-error"),this._geolocateButton.disabled=!0;let m=this._map._getUIString("GeolocateControl.LocationNotAvailable");this._geolocateButton.title=m,this._geolocateButton.setAttribute("aria-label",m),this._geolocationWatchID!==void 0&&this._clearWatch()}else{if(l.code===3&&Vl)return;this._setErrorState()}this._watchState!=="OFF"&&this.options.showUserLocation&&this._dotElement.classList.add("maplibregl-user-location-dot-stale"),this.fire(new o.l("error",l)),this._finish()}},this._finish=()=>{this._timeoutId&&clearTimeout(this._timeoutId),this._timeoutId=void 0},this._setupUI=()=>{this._map&&(this._container.addEventListener("contextmenu",l=>l.preventDefault()),this._geolocateButton=U.create("button","maplibregl-ctrl-geolocate",this._container),U.create("span","maplibregl-ctrl-icon",this._geolocateButton).setAttribute("aria-hidden","true"),this._geolocateButton.type="button",this._geolocateButton.disabled=!0)},this._finishSetupUI=l=>{if(this._map){if(l===!1){o.w("Geolocation support is not available so the GeolocateControl will be disabled.");let m=this._map._getUIString("GeolocateControl.LocationNotAvailable");this._geolocateButton.disabled=!0,this._geolocateButton.title=m,this._geolocateButton.setAttribute("aria-label",m)}else{let m=this._map._getUIString("GeolocateControl.FindMyLocation");this._geolocateButton.disabled=!1,this._geolocateButton.title=m,this._geolocateButton.setAttribute("aria-label",m)}this.options.trackUserLocation&&(this._geolocateButton.setAttribute("aria-pressed","false"),this._watchState="OFF"),this.options.showUserLocation&&(this._dotElement=U.create("div","maplibregl-user-location-dot"),this._userLocationDotMarker=new Tf({element:this._dotElement}),this._circleElement=U.create("div","maplibregl-user-location-accuracy-circle"),this._accuracyCircleMarker=new Tf({element:this._circleElement,pitchAlignment:"map"}),this.options.trackUserLocation&&(this._watchState="OFF"),this._map.on("zoom",this._onUpdate),this._map.on("move",this._onUpdate),this._map.on("rotate",this._onUpdate),this._map.on("pitch",this._onUpdate)),this._geolocateButton.addEventListener("click",()=>this.trigger()),this._setup=!0,this.options.trackUserLocation&&this._map.on("movestart",m=>{let T=m?.[0]instanceof ResizeObserverEntry;m.geolocateSource||this._watchState!=="ACTIVE_LOCK"||T||this._map.isZooming()||(this._watchState="BACKGROUND",this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-background"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"),this.fire(new o.l("trackuserlocationend")),this.fire(new o.l("userlocationlostfocus")))})}},this.options=o.e({},ig,z)}onAdd(z){return this._map=z,this._container=U.create("div","maplibregl-ctrl maplibregl-ctrl-group"),this._setupUI(),function(){return o._(this,arguments,void 0,function*(l=!1){if(th!==void 0&&!l)return th;if(window.navigator.permissions===void 0)return th=!!window.navigator.geolocation,th;try{th=(yield window.navigator.permissions.query({name:"geolocation"})).state!=="denied"}catch{th=!!window.navigator.geolocation}return th})}().then(l=>this._finishSetupUI(l)),this._container}onRemove(){this._geolocationWatchID!==void 0&&(window.navigator.geolocation.clearWatch(this._geolocationWatchID),this._geolocationWatchID=void 0),this.options.showUserLocation&&this._userLocationDotMarker&&this._userLocationDotMarker.remove(),this.options.showAccuracyCircle&&this._accuracyCircleMarker&&this._accuracyCircleMarker.remove(),U.remove(this._container),this._map.off("zoom",this._onUpdate),this._map.off("move",this._onUpdate),this._map.off("rotate",this._onUpdate),this._map.off("pitch",this._onUpdate),this._map=void 0,Fu=0,Vl=!1}_isOutOfMapMaxBounds(z){let l=this._map.getMaxBounds(),m=z.coords;return l&&(m.longitudel.getEast()||m.latitudel.getNorth())}_setErrorState(){switch(this._watchState){case"WAITING_ACTIVE":this._watchState="ACTIVE_ERROR",this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"),this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active-error");break;case"ACTIVE_LOCK":this._watchState="ACTIVE_ERROR",this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"),this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active-error"),this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-waiting");break;case"BACKGROUND":this._watchState="BACKGROUND_ERROR",this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background"),this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-background-error"),this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-waiting");break;case"ACTIVE_ERROR":case"BACKGROUND_ERROR":case"OFF":case void 0:break;default:throw new Error(`Unexpected watchState ${this._watchState}`)}}_updateCircleRadiusIfNeeded(){let z=this._userLocationDotMarker.getLngLat();if(!(this.options.showUserLocation&&this.options.showAccuracyCircle&&this._accuracy&&z))return;let l=this._map.project(z),m=this._map.unproject([l.x+100,l.y]),T=z.distanceTo(m)/100,P=2*this._accuracy/T;this._circleElement.style.width=`${P.toFixed(2)}px`,this._circleElement.style.height=`${P.toFixed(2)}px`}trigger(){if(!this._setup)return o.w("Geolocate control triggered before added to a map"),!1;if(this.options.trackUserLocation){switch(this._watchState){case"OFF":this._watchState="WAITING_ACTIVE",this.fire(new o.l("trackuserlocationstart"));break;case"WAITING_ACTIVE":case"ACTIVE_LOCK":case"ACTIVE_ERROR":case"BACKGROUND_ERROR":Fu--,Vl=!1,this._watchState="OFF",this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active-error"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background"),this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background-error"),this.fire(new o.l("trackuserlocationend"));break;case"BACKGROUND":this._watchState="ACTIVE_LOCK",this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background"),this._lastKnownPosition&&this._updateCamera(this._lastKnownPosition),this.fire(new o.l("trackuserlocationstart")),this.fire(new o.l("userlocationfocus"));break;default:throw new Error(`Unexpected watchState ${this._watchState}`)}switch(this._watchState){case"WAITING_ACTIVE":this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-waiting"),this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active");break;case"ACTIVE_LOCK":this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active");break;case"OFF":break;default:throw new Error(`Unexpected watchState ${this._watchState}`)}if(this._watchState==="OFF"&&this._geolocationWatchID!==void 0)this._clearWatch();else if(this._geolocationWatchID===void 0){let z;this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-waiting"),this._geolocateButton.setAttribute("aria-pressed","true"),Fu++,Fu>1?(z={maximumAge:6e5,timeout:0},Vl=!0):(z=this.options.positionOptions,Vl=!1),this._geolocationWatchID=window.navigator.geolocation.watchPosition(this._onSuccess,this._onError,z)}}else window.navigator.geolocation.getCurrentPosition(this._onSuccess,this._onError,this.options.positionOptions),this._timeoutId=setTimeout(this._finish,1e4);return!0}_clearWatch(){window.navigator.geolocation.clearWatch(this._geolocationWatchID),this._geolocationWatchID=void 0,this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"),this._geolocateButton.setAttribute("aria-pressed","false"),this.options.showUserLocation&&this._updateMarker(null)}},n.GlobeControl=class{constructor(){this._toggleProjection=()=>{var z;let l=(z=this._map.getProjection())===null||z===void 0?void 0:z.type;this._map.setProjection(l!=="mercator"&&l?{type:"mercator"}:{type:"globe"}),this._updateGlobeIcon()},this._updateGlobeIcon=()=>{var z;this._globeButton.classList.remove("maplibregl-ctrl-globe"),this._globeButton.classList.remove("maplibregl-ctrl-globe-enabled"),((z=this._map.getProjection())===null||z===void 0?void 0:z.type)==="globe"?(this._globeButton.classList.add("maplibregl-ctrl-globe-enabled"),this._globeButton.title=this._map._getUIString("GlobeControl.Disable")):(this._globeButton.classList.add("maplibregl-ctrl-globe"),this._globeButton.title=this._map._getUIString("GlobeControl.Enable"))}}onAdd(z){return this._map=z,this._container=U.create("div","maplibregl-ctrl maplibregl-ctrl-group"),this._globeButton=U.create("button","maplibregl-ctrl-globe",this._container),U.create("span","maplibregl-ctrl-icon",this._globeButton).setAttribute("aria-hidden","true"),this._globeButton.type="button",this._globeButton.addEventListener("click",this._toggleProjection),this._updateGlobeIcon(),this._map.on("styledata",this._updateGlobeIcon),this._container}onRemove(){U.remove(this._container),this._map.off("styledata",this._updateGlobeIcon),this._globeButton.removeEventListener("click",this._toggleProjection),this._map=void 0}},n.Hash=Yf,n.ImageSource=ws,n.KeyboardHandler=Dn,n.LngLatBounds=Go,n.LogoControl=iu,n.Map=class extends wd{constructor(z){var l,m;o.cH.mark(o.cI.create);let T=Object.assign(Object.assign(Object.assign({},tf),z),{canvasContextAttributes:Object.assign(Object.assign({},tf.canvasContextAttributes),z.canvasContextAttributes)});if(T.minZoom!=null&&T.maxZoom!=null&&T.minZoom>T.maxZoom)throw new Error("maxZoom must be greater than or equal to minZoom");if(T.minPitch!=null&&T.maxPitch!=null&&T.minPitch>T.maxPitch)throw new Error("maxPitch must be greater than or equal to minPitch");if(T.minPitch!=null&&T.minPitch<0)throw new Error("minPitch must be greater than or equal to 0");if(T.maxPitch!=null&&T.maxPitch>180)throw new Error("maxPitch must be less than or equal to 180");let P=new Hf,V=new Af;if(T.minZoom!==void 0&&P.setMinZoom(T.minZoom),T.maxZoom!==void 0&&P.setMaxZoom(T.maxZoom),T.minPitch!==void 0&&P.setMinPitch(T.minPitch),T.maxPitch!==void 0&&P.setMaxPitch(T.maxPitch),T.renderWorldCopies!==void 0&&P.setRenderWorldCopies(T.renderWorldCopies),T.transformConstrain!==null&&P.setConstrainOverride(T.transformConstrain),super(P,V,{bearingSnap:T.bearingSnap}),this._idleTriggered=!1,this._crossFadingFactor=1,this._renderTaskQueue=new Sa,this._controls=[],this._mapId=o.ah(),this._lostContextStyle={style:null,images:null},this._contextLost=te=>{te.preventDefault(),this._frameRequest&&(this._frameRequest.abort(),this._frameRequest=null),this.painter.destroy();for(let se of Object.values(this.style._layers))if(se.type==="custom"&&console.warn(`Custom layer with id '${se.id}' cannot be restored after WebGL context loss. You will need to re-add it manually after context restoration.`),se._listeners)for(let[fe]of Object.entries(se._listeners))console.warn(`Custom layer with id '${se.id}' had event listeners for event '${fe}' which cannot be restored after WebGL context loss. You will need to re-add them manually after context restoration.`);this._lostContextStyle=this._getStyleAndImages(),this.style.destroy(),this.style=null,this.fire(new o.l("webglcontextlost",{originalEvent:te}))},this._contextRestored=te=>{this._lostContextStyle.style&&this.setStyle(this._lostContextStyle.style,{diff:!1}),this._lostContextStyle.images&&(this.style.imageManager.images=this._lostContextStyle.images),this._setupPainter(),this.resize(),this._update(),this.fire(new o.l("webglcontextrestored",{originalEvent:te}))},this._onMapScroll=te=>{if(te.target===this._container)return this._container.scrollTop=0,this._container.scrollLeft=0,!1},this._onWindowOnline=()=>{this._update()},this._interactive=T.interactive,this._maxTileCacheSize=T.maxTileCacheSize,this._maxTileCacheZoomLevels=T.maxTileCacheZoomLevels,this._canvasContextAttributes=Object.assign({},T.canvasContextAttributes),this._trackResize=T.trackResize===!0,this._bearingSnap=T.bearingSnap,this._centerClampedToGround=T.centerClampedToGround,this._refreshExpiredTiles=T.refreshExpiredTiles===!0,this._fadeDuration=T.fadeDuration,this._crossSourceCollisions=T.crossSourceCollisions===!0,this._collectResourceTiming=T.collectResourceTiming===!0,this._locale=Object.assign(Object.assign({},yu),T.locale),this._clickTolerance=T.clickTolerance,this._overridePixelRatio=T.pixelRatio,this._maxCanvasSize=T.maxCanvasSize,this._zoomLevelsToOverscale=T.experimentalZoomLevelsToOverscale,this.transformCameraUpdate=T.transformCameraUpdate,this.transformConstrain=T.transformConstrain,this.cancelPendingTileRequestsWhileZooming=T.cancelPendingTileRequestsWhileZooming===!0,T.reduceMotion!==void 0&&(M.prefersReducedMotion=T.reduceMotion),this._imageQueueHandle=Oe.addThrottleControl(()=>this.isMoving()),this._requestManager=new ne(T.transformRequest),typeof T.container=="string"){if(this._container=document.getElementById(T.container),!this._container)throw new Error(`Container '${T.container}' not found.`)}else{if(!(T.container instanceof HTMLElement))throw new Error("Invalid type: 'container' must be a String or HTMLElement.");this._container=T.container}if(T.maxBounds&&this.setMaxBounds(T.maxBounds),this._setupContainer(),this._setupPainter(),this.on("move",()=>this._update(!1)),this.on("moveend",()=>this._update(!1)),this.on("zoom",()=>this._update(!0)),this.on("terrain",()=>{this.painter.terrainFacilitator.dirty=!0,this._update(!0)}),this.once("idle",()=>{this._idleTriggered=!0}),typeof window<"u"){addEventListener("online",this._onWindowOnline,!1);let te=!1,se=Kd(fe=>{this._trackResize&&!this._removed&&(this.resize(fe),this.redraw())},50);this._resizeObserver=new ResizeObserver(fe=>{te?se(fe):te=!0}),this._resizeObserver.observe(this._container)}this.handlers=new bd(this,T),this._hash=T.hash&&new Yf(typeof T.hash=="string"&&T.hash||void 0).addTo(this),this._hash&&this._hash._onHashChange()||(this.jumpTo({center:T.center,elevation:T.elevation,zoom:T.zoom,bearing:T.bearing,pitch:T.pitch,roll:T.roll}),T.bounds&&(this.resize(),this.fitBounds(T.bounds,o.e({},T.fitBoundsOptions,{duration:0}))));let W=typeof T.style=="string"||((m=(l=T.style)===null||l===void 0?void 0:l.projection)===null||m===void 0?void 0:m.type)!=="globe";this.resize(null,W),this._localIdeographFontFamily=T.localIdeographFontFamily,this._validateStyle=T.validateStyle,T.style&&this.setStyle(T.style,{localIdeographFontFamily:T.localIdeographFontFamily}),T.attributionControl&&this.addControl(new Ou(typeof T.attributionControl=="boolean"?void 0:T.attributionControl)),T.maplibreLogo&&this.addControl(new iu,T.logoPosition),this.on("style.load",()=>{if(W||this._resizeTransform(),this.transform.unmodified){let te=o.U(this.style.stylesheet,["center","zoom","bearing","pitch","roll"]);this.jumpTo(te)}}),this.on("data",te=>{this._update(te.dataType==="style"),this.fire(new o.l(`${te.dataType}data`,te))}),this.on("dataloading",te=>{this.fire(new o.l(`${te.dataType}dataloading`,te))}),this.on("dataabort",te=>{this.fire(new o.l("sourcedataabort",te))})}_getMapId(){return this._mapId}setGlobalStateProperty(z,l){return this.style.setGlobalStateProperty(z,l),this._update(!0)}getGlobalState(){return this.style.getGlobalState()}addControl(z,l){if(l===void 0&&(l=z.getDefaultPosition?z.getDefaultPosition():"top-right"),!z||!z.onAdd)return this.fire(new o.k(new Error("Invalid argument to map.addControl(). Argument must be a control with onAdd and onRemove methods.")));let m=z.onAdd(this);this._controls.push(z);let T=this._controlPositions[l];return l.indexOf("bottom")!==-1?T.insertBefore(m,T.firstChild):T.appendChild(m),this}removeControl(z){if(!z||!z.onRemove)return this.fire(new o.k(new Error("Invalid argument to map.removeControl(). Argument must be a control with onAdd and onRemove methods.")));let l=this._controls.indexOf(z);return l>-1&&this._controls.splice(l,1),z.onRemove(this),this}hasControl(z){return this._controls.indexOf(z)>-1}coveringTiles(z){return Ke(this.transform,z)}calculateCameraOptionsFromTo(z,l,m,T){return T==null&&this.terrain&&(T=this.terrain.getElevationForLngLat(m)),super.calculateCameraOptionsFromTo(z,l,m,T)}resize(z,l=!0){let[m,T]=this._containerDimensions(),P=this._getClampedPixelRatio(m,T);if(this._resizeCanvas(m,T,P),this.painter.resize(m,T,P),this.painter.overLimit()){let W=this.painter.context.gl;this._maxCanvasSize=[W.drawingBufferWidth,W.drawingBufferHeight];let te=this._getClampedPixelRatio(m,T);this._resizeCanvas(m,T,te),this.painter.resize(m,T,te)}this._resizeTransform(l);let V=!this._moving;return V&&(this.stop(),this.fire(new o.l("movestart",z)).fire(new o.l("move",z))),this.fire(new o.l("resize",z)),V&&this.fire(new o.l("moveend",z)),this}_resizeTransform(z=!0){var l;let[m,T]=this._containerDimensions();this.transform.resize(m,T,z),(l=this._requestedCameraState)===null||l===void 0||l.resize(m,T,z)}_getClampedPixelRatio(z,l){let{0:m,1:T}=this._maxCanvasSize,P=this.getPixelRatio(),V=z*P,W=l*P;return Math.min(V>m?m/V:1,W>T?T/W:1)*P}getPixelRatio(){var z;return(z=this._overridePixelRatio)!==null&&z!==void 0?z:devicePixelRatio}setPixelRatio(z){this._overridePixelRatio=z,this.resize()}getBounds(){return this.transform.getBounds()}getMaxBounds(){return this.transform.getMaxBounds()}setMaxBounds(z){return this.transform.setMaxBounds(Go.convert(z)),this._update()}setMinZoom(z){if((z=z??-2)>=-2&&z<=this.transform.maxZoom)return this.transform.setMinZoom(z),this._update(),this.getZoom()=this.transform.minZoom)return this.transform.setMaxZoom(z),this._update(),this.getZoom()>z&&this.setZoom(z),this;throw new Error("maxZoom must be greater than the current minZoom")}getMaxZoom(){return this.transform.maxZoom}setMinPitch(z){if((z=z??0)<0)throw new Error("minPitch must be greater than or equal to 0");if(z>=0&&z<=this.transform.maxPitch)return this.transform.setMinPitch(z),this._update(),this.getPitch()180)throw new Error("maxPitch must be less than or equal to 180");if(z>=this.transform.minPitch)return this.transform.setMaxPitch(z),this._update(),this.getPitch()>z&&this.setPitch(z),this;throw new Error("maxPitch must be greater than the current minPitch")}getMaxPitch(){return this.transform.maxPitch}getRenderWorldCopies(){return this.transform.renderWorldCopies}setRenderWorldCopies(z){return this.transform.setRenderWorldCopies(z),this._update()}setTransformConstrain(z){return this.transform.setConstrainOverride(z),this._update()}project(z){return this.transform.locationToScreenPoint(o.V.convert(z),this.style&&this.terrain)}unproject(z){return this.transform.screenPointToLocation(o.P.convert(z),this.terrain)}isMoving(){var z;return this._moving||((z=this.handlers)===null||z===void 0?void 0:z.isMoving())}isZooming(){var z;return this._zooming||((z=this.handlers)===null||z===void 0?void 0:z.isZooming())}isRotating(){var z;return this._rotating||((z=this.handlers)===null||z===void 0?void 0:z.isRotating())}_createDelegatedListener(z,l,m){if(z==="mouseenter"||z==="mouseover"){let T=!1;return{layers:l,listener:m,delegates:{mousemove:V=>{let W=l.filter(se=>this.getLayer(se)),te=W.length!==0?this.queryRenderedFeatures(V.point,{layers:W}):[];te.length?T||(T=!0,m.call(this,new Pe(z,this,V.originalEvent,{features:te}))):T=!1},mouseout:()=>{T=!1}}}}if(z==="mouseleave"||z==="mouseout"){let T=!1;return{layers:l,listener:m,delegates:{mousemove:W=>{let te=l.filter(se=>this.getLayer(se));(te.length!==0?this.queryRenderedFeatures(W.point,{layers:te}):[]).length?T=!0:T&&(T=!1,m.call(this,new Pe(z,this,W.originalEvent)))},mouseout:W=>{T&&(T=!1,m.call(this,new Pe(z,this,W.originalEvent)))}}}}{let T=P=>{let V=l.filter(te=>this.getLayer(te)),W=V.length!==0?this.queryRenderedFeatures(P.point,{layers:V}):[];W.length&&(P.features=W,m.call(this,P),delete P.features)};return{layers:l,listener:m,delegates:{[z]:T}}}}_saveDelegatedListener(z,l){this._delegatedListeners=this._delegatedListeners||{},this._delegatedListeners[z]=this._delegatedListeners[z]||[],this._delegatedListeners[z].push(l)}_removeDelegatedListener(z,l,m){if(!this._delegatedListeners||!this._delegatedListeners[z])return;let T=this._delegatedListeners[z];for(let P=0;Pl.includes(W))){for(let W in V.delegates)this.off(W,V.delegates[W]);return void T.splice(P,1)}}}on(z,l,m){if(m===void 0)return super.on(z,l);let T=typeof l=="string"?[l]:l,P=this._createDelegatedListener(z,T,m);this._saveDelegatedListener(z,P);for(let V in P.delegates)this.on(V,P.delegates[V]);return{unsubscribe:()=>{this._removeDelegatedListener(z,T,m)}}}once(z,l,m){if(m===void 0)return super.once(z,l);let T=typeof l=="string"?[l]:l,P=this._createDelegatedListener(z,T,m);for(let V in P.delegates){let W=P.delegates[V];P.delegates[V]=(...te)=>{this._removeDelegatedListener(z,T,m),W(...te)}}this._saveDelegatedListener(z,P);for(let V in P.delegates)this.once(V,P.delegates[V]);return this}off(z,l,m){return m===void 0?super.off(z,l):(this._removeDelegatedListener(z,typeof l=="string"?[l]:l,m),this)}queryRenderedFeatures(z,l){if(!this.style)return[];let m,T=z instanceof o.P||Array.isArray(z),P=T?z:[[0,0],[this.transform.width,this.transform.height]];if(l=l||(T?{}:z)||{},P instanceof o.P||typeof P[0]=="number")m=[o.P.convert(P)];else{let V=o.P.convert(P[0]),W=o.P.convert(P[1]);m=[V,new o.P(W.x,V.y),W,new o.P(V.x,W.y),V]}return this.style.queryRenderedFeatures(m,l,this.transform)}querySourceFeatures(z,l){return this.style.querySourceFeatures(z,l)}setStyle(z,l){return(l=o.e({},{localIdeographFontFamily:this._localIdeographFontFamily,validate:this._validateStyle},l)).diff!==!1&&l.localIdeographFontFamily===this._localIdeographFontFamily&&this.style&&z?(this._diffStyle(z,l),this):(this._localIdeographFontFamily=l.localIdeographFontFamily,this._updateStyle(z,l))}setTransformRequest(z){return this._requestManager.setTransformRequest(z),this}_getUIString(z){let l=this._locale[z];if(l==null)throw new Error(`Missing UI string '${z}'`);return l}_updateStyle(z,l){var m,T;if(l.transformStyle&&this.style&&!this.style._loaded)return void this.style.once("style.load",()=>this._updateStyle(z,l));let P=this.style&&l.transformStyle?this.style.serialize():void 0;return this.style&&(this.style.setEventedParent(null),this.style._remove(!z)),z?(this.style=new Is(this,l||{}),this.style.setEventedParent(this,{style:this.style}),typeof z=="string"?this.style.loadURL(z,l,P):this.style.loadJSON(z,l,P),this):((T=(m=this.style)===null||m===void 0?void 0:m.projection)===null||T===void 0||T.destroy(),delete this.style,this)}_lazyInitEmptyStyle(){this.style||(this.style=new Is(this,{}),this.style.setEventedParent(this,{style:this.style}),this.style.loadEmpty())}_diffStyle(z,l){if(typeof z=="string"){let m=this._requestManager.transformRequest(z,"Style");o.j(m,new AbortController).then(T=>{this._updateDiff(T.data,l)}).catch(T=>{T&&this.fire(new o.k(T))})}else typeof z=="object"&&this._updateDiff(z,l)}_updateDiff(z,l){try{this.style.setState(z,l)&&this._update(!0)}catch(m){o.w(`Unable to perform style diff: ${m.message||m.error||m}. Rebuilding the style from scratch.`),this._updateStyle(z,l)}}getStyle(){if(this.style)return this.style.serialize()}_getStyleAndImages(){return this.style?{style:this.style.serialize(),images:this.style.imageManager.cloneImages()}:{style:null,images:{}}}isStyleLoaded(){return this.style?this.style.loaded():o.w("There is no style added to the map.")}addSource(z,l){return this._lazyInitEmptyStyle(),this.style.addSource(z,l),this._update(!0)}isSourceLoaded(z){let l=this.style&&this.style.tileManagers[z];if(l!==void 0)return l.loaded();this.fire(new o.k(new Error(`There is no tile manager with ID '${z}'`)))}setTerrain(z){if(this.style._checkLoaded(),this._terrainDataCallback&&this.style.off("data",this._terrainDataCallback),z){let l=this.style.tileManagers[z.source];if(!l)throw new Error(`cannot load terrain, because there exists no source with ID: ${z.source}`);this.terrain===null&&l.reload();for(let m in this.style._layers){let T=this.style._layers[m];T.type==="hillshade"&&T.source===z.source&&o.w("You are using the same source for a hillshade layer and for 3D terrain. Please consider using two separate sources to improve rendering quality."),T.type==="color-relief"&&T.source===z.source&&o.w("You are using the same source for a color-relief layer and for 3D terrain. Please consider using two separate sources to improve rendering quality.")}this.terrain=new eh(this.painter,l,z),this.painter.renderToTexture=new _s(this.painter,this.terrain),this.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this.transform.center,this.transform.tileZoom)),this.transform.setElevation(this.terrain.getElevationForLngLatZoom(this.transform.center,this.transform.tileZoom)),this._terrainDataCallback=m=>{var T;m.dataType==="style"?this.terrain.tileManager.freeRtt():m.dataType==="source"&&m.tile&&(m.sourceId!==z.source||this._elevationFreeze||(this.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this.transform.center,this.transform.tileZoom)),this._centerClampedToGround&&this.transform.setElevation(this.terrain.getElevationForLngLatZoom(this.transform.center,this.transform.tileZoom))),((T=m.source)===null||T===void 0?void 0:T.type)==="image"?this.terrain.tileManager.freeRtt():this.terrain.tileManager.freeRtt(m.tile.tileID))},this.style.on("data",this._terrainDataCallback)}else this.terrain&&this.terrain.tileManager.destruct(),this.terrain=null,this.painter.renderToTexture&&this.painter.renderToTexture.destruct(),this.painter.renderToTexture=null,this.transform.setMinElevationForCurrentTile(0),this._centerClampedToGround&&this.transform.setElevation(0);return this.fire(new o.l("terrain",{terrain:z})),this}getTerrain(){var z,l;return(l=(z=this.terrain)===null||z===void 0?void 0:z.options)!==null&&l!==void 0?l:null}areTilesLoaded(){let z=this.style&&this.style.tileManagers;for(let l of Object.values(z))if(!l.areTilesLoaded())return!1;return!0}removeSource(z){return this.style.removeSource(z),this._update(!0)}getSource(z){return this.style.getSource(z)}setSourceTileLodParams(z,l,m){if(m){let T=this.getSource(m);if(!T)throw new Error(`There is no source with ID "${m}", cannot set LOD parameters`);T.calculateTileZoom=Xe(Math.max(1,z),Math.max(1,l))}else for(let T in this.style.tileManagers)this.style.tileManagers[T].getSource().calculateTileZoom=Xe(Math.max(1,z),Math.max(1,l));return this._update(!0),this}refreshTiles(z,l){let m=this.style.tileManagers[z];if(!m)throw new Error(`There is no tile manager with ID "${z}", cannot refresh tile`);l===void 0?m.reload(!0):m.refreshTiles(l.map(T=>new o.ae(T.z,T.x,T.y)))}addImage(z,l,m={}){let{pixelRatio:T=1,sdf:P=!1,stretchX:V,stretchY:W,content:te,textFitWidth:se,textFitHeight:fe}=m;if(this._lazyInitEmptyStyle(),!(l instanceof HTMLImageElement||o.b(l))){if(l.width===void 0||l.height===void 0)return this.fire(new o.k(new Error("Invalid arguments to map.addImage(). The second argument must be an `HTMLImageElement`, `ImageData`, `ImageBitmap`, or object with `width`, `height`, and `data` properties with the same format as `ImageData`")));{let{width:_e,height:Re,data:Ie}=l,je=l;return this.style.addImage(z,{data:new o.R({width:_e,height:Re},new Uint8Array(Ie)),pixelRatio:T,stretchX:V,stretchY:W,content:te,textFitWidth:se,textFitHeight:fe,sdf:P,version:0,userImage:je}),je.onAdd&&je.onAdd(this,z),this}}{let{width:_e,height:Re,data:Ie}=M.getImageData(l);this.style.addImage(z,{data:new o.R({width:_e,height:Re},Ie),pixelRatio:T,stretchX:V,stretchY:W,content:te,textFitWidth:se,textFitHeight:fe,sdf:P,version:0})}}updateImage(z,l){let m=this.style.getImage(z);if(!m)return this.fire(new o.k(new Error("The map has no image with that id. If you are adding a new image use `map.addImage(...)` instead.")));let T=l instanceof HTMLImageElement||o.b(l)?M.getImageData(l):l,{width:P,height:V,data:W}=T;if(P===void 0||V===void 0)return this.fire(new o.k(new Error("Invalid arguments to map.updateImage(). The second argument must be an `HTMLImageElement`, `ImageData`, `ImageBitmap`, or object with `width`, `height`, and `data` properties with the same format as `ImageData`")));if(P!==m.data.width||V!==m.data.height)return this.fire(new o.k(new Error("The width and height of the updated image must be that same as the previous version of the image")));let te=!(l instanceof HTMLImageElement||o.b(l));return m.data.replace(W,te),this.style.updateImage(z,m),this}getImage(z){return this.style.getImage(z)}hasImage(z){return z?!!this.style.getImage(z):(this.fire(new o.k(new Error("Missing required image id"))),!1)}removeImage(z){this.style.removeImage(z)}loadImage(z){return Oe.getImage(this._requestManager.transformRequest(z,"Image"),new AbortController)}listImages(){return this.style.listImages()}addLayer(z,l){return this._lazyInitEmptyStyle(),this.style.addLayer(z,l),this._update(!0)}moveLayer(z,l){return this.style.moveLayer(z,l),this._update(!0)}removeLayer(z){return this.style.removeLayer(z),this._update(!0)}getLayer(z){return this.style.getLayer(z)}getLayersOrder(){return this.style.getLayersOrder()}setLayerZoomRange(z,l,m){return this.style.setLayerZoomRange(z,l,m),this._update(!0)}setFilter(z,l,m={}){return this.style.setFilter(z,l,m),this._update(!0)}getFilter(z){return this.style.getFilter(z)}setPaintProperty(z,l,m,T={}){return this.style.setPaintProperty(z,l,m,T),this._update(!0)}getPaintProperty(z,l){return this.style.getPaintProperty(z,l)}setLayoutProperty(z,l,m,T={}){return this.style.setLayoutProperty(z,l,m,T),this._update(!0)}getLayoutProperty(z,l){return this.style.getLayoutProperty(z,l)}setGlyphs(z,l={}){return this._lazyInitEmptyStyle(),this.style.setGlyphs(z,l),this._update(!0)}getGlyphs(){return this.style.getGlyphsUrl()}addSprite(z,l,m={}){return this._lazyInitEmptyStyle(),this.style.addSprite(z,l,m,T=>{T||this._update(!0)}),this}removeSprite(z){return this._lazyInitEmptyStyle(),this.style.removeSprite(z),this._update(!0)}getSprite(){return this.style.getSprite()}setSprite(z,l={}){return this._lazyInitEmptyStyle(),this.style.setSprite(z,l,m=>{m||this._update(!0)}),this}setLight(z,l={}){return this._lazyInitEmptyStyle(),this.style.setLight(z,l),this._update(!0)}getLight(){return this.style.getLight()}setSky(z,l={}){return this._lazyInitEmptyStyle(),this.style.setSky(z,l),this._update(!0)}getSky(){return this.style.getSky()}setFeatureState(z,l){return this.style.setFeatureState(z,l),this._update()}removeFeatureState(z,l){return this.style.removeFeatureState(z,l),this._update()}getFeatureState(z){return this.style.getFeatureState(z)}getContainer(){return this._container}getCanvasContainer(){return this._canvasContainer}getCanvas(){return this._canvas}_containerDimensions(){let z=0,l=0;return this._container&&(z=this._container.clientWidth||400,l=this._container.clientHeight||300),[z,l]}_setupContainer(){let z=this._container;z.classList.add("maplibregl-map");let l=this._canvasContainer=U.create("div","maplibregl-canvas-container",z);this._interactive&&l.classList.add("maplibregl-interactive"),this._canvas=U.create("canvas","maplibregl-canvas",l),this._canvas.addEventListener("webglcontextlost",this._contextLost,!1),this._canvas.addEventListener("webglcontextrestored",this._contextRestored,!1),this._canvas.setAttribute("tabindex",this._interactive?"0":"-1"),this._canvas.setAttribute("aria-label",this._getUIString("Map.Title")),this._canvas.setAttribute("role","region");let m=this._containerDimensions(),T=this._getClampedPixelRatio(m[0],m[1]);this._resizeCanvas(m[0],m[1],T);let P=this._controlContainer=U.create("div","maplibregl-control-container",z),V=this._controlPositions={};["top-left","top-right","bottom-left","bottom-right"].forEach(W=>{V[W]=U.create("div",`maplibregl-ctrl-${W} `,P)}),this._container.addEventListener("scroll",this._onMapScroll,!1)}_resizeCanvas(z,l,m){this._canvas.width=Math.floor(m*z),this._canvas.height=Math.floor(m*l),this._canvas.style.width=`${z}px`,this._canvas.style.height=`${l}px`}_setupPainter(){let z=Object.assign(Object.assign({},this._canvasContextAttributes),{alpha:!0,depth:!0,stencil:!0,premultipliedAlpha:!0}),l=null;this._canvas.addEventListener("webglcontextcreationerror",T=>{l={requestedAttributes:z},T&&(l.statusMessage=T.statusMessage,l.type=T.type)},{once:!0});let m=null;if(m=this._canvasContextAttributes.contextType?this._canvas.getContext(this._canvasContextAttributes.contextType,z):this._canvas.getContext("webgl2",z)||this._canvas.getContext("webgl",z),!m){let T="Failed to initialize WebGL";throw l?(l.message=T,new Error(JSON.stringify(l))):new Error(T)}this.painter=new Bp(m,this.transform),K.testSupport(m)}migrateProjection(z,l){super.migrateProjection(z,l),this.painter.transform=z,this.fire(new o.l("projectiontransition",{newProjection:this.style.projection.name}))}loaded(){return!this._styleDirty&&!this._sourcesDirty&&!!this.style&&this.style.loaded()}_update(z){return this.style&&this.style._loaded?(this._styleDirty=this._styleDirty||z,this._sourcesDirty=!0,this.triggerRepaint(),this):this}_requestRenderFrame(z){return this._update(),this._renderTaskQueue.add(z)}_cancelRenderFrame(z){this._renderTaskQueue.remove(z)}_render(z){var l,m,T,P,V;let W=this._idleTriggered?this._fadeDuration:0,te=((l=this.style.projection)===null||l===void 0?void 0:l.transitionState)>0;if(this.painter.context.setDirty(),this.painter.setBaseState(),this._renderTaskQueue.run(z),this._removed)return;let se=!1;if(this.style&&this._styleDirty){this._styleDirty=!1;let Re=this.transform.zoom,Ie=k();this.style.zoomHistory.update(Re,Ie);let je=new o.H(Re,{now:Ie,fadeDuration:W,zoomHistory:this.style.zoomHistory,transition:this.style.getTransition()}),tt=je.crossFadingFactor();tt===1&&tt===this._crossFadingFactor||(se=!0,this._crossFadingFactor=tt),this.style.update(je)}let fe=((m=this.style.projection)===null||m===void 0?void 0:m.transitionState)>0!==te;(T=this.style.projection)===null||T===void 0||T.setErrorQueryLatitudeDegrees(this.transform.center.lat),this.transform.setTransitionState((P=this.style.projection)===null||P===void 0?void 0:P.transitionState,(V=this.style.projection)===null||V===void 0?void 0:V.latitudeErrorCorrectionRadians),this.style&&(this._sourcesDirty||fe)&&(this._sourcesDirty=!1,this.style._updateSources(this.transform)),this.terrain?(this.terrain.tileManager.update(this.transform,this.terrain),this.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this.transform.center,this.transform.tileZoom)),!this._elevationFreeze&&this._centerClampedToGround&&this.transform.setElevation(this.terrain.getElevationForLngLatZoom(this.transform.center,this.transform.tileZoom))):(this.transform.setMinElevationForCurrentTile(0),this._centerClampedToGround&&this.transform.setElevation(0)),this._placementDirty=this.style&&this.style._updatePlacement(this.transform,this.showCollisionBoxes,W,this._crossSourceCollisions,fe),this.painter.render(this.style,{showTileBoundaries:this.showTileBoundaries,showOverdrawInspector:this._showOverdrawInspector,rotating:this.isRotating(),zooming:this.isZooming(),moving:this.isMoving(),fadeDuration:W,showPadding:this.showPadding}),this.fire(new o.l("render")),this.loaded()&&!this._loaded&&(this._loaded=!0,o.cH.mark(o.cI.load),this.fire(new o.l("load"))),this.style&&(this.style.hasTransitions()||se)&&(this._styleDirty=!0),this.style&&!this._placementDirty&&this.style._releaseSymbolFadeTiles();let _e=this._sourcesDirty||this._styleDirty||this._placementDirty;return _e||this._repaint?this.triggerRepaint():!this.isMoving()&&this.loaded()&&this.fire(new o.l("idle")),!this._loaded||this._fullyLoaded||_e||(this._fullyLoaded=!0,o.cH.mark(o.cI.fullLoad)),this}redraw(){return this.style&&(this._frameRequest&&(this._frameRequest.abort(),this._frameRequest=null),this._render(0)),this}remove(){var z;this._hash&&this._hash.remove();for(let m of this._controls)m.onRemove(this);this._controls=[],this._frameRequest&&(this._frameRequest.abort(),this._frameRequest=null),this._renderTaskQueue.clear(),this.painter.destroy(),this.handlers.destroy(),delete this.handlers,this.setStyle(null),typeof window<"u"&&removeEventListener("online",this._onWindowOnline,!1),Oe.removeThrottleControl(this._imageQueueHandle),(z=this._resizeObserver)===null||z===void 0||z.disconnect();let l=this.painter.context.gl.getExtension("WEBGL_lose_context");l?.loseContext&&l.loseContext(),this._canvas.removeEventListener("webglcontextrestored",this._contextRestored,!1),this._canvas.removeEventListener("webglcontextlost",this._contextLost,!1),U.remove(this._canvasContainer),U.remove(this._controlContainer),this._container.removeEventListener("scroll",this._onMapScroll,!1),this._container.classList.remove("maplibregl-map"),o.cH.clearMetrics(),this._removed=!0,this.fire(new o.l("remove"))}triggerRepaint(){this.style&&!this._frameRequest&&(this._frameRequest=new AbortController,M.frame(this._frameRequest,z=>{o.cH.frame(z),this._frameRequest=null;try{this._render(z)}catch(l){if(!o.Z(l)&&!function(m){return m.message===gu}(l))throw l}},()=>{}))}get showTileBoundaries(){return!!this._showTileBoundaries}set showTileBoundaries(z){this._showTileBoundaries!==z&&(this._showTileBoundaries=z,this._update())}get showPadding(){return!!this._showPadding}set showPadding(z){this._showPadding!==z&&(this._showPadding=z,this._update())}get showCollisionBoxes(){return!!this._showCollisionBoxes}set showCollisionBoxes(z){this._showCollisionBoxes!==z&&(this._showCollisionBoxes=z,z?this.style._generateCollisionBoxes():this._update())}get showOverdrawInspector(){return!!this._showOverdrawInspector}set showOverdrawInspector(z){this._showOverdrawInspector!==z&&(this._showOverdrawInspector=z,this._update())}get repaint(){return!!this._repaint}set repaint(z){this._repaint!==z&&(this._repaint=z,this.triggerRepaint())}get vertices(){return!!this._vertices}set vertices(z){this._vertices=z,this._update()}get version(){return Il}getCameraTargetElevation(){return this.transform.elevation}getProjection(){return this.style.getProjection()}setProjection(z){return this._lazyInitEmptyStyle(),this.style.setProjection(z),this._update(!0)}},n.MapMouseEvent=Pe,n.MapTouchEvent=Ue,n.MapWheelEvent=ct,n.Marker=Tf,n.NavigationControl=class{constructor(z){this._updateZoomButtons=()=>{let l=this._map.getZoom(),m=l===this._map.getMaxZoom(),T=l===this._map.getMinZoom();this._zoomInButton.disabled=m,this._zoomOutButton.disabled=T,this._zoomInButton.setAttribute("aria-disabled",m.toString()),this._zoomOutButton.setAttribute("aria-disabled",T.toString())},this._rotateCompassArrow=()=>{this._compassIcon.style.transform=this.options.visualizePitch&&this.options.visualizeRoll?`scale(${1/Math.pow(Math.cos(this._map.transform.pitchInRadians),.5)}) rotateZ(${-this._map.transform.roll}deg) rotateX(${this._map.transform.pitch}deg) rotateZ(${-this._map.transform.bearing}deg)`:this.options.visualizePitch?`scale(${1/Math.pow(Math.cos(this._map.transform.pitchInRadians),.5)}) rotateX(${this._map.transform.pitch}deg) rotateZ(${-this._map.transform.bearing}deg)`:this.options.visualizeRoll?`rotate(${-this._map.transform.bearing-this._map.transform.roll}deg)`:`rotate(${-this._map.transform.bearing}deg)`},this._setButtonTitle=(l,m)=>{let T=this._map._getUIString(`NavigationControl.${m}`);l.title=T,l.setAttribute("aria-label",T)},this.options=o.e({},Kg,z),this._container=U.create("div","maplibregl-ctrl maplibregl-ctrl-group"),this._container.addEventListener("contextmenu",l=>l.preventDefault()),this.options.showZoom&&(this._zoomInButton=this._createButton("maplibregl-ctrl-zoom-in",l=>this._map.zoomIn({},{originalEvent:l})),U.create("span","maplibregl-ctrl-icon",this._zoomInButton).setAttribute("aria-hidden","true"),this._zoomOutButton=this._createButton("maplibregl-ctrl-zoom-out",l=>this._map.zoomOut({},{originalEvent:l})),U.create("span","maplibregl-ctrl-icon",this._zoomOutButton).setAttribute("aria-hidden","true")),this.options.showCompass&&(this._compass=this._createButton("maplibregl-ctrl-compass",l=>{this.options.visualizePitch?this._map.resetNorthPitch({},{originalEvent:l}):this._map.resetNorth({},{originalEvent:l})}),this._compassIcon=U.create("span","maplibregl-ctrl-icon",this._compass),this._compassIcon.setAttribute("aria-hidden","true"))}onAdd(z){return this._map=z,this.options.showZoom&&(this._setButtonTitle(this._zoomInButton,"ZoomIn"),this._setButtonTitle(this._zoomOutButton,"ZoomOut"),this._map.on("zoom",this._updateZoomButtons),this._updateZoomButtons()),this.options.showCompass&&(this._setButtonTitle(this._compass,"ResetBearing"),this.options.visualizePitch&&this._map.on("pitch",this._rotateCompassArrow),this.options.visualizeRoll&&this._map.on("roll",this._rotateCompassArrow),this._map.on("rotate",this._rotateCompassArrow),this._rotateCompassArrow(),this._handler=new Lp(this._map,this._compass,this.options.visualizePitch)),this._container}onRemove(){U.remove(this._container),this.options.showZoom&&this._map.off("zoom",this._updateZoomButtons),this.options.showCompass&&(this.options.visualizePitch&&this._map.off("pitch",this._rotateCompassArrow),this.options.visualizeRoll&&this._map.off("roll",this._rotateCompassArrow),this._map.off("rotate",this._rotateCompassArrow),this._handler.off(),delete this._handler),delete this._map}_createButton(z,l){let m=U.create("button",z,this._container);return m.type="button",m.addEventListener("click",l),m}},n.Popup=class extends o.E{constructor(z){super(),this._updateOpacity=()=>{this.options.locationOccludedOpacity!==void 0&&(this._container.style.opacity=this._map.transform.isLocationOccluded(this.getLngLat())?`${this.options.locationOccludedOpacity}`:"")},this.remove=()=>(this._content&&U.remove(this._content),this._container&&(U.remove(this._container),delete this._container),this._map&&(this._map.off("move",this._update),this._map.off("move",this._onClose),this._map.off("click",this._onClose),this._map.off("remove",this.remove),this._map.off("mousemove",this._onMouseMove),this._map.off("mouseup",this._onMouseUp),this._map.off("drag",this._onDrag),this._map._canvasContainer.classList.remove("maplibregl-track-pointer"),delete this._map,this.fire(new o.l("close"))),this),this._onMouseUp=l=>{this._update(l.point)},this._onMouseMove=l=>{this._update(l.point)},this._onDrag=l=>{this._update(l.point)},this._update=l=>{if(!this._map||!this._lngLat&&!this._trackPointer||!this._content)return;if(!this._container){if(this._container=U.create("div","maplibregl-popup",this._map.getContainer()),this._tip=U.create("div","maplibregl-popup-tip",this._container),this._container.appendChild(this._content),this.options.className)for(let W of this.options.className.split(" "))this._container.classList.add(W);this._closeButton&&this._closeButton.setAttribute("aria-label",this._map._getUIString("Popup.Close")),this._trackPointer&&this._container.classList.add("maplibregl-popup-track-pointer")}if(this.options.maxWidth&&this._container.style.maxWidth!==this.options.maxWidth&&(this._container.style.maxWidth=this.options.maxWidth),this._lngLat=Kf(this._lngLat,this._flatPos,this._map.transform,this._trackPointer),this._trackPointer&&!l)return;let m=this._flatPos=this._pos=this._trackPointer&&l?l:this._map.project(this._lngLat);this._map.terrain&&(this._flatPos=this._trackPointer&&l?l:this._map.transform.locationToScreenPoint(this._lngLat));let T=this.options.anchor,P=Q0(this.options.offset);if(!T){let W=this._container.offsetWidth,te=this._container.offsetHeight,se;se=m.y+P.bottom.ythis._map.transform.height-te?["bottom"]:[],m.xthis._map.transform.width-W/2&&se.push("right"),T=se.length===0?"bottom":se.join("-")}let V=m.add(P[T]);this.options.subpixelPositioning||(V=V.round()),U.setTransform(this._container,`${wf[T]} translate(${V.x}px,${V.y}px)`),rf(this._container,T,"popup"),this._updateOpacity()},this._onClose=()=>{this.remove()},this.options=o.e(Object.create(w_),z)}addTo(z){return this._map&&this.remove(),this._map=z,this.options.closeOnClick&&this._map.on("click",this._onClose),this.options.closeOnMove&&this._map.on("move",this._onClose),this._map.on("remove",this.remove),this._update(),this._focusFirstElement(),this._trackPointer?(this._map.on("mousemove",this._onMouseMove),this._map.on("mouseup",this._onMouseUp),this._container&&this._container.classList.add("maplibregl-popup-track-pointer"),this._map._canvasContainer.classList.add("maplibregl-track-pointer")):this._map.on("move",this._update),this.fire(new o.l("open")),this}isOpen(){return!!this._map}getLngLat(){return this._lngLat}setLngLat(z){return this._lngLat=o.V.convert(z),this._pos=null,this._flatPos=null,this._trackPointer=!1,this._update(),this._map&&(this._map.on("move",this._update),this._map.off("mousemove",this._onMouseMove),this._container&&this._container.classList.remove("maplibregl-popup-track-pointer"),this._map._canvasContainer.classList.remove("maplibregl-track-pointer")),this}trackPointer(){return this._trackPointer=!0,this._pos=null,this._flatPos=null,this._update(),this._map&&(this._map.off("move",this._update),this._map.on("mousemove",this._onMouseMove),this._map.on("drag",this._onDrag),this._container&&this._container.classList.add("maplibregl-popup-track-pointer"),this._map._canvasContainer.classList.add("maplibregl-track-pointer")),this}getElement(){return this._container}setText(z){return this.setDOMContent(document.createTextNode(z))}setHTML(z){let l=document.createDocumentFragment(),m=document.createElement("body"),T;for(m.innerHTML=z;T=m.firstChild,T;)l.appendChild(T);return this.setDOMContent(l)}getMaxWidth(){var z;return(z=this._container)===null||z===void 0?void 0:z.style.maxWidth}setMaxWidth(z){return this.options.maxWidth=z,this._update(),this}setDOMContent(z){if(this._content)for(;this._content.hasChildNodes();)this._content.firstChild&&this._content.removeChild(this._content.firstChild);else this._content=U.create("div","maplibregl-popup-content",this._container);return this._content.appendChild(z),this._createCloseButton(),this._update(),this._focusFirstElement(),this}addClassName(z){return this._container&&this._container.classList.add(z),this}removeClassName(z){return this._container&&this._container.classList.remove(z),this}setOffset(z){return this.options.offset=z,this._update(),this}toggleClassName(z){if(this._container)return this._container.classList.toggle(z)}setSubpixelPositioning(z){this.options.subpixelPositioning=z}_createCloseButton(){this.options.closeButton&&(this._closeButton=U.create("button","maplibregl-popup-close-button",this._content),this._closeButton.type="button",this._closeButton.innerHTML="×",this._closeButton.addEventListener("click",this._onClose))}_focusFirstElement(){if(!this.options.focusAfterOpen||!this._container)return;let z=this._container.querySelector(T_);z&&z.focus()}},n.RasterDEMTileSource=Ba,n.RasterTileSource=zs,n.ScaleControl=class{constructor(z){this._onMove=()=>{ng(this._map,this._container,this.options)},this.setUnit=l=>{this.options.unit=l,ng(this._map,this._container,this.options)},this.options=Object.assign(Object.assign({},b_),z)}getDefaultPosition(){return"bottom-left"}onAdd(z){return this._map=z,this._container=U.create("div","maplibregl-ctrl maplibregl-ctrl-scale",z.getContainer()),this._map.on("move",this._onMove),this._onMove(),this._container}onRemove(){U.remove(this._container),this._map.off("move",this._onMove),this._map=void 0}},n.ScrollZoomHandler=yc,n.Style=Is,n.TerrainControl=class{constructor(z){this._toggleTerrain=()=>{this._map.getTerrain()?this._map.setTerrain(null):this._map.setTerrain(this.options),this._updateTerrainIcon()},this._updateTerrainIcon=()=>{this._terrainButton.classList.remove("maplibregl-ctrl-terrain"),this._terrainButton.classList.remove("maplibregl-ctrl-terrain-enabled"),this._map.terrain?(this._terrainButton.classList.add("maplibregl-ctrl-terrain-enabled"),this._terrainButton.title=this._map._getUIString("TerrainControl.Disable")):(this._terrainButton.classList.add("maplibregl-ctrl-terrain"),this._terrainButton.title=this._map._getUIString("TerrainControl.Enable"))},this.options=z}onAdd(z){return this._map=z,this._container=U.create("div","maplibregl-ctrl maplibregl-ctrl-group"),this._terrainButton=U.create("button","maplibregl-ctrl-terrain",this._container),U.create("span","maplibregl-ctrl-icon",this._terrainButton).setAttribute("aria-hidden","true"),this._terrainButton.type="button",this._terrainButton.addEventListener("click",this._toggleTerrain),this._updateTerrainIcon(),this._map.on("terrain",this._updateTerrainIcon),this._container}onRemove(){U.remove(this._container),this._map.off("terrain",this._updateTerrainIcon),this._map=void 0}},n.TwoFingersTouchPitchHandler=Gn,n.TwoFingersTouchRotateHandler=$n,n.TwoFingersTouchZoomHandler=qi,n.TwoFingersTouchZoomRotateHandler=Ju,n.VectorTileSource=mo,n.VideoSource=Yt,n.addSourceType=(z,l)=>o._(void 0,void 0,void 0,function*(){if(rt(z))throw new Error(`A source type called "${z}" already exists.`);((m,T)=>{Ne[m]=T})(z,l)}),n.clearPrewarmedResources=function(){let z=Hn;z&&(z.isPreloaded()&&z.numActive()===1?(z.release(Xi),Hn=null):console.warn("Could not clear WebWorkers since there are active Map instances that still reference it. The pre-warmed WebWorker pool can only be cleared when all map instances have been removed with map.remove()"))},n.createTileMesh=be,n.getMaxParallelImageRequests=function(){return o.c.MAX_PARALLEL_IMAGE_REQUESTS},n.getRTLTextPluginStatus=function(){return fr().getRTLTextPluginStatus()},n.getVersion=function(){return Qg},n.getWorkerCount=function(){return xi.workerCount},n.getWorkerUrl=function(){return o.c.WORKER_URL},n.importScriptInWorkers=function(z){return mr().broadcast("IS",z)},n.isTimeFrozen=function(){return F.isFrozen()},n.now=k,n.prewarm=function(){nt().acquire(Xi)},n.restoreNow=function(){F.restoreNow()},n.setMaxParallelImageRequests=function(z){o.c.MAX_PARALLEL_IMAGE_REQUESTS=z},n.setNow=function(z){F.setNow(z)},n.setRTLTextPlugin=function(z,l){return fr().setRTLTextPlugin(z,l)},n.setWorkerCount=function(z){xi.workerCount=z},n.setWorkerUrl=function(z){o.c.WORKER_URL=z}});var i=r;return i})});var Lle=Vi((pzt,s7)=>{"use strict";s7.exports=HN;s7.exports.default=HN;function HN(r,e,t){t=t||2;var i=e&&e.length,n=i?e[0]*t:r.length,o=Ple(r,0,n,t,!0),s=[];if(!o||o.next===o.prev)return s;var c,f,g,y,M,F,k;if(i&&(o=HNe(r,e,o,t)),r.length>80*t){c=g=r[0],f=y=r[1];for(var U=t;Ug&&(g=M),F>y&&(y=F);k=Math.max(g-c,y-f),k=k!==0?32767/k:0}return _3(o,s,t,c,f,k,0),s}function Ple(r,e,t,i,n){var o,s;if(n===o7(r,e,t,i)>0)for(o=e;o=e;o-=i)s=Ile(o,r[o],r[o+1],s);return s&&WN(s,s.next)&&(v3(s),s=s.next),s}function V1(r,e){if(!r)return r;e||(e=r);var t=r,i;do if(i=!1,!t.steiner&&(WN(t,t.next)||Eu(t.prev,t,t.next)===0)){if(v3(t),t=e=t.prev,t===t.next)break;i=!0}else t=t.next;while(i||t!==e);return e}function _3(r,e,t,i,n,o,s){if(r){!s&&o&&ZNe(r,i,n,o);for(var c=r,f,g;r.prev!==r.next;){if(f=r.prev,g=r.next,o?VNe(r,i,n,o):UNe(r)){e.push(f.i/t|0),e.push(r.i/t|0),e.push(g.i/t|0),v3(r),r=g.next,c=g.next;continue}if(r=g,r===c){s?s===1?(r=jNe(V1(r),e,t),_3(r,e,t,i,n,o,2)):s===2&&GNe(r,e,t,i,n,o):_3(V1(r),e,t,i,n,o,1);break}}}}function UNe(r){var e=r.prev,t=r,i=r.next;if(Eu(e,t,i)>=0)return!1;for(var n=e.x,o=t.x,s=i.x,c=e.y,f=t.y,g=i.y,y=no?n>s?n:s:o>s?o:s,k=c>f?c>g?c:g:f>g?f:g,U=i.next;U!==e;){if(U.x>=y&&U.x<=F&&U.y>=M&&U.y<=k&&_S(n,c,o,f,s,g,U.x,U.y)&&Eu(U.prev,U,U.next)>=0)return!1;U=U.next}return!0}function VNe(r,e,t,i){var n=r.prev,o=r,s=r.next;if(Eu(n,o,s)>=0)return!1;for(var c=n.x,f=o.x,g=s.x,y=n.y,M=o.y,F=s.y,k=cf?c>g?c:g:f>g?f:g,ee=y>M?y>F?y:F:M>F?M:F,he=i7(k,U,e,t,i),xe=i7(K,ee,e,t,i),me=r.prevZ,Ce=r.nextZ;me&&me.z>=he&&Ce&&Ce.z<=xe;){if(me.x>=k&&me.x<=K&&me.y>=U&&me.y<=ee&&me!==n&&me!==s&&_S(c,y,f,M,g,F,me.x,me.y)&&Eu(me.prev,me,me.next)>=0||(me=me.prevZ,Ce.x>=k&&Ce.x<=K&&Ce.y>=U&&Ce.y<=ee&&Ce!==n&&Ce!==s&&_S(c,y,f,M,g,F,Ce.x,Ce.y)&&Eu(Ce.prev,Ce,Ce.next)>=0))return!1;Ce=Ce.nextZ}for(;me&&me.z>=he;){if(me.x>=k&&me.x<=K&&me.y>=U&&me.y<=ee&&me!==n&&me!==s&&_S(c,y,f,M,g,F,me.x,me.y)&&Eu(me.prev,me,me.next)>=0)return!1;me=me.prevZ}for(;Ce&&Ce.z<=xe;){if(Ce.x>=k&&Ce.x<=K&&Ce.y>=U&&Ce.y<=ee&&Ce!==n&&Ce!==s&&_S(c,y,f,M,g,F,Ce.x,Ce.y)&&Eu(Ce.prev,Ce,Ce.next)>=0)return!1;Ce=Ce.nextZ}return!0}function jNe(r,e,t){var i=r;do{var n=i.prev,o=i.next.next;!WN(n,o)&&Rle(n,i,i.next,o)&&y3(n,o)&&y3(o,n)&&(e.push(n.i/t|0),e.push(i.i/t|0),e.push(o.i/t|0),v3(i),v3(i.next),i=r=o),i=i.next}while(i!==r);return V1(i)}function GNe(r,e,t,i,n,o){var s=r;do{for(var c=s.next.next;c!==s.prev;){if(s.i!==c.i&&QNe(s,c)){var f=Ble(s,c);s=V1(s,s.next),f=V1(f,f.next),_3(s,e,t,i,n,o,0),_3(f,e,t,i,n,o,0);return}c=c.next}s=s.next}while(s!==r)}function HNe(r,e,t,i){var n=[],o,s,c,f,g;for(o=0,s=e.length;o=t.next.y&&t.next.y!==t.y){var c=t.x+(n-t.y)*(t.next.x-t.x)/(t.next.y-t.y);if(c<=i&&c>o&&(o=c,s=t.x=t.x&&t.x>=g&&i!==t.x&&_S(ns.x||t.x===s.x&&XNe(s,t)))&&(s=t,M=F)),t=t.next;while(t!==f);return s}function XNe(r,e){return Eu(r.prev,r,e.prev)<0&&Eu(e.next,r,r.next)<0}function ZNe(r,e,t,i){var n=r;do n.z===0&&(n.z=i7(n.x,n.y,e,t,i)),n.prevZ=n.prev,n.nextZ=n.next,n=n.next;while(n!==r);n.prevZ.nextZ=null,n.prevZ=null,YNe(n)}function YNe(r){var e,t,i,n,o,s,c,f,g=1;do{for(t=r,r=null,o=null,s=0;t;){for(s++,i=t,c=0,e=0;e0||f>0&&i;)c!==0&&(f===0||!i||t.z<=i.z)?(n=t,t=t.nextZ,c--):(n=i,i=i.nextZ,f--),o?o.nextZ=n:r=n,n.prevZ=o,o=n;t=i}o.nextZ=null,g*=2}while(s>1);return r}function i7(r,e,t,i,n){return r=(r-t)*n|0,e=(e-i)*n|0,r=(r|r<<8)&16711935,r=(r|r<<4)&252645135,r=(r|r<<2)&858993459,r=(r|r<<1)&1431655765,e=(e|e<<8)&16711935,e=(e|e<<4)&252645135,e=(e|e<<2)&858993459,e=(e|e<<1)&1431655765,r|e<<1}function KNe(r){var e=r,t=r;do(e.x=(r-s)*(o-c)&&(r-s)*(i-c)>=(t-s)*(e-c)&&(t-s)*(o-c)>=(n-s)*(i-c)}function QNe(r,e){return r.next.i!==e.i&&r.prev.i!==e.i&&!JNe(r,e)&&(y3(r,e)&&y3(e,r)&&e4e(r,e)&&(Eu(r.prev,r,e.prev)||Eu(r,e.prev,e))||WN(r,e)&&Eu(r.prev,r,r.next)>0&&Eu(e.prev,e,e.next)>0)}function Eu(r,e,t){return(e.y-r.y)*(t.x-e.x)-(e.x-r.x)*(t.y-e.y)}function WN(r,e){return r.x===e.x&&r.y===e.y}function Rle(r,e,t,i){var n=GN(Eu(r,e,t)),o=GN(Eu(r,e,i)),s=GN(Eu(t,i,r)),c=GN(Eu(t,i,e));return!!(n!==o&&s!==c||n===0&&jN(r,t,e)||o===0&&jN(r,i,e)||s===0&&jN(t,r,i)||c===0&&jN(t,e,i))}function jN(r,e,t){return e.x<=Math.max(r.x,t.x)&&e.x>=Math.min(r.x,t.x)&&e.y<=Math.max(r.y,t.y)&&e.y>=Math.min(r.y,t.y)}function GN(r){return r>0?1:r<0?-1:0}function JNe(r,e){var t=r;do{if(t.i!==r.i&&t.next.i!==r.i&&t.i!==e.i&&t.next.i!==e.i&&Rle(t,t.next,r,e))return!0;t=t.next}while(t!==r);return!1}function y3(r,e){return Eu(r.prev,r,r.next)<0?Eu(r,e,r.next)>=0&&Eu(r,r.prev,e)>=0:Eu(r,e,r.prev)<0||Eu(r,r.next,e)<0}function e4e(r,e){var t=r,i=!1,n=(r.x+e.x)/2,o=(r.y+e.y)/2;do t.y>o!=t.next.y>o&&t.next.y!==t.y&&n<(t.next.x-t.x)*(o-t.y)/(t.next.y-t.y)+t.x&&(i=!i),t=t.next;while(t!==r);return i}function Ble(r,e){var t=new n7(r.i,r.x,r.y),i=new n7(e.i,e.x,e.y),n=r.next,o=e.prev;return r.next=e,e.prev=r,t.next=n,n.prev=t,i.next=t,t.prev=i,o.next=i,i.prev=o,i}function Ile(r,e,t,i){var n=new n7(r,e,t);return i?(n.next=i.next,n.prev=i,i.next.prev=n,i.next=n):(n.prev=n,n.next=n),n}function v3(r){r.next.prev=r.prev,r.prev.next=r.next,r.prevZ&&(r.prevZ.nextZ=r.nextZ),r.nextZ&&(r.nextZ.prevZ=r.prevZ)}function n7(r,e,t){this.i=r,this.x=e,this.y=t,this.prev=null,this.next=null,this.z=0,this.prevZ=null,this.nextZ=null,this.steiner=!1}HN.deviation=function(r,e,t,i){var n=e&&e.length,o=n?e[0]*t:r.length,s=Math.abs(o7(r,0,o,t));if(n)for(var c=0,f=e.length;c0&&(i+=r[n-1].length,t.holes.push(i))}return t}});var Y7=Vi((ide,nR)=>{(function(r,e){typeof define=="function"&&define.amd?define([],e):typeof qY=="function"&&typeof nR=="object"&&nR&&nR.exports?nR.exports=e():(r.dcodeIO=r.dcodeIO||{}).Long=e()})(ide,function(){"use strict";function r(Se,ve,Qe){this.low=Se|0,this.high=ve|0,this.unsigned=!!Qe}r.prototype.__isLong__,Object.defineProperty(r.prototype,"__isLong__",{value:!0,enumerable:!1,configurable:!1});function e(Se){return(Se&&Se.__isLong__)===!0}r.isLong=e;var t={},i={};function n(Se,ve){var Qe,wt,et;return ve?(Se>>>=0,(et=0<=Se&&Se<256)&&(wt=i[Se],wt)?wt:(Qe=s(Se,(Se|0)<0?-1:0,!0),et&&(i[Se]=Qe),Qe)):(Se|=0,(et=-128<=Se&&Se<128)&&(wt=t[Se],wt)?wt:(Qe=s(Se,Se<0?-1:0,!1),et&&(t[Se]=Qe),Qe))}r.fromInt=n;function o(Se,ve){if(isNaN(Se)||!isFinite(Se))return ve?he:ee;if(ve){if(Se<0)return he;if(Se>=k)return ne}else{if(Se<=-U)return He;if(Se+1>=U)return Oe}return Se<0?o(-Se,ve).neg():s(Se%F|0,Se/F|0,ve)}r.fromNumber=o;function s(Se,ve,Qe){return new r(Se,ve,Qe)}r.fromBits=s;var c=Math.pow;function f(Se,ve,Qe){if(Se.length===0)throw Error("empty string");if(Se==="NaN"||Se==="Infinity"||Se==="+Infinity"||Se==="-Infinity")return ee;if(typeof ve=="number"?(Qe=ve,ve=!1):ve=!!ve,Qe=Qe||10,Qe<2||360)throw Error("interior hyphen");if(wt===0)return f(Se.substring(1),ve,Qe).neg();for(var et=o(c(Qe,8)),xt=ee,sr=0;sr>>0:this.low},We.toNumber=function(){return this.unsigned?(this.high>>>0)*F+(this.low>>>0):this.high*F+(this.low>>>0)},We.toString=function(ve){if(ve=ve||10,ve<2||36>>0,bi=mi.toString(ve);if(sr=hr,sr.isZero())return bi+Dr;for(;bi.length<6;)bi="0"+bi;Dr=""+bi+Dr}},We.getHighBits=function(){return this.high},We.getHighBitsUnsigned=function(){return this.high>>>0},We.getLowBits=function(){return this.low},We.getLowBitsUnsigned=function(){return this.low>>>0},We.getNumBitsAbs=function(){if(this.isNegative())return this.eq(He)?64:this.neg().getNumBitsAbs();for(var ve=this.high!=0?this.high:this.low,Qe=31;Qe>0&&!(ve&1<=0},We.isOdd=function(){return(this.low&1)===1},We.isEven=function(){return(this.low&1)===0},We.equals=function(ve){return e(ve)||(ve=g(ve)),this.unsigned!==ve.unsigned&&this.high>>>31===1&&ve.high>>>31===1?!1:this.high===ve.high&&this.low===ve.low},We.eq=We.equals,We.notEquals=function(ve){return!this.eq(ve)},We.neq=We.notEquals,We.lessThan=function(ve){return this.comp(ve)<0},We.lt=We.lessThan,We.lessThanOrEqual=function(ve){return this.comp(ve)<=0},We.lte=We.lessThanOrEqual,We.greaterThan=function(ve){return this.comp(ve)>0},We.gt=We.greaterThan,We.greaterThanOrEqual=function(ve){return this.comp(ve)>=0},We.gte=We.greaterThanOrEqual,We.compare=function(ve){if(e(ve)||(ve=g(ve)),this.eq(ve))return 0;var Qe=this.isNegative(),wt=ve.isNegative();return Qe&&!wt?-1:!Qe&&wt?1:this.unsigned?ve.high>>>0>this.high>>>0||ve.high===this.high&&ve.low>>>0>this.low>>>0?-1:1:this.sub(ve).isNegative()?-1:1},We.comp=We.compare,We.negate=function(){return!this.unsigned&&this.eq(He)?He:this.not().add(xe)},We.neg=We.negate,We.add=function(ve){e(ve)||(ve=g(ve));var Qe=this.high>>>16,wt=this.high&65535,et=this.low>>>16,xt=this.low&65535,sr=ve.high>>>16,Dr=ve.high&65535,hr=ve.low>>>16,mi=ve.low&65535,bi=0,un=0,Yr=0,Xi=0;return Xi+=xt+mi,Yr+=Xi>>>16,Xi&=65535,Yr+=et+hr,un+=Yr>>>16,Yr&=65535,un+=wt+Dr,bi+=un>>>16,un&=65535,bi+=Qe+sr,bi&=65535,s(Yr<<16|Xi,bi<<16|un,this.unsigned)},We.subtract=function(ve){return e(ve)||(ve=g(ve)),this.add(ve.neg())},We.sub=We.subtract,We.multiply=function(ve){if(this.isZero()||(e(ve)||(ve=g(ve)),ve.isZero()))return ee;if(this.eq(He))return ve.isOdd()?He:ee;if(ve.eq(He))return this.isOdd()?He:ee;if(this.isNegative())return ve.isNegative()?this.neg().mul(ve.neg()):this.neg().mul(ve).neg();if(ve.isNegative())return this.mul(ve.neg()).neg();if(this.lt(K)&&ve.lt(K))return o(this.toNumber()*ve.toNumber(),this.unsigned);var Qe=this.high>>>16,wt=this.high&65535,et=this.low>>>16,xt=this.low&65535,sr=ve.high>>>16,Dr=ve.high&65535,hr=ve.low>>>16,mi=ve.low&65535,bi=0,un=0,Yr=0,Xi=0;return Xi+=xt*mi,Yr+=Xi>>>16,Xi&=65535,Yr+=et*mi,un+=Yr>>>16,Yr&=65535,Yr+=xt*hr,un+=Yr>>>16,Yr&=65535,un+=wt*mi,bi+=un>>>16,un&=65535,un+=et*hr,bi+=un>>>16,un&=65535,un+=xt*Dr,bi+=un>>>16,un&=65535,bi+=Qe*mi+wt*hr+et*Dr+xt*sr,bi&=65535,s(Yr<<16|Xi,bi<<16|un,this.unsigned)},We.mul=We.multiply,We.divide=function(ve){if(e(ve)||(ve=g(ve)),ve.isZero())throw Error("division by zero");if(this.isZero())return this.unsigned?he:ee;var Qe,wt,et;if(this.unsigned){if(ve.unsigned||(ve=ve.toUnsigned()),ve.gt(this))return he;if(ve.gt(this.shru(1)))return me;et=he}else{if(this.eq(He)){if(ve.eq(xe)||ve.eq(Ce))return He;if(ve.eq(He))return xe;var xt=this.shr(1);return Qe=xt.div(ve).shl(1),Qe.eq(ee)?ve.isNegative()?xe:Ce:(wt=this.sub(ve.mul(Qe)),et=Qe.add(wt.div(ve)),et)}else if(ve.eq(He))return this.unsigned?he:ee;if(this.isNegative())return ve.isNegative()?this.neg().div(ve.neg()):this.neg().div(ve).neg();if(ve.isNegative())return this.div(ve.neg()).neg();et=ee}for(wt=this;wt.gte(ve);){Qe=Math.max(1,Math.floor(wt.toNumber()/ve.toNumber()));for(var sr=Math.ceil(Math.log(Qe)/Math.LN2),Dr=sr<=48?1:c(2,sr-48),hr=o(Qe),mi=hr.mul(ve);mi.isNegative()||mi.gt(wt);)Qe-=Dr,hr=o(Qe,this.unsigned),mi=hr.mul(ve);hr.isZero()&&(hr=xe),et=et.add(hr),wt=wt.sub(mi)}return et},We.div=We.divide,We.modulo=function(ve){return e(ve)||(ve=g(ve)),this.sub(this.div(ve).mul(ve))},We.mod=We.modulo,We.not=function(){return s(~this.low,~this.high,this.unsigned)},We.and=function(ve){return e(ve)||(ve=g(ve)),s(this.low&ve.low,this.high&ve.high,this.unsigned)},We.or=function(ve){return e(ve)||(ve=g(ve)),s(this.low|ve.low,this.high|ve.high,this.unsigned)},We.xor=function(ve){return e(ve)||(ve=g(ve)),s(this.low^ve.low,this.high^ve.high,this.unsigned)},We.shiftLeft=function(ve){return e(ve)&&(ve=ve.toInt()),(ve&=63)===0?this:ve<32?s(this.low<>>32-ve,this.unsigned):s(0,this.low<>>ve|this.high<<32-ve,this.high>>ve,this.unsigned):s(this.high>>ve-32,this.high>=0?0:-1,this.unsigned)},We.shr=We.shiftRight,We.shiftRightUnsigned=function(ve){if(e(ve)&&(ve=ve.toInt()),ve&=63,ve===0)return this;var Qe=this.high;if(ve<32){var wt=this.low;return s(wt>>>ve|Qe<<32-ve,Qe>>>ve,this.unsigned)}else return ve===32?s(Qe,0,this.unsigned):s(Qe>>>ve-32,0,this.unsigned)},We.shru=We.shiftRightUnsigned,We.toSigned=function(){return this.unsigned?s(this.low,this.high,!1):this},We.toUnsigned=function(){return this.unsigned?this:s(this.low,this.high,!0)},We.toBytes=function(Se){return Se?this.toBytesLE():this.toBytesBE()},We.toBytesLE=function(){var Se=this.high,ve=this.low;return[ve&255,ve>>>8&255,ve>>>16&255,ve>>>24&255,Se&255,Se>>>8&255,Se>>>16&255,Se>>>24&255]},We.toBytesBE=function(){var Se=this.high,ve=this.low;return[Se>>>24&255,Se>>>16&255,Se>>>8&255,Se&255,ve>>>24&255,ve>>>16&255,ve>>>8&255,ve&255]},r})});var qge=Vi(Uq=>{Uq.read=function(r,e,t,i,n){var o,s,c=n*8-i-1,f=(1<>1,y=-7,M=t?n-1:0,F=t?-1:1,k=r[e+M];for(M+=F,o=k&(1<<-y)-1,k>>=-y,y+=c;y>0;o=o*256+r[e+M],M+=F,y-=8);for(s=o&(1<<-y)-1,o>>=-y,y+=i;y>0;s=s*256+r[e+M],M+=F,y-=8);if(o===0)o=1-g;else{if(o===f)return s?NaN:(k?-1:1)*(1/0);s=s+Math.pow(2,i),o=o-g}return(k?-1:1)*s*Math.pow(2,o-i)};Uq.write=function(r,e,t,i,n,o){var s,c,f,g=o*8-n-1,y=(1<>1,F=n===23?Math.pow(2,-24)-Math.pow(2,-77):0,k=i?0:o-1,U=i?1:-1,K=e<0||e===0&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(c=isNaN(e)?1:0,s=y):(s=Math.floor(Math.log(e)/Math.LN2),e*(f=Math.pow(2,-s))<1&&(s--,f*=2),s+M>=1?e+=F/f:e+=F*Math.pow(2,1-M),e*f>=2&&(s++,f/=2),s+M>=y?(c=0,s=y):s+M>=1?(c=(e*f-1)*Math.pow(2,n),s=s+M):(c=e*Math.pow(2,M-1)*Math.pow(2,n),s=0));n>=8;r[t+k]=c&255,k+=U,c/=256,n-=8);for(s=s<0;r[t+k]=s&255,k+=U,s/=256,g-=8);r[t+k-U]|=K*128}});var Qge=Vi((HQt,Kge)=>{"use strict";Kge.exports=gl;var u6=qge();function gl(r){this.buf=ArrayBuffer.isView&&ArrayBuffer.isView(r)?r:new Uint8Array(r||0),this.pos=0,this.type=0,this.length=this.buf.length}gl.Varint=0;gl.Fixed64=1;gl.Bytes=2;gl.Fixed32=5;var Vq=(1<<16)*(1<<16),$ge=1/Vq,e$e=12,Yge=typeof TextDecoder>"u"?null:new TextDecoder("utf-8");gl.prototype={destroy:function(){this.buf=null},readFields:function(r,e,t){for(t=t||this.length;this.pos>3,o=this.pos;this.type=i&7,r(n,e,this),this.pos===o&&this.skip(i)}return e},readMessage:function(r,e){return this.readFields(r,e,this.readVarint()+this.pos)},readFixed32:function(){var r=h6(this.buf,this.pos);return this.pos+=4,r},readSFixed32:function(){var r=Zge(this.buf,this.pos);return this.pos+=4,r},readFixed64:function(){var r=h6(this.buf,this.pos)+h6(this.buf,this.pos+4)*Vq;return this.pos+=8,r},readSFixed64:function(){var r=h6(this.buf,this.pos)+Zge(this.buf,this.pos+4)*Vq;return this.pos+=8,r},readFloat:function(){var r=u6.read(this.buf,this.pos,!0,23,4);return this.pos+=4,r},readDouble:function(){var r=u6.read(this.buf,this.pos,!0,52,8);return this.pos+=8,r},readVarint:function(r){var e=this.buf,t,i;return i=e[this.pos++],t=i&127,i<128||(i=e[this.pos++],t|=(i&127)<<7,i<128)||(i=e[this.pos++],t|=(i&127)<<14,i<128)||(i=e[this.pos++],t|=(i&127)<<21,i<128)?t:(i=e[this.pos],t|=(i&15)<<28,t$e(t,r,this))},readVarint64:function(){return this.readVarint(!0)},readSVarint:function(){var r=this.readVarint();return r%2===1?(r+1)/-2:r/2},readBoolean:function(){return Boolean(this.readVarint())},readString:function(){var r=this.readVarint()+this.pos,e=this.pos;return this.pos=r,r-e>=e$e&&Yge?m$e(this.buf,e,r):p$e(this.buf,e,r)},readBytes:function(){var r=this.readVarint()+this.pos,e=this.buf.subarray(this.pos,r);return this.pos=r,e},readPackedVarint:function(r,e){if(this.type!==gl.Bytes)return r.push(this.readVarint(e));var t=Fy(this);for(r=r||[];this.pos127;);else if(e===gl.Bytes)this.pos=this.readVarint()+this.pos;else if(e===gl.Fixed32)this.pos+=4;else if(e===gl.Fixed64)this.pos+=8;else throw new Error("Unimplemented type: "+e)},writeTag:function(r,e){this.writeVarint(r<<3|e)},realloc:function(r){for(var e=this.length||16;e268435455||r<0){r$e(r,this);return}this.realloc(4),this.buf[this.pos++]=r&127|(r>127?128:0),!(r<=127)&&(this.buf[this.pos++]=(r>>>=7)&127|(r>127?128:0),!(r<=127)&&(this.buf[this.pos++]=(r>>>=7)&127|(r>127?128:0),!(r<=127)&&(this.buf[this.pos++]=r>>>7&127)))},writeSVarint:function(r){this.writeVarint(r<0?-r*2-1:r*2)},writeBoolean:function(r){this.writeVarint(Boolean(r))},writeString:function(r){r=String(r),this.realloc(r.length*4),this.pos++;var e=this.pos;this.pos=g$e(this.buf,r,this.pos);var t=this.pos-e;t>=128&&Xge(e,t,this),this.pos=e-1,this.writeVarint(t),this.pos+=t},writeFloat:function(r){this.realloc(4),u6.write(this.buf,r,this.pos,!0,23,4),this.pos+=4},writeDouble:function(r){this.realloc(8),u6.write(this.buf,r,this.pos,!0,52,8),this.pos+=8},writeBytes:function(r){var e=r.length;this.writeVarint(e),this.realloc(e);for(var t=0;t=128&&Xge(t,i,this),this.pos=t-1,this.writeVarint(i),this.pos+=i},writeMessage:function(r,e,t){this.writeTag(r,gl.Bytes),this.writeRawMessage(e,t)},writePackedVarint:function(r,e){e.length&&this.writeMessage(r,o$e,e)},writePackedSVarint:function(r,e){e.length&&this.writeMessage(r,s$e,e)},writePackedBoolean:function(r,e){e.length&&this.writeMessage(r,c$e,e)},writePackedFloat:function(r,e){e.length&&this.writeMessage(r,a$e,e)},writePackedDouble:function(r,e){e.length&&this.writeMessage(r,l$e,e)},writePackedFixed32:function(r,e){e.length&&this.writeMessage(r,u$e,e)},writePackedSFixed32:function(r,e){e.length&&this.writeMessage(r,h$e,e)},writePackedFixed64:function(r,e){e.length&&this.writeMessage(r,f$e,e)},writePackedSFixed64:function(r,e){e.length&&this.writeMessage(r,d$e,e)},writeBytesField:function(r,e){this.writeTag(r,gl.Bytes),this.writeBytes(e)},writeFixed32Field:function(r,e){this.writeTag(r,gl.Fixed32),this.writeFixed32(e)},writeSFixed32Field:function(r,e){this.writeTag(r,gl.Fixed32),this.writeSFixed32(e)},writeFixed64Field:function(r,e){this.writeTag(r,gl.Fixed64),this.writeFixed64(e)},writeSFixed64Field:function(r,e){this.writeTag(r,gl.Fixed64),this.writeSFixed64(e)},writeVarintField:function(r,e){this.writeTag(r,gl.Varint),this.writeVarint(e)},writeSVarintField:function(r,e){this.writeTag(r,gl.Varint),this.writeSVarint(e)},writeStringField:function(r,e){this.writeTag(r,gl.Bytes),this.writeString(e)},writeFloatField:function(r,e){this.writeTag(r,gl.Fixed32),this.writeFloat(e)},writeDoubleField:function(r,e){this.writeTag(r,gl.Fixed64),this.writeDouble(e)},writeBooleanField:function(r,e){this.writeVarintField(r,Boolean(e))}};function t$e(r,e,t){var i=t.buf,n,o;if(o=i[t.pos++],n=(o&112)>>4,o<128||(o=i[t.pos++],n|=(o&127)<<3,o<128)||(o=i[t.pos++],n|=(o&127)<<10,o<128)||(o=i[t.pos++],n|=(o&127)<<17,o<128)||(o=i[t.pos++],n|=(o&127)<<24,o<128)||(o=i[t.pos++],n|=(o&1)<<31,o<128))return gE(r,n,e);throw new Error("Expected varint not more than 10 bytes")}function Fy(r){return r.type===gl.Bytes?r.readVarint()+r.pos:r.pos+1}function gE(r,e,t){return t?e*4294967296+(r>>>0):(e>>>0)*4294967296+(r>>>0)}function r$e(r,e){var t,i;if(r>=0?(t=r%4294967296|0,i=r/4294967296|0):(t=~(-r%4294967296),i=~(-r/4294967296),t^4294967295?t=t+1|0:(t=0,i=i+1|0)),r>=18446744073709552e3||r<-18446744073709552e3)throw new Error("Given varint doesn't fit into 10 bytes");e.realloc(10),i$e(t,i,e),n$e(i,e)}function i$e(r,e,t){t.buf[t.pos++]=r&127|128,r>>>=7,t.buf[t.pos++]=r&127|128,r>>>=7,t.buf[t.pos++]=r&127|128,r>>>=7,t.buf[t.pos++]=r&127|128,r>>>=7,t.buf[t.pos]=r&127}function n$e(r,e){var t=(r&7)<<4;e.buf[e.pos++]|=t|((r>>>=3)?128:0),r&&(e.buf[e.pos++]=r&127|((r>>>=7)?128:0),r&&(e.buf[e.pos++]=r&127|((r>>>=7)?128:0),r&&(e.buf[e.pos++]=r&127|((r>>>=7)?128:0),r&&(e.buf[e.pos++]=r&127|((r>>>=7)?128:0),r&&(e.buf[e.pos++]=r&127)))))}function Xge(r,e,t){var i=e<=16383?1:e<=2097151?2:e<=268435455?3:Math.floor(Math.log(e)/(Math.LN2*7));t.realloc(i);for(var n=t.pos-1;n>=r;n--)t.buf[n+i]=t.buf[n]}function o$e(r,e){for(var t=0;t>>8,r[t+2]=e>>>16,r[t+3]=e>>>24}function Zge(r,e){return(r[e]|r[e+1]<<8|r[e+2]<<16)+(r[e+3]<<24)}function p$e(r,e,t){for(var i="",n=e;n239?4:o>223?3:o>191?2:1;if(n+c>t)break;var f,g,y;c===1?o<128&&(s=o):c===2?(f=r[n+1],(f&192)===128&&(s=(o&31)<<6|f&63,s<=127&&(s=null))):c===3?(f=r[n+1],g=r[n+2],(f&192)===128&&(g&192)===128&&(s=(o&15)<<12|(f&63)<<6|g&63,(s<=2047||s>=55296&&s<=57343)&&(s=null))):c===4&&(f=r[n+1],g=r[n+2],y=r[n+3],(f&192)===128&&(g&192)===128&&(y&192)===128&&(s=(o&15)<<18|(f&63)<<12|(g&63)<<6|y&63,(s<=65535||s>=1114112)&&(s=null))),s===null?(s=65533,c=1):s>65535&&(s-=65536,i+=String.fromCharCode(s>>>10&1023|55296),s=56320|s&1023),i+=String.fromCharCode(s),n+=c}return i}function m$e(r,e,t){return Yge.decode(r.subarray(e,t))}function g$e(r,e,t){for(var i=0,n,o;i55295&&n<57344)if(o)if(n<56320){r[t++]=239,r[t++]=191,r[t++]=189,o=n;continue}else n=o-55296<<10|n-56320|65536,o=null;else{n>56319||i+1===e.length?(r[t++]=239,r[t++]=191,r[t++]=189):o=n;continue}else o&&(r[t++]=239,r[t++]=191,r[t++]=189,o=null);n<128?r[t++]=n:(n<2048?r[t++]=n>>6|192:(n<65536?r[t++]=n>>12|224:(r[t++]=n>>18|240,r[t++]=n>>12&63|128),r[t++]=n>>6&63|128),r[t++]=n&63|128)}return t}});var b0e=Vi((NR,m6)=>{(function(r){"use strict";var e="Compound",t="Identifier",i="MemberExpression",n="Literal",o="ThisExpression",s="CallExpression",c="UnaryExpression",f="BinaryExpression",g="LogicalExpression",y="ConditionalExpression",M="ArrayExpression",F=46,k=44,U=39,K=34,ee=40,he=41,xe=91,me=93,Ce=63,Oe=59,ne=58,He=function(xi,ii){var Hn=new Error(xi+" at character "+ii);throw Hn.index=ii,Hn.description=xi,Hn},We=!0,Se={"-":We,"!":We,"~":We,"+":We},ve={"||":1,"&&":2,"|":3,"^":4,"&":5,"==":6,"!=":6,"===":6,"!==":6,"<":7,">":7,"<=":7,">=":7,"<<":8,">>":8,">>>":8,"+":9,"-":9,"*":10,"/":10,"%":10},Qe=function(xi){var ii=0,Hn;for(var Ve in xi)(Hn=Ve.length)>ii&&xi.hasOwnProperty(Ve)&&(ii=Hn);return ii},wt=Qe(Se),et=Qe(ve),xt={true:!0,false:!1,null:null},sr="this",Dr=function(xi){return ve[xi]||0},hr=function(xi,ii,Hn){var Ve=xi==="||"||xi==="&&"?g:f;return{type:Ve,operator:xi,left:ii,right:Hn}},mi=function(xi){return xi>=48&&xi<=57},bi=function(xi){return xi===36||xi===95||xi>=65&&xi<=90||xi>=97&&xi<=122||xi>=128&&!ve[String.fromCharCode(xi)]},un=function(xi){return xi===36||xi===95||xi>=65&&xi<=90||xi>=97&&xi<=122||xi>=48&&xi<=57||xi>=128&&!ve[String.fromCharCode(xi)]},Yr=function(xi){for(var ii=0,Hn=xi.charAt,Ve=xi.charCodeAt,nt=function(Fe){return Hn.call(xi,Fe)},Ft=function(Fe){return Ve.call(xi,Fe)},mr=xi.length,Xr=function(){for(var Fe=Ft(ii);Fe===32||Fe===9||Fe===10||Fe===13;)Fe=Ft(++ii)},zr=function(){var Fe=Nn(),Ne,rt;if(Xr(),Ft(ii)===Ce){if(ii++,Ne=zr(),Ne||He("Expected expression",ii),Xr(),Ft(ii)===ne)return ii++,rt=zr(),rt||He("Expected expression",ii),{type:y,test:Fe,consequent:Ne,alternate:rt};He("Expected :",ii)}else return Fe},ln=function(){Xr();for(var Fe,Ne=xi.substr(ii,et),rt=Ne.length;rt>0;){if(ve.hasOwnProperty(Ne)&&(!bi(Ft(ii))||ii+Ne.length2&&Vt<=Ht[Ht.length-2].prec;)nr=Ht.pop(),rt=Ht.pop().value,fr=Ht.pop(),Ne=hr(rt,fr,nr),Ht.push(Ne);Ne=Co(),Ne||He("Expected expression after "+ti,ii),Ht.push(yr,Ne)}for(xr=Ht.length-1,Ne=Ht[xr];xr>1;)Ne=hr(Ht[xr-1].value,Ht[xr-2],Ne),xr-=2;return Ne},Co=function(){var Fe,Ne,rt;if(Xr(),Fe=Ft(ii),mi(Fe)||Fe===F)return Go();if(Fe===U||Fe===K)return is();if(Fe===xe)return so();for(Ne=xi.substr(ii,wt),rt=Ne.length;rt>0;){if(Se.hasOwnProperty(Ne)&&(!bi(Ft(ii))||ii+Ne.length=rt.length&&He("Unexpected token "+String.fromCharCode(Fe),ii);break}else if(Ne===k){if(ii++,yr++,yr!==rt.length){if(Fe===he)He("Unexpected token ,",ii);else if(Fe===me)for(var fr=rt.length;fr"u"){var Xi=r.jsep;r.jsep=Yr,Yr.noConflict=function(){return r.jsep===Yr&&(r.jsep=Xi),Yr}}else typeof m6<"u"&&m6.exports?NR=m6.exports=Yr:NR.parse=Yr})(NR)});var qAe=Vi($6=>{"use strict";$6.byteLength=mZe;$6.toByteArray=AZe;$6.fromByteArray=vZe;var A_=[],Xg=[],pZe=typeof Uint8Array<"u"?Uint8Array:Array,P$="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(Iw=0,HAe=P$.length;Iw0)throw new Error("Invalid string. Length must be a multiple of 4");var t=r.indexOf("=");t===-1&&(t=e);var i=t===e?0:4-t%4;return[t,i]}function mZe(r){var e=WAe(r),t=e[0],i=e[1];return(t+i)*3/4-i}function gZe(r,e,t){return(e+t)*3/4-t}function AZe(r){var e,t=WAe(r),i=t[0],n=t[1],o=new pZe(gZe(r,i,n)),s=0,c=n>0?i-4:i,f;for(f=0;f>16&255,o[s++]=e>>8&255,o[s++]=e&255;return n===2&&(e=Xg[r.charCodeAt(f)]<<2|Xg[r.charCodeAt(f+1)]>>4,o[s++]=e&255),n===1&&(e=Xg[r.charCodeAt(f)]<<10|Xg[r.charCodeAt(f+1)]<<4|Xg[r.charCodeAt(f+2)]>>2,o[s++]=e>>8&255,o[s++]=e&255),o}function _Ze(r){return A_[r>>18&63]+A_[r>>12&63]+A_[r>>6&63]+A_[r&63]}function yZe(r,e,t){for(var i,n=[],o=e;oc?c:s+o));return i===1?(e=r[t-1],n.push(A_[e>>2]+A_[e<<4&63]+"==")):i===2&&(e=(r[t-2]<<8)+r[t-1],n.push(A_[e>>10]+A_[e>>4&63]+A_[e<<2&63]+"=")),n.join("")}});var PE=Vi((X6,$Ae)=>{(function(r,e){typeof X6=="object"&&typeof $Ae<"u"?e(X6):typeof define=="function"&&define.amd?define(["exports"],e):(r=typeof globalThis<"u"?globalThis:r||self,e(r.lumino_coreutils={}))})(X6,function(r){"use strict";r.JSONExt=void 0,function(s){s.emptyObject=Object.freeze({}),s.emptyArray=Object.freeze([]);function c(ee){return ee===null||typeof ee=="boolean"||typeof ee=="number"||typeof ee=="string"}s.isPrimitive=c;function f(ee){return Array.isArray(ee)}s.isArray=f;function g(ee){return!c(ee)&&!f(ee)}s.isObject=g;function y(ee,he){if(ee===he)return!0;if(c(ee)||c(he))return!1;var xe=f(ee),me=f(he);return xe!==me?!1:xe&&me?F(ee,he):k(ee,he)}s.deepEqual=y;function M(ee){return c(ee)?ee:f(ee)?U(ee):K(ee)}s.deepCopy=M;function F(ee,he){if(ee===he)return!0;if(ee.length!==he.length)return!1;for(var xe=0,me=ee.length;xe>>0),s[f]=c&255,c>>>=8}r.Random=void 0,function(s){s.getRandomValues=function(){var c=typeof window<"u"&&(window.crypto||window.msCrypto)||null;return c&&typeof c.getRandomValues=="function"?function(g){return c.getRandomValues(g)}:n}()}(r.Random||(r.Random={}));function o(s){for(var c=new Uint8Array(16),f=new Array(256),g=0;g<16;++g)f[g]="0"+g.toString(16);for(var g=16;g<256;++g)f[g]=g.toString(16);return function(){return s(c),c[6]=64|c[6]&15,c[8]=128|c[8]&63,f[c[0]]+f[c[1]]+f[c[2]]+f[c[3]]+"-"+f[c[4]]+f[c[5]]+"-"+f[c[6]]+f[c[7]]+"-"+f[c[8]]+f[c[9]]+"-"+f[c[10]]+f[c[11]]+f[c[12]]+f[c[13]]+f[c[14]]+f[c[15]]}}r.UUID=void 0,function(s){s.uuid4=o(r.Random.getRandomValues)}(r.UUID||(r.UUID={})),r.MimeData=e,r.PromiseDelegate=t,r.Token=i,Object.defineProperty(r,"__esModule",{value:!0})})});var ZAe=Vi((Cnr,XAe)=>{function xZe(){this.__data__=[],this.size=0}XAe.exports=xZe});var R$=Vi((Mnr,YAe)=>{function bZe(r,e){return r===e||r!==r&&e!==e}YAe.exports=bZe});var hB=Vi((Inr,KAe)=>{var wZe=R$();function TZe(r,e){for(var t=r.length;t--;)if(wZe(r[t][0],e))return t;return-1}KAe.exports=TZe});var JAe=Vi((Pnr,QAe)=>{var SZe=hB(),EZe=Array.prototype,CZe=EZe.splice;function MZe(r){var e=this.__data__,t=SZe(e,r);if(t<0)return!1;var i=e.length-1;return t==i?e.pop():CZe.call(e,t,1),--this.size,!0}QAe.exports=MZe});var t_e=Vi((Rnr,e_e)=>{var IZe=hB();function PZe(r){var e=this.__data__,t=IZe(e,r);return t<0?void 0:e[t][1]}e_e.exports=PZe});var i_e=Vi((Bnr,r_e)=>{var RZe=hB();function BZe(r){return RZe(this.__data__,r)>-1}r_e.exports=BZe});var o_e=Vi((Lnr,n_e)=>{var LZe=hB();function DZe(r,e){var t=this.__data__,i=LZe(t,r);return i<0?(++this.size,t.push([r,e])):t[i][1]=e,this}n_e.exports=DZe});var fB=Vi((Dnr,s_e)=>{var OZe=ZAe(),FZe=JAe(),kZe=t_e(),NZe=i_e(),zZe=o_e();function RE(r){var e=-1,t=r==null?0:r.length;for(this.clear();++e{var UZe=fB();function VZe(){this.__data__=new UZe,this.size=0}a_e.exports=VZe});var u_e=Vi((Fnr,c_e)=>{function jZe(r){var e=this.__data__,t=e.delete(r);return this.size=e.size,t}c_e.exports=jZe});var f_e=Vi((knr,h_e)=>{function GZe(r){return this.__data__.get(r)}h_e.exports=GZe});var p_e=Vi((Nnr,d_e)=>{function HZe(r){return this.__data__.has(r)}d_e.exports=HZe});var B$=Vi((znr,m_e)=>{var WZe=typeof global=="object"&&global&&global.Object===Object&&global;m_e.exports=WZe});var __=Vi((Unr,g_e)=>{var qZe=B$(),$Ze=typeof self=="object"&&self&&self.Object===Object&&self,XZe=qZe||$Ze||Function("return this")();g_e.exports=XZe});var Z6=Vi((Vnr,A_e)=>{var ZZe=__(),YZe=ZZe.Symbol;A_e.exports=YZe});var x_e=Vi((jnr,v_e)=>{var __e=Z6(),y_e=Object.prototype,KZe=y_e.hasOwnProperty,QZe=y_e.toString,dB=__e?__e.toStringTag:void 0;function JZe(r){var e=KZe.call(r,dB),t=r[dB];try{r[dB]=void 0;var i=!0}catch{}var n=QZe.call(r);return i&&(e?r[dB]=t:delete r[dB]),n}v_e.exports=JZe});var w_e=Vi((Gnr,b_e)=>{var eYe=Object.prototype,tYe=eYe.toString;function rYe(r){return tYe.call(r)}b_e.exports=rYe});var BE=Vi((Hnr,E_e)=>{var T_e=Z6(),iYe=x_e(),nYe=w_e(),oYe="[object Null]",sYe="[object Undefined]",S_e=T_e?T_e.toStringTag:void 0;function aYe(r){return r==null?r===void 0?sYe:oYe:S_e&&S_e in Object(r)?iYe(r):nYe(r)}E_e.exports=aYe});var L$=Vi((Wnr,C_e)=>{function lYe(r){var e=typeof r;return r!=null&&(e=="object"||e=="function")}C_e.exports=lYe});var D$=Vi((qnr,M_e)=>{var cYe=BE(),uYe=L$(),hYe="[object AsyncFunction]",fYe="[object Function]",dYe="[object GeneratorFunction]",pYe="[object Proxy]";function mYe(r){if(!uYe(r))return!1;var e=cYe(r);return e==fYe||e==dYe||e==hYe||e==pYe}M_e.exports=mYe});var P_e=Vi(($nr,I_e)=>{var gYe=__(),AYe=gYe["__core-js_shared__"];I_e.exports=AYe});var L_e=Vi((Xnr,B_e)=>{var O$=P_e(),R_e=function(){var r=/[^.]+$/.exec(O$&&O$.keys&&O$.keys.IE_PROTO||"");return r?"Symbol(src)_1."+r:""}();function _Ye(r){return!!R_e&&R_e in r}B_e.exports=_Ye});var F$=Vi((Znr,D_e)=>{var yYe=Function.prototype,vYe=yYe.toString;function xYe(r){if(r!=null){try{return vYe.call(r)}catch{}try{return r+""}catch{}}return""}D_e.exports=xYe});var F_e=Vi((Ynr,O_e)=>{var bYe=D$(),wYe=L_e(),TYe=L$(),SYe=F$(),EYe=/[\\^$.*+?()[\]{}|]/g,CYe=/^\[object .+?Constructor\]$/,MYe=Function.prototype,IYe=Object.prototype,PYe=MYe.toString,RYe=IYe.hasOwnProperty,BYe=RegExp("^"+PYe.call(RYe).replace(EYe,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");function LYe(r){if(!TYe(r)||wYe(r))return!1;var e=bYe(r)?BYe:CYe;return e.test(SYe(r))}O_e.exports=LYe});var N_e=Vi((Knr,k_e)=>{function DYe(r,e){return r?.[e]}k_e.exports=DYe});var Pw=Vi((Qnr,z_e)=>{var OYe=F_e(),FYe=N_e();function kYe(r,e){var t=FYe(r,e);return OYe(t)?t:void 0}z_e.exports=kYe});var Y6=Vi((Jnr,U_e)=>{var NYe=Pw(),zYe=__(),UYe=NYe(zYe,"Map");U_e.exports=UYe});var pB=Vi((eor,V_e)=>{var VYe=Pw(),jYe=VYe(Object,"create");V_e.exports=jYe});var H_e=Vi((tor,G_e)=>{var j_e=pB();function GYe(){this.__data__=j_e?j_e(null):{},this.size=0}G_e.exports=GYe});var q_e=Vi((ror,W_e)=>{function HYe(r){var e=this.has(r)&&delete this.__data__[r];return this.size-=e?1:0,e}W_e.exports=HYe});var X_e=Vi((ior,$_e)=>{var WYe=pB(),qYe="__lodash_hash_undefined__",$Ye=Object.prototype,XYe=$Ye.hasOwnProperty;function ZYe(r){var e=this.__data__;if(WYe){var t=e[r];return t===qYe?void 0:t}return XYe.call(e,r)?e[r]:void 0}$_e.exports=ZYe});var Y_e=Vi((nor,Z_e)=>{var YYe=pB(),KYe=Object.prototype,QYe=KYe.hasOwnProperty;function JYe(r){var e=this.__data__;return YYe?e[r]!==void 0:QYe.call(e,r)}Z_e.exports=JYe});var Q_e=Vi((oor,K_e)=>{var eKe=pB(),tKe="__lodash_hash_undefined__";function rKe(r,e){var t=this.__data__;return this.size+=this.has(r)?0:1,t[r]=eKe&&e===void 0?tKe:e,this}K_e.exports=rKe});var eye=Vi((sor,J_e)=>{var iKe=H_e(),nKe=q_e(),oKe=X_e(),sKe=Y_e(),aKe=Q_e();function LE(r){var e=-1,t=r==null?0:r.length;for(this.clear();++e{var tye=eye(),lKe=fB(),cKe=Y6();function uKe(){this.size=0,this.__data__={hash:new tye,map:new(cKe||lKe),string:new tye}}rye.exports=uKe});var oye=Vi((lor,nye)=>{function hKe(r){var e=typeof r;return e=="string"||e=="number"||e=="symbol"||e=="boolean"?r!=="__proto__":r===null}nye.exports=hKe});var mB=Vi((cor,sye)=>{var fKe=oye();function dKe(r,e){var t=r.__data__;return fKe(e)?t[typeof e=="string"?"string":"hash"]:t.map}sye.exports=dKe});var lye=Vi((uor,aye)=>{var pKe=mB();function mKe(r){var e=pKe(this,r).delete(r);return this.size-=e?1:0,e}aye.exports=mKe});var uye=Vi((hor,cye)=>{var gKe=mB();function AKe(r){return gKe(this,r).get(r)}cye.exports=AKe});var fye=Vi((dor,hye)=>{var _Ke=mB();function yKe(r){return _Ke(this,r).has(r)}hye.exports=yKe});var pye=Vi((por,dye)=>{var vKe=mB();function xKe(r,e){var t=vKe(this,r),i=t.size;return t.set(r,e),this.size+=t.size==i?0:1,this}dye.exports=xKe});var k$=Vi((mor,mye)=>{var bKe=iye(),wKe=lye(),TKe=uye(),SKe=fye(),EKe=pye();function DE(r){var e=-1,t=r==null?0:r.length;for(this.clear();++e{var CKe=fB(),MKe=Y6(),IKe=k$(),PKe=200;function RKe(r,e){var t=this.__data__;if(t instanceof CKe){var i=t.__data__;if(!MKe||i.length{var BKe=fB(),LKe=l_e(),DKe=u_e(),OKe=f_e(),FKe=p_e(),kKe=Aye();function OE(r){var e=this.__data__=new BKe(r);this.size=e.size}OE.prototype.clear=LKe;OE.prototype.delete=DKe;OE.prototype.get=OKe;OE.prototype.has=FKe;OE.prototype.set=kKe;_ye.exports=OE});var xye=Vi((_or,vye)=>{var NKe="__lodash_hash_undefined__";function zKe(r){return this.__data__.set(r,NKe),this}vye.exports=zKe});var wye=Vi((yor,bye)=>{function UKe(r){return this.__data__.has(r)}bye.exports=UKe});var Sye=Vi((vor,Tye)=>{var VKe=k$(),jKe=xye(),GKe=wye();function K6(r){var e=-1,t=r==null?0:r.length;for(this.__data__=new VKe;++e{function HKe(r,e){for(var t=-1,i=r==null?0:r.length;++t{function WKe(r,e){return r.has(e)}Mye.exports=WKe});var N$=Vi((wor,Pye)=>{var qKe=Sye(),$Ke=Cye(),XKe=Iye(),ZKe=1,YKe=2;function KKe(r,e,t,i,n,o){var s=t&ZKe,c=r.length,f=e.length;if(c!=f&&!(s&&f>c))return!1;var g=o.get(r),y=o.get(e);if(g&&y)return g==e&&y==r;var M=-1,F=!0,k=t&YKe?new qKe:void 0;for(o.set(r,e),o.set(e,r);++M{var QKe=__(),JKe=QKe.Uint8Array;Rye.exports=JKe});var Dye=Vi((Sor,Lye)=>{function eQe(r){var e=-1,t=Array(r.size);return r.forEach(function(i,n){t[++e]=[n,i]}),t}Lye.exports=eQe});var Fye=Vi((Eor,Oye)=>{function tQe(r){var e=-1,t=Array(r.size);return r.forEach(function(i){t[++e]=i}),t}Oye.exports=tQe});var Vye=Vi((Cor,Uye)=>{var kye=Z6(),Nye=Bye(),rQe=R$(),iQe=N$(),nQe=Dye(),oQe=Fye(),sQe=1,aQe=2,lQe="[object Boolean]",cQe="[object Date]",uQe="[object Error]",hQe="[object Map]",fQe="[object Number]",dQe="[object RegExp]",pQe="[object Set]",mQe="[object String]",gQe="[object Symbol]",AQe="[object ArrayBuffer]",_Qe="[object DataView]",zye=kye?kye.prototype:void 0,z$=zye?zye.valueOf:void 0;function yQe(r,e,t,i,n,o,s){switch(t){case _Qe:if(r.byteLength!=e.byteLength||r.byteOffset!=e.byteOffset)return!1;r=r.buffer,e=e.buffer;case AQe:return!(r.byteLength!=e.byteLength||!o(new Nye(r),new Nye(e)));case lQe:case cQe:case fQe:return rQe(+r,+e);case uQe:return r.name==e.name&&r.message==e.message;case dQe:case mQe:return r==e+"";case hQe:var c=nQe;case pQe:var f=i&sQe;if(c||(c=oQe),r.size!=e.size&&!f)return!1;var g=s.get(r);if(g)return g==e;i|=aQe,s.set(r,e);var y=iQe(c(r),c(e),i,n,o,s);return s.delete(r),y;case gQe:if(z$)return z$.call(r)==z$.call(e)}return!1}Uye.exports=yQe});var Gye=Vi((Mor,jye)=>{function vQe(r,e){for(var t=-1,i=e.length,n=r.length;++t{var xQe=Array.isArray;Hye.exports=xQe});var qye=Vi((Por,Wye)=>{var bQe=Gye(),wQe=Q6();function TQe(r,e,t){var i=e(r);return wQe(r)?i:bQe(i,t(r))}Wye.exports=TQe});var Xye=Vi((Ror,$ye)=>{function SQe(r,e){for(var t=-1,i=r==null?0:r.length,n=0,o=[];++t{function EQe(){return[]}Zye.exports=EQe});var Jye=Vi((Lor,Qye)=>{var CQe=Xye(),MQe=Yye(),IQe=Object.prototype,PQe=IQe.propertyIsEnumerable,Kye=Object.getOwnPropertySymbols,RQe=Kye?function(r){return r==null?[]:(r=Object(r),CQe(Kye(r),function(e){return PQe.call(r,e)}))}:MQe;Qye.exports=RQe});var tve=Vi((Dor,eve)=>{function BQe(r,e){for(var t=-1,i=Array(r);++t{function LQe(r){return r!=null&&typeof r=="object"}rve.exports=LQe});var nve=Vi((For,ive)=>{var DQe=BE(),OQe=FE(),FQe="[object Arguments]";function kQe(r){return OQe(r)&&DQe(r)==FQe}ive.exports=kQe});var lve=Vi((kor,ave)=>{var ove=nve(),NQe=FE(),sve=Object.prototype,zQe=sve.hasOwnProperty,UQe=sve.propertyIsEnumerable,VQe=ove(function(){return arguments}())?ove:function(r){return NQe(r)&&zQe.call(r,"callee")&&!UQe.call(r,"callee")};ave.exports=VQe});var uve=Vi((Nor,cve)=>{function jQe(){return!1}cve.exports=jQe});var U$=Vi((gB,kE)=>{var GQe=__(),HQe=uve(),dve=typeof gB=="object"&&gB&&!gB.nodeType&&gB,hve=dve&&typeof kE=="object"&&kE&&!kE.nodeType&&kE,WQe=hve&&hve.exports===dve,fve=WQe?GQe.Buffer:void 0,qQe=fve?fve.isBuffer:void 0,$Qe=qQe||HQe;kE.exports=$Qe});var mve=Vi((zor,pve)=>{var XQe=9007199254740991,ZQe=/^(?:0|[1-9]\d*)$/;function YQe(r,e){var t=typeof r;return e=e??XQe,!!e&&(t=="number"||t!="symbol"&&ZQe.test(r))&&r>-1&&r%1==0&&r{var KQe=9007199254740991;function QQe(r){return typeof r=="number"&&r>-1&&r%1==0&&r<=KQe}gve.exports=QQe});var _ve=Vi((Vor,Ave)=>{var JQe=BE(),eJe=V$(),tJe=FE(),rJe="[object Arguments]",iJe="[object Array]",nJe="[object Boolean]",oJe="[object Date]",sJe="[object Error]",aJe="[object Function]",lJe="[object Map]",cJe="[object Number]",uJe="[object Object]",hJe="[object RegExp]",fJe="[object Set]",dJe="[object String]",pJe="[object WeakMap]",mJe="[object ArrayBuffer]",gJe="[object DataView]",AJe="[object Float32Array]",_Je="[object Float64Array]",yJe="[object Int8Array]",vJe="[object Int16Array]",xJe="[object Int32Array]",bJe="[object Uint8Array]",wJe="[object Uint8ClampedArray]",TJe="[object Uint16Array]",SJe="[object Uint32Array]",Qc={};Qc[AJe]=Qc[_Je]=Qc[yJe]=Qc[vJe]=Qc[xJe]=Qc[bJe]=Qc[wJe]=Qc[TJe]=Qc[SJe]=!0;Qc[rJe]=Qc[iJe]=Qc[mJe]=Qc[nJe]=Qc[gJe]=Qc[oJe]=Qc[sJe]=Qc[aJe]=Qc[lJe]=Qc[cJe]=Qc[uJe]=Qc[hJe]=Qc[fJe]=Qc[dJe]=Qc[pJe]=!1;function EJe(r){return tJe(r)&&eJe(r.length)&&!!Qc[JQe(r)]}Ave.exports=EJe});var vve=Vi((jor,yve)=>{function CJe(r){return function(e){return r(e)}}yve.exports=CJe});var bve=Vi((AB,NE)=>{var MJe=B$(),xve=typeof AB=="object"&&AB&&!AB.nodeType&&AB,_B=xve&&typeof NE=="object"&&NE&&!NE.nodeType&&NE,IJe=_B&&_B.exports===xve,j$=IJe&&MJe.process,PJe=function(){try{var r=_B&&_B.require&&_B.require("util").types;return r||j$&&j$.binding&&j$.binding("util")}catch{}}();NE.exports=PJe});var G$=Vi((Gor,Sve)=>{var RJe=_ve(),BJe=vve(),wve=bve(),Tve=wve&&wve.isTypedArray,LJe=Tve?BJe(Tve):RJe;Sve.exports=LJe});var Cve=Vi((Hor,Eve)=>{var DJe=tve(),OJe=lve(),FJe=Q6(),kJe=U$(),NJe=mve(),zJe=G$(),UJe=Object.prototype,VJe=UJe.hasOwnProperty;function jJe(r,e){var t=FJe(r),i=!t&&OJe(r),n=!t&&!i&&kJe(r),o=!t&&!i&&!n&&zJe(r),s=t||i||n||o,c=s?DJe(r.length,String):[],f=c.length;for(var g in r)(e||VJe.call(r,g))&&!(s&&(g=="length"||n&&(g=="offset"||g=="parent")||o&&(g=="buffer"||g=="byteLength"||g=="byteOffset")||NJe(g,f)))&&c.push(g);return c}Eve.exports=jJe});var Ive=Vi((Wor,Mve)=>{var GJe=Object.prototype;function HJe(r){var e=r&&r.constructor,t=typeof e=="function"&&e.prototype||GJe;return r===t}Mve.exports=HJe});var H$=Vi((qor,Pve)=>{function WJe(r,e){return function(t){return r(e(t))}}Pve.exports=WJe});var Bve=Vi(($or,Rve)=>{var qJe=H$(),$Je=qJe(Object.keys,Object);Rve.exports=$Je});var Dve=Vi((Xor,Lve)=>{var XJe=Ive(),ZJe=Bve(),YJe=Object.prototype,KJe=YJe.hasOwnProperty;function QJe(r){if(!XJe(r))return ZJe(r);var e=[];for(var t in Object(r))KJe.call(r,t)&&t!="constructor"&&e.push(t);return e}Lve.exports=QJe});var Fve=Vi((Zor,Ove)=>{var JJe=D$(),eet=V$();function tet(r){return r!=null&&eet(r.length)&&!JJe(r)}Ove.exports=tet});var Nve=Vi((Yor,kve)=>{var ret=Cve(),iet=Dve(),net=Fve();function oet(r){return net(r)?ret(r):iet(r)}kve.exports=oet});var Uve=Vi((Kor,zve)=>{var set=qye(),aet=Jye(),cet=Nve();function uet(r){return set(r,cet,aet)}zve.exports=uet});var Gve=Vi((Qor,jve)=>{var Vve=Uve(),het=1,fet=Object.prototype,det=fet.hasOwnProperty;function pet(r,e,t,i,n,o){var s=t&het,c=Vve(r),f=c.length,g=Vve(e),y=g.length;if(f!=y&&!s)return!1;for(var M=f;M--;){var F=c[M];if(!(s?F in e:det.call(e,F)))return!1}var k=o.get(r),U=o.get(e);if(k&&U)return k==e&&U==r;var K=!0;o.set(r,e),o.set(e,r);for(var ee=s;++M{var met=Pw(),get=__(),Aet=met(get,"DataView");Hve.exports=Aet});var $ve=Vi((esr,qve)=>{var _et=Pw(),yet=__(),vet=_et(yet,"Promise");qve.exports=vet});var Zve=Vi((tsr,Xve)=>{var xet=Pw(),bet=__(),wet=xet(bet,"Set");Xve.exports=wet});var Kve=Vi((rsr,Yve)=>{var Tet=Pw(),Eet=__(),Cet=Tet(Eet,"WeakMap");Yve.exports=Cet});var oxe=Vi((isr,nxe)=>{var W$=Wve(),q$=Y6(),$$=$ve(),X$=Zve(),Z$=Kve(),ixe=BE(),zE=F$(),Qve="[object Map]",Met="[object Object]",Jve="[object Promise]",exe="[object Set]",txe="[object WeakMap]",rxe="[object DataView]",Iet=zE(W$),Pet=zE(q$),Ret=zE($$),Bet=zE(X$),Let=zE(Z$),Rw=ixe;(W$&&Rw(new W$(new ArrayBuffer(1)))!=rxe||q$&&Rw(new q$)!=Qve||$$&&Rw($$.resolve())!=Jve||X$&&Rw(new X$)!=exe||Z$&&Rw(new Z$)!=txe)&&(Rw=function(r){var e=ixe(r),t=e==Met?r.constructor:void 0,i=t?zE(t):"";if(i)switch(i){case Iet:return rxe;case Pet:return Qve;case Ret:return Jve;case Bet:return exe;case Let:return txe}return e});nxe.exports=Rw});var dxe=Vi((nsr,fxe)=>{var Y$=yye(),Det=N$(),Oet=Vye(),Fet=Gve(),sxe=oxe(),axe=Q6(),lxe=U$(),ket=G$(),Net=1,cxe="[object Arguments]",uxe="[object Array]",J6="[object Object]",zet=Object.prototype,hxe=zet.hasOwnProperty;function Uet(r,e,t,i,n,o){var s=axe(r),c=axe(e),f=s?uxe:sxe(r),g=c?uxe:sxe(e);f=f==cxe?J6:f,g=g==cxe?J6:g;var y=f==J6,M=g==J6,F=f==g;if(F&&lxe(r)){if(!lxe(e))return!1;s=!0,y=!1}if(F&&!y)return o||(o=new Y$),s||ket(r)?Det(r,e,t,i,n,o):Oet(r,e,f,t,i,n,o);if(!(t&Net)){var k=y&&hxe.call(r,"__wrapped__"),U=M&&hxe.call(e,"__wrapped__");if(k||U){var K=k?r.value():r,ee=U?e.value():e;return o||(o=new Y$),n(K,ee,t,i,o)}}return F?(o||(o=new Y$),Fet(r,e,t,i,n,o)):!1}fxe.exports=Uet});var Axe=Vi((osr,gxe)=>{var Vet=dxe(),pxe=FE();function mxe(r,e,t,i,n){return r===e?!0:r==null||e==null||!pxe(r)&&!pxe(e)?r!==r&&e!==e:Vet(r,e,t,i,mxe,n)}gxe.exports=mxe});var yxe=Vi((ssr,_xe)=>{var jet=Axe();function Get(r,e){return jet(r,e)}_xe.exports=Get});var xxe=Vi((asr,vxe)=>{var Het=H$(),Wet=Het(Object.getPrototypeOf,Object);vxe.exports=Wet});var Txe=Vi((lsr,wxe)=>{var qet=BE(),$et=xxe(),Xet=FE(),Zet="[object Object]",Yet=Function.prototype,Ket=Object.prototype,bxe=Yet.toString,Qet=Ket.hasOwnProperty,Jet=bxe.call(Object);function ett(r){if(!Xet(r)||qet(r)!=Zet)return!1;var e=$et(r);if(e===null)return!0;var t=Qet.call(e,"constructor")&&e.constructor;return typeof t=="function"&&t instanceof t&&bxe.call(t)==Jet}wxe.exports=ett});var bB,rX,wB,nU,iX,Oxe,v_,Hy,Fxe,nX,kxe,Nxe,oX,sX,aX,zxe,Uxe,oU,lX,Vxe,Jc=Nt(()=>{bB="1.13.7",rX=typeof self=="object"&&self.self===self&&self||typeof global=="object"&&global.global===global&&global||Function("return this")()||{},wB=Array.prototype,nU=Object.prototype,iX=typeof Symbol<"u"?Symbol.prototype:null,Oxe=wB.push,v_=wB.slice,Hy=nU.toString,Fxe=nU.hasOwnProperty,nX=typeof ArrayBuffer<"u",kxe=typeof DataView<"u",Nxe=Array.isArray,oX=Object.keys,sX=Object.create,aX=nX&&ArrayBuffer.isView,zxe=isNaN,Uxe=isFinite,oU=!{toString:null}.propertyIsEnumerable("toString"),lX=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],Vxe=Math.pow(2,53)-1});function Yl(r,e){return e=e==null?r.length-1:+e,function(){for(var t=Math.max(arguments.length-e,0),i=Array(t),n=0;n{});function Ud(r){var e=typeof r;return e==="function"||e==="object"&&!!r}var Sx=Nt(()=>{});function sU(r){return r===null}var jxe=Nt(()=>{});function jE(r){return r===void 0}var cX=Nt(()=>{});function GE(r){return r===!0||r===!1||Hy.call(r)==="[object Boolean]"}var uX=Nt(()=>{Jc()});function aU(r){return!!(r&&r.nodeType===1)}var Gxe=Nt(()=>{});function Xa(r){var e="[object "+r+"]";return function(t){return Hy.call(t)===e}}var dd=Nt(()=>{Jc()});var Bw,lU=Nt(()=>{dd();Bw=Xa("String")});var TB,hX=Nt(()=>{dd();TB=Xa("Number")});var fX,Hxe=Nt(()=>{dd();fX=Xa("Date")});var dX,Wxe=Nt(()=>{dd();dX=Xa("RegExp")});var pX,qxe=Nt(()=>{dd();pX=Xa("Error")});var SB,mX=Nt(()=>{dd();SB=Xa("Symbol")});var EB,gX=Nt(()=>{dd();EB=Xa("ArrayBuffer")});var $xe,ntt,dc,Wm=Nt(()=>{dd();Jc();$xe=Xa("Function"),ntt=rX.document&&rX.document.childNodes;typeof/./!="function"&&typeof Int8Array!="object"&&typeof ntt!="function"&&($xe=function(r){return typeof r=="function"||!1});dc=$xe});var AX,Xxe=Nt(()=>{dd();AX=Xa("Object")});var cU,HE,WE=Nt(()=>{Jc();Xxe();cU=kxe&&(!/\[native code\]/.test(String(DataView))||AX(new DataView(new ArrayBuffer(8)))),HE=typeof Map<"u"&&AX(new Map)});function stt(r){return r!=null&&dc(r.getInt8)&&EB(r.buffer)}var ott,Ex,uU=Nt(()=>{dd();Wm();gX();WE();ott=Xa("DataView");Ex=cU?stt:ott});var yp,Cx=Nt(()=>{Jc();dd();yp=Nxe||Xa("Array")});function pd(r,e){return r!=null&&Fxe.call(r,e)}var Wy=Nt(()=>{Jc()});var _X,Lw,hU=Nt(()=>{dd();Wy();_X=Xa("Arguments");(function(){_X(arguments)||(_X=function(r){return pd(r,"callee")})})();Lw=_X});function fU(r){return!SB(r)&&Uxe(r)&&!isNaN(parseFloat(r))}var Zxe=Nt(()=>{Jc();mX()});function qE(r){return TB(r)&&zxe(r)}var yX=Nt(()=>{Jc();hX()});function $E(r){return function(){return r}}var vX=Nt(()=>{});function CB(r){return function(e){var t=r(e);return typeof t=="number"&&t>=0&&t<=Vxe}}var xX=Nt(()=>{Jc()});function MB(r){return function(e){return e?.[r]}}var bX=Nt(()=>{});var Dw,dU=Nt(()=>{bX();Dw=MB("byteLength")});var Yxe,Kxe=Nt(()=>{xX();dU();Yxe=CB(Dw)});function ltt(r){return aX?aX(r)&&!Ex(r):Yxe(r)&&att.test(Hy.call(r))}var att,IB,wX=Nt(()=>{Jc();uU();vX();Kxe();att=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;IB=nX?ltt:$E(!1)});var kc,qm=Nt(()=>{bX();kc=MB("length")});function ctt(r){for(var e={},t=r.length,i=0;i{Jc();Wm();Wy()});function ba(r){if(!Ud(r))return[];if(oX)return oX(r);var e=[];for(var t in r)pd(r,t)&&e.push(t);return oU&&PB(r,e),e}var Uf=Nt(()=>{Sx();Jc();Wy();TX()});function pU(r){if(r==null)return!0;var e=kc(r);return typeof e=="number"&&(yp(r)||Bw(r)||Lw(r))?e===0:kc(ba(r))===0}var Qxe=Nt(()=>{qm();Cx();lU();hU();Uf()});function XE(r,e){var t=ba(e),i=t.length;if(r==null)return!i;for(var n=Object(r),o=0;o{Uf()});function Ns(r){if(r instanceof Ns)return r;if(!(this instanceof Ns))return new Ns(r);this._wrapped=r}var cm=Nt(()=>{Jc();Ns.VERSION=bB;Ns.prototype.value=function(){return this._wrapped};Ns.prototype.valueOf=Ns.prototype.toJSON=Ns.prototype.value;Ns.prototype.toString=function(){return String(this._wrapped)}});function mU(r){return new Uint8Array(r.buffer||r,r.byteOffset||0,Dw(r))}var Jxe=Nt(()=>{dU()});function EX(r,e,t,i){if(r===e)return r!==0||1/r===1/e;if(r==null||e==null)return!1;if(r!==r)return e!==e;var n=typeof r;return n!=="function"&&n!=="object"&&typeof e!="object"?!1:tbe(r,e,t,i)}function tbe(r,e,t,i){r instanceof Ns&&(r=r._wrapped),e instanceof Ns&&(e=e._wrapped);var n=Hy.call(r);if(n!==Hy.call(e))return!1;if(cU&&n=="[object Object]"&&Ex(r)){if(!Ex(e))return!1;n=ebe}switch(n){case"[object RegExp]":case"[object String]":return""+r==""+e;case"[object Number]":return+r!=+r?+e!=+e:+r==0?1/+r===1/e:+r==+e;case"[object Date]":case"[object Boolean]":return+r==+e;case"[object Symbol]":return iX.valueOf.call(r)===iX.valueOf.call(e);case"[object ArrayBuffer]":case ebe:return tbe(mU(r),mU(e),t,i)}var o=n==="[object Array]";if(!o&&IB(r)){var s=Dw(r);if(s!==Dw(e))return!1;if(r.buffer===e.buffer&&r.byteOffset===e.byteOffset)return!0;o=!0}if(!o){if(typeof r!="object"||typeof e!="object")return!1;var c=r.constructor,f=e.constructor;if(c!==f&&!(dc(c)&&c instanceof c&&dc(f)&&f instanceof f)&&"constructor"in r&&"constructor"in e)return!1}t=t||[],i=i||[];for(var g=t.length;g--;)if(t[g]===r)return i[g]===e;if(t.push(r),i.push(e),o){if(g=r.length,g!==e.length)return!1;for(;g--;)if(!EX(r[g],e[g],t,i))return!1}else{var y=ba(r),M;if(g=y.length,ba(e).length!==g)return!1;for(;g--;)if(M=y[g],!(pd(e,M)&&EX(r[M],e[M],t,i)))return!1}return t.pop(),i.pop(),!0}function gU(r,e){return EX(r,e)}var ebe,rbe=Nt(()=>{cm();Jc();dU();wX();Wm();WE();uU();Uf();Wy();Jxe();ebe="[object DataView]"});function Zg(r){if(!Ud(r))return[];var e=[];for(var t in r)e.push(t);return oU&&PB(r,e),e}var ZE=Nt(()=>{Sx();Jc();TX()});function YE(r){var e=kc(r);return function(t){if(t==null)return!1;var i=Zg(t);if(kc(i))return!1;for(var n=0;n{qm();Wm();ZE();CX="forEach",ibe="has",MX=["clear","delete"],nbe=["get",ibe,"set"],obe=MX.concat(CX,nbe),IX=MX.concat(nbe),sbe=["add"].concat(MX,CX,ibe)});var PX,abe=Nt(()=>{dd();WE();AU();PX=HE?YE(obe):Xa("Map")});var RX,lbe=Nt(()=>{dd();WE();AU();RX=HE?YE(IX):Xa("WeakMap")});var BX,cbe=Nt(()=>{dd();WE();AU();BX=HE?YE(sbe):Xa("Set")});var LX,ube=Nt(()=>{dd();LX=Xa("WeakSet")});function um(r){for(var e=ba(r),t=e.length,i=Array(t),n=0;n{Uf()});function _U(r){for(var e=ba(r),t=e.length,i=Array(t),n=0;n{Uf()});function KE(r){for(var e={},t=ba(r),i=0,n=t.length;i{Uf()});function Fw(r){var e=[];for(var t in r)dc(r[t])&&e.push(t);return e.sort()}var OX=Nt(()=>{Wm()});function kw(r,e){return function(t){var i=arguments.length;if(e&&(t=Object(t)),i<2||t==null)return t;for(var n=1;n{});var RB,FX=Nt(()=>{yU();ZE();RB=kw(Zg)});var Mx,vU=Nt(()=>{yU();Uf();Mx=kw(ba)});var BB,kX=Nt(()=>{yU();ZE();BB=kw(Zg,!0)});function utt(){return function(){}}function LB(r){if(!Ud(r))return{};if(sX)return sX(r);var e=utt();e.prototype=r;var t=new e;return e.prototype=null,t}var NX=Nt(()=>{Sx();Jc()});function xU(r,e){var t=LB(r);return e&&Mx(t,e),t}var fbe=Nt(()=>{NX();vU()});function bU(r){return Ud(r)?yp(r)?r.slice():RB({},r):r}var dbe=Nt(()=>{Sx();Cx();FX()});function wU(r,e){return e(r),r}var pbe=Nt(()=>{});function DB(r){return yp(r)?r:[r]}var zX=Nt(()=>{cm();Cx();Ns.toPath=DB});function q0(r){return Ns.toPath(r)}var QE=Nt(()=>{cm();zX()});function Nw(r,e){for(var t=e.length,i=0;i{});function JE(r,e,t){var i=Nw(r,q0(e));return jE(i)?t:i}var UX=Nt(()=>{QE();TU();cX()});function SU(r,e){e=q0(e);for(var t=e.length,i=0;i{Wy();QE()});function Ix(r){return r}var EU=Nt(()=>{});function $0(r){return r=Mx({},r),function(e){return XE(e,r)}}var OB=Nt(()=>{vU();SX()});function Px(r){return r=q0(r),function(e){return Nw(e,r)}}var CU=Nt(()=>{TU();QE()});function X0(r,e,t){if(e===void 0)return r;switch(t??3){case 1:return function(i){return r.call(e,i)};case 3:return function(i,n,o){return r.call(e,i,n,o)};case 4:return function(i,n,o,s){return r.call(e,i,n,o,s)}}return function(){return r.apply(e,arguments)}}var eC=Nt(()=>{});function FB(r,e,t){return r==null?Ix:dc(r)?X0(r,e,t):Ud(r)&&!yp(r)?$0(r):Px(r)}var VX=Nt(()=>{EU();Wm();Sx();Cx();OB();CU();eC()});function zw(r,e){return FB(r,e,1/0)}var jX=Nt(()=>{cm();VX();Ns.iteratee=zw});function Kl(r,e,t){return Ns.iteratee!==zw?Ns.iteratee(r,e):FB(r,e,t)}var vp=Nt(()=>{cm();VX();jX()});function MU(r,e,t){e=Kl(e,t);for(var i=ba(r),n=i.length,o={},s=0;s{vp();Uf()});function tC(){}var GX=Nt(()=>{});function IU(r){return r==null?tC:function(e){return JE(r,e)}}var Abe=Nt(()=>{GX();UX()});function PU(r,e,t){var i=Array(Math.max(0,r));e=X0(e,t,1);for(var n=0;n{eC()});function Uw(r,e){return e==null&&(e=r,r=0),r+Math.floor(Math.random()*(e-r+1))}var HX=Nt(()=>{});var qy,RU=Nt(()=>{qy=Date.now||function(){return new Date().getTime()}});function kB(r){var e=function(o){return r[o]},t="(?:"+ba(r).join("|")+")",i=RegExp(t),n=RegExp(t,"g");return function(o){return o=o==null?"":""+o,i.test(o)?o.replace(n,e):o}}var WX=Nt(()=>{Uf()});var BU,qX=Nt(()=>{BU={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"}});var $X,ybe=Nt(()=>{WX();qX();$X=kB(BU)});var vbe,xbe=Nt(()=>{DX();qX();vbe=KE(BU)});var XX,bbe=Nt(()=>{WX();xbe();XX=kB(vbe)});var ZX,YX=Nt(()=>{cm();ZX=Ns.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g}});function dtt(r){return"\\"+htt[r]}function LU(r,e,t){!e&&t&&(e=t),e=BB({},e,Ns.templateSettings);var i=RegExp([(e.escape||KX).source,(e.interpolate||KX).source,(e.evaluate||KX).source].join("|")+"|$","g"),n=0,o="__p+='";r.replace(i,function(g,y,M,F,k){return o+=r.slice(n,k).replace(ftt,dtt),n=k+g.length,y?o+=`'+ +((__t=(`+y+`))==null?'':_.escape(__t))+ +'`:M?o+=`'+ +((__t=(`+M+`))==null?'':__t)+ +'`:F&&(o+=`'; +`+F+` +__p+='`),g}),o+=`'; +`;var s=e.variable;if(s){if(!ptt.test(s))throw new Error("variable is not a bare identifier: "+s)}else o=`with(obj||{}){ +`+o+`} +`,s="obj";o=`var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');}; +`+o+`return __p; +`;var c;try{c=new Function(s,"_",o)}catch(g){throw g.source=o,g}var f=function(g){return c.call(this,g,Ns)};return f.source="function("+s+`){ +`+o+"}",f}var KX,htt,ftt,ptt,wbe=Nt(()=>{kX();cm();YX();KX=/(.)^/,htt={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},ftt=/\\|'|\r|\n|\u2028|\u2029/g;ptt=/^\s*(\w|\$)+\s*$/});function DU(r,e,t){e=q0(e);var i=e.length;if(!i)return dc(t)?t.call(r):t;for(var n=0;n{Wm();QE()});function OU(r){var e=++mtt+"";return r?r+e:e}var mtt,Sbe=Nt(()=>{mtt=0});function FU(r){var e=Ns(r);return e._chain=!0,e}var Ebe=Nt(()=>{cm()});function NB(r,e,t,i,n){if(!(i instanceof e))return r.apply(t,n);var o=LB(r.prototype),s=r.apply(o,n);return Ud(s)?s:o}var QX=Nt(()=>{NX();Sx()});var JX,$y,zB=Nt(()=>{lm();QX();cm();JX=Yl(function(r,e){var t=JX.placeholder,i=function(){for(var n=0,o=e.length,s=Array(o),c=0;c{lm();Wm();QX();UB=Yl(function(r,e,t){if(!dc(r))throw new TypeError("Bind must be called on a function");var i=Yl(function(n){return NB(r,i,e,this,t.concat(n))});return i})});var Nc,hm=Nt(()=>{xX();qm();Nc=CB(kc)});function fm(r,e,t,i){if(i=i||[],!e&&e!==0)e=1/0;else if(e<=0)return i.concat(r);for(var n=i.length,o=0,s=kc(r);o1)fm(c,e-1,t,i),n=i.length;else for(var f=0,g=c.length;f{qm();hm();Cx();hU()});var tZ,Cbe=Nt(()=>{lm();Vw();eZ();tZ=Yl(function(r,e){e=fm(e,!1,!1);var t=e.length;if(t<1)throw new Error("bindAll must be passed function names");for(;t--;){var i=e[t];r[i]=UB(r[i],r)}return r})});function kU(r,e){var t=function(i){var n=t.cache,o=""+(e?e.apply(this,arguments):i);return pd(n,o)||(n[o]=r.apply(this,arguments)),n[o]};return t.cache={},t}var Mbe=Nt(()=>{Wy()});var VB,rZ=Nt(()=>{lm();VB=Yl(function(r,e,t){return setTimeout(function(){return r.apply(null,t)},e)})});var iZ,Ibe=Nt(()=>{zB();rZ();cm();iZ=$y(VB,Ns,1)});function NU(r,e,t){var i,n,o,s,c=0;t||(t={});var f=function(){c=t.leading===!1?0:qy(),i=null,s=r.apply(n,o),i||(n=o=null)},g=function(){var y=qy();!c&&t.leading===!1&&(c=y);var M=e-(y-c);return n=this,o=arguments,M<=0||M>e?(i&&(clearTimeout(i),i=null),c=y,s=r.apply(n,o),i||(n=o=null)):!i&&t.trailing!==!1&&(i=setTimeout(f,M)),s};return g.cancel=function(){clearTimeout(i),c=0,i=n=o=null},g}var Pbe=Nt(()=>{RU()});function zU(r,e,t){var i,n,o,s,c,f=function(){var y=qy()-n;e>y?i=setTimeout(f,e-y):(i=null,t||(s=r.apply(c,o)),i||(o=c=null))},g=Yl(function(y){return c=this,o=y,n=qy(),i||(i=setTimeout(f,e),t&&(s=r.apply(c,o))),s});return g.cancel=function(){clearTimeout(i),i=o=c=null},g}var Rbe=Nt(()=>{lm();RU()});function UU(r,e){return $y(e,r)}var Bbe=Nt(()=>{zB()});function Rx(r){return function(){return!r.apply(this,arguments)}}var VU=Nt(()=>{});function jU(){var r=arguments,e=r.length-1;return function(){for(var t=e,i=r[e].apply(this,arguments);t--;)i=r[t].call(this,i);return i}}var Lbe=Nt(()=>{});function GU(r,e){return function(){if(--r<1)return e.apply(this,arguments)}}var Dbe=Nt(()=>{});function rC(r,e){var t;return function(){return--r>0&&(t=e.apply(this,arguments)),r<=1&&(e=null),t}}var nZ=Nt(()=>{});var oZ,Obe=Nt(()=>{zB();nZ();oZ=$y(rC,2)});function iC(r,e,t){e=Kl(e,t);for(var i=ba(r),n,o=0,s=i.length;o{vp();Uf()});function jB(r){return function(e,t,i){t=Kl(t,i);for(var n=kc(e),o=r>0?0:n-1;o>=0&&o{vp();qm()});var jw,HU=Nt(()=>{aZ();jw=jB(1)});var GB,lZ=Nt(()=>{aZ();GB=jB(-1)});function nC(r,e,t,i){t=Kl(t,i,1);for(var n=t(e),o=0,s=kc(r);o{vp();qm()});function HB(r,e,t){return function(i,n,o){var s=0,c=kc(i);if(typeof o=="number")r>0?s=o>=0?o:Math.max(o+c,s):c=o>=0?Math.min(o+1,c):o+c+1;else if(t&&o&&c)return o=t(i,n),i[o]===n?o:-1;if(n!==n)return o=e(v_.call(i,s,c),qE),o>=0?o+s:-1;for(o=r>0?s:c-1;o>=0&&o{qm();Jc();yX()});var WB,hZ=Nt(()=>{cZ();HU();uZ();WB=HB(1,jw,nC)});var fZ,Fbe=Nt(()=>{lZ();uZ();fZ=HB(-1,GB)});function Gw(r,e,t){var i=Nc(r)?jw:iC,n=i(r,e,t);if(n!==void 0&&n!==-1)return r[n]}var dZ=Nt(()=>{hm();HU();sZ()});function WU(r,e){return Gw(r,$0(e))}var kbe=Nt(()=>{dZ();OB()});function Vf(r,e,t){e=X0(e,t);var i,n;if(Nc(r))for(i=0,n=r.length;i{eC();hm();Uf()});function xp(r,e,t){e=Kl(e,t);for(var i=!Nc(r)&&ba(r),n=(i||r).length,o=Array(n),s=0;s{vp();hm();Uf()});function qB(r){var e=function(t,i,n,o){var s=!Nc(t)&&ba(t),c=(s||t).length,f=r>0?0:c-1;for(o||(n=t[s?s[f]:f],f+=r);f>=0&&f=3;return e(t,X0(i,o,4),n,s)}}var pZ=Nt(()=>{hm();Uf();eC()});var $B,Nbe=Nt(()=>{pZ();$B=qB(1)});var qU,zbe=Nt(()=>{pZ();qU=qB(-1)});function $m(r,e,t){var i=[];return e=Kl(e,t),Vf(r,function(n,o,s){e(n,o,s)&&i.push(n)}),i}var oC=Nt(()=>{vp();Bx()});function $U(r,e,t){return $m(r,Rx(Kl(e)),t)}var Ube=Nt(()=>{oC();VU();vp()});function XB(r,e,t){e=Kl(e,t);for(var i=!Nc(r)&&ba(r),n=(i||r).length,o=0;o{vp();hm();Uf()});function ZB(r,e,t){e=Kl(e,t);for(var i=!Nc(r)&&ba(r),n=(i||r).length,o=0;o{vp();hm();Uf()});function Vd(r,e,t,i){return Nc(r)||(r=um(r)),(typeof t!="number"||i)&&(t=0),WB(r,e,t)>=0}var sC=Nt(()=>{hm();Ow();hZ()});var mZ,Gbe=Nt(()=>{lm();Wm();Hw();TU();QE();mZ=Yl(function(r,e,t){var i,n;return dc(e)?n=e:(e=q0(e),i=e.slice(0,-1),e=e[e.length-1]),xp(r,function(o){var s=n;if(!s){if(i&&i.length&&(o=Nw(o,i)),o==null)return;s=o[e]}return s==null?s:s.apply(o,t)})})});function Lx(r,e){return xp(r,Px(e))}var XU=Nt(()=>{Hw();CU()});function ZU(r,e){return $m(r,$0(e))}var Hbe=Nt(()=>{oC();OB()});function aC(r,e,t){var i=-1/0,n=-1/0,o,s;if(e==null||typeof e=="number"&&typeof r[0]!="object"&&r!=null){r=Nc(r)?r:um(r);for(var c=0,f=r.length;ci&&(i=o)}else e=Kl(e,t),Vf(r,function(g,y,M){s=e(g,y,M),(s>n||s===-1/0&&i===-1/0)&&(i=g,n=s)});return i}var gZ=Nt(()=>{hm();Ow();vp();Bx()});function YU(r,e,t){var i=1/0,n=1/0,o,s;if(e==null||typeof e=="number"&&typeof r[0]!="object"&&r!=null){r=Nc(r)?r:um(r);for(var c=0,f=r.length;c{hm();Ow();vp();Bx()});function lC(r){return r?yp(r)?v_.call(r):Bw(r)?r.match(gtt):Nc(r)?xp(r,Ix):um(r):[]}var gtt,AZ=Nt(()=>{Cx();Jc();lU();hm();Hw();EU();Ow();gtt=/[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g});function cC(r,e,t){if(e==null||t)return Nc(r)||(r=um(r)),r[Uw(r.length-1)];var i=lC(r),n=kc(i);e=Math.max(Math.min(e,n),0);for(var o=n-1,s=0;s{hm();Ow();qm();HX();AZ()});function KU(r){return cC(r,1/0)}var qbe=Nt(()=>{_Z()});function QU(r,e,t){var i=0;return e=Kl(e,t),Lx(xp(r,function(n,o,s){return{value:n,index:i++,criteria:e(n,o,s)}}).sort(function(n,o){var s=n.criteria,c=o.criteria;if(s!==c){if(s>c||s===void 0)return 1;if(s{vp();XU();Hw()});function Xy(r,e){return function(t,i,n){var o=e?[[],[]]:{};return i=Kl(i,n),Vf(t,function(s,c){var f=i(s,c,t);r(o,s,f)}),o}}var YB=Nt(()=>{vp();Bx()});var yZ,Xbe=Nt(()=>{YB();Wy();yZ=Xy(function(r,e,t){pd(r,t)?r[t].push(e):r[t]=[e]})});var vZ,Zbe=Nt(()=>{YB();vZ=Xy(function(r,e,t){r[t]=e})});var xZ,Ybe=Nt(()=>{YB();Wy();xZ=Xy(function(r,e,t){pd(r,t)?r[t]++:r[t]=1})});var bZ,Kbe=Nt(()=>{YB();bZ=Xy(function(r,e,t){r[t?0:1].push(e)},!0)});function JU(r){return r==null?0:Nc(r)?r.length:ba(r).length}var Qbe=Nt(()=>{hm();Uf()});function wZ(r,e,t){return e in t}var Jbe=Nt(()=>{});var KB,TZ=Nt(()=>{lm();Wm();eC();ZE();Jbe();Vw();KB=Yl(function(r,e){var t={},i=e[0];if(r==null)return t;dc(i)?(e.length>1&&(i=X0(i,e[1])),e=Zg(r)):(i=wZ,e=fm(e,!1,!1),r=Object(r));for(var n=0,o=e.length;n{lm();Wm();VU();Hw();Vw();sC();TZ();SZ=Yl(function(r,e){var t=e[0],i;return dc(t)?(t=Rx(t),e.length>1&&(i=e[1])):(e=xp(fm(e,!1,!1),String),t=function(n,o){return!Vd(e,o)}),KB(r,t,i)})});function uC(r,e,t){return v_.call(r,0,Math.max(0,r.length-(e==null||t?1:e)))}var EZ=Nt(()=>{Jc()});function hC(r,e,t){return r==null||r.length<1?e==null||t?void 0:[]:e==null||t?r[0]:uC(r,r.length-e)}var t1e=Nt(()=>{EZ()});function Dx(r,e,t){return v_.call(r,e==null||t?1:e)}var CZ=Nt(()=>{Jc()});function e5(r,e,t){return r==null||r.length<1?e==null||t?void 0:[]:e==null||t?r[r.length-1]:Dx(r,Math.max(0,r.length-e))}var r1e=Nt(()=>{CZ()});function t5(r){return $m(r,Boolean)}var i1e=Nt(()=>{oC()});function r5(r,e){return fm(r,e,!1)}var n1e=Nt(()=>{Vw()});var QB,MZ=Nt(()=>{lm();Vw();oC();sC();QB=Yl(function(r,e){return e=fm(e,!0,!0),$m(r,function(t){return!Vd(e,t)})})});var IZ,o1e=Nt(()=>{lm();MZ();IZ=Yl(function(r,e){return QB(r,e)})});function Ww(r,e,t,i){GE(e)||(i=t,t=e,e=!1),t!=null&&(t=Kl(t,i));for(var n=[],o=[],s=0,c=kc(r);s{uX();vp();qm();sC()});var RZ,s1e=Nt(()=>{lm();PZ();Vw();RZ=Yl(function(r){return Ww(fm(r,!0,!0))})});function i5(r){for(var e=[],t=arguments.length,i=0,n=kc(r);i{qm();sC()});function qw(r){for(var e=r&&aC(r,kc).length||0,t=Array(e),i=0;i{gZ();qm();XU()});var LZ,l1e=Nt(()=>{lm();BZ();LZ=Yl(qw)});function n5(r,e){for(var t={},i=0,n=kc(r);i{qm()});function o5(r,e,t){e==null&&(e=r||0,r=0),t||(t=e{});function s5(r,e){if(e==null||e<1)return[];for(var t=[],i=0,n=r.length;i{Jc()});function fC(r,e){return r._chain?Ns(e).chain():e}var DZ=Nt(()=>{cm()});function dC(r){return Vf(Fw(r),function(e){var t=Ns[e]=r[e];Ns.prototype[e]=function(){var i=[this._wrapped];return Oxe.apply(i,arguments),fC(this,t.apply(Ns,i))}}),Ns}var f1e=Nt(()=>{cm();Bx();OX();Jc();DZ()});var d1e,p1e=Nt(()=>{cm();Bx();Jc();DZ();Vf(["pop","push","reverse","shift","sort","splice","unshift"],function(r){var e=wB[r];Ns.prototype[r]=function(){var t=this._wrapped;return t!=null&&(e.apply(t,arguments),(r==="shift"||r==="splice")&&t.length===0&&delete t[0]),fC(this,t)}});Vf(["concat","join","slice"],function(r){var e=wB[r];Ns.prototype[r]=function(){var t=this._wrapped;return t!=null&&(t=e.apply(t,arguments)),fC(this,t)}});d1e=Ns});var OZ={};Wa(OZ,{VERSION:()=>bB,after:()=>GU,all:()=>XB,allKeys:()=>Zg,any:()=>ZB,assign:()=>Mx,before:()=>rC,bind:()=>UB,bindAll:()=>tZ,chain:()=>FU,chunk:()=>s5,clone:()=>bU,collect:()=>xp,compact:()=>t5,compose:()=>jU,constant:()=>$E,contains:()=>Vd,countBy:()=>xZ,create:()=>xU,debounce:()=>zU,default:()=>d1e,defaults:()=>BB,defer:()=>iZ,delay:()=>VB,detect:()=>Gw,difference:()=>QB,drop:()=>Dx,each:()=>Vf,escape:()=>$X,every:()=>XB,extend:()=>RB,extendOwn:()=>Mx,filter:()=>$m,find:()=>Gw,findIndex:()=>jw,findKey:()=>iC,findLastIndex:()=>GB,findWhere:()=>WU,first:()=>hC,flatten:()=>r5,foldl:()=>$B,foldr:()=>qU,forEach:()=>Vf,functions:()=>Fw,get:()=>JE,groupBy:()=>yZ,has:()=>SU,head:()=>hC,identity:()=>Ix,include:()=>Vd,includes:()=>Vd,indexBy:()=>vZ,indexOf:()=>WB,initial:()=>uC,inject:()=>$B,intersection:()=>i5,invert:()=>KE,invoke:()=>mZ,isArguments:()=>Lw,isArray:()=>yp,isArrayBuffer:()=>EB,isBoolean:()=>GE,isDataView:()=>Ex,isDate:()=>fX,isElement:()=>aU,isEmpty:()=>pU,isEqual:()=>gU,isError:()=>pX,isFinite:()=>fU,isFunction:()=>dc,isMap:()=>PX,isMatch:()=>XE,isNaN:()=>qE,isNull:()=>sU,isNumber:()=>TB,isObject:()=>Ud,isRegExp:()=>dX,isSet:()=>BX,isString:()=>Bw,isSymbol:()=>SB,isTypedArray:()=>IB,isUndefined:()=>jE,isWeakMap:()=>RX,isWeakSet:()=>LX,iteratee:()=>zw,keys:()=>ba,last:()=>e5,lastIndexOf:()=>fZ,map:()=>xp,mapObject:()=>MU,matcher:()=>$0,matches:()=>$0,max:()=>aC,memoize:()=>kU,methods:()=>Fw,min:()=>YU,mixin:()=>dC,negate:()=>Rx,noop:()=>tC,now:()=>qy,object:()=>n5,omit:()=>SZ,once:()=>oZ,pairs:()=>_U,partial:()=>$y,partition:()=>bZ,pick:()=>KB,pluck:()=>Lx,property:()=>Px,propertyOf:()=>IU,random:()=>Uw,range:()=>o5,reduce:()=>$B,reduceRight:()=>qU,reject:()=>$U,rest:()=>Dx,restArguments:()=>Yl,result:()=>DU,sample:()=>cC,select:()=>$m,shuffle:()=>KU,size:()=>JU,some:()=>ZB,sortBy:()=>QU,sortedIndex:()=>nC,tail:()=>Dx,take:()=>hC,tap:()=>wU,template:()=>LU,templateSettings:()=>ZX,throttle:()=>NU,times:()=>PU,toArray:()=>lC,toPath:()=>DB,transpose:()=>qw,unescape:()=>XX,union:()=>RZ,uniq:()=>Ww,unique:()=>Ww,uniqueId:()=>OU,unzip:()=>qw,values:()=>um,where:()=>ZU,without:()=>IZ,wrap:()=>UU,zip:()=>LZ});var a5=Nt(()=>{Jc();lm();Sx();jxe();cX();uX();Gxe();lU();hX();Hxe();Wxe();qxe();mX();gX();uU();Cx();Wm();hU();Zxe();yX();wX();Qxe();SX();rbe();abe();lbe();cbe();ube();Uf();ZE();Ow();hbe();DX();OX();FX();vU();kX();fbe();dbe();pbe();UX();mbe();gbe();EU();vX();GX();zX();CU();Abe();OB();_be();HX();RU();ybe();bbe();YX();wbe();Tbe();Sbe();Ebe();jX();zB();eZ();Cbe();Mbe();rZ();Ibe();Pbe();Rbe();Bbe();VU();Lbe();Dbe();nZ();Obe();sZ();HU();lZ();cZ();hZ();Fbe();dZ();kbe();Bx();Hw();Nbe();zbe();oC();Ube();Vbe();jbe();sC();Gbe();XU();Hbe();gZ();Wbe();qbe();_Z();$be();Xbe();Zbe();Ybe();Kbe();AZ();Qbe();TZ();e1e();t1e();EZ();r1e();CZ();i1e();n1e();o1e();PZ();s1e();a1e();MZ();BZ();l1e();c1e();u1e();h1e();f1e();p1e()});var FZ,m1e,g1e=Nt(()=>{a5();a5();FZ=dC(OZ);FZ._=FZ;m1e=FZ});var A1e={};Wa(A1e,{VERSION:()=>bB,after:()=>GU,all:()=>XB,allKeys:()=>Zg,any:()=>ZB,assign:()=>Mx,before:()=>rC,bind:()=>UB,bindAll:()=>tZ,chain:()=>FU,chunk:()=>s5,clone:()=>bU,collect:()=>xp,compact:()=>t5,compose:()=>jU,constant:()=>$E,contains:()=>Vd,countBy:()=>xZ,create:()=>xU,debounce:()=>zU,default:()=>m1e,defaults:()=>BB,defer:()=>iZ,delay:()=>VB,detect:()=>Gw,difference:()=>QB,drop:()=>Dx,each:()=>Vf,escape:()=>$X,every:()=>XB,extend:()=>RB,extendOwn:()=>Mx,filter:()=>$m,find:()=>Gw,findIndex:()=>jw,findKey:()=>iC,findLastIndex:()=>GB,findWhere:()=>WU,first:()=>hC,flatten:()=>r5,foldl:()=>$B,foldr:()=>qU,forEach:()=>Vf,functions:()=>Fw,get:()=>JE,groupBy:()=>yZ,has:()=>SU,head:()=>hC,identity:()=>Ix,include:()=>Vd,includes:()=>Vd,indexBy:()=>vZ,indexOf:()=>WB,initial:()=>uC,inject:()=>$B,intersection:()=>i5,invert:()=>KE,invoke:()=>mZ,isArguments:()=>Lw,isArray:()=>yp,isArrayBuffer:()=>EB,isBoolean:()=>GE,isDataView:()=>Ex,isDate:()=>fX,isElement:()=>aU,isEmpty:()=>pU,isEqual:()=>gU,isError:()=>pX,isFinite:()=>fU,isFunction:()=>dc,isMap:()=>PX,isMatch:()=>XE,isNaN:()=>qE,isNull:()=>sU,isNumber:()=>TB,isObject:()=>Ud,isRegExp:()=>dX,isSet:()=>BX,isString:()=>Bw,isSymbol:()=>SB,isTypedArray:()=>IB,isUndefined:()=>jE,isWeakMap:()=>RX,isWeakSet:()=>LX,iteratee:()=>zw,keys:()=>ba,last:()=>e5,lastIndexOf:()=>fZ,map:()=>xp,mapObject:()=>MU,matcher:()=>$0,matches:()=>$0,max:()=>aC,memoize:()=>kU,methods:()=>Fw,min:()=>YU,mixin:()=>dC,negate:()=>Rx,noop:()=>tC,now:()=>qy,object:()=>n5,omit:()=>SZ,once:()=>oZ,pairs:()=>_U,partial:()=>$y,partition:()=>bZ,pick:()=>KB,pluck:()=>Lx,property:()=>Px,propertyOf:()=>IU,random:()=>Uw,range:()=>o5,reduce:()=>$B,reduceRight:()=>qU,reject:()=>$U,rest:()=>Dx,restArguments:()=>Yl,result:()=>DU,sample:()=>cC,select:()=>$m,shuffle:()=>KU,size:()=>JU,some:()=>ZB,sortBy:()=>QU,sortedIndex:()=>nC,tail:()=>Dx,take:()=>hC,tap:()=>wU,template:()=>LU,templateSettings:()=>ZX,throttle:()=>NU,times:()=>PU,toArray:()=>lC,toPath:()=>DB,transpose:()=>qw,unescape:()=>XX,union:()=>RZ,uniq:()=>Ww,unique:()=>Ww,uniqueId:()=>OU,unzip:()=>qw,values:()=>um,where:()=>ZU,without:()=>IZ,wrap:()=>UU,zip:()=>LZ});var _1e=Nt(()=>{g1e();a5()});var kZ=Vi((y1e,l5)=>{(function(r,e){"use strict";typeof l5=="object"&&typeof l5.exports=="object"?l5.exports=r.document?e(r,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return e(t)}:e(r)})(typeof window<"u"?window:y1e,function(r,e){"use strict";var t=[],i=Object.getPrototypeOf,n=t.slice,o=t.flat?function(q){return t.flat.call(q)}:function(q){return t.concat.apply([],q)},s=t.push,c=t.indexOf,f={},g=f.toString,y=f.hasOwnProperty,M=y.toString,F=M.call(Object),k={},U=function(oe){return typeof oe=="function"&&typeof oe.nodeType!="number"&&typeof oe.item!="function"},K=function(oe){return oe!=null&&oe===oe.window},ee=r.document,he={type:!0,src:!0,nonce:!0,noModule:!0};function xe(q,oe,pe){pe=pe||ee;var be,Ge,qe=pe.createElement("script");if(qe.text=q,oe)for(be in he)Ge=oe[be]||oe.getAttribute&&oe.getAttribute(be),Ge&&qe.setAttribute(be,Ge);pe.head.appendChild(qe).parentNode.removeChild(qe)}function me(q){return q==null?q+"":typeof q=="object"||typeof q=="function"?f[g.call(q)]||"object":typeof q}var Ce="3.7.1",Oe=/HTML$/i,ne=function(q,oe){return new ne.fn.init(q,oe)};ne.fn=ne.prototype={jquery:Ce,constructor:ne,length:0,toArray:function(){return n.call(this)},get:function(q){return q==null?n.call(this):q<0?this[q+this.length]:this[q]},pushStack:function(q){var oe=ne.merge(this.constructor(),q);return oe.prevObject=this,oe},each:function(q){return ne.each(this,q)},map:function(q){return this.pushStack(ne.map(this,function(oe,pe){return q.call(oe,pe,oe)}))},slice:function(){return this.pushStack(n.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(ne.grep(this,function(q,oe){return(oe+1)%2}))},odd:function(){return this.pushStack(ne.grep(this,function(q,oe){return oe%2}))},eq:function(q){var oe=this.length,pe=+q+(q<0?oe:0);return this.pushStack(pe>=0&&pe0&&oe-1 in q}function We(q,oe){return q.nodeName&&q.nodeName.toLowerCase()===oe.toLowerCase()}var Se=t.pop,ve=t.sort,Qe=t.splice,wt="[\\x20\\t\\r\\n\\f]",et=new RegExp("^"+wt+"+|((?:^|[^\\\\])(?:\\\\.)*)"+wt+"+$","g");ne.contains=function(q,oe){var pe=oe&&oe.parentNode;return q===pe||!!(pe&&pe.nodeType===1&&(q.contains?q.contains(pe):q.compareDocumentPosition&&q.compareDocumentPosition(pe)&16))};var xt=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;function sr(q,oe){return oe?q==="\0"?"\uFFFD":q.slice(0,-1)+"\\"+q.charCodeAt(q.length-1).toString(16)+" ":"\\"+q}ne.escapeSelector=function(q){return(q+"").replace(xt,sr)};var Dr=ee,hr=s;(function(){var q,oe,pe,be,Ge,qe=hr,ot,rr,Ut,cr,Fr,li=ne.expando,vr=0,Bi=0,gn=Ah(),gs=Ah(),Io=Ah(),bl=Ah(),al=function(Bt,ar){return Bt===ar&&(Ge=!0),0},Da="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",Ka="(?:\\\\[\\da-fA-F]{1,6}"+wt+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",Yn="\\["+wt+"*("+Ka+")(?:"+wt+"*([*^$|!~]?=)"+wt+`*(?:'((?:\\\\.|[^\\\\'])*)'|"((?:\\\\.|[^\\\\"])*)"|(`+Ka+"))|)"+wt+"*\\]",Er=":("+Ka+`)(?:\\((('((?:\\\\.|[^\\\\'])*)'|"((?:\\\\.|[^\\\\"])*)")|((?:\\\\.|[^\\\\()[\\]]|`+Yn+")*)|.*)\\)|)",Hr=new RegExp(wt+"+","g"),wi=new RegExp("^"+wt+"*,"+wt+"*"),ni=new RegExp("^"+wt+"*([>+~]|"+wt+")"+wt+"*"),b=new RegExp(wt+"|>"),Po=new RegExp(Er),_o=new RegExp("^"+Ka+"$"),Ze={ID:new RegExp("^#("+Ka+")"),CLASS:new RegExp("^\\.("+Ka+")"),TAG:new RegExp("^("+Ka+"|[*])"),ATTR:new RegExp("^"+Yn),PSEUDO:new RegExp("^"+Er),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+wt+"*(even|odd|(([+-]|)(\\d*)n|)"+wt+"*(?:([+-]|)"+wt+"*(\\d+)|))"+wt+"*\\)|)","i"),bool:new RegExp("^(?:"+Da+")$","i"),needsContext:new RegExp("^"+wt+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+wt+"*((?:-\\d)?\\d*)"+wt+"*\\)|)(?=[^-]|$)","i")},Is=/^(?:input|select|textarea|button)$/i,ao=/^h\d$/i,Ps=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Rs=/[+~]/,Ho=new RegExp("\\\\[\\da-fA-F]{1,6}"+wt+"?|\\\\([^\\r\\n\\f])","g"),Ks=function(Bt,ar){var Rr="0x"+Bt.slice(1)-65536;return ar||(Rr<0?String.fromCharCode(Rr+65536):String.fromCharCode(Rr>>10|55296,Rr&1023|56320))},Ts=function(){qn()},Os=_f(function(Bt){return Bt.disabled===!0&&We(Bt,"fieldset")},{dir:"parentNode",next:"legend"});function gh(){try{return ot.activeElement}catch{}}try{qe.apply(t=n.call(Dr.childNodes),Dr.childNodes),t[Dr.childNodes.length].nodeType}catch{qe={apply:function(ar,Rr){hr.apply(ar,n.call(Rr))},call:function(ar){hr.apply(ar,n.call(arguments,1))}}}function Ss(Bt,ar,Rr,Ur){var ci,Li,yi,Pn,sn,To,Gi,Ti=ar&&ar.ownerDocument,Kn=ar?ar.nodeType:9;if(Rr=Rr||[],typeof Bt!="string"||!Bt||Kn!==1&&Kn!==9&&Kn!==11)return Rr;if(!Ur&&(qn(ar),ar=ar||ot,Ut)){if(Kn!==11&&(sn=Ps.exec(Bt)))if(ci=sn[1]){if(Kn===9)if(yi=ar.getElementById(ci)){if(yi.id===ci)return qe.call(Rr,yi),Rr}else return Rr;else if(Ti&&(yi=Ti.getElementById(ci))&&Ss.contains(ar,yi)&&yi.id===ci)return qe.call(Rr,yi),Rr}else{if(sn[2])return qe.apply(Rr,ar.getElementsByTagName(Bt)),Rr;if((ci=sn[3])&&ar.getElementsByClassName)return qe.apply(Rr,ar.getElementsByClassName(ci)),Rr}if(!bl[Bt+" "]&&(!cr||!cr.test(Bt))){if(Gi=Bt,Ti=ar,Kn===1&&(b.test(Bt)||ni.test(Bt))){for(Ti=Rs.test(Bt)&&Ac(ar.parentNode)||ar,(Ti!=ar||!k.scope)&&((Pn=ar.getAttribute("id"))?Pn=ne.escapeSelector(Pn):ar.setAttribute("id",Pn=li)),To=Fn(Bt),Li=To.length;Li--;)To[Li]=(Pn?"#"+Pn:":scope")+" "+Xh(To[Li]);Gi=To.join(",")}try{return qe.apply(Rr,Ti.querySelectorAll(Gi)),Rr}catch{bl(Bt,!0)}finally{Pn===li&&ar.removeAttribute("id")}}}return qf(Bt.replace(et,"$1"),ar,Rr,Ur)}function Ah(){var Bt=[];function ar(Rr,Ur){return Bt.push(Rr+" ")>oe.cacheLength&&delete ar[Bt.shift()],ar[Rr+" "]=Ur}return ar}function mc(Bt){return Bt[li]=!0,Bt}function Qa(Bt){var ar=ot.createElement("fieldset");try{return!!Bt(ar)}catch{return!1}finally{ar.parentNode&&ar.parentNode.removeChild(ar),ar=null}}function Ru(Bt){return function(ar){return We(ar,"input")&&ar.type===Bt}}function gc(Bt){return function(ar){return(We(ar,"input")||We(ar,"button"))&&ar.type===Bt}}function _h(Bt){return function(ar){return"form"in ar?ar.parentNode&&ar.disabled===!1?"label"in ar?"label"in ar.parentNode?ar.parentNode.disabled===Bt:ar.disabled===Bt:ar.isDisabled===Bt||ar.isDisabled!==!Bt&&Os(ar)===Bt:ar.disabled===Bt:"label"in ar?ar.disabled===Bt:!1}}function Fl(Bt){return mc(function(ar){return ar=+ar,mc(function(Rr,Ur){for(var ci,Li=Bt([],Rr.length,ar),yi=Li.length;yi--;)Rr[ci=Li[yi]]&&(Rr[ci]=!(Ur[ci]=Rr[ci]))})})}function Ac(Bt){return Bt&&typeof Bt.getElementsByTagName<"u"&&Bt}function qn(Bt){var ar,Rr=Bt?Bt.ownerDocument||Bt:Dr;return Rr==ot||Rr.nodeType!==9||!Rr.documentElement||(ot=Rr,rr=ot.documentElement,Ut=!ne.isXMLDoc(ot),Fr=rr.matches||rr.webkitMatchesSelector||rr.msMatchesSelector,rr.msMatchesSelector&&Dr!=ot&&(ar=ot.defaultView)&&ar.top!==ar&&ar.addEventListener("unload",Ts),k.getById=Qa(function(Ur){return rr.appendChild(Ur).id=ne.expando,!ot.getElementsByName||!ot.getElementsByName(ne.expando).length}),k.disconnectedMatch=Qa(function(Ur){return Fr.call(Ur,"*")}),k.scope=Qa(function(){return ot.querySelectorAll(":scope")}),k.cssHas=Qa(function(){try{return ot.querySelector(":has(*,:jqfake)"),!1}catch{return!0}}),k.getById?(oe.filter.ID=function(Ur){var ci=Ur.replace(Ho,Ks);return function(Li){return Li.getAttribute("id")===ci}},oe.find.ID=function(Ur,ci){if(typeof ci.getElementById<"u"&&Ut){var Li=ci.getElementById(Ur);return Li?[Li]:[]}}):(oe.filter.ID=function(Ur){var ci=Ur.replace(Ho,Ks);return function(Li){var yi=typeof Li.getAttributeNode<"u"&&Li.getAttributeNode("id");return yi&&yi.value===ci}},oe.find.ID=function(Ur,ci){if(typeof ci.getElementById<"u"&&Ut){var Li,yi,Pn,sn=ci.getElementById(Ur);if(sn){if(Li=sn.getAttributeNode("id"),Li&&Li.value===Ur)return[sn];for(Pn=ci.getElementsByName(Ur),yi=0;sn=Pn[yi++];)if(Li=sn.getAttributeNode("id"),Li&&Li.value===Ur)return[sn]}return[]}}),oe.find.TAG=function(Ur,ci){return typeof ci.getElementsByTagName<"u"?ci.getElementsByTagName(Ur):ci.querySelectorAll(Ur)},oe.find.CLASS=function(Ur,ci){if(typeof ci.getElementsByClassName<"u"&&Ut)return ci.getElementsByClassName(Ur)},cr=[],Qa(function(Ur){var ci;rr.appendChild(Ur).innerHTML="",Ur.querySelectorAll("[selected]").length||cr.push("\\["+wt+"*(?:value|"+Da+")"),Ur.querySelectorAll("[id~="+li+"-]").length||cr.push("~="),Ur.querySelectorAll("a#"+li+"+*").length||cr.push(".#.+[+~]"),Ur.querySelectorAll(":checked").length||cr.push(":checked"),ci=ot.createElement("input"),ci.setAttribute("type","hidden"),Ur.appendChild(ci).setAttribute("name","D"),rr.appendChild(Ur).disabled=!0,Ur.querySelectorAll(":disabled").length!==2&&cr.push(":enabled",":disabled"),ci=ot.createElement("input"),ci.setAttribute("name",""),Ur.appendChild(ci),Ur.querySelectorAll("[name='']").length||cr.push("\\["+wt+"*name"+wt+"*="+wt+`*(?:''|"")`)}),k.cssHas||cr.push(":has"),cr=cr.length&&new RegExp(cr.join("|")),al=function(Ur,ci){if(Ur===ci)return Ge=!0,0;var Li=!Ur.compareDocumentPosition-!ci.compareDocumentPosition;return Li||(Li=(Ur.ownerDocument||Ur)==(ci.ownerDocument||ci)?Ur.compareDocumentPosition(ci):1,Li&1||!k.sortDetached&&ci.compareDocumentPosition(Ur)===Li?Ur===ot||Ur.ownerDocument==Dr&&Ss.contains(Dr,Ur)?-1:ci===ot||ci.ownerDocument==Dr&&Ss.contains(Dr,ci)?1:be?c.call(be,Ur)-c.call(be,ci):0:Li&4?-1:1)}),ot}Ss.matches=function(Bt,ar){return Ss(Bt,null,null,ar)},Ss.matchesSelector=function(Bt,ar){if(qn(Bt),Ut&&!bl[ar+" "]&&(!cr||!cr.test(ar)))try{var Rr=Fr.call(Bt,ar);if(Rr||k.disconnectedMatch||Bt.document&&Bt.document.nodeType!==11)return Rr}catch{bl(ar,!0)}return Ss(ar,ot,null,[Bt]).length>0},Ss.contains=function(Bt,ar){return(Bt.ownerDocument||Bt)!=ot&&qn(Bt),ne.contains(Bt,ar)},Ss.attr=function(Bt,ar){(Bt.ownerDocument||Bt)!=ot&&qn(Bt);var Rr=oe.attrHandle[ar.toLowerCase()],Ur=Rr&&y.call(oe.attrHandle,ar.toLowerCase())?Rr(Bt,ar,!Ut):void 0;return Ur!==void 0?Ur:Bt.getAttribute(ar)},Ss.error=function(Bt){throw new Error("Syntax error, unrecognized expression: "+Bt)},ne.uniqueSort=function(Bt){var ar,Rr=[],Ur=0,ci=0;if(Ge=!k.sortStable,be=!k.sortStable&&n.call(Bt,0),ve.call(Bt,al),Ge){for(;ar=Bt[ci++];)ar===Bt[ci]&&(Ur=Rr.push(ci));for(;Ur--;)Qe.call(Bt,Rr[Ur],1)}return be=null,Bt},ne.fn.uniqueSort=function(){return this.pushStack(ne.uniqueSort(n.apply(this)))},oe=ne.expr={cacheLength:50,createPseudo:mc,match:Ze,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(Bt){return Bt[1]=Bt[1].replace(Ho,Ks),Bt[3]=(Bt[3]||Bt[4]||Bt[5]||"").replace(Ho,Ks),Bt[2]==="~="&&(Bt[3]=" "+Bt[3]+" "),Bt.slice(0,4)},CHILD:function(Bt){return Bt[1]=Bt[1].toLowerCase(),Bt[1].slice(0,3)==="nth"?(Bt[3]||Ss.error(Bt[0]),Bt[4]=+(Bt[4]?Bt[5]+(Bt[6]||1):2*(Bt[3]==="even"||Bt[3]==="odd")),Bt[5]=+(Bt[7]+Bt[8]||Bt[3]==="odd")):Bt[3]&&Ss.error(Bt[0]),Bt},PSEUDO:function(Bt){var ar,Rr=!Bt[6]&&Bt[2];return Ze.CHILD.test(Bt[0])?null:(Bt[3]?Bt[2]=Bt[4]||Bt[5]||"":Rr&&Po.test(Rr)&&(ar=Fn(Rr,!0))&&(ar=Rr.indexOf(")",Rr.length-ar)-Rr.length)&&(Bt[0]=Bt[0].slice(0,ar),Bt[2]=Rr.slice(0,ar)),Bt.slice(0,3))}},filter:{TAG:function(Bt){var ar=Bt.replace(Ho,Ks).toLowerCase();return Bt==="*"?function(){return!0}:function(Rr){return We(Rr,ar)}},CLASS:function(Bt){var ar=gn[Bt+" "];return ar||(ar=new RegExp("(^|"+wt+")"+Bt+"("+wt+"|$)"))&&gn(Bt,function(Rr){return ar.test(typeof Rr.className=="string"&&Rr.className||typeof Rr.getAttribute<"u"&&Rr.getAttribute("class")||"")})},ATTR:function(Bt,ar,Rr){return function(Ur){var ci=Ss.attr(Ur,Bt);return ci==null?ar==="!=":ar?(ci+="",ar==="="?ci===Rr:ar==="!="?ci!==Rr:ar==="^="?Rr&&ci.indexOf(Rr)===0:ar==="*="?Rr&&ci.indexOf(Rr)>-1:ar==="$="?Rr&&ci.slice(-Rr.length)===Rr:ar==="~="?(" "+ci.replace(Hr," ")+" ").indexOf(Rr)>-1:ar==="|="?ci===Rr||ci.slice(0,Rr.length+1)===Rr+"-":!1):!0}},CHILD:function(Bt,ar,Rr,Ur,ci){var Li=Bt.slice(0,3)!=="nth",yi=Bt.slice(-4)!=="last",Pn=ar==="of-type";return Ur===1&&ci===0?function(sn){return!!sn.parentNode}:function(sn,To,Gi){var Ti,Kn,zn,Fs,wl,yo=Li!==yi?"nextSibling":"previousSibling",tc=sn.parentNode,Tl=Pn&&sn.nodeName.toLowerCase(),ll=!Gi&&!Pn,Oa=!1;if(tc){if(Li){for(;yo;){for(zn=sn;zn=zn[yo];)if(Pn?We(zn,Tl):zn.nodeType===1)return!1;wl=yo=Bt==="only"&&!wl&&"nextSibling"}return!0}if(wl=[yi?tc.firstChild:tc.lastChild],yi&&ll){for(Kn=tc[li]||(tc[li]={}),Ti=Kn[Bt]||[],Fs=Ti[0]===vr&&Ti[1],Oa=Fs&&Ti[2],zn=Fs&&tc.childNodes[Fs];zn=++Fs&&zn&&zn[yo]||(Oa=Fs=0)||wl.pop();)if(zn.nodeType===1&&++Oa&&zn===sn){Kn[Bt]=[vr,Fs,Oa];break}}else if(ll&&(Kn=sn[li]||(sn[li]={}),Ti=Kn[Bt]||[],Fs=Ti[0]===vr&&Ti[1],Oa=Fs),Oa===!1)for(;(zn=++Fs&&zn&&zn[yo]||(Oa=Fs=0)||wl.pop())&&!((Pn?We(zn,Tl):zn.nodeType===1)&&++Oa&&(ll&&(Kn=zn[li]||(zn[li]={}),Kn[Bt]=[vr,Oa]),zn===sn)););return Oa-=ci,Oa===Ur||Oa%Ur===0&&Oa/Ur>=0}}},PSEUDO:function(Bt,ar){var Rr,Ur=oe.pseudos[Bt]||oe.setFilters[Bt.toLowerCase()]||Ss.error("unsupported pseudo: "+Bt);return Ur[li]?Ur(ar):Ur.length>1?(Rr=[Bt,Bt,"",ar],oe.setFilters.hasOwnProperty(Bt.toLowerCase())?mc(function(ci,Li){for(var yi,Pn=Ur(ci,ar),sn=Pn.length;sn--;)yi=c.call(ci,Pn[sn]),ci[yi]=!(Li[yi]=Pn[sn])}):function(ci){return Ur(ci,0,Rr)}):Ur}},pseudos:{not:mc(function(Bt){var ar=[],Rr=[],Ur=Wf(Bt.replace(et,"$1"));return Ur[li]?mc(function(ci,Li,yi,Pn){for(var sn,To=Ur(ci,null,Pn,[]),Gi=ci.length;Gi--;)(sn=To[Gi])&&(ci[Gi]=!(Li[Gi]=sn))}):function(ci,Li,yi){return ar[0]=ci,Ur(ar,null,yi,Rr),ar[0]=null,!Rr.pop()}}),has:mc(function(Bt){return function(ar){return Ss(Bt,ar).length>0}}),contains:mc(function(Bt){return Bt=Bt.replace(Ho,Ks),function(ar){return(ar.textContent||ne.text(ar)).indexOf(Bt)>-1}}),lang:mc(function(Bt){return _o.test(Bt||"")||Ss.error("unsupported lang: "+Bt),Bt=Bt.replace(Ho,Ks).toLowerCase(),function(ar){var Rr;do if(Rr=Ut?ar.lang:ar.getAttribute("xml:lang")||ar.getAttribute("lang"))return Rr=Rr.toLowerCase(),Rr===Bt||Rr.indexOf(Bt+"-")===0;while((ar=ar.parentNode)&&ar.nodeType===1);return!1}}),target:function(Bt){var ar=r.location&&r.location.hash;return ar&&ar.slice(1)===Bt.id},root:function(Bt){return Bt===rr},focus:function(Bt){return Bt===gh()&&ot.hasFocus()&&!!(Bt.type||Bt.href||~Bt.tabIndex)},enabled:_h(!1),disabled:_h(!0),checked:function(Bt){return We(Bt,"input")&&!!Bt.checked||We(Bt,"option")&&!!Bt.selected},selected:function(Bt){return Bt.parentNode&&Bt.parentNode.selectedIndex,Bt.selected===!0},empty:function(Bt){for(Bt=Bt.firstChild;Bt;Bt=Bt.nextSibling)if(Bt.nodeType<6)return!1;return!0},parent:function(Bt){return!oe.pseudos.empty(Bt)},header:function(Bt){return ao.test(Bt.nodeName)},input:function(Bt){return Is.test(Bt.nodeName)},button:function(Bt){return We(Bt,"input")&&Bt.type==="button"||We(Bt,"button")},text:function(Bt){var ar;return We(Bt,"input")&&Bt.type==="text"&&((ar=Bt.getAttribute("type"))==null||ar.toLowerCase()==="text")},first:Fl(function(){return[0]}),last:Fl(function(Bt,ar){return[ar-1]}),eq:Fl(function(Bt,ar,Rr){return[Rr<0?Rr+ar:Rr]}),even:Fl(function(Bt,ar){for(var Rr=0;Rrar?Ur=ar:Ur=Rr;--Ur>=0;)Bt.push(Ur);return Bt}),gt:Fl(function(Bt,ar,Rr){for(var Ur=Rr<0?Rr+ar:Rr;++Ur1?function(ar,Rr,Ur){for(var ci=Bt.length;ci--;)if(!Bt[ci](ar,Rr,Ur))return!1;return!0}:Bt[0]}function Wd(Bt,ar,Rr){for(var Ur=0,ci=ar.length;Ur-1&&(yi[Gi]=!(Pn[Gi]=Kn))}}else zn=Ad(zn===Pn?zn.splice(yo,zn.length):zn),ci?ci(null,Pn,zn,To):qe.apply(Pn,zn)})}function yf(Bt){for(var ar,Rr,Ur,ci=Bt.length,Li=oe.relative[Bt[0].type],yi=Li||oe.relative[" "],Pn=Li?1:0,sn=_f(function(Ti){return Ti===ar},yi,!0),To=_f(function(Ti){return c.call(ar,Ti)>-1},yi,!0),Gi=[function(Ti,Kn,zn){var Fs=!Li&&(zn||Kn!=pe)||((ar=Kn).nodeType?sn(Ti,Kn,zn):To(Ti,Kn,zn));return ar=null,Fs}];Pn1&&Zh(Gi),Pn>1&&Xh(Bt.slice(0,Pn-1).concat({value:Bt[Pn-2].type===" "?"*":""})).replace(et,"$1"),Rr,Pn0,Ur=Bt.length>0,ci=function(Li,yi,Pn,sn,To){var Gi,Ti,Kn,zn=0,Fs="0",wl=Li&&[],yo=[],tc=pe,Tl=Li||Ur&&oe.find.TAG("*",To),ll=vr+=tc==null?1:Math.random()||.1,Oa=Tl.length;for(To&&(pe=yi==ot||yi||To);Fs!==Oa&&(Gi=Tl[Fs])!=null;Fs++){if(Ur&&Gi){for(Ti=0,!yi&&Gi.ownerDocument!=ot&&(qn(Gi),Pn=!Ut);Kn=Bt[Ti++];)if(Kn(Gi,yi||ot,Pn)){qe.call(sn,Gi);break}To&&(vr=ll)}Rr&&((Gi=!Kn&&Gi)&&zn--,Li&&wl.push(Gi))}if(zn+=Fs,Rr&&Fs!==zn){for(Ti=0;Kn=ar[Ti++];)Kn(wl,yo,yi,Pn);if(Li){if(zn>0)for(;Fs--;)wl[Fs]||yo[Fs]||(yo[Fs]=Se.call(sn));yo=Ad(yo)}qe.apply(sn,yo),To&&!Li&&yo.length>0&&zn+ar.length>1&&ne.uniqueSort(sn)}return To&&(vr=ll,pe=tc),wl};return Rr?mc(ci):ci}function Wf(Bt,ar){var Rr,Ur=[],ci=[],Li=Io[Bt+" "];if(!Li){for(ar||(ar=Fn(Bt)),Rr=ar.length;Rr--;)Li=yf(ar[Rr]),Li[li]?Ur.push(Li):ci.push(Li);Li=Io(Bt,Bu(ci,Ur)),Li.selector=Bt}return Li}function qf(Bt,ar,Rr,Ur){var ci,Li,yi,Pn,sn,To=typeof Bt=="function"&&Bt,Gi=!Ur&&Fn(Bt=To.selector||Bt);if(Rr=Rr||[],Gi.length===1){if(Li=Gi[0]=Gi[0].slice(0),Li.length>2&&(yi=Li[0]).type==="ID"&&ar.nodeType===9&&Ut&&oe.relative[Li[1].type]){if(ar=(oe.find.ID(yi.matches[0].replace(Ho,Ks),ar)||[])[0],ar)To&&(ar=ar.parentNode);else return Rr;Bt=Bt.slice(Li.shift().value.length)}for(ci=Ze.needsContext.test(Bt)?0:Li.length;ci--&&(yi=Li[ci],!oe.relative[Pn=yi.type]);)if((sn=oe.find[Pn])&&(Ur=sn(yi.matches[0].replace(Ho,Ks),Rs.test(Li[0].type)&&Ac(ar.parentNode)||ar))){if(Li.splice(ci,1),Bt=Ur.length&&Xh(Li),!Bt)return qe.apply(Rr,Ur),Rr;break}}return(To||Wf(Bt,Gi))(Ur,ar,!Ut,Rr,!ar||Rs.test(Bt)&&Ac(ar.parentNode)||ar),Rr}k.sortStable=li.split("").sort(al).join("")===li,qn(),k.sortDetached=Qa(function(Bt){return Bt.compareDocumentPosition(ot.createElement("fieldset"))&1}),ne.find=Ss,ne.expr[":"]=ne.expr.pseudos,ne.unique=ne.uniqueSort,Ss.compile=Wf,Ss.select=qf,Ss.setDocument=qn,Ss.tokenize=Fn,Ss.escape=ne.escapeSelector,Ss.getText=ne.text,Ss.isXML=ne.isXMLDoc,Ss.selectors=ne.expr,Ss.support=ne.support,Ss.uniqueSort=ne.uniqueSort})();var mi=function(q,oe,pe){for(var be=[],Ge=pe!==void 0;(q=q[oe])&&q.nodeType!==9;)if(q.nodeType===1){if(Ge&&ne(q).is(pe))break;be.push(q)}return be},bi=function(q,oe){for(var pe=[];q;q=q.nextSibling)q.nodeType===1&&q!==oe&&pe.push(q);return pe},un=ne.expr.match.needsContext,Yr=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function Xi(q,oe,pe){return U(oe)?ne.grep(q,function(be,Ge){return!!oe.call(be,Ge,be)!==pe}):oe.nodeType?ne.grep(q,function(be){return be===oe!==pe}):typeof oe!="string"?ne.grep(q,function(be){return c.call(oe,be)>-1!==pe}):ne.filter(oe,q,pe)}ne.filter=function(q,oe,pe){var be=oe[0];return pe&&(q=":not("+q+")"),oe.length===1&&be.nodeType===1?ne.find.matchesSelector(be,q)?[be]:[]:ne.find.matches(q,ne.grep(oe,function(Ge){return Ge.nodeType===1}))},ne.fn.extend({find:function(q){var oe,pe,be=this.length,Ge=this;if(typeof q!="string")return this.pushStack(ne(q).filter(function(){for(oe=0;oe1?ne.uniqueSort(pe):pe},filter:function(q){return this.pushStack(Xi(this,q||[],!1))},not:function(q){return this.pushStack(Xi(this,q||[],!0))},is:function(q){return!!Xi(this,typeof q=="string"&&un.test(q)?ne(q):q||[],!1).length}});var xi,ii=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,Hn=ne.fn.init=function(q,oe,pe){var be,Ge;if(!q)return this;if(pe=pe||xi,typeof q=="string")if(q[0]==="<"&&q[q.length-1]===">"&&q.length>=3?be=[null,q,null]:be=ii.exec(q),be&&(be[1]||!oe))if(be[1]){if(oe=oe instanceof ne?oe[0]:oe,ne.merge(this,ne.parseHTML(be[1],oe&&oe.nodeType?oe.ownerDocument||oe:ee,!0)),Yr.test(be[1])&&ne.isPlainObject(oe))for(be in oe)U(this[be])?this[be](oe[be]):this.attr(be,oe[be]);return this}else return Ge=ee.getElementById(be[2]),Ge&&(this[0]=Ge,this.length=1),this;else return!oe||oe.jquery?(oe||pe).find(q):this.constructor(oe).find(q);else{if(q.nodeType)return this[0]=q,this.length=1,this;if(U(q))return pe.ready!==void 0?pe.ready(q):q(ne)}return ne.makeArray(q,this)};Hn.prototype=ne.fn,xi=ne(ee);var Ve=/^(?:parents|prev(?:Until|All))/,nt={children:!0,contents:!0,next:!0,prev:!0};ne.fn.extend({has:function(q){var oe=ne(q,this),pe=oe.length;return this.filter(function(){for(var be=0;be-1:pe.nodeType===1&&ne.find.matchesSelector(pe,q))){qe.push(pe);break}}return this.pushStack(qe.length>1?ne.uniqueSort(qe):qe)},index:function(q){return q?typeof q=="string"?c.call(ne(q),this[0]):c.call(this,q.jquery?q[0]:q):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(q,oe){return this.pushStack(ne.uniqueSort(ne.merge(this.get(),ne(q,oe))))},addBack:function(q){return this.add(q==null?this.prevObject:this.prevObject.filter(q))}});function Ft(q,oe){for(;(q=q[oe])&&q.nodeType!==1;);return q}ne.each({parent:function(q){var oe=q.parentNode;return oe&&oe.nodeType!==11?oe:null},parents:function(q){return mi(q,"parentNode")},parentsUntil:function(q,oe,pe){return mi(q,"parentNode",pe)},next:function(q){return Ft(q,"nextSibling")},prev:function(q){return Ft(q,"previousSibling")},nextAll:function(q){return mi(q,"nextSibling")},prevAll:function(q){return mi(q,"previousSibling")},nextUntil:function(q,oe,pe){return mi(q,"nextSibling",pe)},prevUntil:function(q,oe,pe){return mi(q,"previousSibling",pe)},siblings:function(q){return bi((q.parentNode||{}).firstChild,q)},children:function(q){return bi(q.firstChild)},contents:function(q){return q.contentDocument!=null&&i(q.contentDocument)?q.contentDocument:(We(q,"template")&&(q=q.content||q),ne.merge([],q.childNodes))}},function(q,oe){ne.fn[q]=function(pe,be){var Ge=ne.map(this,oe,pe);return q.slice(-5)!=="Until"&&(be=pe),be&&typeof be=="string"&&(Ge=ne.filter(be,Ge)),this.length>1&&(nt[q]||ne.uniqueSort(Ge),Ve.test(q)&&Ge.reverse()),this.pushStack(Ge)}});var mr=/[^\x20\t\r\n\f]+/g;function Xr(q){var oe={};return ne.each(q.match(mr)||[],function(pe,be){oe[be]=!0}),oe}ne.Callbacks=function(q){q=typeof q=="string"?Xr(q):ne.extend({},q);var oe,pe,be,Ge,qe=[],ot=[],rr=-1,Ut=function(){for(Ge=Ge||q.once,be=oe=!0;ot.length;rr=-1)for(pe=ot.shift();++rr-1;)qe.splice(vr,1),vr<=rr&&rr--}),this},has:function(Fr){return Fr?ne.inArray(Fr,qe)>-1:qe.length>0},empty:function(){return qe&&(qe=[]),this},disable:function(){return Ge=ot=[],qe=pe="",this},disabled:function(){return!qe},lock:function(){return Ge=ot=[],!pe&&!oe&&(qe=pe=""),this},locked:function(){return!!Ge},fireWith:function(Fr,li){return Ge||(li=li||[],li=[Fr,li.slice?li.slice():li],ot.push(li),oe||Ut()),this},fire:function(){return cr.fireWith(this,arguments),this},fired:function(){return!!be}};return cr};function zr(q){return q}function ln(q){throw q}function Nn(q,oe,pe,be){var Ge;try{q&&U(Ge=q.promise)?Ge.call(q).done(oe).fail(pe):q&&U(Ge=q.then)?Ge.call(q,oe,pe):oe.apply(void 0,[q].slice(be))}catch(qe){pe.apply(void 0,[qe])}}ne.extend({Deferred:function(q){var oe=[["notify","progress",ne.Callbacks("memory"),ne.Callbacks("memory"),2],["resolve","done",ne.Callbacks("once memory"),ne.Callbacks("once memory"),0,"resolved"],["reject","fail",ne.Callbacks("once memory"),ne.Callbacks("once memory"),1,"rejected"]],pe="pending",be={state:function(){return pe},always:function(){return Ge.done(arguments).fail(arguments),this},catch:function(qe){return be.then(null,qe)},pipe:function(){var qe=arguments;return ne.Deferred(function(ot){ne.each(oe,function(rr,Ut){var cr=U(qe[Ut[4]])&&qe[Ut[4]];Ge[Ut[1]](function(){var Fr=cr&&cr.apply(this,arguments);Fr&&U(Fr.promise)?Fr.promise().progress(ot.notify).done(ot.resolve).fail(ot.reject):ot[Ut[0]+"With"](this,cr?[Fr]:arguments)})}),qe=null}).promise()},then:function(qe,ot,rr){var Ut=0;function cr(Fr,li,vr,Bi){return function(){var gn=this,gs=arguments,Io=function(){var al,Da;if(!(Fr=Ut&&(vr!==ln&&(gn=void 0,gs=[al]),li.rejectWith(gn,gs))}};Fr?bl():(ne.Deferred.getErrorHook?bl.error=ne.Deferred.getErrorHook():ne.Deferred.getStackHook&&(bl.error=ne.Deferred.getStackHook()),r.setTimeout(bl))}}return ne.Deferred(function(Fr){oe[0][3].add(cr(0,Fr,U(rr)?rr:zr,Fr.notifyWith)),oe[1][3].add(cr(0,Fr,U(qe)?qe:zr)),oe[2][3].add(cr(0,Fr,U(ot)?ot:ln))}).promise()},promise:function(qe){return qe!=null?ne.extend(qe,be):be}},Ge={};return ne.each(oe,function(qe,ot){var rr=ot[2],Ut=ot[5];be[ot[1]]=rr.add,Ut&&rr.add(function(){pe=Ut},oe[3-qe][2].disable,oe[3-qe][3].disable,oe[0][2].lock,oe[0][3].lock),rr.add(ot[3].fire),Ge[ot[0]]=function(){return Ge[ot[0]+"With"](this===Ge?void 0:this,arguments),this},Ge[ot[0]+"With"]=rr.fireWith}),be.promise(Ge),q&&q.call(Ge,Ge),Ge},when:function(q){var oe=arguments.length,pe=oe,be=Array(pe),Ge=n.call(arguments),qe=ne.Deferred(),ot=function(rr){return function(Ut){be[rr]=this,Ge[rr]=arguments.length>1?n.call(arguments):Ut,--oe||qe.resolveWith(be,Ge)}};if(oe<=1&&(Nn(q,qe.done(ot(pe)).resolve,qe.reject,!oe),qe.state()==="pending"||U(Ge[pe]&&Ge[pe].then)))return qe.then();for(;pe--;)Nn(Ge[pe],ot(pe),qe.reject);return qe.promise()}});var Co=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;ne.Deferred.exceptionHook=function(q,oe){r.console&&r.console.warn&&q&&Co.test(q.name)&&r.console.warn("jQuery.Deferred exception: "+q.message,q.stack,oe)},ne.readyException=function(q){r.setTimeout(function(){throw q})};var Go=ne.Deferred();ne.fn.ready=function(q){return Go.then(q).catch(function(oe){ne.readyException(oe)}),this},ne.extend({isReady:!1,readyWait:1,ready:function(q){(q===!0?--ne.readyWait:ne.isReady)||(ne.isReady=!0,!(q!==!0&&--ne.readyWait>0)&&Go.resolveWith(ee,[ne]))}}),ne.ready.then=Go.then;function is(){ee.removeEventListener("DOMContentLoaded",is),r.removeEventListener("load",is),ne.ready()}ee.readyState==="complete"||ee.readyState!=="loading"&&!ee.documentElement.doScroll?r.setTimeout(ne.ready):(ee.addEventListener("DOMContentLoaded",is),r.addEventListener("load",is));var mo=function(q,oe,pe,be,Ge,qe,ot){var rr=0,Ut=q.length,cr=pe==null;if(me(pe)==="object"){Ge=!0;for(rr in pe)mo(q,oe,rr,pe[rr],!0,qe,ot)}else if(be!==void 0&&(Ge=!0,U(be)||(ot=!0),cr&&(ot?(oe.call(q,be),oe=null):(cr=oe,oe=function(Fr,li,vr){return cr.call(ne(Fr),vr)})),oe))for(;rr1,null,!0)},removeData:function(q){return this.each(function(){Fe.remove(this,q)})}}),ne.extend({queue:function(q,oe,pe){var be;if(q)return oe=(oe||"fx")+"queue",be=Yt.get(q,oe),pe&&(!be||Array.isArray(pe)?be=Yt.access(q,oe,ne.makeArray(pe)):be.push(pe)),be||[]},dequeue:function(q,oe){oe=oe||"fx";var pe=ne.queue(q,oe),be=pe.length,Ge=pe.shift(),qe=ne._queueHooks(q,oe),ot=function(){ne.dequeue(q,oe)};Ge==="inprogress"&&(Ge=pe.shift(),be--),Ge&&(oe==="fx"&&pe.unshift("inprogress"),delete qe.stop,Ge.call(q,ot,qe)),!be&&qe&&qe.empty.fire()},_queueHooks:function(q,oe){var pe=oe+"queueHooks";return Yt.get(q,pe)||Yt.access(q,pe,{empty:ne.Callbacks("once memory").add(function(){Yt.remove(q,[oe+"queue",pe])})})}}),ne.fn.extend({queue:function(q,oe){var pe=2;return typeof q!="string"&&(oe=q,q="fx",pe--),arguments.length\x20\t\r\n\f]*)/i,ns=/^$|^module$|\/(?:java|ecma)script/i;(function(){var q=ee.createDocumentFragment(),oe=q.appendChild(ee.createElement("div")),pe=ee.createElement("input");pe.setAttribute("type","radio"),pe.setAttribute("checked","checked"),pe.setAttribute("name","t"),oe.appendChild(pe),k.checkClone=oe.cloneNode(!0).cloneNode(!0).lastChild.checked,oe.innerHTML="",k.noCloneChecked=!!oe.cloneNode(!0).lastChild.defaultValue,oe.innerHTML="",k.option=!!oe.lastChild})();var Be={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};Be.tbody=Be.tfoot=Be.colgroup=Be.caption=Be.thead,Be.th=Be.td,k.option||(Be.optgroup=Be.option=[1,""]);function Xe(q,oe){var pe;return typeof q.getElementsByTagName<"u"?pe=q.getElementsByTagName(oe||"*"):typeof q.querySelectorAll<"u"?pe=q.querySelectorAll(oe||"*"):pe=[],oe===void 0||oe&&We(q,oe)?ne.merge([q],pe):pe}function it(q,oe){for(var pe=0,be=q.length;pe-1){Ge&&Ge.push(qe);continue}if(cr=ti(qe),ot=Xe(li.appendChild(qe),"script"),cr&&it(ot),pe)for(Fr=0;qe=ot[Fr++];)ns.test(qe.type||"")&&pe.push(qe)}return li}var $t=/^([^.]*)(?:\.(.+)|)/;function Jt(){return!0}function ht(){return!1}function Ot(q,oe,pe,be,Ge,qe){var ot,rr;if(typeof oe=="object"){typeof pe!="string"&&(be=be||pe,pe=void 0);for(rr in oe)Ot(q,rr,pe,be,oe[rr],qe);return q}if(be==null&&Ge==null?(Ge=pe,be=pe=void 0):Ge==null&&(typeof pe=="string"?(Ge=be,be=void 0):(Ge=be,be=pe,pe=void 0)),Ge===!1)Ge=ht;else if(!Ge)return q;return qe===1&&(ot=Ge,Ge=function(Ut){return ne().off(Ut),ot.apply(this,arguments)},Ge.guid=ot.guid||(ot.guid=ne.guid++)),q.each(function(){ne.event.add(this,oe,Ge,be,pe)})}ne.event={global:{},add:function(q,oe,pe,be,Ge){var qe,ot,rr,Ut,cr,Fr,li,vr,Bi,gn,gs,Io=Yt.get(q);if(ps(q))for(pe.handler&&(qe=pe,pe=qe.handler,Ge=qe.selector),Ge&&ne.find.matchesSelector(xr,Ge),pe.guid||(pe.guid=ne.guid++),(Ut=Io.events)||(Ut=Io.events=Object.create(null)),(ot=Io.handle)||(ot=Io.handle=function(bl){return typeof ne<"u"&&ne.event.triggered!==bl.type?ne.event.dispatch.apply(q,arguments):void 0}),oe=(oe||"").match(mr)||[""],cr=oe.length;cr--;)rr=$t.exec(oe[cr])||[],Bi=gs=rr[1],gn=(rr[2]||"").split(".").sort(),Bi&&(li=ne.event.special[Bi]||{},Bi=(Ge?li.delegateType:li.bindType)||Bi,li=ne.event.special[Bi]||{},Fr=ne.extend({type:Bi,origType:gs,data:be,handler:pe,guid:pe.guid,selector:Ge,needsContext:Ge&&ne.expr.match.needsContext.test(Ge),namespace:gn.join(".")},qe),(vr=Ut[Bi])||(vr=Ut[Bi]=[],vr.delegateCount=0,(!li.setup||li.setup.call(q,be,gn,ot)===!1)&&q.addEventListener&&q.addEventListener(Bi,ot)),li.add&&(li.add.call(q,Fr),Fr.handler.guid||(Fr.handler.guid=pe.guid)),Ge?vr.splice(vr.delegateCount++,0,Fr):vr.push(Fr),ne.event.global[Bi]=!0)},remove:function(q,oe,pe,be,Ge){var qe,ot,rr,Ut,cr,Fr,li,vr,Bi,gn,gs,Io=Yt.hasData(q)&&Yt.get(q);if(!(!Io||!(Ut=Io.events))){for(oe=(oe||"").match(mr)||[""],cr=oe.length;cr--;){if(rr=$t.exec(oe[cr])||[],Bi=gs=rr[1],gn=(rr[2]||"").split(".").sort(),!Bi){for(Bi in Ut)ne.event.remove(q,Bi+oe[cr],pe,be,!0);continue}for(li=ne.event.special[Bi]||{},Bi=(be?li.delegateType:li.bindType)||Bi,vr=Ut[Bi]||[],rr=rr[2]&&new RegExp("(^|\\.)"+gn.join("\\.(?:.*\\.|)")+"(\\.|$)"),ot=qe=vr.length;qe--;)Fr=vr[qe],(Ge||gs===Fr.origType)&&(!pe||pe.guid===Fr.guid)&&(!rr||rr.test(Fr.namespace))&&(!be||be===Fr.selector||be==="**"&&Fr.selector)&&(vr.splice(qe,1),Fr.selector&&vr.delegateCount--,li.remove&&li.remove.call(q,Fr));ot&&!vr.length&&((!li.teardown||li.teardown.call(q,gn,Io.handle)===!1)&&ne.removeEvent(q,Bi,Io.handle),delete Ut[Bi])}ne.isEmptyObject(Ut)&&Yt.remove(q,"handle events")}},dispatch:function(q){var oe,pe,be,Ge,qe,ot,rr=new Array(arguments.length),Ut=ne.event.fix(q),cr=(Yt.get(this,"events")||Object.create(null))[Ut.type]||[],Fr=ne.event.special[Ut.type]||{};for(rr[0]=Ut,oe=1;oe=1)){for(;cr!==this;cr=cr.parentNode||this)if(cr.nodeType===1&&!(q.type==="click"&&cr.disabled===!0)){for(qe=[],ot={},pe=0;pe-1:ne.find(Ge,this,null,[cr]).length),ot[Ge]&&qe.push(be);qe.length&&rr.push({elem:cr,handlers:qe})}}return cr=this,Ut\s*$/g;function si(q,oe){return We(q,"table")&&We(oe.nodeType!==11?oe:oe.firstChild,"tr")&&ne(q).children("tbody")[0]||q}function Or(q){return q.type=(q.getAttribute("type")!==null)+"/"+q.type,q}function fi(q){return(q.type||"").slice(0,5)==="true/"?q.type=q.type.slice(5):q.removeAttribute("type"),q}function pr(q,oe){var pe,be,Ge,qe,ot,rr,Ut;if(oe.nodeType===1){if(Yt.hasData(q)&&(qe=Yt.get(q),Ut=qe.events,Ut)){Yt.remove(oe,"handle events");for(Ge in Ut)for(pe=0,be=Ut[Ge].length;pe1&&typeof Bi=="string"&&!k.checkClone&&ui.test(Bi))return q.each(function(gs){var Io=q.eq(gs);gn&&(oe[0]=Bi.call(this,gs,Io.html())),Mi(Io,oe,pe,be)});if(li&&(Ge=Ke(oe,q[0].ownerDocument,!1,q,be),qe=Ge.firstChild,Ge.childNodes.length===1&&(Ge=qe),qe||be)){for(ot=ne.map(Xe(Ge,"script"),Or),rr=ot.length;Fr0&&it(ot,!Ut&&Xe(q,"script")),rr},cleanData:function(q){for(var oe,pe,be,Ge=ne.event.special,qe=0;(pe=q[qe])!==void 0;qe++)if(ps(pe)){if(oe=pe[Yt.expando]){if(oe.events)for(be in oe.events)Ge[be]?ne.event.remove(pe,be):ne.removeEvent(pe,be,oe.handle);pe[Yt.expando]=void 0}pe[Fe.expando]&&(pe[Fe.expando]=void 0)}}}),ne.fn.extend({detach:function(q){return Ii(this,q,!0)},remove:function(q){return Ii(this,q)},text:function(q){return mo(this,function(oe){return oe===void 0?ne.text(this):this.empty().each(function(){(this.nodeType===1||this.nodeType===11||this.nodeType===9)&&(this.textContent=oe)})},null,q,arguments.length)},append:function(){return Mi(this,arguments,function(q){if(this.nodeType===1||this.nodeType===11||this.nodeType===9){var oe=si(this,q);oe.appendChild(q)}})},prepend:function(){return Mi(this,arguments,function(q){if(this.nodeType===1||this.nodeType===11||this.nodeType===9){var oe=si(this,q);oe.insertBefore(q,oe.firstChild)}})},before:function(){return Mi(this,arguments,function(q){this.parentNode&&this.parentNode.insertBefore(q,this)})},after:function(){return Mi(this,arguments,function(q){this.parentNode&&this.parentNode.insertBefore(q,this.nextSibling)})},empty:function(){for(var q,oe=0;(q=this[oe])!=null;oe++)q.nodeType===1&&(ne.cleanData(Xe(q,!1)),q.textContent="");return this},clone:function(q,oe){return q=q??!1,oe=oe??q,this.map(function(){return ne.clone(this,q,oe)})},html:function(q){return mo(this,function(oe){var pe=this[0]||{},be=0,Ge=this.length;if(oe===void 0&&pe.nodeType===1)return pe.innerHTML;if(typeof oe=="string"&&!ei.test(oe)&&!Be[(Us.exec(oe)||["",""])[1].toLowerCase()]){oe=ne.htmlPrefilter(oe);try{for(;be=0&&(Ut+=Math.max(0,Math.ceil(q["offset"+oe[0].toUpperCase()+oe.slice(1)]-qe-Ut-rr-.5))||0),Ut+cr}function md(q,oe,pe){var be=po(q),Ge=!k.boxSizingReliable()||pe,qe=Ge&&ne.css(q,"boxSizing",!1,be)==="border-box",ot=qe,rr=No(q,oe,be),Ut="offset"+oe[0].toUpperCase()+oe.slice(1);if(Ni.test(rr)){if(!pe)return rr;rr="auto"}return(!k.boxSizingReliable()&&qe||!k.reliableTrDimensions()&&We(q,"tr")||rr==="auto"||!parseFloat(rr)&&ne.css(q,"display",!1,be)==="inline")&&q.getClientRects().length&&(qe=ne.css(q,"boxSizing",!1,be)==="border-box",ot=Ut in q,ot&&(rr=q[Ut])),rr=parseFloat(rr)||0,rr+Ya(q,oe,pe||(qe?"border":"content"),ot,be,rr)+"px"}ne.extend({cssHooks:{opacity:{get:function(q,oe){if(oe){var pe=No(q,"opacity");return pe===""?"1":pe}}}},cssNumber:{animationIterationCount:!0,aspectRatio:!0,borderImageSlice:!0,columnCount:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,scale:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeMiterlimit:!0,strokeOpacity:!0},cssProps:{},style:function(q,oe,pe,be){if(!(!q||q.nodeType===3||q.nodeType===8||!q.style)){var Ge,qe,ot,rr=so(oe),Ut=zi.test(oe),cr=q.style;if(Ut||(oe=ho(rr)),ot=ne.cssHooks[oe]||ne.cssHooks[rr],pe!==void 0){if(qe=typeof pe,qe==="string"&&(Ge=fr.exec(pe))&&Ge[1]&&(pe=fn(q,oe,Ge),qe="number"),pe==null||pe!==pe)return;qe==="number"&&!Ut&&(pe+=Ge&&Ge[3]||(ne.cssNumber[rr]?"":"px")),!k.clearCloneStyle&&pe===""&&oe.indexOf("background")===0&&(cr[oe]="inherit"),(!ot||!("set"in ot)||(pe=ot.set(q,pe,be))!==void 0)&&(Ut?cr.setProperty(oe,pe):cr[oe]=pe)}else return ot&&"get"in ot&&(Ge=ot.get(q,!1,be))!==void 0?Ge:cr[oe]}},css:function(q,oe,pe,be){var Ge,qe,ot,rr=so(oe),Ut=zi.test(oe);return Ut||(oe=ho(rr)),ot=ne.cssHooks[oe]||ne.cssHooks[rr],ot&&"get"in ot&&(Ge=ot.get(q,!0,pe)),Ge===void 0&&(Ge=No(q,oe,be)),Ge==="normal"&&oe in zc&&(Ge=zc[oe]),pe===""||pe?(qe=parseFloat(Ge),pe===!0||isFinite(qe)?qe||0:Ge):Ge}}),ne.each(["height","width"],function(q,oe){ne.cssHooks[oe]={get:function(pe,be,Ge){if(be)return cs.test(ne.css(pe,"display"))&&(!pe.getClientRects().length||!pe.getBoundingClientRect().width)?Ls(pe,_l,function(){return md(pe,oe,Ge)}):md(pe,oe,Ge)},set:function(pe,be,Ge){var qe,ot=po(pe),rr=!k.scrollboxSize()&&ot.position==="absolute",Ut=rr||Ge,cr=Ut&&ne.css(pe,"boxSizing",!1,ot)==="border-box",Fr=Ge?Ya(pe,oe,Ge,cr,ot):0;return cr&&rr&&(Fr-=Math.ceil(pe["offset"+oe[0].toUpperCase()+oe.slice(1)]-parseFloat(ot[oe])-Ya(pe,oe,"border",!1,ot)-.5)),Fr&&(qe=fr.exec(be))&&(qe[3]||"px")!=="px"&&(pe.style[oe]=be,be=ne.css(pe,oe)),yl(pe,be,Fr)}}}),ne.cssHooks.marginLeft=wo(k.reliableMarginLeft,function(q,oe){if(oe)return(parseFloat(No(q,"marginLeft"))||q.getBoundingClientRect().left-Ls(q,{marginLeft:0},function(){return q.getBoundingClientRect().left}))+"px"}),ne.each({margin:"",padding:"",border:"Width"},function(q,oe){ne.cssHooks[q+oe]={expand:function(pe){for(var be=0,Ge={},qe=typeof pe=="string"?pe.split(" "):[pe];be<4;be++)Ge[q+nr[be]+oe]=qe[be]||qe[be-2]||qe[0];return Ge}},q!=="margin"&&(ne.cssHooks[q+oe].set=yl)}),ne.fn.extend({css:function(q,oe){return mo(this,function(pe,be,Ge){var qe,ot,rr={},Ut=0;if(Array.isArray(be)){for(qe=po(pe),ot=be.length;Ut1)}});function os(q,oe,pe,be,Ge){return new os.prototype.init(q,oe,pe,be,Ge)}ne.Tween=os,os.prototype={constructor:os,init:function(q,oe,pe,be,Ge,qe){this.elem=q,this.prop=pe,this.easing=Ge||ne.easing._default,this.options=oe,this.start=this.now=this.cur(),this.end=be,this.unit=qe||(ne.cssNumber[pe]?"":"px")},cur:function(){var q=os.propHooks[this.prop];return q&&q.get?q.get(this):os.propHooks._default.get(this)},run:function(q){var oe,pe=os.propHooks[this.prop];return this.options.duration?this.pos=oe=ne.easing[this.easing](q,this.options.duration*q,0,1,this.options.duration):this.pos=oe=q,this.now=(this.end-this.start)*oe+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),pe&&pe.set?pe.set(this):os.propHooks._default.set(this),this}},os.prototype.init.prototype=os.prototype,os.propHooks={_default:{get:function(q){var oe;return q.elem.nodeType!==1||q.elem[q.prop]!=null&&q.elem.style[q.prop]==null?q.elem[q.prop]:(oe=ne.css(q.elem,q.prop,""),!oe||oe==="auto"?0:oe)},set:function(q){ne.fx.step[q.prop]?ne.fx.step[q.prop](q):q.elem.nodeType===1&&(ne.cssHooks[q.prop]||q.elem.style[ho(q.prop)]!=null)?ne.style(q.elem,q.prop,q.now+q.unit):q.elem[q.prop]=q.now}}},os.propHooks.scrollTop=os.propHooks.scrollLeft={set:function(q){q.elem.nodeType&&q.elem.parentNode&&(q.elem[q.prop]=q.now)}},ne.easing={linear:function(q){return q},swing:function(q){return .5-Math.cos(q*Math.PI)/2},_default:"swing"},ne.fx=os.prototype.init,ne.fx.step={};var la,mf,Ll=/^(?:toggle|show|hide)$/,Dl=/queueHooks$/;function eu(){mf&&(ee.hidden===!1&&r.requestAnimationFrame?r.requestAnimationFrame(eu):r.setTimeout(eu,ne.fx.interval),ne.fx.tick())}function ca(){return r.setTimeout(function(){la=void 0}),la=Date.now()}function Tc(q,oe){var pe,be=0,Ge={height:q};for(oe=oe?1:0;be<4;be+=2-oe)pe=nr[be],Ge["margin"+pe]=Ge["padding"+pe]=q;return oe&&(Ge.opacity=Ge.width=q),Ge}function Uc(q,oe,pe){for(var be,Ge=(Mu.tweeners[oe]||[]).concat(Mu.tweeners["*"]),qe=0,ot=Ge.length;qe1)},removeAttr:function(q){return this.each(function(){ne.removeAttr(this,q)})}}),ne.extend({attr:function(q,oe,pe){var be,Ge,qe=q.nodeType;if(!(qe===3||qe===8||qe===2)){if(typeof q.getAttribute>"u")return ne.prop(q,oe,pe);if((qe!==1||!ne.isXMLDoc(q))&&(Ge=ne.attrHooks[oe.toLowerCase()]||(ne.expr.match.bool.test(oe)?jh:void 0)),pe!==void 0){if(pe===null){ne.removeAttr(q,oe);return}return Ge&&"set"in Ge&&(be=Ge.set(q,pe,oe))!==void 0?be:(q.setAttribute(oe,pe+""),pe)}return Ge&&"get"in Ge&&(be=Ge.get(q,oe))!==null?be:(be=ne.find.attr(q,oe),be??void 0)}},attrHooks:{type:{set:function(q,oe){if(!k.radioValue&&oe==="radio"&&We(q,"input")){var pe=q.value;return q.setAttribute("type",oe),pe&&(q.value=pe),oe}}}},removeAttr:function(q,oe){var pe,be=0,Ge=oe&&oe.match(mr);if(Ge&&q.nodeType===1)for(;pe=Ge[be++];)q.removeAttribute(pe)}}),jh={set:function(q,oe,pe){return oe===!1?ne.removeAttr(q,pe):q.setAttribute(pe,pe),pe}},ne.each(ne.expr.match.bool.source.match(/\w+/g),function(q,oe){var pe=Ia[oe]||ne.find.attr;Ia[oe]=function(be,Ge,qe){var ot,rr,Ut=Ge.toLowerCase();return qe||(rr=Ia[Ut],Ia[Ut]=ot,ot=pe(be,Ge,qe)!=null?Ut:null,Ia[Ut]=rr),ot}});var Iu=/^(?:input|select|textarea|button)$/i,Sc=/^(?:a|area)$/i;ne.fn.extend({prop:function(q,oe){return mo(this,ne.prop,q,oe,arguments.length>1)},removeProp:function(q){return this.each(function(){delete this[ne.propFix[q]||q]})}}),ne.extend({prop:function(q,oe,pe){var be,Ge,qe=q.nodeType;if(!(qe===3||qe===8||qe===2))return(qe!==1||!ne.isXMLDoc(q))&&(oe=ne.propFix[oe]||oe,Ge=ne.propHooks[oe]),pe!==void 0?Ge&&"set"in Ge&&(be=Ge.set(q,pe,oe))!==void 0?be:q[oe]=pe:Ge&&"get"in Ge&&(be=Ge.get(q,oe))!==null?be:q[oe]},propHooks:{tabIndex:{get:function(q){var oe=ne.find.attr(q,"tabindex");return oe?parseInt(oe,10):Iu.test(q.nodeName)||Sc.test(q.nodeName)&&q.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),k.optSelected||(ne.propHooks.selected={get:function(q){var oe=q.parentNode;return oe&&oe.parentNode&&oe.parentNode.selectedIndex,null},set:function(q){var oe=q.parentNode;oe&&(oe.selectedIndex,oe.parentNode&&oe.parentNode.selectedIndex)}}),ne.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){ne.propFix[this.toLowerCase()]=this});function pc(q){var oe=q.match(mr)||[];return oe.join(" ")}function gf(q){return q.getAttribute&&q.getAttribute("class")||""}function gd(q){return Array.isArray(q)?q:typeof q=="string"?q.match(mr)||[]:[]}ne.fn.extend({addClass:function(q){var oe,pe,be,Ge,qe,ot;return U(q)?this.each(function(rr){ne(this).addClass(q.call(this,rr,gf(this)))}):(oe=gd(q),oe.length?this.each(function(){if(be=gf(this),pe=this.nodeType===1&&" "+pc(be)+" ",pe){for(qe=0;qe-1;)pe=pe.replace(" "+Ge+" "," ");ot=pc(pe),be!==ot&&this.setAttribute("class",ot)}}):this):this.attr("class","")},toggleClass:function(q,oe){var pe,be,Ge,qe,ot=typeof q,rr=ot==="string"||Array.isArray(q);return U(q)?this.each(function(Ut){ne(this).toggleClass(q.call(this,Ut,gf(this),oe),oe)}):typeof oe=="boolean"&&rr?oe?this.addClass(q):this.removeClass(q):(pe=gd(q),this.each(function(){if(rr)for(qe=ne(this),Ge=0;Ge-1)return!0;return!1}});var Gh=/\r/g;ne.fn.extend({val:function(q){var oe,pe,be,Ge=this[0];return arguments.length?(be=U(q),this.each(function(qe){var ot;this.nodeType===1&&(be?ot=q.call(this,qe,ne(this).val()):ot=q,ot==null?ot="":typeof ot=="number"?ot+="":Array.isArray(ot)&&(ot=ne.map(ot,function(rr){return rr==null?"":rr+""})),oe=ne.valHooks[this.type]||ne.valHooks[this.nodeName.toLowerCase()],(!oe||!("set"in oe)||oe.set(this,ot,"value")===void 0)&&(this.value=ot))})):Ge?(oe=ne.valHooks[Ge.type]||ne.valHooks[Ge.nodeName.toLowerCase()],oe&&"get"in oe&&(pe=oe.get(Ge,"value"))!==void 0?pe:(pe=Ge.value,typeof pe=="string"?pe.replace(Gh,""):pe??"")):void 0}}),ne.extend({valHooks:{option:{get:function(q){var oe=ne.find.attr(q,"value");return oe??pc(ne.text(q))}},select:{get:function(q){var oe,pe,be,Ge=q.options,qe=q.selectedIndex,ot=q.type==="select-one",rr=ot?null:[],Ut=ot?qe+1:Ge.length;for(qe<0?be=Ut:be=ot?qe:0;be-1)&&(pe=!0);return pe||(q.selectedIndex=-1),qe}}}}),ne.each(["radio","checkbox"],function(){ne.valHooks[this]={set:function(q,oe){if(Array.isArray(oe))return q.checked=ne.inArray(ne(q).val(),oe)>-1}},k.checkOn||(ne.valHooks[this].get=function(q){return q.getAttribute("value")===null?"on":q.value})});var Hh=r.location,Mo={guid:Date.now()},Sp=/\?/;ne.parseXML=function(q){var oe,pe;if(!q||typeof q!="string")return null;try{oe=new r.DOMParser().parseFromString(q,"text/xml")}catch{}return pe=oe&&oe.getElementsByTagName("parsererror")[0],(!oe||pe)&&ne.error("Invalid XML: "+(pe?ne.map(pe.childNodes,function(be){return be.textContent}).join(` +`):q)),oe};var Qm=/^(?:focusinfocus|focusoutblur)$/,Ep=function(q){q.stopPropagation()};ne.extend(ne.event,{trigger:function(q,oe,pe,be){var Ge,qe,ot,rr,Ut,cr,Fr,li,vr=[pe||ee],Bi=y.call(q,"type")?q.type:q,gn=y.call(q,"namespace")?q.namespace.split("."):[];if(qe=li=ot=pe=pe||ee,!(pe.nodeType===3||pe.nodeType===8)&&!Qm.test(Bi+ne.event.triggered)&&(Bi.indexOf(".")>-1&&(gn=Bi.split("."),Bi=gn.shift(),gn.sort()),Ut=Bi.indexOf(":")<0&&"on"+Bi,q=q[ne.expando]?q:new ne.Event(Bi,typeof q=="object"&&q),q.isTrigger=be?2:3,q.namespace=gn.join("."),q.rnamespace=q.namespace?new RegExp("(^|\\.)"+gn.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,q.result=void 0,q.target||(q.target=pe),oe=oe==null?[q]:ne.makeArray(oe,[q]),Fr=ne.event.special[Bi]||{},!(!be&&Fr.trigger&&Fr.trigger.apply(pe,oe)===!1))){if(!be&&!Fr.noBubble&&!K(pe)){for(rr=Fr.delegateType||Bi,Qm.test(rr+Bi)||(qe=qe.parentNode);qe;qe=qe.parentNode)vr.push(qe),ot=qe;ot===(pe.ownerDocument||ee)&&vr.push(ot.defaultView||ot.parentWindow||r)}for(Ge=0;(qe=vr[Ge++])&&!q.isPropagationStopped();)li=qe,q.type=Ge>1?rr:Fr.bindType||Bi,cr=(Yt.get(qe,"events")||Object.create(null))[q.type]&&Yt.get(qe,"handle"),cr&&cr.apply(qe,oe),cr=Ut&&qe[Ut],cr&&cr.apply&&ps(qe)&&(q.result=cr.apply(qe,oe),q.result===!1&&q.preventDefault());return q.type=Bi,!be&&!q.isDefaultPrevented()&&(!Fr._default||Fr._default.apply(vr.pop(),oe)===!1)&&ps(pe)&&Ut&&U(pe[Bi])&&!K(pe)&&(ot=pe[Ut],ot&&(pe[Ut]=null),ne.event.triggered=Bi,q.isPropagationStopped()&&li.addEventListener(Bi,Ep),pe[Bi](),q.isPropagationStopped()&&li.removeEventListener(Bi,Ep),ne.event.triggered=void 0,ot&&(pe[Ut]=ot)),q.result}},simulate:function(q,oe,pe){var be=ne.extend(new ne.Event,pe,{type:q,isSimulated:!0});ne.event.trigger(be,null,oe)}}),ne.fn.extend({trigger:function(q,oe){return this.each(function(){ne.event.trigger(q,oe,this)})},triggerHandler:function(q,oe){var pe=this[0];if(pe)return ne.event.trigger(q,oe,pe,!0)}});var Ql=/\[\]$/,Jl=/\r?\n/g,_a=/^(?:submit|button|image|reset|file)$/i,Zn=/^(?:input|select|textarea|keygen)/i;function Hs(q,oe,pe,be){var Ge;if(Array.isArray(oe))ne.each(oe,function(qe,ot){pe||Ql.test(q)?be(q,ot):Hs(q+"["+(typeof ot=="object"&&ot!=null?qe:"")+"]",ot,pe,be)});else if(!pe&&me(oe)==="object")for(Ge in oe)Hs(q+"["+Ge+"]",oe[Ge],pe,be);else be(q,oe)}ne.param=function(q,oe){var pe,be=[],Ge=function(qe,ot){var rr=U(ot)?ot():ot;be[be.length]=encodeURIComponent(qe)+"="+encodeURIComponent(rr??"")};if(q==null)return"";if(Array.isArray(q)||q.jquery&&!ne.isPlainObject(q))ne.each(q,function(){Ge(this.name,this.value)});else for(pe in q)Hs(pe,q[pe],oe,Ge);return be.join("&")},ne.fn.extend({serialize:function(){return ne.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var q=ne.prop(this,"elements");return q?ne.makeArray(q):this}).filter(function(){var q=this.type;return this.name&&!ne(this).is(":disabled")&&Zn.test(this.nodeName)&&!_a.test(q)&&(this.checked||!wr.test(q))}).map(function(q,oe){var pe=ne(this).val();return pe==null?null:Array.isArray(pe)?ne.map(pe,function(be){return{name:oe.name,value:be.replace(Jl,`\r +`)}}):{name:oe.name,value:pe.replace(Jl,`\r +`)}}).get()}});var ec=/%20/g,sl=/#.*$/,Ol=/([?&])_=[^&]*/,vl=/^(.*?):[ \t]*([^\r\n]*)$/mg,Pu=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,tu=/^(?:GET|HEAD)$/,La=/^\/\//,xl={},Ws={},Wh="*/".concat("*"),Ec=ee.createElement("a");Ec.href=Hh.href;function Hf(q){return function(oe,pe){typeof oe!="string"&&(pe=oe,oe="*");var be,Ge=0,qe=oe.toLowerCase().match(mr)||[];if(U(pe))for(;be=qe[Ge++];)be[0]==="+"?(be=be.slice(1)||"*",(q[be]=q[be]||[]).unshift(pe)):(q[be]=q[be]||[]).push(pe)}}function qh(q,oe,pe,be){var Ge={},qe=q===Ws;function ot(rr){var Ut;return Ge[rr]=!0,ne.each(q[rr]||[],function(cr,Fr){var li=Fr(oe,pe,be);if(typeof li=="string"&&!qe&&!Ge[li])return oe.dataTypes.unshift(li),ot(li),!1;if(qe)return!(Ut=li)}),Ut}return ot(oe.dataTypes[0])||!Ge["*"]&&ot("*")}function Gd(q,oe){var pe,be,Ge=ne.ajaxSettings.flatOptions||{};for(pe in oe)oe[pe]!==void 0&&((Ge[pe]?q:be||(be={}))[pe]=oe[pe]);return be&&ne.extend(!0,q,be),q}function Cp(q,oe,pe){for(var be,Ge,qe,ot,rr=q.contents,Ut=q.dataTypes;Ut[0]==="*";)Ut.shift(),be===void 0&&(be=q.mimeType||oe.getResponseHeader("Content-Type"));if(be){for(Ge in rr)if(rr[Ge]&&rr[Ge].test(be)){Ut.unshift(Ge);break}}if(Ut[0]in pe)qe=Ut[0];else{for(Ge in pe){if(!Ut[0]||q.converters[Ge+" "+Ut[0]]){qe=Ge;break}ot||(ot=Ge)}qe=qe||ot}if(qe)return qe!==Ut[0]&&Ut.unshift(qe),pe[qe]}function Af(q,oe,pe,be){var Ge,qe,ot,rr,Ut,cr={},Fr=q.dataTypes.slice();if(Fr[1])for(ot in q.converters)cr[ot.toLowerCase()]=q.converters[ot];for(qe=Fr.shift();qe;)if(q.responseFields[qe]&&(pe[q.responseFields[qe]]=oe),!Ut&&be&&q.dataFilter&&(oe=q.dataFilter(oe,q.dataType)),Ut=qe,qe=Fr.shift(),qe){if(qe==="*")qe=Ut;else if(Ut!=="*"&&Ut!==qe){if(ot=cr[Ut+" "+qe]||cr["* "+qe],!ot){for(Ge in cr)if(rr=Ge.split(" "),rr[1]===qe&&(ot=cr[Ut+" "+rr[0]]||cr["* "+rr[0]],ot)){ot===!0?ot=cr[Ge]:cr[Ge]!==!0&&(qe=rr[0],Fr.unshift(rr[1]));break}}if(ot!==!0)if(ot&&q.throws)oe=ot(oe);else try{oe=ot(oe)}catch(li){return{state:"parsererror",error:ot?li:"No conversion from "+Ut+" to "+qe}}}}return{state:"success",data:oe}}ne.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Hh.href,type:"GET",isLocal:Pu.test(Hh.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Wh,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":ne.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(q,oe){return oe?Gd(Gd(q,ne.ajaxSettings),oe):Gd(ne.ajaxSettings,q)},ajaxPrefilter:Hf(xl),ajaxTransport:Hf(Ws),ajax:function(q,oe){typeof q=="object"&&(oe=q,q=void 0),oe=oe||{};var pe,be,Ge,qe,ot,rr,Ut,cr,Fr,li,vr=ne.ajaxSetup({},oe),Bi=vr.context||vr,gn=vr.context&&(Bi.nodeType||Bi.jquery)?ne(Bi):ne.event,gs=ne.Deferred(),Io=ne.Callbacks("once memory"),bl=vr.statusCode||{},al={},Da={},Ka="canceled",Yn={readyState:0,getResponseHeader:function(Hr){var wi;if(Ut){if(!qe)for(qe={};wi=vl.exec(Ge);)qe[wi[1].toLowerCase()+" "]=(qe[wi[1].toLowerCase()+" "]||[]).concat(wi[2]);wi=qe[Hr.toLowerCase()+" "]}return wi==null?null:wi.join(", ")},getAllResponseHeaders:function(){return Ut?Ge:null},setRequestHeader:function(Hr,wi){return Ut==null&&(Hr=Da[Hr.toLowerCase()]=Da[Hr.toLowerCase()]||Hr,al[Hr]=wi),this},overrideMimeType:function(Hr){return Ut==null&&(vr.mimeType=Hr),this},statusCode:function(Hr){var wi;if(Hr)if(Ut)Yn.always(Hr[Yn.status]);else for(wi in Hr)bl[wi]=[bl[wi],Hr[wi]];return this},abort:function(Hr){var wi=Hr||Ka;return pe&&pe.abort(wi),Er(0,wi),this}};if(gs.promise(Yn),vr.url=((q||vr.url||Hh.href)+"").replace(La,Hh.protocol+"//"),vr.type=oe.method||oe.type||vr.method||vr.type,vr.dataTypes=(vr.dataType||"*").toLowerCase().match(mr)||[""],vr.crossDomain==null){rr=ee.createElement("a");try{rr.href=vr.url,rr.href=rr.href,vr.crossDomain=Ec.protocol+"//"+Ec.host!=rr.protocol+"//"+rr.host}catch{vr.crossDomain=!0}}if(vr.data&&vr.processData&&typeof vr.data!="string"&&(vr.data=ne.param(vr.data,vr.traditional)),qh(xl,vr,oe,Yn),Ut)return Yn;cr=ne.event&&vr.global,cr&&ne.active++===0&&ne.event.trigger("ajaxStart"),vr.type=vr.type.toUpperCase(),vr.hasContent=!tu.test(vr.type),be=vr.url.replace(sl,""),vr.hasContent?vr.data&&vr.processData&&(vr.contentType||"").indexOf("application/x-www-form-urlencoded")===0&&(vr.data=vr.data.replace(ec,"+")):(li=vr.url.slice(be.length),vr.data&&(vr.processData||typeof vr.data=="string")&&(be+=(Sp.test(be)?"&":"?")+vr.data,delete vr.data),vr.cache===!1&&(be=be.replace(Ol,"$1"),li=(Sp.test(be)?"&":"?")+"_="+Mo.guid+++li),vr.url=be+li),vr.ifModified&&(ne.lastModified[be]&&Yn.setRequestHeader("If-Modified-Since",ne.lastModified[be]),ne.etag[be]&&Yn.setRequestHeader("If-None-Match",ne.etag[be])),(vr.data&&vr.hasContent&&vr.contentType!==!1||oe.contentType)&&Yn.setRequestHeader("Content-Type",vr.contentType),Yn.setRequestHeader("Accept",vr.dataTypes[0]&&vr.accepts[vr.dataTypes[0]]?vr.accepts[vr.dataTypes[0]]+(vr.dataTypes[0]!=="*"?", "+Wh+"; q=0.01":""):vr.accepts["*"]);for(Fr in vr.headers)Yn.setRequestHeader(Fr,vr.headers[Fr]);if(vr.beforeSend&&(vr.beforeSend.call(Bi,Yn,vr)===!1||Ut))return Yn.abort();if(Ka="abort",Io.add(vr.complete),Yn.done(vr.success),Yn.fail(vr.error),pe=qh(Ws,vr,oe,Yn),!pe)Er(-1,"No Transport");else{if(Yn.readyState=1,cr&&gn.trigger("ajaxSend",[Yn,vr]),Ut)return Yn;vr.async&&vr.timeout>0&&(ot=r.setTimeout(function(){Yn.abort("timeout")},vr.timeout));try{Ut=!1,pe.send(al,Er)}catch(Hr){if(Ut)throw Hr;Er(-1,Hr)}}function Er(Hr,wi,ni,b){var Po,_o,Ze,Is,ao,Ps=wi;Ut||(Ut=!0,ot&&r.clearTimeout(ot),pe=void 0,Ge=b||"",Yn.readyState=Hr>0?4:0,Po=Hr>=200&&Hr<300||Hr===304,ni&&(Is=Cp(vr,Yn,ni)),!Po&&ne.inArray("script",vr.dataTypes)>-1&&ne.inArray("json",vr.dataTypes)<0&&(vr.converters["text script"]=function(){}),Is=Af(vr,Is,Yn,Po),Po?(vr.ifModified&&(ao=Yn.getResponseHeader("Last-Modified"),ao&&(ne.lastModified[be]=ao),ao=Yn.getResponseHeader("etag"),ao&&(ne.etag[be]=ao)),Hr===204||vr.type==="HEAD"?Ps="nocontent":Hr===304?Ps="notmodified":(Ps=Is.state,_o=Is.data,Ze=Is.error,Po=!Ze)):(Ze=Ps,(Hr||!Ps)&&(Ps="error",Hr<0&&(Hr=0))),Yn.status=Hr,Yn.statusText=(wi||Ps)+"",Po?gs.resolveWith(Bi,[_o,Ps,Yn]):gs.rejectWith(Bi,[Yn,Ps,Ze]),Yn.statusCode(bl),bl=void 0,cr&&gn.trigger(Po?"ajaxSuccess":"ajaxError",[Yn,vr,Po?_o:Ze]),Io.fireWith(Bi,[Yn,Ps]),cr&&(gn.trigger("ajaxComplete",[Yn,vr]),--ne.active||ne.event.trigger("ajaxStop")))}return Yn},getJSON:function(q,oe,pe){return ne.get(q,oe,pe,"json")},getScript:function(q,oe){return ne.get(q,void 0,oe,"script")}}),ne.each(["get","post"],function(q,oe){ne[oe]=function(pe,be,Ge,qe){return U(be)&&(qe=qe||Ge,Ge=be,be=void 0),ne.ajax(ne.extend({url:pe,type:oe,dataType:qe,data:be,success:Ge},ne.isPlainObject(pe)&&pe))}}),ne.ajaxPrefilter(function(q){var oe;for(oe in q.headers)oe.toLowerCase()==="content-type"&&(q.contentType=q.headers[oe]||"")}),ne._evalUrl=function(q,oe,pe){return ne.ajax({url:q,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(be){ne.globalEval(be,oe,pe)}})},ne.fn.extend({wrapAll:function(q){var oe;return this[0]&&(U(q)&&(q=q.call(this[0])),oe=ne(q,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&oe.insertBefore(this[0]),oe.map(function(){for(var pe=this;pe.firstElementChild;)pe=pe.firstElementChild;return pe}).append(this)),this},wrapInner:function(q){return U(q)?this.each(function(oe){ne(this).wrapInner(q.call(this,oe))}):this.each(function(){var oe=ne(this),pe=oe.contents();pe.length?pe.wrapAll(q):oe.append(q)})},wrap:function(q){var oe=U(q);return this.each(function(pe){ne(this).wrapAll(oe?q.call(this,pe):q)})},unwrap:function(q){return this.parent(q).not("body").each(function(){ne(this).replaceWith(this.childNodes)}),this}}),ne.expr.pseudos.hidden=function(q){return!ne.expr.pseudos.visible(q)},ne.expr.pseudos.visible=function(q){return!!(q.offsetWidth||q.offsetHeight||q.getClientRects().length)},ne.ajaxSettings.xhr=function(){try{return new r.XMLHttpRequest}catch{}};var ra={0:200,1223:204},mh=ne.ajaxSettings.xhr();k.cors=!!mh&&"withCredentials"in mh,k.ajax=mh=!!mh,ne.ajaxTransport(function(q){var oe,pe;if(k.cors||mh&&!q.crossDomain)return{send:function(be,Ge){var qe,ot=q.xhr();if(ot.open(q.type,q.url,q.async,q.username,q.password),q.xhrFields)for(qe in q.xhrFields)ot[qe]=q.xhrFields[qe];q.mimeType&&ot.overrideMimeType&&ot.overrideMimeType(q.mimeType),!q.crossDomain&&!be["X-Requested-With"]&&(be["X-Requested-With"]="XMLHttpRequest");for(qe in be)ot.setRequestHeader(qe,be[qe]);oe=function(rr){return function(){oe&&(oe=pe=ot.onload=ot.onerror=ot.onabort=ot.ontimeout=ot.onreadystatechange=null,rr==="abort"?ot.abort():rr==="error"?typeof ot.status!="number"?Ge(0,"error"):Ge(ot.status,ot.statusText):Ge(ra[ot.status]||ot.status,ot.statusText,(ot.responseType||"text")!=="text"||typeof ot.responseText!="string"?{binary:ot.response}:{text:ot.responseText},ot.getAllResponseHeaders()))}},ot.onload=oe(),pe=ot.onerror=ot.ontimeout=oe("error"),ot.onabort!==void 0?ot.onabort=pe:ot.onreadystatechange=function(){ot.readyState===4&&r.setTimeout(function(){oe&&pe()})},oe=oe("abort");try{ot.send(q.hasContent&&q.data||null)}catch(rr){if(oe)throw rr}},abort:function(){oe&&oe()}}}),ne.ajaxPrefilter(function(q){q.crossDomain&&(q.contents.script=!1)}),ne.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(q){return ne.globalEval(q),q}}}),ne.ajaxPrefilter("script",function(q){q.cache===void 0&&(q.cache=!1),q.crossDomain&&(q.type="GET")}),ne.ajaxTransport("script",function(q){if(q.crossDomain||q.scriptAttrs){var oe,pe;return{send:function(be,Ge){oe=ne("