সম্পত্তি অনুসন্ধান একটি স্থানগত সমস্যা। ক্রেতা এবং ভাড়াটিয়ারা পোস্টকোড এবং রাস্তার তালিকার পরিবর্তে আশেপাশের এলাকা, যাতায়াতের সময় এবং স্কুলের নৈকট্য সম্পর্কে চিন্তা করে। একটি ম্যাপ-প্রথম ইন্টারফেস মানুষের অনুসন্ধানের ধরনের সঙ্গে মানানসই, কারণ এটি ব্যবহারকারীদের মূল্য, বেডরুম বা অন্য কোনো বৈশিষ্ট্য দ্বারা ফিল্টার করার আগে তাদের অনুসন্ধানকে স্থানগতভাবে সংটুকরো করতে দেয়।
এই টিউটোরিয়াল একটি প্রোডাকশন-রেডি সম্পত্তি তালিকা ম্যাপ তৈরি করে: কম জুমে ক্লাস্টার করা মার্কার, স্বতন্ত্র মার্কারে মূল্য লেবেল, তালিকা বিবরণ সহ ক্লিক-টু-পপআপ, একটি পোস্টকোড অনুসন্ধান যা ম্যাপকে পুনরায় কেন্দ্রীভূত করে, এবং একটি মূল্য পরিসীমা স্লাইডার যা রিয়েল-টাইমে দৃশ্যমান তালিকাগুলি ফিল্টার করে। React উপাদানটি নিজে প্রায় ৬৫ লাইনের, আর লেয়ার ও ক্লিক হ্যান্ডলার সহ সম্পূর্ণ বিল্ড প্রায় ১৫০ লাইনে পৌঁছায়। একই যুক্তি ভ্যানিলা জাভাস্ক্রিপ্টে কাজ করে যদি আপনি কোনো ফ্রেমওয়ার্ক পছন্দ না করেন।
আপনি EU PropTech প্ল্যাটফর্মগুলির জন্য নির্দিষ্ট GDPR বাধ্যবাধকতার একটি নোটও পাবেন, কারণ সম্পত্তি অনুসন্ধান দ্বারা উত্পাদিত অবস্থান ডেটা ব্যক্তিগত ডেটা এবং এটি সেই অনুযায়ী পরিচালনা করা প্রয়োজন।
যদি আপনি ম্যাপ API গুলিতে নতুন হন, তবে আপনার ওয়েবসাইটে ইন্টারেক্টিভ ম্যাপ কীভাবে যোগ করবেন টিউটোরিয়াল এটি চয়ন করার আগে মৌলিক বিষয়গুলি কভার করে।
Property Data Structure
Each listing needs coordinates, a price, and enough metadata for the popup. Keep this as a GeoJSON FeatureCollection, it plugs directly into the map source without transformation.
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.
Setting Up the Map
Initialize the map with a central view over your property market. The MapAtlas Maps API is Mapbox GL JS-compatible, so the initialisation call is identical to what you would write for Mapbox, with a MapAtlas tile URL.
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
});
একটি উজ্জ্বল, কম-কনট্রাস্ট স্টাইল প্রপার্টি ম্যাপের জন্য বিশেষভাবে ভালো কাজ করে, হালকা বেস মূল্য লেবেল এবং রঙিন মার্কারগুলিকে দৃশ্যগত বিশৃঙ্খলা ছাড়াই আলাদা করে তোলে।
Clustering with Price Labels
The key UI pattern for a property map is showing price labels on individual markers and count bubbles on cluster circles. The cluster: true source option groups nearby points automatically. You add separate layers for clusters and individual markers.
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 Interactions
Clicking a cluster zooms the map to reveal its individual listings. Clicking an individual property opens a 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 = ''; });
Price Range Filter
দৃশ্যমান তালিকা ফিল্টার করে এমন একটি রেঞ্জ স্লাইডার প্রপার্টি ম্যাপের সবচেয়ে মূল্যবান UI সংযোজনগুলির একটি। Mapbox GL JS expression সিস্টেম আপনাকে নেটওয়ার্ক রাউন্ড-ট্রিপ ছাড়াই ক্লায়েন্ট-সাইডে একটি লেয়ারের ফিল্টার আপডেট করতে দেয়, ফিল্টারিং ইতিমধ্যে লোড হওয়া 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);
লক্ষ্য করুন যে ক্লাস্টারগুলি সোর্স ডেটা থেকে গণনা করা হয়, লেয়ার ফিল্টার থেকে নয়, তাই স্লাইডার যে তালিকাগুলি লুকিয়েছে সেগুলিও ক্লাস্টার গণনায় থেকে যায়। দুটিকে সিঙ্কে রাখতে, JavaScript-এ তালিকাগুলি ফিল্টার করুন এবং ফলাফলটি map.getSource('properties').setData()-এ পাঠান।
Address Search with the Geocoding API
Let users type a neighbourhood or postcode to recentre the map. The Geocoding API returns GeoJSON features, so coordinates drop straight into map.flyTo.
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());
});
For travel-time search, "show me properties within 20 minutes of this school", add an isochrone overlay using the MapAtlas Travel Time API. The Isochrone Maps Explained article shows how to fetch and display that polygon.
The Complete React Component
Wrapping everything above into a React component with proper lifecycle management:
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
});
// 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>
);
}
এটি উপাদানটির কাঠামো, মূল্য পরিসীমা স্লাইডার এবং লাইফসাইকেল ক্লিনআপ সহ প্রায় ৬৫ লাইন; এটি সম্পূর্ণ করতে উপরের বিভাগগুলি থেকে লেয়ার এবং ক্লিক হ্যান্ডলারের কোড যোগ করুন।
GDPR Notes for EU PropTech
যখন একজন সম্পত্তি সন্ধানকারী তাদের বাড়ির ঠিকানা টাইপ করে বা আপনার ম্যাপে "আমার অবস্থান ব্যবহার করুন" ক্লিক করে, তারা GDPR আর্টিকেল ৪(১) এর অধীনে ব্যক্তিগত ডেটা শেয়ার করছে, যা অবস্থান ডেটাকে একটি শনাক্তকারী হিসাবে উল্লেখ করে। সেই অবস্থান ডেটা প্রকাশ করে যে তারা কোথায় থাকে, যা তাদের সম্পর্কে অন্যান্য সংবেদনশীল বৈশিষ্ট্য অনুমান করতে পারে।
EU সম্মতির জন্য ব্যবহারিক পদক্ষেপ:
- ব্রাউজার থেকে সরাসরি Geocoding API কল করার পরিবর্তে আপনার ব্যাকএন্ডের মাধ্যমে জিওকোডিং অনুরোধগুলি রুট করুন। আপনার ব্যাকএন্ড ফরওয়ার্ড করার আগে সনাক্তকারী হেডার স্ট্রিপ করতে পারে।
- GDPR আর্টিকেল ৬ এর অধীনে একটি নথিভুক্ত আইনি ভিত্তি, যেমন সম্মতি, না থাকলে সেশনের বাইরে অনুসন্ধান স্থানাঙ্ক লগ বা স্থায়ী করবেন না।
- যদি আপনি "আমাকে বিজ্ঞপ্তি দিন যখন আমার কাছাকাছি একটি সম্পত্তি আমার মানদণ্ড মিলে", সংরক্ষিত অনুসন্ধান এলাকাকে নির্ধারিত ধরন সময়কাল সহ ব্যক্তিগত ডেটা হিসাবে বিবেচনা করুন।
EU Developer's Guide to GDPR-Compliant Map APIs এটি সম্পূর্ণভাবে কভার করে, প্রসেসর চুক্তি এবং ডেটা আবাস প্রয়োজনীয়তা সহ। MapAtlas ডেটা EU এর মধ্যে প্রক্রিয়া করে এবং সেই ডেটা প্রসেসিং চুক্তি প্রদান করে যা GDPR আর্টিকেল ২৮(৩) কন্ট্রোলার ও প্রসেসরের মধ্যে বাধ্যতামূলক করে।
Real Estate শিল্প সমাধান পৃষ্ঠা PropTech প্ল্যাটফর্মগুলি উত্পাদনে MapAtlas কীভাবে ব্যবহার করে তা সম্পর্কে অতিরিক্ত প্রসঙ্গ রয়েছে।
Next Steps
- একটি বিনামূল্যে MapAtlas API কী এর জন্য সাইন আপ করুন এবং বিনামূল্যে স্তর দিয়ে শুরু করুন
- ট্রাভেল টাইম API দিয়ে ট্রাভেল-টাইম ওভারলে যোগ করুন, ক্রেতাদের দেখান ২০ মিনিটে কী পৌঁছানো যায়
- আপনার সম্পত্তি প্ল্যাটফর্মের ভিজ্যুয়াল পরিচয় মেলাতে Maps API styling guide অন্বেষণ করুন
Frequently Asked Questions
Can I use MapAtlas for a property listing website?
হ্যাঁ। প্রপার্টি সাইটগুলির যা দরকার MapAtlas তা কভার করে: প্রপার্টি অনুসন্ধান ম্যাপ, বিনিয়োগ ড্যাশবোর্ড এবং ভাড়ার লিস্টিং ম্যাপ। Maps API Mapbox GL JS-সামঞ্জস্যপূর্ণ, তাই বিদ্যমান Mapbox-ভিত্তিক প্রপার্টি ম্যাপ কোড ন্যূনতম পরিবর্তনে মাইগ্রেট হয়।
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.

