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

В прошлой статье, касающейся работы с kml в Delphi были обозначены пути работы — созданы несколько базовых классов для работы с географической информацией и дано описание основных свойств этих классов. Сегодня перейдем к конкретным примерам работы с kml в Delphi, а именно — научимся создавать метки на карте в Google Earth.

Контейнеры в KML

В иерархии элементов kml выделяется группа элементов-контейнеров (Container), которые могут хранить в себе несколько элементов (Feature) и, по сути, служат для создания некой иерархии элементов внутри kml-документа.

Вот часть схемы иерархии элементов kml, относящаяся к контейнерам:

Таким образом, в kml-файле можно создать как документ (Document), так и папки (Folder) для хранения различных элементов, включая метки, линии, полигоны и так далее. По описанию свойств Document и Folder идентичны — оба элемента наследуют свойства Feature и могут содержать в себе коллекцию «фич» (Feature), однако назначение этих элементов несколько различны.

Документ (Document) используется в kml-файле можно описывать в файле один раз. Этот элемент, по-мимо всего прочего, предназначен для хранения общих стилей. Например, в Document можно задать то, какие иконки будут применяться ко всем меткам, или цвет линий и так далее.

Папка же (Folder) используется исключительно для создания иерархии элементов внутри документа. Визуально Document и Folder выглядят в Google Earth следующим образом:

Таким образом, наши метки могут содержаться в одном из видов контейнеров — в документе или папке. Сегодня мы рассмотрим самый простой пример создания метки, которая будет находиться в документе (Document).

Создаем методы сохранения элементов в XML-документа

Для дальнейшей работы нам потребуется дописать методы классов, созданных в прошлой статье. Для начала, создадим метод создания XML-узла (IXMLNode) у базового класса TKmlObject. Этот методы мы будем использовать во всех наследниках класса, поэтому необходимо учесть, что новый узел может быть как корневым для XML-документа, так и дочерним для другого узла в документа. TKmlObject будет теперь таким:

type
  TKmlObject = class
  private
    //поля
  public
    function CreateNode(ANodeName: string; AParentNode: IXMLNode; ADocument: IXMLDocument = nil): IXMLNode; virtual;
    //свойства объекта
  end;

Функция будет работать следующим образом:

  1. Если определен параметр ADocument, то считаем, что новый узел — корневой для документа
  2. Если определен параметр AParentNode, то считаем, что новый узел — родительский для AParentNode.

Функция CreateNode будет такой:

function TKmlObject.CreateNode(ANodeName: string; AParentNode: IXMLNode;
  ADocument: IXMLDocument): IXMLNode;
begin
  if (not Assigned(AParentNode)) and (not Assigned(ADocument)) then
    raise Exception.Create('AParentNode или ADocument должны быть определены');
  if Assigned(ADocument) then
  begin
    Result := ADocument.CreateElement(ANodeName, cKmlNamespace);
    ADocument.DocumentElement.ChildNodes.Add(Result);
  end
  else
    Result := AParentNode.AddChild(ANodeName);
  if not Id.IsEmpty then
    Result.Attributes['id'] := Id;
  if not TargetId.IsEmpty then
    Result.Attributes['targetId'] := TargetId;
end;

Следующий класс, который наследуется от TKmlObject и, имеющий собственные свойства — это TKmlPoint (точка). Для точки можно задать координаты, а также её положение относительно уровня земной поверхности. Так как у этого класса есть конкретное имя в kml-документе (Point) и этот узел обязательно должен быть дочерним для других элементов, то у этого класса мы создадим метод Save:

TKmlPoint = class(TKmlGeometry)
  private
    //поля класса
  public
    function Save(AParentNode: IXMLNode): IXMLNode;
    //свойства класса
  end;

Реализация метода будет следующей:

function TKmlPoint.Save(AParentNode: IXMLNode): IXMLNode;
begin
  Result := inherited CreateNode('Point', AParentNode, nil);
  Result.AddChild('extrude').Text := BoolStrs[Extrude];
  Result.AddChild('altitudeMode').Text := AltitudeModeStr[AltitudeMode];
  Result.AddChild('coordinates').Text:=Coordinates.ToString
end;

Здесь BoolStrs и AltitudeModeStr — это константы следующего вида:

