Google Maps API. Геокодирование

Авторы: Максим Мазитов, Королевство Delphi

Источник: http://www.interface.ru/
 

В предыдущей статье я рассматривал API статических карт Google. Рядом с этой темой, буквально «впритык», располагается геокодирование. Что это за зверь? Цитирую:

«Geocoding is the process of converting addresses (like «1600 Amphitheatre Parkway, Mountain View, CA») into geographic coordinates (like latitude 37.423021 and longitude –122.083739), which you can use to place markers or position the map»

А если нормальным языком, то геокодирование — процесс превращения строкового почтового (не путать с электронной почтой :) ) адреса в координаты. А также, обратный процесс.

Описание API лежит на гугле, к сожалению, пока не переведенное, хотя в адресе присутствует код языка.

Итак, приступим.

Прямое геокодирование

Идем по этой ссылке. Результатом оказывается XML документ. Смотрим на него внимательно. Ветки документа содержат информацию о найденных объектах и их координатах. Дело за малым — сформировать URL, закинуть запрос, считать ответ и разобрать XML. «За чем же дело встало?» (жизненная цитата)

Строка запроса состоит из трех параметров:

Скачиваем пример и смотрим в код:

// получение списка точек по запросу

function TfmMain.DoGeocodingRequest(SearchString:String):TObjectList;

var

  Point:TMapPoint;

  FileOnNet:String;

  Stream:TMemoryStream;

  Utf8Content:UTF8String;

  Node:IXMLNode;

  PlacemarkNode, PointNode, AddressNode:IXMLNode;

  i:Integer;

  sCoordinates:String;

  StringList:TStringList;

begin

  // создаем хранилище результатов

  Result:=TObjectList.Create(True);

  // создаем поток

  Stream:=TMemoryStream.Create;

  StringList:=TStringList.Create;

  try

    // формируем url для запроса

    FileOnNet:='http://maps.google.com/maps/geo?q=%s&output=xml&key=abcdefg&gl=ru';

    FileOnNet:=Format(FileOnNet,[SearchString]);

    // получение потока с данными ответа

    if GetInetFile(FileOnNet,Stream) = True then begin

      // кому надо расскоментировать и смотреть содержимое при отладке

      //Stream.SaveToFile('c:\geo.xml');



      StreamToUtf8Stream(Stream);

      // заполняем XMLDocument

      XMLDocument.LoadFromStream(Stream);

      // формируем содержимое списка точек

      Node:=XMLDocument.DocumentElement;

      Node:=Node.ChildNodes.FindNode('Response');

      if (Node<>nil) and (Node.ChildNodes.Count>0) then

        for i:=0 to Node.ChildNodes.Count-1 do

          if Node.ChildNodes[i].NodeName='Placemark' then begin

            // находим узел точки

            PlacemarkNode:=Node.ChildNodes[i];

            // получаем узел адреса

            AddressNode:=PlacemarkNode.ChildNodes.FindNode('address');

            //получаем узел координат

            PointNode:=PlacemarkNode.ChildNodes.FindNode('Point');

            PointNode:=PointNode.ChildNodes.FindNode('coordinates');

            if (AddressNode<>nil) and (PointNode<>nil) then begin

              Point:=TMapPoint.Create;

              // получаем адрес

              Point.Address:=AddressNode.Text;

              // получаем координаты

              sCoordinates:=PointNode.Text;

              // разбираем координаты

              ExtractStrings([','],[],PChar(sCoordinates),StringList);

              if StringList.Count>1 then begin

                // Формируем точку

                Point.Lon:=StrToFloatDef(StringList[0],-1);

                Point.Lat:=StrToFloatDef(StringList[1],-1);

                // добавляем точку в список

                if (Point.Lat<>-1) and (Point.Lon<>-1) then

                  Result.Add(Point);

                StringList.Clear;

              end else

                Point.Free;

            end;

          end;

    end;

  finally

    StringList.Free;

    Stream.Free;

  end;

end;

Для хранения результатов обработки XML я создал класс.

  TMapPoint=class

  public

    Lat:Double;

    Lon:Double;

    Address:String;

  end;

И складировал результаты в TObjectList. Если уж совсем заморачиваться, то «по уму» нужно написать хранилище для результатов, но почему то мне было лень.

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

Со списком точек я поступил следующим образом — вывел в ListView и отобразил на карте. Если кто–нибудь ковырял самостоятельно описание статических карт на сайте гугла, приведенное в предыдущей статье, то там есть возможность вывода нескольких точек на одной карте. Я добавил следующую функцию:

// получение карты со списком точек

function GetMap(Points:TObjectList):TOleGraphic;overload;

var

  FileOnNet: String;

  Stream:TMemoryStream;

  Markers:String;

  i:Integer;

  Point:TMapPoint;

