Работа с CSV и JSON

Работа с табличными данными в формате CSV в экосистеме Deck.gl обычно строится вокруг предварительного парсинга и последующей трансформации в структуры, пригодные для GPU-рендеринга. CSV сам по себе является текстовым форматом, не содержащим типизации, поэтому ключевой задачей становится корректное преобразование строковых значений в числовые координаты, категории и метаданные.

На практике чаще всего используется связка Deck.gl и loaders.gl, где CSVLoader обеспечивает потоковый и эффективный разбор больших файлов.

import {load} from '@loaders.gl/core';
import {CSVLoader} from '@loaders.gl/csv';

const DATA_URL = 'https://example.com/data.csv';

const data = await load(DATA_URL, CSVLoader);

console.log(data);

Результат загрузки — массив объектов, где каждая строка CSV превращается в объект с ключами, соответствующими заголовкам столбцов.

Типичный CSV:

lat,lon,value
53.9,27.5667,10
55.75,37.6167,20

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

[
  { lat: "53.9", lon: "27.5667", value: "10" },
  { lat: "55.75", lon: "37.6167", value: "20" }
]

Для Deck.gl требуется явное приведение типов:

const parsed = data.map(d => ({
  position: [Number(d.lon), Number(d.lat)],
  value: Number(d.value)
}));

Использование CSV-данных в слоях Deck.gl

После нормализации данных их можно передавать в визуальные слои. Один из базовых вариантов — ScatterplotLayer, предназначенный для точечного отображения.

import {Deck} from '@deck.gl/core';
import {ScatterplotLayer} from '@deck.gl/layers';

new Deck({
  initialViewState: {
    longitude: 37.6,
    latitude: 55.75,
    zoom: 4
  },
  controller: true,
  layers: [
    new ScatterplotLayer({
      id: 'csv-points',
      data: parsed,
      getPosition: d => d.position,
      getRadius: d => d.value * 1000,
      getFillColor: [255, 100, 0],
      pickable: true
    })
  ]
});

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

Стриминговая загрузка больших CSV

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

import {parse} from '@loaders.gl/csv';

const response = await fetch(DATA_URL);
const text = await response.text();

const rows = parse(text);

const cleaned = [];

for (const row of rows) {
  if (!row.lat || !row.lon) continue;

  cleaned.push({
    position: [parseFloat(row.lon), parseFloat(row.lat)],
    value: parseFloat(row.value || 0)
  });
}

Такая обработка позволяет предварительно фильтровать данные до передачи в GPU-слои, уменьшая нагрузку на рендерер.

Работа с JSON-данными

JSON является более естественным форматом для Deck.gl, поскольку уже поддерживает вложенные структуры и типизацию. В большинстве случаев данные можно использовать напрямую без дополнительного парсинга.

Пример структуры:

[
  {
    "coordinates": [37.6, 55.75],
    "population": 12000000
  },
  {
    "coordinates": [30.3, 59.9],
    "population": 5000000
  }
]

Загрузка:

import {load} from '@loaders.gl/core';
import {JSONLoader} from '@loaders.gl/json';

const data = await load('data.json', JSONLoader);

Однако JSONLoader не всегда обязателен — fetch уже возвращает готовый объект:

const data = await fetch('data.json').then(r => r.json());

Прямое использование JSON в слоях

JSON-структуры позволяют сократить количество промежуточных преобразований:

new ScatterplotLayer({
  id: 'json-layer',
  data,
  getPosition: d => d.coordinates,
  getRadius: d => Math.sqrt(d.population) / 100,
  getFillColor: [0, 120, 255]
});

JSON особенно удобен при работе с API, возвращающими геоданные, например:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [37.6, 55.75]
      },
      "properties": {
        "value": 100
      }
    }
  ]
}

Работа с GeoJSON и интеграция с Deck.gl

GeoJSON является частным случаем JSON и активно используется в Deck.gl через GeoJsonLayer.

import {GeoJsonLayer} from '@deck.gl/layers';

