38980-vm/app-9w9pd00g5j41/GOOGLEMAP_QUICK_REFERENCE.md
2026-03-04 18:25:09 +00:00

14 KiB
Raw Permalink Blame History

GoogleMap Marker - Hızlı Referans

🎯 7 KRİTİK KURAL

1. SVG MARKER KULLAN (SymbolPath DEĞİL)

// ❌ YANLIŞ - SymbolPath
return {
  path: google.maps.SymbolPath.CIRCLE,
  scale: 12,
  // ...
};

// ✅ DOĞRU - SVG Data URL
const svg = `
  <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
    <circle cx="16" cy="16" r="12" fill="${fill}" stroke="#ffffff" stroke-width="${strokeWidth}" />
  </svg>
`;

return {
  url: `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`,
  scaledSize: new google.maps.Size(32, 32), // ⚠️ SABİT
  anchor: new google.maps.Point(16, 16), // ⚠️ MERKEZ SABİT
  labelOrigin: new google.maps.Point(16, 16),
};

Neden? SVG = pixel-perfect kontrol, sabit boyut/anchor, jitter sıfır


2. ASLA BOUNCE KULLANMA

// ❌ YANLIŞ
marker.setAnimation(google.maps.Animation.BOUNCE);

// ✅ DOĞRU
marker.setAnimation(null);

Neden? BOUNCE anchor'ı oynatır → jitter yaratır


3. hasCenteredRef MAP INIT'TE SET ETME

// ❌ YANLIŞ - Map init'te set etme
useEffect(() => {
  // ... map oluştur
  hasCenteredRef.current = true; // ❌
}, [isScriptLoaded, places]);

// ✅ DOĞRU - fitBounds sonrası set et
useEffect(() => {
  if (places.length > 0 && !hasCenteredRef.current) {
    map.fitBounds(bounds);
    hasCenteredRef.current = true; // ✅
  }
}, [places]);

Neden? Map init'te set edilirse fitBounds hiç çalışmaz


4. LABEL FONT-SIZE DEĞİŞTİRME

// ❌ YANLIŞ - Değişken font size
fontSize: state === 'default' ? '14px' : '16px'

// ✅ DOĞRU - Sabit font size
fontSize: '14px' // SABİT

Neden? Değişken font size labelOrigin'i yeniden hesaplatır → mikro-jitter


5. PLACES EFFECT'İNDE CLEANUP YAPMA

// ❌ YANLIŞ
useEffect(() => {
  // ... marker creation
  
  return () => {
    markersRef.current.forEach(marker => marker.setMap(null));
    markersRef.current.clear();
  };
}, [places]);

// ✅ DOĞRU
useEffect(() => {
  // ... marker creation
  
  // Cleanup YOK - sadece selective deletion
}, [places]);

Neden? places değiştiğinde cleanup tüm marker'ları yok eder → recreation → jitter


6. POLYLINE SIRALAMA GÜVENLİ YAPMA

// ❌ YANLIŞ - undefined/null güvensiz
(a.orderIndex || 0) - (b.orderIndex || 0)

// ✅ DOĞRU - Number.isFinite check
const ordered = [...dayPlaces].sort((a, b) => {
  const ai = Number.isFinite(a.orderIndex) ? a.orderIndex! : 999;
  const bi = Number.isFinite(b.orderIndex) ? b.orderIndex! : 999;
  return ai - bi;
});

Neden? undefined/null orderIndex'ler 0 olur → rota yanlış çizilir


7. POLYLINE DEPENDENCY OPTİMİZE ET

// ❌ YANLIŞ - places array referansı
}, [places, activeDayId, showPolyline]);

// ✅ DOĞRU - places.length kullan
}, [places.length, activeDayId, showPolyline]);

Neden? places referansı her değişimde effect tetiklenir → gereksiz recreation


🎨 SVG MARKER ICON

