v5: all road types with names on map, snap-to-road labels

This commit is contained in:
Dauren777 2026-06-19 06:14:48 +00:00
parent a1f439001a
commit baedd56a1c

View File

@ -92,14 +92,16 @@ body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,sans-serif;
<span><span style="display:inline-block;width:11px;height:11px;border-radius:50%;background:#0F1218;border:2px solid #00E5FF"></span> Опора</span>
<span><i style="display:inline-block;width:12px;height:3px;background:#00E5FF;border-radius:2px"></i> ВЛ</span>
<span><span style="background:#FF9800;padding:0 3px;border-radius:2px;color:#fff;font-size:8px">В</span> Въезд</span>
<span><i style="display:inline-block;width:12px;height:2px;background:#7c3aed;border-radius:2px"></i> Улица (OSM)</span>
<span><i style="display:inline-block;width:12px;height:3px;background:#e74c3c;border-radius:2px"></i> Асфальт</span>
<span><i style="display:inline-block;width:12px;height:2px;background:#f39c12;border-radius:2px"></i> Грунтовка</span>
<span><i style="display:inline-block;width:12px;height:1px;background:#95a5a6;border-radius:1px"></i> Просёлок/поле</span>
<span><span style="display:inline-block;width:8px;height:8px;background:rgba(255,107,53,.25);border:1px solid #ff6b35"></span> Здание</span>
</div>
<div class="sidebar">
<div class="sidebar-header"><span>📋 Спецификация</span><span id="stats" style="font-weight:400;font-size:10px">0 шт</span></div>
<div style="overflow-y:auto;flex:1">
<table>
<thead><tr><th></th><th>Название</th><th>Широта</th><th>Долгота</th><th>Пролёт</th></tr></thead>
<thead><tr><th></th><th>Название</th><th>Улица</th><th>Широта</th><th>Долгота</th><th>Пролёт</th></tr></thead>
<tbody id="poleTable"></tbody>
</table>
</div>
@ -186,31 +188,59 @@ function autoLoadOSM(){
document.getElementById('autoPanel').style.display='block';
document.getElementById('autoStatus').textContent='⏳ Загрузка...';
// Дороги
const qRoads='[out:json];(way["highway"]('+bbox+'););out geom;';
// ДорогиВСЕ типы (асфальт, грунт, просёлок, полевая)
const qRoads='[out:json];(way["highway"~"motorway|trunk|primary|secondary|tertiary|unclassified|residential|service|track|path|living_street|road"]('+bbox+'););out geom;';
fetch('https://overpass-api.de/api/interpreter?data='+encodeURIComponent(qRoads))
.then(r=>r.json()).then(data=>{
roadsLayer.clearLayers();
loadedRoads=[];
let roadCount=0, namedCount=0;
const colors={primary:'#7c3aed',secondary:'#7c3aed',tertiary:'#7c3aed',residential:'#7c3aed',service:'#7c3aed',unclassified:'#7c3aed',track:'#aaa'};
let roadCount=0, namedCount=0, labelCount=0;
const roadStyles={
motorway:{color:'#e74c3c',weight:4,label:'асфальт'},
trunk:{color:'#e74c3c',weight:3.5,label:'асфальт'},
primary:{color:'#e74c3c',weight:3,label:'асфальт'},
secondary:{color:'#f39c12',weight:2.5,label:'грунт/асфальт'},
tertiary:{color:'#f39c12',weight:2.5,label:'грунт'},
unclassified:{color:'#f39c12',weight:2,label:'грунт'},
residential:{color:'#f39c12',weight:2,label:'грунт'},
living_street:{color:'#f39c12',weight:2,label:'грунт'},
service:{color:'#95a5a6',weight:1.5,label:'просёлок'},
track:{color:'#95a5a6',weight:1.2,label:'полевая/просёлок'},
path:{color:'#95a5a6',weight:1,label:'тропа',dash:'4,4'},
road:{color:'#f39c12',weight:2,label:'грунт'}
};
(data.elements||[]).forEach(el=>{
if(!el.geometry||el.geometry.length<2)return;
const coords=el.geometry.map(g=>[g.lat,g.lon]);
const type=el.tags&&el.tags.highway||'unclassified';
const name=el.tags&&el.tags.name||el.tags&&el.tags['ref']||'';
const color=colors[type]||'#7c3aed';
const weight=type==='track'?1.5:type==='service'?2:2.5;
const road=L.polyline(coords,{color,weight,opacity:0.7}).addTo(roadsLayer);
if(name){road.bindPopup('🛣 '+name);namedCount++;}
loadedRoads.push({coords,type,name,road});
const style=roadStyles[type]||{color:'#95a5a6',weight:1.5,label:'просёлок'};
const opts={color:style.color,weight:style.weight,opacity:0.75};
if(style.dash)opts.dashArray=style.dash;
const road=L.polyline(coords,opts).addTo(roadsLayer);
// popup с названием и типом
const info=name?name+' ('+style.label+')':style.label;
road.bindPopup('🛣 '+info);
loadedRoads.push({coords,type,name,road,stylename:style.label});
roadCount++;
if(name)namedCount++;
// подпись названия улицы прямо на карте (в середине сегмента)
if(name&&coords.length>1){
const mid=Math.floor(coords.length/2);
const mlat=coords[mid][0],mlng=coords[mid][1];
const labelIcon=L.divIcon({
className:'',
html:'<span style="background:rgba(0,0,0,.75);color:#00E5FF;padding:1px 6px;border-radius:3px;font-size:10px;font-weight:700;white-space:nowrap;border:1px solid #00E5FF;text-shadow:0 1px 3px rgba(0,0,0,.8)">'+name+'</span>',
iconSize:[0,0],iconAnchor:[0,0]
});
L.marker([mlat,mlng],{icon:labelIcon}).addTo(roadsLayer);
labelCount++;
}
});
document.getElementById('autoStatus').textContent='✅ Улиц: '+roadCount+', здания: '+loadedBuildings;
let html='<span class="tag">'+roadCount+' улиц</span><span class="tag">'+namedCount+' с названиями</span><span class="tag">'+loadedBuildings+' зданий</span>';
document.getElementById('autoStatus').textContent='✅ Всего дорог: '+roadCount+' (с названиями: '+namedCount+'), здания: '+loadedBuildings;
let html='<span class="tag" style="background:#e74c3c;color:#fff">дopoг: '+roadCount+'</span><span class="tag" style="background:#f39c12;color:#fff">назв.: '+namedCount+'</span><span class="tag" style="background:#ff6b35;color:#fff">зданий: '+loadedBuildings+'</span>';
document.getElementById('autoResults').innerHTML=html;
if(roadCount+namedCount+loadedBuildings===0)document.getElementById('autoStatus').textContent='⚠ Данные не загружены — проверьте зум';
if(roadCount===0)document.getElementById('autoStatus').textContent='⚠ Нет улиц в этом районе';
if(roadCount===0)document.getElementById('autoStatus').textContent='⚠ Нет дорог в этом районе (проверьте зум)';
});
// Здания
const qBuild='[out:json];(way["building"]('+bbox+');rel["building"]('+bbox+'););out geom;';
@ -246,21 +276,21 @@ function applyRoadPoles(){
const margin=parseFloat(document.getElementById('entranceMargin').value)||5;
let totalPoles=0;
loadedRoads.forEach(road=>{
if(!road.name)return; // только именованные улицы
const pts=road.coords;
if(pts.length<2)return;
const len=pathLength2(pts);
if(len<10)return; // короче 10м пропускаем
const n=Math.max(1,Math.round(len/step));
for(let i=0;i<=n;i++){
const pt=pointOnPath2(pts,i/n);
if(!isNearEntrance(pt,margin)){
addPole(L.latLng(pt.lat,pt.lng));
addPole(L.latLng(pt.lat,pt.lng),road.name||road.stylename||'');
totalPoles++;
}
}
});
document.getElementById('autoPanel').style.display='none';
showToast('✅ Расставлено '+totalPoles+' опор вдоль '+loadedRoads.length+' улиц');
showToast('✅ Расставлено '+totalPoles+' опор вдоль дорог');
}
function pathLength2(pts){let t=0;for(let i=1;i<pts.length;i++)t+=haversine(pts[i-1][0],pts[i-1][1],pts[i][0],pts[i][1]);return t;}
function pointOnPath2(pts,t){
@ -337,12 +367,25 @@ function closestPointOnSegment(px,py,ax,ay,bx,by){
}
// ====================== ОПОРЫ ======================
function addPole(latlng){
function getRoadName(latlng){
let best=null,bestDist=Infinity;
loadedRoads.forEach(road=>{
for(let i=1;i<road.coords.length;i++){
const a=road.coords[i-1],b=road.coords[i];
const p=closestPointOnSegment(latlng.lat,latlng.lng,a[0],a[1],b[0],b[1]);
if(p.dist<bestDist&&p.dist<20){bestDist=p.dist;best=road.name||road.stylename||'';}
}
});
return best;
}
function addPole(latlng,roadName){
poleCount++;
const rn=roadName||getRoadName(latlng);
const suffix=rn?' ('+rn+')':'';
const name='Опора '+poleCount;
const icon=L.divIcon({className:'',html:'<div class="pole-label">'+poleCount+'</div>',iconSize:[22,22],iconAnchor:[11,11]});
const marker=L.marker(latlng,{icon}).addTo(map).bindPopup('<b>'+name+'</b><br>'+latlng.lat.toFixed(6)+', '+latlng.lng.toFixed(6));
markers.push({id:poleCount,lat:latlng.lat,lng:latlng.lng,name,marker});
const marker=L.marker(latlng,{icon}).addTo(map).bindPopup('<b>'+name+'</b><br>'+latlng.lat.toFixed(6)+', '+latlng.lng.toFixed(6)+suffix);
markers.push({id:poleCount,lat:latlng.lat,lng:latlng.lng,name,marker,road:rn});
if(markers.length>1){
const p=markers[markers.length-2];
const line=L.polyline([[p.lat,p.lng],[latlng.lat,latlng.lng]],{color:'#00E5FF',weight:2.5,dashArray:'6,5'}).addTo(map);
@ -421,7 +464,7 @@ function updateTable(){
const dist=i>0?haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng):0;
total+=dist;
const tr=document.createElement('tr');
tr.innerHTML='<td class="num">'+p.id+'</td><td>'+p.name+'</td><td>'+p.lat.toFixed(6)+'</td><td>'+p.lng.toFixed(6)+'</td><td class="dist">'+(i>0?dist.toFixed(1):'—')+'</td>';
tr.innerHTML='<td class="num">'+p.id+'</td><td>'+p.name+'</td><td style="max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+(p.road||'')+'</td><td>'+p.lat.toFixed(6)+'</td><td>'+p.lng.toFixed(6)+'</td><td class="dist">'+(i>0?dist.toFixed(1):'—')+'</td>';
tbody.appendChild(tr);
});
const avg=markers.length>1?(total/(markers.length-1)):0;
@ -503,8 +546,8 @@ function exportDrawIO(){
}
function exportCSV(){
if(!markers.length){showToast('Нет опор');return;}
let csv='№,Название,Широта,Долгота,Расстояниео_предыдущей_м\n';
markers.forEach((p,i)=>{const d=i>0?haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng).toFixed(1):'';csv+=p.id+','+p.name+','+p.lat.toFixed(6)+','+p.lng.toFixed(6)+','+d+'\n';});
let csv='№,Название,Улица,Широта,Долгота,Расстояниео_предыдущей_м\n';
markers.forEach((p,i)=>{const d=i>0?haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng).toFixed(1):'';csv+=p.id+','+p.name+','+(p.road||'')+','+p.lat.toFixed(6)+','+p.lng.toFixed(6)+','+d+'\n';});
download(csv,'opory_vl04.csv','text/csv;charset=utf-8');
}
function download(content,filename,type){