Условно-бесплатное определение расстояний между точками с помощью сервисов геокодирования и Open source проектов

04.03.24

Интеграция - WEB-интеграция

Добрый день. Сегодня расскажу о своих инженерных изысканиях в области карт/расстояний между точками и адресами.

Скачать исходный код

Наименование Файл Версия Размер
Условно-бесплатное определение расстояний между точками с помощью сервисов геокодирования и Open source проектов:
.epf 9,48Kb
3
.epf 9,48Kb 3 Скачать

Как-то, около 2х лет назад передо мной одна небольшая, но амбициозная фирма поставила задачу:

"Нам нужно определять расстояние от точки до точки в путевом листе Бухгалтерии автоматически, но, чтобы это стоило "мало деняг". 

Естественно, как инженеру, мне было интересно решить данную задачу и посмотреть, а есть ли вообще способы бесплатно или почти бесплатно решить поставленную задачу. Спойлер: решение есть.

API Яндекса и 2gis не рассматривались изначально, так как они платные и, если не ошибаюсь, только API 2Gis в тех годах стоял около 130 к в год . Может сейчас все изменилось. Ну, в общем, они не бесплатные.

Порыскав в поисках решения задачи, я нашел отличный русскоязычный сервис, который позволяет работать с адресами, обрабатывая данные по API. К моему сожалению, он не мог определять расстояние, но мог преобразовать адреса в координаты, а это уже что-то. Сам процесс преобразования адреса в координаты называется "геокодирование".

Название сервиса - "DaData". Чтобы не сочли за рекламу, я не буду рассказывать, как зарегаться на этом сервисе и поучить ключи. Загуглите, там дело 5 минут. Так вот, запрос для определения координат по адресу там стоит 15 копеек, а первые 100 запросов вообще бесплатно. Если еще и сохранять координаты адресов куда-нибудь в накопитель для повторного использования, то можно сэкономить еще больше. 

Ну, так как мы с вами инженеры - без кода никуда. Самое приятное в этом сервисе на мой взгляд - то, что предварительно не нужно как-то приводить к единому формату адреса. Сервис сам это делает где-то внутри, и очень хорошо работает с российскими адресами. 

Рассматривать пример определения расстояния будем на следующих адресах, которые случайно пришли мне в голову:

 - Москва Большая садовая 10
 - Москва Новослободская 35 (в обработке специально сделал ошибку, чтобы показать, что адрес принимается почти любой)

 

Именно в таком формате: без точек, запятых и так далее. 

Так, на сервисе мы зарегались и ключики получили. 
 

#Область ВспомогательныеФункции

//Инициализирует HTTP соединение
Функция СоединениеССервисом(АдресСервиса)
				
	SSL = Новый ЗащищенноеСоединениеOpenSSL(); 
	
	Попытка
		Соединение = Новый HTTPСоединение(АдресСервиса,,,,,,SSL);
	Исключение
		ЗаписьЖурналаРегистрации(СтрШаблон("Ошибка установки соединения с сервисом: %1", АдресСервиса), 
						УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
								
		Соединение = Неопределено;
		
	КонецПопытки;
	
	Возврат Соединение;
	
КонецФункции

// Функция - Запрос на сервис
//
// Параметры:
//  Соединение	 - HTTPСоединение - Соединение, установленное с сервисом см.СоединениеССервисом 
//  ЗапросHTTP	 - HTTPЗапрос - HTTPЗапрос с задаными (необходимыми) заголовками
//  Параметры	 - Строка - Строка в формате JSON
//  Метод		 - Строка	 - "GET" или "POST"
// 
// Возвращаемое значение:
//   - HTTPОтвет
//
Функция ЗапросНаСервис(Соединение, ЗапросHTTP, Параметры, Метод)
	
	Если Параметры <> Неопределено Тогда
		ЗапросHTTP.УстановитьТелоИзСтроки(Параметры);
	КонецЕсли;
	
	Попытка
		Если Метод = "GET" Тогда
			Ответ = Соединение.Получить(ЗапросHTTP);
		Иначе
			Ответ = Соединение.ОтправитьДляОбработки(ЗапросHTTP);
		КонецЕсли;
	Исключение
		
		ТекстОшибки = ОписаниеОшибки();
		ЗаписьЖурналаРегистрации("Отправка запроса HTTP завершилась с ошибкой", УровеньЖурналаРегистрации.Ошибка,,,ТекстОшибки);
				
		Ответ = Неопределено;
		
	КонецПопытки;
	
	Возврат Ответ;
			
КонецФункции

#КонецОбласти ВспомогательныеФункции

// Определяет координаты точки А и точки Б с помощью сервисов Dadata. Адрес необходимо писать в читаемой форме
// Например: "Москва Большая садовая 10", "Большая садовая 10 Москва" и т.п. Российские адреса воспринимает корректно
// В большинстве случаев. 
//
// Параметры:
//  ТочкаА	 - Строка	 - Адрес откуда считаем расстояние 
//  ТочкаБ	 - Строка	 - Адрес докуда считаем расстояние
// 
// Возвращаемое значение:
//   Структура: 
//		*КоординатыТочкиА - Структура - Координаты с ключами "Долгота" и "Широта"
//		*КоординатыТочкиБ - Структура - Координаты с ключами "Долгота" и "Широта"
//
Функция КоординатыССервисаDaData(ТочкаА, ТочкаБ) Экспорт
	
	КоординатыТочек  = Новый Структура("КоординатыТочкиА, КоординатыТочкиБ", "", "");
	
	СоединениеСDadata = СоединениеССервисом("cleaner.dadata.ru");
	
	//Обработаываем как нужно
	Если СоединениеСDadata = Неопределено Тогда
		Возврат КоординатыТочек;
	КонецЕсли;
		
	КоординатыТочкиА = КоординатыТочки(ТочкаА, СоединениеСDadata);	
	КоординатыТочкиБ = КоординатыТочки(ТочкаБ, СоединениеСDadata);
	
    КоординатыТочек.КоординатыТочкиА = КоординатыТочкиА; 
	КоординатыТочек.КоординатыТочкиБ = КоординатыТочкиБ;
	
	Возврат КоординатыТочек;
		
КонецФункции

//Получает координаты Долгота и Широта для одного адреса. (вспомогательная)
//
// Параметры:
//  Адрес				 - Строка - Адрес, у которого необходмо получить координаты
//  СоединениеСDadata	 - HTTPСоединение - Соединение, установленное с сервисом Dadata 
// 
// Возвращаемое значение:
//   - Структура:
//		*Долгота - Строка - Долгота
//		*Широта - Строка - Широта
//
Функция КоординатыТочки(Адрес, СоединениеСDadata)
	
	КоординатыТочки = Новый Структура("Долгота, Широта", "", "");
	
	ВашТокен = "";
	СекретныйКлюч = "";
	
	ЗапросHTTP = Новый HTTPЗапрос();
	ЗапросHTTP.АдресРесурса = "api/v1/clean/address";
	ЗапросHTTP.Заголовки.Вставить("Content-Type" , "application/json");
	ЗапросHTTP.Заголовки.Вставить("Authorization", "Token " + ВашТокен);
	ЗапросHTTP.Заголовки.Вставить("X-Secret"     , СекретныйКлюч);
	
	ПараметрыЗапроса = СтрШаблон("[ ""%1"" ]", Адрес);
	
	РезультатЗапроса = ЗапросНаСервис(СоединениеСDadata, ЗапросHTTP, ПараметрыЗапроса, "POST");
	
	//Обработайте ошибку, как вам нужно, если код состояния не 200
	Если РезультатЗапроса.КодСостояния <> 200 Тогда
		ЗаписьЖурналаРегистрации("Ошибка запроса, код состояния: " + РезультатЗапроса.КодСостояния, УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());				
	КонецЕсли;
	
	ЧтениеJSON = Новый ЧтениеJSON(); 
	
	МассивОтвета = Новый Массив;
	
	Попытка
		ЧтениеJSON.УстановитьСтроку(РезультатЗапроса.ПолучитьТелоКакСтроку());
		МассивОтвета = ПрочитатьJSON(ЧтениеJSON);
	Исключение		
		ЗаписьЖурналаРегистрации("Не удалось прочитать JSON по причине", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());				
	КонецПопытки;
		
	Если МассивОтвета.Количество() = 1 Тогда
		КоординатыТочки.Долгота = МассивОтвета[0].geo_lon; 
		КоординатыТочки.Широта = МассивОтвета[0].geo_lat;
	КонецЕсли;
	
	Возврат КоординатыТочки;
	
КонецФункции




На выхлопе мы с вами получаем координаты точек:

 

 

Все, половина дела сделана. Можно расслабиться (нет).

Теперь нам нужно куда-то зарядить эти координаты, чтобы получить расстояние. И тут, где-то на форумах я наткнулся на информацию о некоем открытом проекте - "OSRM", расшифровывается как "Open source routing machine". И вот он как раз делает то, что нам нужно. Сам ресурс находится по адресу: https://project-osrm.org/

У него достаточно полная документация. Не буду вдаваться в детали и подробности, можно посмотреть самим. Здесь я рассказываю конкретно о том, как решалась данная задача.

С этого сервиса, нам нужен метод: route. Метод вызывается "GET" запросом и в строку мы передаем - как координаты двух точек, так и с помощью чего мы планируем двигаться между ними: car , bike or foot. Как можно понять, данный сервис считает расстояние не по прямой, а именно так нам и нужно. 

Пришло время закодить эту историю:
 

// Отправляет запрос на определение расстояния между координатами
//
// Параметры:
//  КоординатыТочек	 - Структура:  
//		* КоординатыТочкиА - Структура - Координаты с ключами "Долгота" и "Широта"
//		* КоординатыТочкиБ - Структура - Координаты с ключами "Долгота" и "Широта
// 
// Возвращаемое значение:
//   - Структура:
//		*Расстояние - Число
//
Функция РасстояниеМеждуКоординатами(КоординатыТочек) Экспорт
	
	РасстояниеМеждуТочками = Новый Структура("Расстояние", 0);
	
    Соединение = СоединениеССервисом("router.project-osrm.org");
	
	ЗапросHTTP = Новый HTTPЗапрос();
	
	//Здесь прописываем строку с параметрами для get запроса.
	//https://project-osrm.org/docs/v5.24.0/api/ Тут можно посмотреть документацию. Расстояние можно строить, используя различные способы
	//передвижения "car , bike or foot". В данном случае вычислим для машины	
	ЗапросHTTP.АдресРесурса = СтрШаблон("route/v1/car/%1,%2;%3,%4", 
											КоординатыТочек.КоординатыТочкиА.Долгота,
												КоординатыТочек.КоординатыТочкиА.Широта,
													КоординатыТочек.КоординатыТочкиБ.Долгота,
														КоординатыТочек.КоординатыТочкиБ.Широта);
														
    РезультатЗапроса = ЗапросНаСервис(Соединение, ЗапросHTTP, Неопределено, "GET");
	
	//Здесь можно обработать код состояния
	Если РезультатЗапроса.КодСостояния <> 200 Тогда
		//Сделай что-то
	КонецЕсли;
		
	ЧтениеJSON = Новый ЧтениеJSON(); 	
	СтруктураОтвета = Новый Структура;
	
	Попытка
		ЧтениеJSON.УстановитьСтроку(РезультатЗапроса.ПолучитьТелоКакСтроку());
		СтруктураОтвета = ПрочитатьJSON(ЧтениеJSON);
	Исключение		
		ЗаписьЖурналаРегистрации("Не удалось прочитать JSON по причине", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());				
	КонецПопытки;
					
	Если СтруктураОтвета.Свойство("routes") Тогда
		РасстояниеМеждуТочками.Расстояние = СтруктураОтвета.routes[0].distance;
	КонецЕсли;
	
	Возврат РасстояниеМеждуТочками;
	
КонецФункции

Никаких ключей, регистраций и т.п. Единственное с чем столкнулись: сервис иногда не совсем, как бы это сказать, доступен. И ничего с этим не поделаешь, придется ждать. Но, вроде как по документации, если правильно помню, можно развернуть эту красоту локально у себя.

Ну и в завершение поделюсь функцией, которая объединяет все вышенаписанное воедино:
 

#Область Вспомогательное

//Сопоставляет массив входящих условий оператором И. 
Функция Конъюкция(Операнды)	
	Для Каждого Операнд Из Операнды Цикл
		Если Операнд <> Истина Тогда
			Возврат Ложь;
		КонецЕсли;
	КонецЦикла;
	Возврат Истина;
КонецФункции

#КонецОбласти


// Получает расстояние от точки до точки, через сервисы определения координат и геокодирования
//
// Параметры:
//  ТочкаА	 - Строка	 - Адрес отправления
//  ТочкаБ	 - Строка	 - Адрес назначения
// 
// Возвращаемое значение:
//   - Число - Расстояние между точками
//
Функция РасстояниеОтТочкиАДоТочкиБ(ТочкаА, ТочкаБ) Экспорт
	
	РезультатОбработки = Новый Структура("ЕстьОшибки, ОписаниеОшибки, РезультатЗапроса", Ложь, "", "");
	
	//Сначала получим координаты с сервиса Dadata
	КоординатыТочек = КоординатыССервисаDaData(ТочкаА, ТочкаБ);
	
	Операнды = Новый Массив;
	
	Операнды.Добавить(ЗначениеЗаполнено(КоординатыТочек.КоординатыТочкиА.Долгота));
	Операнды.Добавить(ЗначениеЗаполнено(КоординатыТочек.КоординатыТочкиА.Широта));
	Операнды.Добавить(ЗначениеЗаполнено(КоординатыТочек.КоординатыТочкиБ.Долгота));
	Операнды.Добавить(ЗначениеЗаполнено(КоординатыТочек.КоординатыТочкиБ.Широта));
	
	ЗаполненыКоординаты = Конъюкция(Операнды);
	
	//Для примера, тупо проверка на общую заполненость. Можно реализовать более детальную проверку
	Если Не ЗаполненыКоординаты Тогда
		РезультатОбработки.ЕстьОшибки = Истина;
		РезультатОбработки.ОписаниеОшибки = "Не заполнены обязательные координаты";
		
		Возврат РезультатОбработки;		
	КонецЕсли;
	
	//Теперь получаем расстояние с опенсорсного проекта http://router.project-osrm.org/
	РасстояниеМеждуТочек = РасстояниеМеждуКоординатами(КоординатыТочек);
	
	РезультатОбработки.РезультатЗапроса = РасстояниеМеждуТочек.Расстояние;
	
	Возврат РезультатОбработки;
	
КонецФункции

На выхлопе мы получаем структуру со множеством данных об адресах, а также то, что нам и было нужно - расстояние в метрах.
 

 

Если сравнивать с Яндексом, то, можно сказать, что почти "пуля в пулю". 

 

 

На этом все. Не претендую на гениальность, но вдруг кому-нибудь будут полезны эти изыскания.

Обработку также прикладываю.

Проверено на следующих конфигурациях и релизах:

  • Бухгалтерия предприятия, редакция 3.0, релизы 3.1.22.86

Обработка расстояние геокодирование определение расстояния апи

См. также

Интеграция Альфа Авто 5 / Альфа Авто 6 и AUTOCRM / Инфотек

Сайты и интернет-магазины WEB-интеграция Платформа 1С v8.3 Конфигурации 1cv8 1С:Управление торговлей 11 Автомобили, автосервисы Россия Управленческий учет Платные (руб)

Интеграционный модуль обмена между конфигурацией Альфа Авто 5 и Альфа Авто 6 и порталом AUTOCRM. Данный модуль универсален. Позволяет работать с несколькими обменами AUTOCRM разных брендов в одной информационной базе в ручном и автоматическом режиме.

36000 руб.

03.08.2020    16079    13    18    

13

Интеграция 1С — Битрикс24. Обмен задачами

Сайты и интернет-магазины Интеграция WEB-интеграция Платформа 1С v8.3 Конфигурации 1cv8 Управленческий учет Платные (руб)

Интеграция 1С и Битрикс24. Разработка имеет двухстороннюю синхронизацию 1С и Битрикс24 задачами. Решение позволяет создавать пользователя в 1С из Битрикс24 и наоборот. Данная разработка технически подходит под все основные конфигурации линейки продуктов 1С:Предприятие 8.3 (платформа начиная с 8.3.23). При приобретении предоставляется 1 месяц бесплатных обновлений разработки. Доступна демо-версия продукта с подключением Вашего Битрикс24

5040 руб.

04.05.2021    18154    10    15    

16

Автоматическая загрузка файлов (например, прайс-листов) из электронной почты, FTP, HTTP, их обработка и выгрузка на FTP (на сайт) и для других целей

Прайсы WEB-интеграция Ценообразование, анализ цен Файловый обмен (TXT, XML, DBF), FTP Автомобили, автосервисы Оптовая торговля, дистрибуция, логистика Управленческий учет Платные (руб)

Программа с заданным интервалом времени (или по ручной команде) скачивает файлы (например, прайс-листы поставщиков) из различных источников: письма электронной почты, FTP или HTTP-адреса, и сохраняет их в каталог упорядоченной структуры. При этом извлекает файлы из архивов, может переименовывать файлы и менять их формат (csv, xls, txt). Можно настроить выгрузку обработанных файлов на сайт (через FTP-подключение). Программа будет полезна компаниям, у которых есть большое количество поставщиков и/или прайс-листы поставщиков обновляются часто (необязательно прайс-листы, файлы могут быть любого назначения). Собранные таким образом актуальные версии прайс-листов можно выгрузить с помощью программы себе на сайт (или на любой FTP-сервер) или выполнить другие необходимые задачи.

25200 руб.

28.05.2015    85371    26    51    

50

Модуль для обмена "1С:Предприятие 8. УАТ. ПРОФ" с FortMonitor

WEB-интеграция 8.3.8 Конфигурации 1cv8 Автомобили, автосервисы Беларусь Украина Россия Казахстан Управленческий учет Платные (руб)

Расширение предназначено для конфигурации "1С:Предприятие 8. Управление Автотранспортом. ПРОФ". Функционал модуля: 1. Заполнение регистров сведений по подсистеме "Мониторинг", а именно: события по мониторингу, координаты по мониторингу, пробег и расход по мониторингу, текущее местоположение ТС по мониторингу 2. Заполнение путевого листа: пробег по мониторингу, время выезда/заезда, табличная часть ГСМ, места стоянок по геозонам. 3. Отчеты по данным загруженным в регистры сведений. 4. Предусмотрена автоматическая загрузка данных в фоновом режиме (условия работы данной загрузке читайте в описании товара) Модуль работает без включенной константы по настройкам мониторинга. Модуль формы предоставляется с открытым кодом, общий модуль защищен. Любой заинтересованный пользователь, имеет возможность скачать демо-версию расширения.

22656 руб.

25.05.2021    12989    33    8    

12

Интеграция с сервисом vetmanager

WEB-интеграция Платформа 1С v8.3 Бухгалтерский учет 1С:Бухгалтерия 3.0 Бытовые услуги, сервис Платные (руб)

Внешняя обработка разрабатывалась для загрузки документов из Ветменеджер в 1С: Бухгалтерия 3.0

12000 руб.

02.02.2021    16607    43    49    

23
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. rhtr 90 05.03.24 15:49 Сейчас в теме
в километрах, в метрах видимо.
2. Luis-Gomer 52 05.03.24 16:30 Сейчас в теме
3. grumagargler 724 06.03.24 02:03 Сейчас в теме
если кому надо, вот набольшая формула на плюсах для расчёта расстояния между двумя точками по прямой, может пригодиться для задач нахождения объектов в заданном радиусе:

double distance ( double latitude1, double longitude1, double latitude2, double longitude2 ) {
	double dLat = ( latitude2 - latitude1 ) * M_PI / 180.0;
	double dLon = ( longitude2 - longitude1 ) * M_PI / 180.0;
	latitude1 = latitude1 * M_PI / 180.0;
	latitude2 = latitude2 * M_PI / 180.0;
	double a = pow ( sin ( dLat / 2 ), 2 ) +
		pow ( sin ( dLon / 2 ), 2 ) *
		cos ( latitude1 ) * cos ( latitude2 );
	double rad = 6371;
	double c = 2 * asin ( sqrt ( a ) );
	return rad * c;
}
Показать
Prometeus2011; Luis-Gomer; +2 Ответить
4. Luis-Gomer 52 06.03.24 05:30 Сейчас в теме
5. swenzik 06.03.24 09:11 Сейчас в теме
(3) это по прямой, а их сервис считает по дорогам общего пользования. судя по структуре ответа они ретранслируют OSRM, в общем ничего не мешает установить его самостоятельно в виртуалке. работает неплохо, я применял его для планирования доставки заказов по курьерам.
Luis-Gomer; +1 Ответить
6. Prometeus2011 211 10.04.24 11:35 Сейчас в теме
Пару лет назад у них 'сохранять куда-нибудь туда' считалось нарушением условий бесплатного использования сервиса)
Оставьте свое сообщение