const createSvgMarkerIcon = (
  dayIndex: number,
  state: 'default' | 'hover' | 'selected'
) => {
  const color = getDayColor(dayIndex);
  const fill = state === 'default' ? color.fill : color.stroke;
  const strokeWidth = state === 'selected' ? 3 : 2;

  const svg = `
    <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
      <circle cx="16" cy="16" r="12" fill="${fill}" stroke="#ffffff" stroke-width="${strokeWidth}" />
    </svg>
  `;

  return {
    url: `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`,
    scaledSize: new google.maps.Size(32, 32), // ⚠️ SABİT
    anchor: new google.maps.Point(16, 16), // ⚠️ MERKEZ SABİT
    labelOrigin: new google.maps.Point(16, 16),
  };
};

SABİT KALACAKLAR:

  • width, height (32)
  • cx, cy (16)
  • r (12)
  • scaledSize (32, 32)
  • anchor (16, 16)

DEĞİŞEBİLECEKLER:

  • fill (default: color.fill, hover/select: color.stroke)
  • stroke-width (default/hover: 2, select: 3)

🛣️ PER-DAY POLYLINE

const polylinesRef = useRef<Map<string, google.maps.Polyline>>(new Map());

useEffect(() => {
  if (!mapInstanceRef.current || !showPolyline) return;

  const map = mapInstanceRef.current;

  // Eski polyline'ları temizle
  polylinesRef.current.forEach(line => line.setMap(null));
  polylinesRef.current.clear();

  // Günlere göre grupla
  const groupedByDay = new Map<string, typeof places>();
  places.forEach(place => {
    if (!place.dayId) return;
    if (!groupedByDay.has(place.dayId)) {
      groupedByDay.set(place.dayId, []);
    }
    groupedByDay.get(place.dayId)!.push(place);
  });

  // Her gün için polyline oluştur
  groupedByDay.forEach((dayPlaces, dayId) => {
    // activeDayId varsa sadece o günü göster
    if (activeDayId && activeDayId !== dayId) return;

    if (dayPlaces.length < 2) return;

    // Sıraya göre diz
    const ordered = [...dayPlaces].sort(
      (a, b) => (a.orderIndex || 0) - (b.orderIndex || 0)
    );

    const path = ordered.map(p => ({
      lat: p.lat,
      lng: p.lng,
    }));

    const color = getDayColor(ordered[0].dayIndex || 0);

    const polyline = new google.maps.Polyline({
      path,
      geodesic: true,
      strokeColor: color.stroke,
      strokeOpacity: 0.6,
      strokeWeight: 4,
      map,
    });

    polylinesRef.current.set(dayId, polyline);
  });
}, [places, activeDayId, showPolyline]);

Özellikler:

  • Her gün ayrı polyline (Map<dayId, Polyline>)
  • Her gün kendi renginde (getDayColor)
  • activeDayId desteği (sadece seçili gün)
  • Günler arası geçişler çizilmez

📐 MARKER LIFECYCLE

Effect 1: Map Initialization (Unmount Cleanup)

useEffect(() => {
  // Map oluştur
  
  return () => {
    // ✅ SADECE unmount'ta cleanup
    markersRef.current.forEach(marker => marker.setMap(null));
    markersRef.current.clear();
    polylinesRef.current.forEach(polyline => polyline.setMap(null));
    polylinesRef.current.clear();
  };
}, [isScriptLoaded, places]);

Effect 2: Marker Creation (NO Cleanup)

useEffect(() => {
  // 1. Selective deletion
  markersRef.current.forEach((marker, id) => {
    if (!currentPlaceIds.has(id)) {
      marker.setMap(null);
      markersRef.current.delete(id);
    }
  });

  // 2. Marker creation (skip if exists, use SVG icon)
  places.forEach((place) => {
    if (markersRef.current.has(place.id)) return; // ⚠️ ATLA
    
    const marker = new google.maps.Marker({
      // ...
      icon: createSvgMarkerIcon(place.dayIndex || 0, 'default'), // ✅ SVG
    });
  });

  // ❌ CLEANUP YOK
}, [places]);

Effect 3: Visual Updates (NO Creation)

