Noviembre de 2007
Objetivo
La Web está llena de comunidades centradas en ubicaciones geográficas y en intereses: personas que aman los museos, las catedrales europeas, los parques estatales, etcétera. Por lo tanto, siempre se necesita un desarrollador (como tú) para crear un sistema en el que los usuarios puedan agregar lugares con etiquetas geográficas a un mapa, y eso es exactamente lo que haremos aquí. Al final de este artículo, tendrás un sistema en el que los usuarios podrán registrarse, acceder y agregar lugares con etiquetas geográficas. El sistema usará AJAX para el frontend, PHP para las secuencias de comandos del servidor y Hojas de cálculo de Google para el almacenamiento. Si estás acostumbrado a usar bases de datos MySQL para el almacenamiento, puedes modificar fácilmente el código aquí para usar un backend de base de datos MySQL.
Este artículo se divide en los siguientes pasos:
- Cómo configurar la hoja de cálculo
- Trabajar con el framework de Zend Gdata
- Cómo crear funciones globales
- Cómo registrar un usuario nuevo
- Cómo acceder como usuario
- Cómo permitir que los usuarios agreguen lugares al mapa
- Cómo crear el mapa
- Conclusión
Cómo configurar la hoja de cálculo
Usaremos Hojas de cálculo de Google para almacenar todos los datos de este sistema. Hay dos tipos de datos que necesitamos almacenar: la información de la cuenta del usuario y los lugares agregados por el usuario, por lo que crearemos una hoja de cálculo para cada tipo de datos. Interactuaremos con las hojas de cálculo a través de su feed de lista, que se basa en la primera fila de una hoja de cálculo que contiene etiquetas de columna y cada fila posterior que contiene datos.
Visita docs.google.com y crea una nueva hoja de cálculo. Cambia el nombre de la hoja de cálculo predeterminada a "Usuarios" y crea columnas llamadas "usuario", "contraseña" y "sesión". Luego, agrega otra hoja, cámbiale el nombre a "Ubicaciones" y crea columnas llamadas "usuario", "estado", "latitud", "longitud" y "fecha". O, si no quieres hacer todo ese trabajo manual, descarga esta plantilla y, luego, impórtala a Hojas de cálculo de Google con el comando Archivo->Importar.
La información de la cuenta de usuario debe mantenerse privada (solo la puede ver el propietario de la hoja de cálculo, es decir, tú), mientras que los lugares agregados por el usuario se mostrarán en un mapa visible públicamente. Afortunadamente, Hojas de cálculo de Google te permite decidir de forma selectiva qué hojas de cálculo pueden ser públicas y cuáles deben seguir siendo privadas (opción predeterminada). Para publicar la hoja de cálculo "Ubicaciones", haz clic en la pestaña "Publicar", luego en "Publicar ahora", marca la casilla de verificación "Volver a publicar automáticamente" y, luego, en el menú desplegable "¿Qué partes?", selecciona "Solo la hoja 'Ubicaciones'". Las opciones correctas se muestran en la siguiente captura de pantalla:
Trabajar con el framework de Zend GData
La API de Hojas de cálculo de Google proporciona una interfaz HTTP para operaciones de CRUD, como recuperar, insertar, actualizar y borrar filas. Zend Framework proporciona un wrapper de PHP sobre la API (y las otras APIs de GData) para que no tengas que preocuparte por implementar las operaciones HTTP sin procesar. Zend Framework requiere PHP 5.
Si aún no lo tienes, descarga el framework de Zend y súbelo a tu servidor. El framework está disponible aquí: http://framework.zend.com/download/gdata.
Debes modificar tu include_path de PHP para incluir la biblioteca de Zend. Existen varias formas de hacerlo, según el nivel de derechos de administrador que tengas en tu servidor. Una forma es agregar esta línea sobre las instrucciones require en cualquier archivo PHP que use la biblioteca:
ini_set("include_path", ".:/usr/lib/php:/usr/local/lib/php:../../../library/");
Para probarlo, ejecuta la demostración de Hojas de cálculo ingresando lo siguiente en la línea de comandos de la carpeta demos/Zend/Gdata:
php Spreadsheet-ClientLogin.php --user=YourGMailUsername --pass=YourPassword
Si funciona, deberías ver una lista de tus hojas de cálculo. Si recibes un error, verifica que la ruta de inclusión esté configurada correctamente y que tengas instalado PHP 5.
Crea funciones globales
Todas las secuencias de comandos de PHP que escribiremos para el mapa de la comunidad usarán inclusiones, variables y funciones comunes, que colocaremos en un solo archivo.
Al comienzo del archivo, tendremos las instrucciones necesarias para incluir y cargar la biblioteca de Zend, que se tomaron del ejemplo de Spreadsheets-ClientLogin.php.
Luego, definiremos las constantes que se usarán en todos los archivos: la clave de la hoja de cálculo y los dos IDs de la hoja de trabajo. Para encontrar la información de tu hoja de cálculo, ábrela, haz clic en la pestaña "Publicar" y, luego, en "Más opciones de publicación". Selecciona "ATOM" en la lista desplegable Formato de archivo y haz clic en "Generar URL". Verás algo como lo siguiente:
http://spreadsheets.google.com/feeds/list/o16162288751915453340.4016005092390554215/od6/public/basic
La clave de la hoja de cálculo es la cadena alfanumérica larga que aparece después de "/list/", y el ID de la hoja de cálculo es la cadena de 3 caracteres que aparece después. Para encontrar el ID de la otra hoja de cálculo, selecciona la otra hoja en el menú desplegable "¿Qué hojas?".
Luego, crearemos 3 funciones: setupClient, getWkshtListFeed y printFeed. En setupClient, estableceremos nuestro nombre de usuario y contraseña de Gmail, nos autenticaremos con ClientLogin y devolveremos un objeto Zend_Gdata_Spreadsheets. En getWkshtListFeed, devolveremos un feed de lista de hojas de cálculo para una clave de hoja de cálculo y un ID de hoja de trabajo determinados, con una consulta de hojas de cálculo opcional (vínculo). La función printFeed se toma del ejemplo Spreadsheets-ClientLogin.php y puede ser útil para la depuración. Tomará un objeto de feed y lo imprimirá en la pantalla.
A continuación, se muestra el código PHP que realiza esta acción (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++;
}
}
?>
Cómo registrar un usuario nuevo
Para registrar un usuario nuevo, necesitaremos una página HTML visible para el usuario con campos de texto y un botón de envío, y una secuencia de comandos de backend en PHP para agregar el usuario a la hoja de cálculo.
En la secuencia de comandos de PHP, primero incluimos la secuencia de comandos global y, luego, obtenemos los valores de nombre de usuario y contraseña de la variable GET. Luego, configuramos un cliente de Hojas de cálculo y solicitamos el feed de lista de la hoja de cálculo de los usuarios con una cadena de consulta para restringir los resultados solo a las filas en las que la columna de nombre de usuario sea igual al nombre de usuario que se pasó a la secuencia de comandos. Si no obtenemos filas en el resultado del feed de la lista, podemos continuar con la certeza de que el nombre de usuario que se pasó es único. Antes de insertar una fila en el feed de la lista, creamos un array asociativo de los valores de las columnas: el nombre de usuario, una encriptación de la contraseña con la función sha1 de PHP y un carácter de relleno para la sesión. Luego, llamamos a insertRow en el cliente de hojas de cálculo y pasamos el array asociativo, la clave de la hoja de cálculo y el ID de la hoja de trabajo. Si el objeto devuelto es un ListFeedEntry, mostramos un mensaje de éxito.
A continuación, se muestra el código PHP que realiza esta acción (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!";
}
?>
En la página de registro, podemos incluir la API de Maps para poder usar su función wrapper de XMLHttpRequest llamada GDownloadUrl. Cuando el usuario haga clic en el botón de envío, obtendremos el nombre de usuario y la contraseña de los campos de texto, crearemos una cadena de parámetros a partir de sus valores y llamaremos a GDownloadUrl en la URL y los parámetros de la secuencia de comandos. Como enviamos información sensible, usamos la versión HTTP POST de GDownloadUrl (enviando los parámetros como el tercer argumento en lugar de agregarlos a la URL). En la función de devolución de llamada, verificaremos si la respuesta fue exitosa y mostraremos un mensaje adecuado al usuario.
A continuación, se muestran una captura de pantalla y el código de una página de registro de ejemplo (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>
Cómo acceder como usuario
Para permitir que los usuarios accedan a nuestro sistema, necesitaremos una página HTML visible para el usuario que le solicite su nombre de usuario y contraseña, y una secuencia de comandos PHP para verificar la información de acceso, crear un ID de sesión y pasarlo de vuelta a la página de acceso para establecer una cookie. El usuario permanecerá conectado a través de la cookie de sesión en las páginas posteriores.
En la secuencia de comandos de PHP, primero incluimos la secuencia de comandos global y, luego, obtenemos los valores de nombre de usuario y contraseña de la variable GET. Luego, configuramos un cliente de Hojas de cálculo y solicitamos el feed de lista de la hoja de cálculo de los usuarios con una cadena de consulta para restringir los resultados solo a las filas en las que la columna de nombre de usuario sea igual al nombre de usuario que se pasó a la secuencia de comandos.
En la fila que se devuelve, verificaremos que el hash de la contraseña que se pasó coincida con el hash almacenado en la hoja de cálculo. Si es así, crearemos un ID de sesión con las funciones md5, uniqid y rand. Luego, actualizaremos la fila en la hoja de cálculo con la sesión y la mostraremos en la pantalla si la actualización de la fila se realiza correctamente.
A continuación, se muestra el código PHP que realiza esa acción (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"];
}
}
}
?>
En la página de acceso, podemos volver a incluir la API de Maps para poder usar su función wrapper de XMLHttpRequest llamada GDownloadUrl. Cuando el usuario haga clic en el botón de envío, obtendremos el nombre de usuario y la contraseña de los campos de texto, construiremos la URL de la secuencia de comandos con los parámetros de consulta y llamaremos a GDownloadUrl en la URL de la secuencia de comandos. En la función de devolución de llamada, estableceremos una cookie con el ID de sesión que devuelve la secuencia de comandos o mostraremos un mensaje de error si no se devuelve ninguno. La función setCookie proviene de un archivo cookies.js que se basa en el instructivo de JavaScript de W3C: http://www.w3schools.com/js/js_cookies.asp.
A continuación, se muestran una captura de pantalla y el código de una página de acceso de ejemplo (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>
Permitir que los usuarios agreguen lugares al mapa
Para permitir que un usuario agregue lugares a nuestro mapa, necesitaremos una página HTML visible para el usuario que le permita proporcionar información sobre la ubicación y dos secuencias de comandos PHP: una para verificar que haya accedido a su cuenta a través de la cookie que establecimos y otra para agregar la ubicación a la hoja de cálculo de ubicaciones.
En la primera secuencia de comandos de PHP que verifica si un usuario accedió, primero incluimos la secuencia de comandos global y, luego, obtenemos el valor de la sesión de la variable GET. Luego, configuramos un cliente de Hojas de cálculo y solicitamos el feed de lista para la hoja de cálculo de los usuarios con una cadena de consulta para restringir los resultados solo a aquellas filas en las que la columna de sesión sea igual al valor de sesión que se pasó a la secuencia de comandos. Luego, iteramos las entradas personalizadas de ese feed (las que corresponden a los encabezados de nuestras columnas) y, si existe, imprimimos el nombre de usuario correspondiente a esa sesión.
A continuación, se muestra el código PHP que realiza esa acción (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();
}
}
}
?>
En el segundo código PHP que permite a un usuario agregar una ubicación, primero replicamos el código de communitymap_checksession.php para asegurarnos de que el usuario aún haya accedido y sea válido. Luego, una vez que obtenemos un nombre de usuario válido de la hoja de usuarios, obtenemos los valores de lugar, latitud y longitud de la variable GET. Colocamos todos esos valores en un array asociativo y también agregamos un valor "date" con la función date() de PHP, de modo que sepamos cuándo el usuario agregó el lugar. Pasamos ese array asociativo, la constante de clave de hojas de cálculo y la constante de ID de la hoja de cálculo de ubicaciones a la función insertRow. Luego, mostramos el mensaje "Success" si se agregó una fila para la nueva ubicación en la hoja de cálculo. Si recibes un error en este paso, es probable que se deba a una falta de coincidencia en los nombres de los encabezados de las columnas. Las claves del array asociativo deben coincidir con los encabezados de columna de la hoja de cálculo especificada por la clave de la hoja de cálculo y el ID de la hoja de cálculo.
A continuación, se muestra el código PHP que realiza esa acción (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";
}
}
?>
En la página de agregar ubicación, podemos volver a incluir la API de Maps para poder usar GDownloadUrl y crear un mapa. Después de que se carga la página, usamos la función getCookie de cookies.js para recuperar el valor de la sesión. Si la cadena de sesión es nula o está vacía, mostramos un mensaje de error. Si no es así, llamamos a GDownloadUrl en map.checksession.php y enviamos la sesión. Si se devuelve correctamente un nombre de usuario, le mostramos un mensaje de bienvenida al usuario, revelamos el formulario para agregar una ubicación y cargamos el mapa. El formulario consta de un campo de texto de dirección, un mapa y campos de texto para el nombre del lugar, la latitud y la longitud. Si el usuario aún no conoce la latitud y la longitud de la ubicación, puede geocodificarla ingresando su dirección en el formulario y presionando "Enviar". Esto enviará una llamada a GClientGeocoder de la API de Maps, que colocará un marcador en el mapa si encuentra la dirección y completará automáticamente los campos de texto de latitud y longitud.
Cuando el usuario esté satisfecho, puede presionar el botón "Agregar ubicación". Luego, en JavaScript, obtendremos los valores de user, place, lat y lng, y los enviaremos a la secuencia de comandos communitymap_addlocation.php con GDownloadUrl.
Si esa secuencia de comandos devuelve un resultado exitoso, mostraremos un mensaje de éxito en la pantalla.
A continuación, se muestran una captura de pantalla y el código de una página de ejemplo para agregar una ubicación (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>
Cómo crear el mapa
Como hiciste pública la hoja de cálculo de ubicaciones en el primer paso, no se requiere programación del servidor para crear un mapa de ellas. De hecho, no se requiere programación. Puedes usar este asistente de Hojas de cálculo -> Mapa, que generará todo el código necesario para el mapa. El asistente descarga las entradas de la hoja de cálculo en la página agregando una etiqueta de secuencia de comandos que apunta al resultado JSON del feed y especifica una función de devolución de llamada que se llama una vez que se descarga el JSON. Puedes encontrar más información aquí.
Aquí puedes encontrar un ejemplo de código HTML para hacerlo: mainmap.htm. A continuación, se muestra una captura de pantalla:
Conclusión
Esperamos que ahora tengas tu propio sistema de mapas aportados por los usuarios en ejecución en tu servidor. En este artículo, se proporciona el código muy básico necesario para los aspectos esenciales de este sistema, pero ahora que conoces la biblioteca de Zend Spreadsheets, deberías poder extender el sistema para satisfacer tus necesidades particulares. Si tuviste errores durante el proceso, recuerda que puedes usar el comando echo en PHP o GLog.write() de la API de Maps en JavaScript para depurar, y siempre puedes publicar en los foros para desarrolladores de la API de Maps o la API de Spreadsheets para obtener ayuda adicional.