WME Assist

Check and fix street names for POI and segments.

  1. // ==UserScript==
  2. // @name WME Assist
  3. // @author borman84 (Boris Molodenkov), madnut, turbopirate, Griev0us + (add yourself here)
  4. // @description Check and fix street names for POI and segments.
  5. // @require https://greasyfork.org/scripts/31773-wme-assist-scanner/code/WME_Assist_Scanner.js?version=208266
  6. // @require https://greasyfork.org/scripts/31774-wme-assist-analyzer/code/WME_Assist_Analyzer.js?version=208269
  7. // @grant none
  8. // @include /^https:\/\/(www|beta)\.waze\.com(\/\w{2,3}|\/\w{2,3}-\w{2,3}|\/\w{2,3}-\w{2,3}-\w{2,3})?\/editor\b/
  9. // @version 0.5.28
  10. // @namespace WazeRus
  11. // ==/UserScript==
  12.  
  13. var WME_Assist = WME_Assist || {}
  14.  
  15. WME_Assist.debug = function (message) {
  16. if (!$('#assist_debug').is(':checked')) return;
  17. console.log("WME ASSIST DEBUG: " + message);
  18. }
  19.  
  20. WME_Assist.info = function (message) {
  21. console.log("WME ASSIST INFO: " + message);
  22. }
  23.  
  24. WME_Assist.warning = function (message) {
  25. console.log("WME ASSIST WARN: " + message);
  26. }
  27.  
  28. WME_Assist.series = function (array, start, action, alldone) {
  29. var helper = function (i) {
  30. if (i < array.length) {
  31. action(array[i], function () {
  32. helper(i + 1);
  33. });
  34. } else {
  35. if (alldone) {
  36. alldone();
  37. }
  38. }
  39. }
  40.  
  41. helper(start);
  42. }
  43.  
  44. function run_wme_assist() {
  45. var ver = '0.5.28';
  46.  
  47. var debug = WME_Assist.debug;
  48. var info = WME_Assist.info;
  49. var warning = WME_Assist.warning;
  50.  
  51. function getWazeApi() {
  52. var wazeapi = window.Waze;
  53.  
  54. if (!wazeapi) return null;
  55. if (!wazeapi.map) return null;
  56. if (!wazeapi.model) return null;
  57. if (!wazeapi.model.countries) return null;
  58. if (!wazeapi.model.countries.top) return null;
  59.  
  60. return wazeapi;
  61. }
  62.  
  63. var Rule = function (comment, func, variant) {
  64. this.comment = comment;
  65. this.correct = func;
  66. this.variant = variant;
  67. }
  68.  
  69. var CustomRule = function (oldname, newname) {
  70. var title = '/' + oldname + '/ ➤ ' + newname;
  71. this.oldname = oldname;
  72. this.newname = newname;
  73. this.custom = true;
  74. $.extend(this, new Rule(title, function (text) {
  75. return text.replace(new RegExp(oldname), newname);
  76. }));
  77. }
  78.  
  79. var ExperimentalRule = function (comment, func) {
  80. this.comment = comment;
  81. this.correct = func;
  82. this.experimental = true;
  83. }
  84.  
  85. var Rules = function (countryName) {
  86. var rules_basicRU = function () {
  87. return [
  88. new Rule('Unbreak space in street name', function (text) {
  89. return text.replace(/\s+/g, ' ');
  90. }),
  91. new Rule('ACUTE ACCENT in street name', function (text) {
  92. return text.replace(/\u0301|\u0300|\"|\'/g, '');
  93. }),
  94. new Rule('Dash in street name', function (text) {
  95. return text.replace(/\u2010|\u2011|\u2012|\u2013|\u2014|\u2015|\u2043|\u2212|\u2796/g, '-');
  96. }),
  97. new Rule('No space after the word', function (text) {
  98. return text.replace(/\.(?!\s)/g, '. ');
  99. }),
  100. new Rule('Мусорный знак препинания после пробела', function (text) {
  101. return text.replace(/(^|\s+)(\.|,|;)/g, '$1');
  102. }),
  103. new Rule('Incorrect street name', function (text) {
  104. return text.replace(/улицаица/, 'улица');
  105. }),
  106. new Rule('Incorrect street name', function (text) {
  107. return text.replace(/скя( |$)/, 'ская$1'); // Волгостроевскя набережная
  108. }),
  109. new Rule('Incorrect street name', function (text) {
  110. return text.replace(/(^| )(мая)( |$)/, '$1Мая$3');
  111. }),
  112. new Rule('Incorrect street name', function (text) {
  113. return text.replace(/(^| )(мкрн?\.?|мк?р?-н)( |$)/, '$1микрорайон$3');
  114. }),
  115. new Rule('Incorrect street name', function (text) {
  116. return text.replace(/(^| )(р-о?н)( |$)/, '$1район$3');
  117. }),
  118. new Rule('Incorrect street name', function (text) {
  119. return text.replace(/(^| )(им\.?)( |$)/, '$1имени$3');
  120. }),
  121. new Rule('Incorrect street name', function (text) {
  122. return text.replace(/(^| )(пос\.?)( |$)/i, '$1посёлок$3');
  123. }),
  124. new Rule('Incorrect street name', function (text) {
  125. return text.replace(/(^| )(д\.?)( |$)/, '$1деревня$3');
  126. }),
  127. new Rule('Incorrect street name', function (text) {
  128. return text.replace(/(^| )(просп\.?)( |$)/i, '$1проспект$3');
  129. }),
  130. new Rule('Incorrect street name', function (text) {
  131. return text.replace(/(^| )(ул\.?)( |$)/i, '$1улица$3');
  132. }),
  133. new Rule('Incorrect street name', function (text) {
  134. return text.replace(/(^| )(р-д)( |$)/i, '$1разъезд$3');
  135. }),
  136. new Rule('Incorrect street name', function (text) {
  137. return text.replace(/(^| )(з-д)( |$)/i, '$1заезд$3');
  138. }),
  139. new Rule('Incorrect street name', function (text) {
  140. return text.replace(/(^| )(пер\.?)( |$)/i, '$1переулок$3');
  141. }),
  142. new Rule('Incorrect street name', function (text) {
  143. return text.replace(/(^| )(пр\.?|пр?-з?д\.?)( |$)/i, '$1проезд$3');
  144. }),
  145. new Rule('Incorrect street name', function (text) {
  146. return text.replace(/(^| )(пр?-к?т\.?)( |$)/i, '$1проспект$3');
  147. }),
  148. new Rule('Incorrect street name', function (text) {
  149. return text.replace(/(^| )(тр-т)( |$)/i, '$1тракт$3');
  150. }),
  151. new Rule('Incorrect street name', function (text) {
  152. return text.replace(/(^| )(пл\.?)( |$)/i, '$1площадь$3');
  153. }),
  154. new Rule('Incorrect street name', function (text) {
  155. return text.replace(/(^| )(ш\.?)( |$)/, '$1шоссе$3');
  156. }),
  157. new Rule('Incorrect street name', function (text) {
  158. return text.replace(/(^| )(б-р|бул\.?)( |$)/i, '$1бульвар$3');
  159. }),
  160. new Rule('Incorrect street name', function (text) {
  161. return text.replace(/(^| )(дор\.)( |$)/i, '$1дорога$3');
  162. }),
  163. new Rule('Incorrect street name', function (text) {
  164. return text.replace(/(^| )(о\.?)( |$)/, '$1остров$3');
  165. }),
  166. new Rule('Incorrect street name', function (text) {
  167. return text.replace(/(^| )(наб\.?)( |$)/i, '$1набережная$3');
  168. }),
  169. new Rule('Incorrect street name', function (text) {
  170. return text.replace(/(^| )(туп\.?)( |$)/i, '$1тупик$3');
  171. }),
  172. new Rule('Incorrect street name', function (text) {
  173. return text.replace(/(^| )(кв\.?)( |$)/i, '$1квартал$3');
  174. }),
  175. new Rule('Incorrect street name', function (text) {
  176. return text.replace(/(^| )(набережная р\.?)( |$)/i, '$1набережная реки$3');
  177. }),
  178. new Rule('Incorrect street name', function (text) {
  179. return text.replace(/^На /, 'на ');
  180. }),
  181. new Rule('Incorrect street name', function (text) {
  182. return text.replace(/(\d)(-ая)( |$)/, '$1-я$3');
  183. }),
  184. new Rule('Incorrect street name', function (text) {
  185. return text.replace(/(\d)(-о?го|-ое)( |$)/, '$1$3');
  186. }),
  187. new Rule('Incorrect street name', function (text) {
  188. return text.replace(/(\d)(-[оыи]й)( |$)/, '$1-й$3');
  189. }),
  190. new Rule('Incorrect street name', function (text) {
  191. return text.replace(/(\d)(-ти)( |$)/, '$1-и$3');
  192. }),
  193. new Rule('Incorrect street name', function (text) {
  194. return text.replace(/(\d)й/, '$1-й');
  195. }),
  196. new Rule('Incorrect street name', function (text) {
  197. return text.replace(/(\d)я/, '$1-я');
  198. }),
  199. new Rule('Incorrect street name', function (text) {
  200. return text.replace(/(\d)(\sЛет)(\s|$)/, '$1 лет$3');
  201. }),
  202. new Rule('Incorrect street name', function (text) {
  203. return text.replace(/(Проектируемый проезд)\s+[№#]\s*(\d+)/, '$1 $2');
  204. }),
  205. new Rule('Incorrect street name', function (text) {
  206. return text.replace(/([а-яё])-\s+/, '$1 - ');
  207. }),
  208. new Rule('Incorrect street name', function (text) {
  209. return text.replace(/\s+км\./i, ' км');
  210. }),
  211. new Rule('Incorrect street name', function (text) {
  212. return text.replace(/\[([^P]+)\]/, '$1');
  213. }),
  214. new Rule('Incorrect street name', function (text) {
  215. return text.replace(/(^|\()СН?Т\s([^\)]+)/, '$1$2 снт');
  216. }),
  217. new Rule('Incorrect street name', function (text) {
  218. return text.replace(/^ЖД$/i, 'ж/д');
  219. }),
  220. new Rule('Incorrect highway name', function (text) {
  221. return text.replace(/^M-?(\d)/, 'М$1')
  222. .replace(/^A-?(\d)/, 'А$1')
  223. .replace(/^P-?(\d)/, 'Р$1')
  224. .replace(/^([МАР])-(\d)/, '$1$2')
  225. .replace(/^(\d{2})A-(\d)/, '$1А-$2')
  226. .replace(/^(\d{2})K-(\d)/, '$1К-$2')
  227. .replace(/^(\d{2})H-(\d)/, '$1Н-$2')
  228. .replace(/^(\d{2})P-(\d)/, '$1Р-$2')
  229. .replace(/^(\d+)А(:|\s+|$)/, '$1A$2')
  230. .replace(/^(\d+)В(:|\s+|$)/, '$1B$2');
  231. }),
  232. ];
  233. };
  234.  
  235. var rules_RU = function () {
  236. return rules_basicRU().concat([
  237. new Rule('Incorrect status position', function (text) {
  238. // Неоднозначные улицы, требуется проверка на город
  239. // Москва: Козлова, Мишина
  240. // Питер: Абросимова, Гусева, Комарова, Панфилова, Тарасова, Старцева, Крюкова, Миронова, Перфильева, Шарова, Зеленина, Осокина
  241. // Великий Новгород: Яковлева, Ильина
  242. // Ижевск: Черезова
  243. // Краткие притяжательные прилагательные похожие на фамилии
  244. var exAdjW = 'Репищева|Малая Зеленина|Карташихина|Опочинина|Остоумова|Гаврикова|Прасковьина|Усачёва|Бармалеева|Ильмянинова|Остоумова|Плуталова|Подрезова|Полозова|Рашетова|Сегалева|Шамшева|Эсперова|Замшина|Куракина|Ольгина|Опочинина|Осокина|Рюхина|Тосина|Веткина|Жукова|Абросимова|Черезова|Алымова|Княжнина|Ванчакова|Новоажимова|Чудинцева';
  245.  
  246. // Женские статусы
  247. var wStatus = 'улица|набережная|дорога|линия|аллея|площадь|просека|автодорога|эстакада|магистраль|дамба|хорда|коса|деревня|переправа|площадка|дорожка|трасса';
  248.  
  249. // Мужские статусы
  250. var mStatus = 'проспект|переулок|проезд|тупик|бульвар|тракт|просек|взвоз|спуск|разъезд|переезд|квартал|путепровод|мост|сад|сквер|тоннель|туннель|парк|проток|канал|остров|микрорайон|район|городок|посёлок|поселок|вал|проулок|объезд|заезд|съезд|обвод|обход|подъезд|выход';
  251.  
  252. // Средние статусы
  253. var nStatus = 'шоссе|кольцо|село';
  254.  
  255. // Названия улиц похожие на прилагательные
  256. var exW = 'Нехая|Тукая|Мая|Барклая|Батырая|Маклая|Бикбая|Амантая|Нечая|Эшпая|Орая|Прикамья|Алтая|Ухсая|Хузангая|Галлая|Николая|Гая|Эркая|Камая|Пченушая|Здоровья|Палантая|Ярвенпяя|Гулая|Заполярья|Крылья|Приморья|Калина Красная|Краснолесья|Мазая|Умырзая|Абая|Сибая|Алибая|Шахрая';
  257.  
  258. // Названия проспектов похожие на прилагательные
  259. var exM = 'Расковой|Дуровой|Космодемьянской|.+?строй|Ковалевской|Борисовой|Давлетшиной|Крупской|Шевцовой|Чайкиной|Богомоловой|Савиной|Попковой|Петровой|Ангелиной|Терешковой|Новоселовой|Красноармейской|Гризодубовой|Красноярский рабочий|Цеткин|Молдагуловой|Чайкиной|Цветаевой|Тимофеевой|Дубровиной|Ульяны Громовой|[Нн]абережной|Давлетшиной|Перовской|Шпаковой|Ульяновой|Гачхой|Исаевой|Бой|Плевицкой';
  260.  
  261. // Отделить примечания в скобках (дублёр)
  262. var brackets = '';
  263. text = text.replace(/\s*(.*?)\s*(\(.*\))/,
  264. function (all, s, b) {
  265. brackets = b;
  266. return s;
  267. });
  268.  
  269. // Игнорируем исключения. Не нужно добавлять статус или изменять порядок слов
  270. if (/^$| - |\/|,|:|дцать\s|десят\s|сорок\s|^\d+(:|$)|\d+[AB]|[МАРН]\d|\d{2}[АКНР]-|ТТК|КАД|ЗСД|АГ?ЗС|(^|\s|-)(лестница|зимник|объезд|заезд|съезд|обвод|обход|подъезд|closed|[Сс]троительство|[Дд]убл[её]р|Rail|грунтовка|[Тт]ропа|[Тт]рек|[Пп]лотина|метро|монорельс|ворота|шлагбаум|трамвай|пути|Транссиб|Мост|подход|подъезд|обход|въезд|выезд|разворот|шлагбаум|слобода|Грейдер|брод|на|в|к|под|с|от|во|из|по|об|у|о|над|около|при|перед|про|до|без|за|через|ж\/д|плотина|снт|кп|станция|[Пп]ромзона|паркинг|парковка|Козлова|Абросимова|Гусева|Комарова|Панфилова|Тарасова|Яковлева|Мишина|Старцева|Крюкова|Ильина|Миронова|Перфильева|Шарова|Зеленина|Жукова|Черезова|Осокина|~)(-|\s|$)/.test(text) )
  271. return text + ' ' + brackets;
  272.  
  273. // коттеджный, дачный, клубный посёлок в начало
  274. text = text.replace(/(.*?)(?:\s+)((?:.*ый )посёлок)/, '$2 $1');
  275.  
  276. // Добавляем пропущенный статус
  277. if ( ! new RegExp('(^|\\s+)(' + wStatus + '|' + mStatus + '|' + nStatus + ')(\\s+|$)').test(text) ) {
  278. if ( text == 'Набережная') {
  279. text = 'Набережная улица';
  280. } else
  281. if ( new RegExp('(^|\\s+)(' + wStatus + '|' + mStatus + '|' + nStatus + ')(\\s+|$)', 'i' ).test(text) ) {
  282. // Если статус есть, но записан с заглавной буквы
  283. text = text.replace( new RegExp('(^|\\s+)(' + wStatus + '|' + mStatus + '|' + nStatus + ')(?=\\s+|$)', 'i' ), function (all, space, status) {
  284. return space + status.toLowerCase();
  285. });
  286. } else
  287. if ( /(^|\s+)[Оо]б[ъь]ездная(\s+|$)/.test(text)) {
  288. text = text.replace(new RegExp('(^|\\s+)[Оо]б[ъь]ездная(\\s+|$)(?!=(' + wStatus + '))'), '$1Объездная дорога$2');
  289. } else
  290. if ( /(^|\s+)[Оо]кружная$/.test(text)) {
  291. text = text.replace(/(^|\s+)[Оо]кружная$/, '$1Окружная дорога');
  292. } else
  293. if ( /[-аяь]я$/.test(text)) { // Прилагательное без статуса (Русско-Полянская)
  294. text = text + ' улица';
  295. } else
  296. if (/[а-я]-[А-Я]/.test(text)) { // Не хватает пробелов вокруг тире (Москва-Петушки)
  297. text = text.replace(/([а-я])-([А-Я])/g, '$1 - $2');
  298. } else
  299. text = 'улица ' + text;
  300. }
  301.  
  302. // Голые числительные без склонения
  303. if ( ! new RegExp('\\d\\s+мая(\\s|$)', 'i' ).test(text) )
  304. text = text.replace(new RegExp('(\\d)(?=(\\s+[^\\s]+(?:-я|ая|ья|яя|яся))*\\s+(' + wStatus + ')(\\s|$))', 'g'), '$1-я'); // 1 линия
  305. text = text.replace(new RegExp('(\\d)(?=(\\s+[^\\s]+(?:[-иоы]й|ин|[оеё]в))*\\s+(' + mStatus + ')(\\s|$))', 'g'), '$1-й'); // 2 проезд, 5 Донской проезд
  306.  
  307. // Распространённые сокращения
  308. text = text.replace(/М\.\s+(?=Горького)/, 'Максима ');
  309. text = text.replace(/К\.\s+(?=Маркса|Либкнехта)/, 'Карла ');
  310. text = text.replace(/Р\.\s+(?=Люксембург)/, 'Розы ');
  311. text = text.replace(/А\.\s+(?=Невского)/, 'Александра ');
  312. text = text.replace(/Б\.\s+(?=Хмельницкого)/, 'Богдана ');
  313.  
  314. // Всё пишем заглавными буквами, кроме статусов, предлогов и гидронимов
  315. text = text.replace(/(^|\s+)набережная улица/, '$1Набережная улица');
  316. var foundStatus = false;
  317. text = (' ' + text)
  318. .replace(/([-\s])([^-\s]+)/g,
  319. function(all, space, word) {
  320. if ( ! foundStatus )
  321. if ( new RegExp('^(' + wStatus+ '|' + mStatus + '|' + nStatus + ')$').test(word) ) {
  322. foundStatus = true;
  323. return all;
  324. };
  325. if ( /^(летия|лет|года|реч?к[аи]|канала?|острова?|стороны|год|съезда|имени|области|ручей|канавки|за|из|от|км|километр|де|в|к|о|с|у|на|и)$/i.test(word)
  326. || ( space == '-' && /^(лейтенанта|майора|полковника|губернатора|й|я|ти|го|е|ей|х)$/.test(word) ) )
  327. return space + word.toLowerCase();
  328. else return space + word.charAt(0).toUpperCase() + word.substr(1);
  329. })
  330. .replace(/\s+(.*)/, '$1')
  331. .replace(/Железная дорога/, 'железная дорога')
  332. .replace(/пос[её]лок остров/i, 'поселок Остров')
  333. .replace(/улица остров (.*)/i, 'улица Остров $1')
  334. .replace(/микрорайон в/i, 'микрорайон В');
  335.  
  336. // Статусы женского рода
  337. if ( new RegExp('(^|\\s)(' + wStatus + ')(\\s|$)').test(text) ) {
  338.  
  339. // Распространённые сокращения
  340. text = text.replace(/М\.\s+(?=[^\s]+?(?:ая|ья|яя|яся)( |$))/, 'Малая ');
  341. text = text.replace(/Б\.\s+(?=[^\s]+?(?:ая|ья|яя|яся)( |$))/, 'Большая ');
  342.  
  343. // перед статусом могут быть только прилагательные
  344. // Строителей 1-я улица -> улица Строителей 1-я
  345. text = text.replace(new RegExp('(?:\\s*)(.+?)(?:\\s+)(' + wStatus + ')(?=\\s+|$)'),
  346. function (all, adj, s) {
  347. if ( new RegExp(exAdjW).test(adj) ) return all;
  348. if ( (! new RegExp(exW).test(adj)) &&
  349. (/^((\s+[^\s]+?(-я|ая|ья|яя|яся))+)$/.test(' ' + adj)) ) return all;
  350. return s + ' ' + adj;
  351. });
  352.  
  353. // Прилагательные вперёд
  354. if ( ! new RegExp('(^|\\s|-)(' + exW + ')(-|\\s|$)').test(text) ) {
  355. // улица Малая Зеленина -> Малая Зеленина улица
  356. // улица Мягкая -> Мягкая улица
  357. // улица Авиаконструктора Яковлева, улица Малиновая Гора
  358. text = text.replace(new RegExp('(' + wStatus + ')((?:\\s+(?:' + exAdjW + ')|\\s+[^\\s]+(?:-я|ая|ья|яя|яся))+)$'), '$2 $1');
  359. // улица *** Малая Набережная -> Малая Набережная улица ***
  360. text = text.replace(new RegExp('(' + wStatus + ')(.*?)((?:\\s+[^\\s]+(?:-я|ая|ья|яя|яся))+)$'), '$3 $1$2');
  361. // улица Мягкая 1-й Проезд -> Мягкая улица 1-й Проезд
  362. text = text.replace(new RegExp('(' + wStatus + ')(?:\\s+)([^\\s]+(?:-я|ая|ья|яя|яся))(?=\\s+\\d+-й\\s+Проезд|\\s+\\d+-я\\s+Линия)'), '$2 $1');
  363. };
  364.  
  365. // Числительное всегда вначале если оно согласовано с прилагательным
  366. // Мягкая 1-я -> 1-я Мягкая
  367. text = text.replace(/(.+(?:ая|ья|яя|яся))(?:\s+)(\d+-я)(?! Линия| Ферма| Рота)/, '$2 $1');
  368. // улица 1-я Строителей -> 1-я улица Строителей
  369. text = text.replace(new RegExp('(' + wStatus + ')(?:\\s+)(\\d+-я)(?!\\s+[^\\s]+(?:ая|ья|яя|яся|лка)( |$)|\\s+(' + wStatus + '|Ферма|Авеню|Пристань|Рота|Слобода))', 'i'), '$2 $1');
  370. // 1-я улица Лесоперевалка -> улица 1-я Лесоперевалка
  371. text = text.replace(new RegExp('(\\d+-я)\\s+(' + wStatus + ')\\s+([^\\s]+(?:лка|ель|аза|йка|летка|арка))$'), '$2 $1 $3');
  372. } else
  373.  
  374. // Статусы мужского рода
  375. if ( new RegExp('(^|\\s)(' + mStatus + ')(\\s|$)').test(text) ) {
  376.  
  377. // Распространённые сокращения
  378. text = text.replace(/М\.\s+(?=[^\s]+?(?:[-иоы]й|ин|[оеё]в)( |$))/, 'Малый ');
  379. text = text.replace(/Б\.\s+(?=[^\s]+?(?:[-иоы]й|ин|[оеё]в)( |$))/, 'Большой ');
  380.  
  381. // перед статусом могут быть только прилагательные
  382. text = text.replace(new RegExp('(?:\\s*)(.+?)(?:\\s+)(' + mStatus + ')(?=\\s+|$)'),
  383. function (all, adj, s){
  384. // if ( /[а-яё]+([-иоы]й|ин)(\s+|$)/.test(adj) ) return all;
  385. if ( (! new RegExp(exM, 'i').test(adj)) &&
  386. (/^((\s+[^\s]+?([-иоы]й|ин|[оеё]в))+)$/.test(' ' + adj)) ) return all;
  387. return s + ' ' + adj;
  388. });
  389.  
  390. // Прилагательное вперёд
  391. if (( ! new RegExp('(^|\\s)(' + exM + ')(\\s|$)', 'i').test(text) ) &&
  392. ( ! new RegExp('^(проезд|переулок)([^\.]*?)((?:\\s+[^\\s]+ой)+)$').test(text) ) ) {
  393. // переулок *** 1-й -> 1-й переулок ***
  394. text = text.replace(new RegExp('^(' + mStatus + ')([^\.]*?)((?:\\s+[^\\s]+(?:[-иоы]й|ин))+)$'), '$3 $1$2');
  395. text = text.replace(
  396. new RegExp('^(' + mStatus + ')((?:\\s+[^\\s]+(?:[-иоы]й|ин))+)$'), '$2 $1');
  397. text = text.replace(
  398. new RegExp('^(' + mStatus + ')(?:\\s+)([^\\s]+(?:[-иоы]й|ин))(?=\\s+\\d+-й\\s+Проезд|\\s+\\d+-я\\s+Линия)'), '$2 $1');
  399. }
  400.  
  401. // Числительное всегда вначале если оно согласовано с прилагательным
  402. // переулок 1-й Дунаевского -> 1-й переулок Дунаевского
  403. //text = text.replace(new RegExp('(' + mStatus + ')(?:\\s+)(\\d+-й)(?!\\s+[^\\s]*(?:' + exM + ')|\\s+[^\\s]+(?:[-иоы]й|ин|[оеё]в)( |$)', 'i'), '$2 $1');
  404.  
  405. text = text.replace(/(.+[иоы]й)(?:\s+)(\d+-й)/, '$2 $1');
  406. text = text.replace(new RegExp('(' + mStatus + ')(?:\\s+)(\\d+-й)(?!\\s+[^\\s]+[иоык][ий]( |$)|\\s+(' + mStatus + '|Ряд|км|километр|Поворот))', 'i'), '$2 $1');
  407. } else
  408.  
  409. // Статусы среднего рода
  410. if ( new RegExp('(^|\\s)(' + nStatus + ')(\\s|$)').test(text) ) {
  411.  
  412. // Энтузиастов шоссе -> шоссе Энтузиастов
  413. text = text.replace(new RegExp('(?:\\s*)(.+?)(?:\\s+)(' + nStatus + ')(?=\\s+|$)'),
  414. function (all, adj, s){
  415. if ( /[а-яё]+(ое)(\s+|$)/.test(adj) ) return all;
  416. return s + ' ' + adj;
  417. });
  418.  
  419. // шоссе Воткинское -> Воткинское шоссе, Верхнее шоссе
  420. text = text.replace( new RegExp('^(' + nStatus + ')(?:\\s+)(.+[ео]е)$'), '$2 $1');
  421. }
  422.  
  423. // Возвращаем скобки в конце
  424. return text + ' ' + brackets;
  425. }),
  426. new Rule('Move status to begin of name', function (text) {
  427. return text.replace(/(.*)(улица)(.*)/, '$2 $1 $3');
  428. }, 'Tula'),
  429. new ExperimentalRule('Experimental', function (text) {
  430. return text.replace(/experimental/, 'corrected_experimental');
  431. }),
  432. ]);
  433. };
  434.  
  435. var rules_BY = function () {
  436. var isStatus = function (str) {
  437. var list = ['улица', 'переулок', 'проспект', 'проезд',
  438. 'площадь', 'шоссе', 'бульвар', 'тракт',
  439. 'тупик', 'спуск', 'вуліца', 'завулак',
  440. 'праспект', 'праезд', 'плошча', 'шаша'];
  441. if (list.indexOf(str) > -1) return true;
  442. return false;
  443. }
  444.  
  445. var isPseudoStatus = function (str) {
  446. var list = ['шоссе', 'тракт', 'площадь', 'шаша', 'плошча', 'спуск'];
  447. if (list.indexOf(str) > -1) return true;
  448. return false;
  449. }
  450.  
  451. var isNumber = function (str) {
  452. return /([0-9])-[іыйя]/.test(str);
  453. }
  454.  
  455. var replaceParts = function (text) {
  456. var arr = text.split(' ');
  457. var result = [];
  458. var status;
  459. var number;
  460.  
  461. var previousPart = '';
  462. for (var i = 0; i < arr.length; ++i) {
  463. var part = arr[i];
  464.  
  465. if (isStatus(part) && !isPseudoStatus(part)) {
  466. status = part;
  467. } else if (isNumber(part) && previousPart.toLowerCase() != 'героев') {
  468. number = part;
  469. } else {
  470. result.push(part);
  471. }
  472.  
  473. previousPart = part;
  474. }
  475.  
  476. if (status) {
  477. result.splice(0, 0, status);
  478. }
  479.  
  480. if (number) {
  481. result.push(number);
  482. }
  483.  
  484. return result.join(' ');
  485. }
  486.  
  487. return rules_basicRU().concat([
  488. new Rule('Delete space in initials', function (text) {
  489. return text.replace(/(^|\s+)([А-Я]\.)\s([А-Я]\.)/, '$1$2$3');
  490. }),
  491. new Rule('Incorrect street name', function (text) {
  492. return text.replace(/(^| )(тр-т)( |$)/, '$1тракт$3');
  493. }),
  494. new Rule('Incorrect street name', function (text) {
  495. return text.replace(/(^| )(вул\.?)( |$)/, '$1вуліца$3');
  496. }),
  497. new Rule('Incorrect street name', function (text) {
  498. return text.replace(/(^| )(зав)( |$)/, '$1завулак$3');
  499. }),
  500. new Rule('Incorrect street name', function (text) {
  501. return text.replace(/(^| )(прасп)( |$)/, '$1праспект$3');
  502. }),
  503.  
  504. new Rule('Incorrect street name', function (text) {
  505. return text.replace(/-ая/, '-я').replace(/-ой/, '-й');
  506. }),
  507. new Rule('Incorrect street name', function (text) {
  508. return text.replace(/([РрНнМмPpHM])-?([0-9])/, function (a, p1, p2) {
  509. p1 = p1
  510. .replace('р', 'Р')
  511. .replace('н', 'Н')
  512. .replace('м', 'М')
  513. .replace('P', 'Р')
  514. .replace('p', 'Р')
  515. .replace('H', 'Н')
  516. .replace('M', 'М');
  517.  
  518. return p1 + '-' + p2;
  519. });
  520. }),
  521. new Rule('Incorrect street name', replaceParts),
  522. ]);
  523. }
  524.  
  525. var getCountryRules = function (name) {
  526. var commonRules = [
  527. // Following rules must be at the end because
  528. // previous rules might insert additional spaces
  529. new Rule('Лишние пробелы', function (text) {
  530. return text.replace(/[\s]+/g, ' ');
  531. }),
  532. new Rule('Пробелы в начале улицы или после открывающей скобки', function (text) {
  533. return text.replace(/(^|\()[\s]+/, '$1');
  534. }),
  535. new Rule('Пробелы в конце улицы или перед закрывающей скобкой', function (text) {
  536. return text.replace(/[\s]+(\)|$)/, '$1');
  537. }),
  538. ];
  539. var countryRules;
  540. info('Get rules for country: ' + name);
  541. switch (name) {
  542. case 'Russia':
  543. countryRules = rules_RU();
  544. break;
  545. case 'Ukraine':
  546. countryRules = rules_RU();
  547. break;
  548. case 'Belarus':
  549. countryRules = rules_BY();
  550. break;
  551. default:
  552. info('There are not implemented rules for country: ' + name);
  553. countryRules = [];
  554. }
  555. return countryRules.concat(commonRules);
  556. }
  557.  
  558. var rules = [];
  559. var customRulesNumber = 0;
  560.  
  561. var onAdd = function (rule) {}
  562. var onEdit = function (index, rule) {}
  563. var onDelete = function (index) {}
  564.  
  565. this.onAdd = function (cb) { onAdd = cb }
  566. this.onEdit = function (cb) { onEdit = cb }
  567. this.onDelete = function (cb) { onDelete = cb }
  568.  
  569. this.onCountryChange = function (name) {
  570. info('Country was changed to ' + name);
  571. rules.splice(customRulesNumber, rules.length - customRulesNumber);
  572. rules = rules.concat(getCountryRules(name));
  573. }
  574.  
  575. this.get = function (index) {
  576. return rules[index];
  577. }
  578.  
  579. this.correct = function (variant, text) {
  580. var newtext = text;
  581. var experimental = false;
  582.  
  583. for (var i = 0; i < rules.length; ++i) {
  584. var rule = rules[i];
  585.  
  586. if (rule.experimental && !this.experimental) continue;
  587.  
  588. if (rule.variant && rule.variant != variant) continue;
  589.  
  590. var previous = newtext;
  591. newtext = rule.correct(newtext);
  592. var changed = (previous != newtext);
  593. if (rule.experimental && previous != newtext) {
  594. experimental = true;
  595. }
  596. previous = newtext;
  597. // if (rule.custom && changed) {
  598. // // prevent result overwriting by common rules
  599. // break;
  600. // }
  601. }
  602.  
  603. return {
  604. value: newtext,
  605. experimental: experimental
  606. };
  607. }
  608.  
  609. var save = function (rules) {
  610. if (localStorage) {
  611. localStorage.setItem('assistRulesKey', JSON.stringify(rules.slice(0, customRulesNumber)));
  612. }
  613. }
  614.  
  615. this.load = function () {
  616. if (localStorage) {
  617. var str = localStorage.getItem('assistRulesKey');
  618. if (str) {
  619. var arr = JSON.parse(str);
  620. for (var i = 0; i < arr.length; ++i) {
  621. var rule = arr[i];
  622. this.push(rule.oldname, rule.newname);
  623. }
  624. }
  625. }
  626.  
  627. rules = rules.concat(getCountryRules(countryName));
  628. }
  629.  
  630. this.push = function (oldname, newname) {
  631. var rule = new CustomRule(oldname, newname);
  632. rules.splice(customRulesNumber++, 0, rule);
  633. onAdd(rule);
  634.  
  635. save(rules);
  636. }
  637.  
  638. this.update = function (index, oldname, newname) {
  639. var rule = new CustomRule(oldname, newname);
  640. rules[index] = rule;
  641. onEdit(index, rule);
  642.  
  643. save(rules);
  644. }
  645.  
  646. this.remove = function (index) {
  647. rules.splice(index, 1);
  648. --customRulesNumber;
  649. onDelete(index);
  650.  
  651. save(rules);
  652. }
  653. }
  654.  
  655. var ActionHelper = function (wazeapi) {
  656. var WazeActionUpdateObject = require("Waze/Action/UpdateObject");
  657. var WazeActionAddOrGetStreet = require("Waze/Action/AddOrGetStreet");
  658. var WazeActionAddOrGetCity = require("Waze/Action/AddOrGetCity");
  659.  
  660. var ui;
  661.  
  662. var type2repo = function (type) {
  663. var map = {
  664. 'venue': wazeapi.model.venues,
  665. 'segment': wazeapi.model.segments
  666. };
  667. return map[type];
  668. }
  669.  
  670. this.setUi = function (u) {
  671. ui = u;
  672. }
  673.  
  674. this.Select = function (id, type, center, zoom) {
  675. var attemptNum = 10;
  676.  
  677. var select = function () {
  678. info('select: ' + id);
  679.  
  680. var obj = type2repo(type).objects[id];
  681.  
  682. wazeapi.model.events.unregister('mergeend', map, select);
  683.  
  684. if (obj) {
  685. wazeapi.selectionManager.select([obj]);
  686. } else if (--attemptNum > 0) {
  687. wazeapi.model.events.register('mergeend', map, select);
  688. }
  689.  
  690. WME_Assist.debug("Attempt number left: " + attemptNum);
  691.  
  692. wazeapi.map.setCenter(center, zoom);
  693. }
  694.  
  695. return select;
  696. }
  697.  
  698. this.isObjectVisible = function (obj) {
  699. if (!onlyVisible) return true;
  700. if (obj.geometry)
  701. return wazeapi.map.getExtent().intersectsBounds(obj.geometry.getBounds());
  702. return false;
  703. }
  704.  
  705. var addOrGetStreet = function (cityId, name, isEmpty) {
  706. var foundStreets = wazeapi.model.streets.getByAttributes({
  707. cityID: cityId,
  708. name: name,
  709. });
  710.  
  711. if (foundStreets.length == 1)
  712. return foundStreets[0];
  713.  
  714. var city = wazeapi.model.cities.objects[cityId];
  715. var a = new WazeActionAddOrGetStreet(name, city, isEmpty);
  716. wazeapi.model.actionManager.add(a);
  717.  
  718. return a.street;
  719. }
  720.  
  721. var addOrGetCity = function (countryID, stateID, cityName) {
  722. var foundCities = Waze.model.cities.getByAttributes({
  723. countryID: countryID,
  724. stateID: stateID,
  725. name : cityName
  726. });
  727.  
  728. if (foundCities.length == 1)
  729. return foundCities[0];
  730.  
  731. var state = Waze.model.states.objects[stateID];
  732. var country = Waze.model.countries.objects[countryID];
  733. var a = new WazeActionAddOrGetCity(state, country, cityName);
  734. Waze.model.actionManager.add(a);
  735. return a.city;
  736. }
  737.  
  738. var cityMap = {};
  739. var onlyVisible = false;
  740.  
  741. this.newCityID = function (id) {
  742. var newid = cityMap[id];
  743. if (newid) return newid;
  744. return id;
  745. }
  746.  
  747. this.renameCity = function (oldname, newname) {
  748. var oldcity = wazeapi.model.cities.getByAttributes({name: oldname});
  749.  
  750. if (oldcity.length === 0) {
  751. console.log('City not found: ' + oldname);
  752. return false;
  753. }
  754.  
  755. var city = oldcity[0];
  756. var newcity = addOrGetCity(city.countryID, city.stateID, newname);
  757.  
  758. cityMap[city.getID()] = newcity.getID();
  759. onlyVisible = true;
  760.  
  761. console.log('Do not forget press reset button and re-enable script');
  762. return true;
  763. }
  764.  
  765. this.fixProblem = function (problem) {
  766. var deferred = $.Deferred();
  767. var attemptNum = 10; // after that we decide that object was removed
  768.  
  769. var fix = function () {
  770. var obj = type2repo(problem.object.type).objects[problem.object.id];
  771. wazeapi.model.events.unregister('mergeend', map, fix);
  772.  
  773. if (obj) {
  774. // protect user manual fix
  775. var currentValue = wazeapi.model.streets.objects[obj.attributes[problem.attrName]].name;
  776. if (problem.reason == currentValue) {
  777. var correctStreet = addOrGetStreet(problem.cityId, problem.newStreetName, problem.isEmpty);
  778. var request = {};
  779. request[problem.attrName] = correctStreet.getID();
  780. wazeapi.model.actionManager.add(new WazeActionUpdateObject(obj, request));
  781. } else {
  782. ui.updateProblem(problem.object.id, '(ручное: ' + currentValue + ')');
  783. }
  784. deferred.resolve(obj.getID());
  785. } else if (--attemptNum <= 0) {
  786. ui.updateProblem(problem.object.id, '(Не исправлено. Удалено?)');
  787. deferred.resolve(problem.object.id);
  788. } else {
  789. wazeapi.model.events.register('mergeend', map, fix);
  790. wazeapi.map.setCenter(problem.detectPos, problem.zoom);
  791. }
  792.  
  793. WME_Assist.debug('Attempt number left: ' + attemptNum);
  794. }
  795.  
  796. fix();
  797.  
  798. return deferred.promise();
  799. }
  800. }
  801.  
  802. var Ui = function () {
  803. var addon = document.createElement('section');
  804. addon.innerHTML = '<b>WME Assist</b> v' + ver;
  805.  
  806. var section = document.createElement('p');
  807. section.style.paddingTop = "8px";
  808. section.style.textIndent = "16px";
  809. section.id = "assist_options";
  810. section.innerHTML = '<b>Настройки:</b><br/>' +
  811. '<label><input type="checkbox" id="assist_enabled" value="0"/> Включен</label><br/>' +
  812. '<label><input type="checkbox" id="assist_debug" value="0" checked/> Отладка</label><br/>';
  813. var variant = document.createElement('p');
  814. variant.id = 'variant_options';
  815. variant.innerHTML = '<b>Правила:</b><br/>' +
  816. '<label><input type="radio" name="assist_variant" value="Moscow" checked/> Россия</label><br/>' +
  817. '<label><input type="radio" name="assist_variant" value="Tula"/> Тула</label><br/>';
  818. section.appendChild(variant);
  819. addon.appendChild(section);
  820.  
  821. section = document.createElement('p');
  822. section.style.paddingTop = "8px";
  823. section.style.textIndent = "16px";
  824. section.id = "assist_custom_rules";
  825. $(section)
  826. .append($('<p>').addClass('message').css({'font-weight': 'bold'}).text('Пользовательские правила'))
  827. .append($('<button>').prop('id', 'assist_add_custom_rule').addClass('btn btn-default btn-primary').text('Добавить'))
  828. .append($('<button>').prop('id', 'assist_edit_custom_rule').addClass('btn btn-default').text('Изменить'))
  829. .append($('<button>').prop('id', 'assist_del_custom_rule').addClass('btn btn-default btn-warning').text('Удалить'))
  830. .append($('<ul>').addClass('result-list'));
  831. addon.appendChild(section);
  832.  
  833. section = document.createElement('p');
  834. section.style.paddingTop = "8px";
  835. section.style.textIndent = "16px";
  836. section.id = "assist_exceptions";
  837. $(section)
  838. .append($('<p title="Right click on error in list to add">').addClass('message').css({'font-weight': 'bold'}).text('Исключения'))
  839. .append($('<ul>').addClass('result-list'));
  840. addon.appendChild(section);
  841.  
  842. var newtab = document.createElement('li');
  843. newtab.innerHTML = '<a href="#sidepanel-assist" data-toggle="tab">Assist</a>';
  844. $('#user-info #user-tabs .nav-tabs').append(newtab);
  845.  
  846. addon.id = "sidepanel-assist";
  847. addon.className = "tab-pane";
  848. $('#user-info > div > .tab-content').append(addon);
  849.  
  850. var selectedCustomRule = -1;
  851.  
  852. this.selectedCustomRule = function () {
  853. return selectedCustomRule;
  854. }
  855.  
  856. this.addCustomRule = function (title) {
  857. var thisrule = $('<li>').addClass('result').click(function () {
  858. selectedCustomRule = $('#assist_custom_rules li.result').index(thisrule);
  859. info('index: ' + selectedCustomRule);
  860. $('#assist_custom_rules li.result').css({'background-color': ''});
  861. $('#assist_custom_rules li.result').removeClass('active');
  862. $(this).css({'background-color': 'lightblue'});
  863. $(this).addClass('active');
  864. }).hover(function () {
  865. $(this).css({
  866. cursor: 'pointer',
  867. 'background-color': 'lightblue'
  868. });
  869. }, function () {
  870. $(this).css({
  871. cursor: 'auto'
  872. });
  873. if (!$(this).hasClass('active')) {
  874. $(this).css({
  875. 'background-color': ''
  876. });
  877. }
  878. })
  879. .append($('<p>').addClass('additional-info clearfix').text(title))
  880. .appendTo($('#assist_custom_rules ul.result-list'));
  881. }
  882.  
  883. this.updateCustomRule = function (index, title) {
  884. $('#assist_custom_rules li.result').eq(index).find('p.additional-info').text(title);
  885. }
  886.  
  887. this.removeCustomRule = function (index) {
  888. $('#assist_custom_rules li.result').eq(index).remove();
  889. selectedCustomRule = -1;
  890. }
  891.  
  892. this.addException = function (name, del) {
  893. var thisrule = $('<li>').addClass('result').click(function () {
  894. var index = $('#assist_exceptions li.result').index(thisrule);
  895. del(index);
  896. }).hover(function () {
  897. $(this).css({
  898. cursor: 'pointer',
  899. 'background-color': 'lightblue'
  900. });
  901. }, function () {
  902. $(this).css({
  903. cursor: 'auto'
  904. });
  905. if (!$(this).hasClass('active')) {
  906. $(this).css({
  907. 'background-color': ''
  908. });
  909. }
  910. })
  911. .append($('<p>').addClass('additional-info clearfix').text(name))
  912. .appendTo($('#assist_exceptions ul.result-list'));
  913. }
  914.  
  915. this.removeException = function (index) {
  916. $('#assist_exceptions li.result').eq(index).remove();
  917. }
  918.  
  919. this.showMainWindow = function () {
  920. $('#WazeMap').css('overflow', 'hidden');
  921. mainWindow.dialog('open');
  922. mainWindow.dialog('option', 'position', {
  923. my: 'right top',
  924. at: 'right-50 top',
  925. of: '#WazeMap',
  926. });
  927. // Minimize window
  928. mainWindow.prev('.ui-dialog-titlebar').find('button').click();
  929. }
  930.  
  931. this.hideMainWindow = function () {
  932. mainWindow.dialog('close');
  933. }
  934.  
  935. $('<div>', {
  936. id: 'WME_AssistWindow',
  937. title: 'WME Assist',
  938. })
  939. .append($('<div>').css({
  940. padding: 10,
  941. })
  942. .append($('<button id="assist_fixall_btn" class="btn btn-danger">Исправить</button>'))
  943. .append($('<button id="assist_scanarea_btn" class="btn btn-warning">Скан</button>'))
  944. .append($('<button id="assist_clearfixed_btn" class="btn btn-success">Очистить</button>'))
  945. .append($('<h2>Проблемы</h2>').css({
  946. 'font-size': '100%',
  947. 'font-weight': 'bold',
  948. }))
  949. .append($('<ol id="assist_unresolved_list"></ol>').css({
  950. border: '1px solid lightgrey',
  951. 'padding-top': 2,
  952. 'padding-bottom': 2,
  953. })))
  954. .append($('<div>').css({
  955. padding: 10,
  956. })
  957. .append($('<h2>Исправлено</h2>').css({
  958. 'font-size': '100%',
  959. 'font-weight': 'bold',
  960. }))
  961. .append($('<ol id="assist_fixed_list"></ol>').css({
  962. border: '1px solid lightgrey',
  963. 'padding-top': 2,
  964. 'padding-bottom': 2,
  965. })))
  966. .appendTo($('#WazeMap'));
  967.  
  968. $('<div>').prop('id', 'assist_custom_rule_dialog')
  969. .append($('<p>Заполните все поля</p>'))
  970. .append($('<form>')
  971. .append($('<fieldset>')
  972. .append($('<label>').prop('for', 'oldname').text('Регулярное выражение'))
  973. .append($('<input>', {
  974. type: 'text',
  975. name: 'oldname',
  976. 'class': 'text ui-widget-content ui-corner-all',
  977. id: 'oldname',
  978. }))
  979. .append($('<label>').prop('for', 'newname').text('Текст замены'))
  980. .append($('<input>', {
  981. type: 'text',
  982. name: 'newname',
  983. 'class': 'text ui-widget-content ui-corner-all',
  984. id: 'newname',
  985. }))
  986. )
  987. )
  988. .appendTo($('#map'));
  989.  
  990.  
  991. $('#assist_custom_rule_dialog label').css({display: 'block'});
  992. $('#assist_custom_rule_dialog input').css({display: 'block', width: '100%'});
  993.  
  994. var customRuleDialog_Ok = function () {}
  995. var customRuleDialog = $('#assist_custom_rule_dialog').dialog({
  996. autoOpen: false,
  997. height: 300,
  998. width: 350,
  999. modal: true,
  1000. buttons: {
  1001. Ok: function () {
  1002. customRuleDialog_Ok();
  1003. customRuleDialog.dialog('close');
  1004. },
  1005. Cancel: function () {
  1006. customRuleDialog.dialog('close');
  1007. }
  1008. }
  1009. });
  1010.  
  1011. var mainWindow = $('#WME_AssistWindow').dialog({
  1012. autoOpen: false,
  1013. appendTo: $('#WazeMap'),
  1014. width: 500,
  1015. draggable: true,
  1016. height: 600,
  1017. dragStop: function () {
  1018. $('#WME_AssistWindow').parent().css('height', 'auto');
  1019. }
  1020. });
  1021. mainWindow.parent('.ui-dialog').css({
  1022. 'zIndex': 1040,
  1023. 'opacity': '0.9',
  1024. });
  1025. mainWindow.prev('.ui-dialog-titlebar').css('background','lightblue');
  1026. mainWindow.prev('.ui-dialog-titlebar').find('.ui-dialog-title').append($('<span> - </span>'));
  1027. mainWindow.prev('.ui-dialog-titlebar').find('.ui-dialog-title')
  1028. .append($('<span>', {
  1029. id: 'assist-error-num',
  1030. title: 'Количество нерешенных проблем',
  1031. text: 0,
  1032. }).css({color: 'red'}));
  1033. mainWindow.prev('.ui-dialog-titlebar').find('.ui-dialog-title').append($('<span> / </span>'));
  1034. mainWindow.prev('.ui-dialog-titlebar').find('.ui-dialog-title')
  1035. .append($('<span>', {
  1036. id: 'assist-fixed-num',
  1037. title: 'Количество исправленных',
  1038. text: 0,
  1039. }).css({color: 'green'}));
  1040. mainWindow.prev('.ui-dialog-titlebar').find('.ui-dialog-title').append($('<span> - </span>'));
  1041. mainWindow.prev('.ui-dialog-titlebar').find('.ui-dialog-title')
  1042. .append($('<span>', {
  1043. id: 'assist-scan-progress',
  1044. title: 'Прогресс сканирования',
  1045. text: 0,
  1046. }).css({color: 'blue'}));
  1047.  
  1048. // Hack jquery ui dialog
  1049. var icon = mainWindow.prev('.ui-dialog-titlebar').find('span.ui-icon');
  1050. if (!icon.hasClass('ui-icon-minusthick')) {
  1051. icon.addClass('ui-icon-minusthick');
  1052. }
  1053. if (icon.hasClass('ui-icon-closethick')) {
  1054. icon.removeClass('ui-icon-closethick');
  1055. }
  1056. var btn = mainWindow.prev('.ui-dialog-titlebar').find('button');
  1057. mainWindow.prev('.ui-dialog-titlebar').find('button').unbind('click');
  1058. var visible = true;
  1059. var height;
  1060. mainWindow.prev('.ui-dialog-titlebar').find('button').click(function () {
  1061. if ($('#WME_AssistWindow').is(':visible')) {
  1062. $('#WME_AssistWindow').hide();
  1063. btn.prop('title', 'Развернуть');
  1064.  
  1065. icon.removeClass('ui-icon-minusthick');
  1066. icon.addClass('ui-icon-arrow-4-diag');
  1067. } else {
  1068. $('#WME_AssistWindow').show();
  1069. btn.prop('title', 'Свернуть');
  1070.  
  1071. icon.addClass('ui-icon-minusthick');
  1072. icon.removeClass('ui-icon-arrow-4-diag');
  1073. }
  1074. })
  1075.  
  1076. var self = this;
  1077.  
  1078. this.addProblem = function (id, text, func, exception, experimental) {
  1079. var problem = $('<li>')
  1080. .prop('id', 'issue-' + id)
  1081. .append($('<a>', {
  1082. href: "javascript:void(0)",
  1083. text: text,
  1084. click: function (event) {
  1085. func(event);
  1086. },
  1087. contextmenu: function (event) {
  1088. exception(event);
  1089. event.preventDefault();
  1090. event.stopPropagation();
  1091. },
  1092. }))
  1093. .appendTo($('#assist_unresolved_list'));
  1094.  
  1095. if (experimental) {
  1096. problem.children().css({color: 'red'}).prop('title', 'Experimental rule');
  1097. }
  1098. }
  1099.  
  1100. this.updateProblem = function (id, text) {
  1101. var a = $('li#issue-' + escapeId(id) + ' > a');
  1102. a.text(a.text() + ' ' + text);
  1103. }
  1104.  
  1105. this.setUnresolvedErrorNum = function (text) {
  1106. $('#assist-error-num').text(text);
  1107. }
  1108.  
  1109. this.setFixedErrorNum = function (text) {
  1110. $('#assist-fixed-num').text(text);
  1111. }
  1112.  
  1113. this.setScanProgress = function (text) {
  1114. $('#assist-scan-progress').text(text);
  1115. }
  1116.  
  1117. var escapeId = function (id) {
  1118. return String(id).replace(/\./g, "\\.");
  1119. }
  1120.  
  1121. this.moveToFixedList = function (id) {
  1122. $("#issue-" + escapeId(id)).appendTo($('#assist_fixed_list'));
  1123. }
  1124.  
  1125. this.removeError = function (id) {
  1126. $("#issue-" + escapeId(id)).remove();
  1127. }
  1128.  
  1129. var fixallBtn = $('#assist_fixall_btn');
  1130. var clearfixedBtn = $('#assist_clearfixed_btn');
  1131. var scanAreaBtn = $('#assist_scanarea_btn');
  1132. var unresolvedList = $('#assist_unresolved_list');
  1133. var fixedList = $('#assist_fixed_list');
  1134. var enableCheckbox = $('#assist_enabled');
  1135.  
  1136. var addCustomRuleBtn = $('#assist_add_custom_rule');
  1137. var editCustomRuleBtn = $('#assist_edit_custom_rule');
  1138. var delCustomRuleBtn = $('#assist_del_custom_rule');
  1139.  
  1140. this.fixallBtn = function () { return fixallBtn }
  1141. this.clearfixedBtn = function () { return clearfixedBtn }
  1142. this.scanAreaBtn = function () { return scanAreaBtn }
  1143.  
  1144. this.unresolvedList = function () { return unresolvedList }
  1145. this.fixedList = function () { return fixedList }
  1146.  
  1147. this.enableCheckbox = function () { return enableCheckbox }
  1148. this.variantRadio = function (value) {
  1149. if (!value) {
  1150. return $('[name=assist_variant]');
  1151. }
  1152.  
  1153. return $('[name=assist_variant][value=' + value + ']');
  1154. }
  1155.  
  1156. this.addCustomRuleBtn = function () { return addCustomRuleBtn }
  1157. this.editCustomRuleBtn = function () { return editCustomRuleBtn }
  1158. this.delCustomRuleBtn = function () { return delCustomRuleBtn }
  1159. this.customRuleDialog = function (title, params) {
  1160. var deferred = $.Deferred();
  1161.  
  1162. if (params) {
  1163. customRuleDialog.find('#oldname').val(params.oldname);
  1164. customRuleDialog.find('#newname').val(params.newname);
  1165. }
  1166.  
  1167. customRuleDialog_Ok = function () {
  1168. deferred.resolve({
  1169. oldname: customRuleDialog.find('#oldname').val(),
  1170. newname: customRuleDialog.find('#newname').val(),
  1171. });
  1172. }
  1173.  
  1174. customRuleDialog.dialog('option', 'title', title);
  1175. customRuleDialog.dialog('open');
  1176.  
  1177. return deferred.promise();
  1178. }
  1179. this.variant = function () {
  1180. return $('[name=assist_variant]:checked')[0].value;
  1181. }
  1182. };
  1183.  
  1184. var Application = function (wazeapi) {
  1185. var scaner = new WME_Assist.Scaner(wazeapi);
  1186. var analyzer = new WME_Assist.Analyzer(wazeapi);
  1187.  
  1188. var FULL_ZOOM_LEVEL = 5;
  1189.  
  1190. var scanForZoom = function (zoom) {
  1191. scaner.scan(wazeapi.map.calculateBounds(), zoom, function (bounds, zoom, data) {
  1192. console.log(data);
  1193. analyzer.analyze(bounds, zoom, data, function (obj, title, reason) {
  1194. ui.addProblem(obj.id, title, action.Select(obj.id, obj.type, obj.center, zoom), function () {
  1195. analyzer.addException(reason, function (id) {
  1196. ui.removeError(id);
  1197. ui.setUnresolvedErrorNum(analyzer.unresolvedErrorNum());
  1198. });
  1199. }, false);
  1200.  
  1201. ui.setUnresolvedErrorNum(analyzer.unresolvedErrorNum());
  1202. });
  1203. }, function (progress) {
  1204. ui.setScanProgress(Math.round(progress) + '%');
  1205. });
  1206. }
  1207.  
  1208. var fullscan = function () {
  1209. scanForZoom(FULL_ZOOM_LEVEL);
  1210. }
  1211.  
  1212. var scan = function () {
  1213. scanForZoom(wazeapi.map.getZoom());
  1214. }
  1215.  
  1216. var countryName = function () {
  1217. var id = wazeapi.model.countries.top.id;
  1218. var name = wazeapi.model.countries.objects[id].name;
  1219. return name;
  1220. }
  1221.  
  1222. var country = countryName();
  1223.  
  1224. var action = new ActionHelper(wazeapi);
  1225. var rules = new Rules(country);
  1226. var ui = new Ui();
  1227.  
  1228. analyzer.setRules(rules);
  1229. analyzer.setActionHelper(action);
  1230.  
  1231. action.setUi(ui);
  1232.  
  1233. analyzer.onExceptionAdd(function (name) {
  1234. ui.addException(name, function (index) {
  1235. if (confirm('Удалить исключение: ' + name + '?')) {
  1236. analyzer.removeException(index);
  1237. }
  1238. });
  1239. });
  1240.  
  1241. analyzer.onExceptionDelete(function (index) {
  1242. ui.removeException(index);
  1243. });
  1244.  
  1245. // rules.experimental = true;
  1246.  
  1247. rules.onAdd(function (rule) {
  1248. ui.addCustomRule(rule.comment);
  1249. });
  1250.  
  1251. rules.onEdit(function (index, rule) {
  1252. ui.updateCustomRule(index, rule.comment);
  1253. });
  1254.  
  1255. rules.onDelete(function (index) {
  1256. ui.removeCustomRule(index);
  1257. });
  1258.  
  1259. wazeapi.model.events.register('mergeend', map, function () {
  1260. var name = countryName();
  1261. if (name != country) {
  1262. rules.onCountryChange(name);
  1263. country = name;
  1264. }
  1265. });
  1266.  
  1267. analyzer.loadExceptions();
  1268. rules.load();
  1269.  
  1270. this.start = function () {
  1271. ui.enableCheckbox().click(function () {
  1272. if (ui.enableCheckbox().is(':checked')) {
  1273. localStorage.setItem('assist_enabled', true);
  1274. ui.showMainWindow();
  1275.  
  1276. info('enabled');
  1277.  
  1278. var savedVariant = localStorage.getItem('assist_variant');
  1279. if (savedVariant != null) {
  1280. ui.variantRadio(savedVariant).prop('checked', true);
  1281. analyzer.setVariant(ui.variant());
  1282. }
  1283.  
  1284. scan();
  1285. wazeapi.model.events.register('mergeend', map, scan);
  1286. } else {
  1287. localStorage.setItem('assist_enabled', false);
  1288. ui.hideMainWindow();
  1289.  
  1290. info('disabled');
  1291.  
  1292. wazeapi.model.events.unregister('mergeend', map, scan);
  1293. }
  1294. });
  1295.  
  1296. ui.variantRadio().click(function () {
  1297. localStorage.setItem('assist_variant', this.value);
  1298.  
  1299. analyzer.setVariant(ui.variant());
  1300. ui.scanAreaBtn().click();
  1301. });
  1302.  
  1303. if (localStorage.getItem('assist_enabled') == 'true') {
  1304. ui.enableCheckbox().click();
  1305. }
  1306.  
  1307. ui.fixallBtn().click(function () {
  1308. ui.fixallBtn().hide();
  1309. ui.clearfixedBtn().hide();
  1310. ui.scanAreaBtn().hide();
  1311.  
  1312. wazeapi.model.events.unregister('mergeend', map, scan);
  1313.  
  1314. setTimeout(function () {
  1315. analyzer.fixAll(function (id) {
  1316. ui.setUnresolvedErrorNum(analyzer.unresolvedErrorNum());
  1317. ui.setFixedErrorNum(analyzer.fixedErrorNum());
  1318. ui.moveToFixedList(id);
  1319. }, function () {
  1320. ui.fixallBtn().show();
  1321. ui.clearfixedBtn().show();
  1322. ui.scanAreaBtn().show();
  1323.  
  1324. wazeapi.model.events.register('mergeend', map, scan);
  1325. });
  1326. }, 0);
  1327. });
  1328.  
  1329. ui.clearfixedBtn().click(function () {
  1330. ui.fixedList().empty();
  1331. });
  1332.  
  1333. ui.scanAreaBtn().click(function () {
  1334. ui.fixedList().empty();
  1335. ui.unresolvedList().empty();
  1336.  
  1337. analyzer.reset();
  1338.  
  1339. ui.setUnresolvedErrorNum(0);
  1340. ui.setFixedErrorNum(0);
  1341.  
  1342. fullscan();
  1343. });
  1344.  
  1345. ui.addCustomRuleBtn().click(function () {
  1346. ui.customRuleDialog('Add', {
  1347. oldname: '',
  1348. newname: ''
  1349. }).done(function (response) {
  1350. rules.push(response.oldname, response.newname);
  1351. });
  1352. });
  1353.  
  1354. ui.editCustomRuleBtn().click(function () {
  1355. var id = ui.selectedCustomRule();
  1356. if (id >= 0) {
  1357. ui.customRuleDialog('Edit', {
  1358. oldname: rules.get(id).oldname,
  1359. newname: rules.get(id).newname
  1360. }).done(function (response) {
  1361. rules.update(id, response.oldname, response.newname);
  1362. });
  1363. } else {
  1364. alert('Правило не выбрано');
  1365. }
  1366. });
  1367.  
  1368. ui.delCustomRuleBtn().click(function () {
  1369. var id = ui.selectedCustomRule();
  1370. if (id >= 0) {
  1371. rules.remove(id);
  1372. } else {
  1373. alert('Правило не выбрано');
  1374. }
  1375. });
  1376.  
  1377. window.assist = this;
  1378. }
  1379.  
  1380. this.renameCity = action.renameCity;
  1381. };
  1382.  
  1383. function waitForWaze(done) {
  1384. var wazeapi = getWazeApi();
  1385.  
  1386. // Does not get jQuery.ui
  1387. // Relies on WME Toolbox plugin
  1388. if (wazeapi === null || !jQuery.ui) {
  1389. console.log("WME ASSIST: waiting for Waze");
  1390. setTimeout(function () {
  1391. waitForWaze(done);
  1392. }, 500);
  1393. return;
  1394. }
  1395.  
  1396. done(wazeapi);
  1397. }
  1398.  
  1399. function getByAttr(obj, attr) {
  1400. return obj.getByAttributes().filter(function (e) {
  1401. for (var key in attr) {
  1402. if (e.attributes[key] != attr[key]) {
  1403. return false;
  1404. }
  1405. }
  1406.  
  1407. return true;
  1408. });
  1409. }
  1410.  
  1411. waitForWaze(function (wazeapi) {
  1412. var app = new Application(wazeapi);
  1413. app.start();
  1414. });
  1415. }
  1416.  
  1417. run_wme_assist();