useEffect(() => {
  markersRef.current.forEach((marker, id) => {
    // 1. Visibility
    marker.setVisible(isVisible);

    // 2. State
    if (id === selectedPlaceId) {
      marker.setZIndex(1000);
      marker.setAnimation(null); // ✅ BOUNCE YOK
    }

    // 3. Icon (SVG - sadece renk/stroke değişir)
    marker.setIcon(createSvgMarkerIcon(dayIndex, state));

    // 4. Label
    marker.setLabel({ ... });
  });
}, [hoveredPlaceId, selectedPlaceId, activeDayId, places]);

Effect 4: Per-Day Polyline

useEffect(() => {
  // Eski polyline'ları temizle
  polylinesRef.current.forEach(line => line.setMap(null));
  polylinesRef.current.clear();

  // Günlere göre grupla ve polyline oluştur
  // (detay için yukarıdaki PER-DAY POLYLINE bölümüne bakın)
}, [places, activeDayId, showPolyline]);

🔄 STATE TRANSITIONS

Default → Hover

// SVG Değişenler:
fill: color.fill  color.stroke
stroke-width: 2 (aynı)

// Marker Değişenler:
zIndex: orderIndex  999
label.fontSize: 14px  16px

// Değişmeyenler:
scaledSize: (32, 32) (sabit)
anchor: (16, 16) (sabit)
animation: null (sabit)

Hover → Selected

// SVG Değişenler:
stroke-width: 2  3

// Marker Değişenler:
zIndex: 999  1000

// Değişmeyenler:
fill: color.stroke (aynı)
scaledSize: (32, 32) (sabit)
anchor: (16, 16) (sabit)
animation: null (sabit)  BOUNCE YOK

Selected → Default

// SVG Değişenler:
fill: color.stroke  color.fill
stroke-width: 3  2

// Marker Değişenler:
zIndex: 1000  orderIndex
label.fontSize: 16px  14px

// Değişmeyenler:
scaledSize: (32, 32) (sabit)
anchor: (16, 16) (sabit)
animation: null (sabit)

CHECKLIST

Yeni marker feature eklerken kontrol et:

  • SVG marker kullandım (SymbolPath değil)
  • BOUNCE animation kullanmadım
  • hasCenteredRef map init'te set etmedim
  • Label font-size sabit (14px)
  • Places effect'inde cleanup yapmadım
  • scaledSize (32, 32) sabit kaldı
  • anchor (16, 16) sabit kaldı
  • Sadece fill ve stroke-width değiştirdim
  • Marker recreation yapmadım (has check)
  • Selective deletion kullandım
  • Visual update ayrı effect'te
  • Per-day polyline kullandım (tek polyline değil)
  • Polyline sıralama güvenli (Number.isFinite)
  • Polyline dependency optimize (places.length)
  • Polyline rengi getDayColor ile tutarlı

🚫 YASAKLAR

ASLA YAPMA

  1. SymbolPath kullanma

    path: google.maps.SymbolPath.CIRCLE, // ❌
    
  2. BOUNCE animation kullanma

    marker.setAnimation(google.maps.Animation.BOUNCE); // ❌
    
  3. hasCenteredRef map init'te set etme

    useEffect(() => {
      hasCenteredRef.current = true; // ❌
    }, [isScriptLoaded, places]);
    
  4. Label font-size değiştirme

    fontSize: state === 'default' ? '14px' : '16px', // ❌
    
  5. Places effect'inde cleanup yapma

    useEffect(() => {
      return () => { /* cleanup */ }; // ❌
    }, [places]);
    
  6. SVG boyutunu değiştirme

    const size = state === 'hover' ? 36 : 32; // ❌
    scaledSize: new google.maps.Size(size, size); // ❌
    
  7. Anchor değiştirme

    anchor: new google.maps.Point(16, state === 'hover' ? 18 : 16); // ❌
    
  8. Her effect'te marker creation yapma

    useEffect(() => {
      // Her effect'te yeni marker oluşturma ❌
    }, [hoveredPlaceId]);
    
  9. Tek polyline kullanma (tüm günler için)

    const polylineRef = useRef<google.maps.Polyline | null>(null); // ❌
    
  10. Polyline sıralama güvensiz yapma

    (a.orderIndex || 0) - (b.orderIndex || 0) // ❌
    
  11. Polyline dependency'de places kullanma

    }, [places, activeDayId, showPolyline]); // ❌
    