begin

  // проверяем наличие точек

  if Points.Count<1 then begin

    Result:=nil;

    Exit;

  end;

  Markers:='';

  // формируем список маркеров

  for i:=0 to Points.Count-1 do

    if (Points[i] is TMapPoint) then begin

      Point:=TMapPoint(Points[i]);

      Markers:=Markers+Format('%.6f,%.6f/',[Point.Lat,Point.Lon]);

    end;

  // создаем поток

  Stream:=TMemoryStream.Create;

  try

    // формируем url для запроса

    FileOnNet:='http://maps.google.com/staticmap?size=640x640'

      +'&markers=%s'

      +'&maptype=mobile&key=MAPS_API_KEY';

    FileOnNet:=Format(FileOnNet,[Markers]);

    // получение потока с данными ответа

    if GetInetFile(FileOnNet,Stream) = True then begin

      // создаем графический объект

      Stream.Position:=0;

      Result:=TOleGraphic.Create;

      Result.LoadFromStream(Stream);

    end else

      Result:=nil;

  finally

    Stream.Free;

  end;

end;

Разница с уже имевшейся функцией GetMap во входных параметрах — списке точек для отображения. Вот что получилось:

Почему–то я всегда думал, что улиц Ленина в России больше. И знаю пару неотмеченных городов на карте в которых она есть. Кстати, это отличный тест гугла на наличие карт городов — есть улица/проспект Ленина, Октября, Революции и ещё немного названий — есть и подробная карта. И следствие — Сибирь у гугла не отобразилась, значит городов там нет!

Кстати, а есть ли жизнь за МКАДом? :)

Танцы с бубнами вокруг кодировок

Гугл принимает и отдает данные в формате UTF–8. Этот факт упомянут в описании. К сожалению, не всё так просто. При посылке запроса с указанием языка в URL возвращаемые данные не принимаются в TXMLDocument, русские символы ему, видите ли, не нравятся. Если перевести URL в UTF–8, тогда документ принимается. Но в большинстве случаев часть адресных строк содержат английские названия. Так Дворцовая площадь становится Palace Embankment. Не вполне допустимая ситуация для использования софта пользователями, не знакомыми с языком потенциального противника. При этом, посылая аналогичный запрос из любого браузера, данные приходят по–русски. Приходит мысль, что собачко порылось в HTTP заголовках. Т.к. гугль знает всё (чё, правда? :), решение найдено в добавлении заголовка «Accept–Language: ru» в вызов функции InternetOpenURL. Тогда гугль начинает присылать данные по–русски, НО! в виндовой кодировке. Тут опять больше всех надо становится TXMLDocument–у. В XML указано UTF–8, а реально 1251. Ну, наше дело маленькое, пользуем функцию AnsiToUtf8, и будет нам счастье. Результаты танцев смотрим в функциях GetInetFile и StreamToUtf8Stream.

Обратное геокодирование

Допустим, что у нас есть мобильный объект и нам известны его координаты. Необходимо получить ближайший адрес к точке, по возможности конечно. Адрес «Баренцево море», например, несет слабую смысловую нагрузку, поэтому не для любой точки можно провести процедуру обратного геокодирования с получением почтового адреса.

Обратное геокодирование возможно построением двух типов запросов. В запросе можно указывать координаты в параметре «q» и запрос не отличается от вышеописанного. Но эта функция не документирована. Честный же способ заключается в использовании параметра ll — «latitude, longitude». Пробуем следующую ссылку. Мы видим данные адреса по координатам.

Получение данных выглядит следующим образом:

// запрос обратного геокодирования

procedure TfmMain.DoReverseGeocodingRequest;

var

  FileOnNet: String;

  Stream:TMemoryStream;

begin

  // создаем поток

  Stream:=TMemoryStream.Create;

  try

    // формируем url для запроса

    FileOnNet:='http://maps.google.com/maps/geo?ll=%.6f,%.6f&output=xml&key=abcdefg&gl=ru';

    FileOnNet:=Format(FileOnNet,[Latitude,Longitude]);

    // получение потока с данными ответа

    if GetInetFile(FileOnNet,Stream) = True then begin

      StreamToUtf8Stream(Stream);

      // заполняем XMLDocument

      XMLDocument.LoadFromStream(Stream);



      // отображаем на дерево

      FillGeocodingTree;

    end;

  finally

    Stream.Free;

  end;

end;

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

Структура XML документа содержит узлы «Placemark». Узлы содержат информацию о точке по слоям карты. Самый первый узел «Placemark» содержит наиболее подробную информацию об адресе точки (верхний слой). Прикрутим получение адреса к примеру получения картинки карты из предыдущей статьи.

// обработка нажатия кнопки «Обновить»

procedure TfmMain.btRefershClick(Sender: TObject);

var

  OleGraphic: TOleGraphic;

begin

  // получаем изображение

  OleGraphic:=GetMap(edLatitude.Value,edLongitude.Value,tbScale.Position);

  if OleGraphic<>nil then begin

    // передаем изображение на Image

    Image.Picture.Assign(OleGraphic);

    OleGraphic.Free;

  end;

  // получение адреса

  DoReverseGeocodingRequest(edLatitude.Value,edLongitude.Value);

end;

В результате в строке состояния окна появится адрес точки, отображаемой на карте, на первой вкладке: