Delphi ve Google Maps API

google_maps Hepimizin bildiği üzere Google arama motorunun popüler haritalama hizmetidir Google Maps. Gelişen teknoloji ile iyice küçülen dünyamızı evlerinizde gözlerinizin önüne getiren faydalı bir hizmet. Hizmete girmesinden kısa bir zaman sonra popülerliğini arttıran Google Map, sunduğu API’ler ile daha da yaygınlaşacağa benzer.

Artık hemen hemen her web sitesinde bir Google Map haritasına rastlamak mümkün. Ancak masaüstü uygulamalarda emsaline pek rastlanılmıyor henüz. Bizim makalemizin konusu ise masaüstü uygulamalarda Google Maps API’lerini kullanmak. Devasa bir hizmet kütüphanesi olma yolunda hızla ilerleyen Google Maps’in tüm özelliklerini detaylıca anlatmamız elbette mümkün değil; ancak makelemiz, Delphi’de TWebBrowser nesnesi vasıtası ile Google MAP API’lerinin kullanımını ve JavaScript – Delphi interaktif haberleşmesini anlatacak.

Makalemizi okumakta ilerlemeden evvel, Delphi & JavaScript Kardeşliği isimli makalemizi okumanız faydalı olacaktır. Google’ın bizlere sunduğu bu harita hizmeti ile haberleşmenin temellerini “Delphi & JavaScript Kardeşliği” isimli makalemizde anlattığımız için bu kısımda haritalama hizmetini kullanacak olan JavaScript sınıf tanımını, bu sınıfın Delphi altından kullanımını ve karşılıklı etkileşimini göstereceğim.

JavaScript hususunda pek bilgili olduğum söylenemez, bu vesile ile yapmış olabileceğim hatalardan yada yanlış kullanımlardan ötürü şimdiden affınızı istirham edeceğim. Google Maps’in hizmetlerinden istifade edebilmemiz için hemen hemen heryerde rastlayabileceğiniz; “Google Maps API Key almalısınız” tavsiyesini ben yapmayacağım. Çünkü key’siz de çalışabiliyor bu hizmet. Sitemde bu hizmetten faydalanmadığım için, API Key gereksinimi hakkında pek derin bilgi veremeyeceğim ama geliştirdiğim ve sizlerle paylaşacağım masaüstü uygulamasında bu key’e ihtiyaç duymadım. Ancak sizler illa da key olsun kırmızıdan olsun derseniz, buyurun adresi burası.

Makalemize geçmeden evvel haritalama hususunda okul hayatımızdaki bilgileri anımsamakta fayda olduğunu düşünüyorum. Ülkemiz 36-42 kuzey paralelleri ve 26-45 doğu meridyenleri arasındadır. Google Maps’de latitude ve longitude olarak isimlendirilen kavramlar bu enlem ve boylam bilgilerinden ibarettir. Enlem ve boylam bilgisini bildiğimiz herhangi bir lokasyona Google Map üzerinden erişmemiz son derece kolaydır. Ancak, Google haritalama hizmetinde dünya üzerindeki her ülkeye henüz destek vermemektedir. Hangi ülkelerde hangi google maps fonksiyonalitesinin kullanılabildiğini merak ederseniz eğer, bu adresten bakabilirsiniz.

Kod örneklerimize geçmeden evvel, ülkemizin haritasını yayınlamak istiyorum. Bu harita, sizin denemeleriniz için faydalı olabilir.

turkiye_enlem_boylam_haritasi

Şimdi esas işi yapacak olan JavaScript sınıfımıza bir göz gezdirelim:

function TGoogleMap()
{
    var map = null;
    var smallMapControl = null;
    var smallZoomControl= null;

    this.CreateMap = CreateMap;
    this.Render = Render;
    this.FindGeoCode = FindGeoCode;
    this.AddMarker = AddMarker;
    this.OpenInfoWindow = OpenInfoWindow;
    this.CloseInfoWindow = CloseInfoWindow;
    this.AddSmallMapControl = AddSmallMapControl;
    this.AddSmallZoomControl = AddSmallZoomControl;
    this.RemoveSmallMapControl = RemoveSmallMapControl;
    this.RemoveSmallZoomControl = RemoveSmallZoomControl;
    this.GotoCoordinat = GotoCoordinat;

    function CreateMap()
    {
        map = new GMap2(document.getElementById("Canvas"));

        GEvent.addListener(map, "click", function(overlay, point, overlaypoint) { if(point) external.DoClick(point.lat(), point.lng()); } );
    }

    function Render(lat, lng)
    {
        map.setCenter(new GLatLng(lat, lng), 15);
    }

    function FindGeoCode(address)
    {
        var geoCoder = new GClientGeocoder();
        geoCoder.getLatLng(address,
                     function(point)
                     {
                         if (!point) { external.DoError("Belirtilen adres bulunamadı.!"); }
                         else
                         {
                             external.DoGeoCodeFound(point.lat(), point.lng());
                         }
                     }
                           );
    }

    function AddMarker(lat, lng, draggable)
    {
        var marker;

        if (draggable)
        {
            marker = new GMarker(new GLatLng(lat, lng), {draggable: true});
        }
        else
        {
            marker = new GMarker(new GLatLng(lat, lng), {draggable: false});
        }

        map.addOverlay(marker);
        GEvent.addListener(marker, "click", function()
            {
                var coord = marker.getLatLng();
                if (coord)
                    external.DoMarkerClick(coord.lat(), coord.lng());
            } );
    }

    function OpenInfoWindow(lat, lng, message)
    {
        map.openInfoWindow(new GLatLng(lat, lng),
                           document.createTextNode(message));

    }

    function CloseInfoWindow()
    {
        map.closeInfoWindow();
    }

    function AddSmallMapControl()
    {
        if (!smallMapControl)
        {
            smallMapControl = new GSmallMapControl();
            map.addControl(smallMapControl);
        }
    }

    function AddSmallZoomControl()
    {
        if (!smallZoomControl)
        {
            smallZoomControl = new GSmallZoomControl();
            map.addControl(smallZoomControl);
        }
    }

    function RemoveSmallMapControl()
    {
        if (smallMapControl)
        {
            map.removeControl(smallMapControl);
            smallMapControl = null;
        }
    }

    function RemoveSmallZoomControl()
    {
        if (smallZoomControl)
        {
            map.removeControl(smallZoomControl);
            smallZoomControl = null;
        }
    }

    function GotoCoordinat(lat, lng)
    {
        map.panTo(new GLatLng(lat, lng));
    }

}

var GoogleMap = new TGoogleMap();

Yukarıda TGoogleMap isimlli bir sınıf tanımladık. Bu sınıfın CreateMap metodu, bir google harita nesnesi oluşturacak ve bu haritaya mouse ile tıklanması durumunda, Delphi’deki DoClick metodu tetiklenecek. Haritamızı Delphi’de TWebBrowser nesnesi aracılığı ile göstereceğimizi daha önce söylemiştik. Bunun için bize bir de HTML dosyası lazım. Henüz html dosyasının içeriğini paylaşmayacağım, çünkü daha anlatmamız gereken hususlar var bu JavaScript sınıfında. Ancak bir ön bilgi olarak, JavaScript sınıfımız içinde tanımladığımız CreateMap() isimli metodu, html dosyamızın body bölümünde onload event’ine yazacağız. Bu sayede, html dökümanımız yüklenir yüklenmez, harita nesnemiz oluşturulmuş olacak.

Yukarıda tanımını gördüğünüz JavaScript ile yazılmış TGoogleMap isimli sınıfımızın metodları; en yaygın kullanıldığına inandığım kısımları içeriyor. Elbette Google Maps API’si çok daha devasa boyutlarda ve burada sizinle paylaştıklarımdan çok daha fazlasını yapmaya muktedir. Bizim JavaScript sınıfımız, belirttiğiniz bir adrese ait olan enlem ve boylam bilgisini bulabilen, bu koordinata gidebilen, harita üzerine bir işaretçi(marker) bırakabilen ve istediğiniz bir metin mesajını istenilen enlem ve boylamda ekrana getirebilen küçük bir sınıf. Sizler bu sınıfa yeni özellikler eklemek, daha da yeteneklendirmek arzusunda iseniz, Google Maps API referans sayfasına uğramalısınız.

Render isimli metodumuz ise, belirtilen bir enlem boylamdaki harita bilgisini ekrana yansıtacaktır. Diğer metodların kendisini izah edebildiğini düşünerek, Delphi & Google Maps işbirliğine geçiyorum müsaadenizle.

Delphi & JavaScript konusunda izah etmeye çalıştığım hususları bu örneğimizde de kullanacağız. Örneğimizde izah etmeyeceğimiz bölümleri bir önceki makalemizden takip edebilirsiniz.

//frmGoogleMap uygulamamızın ana formudur..

  TApplicationWrapper = class(TObjectWrapper)
  private
    function GetApplication: TApplication;
  published
    procedure DoError(ErrorMessage : String);
    procedure DoGeoCodeFound(lat, lng : Double);
    procedure DoClick(lat, lng : Double);
    procedure DoMarkerClick(lat, lng : Double);

    property Application: TApplication read GetApplication;
  end;

var
  frmGoogleMap: TfrmGoogleMap;

  MarkerLat,
  MarkerLng : Double;

implementation

{$R *.dfm}

const
  Enter             = Char(13) + Char(10);
  HtmlFirstSection  = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' + Enter +
                      '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';

  HtmlStartSection  = '<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">';
  HtmlEndSection    = '</html>';
  HeadStartSection  = '<head>';
  HeadEndSection    = '</head>';

  BodyStartSection  = '<body onload="GoogleMap.CreateMap()" onunload="GUnload()">';
  BodyEndSection    = '</body>';
  BodyGoogle        = '<div id="Canvas" style="width: 500px; height: 300px"></div>';

  ScriptGoogle      = '<script src="http://maps.google.com/maps?file=api&v=2&sensor=true&key=" '+
                      'type="text/javascript"></script>';

  ScriptStartSection= '<script type="text/javascript">';
  ScriptEndSection  = '</script>';
  MetaSection       = '<meta http-equiv="content-type" content="text/html; charset=utf-8"/>';

  HTML = HtmlFirstSection         + Enter +
         HtmlStartSection         + Enter +
          HeadStartSection        + Enter +
            MetaSection           + Enter +
            ScriptGoogle          + Enter +
            '%HEADSECTION%'       + Enter +
            '%SCRIPTSECTION%'     + Enter +
          HeadEndSection          + Enter +
          BodyStartSection        + Enter +
            BodyGoogle            + Enter +
            '%BODYSECTION%'       + Enter +
          BodyEndSection          + Enter +
         HtmlEndSection;

procedure LoadHTML(WebBrowser: TWebBrowser; HTMLCode: String);
var
  sl: TStringList;
  ms: TMemoryStream;
begin
  WebBrowser.Navigate('about:blank') ;

  if Assigned(WebBrowser.Document) then
  begin
     sl := TStringList.Create;
     try
        ms := TMemoryStream.Create;
        try
           sl.Text := HTMLCode;
           sl.SaveToStream(ms);
           ms.Seek(0, 0);
           (WebBrowser.Document as IPersistStreamInit).Load(TStreamAdapter.Create(ms));
        finally
           ms.Free;
        end;
     finally
        sl.Free;
     end;
  end;
end;

const
  JSGoogle =
   ' function TGoogleMap() ' + Enter +
   ' { ' + Enter +
   '     var map = null; ' + Enter +
   '     var smallMapControl = null; ' + Enter +
   '     var smallZoomControl= null; ' + Enter +

   '     this.CreateMap = CreateMap; ' + Enter +
   '     this.Render = Render; ' + Enter +
   '     this.FindGeoCode = FindGeoCode; ' + Enter +
   '     this.AddMarker = AddMarker; ' + Enter +
   '     this.OpenInfoWindow = OpenInfoWindow; ' + Enter +
   '     this.CloseInfoWindow = CloseInfoWindow; ' + Enter +
   '     this.AddSmallMapControl = AddSmallMapControl; ' + Enter +
   '     this.AddSmallZoomControl = AddSmallZoomControl; ' + Enter +
   '     this.RemoveSmallMapControl = RemoveSmallMapControl; ' + Enter +
   '     this.RemoveSmallZoomControl = RemoveSmallZoomControl; ' + Enter +
   '     this.GotoCoordinat = GotoCoordinat; ' + Enter +

   '     function CreateMap() ' + Enter +
   '     { ' + Enter +
   '         map = new GMap2(document.getElementById("Canvas")); ' + Enter +

   '         GEvent.addListener(map, "click", function(overlay, point, overlaypoint) { if(point) external.DoClick(point.lat(), point.lng()); } ); ' + Enter +
   '     } ' + Enter +

   '     function Render(lat, lng) ' + Enter +
   '     { ' + Enter +
   '         map.setCenter(new GLatLng(lat, lng), 15); ' + Enter +
   '     } ' + Enter +

   '     function FindGeoCode(address) ' + Enter +
   '     { ' + Enter +
   '         var geoCoder = new GClientGeocoder(); ' + Enter +
   '         geoCoder.getLatLng(address, ' + Enter +
   '                                     function(point) ' + Enter +
   '                                     { ' + Enter +
   '                                         if (!point) { external.DoError("Belirtilen adres bulunamadi.!"); } ' + Enter +
   '                                         else ' + Enter +
   '                                         { ' + Enter +
   '                                             external.DoGeoCodeFound(point.lat(), point.lng()); ' + Enter +
   '                                         } ' + Enter +
   '                                     } ' + Enter +
   '                            ); ' + Enter +
   '     } ' + Enter +

   '     function AddMarker(lat, lng, draggable) ' + Enter +
   '     { ' + Enter +
   '         var marker; ' + Enter +

   '         if (draggable) ' + Enter +
   '         { ' + Enter +
   '             marker = new GMarker(new GLatLng(lat, lng), {draggable: true}); ' + Enter +
   '         } ' + Enter +
   '         else ' + Enter +
   '         { ' + Enter +
   '             marker = new GMarker(new GLatLng(lat, lng), {draggable: false}); ' + Enter +
   '         } ' + Enter +

   '         map.addOverlay(marker); ' + Enter +
   '         GEvent.addListener(marker, "click", function() {' +
   '              var coord = marker.getLatLng();' + Enter +
   '              if(coord)' + Enter +
   '                 external.DoMarkerClick(coord.lat(), coord.lng()); } );' + Enter +
   '     } ' + Enter +

   '     function OpenInfoWindow(lat, lng, message) ' + Enter +
   '     { ' + Enter +
   '         map.openInfoWindow(new GLatLng(lat, lng), ' + Enter +
   '                            document.createTextNode(message)); ' + Enter +
   '     } ' + Enter +

   '     function CloseInfoWindow() ' + Enter +
   '     { ' + Enter +
   '         map.closeInfoWindow(); ' + Enter +
   '     } ' + Enter +

   '     function AddSmallMapControl() ' + Enter +
   '     { ' + Enter +
   '         if (!smallMapControl) ' + Enter +
   '         { ' + Enter +
   '             smallMapControl = new GSmallMapControl(); ' + Enter +
   '             map.addControl(smallMapControl); ' + Enter +
   '         } ' + Enter +
   '     } ' + Enter +

   '     function AddSmallZoomControl() ' + Enter +
   '     { ' + Enter +
   '         if (!smallZoomControl) ' + Enter +
   '         { ' + Enter +
   '             smallZoomControl = new GSmallZoomControl(); ' + Enter +
   '             map.addControl(smallZoomControl); ' + Enter +
   '         } ' + Enter +
   '     } ' + Enter +

   '     function RemoveSmallMapControl() ' + Enter +
   '     { ' + Enter +
   '         if (smallMapControl) ' + Enter +
   '         { ' + Enter +
   '             map.removeControl(smallMapControl); ' + Enter +
   '             smallMapControl = null; ' + Enter +
   '         } ' + Enter +
   '     } ' + Enter +

   '     function RemoveSmallZoomControl() ' + Enter +
   '     { ' + Enter +
   '         if (smallZoomControl) ' + Enter +
   '         { ' + Enter +
   '             map.removeControl(smallZoomControl); ' + Enter +
   '             smallZoomControl = null; ' + Enter +
   '         } ' + Enter +
   '     } ' + Enter +

   '     function GotoCoordinat(lat, lng) ' + Enter +
   '     { ' + Enter +
   '         map.panTo(new GLatLng(lat, lng)); ' + Enter +
   '     } ' + Enter +
   ' } ' + Enter +

   ' var GoogleMap = new TGoogleMap(); ';

