В предыдущей части мы научились ставить метки на карте Google Earth, используя возможности kml в delphi. В числе прочего мы также научились делать подписи к меткам, добавлять описание к документу и меткам внутри этого kml-документа. Сегодня будем учиться рисовать на карте линии и полигоны.
Прежде, чем начнем рисовать линии и полигоны на карте в Google Earth небольшое уточнение по предыдущей части цикла статей про KML Reference.
В первой статье, под названием «KML Reference в Delphi по-русски #1: Базовые классы для работы с метками«, я создал вот такой класс для работы с метками:
TKmlPlacemark = class(TKmlFeature) private FPoint: TKmlPoint; public constructor Create; destructor Destroy;override; property Point: TKmlPoint read FPoint; end;
в предположении о том, что элемент Placemark может определять только метки (точки) на карте. Однако, это не так. Дело в том, что, согласно KML Reference, Placemark может содержать любые элементы Geometry (не только точки). Эта ошибка была обнаружена как раз, когда начал разбираться с рисованием линий. Поэтому сегодня мы немного изменим этот класс и адаптируем его под использование в самых различных случаях, включая и использование класса TKmlPlacemark для рисования сколь угодно сложных фигур на карте Google Earth.
Виды линий и полигоны в KML
KML предлагает на выбор следующие геометрические элементы:
- LineString — незамкнутая линия (или, по другому, путь)
- LinearRing — замкнутая линия
- Polygon — полигон.
По сути, LineString и LinearRing — это обычные линии различие между которыми состоит в том, что для рисования LineString необходимо задать минимум две точки (начало и конец линии), а для LinearRing — четыре. Почему четыре? Дело в том, что замыкающая (последняя) точка у LinearRing должна быть точно такой же, как и первая. То есть, порядок отрисовки линий в KML выглядит следующим образом:
Также, у LineString можно задать порядок отрисовки линии, если элемент Placemark будет содержать несколько элементов LineString, о этой возможностью мы пользоваться пока не будем, так как пока нам предстоит только научиться рисовать всякие примитивы.
Что касается элемента Polygon (полигон), то этот геометрический объект может иметь как внешнюю, так и внутренние границы. При этом, с помощью набора внутренних границ мы можем «вырезать» в полигоне участки, например, нарисовать вот такую фигуру:
Теперь приступим к доработке нашего модуля kml.pas.
KML в Delphi
Для начала немного доработаем класс TKmlGeometry, который у нас представляем собой любой геометрический объект на карте. Как мы разбирались ранее, элемент Geometry не может напрямую создаваться в KML-документе, однако, этот элемент обладает общими для всех других геометрических объектов свойствами, а именно:
- Extrude: boolean — «выдавливание» элемента на карте
- AltitudeMode: TAltitudeModeEnum — режим «выдавливания»: прижать к земле, поднять над уровнем моря и так далее.
Об этих свойствах я подробно рассказывал в первой статье, посвященной KML в Delphi.
Исходя из этого, класс TKmlGeometry можно представить следующим образом:
TKmlGeometry = class(TKmlObject) private FExtrude: boolean; FAltitudeMode: TAltitudeModeEnum; public constructor Create; virtual; destructor Destroy; override; property Extrude: boolean read FExtrude write FExtrude; property AltitudeMode: TAltitudeModeEnum read FAltitudeMode write FAltitudeMode; end;
Теперь перед нами стоит задача — сделать так, чтобы наш класс TKmlPlacemark мог содержать в себе любой геометрический элемент, а именно: Point, LineString, LinearRing, Polygon или любого другого наследника от Geometry (см. диаграмму элементов kml).
Для решения этой задачки используем метаклассы в Delphi или, по-другому class reference. Надо сказать, что эту возможность в Delphi я использовал не часто, поэтому могу в чем-то ошибиться, но, представленный ниже код будет работать :)
Итак, делаем такое объявление:
TKmlGeometryClass = class of TKmlGeometry;
и переписываем TKmlPlacemark следующим образом:
TKmlPlacemark = class(TKmlFeature) private FGeometry: TKmlGeometry; public constructor Create(AGeometryClass: TKmlGeometryClass); overload; destructor Destroy; override; function Save(AParentNode: IXMLNode): IXMLNode; override; property Geometry: TKmlGeometry read FGeometry; end; constructor TKmlPlacemark.Create(AGeometryClass: TKmlGeometryClass); begin inherited Create; FGeometry := AGeometryClass.Create end;
Таким образом, мы можем делать такие вызовы конструктора TKmlPlacemark:
Placemark:=TKmlPlacemark.Create(TKmlPolygon); //будем рисовать полигон Placemark:=TKmlPlacemark.Create(TKmlPoint); //будем рисовать метку Placemark:=TKmlPlacemark.Create(TKmlLineString); //будем рисовать линию //и так далее
Далее, в зависимости от того, какой геометрический элемент будет представлять Placemark, мы сможем использовать свойства этих объектов. Следующий класс, который нам желательно создать — это список точек. Конечно, можно было бы ограничиться массивом, но со списком, на мой взгляд, в итоге, код получится немного короче и понятнее. Итак, для хранения набора точек будем использовать следующий тип данных:
TKmlCoordinateList = class(TObjectList) public function ToString: string; override; function AddCoordinate(ALongtitude, ALatitude, AAltitude: double): integer; end;
function TKmlCoordinateList.ToString: string; var I: integer; SB: TStringBuilder; begin SB := TStringBuilder.Create; try for I := 0 to Pred(Count) do begin SB.Append(Items[I].ToString); if I < Pred(Count) then SB.Append(' '); end; Result := SB.ToString(True); finally FreeAndNil(SB); end; end;
Функция AddCoordinate позволяет добавлять в список очередную точку:
function TKmlCoordinateList.AddCoordinate(ALongtitude, ALatitude, AAltitude: double): integer; begin Result := Add(TKmlCoordinate.Create); with Last do begin Latitude := ALatitude; Longtitude := ALongtitude; Altitude := AAltitude; end; end;
Теперь, так как было сказано выше, что LineString и LinearRing практически ничем друг от друга не отличаются, я создал ещё один класс, который будет выступать родителем для линий:
TKmlLine = class(TKmlGeometry) private FAltitudeOffset: double; FTessellate: boolean; FCoordinates: TKmlCoordinateList; public constructor Create; override; destructor Destroy; override; function Save(AParentNode: IXMLNode): IXMLNode; override; function AddCoord(ALongtitude, ALatitude, AAltitude: double): integer; //свойства общие для линий property AltitudeOffset: double read FAltitudeOffset write FAltitudeOffset; property Tessellate: boolean read FTessellate write FTessellate; property Coordinates: TKmlCoordinateList read FCoordinates; end;
Метод Save у класса выглядит следующим образом:
function TKmlLine.Save(AParentNode: IXMLNode): IXMLNode; var FS: TFormatSettings; begin if Self is TKmlLineString then begin //У LineString должно быть не менее двух точек if FCoordinates.Count < 2 then raise EKmlException.Create(rsLineStringError); Result := inherited CreateNode('LineString', AParentNode, nil); end else if Self is TKmlLinearRing then begin //у LinearRing должно быть не менее четырех точек if FCoordinates.Count < 4 then raise EKmlException.Create(rsLinearRingPointCountError); //первая и последняя точки должны совпадать if not FCoordinates[0].IsEqual(FCoordinates[Pred(FCoordinates.Count)]) then raise EKmlException.Create(rsLinearRingError); Result := inherited CreateNode('LinearRing', AParentNode, nil); end; //записываем в XML-узел общие для линий свойства with Result do begin AddChild('extrude').Text := BoolStrs[Extrude]; AddChild('tessellate').Text := BoolStrs[Tessellate]; AddChild('altitudeMode').Text := AltitudeModeStr[AltitudeMode]; FS.DecimalSeparator := '.'; AddChild('altitudeOffset').Text := FloatToStr(AltitudeOffset, FS); AddChild('coordinates').Text := Coordinates.ToString; end; end;
Метод AddCoord — это «обёртка» для AddCoordinate списка координат:
function TKmlLine.AddCoord(ALongtitude, ALatitude, AAltitude: double): integer; begin Result := FCoordinates.AddCoordinate(ALongtitude, ALatitude, AAltitude); end;
Использование класса TKmlLine позволяет нам написать без особых проблем оба класса для создания линий в kml:
//рисует незамкнутую линию TKmlLineString = class(TKmlLine) private FDrawOrder: integer; public function Save(AParentNode: IXMLNode): IXMLNode; override; property DrawOrder: integer read FDrawOrder write FDrawOrder; end; function TKmlLineString.Save(AParentNode: IXMLNode): IXMLNode; begin Result:=inherited Save(AParentNode); Result.AddChild('drawOrder').Text := DrawOrder.ToString; end; //рисует замкнутую линию TKmlLinearRing = class(TKmlLine) public function Save(AParentNode: IXMLNode): IXMLNode; override; procedure CloseLine;//замыкает линию end; function TKmlLinearRing.Save(AParentNode: IXMLNode): IXMLNode; begin Result:=inherited Save(AParentNode); end; procedure TKmlLinearRing.CloseLine; begin if FCoordinates.Count < 3 then raise EKmlException.Create(rsCloseLineError); FCoordinates.Add(TKmlCoordinate.Create); FCoordinates.First.AssignTo(FCoordinates.Last); end;
Проверим, как будут работать наши классы в нашем демонстрационном приложении из прошлой статьи. Добавим вот такой код в программу:
//добавим в документ незамкнутую линию ADocument.Placemark.Add(TKmlPlacemark.Create(TKmlLineString)); with ADocument.Placemark.Last do begin //добавляем свойства, характерные для всех "фич" Name := 'Линия'; Snippet := 'Описание'; Visibility := True; //задаем свойства линии TKmlLineString(Geometry).Extrude := False; TKmlLineString(Geometry).AltitudeMode := TAltitudeModeEnum(cbAltitudeModeEnum.ItemIndex); TKmlLineString(Geometry).AddCoord(-122.366278, 37.818844, 30); TKmlLineString(Geometry).AddCoord(-122.365248, 37.819267, 30); TKmlLineString(Geometry).AddCoord(-122.365640, 37.819861, 30); TKmlLineString(Geometry).AddCoord(-122.366669, 37.819429, 30); end; end;
Выполнение этого кода приведет к тому, что на карте Google Earth будет нарисована незамкнутая линия, состоящая из четырех точек и, при этом, линия будет прижата к земле (extrude=false):
Так как мы ещё не разбирались с тем, как изменять цвета объектов, скажу, что красный цвет и толщину линии я задавал непосредственно в Google Earth, а вообще код «рисует» белую линию толщиной в 1 пиксель.
Проверим, что получится, если «выдавить» линию над поверхностью. Перепишем код для LineString следующим образом:
TKmlLineString(Geometry).Extrude := True; //выдавливаем линию TKmlLineString(Geometry).AltitudeMode := TAltitudeModeEnum.amAbsolute;//высота точки - абсолютное значение TKmlLineString(Geometry).AddCoord(-122.366278, 37.818844, 10); TKmlLineString(Geometry).AddCoord(-122.365248, 37.819267, 30); TKmlLineString(Geometry).AddCoord(-122.365640, 37.819861, 20); TKmlLineString(Geometry).AddCoord(-122.366669, 37.819429, 30);
Этот код приведет к рисованию в Google Earth вот такой замысловатой фигуры:
Таким образом, манипулируя свойством Extrude и координатами высоты точек можно выстраивать на карте «стены», «заборы» и так далее. Теперь попробуем нарисовать замкнутую линию по этим же четырем точкам, сохраняя режим «выдавливания», то есть код теперь будет вот таким:
ADocument.Placemark.Add(TKmlPlacemark.Create(TKmlLinearRing)); with ADocument.Placemark.Last do begin Name := edPlaceName.Text; Snippet := edPlaceSnippet.Text; Description := memPlaceDescr.Text; Visibility := True; TKmlLinearRing(Geometry).Extrude := True; TKmlLinearRing(Geometry).AltitudeMode := TAltitudeModeEnum.amAbsolute; TKmlLinearRing(Geometry).AddCoord(-122.366278, 37.818844, 10); TKmlLinearRing(Geometry).AddCoord(-122.365248, 37.819267, 30); TKmlLinearRing(Geometry).AddCoord(-122.365640, 37.819861, 20); TKmlLinearRing(Geometry).AddCoord(-122.366669, 37.819429, 30); TKmlLinearRing(Geometry).CloseLine; //замыкаем линию end; end;
В результате получим вот такую фигуру на карте:
Что касается полигонов (Polygon), то их рисование чуть-чуть по-сложнее, чем рисование линий, так как эти фигуры должны иметь как минимум одну внешнюю границу и одну или несколько внутренних границ. Класс полигона в Delphi выглядит следующим образом:
TKmlPolygon = class(TKmlGeometry) private FTessellate: boolean; FOuterBoundaryIs: TKmlLinearRing; FInnerBoundaryIs: TObjectList; public constructor Create; override; destructor Destroy; override; function Save(AParentNode: IXMLNode): IXMLNode; override; property Tessellate: boolean read FTessellate write FTessellate; property OuterBoundaryIs: TKmlLinearRing read FOuterBoundaryIs; property InnerBoundaryIs: TObjectList read FInnerBoundaryIs; end;
function TKmlPolygon.Save(AParentNode: IXMLNode): IXMLNode; var BoundaryNode: IXMLNode; I: integer; begin if FInnerBoundaryIs.Count = 0 then raise EKmlException.Create(rsPolygonError); Result := inherited CreateNode('Polygon', AParentNode, nil); with Result do begin AddChild('extrude').Text := BoolStrs[Extrude]; AddChild('tessellate').Text := BoolStrs[Tessellate]; AddChild('altitudeMode').Text := AltitudeModeStr[AltitudeMode]; BoundaryNode := AddChild('outerBoundaryIs'); FOuterBoundaryIs.Save(BoundaryNode); BoundaryNode := AddChild('innerBoundaryIs'); for I := 0 to Pred(FInnerBoundaryIs.Count) do FInnerBoundaryIs[I].Save(BoundaryNode); end; end;
Посмотрим, как использовать этот класс в Delphi. Для начала, нарисуем полигон, как было показано на рисунке выше (с вырезанной областью). В Delphi это делается вот так:
ADocument.Placemark.Add(TKmlPlacemark.Create(TKmlPolygon)); with ADocument.Placemark.Last do begin Name := edPlaceName.Text; Snippet := edPlaceSnippet.Text; Description := memPlaceDescr.Text; Visibility := True; TKmlPolygon(Geometry).Extrude := False; TKmlPolygon(Geometry).AltitudeMode := TAltitudeModeEnum.amClampToGround; //добавляем внешнюю границу TKmlPolygon(Geometry).OuterBoundaryIs.AddCoord(-122.366278, 37.818844, 30); TKmlPolygon(Geometry).OuterBoundaryIs.AddCoord(-122.365248, 37.819267, 30); TKmlPolygon(Geometry).OuterBoundaryIs.AddCoord(-122.365640, 37.819861, 30); TKmlPolygon(Geometry).OuterBoundaryIs.AddCoord(-122.366669, 37.819429, 30); TKmlPolygon(Geometry).OuterBoundaryIs.CloseLine; //добавляем одну внутреннюю границу TKmlPolygon(Geometry).InnerBoundaryIs.Add(TKmlLinearRing.Create); TKmlPolygon(Geometry).InnerBoundaryIs.Last.AddCoord(-122.366212, 37.818977, 30); TKmlPolygon(Geometry).InnerBoundaryIs.Last.AddCoord(-122.365424, 37.819294, 30); TKmlPolygon(Geometry).InnerBoundaryIs.Last.AddCoord(-122.365704, 37.819731, 30); TKmlPolygon(Geometry).InnerBoundaryIs.Last.AddCoord(-122.366488, 37.819402, 30); TKmlPolygon(Geometry).InnerBoundaryIs.Last.CloseLine; end; end;
Результат выполнения кода:
Нарисуем «выдавленный» полигон:
//выдавливаем полигон TKmlPolygon(Geometry).Extrude := True; TKmlPolygon(Geometry).AltitudeMode := TAltitudeModeEnum.amAbsolute; TKmlPolygon(Geometry).OuterBoundaryIs.AddCoord(-122.366278, 37.818844, 30); TKmlPolygon(Geometry).OuterBoundaryIs.AddCoord(-122.365248, 37.819267, 30); TKmlPolygon(Geometry).OuterBoundaryIs.AddCoord(-122.365640, 37.819861, 30); TKmlPolygon(Geometry).OuterBoundaryIs.AddCoord(-122.366669, 37.819429, 30); TKmlPolygon(Geometry).OuterBoundaryIs.CloseLine; TKmlPolygon(Geometry).InnerBoundaryIs.Add(TKmlLinearRing.Create); TKmlPolygon(Geometry).InnerBoundaryIs.Last.AddCoord(-122.366212, 37.818977, 30); TKmlPolygon(Geometry).InnerBoundaryIs.Last.AddCoord(-122.365424, 37.819294, 30); TKmlPolygon(Geometry).InnerBoundaryIs.Last.AddCoord(-122.365704, 37.819731, 30); TKmlPolygon(Geometry).InnerBoundaryIs.Last.AddCoord(-122.366488, 37.819402, 30); TKmlPolygon(Geometry).InnerBoundaryIs.Last.CloseLine;
Результат выполнения кода:
Таким образом, сегодня мы научились использовать три основных примитива для рисования геометрических объектов с использованием kml — линию, замкнутую линию и полигон. Как видите, трех этих элементов kml хватает, чтобы рисовать самые замысловатые фигуры на картах Google Earth. Осталось только поделиться с вами исходником модуля :)
Демку выложу позже, когда добавлю в неё новые возможности по рисованию.
Следующая статья: «KML Reference в Delphi по-русски #4: Работа со стилями элементов»
Книжная полка
Описание Подробно рассматривается библиотека FM, позволяющая создавать полнофункциональное программное обеспечение для операционных систем Windows и OS X, а также для смартфонов и планшетных компьютеров, работающих под управлением Android и iOS
|
||
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|