Работа с CSV в QGIS

Что-то в последнее время стало появляться много вопросов по работе с файлами формата CSV в QGIS. Внесу и я свою скромную лепту в освещение этой запутанной истории. Букв много, так что продолжение под катом.

Итак, что же такое CSV? CSV (англ. comma separated values — значения, разделенные запятыми) это текстовый формат, предназначенный для хранения табличных данных. Файл CSV состоит из записей, каждая из который в свою очередь состоит из полей, разделенных символом-разделителем. Несмотря на всю свою простоту и существование RFC 4180, CSV не является единым четко описанным форматом. На практике словом CSV обозначают любой файл, содержащий значения, разделенные каким-угодно разделителем.

Файл, соответствующий RFC 4180:

XCOORD,YCOORD,CATEGORY,NAME,TYPE
86.587951,49.809867,Туризм,Белуха Пик,
86.593039,51.417530,Туризм,Айрык - Лунная долина,Кемпинг
86.381672,51.447578,Туризм,Замки горных духов,Точка обзора
86.400312,51.478423,Туризм,г. Озёрный белок 2135м,Пик

Те же данные, но с другими разделителями (не соответствует требованиями RFC 4180, но все же является файлом CSV)

XCOORD;YCOORD;CATEGORY:NAME;TYPE
86,587951;49,809867;Туризм;Белуха Пик;
86,593039;51,417530;Туризм;Айрык - Лунная долина;Кемпинг
86,381672;51,447578;Туризм;Замки горных духов;Точка обзора
86,400312;51,478423;Туризм;г. Озёрный белок 2135м;Пик

С форматом более-менее разобрались. Посмотрим, как эти файлы можно использовать в QGIS.

Техническое отступление. Работа с различными источниками данных в QGIS реализована через так называемые «провайдеры». Это обычные подключаемые библиотеки, реализующие ряд функций. Одни провайдеры позволяют работать только с определенным источником данных, например, с базами PostgreSQL/PostGIS, другие — поддерживают несколько, например, OGR. Поэтому, очень часто один и тот же файл можно открыть двумя разными способами. А из-за того, что разные провайдеры имеют разный набор возможностей (capabilities), то открыв файл с использованием не того провайдера можно хм… разочароваться и начать писать гневные отзывы на форумах или в списки рассылки. Конец отступления, переходим в наступление.

В QGIS имеется два провайдера, понимающих формат CSV:

  • delimitedtext (специализированный)
  • ogr (универсальный)

Чтобы использовать провайдер delimitedtext надо активировать модуль «Add Delimited Text Layer». Для работы с провайдером ogr никаких дополнительных модулей загружать не нужно.

Провайдер delimitedtext

Провайдер позволяет открывать только CSV-файлы, содержащие пространственную составляющую. Это могут быть либо столбцы с координатами X и Y точек, либо столбец с описанием геометрии в формате WKT (Well-Known Text). Открыть обычные табличные данные, например, с целью присоединения к атрибутивной таблице другого слоя, с его помощью не получится. Еще одним ограничением является отсутствие возможности какого-либо редактирования слоёв, созданных с его помощью. Разумеется, можно экспортировать созданный слой в другой формат, например shape-файл, но тут есть свои особенности, о них ниже.

Как уже было сказано, чтобы открыть файл с использованием этого провайдера необходимо активировать специальный модуль. Поэтому сначала идем в «Plugins → Manage plugins», в окне менеджера модулей находим необходимый модуль, и, если он не активирован, активируем. Модуль добавляет свою кнопку на панель «Layers» и в меню «Layer».

Работа с модулем достаточно подробно описана в Руководстве пользователя QGIS, поэтому сильно расписывать не буду. После нажатия на кнопку расширения появится вот такое окно.

В поле «File Name» указывается импортируемый CSV-файл; поле «Layer Name» заполняется автоматически, имя слоя будет совпадать с именем файла без суффикса, при желании/необходимости его можно изменить. Сразу же после выбора файла модуль выполнит его анализ, используя заданные разделители и отобразит результат в области предпросмотра в нижней части окна. На этом этапе возможны две ситуации:

  • разделители выбраны правильно, в области предпросмотра данные отображаются как нужно
  • разделители в файле и используемые модулем не совпадают, данные в области предпросмотра отображаются не правильно (столбцы «склеились» или появились лишние)

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

