API Яндекс.Карты – геокодирование и маршрутизация на JavaScript

Яндекс.Карты - замечательный, популярный инструмент, демонстрирующий отличные результаты геокодирования и построения маршрутов.

Но предположим, что перед пользователем стоит задача обработать большой список адресов/координат, а именно, посчитать расстояния между точками по автомобильным дорогам и определить координаты внушительного списка объектов. Тогда-то он и столкнется с отсутствием возможности пакетной обработки в пользовательском интерфейсе сервиса. Однако, Яндекс об этом позаботился, предоставив в условно-бесплатное пользование JavaScript API.

Разработчики на JavaScript API получают техническую поддержку, подробную документацию, инструментарий для тестирования кода в “песочнице” на сайте проекта https://yandex.ru/dev/maps/jsapi/

Итак, я попробую воспользоваться этим инструментом для решения задачи геокодирования и построения маршрутов. Мне понадобится учетная запись Яндекс и API ключ для сервиса “JavaScript API и HTTP Геокодер”, который можно создать в Кабинете разработчика https://developer.tech.yandex.ru. Не буду подробно останавливаться на этом моменте, в сети достаточно информации, чтобы самостоятельно разобраться.

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-объект, содержащий координаты, тип, наименование, точность геокодирования.

API Яндекс.Карты – геокодирование и маршрутизация на 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>

Страница в браузере с результатами обработки выглядит примерно следующим образом:

API Яндекс.Карты – геокодирование и маршрутизация на JavaScript

Верхнее текстовое поле для вставки исходных данных, в нижнее пишется полученный результат. Если в строке исходных данных встречается разделитель (разделителем между пунктом отправления и прибытия является “;”), то осуществляется расчет маршрута, в противном случае выполняется геокодирование сроки (в примере на картинке выше “Лондон”). Также регулярным выражением определяется, являются ли исходные данные географическими координатами (широта и долгота разделены запятой) или строкой адреса, при этом адреса и координаты можно комбинировать при построении маршрута.

Внимательному читателю наверняка сразу бросилась в глаза вот эта конструкция в листинге кода:

await new Promise(r => setTimeout(r, 50));

Тут я делаю короткую (50 мсек) паузу перед обработкой следующей строки, т.е. искусственно устанавливаю предел максимальной скорости обработки в 20 строк в секунду. Спросите, зачем? И тут настало время упомянуть о технических лимитах бесплатного использования JavaScript API от Яндекса. А их два, но они довольно «вкусные»:

- Суточное ограничение – до 25000 запросов к API (каждый вызов метода geocode или route считается за 1). Информация о расходовании суточного лимита доступна в Кабинете разработчика.

- Кол-во запросов в секунду – не более 50

Т.е. пауза между вызовами API нужна, чтобы не превысить «скоростной лимит», установленный Яндексом.

На этом, пожалуй, всё, “за кадром” осталась css-таблица стилей, а также разработка backend на Node.js, но это - уже совсем другая история.

5
3 комментария
\n \n \n \n \n
\n \n
\n \n","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Тут будет два многострочных текстовых поля, первое для ввода исходных данных (data) и второе - для записи результата (target), и две кнопки для запуска процесса обработки и очистки поля с результатом.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Обратите внимание, в заголовке страницы в элементе script подключается так необходимый мне API от Яндекса, а в параметрах url атрибута src необходимо указать тот самый секретный api-ключ, полученный в кабинете разработчика.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После загрузки страницы, в глобальном контексте выполнения JavaScript станет доступен объект ymaps, через который я получаю доступ к API, в том числе и к необходимым мне функциям геокодирования и маршрутизации. Напишу две функции “router” и “geocoder”, которые, как не сложно догадаться, реализуют построение маршрута и выполняют геокодирование. Конечно же JavaScript API реализуют значительно больше картографических сервисов и даже получаемые тут данные о маршруте и местоположении являются далеко не полными. Полное описание используемых методов доступно по ссылке, указанной в начале этого материала. Но вернемся к поставленной задаче и разберемся сначала с функцией “router”:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"function router(from, to){\n return ymaps.route([from,to], {multiRoute: false, routingMode: \"auto\"})\n .then(r => {return {from: from, to: to, dist: r.getLength()}} )\n .catch(e => {console.log(e); return {from: from, to: to, dist: 'route error'}});\n }","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В качестве аргументов она принимает пункты отправления и назначения, которыми могут быть как строками с адресом, так и массивами [широта, долгота]. Эти аргументы передаются в метод route объекта ymaps вместе с параметрами построения маршрута, в моем случае routingMode: “auto” – это указание строить именно автомобильный маршрут. Метод возвращает promise-объект, содержащий в том числе протяженность маршрута в метрах, который затем и возвращает функция.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Очередь функции “geocoder”:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"function geocoder(place){\n return ymaps.geocode(place, {results: 1}).then(\n r => {\n let geo = r.geoObjects.toArray()[0];\n let data = geo.properties.get('metaDataProperty').GeocoderMetaData;\n let point = geo.geometry.getCoordinates()\n let result = {point: point, place: place, kind: data.kind, precision: data.precision, text: data.text}; \n return result;}\n ).catch(e => {console.log(e); return {place: place, point: 'geocoding error', text: null, kind:null, precision: null}})\n }","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Функция принимает адрес строкой или массив [широта, долгота]. Этот параметр передается в метод geocode объекта ymaps вместе с параметрами геокодирования, в моем случае results: 1 – указание вернуть только один, наиболее точный, результат. Функция вернет promise-объект, содержащий координаты, тип, наименование, точность геокодирования.

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"b4729bdd-1b19-5a05-901d-dc4d19b7659d","width":841,"height":108,"size":4223,"type":"png","color":"4474c3","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ну что ж, уже можно пользоваться. Например, если вызвать следующий код

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"(async ()=>{\n console.log(await geocoder(“Москва”));\n console.log(await router(“Москва”,”Санкт-Петербург”));\n})()","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

, то в консоль браузера будут записаны результат геокодирования объекта “Москва” и информация о протяженности автомобильного маршрута “Москва -> Санкт-Петербург”

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Остается только реализовать построчную обработку содержимого элемента “data” и запись результата в элемент “target”. Ниже приводится итоговое содержимое html-файла, в котором это реализуется функцией “start_process”.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"\n\n \n JavaScript API Yndex.Map\n \n \n \n \n \n \n \n
\n \n
\n \n","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Страница в браузере с результатами обработки выглядит примерно следующим образом:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"9ef9df37-e706-5600-a48d-e2e17a889e86","width":590,"height":315,"size":16116,"type":"png","color":"c2c7be","hash":"","external_service":[]}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Верхнее текстовое поле для вставки исходных данных, в нижнее пишется полученный результат. Если в строке исходных данных встречается разделитель (разделителем между пунктом отправления и прибытия является “;”), то осуществляется расчет маршрута, в противном случае выполняется геокодирование сроки (в примере на картинке выше “Лондон”). Также регулярным выражением определяется, являются ли исходные данные географическими координатами (широта и долгота разделены запятой) или строкой адреса, при этом адреса и координаты можно комбинировать при построении маршрута.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Внимательному читателю наверняка сразу бросилась в глаза вот эта конструкция в листинге кода:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"await new Promise(r => setTimeout(r, 50));","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Тут я делаю короткую (50 мсек) паузу перед обработкой следующей строки, т.е. искусственно устанавливаю предел максимальной скорости обработки в 20 строк в секунду. Спросите, зачем? И тут настало время упомянуть о технических лимитах бесплатного использования JavaScript API от Яндекса. А их два, но они довольно «вкусные»:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

- Суточное ограничение – до 25000 запросов к API (каждый вызов метода geocode или route считается за 1). Информация о расходовании суточного лимита доступна в Кабинете разработчика.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

- Кол-во запросов в секунду – не более 50

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Т.е. пауза между вызовами API нужна, чтобы не превысить «скоростной лимит», установленный Яндексом.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На этом, пожалуй, всё, “за кадром” осталась css-таблица стилей, а также разработка backend на Node.js, но это - уже совсем другая история.

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":3,"favorites":4,"reposts":0,"views":2101,"hits":7589,"reads":null,"online":0},"dateFavorite":0,"hitsCount":7589,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/id447345/573174-api-yandekskarty-geokodirovanie-i-marshrutizaciya-na-javascript","author":{"id":447345,"name":"NTA","nickname":null,"description":null,"uri":"","avatar":{"type":"image","data":{"uuid":"e7175678-aaab-09f8-0271-7b3af7ac4670","width":1207,"height":1207,"size":83090,"type":"jpg","color":"e3ebf8","hash":"","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"8575b55d-a1f0-53ee-9ad6-78268f9ea45c","width":1280,"height":373,"size":56005,"type":"jpg","color":"b0b9ca","hash":"","external_service":[]}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4980908,"userId":447345,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4980908"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":1356821,"userId":447345,"count":0,"shareImage":"https://api.vc.ru/achievements/share/1356821"},{"title":"5 лет на vc.ru","code":"registration_5_years","description":"Провёл 5 лет вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"a9140d54-73b8-5f40-afa8-449fbaafd42b","formats":{"glb":"https://static.vc.ru/achievements/whale.glb","usdz":"https://static.vc.ru/achievements/whale.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.533203125,"textY":0.658203125,"logoX":0.533203125,"logoY":0.77734375,"logoXNoText":0.4375,"logoYNoText":0.66015625},"id":130113,"userId":447345,"count":0,"shareImage":"https://api.vc.ru/achievements/share/130113"}],"lastModificationDate":1765085417,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":true,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":447345,"name":"NTA","nickname":null,"description":null,"uri":"","avatar":{"type":"image","data":{"uuid":"e7175678-aaab-09f8-0271-7b3af7ac4670","width":1207,"height":1207,"size":83090,"type":"jpg","color":"e3ebf8","hash":"","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"8575b55d-a1f0-53ee-9ad6-78268f9ea45c","width":1280,"height":373,"size":56005,"type":"jpg","color":"b0b9ca","hash":"","external_service":[]}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4980908,"userId":447345,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4980908"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":1356821,"userId":447345,"count":0,"shareImage":"https://api.vc.ru/achievements/share/1356821"},{"title":"5 лет на vc.ru","code":"registration_5_years","description":"Провёл 5 лет вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"a9140d54-73b8-5f40-afa8-449fbaafd42b","formats":{"glb":"https://static.vc.ru/achievements/whale.glb","usdz":"https://static.vc.ru/achievements/whale.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.533203125,"textY":0.658203125,"logoX":0.533203125,"logoY":0.77734375,"logoXNoText":0.4375,"logoYNoText":0.66015625},"id":130113,"userId":447345,"count":0,"shareImage":"https://api.vc.ru/achievements/share/130113"}],"lastModificationDate":1765085417,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":true,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"reactions":{"counters":[{"id":1,"count":5}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null,"keywords":[],"media":null,"customCover":null,"robotsTag":"noindex","categories":[],"isAnonymized":true}};