Google Places Api i Google Maps Api - aplikacja

Google Places API służy do wyszukiwania miejsc spełniających dane kryteria w danym regionie. W praktyce wykonujemy zapytanie do serwerów google z określonymi parametrami i otrzymujemy w odpowiedzi dane w formacie JSON (lub xml do wyboru). Miejsca można wyszukiwać na podstawie nazwy i typu (lista dostępnych typów jest dość szeroka). Zanim zaczniemy korzystać z google places api musimy mieć api key. W tym celu należy zalogować się swoim kontem google na stronie. Następnie stworzyć nowy project (create project). Z listy serwisów wybieramy places api i akceptujemy regulamin. Na liście serwisów powinniśmy widzieć zieloną ikonkę ustawioną na ON przy Places API. Za darmo mamy limit 1000 zapytań na dzień. Wybieramy z lewego menu api access i kopiujemy widoczny tam api key. Teraz możemy już przejść do tworzenia aplikacji wykorzystującej places api. Nasza aplikacja będzie również wykorzystywać google maps api v3. Aplikacja będzie wyświetlać na mapie google po kliknięciu na odpowiedni link restauracje, noclegi i bankomaty w pobliżu centrum Zakopanego. Google places + google maps - aplikacja Na początek stwórzmy dokument html.

<!DOCTYPE html>
<html lang="pl">
  <head>
    <meta charset="utf-8" />
    <title>Google Places Api + Google Maps Api - aplikacja</title>
    <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js"></script>
    <script type="text/javascript" src="app.js"></script>
    <style type="text/css">
	body {
		font: 12px Arial, Helvetica, sans-serif;
	}
	#map {
	    width:500px; height:300px; float:left;
	}
	#buttons {
	    float:left;
	}
	#buttons a {
	    width:150px;
	    height:30px;
	    display:block;
	    text-decoration:none;
	    color:#175c0b;
	    padding:15px 0 0 35px;
	}
	a#restauracje {
	    background: url("markers/restaurant.png") no-repeat left center;
	}
	a#noclegi {
	    background: url("markers/lodging_0star.png") no-repeat left center;
	}
	a#bankomaty {
	    background: url("markers/atm.png") no-repeat left center;
	}
    </style>
  </head>
  <body>
	<div id="map"><!-- mapa --></div>
    <div id="buttons">
	<a href="#" id="restauracje" style="background: url(markers/restaurant.png) no-repeat left center">Restauracje</a>
	<a href="#" id="noclegi" style="background: url(markers/lodging_0star.png) no-repeat left center">Noclegi</a>
	<a href="#" id="bankomaty" style="background: url(markers/atm.png) no-repeat left center;">Bankomaty</a>
    </div>
  </body>
</html>
Załączamy google maps, jquery oraz app.js (w tym pliku będzie kod javascript naszej aplikacji). Następnie widzimy trochę stylów css. Na naszą mapę przygotowaliśmy pojemnik o id map. Po kliknięciu linków wewnątrz diva o id buttons będą wywoływane odpowiednie zapytania. Następnie na mapie pokazywać się będą odpowiednie markery dotyczące restaturacji, noclegów itd. Dodam dla ciekawych, że ikonki markerów wykorzystane w aplikacji znajdują się na stronie mapicons. Ikony te wykorzystujemy zarówno jako ikony markerów jak i tło dla odpowiednich elementów a wewnątrz buttons. Stwórzmy teraz 3 pliki php: restaturant.php, lodging.php i atm.php. Odpowiadają one trzem typom miejsc w google places api. Poniżej kod jednego z nich. Nazwę klucz trzeba zastąpić wygenerowanym wcześniej api keyem.
header('Content-type: application/json');
echo file_get_contents('https://maps.googleapis.com/maps/api/place/search/json?location=49.299181,19.9495621&radius=500&types=restaurant&sensor=false&key=klucz');
Na początku wysyłamy nagłówki, że dane będą w formacie JSON. Potem następuje już właściwe zapytanie do api. Do tego celu wykorzystujemy funkcję file_get_contents. Przyjrzyjmy się bliżej urlowi. Każde zapytanie do google places zaczynamy od https://maps.googleapis.com/maps/api/place/search/. Potem podajemy format w jakim dane mają być zwrócone (w naszym przypadku json). Po znaku zapytania podajemy kolejne parametry. Parametr location to lat i lng punktu na mapie wokół którego będziemy szukać miejsc. Radius to promień (w metrach), w którym miejsca będą szukane. W kolejnym parametrze types podejemy typ lub typy miejsc, które chcemy odszukać. Jeśli podajemy więcej niż jeden typ oddzielamy je znakiem |. Można podać jeszcze parametry language (kod języka, w którym będą zwracane dane o ile to możliwe) i name (umożliwia wyszukiwanie po nazwie miejsca). W odpowiedzi np. na wyszukiwanie miejsc noclegowych w pobliżu centrum Zakopanego otrzymujemy:
{
   "html_attributions" : [],
   "results" : [
      {
         "geometry" : {
            "location" : {
               "lat" : 49.2952570,
               "lng" : 19.9520690
            }
         },
         "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/lodging-71.png",
         "id" : "c19ec96e2f414ff935415cd8a79de1c962aa522d",
         "name" : "Hotel Orbis Giewont Zakopane",
         "reference" : "CoQBcwAAANO-jJHEB2IYaw9K9QwXYV9JcD0QE6obkPsXJcoEaIbsIXMb61K-nbdL2MroRV8phLgZmVJMnALzYvcXxtAIR3Zzjjda5Q9vw9CGWhx5Tb_Lg7upU77ld0RMyCNg86DsSZHOoCGiUwZtnNSokOLzSh0QVUhjG2pfPZ3cuqAGlm3cEhCzXPZrrmPZmrpwkK_QvtHOGhSmZLBrHMZQmyJBa1DnRXI_b7PD9A",
         "types" : [ "lodging", "establishment" ],
         "vicinity" : "Tadeusza Kościuszki, Zakopane"
      },
		...
      {
         "geometry" : {
            "location" : {
               "lat" : 49.2977660,
               "lng" : 19.9469030
            }
         },
         "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/lodging-71.png",
         "id" : "35992b7de31635e325a93a293fa08333a9d2065a",
         "name" : "TOURISMO.pl - apartamenty Zakopane - wynajem apartamentów",
         "reference" : "CqQBkgAAANyKRc1PP-qutWVjX1RnLgtRNqmGVZq9RUVah7I9av1_xAr_u9emzUK53ZB_NNdE7gTAYqU45nNK0Q1JJTQ54nUhiTKkcYH2i0NDvj4COyKleq3STAYnDL2ZpD2ly89E5-jhuaKHcgAEELIe5gtKLIvnJ003FI-UmaDrUfwOuVCXgLpdliHEoQ9FvPlWDOmlYK2h3fpKCnQ2O8UfcungXWQSECXWfP3m61-dkfgMqhpbZVsaFPEYyg1041c9ofjnlpVLJ2H0uZhW",
         "types" : [ "lodging", "establishment" ],
         "vicinity" : "Droga na Gubałówkę, Zakopane"
      }
   ],
   "status" : "OK"
}
Przedstawiam tylko część wyniku ze względu na oszczędność miejsca. Jak widzimy w jsonie mamy trzy główne elementy: status, results i html_attributions. Pierwszy z nich pokazuje nam czy żądanie się powiodło. Możliwymi odpowiedziami są: OK - zapytanie się powiodło i przynajmniej jeden wynik został zwrócony, UNKNOWN_ERROR - błąd po stronie serwera, ZERO_RESULTS - oznacza, że odwołanie było poprawne ale nie zwraca wyników, OVER_QUERY_LIMIT - przekroczony limit połączeń, REQUEST_DENIED - oznacza, że Twoje zapytanie zostało odrzucone (pamiętaj aby atrybut sensor był ustawiony), INVALID_REQUEST - nieprawidłowe zapytanie. results jest to tablica znalezionych miejsc z informacjami na temat każdego z nich. Przyjrzymy się teraz jakie dane otrzymujemy dla poszczególnego rezultatu.
  • name - nazwa znalezionego miejsca np. "Hotel Orbis Giewont Zakopane",
  • vicinity - określa lokalizację najczęściej w postaci nazwy miasta, ulicy, dzielnicy itp.,
  • types[] - typy miejsc opisujące dany obiekt,
  • geometry - zawiera informacje geometryczne na temat obiektu, generalnie zawiera lat i lng,
  • icon - url do rekomendowanej ikony (my będziemy używać innej),
  • reference - unikalny token służący do pobrania szczegółowych informacji o miejscu,
  • id - unikalny stały identyfikator obiektu (nie służy do wyciągania informacji na temat miejsca).
Zabieramy się teraz do programowania właściwej aplikacji w javascript. Zaczniemy od stworzenia mapy w miejscu diva o id map wycentrowanej na Zakopane. Cały kod wykonujemy po załadowaniu strony (window.onload). Łapiemy najpierw diva, w którym ma być mapka (document.getElementById('map')). Do obiektu google.maps.LatLng przekazujemy lat i lng centrum Zakopanego. W zmiennej options określamy podstawowe parametry mapy: środek mapy, zoom i typ. Następnie do obiektu google.maps.Map przekazujemy naszego diva i opcje. Mamy już mapę, z którą będziemy pracować.
(function() {
	window.onload = function() {
		var mapDiv = document.getElementById('map');
		var latlng = new google.maps.LatLng(49.299181, 19.9495621);
		var options = {
			center: latlng,
			zoom: 14,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};
		var mapa = new google.maps.Map(mapDiv, options);
	}
})();
Jak będzie działać aplikacja? Po kliknięciu na odpowiedni link np. noclegi na mapie będą się pojawiać markery reprezentujące noclegi itd. Poniżej kod, który będzie się wykonywał po kliknięciu na link o id noclegi.
$('#noclegi').live('click', function() {
	clearOverlays();
	$('#buttons a').each(function () {
		$(this).attr('style',$(this).attr('style').replace('2.png','.png'));
		$(this).css({'color': '#175c0b','background-color': 'white'});
	});
	$(this).css({'background': '#175c0b url(markers/lodging_0star2.png) no-repeat left center',
	'color': 'white'});
	markers_display('lodging.php','lodging_0star.png');
	return false;
});
Najpierw wywołujemy funkcję clearOverlays, o której powiem w dalszej części. Poniższa część odpowiada jedynie za zmianę ikonki i innych stylów css aktualnie klikniętego przycisku. Nie będziemy się tu na tym skupiać.
$('#buttons a').each(function () {
	$(this).attr('style',$(this).attr('style').replace('2.png','.png'));
	$(this).css({'color': '#175c0b','background-color': 'white'});
});
$(this).css({'background': '#175c0b url(markers/lodging_0star2.png) no-repeat left center',
'color': 'white'});
Na końcu wywołana jest najważniejsza dla nas funkcja markers_display, której przekazujemy plik z którego ma pobierać dane i ikonkę, która ma reprezentować wszystkie znaczniki tego typu. Funkcja markers_display przedstawia się następująco.
function markers_display(file,ikona)
{
	$.ajax(file).done(function(dane){
		for(i=0;i<dane.results.length;i++)
		{
			var lat = parseFloat(dane.results[i].geometry.location.lat);
			var lng = parseFloat(dane.results[i].geometry.location.lng);
			dodajMarker(lat,lng,dane.results[i].id,dane.results[i].reference,ikona);
		}
	});
}
Wykonujemy zapytanie ajaxowe do przekazanego w parametrze pliku i przechodzimy przez otrzymane rezultaty. Należy pamiętać, że dane z pliku php otrzymujemy w omówionym powyżej formacie json. Dla każdego rezultatu pobieramy lat i lng (czyli położenie markera) i wykonujemy funkcję dodajMarker. Funkcji tej przekazujemy: lat, lng, unikalny identyfikator rezultatu, token reference oraz ikonę. Zadeklarujmy teraz zmienną markersArray. Jest to tablica przechowująca wszystkie markery.
var markersArray = [];
Implementacja funkcji dodajMarker poniżej. Jak się pewnie domyślasz odpowiada ona za wstawienie na mapę pojedyńczego markera. Pierwsze cztery zmienne dotyczą ikony markera (rozmiar,punkt startowy, punkt zaczepienia oraz obrazek ikony. Następnie określamy opcje markera tzn. jego pozycję (position) na przekazaną wcześniej, mapę na jakiej ma być umieszczony oraz ikonę. Marker tworzymy za pomocą google.maps.Marker. Umieścimy jeszcze marker w tablicy (markersArray.push(marker)). Na uwagę zasługuje fragment przypisywania funkcji do zdarzenia click na markerze - google.maps.event.addListener. Wywołujemy w niej funkcję detail obsługującą pobranie szczegółowych danych o miejscu i umieszczenie ich w dymku informacyjnym InfoWindow.
function dodajMarker(lat,lng,id,refek,ikona)
{
	var rozmiar = new google.maps.Size(33,40);
	var punkt_startowy = new google.maps.Point(0,0);
	var punkt_zaczepienia = new google.maps.Point(16,16);
	var ikona1 = new google.maps.MarkerImage('markers/'+ikona,rozmiar,punkt_startowy,punkt_zaczepienia);
	var opcjeMarkera =
	{
		position: new google.maps.LatLng(lat,lng),
		map: mapa,
		icon: ikona1
	}
	var marker = new google.maps.Marker(opcjeMarkera);
	markersArray.push(marker);
	google.maps.event.addListener(marker,"click",function()
	{
		detail(refek, function(detale){
			marker.txt = '<div style="color:black;height:100px;"><a style="color:black;font-size:16px;font-weight:bold;" href="'+detale.result.url+'">'+detale.result.name+'</a><br />'+detale.result.formatted_address+'<br /> '+detale.result.formatted_phone_number+'</div>';
			dymek.setContent(marker.txt);
			dymek.open(mapa,marker);
		});
	});
}
Przejdźmy teraz do omówienia funkcji detail. Najpierw musimy utworzyć dodatkową zmienną.
var dymek = new google.maps.InfoWindow();
Sama funkcja przedstawia się następująco.
function detail(ref, lat,lng, callback)
{
	$.ajax('detail.php?ref='+ref).done(function(detail){
		callback(detail,lat,lng);
	});
}
Przyjrzyjmy się najpierw plikowi detail.php pobierającemu szczegółowe dane dotyczące miejsca. Plik różni się od poprzednio omówionego pliku php jedynie urlem.
header('Content-type: application/json');
echo file_get_contents('https://maps.googleapis.com/maps/api/place/details/json?reference='.$_GET['ref'].'&sensor=false&key=AIzaSyC_cl3qp5zl36ry6jQkfY6iutxV9lJjVZM');
Url zaczyna się od https://maps.googleapis.com/maps/api/place/details/. Potem tak jak poprzednio podajemy format json i po znaku zapytania kolejne parametry. reference to omawiany wcześniej token, który przekażemy do pliku php w parametrze ref urla, key natomiast to nasz api key. W odpowiedzi otrzymamy szczegółowe dane na temat określonego miejsca postaci:
{
   "html_attributions" : [],
   "result" : {
      "address_components" : [
         {
            "long_name" : "2f",
            "short_name" : "2f",
            "types" : [ "street_number" ]
         },
         {
            "long_name" : "ul. Kościeliska",
            "short_name" : "ul. Kościeliska",
            "types" : [ "route" ]
         },
         {
            "long_name" : "Zakopane",
            "short_name" : "Zakopane",
            "types" : [ "locality", "political" ]
         },
         {
            "long_name" : "PL",
            "short_name" : "PL",
            "types" : [ "country", "political" ]
         },
         {
            "long_name" : "34-500",
            "short_name" : "34-500",
            "types" : [ "postal_code" ]
         }
      ],
      "formatted_address" : "ul. Kościeliska 2f, Zakopane, Polska",
      "formatted_phone_number" : "601 505 325",
      "geometry" : {
         "location" : {
            "lat" : 49.2964480,
            "lng" : 19.9474930
         }
      },
      "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/lodging-71.png",
      "id" : "514524346308d3cfafcaa6c5669969a7d692da04",
      "name" : "Apartamenty Zakopane",
      "reference" : "CnRrAAAA1hNtAKCLT1_9WDpOXvSlXcff0bJgIorJ_LkfaAJxLFjVHfGfqP45ecumAMNZPPW9fn1pjAujm7J6UsKl-jZJwT06mZcjOIkjgLRAnlPJIp1KIF6E7jnO2_cDf0jTqAnV4jqGIMNqc07Z1kRmgAlIuRIQaFiRCyvggLToobIrIskZFxoUHQ4FTKVXck1hKxshSvR4MrM5gAk",
      "types" : [ "lodging", "establishment" ],
      "url" : "http://maps.google.com/maps/place?cid=4940985964139476320",
      "vicinity" : "ul. Kościeliska, Zakopane"
   },
   "status" : "OK"
}
W odpowiedzi otrzymamy tak jak poprzednio trzy główne elementy status, html_attributions i result. Element status zwraca jeden z kodów (jak poprzednio) np. OK - żądanie zakończone powodzeniem. Najważniejszy element result zawiera szczegółowe informacje na temat danego miejsca. Przyjrzymy się teraz poszczególnym częściom rezultatu:
  • name - nazwa miejsca,
  • vicinity - lokalizacja najczęściej w postaci nazwy miasta, ulicy, dzielnicy itp.,
  • types[] - tablica zawierająca typy miejsc opisujące dany obiekt,
  • formatted_phone_number - numer telefonu,
  • formatted_address - adres miejsca,
  • address_components[] - tablica, w której przechowywane są części składowe adresu,
  • geometry - zawiera informacje o położeniu w postaci lat i lng,
  • icon - sugerowana ikona obiektu,
  • reference - token,
  • id - unikalny identyfikator,
  • url - adres url do oficjalnej strony miejsca,
  • rating - ocena miejsca przez użytkowników.
Wróćmy teraz do omawiania funkcji detail. Przyjmuje ona następujące parametry: token reference, funkcja zwrotna callback. Wykonuje żądanie ajaxowe do pliku detail.php. Token wykorzystujemy jako parametr przekazany do tego pliku i dostajemy zwrotnie szczegółowe dane na temat określonego miejsca (detail). Następnie wywołujemy funkcję zwrotną callback(detail). Funkcja detail jest wywoływana w momencie kliknięcia na marker co zostało już wcześniej przedstawione, ale przypomnijmy.
detail(refek, function(detale){
	marker.txt = '<div style="color:black;height:100px;"><a style="color:black;font-size:16px;font-weight:bold;" href="'+detale.result.url+'">'+detale.result.name+'</a><br />'+detale.result.formatted_address+'<br /> '+detale.result.formatted_phone_number+'</div>';
	dymek.setContent(marker.txt);
	dymek.open(mapa,marker);
});
Przekazujemy jej wszystkie potrzebne parametry (ostatni funkcja zwrotna to funkcja anonimowa). Określamy tekst jaki ma się pojawić w dymku informacyjnym (na podstawie zwróconych danych). Za pomocą setContent ustawiamy tekst markera, potem umieszczamy go na mapie przy określonym markerze. Do wyjaśnienia pozostała jeszcze funkcja clearOverlays, która czyści mapę z markerów. Wywołujemy ją zawsze po klknięciu na którykolwiek z linków (restauracje, noclegi, itd.) po to aby oczyścić mapę z markerów przed pokazaniem kolejnych.
function clearOverlays() {
	if (markersArray)
		{
		var markersArray_len = markersArray.length, i;
		for (i = 0; i < markersArray_len; i++) {
		  markersArray[i].setMap(null);
		}
	}
}
Przechodzimy po prostu przez wszystkie elementy tablicy markersArray i dla każdego z nich wywołujemy setMap(null). Pełny kod pliku app.js widnieje na poniższym listingu.
(function() {
	window.onload = function() {
		var markersArray = [];
		function clearOverlays() {
		  if (markersArray)
		  {
			var markersArray_len = markersArray.length, i;
			for (i = 0; i < markersArray_len; i++) {
			  markersArray[i].setMap(null);
			}
		  }
		}
		var dymek = new google.maps.InfoWindow();
		var mapDiv = document.getElementById('map');
		var latlng = new google.maps.LatLng(49.299181, 19.9495621);
		var options = {
			center: latlng,
			zoom: 14,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};
		var mapa = new google.maps.Map(mapDiv, options);
		function dodajMarker(lat,lng,id,refek,ikona)
		{
			var rozmiar = new google.maps.Size(33,40);
			var punkt_startowy = new google.maps.Point(0,0);
			var punkt_zaczepienia = new google.maps.Point(16,16);
			var ikona1 = new google.maps.MarkerImage('markers/'+ikona,rozmiar,punkt_startowy,punkt_zaczepienia);
			var opcjeMarkera =
			{
				position: new google.maps.LatLng(lat,lng),
				map: mapa,
				icon: ikona1
			}
			var marker = new google.maps.Marker(opcjeMarkera);
			markersArray.push(marker);
			google.maps.event.addListener(marker,"click",function()
			{
				detail(refek,function(detale){
					marker.txt = '<div style="color:black;height:100px;"><a style="color:black;font-size:16px;font-weight:bold;" href="'+detale.result.url+'">'+detale.result.name+'</a><br />'+detale.result.formatted_address+'<br /> '+detale.result.formatted_phone_number+'</div>';
					dymek.setContent(marker.txt);
					dymek.open(mapa,marker);
				});
			});
		}
		function detail(ref, callback)
		{
			$.ajax('detail.php?ref='+ref).done(function(detail){
				callback(detail);
			});
		}
		function markers_display(file,ikona)
		{
			$.ajax(file).done(function(dane){
				for(i=0;i<dane.results.length;i++)
				{
					var lat = parseFloat(dane.results[i].geometry.location.lat);
					var lng = parseFloat(dane.results[i].geometry.location.lng)
					dodajMarker(lat,lng,dane.results[i].id,dane.results[i].reference,ikona);
				}
			});
		}

		$('#noclegi').live('click', function() {
			clearOverlays();
			$('#buttons a').each(function () {
				$(this).attr('style',$(this).attr('style').replace('2.png','.png'));
				$(this).css({'color': '#175c0b','background-color': 'white'});
			});
			$(this).css({'background': '#175c0b url(markers/lodging_0star2.png) no-repeat left center',
			'color': 'white'});
			markers_display('lodging.php','lodging_0star.png');
			return false;
		});
		$('#restauracje').live('click', function() {
			clearOverlays();
			$('#buttons a').each(function () {
				$(this).attr('style',$(this).attr('style').replace('2.png','.png'));
				$(this).css({'color': '#175c0b','background-color': 'white'});
			});
			$(this).css({'background': '#175c0b url(markers/restaurant2.png) no-repeat left center',
			'color': 'white'});
			markers_display('restaurant.php','restaurant.png');
			return false;
		});
		$('#bankomaty').live('click', function() {
			clearOverlays();
			$('#buttons a').each(function () {
				$(this).attr('style',$(this).attr('style').replace('2.png','.png'));
				$(this).css({'color': '#175c0b','background-color': 'white'});
			});
			$(this).css({'background': '#175c0b url(markers/atm2.png) no-repeat left center',
			'color': 'white'});
			markers_display('atm.php','atm.png');
			return false;
		});
	}

})();