Работа с табличными данными в формате 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)
}));
После нормализации данных их можно передавать в визуальные слои. Один из базовых вариантов — 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-данные часто требуют дополнительной очистки: удаления пустых строк, фильтрации некорректных координат и обработки локальных форматов чисел (например, запятая вместо точки).
При работе с файлами десятков и сотен мегабайт важен потоковый разбор, чтобы не блокировать основной поток. 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 является более естественным форматом для 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-структуры позволяют сократить количество промежуточных преобразований:
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 является частным случаем 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-данные часто преобразуются в плоские массивы. Это снижает стоимость вычислений в 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 из 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
}
После этого визуальные слои перестают зависеть от формата источника и работают только с абстрактной моделью.