Skip to main content

Multiple Markers as Layer

Render hundreds or thousands of markers efficiently using a symbol layer with dynamic icons based on categories — far more performant than individual Marker objects.


Quick Usage

map.addLayer({
id: "poi-layer",
type: "symbol",
source: "pois",
layout: {
"icon-image": ["get", "icon"], // Dynamic icon from properties
"icon-size": 0.09,
},
});

Full Working Example

<!DOCTYPE html>
<html lang="en">
<head>
<title>Multiple Markers as Layer - Barikoi GL</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

<!-- Barikoi GL -->
<link
rel="stylesheet"
href="https://unpkg.com/bkoi-gl@latest/dist/style/bkoi-gl.css"
/>
<script src="https://unpkg.com/bkoi-gl@latest/dist/iife/bkoi-gl.js"></script>

<style>
body {
margin: 0;
padding: 0;
}
html,
body,
#map {
height: 100%;
}

.legend {
position: absolute;
bottom: 30px;
left: 20px;
background: white;
padding: 16px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
font-family: system-ui, sans-serif;
z-index: 10;
max-width: 180px;
}
.legend h4 {
margin: 0 0 12px 0;
font-size: 14px;
color: #333;
}
.legend-item {
display: flex;
align-items: center;
margin: 8px 0;
font-size: 13px;
}
.legend-item img {
width: 28px;
height: 28px;
margin-right: 10px;
}
</style>
</head>
<body>
<div id="map"></div>

<div class="legend">
<h4>Location Types</h4>
<div class="legend-item">
<img src="https://cdn-icons-png.flaticon.com/512/3448/3448609.png" />
Restaurant
</div>
<div class="legend-item">
<img src="https://cdn-icons-png.flaticon.com/512/2933/2933921.png" />
Hotel
</div>
<div class="legend-item">
<img src="https://cdn-icons-png.flaticon.com/512/3514/3514491.png" />
Shop
</div>
<div class="legend-item">
<img src="https://cdn-icons-png.flaticon.com/512/4298/4298087.png" />
Office
</div>
<div class="legend-item">
<img src="https://cdn-icons-png.flaticon.com/512/2785/2785482.png" />
Hospital
</div>
</div>

<script>
const map = new bkoigl.Map({
container: "map",
style: "https://map.barikoi.com/styles/barikoi-light/style.json",
center: [90.4125, 23.8103], // Dhaka
zoom: 12,
accessToken: "YOUR_BARIKOI_API_KEY",
});

const icons = {
restaurant: "https://cdn-icons-png.flaticon.com/512/3448/3448609.png",
hotel: "https://cdn-icons-png.flaticon.com/512/2933/2933921.png",
shop: "https://cdn-icons-png.flaticon.com/512/3514/3514491.png",
office: "https://cdn-icons-png.flaticon.com/512/4298/4298087.png",
hospital: "https://cdn-icons-png.flaticon.com/512/2785/2785482.png",
};

function generateRandomPoints(count = 200) {
const categories = Object.keys(icons);
const features = [];
const center = [90.4125, 23.8103];
const radius = 0.18;

for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * radius;
const category =
categories[Math.floor(Math.random() * categories.length)];

features.push({
type: "Feature",
properties: {
id: i,
name: `${category.charAt(0).toUpperCase() + category.slice(1)} #${
i + 1
}`,
category: category,
icon: `${category}-icon`,
},
geometry: {
type: "Point",
coordinates: [
center[0] + Math.cos(angle) * distance,
center[1] + Math.sin(angle) * distance,
],
},
});
}
return features;
}

map.on("load", async () => {
// Load all icons
await Promise.all(
Object.entries(icons).map(async ([name, url]) => {
const res = await map.loadImage(url);
map.addImage(`${name}-icon`, res.data);
})
);

// Add GeoJSON source
map.addSource("pois", {
type: "geojson",
data: {
type: "FeatureCollection",
features: generateRandomPoints(250),
},
});

// Add symbol layer
map.addLayer({
id: "pois-layer",
type: "symbol",
source: "pois",
layout: {
"icon-image": ["get", "icon"],
"icon-size": 0.09,
"icon-allow-overlap": false,
"icon-ignore-placement": false,
"icon-anchor": "bottom",
},
});

// Click: Show popup
map.on("click", "pois-layer", (e) => {
const props = e.features[0].properties;
const coords = e.features[0].geometry.coordinates;

new bkoigl.Popup({ offset: 30 })
.setLngLat(coords)
.setHTML(
`
<strong>${props.name}</strong><br>
<small style="color:#666">Category: ${props.category}</small>
`
)
.addTo(map);
});

// Hover cursor
map.on(
"mouseenter",
"pois-layer",
() => (map.getCanvas().style.cursor = "pointer")
);
map.on(
"mouseleave",
"pois-layer",
() => (map.getCanvas().style.cursor = "")
);
});
</script>
</body>
</html>

Why Use Symbol Layers?

MethodPerformanceBest For
new bkoigl.Marker()Slow (1–100)Few markers with rich popups
Symbol Layer (this)Fast (100–10,000+)Large datasets, clustering, filtering

Symbol layers are 10–50x faster than individual markers.


Key Layout Properties

PropertyValue ExamplePurpose
"icon-image"["get", "icon"]Dynamic icon from feature properties
"icon-size"0.09Scale (0.05 = small, 0.15 = large)
"icon-allow-overlap"falsePrevent stacking
"icon-anchor""bottom"Align pin tip to coordinate

Advanced: Filter by Category

// Show only restaurants
map.setFilter("pois-layer", ["==", ["get", "category"], "restaurant"]);

// Clear filter
map.setFilter("pois-layer", null);

Advanced: Dynamic Icon Size by Zoom

"icon-size": [
"interpolate",
["linear"],
["zoom"],
10, 0.06,
15, 0.12
]

Scale to thousands — effortlessly!