From 2e250b83177f60a88dfded569ee3560cce7731e6 Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Thu, 1 Oct 2026 16:15:13 -0400 Subject: [PATCH 01/13] Fix group document blob uploads and Call agent action saves Post the upload status before reading the document so the etag-guarded write in upload_to_blob no longer conflicts with its own status update (every group upload failed with Enhanced Citations on). Allow the server-bound scope_id on Call agent manifests so scoped saves pass validation. Adds regression tests and fix notes; version 0.261.217. --- application/single_app/config.py | 2 +- .../single_app/functions_agent_delegation.py | 2 +- application/single_app/functions_documents.py | 4 +- .../fixes/CALL_AGENT_ACTION_SAVE_FIX.md | 29 ++++ .../GROUP_DOCUMENT_BLOB_UPLOAD_ETAG_FIX.md | 34 ++++ ...est_agent_delegation_scoped_action_save.py | 62 +++++++ .../test_group_document_blob_upload_etag.py | 161 ++++++++++++++++++ 7 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 docs/explanation/fixes/CALL_AGENT_ACTION_SAVE_FIX.md create mode 100644 docs/explanation/fixes/GROUP_DOCUMENT_BLOB_UPLOAD_ETAG_FIX.md create mode 100644 functional_tests/test_agent_delegation_scoped_action_save.py create mode 100644 functional_tests/test_group_document_blob_upload_etag.py diff --git a/application/single_app/config.py b/application/single_app/config.py index 2584dd8ec..32111db14 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -100,7 +100,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.261.216" +VERSION = "0.261.217" IS_DEVELOPMENT = is_development_env_enabled() # Opt-out for deployments where App Service Easy Auth is active but the platform diff --git a/application/single_app/functions_agent_delegation.py b/application/single_app/functions_agent_delegation.py index 7d4d15257..505eda655 100644 --- a/application/single_app/functions_agent_delegation.py +++ b/application/single_app/functions_agent_delegation.py @@ -23,7 +23,7 @@ _MANIFEST_FIELDS = frozenset({ "id", "name", "displayName", "type", "description", "is_enabled", "identity_id", "endpoint", "auth", "metadata", "additionalFields", "user_id", "group_id", - "is_global", "is_group", "scope", "created_at", "created_by", "modified_at", + "is_global", "is_group", "scope", "scope_id", "created_at", "created_by", "modified_at", "modified_by", "updated_at", "last_updated", "_attachments", "_etag", "_rid", "_self", "_ts", "_delegation_scope_type", "_delegation_scope_id", }) diff --git a/application/single_app/functions_documents.py b/application/single_app/functions_documents.py index e1c47a4de..009623ea8 100644 --- a/application/single_app/functions_documents.py +++ b/application/single_app/functions_documents.py @@ -6822,6 +6822,8 @@ def upload_to_blob(temp_file_path, user_id, document_id, blob_filename, update_c return None try: + # The status write changes this record's etag, so it must precede the read that guards the final write. + update_callback(status=f"Uploading {blob_filename} to Blob Storage...") cosmos_container = _get_documents_container(group_id=group_id, public_workspace_id=public_workspace_id) current_document = cosmos_container.read_item(item=document_id, partition_key=document_id) storage_account_container_name = current_document.get("blob_container") or _get_blob_container_name( @@ -6875,8 +6877,6 @@ def upload_to_blob(temp_file_path, user_id, document_id, blob_filename, update_c metadata = {k: v for k, v in metadata.items() if v is not None} - update_callback(status=f"Uploading {blob_filename} to Blob Storage...") - with open(temp_file_path, "rb") as f: blob_client.upload_blob(f, overwrite=True, metadata=metadata) diff --git a/docs/explanation/fixes/CALL_AGENT_ACTION_SAVE_FIX.md b/docs/explanation/fixes/CALL_AGENT_ACTION_SAVE_FIX.md new file mode 100644 index 000000000..2709fee05 --- /dev/null +++ b/docs/explanation/fixes/CALL_AGENT_ACTION_SAVE_FIX.md @@ -0,0 +1,29 @@ +# Call Agent Action Save Fix + +Fixed in version: **0.261.217** + +## Issue + +Saving any Call agent action failed, whether personal, group, or global. Admins saw "Invalid plugin configuration." and the server logged `Validation error adding plugin: Call agent actions cannot override target credentials, connections, or configuration.` As a result, an agent could not delegate to another agent. + +## Root Cause + +Every save binds the action to its collection with `bind_action_origin` before validating it. Since action authorization was hardened, binding adds a server-owned `scope_id` field alongside `scope`, `is_global` and `is_group`. + +`validate_agent_action_manifest` accepts only the fields in its `_MANIFEST_FIELDS` allowlist, so it can reject anything a client might use to override the target's endpoint or credentials. The allowlist included `scope` but not `scope_id`, so every bound Call agent action was rejected as if it carried an override. + +## Technical Details + +Files modified: + +- `application/single_app/functions_agent_delegation.py` +- `functional_tests/test_agent_delegation_scoped_action_save.py` +- `application/single_app/config.py` + +`scope_id` is now on the allowlist. The value is always set by `bind_action_origin` from the authorized collection, so a client cannot choose it. All other extra fields are still rejected. + +## Validation + +`functional_tests/test_agent_delegation_scoped_action_save.py` binds a Call agent action with the real `bind_action_origin` for global, personal, and group scopes and validates it with the real `validate_agent_action_manifest`. It also checks that a bound manifest carrying another field, such as `api_key` or `base_url`, is still rejected. + +Without the fix, all three scope cases fail. diff --git a/docs/explanation/fixes/GROUP_DOCUMENT_BLOB_UPLOAD_ETAG_FIX.md b/docs/explanation/fixes/GROUP_DOCUMENT_BLOB_UPLOAD_ETAG_FIX.md new file mode 100644 index 000000000..dbc5048d5 --- /dev/null +++ b/docs/explanation/fixes/GROUP_DOCUMENT_BLOB_UPLOAD_ETAG_FIX.md @@ -0,0 +1,34 @@ +# Group Document Blob Upload Etag Fix + +Fixed in version: **0.261.217** + +## Issue + +With Enhanced Citations turned on, every document uploaded to a group workspace failed. The document row showed "Document processing failed. Contact an administrator if the problem persists." within a few seconds of the upload. That covered files added in the workspace and Word and Markdown documents saved to a group by an agent or workflow. Personal workspace uploads were not affected. + +The application log showed `Error uploading to Blob Storage.` after the blob itself had been written successfully, with `[ENHANCED_CITATIONS] Blob upload failed.` and error type `ScreeningConflictError`. + +## Root Cause + +`upload_to_blob` reads the document record, uploads the source file, and then writes the blob location back with `_upsert_document_and_sync_access_index`. Since group document projections were fenced against concurrent changes, that write for a group document requires the record's etag to match the one it read. + +Between the read and the write, `upload_to_blob` reported progress with `update_callback(status="Uploading ... to Blob Storage...")`. The callback updates the same Cosmos record, which issues a new etag, so the final write always saw a changed record and raised `ScreeningConflictError`. Personal documents without a screening marker use a plain upsert, so they were unaffected. + +## Technical Details + +Files modified: + +- `application/single_app/functions_documents.py` +- `functional_tests/test_group_document_blob_upload_etag.py` +- `application/single_app/config.py` + +`upload_to_blob` now posts the "Uploading ... to Blob Storage..." status before it reads the record. The read therefore returns the current etag, and the guarded write still rejects a change made by anyone else while the blob uploads. + +## Validation + +`functional_tests/test_group_document_blob_upload_etag.py` runs the real `upload_to_blob` and `_upsert_document_and_sync_access_index` definitions against an etag-tracking container: + +- Group and personal uploads complete after their own status update, and the record gets its blob path. +- A group upload still fails when another writer changes the record during the blob upload. + +Without the fix, the group upload test fails with `ScreeningConflictError`. diff --git a/functional_tests/test_agent_delegation_scoped_action_save.py b/functional_tests/test_agent_delegation_scoped_action_save.py new file mode 100644 index 000000000..f35ec304e --- /dev/null +++ b/functional_tests/test_agent_delegation_scoped_action_save.py @@ -0,0 +1,62 @@ +# test_agent_delegation_scoped_action_save.py +""" +Functional test for saving Call agent actions after scope binding. +Version: 0.261.217 +Implemented in: 0.261.217 + +Every save binds the action to its collection with bind_action_origin, which adds the +server-owned scope and scope_id fields, and only then validates the Call agent manifest. +The manifest allowlist must accept those bound fields, or no personal, group, or global +Call agent action can be saved, while still rejecting any other extra field. +""" + +import importlib.util + +import pytest + +from test_support.agent_delegation import APP_ROOT, delegation_environment, manifest, reference + + +@pytest.fixture +def environment(): + with delegation_environment() as value: + yield value + + +def load_action_manifest(): + spec = importlib.util.spec_from_file_location( + "_tested_scoped_functions_action_manifest", APP_ROOT / "functions_action_manifest.py", + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +@pytest.mark.parametrize("scope_type,scope_id", [ + ("global", "global"), + ("personal", "actor"), + ("group", "group-1"), +]) +def test_scope_bound_call_agent_action_passes_manifest_validation(environment, scope_type, scope_id): + helper, _ = environment + target = reference("target-id", scope_type, scope_id) + bound = load_action_manifest().bind_action_origin(manifest(target), scope_type, scope_id) + assert bound["scope_id"] == scope_id + + result = helper.validate_agent_action_manifest(bound) + + assert result["additionalFields"]["target_agent"] == target + assert result["scope_id"] == scope_id + assert result["endpoint"] == "internal://agent" + assert result["auth"] == {"type": "user"} + + +@pytest.mark.parametrize("field,value", [ + ("api_key", "private-key"), + ("base_url", "https://override.invalid"), +]) +def test_scope_binding_does_not_admit_other_fields(environment, field, value): + helper, _ = environment + bound = load_action_manifest().bind_action_origin(manifest(**{field: value}), "global", "global") + with pytest.raises(ValueError): + helper.validate_agent_action_manifest(bound) diff --git a/functional_tests/test_group_document_blob_upload_etag.py b/functional_tests/test_group_document_blob_upload_etag.py new file mode 100644 index 000000000..165aeec9f --- /dev/null +++ b/functional_tests/test_group_document_blob_upload_etag.py @@ -0,0 +1,161 @@ +# test_group_document_blob_upload_etag.py +""" +Functional test for group document blob uploads with Enhanced Citations. +Version: 0.261.217 +Implemented in: 0.261.217 + +Runs the real upload_to_blob and _upsert_document_and_sync_access_index definitions against +an etag-tracking container. The "Uploading..." status update rewrites the document record, so +the etag-guarded final write must read the record after that update. Before the fix, every +group upload with Enhanced Citations failed with ScreeningConflictError. The guard must still +reject a change made by anyone else while the blob uploads. +""" + +import ast +import logging +import uuid +from pathlib import Path +from types import SimpleNamespace + +import pytest + +APP_ROOT = Path(__file__).resolve().parents[1] / "application" / "single_app" +SCREENING_FIELD = "content_screening" + + +class ScreeningConflictError(Exception): + pass + + +class NotFoundError(Exception): + pass + + +class EtagContainer: + """Cosmos-like container whose every write issues a new etag.""" + + def __init__(self, *items): + self.items = {} + for item in items: + self._store(dict(item)) + + def _store(self, item): + item["_etag"] = uuid.uuid4().hex + self.items[item["id"]] = item + return dict(item) + + def read_item(self, item, partition_key): + if item not in self.items: + raise NotFoundError(item) + return dict(self.items[item]) + + def replace_item(self, item, body, etag=None, match_condition=None): + if etag is not None and self.items[item]["_etag"] != etag: + raise ScreeningConflictError("The record changed since it was read.") + return self._store(dict(body)) + + def upsert_item(self, body): + return self._store(dict(body)) + + def create_item(self, body): + return self._store(dict(body)) + + +class BlobStore: + def __init__(self, on_upload=None): + self.uploads = {} + self.on_upload = on_upload + + def get_blob_client(self, path): + store = self + + class BlobClient: + def upload_blob(self, data, overwrite, metadata): + store.uploads[path] = (data.read(), metadata) + if store.on_upload: + store.on_upload() + + return BlobClient() + + +def load_upload(container, blobs): + namespace = { + "logging": logging, + "current_extraction": lambda document_id: None, + "_get_documents_container": lambda group_id=None, public_workspace_id=None: container, + "_get_blob_container_name": lambda **kwargs: "group-documents" if kwargs.get("group_id") else "user-documents", + "build_current_blob_path": lambda filename, **kwargs: f"{kwargs.get('group_id') or kwargs.get('user_id')}/{filename}", + "_get_document_family_items_from_document": lambda document, **kwargs: [document], + "_document_revision_sort_key": lambda document: document.get("version", 0), + "_archive_previous_document_blob": lambda *args, **kwargs: None, + "_get_blob_service_client": lambda: object(), + "_ensure_blob_container_ready": lambda client, name: blobs, + "CURRENT_ALIAS_BLOB_PATH_MODE": "current_alias", + "log_event": lambda *args, **kwargs: None, + "CosmosResourceNotFoundError": NotFoundError, + "assert_group_document_source_writable": lambda document: None, + "ScreeningConflictError": ScreeningConflictError, + "SCREENING_FIELD": SCREENING_FIELD, + "MatchConditions": SimpleNamespace(IfNotModified="if-not-modified"), + "sync_document_access_index_for_document_fail_open": lambda document, operation: {"success": True}, + "DocumentMutationPropagationError": RuntimeError, + } + names = {"upload_to_blob", "_upsert_document_and_sync_access_index"} + tree = ast.parse((APP_ROOT / "functions_documents.py").read_text(encoding="utf-8")) + nodes = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name in names] + assert {node.name for node in nodes} == names + exec(compile(ast.Module(body=nodes, type_ignores=[]), str(APP_ROOT / "functions_documents.py"), "exec"), namespace) + return namespace["upload_to_blob"] + + +def status_writer(container, document_id): + """Mirror update_document: each status change rewrites the record.""" + + def update_callback(**changes): + current = container.read_item(item=document_id, partition_key=document_id) + current.update(changes) + container.upsert_item(current) + + return update_callback + + +@pytest.fixture +def source_file(tmp_path): + path = tmp_path / "guide.md" + path.write_text("# Guide\n\nExercise content.\n", encoding="utf-8") + return path + + +@pytest.mark.parametrize("scope", ["group", "personal"]) +def test_upload_completes_after_its_own_status_update(source_file, scope): + document = {"id": "document", "user_id": "owner", "version": 1, "file_name": "guide.md", "status": "Queued for processing"} + group_id = "group-1" if scope == "group" else None + if group_id: + document["group_id"] = group_id + container = EtagContainer(document) + blobs = BlobStore() + upload_to_blob = load_upload(container, blobs) + + blob_path = upload_to_blob(str(source_file), "owner", "document", "guide.md", status_writer(container, "document"), group_id=group_id) + + saved = container.items["document"] + assert blob_path == f"{group_id or 'owner'}/guide.md" + assert saved["blob_path"] == blob_path and saved["source_file_available"] is True + assert saved["status"] == "Uploading guide.md to Blob Storage..." + assert blobs.uploads[blob_path][0].startswith(b"# Guide") + + +def test_group_upload_still_rejects_a_concurrent_change(source_file): + document = {"id": "document", "user_id": "owner", "group_id": "group-1", "version": 1, "file_name": "guide.md", "status": "Queued for processing"} + container = EtagContainer(document) + + def foreign_write(): + current = container.read_item(item="document", partition_key="document") + current["title"] = "Changed by another writer" + container.upsert_item(current) + + upload_to_blob = load_upload(container, BlobStore(on_upload=foreign_write)) + with pytest.raises(RuntimeError) as raised: + upload_to_blob(str(source_file), "owner", "document", "guide.md", status_writer(container, "document"), group_id="group-1") + assert isinstance(raised.value.__cause__, ScreeningConflictError) + assert "blob_path" not in container.items["document"] From 3b8251453d340c5950c17fcdfdf20ad31b714e0d Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Thu, 1 Oct 2026 16:59:05 -0400 Subject: [PATCH 02/13] Let scheduled workflow runs act as their owner and add or invite group members without Graph Background runs take the owner's stored display name and email instead of the bare object ID, which also stops them overwriting the stored display name. add_user_to_group accepts a user_id with email or display name, as the REST members route does, and group conversation invites match current group members before any directory lookup. Version 0.261.218. --- application/single_app/config.py | 2 +- .../functions_simplechat_operations.py | 43 ++- .../single_app/functions_workflow_runner.py | 27 +- .../simplechat_plugin.py | 6 +- .../fixes/WORKFLOW_BACKGROUND_IDENTITY_FIX.md | 52 ++++ docs/reference/actions/simplechat.md | 9 + .../test_simplechat_agent_group_output.py | 16 +- .../test_workflow_background_identity.py | 247 ++++++++++++++++++ 8 files changed, 395 insertions(+), 7 deletions(-) create mode 100644 docs/explanation/fixes/WORKFLOW_BACKGROUND_IDENTITY_FIX.md create mode 100644 functional_tests/test_workflow_background_identity.py diff --git a/application/single_app/config.py b/application/single_app/config.py index 32111db14..4ef56809c 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -100,7 +100,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.261.217" +VERSION = "0.261.218" IS_DEVELOPMENT = is_development_env_enabled() # Opt-out for deployments where App Service Easy Auth is active but the platform diff --git a/application/single_app/functions_simplechat_operations.py b/application/single_app/functions_simplechat_operations.py index 05a87844b..6f7afc1f3 100644 --- a/application/single_app/functions_simplechat_operations.py +++ b/application/single_app/functions_simplechat_operations.py @@ -2059,10 +2059,12 @@ def invite_group_conversation_members_for_current_user( if not is_group_collaboration_conversation(conversation_doc): raise LookupError(CONVERSATION_ACCESS_ERROR) + group_id = str(((conversation_doc.get("scope") or {}).get("group_id")) or "").strip() participants_to_add = _build_invited_participants( creator_user=current_user, participants=participants, participant_identifiers=participant_identifiers, + group_members=_group_member_summaries(find_group_by_id(group_id) if group_id else None), ) if not participants_to_add: raise ValueError("At least one participant identifier is required") @@ -2339,6 +2341,7 @@ def _build_invited_participants( creator_user: Dict[str, str], participants: Optional[Iterable[Dict[str, Any]]] = None, participant_identifiers: Any = None, + group_members: Optional[List[Dict[str, str]]] = None, ) -> List[Dict[str, str]]: invited_participants: List[Dict[str, str]] = [] seen_user_ids = {creator_user.get("user_id")} @@ -2354,7 +2357,9 @@ def _build_invited_participants( invited_participants.append(normalized_participant) for raw_identifier in _split_participant_identifiers(participant_identifiers): - resolved_user = resolve_directory_user(user_identifier=raw_identifier) + resolved_user = _match_group_member(group_members, raw_identifier) + if resolved_user is None: + resolved_user = resolve_directory_user(user_identifier=raw_identifier) normalized_participant = normalize_collaboration_user(resolved_user) if not normalized_participant: continue @@ -2367,6 +2372,42 @@ def _build_invited_participants( return invited_participants +def _group_member_summaries(group_doc: Optional[Dict[str, Any]]) -> List[Dict[str, str]]: + """The group's owner and members as collaboration user summaries, once each.""" + if not isinstance(group_doc, dict): + return [] + owner = group_doc.get("owner") if isinstance(group_doc.get("owner"), dict) else {} + raw_members = [ + {"userId": owner.get("id"), "displayName": owner.get("displayName"), "email": owner.get("email")}, + *(group_doc.get("users") or []), + ] + members: Dict[str, Dict[str, str]] = {} + for raw_member in raw_members: + summary = normalize_collaboration_user(raw_member) + if summary: + members.setdefault(summary["user_id"], summary) + return list(members.values()) + + +def _match_group_member(group_members: Optional[List[Dict[str, str]]], identifier: str) -> Optional[Dict[str, str]]: + """The one group member an invite identifier names by user ID, email or display name. + + Matching the group's own records needs no directory lookup, which a scheduled + workflow run cannot make. An identifier that names no member, or several, is left + to the directory. + """ + wanted = str(identifier or "").strip().lower() + matches = [ + member for member in group_members or [] + if wanted in { + str(member.get("user_id") or "").lower(), + str(member.get("email") or "").lower(), + str(member.get("display_name") or "").lower(), + } + ] + return matches[0] if len(matches) == 1 else None + + def _split_participant_identifiers(raw_identifiers: Any) -> List[str]: if raw_identifiers is None: return [] diff --git a/application/single_app/functions_workflow_runner.py b/application/single_app/functions_workflow_runner.py index d8da23154..5159bdb22 100644 --- a/application/single_app/functions_workflow_runner.py +++ b/application/single_app/functions_workflow_runner.py @@ -299,6 +299,7 @@ is_cross_format_compare_one_to_many_enabled, is_tabular_processing_enabled, normalize_model_endpoints, + read_user_settings_snapshot, resolve_model_endpoint_foundry_scope, ) from functions_tabular_parity_contract import ( @@ -5909,6 +5910,27 @@ def _get_workflow_runner_app(): return _workflow_runner_app +def _workflow_owner_identity(user_id): + """Return the owner's stored display name and email for a run without a signed-in session. + + Without them a background run acts as the bare object ID, which then shows as the name + on the groups and messages it creates and is written back over the stored display name. + """ + if not user_id: + return '', '' + try: + # The run acts as this owner, whose id comes from the stored workflow rather than a caller. + user_doc = read_user_settings_snapshot(user_id, allow_cross_user=True) + except AzureError as exc: + log_event( + '[WorkflowRunner] Owner profile unavailable for a background run', + extra={'user_id': user_id, 'error_type': type(exc).__name__}, + level=logging.WARNING, + ) + return '', '' + return str(user_doc.get('display_name') or '').strip(), str(user_doc.get('email') or '').strip() + + @contextmanager def _ensure_execution_context(user_id): created_context = None @@ -5920,13 +5942,14 @@ def _ensure_execution_context(user_id): reuse_existing = session_user_id == str(user_id or '').strip() if not reuse_existing: + display_name, email = _workflow_owner_identity(str(user_id or '').strip()) created_context = _get_workflow_runner_app().test_request_context('/api/internal/workflows/run') created_context.push() session['user'] = { 'oid': user_id, 'roles': ['User'], - 'preferred_username': '', - 'name': user_id, + 'preferred_username': email, + 'name': display_name or user_id, } try: diff --git a/application/single_app/semantic_kernel_plugins/simplechat_plugin.py b/application/single_app/semantic_kernel_plugins/simplechat_plugin.py index ad28380c9..08cfacaf7 100644 --- a/application/single_app/semantic_kernel_plugins/simplechat_plugin.py +++ b/application/single_app/semantic_kernel_plugins/simplechat_plugin.py @@ -476,8 +476,8 @@ def create_personal_collaboration_conversation( ) @plugin_function_logger("SimpleChatPlugin") - # bac-check: ignore - add_group_member_for_current_user validates the current user's group role before mutation. - @kernel_function(description="Add a user directly to a group as the current user. user_identifier can be an email, user principal name, or user ID. If group_id is omitted, the active group is used.") + # bac-check: ignore - add_group_member_for_current_user validates the current user's group role before mutation; user_id only names the member to add, as the REST members route accepts. + @kernel_function(description="Add a user directly to a group as the current user. user_identifier can be an email, user principal name, or user ID. When you know the user's object ID, pass it as user_id with their email or display_name to add them without a directory lookup. If group_id is omitted, the active group is used.") def add_user_to_group( self, user_identifier: str = "", @@ -485,11 +485,13 @@ def add_user_to_group( role: str = "user", display_name: str = "", email: str = "", + user_id: str = "", ) -> dict: return self._execute_operation( "add_group_member", lambda: _with_agent_group_summary(add_group_member_for_current_user( group_id=group_id, + user_id=user_id, user_identifier=user_identifier, email=email, display_name=display_name, diff --git a/docs/explanation/fixes/WORKFLOW_BACKGROUND_IDENTITY_FIX.md b/docs/explanation/fixes/WORKFLOW_BACKGROUND_IDENTITY_FIX.md new file mode 100644 index 000000000..b0ff5eda9 --- /dev/null +++ b/docs/explanation/fixes/WORKFLOW_BACKGROUND_IDENTITY_FIX.md @@ -0,0 +1,52 @@ +# Workflow Background Identity Fix + +Fixed in version: **0.261.218** + +## Issue + +A scheduled workflow run that used the SimpleChat action could not finish group and conversation work that a manual run of the same workflow completed: + +- `add_user_to_group` and `invite_group_conversation_members` failed with `Could not acquire access token`. +- Groups and conversation messages the run created showed the owner's object ID where their name should be. +- The run then wrote that object ID over the owner's stored display name. + +## Root Cause + +A scheduled run has no signed-in session, so `_ensure_execution_context` in the workflow runner creates one. That session held only the owner's object ID: `name` was the ID and `preferred_username` was empty. + +- Everything that reads the current user's name, such as group creation and conversation messages, recorded the ID. +- `get_user_settings` syncs the session's name into the stored profile when the session user is the profile's owner, so the ID replaced the stored display name. +- `resolve_directory_user` reaches Microsoft Graph with the signed-in user's delegated token unless it is given a user ID together with an email or display name. A scheduled run has no such token. The SimpleChat action's `add_user_to_group` could not pass a user ID, and group conversation invites always resolved each identifier through Graph, even for people who were already members of the group. + +A manual "Run now" was unaffected because it reuses the signed-in request's session. + +## Technical Details + +Files modified: + +- `application/single_app/functions_workflow_runner.py` +- `application/single_app/functions_simplechat_operations.py` +- `application/single_app/semantic_kernel_plugins/simplechat_plugin.py` +- `application/single_app/config.py` +- `functional_tests/test_workflow_background_identity.py` (new) +- `functional_tests/test_simplechat_agent_group_output.py` + +Changes: + +- **Run identity.** When the runner creates a session, it reads the owner's stored `display_name` and `email` with the read-only `read_user_settings_snapshot` and uses them for `name` and `preferred_username`. The owner ID comes from the stored workflow, not from a caller. If no profile is stored, or the read fails, the run keeps the previous bare identity; a failed read is logged as a warning. A run inside a request already signed in as the owner reuses that session unchanged, as before. Roles stay `['User']`. +- **Adding members.** `add_user_to_group` accepts `user_id`. Given with an email or display name, it resolves without a directory lookup, exactly as the REST `POST /api/groups//members` route already does. The acting user must still be the group's owner or an admin, which is checked again on the fresh group document. +- **Conversation invites.** `invite_group_conversation_members_for_current_user` first matches each identifier against the group's current owner and members, by user ID, email or display name, ignoring case. An identifier that matches no member, or more than one, falls back to the directory lookup as before. Only current group members can be invited to a group conversation, and that check is unchanged. + +## Validation + +`functional_tests/test_workflow_background_identity.py` runs the application's own definitions from source: + +- A scheduled run's session carries the owner's stored name and email, and the owner's settings are never written. +- An owner with no stored profile keeps the bare identity, and an unavailable settings store falls back to it with a warning. +- A run inside the owner's signed-in request keeps that session. A run inside another member's request acts as the owner. +- A user ID with an email resolves with Graph unavailable. +- Group conversation invites by email, ID or display name reach the right members with Graph unavailable. An ambiguous or non-member identifier is still sent to the directory. + +`functional_tests/test_simplechat_agent_group_output.py` checks that `add_user_to_group` passes `user_id` through to the group operation. + +Without the fix, every new check fails. Restoring the old bare-ID session values alone fails the two identity checks, and removing the cross-user allowance fails the member-started run check. diff --git a/docs/reference/actions/simplechat.md b/docs/reference/actions/simplechat.md index 10b79ce8e..f7709b6c8 100644 --- a/docs/reference/actions/simplechat.md +++ b/docs/reference/actions/simplechat.md @@ -23,6 +23,15 @@ Use it for in-app automation. Do not enable capabilities beyond what the agent s - Uses signed-in user permissions; no external credentials. - Users also need access to the action through workspace or governance policy where applicable. +## In scheduled workflows + +A scheduled workflow run acts as the workflow's owner, with the name and email stored in their SimpleChat profile, but it has no signed-in session to look people up in the directory. Two things follow: + +- To add a group member from a scheduled run, give the person's object ID together with their email or display name. An email alone needs a directory lookup, which a scheduled run cannot make. +- Inviting people to a group conversation works by email, object ID or display name for anyone who is already a member of the group. + +A manual run uses the signed-in session, so neither limit applies to it. + ## Configuration overview Choose the default SimpleChat capabilities exposed to agents. diff --git a/functional_tests/test_simplechat_agent_group_output.py b/functional_tests/test_simplechat_agent_group_output.py index 78c3c9829..9fbabfd28 100644 --- a/functional_tests/test_simplechat_agent_group_output.py +++ b/functional_tests/test_simplechat_agent_group_output.py @@ -167,12 +167,26 @@ def test_add_user_to_group_answers_the_summary_and_keeps_the_member(env): "member": MEMBER, "member_role": "user", "group": {"id": "group-1", "name": "Research", "status": "active"}, } assert env.calls == [("add_group_member_for_current_user", { - "group_id": "", "user_identifier": "nia.newcomer@example.test", "email": "", "display_name": "", + "group_id": "", "user_id": "", "user_identifier": "nia.newcomer@example.test", "email": "", "display_name": "", "role": "user", "default_group_id": "group-1", })] assert_private_values_absent(result) +def test_add_user_to_group_passes_a_known_object_id_through(env): + # Since 0.261.218 a scheduled workflow run, which has no token for a directory lookup, + # adds a member it already knows by object ID, as the REST members route does. + env.answers["add_group_member_for_current_user"] = { + "success": True, "message": "Member added", "group_id": "group-1", "group_name": "Research", + "member": MEMBER, "member_role": "user", "group": STORED_GROUP, + } + env.plugin.add_user_to_group(user_id="newcomer-1", email="nia.newcomer@example.test", display_name="Nia Newcomer") + assert env.calls == [("add_group_member_for_current_user", { + "group_id": "", "user_id": "newcomer-1", "user_identifier": "", "email": "nia.newcomer@example.test", + "display_name": "Nia Newcomer", "role": "user", "default_group_id": "group-1", + })] + + @pytest.mark.parametrize("old_status,message", [ ("active", "Marked group 'Research' as inactive."), ("inactive", "Group 'Research' is already inactive."), diff --git a/functional_tests/test_workflow_background_identity.py b/functional_tests/test_workflow_background_identity.py new file mode 100644 index 000000000..d73649c53 --- /dev/null +++ b/functional_tests/test_workflow_background_identity.py @@ -0,0 +1,247 @@ +# test_workflow_background_identity.py +""" +Functional test for scheduled workflow runs acting with their owner's identity. +Version: 0.261.218 +Implemented in: 0.261.218 + +A scheduled workflow run has no signed-in session, so the runner gave it one that held +only the owner's object ID. Groups and messages the run created showed that ID instead +of a name, and the ID was written over the owner's stored display name. The SimpleChat +action also could not add group members or invite them to a group conversation: both +looked people up in Microsoft Graph with a signed-in token that the run does not have. + +- The run's session carries the owner's stored display name and email, read without + writing the owner's settings. +- A user_id given with an email or display name resolves without a directory lookup, as + the REST members route already relies on. add_user_to_group now accepts one. +- A group conversation invite matches the group's current members by ID, email or + display name before it falls back to the directory. + +Each check runs the application's own definitions from source. Only storage, Microsoft +Graph and the collaboration store are modelled. +""" + +import copy +import logging +import re +import sys +from contextlib import contextmanager +from types import SimpleNamespace +from typing import Any, Dict, Iterable, List, Optional +from unittest.mock import patch + +import pytest +from azure.core.exceptions import AzureError +from azure.cosmos import exceptions as cosmos_exceptions +from flask import Flask, g, has_request_context, session + +from test_support.app_source import refusing_module_stub, run_definitions + + +OWNER_ID = "owner-1" +OWNER_EMAIL = "fiona.owner@example.test" +OWNER_PROFILE = {"id": OWNER_ID, "display_name": "Fiona Owner", "email": OWNER_EMAIL, "settings": {}} +OWNER_SESSION = {"oid": OWNER_ID, "roles": ["User"], "preferred_username": OWNER_EMAIL, "name": "Fiona Owner"} +BARE_SESSION = {"oid": OWNER_ID, "roles": ["User"], "preferred_username": "", "name": OWNER_ID} + +GROUP = { + "id": "group-1", + "name": "Regional Response Team", + "owner": {"id": OWNER_ID, "email": OWNER_EMAIL, "displayName": "Fiona Owner"}, + "users": [ + {"userId": OWNER_ID, "email": OWNER_EMAIL, "displayName": "Fiona Owner"}, + {"userId": "member-1", "email": "ada.admin@example.test", "displayName": "Ada Admin"}, + {"userId": "member-2", "email": "sam.lead@example.test", "displayName": "Sam Lead"}, + {"userId": "member-3", "email": "jo.editor@example.test", "displayName": "Jo Editor"}, + {"userId": "twin-1", "email": "alex.one@example.test", "displayName": "Alex Analyst"}, + {"userId": "twin-2", "email": "alex.two@example.test", "displayName": "Alex Analyst"}, + ], +} +CONVERSATION = { + "id": "conversation-1", + "chat_type": "group_multi_user", + "title": "Incident 1 Coordination", + "scope": {"group_id": "group-1", "group_name": "Regional Response Team"}, +} + + +class UserSettingsContainer: + """Stored user settings, which a background run may read but never write.""" + + def __init__(self, docs): + self.docs = docs + self.error = None + self.reads = [] + + def read_item(self, item, partition_key): + self.reads.append(item) + if self.error is not None: + raise self.error + if item not in self.docs: + raise cosmos_exceptions.CosmosResourceNotFoundError(status_code=404, message="Not found") + return copy.deepcopy(self.docs[item]) + + def upsert_item(self, *args, **kwargs): + raise AssertionError("A background run wrote the owner's settings") + + +def refuse_directory(*args, **kwargs): + # A scheduled run has no signed-in token, so every Graph directory call fails like this. + raise PermissionError("Could not acquire access token") + + +@pytest.fixture +def runner(): + logs = [] + container = UserSettingsContainer({OWNER_ID: copy.deepcopy(OWNER_PROFILE)}) + + def log_event(*args, **kwargs): + logs.append((args, kwargs)) + + settings = run_definitions("functions_settings.py", { + "USER_SETTINGS_REQUEST_CACHE_ATTR", "read_user_settings_snapshot", "_authorize_user_settings_access", + "_get_user_settings_request_cache", "_get_request_cached_user_settings", "_clone_user_settings_doc", + }, { + "copy": copy, "logging": logging, "g": g, "has_request_context": has_request_context, + "exceptions": cosmos_exceptions, "cosmos_user_settings_container": container, "log_event": log_event, + }) + namespace = run_definitions("functions_workflow_runner.py", { + "_workflow_runner_app", "_get_workflow_runner_app", "_workflow_owner_identity", "_ensure_execution_context", + }, { + "Flask": Flask, "SECRET_KEY": "test-secret", "session": session, "has_request_context": has_request_context, + "contextmanager": contextmanager, "AzureError": AzureError, "logging": logging, "log_event": log_event, + "read_user_settings_snapshot": settings["read_user_settings_snapshot"], + }) + # A cross-user settings read inside another member's request would consult the signed-in user. + authentication = refusing_module_stub( + "functions_authentication.py", "functions_authentication", + get_current_user_id=lambda: (session.get("user") or {}).get("oid"), + ) + with patch.dict(sys.modules, {"functions_authentication": authentication}): + yield SimpleNamespace(context=namespace["_ensure_execution_context"], container=container, logs=logs) + + +def session_user_inside(runner, user_id): + with runner.context(user_id): + return dict(session["user"]) + + +def signed_in_request(user): + app = Flask("signed_in_request") + app.secret_key = "test-secret" + context = app.test_request_context("/api/user/workflows/workflow-1/run") + context.push() + session["user"] = dict(user) + return context + + +def test_a_scheduled_run_acts_with_the_owners_stored_name_and_email(runner): + assert not has_request_context() + assert session_user_inside(runner, OWNER_ID) == OWNER_SESSION + assert runner.container.reads == [OWNER_ID] + assert not has_request_context() + + +def test_an_owner_without_a_stored_profile_keeps_the_bare_identity(runner): + runner.container.docs.clear() + assert session_user_inside(runner, OWNER_ID) == BARE_SESSION + + +def test_an_unavailable_settings_store_falls_back_to_the_bare_identity(runner): + runner.container.error = cosmos_exceptions.CosmosHttpResponseError(status_code=503, message="Unavailable") + assert session_user_inside(runner, OWNER_ID) == BARE_SESSION + assert [(args[0], kwargs.get("extra"), kwargs.get("level")) for args, kwargs in runner.logs] == [( + "[WorkflowRunner] Owner profile unavailable for a background run", + {"user_id": OWNER_ID, "error_type": "CosmosHttpResponseError"}, + logging.WARNING, + )] + + +def test_a_run_inside_the_owners_signed_in_request_keeps_that_session(runner): + signed_in = {"oid": OWNER_ID, "roles": ["User", "WorkflowUser"], "preferred_username": OWNER_EMAIL, "name": "Fiona"} + context = signed_in_request(signed_in) + try: + assert session_user_inside(runner, OWNER_ID) == signed_in + assert session["user"] == signed_in + finally: + context.pop() + assert runner.container.reads == [] + + +def test_a_run_inside_another_members_request_acts_as_the_owner(runner): + member = {"oid": "member-2", "roles": ["User"], "preferred_username": "max@example.test", "name": "Max Member"} + context = signed_in_request(member) + try: + assert session_user_inside(runner, OWNER_ID) == OWNER_SESSION + assert session["user"] == member + finally: + context.pop() + + +@pytest.fixture +def operations(): + invited = [] + models = run_definitions("collaboration_models.py", {"_clean_string", "normalize_collaboration_user"}, {}) + + def invite(conversation_id, owner_user_id, participants_to_add): + invited.append((conversation_id, owner_user_id, copy.deepcopy(participants_to_add))) + return copy.deepcopy(CONVERSATION), [ + { + "user_id": participant["user_id"], "user_display_name": participant["display_name"], + "user_email": participant["email"], "membership_status": "pending", + } + for participant in participants_to_add + ] + + namespace = run_definitions("functions_simplechat_operations.py", { + "CONVERSATION_ACCESS_ERROR", "invite_group_conversation_members_for_current_user", + "_build_invited_participants", "_group_member_summaries", "_match_group_member", + "_split_participant_identifiers", "resolve_directory_user", + }, { + "Any": Any, "Dict": Dict, "Iterable": Iterable, "List": List, "Optional": Optional, "re": re, + "normalize_collaboration_user": models["normalize_collaboration_user"], + "_require_collaboration_feature_enabled": lambda: {}, + "_require_current_user_info": lambda: {"userId": OWNER_ID, "email": OWNER_EMAIL, "displayName": "Fiona Owner"}, + "get_collaboration_conversation": lambda conversation_id: copy.deepcopy(CONVERSATION), + "CosmosResourceNotFoundError": cosmos_exceptions.CosmosResourceNotFoundError, + "is_group_collaboration_conversation": lambda doc: (doc or {}).get("chat_type") == "group_multi_user", + "find_group_by_id": lambda group_id: copy.deepcopy(GROUP) if group_id == "group-1" else None, + "invite_personal_collaboration_participants": invite, + "_get_directory_user_by_id": refuse_directory, + "_find_directory_users_by_email": refuse_directory, + "search_directory_users": refuse_directory, + }) + return SimpleNamespace(namespace=namespace, invited=invited) + + +def test_a_known_object_id_with_an_email_needs_no_directory_lookup(operations): + resolved = operations.namespace["resolve_directory_user"]( + user_id="member-1", email="ada.admin@example.test", display_name="Ada Admin", + ) + assert resolved == {"id": "member-1", "displayName": "Ada Admin", "email": "ada.admin@example.test"} + + +def test_invites_match_current_group_members_without_a_directory_lookup(operations): + result = operations.namespace["invite_group_conversation_members_for_current_user"]( + conversation_id="conversation-1", + participant_identifiers=f" ADA.ADMIN@example.test , member-2\nJo Editor; {OWNER_EMAIL}", + ) + assert operations.invited == [("conversation-1", OWNER_ID, [ + {"user_id": "member-1", "display_name": "Ada Admin", "email": "ada.admin@example.test"}, + {"user_id": "member-2", "display_name": "Sam Lead", "email": "sam.lead@example.test"}, + {"user_id": "member-3", "display_name": "Jo Editor", "email": "jo.editor@example.test"}, + ])] + assert [participant["user_id"] for participant in result["invited_participants"]] == ["member-1", "member-2", "member-3"] + + +@pytest.mark.parametrize("identifier", ["Alex Analyst", "outsider@example.test"]) +def test_an_identifier_naming_several_members_or_none_is_left_to_the_directory(operations, identifier): + with pytest.raises(PermissionError, match="Could not acquire access token"): + operations.namespace["invite_group_conversation_members_for_current_user"]( + conversation_id="conversation-1", participant_identifiers=identifier, + ) + assert operations.invited == [] + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-q"])) From bc3ab8599174bd44e5b01bc9ab1a985f984c706c Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Thu, 1 Oct 2026 21:09:03 -0400 Subject: [PATCH 03/13] Treat GPT-6 models as reasoning models for request parameters GPT-6 deployments reject max_tokens. Recognize gpt-6 alongside gpt-5 so response length uses max_completion_tokens and no temperature is sent. Version 0.261.219. --- application/single_app/config.py | 2 +- .../single_app/model_endpoint_clients.py | 3 +- .../GPT6_REASONING_MODEL_PARAMETERS_FIX.md | 46 +++++++++++++ .../test_gpt6_reasoning_model_parameters.py | 64 +++++++++++++++++++ 4 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 docs/explanation/fixes/GPT6_REASONING_MODEL_PARAMETERS_FIX.md create mode 100644 functional_tests/test_gpt6_reasoning_model_parameters.py diff --git a/application/single_app/config.py b/application/single_app/config.py index 4ef56809c..fa1818f47 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -100,7 +100,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.261.218" +VERSION = "0.261.219" IS_DEVELOPMENT = is_development_env_enabled() # Opt-out for deployments where App Service Easy Auth is active but the platform diff --git a/application/single_app/model_endpoint_clients.py b/application/single_app/model_endpoint_clients.py index 6a2f1fafd..f584b124f 100644 --- a/application/single_app/model_endpoint_clients.py +++ b/application/single_app/model_endpoint_clients.py @@ -67,7 +67,7 @@ ANTHROPIC_MODEL_MARKERS = ("claude",) -OPENAI_REASONING_MODEL_PREFIXES = ("gpt-5", "o1", "o3", "o4") +OPENAI_REASONING_MODEL_PREFIXES = ("gpt-5", "gpt-6", "o1", "o3", "o4") MODEL_CONTEXT_MODE_SYSTEM = "system" MODEL_CONTEXT_MODE_FOLD_LATEST_USER = "fold_latest_user" @@ -111,6 +111,7 @@ def is_openai_reasoning_model(self) -> bool: return ( normalized_model_name.startswith(OPENAI_REASONING_MODEL_PREFIXES) or "gpt-5" in normalized_model_name + or "gpt-6" in normalized_model_name ) @property diff --git a/docs/explanation/fixes/GPT6_REASONING_MODEL_PARAMETERS_FIX.md b/docs/explanation/fixes/GPT6_REASONING_MODEL_PARAMETERS_FIX.md new file mode 100644 index 000000000..19c49301d --- /dev/null +++ b/docs/explanation/fixes/GPT6_REASONING_MODEL_PARAMETERS_FIX.md @@ -0,0 +1,46 @@ +# GPT-6 Reasoning Model Parameters Fix + +Fixed in version: **0.261.219** + +## Issue + +Requests to GPT-6 deployments, such as `gpt-6-astra`, failed with a 400 whenever SimpleChat set a response length: + +> Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead. + +Workflows were the most visible case. Once a model's token limits are configured, the workflow budget sets a response length on every model call, so each workflow run that used a GPT-6 agent failed on its first call. Chat failed the same way when a Response Length was set. + +## Root Cause + +`ModelEndpointBehavior.is_openai_reasoning_model` in `model_endpoint_clients.py` recognized only `gpt-5`, `o1`, `o3` and `o4` names. The policy decides two things: + +- whether the response length is sent as `max_completion_tokens` or as `max_tokens`; +- whether a temperature is sent. + +GPT-6 deployments fell through to the non-reasoning defaults, which they reject. + +## Technical Details + +Files modified: + +- `application/single_app/model_endpoint_clients.py` +- `application/single_app/config.py` +- `functional_tests/test_gpt6_reasoning_model_parameters.py` (new) + +`gpt-6` joins `gpt-5` in both checks: as a name prefix, and as a family name anywhere in a deployment or display name, such as `N-gpt-6-astra` or `GPT 6 Astra`. Every caller that uses the shared policy follows it, including: + +- workflow budgets +- chat response length +- prompt variables +- content screening +- orchestration + +## Validation + +`functional_tests/test_gpt6_reasoning_model_parameters.py` runs the real policy and the workflow budget's parameter choice from source. It covers these cases: + +- GPT-6 deployment, alias and display names use `max_completion_tokens`, and are treated as reasoning models. +- GPT-5, o-series and GPT-4 models keep their parameters. +- The workflow budget sets `max_completion_tokens` for a GPT-6 model. + +Without the fix, the 4 GPT-6 cases and both workflow budget cases fail. `test_model_endpoint_protocol_inference.py` and `test_model_reasoning_capability_resolution.py` still pass. diff --git a/functional_tests/test_gpt6_reasoning_model_parameters.py b/functional_tests/test_gpt6_reasoning_model_parameters.py new file mode 100644 index 000000000..d30989ac0 --- /dev/null +++ b/functional_tests/test_gpt6_reasoning_model_parameters.py @@ -0,0 +1,64 @@ +# test_gpt6_reasoning_model_parameters.py +""" +Functional test for GPT-6 models using reasoning-model request parameters. +Version: 0.261.219 +Implemented in: 0.261.219 + +GPT-6 models reject `max_tokens` ("Use 'max_completion_tokens' instead"), as GPT-5 and +o-series models do. SimpleChat recognized only gpt-5, o1, o3 and o4 as reasoning models, +so any request that set a response length for a GPT-6 deployment failed with a 400. A +workflow sets one on every model call once the model's token limits are configured. + +The model behavior policy and the workflow budget's parameter choice are run unchanged +from source. +""" + +import sys +from collections.abc import Mapping + +import pytest + +from test_support.app_source import run_definitions + + +def load_behavior(): + namespace = run_definitions( + "model_endpoint_clients.py", {"OPENAI_REASONING_MODEL_PREFIXES", "ModelEndpointBehavior"}, {"Any": object}, + ) + return namespace["ModelEndpointBehavior"] + + +ModelEndpointBehavior = load_behavior() +response_parameter = run_definitions( + "functions_workflow_context.py", {"_response_parameter"}, + {"ModelEndpointBehavior": ModelEndpointBehavior, "Mapping": Mapping}, +)["_response_parameter"] + + +@pytest.mark.parametrize("name", ["gpt-6-astra", "gpt-6-sol", "N-gpt-6-astra", "astra-deployment GPT 6 Astra"]) +def test_gpt6_models_use_reasoning_parameters(name): + behavior = ModelEndpointBehavior("aoai", name) + assert behavior.is_openai_reasoning_model is True + assert behavior.response_length_parameter == "max_completion_tokens" + + +@pytest.mark.parametrize("name,parameter", [ + ("gpt-5.6-luna", "max_completion_tokens"), + ("o4-mini", "max_completion_tokens"), + ("gpt-4o", "max_tokens"), + ("gpt-4.1", "max_tokens"), +]) +def test_other_models_keep_their_parameter(name, parameter): + assert ModelEndpointBehavior("aoai", name).response_length_parameter == parameter + + +@pytest.mark.parametrize("audit,model", [ + ({"model_id": "gpt-6-astra"}, {}), + ({}, {"modelName": "gpt-6-astra", "deploymentName": "gpt-6-astra"}), +]) +def test_workflow_budget_sets_max_completion_tokens_for_gpt6(audit, model): + assert response_parameter(audit, model, "aoai") == "max_completion_tokens" + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-q"])) From a27cee180a3263ad640828bdc4a5b9e9a7b903b3 Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Thu, 1 Oct 2026 22:26:38 -0400 Subject: [PATCH 04/13] Record verified GPT-6 Astra limits, reasoning efforts and Azure tool restriction in the model catalog gpt-6-astra becomes a token-evidenced record: 1,050,000 context, 922,000 input and 128,000 output for OpenAI and Azure, total-generation accounting, and a Chat Completions reasoning policy of low, medium, high and xhigh. Azure Chat Completions rejects its function tools unless reasoning_effort is none, which the model rejects; toolReasoningEfforts can't express that, so it's recorded in notes. Refs #1606. Version 0.261.220. --- application/single_app/config.py | 2 +- .../static/json/model_capabilities.json | 154 ++++++++++++++- .../fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md | 74 +++++++ .../test_gpt6_astra_catalog_record.py | 180 ++++++++++++++++++ .../test_model_catalog_rebase_integration.py | 12 +- 5 files changed, 410 insertions(+), 12 deletions(-) create mode 100644 docs/explanation/fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md create mode 100644 functional_tests/test_gpt6_astra_catalog_record.py diff --git a/application/single_app/config.py b/application/single_app/config.py index fa1818f47..8f3ca0aa3 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -100,7 +100,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.261.219" +VERSION = "0.261.220" IS_DEVELOPMENT = is_development_env_enabled() # Opt-out for deployments where App Service Easy Auth is active but the platform diff --git a/application/single_app/static/json/model_capabilities.json b/application/single_app/static/json/model_capabilities.json index 3dce7f655..8ecdd4531 100644 --- a/application/single_app/static/json/model_capabilities.json +++ b/application/single_app/static/json/model_capabilities.json @@ -1,7 +1,7 @@ { "$schema": "https://simplechat.local/schemas/model-capabilities.schema.json", "schemaVersion": 4, - "lastUpdated": "2026-09-21", + "lastUpdated": "2026-10-01", "description": "SimpleChat model capability, reasoning-policy, embedding-policy, and token-capacity catalog. Published chat/generation limits retain independently verified evidence and hosting profiles. Embedding input, dimension, and batch policies are separate operation contracts and never supply a chat-token budget.", "capabilityFields": { "processesText": "Accepts text input.", @@ -51,7 +51,8 @@ "OpenAI model pages describe api.openai.com, not Azure deployment readiness. imageProfiles separates direct OpenAI, Azure OpenAI, and Foundry image operations. The application hosting cloud never establishes endpoint capabilities or availability.", "Azure documents Responses image generation with a separate GPT Image deployment. SimpleChat deliberately offers only dedicated image models on Azure/Foundry; the conditional Azure Responses integration is not enabled by publisher imageGenerationTool flags or administrator overrides.", "Government image-model availability is not established by the reviewed Government model table. Unknown is not unavailable, and a Government-hosted app may use an explicitly configured commercial endpoint without relabeling that endpoint as Government.", - "Image operation profiles describe the implemented single-image workflow. MAI and FLUX support source-image edits where stated, but their reviewed APIs do not document uploaded masks. Masking is not inferred from vision, tool calling, or inpainting prose." + "Image operation profiles describe the implemented single-image workflow. MAI and FLUX support source-image edits where stated, but their reviewed APIs do not document uploaded masks. Masking is not inferred from vision, tool calling, or inpainting prose.", + "GPT-6 Astra token capacities and reasoning efforts were reviewed on 2026-10-01, separately from the 2026-09-19 audit of the original 75 records. Azure Chat Completions rejected function tools for gpt-6-astra (2026-09-03) with the default and low efforts and rejected none. toolReasoningEfforts can't express a model with no tool-capable effort, so that observation is recorded in notes only." ], "sources": [ { @@ -1354,6 +1355,27 @@ "title": "GPT-6 Astra model and supported Responses tools", "url": "https://developers.openai.com/api/docs/models/gpt-6-astra", "verifiedAt": "2026-09-16" + }, + { + "id": "openai-spec-gpt-6-astra", + "provider": "openai", + "title": "OpenAI gpt-6-astra exact model specification and reasoning efforts", + "url": "https://developers.openai.com/api/docs/models/gpt-6-astra", + "verifiedAt": "2026-10-01" + }, + { + "id": "azure-spec-gpt-6", + "provider": "azure", + "title": "Azure OpenAI exact model/version specifications: gpt-6", + "url": "https://learn.microsoft.com/en-us/azure/foundry/foundry-models/concepts/models-sold-directly-by-azure#gpt-6", + "verifiedAt": "2026-10-01" + }, + { + "id": "gpt-6-astra-deployed-contract", + "provider": "microsoft", + "title": "Observed GPT-6 Astra (2026-09-03) Azure Chat Completions rejections on 2026-10-01: none rejected with low, medium, high and xhigh listed as supported; function tools rejected with the default and low efforts", + "url": "https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/reasoning?view=foundry-classic", + "verifiedAt": "2026-10-01" } ], "models": [ @@ -11004,18 +11026,136 @@ "generatesText": true, "processesImages": true, "generatesImages": false, - "imageGenerationTool": true, - "toolCalling": true + "processesAudio": false, + "generatesAudio": false, + "processesVideo": false, + "generatesVideo": false, + "processesBinaryFiles": true, + "optimizedForCoding": true, + "toolCalling": true, + "structuredOutput": true, + "supportsStreaming": true, + "reasoning": true, + "imageGenerationTool": true }, "imageProfiles": { "openai": "openai-responses" }, "notes": [ - "Image generation is a direct OpenAI Responses tool, not native image output or an Azure image deployment." + "Most capable GPT-6 tier; text and image input with text output.", + "Image generation is a direct OpenAI Responses tool, not native image output or an Azure image deployment.", + "OpenAI lists reasoning efforts low, medium, high, xhigh and max. none isn't supported, and Responses-only max is excluded from this Chat Completions policy.", + "Azure Chat Completions rejects function tools for this model unless reasoning_effort is none, which the model doesn't accept, so local agents with actions fail on it. See the azure profile note." ], "sourceIds": [ - "openai-gpt6-astra" - ] + "openai-gpt6-astra", + "openai-spec-gpt-6-astra", + "openai-reasoning", + "azure-spec-gpt-6", + "azure-reasoning", + "gpt-6-astra-deployed-contract" + ], + "contextWindow": 1050000, + "inputTokenLimit": 922000, + "outputTokenLimit": 128000, + "verifiedAliases": [], + "tokenLimitsApplicability": "text", + "outputTokenAccounting": "total_generation", + "tokenLimitEvidence": { + "contextWindow": { + "status": "verified", + "sourceIds": [ + "openai-spec-gpt-6-astra" + ], + "verifiedAt": "2026-10-01", + "note": "Direct OpenAI explicitly documents 1,050,000 tokens for the shared context of gpt-6-astra; this is a model capacity, not a requested response length." + }, + "inputTokenLimit": { + "status": "verified", + "sourceIds": [ + "openai-spec-gpt-6-astra" + ], + "verifiedAt": "2026-10-01", + "note": "Direct OpenAI explicitly documents 922,000 tokens for the independent maximum input of gpt-6-astra; this is a model capacity, not a requested response length." + }, + "outputTokenLimit": { + "status": "verified", + "sourceIds": [ + "openai-spec-gpt-6-astra" + ], + "verifiedAt": "2026-10-01", + "note": "Direct OpenAI explicitly documents 128,000 tokens for the maximum generation of gpt-6-astra; this is a model capacity, not a requested response length." + }, + "outputTokenAccounting": { + "status": "verified", + "sourceIds": [ + "openai-reasoning" + ], + "verifiedAt": "2026-10-01", + "note": "gpt-6-astra documents reasoning token support, and native OpenAI generation ceilings include reasoning tokens as well as visible output. Do not reserve reasoning twice or treat visible response length as total generation." + } + }, + "tokenLimitProfiles": [ + { + "id": "azure", + "provider": "azure", + "contextWindow": 1050000, + "inputTokenLimit": 922000, + "outputTokenLimit": 128000, + "outputTokenAccounting": "total_generation", + "tokenLimitEvidence": { + "contextWindow": { + "status": "verified", + "sourceIds": [ + "azure-spec-gpt-6" + ], + "verifiedAt": "2026-10-01", + "note": "Azure explicitly documents 1,050,000 tokens for the shared context of gpt-6-astra (2026-09-03); this is a model capacity, not a requested response length." + }, + "inputTokenLimit": { + "status": "verified", + "sourceIds": [ + "azure-spec-gpt-6" + ], + "verifiedAt": "2026-10-01", + "note": "Azure explicitly documents 922,000 tokens for the independent maximum input of gpt-6-astra (2026-09-03); this is a model capacity, not a requested response length." + }, + "outputTokenLimit": { + "status": "verified", + "sourceIds": [ + "azure-spec-gpt-6" + ], + "verifiedAt": "2026-10-01", + "note": "Azure explicitly documents 128,000 tokens for the maximum generation of gpt-6-astra (2026-09-03); this is a model capacity, not a requested response length." + }, + "outputTokenAccounting": { + "status": "verified", + "sourceIds": [ + "azure-reasoning" + ], + "verifiedAt": "2026-10-01", + "note": "Azure generation limits include reasoning and visible output; the actual request cap is separate from maximum model capacity." + } + }, + "notes": [ + "Azure Chat Completions rejected function tools for gpt-6-astra (2026-09-03) with the default and low reasoning efforts, and rejected reasoning_effort none. The provider error directs tool use to the Responses API, which local agents don't use. toolReasoningEfforts can't express a model with no tool-capable effort. Do not silently change the selected protocol or reasoning policy." + ] + } + ], + "reasoningPolicy": { + "status": "supported", + "efforts": [ + "low", + "medium", + "high", + "xhigh" + ], + "default_effort": "low", + "sourceIds": [ + "openai-spec-gpt-6-astra", + "gpt-6-astra-deployed-contract" + ] + } }, { "id": "gpt-image-2.5-flare", diff --git a/docs/explanation/fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md b/docs/explanation/fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md new file mode 100644 index 000000000..2ae46612c --- /dev/null +++ b/docs/explanation/fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md @@ -0,0 +1,74 @@ +# GPT-6 Astra Model Catalog Fix + +Fixed in version: **0.261.220** + +Related issue: #1606 + +## Issue + +The model catalog described `gpt-6-astra` only through its image-generation tool. It had no token limits, reasoning policy or note about how the model handles function tools. That caused three problems: + +- **Token limits had to be entered by hand.** Every deployment needed limits set in Model Endpoints before it could use file evidence or a workflow token budget. Without them, workflows fell back to a small compatibility budget. +- **No reasoning policy.** With no policy, chat sent no reasoning effort for this model, so the model always used its default. Agents send their saved effort as is, so an agent saved with `none` was rejected: "Supported values are: 'low', 'medium', 'high', and 'xhigh'." +- **Nothing warned about function tools.** Azure Chat Completions rejects function tools for `gpt-6-astra` unless `reasoning_effort` is `none`, which the model also rejects. The catalog gave no sign of this, so agents with actions on `gpt-6-astra` failed only when they ran. + +## Root Cause + +`gpt-6-astra` was added as an operation-only record. Operation-only records can't carry token limits, and this one had no reasoning policy. + +The tool restriction isn't in the documentation either: + +- Azure's model table lists Chat Completions and "Functions, tools, and parallel tool calling" for GPT-6. It doesn't have the GPT-5.6 note that Chat Completions tools require `reasoning_effort: none`. +- OpenAI lists reasoning efforts low, medium, high, xhigh and max for `gpt-6-astra`. `none` isn't among them. + +## Technical Details + +Files modified: + +- `application/single_app/static/json/model_capabilities.json` +- `application/single_app/config.py` +- `functional_tests/test_model_catalog_rebase_integration.py` +- `functional_tests/test_gpt6_astra_catalog_record.py` (new) + +`gpt-6-astra` is now a token-evidenced record, reviewed on 2026-10-01 and kept separate from the 2026-09-19 audit of the original 75 records. It records: + +| Field | Value | Source | +|---|---|---| +| Shared context | 1,050,000 | OpenAI `gpt-6-astra` specification; Azure GPT-6 table for the `azure` profile | +| Maximum input | 922,000 | Same | +| Maximum output | 128,000 | Same | +| Output accounting | Total generation (reasoning plus visible output) | OpenAI and Azure reasoning guides | +| Reasoning efforts | low, medium, high, xhigh; fallback low | OpenAI specification and the observed Azure rejection | + +The capability flags match the documented features: text and image input with text output, streaming, structured outputs, function calling and reasoning. These flags are identical to the published feature list for `gpt-5.6-sol`. `max` isn't in the reasoning policy because SimpleChat's policy covers Chat Completions only. + +There are three new sources: `openai-spec-gpt-6-astra`, `azure-spec-gpt-6` and `gpt-6-astra-deployed-contract`. The last one records the Azure rejections observed on 2026-10-01 against model version 2026-09-03: + +| Reasoning effort | With function tools on Chat Completions | +|---|---| +| Not set (model default) | Rejected: "Function tools with reasoning_effort are not supported for gpt-6-astra in /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to 'none'." | +| `low` | Rejected with the same error | +| `none` | Rejected: "'reasoning_effort' does not support 'none' with this model" | + +`toolReasoningEfforts` lists the efforts that may be used with tools. It can't express a model where no effort works, so the record doesn't declare one. The restriction is recorded in the model notes, the `azure` profile note and a coverage note. + +## Behavior After the Fix + +- Budgets for `gpt-6-astra` resolve to the verified limits for Azure and OpenAI, so file evidence and workflow budgets no longer need manual limits. Limits set in Model Endpoints still take precedence. +- Chat now sends the selected reasoning effort. If `none` or another unsupported effort is selected, chat sends `low` instead. Agents still send their saved effort as is, so don't save a `gpt-6-astra` agent with None. +- `gpt-6`, `gpt-6-sol` and `gpt-6-luna` don't inherit anything from this record. Suffixed deployment names such as `gpt-6-astra-eastus` get the reasoning policy, which matches by prefix, but not the token limits, which need the exact model name. +- Agents with actions on `gpt-6-astra` still fail on Azure Chat Completions. Local agents can't use the Responses API yet, and the runtime doesn't yet fail early with a clear message. #1606 tracks both. For agents with actions, use `gpt-6-sol` with Reasoning Effort set to None. + +## Validation + +`functional_tests/test_gpt6_astra_catalog_record.py` checks these cases: + +- The catalog passes schema validation and the cross-record integrity checks. +- The OpenAI and Azure limits each cite their exact specification. +- No tool-effort constraint is invented. +- Budgets resolve for both providers and both protocols. +- The reasoning policy excludes `none` and corrects it to `low`. +- The capability flags keep the Responses image-generation tool. +- Sibling GPT-6 names don't inherit the record, and suffixed names get the reasoning policy but no token limits. + +`test_model_catalog_rebase_integration.py` now treats `gpt-6-astra` as a token-evidenced record with a reasoning policy, and registers the 2026-10-01 source review. The catalog, capability, reasoning, budget, image-generation and AI connection test files all pass. Four files that need `tiktoken` or live Azure credentials to import were skipped. diff --git a/functional_tests/test_gpt6_astra_catalog_record.py b/functional_tests/test_gpt6_astra_catalog_record.py new file mode 100644 index 000000000..74bcfc8ca --- /dev/null +++ b/functional_tests/test_gpt6_astra_catalog_record.py @@ -0,0 +1,180 @@ +# test_gpt6_astra_catalog_record.py +""" +Functional test for the GPT-6 Astra model catalog record. +Version: 0.261.220 +Implemented in: 0.261.220 + +Refs #1606. GPT-6 Astra moves from an operation-only record to a token-evidenced +record: 1,050,000 shared context, 922,000 input and 128,000 output for direct +OpenAI and Azure, total-generation accounting, and a Chat Completions reasoning +policy of low, medium, high and xhigh. Azure Chat Completions rejects function +tools for this model unless reasoning_effort is none, which the model rejects. +toolReasoningEfforts can't express a model with no tool-capable effort, so the +record must not declare one, and the restriction is recorded in notes instead. +No test fetches documentation, initializes application services or calls a model. +""" + +import json +import unittest +from itertools import product + +from test_model_capability_catalog_resolution import ( + CATALOG_PATH, + capabilities, + load_catalog_schema_validator, +) +from test_model_catalog_token_evidence import validate_catalog_integrity + + +ASTRA = "gpt-6-astra" +LIMITS = (1050000, 922000, 128000) +CAPACITY_FIELDS = ("contextWindow", "inputTokenLimit", "outputTokenLimit") +REVIEWED_AT = "2026-10-01" +EFFORTS = ["low", "medium", "high", "xhigh"] + + +class TestGpt6AstraCatalogRecord(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.catalog = json.loads(CATALOG_PATH.read_text(encoding="utf-8")) + cls.models = {record["id"]: record for record in cls.catalog["models"]} + cls.sources = {record["id"]: record for record in cls.catalog["sources"]} + cls.record = cls.models[ASTRA] + + def setUp(self): + capabilities.reset_model_capability_catalog_cache() + self.addCleanup(capabilities.reset_model_capability_catalog_cache) + + def azure_profile(self): + profiles = [ + profile for profile in self.record["tokenLimitProfiles"] + if profile["provider"] == "azure" + ] + self.assertEqual(len(profiles), 1) + self.assertNotIn("protocol", profiles[0]) + self.assertNotIn("modelVersions", profiles[0]) + return profiles[0] + + def test_catalog_validates_with_the_new_record(self): + load_catalog_schema_validator().validate(self.catalog) + validate_catalog_integrity(self.catalog) + self.assertGreaterEqual(self.catalog["lastUpdated"], REVIEWED_AT) + self.assertTrue(any( + ASTRA in note and "toolReasoningEfforts" in note + for note in self.catalog["coverageNotes"] + )) + + def test_native_limits_cite_the_exact_openai_specification(self): + record = self.record + self.assertEqual(tuple(record[field] for field in CAPACITY_FIELDS), LIMITS) + self.assertEqual(record["outputTokenAccounting"], "total_generation") + self.assertEqual(record["tokenLimitsApplicability"], "text") + self.assertEqual(record["verifiedAliases"], []) + source = self.sources["openai-spec-gpt-6-astra"] + self.assertEqual(source["provider"], "openai") + self.assertTrue(source["url"].endswith("/api/docs/models/gpt-6-astra")) + self.assertEqual(source["verifiedAt"], REVIEWED_AT) + for field in CAPACITY_FIELDS: + with self.subTest(field=field): + evidence = record["tokenLimitEvidence"][field] + self.assertEqual(evidence["status"], "verified") + self.assertEqual(evidence["sourceIds"], ["openai-spec-gpt-6-astra"]) + self.assertEqual(evidence["verifiedAt"], REVIEWED_AT) + self.assertIn(ASTRA, evidence["note"]) + + def test_azure_profile_limits_cite_the_gpt6_specification(self): + profile = self.azure_profile() + self.assertEqual(tuple(profile[field] for field in CAPACITY_FIELDS), LIMITS) + self.assertEqual(profile["outputTokenAccounting"], "total_generation") + source = self.sources["azure-spec-gpt-6"] + self.assertEqual(source["provider"], "azure") + self.assertTrue(source["url"].endswith("#gpt-6")) + for field in CAPACITY_FIELDS: + with self.subTest(field=field): + evidence = profile["tokenLimitEvidence"][field] + self.assertEqual(evidence["status"], "verified") + self.assertEqual(evidence["sourceIds"], ["azure-spec-gpt-6"]) + self.assertIn("2026-09-03", evidence["note"]) + + def test_tool_restriction_is_recorded_without_inventing_a_tool_effort(self): + self.assertNotIn("toolReasoningEfforts", self.record) + for profile in self.record["tokenLimitProfiles"]: + self.assertNotIn("toolReasoningEfforts", profile) + profile_notes = " ".join(self.azure_profile()["notes"]) + for phrase in ("Chat Completions", "function tools", "none", "Responses API"): + with self.subTest(phrase=phrase): + self.assertIn(phrase, profile_notes) + self.assertTrue(any( + "local agents with actions" in note for note in self.record["notes"] + )) + self.assertIn("gpt-6-astra-deployed-contract", self.record["sourceIds"]) + + def test_budgets_resolve_the_verified_limits_for_both_hosts(self): + for provider, protocol in product(("azure", "openai"), ("chat_completions", "responses")): + with self.subTest(provider=provider, protocol=protocol): + budget = capabilities.resolve_model_token_budget( + {"modelName": ASTRA}, + provider=provider, + protocol=protocol, + request_output_limit=4096, + ) + self.assertEqual(budget.model_id, ASTRA) + self.assertEqual( + (budget.context_window, budget.input_limit, budget.output_limit), LIMITS + ) + self.assertEqual(budget.output_accounting, "total_generation") + self.assertEqual(budget.tool_reasoning_efforts, ()) + self.assertEqual(budget.remaining_input(1000), 922000 - 1000) + + def test_reasoning_policy_excludes_none_and_responses_only_max(self): + policy = capabilities.resolve_model_reasoning_policy(ASTRA) + self.assertEqual(policy, {"status": "supported", "efforts": EFFORTS, "default_effort": "low"}) + self.assertIn("gpt-6-astra-deployed-contract", self.record["reasoningPolicy"]["sourceIds"]) + for effort in EFFORTS: + with self.subTest(effort=effort): + resolution = capabilities.resolve_model_reasoning_effort(ASTRA, effort) + self.assertEqual(resolution["effective_effort"], effort) + self.assertIsNone(resolution["adjustment_reason"]) + resolution = capabilities.resolve_model_reasoning_effort(ASTRA, "none") + self.assertEqual(resolution["effective_effort"], "low") + self.assertEqual(resolution["adjustment_reason"], "reasoning_effort_unsupported") + + def test_documented_capabilities_keep_the_responses_image_tool(self): + flags = capabilities.get_model_catalog_capabilities(ASTRA) + for name, expected in ( + ("processesText", True), ("generatesText", True), ("processesImages", True), + ("generatesImages", False), ("processesAudio", False), ("generatesAudio", False), + ("processesVideo", False), ("generatesVideo", False), ("toolCalling", True), + ("structuredOutput", True), ("supportsStreaming", True), ("reasoning", True), + ("imageGenerationTool", True), + ): + with self.subTest(capability=name): + self.assertIs(flags[name], expected) + self.assertEqual(flags["imageProfiles"], {"openai": "openai-responses"}) + self.assertTrue(capabilities.is_reasoning_model(ASTRA)) + + def test_siblings_inherit_nothing_and_suffixed_names_get_no_token_limits(self): + for name in ("gpt-6", "gpt-6-sol", "gpt-6-luna"): + with self.subTest(model=name): + self.assertEqual( + capabilities.resolve_model_reasoning_policy(name)["status"], "unknown" + ) + for name in ("gpt-6-astra-2026-09-03", "gpt-6-astra-eastus"): + with self.subTest(model=name): + self.assertEqual( + capabilities.resolve_model_reasoning_policy(name)["efforts"], EFFORTS + ) + for name in ("gpt-6", "gpt-6-sol", "gpt-6-astra-2026-09-03", "gpt-6-astra-eastus"): + with self.subTest(model=name): + budget = capabilities.resolve_model_token_budget( + {"modelName": name}, provider="azure", request_output_limit=4096, + ) + self.assertEqual( + (budget.context_window, budget.input_limit, budget.output_limit), + (None, None, None), + ) + self.assertEqual(budget.output_accounting, "unknown") + + +if __name__ == "__main__": + unittest.main() diff --git a/functional_tests/test_model_catalog_rebase_integration.py b/functional_tests/test_model_catalog_rebase_integration.py index 34d0c505b..3528967bb 100644 --- a/functional_tests/test_model_catalog_rebase_integration.py +++ b/functional_tests/test_model_catalog_rebase_integration.py @@ -1,8 +1,9 @@ # test_model_catalog_rebase_integration.py """ Offline regression tests for audited token and React V2 metadata coexistence. -Version: 0.261.122 +Version: 0.261.220 Implemented in: 0.261.122 +GPT-6 Astra token-evidenced record: 0.261.220 Protect audited, qualitative-capability, reasoning-only, and embedding-policy records, provider-qualified image profiles, historical source reviews, and their @@ -35,7 +36,7 @@ OPERATION_ONLY_IDS = { "gpt-4o", "gpt-4o-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o1", "o3", "o3-mini", "o4-mini", "gpt-image-2", "gpt-image-1.5", - "gpt-image-1", "gpt-image-1-mini", "dall-e-3", "gpt-6-astra", + "gpt-image-1", "gpt-image-1-mini", "dall-e-3", "gpt-image-2.5-flare", "gpt-image-2.5-sunburst", "MAI-Image-2.5", "MAI-Image-2.5-Flash", "MAI-Image-2.5-Pro", "MAI-Image-2.6", "MAI-Image-2.6-Flash", @@ -50,7 +51,7 @@ "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5", "gpt-5-pro", "gpt-5-codex", "gpt-5-mini", "gpt-5-nano", - "gpt-4o", "gpt-4.1", "o1", "o3", "o3-mini", "o4-mini", + "gpt-4o", "gpt-4.1", "o1", "o3", "o3-mini", "o4-mini", "gpt-6-astra", } OPENAI_EMBEDDING_POLICY = { "max_input_tokens": 8192, @@ -154,6 +155,9 @@ "image-profiles-mai", "image-profiles-flux", "image-profiles-government", "openai-gpt6-astra", }, + "2026-10-01": { + "openai-spec-gpt-6-astra", "azure-spec-gpt-6", "gpt-6-astra-deployed-contract", + }, } @@ -389,7 +393,7 @@ def test_historical_metadata_reviews_are_not_relabelled_as_token_audits(self): def test_metadata_only_variants_reject_token_fields(self): audited = self.models["gpt-5.6-sol"] for model_id, field in product( - ("gpt-4", "gpt-4o", "gpt-4.1-mini", "gpt-6-astra", "gpt-image-2", + ("gpt-4", "gpt-4o", "gpt-4.1-mini", "o3", "gpt-image-2", "text-embedding-ada-002", "embed-v-4-0"), TOKEN_METADATA_FIELDS, ): From f9b22d8e96ad021a89215ffbc613cbb0edd14473 Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Fri, 2 Oct 2026 06:45:39 -0400 Subject: [PATCH 05/13] Add GPT-6 Sol to the model catalog so agent tools send reasoning effort none gpt-6-sol gets verified 1,050,000 context, 922,000 input and 128,000 output limits, a reasoning policy of none through xhigh, and Chat Completions toolReasoningEfforts of none for OpenAI and Azure. OpenAI documents that its Chat Completions function calling requires none, and Azure behaved the same. Agents with tools and no effort now send none; another explicit effort fails before the request. Refs #1606. Version 0.261.221. --- application/single_app/config.py | 2 +- .../static/json/model_capabilities.json | 201 +++++++++++++++++- .../fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md | 2 +- .../GPT6_SOL_CHAT_COMPLETIONS_TOOLS_FIX.md | 66 ++++++ .../test_gpt6_astra_catalog_record.py | 6 +- .../test_gpt6_sol_catalog_record.py | 154 ++++++++++++++ .../test_model_catalog_rebase_integration.py | 11 +- 7 files changed, 432 insertions(+), 10 deletions(-) create mode 100644 docs/explanation/fixes/GPT6_SOL_CHAT_COMPLETIONS_TOOLS_FIX.md create mode 100644 functional_tests/test_gpt6_sol_catalog_record.py diff --git a/application/single_app/config.py b/application/single_app/config.py index 8f3ca0aa3..415d78fca 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -100,7 +100,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.261.220" +VERSION = "0.261.221" IS_DEVELOPMENT = is_development_env_enabled() # Opt-out for deployments where App Service Easy Auth is active but the platform diff --git a/application/single_app/static/json/model_capabilities.json b/application/single_app/static/json/model_capabilities.json index 8ecdd4531..c3c566a80 100644 --- a/application/single_app/static/json/model_capabilities.json +++ b/application/single_app/static/json/model_capabilities.json @@ -1,7 +1,7 @@ { "$schema": "https://simplechat.local/schemas/model-capabilities.schema.json", "schemaVersion": 4, - "lastUpdated": "2026-10-01", + "lastUpdated": "2026-10-02", "description": "SimpleChat model capability, reasoning-policy, embedding-policy, and token-capacity catalog. Published chat/generation limits retain independently verified evidence and hosting profiles. Embedding input, dimension, and batch policies are separate operation contracts and never supply a chat-token budget.", "capabilityFields": { "processesText": "Accepts text input.", @@ -52,7 +52,8 @@ "Azure documents Responses image generation with a separate GPT Image deployment. SimpleChat deliberately offers only dedicated image models on Azure/Foundry; the conditional Azure Responses integration is not enabled by publisher imageGenerationTool flags or administrator overrides.", "Government image-model availability is not established by the reviewed Government model table. Unknown is not unavailable, and a Government-hosted app may use an explicitly configured commercial endpoint without relabeling that endpoint as Government.", "Image operation profiles describe the implemented single-image workflow. MAI and FLUX support source-image edits where stated, but their reviewed APIs do not document uploaded masks. Masking is not inferred from vision, tool calling, or inpainting prose.", - "GPT-6 Astra token capacities and reasoning efforts were reviewed on 2026-10-01, separately from the 2026-09-19 audit of the original 75 records. Azure Chat Completions rejected function tools for gpt-6-astra (2026-09-03) with the default and low efforts and rejected none. toolReasoningEfforts can't express a model with no tool-capable effort, so that observation is recorded in notes only." + "GPT-6 Astra token capacities and reasoning efforts were reviewed on 2026-10-01, separately from the 2026-09-19 audit of the original 75 records. Azure Chat Completions rejected function tools for gpt-6-astra (2026-09-03) with the default and low efforts and rejected none. toolReasoningEfforts can't express a model with no tool-capable effort, so that observation is recorded in notes only.", + "GPT-6 Sol was reviewed on 2026-10-02. OpenAI documents that its Chat Completions function calling requires reasoning_effort none, and Azure behaved the same on 2026-10-01, so both providers carry a Chat Completions toolReasoningEfforts of none." ], "sources": [ { @@ -1376,6 +1377,20 @@ "title": "Observed GPT-6 Astra (2026-09-03) Azure Chat Completions rejections on 2026-10-01: none rejected with low, medium, high and xhigh listed as supported; function tools rejected with the default and low efforts", "url": "https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/reasoning?view=foundry-classic", "verifiedAt": "2026-10-01" + }, + { + "id": "openai-spec-gpt-6-sol", + "provider": "openai", + "title": "OpenAI gpt-6-sol exact model specification, reasoning efforts and Chat Completions function-calling rule", + "url": "https://developers.openai.com/api/docs/models/gpt-6-sol", + "verifiedAt": "2026-10-02" + }, + { + "id": "gpt-6-sol-deployed-contract", + "provider": "microsoft", + "title": "Observed GPT-6 Sol (2026-09-22) Azure Chat Completions on 2026-10-01: function tools rejected with the default effort and accepted with reasoning_effort none", + "url": "https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/reasoning?view=foundry-classic", + "verifiedAt": "2026-10-01" } ], "models": [ @@ -11157,6 +11172,188 @@ ] } }, + { + "id": "gpt-6-sol", + "provider": "openai", + "displayName": "GPT-6 Sol", + "aliases": [], + "family": "gpt-6", + "lifecycle": "current", + "capabilities": { + "processesText": true, + "generatesText": true, + "processesImages": true, + "generatesImages": false, + "processesAudio": false, + "generatesAudio": false, + "processesVideo": false, + "generatesVideo": false, + "processesBinaryFiles": true, + "optimizedForCoding": true, + "toolCalling": true, + "structuredOutput": true, + "supportsStreaming": true, + "reasoning": true, + "imageGenerationTool": true + }, + "imageProfiles": { + "openai": "openai-responses" + }, + "notes": [ + "GPT-6 tier for complex coding and agentic workflows; text and image input with text output.", + "Image generation is a direct OpenAI Responses tool, not native image output or an Azure image deployment.", + "OpenAI lists reasoning efforts none, low, medium, high, xhigh and max. Responses-only max is excluded from this Chat Completions policy.", + "Chat Completions supports function calling only with reasoning_effort none, on direct OpenAI and on Azure. With tools, SimpleChat sends none when no effort is set and rejects any other explicit effort." + ], + "sourceIds": [ + "openai-spec-gpt-6-sol", + "openai-reasoning", + "azure-spec-gpt-6", + "azure-reasoning", + "gpt-6-sol-deployed-contract" + ], + "contextWindow": 1050000, + "inputTokenLimit": 922000, + "outputTokenLimit": 128000, + "verifiedAliases": [], + "tokenLimitsApplicability": "text", + "outputTokenAccounting": "total_generation", + "tokenLimitEvidence": { + "contextWindow": { + "status": "verified", + "sourceIds": [ + "openai-spec-gpt-6-sol" + ], + "verifiedAt": "2026-10-02", + "note": "Direct OpenAI explicitly documents 1,050,000 tokens for the shared context of gpt-6-sol; this is a model capacity, not a requested response length." + }, + "inputTokenLimit": { + "status": "verified", + "sourceIds": [ + "openai-spec-gpt-6-sol" + ], + "verifiedAt": "2026-10-02", + "note": "Direct OpenAI explicitly documents 922,000 tokens for the independent maximum input of gpt-6-sol; this is a model capacity, not a requested response length." + }, + "outputTokenLimit": { + "status": "verified", + "sourceIds": [ + "openai-spec-gpt-6-sol" + ], + "verifiedAt": "2026-10-02", + "note": "Direct OpenAI explicitly documents 128,000 tokens for the maximum generation of gpt-6-sol; this is a model capacity, not a requested response length." + }, + "outputTokenAccounting": { + "status": "verified", + "sourceIds": [ + "openai-reasoning" + ], + "verifiedAt": "2026-10-02", + "note": "gpt-6-sol documents reasoning token support, and native OpenAI generation ceilings include reasoning tokens as well as visible output. Do not reserve reasoning twice or treat visible response length as total generation." + } + }, + "tokenLimitProfiles": [ + { + "id": "azure", + "provider": "azure", + "contextWindow": 1050000, + "inputTokenLimit": 922000, + "outputTokenLimit": 128000, + "outputTokenAccounting": "total_generation", + "tokenLimitEvidence": { + "contextWindow": { + "status": "verified", + "sourceIds": [ + "azure-spec-gpt-6" + ], + "verifiedAt": "2026-10-01", + "note": "Azure explicitly documents 1,050,000 tokens for the shared context of gpt-6-sol (2026-09-22); this is a model capacity, not a requested response length." + }, + "inputTokenLimit": { + "status": "verified", + "sourceIds": [ + "azure-spec-gpt-6" + ], + "verifiedAt": "2026-10-01", + "note": "Azure explicitly documents 922,000 tokens for the independent maximum input of gpt-6-sol (2026-09-22); this is a model capacity, not a requested response length." + }, + "outputTokenLimit": { + "status": "verified", + "sourceIds": [ + "azure-spec-gpt-6" + ], + "verifiedAt": "2026-10-01", + "note": "Azure explicitly documents 128,000 tokens for the maximum generation of gpt-6-sol (2026-09-22); this is a model capacity, not a requested response length." + }, + "outputTokenAccounting": { + "status": "verified", + "sourceIds": [ + "azure-reasoning" + ], + "verifiedAt": "2026-10-02", + "note": "Azure generation limits include reasoning and visible output; the actual request cap is separate from maximum model capacity." + } + } + }, + { + "id": "openai-chat-completions-tools", + "provider": "openai", + "protocol": "chat_completions", + "toolReasoningEfforts": [ + "none" + ], + "tokenLimitEvidence": { + "toolReasoningEfforts": { + "status": "verified", + "sourceIds": [ + "openai-spec-gpt-6-sol" + ], + "verifiedAt": "2026-10-02", + "note": "OpenAI documents that gpt-6-sol Chat Completions supports function calling only with reasoning_effort none. This does not establish a Responses restriction; never silently change effort or protocol." + } + }, + "notes": [ + "Validate the explicit request effort against toolReasoningEfforts when function tools are present. The string none is a supported value, not an omitted setting." + ] + }, + { + "id": "azure-chat-completions-tools", + "provider": "azure", + "protocol": "chat_completions", + "toolReasoningEfforts": [ + "none" + ], + "tokenLimitEvidence": { + "toolReasoningEfforts": { + "status": "verified", + "sourceIds": [ + "gpt-6-sol-deployed-contract", + "openai-spec-gpt-6-sol" + ], + "verifiedAt": "2026-10-02", + "note": "Azure Chat Completions rejected function tools for gpt-6-sol (2026-09-22) with the default effort and accepted them with an explicit reasoning_effort of none, matching the documented OpenAI rule. Never silently change effort or protocol." + } + }, + "notes": [ + "Validate the explicit request effort against toolReasoningEfforts when function tools are present. The string none is a supported value, not an omitted setting." + ] + } + ], + "reasoningPolicy": { + "status": "supported", + "efforts": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "default_effort": "low", + "sourceIds": [ + "openai-spec-gpt-6-sol" + ] + } + }, { "id": "gpt-image-2.5-flare", "provider": "openai", diff --git a/docs/explanation/fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md b/docs/explanation/fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md index 2ae46612c..6c51fd6cc 100644 --- a/docs/explanation/fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md +++ b/docs/explanation/fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md @@ -57,7 +57,7 @@ There are three new sources: `openai-spec-gpt-6-astra`, `azure-spec-gpt-6` and ` - Budgets for `gpt-6-astra` resolve to the verified limits for Azure and OpenAI, so file evidence and workflow budgets no longer need manual limits. Limits set in Model Endpoints still take precedence. - Chat now sends the selected reasoning effort. If `none` or another unsupported effort is selected, chat sends `low` instead. Agents still send their saved effort as is, so don't save a `gpt-6-astra` agent with None. - `gpt-6`, `gpt-6-sol` and `gpt-6-luna` don't inherit anything from this record. Suffixed deployment names such as `gpt-6-astra-eastus` get the reasoning policy, which matches by prefix, but not the token limits, which need the exact model name. -- Agents with actions on `gpt-6-astra` still fail on Azure Chat Completions. Local agents can't use the Responses API yet, and the runtime doesn't yet fail early with a clear message. #1606 tracks both. For agents with actions, use `gpt-6-sol` with Reasoning Effort set to None. +- Agents with actions on `gpt-6-astra` still fail on Azure Chat Completions. Local agents can't use the Responses API yet, and the runtime doesn't yet fail early with a clear message. #1606 tracks both. For agents with actions, use `gpt-6-sol`. From 0.261.221, SimpleChat sends Reasoning Effort None for it when tools are present; see [GPT6_SOL_CHAT_COMPLETIONS_TOOLS_FIX.md](GPT6_SOL_CHAT_COMPLETIONS_TOOLS_FIX.md). ## Validation diff --git a/docs/explanation/fixes/GPT6_SOL_CHAT_COMPLETIONS_TOOLS_FIX.md b/docs/explanation/fixes/GPT6_SOL_CHAT_COMPLETIONS_TOOLS_FIX.md new file mode 100644 index 000000000..c4c3da035 --- /dev/null +++ b/docs/explanation/fixes/GPT6_SOL_CHAT_COMPLETIONS_TOOLS_FIX.md @@ -0,0 +1,66 @@ +# GPT-6 Sol Chat Completions Tools Fix + +Fixed in version: **0.261.221** + +Related issue: #1606 + +## Issue + +Agents with actions on `gpt-6-sol` failed unless their Reasoning Effort was set to None by hand. With no effort set, Azure rejected the request: + +> Function tools with reasoning_effort are not supported for gpt-6-sol in /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to 'none'. + +## Root Cause + +`gpt-6-sol` wasn't in the model catalog. SimpleChat already enforces this rule for GPT-5.6 through the catalog's `toolReasoningEfforts` field. With no record for `gpt-6-sol`, it sent the request with no effort, and the model applied its own default. + +OpenAI documents the rule on the `gpt-6-sol` model page: "Chat Completions supports function calling only with `reasoning_effort` set to `none`." Azure behaved the same on 2026-10-01: tools were rejected with the default effort and accepted with `none`. + +## Technical Details + +Files modified: + +- `application/single_app/static/json/model_capabilities.json` +- `application/single_app/config.py` +- `functional_tests/test_model_catalog_rebase_integration.py` +- `functional_tests/test_gpt6_astra_catalog_record.py` +- `functional_tests/test_gpt6_sol_catalog_record.py` (new) + +`gpt-6-sol` is now a token-evidenced record: + +| Field | Value | Source | +|---|---|---| +| Shared context | 1,050,000 | OpenAI `gpt-6-sol` specification; Azure GPT-6 table for the `azure` profile | +| Maximum input | 922,000 | Same | +| Maximum output | 128,000 | Same | +| Output accounting | Total generation (reasoning plus visible output) | OpenAI and Azure reasoning guides | +| Reasoning efforts | none, low, medium, high, xhigh; fallback low | OpenAI specification | +| Chat Completions tools | `toolReasoningEfforts: ["none"]` for `openai` and `azure` | OpenAI specification; Azure behavior observed on 2026-10-01 | + +The capability flags match the published feature list, which is the same as for `gpt-6-astra` and `gpt-5.6-sol`. That list includes the Responses image-generation tool. `max` isn't in the reasoning policy because SimpleChat's policy covers Chat Completions only. + +There are two new sources: `openai-spec-gpt-6-sol` and `gpt-6-sol-deployed-contract`. + +## Behavior After the Fix + +- An agent with tools on `gpt-6-sol` that has no Reasoning Effort set sends `none`, for Azure and for direct OpenAI. +- An explicit effort other than `none` on an agent with tools fails before the request with "This model's Chat Completions tools require Reasoning Effort None." The provider's 400 no longer appears. +- Without tools, every listed effort is sent as selected. +- The Responses protocol carries no tool restriction. +- Budgets use the verified limits, so file evidence and workflow budgets don't need manual limits. Limits set in Model Endpoints still take precedence. +- `gpt-6` and `gpt-6-luna` don't inherit anything from this record. + +## Validation + +`functional_tests/test_gpt6_sol_catalog_record.py` checks these cases: + +- The catalog passes schema validation and the cross-record integrity checks. +- The OpenAI and Azure limits each cite their exact specification. +- Both Chat Completions tool profiles require `none` and cite their evidence. +- Budgets carry the tool rule for Chat Completions only. +- Tools with no effort send `none`, and with `max_completion_tokens`. +- Tools with another explicit effort fail before the request; without tools, that effort is sent as selected. +- The reasoning policy and capability flags are correct. +- `gpt-6` and `gpt-6-luna` stay unknown. + +The catalog, capability, reasoning, budget and image tests pass. `test_ai_connections_capabilities.py` has the same 9 failures before and after this change. None of them involves GPT-6. diff --git a/functional_tests/test_gpt6_astra_catalog_record.py b/functional_tests/test_gpt6_astra_catalog_record.py index 74bcfc8ca..f666808af 100644 --- a/functional_tests/test_gpt6_astra_catalog_record.py +++ b/functional_tests/test_gpt6_astra_catalog_record.py @@ -1,7 +1,7 @@ # test_gpt6_astra_catalog_record.py """ Functional test for the GPT-6 Astra model catalog record. -Version: 0.261.220 +Version: 0.261.221 Implemented in: 0.261.220 Refs #1606. GPT-6 Astra moves from an operation-only record to a token-evidenced @@ -154,7 +154,7 @@ def test_documented_capabilities_keep_the_responses_image_tool(self): self.assertTrue(capabilities.is_reasoning_model(ASTRA)) def test_siblings_inherit_nothing_and_suffixed_names_get_no_token_limits(self): - for name in ("gpt-6", "gpt-6-sol", "gpt-6-luna"): + for name in ("gpt-6", "gpt-6-luna"): with self.subTest(model=name): self.assertEqual( capabilities.resolve_model_reasoning_policy(name)["status"], "unknown" @@ -164,7 +164,7 @@ def test_siblings_inherit_nothing_and_suffixed_names_get_no_token_limits(self): self.assertEqual( capabilities.resolve_model_reasoning_policy(name)["efforts"], EFFORTS ) - for name in ("gpt-6", "gpt-6-sol", "gpt-6-astra-2026-09-03", "gpt-6-astra-eastus"): + for name in ("gpt-6", "gpt-6-luna", "gpt-6-astra-2026-09-03", "gpt-6-astra-eastus"): with self.subTest(model=name): budget = capabilities.resolve_model_token_budget( {"modelName": name}, provider="azure", request_output_limit=4096, diff --git a/functional_tests/test_gpt6_sol_catalog_record.py b/functional_tests/test_gpt6_sol_catalog_record.py new file mode 100644 index 000000000..255d9ed22 --- /dev/null +++ b/functional_tests/test_gpt6_sol_catalog_record.py @@ -0,0 +1,154 @@ +# test_gpt6_sol_catalog_record.py +""" +Functional test for the GPT-6 Sol model catalog record. +Version: 0.261.221 +Implemented in: 0.261.221 + +Refs #1606. GPT-6 Sol's Chat Completions function calling works only with +reasoning_effort none: OpenAI documents it, and Azure behaved the same. The +record carries the verified limits and a Chat Completions toolReasoningEfforts of +none for both providers, so an agent with tools and no effort sends none, and an +explicit other effort fails before the request instead of with a provider 400. +No test fetches documentation, initializes application services or calls a model. +""" + +import json +import unittest +from itertools import product + +from semantic_kernel.connectors.ai.open_ai import OpenAIChatPromptExecutionSettings + +from test_model_capability_catalog_resolution import ( + CATALOG_PATH, + capabilities, + load_catalog_schema_validator, +) +from test_model_catalog_token_evidence import validate_catalog_integrity +from functions_model_budget_runtime import prepare_model_execution_settings + + +SOL = "gpt-6-sol" +LIMITS = (1050000, 922000, 128000) +CAPACITY_FIELDS = ("contextWindow", "inputTokenLimit", "outputTokenLimit") +EFFORTS = ["none", "low", "medium", "high", "xhigh"] + + +class TestGpt6SolCatalogRecord(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.catalog = json.loads(CATALOG_PATH.read_text(encoding="utf-8")) + cls.models = {record["id"]: record for record in cls.catalog["models"]} + cls.sources = {record["id"]: record for record in cls.catalog["sources"]} + cls.record = cls.models[SOL] + + def setUp(self): + capabilities.reset_model_capability_catalog_cache() + self.addCleanup(capabilities.reset_model_capability_catalog_cache) + + def budget(self, provider="azure", protocol="chat_completions"): + return capabilities.resolve_model_token_budget( + {"modelName": SOL}, provider=provider, protocol=protocol, request_output_limit=4096, + ) + + def test_catalog_validates_with_the_new_record(self): + load_catalog_schema_validator().validate(self.catalog) + validate_catalog_integrity(self.catalog) + self.assertTrue(any( + "GPT-6 Sol" in note and "toolReasoningEfforts" in note + for note in self.catalog["coverageNotes"] + )) + + def test_limits_cite_the_exact_openai_and_azure_specifications(self): + record = self.record + self.assertEqual(tuple(record[field] for field in CAPACITY_FIELDS), LIMITS) + self.assertEqual(record["outputTokenAccounting"], "total_generation") + self.assertTrue(self.sources["openai-spec-gpt-6-sol"]["url"].endswith("/api/docs/models/gpt-6-sol")) + azure = next(profile for profile in record["tokenLimitProfiles"] if profile["id"] == "azure") + self.assertEqual(tuple(azure[field] for field in CAPACITY_FIELDS), LIMITS) + for field in CAPACITY_FIELDS: + with self.subTest(field=field): + self.assertEqual(record["tokenLimitEvidence"][field]["sourceIds"], ["openai-spec-gpt-6-sol"]) + self.assertEqual(azure["tokenLimitEvidence"][field]["sourceIds"], ["azure-spec-gpt-6"]) + self.assertIn("2026-09-22", azure["tokenLimitEvidence"][field]["note"]) + + def test_chat_completions_tool_profiles_require_none_for_both_providers(self): + profiles = { + profile["provider"]: profile for profile in self.record["tokenLimitProfiles"] + if "toolReasoningEfforts" in profile + } + self.assertEqual(set(profiles), {"openai", "azure"}) + for provider, profile in profiles.items(): + with self.subTest(provider=provider): + self.assertEqual(profile["protocol"], "chat_completions") + self.assertNotIn("modelVersions", profile) + self.assertEqual(profile["toolReasoningEfforts"], ["none"]) + evidence = profile["tokenLimitEvidence"]["toolReasoningEfforts"] + self.assertEqual(evidence["status"], "verified") + self.assertIn("openai-spec-gpt-6-sol", evidence["sourceIds"]) + self.assertIn( + "gpt-6-sol-deployed-contract", + profiles["azure"]["tokenLimitEvidence"]["toolReasoningEfforts"]["sourceIds"], + ) + + def test_budgets_scope_the_tool_rule_to_chat_completions(self): + for provider, protocol in product(("azure", "openai"), ("chat_completions", "responses")): + with self.subTest(provider=provider, protocol=protocol): + budget = self.budget(provider, protocol) + self.assertEqual(budget.model_id, SOL) + self.assertEqual( + (budget.context_window, budget.input_limit, budget.output_limit), LIMITS + ) + self.assertEqual(budget.output_accounting, "total_generation") + expected = ("none",) if protocol == "chat_completions" else () + self.assertEqual(budget.tool_reasoning_efforts, expected) + + def test_tools_without_an_effort_send_none(self): + for provider in ("azure", "openai"): + with self.subTest(provider=provider): + prepared, _ = prepare_model_execution_settings( + OpenAIChatPromptExecutionSettings(), self.budget(provider), tools_enabled=True, + ) + serialized = prepared.prepare_settings_dict() + self.assertEqual(serialized["extra_body"]["reasoning_effort"], "none") + self.assertEqual(serialized["max_completion_tokens"], 4096) + self.assertNotIn("max_tokens", serialized) + + def test_tools_with_another_explicit_effort_fail_before_the_request(self): + settings = OpenAIChatPromptExecutionSettings(reasoning_effort="high") + with self.assertRaises(capabilities.ModelTokenBudgetError) as error: + prepare_model_execution_settings(settings, self.budget(), tools_enabled=True) + self.assertEqual(error.exception.code, "model_tool_configuration_invalid") + prepared, _ = prepare_model_execution_settings(settings, self.budget(), tools_enabled=False) + self.assertEqual(prepared.prepare_settings_dict()["extra_body"]["reasoning_effort"], "high") + + def test_reasoning_policy_and_capabilities(self): + policy = capabilities.resolve_model_reasoning_policy(SOL) + self.assertEqual(policy, {"status": "supported", "efforts": EFFORTS, "default_effort": "low"}) + resolution = capabilities.resolve_model_reasoning_effort(SOL, "none") + self.assertEqual(resolution["effective_effort"], "none") + self.assertIsNone(resolution["adjustment_reason"]) + flags = capabilities.get_model_catalog_capabilities(SOL) + for name, expected in ( + ("processesImages", True), ("generatesImages", False), ("toolCalling", True), + ("reasoning", True), ("supportsStreaming", True), ("imageGenerationTool", True), + ): + with self.subTest(capability=name): + self.assertIs(flags[name], expected) + self.assertEqual(flags["imageProfiles"], {"openai": "openai-responses"}) + self.assertTrue(capabilities.is_reasoning_model(SOL)) + + def test_luna_and_the_bare_family_stay_unknown(self): + for name in ("gpt-6", "gpt-6-luna"): + with self.subTest(model=name): + self.assertEqual( + capabilities.resolve_model_reasoning_policy(name)["status"], "unknown" + ) + budget = capabilities.resolve_model_token_budget( + {"modelName": name}, provider="azure", request_output_limit=4096, + ) + self.assertEqual(budget.tool_reasoning_efforts, ()) + self.assertIsNone(budget.context_window) + + +if __name__ == "__main__": + unittest.main() diff --git a/functional_tests/test_model_catalog_rebase_integration.py b/functional_tests/test_model_catalog_rebase_integration.py index 3528967bb..afea90d16 100644 --- a/functional_tests/test_model_catalog_rebase_integration.py +++ b/functional_tests/test_model_catalog_rebase_integration.py @@ -1,9 +1,10 @@ # test_model_catalog_rebase_integration.py """ Offline regression tests for audited token and React V2 metadata coexistence. -Version: 0.261.220 +Version: 0.261.221 Implemented in: 0.261.122 GPT-6 Astra token-evidenced record: 0.261.220 +GPT-6 Sol token-evidenced record: 0.261.221 Protect audited, qualitative-capability, reasoning-only, and embedding-policy records, provider-qualified image profiles, historical source reviews, and their @@ -51,7 +52,7 @@ "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5", "gpt-5-pro", "gpt-5-codex", "gpt-5-mini", "gpt-5-nano", - "gpt-4o", "gpt-4.1", "o1", "o3", "o3-mini", "o4-mini", "gpt-6-astra", + "gpt-4o", "gpt-4.1", "o1", "o3", "o3-mini", "o4-mini", "gpt-6-astra", "gpt-6-sol", } OPENAI_EMBEDDING_POLICY = { "max_input_tokens": 8192, @@ -123,7 +124,7 @@ "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5", "gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-5.2", "gpt-5.1", "gpt-5", "gpt-5-nano", - "gpt-4o", "gpt-4o-mini", "gpt-4.1", "gpt-4.1-nano", "o3", "gpt-6-astra", + "gpt-4o", "gpt-4o-mini", "gpt-4.1", "gpt-4.1-nano", "o3", "gpt-6-astra", "gpt-6-sol", } DIRECT_IMAGE_IDS = { "gpt-image-2", "gpt-image-1.5", "gpt-image-1", "gpt-image-1-mini", @@ -157,6 +158,10 @@ }, "2026-10-01": { "openai-spec-gpt-6-astra", "azure-spec-gpt-6", "gpt-6-astra-deployed-contract", + "gpt-6-sol-deployed-contract", + }, + "2026-10-02": { + "openai-spec-gpt-6-sol", }, } From 9ecc5c2048df38a86f90137bf2d424c72de99137 Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Fri, 2 Oct 2026 06:57:00 -0400 Subject: [PATCH 06/13] Add release notes for 0.261.217 to 0.261.221 Group document uploads with Enhanced Citations, Call agent action saves, scheduled-run identity, GPT-6 request parameters, and the GPT-6 Astra and Sol catalog records. Refs #1605, #1606. --- docs/explanation/release_notes.md | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index e34eea96e..5b0eca3f1 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -2,6 +2,59 @@ For feature-focused and fix-focused drill-downs by version, see [Features by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/features) and [Fixes by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/fixes). +### **(v0.261.221)** + +#### Bug Fixes + +* **GPT-6 Sol Agents With Actions Send Reasoning Effort None** + * Agents with actions on `gpt-6-sol` failed unless their Reasoning Effort was set to None by hand. Chat Completions accepts function tools for this model only with `reasoning_effort: none`, and with no effort set the model applied its own default and rejected the tools. + * `gpt-6-sol` is now in the model catalog with a Chat Completions tool rule for Azure and direct OpenAI. An agent with tools and no effort set now sends `none`. Another explicit effort fails before the request with "This model's Chat Completions tools require Reasoning Effort None." instead of a provider error. + * The record also carries the verified limits (1,050,000 context, 922,000 input, 128,000 output, counting reasoning tokens) and the reasoning efforts none, low, medium, high and xhigh. + * (Ref: #1606, `model_capabilities.json`, [GPT-6 Sol Chat Completions Tools Fix](fixes/GPT6_SOL_CHAT_COMPLETIONS_TOOLS_FIX.md)) + +### **(v0.261.220)** + +#### Bug Fixes + +* **GPT-6 Astra Limits And Reasoning Efforts In The Model Catalog** + * `gpt-6-astra` now has verified token limits for Azure and direct OpenAI: 1,050,000 context, 922,000 input and 128,000 output, counting reasoning tokens. File evidence and workflow budgets work without limits entered in Model Endpoints, and limits entered there still take precedence. + * Its reasoning efforts are low, medium, high and xhigh. Chat now sends the selected effort; before, with no policy, it sent no effort and the model used its default. None and other unsupported selections are sent as Low. Agents still send their saved effort as is. + * Azure Chat Completions rejects function tools for this model at every effort it accepts, so agents with actions can't run on it yet. The catalog records this in its notes. #1606 tracks a Responses API path and an early, clear error. + * (Ref: #1606, `model_capabilities.json`, [GPT-6 Astra Model Catalog Fix](fixes/GPT6_ASTRA_MODEL_CATALOG_FIX.md)) + +### **(v0.261.219)** + +#### Bug Fixes + +* **GPT-6 Models Use Reasoning Model Request Parameters** + * Requests to GPT-6 deployments failed whenever a response length was set: "Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead." Every workflow run on a GPT-6 agent failed on its first model call once token limits were configured. + * GPT-6 is now recognized alongside GPT-5, by name prefix and as a family name anywhere in a deployment or display name. Response length is sent as `max_completion_tokens`, and no temperature is sent. Workflow budgets, chat, prompt variables, content screening and orchestration all follow the shared policy. + * (Ref: #1605, `model_endpoint_clients.py`, [GPT-6 Reasoning Model Parameters Fix](fixes/GPT6_REASONING_MODEL_PARAMETERS_FIX.md)) + +### **(v0.261.218)** + +#### Bug Fixes + +* **Scheduled Workflow Runs Act As Their Owner** + * A scheduled run had no signed-in session, so the runner created one that held only the owner's object ID. Groups and conversation messages the run created showed that ID instead of the owner's name, and the run then wrote the ID over the owner's stored display name. The runner now uses the owner's stored display name and email. A manual run keeps the signed-in session, as before. + * The SimpleChat action's `add_user_to_group` now accepts `user_id`. Given with an email or display name, it adds the user without a directory lookup, as the REST members route already does. Scheduled runs have no delegated token for Microsoft Graph, so they couldn't add members before. + * Group conversation invites first match each identifier against the group's current owner and members, by user ID, email or display name. An identifier that matches no member, or more than one, still goes to the directory lookup. + * (Ref: `functions_workflow_runner.py`, `functions_simplechat_operations.py`, `simplechat_plugin.py`, [Workflow Background Identity Fix](fixes/WORKFLOW_BACKGROUND_IDENTITY_FIX.md), [SimpleChat action](../reference/actions/simplechat.md)) + +### **(v0.261.217)** + +#### Bug Fixes + +* **Group Document Uploads With Enhanced Citations** + * With Enhanced Citations on, every document uploaded to a group workspace failed with "Document processing failed." That included Word and Markdown documents an agent or workflow saved to a group. The upload's own status update changed the document record's etag between the read and the guarded write, so the write always saw a conflict. + * The status is now posted before the record is read. A change made by anyone else during the upload is still rejected. + * (Ref: `functions_documents.py`, [Group Document Blob Upload Etag Fix](fixes/GROUP_DOCUMENT_BLOB_UPLOAD_ETAG_FIX.md)) + +* **Call Agent Actions Save Again** + * Saving any Call agent action, whether personal, group or global, failed with "Invalid plugin configuration." The manifest check rejected the server-owned `scope_id` field that binding to a collection adds, so agents couldn't delegate to other agents. + * `scope_id` is now accepted. The server always sets it from the authorized collection, and every other extra field is still rejected. + * (Ref: `functions_agent_delegation.py`, [Call Agent Action Save Fix](fixes/CALL_AGENT_ACTION_SAVE_FIX.md)) + ### **(v0.261.216)** #### Bug Fixes From c06cf8e7170060da7d28330cef4a12374e970037 Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Fri, 2 Oct 2026 11:37:53 -0400 Subject: [PATCH 07/13] Play audio and video inline in V2 chat, show images as cards, and render agent-posted messages as markdown Audio links become an expanding player (play/pause, stop, seek, volume, speed), video links become cards with native controls, and markdown images become captioned cards with the lightbox. One recording plays at a time. Messages an agent posts through add_conversation_message are marked as agent-authored markdown and render like agent replies. CSP_MEDIA_SRC_ORIGINS adds validated https origins to media-src. Version 0.261.222. Refs #1611. --- application/single_app/config.py | 17 +- application/single_app/csp_media_sources.py | 55 +++++ .../functions_simplechat_operations.py | 14 ++ .../src/components/chat/AssistantMarkdown.tsx | 30 ++- .../src/components/chat/InlineAudioPlayer.tsx | 211 +++++++++++++++++ .../src/components/chat/InlineImageCard.tsx | 61 +++++ .../components/chat/InlineMediaFallback.tsx | 29 +++ .../src/components/chat/InlineVideoCard.tsx | 52 +++++ .../v2_ui/src/components/chat/MessageList.tsx | 29 ++- application/v2_ui/src/lib/inlineMedia.ts | 120 ++++++++++ application/v2_ui/src/lib/sharedMessage.ts | 17 ++ docs/explanation/features/REACT_V2_UI.md | 4 +- .../V2_INLINE_MEDIA_AND_AGENT_MESSAGES.md | 112 +++++++++ docs/guides/collaborate-in-a-conversation.md | 4 + docs/guides/create-an-action.md | 7 + .../test_simplechat_conversation_messages.py | 13 +- .../test_v2_inline_media_logic.mjs | 99 ++++++++ .../test_v2_inline_media_players.py | 217 ++++++++++++++++++ 18 files changed, 1080 insertions(+), 11 deletions(-) create mode 100644 application/single_app/csp_media_sources.py create mode 100644 application/v2_ui/src/components/chat/InlineAudioPlayer.tsx create mode 100644 application/v2_ui/src/components/chat/InlineImageCard.tsx create mode 100644 application/v2_ui/src/components/chat/InlineMediaFallback.tsx create mode 100644 application/v2_ui/src/components/chat/InlineVideoCard.tsx create mode 100644 application/v2_ui/src/lib/inlineMedia.ts create mode 100644 docs/explanation/features/V2_INLINE_MEDIA_AND_AGENT_MESSAGES.md create mode 100644 functional_tests/test_v2_inline_media_logic.mjs create mode 100644 functional_tests/test_v2_inline_media_players.py diff --git a/application/single_app/config.py b/application/single_app/config.py index 415d78fca..29eda18db 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -38,6 +38,7 @@ from functions_latest_features_nav import is_development_env_enabled from functions_appinsights import log_event from functions_azure_endpoint_validation import validate_configured_chat_blob_endpoint +from csp_media_sources import build_media_src_directive, parse_csp_media_origins from functions_environment import load_simplechat_dotenv from flask import ( @@ -100,7 +101,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.261.221" +VERSION = "0.261.222" IS_DEVELOPMENT = is_development_env_enabled() # Opt-out for deployments where App Service Easy Auth is active but the platform @@ -464,6 +465,18 @@ def get_allowed_extension_categories(enable_video=False, enable_audio=False, ena if TEAMS_FRAME_ANCESTORS: FRAME_ANCESTORS_DIRECTIVE = f"{FRAME_ANCESTORS_DIRECTIVE} {' '.join(TEAMS_FRAME_ANCESTORS)}" +# External hosts whose audio and video the chat may play in place, for example the media links an +# OpenAPI action returns. Empty by default, which keeps media-src same-origin. Only bare https +# origins are accepted (see csp_media_sources.py); anything else is ignored and logged. +CSP_MEDIA_SRC_ORIGINS, _rejected_csp_media_origins = parse_csp_media_origins(os.getenv('CSP_MEDIA_SRC_ORIGINS', '')) +if _rejected_csp_media_origins: + log_event( + "[CSP] Ignored CSP_MEDIA_SRC_ORIGINS entries that are not bare https origins.", + extra={"rejected_entries": _rejected_csp_media_origins}, + level=logging.WARNING, + ) +MEDIA_SRC_DIRECTIVE = build_media_src_directive(CSP_MEDIA_SRC_ORIGINS) + # Security Headers Configuration SECURITY_HEADERS = { @@ -477,7 +490,7 @@ def get_allowed_extension_categories(enable_video=False, enable_audio=False, ena "img-src 'self' data: https: blob:; " "font-src 'self'; " "connect-src 'self' https: wss: ws:; " - "media-src 'self' blob:; " + f"{MEDIA_SRC_DIRECTIVE}; " "frame-src 'self' blob:; " "object-src 'none'; " f"{FRAME_ANCESTORS_DIRECTIVE}; " diff --git a/application/single_app/csp_media_sources.py b/application/single_app/csp_media_sources.py new file mode 100644 index 000000000..33f5dcac1 --- /dev/null +++ b/application/single_app/csp_media_sources.py @@ -0,0 +1,55 @@ +# csp_media_sources.py +"""Validate the extra origins an administrator allows in the Content-Security-Policy media-src directive. + +Agents and actions can return audio and video hosted on their own domains (for example short-lived, +signed links from an OpenAPI action). The chat can only play those in place when the page's +Content-Security-Policy lists the host in media-src. CSP_MEDIA_SRC_ORIGINS names those hosts. + +Only bare https origins are accepted: a host, an optional leading "*." label and an optional port. +Anything else (other schemes, paths, quotes, semicolons, keywords such as 'unsafe-inline', or a bare +"*") is rejected, so the setting cannot widen the policy beyond media or inject another directive. + +Kept free of application imports so config.py can use it at start-up and tests can import it alone. +""" + +import json +import re + +_LABEL = r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?" +_ORIGIN_PATTERN = re.compile(rf"^https://(?:\*\.)?{_LABEL}(?:\.{_LABEL})+(?::\d{{1,5}})?$") + + +def _split(raw_value): + value = str(raw_value or "").strip() + if not value: + return [] + if value.startswith("["): + try: + parsed = json.loads(value) + except ValueError: + return [value] + return [str(item) for item in parsed] if isinstance(parsed, list) else [value] + return [item for item in re.split(r"[\s,]+", value) if item] + + +def parse_csp_media_origins(raw_value): + """Return (accepted, rejected) origins from a comma, space or JSON-list value. + + Accepted origins are lower-cased, stripped of a trailing slash and de-duplicated in order. + """ + accepted = [] + rejected = [] + for item in _split(raw_value): + candidate = item.strip().rstrip("/").lower() + port = candidate.rsplit(":", 1)[1] if candidate.count(":") == 2 else "" + if _ORIGIN_PATTERN.match(candidate) and (not port or 0 < int(port) <= 65535): + if candidate not in accepted: + accepted.append(candidate) + else: + rejected.append(item) + return accepted, rejected + + +def build_media_src_directive(origins): + """The media-src directive: same-origin and blob media, plus the allowed external origins.""" + return " ".join(["media-src 'self' blob:", *origins]) diff --git a/application/single_app/functions_simplechat_operations.py b/application/single_app/functions_simplechat_operations.py index 6f7afc1f3..b4beb5260 100644 --- a/application/single_app/functions_simplechat_operations.py +++ b/application/single_app/functions_simplechat_operations.py @@ -125,6 +125,9 @@ SIMPLECHAT_PLUGIN_TYPE = "simplechat" SIMPLECHAT_DEFAULT_ENDPOINT = "simplechat://internal" +# Marks a message an agent posted through add_conversation_message. Clients render it as +# markdown, the same way they render the agent's replies (see isAgentPostedMessage in the V2 UI). +AGENT_POSTED_MESSAGE_METADATA = {"posted_via": "agent_action", "content_format": "markdown"} GENERATED_CHAT_ARTIFACT_LIFECYCLE_STAGED = "staged" GENERATED_CHAT_ARTIFACT_LIFECYCLE_PUBLISHED = "published" GENERATED_CHAT_ARTIFACT_LIFECYCLE_ROLLED_BACK = "rolled_back" @@ -1127,6 +1130,12 @@ def add_conversation_message_for_current_user( content: str, reply_to_message_id: str = "", ) -> Dict[str, Any]: + """Post a message an agent wrote, through the SimpleChat action, as the current user. + + The message is stored as the user's own, because it is sent with their permissions, and is + marked with AGENT_POSTED_MESSAGE_METADATA so clients render it as markdown, the way the + agent's replies are rendered, while messages people type stay plain text. + """ current_user_info = _require_current_user_info() normalized_conversation_id = str(conversation_id or "").strip() normalized_content = str(content or "").strip() @@ -1154,6 +1163,7 @@ def add_conversation_message_for_current_user( current_user_info=current_user_info, content=normalized_content, reply_to_message_id=normalized_reply_to_message_id, + extra_metadata=AGENT_POSTED_MESSAGE_METADATA, ) return { "conversation": updated_conversation, @@ -1182,6 +1192,7 @@ def add_conversation_message_for_current_user( current_user, normalized_content, reply_to_message_id=normalized_reply_to_message_id, + extra_metadata=AGENT_POSTED_MESSAGE_METADATA, ) create_collaboration_message_notifications(updated_conversation, message_doc) return { @@ -2429,6 +2440,7 @@ def _persist_personal_conversation_message( current_user_info: Dict[str, str], content: str, reply_to_message_id: Optional[str] = None, + extra_metadata: Optional[Dict[str, Any]] = None, ) -> Tuple[Dict[str, Any], Dict[str, Any]]: conversation_id = str(conversation_item.get("id") or "").strip() if not conversation_id: @@ -2475,6 +2487,8 @@ def _persist_personal_conversation_message( }, }, } + if extra_metadata: + message_doc["metadata"].update(extra_metadata) cosmos_messages_container.upsert_item(message_doc) diff --git a/application/v2_ui/src/components/chat/AssistantMarkdown.tsx b/application/v2_ui/src/components/chat/AssistantMarkdown.tsx index 9e975e630..817631593 100644 --- a/application/v2_ui/src/components/chat/AssistantMarkdown.tsx +++ b/application/v2_ui/src/components/chat/AssistantMarkdown.tsx @@ -1,6 +1,6 @@ // AssistantMarkdown.tsx -// Markdown rendering for assistant output: prose, citations, masks, maths, diagrams, charts -// and image proposals. +// Markdown rendering for assistant output: prose, citations, masks, maths, diagrams, charts, +// image proposals, and inline audio, video and image cards. // // Split out of MessageList so that "what a message looks like" is separable from "how the // thread is laid out". The renderer carries the whole substitution pipeline and the fence @@ -29,10 +29,14 @@ import { import { applyMasks, MASK_PLACEHOLDER_PATTERN, type MaskedRange } from '../../lib/masking'; import { MaskedSpan } from './MaskedSpan'; import { CitationChip } from './CitationChip'; +import { InlineAudioPlayer } from './InlineAudioPlayer'; import { InlineChart } from './InlineChart'; +import { InlineImageCard } from './InlineImageCard'; import { InlineImageProposal } from './InlineImageProposal'; +import { InlineVideoCard } from './InlineVideoCard'; import { MathDisplay, MathInline } from './MathBlock'; import { MermaidDiagram } from './MermaidDiagram'; +import { inlineMediaKind, inlineMediaTitle, safeMarkdownHref, safeMediaUrl } from '../../lib/inlineMedia'; import { IMAGE_PROPOSAL_LANGUAGE, INLINE_CHART_LANGUAGE, @@ -234,6 +238,28 @@ function Markdown({ em: ({ children }) => {renderTokens(children)}, strong: ({ children }) => {renderTokens(children)}, + // A link to an audio or video file plays in place instead of navigating away; + // every other link renders as before. `node` is the hast element, read for the + // link's plain text and kept off the DOM element. + a: ({ href, children, node, ...props }) => { + const kind = inlineMediaKind(href); + const source = safeMediaUrl(href); + if (kind && source) { + const title = inlineMediaTitle(node ? toText(node) : '', source); + return kind === 'audio' ? ( + + ) : ( + + ); + } + return ( + + {children} + + ); + }, + img: ({ src, alt }) => , + // A diagram, chart or image proposal replaces the whole code block, so the //
 wrapper markdown puts around it is dropped: leaving it would box
             // the rendered output in the code block's background and padding.
diff --git a/application/v2_ui/src/components/chat/InlineAudioPlayer.tsx b/application/v2_ui/src/components/chat/InlineAudioPlayer.tsx
new file mode 100644
index 000000000..20bcb8ee0
--- /dev/null
+++ b/application/v2_ui/src/components/chat/InlineAudioPlayer.tsx
@@ -0,0 +1,211 @@
+// InlineAudioPlayer.tsx
+// A compact player for an audio recording linked in a chat message.
+//
+// Collapsed, it is a single line: play/pause, the recording's title, elapsed and total time, and
+// a thin progress line. Pressing play (or the chevron) expands it to add a seek slider, stop,
+// volume and mute, and playback speed. It stays in the message rather than floating over the
+// thread, so a briefing that cites several recordings keeps each one beside the text about it.
+//
+// Built from , 
+                
+
+            {!expanded && (
+                
+    );
+}
diff --git a/application/v2_ui/src/components/chat/InlineImageCard.tsx b/application/v2_ui/src/components/chat/InlineImageCard.tsx
new file mode 100644
index 000000000..1f865f9f4
--- /dev/null
+++ b/application/v2_ui/src/components/chat/InlineImageCard.tsx
@@ -0,0 +1,61 @@
+// InlineImageCard.tsx
+// An image placed in a chat message with markdown, shown as a captioned card.
+//
+// Agents put photographs, document scans and maps into their replies with `![caption](url)`.
+// Rendered bare, a large image fills the thread and its alt text, which is usually the only
+// description of what it shows, is invisible. The card bounds the height, shows the alt text as
+// a caption, and opens the existing lightbox on click for the full-size view, saving and opening
+// in a new tab.
+//
+// Built from  and 
+            {caption && {caption}}
+            {/* Portalled: the dialog is block markup, and this card sits inside a paragraph. */}
+            {open &&
+                createPortal(
+                     setOpen(false)} />,
+                    document.body,
+                )}
+        
+    );
+}
diff --git a/application/v2_ui/src/components/chat/InlineMediaFallback.tsx b/application/v2_ui/src/components/chat/InlineMediaFallback.tsx
new file mode 100644
index 000000000..f9e186d7d
--- /dev/null
+++ b/application/v2_ui/src/components/chat/InlineMediaFallback.tsx
@@ -0,0 +1,29 @@
+// InlineMediaFallback.tsx
+// What an inline audio or video player turns into when its media cannot be played here.
+//
+// A signed link that has expired, a host the page's content security policy does not allow, or
+// a format the browser cannot decode all end the same way: the link is still useful, so it is
+// shown as a link with a short reason rather than as a broken player.
+
+import { ExternalLink } from 'lucide-react';
+import { safeMediaUrl, type InlineMediaKind } from '../../lib/inlineMedia';
+
+export function InlineMediaFallback({ kind, src, title }: { kind: InlineMediaKind; src: string; title: string }) {
+    return (
+        
+            
+                This {kind === 'audio' ? 'recording' : 'video'} can't be played here. The link may have expired or its
+                host may not be allowed.
+            
+            
+                
+        
+    );
+}
diff --git a/application/v2_ui/src/components/chat/InlineVideoCard.tsx b/application/v2_ui/src/components/chat/InlineVideoCard.tsx
new file mode 100644
index 000000000..b597956b8
--- /dev/null
+++ b/application/v2_ui/src/components/chat/InlineVideoCard.tsx
@@ -0,0 +1,52 @@
+// InlineVideoCard.tsx
+// A video clip linked in a chat message, played in place.
+//
+// The browser's own controls are used (play, seek, volume, fullscreen and picture-in-picture):
+// they are accessible and familiar, and a clip needs nothing they lack. The card adds the
+// clip's title and a way to open it in a new tab. Starting a clip pauses any other inline
+// recording or clip, so two never play at once.
+
+import { useState } from 'react';
+import { ExternalLink, Film } from 'lucide-react';
+import { claimPlayback, safeMediaUrl } from '../../lib/inlineMedia';
+import { InlineMediaFallback } from './InlineMediaFallback';
+
+export function InlineVideoCard({ src, title }: { src: string; title: string }) {
+    const [failed, setFailed] = useState(false);
+
+    if (failed) {
+        return ;
+    }
+
+    return (
+        
+            
+    );
+}
diff --git a/application/v2_ui/src/components/chat/MessageList.tsx b/application/v2_ui/src/components/chat/MessageList.tsx
index ecf8a2d41..e3e0059dd 100644
--- a/application/v2_ui/src/components/chat/MessageList.tsx
+++ b/application/v2_ui/src/components/chat/MessageList.tsx
@@ -66,6 +66,7 @@ import {
 } from '../../lib/imageProposalSpec';
 import {
     isAiRequest,
+    isAgentPostedMessage,
     isOwnMessage,
     messageAuthorName,
     resolveReplyContext,
@@ -818,6 +819,12 @@ function MessageBubbleInner({
      * there.
      */
     const alignRight = author ? own : isUser;
+    /**
+     * A message an agent posted through an action on the sender's behalf. It keeps the
+     * sender's side of the thread, but renders as markdown in the neutral bubble: briefings
+     * carry tables, links and media that would be unreadable on the accent colour.
+     */
+    const agentPosted = isAgentPostedMessage(message);
     const replyContext = useMemo(
         () => resolveReplyContext(message, messages, currentUserId),
         [message, messages, currentUserId],
@@ -975,6 +982,7 @@ function MessageBubbleInner({
                 

{author} {isAiRequest(message) && asked the assistant} + {agentPosted && posted through an agent}

)}
@@ -985,7 +993,7 @@ function MessageBubbleInner({ 'rounded-2xl px-4 py-3', // These are repeated per message, so they use the non-blurred surface: // a backdrop-filter per bubble makes long threads scroll badly. - alignRight + alignRight && !agentPosted ? 'bg-accent text-on-accent' : 'glass-flat text-text-1', )} @@ -1003,7 +1011,7 @@ function MessageBubbleInner({ })} className={clsx( 'flex items-center gap-2 text-[15px] italic', - alignRight ? 'text-on-accent/80' : 'text-text-3', + alignRight && !agentPosted ? 'text-on-accent/80' : 'text-text-3', )} > @@ -1011,7 +1019,22 @@ function MessageBubbleInner({ {masks.maskedBy ? ` by ${masks.maskedBy}` : ''}.

) : isUser ? ( - promptUsed ? ( + agentPosted ? ( + <> + + {/* A shared thread already says this beside the author's name. */} + {!author && ( +

+ + Posted through an agent +

+ )} + + ) : promptUsed ? ( <> 0 ? `${hours}:${String(minutes).padStart(2, '0')}:${secs}` : `${minutes}:${secs}`; +} + +let activeMedia: HTMLMediaElement | null = null; + +/** + * Make `element` the one piece of media playing, pausing whichever was playing before it. + * + * Two recordings talking over each other is never what someone wants when they press play on a + * second one, and the same goes for a clip started while a recording runs. + */ +export function claimPlayback(element: HTMLMediaElement): void { + if (activeMedia && activeMedia !== element && !activeMedia.paused) { + activeMedia.pause(); + } + activeMedia = element; +} diff --git a/application/v2_ui/src/lib/sharedMessage.ts b/application/v2_ui/src/lib/sharedMessage.ts index 0de371aa2..a26d6ea04 100644 --- a/application/v2_ui/src/lib/sharedMessage.ts +++ b/application/v2_ui/src/lib/sharedMessage.ts @@ -117,3 +117,20 @@ export function buildReplyPreview(message: ChatMessage): string { export function isAiRequest(message: ChatMessage | undefined): boolean { return asShared(message)?.message_kind === 'ai_request'; } + +/** + * Whether an agent wrote this message through an action, on its sender's behalf. + * + * `add_conversation_message_for_current_user` (functions_simplechat_operations.py) stores such + * messages as the user's own, since they are sent with the user's permissions, and marks them + * with `posted_via: 'agent_action'` and `content_format: 'markdown'`. They are rendered as + * markdown, like the agent's replies; messages people type stay plain text. + */ +export function isAgentPostedMessage(message: ChatMessage | undefined): boolean { + const metadata = message?.metadata as { posted_via?: unknown; content_format?: unknown } | undefined; + return ( + message?.role === 'user' && + metadata?.posted_via === 'agent_action' && + metadata?.content_format === 'markdown' + ); +} diff --git a/docs/explanation/features/REACT_V2_UI.md b/docs/explanation/features/REACT_V2_UI.md index 591e7b7a5..e79951480 100644 --- a/docs/explanation/features/REACT_V2_UI.md +++ b/docs/explanation/features/REACT_V2_UI.md @@ -99,7 +99,9 @@ choice driven by how SimpleChat authenticates: - The application enforces a **custom same-origin CSRF check** on state-changing requests. Same-origin requests pass it unchanged. - The Content-Security-Policy is `default-src 'self'`. Same-origin assets need no CSP - relaxation. + relaxation. Audio and video from an action's own host play inline only when the host is + listed in `CSP_MEDIA_SRC_ORIGINS`, which adds validated `https://` origins to `media-src` + (see [V2 inline media and agent-posted messages](V2_INLINE_MEDIA_AND_AGENT_MESSAGES.md)). - No CORS layer is required. Hosting the SPA on a separate origin is supported but optional — see diff --git a/docs/explanation/features/V2_INLINE_MEDIA_AND_AGENT_MESSAGES.md b/docs/explanation/features/V2_INLINE_MEDIA_AND_AGENT_MESSAGES.md new file mode 100644 index 000000000..68cd7aff3 --- /dev/null +++ b/docs/explanation/features/V2_INLINE_MEDIA_AND_AGENT_MESSAGES.md @@ -0,0 +1,112 @@ +# V2 Inline Media and Agent-Posted Messages + +Implemented in version: **0.261.222** + +Related issue: [#1611](https://github.com/microsoft/simplechat/issues/1611) + +## Overview + +Agents increasingly return media: recordings, short camera clips, photographs and document scans, +usually as short-lived signed links from an OpenAPI action. The V2 chat now presents them in place: + +- A markdown link to an **audio** file renders as a compact player. Collapsed, it shows play/pause, + the title and elapsed/total time with a thin progress line. Pressing play or the chevron expands + it to add a seek slider, stop, volume and mute, playback speed (1x, 1.25x, 1.5x, 2x) and an + "open in a new tab" link. +- A link to a **video** file renders as a card with the browser's own controls (play, seek, volume, + fullscreen) and the link text as its caption. +- A markdown **image** renders as a captioned card that opens the existing image lightbox. +- Starting one recording or clip pauses any other, so two never play over each other. +- Media that cannot play (an expired link, a host the policy does not allow, an unsupported format) + falls back to a short notice and a link. + +Messages an agent posts into a conversation through the Simple Chat action +(`add_conversation_message`) are marked as agent-authored markdown and render like the agent's +replies, so a briefing posted to a group conversation keeps its headings, tables, links, images and +players. Messages people type stay plain text. + +### Dependencies + +- `react-markdown` (already used by the V2 renderer), with raw HTML still disabled and the default + URL transform unchanged. +- `lucide-react` icons. No new npm dependencies and no CDN assets. + +## Technical Specifications + +### Link classification + +`application/v2_ui/src/lib/inlineMedia.ts` + +- `inlineMediaKind(url)` returns `audio`, `video` or `null`, judged only by the extension at the + end of the URL **path** (`.mp3`, `.wav`, `.m4a`, `.aac`, `.ogg`, `.oga`, `.opus`, `.flac`, `.weba`; + `.mp4`, `.m4v`, `.webm`, `.mov`, `.ogv`). Query strings, such as signatures, are ignored. +- `safeMediaUrl(value)` accepts only `http:` and `https:` URLs and resolves root-relative paths + against the page. Every media source and every link the players render passes through it. +- `safeMarkdownHref(value)` keeps the schemes react-markdown allows by default (web, mail, phone and + chat, plus relative and fragment links) for ordinary links rendered by the new `a` component. +- `inlineMediaTitle(text, url)` removes action wording such as "Open audio:" from the link text. +- `claimPlayback(element)` pauses the previously playing media element. + +### Components + +| Component | Purpose | +| --- | --- | +| `components/chat/InlineAudioPlayer.tsx` | Expanding audio bar built from `span`, `button` and `input` elements so it is valid inside a paragraph or list item | +| `components/chat/InlineVideoCard.tsx` | Video card with native controls and caption | +| `components/chat/InlineImageCard.tsx` | Captioned image card; the lightbox is rendered through a portal | +| `components/chat/InlineMediaFallback.tsx` | Notice and link when media cannot play | +| `components/chat/AssistantMarkdown.tsx` | Adds `a` and `img` components that route media links and images to the components above | + +### Agent-posted messages + +- `functions_simplechat_operations.py` defines + `AGENT_POSTED_MESSAGE_METADATA = {"posted_via": "agent_action", "content_format": "markdown"}`. + `add_conversation_message_for_current_user` merges it into the message metadata on both the + personal path (`_persist_personal_conversation_message`, new `extra_metadata` parameter) and the + shared path (`persist_collaboration_message`, existing `extra_metadata` parameter). +- `lib/sharedMessage.ts` `isAgentPostedMessage(message)` recognises the marker on user messages. +- `components/chat/MessageList.tsx` renders those messages with `AssistantMarkdown` in the neutral + bubble (tables and links are unreadable on the accent colour), keeps them on the sender's side of + the thread, and labels them "posted through an agent". + +### Configuration + +| App setting | Default | Purpose | +| --- | --- | --- | +| `CSP_MEDIA_SRC_ORIGINS` | empty | Comma, space or JSON list of external `https://` origins whose audio and video may play inline. Appended to the `media-src 'self' blob:` directive. | + +`application/single_app/csp_media_sources.py` validates each entry. Only bare `https://` origins are +accepted: a host with at least two labels, an optional leading `*.` label and an optional port. +Other schemes, paths, quotes, semicolons, keywords and a bare `*` are rejected and logged through +`log_event`, so the setting cannot widen the policy beyond media or inject another directive. + +Prefer the narrowest origin that covers the host, for example the Container Apps environment's +domain (`https://*...azurecontainerapps.io`) rather than the regional domain. + +## Usage + +1. Give an agent an action that returns media links (for example an OpenAPI action whose responses + include signed audio or video URLs), and tell the agent to include those links in its reply. +2. If the media is served from the action's own host, add the host's origin to + `CSP_MEDIA_SRC_ORIGINS` and restart the app. +3. Ask the agent. Audio links appear as players, video links as cards, and images as captioned cards. +4. When the agent posts into a group conversation through the Simple Chat action, the posted message + renders the same way for every participant. + +## Testing and Validation + +- `functional_tests/test_v2_inline_media_players.py` covers CSP origin validation, the CSP wiring + in `config.py`, the agent-posted marker on both persistence paths, the message rendering branch, + the renderer's XSS boundaries, and runs the Node checks below. +- `functional_tests/test_v2_inline_media_logic.mjs` runs the real `inlineMedia.ts` helpers in Node: + classification, URL safety, titles, time formatting and single-playback. +- `functional_tests/test_v2_rich_rendering.py` still confirms the policy keeps `default-src 'self'` + and no CDN hosts. + +### Known limitations + +- Playback depends on the media host allowing the browser to stream it; signed links that expire + fall back to the notice and link. +- Downloading an external image from the lightbox needs the host to allow cross-origin reads; + opening it in a new tab always works. +- The classic interface is unchanged. diff --git a/docs/guides/collaborate-in-a-conversation.md b/docs/guides/collaborate-in-a-conversation.md index 28a8baca5..08f25e227 100644 --- a/docs/guides/collaborate-in-a-conversation.md +++ b/docs/guides/collaborate-in-a-conversation.md @@ -70,6 +70,10 @@ The same shared conversations work in the V2 interface, with the controls in dif Retry, edit, attempt navigation, and fork are not offered in a shared conversation in either interface; those actions have no shared-conversation equivalent. +### Messages an agent posts for you + +An agent with the Simple Chat action can post into a conversation on your behalf, for example a workflow that opens a group conversation and writes the first briefing. The message is sent with your permissions, so it appears under your name, with **posted through an agent** beside it. Unlike a message you type, it is rendered as formatted text: headings, tables, links, images, and audio or video players appear the way they do in the agent's own replies. + ## Verify it worked The conversation appears with a shared or collaborative indicator, the participant list shows accepted or pending members, and new shared messages appear for each accepted participant. A completed `@` mention appears in the message text. A participant-generated file stays unavailable while approval is pending and becomes downloadable only after an authorized approver approves it. diff --git a/docs/guides/create-an-action.md b/docs/guides/create-an-action.md index 8a859d607..de228cfe1 100644 --- a/docs/guides/create-an-action.md +++ b/docs/guides/create-an-action.md @@ -64,12 +64,19 @@ for workspace boundaries, context sharing, and nested-call limits. The action appears in the Actions list or card view. When you create or edit an agent, it is available on the agent **Actions** step. +## Media an action returns + +When an agent's reply links to an audio or video file, such as a recording or a camera clip an action returned, the V2 chat plays it in place: audio as a compact player with play, pause, stop, seek, volume, and speed, and video as a card with the browser's own controls. Images in a reply appear as captioned cards that open full size. Only one recording or clip plays at a time. + +The browser only plays media from hosts the page's Content-Security-Policy allows. Media served by SimpleChat itself always plays. For media an action serves from its own host, an administrator adds that host to the `CSP_MEDIA_SRC_ORIGINS` app setting as a comma-separated list of `https://` origins, for example `https://media.contoso.com` or `https://*.contoso-env.westus3.azurecontainerapps.io`. Entries that are not bare `https://` origins are ignored. Until a host is allowed, its links show a short notice with a link that opens the file in a new tab. + ## Troubleshooting | Symptom | Likely cause | Fix | | --- | --- | --- | | The Actions tab is missing | Actions are disabled for your workspace or account | Ask an admin to enable `enable_semantic_kernel` and the matching action scope. | | **Test Connection** fails | The endpoint, identity, or credential cannot reach the target service | Fix the connection details and test again before saving. | +| A recording or clip the action returned does not play | Its host is not in `CSP_MEDIA_SRC_ORIGINS`, or the action's signed link has expired | Add the host's `https://` origin to `CSP_MEDIA_SRC_ORIGINS` and restart the app, or ask the agent to fetch the record again for a fresh link. | ## Related diff --git a/functional_tests/test_simplechat_conversation_messages.py b/functional_tests/test_simplechat_conversation_messages.py index 4fb01b59b..228571d04 100644 --- a/functional_tests/test_simplechat_conversation_messages.py +++ b/functional_tests/test_simplechat_conversation_messages.py @@ -2,12 +2,13 @@ #!/usr/bin/env python3 """ Functional test for SimpleChat conversation message support. -Version: 0.241.028 +Version: 0.261.222 Implemented in: 0.241.028 This test ensures SimpleChat can add user-authored messages to personal and collaborative conversations and can seed newly created conversations when the -message capability is enabled. +message capability is enabled. Since 0.261.222 those messages are marked as +agent-posted markdown on both paths. """ import importlib @@ -111,6 +112,9 @@ def test_add_message_to_personal_conversation_for_current_user(): assert result['message']['role'] == 'user' assert result['message']['content'] == 'Kick off the personal workflow with this starter note.' assert result['message']['metadata']['thread_info']['previous_thread_id'] == 'thread-prev-1' + assert result['message']['metadata']['posted_via'] == 'agent_action' + assert result['message']['metadata']['content_format'] == 'markdown' + assert fake_messages_container.upserts[0]['metadata']['posted_via'] == 'agent_action' assert result['conversation']['title'].startswith('Kick off the personal workflow') assert len(fake_messages_container.upserts) == 1 assert len(fake_conversations_container.upserts) == 1 @@ -152,7 +156,7 @@ def test_add_message_to_collaboration_conversation_for_current_user(): 'group_role': 'User', 'conversation_id': conversation_doc.get('id'), }, - 'persist_collaboration_message': lambda conversation_doc, sender_user, content, reply_to_message_id=None: ( + 'persist_collaboration_message': lambda conversation_doc, sender_user, content, reply_to_message_id=None, extra_metadata=None: ( { 'id': 'message-collab-1', 'conversation_id': conversation_doc.get('id'), @@ -160,6 +164,7 @@ def test_add_message_to_collaboration_conversation_for_current_user(): 'content': content, 'metadata': { 'sender': sender_user, + **(extra_metadata or {}), }, }, deepcopy(updated_collaboration_doc), @@ -176,6 +181,8 @@ def test_add_message_to_collaboration_conversation_for_current_user(): assert result['conversation_kind'] == 'collaboration' assert result['message']['content'] == 'Hello group' + assert result['message']['metadata']['posted_via'] == 'agent_action' + assert result['message']['metadata']['content_format'] == 'markdown' assert result['conversation']['message_count'] == 1 assert len(notifications) == 1 diff --git a/functional_tests/test_v2_inline_media_logic.mjs b/functional_tests/test_v2_inline_media_logic.mjs new file mode 100644 index 000000000..6cc928eff --- /dev/null +++ b/functional_tests/test_v2_inline_media_logic.mjs @@ -0,0 +1,99 @@ +// test_v2_inline_media_logic.mjs +// Version: 0.261.222 +// Implemented in: 0.261.222 +// Executes the real V2 inline media helpers: which links play in place, which URLs a player or +// link may use, how titles and times read, and that only one recording plays at a time. + +import assert from 'node:assert/strict'; + +// The helpers resolve relative links against the page, as they do in the browser. +globalThis.window = { location: { href: 'https://chat.example.test/chat?conversationId=abc' } }; + +const { + claimPlayback, formatMediaTime, inlineMediaKind, inlineMediaTitle, PLAYBACK_RATES, safeMarkdownHref, safeMediaUrl, +} = await import('../application/v2_ui/src/lib/inlineMedia.ts'); + +const checks = []; +const check = (name, run) => checks.push([name, run]); + +check('audio and video are recognised by the extension of the URL path only', () => { + assert.equal(inlineMediaKind('https://media.example.test/media/rec-7713042.wav?exp=1&sig=abc'), 'audio'); + assert.equal(inlineMediaKind('https://media.example.test/a/B.MP3'), 'audio'); + assert.equal(inlineMediaKind('https://media.example.test/a/call.m4a'), 'audio'); + assert.equal(inlineMediaKind('https://media.example.test/media/clip.mp4?sig=1'), 'video'); + assert.equal(inlineMediaKind('/media/clip.webm'), 'video'); + assert.equal(inlineMediaKind('https://media.example.test/view?file=clip.mp4'), null); + assert.equal(inlineMediaKind('https://media.example.test/clip.mp4/details'), null); + assert.equal(inlineMediaKind('https://media.example.test/report.pdf'), null); + assert.equal(inlineMediaKind('https://media.example.test/'), null); +}); + +check('only http and https media can load, and relative paths become absolute', () => { + assert.equal(safeMediaUrl('/media/a.wav'), 'https://chat.example.test/media/a.wav'); + assert.equal(safeMediaUrl(' https://media.example.test/a.wav '), 'https://media.example.test/a.wav'); + assert.equal(safeMediaUrl('javascript:alert(1)//.mp3'), null); + assert.equal(inlineMediaKind('javascript:alert(1)//.mp3'), null); + assert.equal(safeMediaUrl('data:audio/wav;base64,AAAA'), null); + assert.equal(safeMediaUrl('ftp://media.example.test/a.mp3'), null); + assert.equal(safeMediaUrl(''), null); + assert.equal(safeMediaUrl(undefined), null); +}); + +check('links keep only the schemes markdown allows by default', () => { + assert.equal(safeMarkdownHref('https://example.test/a'), 'https://example.test/a'); + assert.equal(safeMarkdownHref('mailto:someone@example.test'), 'mailto:someone@example.test'); + assert.equal(safeMarkdownHref('/workspace/documents'), '/workspace/documents'); + assert.equal(safeMarkdownHref('#section'), '#section'); + assert.equal(safeMarkdownHref('relative/path:with-colon'), 'relative/path:with-colon'); + assert.equal(safeMarkdownHref('javascript:alert(1)'), undefined); + assert.equal(safeMarkdownHref('JavaScript:alert(1)'), undefined); + assert.equal(safeMarkdownHref('java\tscript:alert(1)'), undefined); + assert.equal(safeMarkdownHref('vbscript:msgbox(1)'), undefined); + assert.equal(safeMarkdownHref('data:text/html,x'), undefined); + assert.equal(safeMarkdownHref(undefined), undefined); +}); + +check('titles drop the action wording and fall back to the file name', () => { + const url = 'https://media.example.test/media/cra-audio-REC-7713042.wav'; + assert.equal(inlineMediaTitle('Open audio: Call audio REC-7713042', url), 'Call audio REC-7713042'); + assert.equal(inlineMediaTitle('Open video: Counter camera 2 clip', url), 'Counter camera 2 clip'); + assert.equal(inlineMediaTitle('Listen to the recording - follow-up interview', url), 'follow-up interview'); + assert.equal(inlineMediaTitle('Witness call', url), 'Witness call'); + assert.equal(inlineMediaTitle(url, url), 'cra-audio-REC-7713042.wav'); + assert.equal(inlineMediaTitle('', url), 'cra-audio-REC-7713042.wav'); +}); + +check('times read as m:ss, or h:mm:ss past an hour', () => { + assert.equal(formatMediaTime(0), '0:00'); + assert.equal(formatMediaTime(65.4), '1:05'); + assert.equal(formatMediaTime(3725), '1:02:05'); + assert.equal(formatMediaTime(Number.NaN), '0:00'); + assert.equal(formatMediaTime(-3), '0:00'); + assert.equal(PLAYBACK_RATES[0], 1); +}); + +check('starting one recording pauses the one already playing', () => { + const element = () => ({ paused: false, pause() { this.paused = true; } }); + const first = element(); + const second = element(); + claimPlayback(first); + claimPlayback(second); + assert.equal(first.paused, true); + assert.equal(second.paused, false); + claimPlayback(second); + assert.equal(second.paused, false); +}); + +let failures = 0; +for (const [name, run] of checks) { + try { + run(); + console.log(`PASS ${name}`); + } catch (error) { + failures += 1; + console.error(`FAIL ${name}\n${error.stack}`); + } +} +if (failures) { + process.exit(1); +} diff --git a/functional_tests/test_v2_inline_media_players.py b/functional_tests/test_v2_inline_media_players.py new file mode 100644 index 000000000..fc835f608 --- /dev/null +++ b/functional_tests/test_v2_inline_media_players.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +""" +Functional test for V2 inline media players, image cards and agent-posted group messages. +Version: 0.261.222 +Implemented in: 0.261.222 + +This test ensures that: + - links to audio and video files in chat markdown render as inline players (the logic is run in + Node by test_v2_inline_media_logic.mjs), and markdown images render as captioned cards; + - the markdown renderer stays XSS-safe: no raw HTML, the default URL transform, and every + rendered href or media source passes through a scheme check; + - messages an agent posts through add_conversation_message are marked as agent-authored + markdown on both the personal and the shared path, and only those render as markdown; + - CSP_MEDIA_SRC_ORIGINS adds only bare https origins to media-src and cannot inject directives. +""" + +import ast +import re +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +APP_DIR = ROOT / "application" / "single_app" +V2_SRC = ROOT / "application" / "v2_ui" / "src" +CHAT_DIR = V2_SRC / "components" / "chat" + +sys.path.insert(0, str(ROOT / "functional_tests")) +sys.path.insert(0, str(APP_DIR)) + +from test_support.versioning import assert_app_version_at_least # noqa: E402 + +IMPLEMENTED_IN = "0.261.222" + + +def _read(path): + return path.read_text(encoding="utf-8") + + +def test_csp_media_origins_accept_only_bare_https_origins(): + """Only https origins reach media-src; anything that could widen the policy is rejected.""" + print("Testing CSP media origin validation...") + from csp_media_sources import build_media_src_directive, parse_csp_media_origins + + accepted, rejected = parse_csp_media_origins( + "https://media.example.com, https://*.env-abc.westus3.azurecontainerapps.io " + "https://media.example.com/ HTTPS://Clips.Example.org:8443" + ) + assert accepted == [ + "https://media.example.com", + "https://*.env-abc.westus3.azurecontainerapps.io", + "https://clips.example.org:8443", + ], accepted + assert rejected == [], rejected + + hostile = [ + "http://media.example.com", + "https://media.example.com/path", + "*", + "https://*", + "https://*.com.", + "'unsafe-inline'", + "data:", + "https://media.example.com;script-src", + "https://media.example.com:99999", + "https://localhost", + "javascript:alert(1)", + ] + for value in hostile: + accepted, rejected = parse_csp_media_origins(value) + assert accepted == [], f"{value!r} must not be accepted" + assert rejected, f"{value!r} must be reported as rejected" + + accepted, _ = parse_csp_media_origins('["https://a.example.com", "https://a.example.com", "https://b.example.com"]') + assert accepted == ["https://a.example.com", "https://b.example.com"] + assert parse_csp_media_origins("") == ([], []) + assert build_media_src_directive([]) == "media-src 'self' blob:" + assert build_media_src_directive(["https://a.example.com"]) == "media-src 'self' blob: https://a.example.com" + print("CSP media origin validation passed!") + + +def test_config_builds_media_src_from_the_validated_setting(): + """config.py reads CSP_MEDIA_SRC_ORIGINS through the validator and keeps every other directive.""" + print("Testing the Content-Security-Policy wiring...") + config = _read(APP_DIR / "config.py") + assert "parse_csp_media_origins(os.getenv('CSP_MEDIA_SRC_ORIGINS', ''))" in config + assert "MEDIA_SRC_DIRECTIVE = build_media_src_directive(CSP_MEDIA_SRC_ORIGINS)" in config + policy = re.search(r"'Content-Security-Policy':\s*\((.*?)\)\s*\n\s*\}", config, re.DOTALL) + assert policy, "Content-Security-Policy not found in config.py" + policy_text = policy.group(1) + assert 'f"{MEDIA_SRC_DIRECTIVE}; "' in policy_text + assert "\"media-src 'self' blob:; \"" not in policy_text, "media-src must come from the validated directive" + for directive in ("default-src 'self'", "script-src 'self'", "font-src 'self'", "object-src 'none'"): + assert directive in policy_text, f"CSP must still contain {directive!r}" + print("Content-Security-Policy wiring passed!") + + +def test_agent_posted_messages_are_marked_on_both_paths(): + """Personal and shared messages posted through the action carry the agent-markdown marker.""" + print("Testing the agent-posted message marker...") + source = _read(APP_DIR / "functions_simplechat_operations.py") + tree = ast.parse(source) + marker = None + for node in tree.body: + if isinstance(node, ast.Assign) and any(getattr(target, "id", "") == "AGENT_POSTED_MESSAGE_METADATA" for target in node.targets): + marker = ast.literal_eval(node.value) + assert marker == {"posted_via": "agent_action", "content_format": "markdown"}, marker + + function = next(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "add_conversation_message_for_current_user") + calls = { + get_name(call.func): call + for call in ast.walk(function) + if isinstance(call, ast.Call) and get_name(call.func) in ("_persist_personal_conversation_message", "persist_collaboration_message") + } + assert set(calls) == {"_persist_personal_conversation_message", "persist_collaboration_message"}, calls + for name, call in calls.items(): + keywords = {keyword.arg: keyword.value for keyword in call.keywords} + assert getattr(keywords.get("extra_metadata"), "id", "") == "AGENT_POSTED_MESSAGE_METADATA", f"{name} must pass the marker" + + persist = next(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "_persist_personal_conversation_message") + assert "extra_metadata" in [arg.arg for arg in persist.args.args] + assert 'message_doc["metadata"].update(extra_metadata)' in ast.get_source_segment(source, persist) + + shared = _read(V2_SRC / "lib" / "sharedMessage.ts") + assert "metadata?.posted_via === 'agent_action'" in shared + assert "metadata?.content_format === 'markdown'" in shared + assert "message?.role === 'user'" in shared + print("Agent-posted message marker passed!") + + +def get_name(node): + if isinstance(node, ast.Name): + return node.id + if isinstance(node, ast.Attribute): + return node.attr + return "" + + +def test_only_agent_posted_user_messages_render_as_markdown(): + """MessageList renders agent-posted messages with the markdown renderer and leaves typed ones as text.""" + print("Testing message rendering...") + message_list = _read(CHAT_DIR / "MessageList.tsx") + assert "const agentPosted = isAgentPostedMessage(message);" in message_list + branch = message_list.index("agentPosted ? (") + plain = message_list.index('

') + assert branch < plain, "the agent-posted branch must be checked before the plain-text user branch" + assert "" in markdown + assert "const kind = inlineMediaKind(href);" in markdown + assert "" in markdown + assert "img: ({ src, alt }) => " in markdown + assert "from 'rehype-raw'" not in markdown, "raw HTML must stay off" + assert "allowDangerousHtml" not in markdown and "urlTransform=" not in markdown, "keep react-markdown's default URL transform" + + player = _read(CHAT_DIR / "InlineAudioPlayer.tsx") + for control in ("Pause", "Play", "Square", "VolumeX", "Volume2", "ChevronDown", "PLAYBACK_RATES", "claimPlayback(audio)", "aria-expanded"): + assert control in player, f"audio player is missing {control}" + assert player.count('type="range"') == 2, "the player needs a seek slider and a volume slider" + assert "audio.currentTime = 0" in player, "stop must rewind as well as pause" + assert "]*?\b(href|src)=\{([^}]*)\}", text, re.S)) + assert elements, f"{name}: expected a link or media element" + for match in elements: + assert "safeMediaUrl(" in match.group(3), f"{name}: <{match.group(1)} {match.group(2)}> must pass through safeMediaUrl" + assert "dangerouslySetInnerHTML" not in text + + image_card = _read(CHAT_DIR / "InlineImageCard.tsx") + assert "resolveImageSource(src)" in image_card and "createPortal(" in image_card + print("Markdown renderer passed!") + + +def test_inline_media_helpers_behave(): + """Run the real TypeScript helpers in Node.""" + print("Testing inline media helpers in Node...") + subprocess.run(["node", str(ROOT / "functional_tests" / "test_v2_inline_media_logic.mjs")], cwd=ROOT, check=True) + print("Inline media helpers passed!") + + +def test_version_was_incremented(): + assert_app_version_at_least(IMPLEMENTED_IN) + + +if __name__ == "__main__": + tests = [ + test_csp_media_origins_accept_only_bare_https_origins, + test_config_builds_media_src_from_the_validated_setting, + test_agent_posted_messages_are_marked_on_both_paths, + test_only_agent_posted_user_messages_render_as_markdown, + test_markdown_renderer_wires_inline_media_safely, + test_inline_media_helpers_behave, + test_version_was_incremented, + ] + failures = 0 + for test in tests: + try: + test() + except Exception as exc: # noqa: BLE001 - report every failing check + failures += 1 + print(f"FAILED {test.__name__}: {exc}") + print(f"{len(tests) - failures}/{len(tests)} tests passed") + sys.exit(1 if failures else 0) From e044ff7f72d00aaae6122380b1c670023f2534e8 Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Fri, 2 Oct 2026 12:21:20 -0400 Subject: [PATCH 08/13] Add release notes for 0.261.222 Refs #1611. --- docs/explanation/release_notes.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index 5b0eca3f1..a4e27bd61 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -2,6 +2,31 @@ For feature-focused and fix-focused drill-downs by version, see [Features by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/features) and [Fixes by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/fixes). +### **(v0.261.222)** + +#### New Features + +* **Inline Audio and Video Players in the V2 Chat** + * A link to an audio file in a reply, such as a recording an action returned, now plays in place as a compact player. Collapsed, it shows play/pause, the title and elapsed and total time; expanded, it adds a seek slider, stop, volume and mute, playback speed and an open-in-new-tab link. + * A link to a video file renders as a card with the browser's own controls, including fullscreen. Starting one recording or clip pauses any other. + * Media that cannot play, for example an expired signed link or a host the page does not allow, shows a short notice and a link instead of a broken player. + * (Ref: #1611, `InlineAudioPlayer.tsx`, `InlineVideoCard.tsx`, `lib/inlineMedia.ts`, [V2 Inline Media and Agent-Posted Messages](features/V2_INLINE_MEDIA_AND_AGENT_MESSAGES.md)) + +* **External Media Sources in the Content-Security-Policy** + * The new `CSP_MEDIA_SRC_ORIGINS` app setting lets administrators list the `https://` origins whose audio and video may play inline, for example an action's media host. The origins are added to `media-src`. + * Only bare `https://` origins (with an optional leading `*.` label and port) are accepted. Anything else is ignored and logged, so the setting cannot widen the policy beyond media. Empty by default, which keeps media same-origin. + * (Ref: #1611, `csp_media_sources.py`, `config.py`) + +#### User Interface Enhancements + +* **Image Cards in Chat Replies** + * Images in a reply's markdown appear as captioned cards with a bounded height. Selecting one opens the image viewer for the full-size view, saving and opening in a new tab. + * (Ref: #1611, `InlineImageCard.tsx`, `AssistantMarkdown.tsx`) + +* **Agent-Posted Messages Render as Formatted Text** + * A message an agent posts into a personal or shared conversation through the Simple Chat action is now marked as agent-authored markdown. The V2 chat renders it like the agent's own replies, with headings, tables, links, images and players, in the neutral bubble and labeled "posted through an agent". Messages people type are unchanged. + * (Ref: #1611, `functions_simplechat_operations.py`, `MessageList.tsx`, `sharedMessage.ts`) + ### **(v0.261.221)** #### Bug Fixes From cc4b842965662cdf8386dd5e11bbf20c2632d182 Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Fri, 2 Oct 2026 14:36:11 -0400 Subject: [PATCH 09/13] Draw interactive Azure Maps results in the V2 chat Replies that used the Azure Maps action now show their map under the text in the V2 chat. The map opens fitted to all features. It pans and zooms with Ctrl/Cmd + scroll, so a plain scroll still scrolls the conversation, and it has a full screen control. Hovering a marker, path or area shows its details, and a click pins them. A text list shows everything on the map. OpenLayers 10.6.1 is vendored into public/vendor, byte-identical to the published package and to the classic chat's copy, and loaded only when a reply has a map. Map text is only written as text. OpenLayers' HTML attribution control is off, and tiles only load through /api/azure-maps/tile. /api/get_messages and the collaboration messages endpoint now reissue expired tile tokens, so maps in older replies and in group conversations load their tiles. Compact map citations are fetched by artifact. Legacy {{map:...}} blocks are hidden from reply text. Also updates the V2 enhanced citations CSP check for the media-src directive that 0.261.222 moved into csp_media_sources.py. Version 0.261.223. Refs #1609. --- application/single_app/config.py | 2 +- .../single_app/functions_azure_maps.py | 24 + .../single_app/route_backend_collaboration.py | 2 + .../single_app/route_backend_conversations.py | 2 + .../vendor/openlayers-10.6.1/LICENSE.md | 25 + .../public/vendor/openlayers-10.6.1/ol.css | 354 ++++++++++++++ .../public/vendor/openlayers-10.6.1/ol.js | 2 + .../src/components/chat/InlineMapCard.tsx | 352 ++++++++++++++ .../v2_ui/src/components/chat/MessageList.tsx | 13 +- application/v2_ui/src/lib/endpoints.ts | 13 + application/v2_ui/src/lib/inlineMaps.ts | 439 ++++++++++++++++++ application/v2_ui/src/lib/vendor.d.ts | 73 +++ application/v2_ui/src/lib/vendorAssets.ts | 21 +- docs/explanation/features/REACT_V2_UI.md | 5 +- .../features/V2_INTERACTIVE_MAPS.md | 139 ++++++ docs/explanation/release_notes.md | 23 + .../actions/azure-maps-openlayers.md | 14 + .../test_content_screening_history.py | 3 +- .../test_v2_enhanced_citations.py | 18 +- functional_tests/test_v2_inline_maps.py | 286 ++++++++++++ .../test_v2_inline_maps_logic.mjs | 245 ++++++++++ functional_tests/test_v2_rich_rendering.py | 10 +- ui_tests/test_v2_inline_maps.py | 318 +++++++++++++ 23 files changed, 2374 insertions(+), 9 deletions(-) create mode 100644 application/v2_ui/public/vendor/openlayers-10.6.1/LICENSE.md create mode 100644 application/v2_ui/public/vendor/openlayers-10.6.1/ol.css create mode 100644 application/v2_ui/public/vendor/openlayers-10.6.1/ol.js create mode 100644 application/v2_ui/src/components/chat/InlineMapCard.tsx create mode 100644 application/v2_ui/src/lib/inlineMaps.ts create mode 100644 docs/explanation/features/V2_INTERACTIVE_MAPS.md create mode 100644 functional_tests/test_v2_inline_maps.py create mode 100644 functional_tests/test_v2_inline_maps_logic.mjs create mode 100644 ui_tests/test_v2_inline_maps.py diff --git a/application/single_app/config.py b/application/single_app/config.py index 29eda18db..a7b7f1b71 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -101,7 +101,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.261.222" +VERSION = "0.261.223" IS_DEVELOPMENT = is_development_env_enabled() # Opt-out for deployments where App Service Easy Auth is active but the platform diff --git a/application/single_app/functions_azure_maps.py b/application/single_app/functions_azure_maps.py index 7f54081a5..87dafacce 100644 --- a/application/single_app/functions_azure_maps.py +++ b/application/single_app/functions_azure_maps.py @@ -189,6 +189,9 @@ def refresh_azure_maps_function_result(function_result: Any) -> Any: parsed_result = function_result was_serialized = False if isinstance(function_result, str): + # Most tool results are not maps, and some are large, so only parse likely candidates. + if AZURE_MAPS_RENDER_TYPE not in function_result: + return function_result try: parsed_result = json.loads(function_result) was_serialized = True @@ -234,6 +237,27 @@ def refresh_azure_maps_citation_payloads(citations: Any) -> Any: return [refresh_azure_maps_citation_payload(citation) for citation in citations] +def refresh_azure_maps_message_citations(messages: Any) -> Any: + """Return messages whose stored map citations carry a fresh tile proxy token. + + Tile tokens expire, so a map in an older message only loads its tiles if the + token is reissued when the message is read. + """ + if not isinstance(messages, list): + return messages + + refreshed_messages = [] + for message in messages: + if isinstance(message, dict) and isinstance(message.get("agent_citations"), list): + message = { + **message, + "agent_citations": refresh_azure_maps_citation_payloads(message["agent_citations"]), + } + refreshed_messages.append(message) + + return refreshed_messages + + def _find_inline_map_block_end(message_content: str, start_index: int) -> Optional[int]: payload_index = start_index + len(AZURE_MAPS_INLINE_BLOCK_PREFIX) if payload_index >= len(message_content) or message_content[payload_index] != "{": diff --git a/application/single_app/route_backend_collaboration.py b/application/single_app/route_backend_collaboration.py index 583714a56..1ec2cd189 100644 --- a/application/single_app/route_backend_collaboration.py +++ b/application/single_app/route_backend_collaboration.py @@ -131,6 +131,7 @@ apply_visual_style, ) from functions_notifications import mark_collaboration_message_notifications_read_for_conversation +from functions_azure_maps import refresh_azure_maps_message_citations from functions_message_artifacts import make_json_serializable from functions_m365_runtime import read_pending_m365_chat_request from functions_m365_approvals import M365ApprovalRequired, M365PolicyError @@ -1714,6 +1715,7 @@ def get_collaboration_messages_api(conversation_id): allow_pending=True, ) messages = [serialize_collaboration_message(doc) for doc in list_collaboration_messages(conversation_id)] + messages = refresh_azure_maps_message_citations(messages) messages = sanitize_saved_analysis_messages(messages, current_user['user_id']) attach_generated_file_approval_state(messages, current_user['user_id']) messages = hydrate_m365_pending_action_cards(messages, current_user['user_id'], conversation_id) diff --git a/application/single_app/route_backend_conversations.py b/application/single_app/route_backend_conversations.py index 2c8642707..1ff97f487 100644 --- a/application/single_app/route_backend_conversations.py +++ b/application/single_app/route_backend_conversations.py @@ -62,6 +62,7 @@ serialize_chat_upload_workspace_documents_for_conversation, ) from functions_group import get_user_groups +from functions_azure_maps import refresh_azure_maps_message_citations from functions_message_artifacts import ( build_message_artifact_payload_map, filter_assistant_artifact_items, @@ -1139,6 +1140,7 @@ def api_get_messages(): all_items = sanitize_saved_analysis_messages(all_items, user_id) all_items = hydrate_agent_citations_from_artifacts(all_items, artifact_payload_map) + all_items = refresh_azure_maps_message_citations(all_items) try: all_items = public_history_messages(all_items, user_id) except ( diff --git a/application/v2_ui/public/vendor/openlayers-10.6.1/LICENSE.md b/application/v2_ui/public/vendor/openlayers-10.6.1/LICENSE.md new file mode 100644 index 000000000..b86fe0f40 --- /dev/null +++ b/application/v2_ui/public/vendor/openlayers-10.6.1/LICENSE.md @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright 2005-present, OpenLayers 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. + +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/application/v2_ui/public/vendor/openlayers-10.6.1/ol.css b/application/v2_ui/public/vendor/openlayers-10.6.1/ol.css new file mode 100644 index 000000000..df5f04241 --- /dev/null +++ b/application/v2_ui/public/vendor/openlayers-10.6.1/ol.css @@ -0,0 +1,354 @@ +:root, +:host { + --ol-background-color: white; + --ol-accent-background-color: #F5F5F5; + --ol-subtle-background-color: rgba(128, 128, 128, 0.25); + --ol-partial-background-color: rgba(255, 255, 255, 0.75); + --ol-foreground-color: #333333; + --ol-subtle-foreground-color: #666666; + --ol-brand-color: #00AAFF; +} + +.ol-box { + box-sizing: border-box; + border-radius: 2px; + border: 1.5px solid var(--ol-background-color); + background-color: var(--ol-partial-background-color); +} + +.ol-mouse-position { + top: 8px; + right: 8px; + position: absolute; +} + +.ol-scale-line { + background: var(--ol-partial-background-color); + border-radius: 4px; + bottom: 8px; + left: 8px; + padding: 2px; + position: absolute; +} + +.ol-scale-line-inner { + border: 1px solid var(--ol-subtle-foreground-color); + border-top: none; + color: var(--ol-foreground-color); + font-size: 10px; + text-align: center; + margin: 1px; + will-change: contents, width; + transition: all 0.25s; +} + +.ol-scale-bar { + position: absolute; + bottom: 8px; + left: 8px; +} + +.ol-scale-bar-inner { + display: flex; +} + +.ol-scale-step-marker { + width: 1px; + height: 15px; + background-color: var(--ol-foreground-color); + float: right; + z-index: 10; +} + +.ol-scale-step-text { + position: absolute; + bottom: -5px; + font-size: 10px; + z-index: 11; + color: var(--ol-foreground-color); + text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color); +} + +.ol-scale-text { + position: absolute; + font-size: 12px; + text-align: center; + bottom: 25px; + color: var(--ol-foreground-color); + text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color); +} + +.ol-scale-singlebar { + position: relative; + height: 10px; + z-index: 9; + box-sizing: border-box; + border: 1px solid var(--ol-foreground-color); +} + +.ol-scale-singlebar-even { + background-color: var(--ol-subtle-foreground-color); +} + +.ol-scale-singlebar-odd { + background-color: var(--ol-background-color); +} + +.ol-unsupported { + display: none; +} + +.ol-viewport, +.ol-unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; +} + +.ol-viewport canvas { + all: unset; + overflow: hidden; +} + +.ol-viewport { + touch-action: pan-x pan-y; +} + +.ol-selectable { + -webkit-touch-callout: default; + -webkit-user-select: text; + -moz-user-select: text; + user-select: text; +} + +.ol-grabbing { + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; +} + +.ol-grab { + cursor: move; + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} + +.ol-control { + position: absolute; + background-color: var(--ol-subtle-background-color); + border-radius: 4px; +} + +.ol-zoom { + top: .5em; + left: .5em; +} + +.ol-rotate { + top: .5em; + right: .5em; + transition: opacity .25s linear, visibility 0s linear; +} + +.ol-rotate.ol-hidden { + opacity: 0; + visibility: hidden; + transition: opacity .25s linear, visibility 0s linear .25s; +} + +.ol-zoom-extent { + top: 4.643em; + left: .5em; +} + +.ol-full-screen { + right: .5em; + top: .5em; +} + +.ol-control button { + display: block; + margin: 1px; + padding: 0; + color: var(--ol-subtle-foreground-color); + font-weight: bold; + text-decoration: none; + font-size: inherit; + text-align: center; + height: 1.375em; + width: 1.375em; + line-height: .4em; + background-color: var(--ol-background-color); + border: none; + border-radius: 2px; +} + +.ol-control button::-moz-focus-inner { + border: none; + padding: 0; +} + +.ol-zoom-extent button { + line-height: 1.4em; +} + +.ol-compass { + display: block; + font-weight: normal; + will-change: transform; +} + +.ol-touch .ol-control button { + font-size: 1.5em; +} + +.ol-touch .ol-zoom-extent { + top: 5.5em; +} + +.ol-control button:hover, +.ol-control button:focus { + text-decoration: none; + outline: 1px solid var(--ol-subtle-foreground-color); + color: var(--ol-foreground-color); +} + +.ol-zoom .ol-zoom-in { + border-radius: 2px 2px 0 0; +} + +.ol-zoom .ol-zoom-out { + border-radius: 0 0 2px 2px; +} + +.ol-attribution { + text-align: right; + bottom: .5em; + right: .5em; + max-width: calc(100% - 1.3em); + display: flex; + flex-flow: row-reverse; + align-items: center; +} + +.ol-attribution a { + color: var(--ol-subtle-foreground-color); + text-decoration: none; +} + +.ol-attribution ul { + margin: 0; + padding: 1px .5em; + color: var(--ol-foreground-color); + text-shadow: 0 0 2px var(--ol-background-color); + font-size: 12px; +} + +.ol-attribution li { + display: inline; + list-style: none; +} + +.ol-attribution li:not(:last-child):after { + content: " "; +} + +.ol-attribution img { + max-height: 2em; + max-width: inherit; + vertical-align: middle; +} + +.ol-attribution button { + flex-shrink: 0; +} + +.ol-attribution.ol-collapsed ul { + display: none; +} + +.ol-attribution:not(.ol-collapsed) { + background: var(--ol-partial-background-color); +} + +.ol-attribution.ol-uncollapsible { + bottom: 0; + right: 0; + border-radius: 4px 0 0; +} + +.ol-attribution.ol-uncollapsible img { + margin-top: -.2em; + max-height: 1.6em; +} + +.ol-attribution.ol-uncollapsible button { + display: none; +} + +.ol-zoomslider { + top: 4.5em; + left: .5em; + height: 200px; +} + +.ol-zoomslider button { + position: relative; + height: 10px; +} + +.ol-touch .ol-zoomslider { + top: 5.5em; +} + +.ol-overviewmap { + left: 0.5em; + bottom: 0.5em; +} + +.ol-overviewmap.ol-uncollapsible { + bottom: 0; + left: 0; + border-radius: 0 4px 0 0; +} + +.ol-overviewmap .ol-overviewmap-map, +.ol-overviewmap button { + display: block; +} + +.ol-overviewmap .ol-overviewmap-map { + border: 1px solid var(--ol-subtle-foreground-color); + height: 150px; + width: 150px; +} + +.ol-overviewmap:not(.ol-collapsed) button { + bottom: 0; + left: 0; + position: absolute; +} + +.ol-overviewmap.ol-collapsed .ol-overviewmap-map, +.ol-overviewmap.ol-uncollapsible button { + display: none; +} + +.ol-overviewmap:not(.ol-collapsed) { + background: var(--ol-subtle-background-color); +} + +.ol-overviewmap-box { + border: 1.5px dotted var(--ol-subtle-foreground-color); +} + +.ol-overviewmap .ol-overviewmap-box:hover { + cursor: move; +} + +.ol-overviewmap .ol-viewport:hover { + cursor: pointer; +} diff --git a/application/v2_ui/public/vendor/openlayers-10.6.1/ol.js b/application/v2_ui/public/vendor/openlayers-10.6.1/ol.js new file mode 100644 index 000000000..60cd7c9f5 --- /dev/null +++ b/application/v2_ui/public/vendor/openlayers-10.6.1/ol.js @@ -0,0 +1,2 @@ +var ol=function(){"use strict";var t="add",e="remove",i="propertychange",n="change",r="error",s="contextmenu",o="click",a="dblclick",l="dragenter",h="dragover",c="drop",u="keydown",d="keypress",g="load",f="touchmove",p="wheel";class m{constructor(){this.disposed=!1}dispose(){this.disposed||(this.disposed=!0,this.disposeInternal())}disposeInternal(){}}function _(t,e,i){let n,r;i=i||y;let s=0,o=t.length,a=!1;for(;s>1),r=+i(t[n],e),r<0?s=n+1:(o=n,a=!r);return a?s:~s}function y(t,e){return t>e?1:te?-1:0}function v(t,e,i){if(t[0]<=e)return 0;const n=t.length;if(e<=t[n-1])return n-1;if("function"==typeof i){for(let r=1;r0?r-1:r}return n-1}if(i>0){for(let i=1;i0||i&&0===s)}))}function C(){return!0}function R(){return!1}function b(){}function P(t){let e,i,n;return function(){const r=Array.prototype.slice.call(arguments);return i&&this===n&&w(r,i)||(n=this,i=r,e=t.apply(this,arguments)),e}}function I(t){return function(){let e;try{e=t()}catch(t){return Promise.reject(t)}return e instanceof Promise?e:Promise.resolve(e)}()}function F(t){for(const e in t)delete t[e]}function L(t){let e;for(e in t)return!1;return!e}class M{constructor(t){this.propagationStopped,this.defaultPrevented,this.type=t,this.target=null}preventDefault(){this.defaultPrevented=!0}stopPropagation(){this.propagationStopped=!0}}function A(t){t.stopPropagation()}class O extends m{constructor(t){super(),this.eventTarget_=t,this.pendingRemovals_=null,this.dispatching_=null,this.listeners_=null}addEventListener(t,e){if(!t||!e)return;const i=this.listeners_||(this.listeners_={}),n=i[t]||(i[t]=[]);n.includes(e)||n.push(e)}dispatchEvent(t){const e="string"==typeof t,i=e?t:t.type,n=this.listeners_&&this.listeners_[i];if(!n)return;const r=e?new M(t):t;r.target||(r.target=this.eventTarget_||this);const s=this.dispatching_||(this.dispatching_={}),o=this.pendingRemovals_||(this.pendingRemovals_={});let a;i in s||(s[i]=0,o[i]=0),++s[i];for(let t=0,e=n.length;t0)}removeEventListener(t,e){if(!this.listeners_)return;const i=this.listeners_[t];if(!i)return;const n=i.indexOf(e);-1!==n&&(this.pendingRemovals_&&t in this.pendingRemovals_?(i[n]=b,++this.pendingRemovals_[t]):(i.splice(n,1),0===i.length&&delete this.listeners_[t]))}}function D(t,e,i,n,r){if(r){const r=i;i=function(s){return t.removeEventListener(e,i),r.call(n??this,s)}}else n&&n!==t&&(i=i.bind(n));const s={target:t,type:e,listener:i};return t.addEventListener(e,i),s}function N(t,e,i,n){return D(t,e,i,n,!0)}function k(t){t&&t.target&&(t.target.removeEventListener(t.type,t.listener),F(t))}class G extends O{constructor(){super(),this.on=this.onInternal,this.once=this.onceInternal,this.un=this.unInternal,this.revision_=0}changed(){++this.revision_,this.dispatchEvent(n)}getRevision(){return this.revision_}onInternal(t,e){if(Array.isArray(t)){const i=t.length,n=new Array(i);for(let r=0;r0;)this.pop()}extend(t){for(let e=0,i=t.length;ethis.getLength())throw new Error("Index out of bounds: "+e);this.unique_&&this.assertUnique_(i),this.array_.splice(e,0,i),this.updateLength_(),this.dispatchEvent(new W(t,i,e))}pop(){return this.removeAt(this.getLength()-1)}push(t){this.unique_&&this.assertUnique_(t);const e=this.getLength();return this.insertAt(e,t),this.getLength()}remove(t){const e=this.array_;for(let i=0,n=e.length;i=this.getLength())return;const i=this.array_[t];return this.array_.splice(t,1),this.updateLength_(),this.dispatchEvent(new W(e,i,t)),i}setAt(i,n){if(i>=this.getLength())return void this.insertAt(i,n);if(i<0)throw new Error("Index out of bounds: "+i);this.unique_&&this.assertUnique_(n,i);const r=this.array_[i];this.array_[i]=n,this.dispatchEvent(new W(e,r,i)),this.dispatchEvent(new W(t,n,i))}updateLength_(){this.set($,this.array_.length)}assertUnique_(t,e){for(let i=0,n=this.array_.length;it)throw new Error("Tile load sequence violation");this.state=t,this.changed()}}load(){U()}getAlpha(t,e){if(!this.transition_)return 1;let i=this.transitionStarts_[t];if(i){if(-1===i)return 1}else i=e,this.transitionStarts_[t]=i;const n=e-i+1e3/60;return n>=this.transition_?1:Q(n/this.transition_)}inTransition(t){return!!this.transition_&&-1!==this.transitionStarts_[t]}endTransition(t){this.transition_&&(this.transitionStarts_[t]=-1)}disposeInternal(){this.release(),super.disposeInternal()}}const rt="undefined"!=typeof navigator&&void 0!==navigator.userAgent?navigator.userAgent.toLowerCase():"",st=rt.includes("safari")&&!rt.includes("chrom"),ot=st&&(rt.includes("version/15.4")||/cpu (os|iphone os) 15_4 like mac os x/.test(rt)),at=rt.includes("webkit")&&!rt.includes("edge"),lt=rt.includes("macintosh"),ht="undefined"!=typeof devicePixelRatio?devicePixelRatio:1,ct="undefined"!=typeof WorkerGlobalScope&&"undefined"!=typeof OffscreenCanvas&&self instanceof WorkerGlobalScope,ut="undefined"!=typeof Image&&Image.prototype.decode,dt="function"==typeof createImageBitmap,gt=function(){let t=!1;try{const e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("_",null,e),window.removeEventListener("_",null,e)}catch{}return t}();function ft(t,e,i,n){let r;return r=i&&i.length?i.shift():ct?new OffscreenCanvas(t||300,e||300):document.createElement("canvas"),t&&(r.width=t),e&&(r.height=e),r.getContext("2d",n)}let pt;function mt(){return pt||(pt=ft(1,1)),pt}function _t(t){const e=t.canvas;e.width=1,e.height=1,t.clearRect(0,0,1,1)}function yt(t){let e=t.offsetWidth;const i=getComputedStyle(t);return e+=parseInt(i.marginLeft,10)+parseInt(i.marginRight,10),e}function xt(t){let e=t.offsetHeight;const i=getComputedStyle(t);return e+=parseInt(i.marginTop,10)+parseInt(i.marginBottom,10),e}function vt(t,e){const i=e.parentNode;i&&i.replaceChild(t,e)}function St(t){for(;t.lastChild;)t.lastChild.remove()}function Et(t,e){const i=t.childNodes;for(let n=0;;++n){const r=i[n],s=e[n];if(!r&&!s)break;r!==s&&(r?s?t.insertBefore(s,r):(t.removeChild(r),--n):t.appendChild(s))}}function wt(t){return t instanceof Image||t instanceof HTMLCanvasElement||t instanceof HTMLVideoElement||t instanceof ImageBitmap?t:null}function Tt(t){return t instanceof Uint8Array||t instanceof Uint8ClampedArray||t instanceof Float32Array||t instanceof DataView?t:null}const Ct=new Error("disposed");let Rt=null;function bt(t){Rt||(Rt=ft(t.width,t.height,void 0,{willReadFrequently:!0}));const e=Rt.canvas,i=t.width;e.width!==i&&(e.width=i);const n=t.height;return e.height!==n&&(e.height=n),Rt.clearRect(0,0,i,n),Rt.drawImage(t,0,0),Rt.getImageData(0,0,i,n).data}const Pt=[256,256];class It extends nt{constructor(t){const e=Y;super(t.tileCoord,e,{transition:t.transition,interpolate:t.interpolate}),this.loader_=t.loader,this.data_=null,this.error_=null,this.size_=t.size||null,this.controller_=t.controller||null}getSize(){if(this.size_)return this.size_;const t=wt(this.data_);return t?[t.width,t.height]:Pt}getData(){return this.data_}getError(){return this.error_}load(){if(this.state!==Y&&this.state!==q)return;this.state=H,this.changed();const t=this;this.loader_().then((function(e){t.data_=e,t.state=K,t.changed()})).catch((function(e){t.error_=e,t.state=q,t.changed()}))}disposeInternal(){this.controller_&&(this.controller_.abort(Ct),this.controller_=null),super.disposeInternal()}}function Ft(t,e){if(!t)throw new Error(e)}class Lt extends V{constructor(t){if(super(),this.on,this.once,this.un,this.id_=void 0,this.geometryName_="geometry",this.style_=null,this.styleFunction_=void 0,this.geometryChangeKey_=null,this.addChangeListener(this.geometryName_,this.handleGeometryChanged_),t)if("function"==typeof t.getSimplifiedGeometry){const e=t;this.setGeometry(e)}else{const e=t;this.setProperties(e)}}clone(){const t=new Lt(this.hasProperties()?this.getProperties():null);t.setGeometryName(this.getGeometryName());const e=this.getGeometry();e&&t.setGeometry(e.clone());const i=this.getStyle();return i&&t.setStyle(i),t}getGeometry(){return this.get(this.geometryName_)}getId(){return this.id_}getGeometryName(){return this.geometryName_}getStyle(){return this.style_}getStyleFunction(){return this.styleFunction_}handleGeometryChange_(){this.changed()}handleGeometryChanged_(){this.geometryChangeKey_&&(k(this.geometryChangeKey_),this.geometryChangeKey_=null);const t=this.getGeometry();t&&(this.geometryChangeKey_=D(t,n,this.handleGeometryChange_,this)),this.changed()}setGeometry(t){this.set(this.geometryName_,t)}setStyle(t){this.style_=t,this.styleFunction_=t?Mt(t):void 0,this.changed()}setId(t){this.id_=t,this.changed()}setGeometryName(t){this.removeChangeListener(this.geometryName_,this.handleGeometryChanged_),this.geometryName_=t,this.addChangeListener(this.geometryName_,this.handleGeometryChanged_),this.handleGeometryChanged_()}}function Mt(t){if("function"==typeof t)return t;let e;if(Array.isArray(t))e=t;else{Ft("function"==typeof t.getZIndex,"Expected an `ol/style/Style` or an array of `ol/style/Style.js`");e=[t]}return function(){return e}}var At=0,Ot=1,Dt=2,Nt=4,kt=8,Gt=16;function jt(t){const e=Zt();for(let i=0,n=t.length;ir&&(l|=Nt),as&&(l|=Dt),l===At&&(l=Ot),l}function Zt(){return[1/0,1/0,-1/0,-1/0]}function Yt(t,e,i,n,r){return r?(r[0]=t,r[1]=e,r[2]=i,r[3]=n,r):[t,e,i,n]}function Ht(t){return Yt(1/0,1/0,-1/0,-1/0,t)}function Kt(t,e){const i=t[0],n=t[1];return Yt(i,n,i,n,e)}function qt(t,e,i,n,r){return ne(Ht(r),t,e,i,n)}function Jt(t,e){return t[0]==e[0]&&t[2]==e[2]&&t[1]==e[1]&&t[3]==e[3]}function Qt(t,e,i){return Math.abs(t[0]-e[0])t[2]&&(t[2]=e[2]),e[1]t[3]&&(t[3]=e[3]),t}function ee(t,e){e[0]t[2]&&(t[2]=e[0]),e[1]t[3]&&(t[3]=e[1])}function ie(t,e){for(let i=0,n=e.length;ie[0]?n[0]=t[0]:n[0]=e[0],t[1]>e[1]?n[1]=t[1]:n[1]=e[1],t[2]=e[0]&&t[1]<=e[3]&&t[3]>=e[1]}function ve(t){return t[2]=o&&p<=l),n||!(s&Nt)||r&Nt||(m=g-(d-l)*f,n=m>=a&&m<=h),n||!(s&kt)||r&kt||(p=d-(g-a)/f,n=p>=o&&p<=l),n||!(s&Gt)||r&Gt||(m=g-(d-o)*f,n=m>=a&&m<=h)}return n}function Te(t,e,i,n){if(ve(t))return Ht(i);let r=[];if(n>1){const e=t[2]-t[0],i=t[3]-t[1];for(let s=0;s=i[2])){const e=ye(i),r=Math.floor((n[0]-i[0])/e)*e;t[0]-=r,t[2]-=r}return t}function Re(t,e,i){if(e.canWrapX()){const n=e.getExtent();if(!isFinite(t[0])||!isFinite(t[2]))return[[n[0],t[1],n[2],t[3]]];Ce(t,e);const r=ye(n);if(ye(t)>r&&!i)return[[n[0],t[1],n[2],t[3]]];if(t[0]n[2])return[[t[0],t[1],n[2],t[3]],[n[0],t[1],t[2]-r,t[3]]]}return[t]}function be(t,e,i){return Math.min(Math.max(t,e),i)}function Pe(t,e,i,n,r,s){const o=r-i,a=s-n;if(0!==o||0!==a){const l=((t-i)*o+(e-n)*a)/(o*o+a*a);l>1?(i=r,n=s):l>0&&(i+=o*l,n+=a*l)}return Ie(t,e,i,n)}function Ie(t,e,i,n){const r=i-t,s=n-e;return r*r+s*s}function Fe(t){const e=t.length;for(let i=0;ir&&(r=e,n=s)}if(0===r)return null;const s=t[n];t[n]=t[i],t[i]=s;for(let n=i+1;n=0;n--){i[n]=t[n][e]/t[n][n];for(let r=n-1;r>=0;r--)t[r][e]-=t[r][n]*i[n]}return i}function Le(t){return 180*t/Math.PI}function Me(t){return t*Math.PI/180}function Ae(t,e){const i=t%e;return i*e<0?i+e:i}function Oe(t,e,i){return t+i*(e-t)}function De(t,e){const i=Math.pow(10,e);return Math.round(t*i)/i}function Ne(t,e){return Math.round(De(t,e))}function ke(t,e){return Math.floor(De(t,e))}function Ge(t,e){return Math.ceil(De(t,e))}function je(t,e,i){if(t>=e&&t$e.warn||console.warn(...t)}function Ye(...t){We>$e.error||console.error(...t)}function He(t,e,i){const n=void 0!==i?t.toFixed(i):""+t;let r=n.indexOf(".");return r=-1===r?n.length:r,r>e?n:new Array(1+e-r).join("0")+n}function Ke(t,e){const i=(""+t).split("."),n=(""+e).split(".");for(let t=0;tr)return 1;if(r>e)return-1}return 0}function qe(t,e){return t[0]+=+e[0],t[1]+=+e[1],t}function Je(t,e){const i=e.getRadius(),n=e.getCenter(),r=n[0],s=n[1];let o=t[0]-r;const a=t[1]-s;0===o&&0===a&&(o=1);const l=Math.sqrt(o*o+a*a);return[r+i*o/l,s+i*a/l]}function Qe(t,e){const i=t[0],n=t[1],r=e[0],s=e[1],o=r[0],a=r[1],l=s[0],h=s[1],c=l-o,u=h-a,d=0===c&&0===u?0:(c*(i-o)+u*(n-a))/(c*c+u*u||0);let g,f;return d<=0?(g=o,f=a):d>=1?(g=l,f=h):(g=o+d*c,f=a+d*u),[g,f]}function ti(t,e,i){const n=Ae(e+180,360)-180,r=Math.abs(3600*n),s=i||0;let o=Math.floor(r/3600),a=Math.floor((r-3600*o)/60),l=De(r-3600*o-60*a,s);l>=60&&(l=0,a+=1),a>=60&&(a=0,o+=1);let h=o+"°";return 0===a&&0===l||(h+=" "+He(a,2)+"′"),0!==l&&(h+=" "+He(l,2,s)+"″"),0!==n&&(h+=" "+t.charAt(n<0?1:0)),h}function ei(t,e,i){return t?e.replace("{x}",t[0].toFixed(i)).replace("{y}",t[1].toFixed(i)):""}function ii(t,e){let i=!0;for(let n=t.length-1;n>=0;--n)if(t[n]!=e[n]){i=!1;break}return i}function ni(t,e){const i=Math.cos(e),n=Math.sin(e),r=t[0]*i-t[1]*n,s=t[1]*i+t[0]*n;return t[0]=r,t[1]=s,t}function ri(t,e){return t[0]*=e,t[1]*=e,t}function si(t,e){const i=t[0]-e[0],n=t[1]-e[1];return i*i+n*n}function oi(t,e){return Math.sqrt(si(t,e))}function ai(t,e){return si(t,Qe(t,e))}function li(t,e){return ei(t,"{x}, {y}",e)}function hi(t,e){if(e.canWrapX()){const i=ye(e.getExtent()),n=ci(t,e,i);n&&(t[0]-=n*i)}return t}function ci(t,e,i){const n=e.getExtent();let r=0;return e.canWrapX()&&(t[0]n[2])&&(i=i||ye(n),r=Math.floor((t[0]-n[0])/i)),r}const ui={9001:"m",9002:"ft",9003:"us-ft",9101:"radians",9102:"degrees"};function di(t){return ui[t]}const gi={radians:6370997/(2*Math.PI),degrees:2*Math.PI*6370997/360,ft:.3048,m:1,"us-ft":1200/3937};class fi{constructor(t){this.code_=t.code,this.units_=t.units,this.extent_=void 0!==t.extent?t.extent:null,this.worldExtent_=void 0!==t.worldExtent?t.worldExtent:null,this.axisOrientation_=void 0!==t.axisOrientation?t.axisOrientation:"enu",this.global_=void 0!==t.global&&t.global,this.canWrapX_=!(!this.global_||!this.extent_),this.getPointResolutionFunc_=t.getPointResolution,this.defaultTileGrid_=null,this.metersPerUnit_=t.metersPerUnit}canWrapX(){return this.canWrapX_}getCode(){return this.code_}getExtent(){return this.extent_}getUnits(){return this.units_}getMetersPerUnit(){return this.metersPerUnit_||gi[this.units_]}getWorldExtent(){return this.worldExtent_}getAxisOrientation(){return this.axisOrientation_}isGlobal(){return this.global_}setGlobal(t){this.global_=t,this.canWrapX_=!(!t||!this.extent_)}getDefaultTileGrid(){return this.defaultTileGrid_}setDefaultTileGrid(t){this.defaultTileGrid_=t}setExtent(t){this.extent_=t,this.canWrapX_=!(!this.global_||!t)}setWorldExtent(t){this.worldExtent_=t}setGetPointResolution(t){this.getPointResolutionFunc_=t}getPointResolutionFunc(){return this.getPointResolutionFunc_}}const pi=6378137,mi=Math.PI*pi,_i=[-mi,-mi,mi,mi],yi=[-180,-85,180,85],xi=pi*Math.log(Math.tan(Math.PI/2));class vi extends fi{constructor(t){super({code:t,units:"m",extent:_i,global:!0,worldExtent:yi,getPointResolution:function(t,e){return t/Math.cosh(e[1]/pi)}})}}const Si=[new vi("EPSG:3857"),new vi("EPSG:102100"),new vi("EPSG:102113"),new vi("EPSG:900913"),new vi("http://www.opengis.net/def/crs/EPSG/0/3857"),new vi("http://www.opengis.net/gml/srs/epsg.xml#3857")];function Ei(t,e,i,n){const r=t.length;i=i>1?i:2,n=n??i,void 0===e&&(e=i>2?t.slice():new Array(r));for(let i=0;ixi?n=xi:n<-xi&&(n=-xi),e[i+1]=n}return e}function wi(t,e,i,n){const r=t.length;i=i>1?i:2,n=n??i,void 0===e&&(e=i>2?t.slice():new Array(r));for(let i=0;i84&&(e=84);const n=Me(e),r=Math.sin(n),s=Math.cos(n),o=r/s,a=o*o,l=a*a,h=Me(t),c=Me(sn(i.number)),u=en/Math.sqrt(1-Gi*r**2),d=Bi*s**2,g=s*je(h-c,-Math.PI,Math.PI),f=g*g,p=f*g,m=p*g,_=m*g,y=_*g,x=en*(Yi*n-.002514607064228144*Math.sin(2*n)+Hi*Math.sin(4*n)-Ki*Math.sin(6*n)),v=ki*u*(g+p/6*(1-a+d)+_/120*(5-18*a+l+72*d-58*Bi))+5e5;let S=ki*(x+u*o*(f/2+m/24*(5-a+9*d+4*d**2)+y/720*(61-58*a+l+600*d-330*Bi)));return i.north||(S+=1e7),[v,S]}function sn(t){return 6*(t-1)-180+3}const on=[/^EPSG:(\d+)$/,/^urn:ogc:def:crs:EPSG::(\d+)$/,/^http:\/\/www\.opengis\.net\/def\/crs\/EPSG\/0\/(\d+)$/];function an(t){let e=0;for(const i of on){const n=t.match(i);if(n){e=parseInt(n[1]);break}}if(!e)return null;let i=0,n=!1;return e>32700&&e<32761?i=e-32700:e>32600&&e<32661&&(n=!0,i=e-32600),i?{number:i,north:n}:null}function ln(t,e){return function(i,n,r,s){const o=i.length;r=r>1?r:2,s=s??r,n||(n=r>2?i.slice():new Array(o));for(let r=0;r=a?e[o+t]:s[t]}return i}}function Cn(t,e,i,n){const r=xn(t),s=xn(e);Di(r,s,Tn(i)),Di(s,r,Tn(n))}function Rn(t,e){const i=Ln(t,void 0!==e?e:"EPSG:3857","EPSG:4326"),n=i[0];return(n<-180||n>180)&&(i[0]=Ae(n+180,360)-180),i}function bn(t,e){if(t===e)return!0;const i=t.getUnits()===e.getUnits();if(t.getCode()===e.getCode())return i;return Pn(t,e)===pn&&i}function Pn(t,e){const i=t.getCode(),n=e.getCode();let r=Ni(i,n);if(r)return r;let s=null,o=null;for(const i of un)s||(s=i(t)),o||(o=i(e));if(!s&&!o)return null;const a="EPSG:4326";if(o)if(s)r=In(s.inverse,o.forward);else{const t=Ni(i,a);t&&(r=In(t,o.forward))}else{const t=Ni(a,n);t&&(r=In(s.inverse,t))}return r&&(_n(t),_n(e),Di(t,e,r)),r}function In(t,e){return function(i,n,r,s){return n=t(i,n,r,s),e(n,n,r,s)}}function Fn(t,e){return Pn(xn(t),xn(e))}function Ln(t,e,i){const n=Fn(e,i);if(!n){const t=xn(e).getCode(),n=xn(i).getCode();throw new Error(`No transform available between ${t} and ${n}`)}return n(t,void 0,t.length)}function Mn(t,e,i,n){return Te(t,Fn(e,i),void 0,n)}let An=null;function On(t){An=xn(t)}function Dn(){return An}function Nn(t,e){return An?Ln(t,e,An):t}function kn(t,e){return An?Ln(t,An,e):(gn&&!ii(t,[0,0])&&t[0]>=-180&&t[0]<=180&&t[1]>=-90&&t[1]<=90&&(gn=!1,Ze("Call useGeographic() from ol/proj once to work with [longitude, latitude] coordinates.")),t)}function Gn(t,e){return An?Mn(t,e,An):t}function jn(t,e){return An?Mn(t,An,e):t}function Un(t,e){if(!An)return t;const i=xn(e).getMetersPerUnit(),n=An.getMetersPerUnit();return i&&n?t*i/n:t}function Bn(t,e){if(!An)return t;const i=xn(e).getMetersPerUnit(),n=An.getMetersPerUnit();return i&&n?t*n/i:t}function zn(t,e,i){return function(n){let r,s;if(t.canWrapX()){const e=t.getExtent(),o=ye(e);s=ci(n=n.slice(0),t,o),s&&(n[0]=n[0]-s*o),n[0]=be(n[0],e[0],e[2]),n[1]=be(n[1],e[1],e[3]),r=i(n)}else r=i(n);return s&&e.canWrapX()&&(r[0]+=s*ye(e.getExtent())),r}}function Xn(){Sn(Si),Sn(Pi),En(Pi,Si,Ei,wi)}Xn();const Vn=new Array(6);function $n(){return[1,0,0,1,0,0]}function Wn(t){return Yn(t,1,0,0,1,0,0)}function Zn(t,e){const i=t[0],n=t[1],r=t[2],s=t[3],o=t[4],a=t[5],l=e[0],h=e[1],c=e[2],u=e[3],d=e[4],g=e[5];return t[0]=i*l+r*h,t[1]=n*l+s*h,t[2]=i*c+r*u,t[3]=n*c+s*u,t[4]=i*d+r*g+o,t[5]=n*d+s*g+a,t}function Yn(t,e,i,n,r,s,o){return t[0]=e,t[1]=i,t[2]=n,t[3]=r,t[4]=s,t[5]=o,t}function Hn(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4],t[5]=e[5],t}function Kn(t,e){const i=e[0],n=e[1];return e[0]=t[0]*i+t[2]*n+t[4],e[1]=t[1]*i+t[3]*n+t[5],e}function qn(t,e){const i=Math.cos(e),n=Math.sin(e);return Zn(t,Yn(Vn,i,n,-n,i,0,0))}function Jn(t,e,i){return Zn(t,Yn(Vn,e,0,0,i,0,0))}function Qn(t,e,i){return Zn(t,Yn(Vn,1,0,0,1,e,i))}function tr(t,e,i,n,r,s,o,a){const l=Math.sin(s),h=Math.cos(s);return t[0]=n*h,t[1]=r*l,t[2]=-n*l,t[3]=r*h,t[4]=o*n*h-a*n*l+e,t[5]=o*r*l+a*r*h+i,t}function er(t,e){const i=ir(e);Ft(0!==i,"Transformation matrix cannot be inverted");const n=e[0],r=e[1],s=e[2],o=e[3],a=e[4],l=e[5];return t[0]=o/i,t[1]=-r/i,t[2]=-s/i,t[3]=n/i,t[4]=(s*l-o*a)/i,t[5]=-(n*l-r*a)/i,t}function ir(t){return t[0]*t[3]-t[1]*t[2]}const nr=[1e5,1e5,1e5,1e5,2,2];function rr(t){return"matrix("+t.join(", ")+")"}function sr(t){return t.substring(7,t.length-1).split(",").map(parseFloat)}function or(t,e){const i=sr(t),n=sr(e);for(let t=0;t<6;++t)if(0!==Math.round((i[t]-n[t])*nr[t]))return!1;return!0}function ar(t,e,i,n,r,s,o){s=s||[],o=o||2;let a=0;for(let l=e;l{if(!i)return this.getSimplifiedGeometry(e);const n=this.clone();return n.applyTransform(i),n.getSimplifiedGeometry(e)}))}simplifyTransformed(t,e){return this.simplifyTransformedInternal(this.getRevision(),t,e)}clone(){return U()}closestPointXY(t,e,i,n){return U()}containsXY(t,e){return 0===this.closestPointXY(t,e,dr,Number.MIN_VALUE)}getClosestPoint(t,e){return e=e||[NaN,NaN],this.closestPointXY(t[0],t[1],e,1/0),e}intersectsCoordinate(t){return this.containsXY(t[0],t[1])}computeExtent(t){return U()}getExtent(t){if(this.extentRevision_!=this.getRevision()){const t=this.computeExtent(this.extent_);(isNaN(t[0])||isNaN(t[1]))&&Ht(t),this.extentRevision_=this.getRevision()}return Se(this.extent_,t)}rotate(t,e){U()}scale(t,e,i){U()}simplify(t){return this.getSimplifiedGeometry(t*t)}getSimplifiedGeometry(t){return U()}getType(){return U()}applyTransform(t){U()}intersectsExtent(t){return U()}translate(t,e){U()}transform(t,e){const i=xn(t),n="tile-pixels"==i.getUnits()?function(t,n,r){const s=i.getExtent(),o=i.getWorldExtent(),a=fe(o)/fe(s);tr(ur,o[0],o[3],a,-a,0,0,0);const l=ar(t,0,t.length,r,ur,n),h=Fn(i,e);return h?h(l,l,r):l}:Fn(i,e);return this.applyTransform(n),this}}class fr extends gr{constructor(){super(),this.layout="XY",this.stride=2,this.flatCoordinates}computeExtent(t){return qt(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t)}getCoordinates(){return U()}getFirstCoordinate(){return this.flatCoordinates.slice(0,this.stride)}getFlatCoordinates(){return this.flatCoordinates}getLastCoordinate(){return this.flatCoordinates.slice(this.flatCoordinates.length-this.stride)}getLayout(){return this.layout}getSimplifiedGeometry(t){if(this.simplifiedGeometryRevision!==this.getRevision()&&(this.simplifiedGeometryMaxMinSquaredTolerance=0,this.simplifiedGeometryRevision=this.getRevision()),t<0||0!==this.simplifiedGeometryMaxMinSquaredTolerance&&t<=this.simplifiedGeometryMaxMinSquaredTolerance)return this;const e=this.getSimplifiedGeometryInternal(t);return e.getFlatCoordinates().length1)u=i;else{if(d>0){for(let r=0;rr&&(r=a),s=i,o=n}return r}function wr(t,e,i,n,r){for(let s=0,o=i.length;s0;){const i=h.pop(),s=h.pop();let o=0;const a=t[s],u=t[s+1],d=t[i],g=t[i+1];for(let e=s+n;eo&&(c=e,o=i)}o>r&&(l[(c-e)/n]=1,s+nr&&(s[o++]=h,s[o++]=c,a=h,l=c);return h==a&&c==l||(s[o++]=h,s[o++]=c),o}function Gr(t,e){return e*Math.round(t/e)}function jr(t,e,i,n,r,s,o){if(e==i)return o;let a,l,h=Gr(t[e],r),c=Gr(t[e+1],r);e+=n,s[o++]=h,s[o++]=c;do{if(a=Gr(t[e],r),l=Gr(t[e+1],r),(e+=n)==i)return s[o++]=a,s[o++]=l,o}while(a==h&&l==c);for(;e0&&f>d)&&(g<0&&p0&&p>g)?(a=i,l=u):(s[o++]=a,s[o++]=l,h=a,c=l,a=i,l=u)}return s[o++]=a,s[o++]=l,o}function Ur(t,e,i,n,r,s,o,a){for(let l=0,h=i.length;ls&&(i-a)*(s-l)-(r-a)*(n-l)>0&&o++:n<=s&&(i-a)*(s-l)-(r-a)*(n-l)<0&&o--,a=i,l=n}return 0!==o}function Wr(t,e,i,n,r,s){if(0===i.length)return!1;if(!$r(t,e,i[0],n,r,s))return!1;for(let e=1,o=i.length;e_&&(h=(c+u)/2,Wr(t,e,i,n,h,f)&&(m=h,_=r)),c=u}return isNaN(m)&&(m=r[s]),o?(o.push(m,f,_),o):[m,f,_]}function Hr(t,e,i,n,r){let s=[];for(let o=0,a=i.length;o=r[0]&&s[2]<=r[2]||s[1]>=r[1]&&s[3]<=r[3]||Kr(t,e,i,n,(function(t,e){return we(r,t,e)})))}function Qr(t,e,i,n,r){for(let s=0,o=i.length;s0}function ss(t,e,i,n,r){r=void 0!==r&&r;for(let s=0,o=i.length;sthis.loader(this.getExtent(),e,this.getPixelRatio()))).then((t=>{"image"in t&&(this.image_=t.image),"extent"in t&&(this.extent=t.extent),"resolution"in t&&(this.resolution=t.resolution),"pixelRatio"in t&&(this.pixelRatio_=t.pixelRatio),(t instanceof HTMLImageElement||dt&&t instanceof ImageBitmap||t instanceof HTMLCanvasElement||t instanceof HTMLVideoElement)&&(this.image_=t),this.state=Is})).catch((t=>{this.state=Fs,console.error(t)})).finally((()=>this.changed()))}}setImage(t){this.image_=t}setResolution(t){this.resolution=t}}function As(t,e,i){const n=t;let s=!0,o=!1,a=!1;const l=[N(n,g,(function(){a=!0,o||e()}))];return n.src&&ut?(o=!0,n.decode().then((function(){s&&e()})).catch((function(t){s&&(a?e():i())}))):l.push(N(n,r,i)),function(){s=!1,l.forEach(k)}}function Os(t,e){return new Promise(((i,n)=>{function r(){o(),i(t)}function s(){o(),n(new Error("Image load error"))}function o(){t.removeEventListener("load",r),t.removeEventListener("error",s)}t.addEventListener("load",r),t.addEventListener("error",s),e&&(t.src=e)}))}function Ds(t,e){return e&&(t.src=e),t.src&&ut?new Promise(((e,i)=>t.decode().then((()=>e(t))).catch((n=>t.complete&&t.width?e(t):i(n))))):Os(t)}function Ns(t,e){return e&&(t.src=e),t.src&&ut&&dt?t.decode().then((()=>createImageBitmap(t))).catch((e=>{if(t.complete&&t.width)return t;throw e})):Ds(t)}class ks extends Ms{constructor(t,e,i,n,r){super(t,e,i,void 0!==r?bs:Is),this.loader_=void 0!==r?r:null,this.canvas_=n,this.error_=null}getError(){return this.error_}handleLoad_(t){t?(this.error_=t,this.state=Fs):this.state=Is,this.changed()}load(){this.state==bs&&(this.state=Ps,this.changed(),this.loader_(this.handleLoad_.bind(this)))}getImage(){return this.canvas_}}class Gs extends nt{constructor(t,e,i,n,r,s){super(t,e,s),this.crossOrigin_=n,this.src_=i,this.key=i,this.image_=new Image,null!==n&&(this.image_.crossOrigin=n),this.unlisten_=null,this.tileLoadFunction_=r}getImage(){return this.image_}setImage(t){this.image_=t,this.state=K,this.unlistenImage_(),this.changed()}handleImageError_(){this.state=q,this.unlistenImage_(),this.image_=function(){const t=ft(1,1);return t.fillStyle="rgba(0,0,0,0)",t.fillRect(0,0,1,1),t.canvas}(),this.changed()}handleImageLoad_(){const t=this.image_;t.naturalWidth&&t.naturalHeight?this.state=K:this.state=J,this.unlistenImage_(),this.changed()}load(){this.state==q&&(this.state=Y,this.image_=new Image,null!==this.crossOrigin_&&(this.image_.crossOrigin=this.crossOrigin_)),this.state==Y&&(this.state=H,this.changed(),this.tileLoadFunction_(this,this.src_),this.unlisten_=As(this.image_,this.handleImageLoad_.bind(this),this.handleImageError_.bind(this)))}unlistenImage_(){this.unlisten_&&(this.unlisten_(),this.unlisten_=null)}disposeInternal(){this.unlistenImage_(),this.image_=null,super.disposeInternal()}}class js{constructor(t,e,i){this.decay_=t,this.minVelocity_=e,this.delay_=i,this.points_=[],this.angle_=0,this.initialVelocity_=0}begin(){this.points_.length=0,this.angle_=0,this.initialVelocity_=0}update(t,e){this.points_.push(t,e,Date.now())}end(){if(this.points_.length<6)return!1;const t=Date.now()-this.delay_,e=this.points_.length-3;if(this.points_[e+2]0&&this.points_[i+2]>t;)i-=3;const n=this.points_[e+2]-this.points_[i+2];if(n<1e3/60)return!1;const r=this.points_[e]-this.points_[i],s=this.points_[e+1]-this.points_[i+1];return this.angle_=Math.atan2(s,r),this.initialVelocity_=Math.sqrt(r*r+s*s)/n,this.initialVelocity_>this.minVelocity_}getDistance(){return(this.minVelocity_-this.initialVelocity_)/this.decay_}getAngle(){return this.angle_}}class Us extends M{constructor(t,e,i){super(t),this.map=e,this.frameState=void 0!==i?i:null}}class Bs extends Us{constructor(t,e,i,n,r,s){super(t,e,r),this.originalEvent=i,this.pixel_=null,this.coordinate_=null,this.dragging=void 0!==n&&n,this.activePointers=s}get pixel(){return this.pixel_||(this.pixel_=this.map.getEventPixel(this.originalEvent)),this.pixel_}set pixel(t){this.pixel_=t}get coordinate(){return this.coordinate_||(this.coordinate_=this.map.getCoordinateFromPixel(this.pixel)),this.coordinate_}set coordinate(t){this.coordinate_=t}preventDefault(){super.preventDefault(),"preventDefault"in this.originalEvent&&this.originalEvent.preventDefault()}stopPropagation(){super.stopPropagation(),"stopPropagation"in this.originalEvent&&this.originalEvent.stopPropagation()}}var zs={SINGLECLICK:"singleclick",CLICK:o,DBLCLICK:a,POINTERDRAG:"pointerdrag",POINTERMOVE:"pointermove",POINTERDOWN:"pointerdown",POINTERUP:"pointerup",POINTEROVER:"pointerover",POINTEROUT:"pointerout",POINTERENTER:"pointerenter",POINTERLEAVE:"pointerleave",POINTERCANCEL:"pointercancel"},Xs="pointermove",Vs="pointerdown",$s="pointerup",Ws="pointerout";class Zs extends O{constructor(t,e){super(t),this.map_=t,this.clickTimeoutId_,this.emulateClicks_=!1,this.dragging_=!1,this.dragListenerKeys_=[],this.moveTolerance_=void 0===e?1:e,this.down_=null;const i=this.map_.getViewport();this.activePointers_=[],this.trackedTouches_={},this.element_=i,this.pointerdownListenerKey_=D(i,Vs,this.handlePointerDown_,this),this.originalPointerMoveEvent_,this.relayedListenerKey_=D(i,Xs,this.relayMoveEvent_,this),this.boundHandleTouchMove_=this.handleTouchMove_.bind(this),this.element_.addEventListener(f,this.boundHandleTouchMove_,!!gt&&{passive:!1})}emulateClick_(t){let e=new Bs(zs.CLICK,this.map_,t);this.dispatchEvent(e),void 0!==this.clickTimeoutId_?(clearTimeout(this.clickTimeoutId_),this.clickTimeoutId_=void 0,e=new Bs(zs.DBLCLICK,this.map_,t),this.dispatchEvent(e)):this.clickTimeoutId_=setTimeout((()=>{this.clickTimeoutId_=void 0;const e=new Bs(zs.SINGLECLICK,this.map_,t);this.dispatchEvent(e)}),250)}updateActivePointers_(t){const e=t,i=e.pointerId;if(e.type==zs.POINTERUP||e.type==zs.POINTERCANCEL){delete this.trackedTouches_[i];for(const t in this.trackedTouches_)if(this.trackedTouches_[t].target!==e.target){delete this.trackedTouches_[t];break}}else e.type!=zs.POINTERDOWN&&e.type!=zs.POINTERMOVE||(this.trackedTouches_[i]=e);this.activePointers_=Object.values(this.trackedTouches_)}handlePointerUp_(t){this.updateActivePointers_(t);const e=new Bs(zs.POINTERUP,this.map_,t,void 0,void 0,this.activePointers_);this.dispatchEvent(e),this.emulateClicks_&&!e.defaultPrevented&&!this.dragging_&&this.isMouseActionButton_(t)&&this.emulateClick_(this.down_),0===this.activePointers_.length&&(this.dragListenerKeys_.forEach(k),this.dragListenerKeys_.length=0,this.dragging_=!1,this.down_=null)}isMouseActionButton_(t){return 0===t.button}handlePointerDown_(t){this.emulateClicks_=0===this.activePointers_.length,this.updateActivePointers_(t);const e=new Bs(zs.POINTERDOWN,this.map_,t,void 0,void 0,this.activePointers_);if(this.dispatchEvent(e),this.down_=new PointerEvent(t.type,t),Object.defineProperty(this.down_,"target",{writable:!1,value:t.target}),0===this.dragListenerKeys_.length){const t=this.map_.getOwnerDocument();this.dragListenerKeys_.push(D(t,zs.POINTERMOVE,this.handlePointerMove_,this),D(t,zs.POINTERUP,this.handlePointerUp_,this),D(this.element_,zs.POINTERCANCEL,this.handlePointerUp_,this)),this.element_.getRootNode&&this.element_.getRootNode()!==t&&this.dragListenerKeys_.push(D(this.element_.getRootNode(),zs.POINTERUP,this.handlePointerUp_,this))}}handlePointerMove_(t){if(this.isMoving_(t)){this.updateActivePointers_(t),this.dragging_=!0;const e=new Bs(zs.POINTERDRAG,this.map_,t,this.dragging_,void 0,this.activePointers_);this.dispatchEvent(e)}}relayMoveEvent_(t){this.originalPointerMoveEvent_=t;const e=!(!this.down_||!this.isMoving_(t));this.dispatchEvent(new Bs(zs.POINTERMOVE,this.map_,t,e))}handleTouchMove_(t){const e=this.originalPointerMoveEvent_;e&&!e.defaultPrevented||"boolean"==typeof t.cancelable&&!0!==t.cancelable||t.preventDefault()}isMoving_(t){return this.dragging_||Math.abs(t.clientX-this.down_.clientX)>this.moveTolerance_||Math.abs(t.clientY-this.down_.clientY)>this.moveTolerance_}disposeInternal(){this.relayedListenerKey_&&(k(this.relayedListenerKey_),this.relayedListenerKey_=null),this.element_.removeEventListener(f,this.boundHandleTouchMove_),this.pointerdownListenerKey_&&(k(this.pointerdownListenerKey_),this.pointerdownListenerKey_=null),this.dragListenerKeys_.forEach(k),this.dragListenerKeys_.length=0,this.element_=null,super.disposeInternal()}}var Ys="postrender",Hs="movestart",Ks="moveend",qs="loadstart",Js="loadend",Qs="layergroup",to="size",eo="target",io="view";const no=1/0;class ro{constructor(t,e){this.priorityFunction_=t,this.keyFunction_=e,this.elements_=[],this.priorities_=[],this.queuedElements_={}}clear(){this.elements_.length=0,this.priorities_.length=0,F(this.queuedElements_)}dequeue(){const t=this.elements_,e=this.priorities_,i=t[0];1==t.length?(t.length=0,e.length=0):(t[0]=t.pop(),e[0]=e.pop(),this.siftUp_(0));const n=this.keyFunction_(i);return delete this.queuedElements_[n],i}enqueue(t){Ft(!(this.keyFunction_(t)in this.queuedElements_),"Tried to enqueue an `element` that was already added to the queue");const e=this.priorityFunction_(t);return e!=no&&(this.elements_.push(t),this.priorities_.push(e),this.queuedElements_[this.keyFunction_(t)]=!0,this.siftDown_(0,this.elements_.length-1),!0)}getCount(){return this.elements_.length}getLeftChildIndex_(t){return 2*t+1}getRightChildIndex_(t){return 2*t+2}getParentIndex_(t){return t-1>>1}heapify_(){let t;for(t=(this.elements_.length>>1)-1;t>=0;t--)this.siftUp_(t)}isEmpty(){return 0===this.elements_.length}isKeyQueued(t){return t in this.queuedElements_}isQueued(t){return this.isKeyQueued(this.keyFunction_(t))}siftUp_(t){const e=this.elements_,i=this.priorities_,n=e.length,r=e[t],s=i[t],o=t;for(;t>1;){const r=this.getLeftChildIndex_(t),s=this.getRightChildIndex_(t),o=st;){const t=this.getParentIndex_(e);if(!(n[t]>s))break;i[e]=i[t],n[e]=n[t],e=t}i[e]=r,n[e]=s}reprioritize(){const t=this.priorityFunction_,e=this.elements_,i=this.priorities_;let n=0;const r=e.length;let s,o,a;for(o=0;ot.apply(null,e)),(t=>t[0].getKey())),this.boundHandleTileChange_=this.handleTileChange.bind(this),this.tileChangeCallback_=e,this.tilesLoading_=0,this.tilesLoadingKeys_={}}enqueue(t){const e=super.enqueue(t);if(e){t[0].addEventListener(n,this.boundHandleTileChange_)}return e}getTilesLoading(){return this.tilesLoading_}handleTileChange(t){const e=t.target,i=e.getState();if(i===K||i===q||i===J){i!==q&&e.removeEventListener(n,this.boundHandleTileChange_);const t=e.getKey();t in this.tilesLoadingKeys_&&(delete this.tilesLoadingKeys_[t],--this.tilesLoading_),this.tileChangeCallback_()}}loadMoreTiles(t,e){let i=0;for(;this.tilesLoading_0;){const t=this.dequeue()[0],e=t.getKey();t.getState()!==Y||e in this.tilesLoadingKeys_||(this.tilesLoadingKeys_[e]=!0,++this.tilesLoading_,++i,t.load())}}}function oo(t,e,i,n,r){if(!t||!(i in t.wantedTiles))return no;if(!t.wantedTiles[i][e.getKey()])return no;const s=t.viewState.center,o=n[0]-s[0],a=n[1]-s[1];return 65536*Math.log(r)+Math.sqrt(o*o+a*a)/r}var ao=0,lo=1,ho={CENTER:"center",RESOLUTION:"resolution",ROTATION:"rotation"};function co(t,e,i){return function(n,r,s,o,a){if(!n)return;if(!r&&!e)return n;const l=e?0:s[0]*r,h=e?0:s[1]*r,c=a?a[0]:0,u=a?a[1]:0;let d=t[0]+l/2+c,g=t[2]-l/2+c,f=t[1]+h/2+u,p=t[3]-h/2+u;d>g&&(d=(g+d)/2,g=d),f>p&&(f=(p+f)/2,p=f);let m=be(n[0],d,g),_=be(n[1],f,p);if(o&&i&&r){const t=30*r;m+=-t*Math.log(1+Math.max(0,d-n[0])/t)+t*Math.log(1+Math.max(0,n[0]-g)/t),_+=-t*Math.log(1+Math.max(0,f-n[1])/t)+t*Math.log(1+Math.max(0,n[1]-p)/t)}return[m,_]}}function uo(t){return t}function go(t,e,i,n){const r=ye(e)/i[0],s=fe(e)/i[1];return n?Math.min(t,Math.max(r,s)):Math.min(t,Math.min(r,s))}function fo(t,e,i){let n=Math.min(t,e);return n*=Math.log(1+50*Math.max(0,t/e-1))/50+1,i&&(n=Math.max(n,i),n/=Math.log(1+50*Math.max(0,i/t-1))/50+1),be(n,i/2,2*e)}function po(t,e,i,n){return e=void 0===e||e,function(r,s,o,a){if(void 0!==r){const l=t[0],h=t[t.length-1],c=i?go(l,i,o,n):l;if(a)return e?fo(r,c,h):be(r,h,c);const u=Math.min(c,r),d=Math.floor(v(t,u,s));return t[d]>c&&d1&&"function"==typeof arguments[i-1]&&(e=arguments[i-1],--i);let n=0;for(;n0}getInteracting(){return this.hints_[lo]>0}cancelAnimations(){let t;this.setHint(ao,-this.hints_[ao]);for(let e=0,i=this.animations_.length;e=0;--i){const n=this.animations_[i];let r=!0;for(let i=0,s=n.length;i0?o/s.duration:1;a>=1?(s.complete=!0,a=1):r=!1;const l=s.easing(a);if(s.sourceCenter){const t=s.sourceCenter[0],e=s.sourceCenter[1],i=s.targetCenter[0],n=s.targetCenter[1];this.nextCenter_=s.targetCenter;const r=t+l*(i-t),o=e+l*(n-e);this.targetCenter_=[r,o]}if(s.sourceResolution&&s.targetResolution){const t=1===l?s.targetResolution:s.sourceResolution+l*(s.targetResolution-s.sourceResolution);if(s.anchor){const e=this.getViewportSize_(this.getRotation()),i=this.constraints_.resolution(t,0,e,!0);this.targetCenter_=this.calculateCenterZoom(i,s.anchor)}this.nextResolution_=s.targetResolution,this.targetResolution_=t,this.applyTargetState_(!0)}if(void 0!==s.sourceRotation&&void 0!==s.targetRotation){const t=1===l?Ae(s.targetRotation+Math.PI,2*Math.PI)-Math.PI:s.sourceRotation+l*(s.targetRotation-s.sourceRotation);if(s.anchor){const e=this.constraints_.rotation(t,!0);this.targetCenter_=this.calculateCenterRotate(e,s.anchor)}this.nextRotation_=s.targetRotation,this.targetRotation_=t}if(this.applyTargetState_(!0),e=!0,!s.complete)break}if(r){this.animations_[i]=null,this.setHint(ao,-1),this.nextCenter_=null,this.nextResolution_=NaN,this.nextRotation_=NaN;const t=n[0].callback;t&&To(t,!0)}}this.animations_=this.animations_.filter(Boolean),e&&void 0===this.updateAnimationKey_&&(this.updateAnimationKey_=requestAnimationFrame(this.updateAnimations_.bind(this)))}calculateCenterRotate(t,e){let i;const n=this.getCenterInternal();return void 0!==n&&(i=[n[0]-e[0],n[1]-e[1]],ni(i,t-this.getRotation()),qe(i,e)),i}calculateCenterZoom(t,e){let i;const n=this.getCenterInternal(),r=this.getResolution();if(void 0!==n&&void 0!==r){i=[e[0]-t*(e[0]-n[0])/r,e[1]-t*(e[1]-n[1])/r]}return i}getViewportSize_(t){const e=this.viewportSize_;if(t){const i=e[0],n=e[1];return[Math.abs(i*Math.cos(t))+Math.abs(n*Math.sin(t)),Math.abs(i*Math.sin(t))+Math.abs(n*Math.cos(t))]}return e}setViewportSize(t){this.viewportSize_=Array.isArray(t)?t.slice():[100,100],this.getAnimating()||this.resolveConstraints(0)}getCenter(){const t=this.getCenterInternal();return t?Nn(t,this.getProjection()):t}getCenterInternal(){return this.get(ho.CENTER)}getConstraints(){return this.constraints_}getConstrainResolution(){return this.get("constrainResolution")}getHints(t){return void 0!==t?(t[0]=this.hints_[0],t[1]=this.hints_[1],t):this.hints_.slice()}calculateExtent(t){return Gn(this.calculateExtentInternal(t),this.getProjection())}calculateExtentInternal(t){t=t||this.getViewportSizeMinusPadding_();const e=this.getCenterInternal();Ft(e,"The view center is not defined");const i=this.getResolution();Ft(void 0!==i,"The view resolution is not defined");const n=this.getRotation();return Ft(void 0!==n,"The view rotation is not defined"),de(e,i,n,t)}getMaxResolution(){return this.maxResolution_}getMinResolution(){return this.minResolution_}getMaxZoom(){return this.getZoomForResolution(this.minResolution_)}setMaxZoom(t){this.applyOptions_(this.getUpdatedOptions_({maxZoom:t}))}getMinZoom(){return this.getZoomForResolution(this.maxResolution_)}setMinZoom(t){this.applyOptions_(this.getUpdatedOptions_({minZoom:t}))}setConstrainResolution(t){this.applyOptions_(this.getUpdatedOptions_({constrainResolution:t}))}getProjection(){return this.projection_}getResolution(){return this.get(ho.RESOLUTION)}getResolutions(){return this.resolutions_}getResolutionForExtent(t,e){return this.getResolutionForExtentInternal(jn(t,this.getProjection()),e)}getResolutionForExtentInternal(t,e){e=e||this.getViewportSizeMinusPadding_();const i=ye(t)/e[0],n=fe(t)/e[1];return Math.max(i,n)}getResolutionForValueFunction(t){t=t||2;const e=this.getConstrainedResolution(this.maxResolution_),i=this.minResolution_,n=Math.log(e/i)/Math.log(t);return function(i){return e/Math.pow(t,i*n)}}getRotation(){return this.get(ho.ROTATION)}getValueForResolutionFunction(t){const e=Math.log(t||2),i=this.getConstrainedResolution(this.maxResolution_),n=this.minResolution_,r=Math.log(i/n)/e;return function(t){return Math.log(i/t)/e/r}}getViewportSizeMinusPadding_(t){let e=this.getViewportSize_(t);const i=this.padding_;return i&&(e=[e[0]-i[1]-i[3],e[1]-i[0]-i[2]]),e}getState(){const t=this.getProjection(),e=this.getResolution(),i=this.getRotation();let n=this.getCenterInternal();const r=this.padding_;if(r){const t=this.getViewportSizeMinusPadding_();n=Io(n,this.getViewportSize_(),[t[0]/2+r[3],t[1]/2+r[0]],e,i)}return{center:n.slice(0),projection:void 0!==t?t:null,resolution:e,nextCenter:this.nextCenter_,nextResolution:this.nextResolution_,nextRotation:this.nextRotation_,rotation:i,zoom:this.getZoom()}}getViewStateAndExtent(){return{viewState:this.getState(),extent:this.calculateExtent()}}getZoom(){let t;const e=this.getResolution();return void 0!==e&&(t=this.getZoomForResolution(e)),t}getZoomForResolution(t){let e,i,n=this.minZoom_||0;if(this.resolutions_){const r=v(this.resolutions_,t,1);n=r,e=this.resolutions_[r],i=r==this.resolutions_.length-1?2:e/this.resolutions_[r+1]}else e=this.maxResolution_,i=this.zoomFactor_;return n+Math.log(e/t)/Math.log(i)}getResolutionForZoom(t){if(this.resolutions_?.length){if(1===this.resolutions_.length)return this.resolutions_[0];const e=be(Math.floor(t),0,this.resolutions_.length-2),i=this.resolutions_[e]/this.resolutions_[e+1];return this.resolutions_[e]/Math.pow(i,be(t-e,0,1))}return this.maxResolution_/Math.pow(this.zoomFactor_,t-this.minZoom_)}fit(t,e){let i;if(Ft(Array.isArray(t)||"function"==typeof t.getSimplifiedGeometry,"Invalid extent or geometry provided as `geometry`"),Array.isArray(t)){Ft(!ve(t),"Cannot fit empty extent provided as `geometry`");i=ds(jn(t,this.getProjection()))}else if("Circle"===t.getType()){const e=jn(t.getExtent(),this.getProjection());i=ds(e),i.rotate(this.getRotation(),ce(e))}else{const e=Dn();i=e?t.clone().transform(e,this.getProjection()):t}this.fitInternal(i,e)}rotatedExtentForGeometry(t){const e=this.getRotation(),i=Math.cos(e),n=Math.sin(-e),r=t.getFlatCoordinates(),s=t.getStride();let o=1/0,a=1/0,l=-1/0,h=-1/0;for(let t=0,e=r.length;tt.trim().replace(/^['"]|['"]$/g,""))),i};class Uo extends V{constructor(t){super();const e=t.element;!e||t.target||e.style.pointerEvents||(e.style.pointerEvents="auto"),this.element=e||null,this.target_=null,this.map_=null,this.listenerKeys=[],t.render&&(this.render=t.render),t.target&&this.setTarget(t.target)}disposeInternal(){this.element?.remove(),super.disposeInternal()}getMap(){return this.map_}setMap(t){this.map_&&this.element?.remove();for(let t=0,e=this.listenerKeys.length;te.getAttributions(t))));if(void 0!==this.attributions_&&(Array.isArray(this.attributions_)?this.attributions_.forEach((t=>i.add(t))):i.add(this.attributions_)),!this.overrideCollapsible_){const t=!e.some((t=>!1===t.getSource()?.getAttributionsCollapsible()));this.setCollapsible(t)}return Array.from(i)}async updateElement_(t){if(!t)return void(this.renderedVisible_&&(this.element.style.display="none",this.renderedVisible_=!1));const e=await Promise.all(this.collectSourceAttributions_(t).map((t=>I((()=>t))))),i=e.length>0;if(this.renderedVisible_!=i&&(this.element.style.display=i?"":"none",this.renderedVisible_=i),!w(e,this.renderedAttributions_)){St(this.ulElement_);for(let t=0,i=e.length;t0&&e%(2*Math.PI)!=0?t.animate({rotation:0,duration:this.duration_,easing:tt}):t.setRotation(0))}render(t){const e=t.frameState;if(!e)return;const i=e.viewState.rotation;if(i!=this.rotation_){const t="rotate("+i+"rad)";if(this.autoHide_){const t=this.element.classList.contains(Fo);t||0!==i?t&&0!==i&&this.element.classList.remove(Fo):this.element.classList.add(Fo)}this.label_.style.transform=t}this.rotation_=i}}class Xo extends Uo{constructor(t){t=t||{},super({element:document.createElement("div"),target:t.target});const e=void 0!==t.className?t.className:"ol-zoom",i=void 0!==t.delta?t.delta:1,n=void 0!==t.zoomInClassName?t.zoomInClassName:e+"-in",r=void 0!==t.zoomOutClassName?t.zoomOutClassName:e+"-out",s=void 0!==t.zoomInLabel?t.zoomInLabel:"+",a=void 0!==t.zoomOutLabel?t.zoomOutLabel:"–",l=void 0!==t.zoomInTipLabel?t.zoomInTipLabel:"Zoom in",h=void 0!==t.zoomOutTipLabel?t.zoomOutTipLabel:"Zoom out",c=document.createElement("button");c.className=n,c.setAttribute("type","button"),c.title=l,c.appendChild("string"==typeof s?document.createTextNode(s):s),c.addEventListener(o,this.handleClick_.bind(this,i),!1);const u=document.createElement("button");u.className=r,u.setAttribute("type","button"),u.title=h,u.appendChild("string"==typeof a?document.createTextNode(a):a),u.addEventListener(o,this.handleClick_.bind(this,-i),!1);const d=e+" "+Mo+" "+Oo,g=this.element;g.className=d,g.appendChild(c),g.appendChild(u),this.duration_=void 0!==t.duration?t.duration:250}handleClick_(t,e){e.preventDefault(),this.zoomByDelta_(t)}zoomByDelta_(t){const e=this.getMap().getView();if(!e)return;const i=e.getZoom();if(void 0!==i){const n=e.getConstrainedZoom(i+t);this.duration_>0?(e.getAnimating()&&e.cancelAnimations(),e.animate({zoom:n,duration:this.duration_,easing:tt})):e.setZoom(n)}}}function Vo(t){t=t||{};const e=new Z;(void 0===t.zoom||t.zoom)&&e.push(new Xo(t.zoomOptions));(void 0===t.rotate||t.rotate)&&e.push(new zo(t.rotateOptions));return(void 0===t.attribution||t.attribution)&&e.push(new Bo(t.attributionOptions)),e}var $o="active";class Wo extends V{constructor(t){super(),this.on,this.once,this.un,t&&t.handleEvent&&(this.handleEvent=t.handleEvent),this.map_=null,this.setActive(!0)}getActive(){return this.get($o)}getMap(){return this.map_}handleEvent(t){return!0}setActive(t){this.set($o,t)}setMap(t){this.map_=t}}function Zo(t,e,i){const n=t.getCenterInternal();if(n){const r=[n[0]+e[0],n[1]+e[1]];t.animateInternal({duration:void 0!==i?i:250,easing:it,center:t.getConstrainedCenter(r)})}}function Yo(t,e,i,n){const r=t.getZoom();if(void 0===r)return;const s=t.getConstrainedZoom(r+e),o=t.getResolutionForZoom(s);t.getAnimating()&&t.cancelAnimations(),t.animate({resolution:o,anchor:i,duration:void 0!==n?n:250,easing:tt})}class Ho extends Wo{constructor(t){super(),t=t||{},this.delta_=t.delta?t.delta:1,this.duration_=void 0!==t.duration?t.duration:250}handleEvent(t){let e=!1;if(t.type==zs.DBLCLICK){const i=t.originalEvent,n=t.map,r=t.coordinate,s=i.shiftKey?-this.delta_:this.delta_;Yo(n.getView(),s,r,this.duration_),i.preventDefault(),e=!0}return!e}}function Ko(t){const e=arguments;return function(t){let i=!0;for(let n=0,r=e.length;n0}}else if(t.type==zs.POINTERDOWN){const i=this.handleDownEvent(t);this.handlingDownUpSequence=i,e=this.stopDown(i)}else t.type==zs.POINTERMOVE&&this.handleMoveEvent(t);return!e}handleMoveEvent(t){}handleUpEvent(t){return!1}stopDown(t){return t}updateTrackedPointers_(t){t.activePointers&&(this.targetPointers=t.activePointers)}}function da(t){const e=t.length;let i=0,n=0;for(let r=0;r0&&this.condition_(t)){const e=t.map.getView();return this.lastCentroid=null,e.getAnimating()&&e.cancelAnimations(),this.kinetic_&&this.kinetic_.begin(),this.noKinetic_=this.targetPointers.length>1,!0}return!1}}class fa extends ua{constructor(t){t=t||{},super({stopDown:R}),this.condition_=t.condition?t.condition:Jo,this.lastAngle_=void 0,this.duration_=void 0!==t.duration?t.duration:250}handleDragEvent(t){if(!ha(t))return;const e=t.map,i=e.getView();if(i.getConstraints().rotation===yo)return;const n=e.getSize(),r=t.pixel,s=Math.atan2(n[1]/2-r[1],r[0]-n[0]/2);if(void 0!==this.lastAngle_){const t=s-this.lastAngle_;i.adjustRotationInternal(-t)}this.lastAngle_=s}handleUpEvent(t){if(!ha(t))return!0;return t.map.getView().endInteraction(this.duration_),!1}handleDownEvent(t){if(!ha(t))return!1;if(ia(t)&&this.condition_(t)){return t.map.getView().beginInteraction(),this.lastAngle_=void 0,!0}return!1}}class pa extends m{constructor(t){super(),this.geometry_=null,this.element_=document.createElement("div"),this.element_.style.position="absolute",this.element_.style.pointerEvents="auto",this.element_.className="ol-box "+t,this.map_=null,this.startPixel_=null,this.endPixel_=null}disposeInternal(){this.setMap(null)}render_(){const t=this.startPixel_,e=this.endPixel_,i="px",n=this.element_.style;n.left=Math.min(t[0],e[0])+i,n.top=Math.min(t[1],e[1])+i,n.width=Math.abs(e[0]-t[0])+i,n.height=Math.abs(e[1]-t[1])+i}setMap(t){if(this.map_){this.map_.getOverlayContainer().removeChild(this.element_);const t=this.element_.style;t.left="inherit",t.top="inherit",t.width="inherit",t.height="inherit"}this.map_=t,this.map_&&this.map_.getOverlayContainer().appendChild(this.element_)}setPixels(t,e){this.startPixel_=t,this.endPixel_=e,this.createOrUpdateGeometry(),this.render_()}createOrUpdateGeometry(){if(!this.map_)return;const t=this.startPixel_,e=this.endPixel_,i=[t,[t[0],e[1]],e,[e[0],t[1]]].map(this.map_.getCoordinateFromPixelInternal,this.map_);i[4]=i[0].slice(),this.geometry_?this.geometry_.setCoordinates([i]):this.geometry_=new cs([i])}getGeometry(){return this.geometry_}}const ma="boxstart",_a="boxdrag",ya="boxend",xa="boxcancel";class va extends M{constructor(t,e,i){super(t),this.coordinate=e,this.mapBrowserEvent=i}}class Sa extends ua{constructor(t){super(),this.on,this.once,this.un,t=t??{},this.box_=new pa(t.className||"ol-dragbox"),this.minArea_=t.minArea??64,t.onBoxEnd&&(this.onBoxEnd=t.onBoxEnd),this.startPixel_=null,this.condition_=t.condition??ia,this.boxEndCondition_=t.boxEndCondition??this.defaultBoxEndCondition}defaultBoxEndCondition(t,e,i){const n=i[0]-e[0],r=i[1]-e[1];return n*n+r*r>=this.minArea_}getGeometry(){return this.box_.getGeometry()}handleDragEvent(t){this.startPixel_&&(this.box_.setPixels(this.startPixel_,t.pixel),this.dispatchEvent(new va(_a,t.coordinate,t)))}handleUpEvent(t){if(!this.startPixel_)return!1;const e=this.boxEndCondition_(t,this.startPixel_,t.pixel);return e&&this.onBoxEnd(t),this.dispatchEvent(new va(e?ya:xa,t.coordinate,t)),this.box_.setMap(null),this.startPixel_=null,!1}handleDownEvent(t){return!!this.condition_(t)&&(this.startPixel_=t.pixel,this.box_.setMap(t.map),this.box_.setPixels(this.startPixel_,this.startPixel_),this.dispatchEvent(new va(ma,t.coordinate,t)),!0)}onBoxEnd(t){}setActive(t){t||(this.box_.setMap(null),this.startPixel_&&(this.dispatchEvent(new va(xa,this.startPixel_,null)),this.startPixel_=null)),super.setActive(t)}setMap(t){this.getMap()&&(this.box_.setMap(null),this.startPixel_&&(this.dispatchEvent(new va(xa,this.startPixel_,null)),this.startPixel_=null)),super.setMap(t)}}class Ea extends Sa{constructor(t){super({condition:(t=t||{}).condition?t.condition:aa,className:t.className||"ol-dragzoom",minArea:t.minArea}),this.duration_=void 0!==t.duration?t.duration:200,this.out_=void 0!==t.out&&t.out}onBoxEnd(t){const e=this.getMap().getView();let i=this.getGeometry();if(this.out_){const t=e.rotatedExtentForGeometry(i),n=e.getResolutionForExtentInternal(t),r=e.getResolution()/n;i=i.clone(),i.scale(r*r)}e.fitInternal(i,{duration:this.duration_,easing:tt})}}var wa="ArrowLeft",Ta="ArrowUp",Ca="ArrowRight",Ra="ArrowDown";class ba extends Wo{constructor(t){super(),t=t||{},this.defaultCondition_=function(t){return sa(t)&&la(t)},this.condition_=void 0!==t.condition?t.condition:this.defaultCondition_,this.duration_=void 0!==t.duration?t.duration:100,this.pixelDelta_=void 0!==t.pixelDelta?t.pixelDelta:128}handleEvent(t){let e=!1;if(t.type==u){const i=t.originalEvent,n=i.key;if(this.condition_(t)&&(n==Ra||n==wa||n==Ca||n==Ta)){const r=t.map.getView(),s=r.getResolution()*this.pixelDelta_;let o=0,a=0;n==Ra?a=-s:n==wa?o=-s:n==Ca?o=s:a=s;const l=[o,a];ni(l,r.getRotation()),Zo(r,l,this.duration_),i.preventDefault(),e=!0}}return!e}}class Pa extends Wo{constructor(t){super(),t=t||{},this.condition_=t.condition?t.condition:function(t){return!oa(t)&&la(t)},this.delta_=t.delta?t.delta:1,this.duration_=void 0!==t.duration?t.duration:100}handleEvent(t){let e=!1;if(t.type==u||t.type==d){const i=t.originalEvent,n=i.key;if(this.condition_(t)&&("+"===n||"-"===n)){const r=t.map,s="+"===n?this.delta_:-this.delta_;Yo(r.getView(),s,void 0,this.duration_),i.preventDefault(),e=!0}}return!e}}class Ia extends Wo{constructor(t){super(t=t||{}),this.totalDelta_=0,this.lastDelta_=0,this.maxDelta_=void 0!==t.maxDelta?t.maxDelta:1,this.duration_=void 0!==t.duration?t.duration:250,this.timeout_=void 0!==t.timeout?t.timeout:80,this.useAnchor_=void 0===t.useAnchor||t.useAnchor,this.constrainResolution_=void 0!==t.constrainResolution&&t.constrainResolution;const e=t.condition?t.condition:ea;this.condition_=t.onFocusOnly?Ko(ta,e):e,this.lastAnchor_=null,this.startTime_=void 0,this.timeoutId_,this.mode_=void 0,this.trackpadEventGap_=400,this.trackpadTimeoutId_,this.deltaPerZoom_=300}endInteraction_(){this.trackpadTimeoutId_=void 0;const t=this.getMap();if(!t)return;t.getView().endInteraction(void 0,this.lastDelta_?this.lastDelta_>0?1:-1:0,this.lastAnchor_?t.getCoordinateFromPixel(this.lastAnchor_):null)}handleEvent(t){if(!this.condition_(t))return!0;if(t.type!==p)return!0;const e=t.map,i=t.originalEvent;i.preventDefault(),this.useAnchor_&&(this.lastAnchor_=t.pixel);let n=i.deltaY;switch(i.deltaMode){case WheelEvent.DOM_DELTA_LINE:n*=40;break;case WheelEvent.DOM_DELTA_PAGE:n*=300}if(0===n)return!1;this.lastDelta_=n;const r=Date.now();void 0===this.startTime_&&(this.startTime_=r),(!this.mode_||r-this.startTime_>this.trackpadEventGap_)&&(this.mode_=Math.abs(n)<4?"trackpad":"wheel");const s=e.getView();if("trackpad"===this.mode_&&!s.getConstrainResolution()&&!this.constrainResolution_)return this.trackpadTimeoutId_?clearTimeout(this.trackpadTimeoutId_):(s.getAnimating()&&s.cancelAnimations(),s.beginInteraction()),this.trackpadTimeoutId_=setTimeout(this.endInteraction_.bind(this),this.timeout_),s.adjustZoom(-n/this.deltaPerZoom_,this.lastAnchor_?e.getCoordinateFromPixel(this.lastAnchor_):null),this.startTime_=r,!1;this.totalDelta_+=n;const o=Math.max(this.timeout_-(r-this.startTime_),0);return clearTimeout(this.timeoutId_),this.timeoutId_=setTimeout(this.handleWheelZoom_.bind(this,e),o),!1}handleWheelZoom_(t){const e=t.getView();e.getAnimating()&&e.cancelAnimations();let i=-be(this.totalDelta_,-this.maxDelta_*this.deltaPerZoom_,this.maxDelta_*this.deltaPerZoom_)/this.deltaPerZoom_;(e.getConstrainResolution()||this.constrainResolution_)&&(i=i?i>0?1:-1:0),Yo(e,i,this.lastAnchor_?t.getCoordinateFromPixel(this.lastAnchor_):null,this.duration_),this.mode_=void 0,this.totalDelta_=0,this.lastAnchor_=null,this.startTime_=void 0,this.timeoutId_=void 0}setMouseAnchor(t){this.useAnchor_=t,t||(this.lastAnchor_=null)}}class Fa extends ua{constructor(t){const e=t=t||{};e.stopDown||(e.stopDown=R),super(e),this.anchor_=null,this.lastAngle_=void 0,this.rotating_=!1,this.rotationDelta_=0,this.threshold_=void 0!==t.threshold?t.threshold:.3,this.duration_=void 0!==t.duration?t.duration:250}handleDragEvent(t){let e=0;const i=this.targetPointers[0],n=this.targetPointers[1],r=Math.atan2(n.clientY-i.clientY,n.clientX-i.clientX);if(void 0!==this.lastAngle_){const t=r-this.lastAngle_;this.rotationDelta_+=t,!this.rotating_&&Math.abs(this.rotationDelta_)>this.threshold_&&(this.rotating_=!0),e=t}this.lastAngle_=r;const s=t.map,o=s.getView();o.getConstraints().rotation!==yo&&(this.anchor_=s.getCoordinateFromPixelInternal(s.getEventPixel(da(this.targetPointers))),this.rotating_&&(s.render(),o.adjustRotationInternal(e,this.anchor_)))}handleUpEvent(t){if(this.targetPointers.length<2){return t.map.getView().endInteraction(this.duration_),!1}return!0}handleDownEvent(t){if(this.targetPointers.length>=2){const e=t.map;return this.anchor_=null,this.lastAngle_=void 0,this.rotating_=!1,this.rotationDelta_=0,this.handlingDownUpSequence||e.getView().beginInteraction(),!0}return!1}}class La extends ua{constructor(t){const e=t=t||{};e.stopDown||(e.stopDown=R),super(e),this.anchor_=null,this.duration_=void 0!==t.duration?t.duration:400,this.lastDistance_=void 0,this.lastScaleDelta_=1}handleDragEvent(t){let e=1;const i=this.targetPointers[0],n=this.targetPointers[1],r=i.clientX-n.clientX,s=i.clientY-n.clientY,o=Math.sqrt(r*r+s*s);void 0!==this.lastDistance_&&(e=this.lastDistance_/o),this.lastDistance_=o;const a=t.map,l=a.getView();1!=e&&(this.lastScaleDelta_=e),this.anchor_=a.getCoordinateFromPixelInternal(a.getEventPixel(da(this.targetPointers))),a.render(),l.adjustResolutionInternal(e,this.anchor_)}handleUpEvent(t){if(this.targetPointers.length<2){const e=t.map.getView(),i=this.lastScaleDelta_>1?1:-1;return e.endInteraction(this.duration_,i),!1}return!0}handleDownEvent(t){if(this.targetPointers.length>=2){const e=t.map;return this.anchor_=null,this.lastDistance_=void 0,this.lastScaleDelta_=1,this.handlingDownUpSequence||e.getView().beginInteraction(),!0}return!1}}function Ma(t){t=t||{};const e=new Z,i=new js(-.005,.05,100);(void 0===t.altShiftDragRotate||t.altShiftDragRotate)&&e.push(new fa);(void 0===t.doubleClickZoom||t.doubleClickZoom)&&e.push(new Ho({delta:t.zoomDelta,duration:t.zoomDuration}));(void 0===t.dragPan||t.dragPan)&&e.push(new ga({onFocusOnly:t.onFocusOnly,kinetic:i}));(void 0===t.pinchRotate||t.pinchRotate)&&e.push(new Fa);(void 0===t.pinchZoom||t.pinchZoom)&&e.push(new La({duration:t.zoomDuration}));(void 0===t.keyboard||t.keyboard)&&(e.push(new ba),e.push(new Pa({delta:t.zoomDelta,duration:t.zoomDuration})));(void 0===t.mouseWheelZoom||t.mouseWheelZoom)&&e.push(new Ia({onFocusOnly:t.onFocusOnly,duration:t.zoomDuration}));return(void 0===t.shiftDragZoom||t.shiftDragZoom)&&e.push(new Ea({duration:t.zoomDuration})),e}var Aa="opacity",Oa="visible",Da="extent",Na="zIndex",ka="maxResolution",Ga="minResolution",ja="maxZoom",Ua="minZoom",Ba="source",za="map";class Xa extends V{constructor(t){super(),this.on,this.once,this.un,this.background_=t.background;const e=Object.assign({},t);"object"==typeof t.properties&&(delete e.properties,Object.assign(e,t.properties)),e[Aa]=void 0!==t.opacity?t.opacity:1,Ft("number"==typeof e[Aa],"Layer opacity must be a number"),e[Oa]=void 0===t.visible||t.visible,e[Na]=t.zIndex,e[ka]=void 0!==t.maxResolution?t.maxResolution:1/0,e[Ga]=void 0!==t.minResolution?t.minResolution:0,e[Ua]=void 0!==t.minZoom?t.minZoom:-1/0,e[ja]=void 0!==t.maxZoom?t.maxZoom:1/0,this.className_=void 0!==e.className?e.className:"ol-layer",delete e.className,this.setProperties(e),this.state_=null}getBackground(){return this.background_}getClassName(){return this.className_}getLayerState(t){const e=this.state_||{layer:this,managed:void 0===t||t},i=this.getZIndex();return e.opacity=be(Math.round(100*this.getOpacity())/100,0,1),e.visible=this.getVisible(),e.extent=this.getExtent(),e.zIndex=void 0!==i||e.managed?i:1/0,e.maxResolution=this.getMaxResolution(),e.minResolution=Math.max(this.getMinResolution(),0),e.minZoom=this.getMinZoom(),e.maxZoom=this.getMaxZoom(),this.state_=e,e}getLayersArray(t){return U()}getLayerStatesArray(t){return U()}getExtent(){return this.get(Da)}getMaxResolution(){return this.get(ka)}getMinResolution(){return this.get(Ga)}getMinZoom(){return this.get(Ua)}getMaxZoom(){return this.get(ja)}getOpacity(){return this.get(Aa)}getSourceState(){return U()}getVisible(){return this.get(Oa)}getZIndex(){return this.get(Na)}setBackground(t){this.background_=t,this.changed()}setExtent(t){this.set(Da,t)}setMaxResolution(t){this.set(ka,t)}setMinResolution(t){this.set(Ga,t)}setMaxZoom(t){this.set(ja,t)}setMinZoom(t){this.set(Ua,t)}setOpacity(t){Ft("number"==typeof t,"Layer opacity must be a number"),this.set(Aa,t)}setVisible(t){this.set(Oa,t)}setZIndex(t){this.set(Na,t)}disposeInternal(){this.state_&&(this.state_.layer=null,this.state_=null),super.disposeInternal()}}class Va extends M{constructor(t,e){super(t),this.layer=e}}const $a="layers";class Wa extends Xa{constructor(t){t=t||{};const e=Object.assign({},t);delete e.layers;let i=t.layers;super(e),this.on,this.once,this.un,this.layersListenerKeys_=[],this.listenerKeys_={},this.addChangeListener($a,this.handleLayersChanged_),i?Array.isArray(i)?i=new Z(i.slice(),{unique:!0}):Ft("function"==typeof i.getArray,"Expected `layers` to be an array or a `Collection`"):i=new Z(void 0,{unique:!0}),this.setLayers(i)}handleLayerChange_(){this.changed()}handleLayersChanged_(){this.layersListenerKeys_.forEach(k),this.layersListenerKeys_.length=0;const i=this.getLayers();this.layersListenerKeys_.push(D(i,t,this.handleLayersAdd_,this),D(i,e,this.handleLayersRemove_,this));for(const t in this.listenerKeys_)this.listenerKeys_[t].forEach(k);F(this.listenerKeys_);const n=i.getArray();for(let t=0,e=n.length;t{this.dispatchEvent("sourceready")}),0))),this.changed()}getFeatures(t){return this.renderer_?this.renderer_.getFeatures(t):Promise.resolve([])}getData(t){return this.renderer_&&this.rendered?this.renderer_.getData(t):null}isVisible(t){let e;const i=this.getMapInternal();let n;if(!t&&i&&(t=i.getView()),e=t instanceof wo?{viewState:t.getState(),extent:t.calculateExtent()}:t,!e.layerStatesArray&&i&&(e.layerStatesArray=i.getLayerGroup().getLayerStatesArray()),e.layerStatesArray){if(n=e.layerStatesArray.find((t=>t.layer===this)),!n)return!1}else n=this.getLayerState();const r=this.getExtent();return Qa(n,e.viewState)&&(!r||xe(r,e.extent))}getAttributions(t){if(!this.isVisible(t))return[];const e=this.getSource()?.getAttributions();if(!e)return[];let i=e(t instanceof wo?t.getViewStateAndExtent():t);return Array.isArray(i)||(i=[i]),i}render(t,e){const i=this.getRenderer();return i.prepareFrame(t)?(this.rendered=!0,i.renderFrame(t,e)):null}unrender(){this.rendered=!1}getDeclutter(){}renderDeclutter(t,e){}renderDeferred(t){const e=this.getRenderer();e&&e.renderDeferred(t)}setMapInternal(t){t||this.unrender(),this.set(za,t)}getMapInternal(){return this.get(za)}setMap(t){this.mapPrecomposeKey_&&(k(this.mapPrecomposeKey_),this.mapPrecomposeKey_=null),t||this.changed(),this.mapRenderKey_&&(k(this.mapRenderKey_),this.mapRenderKey_=null),t&&(this.mapPrecomposeKey_=D(t,Ha,this.handlePrecompose_,this),this.mapRenderKey_=D(this,n,t.render,t),this.changed())}handlePrecompose_(t){const e=t.frameState.layerStatesArray,i=this.getLayerState(!1);Ft(!e.some((t=>t.layer===i.layer)),"A layer can only be added to the map once. Use either `layer.setMap()` or `map.addLayer()`, not both."),e.push(i)}setSource(t){this.set(Ba,t)}getRenderer(){return this.renderer_||(this.renderer_=this.createRenderer()),this.renderer_}hasRenderer(){return!!this.renderer_}createRenderer(){return null}clearRenderer(){this.renderer_&&(this.renderer_.dispose(),delete this.renderer_)}disposeInternal(){this.clearRenderer(),this.setSource(null),super.disposeInternal()}}function Qa(t,e){if(!t.visible)return!1;const i=e.resolution;if(i=t.maxResolution)return!1;const n=e.zoom;return n>t.minZoom&&n<=t.maxZoom}function tl(t,e,i=0,n=t.length-1,r=il){for(;n>i;){if(n-i>600){const s=n-i+1,o=e-i+1,a=Math.log(s),l=.5*Math.exp(2*a/3),h=.5*Math.sqrt(a*l*(s-l)/s)*(o-s/2<0?-1:1);tl(t,e,Math.max(i,Math.floor(e-o*l/s+h)),Math.min(n,Math.floor(e+(s-o)*l/s+h)),r)}const s=t[e];let o=i,a=n;for(el(t,i,e),r(t[n],s)>0&&el(t,i,n);o0;)a--}0===r(t[i],s)?el(t,i,a):(a++,el(t,a,n)),a<=e&&(i=a+1),e<=a&&(n=a-1)}}function el(t,e,i){const n=t[e];t[e]=t[i],t[i]=n}function il(t,e){return te?1:0}let nl=class{constructor(t=9){this._maxEntries=Math.max(4,t),this._minEntries=Math.max(2,Math.ceil(.4*this._maxEntries)),this.clear()}all(){return this._all(this.data,[])}search(t){let e=this.data;const i=[];if(!fl(t,e))return i;const n=this.toBBox,r=[];for(;e;){for(let s=0;s=0&&r[e].children.length>this._maxEntries;)this._split(r,e),e--;this._adjustParentBBoxes(n,r,e)}_split(t,e){const i=t[e],n=i.children.length,r=this._minEntries;this._chooseSplitAxis(i,r,n);const s=this._chooseSplitIndex(i,r,n),o=pl(i.children.splice(s,i.children.length-s));o.height=i.height,o.leaf=i.leaf,sl(i,this.toBBox),sl(o,this.toBBox),e?t[e-1].children.push(o):this._splitRoot(i,o)}_splitRoot(t,e){this.data=pl([t,e]),this.data.height=t.height+1,this.data.leaf=!1,sl(this.data,this.toBBox)}_chooseSplitIndex(t,e,i){let n,r=1/0,s=1/0;for(let o=e;o<=i-e;o++){const e=ol(t,0,o,this.toBBox),a=ol(t,o,i,this.toBBox),l=dl(e,a),h=cl(e)+cl(a);l=e;n--){const e=t.children[n];al(o,t.leaf?r(e):e),a+=ul(o)}return a}_adjustParentBBoxes(t,e,i){for(let n=i;n>=0;n--)al(e[n],t)}_condense(t){for(let e,i=t.length-1;i>=0;i--)0===t[i].children.length?i>0?(e=t[i-1].children,e.splice(e.indexOf(t[i]),1)):this.clear():sl(t[i],this.toBBox)}};function rl(t,e,i){if(!i)return e.indexOf(t);for(let n=0;n=t.minX&&e.maxY>=t.minY}function pl(t){return{children:t,height:1,leaf:!0,minX:1/0,minY:1/0,maxX:-1/0,maxY:-1/0}}function ml(t,e,i,n,r){const s=[e,i];for(;s.length;){if((i=s.pop())-(e=s.pop())<=n)continue;const o=e+Math.ceil((i-e)/n/2)*n;tl(t,o,e,i,r),s.push(e,o,o,i)}}const _l=[NaN,NaN,NaN,0];let yl;const xl=/^rgba?\(\s*(\d+%?)\s+(\d+%?)\s+(\d+%?)(?:\s*\/\s*(\d+%|\d*\.\d+|[01]))?\s*\)$/i,vl=/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*(\d+%|\d*\.\d+|[01]))?\s*\)$/i,Sl=/^rgba?\(\s*(\d+%)\s*,\s*(\d+%)\s*,\s*(\d+%)(?:\s*,\s*(\d+%|\d*\.\d+|[01]))?\s*\)$/i,El=/^#([\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/i;function wl(t,e){return t.endsWith("%")?Number(t.substring(0,t.length-1))/e:Number(t)}function Tl(t){throw new Error('failed to parse "'+t+'" as color')}function Cl(t){if(t.toLowerCase().startsWith("rgb")){const e=t.match(vl)||t.match(xl)||t.match(Sl);if(e){const t=e[4],i=100/255;return[be(wl(e[1],i)+.5|0,0,255),be(wl(e[2],i)+.5|0,0,255),be(wl(e[3],i)+.5|0,0,255),void 0!==t?be(wl(t,100),0,1):1]}Tl(t)}if(t.startsWith("#")){if(El.test(t)){const e=t.substring(1),i=e.length<=4?1:2,n=[0,0,0,255];for(let t=0,r=e.length;t.0031308?269.025*Math.pow(t,1/2.4)-14.025:3294.6*t}function Ll(t){return t>.2068965?Math.pow(t,3):108/841*(t-4/29)}function Ml(t){return t>10.314724?Math.pow((t+14.025)/269.025,2.4):t/3294.6}function Al(t){return t>.0088564?Math.pow(t,1/3):t/(108/841)+4/29}function Ol(t){const e=Ml(t[0]),i=Ml(t[1]),n=Ml(t[2]),r=Al(.222488403*e+.716873169*i+.06060791*n),s=500*(Al(.452247074*e+.399439023*i+.148375274*n)-r),o=200*(r-Al(.016863605*e+.117638439*i+.865350722*n)),a=Math.atan2(o,s)*(180/Math.PI);return[116*r-16,Math.sqrt(s*s+o*o),a<0?a+360:a,t[3]]}function Dl(t){const e=(t[0]+16)/116,i=t[1],n=t[2]*Math.PI/180,r=Ll(e),s=Ll(e+i/500*Math.cos(n)),o=Ll(e-i/200*Math.sin(n)),a=Fl(3.021973625*s-1.617392459*r-.404875592*o),l=Fl(-.943766287*s+1.916279586*r+.027607165*o),h=Fl(.069407491*s-.22898585*r+1.159737864*o);return[be(a+.5|0,0,255),be(l+.5|0,0,255),be(h+.5|0,0,255),t[3]]}function Nl(t){if("none"===t)return _l;if(bl.hasOwnProperty(t))return bl[t];if(Pl>=1024){let t=0;for(const e in bl)3&t++||(delete bl[e],--Pl)}const e=Cl(t);4!==e.length&&Tl(t);for(const i of e)isNaN(i)&&Tl(t);return bl[t]=e,++Pl,e}function kl(t){return Array.isArray(t)?t:Nl(t)}function Gl(t){let e=t[0];e!=(0|e)&&(e=e+.5|0);let i=t[1];i!=(0|i)&&(i=i+.5|0);let n=t[2];n!=(0|n)&&(n=n+.5|0);return"rgba("+e+","+i+","+n+","+(void 0===t[3]?1:Math.round(1e3*t[3])/1e3)+")"}function jl(t){return t[0]>0&&t[1]>0}function Ul(t,e,i){return void 0===i&&(i=[0,0]),i[0]=t[0]*e+.5|0,i[1]=t[1]*e+.5|0,i}function Bl(t,e){return Array.isArray(t)?t:(void 0===e?e=[t,t]:(e[0]=t,e[1]=t),e)}let zl=0;const Xl=1<",GreaterThanOrEqualTo:">=",LessThan:"<",LessThanOrEqualTo:"<=",Multiply:"*",Divide:"/",Add:"+",Subtract:"-",Clamp:"clamp",Mod:"%",Pow:"^",Abs:"abs",Floor:"floor",Ceil:"ceil",Round:"round",Sin:"sin",Cos:"cos",Atan:"atan",Sqrt:"sqrt",Match:"match",Between:"between",Interpolate:"interpolate",Coalesce:"coalesce",Case:"case",In:"in",Number:"number",String:"string",Array:"array",Color:"color",Id:"id",Band:"band",Palette:"palette",ToString:"to-string",Has:"has"},oh={[sh.Get]:fh(ch(1,1/0),ah),[sh.Var]:fh(ch(1,1),(function(t,e,i){const n=t[1];if("string"!=typeof n)throw new Error("expected a string argument for var operation");return i.variables.add(n),[new eh($l,n)]})),[sh.Has]:fh(ch(1,1/0),ah),[sh.Id]:fh((function(t,e,i){i.featureId=!0}),hh),[sh.Concat]:fh(ch(2,1/0),dh($l)),[sh.GeometryType]:fh((function(t,e,i){i.geometryType=!0}),hh),[sh.LineMetric]:fh(hh),[sh.Resolution]:fh(lh,hh),[sh.Zoom]:fh(lh,hh),[sh.Time]:fh(lh,hh),[sh.Any]:fh(ch(2,1/0),dh(Xl)),[sh.All]:fh(ch(2,1/0),dh(Xl)),[sh.Not]:fh(ch(1,1),dh(Xl)),[sh.Equal]:fh(ch(2,2),dh(Hl)),[sh.NotEqual]:fh(ch(2,2),dh(Hl)),[sh.GreaterThan]:fh(ch(2,2),dh(Vl)),[sh.GreaterThanOrEqualTo]:fh(ch(2,2),dh(Vl)),[sh.LessThan]:fh(ch(2,2),dh(Vl)),[sh.LessThanOrEqualTo]:fh(ch(2,2),dh(Vl)),[sh.Multiply]:fh(ch(2,1/0),uh),[sh.Coalesce]:fh(ch(2,1/0),uh),[sh.Divide]:fh(ch(2,2),dh(Vl)),[sh.Add]:fh(ch(2,1/0),dh(Vl)),[sh.Subtract]:fh(ch(2,2),dh(Vl)),[sh.Clamp]:fh(ch(3,3),dh(Vl)),[sh.Mod]:fh(ch(2,2),dh(Vl)),[sh.Pow]:fh(ch(2,2),dh(Vl)),[sh.Abs]:fh(ch(1,1),dh(Vl)),[sh.Floor]:fh(ch(1,1),dh(Vl)),[sh.Ceil]:fh(ch(1,1),dh(Vl)),[sh.Round]:fh(ch(1,1),dh(Vl)),[sh.Sin]:fh(ch(1,1),dh(Vl)),[sh.Cos]:fh(ch(1,1),dh(Vl)),[sh.Atan]:fh(ch(1,2),dh(Vl)),[sh.Sqrt]:fh(ch(1,1),dh(Vl)),[sh.Match]:fh(ch(4,1/0),gh,(function(t,e,i){const n=t.length-1,r=$l|Vl|Xl,s=rh(t[1],r,i),o=rh(t[t.length-1],e,i),a=new Array(n-2);for(let e=0;ee){throw new Error(`expected ${e===1/0?`${t} or more`:`${t} to ${e}`} arguments for ${s}, got ${o}`)}}}function uh(t,e,i){const n=t.length-1,r=new Array(n);for(let s=0;s{for(let e=0;e{for(let r=0;r{const n=t.args;let r=e.properties[i];for(let t=1,e=n.length;tt.variables[i];case sh.Has:return e=>{const n=t.args;if(!(i in e.properties))return!1;let r=e.properties[i];for(let t=1,e=n.length;tt.featureId;case sh.GeometryType:return t=>t.geometryType;case sh.Concat:{const e=t.args.map((t=>yh(t)));return t=>"".concat(...e.map((e=>e(t).toString())))}case sh.Resolution:return t=>t.resolution;case sh.Any:case sh.All:case sh.Between:case sh.In:case sh.Not:return function(t){const e=t.operator,i=t.args.length,n=new Array(i);for(let e=0;e{for(let e=0;e{for(let e=0;e{const e=n[0](t),i=n[1](t),r=n[2](t);return e>=i&&e<=r};case sh.In:return t=>{const e=n[0](t);for(let r=1;r!n[0](t);default:throw new Error(`Unsupported logical operator ${e}`)}}(t);case sh.Equal:case sh.NotEqual:case sh.LessThan:case sh.LessThanOrEqualTo:case sh.GreaterThan:case sh.GreaterThanOrEqualTo:return function(t){const e=t.operator,i=yh(t.args[0]),n=yh(t.args[1]);switch(e){case sh.Equal:return t=>i(t)===n(t);case sh.NotEqual:return t=>i(t)!==n(t);case sh.LessThan:return t=>i(t)i(t)<=n(t);case sh.GreaterThan:return t=>i(t)>n(t);case sh.GreaterThanOrEqualTo:return t=>i(t)>=n(t);default:throw new Error(`Unsupported comparison operator ${e}`)}}(t);case sh.Multiply:case sh.Divide:case sh.Add:case sh.Subtract:case sh.Clamp:case sh.Mod:case sh.Pow:case sh.Abs:case sh.Floor:case sh.Ceil:case sh.Round:case sh.Sin:case sh.Cos:case sh.Atan:case sh.Sqrt:return function(t){const e=t.operator,i=t.args.length,n=new Array(i);for(let e=0;e{let e=1;for(let r=0;rn[0](t)/n[1](t);case sh.Add:return t=>{let e=0;for(let r=0;rn[0](t)-n[1](t);case sh.Clamp:return t=>{const e=n[0](t),i=n[1](t);if(er?r:e};case sh.Mod:return t=>n[0](t)%n[1](t);case sh.Pow:return t=>Math.pow(n[0](t),n[1](t));case sh.Abs:return t=>Math.abs(n[0](t));case sh.Floor:return t=>Math.floor(n[0](t));case sh.Ceil:return t=>Math.ceil(n[0](t));case sh.Round:return t=>Math.round(n[0](t));case sh.Sin:return t=>Math.sin(n[0](t));case sh.Cos:return t=>Math.cos(n[0](t));case sh.Atan:return 2===i?t=>Math.atan2(n[0](t),n[1](t)):t=>Math.atan(n[0](t));case sh.Sqrt:return t=>Math.sqrt(n[0](t));default:throw new Error(`Unsupported numeric operator ${e}`)}}(t);case sh.Case:return function(t){const e=t.args.length,i=new Array(e);for(let n=0;n{for(let n=0;n{const n=i[0](t);for(let r=1;r{const n=i[0](t),r=i[1](t);let s,o;for(let a=2;a=r)return 2===a?l:h?vh(n,r,s,o,e,l):xh(n,r,s,o,e,l);s=e,o=l}return o}}(t);case sh.ToString:return function(t){const e=t.operator,i=t.args.length,n=new Array(i);for(let e=0;e{const i=n[0](e);return t.args[0].type===Wl?Gl(i):i.toString()};throw new Error(`Unsupported convert operator ${e}`)}(t);default:throw new Error(`Unsupported operator ${i}`)}}function xh(t,e,i,n,r,s){const o=r-i;if(0===o)return n;const a=e-i;return n+(1===t?a/o:(Math.pow(t,a)-1)/(Math.pow(t,o)-1))*(s-n)}function vh(t,e,i,n,r,s){if(0===r-i)return n;const o=Ol(n),a=Ol(s);let l=a[2]-o[2];l>180?l-=360:l<-180&&(l+=360);return Dl([xh(t,e,i,o[0],r,a[0]),xh(t,e,i,o[1],r,a[1]),o[2]+xh(t,e,i,0,r,l),xh(t,e,i,n[3],r,s[3])])}class Sh{constructor(){this.cache_={},this.patternCache_={},this.cacheSize_=0,this.maxCacheSize_=1024}clear(){this.cache_={},this.patternCache_={},this.cacheSize_=0}canExpireCache(){return this.cacheSize_>this.maxCacheSize_}expire(){if(this.canExpireCache()){let t=0;for(const e in this.cache_){const i=this.cache_[e];3&t++||i.hasListener()||(delete this.cache_[e],delete this.patternCache_[e],--this.cacheSize_)}}}get(t,e,i){const n=Eh(t,e,i);return n in this.cache_?this.cache_[n]:null}getPattern(t,e,i){const n=Eh(t,e,i);return n in this.patternCache_?this.patternCache_[n]:null}set(t,e,i,n,r){const s=Eh(t,e,i),o=s in this.cache_;this.cache_[s]=n,r&&(n.getImageState()===bs&&n.load(),n.getImageState()===Ps?n.ready().then((()=>{this.patternCache_[s]=mt().createPattern(n.getImage(1),"repeat")})):this.patternCache_[s]=mt().createPattern(n.getImage(1),"repeat")),o||++this.cacheSize_}setSize(t){this.maxCacheSize_=t,this.expire()}}function Eh(t,e,i){return e+":"+t+":"+(i?kl(i):"null")}const wh=new Sh;let Th=null;class Ch extends O{constructor(t,e,i,n,r){super(),this.hitDetectionImage_=null,this.image_=t,this.crossOrigin_=i,this.canvas_={},this.color_=r,this.imageState_=void 0===n?bs:n,this.size_=t&&t.width&&t.height?[t.width,t.height]:null,this.src_=e,this.tainted_,this.ready_=null}initializeImage_(){this.image_=new Image,null!==this.crossOrigin_&&(this.image_.crossOrigin=this.crossOrigin_)}isTainted_(){if(void 0===this.tainted_&&this.imageState_===Is){Th||(Th=ft(1,1,void 0,{willReadFrequently:!0})),Th.drawImage(this.image_,0,0);try{Th.getImageData(0,0,1,1),this.tainted_=!1}catch{Th=null,this.tainted_=!0}}return!0===this.tainted_}dispatchChangeEvent_(){this.dispatchEvent(n)}handleImageError_(){this.imageState_=Fs,this.dispatchChangeEvent_()}handleImageLoad_(){this.imageState_=Is,this.size_=[this.image_.width,this.image_.height],this.dispatchChangeEvent_()}getImage(t){return this.image_||this.initializeImage_(),this.replaceColor_(t),this.canvas_[t]?this.canvas_[t]:this.image_}getPixelRatio(t){return this.replaceColor_(t),this.canvas_[t]?t:1}getImageState(){return this.imageState_}getHitDetectionImage(){if(this.image_||this.initializeImage_(),!this.hitDetectionImage_)if(this.isTainted_()){const t=this.size_[0],e=this.size_[1],i=ft(t,e);i.fillRect(0,0,t,e),this.hitDetectionImage_=i.canvas}else this.hitDetectionImage_=this.image_;return this.hitDetectionImage_}getSize(){return this.size_}getSrc(){return this.src_}load(){if(this.imageState_===bs){this.image_||this.initializeImage_(),this.imageState_=Ps;try{void 0!==this.src_&&(this.image_.src=this.src_)}catch{this.handleImageError_()}this.image_ instanceof HTMLImageElement&&Ds(this.image_,this.src_).then((t=>{this.image_=t,this.handleImageLoad_()})).catch(this.handleImageError_.bind(this))}}replaceColor_(t){if(!this.color_||this.canvas_[t]||this.imageState_!==Is)return;const e=this.image_,i=ft(Math.ceil(e.width*t),Math.ceil(e.height*t)),n=i.canvas;i.scale(t,t),i.drawImage(e,0,0),i.globalCompositeOperation="multiply",i.fillStyle=Rl(this.color_),i.fillRect(0,0,n.width/t,n.height/t),i.globalCompositeOperation="destination-in",i.drawImage(e,0,0),this.canvas_[t]=n}ready(){return this.ready_||(this.ready_=new Promise((t=>{if(this.imageState_===Is||this.imageState_===Fs)t();else{const e=()=>{this.imageState_!==Is&&this.imageState_!==Fs||(this.removeEventListener(n,e),t())};this.addEventListener(n,e)}}))),this.ready_}}function Rh(t,e,i,n,r,s){let o=void 0===e?void 0:wh.get(e,i,r);return o||(o=new Ch(t,t&&"src"in t?t.src||void 0:e,i,n,r),wh.set(e,i,r,o,s)),s&&o&&!wh.getPattern(e,i,r)&&wh.set(e,i,r,o,s),o}function bh(t){return t?Array.isArray(t)?Gl(t):"object"==typeof t&&"src"in t?function(t){if(!t.offset||!t.size)return wh.getPattern(t.src,"anonymous",t.color);const e=t.src+":"+t.offset,i=wh.getPattern(e,void 0,t.color);if(i)return i;const n=wh.get(t.src,"anonymous",null);if(n.getImageState()!==Is)return null;const r=ft(t.size[0],t.size[1]);return r.drawImage(n.getImage(1),t.offset[0],t.offset[1],t.size[0],t.size[1],0,0,t.size[0],t.size[1]),Rh(r.canvas,e,void 0,Is,t.color,!0),wh.getPattern(e,void 0,t.color)}(t):t:null}const Ph="10px sans-serif",Ih="#000",Fh="round",Lh=[],Mh="round",Ah=10,Oh="#000",Dh="center",Nh="middle",kh=[0,0,0,0],Gh=new V;let jh,Uh=null;const Bh={},zh=new Set(["serif","sans-serif","monospace","cursive","fantasy","system-ui","ui-serif","ui-sans-serif","ui-monospace","ui-rounded","emoji","math","fangsong"]);function Xh(t,e,i){return`${t} ${e} 16px "${i}"`}const Vh=function(){const t=100;let e,i;async function n(t){await i.ready;const e=await i.load(t);if(0===e.length)return!1;const n=jo(t),r=n.families[0].toLowerCase(),s=n.weight;return e.some((t=>{const e=t.family.replace(/^['"]|['"]$/g,"").toLowerCase(),i=Go[t.weight]||t.weight;return e===r&&t.style===n.style&&i==s}))}async function r(){await i.ready;let s=!0;const o=Gh.getProperties(),a=Object.keys(o).filter((e=>o[e]=0;--e){const i=a[e];let r=o[i];rMath.max(e,Zh(t,i))),0);return i[e]=n,n}function Hh(t,e){const i=[],n=[],r=[];let s=0,o=0,a=0,l=0;for(let h=0,c=e.length;h<=c;h+=2){const u=e[h];if("\n"===u||h===c){s=Math.max(s,o),r.push(o),o=0,a+=l,l=0;continue}const d=e[h+1]||t.font,g=Zh(d,u);i.push(g),o+=g;const f=$h(d);n.push(f),l=Math.max(l,f)}return{width:s,height:a,widths:i,heights:n,lineWidths:r}}function Kh(t,e,i,n,r,s,o,a,l,h,c){t.save(),1!==i&&(void 0===t.globalAlpha?t.globalAlpha=t=>t.globalAlpha*=i:t.globalAlpha*=i),e&&t.transform.apply(t,e),n.contextInstructions?(t.translate(l,h),t.scale(c[0],c[1]),function(t,e){const i=t.contextInstructions;for(let t=0,n=i.length;tthis.imageState_=Is)),this.render()}clone(){const t=this.getScale(),e=new Jh({fill:this.getFill()?this.getFill().clone():void 0,points:this.getPoints(),radius:this.getRadius(),radius2:this.getRadius2(),angle:this.getAngle(),stroke:this.getStroke()?this.getStroke().clone():void 0,rotation:this.getRotation(),rotateWithView:this.getRotateWithView(),scale:Array.isArray(t)?t.slice():t,displacement:this.getDisplacement().slice(),declutterMode:this.getDeclutterMode()});return e.setOpacity(this.getOpacity()),e}getAnchor(){const t=this.size_,e=this.getDisplacement(),i=this.getScaleArray();return[t[0]/2-e[0]/i[0],t[1]/2+e[1]/i[1]]}getAngle(){return this.angle_}getFill(){return this.fill_}setFill(t){this.fill_=t,this.render()}getHitDetectionImage(){return this.hitDetectionCanvas_||(this.hitDetectionCanvas_=this.createHitDetectionCanvas_(this.renderOptions_)),this.hitDetectionCanvas_}getImage(t){const e=this.fill_?.getKey(),i=`${t},${this.angle_},${this.radius},${this.radius2_},${this.points_},${e}`+Object.values(this.renderOptions_).join(",");let n=wh.get(i,null,null)?.getImage(1);if(!n){const e=this.renderOptions_,r=Math.ceil(e.size*t),s=ft(r,r);this.draw_(e,s,t),n=s.canvas,wh.set(i,null,null,new Ch(n,void 0,null,Is,null))}return n}getPixelRatio(t){return t}getImageSize(){return this.size_}getImageState(){return this.imageState_}getOrigin(){return this.origin_}getPoints(){return this.points_}getRadius(){return this.radius}getRadius2(){return this.radius2_}getSize(){return this.size_}getStroke(){return this.stroke_}setStroke(t){this.stroke_=t,this.render()}listenImageChange(t){}load(){}unlistenImageChange(t){}calculateLineJoinSize_(t,e,i){if(0===e||this.points_===1/0||"bevel"!==t&&"miter"!==t)return e;let n=this.radius,r=void 0===this.radius2_?n:this.radius2_;if(n{this.patternImage_=null})),e.getImageState()===bs&&e.load(),e.getImageState()===Ps&&(this.patternImage_=e)}this.color_=t}getKey(){const t=this.getColor();return t?t instanceof CanvasPattern||t instanceof CanvasGradient?z(t):"object"==typeof t&&"src"in t?t.src+":"+t.offset:kl(t).toString():""}loading(){return!!this.patternImage_}ready(){return this.patternImage_?this.patternImage_.ready():Promise.resolve()}}function ec(t,e,i,n){return void 0!==i&&void 0!==n?[i/t,n/e]:void 0!==i?i/t:void 0!==n?n/e:1}class ic extends qh{constructor(t){const e=void 0!==(t=t||{}).opacity?t.opacity:1,i=void 0!==t.rotation?t.rotation:0,n=void 0!==t.scale?t.scale:1,r=void 0!==t.rotateWithView&&t.rotateWithView;super({opacity:e,rotation:i,scale:n,displacement:void 0!==t.displacement?t.displacement:[0,0],rotateWithView:r,declutterMode:t.declutterMode}),this.anchor_=void 0!==t.anchor?t.anchor:[.5,.5],this.normalizedAnchor_=null,this.anchorOrigin_=void 0!==t.anchorOrigin?t.anchorOrigin:"top-left",this.anchorXUnits_=void 0!==t.anchorXUnits?t.anchorXUnits:"fraction",this.anchorYUnits_=void 0!==t.anchorYUnits?t.anchorYUnits:"fraction",this.crossOrigin_=void 0!==t.crossOrigin?t.crossOrigin:null;const s=void 0!==t.img?t.img:null;let o,a=t.src;if(Ft(!(void 0!==a&&s),"`image` and `src` cannot be provided at the same time"),void 0!==a&&0!==a.length||!s||(a=s.src||z(s)),Ft(void 0!==a&&a.length>0,"A defined and non-empty `src` or `image` must be provided"),Ft(!((void 0!==t.width||void 0!==t.height)&&void 0!==t.scale),"`width` or `height` cannot be provided together with `scale`"),void 0!==t.src?o=bs:void 0!==s&&(o="complete"in s?s.complete?s.src?Is:bs:Ps:Is),this.color_=void 0!==t.color?kl(t.color):null,this.iconImage_=Rh(s,a,this.crossOrigin_,o,this.color_),this.offset_=void 0!==t.offset?t.offset:[0,0],this.offsetOrigin_=void 0!==t.offsetOrigin?t.offsetOrigin:"top-left",this.origin_=null,this.size_=void 0!==t.size?t.size:null,this.initialOptions_,void 0!==t.width||void 0!==t.height){let e,i;if(t.size)[e,i]=t.size;else{const n=this.getImage(1);if(n.width&&n.height)e=n.width,i=n.height;else if(n instanceof HTMLImageElement){this.initialOptions_=t;const e=()=>{if(this.unlistenImageChange(e),!this.initialOptions_)return;const i=this.iconImage_.getSize();this.setScale(ec(i[0],i[1],t.width,t.height))};return void this.listenImageChange(e)}}void 0!==e&&this.setScale(ec(e,i,t.width,t.height))}}clone(){let t,e,i;return this.initialOptions_?(e=this.initialOptions_.width,i=this.initialOptions_.height):(t=this.getScale(),t=Array.isArray(t)?t.slice():t),new ic({anchor:this.anchor_.slice(),anchorOrigin:this.anchorOrigin_,anchorXUnits:this.anchorXUnits_,anchorYUnits:this.anchorYUnits_,color:this.color_&&this.color_.slice?this.color_.slice():this.color_||void 0,crossOrigin:this.crossOrigin_,offset:this.offset_.slice(),offsetOrigin:this.offsetOrigin_,opacity:this.getOpacity(),rotateWithView:this.getRotateWithView(),rotation:this.getRotation(),scale:t,width:e,height:i,size:null!==this.size_?this.size_.slice():void 0,src:this.getSrc(),displacement:this.getDisplacement().slice(),declutterMode:this.getDeclutterMode()})}getAnchor(){let t=this.normalizedAnchor_;if(!t){t=this.anchor_;const e=this.getSize();if("fraction"==this.anchorXUnits_||"fraction"==this.anchorYUnits_){if(!e)return null;t=this.anchor_.slice(),"fraction"==this.anchorXUnits_&&(t[0]*=e[0]),"fraction"==this.anchorYUnits_&&(t[1]*=e[1])}if("top-left"!=this.anchorOrigin_){if(!e)return null;t===this.anchor_&&(t=this.anchor_.slice()),"top-right"!=this.anchorOrigin_&&"bottom-right"!=this.anchorOrigin_||(t[0]=-t[0]+e[0]),"bottom-left"!=this.anchorOrigin_&&"bottom-right"!=this.anchorOrigin_||(t[1]=-t[1]+e[1])}this.normalizedAnchor_=t}const e=this.getDisplacement(),i=this.getScaleArray();return[t[0]-e[0]/i[0],t[1]+e[1]/i[1]]}setAnchor(t){this.anchor_=t,this.normalizedAnchor_=null}getColor(){return this.color_}getImage(t){return this.iconImage_.getImage(t)}getPixelRatio(t){return this.iconImage_.getPixelRatio(t)}getImageSize(){return this.iconImage_.getSize()}getImageState(){return this.iconImage_.getImageState()}getHitDetectionImage(){return this.iconImage_.getHitDetectionImage()}getOrigin(){if(this.origin_)return this.origin_;let t=this.offset_;if("top-left"!=this.offsetOrigin_){const e=this.getSize(),i=this.iconImage_.getSize();if(!e||!i)return null;t=t.slice(),"top-right"!=this.offsetOrigin_&&"bottom-right"!=this.offsetOrigin_||(t[0]=i[0]-e[0]-t[0]),"bottom-left"!=this.offsetOrigin_&&"bottom-right"!=this.offsetOrigin_||(t[1]=i[1]-e[1]-t[1])}return this.origin_=t,this.origin_}getSrc(){return this.iconImage_.getSrc()}getSize(){return this.size_?this.size_:this.iconImage_.getSize()}getWidth(){const t=this.getScaleArray();return this.size_?this.size_[0]*t[0]:this.iconImage_.getImageState()==Is?this.iconImage_.getSize()[0]*t[0]:void 0}getHeight(){const t=this.getScaleArray();return this.size_?this.size_[1]*t[1]:this.iconImage_.getImageState()==Is?this.iconImage_.getSize()[1]*t[1]:void 0}setScale(t){delete this.initialOptions_,super.setScale(t)}listenImageChange(t){this.iconImage_.addEventListener(n,t)}load(){this.iconImage_.load()}unlistenImageChange(t){this.iconImage_.removeEventListener(n,t)}ready(){return this.iconImage_.ready()}}class nc{constructor(t){t=t||{},this.color_=void 0!==t.color?t.color:null,this.lineCap_=t.lineCap,this.lineDash_=void 0!==t.lineDash?t.lineDash:null,this.lineDashOffset_=t.lineDashOffset,this.lineJoin_=t.lineJoin,this.miterLimit_=t.miterLimit,this.width_=t.width}clone(){const t=this.getColor();return new nc({color:Array.isArray(t)?t.slice():t||void 0,lineCap:this.getLineCap(),lineDash:this.getLineDash()?this.getLineDash().slice():void 0,lineDashOffset:this.getLineDashOffset(),lineJoin:this.getLineJoin(),miterLimit:this.getMiterLimit(),width:this.getWidth()})}getColor(){return this.color_}getLineCap(){return this.lineCap_}getLineDash(){return this.lineDash_}getLineDashOffset(){return this.lineDashOffset_}getLineJoin(){return this.lineJoin_}getMiterLimit(){return this.miterLimit_}getWidth(){return this.width_}setColor(t){this.color_=t}setLineCap(t){this.lineCap_=t}setLineDash(t){this.lineDash_=t}setLineDashOffset(t){this.lineDashOffset_=t}setLineJoin(t){this.lineJoin_=t}setMiterLimit(t){this.miterLimit_=t}setWidth(t){this.width_=t}}class rc{constructor(t){t=t||{},this.geometry_=null,this.geometryFunction_=hc,void 0!==t.geometry&&this.setGeometry(t.geometry),this.fill_=void 0!==t.fill?t.fill:null,this.image_=void 0!==t.image?t.image:null,this.renderer_=void 0!==t.renderer?t.renderer:null,this.hitDetectionRenderer_=void 0!==t.hitDetectionRenderer?t.hitDetectionRenderer:null,this.stroke_=void 0!==t.stroke?t.stroke:null,this.text_=void 0!==t.text?t.text:null,this.zIndex_=t.zIndex}clone(){let t=this.getGeometry();return t&&"object"==typeof t&&(t=t.clone()),new rc({geometry:t??void 0,fill:this.getFill()?this.getFill().clone():void 0,image:this.getImage()?this.getImage().clone():void 0,renderer:this.getRenderer()??void 0,stroke:this.getStroke()?this.getStroke().clone():void 0,text:this.getText()?this.getText().clone():void 0,zIndex:this.getZIndex()})}getRenderer(){return this.renderer_}setRenderer(t){this.renderer_=t}setHitDetectionRenderer(t){this.hitDetectionRenderer_=t}getHitDetectionRenderer(){return this.hitDetectionRenderer_}getGeometry(){return this.geometry_}getGeometryFunction(){return this.geometryFunction_}getFill(){return this.fill_}setFill(t){this.fill_=t}getImage(){return this.image_}setImage(t){this.image_=t}getStroke(){return this.stroke_}setStroke(t){this.stroke_=t}getText(){return this.text_}setText(t){this.text_=t}getZIndex(){return this.zIndex_}setGeometry(t){"function"==typeof t?this.geometryFunction_=t:"string"==typeof t?this.geometryFunction_=function(e){return e.get(t)}:t?void 0!==t&&(this.geometryFunction_=function(){return t}):this.geometryFunction_=hc,this.geometry_=t}setZIndex(t){this.zIndex_=t}}function sc(t){let e;if("function"==typeof t)e=t;else{let i;if(Array.isArray(t))i=t;else{Ft("function"==typeof t.getZIndex,"Expected an `Style` or an array of `Style`");i=[t]}e=function(){return i}}return e}let oc=null;function ac(t,e){if(!oc){const t=new tc({color:"rgba(255,255,255,0.4)"}),e=new nc({color:"#3399CC",width:1.25});oc=[new rc({image:new Qh({fill:t,stroke:e,radius:5}),fill:t,stroke:e})]}return oc}function lc(){const t={},e=[255,255,255,1],i=[0,153,255,1];return t.Polygon=[new rc({fill:new tc({color:[255,255,255,.5]})})],t.MultiPolygon=t.Polygon,t.LineString=[new rc({stroke:new nc({color:e,width:5})}),new rc({stroke:new nc({color:i,width:3})})],t.MultiLineString=t.LineString,t.Circle=t.Polygon.concat(t.LineString),t.Point=[new rc({image:new Qh({radius:6,fill:new tc({color:i}),stroke:new nc({color:e,width:1.5})}),zIndex:1/0})],t.MultiPoint=t.Point,t.GeometryCollection=t.Polygon.concat(t.LineString,t.Point),t}function hc(t){return t.getGeometry()}class cc{constructor(t){t=t||{},this.font_=t.font,this.rotation_=t.rotation,this.rotateWithView_=t.rotateWithView,this.keepUpright_=t.keepUpright,this.scale_=t.scale,this.scaleArray_=Bl(void 0!==t.scale?t.scale:1),this.text_=t.text,this.textAlign_=t.textAlign,this.justify_=t.justify,this.repeat_=t.repeat,this.textBaseline_=t.textBaseline,this.fill_=void 0!==t.fill?t.fill:new tc({color:"#333"}),this.maxAngle_=void 0!==t.maxAngle?t.maxAngle:Math.PI/4,this.placement_=void 0!==t.placement?t.placement:"point",this.overflow_=!!t.overflow,this.stroke_=void 0!==t.stroke?t.stroke:null,this.offsetX_=void 0!==t.offsetX?t.offsetX:0,this.offsetY_=void 0!==t.offsetY?t.offsetY:0,this.backgroundFill_=t.backgroundFill?t.backgroundFill:null,this.backgroundStroke_=t.backgroundStroke?t.backgroundStroke:null,this.padding_=void 0===t.padding?null:t.padding,this.declutterMode_=t.declutterMode}clone(){const t=this.getScale();return new cc({font:this.getFont(),placement:this.getPlacement(),repeat:this.getRepeat(),maxAngle:this.getMaxAngle(),overflow:this.getOverflow(),rotation:this.getRotation(),rotateWithView:this.getRotateWithView(),keepUpright:this.getKeepUpright(),scale:Array.isArray(t)?t.slice():t,text:this.getText(),textAlign:this.getTextAlign(),justify:this.getJustify(),textBaseline:this.getTextBaseline(),fill:this.getFill()?this.getFill().clone():void 0,stroke:this.getStroke()?this.getStroke().clone():void 0,offsetX:this.getOffsetX(),offsetY:this.getOffsetY(),backgroundFill:this.getBackgroundFill()?this.getBackgroundFill().clone():void 0,backgroundStroke:this.getBackgroundStroke()?this.getBackgroundStroke().clone():void 0,padding:this.getPadding()||void 0,declutterMode:this.getDeclutterMode()})}getOverflow(){return this.overflow_}getFont(){return this.font_}getMaxAngle(){return this.maxAngle_}getPlacement(){return this.placement_}getRepeat(){return this.repeat_}getOffsetX(){return this.offsetX_}getOffsetY(){return this.offsetY_}getFill(){return this.fill_}getRotateWithView(){return this.rotateWithView_}getKeepUpright(){return this.keepUpright_}getRotation(){return this.rotation_}getScale(){return this.scale_}getScaleArray(){return this.scaleArray_}getStroke(){return this.stroke_}getText(){return this.text_}getTextAlign(){return this.textAlign_}getJustify(){return this.justify_}getTextBaseline(){return this.textBaseline_}getBackgroundFill(){return this.backgroundFill_}getBackgroundStroke(){return this.backgroundStroke_}getPadding(){return this.padding_}getDeclutterMode(){return this.declutterMode_}setOverflow(t){this.overflow_=t}setFont(t){this.font_=t}setMaxAngle(t){this.maxAngle_=t}setOffsetX(t){this.offsetX_=t}setOffsetY(t){this.offsetY_=t}setPlacement(t){this.placement_=t}setRepeat(t){this.repeat_=t}setRotateWithView(t){this.rotateWithView_=t}setKeepUpright(t){this.keepUpright_=t}setFill(t){this.fill_=t}setRotation(t){this.rotation_=t}setScale(t){this.scale_=t,this.scaleArray_=Bl(void 0!==t?t:1)}setStroke(t){this.stroke_=t}setText(t){this.text_=t}setTextAlign(t){this.textAlign_=t}setJustify(t){this.justify_=t}setTextBaseline(t){this.textBaseline_=t}setBackgroundFill(t){this.backgroundFill_=t}setBackgroundStroke(t){this.backgroundStroke_=t}setPadding(t){this.padding_=t}}function uc(t){return!0}function dc(t){const e=nh(),i=fc(t,e),n={variables:{},properties:{},resolution:NaN,featureId:null,geometryType:""};return function(t,r){if(n.properties=t.getPropertiesInternal(),n.resolution=r,e.featureId){const e=t.getId();n.featureId=void 0!==e?e:null}return e.geometryType&&(n.geometryType=ph(t.getGeometry())),i(n)}}function gc(t){const e=nh(),i=t.length,n=new Array(i);for(let r=0;rnull;n=Sc(t,e+"fill-color",i)}if(!n)return null;const r=new tc;return function(t){const e=n(t);return e===_l?null:(r.setColor(e),r)}}function _c(t,e,i){const n=yc(t,e+"stroke-width",i),r=Sc(t,e+"stroke-color",i);if(!n&&!r)return null;const s=xc(t,e+"stroke-line-cap",i),o=xc(t,e+"stroke-line-join",i),a=Ec(t,e+"stroke-line-dash",i),l=yc(t,e+"stroke-line-dash-offset",i),h=yc(t,e+"stroke-miter-limit",i),c=new nc;return function(t){if(r){const e=r(t);if(e===_l)return null;c.setColor(e)}if(n&&c.setWidth(n(t)),s){const e=s(t);if("butt"!==e&&"round"!==e&&"square"!==e)throw new Error("Expected butt, round, or square line cap");c.setLineCap(e)}if(o){const e=o(t);if("bevel"!==e&&"round"!==e&&"miter"!==e)throw new Error("Expected bevel, round, or miter line join");c.setLineJoin(e)}return a&&c.setLineDash(a(t)),l&&c.setLineDashOffset(l(t)),h&&c.setMiterLimit(h(t)),c}}function yc(t,e,i){if(!(e in t))return;const n=_h(t[e],Vl,i);return function(t){return Mc(n(t),e)}}function xc(t,e,i){if(!(e in t))return null;const n=_h(t[e],$l,i);return function(t){return Lc(n(t),e)}}function vc(t,e,i){if(!(e in t))return null;const n=_h(t[e],Xl,i);return function(t){const i=n(t);if("boolean"!=typeof i)throw new Error(`Expected a boolean for ${e}`);return i}}function Sc(t,e,i){if(!(e in t))return null;const n=_h(t[e],Wl,i);return function(t){return Ac(n(t),e)}}function Ec(t,e,i){if(!(e in t))return null;const n=_h(t[e],Zl,i);return function(t){return Fc(n(t),e)}}function wc(t,e,i){if(!(e in t))return null;const n=_h(t[e],Zl,i);return function(t){const i=Fc(n(t),e);if(2!==i.length)throw new Error(`Expected two numbers for ${e}`);return i}}function Tc(t,e,i){if(!(e in t))return null;const n=_h(t[e],Zl,i);return function(t){return Oc(n(t),e)}}function Cc(t,e,i){if(!(e in t))return null;const n=_h(t[e],Zl|Vl,i);return function(t){return function(t,e){if("number"==typeof t)return t;return Oc(t,e)}(n(t),e)}}function Rc(t,e){const i=t[e];if(void 0!==i){if("number"!=typeof i)throw new Error(`Expected a number for ${e}`);return i}}function bc(t,e){const i=t[e];if(void 0!==i){if("bottom-left"!==i&&"bottom-right"!==i&&"top-left"!==i&&"top-right"!==i)throw new Error(`Expected bottom-left, bottom-right, top-left, or top-right for ${e}`);return i}}function Pc(t,e){const i=t[e];if(void 0!==i){if("pixels"!==i&&"fraction"!==i)throw new Error(`Expected pixels or fraction for ${e}`);return i}}function Ic(t,e){const i=t[e];if(void 0!==i){if("string"!=typeof i)throw new Error(`Expected a string for ${e}`);if("declutter"!==i&&"obstacle"!==i&&"none"!==i)throw new Error(`Expected declutter, obstacle, or none for ${e}`);return i}}function Fc(t,e){if(!Array.isArray(t))throw new Error(`Expected an array for ${e}`);const i=t.length;for(let n=0;n4)throw new Error(`Expected a color with 3 or 4 values for ${e}`);return i}function Oc(t,e){const i=Fc(t,e);if(2!==i.length)throw new Error(`Expected an array of two numbers for ${e}`);return i}const Dc="renderOrder";class Nc extends Ja{constructor(t){t=t||{};const e=Object.assign({},t);delete e.style,delete e.renderBuffer,delete e.updateWhileAnimating,delete e.updateWhileInteracting,super(e),this.declutter_=t.declutter?String(t.declutter):void 0,this.renderBuffer_=void 0!==t.renderBuffer?t.renderBuffer:100,this.style_=null,this.styleFunction_=void 0,this.setStyle(t.style),this.updateWhileAnimating_=void 0!==t.updateWhileAnimating&&t.updateWhileAnimating,this.updateWhileInteracting_=void 0!==t.updateWhileInteracting&&t.updateWhileInteracting}getDeclutter(){return this.declutter_}getFeatures(t){return super.getFeatures(t)}getRenderBuffer(){return this.renderBuffer_}getRenderOrder(){return this.get(Dc)}getStyle(){return this.style_}getStyleFunction(){return this.styleFunction_}getUpdateWhileAnimating(){return this.updateWhileAnimating_}getUpdateWhileInteracting(){return this.updateWhileInteracting_}renderDeclutter(t,e){const i=this.getDeclutter();i in t.declutter==!1&&(t.declutter[i]=new nl(9)),this.getRenderer().renderDeclutter(t,e)}setRenderOrder(t){this.set(Dc,t)}setStyle(t){this.style_=void 0===t?ac:t;const e=function(t){if(void 0===t)return ac;if(!t)return null;if("function"==typeof t)return t;if(t instanceof rc)return t;if(!Array.isArray(t))return gc([t]);if(0===t.length)return[];const e=t.length,i=t[0];if(i instanceof rc){const i=new Array(e);for(let n=0;n=0;--r){const s=f[r],u=s.layer;if(u.hasRenderer()&&Qa(s,h)&&o.call(a,u)){const r=u.getRenderer(),o=u.getSource();if(r&&o){const a=o.getWrapX()?d:t,h=c.bind(null,s.managed);_[0]=a[0]+g[n][0],_[1]=a[1]+g[n][1],l=r.forEachFeatureAtCoordinate(_,e,i,h,m)}if(l)return l}}if(0===m.length)return;const y=1/m.length;return m.forEach(((t,e)=>t.distanceSq+=e*y)),m.sort(((t,e)=>t.distanceSq-e.distanceSq)),m.some((t=>l=t.callback(t.feature,t.layer,t.geometry))),l}hasFeatureAtCoordinate(t,e,i,n,r,s){return void 0!==this.forEachFeatureAtCoordinate(t,e,i,n,C,this,r,s)}getMap(){return this.map_}renderFrame(t){U()}scheduleExpireIconCache(t){wh.canExpireCache()&&t.postRenderFunctions.push(jc)}}function jc(t,e){wh.expire()}class Uc extends Gc{constructor(t){super(t),this.fontChangeListenerKey_=D(Gh,i,t.redrawText,t),this.element_=document.createElement("div");const e=this.element_.style;e.position="absolute",e.width="100%",e.height="100%",e.zIndex="0",this.element_.className=Mo+" ol-layers";const n=t.getViewport();n.insertBefore(this.element_,n.firstChild||null),this.children_=[],this.renderedVisible_=!0}dispatchRenderEvent(t,e){const i=this.getMap();if(i.hasListener(t)){const n=new kc(t,void 0,e);i.dispatchEvent(n)}}disposeInternal(){k(this.fontChangeListenerKey_),this.element_.remove(),super.disposeInternal()}renderFrame(t){if(!t)return void(this.renderedVisible_&&(this.element_.style.display="none",this.renderedVisible_=!1));this.calculateMatrices2D(t),this.dispatchRenderEvent(Ha,t);const e=t.layerStatesArray.sort(((t,e)=>t.zIndex-e.zIndex));e.some((t=>t.layer instanceof Nc&&t.layer.getDeclutter()))&&(t.declutter={});const i=t.viewState;this.children_.length=0;const n=[];let r=null;for(let s=0,o=e.length;s=0;--i){const n=e[i],r=n.layer;r.getDeclutter()&&r.renderDeclutter(t,n)}e.forEach((e=>e.layer.renderDeferred(t)))}}}function Bc(t){t instanceof Ja?t.setMapInternal(null):t instanceof Wa&&t.getLayers().forEach(Bc)}function zc(t,e){if(t instanceof Ja)t.setMapInternal(e);else if(t instanceof Wa){const i=t.getLayers().getArray();for(let t=0,n=i.length;tthis.updateSize())),this.controls=n.controls||Vo(),this.interactions=n.interactions||Ma({onFocusOnly:!0}),this.overlays_=n.overlays,this.overlayIdIndex_={},this.renderer_=null,this.postRenderFunctions_=[],this.tileQueue_=new so(this.getTilePriority.bind(this),this.handleTileChange_.bind(this)),this.addChangeListener(Qs,this.handleLayerGroupChanged_),this.addChangeListener(io,this.handleViewChanged_),this.addChangeListener(to,this.handleSizeChanged_),this.addChangeListener(eo,this.handleTargetChanged_),this.setProperties(n.values);const r=this;!i.view||i.view instanceof wo||i.view.then((function(t){r.setView(new wo(t))})),this.controls.addEventListener(t,(t=>{t.element.setMap(this)})),this.controls.addEventListener(e,(t=>{t.element.setMap(null)})),this.interactions.addEventListener(t,(t=>{t.element.setMap(this)})),this.interactions.addEventListener(e,(t=>{t.element.setMap(null)})),this.overlays_.addEventListener(t,(t=>{this.addOverlayInternal_(t.element)})),this.overlays_.addEventListener(e,(t=>{const e=t.element.getId();void 0!==e&&delete this.overlayIdIndex_[e.toString()],t.element.setMap(null)})),this.controls.forEach((t=>{t.setMap(this)})),this.interactions.forEach((t=>{t.setMap(this)})),this.overlays_.forEach(this.addOverlayInternal_.bind(this))}addControl(t){this.getControls().push(t)}addInteraction(t){this.getInteractions().push(t)}addLayer(t){this.getLayerGroup().getLayers().push(t)}handleLayerAdd_(t){zc(t.layer,this)}addOverlay(t){this.getOverlays().push(t)}addOverlayInternal_(t){const e=t.getId();void 0!==e&&(this.overlayIdIndex_[e.toString()]=t),t.setMap(this)}disposeInternal(){this.controls.clear(),this.interactions.clear(),this.overlays_.clear(),this.resizeObserver_.disconnect(),this.setTarget(null),super.disposeInternal()}forEachFeatureAtPixel(t,e,i){if(!this.frameState_||!this.renderer_)return;const n=this.getCoordinateFromPixelInternal(t),r=void 0!==(i=void 0!==i?i:{}).hitTolerance?i.hitTolerance:0,s=void 0!==i.layerFilter?i.layerFilter:C,o=!1!==i.checkWrapped;return this.renderer_.forEachFeatureAtCoordinate(n,this.frameState_,r,o,e,null,s,null)}getFeaturesAtPixel(t,e){const i=[];return this.forEachFeatureAtPixel(t,(function(t){i.push(t)}),e),i}getAllLayers(){const t=[];return function e(i){i.forEach((function(i){i instanceof Wa?e(i.getLayers()):t.push(i)}))}(this.getLayers()),t}hasFeatureAtPixel(t,e){if(!this.frameState_||!this.renderer_)return!1;const i=this.getCoordinateFromPixelInternal(t),n=void 0!==(e=void 0!==e?e:{}).layerFilter?e.layerFilter:C,r=void 0!==e.hitTolerance?e.hitTolerance:0,s=!1!==e.checkWrapped;return this.renderer_.hasFeatureAtCoordinate(i,this.frameState_,r,s,n,null)}getEventCoordinate(t){return this.getCoordinateFromPixel(this.getEventPixel(t))}getEventCoordinateInternal(t){return this.getCoordinateFromPixelInternal(this.getEventPixel(t))}getEventPixel(t){const e=this.viewport_.getBoundingClientRect(),i=this.getSize(),n=e.width/i[0],r=e.height/i[1],s="changedTouches"in t?t.changedTouches[0]:t;return[(s.clientX-e.left)/n,(s.clientY-e.top)/r]}getTarget(){return this.get(eo)}getTargetElement(){return this.targetElement_}getCoordinateFromPixel(t){return Nn(this.getCoordinateFromPixelInternal(t),this.getView().getProjection())}getCoordinateFromPixelInternal(t){const e=this.frameState_;return e?Kn(e.pixelToCoordinateTransform,t.slice()):null}getControls(){return this.controls}getOverlays(){return this.overlays_}getOverlayById(t){const e=this.overlayIdIndex_[t.toString()];return void 0!==e?e:null}getInteractions(){return this.interactions}getLayerGroup(){return this.get(Qs)}setLayers(t){const e=this.getLayerGroup();if(t instanceof Z)return void e.setLayers(t);const i=e.getLayers();i.clear(),i.extend(t)}getLayers(){return this.getLayerGroup().getLayers()}getLoadingOrNotReady(){const t=this.getLayerGroup().getLayerStatesArray();for(let e=0,i=t.length;e=0;i--){const n=e[i];if(n.getMap()!==this||!n.getActive()||!this.getTargetElement())continue;if(!n.handleEvent(t)||t.propagationStopped)break}}}handlePostRender(){const t=this.frameState_,e=this.tileQueue_;if(!e.isEmpty()){let i=this.maxTilesLoading_,n=i;if(t){const e=t.viewHints;if(e[ao]||e[lo]){const e=Date.now()-t.time>8;i=e?0:8,n=e?0:2}}e.getTilesLoading(){this.postRenderTimeoutHandle_=void 0,this.handlePostRender()}),0))}setLayerGroup(t){const e=this.getLayerGroup();e&&this.handleLayerRemove_(new Va("removelayer",e)),this.set(Qs,t)}setSize(t){this.set(to,t)}setTarget(t){this.set(eo,t)}setView(t){if(!t||t instanceof wo)return void this.set(io,t);this.set(io,new wo);const e=this;t.then((function(t){e.setView(new wo(t))}))}updateSize(){const t=this.getTargetElement();let e;if(t){const i=getComputedStyle(t),n=t.offsetWidth-parseFloat(i.borderLeftWidth)-parseFloat(i.paddingLeft)-parseFloat(i.paddingRight)-parseFloat(i.borderRightWidth),r=t.offsetHeight-parseFloat(i.borderTopWidth)-parseFloat(i.paddingTop)-parseFloat(i.paddingBottom)-parseFloat(i.borderBottomWidth);isNaN(n)||isNaN(r)||(e=[Math.max(0,n),Math.max(0,r)],!jl(e)&&(t.offsetWidth||t.offsetHeight||t.getClientRects().length)&&Ze("No map visible because the map container's width or height are 0."))}const i=this.getSize();!e||i&&w(e,i)||(this.setSize(e),this.updateViewportSize_(e))}updateViewportSize_(t){const e=this.getView();e&&e.setViewportSize(t)}};const Vc="element",$c="map",Wc="offset",Zc="position",Yc="positioning";class Hc extends V{constructor(t){super(),this.on,this.once,this.un,this.options=t,this.id=t.id,this.insertFirst=void 0===t.insertFirst||t.insertFirst,this.stopEvent=void 0===t.stopEvent||t.stopEvent,this.element=document.createElement("div"),this.element.className=void 0!==t.className?t.className:"ol-overlay-container "+Lo,this.element.style.position="absolute",this.element.style.pointerEvents="auto",this.autoPan=!0===t.autoPan?{}:t.autoPan||void 0,this.rendered={transform_:"",visible:!0},this.mapPostrenderListenerKey=null,this.addChangeListener(Vc,this.handleElementChanged),this.addChangeListener($c,this.handleMapChanged),this.addChangeListener(Wc,this.handleOffsetChanged),this.addChangeListener(Zc,this.handlePositionChanged),this.addChangeListener(Yc,this.handlePositioningChanged),void 0!==t.element&&this.setElement(t.element),this.setOffset(void 0!==t.offset?t.offset:[0,0]),this.setPositioning(t.positioning||"top-left"),void 0!==t.position&&this.setPosition(t.position)}getElement(){return this.get(Vc)}getId(){return this.id}getMap(){return this.get($c)||null}getOffset(){return this.get(Wc)}getPosition(){return this.get(Zc)}getPositioning(){return this.get(Yc)}handleElementChanged(){St(this.element);const t=this.getElement();t&&this.element.appendChild(t)}handleMapChanged(){this.mapPostrenderListenerKey&&(this.element?.remove(),k(this.mapPostrenderListenerKey),this.mapPostrenderListenerKey=null);const t=this.getMap();if(t){this.mapPostrenderListenerKey=D(t,Ys,this.render,this),this.updatePixelPosition();const e=this.stopEvent?t.getOverlayContainerStopEvent():t.getOverlayContainer();this.insertFirst?e.insertBefore(this.element,e.childNodes[0]||null):e.appendChild(this.element),this.performAutoPan()}}render(){this.updatePixelPosition()}handleOffsetChanged(){this.updatePixelPosition()}handlePositionChanged(){this.updatePixelPosition(),this.performAutoPan()}handlePositioningChanged(){this.updatePixelPosition()}setElement(t){this.set(Vc,t)}setMap(t){this.set($c,t)}setOffset(t){this.set(Wc,t)}setPosition(t){this.set(Zc,t)}performAutoPan(){this.autoPan&&this.panIntoView(this.autoPan)}panIntoView(t){const e=this.getMap();if(!e||!e.getTargetElement()||!this.get(Zc))return;const i=this.getRect(e.getTargetElement(),e.getSize()),n=this.getElement(),r=this.getRect(n,[yt(n),xt(n)]),s=void 0===(t=t||{}).margin?20:t.margin;if(!Vt(i,r)){const n=r[0]-i[0],o=i[2]-r[2],a=r[1]-i[1],l=i[3]-r[3],h=[0,0];if(n<0?h[0]=n-s:o<0&&(h[0]=Math.abs(o)+s),a<0?h[1]=a-s:l<0&&(h[1]=Math.abs(l)+s),0!==h[0]||0!==h[1]){const i=e.getView().getCenterInternal(),n=e.getPixelFromCoordinateInternal(i);if(!n)return;const r=[n[0]+h[0],n[1]+h[1]],s=t.animation||{};e.getView().animateInternal({center:e.getCoordinateFromPixelInternal(r),duration:s.duration,easing:s.easing})}}}getRect(t,e){const i=t.getBoundingClientRect(),n=i.left+window.pageXOffset,r=i.top+window.pageYOffset;return[n,r,n+e[0],r+e[1]]}setPositioning(t){this.set(Yc,t)}setVisible(t){this.rendered.visible!==t&&(this.element.style.display=t?"":"none",this.rendered.visible=t)}updatePixelPosition(){const t=this.getMap(),e=this.getPosition();if(!t||!t.isRendered()||!e)return void this.setVisible(!1);const i=t.getPixelFromCoordinate(e),n=t.getSize();this.updateRenderedPosition(i,n)}updateRenderedPosition(t,e){const i=this.element.style,n=this.getOffset(),r=this.getPositioning();this.setVisible(!0);let s="0%",o="0%";"bottom-right"==r||"center-right"==r||"top-right"==r?s="-100%":"bottom-center"!=r&&"center-center"!=r&&"top-center"!=r||(s="-50%"),"bottom-left"==r||"bottom-center"==r||"bottom-right"==r?o="-100%":"center-left"!=r&&"center-center"!=r&&"center-right"!=r||(o="-50%");const a=`translate(${s}, ${o}) translate(${`${t[0]+n[0]}px`}, ${`${t[1]+n[1]}px`})`;this.rendered.transform_!=a&&(this.rendered.transform_=a,i.transform=a)}getOptions(){return this.options}}class Kc{constructor(t,e,i,n){this.minX=t,this.maxX=e,this.minY=i,this.maxY=n}contains(t){return this.containsXY(t[1],t[2])}containsTileRange(t){return this.minX<=t.minX&&t.maxX<=this.maxX&&this.minY<=t.minY&&t.maxY<=this.maxY}containsXY(t,e){return this.minX<=t&&t<=this.maxX&&this.minY<=e&&e<=this.maxY}equals(t){return this.minX==t.minX&&this.minY==t.minY&&this.maxX==t.maxX&&this.maxY==t.maxY}extend(t){t.minXthis.maxX&&(this.maxX=t.maxX),t.minYthis.maxY&&(this.maxY=t.maxY)}getHeight(){return this.maxY-this.minY+1}getSize(){return[this.getWidth(),this.getHeight()]}getWidth(){return this.maxX-this.minX+1}intersects(t){return this.minX<=t.maxX&&this.maxX>=t.minX&&this.minY<=t.maxY&&this.maxY>=t.minY}}function qc(t,e,i,n,r){return void 0!==r?(r.minX=t,r.maxX=e,r.minY=i,r.maxY=n,r):new Kc(t,e,i,n)}const Jc=[];class Qc extends nt{constructor(t,e,i,n,r){super(t,e,{transition:0}),this.context_=null,this.executorGroups={},this.loadingSourceTiles=0,this.hitDetectionImageData={},this.replayState_={},this.sourceTiles=[],this.errorTileKeys={},this.wantedResolution,this.getSourceTiles=n.bind(void 0,this),this.removeSourceTiles_=r,this.wrappedTileCoord=i}getContext(){return this.context_||(this.context_=ft(1,1,Jc)),this.context_}hasContext(){return!!this.context_}getImage(){return this.hasContext()?this.getContext().canvas:null}getReplayState(t){const e=z(t);return e in this.replayState_||(this.replayState_[e]={dirty:!1,renderedRenderOrder:null,renderedResolution:NaN,renderedRevision:-1,renderedTileResolution:NaN,renderedTileRevision:-1,renderedTileZ:-1}),this.replayState_[e]}load(){this.getSourceTiles()}release(){this.context_&&(_t(this.context_),Jc.push(this.context_.canvas),this.context_=null),this.removeSourceTiles_(this),this.sourceTiles.length=0,super.release()}}let tu,eu=class extends nt{constructor(t,e,i,n,r,s){super(t,e,s),this.extent=null,this.format_=n,this.features_=null,this.loader_,this.projection=null,this.resolution,this.tileLoadFunction_=r,this.url_=i,this.key=i}getTileUrl(){return this.url_}getFormat(){return this.format_}getFeatures(){return this.features_}load(){this.state==Y&&(this.setState(H),this.tileLoadFunction_(this,this.url_),this.loader_&&this.loader_(this.extent,this.resolution,this.projection))}onLoad(t,e){this.setFeatures(t)}onError(){this.setState(q)}setFeatures(t){this.features_=t,this.setState(K)}setLoader(t){this.loader_=t}},iu=!1;function nu(t,e,i,n,r,s,o){const a=new XMLHttpRequest;a.open("GET","function"==typeof t?t(i,n,r):t,!0),"arraybuffer"==e.getType()&&(a.responseType="arraybuffer"),a.withCredentials=iu,a.onload=function(t){if(!a.status||a.status>=200&&a.status<300){const t=e.getType();try{let n;"text"==t||"json"==t?n=a.responseText:"xml"==t?n=a.responseXML||a.responseText:"arraybuffer"==t&&(n=a.response),n?s(e.readFeatures(n,{extent:i,featureProjection:r}),e.readProjection(n)):o()}catch{o()}}else o()},a.onerror=o,a.send()}function ru(t,e){return function(i,n,r,s,o){nu(t,e,i,n,r,((t,e)=>{this.addFeatures(t),void 0!==s&&s(t)}),(()=>{this.changed(),void 0!==o&&o()}))}}function su(t,e){return[[-1/0,-1/0,1/0,1/0]]}function ou(t,e,i,n){const r=document.createElement("script"),s="olc_"+z(e);function o(){delete window[s],r.parentNode.removeChild(r)}r.async=!0,r.src=t+(t.includes("?")?"&":"?")+(n||"callback")+"="+s;const a=setTimeout((function(){o(),i&&i()}),1e4);window[s]=function(t){clearTimeout(a),o(),e(t)},document.head.appendChild(r)}class au extends Error{constructor(t){super("Unexpected response status: "+t.status),this.name="ResponseError",this.response=t}}class lu extends Error{constructor(t){super("Failed to issue request"),this.name="ClientError",this.client=t}}function hu(t){return new Promise((function(e,i){const n=new XMLHttpRequest;n.addEventListener("load",(function(t){const n=t.target;if(!n.status||n.status>=200&&n.status<300){let t;try{t=JSON.parse(n.responseText)}catch(t){const e="Error parsing response text as JSON: "+t.message;return void i(new Error(e))}e(t)}else i(new au(n))})),n.addEventListener("error",(function(t){i(new lu(t.target))})),n.open("GET",t),n.setRequestHeader("Accept","application/json"),n.send()}))}function cu(t,e){return e.includes("://")?e:new URL(e,t).href}class uu{drawCustom(t,e,i,n,r){}drawGeometry(t){}setStyle(t){}drawCircle(t,e,i){}drawFeature(t,e,i){}drawGeometryCollection(t,e,i){}drawLineString(t,e,i){}drawMultiLineString(t,e,i){}drawMultiPoint(t,e,i){}drawMultiPolygon(t,e,i){}drawPoint(t,e,i){}drawPolygon(t,e,i){}drawText(t,e,i){}setFillStrokeStyle(t,e){}setImageStyle(t,e){}setTextStyle(t,e){}}class du extends uu{constructor(t,e,i,n,r,s,o){super(),this.context_=t,this.pixelRatio_=e,this.extent_=i,this.transform_=n,this.transformRotation_=n?De(Math.atan2(n[1],n[0]),10):0,this.viewRotation_=r,this.squaredTolerance_=s,this.userTransform_=o,this.contextFillState_=null,this.contextStrokeState_=null,this.contextTextState_=null,this.fillState_=null,this.strokeState_=null,this.image_=null,this.imageAnchorX_=0,this.imageAnchorY_=0,this.imageHeight_=0,this.imageOpacity_=0,this.imageOriginX_=0,this.imageOriginY_=0,this.imageRotateWithView_=!1,this.imageRotation_=0,this.imageScale_=[0,0],this.imageWidth_=0,this.text_="",this.textOffsetX_=0,this.textOffsetY_=0,this.textRotateWithView_=!1,this.textRotation_=0,this.textScale_=[0,0],this.textFillState_=null,this.textStrokeState_=null,this.textState_=null,this.pixelCoordinates_=[],this.tmpLocalTransform_=[1,0,0,1,0,0]}drawImages_(t,e,i,n){if(!this.image_)return;const r=ar(t,e,i,n,this.transform_,this.pixelCoordinates_),s=this.context_,o=this.tmpLocalTransform_,a=s.globalAlpha;1!=this.imageOpacity_&&(s.globalAlpha=a*this.imageOpacity_);let l=this.imageRotation_;0===this.transformRotation_&&(l-=this.viewRotation_),this.imageRotateWithView_&&(l+=this.viewRotation_);for(let t=0,e=r.length;tt*this.pixelRatio_)),lineDashOffset:(r||0)*this.pixelRatio_,lineJoin:void 0!==s?s:Mh,lineWidth:(void 0!==o?o:1)*this.pixelRatio_,miterLimit:void 0!==a?a:Ah,strokeStyle:bh(t||Oh)}}else this.strokeState_=null}setImageStyle(t){let e;if(!t||!(e=t.getSize()))return void(this.image_=null);const i=t.getPixelRatio(this.pixelRatio_),n=t.getAnchor(),r=t.getOrigin();this.image_=t.getImage(this.pixelRatio_),this.imageAnchorX_=n[0]*i,this.imageAnchorY_=n[1]*i,this.imageHeight_=e[1]*i,this.imageOpacity_=t.getOpacity(),this.imageOriginX_=r[0],this.imageOriginY_=r[1],this.imageRotateWithView_=t.getRotateWithView(),this.imageRotation_=t.getRotation();const s=t.getScaleArray();this.imageScale_=[s[0]*this.pixelRatio_/i,s[1]*this.pixelRatio_/i],this.imageWidth_=e[0]*i}setTextStyle(t){if(t){const e=t.getFill();if(e){const t=e.getColor();this.textFillState_={fillStyle:bh(t||Ih)}}else this.textFillState_=null;const i=t.getStroke();if(i){const t=i.getColor(),e=i.getLineCap(),n=i.getLineDash(),r=i.getLineDashOffset(),s=i.getLineJoin(),o=i.getWidth(),a=i.getMiterLimit();this.textStrokeState_={lineCap:void 0!==e?e:Fh,lineDash:n||Lh,lineDashOffset:r||0,lineJoin:void 0!==s?s:Mh,lineWidth:void 0!==o?o:1,miterLimit:void 0!==a?a:Ah,strokeStyle:bh(t||Oh)}}else this.textStrokeState_=null;const n=t.getFont(),r=t.getOffsetX(),s=t.getOffsetY(),o=t.getRotateWithView(),a=t.getRotation(),l=t.getScaleArray(),h=t.getText(),c=t.getTextAlign(),u=t.getTextBaseline();this.textState_={font:void 0!==n?n:Ph,textAlign:void 0!==c?c:Dh,textBaseline:void 0!==u?u:Nh},this.text_=void 0!==h?Array.isArray(h)?h.reduce(((t,e,i)=>t+(i%2?" ":e)),""):h:"",this.textOffsetX_=void 0!==r?this.pixelRatio_*r:0,this.textOffsetY_=void 0!==s?this.pixelRatio_*s:0,this.textRotateWithView_=void 0!==o&&o,this.textRotation_=void 0!==a?a:0,this.textScale_=[this.pixelRatio_*l[0],this.pixelRatio_*l[1]]}else this.text_=""}}const gu={Point:function(t,e,i,n,r,s){const o=i.getImage(),a=i.getText(),l=a&&a.getText(),h=s&&o&&l?{}:void 0;if(o){if(o.getImageState()!=Is)return;const s=t.getBuilder(i.getZIndex(),"Image");s.setImageStyle(o,h),s.drawPoint(e,n,r)}if(l){const s=t.getBuilder(i.getZIndex(),"Text");s.setTextStyle(a,h),s.drawText(e,n,r)}},LineString:function(t,e,i,n,r){const s=i.getStroke();if(s){const o=t.getBuilder(i.getZIndex(),"LineString");o.setFillStrokeStyle(null,s),o.drawLineString(e,n,r)}const o=i.getText();if(o&&o.getText()){const s=t.getBuilder(i.getZIndex(),"Text");s.setTextStyle(o),s.drawText(e,n,r)}},Polygon:function(t,e,i,n,r){const s=i.getFill(),o=i.getStroke();if(s||o){const a=t.getBuilder(i.getZIndex(),"Polygon");a.setFillStrokeStyle(s,o),a.drawPolygon(e,n,r)}const a=i.getText();if(a&&a.getText()){const s=t.getBuilder(i.getZIndex(),"Text");s.setTextStyle(a),s.drawText(e,n,r)}},MultiPoint:function(t,e,i,n,r,s){const o=i.getImage(),a=o&&0!==o.getOpacity(),l=i.getText(),h=l&&l.getText(),c=s&&a&&h?{}:void 0;if(a){if(o.getImageState()!=Is)return;const s=t.getBuilder(i.getZIndex(),"Image");s.setImageStyle(o,c),s.drawMultiPoint(e,n,r)}if(h){const s=t.getBuilder(i.getZIndex(),"Text");s.setTextStyle(l,c),s.drawText(e,n,r)}},MultiLineString:function(t,e,i,n,r){const s=i.getStroke();if(s){const o=t.getBuilder(i.getZIndex(),"LineString");o.setFillStrokeStyle(null,s),o.drawMultiLineString(e,n,r)}const o=i.getText();if(o&&o.getText()){const s=t.getBuilder(i.getZIndex(),"Text");s.setTextStyle(o),s.drawText(e,n,r)}},MultiPolygon:function(t,e,i,n,r){const s=i.getFill(),o=i.getStroke();if(o||s){const a=t.getBuilder(i.getZIndex(),"Polygon");a.setFillStrokeStyle(s,o),a.drawMultiPolygon(e,n,r)}const a=i.getText();if(a&&a.getText()){const s=t.getBuilder(i.getZIndex(),"Text");s.setTextStyle(a),s.drawText(e,n,r)}},GeometryCollection:function(t,e,i,n,r,s){const o=e.getGeometriesArray();let a,l;for(a=0,l=o.length;a0;return u&&Promise.all(l).then((()=>r(null))),function(t,e,i,n,r,s,o){const a=i.getGeometryFunction()(e);if(!a)return;const l=a.simplifyTransformed(n,r),h=i.getRenderer();if(h)yu(t,l,i,e,o);else{(0,gu[l.getType()])(t,l,i,e,o,s)}}(t,e,i,n,s,o,a),u}function yu(t,e,i,n,r){if("GeometryCollection"==e.getType()){const s=e.getGeometries();for(let e=0,o=s.length;e2||Math.abs(t[4*e+3]-191.25)>2}function Tu(t,e,i,n){const r=Ln(i,e,t);let s=vn(e,n,i);const o=e.getMetersPerUnit();void 0!==o&&(s*=o);const a=t.getMetersPerUnit();void 0!==a&&(s/=a);const l=t.getExtent();if(!l||Xt(l,r)){const e=vn(t,s,r)/s;isFinite(e)&&e>0&&(s/=e)}return s}function Cu(t,e,i,n){const r=ce(i);let s=Tu(t,e,r,n);return(!isFinite(s)||s<=0)&&oe(i,(function(i){return s=Tu(t,e,i,n),isFinite(s)&&s>0})),s}function Ru(t,e,i,n,r,s,o,a,l,h,c,u,d,g){const f=ft(Math.round(i*t),Math.round(i*e),Su);if(u||(f.imageSmoothingEnabled=!1),0===l.length)return f.canvas;function p(t){return Math.round(t*i)/i}f.scale(i,i),f.globalCompositeOperation="lighter";const m=[1/0,1/0,-1/0,-1/0];let _;l.forEach((function(t,e,i){te(m,t.extent)}));const y=i/n,x=(u?1:1+Math.pow(2,-24))/y;if(!d||1!==l.length||0!==h){if(_=ft(Math.round(ye(m)*y),Math.round(fe(m)*y),Su),u||(_.imageSmoothingEnabled=!1),r&&g){const t=(r[0]-m[0])*y,e=-(r[3]-m[3])*y,i=ye(r)*y,n=fe(r)*y;_.rect(t,e,i,n),_.clip()}l.forEach((function(t,e,i){if(t.image.width>0&&t.image.height>0){if(t.clipExtent){_.save();const e=(t.clipExtent[0]-m[0])*y,i=-(t.clipExtent[3]-m[3])*y,n=ye(t.clipExtent)*y,r=fe(t.clipExtent)*y;_.rect(u?e:Math.round(e),u?i:Math.round(i),u?n:Math.round(e+n)-Math.round(e),u?r:Math.round(i+r)-Math.round(i)),_.clip()}const e=(t.extent[0]-m[0])*y,i=-(t.extent[3]-m[3])*y,n=ye(t.extent)*y,r=fe(t.extent)*y;_.drawImage(t.image,h,h,t.image.width-2*h,t.image.height-2*h,u?e:Math.round(e),u?i:Math.round(i),u?n:Math.round(e+n)-Math.round(e),u?r:Math.round(i+r)-Math.round(i)),t.clipExtent&&_.restore()}}))}const v=me(o);return a.getTriangles().forEach((function(t,e,i){const n=t.source,r=t.target;let o=n[0][0],a=n[0][1],h=n[1][0],c=n[1][1],d=n[2][0],g=n[2][1];const y=p((r[0][0]-v[0])/s),S=p(-(r[0][1]-v[1])/s),E=p((r[1][0]-v[0])/s),w=p(-(r[1][1]-v[1])/s),T=p((r[2][0]-v[0])/s),C=p(-(r[2][1]-v[1])/s),R=o,b=a;o=0,a=0,h-=R,c-=b,d-=R,g-=b;const P=Fe([[h,c,0,0,E-y],[d,g,0,0,T-y],[0,0,h,c,w-S],[0,0,d,g,C-S]]);if(!P)return;if(f.save(),f.beginPath(),function(){if(void 0===vu){const t=ft(6,6,Su);t.globalCompositeOperation="lighter",t.fillStyle="rgba(210, 0, 0, 0.75)",Eu(t,4,5,4,0),Eu(t,4,5,0,5);const e=t.getImageData(0,0,3,3).data;vu=wu(e,0)||wu(e,4)||wu(e,8),_t(t),Su.push(t.canvas)}return vu}()||!u){f.moveTo(E,w);const t=4,e=y-E,i=S-w;for(let n=0;n0&&this.getCount()>this.highWaterMark}expireCache(t){for(;this.canExpireCache();)this.deleteOldest()}clear(){for(;this.oldest_;)this.deleteOldest()}containsKey(t){return this.entries_.hasOwnProperty(t)}forEach(t){let e=this.oldest_;for(;e;)t(e.value_,e.key_,this),e=e.newer}get(t,e){const i=this.entries_[t];return Ft(void 0!==i,"Tried to get a value for a key that does not exist in the cache"),i===this.newest_||(i===this.oldest_?(this.oldest_=this.oldest_.newer,this.oldest_.older=null):(i.newer.older=i.older,i.older.newer=i.newer),i.newer=null,i.older=this.newest_,this.newest_.newer=i,this.newest_=i),i.value_}remove(t){const e=this.entries_[t];return Ft(void 0!==e,"Tried to get a value for a key that does not exist in the cache"),e===this.newest_?(this.newest_=e.older,this.newest_&&(this.newest_.newer=null)):e===this.oldest_?(this.oldest_=e.newer,this.oldest_&&(this.oldest_.older=null)):(e.newer.older=e.older,e.older.newer=e.newer),delete this.entries_[t],--this.count_,e.value_}getCount(){return this.count_}getKeys(){const t=new Array(this.count_);let e,i=0;for(e=this.newest_;e;e=e.older)t[i++]=e.key_;return t}getValues(){const t=new Array(this.count_);let e,i=0;for(e=this.newest_;e;e=e.older)t[i++]=e.value_;return t}peekLast(){return this.oldest_.value_}peekLastKey(){return this.oldest_.key_}peekFirstKey(){return this.newest_.key_}peek(t){return this.entries_[t]?.value_}pop(){const t=this.oldest_;return delete this.entries_[t.key_],t.newer&&(t.newer.older=null),this.oldest_=t.newer,this.oldest_||(this.newest_=null),--this.count_,t.value_}replace(t,e){this.get(t),this.entries_[t].value_=e}set(t,e){Ft(!(t in this.entries_),"Tried to set a value for a key that is used already");const i={key_:t,newer:null,older:this.newest_,value_:e};this.newest_?this.newest_.newer=i:this.oldest_=i,this.newest_=i,this.entries_[t]=i,++this.count_}setSize(t){this.highWaterMark=t}}function Iu(t,e,i,n){return void 0!==n?(n[0]=t,n[1]=e,n[2]=i,n):[t,e,i]}function Fu(t,e,i){return t+"/"+e+"/"+i}function Lu(t){return Fu(t[0],t[1],t[2])}function Mu(t){return Au(t[0],t[1],t[2])}function Au(t,e,i){return(e<i||i>e.getMaxZoom())return!1;const s=e.getFullTileRange(i);return!s||s.containsXY(n,r)}const Du=[0,0,0];class Nu{constructor(t){let e;if(this.minZoom=void 0!==t.minZoom?t.minZoom:0,this.resolutions_=t.resolutions,Ft(T(this.resolutions_,((t,e)=>e-t),!0),"`resolutions` must be sorted in descending order"),!t.origins)for(let t=0,i=this.resolutions_.length-1;t{const n=new Kc(Math.min(0,t[0]),Math.max(t[0]-1,-1),Math.min(0,t[1]),Math.max(t[1]-1,-1));if(i){const t=this.getTileRangeForExtentAndZ(i,e);n.minX=Math.max(t.minX,n.minX),n.maxX=Math.min(t.maxX,n.maxX),n.minY=Math.max(t.minY,n.minY),n.maxY=Math.min(t.maxY,n.maxY)}return n})):i&&this.calculateTileRanges_(i)}forEachTileCoord(t,e,i){const n=this.getTileRangeForExtentAndZ(t,e);for(let t=n.minX,r=n.maxX;t<=r;++t)for(let r=n.minY,s=n.maxY;r<=s;++r)i([e,t,r])}forEachTileCoordParentTileRange(t,e,i,n){let r,s,o,a=null,l=t[0]-1;for(2===this.zoomFactor_?(s=t[1],o=t[2]):a=this.getTileCoordExtent(t,n);l>=this.minZoom;){if(void 0!==s&&void 0!==o?(s=Math.floor(s/2),o=Math.floor(o/2),r=qc(s,s,o,o,i)):r=this.getTileRangeForExtentAndZ(a,l,i),e(l,r))return!0;--l}return!1}getExtent(){return this.extent_}getMaxZoom(){return this.maxZoom}getMinZoom(){return this.minZoom}getOrigin(t){return this.origin_?this.origin_:this.origins_[t]}getResolution(t){return this.resolutions_[t]}getResolutions(){return this.resolutions_}getTileCoordChildTileRange(t,e,i){if(t[0]this.maxZoom||e0)||i.find((function(i){return e[h]==i[l]||!e[h].includes(":")&&t[h]+":"+e[h]===i[l]})),d){r.push(e[h]);const t=28e-5*e[c]/g,i=e.TileWidth,l=e.TileHeight;f?s.push([e[u][1],e[u][0]]):s.push(e[u]),n.push(t),o.push(i==l?i:[i,l]),a.push([e.MatrixWidth,e.MatrixHeight])}})),new ku({extent:e,origins:s,resolutions:n,matrixIds:r,tileSizes:o,sizes:a})}function ju(t){let e=t.getDefaultTileGrid();return e||(e=Vu(t),t.setDefaultTileGrid(e)),e}function Uu(t,e,i){const n=e[0],r=t.getTileCoordCenter(e),s=$u(i);if(!Xt(s,r)){const e=ye(s),i=Math.ceil((s[0]-r[0])/e);return r[0]+=e*i,t.getTileCoordForCoordAndZ(r,n)}return e}function Bu(t,e,i,n){n=void 0!==n?n:"top-left";const r=Xu(t,e,i);return new Nu({extent:t,origin:ue(t,n),resolutions:r,tileSize:i})}function zu(t){const e=t||{},i=e.extent||xn("EPSG:3857").getExtent(),n={extent:i,minZoom:e.minZoom,tileSize:e.tileSize,resolutions:Xu(i,e.maxZoom,e.tileSize,e.maxResolution)};return new Nu(n)}function Xu(t,e,i,n){e=void 0!==e?e:42,i=Bl(void 0!==i?i:Eo);const r=fe(t),s=ye(t);n=n>0?n:Math.max(s/i[0],r/i[1]);const o=e+1,a=new Array(o);for(let t=0;tKn(o,Ln(t,this.targetProj_,this.sourceProj_)))):Fn(this.targetProj_,this.sourceProj_);this.transformInv_=function(t){const e=t[0]+"/"+t[1];return a[e]||(a[e]=l(t)),a[e]},this.maxSourceExtent_=n,this.errorThresholdSquared_=r*r,this.triangles_=[],this.wrapsXInSource_=!1,this.canWrapXInSource_=this.sourceProj_.canWrapX()&&!!n&&!!this.sourceProj_.getExtent()&&ye(n)>=ye(this.sourceProj_.getExtent()),this.sourceWorldWidth_=this.sourceProj_.getExtent()?ye(this.sourceProj_.getExtent()):null,this.targetWorldWidth_=this.targetProj_.getExtent()?ye(this.targetProj_.getExtent()):null;const h=me(i),c=_e(i),u=he(i),d=le(i),g=this.transformInv_(h),f=this.transformInv_(c),p=this.transformInv_(u),m=this.transformInv_(d),_=10+(s?Math.max(0,Math.ceil(Math.log2(ae(i)/(s*s*256*256)))):0);if(this.addQuad_(h,c,u,d,g,f,p,m,_),this.wrapsXInSource_){let t=1/0;this.triangles_.forEach((function(e,i,n){t=Math.min(t,e.source[0][0],e.source[1][0],e.source[2][0])})),this.triangles_.forEach((e=>{if(Math.max(e.source[0][0],e.source[1][0],e.source[2][0])-t>this.sourceWorldWidth_/2){const i=[[e.source[0][0],e.source[0][1]],[e.source[1][0],e.source[1][1]],[e.source[2][0],e.source[2][1]]];i[0][0]-t>this.sourceWorldWidth_/2&&(i[0][0]-=this.sourceWorldWidth_),i[1][0]-t>this.sourceWorldWidth_/2&&(i[1][0]-=this.sourceWorldWidth_),i[2][0]-t>this.sourceWorldWidth_/2&&(i[2][0]-=this.sourceWorldWidth_);const n=Math.min(i[0][0],i[1][0],i[2][0]);Math.max(i[0][0],i[1][0],i[2][0])-n.5&&c<1;let g=!1;if(l>0){if(this.targetProj_.isGlobal()&&this.targetWorldWidth_){g=ye(jt([t,e,i,n]))/this.targetWorldWidth_>.25||g}!d&&this.sourceProj_.isGlobal()&&c&&(g=c>.25||g)}if(!g&&this.maxSourceExtent_&&isFinite(h[0])&&isFinite(h[1])&&isFinite(h[2])&&isFinite(h[3])&&!xe(h,this.maxSourceExtent_))return;let f=0;if(!(g||isFinite(r[0])&&isFinite(r[1])&&isFinite(s[0])&&isFinite(s[1])&&isFinite(o[0])&&isFinite(o[1])&&isFinite(a[0])&&isFinite(a[1])))if(l>0)g=!0;else if(f=(isFinite(r[0])&&isFinite(r[1])?0:8)+(isFinite(s[0])&&isFinite(s[1])?0:4)+(isFinite(o[0])&&isFinite(o[1])?0:2)+(isFinite(a[0])&&isFinite(a[1])?0:1),1!=f&&2!=f&&4!=f&&8!=f)return;if(l>0){if(!g){const e=[(t[0]+i[0])/2,(t[1]+i[1])/2],n=this.transformInv_(e);let s;if(d){s=(Ae(r[0],u)+Ae(o[0],u))/2-Ae(n[0],u)}else s=(r[0]+o[0])/2-n[0];const a=(r[1]+o[1])/2-n[1];g=s*s+a*a>this.errorThresholdSquared_}if(g){if(Math.abs(t[0]-i[0])<=Math.abs(t[1]-i[1])){const h=[(e[0]+i[0])/2,(e[1]+i[1])/2],c=this.transformInv_(h),u=[(n[0]+t[0])/2,(n[1]+t[1])/2],d=this.transformInv_(u);this.addQuad_(t,e,h,u,r,s,c,d,l-1),this.addQuad_(u,h,i,n,d,c,o,a,l-1)}else{const h=[(t[0]+e[0])/2,(t[1]+e[1])/2],c=this.transformInv_(h),u=[(i[0]+n[0])/2,(i[1]+n[1])/2],d=this.transformInv_(u);this.addQuad_(t,h,u,n,r,c,d,a,l-1),this.addQuad_(h,e,i,u,c,s,o,d,l-1)}return}}if(d){if(!this.canWrapXInSource_)return;this.wrapsXInSource_=!0}11&f||this.addTriangle_(t,i,n,r,o,a),14&f||this.addTriangle_(t,i,e,r,o,s),f&&(13&f||this.addTriangle_(e,n,t,s,a,r),7&f||this.addTriangle_(e,n,i,s,a,o))}calculateSourceExtent(){const t=[1/0,1/0,-1/0,-1/0];return this.triangles_.forEach((function(e,i,n){const r=e.source;ee(t,r[0]),ee(t,r[1]),ee(t,r[2])})),t}getTriangles(){return this.triangles_}}const sd=.5;class od extends nt{constructor(t,e,i,n,r,s,o,a,l,h,c,u){super(r,Y,u),this.renderEdges_=void 0!==c&&c,this.pixelRatio_=o,this.gutter_=a,this.canvas_=null,this.sourceTileGrid_=e,this.targetTileGrid_=n,this.wrappedTileCoord_=s||r,this.sourceTiles_=[],this.sourcesListenerKeys_=null,this.sourceZ_=0,this.clipExtent_=t.canWrapX()?t.getExtent():void 0;const d=n.getTileCoordExtent(this.wrappedTileCoord_),g=this.targetTileGrid_.getExtent();let f=this.sourceTileGrid_.getExtent();const p=g?pe(d,g):d;if(0===ae(p))return void(this.state=J);const m=t.getExtent();m&&(f=f?pe(f,m):m);const _=n.getResolution(this.wrappedTileCoord_[0]),y=Cu(t,i,p,_);if(!isFinite(y)||y<=0)return void(this.state=J);const x=void 0!==h?h:sd;if(this.triangulation_=new rd(t,i,p,f,y*x,_),0===this.triangulation_.getTriangles().length)return void(this.state=J);this.sourceZ_=e.getZForResolution(y);let v=this.triangulation_.calculateSourceExtent();if(f&&(t.canWrapX()?(v[1]=be(v[1],f[1],f[3]),v[3]=be(v[3],f[1],f[3])):v=pe(v,f)),ae(v)){let i=0,n=0;t.canWrapX()&&(i=ye(m),n=Math.floor((v[0]-m[0])/i));Re(v.slice(),t,!0).forEach((t=>{const r=e.getTileRangeForExtentAndZ(t,this.sourceZ_);for(let t=r.minX;t<=r.maxX;t++)for(let e=r.minY;e<=r.maxY;e++){const r=l(this.sourceZ_,t,e,o);if(r){const t=n*i;this.sourceTiles_.push({tile:r,offset:t})}}++n})),0===this.sourceTiles_.length&&(this.state=J)}else this.state=J}getImage(){return this.canvas_}reproject_(){const t=[];if(this.sourceTiles_.forEach((e=>{const i=e.tile;if(i&&i.getState()==K){const n=this.sourceTileGrid_.getTileCoordExtent(i.tileCoord);n[0]+=e.offset,n[2]+=e.offset;const r=this.clipExtent_?.slice();r&&(r[0]+=e.offset,r[2]+=e.offset),t.push({extent:n,clipExtent:r,image:i.getImage()})}})),this.sourceTiles_.length=0,0===t.length)this.state=q;else{const e=this.wrappedTileCoord_[0],i=this.targetTileGrid_.getTileSize(e),n="number"==typeof i?i:i[0],r="number"==typeof i?i:i[1],s=this.targetTileGrid_.getResolution(e),o=this.sourceTileGrid_.getResolution(this.sourceZ_),a=this.targetTileGrid_.getTileCoordExtent(this.wrappedTileCoord_);this.canvas_=Ru(n,r,this.pixelRatio_,o,this.sourceTileGrid_.getExtent(),s,a,this.triangulation_,t,this.gutter_,this.renderEdges_,this.interpolate),this.state=K}this.changed()}load(){if(this.state==Y){this.state=H,this.changed();let t=0;this.sourcesListenerKeys_=[],this.sourceTiles_.forEach((({tile:e})=>{const i=e.getState();if(i==Y||i==H){t++;const i=D(e,n,(n=>{const r=e.getState();r!=K&&r!=q&&r!=J||(k(i),t--,0===t&&(this.unlistenSources_(),this.reproject_()))}));this.sourcesListenerKeys_.push(i)}})),0===t?setTimeout(this.reproject_.bind(this),0):this.sourceTiles_.forEach((function({tile:t},e,i){t.getState()==Y&&t.load()}))}}unlistenSources_(){this.sourcesListenerKeys_.forEach(k),this.sourcesListenerKeys_=null}release(){this.canvas_&&(_t(this.canvas_.getContext("2d")),Su.push(this.canvas_),this.canvas_=null),super.release()}}class ad extends V{constructor(t){super(),this.projection=xn(t.projection),this.attributions_=ld(t.attributions),this.attributionsCollapsible_=t.attributionsCollapsible??!0,this.loading=!1,this.state_=void 0!==t.state?t.state:"ready",this.wrapX_=void 0!==t.wrapX&&t.wrapX,this.interpolate_=!!t.interpolate,this.viewResolver=null,this.viewRejector=null;const e=this;this.viewPromise_=new Promise((function(t,i){e.viewResolver=t,e.viewRejector=i}))}getAttributions(){return this.attributions_}getAttributionsCollapsible(){return this.attributionsCollapsible_}getProjection(){return this.projection}getResolutions(t){return null}getView(){return this.viewPromise_}getState(){return this.state_}getWrapX(){return this.wrapX_}getInterpolate(){return this.interpolate_}refresh(){this.changed()}setAttributions(t){this.attributions_=ld(t),this.changed()}setState(t){this.state_=t,this.changed()}}function ld(t){return t?"function"==typeof t?t:(Array.isArray(t)||(t=[t]),e=>t):null}class hd extends ad{constructor(t){super({attributions:t.attributions,attributionsCollapsible:t.attributionsCollapsible,projection:t.projection,state:t.state,wrapX:t.wrapX,interpolate:t.interpolate}),this.on,this.once,this.un,this.tilePixelRatio_=void 0!==t.tilePixelRatio?t.tilePixelRatio:1,this.tileGrid=void 0!==t.tileGrid?t.tileGrid:null;const e=[256,256];this.tileGrid&&Bl(this.tileGrid.getTileSize(this.tileGrid.getMinZoom()),e),this.tmpSize=[0,0],this.key_=t.key||z(this),this.tileOptions={transition:t.transition,interpolate:t.interpolate},this.zDirection=t.zDirection?t.zDirection:0}getGutterForProjection(t){return 0}getKey(){return this.key_}setKey(t){this.key_!==t&&(this.key_=t,this.changed())}getResolutions(t){const e=t?this.getTileGridForProjection(t):this.tileGrid;return e?e.getResolutions():null}getTile(t,e,i,n,r){return U()}getTileGrid(){return this.tileGrid}getTileGridForProjection(t){return this.tileGrid?this.tileGrid:ju(t)}getTilePixelRatio(t){return this.tilePixelRatio_}getTilePixelSize(t,e,i){const n=this.getTileGridForProjection(i),r=this.getTilePixelRatio(e),s=Bl(n.getTileSize(t),this.tmpSize);return 1==r?s:Ul(s,r,this.tmpSize)}getTileCoordForTileUrlFunction(t,e){const i=void 0!==e?e:this.getProjection(),n=void 0!==e?this.getTileGridForProjection(i):this.tileGrid||this.getTileGridForProjection(i);return this.getWrapX()&&i.isGlobal()&&(t=Uu(n,t,i)),Ou(t,n)?t:null}clear(){}refresh(){this.clear(),super.refresh()}}class cd extends M{constructor(t,e){super(t),this.tile=e}}var ud="tileloadstart",dd="tileloadend",gd="tileloaderror";class fd extends hd{constructor(t){super({attributions:t.attributions,cacheSize:t.cacheSize,projection:t.projection,state:t.state,tileGrid:t.tileGrid,tilePixelRatio:t.tilePixelRatio,wrapX:t.wrapX,transition:t.transition,interpolate:t.interpolate,key:t.key,attributionsCollapsible:t.attributionsCollapsible,zDirection:t.zDirection}),this.generateTileUrlFunction_=this.tileUrlFunction===fd.prototype.tileUrlFunction,this.tileLoadFunction=t.tileLoadFunction,t.tileUrlFunction&&(this.tileUrlFunction=t.tileUrlFunction),this.urls=null,t.urls?this.setUrls(t.urls):t.url&&this.setUrl(t.url),this.tileLoadingKeys_={}}getTileLoadFunction(){return this.tileLoadFunction}getTileUrlFunction(){return Object.getPrototypeOf(this).tileUrlFunction===this.tileUrlFunction?this.tileUrlFunction.bind(this):this.tileUrlFunction}getUrls(){return this.urls}handleTileChange(t){const e=t.target,i=z(e),n=e.getState();let r;n==H?(this.tileLoadingKeys_[i]=!0,r=ud):i in this.tileLoadingKeys_&&(delete this.tileLoadingKeys_[i],r=n==q?gd:n==K?dd:void 0),null!=r&&this.dispatchEvent(new cd(r,e))}setTileLoadFunction(t){this.tileLoadFunction=t,this.changed()}setTileUrlFunction(t,e){this.tileUrlFunction=t,void 0!==e?this.setKey(e):this.changed()}setUrl(t){const e=Qu(t);this.urls=e,this.setUrls(e)}setUrls(t){this.urls=t;const e=t.join("\n");this.generateTileUrlFunction_?this.setTileUrlFunction(ed(t,this.tileGrid),e):this.setKey(e)}tileUrlFunction(t,e,i){}}class pd extends fd{constructor(t){super({attributions:t.attributions,cacheSize:t.cacheSize,projection:t.projection,state:t.state,tileGrid:t.tileGrid,tileLoadFunction:t.tileLoadFunction?t.tileLoadFunction:md,tilePixelRatio:t.tilePixelRatio,tileUrlFunction:t.tileUrlFunction,url:t.url,urls:t.urls,wrapX:t.wrapX,transition:t.transition,interpolate:void 0===t.interpolate||t.interpolate,key:t.key,attributionsCollapsible:t.attributionsCollapsible,zDirection:t.zDirection}),this.crossOrigin=void 0!==t.crossOrigin?t.crossOrigin:null,this.tileClass=void 0!==t.tileClass?t.tileClass:Gs,this.tileGridForProjection={},this.reprojectionErrorThreshold_=t.reprojectionErrorThreshold,this.renderReprojectionEdges_=!1}getGutterForProjection(t){return this.getProjection()&&t&&!bn(this.getProjection(),t)?0:this.getGutter()}getGutter(){return 0}getKey(){let t=super.getKey();return this.getInterpolate()||(t+=":disable-interpolation"),t}getTileGridForProjection(t){const e=this.getProjection();if(this.tileGrid&&(!e||bn(e,t)))return this.tileGrid;const i=z(t);return i in this.tileGridForProjection||(this.tileGridForProjection[i]=ju(t)),this.tileGridForProjection[i]}createTile_(t,e,i,r,s,o){const a=[t,e,i],l=this.getTileCoordForTileUrlFunction(a,s),h=l?this.tileUrlFunction(l,r,s):void 0,c=new this.tileClass(a,void 0!==h?Y:J,void 0!==h?h:"",this.crossOrigin,this.tileLoadFunction,this.tileOptions);return c.key=o,c.addEventListener(n,this.handleTileChange.bind(this)),c}getTile(t,e,i,n,r){const s=this.getProjection();if(!s||!r||bn(s,r))return this.getTileInternal(t,e,i,n,s||r);const o=[t,e,i],a=this.getKey(),l=this.getTileGridForProjection(s),h=this.getTileGridForProjection(r),c=this.getTileCoordForTileUrlFunction(o,r),u=new od(s,l,r,h,o,c,this.getTilePixelRatio(n),this.getGutter(),((t,e,i,n)=>this.getTileInternal(t,e,i,n,s)),this.reprojectionErrorThreshold_,this.renderReprojectionEdges_,this.tileOptions);return u.key=a,u}getTileInternal(t,e,i,n,r){const s=this.getKey();return this.createTile_(t,e,i,n,r,s)}setRenderReprojectionEdges(t){this.renderReprojectionEdges_!=t&&(this.renderReprojectionEdges_=t,this.changed())}setTileGridForProjection(t,e){const i=xn(t);if(i){const t=z(i);t in this.tileGridForProjection||(this.tileGridForProjection[t]=e)}}}function md(t,e){t.getImage().src=e}function _d(t){const e=t[0],i=new Array(e);let n,r,s=1<>=1;return i.join("")}class yd extends pd{constructor(t){const e=void 0!==(t=t||{}).projection?t.projection:"EPSG:3857",i=void 0!==t.tileGrid?t.tileGrid:zu({extent:$u(e),maxResolution:t.maxResolution,maxZoom:t.maxZoom,minZoom:t.minZoom,tileSize:t.tileSize});super({attributions:t.attributions,cacheSize:t.cacheSize,crossOrigin:t.crossOrigin,interpolate:t.interpolate,projection:e,reprojectionErrorThreshold:t.reprojectionErrorThreshold,tileGrid:i,tileLoadFunction:t.tileLoadFunction,tilePixelRatio:t.tilePixelRatio,tileUrlFunction:t.tileUrlFunction,url:t.url,urls:t.urls,wrapX:void 0===t.wrapX||t.wrapX,transition:t.transition,attributionsCollapsible:t.attributionsCollapsible,zDirection:t.zDirection}),this.gutter_=void 0!==t.gutter?t.gutter:0}getGutter(){return this.gutter_}}function xd(t,e,i,n){const r=[];let s=[1/0,1/0,-1/0,-1/0];for(let o=0,a=i.length;o1?o:2,s=s||new Array(o);for(let e=0;e>1;r=e[0]||(t[1]<=e[1]&&t[3]>=e[1]||oe(t,this.intersectsCoordinate.bind(this)))}return!1}setCenter(t){const e=this.stride,i=this.flatCoordinates[e]-this.flatCoordinates[0],n=t.slice();n[e]=n[0]+i;for(let i=1;it.clone()))}function Rd(t,e,i,n){let r=t[e],s=t[e+1],o=0;for(let a=e+n;a{if(t===this.squaredTolerance_)return this.simplifiedGeometry_;this.simplifiedGeometry_=this.clone(),e&&this.simplifiedGeometry_.applyTransform(e);const i=this.simplifiedGeometry_.getFlatCoordinates();let n;switch(this.type_){case"LineString":i.length=Dr(i,0,this.simplifiedGeometry_.flatCoordinates_.length,this.simplifiedGeometry_.stride_,t,i,0),n=[i.length];break;case"MultiLineString":n=[],i.length=Nr(i,0,this.simplifiedGeometry_.ends_,this.simplifiedGeometry_.stride_,t,i,0,n);break;case"Polygon":n=[],i.length=Ur(i,0,this.simplifiedGeometry_.ends_,this.simplifiedGeometry_.stride_,Math.sqrt(t),i,0,n)}return n&&(this.simplifiedGeometry_=new Md(this.type_,i,n,2,this.properties_,this.id_)),this.squaredTolerance_=t,this.simplifiedGeometry_})),this}}function Ad(t){const e=t.getType();switch(e){case"Point":return new Xr(t.getFlatCoordinates());case"MultiPoint":return new Id(t.getFlatCoordinates(),"XY");case"LineString":return new bd(t.getFlatCoordinates(),"XY");case"MultiLineString":return new Pd(t.getFlatCoordinates(),"XY",t.getEnds());case"Polygon":const i=t.getFlatCoordinates(),n=t.getEnds(),r=hs(i,n);return r.length>1?new Fd(i,"XY",r):new cs(i,"XY",n);default:throw new Error("Invalid geometry type:"+e)}}Md.prototype.getFlatCoordinates=Md.prototype.getOrientedFlatCoordinates;class Od{constructor(t){this.rbush_=new nl(t),this.items_={}}insert(t,e){const i={minX:t[0],minY:t[1],maxX:t[2],maxY:t[3],value:e};this.rbush_.insert(i),this.items_[z(e)]=i}load(t,e){const i=new Array(e.length);for(let n=0,r=e.length;n{n||(n=!0,this.addFeature(t.element),n=!1)})),i.addEventListener(e,(t=>{n||(n=!0,this.removeFeature(t.element),n=!1)})),this.featuresCollection_=i}clear(t){if(t){for(const t in this.featureChangeKeys_){this.featureChangeKeys_[t].forEach(k)}this.featuresCollection_||(this.featureChangeKeys_={},this.idIndex_={},this.uidIndex_={})}else if(this.featuresRtree_){this.featuresRtree_.forEach((t=>{this.removeFeatureInternal(t)}));for(const t in this.nullGeometryFeatures_)this.removeFeatureInternal(this.nullGeometryFeatures_[t])}this.featuresCollection_&&this.featuresCollection_.clear(),this.featuresRtree_&&this.featuresRtree_.clear(),this.nullGeometryFeatures_={};const e=new zd(kd);this.dispatchEvent(e),this.changed()}forEachFeature(t){if(this.featuresRtree_)return this.featuresRtree_.forEach(t);this.featuresCollection_&&this.featuresCollection_.forEach(t)}forEachFeatureAtCoordinateDirect(t,e){const i=[t[0],t[1],t[0],t[1]];return this.forEachFeatureInExtent(i,(function(i){const n=i.getGeometry();if(n instanceof Md||n.intersectsCoordinate(t))return e(i)}))}forEachFeatureInExtent(t,e){if(this.featuresRtree_)return this.featuresRtree_.forEachInExtent(t,e);this.featuresCollection_&&this.featuresCollection_.forEach(e)}forEachFeatureIntersectingExtent(t,e){return this.forEachFeatureInExtent(t,(function(i){const n=i.getGeometry();if(n instanceof Md||n.intersectsExtent(t)){const t=e(i);if(t)return t}}))}getFeaturesCollection(){return this.featuresCollection_}getFeatures(){let t;return this.featuresCollection_?t=this.featuresCollection_.getArray().slice(0):this.featuresRtree_&&(t=this.featuresRtree_.getAll(),L(this.nullGeometryFeatures_)||E(t,Object.values(this.nullGeometryFeatures_))),t}getFeaturesAtCoordinate(t){const e=[];return this.forEachFeatureAtCoordinateDirect(t,(function(t){e.push(t)})),e}getFeaturesInExtent(t,e){if(this.featuresRtree_){if(!(e&&e.canWrapX()&&this.getWrapX()))return this.featuresRtree_.getInExtent(t);const i=Re(t,e);return[].concat(...i.map((t=>this.featuresRtree_.getInExtent(t))))}return this.featuresCollection_?this.featuresCollection_.getArray().slice(0):[]}getClosestFeatureToCoordinate(t,e){const i=t[0],n=t[1];let r=null;const s=[NaN,NaN];let o=1/0;const a=[-1/0,-1/0,1/0,1/0];return e=e||C,this.featuresRtree_.forEachInExtent(a,(function(t){if(e(t)){const e=t.getGeometry(),l=o;if(o=e instanceof Md?0:e.closestPointXY(i,n,s,o),o{--this.loadingExtentsCount_,this.dispatchEvent(new zd(Ud,void 0,t))}),(()=>{--this.loadingExtentsCount_,this.dispatchEvent(new zd(Bd))})),n.insert(s,{extent:s.slice()}))}this.loading=!(this.loader_.length<4)&&this.loadingExtentsCount_>0}refresh(){this.clear(!0),this.loadedExtentsRtree_.clear(),super.refresh()}removeLoadedExtent(t){const e=this.loadedExtentsRtree_,i=e.forEachInExtent(t,(function(e){if(Jt(e.extent,t))return e}));i&&e.remove(i)}removeFeatures(t){let e=!1;for(let i=0,n=t.length;i 1.0 ||\n v_texcoord.y > 1.0\n ) {\n discard;\n }\n gl_FragColor = texture2D(u_texture, v_texcoord);\n }\n","\n attribute vec4 a_position;\n attribute vec4 a_texcoord;\n\n uniform mat4 u_matrix;\n uniform mat4 u_textureMatrix;\n\n varying vec2 v_texcoord;\n\n void main() {\n gl_Position = u_matrix * a_position;\n vec2 texcoord = (u_textureMatrix * a_texcoord).xy;\n v_texcoord = texcoord;\n }\n"),this.positionLocation=t.getAttribLocation(this.program_,"a_position"),this.texcoordLocation=t.getAttribLocation(this.program_,"a_texcoord"),this.matrixLocation=t.getUniformLocation(this.program_,"u_matrix"),this.textureMatrixLocation=t.getUniformLocation(this.program_,"u_textureMatrix"),this.textureLocation=t.getUniformLocation(this.program_,"u_texture"),this.positionBuffer=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,this.positionBuffer),this.positions=[0,0,0,1,1,0,1,0,0,1,1,1],t.bufferData(t.ARRAY_BUFFER,new Float32Array(this.positions),t.STATIC_DRAW),this.texcoordBuffer=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,this.texcoordBuffer),this.texcoords=[0,0,0,1,1,0,1,0,0,1,1,1],t.bufferData(t.ARRAY_BUFFER,new Float32Array(this.texcoords),t.STATIC_DRAW)}drawImage(t,e,i,n,r,s,o,a,l,h,c,u,d){const g=this.gl_;void 0===a&&(a=n),void 0===l&&(l=r),void 0===s&&(s=e),void 0===o&&(o=i),void 0===h&&(h=s),void 0===c&&(c=o),void 0===u&&(u=g.canvas.width),void 0===d&&(d=g.canvas.height),g.bindTexture(g.TEXTURE_2D,t),g.useProgram(this.program_),g.bindBuffer(g.ARRAY_BUFFER,this.positionBuffer),g.enableVertexAttribArray(this.positionLocation),g.vertexAttribPointer(this.positionLocation,2,g.FLOAT,!1,0,0),g.bindBuffer(g.ARRAY_BUFFER,this.texcoordBuffer),g.enableVertexAttribArray(this.texcoordLocation),g.vertexAttribPointer(this.texcoordLocation,2,g.FLOAT,!1,0,0);let f=Wd(0,u,0,d,-1,1);f=Yd(f,a,l,0),f=Zd(f,h,c,1),g.uniformMatrix4fv(this.matrixLocation,!1,f);let p=Hd(n/e,r/i,0);p=Zd(p,s/e,o/i,1),g.uniformMatrix4fv(this.textureMatrixLocation,!1,p),g.uniform1i(this.textureLocation,0),g.drawArrays(g.TRIANGLES,0,this.positions.length/2)}}function qd(t,e,i){const n=t.createShader(e);if(null===n)throw new Error("Shader compilation failed");if(t.shaderSource(n,i),t.compileShader(n),!t.getShaderParameter(n,t.COMPILE_STATUS)){const e=t.getShaderInfoLog(n);if(null===e)throw new Error("Shader info log creation failed");throw new Error(e)}return n}function Jd(t,e,i){const n=t.createProgram(),r=qd(t,t.VERTEX_SHADER,i),s=qd(t,t.FRAGMENT_SHADER,e);if(null===n)throw new Error("Program creation failed");if(t.attachShader(n,r),t.attachShader(n,s),t.linkProgram(n),!t.getProgramParameter(n,t.LINK_STATUS)){if(null===t.getProgramInfoLog(n))throw new Error("Program info log creation failed");throw new Error}return n}function Qd(t,e,i,n){let r;return r=i&&i.length?i.shift():ct?new OffscreenCanvas(t||300,e||300):document.createElement("canvas"),t&&(r.width=t),e&&(r.height=e),r.getContext("webgl",n)}function tg(t){const e=t.canvas;e.width=1,e.height=1,t.clear(t.COLOR_BUFFER_BIT|t.DEPTH_BUFFER_BIT|t.STENCIL_BUFFER_BIT)}const eg=[];function ig(t,e,i,n,r,s,o,a,l,h,c,u,d,g){const f=Math.round(n*e),p=Math.round(n*i);let m,_;if(t.canvas.width=f,t.canvas.height=p,_=t.createTexture(),t.bindTexture(t.TEXTURE_2D,_),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),d?(t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.LINEAR)):(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,t.RGBA,f,p,0,t.RGBA,c,null),m=t.createFramebuffer(),t.bindFramebuffer(t.FRAMEBUFFER,m),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,_,0),null===m)throw new Error("Could not create framebuffer");if(null===_)throw new Error("Could not create texture");if(0===l.length)return{width:f,height:p,framebuffer:m,texture:_};const y=[1/0,1/0,-1/0,-1/0];let x,v,S;l.forEach((function(t,e,i){te(y,t.extent)}));const E=1/r;if(g&&1===l.length&&0===h)x=l[0].texture,v=l[0].width,S=l[0].width;else{if(x=t.createTexture(),null===_)throw new Error("Could not create texture");v=Math.round(ye(y)*E),S=Math.round(fe(y)*E);const e=t.getParameter(t.MAX_TEXTURE_SIZE),i=Math.max(v,S),n=i>e?e/i:1,r=Math.round(v*n),s=Math.round(S*n);t.bindTexture(t.TEXTURE_2D,x),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),d?(t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.LINEAR)):(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,t.RGBA,r,s,0,t.RGBA,c,null);const o=t.createFramebuffer();t.bindFramebuffer(t.FRAMEBUFFER,o),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,x,0);const a=new Kd(t);l.forEach((function(e,i,l){const c=(e.extent[0]-y[0])*E*n,u=-(e.extent[3]-y[3])*E*n,g=ye(e.extent)*E*n,f=fe(e.extent)*E*n;if(t.bindFramebuffer(t.FRAMEBUFFER,o),t.viewport(0,0,r,s),e.clipExtent){const i=(e.clipExtent[0]-y[0])*E*n,r=-(e.clipExtent[3]-y[3])*E*n,s=ye(e.clipExtent)*E*n,o=fe(e.clipExtent)*E*n;t.enable(t.SCISSOR_TEST),t.scissor(d?i:Math.round(i),d?r:Math.round(r),d?s:Math.round(i+s)-Math.round(i),d?o:Math.round(r+o)-Math.round(r))}a.drawImage(e.texture,e.width,e.height,h,h,e.width-2*h,e.height-2*h,d?c:Math.round(c),d?u:Math.round(u),d?g:Math.round(c+g)-Math.round(c),d?f:Math.round(u+f)-Math.round(u),r,s),t.disable(t.SCISSOR_TEST)})),t.deleteFramebuffer(o)}const w=me(o),T=me(y),C=t=>{const e=(t[0][0]-w[0])/s*n,i=-(t[0][1]-w[1])/s*n;return{u1:(t[1][0]-w[0])/s*n,v1:-(t[1][1]-w[1])/s*n,u0:e,v0:i,u2:(t[2][0]-w[0])/s*n,v2:-(t[2][1]-w[1])/s*n}};t.bindFramebuffer(t.FRAMEBUFFER,m),t.viewport(0,0,f,p);{const e=[],i=[],n=Jd(t,"\n precision mediump float;\n\n varying vec2 v_texcoord;\n\n uniform sampler2D u_texture;\n\n void main() {\n if (v_texcoord.x < 0.0 || v_texcoord.x > 1.0 || v_texcoord.y < 0.0 || v_texcoord.y > 1.0) {\n discard;\n }\n gl_FragColor = texture2D(u_texture, v_texcoord);\n }\n","\n attribute vec4 a_position;\n attribute vec2 a_texcoord;\n\n varying vec2 v_texcoord;\n\n uniform mat4 u_matrix;\n\n void main() {\n gl_Position = u_matrix * a_position;\n v_texcoord = a_texcoord;\n }\n");t.useProgram(n);const s=t.getUniformLocation(n,"u_texture");t.bindTexture(t.TEXTURE_2D,x),t.uniform1i(s,0),a.getTriangles().forEach((function(t,n,s){const o=t.source,a=t.target,{u1:l,v1:h,u0:c,v0:u,u2:d,v2:g}=C(a),f=(o[0][0]-T[0])/r/v,p=-(o[0][1]-T[1])/r/S,m=(o[1][0]-T[0])/r/v,_=-(o[1][1]-T[1])/r/S,y=(o[2][0]-T[0])/r/v,x=-(o[2][1]-T[1])/r/S;e.push(l,h,c,u,d,g),i.push(m,_,f,p,y,x)}));const o=Wd(0,f,p,0,-1,1),l=t.getUniformLocation(n,"u_matrix");t.uniformMatrix4fv(l,!1,o);const h=t.getAttribLocation(n,"a_position"),c=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,c),t.bufferData(t.ARRAY_BUFFER,new Float32Array(e),t.STATIC_DRAW),t.vertexAttribPointer(h,2,t.FLOAT,!1,0,0),t.enableVertexAttribArray(h);const u=t.getAttribLocation(n,"a_texcoord"),d=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,d),t.bufferData(t.ARRAY_BUFFER,new Float32Array(i),t.STATIC_DRAW),t.vertexAttribPointer(u,2,t.FLOAT,!1,0,0),t.enableVertexAttribArray(u),t.drawArrays(t.TRIANGLES,0,e.length/2)}if(u){const e=Jd(t,"\n precision mediump float;\n\n uniform vec4 u_val;\n void main() {\n gl_FragColor = u_val;\n }\n","\n attribute vec4 a_position;\n\n uniform mat4 u_matrix;\n\n void main() {\n gl_Position = u_matrix * a_position;\n }\n");t.useProgram(e);const i=Wd(0,f,p,0,-1,1),n=t.getUniformLocation(e,"u_matrix");t.uniformMatrix4fv(n,!1,i);const r=Array.isArray(u)?u:[0,0,0,255],s=t.getUniformLocation(e,"u_val");t.uniform4fv(s,r);const o=t.getAttribLocation(e,"a_position"),l=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,l),t.vertexAttribPointer(o,2,t.FLOAT,!1,0,0),t.enableVertexAttribArray(o);const h=a.getTriangles().reduce((function(t,e){const i=e.target,{u1:n,v1:r,u0:s,v0:o,u2:a,v2:l}=C(i);return t.concat([n,r,s,o,s,o,a,l,a,l,n,r])}),[]);t.bufferData(t.ARRAY_BUFFER,new Float32Array(h),t.STATIC_DRAW),t.drawArrays(t.LINES,0,h.length/2)}return{width:f,height:p,framebuffer:m,texture:_}}class ng extends It{constructor(t){super({tileCoord:t.tileCoord,loader:()=>Promise.resolve(new Uint8ClampedArray(4)),interpolate:t.interpolate,transition:t.transition}),this.renderEdges_=void 0!==t.renderEdges&&t.renderEdges,this.pixelRatio_=t.pixelRatio,this.gutter_=t.gutter,this.reprojData_=null,this.reprojError_=null,this.reprojSize_=void 0,this.sourceTileGrid_=t.sourceTileGrid,this.targetTileGrid_=t.targetTileGrid,this.wrappedTileCoord_=t.wrappedTileCoord||t.tileCoord,this.sourceTiles_=[],this.sourcesListenerKeys_=null,this.sourceZ_=0;const e=t.sourceProj,i=e.getExtent(),n=t.sourceTileGrid.getExtent();this.clipExtent_=e.canWrapX()?n?pe(i,n):i:n;const r=this.targetTileGrid_.getTileCoordExtent(this.wrappedTileCoord_),s=this.targetTileGrid_.getExtent();let o=this.sourceTileGrid_.getExtent();const a=s?pe(r,s):r;if(0===ae(a))return void(this.state=J);i&&(o=o?pe(o,i):i);const l=this.targetTileGrid_.getResolution(this.wrappedTileCoord_[0]),h=t.targetProj,c=Cu(e,h,a,l);if(!isFinite(c)||c<=0)return void(this.state=J);const u=void 0!==t.errorThreshold?t.errorThreshold:sd;if(this.triangulation_=new rd(e,h,a,o,c*u,l,t.transformMatrix),0===this.triangulation_.getTriangles().length)return void(this.state=J);this.sourceZ_=this.sourceTileGrid_.getZForResolution(c);let d=this.triangulation_.calculateSourceExtent();if(o&&(e.canWrapX()?(d[1]=be(d[1],o[1],o[3]),d[3]=be(d[3],o[1],o[3])):d=pe(d,o)),ae(d)){let n=0,r=0;e.canWrapX()&&(n=ye(i),r=Math.floor((d[0]-i[0])/n));Re(d.slice(),e,!0).forEach((e=>{const i=this.sourceTileGrid_.getTileRangeForExtentAndZ(e,this.sourceZ_),s=t.getTileFunction;for(let t=i.minX;t<=i.maxX;t++)for(let e=i.minY;e<=i.maxY;e++){const i=s(this.sourceZ_,t,e,this.pixelRatio_);if(i){const t=r*n;this.sourceTiles_.push({tile:i,offset:t})}}++r})),0===this.sourceTiles_.length&&(this.state=J)}else this.state=J}getSize(){return this.reprojSize_}getData(){return this.reprojData_}getError(){return this.reprojError_}reproject_(){const t=[];let e=!1;if(this.sourceTiles_.forEach((i=>{const n=i.tile;if(!n||n.getState()!==K)return;const r=n.getSize(),s=this.gutter_;let o;const a=Tt(n.getData());a?o=a:(e=!0,o=bt(wt(n.getData())));const l=[r[0]+2*s,r[1]+2*s],h=o instanceof Float32Array,c=l[0]*l[1],u=h?Float32Array:Uint8ClampedArray,d=new u(o.buffer),g=u.BYTES_PER_ELEMENT,f=g*d.length/c,p=d.byteLength/l[1],m=Math.floor(p/g/l[0]),_=this.sourceTileGrid_.getTileCoordExtent(n.tileCoord);_[0]+=i.offset,_[2]+=i.offset;const y=this.clipExtent_?.slice();y&&(y[0]+=i.offset,y[2]+=i.offset),t.push({extent:_,clipExtent:y,data:d,dataType:u,bytesPerPixel:f,pixelSize:l,bandCount:m})})),this.sourceTiles_.length=0,0===t.length)return this.state=q,void this.changed();const i=this.wrappedTileCoord_[0],n=this.targetTileGrid_.getTileSize(i),r="number"==typeof n?n:n[0],s="number"==typeof n?n:n[1],o=r*this.pixelRatio_,a=s*this.pixelRatio_,l=this.targetTileGrid_.getResolution(i),h=this.sourceTileGrid_.getResolution(this.sourceZ_),c=this.targetTileGrid_.getTileCoordExtent(this.wrappedTileCoord_),u=t[0].bandCount,d=new t[0].dataType(u*o*a),g=Qd(o,a,eg,{premultipliedAlpha:!1,antialias:!1});let f;const p=g.RGBA;let m;if(t[0].dataType==Float32Array){m=g.FLOAT,g.getExtension("WEBGL_color_buffer_float"),g.getExtension("OES_texture_float"),g.getExtension("EXT_float_blend");f=null!==g.getExtension("OES_texture_float_linear")&&this.interpolate}else m=g.UNSIGNED_BYTE,f=this.interpolate;for(let e=Math.ceil(u/4)-1;e>=0;--e){const i=[];for(let n=0,r=t.length;n{const i=e.getState();if(i!==Y&&i!==H)return;t++;const r=D(e,n,(()=>{const i=e.getState();i!=K&&i!=q&&i!=J||(k(r),t--,0===t&&(this.unlistenSources_(),this.reproject_()))}));this.sourcesListenerKeys_.push(r)})),0===t?setTimeout(this.reproject_.bind(this),0):this.sourceTiles_.forEach((function({tile:t}){t.getState()==Y&&t.load()}))}unlistenSources_(){this.sourcesListenerKeys_.forEach(k),this.sourcesListenerKeys_=null}}class rg extends hd{constructor(t){const e=void 0===t.projection?"EPSG:3857":t.projection;let i=t.tileGrid;void 0===i&&e&&(i=zu({extent:$u(e),maxResolution:t.maxResolution,maxZoom:t.maxZoom,minZoom:t.minZoom,tileSize:t.tileSize})),super({cacheSize:.1,attributions:t.attributions,attributionsCollapsible:t.attributionsCollapsible,projection:e,tileGrid:i,state:t.state,wrapX:t.wrapX,transition:t.transition,interpolate:t.interpolate,key:t.key,zDirection:t.zDirection}),this.gutter_=void 0!==t.gutter?t.gutter:0,this.tileSize_=t.tileSize?Bl(t.tileSize):null,this.tileSizes_=null,this.tileLoadingKeys_={},this.loader_=t.loader,this.handleTileChange_=this.handleTileChange_.bind(this),this.bandCount=void 0===t.bandCount?4:t.bandCount,this.tileGridForProjection_={},this.crossOrigin_=t.crossOrigin||"anonymous",this.transformMatrix=null}setTileSizes(t){this.tileSizes_=t}getTileSize(t){if(this.tileSizes_)return this.tileSizes_[t];if(this.tileSize_)return this.tileSize_;const e=this.getTileGrid();return e?Bl(e.getTileSize(t)):[256,256]}getGutterForProjection(t){const e=this.getProjection();return e&&!bn(e,t)||this.transformMatrix?0:this.gutter_}setLoader(t){this.loader_=t}getReprojTile_(t,e,i,n,r){const s=this.tileGrid||this.getTileGridForProjection(r||n),o=Math.max.apply(null,s.getResolutions().map(((t,e)=>{const i=Bl(s.getTileSize(e)),n=this.getTileSize(e);return Math.max(n[0]/i[0],n[1]/i[1])}))),a=this.getTileGridForProjection(n),l=[t,e,i],h=this.getTileCoordForTileUrlFunction(l,n),c=Object.assign({sourceProj:r||n,sourceTileGrid:s,targetProj:n,targetTileGrid:a,tileCoord:l,wrappedTileCoord:h,pixelRatio:o,gutter:this.gutter_,getTileFunction:(t,e,i,n)=>this.getTile(t,e,i,n),transformMatrix:this.transformMatrix},this.tileOptions),u=new ng(c);return u.key=this.getKey(),u}getTile(t,e,i,r,s){const o=this.getProjection();if(s&&(o&&!bn(o,s)||this.transformMatrix))return this.getReprojTile_(t,e,i,s,o);const a=this.getTileSize(t),l=this.loader_,h=new AbortController,c={signal:h.signal,crossOrigin:this.crossOrigin_},u=this.getTileCoordForTileUrlFunction([t,e,i]);if(!u)return null;const d=u[0],g=u[1],f=u[2],p=this.getTileGrid()?.getFullTileRange(d);p&&(c.maxY=p.getHeight()-1);const m=Object.assign({tileCoord:[t,e,i],loader:function(){return I((function(){return l(d,g,f,c)}))},size:a,controller:h},this.tileOptions),_=new It(m);return _.key=this.getKey(),_.addEventListener(n,this.handleTileChange_),_}handleTileChange_(t){const e=t.target,i=z(e),n=e.getState();let r;n==H?(this.tileLoadingKeys_[i]=!0,r=ud):i in this.tileLoadingKeys_&&(delete this.tileLoadingKeys_[i],r=n==q?gd:n==K?dd:void 0),r&&this.dispatchEvent(new cd(r,e))}getTileGridForProjection(t){const e=this.getProjection();if(this.tileGrid&&(!e||bn(e,t))&&!this.transformMatrix)return this.tileGrid;const i=z(t);return i in this.tileGridForProjection_||(this.tileGridForProjection_[i]=ju(t)),this.tileGridForProjection_[i]}setTileGridForProjection(t,e){const i=xn(t);if(i){const t=z(i);t in this.tileGridForProjection_||(this.tileGridForProjection_[t]=e)}}}function sg(t,e){if(!t)return!1;if(!0===t)return!0;if(3!==e.getSamplesPerPixel())return!1;const i=e.fileDirectory.PhotometricInterpretation,n=GeoTIFF.globals.photometricInterpretations;return i===n.CMYK||i===n.YCbCr||i===n.CIELab||i===n.ICCLab}const og="STATISTICS_MAXIMUM",ag="STATISTICS_MINIMUM";let lg;function hg(t){try{return t.getBoundingBox(!0)}catch{return[0,0,t.getWidth(),t.getHeight()]}}function cg(t){try{return t.getOrigin().slice(0,2)}catch{return[0,t.getHeight()]}}function ug(t,e){try{return t.getResolution(e)}catch{return[e.getWidth()/t.getWidth(),e.getHeight()/t.getHeight()]}}function dg(t){const e=t.geoKeys;if(!e)return null;if(e.ProjectedCSTypeGeoKey&&32767!==e.ProjectedCSTypeGeoKey){const t="EPSG:"+e.ProjectedCSTypeGeoKey;let i=xn(t);if(!i){const n=di(e.ProjLinearUnitsGeoKey);n&&(i=new fi({code:t,units:n}))}return i}if(e.GeographicTypeGeoKey&&32767!==e.GeographicTypeGeoKey){const t="EPSG:"+e.GeographicTypeGeoKey;let i=xn(t);if(!i){const n=di(e.GeogAngularUnitsGeoKey);n&&(i=new fi({code:t,units:n}))}return i}return null}function gg(t){return t.getImageCount().then((function(e){const i=new Array(e);for(let n=0;ni*t)throw new Error(n)}function mg(t){return t instanceof Int8Array?127:t instanceof Uint8Array||t instanceof Uint8ClampedArray?255:t instanceof Int16Array?32767:t instanceof Uint16Array?65535:t instanceof Int32Array?2147483647:t instanceof Uint32Array?4294967295:t instanceof Float32Array?34e37:255}class _g extends rg{constructor(t){super({state:"loading",tileGrid:null,projection:t.projection||null,transition:t.transition,interpolate:!1!==t.interpolate,wrapX:t.wrapX}),this.sourceInfo_=t.sources;const e=this.sourceInfo_.length;this.sourceOptions_=t.sourceOptions,this.sourceImagery_=new Array(e),this.sourceMasks_=new Array(e),this.resolutionFactors_=new Array(e),this.samplesPerPixel_,this.nodataValues_,this.metadata_,this.normalize_=!1!==t.normalize,this.addAlpha_=!1,this.error_=null,this.convertToRGB_=t.convertToRGB||!1,this.setKey(this.sourceInfo_.map((t=>t.url)).join(","));const i=this,n=new Array(e);for(let t=0;t=0;--t){const i=dg(e[t]);if(i){this.projection=i;break}}}determineTransformMatrix(t){const e=t[0];for(let t=e.length-1;t>=0;--t){const i=e[t].fileDirectory.ModelTransformation;if(i){const[t,e,n,r,s,o,a,l]=i,h=Zn(Zn([1/Math.sqrt(t*t+s*s),0,0,-1/Math.sqrt(e*e+o*o),r,l],[t,s,e,o,0,0]),[1,0,0,1,-r,-l]);this.transformMatrix=h,this.addAlpha_=!0;break}}}configure_(t){let e,i,n,r,s;const o=new Array(t.length),a=new Array(t.length),l=new Array(t.length);let h=0;const c=t.length;for(let u=0;u{4&~(t.fileDirectory.NewSubfileType||0)?c.push(t):d.push(t)}));const g=c.length;if(d.length>0&&d.length!==g)throw new Error(`Expected one mask per image found ${d.length} masks and ${g} images`);let f,p;const m=new Array(g),_=new Array(g),y=new Array(g);a[u]=new Array(g),l[u]=new Array(g);for(let t=0;ty.length&&(h=s.length-y.length);const t=s[s.length-1]/y[y.length-1];this.resolutionFactors_[u]=t;const e=y.map((e=>e*t)),i=`Resolution mismatch for source ${u}, got [${e}] but expected [${s}]`;pg(s.slice(h,s.length),e,.02,i,this.viewRejector)}else s=y,this.resolutionFactors_[u]=1;n?pg(n.slice(h,n.length),_,.01,`Tile size mismatch for source ${u}`,this.viewRejector):n=_,r?pg(r.slice(h,r.length),m,0,`Tile size mismatch for source ${u}`,this.viewRejector):r=m,this.sourceImagery_[u]=c.reverse(),this.sourceMasks_[u]=d.reverse()}for(let t=0,e=this.sourceImagery_.length;tKn(t,e))))}this.viewResolver({showFullExtent:!0,projection:this.projection,resolutions:s,center:Nn(ce(g),this.projection),extent:Gn(g,this.projection),zoom:1})}loadTile_(t,e,i,n){const r=this.getTileSize(t),s=this.sourceImagery_.length,o=new Array(2*s),a=this.nodataValues_,l=this.sourceInfo_,h=(lg||(lg=new GeoTIFF.Pool),lg);for(let c=0;c1,n=i&&t.imageInfo.profile[1].supports?t.imageInfo.profile[1].supports:[],r=i&&t.imageInfo.profile[1].formats?t.imageInfo.profile[1].formats:[],s=i&&t.imageInfo.profile[1].qualities?t.imageInfo.profile[1].qualities:[];return{url:t.imageInfo["@id"].replace(/\/?(?:info\.json)?$/g,""),sizes:void 0===t.imageInfo.sizes?void 0:t.imageInfo.sizes.map((function(t){return[t.width,t.height]})),tileSize:void 0===t.imageInfo.tiles?void 0:[t.imageInfo.tiles.map((function(t){return t.width}))[0],t.imageInfo.tiles.map((function(t){return void 0===t.height?t.width:t.height}))[0]],resolutions:void 0===t.imageInfo.tiles?void 0:t.imageInfo.tiles.map((function(t){return t.scaleFactors}))[0],supports:[...e.supports,...n],formats:[...e.formats,...r],qualities:[...e.qualities,...s]}},Cg[vg]=function(t){const e=t.getComplianceLevelSupportedFeatures(),i=void 0===t.imageInfo.extraFormats?e.formats:[...e.formats,...t.imageInfo.extraFormats],n=void 0!==t.imageInfo.preferredFormats&&Array.isArray(t.imageInfo.preferredFormats)&&t.imageInfo.preferredFormats.length>0?t.imageInfo.preferredFormats.filter((function(t){return["jpg","png","gif"].includes(t)})).reduce((function(t,e){return void 0===t&&i.includes(e)?e:t}),void 0):void 0;return{url:t.imageInfo.id,sizes:void 0===t.imageInfo.sizes?void 0:t.imageInfo.sizes.map((function(t){return[t.width,t.height]})),tileSize:void 0===t.imageInfo.tiles?void 0:[t.imageInfo.tiles.map((function(t){return t.width}))[0],t.imageInfo.tiles.map((function(t){return t.height}))[0]],resolutions:void 0===t.imageInfo.tiles?void 0:t.imageInfo.tiles.map((function(t){return t.scaleFactors}))[0],supports:void 0===t.imageInfo.extraFeatures?e.supports:[...e.supports,...t.imageInfo.extraFeatures],formats:i,qualities:void 0===t.imageInfo.extraQualities?e.qualities:[...e.qualities,...t.imageInfo.extraQualities],preferredFormat:n}};let Rg=class extends Gs{constructor(t,e,i,n,r,s,o){super(e,i,n,r,s,o),this.zoomifyImage_=null,this.tileSize_=t}getImage(){if(this.zoomifyImage_)return this.zoomifyImage_;const t=super.getImage();if(this.state==K){const e=this.tileSize_;if(t.width==e[0]&&t.height==e[1])return this.zoomifyImage_=t,t;const i=ft(e[0],e[1]);return i.drawImage(t,0,0),this.zoomifyImage_=i.canvas,i.canvas}return t}};function bg(t){return t.toLocaleString("en",{maximumFractionDigits:10})}class Pg extends Ms{constructor(t,e,i,n,r,s,o){let a=t.getExtent();a&&t.canWrapX()&&(a=a.slice(),a[0]=-1/0,a[2]=1/0);let l=e.getExtent();l&&e.canWrapX()&&(l=l.slice(),l[0]=-1/0,l[2]=1/0);const h=l?pe(i,l):i,c=Tu(t,e,ce(h),n),u=new rd(t,e,h,a,.5*c,n),d=u.calculateSourceExtent(),g=ve(d)?null:s(d,c,r),f=g?bs:Ls,p=g?g.getPixelRatio():1;super(i,n,p,f),this.targetProj_=e,this.maxSourceExtent_=a,this.triangulation_=u,this.targetResolution_=n,this.targetExtent_=i,this.sourceImage_=g,this.sourcePixelRatio_=p,this.interpolate_=o,this.canvas_=null,this.sourceListenerKey_=null}disposeInternal(){this.state==Ps&&this.unlistenSource_(),super.disposeInternal()}getImage(){return this.canvas_}getProjection(){return this.targetProj_}reproject_(){const t=this.sourceImage_.getState();if(t==Is){const t=ye(this.targetExtent_)/this.targetResolution_,e=fe(this.targetExtent_)/this.targetResolution_;this.canvas_=Ru(t,e,this.sourcePixelRatio_,bu(this.sourceImage_.getResolution()),this.maxSourceExtent_,this.targetResolution_,this.targetExtent_,this.triangulation_,[{extent:this.sourceImage_.getExtent(),image:this.sourceImage_.getImage()}],0,void 0,this.interpolate_,!0)}this.state=t,this.changed()}load(){if(this.state==bs){this.state=Ps,this.changed();const t=this.sourceImage_.getState();t==Is||t==Fs?this.reproject_():(this.sourceListenerKey_=D(this.sourceImage_,n,(t=>{const e=this.sourceImage_.getState();e!=Is&&e!=Fs||(this.unlistenSource_(),this.reproject_())})),this.sourceImage_.load())}}unlistenSource_(){k(this.sourceListenerKey_),this.sourceListenerKey_=null}}const Ig="imageloadstart",Fg="imageloadend",Lg="imageloaderror";class Mg extends M{constructor(t,e){super(t),this.image=e}}class Ag extends ad{constructor(t){super({attributions:t.attributions,projection:t.projection,state:t.state,interpolate:void 0===t.interpolate||t.interpolate}),this.on,this.once,this.un,this.loader=t.loader||null,this.resolutions_=void 0!==t.resolutions?t.resolutions:null,this.reprojectedImage_=null,this.reprojectedRevision_=0,this.image=null,this.wantedExtent_,this.wantedResolution_,this.static_=!!t.loader&&0===t.loader.length,this.wantedProjection_=null}getResolutions(){return this.resolutions_}setResolutions(t){this.resolutions_=t}findNearestResolution(t){const e=this.getResolutions();if(e){t=e[v(e,t,0)]}return t}getImage(t,e,i,n){const r=this.getProjection();if(!r||!n||bn(r,n))return r&&(n=r),this.getImageInternal(t,e,i,n);if(this.reprojectedImage_){if(this.reprojectedRevision_==this.getRevision()&&bn(this.reprojectedImage_.getProjection(),n)&&this.reprojectedImage_.getResolution()==e&&Jt(this.reprojectedImage_.getExtent(),t))return this.reprojectedImage_;this.reprojectedImage_.dispose(),this.reprojectedImage_=null}return this.reprojectedImage_=new Pg(r,n,t,e,i,((t,e,i)=>this.getImageInternal(t,e,i,r)),this.getInterpolate()),this.reprojectedRevision_=this.getRevision(),this.reprojectedImage_}getImageInternal(t,e,i,r){if(this.loader){const s=Dg(t,e,i,1),o=this.findNearestResolution(e);if(this.image&&(this.static_||this.wantedProjection_===r&&(this.wantedExtent_&&Vt(this.wantedExtent_,s)||Vt(this.image.getExtent(),s))&&(this.wantedResolution_&&bu(this.wantedResolution_)===o||bu(this.image.getResolution())===o)))return this.image;this.wantedProjection_=r,this.wantedExtent_=s,this.wantedResolution_=o,this.image=new Ms(s,o,i,this.loader),this.image.addEventListener(n,this.handleImageChange.bind(this))}return this.image}handleImageChange(t){const e=t.target;let i;switch(e.getState()){case Ps:this.loading=!0,i=Ig;break;case Is:this.loading=!1,i=Fg;break;case Fs:this.loading=!1,i=Lg;break;default:return}this.hasListener(i)&&this.dispatchEvent(new Mg(i,e))}}function Og(t,e){t.getImage().src=e}function Dg(t,e,i,n){const r=e/i,s=ce(t),o=Ge(ye(t)/r,4),a=Ge(fe(t)/r,4);return de(s,r,0,[o+2*Ge((n-1)*o/2,4),a+2*Ge((n-1)*a/2,4)])}function Ng(t,e,i,n,r,s){const o=r.getCode().split(/:(?=\d+$)/).pop(),a=i/n,l=[Ne(ye(e)/a,4),Ne(fe(e)/a,4)];s.SIZE=l[0]+","+l[1],s.BBOX=e.join(","),s.BBOXSR=o,s.IMAGESR=o,s.DPI=Math.round(s.DPI?s.DPI*n:90*n);return Wu(t.replace(/MapServer\/?$/,"MapServer/export").replace(/ImageServer\/?$/,"ImageServer/exportImage"),s)}function kg(t){const e=t.load?t.load:Ns,i=xn(t.projection||"EPSG:3857"),n=t.ratio??1.5,r=t.crossOrigin??null;return function(s,o,a){a=t.hidpi?a:1;const l={F:"image",FORMAT:"PNG32",TRANSPARENT:!0};Object.assign(l,t.params),s=Dg(s,o,a,n);const h=Ng(t.url,s,o,a,i,l),c=new Image;return c.crossOrigin=r,e(c,h).then((t=>{const e=ye(s)/t.width*a;return{image:t,extent:s,resolution:e,pixelRatio:a}}))}}function Gg(t,e,i,n,r,s,o){const a=function(t,e,i,n){const r=ye(t),s=fe(t),o=e[0],a=e[1],l=.0254/n;return a*r>o*s?r*i/(o*l):s*i/(a*l)}(i,n,s,o),l=ce(i),h={OPERATION:r?"GETDYNAMICMAPOVERLAYIMAGE":"GETMAPIMAGE",VERSION:"2.0.0",LOCALE:"en",CLIENTAGENT:"ol/source/ImageMapGuide source",CLIP:"1",SETDISPLAYDPI:o,SETDISPLAYWIDTH:Math.round(n[0]),SETDISPLAYHEIGHT:Math.round(n[1]),SETVIEWSCALE:a,SETVIEWCENTERX:l[0],SETVIEWCENTERY:l[1]};return Object.assign(h,e),Wu(t,h)}function jg(t){const e=t.load||Ns,i=t.useOverlay??!1,n=t.metersPerUnit||1,r=t.displayDpi||96,s=t.ratio??1,o=t.crossOrigin??null;return function(a,l,h){const c=new Image;c.crossOrigin=o;const u=ye(a=Dg(a,l,h,s))/l,d=fe(a)/l,g=[u*h,d*h],f=Gg(t.url,t.params,a,g,i,n,r);return e(c,f).then((t=>({image:t,extent:a,pixelRatio:h})))}}function Ug(t){const e=t.load||Ns,i=t.imageExtent,n=t.crossOrigin??null;return()=>{const r=new Image;return r.crossOrigin=n,e(r,t.url).then((t=>{const e=ye(i)/t.width,n=fe(i)/t.height;return{image:t,extent:i,resolution:e!==n?[e,n]:n,pixelRatio:1}}))}}const Bg=new Error("Image failed to load");function zg(t,e,i,n,r){return new Promise(((s,o)=>{const a=new Image;a.crossOrigin=r.crossOrigin??null,a.addEventListener("load",(()=>s(a))),a.addEventListener("error",(()=>o(Bg))),a.src=qu(t,e,i,n,r.maxY)}))}function Xg(t){return function(e,i,n,r){return zg(Ju(t,e,i,n),e,i,n,r)}}function Vg(t){let e;if(Array.isArray(t))e=Xg(t);else if("string"==typeof t){e=Xg(Qu(t))}else{if("function"!=typeof t)throw new Error("The url option must be a single template, an array of templates, or a function for getting a URL");i=t,e=function(t,e,n,r){return zg(i(t,e,n,r),t,e,n,r)}}var i;return e}let $g=0;function Wg(t){return Array.isArray(t)?t.join("\n"):"string"==typeof t?t:(++$g,"url-function-key-"+$g)}class Zg extends rg{constructor(t){let e,i=(t=t||{}).loader;t.url&&(i=Vg(t.url),e=Wg(t.url));const n=i?t.state:"loading",r=void 0===t.wrapX||t.wrapX;super({loader:i,key:e,attributions:t.attributions,attributionsCollapsible:t.attributionsCollapsible,maxZoom:t.maxZoom,minZoom:t.minZoom,tileSize:t.tileSize,gutter:t.gutter,maxResolution:t.maxResolution,projection:t.projection,tileGrid:t.tileGrid,state:n,wrapX:r,transition:t.transition,interpolate:!1!==t.interpolate,crossOrigin:t.crossOrigin,zDirection:t.zDirection})}setUrl(t){const e=Vg(t);this.setLoader(e),this.setKey(Wg(t)),"ready"!==this.getState()&&this.setState("ready")}}const Yg="1.3.0",Hg=[101,101];function Kg(t,e,i,n,r){r.WIDTH=i[0],r.HEIGHT=i[1];const s=n.getAxisOrientation(),o=Ke(r.VERSION,"1.3")>=0;r[o?"CRS":"SRS"]=n.getCode();const a=o&&s.startsWith("ne")?[e[1],e[0],e[3],e[2]]:e;return r.BBOX=a.join(","),Wu(t,r)}function qg(t,e,i,n,r,s,o){s=Object.assign({REQUEST:"GetMap"},s);const a=e/i,l=[Ne(ye(t)/a,4),Ne(fe(t)/a,4)];if(1!=i)switch(o){case"geoserver":const t=90*i+.5|0;"FORMAT_OPTIONS"in s?s.FORMAT_OPTIONS+=";dpi:"+t:s.FORMAT_OPTIONS="dpi:"+t;break;case"mapserver":s.MAP_RESOLUTION=90*i;break;case"carmentaserver":case"qgis":s.DPI=90*i;break;default:throw new Error("Unknown `serverType` configured")}return Kg(r,t,l,n,s)}function Jg(t,e){return Object.assign({REQUEST:e,SERVICE:"WMS",VERSION:Yg,FORMAT:"image/png",STYLES:"",TRANSPARENT:"TRUE"},t)}function Qg(t){const e=void 0===t.hidpi||t.hidpi,i=xn(t.projection||"EPSG:3857"),n=t.ratio||1.5,r=t.load||Ns,s=t.crossOrigin??null;return(o,a,l)=>{o=Dg(o,a,l,n),1==l||e&&void 0!==t.serverType||(l=1);const h=qg(o,a,l,i,t.url,Jg(t.params,"GetMap"),t.serverType),c=new Image;return c.crossOrigin=s,r(c,h).then((t=>({image:t,extent:o,pixelRatio:l})))}}function tf(t,e,i){if(void 0===t.url)return;const n=xn(t.projection||"EPSG:3857"),r=de(e,i,0,Hg),s={QUERY_LAYERS:t.params.LAYERS,INFO_FORMAT:"application/json"};Object.assign(s,Jg(t.params,"GetFeatureInfo"),t.params);const o=ke((e[0]-r[0])/i,4),a=ke((r[3]-e[1])/i,4),l=Ke(s.VERSION,"1.3")>=0;return s[l?"I":"X"]=o,s[l?"J":"Y"]=a,Kg(t.url,r,Hg,n,s)}function ef(t,e){if(void 0===t.url)return;const i={SERVICE:"WMS",VERSION:Yg,REQUEST:"GetLegendGraphic",FORMAT:"image/png"};if(void 0!==e){const n=xn(t.projection||"EPSG:3857").getMetersPerUnit()||1,r=28e-5;i.SCALE=e*n/r}if(Object.assign(i,t.params),void 0!==t.params&&void 0===i.LAYER){const t=i.LAYERS;if(!(!Array.isArray(t)||1!==t.length))return;i.LAYER=t}return Wu(t.url,i)}const nf={"image/png":!0,"image/jpeg":!0,"image/gif":!0,"image/webp":!0},rf={"application/vnd.mapbox-vector-tile":!0,"application/geo+json":!0};function sf(t,e){if(!e.length)return t;const i=new URL(t,"file:/");if(i.pathname.split("/").includes("collections"))return Ye('The "collections" query parameter cannot be added to collection endpoints'),t;const n=e.map((t=>encodeURIComponent(t))).join(",");i.searchParams.append("collections",n);return`${t.split("?")[0]}?${decodeURIComponent(i.searchParams.toString())}`}function of(t,e,i){let n,r;for(let i=0;it.replace(/E|X|Lon/i,"e").replace(/N|Y|Lat/i,"n"))).join(""):r.getAxisOrientation()).startsWith("en"),a=e.tileMatrices,l={};for(let t=0;tt.maxTileCol||u.tileRowt.maxTileRow)return}Object.assign(u,y);const d=i.replace(/\{(\w+?)\}/g,(function(t,e){return u[e]}));return cu(x,d)}}}function hf(t){return hu(t.url).then((function(e){return function(t,e){const i=e.tileMatrixSetLimits;let n;if("map"===e.dataType)n=of(e.links,t.mediaType,t.collections);else{if("vector"!==e.dataType)throw new Error('Expected tileset data type to be "map" or "vector"');n=af(e.links,t.mediaType,t.supportedMediaTypes,t.collections)}if(e.tileMatrixSet)return lf(t,e.tileMatrixSet,n,i);const r=e.links.find((t=>"http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme"===t.rel));if(!r)throw new Error("Expected http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme link or tileMatrixSet");const s=r.href;return hu(cu(t.url,s)).then((function(e){return lf(t,e,n,i)}))}(t,e)}))}class cf extends fd{constructor(t){const e=t.projection||"EPSG:3857",i=t.extent||$u(e),n=t.tileGrid||zu({extent:i,maxResolution:t.maxResolution,maxZoom:void 0!==t.maxZoom?t.maxZoom:22,minZoom:t.minZoom,tileSize:t.tileSize||512});super({attributions:t.attributions,attributionsCollapsible:t.attributionsCollapsible,cacheSize:t.cacheSize,interpolate:!0,projection:e,state:t.state,tileGrid:n,tileLoadFunction:t.tileLoadFunction?t.tileLoadFunction:uf,tileUrlFunction:t.tileUrlFunction,url:t.url,urls:t.urls,wrapX:void 0===t.wrapX||t.wrapX,transition:t.transition,zDirection:void 0===t.zDirection?1:t.zDirection}),this.format_=t.format?t.format:null,this.tileKeysBySourceTileUrl_={},this.sourceTiles_={},this.overlaps_=null==t.overlaps||t.overlaps,this.tileClass=t.tileClass?t.tileClass:eu,this.tileGrids_={}}getOverlaps(){return this.overlaps_}getSourceTiles(t,e,i){if(i.getState()===Y){i.setState(H);const r=i.wrappedTileCoord,s=this.getTileGridForProjection(e),o=s.getTileCoordExtent(r),a=r[0],l=s.getResolution(a);Ut(o,-l,o);const h=this.tileGrid,c=h.getExtent();c&&pe(o,c,o);const u=h.getZForResolution(l,this.zDirection);h.forEachTileCoord(o,u,(r=>{const s=this.tileUrlFunction(r,t,e);this.sourceTiles_[s]||(this.sourceTiles_[s]=new this.tileClass(r,s?Y:J,s,this.format_,this.tileLoadFunction));const o=this.sourceTiles_[s];i.sourceTiles.push(o),this.tileKeysBySourceTileUrl_[s]||(this.tileKeysBySourceTileUrl_[s]=[]),this.tileKeysBySourceTileUrl_[s].push(i.getKey());const a=o.getState();if(a{this.handleTileChange(e);const r=o.getState();if(r===K||r===q){const e=o.getKey();e in i.errorTileKeys?o.getState()===K&&delete i.errorTileKeys[e]:i.loadingSourceTiles--,r===q?i.errorTileKeys[e]=!0:o.removeEventListener(n,t),0===i.loadingSourceTiles&&i.setState(L(i.errorTileKeys)?K:q)}};o.addEventListener(n,t),i.loadingSourceTiles++}a===Y&&(o.extent=h.getTileCoordExtent(r),o.projection=e,o.resolution=h.getResolution(r[0]),o.load())})),i.loadingSourceTiles||i.setState(i.sourceTiles.some((t=>t.getState()===q))?q:K)}return i.sourceTiles}removeSourceTiles(t){const e=t.getKey(),i=t.sourceTiles;for(let t=0,n=i.length;t{h=h&&!this.tileUrlFunction(t,n,r)}))}const c=new Qc(s,h?J:Y,o,this.getSourceTiles.bind(this,n,r),this.removeSourceTiles.bind(this));return c.key=this.getKey(),c}getTileGridForProjection(t){const e=t.getCode();let i=this.tileGrids_[e];if(!i){const n=this.getProjection();Ft(null===n||bn(n,t),"A VectorTile source can only be rendered if it has a projection compatible with the view projection.");const r=this.tileGrid,s=r.getResolutions().slice(),o=s.map((function(t,e){return r.getOrigin(e)})),a=s.map((function(t,e){return r.getTileSize(e)})),l=43;for(let t=s.length;t{if("function"==typeof mt()[e])return this.push_(e),this.pushMethodArgs_},set:(t,e,i)=>(this.push_(e,i),!0)})}push_(...t){const e=this.instructions_,i=this.zIndex+this.offset_;e[i]||(e[i]=[]),e[i].push(...t)}pushMethodArgs_=(...t)=>(this.push_(t),this);pushFunction(t){this.push_(t)}getContext(){return this.context_}draw(t){this.instructions_.forEach((e=>{for(let i=0,n=e.length;ithis.maxStaleKeys&&(this.staleKeys_.length=this.maxStaleKeys)}getFeatures(t){return U()}getData(t){return null}prepareFrame(t){return U()}renderFrame(t,e){return U()}forEachFeatureAtCoordinate(t,e,i,n,r){}getLayer(){return this.layer_}handleFontsChanged(){}handleImageChange_(t){const e=t.target;e.getState()!==Is&&e.getState()!==Fs||this.renderIfReadyAndVisible()}loadImage(t){let e=t.getState();return e!=Is&&e!=Fs&&t.addEventListener(n,this.boundHandleImageChange_),e==bs&&(t.load(),e=t.getState()),e==Is}renderIfReadyAndVisible(){const t=this.getLayer();t&&t.getVisible()&&"ready"===t.getSourceState()&&t.changed()}renderDeferred(t){}disposeInternal(){delete this.layer_,super.disposeInternal()}}const pf=[];let mf=null;class _f extends ff{constructor(t){super(t),this.container=null,this.renderedResolution,this.tempTransform=[1,0,0,1,0,0],this.pixelTransform=[1,0,0,1,0,0],this.inversePixelTransform=[1,0,0,1,0,0],this.context=null,this.deferredContext_=null,this.containerReused=!1,this.frameState=null}getImageData(t,e,i){let n;mf||(mf=ft(1,1,void 0,{willReadFrequently:!0})),mf.clearRect(0,0,1,1);try{mf.drawImage(t,e,i,1,1,0,0,1,1),n=mf.getImageData(0,0,1,1).data}catch{return mf=null,null}return n}getBackground(t){let e=this.getLayer().getBackground();return"function"==typeof e&&(e=e(t.viewState.resolution)),e||void 0}useContainer(t,e,i){const n=this.getLayer().getClassName();let r,s;if(t&&t.className===n&&(!i||t&&t.style.backgroundColor&&w(kl(t.style.backgroundColor),kl(i)))){const e=t.firstElementChild;e instanceof HTMLCanvasElement&&(s=e.getContext("2d"))}if(s&&or(s.canvas.style.transform,e)?(this.container=t,this.context=s,this.containerReused=!0):this.containerReused?(this.container=null,this.context=null,this.containerReused=!1):this.container&&(this.container.style.backgroundColor=null),!this.container){r=document.createElement("div"),r.className=n;let t=r.style;t.position="absolute",t.width="100%",t.height="100%",s=ft();const e=s.canvas;r.appendChild(e),t=e.style,t.position="absolute",t.left="0",t.transformOrigin="top left",this.container=r,this.context=s}this.containerReused||!i||this.container.style.backgroundColor||(this.container.style.backgroundColor=i)}clipUnrotated(t,e,i){const n=me(i),r=_e(i),s=he(i),o=le(i);Kn(e.coordinateToPixelTransform,n),Kn(e.coordinateToPixelTransform,r),Kn(e.coordinateToPixelTransform,s),Kn(e.coordinateToPixelTransform,o);const a=this.inversePixelTransform;Kn(a,n),Kn(a,r),Kn(a,s),Kn(a,o),t.save(),t.beginPath(),t.moveTo(Math.round(n[0]),Math.round(n[1])),t.lineTo(Math.round(r[0]),Math.round(r[1])),t.lineTo(Math.round(s[0]),Math.round(s[1])),t.lineTo(Math.round(o[0]),Math.round(o[1])),t.clip()}prepareContainer(t,e){const i=t.extent,n=t.viewState.resolution,r=t.viewState.rotation,s=t.pixelRatio,o=Math.round(ye(i)/n*s),a=Math.round(fe(i)/n*s);tr(this.pixelTransform,t.size[0]/2,t.size[1]/2,1/s,1/s,r,-o/2,-a/2),er(this.inversePixelTransform,this.pixelTransform);const l=rr(this.pixelTransform);if(this.useContainer(e,l,this.getBackground(t)),!this.containerReused){const t=this.context.canvas;t.width!=o||t.height!=a?(t.width=o,t.height=a):this.context.clearRect(0,0,o,a),l!==t.style.transform&&(t.style.transform=l)}}dispatchRenderEvent_(t,e,i){const n=this.getLayer();if(n.hasListener(t)){const r=new kc(t,this.inversePixelTransform,i,e);n.dispatchEvent(r)}}preRender(t,e){this.frameState=e,e.declutter||this.dispatchRenderEvent_(Za,t,e)}postRender(t,e){e.declutter||this.dispatchRenderEvent_(Ya,t,e)}renderDeferredInternal(t){}getRenderContext(t){return t.declutter&&!this.deferredContext_&&(this.deferredContext_=new gf),t.declutter?this.deferredContext_.getContext():this.context}renderDeferred(t){t.declutter&&(this.dispatchRenderEvent_(Za,this.context,t),t.declutter&&this.deferredContext_&&(this.deferredContext_.draw(this.context),this.deferredContext_.clear()),this.renderDeferredInternal(t),this.dispatchRenderEvent_(Ya,this.context,t))}getRenderTransform(t,e,i,n,r,s,o){const a=r/2,l=s/2,h=n/e,c=-h,u=-t[0]+o,d=-t[1];return tr(this.tempTransform,a,l,h,c,-i,u,d)}disposeInternal(){delete this.frameState,super.disposeInternal()}}class yf extends _f{constructor(t){super(t),this.image=null}getImage(){return this.image?this.image.getImage():null}prepareFrame(t){const e=t.layerStatesArray[t.layerIndex],i=t.pixelRatio,n=t.viewState,r=n.resolution,s=this.getLayer().getSource(),o=t.viewHints;let a=t.extent;if(void 0!==e.extent&&(a=pe(a,jn(e.extent,n.projection))),!o[ao]&&!o[lo]&&!ve(a))if(s){const t=n.projection,e=s.getImage(a,r,i,t);e&&(this.loadImage(e)?this.image=e:e.getState()===Ls&&(this.image=null))}else this.image=null;return!!this.image}getData(t){const e=this.frameState;if(!e)return null;const i=this.getLayer(),n=Kn(e.pixelToCoordinateTransform,t.slice()),r=i.getExtent();if(r&&!Xt(r,n))return null;const s=this.image.getExtent(),o=this.image.getImage(),a=ye(s),l=Math.floor(o.width*((n[0]-s[0])/a));if(l<0||l>=o.width)return null;const h=fe(s),c=Math.floor(o.height*((s[3]-n[1])/h));return c<0||c>=o.height?null:this.getImageData(o,l,c)}renderFrame(t,e){const i=this.image,n=i.getExtent(),r=i.getResolution(),[s,o]=Array.isArray(r)?r:[r,r],a=i.getPixelRatio(),l=t.layerStatesArray[t.layerIndex],h=t.pixelRatio,c=t.viewState,u=c.center,d=c.resolution,g=h*s/(d*a),f=h*o/(d*a);this.prepareContainer(t,e);const p=this.context.canvas.width,m=this.context.canvas.height,_=this.getRenderContext(t);let y=!1,x=!0;if(l.extent){const e=jn(l.extent,c.projection);x=xe(e,t.extent),y=x&&!Vt(e,t.extent),y&&this.clipUnrotated(_,t,e)}const v=i.getImage(),S=tr(this.tempTransform,p/2,m/2,g,f,0,a*(n[0]-u[0])/s,a*(u[1]-n[3])/o);this.renderedResolution=o*h/a;const E=v.width*S[0],w=v.height*S[3];if(this.getLayer().getSource().getInterpolate()||(_.imageSmoothingEnabled=!1),this.preRender(_,t),x&&E>=.5&&w>=.5){const t=S[4],e=S[5],i=l.opacity;1!==i&&(_.save(),_.globalAlpha=i),_.drawImage(v,0,0,+v.width,+v.height,t,e,E,w),1!==i&&_.restore()}return this.postRender(this.context,t),y&&_.restore(),_.imageSmoothingEnabled=!0,this.container}}class xf extends Ja{constructor(t){super(t=t||{})}}class vf extends xf{constructor(t){super(t)}createRenderer(){return new yf(this)}getData(t){return super.getData(t)}}function Sf(t,e,i,n,r){return`${z(t)},${e},${Fu(i,n,r)}`}function Ef(t,e,i){if(!(i in t))return t[i]=new Set([e]),!0;const n=t[i],r=n.has(e);return r||n.add(e),!r}function wf(t,e,i){const n=t[i];return!!n&&n.delete(e)}function Tf(t,e){const i=t.layerStatesArray[t.layerIndex];i.extent&&(e=pe(e,jn(i.extent,t.viewState.projection)));const n=i.layer.getRenderSource();if(!n.getWrapX()){const i=n.getTileGridForProjection(t.viewState.projection).getExtent();i&&(e=pe(e,i))}return e}class Cf extends _f{constructor(t,e){super(t),e=e||{},this.extentChanged=!0,this.renderComplete=!1,this.renderedExtent_=null,this.renderedPixelRatio,this.renderedProjection=null,this.renderedTiles=[],this.renderedSourceKey_,this.renderedSourceRevision_,this.tempExtent=[1/0,1/0,-1/0,-1/0],this.tempTileRange_=new Kc(0,0,0,0),this.tempTileCoord_=Iu(0,0,0);const i=void 0!==e.cacheSize?e.cacheSize:512;this.tileCache_=new Pu(i),this.maxStaleKeys=.5*i}getTileCache(){return this.tileCache_}getOrCreateTile(t,e,i,n){const r=this.tileCache_,s=this.getLayer().getSource(),o=Sf(s,s.getKey(),t,e,i);let a;if(r.containsKey(o))a=r.get(o);else{if(a=s.getTile(t,e,i,n.pixelRatio,n.viewState.projection),!a)return null;r.set(o,a)}return a}getTile(t,e,i,n){const r=this.getOrCreateTile(t,e,i,n);return r||null}getData(t){const e=this.frameState;if(!e)return null;const i=this.getLayer(),n=Kn(e.pixelToCoordinateTransform,t.slice()),r=i.getExtent();if(r&&!Xt(r,n))return null;const s=e.viewState,o=i.getRenderSource(),a=o.getTileGridForProjection(s.projection),l=o.getTilePixelRatio(e.pixelRatio);for(let t=a.getZForResolution(s.resolution);t>=a.getMinZoom();--t){const i=a.getTileCoordForCoordAndZ(n,t),r=this.getTile(t,i[1],i[2],e);if(!r||r.getState()!==K)continue;const h=a.getOrigin(t),c=Bl(a.getTileSize(t)),u=a.getResolution(t);let d;if(r instanceof Gs||r instanceof od)d=r.getImage();else{if(!(r instanceof It))continue;if(d=wt(r.getData()),!d)continue}const g=Math.floor(l*((n[0]-h[0])/u-i[1]*c[0])),f=Math.floor(l*((h[1]-n[1])/u-i[2]*c[1])),p=Math.round(l*o.getGutterForProjection(s.projection));return this.getImageData(d,g+p,f+p)}return null}prepareFrame(t){this.renderedProjection?t.viewState.projection!==this.renderedProjection&&(this.tileCache_.clear(),this.renderedProjection=t.viewState.projection):this.renderedProjection=t.viewState.projection;const e=this.getLayer().getSource();if(!e)return!1;const i=e.getRevision();return this.renderedSourceRevision_?this.renderedSourceRevision_!==i&&(this.renderedSourceRevision_=i,this.renderedSourceKey_===e.getKey()&&this.tileCache_.clear()):this.renderedSourceRevision_=i,!0}enqueueTiles(t,e,i,n,r){const s=t.viewState,o=this.getLayer(),a=o.getRenderSource(),l=a.getTileGridForProjection(s.projection),h=z(a);h in t.wantedTiles||(t.wantedTiles[h]={});const c=t.wantedTiles[h],u=o.getMapInternal(),d=Math.max(i-r,l.getMinZoom(),l.getZForResolution(Math.min(o.getMaxResolution(),u?u.getView().getResolutionForZoom(Math.max(o.getMinZoom(),0)):l.getResolution(0)),a.zDirection)),g=s.rotation,f=g?ge(s.center,s.resolution,g,t.size):void 0;for(let r=i;r>=d;--r){const i=l.getTileRangeForExtentAndZ(e,r,this.tempTileRange_),s=l.getResolution(r);for(let e=i.minX;e<=i.maxX;++e)for(let o=i.minY;o<=i.maxY;++o){if(g&&!l.tileCoordIntersectsViewport([r,e,o],f))continue;const i=this.getTile(r,e,o,t);if(!i)continue;if(!Ef(n,i,r))continue;const a=i.getKey();if(c[a]=!0,i.getState()===Y&&!t.tileQueue.isKeyQueued(a)){const n=Iu(r,e,o,this.tempTileCoord_);t.tileQueue.enqueue([i,h,l.getTileCoordCenter(n),s])}}}}findStaleTile_(t,e){const i=this.tileCache_,n=t[0],r=t[1],s=t[2],o=this.getStaleKeys();for(let t=0;t0&&setTimeout((()=>{this.enqueueTiles(t,C,u-1,w,T-1)}),0),!(u in w))return this.container;const R=z(this),b=t.time;for(const e of w[u]){const i=e.getState();if(i===J)continue;const n=e.tileCoord;if(i===K){if(1===e.getAlpha(R,b)){e.endTransition(R);continue}}i!==q&&(this.renderComplete=!1);if(this.findStaleTile_(n,w)){wf(w,e,u),t.animate=!0;continue}if(this.findAltTiles_(c,n,u+1,w))continue;const r=c.getMinZoom();for(let t=u-1;t>=r;--t){if(this.findAltTiles_(c,n,t,w))break}}const P=d/s*a/p,I=this.getRenderContext(t);tr(this.tempTransform,m/2,_/2,P,P,0,-m/2,-_/2),i.extent&&this.clipUnrotated(I,t,x),h.getInterpolate()||(I.imageSmoothingEnabled=!1),this.preRender(I,t);const F=Object.keys(w).map(Number);let L;F.sort(y);const M=[],A=[];for(let e=F.length-1;e>=0;--e){const i=F[e],n=h.getTilePixelSize(i,a,r),s=c.getResolution(i)/d,o=n[0]*s*P,l=n[1]*s*P,u=c.getTileCoordForCoordAndZ(me(E),i),g=c.getTileCoordExtent(u),f=Kn(this.tempTransform,[p*(g[0]-E[0])/d,p*(E[3]-g[3])/d]),m=p*h.getGutterForProjection(r);for(const e of w[i]){if(e.getState()!==K)continue;const n=e.tileCoord,r=u[1]-n[1],s=Math.round(f[0]-(r-1)*o),a=u[2]-n[2],c=Math.round(f[1]-(a-1)*l),d=Math.round(f[0]-r*o),g=Math.round(f[1]-a*l),p=s-d,_=c-g,y=1===F.length;let x=!1;L=[d,g,d+p,g,d+p,g+_,d,g+_];for(let t=0,e=M.length;t{const i=z(h),n=e.wantedTiles[i],r=n?Object.keys(n).length:0;this.updateCacheSize(r),this.tileCache_.expireCache()};t.postRenderFunctions.push(e)}return this.container}updateCacheSize(t){this.tileCache_.highWaterMark=Math.max(this.tileCache_.highWaterMark,2*t)}drawTile(t,e,i,n,r,s,o,a){let l;if(t instanceof It){if(l=wt(t.getData()),!l)throw new Error("Rendering array data is not yet supported")}else l=this.getTileImage(t);if(!l)return;const h=this.getRenderContext(e),c=z(this),u=e.layerStatesArray[e.layerIndex],d=u.opacity*(a?t.getAlpha(c,e.time):1),g=d!==h.globalAlpha;g&&(h.save(),h.globalAlpha=d),h.drawImage(l,o,o,l.width-2*o,l.height-2*o,i,n,r,s),g&&h.restore(),d!==u.opacity?e.animate=!0:a&&t.endTransition(c)}getImage(){const t=this.context;return t?t.canvas:null}getTileImage(t){return t.getImage()}updateUsedTiles(t,e,i){const n=z(e);n in t||(t[n]={}),t[n][i.getKey()]=!0}}var Rf="preload",bf="useInterimTilesOnError";class Pf extends Ja{constructor(t){t=t||{};const e=Object.assign({},t),i=t.cacheSize;delete t.cacheSize,delete e.preload,delete e.useInterimTilesOnError,super(e),this.on,this.once,this.un,this.cacheSize_=i,this.setPreload(void 0!==t.preload?t.preload:0),this.setUseInterimTilesOnError(void 0===t.useInterimTilesOnError||t.useInterimTilesOnError)}getCacheSize(){return this.cacheSize_}getPreload(){return this.get(Rf)}setPreload(t){this.set(Rf,t)}getUseInterimTilesOnError(){return this.get(bf)}setUseInterimTilesOnError(t){this.set(bf,t)}getData(t){return super.getData(t)}}class If extends Pf{constructor(t){super(t)}createRenderer(){return new Cf(this,{cacheSize:this.getCacheSize()})}}function Ff(t){return function(e){const i=e.buffers,n=e.meta,r=e.imageOps,s=e.width,o=e.height,a=i.length,l=i[0].byteLength;if(r){const e=new Array(a);for(let t=0;tthis.maxQueueLength_;)this.queue_.shift().callback(null,null)}dispatch_(){if(this.running_||0===this.queue_.length)return;const t=this.queue_.shift();this.job_=t;const e=t.inputs[0].width,i=t.inputs[0].height,n=t.inputs.map((function(t){return t.data.buffer})),r=this.workers_.length;if(this.running_=r,1===r)return void this.workers_[0].postMessage({buffers:n,meta:t.meta,imageOps:this.imageOps_,width:e,height:i},n);const s=t.inputs[0].data.length,o=4*Math.ceil(s/4/r);for(let s=0;s=93&&r--,r>=35&&r--,r-=32;let s=null;if(r in this.keys_){const t=this.keys_[r];s=this.data_&&t in this.data_?this.data_[t]:t}return s}forDataAtCoordinate(t,e,i){this.state==J&&!0===i?(this.state=Y,N(this,n,(i=>{e(this.getData(t))})),this.loadInternal_()):!0===i?setTimeout((()=>{e(this.getData(t))}),0):e(this.getData(t))}getKey(){return this.src_}handleError_(){this.state=q,this.changed()}handleLoad_(t){this.grid_=t.grid,this.keys_=t.keys,this.data_=t.data,this.state=K,this.changed()}loadInternal_(){if(this.state==Y)if(this.state=H,this.jsonp_)ou(this.src_,this.handleLoad_.bind(this),this.handleError_.bind(this));else{const t=new XMLHttpRequest;t.addEventListener("load",this.onXHRLoad_.bind(this)),t.addEventListener("error",this.onXHRError_.bind(this)),t.open("GET",this.src_),t.send()}}onXHRLoad_(t){const e=t.target;if(!e.status||e.status>=200&&e.status<300){let t;try{t=JSON.parse(e.responseText)}catch{return void this.handleError_()}this.handleLoad_(t)}else this.handleError_()}onXHRError_(t){this.handleError_()}load(){this.preemptive_?this.loadInternal_():this.setState(J)}}const Xf=34962,Vf=34963,$f=35044,Wf=35048,Zf=5126,Yf=["experimental-webgl","webgl","webkit-3d","moz-webgl"];function Hf(t,e){e=Object.assign({preserveDrawingBuffer:!0,antialias:!ot},e);const i=Yf.length;for(let n=0;n{this.uniforms_.push({value:t.uniforms[i],location:e.getUniformLocation(this.renderTargetProgram_,i)})}))}getRenderTargetTexture(){return this.renderTargetTexture_}getGL(){return this.gl_}init(t){const e=this.getGL(),i=[e.drawingBufferWidth*this.scaleRatio_,e.drawingBufferHeight*this.scaleRatio_];if(e.bindFramebuffer(e.FRAMEBUFFER,this.getFrameBuffer()),e.bindRenderbuffer(e.RENDERBUFFER,this.getDepthBuffer()),e.viewport(0,0,i[0],i[1]),!this.renderTargetTextureSize_||this.renderTargetTextureSize_[0]!==i[0]||this.renderTargetTextureSize_[1]!==i[1]){this.renderTargetTextureSize_=i;const t=0,n=e.RGBA,r=0,s=e.RGBA,o=e.UNSIGNED_BYTE,a=null;e.bindTexture(e.TEXTURE_2D,this.renderTargetTexture_),e.texImage2D(e.TEXTURE_2D,t,n,i[0],i[1],r,s,o,a),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,this.renderTargetTexture_,0),e.renderbufferStorage(e.RENDERBUFFER,e.DEPTH_COMPONENT16,i[0],i[1]),e.framebufferRenderbuffer(e.FRAMEBUFFER,e.DEPTH_ATTACHMENT,e.RENDERBUFFER,this.depthBuffer_)}}apply(t,e,i,n){const r=this.getGL(),s=t.size;if(r.bindFramebuffer(r.FRAMEBUFFER,e?e.getFrameBuffer():null),r.activeTexture(r.TEXTURE0),r.bindTexture(r.TEXTURE_2D,this.renderTargetTexture_),!e){const e=z(r.canvas);if(!t.renderTargets[e]){const i=r.getContextAttributes();i&&i.preserveDrawingBuffer&&(r.clearColor(0,0,0,0),r.clearDepth(1),r.clear(r.COLOR_BUFFER_BIT|r.DEPTH_BUFFER_BIT)),t.renderTargets[e]=!0}}r.disable(r.DEPTH_TEST),r.enable(r.BLEND),r.blendFunc(r.ONE,r.ONE_MINUS_SRC_ALPHA),r.viewport(0,0,r.drawingBufferWidth,r.drawingBufferHeight),r.bindBuffer(r.ARRAY_BUFFER,this.renderTargetVerticesBuffer_),r.useProgram(this.renderTargetProgram_),r.enableVertexAttribArray(this.renderTargetAttribLocation_),r.vertexAttribPointer(this.renderTargetAttribLocation_,2,r.FLOAT,!1,0,0),r.uniform2f(this.renderTargetUniformLocation_,s[0],s[1]),r.uniform1i(this.renderTargetTextureLocation_,0);const o=t.layerStatesArray[t.layerIndex].opacity;r.uniform1f(this.renderTargetOpacityLocation_,o),this.applyUniforms(t),i&&i(r,t),r.drawArrays(r.TRIANGLES,0,6),n&&n(r,t)}getFrameBuffer(){return this.frameBuffer_}getDepthBuffer(){return this.depthBuffer_}applyUniforms(t){const e=this.getGL();let i,n=1;this.uniforms_.forEach((function(r){if(i="function"==typeof r.value?r.value(t):r.value,i instanceof HTMLCanvasElement||i instanceof ImageData)r.texture||(r.texture=e.createTexture()),e.activeTexture(e[`TEXTURE${n}`]),e.bindTexture(e.TEXTURE_2D,r.texture),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),i instanceof ImageData?e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,i.width,i.height,0,e.UNSIGNED_BYTE,new Uint8Array(i.data)):e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,i),e.uniform1i(r.location,n++);else if(Array.isArray(i))switch(i.length){case 2:return void e.uniform2f(r.location,i[0],i[1]);case 3:return void e.uniform3f(r.location,i[0],i[1],i[2]);case 4:return void e.uniform4f(r.location,i[0],i[1],i[2],i[3]);default:return}else"number"==typeof i&&e.uniform1f(r.location,i)}))}}const Fp={PROJECTION_MATRIX:"u_projectionMatrix",SCREEN_TO_WORLD_MATRIX:"u_screenToWorldMatrix",TIME:"u_time",ZOOM:"u_zoom",RESOLUTION:"u_resolution",ROTATION:"u_rotation",VIEWPORT_SIZE_PX:"u_viewportSizePx",PIXEL_RATIO:"u_pixelRatio",HIT_DETECTION:"u_hitDetection"},Lp=5121,Mp=5123,Ap=5125,Op=Zf,Dp={};function Np(t){return"shared/"+t}let kp=0;class Gp extends m{constructor(t){super(),t=t||{},this.boundHandleWebGLContextLost_=this.handleWebGLContextLost.bind(this),this.boundHandleWebGLContextRestored_=this.handleWebGLContextRestored.bind(this),this.canvasCacheKey_=t.canvasCacheKey?Np(t.canvasCacheKey):function(){const t="unique/"+kp;return kp+=1,t}(),this.gl_=function(t){let e=Dp[t];if(!e){const i=document.createElement("canvas");i.width=1,i.height=1,i.style.position="absolute",i.style.left="0",e={users:0,context:Hf(i)},Dp[t]=e}return e.users+=1,e.context}(this.canvasCacheKey_),this.bufferCache_={},this.extensionCache_={},this.currentProgram_=null,this.needsToBeRecreated_=!1;const e=this.gl_.canvas;e.addEventListener(bp,this.boundHandleWebGLContextLost_),e.addEventListener(Pp,this.boundHandleWebGLContextRestored_),this.offsetRotateMatrix_=[1,0,0,1,0,0],this.offsetScaleMatrix_=[1,0,0,1,0,0],this.tmpMat4_=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],this.uniformLocationsByProgram_={},this.attribLocationsByProgram_={},this.uniforms_=[],t.uniforms&&this.setUniforms(t.uniforms),this.postProcessPasses_=t.postProcesses?t.postProcesses.map((t=>new Ip({webGlContext:this.gl_,scaleRatio:t.scaleRatio,vertexShader:t.vertexShader,fragmentShader:t.fragmentShader,uniforms:t.uniforms}))):[new Ip({webGlContext:this.gl_})],this.shaderCompileErrors_=null,this.startTime_=Date.now(),this.maxAttributeCount_=this.gl_.getParameter(this.gl_.MAX_VERTEX_ATTRIBS)}setUniforms(t){this.uniforms_=[],this.addUniforms(t)}addUniforms(t){for(const e in t)this.uniforms_.push({name:e,value:t[e]})}canvasCacheKeyMatches(t){return this.canvasCacheKey_===Np(t)}getExtension(t){if(t in this.extensionCache_)return this.extensionCache_[t];const e=this.gl_.getExtension(t);return this.extensionCache_[t]=e,e}bindBuffer(t){const e=this.gl_,i=z(t);let n=this.bufferCache_[i];if(!n){n={buffer:t,webGlBuffer:e.createBuffer()},this.bufferCache_[i]=n}e.bindBuffer(t.getType(),n.webGlBuffer)}flushBufferData(t){const e=this.gl_;this.bindBuffer(t),e.bufferData(t.getType(),t.getArray(),t.getUsage())}deleteBuffer(t){const e=z(t);delete this.bufferCache_[e]}disposeInternal(){const t=this.gl_.canvas;t.removeEventListener(bp,this.boundHandleWebGLContextLost_),t.removeEventListener(Pp,this.boundHandleWebGLContextRestored_),function(t){const e=Dp[t];if(!e)return;if(e.users-=1,e.users>0)return;const i=e.context,n=i.getExtension("WEBGL_lose_context");n&&n.loseContext();const r=i.canvas;r.width=1,r.height=1,delete Dp[t]}(this.canvasCacheKey_),delete this.gl_}prepareDraw(t,e,i){const n=this.gl_,r=this.getCanvas(),s=t.size,o=t.pixelRatio;r.width===s[0]*o&&r.height===s[1]*o||(r.width=s[0]*o,r.height=s[1]*o,r.style.width=s[0]+"px",r.style.height=s[1]+"px");for(let e=this.postProcessPasses_.length-1;e>=0;e--)this.postProcessPasses_[e].init(t);n.bindTexture(n.TEXTURE_2D,null),n.clearColor(0,0,0,0),n.depthRange(0,1),n.clearDepth(1),n.clear(n.COLOR_BUFFER_BIT|n.DEPTH_BUFFER_BIT),n.enable(n.BLEND),n.blendFunc(n.ONE,e?n.ZERO:n.ONE_MINUS_SRC_ALPHA),i?(n.enable(n.DEPTH_TEST),n.depthFunc(n.LEQUAL)):n.disable(n.DEPTH_TEST)}bindFrameBuffer(t,e){const i=this.getGL();i.bindFramebuffer(i.FRAMEBUFFER,t),e&&i.framebufferTexture2D(i.FRAMEBUFFER,i.COLOR_ATTACHMENT0,i.TEXTURE_2D,e,0)}bindInitialFrameBuffer(){const t=this.getGL(),e=this.postProcessPasses_[0].getFrameBuffer();t.bindFramebuffer(t.FRAMEBUFFER,e);const i=this.postProcessPasses_[0].getRenderTargetTexture();t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,i,0)}bindTexture(t,e,i){const n=this.gl_;n.activeTexture(n.TEXTURE0+e),n.bindTexture(n.TEXTURE_2D,t),n.uniform1i(this.getUniformLocation(i),e)}bindAttribute(t,e,i){const n=this.getGL();this.bindBuffer(t);const r=this.getAttributeLocation(e);n.enableVertexAttribArray(r),n.vertexAttribPointer(r,i,n.FLOAT,!1,0,0)}prepareDrawToRenderTarget(t,e,i,n){const r=this.gl_,s=e.getSize();r.bindFramebuffer(r.FRAMEBUFFER,e.getFramebuffer()),r.bindRenderbuffer(r.RENDERBUFFER,e.getDepthbuffer()),r.viewport(0,0,s[0],s[1]),r.bindTexture(r.TEXTURE_2D,e.getTexture()),r.clearColor(0,0,0,0),r.depthRange(0,1),r.clearDepth(1),r.clear(r.COLOR_BUFFER_BIT|r.DEPTH_BUFFER_BIT),r.enable(r.BLEND),r.blendFunc(r.ONE,i?r.ZERO:r.ONE_MINUS_SRC_ALPHA),n?(r.enable(r.DEPTH_TEST),r.depthFunc(r.LEQUAL)):r.disable(r.DEPTH_TEST)}drawElements(t,e){const i=this.gl_;this.getExtension("OES_element_index_uint");const n=i.UNSIGNED_INT,r=e-t,s=4*t;i.drawElements(i.TRIANGLES,r,n,s)}finalizeDraw(t,e,i){for(let n=0,r=this.postProcessPasses_.length;n{if(i="function"==typeof r.value?r.value(t):r.value,i instanceof HTMLCanvasElement||i instanceof HTMLImageElement||i instanceof ImageData||i instanceof WebGLTexture){i instanceof WebGLTexture&&!r.texture?(r.prevValue=void 0,r.texture=i):r.texture||(r.prevValue=void 0,r.texture=e.createTexture()),this.bindTexture(r.texture,n,r.name),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE);const t=!(i instanceof HTMLImageElement)||i.complete;i instanceof WebGLTexture||!t||r.prevValue===i||(r.prevValue=i,e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,i)),n++}else if(Array.isArray(i)&&6===i.length)this.setUniformMatrixValue(r.name,$d(this.tmpMat4_,i));else if(Array.isArray(i)&&i.length<=4)switch(i.length){case 2:return void e.uniform2f(this.getUniformLocation(r.name),i[0],i[1]);case 3:return void e.uniform3f(this.getUniformLocation(r.name),i[0],i[1],i[2]);case 4:return void e.uniform4f(this.getUniformLocation(r.name),i[0],i[1],i[2],i[3]);default:return}else"number"==typeof i&&e.uniform1f(this.getUniformLocation(r.name),i)}))}useProgram(t,e){this.disableAllAttributes_();this.gl_.useProgram(t),this.currentProgram_=t,e&&(this.applyFrameState(e),this.applyUniforms(e))}compileShader(t,e){const i=this.gl_,n=i.createShader(e);return i.shaderSource(n,t),i.compileShader(n),n}getProgram(t,e){const i=this.gl_,n=this.compileShader(t,i.FRAGMENT_SHADER),r=this.compileShader(e,i.VERTEX_SHADER),s=i.createProgram();if(i.attachShader(s,n),i.attachShader(s,r),i.linkProgram(s),!i.getShaderParameter(n,i.COMPILE_STATUS)){const t=`Fragment shader compilation failed: ${i.getShaderInfoLog(n)}`;throw new Error(t)}if(i.deleteShader(n),!i.getShaderParameter(r,i.COMPILE_STATUS)){const t=`Vertex shader compilation failed: ${i.getShaderInfoLog(r)}`;throw new Error(t)}if(i.deleteShader(r),!i.getProgramParameter(s,i.LINK_STATUS)){const t=`GL program linking failed: ${i.getProgramInfoLog(s)}`;throw new Error(t)}return s}getUniformLocation(t){const e=z(this.currentProgram_);return void 0===this.uniformLocationsByProgram_[e]&&(this.uniformLocationsByProgram_[e]={}),void 0===this.uniformLocationsByProgram_[e][t]&&(this.uniformLocationsByProgram_[e][t]=this.gl_.getUniformLocation(this.currentProgram_,t)),this.uniformLocationsByProgram_[e][t]}getAttributeLocation(t){const e=z(this.currentProgram_);return void 0===this.attribLocationsByProgram_[e]&&(this.attribLocationsByProgram_[e]={}),void 0===this.attribLocationsByProgram_[e][t]&&(this.attribLocationsByProgram_[e][t]=this.gl_.getAttribLocation(this.currentProgram_,t)),this.attribLocationsByProgram_[e][t]}makeProjectionTransform(t,e){const i=t.size,n=t.viewState.rotation,r=t.viewState.resolution,s=t.viewState.center;return tr(e,0,0,2/(r*i[0]),2/(r*i[1]),-n,-s[0],-s[1]),e}setUniformFloatValue(t,e){this.gl_.uniform1f(this.getUniformLocation(t),e)}setUniformFloatVec2(t,e){this.gl_.uniform2fv(this.getUniformLocation(t),e)}setUniformFloatVec4(t,e){this.gl_.uniform4fv(this.getUniformLocation(t),e)}setUniformMatrixValue(t,e){this.gl_.uniformMatrix4fv(this.getUniformLocation(t),!1,e)}disableAllAttributes_(){for(let t=0;tthis.size_[0]||e>=this.size_[1])return zp[0]=0,zp[1]=0,zp[2]=0,zp[3]=0,zp;this.readAll();const i=Math.floor(t)+(this.size_[1]-Math.floor(e)-1)*this.size_[0];return zp[0]=this.data_[4*i],zp[1]=this.data_[4*i+1],zp[2]=this.data_[4*i+2],zp[3]=this.data_[4*i+3],zp}getTexture(){return this.texture_}getFramebuffer(){return this.framebuffer_}getDepthbuffer(){return this.depthbuffer_}updateSize_(){const t=this.size_,e=this.helper_.getGL();this.texture_=this.helper_.createTexture(t,null,this.texture_),e.bindFramebuffer(e.FRAMEBUFFER,this.framebuffer_),e.viewport(0,0,t[0],t[1]),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,this.texture_,0),e.bindRenderbuffer(e.RENDERBUFFER,this.depthbuffer_),e.renderbufferStorage(e.RENDERBUFFER,e.DEPTH_COMPONENT16,t[0],t[1]),e.framebufferRenderbuffer(e.FRAMEBUFFER,e.DEPTH_ATTACHMENT,e.RENDERBUFFER,this.depthbuffer_),this.data_=new Uint8Array(t[0]*t[1]*4)}}class Vp{constructor(){this.globalCounter_=0,this.refToFeature_=new Map,this.uidToRef_=new Map,this.freeGlobalRef_=[],this.polygonBatch={entries:{},geometriesCount:0,verticesCount:0,ringsCount:0},this.pointBatch={entries:{},geometriesCount:0},this.lineStringBatch={entries:{},geometriesCount:0,verticesCount:0}}addFeatures(t,e){for(let i=0;i0?t[i-1]:null,h=l?l[l.length-1]:0,c=a[a.length-1];a=h>0?a.map((t=>t-h)):a,this.addCoordinates_("Polygon",e.slice(h,c),a,n,r,s,o)}break}case"MultiLineString":{const t=i;for(let i=0,a=t.length;i0?t[i-1]:0;this.addCoordinates_("LineString",e.slice(a,t[i]),null,n,r,s,o)}break}case"MultiPoint":for(let t=0,i=e.length;t1)return void this.addCoordinates_("MultiPolygon",e,i,n,r,s,o)}this.polygonBatch.entries[r]||(this.polygonBatch.entries[r]=this.addRefToEntry_(r,{feature:n,flatCoordss:[],verticesCount:0,ringsCount:0,ringsVerticesCounts:[]})),a=e.length/s;const l=i.length,h=i.map(((t,e,i)=>e>0?(t-i[e-1])/s:t/s));this.polygonBatch.verticesCount+=a,this.polygonBatch.ringsCount+=l,this.polygonBatch.geometriesCount++,this.polygonBatch.entries[r].flatCoordss.push(function(t,e){if(2===e)return t;return t.filter(((t,i)=>i%e<2))}(e,s)),this.polygonBatch.entries[r].ringsVerticesCounts.push(h),this.polygonBatch.entries[r].verticesCount+=a,this.polygonBatch.entries[r].ringsCount+=l;for(let i=0,a=t.length;i0?t[i-1]:0;this.addCoordinates_("LinearRing",e.slice(a,t[i]),null,n,r,s,o)}break}case"Point":this.pointBatch.entries[r]||(this.pointBatch.entries[r]=this.addRefToEntry_(r,{feature:n,flatCoordss:[]})),this.pointBatch.geometriesCount++,this.pointBatch.entries[r].flatCoordss.push(e);break;case"LineString":case"LinearRing":this.lineStringBatch.entries[r]||(this.lineStringBatch.entries[r]=this.addRefToEntry_(r,{feature:n,flatCoordss:[],verticesCount:0})),a=e.length/s,this.lineStringBatch.verticesCount+=a,this.lineStringBatch.geometriesCount++,this.lineStringBatch.entries[r].flatCoordss.push(function(t,e,i){if(3===e&&"XYM"===i)return t;if(4===e)return t.filter(((t,i)=>i%e!=2));if(3===e)return t.map(((t,i)=>i%e!=2?t:0));return new Array(1.5*t.length).fill(0).map(((e,i)=>i%3==2?0:t[Math.round(i/1.5)]))}(e,s,o)),this.lineStringBatch.entries[r].verticesCount+=a}}addRefToEntry_(t,e){const i=this.uidToRef_.get(t),n=i||this.freeGlobalRef_.pop()||++this.globalCounter_;return e.ref=n,i||(this.refToFeature_.set(n,e.feature),this.uidToRef_.set(t,n)),e}removeRef_(t,e){if(!t)throw new Error("This feature has no ref: "+e);this.refToFeature_.delete(t),this.uidToRef_.delete(e),this.freeGlobalRef_.push(t)}changeFeature(t,e){if(!this.uidToRef_.get(z(t)))return;this.removeFeature(t);let i=t.getGeometry();i&&(e&&(i=i.clone(),i.applyTransform(e)),this.addGeometry_(i,t))}removeFeature(t){let e=this.clearFeatureEntryInPointBatch_(t);e=this.clearFeatureEntryInPolygonBatch_(t)||e,e=this.clearFeatureEntryInLineStringBatch_(t)||e,e&&this.removeRef_(e.ref,z(e.feature))}clear(){this.polygonBatch.entries={},this.polygonBatch.geometriesCount=0,this.polygonBatch.verticesCount=0,this.polygonBatch.ringsCount=0,this.lineStringBatch.entries={},this.lineStringBatch.geometriesCount=0,this.lineStringBatch.verticesCount=0,this.pointBatch.entries={},this.pointBatch.geometriesCount=0,this.globalCounter_=0,this.freeGlobalRef_=[],this.refToFeature_.clear(),this.uidToRef_.clear()}getFeatureFromRef(t){return this.refToFeature_.get(t)}isEmpty(){return 0===this.globalCounter_}filter(t){const e=new Vp;e.globalCounter_=this.globalCounter_,e.uidToRef_=this.uidToRef_,e.refToFeature_=this.refToFeature_;let i=!0;for(const n of this.refToFeature_.values())t(n)&&(e.addFeature(n),i=!1);return i?new Vp:e}}class $p extends wp{constructor(t,e){super(t),this.batch_=new Vp,this.styleRenderers_=e,this.buffers=[],this.maskVertices=new Cp(Xf,$f),this.setTile(t.tile)}generateMaskBuffer_(){const t=this.tile.getSourceTiles()[0].extent;this.maskVertices.fromArray([t[0],t[1],t[2],t[1],t[2],t[3],t[0],t[3]]),this.helper.flushBufferData(this.maskVertices)}uploadTile(){this.generateMaskBuffer_(),this.batch_.clear();const t=this.tile.getSourceTiles(),e=t.reduce(((t,e)=>t.concat(e.getFeatures())),[]);this.batch_.addFeatures(e);const i=Qn([1,0,0,1,0,0],-t[0].extent[0],-t[0].extent[1]),n=this.styleRenderers_.map(((t,e)=>t.generateBuffers(this.batch_,i).then((t=>{this.buffers[e]=t}))));Promise.all(n).then((()=>{this.setReady()}))}disposeInternal(){this.buffers.forEach((t=>{this.disposeBuffers(t)})),super.disposeInternal()}disposeBuffers(t){if(!t)return;const e=t=>{for(const e of t)e&&this.helper.deleteBuffer(e)};t.pointBuffers&&e(t.pointBuffers),t.lineStringBuffers&&e(t.lineStringBuffers),t.polygonBuffers&&e(t.polygonBuffers)}}function Wp(t,e,i){const n=i?t.LINEAR:t.NEAREST;t.bindTexture(t.TEXTURE_2D,e),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,n),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,n)}function Zp(t,e,i,n,r,s){const o=t.getGL();let a,l;if(i instanceof Float32Array){a=o.FLOAT,t.getExtension("OES_texture_float");l=null!==t.getExtension("OES_texture_float_linear")}else a=o.UNSIGNED_BYTE,l=!0;Wp(o,e,s&&l);const h=i.byteLength/n[1];let c,u=1;switch(h%8==0?u=8:h%4==0?u=4:h%2==0&&(u=2),r){case 1:c=o.LUMINANCE;break;case 2:c=o.LUMINANCE_ALPHA;break;case 3:c=o.RGB;break;case 4:c=o.RGBA;break;default:throw new Error(`Unsupported number of bands: ${r}`)}const d=o.getParameter(o.UNPACK_ALIGNMENT);o.pixelStorei(o.UNPACK_ALIGNMENT,u),o.texImage2D(o.TEXTURE_2D,0,c,n[0],n[1],0,c,a,i),o.pixelStorei(o.UNPACK_ALIGNMENT,d)}let Yp=null;class Hp extends wp{constructor(t){super(t),this.textures=[],this.renderSize_=Bl(t.grid.getTileSize(t.tile.tileCoord[0])),this.bandCount=NaN;const e=new Cp(Xf,$f);e.fromArray([0,1,1,1,1,0,0,0]),this.helper.flushBufferData(e),this.coords=e,this.setTile(t.tile)}setHelper(t){const e=this.helper?.getGL();if(e){this.helper.deleteBuffer(this.coords);for(let t=0;t{this.clearCache(),this.removeHelper()},t.addChangeListener(za,this.onMapChanged_),this.dispatchPreComposeEvent=this.dispatchPreComposeEvent.bind(this),this.dispatchPostComposeEvent=this.dispatchPostComposeEvent.bind(this)}dispatchPreComposeEvent(t,e){const i=this.getLayer();if(i.hasListener(Ha)){const n=new kc(Ha,void 0,e,t);i.dispatchEvent(n)}}dispatchPostComposeEvent(t,e){const i=this.getLayer();if(i.hasListener(Ka)){const n=new kc(Ka,void 0,e,t);i.dispatchEvent(n)}}reset(t){this.uniforms_=t.uniforms,this.helper&&this.helper.setUniforms(this.uniforms_)}removeHelper(){this.helper&&(this.helper.dispose(),delete this.helper)}prepareFrame(t){if(this.getLayer().getRenderSource()){let e,i=!0,n=-1;for(let r=0,s=t.layerStatesArray.length;r=f;--r){const i=l.getTileRangeForExtentAndZ(e,r,this.tempTileRange_),o=l.getResolution(r);for(let e=i.minX;e<=i.maxX;++e)for(let g=i.minY;g<=i.maxY;++g){if(p&&!l.tileCoordIntersectsViewport([r,e,g],m))continue;const i=Iu(r,e,g,this.tempTileCoord_),f=lm(a,i);let _,y;if(d.containsKey(f)&&(_=d.get(f),y=_.tile),!(_&&_.tile.key===a.getKey()||(y=a.getTile(r,e,g,t.pixelRatio,s.projection),y)))continue;if(sm(n,y))continue;_?_.setTile(y):(_=this.createTileRepresentation({tile:y,grid:l,helper:this.helper,gutter:h}),d.set(f,_)),om(n,_,r);const x=y.getKey();u[x]=!0,y.getState()===Y&&(t.tileQueue.isKeyQueued(x)||t.tileQueue.enqueue([y,c,l.getTileCoordCenter(i),o]))}}}beforeTilesRender(t,e){this.helper.prepareDraw(this.frameState,!e,!0)}beforeTilesMaskRender(t){return!1}renderTile(t,e,i,n,r,s,o,a,l,h,c){}renderTileMask(t,e,i,n){}drawTile_(t,e,i,n,r,s,o){if(!e.ready)return;const a=e.tile.tileCoord,l=Lu(a),h=l in s?s[l]:1,c=o.getResolution(i),u=Bl(o.getTileSize(i),this.tempSize_),d=o.getOrigin(i),g=o.getTileCoordExtent(a),f=h<1?-1:nm(i);h<1&&(t.animate=!0);const p=t.viewState,m=p.center[0],_=p.center[1],y=u[0]+2*n,x=u[1]+2*n,v=y/x,S=(m-d[0])/(u[0]*c),E=(d[1]-_)/(u[1]*c),w=p.resolution/c,T=a[1],C=a[2];Wn(this.tileTransform_),Jn(this.tileTransform_,2/(t.size[0]*w/y),-2/(t.size[1]*w/y)),qn(this.tileTransform_,p.rotation),Jn(this.tileTransform_,1,1/v),Qn(this.tileTransform_,(u[0]*(T-S)-n)/y,(u[1]*(C-E)-n)/x),this.renderTile(e,this.tileTransform_,t,r,c,u,d,g,f,n,h)}renderFrame(t){this.frameState=t,this.renderComplete=!0;const e=this.helper.getGL();this.preRender(e,t);const i=t.viewState,n=this.getLayer(),r=n.getRenderSource(),s=r.getTileGridForProjection(i.projection),o=r.getGutterForProjection(i.projection),a=am(t,t.extent),l=s.getZForResolution(i.resolution,r.zDirection),h=rm(),c=n.getPreload();if(t.nextExtent){const e=s.getZForResolution(i.nextResolution,r.zDirection),n=am(t,t.nextExtent);this.enqueueTiles(t,n,e,h,c)}this.enqueueTiles(t,a,l,h,0),c>0&&setTimeout((()=>{this.enqueueTiles(t,a,l-1,h,c-1)}),0);const u={};let d=!1;const g=h.representationsByZ;if(l in g){const e=z(this),i=t.time;for(const t of g[l]){const n=t.tile;if(n.getState()===J)continue;const r=n.tileCoord;if(t.ready){const t=n.getAlpha(e,i);if(1===t){n.endTransition(e);continue}d=!0;u[Lu(r)]=t}this.renderComplete=!1;if(this.findAltTiles_(s,r,l+1,h))continue;const o=s.getMinZoom();for(let t=l-1;t>=o;--t){if(this.findAltTiles_(s,r,t,h))break}}}const f=Object.keys(g).map(Number).sort(x);if(this.beforeTilesMaskRender(t))for(let t=0,e=f.length;tt.dispose())),t.clear()}afterHelperCreated(){super.afterHelperCreated(),this.tileRepresentationCache.forEach((t=>t.setHelper(this.helper)))}disposeInternal(){super.disposeInternal(),delete this.frameState}}const cm={...im,TILE_TEXTURE_ARRAY:"u_tileTextures",TEXTURE_PIXEL_WIDTH:"u_texturePixelWidth",TEXTURE_PIXEL_HEIGHT:"u_texturePixelHeight",TEXTURE_RESOLUTION:"u_textureResolution",TEXTURE_ORIGIN_X:"u_textureOriginX",TEXTURE_ORIGIN_Y:"u_textureOriginY"},um={TEXTURE_COORD:"a_textureCoord"},dm=[{name:um.TEXTURE_COORD,size:2,type:Op}];class gm extends hm{constructor(t,e){super(t,e),this.program_,this.vertexShader_=e.vertexShader,this.fragmentShader_=e.fragmentShader,this.indices_=new Cp(Vf,$f),this.indices_.fromArray([0,1,3,1,2,3]),this.paletteTextures_=e.paletteTextures||[]}reset(t){if(super.reset(t),this.helper){const t=this.helper.getGL();for(const e of this.paletteTextures_)e.delete(t)}if(this.vertexShader_=t.vertexShader,this.fragmentShader_=t.fragmentShader,this.paletteTextures_=t.paletteTextures||[],this.helper){this.program_=this.helper.getProgram(this.fragmentShader_,this.vertexShader_);const t=this.helper.getGL();for(const e of this.paletteTextures_)e.getTexture(t)}}afterHelperCreated(){super.afterHelperCreated();const t=this.helper.getGL();for(const e of this.paletteTextures_)e.getTexture(t);this.program_=this.helper.getProgram(this.fragmentShader_,this.vertexShader_),this.helper.flushBufferData(this.indices_)}removeHelper(){if(this.helper){const t=this.helper.getGL();for(const e of this.paletteTextures_)e.delete(t)}super.removeHelper()}createTileRepresentation(t){return new Hp(t)}beforeTilesRender(t,e){super.beforeTilesRender(t,e),this.helper.useProgram(this.program_,t)}renderTile(t,e,i,n,r,s,o,a,l,h,c){const u=this.helper.getGL();this.helper.bindBuffer(t.coords),this.helper.bindBuffer(this.indices_),this.helper.enableAttributes(dm);let d=0;for(;d0&&(x=a,pe(x,n,x)),this.helper.setUniformFloatVec4(cm.RENDER_EXTENT,x),this.helper.setUniformFloatValue(cm.RESOLUTION,g.resolution),this.helper.setUniformFloatValue(cm.ZOOM,g.zoom),this.helper.setUniformFloatValue(cm.TEXTURE_PIXEL_WIDTH,f),this.helper.setUniformFloatValue(cm.TEXTURE_PIXEL_HEIGHT,p),this.helper.setUniformFloatValue(cm.TEXTURE_RESOLUTION,r),this.helper.setUniformFloatValue(cm.TEXTURE_ORIGIN_X,o[0]+_*s[0]*r-h*r),this.helper.setUniformFloatValue(cm.TEXTURE_ORIGIN_Y,o[1]-y*s[1]*r+h*r),this.helper.drawElements(0,this.indices_.getSize())}getData(t){if(!this.helper.getGL())return null;const e=this.frameState;if(!e)return null;const i=this.getLayer(),n=Kn(e.pixelToCoordinateTransform,t.slice()),r=e.viewState,s=i.getExtent();if(s&&!Xt(jn(s,r.projection),n))return null;const o=i.getSources(jt([n]),r.resolution);let a,l,h;for(a=o.length-1;a>=0;--a)if(l=o[a],"ready"===l.getState()){if(h=l.getTileGridForProjection(r.projection),l.getWrapX())break;const t=h.getExtent();if(!t||Xt(t,n))break}if(a<0)return null;const c=this.tileRepresentationCache;for(let t=h.getZForResolution(r.resolution);t>=h.getMinZoom();--t){const e=h.getTileCoordForCoordAndZ(n,t),i=lm(l,e);if(!c.containsKey(i))continue;const r=c.get(i);if(r.tile.getState()===J)return null;if(!r.loaded)continue;const s=h.getOrigin(t),o=Bl(h.getTileSize(t)),a=h.getResolution(t),u=(n[0]-s[0])/a-e[1]*o[0],d=(s[1]-n[1])/a-e[2]*o[1];return r.getPixelData(u,d)}return null}disposeInternal(){const t=this.helper;if(t){const e=t.getGL();for(const t of this.paletteTextures_)t.delete(e);this.paletteTextures_.length=0,e.deleteProgram(this.program_),delete this.program_,t.deleteBuffer(this.indices_)}super.disposeInternal(),delete this.indices_}}const fm={TEXTURE:"u_texture",VELOCITY_TEXTURE:"u_velocityTexture",POSITION_TEXTURE:"u_positionTexture",PARTICLE_COUNT_SQRT:"u_particleCountSqrt",MAX_SPEED:"u_maxSpeed",RANDOM_SEED:"u_randomSeed",SPEED_FACTOR:"u_speedFactor",DROP_RATE:"u_dropRate",DROP_RATE_BUMP:"u_dropRateBump",OPACITY:"u_opacity",ROTATION:Fp.ROTATION,VIEWPORT_SIZE_PX:Fp.VIEWPORT_SIZE_PX},pm="a_position",mm="a_index",_m="v_position";class ym extends gm{constructor(t,e){super(t,{vertexShader:e.tileVertexShader,fragmentShader:e.tileFragmentShader,cacheSize:e.cacheSize,postProcesses:[{}],uniforms:{[fm.MAX_SPEED]:e.maxSpeed}}),this.particleColorFragmentShader_=e.particleColorFragmentShader,this.velocityTexture_=null,this.particleCountSqrt_=e.particles?Math.ceil(Math.sqrt(e.particles)):256,this.particleIndexBuffer_,this.quadBuffer_,this.particlePositionProgram_,this.particlePositionVertexShader_=e.particlePositionVertexShader,this.particlePositionFragmentShader_=e.particlePositionFragmentShader,this.previousPositionTexture_,this.nextPositionTexture_,this.particleColorProgram_,this.particleColorVertexShader_=e.particleColorVertexShader,this.particleColorFragmentShader_=e.particleColorFragmentShader,this.textureProgram_,this.textureVertexShader_=e.textureVertexShader,this.textureFragmentShader_=e.textureFragmentShader,this.previousTrailsTexture_,this.nextTrailsTexture_,this.fadeOpacity_=.996,this.maxSpeed_=e.maxSpeed,this.speedFactor_=e.speedFactor||.001,this.dropRate_=.003,this.dropRateBump_=.01,this.tempVec2_=[0,0],this.renderedWidth_=0,this.renderedHeight_=0}afterHelperCreated(){super.afterHelperCreated();const t=this.helper,e=t.getGL();this.framebuffer_=e.createFramebuffer();const i=this.particleCountSqrt_*this.particleCountSqrt_,n=new Float32Array(i);for(let t=0;t{const e=t.data;if(e.type===vm){const i=e.projectionTransform;this.verticesBuffer_.fromArrayBuffer(e.vertexBuffer),this.helper.flushBufferData(this.verticesBuffer_),this.indicesBuffer_.fromArrayBuffer(e.indexBuffer),this.helper.flushBufferData(this.indicesBuffer_),this.renderTransform_=i,er(this.invertRenderTransform_,this.renderTransform_),this.renderInstructions_=new Float32Array(t.data.renderInstructions),e.id===this.lastSentId&&(this.ready=!0),this.getLayer().changed()}})),this.featureCache_={},this.featureCount_=0;const s=this.getLayer().getSource();this.sourceListenKeys_=[D(s,Dd,this.handleSourceFeatureAdded_,this),D(s,Nd,this.handleSourceFeatureChanged_,this),D(s,Gd,this.handleSourceFeatureDelete_,this),D(s,kd,this.handleSourceFeatureClear_,this)],s.forEachFeature((t=>{const e=t.getGeometry();e&&"Point"===e.getType()&&(this.featureCache_[z(t)]={feature:t,properties:t.getProperties(),flatCoordinates:e.getFlatCoordinates()},this.featureCount_++)}))}afterHelperCreated(){this.program_=this.helper.getProgram(this.fragmentShader_,this.vertexShader_),this.hitDetectionEnabled_&&(this.hitRenderTarget_=new Xp(this.helper)),this.verticesBuffer_.getArray()&&this.helper.flushBufferData(this.verticesBuffer_),this.indicesBuffer_.getArray()&&this.helper.flushBufferData(this.indicesBuffer_)}handleSourceFeatureAdded_(t){const e=t.feature,i=e.getGeometry();i&&"Point"===i.getType()&&(this.featureCache_[z(e)]={feature:e,properties:e.getProperties(),flatCoordinates:i.getFlatCoordinates()},this.featureCount_++)}handleSourceFeatureChanged_(t){const e=t.feature,i=z(e),n=this.featureCache_[i],r=e.getGeometry();n?r&&"Point"===r.getType()?(n.properties=e.getProperties(),n.flatCoordinates=r.getFlatCoordinates()):(delete this.featureCache_[i],this.featureCount_--):r&&"Point"===r.getType()&&(this.featureCache_[i]={feature:e,properties:e.getProperties(),flatCoordinates:r.getFlatCoordinates()},this.featureCount_++)}handleSourceFeatureDelete_(t){const e=z(t.feature);e in this.featureCache_&&(delete this.featureCache_[e],this.featureCount_--)}handleSourceFeatureClear_(){this.featureCache_={},this.featureCount_=0}renderFrame(t){const e=this.helper.getGL();this.preRender(e,t);const[i,n,r]=Cm(t,this.getLayer());this.renderWorlds(t,!1,i,n,r),this.helper.finalizeDraw(t,this.dispatchPreComposeEvent,this.dispatchPostComposeEvent),this.hitDetectionEnabled_&&(this.renderWorlds(t,!0,i,n,r),this.hitRenderTarget_.clearCachedData()),this.postRender(e,t);return this.helper.getCanvas()}prepareFrameInternal(t){const e=this.getLayer(),i=e.getSource(),n=t.viewState,r=!t.viewHints[ao]&&!t.viewHints[lo],s=!Jt(this.previousExtent_,t.extent),o=this.sourceRevision_4)throw new Error("`formatArray` can only output `vec2`, `vec3` or `vec4` arrays.");return`vec${t.length}(${t.map(bm).join(", ")})`}function Im(t){const e=kl(t),i=e.length>3?e[3]:1;return Pm([e[0]/255,e[1]/255,e[2]/255,i])}function Fm(t){return Pm(Bl(t))}const Lm={};let Mm=0;function Am(t){return t in Lm||(Lm[t]=Mm++),Lm[t]}function Om(t){return bm(Am(t))}function Dm(t){return"u_var_"+t}function Nm(){return{variables:{},properties:{},functions:{},bandCount:0,featureId:!1,geometryType:!1}}const km="getBandValue",Gm="u_paletteTextures",jm="featureId",Um="geometryType",Bm=-9999999;function zm(t,e,i,n){return $m(rh(t,e,i),e,n)}function Xm(t){return(e,i,n)=>{const r=i.args.length,s=new Array(r);for(let t=0;t{const i=e.args[0].value;return i in t.properties||(t.properties[i]={name:i,type:e.type}),"a_prop_"+i},[sh.Id]:t=>(t.featureId=!0,"a_"+jm),[sh.GeometryType]:t=>(t.geometryType=!0,"a_"+Um),[sh.LineMetric]:()=>"currentLineMetric",[sh.Var]:(t,e)=>{const i=e.args[0].value;return i in t.variables||(t.variables[i]={name:i,type:e.type}),Dm(i)},[sh.Has]:(t,e)=>{const i=e.args[0].value;return i in t.properties||(t.properties[i]={name:i,type:e.type}),`(a_prop_${i} != ${bm(Bm)})`},[sh.Resolution]:()=>"u_resolution",[sh.Zoom]:()=>"u_zoom",[sh.Time]:()=>"u_time",[sh.Any]:Xm((t=>`(${t.join(" || ")})`)),[sh.All]:Xm((t=>`(${t.join(" && ")})`)),[sh.Not]:Xm((([t])=>`(!${t})`)),[sh.Equal]:Xm((([t,e])=>`(${t} == ${e})`)),[sh.NotEqual]:Xm((([t,e])=>`(${t} != ${e})`)),[sh.GreaterThan]:Xm((([t,e])=>`(${t} > ${e})`)),[sh.GreaterThanOrEqualTo]:Xm((([t,e])=>`(${t} >= ${e})`)),[sh.LessThan]:Xm((([t,e])=>`(${t} < ${e})`)),[sh.LessThanOrEqualTo]:Xm((([t,e])=>`(${t} <= ${e})`)),[sh.Multiply]:Xm((t=>`(${t.join(" * ")})`)),[sh.Divide]:Xm((([t,e])=>`(${t} / ${e})`)),[sh.Add]:Xm((t=>`(${t.join(" + ")})`)),[sh.Subtract]:Xm((([t,e])=>`(${t} - ${e})`)),[sh.Clamp]:Xm((([t,e,i])=>`clamp(${t}, ${e}, ${i})`)),[sh.Mod]:Xm((([t,e])=>`mod(${t}, ${e})`)),[sh.Pow]:Xm((([t,e])=>`pow(${t}, ${e})`)),[sh.Abs]:Xm((([t])=>`abs(${t})`)),[sh.Floor]:Xm((([t])=>`floor(${t})`)),[sh.Ceil]:Xm((([t])=>`ceil(${t})`)),[sh.Round]:Xm((([t])=>`floor(${t} + 0.5)`)),[sh.Sin]:Xm((([t])=>`sin(${t})`)),[sh.Cos]:Xm((([t])=>`cos(${t})`)),[sh.Atan]:Xm((([t,e])=>void 0!==e?`atan(${t}, ${e})`:`atan(${t})`)),[sh.Sqrt]:Xm((([t])=>`sqrt(${t})`)),[sh.Match]:Xm((t=>{const e=t[0],i=t[t.length-1];let n=null;for(let r=t.length-3;r>=1;r-=2){n=`(${e} == ${t[r]} ? ${t[r+1]} : ${n||i})`}return n})),[sh.Between]:Xm((([t,e,i])=>`(${t} >= ${e} && ${t} <= ${i})`)),[sh.Interpolate]:Xm((([t,e,...i])=>{let n="";for(let r=0;r{const e=t[t.length-1];let i=null;for(let n=t.length-3;n>=0;n-=2){i=`(${t[n]} ? ${t[n+1]} : ${i||e})`}return i})),[sh.In]:Xm((([t,...e],i)=>{const n=function(t,e){return`operator_${t}_${Object.keys(e.functions).length}`}("in",i),r=[];for(let t=0;t`vec${t.length}(${t.join(", ")})`)),[sh.Color]:Xm((t=>{if(1===t.length)return`vec4(vec3(${t[0]} / 255.0), 1.0)`;if(2===t.length)return`vec4(vec3(${t[0]} / 255.0), ${t[1]})`;const e=t.slice(0,3).map((t=>`${t} / 255.0`));if(3===t.length)return`vec4(${e.join(", ")}, 1.0)`;const i=t[3];return`vec4(${e.join(", ")}, ${i})`})),[sh.Band]:Xm((([t,e,i],n)=>{if(!(km in n.functions)){let t="";const e=n.bandCount||1;for(let i=0;i{const[i,...n]=e.args,r=n.length,s=new Uint8Array(4*r);for(let t=0;t0)return bm(t.value);if((t.type&Xl)>0)return t.value.toString();if((t.type&$l)>0)return Om(t.value.toString());if((t.type&Wl)>0)return Im(t.value);if((t.type&Zl)>0)return Pm(t.value);if((t.type&Yl)>0)return Fm(t.value);throw new Error(`Unexpected expression ${t.value} (expected type ${Jl(e)})`)}function Wm(t,e,i,n){let r=0;for(const s in e){const o=e[s],a=o.callback.call(i,i.feature);let l=a?.[0]??a;l===Bm&&console.warn('The "has" operator might return false positives.'),void 0===l?l=Bm:null===l&&(l=0),t[n+r++]=l,o.size&&1!==o.size&&(t[n+r++]=a[1],o.size<3||(t[n+r++]=a[2],o.size<4||(t[n+r++]=a[3])))}return r}function Zm(t){return Object.keys(t).reduce(((e,i)=>e+(t[i].size||1)),0)}function Ym(t,e,i,n){const r=(2+Zm(i))*t.geometriesCount;e&&e.length===r||(e=new Float32Array(r));const s=[];let o=0;for(const r in t.entries){const a=t.entries[r];for(let t=0,r=a.flatCoordss.length;t80*i){a=1/0,l=1/0;let e=-1/0,n=-1/0;for(let s=i;se&&(e=i),r>n&&(n=r)}h=Math.max(e-a,n-l),h=0!==h?32767/h:0}return t_(s,o,i,a,l,h,0),o}function Jm(t,e,i,n,r){let s;if(r===function(t,e,i,n){let r=0;for(let s=e,o=i-n;s0)for(let r=e;r=e;r-=n)s=v_(r/n|0,t[r],t[r+1],s);return s&&f_(s,s.next)&&(S_(s),s=s.next),s}function Qm(t,e){if(!t)return t;e||(e=t);let i,n=t;do{if(i=!1,n.steiner||!f_(n,n.next)&&0!==g_(n.prev,n,n.next))n=n.next;else{if(S_(n),n=e=n.prev,n===n.next)break;i=!0}}while(i||n!==e);return e}function t_(t,e,i,n,r,s,o){if(!t)return;!o&&s&&function(t,e,i,n){let r=t;do{0===r.z&&(r.z=l_(r.x,r.y,e,i,n)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,i=1;do{let n,r=t;t=null;let s=null;for(e=0;r;){e++;let o=r,a=0;for(let t=0;t0||l>0&&o;)0!==a&&(0===l||!o||r.z<=o.z)?(n=r,r=r.nextZ,a--):(n=o,o=o.nextZ,l--),s?s.nextZ=n:t=n,n.prevZ=s,s=n;r=o}s.nextZ=null,i*=2}while(e>1)}(r)}(t,n,r,s);let a=t;for(;t.prev!==t.next;){const l=t.prev,h=t.next;if(s?i_(t,n,r,s):e_(t))e.push(l.i,t.i,h.i),S_(t),t=h.next,a=h.next;else if((t=h)===a){o?1===o?t_(t=n_(Qm(t),e),e,i,n,r,s,2):2===o&&r_(t,e,i,n,r,s):t_(Qm(t),e,i,n,r,s,1);break}}}function e_(t){const e=t.prev,i=t,n=t.next;if(g_(e,i,n)>=0)return!1;const r=e.x,s=i.x,o=n.x,a=e.y,l=i.y,h=n.y,c=Math.min(r,s,o),u=Math.min(a,l,h),d=Math.max(r,s,o),g=Math.max(a,l,h);let f=n.next;for(;f!==e;){if(f.x>=c&&f.x<=d&&f.y>=u&&f.y<=g&&u_(r,a,s,l,o,h,f.x,f.y)&&g_(f.prev,f,f.next)>=0)return!1;f=f.next}return!0}function i_(t,e,i,n){const r=t.prev,s=t,o=t.next;if(g_(r,s,o)>=0)return!1;const a=r.x,l=s.x,h=o.x,c=r.y,u=s.y,d=o.y,g=Math.min(a,l,h),f=Math.min(c,u,d),p=Math.max(a,l,h),m=Math.max(c,u,d),_=l_(g,f,e,i,n),y=l_(p,m,e,i,n);let x=t.prevZ,v=t.nextZ;for(;x&&x.z>=_&&v&&v.z<=y;){if(x.x>=g&&x.x<=p&&x.y>=f&&x.y<=m&&x!==r&&x!==o&&u_(a,c,l,u,h,d,x.x,x.y)&&g_(x.prev,x,x.next)>=0)return!1;if(x=x.prevZ,v.x>=g&&v.x<=p&&v.y>=f&&v.y<=m&&v!==r&&v!==o&&u_(a,c,l,u,h,d,v.x,v.y)&&g_(v.prev,v,v.next)>=0)return!1;v=v.nextZ}for(;x&&x.z>=_;){if(x.x>=g&&x.x<=p&&x.y>=f&&x.y<=m&&x!==r&&x!==o&&u_(a,c,l,u,h,d,x.x,x.y)&&g_(x.prev,x,x.next)>=0)return!1;x=x.prevZ}for(;v&&v.z<=y;){if(v.x>=g&&v.x<=p&&v.y>=f&&v.y<=m&&v!==r&&v!==o&&u_(a,c,l,u,h,d,v.x,v.y)&&g_(v.prev,v,v.next)>=0)return!1;v=v.nextZ}return!0}function n_(t,e){let i=t;do{const n=i.prev,r=i.next.next;!f_(n,r)&&p_(n,i,i.next,r)&&y_(n,r)&&y_(r,n)&&(e.push(n.i,i.i,r.i),S_(i),S_(i.next),i=t=r),i=i.next}while(i!==t);return Qm(i)}function r_(t,e,i,n,r,s){let o=t;do{let t=o.next.next;for(;t!==o.prev;){if(o.i!==t.i&&d_(o,t)){let a=x_(o,t);return o=Qm(o,o.next),a=Qm(a,a.next),t_(o,e,i,n,r,s,0),void t_(a,e,i,n,r,s,0)}t=t.next}o=o.next}while(o!==t)}function s_(t,e){let i=t.x-e.x;if(0===i&&(i=t.y-e.y,0===i)){i=(t.next.y-t.y)/(t.next.x-t.x)-(e.next.y-e.y)/(e.next.x-e.x)}return i}function o_(t,e){const i=function(t,e){let i=e;const n=t.x,r=t.y;let s,o=-1/0;if(f_(t,i))return i;do{if(f_(t,i.next))return i.next;if(r<=i.y&&r>=i.next.y&&i.next.y!==i.y){const t=i.x+(r-i.y)*(i.next.x-i.x)/(i.next.y-i.y);if(t<=n&&t>o&&(o=t,s=i.x=i.x&&i.x>=l&&n!==i.x&&c_(rs.x||i.x===s.x&&a_(s,i)))&&(s=i,c=e)}i=i.next}while(i!==a);return s}(t,e);if(!i)return e;const n=x_(i,t);return Qm(n,n.next),Qm(i,i.next)}function a_(t,e){return g_(t.prev,t,e.prev)<0&&g_(e.next,t,t.next)<0}function l_(t,e,i,n,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-i)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-n)*r|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function h_(t){let e=t,i=t;do{(e.x=(t-o)*(s-a)&&(t-o)*(n-a)>=(i-o)*(e-a)&&(i-o)*(s-a)>=(r-o)*(n-a)}function u_(t,e,i,n,r,s,o,a){return!(t===o&&e===a)&&c_(t,e,i,n,r,s,o,a)}function d_(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let i=t;do{if(i.i!==t.i&&i.next.i!==t.i&&i.i!==e.i&&i.next.i!==e.i&&p_(i,i.next,t,e))return!0;i=i.next}while(i!==t);return!1}(t,e)&&(y_(t,e)&&y_(e,t)&&function(t,e){let i=t,n=!1;const r=(t.x+e.x)/2,s=(t.y+e.y)/2;do{i.y>s!=i.next.y>s&&i.next.y!==i.y&&r<(i.next.x-i.x)*(s-i.y)/(i.next.y-i.y)+i.x&&(n=!n),i=i.next}while(i!==t);return n}(t,e)&&(g_(t.prev,t,e.prev)||g_(t,e.prev,e))||f_(t,e)&&g_(t.prev,t,t.next)>0&&g_(e.prev,e,e.next)>0)}function g_(t,e,i){return(e.y-t.y)*(i.x-e.x)-(e.x-t.x)*(i.y-e.y)}function f_(t,e){return t.x===e.x&&t.y===e.y}function p_(t,e,i,n){const r=__(g_(t,e,i)),s=__(g_(t,e,n)),o=__(g_(i,n,t)),a=__(g_(i,n,e));return r!==s&&o!==a||(!(0!==r||!m_(t,i,e))||(!(0!==s||!m_(t,n,e))||(!(0!==o||!m_(i,t,n))||!(0!==a||!m_(i,e,n)))))}function m_(t,e,i){return e.x<=Math.max(t.x,i.x)&&e.x>=Math.min(t.x,i.x)&&e.y<=Math.max(t.y,i.y)&&e.y>=Math.min(t.y,i.y)}function __(t){return t>0?1:t<0?-1:0}function y_(t,e){return g_(t.prev,t,t.next)<0?g_(t,e,t.next)>=0&&g_(t,t.prev,e)>=0:g_(t,e,t.prev)<0||g_(t,t.next,e)<0}function x_(t,e){const i=E_(t.i,t.x,t.y),n=E_(e.i,e.x,e.y),r=t.next,s=e.prev;return t.next=e,e.prev=t,i.next=r,r.prev=i,n.next=i,i.prev=n,s.next=n,n.prev=s,n}function v_(t,e,i,n){const r=E_(t,e,i);return n?(r.next=n.next,r.prev=n,n.next.prev=r,n.next=r):(r.prev=r,r.next=r),r}function S_(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function E_(t,e,i){return{i:t,x:e,y:i,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}const w_=.985,T_=[],C_={vertexPosition:0,indexPosition:0};function R_(t,e,i,n,r){t[e+0]=i,t[e+1]=n,t[e+2]=r}const b_="#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_screenToWorldMatrix;\nuniform vec2 u_viewportSizePx;\nuniform float u_pixelRatio;\nuniform float u_globalAlpha;\nuniform float u_time;\nuniform float u_zoom;\nuniform float u_resolution;\nuniform float u_rotation;\nuniform vec4 u_renderExtent;\nuniform vec2 u_patternOrigin;\nuniform float u_depth;\nuniform mediump int u_hitDetection;\n\nconst float PI = 3.141592653589793238;\nconst float TWO_PI = 2.0 * PI;\nfloat currentLineMetric = 0.; // an actual value will be used in the stroke shaders\n",P_={"fill-color":"rgba(255,255,255,0.4)","stroke-color":"#3399CC","stroke-width":1.25,"circle-radius":5,"circle-fill-color":"rgba(255,255,255,0.4)","circle-stroke-width":1.25,"circle-stroke-color":"#3399CC"};class I_{constructor(){this.uniforms_=[],this.attributes_=[],this.hasSymbol_=!1,this.symbolSizeExpression_=`vec2(${bm(P_["circle-radius"])} + ${bm(.5*P_["circle-stroke-width"])})`,this.symbolRotationExpression_="0.0",this.symbolOffsetExpression_="vec2(0.0)",this.symbolColorExpression_=Im(P_["circle-fill-color"]),this.texCoordExpression_="vec4(0.0, 0.0, 1.0, 1.0)",this.discardExpression_="false",this.symbolRotateWithView_=!1,this.hasStroke_=!1,this.strokeWidthExpression_=bm(P_["stroke-width"]),this.strokeColorExpression_=Im(P_["stroke-color"]),this.strokeOffsetExpression_="0.",this.strokeCapExpression_=Om("round"),this.strokeJoinExpression_=Om("round"),this.strokeMiterLimitExpression_="10.",this.strokeDistanceFieldExpression_="-1000.",this.hasFill_=!1,this.fillColorExpression_=Im(P_["fill-color"]),this.vertexShaderFunctions_=[],this.fragmentShaderFunctions_=[]}addUniform(t,e){return this.uniforms_.push({name:t,type:e}),this}addAttribute(t,e,i,n){return this.attributes_.push({name:t,type:e,varyingName:t.replace(/^a_/,"v_"),varyingType:n??e,varyingExpression:i??t}),this}setSymbolSizeExpression(t){return this.hasSymbol_=!0,this.symbolSizeExpression_=t,this}getSymbolSizeExpression(){return this.symbolSizeExpression_}setSymbolRotationExpression(t){return this.symbolRotationExpression_=t,this}setSymbolOffsetExpression(t){return this.symbolOffsetExpression_=t,this}getSymbolOffsetExpression(){return this.symbolOffsetExpression_}setSymbolColorExpression(t){return this.hasSymbol_=!0,this.symbolColorExpression_=t,this}getSymbolColorExpression(){return this.symbolColorExpression_}setTextureCoordinateExpression(t){return this.texCoordExpression_=t,this}setFragmentDiscardExpression(t){return this.discardExpression_=t,this}getFragmentDiscardExpression(){return this.discardExpression_}setSymbolRotateWithView(t){return this.symbolRotateWithView_=t,this}setStrokeWidthExpression(t){return this.hasStroke_=!0,this.strokeWidthExpression_=t,this}setStrokeColorExpression(t){return this.hasStroke_=!0,this.strokeColorExpression_=t,this}getStrokeColorExpression(){return this.strokeColorExpression_}setStrokeOffsetExpression(t){return this.strokeOffsetExpression_=t,this}setStrokeCapExpression(t){return this.strokeCapExpression_=t,this}setStrokeJoinExpression(t){return this.strokeJoinExpression_=t,this}setStrokeMiterLimitExpression(t){return this.strokeMiterLimitExpression_=t,this}setStrokeDistanceFieldExpression(t){return this.strokeDistanceFieldExpression_=t,this}setFillColorExpression(t){return this.hasFill_=!0,this.fillColorExpression_=t,this}getFillColorExpression(){return this.fillColorExpression_}addVertexShaderFunction(t){return this.vertexShaderFunctions_.includes(t)||this.vertexShaderFunctions_.push(t),this}addFragmentShaderFunction(t){return this.fragmentShaderFunctions_.includes(t)||this.fragmentShaderFunctions_.push(t),this}getSymbolVertexShader(){return this.hasSymbol_?`${b_}\n${this.uniforms_.map((t=>`uniform ${t.type} ${t.name};`)).join("\n")}\nattribute vec2 a_position;\nattribute float a_index;\nattribute vec4 a_hitColor;\n\nvarying vec2 v_texCoord;\nvarying vec2 v_quadCoord;\nvarying vec4 v_hitColor;\nvarying vec2 v_centerPx;\nvarying float v_angle;\nvarying vec2 v_quadSizePx;\n\n${this.attributes_.map((t=>`attribute ${t.type} ${t.name};\nvarying ${t.varyingType} ${t.varyingName};`)).join("\n")}\n${this.vertexShaderFunctions_.join("\n")}\nvec2 pxToScreen(vec2 coordPx) {\n vec2 scaled = coordPx / u_viewportSizePx / 0.5;\n return scaled;\n}\n\nvec2 screenToPx(vec2 coordScreen) {\n return (coordScreen * 0.5 + 0.5) * u_viewportSizePx;\n}\n\nvoid main(void) {\n v_quadSizePx = ${this.symbolSizeExpression_};\n vec2 halfSizePx = v_quadSizePx * 0.5;\n vec2 centerOffsetPx = ${this.symbolOffsetExpression_};\n vec2 offsetPx = centerOffsetPx;\n if (a_index == 0.0) {\n offsetPx -= halfSizePx;\n } else if (a_index == 1.0) {\n offsetPx += halfSizePx * vec2(1., -1.);\n } else if (a_index == 2.0) {\n offsetPx += halfSizePx;\n } else {\n offsetPx += halfSizePx * vec2(-1., 1.);\n }\n float angle = ${this.symbolRotationExpression_}${this.symbolRotateWithView_?" + u_rotation":""};\n float c = cos(-angle);\n float s = sin(-angle);\n offsetPx = vec2(c * offsetPx.x - s * offsetPx.y, s * offsetPx.x + c * offsetPx.y);\n vec4 center = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n gl_Position = center + vec4(pxToScreen(offsetPx), u_depth, 0.);\n vec4 texCoord = ${this.texCoordExpression_};\n float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.p;\n float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.q;\n v_texCoord = vec2(u, v);\n v_hitColor = a_hitColor;\n v_angle = angle;\n c = cos(-v_angle);\n s = sin(-v_angle);\n centerOffsetPx = vec2(c * centerOffsetPx.x - s * centerOffsetPx.y, s * centerOffsetPx.x + c * centerOffsetPx.y);\n v_centerPx = screenToPx(center.xy) + centerOffsetPx;\n${this.attributes_.map((t=>` ${t.varyingName} = ${t.varyingExpression};`)).join("\n")}\n}`:null}getSymbolFragmentShader(){return this.hasSymbol_?`${b_}\n${this.uniforms_.map((t=>`uniform ${t.type} ${t.name};`)).join("\n")}\nvarying vec2 v_texCoord;\nvarying vec4 v_hitColor;\nvarying vec2 v_centerPx;\nvarying float v_angle;\nvarying vec2 v_quadSizePx;\n${this.attributes_.map((t=>`varying ${t.varyingType} ${t.varyingName};`)).join("\n")}\n${this.fragmentShaderFunctions_.join("\n")}\n\nvoid main(void) {\n${this.attributes_.map((t=>` ${t.varyingType} ${t.name} = ${t.varyingName}; // assign to original attribute name`)).join("\n")}\n if (${this.discardExpression_}) { discard; }\n vec2 coordsPx = gl_FragCoord.xy / u_pixelRatio - v_centerPx; // relative to center\n float c = cos(v_angle);\n float s = sin(v_angle);\n coordsPx = vec2(c * coordsPx.x - s * coordsPx.y, s * coordsPx.x + c * coordsPx.y);\n gl_FragColor = ${this.symbolColorExpression_};\n gl_FragColor.rgb *= gl_FragColor.a;\n if (u_hitDetection > 0) {\n if (gl_FragColor.a < 0.05) { discard; };\n gl_FragColor = v_hitColor;\n }\n}`:null}getStrokeVertexShader(){return this.hasStroke_?`${b_}\n${this.uniforms_.map((t=>`uniform ${t.type} ${t.name};`)).join("\n")}\nattribute vec2 a_segmentStart;\nattribute vec2 a_segmentEnd;\nattribute float a_measureStart;\nattribute float a_measureEnd;\nattribute float a_parameters;\nattribute float a_distance;\nattribute vec2 a_joinAngles;\nattribute vec4 a_hitColor;\n\nvarying vec2 v_segmentStart;\nvarying vec2 v_segmentEnd;\nvarying float v_angleStart;\nvarying float v_angleEnd;\nvarying float v_width;\nvarying vec4 v_hitColor;\nvarying float v_distanceOffsetPx;\nvarying float v_measureStart;\nvarying float v_measureEnd;\n\n${this.attributes_.map((t=>`attribute ${t.type} ${t.name};\nvarying ${t.varyingType} ${t.varyingName};`)).join("\n")}\n${this.vertexShaderFunctions_.join("\n")}\nvec2 worldToPx(vec2 worldPos) {\n vec4 screenPos = u_projectionMatrix * vec4(worldPos, 0.0, 1.0);\n return (0.5 * screenPos.xy + 0.5) * u_viewportSizePx;\n}\n\nvec4 pxToScreen(vec2 pxPos) {\n vec2 screenPos = 2.0 * pxPos / u_viewportSizePx - 1.0;\n return vec4(screenPos, u_depth, 1.0);\n}\n\nbool isCap(float joinAngle) {\n return joinAngle < -0.1;\n}\n\nvec2 getJoinOffsetDirection(vec2 normalPx, float joinAngle) {\n float halfAngle = joinAngle / 2.0;\n float c = cos(halfAngle);\n float s = sin(halfAngle);\n vec2 angleBisectorNormal = vec2(s * normalPx.x + c * normalPx.y, -c * normalPx.x + s * normalPx.y);\n float length = 1.0 / s;\n return angleBisectorNormal * length;\n}\n\nvec2 getOffsetPoint(vec2 point, vec2 normal, float joinAngle, float offsetPx) {\n // if on a cap or the join angle is too high, offset the line along the segment normal\n if (cos(joinAngle) > 0.998 || isCap(joinAngle)) {\n return point - normal * offsetPx;\n }\n // offset is applied along the inverted normal (positive offset goes "right" relative to line direction)\n return point - getJoinOffsetDirection(normal, joinAngle) * offsetPx;\n}\n\nvoid main(void) {\n v_angleStart = a_joinAngles.x;\n v_angleEnd = a_joinAngles.y;\n float vertexNumber = floor(abs(a_parameters) / 10000. + 0.5);\n currentLineMetric = vertexNumber < 1.5 ? a_measureStart : a_measureEnd;\n // we're reading the fractional part while keeping the sign (so -4.12 gives -0.12, 3.45 gives 0.45)\n float angleTangentSum = fract(abs(a_parameters) / 10000.) * 10000. * sign(a_parameters);\n\n float lineWidth = ${this.strokeWidthExpression_};\n float lineOffsetPx = ${this.strokeOffsetExpression_};\n\n // compute segment start/end in px with offset\n vec2 segmentStartPx = worldToPx(a_segmentStart);\n vec2 segmentEndPx = worldToPx(a_segmentEnd);\n vec2 tangentPx = normalize(segmentEndPx - segmentStartPx);\n vec2 normalPx = vec2(-tangentPx.y, tangentPx.x);\n segmentStartPx = getOffsetPoint(segmentStartPx, normalPx, v_angleStart, lineOffsetPx),\n segmentEndPx = getOffsetPoint(segmentEndPx, normalPx, v_angleEnd, lineOffsetPx);\n\n // compute current vertex position\n float normalDir = vertexNumber < 0.5 || (vertexNumber > 1.5 && vertexNumber < 2.5) ? 1.0 : -1.0;\n float tangentDir = vertexNumber < 1.5 ? 1.0 : -1.0;\n float angle = vertexNumber < 1.5 ? v_angleStart : v_angleEnd;\n vec2 joinDirection;\n vec2 positionPx = vertexNumber < 1.5 ? segmentStartPx : segmentEndPx;\n // if angle is too high, do not make a proper join\n if (cos(angle) > 0.985 || isCap(angle)) {\n joinDirection = normalPx * normalDir - tangentPx * tangentDir;\n } else {\n joinDirection = getJoinOffsetDirection(normalPx * normalDir, angle);\n }\n positionPx = positionPx + joinDirection * (lineWidth * 0.5 + 1.); // adding 1 pixel for antialiasing\n gl_Position = pxToScreen(positionPx);\n\n v_segmentStart = segmentStartPx;\n v_segmentEnd = segmentEndPx;\n v_width = lineWidth;\n v_hitColor = a_hitColor;\n v_distanceOffsetPx = a_distance / u_resolution - (lineOffsetPx * angleTangentSum);\n v_measureStart = a_measureStart;\n v_measureEnd = a_measureEnd;\n${this.attributes_.map((t=>` ${t.varyingName} = ${t.varyingExpression};`)).join("\n")}\n}`:null}getStrokeFragmentShader(){return this.hasStroke_?`${b_}\n${this.uniforms_.map((t=>`uniform ${t.type} ${t.name};`)).join("\n")}\nvarying vec2 v_segmentStart;\nvarying vec2 v_segmentEnd;\nvarying float v_angleStart;\nvarying float v_angleEnd;\nvarying float v_width;\nvarying vec4 v_hitColor;\nvarying float v_distanceOffsetPx;\nvarying float v_measureStart;\nvarying float v_measureEnd;\n${this.attributes_.map((t=>`varying ${t.varyingType} ${t.varyingName};`)).join("\n")}\n${this.fragmentShaderFunctions_.join("\n")}\n\nvec2 pxToWorld(vec2 pxPos) {\n vec2 screenPos = 2.0 * pxPos / u_viewportSizePx - 1.0;\n return (u_screenToWorldMatrix * vec4(screenPos, 0.0, 1.0)).xy;\n}\n\nbool isCap(float joinAngle) {\n return joinAngle < -0.1;\n}\n\nfloat segmentDistanceField(vec2 point, vec2 start, vec2 end, float width) {\n vec2 tangent = normalize(end - start);\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 startToPoint = point - start;\n return abs(dot(startToPoint, normal)) - width * 0.5;\n}\n\nfloat buttCapDistanceField(vec2 point, vec2 start, vec2 end) {\n vec2 startToPoint = point - start;\n vec2 tangent = normalize(end - start);\n return dot(startToPoint, -tangent);\n}\n\nfloat squareCapDistanceField(vec2 point, vec2 start, vec2 end, float width) {\n return buttCapDistanceField(point, start, end) - width * 0.5;\n}\n\nfloat roundCapDistanceField(vec2 point, vec2 start, vec2 end, float width) {\n float onSegment = max(0., 1000. * dot(point - start, end - start)); // this is very high when inside the segment\n return length(point - start) - width * 0.5 - onSegment;\n}\n\nfloat roundJoinDistanceField(vec2 point, vec2 start, vec2 end, float width) {\n return roundCapDistanceField(point, start, end, width);\n}\n\nfloat bevelJoinField(vec2 point, vec2 start, vec2 end, float width, float joinAngle) {\n vec2 startToPoint = point - start;\n vec2 tangent = normalize(end - start);\n float c = cos(joinAngle * 0.5);\n float s = sin(joinAngle * 0.5);\n float direction = -sign(sin(joinAngle));\n vec2 bisector = vec2(c * tangent.x - s * tangent.y, s * tangent.x + c * tangent.y);\n float radius = width * 0.5 * s;\n return dot(startToPoint, bisector * direction) - radius;\n}\n\nfloat miterJoinDistanceField(vec2 point, vec2 start, vec2 end, float width, float joinAngle) {\n if (cos(joinAngle) > 0.985) { // avoid risking a division by zero\n return bevelJoinField(point, start, end, width, joinAngle);\n }\n float miterLength = 1. / sin(joinAngle * 0.5);\n float miterLimit = ${this.strokeMiterLimitExpression_};\n if (miterLength > miterLimit) {\n return bevelJoinField(point, start, end, width, joinAngle);\n }\n return -1000.;\n}\n\nfloat capDistanceField(vec2 point, vec2 start, vec2 end, float width, float capType) {\n if (capType == ${Om("butt")}) {\n return buttCapDistanceField(point, start, end);\n } else if (capType == ${Om("square")}) {\n return squareCapDistanceField(point, start, end, width);\n }\n return roundCapDistanceField(point, start, end, width);\n}\n\nfloat joinDistanceField(vec2 point, vec2 start, vec2 end, float width, float joinAngle, float joinType) {\n if (joinType == ${Om("bevel")}) {\n return bevelJoinField(point, start, end, width, joinAngle);\n } else if (joinType == ${Om("miter")}) {\n return miterJoinDistanceField(point, start, end, width, joinAngle);\n }\n return roundJoinDistanceField(point, start, end, width);\n}\n\nfloat computeSegmentPointDistance(vec2 point, vec2 start, vec2 end, float width, float joinAngle, float capType, float joinType) {\n if (isCap(joinAngle)) {\n return capDistanceField(point, start, end, width, capType);\n }\n return joinDistanceField(point, start, end, width, joinAngle, joinType);\n}\n\nfloat distanceFromSegment(vec2 point, vec2 start, vec2 end) {\n vec2 tangent = end - start;\n vec2 startToPoint = point - start;\n // inspire by capsule fn in https://iquilezles.org/articles/distfunctions/\n float h = clamp(dot(startToPoint, tangent) / dot(tangent, tangent), 0.0, 1.0);\n return length(startToPoint - tangent * h);\n}\n\nvoid main(void) {\n${this.attributes_.map((t=>` ${t.varyingType} ${t.name} = ${t.varyingName}; // assign to original attribute name`)).join("\n")}\n\n vec2 currentPoint = gl_FragCoord.xy / u_pixelRatio;\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n vec2 worldPos = pxToWorld(currentPoint);\n if (\n abs(u_renderExtent[0] - u_renderExtent[2]) > 0.0 && (\n worldPos[0] < u_renderExtent[0] ||\n worldPos[1] < u_renderExtent[1] ||\n worldPos[0] > u_renderExtent[2] ||\n worldPos[1] > u_renderExtent[3]\n )\n ) {\n discard;\n }\n #endif\n\n float segmentLength = length(v_segmentEnd - v_segmentStart);\n vec2 segmentTangent = (v_segmentEnd - v_segmentStart) / segmentLength;\n vec2 segmentNormal = vec2(-segmentTangent.y, segmentTangent.x);\n vec2 startToPoint = currentPoint - v_segmentStart;\n float lengthToPoint = max(0., min(dot(segmentTangent, startToPoint), segmentLength));\n float currentLengthPx = lengthToPoint + v_distanceOffsetPx;\n float currentRadiusPx = distanceFromSegment(currentPoint, v_segmentStart, v_segmentEnd);\n float currentRadiusRatio = dot(segmentNormal, startToPoint) * 2. / v_width;\n currentLineMetric = mix(\n v_measureStart,\n v_measureEnd,\n lengthToPoint / max(segmentLength, 1.17549429e-38)\n );\n\n if (${this.discardExpression_}) { discard; }\n\n float capType = ${this.strokeCapExpression_};\n float joinType = ${this.strokeJoinExpression_};\n float segmentStartDistance = computeSegmentPointDistance(currentPoint, v_segmentStart, v_segmentEnd, v_width, v_angleStart, capType, joinType);\n float segmentEndDistance = computeSegmentPointDistance(currentPoint, v_segmentEnd, v_segmentStart, v_width, v_angleEnd, capType, joinType);\n float distanceField = max(\n segmentDistanceField(currentPoint, v_segmentStart, v_segmentEnd, v_width),\n max(segmentStartDistance, segmentEndDistance)\n );\n distanceField = max(distanceField, ${this.strokeDistanceFieldExpression_});\n\n vec4 color = ${this.strokeColorExpression_};\n color.a *= smoothstep(0.5, -0.5, distanceField);\n gl_FragColor = color;\n gl_FragColor.a *= u_globalAlpha;\n gl_FragColor.rgb *= gl_FragColor.a;\n if (u_hitDetection > 0) {\n if (gl_FragColor.a < 0.1) { discard; };\n gl_FragColor = v_hitColor;\n }\n}`:null}getFillVertexShader(){return this.hasFill_?`${b_}\n${this.uniforms_.map((t=>`uniform ${t.type} ${t.name};`)).join("\n")}\nattribute vec2 a_position;\nattribute vec4 a_hitColor;\n\nvarying vec4 v_hitColor;\n\n${this.attributes_.map((t=>`attribute ${t.type} ${t.name};\nvarying ${t.varyingType} ${t.varyingName};`)).join("\n")}\n${this.vertexShaderFunctions_.join("\n")}\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, u_depth, 1.0);\n v_hitColor = a_hitColor;\n${this.attributes_.map((t=>` ${t.varyingName} = ${t.varyingExpression};`)).join("\n")}\n}`:null}getFillFragmentShader(){return this.hasFill_?`${b_}\n${this.uniforms_.map((t=>`uniform ${t.type} ${t.name};`)).join("\n")}\nvarying vec4 v_hitColor;\n${this.attributes_.map((t=>`varying ${t.varyingType} ${t.varyingName};`)).join("\n")}\n${this.fragmentShaderFunctions_.join("\n")}\nvec2 pxToWorld(vec2 pxPos) {\n vec2 screenPos = 2.0 * pxPos / u_viewportSizePx - 1.0;\n return (u_screenToWorldMatrix * vec4(screenPos, 0.0, 1.0)).xy;\n}\n\nvec2 worldToPx(vec2 worldPos) {\n vec4 screenPos = u_projectionMatrix * vec4(worldPos, 0.0, 1.0);\n return (0.5 * screenPos.xy + 0.5) * u_viewportSizePx;\n}\n\nvoid main(void) {\n${this.attributes_.map((t=>` ${t.varyingType} ${t.name} = ${t.varyingName}; // assign to original attribute name`)).join("\n")}\n vec2 pxPos = gl_FragCoord.xy / u_pixelRatio;\n vec2 pxOrigin = worldToPx(u_patternOrigin);\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n vec2 worldPos = pxToWorld(pxPos);\n if (\n abs(u_renderExtent[0] - u_renderExtent[2]) > 0.0 && (\n worldPos[0] < u_renderExtent[0] ||\n worldPos[1] < u_renderExtent[1] ||\n worldPos[0] > u_renderExtent[2] ||\n worldPos[1] > u_renderExtent[3]\n )\n ) {\n discard;\n }\n #endif\n if (${this.discardExpression_}) { discard; }\n gl_FragColor = ${this.fillColorExpression_};\n gl_FragColor.a *= u_globalAlpha;\n gl_FragColor.rgb *= gl_FragColor.a;\n if (u_hitDetection > 0) {\n if (gl_FragColor.a < 0.1) { discard; };\n gl_FragColor = v_hitColor;\n }\n}`:null}}function F_(t,e,i){return zm(e,i,nh(),t)}function L_(t){const e=kl(t);return[256*e[0]+e[1],256*e[2]+Math.round(255*e[3])]}const M_="vec4 unpackColor(vec2 packedColor) {\n return vec4(\n fract(floor(packedColor[0] / 256.0) / 256.0),\n fract(packedColor[0] / 256.0),\n fract(floor(packedColor[1] / 256.0) / 256.0),\n fract(packedColor[1] / 256.0)\n );\n}";function A_(t){return t===Wl||t===Yl?2:t===Zl?4:1}function O_(t){const e=A_(t);return e>1?`vec${e}`:"float"}function D_(t,e){for(const i in e.variables){const n=e.variables[i],r=Dm(n.name);let s=O_(n.type);n.type===Wl&&(s="vec4"),t.addUniform(r,s)}for(const i in e.properties){const n=e.properties[i],r=O_(n.type),s=`a_prop_${n.name}`;n.type===Wl?(t.addAttribute(s,r,`unpackColor(${s})`,"vec4"),t.addVertexShaderFunction(M_)):t.addAttribute(s,r)}for(const i in e.functions)t.addVertexShaderFunction(e.functions[i]),t.addFragmentShaderFunction(e.functions[i])}function N_(t,e){const i={};for(const n in t.variables){const r=t.variables[n];i[Dm(r.name)]=()=>{const t=e[r.name];if("number"==typeof t)return t;if("boolean"==typeof t)return t?1:0;if(r.type===Wl){const e=[...kl(t||"#eee")];return e[0]/=255,e[1]/=255,e[2]/=255,e[3]??=1,e}return"string"==typeof t?Am(t):t}}return i}function k_(t){const e={};for(const i in t.properties){const n=t.properties[i],r=t=>{const e=t.get(n.name);return n.type===Wl?L_([...kl(e||"#eee")]):"string"==typeof e?Am(e):"boolean"==typeof e?e?1:0:e};e[`prop_${n.name}`]={size:A_(n.type),callback:r}}return e}function G_(t){return(JSON.stringify(t).split("").reduce(((t,e)=>(t<<5)-t+e.charCodeAt(0)),0)>>>0).toString()}function j_(t,e,i,n){if(`${n}radius`in t&&"icon-"!==n){let r=F_(i,t[`${n}radius`],Vl);if(`${n}radius2`in t){r=`max(${r}, ${F_(i,t[`${n}radius2`],Vl)})`}`${n}stroke-width`in t&&(r=`(${r} + ${F_(i,t[`${n}stroke-width`],Vl)} * 0.5)`),e.setSymbolSizeExpression(`vec2(${r} * 2. + 0.5)`)}if(`${n}scale`in t){const r=F_(i,t[`${n}scale`],Yl);e.setSymbolSizeExpression(`${e.getSymbolSizeExpression()} * ${r}`)}`${n}displacement`in t&&e.setSymbolOffsetExpression(F_(i,t[`${n}displacement`],Zl)),`${n}rotation`in t&&e.setSymbolRotationExpression(F_(i,t[`${n}rotation`],Vl)),`${n}rotate-with-view`in t&&e.setSymbolRotateWithView(!!t[`${n}rotate-with-view`])}function U_(t,e,i,n,r){let s="vec4(0.)";if(null!==e&&(s=e),null!==i&&null!==n){s=`mix(${i}, ${s}, ${`smoothstep(-${n} + 0.63, -${n} - 0.58, ${t})`})`}let o=`${s} * vec4(1.0, 1.0, 1.0, ${`(1.0 - smoothstep(-0.63, 0.58, ${t}))`})`;return null!==r&&(o=`${o} * vec4(1.0, 1.0, 1.0, ${r})`),o}function B_(t,e,i,n,r){const s=new Image;s.crossOrigin=void 0===t[`${n}cross-origin`]?"anonymous":t[`${n}cross-origin`],Ft("string"==typeof t[`${n}src`],`WebGL layers do not support expressions for the ${n}src style property`),s.src=t[`${n}src`],i[`u_texture${r}_size`]=()=>s.complete?[s.width,s.height]:[0,0],e.addUniform(`u_texture${r}_size`,"vec2");const o=`u_texture${r}_size`;return i[`u_texture${r}`]=s,e.addUniform(`u_texture${r}`,"sampler2D"),o}function z_(t,e,i,n,r){let s=F_(i,t[`${e}offset`],Yl);if(`${e}offset-origin`in t)switch(t[`${e}offset-origin`]){case"top-right":s=`vec2(${n}.x, 0.) + ${r} * vec2(-1., 0.) + ${s} * vec2(-1., 1.)`;break;case"bottom-left":s=`vec2(0., ${n}.y) + ${r} * vec2(0., -1.) + ${s} * vec2(1., -1.)`;break;case"bottom-right":s=`${n} - ${r} - ${s}`}return s}function X_(t,e,i){const n={variables:{},properties:{},functions:{},bandCount:0,featureId:!1,geometryType:!1},r=new I_,s={};if("icon-src"in t?function(t,e,i,n){let r="vec4(1.0)";"icon-color"in t&&(r=F_(n,t["icon-color"],Wl)),"icon-opacity"in t&&(r=`${r} * vec4(1.0, 1.0, 1.0, ${F_(n,t["icon-opacity"],Vl)})`);const s=G_(t["icon-src"]),o=B_(t,e,i,"icon-",s);if(e.setSymbolColorExpression(`${r} * texture2D(u_texture${s}, v_texCoord)`).setSymbolSizeExpression(o),"icon-width"in t&&"icon-height"in t&&e.setSymbolSizeExpression(`vec2(${F_(n,t["icon-width"],Vl)}, ${F_(n,t["icon-height"],Vl)})`),"icon-offset"in t&&"icon-size"in t){const i=F_(n,t["icon-size"],Zl),r=e.getSymbolSizeExpression();e.setSymbolSizeExpression(i);const s=z_(t,"icon-",n,"v_quadSizePx",i);e.setTextureCoordinateExpression(`(vec4((${s}).xyxy) + vec4(0., 0., ${i})) / (${r}).xyxy`)}if(j_(t,e,n,"icon-"),"icon-anchor"in t){const i=F_(n,t["icon-anchor"],Zl);let r,s="1.0";"icon-scale"in t&&(s=F_(n,t["icon-scale"],Yl)),r="pixels"===t["icon-anchor-x-units"]&&"pixels"===t["icon-anchor-y-units"]?`${i} * ${s}`:"pixels"===t["icon-anchor-x-units"]?`${i} * vec2(vec2(${s}).x, v_quadSizePx.y)`:"pixels"===t["icon-anchor-y-units"]?`${i} * vec2(v_quadSizePx.x, vec2(${s}).x)`:`${i} * v_quadSizePx`;let o=`v_quadSizePx * vec2(0.5, -0.5) + ${r} * vec2(-1., 1.)`;if("icon-anchor-origin"in t)switch(t["icon-anchor-origin"]){case"top-right":o=`v_quadSizePx * -0.5 + ${r}`;break;case"bottom-left":o=`v_quadSizePx * 0.5 - ${r}`;break;case"bottom-right":o=`v_quadSizePx * vec2(-0.5, 0.5) + ${r} * vec2(1., -1.)`}e.setSymbolOffsetExpression(`${e.getSymbolOffsetExpression()} + ${o}`)}}(t,r,s,n):"shape-points"in t?function(t,e,i,n){n.functions.round="float round(float v) {\n return sign(v) * floor(abs(v) + 0.5);\n}",n.functions.starDistanceField="float starDistanceField(vec2 point, float numPoints, float radius, float radius2, float angle) {\n float startAngle = -PI * 0.5 + angle; // tip starts upwards and rotates clockwise with angle\n float c = cos(startAngle);\n float s = sin(startAngle);\n vec2 pointRotated = vec2(c * point.x - s * point.y, s * point.x + c * point.y);\n float alpha = TWO_PI / numPoints; // the angle of one sector\n float beta = atan(pointRotated.y, pointRotated.x);\n float gamma = round(beta / alpha) * alpha; // angle in sector\n c = cos(-gamma);\n s = sin(-gamma);\n vec2 inSector = vec2(c * pointRotated.x - s * pointRotated.y, abs(s * pointRotated.x + c * pointRotated.y));\n vec2 tipToPoint = inSector + vec2(-radius, 0.);\n vec2 edgeNormal = vec2(radius2 * sin(alpha * 0.5), -radius2 * cos(alpha * 0.5) + radius);\n return dot(normalize(edgeNormal), tipToPoint);\n}",n.functions.regularDistanceField="float regularDistanceField(vec2 point, float numPoints, float radius, float angle) {\n float startAngle = -PI * 0.5 + angle; // tip starts upwards and rotates clockwise with angle\n float c = cos(startAngle);\n float s = sin(startAngle);\n vec2 pointRotated = vec2(c * point.x - s * point.y, s * point.x + c * point.y);\n float alpha = TWO_PI / numPoints; // the angle of one sector\n float radiusIn = radius * cos(PI / numPoints);\n float beta = atan(pointRotated.y, pointRotated.x);\n float gamma = round((beta - alpha * 0.5) / alpha) * alpha + alpha * 0.5; // angle in sector from mid\n c = cos(-gamma);\n s = sin(-gamma);\n vec2 inSector = vec2(c * pointRotated.x - s * pointRotated.y, abs(s * pointRotated.x + c * pointRotated.y));\n return inSector.x - radiusIn;\n}",j_(t,e,n,"shape-");let r=null;"shape-opacity"in t&&(r=F_(n,t["shape-opacity"],Vl));let s="coordsPx";"shape-scale"in t&&(s=`coordsPx / ${F_(n,t["shape-scale"],Yl)}`);let o=null;"shape-fill-color"in t&&(o=F_(n,t["shape-fill-color"],Wl));let a=null;"shape-stroke-color"in t&&(a=F_(n,t["shape-stroke-color"],Wl));let l=null;"shape-stroke-width"in t&&(l=F_(n,t["shape-stroke-width"],Vl));const h=F_(n,t["shape-points"],Vl);let c,u="0.";"shape-angle"in t&&(u=F_(n,t["shape-angle"],Vl));let d=F_(n,t["shape-radius"],Vl);if(null!==l&&(d=`${d} + ${l} * 0.5`),"shape-radius2"in t){let e=F_(n,t["shape-radius2"],Vl);null!==l&&(e=`${e} + ${l} * 0.5`),c=`starDistanceField(${s}, ${h}, ${d}, ${e}, ${u})`}else c=`regularDistanceField(${s}, ${h}, ${d}, ${u})`;const g=U_(c,o,a,l,r);e.setSymbolColorExpression(g)}(t,r,0,n):"circle-radius"in t&&function(t,e,i,n){n.functions.circleDistanceField="float circleDistanceField(vec2 point, float radius) {\n return length(point) - radius;\n}",j_(t,e,n,"circle-");let r=null;"circle-opacity"in t&&(r=F_(n,t["circle-opacity"],Vl));let s="coordsPx";"circle-scale"in t&&(s=`coordsPx / ${F_(n,t["circle-scale"],Yl)}`);let o=null;"circle-fill-color"in t&&(o=F_(n,t["circle-fill-color"],Wl));let a=null;"circle-stroke-color"in t&&(a=F_(n,t["circle-stroke-color"],Wl));let l=F_(n,t["circle-radius"],Vl),h=null;"circle-stroke-width"in t&&(h=F_(n,t["circle-stroke-width"],Vl),l=`(${l} + ${h} * 0.5)`);const c=U_(`circleDistanceField(${s}, ${l})`,o,a,h,r);e.setSymbolColorExpression(c)}(t,r,0,n),function(t,e,i,n){if("stroke-color"in t&&e.setStrokeColorExpression(F_(n,t["stroke-color"],Wl)),"stroke-pattern-src"in t){const r=G_(t["stroke-pattern-src"]),s=B_(t,e,i,"stroke-pattern-",r);let o=s,a="vec2(0.)";"stroke-pattern-offset"in t&&"stroke-pattern-size"in t&&(o=F_(n,t["stroke-pattern-size"],Zl),a=z_(t,"stroke-pattern-",n,s,o));let l="0.";"stroke-pattern-spacing"in t&&(l=F_(n,t["stroke-pattern-spacing"],Vl)),n.functions.sampleStrokePattern="vec4 sampleStrokePattern(sampler2D texture, vec2 textureSize, vec2 textureOffset, vec2 sampleSize, float spacingPx, float currentLengthPx, float currentRadiusRatio, float lineWidth) {\n float currentLengthScaled = currentLengthPx * sampleSize.y / lineWidth;\n float spacingScaled = spacingPx * sampleSize.y / lineWidth;\n float uCoordPx = mod(currentLengthScaled, (sampleSize.x + spacingScaled));\n // make sure that we're not sampling too close to the borders to avoid interpolation with outside pixels\n uCoordPx = clamp(uCoordPx, 0.5, sampleSize.x - 0.5);\n float vCoordPx = (-currentRadiusRatio * 0.5 + 0.5) * sampleSize.y;\n vec2 texCoord = (vec2(uCoordPx, vCoordPx) + textureOffset) / textureSize;\n return texture2D(texture, texCoord);\n}";const h=`u_texture${r}`;let c="1.";"stroke-color"in t&&(c=e.getStrokeColorExpression()),e.setStrokeColorExpression(`${c} * sampleStrokePattern(${h}, ${s}, ${a}, ${o}, ${l}, currentLengthPx, currentRadiusRatio, v_width)`)}if("stroke-width"in t&&e.setStrokeWidthExpression(F_(n,t["stroke-width"],Vl)),"stroke-offset"in t&&e.setStrokeOffsetExpression(F_(n,t["stroke-offset"],Vl)),"stroke-line-cap"in t&&e.setStrokeCapExpression(F_(n,t["stroke-line-cap"],$l)),"stroke-line-join"in t&&e.setStrokeJoinExpression(F_(n,t["stroke-line-join"],$l)),"stroke-miter-limit"in t&&e.setStrokeMiterLimitExpression(F_(n,t["stroke-miter-limit"],Vl)),"stroke-line-dash"in t){n.functions.getSingleDashDistance=`float getSingleDashDistance(float distance, float radius, float dashOffset, float dashLength, float dashLengthTotal, float capType, float lineWidth) {\n float localDistance = mod(distance, dashLengthTotal);\n float distanceSegment = abs(localDistance - dashOffset - dashLength * 0.5) - dashLength * 0.5;\n distanceSegment = min(distanceSegment, dashLengthTotal - localDistance);\n if (capType == ${Om("square")}) {\n distanceSegment -= lineWidth * 0.5;\n } else if (capType == ${Om("round")}) {\n distanceSegment = min(distanceSegment, sqrt(distanceSegment * distanceSegment + radius * radius) - lineWidth * 0.5);\n }\n return distanceSegment;\n}`;let i=t["stroke-line-dash"].map((t=>F_(n,t,Vl)));i.length%2==1&&(i=[...i,...i]);let r="0.";"stroke-line-dash-offset"in t&&(r=F_(n,t["stroke-line-dash-offset"],Vl));const s=`dashDistanceField_${G_(t["stroke-line-dash"])}`,o=i.map(((t,e)=>`float dashLength${e}`)).join(", "),a=i.map(((t,e)=>`dashLength${e}`)).join(" + ");let l="0.",h=`getSingleDashDistance(distance, radius, ${l}, dashLength0, totalDashLength, capType, lineWidth)`;for(let t=2;t`${t}`)).join(", ");e.setStrokeDistanceFieldExpression(`${s}(currentLengthPx + ${r}, currentRadiusPx, capType, v_width, ${c})`)}}(t,r,s,n),function(t,e,i,n){if("fill-color"in t&&e.setFillColorExpression(F_(n,t["fill-color"],Wl)),"fill-pattern-src"in t){const r=G_(t["fill-pattern-src"]),s=B_(t,e,i,"fill-pattern-",r);let o=s,a="vec2(0.)";"fill-pattern-offset"in t&&"fill-pattern-size"in t&&(o=F_(n,t["fill-pattern-size"],Zl),a=z_(t,"fill-pattern-",n,s,o)),n.functions.sampleFillPattern="vec4 sampleFillPattern(sampler2D texture, vec2 textureSize, vec2 textureOffset, vec2 sampleSize, vec2 pxOrigin, vec2 pxPosition) {\n float scaleRatio = pow(2., mod(u_zoom + 0.5, 1.) - 0.5);\n vec2 pxRelativePos = pxPosition - pxOrigin;\n // rotate the relative position from origin by the current view rotation\n pxRelativePos = vec2(pxRelativePos.x * cos(u_rotation) - pxRelativePos.y * sin(u_rotation), pxRelativePos.x * sin(u_rotation) + pxRelativePos.y * cos(u_rotation));\n // sample position is computed according to the sample offset & size\n vec2 samplePos = mod(pxRelativePos / scaleRatio, sampleSize);\n // also make sure that we're not sampling too close to the borders to avoid interpolation with outside pixels\n samplePos = clamp(samplePos, vec2(0.5), sampleSize - vec2(0.5));\n samplePos.y = sampleSize.y - samplePos.y; // invert y axis so that images appear upright\n return texture2D(texture, (samplePos + textureOffset) / textureSize);\n}";const l=`u_texture${r}`;let h="1.";"fill-color"in t&&(h=e.getFillColorExpression()),e.setFillColorExpression(`${h} * sampleFillPattern(${l}, ${s}, ${a}, ${o}, pxOrigin, pxPos)`)}}(t,r,s,n),i){const t=F_(n,i,Xl);r.setFragmentDiscardExpression(`!${t}`)}const o={};function a(t,e,i,s){if(!n[t])return;const a=O_(i),l=A_(i);r.addAttribute(`a_${e}`,a),o[e]={size:l,callback:s}}return a("geometryType",Um,$l,(t=>Am(ph(t.getGeometry())))),a("featureId",jm,$l|Vl,(t=>{const e=t.getId()??null;return"string"==typeof e?Am(e):e})),D_(r,n),{builder:r,attributes:{...o,...k_(n)},uniforms:{...s,...N_(n,e)}}}function V_(t){const e=Array.isArray(t)?t:[t];if("style"in e[0]){const t=[],i=e,n=[];for(const e of i){const i=Array.isArray(e.style)?e.style:[e.style];let r=e.filter;e.else&&n.length&&(r=["all",...n.map((t=>["!",t]))],e.filter&&r.push(e.filter),r.length<3&&(r=r[1])),e.filter&&n.push(e.filter);const s=i.map((t=>({style:t,...r&&{filter:r}})));t.push(...s)}return t}return"builder"in e[0]?e:e.map((t=>({style:t})))}const $_=[];let W_;function Z_(){return W_||(W_=Tm()),W_}let Y_=0;const H_="a_position",K_="a_index",q_="a_segmentStart",J_="a_segmentEnd",Q_="a_measureStart",ty="a_measureEnd",ey="a_parameters",iy="a_joinAngles",ny="a_distance";class ry{constructor(t,e,i,n,r){this.helper_,this.hitDetectionEnabled_=!!n;let s=t;if(!("builder"in t)){const i=t,n=X_(i.style,e,i.filter);s={builder:n.builder,attributes:n.attributes,uniforms:n.uniforms}}this.fillProgram_,this.strokeProgram_,this.symbolProgram_,this.hasFill_=!!s.builder.getFillVertexShader(),this.hasFill_&&(this.fillVertexShader_=s.builder.getFillVertexShader(),this.fillFragmentShader_=s.builder.getFillFragmentShader()),this.hasStroke_=!!s.builder.getStrokeVertexShader(),this.hasStroke_&&(this.strokeVertexShader_=s.builder.getStrokeVertexShader(),this.strokeFragmentShader_=s.builder.getStrokeFragmentShader()),this.hasSymbol_=!!s.builder.getSymbolVertexShader(),this.hasSymbol_&&(this.symbolVertexShader_=s.builder.getSymbolVertexShader(),this.symbolFragmentShader_=s.builder.getSymbolFragmentShader()),this.featureFilter_=null,r&&(this.featureFilter_=this.computeFeatureFilter(r));const o=this.hitDetectionEnabled_?{hitColor:{callback(){return Em(this.ref,$_)},size:4}}:{};this.customAttributes_=Object.assign({},o,s.attributes),this.uniforms_=s.uniforms;const a=Object.entries(this.customAttributes_).map((([t,e])=>({name:`a_${t}`,size:e.size||1,type:Op})));this.polygonAttributesDesc_=[{name:H_,size:2,type:Op},...a],this.lineStringAttributesDesc_=[{name:q_,size:2,type:Op},{name:Q_,size:1,type:Op},{name:J_,size:2,type:Op},{name:ty,size:1,type:Op},{name:iy,size:2,type:Op},{name:ny,size:1,type:Op},{name:ey,size:1,type:Op},...a],this.pointAttributesDesc_=[{name:H_,size:2,type:Op},{name:K_,size:1,type:Op},...a],this.setHelper(i)}computeFeatureFilter(t){const e=nh();let i;try{i=_h(t,Xl,e)}catch{return null}if(e.mapState||e.variables.size>0)return null;const n={variables:{},properties:{},resolution:NaN,featureId:null,geometryType:""};return t=>{if(n.properties=t.getPropertiesInternal(),e.featureId){const e=t.getId();n.featureId=void 0!==e?e:null}return n.geometryType=ph(t.getGeometry()),i(n)}}async generateBuffers(t,e){let i=t;if(this.featureFilter_&&(i=i.filter(this.featureFilter_),i.isEmpty()))return null;const n=this.generateRenderInstructions_(i,e),[r,s,o]=await Promise.all([this.generateBuffersForType_(n.polygonInstructions,"Polygon",e),this.generateBuffersForType_(n.lineStringInstructions,"LineString",e),this.generateBuffersForType_(n.pointInstructions,"Point",e)]);return{polygonBuffers:r,lineStringBuffers:s,pointBuffers:o,invertVerticesTransform:er([1,0,0,1,0,0],e)}}generateRenderInstructions_(t,e){return{polygonInstructions:this.hasFill_?Km(t.polygonBatch,new Float32Array(0),this.customAttributes_,e):null,lineStringInstructions:this.hasStroke_?Hm(t.lineStringBatch,new Float32Array(0),this.customAttributes_,e):null,pointInstructions:this.hasSymbol_?Ym(t.pointBatch,new Float32Array(0),this.customAttributes_,e):null}}generateBuffersForType_(t,e,i){if(null===t)return null;const n=Y_++;let r;switch(e){case"Polygon":r=xm;break;case"LineString":r=Sm;break;case"Point":r=vm}const s={id:n,type:r,renderInstructions:t.buffer,renderInstructionsTransform:i,customAttributesSize:Zm(this.customAttributes_)},o=Z_();return o.postMessage(s,[t.buffer]),t=null,new Promise((t=>{const e=i=>{const r=i.data;if(r.id!==n)return;if(o.removeEventListener("message",e),!this.helper_.getGL())return;const s=new Cp(Xf,Wf).fromArrayBuffer(r.vertexBuffer),a=new Cp(Vf,Wf).fromArrayBuffer(r.indexBuffer);this.helper_.flushBufferData(s),this.helper_.flushBufferData(a),t([a,s])};o.addEventListener("message",e)}))}render(t,e,i){this.hasFill_&&this.renderInternal_(t.polygonBuffers[0],t.polygonBuffers[1],this.fillProgram_,this.polygonAttributesDesc_,e,i),this.hasStroke_&&this.renderInternal_(t.lineStringBuffers[0],t.lineStringBuffers[1],this.strokeProgram_,this.lineStringAttributesDesc_,e,i),this.hasSymbol_&&this.renderInternal_(t.pointBuffers[0],t.pointBuffers[1],this.symbolProgram_,this.pointAttributesDesc_,e,i)}renderInternal_(t,e,i,n,r,s){const o=t.getSize();0!==o&&(this.helper_.useProgram(i,r),this.helper_.bindBuffer(e),this.helper_.bindBuffer(t),this.helper_.enableAttributes(n),s(),this.helper_.drawElements(0,o))}setHelper(t,e=null){this.helper_=t,this.hasFill_&&(this.fillProgram_=this.helper_.getProgram(this.fillFragmentShader_,this.fillVertexShader_)),this.hasStroke_&&(this.strokeProgram_=this.helper_.getProgram(this.strokeFragmentShader_,this.strokeVertexShader_)),this.hasSymbol_&&(this.symbolProgram_=this.helper_.getProgram(this.symbolFragmentShader_,this.symbolVertexShader_)),this.helper_.addUniforms(this.uniforms_),e&&(e.polygonBuffers&&(this.helper_.flushBufferData(e.polygonBuffers[0]),this.helper_.flushBufferData(e.polygonBuffers[1])),e.lineStringBuffers&&(this.helper_.flushBufferData(e.lineStringBuffers[0]),this.helper_.flushBufferData(e.lineStringBuffers[1])),e.pointBuffers&&(this.helper_.flushBufferData(e.pointBuffers[0]),this.helper_.flushBufferData(e.pointBuffers[1])))}}const sy={...Fp,RENDER_EXTENT:"u_renderExtent",PATTERN_ORIGIN:"u_patternOrigin",GLOBAL_ALPHA:"u_globalAlpha"};class oy extends em{constructor(t,e){super(t,{uniforms:{[sy.RENDER_EXTENT]:[0,0,0,0],[sy.PATTERN_ORIGIN]:[0,0],[sy.GLOBAL_ALPHA]:1},postProcesses:e.postProcesses}),this.hitDetectionEnabled_=!e.disableHitDetection,this.hitRenderTarget_,this.sourceRevision_=-1,this.previousExtent_=[1/0,1/0,-1/0,-1/0],this.currentTransform_=[1,0,0,1,0,0],this.tmpCoords_=[0,0],this.tmpTransform_=[1,0,0,1,0,0],this.tmpMat4_=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],this.currentFrameStateTransform_=[1,0,0,1,0,0],this.styleVariables_={},this.styles_=[],this.styleRenderers_=[],this.buffers_=[],this.applyOptions_(e),this.batch_=new Vp,this.initialFeaturesAdded_=!1,this.sourceListenKeys_=null}addInitialFeatures_(t){const e=this.getLayer().getSource(),i=Dn();let n;i&&(n=Pn(i,t.viewState.projection)),this.batch_.addFeatures(e.getFeatures(),n),this.sourceListenKeys_=[D(e,Dd,this.handleSourceFeatureAdded_.bind(this,n)),D(e,Nd,this.handleSourceFeatureChanged_.bind(this,n),this),D(e,Gd,this.handleSourceFeatureDelete_,this),D(e,kd,this.handleSourceFeatureClear_,this)]}applyOptions_(t){this.styleVariables_=t.variables,this.styles_=V_(t.style)}createRenderers_(){this.buffers_=[],this.styleRenderers_=this.styles_.map((t=>new ry(t,this.styleVariables_,this.helper,this.hitDetectionEnabled_,"filter"in t?t.filter:null)))}reset(t){this.applyOptions_(t),this.helper&&this.createRenderers_(),super.reset(t)}afterHelperCreated(){this.styleRenderers_.length?this.styleRenderers_.forEach(((t,e)=>t.setHelper(this.helper,this.buffers_[e]))):this.createRenderers_(),this.hitDetectionEnabled_&&(this.hitRenderTarget_=new Xp(this.helper))}handleSourceFeatureAdded_(t,e){const i=e.feature;this.batch_.addFeature(i,t)}handleSourceFeatureChanged_(t,e){const i=e.feature;this.batch_.changeFeature(i,t)}handleSourceFeatureDelete_(t){const e=t.feature;this.batch_.removeFeature(e)}handleSourceFeatureClear_(){this.batch_.clear()}applyUniforms_(t){Hn(this.tmpTransform_,this.currentFrameStateTransform_),Zn(this.tmpTransform_,t),this.helper.setUniformMatrixValue(sy.PROJECTION_MATRIX,$d(this.tmpMat4_,this.tmpTransform_)),er(this.tmpTransform_,this.tmpTransform_),this.helper.setUniformMatrixValue(sy.SCREEN_TO_WORLD_MATRIX,$d(this.tmpMat4_,this.tmpTransform_)),this.tmpCoords_[0]=0,this.tmpCoords_[1]=0,er(this.tmpTransform_,t),Kn(this.tmpTransform_,this.tmpCoords_),this.helper.setUniformFloatVec2(sy.PATTERN_ORIGIN,this.tmpCoords_)}renderFrame(t){const e=this.helper.getGL();this.preRender(e,t);const[i,n,r]=Cm(t,this.getLayer());this.helper.prepareDraw(t),this.renderWorlds(t,!1,i,n,r),this.helper.finalizeDraw(t,this.dispatchPreComposeEvent,this.dispatchPostComposeEvent);const s=this.helper.getCanvas();return this.hitDetectionEnabled_&&(this.renderWorlds(t,!0,i,n,r),this.hitRenderTarget_.clearCachedData()),this.postRender(e,t),s}prepareFrameInternal(t){this.initialFeaturesAdded_||(this.addInitialFeatures_(t),this.initialFeaturesAdded_=!0);const e=this.getLayer(),i=e.getSource(),n=t.viewState,r=!t.viewHints[ao]&&!t.viewHints[lo],s=!Jt(this.previousExtent_,t.extent),o=this.sourceRevision_t.generateBuffers(this.batch_,h).then((t=>{this.buffers_[e]&&this.disposeBuffers(this.buffers_[e]),this.buffers_[e]=t}))));Promise.all(c).then((()=>{this.ready=!0,this.getLayer().changed()})),this.previousExtent_=t.extent.slice()}return!0}renderWorlds(t,e,i,n,r){let s=i;e&&(this.hitRenderTarget_.setSize([Math.floor(t.size[0]/2),Math.floor(t.size[1]/2)]),this.helper.prepareDrawToRenderTarget(t,this.hitRenderTarget_,!0));do{this.helper.makeProjectionTransform(t,this.currentFrameStateTransform_),Qn(this.currentFrameStateTransform_,s*r,0);for(let i=0,n=this.styleRenderers_.length;i{this.applyUniforms_(r.invertVerticesTransform),this.helper.applyHitDetectionUniform(e)}))}}while(++s{for(const e of t)e&&this.helper.deleteBuffer(e)};t.pointBuffers&&e(t.pointBuffers),t.lineStringBuffers&&e(t.lineStringBuffers),t.polygonBuffers&&e(t.polygonBuffers)}disposeInternal(){this.buffers_.forEach((t=>{t&&this.disposeBuffers(t)})),this.sourceListenKeys_&&(this.sourceListenKeys_.forEach((function(t){k(t)})),this.sourceListenKeys_=null),super.disposeInternal()}renderDeclutter(){}}const ay={...im,TILE_MASK_TEXTURE:"u_depthMask",TILE_ZOOM_LEVEL:"u_tileZoomLevel"},ly={POSITION:"a_position"};class hy extends hm{constructor(t,e){super(t,{cacheSize:e.cacheSize,uniforms:{[ay.PATTERN_ORIGIN]:[0,0],[ay.TILE_MASK_TEXTURE]:()=>this.tileMaskTarget_.getTexture()}}),this.hitDetectionEnabled_=!e.disableHitDetection,this.styles_=[],this.styleVariables_=e.variables||{},this.styleRenderers_=[],this.currentFrameStateTransform_=[1,0,0,1,0,0],this.tmpTransform_=[1,0,0,1,0,0],this.tmpMat4_=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],this.tileMaskTarget_=null,this.tileMaskIndices_=new Cp(Vf,$f),this.tileMaskIndices_.fromArray([0,1,3,1,2,3]),this.tileMaskAttributes_=[{name:ly.POSITION,size:2,type:Op}],this.tileMaskProgram_,this.applyOptions_(e)}reset(t){super.reset(t),this.applyOptions_(t),this.helper&&(this.createRenderers_(),this.initTileMask_())}applyOptions_(t){this.styles_=V_(t.style)}createRenderers_(){function t(t){const e=t.getFragmentDiscardExpression(),i=`texture2D(${ay.TILE_MASK_TEXTURE}, gl_FragCoord.xy / u_pixelRatio / u_viewportSizePx).r * 50. > ${ay.TILE_ZOOM_LEVEL} + 0.5`;t.setFragmentDiscardExpression("false"!==e?`(${e}) || (${i})`:i),t.addUniform(ay.TILE_MASK_TEXTURE,"sampler2D"),t.addUniform(ay.TILE_ZOOM_LEVEL,"float")}this.styleRenderers_=this.styles_.map((e=>{let i;if("builder"in e)t(e.builder),i=e;else{const n=X_(e.style,this.styleVariables_,e.filter);t(n.builder),i={builder:n.builder,attributes:n.attributes,uniforms:n.uniforms}}return new ry(i,this.styleVariables_,this.helper,this.hitDetectionEnabled_,"filter"in e?e.filter:null)}))}initTileMask_(){this.tileMaskTarget_=new Xp(this.helper);const t=(new I_).setFillColorExpression(`vec4(${ay.TILE_ZOOM_LEVEL} / 50., 0., 0., 1.)`).addUniform(ay.TILE_ZOOM_LEVEL,"float");this.tileMaskProgram_=this.helper.getProgram(t.getFillFragmentShader(),t.getFillVertexShader()),this.helper.flushBufferData(this.tileMaskIndices_)}afterHelperCreated(){this.createRenderers_(),this.initTileMask_()}createTileRepresentation(t){const e=new $p(t,this.styleRenderers_),i=()=>{e.ready&&(this.getLayer().changed(),e.removeEventListener(n,i))};return e.addEventListener(n,i),e}beforeTilesRender(t,e){super.beforeTilesRender(t,!0),this.helper.makeProjectionTransform(t,this.currentFrameStateTransform_)}beforeTilesMaskRender(t){this.helper.makeProjectionTransform(t,this.currentFrameStateTransform_);const e=t.pixelRatio,i=t.size;return this.tileMaskTarget_.setSize([i[0]*e,i[1]*e]),this.helper.prepareDrawToRenderTarget(t,this.tileMaskTarget_,!0,!0),this.helper.useProgram(this.tileMaskProgram_,t),Hn(this.tmpTransform_,this.currentFrameStateTransform_),this.helper.setUniformMatrixValue(ay.PROJECTION_MATRIX,$d(this.tmpMat4_,this.tmpTransform_)),er(this.tmpTransform_,this.currentFrameStateTransform_),this.helper.setUniformMatrixValue(ay.SCREEN_TO_WORLD_MATRIX,$d(this.tmpMat4_,this.tmpTransform_)),!0}renderTileMask(t,e,i,n){if(!t.ready)return;this.helper.setUniformFloatValue(ay.DEPTH,n),this.helper.setUniformFloatValue(ay.TILE_ZOOM_LEVEL,e),this.helper.setUniformFloatVec4(ay.RENDER_EXTENT,i),this.helper.setUniformFloatValue(ay.GLOBAL_ALPHA,1),this.helper.bindBuffer(t.maskVertices),this.helper.bindBuffer(this.tileMaskIndices_),this.helper.enableAttributes(this.tileMaskAttributes_);const r=this.tileMaskIndices_.getSize();this.helper.drawElements(0,r)}applyUniforms_(t,e,i,n,r){Hn(this.tmpTransform_,this.currentFrameStateTransform_),Zn(this.tmpTransform_,i),this.helper.setUniformMatrixValue(ay.PROJECTION_MATRIX,$d(this.tmpMat4_,this.tmpTransform_)),er(this.tmpTransform_,this.currentFrameStateTransform_),this.helper.setUniformMatrixValue(ay.SCREEN_TO_WORLD_MATRIX,$d(this.tmpMat4_,this.tmpTransform_)),this.helper.setUniformFloatValue(ay.GLOBAL_ALPHA,t),this.helper.setUniformFloatValue(ay.DEPTH,r),this.helper.setUniformFloatValue(ay.TILE_ZOOM_LEVEL,n),this.helper.setUniformFloatVec4(ay.RENDER_EXTENT,e)}renderTile(t,e,i,n,r,s,o,a,l,h,c){const u=pe(a,n,a),d=t.tile.getTileCoord()[0];for(let e=0,n=this.styleRenderers_.length;e{this.applyUniforms_(c,u,r.invertVerticesTransform,d,l)}))}}renderDeclutter(t){}disposeInternal(){super.disposeInternal()}}const cy=0,uy=1,dy=2,gy=3,fy=4,py=5,my=6,_y=7,yy=8,xy=9,vy=10,Sy=11,Ey=12,wy=[yy],Ty=[Ey],Cy=[uy],Ry=[gy];class by extends uu{constructor(t,e,i,n){super(),this.tolerance=t,this.maxExtent=e,this.pixelRatio=n,this.maxLineWidth=0,this.resolution=i,this.beginGeometryInstruction1_=null,this.beginGeometryInstruction2_=null,this.bufferedMaxExtent_=null,this.instructions=[],this.coordinates=[],this.tmpCoordinate_=[],this.hitDetectionInstructions=[],this.state={}}applyPixelRatio(t){const e=this.pixelRatio;return 1==e?t:t.map((function(t){return t*e}))}appendFlatPointCoordinates(t,e){const i=this.getBufferedMaxExtent(),n=this.tmpCoordinate_,r=this.coordinates;let s=r.length;for(let o=0,a=t.length;oa&&(this.instructions.push([fy,a,h,t,i,Mr,r]),this.hitDetectionInstructions.push([fy,a,h,t,n||i,Mr,r]));break;case"Point":l=t.getFlatCoordinates(),this.coordinates.push(l[0],l[1]),h=this.coordinates.length,this.instructions.push([fy,a,h,t,i,void 0,r]),this.hitDetectionInstructions.push([fy,a,h,t,n||i,void 0,r])}this.endGeometry(e)}beginGeometry(t,e,i){this.beginGeometryInstruction1_=[cy,e,0,t,i],this.instructions.push(this.beginGeometryInstruction1_),this.beginGeometryInstruction2_=[cy,e,0,t,i],this.hitDetectionInstructions.push(this.beginGeometryInstruction2_)}finish(){return{instructions:this.instructions,hitDetectionInstructions:this.hitDetectionInstructions,coordinates:this.coordinates}}reverseHitDetectionInstructions(){const t=this.hitDetectionInstructions;let e;t.reverse();const i=t.length;let n,r,s=-1;for(e=0;ethis.maxLineWidth&&(this.maxLineWidth=e.lineWidth,this.bufferedMaxExtent_=null)}else e.strokeStyle=void 0,e.lineCap=void 0,e.lineDash=null,e.lineDashOffset=void 0,e.lineJoin=void 0,e.lineWidth=void 0,e.miterLimit=void 0;return e}setFillStrokeStyle(t,e){const i=this.state;this.fillStyleToState(t,i),this.strokeStyleToState(e,i)}createFill(t){const e=t.fillStyle,i=[vy,e];return"string"!=typeof e&&i.push(t.fillPatternScale),i}applyStroke(t){this.instructions.push(this.createStroke(t))}createStroke(t){return[Sy,t.strokeStyle,t.lineWidth*this.pixelRatio,t.lineCap,t.lineJoin,t.miterLimit,t.lineDash?this.applyPixelRatio(t.lineDash):null,t.lineDashOffset*this.pixelRatio]}updateFillStyle(t,e){const i=t.fillStyle;"string"==typeof i&&t.currentFillStyle==i||(this.instructions.push(e.call(this,t)),t.currentFillStyle=i)}updateStrokeStyle(t,e){const i=t.strokeStyle,n=t.lineCap,r=t.lineDash,s=t.lineDashOffset,o=t.lineJoin,a=t.lineWidth,l=t.miterLimit;(t.currentStrokeStyle!=i||t.currentLineCap!=n||r!=t.currentLineDash&&!w(t.currentLineDash,r)||t.currentLineDashOffset!=s||t.currentLineJoin!=o||t.currentLineWidth!=a||t.currentMiterLimit!=l)&&(e.call(this,t),t.currentStrokeStyle=i,t.currentLineCap=n,t.currentLineDash=r,t.currentLineDashOffset=s,t.currentLineJoin=o,t.currentLineWidth=a,t.currentMiterLimit=l)}endGeometry(t){this.beginGeometryInstruction1_[2]=this.instructions.length,this.beginGeometryInstruction1_=null,this.beginGeometryInstruction2_[2]=this.hitDetectionInstructions.length,this.beginGeometryInstruction2_=null;const e=[_y,t];this.instructions.push(e),this.hitDetectionInstructions.push(e)}getBufferedMaxExtent(){if(!this.bufferedMaxExtent_&&(this.bufferedMaxExtent_=Bt(this.maxExtent),this.maxLineWidth>0)){const t=this.resolution*(this.maxLineWidth+1)/2;Ut(this.bufferedMaxExtent_,t,this.bufferedMaxExtent_)}return this.bufferedMaxExtent_}}class Py extends by{constructor(t,e,i,n){super(t,e,i,n),this.hitDetectionImage_=null,this.image_=null,this.imagePixelRatio_=void 0,this.anchorX_=void 0,this.anchorY_=void 0,this.height_=void 0,this.opacity_=void 0,this.originX_=void 0,this.originY_=void 0,this.rotateWithView_=void 0,this.rotation_=void 0,this.scale_=void 0,this.width_=void 0,this.declutterMode_=void 0,this.declutterImageWithText_=void 0}drawPoint(t,e,i){if(!this.image_||this.maxExtent&&!Xt(this.maxExtent,t.getFlatCoordinates()))return;this.beginGeometry(t,e,i);const n=t.getFlatCoordinates(),r=t.getStride(),s=this.coordinates.length,o=this.appendFlatPointCoordinates(n,r);this.instructions.push([my,s,o,this.image_,this.anchorX_*this.imagePixelRatio_,this.anchorY_*this.imagePixelRatio_,Math.ceil(this.height_*this.imagePixelRatio_),this.opacity_,this.originX_*this.imagePixelRatio_,this.originY_*this.imagePixelRatio_,this.rotateWithView_,this.rotation_,[this.scale_[0]*this.pixelRatio/this.imagePixelRatio_,this.scale_[1]*this.pixelRatio/this.imagePixelRatio_],Math.ceil(this.width_*this.imagePixelRatio_),this.declutterMode_,this.declutterImageWithText_]),this.hitDetectionInstructions.push([my,s,o,this.hitDetectionImage_,this.anchorX_,this.anchorY_,this.height_,1,this.originX_,this.originY_,this.rotateWithView_,this.rotation_,this.scale_,this.width_,this.declutterMode_,this.declutterImageWithText_]),this.endGeometry(e)}drawMultiPoint(t,e,i){if(!this.image_)return;this.beginGeometry(t,e,i);const n=t.getFlatCoordinates(),r=[];for(let e=0,i=n.length;e=t){const e=(t-a+u)/u,d=Oe(i,h,e),g=Oe(n,c,e);l.push(d,g),s.push(l),l=[d,g],a==t&&(o+=r),a=0}else if(a0&&s.push(l),s}function My(t,e,i,n,r){let s,o,a,l,h,c,u,d,g,f,p=i,m=i,_=0,y=0,x=i;for(o=i;ot&&(y>_&&(_=y,p=x,m=o),y=0,x=o-r)),a=l,u=g,d=f),h=i,c=n}return y+=l,y>_?[x,o]:[p,m]}const Ay={left:0,center:.5,right:1,top:0,middle:.5,hanging:.2,alphabetic:.8,ideographic:.8,bottom:1};class Oy extends by{constructor(t,e,i,n){super(t,e,i,n),this.labels_=null,this.text_="",this.textOffsetX_=0,this.textOffsetY_=0,this.textRotateWithView_=void 0,this.textKeepUpright_=void 0,this.textRotation_=0,this.textFillState_=null,this.fillStates={},this.fillStates[Ih]={fillStyle:Ih},this.textStrokeState_=null,this.strokeStates={},this.textState_={},this.textStates={},this.textKey_="",this.fillKey_="",this.strokeKey_="",this.declutterMode_=void 0,this.declutterImageWithText_=void 0}finish(){const t=super.finish();return t.textStates=this.textStates,t.fillStates=this.fillStates,t.strokeStates=this.strokeStates,t}drawText(t,e,i){const n=this.textFillState_,r=this.textStrokeState_,s=this.textState_;if(""===this.text_||!s||!n&&!r)return;const o=this.coordinates;let a=o.length;const l=t.getType();let h=null,c=t.getStride();if("line"!==s.placement||"LineString"!=l&&"MultiLineString"!=l&&"Polygon"!=l&&"MultiPolygon"!=l){let n=s.overflow?null:[];switch(l){case"Point":case"MultiPoint":h=t.getFlatCoordinates();break;case"LineString":h=t.getFlatMidpoint();break;case"Circle":h=t.getCenter();break;case"MultiLineString":h=t.getFlatMidpoints(),c=2;break;case"Polygon":h=t.getFlatInteriorPoint(),s.overflow||n.push(h[2]/this.resolution),c=3;break;case"MultiPolygon":const e=t.getFlatInteriorPoints();h=[];for(let t=0,i=e.length;t{const n=o[2*(t+i)]===h[i*c]&&o[2*(t+i)+1]===h[i*c+1];return n||--t,n}))}this.saveTextStates_();const u=s.backgroundFill?this.createFill(this.fillStyleToState(s.backgroundFill)):null,d=s.backgroundStroke?this.createStroke(this.strokeStyleToState(s.backgroundStroke)):null;this.beginGeometry(t,e,i);let g=s.padding;if(g!=kh&&(s.scale[0]<0||s.scale[1]<0)){let t=s.padding[0],e=s.padding[1],i=s.padding[2],n=s.padding[3];s.scale[0]<0&&(e=-e,n=-n),s.scale[1]<0&&(t=-t,i=-i),g=[t,e,i,n]}const f=this.pixelRatio;this.instructions.push([my,a,r,null,NaN,NaN,NaN,1,0,0,this.textRotateWithView_,this.textRotation_,[1,1],NaN,this.declutterMode_,this.declutterImageWithText_,g==kh?kh:g.map((function(t){return t*f})),u,d,this.text_,this.textKey_,this.strokeKey_,this.fillKey_,this.textOffsetX_,this.textOffsetY_,n]);const p=1/f,m=u?u.slice(0):null;m&&(m[1]=Ih),this.hitDetectionInstructions.push([my,a,r,null,NaN,NaN,NaN,1,0,0,this.textRotateWithView_,this.textRotation_,[p,p],NaN,this.declutterMode_,this.declutterImageWithText_,g,m,d,this.text_,this.textKey_,this.strokeKey_,this.fillKey_?Ih:this.fillKey_,this.textOffsetX_,this.textOffsetY_,n]),this.endGeometry(e)}else{if(!xe(this.maxExtent,t.getExtent()))return;let n;if(h=t.getFlatCoordinates(),"LineString"==l)n=[h.length];else if("MultiLineString"==l)n=t.getEnds();else if("Polygon"==l)n=t.getEnds().slice(0,1);else if("MultiPolygon"==l){const e=t.getEndss();n=[];for(let t=0,i=e.length;tt[2]}else P=S>R;const I=Math.PI,F=[],L=w+n===e;let M;if(_=0,y=T,g=t[e=w],f=t[e+1],L){x(),M=Math.atan2(f-m,g-p),P&&(M+=M>0?-I:I);const t=(R+S)/2,e=(b+E)/2;return F[0]=[t,e,(C-s)/2,M,r],F}for(let t=0,u=(r=r.replace(/\n/g," ")).length;t0?-I:I),void 0!==M){let t=d-M;if(t+=t>I?-2*I:t<-I?2*I:0,Math.abs(t)>o)return null}M=d;const S=t;let E=0;for(;t0&&t.push("\n",""),t.push(e,""),t}function Zy(t,e,i){return i%2==0&&(t+=e),t}class Yy{constructor(t,e,i,n,r){this.overlaps=i,this.pixelRatio=e,this.resolution=t,this.alignAndScaleFill_,this.instructions=n.instructions,this.coordinates=n.coordinates,this.coordinateCache_={},this.renderedTransform_=[1,0,0,1,0,0],this.hitDetectionInstructions=n.hitDetectionInstructions,this.pixelCoordinates_=null,this.viewRotation_=0,this.fillStates=n.fillStates||{},this.strokeStates=n.strokeStates||{},this.textStates=n.textStates||{},this.widths_={},this.labels_={},this.zIndexContext_=r?new gf:null}getZIndexContext(){return this.zIndexContext_}createLabel(t,e,i,n){const r=t+e+i+n;if(this.labels_[r])return this.labels_[r];const s=n?this.strokeStates[n]:null,o=i?this.fillStates[i]:null,a=this.textStates[e],l=this.pixelRatio,h=[a.scale[0]*l,a.scale[1]*l],c=a.justify?Ay[a.justify]:$y(Array.isArray(t)?t[0]:t,a.textAlign||Dh),u=n&&s.lineWidth?s.lineWidth:0,d=Array.isArray(t)?t:String(t).split("\n").reduce(Wy,[]),{width:g,height:f,widths:p,heights:m,lineWidths:_}=Hh(a,d),y=g+u,x=[],v=(y+2)*h[0],S=(f+u)*h[1],E={width:v<0?Math.floor(v):Math.ceil(v),height:S<0?Math.floor(S):Math.ceil(S),contextInstructions:x};1==h[0]&&1==h[1]||x.push("scale",h),n&&(x.push("strokeStyle",s.strokeStyle),x.push("lineWidth",u),x.push("lineCap",s.lineCap),x.push("lineJoin",s.lineJoin),x.push("miterLimit",s.miterLimit),x.push("setLineDash",[s.lineDash]),x.push("lineDashOffset",s.lineDashOffset)),i&&x.push("fillStyle",o.fillStyle),x.push("textBaseline","middle"),x.push("textAlign","center");const w=.5-c;let T=c*y+w*u;const C=[],R=[];let b,P=0,I=0,F=0,L=0;for(let t=0,e=d.length;tt?t-l:r,x=s+h>e?e-h:s,v=g[3]+y*u[0]+g[1],S=g[0]+x*u[1]+g[2],E=m-g[3],w=_-g[0];let T;return(f||0!==c)&&(jy[0]=E,zy[0]=E,jy[1]=w,Uy[1]=w,Uy[0]=E+v,By[0]=Uy[0],By[1]=w+S,zy[1]=By[1]),0!==c?(T=tr([1,0,0,1,0,0],i,n,1,1,c,-i,-n),Kn(T,jy),Kn(T,Uy),Kn(T,By),Kn(T,zy),Yt(Math.min(jy[0],Uy[0],By[0],zy[0]),Math.min(jy[1],Uy[1],By[1],zy[1]),Math.max(jy[0],Uy[0],By[0],zy[0]),Math.max(jy[1],Uy[1],By[1],zy[1]),Gy)):Yt(Math.min(E,E+v),Math.min(w,w+S),Math.max(E,E+v),Math.max(w,w+S),Gy),d&&(m=Math.round(m),_=Math.round(_)),{drawImageX:m,drawImageY:_,drawImageW:y,drawImageH:x,originX:l,originY:h,declutterBox:{minX:Gy[0],minY:Gy[1],maxX:Gy[2],maxY:Gy[3],value:p},canvasTransform:T,scale:u}}replayImageOrLabel_(t,e,i,n,r,s,o){const a=!(!s&&!o),l=n.declutterBox,h=o?o[2]*n.scale[0]/2:0;return l.minX-h<=e[0]&&l.maxX+h>=0&&l.minY-h<=e[1]&&l.maxY+h>=0&&(a&&this.replayTextBackground_(t,jy,Uy,By,zy,s,o),Kh(t,n.canvasTransform,r,i,n.originX,n.originY,n.drawImageW,n.drawImageH,n.drawImageX,n.drawImageY,n.scale)),!0}fill_(t){const e=this.alignAndScaleFill_;if(e){const i=Kn(this.renderedTransform_,[0,0]),n=512*this.pixelRatio;t.save(),t.translate(i[0]%n,i[1]%n),1!==e&&t.scale(e,e),t.rotate(this.viewRotation_)}t.fill(),e&&t.restore()}setStrokeStyle_(t,e){t.strokeStyle=e[1],e[1]&&(t.lineWidth=e[2],t.lineCap=e[3],t.lineJoin=e[4],t.miterLimit=e[5],t.lineDashOffset=e[7],t.setLineDash(e[6]))}drawLabelWithPointPlacement_(t,e,i,n){const r=this.textStates[e],s=this.createLabel(t,e,n,i),o=this.strokeStates[i],a=this.pixelRatio,l=$y(Array.isArray(t)?t[0]:t,r.textAlign||Dh),h=Ay[r.textBaseline||Nh],c=o&&o.lineWidth?o.lineWidth:0;return{label:s,anchorX:l*(s.width/a-2*r.scale[0])+2*(.5-l)*c,anchorY:h*s.height/a+2*(.5-h)*c}}execute_(t,e,i,n,r,s,o,a){const l=this.zIndexContext_;let h;this.pixelCoordinates_&&w(i,this.renderedTransform_)?h=this.pixelCoordinates_:(this.pixelCoordinates_||(this.pixelCoordinates_=[]),h=ar(this.coordinates,0,this.coordinates.length,2,i,this.pixelCoordinates_),Hn(this.renderedTransform_,i));let c=0;const u=n.length;let d,g,f,p,m,_,y,x,v,S,E,T,C,R=0,b=0,P=0;const I=this.coordinateCache_,F=this.viewRotation_,L=Math.round(1e12*Math.atan2(-i[1],i[0]))/1e12,M={context:t,pixelRatio:this.pixelRatio,resolution:this.resolution,rotation:F},A=this.instructions!=n||this.overlaps?0:200;let O,D,N,k;for(;cA&&(this.fill_(t),b=0),P>A&&(t.stroke(),P=0),b||P||(t.beginPath(),m=NaN,_=NaN),++c;break;case dy:R=i[1];const n=h[R],u=h[R+1],w=h[R+2]-n,G=h[R+3]-u,j=Math.sqrt(w*w+G*G);t.moveTo(n+j,u),t.arc(n,u,j,0,2*Math.PI,!0),++c;break;case gy:t.closePath(),++c;break;case fy:R=i[1],d=i[2];const U=i[3],B=i[4],z=i[5];M.geometry=U,M.feature=O,c in I||(I[c]=[]);const X=I[c];z?z(h,R,d,2,X):(X[0]=h[R],X[1]=h[R+1],X.length=2),l&&(l.zIndex=i[6]),B(X,M),++c;break;case my:R=i[1],d=i[2],v=i[3],g=i[4],f=i[5];let V=i[6];const $=i[7],W=i[8],Z=i[9],Y=i[10];let H=i[11];const K=i[12];let q=i[13];p=i[14]||"declutter";const J=i[15];if(!v&&i.length>=20){S=i[19],E=i[20],T=i[21],C=i[22];const t=this.drawLabelWithPointPlacement_(S,E,T,C);v=t.label,i[3]=v;const e=i[23];g=(t.anchorX-e)*this.pixelRatio,i[4]=g;const n=i[24];f=(t.anchorY-n)*this.pixelRatio,i[5]=f,V=v.height,i[6]=V,q=v.width,i[13]=q}let Q,tt,et,it;i.length>25&&(Q=i[25]),i.length>17?(tt=i[16],et=i[17],it=i[18]):(tt=kh,et=null,it=null),Y&&L?H+=F:Y||L||(H-=F);let nt=0;for(;R!Ky.includes(t)));class Jy{constructor(t,e,i,n,r,s,o){this.maxExtent_=t,this.overlaps_=n,this.pixelRatio_=i,this.resolution_=e,this.renderBuffer_=s,this.executorsByZIndex_={},this.hitDetectionContext_=null,this.hitDetectionTransform_=[1,0,0,1,0,0],this.renderedContext_=null,this.deferredZIndexContexts_={},this.createExecutors_(r,o)}clip(t,e){const i=this.getClipCoords(e);t.beginPath(),t.moveTo(i[0],i[1]),t.lineTo(i[2],i[3]),t.lineTo(i[4],i[5]),t.lineTo(i[6],i[7]),t.clip()}createExecutors_(t,e){for(const i in t){let n=this.executorsByZIndex_[i];void 0===n&&(n={},this.executorsByZIndex_[i]=n);const r=t[i];for(const t in r){const i=r[t];n[t]=new Yy(this.resolution_,this.pixelRatio_,this.overlaps_,i,e)}}}hasExecutors(t){for(const e in this.executorsByZIndex_){const i=this.executorsByZIndex_[e];for(let e=0,n=t.length;e0){if(!s||"none"===i||"Image"!==d&&"Text"!==d||s.includes(t)){const i=(u[l]-3)/4,s=n-i%o,a=n-(i/o|0),h=r(t,e,s*s+a*a);if(h)return h}h.clearRect(0,0,o,o);break}}const f=Object.keys(this.executorsByZIndex_).map(Number);let p,m,_,x,v;for(f.sort(y),p=f.length-1;p>=0;--p){const t=f[p].toString();for(_=this.executorsByZIndex_[t],m=Hy.length-1;m>=0;--m)if(d=Hy[m],x=_[d],void 0!==x&&(v=x.executeHitDetection(h,a,i,g,c),v))return v}}getClipCoords(t){const e=this.maxExtent_;if(!e)return null;const i=e[0],n=e[1],r=e[2],s=e[3],o=[i,n,i,s,r,s,r,n];return ar(o,0,8,2,t,o),o}isEmpty(){return L(this.executorsByZIndex_)}execute(t,e,i,n,r,s,o){const a=Object.keys(this.executorsByZIndex_).map(Number);a.sort(o?x:y),s=s||Hy;const l=Hy.length;for(let h=0,c=a.length;hg.execute(t,e,i,n,r,o))):g.execute(c,e,i,n,r,o),u&&c.restore(),s){s.offset();const t=a[h]*l+Hy.indexOf(d);this.deferredZIndexContexts_[t]||(this.deferredZIndexContexts_[t]=[]),this.deferredZIndexContexts_[t].push(s)}}}}this.renderedContext_=t}getDeferredZIndexContexts(){return this.deferredZIndexContexts_}getRenderedContext(){return this.renderedContext_}renderDeferred(){const t=this.deferredZIndexContexts_,e=Object.keys(t).map(Number).sort(y);for(let i=0,n=e.length;i{t.draw(this.renderedContext_),t.clear()})),t[e[i]].length=0}}const Qy={};function tx(t){if(void 0!==Qy[t])return Qy[t];const e=2*t+1,i=t*t,n=new Array(i+1);for(let r=0;r<=t;++r)for(let s=0;s<=t;++s){const o=r*r+s*s;if(o>i)break;let a=n[o];a||(a=[],n[o]=a),a.push(4*((t+r)*e+(t+s))+3),r>0&&a.push(4*((t-r)*e+(t+s))+3),s>0&&(a.push(4*((t+r)*e+(t-s))+3),r>0&&a.push(4*((t-r)*e+(t-s))+3))}const r=[];for(let t=0,e=n.length;t{if(this.frameState&&!this.hitDetectionImageData_&&!this.animatingOrInteracting_){const t=this.frameState.size.slice(),e=this.renderedCenter_,i=this.renderedResolution_,n=this.renderedRotation_,r=this.renderedProjection_,s=this.wrappedRenderedExtent_,o=this.getLayer(),a=[],l=t[0]*ex,h=t[1]*ex;a.push(this.getRenderTransform(e,i,n,ex,l,h,0).slice());const c=o.getSource(),u=r.getExtent();if(c.getWrapX()&&r.canWrapX()&&!Vt(u,s)){let t=s[0];const r=ye(u);let o,c=0;for(;tu[2];)++c,o=r*c,a.push(this.getRenderTransform(e,i,n,ex,l,h,o).slice()),t-=r}const d=Dn();this.hitDetectionImageData_=ix(t,a,this.renderedFeatures_,o.getStyleFunction(),s,i,n,pu(i,this.renderedPixelRatio_),d?r:null)}e(nx(t,this.renderedFeatures_,this.hitDetectionImageData_))}))}forEachFeatureAtCoordinate(t,e,i,n,r){if(!this.replayGroup_)return;const s=e.viewState.resolution,o=e.viewState.rotation,a=this.getLayer(),l={},h=this.getLayer().getDeclutter();return this.replayGroup_.forEachFeatureAtCoordinate(t,s,o,i,(function(t,e,i){const s=z(t),o=l[s];if(o){if(!0!==o&&it.value)):null)}handleFontsChanged(){const t=this.getLayer();t.getVisible()&&this.replayGroup_&&t.changed()}handleStyleImageChange_(t){this.renderIfReadyAndVisible()}prepareFrame(t){const e=this.getLayer(),i=e.getSource();if(!i)return!1;const n=t.viewHints[ao],r=t.viewHints[lo],s=e.getUpdateWhileAnimating(),o=e.getUpdateWhileInteracting();if(this.ready&&!s&&n||!o&&r)return this.animatingOrInteracting_=!0,!0;this.animatingOrInteracting_=!1;const a=t.extent,l=t.viewState,h=l.projection,c=l.resolution,u=t.pixelRatio,d=e.getRevision(),g=e.getRenderBuffer();let f=e.getRenderOrder();void 0===f&&(f=fu);const p=l.center.slice(),m=Ut(a,g*c),_=m.slice(),y=[m.slice()],x=h.getExtent();if(i.getWrapX()&&h.canWrapX()&&!Vt(x,t.extent)){const t=ye(x),e=Math.max(ye(m)/2,t);m[0]=x[0]-e,m[2]=x[2]+e,hi(p,h);const i=Ce(y[0],h);i[0]x[0]&&i[2]>x[2]&&y.push([i[0]-t,i[1],i[2]-t,i[3]])}if(this.ready&&this.renderedResolution_==c&&this.renderedRevision_==d&&this.renderedRenderOrder_==f&&this.renderedFrameDeclutter_===!!t.declutter&&Vt(this.wrappedRenderedExtent_,m))return w(this.renderedExtent_,_)||(this.hitDetectionImageData_=null,this.renderedExtent_=_),this.renderedCenter_=p,this.replayGroupChanged=!1,!0;this.replayGroup_=null;const v=new Ny(mu(c,u),m,c,u),S=Dn();let E;if(S){for(let t=0,e=y.length;t{let n;const r=t.getStyleFunction()||e.getStyleFunction();if(r&&(n=r(t,c)),n){const e=this.renderFeature(t,T,n,v,E,this.getLayer().getDeclutter(),i);C=C&&!e}},b=Gn(m,h),P=i.getFeaturesInExtent(b);f&&P.sort(f);for(let t=0,e=P.length;t{if(f.getState()!==Is)return;this.image=f;const t=f.getPixelRatio(),n=bu(f.getResolution())*e/t;this.renderedResolution=n,this.coordinateToVectorPixelTransform_=tr(this.coordinateToVectorPixelTransform_,l/2,h/2,1/n,-1/n,0,-i.center[0],-i.center[1])})),f.load()}return this.image&&(this.renderedPixelToCoordinateTransform_=t.pixelToCoordinateTransform.slice()),!this.getLayer().getSource()?.loading&&!!this.image}preRender(){}postRender(){}renderDeclutter(){}forEachFeatureAtCoordinate(t,e,i,n,r){return this.vectorRenderer_?this.vectorRenderer_.forEachFeatureAtCoordinate(t,e,i,n,r):super.forEachFeatureAtCoordinate(t,e,i,n,r)}}const ox={image:["Polygon","Circle","LineString","Image","Text"],hybrid:["Polygon","LineString"],vector:[]},ax={hybrid:["Image","Text","Default"],vector:["Polygon","Circle","LineString","Image","Text","Default"]};class lx extends Cf{constructor(t,e){super(t,e),this.boundHandleStyleImageChange_=this.handleStyleImageChange_.bind(this),this.renderedLayerRevision_,this.renderedPixelToCoordinateTransform_=null,this.renderedRotation_,this.renderedOpacity_=1,this.tmpTransform_=[1,0,0,1,0,0],this.tileClipContexts_=null}drawTile(t,e,i,n,r,s,o,a){this.updateExecutorGroup_(t,e.pixelRatio,e.viewState.projection),this.tileImageNeedsRender_(t)&&this.renderTileImage_(t,e),super.drawTile(t,e,i,n,r,s,o,a)}getTile(t,e,i,n){const r=this.getOrCreateTile(t,e,i,n);if(!r)return null;const s=n.viewState.resolution,o=n.viewHints;return!!(o[ao]||o[lo])&&r.wantedResolution||(r.wantedResolution=s),r}prepareFrame(t){const e=this.getLayer().getRevision();return this.renderedLayerRevision_!==e&&(this.renderedLayerRevision_=e,this.renderedTiles.length=0),super.prepareFrame(t)}updateExecutorGroup_(t,e,i){const n=this.getLayer(),r=n.getRevision(),s=n.getRenderOrder()||null,o=t.wantedResolution,a=t.getReplayState(n);if(!a.dirty&&a.renderedResolution===o&&a.renderedRevision==r&&a.renderedRenderOrder==s)return;const l=n.getSource(),h=!!n.getDeclutter(),c=l.getTileGrid(),u=l.getTileGridForProjection(i).getTileCoordExtent(t.wrappedTileCoord),d=l.getSourceTiles(e,i,t),g=z(n);delete t.hitDetectionImageData[g],t.executorGroups[g]=[],a.dirty=!1;for(let i=0,r=d.length;it.value)):null;let m;t:for(let e=0,n=d.length;e{const n=this.getLayer(),r=n.getSource(),s=this.renderedProjection,o=s.getExtent(),a=this.renderedResolution,l=r.getTileGridForProjection(s),h=Kn(this.renderedPixelToCoordinateTransform_,t.slice()),c=l.getTileCoordForCoordAndResolution(h,a).toString(),u=this.renderedTiles.find((t=>t.tileCoord.toString()===c&&t.getState()===K));if(!u||u.loadingSourceTiles>0)return void e([]);r.getWrapX()&&s.canWrapX()&&!Vt(o,l.getTileCoordExtent(u.tileCoord))&&hi(h,s);const d=z(n),g=me(l.getTileCoordExtent(u.wrappedTileCoord)),f=[(h[0]-g[0])/a,(g[1]-h[1])/a],p=u.getSourceTiles().reduce(((t,e)=>t.concat(e.getFeatures())),[]);let m=u.hitDetectionImageData[d];if(!m){const t=Bl(l.getTileSize(l.getZForResolution(a,r.zDirection))),e=this.renderedRotation_;m=ix(t,[this.getRenderTransform(l.getTileCoordCenter(u.wrappedTileCoord),a,0,ex,t[0]*ex,t[1]*ex,0)],p,n.getStyleFunction(),l.getTileCoordExtent(u.wrappedTileCoord),u.getReplayState(n).renderedResolution,e),u.hitDetectionImageData[d]=m}e(nx(f,p,m))}))}getFeaturesInExtent(t){const e=[],i=this.getTileCache();if(0===i.getCount())return e;const n=this.getLayer().getSource().getTileGridForProjection(this.frameState.viewState.projection),r=n.getZForResolution(this.renderedResolution),s={};return i.forEach((i=>{if(i.tileCoord[0]!==r||i.getState()!==K)return;const o=i.getSourceTiles();for(let i=0,r=o.length;i=0;--e)n[e].execute(this.context,o,this.getTileRenderTransform(i,t),t.viewState.rotation,s,Ky,l)}i.globalAlpha=n}renderDeferredInternal(t){const e=this.renderedTiles,i=z(this.getLayer()),n=e.reduce(((t,e,n)=>(e.executorGroups[i].forEach((e=>t.push({executorGroup:e,index:n}))),t)),[]),r=n.map((({executorGroup:t})=>t.getDeferredZIndexContexts())),s={};for(let t=0,e=n.length;t{r.forEach(((e,i)=>{e[t]&&(e[t].forEach((t=>{const{executorGroup:e,index:r}=n[i],s=e.getRenderedContext(),o=s.globalAlpha;s.globalAlpha=this.renderedOpacity_;const a=this.tileClipContexts_[r];a&&a.draw(s),t.draw(s),a&&s.restore(),s.globalAlpha=o,t.clear()})),e[t].length=0)}))}))}getTileRenderTransform(t,e){const i=e.pixelRatio,n=e.viewState,r=n.center,s=n.resolution,o=n.rotation,a=e.size,l=Math.round(a[0]*i),h=Math.round(a[1]*i),c=this.getLayer().getSource().getTileGridForProjection(e.viewState.projection),u=t.tileCoord,d=c.getTileCoordExtent(t.wrappedTileCoord),g=c.getTileCoordExtent(u,this.tempExtent)[0]-d[0];return Zn(Jn(this.inversePixelTransform.slice(),1/i,1/i),this.getRenderTransform(r,s,o,i,l,h,g))}postRender(t,e){const i=e.viewHints,n=!(i[ao]||i[lo]);this.renderedPixelToCoordinateTransform_=e.pixelToCoordinateTransform.slice(),this.renderedRotation_=e.viewState.rotation,this.renderedOpacity_=e.layerStatesArray[e.layerIndex].opacity;const r=this.getLayer(),s=r.getRenderMode(),o=t.globalAlpha;t.globalAlpha=this.renderedOpacity_;const a=r.getDeclutter(),l=a?ax[s].filter((t=>!Ky.includes(t))):ax[s],h=e.viewState,c=h.rotation,u=r.getSource(),d=u.getTileGridForProjection(h.projection).getZForResolution(h.resolution,u.zDirection),g=this.renderedTiles,f=[],p=[],m=[],_=z(r);let y=!0;for(let i=g.length-1;i>=0;--i){const s=g[i];y=y&&!s.getReplayState(r).dirty;const o=s.executorGroups[_].filter((t=>t.hasExecutors(l)));if(0===o.length)continue;const h=this.getTileRenderTransform(s,e),u=s.tileCoord[0];let x=!1;const v=o[0].getClipCoords(h);let S,E=t;if(v){S=new gf,E=S.getContext();for(let t=0,e=f.length;t ${cm.RENDER_EXTENT}[2] ||\n v_mapCoord[1] > ${cm.RENDER_EXTENT}[3]\n ) {\n discard;\n }\n\n vec4 velocity = texture2D(${cm.TILE_TEXTURE_ARRAY}[0], v_textureCoord);\n gl_FragColor = vec4((velocity.xy + ${fm.MAX_SPEED}) / (2.0 * ${fm.MAX_SPEED}), 0, 1);\n }\n`,fx=`\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n attribute vec2 ${pm};\n\n varying vec2 ${_m};\n\n void main() {\n ${_m} = ${pm};\n gl_Position = vec4(1.0 - 2.0 * ${pm}, 0, 1);\n }\n`,px=`\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n uniform sampler2D ${fm.TEXTURE};\n uniform float ${fm.OPACITY};\n\n varying vec2 ${_m};\n\n void main() {\n vec4 color = texture2D(${fm.TEXTURE}, 1.0 - ${_m});\n gl_FragColor = vec4(floor(255.0 * color * ${fm.OPACITY}) / 255.0);\n }\n`,mx=`\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n uniform sampler2D ${fm.POSITION_TEXTURE};\n uniform sampler2D ${fm.VELOCITY_TEXTURE};\n uniform float ${fm.RANDOM_SEED};\n uniform float ${fm.SPEED_FACTOR};\n uniform float ${fm.DROP_RATE};\n uniform float ${fm.DROP_RATE_BUMP};\n uniform vec2 ${fm.ROTATION};\n uniform vec2 ${fm.VIEWPORT_SIZE_PX};\n\n varying vec2 ${_m};\n\n // pseudo-random generator\n const vec3 randConstants = vec3(12.9898, 78.233, 4375.85453);\n\n float rand(const vec2 co) {\n float t = dot(randConstants.xy, co);\n return fract(sin(t) * (randConstants.z + t));\n }\n\n void main() {\n vec4 positionColor = texture2D(${fm.POSITION_TEXTURE}, ${_m});\n\n // decode particle position from pixel RGBA\n vec2 particlePosition = vec2(\n positionColor.r / 255.0 + positionColor.b,\n positionColor.g / 255.0 + positionColor.a\n );\n\n vec4 velocityColor = texture2D(${fm.VELOCITY_TEXTURE}, particlePosition);\n if (velocityColor.a == 0.0) {\n discard;\n }\n\n float vx = 2.0 * velocityColor.r - 1.0;\n float vy = 2.0 * velocityColor.g - 1.0;\n\n // normalized veloicty (magnitude 0 - 1)\n vec2 velocity = vec2(\n vx * ${fm.ROTATION}.x - vy * ${fm.ROTATION}.y,\n vx * ${fm.ROTATION}.y + vy * ${fm.ROTATION}.x\n );\n\n // account for aspect ratio (square particle position texture, non-square map)\n float aspectRatio = ${fm.VIEWPORT_SIZE_PX}.x / ${fm.VIEWPORT_SIZE_PX}.y;\n vec2 offset = vec2(velocity.x / aspectRatio, velocity.y) * ${fm.SPEED_FACTOR};\n\n // update particle position, wrapping around the edge\n particlePosition = fract(1.0 + particlePosition + offset);\n\n // a random seed to use for the particle drop\n vec2 seed = (particlePosition + ${_m}) * ${fm.RANDOM_SEED};\n\n // drop rate is a chance a particle will restart at random position, to avoid degeneration\n float dropRate = ${fm.DROP_RATE} + length(velocity) * ${fm.DROP_RATE_BUMP};\n float drop = step(1.0 - dropRate, rand(seed));\n\n vec2 randomPosition = vec2(rand(seed + 1.3), rand(seed + 2.1));\n particlePosition = mix(particlePosition, randomPosition, drop);\n\n // encode the new particle position back into RGBA\n gl_FragColor = vec4(\n fract(particlePosition * 255.0),\n floor(particlePosition * 255.0) / 255.0\n );\n }\n`,_x=`\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n attribute float ${mm};\n\n uniform sampler2D ${fm.POSITION_TEXTURE};\n uniform float ${fm.PARTICLE_COUNT_SQRT};\n\n varying vec2 ${_m};\n\n void main() {\n vec4 color = texture2D(\n ${fm.POSITION_TEXTURE},\n vec2(\n fract(${mm} / ${fm.PARTICLE_COUNT_SQRT}),\n floor(${mm} / ${fm.PARTICLE_COUNT_SQRT}) / ${fm.PARTICLE_COUNT_SQRT}\n )\n );\n\n ${_m} = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a\n );\n\n gl_PointSize = 1.0;\n gl_Position = vec4(\n 2.0 * ${_m}.x - 1.0,\n 2.0 * ${_m}.y - 1.0,\n 0,\n 1\n );\n }\n`;const yx=[];class xx extends Pf{constructor(t){const e=Object.assign({},t);if(delete e.maxSpeed,delete e.speedFactor,delete e.particles,super(e),this.style_=t.style||{},!(t.maxSpeed>0))throw new Error("maxSpeed is required");this.maxSpeed_=t.maxSpeed,this.speedFactor_=t.speedFactor,this.particles_=t.particles,this.styleVariables_=this.style_.variables||{},this.addChangeListener(Ba,this.handleSourceUpdate_)}handleSourceUpdate_(){this.hasRenderer()&&this.getRenderer().clearCache()}updateStyleVariables(t){Object.assign(this.styleVariables_,t),this.changed()}getSources(t,e){const i=this.getSource();return yx[0]=i,yx}createRenderer(){const t=function(t){const e={variables:{},properties:{},functions:{},bandCount:0,featureId:!1,geometryType:!1},i=[];if(void 0!==t.color){const n=F_(e,t.color,Wl);i.push(`color = ${n};`)}const n=Object.keys(e.variables);if(n.length>1&&!t.variables)throw new Error(`Missing variables in style (expected ${e.variables})`);const r={};for(const e of n){if(!(e in t.variables))throw new Error(`Missing '${e}' in style variables`);r[Dm(e)]=function(){let i=t.variables[e];return"string"==typeof i&&(i=Am(i)),void 0!==i?i:-9999999}}const s=Object.keys(r).map((function(t){return`uniform float ${t};`})),o=Object.keys(e.functions).map((function(t){return e.functions[t]})),a=`\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n uniform sampler2D ${fm.VELOCITY_TEXTURE};\n uniform float ${fm.MAX_SPEED};\n uniform vec2 ${fm.ROTATION};\n\n ${s.join("\n")}\n\n varying vec2 ${_m};\n \n ${o.join("\n")}\n\n void main() {\n vec4 velocityColor = texture2D(${fm.VELOCITY_TEXTURE}, ${_m});\n\n float vx = mix(-${fm.MAX_SPEED}, ${fm.MAX_SPEED}, velocityColor.r);\n float vy = mix(-${fm.MAX_SPEED}, ${fm.MAX_SPEED}, velocityColor.g);\n\n vec2 velocity = vec2(\n vx * ${fm.ROTATION}.x - vy * ${fm.ROTATION}.y,\n vx * ${fm.ROTATION}.y + vy * ${fm.ROTATION}.x\n );\n\n float a_prop_speed = length(velocity);\n\n vec4 color;\n\n ${i.join("\n")}\n\n if (color.a == 0.0) {\n discard;\n }\n\n gl_FragColor = color;\n }\n `;return{tileVertexShader:dx,tileFragmentShader:gx,particleColorVertexShader:_x,particleColorFragmentShader:a,particlePositionVertexShader:fx,particlePositionFragmentShader:mx,textureVertexShader:fx,textureFragmentShader:px}}(this.style_);return new ym(this,{...t,cacheSize:this.getCacheSize(),maxSpeed:this.maxSpeed_,speedFactor:this.speedFactor_,particles:this.particles_})}}function vx(t,e,i){const n=[];let r=t(0),s=t(1),o=e(r),a=e(s);const l=[s,r],h=[a,o],c=[1,0],u={};let d,g,f,p,m,_,y=1e5;for(;--y>0&&c.length>0;)f=c.pop(),r=l.pop(),o=h.pop(),_=f.toString(),_ in u||(n.push(o[0],o[1]),u[_]=!0),p=c.pop(),s=l.pop(),a=h.pop(),m=(f+p)/2,d=t(m),g=e(d),Pe(g[0],g[1],o[0],o[1],a[0],a[1])1&&!t.variables)throw new Error(`Missing variables in style (expected ${n.variables})`);for(let e=0;e ${cm.RENDER_EXTENT}[2] ||\n v_mapCoord[1] > ${cm.RENDER_EXTENT}[3]\n ) {\n discard;\n }\n\n vec4 color = texture2D(${cm.TILE_TEXTURE_ARRAY}[0], v_textureCoord);\n\n ${r.join("\n")}\n\n gl_FragColor = color;\n gl_FragColor.rgb *= gl_FragColor.a;\n gl_FragColor *= ${cm.TRANSITION_ALPHA};\n }`,uniforms:s,paletteTextures:n.paletteTextures}}class Lx extends Pf{constructor(t){const e=(t=t?Object.assign({},t):{}).style||{};delete t.style,super(t),this.sources_=t.sources,this.renderedSource_=null,this.renderedResolution_=NaN,this.style_=e,this.styleVariables_=this.style_.variables||{},this.handleSourceUpdate_(),this.addChangeListener(Ba,this.handleSourceUpdate_)}getSources(t,e){const i=this.getSource();return this.sources_?"function"==typeof this.sources_?this.sources_(t,e):this.sources_:i?[i]:[]}getRenderSource(){return this.renderedSource_||this.getSource()}getSourceState(){const t=this.getRenderSource();return t?t.getState():"undefined"}handleSourceUpdate_(){this.hasRenderer()&&this.getRenderer().clearCache();const t=this.getSource();if(t)if("loading"===t.getState()){const e=()=>{"ready"===t.getState()&&(t.removeEventListener("change",e),this.setStyle(this.style_))};t.addEventListener("change",e)}else this.setStyle(this.style_)}getSourceBandCount_(){const t=Number.MAX_SAFE_INTEGER,e=this.getSources([-t,-t,t,t],t);return e&&e.length&&"bandCount"in e[0]?e[0].bandCount:4}createRenderer(){const t=Fx(this.style_,this.getSourceBandCount_());return new gm(this,{vertexShader:t.vertexShader,fragmentShader:t.fragmentShader,uniforms:t.uniforms,cacheSize:this.getCacheSize(),paletteTextures:t.paletteTextures})}renderSources(t,e){const i=this.getRenderer();let n;for(let r=0,s=e.length;r{"ready"==e.getState()&&(e.removeEventListener("change",t),this.changed())};e.addEventListener("change",t)}r=r&&"ready"==i}const s=this.renderSources(t,n);if(this.getRenderer().renderComplete&&r)return this.renderedResolution_=i.resolution,s;if(this.renderedResolution_>.5*i.resolution){const e=this.getSources(t.extent,this.renderedResolution_).filter((t=>!n.includes(t)));if(e.length>0)return this.renderSources(t,e)}return s}setStyle(t){if(this.styleVariables_=t.variables||{},this.style_=t,this.hasRenderer()){const t=Fx(this.style_,this.getSourceBandCount_());this.getRenderer().reset({vertexShader:t.vertexShader,fragmentShader:t.fragmentShader,uniforms:t.uniforms,paletteTextures:t.paletteTextures}),this.changed()}}updateStyleVariables(t){Object.assign(this.styleVariables_,t),this.changed()}}Lx.prototype.dispose;const Mx="addfeatures";class Ax extends M{constructor(t,e,i,n){super(t),this.features=i,this.file=e,this.projection=n}}const Ox="drawstart",Dx="drawend",Nx="drawabort";class kx extends M{constructor(t,e){super(t),this.feature=e}}function Gx(t,e){return Ie(t[0],t[1],e[0],e[1])}function jx(t,e){const i=t.length;return e<0?t[e+i]:e>=i?t[e-i]:t[e]}function Ux(t,e,i){let n,r;eo){return Gx(Wx(t,n),Wx(t,r))}let a=0;if(n=i?n-=i:n<0&&(n+=i);let s=n+1;s>=i&&(s-=i);const o=t[n],a=o[0],l=o[1],h=t[s];return[a+(h[0]-a)*r,l+(h[1]-l)*r]}function Zx(){const t=lc();return function(e,i){return t[e.getGeometry().getType()]}}const Yx="extentchanged";class Hx extends M{constructor(t){super(Yx),this.extent=t}}function Kx(){const t=lc();return function(e,i){return t.Polygon}}function qx(){const t=lc();return function(e,i){return t.Point}}function Jx(t){return function(e){return jt([t,e])}}function Qx(t,e){return t[0]==e[0]?function(i){return jt([t,[i[0],e[1]]])}:t[1]==e[1]?function(i){return jt([t,[e[0],i[1]]])}:null}function tv(t){return parseFloat(t)}function ev(t){return function(t){return De(t,5)}(t).toString()}function iv(t,e){return!isNaN(t)&&t!==tv(ev(e))}const nv=[0,0,0,0],rv=[],sv="modifystart",ov="modifyend";class av extends M{constructor(t,e,i){super(t),this.features=e,this.mapBrowserEvent=i}}function lv(t,e){return t.index-e.index}function hv(t,e,i){const n=e.geometry;if("Circle"===n.getType()){let r=n;if(1===e.index){const e=Dn();e&&(r=r.clone().transform(e,i));const n=si(r.getCenter(),kn(t,i)),s=Math.sqrt(n)-r.getRadius();return s*s}}const r=kn(t,i);return rv[0]=kn(e.segment[0],i),rv[1]=kn(e.segment[1],i),ai(r,rv)}function cv(t,e,i){const n=e.geometry;if("Circle"===n.getType()&&1===e.index){let e=n;const r=Dn();return r&&(e=e.clone().transform(r,i)),Nn(e.getClosestPoint(kn(t,i)),i)}const r=kn(t,i);return rv[0]=kn(e.segment[0],i),rv[1]=kn(e.segment[1],i),Nn(Qe(r,rv),i)}function uv(){const t=lc();return function(e,i){return t.Point}}const dv="select";class gv extends M{constructor(t,e,i,n){super(t),this.selected=e,this.deselected=i,this.mapBrowserEvent=n}}const fv={};class pv extends Wo{constructor(t){let e;if(super(),this.on,this.once,this.un,t=t||{},this.boundAddFeature_=this.addFeature_.bind(this),this.boundRemoveFeature_=this.removeFeature_.bind(this),this.condition_=t.condition?t.condition:ra,this.addCondition_=t.addCondition?t.addCondition:na,this.removeCondition_=t.removeCondition?t.removeCondition:na,this.toggleCondition_=t.toggleCondition?t.toggleCondition:aa,this.multi_=!!t.multi&&t.multi,this.filter_=t.filter?t.filter:C,this.hitTolerance_=t.hitTolerance?t.hitTolerance:0,this.style_=void 0!==t.style?t.style:function(){const t=lc();return E(t.Polygon,t.LineString),E(t.GeometryCollection,t.LineString),function(e){return e.getGeometry()?t[e.getGeometry().getType()]:null}}(),this.features_=t.features||new Z,t.layers)if("function"==typeof t.layers)e=t.layers;else{const i=t.layers;e=function(t){return i.includes(t)}}else e=C;this.layerFilter_=e,this.featureLayerAssociation_={}}addFeatureLayerAssociation_(t,e){this.featureLayerAssociation_[z(t)]=e}getFeatures(){return this.features_}getHitTolerance(){return this.hitTolerance_}getLayer(t){return this.featureLayerAssociation_[z(t)]}setHitTolerance(t){this.hitTolerance_=t}setMap(i){this.getMap()&&this.style_&&this.features_.forEach(this.restorePreviousStyle_.bind(this)),super.setMap(i),i?(this.features_.addEventListener(t,this.boundAddFeature_),this.features_.addEventListener(e,this.boundRemoveFeature_),this.style_&&this.features_.forEach(this.applySelectedStyle_.bind(this))):(this.features_.removeEventListener(t,this.boundAddFeature_),this.features_.removeEventListener(e,this.boundRemoveFeature_))}addFeature_(t){const e=t.element;if(this.style_&&this.applySelectedStyle_(e),!this.getLayer(e)){const t=this.getMap().getAllLayers().find((function(t){if(t instanceof wx&&t.getSource()&&t.getSource().hasFeature(e))return t}));t&&this.addFeatureLayerAssociation_(e,t)}}removeFeature_(t){this.style_&&this.restorePreviousStyle_(t.element)}getStyle(){return this.style_}applySelectedStyle_(t){const e=z(t);e in fv||(fv[e]=t.getStyle()),t.setStyle(this.style_)}restorePreviousStyle_(t){const e=this.getMap().getInteractions().getArray();for(let i=e.length-1;i>=0;--i){const n=e[i];if(n!==this&&n instanceof pv&&n.getStyle()&&-1!==n.getFeatures().getArray().lastIndexOf(t))return void t.setStyle(n.getStyle())}const i=z(t);t.setStyle(fv[i]),delete fv[i]}removeFeatureLayerAssociation_(t){delete this.featureLayerAssociation_[z(t)]}handleEvent(t){if(!this.condition_(t))return!0;const e=this.addCondition_(t),i=this.removeCondition_(t),n=this.toggleCondition_(t),r=!e&&!i&&!n,s=t.map,o=this.getFeatures(),a=[],l=[];if(r){F(this.featureLayerAssociation_),s.forEachFeatureAtPixel(t.pixel,((t,e)=>{if(t instanceof Lt&&this.filter_(t,e))return this.addFeatureLayerAssociation_(t,e),l.push(t),!this.multi_}),{layerFilter:this.layerFilter_,hitTolerance:this.hitTolerance_});for(let t=o.getLength()-1;t>=0;--t){const e=o.item(t),i=l.indexOf(e);i>-1?l.splice(i,1):(o.remove(e),a.push(e))}0!==l.length&&o.extend(l)}else{s.forEachFeatureAtPixel(t.pixel,((t,r)=>{if(t instanceof Lt&&this.filter_(t,r))return!e&&!n||o.getArray().includes(t)?(i||n)&&o.getArray().includes(t)&&(a.push(t),this.removeFeatureLayerAssociation_(t)):(this.addFeatureLayerAssociation_(t,r),l.push(t)),!this.multi_}),{layerFilter:this.layerFilter_,hitTolerance:this.hitTolerance_});for(let t=a.length-1;t>=0;--t)o.remove(a[t]);o.extend(l)}return(l.length>0||a.length>0)&&this.dispatchEvent(new gv(dv,l,a,t)),!0}}const mv="snap",_v="unsnap";class yv extends M{constructor(t,e){super(t),this.vertex=e.vertex,this.vertexPixel=e.vertexPixel,this.feature=e.feature,this.segment=e.segment}}const xv={Circle(t,e){let i=t;const n=Dn();n&&(i=i.clone().transform(n,e));const r=gs(i);return n&&r.transform(e,n),xv.Polygon(r)},GeometryCollection(t,e){const i=[],n=t.getGeometriesArray();for(let t=0;t[[t.getFlatCoordinates().slice(0,2)]],Polygon(t){const e=[],i=t.getFlatCoordinates(),n=t.getStride(),r=t.getEnds();let s=0;for(let t=0,o=r.length;tAv({...t,geometry:e}))).flat();const n="MultiPolygon"===i.type?"Polygon":i.type;if("GeometryCollection"===n||"Circle"===n)throw new Error("Unsupported geometry type: "+n);const r=i.layout.length;return Fv(new Md(n,"Polygon"===n?function(t,e,i){return Array.isArray(e[0])?(os(t,0,e,i)||ls(t=t.slice(),0,e,i),t):(ss(t,0,e,i)||as(t=t.slice(),0,e,i),t)}(i.flatCoordinates,i.ends,r):i.flatCoordinates,i.ends?.flat(),r,t.properties||{},t.id).enableSimplifyTransformed(),!1,e)}function Ov(t,e){if(!t)return null;if(Array.isArray(t)){const i=t.map((t=>Ov(t,e)));return new Td(i)}return Fv(new(0,Mv[t.type])(t.flatCoordinates,t.layout||"XY",t.ends),!1,e)}class Dv extends Iv{constructor(){super()}getType(){return"json"}readFeature(t,e){return this.readFeatureFromObject(Nv(t),this.getReadOptions(t,e))}readFeatures(t,e){return this.readFeaturesFromObject(Nv(t),this.getReadOptions(t,e))}readFeatureFromObject(t,e){return U()}readFeaturesFromObject(t,e){return U()}readGeometry(t,e){return this.readGeometryFromObject(Nv(t),this.getReadOptions(t,e))}readGeometryFromObject(t,e){return U()}readProjection(t){return this.readProjectionFromObject(Nv(t))}readProjectionFromObject(t){return U()}writeFeature(t,e){return JSON.stringify(this.writeFeatureObject(t,e))}writeFeatureObject(t,e){return U()}writeFeatures(t,e){return JSON.stringify(this.writeFeaturesObject(t,e))}writeFeaturesObject(t,e){return U()}writeGeometry(t,e){return JSON.stringify(this.writeGeometryObject(t,e))}writeGeometryObject(t,e){return U()}}function Nv(t){if("string"==typeof t){const e=JSON.parse(t);return e||null}return null!==t?t:null}const kv={Point:function(t){let e;e=void 0!==t.m&&void 0!==t.z?new Xr([t.x,t.y,t.z,t.m],"XYZM"):void 0!==t.z?new Xr([t.x,t.y,t.z],"XYZ"):void 0!==t.m?new Xr([t.x,t.y,t.m],"XYM"):new Xr([t.x,t.y]);return e},LineString:function(t){const e=Uv(t);return new bd(t.paths[0],e)},Polygon:function(t){const e=Uv(t);return new cs(t.rings,e)},MultiPoint:function(t){const e=Uv(t);return new Id(t.points,e)},MultiLineString:function(t){const e=Uv(t);return new Pd(t.paths,e)},MultiPolygon:function(t){const e=Uv(t);return new Fd(t.rings,e)}},Gv={Point:function(t,e){const i=t.getCoordinates();let n;const r=t.getLayout();if("XYZ"===r)n={x:i[0],y:i[1],z:i[2]};else if("XYM"===r)n={x:i[0],y:i[1],m:i[2]};else if("XYZM"===r)n={x:i[0],y:i[1],z:i[2],m:i[3]};else{if("XY"!==r)throw new Error("Invalid geometry layout");n={x:i[0],y:i[1]}}return n},LineString:function(t,e){const i=Bv(t);return{hasZ:i.hasZ,hasM:i.hasM,paths:[t.getCoordinates()]}},Polygon:function(t,e){const i=Bv(t);return{hasZ:i.hasZ,hasM:i.hasM,rings:t.getCoordinates(!1)}},MultiPoint:function(t,e){const i=Bv(t);return{hasZ:i.hasZ,hasM:i.hasM,points:t.getCoordinates()}},MultiLineString:function(t,e){const i=Bv(t);return{hasZ:i.hasZ,hasM:i.hasM,paths:t.getCoordinates()}},MultiPolygon:function(t,e){const i=Bv(t),n=t.getCoordinates(!1),r=[];for(let t=0;t=0;e--)r.push(n[t][e]);return{hasZ:i.hasZ,hasM:i.hasM,rings:r}}};function jv(t,e){if(!t)return null;let i;if("number"==typeof t.x&&"number"==typeof t.y)i="Point";else if(t.points)i="MultiPoint";else if(t.paths){i=1===t.paths.length?"LineString":"MultiLineString"}else if(t.rings){const e=t,n=Uv(e),r=function(t,e){const i=[],n=[],r=[];let s,o;for(s=0,o=t.length;s=0;s--){const i=n[s][0];if(Vt(new zr(i).getExtent(),new zr(t).getExtent())){n[s].push(t),e=!0;break}}e||n.push([t.reverse()])}return n}(e.rings,n);1===r.length?(i="Polygon",t=Object.assign({},t,{rings:r[0]})):(i="MultiPolygon",t=Object.assign({},t,{rings:r}))}return Fv((0,kv[i])(t),!1,e)}function Uv(t){let e="XY";return!0===t.hasZ&&!0===t.hasM?e="XYZM":!0===t.hasZ?e="XYZ":!0===t.hasM&&(e="XYM"),e}function Bv(t){const e=t.getLayout();return{hasZ:"XYZ"===e||"XYZM"===e,hasM:"XYM"===e||"XYZM"===e}}function zv(t,e){return(0,Gv[t.getType()])(Fv(t,!0,e),e)}class Xv extends Iv{constructor(){super(),this.xmlSerializer_=Sp()}getType(){return"xml"}readFeature(t,e){if(!t)return null;if("string"==typeof t){const i=np(t);return this.readFeatureFromDocument(i,e)}return ep(t)?this.readFeatureFromDocument(t,e):this.readFeatureFromNode(t,e)}readFeatureFromDocument(t,e){const i=this.readFeaturesFromDocument(t,e);return i.length>0?i[0]:null}readFeatureFromNode(t,e){return null}readFeatures(t,e){if(!t)return[];if("string"==typeof t){const i=np(t);return this.readFeaturesFromDocument(i,e)}return ep(t)?this.readFeaturesFromDocument(t,e):this.readFeaturesFromNode(t,e)}readFeaturesFromDocument(t,e){const i=[];for(let n=t.firstChild;n;n=n.nextSibling)n.nodeType==Node.ELEMENT_NODE&&E(i,this.readFeaturesFromNode(n,e));return i}readFeaturesFromNode(t,e){return U()}readGeometry(t,e){if(!t)return null;if("string"==typeof t){const i=np(t);return this.readGeometryFromDocument(i,e)}return ep(t)?this.readGeometryFromDocument(t,e):this.readGeometryFromNode(t,e)}readGeometryFromDocument(t,e){return null}readGeometryFromNode(t,e){return null}readProjection(t){if(!t)return null;if("string"==typeof t){const e=np(t);return this.readProjectionFromDocument(e)}return ep(t)?this.readProjectionFromDocument(t):this.readProjectionFromNode(t)}readProjectionFromDocument(t){return this.dataProjection}readProjectionFromNode(t){return this.dataProjection}writeFeature(t,e){const i=this.writeFeatureNode(t,e);return this.xmlSerializer_.serializeToString(i)}writeFeatureNode(t,e){return null}writeFeatures(t,e){const i=this.writeFeaturesNode(t,e);return this.xmlSerializer_.serializeToString(i)}writeFeaturesNode(t,e){return null}writeGeometry(t,e){const i=this.writeGeometryNode(t,e);return this.xmlSerializer_.serializeToString(i)}writeGeometryNode(t,e){return null}}const Vv="http://www.opengis.net/gml",$v=/^\s*$/;class Wv extends Xv{constructor(t){super(),t=t||{},this.featureType=t.featureType,this.featureNS=t.featureNS,this.srsName=t.srsName,this.schemaLocation="",this.FEATURE_COLLECTION_PARSERS={},this.FEATURE_COLLECTION_PARSERS[this.namespace]={featureMember:sp(this.readFeaturesInternal),featureMembers:op(this.readFeaturesInternal)},this.supportedMediaTypes=["application/gml+xml"]}readFeaturesInternal(t,e){const i=t.localName;let n=null;if("FeatureCollection"==i)n=mp([],this.FEATURE_COLLECTION_PARSERS,t,e,this);else if("featureMembers"==i||"featureMember"==i||"member"==i){const r=e[0];let s=r.featureType,o=r.featureNS;const a="p",l="p0";if(!s&&t.childNodes){s=[],o={};for(let e=0,i=t.childNodes.length;e0&&!(t instanceof gr)){t={_content_:t};for(let e=0;e").forEach(((e,i,n)=>{i0&&(e=">"+e),iS(t,e)})):t.appendChild(Ep().createTextNode(e))}const cS=Vv+" http://schemas.opengis.net/gml/2.1.2/feature.xsd",uS={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"};class dS extends Wv{constructor(t){super(t=t||{}),this.FEATURE_COLLECTION_PARSERS[Vv].featureMember=sp(this.readFeaturesInternal),this.schemaLocation=t.schemaLocation?t.schemaLocation:cS}readFlatCoordinates(t,e){const i=Qf(t,!1).replace(/^\s*|\s*$/g,""),n=e[0].srsName;let r="enu";if(n){const t=xn(n);t&&(r=t.getAxisOrientation())}const s=i.trim().split(/\s+/),o=[];for(let t=0,e=s.length;t0){e[e.length-1].push(...i)}},outerBoundaryIs:function(t,e){const i=mp(void 0,Ew,t,e);if(i){e[e.length-1][0]=i}}});function hw(t,e){const i=mp({},iw,t,e),n=mp([null],lw,t,e);if(n&&n[0]){const t=n[0],e=[t.length];for(let i=1,r=n.length;i0;let o;const a=r.href;let l,h,c;a?o=a:s&&(o=TE);let u="bottom-left";const d=i.hotSpot;let g;d?(l=[d.x,d.y],h=d.xunits,c=d.yunits,u=d.origin):/^https?:\/\/maps\.(?:google|gstatic)\.com\//.test(o)&&(o.includes("pushpin")?(l=vE,h=SE,c=EE):o.includes("arrow-reverse")?(l=[54,42],h=SE,c=EE):o.includes("paddle")&&(l=[32,1],h=SE,c=EE));const f=r.x,p=r.y;let m;void 0!==f&&void 0!==p&&(g=[f,p]);const _=r.w,y=r.h;let x;void 0!==_&&void 0!==y&&(m=[_,y]);const v=i.heading;void 0!==v&&(x=Me(v));const S=i.scale,E=i.color;if(s){o==TE&&(m=wE);const t=new ic({anchor:l,anchorOrigin:u,anchorXUnits:h,anchorYUnits:c,crossOrigin:this.crossOrigin_,offset:g,offsetOrigin:"bottom-left",rotation:x,scale:S,size:m,src:this.iconUrlFunction_(o),color:E}),e=t.getScaleArray()[0],i=t.getSize();if(null===i){const i=t.getImageState();if(i===bs||i===Ps){const n=function(){const i=t.getImageState();if(i!==bs&&i!==Ps){const i=t.getSize();if(i&&2==i.length){const n=OE(i);t.setScale(e*n)}t.unlistenImageChange(n)}};t.listenImageChange(n),i===bs&&t.load()}}else if(2==i.length){const n=OE(i);t.setScale(e*n)}n.imageStyle=t}else n.imageStyle=RE},LabelStyle:function(t,e){const i=mp({},$E,t,e);if(!i)return;const n=e[e.length-1],r=new cc({fill:new tc({color:"color"in i?i.color:xE}),scale:i.scale});n.textStyle=r},LineStyle:function(t,e){const i=mp({},WE,t,e);if(!i)return;const n=e[e.length-1],r=new nc({color:"color"in i?i.color:xE,width:"width"in i?i.width:1});n.strokeStyle=r},PolyStyle:function(t,e){const i=mp({},ZE,t,e);if(!i)return;const n=e[e.length-1],r=new tc({color:"color"in i?i.color:xE});n.fillStyle=r;const s=i.fill;void 0!==s&&(n.fill=s);const o=i.outline;void 0!==o&&(n.outline=o)}});function uw(t,e){const i=mp({},cw,t,e,this);if(!i)return null;let n="fillStyle"in i?i.fillStyle:CE;const r=i.fill;let s;void 0===r||r||(n=null),"imageStyle"in i?i.imageStyle!=RE&&(s=i.imageStyle):s=bE;const o="textStyle"in i?i.textStyle:FE,a="strokeStyle"in i?i.strokeStyle:IE,l=i.outline;return void 0===l||l?[new rc({fill:n,image:s,stroke:a,text:o,zIndex:void 0})]:[new rc({geometry:function(t){const e=t.getGeometry(),i=e.getType();if("GeometryCollection"===i){return new Td(e.getGeometriesArrayRecursive().filter((function(t){const e=t.getType();return"Polygon"!==e&&"MultiPolygon"!==e})))}if("Polygon"!==i&&"MultiPolygon"!==i)return e},fill:n,image:s,stroke:a,text:o,zIndex:void 0}),new rc({geometry:function(t){const e=t.getGeometry(),i=e.getType();if("GeometryCollection"===i){return new Td(e.getGeometriesArrayRecursive().filter((function(t){const e=t.getType();return"Polygon"===e||"MultiPolygon"===e})))}if("Polygon"===i||"MultiPolygon"===i)return e},fill:n,stroke:null,zIndex:void 0})]}function dw(t,e){const i=e.length,n=new Array(e.length),r=new Array(e.length),s=new Array(e.length);let o,a,l;o=!1,a=!1,l=!1;for(let t=0;t0){const t=gp(r,o);yp(n,Zw,Hw,[{names:o,values:t}],i)}const u=i[0];let d=e.getGeometry();d&&(d=Fv(d,!0,u)),yp(n,Zw,Gw,[d],i)}const qw=fp(cE,["extrude","tessellate","altitudeMode","coordinates"]),Jw=fp(cE,{extrude:hp(eS),tessellate:hp(eS),altitudeMode:hp(hS),coordinates:hp((function(t,e,i){const n=i[i.length-1],r=n.layout,s=n.stride;let o;if("XY"==r||"XYM"==r)o=2;else{if("XYZ"!=r&&"XYZM"!=r)throw new Error("Invalid geometry layout");o=3}const a=e.length;let l="";if(a>0){l+=e[0];for(let t=1;t>3,r=this.pos;this.type=7&i,t(n,e,this),this.pos===r&&this.skip(i)}return e}readMessage(t,e){return this.readFields(t,e,this.readVarint()+this.pos)}readFixed32(){const t=this.dataView.getUint32(this.pos,!0);return this.pos+=4,t}readSFixed32(){const t=this.dataView.getInt32(this.pos,!0);return this.pos+=4,t}readFixed64(){const t=this.dataView.getUint32(this.pos,!0)+this.dataView.getUint32(this.pos+4,!0)*hT;return this.pos+=8,t}readSFixed64(){const t=this.dataView.getUint32(this.pos,!0)+this.dataView.getInt32(this.pos+4,!0)*hT;return this.pos+=8,t}readFloat(){const t=this.dataView.getFloat32(this.pos,!0);return this.pos+=4,t}readDouble(){const t=this.dataView.getFloat64(this.pos,!0);return this.pos+=8,t}readVarint(t){const e=this.buf;let i,n;return n=e[this.pos++],i=127&n,n<128?i:(n=e[this.pos++],i|=(127&n)<<7,n<128?i:(n=e[this.pos++],i|=(127&n)<<14,n<128?i:(n=e[this.pos++],i|=(127&n)<<21,n<128?i:(n=e[this.pos],i|=(15&n)<<28,function(t,e,i){const n=i.buf;let r,s;if(s=n[i.pos++],r=(112&s)>>4,s<128)return gT(t,r,e);if(s=n[i.pos++],r|=(127&s)<<3,s<128)return gT(t,r,e);if(s=n[i.pos++],r|=(127&s)<<10,s<128)return gT(t,r,e);if(s=n[i.pos++],r|=(127&s)<<17,s<128)return gT(t,r,e);if(s=n[i.pos++],r|=(127&s)<<24,s<128)return gT(t,r,e);if(s=n[i.pos++],r|=(1&s)<<31,s<128)return gT(t,r,e);throw new Error("Expected varint not more than 10 bytes")}(i,t,this)))))}readVarint64(){return this.readVarint(!0)}readSVarint(){const t=this.readVarint();return t%2==1?(t+1)/-2:t/2}readBoolean(){return Boolean(this.readVarint())}readString(){const t=this.readVarint()+this.pos,e=this.pos;return this.pos=t,t-e>=12&&uT?uT.decode(this.buf.subarray(e,t)):function(t,e,i){let n="",r=e;for(;r239?4:e>223?3:e>191?2:1;if(r+h>i)break;1===h?e<128&&(l=e):2===h?(s=t[r+1],128==(192&s)&&(l=(31&e)<<6|63&s,l<=127&&(l=null))):3===h?(s=t[r+1],o=t[r+2],128==(192&s)&&128==(192&o)&&(l=(15&e)<<12|(63&s)<<6|63&o,(l<=2047||l>=55296&&l<=57343)&&(l=null))):4===h&&(s=t[r+1],o=t[r+2],a=t[r+3],128==(192&s)&&128==(192&o)&&128==(192&a)&&(l=(15&e)<<18|(63&s)<<12|(63&o)<<6|63&a,(l<=65535||l>=1114112)&&(l=null))),null===l?(l=65533,h=1):l>65535&&(l-=65536,n+=String.fromCharCode(l>>>10&1023|55296),l=56320|1023&l),n+=String.fromCharCode(l),r+=h}return n}(this.buf,e,t)}readBytes(){const t=this.readVarint()+this.pos,e=this.buf.subarray(this.pos,t);return this.pos=t,e}readPackedVarint(t=[],e){const i=this.readPackedEnd();for(;this.pos127;);else if(2===e)this.pos=this.readVarint()+this.pos;else if(5===e)this.pos+=4;else{if(1!==e)throw new Error(`Unimplemented type: ${e}`);this.pos+=8}}writeTag(t,e){this.writeVarint(t<<3|e)}realloc(t){let e=this.length||16;for(;e268435455||t<0?function(t,e){let i,n;t>=0?(i=t%4294967296|0,n=t/4294967296|0):(i=~(-t%4294967296),n=~(-t/4294967296),4294967295^i?i=i+1|0:(i=0,n=n+1|0));if(t>=0x10000000000000000||t<-0x10000000000000000)throw new Error("Given varint doesn't fit into 10 bytes");e.realloc(10),function(t,e,i){i.buf[i.pos++]=127&t|128,t>>>=7,i.buf[i.pos++]=127&t|128,t>>>=7,i.buf[i.pos++]=127&t|128,t>>>=7,i.buf[i.pos++]=127&t|128,t>>>=7,i.buf[i.pos]=127&t}(i,0,e),function(t,e){const i=(7&t)<<4;if(e.buf[e.pos++]|=i|((t>>>=3)?128:0),!t)return;if(e.buf[e.pos++]=127&t|((t>>>=7)?128:0),!t)return;if(e.buf[e.pos++]=127&t|((t>>>=7)?128:0),!t)return;if(e.buf[e.pos++]=127&t|((t>>>=7)?128:0),!t)return;if(e.buf[e.pos++]=127&t|((t>>>=7)?128:0),!t)return;e.buf[e.pos++]=127&t}(n,e)}(t,this):(this.realloc(4),this.buf[this.pos++]=127&t|(t>127?128:0),t<=127||(this.buf[this.pos++]=127&(t>>>=7)|(t>127?128:0),t<=127||(this.buf[this.pos++]=127&(t>>>=7)|(t>127?128:0),t<=127||(this.buf[this.pos++]=t>>>7&127))))}writeSVarint(t){this.writeVarint(t<0?2*-t-1:2*t)}writeBoolean(t){this.writeVarint(+t)}writeString(t){t=String(t),this.realloc(4*t.length),this.pos++;const e=this.pos;this.pos=function(t,e,i){for(let n,r,s=0;s55295&&n<57344){if(!r){n>56319||s+1===e.length?(t[i++]=239,t[i++]=191,t[i++]=189):r=n;continue}if(n<56320){t[i++]=239,t[i++]=191,t[i++]=189,r=n;continue}n=r-55296<<10|n-56320|65536,r=null}else r&&(t[i++]=239,t[i++]=191,t[i++]=189,r=null);n<128?t[i++]=n:(n<2048?t[i++]=n>>6|192:(n<65536?t[i++]=n>>12|224:(t[i++]=n>>18|240,t[i++]=n>>12&63|128),t[i++]=n>>6&63|128),t[i++]=63&n|128)}return i}(this.buf,t,this.pos);const i=this.pos-e;i>=128&&fT(e,i,this),this.pos=e-1,this.writeVarint(i),this.pos+=i}writeFloat(t){this.realloc(4),this.dataView.setFloat32(this.pos,t,!0),this.pos+=4}writeDouble(t){this.realloc(8),this.dataView.setFloat64(this.pos,t,!0),this.pos+=8}writeBytes(t){const e=t.length;this.writeVarint(e),this.realloc(e);for(let i=0;i=128&&fT(i,n,this),this.pos=i-1,this.writeVarint(n),this.pos+=n}writeMessage(t,e,i){this.writeTag(t,2),this.writeRawMessage(e,i)}writePackedVarint(t,e){e.length&&this.writeMessage(t,pT,e)}writePackedSVarint(t,e){e.length&&this.writeMessage(t,mT,e)}writePackedBoolean(t,e){e.length&&this.writeMessage(t,xT,e)}writePackedFloat(t,e){e.length&&this.writeMessage(t,_T,e)}writePackedDouble(t,e){e.length&&this.writeMessage(t,yT,e)}writePackedFixed32(t,e){e.length&&this.writeMessage(t,vT,e)}writePackedSFixed32(t,e){e.length&&this.writeMessage(t,ST,e)}writePackedFixed64(t,e){e.length&&this.writeMessage(t,ET,e)}writePackedSFixed64(t,e){e.length&&this.writeMessage(t,wT,e)}writeBytesField(t,e){this.writeTag(t,2),this.writeBytes(e)}writeFixed32Field(t,e){this.writeTag(t,5),this.writeFixed32(e)}writeSFixed32Field(t,e){this.writeTag(t,5),this.writeSFixed32(e)}writeFixed64Field(t,e){this.writeTag(t,1),this.writeFixed64(e)}writeSFixed64Field(t,e){this.writeTag(t,1),this.writeSFixed64(e)}writeVarintField(t,e){this.writeTag(t,0),this.writeVarint(e)}writeSVarintField(t,e){this.writeTag(t,0),this.writeSVarint(e)}writeStringField(t,e){this.writeTag(t,2),this.writeString(e)}writeFloatField(t,e){this.writeTag(t,5),this.writeFloat(e)}writeDoubleField(t,e){this.writeTag(t,1),this.writeDouble(e)}writeBooleanField(t,e){this.writeVarintField(t,+e)}}function gT(t,e,i){return i?4294967296*e+(t>>>0):4294967296*(e>>>0)+(t>>>0)}function fT(t,e,i){const n=e<=16383?1:e<=2097151?2:e<=268435455?3:Math.floor(Math.log(e)/(7*Math.LN2));i.realloc(n);for(let e=i.pos-1;e>=t;e--)i.buf[e+n]=i.buf[e]}function pT(t,e){for(let i=0;i>3)?i.readString():2===t?i.readFloat():3===t?i.readDouble():4===t?i.readVarint64():5===t?i.readVarint():6===t?i.readSVarint():7===t?i.readBoolean():null;e.values.push(n)}}function RT(t,e,i){if(1==t)e.id=i.readVarint();else if(2==t){const t=i.readVarint()+i.pos;for(;i.pos>1):i>>1}return e}function nC(t){let e="";for(let i=0,n=t.length;i=32;)e=63+(32|31&t),i+=String.fromCharCode(e),t>>=5;return e=t+63,i+=String.fromCharCode(e),i}const oC={Point:function(t,e,i){const n=t.coordinates;e&&i&&uC(n,e,i);return new Xr(n)},LineString:function(t,e){const i=aC(t.arcs,e);return new bd(i)},Polygon:function(t,e){const i=[];for(let n=0,r=t.arcs.length;n0&&i.pop(),n>=0){const t=e[n];for(let e=0,n=t.length;e=0;--e)i.push(t[e].slice(0))}return i}function lC(t,e,i,n,r,s,o){const a=t.geometries,l=[];for(let t=0,h=a.length;t=2,"At least 2 conditions are required")}}class fC extends gC{constructor(t){super("And",Array.prototype.slice.call(arguments))}}class pC extends dC{constructor(t,e,i){if(super("BBOX"),this.geometryName=t,this.extent=e,4!==e.length)throw new Error("Expected an extent with four values ([minX, minY, maxX, maxY])");this.srsName=i}}class mC extends dC{constructor(t,e,i,n){super(t),this.geometryName=e||"the_geom",this.geometry=i,this.srsName=n}}class _C extends mC{constructor(t,e,i){super("Contains",t,e,i)}}class yC extends mC{constructor(t,e,i,n,r){super("DWithin",t,e,r),this.distance=i,this.unit=n}}class xC extends mC{constructor(t,e,i){super("Disjoint",t,e,i)}}class vC extends dC{constructor(t,e){super(t),this.propertyName=e}}class SC extends vC{constructor(t,e,i){super("During",t),this.begin=e,this.end=i}}class EC extends vC{constructor(t,e,i,n){super(t,e),this.expression=i,this.matchCase=n}}class wC extends EC{constructor(t,e,i){super("PropertyIsEqualTo",t,e,i)}}class TC extends EC{constructor(t,e){super("PropertyIsGreaterThan",t,e)}}class CC extends EC{constructor(t,e){super("PropertyIsGreaterThanOrEqualTo",t,e)}}class RC extends mC{constructor(t,e,i){super("Intersects",t,e,i)}}class bC extends vC{constructor(t,e,i){super("PropertyIsBetween",t),this.lowerBoundary=e,this.upperBoundary=i}}class PC extends vC{constructor(t,e,i,n,r,s){super("PropertyIsLike",t),this.pattern=e,this.wildCard=void 0!==i?i:"*",this.singleChar=void 0!==n?n:".",this.escapeChar=void 0!==r?r:"!",this.matchCase=s}}class IC extends vC{constructor(t){super("PropertyIsNull",t)}}class FC extends EC{constructor(t,e){super("PropertyIsLessThan",t,e)}}class LC extends EC{constructor(t,e){super("PropertyIsLessThanOrEqualTo",t,e)}}class MC extends dC{constructor(t){super("Not"),this.condition=t}}class AC extends EC{constructor(t,e,i){super("PropertyIsNotEqualTo",t,e,i)}}class OC extends gC{constructor(t){super("Or",Array.prototype.slice.call(arguments))}}class DC extends dC{constructor(t){super("ResourceId"),this.rid=t}}class NC extends mC{constructor(t,e,i){super("Within",t,e,i)}}function kC(t){const e=[null].concat(Array.prototype.slice.call(arguments));return new(Function.prototype.bind.apply(fC,e))}function GC(t,e,i){return new pC(t,e,i)}const jC={"http://www.opengis.net/gml":{boundedBy:lp(Wv.prototype.readExtentElement,"bounds")},"http://www.opengis.net/wfs/2.0":{member:sp(Wv.prototype.readFeaturesInternal)}},UC={"http://www.opengis.net/wfs":{totalInserted:lp(Jv),totalUpdated:lp(Jv),totalDeleted:lp(Jv)},"http://www.opengis.net/wfs/2.0":{totalInserted:lp(Jv),totalUpdated:lp(Jv),totalDeleted:lp(Jv)}},BC={"http://www.opengis.net/wfs":{TransactionSummary:lp(JC,"transactionSummary"),InsertResults:lp(iR,"insertIds")},"http://www.opengis.net/wfs/2.0":{TransactionSummary:lp(JC,"transactionSummary"),InsertResults:lp(iR,"insertIds")}},zC={"http://www.opengis.net/wfs":{PropertyName:hp(hS)},"http://www.opengis.net/wfs/2.0":{PropertyName:hp(hS)}},XC={"http://www.opengis.net/wfs":{Insert:hp(nR),Update:hp(aR),Delete:hp(oR),Property:hp(lR),Native:hp(hR)},"http://www.opengis.net/wfs/2.0":{Insert:hp(nR),Update:hp(aR),Delete:hp(oR),Property:hp(lR),Native:hp(hR)}},VC="feature",$C="http://www.w3.org/2000/xmlns/",WC={"2.0.0":"http://www.opengis.net/ogc/1.1","1.1.0":"http://www.opengis.net/ogc","1.0.0":"http://www.opengis.net/ogc"},ZC={"2.0.0":"http://www.opengis.net/wfs/2.0","1.1.0":"http://www.opengis.net/wfs","1.0.0":"http://www.opengis.net/wfs"},YC={"2.0.0":"http://www.opengis.net/fes/2.0","1.1.0":"http://www.opengis.net/fes","1.0.0":"http://www.opengis.net/fes"},HC={"2.0.0":"http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd","1.1.0":"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd","1.0.0":"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd"},KC={"2.0.0":_S,"1.1.0":pS,"1.0.0":dS};function qC(t,e,i,n){yp(n,XC,up(t),e,i)}function JC(t,e){return mp({},UC,t,e)}const QC={"http://www.opengis.net/ogc":{FeatureId:sp((function(t,e){return t.getAttribute("fid")}))},"http://www.opengis.net/ogc/1.1":{FeatureId:sp((function(t,e){return t.getAttribute("fid")}))}};function tR(t,e){pp(QC,t,e)}const eR={"http://www.opengis.net/wfs":{Feature:tR},"http://www.opengis.net/wfs/2.0":{Feature:tR}};function iR(t,e){return mp([],eR,t,e)}function nR(t,e,i){const n=i[i.length-1],r=n.featureType,s=n.featureNS,o=n.gmlVersion,a=Jf(s,r);t.appendChild(a),2===o?dS.prototype.writeFeatureElement(a,e,i):3===o?pS.prototype.writeFeatureElement(a,e,i):_S.prototype.writeFeatureElement(a,e,i)}function rR(t,e,i){const n=i[i.length-1].version,r=WC[n],s=Jf(r,"Filter"),o=Jf(r,"FeatureId");s.appendChild(o),o.setAttribute("fid",e),t.appendChild(s)}function sR(t,e){const i=(t=t||VC)+":";return e.startsWith(i)?e:i+e}function oR(t,e,i){const n=i[i.length-1];Ft(void 0!==e.getId(),"Features must have an id set");const r=n.featureType,s=n.featurePrefix,o=n.featureNS,a=sR(s,r);t.setAttribute("typeName",a),t.setAttributeNS($C,"xmlns:"+s,o);const l=e.getId();void 0!==l&&rR(t,l,i)}function aR(t,e,i){const n=i[i.length-1];Ft(void 0!==e.getId(),"Features must have an id set");const r=n.version,s=n.featureType,o=n.featurePrefix,a=n.featureNS,l=sR(o,s),h=e.getGeometryName();t.setAttribute("typeName",l),t.setAttributeNS($C,"xmlns:"+o,a);const c=e.getId();if(void 0!==c){const s=e.getKeys(),o=[];for(let t=0,i=s.length;t0,i=this.readUint32(e),n=Math.floor((268435455&i)/1e3),r=Boolean(2147483648&i)||1===n||3===n,s=Boolean(1073741824&i)||2===n||3===n,o=Boolean(536870912&i),a=(268435455&i)%1e3,l=["XY",r?"Z":"",s?"M":""].join(""),h=o?this.readUint32(e):null;if(void 0!==t&&t!==a)throw new Error("Unexpected WKB geometry type "+a);if(this.initialized_){if(this.isLittleEndian_!==e)throw new Error("Inconsistent endian");if(this.layout_!==l)throw new Error("Inconsistent geometry layout");if(h&&this.srid_!==h)throw new Error("Inconsistent coordinate system (SRID)")}else this.isLittleEndian_=e,this.hasZ_=r,this.hasM_=s,this.layout_=l,this.srid_=h,this.initialized_=!0;return a}readWkbPayload(t){switch(t){case IR:return this.readPoint();case FR:return this.readLineString();case LR:case GR:return this.readPolygon();case MR:return this.readMultiPoint();case AR:return this.readMultiLineString();case OR:case NR:case kR:return this.readMultiPolygon();case DR:return this.readGeometryCollection();default:throw new Error("Unsupported WKB geometry type "+t+" is found")}}readWkbBlock(t){return this.readWkbPayload(this.readWkbHeader(t))}readWkbCollection(t,e){const i=this.readUint32(),n=[];for(let r=0;r({[e]:t[i]}))));for(const t of this.layout_)this.writeDouble(t in i?i[t]:this.nodata_[t])}writeLineString(t,e){this.writeUint32(t.length);for(let i=0;it+e[0]),0),e=new ArrayBuffer(t),i=new DataView(e);let n=0;return this.writeQueue_.forEach((t=>{switch(t[0]){case 1:i.setUint8(n,t[1]);break;case 4:i.setUint32(n,t[1],this.isLittleEndian_);break;case 8:i.setFloat64(n,t[1],this.isLittleEndian_)}n+=t[0]})),e}}function BR(t){return"string"==typeof t?function(t){const e=new Uint8Array(t.length/2);for(let i=0;i="a"&&t<="z"||t>="A"&&t<="Z"}isNumeric_(t,e){return e=void 0!==e&&e,t>="0"&&t<="9"||"."==t&&!e}isWhiteSpace_(t){return" "==t||"\t"==t||"\r"==t||"\n"==t}nextChar_(){return this.wkt.charAt(++this.index_)}nextToken(){const t=this.nextChar_(),e=this.index_;let i,n=t;if("("==t)i=WR;else if(","==t)i=HR;else if(")"==t)i=ZR;else if(this.isNumeric_(t)||"-"==t)i=YR,n=this.readNumber_();else if(this.isAlpha_(t))i=$R,n=this.readText_();else{if(this.isWhiteSpace_(t))return this.nextToken();if(""!==t)throw new Error("Unexpected character: "+t);i=KR}return{position:e,value:n,type:i}}readNumber_(){let t;const e=this.index_;let i=!1,n=!1;do{"."==t?i=!0:"e"!=t&&"E"!=t||(n=!0),t=this.nextChar_()}while(this.isNumeric_(t,i)||!n&&("e"==t||"E"==t)||n&&("-"==t||"+"==t));return parseFloat(this.wkt.substring(e,this.index_--))}readText_(){let t;const e=this.index_;do{t=this.nextChar_()}while(this.isAlpha_(t));return this.wkt.substring(e,this.index_--).toUpperCase()}}class QR{constructor(t){this.lexer_=t,this.token_={position:0,type:VR},this.layout_="XY"}consume_(){this.token_=this.lexer_.nextToken()}isTokenType(t){return this.token_.type==t}match(t){const e=this.isTokenType(t);return e&&this.consume_(),e}parse(){return this.consume_(),this.parseGeometry_()}parseGeometryLayout_(){let t="XY";const e=this.token_;if(this.isTokenType($R)){const i=e.value;"Z"===i?t="XYZ":"M"===i?t="XYM":"ZM"===i&&(t="XYZM"),"XY"!==t&&this.consume_()}return t}parseGeometryCollectionText_(){if(this.match(WR)){const t=[];do{t.push(this.parseGeometry_())}while(this.match(HR));if(this.match(ZR))return t}throw new Error(this.formatErrorMessage_())}parsePointText_(){if(this.match(WR)){const t=this.parsePoint_();if(this.match(ZR))return t}throw new Error(this.formatErrorMessage_())}parseLineStringText_(){if(this.match(WR)){const t=this.parsePointList_();if(this.match(ZR))return t}throw new Error(this.formatErrorMessage_())}parsePolygonText_(){if(this.match(WR)){const t=this.parseLineStringTextList_();if(this.match(ZR))return t}throw new Error(this.formatErrorMessage_())}parseMultiPointText_(){if(this.match(WR)){let t;if(t=this.token_.type==WR?this.parsePointTextList_():this.parsePointList_(),this.match(ZR))return t}throw new Error(this.formatErrorMessage_())}parseMultiLineStringText_(){if(this.match(WR)){const t=this.parseLineStringTextList_();if(this.match(ZR))return t}throw new Error(this.formatErrorMessage_())}parseMultiPolygonText_(){if(this.match(WR)){const t=this.parsePolygonTextList_();if(this.match(ZR))return t}throw new Error(this.formatErrorMessage_())}parsePoint_(){const t=[],e=this.layout_.length;for(let i=0;i0&&(n+=" "+e)}return 0===i.length?n+" "+XR:n+"("+i+")"}const sb=[null,"http://www.opengis.net/wms","http://www.opengis.net/sld"];function ob(t){return Ke(t[0].version,"1.3")>=0}const ab=fp(sb,{Service:lp((function(t,e){return mp({},ob(e)?ub:cb,t,e)})),Capability:lp((function(t,e){return mp({},lb,t,e)}))}),lb=fp(sb,{Request:lp((function(t,e){return mp({},Sb,t,e)})),Exception:lp((function(t,e){return mp([],pb,t,e)})),Layer:lp((function(t,e){const i=mp({},ob(e)?yb:_b,t,e);if(void 0===i.Layer)return Object.assign(i,Ib(t,e));return i})),UserDefinedSymbolization:lp((function(t,e){return{SupportSLD:!!Yv(t.getAttribute("SupportSLD")),UserLayer:!!Yv(t.getAttribute("UserLayer")),UserStyle:!!Yv(t.getAttribute("UserStyle")),RemoteWFS:!!Yv(t.getAttribute("RemoteWFS")),InlineFeatureData:!!Yv(t.getAttribute("InlineFeatureData")),RemoteWCS:!!Yv(t.getAttribute("RemoteWCS"))}}))});const hb={Name:lp(tS),Title:lp(tS),Abstract:lp(tS),KeywordList:lp(Ab),OnlineResource:lp(OT),ContactInformation:lp((function(t,e){return mp({},db,t,e)})),Fees:lp(tS),AccessConstraints:lp(tS)},cb=fp(sb,hb),ub=fp(sb,{...hb,LayerLimit:lp(Jv),MaxWidth:lp(Jv),MaxHeight:lp(Jv)}),db=fp(sb,{ContactPersonPrimary:lp((function(t,e){return mp({},gb,t,e)})),ContactPosition:lp(tS),ContactAddress:lp((function(t,e){return mp({},fb,t,e)})),ContactVoiceTelephone:lp(tS),ContactFacsimileTelephone:lp(tS),ContactElectronicMailAddress:lp(tS)}),gb=fp(sb,{ContactPerson:lp(tS),ContactOrganization:lp(tS)}),fb=fp(sb,{AddressType:lp(tS),Address:lp(tS),City:lp(tS),StateOrProvince:lp(tS),PostCode:lp(tS),Country:lp(tS)}),pb=fp(sb,{Format:sp(tS)}),mb={Name:lp(tS),Title:lp(tS),Abstract:lp(tS),KeywordList:lp(Ab),BoundingBox:ap(Pb),Dimension:ap((function(t,e){const i={name:t.getAttribute("name"),units:t.getAttribute("units"),unitSymbol:t.getAttribute("unitSymbol")};ob(e)&&Object.assign(i,{default:t.getAttribute("default"),multipleValues:Yv(t.getAttribute("multipleValues")),nearestValue:Yv(t.getAttribute("nearestValue")),current:Yv(t.getAttribute("current")),values:tS(t)});return i})),Attribution:lp((function(t,e){return mp({},xb,t,e)})),AuthorityURL:ap((function(t,e){const i=Fb(t,e);if(i)return i.name=t.getAttribute("name"),i;return})),Identifier:ap(tS),MetadataURL:ap((function(t,e){const i=Fb(t,e);if(i)return i.type=t.getAttribute("type"),i;return})),DataURL:ap(Fb),FeatureListURL:ap(Fb),Style:ap((function(t,e){return mp({},Cb,t,e)})),Layer:ap(Ib)},_b=fp(sb,{...mb,SRS:ap(tS),Extent:lp((function(t,e){return{name:t.getAttribute("name"),default:t.getAttribute("default"),nearestValue:Yv(t.getAttribute("nearestValue"))}})),ScaleHint:ap((function(t,e){return{min:qv(t.getAttribute("min")),max:qv(t.getAttribute("max"))}})),LatLonBoundingBox:lp(((t,e)=>Pb(t,e,!1))),Layer:ap(Ib)}),yb=fp(sb,{...mb,CRS:ap(tS),EX_GeographicBoundingBox:lp((function(t,e){const i=mp({},vb,t,e);if(!i)return;const n=i.westBoundLongitude,r=i.southBoundLatitude,s=i.eastBoundLongitude,o=i.northBoundLatitude;if(void 0===n||void 0===r||void 0===s||void 0===o)return;return[n,r,s,o]})),MinScaleDenominator:lp(Kv),MaxScaleDenominator:lp(Kv),Layer:ap(Ib)}),xb=fp(sb,{Title:lp(tS),OnlineResource:lp(OT),LogoURL:lp(Mb)}),vb=fp(sb,{westBoundLongitude:lp(Kv),eastBoundLongitude:lp(Kv),southBoundLatitude:lp(Kv),northBoundLatitude:lp(Kv)}),Sb=fp(sb,{GetCapabilities:lp(Lb),GetMap:lp(Lb),GetFeatureInfo:lp(Lb),DescribeLayer:lp(Lb),GetLegendGraphic:lp(Lb)}),Eb=fp(sb,{Format:ap(tS),DCPType:ap((function(t,e){return mp({},wb,t,e)}))}),wb=fp(sb,{HTTP:lp((function(t,e){return mp({},Tb,t,e)}))}),Tb=fp(sb,{Get:lp(Fb),Post:lp(Fb)}),Cb=fp(sb,{Name:lp(tS),Title:lp(tS),Abstract:lp(tS),LegendURL:ap(Mb),StyleSheetURL:lp(Fb),StyleURL:lp(Fb)}),Rb=fp(sb,{Format:lp(tS),OnlineResource:lp(OT)}),bb=fp(sb,{Keyword:sp(tS)});function Pb(t,e,i=!0){const n={extent:[qv(t.getAttribute("minx")),qv(t.getAttribute("miny")),qv(t.getAttribute("maxx")),qv(t.getAttribute("maxy"))],res:[qv(t.getAttribute("resx")),qv(t.getAttribute("resy"))]};return i?(ob(e)?n.crs=t.getAttribute("CRS"):n.srs=t.getAttribute("SRS"),n):n}function Ib(t,e){const i=ob(e),n=e[e.length-1],r=mp({},i?yb:_b,t,e);if(!r)return;let s=Yv(t.getAttribute("queryable"));void 0===s&&(s=n.queryable),r.queryable=void 0!==s&&s;let o=Qv(t.getAttribute("cascaded"));void 0===o&&(o=n.cascaded),r.cascaded=o;let a=Yv(t.getAttribute("opaque"));void 0===a&&(a=n.opaque),r.opaque=void 0!==a&&a;let l=Yv(t.getAttribute("noSubsets"));void 0===l&&(l=n.noSubsets),r.noSubsets=void 0!==l&&l;let h=qv(t.getAttribute("fixedWidth"));h||(h=n.fixedWidth),r.fixedWidth=h;let c=qv(t.getAttribute("fixedHeight"));c||(c=n.fixedHeight),r.fixedHeight=c;const u=["Style","AuthorityURL"];i?u.push("CRS"):u.push("SRS","Dimension"),u.forEach((function(t){if(t in n){const e=r[t]||[];r[t]=e.concat(n[t])}}));const d=["BoundingBox","Attribution"];return i?d.push("Dimension","EX_GeographicBoundingBox","MinScaleDenominator","MaxScaleDenominator"):d.push("LatLonBoundingBox","ScaleHint","Extent"),d.forEach((function(t){if(!(t in r)){const e=n[t];r[t]=e}})),r}function Fb(t,e){return mp({},Rb,t,e)}function Lb(t,e){return mp({},Eb,t,e)}function Mb(t,e){const i=Fb(t,e);if(i){const e=[Qv(t.getAttribute("width")),Qv(t.getAttribute("height"))];return i.size=e,i}}function Ab(t,e){return mp([],bb,t,e)}const Ob=[null,"http://www.opengis.net/wmts/1.0"],Db=[null,"http://www.opengis.net/ows/1.1"],Nb=fp(Ob,{Contents:lp((function(t,e){return mp({},kb,t,e)}))});const kb=fp(Ob,{Layer:ap((function(t,e){return mp({},Gb,t,e)})),TileMatrixSet:ap((function(t,e){return mp({},$b,t,e)}))}),Gb=fp(Ob,{Style:ap((function(t,e){const i=mp({},jb,t,e);if(!i)return;const n="true"===t.getAttribute("isDefault");return i.isDefault=n,i})),Format:ap(tS),TileMatrixSetLink:ap((function(t,e){return mp({},Ub,t,e)})),Dimension:ap((function(t,e){return mp({},Xb,t,e)})),ResourceURL:ap((function(t,e){const i=t.getAttribute("format"),n=t.getAttribute("template"),r=t.getAttribute("resourceType"),s={};i&&(s.format=i);n&&(s.template=n);r&&(s.resourceType=r);return s}))},fp(Db,{Title:lp(tS),Abstract:lp(tS),WGS84BoundingBox:lp(Zb),BoundingBox:ap((function(t,e){const i=t.getAttribute("crs"),n=mp([],Vb,t,e);if(2!=n.length)return;return{extent:jt(n),crs:i}})),Identifier:lp(tS)})),jb=fp(Ob,{LegendURL:ap((function(t,e){const i={};return i.format=t.getAttribute("format"),i.href=OT(t),i}))},fp(Db,{Title:lp(tS),Identifier:lp(tS)})),Ub=fp(Ob,{TileMatrixSet:lp(tS),TileMatrixSetLimits:lp((function(t,e){return mp([],Bb,t,e)}))}),Bb=fp(Ob,{TileMatrixLimits:sp((function(t,e){return mp({},zb,t,e)}))}),zb=fp(Ob,{TileMatrix:lp(tS),MinTileRow:lp(Jv),MaxTileRow:lp(Jv),MinTileCol:lp(Jv),MaxTileCol:lp(Jv)}),Xb=fp(Ob,{Default:lp(tS),Value:ap(tS)},fp(Db,{Identifier:lp(tS)})),Vb=fp(Db,{LowerCorner:sp(Yb),UpperCorner:sp(Yb)}),$b=fp(Ob,{WellKnownScaleSet:lp(tS),TileMatrix:ap((function(t,e){return mp({},Wb,t,e)}))},fp(Db,{SupportedCRS:lp(tS),Identifier:lp(tS),BoundingBox:lp(Zb)})),Wb=fp(Ob,{TopLeftCorner:lp(Yb),ScaleDenominator:lp(Kv),TileWidth:lp(Jv),TileHeight:lp(Jv),MatrixWidth:lp(Jv),MatrixHeight:lp(Jv)},fp(Db,{Identifier:lp(tS)}));function Zb(t,e){const i=mp([],Vb,t,e);if(2==i.length)return jt(i)}function Yb(t,e){const i=tS(t).split(/\s+/);if(!i||2!=i.length)return;const n=+i[0],r=+i[1];return isNaN(n)||isNaN(r)?void 0:[n,r]}const Hb=["fullscreenchange","webkitfullscreenchange"],Kb="enterfullscreen",qb="leavefullscreen";function Jb(t){const e=t.body;return!!(e.webkitRequestFullscreen||e.requestFullscreen&&t.fullscreenEnabled)}function Qb(t){return!(!t.webkitIsFullScreen&&!t.fullscreenElement)}function tP(t){t.requestFullscreen?t.requestFullscreen():t.webkitRequestFullscreen&&t.webkitRequestFullscreen()}const eP="projection",iP="coordinateFormat";const nP=.75,rP=.1;const sP="units",oP=[1,2,5],aP=25.4/.28;const lP=0,hP=1;var cP={};return cP.Collection=Z,cP.Collection.CollectionEvent=W,cP.DataTile=It,cP.DataTile.asArrayLike=Tt,cP.DataTile.asImageLike=wt,cP.DataTile.disposedError=Ct,cP.DataTile.toArray=bt,cP.Disposable=m,cP.Feature=Lt,cP.Feature.createStyleFunction=Mt,cP.Geolocation=class extends V{constructor(t){super(),this.on,this.once,this.un,t=t||{},this.position_=null,this.transform_=mn,this.watchId_=void 0,this.addChangeListener(Ss,this.handleProjectionChanged_),this.addChangeListener(ws,this.handleTrackingChanged_),void 0!==t.projection&&this.setProjection(t.projection),void 0!==t.trackingOptions&&this.setTrackingOptions(t.trackingOptions),this.setTracking(void 0!==t.tracking&&t.tracking)}disposeInternal(){this.setTracking(!1),super.disposeInternal()}handleProjectionChanged_(){const t=this.getProjection();t&&(this.transform_=Pn(xn("EPSG:4326"),t),this.position_&&this.set(vs,this.transform_(this.position_)))}handleTrackingChanged_(){if("geolocation"in navigator){const t=this.getTracking();t&&void 0===this.watchId_?this.watchId_=navigator.geolocation.watchPosition(this.positionChange_.bind(this),this.positionError_.bind(this),this.getTrackingOptions()):t||void 0===this.watchId_||(navigator.geolocation.clearWatch(this.watchId_),this.watchId_=void 0)}}positionChange_(t){const e=t.coords;this.set(ps,e.accuracy),this.set(_s,null===e.altitude?void 0:e.altitude),this.set(ys,null===e.altitudeAccuracy?void 0:e.altitudeAccuracy),this.set(xs,null===e.heading?void 0:Me(e.heading)),this.position_?(this.position_[0]=e.longitude,this.position_[1]=e.latitude):this.position_=[e.longitude,e.latitude];const i=this.transform_(this.position_);this.set(vs,i.slice()),this.set(Es,null===e.speed?void 0:e.speed);const n=us(this.position_,e.accuracy);n.applyTransform(this.transform_),this.set(ms,n),this.changed()}positionError_(t){this.dispatchEvent(new Rs(t))}getAccuracy(){return this.get(ps)}getAccuracyGeometry(){return this.get(ms)||null}getAltitude(){return this.get(_s)}getAltitudeAccuracy(){return this.get(ys)}getHeading(){return this.get(xs)}getPosition(){return this.get(vs)}getProjection(){return this.get(Ss)}getSpeed(){return this.get(Es)}getTracking(){return this.get(ws)}getTrackingOptions(){return this.get(Ts)}setProjection(t){this.set(Ss,xn(t))}setTracking(t){this.set(ws,t)}setTrackingOptions(t){this.set(Ts,t)}},cP.Geolocation.GeolocationError=Rs,cP.Image=Ms,cP.Image.decode=Ns,cP.Image.decodeFallback=Ds,cP.Image.listenImage=As,cP.Image.load=Os,cP.ImageCanvas=ks,cP.ImageTile=Gs,cP.Kinetic=js,cP.Map=Xc,cP.MapBrowserEvent=Bs,cP.MapBrowserEventHandler=Zs,cP.MapEvent=Us,cP.Object=V,cP.Object.ObjectEvent=X,cP.Observable=G,cP.Observable.unByKey=j,cP.Overlay=Hc,cP.Tile=nt,cP.TileQueue=so,cP.TileQueue.getTilePriority=oo,cP.TileRange=Kc,cP.TileRange.createOrUpdate=qc,cP.VectorRenderTile=Qc,cP.VectorTile=eu,cP.View=wo,cP.View.createCenterConstraint=Co,cP.View.createResolutionConstraint=Ro,cP.View.createRotationConstraint=bo,cP.View.isNoopAnimation=Po,cP.array={},cP.array.ascending=y,cP.array.binarySearch=_,cP.array.descending=x,cP.array.equals=w,cP.array.extend=E,cP.array.isSorted=T,cP.array.linearFindNearest=v,cP.array.remove=function(t,e){const i=t.indexOf(e),n=i>-1;return n&&t.splice(i,1),n},cP.array.reverseSubArray=S,cP.array.stableSort=function(t,e){const i=t.length,n=Array(t.length);let r;for(r=0;r$e.info||console.log(...t)},cP.console.setLevel=function(t){We=$e[t]},cP.console.warn=Ze,cP.control={},cP.control.Attribution=Bo,cP.control.Control=Uo,cP.control.FullScreen=class extends Uo{constructor(t){t=t||{},super({element:document.createElement("div"),target:t.target}),this.on,this.once,this.un,this.keys_=void 0!==t.keys&&t.keys,this.source_=t.source,this.isInFullscreen_=!1,this.boundHandleMapTargetChange_=this.handleMapTargetChange_.bind(this),this.cssClassName_=void 0!==t.className?t.className:"ol-full-screen",this.documentListeners_=[],this.activeClassName_=void 0!==t.activeClassName?t.activeClassName.split(" "):[this.cssClassName_+"-true"],this.inactiveClassName_=void 0!==t.inactiveClassName?t.inactiveClassName.split(" "):[this.cssClassName_+"-false"];const e=void 0!==t.label?t.label:"⤢";this.labelNode_="string"==typeof e?document.createTextNode(e):e;const i=void 0!==t.labelActive?t.labelActive:"×";this.labelActiveNode_="string"==typeof i?document.createTextNode(i):i;const n=t.tipLabel?t.tipLabel:"Toggle full-screen";this.button_=document.createElement("button"),this.button_.title=n,this.button_.setAttribute("type","button"),this.button_.appendChild(this.labelNode_),this.button_.addEventListener(o,this.handleClick_.bind(this),!1),this.setClassName_(this.button_,this.isInFullscreen_),this.element.className=`${this.cssClassName_} ${Mo} ${Oo}`,this.element.appendChild(this.button_)}handleClick_(t){t.preventDefault(),this.handleFullScreen_()}handleFullScreen_(){const t=this.getMap();if(!t)return;const e=t.getOwnerDocument();if(Jb(e))if(Qb(e))!function(t){t.exitFullscreen?t.exitFullscreen():t.webkitExitFullscreen&&t.webkitExitFullscreen()}(e);else{let i;i=this.source_?"string"==typeof this.source_?e.getElementById(this.source_):this.source_:t.getTargetElement(),this.keys_?function(t){t.webkitRequestFullscreen?t.webkitRequestFullscreen():tP(t)}(i):tP(i)}}handleFullScreenChange_(){const t=this.getMap();if(!t)return;const e=this.isInFullscreen_;this.isInFullscreen_=Qb(t.getOwnerDocument()),e!==this.isInFullscreen_&&(this.setClassName_(this.button_,this.isInFullscreen_),this.isInFullscreen_?(vt(this.labelActiveNode_,this.labelNode_),this.dispatchEvent(Kb)):(vt(this.labelNode_,this.labelActiveNode_),this.dispatchEvent(qb)),t.updateSize())}setClassName_(t,e){e?(t.classList.remove(...this.inactiveClassName_),t.classList.add(...this.activeClassName_)):(t.classList.remove(...this.activeClassName_),t.classList.add(...this.inactiveClassName_))}setMap(t){const e=this.getMap();e&&e.removeChangeListener(eo,this.boundHandleMapTargetChange_),super.setMap(t),this.handleMapTargetChange_(),t&&t.addChangeListener(eo,this.boundHandleMapTargetChange_)}handleMapTargetChange_(){const t=this.documentListeners_;for(let e=0,i=t.length;e{const e=l.getEventCoordinateInternal(t),i=this.getMap();i.getView().setCenterInternal(e);const n=i.getOwnerDocument();n.removeEventListener("pointermove",f),n.removeEventListener("pointerup",p)};this.ovmapDiv_.addEventListener("pointerdown",(t=>{const e=this.getMap().getOwnerDocument();t.target===g&&e.addEventListener("pointermove",f),e.addEventListener("pointerup",p)}))}setMap(t){const e=this.getMap();if(t!==e){if(e){const t=e.getView();t&&this.unbindView_(t),this.ovmap_.setTarget(null)}if(super.setMap(t),t){this.ovmap_.setTarget(this.ovmapDiv_),this.listenerKeys.push(D(t,i,this.handleMapPropertyChange_,this));const e=t.getView();e&&this.bindView_(e),this.ovmap_.isRendered()||this.updateBoxAfterOvmapIsRendered_()}}}handleMapPropertyChange_(t){if(t.key===io){const e=t.oldValue;e&&this.unbindView_(e);const i=this.getMap().getView();this.bindView_(i)}else this.ovmap_.isRendered()||t.key!==eo&&t.key!==to||this.ovmap_.updateSize()}bindView_(t){if(!this.view_){const e=new wo({projection:t.getProjection()});this.ovmap_.setView(e)}t.addChangeListener(ho.ROTATION,this.boundHandleRotationChanged_),this.handleRotationChanged_(),t.isDef()&&(this.ovmap_.updateSize(),this.resetExtent_())}unbindView_(t){t.removeChangeListener(ho.ROTATION,this.boundHandleRotationChanged_)}handleRotationChanged_(){this.rotateWithView_&&this.ovmap_.getView().setRotation(this.getMap().getView().getRotation())}validateExtent_(){const t=this.getMap(),e=this.ovmap_;if(!t.isRendered()||!e.isRendered())return;const i=t.getSize(),n=t.getView().calculateExtentInternal(i);if(this.viewExtent_&&Jt(n,this.viewExtent_))return;this.viewExtent_=n;const r=e.getSize(),s=e.getView().calculateExtentInternal(r),o=e.getPixelFromCoordinateInternal(me(n)),a=e.getPixelFromCoordinateInternal(he(n)),l=Math.abs(o[0]-a[0]),h=Math.abs(o[1]-a[1]),c=r[0],u=r[1];lc*nP||h>u*nP?this.resetExtent_():Vt(s,n)||this.recenter_()}resetExtent_(){const t=this.getMap(),e=this.ovmap_,i=t.getSize(),n=t.getView().calculateExtentInternal(i),r=e.getView(),s=Math.log(7.5)/Math.LN2;Ee(n,1/(Math.pow(2,s/2)*rP)),r.fitInternal(ds(n))}recenter_(){const t=this.getMap(),e=this.ovmap_,i=t.getView();e.getView().setCenterInternal(i.getCenterInternal())}updateBox_(){const t=this.getMap(),e=this.ovmap_;if(!t.isRendered()||!e.isRendered())return;const i=t.getSize(),n=t.getView(),r=e.getView(),s=this.rotateWithView_?0:-n.getRotation(),o=this.boxOverlay_,a=this.boxOverlay_.getElement(),l=n.getCenter(),h=n.getResolution(),c=r.getResolution(),u=i[0]*h/c,d=i[1]*h/c;if(o.setPosition(l),a){a.style.width=u+"px",a.style.height=d+"px";const t="rotate("+s+"rad)";a.style.transform=t}}updateBoxAfterOvmapIsRendered_(){this.ovmapPostrenderKey_||(this.ovmapPostrenderKey_=N(this.ovmap_,Ys,(t=>{delete this.ovmapPostrenderKey_,this.updateBox_()})))}handleClick_(t){t.preventDefault(),this.handleToggle_()}handleToggle_(){this.element.classList.toggle(Do),this.collapsed_?vt(this.collapseLabel_,this.label_):vt(this.label_,this.collapseLabel_),this.collapsed_=!this.collapsed_;const t=this.ovmap_;if(!this.collapsed_){if(t.isRendered())return this.viewExtent_=void 0,void t.render();t.updateSize(),this.resetExtent_(),this.updateBoxAfterOvmapIsRendered_()}}getCollapsible(){return this.collapsible_}setCollapsible(t){this.collapsible_!==t&&(this.collapsible_=t,this.element.classList.toggle("ol-uncollapsible"),!t&&this.collapsed_&&this.handleToggle_())}setCollapsed(t){this.collapsible_&&this.collapsed_!==t&&this.handleToggle_()}getCollapsed(){return this.collapsed_}getRotateWithView(){return this.rotateWithView_}setRotateWithView(t){this.rotateWithView_!==t&&(this.rotateWithView_=t,0!==this.getMap().getView().getRotation()&&(this.rotateWithView_?this.handleRotationChanged_():this.ovmap_.getView().setRotation(0),this.viewExtent_=void 0,this.validateExtent_(),this.updateBox_()))}getOverviewMap(){return this.ovmap_}render(t){this.validateExtent_(),this.updateBox_()}},cP.control.Rotate=zo,cP.control.ScaleLine=class extends Uo{constructor(t){t=t||{};const e=document.createElement("div");e.style.pointerEvents="none",super({element:e,render:t.render,target:t.target}),this.on,this.once,this.un;const i=void 0!==t.className?t.className:t.bar?"ol-scale-bar":"ol-scale-line";this.innerElement_=document.createElement("div"),this.innerElement_.className=i+"-inner",this.element.className=i+" "+Mo,this.element.appendChild(this.innerElement_),this.viewState_=null,this.minWidth_=void 0!==t.minWidth?t.minWidth:64,this.maxWidth_=t.maxWidth,this.renderedVisible_=!1,this.renderedWidth_=void 0,this.renderedHTML_="",this.addChangeListener(sP,this.handleUnitsChanged_),this.setUnits(t.units||"metric"),this.scaleBar_=t.bar||!1,this.scaleBarSteps_=t.steps||4,this.scaleBarText_=t.text||!1,this.dpi_=t.dpi||void 0}getUnits(){return this.get(sP)}handleUnitsChanged_(){this.updateElement_()}setUnits(t){this.set(sP,t)}setDpi(t){this.dpi_=t}updateElement_(){const t=this.viewState_;if(!t)return void(this.renderedVisible_&&(this.element.style.display="none",this.renderedVisible_=!1));const e=t.center,i=t.projection,n=this.getUnits(),r="degrees"==n?"degrees":"m";let s=vn(i,t.resolution,e,r);const o=this.minWidth_*(this.dpi_||aP)/aP,a=void 0!==this.maxWidth_?this.maxWidth_*(this.dpi_||aP)/aP:void 0;let l=o*s,h="";if("degrees"==n){const t=gi.degrees;l*=t,l=a){c=m,u=g,d=f;break}if(u>=o)break;m=c,g=u,f=d,++p}const _=this.scaleBar_?this.createScaleBar(u,c,h):c.toFixed(d<0?-d:0)+" "+h;this.renderedHTML_!=_&&(this.innerElement_.innerHTML=_,this.renderedHTML_=_),this.renderedWidth_!=u&&(this.innerElement_.style.width=u+"px",this.renderedWidth_=u),this.renderedVisible_||(this.element.style.display="",this.renderedVisible_=!0)}createScaleBar(t,e,i){const n=this.getScaleForResolution(),r=n<1?Math.round(1/n).toLocaleString()+" : 1":"1 : "+Math.round(n).toLocaleString(),s=this.scaleBarSteps_,o=t/s,a=[this.createMarker("absolute")];for(let n=0;n

`+this.createMarker("relative")+(n%2==0||2===s?this.createStepText(n,t,!1,e,i):"")+"
")}a.push(this.createStepText(s,t,!0,e,i));return(this.scaleBarText_?`
`+r+"
":"")+a.join("")}createMarker(t){return`
`}createStepText(t,e,i,n,r){const s=(0===t?0:Math.round(n/this.scaleBarSteps_*t*100)/100)+(0===t?"":" "+r);return`
`+s+"
"}getScaleForResolution(){return vn(this.viewState_.projection,this.viewState_.resolution,this.viewState_.center,"m")*(1e3/25.4)*(this.dpi_||aP)}render(t){const e=t.frameState;this.viewState_=e?e.viewState:null,this.updateElement_()}},cP.control.Zoom=Xo,cP.control.ZoomSlider=class extends Uo{constructor(t){super({target:(t=t||{}).target,element:document.createElement("div"),render:t.render}),this.dragListenerKeys_=[],this.currentResolution_=void 0,this.direction_=lP,this.dragging_,this.heightLimit_=0,this.widthLimit_=0,this.startX_,this.startY_,this.thumbSize_=null,this.sliderInitialized_=!1,this.duration_=void 0!==t.duration?t.duration:200;const e=void 0!==t.className?t.className:"ol-zoomslider",i=document.createElement("button");i.setAttribute("type","button"),i.className=e+"-thumb "+Mo;const n=this.element;n.className=e+" "+Mo+" "+Oo,n.appendChild(i),n.addEventListener(Vs,this.handleDraggerStart_.bind(this),!1),n.addEventListener(Xs,this.handleDraggerDrag_.bind(this),!1),n.addEventListener($s,this.handleDraggerEnd_.bind(this),!1),n.addEventListener(o,this.handleContainerClick_.bind(this),!1),i.addEventListener(o,A,!1)}setMap(t){super.setMap(t),t&&t.render()}initSlider_(){const t=this.element;let e=t.offsetWidth,i=t.offsetHeight;if(0===e&&0===i)return this.sliderInitialized_=!1;const n=getComputedStyle(t);e-=parseFloat(n.paddingRight)+parseFloat(n.paddingLeft),i-=parseFloat(n.paddingTop)+parseFloat(n.paddingBottom);const r=t.firstElementChild,s=getComputedStyle(r),o=r.offsetWidth+parseFloat(s.marginRight)+parseFloat(s.marginLeft),a=r.offsetHeight+parseFloat(s.marginTop)+parseFloat(s.marginBottom);return this.thumbSize_=[o,a],e>i?(this.direction_=hP,this.widthLimit_=e-o):(this.direction_=lP,this.heightLimit_=i-a),this.sliderInitialized_=!0}handleContainerClick_(t){const e=this.getMap().getView(),i=this.getRelativePosition_(t.offsetX-this.thumbSize_[0]/2,t.offsetY-this.thumbSize_[1]/2),n=this.getResolutionForPosition_(i),r=e.getConstrainedZoom(e.getZoomForResolution(n));e.animateInternal({zoom:r,duration:this.duration_,easing:tt})}handleDraggerStart_(t){if(!this.dragging_&&t.target===this.element.firstElementChild){const e=this.element.firstElementChild;if(this.getMap().getView().beginInteraction(),this.startX_=t.clientX-parseFloat(e.style.left),this.startY_=t.clientY-parseFloat(e.style.top),this.dragging_=!0,0===this.dragListenerKeys_.length){const t=this.handleDraggerDrag_,e=this.handleDraggerEnd_,i=this.getMap().getOwnerDocument();this.dragListenerKeys_.push(D(i,Xs,t,this),D(i,$s,e,this))}}}handleDraggerDrag_(t){if(this.dragging_){const e=t.clientX-this.startX_,i=t.clientY-this.startY_,n=this.getRelativePosition_(e,i);this.currentResolution_=this.getResolutionForPosition_(n),this.getMap().getView().setResolution(this.currentResolution_)}}handleDraggerEnd_(t){if(this.dragging_){this.getMap().getView().endInteraction(),this.dragging_=!1,this.startX_=void 0,this.startY_=void 0,this.dragListenerKeys_.forEach(k),this.dragListenerKeys_.length=0}}setThumbPosition_(t){const e=this.getPositionForResolution_(t),i=this.element.firstElementChild;this.direction_==hP?i.style.left=this.widthLimit_*e+"px":i.style.top=this.heightLimit_*e+"px"}getRelativePosition_(t,e){let i;return i=this.direction_===hP?t/this.widthLimit_:e/this.heightLimit_,be(i,0,1)}getResolutionForPosition_(t){return this.getMap().getView().getResolutionForValueFunction()(1-t)}getPositionForResolution_(t){return be(1-this.getMap().getView().getValueForResolutionFunction()(t),0,1)}render(t){if(!t.frameState)return;if(!this.sliderInitialized_&&!this.initSlider_())return;const e=t.frameState.viewState.resolution;this.currentResolution_=e,this.setThumbPosition_(e)}},cP.control.ZoomToExtent=class extends Uo{constructor(t){t=t||{},super({element:document.createElement("div"),target:t.target}),this.extent=t.extent?t.extent:null;const e=void 0!==t.className?t.className:"ol-zoom-extent",i=void 0!==t.label?t.label:"E",n=void 0!==t.tipLabel?t.tipLabel:"Fit to extent",r=document.createElement("button");r.setAttribute("type","button"),r.title=n,r.appendChild("string"==typeof i?document.createTextNode(i):i),r.addEventListener(o,this.handleClick_.bind(this),!1);const s=e+" "+Mo+" "+Oo,a=this.element;a.className=s,a.appendChild(r)}handleClick_(t){t.preventDefault(),this.handleZoomToExtent()}handleZoomToExtent(){const t=this.getMap().getView(),e=this.extent?jn(this.extent,t.getProjection()):t.getProjection().getExtent();t.fitInternal(ds(e))}},cP.control.defaults={},cP.control.defaults.defaults=Vo,cP.coordinate={},cP.coordinate.add=qe,cP.coordinate.closestOnCircle=Je,cP.coordinate.closestOnSegment=Qe,cP.coordinate.createStringXY=function(t){return function(e){return li(e,t)}},cP.coordinate.degreesToStringHDMS=ti,cP.coordinate.distance=oi,cP.coordinate.equals=ii,cP.coordinate.format=ei,cP.coordinate.getWorldsAway=ci,cP.coordinate.rotate=ni,cP.coordinate.scale=ri,cP.coordinate.squaredDistance=si,cP.coordinate.squaredDistanceToSegment=ai,cP.coordinate.toStringHDMS=function(t,e){return t?ti("NS",t[1],e)+" "+ti("EW",t[0],e):""},cP.coordinate.toStringXY=li,cP.coordinate.wrapX=hi,cP.css={},cP.css.CLASS_COLLAPSED=Do,cP.css.CLASS_CONTROL=Oo,cP.css.CLASS_HIDDEN=Fo,cP.css.CLASS_SELECTABLE=Lo,cP.css.CLASS_UNSELECTABLE=Mo,cP.css.CLASS_UNSUPPORTED=Ao,cP.css.fontWeights=Go,cP.css.getFontParameters=jo,cP.dom={},cP.dom.createCanvasContext2D=ft,cP.dom.getSharedCanvasContext2D=mt,cP.dom.outerHeight=xt,cP.dom.outerWidth=yt,cP.dom.releaseCanvas=_t,cP.dom.removeChildren=St,cP.dom.replaceChildren=Et,cP.dom.replaceNode=vt,cP.easing={},cP.easing.easeIn=Q,cP.easing.easeOut=tt,cP.easing.inAndOut=et,cP.easing.linear=it,cP.easing.upAndDown=function(t){return t<.5?et(2*t):1-et(2*(t-.5))},cP.events={},cP.events.Event=M,cP.events.Event.preventDefault=function(t){t.preventDefault()},cP.events.Event.stopPropagation=A,cP.events.SnapEvent={},cP.events.SnapEvent.SnapEvent=yv,cP.events.Target=O,cP.events.condition={},cP.events.condition.all=Ko,cP.events.condition.altKeyOnly=qo,cP.events.condition.altShiftKeysOnly=Jo,cP.events.condition.always=ea,cP.events.condition.click=function(t){return t.type==zs.CLICK},cP.events.condition.doubleClick=function(t){return t.type==zs.DBLCLICK},cP.events.condition.focus=Qo,cP.events.condition.focusWithTabindex=ta,cP.events.condition.mouseActionButton=ia,cP.events.condition.mouseOnly=ha,cP.events.condition.never=na,cP.events.condition.noModifierKeys=sa,cP.events.condition.penOnly=function(t){const e=t.originalEvent;return"pointerId"in e&&"pen"===e.pointerType},cP.events.condition.platformModifierKey=oa,cP.events.condition.platformModifierKeyOnly=function(t){const e=t.originalEvent;return!e.altKey&&(lt?e.metaKey:e.ctrlKey)&&!e.shiftKey},cP.events.condition.pointerMove=function(t){return"pointermove"==t.type},cP.events.condition.primaryAction=ca,cP.events.condition.shiftKeyOnly=aa,cP.events.condition.singleClick=ra,cP.events.condition.targetNotEditable=la,cP.events.condition.touchOnly=function(t){const e=t.originalEvent;return"pointerId"in e&&"touch"===e.pointerType},cP.events.listen=D,cP.events.listenOnce=N,cP.events.unlistenByKey=k,cP.expr={},cP.expr.cpu={},cP.expr.cpu.buildExpression=_h,cP.expr.cpu.newEvaluationContext=mh,cP.expr.expression={},cP.expr.expression.AnyType=Hl,cP.expr.expression.BooleanType=Xl,cP.expr.expression.CallExpression=ih,cP.expr.expression.ColorType=Wl,cP.expr.expression.LiteralExpression=eh,cP.expr.expression.NoneType=0,cP.expr.expression.NumberArrayType=Zl,cP.expr.expression.NumberType=Vl,cP.expr.expression.Ops=sh,cP.expr.expression.SizeType=Yl,cP.expr.expression.StringType=$l,cP.expr.expression.computeGeometryType=ph,cP.expr.expression.includesType=Ql,cP.expr.expression.isType=th,cP.expr.expression.newParsingContext=nh,cP.expr.expression.overlapsType=function(t,e){return!!(t&e)},cP.expr.expression.parse=rh,cP.expr.expression.typeName=Jl,cP.expr.gpu={},cP.expr.gpu.FEATURE_ID_PROPERTY_NAME=jm,cP.expr.gpu.GEOMETRY_TYPE_PROPERTY_NAME=Um,cP.expr.gpu.PALETTE_TEXTURE_ARRAY=Gm,cP.expr.gpu.UNDEFINED_PROP_VALUE=Bm,cP.expr.gpu.arrayToGlsl=Pm,cP.expr.gpu.buildExpression=zm,cP.expr.gpu.colorToGlsl=Im,cP.expr.gpu.getStringNumberEquivalent=Am,cP.expr.gpu.newCompilationContext=Nm,cP.expr.gpu.numberToGlsl=bm,cP.expr.gpu.sizeToGlsl=Fm,cP.expr.gpu.stringToGlsl=Om,cP.expr.gpu.uniformNameForVariable=Dm,cP.extent={},cP.extent.applyTransform=Te,cP.extent.approximatelyEquals=Qt,cP.extent.boundingExtent=jt,cP.extent.buffer=Ut,cP.extent.clone=Bt,cP.extent.closestSquaredDistanceXY=zt,cP.extent.containsCoordinate=Xt,cP.extent.containsExtent=Vt,cP.extent.containsXY=$t,cP.extent.coordinateRelationship=Wt,cP.extent.createEmpty=Zt,cP.extent.createOrUpdate=Yt,cP.extent.createOrUpdateEmpty=Ht,cP.extent.createOrUpdateFromCoordinate=Kt,cP.extent.createOrUpdateFromCoordinates=function(t,e){return ie(Ht(e),t)},cP.extent.createOrUpdateFromFlatCoordinates=qt,cP.extent.createOrUpdateFromRings=function(t,e){return re(Ht(e),t)},cP.extent.equals=Jt,cP.extent.extend=te,cP.extent.extendCoordinate=ee,cP.extent.extendCoordinates=ie,cP.extent.extendFlatCoordinates=ne,cP.extent.extendRings=re,cP.extent.extendXY=se,cP.extent.forEachCorner=oe,cP.extent.getArea=ae,cP.extent.getBottomLeft=le,cP.extent.getBottomRight=he,cP.extent.getCenter=ce,cP.extent.getCorner=ue,cP.extent.getEnlargedArea=function(t,e){const i=Math.min(t[0],e[0]),n=Math.min(t[1],e[1]);return(Math.max(t[2],e[2])-i)*(Math.max(t[3],e[3])-n)},cP.extent.getForViewAndSize=de,cP.extent.getHeight=fe,cP.extent.getIntersection=pe,cP.extent.getIntersectionArea=function(t,e){return ae(pe(t,e))},cP.extent.getMargin=function(t){return ye(t)+fe(t)},cP.extent.getRotatedViewport=ge,cP.extent.getSize=function(t){return[t[2]-t[0],t[3]-t[1]]},cP.extent.getTopLeft=me,cP.extent.getTopRight=_e,cP.extent.getWidth=ye,cP.extent.intersects=xe,cP.extent.intersectsSegment=we,cP.extent.isEmpty=ve,cP.extent.returnOrUpdate=Se,cP.extent.scaleFromCenter=Ee,cP.extent.wrapAndSliceX=Re,cP.extent.wrapX=Ce,cP.featureloader={},cP.featureloader.loadFeaturesXhr=nu,cP.featureloader.setWithCredentials=function(t){iu=t},cP.featureloader.xhr=ru,cP.format={},cP.format.EsriJSON=class extends Dv{constructor(t){t=t||{},super(),this.geometryName_=t.geometryName}readFeatureFromObject(t,e,i){const n=t,r=jv(n.geometry,e),s=new Lt;if(this.geometryName_&&s.setGeometryName(this.geometryName_),s.setGeometry(r),n.attributes){s.setProperties(n.attributes,!0);const t=n.attributes[i];void 0!==t&&s.setId(t)}return s}readFeaturesFromObject(t,e){if(e=e||{},t.features){const i=[],n=t.features;for(let r=0,s=n.length;r0&&"string"==typeof this.imageInfo.profile[0]&&wg.test(this.imageInfo.profile[0]))return this.imageInfo.profile[0]}}getComplianceLevelFromProfile(t){const e=this.getComplianceLevelEntryFromProfile(t);if(void 0===e)return;const i=e.match(/level[0-2](?:\.json)?$/g);return Array.isArray(i)?i[0].replace(".json",""):void 0}getComplianceLevelSupportedFeatures(){if(void 0===this.imageInfo)return;const t=this.getImageApiVersion(),e=this.getComplianceLevelFromProfile(t);return void 0===e?Sg.none.none:Sg[t][e]}getTileSourceOptions(t){const e=t||{},i=this.getImageApiVersion();if(void 0===i)return;const n=void 0===i?void 0:Cg[i](this);return void 0!==n?{url:n.url,version:i,size:[this.imageInfo.width,this.imageInfo.height],sizes:n.sizes,format:void 0!==e.format&&n.formats.includes(e.format)?e.format:void 0!==n.preferredFormat?n.preferredFormat:"jpg",supports:n.supports,quality:e.quality&&n.qualities.includes(e.quality)?e.quality:n.qualities.includes("native")?"native":"default",resolutions:Array.isArray(n.resolutions)?n.resolutions.sort((function(t,e){return e-t})):void 0,tileSize:n.tileSize}:void 0}},cP.format.JSONFeature=Dv,cP.format.KML=class extends Xv{constructor(t){super(),t=t||{},AE||(xE=[255,255,255,1],CE=new tc({color:xE}),vE=[20,2],SE="pixels",EE="pixels",wE=[64,64],TE="https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png",bE=new ic({anchor:vE,anchorOrigin:"bottom-left",anchorXUnits:SE,anchorYUnits:EE,crossOrigin:"anonymous",rotation:0,scale:OE(wE),size:wE,src:TE}),RE="NO_IMAGE",IE=new nc({color:xE,width:1}),PE=new nc({color:[51,51,51,1],width:2}),FE=new cc({font:"bold 16px Helvetica",fill:CE,stroke:PE,scale:.8}),LE=new rc({fill:CE,image:bE,text:FE,stroke:IE,zIndex:0}),AE=[LE]),this.dataProjection=xn("EPSG:4326"),this.defaultStyle_=t.defaultStyle?t.defaultStyle:AE,this.extractStyles_=void 0===t.extractStyles||t.extractStyles,this.writeStyles_=void 0===t.writeStyles||t.writeStyles,this.sharedStyles_={},this.showPointNames_=void 0===t.showPointNames||t.showPointNames,this.crossOrigin_=void 0!==t.crossOrigin?t.crossOrigin:"anonymous",this.iconUrlFunction_=t.iconUrlFunction?t.iconUrlFunction:DE,this.supportedMediaTypes=["application/vnd.google-earth.kml+xml"]}readDocumentOrFolder_(t,e){const i=mp([],fp(cE,{Document:rp(this.readDocumentOrFolder_,this),Folder:rp(this.readDocumentOrFolder_,this),Placemark:sp(this.readPlacemark_,this),Style:this.readSharedStyle_.bind(this),StyleMap:this.readSharedStyleMap_.bind(this)}),t,e,this);if(i)return i}readPlacemark_(t,e){const i=mp({geometry:null},dE,t,e,this);if(!i)return;const n=new Lt,r=t.getAttribute("id");null!==r&&n.setId(r);const s=e[0],o=i.geometry;if(o&&Fv(o,!1,s),n.setGeometry(o),delete i.geometry,this.extractStyles_){const t=function(t,e,i,n,r){return function(s,o){let a=r,l="",h=[];if(a){const t=s.getGeometry();if(t)if(t instanceof Td)h=t.getGeometriesArrayRecursive().filter((function(t){const e=t.getType();return"Point"===e||"MultiPoint"===e})),a=h.length>0;else{const e=t.getType();a="Point"===e||"MultiPoint"===e}}a&&(l=s.get("name"),a=a&&!!l,a&&/&[^&]+;/.test(l)&&(ME||(ME=document.createElement("textarea")),ME.innerHTML=l,l=ME.value));let c=i;if(t?c=t:e&&(c=NE(e,i,n)),a){const t=function(t,e){const i=[0,0];let n="start";const r=t.getImage();if(r){const t=r.getSize();if(t&&2==t.length){const e=r.getScaleArray(),s=r.getAnchor();i[0]=e[0]*(t[0]-s[0]),i[1]=e[1]*(t[1]/2-s[1]),n="left"}}let s=t.getText();s?(s=s.clone(),s.setFont(s.getFont()||FE.getFont()),s.setScale(s.getScale()||FE.getScale()),s.setFill(s.getFill()||FE.getFill()),s.setStroke(s.getStroke()||PE)):s=FE.clone();s.setText(e),s.setOffsetX(i[0]),s.setOffsetY(i[1]),s.setTextAlign(n);const o=new rc({image:r,text:s});return o}(c[0],l);if(h.length>0){t.setGeometry(new Td(h));return[t,new rc({geometry:c[0].getGeometry(),image:null,fill:c[0].getFill(),stroke:c[0].getStroke(),text:null})].concat(c.slice(1))}return t}return c}}(i.Style,i.styleUrl,this.defaultStyle_,this.sharedStyles_,this.showPointNames_);n.setStyle(t)}return delete i.Style,n.setProperties(i,!0),n}readSharedStyle_(t,e){const i=t.getAttribute("id");if(null!==i){const n=uw.call(this,t,e);if(n){let e,r=t.baseURI;if(r&&"about:blank"!=r||(r=window.location.href),r){e=new URL("#"+i,r).href}else e="#"+i;this.sharedStyles_[e]=n}}}readSharedStyleMap_(t,e){const i=t.getAttribute("id");if(null===i)return;const n=XE.call(this,t,e);if(!n)return;let r,s=t.baseURI;if(s&&"about:blank"!=s||(s=window.location.href),s){r=new URL("#"+i,s).href}else r="#"+i;this.sharedStyles_[r]=n}readFeatureFromNode(t,e){if(!cE.includes(t.namespaceURI))return null;const i=this.readPlacemark_(t,[this.getReadOptions(t,e)]);return i||null}readFeaturesFromNode(t,e){if(!cE.includes(t.namespaceURI))return[];let i;const n=t.localName;if("Document"==n||"Folder"==n)return i=this.readDocumentOrFolder_(t,[this.getReadOptions(t,e)]),i||[];if("Placemark"==n){const i=this.readPlacemark_(t,[this.getReadOptions(t,e)]);return i?[i]:[]}if("kml"==n){i=[];for(let n=t.firstElementChild;n;n=n.nextElementSibling){const t=this.readFeaturesFromNode(n,e);t&&E(i,t)}return i}return[]}readName(t){if(t){if("string"==typeof t){const e=np(t);return this.readNameFromDocument(e)}return ep(t)?this.readNameFromDocument(t):this.readNameFromNode(t)}}readNameFromDocument(t){for(let e=t.firstChild;e;e=e.nextSibling)if(e.nodeType==Node.ELEMENT_NODE){const t=this.readNameFromNode(e);if(t)return t}}readNameFromNode(t){for(let e=t.firstElementChild;e;e=e.nextElementSibling)if(cE.includes(e.namespaceURI)&&"name"==e.localName)return tS(e);for(let e=t.firstElementChild;e;e=e.nextElementSibling){const t=e.localName;if(cE.includes(e.namespaceURI)&&("Document"==t||"Folder"==t||"Placemark"==t||"kml"==t)){const t=this.readNameFromNode(e);if(t)return t}}}readNetworkLinks(t){const e=[];if("string"==typeof t){const i=np(t);E(e,this.readNetworkLinksFromDocument(i))}else ep(t)?E(e,this.readNetworkLinksFromDocument(t)):E(e,this.readNetworkLinksFromNode(t));return e}readNetworkLinksFromDocument(t){const e=[];for(let i=t.firstChild;i;i=i.nextSibling)i.nodeType==Node.ELEMENT_NODE&&E(e,this.readNetworkLinksFromNode(i));return e}readNetworkLinksFromNode(t){const e=[];for(let i=t.firstElementChild;i;i=i.nextElementSibling)if(cE.includes(i.namespaceURI)&&"NetworkLink"==i.localName){const t=mp({},gE,i,[]);e.push(t)}for(let i=t.firstElementChild;i;i=i.nextElementSibling){const t=i.localName;!cE.includes(i.namespaceURI)||"Document"!=t&&"Folder"!=t&&"kml"!=t||E(e,this.readNetworkLinksFromNode(i))}return e}readRegion(t){const e=[];if("string"==typeof t){const i=np(t);E(e,this.readRegionFromDocument(i))}else ep(t)?E(e,this.readRegionFromDocument(t)):E(e,this.readRegionFromNode(t));return e}readRegionFromDocument(t){const e=[];for(let i=t.firstChild;i;i=i.nextSibling)i.nodeType==Node.ELEMENT_NODE&&E(e,this.readRegionFromNode(i));return e}readRegionFromNode(t){const e=[];for(let i=t.firstElementChild;i;i=i.nextElementSibling)if(cE.includes(i.namespaceURI)&&"Region"==i.localName){const t=mp({},mE,i,[]);e.push(t)}for(let i=t.firstElementChild;i;i=i.nextElementSibling){const t=i.localName;!cE.includes(i.namespaceURI)||"Document"!=t&&"Folder"!=t&&"kml"!=t||E(e,this.readRegionFromNode(i))}return e}readCamera(t){const e=[];if("string"==typeof t){const i=np(t);E(e,this.readCameraFromDocument(i))}else ep(t)?E(e,this.readCameraFromDocument(t)):E(e,this.readCameraFromNode(t));return e}readCameraFromDocument(t){const e=[];for(let i=t.firstChild;i;i=i.nextSibling)i.nodeType===Node.ELEMENT_NODE&&E(e,this.readCameraFromNode(i));return e}readCameraFromNode(t){const e=[];for(let i=t.firstElementChild;i;i=i.nextElementSibling)if(cE.includes(i.namespaceURI)&&"Camera"===i.localName){const t=mp({},pE,i,[]);e.push(t)}for(let i=t.firstElementChild;i;i=i.nextElementSibling){const t=i.localName;!cE.includes(i.namespaceURI)||"Document"!==t&&"Folder"!==t&&"Placemark"!==t&&"kml"!==t||E(e,this.readCameraFromNode(i))}return e}writeFeaturesNode(t,e){e=this.adaptOptions(e);const i=Jf(cE[4],"kml"),n="http://www.w3.org/2000/xmlns/";i.setAttributeNS(n,"xmlns:gx",hE[0]),i.setAttributeNS(n,"xmlns:xsi",qf),i.setAttributeNS(qf,"xsi:schemaLocation","http://www.opengis.net/kml/2.2 https://developers.google.com/kml/schema/kml22gx.xsd");const r={node:i},s={};t.length>1?s.Document=t:1==t.length&&(s.Placemark=t[0]);const o=_E[i.namespaceURI],a=gp(s,o);return yp(r,yE,dp,a,[e],o,this),i}},cP.format.KML.getDefaultFillStyle=function(){return CE},cP.format.KML.getDefaultImageStyle=function(){return bE},cP.format.KML.getDefaultStrokeStyle=function(){return IE},cP.format.KML.getDefaultStyle=function(){return LE},cP.format.KML.getDefaultStyleArray=function(){return AE},cP.format.KML.getDefaultTextStyle=function(){return FE},cP.format.KML.readFlatCoordinates=GE,cP.format.MVT=class extends Iv{constructor(t){super(),t=t||{},this.dataProjection=new fi({code:"",units:"tile-pixels"}),this.featureClass=t.featureClass?t.featureClass:Md,this.geometryName_=t.geometryName,this.layerName_=t.layerName?t.layerName:"layer",this.layers_=t.layers?t.layers:null,this.idProperty_=t.idProperty,this.supportedMediaTypes=["application/vnd.mapbox-vector-tile","application/x-protobuf"]}readRawGeometry_(t,e,i,n){t.pos=e.geometry;const r=t.readVarint()+t.pos;let s=1,o=0,a=0,l=0,h=0,c=0;for(;t.pos>3}if(o--,1===s||2===s)a+=t.readSVarint(),l+=t.readSVarint(),1===s&&h>c&&(n.push(h),c=h),i.push(a,l),h+=2;else{if(7!==s)throw new Error("Invalid command found in the PBF");h>c&&(i.push(i[c],i[c+1]),h+=2)}}h>c&&(n.push(h),c=h)}createFeature_(t,e,i){const n=e.type;if(0===n)return null;let r;const s=e.properties;let o;this.idProperty_?(o=s[this.idProperty_],delete s[this.idProperty_]):o=e.id,s[this.layerName_]=e.layer.name;const a=[],l=[];this.readRawGeometry_(t,e,a,l);const h=function(t,e){let i;1===t?i=1===e?"Point":"MultiPoint":2===t?i=1===e?"LineString":"MultiLineString":3===t&&(i="Polygon");return i}(n,l.length);if(this.featureClass===Md)r=new this.featureClass(h,a,l,2,s,o),r.transform(i.dataProjection);else{let t;if("Polygon"==h){const e=hs(a,l);t=e.length>1?new Fd(a,"XY",e):new cs(a,"XY",l)}else t="Point"===h?new Xr(a,"XY"):"LineString"===h?new bd(a,"XY"):"MultiPoint"===h?new Id(a,"XY"):"MultiLineString"===h?new Pd(a,"XY",l):null;r=new(0,this.featureClass),this.geometryName_&&r.setGeometryName(this.geometryName_);const e=Fv(t,!1,i);r.setGeometry(e),void 0!==o&&r.setId(o),r.setProperties(s,!0)}return r}getType(){return"arraybuffer"}readFeatures(t,e){const i=this.layers_,n=xn((e=this.adaptOptions(e)).dataProjection);n.setWorldExtent(e.extent),e.dataProjection=n;const r=new dT(t),s=r.readFields(TT,{}),o=[];for(const t in s){if(i&&!i.includes(t))continue;const a=s[t],l=a?[0,0,a.extent,a.extent]:null;n.setExtent(l);for(let t=0,i=a.length;t{const r=this.combineBboxAndFilter(n.geometryName,n.bbox,t.srsName,t.filter);Object.assign(i,{geometryName:n.geometryName,filter:r}),bR(e,[n.name],[i])}));return e}combineBboxAndFilter(t,e,i,n){const r=GC(t,e,i);return n?kC(n,r):r}writeTransaction(t,e,i,n){const r=[],s=n.version?n.version:this.version_,o=Jf(ZC[s],"Transaction");let a;o.setAttribute("service","WFS"),o.setAttribute("version",s),n&&(a=n.gmlOptions?n.gmlOptions:{},n.handle&&o.setAttribute("handle",n.handle)),o.setAttributeNS(qf,"xsi:schemaLocation",HC[s]);const l=function(t,e,i,n){const r=n.featurePrefix?n.featurePrefix:VC;let s;"1.0.0"===i?s=2:"1.1.0"===i?s=3:"2.0.0"===i&&(s=3.2);const o=Object.assign({node:t},{version:i,featureNS:n.featureNS,featureType:n.featureType,featurePrefix:r,gmlVersion:s,hasZ:n.hasZ,srsName:n.srsName},e);return o}(o,a,s,n);return t&&qC("Insert",t,r,l),e&&qC("Update",e,r,l),i&&qC("Delete",i,r,l),n.nativeElements&&qC("Native",n.nativeElements,r,l),o}readProjectionFromDocument(t){for(let e=t.firstChild;e;e=e.nextSibling)if(e.nodeType==Node.ELEMENT_NODE)return this.readProjectionFromNode(e);return null}readProjectionFromNode(t){if(t.firstElementChild&&t.firstElementChild.firstElementChild)for(let e=(t=t.firstElementChild.firstElementChild).firstElementChild;e;e=e.nextElementSibling)if(0!==e.childNodes.length&&(1!==e.childNodes.length||3!==e.firstChild.nodeType)){const t=[{}];return this.gmlFormat_.readGeometryElement(e,t),xn(t.pop().srsName)}return null}},cP.format.WFS.writeFilter=function(t,e){const i=Jf(PR(e=e||"1.1.0"),"Filter"),n={node:i};return Object.assign(n,{version:e,filter:t}),dR(i,t,[n]),i},cP.format.WKB=class extends Iv{constructor(t){super(),t=t||{},this.splitCollection=Boolean(t.splitCollection),this.viewCache_=null,this.hex_=!1!==t.hex,this.littleEndian_=!1!==t.littleEndian,this.ewkb_=!1!==t.ewkb,this.layout_=t.geometryLayout,this.nodataZ_=t.nodataZ||0,this.nodataM_=t.nodataM||0,this.srid_=t.srid}getType(){return this.hex_?"text":"arraybuffer"}readFeature(t,e){return new Lt({geometry:this.readGeometry(t,e)})}readFeatures(t,e){let i=[];const n=this.readGeometry(t,e);return i=this.splitCollection&&n instanceof Td?n.getGeometriesArray():[n],i.map((t=>new Lt({geometry:t})))}readGeometry(t,e){const i=BR(t);if(!i)return null;const n=new jR(i).readGeometry();return this.viewCache_=i,e=this.getReadOptions(t,e),this.viewCache_=null,Fv(n,!1,e)}readProjection(t){const e=this.viewCache_||BR(t);if(!e)return;const i=new jR(e);return i.readWkbHeader(),i.getSrid()&&xn("EPSG:"+i.getSrid())||void 0}writeFeature(t,e){return this.writeGeometry(t.getGeometry(),e)}writeFeatures(t,e){return this.writeGeometry(new Td(t.map((t=>t.getGeometry()))),e)}writeGeometry(t,e){e=this.adaptOptions(e);const i=new UR({layout:this.layout_,littleEndian:this.littleEndian_,ewkb:this.ewkb_,nodata:{Z:this.nodataZ_,M:this.nodataM_}});let n=Number.isInteger(this.srid_)?Number(this.srid_):null;if(!1!==this.srid_&&!Number.isInteger(this.srid_)){const t=e.dataProjection&&xn(e.dataProjection);if(t){const e=t.getCode();e.startsWith("EPSG:")&&(n=Number(e.substring(5)))}}i.writeGeometry(Fv(t,!0,e),n);const r=i.getBuffer();return this.hex_?function(t){const e=new Uint8Array(t);return Array.from(e.values()).map((t=>(t<16?"0":"")+Number(t).toString(16).toUpperCase())).join("")}(r):r}},cP.format.WKT=class extends iE{constructor(t){super(),t=t||{},this.splitCollection_=void 0!==t.splitCollection&&t.splitCollection}parse_(t){const e=new JR(t);return new QR(e).parse()}readFeatureFromText(t,e){const i=this.readGeometryFromText(t,e),n=new Lt;return n.setGeometry(i),n}readFeaturesFromText(t,e){let i=[];const n=this.readGeometryFromText(t,e);i=this.splitCollection_&&"GeometryCollection"==n.getType()?n.getGeometriesArray():[n];const r=[];for(let t=0,e=i.length;t3&&!!yr(t,e,i,n)},cP.geom.flat.transform={},cP.geom.flat.transform.rotate=lr,cP.geom.flat.transform.scale=hr,cP.geom.flat.transform.transform2D=ar,cP.geom.flat.transform.translate=cr,cP.has={},cP.has.CREATE_IMAGE_BITMAP=dt,cP.has.DEVICE_PIXEL_RATIO=ht,cP.has.IMAGE_DECODE=ut,cP.has.MAC=lt,cP.has.PASSIVE_EVENT_LISTENERS=gt,cP.has.SAFARI=st,cP.has.SAFARI_BUG_237906=ot,cP.has.WEBKIT=at,cP.has.WORKER_OFFSCREEN_CANVAS=ct,cP.interaction={},cP.interaction.DblClickDragZoom=class extends Wo{constructor(t){const e=t||{};super(e),e.stopDown&&(this.stopDown=e.stopDown),this.scaleDeltaByPixel_=e.delta?e.delta:.01,this.duration_=void 0!==e.duration?e.duration:250,this.handlingDownUpSequence_=!1,this.handlingDoubleDownSequence_=!1,this.doubleTapTimeoutId_=void 0,this.trackedPointers_={},this.down_=null,this.targetPointers=[]}handleEvent(t){if(!t.originalEvent)return!0;let e=!1;if(this.updateTrackedPointers_(t),this.handlingDownUpSequence_){if(t.type==zs.POINTERDRAG)this.handleDragEvent(t),t.originalEvent.preventDefault();else if(t.type==zs.POINTERUP){const e=this.handleUpEvent(t);this.handlingDownUpSequence_=e}}else if(t.type==zs.POINTERDOWN)if(this.handlingDoubleDownSequence_){this.handlingDoubleDownSequence_=!1;const i=this.handleDownEvent(t);this.handlingDownUpSequence_=i,e=this.stopDown(i)}else e=this.stopDown(!1),this.waitForDblTap_();return!e}handleDragEvent(t){let e=1;const i=this.targetPointers[0],n=this.down_,r=i.clientY-n.clientY;void 0!==this.lastDistance_&&(e=1-(this.lastDistance_-r)*this.scaleDeltaByPixel_),this.lastDistance_=r,1!=e&&(this.lastScaleDelta_=e);const s=t.map,o=s.getView();s.render(),o.adjustResolutionInternal(e)}handleDownEvent(t){if(1==this.targetPointers.length){const e=t.map;return this.anchor_=null,this.lastDistance_=void 0,this.lastScaleDelta_=1,this.down_=t.originalEvent,this.handlingDownUpSequence_||e.getView().beginInteraction(),!0}return!1}handleUpEvent(t){if(0==this.targetPointers.length){const e=t.map.getView(),i=this.lastScaleDelta_>1?1:-1;return e.endInteraction(this.duration_,i),this.handlingDownUpSequence_=!1,this.handlingDoubleDownSequence_=!1,!1}return!0}stopDown(t){return t}updateTrackedPointers_(t){if(function(t){const e=t.type;return e===zs.POINTERDOWN||e===zs.POINTERDRAG||e===zs.POINTERUP}(t)){const e=t.originalEvent,i=e.pointerId.toString();t.type==zs.POINTERUP?delete this.trackedPointers_[i]:(t.type==zs.POINTERDOWN||i in this.trackedPointers_)&&(this.trackedPointers_[i]=e),this.targetPointers=Object.values(this.trackedPointers_)}}waitForDblTap_(){void 0!==this.doubleTapTimeoutId_?(clearTimeout(this.doubleTapTimeoutId_),this.doubleTapTimeoutId_=void 0):(this.handlingDoubleDownSequence_=!0,this.doubleTapTimeoutId_=setTimeout(this.endInteraction_.bind(this),250))}endInteraction_(){this.handlingDoubleDownSequence_=!1,this.doubleTapTimeoutId_=void 0}},cP.interaction.DoubleClickZoom=Ho,cP.interaction.DragAndDrop=class extends Wo{constructor(t){t=t||{},super({handleEvent:C}),this.on,this.once,this.un,this.readAsBuffer_=!1,this.formats_=[];const e=t.formatConstructors?t.formatConstructors:[];for(let t=0,i=e.length;t0){this.source_&&(this.source_.clear(),this.source_.addFeatures(l)),this.dispatchEvent(new Ax(Mx,t,l,s));break}}}registerListeners_(){const t=this.getMap();if(t){const e=this.target?this.target:t.getViewport();this.dropListenKeys_=[D(e,c,this.handleDrop,this),D(e,l,this.handleStop,this),D(e,h,this.handleStop,this),D(e,c,this.handleStop,this)]}}setActive(t){!this.getActive()&&t&&this.registerListeners_(),this.getActive()&&!t&&this.unregisterListeners_(),super.setActive(t)}setMap(t){this.unregisterListeners_(),super.setMap(t),this.getActive()&&this.registerListeners_()}tryReadFeatures_(t,e,i){try{return t.readFeatures(e,i)}catch{return null}}unregisterListeners_(){this.dropListenKeys_&&(this.dropListenKeys_.forEach(k),this.dropListenKeys_=null)}handleDrop(t){const e=t.dataTransfer.files;for(let t=0,i=e.length;t1?1:-1;return e.endInteraction(this.duration_,i),this.lastScaleDelta_=0,!1}handleDownEvent(t){return!!ha(t)&&(!!this.condition_(t)&&(t.map.getView().beginInteraction(),this.lastAngle_=void 0,this.lastMagnitude_=void 0,!0))}},cP.interaction.DragZoom=Ea,cP.interaction.Draw=class extends ua{constructor(t){const e=t;e.stopDown||(e.stopDown=R),super(e),this.on,this.once,this.un,this.shouldHandle_=!1,this.downPx_=null,this.downTimeout_,this.lastDragTime_,this.pointerType_,this.freehand_=!1,this.source_=t.source?t.source:null,this.features_=t.features?t.features:null,this.snapTolerance_=t.snapTolerance?t.snapTolerance:12,this.type_=t.type,this.mode_=function(t){switch(t){case"Point":case"MultiPoint":return"Point";case"LineString":case"MultiLineString":return"LineString";case"Polygon":case"MultiPolygon":return"Polygon";case"Circle":return"Circle";default:throw new Error("Invalid type: "+t)}}(this.type_),this.stopClick_=!!t.stopClick,this.minPoints_=t.minPoints?t.minPoints:"Polygon"===this.mode_?3:2,this.maxPoints_="Circle"===this.mode_?2:t.maxPoints?t.maxPoints:1/0,this.finishCondition_=t.finishCondition?t.finishCondition:C,this.geometryLayout_=t.geometryLayout?t.geometryLayout:"XY";let i=t.geometryFunction;if(!i){const t=this.mode_;if("Circle"===t)i=(t,e,i)=>{const n=e||new wd([NaN,NaN]),r=kn(t[0],i),s=si(r,kn(t[t.length-1],i));n.setCenterAndRadius(r,Math.sqrt(s),this.geometryLayout_);const o=Dn();return o&&n.transform(i,o),n};else{let e;"Point"===t?e=Xr:"LineString"===t?e=bd:"Polygon"===t&&(e=cs),i=(i,n,r)=>(n?"Polygon"===t?i[0].length?n.setCoordinates([i[0].concat([i[0][0]])],this.geometryLayout_):n.setCoordinates([],this.geometryLayout_):n.setCoordinates(i,this.geometryLayout_):n=new e(i,this.geometryLayout_),n)}}this.geometryFunction_=i,this.dragVertexDelay_=void 0!==t.dragVertexDelay?t.dragVertexDelay:500,this.finishCoordinate_=null,this.sketchFeature_=null,this.sketchPoint_=null,this.sketchCoords_=null,this.sketchLine_=null,this.sketchLineCoords_=null,this.squaredClickTolerance_=t.clickTolerance?t.clickTolerance*t.clickTolerance:36,this.overlay_=new wx({source:new Xd({useSpatialIndex:!1,wrapX:!!t.wrapX&&t.wrapX}),style:t.style?t.style:Zx(),updateWhileInteracting:!0}),this.geometryName_=t.geometryName,this.condition_=t.condition?t.condition:sa,this.freehandCondition_,t.freehand?this.freehandCondition_=ea:this.freehandCondition_=t.freehandCondition?t.freehandCondition:aa,this.traceCondition_,this.setTrace(t.trace||!1),this.traceState_={active:!1},this.traceSource_=t.traceSource||t.source||null,this.addChangeListener($o,this.updateState_)}setTrace(t){let e;e=t?!0===t?ea:t:na,this.traceCondition_=e}setMap(t){super.setMap(t),this.updateState_()}getOverlay(){return this.overlay_}handleEvent(t){t.originalEvent.type===s&&t.originalEvent.preventDefault(),this.freehand_="Point"!==this.mode_&&this.freehandCondition_(t);let e=t.type===zs.POINTERMOVE,i=!0;if(!this.freehand_&&this.lastDragTime_&&t.type===zs.POINTERDRAG){Date.now()-this.lastDragTime_>=this.dragVertexDelay_?(this.downPx_=t.pixel,this.shouldHandle_=!this.freehand_,e=!0):this.lastDragTime_=void 0,this.shouldHandle_&&void 0!==this.downTimeout_&&(clearTimeout(this.downTimeout_),this.downTimeout_=void 0)}return this.freehand_&&t.type===zs.POINTERDRAG&&null!==this.sketchFeature_?(this.addToDrawing_(t.coordinate),i=!1):this.freehand_&&t.type===zs.POINTERDOWN?i=!1:e&&this.getPointerCount()<2?(i=t.type===zs.POINTERMOVE,i&&this.freehand_?(this.handlePointerMove_(t),this.shouldHandle_&&t.originalEvent.preventDefault()):("mouse"===t.originalEvent.pointerType||t.type===zs.POINTERDRAG&&void 0===this.downTimeout_)&&this.handlePointerMove_(t)):t.type===zs.DBLCLICK&&(i=!1),super.handleEvent(t)&&i}handleDownEvent(t){return this.shouldHandle_=!this.freehand_,this.freehand_?(this.downPx_=t.pixel,this.finishCoordinate_||this.startDrawing_(t.coordinate),!0):this.condition_(t)?(this.lastDragTime_=Date.now(),this.downTimeout_=setTimeout((()=>{this.handlePointerMove_(new Bs(zs.POINTERMOVE,t.map,t.originalEvent,!1,t.frameState))}),this.dragVertexDelay_),this.downPx_=t.pixel,!0):(this.lastDragTime_=void 0,!1)}deactivateTrace_(){this.traceState_={active:!1}}toggleTraceState_(t){if(!this.traceSource_||!this.traceCondition_(t))return;if(this.traceState_.active)return void this.deactivateTrace_();const e=this.getMap(),i=jt([e.getCoordinateFromPixel([t.pixel[0]-this.snapTolerance_,t.pixel[1]+this.snapTolerance_]),e.getCoordinateFromPixel([t.pixel[0]+this.snapTolerance_,t.pixel[1]-this.snapTolerance_])]),n=this.traceSource_.getFeaturesInExtent(i);if(0===n.length)return;const r=function(t,e){const i=[];for(let n=0;nt.endIndex||!i&&et.endIndex)&&this.removeTracedCoordinates_(e,t.endIndex):(this.removeTracedCoordinates_(t.startIndex,t.endIndex),this.addTracedCoordinates_(t,t.startIndex,e))}removeTracedCoordinates_(t,e){if(t===e)return;let i=0;if(t0&&this.removeLastPoints_(i)}addTracedCoordinates_(t,e,i){if(e===i)return;const n=[];if(e=s;--e)n.push(jx(t.coordinates,e))}n.length&&this.appendCoordinates(n)}updateTrace_(t){const e=this.traceState_;if(!e.active)return;if(-1===e.targetIndex&&oi(e.startPx,t.pixel)i.startIndex?hi.startIndex&&(h-=n.length)),l=h,a=t)}const h=e.targets[a];let c=h.ring;if(e.targetIndex===a&&c){const t=Wx(h.coordinates,l);oi(i.getPixelFromCoordinate(t),e.startPx)>n&&(c=!1)}if(c){const t=h.coordinates,e=t.length,i=h.startIndex,n=l;if(ithis.squaredClickTolerance_:s<=this.squaredClickTolerance_,!this.shouldHandle_)return}this.finishCoordinate_?(this.updateTrace_(t),this.modifyDrawing_(t.coordinate)):this.createOrUpdateSketchPoint_(t.coordinate.slice())}atFinish_(t,e){let i=!1;if(this.sketchFeature_){let n=!1,r=[this.finishCoordinate_];const s=this.mode_;if("Point"===s)i=!0;else if("Circle"===s)i=2===this.sketchCoords_.length;else if("LineString"===s)n=!e&&this.sketchCoords_.length>this.minPoints_;else if("Polygon"===s){const t=this.sketchCoords_;n=t[0].length>this.minPoints_,r=[t[0][0],t[0][t[0].length-2]],r=e?[t[0][0]]:[t[0][0],t[0][t[0].length-2]]}if(n){const e=this.getMap();for(let n=0,s=r.length;n=this.maxPoints_&&(this.freehand_?r.pop():n=!0),r.push(t.slice()),this.geometryFunction_(r,e,i)):"Polygon"===s&&(r=this.sketchCoords_[0],r.length>=this.maxPoints_&&(this.freehand_?r.pop():n=!0),r.push(t.slice()),n&&(this.finishCoordinate_=r[0]),this.geometryFunction_(this.sketchCoords_,e,i)),this.createOrUpdateSketchPoint_(t.slice()),this.updateSketchFeatures_(),n?this.finishDrawing():this.sketchFeature_}removeLastPoints_(t){if(!this.sketchFeature_)return;const e=this.sketchFeature_.getGeometry(),i=this.getMap().getView().getProjection(),n=this.mode_;for(let r=0;r=2){this.finishCoordinate_=t[t.length-2].slice();const e=this.finishCoordinate_.slice();t[t.length-1]=e,this.createOrUpdateSketchPoint_(e)}this.geometryFunction_(t,e,i),"Polygon"===e.getType()&&this.sketchLine_&&this.createOrUpdateCustomSketchLine_(e)}else if("Polygon"===n){t=this.sketchCoords_[0],t.splice(-2,1);const n=this.sketchLine_.getGeometry();if(t.length>=2){const e=t[t.length-2].slice();t[t.length-1]=e,this.createOrUpdateSketchPoint_(e)}n.setCoordinates(t),this.geometryFunction_(this.sketchCoords_,e,i)}if(1===t.length){this.abortDrawing();break}}this.updateSketchFeatures_()}removeLastPoint(){this.removeLastPoints_(1)}finishDrawing(){const t=this.abortDrawing_();if(!t)return null;let e=this.sketchCoords_;const i=t.getGeometry(),n=this.getMap().getView().getProjection();return"LineString"===this.mode_?(e.pop(),this.geometryFunction_(e,i,n)):"Polygon"===this.mode_&&(e[0].pop(),this.geometryFunction_(e,i,n),e=i.getCoordinates()),"MultiPoint"===this.type_?t.setGeometry(new Id([e])):"MultiLineString"===this.type_?t.setGeometry(new Pd([e])):"MultiPolygon"===this.type_&&t.setGeometry(new Fd([e])),this.dispatchEvent(new kx(Dx,t)),this.features_&&this.features_.push(t),this.source_&&this.source_.addFeature(t),t}abortDrawing_(){this.finishCoordinate_=null;const t=this.sketchFeature_;return this.sketchFeature_=null,this.sketchPoint_=null,this.sketchLine_=null,this.overlay_.getSource().clear(!0),this.deactivateTrace_(),t}abortDrawing(){const t=this.abortDrawing_();t&&this.dispatchEvent(new kx(Nx,t))}appendCoordinates(t){const e=this.mode_,i=!this.sketchFeature_;let n;if(i&&this.startDrawing_(t[0]),"LineString"===e||"Circle"===e)n=this.sketchCoords_;else{if("Polygon"!==e)return;n=this.sketchCoords_&&this.sketchCoords_.length?this.sketchCoords_[0]:[]}i&&n.shift(),n.pop();for(let e=0;er?o[1]:o[0]),a}}return null}handlePointerMove_(t){const e=t.pixel,i=t.map;let n=this.snapToVertex_(e,i);n||(n=i.getCoordinateFromPixelInternal(e)),this.createOrUpdatePointerFeature_(n)}createOrUpdateExtentFeature_(t){let e=this.extentFeature_;return e?t?e.setGeometry(ds(t)):e.setGeometry(void 0):(e=new Lt(t?ds(t):{}),this.extentFeature_=e,this.extentOverlay_.getSource().addFeature(e)),e}createOrUpdatePointerFeature_(t){let e=this.vertexFeature_;if(e){e.getGeometry().setCoordinates(t)}else e=new Lt(new Xr(t)),this.vertexFeature_=e,this.vertexOverlay_.getSource().addFeature(e);return e}handleEvent(t){return!t.originalEvent||!this.condition_(t)||(t.type!=zs.POINTERMOVE||this.handlingDownUpSequence||this.handlePointerMove_(t),super.handleEvent(t),!1)}handleDownEvent(t){const e=t.pixel,i=t.map,n=this.getExtentInternal();let r=this.snapToVertex_(e,i);const s=function(t){let e=null,i=null;return t[0]==n[0]?e=n[2]:t[0]==n[2]&&(e=n[0]),t[1]==n[1]?i=n[3]:t[1]==n[3]&&(i=n[1]),null!==e&&null!==i?[e,i]:null};if(r&&n){const t=r[0]==n[0]||r[0]==n[2]?r[0]:null,e=r[1]==n[1]||r[1]==n[3]?r[1]:null;null!==t&&null!==e?this.pointerHandler_=Jx(s(r)):null!==t?this.pointerHandler_=Qx(s([t,n[1]]),s([t,n[3]])):null!==e&&(this.pointerHandler_=Qx(s([n[0],e]),s([n[2],e])))}else r=i.getCoordinateFromPixelInternal(e),this.setExtent([r[0],r[1],r[0],r[1]]),this.pointerHandler_=Jx(r);return!0}handleDragEvent(t){if(this.pointerHandler_){const e=t.coordinate;this.setExtent(this.pointerHandler_(e)),this.createOrUpdatePointerFeature_(e)}}handleUpEvent(t){this.pointerHandler_=null;const e=this.getExtentInternal();return e&&0!==ae(e)||this.setExtent(null),!1}setMap(t){this.extentOverlay_.setMap(t),this.vertexOverlay_.setMap(t),super.setMap(t)}getExtent(){return Gn(this.getExtentInternal(),this.getMap().getView().getProjection())}getExtentInternal(){return this.extent_}setExtent(t){this.extent_=t||null,this.createOrUpdateExtentFeature_(t),this.dispatchEvent(new Hx(this.extent_))}},cP.interaction.Extent.ExtentEvent=Hx,cP.interaction.Interaction=Wo,cP.interaction.Interaction.pan=Zo,cP.interaction.Interaction.zoomByDelta=Yo,cP.interaction.KeyboardPan=ba,cP.interaction.KeyboardZoom=Pa,cP.interaction.Link=class extends Wo{constructor(t){let e;super(),e=!0===(t=Object.assign({animate:!0,params:["x","y","z","r","l"],replace:!1,prefix:""},t||{})).animate?{duration:250}:t.animate?t.animate:null,this.animationOptions_=e,this.params_=t.params.reduce(((t,e)=>(t[e]=!0,t)),{}),this.replace_=t.replace,this.prefix_=t.prefix,this.listenerKeys_=[],this.initial_=!0,this.updateState_=this.updateState_.bind(this),this.trackedCallbacks_={},this.trackedValues_={}}getParamName_(t){return this.prefix_?this.prefix_+t:t}get_(t,e){return t.get(this.getParamName_(e))}set_(t,e,i){e in this.params_&&t.set(this.getParamName_(e),i)}delete_(t,e){e in this.params_&&t.delete(this.getParamName_(e))}setMap(t){const e=this.getMap();super.setMap(t),t!==e&&(e&&this.unregisterListeners_(e),t&&(this.initial_=!0,this.updateState_(),this.registerListeners_(t)))}registerListeners_(t){this.listenerKeys_.push(D(t,Ks,this.updateUrl_,this),D(t.getLayerGroup(),n,this.updateUrl_,this),D(t,"change:layergroup",this.handleChangeLayerGroup_,this)),this.replace_||addEventListener("popstate",this.updateState_)}unregisterListeners_(t){for(let t=0,e=this.listenerKeys_.length;t=0;--t){const n=i[t];for(let t=this.dragSegments_.length-1;t>=0;--t)this.dragSegments_[t][0]===n&&this.dragSegments_.splice(t,1);e.remove(n)}}setActive(t){this.vertexFeature_&&!t&&(this.overlay_.getSource().removeFeature(this.vertexFeature_),this.vertexFeature_=null),super.setActive(t)}setMap(t){this.overlay_.setMap(t),super.setMap(t)}getOverlay(){return this.overlay_}handleSourceAdd_(t){t.feature&&this.features_.push(t.feature)}handleSourceRemove_(t){t.feature&&this.features_.remove(t.feature)}handleFeatureAdd_(t){this.addFeature_(t.element)}handleFeatureChange_(t){if(!this.changingFeature_){const e=t.target;this.removeFeature_(e),this.addFeature_(e)}}handleFeatureRemove_(t){this.removeFeature_(t.element)}writePointGeometry_(t,e){const i=e.getCoordinates(),n={feature:t,geometry:e,segment:[i,i]};this.rBush_.insert(e.getExtent(),n)}writeMultiPointGeometry_(t,e){const i=e.getCoordinates();for(let n=0,r=i.length;nt)));const e=[t.coordinate[0]+this.delta_[0],t.coordinate[1]+this.delta_[1]],i=[],n=[];for(let r=0,s=this.dragSegments_.length;r=0;--e)this.insertVertex_(i[e],t);this.ignoreNextSingleClick_=!0}return!!this.vertexFeature_}handleUpEvent(t){for(let e=this.dragSegments_.length-1;e>=0;--e){const i=this.dragSegments_[e][0],n=i.geometry;if("Circle"===n.getType()){const e=n,r=e.getCenter(),s=i.featureSegments[0],o=i.featureSegments[1];s.segment[0]=r,s.segment[1]=r,o.segment[0]=r,o.segment[1]=r,this.rBush_.update(Kt(r),s);let a=e;const l=Dn();if(l){const e=t.map.getView().getProjection();a=a.clone().transform(l,e),a=gs(a).transform(e,l)}this.rBush_.update(a.getExtent(),o)}else this.rBush_.update(jt(i.segment),i)}return this.featuresBeingModified_&&(this.dispatchEvent(new av(ov,this.featuresBeingModified_,t)),this.featuresBeingModified_=null),!1}handlePointerMove_(t){this.lastPixel_=t.pixel,this.handlePointerAtPixel_(t.coordinate)}handlePointerAtPixel_(t){const e=this.getMap(),i=e.getPixelFromCoordinate(t),n=e.getView().getProjection(),r=function(e,i){return hv(t,e,n)-hv(t,i,n)};let s,o;if(this.hitDetection_){const t="object"==typeof this.hitDetection_?t=>t===this.hitDetection_:void 0;e.forEachFeatureAtPixel(i,((t,e,i)=>{i&&"Point"===i.getType()&&(i=new Xr(Nn(i.getCoordinates(),n)));const r=i||t.getGeometry();if(r&&"Point"===r.getType()&&t instanceof Lt&&this.features_.getArray().includes(t)){o=r;const e=t.getGeometry().getFlatCoordinates().slice(0,2);s=[{feature:t,geometry:o,segment:[e,e]}]}return!0}),{layerFilter:t})}if(!s){const i=Gn(Ut(jn(Kt(t,nv),n),e.getView().getResolution()*this.pixelTolerance_,nv),n);s=this.rBush_.getInExtent(i)}if(s&&s.length>0){const a=s.sort(r)[0],l=a.segment;let h=cv(t,a,n);const c=e.getPixelFromCoordinate(h);let u=oi(i,c);if(o||u<=this.pixelTolerance_){const i={};if(i[z(l)]=!0,this.snapToPointer_||(this.delta_[0]=h[0]-t[0],this.delta_[1]=h[1]-t[1]),"Circle"===a.geometry.getType()&&1===a.index)this.snappedToVertex_=!0,this.createOrUpdateVertexFeature_(h,[a.feature],[a.geometry],this.snappedToVertex_);else{const t=e.getPixelFromCoordinate(l[0]),n=e.getPixelFromCoordinate(l[1]),r=si(c,t),o=si(c,n);if(u=Math.sqrt(Math.min(r,o)),this.snappedToVertex_=u<=this.pixelTolerance_,!this.snappedToVertex_&&!this.insertVertexCondition_(this.lastPointerEvent_))return void(this.vertexFeature_&&(this.overlay_.getSource().removeFeature(this.vertexFeature_),this.vertexFeature_=null));this.snappedToVertex_&&(h=r>o?l[1]:l[0]),this.createOrUpdateVertexFeature_(h,[a.feature],[a.geometry],this.snappedToVertex_);const d={};d[z(a.geometry)]=!0;for(let t=1,e=s.length;t"Circle"===t.getType()||t.getType().endsWith("Point"))))return!1;const t=this.vertexFeature_.getGeometry().getCoordinates();return this.rBush_.getInExtent(jt([t])).some((({segment:e})=>ii(e[0],t)||ii(e[1],t)))}removePoint(t){if(t&&(t=kn(t,this.getMap().getView().getProjection()),this.updatePointer_(t)),!this.lastPointerEvent_||this.lastPointerEvent_&&this.lastPointerEvent_.type!=zs.POINTERDRAG){const t=this.lastPointerEvent_;this.willModifyFeatures_(t,this.dragSegments_.map((([t])=>t)));const e=this.removeVertex_();return this.featuresBeingModified_&&this.dispatchEvent(new av(ov,this.featuresBeingModified_,t)),this.featuresBeingModified_=null,e}return!1}removeVertex_(){const t=this.dragSegments_,e={};let i,n,r,s,o,a,l,h,c,u,d,g=!1;for(o=t.length-1;o>=0;--o)r=t[o],u=r[0],d=z(u.feature),u.depth&&(d+="-"+u.depth.join("-")),d in e||(e[d]={}),0===r[1]?(e[d].right=u,e[d].index=u.index):1==r[1]&&(e[d].left=u,e[d].index=u.index+1);for(d in e){switch(c=e[d].right,l=e[d].left,a=e[d].index,h=a-1,u=void 0!==l?l:c,h<0&&(h=0),s=u.geometry,n=s.getCoordinates(),i=n,g=!1,s.getType()){case"MultiLineString":n[u.depth[0]].length>2&&(n[u.depth[0]].splice(a,1),g=!0);break;case"LineString":n.length>2&&(n.splice(a,1),g=!0);break;case"MultiPolygon":i=i[u.depth[1]];case"Polygon":i=i[u.depth[0]],i.length>4&&(a==i.length-1&&(a=0),i.splice(a,1),g=!0,0===a&&(i.pop(),i.push(i[0]),h=i.length-1))}if(g){this.setGeometryCoordinates_(s,n);const e=[];if(void 0!==l&&(this.rBush_.remove(l),e.push(l.segment[0])),void 0!==c&&(this.rBush_.remove(c),e.push(c.segment[1])),void 0!==l&&void 0!==c){const t={depth:u.depth,feature:u.feature,geometry:u.geometry,index:h,segment:e};this.rBush_.insert(jt(t.segment),t)}this.updateSegmentIndices_(s,a,u.depth,-1),this.vertexFeature_&&(this.overlay_.getSource().removeFeature(this.vertexFeature_),this.vertexFeature_=null),t.length=0}}return g}canInsertPoint(){if(!this.vertexFeature_)return!1;if(this.vertexFeature_.get("geometries").every((t=>"Circle"===t.getType()||t.getType().endsWith("Point"))))return!1;const t=this.vertexFeature_.getGeometry().getCoordinates();return this.rBush_.getInExtent(jt([t])).some((({segment:e})=>!(ii(e[0],t)||ii(e[1],t))))}insertPoint(t){const e=t?kn(t,this.getMap().getView().getProjection()):this.vertexFeature_?.getGeometry().getCoordinates();if(!e)return!1;return this.findInsertVerticesAndUpdateDragSegments_(e).reduce(((t,i)=>t||this.insertVertex_(i,e)),!1)}setGeometryCoordinates_(t,e){this.changingFeature_=!0,t.setCoordinates(e),this.changingFeature_=!1}updateSegmentIndices_(t,e,i,n){this.rBush_.forEachInExtent(t.getExtent(),(function(r){r.geometry===t&&(void 0===i||void 0===r.depth||w(r.depth,i))&&r.index>e&&(r.index+=n)}))}},cP.interaction.Modify.ModifyEvent=av,cP.interaction.MouseWheelZoom=Ia,cP.interaction.PinchRotate=Fa,cP.interaction.PinchZoom=La,cP.interaction.Pointer=ua,cP.interaction.Pointer.centroid=da,cP.interaction.Select=pv,cP.interaction.Select.SelectEvent=gv,cP.interaction.Snap=class extends ua{constructor(t){const e=t=t||{};e.handleDownEvent||(e.handleDownEvent=C),e.stopDown||(e.stopDown=R),super(e),this.on,this.once,this.un,this.source_=t.source?t.source:null,this.vertex_=void 0===t.vertex||t.vertex,this.edge_=void 0===t.edge||t.edge,this.intersection_=void 0!==t.intersection&&t.intersection,this.features_=t.features?t.features:null,this.featuresListenerKeys_=[],this.featureChangeListenerKeys_={},this.indexedFeaturesExtents_={},this.pendingFeatures_={},this.pixelTolerance_=void 0!==t.pixelTolerance?t.pixelTolerance:10,this.rBush_=new Od,this.snapped_=null,this.segmenters_=Object.assign({},xv,t.segmenters)}addFeature(t,e){e=void 0===e||e;const i=z(t),r=t.getGeometry();if(r){const e=this.segmenters_[r.getType()];if(e){this.indexedFeaturesExtents_[i]=r.getExtent([1/0,1/0,-1/0,-1/0]);const n=e(r,this.getMap().getView().getProjection());let s=n.length;for(let e=0;e=0;--t)e.remove(i[t])}i&&(k(this.featureChangeListenerKeys_[n]),delete this.featureChangeListenerKeys_[n])}setMap(i){const n=this.getMap(),r=this.featuresListenerKeys_;let s=this.getFeatures_();if(Array.isArray(s)||(s=s.getArray()),n&&(r.forEach(k),r.length=0,this.rBush_.clear(),Object.values(this.featureChangeListenerKeys_).forEach(k),this.featureChangeListenerKeys_={}),super.setMap(i),i){this.features_?r.push(D(this.features_,t,this.handleFeatureAdd_,this),D(this.features_,e,this.handleFeatureRemove_,this)):this.source_&&r.push(D(this.source_,Dd,this.handleFeatureAdd_,this),D(this.source_,Gd,this.handleFeatureRemove_,this));for(const t of s)this.addFeature(t)}}snapTo(t,e,i){const n=i.getView().getProjection(),r=kn(e,n),s=Gn(Ut(jt([r]),i.getView().getResolution()*this.pixelTolerance_),n),o=this.rBush_.getInExtent(s),a=o.length;if(0===a)return null;let l,h,c,u=1/0,d=null;const g=this.pixelTolerance_*this.pixelTolerance_,f=()=>{if(l){const e=i.getPixelFromCoordinate(l);if(si(t,e)<=g&&(c&&this.intersection_||!c&&(this.vertex_||this.edge_)))return{vertex:l,vertexPixel:[Math.round(e[0]),Math.round(e[1])],feature:h,segment:d}}return null};if(this.vertex_||this.intersection_){for(let t=0;t{if(t instanceof Lt&&this.filter_(t,e)&&(!this.features_||this.features_.getArray().includes(t)))return t}),{layerFilter:this.layerFilter_,hitTolerance:this.hitTolerance_})}getHitTolerance(){return this.hitTolerance_}setHitTolerance(t){this.hitTolerance_=t}setMap(t){const e=this.getMap();super.setMap(t),this.updateState_(e)}handleActiveChanged_(){this.updateState_(null)}updateState_(t){let e=this.getMap();const i=this.getActive();if((!e||!i)&&(e=e||t,e)){e.getViewport().classList.remove("ol-grab","ol-grabbing")}}},cP.interaction.Translate.TranslateEvent=bv,cP.interaction.defaults={},cP.interaction.defaults.defaults=Ma,cP.layer={},cP.layer.Base=Xa,cP.layer.BaseImage=xf,cP.layer.BaseTile=Pf,cP.layer.BaseVector=Nc,cP.layer.Flow=xx,cP.layer.Graticule=class extends wx{constructor(t){t=t||{};const e=Object.assign({updateWhileAnimating:!0,updateWhileInteracting:!0,renderBuffer:0},t);delete e.maxLines,delete e.strokeStyle,delete e.targetSize,delete e.showLabels,delete e.lonLabelFormatter,delete e.latLabelFormatter,delete e.lonLabelPosition,delete e.latLabelPosition,delete e.lonLabelStyle,delete e.latLabelStyle,delete e.intervals,super(e),this.projection_=null,this.maxLat_=1/0,this.maxLon_=1/0,this.minLat_=-1/0,this.minLon_=-1/0,this.maxX_=1/0,this.maxY_=1/0,this.minX_=-1/0,this.minY_=-1/0,this.targetSize_=void 0!==t.targetSize?t.targetSize:100,this.maxLines_=void 0!==t.maxLines?t.maxLines:100,this.meridians_=[],this.parallels_=[],this.strokeStyle_=void 0!==t.strokeStyle?t.strokeStyle:Tx,this.fromLonLatTransform_=void 0,this.toLonLatTransform_=void 0,this.projectionCenterLonLat_=null,this.bottomLeft_=null,this.bottomRight_=null,this.topLeft_=null,this.topRight_=null,this.meridiansLabels_=null,this.parallelsLabels_=null,t.showLabels&&(this.lonLabelFormatter_=null==t.lonLabelFormatter?ti.bind(this,"EW"):t.lonLabelFormatter,this.latLabelFormatter_=null==t.latLabelFormatter?ti.bind(this,"NS"):t.latLabelFormatter,this.lonLabelPosition_=null==t.lonLabelPosition?0:t.lonLabelPosition,this.latLabelPosition_=null==t.latLabelPosition?1:t.latLabelPosition,this.lonLabelStyleBase_=new rc({text:void 0!==t.lonLabelStyle?t.lonLabelStyle.clone():new cc({font:"12px Calibri,sans-serif",textBaseline:"bottom",fill:new tc({color:"rgba(0,0,0,1)"}),stroke:new nc({color:"rgba(255,255,255,1)",width:3})})}),this.lonLabelStyle_=t=>{const e=t.get("graticule_label");return this.lonLabelStyleBase_.getText().setText(e),this.lonLabelStyleBase_},this.latLabelStyleBase_=new rc({text:void 0!==t.latLabelStyle?t.latLabelStyle.clone():new cc({font:"12px Calibri,sans-serif",textAlign:"right",fill:new tc({color:"rgba(0,0,0,1)"}),stroke:new nc({color:"rgba(255,255,255,1)",width:3})})}),this.latLabelStyle_=t=>{const e=t.get("graticule_label");return this.latLabelStyleBase_.getText().setText(e),this.latLabelStyleBase_},this.meridiansLabels_=[],this.parallelsLabels_=[],this.addEventListener(Ya,this.drawLabels_.bind(this))),this.intervals_=void 0!==t.intervals?t.intervals:Cx,this.setSource(new Xd({loader:this.loaderFunction.bind(this),strategy:this.strategyFunction.bind(this),features:new Z,overlaps:!1,useSpatialIndex:!1,wrapX:t.wrapX})),this.featurePool_=[],this.lineStyle_=new rc({stroke:this.strokeStyle_}),this.loadedExtent_=null,this.renderedExtent_=null,this.renderedResolution_=null,this.setRenderOrder(null)}strategyFunction(t,e){let i=t.slice();return this.projection_&&this.getSource().getWrapX()&&Ce(i,this.projection_),this.loadedExtent_&&(Qt(this.loadedExtent_,i,e)?i=this.loadedExtent_.slice():this.getSource().removeLoadedExtent(this.loadedExtent_)),[i]}loaderFunction(t,e,i){this.loadedExtent_=t;const n=this.getSource(),r=pe(this.getExtent()||[-1/0,-1/0,1/0,1/0],t);if(this.renderedExtent_&&Jt(this.renderedExtent_,r)&&this.renderedResolution_===e)return;if(this.renderedExtent_=r,this.renderedResolution_=e,ve(r))return;const s=ce(r),o=e*e/4;(!this.projection_||!bn(this.projection_,i))&&this.updateProjectionInfo_(i),this.createGraticule_(r,s,e,o);let a,l=this.meridians_.length+this.parallels_.length;for(this.meridiansLabels_&&(l+=this.meridians_.length),this.parallelsLabels_&&(l+=this.parallels_.length);l>this.featurePool_.length;)a=new Lt,this.featurePool_.push(a);const h=n.getFeaturesCollection();h.clear();let c,u,d=0;for(c=0,u=this.meridians_.length;cMath.PI/2}const d=xu(t);for(let t=a;t<=l;++t){let i,n,c,g,f=this.meridians_.length+this.parallels_.length;if(this.meridiansLabels_)for(n=0,c=this.meridiansLabels_.length;n=a?(t[0]=o[0],t[2]=o[2]):s=!0);const l=[be(e[0],this.minX_,this.maxX_),be(e[1],this.minY_,this.maxY_)],h=this.toLonLatTransform_(l);isNaN(h[1])&&(h[1]=Math.abs(this.maxLat_)>=Math.abs(this.minLat_)?this.maxLat_:this.minLat_);let c=be(h[0],this.minLon_,this.maxLon_),u=be(h[1],this.minLat_,this.maxLat_);const d=this.maxLines_;let g,f,p,m,_=t;s||(_=[be(t[0],this.minX_,this.maxX_),be(t[1],this.minY_,this.maxY_),be(t[2],this.minX_,this.maxX_),be(t[3],this.minY_,this.maxY_)]);const y=Te(_,this.toLonLatTransform_,void 0,8);let x=y[3],v=y[2],S=y[1],E=y[0];if(s||(Xt(_,this.bottomLeft_)&&(E=this.minLon_,S=this.minLat_),Xt(_,this.bottomRight_)&&(v=this.maxLon_,S=this.minLat_),Xt(_,this.topLeft_)&&(E=this.minLon_,x=this.maxLat_),Xt(_,this.topRight_)&&(v=this.maxLon_,x=this.maxLat_),x=be(x,u,this.maxLat_),v=be(v,c,this.maxLon_),S=be(S,this.minLat_,u),E=be(E,this.minLon_,c)),c=Math.floor(c/r)*r,m=be(c,this.minLon_,this.maxLon_),f=this.addMeridian_(m,S,x,n,t,0),g=0,s)for(;(m-=r)>=E&&g++n[s]&&(r=s,s=1);const o=Math.max(e[1],n[r]),a=Math.min(e[3],n[s]),l=be(e[1]+Math.abs(e[1]-e[3])*this.lonLabelPosition_,o,a),h=[n[r-1]+(n[s-1]-n[r-1])*(l-n[r])/(n[s]-n[r]),l],c=this.meridiansLabels_[i].geom;return c.setCoordinates(h),c}getMeridians(){return this.meridians_}getParallel_(t,e,i,n,r){const s=Ex(t,e,i,this.projection_,n);let o=this.parallels_[r];return o?(o.setFlatCoordinates("XY",s),o.changed()):o=new bd(s,"XY"),o}getParallelPoint_(t,e,i){const n=t.getFlatCoordinates();let r=0,s=n.length-2;n[r]>n[s]&&(r=s,s=0);const o=Math.max(e[0],n[r]),a=Math.min(e[2],n[s]),l=be(e[0]+Math.abs(e[0]-e[2])*this.latLabelPosition_,o,a),h=[l,n[r+1]+(n[s+1]-n[r+1])*(l-n[r])/(n[s]-n[r])],c=this.parallelsLabels_[i].geom;return c.setCoordinates(h),c}getParallels(){return this.parallels_}updateProjectionInfo_(t){const e=xn("EPSG:4326"),i=t.getWorldExtent();this.maxLat_=i[3],this.maxLon_=i[2],this.minLat_=i[1],this.minLon_=i[0];const n=Fn(t,e);if(this.minLon_=Math.abs(this.minLat_)?this.maxLat_:this.minLat_),this.projection_=t}},cP.layer.Group=Wa,cP.layer.Group.GroupEvent=Va,cP.layer.Heatmap=class extends Nc{constructor(t){t=t||{};const e=Object.assign({},t);delete e.gradient,delete e.radius,delete e.blur,delete e.weight,super(e),this.filter_=t.filter??!0,this.styleVariables_=t.variables||{},this.gradient_=null,this.addChangeListener(bx,this.handleGradientChanged_),this.setGradient(t.gradient?t.gradient:Ix),this.setBlur(void 0!==t.blur?t.blur:15),this.setRadius(void 0!==t.radius?t.radius:8);const i=t.weight?t.weight:"weight";this.weight_=i,this.setRenderOrder(null)}getBlur(){return this.get(Rx)}getGradient(){return this.get(bx)}getRadius(){return this.get(Px)}handleGradientChanged_(){this.gradient_=function(t){const e=1,i=256,n=ft(e,i),r=n.createLinearGradient(0,0,e,i),s=1/(t.length-1);for(let e=0,i=t.length;ethis.getBlur(),t.addUniform("a_blur","float")),"number"==typeof this.getRadius()&&(n="a_radius",s.a_radius=()=>this.getRadius(),t.addUniform("a_radius","float"));const o={};let a=null;if("string"==typeof this.weight_||"function"==typeof this.weight_){const e="string"==typeof this.weight_?t=>t.get(this.weight_):this.weight_;o.prop_weight={size:1,callback:t=>{const i=e(t);return void 0!==i?be(i,0,1):1}},a="a_prop_weight",t.addAttribute("a_prop_weight","float")}else{a=F_(e,["clamp",this.weight_,0,1],Vl)}t.addFragmentShaderFunction(`float getBlurSlope() {\n float blur = max(1., ${r});\n float radius = ${n};\n return radius / blur;\n}`).setSymbolSizeExpression(`vec2(${n} + ${r}) * 2.`).setSymbolColorExpression(`vec4(smoothstep(0., 1., (1. - length(coordsPx * 2. / v_quadSizePx)) * getBlurSlope()) * ${a})`).setStrokeColorExpression(`vec4(smoothstep(0., 1., (1. - length(currentRadiusPx * 2. / v_width)) * getBlurSlope()) * ${a})`).setStrokeWidthExpression(`(${n} + ${r}) * 2.`).setFillColorExpression(`vec4(${a})`).setFragmentDiscardExpression(`!${i}`),D_(t,e);const l=k_(e),h=N_(e,this.styleVariables_);return new oy(this,{className:this.getClassName(),variables:this.styleVariables_,style:{builder:t,attributes:{...l,...o},uniforms:{...h,...s}},disableHitDetection:!1,postProcesses:[{fragmentShader:"\n precision mediump float;\n\n uniform sampler2D u_image;\n uniform sampler2D u_gradientTexture;\n uniform float u_opacity;\n\n varying vec2 v_texCoord;\n\n void main() {\n vec4 color = texture2D(u_image, v_texCoord);\n gl_FragColor.a = color.a * u_opacity;\n gl_FragColor.rgb = texture2D(u_gradientTexture, vec2(0.5, color.a)).rgb;\n gl_FragColor.rgb *= gl_FragColor.a;\n }",uniforms:{u_gradientTexture:()=>this.gradient_,u_opacity:()=>this.getOpacity()}}]})}updateStyleVariables(t){Object.assign(this.styleVariables_,t),this.changed()}renderDeclutter(){}},cP.layer.Image=vf,cP.layer.Layer=Ja,cP.layer.Layer.inView=Qa,cP.layer.Tile=If,cP.layer.Vector=wx,cP.layer.VectorImage=class extends Nc{constructor(t){t=t||{};const e=Object.assign({},t);delete e.imageRatio,super(e),this.imageRatio_=void 0!==t.imageRatio?t.imageRatio:1}getImageRatio(){return this.imageRatio_}createRenderer(){return new sx(this)}},cP.layer.VectorTile=class extends Nc{constructor(t){t=t||{};const e=Object.assign({},t);delete e.preload;const i=void 0===t.cacheSize?0:t.cacheSize;delete t.cacheSize,delete e.useInterimTilesOnError,super(e),this.on,this.once,this.un,this.cacheSize_=i;const n=t.renderMode||"hybrid";Ft("hybrid"==n||"vector"==n,"`renderMode` must be `'hybrid'` or `'vector'`"),this.renderMode_=n,this.setPreload(t.preload?t.preload:0),this.setUseInterimTilesOnError(void 0===t.useInterimTilesOnError||t.useInterimTilesOnError),this.getBackground,this.setBackground}createRenderer(){return new lx(this,{cacheSize:this.cacheSize_})}getFeatures(t){return super.getFeatures(t)}getFeaturesInExtent(t){return this.getRenderer().getFeaturesInExtent(t)}getRenderMode(){return this.renderMode_}getPreload(){return this.get(Rf)}getUseInterimTilesOnError(){return this.get(bf)}setPreload(t){this.set(Rf,t)}setUseInterimTilesOnError(t){this.set(bf,t)}},cP.layer.WebGLPoints=class extends Ja{constructor(t){super(Object.assign({},t)),this.styleVariables_=t.variables||{},this.parseResult_=X_(t.style,this.styleVariables_,t.filter),this.hitDetectionDisabled_=!!t.disableHitDetection}createRenderer(){const t=Object.keys(this.parseResult_.attributes).map((t=>({name:t,...this.parseResult_.attributes[t]})));return new Rm(this,{vertexShader:this.parseResult_.builder.getSymbolVertexShader(),fragmentShader:this.parseResult_.builder.getSymbolFragmentShader(),hitDetectionEnabled:!this.hitDetectionDisabled_,uniforms:this.parseResult_.uniforms,attributes:t})}updateStyleVariables(t){Object.assign(this.styleVariables_,t),this.changed()}},cP.layer.WebGLTile=Lx,cP.layer.WebGLVector=class extends Ja{constructor(t){super(Object.assign({},t)),this.styleVariables_=t.variables||{},this.style_=t.style,this.hitDetectionDisabled_=!!t.disableHitDetection}createRenderer(){return new oy(this,{style:this.style_,variables:this.styleVariables_,disableHitDetection:this.hitDetectionDisabled_})}updateStyleVariables(t){Object.assign(this.styleVariables_,t),this.changed()}setStyle(t){this.style_=t,this.clearRenderer(),this.changed()}},cP.layer.WebGLVectorTile=class extends Pf{constructor(t){super(Object.assign({},t)),this.styleVariables_=t.variables||{},this.style_=t.style,this.hitDetectionDisabled_=!!t.disableHitDetection}createRenderer(){return new hy(this,{style:this.style_,variables:this.styleVariables_,disableHitDetection:this.hitDetectionDisabled_})}updateStyleVariables(t){Object.assign(this.styleVariables_,t),this.changed()}setStyle(t){this.style_=t,this.clearRenderer(),this.changed()}},cP.loadingstrategy={},cP.loadingstrategy.all=su,cP.loadingstrategy.bbox=function(t,e){return[t]},cP.loadingstrategy.tile=function(t){return function(e,i,n){const r=t.getZForResolution(Bn(i,n)),s=t.getTileRangeForExtentAndZ(jn(e,n),r),o=[],a=[r,0,0];for(a[1]=s.minX;a[1]<=s.maxX;++a[1])for(a[2]=s.minY;a[2]<=s.maxY;++a[2])o.push(Gn(t.getTileCoordExtent(a),n));return o}},cP.math={},cP.math.ceil=Ge,cP.math.clamp=be,cP.math.floor=ke,cP.math.lerp=Oe,cP.math.modulo=Ae,cP.math.round=Ne,cP.math.solveLinearSystem=Fe,cP.math.squaredDistance=Ie,cP.math.squaredSegmentDistance=Pe,cP.math.toDegrees=Le,cP.math.toFixed=De,cP.math.toRadians=Me,cP.math.wrap=je,cP.net={},cP.net.ClientError=lu,cP.net.ResponseError=au,cP.net.getJSON=hu,cP.net.jsonp=ou,cP.net.overrideXHR=function(t){"undefined"!=typeof XMLHttpRequest&&(tu=XMLHttpRequest),global.XMLHttpRequest=t},cP.net.resolveUrl=cu,cP.net.restoreXHR=function(){global.XMLHttpRequest=tu},cP.obj={},cP.obj.clear=F,cP.obj.isEmpty=L,cP.proj={},cP.proj.Projection=fi,cP.proj.Units={},cP.proj.Units.METERS_PER_UNIT=gi,cP.proj.Units.fromCode=di,cP.proj.addCommon=Xn,cP.proj.addCoordinateTransforms=Cn,cP.proj.addEquivalentProjections=Sn,cP.proj.addEquivalentTransforms=En,cP.proj.addProjection=_n,cP.proj.addProjections=yn,cP.proj.clearAllProjections=function(){Fi(),Oi()},cP.proj.clearUserProjection=function(){An=null},cP.proj.cloneTransform=pn,cP.proj.createProjection=wn,cP.proj.createSafeCoordinateTransform=zn,cP.proj.createTransformFromCoordinateTransform=Tn,cP.proj.disableCoordinateWarning=fn,cP.proj.epsg3857={},cP.proj.epsg3857.EXTENT=_i,cP.proj.epsg3857.HALF_SIZE=mi,cP.proj.epsg3857.MAX_SAFE_Y=xi,cP.proj.epsg3857.PROJECTIONS=Si,cP.proj.epsg3857.RADIUS=pi,cP.proj.epsg3857.WORLD_EXTENT=yi,cP.proj.epsg3857.fromEPSG4326=Ei,cP.proj.epsg3857.toEPSG4326=wi,cP.proj.epsg4326={},cP.proj.epsg4326.EXTENT=Ci,cP.proj.epsg4326.METERS_PER_UNIT=Ri,cP.proj.epsg4326.PROJECTIONS=Pi,cP.proj.epsg4326.RADIUS=Ti,cP.proj.equivalent=bn,cP.proj.fromLonLat=function(t,e){return fn(),Ln(t,"EPSG:4326",void 0!==e?e:"EPSG:3857")},cP.proj.fromUserCoordinate=kn,cP.proj.fromUserExtent=jn,cP.proj.fromUserResolution=Bn,cP.proj.get=xn,cP.proj.getPointResolution=vn,cP.proj.getTransform=Fn,cP.proj.getTransformFromProjections=Pn,cP.proj.getUserProjection=Dn,cP.proj.identityTransform=mn,cP.proj.proj4={},cP.proj.proj4.epsgLookupMapTiler=function(t){return async function(e){const i=await fetch(`https://api.maptiler.com/coordinates/search/code:${e}.json?transformations=true&exports=true&key=${t}`);if(!i.ok)throw new Error(`Unexpected response from maptiler.com: ${i.status}`);return i.json().then((t=>{const i=t.results;if(i?.length>0){const t=i.filter((t=>"EPSG"===t.id?.authority&&t.id?.code===e))[0];if(t){const e=t.transformations;if(e?.length>0){const i=t.default_transformation;if(e.filter((t=>t.id?.authority===i?.authority&&t.id?.code===i?.code&&0===t.grids?.length)).length>0)return t.exports?.proj4;const n=e.filter((t=>0===t.grids?.length&&"EPSG"===t.target_crs?.authority&&4326===t.target_crs?.code&&!1===t.deprecated&&!0===t.usable)).sort(((t,e)=>t.accuracy-e.accuracy))[0]?.exports?.proj4;if(n)return n}return t.exports?.proj4}}}))}},cP.proj.proj4.fromEPSGCode=async function(t){"string"==typeof t&&(t=parseInt(t.split(":").pop(),10));const e=hx;if(!e)throw new Error("Proj4 must be registered first with register(proj4)");const i="EPSG:"+t;return e.defs(i)||(e.defs(i,await ux(t)),cx(e)),Li(i)},cP.proj.proj4.getEPSGLookup=function(){return ux},cP.proj.proj4.isRegistered=function(){return!!hx},cP.proj.proj4.register=cx,cP.proj.proj4.setEPSGLookup=function(t){ux=t},cP.proj.proj4.unregister=function(){hx=null},cP.proj.projections={},cP.proj.projections.add=Mi,cP.proj.projections.clear=Fi,cP.proj.projections.get=Li,cP.proj.setUserProjection=On,cP.proj.toLonLat=Rn,cP.proj.toUserCoordinate=Nn,cP.proj.toUserExtent=Gn,cP.proj.toUserResolution=Un,cP.proj.transform=Ln,cP.proj.transformExtent=Mn,cP.proj.transformWithProjections=function(t,e,i){return Pn(e,i)(t)},cP.proj.transforms={},cP.proj.transforms.add=Di,cP.proj.transforms.clear=Oi,cP.proj.transforms.get=Ni,cP.proj.transforms.remove=function(t,e){const i=t.getCode(),n=e.getCode(),r=Ai[i][n];return delete Ai[i][n],L(Ai[i])&&delete Ai[i],r},cP.proj.useGeographic=function(){On("EPSG:4326")},cP.proj.utm={},cP.proj.utm.makeProjection=hn,cP.proj.utm.makeTransforms=cn,cP.proj.utm.zoneFromCode=an,cP.render={},cP.render.Box=pa,cP.render.Event=kc,cP.render.Feature=Md,cP.render.Feature.toFeature=function(t,e){const i=t.getId(),n=Ad(t),r=t.getProperties(),s=new Lt;return void 0!==e&&s.setGeometryName(e),s.setGeometry(n),void 0!==i&&s.setId(i),s.setProperties(r,!0),s},cP.render.Feature.toGeometry=Ad,cP.render.VectorContext=uu,cP.render.canvas={},cP.render.canvas.Builder=by,cP.render.canvas.BuilderGroup=Ny,cP.render.canvas.Executor=Yy,cP.render.canvas.ExecutorGroup=Jy,cP.render.canvas.ExecutorGroup.ALL=Hy,cP.render.canvas.ExecutorGroup.DECLUTTER=Ky,cP.render.canvas.ExecutorGroup.NON_DECLUTTER=qy,cP.render.canvas.ExecutorGroup.getPixelIndexArray=tx,cP.render.canvas.ImageBuilder=Py,cP.render.canvas.Immediate=du,cP.render.canvas.Instruction={},cP.render.canvas.Instruction.beginPathInstruction=Cy,cP.render.canvas.Instruction.closePathInstruction=Ry,cP.render.canvas.Instruction.fillInstruction=wy,cP.render.canvas.Instruction.strokeInstruction=Ty,cP.render.canvas.LineStringBuilder=Iy,cP.render.canvas.PolygonBuilder=Fy,cP.render.canvas.TextBuilder=Oy,cP.render.canvas.TextBuilder.TEXT_ALIGN=Ay,cP.render.canvas.ZIndexContext=gf,cP.render.canvas.checkedFonts=Gh,cP.render.canvas.defaultFillStyle=Ih,cP.render.canvas.defaultFont=Ph,cP.render.canvas.defaultLineCap=Fh,cP.render.canvas.defaultLineDash=Lh,cP.render.canvas.defaultLineDashOffset=0,cP.render.canvas.defaultLineJoin=Mh,cP.render.canvas.defaultLineWidth=1,cP.render.canvas.defaultMiterLimit=Ah,cP.render.canvas.defaultPadding=kh,cP.render.canvas.defaultStrokeStyle=Oh,cP.render.canvas.defaultTextAlign=Dh,cP.render.canvas.defaultTextBaseline=Nh,cP.render.canvas.drawImageOrLabel=Kh,cP.render.canvas.getTextDimensions=Hh,cP.render.canvas.hitdetect={},cP.render.canvas.hitdetect.HIT_DETECT_RESOLUTION=ex,cP.render.canvas.hitdetect.createHitDetectionImageData=ix,cP.render.canvas.hitdetect.hitDetect=nx,cP.render.canvas.measureAndCacheTextWidth=Yh,cP.render.canvas.measureTextHeight=$h,cP.render.canvas.measureTextWidth=Zh,cP.render.canvas.registerFont=Vh,cP.render.canvas.rotateAtOffset=function(t,e,i,n){0!==e&&(t.translate(i,n),t.rotate(e),t.translate(-i,-n))},cP.render.canvas.style={},cP.render.canvas.style.buildRuleSet=fc,cP.render.canvas.style.buildStyle=pc,cP.render.canvas.style.flatStylesToStyleFunction=gc,cP.render.canvas.style.rulesToStyleFunction=dc,cP.render.canvas.textHeights=Bh,cP.render.getRenderPixel=function(t,e){return Kn(t.inversePixelTransform,e.slice(0))},cP.render.getVectorContext=xu,cP.render.toContext=function(t,e){const i=t.canvas,n=(e=e||{}).pixelRatio||ht,r=e.size;r&&(i.width=r[0]*n,i.height=r[1]*n,i.style.width=r[0]+"px",i.style.height=r[1]+"px");const s=[0,0,i.width,i.height],o=Jn([1,0,0,1,0,0],n,n);return new du(t,n,s,o,0)},cP.render.webgl={},cP.render.webgl.MixedGeometryBatch=Vp,cP.render.webgl.ShaderBuilder={},cP.render.webgl.ShaderBuilder.COMMON_HEADER=b_,cP.render.webgl.ShaderBuilder.ShaderBuilder=I_,cP.render.webgl.VectorStyleRenderer=ry,cP.render.webgl.bufferUtil={},cP.render.webgl.bufferUtil.LINESTRING_ANGLE_COSINE_CUTOFF=w_,cP.render.webgl.bufferUtil.writeLineSegmentToBuffers=function(t,e,i,n,r,s,o,a,l,h,c){const u=10+a.length,d=s.length/u,g=[t[e+0],t[e+1]],f=[t[i],t[i+1]],p=t[e+2],m=t[i+2],_=Kn(l,[...g]),y=Kn(l,[...f]);function x(t,e,i){const n=Math.sqrt((e[0]-t[0])*(e[0]-t[0])+(e[1]-t[1])*(e[1]-t[1])),r=[(e[0]-t[0])/n,(e[1]-t[1])/n],s=[-r[1],r[0]],o=Math.sqrt((i[0]-t[0])*(i[0]-t[0])+(i[1]-t[1])*(i[1]-t[1])),a=[(i[0]-t[0])/o,(i[1]-t[1])/o],l=0===n||0===o?0:Math.acos(be(a[0]*r[0]+a[1]*r[1],-1,1));return a[0]*s[0]+a[1]*s[1]>0?l:2*Math.PI-l}let v=-1,S=-1,E=c;const w=null!==r;if(null!==n){v=x(_,y,Kn(l,[...[t[n],t[n+1]]])),Math.cos(v)<=w_&&(E+=Math.tan((v-Math.PI)/2))}if(w){S=x(y,_,Kn(l,[...[t[r],t[r+1]]])),Math.cos(S)<=w_&&(E+=Math.tan((Math.PI-S)/2))}function T(t,e){return 0===e?1e4*t:Math.sign(e)*(1e4*t+Math.abs(e))}return s.push(g[0],g[1],p,f[0],f[1],m,v,S,h,T(0,c)),s.push(...a),s.push(g[0],g[1],p,f[0],f[1],m,v,S,h,T(1,c)),s.push(...a),s.push(g[0],g[1],p,f[0],f[1],m,v,S,h,T(2,c)),s.push(...a),s.push(g[0],g[1],p,f[0],f[1],m,v,S,h,T(3,c)),s.push(...a),o.push(d,d+1,d+2,d+1,d+3,d+2),{length:h+Math.sqrt((y[0]-_[0])*(y[0]-_[0])+(y[1]-_[1])*(y[1]-_[1])),angle:E}},cP.render.webgl.bufferUtil.writePointFeatureToBuffers=function(t,e,i,n,r,s){const o=3+r,a=t[e+0],l=t[e+1],h=T_;h.length=r;for(let i=0;it.json())).then((t=>this.handleImageryMetadataResponse(t)))}getApiKey(){return this.apiKey_}getImagerySet(){return this.imagerySet_}handleImageryMetadataResponse(t){if(200!=t.statusCode||"OK"!=t.statusDescription||"ValidCredentials"!=t.authenticationResultCode||1!=t.resourceSets.length||1!=t.resourceSets[0].resources.length)return void this.setState("error");const e=t.resourceSets[0].resources[0],i=-1==this.maxZoom_?e.zoomMax:this.maxZoom_,n=$u(this.getProjection()),r=this.hidpi_?2:1,s=e.imageWidth==e.imageHeight?e.imageWidth/r:[e.imageWidth/r,e.imageHeight/r],o=zu({extent:n,minZoom:e.zoomMin,maxZoom:i,tileSize:s});this.tileGrid=o;const a=this.culture_,l=this.hidpi_,h=this.placeholderTiles_;if(this.tileUrlFunction=id(e.imageUrlSubdomains.map((function(t){const i=[0,0,0],n=e.imageUrl.replace("{subdomain}",t).replace("{culture}",a);return function(t,e,r){if(!t)return;Iu(t[0],t[1],t[2],i);const s=new URL(n.replace("{quadkey}",_d(i))),o=s.searchParams;return l&&(o.set("dpi","d1"),o.set("device","mobile")),!0===h?o.delete("n"):!1===h&&o.set("n","z"),s.toString()}}))),e.imageryProviders){const t=Pn(xn("EPSG:4326"),this.getProjection());this.setAttributions((i=>{const n=[],r=i.viewState,s=this.getTileGrid(),o=s.getZForResolution(r.resolution,this.zDirection),a=s.getTileCoordForCoordAndZ(r.center,o)[0];return e.imageryProviders.map((function(e){let r=!1;const s=e.coverageAreas;for(let e=0,n=s.length;e=n.zoomMin&&a<=n.zoomMax){const e=n.bbox;if(xe(Te([e[1],e[0],e[3],e[2]],t),i.extent)){r=!0;break}}}r&&n.push(e.attribution)})),n.push('
Terms of Use'),n}))}this.setState("ready")}},cP.source.BingMaps.quadKey=_d,cP.source.CartoDB=class extends yd{constructor(t){super({attributions:t.attributions,cacheSize:t.cacheSize,crossOrigin:t.crossOrigin,maxZoom:void 0!==t.maxZoom?t.maxZoom:18,minZoom:t.minZoom,projection:t.projection,transition:t.transition,wrapX:t.wrapX,zDirection:t.zDirection}),this.account_=t.account,this.mapId_=t.map||"",this.config_=t.config||{},this.templateCache_={},this.initializeMap_()}getConfig(){return this.config_}updateConfig(t){Object.assign(this.config_,t),this.initializeMap_()}setConfig(t){this.config_=t||{},this.initializeMap_()}initializeMap_(){const t=JSON.stringify(this.config_);if(this.templateCache_[t])return void this.applyTemplate_(this.templateCache_[t]);let e="https://"+this.account_+".carto.com/api/v1/map";this.mapId_&&(e+="/named/"+this.mapId_);const i=new XMLHttpRequest;i.addEventListener("load",this.handleInitResponse_.bind(this,t)),i.addEventListener("error",this.handleInitError_.bind(this)),i.open("POST",e),i.setRequestHeader("Content-type","application/json"),i.send(JSON.stringify(this.config_))}handleInitResponse_(t,e){const i=e.target;if(!i.status||i.status>=200&&i.status<300){let e;try{e=JSON.parse(i.responseText)}catch{return void this.setState("error")}this.applyTemplate_(e),this.templateCache_[t]=e,this.setState("ready")}else this.setState("error")}handleInitError_(t){this.setState("error")}applyTemplate_(t){const e="https://"+t.cdn_url.https+"/"+this.account_+"/api/v1/map/"+t.layergroupid+"/{z}/{x}/{y}.png";this.setUrl(e)}},cP.source.Cluster=class extends Xd{constructor(t){super({attributions:(t=t||{}).attributions,wrapX:t.wrapX}),this.resolution=void 0,this.distance=void 0!==t.distance?t.distance:20,this.minDistance=t.minDistance||0,this.interpolationRatio=0,this.features=[],this.geometryFunction=t.geometryFunction||function(t){const e=t.getGeometry();return Ft(!e||"Point"===e.getType(),"The default `geometryFunction` can only handle `Point` or null geometries"),e},this.createCustomCluster_=t.createCluster,this.source=null,this.boundRefresh_=this.refresh.bind(this),this.updateDistance(this.distance,this.minDistance),this.setSource(t.source||null)}clear(t){this.features.length=0,super.clear(t)}getDistance(){return this.distance}getSource(){return this.source}loadFeatures(t,e,i){this.source?.loadFeatures(t,e,i),e!==this.resolution&&(this.resolution=e,this.refresh())}setDistance(t){this.updateDistance(t,this.minDistance)}setMinDistance(t){this.updateDistance(this.distance,t)}getMinDistance(){return this.minDistance}setSource(t){this.source&&this.source.removeEventListener(n,this.boundRefresh_),this.source=t,t&&t.addEventListener(n,this.boundRefresh_),this.refresh()}refresh(){this.clear(),this.cluster(),this.addFeatures(this.features)}updateDistance(t,e){const i=0===t?0:Math.min(e,t)/t,n=t!==this.distance||this.interpolationRatio!==i;this.distance=t,this.minDistance=e,this.interpolationRatio=i,n&&this.refresh()}cluster(){if(void 0===this.resolution||!this.source)return;const t=[1/0,1/0,-1/0,-1/0],e=this.distance*this.resolution,i=this.source.getFeatures(),n={};for(let r=0,s=i.length;r=0;--e){const n=this.geometryFunction(t[e]);n?qe(i,n.getCoordinates()):t.splice(e,1)}ri(i,1/t.length);const n=ce(e),r=this.interpolationRatio,s=new Xr([i[0]*(1-r)+n[0]*r,i[1]*(1-r)+n[1]*r]);return this.createCustomCluster_?this.createCustomCluster_(s,t):new Lt({geometry:s,features:t})}},cP.source.DataTile=rg,cP.source.GeoTIFF=_g,cP.source.Google=class extends pd{constructor(t){const e=!!t.highDpi;super({attributionsCollapsible:t.attributionsCollapsible,cacheSize:t.cacheSize,crossOrigin:"anonymous",interpolate:t.interpolate,projection:"EPSG:3857",reprojectionErrorThreshold:t.reprojectionErrorThreshold,state:"loading",tileLoadFunction:t.tileLoadFunction,tilePixelRatio:e?2:1,wrapX:void 0===t.wrapX||t.wrapX,transition:t.transition,zDirection:t.zDirection}),this.apiKey_=t.key,this.error_=null;const i={mapType:t.mapType||"roadmap",language:t.language||"en-US",region:t.region||"US"};t.imageFormat&&(i.imageFormat=t.imageFormat),t.scale&&(i.scale=t.scale),e&&(i.highDpi=!0),t.layerTypes&&(i.layerTypes=t.layerTypes),t.styles&&(i.styles=t.styles),!0===t.overlay&&(i.overlay=!0),t.apiOptions&&(i.apiOptions=t.apiOptions),this.sessionTokenRequest_=i,this.sessionTokenValue_,this.sessionRefreshId_,this.previousViewportAttribution_,this.previousViewportExtent_,this.createSession_()}getError(){return this.error_}fetchSessionToken(t,e){return fetch(t,e)}async createSession_(){const t="https://tile.googleapis.com/v1/createSession?key="+this.apiKey_,e={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(this.sessionTokenRequest_)},i=await this.fetchSessionToken(t,e);if(!i.ok){try{const t=await i.json();this.error_=new Error(t.error.message)}catch{this.error_=new Error("Error fetching session token")}return void this.setState("error")}const n=await i.json(),r=this.getTilePixelRatio(1),s=[n.tileWidth/r,n.tileHeight/r];this.tileGrid=zu({extent:$u(this.getProjection()),maxZoom:22,tileSize:s});const o=n.session;this.sessionTokenValue_=o;const a=this.apiKey_;this.tileUrlFunction=function(t,e,i){return`https://tile.googleapis.com/v1/2dtiles/${t[0]}/${t[1]}/${t[2]}?session=${o}&key=${a}`};const l=1e3*parseInt(n.expiry,10),h=Math.max(l-Date.now()-6e4,1);this.sessionRefreshId_=setTimeout((()=>this.createSession_()),h),this.setAttributions(this.fetchAttributions_.bind(this)),this.setState("ready")}async fetchAttributions_(t){if(t.viewHints[ao]||t.viewHints[lo]||t.animate)return this.previousViewportAttribution_;const[e,i]=Rn(le(t.extent),t.viewState.projection),[n,r]=Rn(_e(t.extent),t.viewState.projection),s=`zoom=${this.getTileGrid().getZForResolution(t.viewState.resolution,this.zDirection)}&north=${r}&south=${i}&east=${n}&west=${e}`;if(this.previousViewportExtent_==s)return this.previousViewportAttribution_;this.previousViewportExtent_=s;const o=`https://tile.googleapis.com/tile/v1/viewport?session=${this.sessionTokenValue_}&key=${this.apiKey_}&${s}`;return this.previousViewportAttribution_=await fetch(o).then((t=>t.json())).then((t=>t.copyright)),this.previousViewportAttribution_}disposeInternal(){clearTimeout(this.sessionRefreshId_),super.disposeInternal()}},cP.source.IIIF=class extends pd{constructor(t){const e=t||{};let i=e.url||"";i+=i.lastIndexOf("/")===i.length-1||""===i?"":"/";const n=e.version||xg,r=e.sizes||[],s=e.size;Ft(null!=s&&Array.isArray(s)&&2==s.length&&!isNaN(s[0])&&s[0]>0&&!isNaN(s[1])&&s[1]>0,"Missing or invalid `size`");const o=s[0],a=s[1],l=e.tileSize,h=e.tilePixelRatio||1,c=e.format||"jpg",u=e.quality||(e.version==yg?"native":"default");let d=e.resolutions||[];const g=e.supports||[],f=e.extent||[0,-a,o,0],p=null!=r&&Array.isArray(r)&&r.length>0,m=void 0!==l&&("number"==typeof l&&Number.isInteger(l)&&l>0||Array.isArray(l)&&l.length>0),_=null!=g&&Array.isArray(g)&&(g.includes("regionByPx")||g.includes("regionByPct"))&&(g.includes("sizeByWh")||g.includes("sizeByH")||g.includes("sizeByW")||g.includes("sizeByPct"));let y,x,v;if(d.sort((function(t,e){return e-t})),m||_)if(null!=l&&("number"==typeof l&&Number.isInteger(l)&&l>0?(y=l,x=l):Array.isArray(l)&&l.length>0&&((1==l.length||null==l[1]&&Number.isInteger(l[0]))&&(y=l[0],x=l[0]),2==l.length&&(Number.isInteger(l[0])&&Number.isInteger(l[1])?(y=l[0],x=l[1]):null==l[0]&&Number.isInteger(l[1])&&(y=l[1],x=l[1])))),void 0!==y&&void 0!==x||(y=Eo,x=Eo),0==d.length){v=Math.max(Math.ceil(Math.log(o/y)/Math.LN2),Math.ceil(Math.log(a/x)/Math.LN2));for(let t=v;t>=0;t--)d.push(Math.pow(2,t))}else{const t=Math.max(...d);v=Math.round(Math.log(t)/Math.LN2)}else if(y=o,x=a,d=[],p){r.sort((function(t,e){return t[0]-e[0]})),v=-1;const t=[];for(let e=0;e0&&d[d.length-1]==i?t.push(e):(d.push(i),v++)}if(t.length>0)for(let e=0;ev)return;const S=t[1],E=t[2],w=d[f];if(!(void 0===S||void 0===E||void 0===w||S<0||Math.ceil(o/w/y)<=S||E<0||Math.ceil(a/w/x)<=E)){if(_||m){const t=S*y*w,e=E*x*w;let i=y*w,r=x*w,s=y,c=x;if(t+i>o&&(i=o-t),e+r>a&&(r=a-e),t+y*w>o&&(s=Math.floor((o-t+w-1)/w)),e+x*w>a&&(c=Math.floor((a-e+w-1)/w)),0==t&&i==o&&0==e&&r==a)l="full";else if(!_||g.includes("regionByPx"))l=t+","+e+","+i+","+r;else if(g.includes("regionByPct")){l="pct:"+bg(t/o*100)+","+bg(e/a*100)+","+bg(i/o*100)+","+bg(r/a*100)}n!=vg||_&&!g.includes("sizeByWh")?!_||g.includes("sizeByW")?h=s+",":g.includes("sizeByH")?h=","+c:g.includes("sizeByWh")?h=s+","+c:g.includes("sizeByPct")&&(h="pct:"+bg(100/w)):h=s+","+c}else if(l="full",p){const t=r[f][0],e=r[f][1];h=n==vg?t==o&&e==a?"max":t+","+e:t==o?"full":t+","}else h=n==vg?"max":"full";return i+l+"/"+h+"/0/"+u+"."+c}},transition:e.transition}),this.zDirection=e.zDirection}},cP.source.Image=Ag,cP.source.Image.ImageSourceEvent=Mg,cP.source.Image.defaultImageLoadFunction=Og,cP.source.Image.getRequestExtent=Dg,cP.source.ImageArcGISRest=class extends Ag{constructor(t){super({attributions:(t=t||{}).attributions,interpolate:t.interpolate,projection:t.projection,resolutions:t.resolutions}),this.crossOrigin_=void 0!==t.crossOrigin?t.crossOrigin:null,this.hidpi_=void 0===t.hidpi||t.hidpi,this.url_=t.url,this.imageLoadFunction_=void 0!==t.imageLoadFunction?t.imageLoadFunction:Og,this.params_=Object.assign({},t.params),this.imageSize_=[0,0],this.renderedRevision_=0,this.ratio_=void 0!==t.ratio?t.ratio:1.5,this.loaderProjection_=null}getParams(){return this.params_}getImageInternal(t,e,i,n){return void 0===this.url_?null:(this.loader&&this.loaderProjection_===n||(this.loaderProjection_=n,this.loader=kg({crossOrigin:this.crossOrigin_,params:this.params_,projection:n,hidpi:this.hidpi_,url:this.url_,ratio:this.ratio_,load:(t,e)=>(this.image.setImage(t),this.imageLoadFunction_(this.image,e),Ns(t))})),super.getImageInternal(t,e,i,n))}getImageLoadFunction(){return this.imageLoadFunction_}getUrl(){return this.url_}setImageLoadFunction(t){this.imageLoadFunction_=t,this.changed()}setUrl(t){t!=this.url_&&(this.url_=t,this.loader=null,this.changed())}setParams(t){this.params_=Object.assign({},t),this.changed()}updateParams(t){Object.assign(this.params_,t),this.changed()}changed(){this.image=null,super.changed()}},cP.source.ImageCanvas=class extends Ag{constructor(t){super({attributions:(t=t||{}).attributions,interpolate:t.interpolate,projection:t.projection,resolutions:t.resolutions,state:t.state}),this.canvasFunction_=t.canvasFunction,this.canvas_=null,this.renderedRevision_=0,this.ratio_=void 0!==t.ratio?t.ratio:1.5}getImageInternal(t,e,i,n){e=this.findNearestResolution(e);let r=this.canvas_;if(r&&this.renderedRevision_==this.getRevision()&&r.getResolution()==e&&r.getPixelRatio()==i&&Vt(r.getExtent(),t))return r;Ee(t=t.slice(),this.ratio_);const s=[ye(t)/e*i,fe(t)/e*i],o=this.canvasFunction_.call(this,t,e,i,s,n);return o&&(r=new ks(t,e,i,o)),this.canvas_=r,this.renderedRevision_=this.getRevision(),r}},cP.source.ImageMapGuide=class extends Ag{constructor(t){super({interpolate:t.interpolate,projection:t.projection,resolutions:t.resolutions}),this.crossOrigin_=void 0!==t.crossOrigin?t.crossOrigin:null,this.displayDpi_=void 0!==t.displayDpi?t.displayDpi:96,this.params_=Object.assign({},t.params),this.url_=t.url,this.imageLoadFunction_=void 0!==t.imageLoadFunction?t.imageLoadFunction:Og,this.hidpi_=void 0===t.hidpi||t.hidpi,this.metersPerUnit_=void 0!==t.metersPerUnit?t.metersPerUnit:1,this.ratio_=void 0!==t.ratio?t.ratio:1,this.useOverlay_=void 0!==t.useOverlay&&t.useOverlay,this.renderedRevision_=0,this.loaderProjection_=null}getParams(){return this.params_}getImageInternal(t,e,i,n){return void 0===this.url_?null:(this.loader&&this.loaderProjection_===n||(this.loaderProjection_=n,this.loader=jg({crossOrigin:this.crossOrigin_,params:this.params_,hidpi:this.hidpi_,metersPerUnit:this.metersPerUnit_,url:this.url_,useOverlay:this.useOverlay_,ratio:this.ratio_,load:(t,e)=>(this.image.setImage(t),this.imageLoadFunction_(this.image,e),Ns(t))})),super.getImageInternal(t,e,i,n))}getImageLoadFunction(){return this.imageLoadFunction_}setParams(t){this.params_=Object.assign({},t),this.changed()}updateParams(t){Object.assign(this.params_,t),this.changed()}setImageLoadFunction(t){this.imageLoadFunction_=t,this.changed()}changed(){this.image=null,super.changed()}},cP.source.ImageStatic=class extends Ag{constructor(t){const e=void 0!==t.crossOrigin?t.crossOrigin:null,i=void 0!==t.imageLoadFunction?t.imageLoadFunction:Og;super({attributions:t.attributions,interpolate:t.interpolate,projection:xn(t.projection)}),this.url_=t.url,this.imageExtent_=t.imageExtent,this.image=null,this.image=new Ms(this.imageExtent_,void 0,1,Ug({url:t.url,imageExtent:t.imageExtent,crossOrigin:e,load:(t,e)=>(this.image.setImage(t),i(this.image,e),Ns(t))})),this.image.addEventListener(n,this.handleImageChange.bind(this))}getImageExtent(){return this.imageExtent_}getImageInternal(t,e,i,n){return xe(t,this.image.getExtent())?this.image:null}getUrl(){return this.url_}},cP.source.ImageTile=Zg,cP.source.ImageWMS=class extends Ag{constructor(t){super({attributions:(t=t||{}).attributions,interpolate:t.interpolate,projection:t.projection,resolutions:t.resolutions}),this.crossOrigin_=void 0!==t.crossOrigin?t.crossOrigin:null,this.url_=t.url,this.imageLoadFunction_=void 0!==t.imageLoadFunction?t.imageLoadFunction:Og,this.params_=Object.assign({},t.params),this.serverType_=t.serverType,this.hidpi_=void 0===t.hidpi||t.hidpi,this.renderedRevision_=0,this.ratio_=void 0!==t.ratio?t.ratio:1.5,this.loaderProjection_=null}getFeatureInfoUrl(t,e,i,n){const r=xn(i),s=this.getProjection();s&&s!==r&&(e=Tu(s,r,t,e),t=Ln(t,r,s));return tf({url:this.url_,params:{...this.params_,...n},projection:s||r},t,e)}getLegendUrl(t,e){return ef({url:this.url_,params:{...this.params_,...e}},t)}getParams(){return this.params_}getImageInternal(t,e,i,n){return void 0===this.url_?null:(this.loader&&this.loaderProjection_===n||(this.loaderProjection_=n,this.loader=Qg({crossOrigin:this.crossOrigin_,params:this.params_,projection:n,serverType:this.serverType_,hidpi:this.hidpi_,url:this.url_,ratio:this.ratio_,load:(t,e)=>(this.image.setImage(t),this.imageLoadFunction_(this.image,e),Ns(t))})),super.getImageInternal(t,e,i,n))}getImageLoadFunction(){return this.imageLoadFunction_}getUrl(){return this.url_}setImageLoadFunction(t){this.imageLoadFunction_=t,this.changed()}setUrl(t){t!=this.url_&&(this.url_=t,this.loader=null,this.changed())}setParams(t){this.params_=Object.assign({},t),this.changed()}updateParams(t){Object.assign(this.params_,t),this.changed()}changed(){this.image=null,super.changed()}},cP.source.OGCMapTile=class extends pd{constructor(t){super({attributions:t.attributions,cacheSize:t.cacheSize,crossOrigin:t.crossOrigin,interpolate:t.interpolate,projection:t.projection,reprojectionErrorThreshold:t.reprojectionErrorThreshold,state:"loading",tileLoadFunction:t.tileLoadFunction,wrapX:void 0===t.wrapX||t.wrapX,transition:t.transition});hf({url:t.url,projection:this.getProjection(),mediaType:t.mediaType,context:t.context||null,collections:t.collections}).then(this.handleTileSetInfo_.bind(this)).catch(this.handleError_.bind(this))}handleTileSetInfo_(t){this.tileGrid=t.grid,this.projection=t.projection,this.setTileUrlFunction(t.urlFunction,t.urlTemplate),this.setState("ready")}handleError_(t){Ye(t),this.setState("error")}},cP.source.OGCVectorTile=class extends cf{constructor(t){super({attributions:t.attributions,attributionsCollapsible:t.attributionsCollapsible,cacheSize:t.cacheSize,format:t.format,overlaps:t.overlaps,projection:t.projection,tileClass:t.tileClass,transition:t.transition,wrapX:t.wrapX,zDirection:t.zDirection,state:"loading"});hf({url:t.url,projection:this.getProjection(),mediaType:t.mediaType,supportedMediaTypes:t.format.supportedMediaTypes,context:t.context||null,collections:t.collections}).then(this.handleTileSetInfo_.bind(this)).catch(this.handleError_.bind(this))}handleTileSetInfo_(t){this.tileGrid=t.grid,this.projection=t.projection,this.setTileUrlFunction(t.urlFunction,t.urlTemplate),this.setState("ready")}handleError_(t){Ye(t),this.setState("error")}},cP.source.OSM=class extends yd{constructor(t){let e;e=void 0!==(t=t||{}).attributions?t.attributions:[df];const i=void 0!==t.crossOrigin?t.crossOrigin:"anonymous",n=void 0!==t.url?t.url:"https://tile.openstreetmap.org/{z}/{x}/{y}.png";super({attributions:e,attributionsCollapsible:!1,cacheSize:t.cacheSize,crossOrigin:i,interpolate:t.interpolate,maxZoom:void 0!==t.maxZoom?t.maxZoom:19,reprojectionErrorThreshold:t.reprojectionErrorThreshold,tileLoadFunction:t.tileLoadFunction,transition:t.transition,url:n,wrapX:t.wrapX,zDirection:t.zDirection})}},cP.source.OSM.ATTRIBUTION=df,cP.source.Raster=Nf,cP.source.Raster.Processor=Mf,cP.source.Raster.RasterSourceEvent=Df,cP.source.SentinelHub=class extends rg{constructor(t){const e=t||{};super({state:"loading",projection:e.projection,attributionsCollapsible:e.attributionsCollapsible,interpolate:e.interpolate,tileSize:e.tileSize||qp,wrapX:void 0===e.wrapX||e.wrapX,transition:e.transition}),this.setLoader(((t,e,i)=>this.loadTile_(t,e,i,1))),this.error_=null,this.evalscript_="",this.inputData_=null,this.processUrl_=e.url||"https://services.sentinel-hub.com/api/v1/process",this.token_="",this.tokenRenewalId_,e.auth&&this.setAuth(e.auth),e.data&&this.setData(e.data),e.evalscript&&this.setEvalscript(e.evalscript)}async setAuth(t){if(clearTimeout(this.tokenRenewalId_),"string"==typeof t)return this.token_=t,void this.fireWhenReady_();let e,i;try{e=await async function(t){const e=t.tokenUrl||"https://services.sentinel-hub.com/auth/realms/main/protocol/openid-connect/token",i=new URLSearchParams;i.append("grant_type","client_credentials"),i.append("client_id",t.clientId),i.append("client_secret",t.clientSecret);const n={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i},r=await fetch(e,n);if(!r.ok){if(401===r.status)throw new Error("Bad client id or secret");throw new Error("Failed to get token")}return(await r.json()).access_token}(t),i=Jp(e)}catch(t){return this.error_=t,void this.setState("error")}this.token_=e;const n=1e3*i.exp,r=Math.max(n-Date.now()-6e4,1);this.tokenRenewalId_=setTimeout((()=>this.setAuth(t)),r),this.fireWhenReady_()}setData(t){this.inputData_=t,this.fireWhenReady_()}setEvalscript(t){let e;if("string"==typeof t)e=t;else try{e=function(t){return`//VERSION=${t.version||"3"}\n ${tm("setup",t.setup)}\n ${tm("evaluatePixel",t.evaluatePixel)}\n ${tm("updateOutput",t.updateOutput)}\n `}(t)}catch(t){return this.error_=t,void this.setState("error")}this.evalscript_=e,this.fireWhenReady_()}fireWhenReady_(){if(!this.token_||!this.evalscript_||!this.inputData_)return;"ready"!==this.getState()?this.setState("ready"):this.changed()}async loadTile_(t,e,i,n){const r=this.getTileGrid().getTileCoordExtent([t,e,i]),s=this.getTileSize(t),o={input:{bounds:{bbox:r,properties:{crs:Qp(this.getProjection())}},data:this.inputData_},output:{width:s[0],height:s[1]},evalscript:this.evalscript_},a={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.token_}`,"Access-Control-Request-Headers":"Retry-After"},body:JSON.stringify(o),credentials:"include"},l=await fetch(this.processUrl_,a);if(!l.ok){if(429===l.status&&n<9){const r=500*2**n;return await(h=r,new Promise((t=>setTimeout(t,h)))),this.loadTile_(e,i,t,n+1)}throw new Error(`Failed to get tile: ${l.statusText}`)}var h;return async function(t){const e=await t.blob();return new Promise(((t,i)=>{const n=new Image,r=URL.createObjectURL(e);n.onload=()=>{URL.revokeObjectURL(r),t(n)},n.onerror=()=>{URL.revokeObjectURL(r),i(new Error("Failed to load image"))},n.src=r}))}(l)}getError(){return this.error_}disposeInternal(){clearTimeout(this.tokenRenewalId_),super.disposeInternal()}},cP.source.SentinelHub.getProjectionIdentifier=Qp,cP.source.SentinelHub.parseTokenClaims=Jp,cP.source.SentinelHub.serializeFunction=tm,cP.source.Source=ad,cP.source.StadiaMaps=class extends yd{constructor(t){const e=t.layer.indexOf("-"),i=-1==e?t.layer:t.layer.slice(0,e),n=Bf[i]||{minZoom:0,maxZoom:20,retina:!0},r=Uf[t.layer],s=t.apiKey?"?api_key="+t.apiKey:"",o=n.retina&&t.retina?"@2x":"",a=void 0!==t.url?t.url:"https://tiles.stadiamaps.com/tiles/"+t.layer+"/{z}/{x}/{y}"+o+"."+r.extension+s,l=['© Stadia Maps','© OpenMapTiles',df];t.layer.startsWith("stamen_")&&l.splice(1,0,'© Stamen Design'),super({attributions:l,cacheSize:t.cacheSize,crossOrigin:"anonymous",interpolate:t.interpolate,maxZoom:void 0!==t.maxZoom?t.maxZoom:n.maxZoom,minZoom:void 0!==t.minZoom?t.minZoom:n.minZoom,reprojectionErrorThreshold:t.reprojectionErrorThreshold,tileLoadFunction:t.tileLoadFunction,transition:t.transition,url:a,tilePixelRatio:o?2:1,wrapX:t.wrapX,zDirection:t.zDirection})}},cP.source.Tile=hd,cP.source.Tile.TileSourceEvent=cd,cP.source.TileArcGISRest=class extends pd{constructor(t){super({attributions:(t=t||{}).attributions,cacheSize:t.cacheSize,crossOrigin:t.crossOrigin,interpolate:t.interpolate,projection:t.projection,reprojectionErrorThreshold:t.reprojectionErrorThreshold,tileGrid:t.tileGrid,tileLoadFunction:t.tileLoadFunction,url:t.url,urls:t.urls,wrapX:void 0===t.wrapX||t.wrapX,transition:t.transition,zDirection:t.zDirection}),this.params_=Object.assign({},t.params),this.hidpi_=void 0===t.hidpi||t.hidpi,this.tmpExtent_=[1/0,1/0,-1/0,-1/0],this.setKey(this.getKeyForParams_())}getKeyForParams_(){let t=0;const e=[];for(const i in this.params_)e[t++]=i+"-"+this.params_[i];return e.join("/")}getParams(){return this.params_}getRequestUrl_(t,e,i,n,r,s){const o=this.urls;if(!o)return;let a;if(1==o.length)a=o[0];else{a=o[Ae(Mu(t),o.length)]}return Ng(a,i,(this.tileGrid||this.getTileGridForProjection(r)).getResolution(t[0]),n,r,s)}getTilePixelRatio(t){return this.hidpi_?t:1}setParams(t){this.params_=Object.assign({},t),this.setKey(this.getKeyForParams_())}updateParams(t){Object.assign(this.params_,t),this.setKey(this.getKeyForParams_())}tileUrlFunction(t,e,i){let n=this.getTileGrid();if(n||(n=this.getTileGridForProjection(i)),n.getResolutions().length<=t[0])return;1==e||this.hidpi_||(e=1);const r=n.getTileCoordExtent(t,this.tmpExtent_);let s=Bl(n.getTileSize(t[0]),this.tmpSize);1!=e&&(s=Ul(s,e,this.tmpSize));const o={F:"image",FORMAT:"PNG32",TRANSPARENT:!0};return Object.assign(o,this.params_),this.getRequestUrl_(t,s,r,e,i,o)}},cP.source.TileDebug=class extends Zg{constructor(t){const e=(t=t||{}).template||"z:{z} x:{x} y:{y}",i=t.source,r=t.color||"grey";super({transition:0,wrapX:void 0!==t.wrapX?t.wrapX:void 0!==i?i.getWrapX():void 0});const s=()=>{this.projection=void 0!==t.projection?xn(t.projection):void 0!==i?i.getProjection():this.projection,this.tileGrid=void 0!==t.tileGrid?t.tileGrid:void 0!==i?i.getTileGrid():this.tileGrid,this.zDirection=void 0!==t.zDirection?t.zDirection:void 0!==i?i.zDirection:this.zDirection,i instanceof rg&&(this.transformMatrix=i.transformMatrix?.slice()||null);const n=this.tileGrid;n&&this.setTileSizes(n.getResolutions().map(((t,e)=>Bl(n.getTileSize(e)).map((t=>Math.max(Math.floor(t),1)))))),this.setLoader(((t,i,n,s)=>{const o=qu(e,t,i,n,s.maxY),[a,l]=this.getTileSize(t),h=ft(a,l);return h.strokeStyle=r,h.strokeRect(.5,.5,a+.5,l+.5),h.fillStyle=r,h.strokeStyle="white",h.textAlign="center",h.textBaseline="middle",h.font="24px sans-serif",h.lineWidth=4,h.strokeText(o,a/2,l/2,a),h.fillText(o,a/2,l/2,a),Promise.resolve(h.canvas)})),this.setState("ready")};if(void 0===i||"ready"===i.getState())s();else{const t=()=>{"ready"===i.getState()&&(i.removeEventListener(n,t),s())};i.addEventListener(n,t)}}},cP.source.TileImage=pd,cP.source.TileJSON=class extends pd{constructor(t){if(super({attributions:t.attributions,cacheSize:t.cacheSize,crossOrigin:t.crossOrigin,interpolate:t.interpolate,projection:xn("EPSG:3857"),reprojectionErrorThreshold:t.reprojectionErrorThreshold,state:"loading",tileLoadFunction:t.tileLoadFunction,wrapX:void 0===t.wrapX||t.wrapX,transition:t.transition,zDirection:t.zDirection}),this.tileJSON_=null,this.tileSize_=t.tileSize,t.url)if(t.jsonp)ou(t.url,this.handleTileJSONResponse.bind(this),this.handleTileJSONError.bind(this));else{const e=new XMLHttpRequest;e.addEventListener("load",this.onXHRLoad_.bind(this)),e.addEventListener("error",this.onXHRError_.bind(this)),e.open("GET",t.url),e.send()}else{if(!t.tileJSON)throw new Error("Either `url` or `tileJSON` options must be provided");this.handleTileJSONResponse(t.tileJSON)}}onXHRLoad_(t){const e=t.target;if(!e.status||e.status>=200&&e.status<300){let t;try{t=JSON.parse(e.responseText)}catch{return void this.handleTileJSONError()}this.handleTileJSONResponse(t)}else this.handleTileJSONError()}onXHRError_(t){this.handleTileJSONError()}getTileJSON(){return this.tileJSON_}handleTileJSONResponse(t){const e=xn("EPSG:4326"),i=this.getProjection();let n;if(void 0!==t.bounds){const r=Pn(e,i);n=Te(t.bounds,r)}const r=$u(i),s=t.minzoom||0,o=zu({extent:r,maxZoom:t.maxzoom||22,minZoom:s,tileSize:this.tileSize_});if(this.tileGrid=o,this.tileUrlFunction=ed(t.tiles,o),t.attribution&&!this.getAttributions()){const e=void 0!==n?n:r;this.setAttributions((function(i){return xe(e,i.extent)?[t.attribution]:null}))}this.tileJSON_=t,this.setState("ready")}handleTileJSONError(){this.setState("error")}},cP.source.TileWMS=class extends pd{constructor(t){t=t||{};const e=Object.assign({},t.params);super({attributions:t.attributions,attributionsCollapsible:t.attributionsCollapsible,cacheSize:t.cacheSize,crossOrigin:t.crossOrigin,interpolate:t.interpolate,projection:t.projection,reprojectionErrorThreshold:t.reprojectionErrorThreshold,tileClass:t.tileClass,tileGrid:t.tileGrid,tileLoadFunction:t.tileLoadFunction,url:t.url,urls:t.urls,wrapX:void 0===t.wrapX||t.wrapX,transition:t.transition,zDirection:t.zDirection}),this.gutter_=void 0!==t.gutter?t.gutter:0,this.params_=e,this.v13_=!0,this.serverType_=t.serverType,this.hidpi_=void 0===t.hidpi||t.hidpi,this.tmpExtent_=[1/0,1/0,-1/0,-1/0],this.updateV13_(),this.setKey(this.getKeyForParams_())}getFeatureInfoUrl(t,e,i,n){const r=xn(i),s=this.getProjection()||r;let o=this.getTileGrid();o||(o=this.getTileGridForProjection(s));const a=Ln(t,r,s),l=Tu(s,r,t,e),h=o.getZForResolution(l,this.zDirection),c=o.getResolution(h),u=o.getTileCoordForCoordAndZ(a,h);if(o.getResolutions().length<=u[0])return;let d=o.getTileCoordExtent(u,this.tmpExtent_);const g=this.gutter_;0!==g&&(d=Ut(d,c*g,d));const f={QUERY_LAYERS:this.params_.LAYERS};Object.assign(f,Jg(this.params_,"GetFeatureInfo"),n);const p=Math.floor((a[0]-d[0])/c),m=Math.floor((d[3]-a[1])/c);return f[this.v13_?"I":"X"]=p,f[this.v13_?"J":"Y"]=m,this.getRequestUrl_(u,d,1,s||r,f)}getLegendUrl(t,e){if(void 0===this.urls[0])return;const i={SERVICE:"WMS",VERSION:Yg,REQUEST:"GetLegendGraphic",FORMAT:"image/png"};if(void 0===e||void 0===e.LAYER){const t=this.params_.LAYERS;if(!(!Array.isArray(t)||1===t.length))return;i.LAYER=t}if(void 0!==t){const e=this.getProjection()?this.getProjection().getMetersPerUnit():1,n=28e-5;i.SCALE=t*e/n}return Object.assign(i,e),Wu(this.urls[0],i)}getGutter(){return this.gutter_}getParams(){return this.params_}getRequestUrl_(t,e,i,n,r){const s=this.urls;if(!s)return;let o;if(1==s.length)o=s[0];else{o=s[Ae(Mu(t),s.length)]}return qg(e,(this.tileGrid||this.getTileGridForProjection(n)).getResolution(t[0]),i,n,o,r,this.serverType_)}getTilePixelRatio(t){return this.hidpi_&&void 0!==this.serverType_?t:1}getKeyForParams_(){let t=0;const e=[];for(const i in this.params_)e[t++]=i+"-"+this.params_[i];return e.join("/")}setParams_(t){this.params_=t,this.updateV13_(),this.setKey(this.getKeyForParams_())}setParams(t){this.setParams_(Object.assign({},t))}updateParams(t){this.setParams_(Object.assign(this.params_,t))}updateV13_(){const t=this.params_.VERSION||Yg;this.v13_=Ke(t,"1.3")>=0}tileUrlFunction(t,e,i){let n=this.getTileGrid();if(n||(n=this.getTileGridForProjection(i)),n.getResolutions().length<=t[0])return;1==e||this.hidpi_&&void 0!==this.serverType_||(e=1);const r=n.getResolution(t[0]);let s=n.getTileCoordExtent(t,this.tmpExtent_);const o=this.gutter_;0!==o&&(s=Ut(s,r*o,s));const a=Object.assign({},Jg(this.params_,"GetMap"));return this.getRequestUrl_(t,s,e,i,a)}},cP.source.UTFGrid=class extends hd{constructor(t){if(super({projection:xn("EPSG:3857"),state:"loading",wrapX:void 0===t.wrapX||t.wrapX,zDirection:t.zDirection}),this.preemptive_=void 0===t.preemptive||t.preemptive,this.tileUrlFunction_=nd,this.template_=void 0,this.jsonp_=t.jsonp||!1,this.tileCache_=new Pu(512),t.url)if(this.jsonp_)ou(t.url,this.handleTileJSONResponse.bind(this),this.handleTileJSONError.bind(this));else{const e=new XMLHttpRequest;e.addEventListener("load",this.onXHRLoad_.bind(this)),e.addEventListener("error",this.onXHRError_.bind(this)),e.open("GET",t.url),e.send()}else{if(!t.tileJSON)throw new Error("Either `url` or `tileJSON` options must be provided");this.handleTileJSONResponse(t.tileJSON)}}onXHRLoad_(t){const e=t.target;if(!e.status||e.status>=200&&e.status<300){let t;try{t=JSON.parse(e.responseText)}catch{return void this.handleTileJSONError()}this.handleTileJSONResponse(t)}else this.handleTileJSONError()}onXHRError_(t){this.handleTileJSONError()}getTemplate(){return this.template_}forDataAtCoordinateAndResolution(t,e,i,n){if(this.tileGrid){const r=this.tileGrid.getZForResolution(e,this.zDirection),s=this.tileGrid.getTileCoordForCoordAndZ(t,r),o=this.getTile(s[0],s[1],s[2],1,this.getProjection());o.getState()==Y&&o.load(),o.forDataAtCoordinate(t,i,n)}else!0===n?setTimeout((function(){i(null)}),0):i(null)}handleTileJSONError(){this.setState("error")}handleTileJSONResponse(t){const e=xn("EPSG:4326"),i=this.getProjection();let n;if(void 0!==t.bounds){const r=Pn(e,i);n=Te(t.bounds,r)}const r=$u(i),s=t.minzoom||0,o=zu({extent:r,maxZoom:t.maxzoom||22,minZoom:s});this.tileGrid=o,this.template_=t.template;const a=t.grids;if(a){if(this.tileUrlFunction_=ed(a,o),t.attribution){const e=void 0!==n?n:r;this.setAttributions((function(i){return xe(e,i.extent)?[t.attribution]:null}))}this.setState("ready")}else this.setState("error")}getTile(t,e,i,n,r){const s=[t,e,i],o=this.getTileCoordForTileUrlFunction(s,r),a=this.tileUrlFunction_(o,n,r),l=`${this.getKey()},${Fu(t,e,i)}`;if(this.tileCache_.containsKey(l))return this.tileCache_.get(l);this.tileCache_.expireCache();const h=new zf(s,void 0!==a?Y:J,void 0!==a?a:"",this.tileGrid.getTileCoordExtent(s),this.preemptive_,this.jsonp_);return this.tileCache_.set(l,h),h}},cP.source.UTFGrid.CustomTile=zf,cP.source.UrlTile=fd,cP.source.Vector=Xd,cP.source.Vector.VectorSourceEvent=zd,cP.source.VectorTile=cf,cP.source.VectorTile.defaultLoadFunction=uf,cP.source.WMTS=class extends pd{constructor(t){const e=void 0!==t.requestEncoding?t.requestEncoding:"KVP",i=t.tileGrid;let n=t.urls;void 0===n&&void 0!==t.url&&(n=Qu(t.url)),super({attributions:t.attributions,attributionsCollapsible:t.attributionsCollapsible,cacheSize:t.cacheSize,crossOrigin:t.crossOrigin,interpolate:t.interpolate,projection:t.projection,reprojectionErrorThreshold:t.reprojectionErrorThreshold,tileClass:t.tileClass,tileGrid:i,tileLoadFunction:t.tileLoadFunction,tilePixelRatio:t.tilePixelRatio,urls:n,wrapX:void 0!==t.wrapX&&t.wrapX,transition:t.transition,zDirection:t.zDirection}),this.version_=void 0!==t.version?t.version:"1.0.0",this.format_=void 0!==t.format?t.format:"image/jpeg",this.dimensions_=void 0!==t.dimensions?t.dimensions:{},this.layer_=t.layer,this.matrixSet_=t.matrixSet,this.style_=t.style,this.requestEncoding_=e,this.setKey(this.getKeyForDimensions_()),n&&n.length>0&&(this.tileUrlFunction=id(n.map(this.createFromWMTSTemplate.bind(this))))}setUrls(t){this.urls=t;const e=t.join("\n");this.setTileUrlFunction(id(t.map(this.createFromWMTSTemplate.bind(this))),e)}getDimensions(){return this.dimensions_}getFormat(){return this.format_}getLayer(){return this.layer_}getMatrixSet(){return this.matrixSet_}getRequestEncoding(){return this.requestEncoding_}getStyle(){return this.style_}getVersion(){return this.version_}getKeyForDimensions_(){const t=this.urls?this.urls.slice(0):[];for(const e in this.dimensions_)t.push(e+"-"+this.dimensions_[e]);return t.join("/")}updateDimensions(t){Object.assign(this.dimensions_,t),this.setKey(this.getKeyForDimensions_())}createFromWMTSTemplate(t){const e=this.requestEncoding_,i={layer:this.layer_,style:this.style_,tilematrixset:this.matrixSet_};"KVP"==e&&Object.assign(i,{Service:"WMTS",Request:"GetTile",Version:this.version_,Format:this.format_}),t="KVP"==e?Wu(t,i):t.replace(/\{(\w+?)\}/g,(function(t,e){return e.toLowerCase()in i?i[e.toLowerCase()]:t}));const n=this.tileGrid,r=this.dimensions_;return function(i,s,o){if(!i)return;const a={TileMatrix:n.getMatrixId(i[0]),TileCol:i[1],TileRow:i[2]};Object.assign(a,r);let l=t;return l="KVP"==e?Wu(l,a):l.replace(/\{(\w+?)\}/g,(function(t,e){return encodeURIComponent(a[e])})),l}}},cP.source.WMTS.optionsFromCapabilities=function(t,e){const i=t.Contents.Layer,n=i?.find((function(t){return t.Identifier==e.layer}));if(!n)return null;const r=t.Contents.TileMatrixSet;let s;s=n.TileMatrixSetLink.length>1?"projection"in e?n.TileMatrixSetLink.findIndex((function(t){const i=r.find((function(e){return e.Identifier==t.TileMatrixSet})).SupportedCRS,n=xn(i),s=xn(e.projection);return n&&s?bn(n,s):i==e.projection})):n.TileMatrixSetLink.findIndex((function(t){return t.TileMatrixSet==e.matrixSet})):0,s<0&&(s=0);const o=n.TileMatrixSetLink[s].TileMatrixSet,a=n.TileMatrixSetLink[s].TileMatrixSetLimits;let l=n.Format[0];"format"in e&&(l=e.format),s=n.Style.findIndex((function(t){return"style"in e?t.Title==e.style:t.isDefault})),s<0&&(s=0);const h=n.Style[s].Identifier,c={};"Dimension"in n&&n.Dimension.forEach((function(t,e,i){const n=t.Identifier;let r=t.Default;void 0===r&&(r=t.Value[0]),c[n]=r}));const u=t.Contents.TileMatrixSet.find((function(t){return t.Identifier==o}));let d;const g=u.SupportedCRS;if(g&&(d=xn(g)),"projection"in e){const t=xn(e.projection);t&&(d&&!bn(t,d)||(d=t))}let f=!1;const p=d.getAxisOrientation().startsWith("ne");let m=u.TileMatrix[0],_={MinTileCol:0,MinTileRow:0,MaxTileCol:m.MatrixWidth-1,MaxTileRow:m.MatrixHeight-1};if(a){_=a[a.length-1];const t=u.TileMatrix.find((t=>t.Identifier===_.TileMatrix||u.Identifier+":"+t.Identifier===_.TileMatrix));t&&(m=t)}const y=28e-5*m.ScaleDenominator/d.getMetersPerUnit(),x=p?[m.TopLeftCorner[1],m.TopLeftCorner[0]]:m.TopLeftCorner,v=m.TileWidth*y,S=m.TileHeight*y;let E=u.BoundingBox;E&&p&&(E=[E[1],E[0],E[3],E[2]]);let w=[x[0]+v*_.MinTileCol,x[1]-S*(1+_.MaxTileRow),x[0]+v*(1+_.MaxTileCol),x[1]-S*_.MinTileRow];if(void 0!==E&&!Vt(E,w)){const t=n.WGS84BoundingBox,e=xn("EPSG:4326").getExtent();if(w=E,t)f=t[0]===e[0]&&t[2]===e[2];else{const t=Mn(E,u.SupportedCRS,"EPSG:4326");f=t[0]-1e-10<=e[0]&&t[2]+1e-10>=e[2]}}const T=Gu(u,w,a),C=[];let R=e.requestEncoding;if(R=void 0!==R?R:"","OperationsMetadata"in t&&"GetTile"in t.OperationsMetadata){const e=t.OperationsMetadata.GetTile.DCP.HTTP.Get;for(let t=0,i=e.length;tl||s>l;)o.push([Math.ceil(r/l),Math.ceil(s/l)]),l+=l;break;case"truncated":let t=r,e=s;for(;t>l||e>l;)o.push([Math.ceil(t/l),Math.ceil(e/l)]),t>>=1,e>>=1;break;default:throw new Error("Unknown `tierSizeCalculation` configured")}o.push([1,1]),o.reverse();const h=[n],c=[0];for(let t=1,e=o.length;t{f=a,this.changed()})),y.src=_}},cP.source.Zoomify.CustomTile=Rg,cP.source.arcgisRest={},cP.source.arcgisRest.createLoader=kg,cP.source.arcgisRest.getRequestUrl=Ng,cP.source.common={},cP.source.common.DECIMALS=4,cP.source.common.DEFAULT_WMS_VERSION="1.3.0",cP.source.mapguide={},cP.source.mapguide.createLoader=jg,cP.source.mapserver={},cP.source.mapserver.createLoader=function(t){const e=t.load||Ns,i=t.ratio??1,n=t.crossOrigin??null;return function(r,s,o){const a=new Image;a.crossOrigin=n;const l=ye(r=Dg(r,s,o,i))/s,h=fe(r)/s,c=[l*o,h*o],u=function(t,e,i,n){const r=Math.round(n[0]),s=Math.round(n[1]),o=`${r} ${s}`,a=`${i[0]} ${i[1]} ${i[2]} ${i[3]}`,l={mode:"map",map_imagetype:"png",mapext:a,imgext:a,map_size:o,imgx:r/2,imgy:s/2,imgxy:o};return Object.assign(l,e),Wu(t,l)}(t.url,t.params,r,c);return e(a,u).then((t=>({image:t,extent:r,pixelRatio:o})))}},cP.source.ogcTileUtil={},cP.source.ogcTileUtil.appendCollectionsQueryParam=sf,cP.source.ogcTileUtil.getMapTileUrlTemplate=of,cP.source.ogcTileUtil.getTileSetInfo=hf,cP.source.ogcTileUtil.getVectorTileUrlTemplate=af,cP.source.sourcesFromTileGrid=function(t,e){const i=new Pu(32),n=t.getExtent();return function(r,s){i.expireCache(),n&&(r=pe(n,r));const o=t.getZForResolution(s),a=[];return t.forEachTileCoord(r,o,(t=>{const n=t.toString();if(!i.containsKey(n)){const r=e(t);i.set(n,r)}a.push(i.get(n))})),a}},cP.source.static={},cP.source.static.createLoader=Ug,cP.source.wms={},cP.source.wms.DEFAULT_VERSION=Yg,cP.source.wms.createLoader=Qg,cP.source.wms.getFeatureInfoUrl=tf,cP.source.wms.getImageSrc=qg,cP.source.wms.getLegendUrl=ef,cP.source.wms.getRequestParams=Jg,cP.source.wms.getRequestUrl=Kg,cP.sphere={},cP.sphere.DEFAULT_RADIUS=Ue,cP.sphere.getArea=function t(e,i){const n=(i=i||{}).radius||Ue,r=i.projection||"EPSG:3857",s=e.getType();"GeometryCollection"!==s&&(e=e.clone().transform(r,"EPSG:4326"));let o,a,l,h,c,u,d=0;switch(s){case"Point":case"MultiPoint":case"LineString":case"MultiLineString":case"LinearRing":break;case"Polygon":for(o=e.getCoordinates(),d=Math.abs(Xe(o[0],n)),l=1,h=o.length;l void { + const toMap = (point: LonLat) => ol.proj.fromLonLat(point); + const features: OlFeature[] = []; + const addFeature = (geometry: unknown, label: string, description: string, style: unknown) => { + const feature = new ol.Feature({ geometry, label, description }); + feature.setStyle(style); + features.push(feature); + }; + + // Areas first and markers last, so a marker is never hidden under a shaded area. + for (const area of map.areas) { + addFeature(new ol.geom.Polygon([area.coordinates.map(toMap)]), area.label, area.description, + new ol.style.Style({ + stroke: new ol.style.Stroke({ color: area.strokeColor, width: 2 }), + fill: new ol.style.Fill({ color: area.fillColor }), + })); + } + for (const path of map.paths) { + addFeature(new ol.geom.LineString(path.coordinates.map(toMap)), path.label, path.description, + new ol.style.Style({ stroke: new ol.style.Stroke({ color: path.color, width: path.width }) })); + } + for (const marker of map.markers) { + addFeature(new ol.geom.Point(toMap(marker.lonLat)), marker.label, marker.description, + new ol.style.Style({ + image: new ol.style.Circle({ + radius: 7, + fill: new ol.style.Fill({ color: marker.color }), + stroke: new ol.style.Stroke({ color: '#ffffff', width: 2 }), + }), + })); + } + + const source = new ol.source.Vector(); + source.addFeatures(features); + const popup = createPopup(); + const overlay = new ol.Overlay({ + element: popup.element, + positioning: 'bottom-center', + stopEvent: false, + offset: [0, -12], + }); + const view = new ol.View({ + center: toMap(map.view.center), + zoom: map.view.zoom, + maxZoom: map.view.maxZoom, + }); + const olMap = new ol.Map({ + target, + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: apiUrl(map.tileUrlTemplate), + crossOrigin: TILE_CROSS_ORIGIN, + maxZoom: map.view.maxZoom, + }), + }), + new ol.layer.Vector({ source }), + ], + overlays: [overlay], + view, + controls: ol.control.defaults.defaults({ attribution: false }), + interactions: ol.interaction.defaults.defaults({ mouseWheelZoom: false }), + }); + olMap.addInteraction(new ol.interaction.MouseWheelZoom({ + condition: ol.events.condition.platformModifierKeyOnly, + })); + // OpenLayers labels the button with a glyph and a tooltip only, so it is named explicitly. + const fullScreen = new ol.control.FullScreen({ tipLabel: 'Full screen' }); + fullScreen.element.querySelector('button')?.setAttribute('aria-label', 'Full screen'); + olMap.addControl(fullScreen); + + // Hovering a marker, path or area shows its details; clicking keeps them open until the + // reader clicks somewhere else on the map or presses Escape. + let pinned = false; + const hide = () => { + popup.hide(); + overlay.setPosition(undefined); + }; + const showDetails = (feature: OlFeature, coordinate: number[]) => { + const properties = feature.getProperties(); + popup.show(properties.label, properties.description); + overlay.setPosition(coordinate); + }; + olMap.on('pointermove', (event) => { + if (event.dragging) { + return; + } + const feature = olMap.forEachFeatureAtPixel(event.pixel, (found) => found); + target.style.cursor = feature ? 'pointer' : ''; + if (pinned) { + return; + } + if (feature) { + showDetails(feature, event.coordinate); + } else { + hide(); + } + }); + olMap.on('click', (event) => { + const feature = olMap.forEachFeatureAtPixel(event.pixel, (found) => found); + pinned = Boolean(feature); + if (feature) { + showDetails(feature, event.coordinate); + } else { + hide(); + } + }); + const onPointerLeave = () => { + if (!pinned) { + hide(); + } + }; + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + pinned = false; + hide(); + } + }; + target.addEventListener('pointerleave', onPointerLeave); + target.addEventListener('keydown', onKeyDown); + + // Fit once the card has its final size, so every marker and path starts in view. + const frame = requestAnimationFrame(() => { + olMap.updateSize(); + const extent = source.getExtent(); + if (map.view.fitToFeatures && features.length > 0 && !ol.extent.isEmpty(extent)) { + view.fit(extent, { padding: [40, 40, 40, 40], maxZoom: map.view.maxZoom, duration: 0 }); + } + }); + + return () => { + cancelAnimationFrame(frame); + target.removeEventListener('pointerleave', onPointerLeave); + target.removeEventListener('keydown', onKeyDown); + olMap.setTarget(undefined); + olMap.dispose(); + }; +} + +/** A text list of everything on the map, for keyboard and screen reader users. */ +function MapContentsList({ map }: { map: InlineMap }) { + const items = [ + ...map.markers.map((marker) => ({ icon: MapPin, label: marker.label, description: marker.description })), + ...map.paths.map((path) => ({ icon: Route, label: path.label, description: path.description })), + ...map.areas.map((area) => ({ icon: Square, label: area.label, description: area.description })), + ]; + + return ( +
+ List what the map shows +
    + {items.map((item, index) => { + const Icon = item.icon; + return ( +
  1. +
  2. + ); + })} +
+
+ ); +} + +export function InlineMapCard({ map }: { map: InlineMap }) { + const targetRef = useRef(null); + const [status, setStatus] = useState<'loading' | 'ready' | 'failed'>('loading'); + // Redraw only when the map itself changes, not each time the message object is replaced. + const signature = useMemo(() => JSON.stringify(map), [map]); + const latestMap = useRef(map); + latestMap.current = map; + + useEffect(() => { + const target = targetRef.current; + if (!target) { + return undefined; + } + let disposed = false; + let release: (() => void) | null = null; + setStatus('loading'); + loadOpenLayers() + .then((ol) => { + if (!disposed) { + release = drawMap(ol, target, latestMap.current); + setStatus('ready'); + } + }) + .catch(() => { + if (!disposed) { + setStatus('failed'); + } + }); + return () => { + disposed = true; + release?.(); + }; + }, [signature]); + + return ( +
+
+
+
+
+ {status !== 'ready' && ( +
+ {status === 'loading' + ? 'Loading map\u2026' + : 'This map could not be drawn in this browser. Everything it shows is listed below.'} +
+ )} +
+ +
+ {map.provider} + {map.sourceActionName && ( + <> + + {map.sourceActionName} + + )} + {map.attribution && ( + <> + + {map.attribution} + + )} + {ZOOM_HINT} +
+
+ ); +} + +/** + * Every map attached to a message, drawn under the reply. + * + * A map stored as a compact citation is fetched once, by artifact, before it is drawn. + */ +export function InlineMapCards({ citations, conversationId }: { citations: unknown; conversationId: string }) { + const entries = useMemo(() => collectMapCitations(citations), [citations]); + const [stored, setStored] = useState>({}); + const requested = useRef(new Set()); + + useEffect(() => { + if (!conversationId) { + return; + } + for (const entry of entries) { + const artifactId = entry.artifactId; + if (!artifactId || requested.current.has(artifactId)) { + continue; + } + requested.current.add(artifactId); + fetchAgentCitation(conversationId, artifactId) + .then((response) => readInlineMap(response?.citation)) + .catch(() => null) + .then((map) => setStored((current) => ({ ...current, [artifactId]: map }))); + } + }, [entries, conversationId]); + + const maps = entries.flatMap((entry) => { + const map = entry.map ?? (entry.artifactId ? stored[entry.artifactId] : null); + return map ? [{ key: entry.key, map }] : []; + }); + if (maps.length === 0) { + return null; + } + + return ( +
+ {maps.map(({ key, map }) => ( + + ))} +
+ ); +} diff --git a/application/v2_ui/src/components/chat/MessageList.tsx b/application/v2_ui/src/components/chat/MessageList.tsx index e3e0059dd..30d2170df 100644 --- a/application/v2_ui/src/components/chat/MessageList.tsx +++ b/application/v2_ui/src/components/chat/MessageList.tsx @@ -26,8 +26,10 @@ import { useUiStore } from '../../stores/uiStore'; import { bubbleWidthClass, chatWidthClass } from '../../lib/chatWidth'; import { resolveImageSource, imageEndpointBase } from '../../lib/images'; import { useImageEditCapability, useImageRevisions } from '../../lib/imageRevisions'; +import { stripLegacyMapBlocks } from '../../lib/inlineMaps'; import { EmptyState, GlassButton, GlassPanel, Skeleton } from '../ui/primitives'; import { AssistantMarkdown } from './AssistantMarkdown'; +import { InlineMapCards } from './InlineMapCard'; import { ChatFilePreview } from './ChatFilePreview'; import { GeneratedArtifactCard } from './GeneratedArtifactCard'; import { AnalysisResult } from './AnalysisResult'; @@ -1076,13 +1078,22 @@ function MessageBubbleInner({ generatedImages={generatedImages} > )} + {/* Maps an action returned live in the message's tool results, not its text. */} + {masks.ranges.length === 0 && ( + + )} {(message.model_deployment_name || message.agent_display_name) && (

{message.agent_display_name ? ( diff --git a/application/v2_ui/src/lib/endpoints.ts b/application/v2_ui/src/lib/endpoints.ts index 3764e7418..6a65bb3a1 100644 --- a/application/v2_ui/src/lib/endpoints.ts +++ b/application/v2_ui/src/lib/endpoints.ts @@ -708,6 +708,19 @@ export const fetchMessageThoughts = ( signal, ); +/** + * The full record of one tool call whose result was moved out of the message. + * + * Works for personal and collaborative conversations. Any Azure Maps tile token in the + * result is reissued by the server, so a map from an old reply still loads its tiles. + */ +export const fetchAgentCitation = (conversationId: string, artifactId: string, signal?: AbortSignal) => + api.get<{ citation?: unknown }>( + `/api/conversation/${encodeURIComponent(conversationId)}` + + `/agent-citation/${encodeURIComponent(artifactId)}`, + signal, + ); + export const submitFeedback = ( messageId: string, conversationId: string, diff --git a/application/v2_ui/src/lib/inlineMaps.ts b/application/v2_ui/src/lib/inlineMaps.ts new file mode 100644 index 000000000..0b7046311 --- /dev/null +++ b/application/v2_ui/src/lib/inlineMaps.ts @@ -0,0 +1,439 @@ +// inlineMaps.ts +// Reading the interactive maps an Azure Maps action attaches to an assistant reply. +// +// The map action does not put its map in the reply text. It returns a `map_payload` (a tile +// template, markers, paths, areas and a starting view) as its tool result, and that result is +// stored on the message under `agent_citations`. The classic chat draws every such result as a +// map card under the reply (static/js/chat/chat-inline-maps.js). This module reads the payload +// the same way, without touching the DOM, so it can be tested directly. +// +// The payload is data an action returned, so none of it is trusted as markup or as a place to +// load from: text is only ever rendered as text, a colour must look like a colour, a point must +// be a real longitude and latitude, and tiles only ever come from SimpleChat's own tile proxy. + +export const AZURE_MAPS_RENDER_TYPE = 'azure_maps_openlayers'; + +/** The Azure Maps plugin function whose results are maps. */ +const MAP_FUNCTION_NAME = 'create_map_visualization'; + +/** SimpleChat's Azure Maps tile proxy (`AZURE_MAPS_TILE_PROXY_ROUTE` in functions_azure_maps.py). */ +export const TILE_PROXY_PATH = '/api/azure-maps/tile'; + +/** A point as `[longitude, latitude]`, the order the action and OpenLayers both use. */ +export type LonLat = [number, number]; + +export interface MapMarker { + label: string; + description: string; + color: string; + lonLat: LonLat; +} + +export interface MapPath { + label: string; + description: string; + color: string; + width: number; + coordinates: LonLat[]; +} + +export interface MapArea { + label: string; + description: string; + strokeColor: string; + fillColor: string; + /** A closed ring: the last point repeats the first. */ + coordinates: LonLat[]; +} + +export interface MapView { + center: LonLat; + zoom: number; + maxZoom: number; + fitToFeatures: boolean; +} + +export interface InlineMap { + title: string; + summary: string; + /** Root-relative tile proxy template with `{z}`, `{x}` and `{y}` placeholders. */ + tileUrlTemplate: string; + attribution: string; + provider: string; + sourceActionName: string; + markers: MapMarker[]; + paths: MapPath[]; + areas: MapArea[]; + view: MapView; +} + +/** A map ready to draw, or a stored map whose full result has to be fetched first. */ +export type MapCitation = + | { key: string; map: InlineMap; artifactId?: undefined } + | { key: string; map?: undefined; artifactId: string }; + +// The classic card's colours, so a map looks the same in both clients. +const MARKER_COLOR = '#0d6efd'; +const PATH_COLOR = '#0b5ed7'; +const AREA_STROKE_COLOR = '#b02a37'; +const AREA_FILL_COLOR = 'rgba(176, 42, 55, 0.20)'; +const PATH_WIDTH = 4; + +/** Hex, rgb()/rgba() or a plain colour keyword. Anything else falls back to the default. */ +const COLOR_PATTERN = /^(?:#[0-9a-f]{3,8}|rgba?\(\s*[\d.]+%?\s*(?:,\s*[\d.]+%?\s*){2,3}\)|[a-z]{3,20})$/i; + +type UnknownRecord = Record; + +function isRecord(value: unknown): value is UnknownRecord { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} + +function finiteNumber(value: unknown): number | null { + if (value === null || value === undefined || value === '' || typeof value === 'boolean') { + return null; + } + const number = Number(value); + return Number.isFinite(number) ? number : null; +} + +function text(value: unknown, fallback = ''): string { + return typeof value === 'string' && value.trim() ? value.trim() : fallback; +} + +function color(value: unknown, fallback: string): string { + const candidate = typeof value === 'string' ? value.trim() : ''; + return COLOR_PATTERN.test(candidate) ? candidate : fallback; +} + +function clamp(value: number, low: number, high: number): number { + return Math.min(high, Math.max(low, value)); +} + +function lonLat(longitude: unknown, latitude: unknown): LonLat | null { + const lon = finiteNumber(longitude); + const lat = finiteNumber(latitude); + if (lon === null || lat === null || Math.abs(lon) > 180 || Math.abs(lat) > 90) { + return null; + } + return [lon, lat]; +} + +function coordinatePair(raw: unknown): LonLat | null { + return Array.isArray(raw) && raw.length >= 2 ? lonLat(raw[0], raw[1]) : null; +} + +/** A coordinate list, unwrapping the outer ring when a polygon arrives as `[[[lon, lat], ...]]`. */ +function coordinateList(raw: unknown): LonLat[] { + let list = raw; + if (Array.isArray(list) && Array.isArray(list[0]) && Array.isArray(list[0][0])) { + list = list[0]; + } + if (!Array.isArray(list)) { + return []; + } + return list.map(coordinatePair).filter((pair): pair is LonLat => pair !== null); +} + +function parseJson(value: unknown): unknown { + if (typeof value !== 'string') { + return value; + } + try { + return JSON.parse(value); + } catch { + return null; + } +} + +/** The tool result inside a citation, which may be stored as an object or as JSON text. */ +function citationResult(citation: unknown): UnknownRecord | null { + if (!isRecord(citation)) { + return null; + } + if (citation.render_type && citation.map_payload) { + return citation; + } + const result = parseJson(citation.function_result); + return isRecord(result) ? result : null; +} + +/** + * The tile template if it points at SimpleChat's tile proxy, otherwise null. + * + * Tiles are requested by the browser with the user's session, so a template naming any other + * path or host is refused rather than loaded. + */ +export function safeTileTemplate(value: unknown): string | null { + const template = typeof value === 'string' ? value.trim() : ''; + if (!template.startsWith(`${TILE_PROXY_PATH}?`)) { + return null; + } + if (/[\s"'<>\\]/.test(template)) { + return null; + } + return ['{z}', '{x}', '{y}'].every((placeholder) => template.includes(placeholder)) ? template : null; +} + +function readMarkers(raw: unknown): MapMarker[] { + if (!Array.isArray(raw)) { + return []; + } + return raw.flatMap((marker): MapMarker[] => { + if (!isRecord(marker)) { + return []; + } + const point = lonLat(marker.longitude ?? marker.lon ?? marker.lng, marker.latitude ?? marker.lat); + if (!point) { + return []; + } + return [{ + label: text(marker.label, 'Location'), + description: text(marker.description), + color: color(marker.color, MARKER_COLOR), + lonLat: point, + }]; + }); +} + +function readPaths(raw: unknown): MapPath[] { + if (!Array.isArray(raw)) { + return []; + } + return raw.flatMap((path): MapPath[] => { + if (!isRecord(path)) { + return []; + } + const coordinates = coordinateList(path.coordinates); + if (coordinates.length < 2) { + return []; + } + const width = finiteNumber(path.line_width ?? path.lineWidth ?? path.width); + return [{ + label: text(path.label, 'Path'), + description: text(path.description), + color: color(path.stroke_color, PATH_COLOR), + width: width === null ? PATH_WIDTH : clamp(width, 1, 12), + coordinates, + }]; + }); +} + +function readAreas(raw: unknown): MapArea[] { + if (!Array.isArray(raw)) { + return []; + } + return raw.flatMap((area): MapArea[] => { + if (!isRecord(area)) { + return []; + } + const coordinates = coordinateList(area.coordinates); + if (coordinates.length < 3) { + return []; + } + const [first] = coordinates; + const last = coordinates[coordinates.length - 1]; + if (first[0] !== last[0] || first[1] !== last[1]) { + coordinates.push([first[0], first[1]]); + } + return [{ + label: text(area.label, 'Area'), + description: text(area.description), + strokeColor: color(area.stroke_color, AREA_STROKE_COLOR), + fillColor: color(area.fill_color, AREA_FILL_COLOR), + coordinates, + }]; + }); +} + +function readView(raw: unknown, markers: MapMarker[], paths: MapPath[], areas: MapArea[]): MapView { + const view = isRecord(raw) ? raw : {}; + const fallbackCenter: LonLat = markers[0]?.lonLat ?? areas[0]?.coordinates[0] ?? paths[0]?.coordinates[0] ?? [0, 20]; + const zoom = finiteNumber(view.zoom); + const maxZoom = finiteNumber(view.max_zoom); + const onlyOnePoint = markers.length === 1 && paths.length === 0 && areas.length === 0; + return { + center: coordinatePair(view.center) ?? fallbackCenter, + zoom: zoom === null ? (onlyOnePoint ? 14 : 10) : clamp(zoom, 0, 22), + maxZoom: maxZoom === null ? 15 : clamp(maxZoom, 1, 22), + fitToFeatures: view.fit_to_features !== false, + }; +} + +/** + * The map in one agent citation, or null when the citation is not a usable map. + * + * A map needs a tile template that points at the tile proxy and at least one marker, path or + * area, matching the classic client, which draws nothing for an empty map. + */ +export function readInlineMap(citation: unknown): InlineMap | null { + const result = citationResult(citation); + if (!result || result.success === false || result.render_type !== AZURE_MAPS_RENDER_TYPE) { + return null; + } + const payload = result.map_payload; + if (!isRecord(payload)) { + return null; + } + const tileUrlTemplate = safeTileTemplate(payload.tile_url_template); + if (!tileUrlTemplate) { + return null; + } + const markers = readMarkers(payload.markers); + const paths = readPaths(payload.paths); + const areas = readAreas(payload.areas); + if (markers.length === 0 && paths.length === 0 && areas.length === 0) { + return null; + } + return { + title: text(payload.title, 'Interactive map'), + summary: text(payload.summary) || text(result.summary), + tileUrlTemplate, + attribution: text(payload.tile_attribution), + provider: payload.map_provider === 'azure_maps' ? 'Azure Maps' : 'Map', + sourceActionName: text(payload.source_action_name), + markers, + paths, + areas, + view: readView(payload.view, markers, paths, areas), + }; +} + +/** + * The artifact holding a map's full result, when the message only stores a compact citation. + * + * Large tool results are moved out of the message into artifact records, leaving a citation + * that names the function and the artifact. Only map functions are fetched, so a long list of + * other tool calls does not turn into a request per call. + */ +function storedMapArtifactId(citation: unknown): string | null { + if (!isRecord(citation) || citation.success === false) { + return null; + } + const artifactId = text(citation.artifact_id); + if (!artifactId) { + return null; + } + const isMapFunction = text(citation.function_name) === MAP_FUNCTION_NAME + || /azuremaps/i.test(text(citation.plugin_name)); + return isMapFunction ? artifactId : null; +} + +/** + * Every map attached to a message, in citation order. + * + * An action called twice with the same arguments returns the same map twice; it is drawn once. + */ +export function collectMapCitations(citations: unknown): MapCitation[] { + if (!Array.isArray(citations)) { + return []; + } + const seen = new Set(); + const found: MapCitation[] = []; + citations.forEach((citation, index) => { + const key = `map-${index}`; + const map = readInlineMap(citation); + if (map) { + const fingerprint = JSON.stringify({ ...map, tileUrlTemplate: '' }); + if (!seen.has(fingerprint)) { + seen.add(fingerprint); + found.push({ key, map }); + } + return; + } + const artifactId = storedMapArtifactId(citation); + if (artifactId) { + found.push({ key, artifactId }); + } + }); + return found; +} + +/** Counts shown on the card, so the reader knows what the map holds before exploring it. */ +export function describeMapContents(map: InlineMap): string[] { + const counts: Array<[number, string, string]> = [ + [map.markers.length, 'marker', 'markers'], + [map.paths.length, 'path', 'paths'], + [map.areas.length, 'area', 'areas'], + ]; + return counts + .filter(([count]) => count > 0) + .map(([count, one, many]) => `${count} ${count === 1 ? one : many}`); +} + +/** The prefix of a legacy map block (`AZURE_MAPS_INLINE_BLOCK_PREFIX` in functions_azure_maps.py). */ +const LEGACY_MAP_BLOCK_PREFIX = '{{map:'; + +/** + * Where a legacy map block ends, or null when the text at `start` is not a complete block. + * + * A block is the prefix, a JSON object, then one closing brace. Braces inside JSON strings are + * skipped, exactly as the server does when it reissues the block's tile token. + */ +function legacyMapBlockEnd(content: string, start: number): number | null { + const payloadStart = start + LEGACY_MAP_BLOCK_PREFIX.length; + if (content[payloadStart] !== '{') { + return null; + } + let depth = 0; + let inString = false; + let escaped = false; + for (let index = payloadStart; index < content.length; index += 1) { + const character = content[index]; + if (inString) { + if (escaped) { + escaped = false; + } else if (character === '\\') { + escaped = true; + } else if (character === '"') { + inString = false; + } + continue; + } + if (character === '"') { + inString = true; + } else if (character === '{') { + depth += 1; + } else if (character === '}') { + depth -= 1; + if (depth === 0) { + return content[index + 1] === '}' ? index + 2 : null; + } + } + } + return null; +} + +/** + * Message text without legacy `{{map:...}}` blocks. + * + * Older replies stored the map payload in their text as well as in the tool result. The map is + * drawn from the tool result, so the block is removed rather than shown as raw JSON, as the + * classic chat does. Text that only resembles a block is left alone. + */ +export function stripLegacyMapBlocks(content: string): string { + if (typeof content !== 'string' || !content.includes(LEGACY_MAP_BLOCK_PREFIX)) { + return content; + } + let remaining = ''; + let position = 0; + let removed = false; + for (;;) { + const start = content.indexOf(LEGACY_MAP_BLOCK_PREFIX, position); + if (start === -1) { + remaining += content.slice(position); + break; + } + const end = legacyMapBlockEnd(content, start); + if (end === null) { + // Not a complete block: keep the text and look for another one after it. + const next = start + LEGACY_MAP_BLOCK_PREFIX.length; + remaining += content.slice(position, next); + position = next; + continue; + } + remaining += content.slice(position, start); + position = end; + removed = true; + } + return removed ? remaining.replace(/\n{3,}/g, '\n\n').trim() : content; +} diff --git a/application/v2_ui/src/lib/vendor.d.ts b/application/v2_ui/src/lib/vendor.d.ts index 129e6b368..9f39b989e 100644 --- a/application/v2_ui/src/lib/vendor.d.ts +++ b/application/v2_ui/src/lib/vendor.d.ts @@ -123,11 +123,84 @@ export interface DomPurifyStatic { sanitize(dirty: string, config?: DomPurifyConfig): string; } +/** + * OpenLayers 10.6.1 — the full build, which registers the `ol` namespace. + * https://openlayers.org/en/v10.6.1/apidoc/ + * + * Coordinates are in the map projection (Web Mercator) unless a name says lon/lat. + */ +export type OlCoordinate = number[]; +export type OlOptions = Record; + +export interface OlFeature { + getProperties(): Record; + setStyle(style: unknown): void; +} + +export interface OlMapBrowserEvent { + pixel: number[]; + coordinate: OlCoordinate; + dragging: boolean; +} + +export interface OlMap { + on(type: 'click' | 'pointermove', listener: (event: OlMapBrowserEvent) => void): unknown; + forEachFeatureAtPixel(pixel: number[], callback: (feature: OlFeature) => T): T | undefined; + addInteraction(interaction: unknown): void; + addControl(control: unknown): void; + updateSize(): void; + setTarget(target?: HTMLElement): void; + dispose(): void; +} + +export interface OlView { + fit(extent: number[], options?: { padding?: number[]; maxZoom?: number; duration?: number }): void; +} + +export interface OlVectorSource { + addFeatures(features: OlFeature[]): void; + getExtent(): number[]; +} + +export interface OlOverlay { + setPosition(position?: OlCoordinate): void; +} + +type OlConstructor = new (options: OlOptions) => T; + +export interface OpenLayersStatic { + Map: OlConstructor; + View: OlConstructor; + Feature: OlConstructor; + Overlay: OlConstructor; + layer: { Tile: OlConstructor; Vector: OlConstructor }; + source: { XYZ: OlConstructor; Vector: new (options?: OlOptions) => OlVectorSource }; + geom: { + Point: new (coordinate: OlCoordinate) => unknown; + LineString: new (coordinates: OlCoordinate[]) => unknown; + Polygon: new (rings: OlCoordinate[][]) => unknown; + }; + style: { Style: OlConstructor; Circle: OlConstructor; Fill: OlConstructor; Stroke: OlConstructor }; + proj: { fromLonLat(lonLat: number[]): OlCoordinate }; + extent: { isEmpty(extent: number[]): boolean }; + /** The full build exposes each `defaults` module as a namespace holding the function. */ + interaction: { + defaults: { defaults(options?: OlOptions): unknown }; + MouseWheelZoom: OlConstructor; + }; + control: { + defaults: { defaults(options?: OlOptions): unknown }; + FullScreen: OlConstructor<{ element: HTMLElement }>; + }; + events: { condition: { platformModifierKeyOnly: (event: unknown) => boolean } }; +} + declare global { interface Window { katex?: KatexStatic; mermaid?: MermaidStatic; Chart?: ChartJsConstructor; DOMPurify?: DomPurifyStatic; + ol?: OpenLayersStatic; } } diff --git a/application/v2_ui/src/lib/vendorAssets.ts b/application/v2_ui/src/lib/vendorAssets.ts index bb42681c4..0ad1d835c 100644 --- a/application/v2_ui/src/lib/vendorAssets.ts +++ b/application/v2_ui/src/lib/vendorAssets.ts @@ -12,7 +12,7 @@ // downloads nothing extra. Nothing here reaches the network beyond the app's own origin, so // the `default-src 'self'` Content-Security-Policy is unchanged. -import type { DomPurifyStatic } from './vendor'; +import type { DomPurifyStatic, OpenLayersStatic } from './vendor'; /** * Version-pinned locations of the vendored bundles. @@ -27,6 +27,8 @@ export const VENDOR_PATHS = { mermaid: 'vendor/mermaid-11.17.2/mermaid.min.js', chartJs: 'vendor/chartjs-4.5.1/chart.umd.min.js', domPurify: 'vendor/dompurify-3.4.14/purify.min.js', + openLayersScript: 'vendor/openlayers-10.6.1/ol.js', + openLayersStylesheet: 'vendor/openlayers-10.6.1/ol.css', } as const; /** @@ -108,3 +110,20 @@ export async function loadDomPurify(): Promise { } return purify; } + +/** + * OpenLayers, used to draw the interactive maps an Azure Maps action attaches to a reply. + * + * The same 10.6.1 build the classic chat loads, so a map looks and behaves the same in both. + */ +export async function loadOpenLayers(): Promise { + await Promise.all([ + loadVendorStylesheet(VENDOR_PATHS.openLayersStylesheet), + loadVendorScript(VENDOR_PATHS.openLayersScript), + ]); + const openLayers = window.ol; + if (!openLayers) { + throw new Error('OpenLayers did not register a global after loading'); + } + return openLayers; +} diff --git a/docs/explanation/features/REACT_V2_UI.md b/docs/explanation/features/REACT_V2_UI.md index e79951480..51c373b7d 100644 --- a/docs/explanation/features/REACT_V2_UI.md +++ b/docs/explanation/features/REACT_V2_UI.md @@ -33,7 +33,7 @@ loaded from a CDN, so the `default-src 'self'` Content-Security-Policy is unchan ### Vendored browser libraries -The libraries behind maths, diagrams and charts are **not** npm dependencies. They are +The libraries behind maths, diagrams, charts and maps are **not** npm dependencies. They are downloaded once, committed to this repository under `application/v2_ui/public/vendor/-/`, and loaded at runtime from that local path. @@ -44,6 +44,7 @@ local path. | Mermaid 11.17.2 | `mermaid.min.js` | Diagram rendering | | Chart.js 4.5.1 | `chart.umd.min.js` | Inline chat charts and the settings stats charts | | DOMPurify 3.4.14 | `purify.min.js` | Sanitizer boundary in front of both HTML sinks | +| OpenLayers 10.6.1 | `ol.js`, `ol.css` | Interactive maps from the Azure Maps action, the same build the classic chat loads | Resolving these from a registry at build time would mean the code the browser executes is not the code in this repository. Vendoring pins it: a change to any of these libraries is a @@ -1160,6 +1161,8 @@ this entirely and is the recommended layout. | `functional_tests/test_v2_conversation_details_and_gating.py` | Tags split by category, source documents paged with the citation-tracking note, summary generated on demand, URL access and deep research gated on what is typed, image generation exclusivity, chat width persisted, unsafe tag values not linked | | `functional_tests/test_v2_model_identity_and_scope.py` | The whole model identity is sent and the picker keys on `selection_key`, the document scope is computed rather than hardcoded, and workspace ids travel with it | | `functional_tests/test_v2_rich_rendering.py` | Browser libraries vendored into the repository with their licences and pinned versions, no equivalent npm dependency, KaTeX fonts complete and locally resolvable, a sanitizer boundary at every HTML sink and nowhere else, KaTeX `trust: false`, mermaid strict with no autostart or icon packs, single `$` not treated as maths, fence wiring and chart language parity with the backend, charts copied as data, CSP unchanged | +| `functional_tests/test_v2_inline_maps.py` | OpenLayers vendored byte-identical to the published 10.6.1 package, map text written only as text with OpenLayers' HTML attribution control off, tiles loaded only through the tile proxy, map cards wired under assistant replies, and expired tile tokens reissued by both message endpoints | +| `functional_tests/test_v2_inline_maps_logic.mjs` | Executes the map reading: object and JSON-text results, refused tile templates, coordinate and colour checks, path and area rules, the starting view, de-duplication and compact citations fetched by artifact | | `functional_tests/test_v2_generated_image_lightbox.py` | The image thumbnail opens a dialog rather than a new tab, the dialog is dismissable and manages focus, every source kind is handled by download and open-in-new-tab, and `window.open` is not given `noopener` | | `functional_tests/test_v2_chat_notices.py` | Both notices resolved server-side from the shared helpers, the web search notice's three-key condition including consent, all four AI notice frequencies, dismissal only after a successful write, session keys shared with the classic interface, no hardcoded disclaimer, notice text escaped | | `functional_tests/test_v2_conversation_deep_link.py` | Both parameter spellings read and only the canonical one written, the incoming link captured before any effect can strip it, the URL replaced rather than pushed, a dead link reported instead of stranded, a list row backfilled behind the stale-response guard, and the classic handover carrying the conversation | diff --git a/docs/explanation/features/V2_INTERACTIVE_MAPS.md b/docs/explanation/features/V2_INTERACTIVE_MAPS.md new file mode 100644 index 000000000..b25c754a2 --- /dev/null +++ b/docs/explanation/features/V2_INTERACTIVE_MAPS.md @@ -0,0 +1,139 @@ +# V2 Interactive Maps + +Implemented in version: **0.261.223** + +Related issue: [#1609](https://github.com/microsoft/simplechat/issues/1609) + +## Overview + +The Azure Maps action (`create_map_visualization` in the `azure_maps_openlayers` plugin) returns +a map as its tool result: a tile template, markers, ordered paths, shaded areas and a starting +view. The classic chat has always drawn that result as an interactive map under the reply. The +V2 chat had no map renderer, so a reply that relied on a map showed only its text. + +The V2 chat now draws the same map under the reply: + +- It opens fitted to everything on the map. Drag to pan, use the **+** and **-** buttons, + double-click, pinch, or **Ctrl + scroll** (**Cmd + scroll** on a Mac) to zoom. A plain + scroll still scrolls the conversation, so a long thread with maps stays easy to read. +- Hovering a marker, path or area shows its label and description. Clicking keeps the details + open until you click elsewhere on the map or press **Escape**. +- The **Full screen** button expands the map to the whole screen. +- **List what the map shows** opens a text list of every marker, path and area, for keyboard and + screen reader users and for reading labels without hunting for points. +- The header names the map, repeats the action's summary and counts what the map holds; the + footer names the provider, the action and the tile attribution. + +Maps appear wherever the reply appears: in a personal conversation, and in a group conversation +that received a workflow's reply. + +### Dependencies + +- OpenLayers 10.6.1, vendored into `application/v2_ui/public/vendor/openlayers-10.6.1/`. It is + the same build the classic chat loads from `static/js/openlayers/`, byte-identical to the + published `ol@10.6.1` package, with its BSD 2-Clause licence. It is not an npm dependency and + is downloaded only when a reply has a map. +- The existing Azure Maps tile proxy, `/api/azure-maps/tile`, which keeps the Azure Maps key on + the server. +- `lucide-react` icons. No CDN assets; the Content-Security-Policy is unchanged. + +## Technical Specifications + +### Reading the map + +`application/v2_ui/src/lib/inlineMaps.ts` has no DOM access, so it is tested directly in Node. + +- `readInlineMap(citation)` reads one agent citation. The tool result may be an object or JSON + text, or the payload itself when it was fetched from an artifact. A result is a map only when + it succeeded, has `render_type: 'azure_maps_openlayers'`, a tile template that passes + `safeTileTemplate`, and at least one marker, path or area. +- `safeTileTemplate(value)` accepts only a root-relative template on `/api/azure-maps/tile?` that + has `{z}`, `{x}` and `{y}` placeholders and no whitespace, quotes or angle brackets. Tiles are + requested with the user's session, so a template naming another path or host is refused. +- Markers need a real longitude (-180 to 180) and latitude (-90 to 90); paths need two points + and areas three, and an open area ring is closed. A colour is used only if it is a hex value, + `rgb()`/`rgba()` or a colour keyword; otherwise the classic chat's default colour is used. +- The starting view follows the classic rules: the payload's centre, or the first feature; zoom + 14 for a single marker and 10 otherwise; a maximum zoom of 15; fit to features unless the + payload turns it off. +- `collectMapCitations(citations)` returns every map on a message in citation order. A map + repeated with only a different tile token is drawn once. A compact citation, whose full result + was moved to an artifact record, is returned by artifact ID when its function is + `create_map_visualization` or its plugin is the Azure Maps plugin, so other tool calls never + cause a request. +- `stripLegacyMapBlocks(content)` removes legacy `{{map:...}}` blocks that older replies stored + in their text, matching braces outside JSON strings the way the server does. The classic chat + removes them too; the map itself comes from the tool result. + +### Drawing the map + +`application/v2_ui/src/components/chat/InlineMapCard.tsx` + +- `InlineMapCards` renders under an assistant reply in `MessageList.tsx`, after the reply text, + whenever no part of the message is masked. A compact map citation is fetched once through + `fetchAgentCitation` (`GET /api/conversation//agent-citation/`), + which works for personal and collaborative conversations. +- `InlineMapCard` loads OpenLayers through `loadOpenLayers()` in `vendorAssets.ts` and draws + areas, then paths, then markers, so a marker is never hidden under an area. +- Every title, label and description is action output and is written only as text. React renders + the card, and the popup is built with `document.createElement` and filled with `textContent`. + OpenLayers' own attribution control writes its strings as HTML, so it is turned off and the + attribution is shown as text in the footer. +- The mouse wheel zooms only with the platform modifier key, through `MouseWheelZoom` with + `platformModifierKeyOnly`. +- Tiles are requested with `crossOrigin: 'anonymous'` in the default same-origin deployment, + which still sends the session cookie, and `use-credentials` when the API is on another origin. +- If OpenLayers cannot load, the card says so and the list of what the map shows still works. + +### Tile tokens in older messages + +Tile proxy tokens expire after 240 minutes. The classic message route already reissued them when +a conversation was opened; the endpoints V2 reads did not, so a map in an older reply or in a +group conversation drew markers on blank tiles. + +- `refresh_azure_maps_message_citations(messages)` in `functions_azure_maps.py` reissues the token + in every stored map citation and returns new message objects. Messages and citations that are + not maps are returned unchanged. +- `GET /api/get_messages` (`route_backend_conversations.py`) and + `GET /api/collaboration/conversations//messages` + (`route_backend_collaboration.py`) both call it. The collaborative route already checks that + the caller can view the conversation before any message is read. +- `refresh_azure_maps_function_result` no longer parses JSON-text tool results that cannot be + maps, because these endpoints now pass every citation through it. + +### Configuration + +There is nothing to configure. A map appears when an agent with the Azure Maps action returns +one. + +## Usage + +1. Give an agent the Azure Maps action and ask it to map known locations, a route or an area. +2. The reply shows the map under its text. Hover a point for its details, click to keep them + open, and use **Full screen** for a closer look. +3. Open **List what the map shows** to read every label and description as text. + +## Testing and Validation + +- `functional_tests/test_v2_inline_maps.py`: OpenLayers vendored byte-identical to the published + package; no HTML sink in the card or the reader; the attribution control off; tiles only + through the proxy; the card wired under replies; both message endpoints refreshing tokens; the + refresh helper reissuing expired tokens in object and JSON-text results without mutating its + input. +- `functional_tests/test_v2_inline_maps_logic.mjs`: the reading rules above, run in Node against + the real module. +- `functional_tests/test_v2_rich_rendering.py`: OpenLayers added to the vendored-library and + forbidden npm dependency checks. +- `ui_tests/test_v2_inline_maps.py`: the real `MessageList` and card in Chromium with production + CSS. It covers tiles from the proxy, the expand control, no HTML attribution control, the legacy + block hidden, hover details, click to pin, Escape, a compact citation fetched and drawn, the + text list, and the fallback when OpenLayers cannot load. + +### Known limitations + +- Classic chat still shows details on click only; hover tooltips are V2-only for now. +- A map is drawn only in the chat. Exported conversations do not include a map image. +- A tile token issued when the conversation was opened lasts 240 minutes; reopen the conversation + to reissue it. +- An agent that calls the map action twice with different data shows two maps, as the classic + chat does. diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index a4e27bd61..d5897a88a 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -2,6 +2,29 @@ For feature-focused and fix-focused drill-downs by version, see [Features by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/features) and [Fixes by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/fixes). +### **(v0.261.223)** + +#### New Features + +* **Interactive Maps in the V2 Chat** + * A reply that used the Azure Maps action now shows its map under the text in the V2 chat, as the classic chat does. It opens fitted to every marker, path and area, and can be panned, zoomed and expanded to full screen. + * Hovering a marker, path or area shows its label and description; clicking keeps them open until you click elsewhere or press Escape. **List what the map shows** lists everything on the map as text for keyboard and screen reader users. + * The mouse wheel zooms only with Ctrl (Cmd on a Mac), so scrolling a conversation never zooms a map by accident. + * OpenLayers 10.6.1, the build the classic chat already uses, is vendored into the V2 app and loaded only when a reply has a map. No CDN assets are added and the Content-Security-Policy is unchanged. + * Map text is only ever written as text, and tiles are only loaded through SimpleChat's tile proxy. + * (Ref: #1609, `InlineMapCard.tsx`, `lib/inlineMaps.ts`, `vendorAssets.ts`, [V2 Interactive Maps](features/V2_INTERACTIVE_MAPS.md)) + +#### Bug Fixes + +* **Maps in Older Replies and Group Conversations Load Their Tiles** + * The message endpoints the V2 chat reads, including the one for group conversations, returned maps with their original tile token, which expires after four hours. Older maps drew their points on blank tiles. + * Both endpoints now reissue the token when the conversation is opened, as the classic route already did. + * (Ref: #1609, `functions_azure_maps.py`, `route_backend_conversations.py`, `route_backend_collaboration.py`) + +* **Legacy Map Blocks Hidden From V2 Replies** + * Older replies that stored a `{{map:...}}` block in their text showed it as raw JSON in the V2 chat. The block is now hidden, as in the classic chat, and the map is drawn from the tool result. + * (Ref: #1609, `stripLegacyMapBlocks` in `lib/inlineMaps.ts`, `MessageList.tsx`) + ### **(v0.261.222)** #### New Features diff --git a/docs/reference/actions/azure-maps-openlayers.md b/docs/reference/actions/azure-maps-openlayers.md index 3395d3c3d..df334deac 100644 --- a/docs/reference/actions/azure-maps-openlayers.md +++ b/docs/reference/actions/azure-maps-openlayers.md @@ -29,6 +29,20 @@ Use common setup, then provide **Subscription Key** and test the Azure Maps conn Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). +## What people see in chat + +The map appears under the agent's reply, in personal conversations and in group conversations +that receive the reply. In both the classic and the new chat it opens fitted to every marker, path +and area, and people can pan, zoom and click a point to read its label and description. + +The new chat also shows a point's details on hover, has a **Full screen** button, and lists +everything on the map as text under **List what the map shows**. There the mouse wheel zooms only +with Ctrl (Cmd on a Mac), so scrolling a conversation never zooms a map by accident. + +Tiles load through SimpleChat, so the browser never receives the Azure Maps key. The tile link +stored with a reply expires after four hours and is reissued whenever the conversation is opened, +so maps in older replies keep loading. + ## Related - [Actions reference index]({{ '/reference/actions/' | relative_url }}) diff --git a/functional_tests/test_content_screening_history.py b/functional_tests/test_content_screening_history.py index eb55384aa..3566c7e98 100644 --- a/functional_tests/test_content_screening_history.py +++ b/functional_tests/test_content_screening_history.py @@ -1,7 +1,7 @@ # test_content_screening_history.py """ Functional regressions for public history and native evidence quarantine. -Version: 0.261.122 +Version: 0.261.223 Implemented in: 0.261.106 Executes the real history route, artifact hydration, and model-history guard @@ -179,6 +179,7 @@ def test_history_api_suppresses_held_attachments_and_hydrated_tool_evidence(self "hydrate_m365_pending_action_cards": lambda messages, _reader, _conversation: messages, "deepcopy": deepcopy, "List": List, "Dict": Dict, "Any": Any, "refresh_azure_maps_citation_payload": lambda value: value, + "refresh_azure_maps_message_citations": lambda messages: messages, } load_body("functions_message_artifacts.py", "hydrate_agent_citations_from_artifacts", namespace) load_body("route_backend_conversations.py", "api_get_messages", namespace, nested=True) diff --git a/functional_tests/test_v2_enhanced_citations.py b/functional_tests/test_v2_enhanced_citations.py index 0179e25dc..d3c3a789b 100644 --- a/functional_tests/test_v2_enhanced_citations.py +++ b/functional_tests/test_v2_enhanced_citations.py @@ -2,7 +2,7 @@ """ Functional test for V2 enhanced citations. -Version: 0.261.010 +Version: 0.261.223 Implemented in: 0.261.010 This test ensures the V2 enhanced citation viewers agree with the endpoints they call and @@ -19,6 +19,7 @@ Ignoring the header opens every PDF citation on the wrong page. """ +import importlib.util import re import sys from pathlib import Path @@ -179,8 +180,19 @@ def test_pdf_needs_no_vendored_engine(): assert "frame-src 'self' blob:" in config, ( "The CSP must allow blob: frames for the PDF viewer" ) - assert "media-src 'self' blob:" in config, ( - "The CSP must allow blob: media" + # media-src is built by csp_media_sources so administrators can add media hosts; whatever + # they add, it always starts with same-origin and blob: media. + spec = importlib.util.spec_from_file_location( + "csp_media_sources_under_test", APP_DIR / "csp_media_sources.py" + ) + media_sources = importlib.util.module_from_spec(spec) + spec.loader.exec_module(media_sources) + for origins in ([], ["https://media.example.test"]): + assert media_sources.build_media_src_directive(origins).startswith("media-src 'self' blob:"), ( + "The CSP must allow blob: media" + ) + assert 'f"{MEDIA_SRC_DIRECTIVE}; "' in config, ( + "config.py must put the built media-src directive into the CSP" ) print("PDF rendering approach test passed!") diff --git a/functional_tests/test_v2_inline_maps.py b/functional_tests/test_v2_inline_maps.py new file mode 100644 index 000000000..d1c0cc661 --- /dev/null +++ b/functional_tests/test_v2_inline_maps.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +""" +Functional test for V2 interactive maps from Azure Maps action results. +Version: 0.261.223 +Implemented in: 0.261.223 + +This test ensures that: + - OpenLayers is vendored into the V2 SPA at the same pinned 10.6.1 build the classic chat + uses, byte-identical to the published package, and is loaded only from the app's origin; + - a map an Azure Maps action returns is read from the message's agent citations and drawn + under the reply (the reading logic runs in Node, in test_v2_inline_maps_logic.mjs); + - action output on a map is only ever written as text, and tiles only load through + SimpleChat's tile proxy; + - the personal and collaborative message endpoints V2 reads reissue expired map tile tokens, + so a map in an older reply or a group conversation still loads its tiles. +""" + +import ast +import contextlib +import hashlib +import importlib.util +import json +import subprocess +import sys +import types +from datetime import datetime, timedelta, timezone +from pathlib import Path +from urllib.parse import parse_qs, urlparse + +ROOT = Path(__file__).resolve().parents[1] +APP_DIR = ROOT / "application" / "single_app" +V2_DIR = ROOT / "application" / "v2_ui" +V2_SRC = V2_DIR / "src" +CHAT_DIR = V2_SRC / "components" / "chat" +OPENLAYERS_DIR = V2_DIR / "public" / "vendor" / "openlayers-10.6.1" + +sys.path.insert(0, str(ROOT / "functional_tests")) + +from test_support.versioning import assert_app_version_at_least # noqa: E402 + +IMPLEMENTED_IN = "0.261.223" + +# SHA-256 of the files in the published ol@10.6.1 npm package. +OPENLAYERS_SHA256 = { + "ol.js": "eb5ceb37f7f15f210e9a9db54e471b994ed1f3b205af73c98a7156c95a911b77", + "ol.css": "abc8afd72cc10bd29cc143f443bae4a6804bd3cb3fb262e6b6a6bc6c924ea34f", + "LICENSE.md": "6c4347b83a8c9feef18d57b18e3b6c44cf901b3c344a4a1fbd837e421555ab8e", +} + + +def _read(path): + return path.read_text(encoding="utf-8") + + +def _normalized_bytes(path): + """File bytes with CRLF folded to LF, matching what git stores for a Windows checkout.""" + return path.read_bytes().replace(b"\r\n", b"\n") + + +@contextlib.contextmanager +def _isolated_modules(**stubs): + """Install module stubs for one import and restore sys.modules afterwards.""" + missing = object() + saved = {name: sys.modules.get(name, missing) for name in stubs} + sys.modules.update(stubs) + try: + yield + finally: + for name, module in saved.items(): + if module is missing: + sys.modules.pop(name, None) + else: + sys.modules[name] = module + + +def _load_azure_maps_helpers(): + config_stub = types.ModuleType("config") + config_stub.SECRET_KEY = "functional-test-secret" + appinsights_stub = types.ModuleType("functions_appinsights") + appinsights_stub.log_event = lambda *args, **kwargs: None + + spec = importlib.util.spec_from_file_location( + "functions_azure_maps_under_test", APP_DIR / "functions_azure_maps.py" + ) + module = importlib.util.module_from_spec(spec) + with _isolated_modules(config=config_stub, functions_appinsights=appinsights_stub): + spec.loader.exec_module(module) + return module + + +def _expired_template(helpers): + payload = { + "subscription_key": "maps-test-key", + "expires_at": (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat(), + } + token = helpers._build_fernet_cipher().encrypt(json.dumps(payload).encode("utf-8")).decode("utf-8") + return helpers.build_tile_proxy_url_template(token) + + +def _token(template): + return (parse_qs(urlparse(template).query).get("token") or [""])[0] + + +def test_openlayers_is_vendored_and_pinned(): + """The V2 copy is the published 10.6.1 build, with its licence, in a versioned directory.""" + print("Testing vendored OpenLayers...") + + assert OPENLAYERS_DIR.is_dir(), f"OpenLayers is not vendored at {OPENLAYERS_DIR}" + for name, expected in OPENLAYERS_SHA256.items(): + path = OPENLAYERS_DIR / name + assert path.is_file(), f"openlayers-10.6.1/{name} is missing" + actual = hashlib.sha256(_normalized_bytes(path)).hexdigest() + assert actual == expected, f"openlayers-10.6.1/{name} does not match the published package" + + stylesheet = _read(OPENLAYERS_DIR / "ol.css") + assert "http://" not in stylesheet and "https://" not in stylesheet + + loader = _read(V2_SRC / "lib" / "vendorAssets.ts") + assert "'vendor/openlayers-10.6.1/ol.js'" in loader + assert "'vendor/openlayers-10.6.1/ol.css'" in loader + assert "export async function loadOpenLayers()" in loader + + package = json.loads(_read(V2_DIR / "package.json")) + declared = set(package.get("dependencies", {})) | set(package.get("devDependencies", {})) + assert not declared & {"ol", "openlayers", "@types/ol"}, "OpenLayers must stay vendored, not an npm dependency" + print("Vendored OpenLayers passed!") + + +def test_map_card_writes_action_output_only_as_text(): + """Labels, descriptions and attribution never reach an HTML sink, and tiles stay on the proxy.""" + print("Testing the map card's handling of action output...") + + card = _read(CHAT_DIR / "InlineMapCard.tsx") + helpers = _read(V2_SRC / "lib" / "inlineMaps.ts") + for name, source in (("InlineMapCard.tsx", card), ("inlineMaps.ts", helpers)): + for sink in ("innerHTML", "outerHTML", "insertAdjacentHTML", "dangerouslySetInnerHTML", "document.write"): + assert sink not in source, f"{name} must not use {sink}" + assert "http://" not in source and "https://" not in source, f"{name} must not name an external URL" + + # The popup is filled with text, and OpenLayers' attribution control, which renders its + # strings as HTML, is off; the attribution is shown as text in the footer instead. + assert "title.textContent =" in card and "description.textContent =" in card + assert "ol.control.defaults.defaults({ attribution: false })" in card + assert "attributions:" not in card + assert "{map.attribution}" in card + + # Hover shows details, a click pins them, and the map can be expanded to full screen. + assert "olMap.on('pointermove'" in card and "olMap.on('click'" in card + assert "pinned = Boolean(feature);" in card + assert "new ol.control.FullScreen(" in card + # Scrolling the chat over a map scrolls the chat; the wheel zooms only with a modifier. + assert "mouseWheelZoom: false" in card and "platformModifierKeyOnly" in card + + # Tiles load through SimpleChat's proxy, addressed like every other API call. + assert "url: apiUrl(map.tileUrlTemplate)" in card + assert "export const TILE_PROXY_PATH = '/api/azure-maps/tile';" in helpers + assert "const tileUrlTemplate = safeTileTemplate(payload.tile_url_template);" in helpers + assert "loadOpenLayers()" in card + print("Map card output handling passed!") + + +def test_message_list_draws_maps_from_tool_results(): + """Assistant replies render their map cards, except while part of the message is masked.""" + print("Testing map cards in the message list...") + + message_list = _read(CHAT_DIR / "MessageList.tsx") + assert "import { InlineMapCards } from './InlineMapCard';" in message_list + assert ( + "{masks.ranges.length === 0 && (\n" + "

    {items.map((item, index) => { const Icon = item.icon; + const image = item.image ? openedImage(item.image, item.label) : null; return (
  1. ); })} @@ -229,6 +345,7 @@ function MapContentsList({ map }: { map: InlineMap }) { export function InlineMapCard({ map }: { map: InlineMap }) { const targetRef = useRef(null); const [status, setStatus] = useState<'loading' | 'ready' | 'failed'>('loading'); + const [opened, setOpened] = useState(null); // Redraw only when the map itself changes, not each time the message object is replaced. const signature = useMemo(() => JSON.stringify(map), [map]); const latestMap = useRef(map); @@ -245,7 +362,7 @@ export function InlineMapCard({ map }: { map: InlineMap }) { loadOpenLayers() .then((ol) => { if (!disposed) { - release = drawMap(ol, target, latestMap.current); + release = drawMap(ol, target, latestMap.current, setOpened); setStatus('ready'); } }) @@ -260,6 +377,8 @@ export function InlineMapCard({ map }: { map: InlineMap }) { }; }, [signature]); + const openedSource = opened ? resolveImageSource(opened.url) : null; + return (
    @@ -286,7 +405,7 @@ export function InlineMapCard({ map }: { map: InlineMap }) { )} - +
    {map.provider} {map.sourceActionName && ( @@ -303,6 +422,16 @@ export function InlineMapCard({ map }: { map: InlineMap }) { )} {ZOOM_HINT}
    + {opened && openedSource && + createPortal( + setOpened(null)} + />, + document.body, + )}
    ); } diff --git a/application/v2_ui/src/lib/inlineMaps.ts b/application/v2_ui/src/lib/inlineMaps.ts index 0b7046311..96c00b6de 100644 --- a/application/v2_ui/src/lib/inlineMaps.ts +++ b/application/v2_ui/src/lib/inlineMaps.ts @@ -9,7 +9,8 @@ // // The payload is data an action returned, so none of it is trusted as markup or as a place to // load from: text is only ever rendered as text, a colour must look like a colour, a point must -// be a real longitude and latitude, and tiles only ever come from SimpleChat's own tile proxy. +// be a real longitude and latitude, tiles only ever come from SimpleChat's own tile proxy, and a +// marker's photo must be an absolute https link. export const AZURE_MAPS_RENDER_TYPE = 'azure_maps_openlayers'; @@ -22,11 +23,26 @@ export const TILE_PROXY_PATH = '/api/azure-maps/tile'; /** A point as `[longitude, latitude]`, the order the action and OpenLayers both use. */ export type LonLat = [number, number]; +/** A photo of a marked place, shown with the marker's details. */ +export interface MapImage { + /** An absolute https URL. */ + url: string; + caption: string; +} + +/** One labelled fact about a marked place, such as a reading's time or a transponder ID. */ +export interface MapField { + label: string; + value: string; +} + export interface MapMarker { label: string; description: string; color: string; lonLat: LonLat; + image: MapImage | null; + fields: MapField[]; } export interface MapPath { @@ -82,6 +98,13 @@ const PATH_WIDTH = 4; /** Hex, rgb()/rgba() or a plain colour keyword. Anything else falls back to the default. */ const COLOR_PATTERN = /^(?:#[0-9a-f]{3,8}|rgba?\(\s*[\d.]+%?\s*(?:,\s*[\d.]+%?\s*){2,3}\)|[a-z]{3,20})$/i; +// The same limits the Azure Maps action applies when it builds the payload. +const IMAGE_URL_MAX_LENGTH = 2048; +const IMAGE_CAPTION_MAX_LENGTH = 200; +const FIELD_LIMIT = 12; +const FIELD_LABEL_MAX_LENGTH = 60; +const FIELD_VALUE_MAX_LENGTH = 300; + type UnknownRecord = Record; function isRecord(value: unknown): value is UnknownRecord { @@ -174,6 +197,51 @@ export function safeTileTemplate(value: unknown): string | null { return ['{z}', '{x}', '{y}'].every((placeholder) => template.includes(placeholder)) ? template : null; } +/** + * The image link if it is an absolute https URL, otherwise null. + * + * https is the only scheme the chat's image policy loads from another host. A relative path is + * refused too: it would be requested from SimpleChat with the user's session. + */ +export function safeImageUrl(value: unknown): string | null { + const candidate = typeof value === 'string' ? value.trim() : ''; + if (!candidate || candidate.length > IMAGE_URL_MAX_LENGTH || /[\s"'<>\\]/.test(candidate)) { + return null; + } + try { + const parsed = new URL(candidate); + return parsed.protocol === 'https:' && parsed.hostname ? parsed.href : null; + } catch { + return null; + } +} + +function readImage(url: unknown, caption: unknown): MapImage | null { + const safeUrl = safeImageUrl(url); + return safeUrl ? { url: safeUrl, caption: text(caption).slice(0, IMAGE_CAPTION_MAX_LENGTH) } : null; +} + +function readFields(raw: unknown): MapField[] { + if (!Array.isArray(raw)) { + return []; + } + const fields: MapField[] = []; + for (const field of raw) { + if (!isRecord(field)) { + continue; + } + const value = typeof field.value === 'number' && Number.isFinite(field.value) ? String(field.value) : text(field.value); + const label = text(field.label); + if (label && value) { + fields.push({ label: label.slice(0, FIELD_LABEL_MAX_LENGTH), value: value.slice(0, FIELD_VALUE_MAX_LENGTH) }); + } + if (fields.length === FIELD_LIMIT) { + break; + } + } + return fields; +} + function readMarkers(raw: unknown): MapMarker[] { if (!Array.isArray(raw)) { return []; @@ -191,6 +259,8 @@ function readMarkers(raw: unknown): MapMarker[] { description: text(marker.description), color: color(marker.color, MARKER_COLOR), lonLat: point, + image: readImage(marker.image_url, marker.image_caption), + fields: readFields(marker.fields), }]; }); } diff --git a/application/v2_ui/src/lib/vendor.d.ts b/application/v2_ui/src/lib/vendor.d.ts index 9f39b989e..d027be755 100644 --- a/application/v2_ui/src/lib/vendor.d.ts +++ b/application/v2_ui/src/lib/vendor.d.ts @@ -148,6 +148,7 @@ export interface OlMap { forEachFeatureAtPixel(pixel: number[], callback: (feature: OlFeature) => T): T | undefined; addInteraction(interaction: unknown): void; addControl(control: unknown): void; + getSize(): number[] | undefined; updateSize(): void; setTarget(target?: HTMLElement): void; dispose(): void; @@ -164,6 +165,10 @@ export interface OlVectorSource { export interface OlOverlay { setPosition(position?: OlCoordinate): void; + /** `top`, `center` or `bottom`, a dash, then `left`, `center` or `right`. */ + setPositioning(positioning: string): void; + setOffset(offset: number[]): void; + panIntoView(options?: OlOptions): void; } type OlConstructor = new (options: OlOptions) => T; diff --git a/docs/explanation/features/REACT_V2_UI.md b/docs/explanation/features/REACT_V2_UI.md index 51c373b7d..612871989 100644 --- a/docs/explanation/features/REACT_V2_UI.md +++ b/docs/explanation/features/REACT_V2_UI.md @@ -1162,7 +1162,7 @@ this entirely and is the recommended layout. | `functional_tests/test_v2_model_identity_and_scope.py` | The whole model identity is sent and the picker keys on `selection_key`, the document scope is computed rather than hardcoded, and workspace ids travel with it | | `functional_tests/test_v2_rich_rendering.py` | Browser libraries vendored into the repository with their licences and pinned versions, no equivalent npm dependency, KaTeX fonts complete and locally resolvable, a sanitizer boundary at every HTML sink and nowhere else, KaTeX `trust: false`, mermaid strict with no autostart or icon packs, single `$` not treated as maths, fence wiring and chart language parity with the backend, charts copied as data, CSP unchanged | | `functional_tests/test_v2_inline_maps.py` | OpenLayers vendored byte-identical to the published 10.6.1 package, map text written only as text with OpenLayers' HTML attribution control off, tiles loaded only through the tile proxy, map cards wired under assistant replies, and expired tile tokens reissued by both message endpoints | -| `functional_tests/test_v2_inline_maps_logic.mjs` | Executes the map reading: object and JSON-text results, refused tile templates, coordinate and colour checks, path and area rules, the starting view, de-duplication and compact citations fetched by artifact | +| `functional_tests/test_v2_inline_maps_logic.mjs` | Executes the map reading: object and JSON-text results, refused tile templates, coordinate and colour checks, path and area rules, the starting view, de-duplication, compact citations fetched by artifact, and marker photos (https only) and fields | | `functional_tests/test_v2_generated_image_lightbox.py` | The image thumbnail opens a dialog rather than a new tab, the dialog is dismissable and manages focus, every source kind is handled by download and open-in-new-tab, and `window.open` is not given `noopener` | | `functional_tests/test_v2_chat_notices.py` | Both notices resolved server-side from the shared helpers, the web search notice's three-key condition including consent, all four AI notice frequencies, dismissal only after a successful write, session keys shared with the classic interface, no hardcoded disclaimer, notice text escaped | | `functional_tests/test_v2_conversation_deep_link.py` | Both parameter spellings read and only the canonical one written, the incoming link captured before any effect can strip it, the URL replaced rather than pushed, a dead link reported instead of stranded, a list row backfilled behind the stale-response guard, and the classic handover carrying the conversation | diff --git a/docs/explanation/features/V2_INTERACTIVE_MAPS.md b/docs/explanation/features/V2_INTERACTIVE_MAPS.md index 00287f986..12bd800b3 100644 --- a/docs/explanation/features/V2_INTERACTIVE_MAPS.md +++ b/docs/explanation/features/V2_INTERACTIVE_MAPS.md @@ -2,6 +2,8 @@ Implemented in version: **0.261.223** +Marker photos and fields added in version: **0.261.225** + Related issue: [#1609](https://github.com/microsoft/simplechat/issues/1609) ## Overview @@ -18,9 +20,13 @@ The V2 chat now draws the same map under the reply: scroll still scrolls the conversation, so a long thread with maps stays easy to read. - Hovering a marker, path or area shows its label and description. Clicking keeps the details open until you click elsewhere on the map or press **Escape**. +- A marker can carry a photo of the place and labelled facts about it, such as a toll read's + time, transponder and lane. Both show with the marker's details; click the photo to see it + full size. A reading with no camera image still shows its facts. - The **Full screen** button expands the map to the whole screen. -- **List what the map shows** opens a text list of every marker, path and area, for keyboard and - screen reader users and for reading labels without hunting for points. +- **List what the map shows** opens a text list of every marker, path and area, with each + marker's facts and a thumbnail of its photo, for keyboard and screen reader users and for + reading labels without hunting for points. - The header names the map, repeats the action's summary and counts what the map holds; the footer names the provider, the action and the tile attribution. @@ -65,6 +71,35 @@ that received a workflow's reply. in their text, matching braces outside JSON strings the way the server does. The classic chat removes them too; the map itself comes from the tool result. +### Marker photos and fields + +Added in 0.261.225. Agents usually map places they found in records that also hold a picture of +the place, such as a plate reader's overview frame or a rental counter's camera still, and facts +that matter for each point, such as when a transponder was read. Before this, the only way to +show either was to repeat it in the reply text, away from the point it belongs to. + +- In `locations_json`, each point can carry `image_url`, `image_caption` and `fields`. `fields` + is a list of `{label, value}` objects; the action also accepts an object such as + `{"Lane": 4}`, and `image` as an object with `url` and `caption` or `label`. +- The action keeps a photo only when `image_url` is an absolute `https` URL of at most 2,048 + characters with no whitespace, quotes, angle brackets or backslashes. Anything else is left + out, and the result's summary tells the agent how many links it left out, so it can correct + them. Captions are capped at 200 characters. Up to 12 fields are kept, with labels capped at + 60 characters and values at 300; values must be text, numbers or yes/no. +- `safeImageUrl` in `inlineMaps.ts` applies the same link rules in the browser, so a payload + written by anything other than the action cannot point a photo at another scheme or at a + SimpleChat path that would be requested with the user's session. +- The popup shows the photo, its caption, the label and description, then the fields as a + definition list, all written with `textContent`. The photo loads only when someone points at + the marker, and with `referrerPolicy = 'no-referrer'`, so the host serving it does not learn + which page showed it. If it fails to load, the popup says so. +- The popup opens on whichever side of the marker has more room. Clicks inside it stay in it + (`stopEvent: true`), and a pinned popup is panned fully into view. Clicking the photo opens + `ImageLightbox`, the viewer the chat already uses for images. +- Signed photo links keep working when the conversation is reopened: tool-result redaction does + not touch their `exp` or `sig` parameters. A photo whose own link expires stops loading when + the link does. + ### Drawing the map `application/v2_ui/src/components/chat/InlineMapCard.tsx` @@ -120,7 +155,24 @@ one. 1. Give an agent the Azure Maps action and ask it to map known locations, a route or an area. 2. The reply shows the map under its text. Hover a point for its details, click to keep them open, and use **Full screen** for a closer look. -3. Open **List what the map shows** to read every label and description as text. +3. When a point has a photo, click it in the pinned details to see it full size. +4. Open **List what the map shows** to read every label, description and field as text. + +To give points photos and facts, have the agent pass them in `locations_json`, for example: + +```json +[{ + "label": "MD-T01 10/01 21:14", + "description": "Fort McHenry toll plaza, read TR-5101421", + "latitude": 39.2648, "longitude": -76.5795, + "image_url": "https://media.example.com/tlpr/media/tlpr-scene-TR-5101421.png?exp=...&sig=...", + "image_caption": "Overview image MD-T01 lane 4", + "fields": [ + {"label": "Transponder", "value": "GA-PP-4471023"}, + {"label": "Read at", "value": "10/01 21:14:07 ET"} + ] +}] +``` ## Testing and Validation @@ -136,11 +188,21 @@ one. - `ui_tests/test_v2_inline_maps.py`: the real `MessageList` and card in Chromium with production CSS. It covers tiles from the proxy, the expand control, no HTML attribution control, the legacy block hidden, hover details, click to pin, Escape, a compact citation fetched and drawn, the - text list, and the fallback when OpenLayers cannot load. + text list, and the fallback when OpenLayers cannot load. Since 0.261.225 it also covers a + marker's photo, caption and fields as text, the photo loaded without a referrer, an `http` + photo link never requested, the popup opening below a marker in the top half and above one in + the bottom half, the photo opening full size from the pinned popup and from the list. +- `functional_tests/test_azure_maps_marker_photos_and_fields.py`: which photo links the action + keeps and the summary that reports the rest, field normalization and caps, signed photo links + surviving tool-result redaction, and the card writing captions and fields only as text. ### Known limitations - Classic chat still shows details on click only; hover tooltips are V2-only for now. +- Marker photos and fields show in the V2 chat only. The classic chat shows a marker's label and + description, as before. +- A photo must be served over `https`. A host that refuses requests without a referrer, or that + needs the user's own sign-in, will not show its photos. - A map is drawn only in the chat. Exported conversations do not include a map image. - A tile token issued when the conversation was opened lasts 240 minutes; reopen the conversation to reissue it. diff --git a/docs/reference/actions/azure-maps-openlayers.md b/docs/reference/actions/azure-maps-openlayers.md index df334deac..08ca86958 100644 --- a/docs/reference/actions/azure-maps-openlayers.md +++ b/docs/reference/actions/azure-maps-openlayers.md @@ -39,6 +39,21 @@ The new chat also shows a point's details on hover, has a **Full screen** button everything on the map as text under **List what the map shows**. There the mouse wheel zooms only with Ctrl (Cmd on a Mac), so scrolling a conversation never zooms a map by accident. +## Photos and facts on a point + +A point can carry a photo of the place and labelled facts about it, so a reader sees the evidence +for a location where it sits on the map instead of matching it up from the reply text. An agent +sends them with each item in `locations_json`: + +- `image_url`: an `https` link to the photo. Links that are not `https` are left out, and the + action's result tells the agent how many it dropped. +- `image_caption`: what the photo shows, used as its caption and its alternative text. +- `fields`: a list of `{label, value}` pairs, such as a reading's time or a transponder ID. + +The new chat shows the photo, caption and facts with the point's details, opens the photo full +size when it is clicked, and lists the facts and a thumbnail under **List what the map shows**. +The classic chat shows the point's label and description only. + Tiles load through SimpleChat, so the browser never receives the Azure Maps key. The tile link stored with a reply expires after four hours and is reissued whenever the conversation is opened, so maps in older replies keep loading. diff --git a/functional_tests/test_azure_maps_marker_photos_and_fields.py b/functional_tests/test_azure_maps_marker_photos_and_fields.py new file mode 100644 index 000000000..f29578658 --- /dev/null +++ b/functional_tests/test_azure_maps_marker_photos_and_fields.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +# test_azure_maps_marker_photos_and_fields.py +""" +Functional test for photos and labelled fields on Azure Maps markers. +Version: 0.261.225 +Implemented in: 0.261.225 + +This test ensures that: + - the Azure Maps action keeps a marker's photo only when it is an absolute https link, with an + optional caption, and turns a marker's fields into capped label/value text; + - the action tells the agent when it left a photo link out; + - redacting a tool result before it is stored keeps a signed photo link intact, so the photo + still loads when the conversation is reopened; + - the V2 map card writes captions and fields only as text, loads photos without a referrer, + keeps clicks on the popup inside it, and opens a photo in the existing image viewer. +""" + +import contextlib +import importlib.util +import json +import sys +import types +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +APP_DIR = ROOT / "application" / "single_app" +CARD = ROOT / "application" / "v2_ui" / "src" / "components" / "chat" / "InlineMapCard.tsx" + +sys.path.insert(0, str(ROOT / "functional_tests")) + +from test_support.versioning import assert_app_version_at_least # noqa: E402 + +IMPLEMENTED_IN = "0.261.225" +PHOTO = "https://media.example.test/tlpr/media/tlpr-scene-TR-5101421.png?exp=1791043200&sig=" + "a1" * 32 + + +@contextlib.contextmanager +def _isolated_modules(**stubs): + """Install module stubs for one import and restore sys.modules afterwards.""" + missing = object() + saved = {name: sys.modules.get(name, missing) for name in stubs} + sys.modules.update(stubs) + try: + yield + finally: + for name, module in saved.items(): + if module is missing: + sys.modules.pop(name, None) + else: + sys.modules[name] = module + + +def _module(name, **attributes): + module = types.ModuleType(name) + for key, value in attributes.items(): + setattr(module, key, value) + return module + + +def _exec(name, path): + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _load_plugin(): + class BasePlugin: + def __init__(self, manifest=None): + self.manifest = manifest or {} + + def passthrough_decorator(*_args, **_kwargs): + return lambda function: function + + package = _module("semantic_kernel_plugins") + package.__path__ = [] + stubs = { + "config": _module("config", SECRET_KEY="functional-test-secret"), + "functions_appinsights": _module("functions_appinsights", log_event=lambda *args, **kwargs: None), + "semantic_kernel": _module("semantic_kernel"), + "semantic_kernel.functions": _module("semantic_kernel.functions", kernel_function=passthrough_decorator), + "semantic_kernel_plugins": package, + "semantic_kernel_plugins.base_plugin": _module("semantic_kernel_plugins.base_plugin", BasePlugin=BasePlugin), + "semantic_kernel_plugins.plugin_invocation_logger": _module( + "semantic_kernel_plugins.plugin_invocation_logger", plugin_function_logger=passthrough_decorator + ), + } + with _isolated_modules(**stubs): + helpers = _exec("functions_azure_maps_under_test", APP_DIR / "functions_azure_maps.py") + with _isolated_modules(functions_azure_maps=helpers): + plugin_module = _exec( + "azure_maps_plugin_under_test", APP_DIR / "semantic_kernel_plugins" / "azure_maps_openlayers_plugin.py" + ) + return plugin_module.AzureMapsOpenLayersPlugin({"name": "case_map", "auth": {"key": "maps-test-key"}}) + + +def _load_invocation_logger(): + stubs = { + "agent_execution_context": _module("agent_execution_context", current_agent_execution=lambda: None), + "functions_appinsights": _module( + "functions_appinsights", log_event=lambda *args, **kwargs: None, get_appinsights_logger=lambda: None + ), + "functions_authentication": _module("functions_authentication", get_current_user_id=lambda: None), + "functions_debug": _module("functions_debug", debug_print=lambda *args, **kwargs: None), + } + with _isolated_modules(**stubs): + return _exec("plugin_invocation_logger_under_test", APP_DIR / "semantic_kernel_plugins" / "plugin_invocation_logger.py") + + +def _draw(plugin, markers): + result = plugin.create_map_visualization(title="Vehicle movement", locations_json=json.dumps(markers)) + if result.get("success") is not True: + raise AssertionError(f"Expected a map, got: {result}") + return result + + +def test_action_keeps_only_https_photos(): + print("Testing which photo links the Azure Maps action keeps...") + plugin = _load_plugin() + at = {"latitude": 39.3, "longitude": -76.6} + result = _draw(plugin, [ + {"label": "Kept", "image_url": PHOTO, "image_caption": " Overview MD-T01 lane 4 ", **at}, + {"label": "Object form", "image": {"url": PHOTO, "label": "Counter camera 2 still"}, **at}, + {"label": "No caption", "imageUrl": PHOTO, **at}, + {"label": "Long caption", "image_url": PHOTO, "image_caption": "c" * 250, **at}, + {"label": "http", "image_url": "http://media.example.test/a.png", "image_caption": "dropped", **at}, + {"label": "relative", "image_url": "/api/image/abc", **at}, + {"label": "script", "image_url": "javascript:alert(1)", **at}, + {"label": "data", "image_url": "data:image/png;base64,AAAA", **at}, + {"label": "quote", "image_url": 'https://media.example.test/a.png"onerror="x', **at}, + {"label": "no host", "image_url": "https:///a.png", **at}, + {"label": "too long", "image_url": "https://media.example.test/" + "a" * 2100, **at}, + {"label": "No photo", **at}, + ]) + markers = result["map_payload"]["markers"] + + assert markers[0]["image_url"] == PHOTO + assert markers[0]["image_caption"] == "Overview MD-T01 lane 4" + assert (markers[1]["image_url"], markers[1]["image_caption"]) == (PHOTO, "Counter camera 2 still") + assert markers[2]["image_url"] == PHOTO and "image_caption" not in markers[2] + assert len(markers[3]["image_caption"]) == 200 + for marker in markers[4:]: + assert "image_url" not in marker and "image_caption" not in marker, marker["label"] + + # Seven links were not https URLs; the agent is told so it can correct them. + assert "Left out 7 image links that were not an https URL." in result["summary"], result["summary"] + assert "Left out" not in _draw(plugin, [{"label": "Plain", **at}])["summary"] + print("Photo links passed!") + + +def test_action_normalizes_fields(): + print("Testing marker fields in the Azure Maps action...") + plugin = _load_plugin() + at = {"latitude": 39.3, "longitude": -76.6} + markers = _draw(plugin, [ + {"label": "List form", **at, "fields": [ + {"label": "Transponder", "value": "GA-PP-4471023"}, + {"name": "Toll", "value": 4.5}, + {"label": "Exempt", "value": False}, + {"label": " ", "value": "no label"}, + {"label": "No value", "value": " "}, + {"label": "Nested", "value": {"a": 1}}, + {"label": "L" * 80, "value": "V" * 400}, + "not a field", + ]}, + {"label": "Mapping form", **at, "fields": {"Read": "TR-5101421", "Lane": 4}}, + {"label": "Too many", **at, "fields": [{"label": f"F{index}", "value": "v"} for index in range(20)]}, + {"label": "Text instead of fields", **at, "fields": "Tag GA-PP-4471023"}, + {"label": "None", **at}, + ])["map_payload"]["markers"] + + assert markers[0]["fields"][:3] == [ + {"label": "Transponder", "value": "GA-PP-4471023"}, + {"label": "Toll", "value": "4.5"}, + {"label": "Exempt", "value": "No"}, + ] + assert len(markers[0]["fields"]) == 4 + assert (len(markers[0]["fields"][3]["label"]), len(markers[0]["fields"][3]["value"])) == (60, 300) + assert markers[1]["fields"] == [{"label": "Read", "value": "TR-5101421"}, {"label": "Lane", "value": "4"}] + assert len(markers[2]["fields"]) == 12 + assert "fields" not in markers[3] and "fields" not in markers[4] + print("Marker fields passed!") + + +def test_stored_map_keeps_signed_photo_links(): + print("Testing tool-result redaction of marker photo links...") + plugin = _load_plugin() + logger = _load_invocation_logger() + result = _draw(plugin, [{ + "label": "MD-T01 10/01 21:14", + "latitude": 39.3, + "longitude": -76.6, + "image_url": PHOTO, + "image_caption": "Overview image MD-T01", + "fields": [{"label": "Tag", "value": "GA-PP-4471023"}], + }]) + + for stored in (logger.sanitize_plugin_invocation_value(result), json.loads(logger.sanitize_plugin_invocation_value(json.dumps(result)))): + marker = stored["map_payload"]["markers"][0] + assert marker["image_url"] == PHOTO, marker["image_url"] + assert marker["fields"] == [{"label": "Tag", "value": "GA-PP-4471023"}] + print("Signed photo links passed!") + + +def test_card_shows_photos_and_fields_as_text(): + print("Testing the V2 map card's photo and field handling...") + card = CARD.read_text(encoding="utf-8") + + # Captions and fields are action output, so they are written only as text. + assert "caption.textContent = image.caption;" in card + assert "term.textContent = field.label;" in card and "value.textContent = field.value;" in card + assert "note.textContent = 'The photo could not be loaded.';" in card + # Photos load without a referrer, so another host does not learn which conversation showed them. + assert card.count("referrerPolicy") == 2 + assert "photo.referrerPolicy = 'no-referrer';" in card and 'referrerPolicy="no-referrer"' in card + # A click on the popup, such as on its photo, stays in the popup, and a pinned popup is panned into view. + assert "stopEvent: true," in card + assert "overlay.panIntoView(" in card + # A photo opens in the image viewer the chat already uses. + assert "import { ImageLightbox } from './ImageLightbox';" in card + assert "resolveImageSource(opened.url)" in card + print("Map card photo handling passed!") + + +def test_version_was_incremented(): + assert_app_version_at_least(IMPLEMENTED_IN) + + +if __name__ == "__main__": + tests = [ + test_action_keeps_only_https_photos, + test_action_normalizes_fields, + test_stored_map_keeps_signed_photo_links, + test_card_shows_photos_and_fields_as_text, + test_version_was_incremented, + ] + failures = 0 + for test in tests: + try: + test() + except Exception as exc: # noqa: BLE001 - report every failing check + failures += 1 + print(f"FAILED {test.__name__}: {exc}") + print(f"\n{len(tests) - failures}/{len(tests)} tests passed") + sys.exit(1 if failures else 0) diff --git a/functional_tests/test_v2_inline_maps_logic.mjs b/functional_tests/test_v2_inline_maps_logic.mjs index 8d4dd8585..0a74ffa24 100644 --- a/functional_tests/test_v2_inline_maps_logic.mjs +++ b/functional_tests/test_v2_inline_maps_logic.mjs @@ -1,15 +1,17 @@ // test_v2_inline_maps_logic.mjs -// Version: 0.261.223 +// Version: 0.261.225 // Implemented in: 0.261.223 +// Marker photos and fields in: 0.261.225 // Executes the real V2 inline map helpers: which tool results become maps, how markers, paths, // areas and the starting view are read, and that nothing in a payload can point tiles at -// another host or slip markup or an unchecked colour through. +// another host, load a marker photo from anything but an https link, or slip markup or an +// unchecked colour through. import assert from 'node:assert/strict'; const { - AZURE_MAPS_RENDER_TYPE, TILE_PROXY_PATH, collectMapCitations, describeMapContents, readInlineMap, safeTileTemplate, - stripLegacyMapBlocks, + AZURE_MAPS_RENDER_TYPE, TILE_PROXY_PATH, collectMapCitations, describeMapContents, readInlineMap, safeImageUrl, + safeTileTemplate, stripLegacyMapBlocks, } = await import('../application/v2_ui/src/lib/inlineMaps.ts'); const TEMPLATE = `${TILE_PROXY_PATH}?token=abc&api-version=2024-04-01&tilesetId=microsoft.base.road&zoom={z}&x={x}&y={y}&tileSize=256`; @@ -138,6 +140,73 @@ check('only values that look like colours are passed to the map', () => { ]); }); +check('a marker keeps an https photo and its caption, and any other link is dropped', () => { + const photo = 'https://media.example.test/tlpr/media/tlpr-scene-TR-1.png?exp=1791043200&sig=0a1b2c'; + const at = { latitude: 1, longitude: 1 }; + const map = readInlineMap(citation(mapResult({}, { + markers: [ + { label: 'kept', image_url: photo, image_caption: ' Overview MD-T01 ', ...at }, + { label: 'no caption', image_url: photo, ...at }, + { label: 'none', ...at }, + { label: 'http', image_url: 'http://media.example.test/a.png', ...at }, + { label: 'relative', image_url: '/api/image/abc', ...at }, + { label: 'protocol relative', image_url: '//media.example.test/a.png', ...at }, + { label: 'script', image_url: 'javascript:alert(1)', ...at }, + { label: 'data', image_url: 'data:image/png;base64,AAAA', ...at }, + { label: 'quote', image_url: 'https://media.example.test/a.png"onerror="x', ...at }, + { label: 'space', image_url: 'https://media.example.test/a b.png', ...at }, + { label: 'too long', image_url: `https://media.example.test/${'a'.repeat(2100)}.png`, ...at }, + { label: 'object', image_url: { url: photo }, ...at }, + ], + }))); + assert.deepEqual(map.markers[0].image, { url: photo, caption: 'Overview MD-T01' }); + assert.deepEqual(map.markers[1].image, { url: photo, caption: '' }); + assert.deepEqual(map.markers.slice(2).map((marker) => marker.image), Array(10).fill(null)); + assert.equal(safeImageUrl(` ${photo} `), photo); + assert.equal(safeImageUrl('https:'), null); + assert.equal(safeImageUrl(42), null); + + const longCaption = readInlineMap(citation(mapResult({}, { + markers: [{ image_url: photo, image_caption: 'c'.repeat(250), ...at }], + }))); + assert.equal(longCaption.markers[0].image.caption.length, 200); +}); + +check('fields are label and value text, capped as the action caps them', () => { + const map = readInlineMap(citation(mapResult({}, { + markers: [{ + label: 'Toll read', latitude: 1, longitude: 1, + fields: [ + { label: 'Transponder', value: 'GA-PP-4471023' }, + { label: 'Toll', value: 4.5 }, + { label: ' ', value: 'no label' }, + { label: 'No value', value: '' }, + { label: 'Object', value: { nested: true } }, + { label: 'Lane', value: '' }, + 'not a field', + { label: 'L'.repeat(80), value: 'V'.repeat(400) }, + ], + }], + }))); + const [marker] = map.markers; + assert.deepEqual(marker.fields.slice(0, 3), [ + { label: 'Transponder', value: 'GA-PP-4471023' }, + { label: 'Toll', value: '4.5' }, + { label: 'Lane', value: '' }, + ]); + assert.equal(marker.fields[3].label.length, 60); + assert.equal(marker.fields[3].value.length, 300); + assert.equal(marker.fields.length, 4); + + const many = readInlineMap(citation(mapResult({}, { + markers: [{ latitude: 1, longitude: 1, fields: Array.from({ length: 20 }, (_, index) => ({ label: `F${index}`, value: 'v' })) }], + }))); + assert.equal(many.markers[0].fields.length, 12); + // The action always stores a list; anything else is not read as fields. + const notAList = readInlineMap(citation(mapResult({}, { markers: [{ latitude: 1, longitude: 1, fields: { Tag: 'x' } }] }))); + assert.deepEqual(notAList.markers[0].fields, []); +}); + check('paths need two points and areas three, and an area ring is closed', () => { const map = readInlineMap(citation(mapResult({}, { paths: [ diff --git a/ui_tests/test_v2_inline_maps.py b/ui_tests/test_v2_inline_maps.py index ac724569f..4b38554e9 100644 --- a/ui_tests/test_v2_inline_maps.py +++ b/ui_tests/test_v2_inline_maps.py @@ -1,16 +1,19 @@ # test_v2_inline_maps.py """ UI test for interactive Azure Maps cards in the V2 chat. -Version: 0.261.223 +Version: 0.261.225 Implemented in: 0.261.223 +Marker photos and fields in: 0.261.225 This test ensures that an assistant reply carrying an Azure Maps action result draws an interactive OpenLayers map under the reply: tiles load only through the SimpleChat tile proxy, -hovering a marker shows its details and a click keeps them open, the map can be expanded, a -compact citation is fetched before it is drawn, everything on the map is also listed as text, -a legacy {{map:...}} block is hidden from the reply text, and a readable fallback is shown when -OpenLayers cannot load. Only HTTP boundaries are mocked; the real MessageList, map card and -vendored OpenLayers build run in Chromium with production CSS. +hovering a marker shows its details and a click keeps them open, a marker's photo and fields +show in its popup and in the list and the photo opens full size, the popup opens on the side of +the marker with room, the map can be expanded, a compact citation is fetched before it is drawn, +everything on the map is also listed as text, a legacy {{map:...}} block is hidden from the reply +text, and a readable fallback is shown when OpenLayers cannot load. Only HTTP boundaries are +mocked; the real MessageList, map card and vendored OpenLayers build run in Chromium with +production CSS. Build: npm --prefix .\\application\\v2_ui run build -- --outDir ..\\..\\ui_tests\\artifacts\\orchestration-plan-editor Run: python -m pytest .\\ui_tests\\test_v2_inline_maps.py -q @@ -18,6 +21,7 @@ import base64 import json +import math import re from pathlib import Path from urllib.parse import urlsplit @@ -92,6 +96,39 @@ def map_citation(title, markers, paths=None, areas=None, view=None, **payload): markers=[], areas=[{"label": "Search box", "coordinates": [[-84.5, 33.6], [-84.3, 33.6], [-84.3, 33.8], [-84.5, 33.8]]}], ) +# A same-origin https link, because the harness refuses every other host. +PHOTO = f"{ORIGIN}/map-photos/tlpr-scene-TR-5101421.png?exp=1791043200&sig=0a1b2c" +PHOTO_CAPTION = "Overview image MD-T01 lane 4" +PHOTO_VIEW = {"center": [0, 0], "zoom": 3, "max_zoom": 15, "fit_to_features": False} +# Upper left of the centre, so its popup opens below it; the other is lower right, so its opens above. +READ_POINT = (-20, 15) +TAG_POINT = (20, -15) +PHOTO_MAP = map_citation( + "Reads with photos", + markers=[ + { + "label": "MD-T01 10/01 21:14", + "description": "Fort McHenry toll plaza, toll at 21:14 ET", + "longitude": READ_POINT[0], + "latitude": READ_POINT[1], + "image_url": PHOTO, + "image_caption": PHOTO_CAPTION, + "fields": [ + {"label": "Transponder", "value": "GA-PP-4471023"}, + {"label": "Read at", "value": "10/01 21:14:07 ET"}, + ], + }, + { + "label": "NJ-T03 10/02 01:52", + "description": "Transponder read, no camera", + "longitude": TAG_POINT[0], + "latitude": TAG_POINT[1], + "image_url": "http://media.example.test/not-https.png", + "fields": [{"label": "Toll", "value": "$4.50"}], + }, + ], + view=PHOTO_VIEW, +) COMPACT = { "tool_name": "Map: Search area", "function_name": "create_map_visualization", @@ -120,6 +157,7 @@ def __init__(self, assets, openlayers_available=True): self.assets = assets self.openlayers_available = openlayers_available self.tiles = [] + self.photos = [] self.citation_requests = [] self.unexpected = [] self.errors = [] @@ -145,6 +183,10 @@ def handle(self, route): self.tiles.append(f"{path}?{parsed.query}") route.fulfill(status=200, body=TILE, content_type="image/png") return + if path.startswith("/map-photos/"): + self.photos.append(request.url) + route.fulfill(status=200, body=TILE, content_type="image/png") + return if path == f"/api/conversation/{CONVERSATION}/agent-citation/artifact-search-area": self.citation_requests.append(path) route.fulfill(json={"citation": HYDRATED}) @@ -284,6 +326,89 @@ def test_hover_shows_details_and_a_click_keeps_them_open(map_ui): expect(popup).to_be_hidden() +def marker_pixel(box, point, view=PHOTO_VIEW): + """Where a lon/lat point is drawn on a map that shows `view` without fitting to its features.""" + + def mercator(lon, lat): + return math.radians(lon) * 6378137, math.log(math.tan(math.pi / 4 + math.radians(lat) / 2)) * 6378137 + + resolution = 156543.03392804097 / 2 ** view["zoom"] + x, y = mercator(*point) + center_x, center_y = mercator(*view["center"]) + return ( + box["x"] + box["width"] / 2 + (x - center_x) / resolution, + box["y"] + box["height"] / 2 - (y - center_y) / resolution, + ) + + +def test_marker_photo_and_fields_show_with_its_details_and_open_full_size(map_ui): + page, api = map_ui + mount_messages(page, api, [assistant("photo-reply", [PHOTO_MAP])]) + + card = map_card(page, "Reads with photos") + region = card.get_by_role("region", name=re.compile("Interactive map: Reads with photos")) + expect(region.locator(".ol-viewport")).to_have_count(1) + page.wait_for_timeout(300) + box = region.bounding_box() + read_at = marker_pixel(box, READ_POINT) + tag_at = marker_pixel(box, TAG_POINT) + away = (box["x"] + 30, box["y"] + box["height"] - 30) + popup = card.locator(".ol-overlay-container > div") + + # Hovering the read shows its photo, caption and fields, all as text, below the marker. + page.mouse.move(*read_at) + expect(popup).to_be_visible() + expect(popup).to_contain_text("MD-T01 10/01 21:14") + expect(popup).to_contain_text("Fort McHenry toll plaza, toll at 21:14 ET") + expect(popup).to_contain_text(PHOTO_CAPTION) + expect(popup.locator("dt")).to_have_text(["Transponder", "Read at"]) + expect(popup.locator("dd")).to_have_text(["GA-PP-4471023", "10/01 21:14:07 ET"]) + photo = popup.locator("img") + expect(photo).to_have_attribute("src", PHOTO) + expect(photo).to_have_attribute("alt", PHOTO_CAPTION) + expect(photo).to_have_attribute("referrerpolicy", "no-referrer") + page.wait_for_function("() => { const image = document.querySelector('.ol-overlay-container img'); return image && image.complete; }") + assert api.photos == [PHOTO], api.photos + assert popup.locator("b").count() == 0 + assert popup.bounding_box()["y"] > read_at[1], "a marker in the top half should open its popup below it" + + # The transponder read has no usable photo: its http link is dropped and never requested, + # and its fields show on their own, above the marker. + page.mouse.move(*tag_at) + expect(popup).to_contain_text("NJ-T03 10/02 01:52") + expect(popup.locator("dd")).to_have_text(["$4.50"]) + expect(popup.locator("img")).to_have_count(0) + popup_box = popup.bounding_box() + assert popup_box["y"] + popup_box["height"] < tag_at[1], "a marker in the bottom half should open its popup above it" + + # A click pins the read's details; its photo then opens full size in the image viewer. + page.mouse.click(*read_at) + page.mouse.move(*away) + expect(popup).to_contain_text("MD-T01 10/01 21:14") + popup.get_by_role("button", name=f"View the full-size photo: {PHOTO_CAPTION}").click() + viewer = page.get_by_role("dialog") + expect(viewer).to_be_visible() + expect(viewer.locator(f'img[src="{PHOTO}"]')).to_be_visible() + page.keyboard.press("Escape") + expect(viewer).to_have_count(0) + expect(popup).to_be_visible() + region.press("Escape") + expect(popup).to_be_hidden() + + # The list names the same photo and fields, for keyboard and screen reader users. + card.get_by_text("List what the map shows").click() + expect(card.get_by_text("Transponder: GA-PP-4471023 \u00b7 Read at: 10/01 21:14:07 ET")).to_be_visible() + expect(card.get_by_text("Toll: $4.50", exact=True)).to_be_visible() + listed_photo = card.locator("details").get_by_role("button", name=f"View the full-size photo: {PHOTO_CAPTION}") + expect(listed_photo).to_be_visible() + expect(card.locator("details img")).to_have_count(1) + listed_photo.click() + expect(page.get_by_role("dialog")).to_be_visible() + page.get_by_role("button", name="Close the image").click() + expect(page.get_by_role("dialog")).to_have_count(0) + assert all(url == PHOTO for url in api.photos), api.photos + + def test_compact_citation_is_fetched_and_the_map_is_listed_as_text(map_ui): page, api = map_ui mount_messages(page, api, [assistant("compact-reply", [COMPACT, HOVER_MAP])])