const
  BoolStrs: array [boolean] of string = ('0', '1');
  AltitudeModeStr: array [TAltitudeModeEnum] of string = ('clampToGround', 'relativeToGround', 'absolute');

TAltitudeModeEnum — тип данных, определенный в первой части.

Координаты точки у нас задаются в виде объекта типа TKmlCoordinate. У TKmlCoordinate был определен метод ToString следующего содержания:

function TKmlCoordinate.ToString: string;
var
  FS: TFormatSettings;
  SB: TStringBuilder;
begin
  FS.DecimalSeparator := '.';
  SB := TStringBuilder.Create;
  try
    SB.Append(FloatToStr(Longtitude, FS));
    SB.Append(',');
    SB.Append(FloatToStr(Latitude, FS));
    SB.Append(',');
    SB.Append(FloatToStr(Altitude, FS));
    Result := SB.ToString(True)
  finally
    FreeAndNil(SB);
  end;
end;

Этот метод формирует строку вида «долгота,широта,высота» в соответствии с KML Reference.

Так как элемент Feature может выступать родителем как для элемента «Документ», так и для элемента «Папка», а сам элемент Feature, как и Object — виртуальный и не может создаваться напрямую в KML, то  у класса TKmlFeature метод CreateNode был переопределен следующим образом:

TKmlFeature = class(TKmlObject)
  private
    //поля класса
  public
    function CreateNode(AFeatureName: string; AParentNode: IXMLNode; ADocument: IXMLDocument = nil): IXMLNode; override;
    //свойства класса
  end;
 
function TKmlFeature.CreateNode(AFeatureName: string; AParentNode: IXMLNode; ADocument: IXMLDocument): IXMLNode;
var
  ANode: IXMLNode;
begin
  ANode := inherited CreateNode(AFeatureName, AParentNode, ADocument);
  if not Name.IsEmpty then
    ANode.AddChild('name').Text := Name;
  ANode.AddChild('visibility').Text := BoolStrs[Visibility];
  ANode.AddChild('open').Text := BoolStrs[Open];
  if not Snippet.IsEmpty then
    ANode.AddChild('snippet').Text := Snippet;
  if not Description.IsEmpty then
    ANode.AddChild('description').Text := Description;
  if not Link.IsEmpty then
    ANode.AddChild('Link').Text := Link;
  Result := ANode;
end;

Элемент Placemark может явно создаваться в KML и быть дочерним для папки или документа, поэтому у него создадим метод Save, аналогичный методу у Point:

TKmlPlacemark = class(TKmlFeature)
  private
    //поля класса
  public
    procedure Save(AParentNode: IXMLNode);
    //свойства класса
  end;
 
procedure TKmlPlacemark.Save(AParentNode: IXMLNode);
var
  ANode: IXMLNode;
begin
  //сохраняем свойства Feature
  ANode := inherited CreateNode('Placemark', AParentNode, nil);
  //добавляем координаты
  Point.Save(ANode);
end;

Теперь, добавив все необходимые методы, можно приступать уже непосредственно к формированию KML-файла, содержащего наши метки.

Создаем документ (Document) для хранения меток

Для начала, научимся создавать kml-документ и сохранять его на диск. Наш новый класс (назовем его TKmlDocument) будет наследником ранее созданного TKmlFeature и выглядеть следующим образом:

TKmlDocument = class(TKmlFeature)
  private
    FXmlDoc: IXMLDocument;
    FPlacemark:  TObjectList;
  public
    constructor Create;
    destructor Destroy;override;
    function ToString: string;override;
    procedure SaveToFile(const AFileName: string);
  end;

Функция ToString создает строку, содержащую xml:

const
  cKmlNamespace = 'http://www.opengis.net/kml/2.2';
  cAtomNamespace = 'http://www.w3.org/2005/Atom';
 
function TKmlDocument.ToString: string;
var
  kml, DocNode: IXMLNode;
  I: Integer;
begin
  FXmlDoc := TXMLDocument.Create(nil);
  FXmlDoc.Active := True;
  kml := FXmlDoc.AddChild('kml');
  kml.DeclareNamespace('', cKmlNamespace);
  kml.DeclareNamespace('atom', cAtomNamespace);
 
  DocNode:=CreateNode('Document', nil, FXmlDoc);
 
  for I := 0 to Pred(FPlacemark.Count) do
    FPlacemark[i].Save(DocNode);
 
  Result := FXmlDoc.Xml.Text;
