Tolerance для линий

При работе с векторной графикой и интерактивными картами важную роль играет точность взаимодействия пользователя с объектами. Линии на карте часто имеют небольшую визуальную толщину, поэтому попадание курсором мыши или касанием пальца непосредственно в геометрию линии может быть затруднено. Для решения этой задачи применяется параметр tolerance.

В контексте линий tolerance определяет допустимую область вокруг геометрии линии, которая также считается частью объекта при выполнении различных операций поиска, выбора или обработки событий. Фактически создаётся невидимая зона вокруг линии, увеличивающая область взаимодействия.

Без использования tolerance пользователь должен попадать строго в контур линии. При наличии ненулевого значения tolerance взаимодействие становится значительно удобнее.


Зачем нужен tolerance

На практике линии могут представлять:

  • автомобильные дороги;
  • железнодорожные пути;
  • маршруты движения;
  • границы территорий;
  • инженерные коммуникации;
  • туристические треки;
  • морские маршруты.

Во многих случаях ширина линии на экране составляет всего несколько пикселей. Попасть точно в такой объект бывает сложно даже с помощью мыши, не говоря о сенсорных устройствах.

Использование tolerance позволяет:

  • повысить удобство выбора объектов;
  • уменьшить количество пропущенных кликов;
  • улучшить мобильный пользовательский опыт;
  • упростить работу с плотными наборами данных;
  • повысить точность поиска ближайших объектов.

Принцип работы

Предположим, имеется линия дороги.

Визуально она отображается шириной 2 пикселя.

-------------------------

Если tolerance равен 0, то попадание должно происходить непосредственно по линии.

Если tolerance увеличен до нескольких пикселей, вокруг линии создаётся дополнительная область:

=========================
-------------------------
=========================

Пользователь может взаимодействовать не только с самой линией, но и с окружающей зоной.

С точки зрения алгоритмов проверки попадания фактически происходит вычисление расстояния от точки взаимодействия до ближайшего сегмента линии.

Если расстояние меньше tolerance, объект считается найденным.


Tolerance в queryRenderedFeatures

Одной из наиболее распространённых областей применения является метод поиска объектов на карте.

Пример получения объектов под курсором:

map.on('click', (e) => {
    const features = map.queryRenderedFeatures(e.point);

    console.log(features);
});

По умолчанию поиск выполняется по конкретной точке.

Однако можно расширить область поиска:

map.on('click', (e) => {
    const bbox = [
        [e.point.x - 5, e.point.y - 5],
        [e.point.x + 5, e.point.y + 5]
    ];

    const features = map.queryRenderedFeatures(bbox);

    console.log(features);
});

В данном примере вокруг точки клика создаётся область 10×10 пикселей.

Такой подход фактически реализует механизм tolerance вручную.


Поиск линий рядом с курсором

Рассмотрим ситуацию, когда на карте отображаются маршруты.

map.addLayer({
    id: 'routes',
    type: 'line',
    source: 'routes-source',
    paint: {
        'line-color': '#ff0000',
        'line-width': 2
    }
});

Попадание в линию толщиной 2 пикселя может быть затруднительным.

Поэтому часто используется дополнительный радиус поиска:

map.on('mousemove', (e) => {

    const bbox = [
        [e.point.x - 8, e.point.y - 8],
        [e.point.x + 8, e.point.y + 8]
    ];

    const features = map.queryRenderedFeatures(
        bbox,
        {
            layers: ['routes']
        }
    );

    if (features.length > 0) {
        map.getCanvas().style.cursor = 'pointer';
    }
});

В этом случае линия будет обнаруживаться даже тогда, когда курсор находится рядом с ней.


Разница между шириной линии и tolerance

Начинающие разработчики нередко путают эти понятия.

Ширина линии

Определяет визуальное отображение.

'line-width': 8

Линия действительно становится толще на экране.

Tolerance

Не изменяет внешний вид объекта.

Он влияет исключительно на область обнаружения или взаимодействия.

Например:

'line-width': 2

Визуально линия остаётся тонкой.

Но поиск может выполняться с радиусом:

10 пикселей

В результате пользователь воспринимает объект как более удобный для выбора.


Использование невидимого слоя вместо tolerance

Распространённая техника в Mapbox GL JS — создание дополнительного слоя с увеличенной толщиной.

Основная линия:

map.addLayer({
    id: 'road-line',
    type: 'line',
    source: 'roads',
    paint: {
        'line-color': '#0066ff',
        'line-width': 3
    }
});

Слой для обработки событий:

map.addLayer({
    id: 'road-hitbox',
    type: 'line',
    source: 'roads',
    paint: {
        'line-color': '#000000',
        'line-width': 20,
        'line-opacity': 0
    }
});

Теперь события можно навешивать именно на слой hitbox:

map.on('click', 'road-hitbox', (e) => {
    console.log('Линия выбрана');
});

Пользователь видит тонкую линию, но взаимодействует с гораздо большей областью.

Такой подход часто оказывается эффективнее прямого увеличения области поиска.


Tolerance при работе с касаниями

На мобильных устройствах проблема точного попадания становится особенно заметной.

Палец закрывает значительную часть экрана и обладает гораздо меньшей точностью по сравнению с курсором мыши.

Поэтому рекомендуются более высокие значения tolerance:

Устройство Рекомендуемый диапазон
Мышь 3–8 px
Планшет 8–15 px
Смартфон 10–20 px

Чем меньше отображаемые линии, тем больше обычно требуется зона допуска.


Геометрический смысл tolerance

Пусть имеется линия:

A ------------------- B

Пользователь нажал в точке P.

Алгоритм выполняет следующие действия:

  1. Находит ближайший сегмент линии.
  2. Вычисляет расстояние от точки P до сегмента.
  3. Сравнивает результат с tolerance.
  4. Возвращает совпадение при выполнении условия:
distance <= tolerance

Такой подход используется во многих геоинформационных системах и алгоритмах пространственного поиска.


Влияние на производительность

Небольшие значения tolerance практически не влияют на быстродействие.

Однако чрезмерно большие зоны поиска могут привести к дополнительным вычислениям.

Например:

const bbox = [
    [e.point.x - 50, e.point.y - 50],
    [e.point.x + 50, e.point.y + 50]
];

Область поиска составляет уже 100×100 пикселей.

Если карта содержит тысячи линий, количество потенциальных совпадений возрастает.

Следствием могут стать:

  • увеличение времени поиска;
  • рост нагрузки на процессор;
  • задержки при перемещении курсора;
  • снижение плавности интерфейса.

Поэтому значение tolerance должно быть достаточно большим для удобства пользователя, но не чрезмерным.


Подсветка линий с использованием tolerance

Часто требуется выделять линию при наведении курсора.

map.on('mousemove', (e) => {

    const bbox = [
        [e.point.x - 6, e.point.y - 6],
        [e.point.x + 6, e.point.y + 6]
    ];

    const features = map.queryRenderedFeatures(
        bbox,
        {
            layers: ['routes']
        }
    );

    if (features.length > 0) {

        map.setFeatureState(
            {
                source: 'routes',
                id: features[0].id
            },
            {
                hover: true
            }
        );
    }
});

Даже при небольшом отклонении курсора от линии подсветка продолжает работать корректно.

Это создаёт ощущение плавного и отзывчивого интерфейса.


Подбор оптимального значения

Выбор зависит от нескольких факторов:

Масштаб карты

При сильном уменьшении масштаба линии становятся визуально тоньше.

В таких случаях tolerance обычно увеличивают.

Плотность данных

Если линии расположены близко друг к другу, слишком большой tolerance может приводить к ошибочному выбору соседних объектов.

Тип устройства

Для мобильных устройств значения должны быть выше.

Толщина отображаемой линии

Чем тоньше линия, тем полезнее дополнительная область взаимодействия.


Типичные ошибки

Слишком маленькое значение

const radius = 1;

Пользователь вынужден попадать практически точно в линию.

Слишком большое значение

const radius = 30;

Выбираются соседние объекты, расположенные далеко от курсора.

Игнорирование мобильных устройств

Настройки, удобные для мыши, часто оказываются неудобными для сенсорного ввода.

Использование только визуальной толщины

'line-width': 15

Увеличение ширины линии ради удобства выбора ухудшает внешний вид карты. Намного лучше разделять визуальное отображение и область взаимодействия.


Практические рекомендации

Для большинства проектов с Mapbox GL JS хорошо работают следующие значения:

Сценарий Tolerance
Тонкие маршруты 5–8 px
Дороги 6–10 px
Пешеходные треки 8–12 px
Мобильные карты 10–20 px
Плотные инженерные сети 3–6 px

Наиболее универсальным решением считается сочетание двух подходов:

  1. Сохранение разумной визуальной толщины линии.
  2. Использование расширенной области поиска либо невидимого hitbox-слоя.

Такой подход обеспечивает высокую точность выбора объектов, удобство взаимодействия и стабильную производительность даже при работе с большими наборами линейных геоданных.