All checks were successful
Deploy / deploy (push) Successful in 4m34s
- Izgara 72 → 56 sütun, serpinti kaldırıldı (Bilal: "piksel detaylarını azalt"). - Harita `doldur` (slice) ile SehirYuzeyi kartını kaplar, metin üstünde; iki katlı mask ile hiçbir kenarda kesik kare kalmaz (Bilal: "canvas net bir şekilde kesiliyor"). Telefonda altta, lg'de sağ yarıda belirir. - Yakınlaşma kutusu kabın oranında kurulur, il `odakHiza` ile kabın istenen noktasına gelir (şehir sayfası: lg sağ orta, telefon alt orta; konum kartı orta). - usePixelHeat meet/slice + ortalama ofsetini hesaba katar. - Üniversite konum kartı: kartı doldurur, radyal erime. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
171 lines
6.7 KiB
TypeScript
171 lines
6.7 KiB
TypeScript
// Şehir haritası (DESIGN.md §4.17) için il ve ülke kontürlerini hero'nun
|
||
// dot-matrix ızgarasına rasterize eder. Hero'daki TurkeyPixelMap 64 sütun;
|
||
// burada 56 sütun (Bilal, 24 Eyl 2026: "piksel detaylarını azalt"); her il en az birkaç kare alsın (Yalova, Kilis gibi
|
||
// küçük iller için 2×2 blok garantisi vardır). Çıktı
|
||
// src/lib/il-izgara-verisi.ts dosyasına yazılır ve commit'lenir.
|
||
//
|
||
// Kaynak: turkey-map-react il path'leri (viewBox 1050×585, üstten 138 kırpık)
|
||
// ve harita-ulkeler-verisi.ts ülke kontürleri (1050×447). Hücre merkezi
|
||
// poligonun içindeyse hücre o ile aittir (ray casting; Bézier eğrileri uç
|
||
// noktalarıyla düzleştirilir — hücre ölçeğinde fark etmez).
|
||
//
|
||
// Çalıştırma: pnpm tsx scripts/il-izgara-uret.ts
|
||
|
||
import fs from "node:fs";
|
||
import path from "node:path";
|
||
import { cities } from "turkey-map-react/lib/data";
|
||
import { normalizeIlAdi, HARITA_UST_KIRPMA, HARITA_GENISLIK, HARITA_YUKSEKLIK } from "../src/lib/harita";
|
||
import { ULKE_HARITALARI } from "../src/lib/harita-ulkeler-verisi";
|
||
|
||
const SUTUN = 56;
|
||
const HUCRE = HARITA_GENISLIK / SUTUN;
|
||
const YUKSEKLIK = HARITA_YUKSEKLIK - HARITA_UST_KIRPMA; // 447
|
||
const SATIR = Math.ceil(YUKSEKLIK / HUCRE);
|
||
|
||
type Nokta = [number, number];
|
||
type Hucre = [number, number];
|
||
|
||
function poligonlar(d: string): Nokta[][] {
|
||
const toks = d.match(/[MLHVCZmlhvcz]|-?\d*\.?\d+(?:e-?\d+)?/g) ?? [];
|
||
const polys: Nokta[][] = [];
|
||
let cur: Nokta[] = [];
|
||
let x = 0;
|
||
let y = 0;
|
||
let cmd = "";
|
||
let i = 0;
|
||
const num = () => Number(toks[i++]);
|
||
const kapat = () => {
|
||
if (cur.length > 2) polys.push(cur);
|
||
cur = [];
|
||
};
|
||
while (i < toks.length) {
|
||
const t = toks[i];
|
||
if (/[A-Za-z]/.test(t)) {
|
||
cmd = t;
|
||
i++;
|
||
if (cmd === "Z" || cmd === "z") kapat();
|
||
continue;
|
||
}
|
||
switch (cmd) {
|
||
case "M": x = num(); y = num(); kapat(); cur = [[x, y]]; cmd = "L"; break;
|
||
case "m": x += num(); y += num(); kapat(); cur = [[x, y]]; cmd = "l"; break;
|
||
case "L": x = num(); y = num(); cur.push([x, y]); break;
|
||
case "l": x += num(); y += num(); cur.push([x, y]); break;
|
||
case "H": x = num(); cur.push([x, y]); break;
|
||
case "h": x += num(); cur.push([x, y]); break;
|
||
case "V": y = num(); cur.push([x, y]); break;
|
||
case "v": y += num(); cur.push([x, y]); break;
|
||
case "C": num(); num(); num(); num(); x = num(); y = num(); cur.push([x, y]); break;
|
||
case "c": num(); num(); num(); num(); x += num(); y += num(); cur.push([x, y]); break;
|
||
default: i++;
|
||
}
|
||
}
|
||
kapat();
|
||
return polys;
|
||
}
|
||
|
||
function icinde(px: number, py: number, poly: Nokta[]): boolean {
|
||
let ic = false;
|
||
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
||
const [xi, yi] = poly[i];
|
||
const [xj, yj] = poly[j];
|
||
if (yi > py !== yj > py && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) ic = !ic;
|
||
}
|
||
return ic;
|
||
}
|
||
|
||
/** Her hücre merkezini ilk eşleşen bölgeye verir; sıra öncelik demektir. */
|
||
function rasterize(
|
||
bolgeler: { ad: string; polys: Nokta[][] }[],
|
||
ustKirpma: number,
|
||
): Map<string, Hucre[]> {
|
||
const sonuc = new Map<string, Hucre[]>();
|
||
for (let r = 0; r < SATIR; r++) {
|
||
for (let q = 0; q < SUTUN; q++) {
|
||
const px = (q + 0.5) * HUCRE;
|
||
const py = ustKirpma + (r + 0.5) * HUCRE;
|
||
const b = bolgeler.find((b) => b.polys.some((p) => icinde(px, py, p)));
|
||
if (!b) continue;
|
||
const liste = sonuc.get(b.ad) ?? [];
|
||
liste.push([q, r]);
|
||
sonuc.set(b.ad, liste);
|
||
}
|
||
}
|
||
return sonuc;
|
||
}
|
||
|
||
// ---- Türkiye illeri
|
||
const iller = cities.map((c) => ({ ad: normalizeIlAdi(c.name), polys: poligonlar(c.path) }));
|
||
const ilHucre = rasterize(iller, HARITA_UST_KIRPMA);
|
||
// Küçük il garantisi: 4 hücreden azı olan il bbox merkezinde 2×2 blok alır,
|
||
// o hücreler komşulardan düşer.
|
||
for (const il of iller) {
|
||
const mevcut = ilHucre.get(il.ad) ?? [];
|
||
if (mevcut.length >= 4) continue;
|
||
const pts = il.polys.flat();
|
||
const cx = (Math.min(...pts.map((p) => p[0])) + Math.max(...pts.map((p) => p[0]))) / 2;
|
||
const cy = (Math.min(...pts.map((p) => p[1])) + Math.max(...pts.map((p) => p[1]))) / 2;
|
||
const q0 = Math.min(SUTUN - 2, Math.max(0, Math.round(cx / HUCRE) - 1));
|
||
const r0 = Math.min(SATIR - 2, Math.max(0, Math.round((cy - HARITA_UST_KIRPMA) / HUCRE) - 1));
|
||
const blok: Hucre[] = [[q0, r0], [q0 + 1, r0], [q0, r0 + 1], [q0 + 1, r0 + 1]];
|
||
const anahtar = new Set(blok.map(([q, r]) => `${q},${r}`));
|
||
for (const [ad, liste] of ilHucre) {
|
||
if (ad === il.ad) continue;
|
||
ilHucre.set(ad, liste.filter(([q, r]) => !anahtar.has(`${q},${r}`)));
|
||
}
|
||
const birlesik = new Map(mevcut.map((h) => [`${h[0]},${h[1]}`, h]));
|
||
for (const h of blok) birlesik.set(`${h[0]},${h[1]}`, h);
|
||
ilHucre.set(il.ad, [...birlesik.values()]);
|
||
}
|
||
|
||
// ---- Ülkeler (KKTC, yurt dışı): ülke öncelikli, bağlam ikincil
|
||
const ulkeHucre: Record<string, { ulke: Hucre[]; baglam: Hucre[] }> = {};
|
||
for (const [kod, harita] of Object.entries(ULKE_HARITALARI)) {
|
||
const r = rasterize(
|
||
[
|
||
{ ad: "ulke", polys: harita.ulke.flatMap(poligonlar) },
|
||
{ ad: "baglam", polys: harita.baglam.flatMap(poligonlar) },
|
||
],
|
||
0,
|
||
);
|
||
ulkeHucre[kod] = { ulke: r.get("ulke") ?? [], baglam: r.get("baglam") ?? [] };
|
||
}
|
||
|
||
const enKucuk = [...ilHucre.entries()].sort((a, b) => a[1].length - b[1].length).slice(0, 5);
|
||
console.log(
|
||
`${SUTUN}×${SATIR}, hücre ${HUCRE.toFixed(3)}; kara ${[...ilHucre.values()].reduce((a, b) => a + b.length, 0)}; en küçük: ${enKucuk.map(([a, l]) => `${a}:${l.length}`).join(" ")}`,
|
||
);
|
||
|
||
const satirlar = [...ilHucre.entries()]
|
||
.sort((a, b) => a[0].localeCompare(b[0], "tr-TR"))
|
||
.map(([ad, liste]) => ` ${JSON.stringify(ad)}: ${JSON.stringify(liste)},`);
|
||
const ulkeSatirlar = Object.entries(ulkeHucre).map(
|
||
([kod, v]) => ` ${JSON.stringify(kod)}: { ulke: ${JSON.stringify(v.ulke)}, baglam: ${JSON.stringify(v.baglam)} },`,
|
||
);
|
||
const cikti = `// Bu dosya scripts/il-izgara-uret.ts tarafından üretildi — elle düzenleme.
|
||
// Hero'nun dot-matrix ızgarasında (${SUTUN} sütun × ${SATIR} satır, hücre ${HUCRE.toFixed(4)}
|
||
// viewBox birimi) her ilin ve ülkenin kapladığı hücreler [sütun, satır].
|
||
|
||
import type { UlkeKodu } from "./harita-ulkeler-verisi";
|
||
|
||
export const IL_IZGARA = {
|
||
sutun: ${SUTUN},
|
||
satir: ${SATIR},
|
||
hucre: ${HUCRE.toFixed(4)},
|
||
} as const;
|
||
|
||
export type IzgaraHucre = readonly [number, number];
|
||
|
||
/** Anahtar DB biçiminde il adı (normalizeIlAdi). */
|
||
export const IL_HUCRELERI: Record<string, readonly IzgaraHucre[]> = {
|
||
${satirlar.join("\n")}
|
||
};
|
||
|
||
export const ULKE_HUCRELERI: Record<UlkeKodu, { ulke: readonly IzgaraHucre[]; baglam: readonly IzgaraHucre[] }> = {
|
||
${ulkeSatirlar.join("\n")}
|
||
};
|
||
`;
|
||
const hedef = path.join(process.cwd(), "src/lib/il-izgara-verisi.ts");
|
||
fs.writeFileSync(hedef, cikti);
|
||
console.log("yazıldı:", hedef, `${(cikti.length / 1024).toFixed(0)} KB`);
|