Версия для печати
Интерактивные карты Google
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1410Максим Мазитов
дата публикации 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 и Java Script
Качаем пример, распаковываем и смотрим каталог 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 текст маркера и его стиль.
Вроде бы ничего сложного.
Подключаем к Delphi
Основной вопрос — как управлять 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.
К статье прилагаются примеры