Los servicios web de Google Maps Platform son un conjunto de interfaces HTTP para los servicios de Google que proporcionan datos geográficos para tus aplicaciones de mapas.
En esta guía, se describen algunas prácticas comunes que son útiles para configurar tus solicitudes de servicio web y procesar las respuestas del servicio. Consulta la guía para desarrolladores para obtener la documentación completa de la API de Geocoding.
¿Qué es un servicio web?
Los servicios web de Google Maps Platform son una interfaz para solicitar datos de la API de Maps a servicios externos y usarlos en tus aplicaciones de Maps. Estos servicios están diseñados para usarse junto con un mapa, según las Restricciones de licencia de las Condiciones del Servicio de Google Maps Platform.
Los servicios web de las API de Google Maps usan solicitudes HTTP(S) a URLs específicas y pasan parámetros de URL o datos POST en formato JSON como argumentos a los servicios. Por lo general, estos servicios devuelven datos en el cuerpo de la respuesta como JSON o XML para que tu aplicación los analice o procese.
Una solicitud típica a la API de Geocoding suele tener el siguiente formato:
https://maps.googleapis.com/maps/api/geocode/output?parameters
donde output indica el formato de respuesta (por lo general,
json o xml).
Nota: Todas las aplicaciones de la API de Geocoding requieren autenticación. Obtén más información sobre las credenciales de autenticación.
Acceso SSL/TLS
Se requiere HTTPS para todas las solicitudes de Google Maps Platform que usan claves de API o contienen datos de los usuarios. Es posible que se rechacen las solicitudes realizadas a través de HTTP que contengan datos sensibles.
Cómo crear una URL válida
Tal vez creas que una dirección URL "válida" es evidente, pero no siempre es así. Una URL que se ingresa en una barra de direcciones en un navegador, por ejemplo, puede contener caracteres especiales (p. ej., "上海+中國"), y el navegador debe traducir internamente esos caracteres a una codificación diferente antes de la transmisión.
Con el mismo token, cualquier código que genere o acepte entradas en UTF-8 podría procesar las URLs con caracteres UTF-8 como "válidas", pero también necesitaría traducir esos caracteres antes de enviarlos a un servidor web.
Este proceso se llama codificación de URLs o codificación por ciento.
Caracteres especiales
Debemos traducir los caracteres especiales porque todas las URLs deben cumplir con los requisitos de sintaxis que se indican en la especificación Identificador de recursos uniformes (URI). En efecto, esto significa que las URLs deben contener solo un subconjunto especial de caracteres ASCII: los símbolos alfanuméricos que ya conocemos y algunos caracteres reservados para usar como caracteres de control en las URLs, los cuales se resumen en la siguiente tabla:
| Conjunto | caracteres | Uso en la URL |
|---|---|---|
| Alfanuméricos | a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 | Cadenas de texto, uso de esquemas (http), puerto (8080), etcétera |
| No reservados | - _ . ~ | Cadenas de texto |
| Reservados | ! * ' ( ) ; : @ & = + $ , / ? % # [ ] | Caracteres de control o cadenas de texto |
Al crear una URL válida, debes asegurarte de que contenga solo los caracteres que se muestran en la tabla. Adaptar una dirección URL para usar este conjunto de caracteres generalmente provoca dos problemas, de omisión y de sustitución:
- Quizá quieras usar caracteres que no se encuentran dentro del conjunto anterior. Por ejemplo, los caracteres de otros idiomas, como
上海+中國, deben codificarse con los caracteres que se indican arriba. Por convención popular, los espacios (que no se permiten en las URLs) suelen representarse también con el carácter de signo más'+'. - Hay caracteres dentro del conjunto anterior que son caracteres reservados, pero se deben usar literalmente.
Por ejemplo,
?se usa en las URLs para indicar el comienzo de la cadena de consulta; si deseas utilizar la cadena "? and the Mysterions", debes codificar el carácter'?'.
La codificación de caracteres para URLs usa un carácter '%' y un valor hexadecimal de dos caracteres correspondiente a su carácter en UTF-8. Por ejemplo, 上海+中國 en UTF-8 se codificaría como %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B para usarse en URLs. La cadena ? and the Mysterians se codificaría como %3F+and+the+Mysterians o %3F%20and%20the%20Mysterians para usarse en URLs.
Caracteres comunes que necesitan codificación
A continuación se indican algunos de los caracteres que se deben codificar:
| Caracteres no seguros | Valor codificado |
|---|---|
| Espacio | %20 |
| " | %22 |
| < | %3C |
| > | %3E |
| # | %23 |
| % | %25 |
| | | %7C |
Convertir una dirección URL que recibes a través de la entrada de un usuario puede ser engañoso. Por ejemplo, un usuario puede ingresar la dirección "5th&Main St.". Generalmente, deberías crear tu URL a partir de sus partes y tratar las entradas del usuario como caracteres literales.
Además, las URLs tienen una limitación de 16,384 caracteres en todos los servicios web y las APIs web estáticas de Google Maps Platform. Para la mayoría de los servicios, este límite de caracteres rara vez se alcanza. No obstante, ten en cuenta que algunos servicios tienen varios parámetros que podrían generar URLs extensas.
Uso normal de las API de Google
Los clientes de API con un diseño deficiente pueden generar más carga de la necesaria tanto en Internet como en los servidores de Google. En esta sección se explican algunas de las prácticas recomendadas para los clientes de las API. Seguir estas prácticas recomendadas puede ayudarte a evitar que se bloquee tu aplicación por un abuso involuntario de las APIs.
Gestionar errores y reintentos
Para obtener información sobre los códigos de respuesta UNKNOWN_ERROR o OVER_QUERY_LIMIT de la API de Geocoding, consulta cómo administrar errores y reintentos.
Retirada exponencial
En casos excepcionales, es posible que se produzca un error cuando se entrega tu solicitud. Es posible que recibas un código de respuesta HTTP 4XX o 5XX, o que la conexión TCP simplemente falle en algún punto entre tu cliente y el servidor de Google. A menudo, vale la pena volver a intentar la solicitud, ya que la solicitud de seguimiento puede tener éxito cuando la original falló. Sin embargo, es importante no limitarse a realizar solicitudes repetidas a los servidores de Google. Este comportamiento de bucle puede sobrecargar la red entre tu cliente y Google, lo que causa problemas para muchas partes.
Un mejor enfoque consiste en realizar nuevos intentos con demoras más prolongadas entre uno y otro. Por lo general, el retraso se aumenta con un factor multiplicativo en cada intento, un enfoque conocido como retirada exponencial.
Por ejemplo, considera una aplicación que desea realizar esta solicitud a la API de zona horaria:
https://maps.googleapis.com/maps/api/timezone/json?location=39.6034810,-119.6822510×tamp=1331161200&key=YOUR_API_KEYEn el siguiente ejemplo de Python, se muestra cómo realizar la solicitud con retirada exponencial:
import json import time import urllib.error import urllib.parse import urllib.request # The maps_key defined below isn't a valid Google Maps API key. # You need to get your own API key. # See https://developers.google.com/maps/documentation/timezone/get-api-key API_KEY = "YOUR_KEY_HERE" TIMEZONE_BASE_URL = "https://maps.googleapis.com/maps/api/timezone/json" def timezone(lat, lng, timestamp): # Join the parts of the URL together into one string. params = urllib.parse.urlencode( {"location": f"{lat},{lng}", "timestamp": timestamp, "key": API_KEY,} ) url = f"{TIMEZONE_BASE_URL}?{params}" current_delay = 0.1 # Set the initial retry delay to 100ms. max_delay = 5 # Set the maximum retry delay to 5 seconds. while True: try: # Get the API response. response = urllib.request.urlopen(url) except urllib.error.URLError: pass # Fall through to the retry loop. else: # If we didn't get an IOError then parse the result. result = json.load(response) if result["status"] == "OK": return result["timeZoneId"] elif result["status"] != "UNKNOWN_ERROR": # Many API errors cannot be fixed by a retry, e.g. INVALID_REQUEST or # ZERO_RESULTS. There is no point retrying these requests. raise Exception(result["error_message"]) if current_delay > max_delay: raise Exception("Too many retry attempts.") print("Waiting", current_delay, "seconds before retrying.") time.sleep(current_delay) current_delay *= 2 # Increase the delay each time we retry. if __name__ == "__main__": tz = timezone(39.6034810, -119.6822510, 1331161200) print(f"Timezone: {tz}")
También debes tener cuidado de que no haya código de reintento más arriba en la cadena de llamadas de la aplicación que genere solicitudes repetidas en una sucesión rápida.
Solicitudes sincronizadas
Una gran cantidad de solicitudes sincronizadas a las APIs de Google puede parecer un ataque de denegación de servicio distribuido (DDoS) a la infraestructura de Google y tratarse como tal. Para evitar esto, debes asegurarte de que las solicitudes a la API no se sincronicen entre los clientes.
Por ejemplo, considera una aplicación que muestre la hora en la zona horaria actual. Es probable que esta aplicación configure una alarma en el sistema operativo del cliente para que se active al comienzo del minuto y se pueda actualizar la hora que se muestra. La aplicación no debe realizar llamadas a la API como parte del procesamiento asociado con esa alarma.
Realizar llamadas a la API en respuesta a una alarma fija es una mala práctica, ya que hace que las llamadas a la API se sincronicen con el inicio del minuto, incluso entre diferentes dispositivos, en lugar de distribuirse de manera uniforme en el tiempo. Una aplicación mal diseñada que haga esto producirá un aumento repentino del tráfico de sesenta veces los niveles normales al comienzo de cada minuto.
En su lugar, un posible buen diseño es tener una segunda alarma configurada en una hora elegida al azar. Cuando se activa esta segunda alarma, la aplicación llama a las APIs que necesita y almacena los resultados. Cuando la aplicación quiere actualizar su pantalla al comienzo del minuto, usa los resultados almacenados previamente en lugar de volver a llamar a la API. Con este enfoque, las llamadas a la API se distribuyen de manera uniforme en el tiempo. Además, las llamadas a la API no retrasan la renderización cuando se actualiza la pantalla.
Además del inicio del minuto, otros momentos de sincronización comunes que debes tener cuidado de no segmentar son el inicio de una hora y el inicio de cada día a la medianoche.
Procesamiento de respuestas
En esta sección se discute cómo extraer esos valores de forma dinámica de las respuestas de los servicios web.
Los servicios web de Google Maps proporcionan respuestas que son fáciles de comprender, pero no son exactamente fáciles de usar. Cuando realizas una consulta, en lugar de mostrar un conjunto de datos, es probable que quieras extraer algunos valores específicos. Por lo general, querrás analizar las respuestas del servicio web y extraer solo los valores que te interesen.
El esquema de análisis que uses dependerá de si devuelves el resultado en XML o JSON. Las respuestas JSON, que ya tienen el formato de objetos de JavaScript, se pueden procesar en JavaScript en el cliente. Las respuestas XML se deben procesar con un procesador XML y un lenguaje de consulta XML para abordar los elementos dentro del formato XML. Usamos XPath en los siguientes ejemplos, ya que se admite comúnmente en las bibliotecas de procesamiento de XML.
Procesamiento de XML con XPath
XML es un formato de información estructurada relativamente maduro que se usa para
el intercambio de datos. Aunque no es tan liviano como JSON, XML
proporciona más compatibilidad con lenguajes y herramientas más sólidas. Por ejemplo, el código para procesar XML en Java está integrado en los paquetes javax.xml.
Cuando proceses respuestas XML, debes usar un lenguaje de consulta adecuado para seleccionar nodos dentro del documento XML, en lugar de suponer que los elementos residen en posiciones absolutas dentro del lenguaje de marcado XML. XPath es una sintaxis de lenguaje para describir de forma única nodos y elementos dentro de un documento XML. Las expresiones XPath te permiten identificar contenido específico dentro del documento de respuesta XML.
Expresiones XPath
Tener cierta familiaridad con XPath es de gran ayuda para desarrollar un esquema de análisis sólido. En esta sección, se abordará cómo se direccionan los elementos dentro de un documento XML con XPath, lo que te permite direccionar varios elementos y construir consultas complejas.
XPath usa expresiones para seleccionar elementos dentro de un documento XML, con una sintaxis similar a la que se usa para las rutas de directorio. Estas expresiones identifican elementos dentro de un árbol de documentos XML, que es un árbol jerárquico similar al de un DOM. En general, las expresiones XPath son ambiciosas, lo que indica que coincidirán con todos los nodos que cumplan con los criterios proporcionados.
Usaremos el siguiente XML abstracto para ilustrar nuestros ejemplos:
<WebServiceResponse> <status>OK</status> <result> <type>sample</type> <name>Sample XML</name> <location> <lat>37.4217550</lat> <lng>-122.0846330</lng> </location> </result> <result> <message>The secret message</message> </result> </WebServiceResponse>
Selección de nodos en expresiones
Las selecciones de XPath seleccionan nodos. El nodo raíz
abarca todo el documento. Selecciona este nodo con
la expresión especial "/". Ten en cuenta que el nodo raíz
no es el nodo de nivel superior de tu documento XML; de hecho,
reside un nivel por encima de este elemento de nivel superior y lo
incluye.
Los nodos de elementos representan los diversos elementos dentro del árbol de documentos XML. Por ejemplo, un elemento <WebServiceResponse> representa el elemento de nivel superior que se devuelve en nuestro servicio de ejemplo anterior. Seleccionas nodos individuales a través de
rutas de acceso absolutas o relativas, indicadas por la presencia o
ausencia de un carácter "/" inicial.
- Ruta de acceso absoluta: La expresión "
/WebServiceResponse/result" selecciona todos los nodos<result>que son secundarios del nodo<WebServiceResponse>. (Ten en cuenta que ambos elementos descienden del nodo raíz "/"). - Ruta de acceso relativa desde el contexto actual: La expresión
"
result" coincidiría con cualquier elemento<result>dentro del contexto actual. Por lo general, no deberías preocuparte por el contexto, ya que sueles procesar los resultados del servicio web a través de una sola expresión.
Cualquiera de estas expresiones se puede aumentar agregando
una ruta de acceso comodín, indicada con una barra doble ("//").
Este comodín indica que cero o más elementos pueden coincidir en la
ruta de acceso intermedia. Por ejemplo, la expresión XPath "//formatted_address"
coincidirá con todos los nodos de ese nombre en el documento actual.
La expresión //viewport//lat coincidiría con todos los elementos <lat> que pueden rastrear <viewport> como elemento superior.
De forma predeterminada, las expresiones Xpath coinciden con todos los elementos. Puedes restringir
la expresión para que coincida con un elemento determinado proporcionando un predicado,
que se encierra entre corchetes ([]). Por ejemplo, la expresión
XPath "/GeocodeResponse/result[2] siempre devuelve el
segundo resultado.
| Tipo de expresión | |
|---|---|
| Nodo raíz | Expresión XPath: "
/"Selección:
<WebServiceResponse>
<status>OK</status>
<result>
<type>sample</type>
<name>Sample XML</name>
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
</result>
<result>
<message>The secret message</message>
</result>
</WebServiceResponse>
|
| Ruta de acceso absoluta | Expresión XPath: "
/WebServiceResponse/result"Selección:
<result>
<type>sample</type>
<name>Sample XML</name>
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
</result>
<result>
<message>The secret message</message>
</result>
|
| Ruta de acceso con comodín | Expresión XPath: "
/WebServiceResponse//location"Selección:
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
|
| Ruta de acceso con predicado | Expresión XPath: "
/WebServiceResponse/result[2]/message"Selección:
<message>The secret message</message>
|
Todos los elementos secundarios directos del primer result |
Expresión XPath: "
/WebServiceResponse/result[1]/*"Selección:
<type>sample</type>
<name>Sample XML</name>
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
|
El name de un result cuyo texto type es "sample". |
Expresión XPath: "
/WebServiceResponse/result[type/text()='sample']/name"Selección:
Sample XML
|
Es importante tener en cuenta que, cuando seleccionas elementos, seleccionas nodos, no solo el texto dentro de esos objetos. Por lo general, querrás iterar en todos los nodos que coincidan y extraer el texto. También puedes hacer coincidir nodos de texto directamente; consulta Nodos de texto a continuación.
Ten en cuenta que XPath también admite nodos de atributos. Sin embargo, todos los servicios web de Google Maps entregan elementos sin atributos, por lo que no es necesario que coincidan los atributos.
Selección de texto en expresiones
El texto dentro de un documento XML se especifica en expresiones XPath
a través de un operador de nodo de texto. Este operador "text()"
indica la extracción de texto del nodo indicado. Por ejemplo,
la expresión de XPath "//formatted_address/text()" devolverá
todo el texto dentro de los elementos
<formatted_address>.
| Tipo de expresión | |
|---|---|
| Todos los nodos de tecto (incluidos espacios en blanco) | Expresión XPath: "
//text()"Selección:
sample
Sample XML
37.4217550
-122.0846330
The secret message
|
| Selección de texto | Expresión XPath: "
/WebServiceRequest/result[2]/message/text()"Selección:
The secret message
|
| Selección dependiente del contexto | Expresión XPath: "
/WebServiceRequest/result[type/text() = 'sample']/name/text()"Selección:
Sample XML
|
Como alternativa, puedes evaluar una expresión y devolver un conjunto de nodos y, luego, iterar sobre ese "conjunto de nodos" para extraer el texto de cada nodo. En el siguiente ejemplo usamos este enfoque.
Para obtener más información sobre XPath, consulta la especificación de XPath de W3C.
Evalúa XPath en Java
Java tiene una amplia compatibilidad para analizar XML y usar expresiones XPath
dentro del paquete javax.xml.xpath.*.
Por ese motivo, el código de muestra de esta sección usa Java para
ilustrar cómo controlar XML y analizar datos de las respuestas del servicio de XML.
Para usar XPath en tu código de Java, primero deberás crear una instancia
de un XPathFactory y llamar a
newXPath() en esa fábrica para crear un objeto XPath
. Luego, este objeto puede procesar expresiones XML
y XPath pasadas con el método evaluate().
Cuando evalúes expresiones XPath, asegúrate de iterar
sobre cualquier posible "conjunto de nodos" que se pueda devolver. Debido a que estos
resultados se devuelven como nodos del DOM en el código Java, debes capturar
esos valores múltiples dentro de un objeto NodeList y,
luego, iterar sobre ese objeto para extraer cualquier texto o valor de esos
nodos.
En el siguiente código, se ilustra cómo crear un objeto XPath, asignarle XML y una expresión XPath, y evaluar la expresión para imprimir el contenido pertinente.
import org.xml.sax.InputSource; import org.w3c.dom.*; import javax.xml.xpath.*; import java.io.*; public class SimpleParser { public static void main(String[] args) throws IOException { XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); try { System.out.print("Web Service Parser 1.0\n"); // In practice, you'd retrieve your XML via an HTTP request. // Here we simply access an existing file. File xmlFile = new File("XML_FILE"); // The xpath evaluator requires the XML be in the format of an InputSource InputSource inputXml = new InputSource(new FileInputStream(xmlFile)); // Because the evaluator may return multiple entries, we specify that the expression // return a NODESET and place the result in a NodeList. NodeList nodes = (NodeList) xpath.evaluate("XPATH_EXPRESSION", inputXml, XPathConstants.NODESET); // We can then iterate over the NodeList and extract the content via getTextContent(). // NOTE: this will only return text for element nodes at the returned context. for (int i = 0, n = nodes.getLength(); i < n; i++) { String nodeString = nodes.item(i).getTextContent(); System.out.print(nodeString); System.out.print("\n"); } } catch (XPathExpressionException ex) { System.out.print("XPath Error"); } catch (FileNotFoundException ex) { System.out.print("File Error"); } } }