Блог

Задачка с яндекс.картой

Прежде всего немного воды. В функционал Битрикса широко внедрены карты (яндекс и гугл). Они довольно просто настраиваются есть функционал, который доступен простому (неподготовленному, не программисту) пользователю позволяющий решить задачи в рамках несложных потребностей. Но вот поступает задача более широкого плана, в частности: вывести элементы относящиеся к разным категориям, т.е. как-то должны сортироваться, кроме этого элементы (точки на карте) должны фильтроваться по разным параметрам, причем этих параметров довольно много и на финише пользователь должен иметь возможность построить путь из точки А в точку В, с промежуточными точками и получить вдоль этого пути все точки, находящиеся на удаление не более 5 км от пути. Принципиально задачу можно решить как с помощью гугл-карт так и с помощью яндекс.карт. По субъективным причинам были выбраны яндекс.карты. Документация присутствует у обоих продуктов, и сравнивать оба решения я не возьмусь, т.к. не стоит здесь такая задача, кроме этого, чтобы сравнить нужно попробовать сделать туже самую или близкую реализацию на другом продукте. Как выяснилось, в процессе решения задачи, пришлось прибегнуть к помощи разработчика, т.е. буквально обратиться в тех.поддержку яндекса. И надо отдать должное, ребята ответили и существенно помогли, отдельное спасибо Георгию Атласову. Впрочем, ответы ТП носили весьма специфический характер, и прежде чем понять пришлось долго скрипеть мозгами, а некоторые вещи в ответе были просто новостью, но новые знания всегда несут большую ценность, и главное в нашем деле достижение результата.
Пожалуй, воды хватит. Задача обрисована, как выглядит решение: это симбиоз компонентов Битрикса и API-яндекс.карт. Для фильтрации использовался «умный фильтр» битрикса, массив arrFilter передается json-ом в ajax файлик, где происходит выборка элементов инфоблока и обратно возвращаем json, понятный API-яндекс.карт. Пользователь видит коллекцию объектов. Прокладка маршрута осуществляется с помощью API-яндекс.карт, а вот фильтрация объектов вдоль пути задача не тривиальная. Именно здесь пришлось прибегнуть к помощи ТП яндекса. Маршрут представляет из себя массив GPS координат и прямо скажем этот массив совсем не маленький. Простая логика подсказывает, что нужно взять каждую точку маршрута и сравнить с ней на удаленность все остальные точки. Предположим мы имеем 5000 объектов на карте и 2000 точек на пути, если не приводить цифры, то ожидание браузера для такого расчета составляет около 2-х минут, он даже успевает поинтересоваться нужно ли ждать отклик страницы. В общем от оптимизации не уйти. Перебор решается с помощью стрелочных функций JS, что лично для меня было новостью, но в конечном итоге решение изящное. Дописываю позже: стрелочные функции не работают в IE и Edge (пришлось переписать на анонимные функции, надеюсь эти браузеры умрут совсем). Количество точек на пути оптимизируется с помощью сторонних скриптов предложенных яндексом, оптимизация гибкая и настраиваемая в зависимости от потребностей в точности и желании получить быстрый результат. Измерение расстояния между точками пути и объектами карты не получилось осуществить с помощью метода getClosest, судя по всему этот метод работает с объектом включающем в себя точки, если точки между собой ни чем не связаны, нужно либо создать объект включающий точки в себя, либо воспользоваться функцией getDistance. Конечный итог красивенькая карта с разноцветными маркерами точек, плюс кластера отображающие процентное соотношение тех или иных цветов. В общем игрушка для заказчика, вопрос - нужно ли это пользователю, для меня остается открытым.
Реализация здесь: https://mytoplivo.ru/map/

