Takip et

Temel Konseptler ve Neden Bir Şekil Servisine İhtiyaç Duyarız?

Angular uygulamalarında haritalar oluşturmak, özellikle coğrafi verileri görselleştirmek ve kullanıcı etkileşimini sağlamak söz konusu ol

Angular uygulamalarında haritalar oluşturmak, özellikle coğrafi verileri görselleştirmek ve kullanıcı etkileşimini sağlamak söz konusu olduğunda güçlü bir yetenektir. Leaflet, bu amaçla kullanabileceğiniz hafif, açık kaynaklı ve mobil uyumlu bir JavaScript kütüphanesidir. Önceki bölümlerde Angular ile Leaflet haritalarını nasıl entegre edeceğimizi ve temel harita katmanlarını nasıl yöneteceğimizi incelemiştik. Bu dördüncü bölümde ise harita üzerindeki çeşitli çizimleri (işaretleyiciler, çizgiler, çokgenler vb.) yönetmek için merkezi bir “Şekil Servisi” (Shape Service) oluşturmanın önemini ve nasıl uygulanacağını detaylı bir şekilde ele alacağız.

Harita üzerinde dinamik olarak şekiller eklemek, düzenlemek veya kaldırmak, genellikle karmaşık mantık ve yoğun olay işleme gerektirir. Bu tür bir işlevselliği doğrudan bir Angular bileşeninde yönetmek, bileşenin şişkinleşmesine, kodun tekrarına ve bakım zorluklarına yol açabilir. İşte bu noktada, tüm şekil yönetimi mantığını kapsülleyen bağımsız bir servis devreye girer. Bir Shape Service, harita bileşenlerimizden bağımsız olarak şekilleri yönetmemizi sağlayarak kodumuzu daha düzenli, yeniden kullanılabilir ve test edilebilir hale getirir.


Temel Konseptler ve Neden Bir Şekil Servisine İhtiyaç Duyarız?

Leaflet’te “şekil” terimi, harita üzerinde görsel olarak temsil edilen çeşitli coğrafi nesneleri ifade eder. Bunlar başlıca şunlardır:

  • L.Marker: Belirli bir noktayı işaretlemek için kullanılır (örn. konum işaretçileri).
  • L.Polyline: İki veya daha fazla nokta arasındaki bir çizgiyi temsil eder (örn. rotalar, sınırlar).
  • L.Polygon: Kapalı bir şekli temsil eder (örn. bölgeler, binalar).
  • L.Circle / L.CircleMarker: Belirli bir merkeze ve yarıçapa sahip dairesel alanları temsil eder.
  • L.Rectangle: Dört köşe noktasından oluşan dikdörtgen bir alanı temsil eder.

Bu şekillerin her biri kendi özelliklerine, stillerine ve etkileşimlerine sahiptir. Bir Angular uygulamasında bu şekilleri yönetirken karşılaşılan zorluklar şunlardır:

  • Sorumlulukların Ayrımı: Harita bileşeni, haritanın kendisini ve temel özelliklerini yönetmelidir. Şekil ekleme, düzenleme ve kaldırma gibi işlemlerin ayrı bir serviste olması, bileşenin sorumluluğunu azaltır.
  • Yeniden Kullanılabilirlik: Farklı harita bileşenlerinde veya farklı sayfalarda benzer şekil yönetimi mantığına ihtiyaç duyulduğunda, bir servis bu mantığı tekrar tekrar yazmaktan kurtarır.
  • Bakım Kolaylığı: Şekil yönetimiyle ilgili herhangi bir değişiklik yapıldığında, bu değişikliğin sadece servis içinde yapılması yeterlidir. Bu, hataları azaltır ve geliştirmeyi hızlandırır.
  • Merkezi Durum Yönetimi: Hangi şekillerin haritada olduğunu, hangi şeklin seçili olduğunu veya hangi şeklin düzenlendiğini merkezi bir şekilde yönetmek, uygulamanın tutarlılığını sağlar.
  • Olay İşleme: Şekillerin tıklanması, sürüklenmesi veya düzenlenmesi gibi olayları yakalamak ve işlemek için temiz ve tutarlı bir mekanizma sunar.

