При работе с картографическими данными часто возникает необходимость показывать пользователю не только сами точки на карте, но и их количество. Такая информация помогает быстро оценить объём данных, контролировать результаты фильтрации, отображать статистику по категориям объектов и информировать о количестве элементов в текущей области просмотра.
В MapLibre GL JS количество точек может вычисляться несколькими способами:
Выбор подхода зависит от структуры приложения и объёма отображаемой информации.
Предположим, имеется набор точек в формате GeoJSON.
const points = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "Point",
coordinates: [37.6176, 55.7558]
},
properties: {
name: "Москва"
}
},
{
type: "Feature",
geometry: {
type: "Point",
coordinates: [30.3141, 59.9386]
},
properties: {
name: "Санкт-Петербург"
}
},
{
type: "Feature",
geometry: {
type: "Point",
coordinates: [49.1221, 55.7887]
},
properties: {
name: "Казань"
}
}
]
};
Добавление источника и слоя:
map.on("load", () => {
map.addSource("cities", {
type: "geojson",
data: points
});
map.addLayer({
id: "cities-layer",
type: "circle",
source: "cities",
paint: {
"circle-radius": 6,
"circle-color": "#1976d2"
}
});
});
Если все точки уже находятся в объекте GeoJSON, самый простой способ
— использовать массив features.
const count = points.features.length;
console.log(count);
Результат:
3
Такой вариант подходит для:
Вывод количества в HTML:
<div id="counter"></div>
document.getElementById("counter").textContent =
`Количество точек: ${points.features.length}`;
Иногда данные загружаются с сервера.
fetch("/data/cities.geojson")
.then(response => response.json())
.then(data => {
map.getSource("cities").setData(data);
const count = data.features.length;
document.getElementById("counter").textContent =
`Количество точек: ${count}`;
});
В этом случае количество вычисляется сразу после получения GeoJSON.
Если необходимо получить данные непосредственно из источника MapLibre
GL JS, используется метод getSource().
const source = map.getSource("cities");
Однако объект источника не предоставляет универсальный публичный метод для чтения исходного GeoJSON обратно из карты. Поэтому обычно данные сохраняются отдельно.
Пример:
let citiesData = points;
map.addSource("cities", {
type: "geojson",
data: citiesData
});
Позже:
const count = citiesData.features.length;
Подобная практика считается наиболее надёжной при работе с большими приложениями.
Очень распространённая задача — определить, сколько объектов находится внутри видимой части карты.
Для этого используются текущие границы карты.
Получение границ:
const bounds = map.getBounds();
Затем производится проверка каждой точки.
function countVisiblePoints(data) {
const bounds = map.getBounds();
return data.features.filter(feature => {
const [lng, lat] = feature.geometry.coordinates;
return bounds.contains([lng, lat]);
}).length;
}
Использование:
const visibleCount = countVisiblePoints(points);
console.log(visibleCount);
Количество объектов должно меняться при панорамировании и масштабировании.
function updateCounter() {
const count = countVisiblePoints(points);
document.getElementById("counter").textContent =
`Видимых точек: ${count}`;
}
Подписка на события:
map.on("moveend", updateCounter);
map.on("zoomend", updateCounter);
После каждого изменения положения карты информация будет обновляться автоматически.
MapLibre GL JS позволяет получать объекты, реально отрисованные на карте.
Для этого используется метод:
map.queryRenderedFeatures();
Подсчёт объектов слоя:
const features = map.queryRenderedFeatures({
layers: ["cities-layer"]
});
const count = features.length;
Полный пример:
function updateRenderedCount() {
const features = map.queryRenderedFeatures({
layers: ["cities-layer"]
});
document.getElementById("counter").textContent =
`Отображается объектов: ${features.length}`;
}
Преимущества метода:
Недостаток заключается в том, что объект может присутствовать в источнике данных, но не попасть в выборку, если сейчас не отображается.
Предположим, имеются категории объектов.
properties: {
category: "capital"
}
Устанавливается фильтр:
map.setFilter("cities-layer", [
"==",
["get", "category"],
"capital"
]);
Для подсчёта отображаемых объектов удобно использовать:
const features = map.queryRenderedFeatures({
layers: ["cities-layer"]
});
const count = features.length;
Количество автоматически будет соответствовать текущему фильтру.
Метод querySourceFeatures() работает напрямую с
источником данных.
const features = map.querySourceFeatures("cities");
console.log(features.length);
Отличия от queryRenderedFeatures():
| Метод | Учитывает экран | Учитывает стиль |
|---|---|---|
| queryRenderedFeatures | Да | Да |
| querySourceFeatures | Нет | Нет |
Поэтому второй метод полезен для получения полного количества объектов независимо от того, отображаются они сейчас или нет.
Иногда счётчик удобнее разместить поверх карты.
HTML:
<div id="map"></div>
<div id="stats">
Точек: 0
</div>
CSS:
#stats {
position: absolute;
top: 10px;
left: 10px;
background: white;
padding: 10px;
border-radius: 4px;
box-shadow: 0 2px 6px rgba(0,0,0,.2);
z-index: 1;
}
Обновление данных:
function updateStats() {
const count = points.features.length;
document.getElementById("stats").textContent =
`Точек: ${count}`;
}
При большом количестве объектов часто применяется кластеризация.
Источник:
map.addSource("earthquakes", {
type: "geojson",
data: data,
cluster: true,
clusterRadius: 50
});
В этом случае отдельные точки объединяются в группы.
MapLibre автоматически создаёт специальное свойство:
point_count
Оно содержит количество объектов внутри кластера.
Пример слоя кластеров:
map.addLayer({
id: "clusters",
type: "circle",
source: "earthquakes",
filter: ["has", "point_count"]
});
Получение информации:
map.on("click", "clusters", e => {
const cluster =
map.queryRenderedFeatures(e.point, {
layers: ["clusters"]
})[0];
console.log(cluster.properties.point_count);
});
Результат:
125
Значение означает, что кластер содержит 125 точек.
Очень часто число объектов показывается прямо поверх значка кластера.
Слой подписей:
map.addLayer({
id: "cluster-count",
type: "symbol",
source: "earthquakes",
filter: ["has", "point_count"],
layout: {
"text-field": ["get", "point_count"],
"text-size": 14
}
});
Теперь каждый кластер будет отображать количество входящих в него объектов.
Для более сложных сценариев используется метод
getClusterLeaves().
const source = map.getSource("earthquakes");
Получение объектов:
source.getClusterLeaves(
clusterId,
Infinity,
0,
(error, features) => {
if (error) return;
console.log(features.length);
}
);
Таким способом можно получить реальное число элементов кластера и сами объекты.
Допустим, пользователь выполняет поиск по названию города.
function searchCities(query) {
const filtered = points.features.filter(feature =>
feature.properties.name
.toLowerCase()
.includes(query.toLowerCase())
);
document.getElementById("counter").textContent =
`Найдено: ${filtered.length}`;
}
Пример:
searchCities("ка");
Результат:
Найдено: 1
Иногда необходимо определить общее количество объектов из нескольких слоёв.
const features = map.queryRenderedFeatures({
layers: [
"cities-layer",
"airports-layer",
"stations-layer"
]
});
Получение количества:
const totalCount = features.length;
Либо отдельно по каждому слою:
const cities = map.queryRenderedFeatures({
layers: ["cities-layer"]
}).length;
const airports = map.queryRenderedFeatures({
layers: ["airports-layer"]
}).length;
const stations = map.queryRenderedFeatures({
layers: ["stations-layer"]
}).length;
При работе с десятками или сотнями тысяч точек следует учитывать производительность.
Основные рекомендации:
queryRenderedFeatures() только по необходимым
слоям;moveend, а не через
move;Пример неэффективного решения:
map.on("move", () => {
countVisiblePoints(points);
});
Такой код может выполняться сотни раз в секунду.
Предпочтительный вариант:
map.on("moveend", () => {
countVisiblePoints(points);
});
В крупных приложениях часто одновременно отображаются:
Пример:
function updateStatistics() {
const total =
points.features.length;
const visible =
countVisiblePoints(points);
const rendered =
map.queryRenderedFeatures({
layers: ["cities-layer"]
}).length;
console.log({
total,
visible,
rendered
});
}
Подобный подход позволяет формировать полноценную статистическую панель поверх карты и предоставлять пользователю актуальную информацию о количестве отображаемых географических объектов в любой момент времени.