Время прочтения: 6 мин.
Предположим, что перед пользователем стоит задача обработать большой список адресов/координат, а именно, посчитать расстояния между точками по автомобильным дорогам и определить координаты внушительного списка объектов. Тогда-то он и столкнется с отсутствием возможности пакетной обработки в пользовательском интерфейсе сервиса. Однако, Яндекс об этом позаботился, предоставив в условно-бесплатное пользование JavaScript API.
Разработчики на JavaScript API получают техническую поддержку, подробную документацию, инструментарий для тестирования кода в “песочнице” на сайте проекта
Итак, попробуем воспользоваться этим инструментом для решения задачи геокодирования и построения маршрутов. Понадобится учетная запись Яндекс и API ключ для сервиса “JavaScript API и HTTP Геокодер”, который можно создать в Кабинете разработчика . Не будем подробно останавливаться на этом моменте, в сети достаточно информации, чтобы самостоятельно разобраться.
JavaScript API работает только в браузере, поэтому для выполнения кода нужно разработать web-страничку. В данном случае можно обойтись статичным html-файлом, т.е. читать исходные данные и записывать результат в html-элементы, т.е. для упрощения обойдемся без использования серверной части. Итак, запускаем текстовый редактор и создаем заготовку будущей web-страницы.
<!DOCTYPE html>
<html>
<head>
<title>JavaScript API Yndex.Map</title>
<meta charset="utf-8" />
<script src="https://api-maps.yandex.ru/2.1/?apikey=<ваш API-ключ >=ru_RU" type="text/javascript"></script>
<script type="text/javascript"></script>
</head>
<body style="font-family: Arial, Helvetica, sans-serif;">
<textarea id="data" class="area" rows=5 ></textarea>
<div><button id="process_button" class="button process" onclick="start_process()">Start</button></div>
<textarea id="target" class="area" rows=5 ></textarea>
<div><button id="clear_button" class="button clear" onclick="clear_result()">Clear</button></div>
</body>
</html>
Тут будет два многострочных текстовых поля, первое для ввода исходных данных (data) и второе для записи результата (target), и две кнопки для запуска процесса обработки и очистки поля с результатом.
Обратите внимание, в заголовке страницы в элементе script подключается так необходимый нам API от Яндекса, а в параметрах url атрибута src необходимо указать тот самый секретный api-ключ, полученный в кабинете разработчика.
После загрузки страницы, в глобальном контексте выполнения JavaScript станет доступен объект ymaps, через который получаем доступ к API, в том числе и к необходимым функциям геокодирования и маршрутизации. Напишем две функции “router” и “geocoder”, которые, как не сложно догадаться, реализуют построение маршрута и выполняют геокодирование. Конечно же JavaScript API реализуют значительно больше картографических сервисов и даже получаемые тут данные о маршруте и местоположении являются далеко не полными. Полное описание используемых методов доступно по ссылке, указанной в начале этого материала. Но вернемся к поставленной задаче и разберемся сначала с функцией “router”:
function router(from, to){
return ymaps.route([from,to], {multiRoute: false, routingMode: "auto"})
.then(r => {return {from: from, to: to, dist: r.getLength()}} )
.catch(e => {console.log(e); return {from: from, to: to, dist: 'route error'}});
}
В качестве аргументов она принимает пункты отправления и назначения, которыми могут быть как строками с адресом, так и массивами [широта, долгота]. Эти аргументы передаются в метод route объекта ymaps вместе с параметрами построения маршрута, в данном случае routingMode: “auto” – это указание строить именно автомобильный маршрут. Метод возвращает promise-объект, содержащий в том числе протяженность маршрута в метрах, который затем и возвращает функция.
Очередь функции “geocoder”:
function geocoder(place){
return ymaps.geocode(place, {results: 1}).then(
r => {
let geo = r.geoObjects.toArray()[0];
let data = geo.properties.get('metaDataProperty').GeocoderMetaData;
let point = geo.geometry.getCoordinates()
let result = {point: point, place: place, kind: data.kind, precision: data.precision, text: data.text};
return result;}
).catch(e => {console.log(e); return {place: place, point: 'geocoding error', text: null, kind:null, precision: null}})
}
Функция принимает адрес строкой или массив [широта, долгота]. Этот параметр передается в метод geocode объекта ymaps вместе с параметрами геокодирования, в данном случае results: 1 – указание вернуть только один, наиболее точный, результат. Функция вернет promise-объект, содержащий координаты, тип, наименование, точность геокодирования.
“Promise (обещание) – специальный объект JavaScript, который используется для написания и использования асинхронного кода”
Ну что ж, уже можно пользоваться. Например, если вызвать следующий код:
(async ()=>{
console.log(await geocoder(“Москва”));
console.log(await router(“Москва”,”Санкт-Петербург”));
})()
то в консоль браузера будут записаны результат геокодирования объекта “Москва” и информация о протяженности автомобильного маршрута “Москва -> Санкт-Петербург”
Остается только реализовать построчную обработку содержимого элемента “data” и запись результата в элемент “target”. Ниже приводится итоговое содержимое html-файла, в котором это реализуется функцией “start_process”.
<!DOCTYPE html>
<html>
<head>
<title>JavaScript API Yndex.Map</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="https://api-maps.yandex.ru/2.1/?apikey=<ваш API-ключ >&lang=ru_RU" type="text/javascript"></script>
<script type="text/javascript">
function clear_result() {
document.getElementById("target").value= "";
}
async function start_process() {
let data = document.getElementById("data").value.split("\n")
let target = document.getElementById("target")
for (row of data) {
await new Promise(r => setTimeout(r, 50));
let arr = row.split(";")
let result = (
arr.length > 1
? await (()=>{
let from = iscoord(arr[0])? arr[0].split(",",2): arr[0] ;
let to = iscoord(arr[1])? arr[1].split(",",2): arr[1] ;
return router(from,to).then(r => {
return `${r.from};${r.to};${r.dist}`;
})})()
: await (()=>{
let place = iscoord(arr[0]) ? arr[0].split(",",2) : arr[0];
return geocoder(place).then(r => {
return `${r.place};${r.point};${r.text}`;
})})()
)
target.value += result + "\n"
}
}
function router(from, to){
return ymaps.route([from,to], {multiRoute: false, routingMode: "auto"})
.then(r => {return {from: from, to: to, dist: r.getLength()}} )
.catch(e => {console.log(e); return {from: from, to: to, dist: 'route error'}});
}
function geocoder(place){
return ymaps.geocode(place, {results: 1}).then(
r => {
let geo = r.geoObjects.toArray()[0];
let data = geo.properties.get('metaDataProperty').GeocoderMetaData;
let point = geo.geometry.getCoordinates()
let result = {point: point, place: place, kind: data.kind, precision: data.precision, text: data.text};
return result;}
).catch(e => {console.log(e); return {place: place, point: 'geocoding error', text: null, kind:null, precision: null}})
}
function iscoord(value) {
let regexp = /\d+(?:\.\d+)*,\d+(?:\.\d+)*/ ;
return regexp.test(value)
}
</script>
</head>
<body style="font-family: Arial, Helvetica, sans-serif;">
<textarea id="data" class="area" rows=5 ></textarea>
<div><button id="process_button" class="button process" onclick="start_process()">Start</button></div>
<textarea id="target" class="area" rows=5 ></textarea>
<div><button id="clear_button" class="button clear" onclick="clear_result()">Clear</button></div>
</body>
</html>
Страница в браузере с результатами обработки выглядит примерно следующим образом:

