← Tüm projeler

GeoCommand

Operatörün simüle araçları haritada izlediği, çizdiği bölgelere giriş ve çıkışı takip ettiği, araçlara görev atadığı bir masaüstü uygulaması. Bağlantı kopup geri geldiğinde hiçbir olayın kaybolmaması ve iki kez sayılmaması tasarımın merkeziydi.

Dönem
Eylül 2026
Alan
Savunma & Sistem, Full-Stack & Mobil
Rol
Tek kişilik proje: tasarım, kod, test, değerlendirme
Teknoloji
C#, .NET 10, WPF, CommunityToolkit.Mvvm, Mapsui, ASP.NET Core, SignalR, EF Core, PostGIS, MEF (System.Composition), NMEA 0183, Serilog, xUnit, Testcontainers
Kod
github.com/l4NGEL/GeoCommand
127test, 9 tanesi gerçek PostGIS ile
4katmanda yinelenen olay koruması
3konum kaynağı: simülatör, CSV, NMEA plugin
Görev atama ekranı; araç listesi, harita, seçili aracın ayrıntıları ve canlı olay listesi
Şekil 1Görev atama ekranı; araç listesi, harita, seçili aracın ayrıntıları ve canlı olay listesi

Ne yapıyor

Dört simüle araç (ALFA-1, BRAVO-2, CHARLIE-3, DELTA-4) haritada hareket ediyor. Operatör haritaya tıklayarak çokgen bölgeler çiziyor, araçların bu bölgelere girip çıkmasını canlı olay listesinde görüyor, araçlara öncelikli görevler atıyor ve seçili aracın belirli bir tarih aralığındaki izini sorguluyor.

Eğitim amaçlı bir simülasyon; hiçbir gerçek sisteme, cihaza ya da kurumsal veriye bağlanmıyor.

Mimari

Sunucu dört katmandan oluşuyor. Domain hiçbir projeye bağımlı değil; Application veritabanına kendi tanımladığı bir arayüz üzerinden erişiyor ve Infrastructure bunu EF Core ile uyguluyor. Soyutlamaları yalnızca gerçek bir ihtiyaç olduğunda ekledim. Örneğin IPositionSource’un üç gerçek uygulaması var: simülatör, CSV kayıt dosyası ve bir NMEA plugin’i. Ayrı bir repository katmanı eklemedim, çünkü EF Core’un DbSet’i zaten bu işi görüyor.

Bir konum bildirimi sisteme girdiğinde şu yolu izliyor:

01Kaynaksimülatör, CSVveya HTTP POST02Doğrulaaralık, gelecekzaman, araç03Araç kilidieski bildirim→ Duplicate04Kaydetkonum geçmişi+ son durum05Bölge testiönceki ↔ yeniüyelik → olay06YayınlaSignalR hub→ WPF istemcitek veritabanı işlemi
Şekil 2Bir konum bildiriminin yolu. Kayıt ve bölge olayları tek bir veritabanı işleminde yazılıyor.

Aynı olayı iki kez üretmemek

Bu tür sistemlerde en sinsi hata, aynı bölge girişinin iki kez raporlanması. Bunu dört ayrı katmanda engelliyorum:

  1. Durum geçişi: Olay yalnızca “içeride / dışarıda” durumu değiştiğinde üretiliyor; üyelik (araç, bölge) birincil anahtarlı bir tabloda tutuluyor.
  2. İdempotent alma: Bildirimin zamanı aracın son kayıtlı zamanından yeni değilse Duplicate sayılıyor. Zaman damgalarını PostgreSQL’in mikrosaniye hassasiyetine indirgiyorum; yoksa veritabanından okunan zaman ile tekrar gönderilen bildirim farklı görünüyordu. Bu hatayı bir test yakaladı.
  3. Eşzamanlılık: Aynı araç üzerindeki işlemler araç başına bir kilitle sıraya giriyor. Veritabanında ek güvence olarak kısmi tekil indeksler var: araç başına tek aktif görev, aynı bölge olayının tekrarı yasak.
  4. İstemci: Olaylar kimliğe göre tekilleştiriliyor; yeniden bağlanınca alınan anlık görüntü, canlı akıştan gelen daha yeni bir güncellemenin üzerine yazmıyor.

Bağlantı kopunca

İstemci önce SignalR’a abone oluyor, sonra REST’ten araçları, son olayları ve bölgeleri çekiyor. Sıra önemli: tersini yapsaydım, çekme ile abonelik arasındaki boşlukta gelen güncellemeler kaybolurdu. Sunucu tamamen kapanıp açılsa bile istemci artan aralıklarla yeniden bağlanmayı deniyor ve ekranda bir uyarı bandı gösteriyor.

API bağlantısı koptuğunda uyarı bandı ve durum göstergesi
Şekil 3API bağlantısı koptuğunda uyarı bandı ve durum göstergesi

Coğrafi doğruluk

Harita Web Mercator’da çiziliyor ama tüm hesaplar WGS84 enlem / boylam üzerinde. Tıklanan nokta enlem / boylama çevrilip API’ye öyle gidiyor. Bölge testi sınır kutusuyla değil, NetTopologySuite ile gerçek çokgene göre yapılıyor. Bir bölge çizildiğinde içinde zaten bulunan araçlar için “giriş” olayı üretmiyorum; araç girmedi, bölge onun etrafına çizildi.

Seçili aracın tarih aralığındaki izi ve o aralıktaki olaylar
Şekil 4Seçili aracın tarih aralığındaki izi ve o aralıktaki olaylar

Plugin mimarisi

Konum kaynakları sabit kodlanmış değil. plugins/ klasörüne derlenmiş bir DLL bırakmak yeni bir kaynak eklemeye yetiyor; API, istemci ya da veritabanında değişiklik gerekmiyor. Kaynak, API’nin kaynak listesinde ve istemcinin açılır listesinde kendiliğinden görünüyor ve çalışma anında seçilebiliyor.

  • Keşif (MEF): Plugin sınıfı [PositionSource("Ad")] özniteliğiyle işaretleniyor. Katalog dışa aktarımları Lazy<IPositionSource, Metadata> olarak aldığı için kaynak listesi, plugin sınıfı hiç örneklenmeden okunuyor; yalnızca seçilen kaynak oluşturuluyor.
  • Yalıtım: Her plugin kendi AssemblyLoadContext’ine yükleniyor ve bağımlılıklarını kendi klasöründen çözüyor; API ile farklı sürüm bir paket kullanabiliyor. Sözleşme derlemeleri ise bilerek paylaşılıyor. Paylaşılmasaydı plugin’deki IPositionSource API’dekiyle aynı tür sayılmaz ve MEF hiçbir şeyi eşleştiremezdi; bunu bir birim testi doğruluyor.
  • Bağımlılık: Plugin API’nin DI kapsayıcısını görmüyor; yalnızca kendi yapılandırma bölümünü, logger’ı ve saati veren küçük bir host arayüzü alıyor.
  • Hata dayanıklılığı: Bozuk bir DLL, eksik dosya ya da aynı adlı ikinci kaynak yalnızca o plugin’in atlanmasına yol açıyor; API yine açılıyor ve hata katalogda görünüyor.

İlk plugin, TCP üzerinden gelen NMEA 0183 GPS akışını ($GPRMC cümleleri) okuyor. Sağlama toplamı zorunlu; hatalı toplam, geçersiz alan (60’tan büyük dakika, 30 Şubat) ve 120 karakteri aşan satır reddediliyor. Test için GPS alıcısı yerine geçen bir Python yayıncısı yazdım; cümlelerin %5’inin sağlama toplamını bilerek bozuyor. Kesirli saniyeyi double ile ayrıştırınca 05.06 saniye 05.0599999 oluyordu; bu hatayı gidiş-dönüş testi yakaladı, decimal’a geçtim.

Teknoloji seçimleri

Alan Seçim Neden
Çalışma zamanı .NET 10 LTS .NET 8’in desteği Kasım 2026’da bitiyor
Masaüstü WPF + CommunityToolkit.Mvvm Kaynak üreteçleri tekrar eden kodu kaldırıyor
Harita Mapsui + OpenStreetMap Serbest lisans, NTS geometrisini doğrudan çiziyor
Canlı veri SignalR, tipli hub Sunucudan istemciye itme, otomatik yeniden bağlanma
Genişletilebilirlik MEF (System.Composition) + AssemblyLoadContext Plugin’leri örneklemeden keşif, bağımlılık yalıtımı
Veritabanı PostgreSQL 17 + PostGIS 3.5 GIST indeksli geometri, ST_Covers sorguları
Test xUnit + Testcontainers Entegrasyon testleri sahte değil gerçek PostGIS’e karşı

Sınırlamalar

  • Tek API örneği varsayılıyor. Birden çok örnek için dağıtık kilit ve SignalR backplane gerekir.
  • Kimlik doğrulama yok; yalnızca yerel demo için.
  • Nokta-çokgen testi düzlemsel. Çok büyük bölgelerde kenarlar jeodezik değil.
  • API yeniden başlarsa simülatör baştan başlıyor ve araçlar ilk konumlarına “atlıyor”; bu atlama sahte bir bölge olayı üretebilir.
  • Plugin’ler güvenilir kod sayılıyor: API ile aynı süreçte ve aynı yetkilerle çalışıyor; AssemblyLoadContext bağımlılık yalıtımı sağlıyor, güvenlik yalıtımı sağlamıyor. Çalışırken kaldırılıp yeniden yüklenemiyor.
  • NMEA kaynağı yalnızca RMC cümlesini kullanıyor ve şimdilik yalnızca TCP destekliyor; seri port (RS-232) yok.
  • WPF istemcinin durum mantığı birim testli; ekran akışlarını UI Automation betiğiyle doğruladım.