Если нужны не все данные, а только часть — можно указать номер строки, с которой начинать импорт. Правда, при этом нужно помнить, что с нормалными заголовками столбцов в этом случаем придется распрощаться: вместо них будет содержимое предыдущей строки. В случае необходимости в поле «Decimal point» указываем используемый разделитель целой и дробной части. Ну и наконец, в зависимости от того, в каком виде у нас пространственная составляющая, указываем поля с координатами X и Y (если у нас точечные данные, и координаты заданны отдельными столбцами) или поле с описанием геометрии в формате WKT (любая поддерживаемая QGIS геометрия). В обоих случаях координаты могут быть в любой СК.

После нажатия кнопки «OK» новый слой будет создан и добавлен на карту, а пользователю будет предложено указать систему координат для новосозданного слоя. После этого с данными можно будет работать как и с любым другим слоем. Также необходимо отметить, что модуль распознает поля с разными данными и создает поля соответствующих типов (Integer, Real, String), но управление этим процессом и тонкая настройка невозможны.

При экспорте слоя, созданного при помощи модуля «Add Delimited Text Layer», в другой формат необходимо учитывать особенности соответствующего драйвера OGR. Например, драйвер shape-файлов по умолчанию создаёт тектовые поля с длиной 80 символов, что в некоторых случаях может приводить к потере информации при экспорте.

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

Провайдер ogr

Этот провайдер позволяет открывать CSV-файлы любого типа: как содержащие пространственную составляющую, так и без нее. В качестве источника данных может выступать как отдельный файл, так и каталог с файлами. Чтобы каталог можно было использовать в качестве источника данных, как минимум половина находящихся в нем файлов должна быть файлами CSV. При этом каждый файл будет открываться как отдельный слой/таблица.

GDAL необходимо, чтобы файлы имели определенный формат:

  • одна строка файла соответствует одной записи
  • в строке должно быть как минимум два поля
  • строки оканчиваются переводом строки DOS (CR/LF) или UNIX (LF)
  • количество полей во всех записях должно быть одинаковым
  • значения полей должны быть разделены запятыми («,»). Начиная с GDAL 1.7.0 допускается использование в качестве разделителя точки с запятой («;») или табуляции. Но тут есть маленькая тонкость: автоопределение разделителя будет работать только в том случае, если в первой строке файла не будет других потенциальных разделителей
  • «сложные» значения атрибутов (т.е. содержащие запятые, разрывы строки, кавычки…) должны заключаться в двойные кавычки. Если значение атрибута содержит двойные кавычки их необходимо «экранировать» добавляя к каждой двойной кавычке еще одну такую же

При открытии файла GDAL пытается получить имена полей из первой строки. Однако, если одно или несколько полей числовые, первая строка также трактуется как данные, а имена полей генерируются по шаблону field_1..field_N. Начиная с GDAL 1.9.0, числа, заключенные в двойные кавычки, трактуются как имена полей.

Таким образом, если файл отвечает перечисленным выше требованиям, его можно открыть в QGIS, используя диалог «Add vector layer». В этом случае файл будет открыт как обычная таблица, а все поля будут иметь строковый тип (String). Чтобы поля имели правильный тип необходимо создать специальный файл с информацией о типе данных каждого поля. Это обычный текстовый файл, имя которого должно совпадать с именем файла CSV, и с расширением .csvt. В нем будет всего одна строчка, в которой через запятую указывается тип данных каждого поля, заключенный в двойные кавычки. Поддерживаются следующие типы данных:

  • целое число (Integer)
  • десятичное число (Real)
  • строка (String)
  • дата в формате YYYY-MM-DD (Date)
  • время в формате HH:MM:SS+nn (Time)
  • дата и время в формате YYYY-MM-DD HH:MM:SS+nn (DateTime)

Для первых трех типов в скобках можно указать длину поля, а для значений Real и количество знаков после запятой. Например, для приведенного выше файла CSV соответствующий .csvt будет иметь вид

"Real","Real","String","String","String"

или с указанием длины и точности

"Real(10.6)","Real(10.6)","String(50)","String(255)","String(50)"

Внимание! Открывать надо .csv, а не .csvt

Файл CSV может содержать пространственную информацию: либо два (три) столбца с координатами X, Y (Z) точек или же поле с описанием геометрии в формате WKT. По умолчанию такие файлы тоже открываются как таблицы. Чтобы открыть его как векторный слой нужно создать специальный XML файл, так называемый VRT-файл.

Корневым элементом VRT-файла является OGRVRTDataSource. Для каждого слоя создается свой вложенный элемент OGRVRTLayer. У элемента OGRVRTLayer обязательно должен присутствовать атрибут name с именем слоя, кроме того, могут использоваться различные вложенные элементы. С полным списком элементов можно ознакомиться на страничке описания драйвера VRT в GDAL, здесь рассмотрим мы только те, которые будут нужны для открытия файлов CSV в виде векторного слоя. Итак:

  • SrcDataSource. Значением элемента является имя набора данных, в нашем случае — путь в файлу CSV. В общем случае, в качестве набора данных может выступать любой OGR-совместимый источник. Элемент может также содержать два необязательных атрибута:
    • relativeToVRT — значение по умолчанию 0 если значение 1, то источник данных будет рассматриваться как находящийся по относительному пути
    • shared — контролирует режим открытия. Для SrcLayer значение по умолчанию OFF
  • SrcLayer. Название слоя в наборе данных, на основе которого создаётся виртуальный слой. При использовании с файлом CSV название слоя должно совпадать с именем файла без суффикса. Например, если файл myfile.csv, то имя слоя будет myfile.
  • GeometryType. Тип геометрии слоя. Если не задан, используется тип геометрии исходного слоя. Поддерживаются следующие значения: wkbNone, wkbUnknown, wkbPoint, wkbLineString, wkbPolygon, wkbMultiPoint, wkbMultiLineString, wkbMultiPolygon и wkbGeometryCollection. При необходимости, если используется координата Z, к перечисленным типам может добавляться суффикс 25D. Значение по умолчанию wkbUnknown, т.е. допускается использование геометрии любого типа.
  • LayerSRS (опциональный). Значением этого элемента является описание системы координат слоя в формате WKT или любом другом, поддерживаемом методом OGRSpatialReference::SetUserInput(). Если не задан, используется система координат исходного слоя. Если указать значение NULL, итоговый слой будет без системы координат.
  • GeometryField (опциональный). Этот элемент описывает геометрию виртуального слоя. Если элемент отсутствует, копируется геометрия исходных объектов. Способ представления геометрии задается атрибутом encoding, который может принимать одно из значений WKT, WKB или PointFromColumns. Если используется WKT или WKB, обязательно должен присутствовать атрибут field, значением которого является имя поля с геометрией в формате WKT или WKB. Если же атрибут encoding имеет значение PointFromColumns, должны присутствовать атрибуты x, y и z, значениями которых являются соответсвенно имена полей с координатами X, Y и Z. Атрибут z необязательный. Начиная с GDAL 1.7.0, также можно указывать необязательный атрибут reportSrcColumn, позволяющий контролировать будут ли исходные поля геометрии (т.е, поля, указанные в атрибутах field, x, y и z) оригинального слоя присутствовать в итоговом слое. Если данный атрибут имеет значение FALSE, исходные поля геометрии будут использоваться исключительно для создания геометрии объектов и не будут отображаться как самостоятельные поля.
  • Field (доступен начиная с GDAL 1.7.0). Используется для описания полей виртуального слоя. Если эти элементы отсутствуют, виртуальный слой будет иметь такой же набор полей, как и исходный файл. Каждый элемент Field может иметь следующие атрибуты:
    • name (обязательно): имя поля
    • type: тип данных поля. Можно принимать значения: Integer, IntegerList, Real, RealList, String, StringList, Binary, Date, Time и DateTime. По умолчанию String
    • width: размер поля, по умолчанию unknown
    • precision: точность поля, по умолчанию 0
    • src: имя исходного поля, значения которого будут использоваться. По умолчанию совпадает со значением атрибута name

