v10: district+ village dropdowns via Overpass
This commit is contained in:
parent
69d9d1276c
commit
0c173e048e
157
index.html
157
index.html
@ -64,12 +64,11 @@ body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,sans-serif;
|
|||||||
</div>
|
</div>
|
||||||
<div class="loc-panel">
|
<div class="loc-panel">
|
||||||
<label>📍 Область:</label>
|
<label>📍 Область:</label>
|
||||||
<select id="regionSelect" onchange="onRegionChange()"><option value="">— выберите —</option></select>
|
<select id="regionSelect" onchange="onRegionChange()" style="max-width:110px"><option value="">— выберите —</option></select>
|
||||||
<label>Район:</label>
|
<label>Район:</label>
|
||||||
<select id="districtSelect" onchange="onDistrictSelect()"><option value="">— все —</option></select>
|
<select id="districtSelect" onchange="onDistrictSelect()" style="max-width:110px"><option value="">— загрузка —</option></select>
|
||||||
<label>Село:</label>
|
<label>Село:</label>
|
||||||
<input type="text" id="villageInput" placeholder="Название села..." style="width:100px">
|
<select id="villageSelect" onchange="onVillageSelect()" style="max-width:120px"><option value="">— выберите район —</option></select>
|
||||||
<button onclick="searchLocation()">🔍 Найти</button>
|
|
||||||
<button onclick="autoDetect()" style="background:#FF9800;color:#fff">🔄 Авто</button>
|
<button onclick="autoDetect()" style="background:#FF9800;color:#fff">🔄 Авто</button>
|
||||||
<span id="locStatus" style="color:#888;font-size:9px"></span>
|
<span id="locStatus" style="color:#888;font-size:9px"></span>
|
||||||
</div>
|
</div>
|
||||||
@ -154,84 +153,118 @@ function init(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ====================== ВЫБОР ЛОКАЦИИ ======================
|
// ====================== ВЫБОР ЛОКАЦИИ ======================
|
||||||
|
let districtData=[]; // {name, osm_id}
|
||||||
|
let regionOsmId=null;
|
||||||
|
|
||||||
function onRegionChange(){
|
function onRegionChange(){
|
||||||
const name=document.getElementById('regionSelect').value;
|
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');
|
const sel=document.getElementById('districtSelect');
|
||||||
sel.innerHTML='<option value="">— все —</option>';
|
const vsel=document.getElementById('villageSelect');
|
||||||
if(!r){document.getElementById('locStatus').textContent='';return;}
|
sel.innerHTML='<option value="">— загрузка —</option>';
|
||||||
map.setView(r.center,9);
|
vsel.innerHTML='<option value="">— выберите район —</option>';
|
||||||
|
districtData=[];
|
||||||
|
if(!region){document.getElementById('locStatus').textContent='';return;}
|
||||||
|
map.setView(region.center,9);
|
||||||
document.getElementById('locStatus').textContent='⏳ районы...';
|
document.getElementById('locStatus').textContent='⏳ районы...';
|
||||||
// Ищем область в Nominatim, получаем bounding box, ищем районы внутри
|
fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(name+' область')+'&countrycodes=kz&limit=1')
|
||||||
const searchName=name+' область';
|
.then(r=>r.json())
|
||||||
fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(searchName)+'&countrycodes=kz&limit=1')
|
.then(list=>{
|
||||||
.then(r=>r.json()).then(reg=>{
|
if(list.length)return list[0];
|
||||||
if(!reg.length)return fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(name)+'&countrycodes=kz&limit=1').then(r=>r.json());
|
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);
|
||||||
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');
|
|
||||||
})
|
})
|
||||||
.then(r=>r.json()).then(data=>{
|
.then(reg=>{
|
||||||
const names=new Set();
|
if(!reg)throw new Error('Регион не найден');
|
||||||
data.forEach(item=>{
|
regionOsmId=reg.osm_id;
|
||||||
if(item.type!='administrative')return;
|
const areaId=3600000000+reg.osm_id;
|
||||||
const n=item.display_name.split(',')[0].trim();
|
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));
|
||||||
if(n.toLowerCase().includes('город')||n===name)return;
|
})
|
||||||
if(n&&!names.has(n))names.add(n);
|
.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='<option value="">— все —</option>';
|
||||||
sorted.forEach(n=>{
|
sorted.forEach(n=>{
|
||||||
const o=document.createElement('option');
|
const o=document.createElement('option');
|
||||||
o.value=n;o.textContent=n;
|
o.value=n;o.textContent=n;
|
||||||
sel.appendChild(o);
|
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(()=>{
|
.catch(e=>{
|
||||||
document.getElementById('locStatus').textContent='⚠ ошибка загрузки (введите село вручную)';
|
sel.innerHTML='<option value="">— ошибка —</option>';
|
||||||
|
document.getElementById('locStatus').textContent='⚠ '+e.message;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDistrictSelect(){
|
function onDistrictSelect(){
|
||||||
searchLocation();
|
const dname=document.getElementById('districtSelect').value;
|
||||||
}
|
const vsel=document.getElementById('villageSelect');
|
||||||
function searchLocation(){
|
vsel.innerHTML='<option value="">⏳ загрузка сёл...</option>';
|
||||||
const r=document.getElementById('regionSelect').value;
|
if(!dname||!regionOsmId){
|
||||||
const d=document.getElementById('districtSelect').value;
|
vsel.innerHTML='<option value="">— выберите район —</option>';
|
||||||
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];
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
document.getElementById('locStatus').textContent='⏳ поиск...';
|
// Ищем район среди загруженных
|
||||||
fetch('https://nominatim.openstreetmap.org/search?format=json&q='+encodeURIComponent(q)+'&countrycodes=kz&limit=8')
|
const dist=districtData.find(d=>d.name===dname);
|
||||||
.then(r=>r.json()).then(data=>{
|
if(!dist){
|
||||||
if(!data.length){showToast('❌ Не найдено');document.getElementById('locStatus').textContent='не найдено';return;}
|
vsel.innerHTML='<option value="">— район не найден —</option>';
|
||||||
const loc=data[0];
|
return;
|
||||||
map.setView([loc.lat,loc.lon],16);
|
}
|
||||||
const name=loc.display_name.split(',')[0];
|
const areaId=3600000000+dist.osm_id;
|
||||||
showToast('📍 '+name);
|
// Ищем сёла и посёлки внутри района
|
||||||
document.getElementById('villageInput').value=name;
|
const q='[out:json];area('+areaId+')->.a;(node(area.a)["place"~"village|hamlet|town"];);out tags;';
|
||||||
document.getElementById('locStatus').textContent=name;
|
fetch('https://overpass-api.de/api/interpreter?data='+encodeURIComponent(q))
|
||||||
setTimeout(autoDetect,500);
|
.then(r=>r.json())
|
||||||
|
.then(data=>{
|
||||||
|
vsel.innerHTML='<option value="">— выберите село —</option>';
|
||||||
|
if(!data||!data.elements||!data.elements.length){
|
||||||
|
vsel.innerHTML='<option value="">— сёла не найдены —</option>';
|
||||||
|
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='<option value="">— ошибка загрузки —</option>';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) ======================
|
// ====================== АВТООПРЕДЕЛЕНИЕ УЛИЦ И ЗДАНИЙ (OSM) ======================
|
||||||
function autoDetect(){
|
function autoDetect(){
|
||||||
document.getElementById('autoPanel').style.display='block';
|
document.getElementById('autoPanel').style.display='block';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user