Bu nedenlerle, bir Shape Service oluşturmak, daha büyük ve karmaşık Angular-Leaflet uygulamalarında vazgeçilmez bir mimari desendir.


Şekil Servisi Yapısı ve Uygulanması

Şekil servisimiz, Leaflet kütüphanesini kullanarak harita üzerinde çeşitli geometrik şekilleri oluşturacak, yönetecek ve olaylarını işleyecek yöntemleri içerecektir. İlk adım olarak, Angular CLI kullanarak bir servis oluşturalım:

ng generate service services/shape

Bu komut, src/app/services/shape.service.ts dosyasını ve ilgili test dosyasını oluşturacaktır. Şimdi shape.service.ts dosyasının içeriğini tasarlayalım:

Şekil Servisi Tanımı

import { Injectable } from '@angular/core';
import * as L from 'leaflet';
import { Subject, Observable } from 'rxjs';

// Şekil türlerini tanımlayalım
export type CustomShape = L.Marker | L.Polyline | L.Polygon | L.Circle | L.Rectangle;

@Injectable({
  providedIn: 'root'
})
export class ShapeService {
  private drawnShapes: CustomShape[] = []; // Haritadaki tüm çizilmiş şekilleri tutar
  private shapeClickedSource = new Subject(); // Tıklanan şekil olaylarını yayar
  shapeClicked$: Observable = this.shapeClickedSource.asObservable();

  constructor() { }

  /
   * Haritaya bir işaretleyici (marker) ekler.
   * @param map Harita nesnesi.
   * @param latlng İşaretleyicinin koordinatları.
   * @param options İşaretleyici seçenekleri.
   * @returns Eklenen işaretleyici nesnesi.
   */
  addMarker(map: L.Map, latlng: L.LatLngExpression, options?: L.MarkerOptions): L.Marker {
    const marker = L.marker(latlng, options);
    marker.addTo(map);
    this.drawnShapes.push(marker);
    this.attachShapeEvents(marker); // Olay dinleyicilerini ekle
    return marker;
  }

  /
   * Haritaya bir çizgi (polyline) ekler.
   * @param map Harita nesnesi.
   * @param latlngs Çizginin koordinatları dizisi.
   * @param options Çizgi seçenekleri.
   * @returns Eklenen çizgi nesnesi.
   */
  addPolyline(map: L.Map, latlngs: L.LatLngExpression[], options?: L.PolylineOptions): L.Polyline {
    const polyline = L.polyline(latlngs, options);
    polyline.addTo(map);
    this.drawnShapes.push(polyline);
    this.attachShapeEvents(polyline);
    return polyline;
  }

  /
   * Haritaya bir çokgen (polygon) ekler.
   * @param map Harita nesnesi.
   * @param latlngs Çokgenin koordinatları dizisi.
   * @param options Çokgen seçenekleri.
   * @returns Eklenen çokgen nesnesi.
   */
  addPolygon(map: L.Map, latlngs: L.LatLngExpression[], options?: L.PolylineOptions): L.Polygon {
    const polygon = L.polygon(latlngs, options);
    polygon.addTo(map);
    this.drawnShapes.push(polygon);
    this.attachShapeEvents(polygon);
    return polygon;
  }

  /
   * Haritaya bir daire (circle) ekler.
   * @param map Harita nesnesi.
   * @param latlng Dairenin merkez koordinatları.
   * @param options Daire seçenekleri (yarıçap dahil).
   * @returns Eklenen daire nesnesi.
   */
  addCircle(map: L.Map, latlng: L.LatLngExpression, options?: L.CircleOptions): L.Circle {
    const circle = L.circle(latlng, options);
    circle.addTo(map);
    this.drawnShapes.push(circle);
    this.attachShapeEvents(circle);
    return circle;
  }

