На главную

Фильтрация треков GPS на F#, Часть IV

Интеграция с C#, Azure и Xamarin

Код написанны на F# можно вызывать из других проектов, написанных на других языках .NET и, в частности, на C#.

Но и весь проект может быть написан на F#. На платформе .NET есть выбор среди языков программирования. На мой взгляд, C# больше подходит для решения классических императивных задач, а F# больше подходит для функциональных вставок.

Для интеграции с проектами на C# или VB.NET нам нужен класс с публичным методом.

/// <summary>
/// Implements a methods of fixation of GPS tracks.
/// </summary>
type GpsTrackFilter() =
    /// <summary>Fixes a GPS track.</summary>
    /// <param name="points">Source GPS track with possible bad data.</param>
    /// <returns>Fixed track.</returns>
    member __.Filter(points: seq<SensorItem>): IReadOnlyList<SensorItem> =
           points
        |> List.ofSeq
        |> removeZeroOrNegativeTimespans
        |> removeZeroSpeedDrift zeroSpeedDrift
        |> removeOutlineSpeedValues outlineSpeed
        |> smoothByKalman modelPrecision sensorPrecision
        :> IReadOnlyList<SensorItem>

Класс называется GpsTrackFilter, а метод фильтрации — скучно и неметафорично — просто Filter.

Поскольку класс описан как GpsTrackFilter() у него есть конструктор по умолчанию, то есть конструктор без параметров.

Метод Filter очень прост — очередь из функций, связанных оператором |>, с которой мы уже встречались.

Функция List.ofSeq превращает последовательность точек в список. Последовательностью (sequence, seq) в F# называется то, что в C# прячется за обобщённым интерфейсом IEnumerable<T>.

В конце очереди мы видим оператор :> который приводит результат к указанному типу.

Код метода Filter читается так: взять последовательность точек (points), преобразовать их в список, вызвать функции стабилизации и фильтрации и привести результат к типу IReadOnlyList<SensorItem>.

Несложно, но остаются вопросы. При вызове removeZeroSpeedDrift мы передаём в функцию параметр zeroSpeedDrift, а откуда мы его берём?

Да, в GpsTrackFilter не хватает нескольких свойств, с помощью которых пользователь класса мог бы настраивать параметры стабилизации и фильтрации. Исправим это упущение. В начало класса, перед методом Filter вставим изменяемые (mutable) поля и связанные с ними свойства.

let mutable zeroSpeedDrift = 7.99
let mutable outlineSpeed = 110.0
let mutable modelPrecision = 2.13
let mutable sensorPrecision = 0.77


/// <summary>
/// Gets or sets the minimal valid velocity for the filter of zero speed drift.
/// </summary>
member __.ZeroSpeedDrift with get () = zeroSpeedDrift
                          and set value = zeroSpeedDrift <- value


/// <summary>
/// Gets or sets the maximal valid velocity for the filter of outline speed.
/// </summary>
member __.OutlineSpeed with get () = outlineSpeed
                        and set value = outlineSpeed <- value


/// <summary>
/// Gets or sets the precision of the moving model of the Kalman's filter.
/// </summary>
member __.ModelPrecision with get () = modelPrecision
                          and set value = modelPrecision <- value


/// <summary>
/// Gets or sets the precision of the GPS sensor of the Kalman's filter.
/// </summary>
member __.SensorPrecision with get () = sensorPrecision
                           and set value = sensorPrecision <- value

В C# мы пишем очень похожий код. Значения, присвоенные полям по умолчанию — речь про минимальную скорость, максимальную скорость, погрешность модели и погрешность датчика — я подбирал, опираясь на треки, записанные нашими автомобилями.

F#-проект

У нас готов F#-проект, который мы можем включить в состав решения (solution). Из соседнего проекта на C# мы, добавив ссылку (Add Reference), можем обращаться к классу GpsTrackFilter.

var filter = new GpsTrackFilter();
filter.ModelPrecision = 1.2;
filter.OutlineSpeed = 108.0;
var filteredTrack = filter.Filter(rawTrack);

Пакеты в .NET

Написанный нами код может быть использован в разных проектах. Мы сделаем из него пакет NuGet, чтобы любой программист мог обращаться к нему из своей программы.

Раньше для этого надо было подготовить описание пакета в текстовом файле с расширением .nuspec, но те времена уже в прошлом. Сейчас необходимые данные мы указываем в файле проекта .fsproj.

Добавим в корень проекта текстовый файл LICENSE с лицензией — наш проект с открытым исходным кодом, поэтому нам подойдут GNU GPL, MIT, Unlicense и некоторые другие. Я выбрал MIT.

Для чего нам нужна лицензия? Мы отдаём пакет в открытое пользование, и должны описать свои намерения компаниям и программистам-одиночкам, которые захотят его использовать.

Мы можем настроить параметры пакета на вкладке Package в свойствах пакета, как показано на скриншоте.

Свойства пакета

Мы можем описать их в файле проекта .fsproj.

<PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <Authors>Mark Shevchenko</Authors>
    <Company>Binateq</Company>
    <Description>Implements a few methods of stabilization and filtration of GPS tracks.</Description>
    <Copyright>Copyright (c) 2018–2019 Binateq</Copyright>
    <PackageLicenseFile>LICENSE</PackageLicenseFile>
    <PackageProjectUrl>https://github.com/binateq/gps-track-filter</PackageProjectUrl>
    <RepositoryUrl>https://github.com/binateq/gps-track-filter.git</RepositoryUrl>
    <RepositoryType>git</RepositoryType>
    <PackageTags>GPS, filtration, stabilization, Kalman</PackageTags>
    <PackageReleaseNotes>Fix bug with too large positive timespan.</PackageReleaseNotes>
    <Version>1.5.1</Version>
    <AssemblyVersion>1.5.1.0</AssemblyVersion>
    <FileVersion>1.5.1.0</FileVersion>
</PropertyGroup>

Создатим пакет. Щёлкните правой кнопкой мыши на названии проекта и выберите пункт Pack. В папке bin\Debug или bin\Release появится файл с расширением .nupkg.

Чтобы опубликовать пакет в галерее NuGet нам нужна регистрация и секретный ключ. Предположу, что у вас они есть.

Вызовем из командной строки команду donet nuget с правильными параметрами.

dotnet nuget push Binateq.GpsTrackFilter.1.5.1.nupkg -k XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -s https://api.nuget.org/v3/index.json

Если ошибок нет, пакет станет доступен через несколько минут. Вам остаётся только подключить его к своему проекту.

Реальный пакет с кодом, который мы обсуждали в этих статьях, носит название Binateq.GpsTrackFilter, его исходники доступны на GitHub. Мы используем его в приложении такси, как на сервере, так и на клиенте. Серверный код работает в облачном хостинге Azure, а клиентский — на мобильных устройствах под управлением Android — он написан на Xamarin.

Заключение

Мы научились вызывать код на F# из C# и превращать его в пакет. Наша программа, написанная на функциональном языке программирования, практически близнеце OCaml, может быть запущена на всех платформах, где работает .NET.

Мы можем ускорить и упростить разработку своих проектов, если будем решать задачи на подходящих языках программирования. В этом цикле статей мы продемонстрировали, насколько просто задача фильрации треков GPS решается на F#.