Преобразование данных

Работа с геоданными в Leaflet строится вокруг преобразования исходных структур в формат, пригодный для визуализации на слое карты. Основной единицей становится нормализованный набор координат, согласованный с системой координат карты и внутренними механизмами библиотеки. Любые входящие данные проходят этап адаптации: преобразование форматов, выравнивание структуры свойств, приведение координат к ожидаемому виду.

Большинство источников геоданных не предоставляют данные в виде, непосредственно совместимом с Leaflet. API возвращают массивы координат, объекты с вложенной структурой, иногда — нестандартные порядки полей.

Типичный пример исходной структуры:

{
  "id": 1,
  "name": "Object A",
  "lat": 51.505,
  "lng": -0.09
}

Leaflet требует либо экземпляр L.LatLng, либо GeoJSON-структуру. Поэтому первым шагом становится преобразование:

const point = L.latLng(data.lat, data.lng);

Для более сложных случаев применяется формирование GeoJSON:

const geojson = {
  type: "Feature",
  geometry: {
    type: "Point",
    coordinates: [data.lng, data.lat]
  },
  properties: {
    id: data.id,
    name: data.name
  }
};

Ключевым моментом является порядок координат: GeoJSON использует [longitude, latitude], тогда как многие API возвращают [latitude, longitude]. Ошибки на этом этапе приводят к зеркальному отображению объектов или их исчезновению за пределами видимой области карты.

Преобразование данных в L.GeoJSON

Leaflet предоставляет слой L.GeoJSON, который выполняет базовую обработку структур и автоматическое создание визуальных объектов.

const layer = L.geoJSON(geojsonData).addTo(map);

Однако реальная работа с данными редко ограничивается прямой передачей объекта. Почти всегда требуется трансформация через опции.

Перехват и преобразование геометрии

L.geoJSON(data, {
  coordsToLatLng: function (coords) {
    return L.latLng(coords[1], coords[0]);
  }
});

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

Преобразование свойств объектов

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

function normalizeProperties(props) {
  return {
    id: Number(props.id),
    label: props.name ?? "unknown",
    category: props.type || "default"
  };
}

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

L.geoJSON(data, {
  onEachFeature: function (feature, layer) {
    const props = normalizeProperties(feature.properties);
    layer.bindPopup(props.label);
  }
});

Преобразование свойств становится промежуточным слоем между сырой моделью данных и визуальным представлением.

Фильтрация и трансформация набора объектов

Leaflet позволяет изменять набор отображаемых объектов до их добавления на карту:

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

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

Более сложные трансформации выполняются заранее:

const transformed = {
  type: "FeatureCollection",
  features: raw.features
    .filter(f => f.geometry !== null)
    .map(f => ({
      ...f,
      properties: normalizeProperties(f.properties)
    }))
};

Работа с системами координат

Leaflet по умолчанию использует Web Mercator (EPSG:3857). Входящие данные чаще всего представлены в EPSG:4326. Преобразование между ними внутри Leaflet ограничено базовыми случаями, поэтому при нестандартных CRS применяется расширение через Proj4Leaflet.

const crs = new L.Proj.CRS(
  "EPSG:32636",
  "+proj=utm +zone=36 +datum=WGS84 +units=m +no_defs"
);

const map = L.map("map", { crs: crs });

Данные при этом должны быть преобразованы до передачи в слой:

const projected = proj4(
  "EPSG:4326",
  "EPSG:32636",
  [lng, lat]
);

Таким образом, преобразование данных становится двухэтапным: сначала геометрическое преобразование координат, затем визуализация через Leaflet.

Преобразование массивов координат

Линейные и полигональные объекты требуют обработки вложенных массивов.

Исходный формат:

{
  "type": "LineString",
  "coordinates": [
    [30.5, 50.5],
    [30.6, 50.6]
  ]
}

Иногда требуется инверсия координат или очистка:

function transformLine(coords) {
  return coords.map(c => L.latLng(c[1], c[0]));
}

