18.04.2017 Программируем с использованием GDAL/OGR на C# (часть 2). Получение пространственных данных
Что нужно знать?
Это вторая статья из рубрики Программируем с использованием GDAL/OGR на C#. В первой части рассказывалось о том, что собой представляет данная библиотека и для чего она нужна, а также был описан процесс установки библиотеки GDAL/OGR и настройки проекта под Visual Studio 2017. Читатель также мог ознакомиться с основными ошибками, которые могут возникнуть в процессе установки. В частности, по устранению одной коварной ошибки даже было снято видео, которое вы можете увидеть в моей первой статье по данной теме: Программируем с использованием GDAL/OGR на C# (часть 1). Установка и настройка. Также я буду предполагать, что у вас есть база данных в MS SQL Server MySpatialDb и таблица tm_world_borders_simpl, в которую уже загружены пространственные данные из shape-файла TM_WORLD_BORDERS_SIMPL-0.3.shp с помощью программы ogr2ogr. О том как это сделать, я рассказывал ранее в статье Подключаем MapServer к MS SQL.
Пишем программу для получения геоданных с использованием GDAL/OGR на C#
За основу возьмем наш пример из прошлой статьи. Я буду отталкиваться от примера ручной установки библиотеки. Чтобы долго не ходить вокруг да около, я сразу приведу исходный код, а потом уже разберем, что происходит в программе и опишу используемые в ней классы и методы. Вначале создадим проект по типу консольного приложения, как описано в первой части, а затем скопируем туда следующий код:
using OSGeo.OGR;
using System;
namespace GDALDemoRead
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage: GDALDemoRead <datasource> <layername>");
PauseAndExit(0);
}
Ogr.RegisterAll();
int errCode = 0;
using (DataSource ds = Ogr.Open(args[0], 0))
{
if (ds == null)
{
Console.WriteLine("Open failed");
errCode = 1;
}
if (errCode == 0)
{
Layer layer = ds.GetLayerByName(args[1]);
if (layer == null)
{
Console.WriteLine("Layer not found");
errCode = 2;
}
if (errCode == 0)
{
int n = 1;
Feature feature;
while ((feature = layer.GetNextFeature()) != null)
{
Console.WriteLine($"Feature #{n}");
PrintAttributes(feature);
PrintGeometry(feature);
n++;
}
}
}
}
PauseAndExit(0);
}
static void PauseAndExit(int errCode)
{
Console.ReadLine();
Environment.Exit(errCode);
}
static void PrintAttributes(Feature feature)
{
ConsoleColor oldColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(" Attributes:");
Console.ForegroundColor = oldColor;
int fieldsCount = feature.GetFieldCount();
for (int i = 0; i < fieldsCount; i++)
{
object val = null;
string fName = feature.GetFieldDefnRef(i).GetName();
FieldType fType = feature.GetFieldType(i);
switch (fType)
{
case FieldType.OFTString:
val = feature.GetFieldAsString(i);
break;
case FieldType.OFTInteger:
val = feature.GetFieldAsInteger(i);
break;
case FieldType.OFTInteger64:
val = feature.GetFieldAsInteger64(i);
break;
case FieldType.OFTReal:
val = feature.GetFieldAsDouble(i);
break;
default:
val = "<unknown>";
break;
}
Console.WriteLine($" Name:{fName}, Value:{val}");
}
}
static void PrintGeometry(Feature feature)
{
ConsoleColor oldColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(" Geometry:");
Console.ForegroundColor = oldColor;
PrintGeometry(feature.GetGeometryRef(), " ");
}
static void PrintGeometry(Geometry geom, string padding)
{
Console.WriteLine($"{padding}GeometryName:{geom.GetGeometryName()}, GeometryType:{geom.GetGeometryType()}");
int pointCount = geom.GetPointCount();
for (int j = 0; j < pointCount; j++)
{
double[] points = new double[3];
geom.GetPoint(j, points);
Console.WriteLine($"{padding}x:{points[0]}, y:{points[1]}");
}
int geomCount = geom.GetGeometryCount();
for (int i = 0; i < geomCount; i++)
{
PrintGeometry(geom.GetGeometryRef(i), padding + " ");
}
}
}
}
Теперь давайте получим информацию из нашего хорошо знакомого shape-файла TM_WORLD_BORDERS_SIMPL-0.3.shp. Предполагая, что архив распакован в каталог C:\shapes запустим программу со следующими аргументами.
GDALDemoRead C:\shapes\TM_WORLD_BORDERS_SIMPL-0.3.shp TM_WORLD_BORDERS_SIMPL-0.3
Если все прошло успешно, то в консоли вы должны увидеть атрибутивную информацию объектов, их тип и координаты. Т.е. примерно что-то такое:
Feature #1
Attributes:
Name:FIPS, Value:AC
Name:ISO2, Value:AG
Name:ISO3, Value:ATG
Name:UN, Value:28
Name:NAME, Value:Antigua and Barbuda
Name:AREA, Value:44
Name:POP2005, Value:83039
Name:REGION, Value:19
Name:SUBREGION, Value:29
Name:LON, Value:-61,783
Name:LAT, Value:17,078
Geometry:
GeometryName:MULTIPOLYGON, GeometryType:wkbMultiPolygon
GeometryName:POLYGON, GeometryType:wkbPolygon
GeometryName:LINEARRING, GeometryType:wkbLineString
x:-61,686668, y:17,0244410000002
x:-61,887222, y:17,105274
x:-61,7944489999999, y:17,1633300000001
x:-61,686668, y:17,0244410000002
GeometryName:POLYGON, GeometryType:wkbPolygon
GeometryName:LINEARRING, GeometryType:wkbLineString
x:-61,7291719999999, y:17,608608
x:-61,853058, y:17,5830540000001
x:-61,873062, y:17,7038880000001
x:-61,7291719999999, y:17,608608
...
Давайте рассмотрим, что же происходит в программе. После того, как мы проверяем наличие двух входных аргументов, осуществляется регистрация драйверов для доступа к векторным данным - строка Ogr.RegisterAll();. Далее, с помощью статического метода Ogr.Open(args[0], 0) мы открываем файл или иной источник векторных данных с помощью одного из зарегистрированных драйверов и получаем объект, представляющий собой источник данных. В нашем случае в первом аргументе мы передали путь к shape-файлу, а во втором аргументе указывается уровень доступа. 0 - доступ только для чтения, 1 - для чтения и записи. После окончания работы с источником данных у объекта DataSource необходимо вызвать метод Dispose, который освобождает все связанные с ним неуправляемые ресурсы. В нашем случае данный метод вызывается автоматически после выхода из блока using, поскольку класс DataSource реализует интерфейс IDisposable.
После проверки на успешность открытия, с помощью метода ds.GetLayerByName(args[1]) получаем объект слоя по его имени, переданном во втором аргументе при запуске программы. Аналогично выполняем проверку на успешность получения слоя и далее исследуем все объекты этого слоя посредством последовательного перебора с помощью метода layer.GetNextFeature(). Методами PrintAttributes(feature) и PrintGeometry(feature), которые определены ниже, мы отображаем атрибутивную и пространственную информацию соответственно.
Рассмотрим метод PrintAttributes. С помощью метода feature.GetFieldCount() мы получаем количество полей, содержащих атрибутивную информацию. Далее, в цикле проходим по всем полям и отображаем по ним информацию. С помощью метода feature.GetFieldDefnRef(i) получаем объект описания текущего поля и затем получаем имя поля с помощью метода GetName(). Вызовом feature.GetFieldType(i) получаем тип поля, и затем, в зависимости от типа получаем уже соответствующее значение поля. В данном примере я ограничился только извлечением значений для типов string, int, long, double. И в заключении, информация об имени поля и его значении выводятся на консоль.
О методе PrintGeometry. Из метода Main вызывается метод PrintGeometry(feature), а уже из этого метода вызывается перегруженная версия PrintGeometry(feature.GetGeometryRef(), " ");, которая рекурсивно проходит по вложенным объектам Geometry и выводит на консоль информацию о названии, типе и координатах. Метод класса Feature GetGeometryRef() возвращает объект типа Geometry, который, в свою очередь, может содержать в себе другие объекты геометрии. Чтобы узнать количество вложенных объектов типа Geometry, используется метод geom.GetGeometryCount(), а чтобы получить ссылку на вложенный экземпляр Geometry применяется метод geom.GetGeometryRef(i). Методы geom.GetGeometryName() и geom.GetGeometryType() позволяют получить имя WKT и тип геометрии соответственно. geom.GetPointCount() возвращает количество точек в объекте типа Geometry, а метод geom.GetPoint(j, points) позволяет получить 3 координаты точки (x, y, z) по индексу j в массив points типа double.
Как мне кажется, GDAL/OGR API достаточно понятное, так что большую часть основных моментов можно прояснить, исследуя методанные сборки. А теперь давайте отобразим ту же самую информацию, но уже из БД MS SQL Server.
Получаем информацию о пространственных данных с помощью программы на C# из MS SQL Server
На данный момент у вас в MS SQL Server должна существовать БД MySpatialDb, в которой содержится таблица tm_world_borders_simpl. Эта таблица содержит те же самые пространственные данные, что и shape-файл TM_WORLD_BORDERS_SIMPL-0.3.shp. О том, как загрузить пространственные данные из shape-файлов в таблицу в виде типа geometry, упоминалось в статье Подключаем MapServer к MS SQL.
Теперь все, что нам нужно сделать, чтобы отобразить информацию о геоданных из БД MS SQL Server - это запустить нашу программу с другими аргументами командной строки:
GDALDemoRead "MSSQL:server=.\SQLEXPRESS;uid=sa;pwd=12345;database=MySpatialDb;Integrated Security=false;tables=tm_world_borders_simpl(ogr_geometry)" tm_world_borders_simpl
Результат будет точно такой, как и ранее при чтении информации из shape-файла.
Итак, библиотека GDAL/OGR предоставляет замечательный унифицированный API (слой абстракции) для доступа к различным источникам пространственных данных. Мы с легкостью поменяли наше хранилище геоданных, при этом не внесли никаких изменений в написанный код.
Файлы для загрузки:
- Библиотека GDAL/OGR x64 - архив с библиотекой GDAL/OGR x64 версии 2.1.3.
- TM_WORLD_BORDERS_SIMPL-0.3.zip (shape) - территориальные границы стран (полигоны).
Предыдущие статьи по теме разработки ГИС с использованием GDAL/OGR: