☰

WME πŸ‡ΊπŸ‡¦ E50 Fetch POI Data

Π‘ΠΊΡ€ΠΈΠΏΡ‚ для получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ POI с Π²Π½Π΅ΡˆΠ½ΠΈΡ… рСсурсов

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот скрипт, Π²Ρ‹ сначала Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Tampermonkey, Greasemonkey ΠΈΠ»ΠΈ Violentmonkey.

Для установки этого скрипта Π²Π°ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅, Ρ‚Π°ΠΊΠΎΠ΅ ΠΊΠ°ΠΊ Tampermonkey.

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот скрипт, Π²Ρ‹ сначала Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Tampermonkey ΠΈΠ»ΠΈ Violentmonkey.

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот скрипт, Π²Ρ‹ сначала Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Tampermonkey ΠΈΠ»ΠΈ Userscripts.

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот скрипт, сначала Π²Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Tampermonkey.

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот скрипт, Π²Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ β€” ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ скриптов.

(Ρƒ мСня ΡƒΠΆΠ΅ Π΅ΡΡ‚ΡŒ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ скриптов, Π΄Π°ΠΉΡ‚Π΅ ΠΌΠ½Π΅ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ скрипт!)

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот ΡΡ‚ΠΈΠ»ΡŒ, сначала Π²Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Stylus.

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот ΡΡ‚ΠΈΠ»ΡŒ, сначала Π²Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Stylus.

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот ΡΡ‚ΠΈΠ»ΡŒ, сначала Π²Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Stylus.

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот ΡΡ‚ΠΈΠ»ΡŒ, сначала Π²Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ β€” ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ стилСй.

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот ΡΡ‚ΠΈΠ»ΡŒ, сначала Π²Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ β€” ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ стилСй.

Π§Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ этот ΡΡ‚ΠΈΠ»ΡŒ, сначала Π²Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ β€” ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ стилСй.

(Ρƒ мСня ΡƒΠΆΠ΅ Π΅ΡΡ‚ΡŒ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ стилСй, Π΄Π°ΠΉΡ‚Π΅ ΠΌΠ½Π΅ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ скрипт!)

// ==UserScript==
// @name         WME E50 Fetch POI Data
// @name:uk      WME πŸ‡ΊπŸ‡¦ E50 Fetch POI Data
// @name:ru      WME πŸ‡ΊπŸ‡¦ E50 Fetch POI Data
// @version      0.14.2
// @description  Fetch information about the POI from external sources
// @description:uk Π‘ΠΊΡ€ΠΈΠΏΡ‚ дозволяє ΠΎΡ‚Ρ€ΠΈΠΌΡƒΠ²Π°Ρ‚ΠΈ Ρ–Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†Ρ–ΡŽ ΠΏΡ€ΠΎ POI Π·Ρ– сторонніх рСсурсів
// @description:ru Π‘ΠΊΡ€ΠΈΠΏΡ‚ для получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ POI с Π²Π½Π΅ΡˆΠ½ΠΈΡ… рСсурсов
// @license      MIT License
// @author       Anton Shevchuk
// @namespace    https://greasyfork.org/users/227648-anton-shevchuk
// @supportURL   https://github.com/AntonShevchuk/wme-e50/issues
// @match        https://*.waze.com/editor*
// @match        https://*.waze.com/*/editor*
// @exclude      https://*.waze.com/user/editor*
// @icon         data:image/png;base64,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ICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOmE5OWYwNzEyLTc5ZDctNDY5Ni05MWUyLTE5YmEzNjM2NTIzYiIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpjZGIzMGE2ZS00M2RkLTQ0MzktYTQ0Ny05ODc4YTc0MWZhZWYiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpiMjBiMWUzNS1mZjE3LTRjZjgtODk5My0wMTYwNTc0OGY5MDkiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJNYWMgT1MiCiAgIEdJTVA6VGltZVN0YW1wPSIxNjczNDMzNzgwNTU3NzE0IgogICBHSU1QOlZlcnNpb249IjIuMTAuMzIiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIgogICB4bXA6TWV0YWRhdGFEYXRlPSIyMDIzOjAxOjExVDExOjQzOjAwKzAxOjAwIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyMzowMToxMVQxMTo0MzowMCswMTowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmI4N2M2YmI2LTJmZGItNDdlMi05MjYzLWE3ZjNmMjY0ZDYyOSIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iR2ltcCAyLjEwIChNYWMgT1MpIgogICAgICBzdEV2dDp3aGVuPSIyMDIzLTAxLTExVDExOjQzOjAwKzAxOjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PgpaVBgAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfnAQsKKwBWE1eUAAAGfklEQVR42u2ae0zTVxTHv7R1yuRVymgrdMYhGh9oYDQBCzPMByYm4hAfU1E0IjjjIBKVxEqJIz7+ULFRJEq0hGCFzdDEaEBNAGkZERcnL3EMUXC0gIUCE9BSuj+wP/qzrZSHSy2/T0L4nXvPuXDP795z7rn5ARQUFBQUFBQUFBQUUxEH4ikFNDjgJwCbAIRAb9RnHzPVA5ADyIcDMpCKoZFOEbgQQSG5K9Er1Uq9vaJUK/WSuxI9RFBABK6xA+SV9ZX6qUJlfaUeIsiHF4YIcdcE1zJjVsUAAMprypFTkYPMfzLtagfEe8UjOigayxYvAwBI7kmwS7ErzgEilCp/Vn7HcedAUa1AyM0Quw568g1yCPwEUHWqwBVzH9DooIdy3DkAgCx5lt1HfcMcOe4c0EEPpXHpXCLaS9okdu8A4zly6VwH2lQ/BzCsUYpkRmKhx8Jx/YGetz0QN4sJeb3beiz+arHV9vdb7qNioMJsn9BXiOC5wfB088SgbhC1zbXI+jPLov64HbAtYBsiQyPH5YDW160QXxhxwL7QfVj97WrrB8gFKhrIEwp2DEbO9hz4ePmQ2oMWBmFr2FaIb4mRXJNs1fD/+xZwd3KfkD0NNOTtyjOZvAHH6Y5I+iEJQl+hbTpgotxcdRM8T97HlzWdgYS1CZO3BT5E3a1Gk6rJKt2O7o6P9h/LPTZqDDBm+ZLlJLmosghlz8rAnMlE7OpYuMx0AQB4uHngtN9pHKk+MvkOaFI1gS/lj+sNGv5BA2kNaVbbJs5OBNOZSci1TbVYc3sNIQ/ph3Ao8hAhhy4IBaptbAu4OI44YODdwJhsw+aHkeTy+nKSfLjqMPrf9hPyPK95th0DNP9qxqTvxfIiyXWqOtPt2aMmnlmuLLjR3GzMAca3DPrhX18zvobQVwihrxC7ObstmnJZXJKc/jLdREfVqSLJ+7/ZP/kxYCI4OTqR5BthN7AuaB0cpzsSbSe6TuDy3ctIeZpCflu0sb8vOp0++Q5gu7GRF5Y3+hJ/o0HcwziLDuCwONi8fLPp+Ew2hJuF4BZxEft7LNHu6uRKCnjmGBoity/gLgCeTbIDeGweeGzeqHoqtcrEAdbvFAfsXLETd+rvoKCrYPiQ88XIKul602XWrq2njbwCHOi2EwMczFwzage1qGqswqNnj6DuVpP6pjGmIen7JLNj6bQ6s+1arXbyi6HJYovnFpI88G4AybnJOP/yPNH2JPoJlvgsIWTjZ9Kbo5t/d3QG/dM7oKW9BRVPR6+4Plym0nYppCKp+YzwHtEtEQoSCwjZ+UtnRHtGI6c9BwPaAcyYNgMA4OZkPr2xXdjkmGAhVkzIAW1dbdhUvGniadAMMo0MKrUKHBaHaJvtOhtoBzS9Ghhurxg0hsU6gHRWUNZ9fsVQR4/5+kE3pBvzWDqd7vNzANOJaTGrGHOAd8BsCjXmXOM523FAtiAblT9W4nnccygPKCGaLzKrZ5zvAeBVzysAQHNHM6l9EXeRia2Hqwepau3T99mOA2axZiFwfiDmcOeAw+IgIjDCROci/yKcHZ0Jube/l7jILPmrhKQb6BNIkk8uOkk6UdY1132aLMBmspEflm+1/vXH1yHTyFD6tBQrA1YS7f6+/ihcW4i04jTI++TICs7CjhU7SLbVjSP1rLhZjJTuFLBcWQCApb5Lke6fjsTHiYhiRSFmRQzJtri2+NM4gOfJG/VWxpiqV1WQaWRIa0jD3va9JNtwfjjC+eHmDzWDWlwovUBqK6kuwYaQDUQmSIhIQEJEgtlULaoX2V45fPb2WWgHrTut5T/Ih7RDSj5M3d+C1tetH7XTDmqRUZRhm3eC6S/TceK3E+js7bSo0/+uH1eLrmL7g+0mfYMYxNFfj5qUvcaTPyM7g1ONpybvJCj/Wz6uHGygpqOGJKfWp0LaKMVB/4MI8AkAx50DBo0BZacSDa0NuPTwEkrelFgcT9ImgUQswZWgK+DP5YPNZKO7rxsvVC+QqciETCOz/lzmfdxb33KsZVhIta9vIiyhTx2+ieH9wgNNqVPqDR0x7Bi7n7zxHJU6pZ6mg67MsJ/2hOyxewcY5qjqVEEHXRkNQG7hH4UAAIGfAIooBeK944eLFjv6ifeOhyJKAYGfAADwfs6572vQqfuJjCENbuRL+eXZ97Itphd7QNWpQva9bPCl/HIAG8nV+VT6TE6PDBzHECgoKCgoKCgoKCgopiz/AYtz5BmJvxgCAAAAAElFTkSuQmCC
// @connect      revgeocode.search.hereapi.com
// @connect      api.visicom.ua
// @connect      nominatim.openstreetmap.org
// @connect      dev.virtualearth.net
// @connect      maps.googleapis.com
// @connect      stat.waze.com.ua
// @grant        GM.xmlHttpRequest
// @grant        GM.setClipboard
// @require      https://update.greasyfork.org/scripts/389765/1794584/CommonUtils.js
// @require      https://update.greasyfork.org/scripts/450160/1792042/WME-Bootstrap.js
// @require      https://update.greasyfork.org/scripts/450221/1804989/WME-Base.js
// @require      https://update.greasyfork.org/scripts/450320/1796236/WME-UI.js
// @require      https://cdn.jsdelivr.net/npm/@turf/[email protected]/turf.min.js
// ==/UserScript==

