Версия для печати
Google Maps API. Геокодирование
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1398Максим Мазитов
дата публикации 12-05-2009 10:49Google Maps API. Геокодирование В предыдущей статье я рассматривал 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. "За чем же дело встало?" (жизненная цитата)
Строка запроса состоит из трех параметров:
- q — собственно текст запроса. Разделитель слов запроса знак "+".
- output — тип возвращаемых данных. Возможные значения xml, kml, csv, или json. Описание сморим на сайте указанной ссылке. CSV не рассматриваем, т.к. в этом случае выдается мало информации. KML используется в Google Earth. Json внутренний формат Google Map. XML нам наиболее близок и сердцу дорог, так что берем его.
- key — ключ карт. Как себя ведет не ясно, смотрим предыдущую статью. Пусть живет как есть.
- gl — код страны. Необходимо устанавливать в соответствии с доменами первого уровня страны проживания. В нашем случае — "ru". Для Украины, например, "ua".
Скачиваем пример и смотрим в код:
// получение списка точек по запросу 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;В результате в строке состояния окна появится адрес точки, отображаемой на карте, на первой вкладке:
That's all, folks! Пользуйтесь :)
К статье прилагается пример