WME E50 Fetch POI Data

Fetch information about the POI from external sources

安装此脚本
作者推荐脚本

您可能也喜欢WME E95

安装此脚本
  1. // ==UserScript==
  2. // @name WME E50 Fetch POI Data
  3. // @name:uk WME 🇺🇦 E50 Fetch POI Data
  4. // @version 0.10.20
  5. // @description Fetch information about the POI from external sources
  6. // @description:uk Скрипт дозволяє отримувати інформацію про POI зі сторонніх ресурсів
  7. // @license MIT License
  8. // @author Anton Shevchuk
  9. // @namespace https://greasyfork.org/users/227648-anton-shevchuk
  10. // @supportURL https://github.com/AntonShevchuk/wme-e50/issues
  11. // @match https://*.waze.com/editor*
  12. // @match https://*.waze.com/*/editor*
  13. // @exclude https://*.waze.com/user/editor*
  14. // @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
  15. // @connect api.here.com
  16. // @connect api.visicom.ua
  17. // @connect nominatim.openstreetmap.org
  18. // @connect catalog.api.2gis.com
  19. // @connect dev.virtualearth.net
  20. // @connect maps.googleapis.com
  21. // @connect stat.waze.com.ua
  22. // @grant GM.xmlHttpRequest
  23. // @grant GM.setClipboard
  24. // @require https://update.greasyfork.org/scripts/389765/1090053/CommonUtils.js
  25. // @require https://update.greasyfork.org/scripts/450160/1619452/WME-Bootstrap.js
  26. // @require https://update.greasyfork.org/scripts/452563/1218878/WME.js
  27. // @require https://update.greasyfork.org/scripts/450221/1137043/WME-Base.js
  28. // @require https://update.greasyfork.org/scripts/450320/1555446/WME-UI.js
  29. // @require https://update.greasyfork.org/scripts/480123/1281900/WME-EntryPoint.js
  30. // ==/UserScript==
  31.  
  32. /* jshint esversion: 8 */
  33. /* global require */
  34. /* global $, jQuery, jQuery.Event */
  35. /* global W, W.model */
  36. /* global I18n */
  37. /* global OpenLayers */
  38. /* global NavigationPoint */
  39. /* global WME, WMEBase, WMEUI, WMEUIHelper, WMEUIHelperFieldset */
  40. /* global Container, Settings, SimpleCache, Tools */
  41.  
  42. (function () {
  43. 'use strict'
  44.  
  45. let vectorPoint, vectorLine
  46.  
  47. const NAME = 'E50'
  48.  
  49. // translation structure
  50. const TRANSLATION = {
  51. 'en': {
  52. title: 'Information 📍',
  53. notFound: 'Not found',
  54. options: {
  55. title: 'Options',
  56. modal: 'Use modal window',
  57. transparent: 'Transparent modal window',
  58. entryPoint: 'Create Entry Point if not exists',
  59. copyData: 'Copy POI data to clipboard on click',
  60. lock: 'Lock POI to 2 level',
  61. keys: 'API keys',
  62. },
  63. ranges: {
  64. title: 'Additional',
  65. collapse: 'Collapse the lists longer than',
  66. },
  67. providers: {
  68. title: 'Providers',
  69. magic: 'Closest Segment',
  70. osm: 'Open Street Map',
  71. gis: '2GIS',
  72. bing: 'Bing',
  73. here: 'HERE',
  74. google: 'Google',
  75. visicom: 'Visicom',
  76. ua: 'UA Addresses',
  77. },
  78. questions: {
  79. changeName: 'Are you sure to change the name?',
  80. changeCity: 'Are you sure to change the city?',
  81. changeStreet: 'Are you sure to change the street name?',
  82. changeNumber: 'Are you sure to change the house number?',
  83. notFoundCity: 'City not found in the current location, are you sure to apply this city name?',
  84. notFoundStreet: 'Street not found in the current location, are you sure to apply this street name?'
  85. }
  86. },
  87. 'uk': {
  88. title: 'Інформація 📍',
  89. notFound: 'Нічого не знайдено',
  90. options: {
  91. title: 'Налаштування',
  92. modal: 'Використовувати окрему панель',
  93. transparent: 'Напівпрозора панель',
  94. entryPoint: 'Створювати точку в\'їзду, якщо відсутня',
  95. copyData: 'При виборі, копіювати до буферу обміну назву та адресу POI',
  96. lock: 'Блокувати POI 2-м рівнем',
  97. keys: 'Ключі до API',
  98. },
  99. ranges: {
  100. title: 'Додаткові',
  101. collapse: 'Складати перелік, більший за',
  102. },
  103. providers: {
  104. title: 'Джерела',
  105. magic: 'Найближчий сегмент',
  106. osm: 'Open Street Map',
  107. gis: '2GIS',
  108. bing: 'Bing',
  109. here: 'HERE',
  110. google: 'Google',
  111. visicom: 'Візіком',
  112. ua: 'UA Адреси',
  113. },
  114. questions: {
  115. changeName: 'Ви впевненні що хочете змінити им\'я?',
  116. changeCity: 'Ви впевненні що хочете змінити місто?',
  117. changeStreet: 'Ви впевненні що хочете змінити вулицю?',
  118. changeNumber: 'Ви впевненні що хочете змінити номер дома?',
  119. notFoundCity: 'Ми не знайшли такого міста у поточному місці, ви впевнені, що його треба застосувати?',
  120. notFoundStreet: 'Ми не знайшли таку вулицю у поточному місці, ви впевнені, що треба її додати?',
  121. }
  122. },
  123. 'ru': {
  124. title: 'Информация 📍',
  125. notFound: 'Ничего не найдено',
  126. options: {
  127. title: 'Настройки',
  128. modal: 'Использовать отдельную панель',
  129. transparent: 'Полупрозрачная панель',
  130. entryPoint: 'Создавать точку въезда если отсутствует',
  131. copyData: 'При виборе, копировать в буфер обмена название и адрес POI',
  132. lock: 'Блокировать POI 2-м уровнем',
  133. keys: 'Ключи к API',
  134. },
  135. ranges: {
  136. title: 'Дополнительно',
  137. collapse: 'Складывать списки, которые больше',
  138. },
  139. providers: {
  140. title: 'Источники',
  141. magic: 'Ближайший сегмент',
  142. osm: 'Open Street Map',
  143. gis: '2GIS',
  144. bing: 'Bing',
  145. here: 'HERE',
  146. google: 'Google',
  147. visicom: 'Визиком',
  148. ua: 'UA Адреса',
  149. },
  150. questions: {
  151. changeName: 'Ви уверены, что хотите изменить имя?',
  152. changeCity: 'Ви уверены, что хотите изменить город?',
  153. changeStreet: 'Ви уверены, что хотите изменить улицу?',
  154. changeNumber: 'Ви уверены, что хотите изменить номер дома?',
  155. notFoundCity: 'Мы не нашли такого города в данной локации, вы уверены что нужно его добавить?',
  156. notFoundStreet: 'Мы не нашли такую улицу в данной локации, вы уверены что нужно её добавить?',
  157. }
  158. },
  159. 'fr': {
  160. title: 'Informations 📍',
  161. notFound: 'Lieu inconnu',
  162. options: {
  163. title: 'Réglages',
  164. modal: 'Activer la fenêtre',
  165. transparent: 'Fenêtre transparente',
  166. entryPoint: 'Créer le point d\'entrée s\'il n\'existe pas',
  167. copyData: 'Copier les informations du POI en cliquant',
  168. lock: 'Verrouiller le POI au niveau 2',
  169. keys: 'API keys',
  170. },
  171. ranges: {
  172. title: 'Supplémentaire',
  173. collapse: 'Réduire les listes plus grandes que',
  174. },
  175. providers: {
  176. title: 'Sources',
  177. magic: 'Au plus proche du segment',
  178. osm: 'Open Street Map',
  179. gis: '2GIS',
  180. bing: 'Bing',
  181. here: 'HERE',
  182. google: 'Google',
  183. visicom: 'Visicom',
  184. ua: 'UA Addresses',
  185. },
  186. questions: {
  187. changeName: 'Êtes-vous sûr de changer le nom ?',
  188. changeCity: 'Êtes-vous sûr de changer la ville ?',
  189. changeStreet: 'Êtes-vous sûr de changer la rue ?',
  190. changeNumber: 'Êtes-vous sûr de changer le numéro de rue ?',
  191. notFoundCity: 'City not found in the current location, are you sure to apply this city name?',
  192. notFoundStreet: 'Street not found in the current location, are you sure to apply this street name?'
  193. }
  194. }
  195. }
  196.  
  197. const SETTINGS = {
  198. options: {
  199. modal: true,
  200. transparent: false,
  201. entryPoint: true,
  202. copyData: true,
  203. lock: true,
  204. },
  205. ranges: {
  206. collapse: 3,
  207. },
  208. providers: {
  209. magic: true,
  210. osm: false,
  211. gis: false,
  212. bing: false,
  213. here: false,
  214. google: true,
  215. visicom: false,
  216. ua: false,
  217. },
  218. keys: {
  219. // Russian warship, go f*ck yourself!
  220. visicom: 'da' + '0110' + 'e25fac44b1b9c849296387dba8',
  221. gis: 'rubnkm' + '7490',
  222. here: 'GCFmOOrSp8882vFwTxEm' + ':' + 'O-LgGkoRfypnRuik0WjX9A',
  223. bing: 'AuBfUY8Y1Nzf' + '3sRgceOYxaIg7obOSaqvs' + '0k5dhXWfZyFpT9ArotYNRK7DQ_qZqZw',
  224. google: 'AIzaSyBWB3' + 'jiUm1dkFwvJWy4w4ZmO7K' + 'PyF4oUa0', // extract it from WME
  225. ua: 'E50'
  226. }
  227. }
  228.  
  229. const LOCALE = {
  230. // Ukraine
  231. 232: {
  232. country: 'uk',
  233. language: 'ua',
  234. locale: 'uk_UA'
  235. }
  236. }
  237.  
  238. // Road Types
  239. // I18n.translations.uk.segment.road_types
  240. // I18n.translations.en.segment.road_types
  241. const TYPES = {
  242. street: 1,
  243. primary: 2,
  244. freeway: 3,
  245. ramp: 4,
  246. trail: 5,
  247. major: 6,
  248. minor: 7,
  249. offroad: 8,
  250. walkway: 9,
  251. boardwalk: 10,
  252. ferry: 15,
  253. stairway: 16,
  254. private: 17,
  255. railroad: 18,
  256. runway: 19,
  257. parking: 20,
  258. narrow: 22
  259. }
  260.  
  261. WMEUI.addTranslation(NAME, TRANSLATION)
  262.  
  263. // OpenLayer styles
  264. const STYLE =
  265. '.e50 .header h5 { padding: 16px 16px 0; font-size: 16px }' +
  266. '.e50 .body { overflow-x: auto; max-height: 420px; padding: 4px 0; }' +
  267.  
  268. '.e50 .button-toolbar legend { border: 1px solid #e5e5e5; width: 94%; margin: 0 auto; } ' +
  269. '.e50 .button-toolbar fieldset { margin-bottom: 8px } ' +
  270.  
  271. '.e50 fieldset { border: 1px solid #ddd; }' +
  272. '.e50 fieldset legend { cursor:pointer; font-size: 12px; font-weight: bold; margin: 0; padding: 0 8px; background-color: #f6f7f7; border-top: 1px solid #e5e5e5; }' +
  273. '.e50 fieldset legend::after { display: inline-block; text-rendering: auto; content: ""; float: right; font-size: 10px; line-height: inherit; position: relative; right: 3px; } ' +
  274. '.e50 fieldset.collapsed legend::after { content: "" }' +
  275. '.e50 fieldset.collapsed ul { display: none } ' +
  276. '.e50 fieldset legend span { font-weight: bold; background-color: #fff; border-radius: 5px; color: #ed503b; display: inline-block; font-size: 12px; line-height: 14px; max-width: 30px; padding: 1px 5px; text-align: center; } ' +
  277.  
  278. '.e50 ul { padding: 8px; margin: 0 }' +
  279. '.e50 li { padding: 0; margin: 0; list-style: none; margin-bottom: 2px }' +
  280. '.e50 li a { display: block; padding: 2px 4px; text-decoration: none; border: 1px solid #e4e4e4; }' +
  281. '.e50 li a:hover { background: rgba(255, 255, 200, 1) }' +
  282. '.e50 li a.noaddress { background: rgba(255, 200, 200, 0.5) }' +
  283. '.e50 li a.noaddress:hover { background: rgba(255, 200, 200, 1) }' +
  284.  
  285. '.e50 div.controls { padding: 8px; }' +
  286. '.e50 div.controls:empty, #panel-container .archive-panel .body:empty { min-height: 20px; }' +
  287. '.e50 div.controls:empty::after, #panel-container .archive-panel .body:empty::after { color: #ccc; padding: 0 8px; content: "' + I18n.t(NAME).notFound + '" }' +
  288. '.e50 div.controls label { white-space: normal; font-weight: 400; margin-top: 5px; }' +
  289. '.e50 div.controls input[type="text"] { float:right; }' +
  290.  
  291. '.e50 .e50-collapse label, .e50 .e50-collapse label { font-weight: 400 }' +
  292. '.e50 .e50-collapse label::after { content: attr(data-after); display: inline-block; padding: 2px; margin: 2px; }' +
  293. '.e50 .e50-collapse label::after { content: attr(data-after); display: inline-block; padding: 2px; margin: 2px; }' +
  294.  
  295. 'p.e50-info { border-top: 1px solid #ccc; color: #777; font-size: x-small; margin-top: 15px; padding-top: 10px; text-align: center; }'
  296.  
  297. WMEUI.addStyle(STYLE)
  298.  
  299. let WazeActionUpdateObject
  300. let WazeActionUpdateFeatureAddress
  301.  
  302. let E50Instance, E50Cache, vectorLayer
  303.  
  304. class E50 extends WMEBase {
  305. constructor (name, settings) {
  306. super(name, settings)
  307.  
  308. this.helper = new WMEUIHelper(name)
  309.  
  310. this.modal = this.helper.createModal(I18n.t(name).title)
  311.  
  312. this.panel = this.helper.createPanel(I18n.t(name).title)
  313.  
  314. this.tab = this.helper.createTab(
  315. I18n.t(name).title,
  316. {
  317. image: GM_info.script.icon
  318. }
  319. )
  320.  
  321. // Setup options
  322. /** @type {WMEUIHelperFieldset} */
  323. let fsOptions = this.helper.createFieldset(I18n.t(name).options.title)
  324. for (let item in settings.options) {
  325. if (settings.options.hasOwnProperty(item)) {
  326. fsOptions.addCheckbox(
  327. item,
  328. I18n.t(name).options[item],
  329. (event) => this.settings.set(['options', item], event.target.checked),
  330. this.settings.get('options', item)
  331. )
  332. }
  333. }
  334. this.tab.addElement(fsOptions)
  335.  
  336. // Setup ranges
  337. /** @type {WMEUIHelperFieldset} */
  338. let fsRanges = this.helper.createFieldset(I18n.t(name).ranges.title)
  339. for (let item in settings.ranges) {
  340. if (settings.ranges.hasOwnProperty(item)) {
  341. let range = fsRanges.addRange(
  342. item,
  343. I18n.t(name).ranges[item],
  344. (event) => {
  345. this.settings.set(['ranges', item], event.target.value)
  346. event.target.nextSibling.setAttribute('data-after', event.target.value)
  347. },
  348. this.settings.get('ranges', item),
  349. 0,
  350. 10,
  351. 1
  352. )
  353. range.html()
  354. .getElementsByTagName('label')[0]
  355. .setAttribute('data-after', this.settings.get('ranges', item))
  356.  
  357. }
  358. }
  359. this.tab.addElement(fsRanges)
  360.  
  361. // Setup providers settings
  362. /** @type {WMEUIHelperFieldset} */
  363. let fsProviders = this.helper.createFieldset(I18n.t(name).providers.title)
  364. for (let item in settings.providers) {
  365. if (settings.providers.hasOwnProperty(item)) {
  366. fsProviders.addCheckbox(
  367. item,
  368. I18n.t(NAME).providers[item],
  369. (event) => this.settings.set(['providers', item], event.target.checked),
  370. this.settings.get('providers', item)
  371. )
  372. }
  373. }
  374. this.tab.addElement(fsProviders)
  375.  
  376. // Setup providers key's
  377. /** @type {WMEUIHelperFieldset} */
  378. let fsKeys = this.helper.createFieldset(I18n.t(name).options.keys)
  379. let keys = this.settings.get('keys')
  380. for (let item in keys) {
  381. if (keys.hasOwnProperty(item)) {
  382. fsKeys.addInput(
  383. 'key-' + item,
  384. I18n.t(name).providers[item],
  385. (event) => this.settings.set(['keys', item], event.target.value),
  386. this.settings.get('keys', item)
  387. )
  388. }
  389. }
  390. this.tab.addElement(fsKeys)
  391.  
  392. this.tab.addText(
  393. 'info',
  394. '<a href="' + GM_info.scriptUpdateURL + '">' + GM_info.script.name + '</a> ' + GM_info.script.version
  395. )
  396.  
  397. this.tab.inject()
  398. }
  399.  
  400. /**
  401. * Handler for `none.wme` event
  402. * @param {jQuery.Event} event
  403. * @return {Null}
  404. */
  405. onNone (event) {
  406. if (this.settings.get('options', 'modal')) {
  407. this.modal.html().remove()
  408. }
  409. }
  410.  
  411. /**
  412. * Handler for `venue.wme` event
  413. * - create and fill the modal panel
  414. *
  415. * @param {jQuery.Event} event
  416. * @param {HTMLElement} element
  417. * @param {W.model} model
  418. * @return {null|void}
  419. */
  420. onVenue (event, element, model) {
  421. let container, parent
  422. if (this.settings.get('options', 'modal')) {
  423. parent = this.modal.html()
  424. container = parent.querySelector('.wme-ui-body')
  425. } else {
  426. parent = this.panel.html()
  427. container = parent.querySelector('.controls')
  428. }
  429.  
  430. // Clear container
  431. try {
  432. if (container)
  433. while (container.hasChildNodes()) {
  434. container.removeChild(container.lastChild)
  435. }
  436. } catch (e) {
  437. console.error(e)
  438. }
  439.  
  440. let poi = getSelectedPOI()
  441.  
  442. if (!poi) {
  443. return
  444. }
  445.  
  446. let selected = poi.getOLGeometry().getCentroid().clone()
  447. selected.transform('EPSG:900913', 'EPSG:4326')
  448.  
  449. let providers = []
  450.  
  451. let country = W.model.getTopCountry().getID() // or 232 is Ukraine
  452.  
  453. let settings = LOCALE[country]
  454.  
  455. this.group(
  456. '📍' + selected.x + ' ' + selected.y
  457. )
  458.  
  459. if (this.settings.get('providers', 'magic')) {
  460. let Magic = new MagicProvider(container, settings)
  461. let providerPromise = Magic
  462. .search(selected.x, selected.y)
  463. .then(() => Magic.render())
  464. .catch(() => this.log(':('))
  465. providers.push(providerPromise)
  466. }
  467.  
  468. if (this.settings.get('providers', 'ua')) {
  469. let UaAddresses = new UaAddressesProvider(container, settings, this.settings.get('keys', 'ua'))
  470. let providerPromise = UaAddresses
  471. .search(selected.x, selected.y)
  472. .then(() => UaAddresses.render())
  473. .catch(() => this.log(':('))
  474. providers.push(providerPromise)
  475. }
  476.  
  477. if (this.settings.get('providers', 'osm')) {
  478. let Osm = new OsmProvider(container, settings)
  479. let providerPromise = Osm
  480. .search(selected.x, selected.y)
  481. .then(() => Osm.render())
  482. .catch(() => this.log(':('))
  483. providers.push(providerPromise)
  484. }
  485.  
  486. if (this.settings.get('providers', 'gis')) {
  487. let Gis = new GisProvider(container, settings, this.settings.get('keys', 'gis'))
  488. let providerPromise = Gis
  489. .search(selected.x, selected.y)
  490. .then(() => Gis.render())
  491. .catch(() => this.log(':('))
  492. providers.push(providerPromise)
  493. }
  494.  
  495. if (this.settings.get('providers', 'visicom')) {
  496. let Visicom = new VisicomProvider(container, settings, this.settings.get('keys', 'visicom'))
  497. let providerPromise = Visicom
  498. .search(selected.x, selected.y)
  499. .then(() => Visicom.render())
  500. .catch(() => this.log(':('))
  501. providers.push(providerPromise)
  502. }
  503.  
  504. if (this.settings.get('providers', 'here')) {
  505. let Here = new HereProvider(container, settings, this.settings.get('keys', 'here'))
  506. let providerPromise = Here
  507. .search(selected.x, selected.y)
  508. .then(() => Here.render())
  509. .catch(() => this.log(':('))
  510. providers.push(providerPromise)
  511. }
  512.  
  513. if (this.settings.get('providers', 'bing')) {
  514. let Bing = new BingProvider(container, settings, this.settings.get('keys', 'bing'))
  515. let providerPromise = Bing
  516. .search(selected.x, selected.y)
  517. .then(() => Bing.render())
  518. .catch(() => this.log(':('))
  519. providers.push(providerPromise)
  520. }
  521.  
  522. if (this.settings.get('providers', 'google')) {
  523. let Google = new GoogleProvider(container, settings, this.settings.get('keys', 'google'))
  524. let providerPromise = Google
  525. .search(selected.x, selected.y)
  526. .then(() => Google.render())
  527. .catch(() => this.log(':('))
  528. providers.push(providerPromise)
  529. }
  530.  
  531. Promise
  532. .all(providers)
  533. .then(() => this.groupEnd())
  534.  
  535. if (this.settings.get('options', 'modal')) {
  536. if (this.settings.get('options', 'transparent')) {
  537. parent.style.opacity = '0.6'
  538. parent.onmouseover = () => (parent.style.opacity = '1')
  539. parent.onmouseout = () => (parent.style.opacity = '0.6')
  540. }
  541. this.modal.container().append(parent)
  542. } else {
  543. element.prepend(parent)
  544. }
  545. }
  546. }
  547.  
  548. /**
  549. * Basic Provider class
  550. */
  551. class Provider {
  552. constructor (uid, container, settings) {
  553. this.uid = uid
  554. this.response = []
  555. this.settings = settings
  556. // prepare DOM
  557. this.panel = this._panel()
  558. this.container = container
  559. this.container.append(this.panel)
  560. }
  561.  
  562. /**
  563. * @param {String} url
  564. * @param {Object} data
  565. * @returns {Promise<unknown>}
  566. */
  567. async makeRequest (url, data) {
  568. let query = new URLSearchParams(data).toString()
  569.  
  570. if (query.length) {
  571. url = url + '?' + query
  572. }
  573.  
  574. return new Promise((resolve, reject) => {
  575. GM.xmlHttpRequest({
  576. method: 'GET',
  577. responseType: 'json',
  578. url: url,
  579. onload: response => response && response.response && resolve(response.response) || reject(response),
  580. onabort: response => reject(response),
  581. onerror: response => reject(response),
  582. ontimeout: response => reject(response),
  583. })
  584. })
  585. }
  586.  
  587. /**
  588. * @param {Number} lon
  589. * @param {Number} lat
  590. * @return {Promise<array>}
  591. */
  592. async request (lon, lat) {
  593. throw new Error('Abstract method')
  594. }
  595.  
  596. /**
  597. * @param {Number} lon
  598. * @param {Number} lat
  599. * @return {Promise<void>}
  600. */
  601. async search (lon, lat) {
  602. let key = this.uid + ':' + lon + ',' + lat
  603.  
  604. if (E50Cache.has(key)) {
  605. this.response = E50Cache.get(key)
  606. } else {
  607. this.response = await this.request(lon, lat).catch(e => console.error(this.uid, 'search return error', e))
  608. E50Cache.set(key, this.response)
  609. }
  610.  
  611. return new Promise((resolve, reject) => {
  612. if (this.response) {
  613. resolve()
  614. } else {
  615. reject()
  616. }
  617. })
  618. }
  619.  
  620. /**
  621. * @param {Array} res
  622. * @return {Array}
  623. */
  624. collection (res) {
  625. let result = []
  626. for (let i = 0; i < res.length; i++) {
  627. result.push(this.item(res[i]))
  628. }
  629. result = result.filter(x => x)
  630. return result
  631. }
  632.  
  633. /**
  634. * Should return {Object}
  635. * @param {Object} res
  636. * @return {Object}
  637. */
  638. item (res) {
  639. throw new Error('Abstract method')
  640. }
  641.  
  642. /**
  643. * @param {Number} lon
  644. * @param {Number} lat
  645. * @param {String} city
  646. * @param {String} street
  647. * @param {String} number
  648. * @param {String} name
  649. * @return {{number: *, city: *, street: *, name: *, raw: *, lon: *, title: *, lat: *}}
  650. */
  651. element (lon, lat, city, street, number, name = '') {
  652. // Raw data from provider
  653. let raw = [street, number, name].filter(x => !!x).join(', ')
  654.  
  655. console.info(raw)
  656. {
  657. city = normalizeCity(city)
  658. street = normalizeStreet(street)
  659. number = normalizeNumber(number)
  660. name = normalizeName(name)
  661. }
  662.  
  663. let title = [street, number, name].filter(x => !!x).join(', ')
  664. return {
  665. lat: lat,
  666. lon: lon,
  667. city: city,
  668. street: street,
  669. number: number,
  670. name: name,
  671. title: title,
  672. raw: raw,
  673. }
  674. }
  675.  
  676. /**
  677. * Render result to target element
  678. */
  679. render () {
  680. if (this.response.length === 0) {
  681. // remove empty panel
  682. this.panel.remove()
  683. return
  684. }
  685.  
  686. this.panel.append(this._fieldset())
  687. }
  688.  
  689. /**
  690. * Create div for all items
  691. * @return {HTMLDivElement}
  692. * @private
  693. */
  694. _panel () {
  695. let div = document.createElement('div')
  696. div.id = NAME + '-' + this.uid
  697. div.className = 'e50'
  698. return div
  699. }
  700.  
  701. /**
  702. * Build fieldset with the list of the response items
  703. * @return {HTMLFieldSetElement}
  704. * @protected
  705. */
  706. _fieldset () {
  707. let fieldset = document.createElement('fieldset')
  708. let list = document.createElement('ul')
  709.  
  710. let collapse = parseInt(E50Instance.settings.get('ranges', 'collapse'))
  711.  
  712. if (collapse && this.response.length > collapse) {
  713. fieldset.className = 'collapsed'
  714. } else {
  715. fieldset.className = ''
  716. }
  717.  
  718.  
  719. for (let i = 0; i < this.response.length; i++) {
  720. let item = document.createElement('li')
  721. item.append(this._link(this.response[i]))
  722. list.append(item)
  723. }
  724.  
  725. let legend = document.createElement('legend')
  726. legend.innerHTML = this.uid + ' <span>' + this.response.length + '</span>'
  727. legend.onclick = function () {
  728. this.parentElement.classList.toggle("collapsed")
  729. return false
  730. }
  731. fieldset.append(legend, list)
  732. return fieldset
  733. }
  734.  
  735. /**
  736. * Build link by {Object}
  737. * @param {Object} item
  738. * @return {HTMLAnchorElement}
  739. * @protected
  740. */
  741. _link (item) {
  742. let a = document.createElement('a')
  743. a.href = '#'
  744. a.dataset.lat = item.lat
  745. a.dataset.lon = item.lon
  746. a.dataset.city = item.city
  747. a.dataset.street = item.street
  748. a.dataset.number = item.number
  749. a.dataset.name = item.name
  750. a.innerText = item.title
  751. a.title = item.raw
  752. a.className = NAME + '-link'
  753. if (!item.city || !item.street || !item.number) {
  754. a.className += ' noaddress'
  755. }
  756. return a
  757. }
  758. }
  759.  
  760. /**
  761. * Based on closest segment and city
  762. */
  763. class MagicProvider extends Provider {
  764. constructor (container, settings) {
  765. super(I18n.t(NAME).providers.magic, container, settings)
  766. }
  767.  
  768. async request (lon, lat) {
  769. let city = null
  770. let street = ''
  771. let segment = findClosestSegment(new OpenLayers.Geometry.Point(lon, lat).transform('EPSG:4326', 'EPSG:900913'), true, true)
  772. if (segment) {
  773. city = segment.getAddress(W.model).getCity()
  774. street = segment.getAddress(W.model).getStreetName()
  775.  
  776. // to lon, lat
  777. let point = segment.closestPoint.transform('EPSG:900913', 'EPSG:4326')
  778. lon = point.x
  779. lat = point.y
  780. }
  781.  
  782. if (!city) {
  783. let cities = W.model.cities.getObjectArray()
  784. .filter(c => c.getName()) // not empty city name
  785. .filter(c => c.getName() !== 'поза НП') // not "no" city (hardcoded mistake)
  786. .filter(c => c.getID() !== 55344) // not an EMPTY city for Ukraine
  787. city = cities.length ? cities.shift() : null
  788. }
  789. if (!street) {
  790. return []
  791. }
  792.  
  793. console.groupCollapsed(this.uid)
  794. // lon, lat, city, street, number, name
  795. let result = [
  796. this.element(
  797. lon,
  798. lat,
  799. city ? city.getName() : '',
  800. street,
  801. '',
  802. ''
  803. )
  804. ]
  805. console.groupEnd(this.uid)
  806. return result
  807. }
  808. }
  809.  
  810. /**
  811. * US Addresses
  812. */
  813. class UaAddressesProvider extends Provider {
  814. constructor (container, settings, key) {
  815. super(I18n.t(NAME).providers.ua, container, settings)
  816. this.key = key
  817. }
  818.  
  819. async request (lon, lat) {
  820. let result = []
  821. let url = 'https://stat.waze.com.ua/address_map/address_map.php'
  822. let data = {
  823. lon: lon,
  824. lat: lat,
  825. script: this.key
  826. }
  827. let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e))
  828.  
  829.  
  830. console.groupCollapsed(this.uid)
  831. if (response?.result && response.result === 'success') {
  832. result = this.collection(response.data.polygons.Default)
  833. } else {
  834. console.info('No response returned')
  835. }
  836. console.groupEnd(this.uid)
  837. return result
  838. }
  839.  
  840. item (res) {
  841.  
  842. let data = res.name.split(",")
  843.  
  844. data = data.map(part => part.trim())
  845.  
  846. let number = data.length ? data.pop() : null
  847. let street = data.length ? data.pop() : null
  848. let city = data.length ? data.pop() : null
  849.  
  850. let parser = new OpenLayers.Format.WKT()
  851. parser.internalProjection = W.map.getProjectionObject()
  852. //parser.externalProjection = new OpenLayers.Projection('EPSG:4326')
  853.  
  854. let feature = parser.read(res.polygon)
  855. let centerPoint = feature.geometry.getCentroid()
  856.  
  857. return this.element(centerPoint.x, centerPoint.y, city, street, number)
  858. }
  859. }
  860.  
  861. /**
  862. * visicom.ua
  863. */
  864. class VisicomProvider extends Provider {
  865. constructor (container, settings, key) {
  866. super('Visicom', container, settings)
  867. this.key = key
  868. }
  869.  
  870. async request (lon, lat) {
  871. let result = []
  872. let url = 'https://api.visicom.ua/data-api/5.0/uk/geocode.json'
  873. let data = {
  874. near: lon + ',' + lat,
  875. categories: 'adr_address',
  876. order: 'distance',
  877. radius: 100,
  878. limit: 10,
  879. key: this.key,
  880. }
  881.  
  882. let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e))
  883.  
  884. console.groupCollapsed(this.uid)
  885. if (response?.features?.length > 0) {
  886. result = this.collection(response.features)
  887. } else {
  888. console.info('No response returned')
  889. if (response?.status) {
  890. console.info('Status:', response.status)
  891. }
  892. }
  893. console.groupEnd(this.uid)
  894. return result
  895. }
  896.  
  897. item (res) {
  898. let city = ''
  899. let street = ''
  900. let number = ''
  901. if (res.properties.settlement) {
  902. city = res.properties.settlement
  903. }
  904. if (res.properties.street) {
  905. street = res.properties.street_type + ' ' + res.properties.street
  906. }
  907. if (res.properties.name) {
  908. number = res.properties.name
  909. }
  910. return this.element(res.geo_centroid.coordinates[0], res.geo_centroid.coordinates[1], city, street, number)
  911. }
  912. }
  913.  
  914. /**
  915. * OpenStreetMap
  916. */
  917. class OsmProvider extends Provider {
  918. constructor (container, settings) {
  919. super('OSM', container, settings)
  920. }
  921.  
  922. async request (lon, lat) {
  923. let result = []
  924. let url = 'https://nominatim.openstreetmap.org/reverse'
  925. let data = {
  926. lon: lon,
  927. lat: lat,
  928. zoom: 18,
  929. addressdetails: 1,
  930. countrycodes: this.settings.language,
  931. 'accept-language': this.settings.locale,
  932. format: 'json',
  933. }
  934.  
  935. let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e))
  936.  
  937. console.groupCollapsed(this.uid)
  938. if (response?.address?.house_number) {
  939. result = [this.item(response)]
  940. } else {
  941. console.info('No response returned')
  942. }
  943. console.groupEnd(this.uid)
  944. return result
  945. }
  946.  
  947. item (res) {
  948. let city = ''
  949. let street = ''
  950. let number = ''
  951. if (res.address.city) {
  952. city = res.address.city
  953. } else if (res.address.town) {
  954. city = res.address.town
  955. }
  956. if (res.address.road) {
  957. street = res.address.road
  958. }
  959. if (res.address.house_number) {
  960. number = res.address.house_number
  961. }
  962. return this.element(res.lon, res.lat, city, street, number)
  963. }
  964. }
  965.  
  966. /**
  967. * 2GIS
  968. * @link https://docs.2gis.com/ru/api/search/geocoder/reference/2.0/geo/search#/default/get_2_0_geo_search
  969. */
  970. class GisProvider extends Provider {
  971. constructor (container, settings, key) {
  972. super('2Gis', container, settings)
  973. this.key = key
  974. }
  975.  
  976. async request (lon, lat) {
  977. let result = []
  978. let url = 'https://catalog.api.2gis.com/2.0/geo/search'
  979. let data = {
  980. point: lon + ',' + lat,
  981. radius: 20,
  982. type: 'building',
  983. fields: 'items.address,items.adm_div,items.geometry.centroid',
  984. locale: this.settings.locale,
  985. format: 'json',
  986. key: this.key,
  987. }
  988.  
  989. let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e))
  990.  
  991. console.groupCollapsed(this.uid)
  992. if (response?.result?.items?.length > 0) {
  993. result = this.collection(response.result.items)
  994. } else {
  995. console.info('No response returned')
  996. }
  997. console.groupEnd(this.uid)
  998. return result
  999. }
  1000.  
  1001. item (res) {
  1002. let output = []
  1003. let city = ''
  1004. let street = ''
  1005. let number = ''
  1006. if (res.adm_div.length) {
  1007. for (let i = 0; i < res.adm_div.length; i++) {
  1008. if (res.adm_div[i].type === 'city') {
  1009. city = res.adm_div[i].name
  1010. }
  1011. }
  1012. }
  1013. if (res.address.components) { // optional
  1014. street = res.address.components[0].street
  1015. number = res.address.components[0].number
  1016. } else if (res.address_name) { // optional
  1017. output.push(res.address_name)
  1018. } else if (res.name) {
  1019. output.push(res.name)
  1020. }
  1021. // e.g. POINT(36.401143 49.916814)
  1022. let center = res.geometry.centroid.substring(6, res.geometry.centroid.length - 1).split(' ')
  1023. let lon = center[0]
  1024. let lat = center[1]
  1025.  
  1026. let element = this.element(lon, lat, city, street, number, output.join(', '))
  1027. if (res.purpose_name) {
  1028. element.raw += ', ' + res.purpose_name
  1029. }
  1030. return element
  1031. }
  1032. }
  1033.  
  1034. /**
  1035. * Here Maps
  1036. * @link https://developer.here.com/documentation/geocoder/topics/quick-start-geocode.html
  1037. * @link https://www.here.com/docs/bundle/geocoder-api-developer-guide/page/topics/resource-reverse-geocode.html
  1038. */
  1039. class HereProvider extends Provider {
  1040. constructor (container, settings, key) {
  1041. super('Here', container, settings)
  1042. this.key = key.split(':')
  1043. }
  1044.  
  1045. async request (lon, lat) {
  1046. let result = []
  1047. let url = 'https://reverse.geocoder.api.here.com/6.2/reversegeocode.json'
  1048. let data = {
  1049. app_id: this.key[0],
  1050. app_code: this.key[1],
  1051. prox: lat + ',' + lon + ',10',
  1052. mode: 'retrieveAddresses',
  1053. locationattributes: 'none,ar',
  1054. addressattributes: 'str,hnr'
  1055. }
  1056.  
  1057. let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e))
  1058.  
  1059. console.groupCollapsed(this.uid)
  1060. if (response?.Response?.View?.[0]?.Result?.length) {
  1061. result = this.collection(response.Response.View[0].Result.filter(x => x.MatchLevel === 'houseNumber'))
  1062. } else {
  1063. console.info('No response returned')
  1064. }
  1065. console.groupEnd(this.uid)
  1066. return result
  1067. }
  1068.  
  1069. item (res) {
  1070. return this.element(
  1071. res.Location.DisplayPosition.Longitude,
  1072. res.Location.DisplayPosition.Latitude,
  1073. res.Location.Address.City,
  1074. res.Location.Address.Street,
  1075. res.Location.Address.HouseNumber
  1076. )
  1077. }
  1078. }
  1079.  
  1080. /**
  1081. * Bing Maps
  1082. * @link https://docs.microsoft.com/en-us/bingmaps/rest-services/locations/find-a-location-by-point
  1083. * http://dev.virtualearth.net/REST/v1/Locations/50.03539,36.34732?o=xml&key=AuBfUY8Y1Nzf3sRgceOYxaIg7obOSaqvs0k5dhXWfZyFpT9ArotYNRK7DQ_qZqZw&c=uk
  1084. * http://dev.virtualearth.net/REST/v1/Locations/50.03539,36.34732?o=xml&key=AuBfUY8Y1Nzf3sRgceOYxaIg7obOSaqvs0k5dhXWfZyFpT9ArotYNRK7DQ_qZqZw&c=uk&includeEntityTypes=Address
  1085. */
  1086. class BingProvider extends Provider {
  1087. constructor (container, settings, key) {
  1088. super('Bing', container, settings)
  1089. this.key = key
  1090. }
  1091.  
  1092. async request (lon, lat) {
  1093. let result = []
  1094. let url = 'https://dev.virtualearth.net/REST/v1/Locations/' + lat + ',' + lon
  1095. let data = {
  1096. includeEntityTypes: 'Address',
  1097. c: this.settings.country,
  1098. key: this.key,
  1099. }
  1100.  
  1101. let response = await this.makeRequest(url, data).catch(e => console.error(this.uid, 'return error', e))
  1102.  
  1103. console.groupCollapsed(this.uid)
  1104. if (response?.resourceSets?.[0]?.resources?.length) {
  1105. result = this.collection(
  1106. response.resourceSets[0].resources.filter(
  1107. el => el.address?.addressLine?.includes(',')
  1108. )
  1109. );
  1110. } else {
  1111. console.info('No response returned')
  1112. }
  1113. console.groupEnd(this.uid)
  1114. return result
  1115. }
  1116.  
  1117. item (res) {
  1118. let address = res.address.addressLine.split(',')
  1119. return this.element(
  1120. res.point.coordinates[1],
  1121. res.point.coordinates[0],
  1122. res.address.locality,
  1123. address[0],
  1124. address[1]
  1125. )
  1126. }
  1127. }
  1128.  
  1129. /**
  1130. * Google Place
  1131. * @link https://developers.google.com/places/web-service/search
  1132. */
  1133. class GoogleProvider extends Provider {
  1134. constructor (container, settings, key) {
  1135. super('Google', container, settings)
  1136. this.key = key
  1137. }
  1138.  
  1139. async request (lon, lat) {
  1140. let result = []
  1141. let response = await this.makeAPIRequest(lat, lon)
  1142. .catch(e => null)
  1143. // .catch(e => console.error(this.uid, 'return error', e))
  1144.  
  1145. console.groupCollapsed(this.uid)
  1146. if (response?.length) {
  1147. result = this.collection(response)
  1148. } else {
  1149. console.info('No response returned')
  1150. }
  1151. console.groupEnd(this.uid)
  1152. return result
  1153. }
  1154.  
  1155. async makeAPIRequest (lat, lon) {
  1156. let center = new google.maps.LatLng(lat, lon)
  1157.  
  1158. let map = new google.maps.Map(document.createElement('div'), { center: center })
  1159.  
  1160. let request = {
  1161. location: center,
  1162. radius: '100',
  1163. type: 'point_of_interest',
  1164. // doesn't work
  1165. // fields: ['name', 'address_component', 'geometry'],
  1166. // language: this.settings.country,
  1167. }
  1168.  
  1169. let service = new google.maps.places.PlacesService(map)
  1170. return new Promise((resolve, reject) => {
  1171. service.nearbySearch(request, (results, status) => {
  1172. if (status === google.maps.places.PlacesServiceStatus.OK) {
  1173. resolve(results)
  1174. } else {
  1175. reject(status)
  1176. }
  1177. })
  1178. })
  1179. }
  1180.  
  1181. item (res) {
  1182. let address = res.vicinity.split(',')
  1183. address = address.map(str => str.trim())
  1184.  
  1185. // looks like hell
  1186. let street = address[0] && address[0].length > 4 ? address[0] : ''
  1187. let number = address[1] && address[1].length < 13 ? address[1] : ''
  1188. let city = address[2] ? address[2] : ''
  1189.  
  1190. return this.element(
  1191. res.geometry.location.lng(),
  1192. res.geometry.location.lat(),
  1193. city,
  1194. street,
  1195. number,
  1196. res.name
  1197. )
  1198. }
  1199. }
  1200.  
  1201. $(document)
  1202. .on('bootstrap.wme', ready)
  1203. .on('click', '.' + NAME + '-link', applyData)
  1204. .on('mouseenter', '.' + NAME + '-link', showVector)
  1205. .on('mouseleave', '.' + NAME + '-link', hideVector)
  1206. .on('none.wme', hideVector)
  1207.  
  1208. function ready () {
  1209. WazeActionUpdateObject = require('Waze/Action/UpdateObject')
  1210. WazeActionUpdateFeatureAddress = require('Waze/Action/UpdateFeatureAddress')
  1211.  
  1212. E50Instance = new E50(NAME, SETTINGS)
  1213. E50Cache = new SimpleCache()
  1214. }
  1215.  
  1216. /**
  1217. *
  1218. * @return {null|Object}
  1219. */
  1220. function getSelectedPOI () {
  1221. let venue = WME.getSelectedVenue()
  1222. // For TEST ENV only!
  1223. // venue = W.selectionManager.getSelectedDataModelObjects()[0]
  1224. if (!venue) {
  1225. return null
  1226. }
  1227. let except = ['NATURAL_FEATURES']
  1228. if (except.indexOf(venue.getMainCategory()) === -1) {
  1229. return venue
  1230. }
  1231. return null
  1232. }
  1233.  
  1234. /**
  1235. * Returns an array of all segments in the current extent
  1236. * @function WazeWrap.Model.getOnscreenSegments
  1237. */
  1238. function getOnscreenSegments () {
  1239. let segments = W.model.segments.objects
  1240. let mapExtent = W.map.getExtent()
  1241. let onScreenSegments = []
  1242. let seg
  1243.  
  1244. for (let s in segments) {
  1245. if (!segments.hasOwnProperty(s))
  1246. continue
  1247.  
  1248. seg = W.model.segments.getObjectById(s)
  1249. if (mapExtent.intersectsBounds(seg.getOLGeometry().getBounds()))
  1250. onScreenSegments.push(seg)
  1251. }
  1252. return onScreenSegments
  1253. }
  1254.  
  1255. /**
  1256. * Finds the closest on-screen drivable segment to the given point, ignoring PLR and PR segments if the options are set
  1257. * @function WazeWrap.Geometry.findClosestSegment
  1258. * @param {OpenLayers.Geometry.Point} geometry The given point to find the closest segment to
  1259. * @param {boolean} ignorePLR If true, Parking Lot Road segments will be ignored when finding the closest segment
  1260. * @param {boolean} ignoreUnnamedPR If true, Private Road segments will be ignored when finding the closest segment
  1261. */
  1262. function findClosestSegment (geometry, ignorePLR, ignoreUnnamedPR) {
  1263. let onscreenSegments = getOnscreenSegments()
  1264. let minDistance = Infinity
  1265. let closestSegment
  1266.  
  1267. for (let s in onscreenSegments) {
  1268. if (!onscreenSegments.hasOwnProperty(s))
  1269. continue
  1270.  
  1271. let segmentType = onscreenSegments[s].attributes.roadType
  1272.  
  1273. if (segmentType === TYPES.boardwalk
  1274. || segmentType === TYPES.stairway
  1275. || segmentType === TYPES.railroad
  1276. || segmentType === TYPES.runway)
  1277. continue
  1278.  
  1279. // parking lots
  1280. if (ignorePLR && segmentType === TYPES.parking) //PLR
  1281. continue
  1282.  
  1283. // private roads
  1284. if (ignoreUnnamedPR && segmentType === TYPES.private)
  1285. continue
  1286.  
  1287. // unnamed roads, f**ing magic number
  1288. if (
  1289. !onscreenSegments[s].getAddress(W.model).getStreet().getID() ||
  1290. onscreenSegments[s].getAddress(W.model).getStreet().getID() === 8325397)
  1291. continue
  1292.  
  1293. let distanceToSegment = geometry.distanceTo(onscreenSegments[s].getOLGeometry(), { details: true })
  1294.  
  1295. if (distanceToSegment.distance < minDistance) {
  1296. minDistance = distanceToSegment.distance
  1297. closestSegment = onscreenSegments[s]
  1298. closestSegment.closestPoint = new OpenLayers.Geometry.Point(distanceToSegment.x1, distanceToSegment.y1)
  1299. }
  1300. }
  1301.  
  1302. return closestSegment
  1303. }
  1304.  
  1305. /**
  1306. * Apply data to current selected POI
  1307. * @param event
  1308. */
  1309. function applyData (event) {
  1310. event.preventDefault()
  1311. let poi = getSelectedPOI()
  1312.  
  1313. if (!poi.isGeometryEditable()) {
  1314. return
  1315. }
  1316.  
  1317. E50Instance.group('Apply data')
  1318.  
  1319. let lat = this.dataset.lat
  1320. let lon = this.dataset.lon
  1321. let name = this.dataset.name
  1322. let city = this.dataset.city
  1323. let street = this.dataset.street
  1324. let number = this.dataset.number
  1325.  
  1326. if (E50Instance.settings.get('options', 'copyData')) {
  1327. toClipboard([name, number, street, city].filter(x => !!x).join(' '))
  1328. }
  1329.  
  1330. // POI Name
  1331. let newName
  1332. // If exists name, ask user to replace it or not
  1333. // If not exists - use name or house number as name
  1334. if (poi.attributes.name) {
  1335. if (name && name !== poi.attributes.name) {
  1336. if (window.confirm(I18n.t(NAME).questions.changeName + '\n«' + poi.attributes.name + '» ⟶ «' + name + '»?')) {
  1337. newName = name
  1338. }
  1339. } else if (number && number !== poi.attributes.name) {
  1340. if (window.confirm(I18n.t(NAME).questions.changeName + '\n«' + poi.attributes.name + '» ⟶ «' + number + '»?')) {
  1341. newName = number
  1342. }
  1343. }
  1344. } else if (name) {
  1345. newName = name
  1346. } else if (number) {
  1347. newName = number
  1348. // Update alias for korpus
  1349. if ((new RegExp('[0-9]+[а-яі]?к[0-9]+', 'i')).test(number)) {
  1350. let alias = number.replace('к', ' корпус ')
  1351. let aliases = poi.attributes.aliases.slice()
  1352. if (aliases.indexOf(alias) === -1) {
  1353. aliases.push(alias)
  1354. W.model.actionManager.add(new WazeActionUpdateObject(poi, { aliases: aliases }))
  1355. }
  1356. }
  1357. }
  1358. if (newName) {
  1359. W.model.actionManager.add(new WazeActionUpdateObject(poi, { name: newName }))
  1360. }
  1361.  
  1362. // POI Address Street Name
  1363. let newStreet
  1364. let addressStreet = poi.getAddress(W.model).getStreet()?.getName() || ''
  1365. if (street) {
  1366. let existStreet = detectStreet(street)
  1367.  
  1368. if (existStreet) {
  1369. // We found the street, all OK
  1370. console.info('✅ Street detected, is «' + existStreet + '»')
  1371. street = existStreet
  1372. } else if (!window.confirm(I18n.t(NAME).questions.notFoundStreet + '\n«' + street + '»?')) {
  1373. street = null
  1374. }
  1375.  
  1376. // Check the current POI street name, and ask to rewrite it
  1377. if (street) {
  1378. if (addressStreet) {
  1379. if (addressStreet !== street &&
  1380. window.confirm(I18n.t(NAME).questions.changeStreet + '\n«' + addressStreet + '» ⟶ «' + street + '»?')) {
  1381. newStreet = street
  1382. }
  1383. } else {
  1384. newStreet = street
  1385. }
  1386. }
  1387. }
  1388.  
  1389. // POI Address City
  1390. let newCity
  1391. let addressCity = poi.getAddress(W.model).getCity()?.getName() || ''
  1392.  
  1393. // hardcoded value of common issue
  1394. if (addressCity === 'поза НП') {
  1395. addressCity = ''
  1396. }
  1397.  
  1398. if (city) {
  1399. // Try to find the city in the current location
  1400. let existCity = detectCity(city)
  1401.  
  1402. if (existCity) {
  1403. // We found the city, all OK
  1404. console.info('✅ City detected, is «' + existCity + '»')
  1405. city = existCity
  1406. } else if(!window.confirm(I18n.t(NAME).questions.notFoundCity + '\n«' + city + '»?')) {
  1407. // We can't find a city, and will ask to create a new one, but not needed
  1408. city = null
  1409. }
  1410.  
  1411. if (city) {
  1412. if (addressCity) {
  1413. if (addressCity !== city &&
  1414. window.confirm(I18n.t(NAME).questions.changeCity + '\n«' + addressCity + '» ⟶ «' + city + '»?')) {
  1415. newCity = city
  1416. }
  1417. } else {
  1418. newCity = city
  1419. }
  1420. }
  1421. }
  1422.  
  1423. // Update Address
  1424. if (newCity || newStreet) {
  1425. let address = {
  1426. countryID: W.model.getTopCountry().getID(),
  1427. stateID: W.model.getTopState().getID(),
  1428. cityName: newCity ? newCity : addressCity,
  1429. streetName: newStreet ? newStreet : poi.getAddress(W.model).getStreetName()
  1430. }
  1431. W.model.actionManager.add(new WazeActionUpdateFeatureAddress(poi, address))
  1432. }
  1433.  
  1434. // POI Address HouseNumber
  1435. let newHN
  1436. let addressHN = poi.getAddress(W.model).attributes.houseNumber
  1437. if (number) {
  1438. // Normalize «korpus»
  1439. number = number.replace(/^(\d+)к(\d+)$/i, '$1-$2')
  1440. // Check number for invalid format for Waze
  1441. if ((new RegExp('^[0-9]+[а-яі][к|/][0-9]+$', 'i')).test(number)) {
  1442. // Skip this step
  1443. console.log(
  1444. '%c' + NAME + ': %cskipped «' + number + '»',
  1445. 'color: #0DAD8D; font-weight: bold',
  1446. 'color: dimgray; font-weight: normal'
  1447. )
  1448. } else if (addressHN) {
  1449. if (addressHN !== number &&
  1450. window.confirm(I18n.t(NAME).questions.changeNumber + '\n«' + addressHN + '» ⟶ «' + number + '»?')) {
  1451. newHN = number
  1452. }
  1453. } else {
  1454. newHN = number
  1455. }
  1456. if (newHN) {
  1457. W.model.actionManager.add(new WazeActionUpdateObject(poi, { houseNumber: newHN }))
  1458. }
  1459. }
  1460.  
  1461. // If no an entry point, we would create it
  1462. if (E50Instance.settings.get('options', 'entryPoint') && poi.attributes.entryExitPoints.length === 0) {
  1463. // Create point based on data from the external source
  1464. let point = new OpenLayers.Geometry.Point(lon, lat).transform('EPSG:4326', 'EPSG:900913')
  1465.  
  1466. // Check intersection with selected POI
  1467. if (!poi.isPoint() && !poi.getOLGeometry().intersects(point)) {
  1468. point = poi.getOLGeometry().getCentroid()
  1469. }
  1470.  
  1471. // Create entry point
  1472. let navPoint = new entryPoint({primary: true, point: W.userscripts.toGeoJSONGeometry(point)})
  1473. W.model.actionManager.add(new WazeActionUpdateObject(poi, { entryExitPoints: [navPoint] }))
  1474. }
  1475.  
  1476. // Lock to level 2
  1477. if (E50Instance.settings.get('options', 'lock') && poi.attributes.lockRank < 1 && W.loginManager.user.getRank() > 0) {
  1478. W.model.actionManager.add(new WazeActionUpdateObject(poi, { lockRank: 1 }))
  1479. }
  1480.  
  1481. if (newName || newHN || newStreet || newCity) {
  1482. W.selectionManager.setSelectedModels([poi])
  1483. }
  1484.  
  1485. E50Instance.groupEnd()
  1486. }
  1487.  
  1488. /**
  1489. * Normalize the string:
  1490. * - remove the double quotes
  1491. * - remove double space
  1492. * @param {String} str
  1493. * @returns {String}
  1494. */
  1495. function normalizeString (str) {
  1496. // Clear space symbols and double quotes
  1497. str = str.trim()
  1498. .replace(/["“”]/g, '')
  1499. .replace(/\s{2,}/g, ' ')
  1500.  
  1501. // Clear accents/diacritics, but "\u0306" needed for "й"
  1502. // str = str.normalize('NFD').replace(/[\u0300-\u0305\u0309-\u036f]/g, '');
  1503. return str
  1504. }
  1505.  
  1506. /**
  1507. * Normalize the name:
  1508. * - remove № and #chars
  1509. * - remove dots
  1510. * @param {String} name
  1511. * @return {String}
  1512. */
  1513. function normalizeName (name) {
  1514. name = normalizeString(name)
  1515. name = name.replace(/[№#]/g, '')
  1516. name = name.replace(/\.$/, '')
  1517. return name
  1518. }
  1519.  
  1520. /**
  1521. * Normalize the city name
  1522. * @param {String} city
  1523. * @return {String}
  1524. */
  1525. function normalizeCity (city) {
  1526. return normalizeString(city)
  1527. }
  1528.  
  1529. /**
  1530. * Search the city name from available in editor area
  1531. * @param {String} city
  1532. * @return {String|null}
  1533. */
  1534. function detectCity(city) {
  1535. // Get the list of all available cities
  1536. let cities = W.model.cities.getObjectArray()
  1537. .filter(city => city.getName())
  1538. .filter(city => city.getName() !== 'поза НП')
  1539. .map(city => city.getName())
  1540.  
  1541. // More than one city, use city with best matching score
  1542. // Remove text in the "()", Waze puts region name to the pair brackets
  1543. let best = findBestMatch(city, cities.map(city => city.replace(/( ?\(.*\))/gi, '')))
  1544.  
  1545. if (best > -1) {
  1546. console.info('✅ City detected')
  1547. return cities[best]
  1548. } else if (cities.length === 1) {
  1549. console.info('❎ City doesn\'t found, uses default city')
  1550. return cities.shift()
  1551. } else {
  1552. console.info('❌ City doesn\'t found')
  1553. return null
  1554. }
  1555. }
  1556.  
  1557. /**\
  1558. * Normalize the street name by UA rules
  1559. * @param {String} street
  1560. * @return {String}
  1561. */
  1562. function normalizeStreet (street) {
  1563. street = normalizeString(street)
  1564.  
  1565. if (street === '') {
  1566. return ''
  1567. }
  1568.  
  1569. // Prepare street name
  1570. street = street.replace(/[’']/, '\'')
  1571. // Remove text in the "()", OSM puts alternative name to the pair brackets
  1572. street = street.replace(/( ?\(.*\))/gi, '')
  1573. // Normalize title
  1574. let regs = {
  1575. '(^| )бульвар( |$)': '$1б-р$2', // normalize
  1576. '(^| )вїзд( |$)': '$1в\'їзд$2', // fix mistakes
  1577. '(^| )в\'ізд( |$)': '$1в\'їзд$2', // fix mistakes
  1578. '(^|.+?) ?вулиця ?(.+|$)': 'вул. $1$2', // normalize, but ignore Lviv rules
  1579. '(^|.+?) ?улица ?(.+|$)': 'вул. $1$2', // translate, but ignore Lviv rules
  1580. '^(.+) в?ул\.?$': 'вул. $1', // normalize and translate, but ignore Lviv rules
  1581. '^в?ул.? (.+)$': 'вул. $1', // normalize and translate, but ignore Lviv rules
  1582. '(^| )дорога( |$)': '$1дор.$2', // normalize
  1583. '(^| )мікрорайон( |$)': '$1мкрн.$2', // normalize
  1584. '(^| )набережна( |$)': '$1наб.$2', // normalize
  1585. '(^| )площадь( |$)': '$1площа$2', // translate
  1586. '(^| )провулок провулок( |$)': '$1пров.$2', // O_o
  1587. '(^| )провулок( |$)': '$1пров.$2', // normalize
  1588. //'(^| )проїзд( |$)': '$1пр.$2', // normalize
  1589. '(^| )проспект( |$)': '$1просп.$2', // normalize
  1590. '(^| )район( |$)': '$1р-н$2', // normalize
  1591. '(^| )станція( |$)': '$1ст.$2', // normalize
  1592. }
  1593.  
  1594. for (let key in regs) {
  1595. let re = new RegExp(key, 'gi')
  1596. if (re.test(street)) {
  1597. street = street.replace(re, regs[key])
  1598. break
  1599. }
  1600. }
  1601.  
  1602. return street
  1603. }
  1604.  
  1605. /**
  1606. * Search the street name from available in editor area
  1607. * Normalize the street name by UA rules
  1608. * @param {String} street
  1609. * @return {String|null}
  1610. */
  1611. function detectStreet (street) {
  1612. street = normalizeStreet(street)
  1613.  
  1614. // Get all streets
  1615. let streets = W.model.streets.getObjectArray().filter(m => m.getName()).map(m => m.getName())
  1616.  
  1617. // Get type and create RegExp for filter streets
  1618. let reTypes = new RegExp('(алея|б-р|в\'їзд|вул\\.|дор\\.|мкрн|наб\\.|площа|пров\\.|проїзд|просп\\.|р-н|ст\\.|тракт|траса|тупик|узвіз|шосе)', 'gi')
  1619. let matches = [...street.matchAll(reTypes)]
  1620. let types = []
  1621.  
  1622. // Detect type(s)
  1623. if (matches.length === 0) {
  1624. types.push('вул.') // setup basic type
  1625. street = 'вул. ' + street
  1626. } else {
  1627. types = matches.map(match => match[0].toLowerCase())
  1628. }
  1629. // Filter streets by detected type(s)
  1630. let filteredStreets = streets.filter(street => types.some(type => street.indexOf(type) > -1))
  1631. // Matching names without type(s)
  1632. let best = findBestMatch(
  1633. street.replace(reTypes, '').toLowerCase().trim(),
  1634. filteredStreets.map(street => street.replace(reTypes, '').toLowerCase().trim())
  1635. )
  1636. if (best > -1) {
  1637. street = filteredStreets[best]
  1638. } else {
  1639. // Matching with type
  1640. best = findBestMatch(
  1641. street.toLowerCase().trim(),
  1642. streets.map(street => street.toLowerCase().trim())
  1643. )
  1644. if (best > -1) {
  1645. street = streets[best]
  1646. } else {
  1647. return null
  1648. }
  1649. }
  1650. return street
  1651. }
  1652.  
  1653. /**
  1654. * Normalize the number by UA rules
  1655. * @param {String} number
  1656. * @return {String}
  1657. */
  1658. function normalizeNumber (number) {
  1659. // process "д."
  1660. number = number.replace(/^д\. ?/i, '')
  1661. // process "дом"
  1662. number = number.replace(/^дом ?/i, '')
  1663. // process "буд."
  1664. number = number.replace(/^буд\. ?/i, '')
  1665. // remove spaces
  1666. number = number.trim().replace(/\s/g, '')
  1667. number = number.toUpperCase()
  1668. // process Latin to Cyrillic
  1669. number = number.replace('A', 'А')
  1670. number = number.replace('B', 'В')
  1671. number = number.replace('E', 'Е')
  1672. number = number.replace('I', 'І')
  1673. number = number.replace('K', 'К')
  1674. number = number.replace('M', 'М')
  1675. number = number.replace('H', 'Н')
  1676. number = number.replace('О', 'О')
  1677. number = number.replace('P', 'Р')
  1678. number = number.replace('C', 'С')
  1679. number = number.replace('T', 'Т')
  1680. number = number.replace('Y', 'У')
  1681. // process і,з,о
  1682. number = number.replace('І', 'і')
  1683. number = number.replace('З', 'з')
  1684. number = number.replace('О', 'о')
  1685. // process "корпус" to "к"
  1686. number = number.replace(/(.*)к(?:орп|орпус)?(\d+)/gi, '$1к$2')
  1687. // process "N-M" or "N/M" to "NM"
  1688. number = number.replace(/(.*)[-/]([а-яі])/gi, '$1$2')
  1689. // valid number format
  1690. // 123А 123А/321 123А/321Б 123к1 123Ак2
  1691. if (!number.match(/^\d+[а-яі]?([/к]\d+[а-яі]?)?$/gi)) {
  1692. return ''
  1693. }
  1694. return number
  1695. }
  1696.  
  1697. /**
  1698. * Copy to clipboard
  1699. * @param text
  1700. */
  1701. function toClipboard (text) {
  1702. // normalize
  1703. text = normalizeString(text)
  1704. text = text.replace(/'/g, '')
  1705. GM.setClipboard(text)
  1706. console.log(
  1707. '%c' + NAME + ': %ccopied «' + text + '»',
  1708. 'color: #0DAD8D; font-weight: bold',
  1709. 'color: dimgray; font-weight: normal'
  1710. )
  1711. }
  1712.  
  1713. /**
  1714. * Calculates the distance between given points, returned in meters
  1715. * @function calculateDistance
  1716. * @param {Array<OpenLayers.Geometry.Point>} pointArray An array of OpenLayers.Geometry.Point with which to measure the total distance. A minimum of 2 points is needed.
  1717. */
  1718. function calculateDistance (pointArray) {
  1719. if (pointArray.length < 2) {
  1720. return 0
  1721. }
  1722.  
  1723. let line = new OpenLayers.Geometry.LineString(pointArray)
  1724. return line.getGeodesicLength(W.map.getProjectionObject()) // multiply by 3.28084 to convert to feet
  1725. }
  1726.  
  1727. /**
  1728. * Get vector layer
  1729. * @return {OpenLayers.Layer.Vector}
  1730. */
  1731. function getVectorLayer () {
  1732. if (!vectorLayer) {
  1733. // Create layer for vectors
  1734. vectorLayer = new OpenLayers.Layer.Vector('E50VectorLayer', {
  1735. displayInLayerSwitcher: false,
  1736. uniqueName: '__E50VectorLayer'
  1737. })
  1738. W.map.addLayer(vectorLayer)
  1739. }
  1740. return vectorLayer
  1741. }
  1742.  
  1743. /**
  1744. * Show vector from the center of the selected POI to point by lon and lat
  1745. */
  1746. function showVector () {
  1747. let poi = getSelectedPOI()
  1748. if (!poi) {
  1749. return
  1750. }
  1751. let from = poi.getOLGeometry().getCentroid()
  1752. let to = new OpenLayers.Geometry.Point(this.dataset.lon, this.dataset.lat).transform('EPSG:4326', 'EPSG:900913')
  1753. let distance = Math.round(calculateDistance([to, from]))
  1754.  
  1755. vectorLine = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([from, to]), {}, {
  1756. strokeWidth: 4,
  1757. strokeColor: '#fff',
  1758. strokeLinecap: 'round',
  1759. strokeDashstyle: 'dash',
  1760. label: distance + 'm',
  1761. labelOutlineColor: '#000',
  1762. labelOutlineWidth: 3,
  1763. labelAlign: 'cm',
  1764. fontColor: '#fff',
  1765. fontSize: '24px',
  1766. fontFamily: 'Courier New, monospace',
  1767. fontWeight: 'bold',
  1768. labelYOffset: 24
  1769. })
  1770. vectorPoint = new OpenLayers.Feature.Vector(to, {}, {
  1771. pointRadius: 8,
  1772. fillOpacity: 0.5,
  1773. fillColor: '#fff',
  1774. strokeColor: '#fff',
  1775. strokeWidth: 2,
  1776. strokeLinecap: 'round'
  1777. })
  1778. getVectorLayer().addFeatures([vectorLine, vectorPoint])
  1779. // getVectorLayer().setZIndex(1001)
  1780. getVectorLayer().setVisibility(true)
  1781. }
  1782.  
  1783. /**
  1784. * Hide and clear all vectors
  1785. */
  1786. function hideVector () {
  1787. if (vectorLayer) {
  1788. vectorLayer.removeAllFeatures()
  1789. vectorLayer.setVisibility(false)
  1790. }
  1791. }
  1792.  
  1793. /**
  1794. * @link https://github.com/aceakash/string-similarity
  1795. * @param {String} first
  1796. * @param {String} second
  1797. * @return {Number}
  1798. */
  1799. function compareTwoStrings (first, second) {
  1800. first = first.replace(/\s+/g, '')
  1801. second = second.replace(/\s+/g, '')
  1802.  
  1803. if (!first.length && !second.length) return 1 // if both are empty strings
  1804. if (!first.length || !second.length) return 0 // if only one is empty string
  1805. if (first === second) return 1 // identical
  1806. if (first.length === 1 && second.length === 1) return 0 // both are 1-letter strings
  1807. if (first.length < 2 || second.length < 2) return 0 // if either is a 1-letter string
  1808.  
  1809. let firstBigrams = new Map()
  1810. for (let i = 0; i < first.length - 1; i++) {
  1811. const bigram = first.substring(i, i + 2)
  1812. const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) + 1 : 1
  1813.  
  1814. firstBigrams.set(bigram, count)
  1815. }
  1816.  
  1817. let intersectionSize = 0
  1818. for (let i = 0; i < second.length - 1; i++) {
  1819. const bigram = second.substring(i, i + 2)
  1820. const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) : 0
  1821.  
  1822. if (count > 0) {
  1823. firstBigrams.set(bigram, count - 1)
  1824. intersectionSize++
  1825. }
  1826. }
  1827. return (2.0 * intersectionSize) / (first.length + second.length - 2)
  1828. }
  1829.  
  1830. /**
  1831. * @param {String} mainString
  1832. * @param {String[]} targetStrings
  1833. * @return {Number}
  1834. */
  1835. function findBestMatch (mainString, targetStrings) {
  1836. let bestMatch = ''
  1837. let bestMatchRating = 0
  1838. let bestMatchIndex = -1
  1839.  
  1840. for (let i = 0; i < targetStrings.length; i++) {
  1841. let rating = compareTwoStrings(mainString, targetStrings[i])
  1842. if (rating > bestMatchRating) {
  1843. bestMatch = targetStrings[i]
  1844. bestMatchRating = rating
  1845. bestMatchIndex = i
  1846. }
  1847. }
  1848. if (bestMatch === '' || bestMatchRating < 0.35) {
  1849. console.log('❌', mainString, '🆚', targetStrings)
  1850. return -1
  1851. } else {
  1852. console.log('✅', mainString, '🆚', bestMatch, ':', bestMatchRating)
  1853. return bestMatchIndex
  1854. }
  1855. }
  1856. })()