Предобработка данных

Эффективность визуализации в Kepler.gl напрямую зависит от качества исходных данных. Даже при наличии мощных инструментов агрегации, фильтрации и стилизации некорректные, неполные или плохо структурированные наборы данных приводят к ошибкам отображения, снижению производительности и искажению аналитических выводов.

Предобработка данных представляет собой комплекс операций по подготовке набора данных к загрузке в Kepler.gl. Основные задачи данного этапа:

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

Большинство операций выполняется до передачи данных в компонент Kepler.gl, обычно средствами JavaScript или серверной логики.


Требования Kepler.gl к структуре данных

Kepler.gl работает преимущественно с табличными данными. Каждая строка представляет отдельный объект наблюдения.

Пример набора данных:

id city latitude longitude population
1 Almaty 43.2389 76.8897 1977000
2 Astana 51.1694 71.4491 1350000

В JavaScript такие данные часто представлены массивом объектов:

const cities = [
  {
    id: 1,
    city: "Almaty",
    latitude: 43.2389,
    longitude: 76.8897,
    population: 1977000
  },
  {
    id: 2,
    city: "Astana",
    latitude: 51.1694,
    longitude: 71.4491,
    population: 1350000
  }
];

Перед загрузкой желательно убедиться, что:

  • все поля имеют единый тип данных;
  • отсутствуют поврежденные записи;
  • координаты находятся в допустимых диапазонах;
  • текстовые значения очищены от лишних символов.

Очистка пропущенных значений

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

Пример:

const rawData = [
  {
    city: "Almaty",
    latitude: 43.2389,
    longitude: 76.8897
  },
  {
    city: "Unknown",
    latitude: null,
    longitude: 70.1234
  }
];

Если слой требует координаты, запись без широты становится бесполезной.

Фильтрация выполняется следующим образом:

const cleanedData = rawData.filter(
  item =>
    item.latitude !== null &&
    item.longitude !== null
);

Более универсальный вариант:

const cleanedData = rawData.filter(item =>
  Number.isFinite(item.latitude) &&
  Number.isFinite(item.longitude)
);

Такой подход позволяет исключить:

  • null;
  • undefined;
  • NaN;
  • бесконечные значения.

Удаление дубликатов

Повторяющиеся записи искажают тепловые карты, кластеры и статистические показатели.

Исходный набор:

const data = [
  { id: 1, city: "Almaty" },
  { id: 1, city: "Almaty" },
  { id: 2, city: "Astana" }
];

Удаление по идентификатору:

const uniqueData = [
  ...new Map(
    data.map(item => [item.id, item])
  ).values()
];

Результат:

[
  { id: 1, city: "Almaty" },
  { id: 2, city: "Astana" }
]

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

const unique = [
  ...new Map(
    data.map(item => [
      `${item.city}_${item.latitude}_${item.longitude}`,
      item
    ])
  ).values()
];

Нормализация координат

Kepler.gl ожидает географические координаты в формате WGS84:

  • широта: от −90 до 90;
  • долгота: от −180 до 180.

Проверка координат:

function isValidCoordinate(lat, lng) {
  return (
    lat >= -90 &&
    lat <= 90 &&
    lng >= -180 &&
    lng <= 180
  );
}

Фильтрация:

const validPoints = data.filter(point =>
  isValidCoordinate(
    point.latitude,
    point.longitude
  )
);

Некоторые системы хранят координаты в обратном порядке.

Некорректно:

{
  latitude: 76.8897,
  longitude: 43.2389
}

Корректно:

{
  latitude: 43.2389,
  longitude: 76.8897
}

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


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

Координаты часто поступают как строки.

Пример:

{
  latitude: "43.2389",
  longitude: "76.8897"
}

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

Преобразование:

const normalized = data.map(item => ({
  ...item,
  latitude: parseFloat(item.latitude),
  longitude: parseFloat(item.longitude)
}));

Дополнительная проверка:

const normalized = data
  .map(item => ({
    ...item,
    latitude: Number(item.latitude),
    longitude: Number(item.longitude)
  }))
  .filter(item =>
    Number.isFinite(item.latitude) &&
    Number.isFinite(item.longitude)
  );

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

Kepler.gl поддерживает временную анимацию через Time Playback.

Для этого даты должны иметь корректный формат.

Исходные данные:

{
  event: "Delivery",
  date: "2025-05-15 14:30"
}

Преобразование в ISO-формат:

const normalized = data.map(item => ({
  ...item,
  timestamp: new Date(item.date).toISOString()
}));

Результат:

{
  timestamp: "2025-05-15T14:30:00.000Z"
}

Преимущества ISO-формата:

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

Преобразование временных меток Unix

Во многих API используется Unix Timestamp.

Пример:

{
  createdAt: 1747303200
}

Конвертация:

const converted = data.map(item => ({
  ...item,
  createdAt: new Date(
    item.createdAt * 1000
  ).toISOString()
}));

Если timestamp уже в миллисекундах:

new Date(item.createdAt).toISOString();

Стандартизация текстовых значений

Даже небольшие различия в написании приводят к ошибкам группировки.

Проблемный набор:

[
  { city: "Almaty" },
  { city: " almaty " },
  { city: "ALMATY" }
]

Нормализация:

const normalized = data.map(item => ({
  ...item,
  city: item.city.trim().toLowerCase()
}));

Результат:

[
  { city: "almaty" },
  { city: "almaty" },
  { city: "almaty" }
]

Для отображения можно дополнительно использовать форматирование:

function capitalize(value) {
  return value.charAt(0).toUpperCase() +
         value.slice(1);
}

Агрегация данных перед визуализацией

Не всегда необходимо отображать каждую запись.

Исходные данные:

[
  { city: "Almaty", sales: 100 },
  { city: "Almaty", sales: 150 },
  { city: "Astana", sales: 90 }
]

Группировка:

const grouped = data.reduce(
  (acc, item) => {
    acc[item.city] =
      (acc[item.city] || 0) +
      item.sales;

    return acc;
  },
  {}
);

Результат:

{
  Almaty: 250,
  Astana: 90
}

Подготовка для Kepler.gl:

const aggregated = Object.entries(grouped)
  .map(([city, sales]) => ({
    city,
    sales
  }));

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

  • уменьшение объема данных;
  • ускорение рендеринга;
  • повышение читаемости карты.

Сокращение объема больших наборов данных

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

Выборка части данных:

const sample = data.slice(0, 100000);

Случайная выборка:

const sample = data.filter(
  () => Math.random() < 0.1
);

Выборка 10% записей:

const sample = data.filter(
  (_, index) => index % 10 === 0
);

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


Создание поля GeoJSON

Kepler.gl способен работать с готовыми геометриями.

Формирование объекта Point:

const geoData = data.map(item => ({
  ...item,
  geometry: {
    type: "Point",
    coordinates: [
      item.longitude,
      item.latitude
    ]
  }
}));

Полученный объект соответствует стандарту GeoJSON.

Пример:

{
  type: "Point",
  coordinates: [76.8897, 43.2389]
}

Важно помнить, что GeoJSON использует порядок:

[longitude, latitude]

а не наоборот.


Формирование LineString

Для отображения маршрутов требуется геометрия типа LineString.

Исходные точки:

const route = [
  [76.8897, 43.2389],
  [77.0400, 43.2700],
  [77.2000, 43.3200]
];

Создание объекта:

const line = {
  type: "LineString",
  coordinates: route
};

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

const routes = [
  {
    routeId: 1,
    geometry: line
  }
];

Формирование Polygon

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

Пример:

const polygon = {
  type: "Polygon",
  coordinates: [[
    [76.8, 43.2],
    [77.0, 43.2],
    [77.0, 43.4],
    [76.8, 43.4],
    [76.8, 43.2]
  ]]
};

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

Некорректно:

[
  [76.8, 43.2],
  [77.0, 43.2],
  [77.0, 43.4]
]

Корректно:

[
  [76.8, 43.2],
  [77.0, 43.2],
  [77.0, 43.4],
  [76.8, 43.4],
  [76.8, 43.2]
]

Обогащение данных вычисляемыми полями

Перед загрузкой часто создаются дополнительные признаки.

Исходные данные:

{
  revenue: 12000,
  orders: 300
}

Вычисление среднего чека:

const prepared = data.map(item => ({
  ...item,
  averageOrderValue:
    item.revenue / item.orders
}));

Создание категории:

const prepared = data.map(item => ({
  ...item,
  category:
    item.revenue > 10000
      ? "high"
      : "low"
}));

Такие поля затем используются для:

  • цветовых шкал;
  • фильтрации;
  • кластеризации;
  • легенд;
  • сегментации объектов.

Подготовка данных для нескольких слоев

Нередко один набор данных используется одновременно несколькими слоями.

Исходный объект:

{
  id: 1,
  latitude: 43.2389,
  longitude: 76.8897,
  revenue: 12000
}

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

const pointsLayerData = data.map(item => ({
  latitude: item.latitude,
  longitude: item.longitude,
  revenue: item.revenue
}));
const heatmapLayerData = data.map(item => ({
  latitude: item.latitude,
  longitude: item.longitude,
  weight: item.revenue
}));

Разделение данных заранее позволяет избежать лишних вычислений внутри интерфейса.


Конвейер предобработки данных

Типичный процесс подготовки набора данных перед передачей в Kepler.gl выглядит следующим образом:

const preparedData = rawData
  .filter(item =>
    Number.isFinite(item.latitude) &&
    Number.isFinite(item.longitude)
  )
  .map(item => ({
    ...item,
    latitude: Number(item.latitude),
    longitude: Number(item.longitude),
    timestamp: new Date(
      item.timestamp
    ).toISOString(),
    city: item.city.trim()
  }))
  .filter(item =>
    item.city.length > 0
  );

Такой конвейер обеспечивает:

  • корректность геоданных;
  • единый формат времени;
  • отсутствие пустых записей;
  • совместимость с механизмами визуализации Kepler.gl;
  • более высокую производительность при работе с крупными наборами данных.