Honlap címe

MENÜ

<!doctype html>
<html lang="hu">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Gyöngyös futár — utcakereső & statikus export</title>
<style>
body{font-family:Arial,Helvetica,sans-serif;margin:0;background:#f6f8fa;color:#111}
header{background:#0b74de;color:#fff;padding:12px 16px}
.container{display:flex;gap:12px;padding:12px}
.left{width:340px}
.card{background:#fff;padding:12px;border-radius:8px;box-shadow:0 1px 6px rgba(0,0,0,.06)}
.controls{display:flex;gap:8px;margin-top:8px;flex-wrap:wrap}
input,select,textarea,button{padding:8px;border-radius:6px;border:1px solid #d6dbe1}
button{background:#0b74de;color:#fff;border:none;cursor:pointer}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin-top:10px}
.district{padding:8px;border-radius:6px;border:1px solid #eaeef3;min-height:120px;background:#fff}
.oneway{color:#c94a4a;font-weight:700;font-size:12px}
.muted{color:#666;font-size:13px}
.result{margin-top:8px;padding:8px;background:#eef6ff;border-radius:6px}
.street{display:flex;justify-content:space-between;align-items:center;padding:4px 0}
.small{font-size:12px;color:#444}
@media(max-width:900px){.container{flex-direction:column}.left{width:100%}}
</style>
</head>
<body>
<header>Gyöngyös futár: teljes utcalista + futárbarát 10 körzet + statikus HTML export</header>

<div class="container">
<div class="left">
<div class="card">
<div><strong>1. Lekérés</strong></div>
<div class="muted">A gomb lefuttatja az Overpass lekérdezést, majd a Nominatim segítségével megkeresi a Karácsondi út koordinátáit és kiszámolja a körzeteket.</div>
<div class="controls">
<button id="fetchBtn">Lekér Gyöngyös utcáit (Overpass)</button>
<button id="geocodeBtn">Karácsondi út koordináta</button>
</div>
<div id="status" class="small" style="margin-top:8px">Állapot: készen.</div>

<hr style="margin:10px 0">

<div><strong>2. Keresés</strong></div>
<div style="display:flex;gap:8px;margin-top:8px">
<input id="searchInput" placeholder="Utca pl. Mégész utca 17" style="flex:1"/>
<button id="searchBtn">Keres</button>
</div>
<div id="searchResult" class="result">Találat: —</div>

<hr style="margin:10px 0">

<div><strong>3. Módosítás / Export</strong></div>
<div class="small muted" style="margin-bottom:6px">Jelöld ki a listában a kívánt utcákat, válaszd a körzetet és kattints a hozzárendelésre.</div>
<div style="display:flex;gap:6px">
<select id="assignSelect"></select>
<button id="assignBtn">Hozzárendel</button>
</div>

<div style="display:flex;gap:8px;margin-top:8px">
<button id="exportJson">Export JSON</button>
<button id="exportCsv">Export CSV</button>
<button id="generateStatic">Letöltés: statikus HTML</button>
</div>

<div style="margin-top:10px" class="small muted">
Tipp: először nyomd meg a <strong>Lekér</strong>, várj amíg elkészül, ellenőrizd a felosztást, majd kattints a <strong>Letöltés: statikus HTML</strong>-re — így kapsz egy végleges fájlt, ami már minden utcát tartalmaz.
</div>
</div>
</div>

<div style="flex:1">
<div class="card">
<div style="display:flex;justify-content:space-between;align-items:center">
<div><strong>Körzetek — 1..10 (futárbarát rendezés)</strong></div>
<div class="small muted">Utcák: <span id="count">0</span></div>
</div>

<div id="districtGrid" class="grid" style="margin-top:12px"></div>
</div>
</div>
</div>

<script>
/*
Fő lépések a kódban:
- Overpass lekérdezés: letöltünk 'way' elemeket, ahol 'highway' és van 'name' (Gyöngyös területére korlátozva).
- Aggregáljuk az utcákat név szerint, számolunk centroidot, figyeljük a 'oneway' taget.
- Geokódoljuk a "Karácsondi út, Gyöngyös" címet Nominatim segítségével (a felhasználó által megadott infó alapján).
- Klaszterezés k-means-szel (10 klaszter). A klaszterek számozásához a Karácsondi úttól való irányt és a M3 felé eső irányt figyelembe vesszük (ha geokód sikeres).
- Megjelenítés, keresés, áthelyezés, export, és a végső "generate static HTML" ami beágyazza az utcákat és a kiosztást egy letölthető fájlba.
*/

/* ---------- segédfüggvények ---------- */
const $ = id => document.getElementById(id);
function setStatus(s){ $('status').innerText = 'Állapot: ' + s; }
function escapeHtml(s){ return String(s).replace(/[&<>]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])); }

/* ---------- állapot ---------- */
let streets = []; // {name, coords: [[lat,lon],...], centroid:{lat,lon}, oneway:boolean, ways:[], district: int}
let districts = {}; // d -> [indexes]
const DIST_COUNT = 10;
$('assignSelect').innerHTML = Array.from({length:DIST_COUNT},(_,i)=>`<option value="${i+1}">Körzet ${i+1}</option>`).join('');
let entryPoint = null; // karacsondi út koordináta
let m3Point = null; // approximáció az M3 irányához

/* ---------- Overpass lekérdezés ---------- */
function overpassQLForTown(townName){
// egyszerű lekérdezés: area by name, majd way[highway][name] az adott area-ban
return `[out:json][timeout:60];
area["name"="${townName}"]->.a;
(
way(area.a)["highway"]["name"];
relation(area.a)["highway"]["name"];
);
out body;
>;
out skel qt;`;
}

async function fetchOverpass(town='Gyöngyös'){
setStatus('Overpass lekérés...');
try{
const q = overpassQLForTown(town);
// Overpass POST (lehet, hogy CORS miatt a böngészőnél működhet; ha nem, Overpass-turbo.com használható manuálisan)
const resp = await fetch('https://overpass-api.de/api/interpreter', {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},
body: 'data=' + encodeURIComponent(q)
});
if(!resp.ok) throw new Error('Overpass hiba: ' + resp.status);
const data = await resp.json();
setStatus('Adatok letöltve, feldolgozás...');
processOverpassData(data);
}catch(err){
console.error(err);
setStatus('Hiba: ' + err.message + '. Ha CORS hiba jön, nyisd meg helyben az Overpass Turbo-t és exportáld a JSON-t, majd importáld be.');
alert('Hiba az Overpass lekérésnél: ' + err.message + '\n\nHa a böngésződ blokkolja (CORS), nyisd meg ezt a kódot: https://overpass-turbo.eu/ és illeszd be a lekérdezést, futtasd, majd exportáld GeoJSON/JSON és használd az "Import JSON"-t a felületen.');
}
}

function processOverpassData(osm){
const nodes = {};
for(const e of osm.elements){
if(e.type === 'node') nodes[e.id] = e;
}
const ways = osm.elements.filter(e=>e.type==='way' && e.tags && e.tags.name);
const map = new Map();
for(const w of ways){
const name = w.tags.name.trim();
if(!name) continue;
const key = name.toLowerCase();
if(!map.has(key)) map.set(key, {name, coords:[], oneway:false, ways:[]});
const ent = map.get(key);
ent.ways.push(w.id);
if(w.tags.oneway && w.tags.oneway !== 'no') ent.oneway = true;
if(Array.isArray(w.nodes)){
for(const nid of w.nodes){
const n = nodes[nid];
if(n && typeof n.lat === 'number' && typeof n.lon === 'number') ent.coords.push([n.lat, n.lon]);
}
}
}
streets = [];
for(const [k,v] of map.entries()){
if(v.coords.length === 0) continue;
// centroid
let lat=0, lon=0;
for(const c of v.coords){ lat+=c[0]; lon+=c[1]; }
lat /= v.coords.length; lon /= v.coords.length;
streets.push({name:v.name, coords:v.coords, centroid:{lat,lon}, oneway:!!v.oneway, ways:v.ways, district: null});
}
setStatus('Utcák feldolgozva: ' + streets.length);
$('count').innerText = streets.length;
// automatikus kiosztás (k-means majd rendezés)
autoAssignDistricts();
renderDistricts();
}

/* ---------- geokód: Karácsondi út és M3 approximáció ---------- */
async function geocode(query){
setStatus('Geokódolás: ' + query);
try{
const url = 'https://nominatim.openstreetmap.org/search?format=jsonv2&q=' + encodeURIComponent(query + ', Gyöngyös, Hungary');
const resp = await fetch(url, {headers:{'User-Agent':'GyongyosFutar/1.0 (chatgpt)'}});
if(!resp.ok) throw new Error('Nominatim hiba: ' + resp.status);
const arr = await resp.json();
if(arr && arr.length>0){
const p = arr[0];
setStatus('Geokód sikeres: ' + p.display_name);
return {lat: Number(p.lat), lon: Number(p.lon), display_name: p.display_name};
} else {
setStatus('Nincs találat a geokódon.');
return null;
}
}catch(err){
setStatus('Geokód hiba: ' + err.message);
return null;
}
}

/* ---------- k-means egyszerű implementáció (2D) ---------- */
function kmeans(points, k, maxIter=100){
// points: [{lat,lon,idx}]
// init: véletlen pick
const N = points.length;
if(N===0) return [];
// init centers: pick k random distinct
const centers = [];
for(let i=0;i<k;i++){
const p = points[Math.floor(Math.random()*N)];
centers.push({lat:p.lat, lon:p.lon});
}
let labels = new Array(N).fill(0);
for(let iter=0;iter<maxIter;iter++){
let changed = false;
// assign
for(let i=0;i<N;i++){
const p = points[i];
let best = 0, bestd = dist2(p, centers[0]);
for(let c=1;c<k;c++){
const d = dist2(p, centers[c]);
if(d < bestd){ bestd = d; best = c; }
}
if(labels[i] !== best){ labels[i] = best; changed = true; }
}
// recompute centers
for(let c=0;c<k;c++){
let sumLat=0,sumLon=0,count=0;
for(let i=0;i<N;i++) if(labels[i]===c){ sumLat += points[i].lat; sumLon += points[i].lon; count++; }
if(count>0){ centers[c].lat = sumLat/count; centers[c].lon = sumLon/count; }
}
if(!changed) break;
}
return {labels, centers};
}
function dist2(a,b){ const dlat=a.lat-b.lat; const dlon=a.lon-b.lon; return dlat*dlat + dlon*dlon; }

/* ---------- automatikus kiosztás: kmeans + rendezés bejárási irány szerint ---------- */
function autoAssignDistricts(){
if(streets.length === 0) return;
const pts = streets.map((s,idx)=>({lat:s.centroid.lat, lon:s.centroid.lon, idx}));
// futtatjuk többször és kiválasztjuk a legjobb (egyszerű)
const km = kmeans(pts, DIST_COUNT, 200);
const labels = km.labels;
const centers = km.centers;
// hozzárendelés
districts = {};
for(let i=1;i<=DIST_COUNT;i++) districts[i]=[];
for(let i=0;i<pts.length;i++){
const lab = labels[i];
const dnum = lab + 1;
streets[pts[i].idx].district = dnum;
districts[dnum].push(pts[i].idx);
}
// rendezés: ha van entryPoint és m3Point, sorba rendezzük a cluster-centroidokat egy entry->m3 vektor mentén
if(entryPoint){
// compute angle or projection along vector from entry -> m3 (if m3 known), else from entry -> city centroid
const cityCentroid = computeCityCentroid();
const target = m3Point || cityCentroid;
const vx = target.lon - entryPoint.lon;
const vy = target.lat - entryPoint.lat;
// compute projection of each center onto v
const projections = centers.map((c, i)=> {
const wx = c.lon - entryPoint.lon;
const wy = c.lat - entryPoint.lat;
const proj = (wx*vx + wy*vy) / (Math.sqrt(vx*vx+vy*vy)||1);
return {i, proj};
});
projections.sort((a,b)=> a.proj - b.proj);
// mapping old cluster index -> new district number (1..10) in order from entry towards target
const mapNew = {};
for(let i=0;i<projections.length;i++){
mapNew[projections[i].i] = i+1;
}
// apply mapping
const newDistricts = {};
for(let i=1;i<=DIST_COUNT;i++) newDistricts[i]=[];
for(let d=1; d<=DIST_COUNT; d++){
for(const idx of districts[d]){
const oldLab = (d-1);
const newD = mapNew[oldLab] || d;
streets[idx].district = newD;
newDistricts[newD].push(idx);
}
}
districts = newDistricts;
}
}

/* ---------- helper: városi centroid ---------- */
function computeCityCentroid(){
let lat=0, lon=0, n=0;
for(const s of streets){ lat += s.centroid.lat; lon += s.centroid.lon; n++; }
return {lat: lat/n, lon: lon/n};
}

/* ---------- render ---------- */
function renderDistricts(){
const grid = $('districtGrid');
grid.innerHTML = '';
for(let d=1; d<=DIST_COUNT; d++){
const card = document.createElement('div');
card.className = 'district';
const h = document.createElement('h4'); h.innerText = 'Körzet ' + d + ' (' + (districts[d]?.length || 0) + ' utca)';
card.appendChild(h);
const list = document.createElement('div');
if(!districts[d] || districts[d].length === 0) list.innerHTML = '<div class="small muted">Üres</div>';
else {
for(const idx of districts[d]){
const s = streets[idx];
const row = document.createElement('div');
row.className = 'street';
const left = document.createElement('div');
left.innerHTML = `<strong>${escapeHtml(s.name)}</strong> ${s.oneway ? '<span class="oneway">egyirányú</span>':''}`;
const right = document.createElement('div');
const gmap = 'https://www.google.com/maps/search/?api=1&query=' + encodeURIComponent(s.name + ', Gyöngyös');
right.innerHTML = `<a target="_blank" href="${gmap}">Térkép</a> <label style="margin-left:8px"><input type="checkbox" data-idx="${idx}"></label>`;
row.appendChild(left); row.appendChild(right);
list.appendChild(row);
}
}
card.appendChild(list);
grid.appendChild(card);
}
}

/* ---------- keresés ---------- */
function search(q){
if(!q || q.trim()===''){ $('searchResult').innerHTML = 'Írj be egy utca nevet.'; return; }
const m = q.match(/(.+?)\\s+(\\d+[A-Za-z-]*)\\s*$/);
let house = null, stq = q;
if(m){ stq = m[1]; house = m[2]; }
stq = stq.trim().toLowerCase();
const matches = streets.filter(s=> s.name.toLowerCase().includes(stq));
if(matches.length===0){ $('searchResult').innerHTML = 'Nincs találat: ' + escapeHtml(q); return; }
const first = matches[0];
const g = 'https://www.google.com/maps/search/?api=1&query=' + encodeURIComponent(first.name + ', Gyöngyös');
let html = `<div><strong>${escapeHtml(first.name)}</strong> — körzet <strong>${first.district}</strong></div>`;
html += `<div class="small">Google Maps: <a href="${g}" target="_blank">${g}</a></div>`;
if(first.oneway) html += `<div class="oneway">OSM: egyirányú</div>`;
if(matches.length>1){
html += `<hr><div class="small muted">További találatok (${matches.length}):</div><ul>`;
matches.forEach(m=> html += `<li>${escapeHtml(m.name)} — körzet ${m.district} <a target="_blank" href="https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(m.name + ', Gyöngyös')}">térkép</a></li>`);
html += '</ul>';
}
$('searchResult').innerHTML = html;
// görgess a körzetre
const card = document.querySelector('#districtGrid .district:nth-child(' + first.district + ')');
if(card) card.scrollIntoView({behavior:'smooth', block:'center'});
}

/* ---------- áthelyezés ---------- */
function assignSelectedToDistrict(dnum){
const checks = document.querySelectorAll('#districtGrid input[type="checkbox"]:checked');
if(!checks.length){ alert('Jelölj meg legalább egy utcát.'); return; }
for(const c of checks){
const idx = Number(c.dataset.idx);
const old = streets[idx].district;
// eltávolítjuk a régi helyről
districts[old] = (districts[old] || []).filter(i=> i !== idx);
// új helyre rakjuk
streets[idx].district = dnum;
districts[dnum].push(idx);
c.checked = false;
}
renderDistricts();
}

/* ---------- exportok ---------- */
function exportJSON(){
const out = {meta:{city:'Gyöngyös',source:'OpenStreetMap/Overpass', date: (new Date()).toISOString()}, streets};
const blob = new Blob([JSON.stringify(out,null,2)], {type:'application/json'});
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'gyongyos_utca_lista.json'; a.click();
URL.revokeObjectURL(a.href);
}
function exportCSV(){
let csv = 'name;district;oneway;google_maps_link\\n';
for(const s of streets){
const g = 'https://www.google.com/maps/search/?api=1&query=' + encodeURIComponent(s.name + ', Gyöngyös');
csv += `"${s.name.replace(/"/g,'""')}";${s.district || ''};${s.oneway ? 'yes':''};"${g}"\\n`;
}
const blob = new Blob([csv], {type:'text/csv'});
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'gyongyos_utca_lista.csv'; a.click();
URL.revokeObjectURL(a.href);
}

/* ---------- generálás: statikus HTML letöltése (minden utca beágyazva) ---------- */
function generateStaticHTML(){
// construct minimal HTML with embedded JSON data (streets + districts)
const payload = JSON.stringify({meta:{generated:(new Date()).toISOString(), city:'Gyöngyös'}, streets}, null, 2);
const template = `<!doctype html>
<html lang="hu">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Gyöngyös utca - statikus</title>
<style>body{font-family:Arial,Helvetica,sans-serif;padding:12px;background:#f6f8fa}pre{white-space:pre-wrap}</style>
</head><body>
<h2>Gyöngyös — beágyazott utcalista (generálva: ${new Date().toLocaleString()})</h2>
<p>A fájl tartalmazza az összes utcát és a kiosztott körzeteket (1–10). Használhatod offline a keresésre.</p>
<pre id="data" style="display:none">${payload.replace(/</g,'\\u003c')}</pre>
<script>
const data = JSON.parse(document.getElementById('data').textContent);
// egyszerű kereső és megjelenítő (kicsi)
function esc(s){ return s.replace(/[&<>]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])); }
document.body.insertAdjacentHTML('beforeend','<div><input id="q" style="width:60%" placeholder=\"Utca (pl. Mégész utca 17)\"><button id="b">Keres</button></div><div id="res" style="margin-top:12px"></div>');
document.getElementById('b').addEventListener('click', ()=> {
const q = document.getElementById('q').value.trim().toLowerCase();
if(!q){ document.getElementById('res').innerHTML='Írj be utca nevet.'; return;}
const m = data.streets.filter(s=> s.name.toLowerCase().includes(q));
if(m.length===0){ document.getElementById('res').innerHTML='Nincs találat.'; return; }
let html = '<ul>';
m.forEach(s=> html += '<li>' + esc(s.name) + ' — körzet: ' + (s.district||'') + (s.oneway? ' (egyirányú)':'') + ' <a href=\"https://www.google.com/maps/search/?api=1&query=' + encodeURIComponent(s.name + ', Gyöngyös') + '\" target=\"_blank\">Térkép</a></li>');
html += '</ul>';
document.getElementById('res').innerHTML = html;
});
</script>
</body></html>`;
const blob = new Blob([template], {type:'text/html'});
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'gyongyos_utca_statikus.html'; a.click();
URL.revokeObjectURL(a.href);
}

/* ---------- események és init ---------- */
$('fetchBtn').addEventListener('click', ()=> fetchOverpass('Gyöngyös'));
$('geocodeBtn').addEventListener('click', async ()=>{
const res = await geocode('Karácsondi út');
if(res){ entryPoint = res; setStatus('Entry pont beállítva: ' + res.display_name); }
});
$('searchBtn').addEventListener('click', ()=> search($('searchInput').value));
$('assignBtn').addEventListener('click', ()=> {
const d = Number($('assignSelect').value);
assignSelectedToDistrict(d);
});
$('exportJson').addEventListener('click', exportJSON);
$('exportCsv').addEventListener('click', exportCSV);
$('generateStatic').addEventListener('click', generateStaticHTML);

/* init állapot */
setStatus('Készen. Nyomd meg a "Lekér" gombot az utcák letöltéséhez.');
</script>
</body>
</html>

Asztali nézet