Максим Мазитов дата публикации 12-05-2009 10:49 Google 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
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
StreamToUtf8Stream(Stream);
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
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
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.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.Picture.Assign(OleGraphic);
OleGraphic.Free;
end;
DoReverseGeocodingRequest(edLatitude.Value,edLongitude.Value);
end;
|
|
В результате в строке состояния окна появится адрес точки, отображаемой на карте, на первой вкладке:
That's all, folks! Пользуйтесь :)
К статье прилагается пример
[Сетевые службы и протоколы] [HTTP/HTTPS] [Взаимодействие с ГИС]
Обсуждение материала [ 08-10-2010 05:18 ] 18 сообщений |