Внимание! При использовании файлов VRT открывать надо именно их, а не исходный файл CSV.

Рассмотрим несоколько примеров. Пусть наш файл CSV имеет вид

XCOORD,YCOORD,CATEGORY,NAME,TYPE
86.587951,49.809867,Туризм,Белуха Пик,
86.593039,51.417530,Туризм,Айрык - Лунная долина,Кемпинг
86.381672,51.447578,Туризм,Замки горных духов,Точка обзора
86.400312,51.478423,Туризм,г. Озёрный белок 2135м,Пик

Как видно, координаты точек заданы двумя полями XCOORD и YCOORD. В самом простом случае VRT-файл будет выглядеть так

<OGRVRTDataSource>
  <OGRVRTLayer name="poi">
    <SrcDataSource relativeToVRT="1">poi.csv</SrcDataSource>
    <GeometryType>wkbPoint</GeometryType>
    <LayerSRS>WGS84</LayerSRS>
    <GeometryField encoding="PointFromColumns" x="XCOORD" y="YCOORD"/>
  </OGRVRTLayer>
</OGRVRTDataSource>

При этом все поля исходного файла будут присутствовать и в виртуальном слое, а тип данных у них будет String. Добавим описание полей

<OGRVRTDataSource>
  <OGRVRTLayer name="poi">
    <SrcDataSource relativeToVRT="1">poi.csv</SrcDataSource>
    <GeometryType>wkbPoint</GeometryType>
    <LayerSRS>WGS84</LayerSRS>
    <GeometryField encoding="PointFromColumns" x="XCOORD" y="YCOORD"/>
    <Field name="XCOORD" type="Real" width="10" precision="6"/>
    <Field name="YCOORD" type="Real" width="10" precision="6"/>
    <Field name="CATEGORY" type="String" width="50"/>
    <Field name="NAME" type="String" width="255"/>
    <Field name="TYPE" type="String" width="50"/>
  </OGRVRTLayer>
</OGRVRTDataSource>

Дадим полям удобочитаемые названия

<OGRVRTDataSource>
  <OGRVRTLayer name="poi">
    <SrcDataSource relativeToVRT="1">poi.csv</SrcDataSource>
    <GeometryType>wkbPoint</GeometryType>
    <LayerSRS>WGS84</LayerSRS>
    <GeometryField encoding="PointFromColumns" x="XCOORD" y="YCOORD"/>
    <Field name="Широта" src="XCOORD" type="Real" width="10" precision="6"/>
    <Field name="Долгота" src="YCOORD" type="Real" width="10" precision="6"/>
    <Field name="Категория" src="CATEGORY" type="String" width="50"/>
    <Field name="Наименование объекта" src="NAME" type="String" width="255"/>
    <Field name="Тип объекта" src="TYPE" type="String" width="50"/>
  </OGRVRTLayer>
</OGRVRTDataSource>

Если в исходном файле геометрия записана в формате WKT

WKT_GEOM,CATEGORY,NAME,TYPE
POINT(86.587951 49.809867),Туризм,Белуха Пик,
POINT(86.593039 51.417530),Туризм,Айрык - Лунная долина,Кемпинг
POINT(86.381672 51.447578),Туризм,Замки горных духов,Точка обзора
POINT(86.400312 51.478423),Туризм,г. Озёрный белок 2135м,Пик

vrt-файл изменится незначительно

<OGRVRTDataSource>
  <OGRVRTLayer name="poi">
    <SrcDataSource relativeToVRT="1">poi.csv</SrcDataSource>
    <GeometryType>wkbPoint</GeometryType>
    <LayerSRS>WGS84</LayerSRS>
    <GeometryField encoding="WKT" field="WKT_GEOM"/>
    <Field name="Категория" src="CATEGORY" type="String" width="50"/>
    <Field name="Наименование объекта" src="NAME" type="String" width="255"/>
    <Field name="Тип объекта" src="TYPE" type="String" width="50"/>
  </OGRVRTLayer>
</OGRVRTDataSource>

Стоит отметить еще один момент: при использовании файлов VRT и GDAL >= 1.7.0 можно не создавать .csvt, а прописывать тип данных сразу в .vrt.