Фильтрация треков 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#-проект, который мы можем включить в состав решения (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#.