new GeoJsonLayer({
  id: 'geojson',
  data: 'data.geojson',
  stroked: true,
  filled: true,
  getFillColor: [200, 0, 80],
  getLineColor: [255, 255, 255],
  pointRadiusMinPixels: 4
});

Deck.gl способен напрямую загружать GeoJSON по URL, автоматически определяя структуру.

Преобразование JSON в оптимизированные структуры

Для высоконагруженных сцен JSON-данные часто преобразуются в плоские массивы. Это снижает стоимость вычислений в getPosition и других accessor-функциях.

Исходная структура:

const raw = [
  { coordinates: [37.6, 55.75], value: 10 },
  { coordinates: [30.3, 59.9], value: 20 }
];

Оптимизированная:

const optimized = new Float32Array(raw.length * 3);

raw.forEach((d, i) => {
  optimized[i * 3] = d.coordinates[0];
  optimized[i * 3 + 1] = d.coordinates[1];
  optimized[i * 3 + 2] = d.value;
});

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

new ScatterplotLayer({
  data: {length: raw.length},
  getPosition: i => [
    optimized[i * 3],
    optimized[i * 3 + 1]
  ],
  getRadius: i => optimized[i * 3 + 2] * 100
});

Такой подход уменьшает количество обращений к объектам JavaScript и повышает производительность рендеринга.

Смешивание CSV и JSON источников

В реальных сценариях данные часто поступают из разных источников: CSV из аналитических выгрузок и JSON из API. Объединение требует нормализации схемы.

const csvData = await load('a.csv', CSVLoader);
const jsonData = await fetch('b.json').then(r => r.json());

const merged = [
  ...csvData.map(d => ({
    position: [Number(d.lon), Number(d.lat)],
    value: Number(d.value)
  })),
  ...jsonData.map(d => ({
    position: d.coordinates,
    value: d.value
  }))
];

После унификации структура становится независимой от источника, что упрощает передачу в Deck.gl слои.

Обработка неконсистентных данных

CSV и JSON часто содержат пропуски, строки вместо чисел и неверные координаты. Перед визуализацией выполняется фильтрация:

const valid = merged.filter(d => {
  return (
    Array.isArray(d.position) &&
    isFinite(d.position[0]) &&
    isFinite(d.position[1]) &&
    isFinite(d.value)
  );
});

Дополнительно применяется ограничение диапазонов координат:

const bounded = valid.filter(d =>
  d.position[0] >= -180 &&
  d.position[0] <= 180 &&
  d.position[1] >= -90 &&
  d.position[1] <= 90
);

Использование данных в агрегирующих слоях

CSV и JSON часто используются не только для точек, но и для агрегированной визуализации, например HexagonLayer:

import {HexagonLayer} from '@deck.gl/aggregation-layers';

new HexagonLayer({
  id: 'hex',
  data: bounded,
  getPosition: d => d.position,
  getWeight: d => d.value,
  radius: 10000,
  elevationScale: 50
});

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

Асинхронная подгрузка и обновление слоёв

Deck.gl поддерживает динамическое обновление данных при изменении источника:

let layer;

async function upd ate() {
  const data = await load(DATA_URL, CSVLoader);

  layer = new ScatterplotLayer({
    id: 'dynamic',
    data: data.map(d => ({
      position: [Number(d.lon), Number(d.lat)],
      value: Number(d.value)
    })),
    getPosition: d => d.position,
    getRadius: d => d.value * 500
  });

  deck.setProps({layers: [layer]});
}

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

Кэширование и оптимизация загрузки

При повторных запросах CSV и JSON важно избегать лишней загрузки. Используются браузерный cache и мемоизация:

const cache = new Map();

async function loadData(url, loader) {
  if (cache.has(url)) return cache.get(url);

  const data = await load(url, loader);
  cache.se t(url, data);
  return data;
}

Это особенно критично при работе с тайловыми или сегментированными наборами данных.

Интероперабельность форматов и единая модель данных

Ключевая идея при работе с CSV и JSON в Deck.gl заключается в приведении всех источников к единому контракту данных:

{
  position: [lng, lat],
  value: number,
  category: string,
  timestamp: number
}

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