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: