И первый вопрос: «Серьёзно?». Ответ: «Да, абсолютно».
Ну, а если действительно серьёзно, то много слов не нужно. В общем, теперь можно во всеуслышание заявить: «Отныне проблема жёлтых точек решена!». И это действительно так.
Теперь любой пользователь может удалять т. н. «жёлтые» точки (правильно это называется «Точки пересечения границ»).
Специально для этой операции был написан скрипт, который требует от пользователя лишь ввести примерные координаты той самой злополучной точки и слой, в котором она находится. Всё.
Теперь о том, как это всё происходит.
При запуске скрипта пользователь видит окно:
Координаты вводятся в современном формате: [широта], [долгота] (например, 52.058243, 47.648066).
Координаты точки проще всего получить щелчком правой кнопкой мыши по нужной точке:
В появившемся окошке с координатами нажимаем на кнопку «копировать»:
Вставляем координаты в окно скрипта, выбираем слой и нажимаем «Найти точку».
Скрипт сделает запрос на наличие точек пересечения для выбранного слоя в окрестностях введённой координаты и выведет ближайшую из таких точек:
Вы можете посмотреть предполагаемый объект в новой вкладке, нажав по кнопке «Посмотреть», либо сразу нажимать «Удалить». Ничего «вредного» сервер Вам сделать не даст, максимум будет лишь сообщение об ошибке).
В случае успешного проведения операции удаления Вы получите соответствующее уведомление:
В Вашей истории правок после этого появится запись об этой операции.
_______________________________________________________________________________________________________
Скрипт реализован в виде букмарклета, так как, на мой взгляд, делать в браузере висящую кнопку для вызова скрипта в данном случае не имеет смысла.
Код букмарклета (last update 22:36 23.08.18):
javascript: function loadScripts( src, callback ) { var script = document.createElement("SCRIPT"), head = document.getElementsByTagName( "head" )[ 0 ], error = false; script.id = "jQ"; script.type = "text/javascript"; script.onload = script.onreadystatechange = function( e ){ if ( ( !this.readyState || this.readyState == "loaded" || this.readyState == "complete" ) ) { if ( !error ) { removeListeners(); callback( true ); } else { callback( false ); } } }; script.onerror = function() { error = true; removeListeners(); callback( false ); }; function errorHandle( msg, url, line ) { if ( url == src ) { error = true; removeListeners(); callback( false ); } return false; } function removeListeners() { script.onreadystatechange = script.onload = script.onerror = null; if ( window.removeEventListener ) { window.removeEventListener('error', errorHandle, false ); } else { window.detachEvent("onerror", errorHandle ); } } if ( window.addEventListener ) { window.addEventListener('error', errorHandle, false ); } else { window.attachEvent("onerror", errorHandle ); } script.src = src; head.appendChild( script ); } function exit(){document.getElementById('okn').parentNode.removeChild(document.getElementById('okn'));} function letsgo(){ var zn = document.getElementById('in1').value; var s = 0; do{ s++; if(s>zn.length){f=1; break;}} while(!(zn[s] == ',')); if(f==0){b[0]=zn[0]; b[1]=zn[s+2]; for(var i = 1; i<s; i++)b[0]+=zn[i]; for(var i = s+3; i<zn.length; i++)b[1]+=zn[i]; b[0]=Number(b[0]); b[1]=Number(b[1]);} if(!((b[0] === undefined) || (b[1] === undefined) || (b[0] === null) || (b[1] === null) || isNaN(b[0]) || isNaN(b[1]))){ type = document.getElementById('in2').selectedIndex; document.getElementById('okn').parentNode.removeChild(document.getElementById('okn')); var jQ = document.getElementById('jQ'); if (jQ === null){loadScripts('https://yastatic.net/jquery/3.1.1/jquery.min.js', function(status){if (status) { fun1()};}); }else fun1(); }else{f=0; alert('Координаты введены некорректно'); document.getElementById('ext').onclick = exit; document.getElementById('lg').onclick = letsgo; document.getElementById('in1').onkeypress=function(e){ if(e.keyCode==13){ letsgo(); } }; document.getElementById('in2').onkeypress=function(e){ if(e.keyCode==13){ letsgo(); } };}} function fun1(){$.ajax({ url : "https://n.maps.yandex.ru/api/batch", type : "POST", data : { "branch" : 0, "methods" : null, "token" : JSON.parse(localStorage.getItem('nk:token')), "lang" : "ru", "csrf_token" : null}, success : function(data){tok = data.meta.csrfToken; f=0; fun2();}, error : function(){f+=1; if(f<5){ if(f<5){if(confirm('Не удалось выполнить запрос № 1. Осталось попыток: '+(5-f)+'. Попробовать снова?'))fun1();}} else{alert('Не удалось выполнить запрос. Попробуйте позже.');}} });} function fun2(){ var lat = b[0]; var lon = b[1]; lat = lat * Math.PI / 180.0; var a = 6378137; var k = 0.0818191908426; var z1 = Math.tan(Math.PI / 4 + lat / 2) / Math.pow(Math.tan(Math.PI / 4 + Math.asin(k * Math.sin(lat)) / 2) , k); var pix_Y = Math.round((20037508.342789 - a * Math.log(z1)) * 53.5865938 / Math.pow(2.0, 23 - z)); lat = Math.round(pix_Y / 256); lon = lon * Math.PI / 180.0; var pix_X = Math.round((20037508.342789 + a * lon) * 53.5865938 / Math.pow(2.0 , 23 - z)); lon = Math.round(pix_X / 256); for(var i = 0; i<9; i++){ method[i].params.x += lon; if(method[i].params.x<0)method[i].params.x=Math.pow(2,z); if(method[i].params.x>Math.pow(2,z))method[i].params.x=0; method[i].params.y += lat; method[i].params.zoom = z; method[i].params.categories = cat[type]; } $.ajax({ url : "https://n.maps.yandex.ru/api/batch", type : "POST", data : { "branch" : 0, "methods" : JSON.stringify(method), "token" : JSON.parse(localStorage.getItem('nk:token')), "lang" : "ru", "csrf_token" : tok}, success : function(data){ if(!((data.data[0].data === null) || (data.data[0].data === undefined))){ f = 0; type = data; fun3();} else{f+=1; if(f<5){ if(confirm('Не удалось выполнить запрос № 2. Сервер сообщил об ошибке. Осталось попыток: '+(5-f)+'. Попробовать снова?'))fun2();} else{alert('Не удалось выполнить запрос. Попробуйте позже.');}} }, error : function(){f+=1; if(f<5){ if(confirm('Не удалось выполнить запрос № 2. Осталось попыток: '+(5-f)+'. Попробовать снова?'))fun2();} else{alert('Не удалось выполнить запрос. Попробуйте позже.');}} });} function fun3(){ b[0] = b[0] * Math.PI / 180.0; b[1] = b[1] * Math.PI / 180.0; for(var i = 0; i<9; i++){ for(var j = 0; j < type.data[i].data.data.features.length; j++){ lat = type.data[i].data.data.features[j].properties.geoObject.geometry.coordinates[1]; lon = type.data[i].data.data.features[j].properties.geoObject.geometry.coordinates[0]; lat = lat * Math.PI / 180.0; lon = lon * Math.PI / 180.0; z = Math.acos(Math.sin(b[0])*Math.sin(lat)+Math.cos(b[0])*Math.cos(lat)*Math.cos(lon-b[1])); if(z<min){min = z; mas[0]=i; mas[1]=j;} } } if(!(mas[0] === null)){ z = type.data[mas[0]].data.data.features[mas[1]].properties.geoObject.id; var htm = '<div id="okn1" style="position: fixed; z-index: 99999; right: 600px; top: 350px; background: #fff"><form><fieldset><legend>Удаление точек пересечения границ</legend><p>Предполагаемый объект:</p><table><tr><td>ID: '+z+'</td><td><a class="button10" href="https://n.maps.yandex.ru/#!/objects/'+z+'" target="_blank">Посмотреть</a></td></tr><tr><td><br></td></tr><tr><td><a class="button10" id="ext1">Выйти</a></td><td><a class="button10" id="lg1">Удалить</a></td></tr></table></fieldset></form></div>'; document.getElementsByTagName('body')[0].insertAdjacentHTML('beforeEnd',htm); document.getElementById('ext1').onclick = exit1; document.getElementById('lg1').onclick = prefun4; }else{alert('Не удалось найти точку. Попробуйте в следующий раз задать координаты более точно.');} } function exit1(){document.getElementById('okn1').parentNode.removeChild(document.getElementById('okn1'));} function prefun4(){ req[0].params.id = z; document.getElementById('okn1').parentNode.removeChild(document.getElementById('okn1')); fun4(); } function fun4(){ $.ajax({ url : "https://n.maps.yandex.ru/api/batch", type : "POST", data : { "branch" : 0, "methods" : JSON.stringify(req), "token" : JSON.parse(localStorage.getItem('nk:token')), "lang" : "ru", "csrf_token" : tok}, success : function(data){ if(!((data.data[0].data === null) || (data.data[0].data === undefined))){alert('Точка успешно удалена');} else{f+=1; if(f<5){ if(confirm('Не удалось выполнить запрос № 3. Сервер сообщил об ошибке: '+data.data[0].error.type+'. Осталось попыток: '+(5-f)+'. Попробовать снова?'))fun4();} else{alert('Не удалось выполнить запрос. Попробуйте позже.');}} }, error : function(){f+=1; if(f<5){ if(confirm('Не удалось выполнить запрос № 3. Осталось попыток: '+(5-f)+'. Попробовать снова?'))fun4();} else{alert('Не удалось выполнить запрос. Попробуйте позже.');}} }); } var f = 0; var z = 21; var b =[null,null]; var mas = [null, null]; var min = 1000; var type; var req = [{"method":"editor/deleteGeoObject","params":{"id":null}}]; var method = [{"method":"editor/getGeoObjectHotspots","params":{"x":-1,"y":+1,"zoom":21,"categories":null}},{"method":"editor/getGeoObjectHotspots","params":{"x":0,"y":+1,"zoom":21,"categories":null}},{"method":"editor/getGeoObjectHotspots","params":{"x":+1,"y":+1,"zoom":21,"categories":null}},{"method":"editor/getGeoObjectHotspots","params":{"x":-1,"y":0,"zoom":21,"categories":null}},{"method":"editor/getGeoObjectHotspots","params":{"x":0,"y":0,"zoom":21,"categories":null}},{"method":"editor/getGeoObjectHotspots","params":{"x":+1,"y":0,"zoom":21,"categories":null}},{"method":"editor/getGeoObjectHotspots","params":{"x":-1,"y":-1,"zoom":21,"categories":null}},{"method":"editor/getGeoObjectHotspots","params":{"x":0,"y":-1,"zoom":21,"categories":null}},{"method":"editor/getGeoObjectHotspots","params":{"x":+1,"y":-1,"zoom":21,"categories":null}}]; var cat = ["hydro_fc_jc", "vegetation_jc", "ad_jc", "relief_jc"]; var sk = document.getElementById('sk'); if (sk === null){document.getElementsByTagName('body')[0].insertAdjacentHTML('beforeEnd','<style id="sk">a.button10 {display: inline-block; color: black; font-size: 110%; text-decoration: none; user-select: none; padding: .25em .5em; outline: none; border: 1px solid rgb(250,172,17); border-radius: 7px; background: rgb(255,212,3) linear-gradient(rgb(255,212,3), rgb(248,157,23)); box-shadow: inset 0 -2px 1px rgba(0,0,0,0), inset 0 1px 2px rgba(0,0,0,0), inset 0 0 0 60px rgba(255,255,0,0); transition: box-shadow .2s, border-color .2s;} a.button10:hover { box-shadow: inset 0 -1px 1px rgba(0,0,0,0), inset 0 1px 2px rgba(0,0,0,0), inset 0 0 0 60px rgba(255,255,0,.5);} a.button10:active { padding: calc(.25em + 1px) .5em calc(.25em - 1px); border-color: rgba(177,159,0,1); box-shadow: inset 0 -1px 1px rgba(0,0,0,.1), inset 0 1px 2px rgba(0,0,0,.3), inset 0 0 0 60px rgba(255,255,0,.45);}</style>');} document.getElementsByTagName('body')[0].insertAdjacentHTML('beforeEnd','<div id="okn" style="position: fixed; z-index: 99999; right: 600px; top: 350px; background: #fff"><form><fieldset><legend>Удаление точек пересечения границ</legend><table><tr><td>Координаты:</td><td><input type="text" autocomplete="off" size="15" id="in1"></td></tr><tr><td>Слой:</td><td><select id="in2"><option selected>Гидрография</option><option>Растительность</option><option>АТД</option><option>Рельеф</option></select></td></tr><tr><td><a class="button10" id="ext">Выйти</a></td><td><a class="button10" id="lg">Найти точку</a></td></tr></table></fieldset></form></div>'); document.getElementById('ext').onclick = exit; document.getElementById('lg').onclick = letsgo; document.getElementById('in1').onkeypress=function(e){ if(e.keyCode==13){ letsgo(); } }; document.getElementById('in2').onkeypress=function(e){ if(e.keyCode==13){ letsgo(); } };
Установка стандартно через диспетчер закладок браузера (в Яндекс.Браузере и Google Chrome можно открыть комбинацией клавиш Ctrl + Shift + O).
P. S. Создание этого скрипта было довольно увлекательным занятием. Помимо подробного наблюдения за работой веб-редактора НЯКа пришлось изучить достаточно материала по проекции Меркатора, используемым в геосервисах параметрам земного шара, устройству данных в геосервисах, формированию тайлов, залезать в геометрию (в частности, вычислять длину ортодромии) и др.
Ну что ж, главное, что всё удалось :)