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

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

Пример объекта с метаданными:

const point = {
  type: "Feature",
  geometry: {
    type: "Point",
    coordinates: [37.6176, 55.7558]
  },
  properties: {
    name: "Москва",
    population: 13000000,
    capital: true
  }
};

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


Структура Feature и роль свойства properties

Практически все функции Turf.js работают с объектами типа Feature.

Общая структура:

{
  type: "Feature",
  geometry: {
    ...
  },
  properties: {
    ...
  }
}

Секция geometry описывает пространственную форму объекта.

Секция properties содержит произвольный набор данных:

properties: {
  id: 101,
  city: "Москва",
  district: "Центральный",
  population: 13000000,
  isCapital: true
}

Turf.js не накладывает ограничений на содержимое properties. Допускаются:

  • строки;
  • числа;
  • логические значения;
  • массивы;
  • вложенные объекты;
  • значения null.

Пример:

properties: {
  name: "Парк Горького",
  tags: ["park", "recreation"],
  contacts: {
    phone: "+7 999 123-45-67",
    website: "example.com"
  }
}

Создание объектов с метаданными

Большинство фабричных функций Turf.js принимает объект свойств вторым аргументом.

Создание точки

import * as turf from "@turf/turf";

const point = turf.point(
  [37.6176, 55.7558],
  {
    name: "Москва",
    population: 13000000
  }
);

Результат:

{
  type: "Feature",
  geometry: {
    type: "Point",
    coordinates: [37.6176, 55.7558]
  },
  properties: {
    name: "Москва",
    population: 13000000
  }
}

Создание линии

const road = turf.lineString(
  [
    [37.61, 55.75],
    [37.65, 55.77]
  ],
  {
    roadType: "highway",
    lanes: 4
  }
);

Создание полигона

const district = turf.polygon(
  [[
    [37.60, 55.74],
    [37.64, 55.74],
    [37.64, 55.78],
    [37.60, 55.78],
    [37.60, 55.74]
  ]],
  {
    name: "Центральный район",
    code: "CTR"
  }
);

Доступ к метаданным

Доступ к свойствам осуществляется через объект properties.

const cityName = point.properties.name;

console.log(cityName);

Результат:

Москва

Получение нескольких значений:

const population = point.properties.population;
const capital = point.properties.capital;

Изменение метаданных

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

Обновление существующего поля

point.properties.population = 13500000;

Добавление нового поля

point.properties.country = "Россия";

Результат:

properties: {
  name: "Москва",
  population: 13500000,
  country: "Россия"
}

Удаление поля

delete point.properties.population;

После удаления:

properties: {
  name: "Москва"
}

Использование функции propEach

Для обработки большого количества объектов удобно применять функцию propEach.

Она последовательно перебирает свойства всех объектов коллекции.

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

const cities = turf.featureCollection([
  turf.point([37.61, 55.75], {
    name: "Москва"
  }),
  turf.point([30.31, 59.93], {
    name: "Санкт-Петербург"
  })
]);

Обход свойств:

turf.propEach(cities, (properties) => {
  console.log(properties.name);
});

Результат:

Москва
Санкт-Петербург

Получение массива свойств через propReduce

Функция propReduce работает аналогично Array.reduce().

Пример получения списка названий:

const names = turf.propReduce(
  cities,
  (result, properties) => {
    result.push(properties.name);
    return result;
  },
  []
);

console.log(names);

Результат:

[
  "Москва",
  "Санкт-Петербург"
]

Обход объектов вместе с метаданными

Часто требуется работать одновременно с геометрией и атрибутами.

Для этого используется featureEach.

turf.featureEach(cities, (feature) => {
  console.log(feature.properties.name);
});

Можно анализировать и координаты, и свойства:

turf.featureEach(cities, (feature) => {
  console.log(
    feature.geometry.coordinates,
    feature.properties.name
  );
});

Копирование метаданных между объектами

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

Простое копирование

const source = turf.point(
  [37.61, 55.75],
  {
    city: "Москва",
    region: "Центральный"
  }
);

const target = turf.point([38, 56]);

target.properties = {
  ...source.properties
};

Результат:

{
  city: "Москва",
  region: "Центральный"
}

Глубокое копирование

Если свойства содержат вложенные структуры:

const copiedProperties =
  JSON.parse(
    JSON.stringify(source.properties)
  );

Такой подход предотвращает совместное использование ссылок.


Сохранение свойств при геометрических операциях

Большинство функций Turf.js автоматически переносит свойства исходного объекта.

Пример буферизации:

const point = turf.point(
  [37.61, 55.75],
  {
    city: "Москва"
  }
);

const buffer = turf.buffer(
  point,
  5,
  { units: "kilometers" }
);

Полученный объект:

buffer.properties

Содержит:

{
  city: "Москва"
}

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


Создание новых свойств после анализа

Результаты пространственного анализа часто записываются в properties.

Например, вычисление площади полигона:

const polygon = turf.polygon([
  [
    [0, 0],
    [10, 0],
    [10, 10],
    [0, 10],
    [0, 0]
  ]
]);

polygon.properties = {};

polygon.properties.area =
  turf.area(polygon);

Теперь объект содержит вычисленное значение:

{
  area: 1232921098571.292
}

Формирование производных атрибутов

Метаданные могут вычисляться на основе существующих значений.

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

const city = turf.point(
  [37.61, 55.75],
  {
    population: 13000000,
    areaKm2: 2561
  }
);

Вычисление плотности населения:

city.properties.density =
  city.properties.population /
  city.properties.areaKm2;

Результат:

{
  population: 13000000,
  areaKm2: 2561,
  density: 5076.14
}

Фильтрация объектов по метаданным

Очень распространённая задача — отбор объектов по свойствам.

Исходная коллекция:

const collection = turf.featureCollection([
  turf.point([0, 0], {
    type: "capital"
  }),
  turf.point([1, 1], {
    type: "city"
  }),
  turf.point([2, 2], {
    type: "capital"
  })
]);

Фильтрация:

const capitals =
  collection.features.filter(
    feature =>
      feature.properties.type ===
      "capital"
  );

Результат:

[
  Feature,
  Feature
]

Группировка объектов по свойствам

Пример группировки по категории.

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

const places = [
  {
    properties: {
      category: "park"
    }
  },
  {
    properties: {
      category: "park"
    }
  },
  {
    properties: {
      category: "museum"
    }
  }
];

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

const groups = {};

for (const place of places) {
  const category =
    place.properties.category;

  if (!groups[category]) {
    groups[category] = [];
  }

  groups[category].push(place);
}

Результат:

{
  park: [...],
  museum: [...]
}

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

Коллекции объектов могут содержать тысячи элементов.

Пример:

const collection =
  turf.featureCollection([
    turf.point([0, 0], {
      city: "A"
    }),
    turf.point([1, 1], {
      city: "B"
    })
  ]);

Получение списка городов:

const cities =
  collection.features.map(
    feature =>
      feature.properties.city
  );

Результат:

["A", "B"]

Добавление идентификаторов

Для дальнейшей обработки часто используются уникальные идентификаторы.

turf.featureEach(
  collection,
  (feature, index) => {
    feature.properties.id =
      index + 1;
  }
);

После обработки:

{
  city: "A",
  id: 1
}
{
  city: "B",
  id: 2
}

Использование вложенных структур данных

GeoJSON допускает хранение сложных объектов внутри properties.

const building = turf.point(
  [37.61, 55.75],
  {
    address: {
      city: "Москва",
      street: "Тверская",
      house: 10
    },
    owner: {
      name: "Иван Иванов",
      type: "private"
    }
  }
);

Доступ:

console.log(
  building.properties.address.city
);

Результат:

Москва

Объединение наборов метаданных

Иногда необходимо соединить свойства из нескольких источников.

const baseProperties = {
  city: "Москва",
  population: 13000000
};

const analyticsProperties = {
  density: 5076,
  region: "Центральный"
};

Объединение:

const merged = {
  ...baseProperties,
  ...analyticsProperties
};

Результат:

{
  city: "Москва",
  population: 13000000,
  density: 5076,
  region: "Центральный"
}

Проверка наличия свойств

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

if (
  point.properties &&
  point.properties.population
) {
  console.log(
    point.properties.population
  );
}

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

const population =
  point.properties?.population;

Нормализация метаданных

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

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

{
  cityName: "Москва"
}
{
  name: "Санкт-Петербург"
}

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

feature.properties.city =
  feature.properties.cityName ||
  feature.properties.name;

После обработки:

{
  city: "Москва"
}
{
  city: "Санкт-Петербург"
}

Практический сценарий: обогащение объектов результатами анализа

Создаётся коллекция населённых пунктов:

const cities =
  turf.featureCollection([
    turf.point([37.61, 55.75], {
      name: "Москва"
    }),
    turf.point([30.31, 59.93], {
      name: "Санкт-Петербург"
    })
  ]);

Для каждого объекта вычисляется расстояние до условного центра:

const center =
  turf.point([35, 57]);

turf.featureEach(
  cities,
  (feature) => {
    const distance =
      turf.distance(
        center,
        feature,
        {
          units: "kilometers"
        }
      );

    feature.properties.distanceToCenter =
      distance;
  }
);

Полученные объекты содержат одновременно:

  • исходную геометрию;
  • исходные метаданные;
  • результаты пространственного анализа.

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