  /
   * Haritadan belirli bir şekli kaldırır.
   * @param map Harita nesnesi.
   * @param shape Kaldırılacak şekil nesnesi.
   */
  removeShape(map: L.Map, shape: CustomShape): void {
    map.removeLayer(shape);
    this.drawnShapes = this.drawnShapes.filter(s => s !== shape);
  }

  /
   * Haritadaki tüm çizilmiş şekilleri kaldırır.
   * @param map Harita nesnesi.
   */
  clearAllShapes(map: L.Map): void {
    this.drawnShapes.forEach(shape => {
      map.removeLayer(shape);
    });
    this.drawnShapes = [];
  }

  /
   * Bir şekle temel olay dinleyicilerini ekler.
   * @param shape Olay dinleyicilerinin ekleneceği şekil.
   */
  private attachShapeEvents(shape: CustomShape): void {
    shape.on('click', (event: L.LeafletMouseEvent) => {
      console.log('Shape clicked:', shape);
      this.shapeClickedSource.next(shape); // Tıklanan şekli yay
    });

    // İsteğe bağlı olarak diğer olayları da ekleyebilirsiniz:
    // shape.on('mouseover', () => { / ... / });
    // shape.on('mouseout', () => { / ... / });
  }

  /
   * Mevcut çizilmiş şekilleri döndürür.
   * @returns Çizilmiş şekiller dizisi.
   */
  getDrawnShapes(): CustomShape[] {
    return this.drawnShapes;
  }
}

Yukarıdaki kodda:

  • CustomShape türü, Leaflet’teki farklı şekil türlerini tek bir tip altında toplar.
  • drawnShapes dizisi, servisin yönettiği tüm şekillerin referanslarını tutar. Bu sayede şekiller üzerinde toplu işlemler (temizleme, arama vb.) yapılabilir.
  • shapeClickedSource bir RxJS Subject‘idir. Bu, harita bileşenlerinin tıklanan şekiller hakkında bilgi almasını sağlar.
  • addMarker, addPolyline, addPolygon, addCircle gibi yöntemler, haritaya belirli türde bir şekil eklemek için kullanılır ve eklenen şekli drawnShapes dizisine kaydeder.
  • removeShape ve clearAllShapes yöntemleri, şekilleri haritadan kaldırmak ve drawnShapes dizisinden temizlemek için kullanılır.
  • attachShapeEvents özel yöntemi, her yeni eklenen şekle temel olay dinleyicilerini (örneğin ‘click’) ekler. Bu olaylar, shapeClickedSource aracılığıyla ilgili bileşenlere yayınlanır.

Şekil Servisi Kullanımı ve Pratik Örnekler

Şimdi bu servisi bir harita bileşeninde nasıl kullanacağımıza bakalım. Varsayımsal bir MapComponent‘imiz olduğunu ve Leaflet haritasını başlattığını varsayalım.

MapComponent’te Servisi Kullanma

// map.component.ts
import { Component, OnInit, OnDestroy, AfterViewInit } from '@angular/core';
import * as L from 'leaflet';
import { ShapeService, CustomShape } from '../services/shape.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.css']
})
export class MapComponent implements OnInit, AfterViewInit, OnDestroy {
  private map!: L.Map;
  private shapeClickSubscription!: Subscription;

  constructor(private shapeService: ShapeService) { }

  ngOnInit(): void {
    // Harita tıklandığında veya yüklendiğinde şekilleri eklemek için uygun yer
  }

  ngAfterViewInit(): void {
    this.initMap();
    this.addInitialShapes();
    this.subscribeToShapeClicks();
  }

  ngOnDestroy(): void {
    if (this.shapeClickSubscription) {
      this.shapeClickSubscription.unsubscribe();
    }
    if (this.map) {
      this.map.remove(); // Harita kaynaklarını serbest bırak
    }
  }

