From a1f439001aa2fdfedb965ad7b6c41608d18ecef6 Mon Sep 17 00:00:00 2001 From: Dauren777 Date: Fri, 19 Jun 2026 06:09:19 +0000 Subject: [PATCH] v4: auto-detect streets & buildings from OSM, snap to road --- index.html | 381 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 218 insertions(+), 163 deletions(-) diff --git a/index.html b/index.html index 69c2012..334a71f 100644 --- a/index.html +++ b/index.html @@ -6,44 +6,47 @@ Планировщик опор ВЛ — РК @@ -51,15 +54,14 @@ body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,sans-serif;

⚡ Опоры ВЛ-0.4 кВ

- + - - - + + + - + -
@@ -68,20 +70,30 @@ body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,sans-serif; - + +
+ Режим: ручная расстановка
+
+ 🔄 Автоопределение
+ Загрузка данных... +
+ +
- Опора ЖБ - Линия ВЛ - В Въезд + Опора + ВЛ + В Въезд + Улица (OSM) + Здание
',iconSize:[26,18],iconAnchor:[13,9]}); - const marker=L.marker(latlng,{icon}).addTo(map).bindPopup('🚗 Въезд во двор'); + const marker=L.marker(latlng,{icon}).addTo(map).bindPopup('🚗 Въезд'); entrances.push({lat:latlng.lat,lng:latlng.lng,marker}); const circle=L.circle(latlng,{radius:5,color:'#FF9800',fillColor:'#FF9800',fillOpacity:0.15,dashArray:'3,3'}).addTo(map); entranceCircles.push(circle); showToast('🚗 Въезд отмечен'); } +function isNearEntrance(pt,margin){ + for(const e of entrances)if(haversine(pt.lat,pt.lng,e.lat,e.lng)x*Math.PI/180; const dLat=toRad(lat2-lat1),dLon=toRad(lon2-lon1); - const a=Math.sin(dLat/2)**2+Math.cos(toRad(lat1))*Math.cos(toRad(lat2))*Math.sin(dLon/2)**2; - return R*2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a)); + return R*2*Math.atan2(Math.sqrt(Math.sin(dLat/2)**2+Math.cos(toRad(lat1))*Math.cos(toRad(lat2))*Math.sin(dLon/2)**2),Math.sqrt(1-(Math.sin(dLat/2)**2+Math.cos(toRad(lat1))*Math.cos(toRad(lat2))*Math.sin(dLon/2)**2))); } // ====================== ТАБЛИЦА ====================== @@ -322,136 +454,59 @@ function clearAll(){ } document.addEventListener('keydown',e=>{if(e.key==='Enter'&&mode==='line')finishLine();}); -// ====================== ЭКСПОРТ В SVG (Visio/draw.io) ====================== +// ====================== ЭКСПОРТ ====================== function exportSVG(){ if(!markers.length){showToast('Нет опор');return;} const first=markers[0]; const step=parseFloat(document.getElementById('poleStep').value)||50; let svg=` - - + `; - // заголовок const date=new Date().toLocaleDateString('ru-RU'); svg+=`Схема расстановки опор ВЛ-0.4 кВ -Дата: ${date} | Шаг: ${step} м | Всего опор: ${markers.length} +Дата: ${date} | Шаг: ${step} м | ${markers.length} опор Центр: ${first.lat.toFixed(4)}, ${first.lng.toFixed(4)}`; - // сетка - svg+=``; - // конвертация geo → svg (простая проекция) const lats=markers.map(m=>m.lat),lngs=markers.map(m=>m.lng); const minLat=Math.min(...lats),maxLat=Math.max(...lats); const minLng=Math.min(...lngs),maxLng=Math.max(...lngs); const pad=60; const sx=x=>pad+(x-minLng)/(maxLng-minLng||1)*(760-2*pad); const sy=y=>560-pad-(y-minLat)/(maxLat-minLat||1)*(560-2*pad-85); - // линии - markers.forEach((p,i)=>{ - if(i===0)return; - const p1=markers[i-1]; - svg+=``; - }); - // опоры - markers.forEach(p=>{ - const x=sx(p.lng),y=sy(p.lat); - svg+=``; - svg+=`${p.id}`; - // подпись - svg+=`${p.name}`; - }); - // въезды - entrances.forEach(e=>{ - const x=sx(e.lng),y=sy(e.lat); - svg+=``; - svg+=`В`; - }); - // таблица расстояний - svg+=``; - let total=0; - markers.forEach((p,i)=>{ - if(i>0){const d=haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng);total+=d; - svg+=`${p.id}: ${d.toFixed(1)}м `;} - }); - svg+=`| Всего: ${total.toFixed(1)} м`; - svg+=``; + markers.forEach((p,i)=>{if(i>0){const p1=markers[i-1];svg+=``;}}); + markers.forEach(p=>{const x=sx(p.lng),y=sy(p.lat);svg+=`${p.id}${p.name}`;}); + entrances.forEach(e=>{const x=sx(e.lng),y=sy(e.lat);svg+=`В`;}); + let total=0;markers.forEach((p,i)=>{if(i>0)total+=haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng);}); + svg+=`Всего: ${total.toFixed(1)} м | ${markers.length} опор | ср.пролёт: ${markers.length>1?(total/(markers.length-1)).toFixed(1):0} м`; download(svg,'opory_vl04.svg','image/svg+xml'); - showToast('📥 SVG экспортирован (открывается в Visio)'); + showToast('📥 SVG (Visio/draw.io)'); } - -// ====================== ЭКСПОРТ В DRAW.IO ====================== function exportDrawIO(){ if(!markers.length){showToast('Нет опор');return;} - const first=markers[0]; - const step=parseFloat(document.getElementById('poleStep').value)||50; const date=new Date().toLocaleDateString('ru-RU'); - let mx=''; - mx+=``; - mx+=``; - // заголовок - mx+=``; - // масштаб + let mx=''; + mx+=``; const lats=markers.map(m=>m.lat),lngs=markers.map(m=>m.lng); const minLat=Math.min(...lats),maxLat=Math.max(...lats); const minLng=Math.min(...lngs),maxLng=Math.max(...lngs); const sx=x=>40+(x-minLng)/(maxLng-minLng||1)*700; - const sy=y=>560-(y-minLat)/(maxLat-minLat||1)*500; - let lineId=100; - // линии - markers.forEach((p,i)=>{ - if(i===0)return; - const p1=markers[i-1]; - const d=haversine(p1.lat,p1.lng,p.lat,p.lng).toFixed(1); - lineId++; - mx+=``; - }); - // опоры - markers.forEach(p=>{ - const x=sx(p.lng).toFixed(0),y=sy(p.lat).toFixed(0); - mx+=``; - mx+=``; - }); - // въезды - entrances.forEach((e,i)=>{ - const x=sx(e.lng).toFixed(0),y=sy(e.lat).toFixed(0); - mx+=``; - }); - // итого - let total=0; - markers.forEach((p,i)=>{if(i>0)total+=haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng);}); - mx+=``; + const sy=y=>560-(y-minLat)/(maxLat-minLat||1)*520; + let id=100; + markers.forEach((p,i)=>{if(i>0){const d=haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng).toFixed(1);id++;mx+=``;}}); + markers.forEach(p=>{const x=sx(p.lng).toFixed(0),y=sy(p.lat).toFixed(0);mx+=``;}); + entrances.forEach((e,i)=>{const x=sx(e.lng).toFixed(0),y=sy(e.lat).toFixed(0);mx+=``;}); + let total=0;markers.forEach((p,i)=>{if(i>0)total+=haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng);}); + mx+=``; mx+=``; download(mx,'opory_vl04.drawio','application/xml'); - showToast('📥 draw.io экспортирован'); + showToast('📥 draw.io'); } - -// ====================== CSV / JSON ====================== function exportCSV(){ if(!markers.length){showToast('Нет опор');return;} let csv='№,Название,Широта,Долгота,Расстояние_до_предыдущей_м\n'; - markers.forEach((p,i)=>{ - const dist=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)+','+dist+'\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';}); download(csv,'opory_vl04.csv','text/csv;charset=utf-8'); } -function exportJSON(){ - if(!markers.length){showToast('Нет опор');return;} - const data=markers.map((p,i)=>{ - const dist=i>0?haversine(markers[i-1].lat,markers[i-1].lng,p.lat,p.lng):0; - return{id:p.id,name:p.name,lat:parseFloat(p.lat.toFixed(6)),lng:parseFloat(p.lng.toFixed(6)),distance_m:parseFloat(dist.toFixed(1))}; - }); - download(JSON.stringify({poles:data,total_m:data.reduce((s,d)=>s+d.distance_m,0),average_span_m:data.length>1?(data.reduce((s,d)=>s+d.distance_m,0)/(data.length-1)):0},null,2),'opory_vl04.json','application/json'); -} function download(content,filename,type){ const blob=new Blob([content],{type:'application/octet-stream'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob);