Строим маршруты на картах в 1С с помощью OpenStreetMap, OSRM и Leaflet

Публикация № 1000836

Обмен - Интеграция с WEB

Карты маршруты OpenStreetMap OSRM Leaflet

57
Краткая статья о том как вывести на карту (в 1С) маршруты с помощью OpenStreetMap, OSRM и Leaflet. По данной системе очень мало примеров, но так как OpenStreetMap является бесплатным сервисом и не требует никаких ключей и регистраций, и является довольно мощным механизмом, решил написать небольшую статью "как это сделать?". В первую очередь скажу, все намного проще, если вы используете последнюю версию платформы (8.3.14), где есть поддержка практически всех браузеров (IE 11, EDGE, Mozilla), но что делать если у нас не самая свежая платформа, где поддержка только IE 9?

Берем Leaflet API

1. Создаем текстовый общий макет:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=9"/>
    <link rel="stylesheet" href="/redirect.php?url=aHR0cHM6Ly91bnBrZy5jb20vbGVhZmxldEAxLjMuMS9kaXN0L2xlYWZsZXQuY3Nz" />
    <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
</head>
<body>
    <div id="map" class="map" style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px;"></div>
    <script type="text/javascript">
    	var map = L.map('map');
    	map.setView([&ШиротаЦентр, &ДолготаЦентр], 13);
	    L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    		attribution: '&copy; <a href="/redirect.php?url=aHR0cDovL29zbS5vcmcvY29weXJpZ2h0">OpenStreetMap</a> and &copy; <a href="/redirect.php?url=aHR0cDovL215LnNlcnZlcg==">Тут наша организация</a> contributors'
		}).addTo(map);
	&КодМаршрута
    </script>
</body>
</html>

Версия leaflet указывается актуальная, либо используйте ту, которая в примере.. В примере кода заменяются ссылки на неверные <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" />

В коде HTML присутствует 3 параметра: &ШиротаЦентр, &ДолготаЦентр, &КодМаршрута, которые нам нужно будет заменить на свои, при выводе текста в поле HTML документа. Первые два параметра указывают на центр карты для начального отображения (предлагается ставить координаты первой точки маршрута), третий - сам "код" (скрипт) маршрута (возможно сделать встроенные функции на JS, но мне было лень)).

2. Вторым шагом пишем две процедуры (в общем модуле, где хотите), первая - определение геокодирования адреса (получение координат по представлению адреса), описание сервиса доступно по ссылке Nominatim:

Функция ГеокодироватьАдрес(ТекАдрес) Экспорт

	оср = Новый HTTPСоединение("nominatim.openstreetmap.org", , , , , , Новый ЗащищенноеСоединениеOpenSSL());
	мАдрес = СтрРазделить(ТекАдрес, " ");
	Попытка
		Запрос = Новый HTTPЗапрос("/?format=json&q=" + СтрСоединить(мАдрес, "+"));
		Ответ = оср.Получить(Запрос);
	Исключение
		ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка при попытке геокодировать по OSR, адрес: " + ТекАдрес + Символы.ПС + "Описание: " + ОписаниеОшибки());
		Возврат Неопределено;
	КонецПопытки;
	
	ЧтениеJSON = Новый ЧтениеJSON;
	ЧтениеJSON.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
	Попытка
		СтруктАдреса = ПрочитатьJSON(ЧтениеJSON);
	Исключение
		ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка при попытке геокодировать по OSR, адрес: " + ТекАдрес + Символы.ПС + "Описание: " + ОписаниеОшибки());
		Возврат Неопределено;
	КонецПопытки;
	Если ТипЗнч(СтруктАдреса) = Тип("Массив") Тогда
		Если СтруктАдреса.Количество() = 0 Тогда
			ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Для адреса: """ + ТекАдрес + """ не обнаружено ни одной геопозиции");
			Возврат Неопределено;
		ИначеЕсли СтруктАдреса.Количество() > 1 Тогда
			ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Для адреса: """ + ТекАдрес + """ обнаружено больше одной геопозиции");
		КонецЕсли;
		Адрес = СтруктАдреса[0];
		Возврат Новый Структура("Адрес,Широта,Долгота", Адрес.display_name, Адрес.lat, Адрес.lon) 
	КонецЕсли;

КонецФункции

немного комментария к тексту кода: адрес указывать (область, город улица, номер дома) через пробелы. При геокодировании возможен возврат нескольких координат, в данном случае берется первый.

вторая - получение точек маршрута от routing machine

Функция ПолучитьМаршрут(МассивАдресов)

	оср = Новый HTTPСоединение("router.project-osrm.org", , , , , , Новый ЗащищенноеСоединениеOpenSSL());
	мТочки = Новый Массив;
	Для каждого Точка Из МассивАдресов Цикл
		мТочки.Добавить(Формат(Точка.Долгота, "ЧЦ=15; ЧДЦ=12; ЧРД=.; ЧГ=0") + "," + Формат(Точка.Широта, "ЧЦ=15; ЧДЦ=12; ЧРД=.; ЧГ=0"));
	КонецЦикла;
	Попытка
		Запрос = Новый HTTPЗапрос("/route/v1/driving/" + СтрСоединить(мТочки, ";") + "?overview=false&alternatives=true&steps=true&hints=;");
		Ответ = оср.Получить(Запрос);
	Исключение
		Сообщить("Ошибка при попытке маршутизации по OSR, адрес" + Символы.ПС + "Описание: " + ОписаниеОшибки());
		Возврат Неопределено;
	КонецПопытки;
	ЧтениеJSON = Новый ЧтениеJSON;
	ЧтениеJSON.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
	Попытка
		СтруктМаршрута = ПрочитатьJSON(ЧтениеJSON);
	Исключение
		Сообщить("Ошибка при попытке маршутизации по OSR, адрес:" + Символы.ПС + ОписаниеОшибки());
		Возврат Неопределено;
	КонецПопытки;
	Если СтруктМаршрута.Свойство("code") Тогда
		Если НРег(СтруктМаршрута.code) = "ok" Тогда
			Результат = Новый Структура("Маршрут,АльтМаршрут"); 
			Если СтруктМаршрута.routes.Количество() Тогда
				Результат.Маршрут = Новый Массив;
				Для каждого Нога Из СтруктМаршрута.routes[0].legs Цикл
					Для каждого ШагМаршрута Из Нога.steps Цикл
						Для каждого Секция Из ШагМаршрута.intersections Цикл
							Результат.Маршрут.Добавить(Новый Структура("Долгота,Широта", Секция.location[0], Секция.location[1]));
						КонецЦикла;
					КонецЦикла;
				КонецЦикла;
				Если СтруктМаршрута.routes.Количество() > 1 Тогда
					Результат.АльтМаршрут = Новый Массив;
					Для каждого Нога Из СтруктМаршрута.routes[0].legs Цикл
						Для каждого ШагМаршрута Из Нога.steps Цикл
							Для каждого Секция Из ШагМаршрута.intersections Цикл
								Результат.АльтМаршрут.Добавить(Новый Структура("Долгота,Широта", Секция.location[0], Секция.location[1]));
							КонецЦикла;
						КонецЦикла;
					КонецЦикла;
				КонецЕсли;
			КонецЕсли;
			Возврат Результат;
		Иначе
			Возврат Неопределено;
		КонецЕсли;
	ИначеЕсли СтруктМаршрута.Свойство("message") Тогда
		Сообщить("Ошибка при попытке маршутизации по OSR, адрес:" + Символы.ПС + СтруктМаршрута.message);
		Возврат Неопределено;
	КонецЕсли;

КонецФункции // ПолучитьМаршрут()

МассивАдресов - передаем сюда наш массив, полученный в первой процедуре. Для более оптимальной работы, предлагаю хранить координаты в контактной информации объекта (напр. контрагента), получать их на стадии создания адреса. Да забыл, здесь мы получаем не только основной маршрут но и альтернативный (массив АльтМаршрут в структуре результата), его мы нарисуем другим цветом.

3. Теперь нам осталось только создать на основании наших точек сам JS код, который нарисует на полилинию на карте,  и поставит точки начала и конца маршрута (ов).

Функция СформироватьJavaScriptМаршрута(МассивАдресов, Маршрут = Неопределено, АльтМаршрут = Неопределено) Экспорт

	КодМаршрута = "";
	Для каждого Адрес Из МассивАдресов Цикл
		КодМаршрута = КодМаршрута + "L.marker(["+Формат(Адрес.Широта, "ЧЦ=16; ЧДЦ=12; ЧРД=.; ЧГ=0")+", "+Формат(Адрес.Долгота, "ЧЦ=16; ЧДЦ=12; ЧРД=.; ЧГ=0")+"], {title: '" + Адрес.Адрес + "'}).addTo(map);
		|	";
	КонецЦикла;
	Если НЕ Маршрут = Неопределено Тогда
		мМаршрут = Новый Массив;
		Для каждого ТочкаМаршрута Из Маршрут Цикл
			мМаршрут.Добавить("[" + Формат(ТочкаМаршрута.Широта, "ЧЦ=15; ЧДЦ=12; ЧРД=.; ЧГ=0")+", "+Формат(ТочкаМаршрута.Долгота, "ЧЦ=15; ЧДЦ=12; ЧРД=.; ЧГ=0") + "]");
		КонецЦикла;
		КодМаршрута = КодМаршрута + "var latlngs = [" + СтрСоединить(мМаршрут, ",") + "];
		|	var polyline = L.polyline(latlngs, {color: 'red', weight: 5, opacity: 0.7}).addTo(map);
		|	map.fitBounds(polyline.getBounds());
		|	";
	КонецЕсли;
	Если НЕ АльтМаршрут = Неопределено Тогда
		мМаршрут.Очистить();
		Для каждого ТочкаМаршрута Из АльтМаршрут Цикл
			мМаршрут.Добавить("[" + Формат(ТочкаМаршрута.Широта, "ЧЦ=15; ЧДЦ=12; ЧРД=.; ЧГ=0")+", "+Формат(ТочкаМаршрута.Долгота, "ЧЦ=15; ЧДЦ=12; ЧРД=.; ЧГ=0") + "]");
		КонецЦикла;
		КодМаршрута = КодМаршрута + "var altlatlngs = [" + СтрСоединить(мМаршрут, ",") + "];
			|	var altpolyline = L.polyline(altlatlngs, {color: 'blue', weight: 5, opacity: 0.7}).addTo(map);
			|	map.fitBounds(altpolyline.getBounds());
			|	";
	КонецЕсли;
	
	Возврат КодМаршрута;

КонецФункции // СформироватьJavaScriptМаршрута()

Первым параметром передаем наш массив (с первой функции), вторым и третьим передаем массивы маршрутов со второй функции

Вот и все, теперь осталось подменить строки параметров в тексте HTML и засунуть его в наше поле карты.

Кому лень разбираться, предлагается тестовая обработка. Не судите строго ))

Тестовая обработка написана под УФ (тестировалось на платформе 8.3.11)

З.Ы. в построении маршрута обнаружился некий недочет - изгибы дорог и повороты (некоторые) почему то не учитываются, надо разбираться...

 

Обновлено 12.02.19 г.

Для более точной прорисовки изгибов и внутриквартальных поворотов, изменяем запрос к ресурсу routing machine:

"?overview=full&alternatives=true&steps=true&geometries=geojson&hints=;"

и немного дорабатываем процедуру обработки ответа:

Функция ПолучитьМаршрут(МассивАдресов)

	оср = Новый HTTPСоединение("router.project-osrm.org", , , , , , Новый ЗащищенноеСоединениеOpenSSL());
	мТочки = Новый Массив;
	Для каждого Точка Из МассивАдресов Цикл
		мТочки.Добавить(Формат(Точка.Долгота, "ЧЦ=15; ЧДЦ=12; ЧРД=.; ЧГ=0") + "," + Формат(Точка.Широта, "ЧЦ=15; ЧДЦ=12; ЧРД=.; ЧГ=0"));
	КонецЦикла;
	Попытка
		Запрос = Новый HTTPЗапрос("/route/v1/driving/" + СтрСоединить(мТочки, ";") + "?overview=full&alternatives=true&steps=true&geometries=geojson&hints=;");
		Ответ = оср.Получить(Запрос);
	Исключение
		Сообщить("Ошибка при попытке маршутизации по OSR, адрес" + Символы.ПС + "Описание: " + ОписаниеОшибки());
		Возврат Неопределено;
	КонецПопытки;
	ЧтениеJSON = Новый ЧтениеJSON;
	ЧтениеJSON.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
	Попытка
		СтруктМаршрута = ПрочитатьJSON(ЧтениеJSON);
	Исключение
		Сообщить("Ошибка при попытке маршутизации по OSR, адрес:" + Символы.ПС + ОписаниеОшибки());
		Возврат Неопределено;
	КонецПопытки;
	Если СтруктМаршрута.Свойство("code") Тогда
		Если НРег(СтруктМаршрута.code) = "ok" Тогда
			Результат = Новый Структура("Маршрут,АльтМаршрут"); 
			Если СтруктМаршрута.routes.Количество() Тогда
				Результат.Маршрут = Новый Массив;
				ОсновнойМаршрут = СтруктМаршрута.routes[0];
				Если ОсновнойМаршрут.Свойство("geometry") И ОсновнойМаршрут.geometry.coordinates.Количество() Тогда
					Для каждого Координаты Из ОсновнойМаршрут.geometry.coordinates Цикл
						Результат.Маршрут.Добавить(Новый Структура("Долгота,Широта", Координаты[0], Координаты[1]));
					КонецЦикла;
				Иначе
					Для каждого Нога Из ОсновнойМаршрут.legs Цикл
						Для каждого ШагМаршрута Из Нога.steps Цикл
							Для каждого Секция Из ШагМаршрута.intersections Цикл
								Результат.Маршрут.Добавить(Новый Структура("Долгота,Широта", Секция.location[0], Секция.location[1]));
							КонецЦикла;
						КонецЦикла;
					КонецЦикла;
				КонецЕсли;
				Если СтруктМаршрута.routes.Количество() > 1 Тогда
					Результат.АльтМаршрут = Новый Массив;
					Альтернативный = СтруктМаршрута.routes[1];
					Если Альтернативный.Свойство("geometry") И Альтернативный.geometry.coordinates.Количество() Тогда
						Для каждого Координаты Из Альтернативный.geometry.coordinates Цикл
							Результат.АльтМаршрут.Добавить(Новый Структура("Долгота,Широта", Координаты[0], Координаты[1]));
						КонецЦикла;
					Иначе
						Для каждого Нога Из Альтернативный.legs Цикл
							Для каждого ШагМаршрута Из Нога.steps Цикл
								Для каждого Секция Из ШагМаршрута.intersections Цикл
									Результат.АльтМаршрут.Добавить(Новый Структура("Долгота,Широта", Секция.location[0], Секция.location[1]));
								КонецЦикла;
							КонецЦикла;
						КонецЦикла;
					КонецЕсли;
				КонецЕсли;
			КонецЕсли;
			Возврат Результат;
		Иначе
			Возврат Неопределено;
		КонецЕсли;
	ИначеЕсли СтруктМаршрута.Свойство("message") Тогда
		Сообщить("Ошибка при попытке маршутизации по OSR, адрес:" + Символы.ПС + СтруктМаршрута.message);
		Возврат Неопределено;
	КонецЕсли;