end;

Второй метод — SaveToFile сохраняет наш kml-документ в файл на диске:

procedure TKmlDocument.SaveToFile(const AFileName: string);
var S: TStringStream;
begin
  S:=TStringStream.Create(ToString,TEncoding.UTF8);
  try
    S.SaveToFile(AFileName);
  finally
    FreeAndNil(S);
  end;
end;

Попробуем воспользоваться нашим классом, создать пустой kml-документ и посмотрим, что из этого получиться в Google Earth. Создадим новый проект в Delphi, на главную форму бросим кнопку TButton и напишем такой обработчик для события OnClick:

procedure TForm10.Button1Click(Sender: TObject);
var D: TKmlDocument;
begin
  D:=TKmlDocument.Create;
  try
    D.Name:='Мой первый документ';
    D.Visibility:=True;
    D.Open:=False;
    D.Link:='http://webdelphi.ru';
    D.Snippet:='Краткое описание';
    D.Description:='Описание подробное';
    D.SaveToFile('Doc.kml');
  finally
    FreeAndNil(D)
  end;
end;

После запуска приложения и клика по кнопке, рядом с exe-файлом будет создан файл Doc.kml. Если его открыть в Google Earth можно увидеть следующее:

Как можно видеть на рисунке — название документа представляет собой гиперссылку. Если нажать мышкой по названию документа, то по центру карты откроется описание документа, которое мы задавали в свойстве Description:

При этом, никто нам не запрещает добавить в описание документа и HTML-разметку и получить вот такое описание документа:

О том, как добавлять в описание HTML и JavaScript мы ещё поговорим в следующих статьях блога, а пока того, что уже сделано вполне достаточно, чтобы перейти к цели статьи — создавать на карте метки.

Добавляем метку на карту Google Earth

В принципе, у нас уже всё готово для того, чтобы добавить на карту любое количество меток. Все метки будут находится в корне нашего документа. Например, можно написать вот такой код:

procedure TForm10.Button1Click(Sender: TObject);
var D: TKmlDocument;
    P:TKmlPlacemark;
begin
  D:=TKmlDocument.Create;
  try
    D.Name:='Мой первый документ';
    D.Visibility:=True;
    D.Open:=True;
    //добавляем новую метку
    D.Placemark.Add(TKmlPlacemark.Create);
    P:=D.Placemark.Last;
    P.Visibility:=True;
    P.Id:='placemark_id';
    P.Name:='Моя метка';
    P.Description:='Описание метки';
    P.Point.Extrude:=True;
    P.Point.AltitudeMode:=amAbsolute;
    P.Point.Coordinates.Latitude:=59.831795;
    P.Point.Coordinates.Longtitude:=29.814903;
    P.Point.Coordinates.Altitude:=5;
    //сохраняем файл на диск
    D.SaveToFile('Doc.kml');
  finally
    FreeAndNil(D)
  end;
end;

выполнить этот код и получить в Google Earth вот такую картинку:

Нашей метке назначена иконка по умолчанию («кнопка»). Однако, сегодня мы и не ставили перед собой задачи как-то разукрасить нашу карту. Об этом мы поговорим позднее, когда разберемся со всеми возможными географическими примитивами в Google Earth. А пока, нам осталось оформить весь код в виде небольшой демки.

Демонстрационное приложение

Внешний вид демонстрационного приложения будет таким:

Можно добавить в список любое количество меток, которые необходимо добавить на карту в Google Earth, метки можно «выдавливать» над уровнем земли, давать меткам короткие и подробные описания, а также скрывать метки на карте.

Например, ниже в Google Earth была задана точка расположенная на высоте 10 м. от уровня земной поверхности:

Скачать демонстрационное приложение можно со страницы с исходниками из раздела «Google API» 

В архиве вы найдете исходник демонстрационного приложения и модуль kml.pas, содержащий классы, разработанные в этой и прошлой статье про KML Reference.

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

Описание Подробно рассматривается библиотека FM, позволяющая создавать полнофункциональное программное обеспечение для операционных систем Windows и OS X, а также для смартфонов и планшетных компьютеров, работающих под управлением Android и iOS
купить книгу delphi на ЛитРес
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес
0 0 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии