Immobiliensuche ist ein raeumliches problem. Kaeufer und mieter denken in begriffen von nachbarschaften, pendelzeiten und naehe zu schulen, nicht postleitzahlen und strasse listen. Eine karten-erste schnittschelle konvertiert besser als eine listen-erste schnittschelle, da sie benutzern erlaubt, ihre suche raeumlich zu verankern, bevor nach preis, schlafzimmern oder jedem anderen attribut gefiltert wird.
Dieses tutorial erstellt eine produktionsreife immobilienauflistungs-karte: geclusterte marker bei niedrigem zoom, preis-lables auf einzelnen markern, click-to-popup mit auflistung-details, eine postleitzahl-suche, die die karte neu zentriert und ein preis-bereichs-schieberegler, der sichtbare auflistungen in echtzeit filtert. Die komplette react-komponenten kommt unter 100 zeilen. Die gleiche logik funktioniert in vanille javascript, wenn Sie kein framework bevorziehen.
Sie werden auch eine notiz ueber gdpr-verpflichtungen spezifisch fuer eu-proptech-plattformen finden, da die standortdaten, die durch immobiliensuchen generiert werden, persoenliche daten sind und entsprechend gehandhabt werden muessen.
Wenn Sie neu fuer kartenapi sind, das Wie man interaktive karten zu Ihrer website hinzufuegt tutorial behandelt die grundlagen, bevor dieses sich abhebt.
Immobilien-Datenstruktur
Jede auflistung benoetigt koordinaten, einen preis und genug metadaten fuer das popup. Halten sie dies als geojson FeatureCollection, es steckt direkt in die karten-quelle ohne umwandlung.
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"
}
}
]
};
Auf einer echten plattform kommt dieses array von Ihrer auflistungen-api, ein fetch ruf auf karten-belastung oder ein abfrage-parameter-wechsel.
Setting Up the Map
Initialisieren Sie die karte mit einer zentralen ansicht ueber Ihren immobilien-markt. Die MapAtlas Maps API ist Mapbox GL JS-komppatibel, also ist der initialisierungs-ruf identisch zu dem, das Sie fuer Mapbox schreiben wuerden, mit einer MapAtlas kachel-url.
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
});
Das Bright-style funktioniert besonders gut fuer immobilien-karten, die leichtere basis laesst preis-lables und gefaerbte marker ohne visuelle unordnung herausragen.
Clustering mit Preismarkierungen
Das schluesselelement-ui-muster fuer eine immobilien-karte ist die anzeige von preis-markierungen auf einzelnen markern und zaehler-blasen auf cluster-kreisen. Die cluster: true quellen-option gruppiert nearby punkte automatisch. Sie fuegen separate schichten fuer cluster und einzelne marker hinzu.
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
}
});
});
Click-Interaktionen
Beim klicken auf einen cluster zoomt die karte, um seine einzelnen auflistungen anzuzeigen. Das klicken auf eine einzelne eigenschaft oeffnet ein detail-popup.
// Zoom into clicked cluster
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 });
}
);
});
// Detail popup for individual listing
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()}/mo`
: `€${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} bed · ${sqm} m²</p>
<a href="/listings/${id}" style="color:#2563EB;font-weight:600">View listing →</a>
</div>
`)
.addTo(map);
});
// Change cursor on hover
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 = ''; });
Preis-Bereichs-Filter
Ein bereichs-schieberegler, der sichtbare auflistungen filtert, ist einer der hoechsten-wert ui-zusaetze fuer eine immobilien-karte. Das Mapbox GL JS-ausdrucks-system laesst Sie ein quellen-filter client-seite aktualisieren, ohne einen netzwerk-round-trip, das filtern passiert im browser auf dem bereits-geladenen geojson.
function applyPriceFilter(min, max) {
const filter = ['all',
['!', ['has', 'point_count']],
['>=', ['get', 'price'], min],
['<=', ['get', 'price'], max]
];
map.setFilter('property-price', filter);
}
// Wire up range inputs
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);
Beachten Sie, dass die cluster-schicht sich automatisch aktualisiert, wenn die zugrunde liegende quelle sich aendert, eigenschaften, die aus dem einzelnen marker-schicht gefiltert werden, fallen auch aus den cluster-zaehle.
Adress-Suche mit der Geocoding API
Lassen sie benutzer ein nachbarschaft oder postleitzahl eingeben, um die karte neu zu zentrieren. Die Geocoding API gibt geojson-funktionen zurueck, also koordinaten fallen direkt in map.flyTo.
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());
});
Fuer reisezeit-suche, 'zeige mir eigenschaften innerhalb von 20 minuten dieser schule', fuegen Sie eine isochrone-ueberablagerung mit der MapAtlas Travel Time API hinzu. Der Isochrone Maps Explained artikel zeigt, wie das polygon zu holen und zu anzeigen.
Die komplette React-Komponente
Das einhuellen von allem oben in eine react-komponenten mit ordnungsabhaengigen lebeszyklus-verwaltung:
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
});
// Add cluster, cluster-label, property-price layers (see above)
// Add click handlers (see above)
});
return () => map.remove();
}, [apiKey]);
// Update filter when price range slider changes
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>
);
}
Das ist die komplette komponente, unter 100 zeilen einschliesslich der preis-bereichs-schieberegler und lebeszyklus-bereinigung.
GDPR-Anmerkungen fuer EU-PropTech
Wenn ein immobilien-sucher ihre heimat-adresse oder klicks 'verwenden meine lage' auf Ihrer karte eingibt, gibt es persoenliche daten gemaess gdpr-artikel 4 teil. Diese standortdaten zeigt, wo sie leben, die kann in andere sensible attribute ueber sie ableiten.
Praktische schritte fuer eu-konformitaet:
- Route geocoding anfragen durch Ihren backend anstatt die geocoding api direkt aus dem browser zu nennen. Ihr backend kann identifizierende kopfzeilen vor dem weiterleiten streifen.
- Protokollieren oder behaendigen sie keine search-koordinaten ueber die sitzung, wenn Sie nicht eine dokumentierte rechtsgrundlage haben und eine spezifische zustimmung gesammelt haben.
- Wenn Sie 'benachrichtige mich, wenn eine eigenschaft in meiner naehe meine kriterien erfuellt' implementieren, behaendigen Sie den gespeicherten search-bereich als persoenliche daten mit einem definierten aufbewahrungszeitraum.
Der EU Developer's Guide to GDPR-Compliant Map APIs behandelt dies komplett, einschliesslich prozessor-vereinbarungen und daten-residenzverforderungen. MapAtlas verarbeitet alle daten innerhalb der eu und bietet die daten-verarbeitungs-vereinbarung erfordert gemaess gdpr-artikel 28.
Die Real Estate industry solution page hat zusaetzlichen kontext auf wie proptech-plattformen mapAtlas in produktion nutzen.
Nächste Schritte
- Melden Sie sich fuer einen kostenlosen MapAtlas API-Schluessel an und beginnen Sie mit dem freien tier
- Fuegen Sie reisezeit-ueberablagerungen mit der Travel Time API hinzu, zeigen Sie kaeufer was innerhalb von 20 minuten erreichbar ist
- Erkunden Sie den Maps API styling guide, um Ihre immobilien-plattform visuelle identitaet abzugleichen
Häufig gestellte Fragen
Kann ich MapAtlas fuer eine immobilienauflistungs-website verwenden?
Ja. MapAtlas wird von proptech-plattformen ueberall in der eu fuer immobilien-such-karten, investitions-dashboards und mieterauflistungs-seiten verwendet. Die Maps API ist Mapbox GL JS-kompatibel, so bestehende mapbox-basiierte immobilien-karten-code migriert mit minimalen aenderungen.
Wie funktioniert marker-clustering fuer immobilien-karten?
Clustering gruppiert nearby immobilien-marker in einen einzelnen kreis, der die zaehler zeigt, wenn die karte heraus-zoomiert wird. Wenn der benutzer herein-zoomiert, cluster teilen sich in einzelne marker. MapAtlas staettet geojson-quellen-clustering intern, stellen Sie cluster: true auf der quelle und das sdk handhabt den rest.
Unterliegen standortdaten von immobilien-suchern GDPR?
Ja. Wenn ein benutzer nach ihrer heimat-adresse oder klicks 'verwenden meine lage' auf einer immobilien-seite sucht, dies besteht sich persoenliche daten gemaess gdpr-artikel 4. EU-proptech-plattformen sollten geocoding-anfragen durch einen backend-proxy leiten und benoetigen daten-minimierungs- und aufbewahrungsrichtlinien anwenden, anstatt client-seite api-rufe zu machen, die benutzer-standorte zu dritten-anbieter-servern protokollieren.