  private initMap(): void {
    this.map = L.map('map', {
      center: [ 39.9334, 32.8597 ], // Ankara koordinatları
      zoom: 10
    });

    const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 18,
      minZoom: 3,
      attribution: '© OpenStreetMap'
    });

    tiles.addTo(this.map);
  }

  private addInitialShapes(): void {
    // Örnek bir işaretleyici ekleyelim
    this.shapeService.addMarker(this.map, [39.9334, 32.8597], {
      title: 'Ankara Kalesi',
      draggable: true,
      alt: 'Ankara Kalesi'
    }).bindPopup('Ankara Kalesi
Başkentin tarihi simgesi.'); // Örnek bir çizgi ekleyelim const routeCoordinates: L.LatLngExpression[] = [ [39.95, 32.80], [39.90, 32.85], [39.92, 32.90] ]; this.shapeService.addPolyline(this.map, routeCoordinates, { color: 'blue', weight: 5, opacity: 0.7 }).bindPopup('Örnek Rota'); // Örnek bir çokgen ekleyelim const polygonCoordinates: L.LatLngExpression[] = [ [39.90, 32.75], [39.88, 32.78], [39.90, 32.82], [39.92, 32.78] ]; this.shapeService.addPolygon(this.map, polygonCoordinates, { color: 'red', fillColor: '#f03', fillOpacity: 0.5 }).bindPopup('Örnek Bölge'); // Örnek bir daire ekleyelim this.shapeService.addCircle(this.map, [39.91, 32.89], { radius: 500, // metre cinsinden color: 'green', fillColor: '#0f3', fillOpacity: 0.4 }).bindPopup('500m Yarıçaplı Daire'); } private subscribeToShapeClicks(): void { this.shapeClickSubscription = this.shapeService.shapeClicked$.subscribe(clickedShape => { console.log('Harita bileşeninde tıklanan şekil:', clickedShape); // Tıklanan şekle göre UI'da bir şeyler yapabiliriz // Örneğin, bir panelde şeklin özelliklerini gösterebiliriz. if (clickedShape instanceof L.Marker) { console.log('Tıklanan bir işaretleyici:', clickedShape.getLatLng()); } else if (clickedShape instanceof L.Polyline) { console.log('Tıklanan bir çizgi veya çokgen.'); // Çizginin rengini değiştirebiliriz clickedShape.setStyle({ color: 'purple', weight: 8 }); } }); } // Kullanıcı etkileşimiyle şekil ekleme örneği addCustomMarker(): void { const randomLat = 39.8 + Math.random() * 0.2; const randomLng = 32.7 + Math.random() * 0.3; this.shapeService.addMarker(this.map, [randomLat, randomLng], { title: 'Yeni İşaretleyici', icon: L.icon({ iconUrl: 'assets/marker-icon-2x.png', // Kendi ikonunuzu kullanabilirsiniz iconSize: [25, 41], iconAnchor: [12, 41] }) }).bindPopup('Dinamik olarak eklenen işaretleyici'); } // Tüm şekilleri temizleme örneği clearMapShapes(): void { this.shapeService.clearAllShapes(this.map); console.log('Haritadaki tüm şekiller temizlendi.'); } }

Bu örnekte:

  • MapComponent, ShapeService‘i constructor’ında enjekte eder.
  • ngAfterViewInit yaşam döngüsü kancasında harita başlatılır ve ardından addInitialShapes() metodu ile önceden tanımlanmış bazı şekiller servisi kullanarak haritaya eklenir.
  • subscribeToShapeClicks() metodu, servisin shapeClicked$ Observable’ına abone olur. Bu sayede, herhangi bir şekle tıklandığında MapComponent bilgilendirilir ve tıklanan şekle özel işlemler yapabilir (örneğin, rengini değiştirmek veya detaylarını bir kenar çubuğunda göstermek).
  • addCustomMarker() ve clearMapShapes() gibi yöntemler, kullanıcı etkileşimleriyle servisin nasıl kullanılacağını gösterir.
  • ngOnDestroy içinde aboneliklerin iptal edilmesi ve harita kaynaklarının serbest bırakılması önemlidir.

Gelişmiş Konular ve En İyi Uygulamalar

Leaflet.Draw ile Entegrasyon

Eğer kullanıcıların harita üzerinde kendi şekillerini çizmesine izin vermek istiyorsanız, Leaflet.Draw eklentisini kullanabilirsiniz. Shape Service’imiz doğrudan çizim araçlarını sağlamasa da, Leaflet.Draw tarafından çizilen şekilleri yönetmek için mükemmel bir araçtır. Leaflet.Draw’ın draw:created olayını dinleyerek oluşturulan şekilleri Shape Service’e ekleyebilir ve böylece merkezi yönetimden faydalanabilirsiniz.

// MapComponent içinde veya ayrı bir DrawingService içinde
this.map.on(L.Draw.Event.CREATED, (event: any) => {
  const layer = event.layer;
  // Burada layer'ı uygun şekilde ShapeService'e ekleyebilirsiniz
  if (layer instanceof L.Marker) {
      this.shapeService.addMarker(this.map, layer.getLatLng(), layer.options);
  } else if (layer instanceof L.Polyline) {
      this.shapeService.addPolyline(this.map, layer.getLatLngs() as L.LatLngExpression[], layer.options);
  } // Diğer şekil türleri için de benzer şekilde devam edilebilir
  layer.addTo(this.map); // Leaflet.Draw otomatik ekler, bu satır gerekli olmayabilir
  console.log('Yeni şekil çizildi:', layer);
});

Şekilleri Kalıcı Hale Getirme (Persistence)

Çizilen veya eklenen şekilleri bir veritabanında saklamak ve daha sonra yüklemek, birçok harita uygulamasının temel gereksinimidir. Şekilleri genellikle GeoJSON formatında saklamak en yaygın yaklaşımdır. Leaflet, her şeklin toGeoJSON() metodunu sunar.

// ShapeService içinde yeni bir metod
export class ShapeService {
  // ... diğer metotlar ...

  /
   * Tüm çizilmiş şekilleri GeoJSON formatında döndürür.
   * @returns FeatureCollection GeoJSON nesnesi.
   */
  getShapesAsGeoJSON(): GeoJSON.FeatureCollection {
    const features: GeoJSON.Feature[] = [];
    this.drawnShapes.forEach(shape => {
      if (shape.toGeoJSON) { // toGeoJSON metodunun varlığını kontrol et
        const geoJsonFeature = shape.toGeoJSON();
        // İsteğe bağlı olarak özellikler ekleyebilirsiniz
        // geoJsonFeature.properties = { ... };
        features.push(geoJsonFeature);
      }
    });
    return {
      type: 'FeatureCollection',
      features: features
    };
  }

  /
   * GeoJSON verilerinden haritaya şekiller ekler.
   * @param map Harita nesnesi.
   * @param geojsonData GeoJSON FeatureCollection nesnesi.
   */
  addShapesFromGeoJSON(map: L.Map, geojsonData: GeoJSON.FeatureCollection): void {
    L.geoJSON(geojsonData, {
      onEachFeature: (feature, layer) => {
        // Her GeoJSON özelliğini Leaflet katmanına dönüştürür
        // ve ShapeService tarafından yönetilmesini sağlar.
        if (layer instanceof L.Marker) {
          this.addMarker(map, layer.getLatLng(), layer.options);
        } else if (layer instanceof L.Polyline) { // Hem Polyline hem Polygon için geçerli
          this.addPolyline(map, layer.getLatLngs() as L.LatLngExpression[], layer.options);
        } else if (layer instanceof L.Polygon) {
            this.addPolygon(map, layer.getLatLngs() as L.LatLngExpression[], layer.options);
        }
        // Daha karmaşık senaryolarda L.geoJSON'un kendisi yeterli olabilir,
        // ancak ShapeService'in 'drawnShapes' listesine eklemek için bu yaklaşım tercih edilebilir.
      }
    }).addTo(map);
  }
}

Tip Güvenliği ve Arayüzler

Leaflet’in seçenek nesneleri (MarkerOptions, PolylineOptions vb.) zaten iyi tanımlanmıştır. Ancak, kendi özel şekil özelliklerinizi veya meta verilerinizi saklamak isterseniz, bu tür seçenekler için özel TypeScript arayüzleri oluşturmak kodunuzu daha tip güvenli hale getirir.


Sonuç ve Sıkça Sorulan Sorular (SSS)

Bu rehberde, Angular ve Leaflet ile haritalarda şekil yönetimini merkezileştirmek için güçlü bir Shape Service’in nasıl oluşturulacağını detaylıca inceledik. Bu servis sayesinde, harita bileşenlerimiz daha temiz, daha odaklı hale gelirken, şekil ekleme, kaldırma ve olay işleme mantığı yeniden kullanılabilir ve bakımı kolay bir birimde kapsüllenmiş olur. RxJS Subject’ler kullanarak bileşenler arası iletişimi sağlamak, modern Angular uygulamalarında yaygın ve etkili bir yaklaşımdır. GeoJSON entegrasyonu gibi gelişmiş konular, uygulamalarınızın kapsamını genişletmenize olanak tanır.

Sıkça Sorulan Sorular (SSS)

  1. Neden şekilleri doğrudan harita bileşeninde ekleyip yönetmiyorum?

    Doğrudan bileşende yönetmek, basit uygulamalar için yeterli olabilir. Ancak uygulamanız büyüdükçe, bileşeniniz şişkinleşir, sorumlulukları artar ve kodun bakımı zorlaşır. Bir servis kullanmak, sorumlulukların ayrılmasını sağlar, kodu daha modüler, yeniden kullanılabilir ve test edilebilir hale getirir.

  2. Farklı şekil türlerini tek bir serviste nasıl yönetebilirim?

    CustomShape gibi bir birleşim tipi (union type) kullanarak farklı Leaflet şekil türlerini tek bir dizide saklayabilirsiniz. Her şekil türü için ayrı addXxx metotları tanımlayarak API’yi temiz tutarsınız. Olay işleme sırasında instanceof operatörünü kullanarak şeklin türünü belirleyebilir ve ona göre işlem yapabilirsiniz.

  3. Şekilleri nasıl etkileşimli hale getirebilirim (tıklama, sürükleme vb.)?

    Leaflet şekillerinin .on('event', callback) metodu bulunur. Shape Service içinde attachShapeEvents gibi bir metot ile bu olay dinleyicilerini şekillere ekleyebilirsiniz. Tıklama gibi olayları bir RxJS Subject aracılığıyla bileşenlere yayınlayarak, bileşenlerin bu olaylara abone olmasını ve tepki vermesini sağlayabilirsiniz.

  4. Bu servisi başka harita kütüphaneleriyle (örneğin Google Maps) kullanabilir miyim?

    Hayır, bu Shape Service tamamen Leaflet kütüphanesine özgüdür. Leaflet’in kendi API’sini (L.marker, L.polyline vb.) kullanır. Google Maps veya OpenLayers gibi başka bir harita kütüphanesi kullanıyorsanız, o kütüphanenin API’sine uygun benzer bir servis oluşturmanız gerekir.

  5. Çizilen şekilleri nasıl kalıcı hale getirebilirim (veritabanına kaydetme)?

    Şekilleri kalıcı hale getirmek için genellikle GeoJSON formatı kullanılır. Leaflet’teki çoğu şekil, kendi durumlarını GeoJSON formatına dönüştüren bir toGeoJSON() metoduna sahiptir. Bu GeoJSON verilerini bir sunucuya gönderebilir ve bir veritabanında saklayabilirsiniz. Uygulama yüklendiğinde, veritabanından GeoJSON verilerini alıp L.geoJSON() metodu veya özel olarak yazılmış addShapesFromGeoJSON metodu aracılığıyla tekrar haritaya ekleyebilirsiniz.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık