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:
CustomShapetürü, Leaflet’teki farklı şekil türlerini tek bir tip altında toplar.drawnShapesdizisi, servisin yönettiği tüm şekillerin referanslarını tutar. Bu sayede şekiller üzerinde toplu işlemler (temizleme, arama vb.) yapılabilir.shapeClickedSourcebir RxJSSubject‘idir. Bu, harita bileşenlerinin tıklanan şekiller hakkında bilgi almasını sağlar.addMarker,addPolyline,addPolygon,addCirclegibi yöntemler, haritaya belirli türde bir şekil eklemek için kullanılır ve eklenen şeklidrawnShapesdizisine kaydeder.removeShapeveclearAllShapesyöntemleri, şekilleri haritadan kaldırmak vedrawnShapesdizisinden temizlemek için kullanılır.attachShapeEventsözel yöntemi, her yeni eklenen şekle temel olay dinleyicilerini (örneğin ‘click’) ekler. Bu olaylar,shapeClickedSourcearacı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.ngAfterViewInityaşam döngüsü kancasında harita başlatılır ve ardındanaddInitialShapes()metodu ile önceden tanımlanmış bazı şekiller servisi kullanarak haritaya eklenir.subscribeToShapeClicks()metodu, servisinshapeClicked$Observable’ına abone olur. Bu sayede, herhangi bir şekle tıklandığındaMapComponentbilgilendirilir ve tıklanan şekle özel işlemler yapabilir (örneğin, rengini değiştirmek veya detaylarını bir kenar çubuğunda göstermek).addCustomMarker()veclearMapShapes()gibi yöntemler, kullanıcı etkileşimleriyle servisin nasıl kullanılacağını gösterir.ngOnDestroyiç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)
-
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.
-
Farklı şekil türlerini tek bir serviste nasıl yönetebilirim?
CustomShapegibi bir birleşim tipi (union type) kullanarak farklı Leaflet şekil türlerini tek bir dizide saklayabilirsiniz. Her şekil türü için ayrıaddXxxmetotları tanımlayarak API’yi temiz tutarsınız. Olay işleme sırasındainstanceofoperatörünü kullanarak şeklin türünü belirleyebilir ve ona göre işlem yapabilirsiniz. -
Şekilleri nasıl etkileşimli hale getirebilirim (tıklama, sürükleme vb.)?
Leaflet şekillerinin
.on('event', callback)metodu bulunur. Shape Service içindeattachShapeEventsgibi bir metot ile bu olay dinleyicilerini şekillere ekleyebilirsiniz. Tıklama gibi olayları bir RxJSSubjectaracılığıyla bileşenlere yayınlayarak, bileşenlerin bu olaylara abone olmasını ve tepki vermesini sağlayabilirsiniz. -
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.polylinevb.) 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. -
Ç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ıpL.geoJSON()metodu veya özel olarak yazılmışaddShapesFromGeoJSONmetodu aracılığıyla tekrar haritaya ekleyebilirsiniz.
