Веб-сервисы платформы Google Maps представляют собой набор HTTP-интерфейсов к сервисам Google, предоставляющим географические данные для ваших картографических приложений.
В этом руководстве описаны некоторые распространенные методы, полезные для настройки запросов к веб-сервису и обработки ответов от него. Полную документацию по API Directions (Legacy) см. в руководстве разработчика .
Что такое веб-сервис?
Веб-сервисы платформы Google Maps представляют собой интерфейс для запроса данных API карт у внешних сервисов и использования этих данных в ваших картографических приложениях. Эти сервисы предназначены для использования совместно с картой в соответствии с ограничениями лицензии , указанными в Условиях использования платформы Google Maps.
Веб-сервисы Maps API используют HTTP(S)-запросы к определенным URL-адресам, передавая в качестве аргументов параметры URL-адреса и/или данные POST в формате JSON. Как правило, эти сервисы возвращают данные в теле ответа в формате JSON или XML для анализа и/или обработки вашим приложением.
Типичный запрос к Directions API (устаревшая версия) обычно имеет следующий вид:
https://maps.googleapis.com/maps/api/directions/output?parameters
где output указывает формат ответа (обычно json или xml ).
Примечание : Для всех приложений Directions API (устаревшей версии) требуется аутентификация. Дополнительную информацию об учетных данных для аутентификации можно найти здесь.
Доступ по SSL/TLS
Для всех запросов к платформе Google Maps, использующих ключи API или содержащих пользовательские данные, требуется протокол HTTPS. Запросы, отправляемые по протоколу HTTP и содержащие конфиденциальные данные, могут быть отклонены.
Создание действительного URL-адреса
Вы можете подумать, что «действительный» URL-адрес очевиден, но это не совсем так. Например, URL-адрес, введенный в адресную строку браузера, может содержать специальные символы (например "上海+中國" ); браузеру необходимо внутренне преобразовать эти символы в другую кодировку перед передачей. Аналогично, любой код, который генерирует или принимает входные данные в кодировке UTF-8, может рассматривать URL-адреса с символами UTF-8 как «действительные», но ему также потребуется преобразовать эти символы перед отправкой на веб-сервер. Этот процесс называется кодированием URL или процентным кодированием .
Особые персонажи
Нам необходимо переводить специальные символы, поскольку все URL-адреса должны соответствовать синтаксису, указанному в спецификации унифицированного идентификатора ресурса (URI) . Фактически это означает, что URL-адреса должны содержать только специальное подмножество символов ASCII: привычные буквенно-цифровые символы и некоторые зарезервированные символы для использования в качестве управляющих символов внутри URL-адресов. В этой таблице приведено краткое описание этих символов:
| Набор | персонажи | использование URL |
|---|---|---|
| Буквенно-цифровой | abcdefghijklm nopqrstuvwxyz ABCDEFGHIJKLM NOPQRSTUVWXYZ 0 1 2 3 4 5 6 7 8 9 | Текстовые строки, используемая схема ( http ), порт ( 8080 ) и т. д. |
| Без ограничений | - _ . ~ | Текстовые строки |
| Сдержанный | ! * ' ( ) ; : @ & = + $ , / ? % # [ ] | Управляющие символы и/или текстовые строки |
При создании корректного URL-адреса необходимо убедиться, что он содержит только символы, указанные в таблице. Приведение URL-адреса в соответствие с этим набором символов обычно приводит к двум проблемам: пропуску символов и их замене:
- Символы, которые вы хотите обработать, находятся за пределами указанного выше набора. Например, символы в иностранных языках, такие как
上海+中國необходимо кодировать с использованием указанных выше символов. По общепринятой практике, пробелы (которые не допускаются в URL-адресах) также часто обозначаются символом плюса'+'. - Символы из приведенного выше набора являются зарезервированными, но их необходимо использовать буквально. Например, символ
?используется в URL-адресах для обозначения начала строки запроса; если вы хотите использовать строку "? and the Mysterions", вам нужно закодировать символ'?'.
Все символы, подлежащие URL-кодированию, кодируются с использованием символа '%' и двухсимвольного шестнадцатеричного значения, соответствующего их символу в кодировке UTF-8. Например,上海+中國в UTF-8 будет закодировано в URL как %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B . Строка ? and the Mysterians будет закодирована в URL как %3F+and+the+Mysterians или %3F%20and%20the%20Mysterians .
Распространенные символы, требующие кодирования
К числу распространенных символов, которые необходимо закодировать, относятся:
| Опасный персонаж | Закодированное значение |
|---|---|
| Космос | %20 |
| " | %22 |
| < | %3C |
| > | %3E |
| # | %23 |
| % | %25 |
| | | %7C |
Преобразование URL-адреса, полученного в результате ввода пользователя, иногда может быть сложной задачей. Например, пользователь может ввести адрес как "5th&Main St". Как правило, URL-адрес следует формировать из его частей, рассматривая любой введенный пользователем текст как буквальные символы.
Кроме того, длина URL-адресов для всех веб-сервисов платформы Google Maps и статических веб-API ограничена 16384 символами. Для большинства сервисов это ограничение по количеству символов будет достигаться крайне редко. Однако следует отметить, что некоторые сервисы имеют ряд параметров, которые могут привести к созданию длинных URL-адресов.
Вежливое использование API Google
Неправильно разработанные API-клиенты могут создавать избыточную нагрузку как на интернет, так и на серверы Google. В этом разделе приведены некоторые рекомендации для клиентов API. Следование этим рекомендациям поможет вам избежать блокировки вашего приложения из-за непреднамеренного злоупотребления API.
Экспоненциальная задержка
В редких случаях при обработке вашего запроса могут возникнуть проблемы; вы можете получить HTTP-код ответа 4XX или 5XX, или же TCP-соединение может просто прерваться где-то между вашим клиентом и сервером Google. Часто стоит повторить запрос, поскольку последующий запрос может быть успешным, если первоначальный не удался. Однако важно не зацикливаться на многократных запросах к серверам Google. Такое зацикливание может перегрузить сеть между вашим клиентом и Google, создавая проблемы для многих сторон.
Более эффективный подход заключается в повторных попытках с увеличением задержки между ними. Обычно задержка увеличивается в несколько раз с каждой попыткой, этот подход известен как экспоненциальная задержка .
Например, рассмотрим приложение, которое хочет отправить следующий запрос к API часовых поясов:
https://maps.googleapis.com/maps/api/timezone/json?location=39.6034810,-119.6822510×tamp=1331161200&key=YOUR_API_KEYСледующий пример на Python демонстрирует, как выполнить запрос с экспоненциальной задержкой:
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}")
Также следует убедиться, что в цепочке вызовов приложения выше нет кода повторных попыток, который приводит к повторным запросам с небольшой периодичностью.
Синхронизированные запросы
Большое количество синхронизированных запросов к API Google может выглядеть как распределенная атака типа «отказ в обслуживании» (DDoS) на инфраструктуру Google и обрабатываться соответствующим образом. Чтобы избежать этого, следует убедиться, что запросы к API не синхронизируются между клиентами.
Например, рассмотрим приложение, отображающее время в текущем часовом поясе. Это приложение, вероятно, установит будильник в клиентской операционной системе, который будет активироваться в начале минуты, чтобы отображаемое время могло обновиться. Приложение не должно выполнять никаких вызовов API в рамках обработки, связанной с этим будильником.
Выполнение API-запросов в ответ на фиксированный сигнал тревоги — плохая идея, поскольку это приводит к синхронизации API-запросов с началом минуты, даже между разными устройствами, вместо равномерного распределения во времени. Плохо спроектированное приложение, использующее такой подход, будет вызывать всплеск трафика, в шестьдесят раз превышающий нормальный уровень, в начале каждой минуты.
Вместо этого, одним из возможных вариантов удачного решения является установка второго будильника на случайно выбранное время. Когда этот второй будильник срабатывает, приложение вызывает необходимые API и сохраняет результаты. Когда приложению нужно обновить отображение в начале минуты, оно использует ранее сохраненные результаты, а не вызывает API снова. При таком подходе вызовы API равномерно распределяются во времени. Кроме того, вызовы API не задерживают отрисовку во время обновления отображения.
Помимо начала минуты, следует избегать синхронизации с началом часа, а также с началом каждого дня в полночь.
Обработка ответов
В этом разделе обсуждается, как динамически извлекать эти значения из ответов веб-сервиса.
Веб-сервисы Google Maps предоставляют ответы, которые легко понять, но не совсем удобны для пользователя. При выполнении запроса, вместо отображения набора данных, вам, вероятно, потребуется извлечь несколько конкретных значений. Как правило, вам потребуется проанализировать ответы веб-сервиса и извлечь только те значения, которые вас интересуют.
Выбор схемы парсинга зависит от того, возвращаете ли вы результат в формате XML или JSON. Ответы в формате JSON, уже представленные в виде объектов JavaScript, могут обрабатываться непосредственно в JavaScript на стороне клиента. Ответы в формате XML следует обрабатывать с помощью обработчика XML и языка запросов XML для обращения к элементам в формате XML. В следующих примерах мы используем XPath , поскольку он обычно поддерживается библиотеками обработки XML.
Обработка XML с помощью XPath
XML — это относительно зрелый формат структурированной информации, используемый для обмена данными. Хотя он не такой легковесный, как JSON, XML предоставляет более широкую поддержку языков программирования и более мощные инструменты. Например, код для обработки XML на Java встроен в пакеты javax.xml .
При обработке XML-ответов следует использовать соответствующий язык запросов для выбора узлов в XML-документе, а не предполагать, что элементы находятся в абсолютных позициях в XML-разметке. XPath — это синтаксис языка для однозначного описания узлов и элементов в XML-документе. Выражения XPath позволяют идентифицировать конкретное содержимое в XML-ответе.
Выражения XPath
Знание XPath значительно облегчит разработку надежной схемы синтаксического анализа. В этом разделе мы рассмотрим, как элементы в XML-документе обрабатываются с помощью XPath, что позволит вам обращаться к нескольким элементам одновременно и создавать сложные запросы.
XPath использует выражения для выбора элементов в XML-документе, применяя синтаксис, аналогичный синтаксису для путей к каталогам. Эти выражения идентифицируют элементы в дереве XML-документа, которое представляет собой иерархическое дерево, подобное DOM. Как правило, выражения XPath являются «жадными», то есть они будут соответствовать всем узлам, отвечающим заданным критериям.
Для иллюстрации наших примеров мы будем использовать следующий абстрактный XML-код:
<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>
Выбор узлов в выражениях
XPath-выборки позволяют выбирать узлы . Корневой узел охватывает весь документ. Выбрать этот узел можно с помощью специального выражения " / ". Обратите внимание, что корневой узел не является узлом верхнего уровня вашего XML-документа; фактически, он находится на один уровень выше этого элемента верхнего уровня и включает его.
Элементы-узлы представляют различные элементы в дереве XML-документа. Например, элемент <WebServiceResponse> представляет собой элемент верхнего уровня, возвращаемый в приведенном выше примере сервиса. Вы можете выбирать отдельные узлы либо по абсолютным, либо по относительным путям, которые указываются наличием или отсутствием символа " / " в начале.
- Абсолютный путь: выражение "
/WebServiceResponse/result" выбирает все узлы<result>, являющиеся дочерними элементами узла<WebServiceResponse>. (Обратите внимание, что оба этих элемента являются потомками корневого узла "/".) - Относительный путь относительно текущего контекста: выражение "
result" будет соответствовать любым элементам<result>в текущем контексте. Как правило, вам не нужно беспокоиться о контексте, поскольку вы обычно обрабатываете результаты веб-сервиса с помощью одного выражения.
Любое из этих выражений может быть дополнено путем добавления подстановочного знака, обозначаемого двойной косой чертой (" // "). Этот подстановочный знак указывает, что в промежуточном пути может совпадать ноль или более элементов. Например, выражение XPath " //formatted_address ", будет соответствовать всем узлам с таким именем в текущем документе. Выражение //viewport//lat будет соответствовать всем элементам <lat> , которые могут отслеживать <viewport> как родительский элемент.
По умолчанию выражения XPath соответствуют всем элементам. Вы можете ограничить выражение соответствием определенному элементу, указав предикат , заключенный в квадратные скобки ( [] ). Например, выражение XPath " /GeocodeResponse/result[2] всегда возвращает второй результат.
| Тип выражения | |
|---|---|
| Корневой узел | Выражение XPath: " / " Выбор:
<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>
|
| Абсолютный Путь | XPath-выражение: " /WebServiceResponse/result " Выбор:
<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>
|
| Путь с подстановочным знаком | XPath-выражение: " /WebServiceResponse//location " Выбор:
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
|
| Путь с предикатом | Выражение XPath: " /WebServiceResponse/result[2]/message " Выбор:
<message>The secret message</message>
|
Все прямые потомки первого result | XPath-выражение: " /WebServiceResponse/result[1]/* " Выбор:
<type>sample</type>
<name>Sample XML</name>
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
|
name result , type текста которого — «образец». | XPath-выражение: " /WebServiceResponse/result[type/text()='sample']/name " Выбор:
Sample XML
|
Важно отметить, что при выборе элементов вы выбираете узлы, а не только текст внутри этих объектов. Как правило, вам потребуется перебрать все найденные узлы и извлечь из них текст. Вы также можете напрямую сопоставлять текстовые узлы; см. раздел «Текстовые узлы» ниже.
Обратите внимание, что XPath поддерживает и узлы с атрибутами; однако все веб-сервисы Google Maps предоставляют элементы без атрибутов, поэтому сопоставление атрибутов не требуется.
Выделение текста в выражениях
Текст в XML-документе указывается в выражениях XPath с помощью оператора текстового узла . Этот оператор " text() " указывает на извлечение текста из указанного узла. Например, выражение XPath " //formatted_address/text() " вернет весь текст внутри элементов <formatted_address> .
| Тип выражения | |
|---|---|
| Все текстовые узлы (включая пробелы) | Выражение XPath: " //text() " Выбор:
sample
Sample XML
37.4217550
-122.0846330
The secret message
|
| Выделение текста | Выражение XPath: " /WebServiceRequest/result[2]/message/text() " Выбор:
The secret message
|
| Выбор с учетом контекста | XPath-выражение: " /WebServiceRequest/result[type/text() = 'sample']/name/text() " Выбор:
Sample XML
|
В качестве альтернативы вы можете вычислить выражение, вернуть набор узлов, а затем пройтись по этому «набору узлов», извлекая текст из каждого узла. Мы используем этот подход в приведенном ниже примере.
Для получения более подробной информации об XPath обратитесь к спецификации XPath W3C .
Оценка XPath в Java
В Java широко поддерживается разбор XML и использование выражений XPath в пакете javax.xml.xpath.* . Поэтому в приведенном в этом разделе примере кода используется Java для иллюстрации обработки XML и разбора данных из ответов XML-сервисов.
Для использования XPath в вашем Java-коде вам сначала нужно создать экземпляр XPathFactory и вызвать метод newXPath() для этой фабрики, чтобы создать объект XPath . Затем этот объект сможет обрабатывать переданные XML- и XPath-выражения с помощью метода evaluate() .
При оценке выражений XPath убедитесь, что вы перебираете все возможные «наборы узлов», которые могут быть возвращены. Поскольку эти результаты возвращаются в виде узлов DOM в коде Java, вам следует сохранять такие множественные значения в объекте NodeList и перебирать этот объект, чтобы извлечь любой текст или значения из этих узлов.
Приведенный ниже код демонстрирует, как создать объект XPath , присвоить ему XML-данные и выражение XPath, а затем вычислить это выражение для вывода соответствующего содержимого.
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"); } } }