Files
kolaytercih/scripts/il-izgara-uret.ts
bilalgursen eefac56659
All checks were successful
Deploy / deploy (push) Successful in 4m39s
feat(tasarim): şehir sayfasına hero dilinde piksel harita; üniversite konum kartı da aynı harita
- scripts/il-izgara-uret.ts → src/lib/il-izgara-verisi.ts: 81 il ve 7 ülke
  kontürü hero'nun dot-matrix ızgarasına (72×31) rasterize; küçük iller 2×2 garanti.
- SehirPikselHaritasi: iri kareler, altı opaklık, kıyı serpintisi, usePixelHeat
  ısı fırçası; bulunulan il iri turuncu karelerle sırayla belirir (kt-piksel-belir)
  ve nabız atar; KKTC/yurt dışında şehir merkezinde 2×2 iri kare.
- Şehir sayfası başlığı beyaz piksel dokulu yüzeyde (SehirYuzeyi), harita sağda/altta.
- UniversiteKonumHaritasi düz SVG yerine aynı piksel haritayı kullanır.
- DESIGN.md §4.12/§4.17/§10.8 güncellendi.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 14:56:11 +03:00

171 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Ş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 72 sütun ki 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 = 72;
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`);