MultiPoint геометрия

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

В библиотеке MapLibre GL JS поддержка MultiPoint реализуется через стандарт GeoJSON-источники, что позволяет использовать его наравне с Point, LineString и Polygon. Несмотря на то что визуально каждая точка обрабатывается рендеринг-пайплайном как отдельный элемент, логически они остаются частью единой геометрии.


Базовая структура MultiPoint строго соответствует спецификации GeoJSON:

{
  "type": "MultiPoint",
  "coordinates": [
    [30.31413, 59.93863],
    [30.31510, 59.93920],
    [30.31600, 59.94010]
  ]
}

Каждый элемент массива coordinates представляет собой отдельную точку в формате [долгота, широта]. Важно учитывать порядок координат: GeoJSON всегда использует именно [lng, lat], а не наоборот.


Подключение MultiPoint через GeoJSON source

В MapLibre GL JS MultiPoint добавляется через источник типа geojson, где геометрия передаётся напрямую в объект Feature.

map.addSource('multipoint-source', {
  type: 'geojson',
  data: {
    type: 'Feature',
    geometry: {
      type: 'MultiPoint',
      coordinates: [
        [37.6173, 55.7558],
        [37.6250, 55.7600],
        [37.6300, 55.7620]
      ]
    },
    properties: {
      group: 'demo-points'
    }
  }
});

Несмотря на то что используется Feature, ключевым моментом является именно geometry.type = "MultiPoint".


Отображение MultiPoint через circle layer

Наиболее прямой способ визуализации — использование слоя circle. В этом случае каждая координата внутри MultiPoint будет отрисована как отдельный круг.

map.addLayer({
  id: 'multipoint-layer',
  type: 'circle',
  source: 'multipoint-source',
  paint: {
    'circle-radius': 6,
    'circle-color': '#3b82f6',
    'circle-stroke-width': 1,
    'circle-stroke-color': '#1e3a8a'
  }
});

Внутренне MapLibre GL JS автоматически “разворачивает” MultiPoint в набор отдельных feature-подобных элементов для рендеринга в WebGL.


Поведение в symbol layer

При использовании symbol слоя каждая точка MultiPoint может быть представлена как отдельный текстовый или иконный маркер.

map.addLayer({
  id: 'multipoint-symbols',
  type: 'symbol',
  source: 'multipoint-source',
  layout: {
    'icon-image': 'marker-15',
    'icon-size': 1,
    'text-field': ['get', 'label'],
    'text-offset': [0, 1.2]
  }
});

Если у Feature присутствуют свойства, они применяются ко всем точкам внутри MultiPoint, поскольку объект общий.


Особенности свойств (properties) и наследование

MultiPoint является единым Feature, поэтому:

  • properties общие для всех точек
  • нельзя задать индивидуальные свойства для каждой координаты внутри одного MultiPoint
  • любые фильтры работают на уровне всего объекта

Это важное ограничение, которое часто приводит к необходимости перехода от MultiPoint к набору отдельных Point-feature.


Фильтрация MultiPoint в слоях

Фильтры применяются ко всему объекту целиком:

map.addLayer({
  id: 'filtered-multipoint',
  type: 'circle',
  source: 'multipoint-source',
  filter: ['==', ['get', 'group'], 'demo-points'],
  paint: {
    'circle-radius': 5,
    'circle-color': '#ef4444'
  }
});

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


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

Использование MultiPoint может быть как оптимизацией, так и источником ограничений:

Преимущества:

  • уменьшение количества feature-объектов в источнике
  • снижение накладных расходов на хранение и передачу GeoJSON
  • более компактная структура данных

Ограничения:

  • отсутствие индивидуального управления точками внутри объекта
  • невозможность частичного обновления отдельных координат без перезаписи всей геометрии
  • усложнение интерактивной логики (hover, click по отдельной точке)

В WebGL-конвейере MapLibre каждый элемент MultiPoint всё равно превращается в отдельный draw call на уровне вершинного буфера, поэтому выигрыш наблюдается в основном на уровне CPU и JSON-парсинга.


Обработка событий взаимодействия

События click, mouseenter, mouseleave работают на уровне отдельных отрисованных точек:

map.on('click', 'multipoint-layer', (e) => {
  const features = map.queryRenderedFeatures(e.point, {
    layers: ['multipoint-layer']
  });

  if (features.length) {
    const feature = features[0];
    console.log(feature.geometry.type);
  }
});

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


Динамическое обновление MultiPoint

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

const source = map.getSource('multipoint-source');

source.setData({
  type: 'Feature',
  geometry: {
    type: 'MultiPoint',
    coordinates: [
      [37.6200, 55.7550],
      [37.6210, 55.7560],
      [37.6220, 55.7570],
      [37.6230, 55.7580]
    ]
  }
});

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


Использование data-driven styling

Хотя MultiPoint сам по себе не поддерживает индивидуальные свойства точек, можно применять стили на уровне слоя:

paint: {
  'circle-radius': [
    'interpolate',
    ['linear'],
    ['zoom'],
    5, 2,
    10, 6,
    15, 10
  ],
  'circle-color': [
    'match',
    ['get', 'type'],
    'A', '#22c55e',
    'B', '#f59e0b',
    '#3b82f6'
  ]
}

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


Сравнение с набором Point features

Использование MultiPoint и набора отдельных Point имеет принципиальные различия:

  • MultiPoint — единая сущность, компактная структура
  • Point features — максимальная гибкость, индивидуальные свойства

В сценариях с высокой интерактивностью (кластеризация, уникальные tooltip-данные, динамические состояния) отдельные Point-объекты оказываются более практичными. MultiPoint же лучше подходит для статических или слабо дифференцированных наборов координат.


Применение в реальных задачах

MultiPoint часто используется в следующих сценариях:

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

При работе с потоковыми данными MultiPoint может выступать промежуточным форматом перед разбиением на полноценные feature-объекты.


Геометрические ограничения и специфика обработки

GeoJSON не накладывает ограничений на количество точек внутри MultiPoint, однако производительность рендеринга зависит от:

  • плотности точек на экранном пространстве
  • сложности стилей слоя
  • частоты обновления источника
  • масштаба карты

При больших объёмах данных возможна деградация интерактивности, особенно при частых вызовах setData.


Взаимодействие с кластеризацией

Кластеризация в MapLibre GL JS применяется на уровне Point-features. Поэтому MultiPoint сначала должен быть преобразован в отдельные точки, иначе кластеризация не будет работать корректно.

map.addSource('points', {
  type: 'geojson',
  data: expandedPoints,
  cluster: true,
  clusterMaxZoom: 14,
  clusterRadius: 50
});

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


Особенности сериализации и хранения

При передаче через сеть MultiPoint имеет меньший overhead по сравнению с массивом Feature-объектов:

// компактный вариант
{
  "type": "MultiPoint",
  "coordinates": [...]
}

против:

{
  "type": "FeatureCollection",
  "features": [
    { "type": "Feature", "geometry": { "type": "Point", ... } },
    ...
  ]
}

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