v5: all road types with names on map, snap-to-road labels
This commit is contained in:
parent
a1f439001a
commit
baedd56a1c
91
index.html
91
index.html
@ -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]
|
||||
});
|
||||
document.getElementById('autoStatus').textContent='✅ Улиц: '+roadCount+', здания: '+loadedBuildings;
|
||||
let html='<span class="tag">'+roadCount+' улиц</span><span class="tag">'+namedCount+' с названиями</span><span class="tag">'+loadedBuildings+' зданий</span>';
|
||||
L.marker([mlat,mlng],{icon:labelIcon}).addTo(roadsLayer);
|
||||
labelCount++;
|
||||
}
|
||||
});
|
||||
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){
|
||||
|
||||
Loading…
Reference in New Issue
Block a user