Coğrafi veri ve gizlilik
Konumu saklamadan harita özelliği nasıl yapılır? H3 altıgen ızgara
HuntLine IQ’da bir fotokapan gözlemi veritabanına koordinatla değil, altı H3 hücresiyle yazılıyor. Akış, istatistik ve hava eşleştirmesi bu hücreler üzerinden çalışıyor.


KISA CEVAP
Koordinatı yalnızca yazma anında kullanıp H3 hücresine çevirin, ham noktayı atın. HuntLine IQ her gözlem için h3-js ile res3’ten res8’e altı hücre hesaplıyor ve content.signals tablosunda yalnızca bunları saklıyor; en ince hücre (res8) ortalama 0,74 km². Akış mesafeleri bu hücrenin merkezinden ölçülüyor ve kullanıcıya hiçbir zaman tam mesafe gösterilmiyor; istatistik ise yerel alanlarda, alan boyutuna göre 6 ile 25 arasında değişen katılımcı eşiğinin altında yayınlanmıyor.
Tam koordinat nerede tutuluyor, nerede hiç tutulmuyor?
HuntLine IQ, ABD’deki avcıların fotokapan gözlemlerini ve hasat kayıtlarını paylaştığı bir platform: web/PWA, iOS ve Android aynı Supabase projesini kullanıyor. Avcı bir gözlem paylaşmak istiyor ama kamerasının yerini kimseye vermek istemiyor. Bu yüzden ilk kural veri modelinde: gözlem satırında enlem ve boylam kolonu yok.
Tam nokta yalnızca kullanıcının kendi kaydı olan iki yerde duruyor. Mülkün merkezi app.zones.center_point kolonunda; satır güvenliği (RLS) bunu sadece sahibine okutuyor. Kayıtlı yükleme noktaları app.upload_locations tablosunda; bu tabloda RLS açık ama hiç politika yok, yani istemci erişemiyor, tek okuyucu ve yazıcı Edge Function.
Gözlemin kendisi ise şu şekilde yazılıyor:
- Fotoğrafın EXIF verisi yüklemede siliniyor; silme başarısız olursa yükleme reddediliyor (hard fail). Fotoğrafın içindeki GPS bu yüzden saklanmıyor.
- Uygulamanın gönderdiği pin ya da mülk merkezi yalnızca H3 hücreleri ve ilçe FIPS kodu üretmek için kullanılıyor, sonra atılıyor.
- Kapsam dışındaki eyaletlerden gelen pin istekleri ürün tarafında sayılıyor, ama yalnızca eyalet ve gün olarak; nokta hiç kaydedilmiyor.
Mobil uygulamanın iOS konum izni metni de bunu söylüyor: konum bir kez, üzerinde durduğunuz mülkü yerleştirmek için okunuyor ve arka plan konumu kapalı.
Hangi H3 çözünürlüğü, ve neden altısı birden?
Tek bir çözünürlük seçmek yerine her gözleme res3’ten res8’e altı hücre birden yazılıyor: h3_index_res3 … h3_index_res8. Sebep basit: ürün farklı büyüklükte alanlar için ayrı istatistik üretiyor ve her alan katmanı kendi çözünürlüğünde yaşıyor. Kod tarafında bu eşleme lib/constants/zone-h3.ts içinde, import içermeyen tek bir dosyada; Next.js ve Deno aynı dosyayı paylaşıyor:
- micro → res8 (ortalama 0,74 km²)
- tight → res7 (5,16 km²)
- neighborhood ve local_plus → res6 (36,1 km²)
- local → res5 (252,9 km²)
- county, regional, statewide → H3 değil; ilçe FIPS çokgeni (PostGIS), eyaletin kendi bölge tanımı ve eyalet kodu
En ince seviyenin res8’de durması bilinçli. res8 hücresinin kenarı ortalama 530 metre civarında; bir kamerayı tarif etmeye yetmeyecek kadar kaba, ama aynı kameradan art arda gelen kareleri aynı yere bağlamaya yetecek kadar ince. Daha ince bir çözünürlük (res9 ve üstü) hiç saklanmıyor, bu yüzden sonradan biri istese bile veritabanından daha keskin bir konum çıkarılamıyor.
Bir pratik kısıt daha var: Supabase Cloud’da H3 Postgres eklentisi yok. Bu yüzden hücreler yazma anında Edge Function’da npm:h3-js@4.1.0 ile hesaplanıyor ve denormalize kolon olarak saklanıyor. SQL tarafı hücreyi hiç yeniden hesaplamıyor, sadece metin eşitliğiyle karşılaştırıyor; res5’ten res8’e her çözünürlük için (h3_index_resN, observed_at desc) üzerinde, yalnızca onaylı satırları kapsayan kısmi bir indeks var.
Bir gözlem yazılırken adımların sırası ne?
ingest-signal fonksiyonunun sırası dosyanın başında yazılı: kimlik doğrulama → idempotency → doğrulama → üyelik kapısı → boyut ve MIME → EXIF silme (hard fail) → görüntü analizi (moderasyon, kırpma, geyik olmayanı reddetme; hata durumunda kapalı) → algısal hash → seçili mülk → konum → H3 ve ilçe → sezon ve ağırlık → depolama → tek transaction’da insert → idempotency önbelleği.
Konum adımının kendi kuralları var. Uygulama koordinat gönderirse ikisi birden gelmeli, sayı olmalı ve dünya üzerinde olmalı; aksi halde istek onarılmadan 422 ile reddediliyor, çünkü uygulama yalnızca kendi doğruladığı bir pini gönderebilir. Pin, harita ekranındaki kuralın aynısıyla tekrar kontrol ediliyor: bir ilçeye düşmüyorsa “launched states” dışında sayılıyor ve reddediliyor. Koordinat gelmezse seçili mülkün merkezi kullanılıyor.
Ham noktanın atılmadan önce bir işi daha var. Kayıp geyik (missing buck) ilanında, yayın yarıçapı içindeki diğer mülk sahiplerini bulmak için PostGIS ST_DWithin aynı istek içinde çalışıyor; bildirim gidiyor, nokta yine yazılmıyor.
Bu tasarımın bir bedeli var ve proje notlarında açıkça kayıtlı: hücreler yükleme anında damgalandığı için, bir avcı mülk pinini sonradan taşıdığında eski gözlemleri yeni konuma göre yeniden hesaplanamıyor. Ham koordinat olmadığından geriye dönük düzeltme ancak tek seferlik, bilinçli bir işlemle yapılabilir. Gizlilik için kabul edilmiş bir ödünleşim.
Akış (feed) sorguları hücrelerle nasıl çalışıyor?
Akışta “bölge”, avcının seçili mülkünden ölçülen bir görüş halkası. Halkalar feed.bands ayar satırında duruyor, mil cinsinden ve birbirini dışlıyor: micro 0–2, tight 2–3, neighborhood 3–5, local+ 5–7, local 7–10, county 10–35, regional 35–75, statewide 75+. Her gözlem tam olarak bir halkaya düşüyor.
Mesafe, mülk merkezinden gözlemin res8 hücresinin merkezine haversine ile hesaplanıyor (haversineMiles(anchorPoint, cellCenter(c.h3_index_res8))). Cihaz GPS’i ya da ham koordinat bu hesaba hiç girmiyor.
Asıl önemli kural sunum katmanında: kamuya açık içerik hiçbir zaman tam mesafe göstermiyor. Gerekçe kodda yazılı: bilinen mülk merkezlerinde birlikte çalışan birkaç kullanıcı, kesin yarıçaplarla bir gözlemi üçgenleyebilir. Bu yüzden:
- standart gözlemde kart üzerinde hiç mesafe yok, bağlamı halka veriyor;
- hasatta yalnızca halka aralığı var (“35–75 mi”);
- kayıp geyik ilanında “Reported within N miles of your hunting property” yazıyor.
Yanıtta distanceMiles alanı payload şekli bozulmasın diye duruyor ama her zaman null.
Birden fazla mülkü olan avcı için ikinci bir koruma var: aynı gözlem, kapsayan mülkler arasından merkezi en yakın olana teslim ediliyor, diğerlerinde görünmüyor. Böylece iki ayrı akışı karşılaştırıp konumu daraltmak mümkün olmuyor. Aynı kullanıcının mülkleri arasında en az 3 mil mesafe de oluşturma anında zorunlu. Tarama tarafında bir istek en fazla 3.000 gözleme bakıyor ve geriye 365 gün gidiyor.
İstatistikler neden kişiye özel halkalarda değil, ortak hücrelerde hesaplanıyor?
Akış halkaları her kullanıcıya göre farklı; istatistik (Intelligence) ise herkes için aynı olan sabit alanlarda hesaplanıyor: H3 hücreleri, gerçek ilçe sınırı, eyaletin bölgeleri ve eyalet. Bir alanın gözlemleri tek bir SQL fonksiyonunda, hücre metni eşitliğiyle seçiliyor (intelligence.fn_zone_window_aggregate):
or (z.zone_type = 'tight' and s.h3_index_res7 = z.h3_index)
Bu, mekânsal sorguya gerek bırakmıyor; indeksli bir metin karşılaştırması.
Bir alanın raporu yayınlanmadan önce iki kapıdan geçiyor. Birincisi katılımcı eşiği: micro 6, tight 9, neighborhood 12, local+ 15, local 20, county 25 katılımcı. Gözlem eşiği de ayrı: micro 7’den statewide 30’a kadar. Veri inceyse ürün tahmin etmiyor, “Insufficient Data” diyor.
İkincisi bağımsızlık katmanı, yalnızca en ince üç seviyede çalışıyor:
- Katman 1, mesafe (zorunlu): katılımcıların res8 hücre merkezleri arasındaki en büyük ikili mesafe micro ve tight için 0,16, neighborhood için 0,23 mili geçmeli. Tek bir kamera kümesi alanı tek başına açamıyor.
- Katman 2, alt hücre (zorunlu): gözlemler bir alt çözünürlükte en az 3 farklı hücreye yayılmalı.
- Katman 3, zaman (yumuşak): en az 2 farklı gün; sadece loglanıyor, kapı değil.
Micro’nun alt hücresi res9 olurdu ve saklanmadığı için Katman 2 orada otomatik geçiyor. Bu, “res8’in altına inme” kararının istatistik tarafındaki doğrudan sonucu.
Hava durumu ve ay evresi korelasyonu koordinatsız nasıl kuruluyor?
Her gözlem kaydedildikten sonra enrich-signals adlı bir cron işi tarafından zenginleştiriliyor; bir turda 200 gözlem. Hava, gözlemin res5 hücresinin merkezinden isteniyor (cellCenter(s.h3IndexRes5)), yani yaklaşık 250 km²’lik bir hücrenin ortasından. Hava bölgesel bir sinyal, hücre merkezi bunun için fazlasıyla yeterli ve dış servise de ham nokta gitmemiş oluyor.
Kaynak Open-Meteo’nun saatlik serisi; en fazla 92 günlük geçmiş isteniyor, zaman aşımı 8 saniye. Kurallar:
- En yakın saat gözlem zamanından 3 saatten uzaksa hava alanları boş bırakılıyor, yanlış saatin hava durumu bağlanmıyor.
- Basınç eğilimi 6 saatlik pencerede 0,02 inHg, sıcaklık eğilimi 24 saatlik pencerede 2,0 °F eşiğiyle “rising/falling/steady” oluyor.
- Basınç
pressure_msl, yani deniz seviyesine indirgenmiş değer. İlk sürüm istasyon basıncını kullanıyordu; Illinois arazisinde yarım inç düşük okuyup gözlemlerin çoğunu en alt basınç grubuna yığdı. - Başarısız bir istek kayıtlı değeri silmiyor, sadece tekrar denemeye işaretliyor; sağlayıcının verisi olmayan saat 0 °F ve kuzey rüzgârı değil,
nulloluyor.
Ay evresi ağa hiç çıkmadan hesaplanıyor: referans yeni ay 2000-01-06 18:14 UTC, sinodik ay 29,530588853 gün, sonuç 8 isimli evreye bölünüyor. Kartlar sayıma dayalı: her koşul değeri için gözlem sayısı, gündüz aktivitesi yüzdesi ve sabah/akşam payı. “Bu koşulda geçen süre” bilinçli olarak yok; mülk başına sürekli saatlik hava zaman çizelgesi toplanmıyor.
Fotokapan kareleri ve hasat kayıtları istatistiği nasıl bozmadan sayılıyor?
Fotokapan seri çekiyor: bir geyik geçerken saniye arayla üç kare. İlk sürümde her kare ayrı gözlem sayılıyordu. Şimdi kural şu: aynı avcı, aynı res8 hücresi, aralarında iki dakikadan az olan kareler tek “karşılaşma” (encounter). Hangi kameradan geldiği bilinmediği için kameranın yerini res8 hücresi tutuyor; ham koordinat olmadan bile bu ayrım yapılabiliyor. Zincirin ilk karesi encounter_lead olarak işaretleniyor, istatistik yalnızca bunları sayıyor, akış ise bütün kareleri gösteriyor. Hesap her gece anlık görüntülerden önce yeniden yapılıyor.
Saat güvenilirliği de ayrı bir kapı. Gece fotoğrafı olarak okunan (ortalama parlaklık 0,26’nın altında) ama saati 10:00–14:59 diyen bir kare, AM/PM’i yanlış ayarlanmış kamera şüphesiyle “manual” güven seviyesine düşüyor: akışta ve gün düzeyindeki sayılarda kalıyor, saat gerektiren hesaplara (zirve saatleri, gündüz payı, koşul kartları) girmiyor. Sabah ya da akşam zirvesi de en az 4 gözlem ve o yarının en az %20’sini taşımadan yayınlanmıyor.
Hareket eğilimi de hücre bazlı: iki pencerede de gözlem olan res8 hücreleri karşılaştırılıyor, böylece katılımın artması hareket artışı gibi görünmüyor. Yüzde için en az 4 kamera konumu gerekiyor, tek bir konum değişimin %60’ından fazlasını taşıyamıyor.
Hasat, content.harvests adlı bire bir alt tabloda tutuluyor ve hareket istatistiğine hiç girmiyor; sorgular yalnızca standard_sighting alt türünü okuyor. Hasat, gerçekleştiği eyaletin galerisinde kalıyor. Av günlükleri (content.hunting_logs) ise koordinat değil mülk kimliği taşıyor; hava verisi kayıt sırasında mülk için ekleniyor.
Benzer bir ürün kuruyorsanız: kontrol listesi
HuntLine IQ’dan çıkan, başka alanlara da taşınabilecek kararlar:
- Ham koordinatı yazma anında kullanın, sonra atın. Satırda yalnızca hücre kimlikleri kalsın; dosya metadata’sını (EXIF) silmeyi “başarısızsa reddet” kuralına bağlayın.
- En ince çözünürlüğü ürün ihtiyacına göre değil, ifşa riskine göre seçin. Saklamadığınız çözünürlüğü kimse sızdıramaz.
- Birden çok çözünürlüğü denormalize kolon olarak yazın; veritabanında H3 eklentisi yoksa bile istatistik sorguları indeksli metin eşitliğine iner.
- Mesafeyi hücre merkezinden hesaplayın ve kullanıcıya aralık ya da halka olarak gösterin; tam sayı, üçgenleme için yeterli bilgidir.
- Çok mülklü kullanıcıya aynı kaydı birden fazla akışta göstermeyin.
- İstatistiği bir katılımcı eşiğinin ve bağımsızlık kontrolünün arkasına koyun; veri azsa “yetersiz veri” deyin.
- Dış servislere (hava gibi) kaba hücre merkezini gönderin. Eşleştirme toleransı koyun, eksik değeri 0 değil
nullyazın. - Bedeli baştan yazın: koordinat olmadan geriye dönük yeniden hesaplama yapılamaz.
AMG Yazılım olarak konum hassasiyeti olan mobil ve web ürünleri, gizlilik kurallarını veri modeline gömerek geliştiriyoruz.
PROJENİZE UYARLAYALIM
Mobil Uygulama Geliştirme
iOS native (Swift, SwiftUI) ve React Native ile çapraz platform. Fikirden mağazaya tam süreç.
