יצירת מפה עם תוכן שנוצר על ידי משתמשים באמצעות PHP וגיליונות אלקטרוניים של Google

פמלה פוקס, צוות Google Maps API
נובמבר 2007

מטרה

האינטרנט מלא בקהילות שמתמקדות במיקומים גיאוגרפיים ובתחומי עניין: אנשים שאוהבים מוזיאונים, קתדרלות אירופאיות, פארקים ממלכתיים וכו'. לכן, תמיד יש צורך במפתח (כמוך!) שיצור מערכת שבה משתמשים יכולים להוסיף למפה מקומות עם תיוג גיאוגרפי, וזה בדיוק מה שנעשה כאן. בסוף המאמר הזה, תהיה לכם מערכת שבה משתמשים יכולים להירשם, להיכנס ולהוסיף מקומות עם תיוג גיאוגרפי. המערכת תשתמש ב-AJAX לחלק הקדמי, ב-PHP לתסריטים בצד השרת וב-Google Sheets לאחסון. אם אתם רגילים להשתמש במסדי נתונים של MySQL לאחסון, תוכלו לשנות בקלות את הקוד כאן כדי להשתמש במקום זאת ב-backend של מסד נתונים של MySQL.

המאמר הזה מחולק לשלבים הבאים:


הגדרת הגיליון האלקטרוני

נשתמש בגיליונות אלקטרוניים של Google כדי לאחסן את כל הנתונים של המערכת הזו. יש שני סוגי נתונים שאנחנו צריכים לאחסן: פרטי חשבון של משתמשים ומקומות שמשתמשים הוסיפו. לכן, ניצור גיליון עבודה אחד לכל סוג נתונים. אנחנו נבצע אינטראקציה עם גליונות העבודה באמצעות פיד הרשימה שלהם, שמסתמך על השורה הראשונה בגליון עבודה שמכילה תוויות של עמודות, וכל שורה עוקבת שמכילה נתונים.

נכנסים לכתובת docs.google.com ויוצרים גיליון אלקטרוני חדש. משנים את השם של גליון העבודה שמוגדר כברירת מחדל ל-Users ויוצרים עמודות בשמות user,‏ password ו-session. לאחר מכן מוסיפים עוד גיליון, משנים את השם שלו ל-'Locations' ויוצרים עמודות בשמות 'user',‏ 'status',‏ 'lat',‏ 'lng' ו-'date'. לחלופין, אם אתם לא רוצים לבצע את כל העבודה הזו באופן ידני, אתם יכולים להוריד את התבנית הזו ולייבא אותה ל-Google Sheets באמצעות הפקודה 'קובץ' -> 'ייבוא'.

פרטי חשבון המשתמש צריכים להישאר פרטיים (גלויים רק לבעלים של הגיליון האלקטרוני – לכם), אבל המקומות שהמשתמשים הוסיפו יוצגו במפה שגלוי לכולם. למזלכם, Google Sheets מאפשר לכם להחליט אילו גליונות עבודה בגיליון אלקטרוני יהיו ציבוריים ואילו יישארו פרטיים (ברירת מחדל). כדי לפרסם את גליון העבודה 'מיקומים', לוחצים על הכרטיסייה 'פרסום', לוחצים על 'פרסום עכשיו', מסמנים את תיבת הסימון 'פרסום מחדש אוטומטי', ואז בתפריט הנפתח 'אילו חלקים?' בוחרים באפשרות 'גליון 'מיקומים' בלבד'. האפשרויות הנכונות מוצגות בצילום המסך שלמטה:

עבודה עם Zend GData Framework

‫Google Spreadsheets API מספק ממשק HTTP לפעולות CRUD כמו אחזור שורות, הוספת שורות, עדכון שורות ומחיקת שורות. ‫Zend Framework מספקת עטיפת PHP מעל ה-API (וממשקי GData API אחרים), כך שלא צריך לדאוג לגבי יישום של פעולות HTTP גולמיות. ‫Zend Framework דורש PHP 5.

אם עדיין לא עשיתם זאת, מורידים את Zend Framework ומעלים אותו לשרת. ה-framework זמין כאן: 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 backend כדי להוסיף את המשתמש לגיליון האלקטרוני.