YAPILACAKLAR

HER ZAMAN YAP

  1. SVG marker kullan

    icon: createSvgMarkerIcon(dayIndex, state), // ✅
    
  2. hasCenteredRef fitBounds sonrası set et

    if (places.length > 0 && !hasCenteredRef.current) {
      map.fitBounds(bounds);
      hasCenteredRef.current = true; // ✅
    }
    
  3. Label font-size sabit tut

    fontSize: '14px', // ✅ SABİT
    
  4. Marker recreation check

    if (markersRef.current.has(place.id)) return; // ✅
    
  5. Selective deletion

    if (!currentPlaceIds.has(id)) {
      marker.setMap(null);
      markersRef.current.delete(id);
    }
    
  6. Visual update için setIcon kullan

    marker.setIcon(createSvgMarkerIcon(dayIndex, state)); // ✅
    
  7. Animation null tut

    marker.setAnimation(null); // ✅
    
  8. Cleanup sadece unmount'ta

    useEffect(() => {
      return () => { /* cleanup */ }; // ✅
    }, [isScriptLoaded, places]); // Map init effect
    
  9. Per-day polyline kullan

    const polylinesRef = useRef<Map<string, google.maps.Polyline>>(new Map()); // ✅
    
  10. Polyline sıralama güvenli yap

    const ordered = [...dayPlaces].sort((a, b) => {
      const ai = Number.isFinite(a.orderIndex) ? a.orderIndex! : 999;
      const bi = Number.isFinite(b.orderIndex) ? b.orderIndex! : 999;
      return ai - bi;
    }); // ✅
    
  11. Polyline dependency optimize et

    }, [places.length, activeDayId, showPolyline]); // ✅
    

🎯 ÖZET

11 Kritik Düzeltme:

GoogleMap Component (8 düzeltme):

  1. SVG marker kullanıldı → Pixel-perfect kontrol, sabit boyut/anchor
  2. BOUNCE kaldırıldı → Anchor oynatma YOK
  3. hasCenteredRef düzeltildi → fitBounds doğru çalışıyor
  4. Label font-size sabit → Mikro-jitter YOK
  5. Places cleanup kaldırıldı → Marker recreation YOK
  6. Per-day polyline eklendi → Her gün ayrı renkli rota
  7. Polyline sıralama güvenli → Rota her zaman doğru
  8. Polyline performance optimize → Gereksiz recreation YOK

TripPlanner Component (3 düzeltme): 9. allPlaces useMemo → Referans stabilitesi, 0 gereksiz işlem/saniye 10. orderIndex backend öncelikli → Drag/reorder sonrası marker sabit 11. activeDayId ilk gün otomatik → İlk yüklemede senkron visibility

Sonuç:

  • Jitter tamamen yok
  • Smooth transitions
  • Profesyonel görünüm (Wanderlog/Layla hissi)
  • Gün bazlı renkli rotalar
  • Yüksek performans (0 gereksiz işlem/saniye)
  • Stabil marker & polyline
  • Senkron visibility

📚 DAHA FAZLA BİLGİ

  • TripPlanner 3 kritik düzeltme: TRIPPLANNER_CRITICAL_FIXES.md
  • GoogleMap 4 kritik düzeltme: GOOGLEMAP_CRITICAL_FIXES.md
  • SVG marker detayları: GOOGLEMAP_SVG_POLYLINE.md
  • Özet: GOOGLEMAP_SUMMARY.md
  • Jitter düzeltmeleri: GOOGLEMAP_JITTER_FIX.md
  • Component mimarisi: GOOGLEMAP_ARCHITECTURE.md
  • Lifecycle düzeltmeleri: GOOGLEMAP_LIFECYCLE_FIX.md