Как автоматизировать прокладку маршрута на карте яндекса

134
04 декабря 2019, 04:10

У меня есть приложение, которое реализует движение персонажей анимации вдоль заранее проложенного маршрута на карте города. Маршруты, точнее path сделаны вручную в векторном редакторе. Вся техника создания маршрутов показана здесь.

Ниже код, реализующий данную технику и анимацию:

.container { 
width:100%; 
height:100%; 
}
<div class="container"> 
<svg xmlns="http://www.w3.org/2000/svg"   xmlns:xlink="http://www.w3.org/1999/xlink" version="1"   viewBox="0 0 800 540"  > 
  <defs> 
   <path id="walk" 
   d="m343 268 34-10 50-9-33-86 22-14 7-21 8-3 13 18 34-25 47 65 22-19"  /> 
  <g id="Man" transform="translate(0,0) scale(1,-1)"> 
    <path   fill="none"> 
         <animate 
          attributeName="d" 
          begin="0.1s" 
          dur="0.3s" 
          repeatCount="indefinite" 
          values="M-3,0 0,10 3,0 M0,10 0,16 l 4,-5 M0,16 l-4,-5 M0,16 c4,4 -4,4 0,0; 
                  M 0,0 0,10 0,0 M0,10 0,16 l 0,-5 M0,16 l 0,-5 M0,16 c4,4 -4,4 0,0; 
                  M-3,0 0,10 3,0 M0,10 0,16 l 4,-5 M0,16 l-4,-5 M0,16 c4,4 -4,4 0,0"/> 
    </path>  
	  </g> 
  </defs> 
   
  <image xlink:href="https://i.stack.imgur.com/XPdWW.png" width="100%" height="100%" /> 
  <path id="train" stroke-dasharray="312" stroke-dashoffset="312" stroke-width="2" d="M443 534 426 477 415 435 397 391 375 347 350 304 334 277 317 251" style="fill:none;stroke:violet;"/> 
   
    <text 
      font-size="28" 
      font-family="Times New Roman" 
      fill="#517DA6" > 
<textPath id="result" 
xlink:href="#train"> 
<tspan dx="0" > &#128642; </tspan> 
<tspan dx="-12">  &#45; </tspan> 
<tspan dx="-15"> &#128643;</tspan> 
<tspan dx="-12">   &#45;</tspan> 
<tspan dx="-15">   &#128643; </tspan> 
<tspan dx="-12">   &#45; </tspan> 
<tspan dx="-15"> &#128643;</tspan> 
<tspan dx="-12">   &#45;</tspan> 
<tspan dx="-15">   &#128643; </tspan> 
<tspan dx="-12">   &#45; </tspan> 
<tspan dx="-15"> &#128643;</tspan> 
<tspan dx="-12">   &#45;</tspan> 
<tspan dx="-15" >   &#128642; </tspan> 
 <animate id="anTrain" 
   begin="0s;an5.end" 
   dur="12s" 
   repeatCount="1" 
   attributeName="startOffset" 
   values="-60%;45%;45%;-60%" 
   fill="freeze"/>  
</textPath> 
</text>	  
   
  <path id="walk" 
   stroke-dasharray="409" 
   stroke-dashoffset="409" 
   stroke-width="3" 
   d="m343 268 34-10 50-9-33-86 22-14 7-21 8-3 13 18 34-25 47 65 22-19" 
   style="fill:none;stroke:#B34EE9"> 
  <animate id="anPathWalk" 
   attributeName="stroke-dashoffset" 
   begin="anTrain.end-7.5s" 
   dur="2s" 
   values="409;0" 
   fill="freeze" /> 
  </path> 
   
 
  
 <use xlink:href="#Man" transform="translate(0,0) scale(1.2)" style="stroke:blue; fill:black;">  
     <animateMotion id="an2" 
       begin="anPathWalk.end" 
       dur="16s" 
       repeatCount="1"  > 
          <mpath xlink:href="#walk"/> 
     </animateMotion>	  
 
	 	  </use>	 
 
	  <use xlink:href="#Man" transform="translate(0,0) scale(1.2)" style="stroke:crimson;" >  
     <animateMotion id="an3" 
       begin="anPathWalk.end+0.5s" 
       dur="17s" repeatCount="1"  > 
             <mpath xlink:href="#walk"/> 
      </animateMotion>	  
	 </use>	 
	   <use xlink:href="#Man" 
       transform="translate(0,0) 
       scale(1)" 
       style="stroke:black;">  
     <animateMotion id="an4" 
       begin="anPathWalk.end+1s" 
       dur="13s" 
       repeatCount="1"  > 
          <mpath xlink:href="#walk"/> 
      </animateMotion>	  
	   </use>	    
	    
	   <use xlink:href="#Man" 
       transform="translate(0,0) 
       scale(0.8)" 
       style="stroke:red; 
       fill:black;">  
     <animateMotion id="an5" 
       begin="anPathWalk.end+1.5s" 
       dur="11s" 
       repeatCount="1"  > 
          <mpath xlink:href="#walk"/> 
     </animateMotion>	  
	   </use>	  
	    <use xlink:href="#Man" 
        transform="translate(0,0) 
        scale(0.8)" 
        style="stroke:black;">  
     <animateMotion id="an5" 
      begin="anPathWalk.end+1.8s" 
      dur="9.5s" repeatCount="1"  > 
         <mpath xlink:href="#walk"/> 
     </animateMotion>	  
	   </use>	 
   
</svg> 
<audio src="https://svg-art.ru/files/Time_Machine.mp3" autoplay="autoplay"></audio>  
</div>

Возможно ли максимально автоматизировать прокладку маршрута из одной точки карты в другую с помощью создания ломанных отрезков path щёлкая мышкой по контрольным точкам карты, - у разветвления улиц, при изменении направления движения.

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

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

Answer 1

Первый раз работаю с Яндекс картами, был неприятно удивлен, что они поддерживают только целочисленное значение zoom, это осложняет синхронизацию карты с svg картинкой, в которой zoom может быть дробный.

Решение сделано при помощи d3.js, сперва необходимо при помощи мыши спозиционировать карту, затем нажать кнопку и появится svg оверлей, по которому можно кликать, прокладывая маршрут. В этом режиме drag правой кнопкой мыши таскает карту, клики левой добавляют точки в маршрут, даблклики по точкам удаляют их. Для того, чтобы получить SVG результат в виде SVG кода, нажмите на кнопку

Вот как это выглядит:

PS: принимаю предложения по анимации маршрута и другие предложения.

<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU"></script> 
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.7.2/css/all.min.css" rel="stylesheet"/> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> 
 
<style> 
body, #map, #overlay, svg, #bg { 
    position: absolute; 
    margin: 0; 
    width: 100vw; 
    height: 100vh; 
    overflow: hidden; 
} 
#bg { 
    top: 0; 
    left: 0; 
    background-color: rgba(0,0,0,0.5); 
    pointer-events: all;   
} 
button, textarea { 
    pointer-events: all; 
} 
textarea { 
    position: absolute; 
    width: 400px; 
    height: 300px; 
    left: 50%; 
    top: 50%; 
    transform: translate(-50%,-50%) 
} 
#ui { 
    pointer-events: none; 
    position: absolute; 
    padding: 5px; 
} 
path { 
    fill: none; 
    stroke-width: 2.2; 
    stroke: red; 
} 
circle { 
    pointer-events: all; 
    stroke: red; 
    stroke-width: 1.5px; 
    fill: #fff; 
    fill-opacity: .2; 
    cursor: move; 
} 
.selected { 
    fill: #ff7f0e; 
    stroke: #ff7f0e; 
} 
.hidden { 
    display: none !important; 
} 
.buttons { 
    position: absolute; 
    width: 150px; 
} 
button { 
    display: inline; 
} 
</style> 
 
<div id="map"></div> 
<div id="overlay" class='hidden'> 
    <svg></svg> 
</div> 
<div id="ui"> 
    <div id="bg" class='hidden'> 
        <textarea></textarea> 
    </div> 
    <div class="buttons"> 
        <button class='fa fa-2x fa-code'></button> 
        <button class='fa fa-2x fa-route'></button> 
    </div> 
</div> 
 
<script> 
 
let lat = 60; 
let lon = 30.3; 
let zoom = 15; 
let yMaps; 
 
let points = []; 
let transform = {}; 
let dragged = null; 
let selected = points[points.length-1]; 
 
let line = d3.line().curve(d3.curveLinear); 
let svg = d3.select("svg"); 
let canvas = svg.append('g'); 
 
let path = canvas.append("path") 
    .datum(points); 
 
svg.on("mousedown", mousedown) 
    .on("mousemove", mousemove) 
 
d3.select(window) 
    .on("mouseup", mouseup) 
    .on("resize", adjustSize); 
 
d3.select('.fa-route').on('click', showOverlay); 
d3.select('.fa-code').on('click', showCode); 
 
window.oncontextmenu = () => false; 
ymaps.ready(startmap); 
svg.call(createZoom()); 
adjustSize(); 
redraw(); 
 
function showCode() { 
    toggleElement('#bg'); 
    let div = d3.select(document.createElement('div')) 
        .html(svg.node().outerHTML) 
    div.select('svg') 
        .attr('zoom', yMaps.getZoom()) 
        .attr('lat', yMaps.getCenter()[0]) 
        .attr('lon', yMaps.getCenter()[1]); 
    div.selectAll('circle').remove(); 
    d3.select('#bg textarea').html(div.node().innerHTML); 
} 
 
function showOverlay() { 
    lat = yMaps.getCenter()[0]; 
    lon = yMaps.getCenter()[1]; 
    zoom = yMaps.getZoom(); 
    d3.select(this).classed('hidden', true); 
    toggleElement('#overlay', false); 
} 
 
function toggleElement(selector, isVisible) { 
    return d3.select(selector) 
        .node() 
        .classList 
        .toggle('hidden', isVisible) 
} 
 
function applyTransform() { 
    transform = d3.event.transform; 
    canvas.attr("transform", transform); 
    onTransform(transform); 
} 
 
function createZoom() { 
    return d3.zoom() 
        .filter(() => d3.event.button === 2) 
        .scaleExtent([1, 1]) 
        .on("zoom", applyTransform); 
} 
 
function adjustSize() { 
    let w = window.innerWidth; 
    let h = window.innerHeight; 
    svg.attr("width", w).attr("height", h) 
        .attr("viewBox", `${-w/2} ${-h/2} ${w} ${h}`); 
} 
 
function redraw() { 
 
    canvas.select("path").attr("d", line); 
 
    var circle = canvas.selectAll("circle.knob") 
        .data(points, d => d); 
 
    circle.exit().remove(); 
 
    let newNodes = circle.enter() 
        .append("circle") 
        .classed('knob', true) 
        .attr("r", 1e-6) 
        .on("mousedown", d => { 
            selected = dragged = d; 
            redraw(); 
        }) 
        .on("dblclick", deletePoint) 
        .transition() 
        .duration(250) 
        .attr("r", 6.5); 
 
    circle.merge(newNodes) 
        .classed("selected", d => d === selected) 
        .attr("cx", d => d[0]) 
        .attr("cy", d => d[1]); 
 
    if (d3.event) { 
        d3.event.preventDefault(); 
        d3.event.stopPropagation(); 
    } 
} 
 
function mousedown() { 
    if (d3.event.button !== 0) 
        return; 
    points.push(selected = dragged =  
                d3.mouse(canvas.node())); 
    redraw(); 
} 
 
function mousemove() { 
    if (!dragged) 
        return; 
    let m = d3.mouse(canvas.node()); 
    dragged[0] = m[0]; 
    dragged[1] = m[1]; 
    redraw(); 
} 
 
function mouseup() { 
    if (!dragged) 
        return; 
    mousemove(); 
    dragged = null; 
} 
 
function deletePoint(d) { 
    if (!selected) 
        return; 
    let i = points.indexOf(selected); 
    points.splice(i, 1); 
    selected = points.length ?  
        points[i > 0 ? i - 1 : 0] : null; 
    redraw(); 
} 
 
function startmap() { 
    yMaps = new ymaps.Map("map", { 
        center: [lat, lon], 
        zoom: 15, 
        controls: [] 
    }); 
} 
 
function onTransform(transform) { 
    var merc = Math.cos(yMaps.getCenter()[0]*Math.PI/180); 
    var z = zoom;// + Math.log2(transform.k); 
    var s = Math.pow(2, z-1) * 256 / 180 / merc; 
    yMaps.setCenter([ 
        lat + transform.y / s, 
        lon - transform.x / s / merc, 
    ]); 
  //  yMaps.setZoom(z+1); 
} 
 
</script>

P.S. Карты могут быть и не яндекс

Answer 2

Информации, скриншотов, сниппетов достаточно много, поэтому решил оформить отдельным ответом, так как этот объем не поместится в комментариях.

Хочу остановиться на некоторых аспектах применения замечательного решения @Stranger in the Q

Как я попытался применить данное решение:

  • Выбираю на карте нужный фрагмент и делаю скриншот.

  • Добавляю узловые точки вдоль выбранного маршрута и вывожу код этого маршрута

Копирую код в отдельный файл, в котором добавлен скриншот карты с помощью команды <image>

<div class="container"> 
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"  
    xmlns:xlink="http://www.w3.org/1999/xlink" 
       width="1400" height="858" viewBox="-700 -429 1400 858" zoom="14" lat="60.02023833197165" lon="30.424723377333542">   
 