КонецФункции // ПолучитьМаршрут()

Обновлена обработка...

57

Скачать файлы

Наименование Файл Версия Размер
Обработка "Работа с картами OSR"
.epf 11,99Kb
12.02.19
12
.epf 1.1 11,99Kb 12 Скачать

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. mi1man 238 12.02.19 11:51 Сейчас в теме
".. З.Ы. в построении маршрута обнаружился некий недочет - изгибы дорог и повороты (некоторые) почему то не учитываются, надо разбираться..."

используйте rs_overview = "full"
2. Ditron 73 12.02.19 12:42 Сейчас в теме
3. Ditron 73 12.02.19 14:27 Сейчас в теме
(1) нет, не работает, не так надо, а вот так: &geometries=geojson (в запросе) тогда появится структура geometry а в ней массив с более точными координатами... обновлю статью и обработку... спасибо за пинок ))
4. pahalovo 13.02.19 09:11 Сейчас в теме
(3) А полученный geoJson от OSRM можно отправить в leaflet.js L.geoJSON(data). Очень удобно.
5. Ditron 73 13.02.19 11:19 Сейчас в теме
(4) да можно и так, там вообще можно много чего, да только некогда разбираться ))
8. qwed557 28 17.02.19 12:47 Сейчас в теме
Координаты получил, в списке адресов 2 точки, при нажатии построить маршрут выходит сообщение
Ошибка при попытке маршутизации по OSR, адрес:
Too Many Requests
9. Ditron 73 17.02.19 21:57 Сейчас в теме
(8)А перевести на русский не судьба? Сервис бесплатный, соответственно нагрузка большая, переводится как "Слишком много запросов", надеюсь дальше объяснять не надо? П.С. это я вывел как ошибку, а вообще это ответ сервера сервиса...
10. qwed557 28 18.02.19 15:29 Сейчас в теме
(9)да нах такой сервис, в течении дня ни разу не построил маршрут, сколько не бы не тестил. и что то он бесплатный, пользы от него 0.
11. Ditron 73 18.02.19 15:47 Сейчас в теме
(10)ну извините, не мой сервис, хотя у меня у двух клиентов подсистема сделана маршрутизации, не жалуются, бывает конечно но не напрягает...
12. Ditron 73 18.02.19 15:50 Сейчас в теме
(10)там на routing machine есть несколько вариантов получения точек для построения полилинии, попробуйте получать в виде таблицы http://project-osrm.org/docs/v5.7.0/api/?language=cURL#table-service
13. Ditron 73 18.02.19 15:52 Сейчас в теме
Оставьте свое сообщение