Задача: добавить инструментарий на Яндекс карты для выделения произвольной области.
Яндекс карты обладают огромным количеством возможностей и хорошей документацией. Но, когда необходимо сделать что-то не стандартное, документация не всегда может помочь. Одной из таких задач была реализация для одного из наших клиентов в рамках разработки системы управления клиентами aveCRM поиск объектов на карте, попадающих в произвольную область.
Рассмотрим на примере добавление дополнительной панели инструментов на карту.
html разметка блока карты
<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script> <div class="map_container"> <div class="mapMenu"> <div class="title">Выделение области:</div> <button class="mapMenuIcon hidden"> <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 25 25"> <g fill="none" fill-rule="evenodd" stroke="currentColor"> <path stroke-width="2" d="M17 2l5 11-9 9-11-6L4 4z"></path> <path fill="#FFF" d="M3.3 5.348c.716.414 1.634.168 2.048-.55.414-.716.168-1.634-.55-2.048-.716-.414-1.634-.168-2.048.55-.414.716-.168 1.634.55 2.048zm13-2c.716.414 1.634.168 2.048-.55.414-.716.168-1.634-.55-2.048-.716-.414-1.634-.168-2.048.55-.414.716-.168 1.634.55 2.048zm5 11c.716.414 1.634.168 2.048-.55.414-.716.168-1.634-.55-2.048-.716-.414-1.634-.168-2.048.55-.414.716-.168 1.634.55 2.048zm-9 9c.716.414 1.634.168 2.048-.55.414-.716.168-1.634-.55-2.048-.716-.414-1.634-.168-2.048.55-.414.716-.168 1.634.55 2.048zm-11-6c.716.414 1.634.168 2.048-.55.414-.716.168-1.634-.55-2.048-.716-.414-1.634-.168-2.048.55-.414.716-.168 1.634.55 2.048z"></path> </g> </svg> </button> <button id="draw_line" class="mapMenuIcon"> <svg xmlns="http://www.w3.org/2000/svg" width="28" height="25" viewBox="0 0 28 25"> <g fill="none" fill-rule="evenodd" stroke="currentColor" transform="rotate(90 12.5 14)"> <path stroke-width="2" d="M5.9 3.273c2.466-2.195 13.518-5.957 14.01-.302.156 1.79-1.874 3.436-1.475 5.187.393 1.723 3.61 2.214 3.565 3.98-.042 1.61-3.015 1.694-3.75 3.135-1.863 3.658 4.84 5.727 2.15 8.984-3.06 3.714-20.53-5.01-20.398-7.9.164-3.59 4.257-1.693 6.206-3.797 2.53-2.73-8.662-6.157-2.704-8.322L5.9 3.273z"></path> <circle cx="8.5" cy="22.5" r="1.5" fill="#FFF"></circle> </g> </svg> </button> <button class="mapMenuIcon hidden"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <g fill="none" fill-rule="evenodd" stroke="currentColor" transform="translate(1 1)"> <path stroke-width="2" d="M11 22c6.075 0 11-4.925 11-11S17.075 0 11 0C9.179 0 7.46.443 5.948 1.226A11 11 0 0 0 0 11c0 6.075 4.925 11 11 11z"></path> <circle cx="18.5" cy="18.5" r="1.5" fill="#FFF"></circle> </g> </svg> </button> </div> <div id="map" style="width: 100%; height: 800px;"></div> <canvas id="draw-canvas" style="position: absolute; left: 0; top: 0; display: none;"></canvas> </div>
Стили
.map_container { position:relative; } .mapMenu { position: absolute; z-index: 1; top: 20px; left: calc(50% - 130px); padding: 0 15px; display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; height: 46px; background-color: #fff; border-radius: 24px; -webkit-box-shadow: 0 2px 8px 0 rgba(0,0,0,.25); box-shadow: 0 2px 8px 0 rgba(0,0,0,.25); -webkit-box-pack: center; -webkit-justify-content: center; -ms-flex-pack: center; justify-content: center; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; } .mapMenu .title { padding: 0 10px; font-size: 14px; line-height: 1.57; color: #505152; } .mapMenu .mapMenuIcon { padding: 5px; width: 40px; height: 35px; cursor: pointer; color: #7b7b7b; background: 0 0; border: 0; outline-style: none; } .mapMenu .mapMenuIcon:hover { color: #000 } .mapMenu .mapMenuIcon.active, .mapMenu .mapMenuIcon.active:hover { color: #147de8 }
Скрипт
$(function () { var polygonOptions = { strokeColor: '#1679e7', fillColor: '#a2c9d8', interactivityModel: 'default#transparent', strokeWidth: 4, opacity: 0.7 }; var canvasOptions = { strokeStyle: '#1679e7', lineWidth: 4, opacity: 0.7 }; ymaps.ready(['Map', 'Polygon']).then(function () { var map = new ymaps.Map('map', { center: [59.91807704072416, 30.30557799999997], zoom: 12 }); map.behaviors.disable('scrollZoom'); var polygon = null; if ($("input[name=in_poligon]").val() != '') { var point = $("input[name=in_poligon]").val(); point = point.split(','); var point_array = []; point.forEach(element => { el = element.split('_') point_array.push([el[1], el[0]]); }); // Создаем многоугольник, используя класс GeoObject. var myGeoObject = new ymaps.GeoObject({ // Описываем геометрию геообъекта. geometry: { // Тип геометрии - "Многоугольник". type: "Polygon", // Указываем координаты вершин многоугольника. coordinates: [point_array], // Задаем правило заливки внутренних контуров по алгоритму "nonZero". fillRule: "nonZero" }, // Описываем свойства геообъекта. properties: { // Содержимое балуна. balloonContent: "Многоугольник" } }, { // Описываем опции геообъекта. // Цвет заливки. fillColor: '#a2c9d8', // Цвет обводки. strokeColor: '#1679e7', // Общая прозрачность (как для заливки, так и для обводки). opacity: 0.7, // Ширина обводки. strokeWidth: 4 // Стиль обводки. }); // Добавляем многоугольник на карту. map.geoObjects.add(myGeoObject); } var drawButton = document.querySelector('#draw_line'); drawButton.onclick = function () { drawButton.disabled = true; drawLineOverMap(map) .then(function (coordinates) { // Переводим координаты из 0..1 в географические. var bounds = map.getBounds(); coordinates = coordinates.map(function (x) { return [ // Широта (latitude). // Y переворачивается, т.к. на canvas'е он направлен вниз. bounds[0][0] + (1 - x[1]) * (bounds[1][0] - bounds[0][0]), // Долгота (longitude). bounds[0][1] + x[0] * (bounds[1][1] - bounds[0][1]), ]; }); // Тут надо симплифицировать линию. // Для простоты я оставляю только каждую третью координату. coordinates = coordinates.filter(function (_, index) { return index % 3 === 0; }); // Удаляем старый полигон. if (polygon) { map.geoObjects.remove(polygon); } // Создаем новый полигон polygon = new ymaps.Polygon([coordinates], {}, polygonOptions); map.geoObjects.add(polygon); drawButton.disabled = false; // На этом этапе переменная coordinates содержит координаты всех точек фигуры $(".mapMenu button").removeClass("active"); }); }; }); function drawLineOverMap(map) { var canvas = document.querySelector('#draw-canvas'); var ctx2d = canvas.getContext('2d'); var drawing = false; var coordinates = []; // Задаем размеры канвасу как у карты. var rect = map.container.getParentElement().getBoundingClientRect(); canvas.style.width = rect.width + 'px'; canvas.style.height = rect.height + 'px'; canvas.width = rect.width; canvas.height = rect.height; // Применяем стили. ctx2d.strokeStyle = canvasOptions.strokeStyle; ctx2d.lineWidth = canvasOptions.lineWidth; canvas.style.opacity = canvasOptions.opacity; ctx2d.clearRect(0, 0, canvas.width, canvas.height); // Показываем канвас. Он будет сверху карты из-за position: absolute. canvas.style.display = 'block'; canvas.onmousedown = function (e) { // При нажатии мыши запоминаем, что мы начали рисовать и координаты. drawing = true; coordinates.push([e.offsetX, e.offsetY]); }; canvas.onmousemove = function (e) { // При движении мыши запоминаем координаты и рисуем линию. if (drawing) { var last = coordinates[coordinates.length - 1]; ctx2d.beginPath(); ctx2d.moveTo(last[0], last[1]); ctx2d.lineTo(e.offsetX, e.offsetY); ctx2d.stroke(); coordinates.push([e.offsetX, e.offsetY]); } }; return new Promise(function (resolve) { // При отпускании мыши запоминаем координаты и скрываем канвас. canvas.onmouseup = function (e) { coordinates.push([e.offsetX, e.offsetY]); canvas.style.display = 'none'; drawing = false; coordinates = coordinates.map(function (x) { return [x[0] / canvas.width, x[1] / canvas.height]; }); resolve(coordinates); }; }); } $(".mapMenu button").on('click', function () { $(".mapMenu button").removeClass("active"); $(this).addClass("active"); }); });