您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show area and point place names in WME, color and highlight places by type and properties
// ==UserScript== // @name WME PlaceNames Russian // @version 0.76 // @description Show area and point place names in WME, color and highlight places by type and properties // @include https://www.waze.com/editor* // @include https://www.waze.com/*/editor* // @include https://beta.waze.com/editor* // @include https://beta.waze.com/*/editor* // @copyright Vinkoy, ragacs // @namespace https://greasyfork.org/en/scripts/15310-wme-placenames-russian // @grant none // ==/UserScript== var wmepn_translations = { "en": { enable_script: "Enable script", enable_script_tooltip: "Toggle highlighting and place names layer\nUse the Layer selector or Shift+N hotkey to toggle names only", color_places: "Color places", color_places_tooltip: "Color the places like WMECH does", highlight_places: "Highlight places without name/HN", highlight_places_tooltip: "Highlight public places without name and private places without house number (yellow)", highlight_address: "Places without address", highlight_address_tooltip: "Highlight places without street or housenumber (dashed green)", highlight_dif_address: "The name doesn't match the housenumber", highlight_dif_address_tooltip: "Check if the name mathes the housenumber. Highlights (Other and Public places), where the name doesn't match the housenumber (dashed red)", highlight_small: "Place area less than", highlight_small_tooltip: "Highlight places area less than specified (red). Small places may not be visible in app", highlight_linked: "Linked places", highlight_linked_tooltip: "Highlight places linked to Google (cyan)", highlight_not_linked: "Not linked places", highlight_not_linked_tooltip: "Highlight places NOT linked to Google (cyan)", show_address: "Show address", show_address_tooltip: "Show address under the name", show: "Show", show_tooltip: "Select desired names to show", option_area: "Area", option_point: "POI", option_residential: "Residential", option_comments: "Comments", filter: "Filter", filter_tooltip: "Filter only the names containing this string (you could use regex e.g. /school/i)", show_locklevel: "Show lock level", show_locklevel_tooltip: "Display lock level after the name, like [L3] or [L4]", stop_over: "Stop over", stop_over_tooltip: "Limit displayed place names to the specified value", option_unlimited: "Unlimited", show_zool: "Zoom", show_zool_tooltip: "Minimum zoom to display the names", showing: "Showing", place_names_and: { one: "place name and", other: "place names and" }, house_numbers: { one: "house number", other: "house numbers" }, translator: "translated by [Your Waze Nickname]" }, "hu": { enable_script: "Szkript engedélyezése", enable_script_tooltip: "A színezés, kiemelés és a Helynevek réteg bekapcsolása\nHasználd a rétegválasztót vagy a Shift+N forróbillentyűt, ha csak a neveket akarod kapcsolgatni", color_places: "Helyek színezése", color_places_tooltip: "Helyek színezése, ahogyan a WMECH teszi", highlight_places: "Név/hsz nélküli helyek kiemelése", highlight_places_tooltip: "Kiemeli (sárgával) a névtelen nyilvános helyeket és a házszám nélküli magánházakat", show: "Mutasd", show_tooltip: "Válaszd ki a megmutatni kívánt neveket", option_area: "Csak terület", option_point: "Terület és pont", option_residential: "Csak pont", filter: "Szűrő", filter_tooltip: "Csak azokat a neveket mutassa, amik ezt a szöveget tartalmazzák (reguláris kifejezések használhatók, pl. /iskola/i)", show_locklevel: "Védelem mutatása", show_locklevel_tooltip: "Mutassa a védelmi szintet is a név után, pl. [L3] vagy [L4]", stop_over: "Maximum", stop_over_tooltip: "A képernyőn egyszerre látható név-feliratok számát korlátozza", option_unlimited: "Korlátlan", showing: "Látható", place_names_and: "helynév és", house_numbers: "házszám", translator: "fordította ragacs" }, "cs": { enable_script: "Povolit skript", enable_script_tooltip: "Přepínač zvýraznění a jmen míst\nPoužijte menu Vrstvy nebo klávesovou zkratku Shift+N, aby se zobrazila jen jména míst", color_places: "Barevné odlišení", color_places_tooltip: "Barevné odlišení jako WMECH", highlight_places: "Odlišit nepojmenovaná místa", highlight_places_tooltip: "Odlišit nepojmenovaná veřejná místa a soukromá místa bez čísla domu (žlutě)", show: "Zobrazit", show_tooltip: "Zobrazit požadovaná jména", option_area: "Jen plochy", option_point: "Plochy a body", option_residential: "Jen body", filter: "Filtr", filter_tooltip: "Filtrovat jen jména obsahující tento řetězec (lze použít regex např. /škola/i)", show_locklevel: "Ukázat zámek", show_locklevel_tooltip: "Zobrazit zámek za jménem místa (např. [L3] nebo [L4])", stop_over: "Omezení", stop_over_tooltip: "Omezit zobrazená místa na zadanou hodnotu", option_unlimited: "Bez omezení", showing: "Zobrazení", place_names_and: "jména míst a", house_numbers: "čísla domů", translator: "překládal bures" }, "nl" : { enable_script: "Script inschakelen", enable_script_tooltip: "De laag voor het weergeven en markeren van plaatsnamen beheren\nGebruik de laagselector of Shift+N om enkel de namen te beheren", color_places: "Voeg kleur toe aan plaatsen", color_places_tooltip: "Kleur de plaatsen in zoals het WMECH-script dit doet", highlight_places: "Markeer plaatsen zonder naam of huisnummer", highlight_places_tooltip: "Markeer publieke plaatsen zonder naam en private plaatsen zonder huisnummer (geel)", show: "Weergave", show_tooltip: "Selecteer welke namen er moeten weergegeven worden", option_area: "Enkel gebieden", option_point: "Gebieden en punten", option_residential: "Enkel punten", filter: "Filter", filter_tooltip: "Toon enkel de plaatsen met de volgende naam (je kan ook een regex gebruiken zoals /school/i)", show_locklevel: "Lock-level weergeven", show_locklevel_tooltip: "Geef het lock-level weer achter de naam als [L3] of [L4]", stop_over: "Beperk aantal plaatsnamen", stop_over_tooltip: "Beperk het aantal weergegeven plaatsnamen tot dit aantal", option_unlimited: "Onbeperkt", showing: "Huidige weergave: ", place_names_and: { one: "plaatsnaam en", other: "plaatsnamen en" }, house_numbers: { one: "huisnummer", other: "huisnummers" }, translator: "vertaald door Glodenox" }, "uk":{ enable_script: "Включити скрипт", enable_script_tooltip: "Включити підсвічування та відображення імен POI", color_places: "Кольорові POI", color_places_tooltip: "Відображати кольорові POI в залежності від їх типу", highlight_places: "POI без імені", highlight_places_tooltip: "Підсвічувати POI без імені (жовтий)", highlight_address: "POI без адреси", highlight_address_tooltip: "ППідсвічувати POI, у яких не заповнені поля адреси: вулиця і номер будинку (зелений пунктир)", highlight_dif_address: "Ім'я не збігається з номером будинку", highlight_dif_address_tooltip: "Перевірка відповідності імені контура з номером будинку в адресі. Підсвічує POI (Інша / контур будівлі та Громадське місце), у яких ім'я не збігається з номером будинку в адресі (червоний пунктир)", highlight_small: "POI з площею менше ", highlight_small_tooltip: "Підсвічування POI з площею менше зазначеної (червоний). Маленькі POI можуть не відображатися в застосунку", highlight_linked: "Лінковані POI", highlight_linked_tooltip: "Підсвічувати POI, що мають прив'язку до адреси Google (блакитний)", highlight_not_linked: "Нелінковані POI", highlight_not_linked_tooltip: "Підсвічувати POI, без прив'язки до адреси Google (блакитний)", show_address: "Відображати адресу POI", show_address_tooltip: "Відображати адресу POI під ім'ям", show: "Відображати ім'я (адресу)", show_tooltip: "Вибрати для відображення імені", option_area: "Області", option_point: "Точкові POI", option_residential: "АТ", option_comments: "Коментарі", filter: "Фільтр", filter_tooltip: "Фільтр відображення в назві (можна використовувати regex, наприклад / школа/i)", show_locklevel: "Відображати рівень блокування", show_locklevel_tooltip: "Відображати рівень блокування після імені, наприклад, [L3] або [L4]", stop_over: "Кількість відображуваних імен", stop_over_tooltip: "Обмеження кількості відображуваних імен на карті", option_unlimited: "Без обмеження", show_zool: "Масштаб ", show_zool_tooltip: "Мінімальний масштаб для відображення імен", showing: "Відображається", place_names_and: "імен POI та", house_numbers: "АТ", translator: "translated and modified by Vinkoy" }, "ru": { enable_script: "Включить скрипт", enable_script_tooltip: "Включить подсветку и отображение имен POI", color_places: "Цветные POI", color_places_tooltip: "Отображать цветные POI в зависимости от их типа", highlight_places: "POI без имени", highlight_places_tooltip: "Подсвечивать POI без имени (желтый)", highlight_address: "POI без адреса", highlight_address_tooltip: "Подсвечивать POI, у которых не заполнены поля адреса: улица и номер дома (зеленый пунктир)", highlight_dif_address: "Имя не совпадает с номером дома", highlight_dif_address_tooltip: "Проверка соответствия имени контура с номером дома в адресе. Подсвечивает POI (Другое/контур здания и Общественное место), у которых имя не совпадает с номером дома в адресе (красный пунктир)", highlight_small: "POI с площадью менее", highlight_small_tooltip: "Подсветка POI с площадью меньше указанной (красный). Маленькие POI могут не отображаться в приложении", highlight_linked: "Линкованные POI", highlight_linked_tooltip: "Подсвечивать POI, имеющие привязку к адресу Google (голубой)", highlight_not_linked: "Нелинкованные POI", highlight_not_linked_tooltip: "Подсвечивать POI, без привязки к адресу Google (голубой)", show_address: "Отображать адрес POI", show_address_tooltip: "Отображать адрес POI под именем", show: "Отображать имя (адрес)", show_tooltip: "Выбрать для отображения имени", option_area: "Области", option_point: "POI-точки", option_residential: "ПТ", option_comments: "Комментарии", filter: "Фильтр", filter_tooltip: "Фильтр отображения по имени (можно использовать regex, например /школа/i)", show_locklevel: "Отображать уровень блокировки", show_locklevel_tooltip: "Отображать уровень блокировки после имени, например, [L3] или [L4]", stop_over: "Количество отображаемых имен", stop_over_tooltip: "Ограничение количества отображаемых имен на карте", option_unlimited: "Без ограничения", show_zool: "Масштаб", show_zool_tooltip: "Минимальный масштаб для отображения имен", showing: "Отображается", place_names_and: "имен POI и", house_numbers: "ПТ", translator: "translated and modified by Vinkoy" } }; // Using parts from highlight and route speed scripts by various authors /* bootstrap, will call initialiseLandmarkNames() */ function bootstrapLandmarkNames() { /* begin running the code! */ if (typeof W === "undefined" || typeof W.map === "undefined" || typeof W.selectionManager === "undefined" || typeof W.model.countries === "undefined" || typeof I18n === "undefined" || typeof I18n.translations === "undefined") { setTimeout(bootstrapLandmarkNames, 700); return; } initialiseLandmarkNames(); } function wmepn_wordWrap(str, maxWidth) { function testWhite(x) { var white = new RegExp(/^[ \t\r\n\f]$/); // We are not using \s because it matches non-breaking space too return white.test(x.charAt(0)); } var newLineStr = "\n"; done = false; res = ''; do { found = false; // Inserts new line at first whitespace of the line for (i = maxWidth - 1; i >= 0; i--) { if (testWhite(str.charAt(i))) { res = res + [str.slice(0, i), newLineStr].join(''); str = str.slice(i + 1); found = true; break; } } // Inserts new line at maxWidth position, the word is too long to wrap if (!found && str.length > maxWidth) { res += [str.slice(0, maxWidth), newLineStr].join(''); str = str.slice(maxWidth); } if (str.length < maxWidth) { res = res + str; done = true; } } while (!done); return res; } function wmepn_resetLandmarks() { var venues = W.model.venues; for (var mark in venues.objects) { var venue = venues.getObjectById(mark); var poly = wmepn_getId(venue.geometry.id); if (poly !== null) { if (poly.getAttribute("stroke-opacity") != 1) { poly.setAttribute("fill","#d191d6"); poly.setAttribute("stroke","#d191d6"); poly.setAttribute("fill-opacity",0.3); poly.setAttribute("stroke-opacity",1); } } } wmepn_showLandmarkNames(); } function wmepn_showLandmarkNames() { wmepn_NameLayer.removeAllFeatures(); if (typeof W.model.venues == "undefined" || wmepn_getId('_cbLandmarkNamesEnable').checked === false) { wmepn_getId('_stLandmarkNumber').innerHTML = 0; wmepn_getId('_stLandmarkHNNumber').innerHTML = 0; return; } var venues = W.model.venues; var streets = W.model.streets; var map = W.map; var showNames = wmepn_NameLayer.getVisibility() && map.getLayersBy("uniqueName", "venues")[0].getVisibility(); // if checkbox unticked, reset places to original style if (!showNames && !wmepn_getId('_cbLandmarkColors').checked && !wmepn_getId('_cbLandmarkHiliteNoName').checked && !wmepn_getId('_cbLandmarkHiliteNoAddress').checked && !wmepn_getId('_cbLandmarkHiliteDifHN').checked && !wmepn_getId('_cbLandmarkHiliteSmall').checked ) { for (var mark in venues.objects) { var venue = venues.getObjectById(mark); var poly = wmepn_getId(venue.geometry.id); if (poly !== null) { if (poly.getAttribute("stroke-opacity") != 1) { poly.setAttribute("fill","#d191d6"); poly.setAttribute("stroke","#d191d6"); poly.setAttribute("fill-opacity",0.3); poly.setAttribute("stroke-opacity",1); poly.setAttribute("stroke-dasharray","none"); } } } wmepn_getId('_stLandmarkNumber').innerHTML = 0; wmepn_getId('_stLandmarkHNNumber').innerHTML = 0; return; } var hiliteNoName = wmepn_getId('_cbLandmarkHiliteNoName').checked; var colorLandmarks = wmepn_getId('_cbLandmarkColors').checked; var hiliteNoAddress = wmepn_getId('_cbLandmarkHiliteNoAddress').checked; var hiliteDifHN = wmepn_getId('_cbLandmarkHiliteDifHN').checked; var hiliteSmall = wmepn_getId('_cbLandmarkHiliteSmall').checked; var hiliteLinked = wmepn_getId('_cbShowLinked').checked; var hiliteNotLinked = wmepn_getId('_cbShowNotLinked').checked; var showAddresses = wmepn_getId('_cbLandmarkShowAddresses').checked; var minArea = wmepn_getId('_minArea').value; var showPoints = wmepn_getId('_cbShowPoi').checked; var showAreas = wmepn_getId('_cbShowArea').checked; var showResidentials = wmepn_getId('_cbShowRH').checked; var showComments = wmepn_getId('_cbShowComment').checked; var showLockLevel = wmepn_getId('_cbLandmarkLockLevel').checked; var limitNames = wmepn_getId('_seLandmarkLimit').value; var nameFilterArray = wmepn_getId('_inLandmarkNameFilter').value.split("/"); var nameFilter = (nameFilterArray.length > 1 ? nameFilterArray[1] : nameFilterArray[0]); var nameFilterOptions = nameFilterArray[2]; var nameFilterRegEx = (nameFilterArray.length > 1 ? new RegExp(nameFilter, nameFilterOptions) : null); var doFilter = function (name) { if(nameFilter.length === 0) return true; // show all when no filter entered if(nameFilterRegEx === null) return (name.indexOf(nameFilter) >= 0); else return nameFilterRegEx.test(name); }; var drawnNames = 0; var drawnHNs = 0; for (var mark in venues.objects) { var venue = venues.getObjectById(mark); var poly = wmepn_getId(venue.geometry.id); var isPoint = venue.geometry.toString().match(/^POINT/); var isArea = venue.geometry.toString().match(/^POLYGON/); var isRH = venue.attributes.residential; var houseNumber = venue.attributes.houseNumber; var trimmedName = isRH ? houseNumber : venue.attributes.name.trim(); var noTrName = (trimmedName.length == 0); if(showLockLevel) trimmedName += (noTrName ? "" : "\n") + "[L" + (venue.attributes.lockRank+1) + "]"; if (poly !== null) { var venueStreet = streets.getObjectById(venue.attributes.streetID); var haveNoName = (isRH ? (houseNumber.length == 0) : noTrName); var hasHN = houseNumber != "" && houseNumber != null; var hasStreet = venueStreet != null && venueStreet.name != null && venueStreet.name != ""; var haveNoAddress = !hasHN || !hasStreet; if(showNames && (showAreas || showPoints || showResidentials) && (limitNames == 0 || drawnNames < limitNames) && (map.zoom >= wmepn_getId('_zoomLevel').value)) { var wrappedText = wmepn_wordWrap(trimmedName, 30); var addressText = ""; if(showAddresses && ( showAreas && isArea || showPoints && isPoint || showResidentials && isRH) ) { // how many words in POI name (needed to determine offsetY below) var words = wrappedText.replace(/\n/g,' ') + ' '; words = words.split(/\s* \s*/).length-1; addressText = hasStreet ? venueStreet.name.trim() : addressText; addressText = hasHN ? (hasStreet ? (addressText+", "+houseNumber) : houseNumber) : addressText; addressText = (addressText.length>0) ? ("("+addressText+")") : addressText; } var filterMatched = (!noTrName && doFilter(trimmedName))||(hasHN && isRH && doFilter(houseNumber))||(showAddresses && doFilter(addressText)); if(showAreas && isArea && filterMatched) { // Add label texts var labelFeatures = []; var bounds = venue.geometry.bounds; var pt; //if(bounds.getWidth() * bounds.getHeight() * .3 > venue.geometry.getArea() && venue.attributes.entryExitPoints.length > 0) // pt = venue.attributes.entryExitPoints[0].point; //else pt = venue.geometry.getCentroid(); var textFeature = new OpenLayers.Feature.Vector( pt, {labelText: wrappedText, fontColor: '#F0F0F0', pointRadius: 0 }); labelFeatures.push(textFeature); if(showAddresses) { var offsetY = wmepn_getYoffset(words, wrappedText.length); var addrFeature = new OpenLayers.Feature.Vector( pt, {labelText: addressText, style: "italic", pointRadius: 0, yOffset: offsetY } ); labelFeatures.push(addrFeature); } wmepn_NameLayer.addFeatures(labelFeatures); drawnNames++; } if(showPoints && isPoint && !isRH && filterMatched ) { // Add label texts var labelFeatures = []; var pt = new OpenLayers.Geometry.Point(venue.geometry.x, venue.geometry.y); var isHouseNumber = !filterMatched; var textFeature = new OpenLayers.Feature.Vector( pt, {labelText: wrappedText, fontColor: '#F0F0F0', pointRadius: 0, yOffset: 15 }); labelFeatures.push(textFeature); if(showAddresses) { var offsetY = wmepn_getYoffset(words, wrappedText.length); var addrFeature = new OpenLayers.Feature.Vector( pt, {labelText: addressText, style: "italic", pointRadius: 0, yOffset: offsetY } ); labelFeatures.push(addrFeature); } wmepn_NameLayer.addFeatures(labelFeatures); drawnNames++; } if(showResidentials && isPoint && isRH && filterMatched ) { // Add label texts var labelFeatures = []; var pt = new OpenLayers.Geometry.Point(venue.geometry.x, venue.geometry.y); var isHouseNumber = !filterMatched; var textFeature = new OpenLayers.Feature.Vector( pt, {labelText: wrappedText, style: "italic", fontColor: '#F0F0F0', pointRadius: 0, yOffset: 15 }); labelFeatures.push(textFeature); if(showAddresses) { var addrFeature = new OpenLayers.Feature.Vector( pt, {labelText: addressText, style: "italic", pointRadius: 0, yOffset: -15 } ); labelFeatures.push(addrFeature); } wmepn_NameLayer.addFeatures(labelFeatures); drawnHNs++; } } wmepn_getId('_stLandmarkNumber').innerHTML = drawnNames; wmepn_getId('_stLandmarkHNNumber').innerHTML = drawnHNs; if (W.selectionManager.getSelectedFeatures().length > 0 && W.selectionManager.getSelectedFeatures()[0].model.type === "venue") { var area_poi=document.getElementById('WME.PlaceNames-Square'); if(!area_poi) { var wcp=document.getElementsByClassName('additional-attributes list-unstyled side-panel-section'); if (wcp) { var li=document.createElement("LI"); li.setAttribute('id', 'WME.PlaceNames-Square'); wcp[0].appendChild(li); area_poi=document.getElementById('WME.PlaceNames-Square'); } } if(area_poi) { var v_id=W.selectionManager.getSelectedFeatures()[0].model.attributes.id; var getv=W.model.venues.getObjectById(v_id); if (typeof getv === "undefined" || typeof getv.geometry.getGeodesicArea === "undefined") area_poi.innerHTML=""; else { var square=W.model.venues.getObjectById(v_id).geometry.getGeodesicArea(W.map.getProjectionObject()); area_poi.style=(square < minArea)?"color: red;":"color: black;"; area_poi.innerHTML="Площадь: " + square.toFixed(2) + " м²" + "<a href='#' id='_modifyArea' title='Горячая клавиша \"Y\"'> (Сделать ~"+minArea+"м²) </a>"; $('#_modifyArea').click(modifyArea); } } } // Production polygons: #d191d6, Beta editor polygons: #c290c6 if ((poly.getAttribute("fill") == "#d191d6" || poly.getAttribute("fill") == "#c290c6") && poly.getAttribute("stroke-opacity") == 1) { var categories = venue.attributes.categories; var colored = false; if(colorLandmarks) { // gas station = orange if (categories.indexOf("GAS_STATION") > -1) { poly.setAttribute("fill","#f90"); poly.setAttribute("stroke","#f90"); colored = true; } // parking lot = cyan else if (categories.indexOf("PARKING_LOT") > -1) { poly.setAttribute("fill","#099"); poly.setAttribute("stroke","#0cc"); colored = true; } // water = blue else if (categories.indexOf("RIVER_STREAM") > -1 || categories.indexOf("SEA_LAKE_POOL") > -1) { poly.setAttribute("fill","#09f"); poly.setAttribute("stroke","#06c"); colored = true; } // park/grass/trees = green else if (categories.indexOf("PARK") > -1 || categories.indexOf("FARM") > -1 || categories.indexOf("FOREST_GROVE") > -1 || categories.indexOf("GOLF_COURSE") > -1) { poly.setAttribute("fill","#4f4"); poly.setAttribute("stroke","#6a6"); colored = true; } } poly.setAttribute("stroke-opacity",0.97); poly.setAttribute("stroke-dasharray","none"); var isNature = 0; isNature = ((venue.attributes.categories[0] === "PARKING_LOT") || (venue.attributes.categories[0] === "RIVER_STREAM") || (venue.attributes.categories[0] === "SEA_LAKE_POOL") || (venue.attributes.categories[0] === "PARK") || (venue.attributes.categories[0] === "FARM") || (venue.attributes.categories[0] === "FOREST_GROVE") || (venue.attributes.categories[0] === "GOLF_COURSE") ); // highlight places with place surface area less than _minArea if (hiliteSmall && isArea && (W.map.zoom >= 3) && (venue.geometry.getGeodesicArea(W.map.getProjectionObject()) < minArea)) { poly.setAttribute("fill","#f00"); poly.setAttribute("stroke","#f00"); } // then highlight places which have no name and not colored else if (hiliteNoName && haveNoName && (colored === false) ) { poly.setAttribute("fill","#ff8"); poly.setAttribute("stroke","#cc0"); } // if was yellow and now not yellow, reset else if (poly.getAttribute("fill") == "#ff8" && (!hiliteNoName || !haveNoName)) { poly.setAttribute("fill","#d191d6"); poly.setAttribute("stroke","#d191d6"); poly.setAttribute("stroke-opacity",1); } // highlight places with linked Google address if (hiliteLinked && venue.attributes.externalProviderIDs.length > 0) { poly.setAttribute("stroke","#0ff"); colored = true; } // highlight places without linked Google address else if (hiliteNotLinked && !isRH && venue.attributes.externalProviderIDs.length === 0) { poly.setAttribute("stroke","#0ff"); colored = true; } // highlight places which have no address else if ( hiliteNoAddress && !isNature && haveNoAddress && (W.map.zoom >= wmepn_getId('_zoomLevel').value) ) { poly.setAttribute("stroke","#0f0"); poly.setAttribute("stroke-dasharray","4 7"); colored = true; } // highlight places which have different name and HN else if ( hiliteDifHN && (colored == false) && (map.zoom >= wmepn_getId('_zoomLevel').value) && hasHN && !haveNoName && ((venue.attributes.categories[0] === "OTHER") || (venue.attributes.categories[0] === "PROFESSIONAL_AND_PUBLIC")) && ( !(houseNumber == venue.attributes.name.trim() || houseNumber == venue.attributes.name.trim().split(',')[0]) ) ) { poly.setAttribute("stroke","#f00"); poly.setAttribute("stroke-dasharray","4 7"); colored = true; } } } } if (map.getLayersBy("uniqueName", "mapComments").length === 1 && map.getLayersBy("uniqueName", "mapComments")[0].getVisibility()) { for (var mark in W.model.mapComments.objects) { var comment = W.model.mapComments.getObjectById(mark); var poly = wmepn_getId(comment.geometry.id); var isPoint = comment.geometry.toString().match(/^POINT/); var isArea = comment.geometry.toString().match(/^POLYGON/); var isComment = comment.type === "mapComment"; var trimmedName = comment.attributes.subject; var noTrName = (trimmedName.length == 0); if(showLockLevel) trimmedName += (noTrName ? "" : "\n") + "[L" + (comment.attributes.lockRank+1) + "]"; if (poly !== null) { var haveNoName = noTrName; if(showComments && (limitNames == 0 || drawnNames < limitNames) && (map.zoom >= wmepn_getId('_zoomLevel').value)) { var wrappedText = wmepn_wordWrap(trimmedName, 30); var commentBody = ""; if(showAddresses && ( showComments && isComment) ) { // how many words in Comment subject (needed to determine offsetY below) var words = wrappedText.replace(/\n/g,' ') + ' '; words = words.split(/\s* \s*/).length-1; commentBody = comment.attributes.body === "" || comment.attributes.body === "undefined" ? commentBody : wmepn_wordWrap(comment.attributes.body, 30); var commentsWords = commentBody.replace(/\n/g,' ') + ' '; commentsWords = commentsWords.split(/\s* \s*/).length-1; } var filterMatched = (!noTrName && doFilter(trimmedName))||(showAddresses && doFilter(commentBody)); if(showComments && ( (showAreas && isArea) || (!showAreas && !showPoints)) && filterMatched) { // Add label texts var labelFeatures = []; var bounds = comment.geometry.bounds; var pt = comment.geometry.getCentroid(); var textFeature = new OpenLayers.Feature.Vector( pt, {labelText: wrappedText, fontColor: '#F0F0F0', pointRadius: 0 }); labelFeatures.push(textFeature); if(showAddresses) { var offsetY = wmepn_getYoffset(words, wrappedText.length); offsetY += wmepn_getYoffset(commentsWords, commentBody.length); var addrFeature = new OpenLayers.Feature.Vector( pt, {labelText: commentBody, style: "italic", pointRadius: 0, yOffset: offsetY } ); labelFeatures.push(addrFeature); } wmepn_NameLayer.addFeatures(labelFeatures); drawnNames++; } if(showComments && ( (showPoints && isPoint) || (!showAreas && !showPoints) ) && filterMatched ) { // Add label texts var labelFeatures = []; var pt = new OpenLayers.Geometry.Point(comment.geometry.x, comment.geometry.y); var textFeature = new OpenLayers.Feature.Vector( pt, {labelText: wrappedText, fontColor: '#F0F0F0', pointRadius: 0, yOffset: 15 }); labelFeatures.push(textFeature); if(showAddresses) { var offsetY = wmepn_getYoffset(words, wrappedText.length); offsetY += wmepn_getYoffset(commentsWords, commentBody.length); var addrFeature = new OpenLayers.Feature.Vector( pt, {labelText: commentBody, style: "italic", pointRadius: 0, yOffset: offsetY } ); labelFeatures.push(addrFeature); } wmepn_NameLayer.addFeatures(labelFeatures); drawnNames++; } } } } } wmepn_getId('_stLandmarkNumber').innerHTML = '<i>' + drawnNames + '</i> ' + I18n.t("wmepn.place_names_and", {count: drawnNames}); wmepn_getId('_stLandmarkHNNumber').innerHTML = '<i>' + drawnHNs + '</i> ' + I18n.t("wmepn.house_numbers", {count: drawnHNs}); //map.getLayersBy("name", "Place Names")[0].setZIndex(730); } function wmepn_getYoffset (words, length) { return (words == 1) ? (-12) : (((words > 1) && (length < 60 )) ? (-15) : (length < 90 ) ? (-20) : (length < 120 ) ? (-25) : (length < 150 ) ? (-30) : (length < 180 ) ? (-35) : (length < 210 ) ? (-40) : (length < 240 ) ? (-45) : (length < 270 ) ? (-50) : (-55)); } var modifyArea = function () { var selectorManager = W.selectionManager; if (!selectorManager.hasSelectedFeatures() || selectorManager.getSelectedFeatures()[0].model.type !== "venue" || !selectorManager.getSelectedFeatures()[0].model.isGeometryEditable()) { return; } var requiredArea = parseInt(wmepn_getId('_minArea').value,10) + 5; var SelectedLandmark = selectorManager.getSelectedFeatures()[0]; var oldGeometry = SelectedLandmark.geometry.clone(); var newGeometry = SelectedLandmark.geometry.clone(); var centerPT = newGeometry.getCentroid(); var oldArea = oldGeometry.getGeodesicArea(W.map.getProjectionObject()); var scale = Math.sqrt(requiredArea/oldArea); newGeometry.resize(scale,centerPT); var wazeActionUpdateFeatureGeometry = require("Waze/Action/UpdateFeatureGeometry"); var action = new wazeActionUpdateFeatureGeometry(SelectedLandmark.model, W.model.venues, oldGeometry, newGeometry); W.model.actionManager.add(action); }; function wmepn_toggleOptions () { return false; } /* helper function */ function wmepn_getId(node) { return document.getElementById(node); } /* =========================================================================== */ function initialiseLandmarkNames() { // global variables wmepn_betaMode = location.hostname.match(/editor-beta.W.com/); wmepn_NameLayer = undefined; // helper fn function getElementsByClassName(classname, node) { if(!node) node = document.getElementsByTagName("body")[0]; var a = []; var re = new RegExp('\\b' + classname + '\\b'); var els = node.getElementsByTagName("*"); for (var i=0,j=els.length; i<j; i++) if (re.test(els[i].className)) a.push(els[i]); return a; } // Some internationalization I18n.translations[I18n.locale].wmepn = wmepn_translations[I18n.locale]; if(wmepn_translations[I18n.locale] === undefined) I18n.translations[I18n.locale].wmepn = wmepn_translations[I18n.locale]; I18n.translations[I18n.locale].layers.name.__DrawPlaceNames = 'Place Names Rus'; // add new box to left of the map var addon = document.createElement('section'); var map = W.map; var translator=""; addon.id = "landmarkname-addon"; if(I18n.locale != I18n.locale) translator = 'title="'+I18n.t("wmepn.translator")+'"'; //if (navigator.userAgent.match(/Chrome/)) { } addon.innerHTML = '<b>' + '<a href="https://www.waze.com/forum/viewtopic.php?f=819&t=116843" target="_blank" ' + translator + '>' + GM_info.script.name + '</a></b> v' + GM_info.script.version; if(wmepn_translations[I18n.locale] === undefined) addon.innerHTML += ' <small>[<a href="https://www.waze.com/forum/viewtopic.php?f=819&t=116843&p=1302802#p1302802" target="_blank">translate me!</a>]</small>'; // highlight landmarks var section = document.createElement('p'); section.style.padding = "8px 16px"; //section.style.textIndent = "-16px"; section.id = "nameLandmarks"; section.innerHTML = '<div title="'+I18n.t("wmepn.enable_script_tooltip")+'"><input type="checkbox" id="_cbLandmarkNamesEnable" /> <b>'+I18n.t("wmepn.enable_script")+'</b></div>' + '<div title="'+I18n.t("wmepn.color_places_tooltip")+'"><input type="checkbox" id="_cbLandmarkColors" /> <b>'+I18n.t("wmepn.color_places")+'</b></div>' + '<div title="'+I18n.t("wmepn.highlight_places_tooltip")+'"><input type="checkbox" id="_cbLandmarkHiliteNoName"/> <b>'+I18n.t("wmepn.highlight_places")+'</b></div>' + '<div title="'+I18n.t("wmepn.highlight_address_tooltip")+'"><input type="checkbox" id="_cbLandmarkHiliteNoAddress"/> <b>'+I18n.t("wmepn.highlight_address")+'</b></div>' + '<div title="'+I18n.t("wmepn.highlight_dif_address_tooltip")+'"><input type="checkbox" id="_cbLandmarkHiliteDifHN"/> <b>'+I18n.t("wmepn.highlight_dif_address")+'</b></div>' + '<div title="'+I18n.t("wmepn.highlight_linked_tooltip")+'"><input type="checkbox" id="_cbShowLinked" /> <b>'+I18n.t("wmepn.highlight_linked")+'</b></div>' + '<div title="'+I18n.t("wmepn.highlight_not_linked_tooltip")+'"><input type="checkbox" id="_cbShowNotLinked" /> <b>'+I18n.t("wmepn.highlight_not_linked")+'</b></div>' + '<div title="'+I18n.t("wmepn.highlight_small_tooltip")+'"><input type="checkbox" id="_cbLandmarkHiliteSmall"/> <b>'+I18n.t("wmepn.highlight_small")+'</b><input id="_minArea" style="width: 40px;"/><b>м²</b></div>' + '<div title="'+I18n.t("wmepn.show_address_tooltip")+'"><input type="checkbox" id="_cbLandmarkShowAddresses"/> <b>'+I18n.t("wmepn.show_address")+'</b></div>' + '<div title="'+I18n.t("wmepn.show_tooltip")+'"><b>'+I18n.t("wmepn.show")+':</b></div>' + '<div title="'+I18n.t("wmepn.show")+' '+I18n.t("wmepn.option_area")+'" style="padding-left: 20px;"><input type="checkbox" id="_cbShowArea"> '+I18n.t("wmepn.option_area")+'</div>' + '<div title="'+I18n.t("wmepn.show")+' '+I18n.t("wmepn.option_point")+'" style="padding-left: 20px;"><input type="checkbox" id="_cbShowPoi"> '+I18n.t("wmepn.option_point")+'</div>' + '<div title="'+I18n.t("wmepn.show")+' '+I18n.t("wmepn.option_residential")+'" style="padding-left: 20px;"><input type="checkbox" id="_cbShowRH"> '+I18n.t("wmepn.option_residential")+'</div>' + '<div title="'+I18n.t("wmepn.show")+' '+I18n.t("wmepn.option_comments")+'" style="padding-left: 20px;"><input type="checkbox" id="_cbShowComment"> '+I18n.t("wmepn.option_comments")+'</div>' + '<div title="'+I18n.t("wmepn.filter_tooltip")+'"><b>'+I18n.t("wmepn.filter")+':</b><input type="text" id="_inLandmarkNameFilter"/></div>' + '<div title="'+I18n.t("wmepn.show_locklevel_tooltip")+'"><input type="checkbox" id="_cbLandmarkLockLevel" /> <b>'+I18n.t("wmepn.show_locklevel")+'</b></div>' + '<div title="'+I18n.t("wmepn.stop_over_tooltip")+'"><b>'+I18n.t("wmepn.stop_over")+'</b> <select id="_seLandmarkLimit">' + '<option value="0">'+I18n.t("wmepn.option_unlimited")+'</option>' + '<option value="500">500</option>' + '<option value="200">200</option>' + '<option value="100">100</option>' + '<option value="50">50</option>' + '<option value="25">25</option>' + '<option value="10">10</option>' +'</select></div>' + '<div><small>'+I18n.t("wmepn.showing")+' <span id="_stLandmarkNumber"></span> <span id="_stLandmarkHNNumber"></span></small></div>' + '<div title="'+I18n.t("wmepn.show_zool_tooltip")+'"><b>'+I18n.t("wmepn.show_zool")+'</b><input type="number" id="_zoomLevel"/></div>'; addon.appendChild(section); var userTabs = wmepn_getId('user-info'); var navTabs = getElementsByClassName('nav-tabs', userTabs)[0]; var tabContent = getElementsByClassName('tab-content', userTabs)[0]; newtab = document.createElement('li'); newtab.innerHTML = '<a href="#sidepanel-landmarknames" data-toggle="tab">PlaceNames Rus</a>'; navTabs.appendChild(newtab); addon.id = "sidepanel-landmarknames"; addon.className = "tab-pane"; tabContent.appendChild(addon); // setup onclick handlers for instant update: wmepn_getId('_cbLandmarkColors').onclick = wmepn_resetLandmarks; wmepn_getId('_cbLandmarkHiliteNoName').onclick = wmepn_resetLandmarks; wmepn_getId('_cbLandmarkHiliteNoAddress').onclick = wmepn_resetLandmarks; wmepn_getId('_cbLandmarkHiliteDifHN').onclick = wmepn_resetLandmarks; wmepn_getId('_cbLandmarkHiliteSmall').onclick = wmepn_resetLandmarks; wmepn_getId('_cbLandmarkNamesEnable').onclick = wmepn_resetLandmarks; wmepn_getId('_inLandmarkNameFilter').oninput = wmepn_showLandmarkNames; wmepn_getId('_cbLandmarkLockLevel').onclick = wmepn_showLandmarkNames; wmepn_getId('_seLandmarkLimit').onchange = wmepn_showLandmarkNames; wmepn_getId('_zoomLevel').onchange = wmepn_resetLandmarks; wmepn_getId('_cbLandmarkShowAddresses').onclick = wmepn_resetLandmarks; wmepn_getId('_minArea').onchange = wmepn_resetLandmarks; wmepn_getId('_cbShowArea').onclick = wmepn_resetLandmarks; wmepn_getId('_cbShowPoi').onclick = wmepn_resetLandmarks; wmepn_getId('_cbShowRH').onclick = wmepn_resetLandmarks; wmepn_getId('_cbShowComment').onclick = wmepn_resetLandmarks; wmepn_getId('_cbShowLinked').onclick = function(){ if (wmepn_getId('_cbShowLinked').checked) wmepn_getId('_cbShowNotLinked').checked = false; wmepn_resetLandmarks(); }; wmepn_getId('_cbShowNotLinked').onclick = function(){ if (wmepn_getId('_cbShowNotLinked').checked) wmepn_getId('_cbShowLinked').checked = false; wmepn_resetLandmarks(); }; // Create PlaceName layer var rlayers = map.getLayersBy("uniqueName","__DrawPlaceNames"); if(rlayers.length == 0) { var lname = "Place Names"; var style = new OpenLayers.Style({ strokeDashstyle: 'solid', strokeColor : "${strokeColor}", strokeOpacity: 1.0, strokeWidth: "${strokeWidth}", fillColor: '#0040FF', fillOpacity: 1.0, pointRadius: "${pointRadius}", label : "${labelText}", fontFamily: "Tahoma, Courier New", labelOutlineColor: '#FFEEEE', labelOutlineWidth: 2, labelAlign: 'cm', fontColor: "#301130", fontOpacity: 1.0, fontSize: "11px", display: 'block', labelYOffset: "${yOffset}", fontStyle: "${style}" }); var nameLayer = new OpenLayers.Layer.Vector(lname, { displayInLayerSwitcher: true, uniqueName: "__DrawPlaceNames", shortcutKey: "S+n", accelerator: "toggle" + lname.replace(/\s+/g,''), styleMap: new OpenLayers.StyleMap(style) }); nameLayer.setVisibility(true); //drc_mapLayer1.moveLayerToTop(); map.addLayer(nameLayer); //var zLandmarks = map.getLayersBy("uniqueName", "landmarks")[0].getZIndex(); //var zPlaceNames = drc_mapLayer1.getZIndex(); //map.getLayersBy("uniqueName", "landmarks")[0].setZIndex(zPlaceNames); //drc_mapLayer1.setZIndex(zLandmarks); wmepn_NameLayer = nameLayer; } else wmepn_NameLayer = rlayers[0]; // restore saved settings if (localStorage.WMELandmarkNamesScript) { console.log("WME LandmarkNames: loading options"); options = JSON.parse(localStorage.WMELandmarkNamesScript); wmepn_getId('_cbLandmarkColors').checked = options[1]; wmepn_getId('_cbLandmarkHiliteNoName').checked = options[2]; if(options[3] !== undefined) wmepn_getId('_cbShowArea').checked = options[3]; wmepn_NameLayer.setVisibility(options[4]); if(options[5] !== undefined) wmepn_getId('_cbLandmarkNamesEnable').checked = options[5]; else wmepn_NameLayer.setVisibility(true); if(options[6] !== undefined) wmepn_getId('_inLandmarkNameFilter').value = options[6]; if(options[7] !== undefined) wmepn_getId('_cbLandmarkLockLevel').checked = options[7]; if(options[8] !== undefined) wmepn_getId('_seLandmarkLimit').value = options[8]; else wmepn_getId('_seLandmarkLimit').value = 100; if(options[9] !== undefined) wmepn_getId('_zoomLevel').value = options[9]; else wmepn_getId('_zoomLevel').value = 4; if(options[10] !== undefined) wmepn_getId('_cbLandmarkHiliteSmall').checked = options[10]; if(options[11] !== undefined) wmepn_getId('_cbLandmarkHiliteNoAddress').checked = options[11]; if(options[12] !== undefined) wmepn_getId('_cbLandmarkShowAddresses').checked = options[12]; if(options[13] !== undefined) wmepn_getId('_cbLandmarkHiliteDifHN').checked = options[13]; if(options[14] !== undefined) wmepn_getId('_minArea').value = options[14]; else wmepn_getId('_minArea').value = 650; if(options[15] !== undefined) wmepn_getId('_cbShowPoi').checked = options[15]; if(options[16] !== undefined) wmepn_getId('_cbShowRH').checked = options[16]; if(options[17] !== undefined) wmepn_getId('_cbShowLinked').checked = options[17]; if(options[18] !== undefined) wmepn_getId('_cbShowNotLinked').checked = options[18]; if(options[19] !== undefined) wmepn_getId('_cbShowComment').checked = options[19]; } else { wmepn_getId('_cbLandmarkColors').checked = true; wmepn_getId('_cbLandmarkHiliteNoName').checked = true; wmepn_getId('_cbLandmarkHiliteNoAddress').checked = true; wmepn_getId('_cbLandmarkHiliteDifHN').checked = true; wmepn_getId('_cbLandmarkHiliteSmall').checked = true; wmepn_getId('_cbLandmarkShowAddresses').checked = true; wmepn_getId('_cbShowArea').checked = true; wmepn_getId('_cbShowPoi').checked = true; wmepn_getId('_cbShowRH').checked = true; wmepn_getId('_cbShowComment').checked = true; wmepn_NameLayer.setVisibility(true); wmepn_getId('_cbLandmarkNamesEnable').checked = true; wmepn_getId('_cbLandmarkLockLevel').checked = false; wmepn_getId('_cbShowLinked').checked = false; wmepn_getId('_cbShowNotLinked').checked = false; wmepn_getId('_seLandmarkLimit').value = 100; wmepn_getId('_zoomLevel').value = 4; wmepn_getId('_minArea').value = 650; } var layerItem = '<li><div class="controls-container toggler"><input class="layer-switcher-item_placenames_rus toggle" id="layer-switcher-item_placenames_rus" type="checkbox"><label for="layer-switcher-item_placenames_rus"><span class="label-text">PlaceNames Rus</span></label></div></li>'; $("#layer-switcher-group_places").parent().parent().children("ul.children").append(layerItem); $("#layer-switcher-item_placenames_rus").click(function() { wmepn_NameLayer.setVisibility(!wmepn_NameLayer.getVisibility()); }); $("#layer-switcher-item_placenames_rus").prop("checked", wmepn_NameLayer.getVisibility()); if (typeof W.model.venues == "undefined") { wmepn_getId('_cbLandmarkColors').checked = false; wmepn_getId('_cbLandmarkHiliteNoName').checked = false; wmepn_getId('_cbLandmarkHiliteNoAddress').checked = false; wmepn_getId('_cbLandmarkHiliteDifHN').checked = false; wmepn_getId('_cbLandmarkHiliteSmall').checked = false; wmepn_getId('_cbLandmarkShowAddresses').checked = false; wmepn_getId('_cbLandmarkColors').disabled = true; wmepn_getId('_cbLandmarkHiliteNoName').disabled = true; wmepn_getId('_cbLandmarkHiliteNoAddress').disabled = true; wmepn_getId('_cbLandmarkHiliteDifHN').disabled = true; wmepn_getId('_cbLandmarkHiliteSmall').checked = true; wmepn_getId('_cbShowArea').checked = true; wmepn_getId('_cbShowPoi').checked = true; wmepn_getId('_cbShowRH').checked = true; wmepn_getId('_cbShowComment').checked = true; wmepn_getId('_cbLandmarkLockLevel').disabled = true; wmepn_getId('_cbShowLinked').disabled = true; wmepn_getId('_cbShowNotLinked').disabled = true; wmepn_getId('_seLandmarkLimit').disabled = true; wmepn_getId('_cbLandmarkShowAddresses').checked = true; } // overload the WME exit function wmepn_saveLandmarkNamesOptions = function() { if (localStorage) { console.log("WME LandmarkNames: saving options"); var options = []; // preserve previous options which may get lost after logout if (localStorage.WMELandmarkNamesScript) options = JSON.parse(localStorage.WMELandmarkNamesScript); options[1] = wmepn_getId('_cbLandmarkColors').checked; options[2] = wmepn_getId('_cbLandmarkHiliteNoName').checked; options[3] = wmepn_getId('_cbShowArea').checked; options[4] = wmepn_NameLayer.getVisibility(); options[5] = wmepn_getId('_cbLandmarkNamesEnable').checked; options[6] = wmepn_getId('_inLandmarkNameFilter').value; options[7] = wmepn_getId('_cbLandmarkLockLevel').checked; options[8] = wmepn_getId('_seLandmarkLimit').value; options[9] = wmepn_getId('_zoomLevel').value; options[10] = wmepn_getId('_cbLandmarkHiliteSmall').checked; options[11] = wmepn_getId('_cbLandmarkHiliteNoAddress').checked; options[12] = wmepn_getId('_cbLandmarkShowAddresses').checked; options[13] = wmepn_getId('_cbLandmarkHiliteDifHN').checked; options[14] = wmepn_getId('_minArea').value; options[15] = wmepn_getId('_cbShowPoi').checked; options[16] = wmepn_getId('_cbShowRH').checked; options[17] = wmepn_getId('_cbShowLinked').checked; options[18] = wmepn_getId('_cbShowNotLinked').checked; options[19] = wmepn_getId('_cbShowComment').checked; localStorage.WMELandmarkNamesScript = JSON.stringify(options); } }; window.addEventListener("beforeunload", wmepn_saveLandmarkNamesOptions, false); // begin periodic updates //window.setInterval(wmepn_showLandmarkNames,500); // trigger code when page is fully loaded, to catch any missing bits window.addEventListener("load", function(e) { var mapProblems = wmepn_getId('map-problems-explanation'); if (mapProblems !== null) mapProblems.style.display = "none"; }); // register some events... map.events.register("zoomend", null, wmepn_showLandmarkNames); map.events.register("changelayer", null, wmepn_showLandmarkNames); map.events.register("mouseout", null, wmepn_showLandmarkNames); W.selectionManager.events.register("selectionchanged", null, wmepn_showLandmarkNames); I18n.translations[I18n.locale].keyboard_shortcuts.groups['default'].members.WME_PlaceNames_enable = "Включить/выключить скрипт PlaceNames Russian"; W.accelerators.addAction("WME_PlaceNames_enable", {group: 'default'}); W.accelerators.events.register("WME_PlaceNames_enable", null, enablePlaceNames); W.accelerators._registerShortcuts({ 'S+n' : "WME_PlaceNames_enable"}); I18n.translations[I18n.locale].keyboard_shortcuts.groups['default'].members.WME_PlaceNames_increase = "Увеличить площадь POI до "+wmepn_getId('_minArea').value.toString()+"м² (минимальная площадь для отображения в приложении)"; W.accelerators.addAction("WME_PlaceNames_increase", {group: 'default'}); W.accelerators.events.register("WME_PlaceNames_increase", null, modifyArea); W.accelerators._registerShortcuts({ 'y' : "WME_PlaceNames_increase"}); } var enablePlaceNames = function () { wmepn_getId('_cbLandmarkNamesEnable').click(); }; /* engage! =================================================================== */ bootstrapLandmarkNames(); /* end ======================================================================= */