Максим Мазитов дата публикации 16-08-2009 04:08 Интерактивные карты Google
В предыдущих опусах API статических карт Google и Google Maps API. Геокодирование я рассматривал варианты картографии, ограниченные во взаимодействии с пользователем.
В этой статье я попытаюсь расписать построение приложения на Delphi 7, работающего с интерактивными гуглокартами. Используемая здесь информация находится на стыке HTML, JavaScript, ActiveX и Delphi. Я заранее не претендую на полноту и высокую точность описываемого предмета. В данной области танцев с бубнами хватит на всех шаманов Сибири. Возможно, кому-нибудь статья, а может быть, и коды, помогут обойти те проблемы, с которыми я столкнулся. Но и я решил не все.
Вам требуется включить картографию в приложение. И какой бюджет у вашего проекта? 0? Тогда первый вариант — берем растровую карту, привязываем по трем точкам, немного нетривиальных математических вычислений и вы в состоянии выводить требуемые точки по координатам. Отметаем сразу. Второй вариант — качаем набор компонентов, например NGis (описан на этом сайте). Качаем MapInfo, GpsEdit (есть его исходники!). Идем на www.freemaps.ru. Убиваем вагон времени на конвертацию и сведение слоев. Результат работает, но для коммерческого продукта выглядит не слишком презентабельно. И сразу же возникает вопрос актуальности карт.
У вас есть $500 на карты? Ну что же, есть вариант — (контору называть не буду) ActiveX компонент, HASP ключ, без которого компонент не работает и отдельные деньги за карту каждого региона. Цена экземпляра софта возрастает на стоимость ключа и карт. Есть вариант взлома, как же без него, но если вы собираетесь работать серьезно и распространять легальный софт, то вариант взлома не проходит.
Вы готовы выложить $5000 — отлично! Именно столько стоили все слои Питера в формате MapInfo пару-тройку лет назад. У вас есть права на карты, вы их легко распространяете (закодировав конечно, кто захочет делиться пятью тысячами уёв?). Внимание, вопрос: а существует ли актуальная карта Петербурга, не говоря уже о Москве? Сколько будут стоить обновления? Богатые конторы, конечно, могут себе позволить, хотя и они сейчас экономят. Но для старта проекта без солидной финансовой поддержки этот вариант не проходит.
В нашем случае мы выбрали вариант за $500 и реализовали его. Когда в нете появилась разрозненная информация по возможности встраивания карт с источников web-картографии, естественно, мы за неё ухватились.
Чего мы хотим добиться: в приложении Delphi вывести интерактивную карту Google со всеми возможностями пользовательского интерфейса (масштабирование, центрирование, перетаскивание) и отобразить на карте произвольный набор точек.
Кратко схема взаимодействия выглядит так: мы встраиваем TWebBrowser в приложение, открываем HTML страницу, на ней расположен Java-applet Google Maps, которым мы управляем через вызовы JavaScript.
Я использовал следующий подход в создании приложения: программное обеспечение должно работать с разными провайдерами картографической информации. Для этого я создал базовый класс, инкапсулирующий действия над точками и картой. Для подключения другого типа карты нужно будет только переопределить методы базового класса.
И еще: вся инфа по аплету карты почерпнута из http://code.google.com/intl/ru/apis/maps/documentation/index.html.
Качаем пример, распаковываем и смотрим каталог HTML.
Небольшое замечание — приведенные примеры работают только с Internet Explorer 7 и выше. При попытке работать в связке Delpi7-IE6 возникли проблемы, которые от недостатка времени решать даже не пытались.
Список файлов каталога HTML:
map-article-1-simple-map.html | Самой простой вариант страницы с картой Google. |
map-article-2-map-with-controls.html | Карта с добавленными элементами управления. |
map-article-3-map-with-marker.html | Карта с примером добавления маркеров на карту. |
47.png | Картинка для пользовательского маркера. |
tlabel.2.05.js | Реализация TLabel (см. ниже) |
map-final.html | Реализация для использования в программе. |
|
Все страницы подробно прокомментированы.
Откроем в текстовом редакторе страницу "map-article-3-map-with-marker.html".
В теге Body в событии загрузки страницы вызывается функция initialize, которая производит действия по созданию и настройке карты. Читаем комментарии.
Производимые действия:
- Создание экземпляра класса GMap2.
- Центрирование на требуемой координате.
- Создание элементов управления картой.
- Экспериментальный вывод точек на карте.
Ещё в теге скрипта имеются функции добавления маркеров:
function addMarker(latitude, longitude, hint) | добавление пустого маркера с точкой и подсказкой |
function addMarkerIcon(latitude, longitude, hint, iconPath) | добавление маркера с подсказкой и пользовательской иконкой |
|
Теперь о маркерах — в зависимости от сложности задачи, приведенных маркеров может вполне оказаться достаточно. Но меня не устроили ограничения маркеров Google — если нужно вывести несколько произвольных текстовых надписей в разных местах, то простыми вызовами встроенных функций не обойдешься.
Пришлось посерфить в нете и в результате, я нашел устраивающий меня код на JScript, размещающий прямоугольник с текстом в указанной географической точке. Смотрим файл "tlabel.2.05.js".
В нем реализовывается класс TLabel и добавляется функционал к классу GMap2, в котором добавляются и удаляются экземпляры класса TLabel. В меру своих скромных возможностей, я добавил метод по удалению TLabel по идентификатору. Но вот профессионального пояснения по коду скрипта дать не могу, хоть и понимаю, как он работает (сам такое написать не смогу).
Для добавления на страницу нового маркера пишем следующий код в HTML на JavaScript (см. файл "map-final.html"):
function createTLabel(id, point, html, astyle) {
var label = new TLabel();
label.id = id;
label.anchorLatLng = point;
label.anchorPoint = 'center';
label.content = '<div class="'+astyle+'"><nobr><b>'+html+'</b></nobr></div>';
label.percentOpacity = 70;
map.addTLabel(label);
}
|
|
Маркер использует стили, описанные в начале файла "map-final.html". Стили завязаны на строковые названия цветов. Если возникнет надобность в новом цвете достаточно в тексте страницы описать стиль с именем формата ".<имя цвета>label".
Вызов функции добавления маркера на JavaScript:
createTLabel("Id0", new GLatLng(59.944265, 30.319948), "<nobr>Текст маркера</nobr>", "bluelabel");
|
|
Первый параметр — уникальный идентификатор, указывая который возможно удалить маркер с карты. Далее идут координаты в десятичных долях градуса, HTML текст маркера и его стиль.
Вроде бы ничего сложного.
Основной вопрос — как управлять Java аплетом Google. Берем TWebBrowser и смотрим его интерфейсы. Мы можем заставить IE выполнить код JavaScript из внешнего приложения следующим образом (на этом сайте он тоже описан):
try
if WebBrowser.Document<>nil then
IHTMLDocument2(WebBrowser.Document).parentWindow.execScript(Script,'JavaScript');
except
on E:Exception do begin
MessageBox(Handle,PChar(E.Message),'Ошибка исполнения', MB_OK or MB_ICONERROR);
end;
end;
|
|
, где переменная Script содержит текст скрипта. И этого достаточно.
Дальше остается только кодирование и чтение доков гугла. Смотрим исходные коды.
Все исходники, работающие с картографией, расположены в каталоге "MapsFrames".
- BaseMapFrames.pas содержит класс точки на карте (описания полей в коде), вспомогательные типы и базовый класс фрейма карты.
- GoogleMapFrames.pas содержит реализацию базового класса фрейма карты для Google.
Я использовал фреймы (TFrame) вместо компонентов по причине более легкой переносимости кода с компьютера на компьютер — их не надо регистрировать, а написать один раз 5 строк кода по встраиванию фрейма в форму ненапрягающая задача.
Класс точки TMapPoint является простым контейнером свойств точки карты — координаты, текст и т.п.
Тип TPointsChangingType описывает действия, которые производились с точками, для передачи форме в соответствующем событии.
Класс TBaseMapFrame является контейнером точек, позволяющим добавлять, изменять и удалять точки на карте, и содержит абстрактные методы по работе с картой для перекрытия их (методов) в реализациях под конкретную картографию. Это различные варианты масштабирования, а также показ и скрытие точек. При создании класса я применил Doc-View схему (не до конца, правда), поэтому фрейм имеет событие PointsChangingEvent, уведомляющее об изменениях в коллекции точек. По большому счету, это просто обертка над TObjectList с небольшим количеством абстрактных методов.
Теперь о TGoogleMapFrame. Перекрываем методы масштабирования:
procedure TGoogleMapFrame.ZoomIn;
begin
ExecScript('map.zoomIn();');
end;
procedure TGoogleMapFrame.ZoomOut;
begin
ExecScript('map.zoomOut();');
end;
|
|
Центрирование:
procedure TGoogleMapFrame.SetCenter(Latitude:Double;Longitude:Double;Scale:Integer=-1);
var
Script:String;
begin
if Scale>=0 then begin
Script:='var point = new GLatLng('+FloatToStr(Latitude)+','+FloatToStr(Longitude)+'); '+
'map.setCenter(point, '+IntToStr(Scale)+')';
end else begin
Script:='var point = new GLatLng('+FloatToStr(Latitude)+','+FloatToStr(Longitude)+'); '+
'map.setCenter(point)';
end;
ExecScript(Script);
end;
|
|
Добавление точки:
function TGoogleMapFrame.PointToAddString(MapPoint:TMapPoint):String;
var
ColorStr:String;
begin
ColorStr:=ColorToString(MapPoint.Color)+'label';
if (Copy(ColorStr,1,2)='cl') then
Delete(ColorStr,1,2);
Result:='createTLabel("%s", new GLatLng(%.8f, %.8f), "%s", "%s");';
Result:=Format(Result,[MapPoint.Id,MapPoint.Latitude,MapPoint.Longitude,MapPoint.Caption,ColorStr]);
end;
procedure TGoogleMapFrame.ShowPoint(Index:Integer);
var
Script:String;
begin
HidePoint(Index);
Script:=PointToAddString(Points[Index]);
ExecScript(Script);
end;
procedure TGoogleMapFrame.HidePoint(Index:Integer);
var
Script:String;
begin
if Index>=PointsCount then
Exit;
Script:='map.removeTLabelById("'+Points[Index].Id+'");';
ExecScript(Script);
end;
|
|
Функция PointToAddString возвращает скрипт по созданию точки. ShowPoint размещает точку из коллекции на карте. Вызов ShowPoint производится из методов базового класса. HidePoint убирает точку с карты.
Вызовы любых скриптов приводят к исключительной ситуации, если страница находится в процессе загрузки. Для обхода этих грабель я ввел событие загрузки страницы:
procedure TGoogleMapFrame.WebBrowserStatusTextChange(Sender: TObject;
const Text: WideString);
begin
if not FInitialized and (WebBrowser.ReadyState=READYSTATE_COMPLETE) then begin
FInitialized:=True;
if Assigned(FOnMapLoaded) then
FOnMapLoaded(Self);
end;
end;
|
|
При первом получении статуса браузера READYSTATE_COMPLETE вызывается событие OnMapLoaded. До срабатывания этого события работать со скриптами нельзя!
В классе TBaseMapFrame предусмотрены методы поддержки длительных изменений BeginUpate и EndUpdate. Смысл я думаю понятен. При построении больших маршрутов очень даже полезно.
В примерах лежат 2 приложения.
Приложение GoogleInteractiveMap демонстрирует большинство функционала фреймов. Для корректной работы необходимо в приложении отрыть файл "HTML\map-final.html". Только в нем есть все требуемые функции JavaScript. После загрузки страницы карты становятся доступными все функции программы. Маршруты подгружаются из каталога "Routes". Маршруты я создавал через Google Earth, т.к. он выдает в маршрутах некие осмысленные данные (но осознанно не строит маршрутов по России). В файле "RouteFromHelsinkiToRussia.txt" дорога из Хельсинки до Российской границы, более 1100 точек. На моей не самой медленной машинке с четырьмя ядрами загрузка занимает 1 минуту ровно. В файле "SmallRoute.txt" первые 10 точек маршрута.
Приложение RoutePlayer реализует работу с одной точкой и демонстрирует работу события OnMapLoaded. После загрузки карты начинается перемещение по точкам маршрута с периодом 1 секунда. Путь к файлу маршрута зашит в исходном тексте.
Рисунок 2
Видите синий прямоугольник со звездочкой в центре карты?
Все коды обильно закомментированы.
Как я говорил в начале, все проблемы обойти не удалось. Осталось у меня два вопроса, если кто-то подскажет мне решение, моя благодарность не будет знать границ в определенных объемах :):
- При достаточно большом маршруте, закрывая GoogleInteractiveMap, я получаю сообщение от IE о том, что скрипт тормозит работу и предложение прекратить выполнять скрипты. Я пытаюсь убивать все точки перед закрытием. Но, то ли я напутал что-то в событиях, то ли в силу природной тупости проблема не решается.
- Очень хотелось бы получать координаты под курсором. Насколько я понимаю, функционал у класса GMap2 для этого есть. Но как получить содержимое переменной JavaScript в Delphi я не догнал. Я понимаю, что путь лежит через DOM модель IE. Но как?
На этом, пожалуй, цикл гуглокартографии можно считать закрытым. Ну и отлично.
В эту обертку можно завернуть картографию Яндекс (лучше покрытие России), Yahoo, Microsoft (возможно). И особенно рекомендую посмотреть http://www.openstreetmap.org/, потому что OpenSource.
К статье прилагаются примеры
[Сетевые службы и протоколы] [HTTP/HTTPS] [Взаимодействие с ГИС] [JavaScript,JScript]
Обсуждение материала [ 22-04-2010 01:38 ] 12 сообщений |