(function () {
    'use strict';

    const NAME = 'E50';

    const TRANSLATION = {
        'en': {
            title: 'Information πŸ“',
            notFound: 'Not found',
            options: {
                title: 'Options',
                modal: 'Use modal window',
                transparent: 'Transparent modal window',
                entryPoint: 'Create Entry Point if not exists',
                externalProvider: 'Show pointer to linked place',
                copyData: 'Copy POI data to clipboard on click',
                lock: 'Lock POI to 2 level',
                keys: 'API keys',
            },
            ranges: {
                title: 'Additional',
                radius: 'Radius for search',
                collapse: 'Collapse the lists longer than',
            },
            providers: {
                title: 'Providers',
                magic: 'Closest Segments',
                osm: 'Open Street Map',
                bing: 'Bing',
                here: 'HERE',
                google: 'Google',
                visicom: 'Visicom',
                ua: 'UA Addresses',
            },
            questions: {
                changeName: 'Are you sure to change the name?',
                changeCity: 'Are you sure to change the city?',
                changeStreet: 'Are you sure to change the street name?',
                changeNumber: 'Are you sure to change the house number?',
                notFoundCity: 'City not found in the current location, are you sure to create a new one?',
                notFoundStreet: 'Street not found in the current location, are you sure to create a new one?'
            }
        },
        'uk': {
            title: 'Інформація πŸ“',
            notFound: 'Нічого Π½Π΅ Π·Π½Π°ΠΉΠ΄Π΅Π½ΠΎ',
            options: {
                title: 'ΠΠ°Π»Π°ΡˆΡ‚ΡƒΠ²Π°Π½Π½Ρ',
                modal: 'Використовувати ΠΎΠΊΡ€Π΅ΠΌΡƒ панСль',
                transparent: 'Напівпрозора панСль',
                entryPoint: 'Π‘Ρ‚Π²ΠΎΡ€ΡŽΠ²Π°Ρ‚ΠΈ Ρ‚ΠΎΡ‡ΠΊΡƒ Π²\'Ρ—Π·Π΄Ρƒ, якщо відсутня',
                externalProvider: 'Π’Ρ–Π΄ΠΎΠ±Ρ€Π°ΠΆΠ°Ρ‚ΠΈ ΠΏΠΎΠ²\'язанС місцС',
                copyData: 'ΠŸΡ€ΠΈ Π²ΠΈΠ±ΠΎΡ€Ρ–, ΠΊΠΎΠΏΡ–ΡŽΠ²Π°Ρ‚ΠΈ Π΄ΠΎ Π±ΡƒΡ„Π΅Ρ€Ρƒ ΠΎΠ±ΠΌΡ–Π½Ρƒ Π½Π°Π·Π²Ρƒ Ρ‚Π° адрСсу POI',
                lock: 'Π‘Π»ΠΎΠΊΡƒΠ²Π°Ρ‚ΠΈ POI 2-ΠΌ Ρ€Ρ–Π²Π½Π΅ΠΌ',
                keys: 'ΠšΠ»ΡŽΡ‡Ρ– Π΄ΠΎ API',
            },
            ranges: {
                title: 'Π”ΠΎΠ΄Π°Ρ‚ΠΊΠΎΠ²Ρ–',
                radius: 'Радіус для ΠΏΠΎΡˆΡƒΠΊΡƒ',
                collapse: 'Π‘ΠΊΠ»Π°Π΄Π°Ρ‚ΠΈ ΠΏΠ΅Ρ€Π΅Π»Ρ–ΠΊ, Π±Ρ–Π»ΡŒΡˆΠΈΠΉ Π·Π°',
            },
            providers: {
                title: 'Π”ΠΆΠ΅Ρ€Π΅Π»Π°',
                magic: 'Π‘Π΅Π³ΠΌΠ΅Π½Ρ‚ΠΈ ΠΏΠΎΡ€ΡƒΡ‡',
                osm: 'Open Street Map',
                bing: 'Bing',
                here: 'HERE',
                google: 'Google',
                visicom: 'Π’Ρ–Π·Ρ–ΠΊΠΎΠΌ',
                ua: 'UA АдрСси',
            },
            questions: {
                changeName: 'Π’ΠΈ Π²ΠΏΠ΅Π²Π½Π΅Π½Π½Ρ– Ρ‰ΠΎ Ρ…ΠΎΡ‡Π΅Ρ‚Π΅ Π·ΠΌΡ–Π½ΠΈΡ‚ΠΈ ΠΈΠΌ\'я?',
                changeCity: 'Π’ΠΈ Π²ΠΏΠ΅Π²Π½Π΅Π½Π½Ρ– Ρ‰ΠΎ Ρ…ΠΎΡ‡Π΅Ρ‚Π΅ Π·ΠΌΡ–Π½ΠΈΡ‚ΠΈ місто?',
                changeStreet: 'Π’ΠΈ Π²ΠΏΠ΅Π²Π½Π΅Π½Π½Ρ– Ρ‰ΠΎ Ρ…ΠΎΡ‡Π΅Ρ‚Π΅ Π·ΠΌΡ–Π½ΠΈΡ‚ΠΈ Π²ΡƒΠ»ΠΈΡ†ΡŽ?',
                changeNumber: 'Π’ΠΈ Π²ΠΏΠ΅Π²Π½Π΅Π½Π½Ρ– Ρ‰ΠΎ Ρ…ΠΎΡ‡Π΅Ρ‚Π΅ Π·ΠΌΡ–Π½ΠΈΡ‚ΠΈ Π½ΠΎΠΌΠ΅Ρ€ Π΄ΠΎΠΌΠ°?',
                notFoundCity: 'Ми Π½Π΅ знайшли Ρ‚Π°ΠΊΠΎΠ³ΠΎ міста Ρƒ ΠΏΠΎΡ‚ΠΎΡ‡Π½ΠΎΠΌΡƒ місці, Π²ΠΈ Π²ΠΏΠ΅Π²Π½Π΅Π½Ρ–, Ρ‰ΠΎ Ρ‚Ρ€Π΅Π±Π° ΠΉΠΎΠ³ΠΎ Π΄ΠΎΠ΄Π°Ρ‚ΠΈ?',
                notFoundStreet: 'Ми Π½Π΅ знайшли Ρ‚Π°ΠΊΡƒ Π²ΡƒΠ»ΠΈΡ†ΡŽ Ρƒ ΠΏΠΎΡ‚ΠΎΡ‡Π½ΠΎΠΌΡƒ місці, Π²ΠΈ Π²ΠΏΠ΅Π²Π½Π΅Π½Ρ–, Ρ‰ΠΎ Ρ‚Ρ€Π΅Π±Π° Ρ—Ρ— Π΄ΠΎΠ΄Π°Ρ‚ΠΈ?',
            }
        },
        'ru': {
            title: 'Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ πŸ“',
            notFound: 'НичСго Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ΠΎ',
            options: {
                title: 'Настройки',
                modal: 'Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΡƒΡŽ панСль',
                transparent: 'ΠŸΠΎΠ»ΡƒΠΏΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½Π°Ρ панСль',
                entryPoint: 'Π‘ΠΎΠ·Π΄Π°Π²Π°Ρ‚ΡŒ Ρ‚ΠΎΡ‡ΠΊΡƒ въСзда Ссли отсутствуСт',
                externalProvider: 'ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ связанноС мСсто',
                copyData: 'ΠŸΡ€ΠΈ Π²ΠΈΠ±ΠΎΡ€Π΅, ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π° Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΈ адрСс POI',
                lock: 'Π‘Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ POI 2-ΠΌ ΡƒΡ€ΠΎΠ²Π½Π΅ΠΌ',
                keys: 'ΠšΠ»ΡŽΡ‡ΠΈ ΠΊ API',
            },
            ranges: {
                title: 'Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ',
                radius: 'Радиус поиска',
                collapse: 'Π‘ΠΊΠ»Π°Π΄Ρ‹Π²Π°Ρ‚ΡŒ списки, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ большС',
            },
            providers: {
                title: 'Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊΠΈ',
                magic: 'Π‘Π»ΠΈΠΆΠ°ΠΉΡˆΠΈΠ΅ сСгмСнты',
                osm: 'Open Street Map',
                bing: 'Bing',
                here: 'HERE',
                google: 'Google',
                visicom: 'Π’ΠΈΠ·ΠΈΠΊΠΎΠΌ',
                ua: 'UA АдрСса',
            },
            questions: {
                changeName: 'Π’ΠΈ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹, Ρ‡Ρ‚ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ имя?',
                changeCity: 'Π’ΠΈ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹, Ρ‡Ρ‚ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Π³ΠΎΡ€ΠΎΠ΄?',
                changeStreet: 'Π’ΠΈ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹, Ρ‡Ρ‚ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΡƒΠ»ΠΈΡ†Ρƒ?',
                changeNumber: 'Π’ΠΈ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹, Ρ‡Ρ‚ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Π½ΠΎΠΌΠ΅Ρ€ Π΄ΠΎΠΌΠ°?',
                notFoundCity: 'ΠœΡ‹ Π½Π΅ нашли Ρ‚Π°ΠΊΠΎΠ³ΠΎ Π³ΠΎΡ€ΠΎΠ΄Π° Π² Π΄Π°Π½Π½ΠΎΠΉ Π»ΠΎΠΊΠ°Ρ†ΠΈΠΈ, Π²Ρ‹ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹ Ρ‡Ρ‚ΠΎ Π½ΡƒΠΆΠ½ΠΎ Π΅Π³ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ?',
                notFoundStreet: 'ΠœΡ‹ Π½Π΅ нашли Ρ‚Π°ΠΊΡƒΡŽ ΡƒΠ»ΠΈΡ†Ρƒ Π² Π΄Π°Π½Π½ΠΎΠΉ Π»ΠΎΠΊΠ°Ρ†ΠΈΠΈ, Π²Ρ‹ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹ Ρ‡Ρ‚ΠΎ Π½ΡƒΠΆΠ½ΠΎ Π΅Ρ‘ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ?',
            }
        },
        'fr': {
            title: 'Informations πŸ“',
            notFound: 'Lieu inconnu',
            options: {
                title: 'RΓ©glages',
                modal: 'Activer la fenΓͺtre',
                transparent: 'FenΓͺtre transparente',
                entryPoint: 'CrΓ©er le point d\'entrΓ©e s\'il n\'existe pas',
                copyData: 'Copier les informations du POI en cliquant',
                lock: 'Verrouiller le POI au niveau 2',
                keys: 'API keys',
            },
            ranges: {
                title: 'SupplΓ©mentaire',
                radius: 'Rayon de recherche',
                collapse: 'RΓ©duire les listes plus grandes que',
            },
            providers: {
                title: 'Sources',
                magic: 'Au plus proche du segment',
                osm: 'Open Street Map',
                bing: 'Bing',
                here: 'HERE',
                google: 'Google',
                visicom: 'Visicom',
                ua: 'UA Addresses',
            },
            questions: {
                changeName: 'Êtes-vous sûr de changer le nom ?',
                changeCity: 'Êtes-vous sûr de changer la ville ?',
                changeStreet: 'Êtes-vous sûr de changer la rue ?',
                changeNumber: 'Êtes-vous sûr de changer le numéro de rue ?',
                notFoundCity: 'City not found in the current location, are you sure to create a new one?',
                notFoundStreet: 'Street not found in the current location, are you sure to create a new one?'
            }
        }
    };

    const SETTINGS = {
        options: {
            modal: true,
            transparent: false,
            entryPoint: true,
            externalProvider: false,
            copyData: true,
            lock: true,
        },
        ranges: {
            radius: 200,
            collapse: 3,
        },
        providers: {
            magic: true,
            osm: false,
            // bing: false,
            here: false,
            google: true,
            visicom: false,
            ua: false,
        },
        keys: {
            // Russian warship, go f*ck yourself!
            visicom: '',
            here: '',
            // bing: '',
            google: 'AIzaSyBWB3' + 'jiUm1dkFwvJWy4w4ZmO7K' + 'PyF4oUa0', // extracted from WME
            ua: 'E50'
        }
    };

    const LOCALE = {
        // Ukraine
        232: {
            country: 'uk',
            language: 'ua',
            locale: 'uk_UA'
        }
    };
    // Road Types
    //   I18n.translations.uk.segment.road_types
    //   I18n.translations.en.segment.road_types
    const TYPES = {
        boardwalk: 10,
        stairway: 16,
        railroad: 18,
        runway: 19,
        parking: 20};

    const layerConfig = {
        styleContext: {
            label: (context) => {
                const style = context?.feature?.properties?.style;
                if (!style)
                    return style;
                return style?.label;
            },
        },
        styleRules: [
            {
                predicate: (properties) => properties.styleName === "styleNode",
                style: {
                    pointRadius: 8,
                    fillOpacity: 0.5,
                    fillColor: '#fff',
                    strokeColor: '#fff',
                    strokeWidth: 2,
                    strokeLinecap: 'round',
                    graphicZIndex: 9999,
                },
            },
            {
                predicate: (properties) => properties.styleName === "styleLine",
                style: {
                    strokeWidth: 4,
                    strokeColor: '#fff',
                    strokeLinecap: 'round',
                    strokeDashstyle: 'dash',
                    label: "${label}",
                    labelOutlineColor: '#000',
                    labelOutlineWidth: 3,
                    labelAlign: 'cm',
                    fontColor: '#fff',
                    fontSize: '24px',
                    fontFamily: 'Courier New, monospace',
                    fontWeight: 'bold',
                    labelYOffset: 24,
                    graphicZIndex: 9999,
                }
            }
        ],
    };

    /**
     * Normalize the string:
     *  - remove the double quotes
     *  - remove double space
     */
    function normalizeString$1(str) {
        // Clear space symbols and double quotes
        str = str.trim()
            .replace(/["""]/g, '')
            .replace(/\s{2,}/g, ' ');
        // Clear accents/diacritics, but "\u0306" needed for "ΠΉ"
        // str = str.normalize('NFD').replace(/[\u0300-\u0305\u0309-\u036f]/g, '');
        return str;
    }
    /**
     * Normalize the name:
     *  - remove β„– and # chars
     *  - remove dots
     */
    function normalizeName(name) {
        name = normalizeString$1(name);
        name = name.replace(/[β„–#]/g, '');
        name = name.replace(/\.$/, '');
        return name;
    }
    /**
     * Normalize the city name
     */
    function normalizeCity(city) {
        return normalizeString$1(city);
    }
    /**
     * Normalize the street name by UA rules
     */
    function normalizeStreet(street) {
        street = normalizeString$1(street);
        if (street === '') {
            return '';
        }
        // Prepare street name
        street = street.replace(/['']/, '\'');
        // Remove text in the "()", OSM puts alternative name to the pair brackets
        street = street.replace(/( ?\(.*\))/gi, '');
        // Normalize title
        let regs = {
            '(^| )Π±ΡƒΠ»ΡŒΠ²Π°Ρ€( |$)': '$1Π±-Ρ€$2', // normalize
            '(^| )Π²Ρ—Π·Π΄( |$)': '$1Π²\'Ρ—Π·Π΄$2', // fix mistakes
            '(^| )Π²\'Ρ–Π·Π΄( |$)': '$1Π²\'Ρ—Π·Π΄$2', // fix mistakes
            '(^|.+?) ?вулиця ?(.+|$)': 'Π²ΡƒΠ». $1$2', // normalize, but ignore Lviv rules
            '(^|.+?) ?ΡƒΠ»ΠΈΡ†Π° ?(.+|$)': 'Π²ΡƒΠ». $1$2', // translate, but ignore Lviv rules
            '^(.+) Π²?ΡƒΠ»\\.?$': 'Π²ΡƒΠ». $1', // normalize and translate, but ignore Lviv rules
            '^Π²?ΡƒΠ».? (.+)$': 'Π²ΡƒΠ». $1', // normalize and translate, but ignore Lviv rules
            '(^| )Π΄ΠΎΡ€ΠΎΠ³Π°( |$)': '$1Π΄ΠΎΡ€.$2', // normalize
            '(^| )ΠΌΡ–ΠΊΡ€ΠΎΡ€Π°ΠΉΠΎΠ½( |$)': '$1ΠΌΠΊΡ€Π½.$2', // normalize
            '(^| )Π½Π°Π±Π΅Ρ€Π΅ΠΆΠ½Π°( |$)': '$1Π½Π°Π±.$2', // normalize
            '(^| )ΠΏΠ»ΠΎΡ‰Π°Π΄ΡŒ( |$)': '$1ΠΏΠ»ΠΎΡ‰Π°$2', // translate
            '(^| )ΠΏΡ€ΠΎΠ²ΡƒΠ»ΠΎΠΊ ΠΏΡ€ΠΎΠ²ΡƒΠ»ΠΎΠΊ( |$)': '$1ΠΏΡ€ΠΎΠ².$2', // O_o
            '(^| )ΠΏΡ€ΠΎΠ²ΡƒΠ»ΠΎΠΊ( |$)': '$1ΠΏΡ€ΠΎΠ².$2', // normalize
            //'(^| )ΠΏΡ€ΠΎΡ—Π·Π΄( |$)': '$1ΠΏΡ€.$2',          // normalize
            '(^| )проспСкт( |$)': '$1просп.$2', // normalize
            '(^| )Ρ€Π°ΠΉΠΎΠ½( |$)': '$1Ρ€-Π½$2', // normalize
            '(^| )станція( |$)': '$1ст.$2', // normalize
        };
        for (let key in regs) {
            let re = new RegExp(key, 'gi');
            if (re.test(street)) {
                street = street.replace(re, regs[key]);
                break;
            }
        }
        return street;
    }
    /**
     * Normalize the number by UA rules
     */
    function normalizeNumber(number) {
        // invalid data as a number
        if (number?.trim().length > 16) {
            return '';
        }
        // process "Π΄."
        number = number.replace(/^Π΄\. ?/i, '');
        // process "Π΄ΠΎΠΌ"
        number = number.replace(/^Π΄ΠΎΠΌ ?/i, '');
        // process "Π±ΡƒΠ΄."
        number = number.replace(/^Π±ΡƒΠ΄\. ?/i, '');
        // remove spaces
        number = number.trim().replace(/\s/g, '');
        number = number.toUpperCase();
        // process Latin to Cyrillic
        number = number.replace('A', 'А');
        number = number.replace('B', 'Π’');
        number = number.replace('E', 'Π•');
        number = number.replace('I', 'Π†');
        number = number.replace('K', 'К');
        number = number.replace('M', 'М');
        number = number.replace('H', 'Н');
        number = number.replace('О', 'О');
        number = number.replace('P', 'Π ');
        number = number.replace('C', 'Π‘');
        number = number.replace('T', 'Π’');
        number = number.replace('Y', 'Π£');
        // process Ρ–, Π·, ΠΎ
        number = number.replace('Π†', 'Ρ–');
        number = number.replace('Π—', 'Π·');
        number = number.replace('О', 'о');
        // process "корпус" to "ΠΊ"
        number = number.replace(/(.*)ΠΊ(?:ΠΎΡ€ΠΏ|орпус)?(\d+)/gi, '$1ΠΊ$2');
        // process "N-M" or "N/M" to "NM"
        number = number.replace(/(.*)[-/]([Π°-яі])/gi, '$1$2');
        // valid number format
        //  123А  123А/321 123А/321Π‘ 123ΠΊ1 123Ак2
        /*if (!number.match(/^\d+[Π°-яі]?([/ΠΊ]\d+[Π°-яі]?)?$/gi)) {
          return ''
        }*/
        return number;
    }
    /**
     * SΓΈrensen-Dice coefficient similarity
     * @link https://github.com/aceakash/string-similarity
     */
    function compareTwoStrings(first, second) {
        first = first.replace(/\s+/g, '');
        second = second.replace(/\s+/g, '');
        if (!first.length && !second.length)
            return 1; // if both are empty strings
        if (!first.length || !second.length)
            return 0; // if only one is empty string
        if (first === second)
            return 1; // identical
        if (first.length === 1 && second.length === 1)
            return 0; // both are 1-letter strings
        if (first.length < 2 || second.length < 2)
            return 0; // if either is a 1-letter string
        let firstBigrams = new Map();
        for (let i = 0; i < first.length - 1; i++) {
            const bigram = first.substring(i, i + 2);
            const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) + 1 : 1;
            firstBigrams.set(bigram, count);
        }
        let intersectionSize = 0;
        for (let i = 0; i < second.length - 1; i++) {
            const bigram = second.substring(i, i + 2);
            const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) : 0;
            if (count > 0) {
                firstBigrams.set(bigram, count - 1);
                intersectionSize++;
            }
        }
        return (2.0 * intersectionSize) / (first.length + second.length - 2);
    }
    /**
     * Find the best match from an array of target strings
     */
    function findBestMatch(mainString, targetStrings) {
        let bestMatch = '';
        let bestMatchRating = 0;
        let bestMatchIndex = -1;
        for (let i = 0; i < targetStrings.length; i++) {
            let rating = compareTwoStrings(mainString, targetStrings[i]);
            if (rating > bestMatchRating) {
                bestMatch = targetStrings[i];
                bestMatchRating = rating;
                bestMatchIndex = i;
            }
        }
        if (bestMatch === '' || bestMatchRating < 0.35) {
            console.log('❌', mainString, 'πŸ†š', targetStrings);
            return -1;
        }
        else {
            console.log('βœ…', mainString, 'πŸ†š', bestMatch, ':', bestMatchRating);
            return bestMatchIndex;
        }
    }

    /**
     * Search the city name from available in the editor area
     */
    function detectCity(wmeSDK, cityName) {
        // Get the list of all available cities
        let cities = wmeSDK.DataModel.Cities.getAll()
            .filter((city) => city.name);
        console.log("Total found " + cities.length + " cities.");
        // More than one city, use city with best matching score
        // Remove text in the "()"; Waze puts the region name to the pair brackets
        let best = findBestMatch(cityName, cities.map((city) => city.name.replace(/( ?\(.*\))/gi, '')));
        if (best > -1) {
            console.info("βœ… City detected");
            return [cities[best]['id'], cities[best]['name']];
            /*} else if (cities.length === 1) {
              console.info("❎ City doesn't found, uses default city")
              return [cities[0]['id'], cities[0]['name']]*/
        }
        else {
            console.info("❌ City doesn't found");
            return [null, cityName];
        }
    }
    /**
     * Search the street name from available in the editor area
     */
    function detectStreet(wmeSDK, cityId, streetName) {
        // It can be empty
        if (streetName.trim() === '') {
            return [null, null];
        }
        // Get all streets
        let streets = wmeSDK.DataModel.Streets.getAll()
            .filter((street) => street.cityId === cityId)
            .filter((street) => street.name);
        // Get type and create RegExp for filter streets
        let reTypes = new RegExp('(алСя|Π±-Ρ€|Π²\'Ρ—Π·Π΄|Π²ΡƒΠ»\\.|Π΄ΠΎΡ€\\.|ΠΌΠΊΡ€Π½|Π½Π°Π±\\.|ΠΏΠ»ΠΎΡ‰Π°|ΠΏΡ€ΠΎΠ²\\.|ΠΏΡ€ΠΎΡ—Π·Π΄|просп\\.|Ρ€-Π½|ст\\.|Ρ‚Ρ€Π°ΠΊΡ‚|траса|Ρ‚ΡƒΠΏΠΈΠΊ|ΡƒΠ·Π²Ρ–Π·|шосС)', 'gi');
        let matches = [...streetName.matchAll(reTypes)];
        let types = [];
        // Detect type(s)
        if (matches.length === 0) {
            types.push('Π²ΡƒΠ».'); // set up a basic type
            streetName = 'Π²ΡƒΠ». ' + streetName;
        }
        else {
            types = matches.map(match => match[0].toLowerCase());
        }
        // Filter streets by detected type(s)
        let filteredStreets = streets.filter((street) => types.some(type => street.name.indexOf(type) > -1));
        // Matching names without type(s)
        let best = findBestMatch(streetName.replace(reTypes, '').toLowerCase().trim(), filteredStreets.map((street) => street.name.replace(reTypes, '').toLowerCase().trim()));
        if (best > -1) {
            return [filteredStreets[best]['id'], filteredStreets[best]['name']];
        }
        else {
            return [null, streetName];
        }
    }

    let E50Cache;
    function setCache(cache) { E50Cache = cache; }
    class Provider {
        constructor(uid, container, settings, scriptSettings, wmeSDK) {
            this.uid = uid.trim().toLowerCase().replace(/\s/g, '-');
            this.name = uid;
            this.response = [];
            this.settings = settings;
            this.scriptSettings = scriptSettings;
            this.wmeSDK = wmeSDK;
            // prepare DOM
            this.panel = this._panel();
            this.container = container;
            this.container.append(this.panel);
        }
        /**
         * @param {String} url
         * @param {Object} data
         * @returns {Promise<unknown>}
         */
        async makeRequest(url, data) {
            let query = new URLSearchParams(data).toString();
            if (query.length) {
                url = url + '?' + query;
            }
            console.log(url);
            return new Promise((resolve, reject) => {
                GM.xmlHttpRequest({
                    method: 'GET',
                    responseType: 'json',
                    url: url,
                    onload: (response) => {
                        if (response && response.response) {
                            resolve(response.response);
                        }
                        else {
                            reject(response);
                        }
                    },
                    onabort: () => reject('aborted'),
                    onerror: (response) => reject(response),
                    ontimeout: () => reject('timeout'),
                });
            });
        }
        /**
         * @param  {Number} lon
         * @param  {Number} lat
         * @param  {Number} radius
         * @return {Promise<array>}
         */
        async request(lon, lat, radius) {
            throw new Error('Abstract method');
        }
        /**
         * @param  {Number} lon
         * @param  {Number} lat
         * @param  {Number} radius
         * @return {Promise<void>}
         */
        async search(lon, lat, radius = 1000) {
            let key = this.uid + ':' + lon + ',' + lat;
            if (E50Cache.has(key)) {
                console.log('E50 Cache hit for ' + key);
                this.response = E50Cache.get(key);
            }
            else {
                console.log('E50 Cache miss for ' + key);
                this.response = await this.request(lon, lat, radius).catch(e => { console.error(this.uid, 'search return error', e); return []; });
                E50Cache.set(key, this.response);
            }
            return new Promise((resolve, reject) => {
                if (this.response) {
                    resolve();
                }
                else {
                    reject();
                }
            });
        }
        /**
         * @param  {Array} res
         * @return {Array}
         */
        collection(res) {
            let result = [];
            for (let i = 0; i < res.length; i++) {
                result.push(this.item(res[i]));
            }
            result = result.filter(x => x);
            return result;
        }
        /**
         * Should return {Object}
         * @param  {Object} res
         * @return {Object}
         */
        item(res) {
            throw new Error('Abstract method');
        }
        /**
         * @param  {Number} lon
         * @param  {Number} lat
         * @param  {String} city
         * @param  {String} street
         * @param  {String} number
         * @param  {String} name
         * @param  {String} reference
         * @return {{number: *, cityId: Number, cityName: *, streetId: Number, streetName: *, name: *, raw: *, lon: *, title: *, lat: *}}
         */
        element(lon, lat, city, street, number, name = '', reference = '') {
            // Raw data from provider
            let raw = [city, street, number, name].filter(x => !!x).join(', ');
            {
                city = normalizeCity(city);
                street = normalizeStreet(street);
                number = normalizeNumber(number);
                name = normalizeName(name);
            }
            let [cityId, cityName] = detectCity(this.wmeSDK, city);
            let [streetId, streetName] = detectStreet(this.wmeSDK, cityId, street);
            if (!cityId && streetId) {
                let streetModel = this.wmeSDK.DataModel.Streets.getById({ streetId: streetId });
                let cityModel = this.wmeSDK.DataModel.Cities.getById({ cityId: streetModel.cityId });
                cityId = cityModel.id;
                cityName = cityModel.name;
            }
            let title = [street, number, name].filter(x => !!x).join(', ');
            return {
                lat: lat,
                lon: lon,
                cityId: cityId,
                cityName: cityName,
                streetId: streetId,
                streetName: streetName,
                number: number,
                name: name,
                title: title,
                raw: raw,
                reference: reference
            };
        }
        /**
         * Render result to target element
         */
        render() {
            if (this.response.length === 0) {
                // remove empty panel
                this.panel.remove();
                return;
            }
            this.panel.append(this._fieldset());
        }
        /**
         * Create div for all items
         * @return {HTMLDivElement}
         * @private
         */
        _panel() {
            let div = document.createElement('div');
            div.id = NAME.toLowerCase() + '-' + this.name;
            div.className = NAME.toLowerCase();
            return div;
        }
        /**
         * Build fieldset with the list of the response items
         * @return {HTMLFieldSetElement}
         * @protected
         */
        _fieldset() {
            let fieldset = document.createElement('fieldset');
            let list = document.createElement('ul');
            let collapse = parseInt(this.scriptSettings.get('ranges', 'collapse'));
            if (collapse && this.response.length > collapse) {
                fieldset.className = 'collapsed';
            }
            else {
                fieldset.className = '';
            }
            for (let i = 0; i < this.response.length; i++) {
                let item = document.createElement('li');
                item.append(this._link(this.response[i]));
                list.append(item);
            }
            let legend = document.createElement('legend');
            legend.innerHTML = this.name + ' <span>' + this.response.length + '</span>';
            legend.onclick = function () {
                this.parentElement.classList.toggle("collapsed");
                return false;
            };
            fieldset.append(legend, list);
            return fieldset;
        }
        /**
         * Build link by {Object}
         * @param  {Object} item
         * @return {HTMLAnchorElement}
         * @protected
         */
        _link(item) {
            let a = document.createElement('a');
            a.href = '#';
            a.dataset.lat = item.lat;
            a.dataset.lon = item.lon;
            a.dataset.cityId = item.cityId || '';
            a.dataset.cityName = item.cityName || '';
            a.dataset.streetId = item.streetId || '';
            a.dataset.streetName = item.streetName || '';
            a.dataset.number = item.number;
            a.dataset.name = item.name;
            a.dataset.reference = item.reference || '';
            a.innerText = item.title || item.raw;
            a.title = item.raw;
            a.className = NAME + '-link';
            if (!item.cityId || !item.streetId) {
                a.className += ' noaddress';
            }
            if (!item.number) {
                a.className += ' nonumber';
            }
            return a;
        }
    }

    /**
     * Based on the closest segment and city
     */
    class MagicProvider extends Provider {
        constructor(container, settings, scriptSettings, wmeSDK) {
            super(WMEUI.t(NAME).providers.magic, container, settings, scriptSettings, wmeSDK);
        }
        async request(lon, lat, radius) {
            let segments = this.wmeSDK.DataModel.Segments.getAll();
            let except = [TYPES.boardwalk, TYPES.stairway, TYPES.railroad, TYPES.runway, TYPES.parking];
            segments = segments.filter((segment) => except.indexOf(segment.roadType) === -1);
            let streets = {};
            console.groupCollapsed(this.uid);
            for (let key in segments) {
                let segment = segments[key];
                let address = this.wmeSDK.DataModel.Segments.getAddress({ segmentId: segment.id });
                if (address.street.name === '') {
                    continue;
                }
                let distance = turf.pointToLineDistance(turf.point([lon, lat]), segment.geometry, {
                    units: 'meters'
                });
                if (!streets[address.street.id]
                    || distance < streets[address.street.id].distance) {
                    let nearestPointOnLine = turf.nearestPointOnLine(segment.geometry, turf.point([lon, lat]));
                    streets[address.street.id] = {
                        lon: nearestPointOnLine.geometry.coordinates[0],
                        lat: nearestPointOnLine.geometry.coordinates[1],
                        streetId: address.street.id,
                        streetName: address.street.name,
                        cityId: address.city.id,
                        cityName: address.city.name,
                        number: '',
                        name: '',
                        title: address.street.name,
                        raw: address.city.name + ', ' + address.street.name,
                        distance: distance,
                    };
                }
            }
            let result = [];
            for (let key in streets) {
                if (streets.hasOwnProperty(key) && streets[key].distance <= radius) {
                    result.push(streets[key]);
                }
            }
            result.sort((a, b) => {
                if (a.distance < b.distance) {
                    return -1;
                }
                if (a.distance > b.distance) {
                    return 1;
                }
                return 0;
            });
            console.log(result.length + ' streets found.');
            console.groupEnd();
            return result;
        }
    }

    /**
     * UA Addresses
     */
    class UaAddressesProvider extends Provider {
        constructor(container, settings, scriptSettings, wmeSDK, key) {
            super(WMEUI.t(NAME).providers.ua, container, settings, scriptSettings, wmeSDK);
            this.key = key;
        }
        async request(lon, lat, radius) {
            let result = [];
            let url = 'https://stat.waze.com.ua/address_map/address_map.php';
            let data = {
                lon: lon,
                lat: lat,
                radius: radius,
                limit: 20,
                script: this.key
            };
            let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e));
            console.groupCollapsed(this.uid);
            if (response?.result && response.result === 'success') {
                result = this.collection(response.data.polygons.Default);
            }
            else {
                console.info('No response returned');
            }
            console.groupEnd();
            return result;
        }
        item(res) {
            let data = res.name.split(",");
            data = data.map((part) => part.trim());
            let number = data.length ? data.pop() : null;
            let street = data.length ? data.pop() : null;
            let city = data.length ? data.pop() : null;
            // https://cdn.jsdelivr.net/npm/[email protected]/wellknown.min.js
            // let element = wellknown.parse(res.polygon);
            // let center = turf.centroid(element)
            //  center.geometry.coordinates[0],
            //  center.geometry.coordinates[1],
            let [lat, lon] = res.center.split(';');
            return this.element(lon, lat, city, street, number);
        }
    }

    /**
     * visicom.ua
     */
    class VisicomProvider extends Provider {
        constructor(container, settings, scriptSettings, wmeSDK, key) {
            super(WMEUI.t(NAME).providers.visicom, container, settings, scriptSettings, wmeSDK);
            this.key = key;
        }
        async request(lon, lat, radius) {
            let result = [];
            let url = 'https://api.visicom.ua/data-api/5.0/uk/geocode.json';
            let data = {
                near: lon + ',' + lat,
                categories: 'adr_address',
                order: 'distance',
                radius: radius,
                limit: 10,
                key: this.key,
            };
            let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e));
            console.groupCollapsed(this.uid);
            if (response?.features?.length > 0) {
                result = this.collection(response.features);
            }
            else {
                console.info('No response returned');
                if (response?.status) {
                    console.info('Status:', response.status);
                }
            }
            console.groupEnd();
            return result;
        }
        item(res) {
            let city = '';
            let street = '';
            let number = '';
            if (res.properties.settlement) {
                city = res.properties.settlement;
            }
            if (res.properties.street) {
                street = res.properties.street_type + ' ' + res.properties.street;
            }
            if (res.properties.name) {
                number = res.properties.name;
            }
            return this.element(res.geo_centroid.coordinates[0], res.geo_centroid.coordinates[1], city, street, number);
        }
    }

    /**
     * OpenStreetMap
     */
    class OsmProvider extends Provider {
        constructor(container, settings, scriptSettings, wmeSDK) {
            super(WMEUI.t(NAME).providers.osm, container, settings, scriptSettings, wmeSDK);
        }
        async request(lon, lat, radius) {
            let result = [];
            let url = 'https://nominatim.openstreetmap.org/reverse';
            let data = {
                lon: lon,
                lat: lat,
                zoom: 18,
                addressdetails: 1,
                countrycodes: this.settings.language,
                'accept-language': this.settings.locale,
                format: 'json',
            };
            let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e));
            console.groupCollapsed(this.uid);
            if (response?.address) {
                result = [this.item(response)];
            }
            else {
                console.info('No response returned');
            }
            console.groupEnd();
            return result;
        }
        item(res) {
            let city = '';
            let street = '';
            let number = '';
            if (res.address.city) {
                city = res.address.city;
            }
            else if (res.address.town) {
                city = res.address.town;
            }
            if (res.address.road) {
                street = res.address.road;
            }
            if (res.address.house_number) {
                number = res.address.house_number;
            }
            return this.element(res.lon, res.lat, city, street, number);
        }
    }

    /**
     * Here Maps
     * @link https://developer.here.com/documentation/geocoder/topics/quick-start-geocode.html
     * @link https://www.here.com/docs/bundle/geocoder-api-developer-guide/page/topics/resource-reverse-geocode.html
     */
    class HereProvider extends Provider {
        constructor(container, settings, scriptSettings, wmeSDK, key) {
            super(WMEUI.t(NAME).providers.here, container, settings, scriptSettings, wmeSDK);
            this.key = key;
        }
        async request(lon, lat, radius) {
            let result = [];
            let url = 'https://revgeocode.search.hereapi.com/v1/revgeocode';
            let data = {
                apiKey: this.key,
                at: lat + ',' + lon,
                types: 'address',
                limit: 20
            };
            let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e));
            console.groupCollapsed(this.uid);
            if (response?.items?.length) {
                result = this.collection(response.items.filter((x) => x.resultType === 'houseNumber'));
            }
            else {
                console.info('No response returned');
            }
            console.groupEnd();
            return result;
        }
        item(res) {
            console.log(res);
            return this.element(res.position.lng, res.position.lat, res.address.city, res.address.street, res.address.houseNumber);
        }
    }

    /**
     * Bing Maps
     * @link https://docs.microsoft.com/en-us/bingmaps/rest-services/locations/find-a-location-by-point
     * http://dev.virtualearth.net/REST/v1/Locations/50.03539,36.34732?o=xml&key=AuBfUY8Y1Nzf3sRgceOYxaIg7obOSaqvs0k5dhXWfZyFpT9ArotYNRK7DQ_qZqZw&c=uk
     * http://dev.virtualearth.net/REST/v1/Locations/50.03539,36.34732?o=xml&key=AuBfUY8Y1Nzf3sRgceOYxaIg7obOSaqvs0k5dhXWfZyFpT9ArotYNRK7DQ_qZqZw&c=uk&includeEntityTypes=Address
     */
    class BingProvider extends Provider {
        constructor(container, settings, scriptSettings, wmeSDK, key) {
            super(WMEUI.t(NAME).providers.bing, container, settings, scriptSettings, wmeSDK);
            this.key = key;
        }
        async request(lon, lat, radius) {
            let result = [];
            let url = 'https://dev.virtualearth.net/REST/v1/Locations/' + lat + ',' + lon;
            let data = {
                includeEntityTypes: 'Address',
                c: this.settings.country,
                key: this.key,
            };
            let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e));
            console.groupCollapsed(this.uid);
            if (response?.resourceSets?.[0]?.resources?.length) {
                result = this.collection(response.resourceSets[0].resources.filter((el) => el.address?.addressLine?.includes(',')));
            }
            else {
                console.info('No response returned');
            }
            console.groupEnd();
            return result;
        }
        item(res) {
            let address = res.address.addressLine.split(',');
            return this.element(res.point.coordinates[1], res.point.coordinates[0], res.address.locality, address[0], address[1]);
        }
    }

    /**
     * Google Place
     * @link https://developers.google.com/places/web-service/search
     */
    class GoogleProvider extends Provider {
        constructor(container, settings, scriptSettings, wmeSDK, key) {
            super(WMEUI.t(NAME).providers.google, container, settings, scriptSettings, wmeSDK);
            this.key = key;
        }
        async request(lon, lat, radius) {
            let result = [];
            let response = await this.makeAPIRequest(lat, lon, radius)
                .catch(e => null);
            //.catch(e => console.error(this.uid, 'return error', e))
            console.groupCollapsed(this.uid);
            if (response?.length) {
                result = this.collection(response);
            }
            else {
                console.info('No response returned');
            }
            console.groupEnd();
            return result;
        }
        async makeAPIRequest(lat, lon, radius) {
            let center = new google.maps.LatLng(lat, lon);
            let map = new google.maps.Map(document.createElement('div'), { center: center });
            let request = {
                location: center,
                radius: radius,
                type: 'point_of_interest',
                // doesn't work
                // fields: ['name', 'address_component', 'geometry'],
                // language: this.settings.country,
            };
            let service = new google.maps.places.PlacesService(map);
            return new Promise((resolve, reject) => {
                service.nearbySearch(request, (results, status) => {
                    if (status === google.maps.places.PlacesServiceStatus.OK) {
                        resolve(results);
                    }
                    else {
                        reject(status);
                    }
                });
            });
        }
        item(res) {
            let address = res.vicinity.split(',');
            address = address.map((str) => str.trim());
            // looks like hell
            let street = address[0] && address[0].length > 4 ? address[0] : '';
            let number = address[1] && address[1].length < 13 ? address[1] : '';
            let city = address[2] ? address[2] : '';
            return this.element(res.geometry.location.lng(), res.geometry.location.lat(), city, street, number, res.name, res.reference);
        }
        /**
         * Details about a specific object or entity.
         *
         * This variable is used to encapsulate information or attributes
         * related to a particular subject. The structure and type of the
         * details may vary depending on the specific application or use-case.
         */
        static async makeDetailsRequest(reference) {
            // We need a map instance to initialize the service (even a dummy one)
            let map = new google.maps.Map(document.createElement('div'));
            let service = new google.maps.places.PlacesService(map);
            let request = {
                placeId: reference, // Google now uses placeId instead of reference
                // Specifying fields is cheaper and faster
                fields: ['business_status', 'geometry', 'name', 'place_id', 'vicinity']
            };
            return new Promise((resolve, reject) => {
                service.getDetails(request, (place, status) => {
                    if (status === google.maps.places.PlacesServiceStatus.OK) {
                        resolve(place);
                    }
                    else {
                        reject(status);
                    }
                });
            });
        }
    }

    class E50 extends WMEBase {
        constructor(name, settings) {
            super(name, settings);
            this.initTab();
            this.initLayer();
        }
        initTab() {
            this.modal = this.helper.createModal(WMEUI.t(NAME).title);
            this.panel = this.helper.createPanel(WMEUI.t(NAME).title);
            let tab = this.helper.createTab(WMEUI.t(NAME).title, {
                sidebar: this.wmeSDK.Sidebar,
                image: GM_info.script.icon
            });
            // Setup options
            /** @type {WMEUIHelperFieldset} */
            let fsOptions = this.helper.createFieldset(WMEUI.t(NAME).options.title);
            let options = this.settings.get('options');
            let checkboxes = {};
            for (let item in options) {
                if (options.hasOwnProperty(item)) {
                    checkboxes[item] = {
                        title: WMEUI.t(NAME).options[item],
                        callback: (event) => this.settings.set('options', item, event.target.checked),
                        checked: this.settings.get('options', item),
                    };
                }
            }
            fsOptions.addCheckboxes(checkboxes);
            tab.addElement(fsOptions);
            // Setup ranges
            /** @type {WMEUIHelperFieldset} */
            let fsRanges = this.helper.createFieldset(WMEUI.t(NAME).ranges.title);
            let ranges = this.settings.get('ranges');
            for (let item in ranges) {
                if (ranges.hasOwnProperty(item)) {
                    fsRanges.addNumber('settings-ranges-' + item, WMEUI.t(NAME).ranges[item], (event) => this.settings.set('ranges', item, event.target.value), this.settings.get('ranges', item), (item === 'radius') ? 100 : 0, (item === 'radius') ? 1000 : 10, (item === 'radius') ? 50 : 1);
                }
            }
            tab.addElement(fsRanges);
            // Setup providers settings
            /** @type {WMEUIHelperFieldset} */
            let fsProviders = this.helper.createFieldset(WMEUI.t(NAME).providers.title);
            let providers = this.settings.get('providers');
            let providerCheckboxes = {};
            for (let item in providers) {
                if (providers.hasOwnProperty(item) && SETTINGS.providers.hasOwnProperty(item)) {
                    providerCheckboxes[item] = {
                        title: WMEUI.t(NAME).providers[item],
                        callback: (event) => this.settings.set('providers', item, event.target.checked),
                        checked: this.settings.get('providers', item),
                    };
                }
            }
            fsProviders.addCheckboxes(providerCheckboxes);
            tab.addElement(fsProviders);
            // Set up provider's keys
            /** @type {WMEUIHelperFieldset} */
            let fsKeys = this.helper.createFieldset(WMEUI.t(NAME).options.keys);
            let keys = this.settings.get('keys');
            for (let item in keys) {
                if (keys.hasOwnProperty(item) && SETTINGS.keys.hasOwnProperty(item)) {
                    fsKeys.addInput('key-' + item, WMEUI.t(NAME).providers[item], (event) => this.settings.set('keys', item, event.target.value), this.settings.get('keys', item));
                }
            }
            tab.addElement(fsKeys);
            tab.addText('info', '<a href="' + GM_info.scriptUpdateURL + '">' + GM_info.script.name + '</a> ' + GM_info.script.version);
            tab.addText('blue', 'made in');
            tab.addText('yellow', 'Ukraine');
            tab.inject();
        }
        initLayer() {
            this.wmeSDK.Map.addLayer({
                layerName: this.name,
                styleRules: layerConfig.styleRules,
                styleContext: layerConfig.styleContext
            });
            // this.wmeSDK.LayerSwitcher.addLayerCheckbox({ name: this.name });
            this.wmeSDK.Map.setLayerZIndex({ layerName: this.name, zIndex: 9999 });
            this.wmeSDK.Map.setLayerVisibility({ layerName: this.name, visibility: false });
        }
        /**
         * Create the vector from the center of the selected POI to point by lon and lat
         * @param {Number} lon
         * @param {Number} lat
         */
        createVector(lon, lat) {
            let poi = this.getSelectedPOI();
            if (!poi) {
                return;
            }
            const from = turf.centroid(poi.geometry);
            const to = turf.point([lon, lat], { styleName: "styleNode" }, { id: `node_${lon}_${lat}` });
            this.wmeSDK.Map.addFeatureToLayer({ layerName: this.name, feature: to });
            const lineCoordinates = [
                from.geometry.coordinates,
                to.geometry.coordinates,
            ];
            const distance = Math.round(turf.distance(to, from) * 1000);
            const label = (distance > 2000)
                ? (distance / 1000).toFixed(1) + 'km'
                : distance + 'm';
            // https://www.waze.com/editor/sdk/interfaces/index.SDK.FeatureStyle.html
            const line = turf.lineString(lineCoordinates, {
                styleName: "styleLine",
                style: {
                    label: label,
                },
            }, { id: `line_${lon}_${lat}` });
            this.wmeSDK.Map.addFeatureToLayer({ layerName: this.name, feature: line });
        }
        /**
         * Remove all vectors from the layer
         */
        removeVectors() {
            this.wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: this.name });
        }
        /**
         * Show the Layer
         */
        showLayer() {
            this.wmeSDK.Map.setLayerVisibility({ layerName: this.name, visibility: true });
        }
        /**
         * Hide the Layer
         */
        hideLayer() {
            this.wmeSDK.Map.setLayerVisibility({ layerName: this.name, visibility: false });
        }
        /**
         * Handler for `none.wme` event
         * @param {jQuery.Event} event
         * @return {Null}
         */
        onNone(event) {
            if (this.settings.get('options', 'modal')) {
                this.modal.remove();
            }
        }
        /**
         * Handler for `venue.wme` event
         *  - create and fill the modal panel
         *
         * @param {jQuery.Event} event
         * @param {HTMLElement} element
         * @param {Venue} model
         * @return {null|void}
         */
        onVenue(event, element, model) {
            let container, parent;
            if (this.settings.get('options', 'modal')) {
                parent = this.modal.html();
                container = parent.querySelector('.wme-ui-modal-content');
            }
            else {
                parent = this.panel.html();
                container = parent.querySelector('.wme-ui-panel-content');
            }
            // Clear container
            try {
                if (container)
                    while (container.hasChildNodes()) {
                        container.removeChild(container.lastChild);
                    }
            }
            catch (e) {
                console.error(e);
            }
            if (!model) {
                return;
            }
            let feature = turf.centroid(model.geometry);
            let [lon, lat] = feature.geometry.coordinates;
            let providers = [];
            let country = this.wmeSDK.DataModel.Countries.getTopCountry()?.id || 232;
            let settings = LOCALE[country] || { country: 'en', language: 'en', locale: 'en_US' };
            this.group('\u{1F4CD}' + lon + ' ' + lat);
            let radius = this.settings.get('ranges', 'radius');
            if (this.settings.get('providers', 'magic')) {
                let Magic = new MagicProvider(container, settings, this.settings, this.wmeSDK);
                let providerPromise = Magic
                    .search(lon, lat, radius)
                    .then(() => Magic.render())
                    .catch(() => this.log(':('));
                providers.push(providerPromise);
            }
            if (this.settings.get('providers', 'ua')) {
                let UaAddresses = new UaAddressesProvider(container, settings, this.settings, this.wmeSDK, this.settings.get('keys', 'ua'));
                let providerPromise = UaAddresses
                    .search(lon, lat, radius)
                    .then(() => UaAddresses.render())
                    .catch(() => this.log(':('));
                providers.push(providerPromise);
            }
            if (this.settings.get('providers', 'osm')) {
                let Osm = new OsmProvider(container, settings, this.settings, this.wmeSDK);
                let providerPromise = Osm
                    .search(lon, lat, radius)
                    .then(() => Osm.render())
                    .catch(() => this.log(':('));
                providers.push(providerPromise);
            }
            if (this.settings.get('providers', 'visicom')) {
                let Visicom = new VisicomProvider(container, settings, this.settings, this.wmeSDK, this.settings.get('keys', 'visicom'));
                let providerPromise = Visicom
                    .search(lon, lat, radius)
                    .then(() => Visicom.render())
                    .catch(() => this.log(':('));
                providers.push(providerPromise);
            }
            if (this.settings.get('providers', 'here')) {
                let Here = new HereProvider(container, settings, this.settings, this.wmeSDK, this.settings.get('keys', 'here'));
                let providerPromise = Here
                    .search(lon, lat, radius)
                    .then(() => Here.render())
                    .catch(() => this.log(':('));
                providers.push(providerPromise);
            }
            if (this.settings.get('providers', 'bing')) {
                let Bing = new BingProvider(container, settings, this.settings, this.wmeSDK, this.settings.get('keys', 'bing'));
                let providerPromise = Bing
                    .search(lon, lat, radius)
                    .then(() => Bing.render())
                    .catch(() => this.log(':('));
                providers.push(providerPromise);
            }
            if (this.settings.get('providers', 'google')) {
                let Google = new GoogleProvider(container, settings, this.settings, this.wmeSDK, this.settings.get('keys', 'google'));
                let providerPromise = Google
                    .search(lon, lat, radius)
                    .then(() => Google.render())
                    .catch(() => this.log(':('));
                providers.push(providerPromise);
            }
            if (this.settings.get('options', 'externalProvider')) {
                if (model.externalProviderIds?.length) {
                    let items = element.querySelectorAll('.external-providers-control .external-provider');
                    for (let i = 0; i < model.externalProviderIds.length; i++) {
                        let externalProviderId = model.externalProviderIds[i];
                        let item = items[i];
                        GoogleProvider
                            .makeDetailsRequest(externalProviderId)
                            .then((details) => {
                            let extLat = details.geometry.location.lat();
                            let extLng = details.geometry.location.lng();
                            let distance = turf.distance(turf.point([lon, lat]), turf.point([extLng, extLat]), {
                                units: 'meters'
                            });
                            item.dataset.distance = Math.round(distance);
                            item.dataset.lat = extLat;
                            item.dataset.lon = extLng;
                            if (details.business_status === 'OPERATIONAL') {
                                item.classList.add('external-operational');
                            }
                            else if (details.business_status === 'CLOSED_TEMPORARILY') {
                                item.classList.add('external-closed-temporarily');
                            }
                            else if (details.business_status === 'CLOSED_PERMANENTLY') {
                                item.classList.add('external-closed-permanently');
                            }
                            item.classList.add(this.name + '-external');
                            if (distance > 1000) {
                                item.classList.add('distance-over-1000');
                            }
                            else if (distance > 200) {
                                item.classList.add('distance-over-200');
                            }
                        })
                            .catch(() => { this.log(':('); });
                    }
                }
            }
            Promise
                .all(providers)
                .then(() => this.groupEnd());
            if (this.settings.get('options', 'modal')) {
                if (this.settings.get('options', 'transparent')) {
                    parent.style.opacity = '0.6';
                    parent.onmouseover = () => (parent.style.opacity = '1');
                    parent.onmouseout = () => (parent.style.opacity = '0.6');
                }
                this.modal.container().append(parent);
            }
            else {
                element.prepend(parent);
            }
        }
        /**
         * Get Selected Venue if it not the NATURAL_FEATURES
         * @return {null|Object}
         */
        getSelectedPOI() {
            let venue = this.getSelectedVenues().shift();
            if (!venue) {
                return null;
            }
            let except = [
                'CAMPING_TRAILER_PARK',
                'FOREST_GROVE',
                'JUNCTION_INTERCHANGE',
                'NATURAL_FEATURES',
                'OUTDOORS',
                'PARKING_LOT',
                'PLAYGROUND',
            ];
            if (except.indexOf(venue.categories[0]) === -1) {
                return venue;
            }
            return null;
        }
        /**
         * Apply data to the current selected place
         * @param {Object} data
         */
        applyData(data) {
            let venue = this.getSelectedPOI();
            if (!this.canEditVenue(venue)) {
                this.log('You don\'t have permissions to edit this venue');
                return;
            }
            let address = this.wmeSDK.DataModel.Venues.getAddress({ venueId: venue.id });
            let lat = parseFloat(data.lat);
            let lon = parseFloat(data.lon);
            if (isNaN(lat) || isNaN(lon)) {
                this.log('Invalid coordinates');
                return;
            }
            this.group('Apply data to selected Venue \u2193');
            let name = data.name ? data.name.trim() : '';
            let cityId = isNaN(parseInt(data.cityId)) ? null : parseInt(data.cityId);
            let cityName = data.cityName ? data.cityName.trim() : '';
            let streetId = isNaN(parseInt(data.streetId)) ? null : parseInt(data.streetId);
            let streetName = data.streetName ? data.streetName.trim() : '';
            let number = data.number ? data.number.trim() : '';
            if (this.settings.get('options', 'copyData')) {
                toClipboard([name, number, streetName, cityName].filter(x => !!x).join(' '));
            }
            // Apply new Name
            let newName;
            // If exists, ask the user to replace it or not
            // If not exists - use name or house number as name
            if (venue.name) {
                this.log('The Venue has a Name \u00AB' + venue.name + '\u00BB');
                if (name && name !== venue.name) {
                    this.log('Replace a Venue Name with a new one?');
                    if (window.confirm(WMEUI.t(NAME).questions.changeName + '\n\u00AB' + venue.name + '\u00BB \u27F6 \u00AB' + name + '\u00BB?')) {
                        newName = name;
                        this.log(' \u2014 Yes, a new Venue Name is \u00AB' + newName + '\u00BB');
                    }
                    else {
                        newName = venue.name;
                        this.log(' \u2014 No, use a old Venue Name \u00AB' + newName + '\u00BB');
                    }
                }
                else if (number && number !== venue.name) {
                    this.log('Replace the Venue Name with a number?');
                    if (window.confirm(WMEUI.t(NAME).questions.changeName + '\n\u00AB' + venue.name + '\u00BB \u27F6 \u00AB' + number + '\u00BB?')) {
                        newName = number;
                        this.log(' \u2014 Yes, a new Venue Name is \u00AB' + newName + '\u00BB');
                    }
                    else {
                        newName = venue.name;
                        this.log(' \u2014 No, use a old Venue Name \u00AB' + newName + '\u00BB');
                    }
                }
            }
            else if (name) {
                newName = name;
                this.log('Use a new Venue Name \u00AB' + newName + '\u00BB');
            }
            else if (number) {
                newName = number;
                this.log('Use a new Venue Name \u00AB' + newName + '\u00BB');
                // Update alias for korpus
                if ((new RegExp('[0-9]+[\u0430-\u044F\u0456]?\u043A[0-9]+', 'i')).test(number)) {
                    let alias = number.replace('\u043A', ' \u043A\u043E\u0440\u043F\u0443\u0441 ');
                    let aliases = venue.aliases?.slice() || [];
                    if (aliases.indexOf(alias) === -1) {
                        aliases.push(alias);
                        this.log('Apply a new Venue Alias \u00AB' + alias + '\u00BB');
                        this.wmeSDK.DataModel.Venues.updateVenue({
                            venueId: venue.id,
                            aliases: aliases
                        });
                    }
                }
            }
            // Set only really new name
            if (newName && newName !== venue.name) {
                this.log('Apply a new Venue Name \u00AB' + newName + '\u00BB');
                this.wmeSDK.DataModel.Venues.updateVenue({
                    venueId: venue.id,
                    name: newName
                });
            }
            // Apply a City name
            if (!cityId && cityName) {
                this.log('We don\'t find a City with name \u00AB' + cityName + '\u00BB, create a new one?');
                // Ask to create a new City
                if (window.confirm(WMEUI.t(NAME).questions.notFoundCity + '\n\u00AB' + cityName + '\u00BB?')) {
                    cityId = this.getCity(cityName).id;
                    this.log(' \u2014 Yes, create new City \u00AB' + cityName + '\u00BB');
                }
                else {
                    cityId = this.getCity().id;
                    this.log(' \u2014 No, use the empty City with ID \u00AB' + cityId + '\u00BB');
                }
            }
            else if (!cityId && !cityName) {
                cityId = this.getCity().id;
                this.log('We don\'t find a City and use the empty City with ID \u00AB' + cityId + '\u00BB');
            }
            let city = this.getCityById(cityId);
            let newStreetId;
            // Apply a new Street
            if (streetId && address.street
                && streetId !== address.street.id
                && '' !== address.street.name) {
                this.log('Replace the Street with a new one?');
                if (window.confirm(WMEUI.t(NAME).questions.changeStreet + '\n\u00AB' + address.street.name + '\u00BB \u27F6 \u00AB' + streetName + '\u00BB?')) {
                    newStreetId = streetId;
                    this.log(' \u2014 Yes, use a new Street Name \u00AB' + streetName + '\u00BB');
                }
                else {
                    this.log(' \u2014 No, use a old Street Name \u00AB' + address.street.name + '\u00BB');
                }
            }
            else if (streetId) {
                newStreetId = streetId;
                this.log('Use a new Street with ID \u00AB' + newStreetId + '\u00BB');
            }
            else if (!streetId) {
                let street;
                if (streetName) {
                    this.log('We don\'t find the street \u00AB' + streetName + '\u00BB');
                    this.log('Create a new Street?');
                    if (window.confirm(WMEUI.t(NAME).questions.notFoundStreet + '\n\u00AB' + streetName + '\u00BB?')) {
                        street = this.getStreet(city.id, streetName);
                        this.log(' \u2014 Yes, create a new Street \u00AB' + streetName + '\u00BB');
                    }
                    else if ('' !== address.street?.name) {
                        street = this.wmeSDK.DataModel.Streets.getById({ streetId: address.street.id });
                        this.log(' \u2014 No, use the current Street \u00AB' + street.name + '\u00BB');
                    }
                    else {
                        street = this.getStreet(city.id, '');
                        this.log(' \u2014 No, use the empty Street with ID \u00AB' + street.id + '\u00BB');
                    }
                }
                else {
                    this.log('We don\'t find the street');
                    street = this.getStreet(city.id, '');
                    this.log('Use the empty Street with ID \u00AB' + street.id + '\u00BB');
                }
                if (street.id !== address.street?.id && '' !== address.street?.name) {
                    this.log('Replace the Street with new one?');
                    if (window.confirm(WMEUI.t(NAME).questions.changeStreet + '\n\u00AB' + address.street.name + '\u00BB \u27F6 \u00AB' + streetName + '\u00BB?')) {
                        newStreetId = street.id;
                        this.log(' \u2014 Yes, use a new Street Name \u00AB' + streetName + '\u00BB');
                    }
                    else {
                        this.log(' \u2014 No, use the current Street Name \u00AB' + address.street.name + '\u00BB');
                    }
                }
                else {
                    newStreetId = street.id;
                }
            }
            if (newStreetId && newStreetId !== address.street?.id) {
                this.log('Apply a new Street ID \u00AB' + newStreetId + '\u00BB');
                this.wmeSDK.DataModel.Venues.updateAddress({
                    venueId: venue.id,
                    streetId: newStreetId
                });
            }
            let newHouseNumber;
            // Apply a House Number
            if (number) {
                if (address.houseNumber) {
                    this.log('Replace the House Number with a new one?');
                    if (address.houseNumber !== number &&
                        window.confirm(WMEUI.t(NAME).questions.changeNumber + '\n\u00AB' + address.houseNumber + '\u00BB \u27F6 \u00AB' + number + '\u00BB?')) {
                        newHouseNumber = number;
                        this.log(' \u2014 Yes, use a new House Number \u00AB' + number + '\u00BB');
                    }
                    else {
                        this.log(' \u2014 No, use the current House Number \u00AB' + address.houseNumber + '\u00BB');
                    }
                }
                else {
                    newHouseNumber = number;
                    this.log('Use a new House Number \u00AB' + number + '\u00BB');
                }
            }
            if (newHouseNumber) {
                this.log('Apply a new House Number \u00AB' + newHouseNumber + '\u00BB');
                this.wmeSDK.DataModel.Venues.updateAddress({
                    venueId: venue.id,
                    houseNumber: newHouseNumber
                });
            }
            // Lock to level 2
            if (this.settings.get('options', 'lock')
                && venue.lockRank < 1
                && this.wmeSDK.State.getUserInfo().rank > 0) {
                this.log('Apply a new Lock Rank \u00AB' + (1 + 1) + '\u00BB');
                this.wmeSDK.DataModel.Venues.updateVenue({
                    venueId: venue.id,
                    lockRank: 1
                });
            }
            // If no an entry point, we would create it
            if (this.settings.get('options', 'entryPoint')
                && venue.navigationPoints?.length === 0) {
                this.log('Create a Navigation Point');
                let point = turf.point([lon, lat]);
                if (venue.geometry.type === 'Point') {
                    this.log('Use the coordinates for new Navigation Point for Point');
                }
                else if (turf.pointsWithinPolygon(point, venue.geometry).features?.length > 0) {
                    this.log('Use the coordinates for new Navigation Point inside Polygon');
                }
                else {
                    // point is outside the venue geometry
                    this.log('Use the intersection of Polygon and vector to coordinates as new Navigation Point');
                    let centroid = turf.centroid(venue.geometry);
                    let line = turf.lineString([
                        centroid.geometry.coordinates,
                        point.geometry.coordinates,
                    ]);
                    let featureCollection = turf.lineIntersect(venue.geometry, line);
                    point = featureCollection.features?.pop();
                }
                // create a navigation point
                let navigationPoint = {
                    isEntry: true,
                    isExit: false,
                    isPrimary: true,
                    name: "",
                    point: point.geometry
                };
                this.log('Apply a new Navigation Point');
                this.wmeSDK.DataModel.Venues.replaceNavigationPoints({
                    venueId: venue.id,
                    navigationPoints: [navigationPoint]
                });
            }
            this.groupEnd();
        }
        getCityById(cityID) {
            if (!cityID || isNaN(parseInt(cityID))) {
                return null;
            }
            return this.wmeSDK.DataModel.Cities.getById({
                cityId: cityID
            });
        }
        getCity(cityName = '') {
            return this.wmeSDK.DataModel.Cities.getCity({
                countryId: this.wmeSDK.DataModel.Countries.getTopCountry().id,
                cityName: cityName
            })
                || this.wmeSDK.DataModel.Cities.addCity({
                    countryId: this.wmeSDK.DataModel.Countries.getTopCountry().id,
                    cityName: cityName
                });
        }
        getStreet(cityId, streetName = '') {
            return this.wmeSDK.DataModel.Streets.getStreet({
                cityId: cityId,
                streetName: streetName,
            })
                || this.wmeSDK.DataModel.Streets.addStreet({
                    cityId: cityId,
                    streetName: streetName
                });
        }
    }
    /**
     * Copy to clipboard
     */
    function toClipboard(text) {
        // normalize
        text = normalizeString(text);
        text = text.replace(/'/g, '');
        GM.setClipboard(text);
        console.log('%c' + NAME + ': %cCopied \u00AB' + text + '\u00BB to the clipboard', 'color: #0DAD8D; font-weight: bold', 'color: dimgray; font-weight: normal');
    }
    /**
     * Normalize the string:
     *  - remove the double quotes
     *  - remove double space
     */
    function normalizeString(str) {
        // Clear space symbols and double quotes
        str = str.trim()
            .replace(/["\u201C\u201D]/g, '')
            .replace(/\s{2,}/g, ' ');
        return str;
    }

    let E50Instance;
    function setE50Instance(instance) {
        E50Instance = instance;
    }
    /**
     * Apply data to the current selected POI
     */
    function applyData(event) {
        event.preventDefault();
        E50Instance.applyData(event.target.dataset);
    }
    /**
     * Create the vector from the center of the selected POI to point by lon and lat
     */
    function showLayer(event) {
        const lon = parseFloat(event.target.dataset.lon);
        const lat = parseFloat(event.target.dataset.lat);
        E50Instance.createVector(lon, lat);
        E50Instance.showLayer();
    }
    /**
     * Remove all vectors and hide the layer
     */
    function hideLayer() {
        E50Instance.removeVectors();
        E50Instance.hideLayer();
    }

    var css_248z = ".wme-ui-modal.e50 .wme-ui-modal-header h5 {\n  padding: 16px 16px 0;\n  font-size: 16px;\n}\n\n.wme-ui-modal.e50 .wme-ui-modal-content {\n  overflow-x: auto;\n  max-height: 420px;\n  padding: 4px 0;\n}\n\n#venue-edit-general .e50 fieldset {\n  border: 0;\n  padding: 0;\n  margin: 0;\n}\n\n#venue-edit-general .e50 legend {\n  width: 100%;\n  text-align: left;\n}\n\n#venue-edit-general .e50 fieldset legend,\n.wme-ui-panel.e50 fieldset legend,\n.wme-ui-modal.e50 fieldset legend {\n  cursor: pointer;\n  font-size: 12px;\n  font-weight: bold;\n  margin: 0;\n  padding: 0 8px;\n  background-color: #f6f7f7;\n  border: 1px solid #e5e5e5;\n}\n\n#venue-edit-general .e50 fieldset legend::after,\n.wme-ui-panel.e50 fieldset legend::after,\n.wme-ui-modal.e50 fieldset legend::after {\n  display: inline-block;\n  text-rendering: auto;\n  content: \"\\2191\";\n  float: right;\n  font-size: 10px;\n  line-height: inherit;\n  position: relative;\n  right: 3px;\n}\n\n#venue-edit-general .e50 fieldset legend span,\n.wme-ui-panel.e50 fieldset legend span,\n.wme-ui-modal.e50 fieldset legend span {\n  font-weight: bold;\n  background-color: #fff;\n  border-radius: 5px;\n  color: #ed503b;\n  display: inline-block;\n  font-size: 12px;\n  line-height: 14px;\n  max-width: 30px;\n  padding: 1px 5px;\n  text-align: center;\n}\n\n#venue-edit-general .e50 fieldset ul,\n.wme-ui-panel.e50 fieldset ul,\n.wme-ui-modal.e50 fieldset ul {\n  border: 1px solid #ddd;\n}\n\n#venue-edit-general .e50 fieldset.collapsed ul,\n.wme-ui-panel.e50 fieldset.collapsed ul,\n.wme-ui-modal.e50 fieldset.collapsed ul {\n  display: none;\n}\n\n#venue-edit-general .e50 fieldset.collapsed legend::after,\n.wme-ui-panel.e50 fieldset.collapsed legend::after,\n.wme-ui-modal.e50 fieldset.collapsed legend::after {\n  content: \"\\2193\";\n}\n\n#venue-edit-general .e50 ul,\n.wme-ui-panel.e50 ul,\n.wme-ui-modal.e50 ul {\n  padding: 8px;\n  margin: 0;\n}\n\n#venue-edit-general .e50 li,\n.wme-ui-panel.e50 li,\n.wme-ui-modal.e50 li {\n  padding: 0;\n  margin: 0;\n  list-style: none;\n  margin-bottom: 2px;\n}\n\n#venue-edit-general .e50 li a,\n.wme-ui-panel.e50 li a,\n.wme-ui-modal.e50 li a {\n  display: block;\n  padding: 2px 4px;\n  text-decoration: none;\n  border: 1px solid #e4e4e4;\n}\n\n#venue-edit-general .e50 li a:hover,\n.wme-ui-panel.e50 li a:hover,\n.wme-ui-modal.e50 li a:hover {\n  background: rgba(255, 255, 200, 1);\n}\n\n#venue-edit-general .e50 li a.nonumber,\n.wme-ui-panel.e50 li a.nonumber,\n.wme-ui-modal.e50 li a.nonumber {\n  background: rgba(250, 250, 200, 0.5);\n}\n\n#venue-edit-general .e50 li a.nonumber:hover,\n.wme-ui-panel.e50 li a.nonumber:hover,\n.wme-ui-modal.e50 li a.nonumber:hover {\n  background: rgba(250, 250, 200, 1);\n}\n\n#venue-edit-general .e50 li a.noaddress,\n.wme-ui-panel.e50 li a.noaddress,\n.wme-ui-modal.e50 li a.noaddress {\n  background: rgba(250, 200, 100, 0.5);\n}\n\n#venue-edit-general .e50 li a.noaddress:hover,\n.wme-ui-panel.e50 li a.noaddress:hover,\n.wme-ui-modal.e50 li a.noaddress:hover {\n  background: rgba(250, 200, 100, 1);\n}\n\n.wme-ui-panel.e50 .wme-ui-fieldset-content:empty,\n.wme-ui-modal.e50 .wme-ui-modal-content:empty {\n  min-height: 20px;\n}\n\n.wme-ui-panel.e50 .wme-ui-fieldset-content:empty::after,\n.wme-ui-modal.e50 .wme-ui-modal-content:empty::after {\n  color: #ccc;\n  padding: 0 8px;\n  content: \"\\2014\";\n}\n\n.wme-ui-panel.e50 .wme-ui-fieldset-content label {\n  margin-top: 5px;\n  line-height: 18px;\n}\n\n.wme-ui-panel.e50 .wme-ui-fieldset-content input[type=\"text\"] {\n  float: right;\n}\n\n.distance-over-200 {\n  background-color: #f08a24;\n}\n\n.distance-over-1000 {\n  background-color: #ed503b;\n}\n\n.external-operational a.url {\n  border: 4px solid #009900;\n  border-radius: 50%;\n}\n\n.external-closed-temporarily a.url {\n  border: 4px solid #ff7300;\n  border-radius: 50%;\n}\n\n.external-closed-permanently a.url {\n  border: 4px solid #ff0000;\n  border-radius: 50%;\n}\n\n.e50 .wme-ui-tab-content {\n  padding: 8px;\n}\n\np.e50-info {\n  border-top: 1px solid #ccc;\n  color: #777;\n  font-size: x-small;\n  margin-top: 15px;\n  padding-top: 10px;\n  text-align: center;\n}\n\n#sidebar p.e50-blue {\n  background-color: #0057B8;\n  color: white;\n  height: 32px;\n  text-align: center;\n  line-height: 32px;\n  font-size: 24px;\n  margin: 0;\n}\n\n#sidebar p.e50-yellow {\n  background-color: #FFDD00;\n  color: black;\n  height: 32px;\n  text-align: center;\n  line-height: 32px;\n  font-size: 24px;\n  margin: 0;\n}\n";

    $(document)
        .on('bootstrap.wme', () => {
        WMEUI.addTranslation(NAME, TRANSLATION);
        WMEUI.addStyle(css_248z);
        let scriptSettings = new Settings(NAME, SETTINGS);
        let instance = new E50(NAME, scriptSettings);
        setE50Instance(instance);
        setCache(new Container());
    })
        .on('click', '.' + NAME + '-link', applyData)
        .on('mouseenter', '.' + NAME + '-link', showLayer)
        .on('mouseleave', '.' + NAME + '-link', hideLayer)
        .on('mouseenter', '.' + NAME + '-external', showLayer)
        .on('mouseleave', '.' + NAME + '-external', hideLayer)
        .on('none.wme', hideLayer);

})();