Код для яндекс.карт:

    ymaps.ready(init);
    var myMap,
        arrId=[],
        wayCreated,
        objectManager,
        myPlacemark;
    function init(){
        suggestViewStart = new ymaps.SuggestView('suggeststart');
        suggestViewEnd = new ymaps.SuggestView('suggestend');
        new ymaps.SuggestView('fsuggeststart');
        new ymaps.SuggestView('fsuggestend');
        myMap = new ymaps.Map("map", {
            center: [56.726032, 65.968531],
            zoom: 4,

            }
        );
        objectManager = new ymaps.ObjectManager({
            // Чтобы метки начали кластеризоваться, выставляем опцию.
            clusterize: true,
            // ObjectManager принимает те же опции, что и кластеризатор.
            gridSize: 48,
            // Макет метки кластера pieChart.
            clusterIconLayout: 'default#pieChart',
            // Радиус диаграммы в пикселях.
            clusterIconPieChartRadius: 25,
            // Радиус центральной части макета.
            clusterIconPieChartCoreRadius: 15,
            // Ширина линий-разделителей секторов и внешней обводки диаграммы.
            clusterIconPieChartStrokeWidth: 3,
        });
        myMap.geoObjects.add(objectManager);
        $.ajax({
            url: "map.php",
            data: {filter: <?echo json_encode($arrFilter)?>},

        }).done(function(data) {
            objectManager.add(data);
                var arAdres=[],
                    countPoint=0,
                    arPoints;
                arPoints=$('#fpointVia').find('input');
                if ($('#fsuggeststart').val()!=''&&$('#fsuggestend').val()!=''){
                    arAdres[countPoint]=$('#fsuggeststart').val();
                    countPoint++;
                    for (i=0; i<arPoints.length; i++) {arAdres[countPoint]=arPoints[i].value; countPoint++;}
                    arAdres[countPoint]=$('#fsuggestend').val();
                } else return false;
                ymaps.route(arAdres).then(function (route) {
                    myMap.geoObjects.removeAll();
                    myMap.geoObjects.add(route);
                    $('#flengthpath').html((route.getLength()/1000).toFixed(2));
                    $('#fconsumption').html((((route.getLength()/1000)*$('#fconsump100').val())/100).toFixed(2));
                    var points=route.getWayPoints();
                    var routeLines=[];
                    for (i=0; i<countPoint; i++) routeLines = routeLines.concat(route.getPaths().get(i).geometry);
                    var routePoints = routeLines.map(x => x.getCoordinates()).reduce((s, x) => s.concat(x));
                    var newArr=[];
                    for (i=0; i<routePoints.length; i++) newArr[i]={x: routePoints[i][0],y: routePoints[i][1]};
                    var simpArr = simplify(newArr,0.005,true);
                    var simpRoutePoints=[];
                    for (i=0; i<simpArr.length; i++) simpRoutePoints[i]=[simpArr[i]['x'],simpArr[i]['y']];
                    var arId=[];
                    var counter=0;
                    arrId=[];
                    objectManager.setFilter(function (object) {
                        if (simpRoutePoints.some(line => ymaps.coordSystem.geo.getDistance(line , object.geometry.coordinates) < 5000)){
                            arId[counter]=object; arrId[counter]=object.id; counter++; return true;
                        } else return false;
                    });
                    myMap.geoObjects.add(objectManager);
                    myMap.setBounds([points.get(0).geometry.getCoordinates(),points.get(points.getLength()-1).geometry.getCoordinates()],{checkZoomRange: true});
                    $('#flistFuelSt').html('');
                    for (i=0; i<arId.length; i++) $('#flistFuelSt').append('<div class="adr-div"><img src="<?=SITE_TEMPLATE_PATH?>/images/oil_station.svg" class="adr-icn"><div>'+arId[i].properties.balloonContent+'</div></div>');
                    var moveList = 'Трогаемся,</br>',
                        way,
                        segments;
                    // Получаем массив путей.
                    for (var i = 0; i < route.getPaths().getLength(); i++) {
                        way = route.getPaths().get(i);
                        segments = way.getSegments();
                        for (var j = 0; j < segments.length; j++) {
                            var street = segments[j].getStreet();
                            moveList += ('Едем ' + segments[j].getHumanAction() + (street ? ' на ' + street : '') + ', проезжаем ' + segments[j].getLength() + ' м.,');
                            moveList += '</br>'
                        }
                    }
                    moveList += 'Останавливаемся.';
                    $('#flist').html('');
                    // Выводим маршрутный лист.
                    $('#flist').append(moveList);
                });

        });
    }
Код ajax:

<?php
require_once($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/main/include/prolog_before.php');
CModule::IncludeModule("iblock");
$arItems=array();
$arSelect = Array("ID", "IBLOCK_ID", "NAME", "PROPERTY_*");//IBLOCK_ID и ID обязательно должны быть указаны, см. описание arSelectFields выше
$arFilter = Array("IBLOCK_ID"=>11, "ACTIVE"=>"Y");
if (isset($_REQUEST[filter])) $arFilter=array_merge($arFilter,$_REQUEST[filter]);
$res = CIBlockElement::GetList(Array(), $arFilter, false, false, $arSelect);
while($ob = $res->GetNextElement()){
    $arFields = $ob->GetFields();
    $arProps = $ob->GetProperties();
    $arrCoor=explode(',',$arProps[GPS][VALUE]);
    $optionsPreset = '';
    if (empty($arProps[NET_TRANSOIL][VALUE])) $optionsPreset="islands#yellowFuelStationIcon";
    elseif (in_array(1,$arProps[NET_TRANSOIL][VALUE])) $optionsPreset="islands#greenFuelStationIcon";
    elseif (in_array(2,$arProps[NET_TRANSOIL][VALUE])) $optionsPreset="islands#blueFuelStationIcon";
    elseif (in_array(3,$arProps[NET_TRANSOIL][VALUE])) $optionsPreset="islands#redFuelStationIcon";
    elseif (in_array(4,$arProps[NET_TRANSOIL][VALUE])) $optionsPreset="islands#orangeFuelStationIcon";
    else $optionsPreset="islands#yellowFuelStationIcon";

    $arItems[]=array("type"=> "Feature",
                    "id"=>$arFields[ID],
                    "geometry"=> array(
                        "type"=> "Point",
                        "coordinates"=> [floatval($arrCoor[0]),floatval($arrCoor[1])],
                    ),
                    "properties"=> array(
                        "balloonContent"=> $arFields[NAME],
                        "clusterCaption"=> $arProps[OWNER][VALUE],
                        "hintContent"=> $arProps[BRAND][VALUE],
                    ),
                    "options"=>["preset"=> $optionsPreset]
   );
}
$arResult=array("type"=> "FeatureCollection","id"=> 0,
    "features"=> $arItems
);
echo json_encode($arResult);
?>