В Mapbox GL JS под прямым выбором (direct select) понимается взаимодействие с конкретными географическими объектами, отрисованными на карте, без промежуточных списков, поисковых запросов или серверных фильтров. Основой такого подхода становится работа с уже загруженными в стиль слоями и геометриями, где пользовательское действие (клик, наведение, касание) преобразуется в запрос к отрисованным данным.
Ключевая идея заключается в том, что карта выступает как интерактивный рендерер WebGL-сцены, а выбор объекта реализуется через попадание курсора в область отрисованного фрагмента слоя.
Основной механизм прямого выбора в Mapbox GL JS реализуется через метод:
map.queryRenderedFeatures(point, options)
Этот метод возвращает массив объектов, которые в данный момент отрисованы в области экрана, соответствующей координате курсора.
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
if (!features.length) return;
console.log(features[0]);
});
При сложной карте с множеством слоёв критически важно ограничивать область поиска:
map.queryRenderedFeatures(e.point, {
layers: ['cities-layer']
});
Это позволяет исключить фоновые или вспомогательные слои и работать только с интерактивными объектами.
Вместо точки можно использовать прямоугольную область:
map.queryRenderedFeatures([
[e.point.x - 5, e.point.y - 5],
[e.point.x + 5, e.point.y + 5]
]);
Такой подход используется для расширения зоны захвата при неточных взаимодействиях.
При попадании курсора сразу в несколько слоёв порядок результатов зависит от порядка рендеринга. Первый элемент массива обычно соответствует верхнему видимому слою.
const feature = features[0];
Для контроля поведения применяется явное управление слоями через
layer order, где порядок добавления слоёв влияет на
приоритет выбора.
Одним из ключевых инструментов реализации прямого выбора является система состояний объектов:
map.setFeatureState(
{
source: 'places',
id: 123
},
{
selected: true
}
);
Для работы feature-state источник данных должен содержать уникальные
id:
{
"type": "Feature",
"id": 42,
"geometry": { ... },
"properties": { ... }
}
map.setFeatureState(
{ source: 'places', id: 42 },
{ selected: false }
);
Отображение состояния обычно реализуется через
paint-свойства слоёв и выражения.
map.setPaintProperty('places-layer', 'circle-color', [
'case',
['boolean', ['feature-state', 'selected'], false],
'#ff0000',
'#3388ff'
]);
Такой подход позволяет отделить логику выбора от рендеринга.
map.setPaintProperty('places-layer', 'circle-radius', [
'case',
['boolean', ['feature-state', 'selected'], false],
10,
6
]);
Помимо клика часто используется наведение курсора:
map.on('mousemove', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['places-layer']
});
map.getCanvas().style.cursor = features.length ? 'pointer' : '';
});
При интенсивных данных используется throttle:
let lastFeatureId = null;
map.on('mousemove', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['places-layer']
});
const feature = features[0];
if (!feature || feature.id === lastFeatureId) return;
lastFeatureId = feature.id;
});
В типичном сценарии direct select предполагает, что одновременно выбран только один объект.
let selectedId = null;
function selectFeature(feature) {
if (selectedId !== null) {
map.setFeatureState(
{ source: 'places', id: selectedId },
{ selected: false }
);
}
selectedId = feature.id;
map.setFeatureState(
{ source: 'places', id: selectedId },
{ selected: true }
);
}
Direct select часто используется совместно с GeoJSON источниками:
map.addSource('places', {
type: 'geojson',
data: 'places.geojson'
});
И слоем:
map.addLayer({
id: 'places-layer',
type: 'circle',
source: 'places',
paint: {
'circle-radius': 6,
'circle-color': '#3388ff'
}
});
При плотной визуализации возникает проблема перекрытия. В этом случае применяются стратегии:
const features = map.queryRenderedFeatures(e.point, {
layers: ['places-layer']
}).sort((a, b) => b.properties.priority - a.properties.priority);
if (map.getZoom() < 10) return;
Direct select работает не только с точками, но и с любыми типами геометрий.
map.queryRenderedFeatures(e.point, {
layers: ['polygons-layer']
});
При этом попадание определяется по пиксельной триангуляции WebGL, а не по геометрическим вычислениям в JS.
Для линий часто используется расширение hitbox через
line-width:
map.addLayer({
id: 'routes',
type: 'line',
source: 'routes',
paint: {
'line-width': 8,
'line-color': '#00a'
}
});
При частых вызовах queryRenderedFeatures важно учитывать
стоимость операций.
let scheduled = false;
map.on('mousemove', (e) => {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
map.queryRenderedFeatures(e.point);
scheduled = false;
});
});
Чем меньше интерактивных слоёв, тем быстрее работает выбор.
Часто direct select связывается с внешним UI-состоянием:
map.on('click', (e) => {
const feature = map.queryRenderedFeatures(e.point, {
layers: ['places-layer']
})[0];
if (!feature) return;
updateSidebar(feature.properties);
});
Архитектурно важно отделять:
queryRenderedFeatures)feature-state или store)paint expressions)Такая структура предотвращает дублирование логики и снижает связность компонентов.
В многослойных картах выбор может происходить по приоритету типов объектов:
const layersPriority = [
'pois-layer',
'roads-layer',
'buildings-layer'
];
const features = map.queryRenderedFeatures(e.point, {
layers: layersPriority
});
Первый найденный объект становится активным независимо от геометрического типа.
Direct select часто является основой для редакторов геоданных, где после выбора активируется режим трансформации:
Выбранный объект фиксируется через feature-state, а
дальнейшие операции работают с его координатами через source update.