CBS Sİstemi deneme uygulaması

翻译
<!DOCTYPE html>
<html lang="tr">
<head>
GeoHub CBS — Türkiye Atlası
:root{
--bg:#eef2f7; --panel:#ffffff; --panel2:#f5f7fb; --text:#0f172a; --muted:#64748b;
--border:#e2e8f0; --accent:#0284c7; --accent-soft:rgba(2,132,199,.10); --shadow:0 12px 32px -12px rgba(15,23,42,.35);
--sea:#dbe7f3;
}
html.dark{
--bg:#020617; --panel:#0f172a; --panel2:#15203a; --text:#e2e8f0; --muted:#94a3b8;
--border:#1e293b; --accent:#38bdf8; --accent-soft:rgba(56,189,248,.14); --shadow:0 12px 32px -12px rgba(0,0,0,.8);
--sea:#0b1324;
}
*{box-sizing:border-box}
/* Tailwind yüklenmeden önce de yerleşimin doğru olması için temel düzen */
body{display:flex;flex-direction:column;overflow:hidden}
#shell{position:relative;display:flex;flex:1 1 auto;min-height:0}
#mapWrap{position:relative;flex:1 1 auto;min-width:0}
#map{position:absolute;top:0;right:0;bottom:0;left:0}
#sidebar{display:flex;flex-direction:column;flex:none;width:380px}
html,body{height:100%;margin:0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased}
.panel{background:var(--panel);border:1px solid var(--border)}
.panel2{background:var(--panel2);border:1px solid var(--border)}
.muted{color:var(--muted)} .accent{color:var(--accent)}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.4rem;border:1px solid var(--border);background:var(--panel);color:var(--text);border-radius:.6rem;padding:.42rem .7rem;font-size:.78rem;font-weight:600;cursor:pointer;transition:.15s;white-space:nowrap}
.btn:hover{border-color:var(--accent);color:var(--accent)}
.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}
.btn.primary:hover{filter:brightness(1.1);color:#fff}
.btn.on{background:var(--accent);border-color:var(--accent);color:#fff}
input,select{background:var(--panel2);color:var(--text);border:1px solid var(--border);border-radius:.6rem;outline:none;font-family:inherit}
input:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
input[type=range]{padding:0;height:4px;border-radius:99px;accent-color:var(--accent);background:var(--border);border:none;width:100%}
input[type=checkbox]{accent-color:var(--accent);width:1rem;height:1rem}
.scroll::-webkit-scrollbar{width:8px;height:8px}.scroll::-webkit-scrollbar-thumb{background:var(--border);border-radius:8px}
.sec{margin-bottom:1.25rem}
.sec>h3{font-size:.66rem;font-weight:800;letter-spacing:.08em;color:var(--muted);text-transform:uppercase;margin:0 0 .5rem}
.seg{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;background:var(--panel2);border:1px solid var(--border);border-radius:.7rem;padding:3px;gap:2px}
.seg button{padding:.42rem .3rem;font-size:.74rem;font-weight:700;border-radius:.5rem;color:var(--muted);cursor:pointer;transition:.15s}
.seg button.on{background:var(--panel);color:var(--accent);box-shadow:0 1px 4px rgba(15,23,42,.18)}
.tabbtn{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;padding:.55rem .2rem .5rem;font-size:.68rem;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;cursor:pointer;transition:.15s}
.tabbtn:hover{color:var(--text)}
.tabbtn.active{color:var(--accent);border-color:var(--accent);background:var(--accent-soft)}
.chip{display:inline-flex;align-items:center;gap:.35rem;padding:.25rem .6rem;border-radius:999px;font-size:.72rem;font-weight:600;border:1px solid var(--border);background:var(--panel2);cursor:pointer;user-select:none;transition:.15s}
.chip.off{opacity:.4}
.chip i{width:.6rem;height:.6rem;border-radius:50%;display:inline-block}
.row{display:flex;align-items:center;gap:.6rem;padding:.45rem .6rem;border-radius:.6rem;cursor:pointer;transition:.12s}
.row:hover,.row.sel{background:var(--accent-soft)}
.bar{height:.42rem;border-radius:999px;background:var(--border);overflow:hidden}
.bar>span{display:block;height:100%;border-radius:999px;transition:width .5s}
.toolbtn{width:2.4rem;height:2.4rem;display:flex;align-items:center;justify-content:center;border-radius:.7rem;background:var(--panel);border:1px solid var(--border);color:var(--text);cursor:pointer;box-shadow:var(--shadow);transition:.15s}
.toolbtn:hover{color:var(--accent);border-color:var(--accent)}
.toolbtn.on{background:var(--accent);color:#fff;border-color:var(--accent)}
.ramp{height:1.5rem;flex:1;border-radius:.45rem;border:2px solid transparent;cursor:pointer;transition:.15s}
.ramp.on{border-color:var(--text)}
.base{padding:.5rem .3rem;border-radius:.6rem;border:1px solid var(--border);background:var(--panel2);font-size:.7rem;font-weight:700;cursor:pointer;text-align:center;transition:.15s}
.base.on{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
table.tbl{width:100%;border-collapse:collapse;font-size:.74rem}
table.tbl th{position:sticky;top:0;background:var(--panel);text-align:left;padding:.45rem .4rem;font-size:.66rem;letter-spacing:.04em;color:var(--muted);cursor:pointer;border-bottom:1px solid var(--border);user-select:none;white-space:nowrap}
table.tbl th:hover{color:var(--accent)}
table.tbl td{padding:.38rem .4rem;border-bottom:1px solid var(--border);white-space:nowrap}
table.tbl tr.r{cursor:pointer}table.tbl tr.r:hover,table.tbl tr.r.sel{background:var(--accent-soft)}
.num{text-align:right;font-variant-numeric:tabular-nums}
/* Leaflet */
.leaflet-container{background:var(--sea);font-family:inherit}
.leaflet-container.crosshair,.leaflet-container.crosshair .leaflet-interactive{cursor:crosshair!important}
.leaflet-control-zoom a,.leaflet-bar a{background:var(--panel)!important;color:var(--text)!important;border-color:var(--border)!important}
.leaflet-control-attribution{background:color-mix(in srgb,var(--panel) 80%,transparent)!important;color:var(--muted)!important;font-size:10px}
.leaflet-control-attribution a{color:var(--accent)!important}
.leaflet-control-scale-line{background:color-mix(in srgb,var(--panel) 80%,transparent);color:var(--text);border-color:var(--muted)}
.ptip{background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:.7rem;box-shadow:var(--shadow);padding:.5rem .7rem;font-size:12px;line-height:1.35}
.ptip:before{display:none}
.plab{background:none!important;border:none!important}
.plab span{position:absolute;transform:translate(-50%,-50%);white-space:nowrap;font-weight:700;color:#0f172a;pointer-events:none;
font-size:calc(7px + (var(--z,6) - 5) * 1.6px);text-shadow:0 0 2px #fff,0 0 2px #fff,0 0 3px #fff,0 0 4px #fff}
.fade-in{animation:fade .25s ease}
@keyframes fade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes load{0%{transform:translateX(-100%)}100%{transform:translateX(250%)}}
.loadbar{width:40%;animation:load 1.1s ease-in-out infinite}
#sidebar{transition:transform .3s}
@media(max-width:767px){
#sidebar{position:absolute;z-index:1100;top:0;bottom:0;left:0;width:92%;max-width:390px;transform:translateX(-105%);box-shadow:var(--shadow)}
#sidebar.open{transform:none}
main.card-open #legend{display:none}
}
</style>
</head>
<body class="flex flex-col overflow-hidden">
<!-- ÜST ÇUBUK -->
GeoHub CBS
TÜRKİYE ATLASI
</div>
</div>
</div>
</div>
</div>
</div>
</header>
<!-- KENAR PANEL -->
</div>
<!-- KATMAN -->

Tematik gösterge

</div>
</div>

Sınıflandırma

Kuantil (eşit sayıda il)
Doğal kırılım (Jenks)
Eşit aralık
Logaritmik aralık
</select>
Sınıf sayısı6
</div>

Görünüm

Saydamlık85%
İl sınırları
Orantılı semboller (daire)
Etiketler
</div>
</div>
</div>

Coğrafi bölgeler

</div>

Altlık harita

</div>
</section>
<!-- SORGU -->

Hazır sorgular

</div>

Öznitelik sorgusu

</div>
</div>
</section>
<!-- İSTATİSTİK -->
<!-- TABLO -->
</div>
</section>
Sınırlar: alpers/Turkey-Maps-GeoJSON · Nüfus: TÜİK ADNKS 2023 · Yüzölçümü: HGM (yaklaşık)
</div>
</aside>
<!-- HARİTA -->
<!-- Araç çubuğu -->
</div>
<!-- Araç paneli -->
<!-- İl kartı -->
<!-- Lejant -->
<!-- Durum çubuğu -->
—
<!-- Veri uyarısı -->
<!-- Yükleniyor -->
İl sınırları yükleniyor
Veri kaynağına bağlanılıyor…
</div>
</div>
</main>
</div>
<!-- Karşılaştırma -->

İl karşılaştırma

</div>
</div>
</div>
/* =====================================================================
VERİ — TÜİK ADNKS 2023 nüfus (31.12.2023) · yüzölçümü HGM (yaklaşık)
[plaka, ad, bölge, nüfus, yüzölçümü km², enlem, boylam]
===================================================================== */
const RAW=[
[1,"Adana","A",2270298,13844,37.00,35.32],[2,"Adıyaman","G",604978,7337,37.76,38.28],[3,"Afyonkarahisar","E",751344,14016,38.76,30.54],
[4,"Ağrı","D",511238,11099,39.72,43.05],[5,"Amasya","K",339529,5701,40.65,35.83],[6,"Ankara","I",5803482,25632,39.93,32.86],
[7,"Antalya","A",2696249,20723,36.90,30.70],[8,"Artvin","K",172356,7436,41.18,41.82],[9,"Aydın","E",1161702,8007,37.85,27.85],
[10,"Balıkesir","M",1273519,14583,39.65,27.89],[11,"Bilecik","M",228058,4179,40.14,29.98],[12,"Bingöl","D",285655,8125,38.88,40.50],
[13,"Bitlis","D",359747,8294,38.40,42.11],[14,"Bolu","K",324789,8313,40.73,31.61],[15,"Burdur","A",277452,7175,37.72,30.29],
[16,"Bursa","M",3214571,10813,40.19,29.06],[17,"Çanakkale","M",570499,9887,40.15,26.41],[18,"Çankırı","I",205501,7542,40.60,33.62],
[19,"Çorum","K",528351,12820,40.55,34.95],[20,"Denizli","E",1059082,11868,37.78,29.09],[21,"Diyarbakır","G",1818133,15168,37.91,40.24],
[22,"Edirne","M",419913,6145,41.68,26.56],[23,"Elazığ","D",604411,9383,38.67,39.22],[24,"Erzincan","D",243399,11815,39.75,39.49],
[25,"Erzurum","D",749993,25066,39.90,41.27],[26,"Eskişehir","I",915418,13960,39.78,30.52],[27,"Gaziantep","G",2164134,6803,37.07,37.38],
[28,"Giresun","K",461712,6934,40.91,38.39],[29,"Gümüşhane","K",148539,6668,40.46,39.48],[30,"Hakkâri","D",287625,7121,37.58,43.74],
[31,"Hatay","A",1544640,5524,36.40,36.35],[32,"Isparta","A",449777,8733,37.76,30.55],[33,"Mersin","A",1938389,15737,36.80,34.64],
[34,"İstanbul","M",15655924,5343,41.01,28.98],[35,"İzmir","E",4479525,12016,38.42,27.14],[36,"Kars","D",278335,10193,40.60,43.10],
[37,"Kastamonu","K",388990,13108,41.38,33.78],[38,"Kayseri","I",1445683,16970,38.73,35.49],[39,"Kırklareli","M",377156,6550,41.73,27.22],
[40,"Kırşehir","I",247179,6584,39.15,34.16],[41,"Kocaeli","M",2102907,3626,40.77,29.94],[42,"Konya","I",2320241,40838,37.87,32.49],
[43,"Kütahya","E",575674,11634,39.42,29.98],[44,"Malatya","D",742725,12313,38.35,38.31],[45,"Manisa","E",1475716,13339,38.61,27.43],
[46,"Kahramanmaraş","A",1116618,14346,37.58,36.94],[47,"Mardin","G",888874,8891,37.31,40.74],[48,"Muğla","E",1066736,12654,37.22,28.36],
[49,"Muş","D",399879,8196,38.75,41.49],[50,"Nevşehir","I",315994,5467,38.62,34.71],[51,"Niğde","I",377080,7234,37.97,34.68],
[52,"Ordu","K",775800,5861,40.98,37.88],[53,"Rize","K",350506,3922,41.02,40.52],[54,"Sakarya","M",1098115,4817,40.76,30.40],
[55,"Samsun","K",1377546,9725,41.29,36.33],[56,"Siirt","G",347412,5717,37.93,41.94],[57,"Sinop","K",229716,5862,42.03,35.15],
[58,"Sivas","I",650401,28488,39.75,37.02],[59,"Tekirdağ","M",1167059,6190,40.98,27.51],[60,"Tokat","K",606934,10042,40.31,36.55],
[61,"Trabzon","K",824352,4628,41.00,39.72],[62,"Tunceli","D",89317,7582,39.11,39.55],[63,"Şanlıurfa","G",2213964,19242,37.16,38.79],
[64,"Uşak","E",377001,5341,38.68,29.41],[65,"Van","D",1127612,20921,38.49,43.38],[66,"Yozgat","I",420699,13690,39.82,34.81],
[67,"Zonguldak","K",591492,3342,41.45,31.80],[68,"Aksaray","I",438504,7659,38.37,34.03],[69,"Bayburt","K",86047,3746,40.26,40.23],
[70,"Karaman","I",263960,8678,37.18,33.23],[71,"Kırıkkale","I",285744,4791,39.85,33.51],[72,"Batman","G",647205,4694,37.88,41.13],
[73,"Şırnak","G",570745,7078,37.52,42.46],[74,"Bartın","K",207238,2330,41.64,32.34],[75,"Ardahan","D",92819,4934,41.11,42.70],
[76,"Iğdır","D",209738,3664,39.92,44.05],[77,"Yalova","M",304780,798,40.65,29.27],[78,"Karabük","K",255242,4145,41.20,32.62],
[79,"Kilis","G",155179,1412,36.72,37.12],[80,"Osmaniye","A",557666,3320,37.07,36.25],[81,"Düzce","K",409865,2492,40.84,31.16]
];
const REG={
M:{n:"Marmara",c:"#3b82f6"},E:{n:"Ege",c:"#10b981"},A:{n:"Akdeniz",c:"#f59e0b"},I:{n:"İç Anadolu",c:"#a855f7"},
K:{n:"Karadeniz",c:"#14b8a6"},D:{n:"Doğu Anadolu",c:"#ef4444"},G:{n:"Güneydoğu Anadolu",c:"#ec4899"}
};
const RAMPS={
blues:['#eff6ff','#bfdbfe','#60a5fa','#2563eb','#1e3a8a'],
orange:['#ffffcc','#fed976','#fd8d3c','#e31a1c','#800026'],
green:['#f0fdf4','#bbf7d0','#4ade80','#16a34a','#14532d'],
purple:['#faf5ff','#e9d5ff','#c084fc','#9333ea','#581c87'],
viridis:['#440154','#3b528b','#21918c','#5ec962','#fde725']
};
const GEO_SOURCES=[
'https://cdn.jsdelivr.net/gh/alpers/Turkey-Maps-GeoJSON@master/tr-cities.json',
'https://raw.githubusercontent.com/alpers/Turkey-Maps-GeoJSON/master/tr-cities.json',
'https://cdn.statically.io/gh/alpers/Turkey-Maps-GeoJSON/master/tr-cities.json'
];
const BASES={
light:{n:'Açık',u:'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',a:'© OpenStreetMap, © CARTO',s:'abcd'},
dark:{n:'Koyu',u:'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',a:'© OpenStreetMap, © CARTO',s:'abcd'},
osm:{n:'OSM',u:'https://tile.openstreetmap.org/{z}/{x}/{y}.png',a:'© OpenStreetMap',s:'abc'},
sat:{n:'Uydu',u:'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',a:'Tiles © Esri',s:'',mz:17},
topo:{n:'Topoğrafya',u:'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',a:'© OpenStreetMap, SRTM · © OpenTopoMap',s:'abc',mz:17},
none:{n:'Altlık yok',u:null}
};
const ICONS={
menu:'',
layers:'',
search:'',
filter:'',
chart:'',
table:'',
ruler:'',
polygon:'',
radius:'',
locate:'',
home:'',
full:'',
moon:'',
sun:'',
download:'',
x:'',
scale:'',
pin:''
};
const ic=(n,s=18)=>``;
/* =====================================================================
NESNELER & YARDIMCILAR
===================================================================== */
const $=s=>document.querySelector(s), $=s=>[...document.querySelectorAll(s)];
const P=RAW.map(r=>({plate:r[0],name:r[1],reg:r[2],pop:r[3],area:r[4],lat:r[5],lng:r[6],cx:r[6],cy:r[5],
layer:null,rings:null,bbox:null,samples:[[r[5],r[6]]],cbsArea:null,nb:[]}));
const byPlate=Object.fromEntries(P.map(p=>[p.plate,p]));
P.forEach(p=>p.density=p.pop/p.area);
['pop','area','density'].forEach(k=>[...P].sort((a,b)=>b[k]-a[k]).forEach((p,i)=>p['rk_'+k]=i+1));
const TOTAL_POP=P.reduce((s,p)=>s+p.pop,0);
const norm=s=>s.toLocaleLowerCase('tr').replace(/ı/g,'i').replace(/ğ/g,'g').replace(/ş/g,'s').replace(/ç/g,'c').replace(/ö/g,'o').replace(/ü/g,'u').replace(/â/g,'a').replace(/î/g,'i').replace(/[\u0300-\u036f]/g,'');
const nf=(n,d=0)=>n.toLocaleString('tr-TR',{maximumFractionDigits:d,minimumFractionDigits:d});
const pad=n=>String(n).padStart(2,'0');
const UNIT={pop:'kişi',area:'km²',density:'kişi/km²'};
const TITLE={pop:'Nüfus (kişi)',area:'Yüzölçümü (km²)',density:'Nüfus yoğunluğu (kişi/km²)',region:'Coğrafi bölge'};
const fmtVal=(m,p)=>m==='region'?REG[p.reg].n:m==='pop'?nf(p.pop)+' kişi':m==='area'?nf(p.area)+' km²':nf(p.density,1)+' kişi/km²';
const compact=(m,v)=>m==='pop'?(v>=1e6?nf(v/1e6,2)+' mn':nf(v/1e3,0)+' bin'):nf(v,m==='density'?0:0);
const mv=(m,p)=>m==='region'?p.reg:p[m];
const hav=(a,b)=>{const R=6371,t=x=>x*Math.PI/180,dLa=t(b.lat-a.lat),dLo=t(b.lng-a.lng);
const h=Math.sin(dLa/2)**2+Math.cos(t(a.lat))*Math.cos(t(b.lat))*Math.sin(dLo/2)**2;return 2*R*Math.asin(Math.sqrt(h))};
let toastT;
function toast(msg,ms=2800){const t=$('#toast');t.textContent=msg;t.classList.remove('hidden');clearTimeout(toastT);toastT=setTimeout(()=>t.classList.add('hidden'),ms)}
function hexToRgb(h){const n=parseInt(h.slice(1),16);return[n>>16,(n>>8)&255,n&255]}
function rampColor(name,t){
const s=RAMPS[name],x=Math.max(0,Math.min(1,t))*(s.length-1),i=Math.min(s.length-2,Math.floor(x)),f=x-i;
const a=hexToRgb(s[i]),b=hexToRgb(s[i+1]);
return '#'+[0,1,2].map(k=>Math.round(a[k]+(b[k]-a[k])*f).toString(16).padStart(2,'0')).join('');
}
function download(name,text,type){const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([text],{type}));a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),2000)}
/* ---------- Jeodezik hesaplar ---------- */
function ringAreaM2(r){const R=6378137,d=Math.PI/180;let s=0;for(let i=0;i<r.length-1;i++){const a=r[i],b=r[i+1];s+=(b[0]-a[0])*d*(2+Math.sin(a[1]*d)+Math.sin(b[1]*d))}return Math.abs(s*R*R/2)}
function geomAreaKm2(g){const polys=g.type==='Polygon'?[g.coordinates]:g.coordinates;let a=0;polys.forEach(pl=>pl.forEach((r,i)=>{a+=(i===0?1:-1)*ringAreaM2(r)}));return a/1e6}
function pipRing(x,y,ring){let c=false;for(let i=0,j=ring.length-1;iy)!==(yj>y)&&x<(xj-xi)*(y-yi)/(yj-yi)+xi)c=!c}return c}
function provinceAt(lat,lng){for(const p of P){if(!p.bbox)continue;const b=p.bbox;if(lngb[2]||latb[3])continue;for(const r of p.rings)if(pipRing(lng,lat,r))return p}return null}
/* ---------- Sınıflandırma ---------- */
function jenks(data,k){
data=[...data].sort((a,b)=>a-b);const n=data.length,m1=[],m2=[];
for(let i=0;i<=n;i++){m1.push(new Array(k+1).fill(0));m2.push(new Array(k+1).fill(Infinity))}
for(let i=1;i<=k;i++){m1[1][i]=1;m2[1][i]=0}
let v=0;
for(let l=2;l<=n;l++){
let s1=0,s2=0,w=0;
for(let m=1;m<=l;m++){
const i3=l-m+1,val=data[i3-1];s2+=val*val;s1+=val;w++;v=s2-(s1*s1)/w;const i4=i3-1;
if(i4!==0)for(let j=2;j<=k;j++)if(m2[l][j]>=v+m2[i4][j-1]){m1[l][j]=i3;m2[l][j]=v+m2[i4][j-1]}
}
m1[l][1]=1;m2[l][1]=v;
}
const kc=new Array(k+1);kc[k]=data[n-1];kc[0]=data[0];
let kk=n,c=k;while(c>1){kc[c-1]=data[m1[kk][c]-2];kk=m1[kk][c]-1;c--}
return kc;
}
/* =====================================================================
DURUM
===================================================================== */
const state={metric:'pop',method:'quantile',k:6,ramp:'blues',opacity:.85,borders:true,symbols:false,labels:false,labelMode:'name',
regions:new Set(Object.keys(REG)),base:'auto',dark:false,selected:null,hl:null,hlSrc:null,focus:null,hover:null,
sortKey:'name',sortDir:1,tblFilter:'',cmp:[],geo:false};
try{const s=localStorage.getItem('geohub-theme');state.dark=s?s==='dark':matchMedia('(prefers-color-scheme: dark)').matches}catch(e){}
let CI=null; // sınıf bilgisi
let nbSet=new Set();
function computeClasses(){
if(state.metric==='region'){CI=null;return}
const m=state.metric,vals=P.map(p=>p[m]),sorted=[...vals].sort((a,b)=>a-b),k=state.k,min=sorted[0],max=sorted[sorted.length-1];
let b=[];
if(state.method==='equal'){for(let i=0;i<=k;i++)b.push(min+(max-min)*i/k)}
else if(state.method==='log'){const lo=Math.log(min),hi=Math.log(max);for(let i=0;i<=k;i++)b.push(Math.exp(lo+(hi-lo)*i/k))}
else if(state.method==='jenks'){b=jenks(vals,k)}
else{for(let i=0;i<=k;i++)b.push(sorted[Math.round((sorted.length-1)*i/k)])}
b[0]=min;b[k]=max;
const colors=Array.from({length:k},(_,i)=>rampColor(state.ramp,k===1?0:i/(k-1)));
CI={b,colors,counts:new Array(k).fill(0)};
P.forEach(p=>CI.counts[classOf(p[m])]++);
}
function classOf(v){const k=CI.b.length-1;let i=0;for(let j=1;j=CI.b[j])i=j;return i}
const fillOf=p=>state.metric==='region'?REG[p.reg].c:CI.colors[classOf(p[state.metric])];
/* =====================================================================
HARİTA
===================================================================== */
const TR_BOUNDS=[[35.7,25.6],[42.3,44.9]];
const map=L.map('map',{zoomControl:false,minZoom:4.5,maxZoom:17,zoomSnap:.25,zoomDelta:.5,wheelPxPerZoomLevel:90,
maxBounds:[[29,17],[47,54]],maxBoundsViscosity:.6,preferCanvas:true,renderer:L.canvas({padding:.4})}).fitBounds(TR_BOUNDS);
L.control.zoom({position:'bottomright'}).addTo(map);
L.control.scale({position:'bottomright',imperial:false}).addTo(map);
const provLG=L.layerGroup().addTo(map), symLG=L.layerGroup().addTo(map), labLG=L.layerGroup().addTo(map), toolLG=L.layerGroup().addTo(map);
/* ---------- Altlık ---------- */
let baseLayer=null,tileOK=0,tileErr=0,tileWarned=false;
function setBase(){
const key=state.base==='auto'?(state.dark?'dark':'light'):state.base,b=BASES[key];
if(baseLayer){map.removeLayer(baseLayer);baseLayer=null}
tileOK=0;tileErr=0;tileWarned=false;
if(b.u){
baseLayer=L.tileLayer(b.u,{attribution:b.a,subdomains:b.s||'abc',maxZoom:b.mz||18,maxNativeZoom:b.mz||18});
baseLayer.on('tileload',()=>{tileOK++});
baseLayer.on('tileerror',()=>{tileErr++;if(tileErr>=6&&tileOK===0&&!tileWarned){tileWarned=true;toast('Altlık harita sunucusuna ulaşılamadı. İl sınırları çalışmaya devam eder; farklı bir altlık deneyebilirsiniz.',5000)}});
baseLayer.addTo(map).bringToBack();
}
$('#baseBtns .base').forEach(el=>el.classList.toggle('on',el.dataset.b===state.base));
restyleAll();
}
/* ---------- Stil ---------- */
const isDark=()=>state.baseKey==='dark'||(state.base==='auto'&&state.dark);
function isDim(p){
if(!state.regions.has(p.reg))return true;
if(state.hl&&!state.hl.has(p.plate))return true;
const f=state.focus;
if(f){if(f.t==='c'&&classOf(p[state.metric])!==f.v)return true;if(f.t==='r'&&p.reg!==f.v)return true}
return false;
}
function styleOf(p){
const dim=isDim(p),sel=state.selected===p.plate,nb=nbSet.has(p.plate);
const dk=(state.base==='auto'?state.dark:state.base==='dark'||state.base==='sat');
const st={fillColor:fillOf(p),fillOpacity:dim?.10:state.opacity,
color:state.borders?(dk?'#cbd5e1':'#ffffff'):'transparent',weight:state.borders?(dim?.4:.9):0,opacity:dim?.5:.95,dashArray:null};
if(state.hl&&!dim){st.color='#e11d48';st.weight=2;st.opacity=1}
if(nb&&!dim){st.color='#0ea5e9';st.weight=2.2;st.dashArray='5 3';st.opacity=1}
if(sel){st.color=dk?'#fde047':'#0f172a';st.weight=3.2;st.opacity=1;st.dashArray=null;st.fillOpacity=Math.max(.9,state.opacity)}
return st;
}
function restyleAll(){
state.baseKey=state.base==='auto'?(state.dark?'dark':'light'):state.base;
P.forEach(p=>{if(p.layer)p.layer.setStyle(styleOf(p))});
if(state.selected&&byPlate[state.selected].layer)byPlate[state.selected].layer.bringToFront();
}
/* ---------- İl katmanları ---------- */
function tipHTML(p){
const m=state.metric;
return `${p.name} · ${pad(p.plate)}
${REG[p.reg].n} Bölgesi
${fmtVal(m==='region'?'pop':m,p)}${m!=='region'?` · #${p['rk_'+m]}`:''}`;
}
function attach(p,layer){
p.layer=layer;
layer.setStyle(styleOf(p));
layer.bindTooltip(()=>tipHTML(p),{sticky:true,className:'ptip',direction:'top',opacity:1});
layer.on('mouseover',()=>{if(state.selected!==p.plate){layer.setStyle({weight:2.6,color:isDark()?'#fff':'#0f172a',opacity:1,dashArray:null});layer.bringToFront()}if(state.selected)byPlate[state.selected].layer.bringToFront()});
layer.on('mouseout',()=>{layer.setStyle(styleOf(p));if(state.selected)byPlate[state.selected].layer.bringToFront()});
layer.on('click',()=>{if(!tool)select(p.plate)});
layer.addTo(provLG);
}
function processGeom(f,p){
const g=f.geometry,polys=g.type==='Polygon'?[g.coordinates]:g.coordinates;
p.rings=polys.map(pl=>pl[0]);
p.cbsArea=geomAreaKm2(g);
let x0=1e9,y0=1e9,x1=-1e9,y1=-1e9,best=null,bestA=-1;
p.rings.forEach(r=>{
let a=0,cx=0,cy=0;
for(let i=0;i<r.length;i++){
const A=r[i],B=r[(i+1)%r.length],cr=A[0]*B[1]-B[0]*A[1];
a+=cr;cx+=(A[0]+B[0])*cr;cy+=(A[1]+B[1])*cr;
if(A[0]x1)x1=A[0];if(A[1]y1)y1=A[1];
}
a/=2;
if(Math.abs(a)>bestA){bestA=Math.abs(a);best=a?[cx/(6*a),cy/(6*a)]:[r[0][0],r[0][1]]}
});
p.cx=best[0];p.cy=best[1];p.bbox=[x0,y0,x1,y1];
const tot=p.rings.reduce((s,r)=>s+r.length,0),step=Math.max(1,Math.floor(tot/700));
p.samples=[];let k=0;p.rings.forEach(r=>r.forEach(pt=>{if(k++%step===0)p.samples.push([pt[1],pt[0]])}));
}
function buildFromGeo(data){
provLG.clearLayers();P.forEach(p=>{p.layer=null});
let ok=0;
data.features.forEach(f=>{
let p=byPlate[+f.properties.number];
if(!p){const nn=norm(String(f.properties.name||''));p=P.find(x=>norm(x.name)===nn||norm(x.name).startsWith(nn))}
if(!p||!f.geometry)return;
processGeom(f,p);
const gl=L.geoJSON(f.geometry,{smoothFactor:1.4}),layer=gl.getLayers()[0];
if(layer){attach(p,layer);ok++}
});
return ok;
}
function buildFallback(){
provLG.clearLayers();
[...P].sort((a,b)=>b.area-a.area).forEach(p=>{p.rings=null;p.bbox=null;p.cbsArea=null;p.cx=p.lng;p.cy=p.lat;p.samples=[[p.lat,p.lng]];
attach(p,L.circle([p.lat,p.lng],{radius:Math.sqrt(p.area/Math.PI)*1000,weight:1}))});
}
/* ---------- Komşuluk (paylaşılan sınır) ---------- */
function computeNeighbors(){
const cells=new Map();
P.forEach(p=>{
if(!p.rings)return;
const s=new Set();
p.rings.forEach(r=>{for(let i=0;i<r.length;i++)s.add(Math.floor(r[i][0]*25)*4096+Math.floor(r[i][1]*25))});
s.forEach(c=>{let a=cells.get(c);if(!a){a=[];cells.set(c,a)}a.push(p.plate)});
});
const pair=new Map();
cells.forEach(a=>{if(a.length<2)return;for(let i=0;i<a.length;i++)for(let j=i+1;j<a.length;j++){const lo=Math.min(a[i],a[j]),hi=Math.max(a[i],a[j]),k=lo*100+hi;pair.set(k,(pair.get(k)||0)+1)}});
P.forEach(p=>p.nb=[]);
pair.forEach((c,k)=>{if(c>=3){const a=Math.floor(k/100),b=k%100;byPlate[a].nb.push(b);byPlate[b].nb.push(a)}});
P.forEach(p=>p.nb.sort((a,b)=>byPlate[a].name.localeCompare(byPlate[b].name,'tr')));
if(state.selected){nbSet=new Set(byPlate[state.selected].nb);restyleAll();renderCard()}
}
/* ---------- Semboller & etiketler ---------- */
function renderSymbols(){
symLG.clearLayers();if(!state.symbols||!state.geo&&false)return;
const m=state.metric==='region'?'pop':state.metric,max=Math.max(...P.map(p=>p[m]));
[...P].sort((a,b)=>b[m]-a[m]).forEach(p=>{
if(!state.regions.has(p.reg))return;
L.circleMarker([p.cy,p.cx],{radius:3+22*Math.sqrt(p[m]/max),color:'#0f172a',weight:1.2,fillColor:'#ffffff',fillOpacity:.55,interactive:false}).addTo(symLG);
});
}
function renderLabels(){
labLG.clearLayers();
$('#map').style.setProperty('--z',map.getZoom());
if(!state.labels||map.getZoom()<5.25)return;
const m=state.metric==='region'?'pop':state.metric;
P.forEach(p=>{
if(!state.regions.has(p.reg))return;
const t=state.labelMode==='plate'?pad(p.plate):state.labelMode==='value'?compact(m,p[m]):p.name;
L.marker([p.cy,p.cx],{interactive:false,keyboard:false,icon:L.divIcon({className:'plab',html:`${t}`,iconSize:[0,0]})}).addTo(labLG);
});
}
/* =====================================================================
LEJANT
===================================================================== */
function renderLegend(){
const lg=$('#legend'),m=state.metric;
let h=`${TITLE[m].toUpperCase()}`;
let body='';
if(m==='region'){
body=Object.entries(REG).map(([k,r])=>{const n=P.filter(p=>p.reg===k).length;
return `${r.n}${n}`}).join('');
}else{
body=CI.colors.map((c,i)=>`${compact(m,CI.b[i])} – ${compact(m,CI.b[i+1])}${CI.counts[i]}`).join('');
body+=`Sağdaki sayı: sınıftaki il adedi`;
}
lg.innerHTML=h+`${body}`;
$('#legend .lg').forEach(el=>{
const f={t:el.dataset.t,v:el.dataset.t==='c'?+el.dataset.v:el.dataset.v};
el.onmouseenter=()=>{state.focus=f;restyleAll()};
el.onmouseleave=()=>{state.focus=null;restyleAll()};
});
const tg=$('#lgToggle');if(tg)tg.onclick=()=>{lg.dataset.c=lg.dataset.c==='1'?'0':'1';renderLegend()};
}
/* =====================================================================
SEÇİM & İL KARTI
===================================================================== */
function fitBoundsPad(b,maxZoom=9){
const wide=innerWidth>=768;
map.flyToBounds(b,{paddingTopLeft:[wide?70:30,40],paddingBottomRight:[wide?(state.selected?370:40):30,wide?40:(state.selected?260:40)],maxZoom,duration:.8});
}
function select(plate,fly=true){
const p=byPlate[plate];if(!p)return;
state.selected=plate;nbSet=new Set(p.nb);
history.replaceState(null,'','#il='+plate);
restyleAll();renderCard();renderTable();
if(fly)fitBoundsPad(p.layer.getBounds());
if(innerWidth<768)closeSidebar();
}
function deselect(){
state.selected=null;nbSet=new Set();history.replaceState(null,'',location.pathname+location.search);
$('#card').classList.add('hidden');$('#mapWrap').classList.remove('card-open');restyleAll();renderTable();
}
function renderCard(){
const p=byPlate[state.selected],card=$('#card');if(!p)return;
const R=REG[p.reg],share=p.pop/TOTAL_POP*100;
let nbs=p.nb.map(x=>byPlate[x]);
let nbTitle='KOMŞU İLLER';
if(!nbs.length){nbs=P.filter(x=>x!==p).map(x=>({x,d:hav({lat:p.cy,lng:p.cx},{lat:x.cy,lng:x.cx})})).sort((a,b)=>a.d-b.d).slice(0,4).map(o=>o.x);nbTitle=state.geo?'KOMŞU İLLER HESAPLANIYOR — EN YAKIN İLLER':'EN YAKIN İLLER'}
const inCmp=state.cmp.includes(p.plate);
card.classList.remove('hidden');$('#mapWrap').classList.add('card-open');
card.innerHTML=`
PLAKA ${pad(p.plate)} · ${R.n.toUpperCase()}
${p.name}
</div>
</div>
NÜFUS${nf(p.pop)}${p.rk_pop}. sıra
YÜZÖLÇÜMÜ${nf(p.area)} km²${p.rk_area}. sıra
YOĞUNLUK${nf(p.density,1)}${p.rk_density}. sıra
</div>
Türkiye nüfusundaki payı%${nf(share,2)}
</div>
${p.cbsArea?`CBS poligon alanı (hesaplanan)${nf(p.cbsArea)} km²`:''}
Ağırlık merkezi${p.cy.toFixed(4)}°K, ${p.cx.toFixed(4)}°D
</div>
${nbTitle}${p.nb.length?` (${p.nb.length})`:''}
${nbs.map(n=>``).join('')}
</div>
Google Maps ↗
</div>
</div>`;
$('#cardClose').onclick=deselect;
$('#cardZoom').onclick=()=>fitBoundsPad(p.layer.getBounds());
$('#cardCopy').onclick=()=>{navigator.clipboard?.writeText(`${p.cy.toFixed(5)}, ${p.cx.toFixed(5)}`);toast('Koordinatlar kopyalandı')};
$('#cardCmp').onclick=()=>addCompare(p.plate);
card.querySelectorAll('[data-go]').forEach(b=>b.onclick=()=>select(+b.dataset.go));
}
/* =====================================================================
SEKME: KATMAN
===================================================================== */
function renderChips(){
$('#regionChips').innerHTML=Object.entries(REG).map(([k,r])=>`${r.n}`).join('');
$('#regionChips .chip').forEach(c=>c.onclick=()=>{const k=c.dataset.r;state.regions.has(k)?state.regions.delete(k):state.regions.add(k);refresh()});
$('#allRegions').textContent=state.regions.size===7?'Hiçbirini seçme':'Tümünü seç';
}
function renderRamps(){
$('#rampBtns').innerHTML=Object.keys(RAMPS).map(k=>``).join('');
$('#rampBtns .ramp').forEach(b=>b.onclick=()=>{state.ramp=b.dataset.r;refresh()});
}
function renderBaseBtns(){
$('#baseBtns').innerHTML=[['auto','Otomatik'],...Object.entries(BASES).map(([k,b])=>[k,b.n])].map(([k,n])=>``).join('');
$('#baseBtns .base').forEach(b=>b.onclick=()=>{state.base=b.dataset.b;setBase()});
}
function renderDataInfo(){
const n=P.filter(p=>p.rings).length;
$('#dataInfo').innerHTML=state.geo
?`Veri durumu: ${n}/81 il sınırı yüklendi (poligon). Nüfus verisi TÜİK ADNKS 2023; yüzölçümü HGM değerleridir ve kaynağa göre küçük farklar gösterebilir. "CBS poligon alanı", sınır verisinden jeodezik olarak hesaplanır.`
:`Yaklaşık görünüm: Sınır verisi indirilemedi. İller yüzölçümüyle orantılı dairelerle gösteriliyor. Nüfus ve yüzölçümü verileri yine doğrudur.`;
}
$('#segMetric button').forEach(b=>b.onclick=()=>{state.metric=b.dataset.m;state.focus=null;refresh()});
$('#method').onchange=e=>{state.method=e.target.value;refresh()};
$('#nClass').oninput=e=>{state.k=+e.target.value;$('#nClassV').textContent=state.k;refresh()};
$('#opacity').oninput=e=>{state.opacity=e.target.value/100;$('#opacityV').textContent=e.target.value+'%';restyleAll()};
$('#optBorders').onchange=e=>{state.borders=e.target.checked;restyleAll()};
$('#optSymbols').onchange=e=>{state.symbols=e.target.checked;renderSymbols()};
$('#optLabels').onchange=e=>{state.labels=e.target.checked;renderLabels()};
$('#labelMode').onchange=e=>{state.labelMode=e.target.value;if(!state.labels){state.labels=true;$('#optLabels').checked=true}renderLabels()};
$('#allRegions').onclick=()=>{state.regions.size===7?state.regions.clear():Object.keys(REG).forEach(k=>state.regions.add(k));refresh()};
/* =====================================================================
SEKME: SORGU
===================================================================== */
const QF={
pop:{l:'Nüfus',t:'num'},area:{l:'Yüzölçümü (km²)',t:'num'},density:{l:'Yoğunluk (kişi/km²)',t:'num'},
plate:{l:'Plaka kodu',t:'num'},reg:{l:'Coğrafi bölge',t:'reg'},name:{l:'İl adı',t:'txt'}
};
const QOPS={num:[['>','büyüktür'],['>=','büyük eşit'],['<','küçüktür'],['<=','küçük eşit'],['=','eşittir'],['between','arasında']],
reg:[['=','eşittir'],['!=','eşit değil']],txt:[['contains','içerir'],['starts','ile başlar']]};
const PRESETS=[
['1 milyondan fazla nüfus',{f:'pop',op:'>',v:[1000000]}],
['Yoğunluk > 200 kişi/km²',{f:'density',op:'>',v:[200]}],
['Seyrek nüfuslu (< 30)',{f:'density',op:'<',v:[30]}],
['Yüzölçümü > 15.000 km²',{f:'area',op:'>',v:[15000]}],
['Küçük iller (< 4.000 km²)',{f:'area',op:'<',v:[4000]}],
['Nüfus < 250 bin',{f:'pop',op:'<',v:[250000]}]
];
function qBuild(){
const f=$('#qField').value,t=QF[f].t;
$('#qOp').innerHTML=QOPS[t].map(o=>`${o[1]}`).join('');
qVals();
}
function qVals(){
const f=$('#qField').value,t=QF[f].t,op=$('#qOp').value,box=$('#qVals');
if(t==='reg')box.innerHTML=``;
else if(t
添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论