Для полигонов с вложенными кольцами:

function transformPolygon(coords) {
  return coords.map(ring =>
    ring.map(c => L.latLng(c[1], c[0]))
  );
}

Обогащение данных при трансформации

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

function enrichFeature(feature) {
  const coords = feature.geometry.coordinates;
  const center = L.latLng(coords[1], coords[0]);

  return {
    ...feature,
    properties: {
      ...feature.properties,
      center,
      timestamp: Date.now()
    }
  };
}

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

Использование pointToLayer для трансформации точек

Для точечных объектов Leaflet позволяет полностью переопределить процесс создания слоя:

L.geoJSON(data, {
  pointToLayer: function (feature, latlng) {
    return L.circleMarker(latlng, {
      radius: feature.properties.size || 5,
      weight: 1
    });
  }
});

Здесь происходит трансформация геометрии в визуальный объект. Входная точка превращается в маркер с заданными параметрами, зависящими от свойств данных.

Преобразование для кластеризации

При больших объёмах данных применяется предварительная подготовка перед передачей в кластерные слои:

const markers = L.markerClusterGroup();

data.features.forEach(f => {
  const latlng = L.latLng(
    f.geometry.coordinates[1],
    f.geometry.coordinates[0]
  );

  const marker = L.marker(latlng).bindPopup(f.properties.name);
  markers.addLayer(marker);
});

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

Сложные трансформации: агрегация данных

Иногда требуется не просто отображение, а объединение данных в новые структуры.

Пример агрегации точек по категориям:

function groupByCategory(features) {
  return features.reduce((acc, f) => {
    const key = f.properties.category;

    if (!acc[key]) acc[key] = [];

    acc[key].push(L.latLng(
      f.geometry.coordinates[1],
      f.geometry.coordinates[0]
    ));

    return acc;
  }, {});
}

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

Преобразование геометрии через упрощение

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

const simplified = turf.simplify(data, {
  tolerance: 0.01,
  highQuality: false
});

После упрощения данные снова проходят нормализацию перед добавлением в L.GeoJSON.

Потоковая обработка больших наборов данных

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

async function processChunks(features, chunkSize) {
  for (let i = 0; i < features.length; i += chunkSize) {
    const chunk = features.slice(i, i + chunkSize);

    const transformed = chunk.map(f => ({
      ...f,
      geometry: {
        ...f.geometry,
        coordinates: [
          f.geometry.coordinates[0],
          f.geometry.coordinates[1]
        ]
      }
    }));

    L.geoJSON(transformed).addTo(map);

    await new Promise(r => setTimeout(r, 0));
  }
}

Такой подход сохраняет отзывчивость интерфейса при работе с тысячами объектов.

Преобразование bounding box и границ

Границы часто приходят в виде массива:

[west, south, east, north]

Leaflet использует L.LatLngBounds, требующий другого представления:

const bounds = L.latLngBounds(
  [south, west],
  [north, east]
);

Ошибки в порядке координат приводят к некорректному масштабированию карты или смещению области отображения.

Инверсия и адаптация координат под интерфейсы API

Некоторые сервисы используют нестандартные порядки координат:

function adaptCoordinates(coords, format) {
  if (format === "latlng") {
    return [coords[1], coords[0]];
  }
  if (format === "lnglat") {
    return coords;
  }
}

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

Композиция трансформационных функций

В сложных системах преобразование данных строится как цепочка функций:

const pipeline = [
  normalizeProperties,
  enrichFeature,
  validateGeometry
];

function applyPipeline(feature) {
  return pipeline.reduce((f, fn) => fn(f), feature);
}

Каждый этап выполняет строго определённую операцию: очистку, дополнение, проверку или изменение структуры.

Валидация после преобразования

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

function validateGeometry(feature) {
  const coords = feature.geometry.coordinates;

  if (!coords || coords.length === 0) {
    return null;
  }

  return feature;
}

Такая стадия предотвращает попадание некорректных объектов в слой визуализации, что особенно важно при динамическом обновлении данных.