Работа со свойствами feature

Работа со свойствами объектов (feature properties) в MapLibre GL JS строится вокруг концепции GeoJSON, где каждый географический объект представляет собой структуру Feature с набором координат и произвольным набором атрибутов в поле properties. Эти свойства являются основным источником динамики при визуализации данных, фильтрации, стилизации и взаимодействии с картой.

GeoJSON Feature имеет следующую логическую форму:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [30.5, 50.5]
  },
  "properties": {
    "name": "Объект A",
    "category": "restaurant",
    "rating": 4.6,
    "isActive": true
  }
}

Поле properties не ограничено по структуре, что делает его универсальным контейнером для любых атрибутов: от числовых значений до строк, булевых флагов и вложенных структур (хотя вложенность поддерживается ограниченно в выражениях стилей).

В MapLibre GL JS свойства feature становятся ключевым источником данных для:

  • стилизации слоёв (layers)
  • фильтрации объектов
  • событий взаимодействия
  • динамического обновления состояния

Доступ к свойствам через события

При работе с интерактивными слоями свойства feature часто извлекаются из событий карты:

map.on('click', 'restaurants-layer', (e) => {
  const feature = e.features[0];
  const props = feature.properties;

  console.log(props.name);
  console.log(props.rating);
});

Объект e.features содержит список всех объектов слоя в точке клика. Каждый feature уже содержит поле properties, полученное из источника данных.

Важно учитывать, что свойства доступны только если слой поддерживает queryable features (обычно vector sources или GeoJSON sources с включённой интерактивностью).

Использование properties в стилях (data-driven styling)

Одна из ключевых возможностей заключается в привязке визуальных параметров к значениям properties через expression API.

Цвет на основе категории

map.addLayer({
  id: 'restaurants-layer',
  type: 'circle',
  source: 'restaurants',
  paint: {
    'circle-color': [
      'match',
      ['get', 'category'],
      'restaurant', '#ff4d4d',
      'cafe', '#ffd24d',
      'bar', '#8c4dff',
      '#cccccc'
    ]
  }
});

Здесь функция ['get', 'category'] извлекает значение свойства category из feature.

Размер на основе числового свойства

'circle-radius': [
  'interpolate',
  ['linear'],
  ['get', 'rating'],
  0, 2,
  5, 10
]

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

Фильтрация по properties

Фильтры позволяют ограничивать отображение объектов слоя на основе их свойств.

map.setFilter('restaurants-layer', [
  '>=',
  ['get', 'rating'],
  4
]);

Другой пример — фильтрация по категории:

map.setFilter('restaurants-layer', [
  'in',
  ['get', 'category'],
  'restaurant',
  'cafe'
]);

Фильтры выполняются на уровне рендеринга, что делает их более эффективными, чем ручное скрытие объектов.

Обновление properties через source

В MapLibre GL JS свойства feature не являются напрямую мутабельными. Изменение properties требует обновления источника данных.

Типичный подход при использовании GeoJSON source:

const source = map.getSource('restaurants');

const data = source._data;

data.features[0].properties.rating = 5.0;

source.setData(data);

После вызова setData происходит полная перерисовка слоя.

Этот подход подходит для небольших наборов данных. При больших объёмах он становится затратным по производительности.

Разделение properties и feature-state

Вместо изменения самих properties часто используется механизм feature-state, который позволяет хранить динамическое состояние отдельно от GeoJSON.

map.setFeatureState(
  {
    source: 'restaurants',
    id: 123
  },
  {
    hovered: true
  }
);

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

'circle-color': [
  'case',
  ['boolean', ['feature-state', 'hovered'], false],
  '#ff0000',
  '#0000ff'
]

Преимущество подхода заключается в том, что исходные properties остаются неизменными, а состояние можно обновлять без перезагрузки source.

Доступ к properties через queryRenderedFeatures

Метод queryRenderedFeatures позволяет получать feature прямо с карты в заданной области:

const features = map.queryRenderedFeatures(point, {
  layers: ['restaurants-layer']
});

features.forEach(f => {
  console.log(f.properties.name);
});

Этот метод часто используется для кастомных взаимодействий, таких как построение всплывающих окон или динамических панелей информации.

Типы значений в properties

В properties допустимы базовые JSON-типы:

  • string
  • number
  • boolean
  • null

Однако при использовании expression API важно учитывать ограничения:

  • массивы поддерживаются частично
  • вложенные объекты не всегда доступны через get
  • значения должны быть детерминированными

Пример допустимой структуры:

{
  "temperature": 22.5,
  "label": "station A",
  "active": true
}

Пример проблемной структуры:

{
  "meta": {
    "nested": {
      "value": 10
    }
  }
}

В таких случаях доступ к meta.nested.value через expression невозможен напрямую.

Использование свойств в popup и UI

properties часто применяются для генерации интерфейсов:

map.on('click', 'restaurants-layer', (e) => {
  const f = e.features[0];

  new maplibregl.Popup()
    .setLngLat(f.geometry.coordinates)
    .setHTML(`
      <div>
        <strong>${f.properties.name}</strong><br/>
        Rating: ${f.properties.rating}
      </div>
    `)
    .addTo(map);
});

Здесь properties становятся источником данных для UI-слоя поверх карты.

Сравнение properties и атрибутов слоя

Важно различать:

  • feature.properties — данные конкретного объекта
  • layer.paint и layer.layout — правила отображения
  • feature-state — временное состояние

Properties статичны относительно GeoJSON, layer definitions описывают поведение, feature-state обеспечивает динамику.

Производительность при работе с properties

При интенсивном использовании properties важно учитывать:

  • выражения ['get', ...] выполняются на GPU/рендер-уровне
  • частые setData приводят к перерасчёту слоя
  • feature-state предпочтительнее для интерактивных изменений

Оптимальная стратегия:

  • статические данные → properties
  • динамическое состояние → feature-state
  • фильтрация → setFilter
  • визуализация → expressions

Ошибки при работе с properties

На практике встречаются типичные ошибки:

1. Несовпадение типов

['==', ['get', 'rating'], '5'] // строка вместо числа

2. Отсутствие свойства

Если feature не содержит ключа, выражение вернёт null, что может привести к fallback-значениям.

3. Попытка изменения properties без setData

Прямое изменение объекта feature не обновляет карту:

feature.properties.name = 'New'; // не сработает визуально

Требуется обновление источника.

Комбинирование properties в выражениях

Properties можно комбинировать:

[
  'case',
  ['all',
    ['==', ['get', 'category'], 'restaurant'],
    ['>=', ['get', 'rating'], 4]
  ],
  '#00ff00',
  '#999999'
]

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

Свойства и кластеризация

При использовании кластеров свойства исходных features агрегируются:

'cluster-radius': 40,
'cluster-properties': {
  'maxRating': ['max', ['get', 'rating']]
}

Таким образом можно вычислять агрегированные характеристики кластеров на основе properties вложенных объектов.