Mülk arama mekansal bir problemdir. Alıcılar ve kiracılar posta kodları ve sokak listeleri açısından değil, mahalleler, işe gidiş saatleri ve okullar yakınlığı açısından düşünürler. Harita öncelikli bir arayüz insanların arama biçimine uyar, çünkü kullanıcıların fiyat, yatak odası veya başka bir özelliğe göre filtrelemeden önce aramalarını harita üzerinde bir bölgeye sabitlemesini sağlar.
Bu öğretici, üretime hazır bir mülk listeleme haritası oluşturur: düşük yakınlaştırmada kümelenmiş işaretçiler, bireysel işaretçilerde fiyat etiketleri, listeleme ayrıntılarıyla tıkla açılır, haritayı yeniden ortalayan posta kodu araması ve görünür listemeleri gerçek zamanlı olarak filtreleyen fiyat aralığı kaydırıcısı. React bileşeninin kendisi yaklaşık 65 satırdır; katmanlar ve tıklama işleyicileriyle birlikte tam yapı yaklaşık 150 satıra ulaşır. Çerçeve tercih etmezseniz aynı mantık sade JavaScript'te çalışır.
Mülk aramaları tarafından üretilen konum verileri kişisel verisi olduğundan ve buna göre işlenmesi gerektiğinden, EU PropTech platformlarına özgü GDPR yükümlülükleri ile ilgili bir not da bulacaksınız.
Harita API'lerine yeni iseniz, Web Sitenize İnteraktif Haritalar Nasıl Eklenir öğretimi bu konu ile başlamadan önce temelleri kapsar.
Mülk Veri Yapısı
Her listelemenin koordinatları, bir fiyatı ve açılır pencere için yeterli meta verileri olması gerekir. Bunu GeoJSON FeatureCollection olarak tutun, dönüştürme olmadan doğrudan harita kaynağına takılır.
const properties = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.8952, 52.3702] },
properties: {
id: "prop-001",
price: 485000,
bedrooms: 3,
sqm: 112,
address: "Keizersgracht 142, Amsterdam",
type: "apartment",
status: "for-sale"
}
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.9123, 52.3601] },
properties: {
id: "prop-002",
price: 1250,
bedrooms: 2,
sqm: 78,
address: "Sarphatistraat 58, Amsterdam",
type: "apartment",
status: "for-rent"
}
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.8801, 52.3780] },
properties: {
id: "prop-003",
price: 720000,
bedrooms: 4,
sqm: 195,
address: "Herengracht 380, Amsterdam",
type: "house",
status: "for-sale"
}
}
]
};
Gerçek bir platformda bu dizi ilan API'nizden gelir: harita yüklendiğinde veya bir sorgu parametresi değiştiğinde yapılan bir fetch çağrısıyla.
Haritayı Ayarlama
Mülk pazarınıza merkezi bir görünüm ile haritayı başlatın. MapAtlas Harita API'si Mapbox GL JS ile uyumlu, bu nedenle başlatma çağrısı MapAtlas döşeme URL'si ile Mapbox için yazacağınız şey ile aynıdır.
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const map = new mapmetricsgl.Map({
container: 'map',
style: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/style.json&token=YOUR_API_KEY',
center: [4.9041, 52.3676],
zoom: 13
});
Açık renkli, düşük kontrastlı bir stil mülk haritaları için özellikle iyi çalışır, daha açık taban fiyat etiketlerinin ve renkli işaretçilerin görsel karışıklık olmadan öne çıkmasını sağlar.
Fiyat Etiketleri ile Kümeleme
Mülk haritası için anahtar kullanıcı arayüzü deseni, bireysel işaretçilerde fiyat etiketlerini ve küme dairelerinde sayı kabarcıklarını göstermektir. cluster: true kaynak seçeneği yakındaki noktaları otomatik olarak gruplandırır. Kümeler ve bireysel işaretçiler için ayrı katmanlar eklersiniz.
map.on('load', () => {
map.addSource('properties', {
type: 'geojson',
data: properties,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 60
});
// Cluster circles, size scales with listing count
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'properties',
filter: ['has', 'point_count'],
paint: {
'circle-color': '#2563EB',
'circle-radius': [
'step', ['get', 'point_count'],
22, 5, 30, 20, 38
],
'circle-stroke-width': 3,
'circle-stroke-color': '#ffffff'
}
});
// Cluster count labels
map.addLayer({
id: 'cluster-label',
type: 'symbol',
source: 'properties',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': 14
},
paint: { 'text-color': '#ffffff' }
});
// Individual property markers, white circle with price text
map.addLayer({
id: 'property-price',
type: 'symbol',
source: 'properties',
filter: ['!', ['has', 'point_count']],
layout: {
'text-field': [
'concat',
'€',
['to-string', ['round', ['/', ['get', 'price'], 1000]]],
'k'
],
'text-font': ['DIN Offc Pro Bold', 'Arial Unicode MS Bold'],
'text-size': 12
},
paint: {
'text-color': '#1e293b',
'text-halo-color': '#ffffff',
'text-halo-width': 2
}
});
});
Tıklama Etkileşimleri
Bir kümeyi tıklamak haritayı yakınlaştırarak bireysel listelemeleri ortaya çıkarır. Bireysel bir mülkü tıklamak bir detay açılır penceresini açar.
// Tıklanan kümeye yakınlaşma
map.on('click', 'clusters', (e) => {
const [feature] = map.queryRenderedFeatures(e.point, { layers: ['clusters'] });
map.getSource('properties').getClusterExpansionZoom(
feature.properties.cluster_id,
(err, zoom) => {
if (!err) map.easeTo({ center: feature.geometry.coordinates, zoom });
}
);
});
// Bireysel listeleme için detay açılır penceresi
map.on('click', 'property-price', (e) => {
const { address, price, bedrooms, sqm, status, id } = e.features[0].properties;
const coords = e.features[0].geometry.coordinates.slice();
const formattedPrice = status === 'for-rent'
? `€${price.toLocaleString()}/ay`
: `€${price.toLocaleString()}`;
new mapmetricsgl.Popup({ offset: 10 })
.setLngLat(coords)
.setHTML(`
<div style="min-width:200px">
<strong style="font-size:15px">${formattedPrice}</strong>
<p style="margin:4px 0">${address}</p>
<p style="margin:4px 0;color:#64748b">${bedrooms} yatak · ${sqm} m²</p>
<a href="/listings/${id}" style="color:#2563EB;font-weight:600">Listeyi görüntüle →</a>
</div>
`)
.addTo(map);
});
// Üzerine gelirken imleci değiştirir
map.on('mouseenter', 'property-price', () => { map.getCanvas().style.cursor = 'pointer'; });
map.on('mouseleave', 'property-price', () => { map.getCanvas().style.cursor = ''; });
map.on('mouseenter', 'clusters', () => { map.getCanvas().style.cursor = 'pointer'; });
map.on('mouseleave', 'clusters', () => { map.getCanvas().style.cursor = ''; });
Fiyat Aralığı Filtresi
Görünür listemeleri filtreleyen aralık kaydırıcısı, mülk haritası için en yüksek değerli kullanıcı arayüzü eklemelerinden biridir. Mapbox GL JS ifade sistemi, bir katmanın filtresini ağ isteği olmadan istemci tarafında güncellemenizi sağlar; filtreleme zaten yüklenen GeoJSON'da tarayıcıda gerçekleşir.
function applyPriceFilter(min, max) {
const filter = ['all',
['!', ['has', 'point_count']],
['>=', ['get', 'price'], min],
['<=', ['get', 'price'], max]
];
map.setFilter('property-price', filter);
}
// Aralık girişlerini bağlayın
const minInput = document.getElementById('price-min');
const maxInput = document.getElementById('price-max');
function onRangeChange() {
applyPriceFilter(Number(minInput.value), Number(maxInput.value));
}
minInput.addEventListener('input', onRangeChange);
maxInput.addEventListener('input', onRangeChange);
Kümelerin katman filtrelerinden değil, kaynak verilerinden hesaplandığını unutmayın: bu nedenle küme sayıları, kaydırıcının gizlediği ilanları da saymaya devam eder. İkisini senkron tutmak için ilanları JavaScript'te filtreleyin ve sonucu map.getSource('properties').setData() fonksiyonuna aktarın.
Jeokodlama API'si ile Adres Araması
Kullanıcıların haritayı yeniden ortalamak için bir mahalle veya posta kodu yazmasına izin verin. Jeokodlama API'si GeoJSON özelliklerini döndürür, böylece koordinatlar doğrudan map.flyToe düşer.
async function searchArea(query) {
const url = new URL('https://gateway.mapmetrics-atlas.net/forward-geocode/');
url.searchParams.set('text', query);
url.searchParams.set('token', 'YOUR_API_KEY');
url.searchParams.set('size', '1');
const res = await fetch(url);
const data = await res.json();
if (!data.features.length) return;
const [lng, lat] = data.features[0].geometry.coordinates;
map.flyTo({ center: [lng, lat], zoom: 13 });
}
document.getElementById('area-search').addEventListener('keydown', (e) => {
if (e.key === 'Enter') searchArea(e.target.value.trim());
});
Seyahat süresi araması için "bu okula 20 dakika içinde ulaşılabilecek mülkleri göster", MapAtlas Travel Time API'sini kullanarak bir isokron katmanı ekleyin. İsokron Haritaları Açıklanan makale bu çokgeni getirmeyi ve görüntülemeyi nasıl yapacağını gösterir.
Tam React Bileşeni
Yukarıdaki her şeyi uygun yaşam döngüsü yönetimi ile bir React bileşenine sarmalama:
import { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
export function PropertyMap({ listings, apiKey }) {
const containerRef = useRef(null);
const mapRef = useRef(null);
const [priceRange, setPriceRange] = useState([0, 2000000]);
useEffect(() => {
const map = new mapmetricsgl.Map({
container: containerRef.current,
style: `https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/style.json&token=${apiKey}`,
center: [4.9041, 52.3676],
zoom: 13
});
mapRef.current = map;
map.on('load', () => {
map.addSource('properties', {
type: 'geojson',
data: { type: 'FeatureCollection', features: listings },
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 60
});
// Küme, küme etiketi, mülk fiyatı katmanları ekle (yukarıya bakın)
// Tıklama işleyicileri ekle (yukarıya bakın)
});
return () => map.remove();
}, [apiKey]);
// Fiyat aralığı kaydırıcısı değiştiğinde filtreyi güncelleyin
useEffect(() => {
const map = mapRef.current;
if (!map || !map.getLayer('property-price')) return;
map.setFilter('property-price', [
'all',
['!', ['has', 'point_count']],
['>=', ['get', 'price'], priceRange[0]],
['<=', ['get', 'price'], priceRange[1]]
]);
}, [priceRange]);
return (
<div>
<div style={{ padding: '12px 0', display: 'flex', gap: 12 }}>
<label>
Min €
<input type="range" min={0} max={2000000} step={10000}
value={priceRange[0]}
onChange={e => setPriceRange([+e.target.value, priceRange[1]])} />
</label>
<label>
Max €
<input type="range" min={0} max={2000000} step={10000}
value={priceRange[1]}
onChange={e => setPriceRange([priceRange[0], +e.target.value])} />
</label>
</div>
<div ref={containerRef} style={{ width: '100%', height: '600px' }} />
</div>
);
}
Bu, bileşenin iskeletidir: fiyat aralığı kaydırıcısı ve yaşam döngüsü temizliği dahil yaklaşık 65 satır. Tamamlamak için yukarıdaki bölümlerdeki katman ve tıklama işleyicisi kodunu ekleyin.
EU PropTech için GDPR Notları
Bir mülk arayanı ev adresini yazarsa veya haritanızda "konumumu kullan" düğmesini tıklarsa, konum verisini bir tanımlayıcı olarak sayan GDPR Madde 4(1) kapsamında kişisel veri paylaşmış olur. Bu konum verisi nerede yaşadıklarını ortaya koymakta ve hakkında diğer hassas nitelikleri çıkarmaya yol açabilir.
AB uyumluluğu için pratik adımlar:
- Jeokodlama isteklerini tarayıcıdan doğrudan Jeokodlama API'sini çağırmak yerine arka uçunuz aracılığıyla yönlendirin. Arka ucunuz iletmeden önce tanımlayıcı başlıkları soyabilir.
- GDPR Madde 6 kapsamında belgelenmiş bir hukuki dayanağınız (örneğin rıza) yoksa, arama koordinatlarını oturum süresinden sonra kaydetmeyin veya saklamayın.
- "Benim yakınımda kriterlerimi eşleşen bir mülk olduğunda bana bildir" uygularsanız, depolanan arama alanını belirli bir saklama süresi ile kişisel veri olarak ele alın.
GDPR Uyumlu Harita API'lerine EU Geliştirici Rehberi işlemci sözleşmeleri ve veri kalıntısı gereksinimleri de dahil olmak üzere bunu tam olarak kapsar. MapAtlas verileri AB içinde işler ve GDPR Madde 28(3)'ün veri sorumlusu ile veri işleyen arasında zorunlu kıldığı Veri İşleme Sözleşmesini sağlar.
Gayrimenkul sektörü çözüm sayfası PropTech platformlarının üretimde MapAtlas'ı nasıl kullandığında ek bağlam sunmuştur.
Sonraki Adımlar
- Ücretsiz bir MapAtlas API anahtarı için kaydolun ve ücretsiz katmanla başlayın
- Travel Time API ile seyahat süresi katmanları ekleyin, alıcılara 20 dakikada nerelere ulaşabileceklerini gösterin
- Mülk platformunuzun görsel kimliğine uyması için Maps API stil rehberini inceleyin
Sıkça Sorulan Sorular
MapAtlas'ı bir emlak ilan sitesi için kullanabilir miyim?
Evet. MapAtlas, emlak sitelerinin ihtiyaç duyduğu şeyleri karşılar: mülk arama haritaları, yatırım panoları ve kiralık ilan haritaları. Maps API, Mapbox GL JS uyumlu olduğundan Mapbox tabanlı mevcut mülk haritası kodu çok az değişiklikle taşınır.
Emlak haritalarında işaretçi kümeleme nasıl çalışır?
Kümeleme, harita uzaklaştırıldığında yakın mülk işaretçilerini sayıyı gösteren tek bir dairede toplar. Kullanıcı yakınlaştırdıkça kümeler tek tek işaretçilere ayrılır. MapAtlas GeoJSON kaynak kümelemesini yerleşik olarak destekler: kaynakta cluster: true ayarlayın, gerisini SDK halleder.
Mülk arayanların konum verileri GDPR kapsamında mı?
Evet. Kullanıcı bir emlak sitesinde ev adresine veya mevcut konumuna göre arama yaptığında bu, GDPR Madde 4 kapsamında kişisel veridir. AB'deki PropTech platformları, kullanıcı konumlarını üçüncü taraf sunuculara kaydeden istemci tarafı API çağrıları yapmak yerine geocoding isteklerini bir backend proxy üzerinden yönlendirmeli ve uygun veri minimizasyonu ile saklama politikaları uygulamalıdır.
Sıkça Sorulan Sorular
MapAtlas'ı bir emlak ilan sitesi için kullanabilir miyim?
Evet. MapAtlas, emlak sitelerinin ihtiyaç duyduğu şeyleri karşılar: mülk arama haritaları, yatırım panoları ve kiralık ilan haritaları. Maps API, Mapbox GL JS uyumlu olduğundan Mapbox tabanlı mevcut mülk haritası kodu çok az değişiklikle taşınır.
Emlak haritalarında işaretçi kümeleme nasıl çalışır?
Kümeleme, harita uzaklaştırıldığında yakın mülk işaretçilerini sayıyı gösteren tek bir dairede toplar. Kullanıcı yakınlaştırdıkça kümeler tek tek işaretçilere ayrılır. MapAtlas GeoJSON kaynak kümelemesini yerleşik olarak destekler: kaynakta cluster: true ayarlayın, gerisini SDK halleder.
Mülk arayanların konum verileri GDPR kapsamında mı?
Evet. Kullanıcı bir emlak sitesinde ev adresine veya mevcut konumuna göre arama yaptığında bu, GDPR Madde 4 kapsamında kişisel veridir. AB'deki PropTech platformları, kullanıcı konumlarını üçüncü taraf sunuculara kaydeden istemci tarafı API çağrıları yapmak yerine geocoding isteklerini bir backend proxy üzerinden yönlendirmeli ve uygun veri minimizasyonu ile saklama politikaları uygulamalıdır.

