ноябрь 2007 г.
Цель
В Интернете полно сообществ, объединенных географическим положением и интересами: люди, которые любят музеи, европейские соборы, государственные парки и т. д. Таким образом, разработчику (например, вам!) всегда необходимо создать систему, в которой пользователи могут вносить геотегированные места в карту, и это именно то, что мы будем делать здесь. В конце этой статьи у вас будет система, в которой пользователи могут регистрироваться, входить в систему и добавлять места с геотегами. Система будет использовать AJAX для внешнего интерфейса, PHP для сценариев на стороне сервера и Google Spreadsheets для хранения. Если вы привыкли использовать базы данных MySQL для хранения, вы можете легко изменить приведенный здесь код, чтобы вместо этого использовать серверную часть базы данных MySQL.
Эта статья разбита на следующие этапы:
- Настройка электронной таблицы
- Работа с Zend Gdata Framework
- Создание глобальных функций
- Регистрация нового пользователя
- Вход пользователя
- Разрешение пользователям добавлять места на карту
- Создание карты
- Заключение
Настройка электронной таблицы
Мы будем использовать таблицы Google для хранения всех данных для этой системы. Нам нужно хранить два типа данных: информацию об учетной записи пользователя и места, добавленные пользователем, поэтому мы создадим по одному рабочему листу для каждого типа данных. Мы будем взаимодействовать с рабочими листами, используя их списки, которые основаны на первой строке рабочего листа, содержащей метки столбцов, и каждой последующей строке, содержащей данные.
Посетите docs.google.com и создайте новую таблицу. Переименуйте рабочий лист по умолчанию в «Пользователи» и создайте столбцы с именами «пользователь», «пароль» и «сеанс». Затем добавьте еще один лист, переименуйте его в «Местоположения» и создайте столбцы с именами «пользователь», «статус», «широта», «долгота» и «дата». Или, если вам не нравится весь этот ручной труд, загрузите этот шаблон и импортируйте его в Google Spreadsheets с помощью команды File->Import.
Информация об учетной записи пользователя должна быть конфиденциальной (видимой только для владельца электронной таблицы — вас), в то время как добавленные пользователем места будут отображаться на общедоступной карте. К счастью, Google Spreadsheets позволяет вам выборочно решать, какие рабочие листы в электронной таблице могут быть общедоступными, а какие должны оставаться закрытыми (по умолчанию). Чтобы опубликовать рабочий лист «Местоположения», щелкните вкладку «Опубликовать», нажмите «Опубликовать сейчас», установите флажок «Автоматически повторно публиковать», а затем в разделе «Какие части?» в раскрывающемся списке выберите «Только лист «Местоположения». Правильные варианты показаны на скриншоте ниже:

Работа с Zend GData Framework
API таблиц Google предоставляет HTTP-интерфейс для операций CRUD, таких как извлечение строк, вставка строк, обновление строк и удаление строк. Zend Framework предоставляет PHP-оболочку поверх API (и других API-интерфейсов GData), так что вам не нужно беспокоиться о реализации необработанных HTTP-операций. Zend Framework требует PHP 5.
Если у вас его еще нет, скачайте фреймворк Zend и загрузите его на свой сервер. Фреймворк доступен здесь: http://framework.zend.com/download/gdata .
Вы должны изменить свой PHP include_path, чтобы включить библиотеку Zend. Есть несколько способов сделать это, в зависимости от уровня прав администратора, которые у вас есть на вашем сервере. Один из способов — добавить эту строку над операторами require в любых файлах PHP, использующих библиотеку:
ini_set("include_path", ".:/usr/lib/php:/usr/local/lib/php:../../../library/");
Чтобы проверить это, запустите демонстрацию Spreadsheets, введя это в командной строке в папке demos/Zend/Gdata:
php Spreadsheet-ClientLogin.php --user=YourGMailUsername --pass=YourPassword
Если это работает, вы должны увидеть список ваших электронных таблиц. Если вы получили сообщение об ошибке, убедитесь, что ваш путь включения установлен правильно и что у вас установлен PHP 5.
Создание глобальных функций
Все PHP-скрипты, которые мы будем писать для карты сообщества, будут использовать общие включения, переменные и функции, которые мы поместим в один файл.
В начале файла у нас будут необходимые операторы для включения и загрузки библиотеки Zend, взятые из примера Spreadsheets-ClientLogin.php.
Затем мы определим константы, которые будут использоваться во всех файлах: ключ электронной таблицы и два идентификатора рабочего листа. Чтобы найти информацию для электронной таблицы, откройте ее, щелкните вкладку «Опубликовать» и нажмите «Дополнительные параметры публикации». Выберите «ATOM» в раскрывающемся списке «Формат файла» и нажмите «Создать URL». Вы увидите что-то вроде:
http://spreadsheets.google.com/feeds/list/o16162288751915453340.4016005092390554215/od6/public/basic
Ключ электронной таблицы — это длинная буквенно-цифровая строка после «/list/», а идентификатор рабочего листа — это 3-символьная длинная строка после нее. Чтобы найти другой идентификатор рабочего листа, выберите другой лист в списке «Какие листы?» падать.
Затем мы создадим 3 функции: setupClient, getWkshtListFeed и printFeed. В setupClient мы установим имя пользователя и пароль GMail, выполним аутентификацию с помощью ClientLogin и вернем объект Zend_Gdata_Spreadsheets. В getWkshtListFeed мы вернем ленту списка электронных таблиц для данного ключа электронной таблицы и идентификатора таблицы с необязательным запросом электронных таблиц (ссылка). Функция printFeed взята из примера Spreadsheets-ClientLogin.php и может быть полезна для отладки. Он примет объект фида и выведет его на экран.
PHP, который это делает, показан ниже ( communitymap_globals.php ):
<?php ini_set("include_path", ".:/usr/lib/php:/usr/local/lib/php:../../../library/"); require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); Zend_Loader::loadClass('Zend_Gdata_Spreadsheets'); Zend_Loader::loadClass('Zend_Http_Client'); define("SPREADSHEET_KEY", "o16162288751915453340.4016005092390554215"); define("USER_WORKSHEET_ID", "od6"); define("LOC_WORKSHEET_ID", "od7"); function setupClient() { $email = "your.name@gmail.com"; $password = "yourPassword"; $client = Zend_Gdata_ClientLogin::getHttpClient($email, $password, Zend_Gdata_Spreadsheets::AUTH_SERVICE_NAME); $gdClient = new Zend_Gdata_Spreadsheets($client); return $gdClient; } function getWkshtListFeed($gdClient, $ssKey, $wkshtId, $queryString=null) { $query = new Zend_Gdata_Spreadsheets_ListQuery(); $query->setSpreadsheetKey($ssKey); $query->setWorksheetId($wkshtId); if ($queryString !== null) { $query->setSpreadsheetQuery($queryString); } $listFeed = $gdClient->getListFeed($query); return $listFeed; } function printFeed($feed) { print "printing feed"; $i = 0; foreach($feed->entries as $entry) { if ($entry instanceof Zend_Gdata_Spreadsheets_CellEntry) { print $entry->title->text .' '. $entry->content->text . "\n"; } else if ($entry instanceof Zend_Gdata_Spreadsheets_ListEntry) { print $i .' '. $entry->title->text .' '. $entry->content->text . "\n"; } else { print $i .' '. $entry->title->text . "\n"; } $i++; } } ?>
Регистрация нового пользователя
Чтобы зарегистрировать нового пользователя, нам понадобится HTML-страница для пользователя с текстовыми полями и кнопкой отправки, а также внутренний PHP-скрипт для добавления пользователя в электронную таблицу.
В сценарии PHP мы сначала включаем глобальный сценарий, а затем получаем значения имени пользователя и пароля из переменной GET. Затем мы настраиваем клиент электронных таблиц и запрашиваем ленту списка для рабочего листа пользователей со строкой запроса, чтобы ограничить результаты только строками, в которых столбец имени пользователя равен имени пользователя, переданному в сценарий. Если мы не получим ни одной строки в результате ленты списка, то мы можем безопасно продолжить, зная, что переданное имя пользователя уникально. Прежде чем вставить строку в ленту списка, мы создаем ассоциативный массив значений столбца: имя пользователя, шифрование пароля с помощью функции PHP sha1 и символ-заполнитель для сеанса. Затем мы вызываем метод insertRow в клиенте электронных таблиц, передавая ассоциативный массив, ключ электронной таблицы и идентификатор рабочего листа. Если возвращенный объект является ListFeedEntry, мы выводим Success! сообщение.
PHP, который делает это, показан ниже ( communitymap_newuser.php ):
<?php require_once 'communitymap_globals.php'; $username = $_GET['username']; $password = $_GET['password']; $gdClient = setupClient(); $listFeed = getWkshtListFeed($gdClient, SPREADSHEET_KEY, USER_WORKSHEET_ID, ('user='.$username)); $totalResults = $listFeed->totalResults; if ( $totalResults != "0") { // Username already exists exit; } $rowArray["user"] = $username; $rowArray["password"] = sha1($password); $rowArray["session"] = "a"; $entry = $gdClient->insertRow($rowArray, SPREADSHEET_KEY, USER_WORKSHEET_ID); if ($entry instanceof Zend_Gdata_Spreadsheets_ListEntry) { echo "Success!"; } ?>
На странице регистрации мы можем включить Maps API, чтобы мы могли использовать его функцию-оболочку XMLHttpRequest, называемую GDownloadUrl. Когда пользователь нажимает кнопку отправки, мы получаем имя пользователя и пароль из текстовых полей, создаем строку параметров из их значений и вызываем GDownloadUrl для URL-адреса и параметров скрипта. Поскольку мы отправляем конфиденциальную информацию, мы используем HTTP-версию POST GDownloadUrl (путем отправки параметров в качестве третьего аргумента вместо добавления их к URL-адресу). В функции обратного вызова мы проверим успешный ответ и выведем пользователю соответствующее сообщение.
Скриншот и код приведены ниже для примера страницы регистрации ( communitymap_register.htm ):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> Community Map - Register/Login </title> <script src="http://maps.google.com/maps?file=api&v=2&key=abcdef" type="text/javascript"></script> <script type="text/javascript"> function register() { var username = document.getElementById("username").value; var password = document.getElementById("password").value; var url = "communitymap_newuser.php?"; var params = "username=" + username + "&password=" + password; GDownloadUrl(url, function(data, responseCode) { if (data.length > 1) { document.getElementById("message").innerHTML = "Successfully registered." + "<a href='communitymap_login.htm'>Proceed to Login</a>."; } else { document.getElementById("message").innerHTML = "Username already exists. Try again."; } }, params); } </script> </head> <body> <h1>Register for Community Map</h1> <input type="text" id="username"> <input type="password" id="password"> <input type="button" onclick="register()" value="Register"> <div id="message"></div> </body> </html>
Вход пользователя
Чтобы пользователи могли входить в нашу систему, нам понадобится HTML-страница, обращенная к пользователю, которая будет запрашивать у них имя пользователя и пароль, а также PHP-скрипт для проверки информации для входа, создания идентификатора сеанса и передачи его обратно в логин. страницу для установки файла cookie. Пользователь останется авторизованным через файл cookie сеанса на последующих страницах.
В сценарии PHP мы сначала включаем глобальный сценарий, а затем получаем значения имени пользователя и пароля из переменной GET. Затем мы настраиваем клиент электронных таблиц и запрашиваем ленту списка для рабочего листа пользователей со строкой запроса, чтобы ограничить результаты только строками, в которых столбец имени пользователя равен имени пользователя, переданному в сценарий.
В возвращаемой строке мы проверим, соответствует ли хэш переданного пароля хешу, хранящемуся в электронной таблице. Если это так, мы создадим идентификатор сеанса, используя функции md5, uniqid и rand. Затем мы обновим строку в электронной таблице с помощью сеанса и выведем ее на экран, если обновление строки прошло успешно.
PHP, который это делает, показан ниже ( communitymap_loginuser.php ):
<?php require_once 'communitymap_globals.php'; $username = $_POST['username']; $password = $_POST['password']; $gdClient = setupClient(); $listFeed = getWkshtListFeed($gdClient, SPREADSHEET_KEY, USER_WORKSHEET_ID, ('user='.$username)); $password_hash = sha1($password); $row = $listFeed->entries[0]; $rowData = $row->getCustom(); foreach($rowData as $customEntry) { if ($customEntry->getColumnName()=="password" && $customEntry->getText()==$password_hash) { $updatedRowArray["user"] = $username; $updatedRowArray["password"] = sha1($password); $updatedRowArray["session"] = md5(uniqid(rand(), true)); $updatedRow = $gdClient->updateRow($row, $updatedRowArray); if ($updatedRow instanceof Zend_Gdata_Spreadsheets_ListEntry) { echo $updatedRowArray["session"]; } } } ?>
На странице входа мы можем снова включить Maps API, чтобы мы могли использовать его функцию-оболочку XMLHttpRequest, называемую GDownloadUrl. Когда пользователь нажимает кнопку отправки, мы получаем имя пользователя и пароль из текстовых полей, создаем URL-адрес скрипта с параметрами запроса и вызываем GDownloadUrl для URL-адреса скрипта. В функции обратного вызова мы устанавливаем файл cookie с идентификатором сеанса, который возвращается сценарием, или выводим сообщение об ошибке, если ничего не возвращается. Функция setCookie взята из файла cookie.js, основанного на руководстве w3c по JavaScript: http://www.w3schools.com/js/js_cookies.asp.
Скриншот и код приведены ниже для примера страницы входа ( communitymap_login.htm ):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Community Map - Login</title> <script src="http://maps.google.com/maps?file=api&v=2&key=abcdef" type="text/javascript"></script> <script src="cookies.js" type="text/javascript"></script> <script type="text/javascript"> function login() { var username = document.getElementById("username").value; var password = document.getElementById("password").value; var url = "communitymap_loginuser.php?username=" + username + "&password=" + password; GDownloadUrl(url, function(data, responseCode) { if (data.length > 1) { setCookie("session", data, 5); } else { document.getElementById("nessage").innerHTML = "Error. Try again."; } }); } </script> </head> <body> <h1>Login for Community Map</h1> <input type="text" id="username"> <input type="password" id="password"> <input type="button" onclick="login()" value="Login"> <div id="message"></div> </body> </html>
Разрешение пользователям добавлять места на карту
Чтобы позволить пользователю добавлять места на нашу карту, нам понадобится HTML-страница, обращенная к пользователю, которая позволит ему предоставлять информацию о местоположении, и два PHP-скрипта: один для проверки того, что они вошли в систему с помощью установленного нами файла cookie, и другой, чтобы добавить местоположение на лист местоположений.
В первом скрипте PHP, который проверяет, вошел ли пользователь в систему, мы сначала включаем глобальный скрипт, а затем получаем значение сеанса из переменной GET. Затем мы настраиваем клиент электронных таблиц и запрашиваем ленту списка для рабочего листа пользователей со строкой запроса, чтобы ограничить результаты только теми строками, где столбец сеанса равен значению сеанса, переданному в сценарий. Затем мы перебираем пользовательские записи этого канала (те, которые соответствуют заголовкам наших столбцов) и распечатываем соответствующее имя пользователя для этого сеанса, если оно существует.
PHP, который это делает, показан ниже ( communitymap_checksession.php ):
<?php require_once 'communitymap_globals.php'; $session = $_GET['session']; $gdClient = setupClient(); $listFeed = getWkshtListFeed($gdClient, SPREADSHEET_KEY, USER_WORKSHEET_ID, ('session='.$session)); if ( count($listFeed->entries) > 0) { $row = $listFeed->entries[0]; $rowData = $row->getCustom(); foreach($rowData as $customEntry) { if ($customEntry->getColumnName()=="user") { echo $customEntry->getText(); } } } ?>
Во втором PHP-скрипте, который позволяет пользователю добавить местоположение, мы сначала реплицируем код из communitymap_checksession.php, чтобы убедиться, что пользователь все еще вошел в систему и действителен. Затем, как только мы получим действительное имя пользователя из таблицы пользователей, мы получим значения места, широты и долготы из переменной GET. Мы помещаем все эти значения в ассоциативный массив, а также добавляем значение «дата», используя функцию PHP date(), чтобы мы знали, когда пользователь добавил место. Мы передаем этот ассоциативный массив, константу ключа электронной таблицы и константу идентификатора рабочего листа местоположения в функцию insertRow. Затем мы выводим «Успех», если в электронную таблицу была добавлена строка для нового местоположения. Если на этом этапе вы получаете сообщение об ошибке, скорее всего, это связано с несоответствием имен заголовков столбцов. Ключи в ассоциативном массиве должны совпадать с заголовками столбцов на рабочем листе, заданном ключом электронной таблицы и идентификатором рабочего листа.
PHP, который это делает, показан ниже ( communitymap_addlocation.php ):
<?php require_once 'communitymap_globals.php'; $session = $_GET['session']; $gdClient = setupClient(); $listFeed = getWkshtListFeed($gdClient, SPREADSHEET_KEY, USER_WORKSHEET_ID, ('session='.$session)); if ( count($listFeed->entries) > 0) { $row = $listFeed->entries[0]; $rowData = $row->getCustom(); foreach($rowData as $customEntry) { if ($customEntry->getColumnName()=="user") { $user = $customEntry->getText(); } } $place = $_GET['place']; $lat = $_GET['lat']; $lng = $_GET['lng']; $rowArray["user"] = $user; $rowArray["place"] = $place; $rowArray["lat"] = $lat; $rowArray["lng"] = $lng; $rowArray["date"] = date("F j, Y, g:i a"); $entry = $gdClient->insertRow($rowArray, SPREADSHEET_KEY, LOC_WORKSHEET_ID); if ($entry instanceof Zend_Gdata_Spreadsheets_ListEntry) { echo "Success!\n"; } } ?>
На странице добавления местоположения мы снова можем включить Maps API, чтобы использовать GDownloadUrl и создать карту. После загрузки страницы мы используем функцию getCookie из cookies.js для получения значения сеанса. Если строка сеанса нулевая или пустая, мы выводим сообщение об ошибке. Если это не так, то мы вызываем GDownloadUrl на map.checksession.php, отправляя сессию. Если это успешно возвращает имя пользователя, мы показываем пользователю приветственное сообщение, открываем форму добавления местоположения и загружаем карту. Форма состоит из текстового поля адреса, карты и текстовых полей для названия места, широты. , и долгота. Если пользователь еще не знает широту/долготу местоположения, он может геокодировать его, введя свой адрес в форму и нажав «Отправить». Это отправит вызов GClientGeocoder Map API, который разместит маркер на карте, если найдет адрес, и автоматически заполнит текстовые поля lat/lng.
Когда пользователь удовлетворен, он может нажать кнопку «Добавить местоположение». Затем в JavaScript мы получим значения для пользователя, места, широты и долготы и отправим их в сценарий communitymap_addlocation.php с помощью GDownloadUrl
.
Если этот скрипт вернет успех, мы выведем сообщение об успехе на экран.
Скриншот и код приведены ниже для примера страницы добавления местоположения ( communitymap_addlocation.htm ):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"/> <title>Community Map - Add a Place!</title> <script src="http://maps.google.com/maps?file=api&v=2.x&key=abcdef" type="text/javascript"></script> <script src="cookies.js" type="text/javascript"></script> <script type="text/javascript"> //<![CDATA[ var map = null; var geocoder = null; var session = null; function load() { session = getCookie('session'); if (session != null && session != "") { url = "communitymap_checksession.php?session=" + session; GDownloadUrl(url, function(data, responseCode) { if (data.length > 0) { document.getElementById("message").innerHTML = "Welcome " + data; document.getElementById("content").style.display = "block"; map = new GMap2(document.getElementById("map")); map.setCenter(new GLatLng(37.4419, -122.1419), 13); geocoder = new GClientGeocoder(); } }); } else { document.getElementById("message").innerHTML = "Error: Not logged in."; } } function addLocation() { var place = document.getElementById("place").value; var lat = document.getElementById("lat").value; var lng = document.getElementById("lng").value; var url = "communitymap_addlocation.php?session=" + session + "&place=" + place + "&lat=" + lat + "&lng=" + lng; GDownloadUrl(url, function(data, responseCode) { GLog.write(data); if (data.length > 0) { document.getElementById("message").innerHTML = "Location added."; } }); } function showAddress(address) { if (geocoder) { geocoder.getLatLng( address, function(point) { if (!point) { alert(address + " not found"); } else { map.setCenter(point, 13); var marker = new GMarker(point, {draggable:true}); document.getElementById("lat").value = marker.getPoint().lat().toFixed(6); document.getElementById("lng").value = marker.getPoint().lng().toFixed(6); map.addOverlay(marker); GEvent.addListener(marker, "dragend", function() { document.getElementById("lat").value = marker.getPoint().lat().toFixed(6); document.getElementById("lng").value = marker.getPoint().lng().toFixed(6); }); } } ); } } //]]> </script> </head> <body onload="load()" onunload="GUnload()"> <div id="message"></div> <div id="content" style="display:none"> <form action="#" onsubmit="showAddress(this.address.value); return false"> <p> <input type="text" size="60" name="address" value="1600 Amphitheatre Pky, Mountain View, CA" /> <input type="submit" value="Geocode!" /> </form> </p> <div id="map" style="width: 500px; height: 300px"></div> Place name: <input type="text" size="20" id="place" value="" /> <br/> Lat: <input type="text" size="20" id="lat" value="" /> <br/> Lng: <input type="text" size="20" id="lng" value="" /> <br/> <input type="button" onclick="addLocation()" value="Add a location" /> </form> </div> </body> </html>
Создание карты
Поскольку на первом шаге вы сделали таблицу местоположений общедоступной, для создания их карты не требуется программирование на стороне сервера. На самом деле никакого программирования не требуется. Вы можете использовать этот Мастер электронных таблиц -> Карта , и он сгенерирует весь код, необходимый для карты. Мастер загружает записи рабочего листа на страницу путем добавления тега скрипта, указывающего на вывод JSON для веб-канала, и указывает функцию обратного вызова, которая вызывается после загрузки JSON. Более подробная информация доступна здесь .
Пример HTML-кода для этого доступен здесь: mainmap.htm . Скриншот показан ниже:

Заключение
Надеюсь, теперь у вас есть собственная система карт, созданная пользователями и работающая на вашем сервере. В этой статье представлен очень простой код, необходимый для основных аспектов этой системы, но теперь, когда вы знакомы с библиотекой Zend Spreadsheets, вы сможете расширить систему в соответствии со своими конкретными потребностями. Если вы столкнулись с ошибками по пути, помните, что вы можете использовать команду echo
в PHP или GLog.write()
Map API в JavaScript для отладки, и вы всегда можете публиковать сообщения на форумах разработчиков Maps API или Spreadsheets API для дополнительная помощь.