Параметр onEachFeature

onEachFeature используется в Leaflet как параметр конфигурации при создании слоя GeoJSON и предназначен для постобработки каждого объекта данных после его добавления на карту. Механизм работы основан на том, что при разборе GeoJSON каждый элемент преобразуется в слой Leaflet, после чего к нему применяется пользовательская функция, позволяющая навесить поведение, события или связать дополнительные данные.

Параметр onEachFeature передаётся в объект настроек при создании слоя:

L.geoJSON(data, {
  onEachFeature: function (feature, layer) {
    // логика обработки
  }
});

Функция получает два аргумента:

  • feature — исходный объект GeoJSON, содержащий свойства (properties) и геометрию (geometry)
  • layer — созданный Leaflet-слой, соответствующий геометрии объекта

Каждый объект GeoJSON проходит через эту функцию индивидуально, что делает возможным точечное управление поведением элементов.

Место onEachFeature в процессе рендеринга GeoJSON

При добавлении GeoJSON в Leaflet происходит последовательная обработка:

  1. Парсинг структуры GeoJSON
  2. Определение типа геометрии (Point, LineString, Polygon и т.д.)
  3. Создание соответствующего Leaflet-слоя
  4. Применение pointToLayer (для точек), style (для векторных объектов)
  5. Вызов onEachFeature
  6. Добавление слоя на карту

onEachFeature выполняется после создания слоя, что делает его ключевой точкой для навешивания логики.

Основные сценарии использования

Привязка всплывающих окон

Наиболее распространённый сценарий — связывание данных GeoJSON с popup:

L.geoJSON(geoData, {
  onEachFeature: function (feature, layer) {
    if (feature.properties && feature.properties.name) {
      layer.bindPopup(feature.properties.name);
    }
  }
});

В этом случае каждый объект получает индивидуальный popup на основе свойств.

Добавление событий

Через onEachFeature удобно навешивать обработчики событий:

L.geoJSON(geoData, {
  onEachFeature: function (feature, layer) {
    layer.on('click', function () {
      console.log(feature.properties);
    });

    layer.on('mouseover', function () {
      layer.setStyle({
        weight: 5
      });
    });

    layer.on('mouseout', function () {
      layer.setStyle({
        weight: 2
      });
    });
  }
});

Каждый слой становится интерактивным без необходимости обхода коллекции вручную.

Связывание пользовательских данных

GeoJSON часто содержит атрибуты, которые необходимо использовать внутри логики приложения:

L.geoJSON(geoData, {
  onEachFeature: function (feature, layer) {
    layer.featureId = feature.properties.id;
    layer.category = feature.properties.type;
  }
});

Таким образом слой расширяется пользовательскими полями.

Взаимодействие с другими параметрами GeoJSON

onEachFeature работает совместно с другими опциями:

  • style — применяется к полигональным и линейным объектам
  • pointToLayer — отвечает за создание маркеров
  • filter — определяет, какие объекты будут включены

Типичный порядок взаимодействия:

L.geoJSON(geoData, {
  filter: function (feature) {
    return feature.properties.active;
  },

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

  style: function (feature) {
    return { color: feature.properties.color };
  },

  onEachFeature: function (feature, layer) {
    layer.bindPopup(feature.properties.name);
  }
});

Особенности контекста выполнения

Функция onEachFeature вызывается без привязки к конкретному контексту приложения. Значение this внутри callback зависит от способа вызова и не гарантирует доступ к внешнему состоянию. Поэтому при необходимости доступа к внешним переменным используется замыкание:

const selectedId = 10;

L.geoJSON(geoData, {
  onEachFeature: function (feature, layer) {
    if (feature.properties.id === selectedId) {
      layer.setStyle({ color: 'red' });
    }
  }
});

Работа с различными типами геометрии

onEachFeature не зависит от типа геометрии, однако слой, передаваемый вторым аргументом, может отличаться:

  • Point → Marker или CircleMarker
  • LineString → Polyline
  • Polygon → Polygon
  • Multi* → группа слоёв

Это важно при применении методов, специфичных для типа слоя:

L.geoJSON(geoData, {
  onEachFeature: function (feature, layer) {
    if (layer instanceof L.Polygon) {
      layer.setStyle({ fillOpacity: 0.5 });
    }

    if (layer instanceof L.Marker) {
      layer.setOpacity(0.8);
    }
  }
});

Организация сложной логики

При увеличении сложности данных onEachFeature часто становится центральной точкой бизнес-логики для слоя:

function createPopupContent(props) {
  return `
    <div>
      <h4>${props.name}</h4>
      <p>Тип: ${props.type}</p>
      <p>Рейтинг: ${props.rating}</p>
    </div>
  `;
}

L.geoJSON(geoData, {
  onEachFeature: function (feature, layer) {
    const props = feature.properties;

    layer.bindPopup(createPopupContent(props));

    if (props.priority === 'high') {
      layer.setStyle({ color: 'red' });
    }

    layer.on('click', function () {
      layer.openPopup();
    });
  }
});

Ограничения и поведение при ошибках данных

При отсутствии properties или некорректной структуре GeoJSON функция всё равно вызывается, поэтому требуется защита от undefined:

onEachFeature: function (feature, layer) {
  if (!feature || !feature.properties) return;

  layer.bindPopup(feature.properties.name || 'Без названия');
}

Отличие от pointToLayer и style

Разделение ответственности между параметрами:

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

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

L.geoJSON(geoData, {
  pointToLayer: function (feature, latlng) {
    return L.circleMarker(latlng);
  },

  style: function (feature) {
    return { color: 'blue' };
  },

  onEachFeature: function (feature, layer) {
    layer.bindTooltip(feature.properties.name);
  }
});

Использование для динамических слоёв

При обновлении данных onEachFeature снова применяется ко всем объектам, что позволяет автоматически пересоздавать поведение без дополнительной ручной обработки коллекции слоёв.

function render(data) {
  return L.geoJSON(data, {
    onEachFeature: function (feature, layer) {
      layer.bindPopup(feature.properties.title);
    }
  });
}