уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

В последней статье, касающейся App Tethering в Delphi рассмотрены, в основном, теоретические моменты работы с этой технологией и приведены небольшие примеры, которые раскрывают отдельные вопросы использования App Tethering. Недавно появилась возможность применить App Tethering Delphi на практике.

Суть решаемой проблемы следующая: имеется ряд точек на местности, для которых необходимо определить их GPS-координаты и передать полученные данные в приложение под Windows для дальнейшей обработки. При этом приложение должно быть максимально простым в использовании.

Вот здесь-то App Tethering, на мой взгляд подходит как нельзя кстати. 

Предварительный алгоритм работы программы можно описать следующим образом:

  1. Считывает с датчика положения текущие координаты устройства (здесь нам пригодятся навыки работы с TLocationSensor)
  2. По требованию пользователя сохраняем координаты точки в базу данных (здесь нам пригодятся навыки работы с SQLite в Delphi)
  3. Показываем пользователю список сохраненных в базе точек
  4. Пользователь выбирает необходимые ему точки и, используя App Tethering, передает их в Windows-приложение.

Рассмотрим, как реализуется этот простенький алгоритм в Delphi 10.1 Berlin.

Макет приложения Android

Так как наше приложение должно быть максимально простым, то я решил сделать вот такой макет приложения:

Макет приложения Android

В панели сверху три кнопки: слева — кнопка для добавления текущих координат в базу данных, справа — кнопки удаления выбранных в списке точек из базы данных и отправка выбранных записей в Windows-приложение.

Под кнопками расположены два TLayout‘а c компонентами TLabel в которых будут отображаться текущие координаты устройства.

Ниже — список сохраненных в базе данных точек (TListBox).

В качестве библиотеки для работы с базой данных SQLite (а именно её мы и будем использовать в Android) я выбрал LiteDAC три компонента которой вы так же можете увидеть на скриншоте формы — это TLiteConnection, TLiteSQL и TLiteQuery.

Также на форме расположен компонент TLocationSensor для считывания координат местоположения устройства и, конечно, компоненты для работы с App Tethering.

Необходимые разрешения для приложения Android

Чтобы наше приложение могло считывать сведения о текущем местоположении с датчиков и пересылать данные по Сети нам необходимо установить для приложения соответствующие разрешения.

Для того, чтобы установить для Android-приложения необходимые разрешения в Delphi необходимо зайти в настройки проекта:

Project —> Options —> User Permissions

и выбрать настройки: Access Fine Location и Internet

Настройки User Permissions

Чтение координат местоположения. Работа с TLocationSensor в Delphi

Для того, чтобы получить текущие координаты я воспользовался событием компонента TLocationSensor OnLocationChanged:

procedure TForm3.LocationSensor1LocationChanged(Sender: TObject;
const OldLocation, NewLocation: TLocationCoord2D);
begin
  Label3.Text := GPSToString(NewLocation.Latitude) + 'С';
  Label4.Text := GPSToString(NewLocation.Longitude) + 'В';
end;

Функция GPSToString() — вспомогательная функция, которая переводит значение координаты из градусов с десятичными долями в формат ГГ.ММ.SS:

function GPSToString(Degree: double): string;
const
  cGrad = 176;
  cMin = 39;
  cSecond = 34;
var
  Grad: integer;
  Min: integer;
  Second: double;
begin
  Grad := Trunc(Degree);
  Min := Trunc((Degree - Grad) * 60);
  Second := ((Degree - Grad) * 60 - Min) * 60;
  Result := IntToStr(Grad) + chr(cGrad) + IntToStr(Min) + chr(cMin) + StringReplace(FloatToStrF(Second, ffFixed, 5, 2), ',', '.', [rfReplaceAll]) + chr(cSecond);
end;

Получив текущие координаты можно записывать их в базу данных.

База данных SQLite в Android

Для компонента TLiteConnection я установил свойство TLiteConnection.Options.ForceCreateDatabase в значение True и написал следующие обработчики:
перед подключением к БД:

procedure TForm3.LiteConnection1BeforeConnect(Sender: TObject);
begin
  LiteConnection1.Database := TPath.Combine(TPath.GetDocumentsPath, 'pdv_coords.sqlite');
end;

После подключения:

