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 |
评论
?
参与讨论