ArcObjects и LINQ
Задача.
Допустим у нас есть полигональный слой Polygons, объекты которого разбиты по группам - каждому объекту приписан тип (целочисленное поле Type).
Для каждой группы необходимо посчитать суммарную площадь входящих в неё полигонов.
Затем для каждого полигона необходимо вычислить отношение его площади к общей площади группы, в которую он входит. Полученное значение необходимо записать в поле NormArea.
Для решения подобных задач обработки данных в .NET 3.5 есть ряд удобных средств, например технология LINQ.
Только вот ArcObjects пока не предоставляет никаких средств для поддержки этой технологии.
Поэтому предлагаю ещё немного побаловаться и сделать следующее:
1. Добавить поддержку индексаторов, чтобы вместо:
int fldArea = feature.FindField("Area")
double area = (double)feature.get_Value(fldArea);
писать:
double area = (double)feature["Area"];
2. добавить поддержку оператора foreach для курсоров, чтобы вместо:
int fldArea = pFeatureCursor.FindField("Area");
IFeature pFeature = pFeatureCursor.NextFeature();
while (pFeature != null)
totalArea += (double)feature.get_Value(fldArea);
Marshal.ReleaseComObject(pFeatureCursor);
писать:
foreach (var feature in features)
totalArea += (double)feature["Area"];
3. добавить поддержку запросов LINQ, как с помощью синтаксиса, облегчающего восприятие:
var ids = from feature in features
where (double)feature["Area"] > 100
orderby feature["Name"] descending
select (string)feature["Name"];
так и в форме расширяющих методов с лямбда-выражениями:
var ids = features.Where(f => (double) f["Area"] > 100).OrderByDescending(f => f["Name"]).Select(f => (string) f["Name"]);
4. добавить поддержку нескольких идущих подряд запросов к одному курсору:
double totalArea = 0.0;
foreach (var feature in features)
totalArea += (double)feature["Area"];
var ids = from feature in features
where (double)feature["Area"] > 100
orderby feature["Name"] descending
select (string)feature["Name"];
5. Добавить поддержку оператора using для курсора:
using (var features = featureLayer.SearchEx(null))
{
...
}
6. Забыть про необходимость вызова метода ReleaseComObject для курсора в случае перебора как с помощью foreach (даже если вы выходите из цикла с помощью break), так и с помощью запросов LINQ. Даже если вы не используете оператор using.
Итак...
Менять стандартные объекты мы не можем. Поэтому будем оборачивать (декорировать) их новыми объектами, попутно добавляя новый фунционал.
Начнём с интерфейса IFeatureLayer, добавим ему расширяющий метод:
public static class IFeatureLayerExtensions
{
public static IFeatureCursorEx SearchEx(this IFeatureLayer pFeatureLayer, IQueryFilter filter)
{
return new FeatureCursorEx(pFeatureLayer.FeatureClass, filter, false);
}
}
То же самое можно проделать и с IFeatureCursor.
Здесь мы ввели новый интерфейс IFeatureCursorEx и объект FeatureCursorEx.
IFeatureCursorEx обернёт IFeatureCursor, а так же потребует реализацию интерфейса IEnumerable<T>. Это необходимо для поддержки перебора оператором foreach и методами LINQ (п.2 и п.3).
Вместе с тем, мы объявим необходимость реализации интерфейса IDisposable, который позволит вызывать код очистки ресурсов при завершении работы с объектом (п.5 и п.6).
public interface IFeatureCursorEx : IFeatureCursor, IEnumerable<FeatureEx>, IEnumerator<FeatureEx>, IDisposable
{
}
Что касается реализации.
Для возможности "перезапуска" курсора мы должны сохранить исходные IFeatureClass, IQueryFilter и параметр Recycling.
Должны автоматически инициализировать стандартный курсор, когда он нужен, иметь возможность его автоматически перезапускать, корректно удалять его после прохода по всем записям, а также при уничтожении самого объекта.
Не буду копировать сюда весь код класса, потому что он достаточно объемный, хоть и не сложный, и ссылка на исходник приведена далее. Рассмотрим лишь реализацию метода получения очередного объекта:
public IFeature NextFeature()
{
_current = null;
if (_isAttached == false)
Attach();
IFeature pFeature = _cursor.NextFeature();
if (pFeature != null)
_current = new FeatureEx(pFeature);
else
Detach();
return _current;
}
Проверяем, подключен (инициализирован) ли курсор. Если нет, то инициализируем (п.4).
Если следующий объект оказывается равен null, то это означает, что записи кончились. В этом случае мы отключаем (удаляем) курсор (п.5, п.6).
Заметьте, что мы возвращаем созданный объект FeatureEx.
FeatureEx обёртывает IFeature с целью добавления индексаторов (п.1).
Класс FeatureEx обеспечивает поддержку сразу двух типов индексаторов, числового:
public object this[int index]
{
get
{
return this.get_Value(index);
}
set
{
this.set_Value(index, value);
}
}
и текстового:
public object this[string name]
{
get
{
return this.get_Value(this.Fields.FindField(name));
}
set
{
this.set_Value(this.Fields.FindField(name), value);
}
}
Заметьте, этот индексатор каждый раз заново определяет номер поля. Для увеличения производительности попробуйте кэшировать значения либо с помощью обобщенного Dictionary<TKey, TValue>, либо, например, с помощью HybridDictionary.
Поскольку объект, реализующий IFeature может реализовывать ещё ряд интерфейсов, которые мы оборачивать не собираемся, то наша обёртка должна обеспечивать возможность получения исходного объекта (свойство Feature).
public IFeature Feature
{
get
{
return _feature;
}
private set
{
_feature = value;
}
}
Ну а все прочие свойства просто оборачиваются:
public IObjectClass Class
{
get { return _feature.Class; }
}
Всё.
Вернёмся