Параметр pointToLayer

Leaflet предоставляет механизм преобразования GeoJSON-геометрий в визуальные слои карты через L.geoJSON. Одним из ключевых расширений поведения при рендеринге точечных объектов является параметр pointToLayer, определяющий способ создания слоя для каждого Point-объекта.


Назначение pointToLayer

Параметр pointToLayer используется для переопределения стандартного поведения, при котором точечные GeoJSON-объекты автоматически превращаются в L.marker.

Основная функция:

  • контроль над тем, каким слоем становится каждая точка
  • возможность замены L.marker на L.circleMarker, L.divIcon или пользовательский слой
  • динамическое формирование визуализации на основе свойств GeoJSON

Сигнатура функции

pointToLayer: function (feature, latlng) {
    return L.marker(latlng);
}

Аргументы функции

feature

GeoJSON-объект текущего элемента слоя.

Содержит:

  • feature.type — тип геометрии
  • feature.properties — пользовательские данные
  • feature.geometry — координаты и тип GeoJSON

Ключевая роль — предоставление контекста для стилизации слоя.

latlng

Объект координат L.LatLng, полученный из GeoJSON координат.

Используется для создания любых типов маркеров или слоёв, привязанных к точке.


Возвращаемое значение

Функция должна возвращать экземпляр слоя Leaflet:

  • L.Marker
  • L.CircleMarker
  • L.Circle
  • L.DivIcon (через L.marker)
  • пользовательские расширения L.Layer

Возвращённый объект автоматически добавляется в слой L.geoJSON.


Поведение по умолчанию

Без pointToLayer точечные объекты обрабатываются так:

L.geoJSON(data)

эквивалентно:

L.geoJSON(data, {
    pointToLayer: function (feature, latlng) {
        return L.marker(latlng);
    }
});

Таким образом, каждый Point становится стандартным маркером.


Базовое использование с L.marker

L.geoJSON(geojsonData, {
    pointToLayer: function (feature, latlng) {
        return L.marker(latlng);
    }
}).addTo(map);

Такой вариант фактически не изменяет поведение, но фиксирует явную точку расширения.


Использование L.circleMarker для стилизации точек

Одним из наиболее распространённых сценариев является замена маркеров на L.circleMarker для визуализации количественных данных.

L.geoJSON(geojsonData, {
    pointToLayer: function (feature, latlng) {
        return L.circleMarker(latlng, {
            radius: 6,
            fillColor: "#3388ff",
            color: "#1f5eff",
            weight: 1,
            opacity: 1,
            fillOpacity: 0.8
        });
    }
}).addTo(map);

Преимущества такого подхода:

  • высокая производительность при большом количестве точек
  • возможность стилизации через SVG/CSS-подобные параметры
  • удобство масштабирования визуального веса данных

Динамическая стилизация через feature.properties

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

L.geoJSON(geojsonData, {
    pointToLayer: function (feature, latlng) {
        const value = feature.properties.value;

        let color = "#2ecc71";

        if (value > 100) color = "#e74c3c";
        else if (value > 50) color = "#f1c40f";

        return L.circleMarker(latlng, {
            radius: 5 + value / 20,
            color: color,
            fillColor: color,
            fillOpacity: 0.7,
            weight: 1
        });
    }
}).addTo(map);

Используется:

  • визуализация плотности
  • тепловые псевдо-карты
  • категориальная классификация

Использование кастомных иконок

const customIcon = L.icon({
    iconUrl: 'icon.png',
    iconSize: [24, 24],
    iconAnchor: [12, 12]
});

L.geoJSON(geojsonData, {
    pointToLayer: function (feature, latlng) {
        return L.marker(latlng, {
            icon: customIcon
        });
    }
}).addTo(map);

Подход применяется при необходимости:

  • брендированных маркеров
  • пиктограмм категорий
  • тематических слоёв

Использование DivIcon через pointToLayer

HTML-маркеры позволяют создавать сложные визуальные элементы.

L.geoJSON(geojsonData, {
    pointToLayer: function (feature, latlng) {
        const html = `
            <div class="custom-pin">
                ${feature.properties.name}
            </div>
        `;

        return L.marker(latlng, {
            icon: L.divIcon({
                html: html,
                className: "custom-div-icon"
            })
        });
    }
}).addTo(map);

Особенности:

  • полная свобода верстки
  • возможность CSS-анимаций
  • более высокая нагрузка по сравнению с circleMarker

Различие pointToLayer и style

Частая ошибка — смешение pointToLayer и style.

Параметр Применение
style LineString и Polygon
pointToLayer Point

style не влияет на точки, тогда как pointToLayer полностью определяет их отображение.


Интеграция с фильтрацией данных

pointToLayer часто работает совместно с filter:

L.geoJSON(geojsonData, {
    filter: function (feature) {
        return feature.properties.active === true;
    },
    pointToLayer: function (feature, latlng) {
        return L.circleMarker(latlng, {
            radius: 4,
            color: "#3498db"
        });
    }
}).addTo(map);

Фильтрация определяет набор данных, pointToLayer — способ отображения.


Производительность при большом количестве точек

При тысячах объектов выбор слоя критичен.

Рекомендуемые подходы:

  • L.circleMarker вместо L.marker
  • минимизация DOM-структуры в DivIcon
  • отказ от тяжёлых HTML-элементов
  • отсутствие сложных обработчиков в pointToLayer

Преимущество circleMarker:

  • отрисовка через SVG
  • отсутствие отдельных DOM-иконок
  • лучшая масштабируемость

Комбинация с событиями

Созданные слои можно сразу обогащать событиями:

L.geoJSON(geojsonData, {
    pointToLayer: function (feature, latlng) {
        const marker = L.circleMarker(latlng, {
            radius: 6,
            color: "#2c3e50"
        });

        marker.on("click", function () {
            console.log(feature.properties.id);
        });

        return marker;
    }
}).addTo(map);

Используется для:

  • интерактивных аналитических карт
  • выбора объектов
  • построения пользовательских интерфейсов поверх карты

Использование условий на основе масштаба

Хотя pointToLayer не имеет прямого доступа к zoom, логика может быть вынесена наружу:

function createLayer(feature, latlng, zoom) {
    if (zoom > 10) {
        return L.marker(latlng);
    }
    return L.circleMarker(latlng, { radius: 4 });
}

И затем:

L.geoJSON(data, {
    pointToLayer: function (feature, latlng) {
        return createLayer(feature, latlng, map.getZoom());
    }
});

Распространённые ошибки

  • возврат undefined вместо слоя
  • использование style для точек вместо pointToLayer
  • создание тяжёлых DOM-структур без необходимости
  • игнорирование производительности при массовых данных
  • возврат одного и того же экземпляра слоя для разных точек

Паттерны проектирования с pointToLayer

фабрика слоёв

function layerFactory(feature) {
    if (feature.properties.type === "city") {
        return L.circleMarker;
    }
    return L.marker;
}

параметризация через конфигурацию

const geojsonOptions = {
    pointToLayer: (feature, latlng) =>
        L.circleMarker(latlng, config.style[feature.properties.category])
};

Роль в архитектуре GeoJSON слоя

pointToLayer является точкой расширения между:

  • сырыми геоданными
  • визуальной моделью карты

Именно на этом уровне происходит:

  • трансформация данных в графику
  • привязка бизнес-логики к геометрии
  • выбор визуального представления объекта