<image xlink:href="https://i.stack.imgur.com/xKUKV.png" width="100%" height="100%" /> 
<path stroke="red" fill="none" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173"></path> 
</svg>	  
</div>

  • Произошёл сдвиг карты вправо и вниз из-за отрицательных значений viewBox="-700 -429 1400 858"
  • Обнуляю viewBox="0 0 1400 858"

<div class="container"> 
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"  
    xmlns:xlink="http://www.w3.org/1999/xlink" 
       width="1400" height="858" viewBox="0 0 1400 858" zoom="14" lat="60.02023833197165" lon="30.424723377333542">   
 
<image xlink:href="https://i.stack.imgur.com/xKUKV.png" width="100%" height="100%" /> 
<path stroke="red" fill="none" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173"></path> 
</svg>	  
</div>

  • Карта встала на место, но ушел влево и вверх Path маршрута

Чтобы вернуть Path на место добавляю команду к <path transform="translate(700 429)"

<div class="container"> 
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"  
    xmlns:xlink="http://www.w3.org/1999/xlink" 
       width="1400" height="858" viewBox="0 0 1400 858" zoom="14" lat="60.02023833197165" lon="30.424723377333542">   
 
<image xlink:href="https://i.stack.imgur.com/xKUKV.png" width="100%" height="100%" /> 
<path  transform="translate(700 429)" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173"  stroke="red" stroke-width="2" fill="none"></path> 
</svg>	  
</div>

Выводы:

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

  1. Заменить отрицательные значения viewBox="-700 -429 1400 858"
    на нулевые viewBox="0 0 1400 858"

  2. Добавить команду к <path transform="translate(700 429)"

Пример анимации

по маршруту, полученному из программы @Stranger in the Q

<div class="container"> 
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"  
    xmlns:xlink="http://www.w3.org/1999/xlink" 
       width="1400" height="858" viewBox="0 0 1400 858" zoom="14" lat="60.02023833197165" lon="30.424723377333542">   
<defs> 
<path   transform="translate(700 429)" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173"  stroke="red" stroke-width="2" fill="none">  
 </path> 
  <g id="Man" transform="translate(700 429) scale(1.5,-1.5)"> 
    <path   fill="none"> 
         <animate 
          attributeName="d" 
          begin="0s" 
          dur="0.25s" 
          repeatCount="indefinite" 
          values="M-3,0 0,10 3,0 M0,10 0,16 l 4,-5 M0,16 l-4,-5 M0,16 c4,4 -4,4 0,0; 
                  M 0,0 0,10 0,0 M0,10 0,16 l 0,-5 M0,16 l 0,-5 M0,16 c4,4 -4,4 0,0; 
                  M-3,0 0,10 3,0 M0,10 0,16 l 4,-5 M0,16 l-4,-5 M0,16 c4,4 -4,4 0,0"/> 
    </path>  
	  </g> 
  </defs>	    
	    
<image xlink:href="https://i.stack.imgur.com/xKUKV.png" width="100%" height="100%" /> 
<path id="walk"  transform="translate(700 429)" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173"  stroke="red" stroke-width="2" fill="none"> 
</path> 
 
<use xlink:href="#Man"  style="stroke:blue; fill:none;">  
     <animateMotion id="an1" 
       begin="0s" 
       dur="20s" 
       repeatCount="indefinite"  > 
          <mpath xlink:href="#walk"/> 
     </animateMotion>	  
</use>	   
    <use xlink:href="#Man"  style="stroke:purple; fill:none;">  
     <animateMotion id="an2" 
       begin="an1.begin+2s" 
       dur="19s" 
       repeatCount="indefinite"  > 
          <mpath xlink:href="#walk"/> 
     </animateMotion>	  
    </use>	 
<use xlink:href="#Man"  style="stroke:green; fill:none;">  
     <animateMotion id="an3" 
       begin="an2.begin+1s" 
       dur="18s" 
       repeatCount="indefinite"  > 
          <mpath xlink:href="#walk"/> 
     </animateMotion>	  
    </use>		 
 
</svg>	  
</div>

READ ALSO
Странность неявной типизации JS

Странность неявной типизации JS

Я только начал изучать JS, до этого (как и сейчас) кодю на C#Для меня неявная типизация JS - просто ужас! Появился конкретный кейс, код успешно...

145
JS: изменить стиль display по таймеру

JS: изменить стиль display по таймеру

Я не очень сильна в JS, помогите пожалуйста решить вопросЕсть div, который нужно отображать каждые условно 5 минут

121
Всплывающее окно не отображается magnific-popup

Всплывающее окно не отображается magnific-popup

Есть несколько товаров с кнопкой заказать, при нажатии ничего не происходит, и ещё вопрос как можно передать название именно того товара...

129