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. Bir harita öncelikli arayüz, fiyat, yatak odası veya başka herhangi bir özniteliğine göre filtrelemeden önce kullanıcıların aramasını mekansal olarak sabitlemesine izin verdiği için liste öncelikli arayüzden daha iyi dönüştürülür.
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ı. Tam React bileşeni 100 satırdan az gelir. Ç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"
}
}
]
};
In a real platform this array comes from your listings API, a fetch call on map load or a query parameter change.
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://tiles.mapatlas.eu/styles/bright/style.json?key=YOUR_API_KEY',
center: [4.9041, 52.3676],
zoom: 13
});
Bright stili 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 ifadesi sistemi, bir kaynağın filtresini ağ turunda olmadan istemci tarafında güncellemesine izin verir; 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);
Temel kaynak değiştiğinde küme katmanının otomatik olarak güncellendiğini, bireysel işaretçi katmanından filtrelenen özelliklerin de küme sayılarından çıktığını unutmayı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://api.mapatlas.eu/geocoding/v1/search');
url.searchParams.set('text', query);
url.searchParams.set('key', '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://tiles.mapatlas.eu/styles/bright/style.json?key=${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>
);
}
Tam bileşen budur, fiyat aralığı kaydırıcısı ve yaşam döngüsü temizliği dahil 100 satırın altında.
EU PropTech için GDPR Notları
Bir mülk arayanı ev adresini yazarsa veya haritanızda "konumumu kullan" düğmesini tıklarsa, GDPR Madde 4'ün altında kişisel veriler paylaşıyor. 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.
- Dokumentali yasal bir temel yoksa ve belirli rıza toplamadığınız sürece oturum ötesinde arama koordinatlarını günlüğe almayın veya devam etmeyin.
- "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 tüm verileri AB içinde işler ve GDPR Madde 28'in gerektirdiği 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.
Next Steps
- Sign up for a free MapAtlas API key and start with the free tier
- Add travel-time overlays with the Travel Time API, show buyers what is reachable in 20 minutes
- Explore the Maps API styling guide to match your property platform's visual identity
Frequently Asked Questions
Can I use MapAtlas for a property listing website?
Yes. MapAtlas is used by PropTech platforms across the EU for property search maps, investment dashboards, and rental listing sites. The Maps API is Mapbox GL JS-compatible, so existing Mapbox-based property map code migrates with minimal changes.
How does marker clustering work for real estate maps?
Clustering groups nearby property markers into a single circle showing the count when the map is zoomed out. As the user zooms in, clusters split into individual markers. MapAtlas supports GeoJSON source clustering natively, set cluster: true on the source and the SDK handles the rest.
Is location data from property searchers subject to GDPR?
Yes. When a user searches by their home address or current location on a property site, that constitutes personal data under GDPR Article 4. EU PropTech platforms should route geocoding requests through a backend proxy and apply appropriate data minimisation and retention policies rather than making client-side API calls that log user locations to third-party servers.

