Работа с TopoJSON: topojson.feature(), topojson.mesh()

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

Библиотека D3.js предоставляет инструменты для преобразования TopoJSON в GeoJSON-совместимые структуры и извлечения геометрии для отрисовки SVG или Canvas. Центральными функциями в этом процессе выступают topojson.feature() и topojson.mesh().


TopoJSON описывает геометрию через:

  • arcs — массив координатных сегментов (дуг)
  • objects — коллекции геометрий, ссылающиеся на arcs
  • transform — масштабирование и декомпрессия координат

Пример упрощённой структуры:

{
  "type": "Topology",
  "transform": {
    "scale": [0.1, 0.1],
    "translate": [0, 0]
  },
  "arcs": [
    [[0,0],[10,0],[10,10]],
    [[10,10],[0,10],[0,0]]
  ],
  "objects": {
    "region": {
      "type": "GeometryCollection",
      "geometries": [
        { "type": "Polygon", "arcs": [[0,1]] }
      ]
    }
  }
}

topojson.feature(): преобразование в GeoJSON

Назначение функции

topojson.feature() преобразует TopoJSON-объект в стандартный GeoJSON, разворачивая топологические ссылки в полноценные координаты.

Сигнатура

topojson.feature(topology, object)
  • topology — исходный TopoJSON объект
  • object — конкретный элемент из topology.objects

Результат

Возвращается объект GeoJSON:

  • Feature
  • FeatureCollection
  • Geometry

Принцип работы преобразования

При вызове функции происходит:

  1. Извлечение arcs из топологии
  2. Применение transform (scale + translate)
  3. Декодирование координатных цепочек
  4. Сборка GeoJSON-геометрии

TopoJSON хранит координаты как дельты, поэтому этап декодирования включает накопление значений:

[ x_i = x_{i-1} + x_i,y_i = y_{i-1} + y_i]

x_i = x_{i-1} + x_i

y_i = y_{i-1} + y_i


Пример использования topojson.feature()

import * as topojson from "topojson-client";

d3.json("world.topojson").then(topology => {
  const countries = topojson.feature(
    topology,
    topology.objects.countries
  );

  svg.selectAll("path")
    .data(countries.features)
    .enter()
    .append("path")
    .attr("d", pathGenerator);
});

Типичные сценарии применения feature()

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

GeoJSON-результат напрямую используется в d3.geoPath():

  • страны
  • административные границы
  • природные зоны

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

После преобразования возможно:

  • привязка атрибутов (population, GDP)
  • раскраска через шкалы
  • фильтрация объектов

topojson.mesh(): извлечение границ

Назначение функции

topojson.mesh() извлекает линейную геометрию (LineString/MultiLineString), представляющую границы между объектами.

Функция не возвращает полигоны — только линии.

Сигнатура

topojson.mesh(topology, object[, filter])
  • topology — TopoJSON объект
  • object — слой или коллекция
  • filter — функция отбора границ

Принцип работы mesh()

Функция анализирует общие arcs между соседними полигонами.

Если два объекта используют одну дугу, mesh позволяет:

  • выделить общую границу
  • исключить внутренние линии
  • оставить только внешние контуры

Режимы работы mesh()

1. Полный mesh без фильтра

const borders = topojson.mesh(topology, topology.objects.countries);

Результат:

  • все границы между странами
  • включая внутренние соединения

2. Фильтрованный mesh (границы между объектами)

const borders = topojson.mesh(
  topology,
  topology.objects.countries,
  (a, b) => a !== b
);

В этом режиме возвращаются только линии, разделяющие разные объекты.


3. Внутренние границы одного объекта

const internal = topojson.mesh(
  topology,
  topology.objects.states,
  (a, b) => a === b
);

Используется для отображения внутренних административных границ.


Использование mesh() в визуализации

Отрисовка границ поверх карты

svg.append("path")
  .datum(topojson.mesh(topology, topology.objects.countries))
  .attr("d", pathGenerator)
  .attr("fill", "none")
  .attr("stroke", "#333");

Разделение слоёв карты

Типичная структура визуализации:

  1. Полигоны (feature)
  2. Границы (mesh)
  3. Подписи

mesh используется как отдельный слой, чтобы:

  • повысить читаемость
  • избежать “разрывов” между полигонами
  • обеспечить единый контур

Сравнение topojson.feature() и topojson.mesh()

topojson.feature()

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

topojson.mesh()

  • возвращает линии
  • используется для границ
  • игнорирует атрибуты
  • работает с общей топологией

Оптимизация отрисовки

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

  • устранения дублирования координат
  • хранения общих границ
  • применения delta encoding

При использовании mesh дополнительно:

  • уменьшается количество SVG-элементов
  • упрощается рендеринг границ
  • повышается производительность при зуме

Работа с проекциями D3

После преобразования TopoJSON в GeoJSON данные проходят через географические проекции:

const projection = d3.geoMercator();
const path = d3.geoPath().projection(projection);
  • feature() подаётся в geoPath
  • mesh() подаётся в отдельный path без заливки

Частые ошибки при использовании

Потеря структуры объектов

Использование feature() без правильного object:

topojson.feature(topology, topology.objects)

Ошибка: требуется конкретный слой, а не весь objects.


Неправильный фильтр mesh()

Фильтр должен учитывать пары объектов:

(a, b) => a !== b

Иначе границы могут исчезнуть полностью или дублироваться.


Игнорирование transform

TopoJSON требует применения transform для корректных координат. При ручной обработке без feature() возникают смещения.


Комбинирование feature() и mesh()

Типичная архитектура карты:

const countries = topojson.feature(topology, topology.objects.countries);
const borders = topojson.mesh(topology, topology.objects.countries);

svg.selectAll("countries")
  .data(countries.features)
  .enter()
  .append("path")
  .attr("d", path);

svg.append("path")
  .datum(borders)
  .attr("d", path)
  .attr("fill", "none")
  .attr("stroke", "#000");

Взаимодействие с большими наборами данных

TopoJSON особенно эффективен при:

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

mesh снижает нагрузку на рендеринг, так как заменяет множество внутренних границ одной геометрией.


Геометрическая модель mesh()

mesh можно рассматривать как:

  • граф смежности регионов
  • набор рёбер между полигонами
  • производную структуру от TopoJSON-арков

Это делает его полезным не только для визуализации, но и для анализа связности регионов.