Верхнее текстовое поле для вставки исходных данных, в нижнее пишется полученный результат. Если в строке исходных данных встречается разделитель (разделителем между пунктом отправления и прибытия является “;”), то осуществляется расчет маршрута, в противном случае выполняется геокодирование сроки (в примере на картинке выше “Лондон”). Также регулярным выражением определяется, являются ли исходные данные географическими координатами (широта и долгота разделены запятой) или строкой адреса, при этом адреса и координаты можно комбинировать при построении маршрута.
Внимательному читателю наверняка сразу бросилась в глаза вот эта конструкция в листинге кода:
await new Promise(r => setTimeout(r, 50));
Тут делаем короткую (50 мсек) паузу перед обработкой следующей строки, т.е. искусственно устанавливается предел максимальной скорости обработки в 20 строк в секунду. Спросите, зачем? И тут настало время упомянуть о бесплатном сыре технических лимитах бесплатного использования JavaScript API от Яндекса. А их два, но они довольно «вкусные»:
- Суточное ограничение – до 25000 запросов к API (каждый вызов метода geocode или route считается за 1). Информация о расходовании суточного лимита доступна в Кабинете разработчика.
- Кол-во запросов в секунду – не более 50
Т.е. пауза между вызовами API нужна, чтобы не превысить «скоростной лимит», установленный Яндексом.
На этом, пожалуй, всё, “за кадром” осталась css-таблица стилей, а также разработка backend на Node.js, но это — уже совсем другая история.