בסקריפט ה-PHP, אנחנו קודם כוללים את הסקריפט הגלובלי ואז מקבלים את הערכים של שם המשתמש והסיסמה מהמשתנה GET. לאחר מכן הגדרנו לקוח של Spreadsheets וביקשנו את פיד הרשימה של גליון העבודה של המשתמשים עם מחרוזת שאילתה כדי להגביל את התוצאות רק לשורות שבהן העמודה של שם המשתמש שווה לשם המשתמש שהועבר לסקריפט. אם לא נקבל שורות בתוצאה של פיד הרשימה, נוכל להמשיך בבטחה בידיעה ששם המשתמש שהועבר הוא ייחודי. לפני שמוסיפים שורה לפיד הרשימה, יוצרים מערך משויך של ערכי העמודות: שם המשתמש, הצפנה של הסיסמה באמצעות הפונקציה sha1 של PHP ותו מילוי לסשן. לאחר מכן קוראים ל-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. לאחר מכן הגדרנו לקוח של Spreadsheets וביקשנו את פיד הרשימה של גליון העבודה של המשתמשים עם מחרוזת שאילתה כדי להגביל את התוצאות רק לשורות שבהן העמודה של שם המשתמש שווה לשם המשתמש שהועבר לסקריפט.

בשורה שמוחזרת, נבדוק שהגיבוב של הסיסמה שהועברה תואם לגיבוב שמאוחסן בגיליון האלקטרוני. אם כן, ניצור מזהה סשן באמצעות הפונקציות 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 מגיעה מקובץ cookies.js שמבוסס על מדריך JavaScript של w3c: ‏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. לאחר מכן, אנחנו מגדירים לקוח של Spreadsheets ומבקשים את פיד הרשימה של גליון העבודה של המשתמשים עם מחרוזת שאילתה כדי להגביל את התוצאות רק לשורות שבהן העמודה של הסשן שווה לערך הסשן שמועבר לסקריפט. לאחר מכן, אנחנו עוברים על הרשומות המותאמות אישית בפיד (אלה שתואמות לכותרות העמודות שלנו), ומדפיסים את שם המשתמש המתאים לאותה סשן, אם קיים כזה.

קוד ה-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. אנחנו מכניסים את כל הערכים האלה למערך משויך, ומוסיפים גם ערך של 'תאריך' באמצעות הפונקציה date()‎ של PHP, כדי שנדע מתי המשתמש הוסיף את המקום. אנחנו מעבירים את המערך האסוציאטיבי, את הקבוע של מפתח הגיליון האלקטרוני ואת הקבוע של מזהה גיליון העבודה של המיקומים לפונקציה insertRow. לאחר מכן, אם נוספה שורה למיקום החדש בגיליון האלקטרוני, המערכת מחזירה את הערך 'Success'. אם מופיעה שגיאה בשלב הזה, סביר להניח שהסיבה לכך היא חוסר התאמה בשמות של כותרות העמודות. המפתחות במערך המשויך צריכים להיות זהים לכותרות העמודות בגליון העבודה שצוין על ידי מפתח הגיליון האלקטרוני ומזהה גליון העבודה.

קוד ה-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 כדי לאחזר את ערך הסשן. אם מחרוזת הסשן היא null או ריקה, אנחנו מוציאים הודעת שגיאה. אם לא, אנחנו קוראים ל-GDownloadUrl ב-map.checksession.php ושולחים את הסשן. אם שם המשתמש מוחזר בהצלחה, אנחנו מציגים למשתמש הודעת ברוכים הבאים, חושפים את הטופס להוספת מיקום וטוענים את המפה. הטופס כולל שדה טקסט לכתובת, מפה ושדות טקסט לשם המקום, לקו הרוחב ולקו האורך. אם המשתמש לא יודע את קווי האורך והרוחב של המיקום, הוא יכול לבצע גיאו-קידוד של המיקום על ידי הזנת הכתובת שלו בטופס ולחיצה על 'שליחה'. הפעולה הזו תשלח קריאה ל-GClientGeocoder של Map API, שיציב סמן במפה אם הכתובת תימצא, ויאכלס אוטומטית את שדות הטקסט של קווי הרוחב והאורך.

כשהמשתמש מרוצה, הוא יכול ללחוץ על הלחצן 'הוספת מיקום'. לאחר מכן, בסקריפט JavaScript, נקבל את הערכים של user,‏ place,‏ lat ו-lng, ונשלח אותם לסקריפט 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 כדי לקבל עזרה נוספת.