{ TApplicationWrapper }

procedure TApplicationWrapper.DoClick(lat, lng: Double);
begin
  frmGoogleMap.Caption := 'Map Clicked. Lat:' + FloatToStr(lat) + ' / Lng:' + FloatToStr(lng);
end;

procedure TApplicationWrapper.DoMarkerClick(lat, lng: Double);
begin
  frmGoogleMap.Caption := 'Marker Clicked. Lat:' + FloatToStr(lat) + ' / Lng:' + FloatToStr(lng);
end;

procedure TApplicationWrapper.DoError(ErrorMessage: String);
begin
  ShowMessage('Hata oluştu:' + ErrorMessage);
end;

procedure TApplicationWrapper.DoGeoCodeFound(lat, lng: Double);
var
  Doc : IHTMLDocument2;
  Win : IHTMLWindow2;
  sLat,
  sLng : String;
begin
  frmGoogleMap.Caption := 'Geocode found. Lat=' + FloatToStr(lat) + ', Lng=' + FloatToStr(lng);

  sLat := FloatToStr(lat);
  sLng := FloatToStr(lng);

  sLat := StringReplace(sLat, ',', '.', [rfReplaceAll]);
  sLng := StringReplace(sLng, ',', '.', [rfReplaceAll]);

  Doc := frmGoogleMap.wBrowser.Document as IHTMLDocument2;
  Win := Doc.parentWindow;

  Win.execScript('GoogleMap.Render(' + sLat + ',' + sLng + ')', 'JavaScript');

  MarkerLat := lat;
  MarkerLng := lng;
end;

function TApplicationWrapper.GetApplication: TApplication;
begin
  Result := Forms.Application;
end;

Yukarıdaki kod bloğunda, daha önce izah ettiğimiz Delphi & JavaScript haberleşmesini sağlayan TApplicationWrapper sınıfını ve ona eklenen metodları; örneğimiz için gereken HTML dosyanın yapısını ve JavaScript sınıfımızın şablonunun Delphi içine gömülü halini gözlemleyebilirsiniz. Eğer siz bir Google Maps API Key’i aldı iseniz bu anahtar değerini ScriptGoogle isimli sabitimizin key parametresine ekleyebilirsiniz. Ancak bizim örneğimizde gördüğünüz gibi boş ve çalışmaya mâni bir durum söz konusu değil. Tüm bu tanımlardan sonra, artık ekranımıza bir harita getirmenin zamanı geldi sanırım :)

procedure TfrmGoogleMap.btnLoadMapClick(Sender: TObject);
var
  sHTML,
  sScript : String;
begin
  sHTML := HTML;
  sScript :=  ScriptStartSection + Enter +
                   JSGoogle + Enter +
                 ScriptEndSection;

  sHTML := StringReplace(sHTML, '%HEADSECTION%'   , ''          , [rfReplaceAll]);
  sHTML := StringReplace(sHTML, '%SCRIPTSECTION%' , sScript , [rfReplaceAll]);
  sHTML := StringReplace(sHTML, '%BODYSECTION%'   , ''         , [rfReplaceAll]);

  LoadHTML(wBrowser, sHTML);

  while wBrowser.ReadyState < READYSTATE_INTERACTIVE do Application.ProcessMessages;
end;

Bu örneğimiz TWebBrowser nesnemizde boş bir haritanın oluşturulmasını sağlar. Şimdi Istanbul’daki Topkapı Sarayı‘nı arayalım ve haritamızda gösterelim:

procedure TfrmGoogleMap.btnFindTopkapiClick(Sender: TObject);
var
  Doc : IHTMLDocument2;
  Win : IHTMLWindow2;
begin
  Doc := wBrowser.Document as IHTMLDocument2;
  Win := Doc.parentWindow;

  Win.execScript('GoogleMap.FindGeoCode("Topkapı Sarayı, istanbul");', 'JavaScript');
end;

Karşımıza aşağıdaki gibi bir görüntü çıkmış olmalı:

topkapi_sarayi_google_maps

Bir sonraki adımımız haritaya bir işaretçi koymak olacak;

procedure TfrmGoogleMap.btnAddMarkerClick(Sender: TObject);
var
  Doc : IHTMLDocument2;
  Win : IHTMLWindow2;

  sLat,
  sLng : String;
begin
  Doc := wBrowser.Document as IHTMLDocument2;
  Win := Doc.parentWindow;

  sLat := FloatToStr(MarkerLat); // TApplicationWrapper.DoGeoCodeFound'da atama yapılmıştı.
  sLng := FloatToStr(MarkerLng);// TApplicationWrapper.DoGeoCodeFound'da atama yapılmıştı.

  sLat := StringReplace(sLat, ',', '.', [rfReplaceAll]);
  sLng := StringReplace(sLng, ',', '.', [rfReplaceAll]);

  Win.execScript('GoogleMap.AddMarker(' + sLat + ',' + sLng + ', true);', 'JavaScript');
end;

TGoogleMap sınıfımızın FindGeoCode çağrısı, Delphi’de TApplicationWrapper.DoGeoCodeFound çağrısının yapılmasına neden olur. Bulunan coğrafik koordinat bilgileri geçici iki değişkende saklanır ve marker eklemek için kullanılır. Son çağrımız ise , belirtilen koordinata gitmek ile ilgili olacaktır. Buyrun kodumuza bakalım:

procedure TfrmGoogleMap.btnGotoCoordinatClick(Sender: TObject);
var
  Doc : IHTMLDocument2;
  Win : IHTMLWindow2;

  sLat,
  sLng : String;
begin
  Doc := wBrowser.Document as IHTMLDocument2;
  Win := Doc.parentWindow;

  sLat := edtLat.Text;
  sLng := edtLng.Text;

  sLat := StringReplace(sLat, ',', '.', [rfReplaceAll]);
  sLng := StringReplace(sLng, ',', '.', [rfReplaceAll]);

  Win.execScript('GoogleMap.GotoCoordinat(' + sLat + ',' + sLng + ');', 'JavaScript');
  Win.execScript('GoogleMap.AddMarker(' + sLat + ',' + sLng + ', true);', 'JavaScript');
end;

Bu örneğimiz ise belirttiğimiz enlem ve boylam noktalarına haritayı konumlandıracak ve o noktaya bir adet işaretçi(marker) bırakacaktır. Örneğimizde harita üzerinde yada marker’lar üzerinde yapacağınız tıklamalar, Delphi’ye iletilecek ve TApplicationWrapper sınıfının DoClick ve DoMarkerClick metodlarının tetiklenmesine neden olacaktır.

Gördüğünüz gibi, bir önceki makelemizde değindiğimiz Delphi ve JavaScript kardeşliğinin pek çok faydası var. Verdiğimiz bu örnek şu aşamada pek bir anlam ifade etmese de, siz uygulamalarınızda eminimki bu fonksiyonaliteyi anlamlandırabileceksiniz. Belki veritabınında kayıtlı olan müşterilerinizin adres bilgilerini harita üzerinde gösterecek yada sadece merakınızı tatmin etmek için kullanacaksınız. Ancak bir gerçek var ki, bu verilen bilgiler ışığında daha fazlasını yapabilmek için yola çıkacaksınız ;) Daha önce de dediğim gibi, Google Maps API bir derya, ben sizler için deryadan bir damla aldım, sizlere sundum.

Gerisi sizlerin keşfini bekliyor.

Saygılar, sevgiler..