www.gmapsapi.com

Kompleksowy kurs podstaw API, po którym mapowianie nie będzie miało przed Tobą żadnych tajemnic!

Setki przykładów, kursów i poradników z kodem gotowym do skopiowania i korzystania.

Największa strona o Google Maps API w Polsce, największe źródło informacji w języku polskim.

Wyszukiwanie markerów as-you-type

Ten artykuł dotyczy API w wersji 2

« powrót do listy poradników

W poradniku Dodanie funkcjonalnego paska bocznego został opisany sposób na dodanie bocznego baska, w którym zawarta by była lista markerów z mapy. Rozwiązanie to dawało dobre efekty dla małej ilości markerów. Gdy ilość markerów jest zbyt duża, warto zapewnić możliwość wyszukiwania. Dodatkowo, jak pamiętasz z lekcji Debugowanie skryptów dla początkujących należy zoptymalizować aplikację, poprzez korzystanie z menedżera markerów. W tym poradniku zbudujemy od podstaw nową aplikację, która spełniać będzie powyższe założenia (menedżer markerów, wyszukiwarka).

Napisanie prostej wyszukiwarki nie jest trudne, toteż w tej części kursu stworzymy program, wyszukujący w trybie "as-you-type". Wyszukiwanie tego typu oznacza, że proces wyszukiwania odbywa się na bieżąco w trakcie wpisywania kolejnych liter. Przykładowo, jeśli wpiszemy fragment "ma", to pokażą się między innymi takie pozycje jak Mama's Pizza, Mario's Seawall Italian Restaurant itp. Dodanie jednej literki m spowoduje, że na liście pozostanie już tylko Mama's Pizza.

Dane

Dane o markerach przechowywane będą w bazie MySql. Dla mniejszej ilości markerów wyszukiwanie może odbywać się z poziomu JavaScript, jednakże metoda z bazą MySql daje znacznie większe możliwości (filtrowanie, szukanie, sortowanie, wiele warunków itp.). Dla celów tego poradnika skorzystam z bazy, stworzonej w poradniku Wczytanie najbliżej położonych punktów. Kopię pliku CSV znajdziesz pod tym adresem:

http://gmapsapi.com/download/przykladowe_dane_pizzerie.csv

W pliku tym separatorem jest przecinek. W programie phpMyAdmin istnieje opcja importowania danych z pliku CSV, dla stworzonej przed chwilą tabeli należy podać poniższe wartości:

parametry importu pliku CSV

Po zaimportowaniu pozostaje sprawdzenie zawartości tabeli. Wszystko wydaje się być w porządku.

dane w bazie z pliku CSV

Część bazodanowa

Sortowanie i filtrowanie wyników spoczywać będzie na mechanizmach bazy danych. W PHP konieczne jest natomiast zadanie zapytania, odebranie i przetworzenie go, a następnie zwrócenie w oczekiwanej postaci. W celu optymalizacji szybkości, wybrałem do tego format JSON. Szczegóły na ten temat w tym poradniku. Poniżej zamieszczam gotowy kod:

<?
header('Content-Type: text/javascript; charset=utf-8');

$user="użytkownik_bazy";
$pass="hasło_użytkownika";
$baza="nazwa_bazy";

mysql_connect ("localhost", $user, $pass) or 
	die ("Nie nawiazano polaczenie z baza MySQL");
mysql_select_db ($baza) or 
	die ("Nie nawiazano polaczenia z baza serwisu.");
	
mysql_query("SET NAMES 'UTF8';");

if($_GET['fraza'])
	$zapytanie = sprintf('SELECT id,nazwa,adres,lat,lng FROM PoznajGoogleMaps_markery WHERE nazwa LIKE "%%%s%%" ORDER BY nazwa',$_GET['fraza']);
else
	$zapytanie = 'SELECT id,nazwa,adres,lat,lng FROM PoznajGoogleMaps_markery';
$pobierz = mysql_query($zapytanie);

include('jsonencoder.php');
$json = new Services_JSON();

$tablica = array();
$tablica['id_wyszukiwania'] = (int) $_GET['id_wyszukiwania'];
$tablica['markery'] = array();

