feat(tasarim): şehir sayfasına hero dilinde piksel harita; üniversite konum kartı da aynı harita
All checks were successful
Deploy / deploy (push) Successful in 4m39s
All checks were successful
Deploy / deploy (push) Successful in 4m39s
- 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>
This commit is contained in:
170
scripts/il-izgara-uret.ts
Normal file
170
scripts/il-izgara-uret.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
// Ş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`);
|
||||
Reference in New Issue
Block a user