procedure TForm3.LiteConnection1AfterConnect(Sender: TObject);
begin
  LiteConnection1.ExecSQL('CREATE TABLE IF NOT EXISTS [data] ('
    + ' [id] INTEGER PRIMARY KEY AUTOINCREMENT,'
    + ' [latitude] DOUBLE,'
    + ' [longtitude] DOUBLE,'
    + ' [caption] TEXT);');
  RefreshList;
end;

Здесь метод RefreshList — вспомогательный и используется для обновления списка сохраненных в БД точек:

procedure TForm3.RefreshList;
begin
  ListBox1.Items.Clear;
  LiteQuery1.Close;
  LiteQuery1.SQL.Text := 'SELECT * FROM data';
  LiteQuery1.Open;
  LiteQuery1.First;
  while not LiteQuery1.Eof do
  begin
    PointToList(LiteQuery1.FieldByName('caption').AsString,
                LiteQuery1.FieldByName('id').AsInteger,
                LiteQuery1.FieldByName('latitude').AsFloat,
                LiteQuery1.FieldByName('longtitude').AsFloat);
    LiteQuery1.Next;
  end;
end;

В свою очередь PointToList() добавляет одну точку в список:

procedure TForm3.PointToList(const Caption: string; ID: integer; Lat, Long: double);
var
  LI: TListBoxItem;
begin
  LI := ListBox1.ListItems[ListBox1.Items.Add(Caption)];
  LI.ItemData.Accessory := TListBoxItemData.TAccessory.aDetail;
  LI.ItemData.Detail := GPSToString(Lat) + 'C '
                      + GPSToString(Long) + 'В';
  LI.Tag:=ID;
  LI.TagString := Caption + '##' + Lat.ToString + '##' + Long.ToString;
end;

Чтобы не создавать лишние объекты и не «дёргать» лишний раз базу данных я решил сразу в элементе списка хранить всю необходимую для дальнейшей работы информацию, а именно:

  • в свойстве Tag хранится ID записи из базы данных для того, чтобы можно было удалить её при необходимости
  • в свойстве TagString хранится уже сформированная строка для отправки в Windows-приложение с помощью App Tethering.

Так как предполагается, что за раз в списке будет накапливаться не более 50-100 точек, то, думаю, что на потреблении памяти и скорости работы приложения такой подход к хранению данных сильно не скажется.

Соответственно, если человек жмет кнопку «Сохранить» (на верхней панели слева), то срабатывает следующий обработчик OnClick кнопки:

procedure TForm3.Button1Click(Sender: TObject);
var
  PlacemarkCaption: string;
begin
  TDialogservice.InputQuery('Введите текст метки', ['Метка'], [''],
    procedure(const AResult: TModalResult; const AValues: array of string)
    begin
      case AResult of
        mrOk:
          begin
            PlacemarkCaption := AValues[0];
            LiteSQL1.SQL.Text := 'INSERT INTO data (latitude, longtitude, caption) VALUES (:latitude, :longtitude, :caption)';
            LiteSQL1.ParamByName('latitude').AsFloat := LocationSensor1.Sensor.Latitude;
            LiteSQL1.ParamByName('longtitude').AsFloat := LocationSensor1.Sensor.Longitude;
            LiteSQL1.ParamByName('caption').AsString := PlacemarkCaption;
            LiteSQL1.Execute;
            PointToList(PlacemarkCaption, LiteSQL1.LastInsertId, Latitude, Longtitude);
          end;
        mrCancel:
          PlacemarkCaption := EmptyStr;
      end;
    end
    );
end;

Про работу с диалогами в FMX и, в частности, в Android я рассказывал в статье «Работа с диалогами InputQuery, InputBox в Delphi 10.1 Berlin» — не забудьте подключить в uses модуль FMX.DialogService.

Если в списке выбраны точки и пользователь нажимает кнопку удалить — срабатывает следующий обработчик:

procedure TForm3.Button2Click(Sender: TObject);
var
  I: integer;
  Deleted: TArray;
begin
  LiteSQL1.SQL.Text := 'DELETE FROM data WHERE id=:id';
  for I := 0 to Pred(ListBox1.Items.Count) do
    if ListBox1.ListItems[I].IsChecked then
      Deleted := Deleted + [ListBox1.ListItems[I].Tag];
  if Length(Deleted)=0 then
    Exit;
  LiteSQL1.Params.ValueCount := Length(Deleted);
  for I := 0 to High(Deleted) do
    LiteSQL1.Params[0][I].AsInteger := Deleted[I];
  LiteSQL1.Execute(Length(Deleted));
  RefreshList;
end;

Здесь я пользуюсь пакетными операциями в LiteDAC о которых рассказывал в этой заметке. После того, как сформирован список всех точек, которые необходимо отправить в Windows можно приступать к работе с App Tethering.

Отправляем список координат, используя App Tethering

Приемник данных

Для теста я написал простенькое VCL-приложение суть которого — получить строку и отобразить её в Memo. Внешний вид приложения весьма и весьма аскетичен :)

приложение VCL

У компонента TTetheringManager установлены следующие свойства:

  • Name = tmVCLManager
  • Text = tmVCLManager;
  • AllowedAdapters = Network

У компонента TTetheringAppProfile установлены следующие свойства:

  • Name = tapVCLProfile
  • Text = tapVCLProfile
  • Manager = tmVCLManager
  • Group = pdv_coords.

Исходный код приёмника данных так же прост и лаконичен, как и интерфейс. Здесь есть всего один обработчик события OnResourceReceived компонента TTetheringAppProfile:

procedure TForm4.tapVCLProfileResourceReceived(const Sender: TObject;
  const AResource: TRemoteResource);
begin
  if AResource.ResType = TRemoteResourceType.Data then
     Memo1.Lines.Add(AResource.Value.AsString);
end;

Больше в приложении-приемнике ничего нет.

Отправитель данных. Android-приложение

На форме Android-приложения расположены также TTetheringManager и TTetheringAppProfile. Для связывания приложений я решил использовать автоматическое подключение, поэтому установил у компонента TTetheringAppProfile свойство Group точно таким же, как и в VCL, то есть:

  • Group = pdv_coords

Чтобы отправить сведения о выбранных в списке точек, для кнопки «Отправить» был определен следующий обработчик OnClick:

procedure TForm3.Button3Click(Sender: TObject);
begin
  if PInfo.ProfileIdentifier=EmptyStr then
    tamAndroidManager.AutoConnect()
  else
    SendPoints;
end;

PInfo содержит информацию по профилю в VCL-приложении с которым связывается приложение Android:

type
  TForm3 = class(TForm)
  //....//  
  private
    PInfo: TTetheringProfileInfo;
    //....//
    procedure SendPoints;
  public
    { Public declarations }
  end;

Метод SendPoints отправляет информацию о выбранных точках:

procedure TForm3.SendPoints;
var
  I: integer;
begin
  if PInfo.ProfileIdentifier=EmptyStr then
    Exit;
  for I := 0 to Pred(ListBox1.Items.Count) do
    if ListBox1.ListItems[I].IsChecked then
      tapAndroidProfile.SendString(PInfo, 'point', ListBox1.ListItems[I].TagString)
end;

Соответственно, раз мы воспользовались автоматическим подключением к удаленному профилю, то необходимо также обработать событие у менеджера:

procedure TForm3.tamAndroidManagerEndAutoConnect(Sender: TObject);
var
  I, J: integer;
begin
  for I := 0 to Pred(tamAndroidManager.RemoteProfiles.Count) do
  begin
    if tamAndroidManager.RemoteProfiles[I].ProfileText = 'tapVCLProfile' then
      PInfo := tamAndroidManager.RemoteProfiles[I];
  end;
  SendPoints;
end;

В этом обработчике мы ищем профиль VCL-приложения и отправляем ему данные о выбранных точках.

Теперь можно запустить оба приложения и проверить их работу.

работа Android-приложения:

Android приложение для сохранения координат

После того, как в списке выбраны точки и нажата кнопка «Отправить» в VCL-приложении увидим следующее:

прием данных о местоположении

Книжная полка

Описание Подробно рассматривается библиотека FM, позволяющая создавать полнофункциональное программное обеспечение для операционных систем Windows и OS X, а также для смартфонов и планшетных компьютеров, работающих под управлением Android и iOS
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес


 

5 1 голос
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
3 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Александр
Александр
24/10/2016 14:37

Добрый день! Как происходит «борьба» с GPS-координатами полученными не от датчика GPS? Имеется ввиду, что от компонента TLocationSensor иногда присылает координаты отличные от текущего положения

Александр
Александр
24/10/2016 20:01

Там могут быть и км… Спасибо за ответ!