while($dane = mysql_fetch_array($pobierz))
{
	$marker = array(
	'id' => (int) $dane['id'],
	'nazwa' => $dane['nazwa'],
	'lat' => (float) $dane['lat'],
	'lng' => (float) $dane['lng'],
	);
	array_push($tablica['markery'],$marker);
}

$wynik = $json->encode($tablica);
print($wynik);
?>

W adresie URL należy przekazać frazę do wyszukiwania w parametrze o nazwie fraza. Przykładowo, zobacz co daje wywołanie adresu http://gmapsapi.com/examples/066/markery.php?fraza=pizza.

Do tego skryptu przekazana może zostać jeszcze wartość zmiennej id_wyszukiwania. Jest ona stosowana w celu optymalizacji aplikacji. Póki co, jedyna rzecz, którą po stronie PHP robimy z tą zmienną to dołączenie jej do tablicy danych, by znalazła się ona w pliku JSON. Jej interpretację zostanie omówiona później.

Aplikacja po stronie użytkownika

Wstawienie markerów

Pierwszą czynnością jest zadeklarowania kilku globalnych zmiennych:

var mapa;
var manager;
var markery = [];
var id_wyszukiwania = 0;
var ostatnio_szukany = null;

var ikona 				= new GIcon();
ikona.image 			= '/examples/066/marker.png';
ikona.shadow			= '';
ikona.infoWindowAnchor	= new GPoint(8,8);
ikona.iconAnchor		= new GPoint(8,8);
ikona.iconSize			= new GSize(15,15);
ikona.shadowSize		= new GSize(15,15);

Zmienna manager będzie reprezentować menedżer markerów, tablica markery będzie zawierała odwołania do obiektów GMarker, wstawianych na mapę. id_wyszukiwania oraz ostatnio_szukany to pomocnicze zmienne, służące do optymalizacji działania (czytaj dalej). Oprócz tego, tworzymy jeszcze ikonkę, reprezentującą obiekty na mapie.

Po starcie mapy wywołana zostanie funkcja pobierzMarkery().

function pobierzMarkery()
{
	GDownloadUrl('/examples/066/markery.php',function(dane,kodOdpowiedzi)
	{
		var wyniki = eval('('+dane+')');
		var tablicaMarkerow = [];
		for(var i=0; i<wyniki['markery'].length; i++)
		{
			tablicaMarkerow.push(utworzMarker(wyniki['markery'][i]['id'],wyniki['markery'][i]['lat'], wyniki['markery'][i]['lng'], wyniki['markery'][i]['nazwa']));
		}
		manager.addMarkers(tablicaMarkerow,5,19);	
		manager.refresh();
	});
}

Ponieważ zakładam, że sposób użycia menedżera markerów jest Tobie już znany, to nie wyjaśniam szczegółów działania powyższego kodu. Skupmy się raczej na funkcji, tworzącej marker:

function utworzMarker(id,lat,lng,tekst)
{
	var marker = new GMarker(new GLatLng(lat,lng),{title: tekst, icon: ikona});
	marker.tekst = tekst;
	markery[id] = marker;
	GEvent.addListener(marker,'click',function()
	{
		marker.openInfoWindowHtml(marker.tekst);
	});
	return marker;
}

Bardzo istotną kwestią jest ustawienie elementu tablicy markery o indeksie id na odwołanie do tworzonego markera, jak również zwrócenie odwołania przez funkcję w linii 10. Tablica markery będzie używana w celu otworzenia okna informacyjnego wybranego markera (zostało to omówione w końcowej części tego artykułu).

Możliwość wyszukiwania

Poniższy kod HTML odpowiada za wyświetlenie obszaru mapy i paska bocznego z prawej strony:

<table style="width: 600px; border-collapse: collapse;">
	<tr>
		<td rowspan="2" style="padding-right: 10px;">
			<div id='mapka' style='width: 400px; height: 500px; border: 1px solid black; background: gray;'></div>
		</td>
		<td class="sidebar" valign="top" style="height: 50px;">
			<form action="/examples/066/01.html" method="get" onsubmit="return false;">
				<input onkeyup="szukaj(this.value)" type="text" style="width: 180px;" id="fraza" /><div id="status">wpisz frazę do wyszukania</div>
			</form>
		</td>
	</tr>
	<tr>
		<td class="sidebar" valign="top">
			<ul id="wyszukiwanie">
			</ul>
		</td>
	</tr>
</table>

Twoją uwagę powinien zwrócić fragment onkeyup="szukaj(this.value)". W momencie wciśnięcia klawisza, wywołana zostanie funkcja szukaj() z argumentem, równym zawartości pola tekstowego. Innymi słowy umożliwi to odświeżanie na bieżąco wyników w czasie wpisywania kolejnych liter nazwy. Co robi ta funkcja?:

function szukaj(fraza)
{
	if(ostatnio_szukany !== fraza)
	{
		id_wyszukiwania++;
		GDownloadUrl('/examples/066/markery.php?fraza='+escape(fraza)+'&id_wyszukiwania='+id_wyszukiwania,odswiezSidebar);
		ostatnio_szukany = fraza;
	}
}

Jeśli wyraz, którego szukamy różni się od poprzedniego wyszukiwania, to pobieramy plik http://gmapsapi.com/examples/066/markery.php z odpowiednimi parametrami, a zwrócone wyniki nakazujemy przekazać funkcji odswiezSidebar(). Ostatnim etapem jest ustawienie ostatnio szukanej frazy na obecną.

Parametr id_wyszukiwania to liczba całkowita, zwiększana przy każdym wyszukiwaniu o jeden (zwróć uwagę, że zmienna id_wyszukiwania musi być globalna!). Po co właściwie taka informacja? Ma ona kluczowe znaczenie - jeśli użytkownik wpisze na klawiaturze słowo mama, to przeglądarka kolejno wyśle zapytania do pliku markery.php z parametrami fraza=m, fraza=ma, fraza=mam, fraza=mama. Wysłanie i odebranie pliku jest jednak zazwyczaj oddzielone opóźnieniem, wynikającym z obciążenia serwera, jakości łącza itp. Odbierając wyniki i parsując je musimy być pewni, że analizujemy ostatni z plików, o który pytała przeglądarka (czyli ten z frazą "mama"). To właście w tym celu id_wyszukiwania dołączane jest do pliku JSON - jak za chwilę zobaczysz, funkcja odswiezSidebar() porównuje je z ostatnim id_wyszukiwania, dzięki czemu wie czy plik ma być dalej przetwarzany czy też nie.

function odswiezSidebar(dane,kodOdpowiedzi)
{
	var wyniki = eval('('+dane+')');
	if(wyniki['id_wyszukiwania'] == id_wyszukiwania)
	{
		var html = '';
		for(var i=0; i<wyniki['markery'].length; i++)
		{
			html += '<li><a href="#" onclick="idzDo('+wyniki['markery'][i]['id']+'); return false;">'+wyniki['markery'][i]['nazwa']+'</a></li>';
		}
		document.getElementById('wyszukiwanie').innerHTML = html;
		document.getElementById('status').innerHTML = 'Znaleziono <strong>'+wyniki['markery'].length+'</strong> wyników';
	}
}

W linii 4 następuje sprawdzanie id_wyszukiwania. Jeśli jest ono równe ostatniemu id, to parsowane dane pliku są użyte do konstrukcji sidebara.

Otwieranie okna infoWindow

Otwieranie okna niestety nie może być zrealizowane tak prosto, jak w przykładzie ze zwykłym paskiem bocznym. Działanie menedżera markerów opiera się na usuwaniu i dodawaniu obiektów w strukturze DOM tak, by nie istniała konieczność obliczania i odświeżania pozycji markera, nie będącego w polu widzenia. To zaś powoduje, że wykonanie dowolnej akcji (np. kliknięcia) na markerze, do którego mamy odwołanie, lecz który w danym momencie nie istnieje (bo jest np. poza krawędzią mapy) wywoła błąd javascript. Przy otwieraniu mapy konieczne więc będzie najpierw przesunięcie ekranu w okolice markera, a dopiero gdy marker pojawi się w polu widzenia będzie można otworzyć okno indoWindow. Mamy więc funkcję idzDo(), która jako argument przyjmuje liczbę całkowitą - id markera:

function idzDo(id_markera)
{
	if(markery[id_markera] && markery[id_markera].openInfoWindowHtml)
	{
		mapa.panTo(markery[id_markera].getPoint());
		otworzInfo(id_markera);
	}
}

Zauważ, że w linii 3 sprawdzamy, czy istnieje odpowiedni element tablicy, a także, czy zapisane w nim odwołanie jest odwołaniem do obiektu GMarker (poprzez sprawdzenie istnienia odpowiedniej metody). Jeśli tak jest, to mapa jest przesuwana/centrowana w okolice punktu zaczepienia markera, a następnie wywoływana jest funkcja otworzInfo(), z tym samym argumentem, z którym wywołano funkcję idzDo().

function otworzInfo(id_markera)
{
	if(!markery[id_markera].isHidden())
	{
		GEvent.trigger(markery[id_markera],'click');
	}
	else
	{
		window.setTimeout('otworzInfo('+id_markera+')',100);
	}
}

Działanie funkcji otworzInfo() polega na sprawdzeniu, czy marker jest widoczny. Jeśli jest, to symulowane jest kliknięcie na nim. Jeśli nie jest, to czekamy 100 milisekund, a następnie ponawiamy próbę. Liczba 100 może być dowolna, ważne, by nie była zbyt duża, ani też przesadnie mała. Ten dodatkowy okres oczekiwania pozwala API na dorysowanie markera, dzieki czemu w kolejnej próbie otworzenia okna będzie on już widoczny (a jeśli nadal nie będzie, to API poczeka kolejne 100 milisekund).

Działanie wyszukiwarki możesz sprawdzić w poniższym przykładzie: przykład 1pokaż kod przykładu

Ikonka aktywności

Można również dać znać użytkownikowi o tym, że wyszukiwarka pracuje. Dobrym serwisem do pozyskania animowanych ikonek wczytywania jest strona ajaxload.info. Ja wybrałem z niej taki wskaźnik:

wskaźnik postępu

Proponuję, aby wyświetlać ją bezpośrednio w polu, przeznaczonym na frazę. Za pomocą javascriptu należy zmieniać nazwę klasy tego elementu na start w momencie rozpoczęcia wyszukiwania, oraz na koniec w momencie zakończenia i tworzenia sidebara. Funkcja szukaj() po zmianach będzie wyglądać tak:

function szukaj(fraza)
{
	if(ostatnio_szukany !== fraza)
	{
		id_wyszukiwania++;
		document.getElementById('fraza').className = 'start';
		GDownloadUrl('/examples/066/markery.php?fraza='+escape(fraza)+'&id_wyszukiwania='+id_wyszukiwania,odswiezSidebar);
		ostatnio_szukany = fraza;
	}
}

Z kolei w funkcji odswiezSidebar() trzeba dodać linię:

document.getElementById('fraza').className = 'koniec';

za miejscem odświeżenia paska bocznego.

Reszta należy już do odpowiedniego ustawienia stylu CSS, np.:

input.start
{
	background-image: url(/examples/066/ajax-loader.gif);
	background-repeat: no-repeat;
	background-position: center right;
}

input.koniec
{
	background: white;
}

Sprawdź, jak ikonka jest w stanie informować o aktywności wyszukiwarki w tym przykładzie: przykład 2pokaż kod przykładu

Polecane artykuły

Dodaj stronę do ulubionego serwisu społecznościowego

Oto, co najczęściej czytają internauci, którzy przeczytali ten artykuł:

Dodawanie znaczników na mapę

API v2

Kurs podstaw cz. II: Podstawowe informacje na temat wstawiania markerów (znaczników)


Dodawanie markerów przez użytkownika

API v2

Poradnik pokazuje, w jaki sposób stworzyć formularz, pozwalający na dodawanie markerów


Popularne, darmowe ikony dla markerów

API v2

Kurs podstaw cz. IV: Lista darmowych ikon do wykorzystania w Twojej aplikacji mapowej


Wczytywanie danych z pliku XML

API v2

Kurs podstaw cz. X: Omówienie wczytywania danych z pliku XML za pomocą AJAXa