From 0c173e048eeb849a2f048365cb2576e880d070ff Mon Sep 17 00:00:00 2001 From: Dauren777 Date: Fri, 19 Jun 2026 06:45:33 +0000 Subject: [PATCH] v10: district+ village dropdowns via Overpass --- index.html | 157 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 95 insertions(+), 62 deletions(-) diff --git a/index.html b/index.html index ffc82a8..5dfb19d 100644 --- a/index.html +++ b/index.html @@ -64,12 +64,11 @@ body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,sans-serif;
- + - + - - +
@@ -154,84 +153,118 @@ function init(){ } // ====================== ВЫБОР ЛОКАЦИИ ====================== +let districtData=[]; // {name, osm_id} +let regionOsmId=null; + function onRegionChange(){ const name=document.getElementById('regionSelect').value; - const r=REGIONS.find(x=>x.name===name); + const region=REGIONS.find(x=>x.name===name); const sel=document.getElementById('districtSelect'); - sel.innerHTML=''; - if(!r){document.getElementById('locStatus').textContent='';return;} - map.setView(r.center,9); + const vsel=document.getElementById('villageSelect'); + sel.innerHTML=''; + vsel.innerHTML=''; + districtData=[]; + if(!region){document.getElementById('locStatus').textContent='';return;} + map.setView(region.center,9); document.getElementById('locStatus').textContent='⏳ районы...'; - // Ищем область в Nominatim, получаем bounding box, ищем районы внутри - const searchName=name+' область'; - fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(searchName)+'&countrycodes=kz&limit=1') - .then(r=>r.json()).then(reg=>{ - if(!reg.length)return fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(name)+'&countrycodes=kz&limit=1').then(r=>r.json()); - return reg; - }).then(reg=>{ - if(!reg.length)throw new Error('region not found'); - const b=reg[0].boundingbox; - return fetch('https://nominatim.openstreetmap.org/search?format=json&q=район&countrycodes=kz&bounded=1&viewbox='+b[2]+','+b[0]+','+b[3]+','+b[1]+'&limit=40'); + fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(name+' область')+'&countrycodes=kz&limit=1') + .then(r=>r.json()) + .then(list=>{ + if(list.length)return list[0]; + return fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(name)+'&countrycodes=kz&limit=1').then(r=>r.json()).then(l=>l[0]||null); }) - .then(r=>r.json()).then(data=>{ - const names=new Set(); - data.forEach(item=>{ - if(item.type!='administrative')return; - const n=item.display_name.split(',')[0].trim(); - // отсекаем лишнее - if(n.toLowerCase().includes('город')||n===name)return; - if(n&&!names.has(n))names.add(n); + .then(reg=>{ + if(!reg)throw new Error('Регион не найден'); + regionOsmId=reg.osm_id; + const areaId=3600000000+reg.osm_id; + const q='[out:json];area('+areaId+')->.a;(rel(area.a)["boundary"="administrative"]["admin_level"~"7|8"];);out tags id;'; + return fetch('https://overpass-api.de/api/interpreter?data='+encodeURIComponent(q)); + }) + .then(r=>r.json()) + .then(data=>{ + if(!data||!data.elements)throw new Error('Нет данных'); + const map=new Map(); + data.elements.forEach(el=>{ + if(!el.tags||!el.tags.name)return; + let n=el.tags.name.replace(/\s*ауданы$/i,'').replace(/\s*районы$/i,'').replace(/\s*район$/i,'').trim(); + if(n&&!map.has(n))map.set(n,el.id); }); - const sorted=[...names].sort(); + const sorted=[...map.keys()].sort(); + sel.innerHTML=''; sorted.forEach(n=>{ const o=document.createElement('option'); o.value=n;o.textContent=n; sel.appendChild(o); + districtData.push({name:n,osm_id:map.get(n)}); }); - document.getElementById('locStatus').textContent=sorted.length?'✅ '+sorted.length+' районов':'⚠ районы не найдены (введите село вручную)'; + document.getElementById('locStatus').textContent=sorted.length?'✅ '+sorted.length+' районов':'⚠ районы не найдены'; }) - .catch(()=>{ - document.getElementById('locStatus').textContent='⚠ ошибка загрузки (введите село вручную)'; + .catch(e=>{ + sel.innerHTML=''; + document.getElementById('locStatus').textContent='⚠ '+e.message; }); } + function onDistrictSelect(){ - searchLocation(); -} -function searchLocation(){ - const r=document.getElementById('regionSelect').value; - const d=document.getElementById('districtSelect').value; - const v=document.getElementById('villageInput').value.trim(); - const parts=[]; - if(r)parts.push(r+' область'); - if(d)parts.push(d+' район'); - if(v)parts.push(v); - const q=parts.join(', '); - if(!q||!v&&!d){showToast('Введите название села');return;} - // Если есть район но нет села — летим в центр района - if(!v&&d){ - document.getElementById('locStatus').textContent='⏳ поиск района...'; - fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(d+' район '+r+' область')+'&countrycodes=kz&limit=5') - .then(r=>r.json()).then(data=>{ - if(!data.length){showToast('❌ Район не найден');document.getElementById('locStatus').textContent='';return;} - map.setView([data[0].lat,data[0].lon],11); - document.getElementById('locStatus').textContent='📍 '+data[0].display_name.split(',')[0]; - }); + const dname=document.getElementById('districtSelect').value; + const vsel=document.getElementById('villageSelect'); + vsel.innerHTML=''; + if(!dname||!regionOsmId){ + vsel.innerHTML=''; return; } - document.getElementById('locStatus').textContent='⏳ поиск...'; - fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(q)+'&countrycodes=kz&limit=8') - .then(r=>r.json()).then(data=>{ - if(!data.length){showToast('❌ Не найдено');document.getElementById('locStatus').textContent='не найдено';return;} - const loc=data[0]; - map.setView([loc.lat,loc.lon],16); - const name=loc.display_name.split(',')[0]; - showToast('📍 '+name); - document.getElementById('villageInput').value=name; - document.getElementById('locStatus').textContent=name; - setTimeout(autoDetect,500); + // Ищем район среди загруженных + const dist=districtData.find(d=>d.name===dname); + if(!dist){ + vsel.innerHTML=''; + return; + } + const areaId=3600000000+dist.osm_id; + // Ищем сёла и посёлки внутри района + const q='[out:json];area('+areaId+')->.a;(node(area.a)["place"~"village|hamlet|town"];);out tags;'; + fetch('https://overpass-api.de/api/interpreter?data='+encodeURIComponent(q)) + .then(r=>r.json()) + .then(data=>{ + vsel.innerHTML=''; + if(!data||!data.elements||!data.elements.length){ + vsel.innerHTML=''; + return; + } + const villages=[]; + data.elements.forEach(el=>{ + if(!el.tags||!el.tags.name)return; + villages.push({name:el.tags.name,lat:el.lat,lng:el.lon}); + }); + villages.sort((a,b)=>a.name.localeCompare(b.name,'ru')); + villages.forEach(v=>{ + const o=document.createElement('option'); + o.value=v.name; + o.dataset.lat=v.lat; + o.dataset.lng=v.lng; + o.textContent=v.name; + vsel.appendChild(o); + }); + document.getElementById('locStatus').textContent='✅ '+villages.length+' сёл'; + }) + .catch(()=>{ + vsel.innerHTML=''; }); } +function onVillageSelect(){ + const vsel=document.getElementById('villageSelect'); + const opt=vsel.options[vsel.selectedIndex]; + if(!opt||!opt.dataset.lat)return; + const lat=parseFloat(opt.dataset.lat); + const lng=parseFloat(opt.dataset.lng); + if(!isNaN(lat)&&!isNaN(lng)){ + map.setView([lat,lng],16); + showToast('📍 '+opt.value); + setTimeout(autoDetect,500); + } +} + + // ====================== АВТООПРЕДЕЛЕНИЕ УЛИЦ И ЗДАНИЙ (OSM) ====================== function autoDetect(){ document.getElementById('autoPanel').style.display='block';