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><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><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><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>
|
<span><span style="display:inline-block;width:8px;height:8px;background:rgba(255,107,53,.25);border:1px solid #ff6b35"></span> Здание</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<div class="sidebar-header"><span>📋 Спецификация</span><span id="stats" style="font-weight:400;font-size:10px">0 шт</span></div>
|
<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">
|
<div style="overflow-y:auto;flex:1">
|
||||||
<table>
|
<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>
|
<tbody id="poleTable"></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -186,31 +188,59 @@ function autoLoadOSM(){
|
|||||||
document.getElementById('autoPanel').style.display='block';
|
document.getElementById('autoPanel').style.display='block';
|
||||||
document.getElementById('autoStatus').textContent='⏳ Загрузка...';
|
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))
|
fetch('https://overpass-api.de/api/interpreter?data='+encodeURIComponent(qRoads))
|
||||||
.then(r=>r.json()).then(data=>{
|
.then(r=>r.json()).then(data=>{
|
||||||
roadsLayer.clearLayers();
|
roadsLayer.clearLayers();
|
||||||
loadedRoads=[];
|
loadedRoads=[];
|
||||||
let roadCount=0, namedCount=0;
|
let roadCount=0, namedCount=0, labelCount=0;
|
||||||
const colors={primary:'#7c3aed',secondary:'#7c3aed',tertiary:'#7c3aed',residential:'#7c3aed',service:'#7c3aed',unclassified:'#7c3aed',track:'#aaa'};
|
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=>{
|
(data.elements||[]).forEach(el=>{
|
||||||
if(!el.geometry||el.geometry.length<2)return;
|
if(!el.geometry||el.geometry.length<2)return;
|
||||||
const coords=el.geometry.map(g=>[g.lat,g.lon]);
|
const coords=el.geometry.map(g=>[g.lat,g.lon]);
|
||||||
const type=el.tags&&el.tags.highway||'unclassified';
|
const type=el.tags&&el.tags.highway||'unclassified';
|
||||||
const name=el.tags&&el.tags.name||el.tags&&el.tags['ref']||'';
|
const name=el.tags&&el.tags.name||el.tags&&el.tags['ref']||'';
|
||||||
const color=colors[type]||'#7c3aed';
|
const style=roadStyles[type]||{color:'#95a5a6',weight:1.5,label:'просёлок'};
|
||||||
const weight=type==='track'?1.5:type==='service'?2:2.5;
|
const opts={color:style.color,weight:style.weight,opacity:0.75};
|
||||||
const road=L.polyline(coords,{color,weight,opacity:0.7}).addTo(roadsLayer);
|
if(style.dash)opts.dashArray=style.dash;
|
||||||
if(name){road.bindPopup('🛣 '+name);namedCount++;}
|
const road=L.polyline(coords,opts).addTo(roadsLayer);
|
||||||
loadedRoads.push({coords,type,name,road});
|
// popup с названием и типом
|
||||||
|
const info=name?name+' ('+style.label+')':style.label;
|
||||||
|
road.bindPopup('🛣 '+info);
|
||||||
|
loadedRoads.push({coords,type,name,road,stylename:style.label});
|
||||||
roadCount++;
|
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;
|
document.getElementById('autoStatus').textContent='✅ Всего дорог: '+roadCount+' (с названиями: '+namedCount+'), здания: '+loadedBuildings;
|
||||||
let html='<span class="tag">'+roadCount+' улиц</span><span class="tag">'+namedCount+' с названиями</span><span class="tag">'+loadedBuildings+' зданий</span>';
|
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;
|
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;';
|
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;
|
const margin=parseFloat(document.getElementById('entranceMargin').value)||5;
|
||||||
let totalPoles=0;
|
let totalPoles=0;
|
||||||
loadedRoads.forEach(road=>{
|
loadedRoads.forEach(road=>{
|
||||||
if(!road.name)return; // только именованные улицы
|
|
||||||
const pts=road.coords;
|
const pts=road.coords;
|
||||||
if(pts.length<2)return;
|
if(pts.length<2)return;
|
||||||
const len=pathLength2(pts);
|
const len=pathLength2(pts);
|
||||||
|
if(len<10)return; // короче 10м — пропускаем
|
||||||
const n=Math.max(1,Math.round(len/step));
|
const n=Math.max(1,Math.round(len/step));
|
||||||
for(let i=0;i<=n;i++){
|
for(let i=0;i<=n;i++){
|
||||||
const pt=pointOnPath2(pts,i/n);
|
const pt=pointOnPath2(pts,i/n);
|
||||||
if(!isNearEntrance(pt,margin)){
|
if(!isNearEntrance(pt,margin)){
|
||||||
addPole(L.latLng(pt.lat,pt.lng));
|
addPole(L.latLng(pt.lat,pt.lng),road.name||road.stylename||'');
|
||||||
totalPoles++;
|
totalPoles++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.getElementById('autoPanel').style.display='none';
|
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 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){
|
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++;
|
poleCount++;
|
||||||
|
const rn=roadName||getRoadName(latlng);
|
||||||
|
const suffix=rn?' ('+rn+')':'';
|
||||||
const name='Опора '+poleCount;
|
const name='Опора '+poleCount;
|
||||||
const icon=L.divIcon({className:'',html:'<div class="pole-label">'+poleCount+'</div>',iconSize:[22,22],iconAnchor:[11,11]});
|
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));
|
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});
|
markers.push({id:poleCount,lat:latlng.lat,lng:latlng.lng,name,marker,road:rn});
|
||||||
if(markers.length>1){
|
if(markers.length>1){
|
||||||
const p=markers[markers.length-2];
|
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);
|
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;
|
const dist=i>0?haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng):0;
|
||||||
total+=dist;
|
total+=dist;
|
||||||
const tr=document.createElement('tr');
|
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);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
const avg=markers.length>1?(total/(markers.length-1)):0;
|
const avg=markers.length>1?(total/(markers.length-1)):0;
|
||||||
@ -503,8 +546,8 @@ function exportDrawIO(){
|
|||||||
}
|
}
|
||||||
function exportCSV(){
|
function exportCSV(){
|
||||||
if(!markers.length){showToast('Нет опор');return;}
|
if(!markers.length){showToast('Нет опор');return;}
|
||||||
let csv='№,Название,Широта,Долгота,Расстояние_до_предыдущей_м\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.lat.toFixed(6)+','+p.lng.toFixed(6)+','+d+'\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');
|
download(csv,'opory_vl04.csv','text/csv;charset=utf-8');
|
||||||
}
|
}
|
||||||
function download(content,filename,type){
|
function download(content,filename,type){
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user